Move control state/command method wrapping to topic classes

This commit is contained in:
Edward Brown 2025-11-26 09:31:53 +00:00
parent 5be372be03
commit d0808006ab
3 changed files with 36 additions and 29 deletions

View file

@ -20,7 +20,7 @@ class Capability(Base):
state: AnnounceTopicState | None = field(default_factory=AnnounceTopicState)
command: AnnounceTopicCommand | None = field(default_factory=AnnounceTopicCommand)
subscriptions: dict | None = field(default=None, metadata=dict(exclude=True), init=False)
parent: Control | None = None
announce = None
@ -31,20 +31,17 @@ class Capability(Base):
topic.set_parent(self)
self.converters = self.converters or self.control.converters
self.subscriptions = self.get_subscriptions()
self.announce = self.get_announce()
def get_announce(self) -> dict:
data = {}
for topic in self.topics:
data |= topic.announce
data |= topic.get_announce()
return data
def get_subscriptions(self) -> dict:
data = {}
for topic in self.topics:
data |= topic.subscriptions
data |= topic.get_subscriptions()
return data
@property

View file

@ -41,13 +41,7 @@ class Control(Base, Generic[DeviceT]):
self.subscriptions = self.get_subscriptions()
#self.announce = self.get_announce()
for capability in self.capabilities:
for topic in capability.topics:
method_name = topic.callback_name
method = getattr(self, method_name, None)
if not method: continue
setattr(self, f'_{method_name}', method)
setattr(self, f'{method_name}', topic.wrap_back)
@property
def parent(self):
@ -56,7 +50,7 @@ class Control(Base, Generic[DeviceT]):
def get_announce(self):
data = self.model_dump()
for cap in self.capabilities:
data |= cap.announce
data |= cap.get_announce()
return data
@ -89,7 +83,7 @@ class Control(Base, Generic[DeviceT]):
data = {}
for capability in self.capabilities:
data |= capability.subscriptions
data |= capability.get_subscriptions()
return data

View file

@ -2,7 +2,7 @@ from __future__ import annotations
from dataclasses import dataclass, field
from functools import cached_property
from typing import ClassVar, TYPE_CHECKING
from typing import ClassVar, TYPE_CHECKING, Callable
from fmtr.tools import aio
from haco.obs import logger
@ -24,12 +24,17 @@ class AnnounceTopic:
announce: dict | None = field(default=None, metadata=dict(exclude=True))
subscriptions: dict | None = field(default=None, metadata=dict(exclude=True), init=False)
control_method: Callable | None = field(default=None, metadata=dict(exclude=True))
def set_parent(self, capability: Capability):
self.parent = capability
if self.key is None:
self.key = self.KEY_DEFAULTS[bool(self.capability.name)]
self.announce = self.get_announce()
self.subscriptions = self.get_subscriptions()
method_name = self.callback_name
method = getattr(self.capability.control, method_name, None)
self.control_method = method
setattr(self.capability.control, method_name, self.wrap_back)
def fill(self, mask):
return mask.format(**self.fills)
@ -60,6 +65,10 @@ class AnnounceTopic:
return f'{self.capability.name}_{self.IO}'
@property
def callback_class_method_name(self):
return f'{self.capability.control.__class__.__name__}.{self.callback_name}'
def get_subscriptions(self):
return {}
@ -67,6 +76,9 @@ class AnnounceTopic:
def capability(self):
return self.parent
def wrap_back(self, value):
raise NotImplementedError()
@dataclass(kw_only=True)
class AnnounceTopicState(AnnounceTopic):
@ -76,20 +88,21 @@ class AnnounceTopicState(AnnounceTopic):
return {}
async def wrap_back(self, value):
method = getattr(self.capability.control, f'_{self.callback_name}', None)
is_async = aio.is_async(method)
# test
if not self.control_method:
logger.error(f'Incomplete base class: {self.callback_class_method_name}')
return
is_async = aio.is_async(self.control_method)
try:
if is_async:
value = await method(value)
value = await self.control_method(value)
else:
value = method(value)
value = self.control_method(value)
except NotImplementedError:
logger.warning(f'No method implemented for {self.capability.control.__class__.__name__}.{self.callback_name}: {self.topic}')
logger.warning(f'Subclass has not implemented method: {self.callback_class_method_name} {self.topic} {value}')
return
value_raw = self.capability.converters.state(value)
@ -105,21 +118,24 @@ class AnnounceTopicCommand(AnnounceTopic):
return self.capability.state
async def wrap_back(self, message):
method = getattr(self.capability.control, f'_{self.callback_name}', None)
is_async = aio.is_async(method)
if not self.control_method:
logger.error(f'Incomplete base class: {self.callback_class_method_name}')
return
is_async = aio.is_async(self.control_method)
value = message.payload.decode('utf-8')
value = self.capability.converters.command(value)
try:
if is_async:
value_raw = await method(value)
value_raw = await self.control_method(value)
else:
value_raw = method(value)
value_raw = self.control_method(value)
except NotImplementedError:
logger.warning(f'{self.capability.control.__class__.__name__}.{self.callback_name}: {self.topic}')
logger.warning(f'Subclass has not implemented method: {self.callback_class_method_name} {self.topic} {value}')
return
# Echo back as new state