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.
discover() only emitted a BoundEntity per capability *entity*, so a
coverage-only Capability (entities=(), used to mark a href as handled
elsewhere -- e.g. the AC climate card's wind/strength, wind/direction,
temperature/control hrefs) produced zero rows. The coordinator computed
its hot/warm href lists by walking `bound`, so every such href's
poll_tier was silently discarded and it fell back to the ~30s summary
poll only -- no sub-poll cadence and never attempted for OCF OBSERVE.
This is the root cause of issue #166's "up to a minute" lag for
remote-driven fan-speed changes: /wind/strength/vs/0 carries poll_tier
'warm' via airconditioner.COVERAGE but never reached
_hot_hrefs/_warm_hrefs, so it wasn't in the OBSERVE-attempt href list
and only refreshed on the summary poll.
discover() now takes an optional tier_log(href, poll_tier) callback
fired for every href a capability matches, entities or not. The
coordinator uses it directly instead of deriving tiers from `bound`.
Issue #27: the flex-zone/cooler-drawer select vanished after a device
stopped including supportedOptions on an update for /mode/vs/0.
ObserveManager.apply() handed reps straight to StateCache.apply_rep,
which fully replaces the cached rep -- so a partial update (missing a
field the select's exists_fn/options_field gate on) silently erased
data a fuller update had previously supplied. apply() now merges
incoming reps onto whatever's already cached instead.
Also give ice-maker entities (and any future pattern-cap instance) a
device-given display name instead of the href-derived "Icemaker
One"/"Icemaker Two": Capability.name_field lets a pattern capability
read and normalize an instance name (e.g. iceMaker.name's "CUBED_ICE")
for use as the entity name prefix, independent of the stable
key/unique_id.
Maps the fridge/dishwasher hrefs flagged by last session's coverage-gap
Repairs issue: defrost delay (switch) + block status (diagnostic binary
sensor), master ice-maker enable, self-check trigger/status, dishwasher
diagnosis trigger/status, and last-operation-source. Adds fallback
capabilities for /doors, /temperatures, and /icemaker/status that only
bind when the richer per-instance hrefs they duplicate are absent, so
simpler devices without those hrefs still get the data. Everything else
in the original gap list moves to capabilities.ignored (Bixby audio
feedback, inert DR/energy-planner resources, redundant metadata).
Fixes discover() reporting a known href as an unregistered gap whenever
its match_fn declined to bind for that device (e.g. a filter capability
on hardware without that filter) — it's now only a gap when no capability
is registered for the href at all.
Device naming now uses x.com.samsung.da.modelNum (not the OCF /oic/p,d
metadata, which produced inconsistent names like "[dishwasher] Samsung")
to build "Samsung <Type> (<model>)".
The DTLS/CoAP transport code that made "ocf" an accurate name moved out to
the smartthings-local package. What's left here (capability.py, entities.py,
discovery.py, adapter.py, identity.py, capabilities/, by_type/, plus the
/device/0 batch parser) is entirely the device capability registry, so name
the package for what it does.
Flattened the redundant ocf/registry/ nesting into a single top-level
registry/ package and updated every import across the platform modules and
test suite accordingly. Verified: full test suite (80/80) passes, and the
Docker dev container reconnects to both live appliances and rediscovers
their entities cleanly after the rename.
Makes the integration fully self-contained for HACS distribution.
Users installing via HACS get everything in one directory; no external
samsung_appliance package needed.
All component imports updated to relative (.ocf.*); all test imports
updated to custom_components.localthings.ocf.*. ocf_root_ca.pem moves
with the package and resolves correctly via Path(__file__).parent.
Remove key_fn from Capability — pattern cap keys are now always derived
automatically: strip digits from href segments, join with '_', append
'_' + desc.key. /door/cooler/0 + 'open' -> door_cooler_open.
DOOR_GENERIC entity key: 'open' (not 'door_open'). Remove _door_key helper.
Fridge registry: DOOR_FRIDGE/DOOR_FREEZER replaced by DOOR_GENERIC pattern cap.
Golden baseline: door_fridge_open -> door_cooler_open.
Move device dumps to tests/fixtures/dumps/ (tracked) so tests run in CI
without local-tools/ dependencies.
Registry changes from dict[str,Capability] to dict[str,list[Capability]].
discover() gains pattern_caps parameter (fallback for unmatched hrefs)
and gates each candidate on rt_filter and match_fn before binding.
Duplicate hrefs in registry allowed only if all caps set a discriminator.
OCF rt is a schema category shared by many unrelated resources.
The stable unique resource address is the href. Update Capability,
discovery, common capabilities, tests, and plan accordingly.
Implement discovery module for converting OCF resource metadata into bound
entities, with support for multi-instance device resources and logging of
unknown capabilities.