fix(airconditioner): model legacy-board beep as a switch, not a volume (#136)
Every ARTIK051_KRAC_18K-generation unit confirmed on hardware (three units across two reporters) only ever carries Volume_100 or Volume_Mute in /mode/vs/0's options -- never an intermediate value -- so the existing buzzer_volume Number (0-100, step 10) modeled a control this firmware doesn't have. Worse, its write path could never produce the literal 'Mute' token needed to actually turn the beep off, since it only ever wrote a plain integer string. The 'beep' switch already used on newer boards is the correct model here too; it's no longer gated off the legacy board generation, and the Number entity is removed. The WindFree-preset-not-applying report earlier in this issue self-resolved per the reporter's own follow-up testing, so no code change was needed for that part.
This commit is contained in:
7 files changed
+49
-28
No files matched your search
@@ -486,14 +486,19 @@ CLIMATE = Capability(
|
||||
icon='mdi:led-on', entity_category='config'),
|
||||
# Beep on/off from the `Volume_*` option token (Volume_Mute/Volume_100).
|
||||
# Single-token option_write; a full options RMW reverts on ARTIK051_PRAC.
|
||||
# Gated off the legacy ARTIK051 board generation (see is_legacy_board):
|
||||
# that generation's own Volume_ token is already modeled as the
|
||||
# buzzer_volume Number below, and both reading the same options[] slot
|
||||
# into two entities would be redundant.
|
||||
#
|
||||
# Applies uniformly across board generations, including legacy
|
||||
# ARTIK051 boards -- issue #136: this token was originally modeled as
|
||||
# a 0-100 buzzer_volume Number for legacy boards on the assumption it
|
||||
# was a real graduated volume level, but every unit confirmed on
|
||||
# hardware (three units across two reporters) only ever carries
|
||||
# Volume_100 or Volume_Mute, never an intermediate value, and the
|
||||
# Number's write path (a plain integer string) could never produce
|
||||
# the literal 'Mute' token needed to actually turn it off -- writing
|
||||
# '0' is simply not a token this firmware recognizes. A switch is
|
||||
# both the correct model and the actual fix.
|
||||
SwitchDesc(key='beep', rep_fn=_beep_on,
|
||||
exists_fn=lambda rep, resources: (
|
||||
not is_legacy_board(resources)
|
||||
and _option_token(rep, 'Volume') is not None),
|
||||
exists_fn=lambda rep, resources: _option_token(rep, 'Volume') is not None,
|
||||
write_fn=_beep_write,
|
||||
icon='mdi:volume-high', entity_category='config'),
|
||||
# Tropical night mode level (0-16) from the `Sleep_<N>` option token.
|
||||
@@ -539,11 +544,6 @@ CLIMATE = Capability(
|
||||
exists_fn=_has_option_token('AirMonitoring'),
|
||||
write_fn=_option_switch_write('AirMonitoring'),
|
||||
icon='mdi:air-filter', entity_category='config'),
|
||||
NumberDesc(key='buzzer_volume', rep_fn=_option_token_num('Volume'),
|
||||
exists_fn=_has_option_token('Volume'),
|
||||
write_fn=_option_number_write('Volume'),
|
||||
native_min=0, native_max=100, step=10,
|
||||
icon='mdi:volume-high', entity_category='config'),
|
||||
# "Good Sleep" timer. 0 = off; the upper bound is a guess (the token
|
||||
# carries no range hint and only 0 has been observed on hardware), so a
|
||||
# write above 0 is unverified.
|
||||
|
||||
@@ -158,9 +158,6 @@
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"buzzer_volume": {
|
||||
"name": "Beep volume"
|
||||
},
|
||||
"cook_time": {
|
||||
"name": "Cook time"
|
||||
},
|
||||
|
||||
@@ -158,9 +158,6 @@
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"buzzer_volume": {
|
||||
"name": "Zoemervolume"
|
||||
},
|
||||
"cook_time": {
|
||||
"name": "Bereidingstijd"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"air_monitoring",
|
||||
"alarm_code",
|
||||
"auto_clean_legacy",
|
||||
"buzzer_volume",
|
||||
"beep",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"diagnosis_status",
|
||||
@@ -15,4 +15,4 @@
|
||||
"outdoor_temperature",
|
||||
"spi"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"air_monitoring",
|
||||
"alarm_code",
|
||||
"auto_clean_legacy",
|
||||
"buzzer_volume",
|
||||
"beep",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"diagnosis_status",
|
||||
|
||||
@@ -102,7 +102,7 @@ def test_token_entities_present_with_calibrated_values():
|
||||
assert state['filter_time'] == 171.5
|
||||
# token - 55 == 19 C, against a 19.4 C forecast at the time of the dump.
|
||||
assert state['outdoor_temperature'] == 19.0
|
||||
assert state['buzzer_volume'] == 100.0
|
||||
assert state['beep'] is True
|
||||
assert state['good_sleep'] == 0.0
|
||||
assert state['spi'] is False
|
||||
assert state['auto_clean_legacy'] is False
|
||||
@@ -115,7 +115,7 @@ def test_token_entities_stay_off_newer_boards():
|
||||
token entities would duplicate them (auto clean) or apply a scale
|
||||
calibrated on another board generation (outdoor temperature)."""
|
||||
state = _state('airconditioner_tp1x_rac')
|
||||
for key in ('spi', 'auto_clean_legacy', 'air_monitoring', 'buzzer_volume',
|
||||
for key in ('spi', 'auto_clean_legacy', 'air_monitoring',
|
||||
'good_sleep', 'outdoor_temperature', 'filter_time'):
|
||||
assert key not in state, key
|
||||
|
||||
|
||||
@@ -814,18 +814,45 @@ def test_sensor_item_value_picks_first_value():
|
||||
|
||||
def test_beep_and_tropical_night_stay_off_legacy_krac_board():
|
||||
"""ARTIK051_KRAC_18K (issue #136) reports both a Volume_ and a Sleep_
|
||||
option token, but they're already modeled as buzzer_volume/good_sleep
|
||||
(see airconditioner.CLIMATE) -- beep/tropical_night_mode must not also
|
||||
bind there, or the same options[] slot would surface as two entities."""
|
||||
option token. Volume_ is modeled as the shared 'beep' switch (issue #136's
|
||||
buzzer_volume Number never correctly modeled this board -- see
|
||||
test_legacy_krac_board_beep_is_a_switch_not_a_volume_number below);
|
||||
Sleep_ is still good_sleep, not tropical_night_mode, on this board (see
|
||||
airconditioner.CLIMATE)."""
|
||||
reg, resources = _resolve('airconditioner_artik051_krac_18k')
|
||||
state = flatten(
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
|
||||
assert 'beep' not in state
|
||||
assert 'buzzer_volume' not in state
|
||||
assert 'tropical_night_mode' not in state
|
||||
assert state['buzzer_volume'] == 100.0
|
||||
assert state['beep'] is True
|
||||
assert state['good_sleep'] == 0.0
|
||||
|
||||
|
||||
def test_legacy_krac_board_beep_is_a_switch_not_a_volume_number():
|
||||
"""Issue #136: three real units across two reporters only ever reported
|
||||
Volume_100 or Volume_Mute -- never an intermediate value -- and the old
|
||||
buzzer_volume Number's write path (a plain integer string) could never
|
||||
produce the literal 'Mute' token needed to turn it off. 'beep' now
|
||||
applies uniformly across board generations instead of being gated off
|
||||
the legacy board."""
|
||||
reg, resources = _resolve('airconditioner_artik051_krac_18k')
|
||||
state = flatten(
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
|
||||
assert state['beep'] is True
|
||||
|
||||
resources['/mode/vs/0']['x.com.samsung.da.options'] = [
|
||||
o.replace('Volume_100', 'Volume_Mute')
|
||||
for o in resources['/mode/vs/0']['x.com.samsung.da.options']
|
||||
]
|
||||
state = flatten(
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities), resources)
|
||||
assert state['beep'] is False
|
||||
|
||||
path, body = airconditioner._beep_write('Off', resources['/mode/vs/0'])
|
||||
assert path == ['mode', 'vs', '0']
|
||||
assert body == {'x.com.samsung.da.options': ['Volume_Mute']}
|
||||
|
||||
|
||||
def test_legacy_krac_board_energy_kwh_uses_centiwatt_hour_scale():
|
||||
"""Issue #193: this legacy ARTIK051 board reports cumulativePower in
|
||||
centiwatt-hours (raw '117430000'), not the plain Wh common.wh_to_kwh
|
||||
|
||||
Reference in new issue
Block a user