diff --git a/custom_components/localthings/coordinator.py b/custom_components/localthings/coordinator.py index 536391e..376a8a9 100644 --- a/custom_components/localthings/coordinator.py +++ b/custom_components/localthings/coordinator.py @@ -285,8 +285,8 @@ class LocalThingsCoordinator(DataUpdateCoordinator[dict[str, Any]]): # identity resource -- fall back to a generic per-subdevice label # rather than leaving the device unnamed. 'Subdevice ' only # makes sense for an indexed subdevice (the key is a small - # ordinal); jhkwon19-pattern (prefixed) subdevices are never more - # than one per connection today, so there's no ordinal to show. + # ordinal); UUID-prefixed subdevices are never more than one per + # connection today, so there's no ordinal to show. label = ( f'Subdevice {subdevice.key}' if subdevice.kind == 'indexed' else 'Secondary Subdevice' diff --git a/custom_components/localthings/registry/subdevices.py b/custom_components/localthings/registry/subdevices.py index f89635d..242d401 100644 --- a/custom_components/localthings/registry/subdevices.py +++ b/custom_components/localthings/registry/subdevices.py @@ -4,8 +4,8 @@ more than one logical indoor subdevice -- issue #177. Two reporters, two different board families, two genuinely different mechanisms for exposing a second indoor subdevice over one IP / one DTLS session (see DESIGN-177.md section 1 for the full evidence trail; the two -diagnostics dumps this was built against are HJcom's and jhkwon19's -- they -each filed one of the two reports this module unifies): +diagnostics dumps this was built against are HJcom's and issue #177's other +reporter's -- they each filed one of the two reports this module unifies): Pattern A -- indexed siblings (`ARTIK051_DONGLE_FAC_18K`, HJcom's board). `/oic/res` lists three complete parallel resource sets whose trailing path @@ -14,12 +14,12 @@ OCF-standard and vendor hrefs), and `/device/0`'s batch carries only the index-0 hrefs -- the sibling subdevices are reachable only via their own `/device/` collection. -Pattern B -- UUID-prefixed tree (`TP2X_FAC_BORA_21K`, jhkwon19's board). +Pattern B -- UUID-prefixed tree (`TP2X_FAC_BORA_21K`, that reporter's board). `/oic/res` hides the whole appliance tree; `/device/0`'s batch instead carries `x.com.samsung.da.subdeviceIdList` on `/subdevices/vs/0`, and that same UUID appears as a literal href prefix in `/oic/res` (`//file/list/vs/0`, ...). What's actually been confirmed live on -jhkwon19's unit is narrower than early issue #177 writeups suggested: a +that reporter's unit is narrower than early issue #177 writeups suggested: a single individual `GET //information/vs/0` was read by hand through the debug panel and came back carrying a different model/serial than the master (`TP2X_FAC_BORA_RAC_21K`, the wall-mounted subdevice, vs. the @@ -39,7 +39,7 @@ Both are "the same thing wearing different clothes": a logical subdevice is a seed collection path to poll, plus an href transform between the canonical href the registry knows (`/mode/vs/0`) and the actual on-the-wire href. The detection signals don't overlap on either captured board (HJcom's has no -`/subdevices/vs/0` at all; jhkwon19's has no `/device/1`), so no +`/subdevices/vs/0` at all; the other reporter's has no `/device/1`), so no disambiguation logic is needed -- `enumerate_subdevices` checks both and materializes any candidate whose seed answers with a non-empty batch. @@ -218,8 +218,9 @@ def normalize_seed_batch(subdevice: Subdevice, batch: dict[str, dict]) -> dict[s Indexed subdevices need no change -- the device echoes the real `/x/` href in its own `/device/` batch (confirmed against HJcom's dump). A prefixed subdevice's batch entries may or may not already carry the - `/` prefix (unconfirmed which -- jhkwon19's board was never probed - live before the subdevice id was known), so it's added when missing. + `/` prefix (unconfirmed which -- the Pattern B reporter's board was + never probed live before the subdevice id was known), so it's added + when missing. """ if subdevice.kind != 'prefixed': return batch @@ -395,7 +396,7 @@ def enumerate_subdevices( }) if not indices: # A board that hides its whole tree from /oic/res (Pattern B's - # jhkwon19 board does this too, but it has no /device/ to find + # reporter board does this too, but it has no /device/ to find # regardless) gives us nothing to enumerate from -- fall back to the # bounded speculative probe this replaces from identity.py. indices = list(_SPECULATIVE_DEVICE_INDICES) @@ -479,8 +480,8 @@ def discover_partitioned( device's registry claims that literal href) and raise a spurious coverage-gap repair. Then one pass per *candidate* subdevice over its own canonical view, resolving that subdevice's own device type from its own - `/information/vs/0` when it reports one (e.g. jhkwon19's wall subdevice - reports `TP2X_FAC_BORA_RAC_21K` -> the 'RAC' board token -> + `/information/vs/0` when it reports one (e.g. the Pattern B reporter's + wall subdevice reports `TP2X_FAC_BORA_RAC_21K` -> the 'RAC' board token -> airconditioner), falling back to the master's registry otherwise -- every AC family shares the same resource surface, and a sibling that fails to answer its own identity resource is still the same appliance diff --git a/tests/fixtures/airconditioner_fac_bora_205_flat_device.json b/tests/fixtures/airconditioner_fac_bora_205_flat_device.json index 0cfb0e6..8421ae0 100644 --- a/tests/fixtures/airconditioner_fac_bora_205_flat_device.json +++ b/tests/fixtures/airconditioner_fac_bora_205_flat_device.json @@ -716,5 +716,5 @@ "x.com.samsung.da.serialNum": "TEST-SUBDEVICE-SERIAL-0000" } }, - "seeds_note": "device0 and oic_res are real, captured from jhkwon19's issue #205 report. subdeviceIdList in device0's /subdevices/vs/0 is restored to the real ['6c2dff6d-ee5c-dad1-6a5e-000000000001'] (HA's diagnostics download redacts it, matching redact.py's 'deviceid' substring rule, not because it's account data -- same restoration airconditioner_fac_bora_2in1_device.json documents for the same field), and every other value is otherwise exactly what HA's download redacts (serials/otnDUID etc.) against the same physical TP2X_FAC_BORA_21K unit as airconditioner_fac_bora_2in1_device.json -- same subdeviceIdList UUID, same wall subdevice. Filed specifically because, contrary to the assumption that fixture's seed batch was built on, this unit's /6c2dff6d-ee5c-dad1-6a5e-000000000001/device/0 does NOT answer (subdevice_probes in the report shows it False), so the Collection-batch fallback this fixture exercises (registry.subdevices.enumerate_subdevices' per-href probe, issue #205) is what has to find the subdevice instead. The one probes entry, /6c2dff6d-ee5c-dad1-6a5e-000000000001/information/vs/0, is the same real capture already used in airconditioner_fac_bora_2in1_device.json's seed batch (issue #177 comment 5113518087) -- the only href ever actually confirmed to answer under this UUID prefix. No other /6c2dff6d-ee5c-dad1-6a5e-000000000001/* href has been confirmed live yet, so none are seeded here; this fixture's expected outcome is the candidate being found by the flat-href probe but then correctly held back by discover_partitioned's liveness gate (information alone binds no entity), matching where the real issue stands -- not a materialized climate entity, which would require guessing at unconfirmed hrefs. The probe's serialNum (\"TEST-SUBDEVICE-SERIAL-0000\") is a hand-placed placeholder for the real value, not HA's own redaction output -- same placeholder airconditioner_fac_bora_2in1_device.json uses for the identical field." + "seeds_note": "device0 and oic_res are real, captured from the reporter's issue #205 report. subdeviceIdList in device0's /subdevices/vs/0 is restored to the real ['6c2dff6d-ee5c-dad1-6a5e-000000000001'] (HA's diagnostics download redacts it, matching redact.py's 'deviceid' substring rule, not because it's account data -- same restoration airconditioner_fac_bora_2in1_device.json documents for the same field), and every other value is otherwise exactly what HA's download redacts (serials/otnDUID etc.) against the same physical TP2X_FAC_BORA_21K unit as airconditioner_fac_bora_2in1_device.json -- same subdeviceIdList UUID, same wall subdevice. Filed specifically because, contrary to the assumption that fixture's seed batch was built on, this unit's /6c2dff6d-ee5c-dad1-6a5e-000000000001/device/0 does NOT answer (subdevice_probes in the report shows it False), so the Collection-batch fallback this fixture exercises (registry.subdevices.enumerate_subdevices' per-href probe, issue #205) is what has to find the subdevice instead. The one probes entry, /6c2dff6d-ee5c-dad1-6a5e-000000000001/information/vs/0, is the same real capture already used in airconditioner_fac_bora_2in1_device.json's seed batch (issue #177 comment 5113518087) -- the only href ever actually confirmed to answer under this UUID prefix. No other /6c2dff6d-ee5c-dad1-6a5e-000000000001/* href has been confirmed live yet, so none are seeded here; this fixture's expected outcome is the candidate being found by the flat-href probe but then correctly held back by discover_partitioned's liveness gate (information alone binds no entity), matching where the real issue stands -- not a materialized climate entity, which would require guessing at unconfirmed hrefs. The probe's serialNum (\"TEST-SUBDEVICE-SERIAL-0000\") is a hand-placed placeholder for the real value, not HA's own redaction output -- same placeholder airconditioner_fac_bora_2in1_device.json uses for the identical field." } diff --git a/tests/fixtures/airconditioner_fac_bora_2in1_device.json b/tests/fixtures/airconditioner_fac_bora_2in1_device.json index cb656e5..320f0bf 100644 --- a/tests/fixtures/airconditioner_fac_bora_2in1_device.json +++ b/tests/fixtures/airconditioner_fac_bora_2in1_device.json @@ -805,5 +805,5 @@ } ] }, - "seeds_note": "device0 and oic_res are real, captured from jhkwon19's diagnostics dump for the TP2X_FAC_BORA_21K board (issue #177, the same physical device as tests/fixtures/airconditioner_fac_bora_device.json -- that fixture's redacted x.com.samsung.da.subdeviceIdList string is a deliberate, separate regression case and is NOT changed here). subdeviceIdList above is restored to the real ['6c2dff6d-ee5c-dad1-6a5e-000000000001'] reported in the issue thread (redacted in the raw capture the same way serialNum/otnDUID/deviceId are -- it matches redact.py's 'deviceid' substring rule, not because it's actually account data). The seed's /information/vs/0 rep is REAL, verbatim from the reporter's live debug-panel read of /6c2dff6d-.../information/vs/0 (serial replaced with a placeholder) -- modelNum TP2X_FAC_BORA_RAC_21K confirms this is the wall-mounted room subdevice, distinct from the master's floor subdevice TP2X_FAC_BORA_21K. Every other href in this seed batch is CONSTRUCTED (never read from this subdevice) so the subdevice's climate entity has a resource surface to bind -- per DESIGN-177.md section 4, retrieval for this pattern is batch-only, and confirming '/6c2dff6d-.../device/0' actually returns a full batch (as opposed to just answering the information probe the reporter tried by hand) is listed as a follow-up, not something this fixture can attest to." + "seeds_note": "device0 and oic_res are real, captured from the reporter's diagnostics dump for the TP2X_FAC_BORA_21K board (issue #177, the same physical device as tests/fixtures/airconditioner_fac_bora_device.json -- that fixture's redacted x.com.samsung.da.subdeviceIdList string is a deliberate, separate regression case and is NOT changed here). subdeviceIdList above is restored to the real ['6c2dff6d-ee5c-dad1-6a5e-000000000001'] reported in the issue thread (redacted in the raw capture the same way serialNum/otnDUID/deviceId are -- it matches redact.py's 'deviceid' substring rule, not because it's actually account data). The seed's /information/vs/0 rep is REAL, verbatim from the reporter's live debug-panel read of /6c2dff6d-.../information/vs/0 (serial replaced with a placeholder) -- modelNum TP2X_FAC_BORA_RAC_21K confirms this is the wall-mounted room subdevice, distinct from the master's floor subdevice TP2X_FAC_BORA_21K. Every other href in this seed batch is CONSTRUCTED (never read from this subdevice) so the subdevice's climate entity has a resource surface to bind -- per DESIGN-177.md section 4, retrieval for this pattern is batch-only, and confirming '/6c2dff6d-.../device/0' actually returns a full batch (as opposed to just answering the information probe the reporter tried by hand) is listed as a follow-up, not something this fixture can attest to." } diff --git a/tests/test_golden_regression.py b/tests/test_golden_regression.py index 540ffed..b93d3d9 100644 --- a/tests/test_golden_regression.py +++ b/tests/test_golden_regression.py @@ -942,7 +942,7 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_cac(): def test_registry_reproduces_golden_state_keys_for_airconditioner_fac_bora_2in1(): - """jhkwon19's TP2X_FAC_BORA_21K (issue #177, Pattern B -- UUID-prefixed + """The reporter's TP2X_FAC_BORA_21K (issue #177, Pattern B -- UUID-prefixed tree): device0/oic_res are real; the wall-mounted subdevice's own /information/vs/0 is real (confirmed live by the reporter), the rest of its seed tree is constructed (see the fixture's own seeds_note) -- just @@ -962,8 +962,8 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_fac_bora_2in1( def test_registry_reproduces_golden_state_keys_for_airconditioner_fac_bora_205_flat(): - """jhkwon19's same physical TP2X_FAC_BORA_21K unit as the _2in1 fixture - above, but a later capture (issue #205) where //device/0 doesn't + """The same reporter's same physical TP2X_FAC_BORA_21K unit as the _2in1 + fixture above, but a later capture (issue #205) where //device/0 doesn't answer -- contrary to what that fixture's own seed batch assumed the Collection endpoint would do. device0/oic_res are real; the only UUID-prefixed data is the one href ever actually confirmed live diff --git a/tests/test_subdevice_discovery.py b/tests/test_subdevice_discovery.py index 4657c15..49a3235 100644 --- a/tests/test_subdevice_discovery.py +++ b/tests/test_subdevice_discovery.py @@ -138,7 +138,7 @@ async def test_hjcom_sub1_device_info_links_via_device_to_master(hass: HomeAssis # --------------------------------------------------------------------------- -# jhkwon19 -- TP2X_FAC_BORA_21K, Pattern B (UUID-prefixed tree) +# Issue #177's Pattern B reporter -- TP2X_FAC_BORA_21K, UUID-prefixed tree # --------------------------------------------------------------------------- _SUB_UUID = '6c2dff6d-ee5c-dad1-6a5e-000000000001' @@ -191,7 +191,7 @@ async def test_fac_bora_2in1_unique_ids_include_subdevice_prefix(hass: HomeAssis # --------------------------------------------------------------------------- -# jhkwon19 again -- issue #205, same physical unit/UUID as above, but this +# Same reporter and physical unit/UUID again -- issue #205, but this # time //device/0 doesn't answer. Exercises enumerate_subdevices' # per-href flat-probe fallback (registry/subdevices.py) against a real # capture instead of the synthetic sessions test_subdevices.py uses. diff --git a/tests/test_subdevices.py b/tests/test_subdevices.py index f157cfb..86fcd48 100644 --- a/tests/test_subdevices.py +++ b/tests/test_subdevices.py @@ -524,8 +524,8 @@ def test_discover_partitioned_main_pass_excludes_subdevice_hrefs_from_unbound(): def test_discover_partitioned_subdevice_resolves_its_own_registry(): """A subdevice reporting its own /information/vs/0 resolves its own - device type (jhkwon19's wall subdevice: TP2X_FAC_BORA_RAC_21K -> RAC -> - airconditioner) independent of the master's.""" + device type (issue #177's real wall subdevice: TP2X_FAC_BORA_RAC_21K -> + RAC -> airconditioner) independent of the master's.""" main_cap = Capability(href='/mode/vs/0', entities=(BinarySensorDesc(key='m', field='x'),)) sub_cap = Capability(href='/mode/vs/0', entities=(BinarySensorDesc(key='m2', field='x'),)) main_reg = _FakeRegistry('main_type', {'/mode/vs/0': [main_cap]})