feat(airconditioner): make absence/motion-detect enable switches writable
status on both /mds/absencepowersaving/vs/0 and /option/motiondetectwind/stateful/vs/0 is a bare On/Off boolean, the same shape already shipped writable elsewhere in this file (MUTE_ONCE, AUTO_CLEAN, AIR_PURIFY) without a live-confirmed write either -- worst case a wrong token no-ops. The paired mode selects (switchPowerSaveMode, motion-detect modes) stay read-only: their behavioral effect on live HVAC isn't inferable from the dump, same reasoning as ANOMALY_LOAD's mode field.
This commit is contained in:
@@ -678,22 +678,30 @@ ANOMALY_LOAD = Capability(
|
||||
)
|
||||
|
||||
# Absence-detection power-saving (issue #173, TP1X_LNX-AC-RAC-01001 --
|
||||
# Lennox-branded heat pump on the RAC board family): `status` toggles the
|
||||
# feature, `switchPowerSaveMode` picks the save intensity out of its own
|
||||
# supportedSwitchPowerSaveMode list. A third field, `motionState`, also
|
||||
# carries a supportedMotionState list but its role (a live sensor readout vs.
|
||||
# a sensitivity setting) isn't distinguishable from the dump, so it's left
|
||||
# unmodeled. Same 'don't guess' read-only treatment as CURRENT_LIMIT/
|
||||
# ANOMALY_LOAD above -- nothing here confirms write safety on live HVAC
|
||||
# hardware.
|
||||
# Lennox-branded heat pump on the RAC board family): `status` is a bare
|
||||
# On/Off boolean with no vendor prefix, the same shape already shipped
|
||||
# writable elsewhere in this file (MUTE_ONCE, AUTO_CLEAN, AIR_PURIFY,
|
||||
# DISPLAY_LIGHT) despite none of those having a live-confirmed write either
|
||||
# -- worst case a wrong token no-ops, same risk profile as that family, so
|
||||
# it's a switch rather than a sensor. `switchPowerSaveMode` picks the save
|
||||
# intensity out of its own supportedSwitchPowerSaveMode list, but *what*
|
||||
# writing it actually does to a running compressor isn't knowable from the
|
||||
# dump -- same 'don't guess' read-only treatment as CURRENT_LIMIT/
|
||||
# ANOMALY_LOAD's mode fields. A third field, `motionState`, also carries a
|
||||
# supportedMotionState list but its role (a live sensor readout vs. a
|
||||
# sensitivity setting) isn't distinguishable from the dump, so it's left
|
||||
# unmodeled entirely.
|
||||
ABSENCE_POWER_SAVING = Capability(
|
||||
href='/mds/absencepowersaving/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
BinarySensorDesc(key='absence_power_saving_active', field='status',
|
||||
icon='mdi:human-greeting-proximity',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: v == 'On'),
|
||||
SwitchDesc(key='absence_power_saving_active', field='status',
|
||||
icon='mdi:human-greeting-proximity',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['mds', 'absencepowersaving', 'vs', '0'],
|
||||
{'status': 'On' if p == 'On' else 'Off'})),
|
||||
SensorDesc(key='absence_power_saving_mode', field='switchPowerSaveMode',
|
||||
device_class='enum',
|
||||
options=('eco', 'normal', 'comfort'),
|
||||
@@ -704,16 +712,21 @@ ABSENCE_POWER_SAVING = Capability(
|
||||
)
|
||||
|
||||
# Avoid-direct-wind-on-motion, a sibling AI feature to ABSENCE_POWER_SAVING
|
||||
# above on the same dump: `status` toggles it, `modes` picks Direct/Indirect
|
||||
# airflow out of `supportedModes`. Same read-only treatment.
|
||||
# above on the same dump: `status` is the same bare On/Off shape, promoted to
|
||||
# a switch for the same reason. `modes` (Direct/Indirect airflow out of
|
||||
# `supportedModes`) stays read-only -- same reasoning as
|
||||
# absence_power_saving_mode above.
|
||||
MOTION_DETECT_WIND = Capability(
|
||||
href='/option/motiondetectwind/stateful/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
BinarySensorDesc(key='motion_detect_wind_active', field='status',
|
||||
icon='mdi:motion-sensor',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: v == 'On'),
|
||||
SwitchDesc(key='motion_detect_wind_active', field='status',
|
||||
icon='mdi:motion-sensor',
|
||||
entity_category='config',
|
||||
value_fn=lambda v: v == 'On',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['option', 'motiondetectwind', 'stateful', 'vs', '0'],
|
||||
{'status': 'On' if p == 'On' else 'Off'})),
|
||||
SensorDesc(key='motion_detect_wind_mode', field='modes',
|
||||
device_class='enum',
|
||||
options=('direct', 'indirect'),
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
"overload_protection_active": {
|
||||
"name": "Overload protection active"
|
||||
},
|
||||
"absence_power_saving_active": {
|
||||
"name": "Absence power saving active"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Motion-detect wind avoidance active"
|
||||
},
|
||||
"cycle_active": {
|
||||
"name": "Running"
|
||||
},
|
||||
@@ -919,6 +913,12 @@
|
||||
"auto_clean": {
|
||||
"name": "Auto clean"
|
||||
},
|
||||
"absence_power_saving_active": {
|
||||
"name": "Absence power saving active"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Motion-detect wind avoidance active"
|
||||
},
|
||||
"beep": {
|
||||
"name": "Beep"
|
||||
},
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
"overload_protection_active": {
|
||||
"name": "Overbelastingsbeveiliging actief"
|
||||
},
|
||||
"absence_power_saving_active": {
|
||||
"name": "Energiebesparing bij afwezigheid actief"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Bewegingsdetectie luchtstroomvermijding actief"
|
||||
},
|
||||
"cycle_active": {
|
||||
"name": "Actief"
|
||||
},
|
||||
@@ -919,6 +913,12 @@
|
||||
"auto_clean": {
|
||||
"name": "Automatisch reinigen"
|
||||
},
|
||||
"absence_power_saving_active": {
|
||||
"name": "Energiebesparing bij afwezigheid actief"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Bewegingsdetectie luchtstroomvermijding actief"
|
||||
},
|
||||
"beep": {
|
||||
"name": "Piep"
|
||||
},
|
||||
|
||||
@@ -514,16 +514,38 @@ def test_lnx_rac_heatpump_motion_detect_wind_state():
|
||||
assert state['motion_detect_wind_mode'] == 'indirect'
|
||||
|
||||
|
||||
def test_lnx_rac_heatpump_new_capabilities_are_read_only():
|
||||
"""Nothing in the dump confirms write safety on live HVAC hardware for
|
||||
either feature -- exposed as sensors, not switches/selects."""
|
||||
def test_lnx_rac_heatpump_enable_switches_are_writable():
|
||||
"""status is a bare On/Off boolean, same shape already shipped writable
|
||||
elsewhere in this file (MUTE_ONCE, AUTO_CLEAN) -- worst case a wrong
|
||||
token no-ops. The paired mode selects stay read-only sensors."""
|
||||
absence_keys = {e.key for e in airconditioner.ABSENCE_POWER_SAVING.entities}
|
||||
motion_keys = {e.key for e in airconditioner.MOTION_DETECT_WIND.entities}
|
||||
assert absence_keys == {'absence_power_saving_active', 'absence_power_saving_mode'}
|
||||
assert motion_keys == {'motion_detect_wind_active', 'motion_detect_wind_mode'}
|
||||
for entity in (*airconditioner.ABSENCE_POWER_SAVING.entities,
|
||||
*airconditioner.MOTION_DETECT_WIND.entities):
|
||||
assert not hasattr(entity, 'write_fn') or entity.write_fn is None
|
||||
absence_mode = next(e for e in airconditioner.ABSENCE_POWER_SAVING.entities
|
||||
if e.key == 'absence_power_saving_mode')
|
||||
motion_mode = next(e for e in airconditioner.MOTION_DETECT_WIND.entities
|
||||
if e.key == 'motion_detect_wind_mode')
|
||||
assert not hasattr(absence_mode, 'write_fn') or absence_mode.write_fn is None
|
||||
assert not hasattr(motion_mode, 'write_fn') or motion_mode.write_fn is None
|
||||
|
||||
|
||||
def test_lnx_rac_heatpump_absence_power_saving_write_target():
|
||||
write = next(e for e in airconditioner.ABSENCE_POWER_SAVING.entities
|
||||
if e.key == 'absence_power_saving_active').write_fn
|
||||
assert write('On', {}) == (
|
||||
['mds', 'absencepowersaving', 'vs', '0'], {'status': 'On'})
|
||||
assert write('Off', {}) == (
|
||||
['mds', 'absencepowersaving', 'vs', '0'], {'status': 'Off'})
|
||||
|
||||
|
||||
def test_lnx_rac_heatpump_motion_detect_wind_write_target():
|
||||
write = next(e for e in airconditioner.MOTION_DETECT_WIND.entities
|
||||
if e.key == 'motion_detect_wind_active').write_fn
|
||||
assert write('On', {}) == (
|
||||
['option', 'motiondetectwind', 'stateful', 'vs', '0'], {'status': 'On'})
|
||||
assert write('Off', {}) == (
|
||||
['option', 'motiondetectwind', 'stateful', 'vs', '0'], {'status': 'Off'})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user