Address review: gate the AddWash master write on a readable mask
The master alarm switch wrote AddWashSet_7/_0 without consulting _add_wash_mask, so a device reporting a wider mask (AddWashSet_15) or a non-numeric one had it truncated to three bits -- the write that mask's own docstring rules out, while the per-moment switches already refused it. Also corrects the washer_ww6500 golden docstring: the fixture carries no /oic/d and the test passes no device_types, so the device is typed solely by the WW consumer prefix in its description. A51 is not a board token, which makes that the fragile route worth naming.
This commit is contained in:
@@ -310,7 +310,10 @@ def _add_wash_set_write(mask):
|
||||
|
||||
|
||||
def _add_wash_alarm_write(p, rep, href=None):
|
||||
if p not in ("On", "Off") or not rep.get("x.com.samsung.da.options"):
|
||||
# Gated on the mask being readable, like the per-moment writes: a device
|
||||
# reporting a wider mask than these three bits would otherwise have it
|
||||
# truncated to 7 here, silently dropping a moment it supports.
|
||||
if p not in ("On", "Off") or _add_wash_mask(rep, "AddWashSet") is None:
|
||||
return None
|
||||
return _add_wash_set_write(0b111 if p == "On" else 0)
|
||||
|
||||
@@ -320,7 +323,10 @@ def _add_wash_bit_switch(key, icon, bit):
|
||||
|
||||
The mask is the only state, so switching the last moment off lands on 0
|
||||
and takes the alarm with it, and switching one on from 0 turns the alarm
|
||||
back on.
|
||||
back on. The corollary is that switching the master off and on again
|
||||
writes 7, resetting a rinse-only selection to all three moments -- the
|
||||
appliance remembers no previous subset either, so there is nothing to
|
||||
restore.
|
||||
"""
|
||||
|
||||
def read(rep):
|
||||
@@ -445,6 +451,10 @@ WASHER_COURSE = Capability(
|
||||
_add_wash_bit_switch("add_wash_alarm_rinse", "mdi:water", 0),
|
||||
_add_wash_bit_switch("add_wash_alarm_final_rinse", "mdi:water-check", 1),
|
||||
_add_wash_bit_switch("add_wash_alarm_spin", "mdi:sync", 2),
|
||||
# On at rest: an idle washer reports AddWashAvailable_7 and the mask
|
||||
# only empties as the cycle consumes each moment. This says the cycle
|
||||
# permits AddWash, not that laundry can go in now -- that is
|
||||
# add_wash_indicator.
|
||||
BinarySensorDesc(
|
||||
key="add_wash_available",
|
||||
icon="mdi:tshirt-crew-outline",
|
||||
|
||||
@@ -390,9 +390,12 @@ def test_registry_reproduces_golden_state_keys_for_washer_dryer_combo():
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_washer_ww6500():
|
||||
"""DA_WM_A51_20 front-loader, routed by /oic/d's oic.d.washer rather
|
||||
than a board token. Reports the AddWash tokens and an empty
|
||||
/wm/editcourse/vs/0, so its cycle list comes from supportedOptions."""
|
||||
"""DA_WM_A51_20 front-loader, typed solely by the WW consumer prefix in
|
||||
its /information/vs/0 description: A51 is not a board token, so with the
|
||||
description blanked this device resolves to nothing and drops to the
|
||||
unknown-device fallback. That is the fragile route this test pins.
|
||||
Reports the AddWash tokens and an empty /wm/editcourse/vs/0, so its cycle
|
||||
list comes from supportedOptions."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("washer_ww6500")
|
||||
|
||||
@@ -86,6 +86,17 @@ class TestAlarmMasterSwitch:
|
||||
desc = _desc("add_wash_alarm", SwitchDesc)
|
||||
assert _write(desc, "On", {}) is None
|
||||
|
||||
@pytest.mark.parametrize("raw", ["15", "On", "-1"])
|
||||
def test_refuses_to_write_over_a_mask_it_cannot_read(self, raw):
|
||||
"""A device reporting a wider mask must not have it truncated to 7.
|
||||
The master is gated exactly like the per-moment writes, so an
|
||||
unrecognized mask leaves every AddWash switch read-only."""
|
||||
desc = _desc("add_wash_alarm", SwitchDesc)
|
||||
rep = _rep(f"AddWashSet_{raw}")
|
||||
assert desc.rep_fn(rep) is None
|
||||
assert _write(desc, "On", rep) is None
|
||||
assert _write(desc, "Off", rep) is None
|
||||
|
||||
|
||||
class TestAlarmMomentSwitches:
|
||||
@pytest.mark.parametrize("key,bit", BITS.items())
|
||||
@@ -197,6 +208,17 @@ class TestAgainstTheWW6500Dump:
|
||||
"""DA_WM_A51_20_COMMON_WW6500, captured with the alarm off, the course
|
||||
permitting all three moments and the lamp dark."""
|
||||
|
||||
def test_types_only_by_the_description_consumer_prefix(self):
|
||||
"""A51 is not a board token, so the WW prefix in the description is
|
||||
the only thing routing this device -- see the golden test's docstring.
|
||||
Pinned here so the claim can't quietly stop being true."""
|
||||
from custom_components.localthings.registry.by_type import for_device_by_model
|
||||
|
||||
info = _load_device("washer_ww6500")["/information/vs/0"]
|
||||
model = info["x.com.samsung.da.modelNum"]
|
||||
assert for_device_by_model(model, info["x.com.samsung.da.description"]).name == "washer"
|
||||
assert for_device_by_model(model, "") is None
|
||||
|
||||
def test_no_unbound_hrefs(self):
|
||||
resources = _load_device("washer_ww6500")
|
||||
reg = resolve(resources)
|
||||
|
||||
Reference in New Issue
Block a user