Files
localthings/tests/test_range_tp1x_da_ks_range_0101x.py
T
Marc Billow 5e2c23a62d Add device support for dual-cavity range TP1X_DA-KS-RANGE-0101X (issue #324)
This board (NE63T8751SG/AA-class) reports no /information/vs/0 at all --
the modelNum-based routing fallback has nothing to read -- so it fell
back to 'unknown' and lost the whole range registry (oven mode/setpoint/
door/connected, cooktop monitoring). /oic/d does carry oic.d.range,
though, so this is a routing fix, not a new capability: adds 'oic.d.range'
to _OIC_TYPE_TO_KEY.

The second oven cavity is a genuine Pattern A indexed subdevice at
/device/1 (issue #177's mechanism) -- once routing resolves the master to
the range registry, the same registry already applies to the subdevice's
canonical view and every href on both binds with zero gaps.

_discover_full gains an optional device_types param (default (), every
other fixture unaffected) so a fixture that can only route via /oic/d can
exercise the same subdevice-aware pipeline the other composite fixtures
already do.
2026-08-09 00:58:26 +00:00

93 lines
3.7 KiB
Python

"""Dual-cavity range (TP1X_DA-KS-RANGE-0101X, NE63T8751SG/AA-class, issue
#324): no /information/vs/0 at all, so the modelNum-based fallback has
nothing to read -- routing depends entirely on /oic/d's own device type
(`oic.d.range`), read separately from the /device/0 batch (see
registry/identity.py and by_type/__init__.py's `_OIC_TYPE_TO_KEY`).
The second oven cavity answers as an indexed sibling at /device/1 (Pattern
A, same mechanism as the AC family's issue #177 fixtures) -- the master's
/mode/vs/0 (defaultMode 'UpperConvectionBake') is the upper cavity, the
subdevice's canonical /mode/vs/0 (defaultMode 'LowerConvectionBake') is the
lower one.
"""
from custom_components.localthings.registry.adapter import flatten
from custom_components.localthings.registry.by_type import for_device_by_oic_type, resolve
from tests.conftest import _discover_full, _load_device_full
FIXTURE = "range_tp1x_da_ks_range_0101x"
DEVICE_TYPES = ("oic.wk.d", "oic.d.range")
def _discover():
resources, oic_res, seeds = _load_device_full(FIXTURE)
return _discover_full(resources, oic_res, seeds, DEVICE_TYPES)
def test_resolves_via_oic_type():
reg = for_device_by_oic_type(DEVICE_TYPES)
assert reg is not None and reg.name == "range"
def test_resolves_via_the_full_resolve_entrypoint():
resources, _oic_res, _seeds = _load_device_full(FIXTURE)
reg = resolve(resources, device_types=DEVICE_TYPES)
assert reg is not None and reg.name == "range"
def test_no_unbound_hrefs():
"""Every resource in the issue #324 dump binds or is ignored, on both
the master (upper cavity) and the materialized second cavity -- clears
the coverage-gap repair."""
resources, oic_res, seeds = _load_device_full(FIXTURE)
unbound = []
from custom_components.localthings.registry.registry import CAPABILITIES
from custom_components.localthings.registry.subdevices import (
discover_partitioned,
enumerate_subdevices,
)
from tests.conftest import FakeCoapSession
sess = FakeCoapSession(seeds)
candidates, extra = enumerate_subdevices(sess, resources, oic_res)
full_resources = {**resources, **extra}
discover_partitioned(
full_resources,
candidates,
resolve,
CAPABILITIES,
log=unbound.append,
oic_device_types=DEVICE_TYPES,
)
assert unbound == []
def test_second_cavity_materializes_as_an_indexed_subdevice():
_bound, materialized, skipped, _full_resources, device_type_name = _discover()
assert device_type_name == "range"
assert skipped == []
assert [(s.kind, s.key) for s in materialized] == [("indexed", "1")]
assert materialized[0].seed_path == ("device", "1")
def test_both_cavities_expose_distinct_oven_state():
bound, _materialized, _skipped, full_resources, _name = _discover()
state = flatten(bound, full_resources)
for key in ("oven_mode", "oven_setpoint", "oven_state", "machine_state", "door_open"):
assert key in state, key
# Second cavity's entities carry the subdevice1_ prefix (adapter._key) --
# the master's own keys stay unprefixed, so both cavities get distinct
# unique_ids rather than colliding on the same entity key.
assert "subdevice1_oven_mode" in state
assert "subdevice1_oven_setpoint" in state
def test_cooktop_monitoring_present_on_the_master_only():
"""The cooktop half belongs to the appliance as a whole, not either oven
cavity -- /cooktopmonitoring/vs/0 has no per-cavity index."""
bound, _materialized, _skipped, full_resources, _name = _discover()
state = flatten(bound, full_resources)
assert "cooktop_running_state" in state
assert "warming_center_state" in state
assert "subdevice1_cooktop_running_state" not in state