Commit Graph
3 Commits
Author SHA1 Message Date
Marc Billow b2c32a90b6 Scope washer/dryer cycle translations to the device's own course table
Course codes on the shared /course/vs/0 contract aren't guaranteed
consistent across board generations: washer/combo devices report course
table Table_02, dryer devices Table_03 (x.com.samsung.da.st.courseTable,
previously fully ignored), and every code in washer_cycle/dryer_cycle was
confirmed exclusively against those. FlexWash's older DA_WM_A51 board
reports Table_00 instead -- applying the same translations there risked
showing a wrong name for any code that happens to numerically collide
between tables, not just an untranslated one.

SelectDesc.translation_key can now be a callable (resources -> key or
None), mirroring the existing pattern for `options`. laundry.cycle_select
gains optional table_href/validated_table params: when given, the renamed
washer_cycle_table_02/dryer_cycle_table_03 keys only apply when the
device's own course table matches exactly -- a different table, or no
table id at all, gets no translation_key (raw code display) rather than
a guess. dishwasher's call site is unchanged (static key, unconditional):
no equivalent table-id resource exists in any dump seen, and no evidence
its course codes vary by table the way washer/dryer's do.

entity.py and select.py resolve a callable translation_key once (via
coordinator.last_resources) and reuse that resolved value everywhere
_display() needs it, rather than re-checking the raw descriptor field.
2026-07-24 05:42:12 +00:00
Marc Billow b2c0517115 feat: derive washer/dryer/dishwasher cycle list from supportedOptions when editCourseList is empty
Some DA_WM_TP1/TP2-class boards populate /wm/editcourse/vs/0 without ever
filling in editCourseList itself (issue #1), so the Cycle select never gets
created even though the device clearly has one (confirmed via SmartThings
app screenshots and a currently-selected course).

/course/vs/0's own x.com.samsung.da.supportedOptions turns out to already
carry the course list, just undocumented: a 1-hex-nibble header followed by
one fixed-width record per course, self-indexed by a course-code first byte
rather than positional like editCourseList. Confirmed against six
independent real-world dumps pulled from open and closed GitHub issues.

cycle_options() now falls back to deriving this when editCourseList is
empty, gated on two checks: the derived codes must all be distinct, and
must include whatever course is currently selected. Larger multiples of
the true record width trivially re-pass both checks too (they're just a
sparser sampling of the same table), so the smallest passing width wins
rather than requiring one unambiguous match.

Also fires on the washer_flexwash fixture, newly creating a Cycle select
there -- unconfirmed against any ground truth for that device (a different,
older board generation with no editCourseList and no screenshots to check
against), flagged for follow-up discussion rather than silently accepted.
2026-07-24 05:06:39 +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