Commit Graph
14 Commits
Author SHA1 Message Date
Jeroen Hof 1d4d38db14 Merge branch 'main' into feature/add-completion-time 2026-07-25 04:47:22 +02:00
Marc Billow 30d38d855c Merge branch 'main' into claude/home-assistant-i18n-iwwyvr 2026-07-24 15:23:10 -05:00
Marc Billow ea986fe8e5 refactor: adopt cleaner write pattern for options arrays 2026-07-24 15:05:26 -05:00
Hmmbob b85af10ae1 Test translation coverage and dynamic fallbacks 2026-07-24 18:08:29 +02:00
Jeroen Hof 365a1c4722 feat: enhance operational capabilities by adding completion_minutes and updating related logic; remove completion_time references 2026-07-24 15:35:17 +02:00
Jeroen Hof 7be91b56e8 Merge remote-tracking branch 'upstream/main' into feature/add-completion-time 2026-07-24 08:23:42 +02:00
Jeroen Hof 2c283218b0 feat: add completion_minutes and completion_time to state_keys in JSON fixtures 2026-07-24 08:10:17 +02:00
Marc Billow 3b3dd54372 Simplify table-scoped translation key; fix stale resolution; correct docstring
Simplification (feedback: this was overcomplicated): drop the
validated_table gate entirely. cycle_select's table_href now just builds
the translation key directly from whatever course table the device
reports (washer_cycle + Table_02 -> washer_cycle_table_02) instead of
comparing against a hardcoded known-good value and falling back to no key
on any mismatch. A table we haven't shipped translations for yet (e.g.
FlexWash's Table_00) still gets a key built for it -- Home Assistant's own
missing-translation handling takes it from there, the same graceful
fallback already relied on for any individual untranslated code within an
existing table. Adding a newly-confirmed table later is just new
strings.json entries, no code change.

Independent (Opus) review of the prior version caught two real issues,
fixed here regardless of the simplification above:

- translation_key was resolved once at entity construction from whatever
  coordinator.last_resources held at that moment. Discovery can run while
  a sibling resource is still an empty stub (documented precedent: see
  _is_included), so a callable translation_key could permanently bake in
  a stale value for the entity's lifetime. Moved resolution into a
  translation_key property override (Entity.translation_key is a property
  upstream, not a plain attribute), re-evaluated against live coordinator
  data on every access, matching how options/current_option already work.

- The supportedOptions fallback's "smallest passing K wins" docstring
  claimed every larger passing K is an exact multiple of the true one.
  False: the shipped dishwasher fixture has passing K=7 (true) alongside
  10, 14, and 35, none of which are multiples of 7 -- position 0 always
  lands on the same real course code regardless of K, which alone
  satisfies the current-course guard for several unrelated splits.
  Corrected the reasoning to what's actually true (an empirically-matched
  heuristic across six real dumps, not a proof) and added a regression
  test locking in the real dishwasher case so this isn't silently lost.
2026-07-24 05:55:04 +00:00
Jeroen Hof f5d4e2219a fix: remove redundant course codes from parse_edit_course_list test case 2026-07-24 07:47:53 +02:00
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
Jeroen Hof 86f07abf30 fix: correct expected output for parse_edit_course_list test case 2026-07-24 06:29:53 +02:00
Jeroen Hof e913a3c2c0 feat: add new washer cycle options for AI Wash and Jeans 2026-07-24 06:26:24 +02: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