From caa19f016dea57f9cfad26225d1e24961c3bac41 Mon Sep 17 00:00:00 2001 From: Edward Brown Date: Mon, 24 Nov 2025 12:04:32 +0000 Subject: [PATCH] Implement announce as control method and allow async methods --- haco/client.py | 13 +++---------- haco/control.py | 14 +++++++++----- haco/device.py | 12 +++++------- haco/topics.py | 34 +++++++++++++++++++++++++++++----- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/haco/client.py b/haco/client.py index 2147286..fa553e6 100644 --- a/haco/client.py +++ b/haco/client.py @@ -2,7 +2,6 @@ import aiomqtt import asyncio from fmtr.tools import mqtt, Constants -from fmtr.tools.json_tools import to_json from haco import constants from haco.obs import logger @@ -35,22 +34,16 @@ class ClientHaco(mqtt.Client): payload = message.payload.decode() logger.info(f"{message.topic.value}{Constants.ARROW}{payload}") topic_command = self.device.subscriptions[message.topic.value] - echo_val = topic_command.wrap_back(message) + echo_val = await topic_command.wrap_back(message) + - # Echo back as new state - topic_state = topic_command.state - if topic_state: - await topic_state.wrap_back(echo_val) async def start(self): while True: try: async with self: - for topic, data in self.device.announce.items(): - data_json = to_json(data) - logger.info(f'Announcing {topic} with {data_json}') - await self.publish(topic, payload=data_json, retain=True) + await self.device.announce() await self.publish(self.will.topic, self.ONLINE, retain=True) diff --git a/haco/control.py b/haco/control.py index c8665ef..9e2895b 100644 --- a/haco/control.py +++ b/haco/control.py @@ -3,8 +3,10 @@ from __future__ import annotations from dataclasses import dataclass, field from typing import TYPE_CHECKING +from fmtr.tools.json_tools import to_json from haco.base import Base from haco.capabilities import Capability +from haco.obs import logger from haco.utils import sanitize_name if TYPE_CHECKING: @@ -21,7 +23,7 @@ class Control(Base): unique_id: str | None = field(default=None, init=False) availability_topic: str | None = field(default=None, init=False) - announce: dict | None = field(default=None, metadata=dict(exclude=True)) + # announce: dict | None = field(default=None, metadata=dict(exclude=True)) subscriptions: dict | None = field(default=None, metadata=dict(exclude=True), init=False) def __post_init__(self): @@ -36,7 +38,7 @@ class Control(Base): self.unique_id = self.get_unique_id() self.availability_topic = self.get_availability_topic() self.subscriptions = self.get_subscriptions() - self.announce = self.get_announce() + #self.announce = self.get_announce() @property def parent(self): @@ -47,7 +49,6 @@ class Control(Base): for cap in self.capabilities: data |= cap.announce - data = {self.announce_topic: data} return data @classmethod @@ -86,13 +87,16 @@ class Control(Base): async def initialise(self): ... - async def announce2(self): + async def announce(self): """ Entities must be able to announce themselves to Home Assistant, e.g. when a pulldown changes its options, etc. """ - await self.device.client.publish(self.announce_topic, self.announce) + topic = self.announce_topic + data_json = to_json(self.get_announce()) + logger.info(f'Announcing {topic} with {data_json}') + await self.device.client.publish(topic, data_json, retain=True) if __name__ == "__main__": diff --git a/haco/device.py b/haco/device.py index f28e16b..0f1fa0a 100644 --- a/haco/device.py +++ b/haco/device.py @@ -23,7 +23,7 @@ class Device(Base): controls: List[Control] = field(default_factory=list, metadata=dict(exclude=True)) parent: ClientHaco | None = field(metadata=dict(exclude=True), init=False) - announce: dict | None = field(default=None, metadata=dict(exclude=True), init=False) + # announce: dict | None = field(default=None, metadata=dict(exclude=True), init=False) subscriptions: dict | None = field(default=None, metadata=dict(exclude=True), init=False) @@ -34,7 +34,7 @@ class Device(Base): for control in self.controls: control.set_parent(self) - self.announce = self.get_announce() + #self.announce = self.get_announce() self.subscriptions = self.get_subscriptions() self @@ -47,12 +47,10 @@ class Device(Base): def name_san(self) -> str: return sanitize_name(self.name) - def get_announce(self): + async def announce(self): + for control in self.controls: + await control.announce() - data = {} - for capability in self.controls: - data |= capability.announce - return data @property def client(self): diff --git a/haco/topics.py b/haco/topics.py index 436fae2..ad491d8 100644 --- a/haco/topics.py +++ b/haco/topics.py @@ -4,6 +4,7 @@ from dataclasses import dataclass, field from functools import cached_property from typing import ClassVar, TYPE_CHECKING +from fmtr.tools import aio from haco.obs import logger if TYPE_CHECKING: @@ -76,11 +77,19 @@ class AnnounceTopicState(AnnounceTopic): async def wrap_back(self, value): method = getattr(self.capability.control, self.callback_name, None) + is_async = aio.is_async(method) + + # test try: - value_raw = method(value) + + if is_async: + value_raw = await method(value) + else: + value_raw = method(value) + except NotImplementedError: - logger.warning(f'No method implemented for {self.topic}') + logger.warning(f'No method implemented for {self.__class__.__name__}.{self.callback_name}: {self.topic}') return await self.capability.control.device.client.publish(self.topic, value_raw) @@ -94,16 +103,31 @@ class AnnounceTopicCommand(AnnounceTopic): def state(self): return self.capability.state - def wrap_back(self, message): + async def wrap_back(self, message): method = getattr(self.capability.control, self.callback_name, None) + is_async = aio.is_async(method) + + #test + try: - value = method(message.payload.decode('utf-8')) + + value = message.payload.decode('utf-8') + + if is_async: + value_raw = await method(value) + else: + value_raw = method(value) + except NotImplementedError: logger.warning(f'No method implemented for {self.topic}') return - return value + # Echo back as new state + topic_state = self.state + if topic_state: + await topic_state.wrap_back(value_raw) + def get_subscriptions(self): return {self.topic: self}