Files
localthings/tests/test_climate_ac_modes.py
T
Marc Billow daa7546208 Add device support for Wind-Free 2-in-1 AC TP2X_FAC_BORA_21K (#150, #153)
This board (a floor-standing + wall-mounted indoor unit pair sharing one
outdoor unit and one local IP) reports no oneUiVersion and carries the
'_FAC_' modelNum token, which no existing routing rule matched -- it fell
back to 'unknown' and exposed nothing but a power switch, with no climate
entity generated at all (both issues' reported symptom).

Once routed to the existing airconditioner registry, it binds cleanly
against the exact same CLIMATE composite every other room-AC family uses --
same Cool/Dry/Wind/AIComfort mode vocabulary, same wind-strength/humidity/
filter/energy resource shapes already modeled. Only two hrefs are unique to
this board: /subdevices/vs/0 (an opaque paired-subdevice id list -- issue
#150 asked whether the second indoor unit can be controlled separately;
it can't through this or any other resource in the dump, the same
"remote device ids, not locally actionable" role as the existing
/remotedeviceinfo/vs/0 ignore) and /runn/vs/0 (a single undocumented int
with no supported-values list to interpret). Both added to _AC_IGNORED
rather than guessed at.
2026-07-28 14:09:21 +00:00

87 lines
3.6 KiB
Python

"""Tests for the AC HVAC-mode/preset device<->HA maps in climate.py (issue #93).
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 (
_AI_COMFORT_MODE, _DEVICE_TO_FAN, _DEVICE_TO_HVAC, _HVAC_TO_DEVICE,
PRESET_AI_COMFORT, _preset_to_ha,
)
def test_auto_maps_to_hvac_auto():
"""The device's 'Auto' is a single-setpoint "device decides" mode -> HA
HVACMode.AUTO, not HEAT_COOL (issue #91 review): HEAT_COOL implies a
two-setpoint heat+cool range these single-setpoint units (including
cool-only models) don't have. AIComfort is handled separately, not
folded into this map."""
assert _DEVICE_TO_HVAC['Auto'] == HVACMode.AUTO
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_writes_back_to_plain_auto_not_aicomfort():
"""HVACMode.AUTO is reachable via async_set_hvac_mode -- it writes the
device's plain 'Auto' code. AIComfort stays reachable only through the
ai_comfort preset, since it isn't a flat _DEVICE_TO_HVAC entry (see
test_aicomfort_not_in_flat_hvac_map) and so can never win the reverse
{v: k} dict even though both map to HVACMode.AUTO conceptually."""
assert _HVAC_TO_DEVICE[HVACMode.AUTO] == 'Auto'
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_fan_only_still_reachable_via_fan():
"""'Fan' (e.g. TP1X_DA-AC-RAC-01011) is a second FAN_ONLY spelling
alongside 'Wind' -- issue #91."""
assert _DEVICE_TO_HVAC['Fan'] == HVACMode.FAN_ONLY
def test_fan_only_reverse_fallback_prefers_wind():
"""_device_code_for_hvac() resolves FAN_ONLY from a unit's own
supportedModes first, so _HVAC_TO_DEVICE is only a fallback for a unit
reporting no supportedModes at all. That fallback must stay 'Wind' (the
original single spelling, predating 'Fan') rather than silently
flipping to whichever of the two duplicate-value entries happens to
come last in _DEVICE_TO_HVAC."""
assert _HVAC_TO_DEVICE[HVACMode.FAN_ONLY] == 'Wind'
def test_preset_ai_comfort_constant():
assert PRESET_AI_COMFORT == 'ai_comfort'
def test_preset_to_ha_off_maps_to_preset_none():
from homeassistant.components.climate import PRESET_NONE
assert _preset_to_ha('Off') == PRESET_NONE
def test_preset_to_ha_lowercases_other_codes():
"""Every other device code is exposed as its lowercased self -- resolved
dynamically, not via a per-model table (issue #91)."""
assert _preset_to_ha('Sleep') == 'sleep'
assert _preset_to_ha('NanoSleep') == 'nanosleep'
assert _preset_to_ha('MotionIndirect') == 'motionindirect'
def test_fac_bora_wind_strength_codes_fit_the_standard_scale():
"""TP2X_FAC_BORA_21K's (issues #150/#153) /wind/strength/vs/0 codes
(0/2/3/4, skipping 1/'low') and modesName (Auto/Mid/High/Turbo) already
match _DEVICE_TO_FAN's own mapping exactly -- no dynamic modesName
fallback needed for this particular board, unlike issue #155's
TP1X_DA-AC-RAC-01001_0000."""
for code in ('0', '2', '3', '4'):
assert code in _DEVICE_TO_FAN