feat(washer): prefer OCF-native power/kidslock/remotectrl hrefs, fall back to -vs
This commit is contained in:
@@ -7,7 +7,7 @@ family). Washers never report `oneUiVersion` -- see
|
||||
detection this device type requires.
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import SelectDesc, SensorDesc
|
||||
from ..entities import BinarySensorDesc, SelectDesc, SensorDesc, SwitchDesc
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /washer/vs/0 -- wash temperature, spin speed, rinse cycle count
|
||||
@@ -108,3 +108,84 @@ WASHER_JOB_BEGINNING_STATUS = Capability(
|
||||
entity_category='diagnostic'),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OCF-native / '-vs' fallback pairs for power, kids-lock, remote control.
|
||||
#
|
||||
# Same shape as fridge.py's "Aggregate-resource fallbacks": the generic OCF
|
||||
# href (/power/0, plain boolean 'value') is preferred when present; the
|
||||
# vendor '-vs' href (richer historically, but for these three controls just
|
||||
# a string-encoded duplicate) only binds when the generic href is absent
|
||||
# from this device's resource set, via match_fn. Scoped to washer.py, not
|
||||
# common.py -- no other device type has been confirmed to expose the
|
||||
# generic hrefs, so this must not change behavior for dishwasher/dryer/
|
||||
# oven/refrigerator.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
POWER_GENERIC = Capability(
|
||||
href='/power/0',
|
||||
entities=(
|
||||
SwitchDesc(key='power_switch', field='value',
|
||||
name='Power',
|
||||
value_fn=lambda v: bool(v),
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['power', '0'], {'value': p == 'On'})),
|
||||
),
|
||||
)
|
||||
|
||||
POWER_VS_FALLBACK = Capability(
|
||||
href='/power/vs/0',
|
||||
match_fn=lambda rep, resources: '/power/0' not in resources,
|
||||
entities=(
|
||||
SwitchDesc(key='power_switch', field='x.com.samsung.da.power',
|
||||
name='Power',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['power', 'vs', '0'],
|
||||
{'x.com.samsung.da.power': 'On' if p == 'On' else 'Off'})),
|
||||
),
|
||||
)
|
||||
|
||||
KIDS_LOCK_GENERIC = Capability(
|
||||
href='/kidslock/0',
|
||||
entities=(
|
||||
SwitchDesc(key='child_lock', field='value',
|
||||
name='Child lock', device_class='lock',
|
||||
value_fn=lambda v: bool(v),
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['kidslock', '0'], {'value': p == 'On'})),
|
||||
),
|
||||
)
|
||||
|
||||
KIDS_LOCK_VS_FALLBACK = Capability(
|
||||
href='/kidslock/vs/0',
|
||||
match_fn=lambda rep, resources: '/kidslock/0' not in resources,
|
||||
entities=(
|
||||
SwitchDesc(key='child_lock', field='x.com.samsung.da.kidsLock',
|
||||
name='Child lock', device_class='lock',
|
||||
value_fn=lambda v: v != 'Ready',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['kidslock', 'vs', '0'],
|
||||
{'x.com.samsung.da.kidsLock': 'Enable' if p == 'On' else 'Ready'})),
|
||||
),
|
||||
)
|
||||
|
||||
REMOTE_CONTROL_GENERIC = Capability(
|
||||
href='/remotectrl/0',
|
||||
entities=(
|
||||
BinarySensorDesc(key='remote_control', field='value',
|
||||
name='Smart Control', device_class='connectivity',
|
||||
value_fn=lambda v: bool(v)),
|
||||
),
|
||||
)
|
||||
|
||||
REMOTE_CONTROL_VS_FALLBACK = Capability(
|
||||
href='/remotectrl/vs/0',
|
||||
match_fn=lambda rep, resources: '/remotectrl/0' not in resources,
|
||||
entities=(
|
||||
BinarySensorDesc(key='remote_control',
|
||||
field='x.com.samsung.da.remoteControlEnabled',
|
||||
name='Smart Control', device_class='connectivity',
|
||||
value_fn=lambda v: str(v).lower() == 'true'),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -81,3 +81,59 @@ class TestJobBeginningStatus:
|
||||
assert washer.WASHER_JOB_BEGINNING_STATUS.href == '/wm/jobbeginingstatus/vs/0'
|
||||
desc = washer.WASHER_JOB_BEGINNING_STATUS.entities[0]
|
||||
assert desc.field == 'x.com.samsung.da.currentStatus'
|
||||
|
||||
|
||||
class TestPowerFallback:
|
||||
def test_generic_href(self):
|
||||
assert washer.POWER_GENERIC.href == '/power/0'
|
||||
|
||||
def test_generic_read_write(self):
|
||||
desc = washer.POWER_GENERIC.entities[0]
|
||||
assert desc.value_fn(True) is True
|
||||
assert desc.value_fn(False) is False
|
||||
path, body = desc.write_fn('On', {})
|
||||
assert path == ['power', '0']
|
||||
assert body == {'value': True}
|
||||
path, body = desc.write_fn('Off', {})
|
||||
assert body == {'value': False}
|
||||
|
||||
def test_vs_fallback_binds_only_when_generic_absent(self):
|
||||
assert washer.POWER_VS_FALLBACK.href == '/power/vs/0'
|
||||
assert washer.POWER_VS_FALLBACK.match_fn({}, {'/power/vs/0': {}}) is True
|
||||
assert washer.POWER_VS_FALLBACK.match_fn({}, {'/power/0': {}, '/power/vs/0': {}}) is False
|
||||
|
||||
def test_vs_fallback_read_write(self):
|
||||
desc = washer.POWER_VS_FALLBACK.entities[0]
|
||||
assert desc.value_fn('On') is True
|
||||
assert desc.value_fn('Off') is False
|
||||
path, body = desc.write_fn('On', {})
|
||||
assert path == ['power', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.power': 'On'}
|
||||
|
||||
|
||||
class TestKidsLockFallback:
|
||||
def test_generic_read_write(self):
|
||||
assert washer.KIDS_LOCK_GENERIC.href == '/kidslock/0'
|
||||
desc = washer.KIDS_LOCK_GENERIC.entities[0]
|
||||
assert desc.value_fn(True) is True
|
||||
path, body = desc.write_fn('On', {})
|
||||
assert path == ['kidslock', '0']
|
||||
assert body == {'value': True}
|
||||
|
||||
def test_vs_fallback_gated(self):
|
||||
assert washer.KIDS_LOCK_VS_FALLBACK.match_fn({}, {'/kidslock/vs/0': {}}) is True
|
||||
assert washer.KIDS_LOCK_VS_FALLBACK.match_fn(
|
||||
{}, {'/kidslock/0': {}, '/kidslock/vs/0': {}}) is False
|
||||
|
||||
|
||||
class TestRemoteControlFallback:
|
||||
def test_generic_read(self):
|
||||
assert washer.REMOTE_CONTROL_GENERIC.href == '/remotectrl/0'
|
||||
desc = washer.REMOTE_CONTROL_GENERIC.entities[0]
|
||||
assert desc.value_fn(True) is True
|
||||
assert desc.value_fn(False) is False
|
||||
|
||||
def test_vs_fallback_gated(self):
|
||||
assert washer.REMOTE_CONTROL_VS_FALLBACK.match_fn({}, {'/remotectrl/vs/0': {}}) is True
|
||||
assert washer.REMOTE_CONTROL_VS_FALLBACK.match_fn(
|
||||
{}, {'/remotectrl/0': {}, '/remotectrl/vs/0': {}}) is False
|
||||
|
||||
Reference in New Issue
Block a user