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.
87 lines
3.6 KiB
Python
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
|