Commit Graph
9 Commits
Author SHA1 Message Date
Marc Billow 679c3d2bce Fix remaining pre-existing ty diagnostics in test files
Completes the isinstance/cast narrowing + Optional-field assert pattern
across the last batch of test files. custom_components and tests are
now both fully clean under ruff check, ruff format --check, and ty check.
2026-08-03 00:14:58 +00:00
Marc Billow 36a642135b Fix pre-existing ty diagnostics in a second batch of test files
Same isinstance/cast narrowing and Optional-field assert pattern as the
prior commits, covering the airconditioner, fridge, washer, operational,
subdevices, sensor_hysteresis, laundry, select_options, identity and
entities test files.
2026-08-03 00:11:05 +00:00
Marc Billow daf7e3787f Add ruff (lint + format) and ty (type checking) to the project
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.
2026-08-02 23:56:38 +00:00
Marc Billow 2be742489b Fix airflow fan power-href preference and unique_id collision risk
Opus review of the previous commit caught two real bugs. The airflow fan's
power writes preferred /power/vs/0, copied from the TP1X fan class -- but
that order is only harmless there because TP1X never reports /power/0 at
all. This family's dumps carry both hrefs, and the power_switch entity is
unconditionally bound to /power/0 when present, so the fan was writing to
a different resource than power_switch reads/writes, leaving the two
entities disagreeing until the next poll. Flipped to prefer /power/0,
matching the range hood's fan and common.POWER_GENERIC.

Also renamed the new FanDesc's key from 'fan' to 'airflow_fan': BoundEntity
unique_ids are derived from key alone, not href, so it collided with
air_purifier.FAN's own 'fan' key on the (currently unobserved, but
unenforced) possibility of a board reporting both.

Added a platform-level test file covering the power-href preference and
percentage<->speed-code mapping, mirroring test_range_hood_fan.py's harness.
2026-07-27 21:48:23 +00:00
Marc Billow d4583b3240 Add real fan-speed control for ARTIK051_TVTL air purifiers (issue #56)
/airflow/0's speed was left read-only because the first round of diagnostics
wasn't conclusive (0 for both Auto and High, 3 for Low/Medium and Sleep) --
likely because all five dumps were captured within about a minute of each
other, faster than this integration's own poll cycle could settle each
change. A second round, captured 60-90s apart per setting on two independent
units, confirmed a clean monotonic 0-4 mapping across Auto/Sleep/Low/Medium/
High instead.

Builds an ordered-speed fan off that confirmed range, the same SET_SPEED
shape as the range hood's fan -- this board never self-reports a
supportedModes-style label list, so there's no named-preset table to
preserve, just percentage steps over the raw code. /airflow/vs/0's vendor
speedLevel stays a read-only fallback since it was unreliable in that same
second round.
2026-07-27 21:32:44 +00:00
Marc Billow ea986fe8e5 refactor: adopt cleaner write pattern for options arrays 2026-07-24 15:05:26 -05:00
Marc Billow 39dcf6a7b8 Drop unexplained Blooming_* diagnostic, confirm remaining air purifier fields (issue #56)
Per the five running-state diagnostics dumps (Auto/Sleep/Low/Medium/High)
gathered in the issue thread:
- Blooming_* has no corresponding SmartThings app setting, so it's dropped
  entirely rather than kept as an unexplained diagnostic.
- Comode_* reads 'Off' on all five, ruling out the original guess that it
  was the fan-speed selector -- still exposed read-only, purpose unconfirmed.
- OptionCode_60282 and the missing humidity sensor are confirmed correct as
  already modeled.
- /airflow's speed doesn't map monotonically to the five settings and the
  dumps were all captured within one ~30s poll cycle of each other, so it
  stays read-only pending a cleaner, time-spaced capture.

FilterProgress is untouched here: an earlier pass on this issue read the
thread as confirming 100 means "fresh" and renamed the sensor to filter_life
to match, but that reading was backwards -- the reporter clarified 100
means fully used and needs replacing, which is what filter_progress (the
already-shipped name) already implies. That rename was caught before
merging and is not part of this change.
2026-07-24 04:20:42 +00:00
Marc Billow 42a80fffe6 Simplify air purifier support by reusing existing helpers
Rewires /mode/vs/0's packed-options parsing (display_light/operating_mode/
blooming_level) onto laundry.py's existing option_value/replace_in_options/
bool_option_exists/bool_option_value instead of hand-rolled reimplementations,
hoists the duplicated int-conversion helper into common.py (shared by
range_hood.py too), collapses the five near-identical AIR_QUALITY sensors
into a table-driven loop, extracts the /consumable/vs/0 item lookup into a
named helper, and moves the humidity ignore list into capabilities/
air_purifier.py's own COVERAGE list to match the airconditioner/range_hood
convention of keeping by_type files as pure composition.

No behavior change; golden state keys and all existing tests are unaffected.
2026-07-23 22:36:47 +00:00
Marc Billow b121d24966 Add Samsung air purifier support (ARTIK051_TVTL-class, issue #56)
Adds a by_type registry for the AX60R5080WD/SE air purifier family, verified
against two independent diagnostics dumps (issue #56 and its comment). Binds
power, alarms, energy, diagnosis (reusing dishwasher.DIAGNOSIS), the dust/
fine-dust/super-fine-dust/odor/clean-level sensors off /sensors/vs/0, filter
progress, a device-active diagnostic, and a display-light switch parsed out
of /mode/vs/0's packed options list.

Fan speed/direction (/airflow/0, /airflow/vs/0) and two other /mode/vs/0
tokens (Comode_*, Blooming_*) are exposed as read-only diagnostics rather
than full controls -- neither dump has a supported-values list to confirm
their write contracts, so they're left for a follow-up once that's
clarified in the issue thread.

Hoists range_hood's items[]-sensor-value helper into common.py
(sensor_item_value) since air_purifier now reads the same /sensors/vs/0
shape.
2026-07-23 22:12:15 +00:00