From 13d2a38f5dca69e93423f17ec5aa83d1cfaac3af Mon Sep 17 00:00:00 2001 From: galaxysj Date: Mon, 10 Aug 2026 15:16:42 +0900 Subject: [PATCH] Avoid probing UUID file-transfer namespaces --- .../localthings/registry/subdevices.py | 10 +++-- tests/test_laundry_capabilities.py | 31 +++++++++++++ tests/test_subdevices.py | 43 ++++++++++++++++++- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/custom_components/localthings/registry/subdevices.py b/custom_components/localthings/registry/subdevices.py index 9b14503..ab3a642 100644 --- a/custom_components/localthings/registry/subdevices.py +++ b/custom_components/localthings/registry/subdevices.py @@ -416,8 +416,12 @@ def enumerate_subdevices( # /subdevices/vs/0 and /device/ 404s; the only trace of the sibling # is a UUID-prefixed link in /oic/res (the x.com.samsung.da.multidevice # link). Its own tree answers a full Collection at //device/0, - # exactly Pattern B's transform, so every UUID path prefix seen in - # /oic/res is treated as a candidate. _probe_prefixed's probed_ids + # exactly Pattern B's transform, so a UUID prefix attached to that + # resource type is treated as a candidate. Other UUID-prefixed links are + # not evidence of a sibling: some single-unit AC boards advertise only + # per-prefix file-transfer resources, and probing those prefixes against + # every master href needlessly burns the setup timeout budget. + # _probe_prefixed's probed_ids # guard (not a set difference against `listed`) is what keeps an id # already named by subdeviceIdList from being probed twice, since the # two sources can disagree on case. @@ -426,7 +430,7 @@ def enumerate_subdevices( m.group(1) for link in _iter_oic_res_hrefs(oic_res_links) for m in [_UUID_PREFIX_RE.match(link.get("href", ""))] - if m + if m and "x.com.samsung.da.multidevice" in (link.get("rt") or ()) } ) for sub_id in linked: diff --git a/tests/test_laundry_capabilities.py b/tests/test_laundry_capabilities.py index 2934d6c..5590e30 100644 --- a/tests/test_laundry_capabilities.py +++ b/tests/test_laundry_capabilities.py @@ -226,6 +226,37 @@ class TestCourseCodesFromSupportedOptions: "8F", ] + def test_decodes_reported_dishwasher_course_table_without_editcourse(self): + """Real DA_DW_A51_20_COMMON diagnostics: the board has no + /wm/editcourse/vs/0, so every selectable code must come from its + packed supportedOptions table, including the newer 82/8A/A7/A8/8C + family that previously appeared as raw UI text. + """ + resources = { + "/course/vs/0": { + "x.com.samsung.da.options": ["Course_82"], + "x.com.samsung.da.supportedOptions": [ + "482E0026002F00270028AE0026002F0027002A7E0026002F00270028" + "0E0006000F0027000A8E0026002F002700288E0006000F00070008C" + "E0026002F00270028DE0026002F00270028EE0006000F00270028FE" + "0006002F0027002" + ], + }, + } + + assert laundry.cycle_options(resources) == [ + "82", + "8A", + "A7", + "80", + "A8", + "88", + "8C", + "8D", + "8E", + "8F", + ] + class TestCycleSelect: def test_builds_labelled_cycle_select(self): diff --git a/tests/test_subdevices.py b/tests/test_subdevices.py index 3b4dd96..a5cbb02 100644 --- a/tests/test_subdevices.py +++ b/tests/test_subdevices.py @@ -625,7 +625,10 @@ def test_enumerate_prefixed_id_named_by_both_subdevice_id_list_and_oic_res_is_no { "di": "a", "links": [ - {"href": f"/{_UUID}/multidevice/vs/0"}, + { + "href": f"/{_UUID}/multidevice/vs/0", + "rt": ["x.com.samsung.da.multidevice"], + }, ], } ] @@ -650,7 +653,10 @@ def test_enumerate_prefixed_id_case_mismatch_between_sources_is_not_duplicated() { "di": "a", "links": [ - {"href": f"/{_UUID}/multidevice/vs/0"}, + { + "href": f"/{_UUID}/multidevice/vs/0", + "rt": ["x.com.samsung.da.multidevice"], + }, ], } ] @@ -666,6 +672,39 @@ def test_enumerate_prefixed_id_case_mismatch_between_sources_is_not_duplicated() assert subdevices[0].key.lower() == _UUID +def test_uuid_prefixed_file_links_do_not_trigger_flat_subdevice_probe(): + """A single-unit AC can advertise UUID-prefixed file-transfer links. + + They identify a transfer namespace, not a sibling indoor unit. Treating + the prefix as Pattern C makes setup probe every master href under a UUID + that never answers, consuming the whole enumeration timeout budget. + """ + oic_res = [ + { + "di": "a", + "links": [ + { + "href": f"/{_UUID}/file/transfer/vs/0", + "rt": ["x.com.samsung.file.transfer"], + }, + { + "href": f"/{_UUID}/file/list/vs/0", + "rt": ["x.com.samsung.file.list"], + }, + ], + } + ] + sess = _FakeSession({}) + probes: dict[str, bool] = {} + + subdevices, _extra = enumerate_subdevices( + sess, {}, oic_res, probe_log=probes.__setitem__ + ) + + assert subdevices == [] + assert not any(href.startswith(f"/{_UUID}/") for href in probes) + + # --------------------------------------------------------------------------- # discover_partitioned # ---------------------------------------------------------------------------