Close gas-cooktop coverage gap for TP2X_DA-KS-COOKTOP-000001 (issue #314)
/alarms/vs/0 and /kidslock/vs/0 were the dump's two unbound hrefs -- both are the exact shapes common.UNIVERSAL already models elsewhere (common.ALARMS, common.KIDS_LOCK_VS_FALLBACK), picked individually rather than pulling in all of UNIVERSAL to match this registry's existing hand-picked-common style. The six-vs-three burner count the reporter originally asked about is expected behavior (the board's own /mode/vs/0 options genuinely advertise six OperationState slots on hardware with three physical burners, with no per-device signal to tell real slots from phantom ones) -- already explained on the issue; this commit is scoped to the coverage warning.
This commit is contained in:
@@ -22,6 +22,12 @@ REGISTRY = DeviceRegistry(
|
|||||||
cooktop.COOKTOP_CONNECTED,
|
cooktop.COOKTOP_CONNECTED,
|
||||||
cooktop.PAIRED_HOOD_STATUS,
|
cooktop.PAIRED_HOOD_STATUS,
|
||||||
common.FIRMWARE_UPDATE,
|
common.FIRMWARE_UPDATE,
|
||||||
|
# issue #314: /alarms/vs/0 and /kidslock/vs/0 are the same
|
||||||
|
# generic shapes common.UNIVERSAL already models elsewhere --
|
||||||
|
# picked individually rather than pulling in all of UNIVERSAL,
|
||||||
|
# matching this registry's existing hand-picked-common style.
|
||||||
|
common.ALARMS,
|
||||||
|
common.KIDS_LOCK_VS_FALLBACK,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
{
|
||||||
|
"device0": [
|
||||||
|
{
|
||||||
|
"rt": [
|
||||||
|
"x.com.samsung.devcol",
|
||||||
|
"oic.wk.col"
|
||||||
|
],
|
||||||
|
"if": [
|
||||||
|
"oic.if.baseline",
|
||||||
|
"oic.if.ll",
|
||||||
|
"oic.if.b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/alarms/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"rt": [
|
||||||
|
"x.com.samsung.da.alarms"
|
||||||
|
],
|
||||||
|
"if": [
|
||||||
|
"oic.if.baseline",
|
||||||
|
"oic.if.a"
|
||||||
|
],
|
||||||
|
"x.com.samsung.da.items": [
|
||||||
|
{
|
||||||
|
"x.com.samsung.da.id": "0",
|
||||||
|
"x.com.samsung.da.description": "Alarm",
|
||||||
|
"x.com.samsung.da.alarmType": "Device",
|
||||||
|
"x.com.samsung.da.code": "CT_E",
|
||||||
|
"x.com.samsung.da.triggeredTime": "2026-08-06T14:24:02"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/bluetooth/hood/status/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"connectionState": "disconnected",
|
||||||
|
"micomModelId": "",
|
||||||
|
"firmwareVersion": "",
|
||||||
|
"power": "off",
|
||||||
|
"fanSpeed": 0,
|
||||||
|
"lampState": "off",
|
||||||
|
"timer": {},
|
||||||
|
"rt": [
|
||||||
|
"bluetoothHoodStatus"
|
||||||
|
],
|
||||||
|
"if": [
|
||||||
|
"oic.if.baseline",
|
||||||
|
"oic.if.s"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/configuration/vs/0",
|
||||||
|
"rep": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/connected/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.connected": "On",
|
||||||
|
"rt": [
|
||||||
|
"x.com.samsung.da.connected"
|
||||||
|
],
|
||||||
|
"if": [
|
||||||
|
"oic.if.baseline",
|
||||||
|
"oic.if.a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/kidslock/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.kidsLock": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/mode/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.options": [
|
||||||
|
"DeviceType_NV8000T-/KO0",
|
||||||
|
"Pause_Off",
|
||||||
|
"SyncFlex_Off",
|
||||||
|
"FlexCoil_0",
|
||||||
|
"MainTimerCurrent_0",
|
||||||
|
"MainTimerSet_0",
|
||||||
|
"MainTimerState_Ready",
|
||||||
|
"IndependentTimerCheck_Enable",
|
||||||
|
"OperationState0_Ready",
|
||||||
|
"HotSurface0_Normal",
|
||||||
|
"PowerLevel0_0",
|
||||||
|
"OperationState1_Ready",
|
||||||
|
"HotSurface1_Normal",
|
||||||
|
"PowerLevel1_0",
|
||||||
|
"OperationState2_Ready",
|
||||||
|
"HotSurface2_Normal",
|
||||||
|
"PowerLevel2_0",
|
||||||
|
"OperationState3_Ready",
|
||||||
|
"HotSurface3_Normal",
|
||||||
|
"PowerLevel3_0",
|
||||||
|
"OperationState4_Ready",
|
||||||
|
"HotSurface4_Normal",
|
||||||
|
"PowerLevel4_0",
|
||||||
|
"OperationState5_Ready",
|
||||||
|
"HotSurface5_Normal",
|
||||||
|
"PowerLevel5_0"
|
||||||
|
],
|
||||||
|
"rt": [
|
||||||
|
"x.com.samsung.da.mode"
|
||||||
|
],
|
||||||
|
"if": [
|
||||||
|
"oic.if.baseline",
|
||||||
|
"oic.if.a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/otninformation/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.target": "",
|
||||||
|
"x.com.samsung.da.newVersionAvailable": "false"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "/power/vs/0",
|
||||||
|
"rep": {
|
||||||
|
"x.com.samsung.da.power": "Off",
|
||||||
|
"rt": [
|
||||||
|
"x.com.samsung.da.operation"
|
||||||
|
],
|
||||||
|
"if": [
|
||||||
|
"oic.if.baseline",
|
||||||
|
"oic.if.a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"state_keys": [
|
||||||
|
"alarm_code",
|
||||||
|
"any_burner_active",
|
||||||
|
"burner_0_state",
|
||||||
|
"burner_1_state",
|
||||||
|
"burner_2_state",
|
||||||
|
"burner_3_state",
|
||||||
|
"burner_4_state",
|
||||||
|
"burner_5_state",
|
||||||
|
"child_lock",
|
||||||
|
"cloud_connected",
|
||||||
|
"firmware_update",
|
||||||
|
"main_timer_current",
|
||||||
|
"main_timer_state",
|
||||||
|
"paired_hood_connected",
|
||||||
|
"paired_hood_fan_speed",
|
||||||
|
"paired_hood_firmware",
|
||||||
|
"paired_hood_light",
|
||||||
|
"paired_hood_model",
|
||||||
|
"paired_hood_power",
|
||||||
|
"power_state"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"""Tests for the TP2X_DA-KS-COOKTOP-000001 gas cooktop (issue #314).
|
||||||
|
|
||||||
|
The reporter's actual complaint (six advertised burner slots when only
|
||||||
|
three are physically present) is expected: the registry declares a
|
||||||
|
generous static superset of slots and gates each on the live options array
|
||||||
|
per cooktop.py's own comment; there's no per-device signal that
|
||||||
|
distinguishes an advertised-but-nonexistent slot from a real one, so the
|
||||||
|
extra entities are disabled by the user, not filtered by the integration.
|
||||||
|
This file locks in the actual gap from that issue -- /alarms/vs/0 and
|
||||||
|
/kidslock/vs/0 -- now bound via common.ALARMS/KIDS_LOCK_VS_FALLBACK.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from custom_components.localthings.registry.adapter import flatten
|
||||||
|
from custom_components.localthings.registry.by_type import resolve
|
||||||
|
from custom_components.localthings.registry.discovery import discover
|
||||||
|
from tests.conftest import _load_device
|
||||||
|
|
||||||
|
|
||||||
|
def _bound():
|
||||||
|
resources = _load_device("gas_cooktop_tp2x_ks")
|
||||||
|
reg = resolve(resources)
|
||||||
|
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolves_to_gas_cooktop_registry():
|
||||||
|
resources = _load_device("gas_cooktop_tp2x_ks")
|
||||||
|
reg = resolve(resources)
|
||||||
|
assert reg is not None and reg.name == "gas_cooktop"
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_unbound_hrefs():
|
||||||
|
_, resources = _bound()
|
||||||
|
reg = resolve(resources)
|
||||||
|
unbound = []
|
||||||
|
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||||
|
assert unbound == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_alarm_code_and_child_lock_bound():
|
||||||
|
bound, resources = _bound()
|
||||||
|
state = flatten(bound, resources)
|
||||||
|
assert state["alarm_code"] == "CT_E"
|
||||||
|
# x.com.samsung.da.kidsLock == "Ready" reads as unlocked, same polarity
|
||||||
|
# as every other board KIDS_LOCK_VS_FALLBACK covers.
|
||||||
|
assert state["child_lock"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_advertises_six_burner_slots_only_three_of_which_are_real():
|
||||||
|
"""Not a bug to fix -- the board's own /mode/vs/0 options genuinely
|
||||||
|
list six OperationState slots (issue #314); the reporter's physical
|
||||||
|
cooktop only has three. Locks in the current, documented behavior."""
|
||||||
|
bound, resources = _bound()
|
||||||
|
state = flatten(bound, resources)
|
||||||
|
for i in range(6):
|
||||||
|
assert f"burner_{i}_state" in state
|
||||||
@@ -231,6 +231,25 @@ def test_registry_reproduces_golden_state_keys_for_water_purifier_coffee():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_registry_reproduces_golden_state_keys_for_gas_cooktop_tp2x_ks():
|
||||||
|
"""TP2X_DA-KS-COOKTOP-000001 (issue #314) -- routes via
|
||||||
|
for_device_by_resources' DeviceType_/OperationState signature, same as
|
||||||
|
the original cooktop fixture. /alarms/vs/0 and /kidslock/vs/0 were the
|
||||||
|
dump's only unbound hrefs; both are common.UNIVERSAL shapes this
|
||||||
|
registry now picks up individually (common.ALARMS,
|
||||||
|
common.KIDS_LOCK_VS_FALLBACK)."""
|
||||||
|
from tests.conftest import _load_device
|
||||||
|
|
||||||
|
resources = _load_device("gas_cooktop_tp2x_ks")
|
||||||
|
golden = json.loads((GOLDEN / "gas_cooktop_tp2x_ks.json").read_text())
|
||||||
|
state_keys = _new_state_keys("gas_cooktop_tp2x_ks", resources)
|
||||||
|
assert set(state_keys) == set(golden["state_keys"]), (
|
||||||
|
f"state_keys mismatch:\n"
|
||||||
|
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||||
|
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_registry_reproduces_golden_state_keys_for_cooktop():
|
def test_registry_reproduces_golden_state_keys_for_cooktop():
|
||||||
from tests.conftest import _load_device
|
from tests.conftest import _load_device
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user