Merge pull request #170 from mbillow/claude/issue-triage-gating-gf6bg4
Fix AC entity gating from #164, add missing preset/mode icons (#166, #169)
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"entity": {
|
||||
"climate": {
|
||||
"airconditioner": {
|
||||
"state_attributes": {
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"turbo": "mdi:speedometer",
|
||||
"max": "mdi:speedometer"
|
||||
}
|
||||
},
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"ai_comfort": "mdi:creation",
|
||||
"quiet": "mdi:volume-off",
|
||||
"smart": "mdi:brain",
|
||||
"speed": "mdi:speedometer",
|
||||
"nano": "mdi:weather-dust",
|
||||
"nanosleep": "mdi:sleep",
|
||||
"longwind": "mdi:weather-windy",
|
||||
"motiondirect": "mdi:account-arrow-right",
|
||||
"motionindirect": "mdi:account-arrow-left",
|
||||
"drycomfort": "mdi:water-percent",
|
||||
"2step": "mdi:stairs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fan": {
|
||||
"air_purifier_fan": {
|
||||
"state_attributes": {
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"smart": "mdi:brain",
|
||||
"max": "mdi:speedometer",
|
||||
"mid": "mdi:speedometer-medium",
|
||||
"windfree": "mdi:weather-dust",
|
||||
"sleep": "mdi:sleep"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"machine_state": {
|
||||
"state": {
|
||||
"idle": "mdi:power-standby",
|
||||
"active": "mdi:play",
|
||||
"pause": "mdi:pause"
|
||||
}
|
||||
},
|
||||
"connection_mode": {
|
||||
"state": {
|
||||
"observe": "mdi:broadcast",
|
||||
"poll": "mdi:sync"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user