Complete dryer coverage from issue #14 screenshots (DV90BB5245AES1)
Name the two remaining unidentified course codes (0x21 Hygiene Care, 0x4C Air Refresh) by matching the SmartThings app's course-list order against editCourseList in the reporter's dump. Also close three entity gaps the screenshots surfaced that v0.6.0 missed: dry level was read-only despite the app exposing a writable Level 1/2/3 picker; the app's "AI pattern" toggle (AiOption_On/Off) and "Auto cycle link" toggle (/cycleinterface/vs/0, previously globally ignored as always-empty on washer dumps) were both unbound. Scope that ignore to washer.py now that the dryer binds it for real.
This commit is contained in:
10 files changed
+121
-18
No files matched your search
Vendored
+2
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"ai_pattern",
|
||||
"alarm_code",
|
||||
"auto_cycle_link",
|
||||
"buzzer_sound",
|
||||
"child_lock",
|
||||
"cycle",
|
||||
|
||||
@@ -39,10 +39,46 @@ def test_no_unbound_hrefs():
|
||||
def test_expected_entities_present():
|
||||
state = _state()
|
||||
for key in ('buzzer_sound', 'cycle', 'power_switch', 'child_lock',
|
||||
'remote_control', 'dry_level', 'wrinkle_prevent', 'energy_kwh'):
|
||||
'remote_control', 'dry_level', 'wrinkle_prevent', 'energy_kwh',
|
||||
'ai_pattern', 'auto_cycle_link'):
|
||||
assert key in state, key
|
||||
|
||||
|
||||
def test_dry_level_is_a_writable_select():
|
||||
"""dryLevel used to be a read-only sensor, but the app's "Dryness"
|
||||
control (issue #14 screenshots) is a Level 1/2/3 picker, and the dump's
|
||||
supportedDryLevel list confirms the device accepts writes -- it should
|
||||
be a select, not a sensor."""
|
||||
desc = next(e for e in dryer.DRYER_SETTINGS.entities if e.key == 'dry_level')
|
||||
assert desc.options_field == 'x.com.samsung.da.supportedDryLevel'
|
||||
assert desc.write_fn is not None
|
||||
assert desc.write_fn('Less', {}) == (
|
||||
['washer', 'vs', '0'], {'x.com.samsung.da.dryLevel': 'Less'})
|
||||
assert _state()['dry_level'] == 'Normal'
|
||||
|
||||
|
||||
def test_ai_pattern_reads_and_writes_options_token():
|
||||
"""AiOption_On/Off on /course/vs/0's options[] array is the app's "AI
|
||||
pattern" toggle (issue #14 screenshot: on)."""
|
||||
assert _state()['ai_pattern'] is True
|
||||
desc = next(e for e in dryer.DRYER_COURSE.entities if e.key == 'ai_pattern')
|
||||
opts = ['Course_16', 'AiOption_On']
|
||||
href, payload = desc.write_fn('Off', {'x.com.samsung.da.options': opts})
|
||||
assert href == ['course', 'vs', '0']
|
||||
assert 'AiOption_Off' in payload['x.com.samsung.da.options']
|
||||
|
||||
|
||||
def test_auto_cycle_link_bound_to_cycleinterface():
|
||||
"""cycleInterfaceEnabled on /cycleinterface/vs/0 is the app's "Auto cycle
|
||||
link" toggle (issue #14 screenshot: off) -- previously globally ignored
|
||||
as always-empty until this dump showed it populated on a dryer."""
|
||||
assert dryer.AUTO_CYCLE_LINK.href == '/cycleinterface/vs/0'
|
||||
assert _state()['auto_cycle_link'] is False
|
||||
desc = next(e for e in dryer.AUTO_CYCLE_LINK.entities if e.key == 'auto_cycle_link')
|
||||
assert desc.write_fn('On', {}) == (
|
||||
['cycleinterface', 'vs', '0'], {'x.com.samsung.da.cycleInterfaceEnabled': 'On'})
|
||||
|
||||
|
||||
def test_job_beginning_status_reads_current_status():
|
||||
"""The dump carries x.com.samsung.da.currentStatus (not the old
|
||||
jobBeginingStatus field the dryer descriptor used to read), so the sensor
|
||||
|
||||
@@ -68,8 +68,18 @@ class TestWasherIgnoredHrefs:
|
||||
'/wm/welcomemsg/vs/0',
|
||||
'/wm/personalcourse/vs/0',
|
||||
'/energy/consumption/0',
|
||||
'/cycleinterface/vs/0',
|
||||
'/drlc/0',
|
||||
'/operational/state/0',
|
||||
):
|
||||
assert href in ignored_hrefs, f"{href} should be in ignored.IGNORED"
|
||||
|
||||
def test_cycleinterface_is_scoped_to_washer_registry(self):
|
||||
"""/cycleinterface/vs/0 is empty on every washer dump but real on the
|
||||
dryer (issue #14's AUTO_CYCLE_LINK), so it's a washer-scoped ignore
|
||||
(washer.CYCLE_INTERFACE_IGNORED) rather than a global one -- a global
|
||||
entry would collide with the dryer's real capability."""
|
||||
from custom_components.localthings.registry.by_type import washer as washer_registry
|
||||
from custom_components.localthings.registry.capabilities.ignored import IGNORED
|
||||
|
||||
assert '/cycleinterface/vs/0' not in {cap.href for cap in IGNORED}
|
||||
assert '/cycleinterface/vs/0' in washer_registry.REGISTRY.capabilities
|
||||
Reference in new issue
Block a user