Files
localthings/tests/test_climate_ac_modes.py
T
Marc Billow fa8f1b8675 Add cool-only global RAC support: WindFree, Auto mode, display light
Finishes PR #91's contribution (pedroperosin) with the requested review
changes applied, on our own branch:

- Preset resolution is now fully dynamic, read from each unit's own
  /mode/convenient/vs/0 supportedModes instead of a static per-model
  table -- any board's convenient modes (including WindFree's Nano/
  NanoSleep) surface without code changes. Unlabelled codes across
  existing fixtures (longwind, motionindirect, motiondirect, drycomfort)
  plus the two new WindFree ones are added to en.json/nl.json.
- 'Auto' now maps to HVACMode.AUTO instead of HEAT_COOL: these are
  single-setpoint "device decides" units, not two-setpoint heat+cool
  ones. 'Fan' is added alongside 'Wind' as a second FAN_ONLY spelling;
  a new _device_code_for_hvac() picks the code from the unit's own
  supportedModes since the flat map can't disambiguate two device codes
  mapping to one HA value.
- Detection gains a hyphenated '-RAC-' modelNum fallback (alongside the
  existing '_RAC_') for cool-only global RAC variants whose
  /otninformation/vs/0 ships no swVersionInfo block.
- Adds a display_light switch sourced from /mode/vs/0's opaque options
  blob (inverted Light_On/Light_Off token) for boards with no dedicated
  /light/vs/0 switch.

Write-path behavioural change (touches the path iterated on across
#9/#17/#27/#38/#54): power now targets the vendor /power/vs/0 instead of
the OCF /power/0, and _is_on() reads vendor-first. /power/0 is absent on
several known AC boards, so the previous OCF-first read/write pair could
report and act on stale state -- consistent with #53's "can turn on but
not off". Target temperature now picks its channel (OCF pair vs. vendor
items[]) based on which the unit actually reports, reading and writing
the same one.

The vendor temperature write and the new display-light write both carry
only the changed field(s), not the whole resource -- confirmed sufficient
on the wire, the device merges the rest itself. That requires the
coordinator's optimistic cache to do the same merge on the read side so a
setpoint change doesn't blank out current/min/max/unit for the settle
window: common.py gains merge_items_field() next to the existing
merge_options_field(), and async_send_command wires it in for any write
touching x.com.samsung.da.items.

New fixture/golden for the cool-only global RAC variant (TP1X_DA-AC-RAC-
01001, AI_RAC_GLOBAL_COOLONLY_3.0); display_light added to the goldens
for boards that gain the new options-based switch. Regenerated against
current main rather than copied from the original PR, since those
goldens had already shifted (current_temperature_c/humidity from issue
#75).
2026-07-27 05:00:06 +00:00

47 lines
1.9 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_HVAC, _HVAC_TO_DEVICE, PRESET_AI_COMFORT,
)
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_preset_ai_comfort_constant():
assert PRESET_AI_COMFORT == 'ai_comfort'