feat: add HA-shaped entity description dataclasses
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""HA-shaped entity descriptions. The subclass *type* selects the HA platform.
|
||||
|
||||
Frozen dataclasses so the future native HA component can consume them as
|
||||
EntityDescription subclasses unchanged. Read transforms live in value_fn;
|
||||
presence gating in exists_fn; write logic in write_fn on command platforms.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
WriteFn = Optional[Callable[[Any, dict], "tuple[list[str], dict] | None"]]
|
||||
|
||||
|
||||
def _identity(v: Any) -> Any:
|
||||
return v
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SamsungEntityDescription:
|
||||
key: str
|
||||
field: str = ''
|
||||
name: Optional[str] = None
|
||||
translation_key: Optional[str] = None
|
||||
icon: Optional[str] = None
|
||||
entity_category: Optional[str] = None # 'diagnostic' | 'config' | None
|
||||
enabled_default: bool = True
|
||||
value_fn: Callable[[Any], Any] = _identity
|
||||
exists_fn: Optional[Callable[[dict], bool]] = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SensorDesc(SamsungEntityDescription):
|
||||
device_class: Optional[str] = None
|
||||
state_class: Optional[str] = None
|
||||
unit: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class BinarySensorDesc(SamsungEntityDescription):
|
||||
device_class: Optional[str] = None # value_fn must return bool
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SelectDesc(SamsungEntityDescription):
|
||||
options: Any = () # tuple[str,...] | Callable[[dict], list[str]]
|
||||
write_fn: WriteFn = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SwitchDesc(SamsungEntityDescription):
|
||||
device_class: Optional[str] = None
|
||||
write_fn: WriteFn = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ButtonDesc(SamsungEntityDescription):
|
||||
payload: str = ''
|
||||
write_fn: WriteFn = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class NumberDesc(SamsungEntityDescription):
|
||||
device_class: Optional[str] = None
|
||||
unit: Optional[str] = None
|
||||
native_min: Optional[float] = None
|
||||
native_max: Optional[float] = None
|
||||
step: Optional[float] = None
|
||||
write_fn: WriteFn = None
|
||||
|
||||
|
||||
PLATFORM_OF: dict[type, str] = {
|
||||
SensorDesc: 'sensor',
|
||||
BinarySensorDesc: 'binary_sensor',
|
||||
SelectDesc: 'select',
|
||||
SwitchDesc: 'switch',
|
||||
ButtonDesc: 'button',
|
||||
NumberDesc: 'number',
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
from samsung_appliance.registry.entities import (
|
||||
SensorDesc, BinarySensorDesc, SelectDesc, SwitchDesc, ButtonDesc,
|
||||
NumberDesc, PLATFORM_OF,
|
||||
)
|
||||
|
||||
|
||||
def test_value_fn_defaults_to_identity():
|
||||
d = SensorDesc(key='power', field='x.com.samsung.da.instantaneousPower')
|
||||
assert d.value_fn(42) == 42
|
||||
|
||||
|
||||
def test_descriptions_are_frozen():
|
||||
d = SensorDesc(key='power', field='f')
|
||||
try:
|
||||
d.key = 'other'
|
||||
except Exception as e:
|
||||
assert 'frozen' in str(type(e)).lower() or 'cannot' in str(e).lower()
|
||||
else:
|
||||
raise AssertionError("expected frozen dataclass")
|
||||
|
||||
|
||||
def test_platform_mapping_covers_all_subclasses():
|
||||
assert PLATFORM_OF[SensorDesc] == 'sensor'
|
||||
assert PLATFORM_OF[BinarySensorDesc] == 'binary_sensor'
|
||||
assert PLATFORM_OF[SelectDesc] == 'select'
|
||||
assert PLATFORM_OF[SwitchDesc] == 'switch'
|
||||
assert PLATFORM_OF[ButtonDesc] == 'button'
|
||||
assert PLATFORM_OF[NumberDesc] == 'number'
|
||||
|
||||
|
||||
def test_select_carries_options_and_write_fn():
|
||||
d = SelectDesc(key='sound', field='mode', options=('voice', 'tone', 'mute'),
|
||||
write_fn=lambda p, rep: (['settings', 'sound', 'mode', 'vs', '0'], {'mode': p}))
|
||||
assert d.write_fn('tone', {}) == (['settings', 'sound', 'mode', 'vs', '0'], {'mode': 'tone'})
|
||||
Reference in New Issue
Block a user