Address Opus review findings on the device-support commits above

- Fix a real bug: airconditioner.SOUND_MODE had no exists_fn, so on
  boards (issue #319's FAC) that never report a live 'mode' value,
  entity.py's default field-presence gate silently kept the select from
  ever registering in HA -- while adapter.flatten() (what the golden/tests
  read) has no such gate, so the tests passed while documenting behavior
  the opposite of what shipped. Gate on supportedModes' presence instead.
- Add airconditioner.MDS_ABSENCE_CLEAN for the CAC-class board's
  /mds/absenceclean/vs/0 -- byte-identical shape to issue #319's
  /csi/absenceclean/vs/0, confirmed rather than guessed, closing one more
  of that board's documented coverage-gap hrefs.
- Add missing translation state labels (all 6 languages) for
  edge_lighting_mode/edge_lighting_color/indicator_light_mode's raw device
  codes, so they render as real words instead of a raw '3000K' -> '3000 K'
  fallback.
- Fix an orphaned comment above SOUND_MODE that actually described the
  unrelated DISPLAY reuse, and correct two inaccurate rationale comments:
  the sound/voice ignore reason claimed a distinction from SOUND_MODE that
  this same dump contradicts, and the /csi/* ignore block's 'same
  reasoning as air_purifier.COVERAGE' precedent only actually covers 1 of
  its 5 hrefs.
- Correct the false 'no board-token match' claim in the FAC test file and
  golden-regression docstring -- 'FAC' is a real _BOARD_TOKEN_TO_KEY entry
  (for_device_by_model alone already resolves this board); add a test
  that actually exercises that path, which nothing previously did despite
  the docstring's claim.
- Drop a tautological burner-slot test that only re-asserted what the
  golden regression test already covers via the same code path.
This commit is contained in:
Marc Billow
2026-08-07 14:50:24 +00:00
parent c203bd42c5
commit e2dcc75ed5
13 changed files with 250 additions and 68 deletions
@@ -54,6 +54,7 @@ REGISTRY = DeviceRegistry(
air_purifier.SOUND_VOLUME,
airconditioner.SOUND_MODE,
airconditioner.ABSENCE_CLEAN,
airconditioner.MDS_ABSENCE_CLEAN,
airconditioner.ENERGY_SAVING,
airconditioner.EDGE_LIGHTING,
airconditioner.LIGHT_STATEFUL,
@@ -1167,13 +1167,11 @@ 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.
# TP1X_DA-AC-FAC-class additions (issue #319): most of these hrefs are the
# same shapes air_purifier.py already models on the sibling TP1X_DA-AC-AIR
# board (DISPLAY/SOUND_OUTPUT/SOUND_VOLUME, reused directly in the
# registry); SOUND_MODE and the two below are genuinely new.
# 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",
@@ -1182,12 +1180,22 @@ SOUND_MODE = Capability(
# 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).
#
# exists_fn is required, not optional here: this board's rep never
# reports a live 'mode' value ({"supportedModes": [...]} only), and
# entity.py's default field-presence gate would otherwise keep the
# select from ever registering -- adapter.flatten() (what the
# golden/tests read) has no such gate, so it would look bound while
# silently absent from HA. Register on supportedModes' presence
# instead; current_option reads unknown until the device reports
# 'mode' live.
SelectDesc(
key="sound_mode",
field="mode",
icon="mdi:volume-high",
entity_category="config",
options_field="supportedModes",
exists_fn=lambda rep, resources: bool(rep.get("supportedModes")),
write_fn=lambda p, rep, href=None: (
["settings", "sound", "mode", "vs", "0"],
{"mode": p},
@@ -1216,6 +1224,30 @@ ABSENCE_CLEAN = Capability(
),
)
# The CAC-class board (issue #191) reports the identical {mode,
# supportedModes: [On, Off]} shape under /mds/absenceclean/vs/0 instead --
# confirmed against that board's own fixture, not guessed. Shares
# ABSENCE_CLEAN's key/translation: no dump has ever reported both hrefs
# together, so there's nothing for the two to collide over in
# adapter.flatten().
MDS_ABSENCE_CLEAN = Capability(
href="/mds/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: (
["mds", "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
@@ -1415,19 +1447,23 @@ _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.
# TP1X_DA-AC-FAC-class-only (issue #319) -- scoped here rather than
# promoted to the global ignore list since it'd collide with families
# that do bind some of these. Only /dnd/autosleep/vs/0 has a precedent
# (air_purifier.COVERAGE ignores the same href for the same reason);
# the rest are new, each with its own reason below.
"/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.
# supportedVoices carries opaque numeric voice-pack IDs ("100"/"101")
# with no live current-selection field and, unlike SOUND_MODE's
# self-descriptive mute/tone/voice codes, no confirmed human-readable
# meaning to expose them under -- don't guess.
"/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.
# rssi/wifiFrequency (network housekeeping); lastEnergySavingTime and
# cleaningStartTime are inert '1900-01-00' placeholders on this dump;
# absenceInfo is an unconfirmed 48-slot P/A history blob with no
# documented meaning -- don't guess what it encodes.
"/csi/information/vs/0",
]
@@ -670,13 +670,28 @@
"name": "Teplota mrazicí zóny"
},
"edge_lighting_mode": {
"name": "Režim okrajového osvětlení"
"name": "Režim okrajového osvětlení",
"state": {
"smart": "Chytrý",
"high": "Vysoký",
"low": "Nízký"
}
},
"edge_lighting_color": {
"name": "Barva okrajového osvětlení"
"name": "Barva okrajového osvětlení",
"state": {
"3000k": "3000 K",
"4000k": "4000 K",
"6500k": "6500 K"
}
},
"indicator_light_mode": {
"name": "Režim kontrolky"
"name": "Režim kontrolky",
"state": {
"smart": "Chytrý",
"high": "Vysoký",
"low": "Nízký"
}
}
},
"sensor": {
@@ -670,13 +670,28 @@
"name": "Freezer temperature"
},
"edge_lighting_mode": {
"name": "Edge lighting mode"
"name": "Edge lighting mode",
"state": {
"smart": "Smart",
"high": "High",
"low": "Low"
}
},
"edge_lighting_color": {
"name": "Edge lighting color"
"name": "Edge lighting color",
"state": {
"3000k": "3000 K",
"4000k": "4000 K",
"6500k": "6500 K"
}
},
"indicator_light_mode": {
"name": "Indicator light mode"
"name": "Indicator light mode",
"state": {
"smart": "Smart",
"high": "High",
"low": "Low"
}
}
},
"sensor": {
@@ -795,13 +795,28 @@
"name": "Temperatura del congelador"
},
"edge_lighting_mode": {
"name": "Modo de iluminación perimetral"
"name": "Modo de iluminación perimetral",
"state": {
"smart": "Inteligente",
"high": "Alto",
"low": "Bajo"
}
},
"edge_lighting_color": {
"name": "Color de iluminación perimetral"
"name": "Color de iluminación perimetral",
"state": {
"3000k": "3000 K",
"4000k": "4000 K",
"6500k": "6500 K"
}
},
"indicator_light_mode": {
"name": "Modo de luz indicadora"
"name": "Modo de luz indicadora",
"state": {
"smart": "Inteligente",
"high": "Alto",
"low": "Bajo"
}
}
},
"sensor": {
@@ -670,13 +670,28 @@
"name": "Temperatura freezer"
},
"edge_lighting_mode": {
"name": "Modalità illuminazione perimetrale"
"name": "Modalità illuminazione perimetrale",
"state": {
"smart": "Smart",
"high": "Alta",
"low": "Bassa"
}
},
"edge_lighting_color": {
"name": "Colore illuminazione perimetrale"
"name": "Colore illuminazione perimetrale",
"state": {
"3000k": "3000 K",
"4000k": "4000 K",
"6500k": "6500 K"
}
},
"indicator_light_mode": {
"name": "Modalità spia luminosa"
"name": "Modalità spia luminosa",
"state": {
"smart": "Smart",
"high": "Alta",
"low": "Bassa"
}
}
},
"sensor": {
@@ -670,13 +670,28 @@
"name": "냉동실 온도"
},
"edge_lighting_mode": {
"name": "엣지 라이팅 모드"
"name": "엣지 라이팅 모드",
"state": {
"smart": "스마트",
"high": "높음",
"low": "낮음"
}
},
"edge_lighting_color": {
"name": "엣지 라이팅 색상"
"name": "엣지 라이팅 색상",
"state": {
"3000k": "3000 K",
"4000k": "4000 K",
"6500k": "6500 K"
}
},
"indicator_light_mode": {
"name": "표시등 모드"
"name": "표시등 모드",
"state": {
"smart": "스마트",
"high": "높음",
"low": "낮음"
}
}
},
"sensor": {
@@ -670,13 +670,28 @@
"name": "Temperatuur vriesgedeelte"
},
"edge_lighting_mode": {
"name": "Randverlichtingsmodus"
"name": "Randverlichtingsmodus",
"state": {
"smart": "Slim",
"high": "Hoog",
"low": "Laag"
}
},
"edge_lighting_color": {
"name": "Randverlichtingskleur"
"name": "Randverlichtingskleur",
"state": {
"3000k": "3000 K",
"4000k": "4000 K",
"6500k": "6500 K"
}
},
"indicator_light_mode": {
"name": "Indicatorlampjemodus"
"name": "Indicatorlampjemodus",
"state": {
"smart": "Slim",
"high": "Hoog",
"low": "Laag"
}
}
},
"sensor": {