Merge pull request #293 from mbillow/claude/ac-filter-reset-cleanup
feat(airconditioner): reset the legacy filter counter locally
This commit is contained in:
@@ -555,9 +555,9 @@ CLIMATE = Capability(
|
||||
),
|
||||
# Filter time in tenths of an hour, counting UP since last filter
|
||||
# reset; scale and direction confirmed against the Samsung app and
|
||||
# the /alarms/vs/0 threshold crossing (500h). No reset entity: no
|
||||
# local write path has been found -- see
|
||||
# docs/investigations/ac-filter-reset.md for what's been tried.
|
||||
# the /alarms/vs/0 threshold crossing (500h). Resettable via the
|
||||
# FilterCleanAlarm_Clear trigger token below -- see
|
||||
# docs/investigations/ac-filter-reset.md for how that was found.
|
||||
SensorDesc(
|
||||
key="filter_time",
|
||||
rep_fn=_option_token_num("FilterTime", divisor=10),
|
||||
@@ -567,6 +567,26 @@ CLIMATE = Capability(
|
||||
state_class="measurement",
|
||||
icon="mdi:air-filter",
|
||||
),
|
||||
# Resets the counter above via the FilterCleanAlarm_Clear token, the
|
||||
# same single-token options merge as every other /mode/vs/0 setting.
|
||||
# It's a trigger, not a stored setting -- the board zeroes the
|
||||
# counter on receipt and never reports this token back, which is why
|
||||
# it doesn't show up in exists_fn like the others. Confirmed on an
|
||||
# ARTIK051_KRAC_18K: FilterTime_95 -> FilterTime_0, stable across a
|
||||
# fresh DTLS session and every poll after (see the investigation doc
|
||||
# for why this was believed cloud-only until now).
|
||||
ButtonDesc(
|
||||
key="filter_time_reset",
|
||||
field="",
|
||||
payload="FilterCleanAlarm_Clear",
|
||||
icon="mdi:restart",
|
||||
entity_category="config",
|
||||
exists_fn=_has_option_token("FilterTime"),
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["mode", "vs", "0"],
|
||||
{"x.com.samsung.da.options": [p]},
|
||||
),
|
||||
),
|
||||
# FilterTime_'s threshold, exposed as a static 4-way radio
|
||||
# (180/300/500/700h, matching the app) since options[] tokens carry
|
||||
# no supported-values list to read from, unlike air_filter_threshold
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
"auto_clean_stop": {
|
||||
"name": "Zastavit samočištění"
|
||||
},
|
||||
"filter_time_reset": {
|
||||
"name": "Vynulovat počítadlo filtru"
|
||||
},
|
||||
"pause": {
|
||||
"name": "Pozastavit"
|
||||
},
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
"auto_clean_stop": {
|
||||
"name": "Stop auto clean"
|
||||
},
|
||||
"filter_time_reset": {
|
||||
"name": "Reset filter time"
|
||||
},
|
||||
"pause": {
|
||||
"name": "Pause"
|
||||
},
|
||||
|
||||
@@ -242,6 +242,9 @@
|
||||
"auto_clean_stop": {
|
||||
"name": "Detener la autolimpieza"
|
||||
},
|
||||
"filter_time_reset": {
|
||||
"name": "Restablecer el contador del filtro"
|
||||
},
|
||||
"pause": {
|
||||
"name": "Pausar"
|
||||
},
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
"auto_clean_stop": {
|
||||
"name": "Arresta autopulizia"
|
||||
},
|
||||
"filter_time_reset": {
|
||||
"name": "Azzera il contatore del filtro"
|
||||
},
|
||||
"pause": {
|
||||
"name": "Pausa"
|
||||
},
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
"auto_clean_stop": {
|
||||
"name": "자동 청소 정지"
|
||||
},
|
||||
"filter_time_reset": {
|
||||
"name": "필터 사용 시간 초기화"
|
||||
},
|
||||
"pause": {
|
||||
"name": "일시정지"
|
||||
},
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
"auto_clean_stop": {
|
||||
"name": "Zelfreiniging stoppen"
|
||||
},
|
||||
"filter_time_reset": {
|
||||
"name": "Filterteller op nul zetten"
|
||||
},
|
||||
"pause": {
|
||||
"name": "Pauzeren"
|
||||
},
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
# AC filter-time counter reset: not solved
|
||||
# AC filter-time counter reset: solved
|
||||
|
||||
`registry/capabilities/airconditioner.py`'s `filter_time` sensor
|
||||
(`FilterTime_<N>` option token, tenths of an hour) has no reset entity. This
|
||||
is where the failed attempts to find one are kept, so the next attempt starts
|
||||
from the evidence instead of from scratch. Not finding a mechanism is not the
|
||||
same as it not existing.
|
||||
(`FilterTime_<N>` option token, tenths of an hour) has a reset entity now
|
||||
(`filter_time_reset`, issue-tracked as PR #289): a single-token options write
|
||||
of `FilterCleanAlarm_Clear` to `/mode/vs/0`, the same merge every other
|
||||
setting on that href uses. Measured on an ARTIK051_KRAC_18K: `FilterTime_95`
|
||||
(9h30m) → `FilterTime_0`, still zero on a fresh DTLS session and every poll
|
||||
after; none of the other 17 tokens moved and the `/alarms/vs/0` entries
|
||||
stayed `Deleted`.
|
||||
|
||||
The rest of this file is kept as-is: the failed attempts below are still the
|
||||
best record of what *doesn't* work on this generation, and the reasoning
|
||||
that follows them explains why the reset looked cloud-only for as long as it
|
||||
did — a genuine trap worth knowing about before the next reset-adjacent
|
||||
mystery on this board family.
|
||||
|
||||
## What the reset actually is
|
||||
|
||||
Samsung models it as a **command**, not a value write: capability
|
||||
A **command**, not a value write. Samsung's cloud models it as capability
|
||||
`custom.dustFilter`, command `resetDustFilter`, no arguments (implemented in
|
||||
several SmartThings HA forks; not in the core integration). That reframes
|
||||
every attempt below — nothing changes the counter by writing to it, because
|
||||
the board zeroes it itself on receiving a command.
|
||||
several SmartThings HA forks; not in the core integration) — that command
|
||||
name is real, but see below for why POSTing it directly went nowhere.
|
||||
|
||||
## Tried, all against a live unit, all failed
|
||||
## Tried, all against a live unit, all failed (before the token above was found)
|
||||
|
||||
- `FilterTime_0` via the single-token options merge that works for every
|
||||
other setting on this href — accepted with no error, then discarded. Tried
|
||||
@@ -50,7 +58,18 @@ on `/rm/state/vs/0` was accepted (2.04 Changed, value held, restored
|
||||
afterwards), and `FilterAlarmTime_` is written through the very same options
|
||||
merge and kept. Writes work; this one value just isn't driven that way.
|
||||
|
||||
## Where to look next
|
||||
## The token, and why the dead ends below missed it
|
||||
|
||||
`FilterCleanAlarm_Clear` is not derived from anything in this file's earlier
|
||||
attempts — how it was originally identified isn't recorded here. What is
|
||||
recorded is why the standard technique (diff the appliance's reported state
|
||||
before/after triggering the action in Samsung's app) couldn't have found it
|
||||
on its own: the token is a trigger, never stored and never echoed back in
|
||||
`x.com.samsung.da.options`, so a before/after diff of stored state shows
|
||||
only the *effects* (counter zeroing, alarm clearing) and never the token
|
||||
that caused them.
|
||||
|
||||
## Dead ends tried before the token was known (kept for the next unrelated mystery)
|
||||
|
||||
- The `/actions/vs/0` action vocabulary from an independent source (a
|
||||
firmware image, or a capture of what the cloud sends the device).
|
||||
|
||||
@@ -261,6 +261,28 @@ def test_auto_clean_stop_stays_off_boards_without_the_token():
|
||||
assert "auto_clean_progress_legacy" not in _state("airconditioner_tp1x_rac")
|
||||
|
||||
|
||||
def test_filter_time_reset_writes_the_appliance_s_own_trigger_token():
|
||||
"""FilterCleanAlarm_Clear, measured on hardware: 2.04 Changed and
|
||||
FilterTime_95 -> FilterTime_0, still zero on a fresh session and every poll
|
||||
after. The token is a trigger the board acts on rather than a value it
|
||||
stores -- it never appears in options[] -- so the button is gated on the
|
||||
counter's own token being present, which is what says this board has a
|
||||
filter timer at all."""
|
||||
desc = _desc(_load_device(FIXTURE), "filter_time_reset")
|
||||
assert desc is not None
|
||||
assert desc.write_fn(desc.payload, {}) == (
|
||||
["mode", "vs", "0"],
|
||||
{"x.com.samsung.da.options": ["FilterCleanAlarm_Clear"]},
|
||||
)
|
||||
|
||||
|
||||
def test_filter_time_reset_stays_off_boards_without_the_counter():
|
||||
"""No FilterTime_ token, no counter to reset. Newer boards report filter
|
||||
usage through their own resource and would need a different mechanism, so
|
||||
offering a button that writes a legacy token there would be a guess."""
|
||||
assert _desc(_load_device("airconditioner_tp1x_rac"), "filter_time_reset") is None
|
||||
|
||||
|
||||
def test_filter_alarm_time_stays_off_boards_with_a_real_threshold_resource():
|
||||
"""Newer boards carry air_filter_threshold off supportedFilterDesiredUsage;
|
||||
two thresholds on one device would be a coin flip for the user.
|
||||
|
||||
Reference in New Issue
Block a user