Add internal air-filter support for TP1X_REF_21K refrigerators (issue #318)

/filter/airdustfilter/vs/0 was the dump's only unbound href -- this board's
internal deodorizing filter, same filterUsage/filterStatus field pair as
common.WATER_FILTER, but filterUsage here is already a 0-100 percentage
with no filterCapacity to divide by (confirmed by filterStatus=="wash" at
filterUsage=="100"). Uses air_-prefixed keys so a fridge with both a
water and an air filter gets two distinct entities.
This commit is contained in:
Marc Billow
2026-08-07 14:13:38 +00:00
parent edf77309ba
commit 26c9168fb7
6 changed files with 745 additions and 1 deletions
@@ -13,6 +13,7 @@ REGISTRY = DeviceRegistry(
fridge.STATUS_LOCK,
fridge.DOOR_ALERT,
common.WATER_FILTER,
fridge.AIR_FILTER,
dishwasher.DIAGNOSIS,
fridge.ICEMAKER_NIGHTTIME,
fridge.FLEX_ZONE,
@@ -25,7 +25,7 @@ from ..entities import (
SwitchDesc,
TimeDesc,
)
from .common import normalize_temp_unit
from .common import int_or_none, normalize_temp_unit
# Display names for the beverage zone, flex zone, ice type, and
# ice-making-status enums below live in translations/en.json, keyed by the
@@ -266,6 +266,39 @@ DOOR_ALERT = Capability(
)
# Internal deodorizing filter (issue #318, TP1X_REF_21K). Same
# filterUsage/filterStatus field pair as common.WATER_FILTER, but
# filterUsage here is already a 0-100 percentage with no filterCapacity to
# divide by (confirmed by filterStatus=="wash" at filterUsage=="100") --
# 'air_'-prefixed keys so a fridge with both a water and an air filter gets
# two distinct entities rather than a unique_id collision.
AIR_FILTER = Capability(
href="/filter/airdustfilter/vs/0",
poll_tier="cold",
entities=(
SensorDesc(
key="air_filter_usage",
field="x.com.samsung.da.filterUsage",
unit="%",
state_class="measurement",
icon="mdi:air-filter",
entity_category="diagnostic",
value_fn=int_or_none,
),
SensorDesc(
key="air_filter_status",
field="x.com.samsung.da.filterStatus",
device_class="enum",
options=("normal", "wash", "replace"),
translation_key="filter_status",
icon="mdi:air-filter",
entity_category="diagnostic",
value_fn=lambda v: v.lower() if isinstance(v, str) else v,
),
),
)
def _status_lock_write(field):
return lambda p, rep, href=None: (
["status", "lock", "vs", "0"],
@@ -0,0 +1,36 @@
{
"state_keys": [
"ai_energy_level",
"air_filter_status",
"air_filter_usage",
"alarm_code",
"auto_door_opener",
"brightness_level",
"cabinet_light_dim",
"cabinet_light_switch",
"cooler_setpoint",
"cooler_temperature",
"day_brightness",
"door_alert",
"door_cooler_open",
"door_freezer_open",
"energy_kwh",
"energy_saved_kwh",
"firmware_update",
"freezer_setpoint",
"freezer_temperature",
"fridge_sound",
"ice_night_mode",
"icemaker_one_enabled",
"icemaker_one_making_status",
"night_end",
"night_start",
"power_energy_kwh",
"power_watts",
"rapid_freezing",
"rapid_fridge",
"selfcheck_error",
"selfcheck_result",
"selfcheck_status"
]
}
@@ -0,0 +1,623 @@
{
"device0": [
{
"rt": [
"x.com.samsung.devcol",
"oic.wk.col"
],
"if": [
"oic.if.baseline",
"oic.if.ll",
"oic.if.b"
]
},
{
"href": "/alarms/vs/0",
"rep": {
"href": "/alarms/vs/0",
"rt": [
"x.com.samsung.da.alarms"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/bespoke/vs/0",
"rep": {
"x.com.samsung.da.BespokeProduct": "On",
"href": "/bespoke/vs/0"
}
},
{
"href": "/cabinet/light/enhanced/vs/0",
"rep": {
"light.control.status": "On",
"level.brightness.daytime": "100",
"level.brightness.nighttime": "33",
"night.starttime": "2026-08-07T12:00:00",
"night.duration.minute": "540",
"timezone.offset": "+09:00",
"href": "/cabinet/light/enhanced/vs/0",
"rt": [
"x.com.samsung.da.light.enhanced"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/cabinet/light/total/vs/0",
"rep": {
"x.com.samsung.da.lightLevel": "100",
"x.com.samsung.da.lightResolution": "3",
"x.com.samsung.da.lightControl.off.include": "Off",
"x.com.samsung.da.lightControl": "Off",
"x.com.samsung.da.lightControl.hide": "true",
"light.dimming.status": "On",
"href": "/cabinet/light/total/vs/0",
"rt": [
"x.com.samsung.da.cabinetlight"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/configuration/vs/0",
"rep": {
"x.com.samsung.da.region": "",
"x.com.samsung.da.countryCode": "",
"href": "/configuration/vs/0"
}
},
{
"href": "/connectionconfig/vs/0",
"rep": {
"autoReconnectionMinVersion": "1.0",
"autoReconnection": "true",
"autoReconnectionProtocolType": [
"helper_hotspot",
"ble_ocf"
],
"supportedWiFiAuthType": [
"OPEN",
"WEP",
"WPA-PSK",
"WPA2-PSK",
"SAE"
],
"supportedWiFiCryptoType": [
"TKIP",
"AES",
"WEP-64",
"WEP-128"
],
"supportedWiFiFreq": [
"2.4G"
],
"calmConnectionCare": {
"version": "1.0",
"role": [
"things"
]
}
}
},
{
"href": "/defrost/prediction/vs/0",
"rep": {
"ai.cooling.care": "Off",
"href": "/defrost/prediction/vs/0"
}
},
{
"href": "/dginformation/vs/0",
"rep": {
"enrolmentstatus": "Unknown",
"devicestate": "Unknown",
"lockstatus": "Normal",
"nextduedate": "",
"workingminutes": 0,
"paymentinfo": {
"emiplan": "Unknown",
"currency": "Unknown",
"totalemi": 0,
"totalemipaid": 0
}
}
},
{
"href": "/door/cooler/0",
"rep": {
"openState": "Close",
"href": "/door/cooler/0",
"rt": [
"oic.r.door"
],
"if": [
"oic.if.baseline",
"oic.if.s"
]
}
},
{
"href": "/door/freezer/0",
"rep": {
"openState": "Close",
"href": "/door/freezer/0",
"rt": [
"oic.r.door"
],
"if": [
"oic.if.baseline",
"oic.if.s"
]
}
},
{
"href": "/doors/vs/0",
"rep": {
"x.com.samsung.da.items": [
{
"x.com.samsung.da.openState": "Close",
"x.com.samsung.da.id": "0",
"x.com.samsung.da.description": "Door"
},
{
"x.com.samsung.da.openState": "Close",
"x.com.samsung.da.id": "1",
"x.com.samsung.da.description": "Door"
}
],
"href": "/doors/vs/0"
}
},
{
"href": "/drlc/vs/0",
"rep": {
"x.com.samsung.da.drlcLevel": "2",
"x.com.samsung.da.override": "Not_Supported",
"x.com.samsung.da.durationminutes": "1441",
"x.com.samsung.da.start": "2026-08-07T00:00:41Z",
"x.com.samsung.da.realSaving": "On",
"href": "/drlc/vs/0"
}
},
{
"href": "/energy/ailevel/vs/0",
"rep": {
"aiLevel": "1",
"supportedAiLevel": [
"1",
"2"
],
"href": "/energy/ailevel/vs/0"
}
},
{
"href": "/energy/consumption/vs/0",
"rep": {
"x.com.samsung.da.cumulativeConsumption": "25344",
"x.com.samsung.da.instantaneousPower": "48",
"x.com.samsung.da.cumulativePower": "413385",
"x.com.samsung.da.cumulativeSavedPower": "50694",
"x.com.samsung.da.cumulativeUnit": "Wh",
"x.com.samsung.da.instantaneousPowerUnit": "W",
"href": "/energy/consumption/vs/0",
"x.com.samsung.da.cumulativeDateUTC": "1786020360"
}
},
{
"href": "/file/information/vs/0",
"rep": {
"x.com.samsung.timeoffset": "+09:00",
"x.com.samsung.supprtedtype": 1,
"href": "/file/information/vs/0"
}
},
{
"href": "/filter/airdustfilter/vs/0",
"rep": {
"x.com.samsung.da.filterUsage": "100",
"x.com.samsung.da.filterUsageResolution": "1",
"x.com.samsung.da.filterResetType": [
"washable"
],
"x.com.samsung.da.filterStatus": "wash",
"href": "/filter/airdustfilter/vs/0"
}
},
{
"href": "/icemaker/nighttime/vs/0",
"rep": {
"ice.night.status": "On",
"ice.night.starttime": "2026-08-07T12:00:00",
"ice.night.duration": "540",
"ice.night.timezone": "+09:00",
"href": "/icemaker/nighttime/vs/0",
"rt": [
"x.com.samsung.da.ice.night"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/icemaker/one/vs/0",
"rep": {
"x.com.samsung.da.iceMaker.name": "ICE_MAKER",
"x.com.samsung.da.iceMaker.state": "On",
"x.com.samsung.da.iceType.desired": "NORMAL",
"x.com.samsung.da.iceMaker.iceMakingStatus": "ICESTATUS_STOP",
"x.com.samsung.da.iceMaker.type": "toggle",
"href": "/icemaker/one/vs/0",
"rt": [
"x.com.samsung.da.icemaker"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/icemaker/status/vs/0",
"rep": {
"x.com.samsung.da.iceMaker": "On",
"href": "/icemaker/status/vs/0"
}
},
{
"href": "/information/vs/0",
"rep": {
"x.com.samsung.da.modelNum": "TP1X_REF_21K|70664141|0000033C011913114100000041FB5F00",
"x.com.samsung.da.description": "TP1X_REF_21K",
"x.com.samsung.da.serialNum": "**REDACTED**",
"x.com.samsung.da.otnDUID": "**REDACTED**",
"x.com.samsung.da.diagDumpType": "file",
"x.com.samsung.da.diagEndPoint": "SSM",
"x.com.samsung.da.diagLogType": [
"errCode",
"dump"
],
"x.com.samsung.da.diagMnid": "0AJT",
"x.com.samsung.da.diagSetupid": "RR7",
"x.com.samsung.da.diagProtocolType": "BLE_OCF",
"x.com.samsung.da.diagMinVersion": "3.0",
"x.com.samsung.da.diagTsId": "DA01",
"x.com.samsung.da.items": [
{
"x.com.samsung.da.id": "0",
"x.com.samsung.da.description": "WiFi Module",
"x.com.samsung.da.type": "Software",
"x.com.samsung.da.number": "260618",
"x.com.samsung.da.newVersionAvailable": "0"
},
{
"x.com.samsung.da.id": "1",
"x.com.samsung.da.description": "Micom",
"x.com.samsung.da.type": "Firmware",
"x.com.samsung.da.number": "2605071D, 24120906, 26040701, FFFFFFFF, 25082208, FFFFFFFF",
"x.com.samsung.da.newVersionAvailable": "0"
}
],
"href": "/information/vs/0"
}
},
{
"href": "/mode/vs/0",
"rep": {
"x.com.samsung.da.supportedModes": [
"HOMECARE_WIZARD_V2",
"ENERGY_REPORT_MODEL",
"18K_REF_OUTDOOR_CONTROL_V2"
],
"href": "/mode/vs/0",
"rt": [
"x.com.samsung.da.mode"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/otninformation/vs/0",
"rep": {
"x.com.samsung.da.target": "Micom",
"x.com.samsung.da.newVersionAvailable": "false",
"x.com.samsung.da.newVersionNo": "26040701",
"x.com.samsung.da.currentVersionInfo": "10000000",
"otnStatus": "None",
"flashingProgress": "0",
"otnTarget": "inverter",
"otnCompleteDate": "noHistory",
"scheduledTime": "None",
"swVersionInfo": {
"platform": "Tizen Lite",
"oneUiVersion": "7.0 Refrigerator",
"osVersion": "4.0"
},
"otnList": [
{
"type": "WIFI",
"modelId": "A-RFWW-TP1-24-T4-RE1",
"versions": [
"20260618"
],
"visVersion": "260618"
},
{
"type": "Micom",
"modelId": "823070664141FFFFFFFF",
"versions": [
"2605071D",
"FFFFFFFF"
],
"visVersion": "260507"
},
{
"type": "Micom",
"modelId": "823070664041FFFFFFFF",
"versions": [
"24120906",
"FFFFFFFF"
],
"visVersion": "241209"
},
{
"type": "Micom",
"modelId": "02307066414170664041",
"versions": [
"2605071D",
"24120906"
],
"visVersion": "260507"
},
{
"type": "Micom",
"modelId": "023070680641FFFFFFFF",
"versions": [
"26040701",
"FFFFFFFF"
],
"visVersion": "260407"
},
{
"type": "Micom",
"modelId": "023070668841FFFFFFFF",
"versions": [
"25082208",
"FFFFFFFF"
],
"visVersion": "250822"
}
]
}
},
{
"href": "/quickcontrol/info/vs/0",
"rep": {
"supportedVersion": "1.0"
}
},
{
"href": "/realtimenotiforclient/vs/0",
"rep": {
"x.com.samsung.da.timeforshortnoti": "0",
"x.com.samsung.da.periodicnotisubscription": "true",
"href": "/realtimenotiforclient/vs/0"
}
},
{
"href": "/refrigeration/vs/0",
"rep": {
"x.com.samsung.da.rapidFridge": "Off",
"x.com.samsung.da.rapidFreezing": "Off",
"href": "/refrigeration/vs/0",
"rt": [
"x.com.samsung.da.fridge"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/rm/control/vs/0",
"rep": {
"minPeriod": "9000",
"href": "/rm/control/vs/0"
}
},
{
"href": "/runningmode/vs/0",
"rep": {
"x.com.samsung.da.runningMode": 0,
"href": "/runningmode/vs/0"
}
},
{
"href": "/selfcheck/vs/0",
"rep": {
"x.com.samsung.da.supportedActions": [
"Start"
],
"x.com.samsung.da.status": "Ready",
"x.com.samsung.da.result": "Success",
"x.com.samsung.da.error": [
"ErrorCode_None"
],
"href": "/selfcheck/vs/0"
}
},
{
"href": "/settings/sound/alert/door/vs/0",
"rep": {
"alert.door": "1",
"supportedAlert.door": [
"1",
"2",
"3",
"4"
],
"href": "/settings/sound/alert/door/vs/0",
"rt": [
"x.com.samsung.alert.door"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/status/lock/vs/0",
"rep": {
"x.com.samsung.da.device.sound": "On",
"x.com.samsung.da.preciseCooling": "On",
"x.com.samsung.da.doorAlarmSound": "On",
"cleaning.status": "On",
"cleaning.type": "SPI_AND_UV",
"href": "/status/lock/vs/0",
"rt": [
"x.com.samsung.da.lockstatus"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/temperature/current/cooler/0",
"rep": {
"temperature": 2.0,
"range": [
1.0,
7.0
],
"units": "C",
"href": "/temperature/current/cooler/0",
"rt": [
"oic.r.temperature"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/temperature/current/freezer/0",
"rep": {
"temperature": -19.0,
"range": [
-23.0,
-15.0
],
"units": "C",
"href": "/temperature/current/freezer/0",
"rt": [
"oic.r.temperature"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/temperature/desired/cooler/0",
"rep": {
"temperature": 2.0,
"range": [
1.0,
7.0
],
"units": "C",
"href": "/temperature/desired/cooler/0",
"rt": [
"oic.r.temperature"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/temperature/desired/freezer/0",
"rep": {
"temperature": -19.0,
"range": [
-23.0,
-15.0
],
"units": "C",
"href": "/temperature/desired/freezer/0",
"rt": [
"oic.r.temperature"
],
"if": [
"oic.if.baseline",
"oic.if.a"
]
}
},
{
"href": "/temperatures/vs/0",
"rep": {
"temperature.unit.control": "true",
"x.com.samsung.da.items": [
{
"x.com.samsung.da.id": "0",
"x.com.samsung.da.description": "Freezer",
"x.com.samsung.da.desired": "-19",
"x.com.samsung.da.current": "-19",
"x.com.samsung.da.maximum": "-15",
"x.com.samsung.da.minimum": "-23",
"x.com.samsung.da.unit": "Celsius"
},
{
"x.com.samsung.da.id": "1",
"x.com.samsung.da.description": "Fridge",
"x.com.samsung.da.desired": "2",
"x.com.samsung.da.current": "2",
"x.com.samsung.da.maximum": "7",
"x.com.samsung.da.minimum": "1",
"x.com.samsung.da.unit": "Celsius"
}
],
"href": "/temperatures/vs/0"
}
},
{
"href": "/timezone/vs/0",
"rep": {
"timezoneid": "Asia/Seoul",
"offset": "+09:00",
"DST": "OFF"
}
},
{
"href": "/wirelessinfo/vs/0",
"rep": {
"macaddressWiFi": "**REDACTED**",
"macaddressBLE": "**REDACTED**",
"connectedApSsid": "eomkim_IoT"
}
}
]
}
+18
View File
@@ -277,6 +277,24 @@ def test_registry_reproduces_golden_state_keys_for_tp1x_ref_21k_eu():
)
def test_registry_reproduces_golden_state_keys_for_tp1x_ref_21k_airfilter():
"""TP1X_REF_21K, air-filter-equipped variant (issue #318) -- reports
/filter/airdustfilter/vs/0 (internal deodorizing filter), the one
unbound href on this dump. Unlike airconditioner.AIR_FILTER's
filterUsage/filterCapacity pair, this board's filterUsage is already a
0-100 percentage with no filterCapacity to divide by."""
from tests.conftest import _load_device
resources = _load_device("refrigerator_tp1x_ref_21k_airfilter")
golden = json.loads((GOLDEN / "refrigerator_tp1x_ref_21k_airfilter.json").read_text())
state_keys = _new_state_keys("refrigerator_tp1x_ref_21k_airfilter", 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_range_hood():
from tests.conftest import _load_device
@@ -0,0 +1,33 @@
"""Tests for the TP1X_REF_21K air-filter-equipped variant (issue #318).
The dump's only unbound href was /filter/airdustfilter/vs/0, this board's
internal deodorizing filter -- same filterUsage/filterStatus field pair as
common.WATER_FILTER, but filterUsage here is already a 0-100 percentage
(no filterCapacity to divide by, unlike airconditioner.AIR_FILTER).
"""
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("refrigerator_tp1x_ref_21k_airfilter")
reg = resolve(resources)
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
def test_no_unbound_hrefs():
_, resources = _bound()
unbound = []
reg = resolve(resources)
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
assert unbound == []
def test_air_filter_usage_and_status():
bound, resources = _bound()
state = flatten(bound, resources)
assert state["air_filter_usage"] == 100
assert state["air_filter_status"] == "wash"