diff --git a/berry/haco.be b/berry/haco.be index 3f79d22..21951b1 100644 --- a/berry/haco.be +++ b/berry/haco.be @@ -230,6 +230,8 @@ class Daemon 'timestamp': tasmota.rtc(), 'config_id': self.config_id, 'topic': tools.mqtt.get_topic(), + 'prefix': tasmota.cmd('Prefix'), + 'fulltopic': tasmota.cmd('FullTopic')['FullTopic'], 'device_name': tools.platform.get_device_name(), } end diff --git a/berry/manifest.yaml b/berry/manifest.yaml index 3187968..ead57de 100644 --- a/berry/manifest.yaml +++ b/berry/manifest.yaml @@ -26,3 +26,7 @@ patches: path: haco_version.be pattern: \bvar\s+VERSION\s*=\s*'{version}' +code_mask: | + import tools + downloader=tools.web.Downloader('{url}',nil,/->print(tasmota.cmd('restart 1'))) + diff --git a/haco/control.py b/haco/control.py index 052d737..b946d99 100644 --- a/haco/control.py +++ b/haco/control.py @@ -72,7 +72,7 @@ class Control: data = { "unique_id": f"{self.device.name_sanitized}-{self.device.mac_short}-{self.name_sanitized}", "name": self.name, - "availability_topic": f"tele/{self.device.topic}/LWT", + "availability_topic": str(self.device.data_announce.topic_lwt_prop), "payload_off": haco.constants.OFF, "payload_available": "Online", "payload_on": haco.constants.ON, diff --git a/haco/device.py b/haco/device.py index 679284a..294bc6c 100644 --- a/haco/device.py +++ b/haco/device.py @@ -4,40 +4,41 @@ from typing import List from haco import tools, constants from haco.control import Control +from haco.device_data import DeviceInfo from haco.tools import log_publish, split_into_chunks, log_subscribe class Device: def __init__(self, identifier, config_module, controls: List[Control], run_config_post=None): - self.name = None + self.config_module = config_module - self.topic = None - self.hostname = None - self.mac = None + self.identifier = identifier self.run_config_post = run_config_post self.controls = controls - self.config_id = None + for control in self.controls: control.device = self self.contols_dict = {control.name_sanitized: control for control in self.controls} self.get = self.contols_dict.get + self.data_announce = None + @property def name_sanitized(self): - return tools.sanitize_name(self.name) + return tools.sanitize_name(self.data_announce.name) @property def mac_short(self): - return self.mac.replace(':', '').lower() + return self.data_announce.mac.replace(':', '').lower() def get_config_ha(self): data_device = { - "connections": [["mac", self.mac]], - "identifiers": self.mac + "connections": [["mac", self.data_announce.mac]], + "identifiers": self.data_announce.mac } data = {} @@ -80,23 +81,23 @@ class Device: } - self.config_id = tools.hash_string(json.dumps(tasmota_config)) + self.data_announce.config_id = tools.hash_string(json.dumps(tasmota_config)) - tasmota_config['id'] = self.config_id + tasmota_config['id'] = self.data_announce.config_id tasmota_config_str = json.dumps(tasmota_config) - tasmota_config_topic = Path(f"haco/{constants.BRANCH}/{self.hostname}/config") + tasmota_config_topic = Path(f"haco/{constants.BRANCH}/{self.data_announce.hostname}/config") parts = split_into_chunks(tasmota_config_str, 750) for i, part in enumerate(parts): - part = json.dumps({"id": self.config_id, "data": part}) + part = json.dumps({"id": self.data_announce.config_id, "data": part}) if len(part) > 1200: raise ValueError(f'Config part too large.') await log_publish(client, str(tasmota_config_topic / str(i)), payload=part, retain=True) - payload_header = json.dumps({"size": len(parts), "id": self.config_id, 'force_update': True}) + payload_header = json.dumps({"size": len(parts), "id": self.data_announce.config_id, 'force_update': True}) await log_publish(client, str(tasmota_config_topic), payload=payload_header, retain=True) - payload_full = json.dumps({"size": len(parts), "id": self.config_id, "data": tasmota_config}) + payload_full = json.dumps({"size": len(parts), "id": self.data_announce.config_id, "data": tasmota_config}) await log_publish(client, str(tasmota_config_topic / 'full'), payload=payload_full, retain=True) async def do_subscriptions(self, client): @@ -104,13 +105,11 @@ class Device: for topic in data.keys(): await log_subscribe(client, str(topic)) - async def bind(self, client, data_announce): - msg = f'Found device with matching config {self.config_module.__name__}: Hostname: {data_announce["hostname"]} MAC:{self.mac}' + async def bind(self, client, data_announce: DeviceInfo): + msg = f'Found device with matching config {self.config_module.__name__}: Hostname: {data_announce.hostname} MAC:{data_announce.mac}' tools.logger.info(msg) - self.name = data_announce['device_name'] - self.topic = data_announce['topic'] - self.hostname = data_announce['hostname'] - self.mac = data_announce['mac'] + + self.data_announce = data_announce await self.do_subscriptions(client) diff --git a/haco/device_data.py b/haco/device_data.py new file mode 100644 index 0000000..6da1c75 --- /dev/null +++ b/haco/device_data.py @@ -0,0 +1,48 @@ +from dataclasses import dataclass, field +from pathlib import Path +from typing import Dict + + +@dataclass +class DeviceInfo: + wifi: Dict[str, any] + device_name: str + fulltopic: str + hostname: str + mac: str + version: str + topic: str + config_id: str + eth: Dict[str, any] + timestamp: Dict[str, int] + prefix: Dict[str, str] + prefix_cmnd: str = field(init=False) + prefix_stat: str = field(init=False) + prefix_tele: str = field(init=False) + + name: str = field(init=False) + + def __post_init__(self): + # Unpack the 'prefix' argument into individual fields + self.prefix_cmnd = self.prefix['Prefix1'] + self.prefix_stat = self.prefix['Prefix2'] + self.prefix_tele = self.prefix['Prefix3'] + + # Replace placeholders in the fulltopic with actual values + self.fulltopic = self.fulltopic.replace('%topic%', self.topic) + + self.name = self.device_name + + @property + def identifiers(self): + return {self.wifi.get('mac'), self.eth.get('mac'), self.hostname, self.mac} + + @property + def topic_tele(self): + topic = Path(self.fulltopic.replace('%prefix%', self.prefix_tele)) + return topic + + @property + def topic_lwt_prop(self): + topic = self.topic_tele / 'LWT' + return topic diff --git a/haco/mqtt.py b/haco/mqtt.py index f1e8942..3aa406e 100644 --- a/haco/mqtt.py +++ b/haco/mqtt.py @@ -6,13 +6,14 @@ from pathlib import Path from haco import constants, tools from haco.constants import MQTT_PASSWORD, MQTT_HOST, MQTT_PORT, MQTT_USERNAME from haco.device import Device +from haco.device_data import DeviceInfo from haco.load_configs import load_devices from haco.tools import log_received, logger async def listen(client, devices): TOPIC_ANNOUNCE = f"haco/{constants.BRANCH}/+/announce" - await client.subscribe(TOPIC_ANNOUNCE) + await tools.log_subscribe(client, TOPIC_ANNOUNCE) async with client.messages() as messages: @@ -26,11 +27,9 @@ async def listen(client, devices): continue data_announce = json.loads(message.payload) + data_announce = DeviceInfo(**data_announce) - identifiers = {data_announce['wifi'].get('mac'), data_announce['eth'].get('mac'), - data_announce['hostname'], data_announce['mac']} - - for identifier in identifiers: + for identifier in data_announce.identifiers: device: Device = devices.get(identifier) if device: break @@ -40,16 +39,21 @@ async def listen(client, devices): tools.logger.info(msg) continue - if device.config_id and data_announce['config_id'] == device.config_id: - msg = f'Device "{device.name}" successfully configured. Hostname: {device.hostname} MAC:{device.mac}' + if device.data_announce and data_announce.config_id == device.data_announce.config_id: + msg = f'Device "{device.data_announce.name}" successfully configured. Hostname: {device.data_announce.hostname} MAC:{device.data_announce.mac}' tools.logger.info(msg) continue await device.bind(client, data_announce) - devices[device.hostname] = device + devices[device.data_announce.hostname] = device else: + + if message.topic.matches('haco/release/+/config/#'): + logger.error(f'Control code got config message. Do not know why.') + continue + _, _, device_name, _, control, capability_id, platform, io = Path(message.topic.value).parts device = devices[device_name] await device.get(control).process_message(client, capability_id, platform, message) diff --git a/haco/schema.py b/haco/schema.py index b41e3c9..e6679f2 100644 --- a/haco/schema.py +++ b/haco/schema.py @@ -378,7 +378,8 @@ class Schema: return names def get_path(self): - return Path(f"haco") / constants.BRANCH / self.control.device.hostname / 'control' / self.control.name_sanitized + return Path( + f"haco") / constants.BRANCH / self.control.device.data_announce.hostname / 'control' / self.control.name_sanitized def get_config_ha(self): data = {}