From 1b9fc0ae1de69c218f752e1aab76ef4a5194adde Mon Sep 17 00:00:00 2001 From: connorroy Date: Sun, 23 Mar 2025 12:59:17 +0000 Subject: [PATCH] Initial Commit --- src/main.py | 238 +++++++++++++++++++++++++++++++++++++++++++++++ src/pytevolve.py | 235 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+) create mode 100644 src/main.py create mode 100644 src/pytevolve.py diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..658da66 --- /dev/null +++ b/src/main.py @@ -0,0 +1,238 @@ +import time +from pytevolve import Tevolve +import paho.mqtt.client as mqtt +import threading +import json + +broker_address = "192.168.0.100" + + +def on_disconnect(client, userdata, rc): + + client.connect(broker_address) + + +def on_connect(client, userdata, flags, rc): + + try: + + # client.username_pw_set("admin", "bK2F2ZPjyyngmsN6R32s") + # client.username_pw_set(username="admin", password ="bK2F2ZPjyyngmsN6R32s") + client.connect(broker_address) + except: + + time.sleep(10) + + client.connect(client) + + +def on_message(client, userdata, message): + new_topic = str(message.topic) + + if new_topic == "hallway/heater/settemp": + tevolve.set_temperature("3", str(message.payload.decode("utf-8"))) + # Assume set temp success (Prevent debounce) + client.publish("hallway/heater/currentsettemp", payload=str(message.payload.decode("utf-8")), qos=0, retain=False) + + if new_topic == "hallway/heater/setmode": + if str(message.payload.decode("utf-8")) == "heat": + mode = "manual" + # client.publish("hallway/heater/idleaction", payload="heating", qos=0, retain=False) + client.publish("hallway/heater/currentmode", payload="heat", qos=0, retain=False) + else: + mode = "off" + client.publish("hallway/heater/currentmode", payload="off", qos=0, retain=False) + client.publish("hallway/heater/idleaction", payload="off", qos=0, retain=False) + + + + if mode == "manual": + if float(Tevolve.hallway_current_temp) > float(Tevolve.hallway_set_temp): + client.publish("hallway/heater/idleaction", payload="idle", qos=0, retain=False) + else: + client.publish("hallway/heater/idleaction", payload="heating", qos=0, retain=False) + + + Tevolve.setmode = mode + tevolve.set_mode("3") + + + if new_topic == "livingroom/heater/settemp": + tevolve.set_temperature("2", str(message.payload.decode("utf-8"))) + # Assume set temp success (Prevent debounce) + client.publish("livingroom/heater/currentsettemp", payload=str(message.payload.decode("utf-8")), qos=0, + retain=False) + + if new_topic == "livingroom/heater/setmode": + # print(str(message.payload.decode("utf-8"))) + + if str(message.payload.decode("utf-8")) == "heat": + mode = "manual" + client.publish("livingroom/heater/idleaction", payload="heating", qos=0, retain=False) + client.publish("livingroom/heater/currentmode", payload="heat", qos=0, retain=False) + else: + mode = "off" + client.publish("livingroom/heater/currentmode", payload="off", qos=0, retain=False) + client.publish("livingroom/heater/idleaction", payload="off", qos=0, retain=False) + + + if mode == "manual": + if float(Tevolve.livingroom_current_temp) > float(Tevolve.livingroom_set_temp): + client.publish("livingroom/heater/idleaction", payload="idle", qos=0, retain=False) + else: + client.publish("livingroom/heater/idleaction", payload="heating", qos=0, retain=False) + + Tevolve.setmode = mode + tevolve.set_mode("2") + + + + +if __name__ == '__main__': + + tevolve = Tevolve() + + tevolve.post_websocket() + time.sleep(1) + + + t = threading.Thread(target=tevolve.create_websocket, daemon=True, args=()) + t.start() + + client = mqtt.Client("ryan-heating") + + # client.username_pw_set("admin", "bK2F2ZPjyyngmsN6R32s") + + client.connect(broker_address) + client.on_message = on_message + client.on_disconnect = on_disconnect + client.loop_start() + + + client.subscribe("hallway/heater/settemp") + client.subscribe("hallway/heater/setmode") + + client.subscribe("livingroom/heater/settemp") + client.subscribe("livingroom/heater/setmode") + + + + livingroom_status = tevolve.get_status("2") + hallway_status = tevolve.get_status("3") + + + Tevolve.hallway_active = hallway_status["active"] + Tevolve.hallway_mode = hallway_status["mode"] + Tevolve.hallway_set_temp = hallway_status["stemp"] + Tevolve.hallway_current_temp = hallway_status["mtemp"] + + + # Tevolve.livingroom_active = livingroom_status["active"] + # Tevolve.livingroom_mode = livingroom_status["mode"] + # Tevolve.livingroom_set_temp = livingroom_status["stemp"] + # Tevolve.livingroom_current_temp = livingroom_status["mtemp"] + + + if Tevolve.livingroom_mode == "manual": + Tevolve.livingroom_mode = "heat" + if Tevolve.hallway_mode == "manual": + Tevolve.hallway_mode = "heat" + + client.publish("hallway/heater/currenttemp", payload=Tevolve.hallway_current_temp, qos=0, retain=False) + client.publish("livingroom/heater/currenttemp", payload=Tevolve.livingroom_current_temp, qos=0, retain=False) + + client.publish("hallway/heater/currentmode", payload=Tevolve.hallway_mode, qos=0, retain=False) + client.publish("livingroom/heater/currentmode", payload=Tevolve.livingroom_mode, qos=0, retain=False) + + client.publish("hallway/heater/currentsettemp", payload=Tevolve.hallway_set_temp, qos=0, retain=False) + client.publish("livingroom/heater/currentsettemp", payload=Tevolve.livingroom_set_temp, qos=0, retain=False) + + # Setup initial hallway heater values + if Tevolve.hallway_mode == "off": + client.publish("hallway/heater/setmode", payload="off", qos=0, retain=False) + # client.publish("hallway/heater/idleaction", payload="idle", qos=0, retain=False) + + if Tevolve.hallway_mode == "manual": + if float(Tevolve.hallway_current_temp) >= float(Tevolve.hallway_set_temp): + client.publish("hallway/heater/idleaction", payload="idle", qos=0, retain=False) + if float(Tevolve.hallway_set_temp) > float(Tevolve.hallway_current_temp): + client.publish("hallway/heater/idleaction", payload="heating", qos=0, retain=False) + + + # Setup initial living room heater values + if Tevolve.livingroom_mode == "off": + client.publish("livingroom/heater/setmode", payload="off", qos=0, retain=False) + + if Tevolve.livingroom_mode == "manual": + if float(Tevolve.livingroom_current_temp) >= float(Tevolve.livingroom_set_temp): + client.publish("livingroom/heater/idleaction", payload="idle", qos=0, retain=False) + if float(Tevolve.livingroom_set_temp) > float(Tevolve.livingroom_current_temp): + client.publish("livignroom/heater/idleaction", payload="heating", qos=0, retain=False) + + + while True: + + if Tevolve.websocket_message != "": + + if "/api/v2" in Tevolve.websocket_message: + + print(Tevolve.websocket_message[30:-1]) + m = json.loads(Tevolve.websocket_message[30:-1]) + # print(m) + + if m["path"] == "/htr/3/status": + + Tevolve.hallway_active = m["body"]["active"] + Tevolve.hallway_mode = m["body"]["mode"] + Tevolve.hallway_set_temp = m["body"]["stemp"] + Tevolve.hallway_current_temp = m["body"]["mtemp"] + + + + if Tevolve.hallway_mode == "off": + client.publish("hallway/heater/setmode", payload="off", qos=0, retain=False) + # client.publish("hallway/heater/idleaction", payload="idle", qos=0, retain=False) + + if Tevolve.hallway_mode == "manual": + + if float(Tevolve.hallway_set_temp) > float(Tevolve.hallway_current_temp): + client.publish("hallway/heater/idleaction", payload="heating", qos=0, retain=False) + client.publish("hallway/heater/currentmode", payload="heat", qos=0, retain=False) + + if float(Tevolve.hallway_set_temp) <= float(Tevolve.hallway_current_temp): + client.publish("hallway/heater/idleaction", payload="idle", qos=0, retain=False) + client.publish("hallway/heater/currentmode", payload="heat", qos=0, retain=False) + + client.publish("hallway/heater/currentsettemp", payload=m["body"]["stemp"], qos=0, retain=False) + # Publish hallway current temperature + client.publish("hallway/heater/currenttemp", payload=m["body"]["mtemp"], qos=0, retain=False) + + + + if m["path"] == "/htr/2/status": + + Tevolve.livingroom_active = m["body"]["active"] + Tevolve.livingroom_mode = m["body"]["mode"] + Tevolve.livingroom_set_temp = m["body"]["stemp"] + Tevolve.livingroom_current_temp = m["body"]["mtemp"] + + if Tevolve.livingroom_mode == "off": + client.publish("livingroom/heater/setmode", payload="off", qos=0, retain=False) + + if Tevolve.livingroom_mode == "manual": + if float(Tevolve.livingroom_set_temp) > float(Tevolve.livingroom_current_temp): + client.publish("livingroom/heater/idleaction", payload="heating", qos=0, retain=False) + client.publish("livingroom/heater/currentmode", payload="heat", qos=0, retain=False) + + if float(Tevolve.livingroom_set_temp) <= float(Tevolve.livingroom_current_temp): + client.publish("livingroom/heater/idleaction", payload="idle", qos=0, retain=False) + client.publish("livingroom/heater/currentmode", payload="heat", qos=0, retain=False) + + client.publish("livingroom/heater/currentsettemp", payload=m["body"]["stemp"], qos=0, retain=False) + # Publish hallway current temperature + client.publish("livingroom/heater/currenttemp", payload=m["body"]["mtemp"], qos=0, retain=False) + + Tevolve.websocket_message = "" + + time.sleep(1) + diff --git a/src/pytevolve.py b/src/pytevolve.py new file mode 100644 index 0000000..1c68640 --- /dev/null +++ b/src/pytevolve.py @@ -0,0 +1,235 @@ +import requests +import json +import threading +import websocket +import time + +data = "username=1202283%40uad.ac.uk&password=24e76d8e4&grant_type=password" +url = "https://api-tevolve.termoweb.net/client/token" + +headers = {'content-type': 'application/x-www-form-urlencoded', + 'authorization': 'Basic NWM0OWRjZTk3NzUxMDM1MTUwNmM0MmRiOnRldm9sdmU='} +dev_id = "082e858131ff012c51" + + +class Tevolve: + setmode = "" + setTemperature = "" + + token = None + sid = "" + websocket_url = "" + mode = "" + set_point = "" + websocket_message = "" + + hallway_mode = "" + hallway_current_temp = "" + hallway_set_temp = "" + hallway_active = "" + + livingroom_mode = "" + livingroom_current_temp = "" + livingroom_set_temp = "" + livingroom_active = "" + + @staticmethod + def token_manager(): + + while Tevolve.token is None: + + x = requests.post(url, headers=headers, data=data) + + print(x) + + if x.status_code == 200: + Tevolve.token = x.json()["access_token"] + else: + time.sleep(30) + + @staticmethod + def get_sid(): + time.sleep(5) + + url = "https://api-tevolve.termoweb.net/socket.io/?token=" + Tevolve.token + "&dev_id=082e858131ff012c51&EIO" \ + "=3&transport=polling" + + payload = {} + + response = requests.request("GET", url, data=payload) + + if response.status_code == 200: + + temp = response.text[5:] + temp = json.loads(temp) + + Tevolve.sid = temp['sid'] + else: + Tevolve.token = None + Tevolve.token_manager() + + @staticmethod + def post_websocket(): + + Tevolve.token_manager() + Tevolve.get_sid() + + + url = "https://api-tevolve.termoweb.net/socket.io/?token=" + Tevolve.token + "&dev_id=082e858131ff012c51&EIO" \ + "=3&transport=polling&t=Ntb6xXn" \ + "&sid=" + Tevolve.sid + + payload = "401:40/api/v2/socket_io?token=" + Tevolve.token + "&dev_id=082e858131ff012c51" + + headers = { + 'Cookie': "io=" + Tevolve.sid, + 'Content-Type': 'text/plain', + 'Connection': 'keep-alive' + + } + + requests.request("POST", url, headers=headers, data=payload) + + @staticmethod + def create_websocket(): + time.sleep(5) + + websocket_url = "wss://api-tevolve.termoweb.net/socket.io/?token=" + Tevolve.token + "&dev_id=082e858131ff012c51&EIO=3&transport=websocket&sid=" + Tevolve.sid + "" + + def on_message(ws, message): + Tevolve.websocket_message = message + + def on_open(ws): + + def run(): + print("websocket (run)") + counter = 0 + ws.send("2probe") + time.sleep(2) + ws.send(str("5")) + while 1: + ws.send("2") + counter = counter + 1 + if counter >= 360: + break + time.sleep(10) + + r = threading.Thread(target=run, daemon=True) + r.start() + + def on_close(ws, close_status_code, close_msg): + print("WEB SOCKET ClOSED") + Tevolve.post_websocket() + Tevolve.create_websocket() + + + def on_error(ws, err): + print("WEB SOCKET ERROR") + Tevolve.post_websocket() + Tevolve.create_websocket() + + ws = websocket.WebSocketApp(websocket_url) + websocket.enableTrace(False) + ws.on_message = on_message + ws.on_open = on_open + ws.on_close = on_close + ws.on_error = on_error + + ws.run_forever(ping_interval=10, ping_payload='42/api/v2/socket_io,["message","ping"]') + + @staticmethod + def set_mode(heater_id): + + heater_id = heater_id + mode = Tevolve.setmode + headers = { + 'host': 'api-tevolve.termoweb.net', + 'origin': 'https://tevolve.termoweb.net', + 'content-type': 'application/json', + 'accept': 'application/json, text/plain, */*', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) ' + 'Version/15.4 Safari/605.1.15', + 'authorization': 'Bearer ' + Tevolve.token, + 'referer': 'https://tevolve.termoweb.net/' + + } + url = "https://api-tevolve.termoweb.net/api/v2/devs/082e858131ff012c51/htr/" + heater_id + "/status" + + if mode == "heat": + mode = "manual" + + payload = json.dumps({ + "mode": mode + }) + + response = requests.post(url, headers=headers, data=payload) + if response.status_code == 201 or response.status_code == 200: + pass + + @staticmethod + def get_mode(heater_id): + headers = { + 'host': 'api-tevolve.termoweb.net', + 'origin': 'https://tevolve.termoweb.net', + 'content-type': 'application/json', + 'accept': 'application/json, text/plain, */*', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) ' + 'Version/15.4 Safari/605.1.15', + 'authorization': 'Bearer ' + Tevolve.token, + 'referer': 'https://tevolve.termoweb.net/', + + } + url = "https://api-tevolve.termoweb.net/api/v2/devs/082e858131ff012c51/htr/" + heater_id + "/status" + + response = requests.get(url, headers=headers) + if response.status_code == 201 or response.status_code == 200: + mode = response.json()["mode"] + if mode == "manual": + return "heat" + + @staticmethod + def get_status(heater_id): + + headers = { + 'host': 'api-tevolve.termoweb.net', + 'origin': 'https://tevolve.termoweb.net', + 'content-type': 'application/json', + 'accept': 'application/json, text/plain, */*', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) ' + 'Version/15.4 Safari/605.1.15', + 'authorization': 'Bearer ' + Tevolve.token, + 'referer': 'https://tevolve.termoweb.net/', + + } + url = "https://api-tevolve.termoweb.net/api/v2/devs/082e858131ff012c51/htr/" + heater_id + "/status" + + response = requests.get(url, headers=headers) + if response.status_code == 201 or response.status_code == 200: + return response.json() + + @staticmethod + def set_temperature(heater_id, set_temperature): + + headers = { + 'host': 'api-tevolve.termoweb.net', + 'origin': 'https://tevolve.termoweb.net', + 'content-type': 'application/json', + 'accept': 'application/json, text/plain, */*', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) ' + 'Version/15.4 Safari/605.1.15', + 'authorization': 'Bearer ' + Tevolve.token, + 'referer': 'https://tevolve.termoweb.net/' + + } + + url = "https://api-tevolve.termoweb.net/api/v2/devs/082e858131ff012c51/htr/" + heater_id + "/status" + + payload = json.dumps({ + "stemp": str(float(set_temperature)), + "units": "C", + "mode": "manual" + }) + + response = requests.post(url, headers=headers, data=payload) + if response.status_code == 201 or response.status_code == 200: + pass