From 6007505e6bf5460bc4d54fb8ce33d700e25cd45f Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Tue, 28 Jul 2026 14:43:21 +0000 Subject: [PATCH] Stop surfacing '_OFF'-suffixed placeholder alarm codes (#166) Samsung pre-populates /alarms/vs/0 with one row per supported alarm type, each carrying a '_OFF' placeholder code (no 'Deleted' state at all) when that alarm isn't firing. common._active_alarm_codes only ever filtered on 'Deleted' state, so every device using this shared capability (including the AC family) showed these inert placeholders -- 'ErrorCode_OFF', 'FilterAlarm_OFF' -- as if they were live alarms. Confirmed the '_OFF' suffix convention holds across every alarm code seen in this repo's fixtures so far (ErrorCode_OFF, FilterAlarm_OFF, OV_E_OFF, CT_E_OFF, WaterTankFull_OFF, AC_V_0002_OFF, all placeholders; DoorA_Opened, FilterAlarm, SNSF_Reached, all genuinely active with no suffix) -- issue #166's own dump has both a FilterAlarm_OFF placeholder and, on a second unit, a live FilterAlarm/state=Created alert, which is what motivated generalizing range_hood.py's existing (but narrower, ErrorCode_OFF-only) special case into the shared helper instead of duplicating it further. The other three points in #166 (filter-usage percentage vs. filterStatus disagreement, an "air purification" config toggle the reporter says has no physical effect, a "beep on/off" control) don't have a confirmed code fix: the percentage math already matches the device's own filterUsage/ filterCapacity fields (filterStatus is a separate device-computed field we already relay verbatim, not something we derive), the air-purify resource is correctly wired to what the board reports and its absence from the official app's own options list suggests an inert shared-board-profile stub rather than an integration bug, and a "Beep volume" NumberDesc keyed off the same Volume_100 option both dumps report already exists (0 mutes it). --- .../registry/capabilities/common.py | 20 +++++++++-- tests/test_common_capabilities.py | 34 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/custom_components/localthings/registry/capabilities/common.py b/custom_components/localthings/registry/capabilities/common.py index ac60523..4ed9bab 100644 --- a/custom_components/localthings/registry/capabilities/common.py +++ b/custom_components/localthings/registry/capabilities/common.py @@ -91,12 +91,25 @@ def _ml_to_l(v): def _active_alarm_codes(items): - """Join active alarm codes; skip retained rows Samsung leaves as Deleted. + """Join active alarm codes; skip retained rows Samsung leaves as Deleted, + and any code ending in '_OFF'. Laundry boards keep a Deleted ErrorCode row in /alarms/vs/0 after the condition clears (see WD7000B diagnostics). Surface only live alarms so - HA doesn't stick on a stale ErrorCode. Range-hood has its own stricter - helper that also drops ErrorCode_OFF. + HA doesn't stick on a stale ErrorCode. + + Samsung pre-populates this array with one row per alarm *type* the board + supports, each carrying its own '_OFF' placeholder code when that + alarm isn't firing -- confirmed across independent device families + (ErrorCode_OFF, FilterAlarm_OFF, OV_E_OFF, CT_E_OFF, WaterTankFull_OFF, + AC_V_0002_OFF all appear in fixtures with no corresponding active + condition). An alarm that's actually firing instead reports a plain, + unsuffixed code (FilterAlarm, DoorA_Opened, SNSF_Reached) -- issue #166's + AC dump has both a FilterAlarm_OFF placeholder and shows what a live + filter alert looks like: code 'FilterAlarm' (no suffix), state + 'Created'. Range-hood previously special-cased only the literal + 'ErrorCode_OFF' string in its own stricter helper; this generalizes + the same rule to the whole '_OFF' suffix convention. """ if not items or not isinstance(items, list): return 'none' @@ -105,6 +118,7 @@ def _active_alarm_codes(items): for i in items if i.get('x.com.samsung.da.code') and str(i.get('x.com.samsung.da.state', '')).lower() != 'deleted' + and not str(i.get('x.com.samsung.da.code', '')).lower().endswith('_off') ] return ', '.join(codes) if codes else 'none' diff --git a/tests/test_common_capabilities.py b/tests/test_common_capabilities.py index 2050a70..d536165 100644 --- a/tests/test_common_capabilities.py +++ b/tests/test_common_capabilities.py @@ -184,6 +184,40 @@ class TestWmSetinfoFlags: }, ]) == 'LE' + def test_off_suffixed_placeholder_codes_filtered(self): + """issue #166: Samsung pre-populates /alarms/vs/0 with one row per + supported alarm *type*, each carrying a '_OFF' placeholder + code (no 'Deleted' state at all) when that alarm isn't firing -- + confirmed not just for 'ErrorCode_OFF' but also 'FilterAlarm_OFF' + on the same dump. These were previously shown as if they were + active alarms.""" + assert common._active_alarm_codes([ + { + 'x.com.samsung.da.code': 'ErrorCode_OFF', + 'x.com.samsung.da.triggeredTime': '2026-07-28T12:59:22', + }, + { + 'x.com.samsung.da.code': 'FilterAlarm_OFF', + 'x.com.samsung.da.triggeredTime': '2026-07-28T12:59:22', + }, + ]) == 'none' + + def test_real_filter_alarm_not_off_suffixed_still_shown(self): + """issue #166's actual live filter alert: code 'FilterAlarm' (no + '_OFF' suffix), state 'Created' -- distinct from the 'FilterAlarm_OFF' + placeholder and must still surface.""" + assert common._active_alarm_codes([ + { + 'x.com.samsung.da.code': 'ErrorCode_OFF', + 'x.com.samsung.da.triggeredTime': '2026-07-28T12:59:22', + }, + { + 'x.com.samsung.da.code': 'FilterAlarm', + 'x.com.samsung.da.state': 'Created', + 'x.com.samsung.da.triggeredTime': '2026-07-28T12:59:22', + }, + ]) == 'FilterAlarm' + class TestKidsLockFallback: def test_generic_read_write(self):