Refactor Tasmota announce data into object

This commit is contained in:
Edward Brown 2023-09-17 14:00:25 +01:00
parent 4db6b29b22
commit d5f49a7729
7 changed files with 89 additions and 31 deletions

View file

@ -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

View file

@ -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')))

View file

@ -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,

View file

@ -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)

48
haco/device_data.py Normal file
View file

@ -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

View file

@ -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)

View file

@ -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 = {}