airconditioner: ungate outdoor_temperature from is_legacy_board
The OutdoorTemp_ options token was only surfaced on legacy boards (is_legacy_board), even though 14 of 17 fixtures carrying the token are non-legacy. issue #367 confirmed with a 48h field capture (r=0.92 against weather.forecast_home) that the token tracks real outdoor temperature independent of board generation, and that no non-legacy board exposes an alternative outdoor-temperature resource. Split a token-presence-only exists_fn (_has_option_token_any_board) for this token, leaving _has_option_token's legacy gate untouched for the other options[] settings that still need it. The -55 offset itself was only field-validated on Celsius-locale boards, so a second gate (_reports_celsius, reading the board's own /temperatures/vs/0) keeps the sensor off the one Fahrenheit-locale fixture on record rather than guess whether the same offset and unit still apply there. Ships enabled_default=False since multi-split installs report the same token on every indoor head, which would otherwise create one duplicate active sensor per head. Updates the golden fixtures for the 13 affected Celsius-locale boards and the artik051_krac test that had asserted outdoor_temperature stays off newer boards; adds coverage for the Fahrenheit-locale gate.
This commit is contained in:
@@ -384,6 +384,21 @@ def _has_option_token_any_board(prefix):
|
||||
return lambda rep, resources: _option_token(rep, prefix) is not None
|
||||
|
||||
|
||||
def _reports_celsius(resources):
|
||||
"""Whether this subdevice's own /temperatures/vs/0 declares Celsius (or
|
||||
says nothing at all, `_temps_vs_unit`'s default).
|
||||
|
||||
Guards `OutdoorTemp_`'s -55 offset below: issue #367's field validation
|
||||
(48h against weather.forecast_home, r=0.92) ran on Celsius-locale boards
|
||||
only -- 13 of the 14 non-legacy fixtures that carry the token declare
|
||||
Celsius, and the offset's own calibration comment is a Celsius reading
|
||||
too. The one Fahrenheit-locale exception on record
|
||||
(`airconditioner_lnx_rac_heatpump`) has nothing to confirm the same
|
||||
additive constant, or degrees C rather than F, still hold -- so this
|
||||
stays off boards that declare anything else, rather than guess."""
|
||||
return _temps_vs_unit(resources.get(HREF_TEMPS_VS) or {}) == "°C"
|
||||
|
||||
|
||||
def _option_token_on(prefix):
|
||||
return lambda rep: _option_token(rep, prefix) == "On"
|
||||
|
||||
@@ -795,13 +810,14 @@ CLIMATE = Capability(
|
||||
# Outdoor temperature, offset by 55 -- calibrated against an
|
||||
# independent thermometer (token 75 while it read 20.3°C).
|
||||
#
|
||||
# exists_fn is token-presence-only (issue #367): the token was
|
||||
# previously gated behind is_legacy_board for no reason tied to the
|
||||
# token itself, which silently dropped the sensor on every
|
||||
# non-legacy board that reports it -- 14 of 17 fixtures carrying the
|
||||
# token in the issue's own survey. A 48h/289-sample field capture
|
||||
# correlated it against weather.forecast_home at r=0.92, ruling out
|
||||
# a firmware constant.
|
||||
# exists_fn is token-presence-only (issue #367), not is_legacy_board:
|
||||
# gating it there dropped the sensor on every non-legacy board that
|
||||
# reports it -- 14 of 17 fixtures carrying the token in the issue's
|
||||
# own survey. A 48h/289-sample field capture correlated it against
|
||||
# weather.forecast_home at r=0.92, ruling out a firmware constant.
|
||||
# `_reports_celsius` keeps that presence check from also claiming
|
||||
# the -55 Celsius offset for board generations it was never
|
||||
# validated on -- see its own docstring.
|
||||
#
|
||||
# enabled_default=False: multi-split installs (multiple indoor heads
|
||||
# on one outdoor condenser) report the same token on every head, so
|
||||
@@ -813,7 +829,10 @@ CLIMATE = Capability(
|
||||
SensorDesc(
|
||||
key="outdoor_temperature",
|
||||
rep_fn=_option_token_num("OutdoorTemp", offset=55),
|
||||
exists_fn=_has_option_token_any_board("OutdoorTemp"),
|
||||
exists_fn=lambda rep, resources: (
|
||||
_has_option_token_any_board("OutdoorTemp")(rep, resources)
|
||||
and _reports_celsius(resources)
|
||||
),
|
||||
device_class="temperature",
|
||||
state_class="measurement",
|
||||
unit="°C",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"mute_once",
|
||||
"odor_controller_active",
|
||||
"odor_controller_progress",
|
||||
"outdoor_temperature",
|
||||
"power_watts",
|
||||
"tropical_night_mode"
|
||||
]
|
||||
|
||||
@@ -654,6 +654,22 @@ def test_lnx_rac_heatpump_no_unbound_hrefs():
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_lnx_rac_heatpump_outdoor_temperature_stays_off_fahrenheit_boards():
|
||||
"""issue #367's -55 offset was field-validated on Celsius-locale boards
|
||||
only; this fixture's own /temperatures/vs/0 declares Fahrenheit despite
|
||||
carrying an OutdoorTemp_ token, so the sensor stays off rather than
|
||||
apply an unvalidated offset/unit to it (see _reports_celsius)."""
|
||||
reg, resources = _ac_lnx_rac_heatpump()
|
||||
temps_item = resources["/temperatures/vs/0"]["x.com.samsung.da.items"][0]
|
||||
assert temps_item["x.com.samsung.da.unit"] == "Fahrenheit"
|
||||
options = resources["/mode/vs/0"]["x.com.samsung.da.options"]
|
||||
assert any(o.startswith("OutdoorTemp_") for o in options)
|
||||
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
state = flatten(bound, resources)
|
||||
assert "outdoor_temperature" not in state
|
||||
|
||||
|
||||
def test_lnx_rac_heatpump_absence_power_saving_state():
|
||||
reg, resources = _ac_lnx_rac_heatpump()
|
||||
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
||||
|
||||
Reference in New Issue
Block a user