fridge: give DEODOR_FILTER its own entity keys, not AIR_FILTER's (code review)

DEODOR_FILTER reused AIR_FILTER.entities verbatim, so both capabilities
produced identically-keyed entities (air_filter_usage/air_filter_status)
despite living at different hrefs. adapter.flatten()'s key derivation has
no href component, so a unit reporting both /filter/airdustfilter/vs/0
and /filter/deodorfilter/vs/0 would silently clobber one filter's reading
with the other's -- the exact collision AIR_FILTER's own 'air_' prefix
was chosen to avoid against WATER_FILTER's filter_usage/filter_status.

Gives DEODOR_FILTER its own deodor_filter_usage/deodor_filter_status keys
(status still shares the filter_status translation_key, same as AIR_FILTER
already does). Updated the winecellar fixture's golden and test, and added
deodor_filter_usage to all seven translation catalogs.
This commit is contained in:
Marc Billow
2026-08-09 01:21:17 +00:00
parent 61a953d39a
commit 1f7bdc9ac6
10 changed files with 61 additions and 9 deletions
@@ -411,10 +411,35 @@ AUTO_DOOR_VARIANT = Capability(
# comment). filterUsage reads '-1' on the only dump seen (filterStatus
# 'normal'), relayed as-is rather than special-cased -- no second dump to
# confirm whether that's a real sentinel or this unit just not tracking it.
# Own 'deodor_'-prefixed keys rather than reusing AIR_FILTER.entities
# verbatim -- same collision AIR_FILTER's own 'air_' prefix was chosen to
# avoid against WATER_FILTER, and both filters are plausible on one unit
# (this device's own board reports an internal air filter on other
# TP1X_REF_21K variants).
DEODOR_FILTER = Capability(
href="/filter/deodorfilter/vs/0",
poll_tier="cold",
entities=AIR_FILTER.entities,
entities=(
SensorDesc(
key="deodor_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="deodor_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,
),
),
)
# Wine-cellar multi-compartment pantry select (issue #328): same
@@ -761,6 +761,9 @@
"air_filter_usage_hours": {
"name": "Hodiny využití filtru"
},
"deodor_filter_usage": {
"name": "Využití filtru"
},
"air_quality_standard": {
"name": "Norma kvality vzduchu"
},
@@ -761,6 +761,9 @@
"air_filter_usage_hours": {
"name": "Filternutzungsstunden"
},
"deodor_filter_usage": {
"name": "Filternutzung"
},
"air_quality_standard": {
"name": "Luftqualitätsstandard"
},
@@ -761,6 +761,9 @@
"air_filter_usage_hours": {
"name": "Filter usage hours"
},
"deodor_filter_usage": {
"name": "Filter usage"
},
"air_quality_standard": {
"name": "Air quality standard"
},
@@ -907,6 +907,9 @@
"air_filter_usage_hours": {
"name": "Horas de uso del filtro"
},
"deodor_filter_usage": {
"name": "Uso del filtro"
},
"air_quality_standard": {
"name": "Estándar de calidad del aire"
},
@@ -761,6 +761,9 @@
"air_filter_usage_hours": {
"name": "Ore di utilizzo filtro"
},
"deodor_filter_usage": {
"name": "Utilizzo filtro"
},
"air_quality_standard": {
"name": "Standard qualità aria"
},
@@ -761,6 +761,9 @@
"air_filter_usage_hours": {
"name": "필터 사용 시간"
},
"deodor_filter_usage": {
"name": "필터 사용량"
},
"air_quality_standard": {
"name": "공기질 기준"
},
@@ -761,6 +761,9 @@
"air_filter_usage_hours": {
"name": "Filterverbruik (uren)"
},
"deodor_filter_usage": {
"name": "Filterverbruik"
},
"air_quality_standard": {
"name": "Luchtkwaliteitsnorm"
},
+2 -2
View File
@@ -1,7 +1,5 @@
{
"state_keys": [
"air_filter_status",
"air_filter_usage",
"alarm_code",
"auto_door_opener",
"auto_door_sound_control",
@@ -9,6 +7,8 @@
"auto_door_voice_control",
"cabinet_light_dim",
"cabinet_light_switch",
"deodor_filter_status",
"deodor_filter_usage",
"door_winecellar_open",
"energy_kwh",
"energy_saved_kwh",
@@ -48,15 +48,21 @@ def test_auto_door_sound_control_present_here_only():
assert state["auto_door_voice_control"] is False
def test_deodor_filter_reuses_air_filter_shape():
"""Same entities as fridge.AIR_FILTER (see its own already-confirmed
percentage-not-raw-count reasoning), bound at this variant's own href."""
assert fridge.DEODOR_FILTER.entities is fridge.AIR_FILTER.entities
def test_deodor_filter_has_its_own_keys_not_air_filters():
"""Same shape/reasoning as fridge.AIR_FILTER (percentage-not-raw-count,
see its own comment) but its own 'deodor_'-prefixed keys, not a shared
tuple -- AIR_FILTER's own docstring picked 'air_' specifically to avoid
colliding with WATER_FILTER's filter_usage/filter_status, and reusing
AIR_FILTER.entities verbatim here would silently recreate that same
collision if a unit ever reports both hrefs."""
assert fridge.DEODOR_FILTER.entities != fridge.AIR_FILTER.entities
state = _state()
assert state["air_filter_status"] == "normal"
assert state["deodor_filter_status"] == "normal"
# The device's own sentinel, relayed as-is -- not a real 0-100 reading
# on this unit, and no second dump to say what else -1 could mean.
assert state["air_filter_usage"] == -1
assert state["deodor_filter_usage"] == -1
assert "air_filter_usage" not in state
assert "air_filter_status" not in state
def test_winecellar_pantry_zone_mode_options_and_write():