Rework AIComfort as an HVACMode.AUTO + preset overlay, add unmapped-mode warning (issue #93)

AIComfort isn't a distinct thermodynamic operation like Cool/Dry/Heat --
it's an AI-driven overlay on top of the device's own 'Auto' behavior,
confirmed by A-CAWW-TP2-20-COMMON reporting both 'Auto' and 'AIComfort'
as separate, mutually-exclusive entries in /mode/vs/0's supportedModes.
Modeled the idiomatic HA way instead of a flat _DEVICE_TO_HVAC entry:
hvac_mode reports AUTO and a new 'ai_comfort' preset carries the
distinction. Entered/left only via the preset (writes the primary mode
resource, not the convenient one) -- there's no dedicated HVACMode
value for it, so it's not offered in the hvac_mode dropdown directly.

Also adds a once-per-(href, code) warning log when a device-reported
mode has no entry in the relevant map, so a future gap like this one
surfaces in the log instead of silently vanishing -- the exact failure
mode issue #93 called out ("this class of gap is invisible without
diffing against supportedModes").
This commit is contained in:
Marc Billow committed 2026-07-27 02:01:51 +00:00
1 parent 8c3250b163
commit 45931332d4
4 files changed
+93 -27

No files matched your search

+24 -18
View File
@@ -1,34 +1,40 @@
"""Tests for the AC HVAC-mode device<->HA map in climate.py (issue #93).
"""Tests for the AC HVAC-mode/preset device<->HA maps in climate.py (issue #93).
`_DEVICE_TO_HVAC`/`_HVAC_TO_DEVICE` are plain module-level dicts with no
coordinator/entity dependency, so -- like `_temps_vs_item` in
test_climate_temperature_fallback.py -- they're testable directly.
Module-level dicts/constants with no coordinator/entity dependency, so --
like `_temps_vs_item` in test_climate_temperature_fallback.py -- they're
testable directly.
"""
from homeassistant.components.climate import HVACMode
from custom_components.localthings.climate import _DEVICE_TO_HVAC, _HVAC_TO_DEVICE
def test_aicomfort_maps_to_auto():
"""A-CAWW-TP2-20-COMMON (issue #93) reports 'AIComfort' in its
supportedModes alongside 'Auto' -- a separate AI-driven auto-comfort
mode, distinct from the existing 'Auto' -> HEAT_COOL entry."""
assert _DEVICE_TO_HVAC['AIComfort'] == HVACMode.AUTO
from custom_components.localthings.climate import (
_AI_COMFORT_MODE, _DEVICE_TO_HVAC, _HVAC_TO_DEVICE, PRESET_AI_COMFORT,
)
def test_auto_still_maps_to_heat_cool():
"""'AIComfort' is additive -- the existing 'Auto' -> HEAT_COOL mapping
(a different device code) is unchanged."""
"""'Auto' is unchanged -- AIComfort is handled separately, not folded
into this map."""
assert _DEVICE_TO_HVAC['Auto'] == HVACMode.HEAT_COOL
def test_hvac_auto_writes_back_aicomfort():
"""Reverse map: selecting HA's Auto hvac_mode writes the 'AIComfort'
device code, not 'Auto' (which is reserved for HEAT_COOL)."""
assert _HVAC_TO_DEVICE[HVACMode.AUTO] == 'AIComfort'
def test_aicomfort_not_in_flat_hvac_map():
"""AIComfort isn't a flat _DEVICE_TO_HVAC entry -- it's an AI overlay on
top of 'Auto', modeled as hvac_mode=AUTO + a dedicated preset instead of
a distinct HVACMode value (see the climate.py module comment)."""
assert _AI_COMFORT_MODE not in _DEVICE_TO_HVAC
def test_hvac_auto_not_writable_via_hvac_mode():
"""HVACMode.AUTO has no _DEVICE_TO_HVAC entry, so it's unreachable via
async_set_hvac_mode -- entered/left only through the ai_comfort preset."""
assert HVACMode.AUTO not in _HVAC_TO_DEVICE
def test_fan_only_still_reachable_via_wind():
"""Guard against regressing the existing 'Wind' -> FAN_ONLY entry while
editing this map."""
assert _DEVICE_TO_HVAC['Wind'] == HVACMode.FAN_ONLY
def test_preset_ai_comfort_constant():
assert PRESET_AI_COMFORT == 'ai_comfort'