Implement per-control/capability state/command datatype converters

This commit is contained in:
Edward Brown 2025-11-25 12:30:35 +00:00
parent 1604e2447e
commit 879bc347f8
5 changed files with 39 additions and 13 deletions

View file

@ -1,11 +1,12 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Type
from fmtr.tools.iterator_tools import strip_none
from haco.base import Base
from haco.topics import AnnounceTopicState, AnnounceTopicCommand
from haco.utils import Converters
if TYPE_CHECKING:
from haco.control import Control
@ -15,6 +16,8 @@ if TYPE_CHECKING:
class Capability(Base):
name: str | None = None
converters: Type[Converters] | None = field(default=None, metadata=dict(exclude=True))
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)
@ -27,6 +30,7 @@ class Capability(Base):
for topic in self.topics:
topic.set_parent(self)
self.converters = self.converters or self.control.converters
self.subscriptions = self.get_subscriptions()
self.announce = self.get_announce()

View file

@ -1,24 +1,25 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, TypeVar, Generic, Type, ClassVar
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
from haco.utils import sanitize_name, Converters, ConvertersBool
if TYPE_CHECKING:
from haco.device import Device
DeviceT = TypeVar("DeviceT", bound="Device")
@dataclass(kw_only=True)
class Control(Base):
class Control(Base, Generic[DeviceT]):
converters: ClassVar[Type[Converters]] = ConvertersBool
name: str
device: Device = field(default=None, init=False)
device: DeviceT = field(default=None, init=False)
capabilities: None | list[Capability] = field(default=None, metadata=dict(exclude=True))
unique_id: str | None = field(default=None, init=False)
availability_topic: str | None = field(default=None, init=False)

View file

@ -1,6 +1,7 @@
from dataclasses import dataclass
from haco.control import Control
from haco.utils import ConvertersString
@dataclass(kw_only=True)
@ -8,6 +9,7 @@ class Select(Control):
DATA = dict(
platform='select'
)
converters = ConvertersString
options: list[str]

View file

@ -84,14 +84,15 @@ class AnnounceTopicState(AnnounceTopic):
try:
if is_async:
value_raw = await method(value)
value = await method(value)
else:
value_raw = method(value)
value = method(value)
except NotImplementedError:
logger.warning(f'No method implemented for {self.capability.control.__class__.__name__}.{self.callback_name}: {self.topic}')
return
value_raw = self.capability.converters.state(value)
await self.capability.control.device.client.publish(self.topic, value_raw)
return value_raw
@ -107,13 +108,11 @@ class AnnounceTopicCommand(AnnounceTopic):
method = getattr(self.capability.control, self.callback_name, None)
is_async = aio.is_async(method)
#test
value = message.payload.decode('utf-8')
value = self.capability.converters.command(value)
try:
value = message.payload.decode('utf-8')
if is_async:
value_raw = await method(value)
else:

View file

@ -1,5 +1,8 @@
import string
from dataclasses import dataclass
from typing import Callable
from fmtr.tools.datatype_tools import to_bool
CHARS_ALLOWED = string.ascii_lowercase + string.digits
SEPS = '_- /'
@ -10,6 +13,9 @@ RECONNECT_DELAY_SEC = 5
ON = 'ON'
OFF = 'OFF'
BOOL_MAP = {True: ON, False: OFF}
to_bool_ha = BOOL_MAP.get
def sanitize_name(s, sep='-'):
chars = []
@ -28,6 +34,20 @@ def sanitize_name(s, sep='-'):
return sanitized
class Converters:
command: Callable = None
state: Callable = None
class ConvertersBool(Converters):
command: Callable = to_bool
state: Callable = to_bool_ha
class ConvertersString(Converters):
command: Callable = str
state: Callable = str
@dataclass
class Metadata:
exclude: bool = False