PR #316 (fork stale by several months, most of its ~2200-line diff was drift against main rather than real changes) proposed device support for the Samsung System Fresh Air Ventilator (ACA-KR-TP2-21-AN9000). Extracted what holds up, adapted to this project's conventions, and left out what doesn't: Extracted: - ventilation_mode select on CLIMATE's own href, gated via _is_ventilation_mode_device so it can only ever bind on a device whose entire supportedModes set is Purification/Ventilation/SmartVentilation -- verified against every real AC fixture in the corpus to confirm it can't false-positive on an actual air conditioner's climate card. - WINDFREE / WINDSLEEP switches on their own dedicated hrefs. - A CO2 sensor on AIR_QUALITY, matching air_monitor.SENSORS' already-bound device_class='carbon_dioxide'/unit='ppm' descriptor for the same field shape rather than guessing fresh. - HEPA_FILTER / DEVICE_ACTIVE reuse from air_purifier.py. - Removing /airlevelcheck/vs/0 from _AC_IGNORED and binding air_purifier.AIR_LEVEL_CHECK in its place: the PR's claim that this project's old "scheduler plumbing" description was wrong turned out to be independently verifiable against two of our own existing fixtures (airconditioner_cac and airconditioner_tp1x_da_ac_rac_01011 both already carry real, populated periodicSensingActivationState/autoExeState values), so this benefits existing users, not just the one new device. Left out: - Unit/device_class ('ug/m3', pm10/pm25/pm1) on the existing dust/ fine_dust/super_fine_dust sensors, sourced from an unverified third-party screenshot description. air_monitor.py already has an explicit, reasoned rejection of this exact mapping for the exact same three fields: Samsung's PM10/PM2.5 convention doesn't confirm where a third tier or a PM1 reading fits, and a wrong guess mislabels the reading forever. - A standalone common.POWER switch -- contradicts this registry's own documented design (power is deliberately the climate entity's job) and would affect every AC user, not just this device. - Promoting wind/swing to independent selects for every AC user -- a UX opinion, not a coverage necessity, and out of scope for this device's own support. - A model-name diagnostic sensor -- /information/vs/0 is already covered via the global ignore list, so this wasn't closing an actual gap. No raw diagnostics dump for this model was ever attached to PR #316, so there's no fixture for it here (fabricating one would violate this project's fixture-integrity rule) -- see tests/test_airconditioner_ventilation_windfree.py's module docstring.
120 lines
5.1 KiB
Python
120 lines
5.1 KiB
Python
"""Tests for the TP1X_DA-AC-CAC-01001_0000 cassette AC (issue #191).
|
|
|
|
0.16.0's device-type simplification dropped oneUiVersion detection on the
|
|
assumption every device it typed was already reachable via a modelNum board
|
|
token -- this board was the one exception (its oneUiVersion self-reports
|
|
"7.0 Air conditioner", but 'CAC' had never been added to the board-token
|
|
table), so it silently fell back to common caps and lost its climate entity.
|
|
|
|
This dump is NOT fully covered yet -- two hrefs remain unbound
|
|
(`/settings/sound/optimization/vs/0`, smart-sensing-cooling), both
|
|
genuinely new to this board generation. That's a real device-support gap,
|
|
left documented here rather than guessed at, per the 'don't guess' rule --
|
|
fixing the routing regression was the scope of #191.
|
|
|
|
/settings/sound/mode/vs/0, /settings/sound/output/vs/0 and
|
|
/settings/sound/volume/vs/0 used to be on this list too, until issue #319
|
|
(a sibling TP1X_DA-AC-FAC-class board) supplied a live dump for them --
|
|
airconditioner.SOUND_MODE and the reused air_purifier.SOUND_OUTPUT/
|
|
SOUND_VOLUME now cover all three here as well.
|
|
|
|
/edgelighting/vs/0 and /light/stateful/vs/0 used to be on this list too,
|
|
until issue #288 (six System A/C cassette units on this same board) gave
|
|
real dump evidence for both -- airconditioner.EDGE_LIGHTING and
|
|
LIGHT_STATEFUL now cover them.
|
|
|
|
/mds/absenceclean/vs/0 used to be on this list too -- its {mode,
|
|
supportedModes: [On, Off]} shape is byte-identical to issue #319's
|
|
/csi/absenceclean/vs/0, confirmed on that sibling board rather than
|
|
guessed, so airconditioner.MDS_ABSENCE_CLEAN now covers it too.
|
|
|
|
/uvled/vs/0 and /filter/airdustPM1filter/vs/0 used to be on this list too,
|
|
until issue #270 (TP1X_FAC_TIME_23K) added real capabilities for both --
|
|
this board's own live filterUsage/filterStatus data on the PM1 filter binds
|
|
through the same exists_fn-gated entities #270's dump (which has neither
|
|
field) leaves empty.
|
|
|
|
/airlevelcheck/vs/0 was never on this unbound list (this fixture's rep
|
|
already carried a full set of periodicSensing*/autoExeState fields), but
|
|
until PR #316 it was globally ignored by airconditioner.py's own
|
|
_AC_IGNORED as "scheduler plumbing" -- this fixture's own populated values
|
|
were the proof that description was wrong. air_purifier.AIR_LEVEL_CHECK
|
|
now covers it (see test_airlevelcheck_binds_real_ai_purify_state below).
|
|
"""
|
|
|
|
from custom_components.localthings.registry.adapter import flatten
|
|
from custom_components.localthings.registry.by_type import for_device_by_model
|
|
from custom_components.localthings.registry.discovery import discover
|
|
from tests.conftest import _load_device
|
|
|
|
_STILL_UNBOUND = frozenset(
|
|
{
|
|
"/settings/sound/optimization/vs/0",
|
|
"/smartsensingcooling/vs/0",
|
|
}
|
|
)
|
|
|
|
|
|
def _resources():
|
|
return _load_device("airconditioner_cac")
|
|
|
|
|
|
def _reg(resources):
|
|
info = resources["/information/vs/0"]
|
|
return for_device_by_model(
|
|
info["x.com.samsung.da.modelNum"], info["x.com.samsung.da.description"]
|
|
)
|
|
|
|
|
|
def test_resolves_to_airconditioner_registry():
|
|
assert _reg(_resources()).name == "airconditioner"
|
|
|
|
|
|
def test_documented_coverage_gap_is_exactly_this_set():
|
|
"""Locks in the current, known-incomplete coverage so a future fix to
|
|
any of these hrefs shows up as a golden-regression diff (extra keys) to
|
|
update here, rather than silently shrinking this list unnoticed."""
|
|
resources = _resources()
|
|
reg = _reg(resources)
|
|
unbound = []
|
|
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
|
assert set(unbound) == _STILL_UNBOUND
|
|
|
|
|
|
def test_mds_absenceclean_shares_csi_absenceclean_key():
|
|
"""/mds/absenceclean/vs/0's mode=='Off' on this dump -- confirms
|
|
MDS_ABSENCE_CLEAN actually binds (not just that the href stops
|
|
reporting as unbound)."""
|
|
resources = _resources()
|
|
reg = _reg(resources)
|
|
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
|
state = flatten(bound, resources)
|
|
assert state["absence_clean"] is False
|
|
|
|
|
|
def test_airlevelcheck_binds_real_ai_purify_state():
|
|
"""This fixture's /airlevelcheck/vs/0 has real, populated values --
|
|
periodic_air_sensing on, sensing_mode 'Alarm' -- confirming
|
|
air_purifier.AIR_LEVEL_CHECK binds real AI-Purify state here rather
|
|
than the inert plumbing _AC_IGNORED used to describe."""
|
|
resources = _resources()
|
|
reg = _reg(resources)
|
|
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
|
state = flatten(bound, resources)
|
|
assert state["periodic_air_sensing"] is True
|
|
assert state["sensing_mode"] == "Alarm"
|
|
assert state["sensing_interval"] == 30 # 1800s
|
|
assert state["air_sensing_state"] == "NonProcessing"
|
|
|
|
|
|
def test_non_legacy_board_uses_the_generic_energy_scale():
|
|
"""This board reports /wind/strength/vs/0 (not /airflow/vs/0), so
|
|
is_legacy_board() is False and it must use the plain wh_to_kwh scale,
|
|
not the /100000 correction added for the unrelated ARTIK051_KRAC-class
|
|
board in issue #193."""
|
|
resources = _resources()
|
|
reg = _reg(resources)
|
|
bound = discover(resources, reg.capabilities, reg.pattern_capabilities)
|
|
state = flatten(bound, resources)
|
|
assert state["energy_kwh"] == round(84044 / 1000.0, 2)
|