Commit Graph
12 Commits
Author SHA1 Message Date
Marc Billow 2f6b7d54e3 refactor: share remote-control read between sensor and write guard, poll it warm
Move the on/off interpretation into a single remote_control_enabled()
in registry/capabilities/common.py so the write-guard added in the
previous commit can't silently drift from the Smart Control binary
sensor's own reading of the same hrefs. Also promotes both
/remotectrl hrefs to poll_tier='warm' so the coordinator's cached
state backing that write guard doesn't lag up to a full 30s cold
summary poll behind the device's actual toggle state.
2026-07-23 02:29:03 +00:00
Marc Billow 1493467c63 Fix AI energy level platform-flip bug found in Opus review
The switch and select shared a stub-time asymmetry: only the select had a
`not rep` carve-out, so an unfetched-stub rep at the moment platforms are
set up (entity creation runs once, ever) would instantiate a Select, while
flatten() re-evaluates exists_fn every poll against live data -- once the
resource populated to a single-level list, the switch's exists_fn would win
instead and feed the already-created Select a bool through their shared
'ai_energy_level' key, which isn't a valid select option.

Dropped the stub carve-out from the select's exists_fn so both sides
require real, populated data to decide the platform -- on a device that
stubs this cold-tier href on its very first poll, the entity now simply
doesn't appear until a reload, instead of appearing as the wrong widget
type. Added tests for the missing/empty-list supportedAiLevel shapes on
both widgets and a regression test locking in the stub behavior.
2026-07-23 00:44:58 +00:00
Marc Billow 61ee2972dc Trim duplicated comments from /simplify review
Consolidate the AC/power-exclusion rationale (previously spelled out nearly
verbatim in three places) down to one canonical explanation next to
common.POWER, with one-line pointers elsewhere. Dedupe the switch/select
test classes' identical _desc() lookup into one shared helper.
2026-07-23 00:44:58 +00:00
Marc Billow 3d5263d792 Add common.UNIVERSAL/POWER bundles to stop hand-duplicating capabilities
FIRMWARE_UPDATE and ALARMS were already copy-pasted into all 6 device-type
registries by hand; POWER/KIDS_LOCK/REMOTE_CONTROL into 5 of 6. Consolidate
into two bundles in common.py, unpacked via *common.UNIVERSAL / *common.POWER
the same way ignored.IGNORED already is:

- UNIVERSAL: ALARMS, ENERGY_METER, FIRMWARE_UPDATE (moved from fridge.py),
  SELF_CHECK (moved from fridge.py), AI_ENERGY_LEVEL, and the kids-lock/
  remote-control pairs. Safe everywhere -- discover() only binds a href
  actually present in a device's dump, so a capability with no known
  conflicting family is a no-op where the href is absent and a real,
  wanted entity where it's present. This also broadens AI_ENERGY_LEVEL,
  ENERGY_METER, and SELF_CHECK to device types they weren't confirmed on
  before, on the same reasoning.
- POWER: just POWER_GENERIC/POWER_VS_FALLBACK, applied to the 5 non-AC
  registries. Airconditioner keeps its own opt-out: its climate entity
  already owns /power/0 and /power/vs/0 via a bare, no-entity claim
  (airconditioner.COVERAGE), and a real power capability on the same
  href would make _build() raise (a href with multiple caps requires
  every cap to have rt_filter or match_fn; the bare COVERAGE cap has
  neither).

Full test suite (327 tests, all 6 device-type golden fixtures) passes
unchanged -- none of the newly-broadened capabilities bind on any existing
fixture, confirming the no-op reasoning held in practice, not just theory.
2026-07-23 00:44:58 +00:00
Marc Billow d6639c99d4 Bind AI energy level on washer; switch/select split, drop translations
Issue #40: /energy/ailevel/vs/0 was unbound on a plain washer. The
capability already existed for fridges but was gated off entirely on
single-level hardware (the common case), so it's moved to common.py
(cross-family, like fridge + washer now) and split into two entities:
a switch when supportedAiLevel has exactly one entry (aiLevel is really
just an on/off toggle there), and a select otherwise, with '0' (off)
synthesized back into the select's options since supportedAiLevel never
lists it but it's a real observed value.

Also drops the translation_key/strings.json entries -- aiLevel's raw
digit values already render fine untranslated, and translating a
handful of levels can't cover devices with more.
2026-07-23 00:44:57 +00:00
Marc Billow febc8fa4a0 fix: keep energy sensors on empty-stub reps; gate power on field presence
Review follow-up. An explicit exists_fn bypasses entity._is_included's
empty-{} stub carve-out, so the sentinel-aware energy meter would drop
power_watts/energy_kwh when /device/0 returns a not-yet-fetched stub for
/energy/consumption/vs/0. Restore the carve-out (`not rep or ...`), and
hide power_watts when instantaneousPower is absent from a populated rep
(not just when it's the -500 sentinel) so a partial rep can't spawn a
phantom power sensor.
2026-07-21 01:39:49 +00:00
Marc Billow 9264b912f9 feat: full dryer support + consistent laundry capability surface
First full dryer dump (issue #14, DV90BB5245AES1) surfaced 5 unbound
hrefs and an "incomplete capability coverage" repair. Handle them and,
while here, make the washer/dryer/dishwasher families consistent instead
of each carrying a bespoke variant of the same controls.

Dryer coverage:
- /power/0, /kidslock/0, /remotectrl/0: bind via the OCF-native + vendor
  fallback pairs (prefer the standard OCF resource, fall back to -vs).
- /buzzersound/vs/0: new Buzzer sound select.
- /course/vs/0: cycle select shared with washer/dishwasher; ignore the
  /st/dryercourse/vs/0 re-encoding (mirror of /st/washercourse/vs/0).

Consistency / de-duplication:
- Move generic OCF controls (power/kids-lock/remote-control fallback
  pairs, energy meter) into common.py; every registry uses them.
- Move shared laundry controls (buzzer, job-beginning-status, and the
  /course/vs/0 cycle-select machinery) into laundry.py; washer and
  dishwasher stop hand-rolling their own copies.
- Energy meter is now sentinel-aware everywhere: the dead '-500'
  instantaneousPower reading no longer shows a misleading 0 W (fixes it
  on dryers and dishwashers, matching the earlier washer fix).
- Job-beginning-status reads x.com.samsung.da.currentStatus, the field
  every dump actually carries; the dryer sensor was previously blank.

Adds a scrubbed dryer fixture, golden, and capability tests, plus an
.claude/skills/adding-device-support skill capturing the dump-reading,
OCF-vs-vendor, entity-taxonomy, and coverage workflow. Bumps to 0.6.0.
2026-07-21 01:15:12 +00:00
Marc Billow b67e0e5cf5 Rename ocf/ to registry/, it's a capability registry now
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.
2026-07-07 22:54:57 -05:00
Marc Billow cc2c1e5a1f refactor: move samsung_appliance/ into custom_components/localthings/ocf/
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.
2026-06-30 16:34:20 -05:00
Marc Billow b10cfad194 feat: update discover() for pattern caps, match_fn, rt_filter gating
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.
2026-06-30 16:32:42 -05:00
Marc Billow 0499c702d7 refactor: key capabilities on href instead of rt
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.
2026-06-30 16:32:37 -05:00
Marc Billow bdae3cc82d feat: add shared common capabilities 2026-06-30 16:32:37 -05:00