Add device support for AILP_DA-AC-FAC-02011 air conditioner (issue #319)
This board routes purely via /oic/d's oic.d.airconditioner type (no board-token match) and reports several resources the sibling TP1X_DA-AC-CAC-01001 board (issue #191) left as a documented gap: - /display/vs/0, /settings/sound/output/vs/0, /settings/sound/volume/vs/0 now reuse air_purifier.py's identical-shape capabilities instead of duplicating them. - /settings/sound/mode/vs/0 gets a new airconditioner.SOUND_MODE reading the live supportedModes field, sharing laundry.py's existing voice/tone/mute translation catalog since the value vocabulary matches. - /csi/absenceclean/vs/0 and /csi/energysaving/vs/0 are new, genuinely useful controls (absence auto-clean toggle, energy-saving mode select plus its state/operatingStatus diagnostics). - /dnd/autosleep/vs/0, /outdoorsharing/vs/0, /lifestyle/survey/vs/0, /settings/sound/voice/vs/0 and /csi/information/vs/0 are ignored as plumbing/unconfirmed data with no user-actionable state. Also fixes a latent bug in air_purifier.SOUND_VOLUME: boards that report minLevel/resolution but no maxLevel (this one) would have produced a min=0/max=0 number entity instead of self-gating off. Updates test_airconditioner_cac.py's documented coverage gap now that sound_mode/sound_output/sound_volume are covered there too.
This commit is contained in:
@@ -17,7 +17,7 @@ uses unconditionally.
|
||||
Reuses dishwasher.DIAGNOSIS for /diagnosis/vs/0.
|
||||
"""
|
||||
|
||||
from ..capabilities import airconditioner, common, dishwasher, ignored
|
||||
from ..capabilities import air_purifier, airconditioner, common, dishwasher, ignored
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
@@ -46,6 +46,15 @@ REGISTRY = DeviceRegistry(
|
||||
airconditioner.CURRENT_TEMPERATURE,
|
||||
airconditioner.CURRENT_TEMPERATURE_VS,
|
||||
airconditioner.HUMIDITY,
|
||||
# TP1X_DA-AC-FAC-class (issue #319): shares its /display/vs/0,
|
||||
# /settings/sound/output/vs/0 and /settings/sound/volume/vs/0
|
||||
# shape with the sibling TP1X_DA-AC-AIR board in air_purifier.py.
|
||||
air_purifier.DISPLAY,
|
||||
air_purifier.SOUND_OUTPUT,
|
||||
air_purifier.SOUND_VOLUME,
|
||||
airconditioner.SOUND_MODE,
|
||||
airconditioner.ABSENCE_CLEAN,
|
||||
airconditioner.ENERGY_SAVING,
|
||||
*airconditioner.COVERAGE,
|
||||
]
|
||||
),
|
||||
|
||||
@@ -441,6 +441,10 @@ SOUND_VOLUME = Capability(
|
||||
field="level",
|
||||
icon="mdi:volume-medium",
|
||||
entity_category="config",
|
||||
# Some boards (issue #319's AC) report minLevel/resolution but no
|
||||
# maxLevel -- native_max_fn would silently collapse to 0, giving a
|
||||
# slider with no real range instead of no entity at all.
|
||||
exists_fn=lambda rep, resources: "maxLevel" in rep,
|
||||
native_min_fn=lambda rep: int_or_none(rep.get("minLevel")) or 0,
|
||||
native_max_fn=lambda rep: int_or_none(rep.get("maxLevel")) or 0,
|
||||
step_fn=lambda rep: int_or_none(rep.get("resolution")) or 1,
|
||||
|
||||
@@ -1167,6 +1167,91 @@ HUMIDITY = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# TP1X_DA-AC-FAC-class additions (issue #319): resources the CAC-class board
|
||||
# (issue #191) also reports but left as a documented gap -- this dump gives
|
||||
# the live supportedModes/mode shapes those needed.
|
||||
|
||||
# Same {mode, supportedModes: [On, Off]} shape as air_purifier.DISPLAY on the
|
||||
# sibling TP1X_DA-AC-AIR board; shares that capability rather than
|
||||
# duplicating it.
|
||||
SOUND_MODE = Capability(
|
||||
href="/settings/sound/mode/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
# Values seen (mute/tone/voice) are exactly laundry.SOUND_MODE's
|
||||
# vocabulary, so this shares that catalog entry -- but reads the
|
||||
# live supportedModes field rather than laundry's static tuple,
|
||||
# since this resource carries one (issue #319).
|
||||
SelectDesc(
|
||||
key="sound_mode",
|
||||
field="mode",
|
||||
icon="mdi:volume-high",
|
||||
entity_category="config",
|
||||
options_field="supportedModes",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["settings", "sound", "mode", "vs", "0"],
|
||||
{"mode": p},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Absence-detection auto air clean (issue #319) -- a plain On/Off toggle,
|
||||
# sibling feature to ABSENCE_POWER_SAVING above but on its own href.
|
||||
ABSENCE_CLEAN = Capability(
|
||||
href="/csi/absenceclean/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SwitchDesc(
|
||||
key="absence_clean",
|
||||
field="mode",
|
||||
icon="mdi:broom",
|
||||
entity_category="config",
|
||||
value_fn=lambda v: v == "On",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["csi", "absenceclean", "vs", "0"],
|
||||
{"mode": "On" if p == "On" else "Off"},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Energy-saving schedule (issue #319). `mode` is a device-chosen preset
|
||||
# (e.g. Cooling_60/Off_180) with no confirmed unit for the trailing number
|
||||
# (minutes seen elsewhere on this board are unprefixed ints, not
|
||||
# underscore-suffixed) -- exposed as a select over the live options rather
|
||||
# than translating labels we can't confirm. `state`/`operatingStatus` stay
|
||||
# bare diagnostic passthroughs for the same reason.
|
||||
ENERGY_SAVING = Capability(
|
||||
href="/csi/energysaving/vs/0",
|
||||
poll_tier="cold",
|
||||
entities=(
|
||||
SelectDesc(
|
||||
key="energy_saving_mode",
|
||||
field="mode",
|
||||
icon="mdi:leaf",
|
||||
entity_category="config",
|
||||
options_field="supportedModes",
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
["csi", "energysaving", "vs", "0"],
|
||||
{"mode": p},
|
||||
),
|
||||
),
|
||||
SensorDesc(
|
||||
key="energy_saving_state",
|
||||
field="state",
|
||||
icon="mdi:leaf",
|
||||
entity_category="diagnostic",
|
||||
),
|
||||
SensorDesc(
|
||||
key="energy_saving_operating_status",
|
||||
field="operatingStatus",
|
||||
icon="mdi:leaf",
|
||||
entity_category="diagnostic",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# /sensors/vs/0 items[] carry live air-quality readings. CleanLevel is
|
||||
# corroborated as numeric by a top-level x.com.samsung.da.cleanLevel scalar,
|
||||
# so it's a measurement; the others stay string diagnostics (see
|
||||
@@ -1250,6 +1335,20 @@ _AC_IGNORED = [
|
||||
# registry.subdevices.enumerate_subdevices, hence the entry here rather
|
||||
# than a coverage gap.
|
||||
"/multidevice/vs/0",
|
||||
# TP1X_DA-AC-FAC-class-only (issue #319), same reasoning as
|
||||
# air_purifier.COVERAGE's duplicate of these hrefs -- scoped here rather
|
||||
# than promoted to the global ignore list since it'd collide with
|
||||
# families that do bind some of these.
|
||||
"/dnd/autosleep/vs/0", # every field its inert default; needs a schedule editor
|
||||
"/outdoorsharing/vs/0", # empty on this dump -- outdoor-unit sharing plumbing
|
||||
"/lifestyle/survey/vs/0", # {list: [""]} placeholder, nothing to expose
|
||||
# supportedVoices list only -- no live selection field to read a current
|
||||
# value from, unlike SOUND_MODE/SOUND_OUTPUT above.
|
||||
"/settings/sound/voice/vs/0",
|
||||
# rssi/wifiFrequency (network housekeeping) plus an unconfirmed
|
||||
# 48-slot absenceInfo P/A history blob with no documented meaning --
|
||||
# don't guess what it encodes.
|
||||
"/csi/information/vs/0",
|
||||
]
|
||||
|
||||
# Built as bare no-entity caps; folded into the AC registry (not global).
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "Ztlumeno"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Režim úspory energie"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Zvukový režim",
|
||||
"state": {
|
||||
@@ -787,6 +790,12 @@
|
||||
"indirect": "Nepřímý"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Stav úspory energie"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Provozní stav úspory energie"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Teplota"
|
||||
},
|
||||
@@ -1040,6 +1049,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Úspora energie při nepřítomnosti aktivní"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Čištění při nepřítomnosti"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Vyhýbání se proudu vzduchu při pohybu aktivní"
|
||||
},
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "Mute"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Energy saving mode"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Sound mode",
|
||||
"state": {
|
||||
@@ -787,6 +790,12 @@
|
||||
"indirect": "Indirect"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Energy saving state"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Energy saving operating status"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperature"
|
||||
},
|
||||
@@ -1040,6 +1049,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Absence power saving active"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Absence clean"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Motion-detect wind avoidance active"
|
||||
},
|
||||
|
||||
@@ -663,6 +663,9 @@
|
||||
"mute": "Silencio"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Modo de ahorro de energía"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Modo de sonido",
|
||||
"state": {
|
||||
@@ -909,6 +912,12 @@
|
||||
"indirect": "Indirecto"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Estado de ahorro de energía"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Estado de funcionamiento del ahorro de energía"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperatura"
|
||||
},
|
||||
@@ -1165,6 +1174,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Ahorro por ausencia activo"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Limpieza por ausencia"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Evitación de viento por detección de movimiento activa"
|
||||
},
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "Muto"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Modalità risparmio energetico"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Modalità audio",
|
||||
"state": {
|
||||
@@ -787,6 +790,12 @@
|
||||
"indirect": "Indiretto"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Stato risparmio energetico"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Stato di funzionamento del risparmio energetico"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperatura"
|
||||
},
|
||||
@@ -1040,6 +1049,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Risparmio energetico in assenza attivo"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Pulizia in assenza"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Elusione flusso d'aria a rilevamento movimento attiva"
|
||||
},
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "음소거"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "절전 모드"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "소리 모드",
|
||||
"state": {
|
||||
@@ -787,6 +790,12 @@
|
||||
"indirect": "간접풍"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "절전 상태"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "절전 작동 상태"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "온도"
|
||||
},
|
||||
@@ -1040,6 +1049,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "부재 절전 작동"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "부재 청소"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "모션 감지 바람 회피 작동"
|
||||
},
|
||||
|
||||
@@ -538,6 +538,9 @@
|
||||
"mute": "Stil"
|
||||
}
|
||||
},
|
||||
"energy_saving_mode": {
|
||||
"name": "Energiebesparingsmodus"
|
||||
},
|
||||
"air_purifier_sound_mode": {
|
||||
"name": "Geluidsmodus",
|
||||
"state": {
|
||||
@@ -787,6 +790,12 @@
|
||||
"indirect": "Indirect"
|
||||
}
|
||||
},
|
||||
"energy_saving_state": {
|
||||
"name": "Energiebesparing status"
|
||||
},
|
||||
"energy_saving_operating_status": {
|
||||
"name": "Bedrijfsstatus energiebesparing"
|
||||
},
|
||||
"current_temp_c": {
|
||||
"name": "Temperatuur"
|
||||
},
|
||||
@@ -1040,6 +1049,9 @@
|
||||
"absence_power_saving_active": {
|
||||
"name": "Energiebesparing bij afwezigheid actief"
|
||||
},
|
||||
"absence_clean": {
|
||||
"name": "Reiniging bij afwezigheid"
|
||||
},
|
||||
"motion_detect_wind_active": {
|
||||
"name": "Bewegingsdetectie luchtstroomvermijding actief"
|
||||
},
|
||||
|
||||
+1056
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"state_keys": [
|
||||
"absence_clean",
|
||||
"ai_energy_level",
|
||||
"air_filter_status",
|
||||
"air_filter_threshold",
|
||||
"air_filter_usage",
|
||||
"air_filter_usage_hours",
|
||||
"air_purify",
|
||||
"alarm_code",
|
||||
"auto_clean",
|
||||
"auto_clean_progress",
|
||||
"auto_clean_running",
|
||||
"beep",
|
||||
"climate",
|
||||
"current_temperature_c",
|
||||
"display",
|
||||
"energy_kwh",
|
||||
"energy_saved_kwh",
|
||||
"energy_saving_mode",
|
||||
"energy_saving_operating_status",
|
||||
"energy_saving_state",
|
||||
"firmware_update",
|
||||
"humidity",
|
||||
"mute_once",
|
||||
"odor_controller_active",
|
||||
"odor_controller_progress",
|
||||
"power_energy_kwh",
|
||||
"power_watts",
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"sound_mode",
|
||||
"sound_output",
|
||||
"tropical_night_mode",
|
||||
"uv_led",
|
||||
"ventilation_alarm"
|
||||
]
|
||||
}
|
||||
@@ -31,6 +31,9 @@
|
||||
"selfcheck_error",
|
||||
"selfcheck_result",
|
||||
"selfcheck_status",
|
||||
"sound_mode",
|
||||
"sound_output",
|
||||
"sound_volume",
|
||||
"super_fine_dust",
|
||||
"tropical_night_mode",
|
||||
"uv_led"
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Tests for the AILP_DA-AC-FAC-02011_0000 air conditioner (issue #319).
|
||||
|
||||
This board has no `_BOARD_TOKEN_TO_KEY` entry (routes via `/oic/d`'s
|
||||
`oic.d.airconditioner` type only) and reports several resources the sibling
|
||||
TP1X_DA-AC-CAC-01001 board (issue #191) left as a documented gap:
|
||||
/display/vs/0 and the /settings/sound/* trio (now shared with
|
||||
air_purifier.py's identical shapes), plus two genuinely new hrefs
|
||||
(/csi/absenceclean/vs/0, /csi/energysaving/vs/0).
|
||||
"""
|
||||
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import for_device_by_oic_type, resolve
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
from tests.conftest import _load_device
|
||||
|
||||
_DEVICE_TYPES = ("oic.wk.d", "oic.d.airconditioner")
|
||||
|
||||
|
||||
def _resources():
|
||||
return _load_device("airconditioner_ailp_fac")
|
||||
|
||||
|
||||
def _bound():
|
||||
resources = _resources()
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
return discover(resources, reg.capabilities, reg.pattern_capabilities), resources
|
||||
|
||||
|
||||
def test_oic_device_type_resolves_to_airconditioner_registry():
|
||||
reg = for_device_by_oic_type(_DEVICE_TYPES)
|
||||
assert reg is not None and reg.name == "airconditioner"
|
||||
|
||||
|
||||
def test_no_unbound_hrefs():
|
||||
resources = _resources()
|
||||
reg = resolve(resources, device_types=_DEVICE_TYPES)
|
||||
unbound = []
|
||||
discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append)
|
||||
assert unbound == []
|
||||
|
||||
|
||||
def test_sound_mode_bound_with_live_supported_modes():
|
||||
"""This dump has no current `mode` value yet, only supportedModes
|
||||
(mute/tone/voice) -- confirms the select is bound (present in state,
|
||||
even if unknown) and its options come from the live field rather than
|
||||
a static tuple (see laundry.SOUND_MODE's docstring for why that's
|
||||
preferred whenever the resource carries one)."""
|
||||
bound, resources = _bound()
|
||||
desc = next(b.desc for b in bound if b.desc.key == "sound_mode")
|
||||
assert desc.options_field == "supportedModes"
|
||||
state = flatten(bound, resources)
|
||||
assert "sound_mode" in state
|
||||
assert state["sound_mode"] is None
|
||||
|
||||
|
||||
def test_absence_clean_and_energy_saving_bound():
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert state["absence_clean"] is True
|
||||
assert state["energy_saving_mode"] == "Off_100"
|
||||
assert state["energy_saving_state"] == "Off"
|
||||
|
||||
|
||||
def test_sound_volume_self_gates_off_without_max_level():
|
||||
"""This board's /settings/sound/volume/vs/0 reports minLevel/resolution
|
||||
but no maxLevel -- air_purifier.SOUND_VOLUME's exists_fn should keep it
|
||||
unbound-but-covered rather than shipping a min=0/max=0 slider."""
|
||||
bound, resources = _bound()
|
||||
state = flatten(bound, resources)
|
||||
assert "sound_volume" not in state
|
||||
@@ -6,12 +6,18 @@ 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 -- eight hrefs remain unbound (edge
|
||||
lighting, a second stateful light resource, absence-clean, four
|
||||
`/settings/sound/*` resources, smart-sensing-cooling), all 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.
|
||||
This dump is NOT fully covered yet -- five hrefs remain unbound (edge
|
||||
lighting, a second stateful light resource, absence-clean,
|
||||
`/settings/sound/optimization/vs/0`, smart-sensing-cooling), all 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.
|
||||
|
||||
/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 --
|
||||
@@ -30,10 +36,7 @@ _STILL_UNBOUND = frozenset(
|
||||
"/edgelighting/vs/0",
|
||||
"/light/stateful/vs/0",
|
||||
"/mds/absenceclean/vs/0",
|
||||
"/settings/sound/mode/vs/0",
|
||||
"/settings/sound/optimization/vs/0",
|
||||
"/settings/sound/output/vs/0",
|
||||
"/settings/sound/volume/vs/0",
|
||||
"/smartsensingcooling/vs/0",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -6,12 +6,12 @@ import pytest
|
||||
GOLDEN = Path(__file__).parent / "fixtures" / "golden"
|
||||
|
||||
|
||||
def _new_state_keys(name, resources):
|
||||
def _new_state_keys(name, resources, device_types=()):
|
||||
from custom_components.localthings.registry.adapter import flatten
|
||||
from custom_components.localthings.registry.by_type import resolve
|
||||
from custom_components.localthings.registry.discovery import discover
|
||||
|
||||
reg = resolve(resources)
|
||||
reg = resolve(resources, device_types=device_types)
|
||||
if reg is None:
|
||||
from custom_components.localthings.registry.registry import CAPABILITIES
|
||||
|
||||
@@ -134,6 +134,24 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner():
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_airconditioner_ailp_fac():
|
||||
"""AILP_DA-AC-FAC-02011_0000 (issue #319) has no board-token match --
|
||||
resolves purely via /oic/d's oic.d.airconditioner type, exercising the
|
||||
device_types-only path through resolve()."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("airconditioner_ailp_fac")
|
||||
golden = json.loads((GOLDEN / "airconditioner_ailp_fac.json").read_text())
|
||||
state_keys = _new_state_keys(
|
||||
"airconditioner_ailp_fac", resources, device_types=("oic.wk.d", "oic.d.airconditioner")
|
||||
)
|
||||
assert set(state_keys) == set(golden["state_keys"]), (
|
||||
f"state_keys mismatch:\n"
|
||||
f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n"
|
||||
f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_reproduces_golden_state_keys_for_dehumidifier():
|
||||
"""TP1X_DA_AC_DHM_01001_0000 (issue #88, AY18CG7500GED) shares the DA_AC_
|
||||
board family with the room-AC models but carries the '_DHM_' token;
|
||||
@@ -1095,10 +1113,10 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_cac():
|
||||
0.16.0 when oneUiVersion detection was dropped, since 'CAC' had never
|
||||
been added to the modelNum board-token table. Resolved via the new 'CAC'
|
||||
token onto the existing airconditioner registry. Not fully covered yet --
|
||||
ten hrefs remain unbound (edge lighting, PM1 filter, stateful light,
|
||||
absence-clean, four sound-settings resources, smart-sensing-cooling, UV
|
||||
LED), all genuinely new to this board generation and out of scope for
|
||||
the routing fix; see test_airconditioner_cac.py for the documented gap."""
|
||||
five hrefs remain unbound (edge lighting, stateful light, absence-clean,
|
||||
sound-optimization, smart-sensing-cooling), all genuinely new to this
|
||||
board generation and out of scope for the routing fix; see
|
||||
test_airconditioner_cac.py for the documented gap."""
|
||||
from tests.conftest import _load_device
|
||||
|
||||
resources = _load_device("airconditioner_cac")
|
||||
|
||||
Reference in New Issue
Block a user