Close coverage gap and fix phantom lamp switch for TP2X_DA-KS-WALLOVEN (issue #300)
/diagnosis/vs/0 was the dump's only unbound href, now covered via dishwasher.DIAGNOSIS (same shape already reused by airconditioner.py). This steam-oven-class board's /mode/vs/0 options[] carries no UpperLamp_ token at all, unlike the NV7000BS-class board LAMP was proven against -- LAMP had no exists_fn, so it registered anyway, always read Off, and any write to it was a no-op the device had no reason to honor. Gives it the same options-token exists_fn gate issue #183 already added to fast_preheat/natural_steam/energy_saving/cooktop_on_alert.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""Oven device registry."""
|
||||
|
||||
from ..capabilities import common, ignored, oven
|
||||
from ..capabilities import common, dishwasher, ignored, oven
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
@@ -18,6 +18,9 @@ REGISTRY = DeviceRegistry(
|
||||
oven.OVEN_CONNECTED,
|
||||
oven.OVEN_SPEC,
|
||||
oven.OVEN_RECIPE_COOK,
|
||||
# issue #300: /diagnosis/vs/0 is the same diagnosisStart shape
|
||||
# dishwasher.py and airconditioner.py already reuse.
|
||||
dishwasher.DIAGNOSIS,
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
@@ -401,10 +401,17 @@ OVEN_MODE = Capability(
|
||||
value_fn=lambda v: v[0] if v else None,
|
||||
write_fn=_oven_mode_write,
|
||||
),
|
||||
# No exists_fn on the NV7000BS-class board this was proven against
|
||||
# (UpperLamp_ is always in its options[]) -- but issue #300's
|
||||
# steam-oven-class WALLOVEN board's options[] has no UpperLamp_
|
||||
# token at all, so this was a phantom, always-off, write-does-
|
||||
# nothing switch there. Same fastpreheat/NaturalSteam-class gap
|
||||
# issue #183 already fixed on the other switches below.
|
||||
SwitchDesc(
|
||||
key="lamp",
|
||||
field="x.com.samsung.da.options",
|
||||
icon="mdi:track-light",
|
||||
exists_fn=_has_option("UpperLamp"),
|
||||
value_fn=lambda opts: _option_value(opts, "UpperLamp") == "On",
|
||||
write_fn=_option_switch_write("UpperLamp"),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"alarm_code",
|
||||
"child_lock",
|
||||
"cloud_connected",
|
||||
"cook_time",
|
||||
"current_temp_c",
|
||||
"cycle_active",
|
||||
"diagnosis_status",
|
||||
"door_open",
|
||||
"energy_saving",
|
||||
"finish_time",
|
||||
"firmware_update",
|
||||
"machine_state",
|
||||
"operation_time_minutes",
|
||||
"oven_mode",
|
||||
"oven_setpoint",
|
||||
"oven_state",
|
||||
"power_switch",
|
||||
"progress_percentage",
|
||||
"remote_control",
|
||||
"sound"
|
||||
]
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"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.s"
|
||||
],
|
||||
"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": "OV_E_OFF",
|
||||
"x.com.samsung.da.triggeredTime": "2026-08-05T14:20:14"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/connected/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.connected": "On",
|
||||
"rt": [
|
||||
"x.com.samsung.da.connected"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/diagnosis/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.diagnosisStart": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/doors/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Door",
|
||||
"x.com.samsung.da.openState": "Close",
|
||||
"x.com.samsung.da.lock": "Unlock"
|
||||
}
|
||||
],
|
||||
"rt": [
|
||||
"x.com.samsung.da.doors"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/information/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.modelNum": "TP2X_DA-KS-WALLOVEN-000002|40441841|5002011E021011150100000000000000",
|
||||
"x.com.samsung.da.description": "NW9000KD/AA1",
|
||||
"x.com.samsung.da.serialNum": "**REDACTED**",
|
||||
"x.com.samsung.da.otnDUID": "**REDACTED**",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "240205",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Version",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "DE92-04418A_20062500, DE92-04011A_17041300",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
],
|
||||
"x.com.samsung.da.diagProtocolType": "WIFI_HTTPS",
|
||||
"x.com.samsung.da.diagLogType": [
|
||||
"errCode",
|
||||
"dump"
|
||||
],
|
||||
"x.com.samsung.da.diagDumpType": "file",
|
||||
"x.com.samsung.da.diagEndPoint": "SSM",
|
||||
"x.com.samsung.da.diagMnid": "0AJT",
|
||||
"x.com.samsung.da.diagSetupid": "610",
|
||||
"x.com.samsung.da.diagMinVersion": "1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/kidslock/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.kidsLock": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/mode/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.supportedModes": [
|
||||
"ConvectionBake",
|
||||
"ConvectionRoast",
|
||||
"Bake",
|
||||
"Broil",
|
||||
"SteamBake",
|
||||
"SteamRoast",
|
||||
"Easycook3",
|
||||
"Descale",
|
||||
"PyroFree",
|
||||
"Drain",
|
||||
"SelfClean",
|
||||
"NoOperation"
|
||||
],
|
||||
"x.com.samsung.da.modes": [
|
||||
"NoOperation"
|
||||
],
|
||||
"x.com.samsung.da.options": [
|
||||
"DeviceType_NW9000KD/AA1",
|
||||
"keepWarmReservation_Off",
|
||||
"meatprobe_disconnected",
|
||||
"NoPreheat_Off",
|
||||
"waterInlet_Closed",
|
||||
"steamAddLevel_0",
|
||||
"descaleAlarm_Normal",
|
||||
"descaleNewWaterAlarm_Off",
|
||||
"descaleEmptyWaterAlarm_Off",
|
||||
"steamGeneratorLevel_Empty",
|
||||
"steamUsingTime_66",
|
||||
"drainRequired_00",
|
||||
"steamState_Standby",
|
||||
"descaleState_Standby",
|
||||
"waterTankInSwitch_On",
|
||||
"pyroFreeState_Standby",
|
||||
"waterTankOutSwitch_Off",
|
||||
"Sound_On",
|
||||
"AdjustingTemp_0",
|
||||
"Sabbath_Off",
|
||||
"EnergySaving_On"
|
||||
],
|
||||
"rt": [
|
||||
"x.com.samsung.da.mode"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/operational/state/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.state": "Ready",
|
||||
"x.com.samsung.da.operationTime": "00:00:00",
|
||||
"x.com.samsung.da.remainingTime": "00:00:00",
|
||||
"x.com.samsung.da.progressPercentage": "1",
|
||||
"rt": [
|
||||
"x.com.samsung.da.operation"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/otninformation/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.target": "",
|
||||
"x.com.samsung.da.newVersionAvailable": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/oven/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.state": "Ready",
|
||||
"rt": [
|
||||
"x.com.samsung.da.oven"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/power/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.power": "On",
|
||||
"rt": [
|
||||
"x.com.samsung.da.operation"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/remotectrl/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.remoteControlEnabled": "false",
|
||||
"rt": [
|
||||
"x.com.samsung.da.configuration"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.s"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"href": "/temperatures/vs/0",
|
||||
"rep": {
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "Temperature",
|
||||
"x.com.samsung.da.desired": "0",
|
||||
"x.com.samsung.da.current": "0",
|
||||
"x.com.samsung.da.increment": "0",
|
||||
"x.com.samsung.da.unit": "Fahrenheit"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "Temperature",
|
||||
"x.com.samsung.da.desired": "0",
|
||||
"x.com.samsung.da.current": "0",
|
||||
"x.com.samsung.da.increment": "0",
|
||||
"x.com.samsung.da.unit": "Fahrenheit"
|
||||
}
|
||||
],
|
||||
"rt": [
|
||||
"x.com.samsung.da.temperatures"
|
||||
],
|
||||
"if": [
|
||||
"oic.if.baseline",
|
||||
"oic.if.a"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -152,6 +152,29 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_ailp_fac():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_oven_tp2x_ks_walloven():
|
||||
"""TP2X_DA-KS-WALLOVEN-000002 (issue #300), a steam-oven-class board
|
||||
(water tank, descale, pyro-free) quite unlike the NV7000BS-class board
|
||||
oven.py's write comments were written against. /diagnosis/vs/0 was the
|
||||
dump's only unbound href, now covered via dishwasher.DIAGNOSIS. The
|
||||
board's options[] also has no UpperLamp_ token at all -- confirms
|
||||
LAMP's new exists_fn keeps it from registering a phantom switch here,
|
||||
same fastpreheat/NaturalSteam-class gap issue #183 fixed on its
|
||||
siblings."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("oven_tp2x_ks_walloven")
|
||||
golden = json.loads((GOLDEN / "oven_tp2x_ks_walloven.json").read_text())
|
||||
state_keys = _new_state_keys(
|
||||
"oven_tp2x_ks_walloven", resources, device_types=("oic.wk.d", "oic.d.oven")
|
||||
)
|
||||
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_dehumidifier():
|
||||
"""TP1X_DA_AC_DHM_01001_0000 (issue #88, AY18CG7500GED) shares the DA_AC_
|
||||
board family with the room-AC models but carries the '_DHM_' token;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Tests for the TP2X_DA-KS-WALLOVEN-000002 steam oven (issue #300).
|
||||
|
||||
The reporter's "not fully supported" notification was /diagnosis/vs/0,
|
||||
now covered via dishwasher.DIAGNOSIS. Their "doesn't respond to commands"
|
||||
complaint is broader: oven.py's own module docstring already documents
|
||||
setpoint/cook-time/mode writes as unproven on this local-OCF firmware, and
|
||||
this board's options[] carries no UpperLamp_ token at all -- confirming
|
||||
`lamp` was previously a phantom, write-does-nothing switch here (same gap
|
||||
issue #183 fixed for fast_preheat/natural_steam on a different board).
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_oic_type, resolve
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
_DEVICE_TYPES = ("oic.wk.d", "oic.d.oven")
|
||||
|
||||
|
||||
def _bound():
|
||||
resources = _load_device("oven_tp2x_ks_walloven")
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
|
||||
|
||||
def test_oic_device_type_resolves_to_oven_registry():
|
||||
reg = for_device_by_oic_type(_DEVICE_TYPES)
|
||||
assert reg is not None and reg.name == "oven"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
resources = _load_device("oven_tp2x_ks_walloven")
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_lamp_not_bound_without_an_upperlamp_token():
|
||||
"""This board's options[] has no UpperLamp_ entry -- confirms the new
|
||||
exists_fn keeps `lamp` from registering a switch that would always
|
||||
read Off and never actually do anything on write."""
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert "lamp" not in state
|
||||
|
||||
|
||||
def test_sound_and_energy_saving_bound_from_real_options_tokens():
|
||||
"""Unlike lamp, this board's options[] does carry Sound_On and
|
||||
EnergySaving_On -- these switches should reflect real state."""
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert state["sound"] is True
|
||||
assert state["energy_saving"] is True
|
||||
Reference in New Issue
Block a user