From 1cb68fa0114a1b42f6d86b95996e9b3fb913e825 Mon Sep 17 00:00:00 2001 From: connorroy Date: Fri, 4 Apr 2025 23:43:03 +0100 Subject: [PATCH] Refactor --- src/climate.json | 15 +++ src/main.py | 235 ++------------------------------------------ src/mqtt_manager.py | 81 +++++++++++++++ src/pytevolve.py | 32 +++--- 4 files changed, 127 insertions(+), 236 deletions(-) create mode 100644 src/climate.json create mode 100644 src/mqtt_manager.py diff --git a/src/climate.json b/src/climate.json new file mode 100644 index 0000000..09c420c --- /dev/null +++ b/src/climate.json @@ -0,0 +1,15 @@ +{ + "name": "{name}", + "device_class": "climate", + "state_topic": "homeassistant/climate/{uid}/state", + "modes": ["heat", "off"], + "mode_command_topic": "{name}/heater/setmode", + "mode_state_topic": "{name}/heater/currentmode", + "min_temp": 7, + "max_temp": 30, + "temp_step": 0.5, + "current_temperature_topic": "{name}/heater/currenttemp", + "temperature_command_topic": "{name}/heater/settemp", + "temperature_state_topic": "{name}/heater/currentsettemp", + "action_topic": "{name}/heater/idleaction" +} \ No newline at end of file diff --git a/src/main.py b/src/main.py index 658da66..7f1c879 100644 --- a/src/main.py +++ b/src/main.py @@ -1,238 +1,23 @@ -import time from pytevolve import Tevolve -import paho.mqtt.client as mqtt -import threading -import json - -broker_address = "192.168.0.100" +from mqtt_manager import MqttManager -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__': - +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() + tevolve.get_token() + tevolve.get_dev() - client.subscribe("hallway/heater/settemp") - client.subscribe("hallway/heater/setmode") + devices = tevolve.get_devices() + mqtt_manager = MqttManager(devices["nodes"]) - client.subscribe("livingroom/heater/settemp") - client.subscribe("livingroom/heater/setmode") + mqtt_manager.publish_discovery() - 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) - + tevolve.get_sid() + # test.post_websocket() + # test.create_websocket() \ No newline at end of file diff --git a/src/mqtt_manager.py b/src/mqtt_manager.py new file mode 100644 index 0000000..ec88547 --- /dev/null +++ b/src/mqtt_manager.py @@ -0,0 +1,81 @@ +import json +from pathlib import Path +import paho.mqtt.client as mqtt + + +class MqttManager: + def __init__(self, heaters): + self.host = "192.168.0.100" + self.username = "connorroy" + self.password = "arkreactor7" + + self.object_ids = heaters + + self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) + self.client.on_connect = self.on_connect + self.client.on_message = self.on_message + + self.client.username_pw_set(username=self.username, password=self.password) + + self.client.connect(self.host, 1883, 60) + + self.client.loop_forever() + + def on_connect(self, client, userdata, flags, reason_code, properties): + print(f"Connected with result code {reason_code}") + + discovery_topic = "homeassistant/climate/" + + json_payload = self._load_json() + + for index, i in enumerate(self.object_ids): + + json_topic = "hmd/climate/" + i["uid"] + + self.client.publish(topic=discovery_topic + i["uid"] + "/config", qos=0, retain=False, + payload=json.dumps(json_payload[index])) + + for device_index, device in enumerate(self.object_ids): + + print(json_payload[device_index]["current_temperature_topic"]) + + self.client.publish(topic=json_payload[device_index]["current_temperature_topic"], qos=0, retain=False, + payload=json.dumps(json_payload[index])) + + # # Subscribing in on_connect() means that if we lose the connection and + # # reconnect then subscriptions will be renewed. + # client.subscribe("$SYS/#") + + def on_message(self, client, userdata, msg): + print(msg.topic + " " + str(msg.payload)) + + def publish_discovery(self): + discovery_topic = "homeassistant/climate/" + for i in self.object_ids: + self.client.publish(topic=discovery_topic + i["uid"] + "/config", qos=0, retain=False) + + + + def _load_json(self): + path = Path(__file__).parent + json_list = [] + with open(str(path) + "/climate.json") as j: + data = json.load(j) + + for dev in self.object_ids: + new_data = data.copy() + for i in new_data: + if "{name}" in str(new_data.get(i)): + new_data[i] = new_data[i].replace("{name}", dev["name"]) + elif "{uid}" in str(new_data.get(i)): + new_data[i] = new_data[i].replace("{uid}", dev["uid"]) + json_list.append(new_data) + return json_list + +# test = MqttManager() + + +# Blocking call that processes network traffic, dispatches callbacks and +# handles reconnecting. +# Other loop*() functions are available that give a threaded interface and a +# manual interface. diff --git a/src/pytevolve.py b/src/pytevolve.py index ce65f9a..7486924 100644 --- a/src/pytevolve.py +++ b/src/pytevolve.py @@ -23,6 +23,7 @@ class Tevolve: self.token_primary = "" self.token_refresh = "" self.sid = "" + self.devices = [] setmode = "" setTemperature = "" @@ -112,13 +113,16 @@ class Tevolve: def get_devices(self): - endpoint = "/devs/082e858131ff012c51/mgr/nodes" + endpoint = "/devs/"+self.dev_id+"/mgr/nodes" - header = {"Authorization": "Bearer " + self.token_primary} + header = {'content-type': 'application/json', + "Authorization": "Bearer " + self.token_primary} - devices_request = requests.get(str(self.api_url + endpoint), headers=headers).json() + devices_request = requests.get(str(self.api_url + endpoint), headers=header).json() - print(devices_request) + self.devices = devices_request + + return self.devices @@ -222,8 +226,8 @@ class Tevolve: if response.status_code == 201 or response.status_code == 200: pass - @staticmethod - def get_mode(heater_id): + + def get_mode(self): headers = { 'host': 'api-tevolve.termoweb.net', 'origin': 'https://tevolve.termoweb.net', @@ -231,11 +235,11 @@ class Tevolve: '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, + 'authorization': 'Bearer ' + self.token_primary, 'referer': 'https://tevolve.termoweb.net/', } - url = "https://api-tevolve.termoweb.net/api/v2/devs/082e858131ff012c51/htr/" + heater_id + "/status" + url = "https://api-tevolve.termoweb.net/api/v2/devs/082e858131ff012c51/htr/" + self.heater_id + "/status" response = requests.get(url, headers=headers) if response.status_code == 201 or response.status_code == 200: @@ -244,7 +248,7 @@ class Tevolve: return "heat" - def get_status(self): + def get_status(self, heater_id): headers = { 'host': 'api-tevolve.termoweb.net', @@ -257,7 +261,7 @@ class Tevolve: 'referer': 'https://tevolve.termoweb.net/', } - url = "https://api-tevolve.termoweb.net/api/v2/devs/082e858131ff012c51/status" + url = "https://api-tevolve.termoweb.net/api/v2/devs/"+self.dev_id+"/htr/"+str(heater_id)+"/status" response = requests.get(url, headers=headers) if response.status_code == 201 or response.status_code == 200: @@ -297,8 +301,14 @@ if __name__ == '__main__': test.get_devices() + + for device in test.devices["nodes"]: + if device["name"] == "Hallway": + test.get_status(device["addr"]) + test.get_sid() - test.get_status() + + test.post_websocket() test.create_websocket()