From dbc5c55a97b6fd1f0cb00d4927ef4313514e807f Mon Sep 17 00:00:00 2001 From: Andy Warwick Date: Sat, 15 Aug 2026 04:00:49 +0100 Subject: [PATCH] docs(ac-filter-reset): record a board family with no local reset (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The investigation is written around the FilterTime_ option token on /mode/vs/0, and its conclusion holds for the ARTIK051_KRAC_18K it was measured on. An ARTIK051_PRAC_20K has no such token: no FilterTime, no FilterAlarmTime, no FilterCleanAlarm anywhere in its options blob. It keeps the counter in /filter/airdustfilter/vs/0 as a percentage of a 500-hour interval instead. Neither route resets it. FilterCleanAlarm_Clear to /mode/vs/0 returns 4.00 with the options blob byte-identical; writing filterUsage as the string "0" returns 4.00; writing it as an integer returns 5.00. That last difference is the useful part — two payloads differing only in JSON type returning different codes rules out an unresolved href or an unrecognised field name, leaving read-only as the reading. Adds a scope line to the intro and a section documenting the board, its two resource dumps, the attempt table, and an observation-only workaround for percentage-counter boards. --- docs/investigations/ac-filter-reset.md | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/docs/investigations/ac-filter-reset.md b/docs/investigations/ac-filter-reset.md index e9c35e1..9e4de45 100644 --- a/docs/investigations/ac-filter-reset.md +++ b/docs/investigations/ac-filter-reset.md @@ -15,6 +15,12 @@ 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. +Scope: all of the above applies to boards that carry the counter as a +`FilterTime_` option token on `/mode/vs/0`. Not every AC does. See +"Boards with no `FilterTime` token" at the end for an `ARTIK051_PRAC_20K` +that keeps the counter in its own resource, rejects both the token and a +direct write, and has no local reset at all. + ## What the reset actually is A **command**, not a value write. Samsung's cloud models it as capability @@ -92,3 +98,72 @@ on every unit on record). The entity key stays `filter_time` rather than `filter_time_elapsed`: renaming it would change every existing unit's `entity_id`/`unique_id` for a wording improvement only. + +## Boards with no `FilterTime` token (`ARTIK051_PRAC_20K`) + +Negative result, measured 2026-08-11 on integration v0.21.0 / HA 2026.8.1, +against one head of a three-head multi-split (`OptionCode_35880`, +`ExtendOptionCode_199181`). **There is no local reset on this board**, by +either route. Reset appears to be panel-only. + +This generation does not put the counter in `/mode/vs/0` at all. Its +options blob carries no `FilterTime`, no `FilterAlarmTime` and no +`FilterCleanAlarm`: + +```json +["Sleep_0", "ArtificialWorking_Off", "ComfortAICooling_Off", + "AiTempChanged_Off", "AiTemp_240", "OutdoorTemp_77", "CoolCapa_25", + "WarmCapa_32", "Light_Off", "Volume_100", "OptionCode_35880", + "ExtendOptionCode_199181", "RacInfo_None", "UpdateAllow_NotAllowed", + "DurationOn_0", "WelcomeCoolingState_Off"] +``` + +The counter lives in its own resource instead, as a **percentage** of a +500-hour interval rather than tenths of an hour — +`/filter/airdustfilter/vs/0`: + +```json +{ + "x.com.samsung.da.filterUsage": "96", + "x.com.samsung.da.filterUsageResolution": "1", + "x.com.samsung.da.filterDesiredUsage": "500", + "x.com.samsung.da.filterStatus": "normal", + "x.com.samsung.da.filterCapacity": "500", + "x.com.samsung.da.filterCapacityUnit": "Hour", + "x.com.samsung.da.filterResetType": ["replaceable", "washable"] +} +``` + +Three attempts, all against a live unit deliberately put in `fan_only` +first — writes to a powered-off head on this board are dropped silently +with no error, which would otherwise be indistinguishable from a rejected +write: + +| Target | Payload | Result | +|---|---|---| +| `/mode/vs/0` | `{"x.com.samsung.da.options": ["FilterCleanAlarm_Clear"]}` | 4.00, options blob byte-identical | +| `/filter/airdustfilter/vs/0` | `{"x.com.samsung.da.filterUsage": "0"}` | 4.00 | +| `/filter/airdustfilter/vs/0` | `{"x.com.samsung.da.filterUsage": 0}` (integer) | 5.00 | + +`filterUsage` stayed at `96` throughout, verified by a live re-read after +each write rather than by the integration's optimistic state. + +The last two rows are the informative pair. They differ only in JSON type +and return *different* codes, which rules out both boring explanations: an +unresolved href or an unrecognised field name would fail identically. The +board parses the field, faults on the wrong type, and still refuses the +value when typed as the string its own rep uses. The resource is +**read-only**, not mis-addressed. + +One trap worth stating plainly: `filterResetType: +["replaceable","washable"]` describes what the filter *is*, not a reset +command that exists. It reads like a hint that a reset write is available +somewhere. It is not. + +Since the integration cannot perform the reset here, it can still observe +it. The counter only climbs in normal use, so a downward crossing is +unambiguous: a `numeric_state` trigger with `below: 10` on +`sensor._filter_usage`, stamping an `input_datetime`, keeps an +honest "last cleaned" date without pretending a reset entity exists. The +blind spot is a reset performed while HA is down or the entry is +unloaded — no state transition, so that stamp has to be set by hand.