mirror of
https://github.com/fmtr/haco.git
synced 2026-10-26 02:48:54 -04:00
Refactor Tasmota announce data into object
This commit is contained in:
parent
4db6b29b22
commit
d5f49a7729
7 changed files with 89 additions and 31 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')))
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
48
haco/device_data.py
Normal 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
|
||||
20
haco/mqtt.py
20
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)
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue