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.
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.
_finish_time added datetime.now(timezone.utc) (fresh seconds/
microseconds every call) to the device's remaining-time duration, so the
returned timestamp differed at the sub-minute level on nearly every poll
even when remainingTime itself hadn't changed. The recorder logged a new
history/logbook entry each time, while the UI rounds the display down to
the minute, making repeated polls look like duplicate identical entries.
Round the result down to the minute so the entity only changes state
when the estimate actually shifts.
progress_percentage lacked the active-state gate already applied to
progress/cycle_active/finish_time, so it kept showing a stale device value
(e.g. 1%) while idle -- now zeroed the same way. Shared by dryer/dishwasher/
oven via operational.py's OPERATIONAL_STATE.
Also exposes detergent/softener auto-dispense quantity, water hardness/
concentration, and low-reservoir alarms from /course/vs/0's options array,
using the same decode/RMW helpers already used for course selection and
drum-clean tracking. Gated by exists_fn so washer models without these
fields (e.g. the existing test fixture) are unaffected.
Water consumption is unaffected -- common.WATER_METER is already wired
into the washer registry; this reporter's device just doesn't expose
/water/consumption/vs/0.
Replace the breadth/silence downgrade heuristic with a simpler rule: a
still-live OBSERVE session is never torn down on a sweep/cache mismatch
(the 30s sweep already corrects the cache regardless of mode) -- only a
proven reconnect invalidates subscriptions. A mismatch instead triggers
extra hot/warm subpolls this cycle as a bounded fallback.
Also stop treating a summary-poll block-level ACK timeout as session
death: distinguish TimeoutError (transfer was progressing, session
likely alive) from ConnectionError (session actually closed), backed by
a consecutive-timeout counter so a genuinely dead channel still
recovers. This was causing a flaky/slow device (e.g. a dishwasher) to
flap observe<->poll every ~45s on nothing but a slow blockwise GET.
Also remove the dead is_active/active_when scaffolding (never wired
into the coordinator) and add DEBUG logging for each OBSERVE notify
received, including its href.
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.
Dishwasher:
- Revert remote_control to BinarySensorDesc ("Smart Control", read-only)
- Add cycle SelectDesc on /course/vs/0 via options-array RMW (AI Wash,
Pre blast, Self clean, Normal, Express 60, Heavy, Pots and pans,
Delicate, Plastic, Baby Care)
- Add Storm Wash+ and Auto release dry SwitchDescs on /course/vs/0
- Add Sanitize SwitchDesc and Smart Dry SelectDesc on /dishwasher/vs/0
- Add Sound volume NumberDesc (0–15 step 5) on /settings/sound/volume/vs/0
- Add Door LED night brightness, night start/end time to DOOR_LED capability
- Add SOUND_VOLUME capability to laundry module
Stale-state fixes (firmware leaves values set after cycle ends):
- finish_time: suppress when machine state is not active
- progress: return 'Idle' when machine state is not active
- water filter: gate WATER_FILTER capability on filterStatus != 'notused'
Translations:
- Add ICESTATUS_RUN → "Making ice" to strings.json and en.json
Tests:
- Update dishwasher and refrigerator golden fixtures to reflect current entity set
- Remove dead test_project_extrapolates_remaining_time (on_observation removed)
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.