Gate AC tropical night mode and air-quality sensors on real capability signals (#166)

Issue #166 (ARxxTXFCAWKNEU, board ARTIK051_PRAC_20K) reported tropical
night mode, clean level, dust, fine dust, odor, and super fine dust
entities showing up even though the reporter's units have no such
physical features. All six were added in #164.

The Sleep_<N> options token backing tropical_night_mode is present in
every AC dump on record regardless of confirmed reality, so there's no
usable signal at boot time -- it's now registered but disabled by
default (matching the precedent already set by fridge.rack_count /
cooktop.paired_hood_model), letting units that do have it opt in.

/sensors/vs/0's item-type list has the same problem (all five types
always listed, permanently zero on this board), but there turned out
to be a real tell: a top-level x.com.samsung.da.cleanLevel scalar is
present only alongside genuinely populated readings on every dump on
record (tp1x_da_ac_rac_01011, the tp1x_da_ac_air air purifier fixture)
and absent on every all-zero ARTIK051_PRAC_20K dump, including both
#166 units and the original windfree/#17 fixtures this capability was
first verified against -- which, per their /information/vs/0, turn out
to be the same board revision as #166's units, so that "verification"
never actually proved a real sensor either. AIR_QUALITY's exists_fn now
requires that scalar, and the windfree/airconditioner golden fixtures
are updated to match (those five entities no longer bind there).
This commit is contained in:
Marc Billow committed 2026-07-28 18:12:02 +00:00
1 parent 6080d37f7d
commit 739881de16
4 files changed
+109 -20

No files matched your search

@@ -128,7 +128,27 @@ def _sensor_item_value(items, type_):
def _has_sensor_type(type_):
"""Item-type presence AND a corroborating top-level
x.com.samsung.da.cleanLevel scalar on the same /sensors/vs/0 rep.
Item-type presence alone isn't a real capability signal: issue #166
(ARxxTXFCAWKNEU, board ARTIK051_PRAC_20K) lists all five item types with
permanent zero values on both its units, the same shape as this repo's
other ARTIK051_PRAC_20K dumps (the original issue #17 dump and the
windfree fixture -- verified against the *same* board revision, per its
/information/vs/0) -- yet the #166 reporter confirmed none of these
sensors are physically present. The top-level cleanLevel scalar (separate
from the CleanLevel item inside items[]) is only ever present alongside
genuinely populated readings in every dump on record: present on
tp1x_da_ac_rac_01011 (real AC, clean_level=1) and the tp1x_da_ac_air air
purifier fixture (all five types real/nonzero), absent on every
ARTIK051_PRAC_20K dump (all zero, including both #166 units). A small
sample, but a consistent one and the only signal found that actually
explains the #166 report -- gate on it rather than leaving the always-
present item type to imply a capability that may not exist."""
def fn(rep, resources):
if 'x.com.samsung.da.cleanLevel' not in rep:
return False
return any(isinstance(i, dict) and i.get('x.com.samsung.da.type') == type_
for i in (rep.get('x.com.samsung.da.items') or []))
return fn
@@ -441,12 +461,26 @@ CLIMATE = Capability(
# Single-token option_write. Cloud: custom.airConditionerTropicalNightMode.
# Gated off the legacy board for the same reason as beep above -- its
# Sleep_ token is already the good_sleep Number below.
#
# exists_fn only proves the Sleep_ token slot is present, not that
# tropical night mode is a real feature of the unit: issue #166
# (ARxxTXFCAWKNEU) reports Sleep_0 in every dump -- the exact same
# always-there-at-zero shape as the issue #17 dump #164 was verified
# against -- yet the reporter confirmed their remote/app has no
# tropical night mode control at all. Samsung's OCF options[] blob
# carries this scaffolding token regardless of physical capability,
# so there's no reliable signal here to gate on (same 'don't guess'
# rule as elsewhere in this file, just with no signal to guess from).
# Registered but disabled by default, same precedent as
# fridge.rack_count / cooktop.paired_hood_* -- units that do have the
# feature can enable it themselves.
NumberDesc(key='tropical_night_mode', rep_fn=_tropical_night_value,
exists_fn=lambda rep, resources: (
not is_legacy_board(resources)
and _option_token(rep, 'Sleep') is not None),
write_fn=_tropical_night_write,
native_min=0, native_max=16, step=1,
enabled_default=False,
icon='mdi:weather-night', entity_category='config'),
# Settings that this board generation keeps as options[] tokens.
SwitchDesc(key='spi', rep_fn=_option_token_on('Spi'),
@@ -702,6 +736,21 @@ HUMIDITY = Capability(
# are 1- or 2-element arrays with no corroborating scalar, so they stay string
# diagnostics (see _sensor_item_value for the 2-element ambiguity and why only
# v[0] is taken). No unit is advertised on the resource, so no device_class.
#
# _has_sensor_type requires that same top-level cleanLevel scalar, not just
# item-type presence: issue #166 (ARxxTXFCAWKNEU, board ARTIK051_PRAC_20K)
# reports all five item types on both its units, values permanently
# '0'/['0','0'] -- the exact same shape as the issue #17 dump AIR_QUALITY was
# first verified against and the WindFree fixture (see
# test_air_quality_sensors_from_sensors_vs_items) -- both the *same board
# revision* per /information/vs/0, so that "verification" never actually
# proved a real sensor either. Item-type presence alone is Samsung's OCF
# scaffolding listing every known sensor type regardless of physical
# capability, not a capability signal. The top-level scalar is: it's present,
# with genuinely populated readings, on every dump with a confirmed-real
# sensor (tp1x_da_ac_rac_01011, and the tp1x_da_ac_air air purifier fixture),
# and absent on every all-zero ARTIK051_PRAC_20K dump on record, including
# both #166 units. Gate on it.
AIR_QUALITY = Capability(
href='/sensors/vs/0',
poll_tier='cold',
-5
View File
@@ -7,19 +7,14 @@
"alarm_code",
"auto_clean",
"beep",
"clean_level",
"climate",
"current_temperature_c",
"diagnosis_status",
"display_light",
"dust",
"energy_kwh",
"energy_saved_kwh",
"fine_dust",
"humidity",
"odor",
"power_watts",
"super_fine_dust",
"tropical_night_mode"
]
}
-5
View File
@@ -7,18 +7,13 @@
"alarm_code",
"auto_clean",
"beep",
"clean_level",
"climate",
"current_temperature_c",
"diagnosis_status",
"display_light",
"dust",
"energy_kwh",
"fine_dust",
"humidity",
"odor",
"power_watts",
"super_fine_dust",
"tropical_night_mode"
]
}
+60 -10
View File
@@ -564,6 +564,19 @@ def test_tropical_night_write_is_single_token_options_merge():
['mode', 'vs', '0'], {'x.com.samsung.da.options': ['Sleep_6']})
def test_tropical_night_disabled_by_default():
"""Issue #166: Sleep_0 is present in /mode/vs/0's options on every dump
seen, including the original issue #17 dump this capability was verified
against (see airconditioner_device.json / _ac()) -- yet the #166 reporter
confirmed their unit (also an ARTIK051_PRAC_20K board, per its own
/information/vs/0) has no tropical night mode feature at all. The token
slot's presence proves nothing about the physical feature, so this can't
be existence-gated any tighter than it already is -- registered but
disabled by default instead, same precedent as fridge.rack_count /
cooktop.paired_hood_model."""
assert _tropical_desc().enabled_default is False
def test_tropical_night_absent_when_no_sleep_token():
"""TP1X_DA-AC-WAC (window AC) carries no Sleep_ option -- tropical night
mode must not bind."""
@@ -661,19 +674,56 @@ def test_air_quality_sensors_from_sensors_vs_items():
by a top-level cleanLevel scalar, so it's an int measurement; the others
are string diagnostics. Dust/FineDust/SuperFineDust carry a 2-element
array whose second element is unconfirmed -- v[0] is taken as the reading
(see _sensor_item_value)."""
(see _sensor_item_value). Exercised on tp1x_da_ac_rac_01011, the fixture
proven real by the corroborating scalar (see
test_air_quality_present_with_corroborating_clean_level_scalar) --
windfree no longer applies here post-#166 fix, since it lacks that
scalar and binds no air-quality entities at all."""
reg, resources = _ac_tp1x()
state = flatten(
discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
assert state['clean_level'] == 1 # numeric (int), corroborated
for key in ('dust', 'fine_dust', 'super_fine_dust'):
assert state[key] == '0' # string diagnostic
def test_air_quality_absent_without_corroborating_clean_level_scalar():
"""Issue #166 (ARxxTXFCAWKNEU, board ARTIK051_PRAC_20K): /sensors/vs/0
lists all five item types, values permanently '0'/['0', '0'] -- the exact
same shape as the WindFree fixture, which is the *same board revision*
(see /information/vs/0: both report modelNum
'ARTIK051_PRAC_20K|10217841|...') that this capability was originally
verified against. The reporter confirmed none of these sensors are
physically present on their unit, which means that original
"verification" never actually proved a real sensor either -- item-type
presence is Samsung's OCF scaffolding, not a capability signal (see
_has_sensor_type). The tell that's actually reliable: a top-level
x.com.samsung.da.cleanLevel scalar (separate from the CleanLevel item),
present only alongside genuinely populated readings on every dump on
record. WindFree lacks it -- so post-fix, none of the five entities
should bind there at all, matching the #166 report."""
reg, resources = _ac_windfree()
state = flatten(
discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
assert state['clean_level'] == 0 # numeric (int), corroborated
for key in ('odor', 'dust', 'fine_dust', 'super_fine_dust'):
assert state[key] == '0' # string diagnostic
# tp1x_da_ac_rac_01011 is the only fixture with a non-zero air-quality
# reading -- the one that catches a value_fn regression.
reg2, resources2 = _ac_tp1x()
state2 = flatten(
discover(resources2, reg2.capabilities, reg2.pattern_capabilities), resources2)
assert state2['clean_level'] == 1
assert 'x.com.samsung.da.cleanLevel' not in resources['/sensors/vs/0']
for key in ('clean_level', 'odor', 'dust', 'fine_dust', 'super_fine_dust'):
assert key not in state, key
def test_air_quality_present_with_corroborating_clean_level_scalar():
"""tp1x_da_ac_rac_01011 carries the top-level cleanLevel scalar alongside
a genuinely populated CleanLevel reading -- the one fixture in this repo
proven real rather than placeholder, so its air-quality entities must
still bind post-fix. It has no Odor item at all (unrelated to the scalar
gate -- item-type absence, not zero-value ambiguity)."""
reg, resources = _ac_tp1x()
assert resources['/sensors/vs/0']['x.com.samsung.da.cleanLevel'] == '1'
state = flatten(
discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
assert state['clean_level'] == 1
for key in ('dust', 'fine_dust', 'super_fine_dust'):
assert key in state
assert 'odor' not in state
def test_air_quality_absent_when_no_sensor_items():