Commit Graph
268 Commits
Author SHA1 Message Date
Marc Billow 5d7789eb4e Add confirmed washer/dryer cycle code labels (issue #80)
Five raw course codes were rendering unlabeled because no translation
entry existed for them, confirmed by the reporter selecting each cycle
on the physical appliance and reading back the raw code from the
entity's state:

- washer_cycle_table_02: '52' Eco Cold, '54' Towels, '60' Self Clean+
  (a WF50A8600AV/US). '54' shares a display name with the existing '24'
  Towels -- a different code on the same table legitimately landing on
  the same label, matching the existing '21'/'65' Colors and
  '27'/'5E' Rinse+Spin pairs, not a duplicate-in-error.
- dryer_cycle_table_03: '01' Normal, '06' Time dry (a DVE50A8600V/A3,
  the same model added in the previous commit's detection fix).

No code changes -- select.py already derives which raw values it
normalizes from the shipped catalog, so labelling a code is purely a
translations/en.json (mirrored to nl.json) addition.
2026-07-27 00:47:16 +00:00
Marc Billow aff7647460 Merge pull request #65 from Metal-Eagle/feature/add-completion-time
Feature/add completion time
2026-07-24 23:46:47 -05:00
Jeroen Hof 705da781f1 Merge branch 'main' into feature/add-completion-time 2026-07-25 06:41:17 +02:00
Jeroen Hof 6de1a4090e i18n: simplify completion time translation in English and Dutch 2026-07-25 06:02:45 +02:00
Jeroen Hof ef75db6697 i18n: update completion time translation in English and Dutch 2026-07-25 05:55:47 +02:00
Jeroen Hof 9e290f27ee refactor: update sensor descriptions for completion time and delay start 2026-07-25 05:24:10 +02:00
Jeroen Hof 37bfff152d refactor: remove unused strings.json file 2026-07-25 05:15:34 +02:00
Jeroen Hof 317b7d0c1b fix: add missing newline at end of en.json file 2026-07-25 05:09:55 +02:00
Jeroen Hof ba8e41d310 Implement code changes to enhance functionality and improve performance 2026-07-25 05:08:18 +02:00
Jeroen Hof 724686c42c i18n: add translations for AI Wash and Jeans 2026-07-25 05:01:40 +02:00
Jeroen Hof edccb6ae3f i18n: add new wash options for AI Wash and Jeans 2026-07-25 04:59:37 +02:00
Jeroen Hof 4a91f62a36 i18n: add new translations for AI Wash and Spijkerbroek 2026-07-25 04:49:40 +02:00
Jeroen Hof 1d4d38db14 Merge branch 'main' into feature/add-completion-time 2026-07-25 04:47:22 +02:00
Marc Billow 35e2c79b14 brand: use updated localthing logo v0.12.0 2026-07-24 20:23:48 -05:00
Marc Billow f185951a8a i18n: missed a quick wash translation 2026-07-24 15:42:24 -05:00
Marc Billow adcb8a0ecb chore: add additional translations provided in #1 dutch 2026-07-24 15:33:31 -05:00
Marc Billow dc86c7f0ef Merge pull request #72 from mbillow/claude/home-assistant-i18n-iwwyvr
feat (i18n): refactor and improve translation support
2026-07-24 15:23:39 -05:00
Marc Billow 30d38d855c Merge branch 'main' into claude/home-assistant-i18n-iwwyvr 2026-07-24 15:23:10 -05:00
Marc Billow ee77e3bda8 chore: add additional translations provided in #1 2026-07-24 15:20:19 -05:00
Marc Billow ea986fe8e5 refactor: adopt cleaner write pattern for options arrays 2026-07-24 15:05:26 -05:00
Marc Billow 10c850f2b9 refactor(i18n): drop the vestigial descriptor name field
Since entity.py started routing named descriptors through the catalog
under desc.key, SamsungEntityDescription.name has been read for its
value nowhere -- only twice as a flag, to decide whether an entity was
translated at all. That left 148 English names duplicated between Python
and translations/en.json with nothing keeping them honest: six had
already drifted, invisibly, because editing the Python side changes
nothing a user sees.

So the field is gone, and translation_key defaults to desc.key. A
descriptor now sets translation_key only to share one catalog entry
across descriptors or to point at a differently-named one, and the
catalog is the only place an entity name exists.

Every descriptor resolves to exactly the translation key, icon, entity
category, enabled-default and gating it did before -- with one
deliberate exception: the hood fan, previously the sole descriptor with
no key at all, now resolves to 'fan'. That is inert, because fan.py sets
_attr_name = None so the entity presents as the device itself.

The three helpers that forwarded a name into a descriptor
(laundry.bool_option_switch, washer._bool_option_switch, air_purifier's
sensor table) lose that parameter. test_translations.py now requires a
catalog entry for every descriptor rather than only translated ones.

Claude-Session: https://claude.ai/code/session_01GiibJZZLWVvyxq7mc7EDNp
2026-07-24 19:45:37 +00:00
Marc Billow 6281b40549 refactor(i18n): make the shipped catalog the single source of truth
PR #68 restated its own translation data in Python: a 60-line
TRANSLATED_SELECT_STATES table of frozensets duplicating every
entity.select.*.state key, a second _TRANSLATED_COURSE_TABLES table
naming which course tables have translations, and a strings.json that
was a 835-line byte-for-byte copy of translations/en.json save 43
[%key:...%] references. Each needed hand-syncing, and one was already
drifting.

Home Assistant loads exactly one file per language for a custom
integration -- translations/<lang>.json. It never reads strings.json and
never resolves [%key:...%]; both belong to Core's build tooling, which
custom integrations don't run through (hassfest skips a missing
strings.json and validates translations/en.json instead). So en.json is
the source, and the new catalog.py reads the keys and states back out of
it for the two decisions Python genuinely has to make:

  - select._display() normalizes a raw Samsung option to a lowercase
    state key only when the catalog knows it, else leaves the vendor's
    casing alone. Derived sets are identical to the removed literals.
  - laundry.cycle_select() keys off a device-reported course table only
    when that table has an entry, else falls back to the name-only
    'cycle' key. Translating Table_00 is now a translations-only change.

Also fixes six names that had already drifted between the Python
descriptors and the catalog, restoring HA's sentence case for two
generic ones (Auto release dry, Bubble soak) and taking the catalog's
wording for the rest, and adds a test so the vestigial descriptor names
can't silently disagree with the UI again.

Claude-Session: https://claude.ai/code/session_01GiibJZZLWVvyxq7mc7EDNp
2026-07-24 19:32:09 +00:00
Marc Billow 0b6cc6aa9f Merge branch 'pr68' into claude/home-assistant-i18n-iwwyvr 2026-07-24 19:24:59 +00:00
Hmmbob 5d86dbe7e8 Resolve runtime translation references 2026-07-24 21:13:07 +02:00
Marc Billow e47a71689d Merge pull request #71 from splitsec2/fix/switch-off-inversion
fix(fridge): switch turn-off silently sends 'On' (truthy-string guard)
2026-07-24 13:50:53 -05:00
Rob Martin 9cf1e46bf6 fix(fridge): stop switch turn-off silently sending 'On'
Six fridge SwitchDesc write_fns built their payload with
`'On' if p else 'Off'`. The switch platform passes the literal
string 'Off' on turn-off, which is truthy, so the guard always
produced 'On' -- turning these switches off silently re-sent On
and they could never be turned off:

  - ICEMAKER_NIGHTTIME (ice.night.status)
  - STATUS_LOCK helper (devicecontrol + device.sound)
  - DEFROST_DELAY (delayDefrost)
  - WELCOME_LIGHTING (status)
  - CABINET_LIGHT dim (light.dimming.status)
  - ICEMAKER_STATUS_FALLBACK (iceMaker)

Compare `p == 'On'` instead, matching the pattern the other
capability files already use. Adds a regression test asserting
every affected write_fn sends 'Off' on 'Off' and 'On' on 'On'.
2026-07-24 12:11:53 -06:00
Hmmbob 82acc05d38 Limit changes to translation support 2026-07-24 18:40:44 +02:00
Hmmbob da49825e2e Harden unknown vendor value fallbacks 2026-07-24 18:12:01 +02:00
Hmmbob b85af10ae1 Test translation coverage and dynamic fallbacks 2026-07-24 18:08:29 +02:00
Hmmbob f9ebc8e286 Make LocalThings UI fully translatable 2026-07-24 18:08:29 +02:00
Hmmbob d0cfaeebc1 Add Dutch translations 2026-07-24 17:29:50 +02:00
Marc Billow 7276ff65a8 Merge pull request #67 from mbillow/claude/arbitrary-href-writes-debug-5wjtm3
feat: add debug options panel for arbitrary resource-href writes (#54)
2026-07-24 10:17:35 -05:00
Marc Billow c731ecefe5 feat: add debug options panel for arbitrary resource-href writes (#54)
Power users can now pick a resource href from a live dropdown, view its
current value, and POST a minimal patch straight to the device -- to pin
down device-specific write behavior without waiting on a new release.
Bypasses the remote-control block and all write_fn/validate_fn logic by
design; the existing remote-control settings toggle moves behind the same
options-flow menu.
2026-07-24 14:02:15 +00: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
Marc Billow 77cc875608 Merge pull request #64 from mbillow/claude/course-list-supportedoptions-fallback
feat: derive washer/dryer/dishwasher cycle list from supportedOptions when editCourseList is empty
2026-07-24 01:18:02 -05:00
Marc Billow ae711038ca Merge pull request #63 from mbillow/claude/issue-56-followup
Drop unexplained Blooming_* diagnostic, confirm remaining air purifier fields (issue #56)
2026-07-24 01:17:33 -05: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 ab874797fc Merge branch 'main' into feature/add-completion-time 2026-07-24 07:48:57 +02: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 b00390c093 fix: correct key for Jeans cycle in strings and translations 2026-07-24 06:27:28 +02:00
Jeroen Hof e913a3c2c0 feat: add new washer cycle options for AI Wash and Jeans 2026-07-24 06:26:24 +02:00
Jeroen Hof b2db4bf357 feat: add completion time and minutes sensors with parsing logic 2026-07-24 06:23:53 +02:00
Marc Billow 39dcf6a7b8 Drop unexplained Blooming_* diagnostic, confirm remaining air purifier fields (issue #56)
Per the five running-state diagnostics dumps (Auto/Sleep/Low/Medium/High)
gathered in the issue thread:
- Blooming_* has no corresponding SmartThings app setting, so it's dropped
  entirely rather than kept as an unexplained diagnostic.
- Comode_* reads 'Off' on all five, ruling out the original guess that it
  was the fan-speed selector -- still exposed read-only, purpose unconfirmed.
- OptionCode_60282 and the missing humidity sensor are confirmed correct as
  already modeled.
- /airflow's speed doesn't map monotonically to the five settings and the
  dumps were all captured within one ~30s poll cycle of each other, so it
  stays read-only pending a cleaner, time-spaced capture.

FilterProgress is untouched here: an earlier pass on this issue read the
thread as confirming 100 means "fresh" and renamed the sensor to filter_life
to match, but that reading was backwards -- the reporter clarified 100
means fully used and needs replacing, which is what filter_progress (the
already-shipped name) already implies. That rename was caught before
merging and is not part of this change.
2026-07-24 04:20:42 +00:00
Marc Billow ed768d7257 Merge pull request #62 from mbillow/claude/issue-52-quick-fix
Recognize A-CAWW-TP2-20-COMMON system air conditioners (issue #52)
v0.11.1
2026-07-23 23:08:32 -05:00
Marc Billow efe87381b7 Bump version to 0.11.1 2026-07-24 04:06:31 +00:00