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:
Marc Billow committed 2026-07-29 03:34:18 +00:00
1 parent a6eb1c62d4
commit a7aea3764b
4 files changed
+71 -36

No files matched your search

@@ -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"
},