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.
- 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.
Six System A/C cassette units on the same board test_airconditioner_cac.py
already documented as having an incomplete coverage gap gave real dump
evidence for two of its remaining unbound hrefs:
- /edgelighting/vs/0: an accent-light strip with on/off, a Smart/High/Low
mode, and a Kelvin color-temperature select (3000K/4000K/6500K), all read
from the device's own live supported-value lists.
- /light/stateful/vs/0: a second, distinct light resource with its own
on/off and Smart/Low/High mode -- not to be confused with EDGE_LIGHTING
or DISPLAY_LIGHT's ambient mood light.
convenientMode/operatingOption on /edgelighting/vs/0 stay unexposed: present
on every dump but no evidence of what either actually controls.
Only three hrefs remain in test_airconditioner_cac.py's documented gap now
(absence-clean, sound-optimization, smart-sensing-cooling).
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.
TP1X_FAC_TIME_23K reports three previously unbound hrefs: a UV-C
sterilization LED and a ventilation-reminder alarm (both plain On/Off
toggles), and a second PM1-rated dust filter with no live usage/status
fields on this particular dump.
The PM1 filter capability gates each entity on its own field's presence
rather than a blanket ignore, since the TP1X_DA-AC-CAC-01001_0000 cassette
AC (issue #191) reports the same href with full live data -- this also
closes two of that device's ten documented coverage gaps (UV LED and the
PM1 filter) as a side effect.
Adds [tool.ruff] and [tool.ty] config to pyproject.toml with a curated
ruff rule set (E, F, W, I, UP, B, C4, SIM, RUF, ASYNC, LOG, G, PIE, RET,
PERF, N), pins ruff/ty in requirements-dev.txt, reformats the whole tree
with `ruff format`, and fixes the pre-existing lint and type-check debt
those tools surfaced so both run clean.
Production-code type fixes include: HA's ConfigFlowResult vs. the
generic FlowResult in config_flow.py, narrowing BoundEntity.desc to its
platform-specific subclass (SelectDesc/NumberDesc/SensorDesc/etc.) via
cast() instead of an unchecked annotation, converting HA device_class
strings to their proper enum types, a resolve_registry callback typed
as `object` instead of `DeviceRegistry | None`, and a couple of other
narrow correctness fixes (CA key type validation, an index-out-of-bounds
false positive from an empty-tuple fallback, a bool/dict argument swap).
Test-file fixes are mechanical: narrowing SamsungEntityDescription to
the correct subclass via isinstance()/cast() before accessing
subclass-only fields, and asserting Optional write_fn/unit_fn fields
are set before calling them.
- config_flow: the #192 port-rescue made the "every port refused" fast-fail
permanently unreachable (PREFERRED_PROBE_PORTS is always non-empty and
always rescued), so removed the dead branch instead of leaving it as
misleading dead code. A dead host now fails via the handshake loop's own
error, which carries the real per-port reason.
- oven._has_option: added the is_stub_rep carve-out cooktop.py's identical
per-token exists_fn already has on the same kind of href, so a not-yet
sub-polled /mode/vs/0 doesn't permanently exclude energy_saving/
cooktop_on_alert before their first real fetch lands.
- airconditioner.ENERGY_METER_LEGACY: build via dataclasses.replace() like
its ENERGY_METER_GENERIC sibling, instead of hand-copying href/poll_tier
(which would silently drift if common.ENERGY_METER ever gains a field).
- Fixed two stale comments: is_legacy_board()'s docstring still listed
Volume among tokens needing legacy-only gating, though 'beep' now applies
unconditionally across board generations; and common.py's UNIVERSAL
invariant comment didn't mention that airconditioner also now excludes
ENERGY_METER from the wholesale bundle.
- #191 (CAC token): added the fixture/golden/capability-test coverage the
AVT token in the same branch got, including an honestly-documented list
of the ten hrefs this board generation doesn't cover yet.
- fridge.cooler_temperature_setpoint: dropped the hardcoded "N °C" state
labels -- the resource's own unit field isn't necessarily Celsius on a
different model reporting the same href, and the options themselves are
already read live via options_field, so a static per-value label risked
asserting the wrong unit for a future device sharing this capability.
- airconditioner._legacy_cumulative_power_kwh: parse with float (matching
common.wh_to_kwh's own numeric parsing) instead of this module's
integer-only _int, so a decimal-formatted reading doesn't raise.
- test_config_flow: the port-rescue test bound the real 49154 directly,
which could collide with an actually-running service on some machine;
now monkeypatches PREFERRED_PROBE_PORTS to an OS-assigned port instead.
- oven.py: consolidated six byte-for-byte-identical single-token options
write_fns (lamp/sound/fast_preheat/natural_steam/energy_saving/
cooktop_on_alert) into one _option_switch_write(prefix) factory.
845 tests passing (up from 841 -- 4 new CAC coverage tests).