laundry: add Table_00 cycle labels for WF45R6300 washer and DVE45R6300 dryer

Adds washer_cycle_table_00 and dryer_cycle_table_00 translation catalog
entries, confirmed by the issue #357 reporter selecting each cycle on a
WF45R6300AW/US washer and DVE45R6300W/A3 dryer and reading back the raw
course code. Table_00 is a separate, older course-code family from the
existing Table_02/Table_03 catalogs -- laundry.cycle_select's table_href
scoping already keeps them apart, so this is a translations-only change.

Table_00 was previously used only as an example of an unconfirmed table in
tests; those now use Table_99 for that role, and new tests assert the
confirmed codes translate and that the resolved key routes to the new
table-scoped catalog entries.

Mirrored to all shipped languages (cs/de/es/it/ko/nl) to keep
tests/test_translations.py's key-for-key invariant.
This commit is contained in:
Marc Billow
2026-08-12 15:25:54 +00:00
parent 0c1231794a
commit 8d1ecb4f2a
12 changed files with 271 additions and 6 deletions
@@ -46,6 +46,13 @@ DRYER_SETTINGS = Capability(
# (issue #244). /st/dryercourse/vs/0 re-encodes the same selected course
# and is ignored (ignored.py), mirroring /st/washercourse/vs/0 for washers.
#
# dryer_cycle_table_00 is a separate, older course-code family reported by
# a DVE45R6300W/A3 (issue #357), confirmed the same way: the reporter
# selected each cycle on the appliance and read back the resulting raw
# code. It shares no codes with Table_03 above -- 'a5' Bedding here and
# '01' Normal are both table-scoped, so a Table_03 dryer never picks up a
# Table_00 label or vice versa (see laundry.cycle_select's table_href).
#
# Drum Clean+ maintenance tracking (issue #258) reuses washer.py's
# DrumCleanProposal_/WashingTimes_/DrumCleanLog_ tokens on this same
# options[] array -- see laundry.drum_clean_cycles_remaining/
@@ -58,6 +58,15 @@ from .laundry import (
# the owner or device metadata falls back to washer_cycle_fallback, which
# surfaces a personal-course name only -- no invented English label for an
# unrecognized standard code (PR #251 review).
#
# washer_cycle_table_00 (issue #357) is a separate, older course-code family
# reported by a WF45R6300AW/US -- confirmed by the reporter selecting each
# cycle on the appliance and reading back the raw code, the same method used
# for Table_02's WF50A8600AV/US codes above. A device reporting Table_00 with
# an unconfirmed code (FlexWash's washer_flexwash_device fixture, for
# instance) still renders that code raw rather than borrowing a Table_02
# label -- the two tables are unrelated code spaces despite a handful of
# overlapping hex values.
# ---------------------------------------------------------------------------
# /washer/vs/0 -- wash temperature, spin speed, rinse cycle count.
@@ -324,6 +324,22 @@
"4": "Alarm 4"
}
},
"dryer_cycle_table_00": {
"name": "Cyklus",
"state": {
"01": "Normální",
"9c": "Intenzivní",
"a5": "Ložní prádlo",
"9e": "Nežehlivé prádlo",
"9b": "Parní dezinfekce+",
"27": "Osvěžení",
"a0": "Provětrání",
"a4": "Časové sušení",
"a6": "Rychlé sušení",
"a3": "Sportovní oblečení",
"a2": "Jemné prádlo"
}
},
"dryer_cycle_table_03": {
"name": "Cyklus",
"state": {
@@ -604,6 +620,22 @@
"extra_hot": "Extra horká"
}
},
"washer_cycle_table_00": {
"name": "Cyklus",
"state": {
"01": "Normální",
"70": "Intenzivní",
"55": "Bílé prádlo",
"71": "Ložní prádlo",
"72": "Dezinfekce",
"77": "Nežehlivé prádlo",
"57": "Samočištění+",
"73": "Máchání + odstřeďování",
"74": "Sportovní oblečení",
"75": "Jemné prádlo",
"78": "Rychlé praní"
}
},
"washer_cycle_table_02": {
"name": "Cyklus",
"state": {
@@ -324,6 +324,22 @@
"4": "Alarm 4"
}
},
"dryer_cycle_table_00": {
"name": "Programm",
"state": {
"01": "Normal",
"9c": "Intensiv",
"a5": "Bettwäsche",
"9e": "Pflegeleicht",
"9b": "Dampf-Hygiene+",
"27": "Auffrischen",
"a0": "Lüften",
"a4": "Zeittrocknen",
"a6": "Schnelltrocknen",
"a3": "Sportkleidung",
"a2": "Feinwäsche"
}
},
"dryer_cycle_table_03": {
"name": "Programm",
"state": {
@@ -567,6 +583,22 @@
"extra_hot": "Extra heiß"
}
},
"washer_cycle_table_00": {
"name": "Programm",
"state": {
"01": "Normal",
"70": "Intensiv",
"55": "Weißwäsche",
"71": "Bettwäsche",
"72": "Hygienespülung",
"77": "Pflegeleicht",
"57": "Selbstreinigung+",
"73": "Spülen + Schleudern",
"74": "Sportkleidung",
"75": "Feinwäsche",
"78": "Schnellwäsche"
}
},
"washer_cycle_table_02": {
"name": "Programm",
"state": {
@@ -324,6 +324,22 @@
"4": "Alarm 4"
}
},
"dryer_cycle_table_00": {
"name": "Cycle",
"state": {
"01": "Normal",
"9c": "Heavy Duty",
"a5": "Bedding",
"9e": "Perm Press",
"9b": "Steam Sanitize+",
"27": "Refresh",
"a0": "Air Fluff",
"a4": "Time Dry",
"a6": "Quick Dry",
"a3": "Active Wear",
"a2": "Delicates"
}
},
"dryer_cycle_table_03": {
"name": "Cycle",
"state": {
@@ -604,6 +620,22 @@
"extra_hot": "Extra hot"
}
},
"washer_cycle_table_00": {
"name": "Cycle",
"state": {
"01": "Normal",
"70": "Heavy Duty",
"55": "Whites",
"71": "Bedding",
"72": "Sanitize",
"77": "Perm Press",
"57": "Self Clean+",
"73": "Rinse + Spin",
"74": "Active Wear",
"75": "Delicates",
"78": "Quick Wash"
}
},
"washer_cycle_table_02": {
"name": "Cycle",
"state": {
@@ -517,6 +517,22 @@
"4": "Alarma 4"
}
},
"dryer_cycle_table_00": {
"name": "Ciclo",
"state": {
"01": "Normal",
"9c": "Servicio intensivo",
"a5": "Ropa de cama",
"9e": "Planchado fácil",
"9b": "Desinfección por vapor+",
"27": "Renovar",
"a0": "Aireación",
"a4": "Secado por tiempo",
"a6": "Secado rápido",
"a3": "Ropa deportiva",
"a2": "Delicados"
}
},
"dryer_cycle_table_03": {
"name": "Ciclo",
"state": {
@@ -797,6 +813,22 @@
"extra_hot": "Muy caliente"
}
},
"washer_cycle_table_00": {
"name": "Ciclo",
"state": {
"01": "Normal",
"70": "Servicio intensivo",
"55": "Blancos",
"71": "Ropa de cama",
"72": "Desinfección",
"77": "Planchado fácil",
"57": "Autolimpieza+",
"73": "Aclarar + Centrifugar",
"74": "Ropa deportiva",
"75": "Delicados",
"78": "Lavado rápido"
}
},
"washer_cycle_table_02": {
"name": "Ciclo",
"state": {
@@ -324,6 +324,22 @@
"4": "Allarme 4"
}
},
"dryer_cycle_table_00": {
"name": "Ciclo",
"state": {
"01": "Normale",
"9c": "Intenso",
"a5": "Biancheria da letto",
"9e": "Pronto da stirare",
"9b": "Igienizzante a vapore+",
"27": "Rinfresca",
"a0": "Arieggiatura",
"a4": "Asciugatura a tempo",
"a6": "Asciugatura rapida",
"a3": "Abbigliamento sportivo",
"a2": "Delicati"
}
},
"dryer_cycle_table_03": {
"name": "Ciclo",
"state": {
@@ -604,6 +620,22 @@
"extra_hot": "Extra caldo"
}
},
"washer_cycle_table_00": {
"name": "Ciclo",
"state": {
"01": "Normale",
"70": "Intenso",
"55": "Bianchi",
"71": "Biancheria da letto",
"72": "Igienizzante",
"77": "Pronto da stirare",
"57": "Self Clean+",
"73": "Risciacquo+Centrifuga",
"74": "Abbigliamento sportivo",
"75": "Delicati",
"78": "Lavaggio rapido"
}
},
"washer_cycle_table_02": {
"name": "Ciclo",
"state": {
@@ -324,6 +324,22 @@
"4": "알림음 4"
}
},
"dryer_cycle_table_00": {
"name": "코스",
"state": {
"01": "표준건조",
"9c": "강력건조",
"a5": "이불",
"9e": "구김방지",
"9b": "스팀살균+",
"27": "리프레시",
"a0": "송풍",
"a4": "시간건조",
"a6": "쾌속건조",
"a3": "운동복",
"a2": "섬세의류"
}
},
"dryer_cycle_table_03": {
"name": "코스",
"state": {
@@ -604,6 +620,22 @@
"extra_hot": "고온수"
}
},
"washer_cycle_table_00": {
"name": "코스",
"state": {
"01": "표준세탁",
"70": "강력세탁",
"55": "흰옷",
"71": "이불",
"72": "살균",
"77": "구김방지",
"57": "통세척+",
"73": "헹굼+탈수",
"74": "운동복",
"75": "섬세의류",
"78": "쾌속세탁"
}
},
"washer_cycle_table_02": {
"name": "코스",
"state": {
@@ -324,6 +324,22 @@
"4": "Alarm 4"
}
},
"dryer_cycle_table_00": {
"name": "Programma",
"state": {
"01": "Normaal",
"9c": "Intensief",
"a5": "Beddengoed",
"9e": "Strijkvrij",
"9b": "Stoomhygiëne+",
"27": "Opfrissen",
"a0": "Luchtdrogen",
"a4": "Tijdprogramma",
"a6": "Snel drogen",
"a3": "Sportkleding",
"a2": "Fijne was"
}
},
"dryer_cycle_table_03": {
"name": "Programma",
"state": {
@@ -604,6 +620,22 @@
"extra_hot": "Extra heet"
}
},
"washer_cycle_table_00": {
"name": "Programma",
"state": {
"01": "Normaal",
"70": "Intensief",
"55": "Witte was",
"71": "Beddengoed",
"72": "Hygiëne",
"77": "Strijkvrij",
"57": "Self Clean+",
"73": "Spoelen + centrifugeren",
"74": "Sportkleding",
"75": "Fijne was",
"78": "Snelle was"
}
},
"washer_cycle_table_02": {
"name": "Programma",
"state": {
+14
View File
@@ -89,6 +89,20 @@ def test_course_bound_to_shared_course_vs_0():
assert desc.rep_fn(rep) == "16"
def test_reported_table_00_course_codes_are_translated():
"""The reporter confirmed these codes on a DVE45R6300W/A3 by selecting
each cycle and reading back the raw course code (issue #357)."""
from custom_components.localthings.catalog import translated_states
desc = next(
e for e in dryer.DRYER_COURSE.entities if e.key == "cycle" and isinstance(e, SelectDesc)
)
table_00 = {"/st/dryercourse/vs/0": {"x.com.samsung.da.st.courseTable": "Table_00"}}
assert desc.translation_key(table_00) == "dryer_cycle_table_00"
confirmed = {"01", "9c", "a5", "9e", "9b", "27", "a0", "a4", "a6", "a3", "a2"}
assert confirmed <= translated_states("select", "dryer_cycle_table_00")
def test_st_dryercourse_is_ignored():
"""/st/dryercourse/vs/0 re-encodes the course exposed via /course/vs/0 and
is globally ignored -- the mirror of /st/washercourse/vs/0."""
+1 -1
View File
@@ -369,7 +369,7 @@ class TestCycleSelectTableGating:
def test_untranslated_table_uses_generic_cycle_key(self):
"""An unknown table does not claim another board's state labels."""
desc = self._desc()
resources = {"/st/washercourse/vs/0": {"x.com.samsung.da.st.courseTable": "Table_00"}}
resources = {"/st/washercourse/vs/0": {"x.com.samsung.da.st.courseTable": "Table_99"}}
assert desc.translation_key(resources) == "cycle"
def test_resolves_to_generic_cycle_when_table_id_is_unknown(self):
+16 -5
View File
@@ -99,16 +99,18 @@ class TestWasherCourse:
def test_translation_key(self):
"""Table-scoped (issue: course codes aren't guaranteed consistent
across board generations sharing /course/vs/0 -- FlexWash's older
board reports Table_00, not the Table_02 every washer_cycle_table_02
name was confirmed against) -- see laundry.cycle_select. Only a
verified table gets table-specific state translations."""
across board generations sharing /course/vs/0 -- a device reporting
an unrecognized table id must not borrow another board generation's
labels) -- see laundry.cycle_select. Only a verified table gets
table-specific state translations."""
desc = next(e for e in washer.WASHER_COURSE.entities if e.key == "cycle")
assert callable(desc.translation_key)
table_02 = {"/st/washercourse/vs/0": {"x.com.samsung.da.st.courseTable": "Table_02"}}
assert desc.translation_key(table_02) == "washer_cycle_table_02"
table_00 = {"/st/washercourse/vs/0": {"x.com.samsung.da.st.courseTable": "Table_00"}}
assert desc.translation_key(table_00) == "cycle"
assert desc.translation_key(table_00) == "washer_cycle_table_00"
unrecognized = {"/st/washercourse/vs/0": {"x.com.samsung.da.st.courseTable": "Table_99"}}
assert desc.translation_key(unrecognized) == "cycle"
assert desc.translation_key({}) == "cycle"
def test_reads_raw_course_code_from_options_array(self):
@@ -146,6 +148,15 @@ class TestWasherCourse:
}
assert confirmed <= translated_states("select", "washer_cycle_table_02")
def test_reported_table_00_course_codes_are_translated(self):
"""The reporter confirmed these codes on a WF45R6300AW/US by
selecting each cycle and reading back the raw course code
(issue #357)."""
from custom_components.localthings.catalog import translated_states
confirmed = {"01", "70", "55", "71", "72", "77", "57", "73", "74", "75", "78"}
assert confirmed <= translated_states("select", "washer_cycle_table_00")
def test_missing_course_option_returns_none(self):
desc = next(e for e in washer.WASHER_COURSE.entities if e.key == "cycle")
assert desc.rep_fn is not None