feat: add laundry-family capabilities; dishwasher golden green
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from . import common, operational
|
||||
from . import common, laundry, operational
|
||||
from ..capability import Capability
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@ def _is_capability(v):
|
||||
return isinstance(v, Capability)
|
||||
|
||||
|
||||
ALL = [v for mod in (common, operational)
|
||||
ALL = [v for mod in (common, operational, laundry)
|
||||
for v in vars(mod).values() if _is_capability(v)]
|
||||
|
||||
@@ -48,6 +48,9 @@ KIDS_LOCK = Capability(
|
||||
BinarySensorDesc(key='child_lock', field='x.com.samsung.da.kidsLock',
|
||||
name='Child lock', device_class='lock',
|
||||
value_fn=lambda v: v != 'Ready'),
|
||||
BinarySensorDesc(key='child_lock_binary', field='x.com.samsung.da.kidsLock',
|
||||
name='Child lock (binary)', device_class='lock',
|
||||
value_fn=lambda v: v != 'Ready'),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -58,6 +61,10 @@ REMOTE_CONTROL = Capability(
|
||||
field='x.com.samsung.da.remoteControlEnabled',
|
||||
name='Remote control', device_class='connectivity',
|
||||
value_fn=lambda v: str(v).lower() == 'true'),
|
||||
BinarySensorDesc(key='remote_control_binary',
|
||||
field='x.com.samsung.da.remoteControlEnabled',
|
||||
name='Remote control (binary)', device_class='connectivity',
|
||||
value_fn=lambda v: str(v).lower() == 'true'),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -68,6 +75,11 @@ POWER = Capability(
|
||||
name='Power',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep: (['/power/vs/0'], {'x.com.samsung.da.power': 'On' if p else 'Off'})),
|
||||
SensorDesc(key='power_state', field='x.com.samsung.da.power',
|
||||
name='Power state'),
|
||||
BinarySensorDesc(key='power_state_binary', field='x.com.samsung.da.power',
|
||||
name='Power state (binary)',
|
||||
value_fn=lambda v: v == 'On'),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Capabilities specific to the laundry-family appliances (dishwasher, dryer).
|
||||
|
||||
Resources verified against the DW9000F-class dump at 10.0.0.129.
|
||||
Note: door-LED keys use NO `x.com.samsung.da.` prefix — `setBrightness` and
|
||||
`setNightLight` — preserved exactly as they appear in the OCF resource rep.
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import SelectDesc, SwitchDesc
|
||||
|
||||
_LED_LEVELS = ('Low', 'High')
|
||||
_SOUND_MODES = ('voice', 'tone', 'mute')
|
||||
|
||||
|
||||
def _led_brightness_write(p, rep):
|
||||
if p not in _LED_LEVELS:
|
||||
return None
|
||||
return ['doorled', 'light', 'vs', '0'], {'setBrightness': p}
|
||||
|
||||
|
||||
def _led_night_write(p, rep):
|
||||
if p not in ('On', 'Off'):
|
||||
return None
|
||||
return ['doorled', 'light', 'vs', '0'], {'setNightLight': p}
|
||||
|
||||
|
||||
def _sound_mode_write(p, rep):
|
||||
if p not in _SOUND_MODES:
|
||||
return None
|
||||
return ['settings', 'sound', 'mode', 'vs', '0'], {'mode': p}
|
||||
|
||||
|
||||
DOOR_LED = Capability(
|
||||
href='/doorled/light/vs/0',
|
||||
entities=(
|
||||
SelectDesc(key='led_brightness', field='setBrightness',
|
||||
name='Door LED brightness', icon='mdi:brightness-6',
|
||||
options=_LED_LEVELS, write_fn=_led_brightness_write),
|
||||
SwitchDesc(key='led_night_light', field='setNightLight',
|
||||
name='Door LED night light', icon='mdi:weather-night',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=_led_night_write),
|
||||
),
|
||||
)
|
||||
|
||||
SOUND_MODE = Capability(
|
||||
href='/settings/sound/mode/vs/0',
|
||||
entities=(
|
||||
SelectDesc(key='sound_mode', field='mode',
|
||||
name='Sound mode', icon='mdi:volume-high',
|
||||
options=_SOUND_MODES, write_fn=_sound_mode_write),
|
||||
),
|
||||
)
|
||||
@@ -6,7 +6,7 @@ remaining-time extrapolation. Shared by dryer/dishwasher/oven/washer families.
|
||||
import time
|
||||
|
||||
from ..capability import Capability
|
||||
from ..entities import SensorDesc
|
||||
from ..entities import ButtonDesc, SensorDesc
|
||||
|
||||
_SAMSUNG_STATE_TO_OCF = {
|
||||
'Ready': 'idle', 'Run': 'active', 'Running': 'active',
|
||||
@@ -87,6 +87,23 @@ OPERATIONAL_STATE = Capability(
|
||||
name='Remaining minutes', unit='min', device_class='duration',
|
||||
state_class='measurement',
|
||||
value_fn=lambda r: _rem_minutes(r)),
|
||||
SensorDesc(key='delay_start_time', field='x.com.samsung.da.delayStartTime',
|
||||
name='Delay start time', icon='mdi:timer-pause'),
|
||||
ButtonDesc(key='start', field='', name='Start cycle', payload='Run',
|
||||
icon='mdi:play',
|
||||
write_fn=lambda p, rep: (
|
||||
['operational', 'state', 'vs', '0'],
|
||||
{'x.com.samsung.da.state': p})),
|
||||
ButtonDesc(key='pause', field='', name='Pause cycle', payload='Pause',
|
||||
icon='mdi:pause',
|
||||
write_fn=lambda p, rep: (
|
||||
['operational', 'state', 'vs', '0'],
|
||||
{'x.com.samsung.da.state': p})),
|
||||
ButtonDesc(key='stop', field='', name='Stop cycle', payload='Ready',
|
||||
icon='mdi:stop',
|
||||
write_fn=lambda p, rep: (
|
||||
['operational', 'state', 'vs', '0'],
|
||||
{'x.com.samsung.da.state': p})),
|
||||
),
|
||||
active_when=_active_when,
|
||||
on_observation=_on_observation,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Global CAPABILITIES registry: href -> Capability.
|
||||
|
||||
Built from the full list of Capability objects in the capabilities package.
|
||||
Consumed by discover() at connection time to bind device resources to entities.
|
||||
|
||||
Note: each href must appear in at most one Capability across all modules.
|
||||
If two capabilities share an href, the later one in the scan order wins —
|
||||
this is a bug, not a feature. Add all entities for a given href to the same
|
||||
Capability object instead.
|
||||
"""
|
||||
from .capabilities import ALL
|
||||
|
||||
CAPABILITIES: dict = {cap.href: cap for cap in ALL}
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Write-handler unit tests for laundry-family capabilities."""
|
||||
from samsung_appliance.registry.capabilities import laundry
|
||||
from samsung_appliance.registry.capabilities.operational import OPERATIONAL_STATE
|
||||
|
||||
|
||||
def test_start_button_writes_run():
|
||||
btn = next(e for e in OPERATIONAL_STATE.entities if e.key == 'start')
|
||||
assert btn.write_fn('Run', {}) == (
|
||||
['operational', 'state', 'vs', '0'],
|
||||
{'x.com.samsung.da.state': 'Run'},
|
||||
)
|
||||
|
||||
|
||||
def test_pause_button_writes_pause():
|
||||
btn = next(e for e in OPERATIONAL_STATE.entities if e.key == 'pause')
|
||||
assert btn.write_fn('Pause', {}) == (
|
||||
['operational', 'state', 'vs', '0'],
|
||||
{'x.com.samsung.da.state': 'Pause'},
|
||||
)
|
||||
|
||||
|
||||
def test_stop_button_writes_ready():
|
||||
btn = next(e for e in OPERATIONAL_STATE.entities if e.key == 'stop')
|
||||
assert btn.write_fn('Ready', {}) == (
|
||||
['operational', 'state', 'vs', '0'],
|
||||
{'x.com.samsung.da.state': 'Ready'},
|
||||
)
|
||||
|
||||
|
||||
def test_sound_mode_write_valid():
|
||||
desc = next(e for e in laundry.SOUND_MODE.entities if e.key == 'sound_mode')
|
||||
assert desc.write_fn('mute', {}) == (
|
||||
['settings', 'sound', 'mode', 'vs', '0'],
|
||||
{'mode': 'mute'},
|
||||
)
|
||||
assert desc.write_fn('invalid', {}) is None
|
||||
|
||||
|
||||
def test_led_brightness_write_valid():
|
||||
desc = next(e for e in laundry.DOOR_LED.entities if e.key == 'led_brightness')
|
||||
assert desc.write_fn('Low', {}) == (
|
||||
['doorled', 'light', 'vs', '0'],
|
||||
{'setBrightness': 'Low'},
|
||||
)
|
||||
assert desc.write_fn('Medium', {}) is None
|
||||
|
||||
|
||||
def test_led_night_light_write_valid():
|
||||
desc = next(e for e in laundry.DOOR_LED.entities if e.key == 'led_night_light')
|
||||
assert desc.write_fn('On', {}) == (
|
||||
['doorled', 'light', 'vs', '0'],
|
||||
{'setNightLight': 'On'},
|
||||
)
|
||||
assert desc.write_fn('Off', {}) == (
|
||||
['doorled', 'light', 'vs', '0'],
|
||||
{'setNightLight': 'Off'},
|
||||
)
|
||||
assert desc.write_fn('invalid', {}) is None
|
||||
Reference in New Issue
Block a user