From 3ef64eae523905b440ce38505b79a0cc330b11c8 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 18:59:22 +0000 Subject: [PATCH 01/10] Add dehumidifier display switch and watertank lighting (issues #271, #231) The TP1X_DA_AC_DHM_01001_0000 revision (model AY70H18100GTD) additionally reports /display/vs/0 (same shape as air_purifier's screen toggle, reused directly) and /watertank/lighting/vs/0 (on/off, color, and brightness for the tank's ambient light, plus a diagnostic alarm-status flag). Both issues submitted the identical dump, so one fix covers both reports. Also adds x.com.st.d.dehumidifier to the /oic/d device-type routing table now that a dump confirms it. --- .../localthings/registry/by_type/__init__.py | 1 + .../registry/by_type/dehumidifier.py | 9 +- .../registry/capabilities/dehumidifier.py | 56 +- .../localthings/translations/cs.json | 2584 +++++++++-------- .../localthings/translations/en.json | 22 + .../localthings/translations/es.json | 22 + .../localthings/translations/it.json | 22 + .../localthings/translations/nl.json | 22 + .../dehumidifier_tp1x_dhm01001_device.json | 426 +++ .../golden/dehumidifier_tp1x_dhm01001.json | 27 + ...dehumidifier_tp1x_dhm01001_capabilities.py | 94 + tests/test_golden_regression.py | 17 + 12 files changed, 2018 insertions(+), 1284 deletions(-) create mode 100644 tests/fixtures/dehumidifier_tp1x_dhm01001_device.json create mode 100644 tests/fixtures/golden/dehumidifier_tp1x_dhm01001.json create mode 100644 tests/test_dehumidifier_tp1x_dhm01001_capabilities.py diff --git a/custom_components/localthings/registry/by_type/__init__.py b/custom_components/localthings/registry/by_type/__init__.py index 917f7cd..d1fbce0 100644 --- a/custom_components/localthings/registry/by_type/__init__.py +++ b/custom_components/localthings/registry/by_type/__init__.py @@ -259,6 +259,7 @@ _OIC_TYPE_TO_KEY: dict[str, str] = { "oic.d.refrigerator": "refrigerator", "oic.d.washer": "washer", "x.com.st.d.airqualitysensor": "air_monitor", + "x.com.st.d.dehumidifier": "dehumidifier", "x.com.st.d.hood": "range_hood", # AHD-WW-TP1-22-COMMON "x.com.st.d.stickcleaner": "vacuum_station", "x.com.st.d.steamcloset": "air_dresser", diff --git a/custom_components/localthings/registry/by_type/dehumidifier.py b/custom_components/localthings/registry/by_type/dehumidifier.py index 373fbdc..9230e60 100644 --- a/custom_components/localthings/registry/by_type/dehumidifier.py +++ b/custom_components/localthings/registry/by_type/dehumidifier.py @@ -2,10 +2,13 @@ Shares the DA_AC_ board family with airconditioner.py (power, air filter, auto-clean, mute-once all use the identical resource shapes), so those three -Capability objects are reused directly rather than duplicated. +Capability objects are reused directly rather than duplicated. The +TP1X_DA_AC_DHM_01001_0000 revision (issues #271/#231) also reports +air_purifier.py's screen-on/off resource on the identical href/shape, so +that's reused too rather than re-defined. """ -from ..capabilities import airconditioner, common, dehumidifier, ignored +from ..capabilities import airconditioner, air_purifier, common, dehumidifier, ignored from ._base import DeviceRegistry, _build REGISTRY = DeviceRegistry( @@ -17,9 +20,11 @@ REGISTRY = DeviceRegistry( *common.POWER, dehumidifier.MODE, dehumidifier.HUMIDITY, + dehumidifier.WATERTANK_LIGHTING, airconditioner.AUTO_CLEAN, airconditioner.AIR_FILTER, airconditioner.MUTE_ONCE, + air_purifier.DISPLAY, *dehumidifier.COVERAGE, ] ), diff --git a/custom_components/localthings/registry/capabilities/dehumidifier.py b/custom_components/localthings/registry/capabilities/dehumidifier.py index 27c0e5b..944d62b 100644 --- a/custom_components/localthings/registry/capabilities/dehumidifier.py +++ b/custom_components/localthings/registry/capabilities/dehumidifier.py @@ -9,7 +9,7 @@ entities rather than folded into one card. """ from ..capability import Capability -from ..entities import NumberDesc, SelectDesc, SensorDesc +from ..entities import NumberDesc, SelectDesc, SensorDesc, SwitchDesc from .common import int_or_none @@ -77,6 +77,60 @@ HUMIDITY = Capability( ), ) +# Water-tank ambient light (issues #271/#231, TP1X_DA_AC_DHM_01001_0000): +# on/off, color, and brightness are three independent controls on this one +# resource. `waterfullAlarmStatus` differs between the two dumps that +# reported this href (On vs. Off) so it's a real live flag, not a constant -- +# but its exact meaning (tank actually full vs. the chime feature merely +# enabled) isn't confirmed by either dump alone, and /alarms/vs/0's +# alarm_code already surfaces a live WaterTankFull condition when one fires +# (see common._active_alarm_codes), so this is exposed read-only as a plain +# diagnostic value rather than guessed at as a binary_sensor. +WATERTANK_LIGHTING = Capability( + href="/watertank/lighting/vs/0", + poll_tier="cold", + entities=( + SwitchDesc( + key="watertank_light", + field="status", + icon="mdi:led-on", + entity_category="config", + value_fn=lambda v: v == "On", + write_fn=lambda p, rep, href=None: ( + ["watertank", "lighting", "vs", "0"], + {"status": "On" if p == "On" else "Off"}, + ), + ), + SelectDesc( + key="watertank_light_color", + field="colorOption", + icon="mdi:palette", + entity_category="config", + options_field="colorSupportedList", + write_fn=lambda p, rep, href=None: ( + ["watertank", "lighting", "vs", "0"], + {"colorOption": p}, + ), + ), + SelectDesc( + key="watertank_light_brightness", + field="mode", + icon="mdi:brightness-6", + entity_category="config", + options_field="modeSupportedList", + write_fn=lambda p, rep, href=None: ( + ["watertank", "lighting", "vs", "0"], + {"mode": p}, + ), + ), + SensorDesc( + key="watertank_full_alarm_status", + field="waterfullAlarmStatus", + entity_category="diagnostic", + ), + ), +) + # --------------------------------------------------------------------------- # Dehumidifier-scoped coverage: vendor plumbing with no user-actionable state # or no documented write contract, following the same 'don't guess' rule as diff --git a/custom_components/localthings/translations/cs.json b/custom_components/localthings/translations/cs.json index 732049b..a4f3170 100644 --- a/custom_components/localthings/translations/cs.json +++ b/custom_components/localthings/translations/cs.json @@ -1,1281 +1,1303 @@ -{ - "entity": { - "binary_sensor": { - "auto_clean_running": { - "name": "Automatické čištění probíhá" - }, - "after_run_active": { - "name": "Dosoušení aktivní" - }, - "any_burner_active": { - "name": "Hořák aktivní" - }, - "automatic_operation": { - "name": "Automatický provoz" - }, - "battery_charging": { - "name": "Nabíjení" - }, - "burner_hot_surface": { - "name": "Hořák {number} horký povrch" - }, - "burner_pan_detected": { - "name": "Hořák {number} rozpoznána nádoba" - }, - "cloud_connected": { - "name": "Připojeno ke cloudu" - }, - "dustbag_full": { - "name": "Sáček na prach plný" - }, - "cooktop_power": { - "name": "Napájení varné desky" - }, - "cooktop_safety_shutoff_enabled": { - "name": "Automatické vypnutí při horkém povrchu" - }, - "current_limit_enabled": { - "name": "Omezení proudu zapnuto" - }, - "overload_protection_active": { - "name": "Ochrana proti přetížení aktivní" - }, - "odor_controller_active": { - "name": "Odstraňování pachů aktivní" - }, - "cycle_active": { - "name": "Běží" - }, - "defrost_active": { - "name": "Odmrazování aktivní" - }, - "detergent_low": { - "name": "Málo pracího prostředku" - }, - "device_active": { - "name": "Zařízení aktivní" - }, - "door_open": { - "name": "Dvířka" - }, - "filter_door_status": { - "name": "Dvířka filtru" - }, - "firmware_update": { - "name": "Dostupná aktualizace firmwaru" - }, - "instance_open": { - "name": "{instance_name} otevřeno" - }, - "paired_hood_connected": { - "name": "Spárovaný odsavač připojen" - }, - "paired_hood_light": { - "name": "Světlo spárovaného odsavače" - }, - "paired_hood_power": { - "name": "Napájení spárovaného odsavače" - }, - "periodic_air_sensing": { - "name": "Pravidelné měření kvality vzduchu" - }, - "pouring": { - "name": "Nalévání" - }, - "alarm_in_mute": { - "name": "Alarm ztlumen" - }, - "child_lock": { - "name": "Dětská pojistka" - }, - "power_state": { - "name": "Stav napájení" - }, - "probe_connected": { - "name": "Sonda připojena" - }, - "power_switch": { - "name": "Napájení" - }, - "rapid_freezing": { - "name": "Rychlé zmrazování" - }, - "rapid_fridge": { - "name": "Rychlé chlazení" - }, - "remote_control": { - "name": "Chytré ovládání" - }, - "softener_low": { - "name": "Málo aviváže" - } - }, - "button": { - "after_run_cancel": { - "name": "Zrušit dosoušení" - }, - "diagnosis_start": { - "name": "Spustit diagnostiku" - }, - "pause": { - "name": "Pozastavit" - }, - "selfcheck_start": { - "name": "Spustit vlastní kontrolu" - }, - "start": { - "name": "Spustit" - }, - "stop": { - "name": "Zastavit" - } - }, - "climate": { - "airconditioner": { - "state_attributes": { - "fan_mode": { - "state": { - "turbo": "Turbo", - "max": "Max" - } - }, - "preset_mode": { - "state": { - "ai_comfort": "AI komfort", - "quiet": "Tichý", - "smart": "Chytrý", - "speed": "Rychlý", - "nano": "WindFree", - "nanosleep": "WindFree spánek", - "longwind": "Dlouhý vánek", - "motionindirect": "Nepřímý vzduch při pohybu", - "motiondirect": "Přímý vzduch při pohybu", - "drycomfort": "Komfortní sušení", - "2step": "2stupňový" - } - } - } - } - }, - "fan": { - "air_purifier_fan": { - "state_attributes": { - "preset_mode": { - "state": { - "smart": "Chytrý", - "max": "Max", - "mid": "Střední", - "windfree": "WindFree", - "sleep": "Spánek" - } - } - } - } - }, - "number": { - "cook_time": { - "name": "Doba vaření" - }, - "delay_start_hours": { - "name": "Odložený start" - }, - "dispense_capacity": { - "name": "Dávkované množství" - }, - "good_sleep": { - "name": "Klidný spánek" - }, - "instance_setpoint": { - "name": "{instance_name} požadovaná teplota" - }, - "oven_setpoint": { - "name": "Požadovaná teplota" - }, - "setpoint": { - "name": "Požadovaná teplota" - }, - "sound_volume": { - "name": "Hlasitost zvuku" - }, - "target_humidity": { - "name": "Požadovaná vlhkost" - }, - "tropical_night_mode": { - "name": "Tropický noční režim" - }, - "zone_target_temperature": { - "name": "Požadovaná teplota zóny" - } - }, - "select": { - "ai_energy_level": { - "name": "Úroveň režimu AI Energy" - }, - "air_filter_threshold": { - "name": "Práh upozornění na filtr" - }, - "beverage_zone_mode": { - "name": "Režim zóny nápojů", - "state": { - "sp_ttype_beer_drinks": "Nápoje", - "sp_ttype_wine_dessert": "Víno a dezert" - } - }, - "brightness_level": { - "name": "Noční jas", - "state": { - "33": "Nízký", - "66": "Střední", - "100": "Vysoký" - } - }, - "cooler_temperature_setpoint": { - "name": "Teplota chladicí zóny" - }, - "day_brightness": { - "name": "Jas skříně", - "state": { - "33": "Nízký", - "66": "Střední", - "100": "Vysoký" - } - }, - "buzzer_sound": { - "name": "Zvuk bzučáku", - "state": { - "off": "Vypnuto", - "on": "Zapnuto" - } - }, - "discharging_time": { - "name": "Doba vypouštění", - "state": { - "1": "1 minuta", - "3": "3 minuty" - } - }, - "cycle": { - "name": "Cyklus" - }, - "detergent_quantity": { - "name": "Množství pracího prostředku", - "state": { - "00": "Žádné", - "01": "Nízké", - "02": "Střední", - "03": "Vysoké" - } - }, - "detergent_water_hardness": { - "name": "Tvrdost vody pro prací prostředek", - "state": { - "01": "Měkká", - "02": "Střední", - "03": "Tvrdá" - } - }, - "dishwasher_cycle": { - "name": "Cyklus", - "state": { - "80": "Jemné", - "83": "Express 60", - "84": "Intenzivní", - "86": "Normální", - "90": "Samočištění", - "0e": "AI mytí", - "07": "Předoplach", - "8d": "Hrnce a pánve", - "8e": "Plast", - "8f": "Dětské potřeby" - } - }, - "dispense_type": { - "name": "Typ dávkování" - }, - "door_alert": { - "name": "Alarm dvířek", - "state": { - "1": "Alarm 1", - "2": "Alarm 2", - "3": "Alarm 3", - "4": "Alarm 4" - } - }, - "dryer_cycle_table_03": { - "name": "Cyklus", - "state": { - "01": "Normální", - "06": "Časové sušení", - "16": "Bavlna", - "17": "Super Speed", - "18": "Syntetika", - "19": "Jemné prádlo", - "20": "Žehlení", - "21": "Hygienické sušení", - "22": "Tiché sušení", - "23": "Rychlé sušení 35", - "24": "Studený vzduch", - "25": "Teplý vzduch", - "27": "Časové sušení", - "29": "AI sušení", - "1a": "Vlna", - "1b": "Ložní prádlo", - "1c": "Košile", - "1d": "Ručníky", - "1e": "Outdoor", - "1f": "Smíšená náplň", - "2b": "Sušení bubnu", - "4c": "Osvěžení vzduchem" - } - }, - "favorite_capacity": { - "name": "Oblíbené množství" - }, - "favorite_hotwater_temperature": { - "name": "Oblíbená teplota horké vody" - }, - "filter_alarm_time": { - "name": "Interval alarmu filtru", - "state": { - "180": "180 hodin", - "300": "300 hodin", - "500": "500 hodin", - "700": "700 hodin" - } - }, - "finish_sound": { - "name": "Zvuk dokončení", - "state": { - "off": "Vypnuto", - "on": "Zapnuto" - } - }, - "flex_zone_mode": { - "name": "Režim flexibilní zóny", - "state": { - "cv_ttype_rf9000a_freeze": "Mražení", - "cv_ttype_rf9000a_softfreeze": "Mírné mražení", - "cv_ttype_rf9000a_meat_fish": "Maso/Ryby", - "cv_ttype_rf9000a_fruit_veggies": "Ovoce a zelenina", - "cv_ttype_rf9000a_beverage": "Nápoje", - "cv_fdr_wine": "Víno", - "cv_fdr_deli": "Lahůdky", - "cv_fdr_beverage": "Nápoje", - "cv_fdr_meat": "Maso", - "cv_fdr_soft_freezer": "Mírný mrazák" - } - }, - "heated_dry": { - "name": "Chytré sušení", - "state": { - "off": "Vypnuto", - "low": "Nízké", - "high": "Vysoké", - "extra_high": "Extra vysoké" - } - }, - "hot_water_temperature": { - "name": "Teplota horké vody" - }, - "ice_type": { - "name": "{instance_name} typ", - "state": { - "off": "Vypnuto", - "whiskey_iceball_3": "3 kuličky/den", - "whiskey_iceball_6": "6 kuliček/den", - "whiskey_iceball_9": "9 kuliček/den" - } - }, - "led_brightness": { - "name": "Jas LED dvířek", - "state": { - "low": "Nízký", - "high": "Vysoký" - } - }, - "led_night_brightness": { - "name": "Noční jas LED dvířek", - "state": { - "low": "Nízký", - "high": "Vysoký" - } - }, - "cooking_mode": { - "name": "Režim vaření", - "state": { - "no_operation": "Bez provozu", - "micro_wave": "Mikrovlny", - "micro_wave_grill": "Mikrovlny + gril", - "micro_wave_convection": "Mikrovlny + konvekce", - "convection": "Konvekce", - "air_fryer": "Fritéza na vzduch", - "grill": "Gril", - "autocook": "Automatické vaření", - "autocook_custom": "Automatické vaření (vlastní)", - "deodorization": "Odstranění pachů", - "keep_warm": "Udržování teploty" - } - }, - "oven_mode": { - "name": "Režim vaření", - "state": { - "no_operation": "Bez provozu", - "bake": "Pečení", - "broil": "Gril", - "convection": "Konvekce", - "convection_bake": "Konvekční pečení", - "convection_broil": "Konvekční gril", - "frozen_pizza_plus": "Mražená pizza+", - "slow_cook": "Pomalé vaření", - "plate_warm": "Ohřev talířů", - "air_fry": "Fritéza na vzduch" - } - }, - "operating_mode": { - "name": "Provozní režim" - }, - "kimchi_zone_mode": { - "name": "{instance_name} režim uskladnění", - "state": { - "off": "Vypnuto", - "kimchi_storage_normal": "Kimči", - "kimchi_storage_cold": "Kimči, silné", - "kimchi_storage_warm": "Kimči, slabé", - "kimchi_storage_low_salt_normal": "Kimči s nízkým obsahem soli", - "kimchi_storage_low_salt_cold": "Kimči s nízkým obsahem soli, silné", - "kimchi_storage_low_salt_warm": "Kimči s nízkým obsahem soli, slabé", - "kimchi_storage_crunfch": "Křupavé kimči", - "kimchi_storage_buy": "Kupované kimči", - "storage_fridge_normal": "Chlazení", - "storage_fridge_cold": "Chlazení, silné", - "storage_fridge_warm": "Chlazení, slabé", - "storage_freezer_normal": "Mražení", - "storage_freezer_cold": "Mražení, silné", - "storage_freezer_warm": "Mražení, slabé", - "kimchi_ripe_low_temp": "Zrání kimči, nízká teplota", - "kimchi_ripe_normal_temp": "Zrání kimči, pokojová teplota", - "kimchi_ripe_kkakdugi": "Zrání kkakdugi", - "kimchi_ripe_dongchimi": "Zrání dongchimi", - "meat_ripe_normal": "Zrání masa", - "storage_meat": "Maso a ryby", - "storage_fridge_vegetables_fruit": "Ovoce a zelenina", - "storage_fresh_cereal": "Obiloviny", - "storage_fridge_drink": "Nápoje", - "storage_fresh_wine": "Víno", - "storage_fresh_potato_banana": "Brambory a banány" - } - }, - "pantry_zone_mode": { - "name": "Režim spíže", - "state": { - "fdr_wine": "Víno", - "fdr_deli": "Lahůdky", - "fdr_drinks": "Nápoje" - } - }, - "range_burner_power_level": { - "name": "Výkon hořáku {number}", - "state": { - "0": "Vypnuto", - "boost": "Boost", - "simmer": "Mírný var" - } - }, - "range_hood_lamp_brightness": { - "name": "Jas osvětlení", - "state": { - "1": "Úroveň 1", - "2": "Úroveň 2" - } - }, - "rinse_cycles": { - "name": "Počet máchání" - }, - "softener_concentration": { - "name": "Koncentrace aviváže", - "state": { - "01": "1x", - "02": "2x", - "03": "3x" - } - }, - "softener_quantity": { - "name": "Množství aviváže", - "state": { - "00": "Žádné", - "01": "Nízké", - "02": "Střední", - "03": "Vysoké" - } - }, - "sound_mode": { - "name": "Zvukový režim", - "state": { - "voice": "Hlas", - "tone": "Tón", - "mute": "Ztlumeno" - } - }, - "air_purifier_sound_mode": { - "name": "Zvukový režim", - "state": { - "mute": "Ztlumeno", - "buzzer": "Bzučák" - } - }, - "water_purifier_sound_mode": { - "name": "Zvukový režim", - "state": { - "voice": "Hlas", - "fixedtone": "Pevný tón", - "mute": "Ztlumeno" - } - }, - "spin_speed": { - "name": "Rychlost odstřeďování", - "state": { - "rinse_hold": "Zadržení máchání", - "no_spin": "Bez odstřeďování", - "low": "Nízká", - "medium": "Střední", - "high": "Vysoká" - } - }, - "wash_temperature": { - "name": "Teplota praní", - "state": { - "none": "Žádná", - "cold": "Studená", - "cool": "Chladná", - "warm": "Teplá", - "hot": "Horká", - "extra_hot": "Extra horká" - } - }, - "washer_cycle_table_02": { - "name": "Cyklus", - "state": { - "01": "Normální", - "04": "Rychlé praní", - "17": "Stažený program", - "1b": "Bavlna", - "1c": "Eco 40-60", - "1d": "Super rychlé", - "1e": "15min rychlé praní", - "1f": "Intenzivní studená", - "20": "Hygienická pára", - "21": "Barevné prádlo", - "22": "Vlna", - "23": "Outdoor", - "24": "Ručníky", - "25": "Syntetika", - "26": "Jemné prádlo", - "27": "Máchání+odstřeďování", - "28": "Vypuštění/odstřeďování", - "29": "Čištění bubnu+", - "2a": "Džíny", - "2b": "AI praní", - "2d": "Tiché praní", - "2e": "Dětské potřeby", - "2f": "Sportovní oblečení", - "30": "Zataženo", - "32": "Košile", - "33": "Ložní prádlo", - "34": "Smíšené", - "36": "Praní+sušení", - "37": "Sušení vzduchem", - "38": "Sušení bavlny", - "39": "Sušení syntetiky", - "3a": "Čištění bubnu", - "52": "Eco studená", - "53": "Intenzivní", - "54": "Ručníky", - "55": "Sportovní oblečení", - "57": "Jemné", - "5e": "Máchání+odstřeďování", - "60": "Samočištění+", - "65": "Barevné prádlo", - "66": "Džíny", - "7c": "Bílé prádlo", - "7d": "Ložní prádlo/nepromokavé", - "7e": "Samočištění", - "7f": "Vlna/jemné", - "86": "Hloubkové praní", - "87": "Stažený program", - "8f": "Intenzivní studená", - "96": "Méně mikrovláken" - } - }, - "washer_dry_level": { - "name": "Úroveň sušení", - "state": { - "30": "30 min", - "60": "1 h", - "90": "1 h 30 min", - "120": "2 h", - "180": "3 h", - "240": "4 h", - "none": "Vypnuto", - "cupboard": "Do skříně" - } - }, - "zone_mode": { - "name": "Režim zóny" - } - }, - "sensor": { - "auto_clean_progress": { - "name": "Průběh automatického čištění" - }, - "after_run_progress": { - "name": "Průběh dosoušení" - }, - "air_filter_usage": { - "name": "Využití filtru" - }, - "air_filter_usage_hours": { - "name": "Hodiny využití filtru" - }, - "air_quality_standard": { - "name": "Norma kvality vzduchu" - }, - "air_sensing_state": { - "name": "Stav měření kvality vzduchu" - }, - "alarm_code": { - "name": "Kód alarmu" - }, - "auto_ventilation_action": { - "name": "Akce automatického větrání" - }, - "automatic_ventilation_state": { - "name": "Stav automatického větrání" - }, - "battery": { - "name": "Baterie" - }, - "burner_state": { - "name": "Stav hořáku {number}" - }, - "clean_level": { - "name": "Úroveň čištění" - }, - "coffee_brew_status": { - "name": "Stav přípravy kávy" - }, - "co2": { - "name": "CO2" - }, - "connection_mode": { - "name": "Režim připojení", - "state": { - "observe": "Push (observe)", - "poll": "Dotazování" - } - }, - "cooktop_running_state": { - "name": "Provozní stav varné desky" - }, - "cooktop_state": { - "name": "Stav varné desky" - }, - "current_limit_level": { - "name": "Úroveň omezení proudu" - }, - "filter_time": { - "name": "Čas filtru" - }, - "hepa_filter_usage": { - "name": "Využití HEPA filtru" - }, - "outdoor_temperature": { - "name": "Venkovní teplota" - }, - "odor_controller_progress": { - "name": "Průběh odstraňování pachů" - }, - "panel_status": { - "name": "Stav panelu" - }, - "sound_output": { - "name": "Zvukový výstup" - }, - "dustbag_usage": { - "name": "Využití sáčku na prach" - }, - "cleanstation_status": { - "name": "Stav čisticí stanice" - }, - "stick_status": { - "name": "Stav tyče" - }, - "uvc_operation_time": { - "name": "Doba provozu UV-C" - }, - "uvc_total_operation_time": { - "name": "Celková doba provozu UV-C" - }, - "uvc_finished_time": { - "name": "Čas dokončení UV-C" - }, - "uvc_emitted_time": { - "name": "Doba vyzařování UV-C" - }, - "overload_protection_mode": { - "name": "Režim ochrany proti přetížení", - "state": { - "alarm": "Alarm", - "powersaving": "Úspora energie" - } - }, - "absence_power_saving_mode": { - "name": "Režim úspory energie při nepřítomnosti", - "state": { - "eco": "Eco", - "normal": "Normální", - "comfort": "Komfort" - } - }, - "motion_detect_wind_mode": { - "name": "Režim vzduchu při detekci pohybu", - "state": { - "direct": "Přímý", - "indirect": "Nepřímý" - } - }, - "current_temp_c": { - "name": "Teplota" - }, - "current_temperature_c": { - "name": "Teplota" - }, - "diagnosis": { - "name": "Diagnostika" - }, - "diagnosis_status": { - "name": "Stav diagnostiky" - }, - "drum_clean_cycles_remaining": { - "name": "Čištění bubnu za" - }, - "drum_clean_last_cleaned": { - "name": "Poslední čištění bubnu" - }, - "dry_level": { - "name": "Úroveň sušení" - }, - "dry_time": { - "name": "Doba sušení" - }, - "dryer_type": { - "name": "Typ sušičky" - }, - "dust": { - "name": "Prach" - }, - "energy_kwh": { - "name": "Energie" - }, - "energy_last_month_kwh": { - "name": "Energie (minulý měsíc)" - }, - "energy_saved_kwh": { - "name": "Ušetřená energie" - }, - "energy_this_month_kwh": { - "name": "Energie (tento měsíc)" - }, - "fan_direction": { - "name": "Směr proudění vzduchu" - }, - "fan_speed_level": { - "name": "Úroveň otáček ventilátoru" - }, - "filter_clean_remain_time": { - "name": "Zbývající čas do čištění filtru" - }, - "filter_progress": { - "name": "Průběh filtru" - }, - "filter_usage": { - "name": "Využití filtru" - }, - "fine_dust": { - "name": "Jemný prach" - }, - "finish_time": { - "name": "Odhadované dokončení" - }, - "completion_minutes": { - "name": "Čas dokončení" - }, - "freezer_temperature": { - "name": "Teplota mrazáku" - }, - "fridge_temperature": { - "name": "Teplota chladničky" - }, - "kimchi_ripening_status": { - "name": "{instance_name} stav zrání" - }, - "kimchi_ripening_remaining": { - "name": "{instance_name} zbývající čas zrání" - }, - "kimchi_rack_count": { - "name": "{instance_name} počet přihrádek" - }, - "hood_filter_capacity": { - "name": "Kapacita filtru" - }, - "humidity": { - "name": "Vlhkost" - }, - "hood_filter_usage": { - "name": "Využití filtru" - }, - "instance_temperature": { - "name": "{instance_name} teplota" - }, - "job_beginning_status": { - "name": "Stav zahájení úlohy" - }, - "last_air_sensing_level": { - "name": "Poslední úroveň měření vzduchu" - }, - "last_air_sensing_time": { - "name": "Poslední čas měření vzduchu" - }, - "main_timer_current": { - "name": "Aktuální hodnota časovače" - }, - "main_timer_state": { - "name": "Stav časovače" - }, - "odor": { - "name": "Pach" - }, - "operating_mode": { - "name": "Provozní režim" - }, - "operation_origin": { - "name": "Poslední zdroj ovládání" - }, - "operation_time_minutes": { - "name": "Doba provozu" - }, - "oven_state": { - "name": "Stav trouby" - }, - "cavity_state": { - "name": "Stav trouby" - }, - "power_level": { - "name": "Úroveň výkonu" - }, - "paired_hood_fan_speed": { - "name": "Otáčky ventilátoru spárovaného odsavače" - }, - "paired_hood_firmware": { - "name": "Firmware spárovaného odsavače" - }, - "paired_hood_model": { - "name": "Model spárovaného odsavače" - }, - "power_energy_kwh": { - "name": "Spotřeba energie" - }, - "power_watts": { - "name": "Výkon" - }, - "probe_battery": { - "name": "Baterie sondy" - }, - "probe_target_temperature": { - "name": "Požadovaná teplota sondy" - }, - "probe_temperature": { - "name": "Teplota sondy" - }, - "progress": { - "name": "Průběh" - }, - "progress_percentage": { - "name": "Průběh v procentech" - }, - "selfcheck_error": { - "name": "Chyba vlastní kontroly" - }, - "selfcheck_result": { - "name": "Výsledek vlastní kontroly" - }, - "selfcheck_status": { - "name": "Stav vlastní kontroly" - }, - "sterilize_last_time": { - "name": "Poslední sterilizace" - }, - "sterilize_period": { - "name": "Interval sterilizace" - }, - "sterilize_plan_time": { - "name": "Plánovaný čas sterilizace" - }, - "sterilize_run_time": { - "name": "Doba běhu sterilizace" - }, - "super_fine_dust": { - "name": "Velmi jemný prach" - }, - "warming_center_state": { - "name": "Stav ohřevného centra" - }, - "water_liters": { - "name": "Spotřeba vody" - }, - "waterpurifier_status": { - "name": "Stav" - }, - "cup_state": { - "name": "Stav šálku" - }, - "last_pour_type": { - "name": "Poslední typ nalévání" - }, - "last_pour_capacity": { - "name": "Poslední nalité množství" - }, - "machine_state": { - "name": "Stav stroje", - "state": { - "idle": "Nečinný", - "active": "Aktivní", - "pause": "Pozastaveno" - } - }, - "filter_status": { - "name": "Stav filtru", - "state": { - "normal": "Normální", - "wash": "Umýt", - "replace": "Vyměnit" - } - }, - "ice_making_status": { - "name": "{instance_name} stav výroby", - "state": { - "icestatus_stop": "Nečinný", - "icestatus_run": "Vyrábí led" - } - }, - "zone_temperature": { - "name": "Teplota zóny" - } - }, - "switch": { - "ai_energy_level": { - "name": "Režim AI Energy" - }, - "air_monitoring": { - "name": "Sledování kvality vzduchu" - }, - "air_purify": { - "name": "Čištění vzduchu" - }, - "auto_clean": { - "name": "Automatické čištění" - }, - "absence_power_saving_active": { - "name": "Úspora energie při nepřítomnosti aktivní" - }, - "motion_detect_wind_active": { - "name": "Vyhýbání se proudu vzduchu při pohybu aktivní" - }, - "beep": { - "name": "Pípání" - }, - "display": { - "name": "Displej" - }, - "pet_filter_activation": { - "name": "Aktivace filtru pro domácí mazlíčky" - }, - "auto_empty": { - "name": "Automatické vyprázdnění" - }, - "dustbin_auto_close": { - "name": "Automatické zavření nádoby na prach" - }, - "spi": { - "name": "Ionizace vzduchu" - }, - "uvc_intensive_mode": { - "name": "Intenzivní režim UV-C" - }, - "auto_door_opener": { - "name": "Automatické otevírání dvířek" - }, - "auto_release_dry": { - "name": "Automatické uvolnění při sušení" - }, - "autofill": { - "name": "Automatické plnění konvice" - }, - "bubble_soak": { - "name": "Bublinkové namáčení" - }, - "buzz_lock": { - "name": "Zámek bzučáku" - }, - "cabinet_light_dim": { - "name": "Postupné rozsvícení" - }, - "cabinet_light_switch": { - "name": "Osvětlení skříně" - }, - "child_lock": { - "name": "Dětský zámek" - }, - "coldwater_lock": { - "name": "Zámek studené vody" - }, - "cooktop_child_lock": { - "name": "Dětský zámek" - }, - "defrost_delay": { - "name": "Odložené odmrazování" - }, - "display_light": { - "name": "Podsvícení displeje" - }, - "dnd": { - "name": "Nerušit" - }, - "fast_preheat": { - "name": "Rychlé předehřátí" - }, - "favorite_capacity_enabled": { - "name": "Oblíbené množství" - }, - "favorite_coffee_enabled": { - "name": "Oblíbená káva" - }, - "filter_remind": { - "name": "Připomenutí filtru" - }, - "fridge_sound": { - "name": "Zvuk" - }, - "hotwater_lock": { - "name": "Zámek horké vody" - }, - "ice_maker_enabled": { - "name": "Výrobník ledu" - }, - "ice_night_mode": { - "name": "Noční tichý režim výroby ledu" - }, - "instance_enabled": { - "name": "{instance_name} zapnuto" - }, - "intensive": { - "name": "Intenzivní" - }, - "lamp": { - "name": "Lampa" - }, - "led_night_light": { - "name": "Noční LED osvětlení dvířek" - }, - "mute_once": { - "name": "Jednou ztlumit" - }, - "natural_steam": { - "name": "Přírodní pára" - }, - "energy_saving": { - "name": "Úspora energie" - }, - "cooktop_on_alert": { - "name": "Upozornění na zapnutou varnou desku" - }, - "power_switch": { - "name": "Napájení" - }, - "pre_wash": { - "name": "Předpírka" - }, - "rapid_freezing": { - "name": "Rychlé zmrazování" - }, - "rapid_fridge": { - "name": "Rychlé chlazení" - }, - "remind_beep": { - "name": "Připomínací signál konce" - }, - "sabbath_mode": { - "name": "Sabatní režim" - }, - "sanitize": { - "name": "Dezinfekce" - }, - "sound": { - "name": "Zvuk" - }, - "storm_wash": { - "name": "Storm Wash+" - }, - "welcome_lighting": { - "name": "Uvítací osvětlení" - }, - "wrinkle_prevent": { - "name": "Ochrana proti pomačkání" - }, - "zone_power": { - "name": "Napájení zóny" - }, - "away_mode": { - "name": "Režim nepřítomnosti" - } - }, - "time": { - "dnd_end": { - "name": "Konec režimu Nerušit" - }, - "dnd_start": { - "name": "Začátek režimu Nerušit" - }, - "led_night_end": { - "name": "Konec nočního LED osvětlení dvířek" - }, - "led_night_start": { - "name": "Začátek nočního LED osvětlení dvířek" - }, - "night_end": { - "name": "Konec nočního osvětlení" - }, - "night_start": { - "name": "Začátek nočního osvětlení" - } - }, - "water_heater": { - "dhw": { - "name": "Teplá voda" - } - } - }, - "config": { - "step": { - "user": { - "title": "Přidat spotřebič Samsung", - "description": "Zadejte IP adresu vašeho spotřebiče Samsung a přihlašovací údaje CA AC14K_M použité k vydávání certifikátů zařízení.", - "data": { - "host": "IP adresa", - "ca_cert_pem": "Certifikát CA (PEM)", - "ca_key_pem": "Privátní klíč CA (PEM)" - }, - "data_description": { - "host": "Lokální IP adresa vašeho spotřebiče Samsung (např. 192.168.1.50).", - "ca_cert_pem": "Certifikát CA AC14K_M ve formátu PEM. Vložte celý obsah řetězce fullchain PEM včetně všech bloků BEGIN/END CERTIFICATE.", - "ca_key_pem": "Privátní klíč AC14K_M ve formátu PEM. Vložte celý obsah včetně hlavičky a patičky BEGIN/END PRIVATE KEY." - } - }, - "user_reuse": { - "title": "Přidat spotřebič Samsung", - "description": "Zadejte IP adresu vašeho spotřebiče Samsung. Přihlašovací údaje CA z existujícího spotřebiče LocalThings budou znovu použity.", - "data": { - "host": "IP adresa" - }, - "data_description": { - "host": "Lokální IP adresa vašeho spotřebiče Samsung (např. 192.168.1.50)." - } - }, - "confirm_unknown_type": { - "title": "Typ spotřebiče nebyl rozpoznán", - "description": "Typ tohoto spotřebiče se nepodařilo rozpoznat. Přesto bude přidán, ale pouze se společnými funkcemi (napájení, alarmy atd., pokud jsou k dispozici), nikoli s plnou sadou funkcí pro danou rodinu spotřebičů. Podporu můžete později pomoci rozšířit stažením diagnostiky pro toto zařízení (Nastavení > Zařízení a služby > toto zařízení > nabídka > Stáhnout diagnostiku) a jejím vložením do nového issue. Odesláním zařízení přidáte i tak." - } - }, - "error": { - "cannot_connect": "Nelze se připojit k zařízení. Ověřte, že je IP adresa dostupná a že jsou přihlašovací údaje CA správné.", - "invalid_ca": "Certifikát CA nebo privátní klíč se nepodařilo načíst. Ověřte, že obsah PEM je správný a že klíč odpovídá certifikátu.", - "unknown": "Neočekávaná chyba. Podrobnosti najdete v protokolech Home Assistant." - }, - "abort": { - "already_configured": "Zařízení je již nakonfigurováno" - } - }, - "options": { - "step": { - "init": { - "title": "Možnosti LocalThings", - "menu_options": { - "settings": "Nastavení zápisu pro dálkové ovládání", - "debug_write": "Ladění: zápis do prostředku" - } - }, - "settings": { - "title": "Nastavení zápisu pro dálkové ovládání", - "description": "Některá zařízení přijímají určité zápisy (např. výchozí dávkování pracího prostředku/aviváže v pračce) i když hlásí vypnuté dálkové ovládání. LocalThings ve výchozím nastavení blokuje každý zápis srozumitelnou chybou, kdykoli zařízení hlásí vypnuté dálkové ovládání, místo aby jej zařízení tiše odmítlo. Povolte toto pouze tehdy, pokud jste ověřili, že zápisy na tomto zařízení skutečně fungují i s vypnutým dálkovým ovládáním – jinak tuto srozumitelnou chybu vyměníte za tiché selhání.", - "data": { - "bypass_remote_control_lock": "Povolit zápis i když je dálkové ovládání hlášeno jako vypnuté", - "finish_time_hysteresis_minutes": "Odhadovaný konec -- minimální změna (minuty)" - } - }, - "debug_write": { - "title": "Ladění: zápis do prostředku", - "description": "Nástroj pro pokročilé uživatele k odhalení chování zápisu specifického pro dané zařízení. Vyberte prostředek (href), do kterého chcete zapisovat, nebo zadejte vlastní, který není uveden v seznamu. Toto obchází blokování při vypnutém dálkovém ovládání a odesílá přesně ta pole, která zadáte -- může to špatně nakonfigurovat váš spotřebič, používejte tedy záměrně.", - "data": { - "href": "Href prostředku" - } - }, - "debug_edit": { - "title": "Ladicí zápis: {href}", - "description": "Aktuální hodnota `{href}`:\n```\n{current_value}\n```\nZadejte POUZE pole, která chcete změnit. To, co zadáte, je odesláno zařízení tak, jak je (částečná aktualizace); NENÍ to sloučeno s poli zobrazenými výše, takže nevkládejte zpět celou hodnotu. Dávejte pozor na typy: číselný řetězec jako \"1\" musí zůstat v uvozovkách -- holé 1 je odesláno jako celé číslo, což některá zařízení odmítají.", - "data": { - "payload": "Zapisovaný obsah" - } - }, - "debug_result": { - "title": "Výsledek ladicího zápisu", - "description": "Zařízení vrátilo kód CoAP {code}. Třída 2.xx znamená, že zápis byl přijat; 4.xx/5.xx znamená, že byl odmítnut. Prostředek nyní obsahuje:\n```\n{new_value}\n```\nPokud se hodnota nezměnila, zařízení zápis pravděpodobně zahodilo. Vyberte, co dělat dál:", - "menu_options": { - "debug_write": "Zapsat do dalšího prostředku", - "finish": "Dokončit" - } - } - }, - "error": { - "empty_payload": "Zadejte alespoň jedno pole k zápisu.", - "write_failed": "Zápis se nezdařil. Podrobnosti najdete v protokolech Home Assistant." - }, - "abort": { - "not_loaded": "Toto zařízení ještě není připojeno. Zkuste to znovu, až se načte." - } - }, - "issues": { - "device_gap": { - "title": "Neúplné pokrytí funkcí pro {device_name}", - "description": "Toto zařízení nemá úplné pokrytí funkcí. Buď nebyl rozpoznán jeho typ spotřebiče, nebo některé jím poskytované prostředky ještě nejsou namodelovány. Bude i nadále fungovat se vším, co je již podporováno. Podporu můžete pomoci rozšířit tak, že přejdete do Nastavení > Zařízení a služby > {device_name} > nabídka (vpravo nahoře) > Stáhnout diagnostiku a poté ji vložíte do odkazované šablony issue." - } - }, - "exceptions": { - "remote_control_disabled": { - "message": "Dálkové ovládání je na tomto zařízení vypnuté. V návodu ke spotřebiči zjistěte, jak zapnout dálkové ovládání, než jej bude moci Home Assistant ovládat." - }, - "debug_payload_empty": { - "message": "Obsah ladicího zápisu musí být neprázdný objekt." - }, - "resource_href_required": { - "message": "Je vyžadován href prostředku." - }, - "bubble_soak_unavailable_for_cycle": { - "message": "Bublinkové namáčení není u vybraného cyklu k dispozici." - }, - "pre_wash_unavailable_for_cycle": { - "message": "Předpírka není u vybraného cyklu k dispozici." - }, - "intensive_unavailable_for_cycle": { - "message": "Intenzivní není u vybraného cyklu k dispozici." - } - } -} +{ + "entity": { + "binary_sensor": { + "auto_clean_running": { + "name": "Automatické čištění probíhá" + }, + "after_run_active": { + "name": "Dosoušení aktivní" + }, + "any_burner_active": { + "name": "Hořák aktivní" + }, + "automatic_operation": { + "name": "Automatický provoz" + }, + "battery_charging": { + "name": "Nabíjení" + }, + "burner_hot_surface": { + "name": "Hořák {number} horký povrch" + }, + "burner_pan_detected": { + "name": "Hořák {number} rozpoznána nádoba" + }, + "cloud_connected": { + "name": "Připojeno ke cloudu" + }, + "dustbag_full": { + "name": "Sáček na prach plný" + }, + "cooktop_power": { + "name": "Napájení varné desky" + }, + "cooktop_safety_shutoff_enabled": { + "name": "Automatické vypnutí při horkém povrchu" + }, + "current_limit_enabled": { + "name": "Omezení proudu zapnuto" + }, + "overload_protection_active": { + "name": "Ochrana proti přetížení aktivní" + }, + "odor_controller_active": { + "name": "Odstraňování pachů aktivní" + }, + "cycle_active": { + "name": "Běží" + }, + "defrost_active": { + "name": "Odmrazování aktivní" + }, + "detergent_low": { + "name": "Málo pracího prostředku" + }, + "device_active": { + "name": "Zařízení aktivní" + }, + "door_open": { + "name": "Dvířka" + }, + "filter_door_status": { + "name": "Dvířka filtru" + }, + "firmware_update": { + "name": "Dostupná aktualizace firmwaru" + }, + "instance_open": { + "name": "{instance_name} otevřeno" + }, + "paired_hood_connected": { + "name": "Spárovaný odsavač připojen" + }, + "paired_hood_light": { + "name": "Světlo spárovaného odsavače" + }, + "paired_hood_power": { + "name": "Napájení spárovaného odsavače" + }, + "periodic_air_sensing": { + "name": "Pravidelné měření kvality vzduchu" + }, + "pouring": { + "name": "Nalévání" + }, + "alarm_in_mute": { + "name": "Alarm ztlumen" + }, + "child_lock": { + "name": "Dětská pojistka" + }, + "power_state": { + "name": "Stav napájení" + }, + "probe_connected": { + "name": "Sonda připojena" + }, + "power_switch": { + "name": "Napájení" + }, + "rapid_freezing": { + "name": "Rychlé zmrazování" + }, + "rapid_fridge": { + "name": "Rychlé chlazení" + }, + "remote_control": { + "name": "Chytré ovládání" + }, + "softener_low": { + "name": "Málo aviváže" + } + }, + "button": { + "after_run_cancel": { + "name": "Zrušit dosoušení" + }, + "diagnosis_start": { + "name": "Spustit diagnostiku" + }, + "pause": { + "name": "Pozastavit" + }, + "selfcheck_start": { + "name": "Spustit vlastní kontrolu" + }, + "start": { + "name": "Spustit" + }, + "stop": { + "name": "Zastavit" + } + }, + "climate": { + "airconditioner": { + "state_attributes": { + "fan_mode": { + "state": { + "turbo": "Turbo", + "max": "Max" + } + }, + "preset_mode": { + "state": { + "ai_comfort": "AI komfort", + "quiet": "Tichý", + "smart": "Chytrý", + "speed": "Rychlý", + "nano": "WindFree", + "nanosleep": "WindFree spánek", + "longwind": "Dlouhý vánek", + "motionindirect": "Nepřímý vzduch při pohybu", + "motiondirect": "Přímý vzduch při pohybu", + "drycomfort": "Komfortní sušení", + "2step": "2stupňový" + } + } + } + } + }, + "fan": { + "air_purifier_fan": { + "state_attributes": { + "preset_mode": { + "state": { + "smart": "Chytrý", + "max": "Max", + "mid": "Střední", + "windfree": "WindFree", + "sleep": "Spánek" + } + } + } + } + }, + "number": { + "cook_time": { + "name": "Doba vaření" + }, + "delay_start_hours": { + "name": "Odložený start" + }, + "dispense_capacity": { + "name": "Dávkované množství" + }, + "good_sleep": { + "name": "Klidný spánek" + }, + "instance_setpoint": { + "name": "{instance_name} požadovaná teplota" + }, + "oven_setpoint": { + "name": "Požadovaná teplota" + }, + "setpoint": { + "name": "Požadovaná teplota" + }, + "sound_volume": { + "name": "Hlasitost zvuku" + }, + "target_humidity": { + "name": "Požadovaná vlhkost" + }, + "tropical_night_mode": { + "name": "Tropický noční režim" + }, + "zone_target_temperature": { + "name": "Požadovaná teplota zóny" + } + }, + "select": { + "ai_energy_level": { + "name": "Úroveň režimu AI Energy" + }, + "air_filter_threshold": { + "name": "Práh upozornění na filtr" + }, + "beverage_zone_mode": { + "name": "Režim zóny nápojů", + "state": { + "sp_ttype_beer_drinks": "Nápoje", + "sp_ttype_wine_dessert": "Víno a dezert" + } + }, + "brightness_level": { + "name": "Noční jas", + "state": { + "33": "Nízký", + "66": "Střední", + "100": "Vysoký" + } + }, + "cooler_temperature_setpoint": { + "name": "Teplota chladicí zóny" + }, + "day_brightness": { + "name": "Jas skříně", + "state": { + "33": "Nízký", + "66": "Střední", + "100": "Vysoký" + } + }, + "buzzer_sound": { + "name": "Zvuk bzučáku", + "state": { + "off": "Vypnuto", + "on": "Zapnuto" + } + }, + "discharging_time": { + "name": "Doba vypouštění", + "state": { + "1": "1 minuta", + "3": "3 minuty" + } + }, + "cycle": { + "name": "Cyklus" + }, + "detergent_quantity": { + "name": "Množství pracího prostředku", + "state": { + "00": "Žádné", + "01": "Nízké", + "02": "Střední", + "03": "Vysoké" + } + }, + "detergent_water_hardness": { + "name": "Tvrdost vody pro prací prostředek", + "state": { + "01": "Měkká", + "02": "Střední", + "03": "Tvrdá" + } + }, + "dishwasher_cycle": { + "name": "Cyklus", + "state": { + "80": "Jemné", + "83": "Express 60", + "84": "Intenzivní", + "86": "Normální", + "90": "Samočištění", + "0e": "AI mytí", + "07": "Předoplach", + "8d": "Hrnce a pánve", + "8e": "Plast", + "8f": "Dětské potřeby" + } + }, + "dispense_type": { + "name": "Typ dávkování" + }, + "door_alert": { + "name": "Alarm dvířek", + "state": { + "1": "Alarm 1", + "2": "Alarm 2", + "3": "Alarm 3", + "4": "Alarm 4" + } + }, + "dryer_cycle_table_03": { + "name": "Cyklus", + "state": { + "01": "Normální", + "06": "Časové sušení", + "16": "Bavlna", + "17": "Super Speed", + "18": "Syntetika", + "19": "Jemné prádlo", + "20": "Žehlení", + "21": "Hygienické sušení", + "22": "Tiché sušení", + "23": "Rychlé sušení 35", + "24": "Studený vzduch", + "25": "Teplý vzduch", + "27": "Časové sušení", + "29": "AI sušení", + "1a": "Vlna", + "1b": "Ložní prádlo", + "1c": "Košile", + "1d": "Ručníky", + "1e": "Outdoor", + "1f": "Smíšená náplň", + "2b": "Sušení bubnu", + "4c": "Osvěžení vzduchem" + } + }, + "favorite_capacity": { + "name": "Oblíbené množství" + }, + "favorite_hotwater_temperature": { + "name": "Oblíbená teplota horké vody" + }, + "filter_alarm_time": { + "name": "Interval alarmu filtru", + "state": { + "180": "180 hodin", + "300": "300 hodin", + "500": "500 hodin", + "700": "700 hodin" + } + }, + "finish_sound": { + "name": "Zvuk dokončení", + "state": { + "off": "Vypnuto", + "on": "Zapnuto" + } + }, + "flex_zone_mode": { + "name": "Režim flexibilní zóny", + "state": { + "cv_ttype_rf9000a_freeze": "Mražení", + "cv_ttype_rf9000a_softfreeze": "Mírné mražení", + "cv_ttype_rf9000a_meat_fish": "Maso/Ryby", + "cv_ttype_rf9000a_fruit_veggies": "Ovoce a zelenina", + "cv_ttype_rf9000a_beverage": "Nápoje", + "cv_fdr_wine": "Víno", + "cv_fdr_deli": "Lahůdky", + "cv_fdr_beverage": "Nápoje", + "cv_fdr_meat": "Maso", + "cv_fdr_soft_freezer": "Mírný mrazák" + } + }, + "heated_dry": { + "name": "Chytré sušení", + "state": { + "off": "Vypnuto", + "low": "Nízké", + "high": "Vysoké", + "extra_high": "Extra vysoké" + } + }, + "hot_water_temperature": { + "name": "Teplota horké vody" + }, + "ice_type": { + "name": "{instance_name} typ", + "state": { + "off": "Vypnuto", + "whiskey_iceball_3": "3 kuličky/den", + "whiskey_iceball_6": "6 kuliček/den", + "whiskey_iceball_9": "9 kuliček/den" + } + }, + "led_brightness": { + "name": "Jas LED dvířek", + "state": { + "low": "Nízký", + "high": "Vysoký" + } + }, + "led_night_brightness": { + "name": "Noční jas LED dvířek", + "state": { + "low": "Nízký", + "high": "Vysoký" + } + }, + "cooking_mode": { + "name": "Režim vaření", + "state": { + "no_operation": "Bez provozu", + "micro_wave": "Mikrovlny", + "micro_wave_grill": "Mikrovlny + gril", + "micro_wave_convection": "Mikrovlny + konvekce", + "convection": "Konvekce", + "air_fryer": "Fritéza na vzduch", + "grill": "Gril", + "autocook": "Automatické vaření", + "autocook_custom": "Automatické vaření (vlastní)", + "deodorization": "Odstranění pachů", + "keep_warm": "Udržování teploty" + } + }, + "oven_mode": { + "name": "Režim vaření", + "state": { + "no_operation": "Bez provozu", + "bake": "Pečení", + "broil": "Gril", + "convection": "Konvekce", + "convection_bake": "Konvekční pečení", + "convection_broil": "Konvekční gril", + "frozen_pizza_plus": "Mražená pizza+", + "slow_cook": "Pomalé vaření", + "plate_warm": "Ohřev talířů", + "air_fry": "Fritéza na vzduch" + } + }, + "operating_mode": { + "name": "Provozní režim" + }, + "kimchi_zone_mode": { + "name": "{instance_name} režim uskladnění", + "state": { + "off": "Vypnuto", + "kimchi_storage_normal": "Kimči", + "kimchi_storage_cold": "Kimči, silné", + "kimchi_storage_warm": "Kimči, slabé", + "kimchi_storage_low_salt_normal": "Kimči s nízkým obsahem soli", + "kimchi_storage_low_salt_cold": "Kimči s nízkým obsahem soli, silné", + "kimchi_storage_low_salt_warm": "Kimči s nízkým obsahem soli, slabé", + "kimchi_storage_crunfch": "Křupavé kimči", + "kimchi_storage_buy": "Kupované kimči", + "storage_fridge_normal": "Chlazení", + "storage_fridge_cold": "Chlazení, silné", + "storage_fridge_warm": "Chlazení, slabé", + "storage_freezer_normal": "Mražení", + "storage_freezer_cold": "Mražení, silné", + "storage_freezer_warm": "Mražení, slabé", + "kimchi_ripe_low_temp": "Zrání kimči, nízká teplota", + "kimchi_ripe_normal_temp": "Zrání kimči, pokojová teplota", + "kimchi_ripe_kkakdugi": "Zrání kkakdugi", + "kimchi_ripe_dongchimi": "Zrání dongchimi", + "meat_ripe_normal": "Zrání masa", + "storage_meat": "Maso a ryby", + "storage_fridge_vegetables_fruit": "Ovoce a zelenina", + "storage_fresh_cereal": "Obiloviny", + "storage_fridge_drink": "Nápoje", + "storage_fresh_wine": "Víno", + "storage_fresh_potato_banana": "Brambory a banány" + } + }, + "pantry_zone_mode": { + "name": "Režim spíže", + "state": { + "fdr_wine": "Víno", + "fdr_deli": "Lahůdky", + "fdr_drinks": "Nápoje" + } + }, + "range_burner_power_level": { + "name": "Výkon hořáku {number}", + "state": { + "0": "Vypnuto", + "boost": "Boost", + "simmer": "Mírný var" + } + }, + "range_hood_lamp_brightness": { + "name": "Jas osvětlení", + "state": { + "1": "Úroveň 1", + "2": "Úroveň 2" + } + }, + "rinse_cycles": { + "name": "Počet máchání" + }, + "softener_concentration": { + "name": "Koncentrace aviváže", + "state": { + "01": "1x", + "02": "2x", + "03": "3x" + } + }, + "softener_quantity": { + "name": "Množství aviváže", + "state": { + "00": "Žádné", + "01": "Nízké", + "02": "Střední", + "03": "Vysoké" + } + }, + "sound_mode": { + "name": "Zvukový režim", + "state": { + "voice": "Hlas", + "tone": "Tón", + "mute": "Ztlumeno" + } + }, + "air_purifier_sound_mode": { + "name": "Zvukový režim", + "state": { + "mute": "Ztlumeno", + "buzzer": "Bzučák" + } + }, + "water_purifier_sound_mode": { + "name": "Zvukový režim", + "state": { + "voice": "Hlas", + "fixedtone": "Pevný tón", + "mute": "Ztlumeno" + } + }, + "spin_speed": { + "name": "Rychlost odstřeďování", + "state": { + "rinse_hold": "Zadržení máchání", + "no_spin": "Bez odstřeďování", + "low": "Nízká", + "medium": "Střední", + "high": "Vysoká" + } + }, + "wash_temperature": { + "name": "Teplota praní", + "state": { + "none": "Žádná", + "cold": "Studená", + "cool": "Chladná", + "warm": "Teplá", + "hot": "Horká", + "extra_hot": "Extra horká" + } + }, + "washer_cycle_table_02": { + "name": "Cyklus", + "state": { + "01": "Normální", + "04": "Rychlé praní", + "17": "Stažený program", + "1b": "Bavlna", + "1c": "Eco 40-60", + "1d": "Super rychlé", + "1e": "15min rychlé praní", + "1f": "Intenzivní studená", + "20": "Hygienická pára", + "21": "Barevné prádlo", + "22": "Vlna", + "23": "Outdoor", + "24": "Ručníky", + "25": "Syntetika", + "26": "Jemné prádlo", + "27": "Máchání+odstřeďování", + "28": "Vypuštění/odstřeďování", + "29": "Čištění bubnu+", + "2a": "Džíny", + "2b": "AI praní", + "2d": "Tiché praní", + "2e": "Dětské potřeby", + "2f": "Sportovní oblečení", + "30": "Zataženo", + "32": "Košile", + "33": "Ložní prádlo", + "34": "Smíšené", + "36": "Praní+sušení", + "37": "Sušení vzduchem", + "38": "Sušení bavlny", + "39": "Sušení syntetiky", + "3a": "Čištění bubnu", + "52": "Eco studená", + "53": "Intenzivní", + "54": "Ručníky", + "55": "Sportovní oblečení", + "57": "Jemné", + "5e": "Máchání+odstřeďování", + "60": "Samočištění+", + "65": "Barevné prádlo", + "66": "Džíny", + "7c": "Bílé prádlo", + "7d": "Ložní prádlo/nepromokavé", + "7e": "Samočištění", + "7f": "Vlna/jemné", + "86": "Hloubkové praní", + "87": "Stažený program", + "8f": "Intenzivní studená", + "96": "Méně mikrovláken" + } + }, + "washer_dry_level": { + "name": "Úroveň sušení", + "state": { + "30": "30 min", + "60": "1 h", + "90": "1 h 30 min", + "120": "2 h", + "180": "3 h", + "240": "4 h", + "none": "Vypnuto", + "cupboard": "Do skříně" + } + }, + "zone_mode": { + "name": "Režim zóny" + }, + "watertank_light_color": { + "name": "Barva osvětlení nádržky na vodu", + "state": { + "warmwhite": "Teplá bílá", + "naturalwhite": "Přirozená bílá", + "coolwhite": "Studená bílá", + "green": "Zelená", + "purple": "Fialová", + "blue": "Modrá", + "orange": "Oranžová", + "pink": "Růžová" + } + }, + "watertank_light_brightness": { + "name": "Jas osvětlení nádržky na vodu" + } + }, + "sensor": { + "auto_clean_progress": { + "name": "Průběh automatického čištění" + }, + "after_run_progress": { + "name": "Průběh dosoušení" + }, + "air_filter_usage": { + "name": "Využití filtru" + }, + "air_filter_usage_hours": { + "name": "Hodiny využití filtru" + }, + "air_quality_standard": { + "name": "Norma kvality vzduchu" + }, + "air_sensing_state": { + "name": "Stav měření kvality vzduchu" + }, + "alarm_code": { + "name": "Kód alarmu" + }, + "auto_ventilation_action": { + "name": "Akce automatického větrání" + }, + "automatic_ventilation_state": { + "name": "Stav automatického větrání" + }, + "battery": { + "name": "Baterie" + }, + "burner_state": { + "name": "Stav hořáku {number}" + }, + "clean_level": { + "name": "Úroveň čištění" + }, + "coffee_brew_status": { + "name": "Stav přípravy kávy" + }, + "co2": { + "name": "CO2" + }, + "connection_mode": { + "name": "Režim připojení", + "state": { + "observe": "Push (observe)", + "poll": "Dotazování" + } + }, + "cooktop_running_state": { + "name": "Provozní stav varné desky" + }, + "cooktop_state": { + "name": "Stav varné desky" + }, + "current_limit_level": { + "name": "Úroveň omezení proudu" + }, + "filter_time": { + "name": "Čas filtru" + }, + "hepa_filter_usage": { + "name": "Využití HEPA filtru" + }, + "outdoor_temperature": { + "name": "Venkovní teplota" + }, + "odor_controller_progress": { + "name": "Průběh odstraňování pachů" + }, + "panel_status": { + "name": "Stav panelu" + }, + "sound_output": { + "name": "Zvukový výstup" + }, + "dustbag_usage": { + "name": "Využití sáčku na prach" + }, + "cleanstation_status": { + "name": "Stav čisticí stanice" + }, + "stick_status": { + "name": "Stav tyče" + }, + "uvc_operation_time": { + "name": "Doba provozu UV-C" + }, + "uvc_total_operation_time": { + "name": "Celková doba provozu UV-C" + }, + "uvc_finished_time": { + "name": "Čas dokončení UV-C" + }, + "uvc_emitted_time": { + "name": "Doba vyzařování UV-C" + }, + "overload_protection_mode": { + "name": "Režim ochrany proti přetížení", + "state": { + "alarm": "Alarm", + "powersaving": "Úspora energie" + } + }, + "absence_power_saving_mode": { + "name": "Režim úspory energie při nepřítomnosti", + "state": { + "eco": "Eco", + "normal": "Normální", + "comfort": "Komfort" + } + }, + "motion_detect_wind_mode": { + "name": "Režim vzduchu při detekci pohybu", + "state": { + "direct": "Přímý", + "indirect": "Nepřímý" + } + }, + "current_temp_c": { + "name": "Teplota" + }, + "current_temperature_c": { + "name": "Teplota" + }, + "diagnosis": { + "name": "Diagnostika" + }, + "diagnosis_status": { + "name": "Stav diagnostiky" + }, + "drum_clean_cycles_remaining": { + "name": "Čištění bubnu za" + }, + "drum_clean_last_cleaned": { + "name": "Poslední čištění bubnu" + }, + "dry_level": { + "name": "Úroveň sušení" + }, + "dry_time": { + "name": "Doba sušení" + }, + "dryer_type": { + "name": "Typ sušičky" + }, + "dust": { + "name": "Prach" + }, + "energy_kwh": { + "name": "Energie" + }, + "energy_last_month_kwh": { + "name": "Energie (minulý měsíc)" + }, + "energy_saved_kwh": { + "name": "Ušetřená energie" + }, + "energy_this_month_kwh": { + "name": "Energie (tento měsíc)" + }, + "fan_direction": { + "name": "Směr proudění vzduchu" + }, + "fan_speed_level": { + "name": "Úroveň otáček ventilátoru" + }, + "filter_clean_remain_time": { + "name": "Zbývající čas do čištění filtru" + }, + "filter_progress": { + "name": "Průběh filtru" + }, + "filter_usage": { + "name": "Využití filtru" + }, + "fine_dust": { + "name": "Jemný prach" + }, + "finish_time": { + "name": "Odhadované dokončení" + }, + "completion_minutes": { + "name": "Čas dokončení" + }, + "freezer_temperature": { + "name": "Teplota mrazáku" + }, + "fridge_temperature": { + "name": "Teplota chladničky" + }, + "kimchi_ripening_status": { + "name": "{instance_name} stav zrání" + }, + "kimchi_ripening_remaining": { + "name": "{instance_name} zbývající čas zrání" + }, + "kimchi_rack_count": { + "name": "{instance_name} počet přihrádek" + }, + "hood_filter_capacity": { + "name": "Kapacita filtru" + }, + "humidity": { + "name": "Vlhkost" + }, + "hood_filter_usage": { + "name": "Využití filtru" + }, + "instance_temperature": { + "name": "{instance_name} teplota" + }, + "job_beginning_status": { + "name": "Stav zahájení úlohy" + }, + "last_air_sensing_level": { + "name": "Poslední úroveň měření vzduchu" + }, + "last_air_sensing_time": { + "name": "Poslední čas měření vzduchu" + }, + "main_timer_current": { + "name": "Aktuální hodnota časovače" + }, + "main_timer_state": { + "name": "Stav časovače" + }, + "odor": { + "name": "Pach" + }, + "operating_mode": { + "name": "Provozní režim" + }, + "operation_origin": { + "name": "Poslední zdroj ovládání" + }, + "operation_time_minutes": { + "name": "Doba provozu" + }, + "oven_state": { + "name": "Stav trouby" + }, + "cavity_state": { + "name": "Stav trouby" + }, + "power_level": { + "name": "Úroveň výkonu" + }, + "paired_hood_fan_speed": { + "name": "Otáčky ventilátoru spárovaného odsavače" + }, + "paired_hood_firmware": { + "name": "Firmware spárovaného odsavače" + }, + "paired_hood_model": { + "name": "Model spárovaného odsavače" + }, + "power_energy_kwh": { + "name": "Spotřeba energie" + }, + "power_watts": { + "name": "Výkon" + }, + "probe_battery": { + "name": "Baterie sondy" + }, + "probe_target_temperature": { + "name": "Požadovaná teplota sondy" + }, + "probe_temperature": { + "name": "Teplota sondy" + }, + "progress": { + "name": "Průběh" + }, + "progress_percentage": { + "name": "Průběh v procentech" + }, + "selfcheck_error": { + "name": "Chyba vlastní kontroly" + }, + "selfcheck_result": { + "name": "Výsledek vlastní kontroly" + }, + "selfcheck_status": { + "name": "Stav vlastní kontroly" + }, + "sterilize_last_time": { + "name": "Poslední sterilizace" + }, + "sterilize_period": { + "name": "Interval sterilizace" + }, + "sterilize_plan_time": { + "name": "Plánovaný čas sterilizace" + }, + "sterilize_run_time": { + "name": "Doba běhu sterilizace" + }, + "super_fine_dust": { + "name": "Velmi jemný prach" + }, + "warming_center_state": { + "name": "Stav ohřevného centra" + }, + "water_liters": { + "name": "Spotřeba vody" + }, + "waterpurifier_status": { + "name": "Stav" + }, + "cup_state": { + "name": "Stav šálku" + }, + "last_pour_type": { + "name": "Poslední typ nalévání" + }, + "last_pour_capacity": { + "name": "Poslední nalité množství" + }, + "machine_state": { + "name": "Stav stroje", + "state": { + "idle": "Nečinný", + "active": "Aktivní", + "pause": "Pozastaveno" + } + }, + "filter_status": { + "name": "Stav filtru", + "state": { + "normal": "Normální", + "wash": "Umýt", + "replace": "Vyměnit" + } + }, + "ice_making_status": { + "name": "{instance_name} stav výroby", + "state": { + "icestatus_stop": "Nečinný", + "icestatus_run": "Vyrábí led" + } + }, + "zone_temperature": { + "name": "Teplota zóny" + }, + "watertank_full_alarm_status": { + "name": "Stav alarmu plné nádržky na vodu" + } + }, + "switch": { + "ai_energy_level": { + "name": "Režim AI Energy" + }, + "air_monitoring": { + "name": "Sledování kvality vzduchu" + }, + "air_purify": { + "name": "Čištění vzduchu" + }, + "auto_clean": { + "name": "Automatické čištění" + }, + "absence_power_saving_active": { + "name": "Úspora energie při nepřítomnosti aktivní" + }, + "motion_detect_wind_active": { + "name": "Vyhýbání se proudu vzduchu při pohybu aktivní" + }, + "beep": { + "name": "Pípání" + }, + "display": { + "name": "Displej" + }, + "pet_filter_activation": { + "name": "Aktivace filtru pro domácí mazlíčky" + }, + "auto_empty": { + "name": "Automatické vyprázdnění" + }, + "dustbin_auto_close": { + "name": "Automatické zavření nádoby na prach" + }, + "spi": { + "name": "Ionizace vzduchu" + }, + "uvc_intensive_mode": { + "name": "Intenzivní režim UV-C" + }, + "auto_door_opener": { + "name": "Automatické otevírání dvířek" + }, + "auto_release_dry": { + "name": "Automatické uvolnění při sušení" + }, + "autofill": { + "name": "Automatické plnění konvice" + }, + "bubble_soak": { + "name": "Bublinkové namáčení" + }, + "buzz_lock": { + "name": "Zámek bzučáku" + }, + "cabinet_light_dim": { + "name": "Postupné rozsvícení" + }, + "cabinet_light_switch": { + "name": "Osvětlení skříně" + }, + "child_lock": { + "name": "Dětský zámek" + }, + "coldwater_lock": { + "name": "Zámek studené vody" + }, + "cooktop_child_lock": { + "name": "Dětský zámek" + }, + "defrost_delay": { + "name": "Odložené odmrazování" + }, + "display_light": { + "name": "Podsvícení displeje" + }, + "dnd": { + "name": "Nerušit" + }, + "fast_preheat": { + "name": "Rychlé předehřátí" + }, + "favorite_capacity_enabled": { + "name": "Oblíbené množství" + }, + "favorite_coffee_enabled": { + "name": "Oblíbená káva" + }, + "filter_remind": { + "name": "Připomenutí filtru" + }, + "fridge_sound": { + "name": "Zvuk" + }, + "hotwater_lock": { + "name": "Zámek horké vody" + }, + "ice_maker_enabled": { + "name": "Výrobník ledu" + }, + "ice_night_mode": { + "name": "Noční tichý režim výroby ledu" + }, + "instance_enabled": { + "name": "{instance_name} zapnuto" + }, + "intensive": { + "name": "Intenzivní" + }, + "lamp": { + "name": "Lampa" + }, + "led_night_light": { + "name": "Noční LED osvětlení dvířek" + }, + "mute_once": { + "name": "Jednou ztlumit" + }, + "natural_steam": { + "name": "Přírodní pára" + }, + "energy_saving": { + "name": "Úspora energie" + }, + "cooktop_on_alert": { + "name": "Upozornění na zapnutou varnou desku" + }, + "power_switch": { + "name": "Napájení" + }, + "pre_wash": { + "name": "Předpírka" + }, + "rapid_freezing": { + "name": "Rychlé zmrazování" + }, + "rapid_fridge": { + "name": "Rychlé chlazení" + }, + "remind_beep": { + "name": "Připomínací signál konce" + }, + "sabbath_mode": { + "name": "Sabatní režim" + }, + "sanitize": { + "name": "Dezinfekce" + }, + "sound": { + "name": "Zvuk" + }, + "storm_wash": { + "name": "Storm Wash+" + }, + "welcome_lighting": { + "name": "Uvítací osvětlení" + }, + "wrinkle_prevent": { + "name": "Ochrana proti pomačkání" + }, + "zone_power": { + "name": "Napájení zóny" + }, + "away_mode": { + "name": "Režim nepřítomnosti" + }, + "watertank_light": { + "name": "Osvětlení nádržky na vodu" + } + }, + "time": { + "dnd_end": { + "name": "Konec režimu Nerušit" + }, + "dnd_start": { + "name": "Začátek režimu Nerušit" + }, + "led_night_end": { + "name": "Konec nočního LED osvětlení dvířek" + }, + "led_night_start": { + "name": "Začátek nočního LED osvětlení dvířek" + }, + "night_end": { + "name": "Konec nočního osvětlení" + }, + "night_start": { + "name": "Začátek nočního osvětlení" + } + }, + "water_heater": { + "dhw": { + "name": "Teplá voda" + } + } + }, + "config": { + "step": { + "user": { + "title": "Přidat spotřebič Samsung", + "description": "Zadejte IP adresu vašeho spotřebiče Samsung a přihlašovací údaje CA AC14K_M použité k vydávání certifikátů zařízení.", + "data": { + "host": "IP adresa", + "ca_cert_pem": "Certifikát CA (PEM)", + "ca_key_pem": "Privátní klíč CA (PEM)" + }, + "data_description": { + "host": "Lokální IP adresa vašeho spotřebiče Samsung (např. 192.168.1.50).", + "ca_cert_pem": "Certifikát CA AC14K_M ve formátu PEM. Vložte celý obsah řetězce fullchain PEM včetně všech bloků BEGIN/END CERTIFICATE.", + "ca_key_pem": "Privátní klíč AC14K_M ve formátu PEM. Vložte celý obsah včetně hlavičky a patičky BEGIN/END PRIVATE KEY." + } + }, + "user_reuse": { + "title": "Přidat spotřebič Samsung", + "description": "Zadejte IP adresu vašeho spotřebiče Samsung. Přihlašovací údaje CA z existujícího spotřebiče LocalThings budou znovu použity.", + "data": { + "host": "IP adresa" + }, + "data_description": { + "host": "Lokální IP adresa vašeho spotřebiče Samsung (např. 192.168.1.50)." + } + }, + "confirm_unknown_type": { + "title": "Typ spotřebiče nebyl rozpoznán", + "description": "Typ tohoto spotřebiče se nepodařilo rozpoznat. Přesto bude přidán, ale pouze se společnými funkcemi (napájení, alarmy atd., pokud jsou k dispozici), nikoli s plnou sadou funkcí pro danou rodinu spotřebičů. Podporu můžete později pomoci rozšířit stažením diagnostiky pro toto zařízení (Nastavení > Zařízení a služby > toto zařízení > nabídka > Stáhnout diagnostiku) a jejím vložením do nového issue. Odesláním zařízení přidáte i tak." + } + }, + "error": { + "cannot_connect": "Nelze se připojit k zařízení. Ověřte, že je IP adresa dostupná a že jsou přihlašovací údaje CA správné.", + "invalid_ca": "Certifikát CA nebo privátní klíč se nepodařilo načíst. Ověřte, že obsah PEM je správný a že klíč odpovídá certifikátu.", + "unknown": "Neočekávaná chyba. Podrobnosti najdete v protokolech Home Assistant." + }, + "abort": { + "already_configured": "Zařízení je již nakonfigurováno" + } + }, + "options": { + "step": { + "init": { + "title": "Možnosti LocalThings", + "menu_options": { + "settings": "Nastavení zápisu pro dálkové ovládání", + "debug_write": "Ladění: zápis do prostředku" + } + }, + "settings": { + "title": "Nastavení zápisu pro dálkové ovládání", + "description": "Některá zařízení přijímají určité zápisy (např. výchozí dávkování pracího prostředku/aviváže v pračce) i když hlásí vypnuté dálkové ovládání. LocalThings ve výchozím nastavení blokuje každý zápis srozumitelnou chybou, kdykoli zařízení hlásí vypnuté dálkové ovládání, místo aby jej zařízení tiše odmítlo. Povolte toto pouze tehdy, pokud jste ověřili, že zápisy na tomto zařízení skutečně fungují i s vypnutým dálkovým ovládáním – jinak tuto srozumitelnou chybu vyměníte za tiché selhání.", + "data": { + "bypass_remote_control_lock": "Povolit zápis i když je dálkové ovládání hlášeno jako vypnuté", + "finish_time_hysteresis_minutes": "Odhadovaný konec -- minimální změna (minuty)" + } + }, + "debug_write": { + "title": "Ladění: zápis do prostředku", + "description": "Nástroj pro pokročilé uživatele k odhalení chování zápisu specifického pro dané zařízení. Vyberte prostředek (href), do kterého chcete zapisovat, nebo zadejte vlastní, který není uveden v seznamu. Toto obchází blokování při vypnutém dálkovém ovládání a odesílá přesně ta pole, která zadáte -- může to špatně nakonfigurovat váš spotřebič, používejte tedy záměrně.", + "data": { + "href": "Href prostředku" + } + }, + "debug_edit": { + "title": "Ladicí zápis: {href}", + "description": "Aktuální hodnota `{href}`:\n```\n{current_value}\n```\nZadejte POUZE pole, která chcete změnit. To, co zadáte, je odesláno zařízení tak, jak je (částečná aktualizace); NENÍ to sloučeno s poli zobrazenými výše, takže nevkládejte zpět celou hodnotu. Dávejte pozor na typy: číselný řetězec jako \"1\" musí zůstat v uvozovkách -- holé 1 je odesláno jako celé číslo, což některá zařízení odmítají.", + "data": { + "payload": "Zapisovaný obsah" + } + }, + "debug_result": { + "title": "Výsledek ladicího zápisu", + "description": "Zařízení vrátilo kód CoAP {code}. Třída 2.xx znamená, že zápis byl přijat; 4.xx/5.xx znamená, že byl odmítnut. Prostředek nyní obsahuje:\n```\n{new_value}\n```\nPokud se hodnota nezměnila, zařízení zápis pravděpodobně zahodilo. Vyberte, co dělat dál:", + "menu_options": { + "debug_write": "Zapsat do dalšího prostředku", + "finish": "Dokončit" + } + } + }, + "error": { + "empty_payload": "Zadejte alespoň jedno pole k zápisu.", + "write_failed": "Zápis se nezdařil. Podrobnosti najdete v protokolech Home Assistant." + }, + "abort": { + "not_loaded": "Toto zařízení ještě není připojeno. Zkuste to znovu, až se načte." + } + }, + "issues": { + "device_gap": { + "title": "Neúplné pokrytí funkcí pro {device_name}", + "description": "Toto zařízení nemá úplné pokrytí funkcí. Buď nebyl rozpoznán jeho typ spotřebiče, nebo některé jím poskytované prostředky ještě nejsou namodelovány. Bude i nadále fungovat se vším, co je již podporováno. Podporu můžete pomoci rozšířit tak, že přejdete do Nastavení > Zařízení a služby > {device_name} > nabídka (vpravo nahoře) > Stáhnout diagnostiku a poté ji vložíte do odkazované šablony issue." + } + }, + "exceptions": { + "remote_control_disabled": { + "message": "Dálkové ovládání je na tomto zařízení vypnuté. V návodu ke spotřebiči zjistěte, jak zapnout dálkové ovládání, než jej bude moci Home Assistant ovládat." + }, + "debug_payload_empty": { + "message": "Obsah ladicího zápisu musí být neprázdný objekt." + }, + "resource_href_required": { + "message": "Je vyžadován href prostředku." + }, + "bubble_soak_unavailable_for_cycle": { + "message": "Bublinkové namáčení není u vybraného cyklu k dispozici." + }, + "pre_wash_unavailable_for_cycle": { + "message": "Předpírka není u vybraného cyklu k dispozici." + }, + "intensive_unavailable_for_cycle": { + "message": "Intenzivní není u vybraného cyklu k dispozici." + } + } +} diff --git a/custom_components/localthings/translations/en.json b/custom_components/localthings/translations/en.json index 9a9ba06..8495f04 100644 --- a/custom_components/localthings/translations/en.json +++ b/custom_components/localthings/translations/en.json @@ -621,6 +621,22 @@ }, "zone_mode": { "name": "Zone mode" + }, + "watertank_light_color": { + "name": "Water tank light color", + "state": { + "warmwhite": "Warm white", + "naturalwhite": "Natural white", + "coolwhite": "Cool white", + "green": "Green", + "purple": "Purple", + "blue": "Blue", + "orange": "Orange", + "pink": "Pink" + } + }, + "watertank_light_brightness": { + "name": "Water tank light brightness" } }, "sensor": { @@ -969,6 +985,9 @@ }, "zone_temperature": { "name": "Zone temperature" + }, + "watertank_full_alarm_status": { + "name": "Water tank full alarm status" } }, "switch": { @@ -1136,6 +1155,9 @@ }, "away_mode": { "name": "Away mode" + }, + "watertank_light": { + "name": "Water tank light" } }, "time": { diff --git a/custom_components/localthings/translations/es.json b/custom_components/localthings/translations/es.json index 55f2f99..30493b4 100644 --- a/custom_components/localthings/translations/es.json +++ b/custom_components/localthings/translations/es.json @@ -735,6 +735,22 @@ }, "zone_mode": { "name": "Modo de zona" + }, + "watertank_light_color": { + "name": "Color de la luz del depósito de agua", + "state": { + "warmwhite": "Blanco cálido", + "naturalwhite": "Blanco natural", + "coolwhite": "Blanco frío", + "green": "Verde", + "purple": "Púrpura", + "blue": "Azul", + "orange": "Naranja", + "pink": "Rosa" + } + }, + "watertank_light_brightness": { + "name": "Brillo de la luz del depósito de agua" } }, "sensor": { @@ -1083,6 +1099,9 @@ }, "zone_temperature": { "name": "Temperatura de zona" + }, + "watertank_full_alarm_status": { + "name": "Estado de alarma de depósito de agua lleno" } }, "switch": { @@ -1250,6 +1269,9 @@ }, "zone_power": { "name": "Alimentación de zona" + }, + "watertank_light": { + "name": "Luz del depósito de agua" } }, "time": { diff --git a/custom_components/localthings/translations/it.json b/custom_components/localthings/translations/it.json index 5d7f61c..55f8bdf 100644 --- a/custom_components/localthings/translations/it.json +++ b/custom_components/localthings/translations/it.json @@ -621,6 +621,22 @@ }, "zone_mode": { "name": "Modalità zona" + }, + "watertank_light_color": { + "name": "Colore della luce del serbatoio dell'acqua", + "state": { + "warmwhite": "Bianco caldo", + "naturalwhite": "Bianco naturale", + "coolwhite": "Bianco freddo", + "green": "Verde", + "purple": "Viola", + "blue": "Blu", + "orange": "Arancione", + "pink": "Rosa" + } + }, + "watertank_light_brightness": { + "name": "Luminosità della luce del serbatoio dell'acqua" } }, "sensor": { @@ -969,6 +985,9 @@ }, "zone_temperature": { "name": "Temperatura zona" + }, + "watertank_full_alarm_status": { + "name": "Stato allarme serbatoio d'acqua pieno" } }, "switch": { @@ -1136,6 +1155,9 @@ }, "away_mode": { "name": "Modalità assenza" + }, + "watertank_light": { + "name": "Luce del serbatoio dell'acqua" } }, "time": { diff --git a/custom_components/localthings/translations/nl.json b/custom_components/localthings/translations/nl.json index d65dcf5..f25be37 100644 --- a/custom_components/localthings/translations/nl.json +++ b/custom_components/localthings/translations/nl.json @@ -621,6 +621,22 @@ }, "zone_mode": { "name": "Zonemodus" + }, + "watertank_light_color": { + "name": "Kleur van het waterreservoirlicht", + "state": { + "warmwhite": "Warm wit", + "naturalwhite": "Natuurlijk wit", + "coolwhite": "Koel wit", + "green": "Groen", + "purple": "Paars", + "blue": "Blauw", + "orange": "Oranje", + "pink": "Roze" + } + }, + "watertank_light_brightness": { + "name": "Helderheid van het waterreservoirlicht" } }, "sensor": { @@ -969,6 +985,9 @@ }, "zone_temperature": { "name": "Zonetemperatuur" + }, + "watertank_full_alarm_status": { + "name": "Status alarm waterreservoir vol" } }, "switch": { @@ -1136,6 +1155,9 @@ }, "away_mode": { "name": "Afwezigheidsmodus" + }, + "watertank_light": { + "name": "Waterreservoirlicht" } }, "time": { diff --git a/tests/fixtures/dehumidifier_tp1x_dhm01001_device.json b/tests/fixtures/dehumidifier_tp1x_dhm01001_device.json new file mode 100644 index 0000000..4bcdda5 --- /dev/null +++ b/tests/fixtures/dehumidifier_tp1x_dhm01001_device.json @@ -0,0 +1,426 @@ +{ + "device0": [ + { + "rt": [ + "x.com.samsung.devcol", + "oic.wk.col" + ], + "if": [ + "oic.if.baseline", + "oic.if.ll", + "oic.if.b" + ] + }, + { + "href": "/personality/presence/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "", + "x.com.samsung.da.deviceId": "**REDACTED**", + "x.com.samsung.da.value": "" + } + ] + } + }, + { + "href": "/realtimenotiforclient/vs/0", + "rep": { + "x.com.samsung.da.timeforshortnoti": "0", + "x.com.samsung.da.longnotisubscription": "false", + "x.com.samsung.da.periodicnotisubscription": "true" + } + }, + { + "href": "/filter/airdustfilter/vs/0", + "rep": { + "x.com.samsung.da.filterUsage": "100", + "x.com.samsung.da.filterUsageResolution": "1", + "x.com.samsung.da.filterDesiredUsage": "224", + "x.com.samsung.da.filterStatus": "wash", + "x.com.samsung.da.filterCapacity": "224", + "x.com.samsung.da.filterCapacityUnit": "Hour", + "x.com.samsung.da.filterResetType": [ + "washable" + ], + "x.com.samsung.da.supportedFilterDesiredUsage": [ + "224", + "300", + "500", + "700" + ] + } + }, + { + "href": "/mode/convenient/vs/0", + "rep": { + "x.com.samsung.da.supportedModes": [ + "Off", + "Sleep" + ] + } + }, + { + "href": "/option/autoclean/vs/0", + "rep": { + "x.com.samsung.da.status": "Stop", + "x.com.samsung.da.settingStatus": "On", + "x.com.samsung.da.progress": "0", + "cleaningTime": "30", + "minCleaningTime": "10", + "maxCleaningTime": "60", + "cleaningTimeUnit": "10", + "x.com.samsung.da.supportedStatus": [ + "Start", + "TimedClean", + "Stop" + ], + "x.com.samsung.da.supportedSettingStatus": [ + "On", + "TimedClean", + "Off" + ] + } + }, + { + "href": "/alarms/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "Alarm", + "x.com.samsung.da.alarmType": "Device", + "x.com.samsung.da.code": "ErrorCode_OFF", + "x.com.samsung.da.triggeredTime": "2026-08-03T14:54:15", + "x.com.samsung.da.state": "Deleted" + }, + { + "x.com.samsung.da.id": "2", + "x.com.samsung.da.description": "Alarm", + "x.com.samsung.da.alarmType": "Device", + "x.com.samsung.da.code": "WaterTankFull_OFF", + "x.com.samsung.da.triggeredTime": "2026-08-03T14:54:15", + "x.com.samsung.da.state": "Deleted" + } + ] + } + }, + { + "href": "/energy/consumption/vs/0", + "rep": { + "x.com.samsung.da.instantaneousPower": "145.000000", + "x.com.samsung.da.cumulativePower": "101100", + "x.com.samsung.da.cumulativeSavedPower": "0", + "x.com.samsung.da.cumulativeUnit": "Wh", + "x.com.samsung.da.instantaneousPowerUnit": "W", + "x.com.samsung.da.cumulativePowerType": "total" + } + }, + { + "href": "/mode/vs/0", + "rep": { + "x.com.samsung.da.supportedModes": [ + "Smart", + "Max", + "High", + "Medium", + "Quiet", + "ClothesDrying" + ], + "x.com.samsung.da.modes": [ + "Quiet" + ], + "x.com.samsung.da.options": [ + "Volume_Mute", + "RacInfo_None", + "UpdateAllow_NotAllowed", + "WelcomeCoolingState_Off" + ] + } + }, + { + "href": "/power/vs/0", + "rep": { + "x.com.samsung.da.power": "On", + "operationNumber": "1", + "displayCondition": "Enable" + } + }, + { + "href": "/sensors/vs/0", + "rep": {} + }, + { + "href": "/information/vs/0", + "rep": { + "x.com.samsung.da.modelNum": "TP1X_DA_AC_DHM_01001_0000|10283341|77000000001700000A00000000000000", + "x.com.samsung.da.description": "TP1X_DA_AC_DHM_01001_0000", + "x.com.samsung.da.serialNum": "**REDACTED**", + "x.com.samsung.da.otnDUID": "**REDACTED**", + "x.com.samsung.da.diagProtocolType": "BLE_OCF", + "x.com.samsung.da.diagLogType": [ + "errCode", + "dump" + ], + "x.com.samsung.da.diagDumpType": "file", + "x.com.samsung.da.diagEndPoint": "SSM", + "x.com.samsung.da.diagMnid": "0AJT", + "x.com.samsung.da.diagSetupid": "AY0", + "x.com.samsung.da.diagMinVersion": "3.0", + "x.com.samsung.da.diagTsId": "DA01", + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Software", + "x.com.samsung.da.number": "02544A260401", + "x.com.samsung.da.newVersionAvailable": "0" + }, + { + "x.com.samsung.da.id": "1", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Firmware", + "x.com.samsung.da.number": "02833A26010600,FFFFFFFFFFFFFF", + "x.com.samsung.da.newVersionAvailable": "0" + }, + { + "x.com.samsung.da.id": "2", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Outdoor", + "x.com.samsung.da.number": "02590A10000100,FFFFFFFFFFFFFF" + }, + { + "x.com.samsung.da.id": "4", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Touch IC", + "x.com.samsung.da.number": "02552A23020200,FFFFFFFFFFFFFF" + } + ] + } + }, + { + "href": "/file/information/vs/0", + "rep": { + "x.com.samsung.timeoffset": "+09:00", + "x.com.samsung.supprtedtype": 1 + } + }, + { + "href": "/configuration/vs/0", + "rep": { + "x.com.samsung.da.region": "0000000000", + "x.com.samsung.da.airconOptionList": [ + "SingleCommand_1", + "DR", + "HOMECARE_WIZARD_V2", + "PRODUCT_GLOBAL", + "AI_RAC_GLOBAL_2.0", + "AI_Heat" + ] + } + }, + { + "href": "/humidity/vs/0", + "rep": { + "x.com.samsung.da.humidity": "63", + "x.com.samsung.da.desiredHumidity": "60", + "increment": "5", + "x.com.samsung.da.fivepercentHumidity": "64" + } + }, + { + "href": "/drlc/vs/0", + "rep": { + "x.com.samsung.da.drlcLevel": "0", + "x.com.samsung.da.durationminutes": "0", + "x.com.samsung.da.start": "1970-01-01T00:00:00Z", + "x.com.samsung.da.override": "Off", + "x.com.samsung.da.realSaving": "Off" + } + }, + { + "href": "/availablecontrolsets/vs/0", + "rep": { + "x.com.samsung.da.sets": "04001E46070A5A00000000000000", + "x.com.samsung.da.id": "DHM", + "x.com.samsung.da.version": "1.0" + } + }, + { + "href": "/da/softreset/vs/0", + "rep": { + "x.com.samsung.da.softwarereset": "false" + } + }, + { + "href": "/keepnormalstate/vs/0", + "rep": { + "x.com.samsung.da.keepnormal": 1 + } + }, + { + "href": "/otninformation/vs/0", + "rep": { + "x.com.samsung.da.target": "", + "x.com.samsung.da.newVersionAvailable": "false", + "x.com.samsung.da.newVersionNo": "00000000", + "x.com.samsung.da.currentVersionInfo": "00000000", + "otnStatus": "None", + "flashingProgress": "", + "otnTarget": "main", + "otnCompleteDate": "2026-06-24", + "otnList": [ + { + "type": "WIFI", + "modelId": "ADH-WW-TP1-23-AY7100", + "versions": [ + "18260401" + ], + "visVersion": "260401" + }, + { + "type": "Micom", + "modelId": "042110283341FFFFFFFF", + "versions": [ + "26010600", + "FFFFFFFF" + ], + "visVersion": "260106" + }, + { + "type": "Micom", + "modelId": "042110259041FFFFFFFF", + "versions": [ + "10000100", + "FFFFFFFF" + ], + "visVersion": "100001" + }, + { + "type": "Micom", + "modelId": "042110255241FFFFFFFF", + "versions": [ + "23020200", + "FFFFFFFF" + ], + "visVersion": "230202" + } + ] + } + }, + { + "href": "/timezone/vs/0", + "rep": { + "timezoneid": "Asia/Seoul", + "offset": "+09:00", + "DST": "OFF" + } + }, + { + "href": "/option/muteonce/vs/0", + "rep": { + "muteonce": "Off" + } + }, + { + "href": "/reserverulesets/vs/0", + "rep": { + "x.com.samsung.da.sets": "3F1E461E461E46FFFFFFFF1E4600", + "x.com.samsung.da.id": "DHM", + "x.com.samsung.da.version": "1.0" + } + }, + { + "href": "/display/vs/0", + "rep": { + "mode": "On", + "supportedModes": [ + "On", + "Off" + ] + } + }, + { + "href": "/connectionconfig/vs/0", + "rep": { + "autoReconnectionMinVersion": "1.0", + "autoReconnection": "true", + "autoReconnectionProtocolType": [ + "helper_hotspot", + "ble_ocf" + ], + "supportedWiFiAuthType": [ + "OPEN", + "WEP", + "WPA-PSK", + "WPA2-PSK", + "SAE" + ], + "supportedWiFiCryptoType": [ + "TKIP", + "AES", + "WEP-64", + "WEP-128" + ], + "supportedWiFiFreq": [ + "2.4G" + ], + "calmConnectionCare": { + "version": "1.0", + "role": [ + "things" + ] + } + } + }, + { + "href": "/welcome/humidity/vs/0", + "rep": { + "requestId": "0000", + "operatingStatus": "None" + } + }, + { + "href": "/kidslock/vs/0", + "rep": { + "x.com.samsung.da.kidsLock": "Ready" + } + }, + { + "href": "/watertank/lighting/vs/0", + "rep": { + "status": "On", + "colorOption": "NaturalWhite", + "mode": "High", + "waterfullAlarmStatus": "On", + "colorSupportedList": [ + "WarmWhite", + "NaturalWhite", + "CoolWhite", + "Green", + "Purple", + "Blue", + "Orange", + "Pink" + ], + "modeSupportedList": [ + "High", + "Low" + ] + } + }, + { + "href": "/wirelessinfo/vs/0", + "rep": { + "macaddressWiFi": "**REDACTED**", + "macaddressBLE": "**REDACTED**" + } + }, + { + "href": "/quickcontrol/info/vs/0", + "rep": { + "supportedVersion": "1.0" + } + } + ] +} diff --git a/tests/fixtures/golden/dehumidifier_tp1x_dhm01001.json b/tests/fixtures/golden/dehumidifier_tp1x_dhm01001.json new file mode 100644 index 0000000..fc13d8f --- /dev/null +++ b/tests/fixtures/golden/dehumidifier_tp1x_dhm01001.json @@ -0,0 +1,27 @@ +{ + "state_keys": [ + "air_filter_status", + "air_filter_threshold", + "air_filter_usage", + "air_filter_usage_hours", + "alarm_code", + "auto_clean", + "auto_clean_progress", + "auto_clean_running", + "child_lock", + "display", + "energy_kwh", + "energy_saved_kwh", + "firmware_update", + "humidity", + "mute_once", + "operating_mode", + "power_switch", + "power_watts", + "target_humidity", + "watertank_full_alarm_status", + "watertank_light", + "watertank_light_brightness", + "watertank_light_color" + ] +} diff --git a/tests/test_dehumidifier_tp1x_dhm01001_capabilities.py b/tests/test_dehumidifier_tp1x_dhm01001_capabilities.py new file mode 100644 index 0000000..6a0e74a --- /dev/null +++ b/tests/test_dehumidifier_tp1x_dhm01001_capabilities.py @@ -0,0 +1,94 @@ +"""Tests for the TP1X_DA_AC_DHM_01001_0000 dehumidifier revision +(issues #271/#231, model AY70H18100GTD). + +Both issues submitted the identical dump: same board, same two previously +unbound hrefs (/display/vs/0, /watertank/lighting/vs/0). Confirms both the +new capabilities and the /oic/d device-type routing added alongside them. +""" + +from custom_components.localthings.registry.adapter import flatten +from custom_components.localthings.registry.by_type import ( + for_device_by_model, + for_device_by_oic_type, +) +from custom_components.localthings.registry.discovery import discover +from custom_components.localthings.registry.entities import SelectDesc, SwitchDesc +from tests.conftest import _load_device + + +def _dehumidifier(): + resources = _load_device("dehumidifier_tp1x_dhm01001") + info = resources["/information/vs/0"] + reg = for_device_by_model( + info["x.com.samsung.da.modelNum"], + info["x.com.samsung.da.description"], + ) + return reg, resources + + +def _bound(): + reg, resources = _dehumidifier() + return discover(resources, reg.capabilities, reg.pattern_capabilities), resources + + +def _state(): + bound, resources = _bound() + return flatten(bound, resources) + + +def _desc(key): + bound, _ = _bound() + return next(b.desc for b in bound if b.desc.key == key) + + +def test_model_resolves_to_dehumidifier_registry(): + reg, _ = _dehumidifier() + assert reg is not None and reg.name == "dehumidifier" + + +def test_oic_device_type_resolves_to_dehumidifier_registry(): + reg = for_device_by_oic_type(("oic.wk.d", "x.com.st.d.dehumidifier")) + assert reg is not None and reg.name == "dehumidifier" + + +def test_no_unbound_hrefs(): + reg, resources = _dehumidifier() + unbound = [] + discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append) + assert unbound == [] + + +def test_display_switch_present_and_reads_on(): + state = _state() + assert state["display"] is True + + +def test_watertank_light_switch_write_contract(): + desc = _desc("watertank_light") + assert isinstance(desc, SwitchDesc) + path, body = desc.write_fn("On", {}) + assert path == ["watertank", "lighting", "vs", "0"] + assert body == {"status": "On"} + + +def test_watertank_light_color_options_come_from_live_supported_list(): + desc = _desc("watertank_light_color") + assert isinstance(desc, SelectDesc) + assert desc.options_field == "colorSupportedList" + path, body = desc.write_fn("Green", {}) + assert path == ["watertank", "lighting", "vs", "0"] + assert body == {"colorOption": "Green"} + + +def test_watertank_light_brightness_options_come_from_live_supported_list(): + desc = _desc("watertank_light_brightness") + assert isinstance(desc, SelectDesc) + assert desc.options_field == "modeSupportedList" + path, body = desc.write_fn("Low", {}) + assert path == ["watertank", "lighting", "vs", "0"] + assert body == {"mode": "Low"} + + +def test_watertank_full_alarm_status_exposed_read_only(): + state = _state() + assert state["watertank_full_alarm_status"] == "On" diff --git a/tests/test_golden_regression.py b/tests/test_golden_regression.py index b88f5a0..3877446 100644 --- a/tests/test_golden_regression.py +++ b/tests/test_golden_regression.py @@ -1226,6 +1226,23 @@ def test_registry_reproduces_golden_state_keys_for_range_ne6516a(): ) +def test_registry_reproduces_golden_state_keys_for_dehumidifier_tp1x_dhm01001(): + """TP1X_DA_AC_DHM_01001_0000 revision (issues #271/#231) additionally + reports /display/vs/0 (air_purifier.DISPLAY's exact shape, reused as-is) + and /watertank/lighting/vs/0 (new WATERTANK_LIGHTING capability), both + unbound on the original issue #88 dump.""" + from tests.conftest import _load_device + + resources = _load_device("dehumidifier_tp1x_dhm01001") + golden = json.loads((GOLDEN / "dehumidifier_tp1x_dhm01001.json").read_text()) + state_keys = _new_state_keys("dehumidifier_tp1x_dhm01001", resources) + assert set(state_keys) == set(golden["state_keys"]), ( + f"state_keys mismatch:\n" + f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n" + f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}" + ) + + def test_resources_from_batch_preferred_over_flat(): from tests.conftest import _resources_from_dump From 0994ca487a87a608a78503faf02df02012555269 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:00:06 +0000 Subject: [PATCH 02/10] Restore CRLF line endings in cs.json The previous commit's translation update rewrote this file with LF endings; every other language file in the catalog already uses LF, but this one was CRLF before that change. --- .../localthings/translations/cs.json | 2606 ++++++++--------- 1 file changed, 1303 insertions(+), 1303 deletions(-) diff --git a/custom_components/localthings/translations/cs.json b/custom_components/localthings/translations/cs.json index a4f3170..95de516 100644 --- a/custom_components/localthings/translations/cs.json +++ b/custom_components/localthings/translations/cs.json @@ -1,1303 +1,1303 @@ -{ - "entity": { - "binary_sensor": { - "auto_clean_running": { - "name": "Automatické čištění probíhá" - }, - "after_run_active": { - "name": "Dosoušení aktivní" - }, - "any_burner_active": { - "name": "Hořák aktivní" - }, - "automatic_operation": { - "name": "Automatický provoz" - }, - "battery_charging": { - "name": "Nabíjení" - }, - "burner_hot_surface": { - "name": "Hořák {number} horký povrch" - }, - "burner_pan_detected": { - "name": "Hořák {number} rozpoznána nádoba" - }, - "cloud_connected": { - "name": "Připojeno ke cloudu" - }, - "dustbag_full": { - "name": "Sáček na prach plný" - }, - "cooktop_power": { - "name": "Napájení varné desky" - }, - "cooktop_safety_shutoff_enabled": { - "name": "Automatické vypnutí při horkém povrchu" - }, - "current_limit_enabled": { - "name": "Omezení proudu zapnuto" - }, - "overload_protection_active": { - "name": "Ochrana proti přetížení aktivní" - }, - "odor_controller_active": { - "name": "Odstraňování pachů aktivní" - }, - "cycle_active": { - "name": "Běží" - }, - "defrost_active": { - "name": "Odmrazování aktivní" - }, - "detergent_low": { - "name": "Málo pracího prostředku" - }, - "device_active": { - "name": "Zařízení aktivní" - }, - "door_open": { - "name": "Dvířka" - }, - "filter_door_status": { - "name": "Dvířka filtru" - }, - "firmware_update": { - "name": "Dostupná aktualizace firmwaru" - }, - "instance_open": { - "name": "{instance_name} otevřeno" - }, - "paired_hood_connected": { - "name": "Spárovaný odsavač připojen" - }, - "paired_hood_light": { - "name": "Světlo spárovaného odsavače" - }, - "paired_hood_power": { - "name": "Napájení spárovaného odsavače" - }, - "periodic_air_sensing": { - "name": "Pravidelné měření kvality vzduchu" - }, - "pouring": { - "name": "Nalévání" - }, - "alarm_in_mute": { - "name": "Alarm ztlumen" - }, - "child_lock": { - "name": "Dětská pojistka" - }, - "power_state": { - "name": "Stav napájení" - }, - "probe_connected": { - "name": "Sonda připojena" - }, - "power_switch": { - "name": "Napájení" - }, - "rapid_freezing": { - "name": "Rychlé zmrazování" - }, - "rapid_fridge": { - "name": "Rychlé chlazení" - }, - "remote_control": { - "name": "Chytré ovládání" - }, - "softener_low": { - "name": "Málo aviváže" - } - }, - "button": { - "after_run_cancel": { - "name": "Zrušit dosoušení" - }, - "diagnosis_start": { - "name": "Spustit diagnostiku" - }, - "pause": { - "name": "Pozastavit" - }, - "selfcheck_start": { - "name": "Spustit vlastní kontrolu" - }, - "start": { - "name": "Spustit" - }, - "stop": { - "name": "Zastavit" - } - }, - "climate": { - "airconditioner": { - "state_attributes": { - "fan_mode": { - "state": { - "turbo": "Turbo", - "max": "Max" - } - }, - "preset_mode": { - "state": { - "ai_comfort": "AI komfort", - "quiet": "Tichý", - "smart": "Chytrý", - "speed": "Rychlý", - "nano": "WindFree", - "nanosleep": "WindFree spánek", - "longwind": "Dlouhý vánek", - "motionindirect": "Nepřímý vzduch při pohybu", - "motiondirect": "Přímý vzduch při pohybu", - "drycomfort": "Komfortní sušení", - "2step": "2stupňový" - } - } - } - } - }, - "fan": { - "air_purifier_fan": { - "state_attributes": { - "preset_mode": { - "state": { - "smart": "Chytrý", - "max": "Max", - "mid": "Střední", - "windfree": "WindFree", - "sleep": "Spánek" - } - } - } - } - }, - "number": { - "cook_time": { - "name": "Doba vaření" - }, - "delay_start_hours": { - "name": "Odložený start" - }, - "dispense_capacity": { - "name": "Dávkované množství" - }, - "good_sleep": { - "name": "Klidný spánek" - }, - "instance_setpoint": { - "name": "{instance_name} požadovaná teplota" - }, - "oven_setpoint": { - "name": "Požadovaná teplota" - }, - "setpoint": { - "name": "Požadovaná teplota" - }, - "sound_volume": { - "name": "Hlasitost zvuku" - }, - "target_humidity": { - "name": "Požadovaná vlhkost" - }, - "tropical_night_mode": { - "name": "Tropický noční režim" - }, - "zone_target_temperature": { - "name": "Požadovaná teplota zóny" - } - }, - "select": { - "ai_energy_level": { - "name": "Úroveň režimu AI Energy" - }, - "air_filter_threshold": { - "name": "Práh upozornění na filtr" - }, - "beverage_zone_mode": { - "name": "Režim zóny nápojů", - "state": { - "sp_ttype_beer_drinks": "Nápoje", - "sp_ttype_wine_dessert": "Víno a dezert" - } - }, - "brightness_level": { - "name": "Noční jas", - "state": { - "33": "Nízký", - "66": "Střední", - "100": "Vysoký" - } - }, - "cooler_temperature_setpoint": { - "name": "Teplota chladicí zóny" - }, - "day_brightness": { - "name": "Jas skříně", - "state": { - "33": "Nízký", - "66": "Střední", - "100": "Vysoký" - } - }, - "buzzer_sound": { - "name": "Zvuk bzučáku", - "state": { - "off": "Vypnuto", - "on": "Zapnuto" - } - }, - "discharging_time": { - "name": "Doba vypouštění", - "state": { - "1": "1 minuta", - "3": "3 minuty" - } - }, - "cycle": { - "name": "Cyklus" - }, - "detergent_quantity": { - "name": "Množství pracího prostředku", - "state": { - "00": "Žádné", - "01": "Nízké", - "02": "Střední", - "03": "Vysoké" - } - }, - "detergent_water_hardness": { - "name": "Tvrdost vody pro prací prostředek", - "state": { - "01": "Měkká", - "02": "Střední", - "03": "Tvrdá" - } - }, - "dishwasher_cycle": { - "name": "Cyklus", - "state": { - "80": "Jemné", - "83": "Express 60", - "84": "Intenzivní", - "86": "Normální", - "90": "Samočištění", - "0e": "AI mytí", - "07": "Předoplach", - "8d": "Hrnce a pánve", - "8e": "Plast", - "8f": "Dětské potřeby" - } - }, - "dispense_type": { - "name": "Typ dávkování" - }, - "door_alert": { - "name": "Alarm dvířek", - "state": { - "1": "Alarm 1", - "2": "Alarm 2", - "3": "Alarm 3", - "4": "Alarm 4" - } - }, - "dryer_cycle_table_03": { - "name": "Cyklus", - "state": { - "01": "Normální", - "06": "Časové sušení", - "16": "Bavlna", - "17": "Super Speed", - "18": "Syntetika", - "19": "Jemné prádlo", - "20": "Žehlení", - "21": "Hygienické sušení", - "22": "Tiché sušení", - "23": "Rychlé sušení 35", - "24": "Studený vzduch", - "25": "Teplý vzduch", - "27": "Časové sušení", - "29": "AI sušení", - "1a": "Vlna", - "1b": "Ložní prádlo", - "1c": "Košile", - "1d": "Ručníky", - "1e": "Outdoor", - "1f": "Smíšená náplň", - "2b": "Sušení bubnu", - "4c": "Osvěžení vzduchem" - } - }, - "favorite_capacity": { - "name": "Oblíbené množství" - }, - "favorite_hotwater_temperature": { - "name": "Oblíbená teplota horké vody" - }, - "filter_alarm_time": { - "name": "Interval alarmu filtru", - "state": { - "180": "180 hodin", - "300": "300 hodin", - "500": "500 hodin", - "700": "700 hodin" - } - }, - "finish_sound": { - "name": "Zvuk dokončení", - "state": { - "off": "Vypnuto", - "on": "Zapnuto" - } - }, - "flex_zone_mode": { - "name": "Režim flexibilní zóny", - "state": { - "cv_ttype_rf9000a_freeze": "Mražení", - "cv_ttype_rf9000a_softfreeze": "Mírné mražení", - "cv_ttype_rf9000a_meat_fish": "Maso/Ryby", - "cv_ttype_rf9000a_fruit_veggies": "Ovoce a zelenina", - "cv_ttype_rf9000a_beverage": "Nápoje", - "cv_fdr_wine": "Víno", - "cv_fdr_deli": "Lahůdky", - "cv_fdr_beverage": "Nápoje", - "cv_fdr_meat": "Maso", - "cv_fdr_soft_freezer": "Mírný mrazák" - } - }, - "heated_dry": { - "name": "Chytré sušení", - "state": { - "off": "Vypnuto", - "low": "Nízké", - "high": "Vysoké", - "extra_high": "Extra vysoké" - } - }, - "hot_water_temperature": { - "name": "Teplota horké vody" - }, - "ice_type": { - "name": "{instance_name} typ", - "state": { - "off": "Vypnuto", - "whiskey_iceball_3": "3 kuličky/den", - "whiskey_iceball_6": "6 kuliček/den", - "whiskey_iceball_9": "9 kuliček/den" - } - }, - "led_brightness": { - "name": "Jas LED dvířek", - "state": { - "low": "Nízký", - "high": "Vysoký" - } - }, - "led_night_brightness": { - "name": "Noční jas LED dvířek", - "state": { - "low": "Nízký", - "high": "Vysoký" - } - }, - "cooking_mode": { - "name": "Režim vaření", - "state": { - "no_operation": "Bez provozu", - "micro_wave": "Mikrovlny", - "micro_wave_grill": "Mikrovlny + gril", - "micro_wave_convection": "Mikrovlny + konvekce", - "convection": "Konvekce", - "air_fryer": "Fritéza na vzduch", - "grill": "Gril", - "autocook": "Automatické vaření", - "autocook_custom": "Automatické vaření (vlastní)", - "deodorization": "Odstranění pachů", - "keep_warm": "Udržování teploty" - } - }, - "oven_mode": { - "name": "Režim vaření", - "state": { - "no_operation": "Bez provozu", - "bake": "Pečení", - "broil": "Gril", - "convection": "Konvekce", - "convection_bake": "Konvekční pečení", - "convection_broil": "Konvekční gril", - "frozen_pizza_plus": "Mražená pizza+", - "slow_cook": "Pomalé vaření", - "plate_warm": "Ohřev talířů", - "air_fry": "Fritéza na vzduch" - } - }, - "operating_mode": { - "name": "Provozní režim" - }, - "kimchi_zone_mode": { - "name": "{instance_name} režim uskladnění", - "state": { - "off": "Vypnuto", - "kimchi_storage_normal": "Kimči", - "kimchi_storage_cold": "Kimči, silné", - "kimchi_storage_warm": "Kimči, slabé", - "kimchi_storage_low_salt_normal": "Kimči s nízkým obsahem soli", - "kimchi_storage_low_salt_cold": "Kimči s nízkým obsahem soli, silné", - "kimchi_storage_low_salt_warm": "Kimči s nízkým obsahem soli, slabé", - "kimchi_storage_crunfch": "Křupavé kimči", - "kimchi_storage_buy": "Kupované kimči", - "storage_fridge_normal": "Chlazení", - "storage_fridge_cold": "Chlazení, silné", - "storage_fridge_warm": "Chlazení, slabé", - "storage_freezer_normal": "Mražení", - "storage_freezer_cold": "Mražení, silné", - "storage_freezer_warm": "Mražení, slabé", - "kimchi_ripe_low_temp": "Zrání kimči, nízká teplota", - "kimchi_ripe_normal_temp": "Zrání kimči, pokojová teplota", - "kimchi_ripe_kkakdugi": "Zrání kkakdugi", - "kimchi_ripe_dongchimi": "Zrání dongchimi", - "meat_ripe_normal": "Zrání masa", - "storage_meat": "Maso a ryby", - "storage_fridge_vegetables_fruit": "Ovoce a zelenina", - "storage_fresh_cereal": "Obiloviny", - "storage_fridge_drink": "Nápoje", - "storage_fresh_wine": "Víno", - "storage_fresh_potato_banana": "Brambory a banány" - } - }, - "pantry_zone_mode": { - "name": "Režim spíže", - "state": { - "fdr_wine": "Víno", - "fdr_deli": "Lahůdky", - "fdr_drinks": "Nápoje" - } - }, - "range_burner_power_level": { - "name": "Výkon hořáku {number}", - "state": { - "0": "Vypnuto", - "boost": "Boost", - "simmer": "Mírný var" - } - }, - "range_hood_lamp_brightness": { - "name": "Jas osvětlení", - "state": { - "1": "Úroveň 1", - "2": "Úroveň 2" - } - }, - "rinse_cycles": { - "name": "Počet máchání" - }, - "softener_concentration": { - "name": "Koncentrace aviváže", - "state": { - "01": "1x", - "02": "2x", - "03": "3x" - } - }, - "softener_quantity": { - "name": "Množství aviváže", - "state": { - "00": "Žádné", - "01": "Nízké", - "02": "Střední", - "03": "Vysoké" - } - }, - "sound_mode": { - "name": "Zvukový režim", - "state": { - "voice": "Hlas", - "tone": "Tón", - "mute": "Ztlumeno" - } - }, - "air_purifier_sound_mode": { - "name": "Zvukový režim", - "state": { - "mute": "Ztlumeno", - "buzzer": "Bzučák" - } - }, - "water_purifier_sound_mode": { - "name": "Zvukový režim", - "state": { - "voice": "Hlas", - "fixedtone": "Pevný tón", - "mute": "Ztlumeno" - } - }, - "spin_speed": { - "name": "Rychlost odstřeďování", - "state": { - "rinse_hold": "Zadržení máchání", - "no_spin": "Bez odstřeďování", - "low": "Nízká", - "medium": "Střední", - "high": "Vysoká" - } - }, - "wash_temperature": { - "name": "Teplota praní", - "state": { - "none": "Žádná", - "cold": "Studená", - "cool": "Chladná", - "warm": "Teplá", - "hot": "Horká", - "extra_hot": "Extra horká" - } - }, - "washer_cycle_table_02": { - "name": "Cyklus", - "state": { - "01": "Normální", - "04": "Rychlé praní", - "17": "Stažený program", - "1b": "Bavlna", - "1c": "Eco 40-60", - "1d": "Super rychlé", - "1e": "15min rychlé praní", - "1f": "Intenzivní studená", - "20": "Hygienická pára", - "21": "Barevné prádlo", - "22": "Vlna", - "23": "Outdoor", - "24": "Ručníky", - "25": "Syntetika", - "26": "Jemné prádlo", - "27": "Máchání+odstřeďování", - "28": "Vypuštění/odstřeďování", - "29": "Čištění bubnu+", - "2a": "Džíny", - "2b": "AI praní", - "2d": "Tiché praní", - "2e": "Dětské potřeby", - "2f": "Sportovní oblečení", - "30": "Zataženo", - "32": "Košile", - "33": "Ložní prádlo", - "34": "Smíšené", - "36": "Praní+sušení", - "37": "Sušení vzduchem", - "38": "Sušení bavlny", - "39": "Sušení syntetiky", - "3a": "Čištění bubnu", - "52": "Eco studená", - "53": "Intenzivní", - "54": "Ručníky", - "55": "Sportovní oblečení", - "57": "Jemné", - "5e": "Máchání+odstřeďování", - "60": "Samočištění+", - "65": "Barevné prádlo", - "66": "Džíny", - "7c": "Bílé prádlo", - "7d": "Ložní prádlo/nepromokavé", - "7e": "Samočištění", - "7f": "Vlna/jemné", - "86": "Hloubkové praní", - "87": "Stažený program", - "8f": "Intenzivní studená", - "96": "Méně mikrovláken" - } - }, - "washer_dry_level": { - "name": "Úroveň sušení", - "state": { - "30": "30 min", - "60": "1 h", - "90": "1 h 30 min", - "120": "2 h", - "180": "3 h", - "240": "4 h", - "none": "Vypnuto", - "cupboard": "Do skříně" - } - }, - "zone_mode": { - "name": "Režim zóny" - }, - "watertank_light_color": { - "name": "Barva osvětlení nádržky na vodu", - "state": { - "warmwhite": "Teplá bílá", - "naturalwhite": "Přirozená bílá", - "coolwhite": "Studená bílá", - "green": "Zelená", - "purple": "Fialová", - "blue": "Modrá", - "orange": "Oranžová", - "pink": "Růžová" - } - }, - "watertank_light_brightness": { - "name": "Jas osvětlení nádržky na vodu" - } - }, - "sensor": { - "auto_clean_progress": { - "name": "Průběh automatického čištění" - }, - "after_run_progress": { - "name": "Průběh dosoušení" - }, - "air_filter_usage": { - "name": "Využití filtru" - }, - "air_filter_usage_hours": { - "name": "Hodiny využití filtru" - }, - "air_quality_standard": { - "name": "Norma kvality vzduchu" - }, - "air_sensing_state": { - "name": "Stav měření kvality vzduchu" - }, - "alarm_code": { - "name": "Kód alarmu" - }, - "auto_ventilation_action": { - "name": "Akce automatického větrání" - }, - "automatic_ventilation_state": { - "name": "Stav automatického větrání" - }, - "battery": { - "name": "Baterie" - }, - "burner_state": { - "name": "Stav hořáku {number}" - }, - "clean_level": { - "name": "Úroveň čištění" - }, - "coffee_brew_status": { - "name": "Stav přípravy kávy" - }, - "co2": { - "name": "CO2" - }, - "connection_mode": { - "name": "Režim připojení", - "state": { - "observe": "Push (observe)", - "poll": "Dotazování" - } - }, - "cooktop_running_state": { - "name": "Provozní stav varné desky" - }, - "cooktop_state": { - "name": "Stav varné desky" - }, - "current_limit_level": { - "name": "Úroveň omezení proudu" - }, - "filter_time": { - "name": "Čas filtru" - }, - "hepa_filter_usage": { - "name": "Využití HEPA filtru" - }, - "outdoor_temperature": { - "name": "Venkovní teplota" - }, - "odor_controller_progress": { - "name": "Průběh odstraňování pachů" - }, - "panel_status": { - "name": "Stav panelu" - }, - "sound_output": { - "name": "Zvukový výstup" - }, - "dustbag_usage": { - "name": "Využití sáčku na prach" - }, - "cleanstation_status": { - "name": "Stav čisticí stanice" - }, - "stick_status": { - "name": "Stav tyče" - }, - "uvc_operation_time": { - "name": "Doba provozu UV-C" - }, - "uvc_total_operation_time": { - "name": "Celková doba provozu UV-C" - }, - "uvc_finished_time": { - "name": "Čas dokončení UV-C" - }, - "uvc_emitted_time": { - "name": "Doba vyzařování UV-C" - }, - "overload_protection_mode": { - "name": "Režim ochrany proti přetížení", - "state": { - "alarm": "Alarm", - "powersaving": "Úspora energie" - } - }, - "absence_power_saving_mode": { - "name": "Režim úspory energie při nepřítomnosti", - "state": { - "eco": "Eco", - "normal": "Normální", - "comfort": "Komfort" - } - }, - "motion_detect_wind_mode": { - "name": "Režim vzduchu při detekci pohybu", - "state": { - "direct": "Přímý", - "indirect": "Nepřímý" - } - }, - "current_temp_c": { - "name": "Teplota" - }, - "current_temperature_c": { - "name": "Teplota" - }, - "diagnosis": { - "name": "Diagnostika" - }, - "diagnosis_status": { - "name": "Stav diagnostiky" - }, - "drum_clean_cycles_remaining": { - "name": "Čištění bubnu za" - }, - "drum_clean_last_cleaned": { - "name": "Poslední čištění bubnu" - }, - "dry_level": { - "name": "Úroveň sušení" - }, - "dry_time": { - "name": "Doba sušení" - }, - "dryer_type": { - "name": "Typ sušičky" - }, - "dust": { - "name": "Prach" - }, - "energy_kwh": { - "name": "Energie" - }, - "energy_last_month_kwh": { - "name": "Energie (minulý měsíc)" - }, - "energy_saved_kwh": { - "name": "Ušetřená energie" - }, - "energy_this_month_kwh": { - "name": "Energie (tento měsíc)" - }, - "fan_direction": { - "name": "Směr proudění vzduchu" - }, - "fan_speed_level": { - "name": "Úroveň otáček ventilátoru" - }, - "filter_clean_remain_time": { - "name": "Zbývající čas do čištění filtru" - }, - "filter_progress": { - "name": "Průběh filtru" - }, - "filter_usage": { - "name": "Využití filtru" - }, - "fine_dust": { - "name": "Jemný prach" - }, - "finish_time": { - "name": "Odhadované dokončení" - }, - "completion_minutes": { - "name": "Čas dokončení" - }, - "freezer_temperature": { - "name": "Teplota mrazáku" - }, - "fridge_temperature": { - "name": "Teplota chladničky" - }, - "kimchi_ripening_status": { - "name": "{instance_name} stav zrání" - }, - "kimchi_ripening_remaining": { - "name": "{instance_name} zbývající čas zrání" - }, - "kimchi_rack_count": { - "name": "{instance_name} počet přihrádek" - }, - "hood_filter_capacity": { - "name": "Kapacita filtru" - }, - "humidity": { - "name": "Vlhkost" - }, - "hood_filter_usage": { - "name": "Využití filtru" - }, - "instance_temperature": { - "name": "{instance_name} teplota" - }, - "job_beginning_status": { - "name": "Stav zahájení úlohy" - }, - "last_air_sensing_level": { - "name": "Poslední úroveň měření vzduchu" - }, - "last_air_sensing_time": { - "name": "Poslední čas měření vzduchu" - }, - "main_timer_current": { - "name": "Aktuální hodnota časovače" - }, - "main_timer_state": { - "name": "Stav časovače" - }, - "odor": { - "name": "Pach" - }, - "operating_mode": { - "name": "Provozní režim" - }, - "operation_origin": { - "name": "Poslední zdroj ovládání" - }, - "operation_time_minutes": { - "name": "Doba provozu" - }, - "oven_state": { - "name": "Stav trouby" - }, - "cavity_state": { - "name": "Stav trouby" - }, - "power_level": { - "name": "Úroveň výkonu" - }, - "paired_hood_fan_speed": { - "name": "Otáčky ventilátoru spárovaného odsavače" - }, - "paired_hood_firmware": { - "name": "Firmware spárovaného odsavače" - }, - "paired_hood_model": { - "name": "Model spárovaného odsavače" - }, - "power_energy_kwh": { - "name": "Spotřeba energie" - }, - "power_watts": { - "name": "Výkon" - }, - "probe_battery": { - "name": "Baterie sondy" - }, - "probe_target_temperature": { - "name": "Požadovaná teplota sondy" - }, - "probe_temperature": { - "name": "Teplota sondy" - }, - "progress": { - "name": "Průběh" - }, - "progress_percentage": { - "name": "Průběh v procentech" - }, - "selfcheck_error": { - "name": "Chyba vlastní kontroly" - }, - "selfcheck_result": { - "name": "Výsledek vlastní kontroly" - }, - "selfcheck_status": { - "name": "Stav vlastní kontroly" - }, - "sterilize_last_time": { - "name": "Poslední sterilizace" - }, - "sterilize_period": { - "name": "Interval sterilizace" - }, - "sterilize_plan_time": { - "name": "Plánovaný čas sterilizace" - }, - "sterilize_run_time": { - "name": "Doba běhu sterilizace" - }, - "super_fine_dust": { - "name": "Velmi jemný prach" - }, - "warming_center_state": { - "name": "Stav ohřevného centra" - }, - "water_liters": { - "name": "Spotřeba vody" - }, - "waterpurifier_status": { - "name": "Stav" - }, - "cup_state": { - "name": "Stav šálku" - }, - "last_pour_type": { - "name": "Poslední typ nalévání" - }, - "last_pour_capacity": { - "name": "Poslední nalité množství" - }, - "machine_state": { - "name": "Stav stroje", - "state": { - "idle": "Nečinný", - "active": "Aktivní", - "pause": "Pozastaveno" - } - }, - "filter_status": { - "name": "Stav filtru", - "state": { - "normal": "Normální", - "wash": "Umýt", - "replace": "Vyměnit" - } - }, - "ice_making_status": { - "name": "{instance_name} stav výroby", - "state": { - "icestatus_stop": "Nečinný", - "icestatus_run": "Vyrábí led" - } - }, - "zone_temperature": { - "name": "Teplota zóny" - }, - "watertank_full_alarm_status": { - "name": "Stav alarmu plné nádržky na vodu" - } - }, - "switch": { - "ai_energy_level": { - "name": "Režim AI Energy" - }, - "air_monitoring": { - "name": "Sledování kvality vzduchu" - }, - "air_purify": { - "name": "Čištění vzduchu" - }, - "auto_clean": { - "name": "Automatické čištění" - }, - "absence_power_saving_active": { - "name": "Úspora energie při nepřítomnosti aktivní" - }, - "motion_detect_wind_active": { - "name": "Vyhýbání se proudu vzduchu při pohybu aktivní" - }, - "beep": { - "name": "Pípání" - }, - "display": { - "name": "Displej" - }, - "pet_filter_activation": { - "name": "Aktivace filtru pro domácí mazlíčky" - }, - "auto_empty": { - "name": "Automatické vyprázdnění" - }, - "dustbin_auto_close": { - "name": "Automatické zavření nádoby na prach" - }, - "spi": { - "name": "Ionizace vzduchu" - }, - "uvc_intensive_mode": { - "name": "Intenzivní režim UV-C" - }, - "auto_door_opener": { - "name": "Automatické otevírání dvířek" - }, - "auto_release_dry": { - "name": "Automatické uvolnění při sušení" - }, - "autofill": { - "name": "Automatické plnění konvice" - }, - "bubble_soak": { - "name": "Bublinkové namáčení" - }, - "buzz_lock": { - "name": "Zámek bzučáku" - }, - "cabinet_light_dim": { - "name": "Postupné rozsvícení" - }, - "cabinet_light_switch": { - "name": "Osvětlení skříně" - }, - "child_lock": { - "name": "Dětský zámek" - }, - "coldwater_lock": { - "name": "Zámek studené vody" - }, - "cooktop_child_lock": { - "name": "Dětský zámek" - }, - "defrost_delay": { - "name": "Odložené odmrazování" - }, - "display_light": { - "name": "Podsvícení displeje" - }, - "dnd": { - "name": "Nerušit" - }, - "fast_preheat": { - "name": "Rychlé předehřátí" - }, - "favorite_capacity_enabled": { - "name": "Oblíbené množství" - }, - "favorite_coffee_enabled": { - "name": "Oblíbená káva" - }, - "filter_remind": { - "name": "Připomenutí filtru" - }, - "fridge_sound": { - "name": "Zvuk" - }, - "hotwater_lock": { - "name": "Zámek horké vody" - }, - "ice_maker_enabled": { - "name": "Výrobník ledu" - }, - "ice_night_mode": { - "name": "Noční tichý režim výroby ledu" - }, - "instance_enabled": { - "name": "{instance_name} zapnuto" - }, - "intensive": { - "name": "Intenzivní" - }, - "lamp": { - "name": "Lampa" - }, - "led_night_light": { - "name": "Noční LED osvětlení dvířek" - }, - "mute_once": { - "name": "Jednou ztlumit" - }, - "natural_steam": { - "name": "Přírodní pára" - }, - "energy_saving": { - "name": "Úspora energie" - }, - "cooktop_on_alert": { - "name": "Upozornění na zapnutou varnou desku" - }, - "power_switch": { - "name": "Napájení" - }, - "pre_wash": { - "name": "Předpírka" - }, - "rapid_freezing": { - "name": "Rychlé zmrazování" - }, - "rapid_fridge": { - "name": "Rychlé chlazení" - }, - "remind_beep": { - "name": "Připomínací signál konce" - }, - "sabbath_mode": { - "name": "Sabatní režim" - }, - "sanitize": { - "name": "Dezinfekce" - }, - "sound": { - "name": "Zvuk" - }, - "storm_wash": { - "name": "Storm Wash+" - }, - "welcome_lighting": { - "name": "Uvítací osvětlení" - }, - "wrinkle_prevent": { - "name": "Ochrana proti pomačkání" - }, - "zone_power": { - "name": "Napájení zóny" - }, - "away_mode": { - "name": "Režim nepřítomnosti" - }, - "watertank_light": { - "name": "Osvětlení nádržky na vodu" - } - }, - "time": { - "dnd_end": { - "name": "Konec režimu Nerušit" - }, - "dnd_start": { - "name": "Začátek režimu Nerušit" - }, - "led_night_end": { - "name": "Konec nočního LED osvětlení dvířek" - }, - "led_night_start": { - "name": "Začátek nočního LED osvětlení dvířek" - }, - "night_end": { - "name": "Konec nočního osvětlení" - }, - "night_start": { - "name": "Začátek nočního osvětlení" - } - }, - "water_heater": { - "dhw": { - "name": "Teplá voda" - } - } - }, - "config": { - "step": { - "user": { - "title": "Přidat spotřebič Samsung", - "description": "Zadejte IP adresu vašeho spotřebiče Samsung a přihlašovací údaje CA AC14K_M použité k vydávání certifikátů zařízení.", - "data": { - "host": "IP adresa", - "ca_cert_pem": "Certifikát CA (PEM)", - "ca_key_pem": "Privátní klíč CA (PEM)" - }, - "data_description": { - "host": "Lokální IP adresa vašeho spotřebiče Samsung (např. 192.168.1.50).", - "ca_cert_pem": "Certifikát CA AC14K_M ve formátu PEM. Vložte celý obsah řetězce fullchain PEM včetně všech bloků BEGIN/END CERTIFICATE.", - "ca_key_pem": "Privátní klíč AC14K_M ve formátu PEM. Vložte celý obsah včetně hlavičky a patičky BEGIN/END PRIVATE KEY." - } - }, - "user_reuse": { - "title": "Přidat spotřebič Samsung", - "description": "Zadejte IP adresu vašeho spotřebiče Samsung. Přihlašovací údaje CA z existujícího spotřebiče LocalThings budou znovu použity.", - "data": { - "host": "IP adresa" - }, - "data_description": { - "host": "Lokální IP adresa vašeho spotřebiče Samsung (např. 192.168.1.50)." - } - }, - "confirm_unknown_type": { - "title": "Typ spotřebiče nebyl rozpoznán", - "description": "Typ tohoto spotřebiče se nepodařilo rozpoznat. Přesto bude přidán, ale pouze se společnými funkcemi (napájení, alarmy atd., pokud jsou k dispozici), nikoli s plnou sadou funkcí pro danou rodinu spotřebičů. Podporu můžete později pomoci rozšířit stažením diagnostiky pro toto zařízení (Nastavení > Zařízení a služby > toto zařízení > nabídka > Stáhnout diagnostiku) a jejím vložením do nového issue. Odesláním zařízení přidáte i tak." - } - }, - "error": { - "cannot_connect": "Nelze se připojit k zařízení. Ověřte, že je IP adresa dostupná a že jsou přihlašovací údaje CA správné.", - "invalid_ca": "Certifikát CA nebo privátní klíč se nepodařilo načíst. Ověřte, že obsah PEM je správný a že klíč odpovídá certifikátu.", - "unknown": "Neočekávaná chyba. Podrobnosti najdete v protokolech Home Assistant." - }, - "abort": { - "already_configured": "Zařízení je již nakonfigurováno" - } - }, - "options": { - "step": { - "init": { - "title": "Možnosti LocalThings", - "menu_options": { - "settings": "Nastavení zápisu pro dálkové ovládání", - "debug_write": "Ladění: zápis do prostředku" - } - }, - "settings": { - "title": "Nastavení zápisu pro dálkové ovládání", - "description": "Některá zařízení přijímají určité zápisy (např. výchozí dávkování pracího prostředku/aviváže v pračce) i když hlásí vypnuté dálkové ovládání. LocalThings ve výchozím nastavení blokuje každý zápis srozumitelnou chybou, kdykoli zařízení hlásí vypnuté dálkové ovládání, místo aby jej zařízení tiše odmítlo. Povolte toto pouze tehdy, pokud jste ověřili, že zápisy na tomto zařízení skutečně fungují i s vypnutým dálkovým ovládáním – jinak tuto srozumitelnou chybu vyměníte za tiché selhání.", - "data": { - "bypass_remote_control_lock": "Povolit zápis i když je dálkové ovládání hlášeno jako vypnuté", - "finish_time_hysteresis_minutes": "Odhadovaný konec -- minimální změna (minuty)" - } - }, - "debug_write": { - "title": "Ladění: zápis do prostředku", - "description": "Nástroj pro pokročilé uživatele k odhalení chování zápisu specifického pro dané zařízení. Vyberte prostředek (href), do kterého chcete zapisovat, nebo zadejte vlastní, který není uveden v seznamu. Toto obchází blokování při vypnutém dálkovém ovládání a odesílá přesně ta pole, která zadáte -- může to špatně nakonfigurovat váš spotřebič, používejte tedy záměrně.", - "data": { - "href": "Href prostředku" - } - }, - "debug_edit": { - "title": "Ladicí zápis: {href}", - "description": "Aktuální hodnota `{href}`:\n```\n{current_value}\n```\nZadejte POUZE pole, která chcete změnit. To, co zadáte, je odesláno zařízení tak, jak je (částečná aktualizace); NENÍ to sloučeno s poli zobrazenými výše, takže nevkládejte zpět celou hodnotu. Dávejte pozor na typy: číselný řetězec jako \"1\" musí zůstat v uvozovkách -- holé 1 je odesláno jako celé číslo, což některá zařízení odmítají.", - "data": { - "payload": "Zapisovaný obsah" - } - }, - "debug_result": { - "title": "Výsledek ladicího zápisu", - "description": "Zařízení vrátilo kód CoAP {code}. Třída 2.xx znamená, že zápis byl přijat; 4.xx/5.xx znamená, že byl odmítnut. Prostředek nyní obsahuje:\n```\n{new_value}\n```\nPokud se hodnota nezměnila, zařízení zápis pravděpodobně zahodilo. Vyberte, co dělat dál:", - "menu_options": { - "debug_write": "Zapsat do dalšího prostředku", - "finish": "Dokončit" - } - } - }, - "error": { - "empty_payload": "Zadejte alespoň jedno pole k zápisu.", - "write_failed": "Zápis se nezdařil. Podrobnosti najdete v protokolech Home Assistant." - }, - "abort": { - "not_loaded": "Toto zařízení ještě není připojeno. Zkuste to znovu, až se načte." - } - }, - "issues": { - "device_gap": { - "title": "Neúplné pokrytí funkcí pro {device_name}", - "description": "Toto zařízení nemá úplné pokrytí funkcí. Buď nebyl rozpoznán jeho typ spotřebiče, nebo některé jím poskytované prostředky ještě nejsou namodelovány. Bude i nadále fungovat se vším, co je již podporováno. Podporu můžete pomoci rozšířit tak, že přejdete do Nastavení > Zařízení a služby > {device_name} > nabídka (vpravo nahoře) > Stáhnout diagnostiku a poté ji vložíte do odkazované šablony issue." - } - }, - "exceptions": { - "remote_control_disabled": { - "message": "Dálkové ovládání je na tomto zařízení vypnuté. V návodu ke spotřebiči zjistěte, jak zapnout dálkové ovládání, než jej bude moci Home Assistant ovládat." - }, - "debug_payload_empty": { - "message": "Obsah ladicího zápisu musí být neprázdný objekt." - }, - "resource_href_required": { - "message": "Je vyžadován href prostředku." - }, - "bubble_soak_unavailable_for_cycle": { - "message": "Bublinkové namáčení není u vybraného cyklu k dispozici." - }, - "pre_wash_unavailable_for_cycle": { - "message": "Předpírka není u vybraného cyklu k dispozici." - }, - "intensive_unavailable_for_cycle": { - "message": "Intenzivní není u vybraného cyklu k dispozici." - } - } -} +{ + "entity": { + "binary_sensor": { + "auto_clean_running": { + "name": "Automatické čištění probíhá" + }, + "after_run_active": { + "name": "Dosoušení aktivní" + }, + "any_burner_active": { + "name": "Hořák aktivní" + }, + "automatic_operation": { + "name": "Automatický provoz" + }, + "battery_charging": { + "name": "Nabíjení" + }, + "burner_hot_surface": { + "name": "Hořák {number} horký povrch" + }, + "burner_pan_detected": { + "name": "Hořák {number} rozpoznána nádoba" + }, + "cloud_connected": { + "name": "Připojeno ke cloudu" + }, + "dustbag_full": { + "name": "Sáček na prach plný" + }, + "cooktop_power": { + "name": "Napájení varné desky" + }, + "cooktop_safety_shutoff_enabled": { + "name": "Automatické vypnutí při horkém povrchu" + }, + "current_limit_enabled": { + "name": "Omezení proudu zapnuto" + }, + "overload_protection_active": { + "name": "Ochrana proti přetížení aktivní" + }, + "odor_controller_active": { + "name": "Odstraňování pachů aktivní" + }, + "cycle_active": { + "name": "Běží" + }, + "defrost_active": { + "name": "Odmrazování aktivní" + }, + "detergent_low": { + "name": "Málo pracího prostředku" + }, + "device_active": { + "name": "Zařízení aktivní" + }, + "door_open": { + "name": "Dvířka" + }, + "filter_door_status": { + "name": "Dvířka filtru" + }, + "firmware_update": { + "name": "Dostupná aktualizace firmwaru" + }, + "instance_open": { + "name": "{instance_name} otevřeno" + }, + "paired_hood_connected": { + "name": "Spárovaný odsavač připojen" + }, + "paired_hood_light": { + "name": "Světlo spárovaného odsavače" + }, + "paired_hood_power": { + "name": "Napájení spárovaného odsavače" + }, + "periodic_air_sensing": { + "name": "Pravidelné měření kvality vzduchu" + }, + "pouring": { + "name": "Nalévání" + }, + "alarm_in_mute": { + "name": "Alarm ztlumen" + }, + "child_lock": { + "name": "Dětská pojistka" + }, + "power_state": { + "name": "Stav napájení" + }, + "probe_connected": { + "name": "Sonda připojena" + }, + "power_switch": { + "name": "Napájení" + }, + "rapid_freezing": { + "name": "Rychlé zmrazování" + }, + "rapid_fridge": { + "name": "Rychlé chlazení" + }, + "remote_control": { + "name": "Chytré ovládání" + }, + "softener_low": { + "name": "Málo aviváže" + } + }, + "button": { + "after_run_cancel": { + "name": "Zrušit dosoušení" + }, + "diagnosis_start": { + "name": "Spustit diagnostiku" + }, + "pause": { + "name": "Pozastavit" + }, + "selfcheck_start": { + "name": "Spustit vlastní kontrolu" + }, + "start": { + "name": "Spustit" + }, + "stop": { + "name": "Zastavit" + } + }, + "climate": { + "airconditioner": { + "state_attributes": { + "fan_mode": { + "state": { + "turbo": "Turbo", + "max": "Max" + } + }, + "preset_mode": { + "state": { + "ai_comfort": "AI komfort", + "quiet": "Tichý", + "smart": "Chytrý", + "speed": "Rychlý", + "nano": "WindFree", + "nanosleep": "WindFree spánek", + "longwind": "Dlouhý vánek", + "motionindirect": "Nepřímý vzduch při pohybu", + "motiondirect": "Přímý vzduch při pohybu", + "drycomfort": "Komfortní sušení", + "2step": "2stupňový" + } + } + } + } + }, + "fan": { + "air_purifier_fan": { + "state_attributes": { + "preset_mode": { + "state": { + "smart": "Chytrý", + "max": "Max", + "mid": "Střední", + "windfree": "WindFree", + "sleep": "Spánek" + } + } + } + } + }, + "number": { + "cook_time": { + "name": "Doba vaření" + }, + "delay_start_hours": { + "name": "Odložený start" + }, + "dispense_capacity": { + "name": "Dávkované množství" + }, + "good_sleep": { + "name": "Klidný spánek" + }, + "instance_setpoint": { + "name": "{instance_name} požadovaná teplota" + }, + "oven_setpoint": { + "name": "Požadovaná teplota" + }, + "setpoint": { + "name": "Požadovaná teplota" + }, + "sound_volume": { + "name": "Hlasitost zvuku" + }, + "target_humidity": { + "name": "Požadovaná vlhkost" + }, + "tropical_night_mode": { + "name": "Tropický noční režim" + }, + "zone_target_temperature": { + "name": "Požadovaná teplota zóny" + } + }, + "select": { + "ai_energy_level": { + "name": "Úroveň režimu AI Energy" + }, + "air_filter_threshold": { + "name": "Práh upozornění na filtr" + }, + "beverage_zone_mode": { + "name": "Režim zóny nápojů", + "state": { + "sp_ttype_beer_drinks": "Nápoje", + "sp_ttype_wine_dessert": "Víno a dezert" + } + }, + "brightness_level": { + "name": "Noční jas", + "state": { + "33": "Nízký", + "66": "Střední", + "100": "Vysoký" + } + }, + "cooler_temperature_setpoint": { + "name": "Teplota chladicí zóny" + }, + "day_brightness": { + "name": "Jas skříně", + "state": { + "33": "Nízký", + "66": "Střední", + "100": "Vysoký" + } + }, + "buzzer_sound": { + "name": "Zvuk bzučáku", + "state": { + "off": "Vypnuto", + "on": "Zapnuto" + } + }, + "discharging_time": { + "name": "Doba vypouštění", + "state": { + "1": "1 minuta", + "3": "3 minuty" + } + }, + "cycle": { + "name": "Cyklus" + }, + "detergent_quantity": { + "name": "Množství pracího prostředku", + "state": { + "00": "Žádné", + "01": "Nízké", + "02": "Střední", + "03": "Vysoké" + } + }, + "detergent_water_hardness": { + "name": "Tvrdost vody pro prací prostředek", + "state": { + "01": "Měkká", + "02": "Střední", + "03": "Tvrdá" + } + }, + "dishwasher_cycle": { + "name": "Cyklus", + "state": { + "80": "Jemné", + "83": "Express 60", + "84": "Intenzivní", + "86": "Normální", + "90": "Samočištění", + "0e": "AI mytí", + "07": "Předoplach", + "8d": "Hrnce a pánve", + "8e": "Plast", + "8f": "Dětské potřeby" + } + }, + "dispense_type": { + "name": "Typ dávkování" + }, + "door_alert": { + "name": "Alarm dvířek", + "state": { + "1": "Alarm 1", + "2": "Alarm 2", + "3": "Alarm 3", + "4": "Alarm 4" + } + }, + "dryer_cycle_table_03": { + "name": "Cyklus", + "state": { + "01": "Normální", + "06": "Časové sušení", + "16": "Bavlna", + "17": "Super Speed", + "18": "Syntetika", + "19": "Jemné prádlo", + "20": "Žehlení", + "21": "Hygienické sušení", + "22": "Tiché sušení", + "23": "Rychlé sušení 35", + "24": "Studený vzduch", + "25": "Teplý vzduch", + "27": "Časové sušení", + "29": "AI sušení", + "1a": "Vlna", + "1b": "Ložní prádlo", + "1c": "Košile", + "1d": "Ručníky", + "1e": "Outdoor", + "1f": "Smíšená náplň", + "2b": "Sušení bubnu", + "4c": "Osvěžení vzduchem" + } + }, + "favorite_capacity": { + "name": "Oblíbené množství" + }, + "favorite_hotwater_temperature": { + "name": "Oblíbená teplota horké vody" + }, + "filter_alarm_time": { + "name": "Interval alarmu filtru", + "state": { + "180": "180 hodin", + "300": "300 hodin", + "500": "500 hodin", + "700": "700 hodin" + } + }, + "finish_sound": { + "name": "Zvuk dokončení", + "state": { + "off": "Vypnuto", + "on": "Zapnuto" + } + }, + "flex_zone_mode": { + "name": "Režim flexibilní zóny", + "state": { + "cv_ttype_rf9000a_freeze": "Mražení", + "cv_ttype_rf9000a_softfreeze": "Mírné mražení", + "cv_ttype_rf9000a_meat_fish": "Maso/Ryby", + "cv_ttype_rf9000a_fruit_veggies": "Ovoce a zelenina", + "cv_ttype_rf9000a_beverage": "Nápoje", + "cv_fdr_wine": "Víno", + "cv_fdr_deli": "Lahůdky", + "cv_fdr_beverage": "Nápoje", + "cv_fdr_meat": "Maso", + "cv_fdr_soft_freezer": "Mírný mrazák" + } + }, + "heated_dry": { + "name": "Chytré sušení", + "state": { + "off": "Vypnuto", + "low": "Nízké", + "high": "Vysoké", + "extra_high": "Extra vysoké" + } + }, + "hot_water_temperature": { + "name": "Teplota horké vody" + }, + "ice_type": { + "name": "{instance_name} typ", + "state": { + "off": "Vypnuto", + "whiskey_iceball_3": "3 kuličky/den", + "whiskey_iceball_6": "6 kuliček/den", + "whiskey_iceball_9": "9 kuliček/den" + } + }, + "led_brightness": { + "name": "Jas LED dvířek", + "state": { + "low": "Nízký", + "high": "Vysoký" + } + }, + "led_night_brightness": { + "name": "Noční jas LED dvířek", + "state": { + "low": "Nízký", + "high": "Vysoký" + } + }, + "cooking_mode": { + "name": "Režim vaření", + "state": { + "no_operation": "Bez provozu", + "micro_wave": "Mikrovlny", + "micro_wave_grill": "Mikrovlny + gril", + "micro_wave_convection": "Mikrovlny + konvekce", + "convection": "Konvekce", + "air_fryer": "Fritéza na vzduch", + "grill": "Gril", + "autocook": "Automatické vaření", + "autocook_custom": "Automatické vaření (vlastní)", + "deodorization": "Odstranění pachů", + "keep_warm": "Udržování teploty" + } + }, + "oven_mode": { + "name": "Režim vaření", + "state": { + "no_operation": "Bez provozu", + "bake": "Pečení", + "broil": "Gril", + "convection": "Konvekce", + "convection_bake": "Konvekční pečení", + "convection_broil": "Konvekční gril", + "frozen_pizza_plus": "Mražená pizza+", + "slow_cook": "Pomalé vaření", + "plate_warm": "Ohřev talířů", + "air_fry": "Fritéza na vzduch" + } + }, + "operating_mode": { + "name": "Provozní režim" + }, + "kimchi_zone_mode": { + "name": "{instance_name} režim uskladnění", + "state": { + "off": "Vypnuto", + "kimchi_storage_normal": "Kimči", + "kimchi_storage_cold": "Kimči, silné", + "kimchi_storage_warm": "Kimči, slabé", + "kimchi_storage_low_salt_normal": "Kimči s nízkým obsahem soli", + "kimchi_storage_low_salt_cold": "Kimči s nízkým obsahem soli, silné", + "kimchi_storage_low_salt_warm": "Kimči s nízkým obsahem soli, slabé", + "kimchi_storage_crunfch": "Křupavé kimči", + "kimchi_storage_buy": "Kupované kimči", + "storage_fridge_normal": "Chlazení", + "storage_fridge_cold": "Chlazení, silné", + "storage_fridge_warm": "Chlazení, slabé", + "storage_freezer_normal": "Mražení", + "storage_freezer_cold": "Mražení, silné", + "storage_freezer_warm": "Mražení, slabé", + "kimchi_ripe_low_temp": "Zrání kimči, nízká teplota", + "kimchi_ripe_normal_temp": "Zrání kimči, pokojová teplota", + "kimchi_ripe_kkakdugi": "Zrání kkakdugi", + "kimchi_ripe_dongchimi": "Zrání dongchimi", + "meat_ripe_normal": "Zrání masa", + "storage_meat": "Maso a ryby", + "storage_fridge_vegetables_fruit": "Ovoce a zelenina", + "storage_fresh_cereal": "Obiloviny", + "storage_fridge_drink": "Nápoje", + "storage_fresh_wine": "Víno", + "storage_fresh_potato_banana": "Brambory a banány" + } + }, + "pantry_zone_mode": { + "name": "Režim spíže", + "state": { + "fdr_wine": "Víno", + "fdr_deli": "Lahůdky", + "fdr_drinks": "Nápoje" + } + }, + "range_burner_power_level": { + "name": "Výkon hořáku {number}", + "state": { + "0": "Vypnuto", + "boost": "Boost", + "simmer": "Mírný var" + } + }, + "range_hood_lamp_brightness": { + "name": "Jas osvětlení", + "state": { + "1": "Úroveň 1", + "2": "Úroveň 2" + } + }, + "rinse_cycles": { + "name": "Počet máchání" + }, + "softener_concentration": { + "name": "Koncentrace aviváže", + "state": { + "01": "1x", + "02": "2x", + "03": "3x" + } + }, + "softener_quantity": { + "name": "Množství aviváže", + "state": { + "00": "Žádné", + "01": "Nízké", + "02": "Střední", + "03": "Vysoké" + } + }, + "sound_mode": { + "name": "Zvukový režim", + "state": { + "voice": "Hlas", + "tone": "Tón", + "mute": "Ztlumeno" + } + }, + "air_purifier_sound_mode": { + "name": "Zvukový režim", + "state": { + "mute": "Ztlumeno", + "buzzer": "Bzučák" + } + }, + "water_purifier_sound_mode": { + "name": "Zvukový režim", + "state": { + "voice": "Hlas", + "fixedtone": "Pevný tón", + "mute": "Ztlumeno" + } + }, + "spin_speed": { + "name": "Rychlost odstřeďování", + "state": { + "rinse_hold": "Zadržení máchání", + "no_spin": "Bez odstřeďování", + "low": "Nízká", + "medium": "Střední", + "high": "Vysoká" + } + }, + "wash_temperature": { + "name": "Teplota praní", + "state": { + "none": "Žádná", + "cold": "Studená", + "cool": "Chladná", + "warm": "Teplá", + "hot": "Horká", + "extra_hot": "Extra horká" + } + }, + "washer_cycle_table_02": { + "name": "Cyklus", + "state": { + "01": "Normální", + "04": "Rychlé praní", + "17": "Stažený program", + "1b": "Bavlna", + "1c": "Eco 40-60", + "1d": "Super rychlé", + "1e": "15min rychlé praní", + "1f": "Intenzivní studená", + "20": "Hygienická pára", + "21": "Barevné prádlo", + "22": "Vlna", + "23": "Outdoor", + "24": "Ručníky", + "25": "Syntetika", + "26": "Jemné prádlo", + "27": "Máchání+odstřeďování", + "28": "Vypuštění/odstřeďování", + "29": "Čištění bubnu+", + "2a": "Džíny", + "2b": "AI praní", + "2d": "Tiché praní", + "2e": "Dětské potřeby", + "2f": "Sportovní oblečení", + "30": "Zataženo", + "32": "Košile", + "33": "Ložní prádlo", + "34": "Smíšené", + "36": "Praní+sušení", + "37": "Sušení vzduchem", + "38": "Sušení bavlny", + "39": "Sušení syntetiky", + "3a": "Čištění bubnu", + "52": "Eco studená", + "53": "Intenzivní", + "54": "Ručníky", + "55": "Sportovní oblečení", + "57": "Jemné", + "5e": "Máchání+odstřeďování", + "60": "Samočištění+", + "65": "Barevné prádlo", + "66": "Džíny", + "7c": "Bílé prádlo", + "7d": "Ložní prádlo/nepromokavé", + "7e": "Samočištění", + "7f": "Vlna/jemné", + "86": "Hloubkové praní", + "87": "Stažený program", + "8f": "Intenzivní studená", + "96": "Méně mikrovláken" + } + }, + "washer_dry_level": { + "name": "Úroveň sušení", + "state": { + "30": "30 min", + "60": "1 h", + "90": "1 h 30 min", + "120": "2 h", + "180": "3 h", + "240": "4 h", + "none": "Vypnuto", + "cupboard": "Do skříně" + } + }, + "zone_mode": { + "name": "Režim zóny" + }, + "watertank_light_color": { + "name": "Barva osvětlení nádržky na vodu", + "state": { + "warmwhite": "Teplá bílá", + "naturalwhite": "Přirozená bílá", + "coolwhite": "Studená bílá", + "green": "Zelená", + "purple": "Fialová", + "blue": "Modrá", + "orange": "Oranžová", + "pink": "Růžová" + } + }, + "watertank_light_brightness": { + "name": "Jas osvětlení nádržky na vodu" + } + }, + "sensor": { + "auto_clean_progress": { + "name": "Průběh automatického čištění" + }, + "after_run_progress": { + "name": "Průběh dosoušení" + }, + "air_filter_usage": { + "name": "Využití filtru" + }, + "air_filter_usage_hours": { + "name": "Hodiny využití filtru" + }, + "air_quality_standard": { + "name": "Norma kvality vzduchu" + }, + "air_sensing_state": { + "name": "Stav měření kvality vzduchu" + }, + "alarm_code": { + "name": "Kód alarmu" + }, + "auto_ventilation_action": { + "name": "Akce automatického větrání" + }, + "automatic_ventilation_state": { + "name": "Stav automatického větrání" + }, + "battery": { + "name": "Baterie" + }, + "burner_state": { + "name": "Stav hořáku {number}" + }, + "clean_level": { + "name": "Úroveň čištění" + }, + "coffee_brew_status": { + "name": "Stav přípravy kávy" + }, + "co2": { + "name": "CO2" + }, + "connection_mode": { + "name": "Režim připojení", + "state": { + "observe": "Push (observe)", + "poll": "Dotazování" + } + }, + "cooktop_running_state": { + "name": "Provozní stav varné desky" + }, + "cooktop_state": { + "name": "Stav varné desky" + }, + "current_limit_level": { + "name": "Úroveň omezení proudu" + }, + "filter_time": { + "name": "Čas filtru" + }, + "hepa_filter_usage": { + "name": "Využití HEPA filtru" + }, + "outdoor_temperature": { + "name": "Venkovní teplota" + }, + "odor_controller_progress": { + "name": "Průběh odstraňování pachů" + }, + "panel_status": { + "name": "Stav panelu" + }, + "sound_output": { + "name": "Zvukový výstup" + }, + "dustbag_usage": { + "name": "Využití sáčku na prach" + }, + "cleanstation_status": { + "name": "Stav čisticí stanice" + }, + "stick_status": { + "name": "Stav tyče" + }, + "uvc_operation_time": { + "name": "Doba provozu UV-C" + }, + "uvc_total_operation_time": { + "name": "Celková doba provozu UV-C" + }, + "uvc_finished_time": { + "name": "Čas dokončení UV-C" + }, + "uvc_emitted_time": { + "name": "Doba vyzařování UV-C" + }, + "overload_protection_mode": { + "name": "Režim ochrany proti přetížení", + "state": { + "alarm": "Alarm", + "powersaving": "Úspora energie" + } + }, + "absence_power_saving_mode": { + "name": "Režim úspory energie při nepřítomnosti", + "state": { + "eco": "Eco", + "normal": "Normální", + "comfort": "Komfort" + } + }, + "motion_detect_wind_mode": { + "name": "Režim vzduchu při detekci pohybu", + "state": { + "direct": "Přímý", + "indirect": "Nepřímý" + } + }, + "current_temp_c": { + "name": "Teplota" + }, + "current_temperature_c": { + "name": "Teplota" + }, + "diagnosis": { + "name": "Diagnostika" + }, + "diagnosis_status": { + "name": "Stav diagnostiky" + }, + "drum_clean_cycles_remaining": { + "name": "Čištění bubnu za" + }, + "drum_clean_last_cleaned": { + "name": "Poslední čištění bubnu" + }, + "dry_level": { + "name": "Úroveň sušení" + }, + "dry_time": { + "name": "Doba sušení" + }, + "dryer_type": { + "name": "Typ sušičky" + }, + "dust": { + "name": "Prach" + }, + "energy_kwh": { + "name": "Energie" + }, + "energy_last_month_kwh": { + "name": "Energie (minulý měsíc)" + }, + "energy_saved_kwh": { + "name": "Ušetřená energie" + }, + "energy_this_month_kwh": { + "name": "Energie (tento měsíc)" + }, + "fan_direction": { + "name": "Směr proudění vzduchu" + }, + "fan_speed_level": { + "name": "Úroveň otáček ventilátoru" + }, + "filter_clean_remain_time": { + "name": "Zbývající čas do čištění filtru" + }, + "filter_progress": { + "name": "Průběh filtru" + }, + "filter_usage": { + "name": "Využití filtru" + }, + "fine_dust": { + "name": "Jemný prach" + }, + "finish_time": { + "name": "Odhadované dokončení" + }, + "completion_minutes": { + "name": "Čas dokončení" + }, + "freezer_temperature": { + "name": "Teplota mrazáku" + }, + "fridge_temperature": { + "name": "Teplota chladničky" + }, + "kimchi_ripening_status": { + "name": "{instance_name} stav zrání" + }, + "kimchi_ripening_remaining": { + "name": "{instance_name} zbývající čas zrání" + }, + "kimchi_rack_count": { + "name": "{instance_name} počet přihrádek" + }, + "hood_filter_capacity": { + "name": "Kapacita filtru" + }, + "humidity": { + "name": "Vlhkost" + }, + "hood_filter_usage": { + "name": "Využití filtru" + }, + "instance_temperature": { + "name": "{instance_name} teplota" + }, + "job_beginning_status": { + "name": "Stav zahájení úlohy" + }, + "last_air_sensing_level": { + "name": "Poslední úroveň měření vzduchu" + }, + "last_air_sensing_time": { + "name": "Poslední čas měření vzduchu" + }, + "main_timer_current": { + "name": "Aktuální hodnota časovače" + }, + "main_timer_state": { + "name": "Stav časovače" + }, + "odor": { + "name": "Pach" + }, + "operating_mode": { + "name": "Provozní režim" + }, + "operation_origin": { + "name": "Poslední zdroj ovládání" + }, + "operation_time_minutes": { + "name": "Doba provozu" + }, + "oven_state": { + "name": "Stav trouby" + }, + "cavity_state": { + "name": "Stav trouby" + }, + "power_level": { + "name": "Úroveň výkonu" + }, + "paired_hood_fan_speed": { + "name": "Otáčky ventilátoru spárovaného odsavače" + }, + "paired_hood_firmware": { + "name": "Firmware spárovaného odsavače" + }, + "paired_hood_model": { + "name": "Model spárovaného odsavače" + }, + "power_energy_kwh": { + "name": "Spotřeba energie" + }, + "power_watts": { + "name": "Výkon" + }, + "probe_battery": { + "name": "Baterie sondy" + }, + "probe_target_temperature": { + "name": "Požadovaná teplota sondy" + }, + "probe_temperature": { + "name": "Teplota sondy" + }, + "progress": { + "name": "Průběh" + }, + "progress_percentage": { + "name": "Průběh v procentech" + }, + "selfcheck_error": { + "name": "Chyba vlastní kontroly" + }, + "selfcheck_result": { + "name": "Výsledek vlastní kontroly" + }, + "selfcheck_status": { + "name": "Stav vlastní kontroly" + }, + "sterilize_last_time": { + "name": "Poslední sterilizace" + }, + "sterilize_period": { + "name": "Interval sterilizace" + }, + "sterilize_plan_time": { + "name": "Plánovaný čas sterilizace" + }, + "sterilize_run_time": { + "name": "Doba běhu sterilizace" + }, + "super_fine_dust": { + "name": "Velmi jemný prach" + }, + "warming_center_state": { + "name": "Stav ohřevného centra" + }, + "water_liters": { + "name": "Spotřeba vody" + }, + "waterpurifier_status": { + "name": "Stav" + }, + "cup_state": { + "name": "Stav šálku" + }, + "last_pour_type": { + "name": "Poslední typ nalévání" + }, + "last_pour_capacity": { + "name": "Poslední nalité množství" + }, + "machine_state": { + "name": "Stav stroje", + "state": { + "idle": "Nečinný", + "active": "Aktivní", + "pause": "Pozastaveno" + } + }, + "filter_status": { + "name": "Stav filtru", + "state": { + "normal": "Normální", + "wash": "Umýt", + "replace": "Vyměnit" + } + }, + "ice_making_status": { + "name": "{instance_name} stav výroby", + "state": { + "icestatus_stop": "Nečinný", + "icestatus_run": "Vyrábí led" + } + }, + "zone_temperature": { + "name": "Teplota zóny" + }, + "watertank_full_alarm_status": { + "name": "Stav alarmu plné nádržky na vodu" + } + }, + "switch": { + "ai_energy_level": { + "name": "Režim AI Energy" + }, + "air_monitoring": { + "name": "Sledování kvality vzduchu" + }, + "air_purify": { + "name": "Čištění vzduchu" + }, + "auto_clean": { + "name": "Automatické čištění" + }, + "absence_power_saving_active": { + "name": "Úspora energie při nepřítomnosti aktivní" + }, + "motion_detect_wind_active": { + "name": "Vyhýbání se proudu vzduchu při pohybu aktivní" + }, + "beep": { + "name": "Pípání" + }, + "display": { + "name": "Displej" + }, + "pet_filter_activation": { + "name": "Aktivace filtru pro domácí mazlíčky" + }, + "auto_empty": { + "name": "Automatické vyprázdnění" + }, + "dustbin_auto_close": { + "name": "Automatické zavření nádoby na prach" + }, + "spi": { + "name": "Ionizace vzduchu" + }, + "uvc_intensive_mode": { + "name": "Intenzivní režim UV-C" + }, + "auto_door_opener": { + "name": "Automatické otevírání dvířek" + }, + "auto_release_dry": { + "name": "Automatické uvolnění při sušení" + }, + "autofill": { + "name": "Automatické plnění konvice" + }, + "bubble_soak": { + "name": "Bublinkové namáčení" + }, + "buzz_lock": { + "name": "Zámek bzučáku" + }, + "cabinet_light_dim": { + "name": "Postupné rozsvícení" + }, + "cabinet_light_switch": { + "name": "Osvětlení skříně" + }, + "child_lock": { + "name": "Dětský zámek" + }, + "coldwater_lock": { + "name": "Zámek studené vody" + }, + "cooktop_child_lock": { + "name": "Dětský zámek" + }, + "defrost_delay": { + "name": "Odložené odmrazování" + }, + "display_light": { + "name": "Podsvícení displeje" + }, + "dnd": { + "name": "Nerušit" + }, + "fast_preheat": { + "name": "Rychlé předehřátí" + }, + "favorite_capacity_enabled": { + "name": "Oblíbené množství" + }, + "favorite_coffee_enabled": { + "name": "Oblíbená káva" + }, + "filter_remind": { + "name": "Připomenutí filtru" + }, + "fridge_sound": { + "name": "Zvuk" + }, + "hotwater_lock": { + "name": "Zámek horké vody" + }, + "ice_maker_enabled": { + "name": "Výrobník ledu" + }, + "ice_night_mode": { + "name": "Noční tichý režim výroby ledu" + }, + "instance_enabled": { + "name": "{instance_name} zapnuto" + }, + "intensive": { + "name": "Intenzivní" + }, + "lamp": { + "name": "Lampa" + }, + "led_night_light": { + "name": "Noční LED osvětlení dvířek" + }, + "mute_once": { + "name": "Jednou ztlumit" + }, + "natural_steam": { + "name": "Přírodní pára" + }, + "energy_saving": { + "name": "Úspora energie" + }, + "cooktop_on_alert": { + "name": "Upozornění na zapnutou varnou desku" + }, + "power_switch": { + "name": "Napájení" + }, + "pre_wash": { + "name": "Předpírka" + }, + "rapid_freezing": { + "name": "Rychlé zmrazování" + }, + "rapid_fridge": { + "name": "Rychlé chlazení" + }, + "remind_beep": { + "name": "Připomínací signál konce" + }, + "sabbath_mode": { + "name": "Sabatní režim" + }, + "sanitize": { + "name": "Dezinfekce" + }, + "sound": { + "name": "Zvuk" + }, + "storm_wash": { + "name": "Storm Wash+" + }, + "welcome_lighting": { + "name": "Uvítací osvětlení" + }, + "wrinkle_prevent": { + "name": "Ochrana proti pomačkání" + }, + "zone_power": { + "name": "Napájení zóny" + }, + "away_mode": { + "name": "Režim nepřítomnosti" + }, + "watertank_light": { + "name": "Osvětlení nádržky na vodu" + } + }, + "time": { + "dnd_end": { + "name": "Konec režimu Nerušit" + }, + "dnd_start": { + "name": "Začátek režimu Nerušit" + }, + "led_night_end": { + "name": "Konec nočního LED osvětlení dvířek" + }, + "led_night_start": { + "name": "Začátek nočního LED osvětlení dvířek" + }, + "night_end": { + "name": "Konec nočního osvětlení" + }, + "night_start": { + "name": "Začátek nočního osvětlení" + } + }, + "water_heater": { + "dhw": { + "name": "Teplá voda" + } + } + }, + "config": { + "step": { + "user": { + "title": "Přidat spotřebič Samsung", + "description": "Zadejte IP adresu vašeho spotřebiče Samsung a přihlašovací údaje CA AC14K_M použité k vydávání certifikátů zařízení.", + "data": { + "host": "IP adresa", + "ca_cert_pem": "Certifikát CA (PEM)", + "ca_key_pem": "Privátní klíč CA (PEM)" + }, + "data_description": { + "host": "Lokální IP adresa vašeho spotřebiče Samsung (např. 192.168.1.50).", + "ca_cert_pem": "Certifikát CA AC14K_M ve formátu PEM. Vložte celý obsah řetězce fullchain PEM včetně všech bloků BEGIN/END CERTIFICATE.", + "ca_key_pem": "Privátní klíč AC14K_M ve formátu PEM. Vložte celý obsah včetně hlavičky a patičky BEGIN/END PRIVATE KEY." + } + }, + "user_reuse": { + "title": "Přidat spotřebič Samsung", + "description": "Zadejte IP adresu vašeho spotřebiče Samsung. Přihlašovací údaje CA z existujícího spotřebiče LocalThings budou znovu použity.", + "data": { + "host": "IP adresa" + }, + "data_description": { + "host": "Lokální IP adresa vašeho spotřebiče Samsung (např. 192.168.1.50)." + } + }, + "confirm_unknown_type": { + "title": "Typ spotřebiče nebyl rozpoznán", + "description": "Typ tohoto spotřebiče se nepodařilo rozpoznat. Přesto bude přidán, ale pouze se společnými funkcemi (napájení, alarmy atd., pokud jsou k dispozici), nikoli s plnou sadou funkcí pro danou rodinu spotřebičů. Podporu můžete později pomoci rozšířit stažením diagnostiky pro toto zařízení (Nastavení > Zařízení a služby > toto zařízení > nabídka > Stáhnout diagnostiku) a jejím vložením do nového issue. Odesláním zařízení přidáte i tak." + } + }, + "error": { + "cannot_connect": "Nelze se připojit k zařízení. Ověřte, že je IP adresa dostupná a že jsou přihlašovací údaje CA správné.", + "invalid_ca": "Certifikát CA nebo privátní klíč se nepodařilo načíst. Ověřte, že obsah PEM je správný a že klíč odpovídá certifikátu.", + "unknown": "Neočekávaná chyba. Podrobnosti najdete v protokolech Home Assistant." + }, + "abort": { + "already_configured": "Zařízení je již nakonfigurováno" + } + }, + "options": { + "step": { + "init": { + "title": "Možnosti LocalThings", + "menu_options": { + "settings": "Nastavení zápisu pro dálkové ovládání", + "debug_write": "Ladění: zápis do prostředku" + } + }, + "settings": { + "title": "Nastavení zápisu pro dálkové ovládání", + "description": "Některá zařízení přijímají určité zápisy (např. výchozí dávkování pracího prostředku/aviváže v pračce) i když hlásí vypnuté dálkové ovládání. LocalThings ve výchozím nastavení blokuje každý zápis srozumitelnou chybou, kdykoli zařízení hlásí vypnuté dálkové ovládání, místo aby jej zařízení tiše odmítlo. Povolte toto pouze tehdy, pokud jste ověřili, že zápisy na tomto zařízení skutečně fungují i s vypnutým dálkovým ovládáním – jinak tuto srozumitelnou chybu vyměníte za tiché selhání.", + "data": { + "bypass_remote_control_lock": "Povolit zápis i když je dálkové ovládání hlášeno jako vypnuté", + "finish_time_hysteresis_minutes": "Odhadovaný konec -- minimální změna (minuty)" + } + }, + "debug_write": { + "title": "Ladění: zápis do prostředku", + "description": "Nástroj pro pokročilé uživatele k odhalení chování zápisu specifického pro dané zařízení. Vyberte prostředek (href), do kterého chcete zapisovat, nebo zadejte vlastní, který není uveden v seznamu. Toto obchází blokování při vypnutém dálkovém ovládání a odesílá přesně ta pole, která zadáte -- může to špatně nakonfigurovat váš spotřebič, používejte tedy záměrně.", + "data": { + "href": "Href prostředku" + } + }, + "debug_edit": { + "title": "Ladicí zápis: {href}", + "description": "Aktuální hodnota `{href}`:\n```\n{current_value}\n```\nZadejte POUZE pole, která chcete změnit. To, co zadáte, je odesláno zařízení tak, jak je (částečná aktualizace); NENÍ to sloučeno s poli zobrazenými výše, takže nevkládejte zpět celou hodnotu. Dávejte pozor na typy: číselný řetězec jako \"1\" musí zůstat v uvozovkách -- holé 1 je odesláno jako celé číslo, což některá zařízení odmítají.", + "data": { + "payload": "Zapisovaný obsah" + } + }, + "debug_result": { + "title": "Výsledek ladicího zápisu", + "description": "Zařízení vrátilo kód CoAP {code}. Třída 2.xx znamená, že zápis byl přijat; 4.xx/5.xx znamená, že byl odmítnut. Prostředek nyní obsahuje:\n```\n{new_value}\n```\nPokud se hodnota nezměnila, zařízení zápis pravděpodobně zahodilo. Vyberte, co dělat dál:", + "menu_options": { + "debug_write": "Zapsat do dalšího prostředku", + "finish": "Dokončit" + } + } + }, + "error": { + "empty_payload": "Zadejte alespoň jedno pole k zápisu.", + "write_failed": "Zápis se nezdařil. Podrobnosti najdete v protokolech Home Assistant." + }, + "abort": { + "not_loaded": "Toto zařízení ještě není připojeno. Zkuste to znovu, až se načte." + } + }, + "issues": { + "device_gap": { + "title": "Neúplné pokrytí funkcí pro {device_name}", + "description": "Toto zařízení nemá úplné pokrytí funkcí. Buď nebyl rozpoznán jeho typ spotřebiče, nebo některé jím poskytované prostředky ještě nejsou namodelovány. Bude i nadále fungovat se vším, co je již podporováno. Podporu můžete pomoci rozšířit tak, že přejdete do Nastavení > Zařízení a služby > {device_name} > nabídka (vpravo nahoře) > Stáhnout diagnostiku a poté ji vložíte do odkazované šablony issue." + } + }, + "exceptions": { + "remote_control_disabled": { + "message": "Dálkové ovládání je na tomto zařízení vypnuté. V návodu ke spotřebiči zjistěte, jak zapnout dálkové ovládání, než jej bude moci Home Assistant ovládat." + }, + "debug_payload_empty": { + "message": "Obsah ladicího zápisu musí být neprázdný objekt." + }, + "resource_href_required": { + "message": "Je vyžadován href prostředku." + }, + "bubble_soak_unavailable_for_cycle": { + "message": "Bublinkové namáčení není u vybraného cyklu k dispozici." + }, + "pre_wash_unavailable_for_cycle": { + "message": "Předpírka není u vybraného cyklu k dispozici." + }, + "intensive_unavailable_for_cycle": { + "message": "Intenzivní není u vybraného cyklu k dispozici." + } + } +} From cf09247e3917962d4fac812e6c4a046d87e93ad0 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:07:03 +0000 Subject: [PATCH 03/10] Add AC UV LED, ventilation alarm, and PM1 filter support (issue #270) TP1X_FAC_TIME_23K reports three previously unbound hrefs: a UV-C sterilization LED and a ventilation-reminder alarm (both plain On/Off toggles), and a second PM1-rated dust filter with no live usage/status fields on this particular dump. The PM1 filter capability gates each entity on its own field's presence rather than a blanket ignore, since the TP1X_DA-AC-CAC-01001_0000 cassette AC (issue #191) reports the same href with full live data -- this also closes two of that device's ten documented coverage gaps (UV LED and the PM1 filter) as a side effect. --- .../registry/by_type/airconditioner.py | 3 + .../registry/capabilities/airconditioner.py | 119 ++++ .../localthings/translations/cs.json | 15 + .../localthings/translations/en.json | 15 + .../localthings/translations/es.json | 15 + .../localthings/translations/it.json | 15 + .../localthings/translations/nl.json | 15 + ...rconditioner_tp1x_fac_time_23k_device.json | 641 ++++++++++++++++++ tests/fixtures/golden/airconditioner_cac.json | 6 +- .../airconditioner_tp1x_fac_time_23k.json | 33 + tests/test_airconditioner_cac.py | 20 +- .../test_airconditioner_tp1x_fac_time_23k.py | 83 +++ tests/test_golden_regression.py | 20 + 13 files changed, 991 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/airconditioner_tp1x_fac_time_23k_device.json create mode 100644 tests/fixtures/golden/airconditioner_tp1x_fac_time_23k.json create mode 100644 tests/test_airconditioner_tp1x_fac_time_23k.py diff --git a/custom_components/localthings/registry/by_type/airconditioner.py b/custom_components/localthings/registry/by_type/airconditioner.py index 5a84e16..e76e193 100644 --- a/custom_components/localthings/registry/by_type/airconditioner.py +++ b/custom_components/localthings/registry/by_type/airconditioner.py @@ -33,8 +33,11 @@ REGISTRY = DeviceRegistry( airconditioner.AIR_PURIFY, airconditioner.AUTO_CLEAN, airconditioner.AIR_FILTER, + airconditioner.AIR_FILTER_PM1, airconditioner.AIR_QUALITY, airconditioner.DISPLAY_LIGHT, + airconditioner.UV_LED, + airconditioner.VENTILATION_ALARM, airconditioner.MUTE_ONCE, airconditioner.CURRENT_LIMIT, airconditioner.ANOMALY_LOAD, diff --git a/custom_components/localthings/registry/capabilities/airconditioner.py b/custom_components/localthings/registry/capabilities/airconditioner.py index 3da18bd..d0de28d 100644 --- a/custom_components/localthings/registry/capabilities/airconditioner.py +++ b/custom_components/localthings/registry/capabilities/airconditioner.py @@ -909,6 +909,80 @@ AIR_FILTER = Capability( ), ) + +def _pm1_threshold_write(payload, rep, href=None): + """Same contract as _threshold_write, against this filter's own href -- + not yet confirmed live (no dump seen advertises + supportedFilterDesiredUsage for this href), so the Select this backs + stays gated behind that field's presence, same as AIR_FILTER's.""" + return ["filter", "airdustPM1filter", "vs", "0"], { + "x.com.samsung.da.filterDesiredUsage": payload, + } + + +def _has_filter_field(field): + return lambda rep, resources: rep.get(field) is not None + + +# Second, PM1-rated dust filter some TP1X_FAC-class boards report alongside +# AIR_FILTER's /filter/airdustfilter/vs/0. TP1X_FAC_TIME_23K (issue #270) +# reports only filterCapacity/filterCapacityUnit/filterResetType +# ('notresetable') on this href, with no live filterUsage/filterStatus at +# all -- but the TP1X_DA-AC-CAC-01001_0000 cassette AC (issue #191) reports +# the identical href with full live fields. So this stays a real capability +# rather than a blanket ignore, with every entity individually gated on its +# own field's presence per the 'don't guess' rule: no entity on a board that +# has nothing behind it, real readings on one that does. +AIR_FILTER_PM1 = Capability( + href="/filter/airdustPM1filter/vs/0", + poll_tier="cold", + entities=( + SensorDesc( + key="air_filter_pm1_usage", + rep_fn=filter_usage_percent, + unit="%", + state_class="measurement", + icon="mdi:air-filter", + entity_category="diagnostic", + exists_fn=_has_filter_field("x.com.samsung.da.filterUsage"), + ), + SensorDesc( + key="air_filter_pm1_usage_hours", + field="x.com.samsung.da.filterUsage", + device_class="duration", + state_class="total_increasing", + unit_fn=_filter_unit, + icon="mdi:air-filter", + entity_category="diagnostic", + value_fn=_int, + exists_fn=_has_filter_field("x.com.samsung.da.filterUsage"), + ), + SelectDesc( + key="air_filter_pm1_threshold", + field="x.com.samsung.da.filterDesiredUsage", + options_field="x.com.samsung.da.supportedFilterDesiredUsage", + exists_fn=lambda rep, res: bool( + rep.get("x.com.samsung.da.supportedFilterDesiredUsage") + ), + icon="mdi:alarm", + entity_category="config", + write_fn=_pm1_threshold_write, + value_fn=lambda v: str(v) if v is not None else None, + ), + SensorDesc( + key="air_filter_pm1_status", + field="x.com.samsung.da.filterStatus", + device_class="enum", + options=("normal", "wash", "replace"), + translation_key="filter_status", + icon="mdi:air-filter", + entity_category="diagnostic", + value_fn=lambda v: v.lower() if isinstance(v, str) else v, + exists_fn=_has_filter_field("x.com.samsung.da.filterStatus"), + ), + ), +) + DISPLAY_LIGHT = Capability( href="/light/vs/0", poll_tier="cold", @@ -927,6 +1001,51 @@ DISPLAY_LIGHT = Capability( ), ) +# UV-C sterilization LED (issue #270, TP1X_FAC_TIME_23K). Same On/Off +# convention as everything else on this board, and (unlike VENTILATION_ALARM +# below) a real supportedModes list confirms the value set. +UV_LED = Capability( + href="/uvled/vs/0", + poll_tier="cold", + entities=( + SwitchDesc( + key="uv_led", + field="x.com.samsung.da.modes", + icon="mdi:lightbulb-fluorescent-tube", + entity_category="config", + value_fn=lambda v: v == "On", + write_fn=lambda p, rep, href=None: ( + ["uvled", "vs", "0"], + {"x.com.samsung.da.modes": "On" if p == "On" else "Off"}, + ), + ), + ), +) + +# Ventilation-reminder alarm toggle (issue #270). Only a bare `alarm` field +# is present -- no supportedModes list to confirm the value set against, +# unlike UV_LED above. Modeled as a switch on the strength of the same +# On/Off convention used everywhere else in this API; a rejected write is +# the worst case for a wrong guess (see the adding-device-support skill), +# but this hasn't been round-trip confirmed on real hardware. +VENTILATION_ALARM = Capability( + href="/ventilation/setting/vs/0", + poll_tier="cold", + entities=( + SwitchDesc( + key="ventilation_alarm", + field="alarm", + icon="mdi:bell-alert", + entity_category="config", + value_fn=lambda v: v == "On", + write_fn=lambda p, rep, href=None: ( + ["ventilation", "setting", "vs", "0"], + {"alarm": "On" if p == "On" else "Off"}, + ), + ), + ), +) + # Confirmed against issue #38's dump (TP1X_DA-AC-RAC-01001_0000): a single # boolean field, no vendor prefix, mirroring the On/Off convention used # throughout the rest of this API. diff --git a/custom_components/localthings/translations/cs.json b/custom_components/localthings/translations/cs.json index 95de516..1b3b139 100644 --- a/custom_components/localthings/translations/cs.json +++ b/custom_components/localthings/translations/cs.json @@ -637,6 +637,9 @@ }, "watertank_light_brightness": { "name": "Jas osvětlení nádržky na vodu" + }, + "air_filter_pm1_threshold": { + "name": "Práh alarmu PM1 filtru" } }, "sensor": { @@ -988,6 +991,12 @@ }, "watertank_full_alarm_status": { "name": "Stav alarmu plné nádržky na vodu" + }, + "air_filter_pm1_usage": { + "name": "Využití PM1 filtru" + }, + "air_filter_pm1_usage_hours": { + "name": "Hodiny využití PM1 filtru" } }, "switch": { @@ -1158,6 +1167,12 @@ }, "watertank_light": { "name": "Osvětlení nádržky na vodu" + }, + "uv_led": { + "name": "UV LED" + }, + "ventilation_alarm": { + "name": "Alarm větrání" } }, "time": { diff --git a/custom_components/localthings/translations/en.json b/custom_components/localthings/translations/en.json index 8495f04..4f85217 100644 --- a/custom_components/localthings/translations/en.json +++ b/custom_components/localthings/translations/en.json @@ -637,6 +637,9 @@ }, "watertank_light_brightness": { "name": "Water tank light brightness" + }, + "air_filter_pm1_threshold": { + "name": "PM1 filter alarm threshold" } }, "sensor": { @@ -988,6 +991,12 @@ }, "watertank_full_alarm_status": { "name": "Water tank full alarm status" + }, + "air_filter_pm1_usage": { + "name": "PM1 filter usage" + }, + "air_filter_pm1_usage_hours": { + "name": "PM1 filter usage hours" } }, "switch": { @@ -1158,6 +1167,12 @@ }, "watertank_light": { "name": "Water tank light" + }, + "uv_led": { + "name": "UV LED" + }, + "ventilation_alarm": { + "name": "Ventilation alarm" } }, "time": { diff --git a/custom_components/localthings/translations/es.json b/custom_components/localthings/translations/es.json index 30493b4..7a053a7 100644 --- a/custom_components/localthings/translations/es.json +++ b/custom_components/localthings/translations/es.json @@ -751,6 +751,9 @@ }, "watertank_light_brightness": { "name": "Brillo de la luz del depósito de agua" + }, + "air_filter_pm1_threshold": { + "name": "Umbral de alarma del filtro PM1" } }, "sensor": { @@ -1102,6 +1105,12 @@ }, "watertank_full_alarm_status": { "name": "Estado de alarma de depósito de agua lleno" + }, + "air_filter_pm1_usage": { + "name": "Uso del filtro PM1" + }, + "air_filter_pm1_usage_hours": { + "name": "Horas de uso del filtro PM1" } }, "switch": { @@ -1272,6 +1281,12 @@ }, "watertank_light": { "name": "Luz del depósito de agua" + }, + "uv_led": { + "name": "LED UV" + }, + "ventilation_alarm": { + "name": "Alarma de ventilación" } }, "time": { diff --git a/custom_components/localthings/translations/it.json b/custom_components/localthings/translations/it.json index 55f8bdf..cbedc3c 100644 --- a/custom_components/localthings/translations/it.json +++ b/custom_components/localthings/translations/it.json @@ -637,6 +637,9 @@ }, "watertank_light_brightness": { "name": "Luminosità della luce del serbatoio dell'acqua" + }, + "air_filter_pm1_threshold": { + "name": "Soglia di allarme del filtro PM1" } }, "sensor": { @@ -988,6 +991,12 @@ }, "watertank_full_alarm_status": { "name": "Stato allarme serbatoio d'acqua pieno" + }, + "air_filter_pm1_usage": { + "name": "Utilizzo del filtro PM1" + }, + "air_filter_pm1_usage_hours": { + "name": "Ore di utilizzo del filtro PM1" } }, "switch": { @@ -1158,6 +1167,12 @@ }, "watertank_light": { "name": "Luce del serbatoio dell'acqua" + }, + "uv_led": { + "name": "LED UV" + }, + "ventilation_alarm": { + "name": "Allarme ventilazione" } }, "time": { diff --git a/custom_components/localthings/translations/nl.json b/custom_components/localthings/translations/nl.json index f25be37..eac94b7 100644 --- a/custom_components/localthings/translations/nl.json +++ b/custom_components/localthings/translations/nl.json @@ -637,6 +637,9 @@ }, "watertank_light_brightness": { "name": "Helderheid van het waterreservoirlicht" + }, + "air_filter_pm1_threshold": { + "name": "PM1-filteralarmdrempel" } }, "sensor": { @@ -988,6 +991,12 @@ }, "watertank_full_alarm_status": { "name": "Status alarm waterreservoir vol" + }, + "air_filter_pm1_usage": { + "name": "PM1-filtergebruik" + }, + "air_filter_pm1_usage_hours": { + "name": "PM1-filtergebruik in uren" } }, "switch": { @@ -1158,6 +1167,12 @@ }, "watertank_light": { "name": "Waterreservoirlicht" + }, + "uv_led": { + "name": "Uv-led" + }, + "ventilation_alarm": { + "name": "Ventilatiealarm" } }, "time": { diff --git a/tests/fixtures/airconditioner_tp1x_fac_time_23k_device.json b/tests/fixtures/airconditioner_tp1x_fac_time_23k_device.json new file mode 100644 index 0000000..dad24a2 --- /dev/null +++ b/tests/fixtures/airconditioner_tp1x_fac_time_23k_device.json @@ -0,0 +1,641 @@ +{ + "device0": [ + { + "rt": [ + "x.com.samsung.devcol", + "oic.wk.col" + ], + "if": [ + "oic.if.baseline", + "oic.if.ll", + "oic.if.b" + ] + }, + { + "href": "/personality/presence/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "", + "x.com.samsung.da.deviceId": "**REDACTED**", + "x.com.samsung.da.value": "" + } + ] + } + }, + { + "href": "/realtimenotiforclient/vs/0", + "rep": { + "x.com.samsung.da.timeforshortnoti": "0", + "x.com.samsung.da.longnotisubscription": "true", + "x.com.samsung.da.periodicnotisubscription": "true" + } + }, + { + "href": "/filter/airdustfilter/vs/0", + "rep": { + "x.com.samsung.da.filterUsage": "100", + "x.com.samsung.da.filterUsageResolution": "1", + "x.com.samsung.da.filterDesiredUsage": "224", + "x.com.samsung.da.filterStatus": "wash", + "x.com.samsung.da.filterCapacity": "224", + "x.com.samsung.da.filterCapacityUnit": "Hour", + "x.com.samsung.da.filterResetType": [ + "washable" + ], + "x.com.samsung.da.supportedFilterDesiredUsage": [ + "112", + "224", + "336", + "448" + ] + } + }, + { + "href": "/filter/airdustPM1filter/vs/0", + "rep": { + "x.com.samsung.da.filterCapacity": "3", + "x.com.samsung.da.filterCapacityUnit": "Month", + "x.com.samsung.da.filterResetType": [ + "notresetable" + ] + } + }, + { + "href": "/temperature/control/vs/0", + "rep": { + "x.com.samsung.da.increment": "0.5" + } + }, + { + "href": "/mode/convenient/vs/0", + "rep": { + "x.com.samsung.da.modes": "Off", + "x.com.samsung.da.supportedModes": [ + "Off", + "Sleep", + "Nano", + "NanoSleep" + ] + } + }, + { + "href": "/option/airpurify/vs/0", + "rep": { + "x.com.samsung.da.modes": "Off", + "x.com.samsung.da.supportedModes": [ + "On", + "Off" + ] + } + }, + { + "href": "/option/autoclean/vs/0", + "rep": { + "x.com.samsung.da.status": "Stop", + "x.com.samsung.da.settingStatus": "On", + "x.com.samsung.da.progress": "0", + "x.com.samsung.da.supportedStatus": [ + "Start", + "SpeedClean", + "QuietClean", + "Stop" + ], + "x.com.samsung.da.supportedSettingStatus": [ + "On", + "SpeedClean", + "QuietClean", + "Off" + ] + } + }, + { + "href": "/wind/strength/vs/0", + "rep": { + "x.com.samsung.da.modes": "12", + "x.com.samsung.da.supportedModes": [ + "0", + "11", + "12", + "13", + "14", + "15", + "16" + ], + "x.com.samsung.da.modesName": [ + "Auto", + "1", + "2", + "3", + "4", + "5", + "Max" + ] + } + }, + { + "href": "/wind/direction/vs/0", + "rep": { + "x.com.samsung.da.modes": "NotSupported", + "x.com.samsung.da.supportedModes": [ + "NotSupported" + ] + } + }, + { + "href": "/subdevices/vs/0", + "rep": { + "x.com.samsung.da.subdeviceIdList": "**REDACTED**" + } + }, + { + "href": "/alarms/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "Alarm", + "x.com.samsung.da.alarmType": "Device", + "x.com.samsung.da.code": "ErrorCode_OFF", + "x.com.samsung.da.triggeredTime": "2026-08-03T14:14:25" + }, + { + "x.com.samsung.da.id": "2", + "x.com.samsung.da.description": "Alarm", + "x.com.samsung.da.alarmType": "Device", + "x.com.samsung.da.code": "AC_V_0002_OFF", + "x.com.samsung.da.triggeredTime": "2026-08-03T14:14:25" + } + ] + } + }, + { + "href": "/temperatures/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "Temperature", + "x.com.samsung.da.desired": "27.0", + "x.com.samsung.da.current": "28.0", + "x.com.samsung.da.maximum": "30", + "x.com.samsung.da.minimum": "18", + "x.com.samsung.da.increment": "0.5", + "x.com.samsung.da.unit": "Celsius" + } + ] + } + }, + { + "href": "/temperature/current/0", + "rep": { + "range": [ + 18.0, + 30.0 + ], + "units": "C", + "temperature": 28.0 + } + }, + { + "href": "/temperature/desired/0", + "rep": { + "range": [ + 18.0, + 30.0 + ], + "units": "C", + "temperature": 27.0 + } + }, + { + "href": "/diagnosis/vs/0", + "rep": {} + }, + { + "href": "/energy/consumption/vs/0", + "rep": { + "x.com.samsung.da.cumulativeConsumption": "400.000000", + "x.com.samsung.da.instantaneousPower": "14.000000", + "x.com.samsung.da.cumulativePower": "882861", + "x.com.samsung.da.cumulativeSavedPower": "0", + "x.com.samsung.da.cumulativeDate": "1785798000", + "x.com.samsung.da.cumulativeUnit": "Wh", + "x.com.samsung.da.instantaneousPowerUnit": "W", + "x.com.samsung.da.cumulativePowerType": "individual" + } + }, + { + "href": "/energy/consumption/0", + "rep": { + "energy": 400.0, + "power": 14.0 + } + }, + { + "href": "/mode/vs/0", + "rep": { + "x.com.samsung.da.supportedModes": [ + "AIComfort", + "Cool", + "Dry", + "Wind" + ], + "x.com.samsung.da.modes": [ + "Cool" + ], + "x.com.samsung.da.options": [ + "ArtificialWorking_Off", + "welcomecare_Off", + "Volume_Mute", + "Operation_Family", + "ComfortAICooling_Off", + "OnTimer_0", + "OffTimer_0", + "Sleep_0", + "Booster_Off", + "OutdoorSilence_Off", + "AiTempChanged_Off", + "AiTemp_250", + "StopAutoClean_Idle", + "Weather_Off", + "Service_Off", + "SmartCoolClean_Off", + "ProgressSmartClean_0", + "OutDoorVentil_Off", + "InDoorVentil_Off", + "FreezeAlarmSetting_Off", + "DesiredFreezeAlarm_240", + "ProtectOperation_Off", + "EnergySaveIcon_Off", + "ExtendOptionCode_16975", + "OptionCode_2589", + "RacInfo_First", + "ModelInfo_16K_BORA_VENT2", + "RacInfo_None_Second", + "OutdoorConnection_Connected", + "DurationOn_0", + "UpdateAllow_NotAllowed", + "welcomecareElapsedTime_0", + "welcomecareThresholdTemp_0", + "welcomecareStartDate_0000", + "welcomecareEndDate_0000", + "welcomecareSeason_None" + ] + } + }, + { + "href": "/power/vs/0", + "rep": { + "x.com.samsung.da.power": "Off", + "causeSource": "SMTS" + } + }, + { + "href": "/power/0", + "rep": { + "value": false + } + }, + { + "href": "/information/vs/0", + "rep": { + "x.com.samsung.da.modelNum": "TP1X_FAC_TIME_23K|10250841|600007110017112BCE00CE2200937000", + "x.com.samsung.da.description": "TP1X_FAC_TIME_23K", + "x.com.samsung.da.serialNum": "**REDACTED**", + "x.com.samsung.da.otnDUID": "**REDACTED**", + "x.com.samsung.da.diagProtocolType": "BLE_OCF", + "x.com.samsung.da.diagLogType": [ + "errCode", + "dump" + ], + "x.com.samsung.da.diagDumpType": "file", + "x.com.samsung.da.diagEndPoint": "SSM", + "x.com.samsung.da.diagMnid": "0AJT", + "x.com.samsung.da.diagSetupid": "AF2", + "x.com.samsung.da.diagMinVersion": "3.0", + "x.com.samsung.da.diagTsId": "DA01", + "x.com.samsung.da.serialNumOption": "**REDACTED**", + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Software", + "x.com.samsung.da.number": "02517A260330", + "x.com.samsung.da.newVersionAvailable": "0" + }, + { + "x.com.samsung.da.id": "1", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Firmware", + "x.com.samsung.da.number": "02508A23020200,02509A24030701", + "x.com.samsung.da.newVersionAvailable": "0" + }, + { + "x.com.samsung.da.id": "2", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Outdoor", + "x.com.samsung.da.number": "02502A23011300,02299A10000300" + }, + { + "x.com.samsung.da.id": "3", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "LED", + "x.com.samsung.da.number": "02510A22121900,FFFFFFFFFFFFFF" + }, + { + "x.com.samsung.da.id": "4", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "BLERemoteController", + "x.com.samsung.da.number": "22021760" + } + ] + } + }, + { + "href": "/file/information/vs/0", + "rep": { + "x.com.samsung.timeoffset": "+09:00", + "x.com.samsung.supprtedtype": 1 + } + }, + { + "href": "/configuration/vs/0", + "rep": { + "x.com.samsung.da.region": "1138000000", + "x.com.samsung.da.airconOptionList": [ + "HOMECARE_WIZARD_V2", + "ENERGY_2.0", + "AI_2.0", + "DeviceTypeMaster", + "CLEAN_OPTION_SUPPORT", + "DR", + "SingleCommand_1" + ] + } + }, + { + "href": "/humidity/0", + "rep": { + "humidity": 0 + } + }, + { + "href": "/humidity/vs/0", + "rep": { + "x.com.samsung.da.humidity": "0.000000", + "x.com.samsung.da.fivepercentHumidity": "71" + } + }, + { + "href": "/drlc/0", + "rep": { + "DRLevel": 0, + "start": "1970-01-01T00:00:00Z", + "durationminutes": 0, + "override": false + } + }, + { + "href": "/drlc/vs/0", + "rep": { + "x.com.samsung.da.drlcLevel": "0", + "x.com.samsung.da.durationminutes": "0", + "x.com.samsung.da.start": "1970-01-01T00:00:00Z", + "x.com.samsung.da.override": "Off", + "x.com.samsung.da.realSaving": "Off" + } + }, + { + "href": "/runn/vs/0", + "rep": { + "x.com.samsung.da.runningMode": 0 + } + }, + { + "href": "/availablecontrolsets/vs/0", + "rep": { + "x.com.samsung.da.sets": "000000B4012C0000000D04000000", + "x.com.samsung.da.id": "FAC", + "x.com.samsung.da.version": "1.0" + } + }, + { + "href": "/keepnormalstate/vs/0", + "rep": { + "x.com.samsung.da.keepnormal": 1 + } + }, + { + "href": "/uvled/vs/0", + "rep": { + "x.com.samsung.da.modes": "On", + "x.com.samsung.da.supportedModes": [ + "On", + "Off" + ] + } + }, + { + "href": "/remotedatacontrol/vs/0", + "rep": { + "x.com.samsung.da.status": "Off", + "x.com.samsung.da.connectionStatus": "Disconnected" + } + }, + { + "href": "/remotetemperature/vs/0", + "rep": { + "x.com.samsung.da.temperature": "", + "x.com.samsung.da.unit": "", + "x.com.samsung.da.error": "" + } + }, + { + "href": "/aisleep/vs/0", + "rep": { + "x.com.samsung.da.displayNightMode": "Off", + "x.com.samsung.da.elapsedTime": "0", + "x.com.samsung.da.requestFeedback": "Off", + "x.com.samsung.da.resultFeedback": "0", + "x.com.samsung.da.statusFeedback": "Idle", + "x.com.samsung.da.sleepTime": "14002200" + } + }, + { + "href": "/remotedeviceinfo/vs/0", + "rep": { + "x.com.samsung.da.didList": "" + } + }, + { + "href": "/otninformation/vs/0", + "rep": { + "x.com.samsung.da.target": "", + "x.com.samsung.da.newVersionAvailable": "false", + "x.com.samsung.da.newVersionNo": "00000000", + "x.com.samsung.da.currentVersionInfo": "00000000", + "otnStatus": "None", + "flashingProgress": "", + "otnTarget": "main", + "otnCompleteDate": "noHistory", + "otnList": [ + { + "type": "WIFI", + "modelId": "AFA-WW-TP1-23-AFXX00", + "versions": [ + "10260330" + ], + "visVersion": "260330" + }, + { + "type": "Micom", + "modelId": "845110250241FFFFFFFF", + "versions": [ + "23011300", + "FFFFFFFF" + ], + "visVersion": "230113" + }, + { + "type": "Micom", + "modelId": "04511025084110250941", + "versions": [ + "23020200", + "24030701" + ], + "visVersion": "240307" + }, + { + "type": "Micom", + "modelId": "04511025024110229941", + "versions": [ + "23011300", + "10000300" + ], + "visVersion": "230113" + }, + { + "type": "Micom", + "modelId": "045110240441FFFFFFFF", + "versions": [ + "23121900", + "FFFFFFFF" + ], + "visVersion": "231219" + }, + { + "type": "Micom", + "modelId": "045110251041FFFFFFFF", + "versions": [ + "22121900", + "FFFFFFFF" + ], + "visVersion": "221219" + } + ] + } + }, + { + "href": "/option/muteonce/vs/0", + "rep": { + "muteonce": "Off" + } + }, + { + "href": "/selfcheck/vs/0", + "rep": { + "x.com.samsung.da.start": "Cancel", + "x.com.samsung.da.status": "Ready", + "x.com.samsung.da.progress": "1", + "x.com.samsung.da.result": "NA", + "x.com.samsung.da.supportedActions": [ + "Start", + "Cancel" + ], + "x.com.samsung.da.error": [ + "DA_ERROR_NONE" + ] + } + }, + { + "href": "/reserverulesets/vs/0", + "rep": { + "x.com.samsung.da.sets": "1DFFFFFFFF121E121EFFFFFFFFFFFF01000000013F0100013F000000001C0C", + "x.com.samsung.da.id": "FAC", + "x.com.samsung.da.version": "1.0" + } + }, + { + "href": "/ventilation/setting/vs/0", + "rep": { + "alarm": "Off" + } + }, + { + "href": "/light/vs/0", + "rep": { + "mode": "Off", + "supportedModes": [ + "On", + "Off" + ] + } + }, + { + "href": "/timezone/vs/0", + "rep": { + "timezoneid": "Asia/Seoul", + "offset": "+09:00", + "DST": "OFF" + } + }, + { + "href": "/connectionconfig/vs/0", + "rep": { + "autoReconnectionMinVersion": "1.0", + "autoReconnection": "true", + "autoReconnectionProtocolType": [ + "helper_hotspot", + "ble_ocf" + ], + "supportedWiFiAuthType": [ + "OPEN", + "WEP", + "WPA-PSK", + "WPA2-PSK", + "SAE" + ], + "supportedWiFiCryptoType": [ + "TKIP", + "AES", + "WEP-64", + "WEP-128" + ], + "supportedWiFiFreq": [ + "2.4G" + ], + "calmConnectionCare": { + "version": "1.0", + "role": [ + "things" + ] + } + } + }, + { + "href": "/wirelessinfo/vs/0", + "rep": { + "macaddressWiFi": "**REDACTED**", + "macaddressBLE": "**REDACTED**", + "connectedApSsid": "**REDACTED**" + } + }, + { + "href": "/quickcontrol/info/vs/0", + "rep": { + "supportedVersion": "1.0" + } + } + ] +} diff --git a/tests/fixtures/golden/airconditioner_cac.json b/tests/fixtures/golden/airconditioner_cac.json index c00c271..30ee055 100644 --- a/tests/fixtures/golden/airconditioner_cac.json +++ b/tests/fixtures/golden/airconditioner_cac.json @@ -2,6 +2,9 @@ "state_keys": [ "absence_power_saving_active", "absence_power_saving_mode", + "air_filter_pm1_status", + "air_filter_pm1_usage", + "air_filter_pm1_usage_hours", "air_filter_status", "air_filter_usage", "air_filter_usage_hours", @@ -29,6 +32,7 @@ "selfcheck_result", "selfcheck_status", "super_fine_dust", - "tropical_night_mode" + "tropical_night_mode", + "uv_led" ] } diff --git a/tests/fixtures/golden/airconditioner_tp1x_fac_time_23k.json b/tests/fixtures/golden/airconditioner_tp1x_fac_time_23k.json new file mode 100644 index 0000000..a61e80e --- /dev/null +++ b/tests/fixtures/golden/airconditioner_tp1x_fac_time_23k.json @@ -0,0 +1,33 @@ +{ + "state_keys": [ + "air_filter_status", + "air_filter_threshold", + "air_filter_usage", + "air_filter_usage_hours", + "air_purify", + "alarm_code", + "auto_clean", + "auto_clean_progress", + "auto_clean_running", + "beep", + "climate", + "current_temperature_c", + "diagnosis_status", + "display_light", + "energy_kwh", + "energy_saved_kwh", + "firmware_update", + "humidity", + "mute_once", + "odor_controller_active", + "odor_controller_progress", + "power_energy_kwh", + "power_watts", + "selfcheck_error", + "selfcheck_result", + "selfcheck_status", + "tropical_night_mode", + "uv_led", + "ventilation_alarm" + ] +} diff --git a/tests/test_airconditioner_cac.py b/tests/test_airconditioner_cac.py index e1e5bc7..99e1c27 100644 --- a/tests/test_airconditioner_cac.py +++ b/tests/test_airconditioner_cac.py @@ -6,12 +6,18 @@ token -- this board was the one exception (its oneUiVersion self-reports "7.0 Air conditioner", but 'CAC' had never been added to the board-token table), so it silently fell back to common caps and lost its climate entity. -This dump is NOT fully covered yet -- ten hrefs remain unbound (edge -lighting, PM1 filter, a second stateful light resource, absence-clean, four -`/settings/sound/*` resources, smart-sensing-cooling, UV LED), all genuinely -new to this board generation. That's a real device-support gap, left -documented here rather than guessed at, per the 'don't guess' rule -- fixing -the routing regression was the scope of #191. +This dump is NOT fully covered yet -- eight hrefs remain unbound (edge +lighting, a second stateful light resource, absence-clean, four +`/settings/sound/*` resources, smart-sensing-cooling), all genuinely new to +this board generation. That's a real device-support gap, left documented +here rather than guessed at, per the 'don't guess' rule -- fixing the +routing regression was the scope of #191. + +/uvled/vs/0 and /filter/airdustPM1filter/vs/0 used to be on this list too, +until issue #270 (TP1X_FAC_TIME_23K) added real capabilities for both -- +this board's own live filterUsage/filterStatus data on the PM1 filter binds +through the same exists_fn-gated entities #270's dump (which has neither +field) leaves empty. """ from custom_components.localthings.registry.adapter import flatten @@ -22,7 +28,6 @@ from tests.conftest import _load_device _STILL_UNBOUND = frozenset( { "/edgelighting/vs/0", - "/filter/airdustPM1filter/vs/0", "/light/stateful/vs/0", "/mds/absenceclean/vs/0", "/settings/sound/mode/vs/0", @@ -30,7 +35,6 @@ _STILL_UNBOUND = frozenset( "/settings/sound/output/vs/0", "/settings/sound/volume/vs/0", "/smartsensingcooling/vs/0", - "/uvled/vs/0", } ) diff --git a/tests/test_airconditioner_tp1x_fac_time_23k.py b/tests/test_airconditioner_tp1x_fac_time_23k.py new file mode 100644 index 0000000..2b677fb --- /dev/null +++ b/tests/test_airconditioner_tp1x_fac_time_23k.py @@ -0,0 +1,83 @@ +"""Tests for the TP1X_FAC_TIME_23K air conditioner (issue #270). + +A 2-in-1 system (x.com.samsung.da.numofsubdevice='2') whose UUID-prefixed +sibling materializes via the flat fallback (issue #205) -- unrelated to the +three capability gaps this fixture actually locks in (UV LED, ventilation +alarm, and the unmodeled PM1 filter), which live entirely on canonical +hrefs already exercised by the master's own resources. +""" + +from custom_components.localthings.registry.adapter import flatten +from custom_components.localthings.registry.by_type import for_device_by_model +from custom_components.localthings.registry.discovery import discover +from custom_components.localthings.registry.entities import SwitchDesc +from tests.conftest import _load_device + + +def _airconditioner(): + resources = _load_device("airconditioner_tp1x_fac_time_23k") + info = resources["/information/vs/0"] + reg = for_device_by_model( + info["x.com.samsung.da.modelNum"], + info["x.com.samsung.da.description"], + ) + return reg, resources + + +def _bound(): + reg, resources = _airconditioner() + return discover(resources, reg.capabilities, reg.pattern_capabilities), resources + + +def _state(): + bound, resources = _bound() + return flatten(bound, resources) + + +def _desc(key): + bound, _ = _bound() + return next(b.desc for b in bound if b.desc.key == key) + + +def test_model_resolves_to_airconditioner_registry(): + reg, _ = _airconditioner() + assert reg is not None and reg.name == "airconditioner" + + +def test_no_unbound_hrefs(): + reg, resources = _airconditioner() + unbound = [] + discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append) + assert unbound == [] + + +def test_uv_led_switch_reads_on_and_has_write_contract(): + state = _state() + assert state["uv_led"] is True + desc = _desc("uv_led") + assert isinstance(desc, SwitchDesc) + path, body = desc.write_fn("Off", {}) + assert path == ["uvled", "vs", "0"] + assert body == {"x.com.samsung.da.modes": "Off"} + + +def test_ventilation_alarm_switch_reads_off_and_has_write_contract(): + state = _state() + assert state["ventilation_alarm"] is False + desc = _desc("ventilation_alarm") + assert isinstance(desc, SwitchDesc) + path, body = desc.write_fn("On", {}) + assert path == ["ventilation", "setting", "vs", "0"] + assert body == {"alarm": "On"} + + +def test_pm1_filter_has_no_live_fields_so_no_state_is_reported(): + """This dump's /filter/airdustPM1filter/vs/0 has only + filterCapacity/filterCapacityUnit/filterResetType -- no filterUsage or + filterStatus -- so every AIR_FILTER_PM1 entity's exists_fn gates it out. + The href still covers (see test_no_unbound_hrefs above) without + fabricating a reading from nothing (the 'don't guess' rule); contrast + with test_airconditioner_cac.py, whose dump has live data on the same + href and does get real air_filter_pm1_* state.""" + state = _state() + assert not any(k.startswith("air_filter_pm1_") for k in state) diff --git a/tests/test_golden_regression.py b/tests/test_golden_regression.py index 3877446..3181bb9 100644 --- a/tests/test_golden_regression.py +++ b/tests/test_golden_regression.py @@ -1243,6 +1243,26 @@ def test_registry_reproduces_golden_state_keys_for_dehumidifier_tp1x_dhm01001(): ) +def test_registry_reproduces_golden_state_keys_for_airconditioner_tp1x_fac_time_23k(): + """TP1X_FAC_TIME_23K (issue #270) reports a UV-C LED, a ventilation-alarm + toggle, and a second (PM1-rated, no live usage field) dust filter, none + previously modeled. Also a 2-in-1 system on this dump (numofsubdevice=2, + a UUID-prefixed sibling materializes via the flat fallback) -- the three + new/ignored hrefs are fixed on their canonical form so the sibling picks + them up too, per the adding-device-support skill's 'never index or + prefix a registry href' rule.""" + from tests.conftest import _load_device + + resources = _load_device("airconditioner_tp1x_fac_time_23k") + golden = json.loads((GOLDEN / "airconditioner_tp1x_fac_time_23k.json").read_text()) + state_keys = _new_state_keys("airconditioner_tp1x_fac_time_23k", resources) + assert set(state_keys) == set(golden["state_keys"]), ( + f"state_keys mismatch:\n" + f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n" + f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}" + ) + + def test_resources_from_batch_preferred_over_flat(): from tests.conftest import _resources_from_dump From e6d7dcddc86da70ed8f0800e97785ba78cc4bfdf Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:14:29 +0000 Subject: [PATCH 04/10] Add dryer Drum Clean+ tracking; fix multi-entry DrumCleanLog parsing (#258) Dryers report the same DrumCleanProposal_/WashingTimes_/DrumCleanLog_ options[] tokens washer.py already models for issue #9, so drum_clean_cycles_remaining/drum_clean_last_cleaned move to laundry.py and get bound on dryer's /course/vs/0 too. DrumCleanLog_ on the reporter's dump is a '|'-joined history of every past clean rather than washer's single bare timestamp -- the shared helper now takes the last (most recent) entry, which turns out to also fix a latent bug on four existing washer fixtures whose own DrumCleanLog_ was already multi-entry and silently failing to parse into drum_clean_last_cleaned. No heat-exchanger-clean tracking was found in either dump #258 supplied; noted in dryer.py so a future report knows this was checked. --- .../registry/capabilities/dryer.py | 29 +- .../registry/capabilities/laundry.py | 42 ++ .../registry/capabilities/washer.py | 46 +- .../dryer_tp1_21_drum_clean_device.json | 438 ++++++++++++++++++ .../golden/dryer_tp1_21_drum_clean.json | 28 ++ tests/fixtures/golden/washer_dryer_combo.json | 1 + tests/fixtures/golden/washer_flexwash.json | 1 + tests/fixtures/golden/washer_wa55a7700av.json | 3 +- tests/fixtures/golden/washer_wa8000t.json | 1 + tests/test_dryer_drum_clean.py | 86 ++++ tests/test_golden_regression.py | 41 +- 11 files changed, 674 insertions(+), 42 deletions(-) create mode 100644 tests/fixtures/dryer_tp1_21_drum_clean_device.json create mode 100644 tests/fixtures/golden/dryer_tp1_21_drum_clean.json create mode 100644 tests/test_dryer_drum_clean.py diff --git a/custom_components/localthings/registry/capabilities/dryer.py b/custom_components/localthings/registry/capabilities/dryer.py index d903b6c..9887bf4 100644 --- a/custom_components/localthings/registry/capabilities/dryer.py +++ b/custom_components/localthings/registry/capabilities/dryer.py @@ -11,7 +11,7 @@ the /course/vs/0 cycle select -- lives in laundry.py. from ..capability import Capability from ..entities import SensorDesc, SwitchDesc -from .laundry import cycle_select +from .laundry import cycle_select, drum_clean_cycles_remaining, drum_clean_last_cleaned def _wrinkle_write(p, rep, href=None): @@ -49,6 +49,17 @@ DRYER_SETTINGS = Capability( # #80). The /st/dryercourse/vs/0 resource re-encodes the same selected # course and is ignored (ignored.py) -- the mirror of how /st/washercourse/vs/0 # is ignored for washers. +# +# 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/ +# drum_clean_last_cleaned's docstrings for the field contract, including +# the dryer-specific '|'-joined multi-entry DrumCleanLog_ shape. No +# separate heat-exchanger-clean tracking was found on either dump #258 +# supplied (DV90BB7445GES7, DV91T6440LE/SA) -- no HeatExchanger*-prefixed +# token, nor any other options[] entry that looks like a second maintenance +# counter -- so if the Samsung app surfaces that reminder for these units, +# it isn't computed from anything this integration can read locally. DRYER_COURSE = Capability( href="/course/vs/0", entities=( @@ -57,6 +68,22 @@ DRYER_COURSE = Capability( icon="mdi:tumble-dryer", table_href="/st/dryercourse/vs/0", ), + SensorDesc( + key="drum_clean_cycles_remaining", + unit="cycles", + icon="mdi:tumble-dryer-alert", + state_class="measurement", + exists_fn=lambda rep, resources: drum_clean_cycles_remaining(rep) is not None, + rep_fn=drum_clean_cycles_remaining, + ), + SensorDesc( + key="drum_clean_last_cleaned", + device_class="timestamp", + icon="mdi:calendar-clock", + entity_category="diagnostic", + exists_fn=lambda rep, resources: drum_clean_last_cleaned(rep) is not None, + rep_fn=drum_clean_last_cleaned, + ), ), ) diff --git a/custom_components/localthings/registry/capabilities/laundry.py b/custom_components/localthings/registry/capabilities/laundry.py index 802e371..512d4b8 100644 --- a/custom_components/localthings/registry/capabilities/laundry.py +++ b/custom_components/localthings/registry/capabilities/laundry.py @@ -20,6 +20,7 @@ Door-LED keys use NO `x.com.samsung.da.` prefix -- `setBrightness` / `setNightLight` -- preserved exactly as they appear in the OCF resource rep. """ +from datetime import UTC, datetime from datetime import time as dt_time from ...catalog import has_entity_translation @@ -234,6 +235,47 @@ def option_value(options, prefix): return None +# Drum Clean+ maintenance tracking, from the same options[] array as the +# selected course -- shared by washer.py (issue #9) and dryer.py (issue +# #258); both families use identical DrumCleanProposal_/WashingTimes_/ +# DrumCleanLog_ tokens. DrumCleanProposal_ is the wash/dry-cycle interval +# between recommended cleans; WashingTimes_ is the count since the last +# one -- their difference is exactly the "N cycles until due" figure the +# Samsung app shows (verified on a washer: DrumCleanProposal_40 - +# WashingTimes_3 == 37, matching a live app screenshot's "Potreba cistenia +# po 37 cykloch"). +def drum_clean_cycles_remaining(rep): + opts = rep.get("x.com.samsung.da.options") or [] + proposal = option_value(opts, "DrumCleanProposal") + washed = option_value(opts, "WashingTimes") + if proposal is None or washed is None: + return None + try: + return max(int(proposal) - int(washed), 0) + except ValueError: + return None + + +# DrumCleanLog_ is the clean-history field: a washer reports one bare ISO +# datetime (the last clean, verified against the same app screenshot's "10 +# days ago"); a dryer (issue #258's Dillton-reported dump) instead reports a +# '|'-joined history of every past clean, ten deep on that dump, in +# strictly increasing order. Splitting on '|' and taking the last element +# handles both shapes identically -- a no-'|' value is unaffected. No +# explicit timezone field accompanies either shape, so it's treated as UTC, +# matching this integration's convention for other bare ISO datetime fields +# (see fridge.py's night-light schedule comment). +def drum_clean_last_cleaned(rep): + raw = option_value(rep.get("x.com.samsung.da.options"), "DrumCleanLog") + if not raw: + return None + last = raw.rsplit("|", 1)[-1] + try: + return datetime.fromisoformat(last).replace(tzinfo=UTC) + except ValueError: + return None + + def _course_codes_from_supported_options(course_rep): """Fallback for an empty/missing editCourseList: derive the selectable course list from /course/vs/0's own x.com.samsung.da.supportedOptions diff --git a/custom_components/localthings/registry/capabilities/washer.py b/custom_components/localthings/registry/capabilities/washer.py index ef41fe4..2a78d4b 100644 --- a/custom_components/localthings/registry/capabilities/washer.py +++ b/custom_components/localthings/registry/capabilities/washer.py @@ -15,8 +15,6 @@ here; they read washer-only fields off the same shared /course/vs/0 options array. """ -from datetime import UTC, datetime - from ..capability import Capability from ..entities import BinarySensorDesc, SelectDesc, SensorDesc from .laundry import ( @@ -24,6 +22,8 @@ from .laundry import ( bool_option_switch, cycle_options, cycle_select, + drum_clean_cycles_remaining, + drum_clean_last_cleaned, hex_pairs, option_value, option_write, @@ -149,36 +149,10 @@ WASHER_SETTINGS = Capability( # --------------------------------------------------------------------------- -# Drum Clean+ maintenance tracking, from the same options[] array as the -# selected course. DrumCleanProposal_ is the wash-cycle interval between -# recommended cleans; WashingTimes_ is the count since the last one -- -# their difference is exactly the "N cycles until due" figure the Samsung -# app shows (verified: DrumCleanProposal_40 - WashingTimes_3 == 37, matching -# a live app screenshot's "Potreba cistenia po 37 cykloch"). DrumCleanLog_ -# is the last-clean timestamp (verified against the same screenshot's "10 -# days ago"); no explicit timezone field accompanies it on this resource, -# so it's treated as UTC, matching this integration's convention for other -# bare ISO datetime fields (see fridge.py's night-light schedule comment). -def _drum_clean_cycles_remaining(rep): - opts = rep.get("x.com.samsung.da.options") or [] - proposal = option_value(opts, "DrumCleanProposal") - washed = option_value(opts, "WashingTimes") - if proposal is None or washed is None: - return None - try: - return max(int(proposal) - int(washed), 0) - except ValueError: - return None - - -def _drum_clean_last_cleaned(rep): - raw = option_value(rep.get("x.com.samsung.da.options"), "DrumCleanLog") - if not raw: - return None - try: - return datetime.fromisoformat(raw).replace(tzinfo=UTC) - except ValueError: - return None +# Drum Clean+ maintenance tracking (issue #9): drum_clean_cycles_remaining/ +# drum_clean_last_cleaned live in laundry.py, shared with dryer.py (issue +# #258) since both families report identical DrumCleanProposal_/ +# WashingTimes_/DrumCleanLog_ tokens on the same options[] array. # Detergent/softener auto-dispense dosing, from the same options[] array @@ -340,16 +314,16 @@ WASHER_COURSE = Capability( unit="cycles", icon="mdi:washing-machine-alert", state_class="measurement", - exists_fn=lambda rep, resources: _drum_clean_cycles_remaining(rep) is not None, - rep_fn=_drum_clean_cycles_remaining, + exists_fn=lambda rep, resources: drum_clean_cycles_remaining(rep) is not None, + rep_fn=drum_clean_cycles_remaining, ), SensorDesc( key="drum_clean_last_cleaned", device_class="timestamp", icon="mdi:calendar-clock", entity_category="diagnostic", - exists_fn=lambda rep, resources: _drum_clean_last_cleaned(rep) is not None, - rep_fn=_drum_clean_last_cleaned, + exists_fn=lambda rep, resources: drum_clean_last_cleaned(rep) is not None, + rep_fn=drum_clean_last_cleaned, ), SelectDesc( key="detergent_quantity", diff --git a/tests/fixtures/dryer_tp1_21_drum_clean_device.json b/tests/fixtures/dryer_tp1_21_drum_clean_device.json new file mode 100644 index 0000000..a839211 --- /dev/null +++ b/tests/fixtures/dryer_tp1_21_drum_clean_device.json @@ -0,0 +1,438 @@ +{ + "device0": [ + { + "rt": [ + "x.com.samsung.devcol", + "oic.wk.col" + ], + "if": [ + "oic.if.baseline", + "oic.if.ll", + "oic.if.b" + ] + }, + { + "href": "/realtimenotiforclient/vs/0", + "rep": { + "x.com.samsung.da.timeforshortnoti": "0", + "x.com.samsung.da.periodicnotisubscription": "true" + } + }, + { + "href": "/alarms/vs/0", + "rep": {} + }, + { + "href": "/diagnosis/vs/0", + "rep": { + "x.com.samsung.da.diagnosisStart": "Ready" + } + }, + { + "href": "/energy/consumption/vs/0", + "rep": { + "x.com.samsung.da.instantaneousPower": "-500", + "x.com.samsung.da.instantaneousPowerUnit": "W", + "x.com.samsung.da.cumulativePower": "211700", + "x.com.samsung.da.cumulativeUnit": "Wh", + "x.com.samsung.da.cumulativeDate": "1785693600", + "x.com.samsung.da.cumulativeDateUTC": "1785686400", + "x.com.samsung.da.cumulativeSavedPower": "0" + } + }, + { + "href": "/energy/consumption/0", + "rep": {} + }, + { + "href": "/course/vs/0", + "rep": { + "x.com.samsung.da.supportedModes": [ + "HOMECARE_WIZARD_V2" + ], + "x.com.samsung.da.options": [ + "DeviceType_0165", + "UpdateAllow_NotAllowed", + "Course_16", + "AiOption_On", + "MixedLoadBell_Disable", + "MixedLoadBellNoti_Nothing", + "LaundryOutTime_0", + "SeamlessControl_Enable", + "KidsLockBypass_On", + "WashingTimes_25", + "DrumCleanProposal_25", + "DetergentOnce_1", + "DetergentLeft_0", + "DetergentBase_5", + "DetergentAlarm_Off", + "DetergentType_3", + "DetergentTotal_0", + "SpecialFunction_20", + "AvailableDelayTime_215", + "LaundryPlannerUserSetTime_0", + "ProgressTimeSet_B214DCB4003C", + "WrinklePreventRunning_Off", + "QuickDrive_Off", + "SendToDevice_Off", + "GMT_04", + "DrumLight_Off", + "DryTimeSet_FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060007FE020007FE020007FE0000000000000000", + "EnergyLevelSet_05030403050302040503050203030402010205", + "MostUsed_16D31E", + "WrinklePreventSet_0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0FF0", + "MixedLoadBellSet_0202FF0202FFFFFF02FFFFFFFFFFFFFFFFFF", + "EmptyingTheBucket_Nothing", + "SavingModeCondition_01010202171D0300", + "SavingMode_Off", + "QuickDriveSet_F0000000F000000000000000000000000000", + "GeoFenceAlarm", + "UsagesDB_ok", + "EnergyKW_396", + "DrumCleanLog_2023-12-03T22:07:18|2024-02-09T15:18:59|2024-07-07T23:06:16|2024-10-19T19:29:45|2025-01-26T16:10:23|2025-05-10T19:59:59|2025-07-13T14:58:26|2026-01-09T21:27:43|2026-03-21T18:52:46|2026-05-24T21:38:08", + "TimeSync_NotSupported" + ], + "x.com.samsung.da.supportedOptions": [ + "116D31E29D31E23D00017D31E18D21E19D20C1DD3181BD3181CD31E21D4101AD1021ED31820D10227D00025D00024D0004CD21C2BD000" + ] + } + }, + { + "href": "/power/vs/0", + "rep": { + "x.com.samsung.da.power": "Off" + } + }, + { + "href": "/power/0", + "rep": { + "value": false + } + }, + { + "href": "/cycleinterface/vs/0", + "rep": { + "x.com.samsung.da.cycleInterfaceEnabled": "On" + } + }, + { + "href": "/kidslock/vs/0", + "rep": { + "x.com.samsung.da.kidsLock": "Ready" + } + }, + { + "href": "/kidslock/0", + "rep": { + "value": false + } + }, + { + "href": "/operational/state/vs/0", + "rep": { + "x.com.samsung.da.state": "Ready", + "x.com.samsung.da.remainingTime": "03:35:00", + "x.com.samsung.da.progressPercentage": "1", + "x.com.samsung.da.progress": "None", + "x.com.samsung.da.delayEndTime": "00:00:00", + "x.com.samsung.da.supportedProgress": [ + "None", + "Drying", + "Cooling", + "Finish" + ] + } + }, + { + "href": "/operational/state/0", + "rep": { + "currentMachineState": "**REDACTED**", + "machineStates": "**REDACTED**", + "jobStates": [ + "None", + "Drying", + "Cooling", + "Finish" + ], + "currentJobState": "None", + "remainingTime": "03:35:00", + "progressPercentage": "1" + } + }, + { + "href": "/information/vs/0", + "rep": { + "x.com.samsung.da.modelNum": "DA_WM_TP1_21_COMMON|20317441|30010102001611000AA3031F00000000", + "x.com.samsung.da.description": "DA_WM_TP1_21_COMMON_DV9400B/DC92-03267A_2C56", + "x.com.samsung.da.serialNum": "**REDACTED**", + "x.com.samsung.da.otnDUID": "**REDACTED**", + "x.com.samsung.da.diagProtocolType": "BLE_OCF", + "x.com.samsung.da.diagLogType": [ + "errCode", + "dump" + ], + "x.com.samsung.da.diagDumpType": "file", + "x.com.samsung.da.diagEndPoint": "SSM", + "x.com.samsung.da.diagMnid": "0AJT", + "x.com.samsung.da.diagSetupid": "DR1", + "x.com.samsung.da.diagMinVersion": "3.0", + "x.com.samsung.da.diagTsId": "DA01", + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "DA_WM_TP1_21_COMMON|20317441|30010102001611000AA3031F00000000", + "x.com.samsung.da.type": "Software", + "x.com.samsung.da.number": "02986A260118(A182)", + "x.com.samsung.da.newVersionAvailable": "0" + }, + { + "x.com.samsung.da.id": "1", + "x.com.samsung.da.description": "Firmware_1_DB_20317441221114080FFFFF203267412210245204FFFF(01652031744120326741_30221118)(FileDown:0)(Type:0)", + "x.com.samsung.da.type": "Firmware", + "x.com.samsung.da.number": "03174A22111408,03267A22102452", + "x.com.samsung.da.newVersionAvailable": "0" + }, + { + "x.com.samsung.da.id": "2", + "x.com.samsung.da.description": "Firmware_2_DB_2032244122050903042FFFFFFFFFFFFFFFFFFFFFFFFE(016520322441FFFFFFFF_30000000)(FileDown:0)(Type:0)", + "x.com.samsung.da.type": "Firmware", + "x.com.samsung.da.number": "03224A22050903,FFFFFFFFFFFFFF" + } + ] + } + }, + { + "href": "/file/information/vs/0", + "rep": { + "x.com.samsung.timeoffset": "+02:00" + } + }, + { + "href": "/washer/vs/0", + "rep": { + "x.com.samsung.da.wrinklePrevent": "Off", + "x.com.samsung.da.dryLevel": "Normal", + "x.com.samsung.da.supportedDryLevel": [ + "None", + "Damp", + "Less", + "Normal", + "More" + ], + "x.com.samsung.da.dryTime": "00:00:00", + "x.com.samsung.da.supportedDryTime": [ + "00:00:00", + "00:20:00", + "00:30:00", + "00:40:00", + "00:50:00", + "01:00:00", + "01:30:00", + "02:00:00", + "02:30:00", + "03:00:00", + "04:00:00" + ], + "x.com.samsung.da.dryerType": "Electricity" + } + }, + { + "href": "/st/dryercourse/vs/0", + "rep": { + "x.com.samsung.da.st.dryerMode": "Table_03_Course_16", + "x.com.samsung.da.st.courseTable": "Table_03" + } + }, + { + "href": "/setting/vs/0", + "rep": {} + }, + { + "href": "/wm/editcourse/vs/0", + "rep": { + "x.com.samsung.da.editCourseList": "EditCourseList_161B1D29231718191C211A1E202725244C2B", + "x.com.samsung.da.fixedCourseList": "FixedCourseList_1618202B" + } + }, + { + "href": "/wm/setinfo/vs/0", + "rep": { + "x.com.samsung.da.isModelSettingWithoutSC": "true", + "x.com.samsung.da.aiCourse": "false", + "x.com.samsung.da.isModelSettingPowerOnOff": "false", + "x.com.samsung.da.modelCode": "M(None),W(DV90BB7445GES7)" + } + }, + { + "href": "/wm/jobbeginingstatus/vs/0", + "rep": { + "x.com.samsung.da.currentStatus": "None" + } + }, + { + "href": "/wm/welcomemsg/vs/0", + "rep": {} + }, + { + "href": "/wm/personalcourse/vs/0", + "rep": { + "x.com.samsung.da.courses": [ + "F1_00", + "F2_00", + "F3_00", + "F4_00", + "F5_00", + "F6_00", + "F7_00", + "F8_00", + "F9_00", + "FA_00" + ], + "x.com.samsung.da.maxCourseNum": "10" + } + }, + { + "href": "/otninformation/vs/0", + "rep": { + "x.com.samsung.da.target": "", + "x.com.samsung.da.newVersionAvailable": "false", + "x.com.samsung.da.newVersionNo": "00000000", + "x.com.samsung.da.currentVersionInfo": "00000000", + "otnStatus": "None", + "flashingProgress": "", + "otnTarget": "main", + "otnCompleteDate": "2026-03-04", + "otnList": [ + { + "type": "WIFI", + "modelId": "DA_WM_TP1_21_COMMON", + "versions": [ + "30260118" + ], + "visVersion": "260118" + }, + { + "type": "Micom", + "modelId": "01652031744120326741", + "versions": [ + "22111408", + "22102452" + ], + "visVersion": "221114" + }, + { + "type": "Micom", + "modelId": "016520322441FFFFFFFF", + "versions": [ + "22050903", + "FFFFFFFF" + ], + "visVersion": "220509" + } + ] + } + }, + { + "href": "/buzzersound/vs/0", + "rep": { + "supportedBuzzerSound": [ + "Off", + "On" + ], + "setBuzzerSound": "Off" + } + }, + { + "href": "/remotectrl/vs/0", + "rep": { + "x.com.samsung.da.remoteControlEnabled": "false" + } + }, + { + "href": "/remotectrl/0", + "rep": { + "value": false + } + }, + { + "href": "/configuration/vs/0", + "rep": { + "x.com.samsung.da.region": "0000000000", + "x.com.samsung.da.countryCode": "CZ" + } + }, + { + "href": "/drlc/0", + "rep": { + "DRLevel": 0, + "start": "0000-00-00T00:00:00Z", + "duration": 0, + "override": false + } + }, + { + "href": "/drlc/vs/0", + "rep": { + "x.com.samsung.da.drlcLevel": "0", + "x.com.samsung.da.durationminutes": "0", + "x.com.samsung.da.start": "0000-00-00T00:00:00Z", + "x.com.samsung.da.override": "Off", + "x.com.samsung.da.realSaving": "Off" + } + }, + { + "href": "/timezone/vs/0", + "rep": { + "timezoneid": "Europe/Prague", + "offset": "+02:00", + "DST": "ON" + } + }, + { + "href": "/connectionconfig/vs/0", + "rep": { + "autoReconnectionMinVersion": "1.0", + "autoReconnection": "true", + "autoReconnectionProtocolType": [ + "helper_hotspot", + "ble_ocf" + ], + "supportedWiFiAuthType": [ + "OPEN", + "WEP", + "WPA-PSK", + "WPA2-PSK", + "SAE" + ], + "supportedWiFiCryptoType": [ + "TKIP", + "AES", + "WEP-64", + "WEP-128" + ], + "supportedWiFiFreq": [ + "2.4G" + ], + "calmConnectionCare": { + "version": "1.0", + "role": [ + "things" + ] + } + } + }, + { + "href": "/wirelessinfo/vs/0", + "rep": { + "macaddressWiFi": "**REDACTED**", + "macaddressBLE": "**REDACTED**" + } + }, + { + "href": "/quickcontrol/info/vs/0", + "rep": { + "supportedVersion": "1.0" + } + } + ] +} diff --git a/tests/fixtures/golden/dryer_tp1_21_drum_clean.json b/tests/fixtures/golden/dryer_tp1_21_drum_clean.json new file mode 100644 index 0000000..b10ac51 --- /dev/null +++ b/tests/fixtures/golden/dryer_tp1_21_drum_clean.json @@ -0,0 +1,28 @@ +{ + "state_keys": [ + "alarm_code", + "buzzer_sound", + "child_lock", + "completion_minutes", + "cycle", + "cycle_active", + "delay_start_hours", + "diagnosis", + "drum_clean_cycles_remaining", + "drum_clean_last_cleaned", + "dry_level", + "dry_time", + "dryer_type", + "energy_kwh", + "energy_saved_kwh", + "finish_time", + "firmware_update", + "job_beginning_status", + "machine_state", + "power_switch", + "progress", + "progress_percentage", + "remote_control", + "wrinkle_prevent" + ] +} diff --git a/tests/fixtures/golden/washer_dryer_combo.json b/tests/fixtures/golden/washer_dryer_combo.json index da6f20c..4fc1806 100644 --- a/tests/fixtures/golden/washer_dryer_combo.json +++ b/tests/fixtures/golden/washer_dryer_combo.json @@ -10,6 +10,7 @@ "detergent_low", "diagnosis_status", "drum_clean_cycles_remaining", + "drum_clean_last_cleaned", "dry_level", "energy_kwh", "finish_time", diff --git a/tests/fixtures/golden/washer_flexwash.json b/tests/fixtures/golden/washer_flexwash.json index e100a59..b752199 100644 --- a/tests/fixtures/golden/washer_flexwash.json +++ b/tests/fixtures/golden/washer_flexwash.json @@ -9,6 +9,7 @@ "detergent_low", "diagnosis_status", "drum_clean_cycles_remaining", + "drum_clean_last_cleaned", "energy_kwh", "finish_time", "job_beginning_status", diff --git a/tests/fixtures/golden/washer_wa55a7700av.json b/tests/fixtures/golden/washer_wa55a7700av.json index 930f097..069b0a1 100644 --- a/tests/fixtures/golden/washer_wa55a7700av.json +++ b/tests/fixtures/golden/washer_wa55a7700av.json @@ -11,6 +11,7 @@ "detergent_water_hardness", "diagnosis_status", "drum_clean_cycles_remaining", + "drum_clean_last_cleaned", "energy_kwh", "finish_time", "firmware_update", @@ -27,4 +28,4 @@ "spin_speed", "wash_temperature" ] -} \ No newline at end of file +} diff --git a/tests/fixtures/golden/washer_wa8000t.json b/tests/fixtures/golden/washer_wa8000t.json index b0e0ef6..7cfedb5 100644 --- a/tests/fixtures/golden/washer_wa8000t.json +++ b/tests/fixtures/golden/washer_wa8000t.json @@ -9,6 +9,7 @@ "detergent_low", "diagnosis_status", "drum_clean_cycles_remaining", + "drum_clean_last_cleaned", "energy_kwh", "finish_time", "firmware_update", diff --git a/tests/test_dryer_drum_clean.py b/tests/test_dryer_drum_clean.py new file mode 100644 index 0000000..bd3d7e4 --- /dev/null +++ b/tests/test_dryer_drum_clean.py @@ -0,0 +1,86 @@ +"""Tests for dryer Drum Clean+ maintenance tracking (issue #258). + +Same DrumCleanProposal_/WashingTimes_/DrumCleanLog_ options[] tokens as +washer.py's issue #9 feature, shared via laundry.drum_clean_cycles_remaining/ +drum_clean_last_cleaned -- except DrumCleanLog_ on a dryer is a '|'-joined +history of every past clean rather than one bare timestamp. +""" + +from custom_components.localthings.registry.adapter import flatten +from custom_components.localthings.registry.by_type import for_device_by_model +from custom_components.localthings.registry.capabilities.laundry import ( + drum_clean_cycles_remaining, + drum_clean_last_cleaned, +) +from custom_components.localthings.registry.discovery import discover +from tests.conftest import _load_device + + +def _state(fixture_name): + resources = _load_device(fixture_name) + info = resources["/information/vs/0"] + reg = for_device_by_model( + info["x.com.samsung.da.modelNum"], + info["x.com.samsung.da.description"], + ) + bound = discover(resources, reg.capabilities, reg.pattern_capabilities) + return flatten(bound, resources) + + +def test_no_unbound_hrefs(): + resources = _load_device("dryer_tp1_21_drum_clean") + info = resources["/information/vs/0"] + reg = for_device_by_model( + info["x.com.samsung.da.modelNum"], info["x.com.samsung.da.description"] + ) + unbound = [] + discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append) + assert unbound == [] + + +def test_confirmed_dump_reports_cycles_remaining_and_last_cleaned(): + """DA_WM_TP1_21_COMMON/DV9400B reports DrumCleanProposal_25/ + WashingTimes_25 (0 cycles remaining -- due now) and a ten-entry + '|'-joined DrumCleanLog_, most recent 2026-05-24T21:38:08.""" + state = _state("dryer_tp1_21_drum_clean") + assert state["drum_clean_cycles_remaining"] == 0 + assert str(state["drum_clean_last_cleaned"]) == "2026-05-24 21:38:08+00:00" + + +def test_dump_with_no_drum_clean_data_reports_neither_entity(): + """The default dryer fixture only has 'DrumCleanLog_Empty', no + DrumCleanProposal_/WashingTimes_ tokens at all -- both entities stay + gated off rather than reporting a fabricated 0 or an unparseable date.""" + state = _state("dryer") + assert "drum_clean_cycles_remaining" not in state + assert "drum_clean_last_cleaned" not in state + + +def test_multi_entry_drum_clean_log_takes_the_last_timestamp(): + rep = { + "x.com.samsung.da.options": [ + "DrumCleanLog_2023-12-03T22:07:18|2024-02-09T15:18:59|2026-05-24T21:38:08", + ] + } + assert str(drum_clean_last_cleaned(rep)) == "2026-05-24 21:38:08+00:00" + + +def test_single_entry_drum_clean_log_matches_washer_shape(): + """A bare, no-'|' value (the washer's own shape) parses identically.""" + rep = {"x.com.samsung.da.options": ["DrumCleanLog_2024-02-09T15:18:59"]} + assert str(drum_clean_last_cleaned(rep)) == "2024-02-09 15:18:59+00:00" + + +def test_empty_drum_clean_log_reports_none(): + rep = {"x.com.samsung.da.options": ["DrumCleanLog_Empty"]} + assert drum_clean_last_cleaned(rep) is None + + +def test_cycles_remaining_never_negative(): + rep = { + "x.com.samsung.da.options": [ + "DrumCleanProposal_25", + "WashingTimes_40", + ] + } + assert drum_clean_cycles_remaining(rep) == 0 diff --git a/tests/test_golden_regression.py b/tests/test_golden_regression.py index 3181bb9..8cfa8ac 100644 --- a/tests/test_golden_regression.py +++ b/tests/test_golden_regression.py @@ -72,7 +72,12 @@ def test_registry_reproduces_golden_state_keys_for_ehs(): def test_registry_reproduces_golden_state_keys_for_washer_wa8000t(): """Top-load washer (WA8000T, issue #106) reports no oneUiVersion and used the 'WA' consumer-model prefix, previously unmapped in - _CONSUMER_PREFIX_TO_KEY -- fell back to 'unknown'.""" + _CONSUMER_PREFIX_TO_KEY -- fell back to 'unknown'. + + Also gains drum_clean_last_cleaned (issue #258): this fixture's + DrumCleanLog_ is a '|'-joined multi-entry history, which always failed + datetime.fromisoformat and silently returned None before + laundry.drum_clean_last_cleaned learned to take the last entry.""" from tests.conftest import _load_device resources = _load_device("washer_wa8000t") @@ -270,7 +275,10 @@ def test_registry_reproduces_golden_state_keys_for_range_hood(): def test_registry_reproduces_golden_state_keys_for_washer_flexwash(): """FlexWash twin washers (WV-prefix consumer model, e.g. WV55M9600AW) report no oneUiVersion and previously fell through for_device_by_model's - consumer-prefix map entirely -- issue #19.""" + consumer-prefix map entirely -- issue #19. + + Also gains drum_clean_last_cleaned (issue #258) -- see the wa8000t test + above for why.""" from tests.conftest import _load_device resources = _load_device("washer_flexwash") @@ -285,7 +293,10 @@ def test_registry_reproduces_golden_state_keys_for_washer_flexwash(): def test_registry_reproduces_golden_state_keys_for_washer_dryer_combo(): """Washer/dryer combo units carry a writable dryLevel field on - /washer/vs/0 itself, with no separate dryer resource -- issue #22.""" + /washer/vs/0 itself, with no separate dryer resource -- issue #22. + + Also gains drum_clean_last_cleaned (issue #258) -- see the wa8000t test + above for why.""" from tests.conftest import _load_device resources = _load_device("washer_dryer_combo") @@ -622,7 +633,10 @@ def test_registry_reproduces_golden_state_keys_for_washer_wa55a7700av(): -- a different board generation than the WA8000T's TP2_20_COMMON, reached through the same 'WA' consumer-model-prefix fallback (issue #106). Binds cleanly against the existing washer registry with zero - unbound hrefs.""" + unbound hrefs. + + Also gains drum_clean_last_cleaned (issue #258) -- see the wa8000t test + above for why.""" from tests.conftest import _load_device resources = _load_device("washer_wa55a7700av") @@ -1263,6 +1277,25 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_tp1x_fac_time_ ) +def test_registry_reproduces_golden_state_keys_for_dryer_tp1_21_drum_clean(): + """DA_WM_TP1_21_COMMON/DV9400B (issue #258) is the first dryer dump with + live DrumCleanProposal_/WashingTimes_ tokens (dryer's default fixture + only ever had 'DrumCleanLog_Empty', same as the parallel washer capability + started from) -- locks in the new drum_clean_cycles_remaining/ + drum_clean_last_cleaned entities, including the dryer-specific + '|'-joined multi-timestamp DrumCleanLog_ shape.""" + from tests.conftest import _load_device + + resources = _load_device("dryer_tp1_21_drum_clean") + golden = json.loads((GOLDEN / "dryer_tp1_21_drum_clean.json").read_text()) + state_keys = _new_state_keys("dryer_tp1_21_drum_clean", resources) + assert set(state_keys) == set(golden["state_keys"]), ( + f"state_keys mismatch:\n" + f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n" + f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}" + ) + + def test_resources_from_batch_preferred_over_flat(): from tests.conftest import _resources_from_dump From 0cc9486ad52a12b5df722606d9192a41b9ac4156 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:17:41 +0000 Subject: [PATCH 05/10] Add missing dryer cycle labels for DV90DG6845LHU5 (issue #244) Codes 51 (Eco Cotton), 53 (AI Dry+), and 4e (Self Dry) were confirmed by the reporter selecting each program on the physical appliance and reading back the resulting raw course code, same table (Table_03) as the existing issue #80 confirmations. Also fixes an import-sort lint error left over in by_type/dehumidifier.py and a stale comment in dryer.py claiming codes 21/4c were still unidentified when the catalog already had them. --- .../localthings/registry/by_type/dehumidifier.py | 2 +- .../localthings/registry/capabilities/dryer.py | 12 ++++++------ custom_components/localthings/translations/cs.json | 5 ++++- custom_components/localthings/translations/en.json | 5 ++++- custom_components/localthings/translations/es.json | 5 ++++- custom_components/localthings/translations/it.json | 5 ++++- custom_components/localthings/translations/nl.json | 5 ++++- tests/test_dryer_capabilities.py | 12 ++++++++++++ 8 files changed, 39 insertions(+), 12 deletions(-) diff --git a/custom_components/localthings/registry/by_type/dehumidifier.py b/custom_components/localthings/registry/by_type/dehumidifier.py index 9230e60..ee3b041 100644 --- a/custom_components/localthings/registry/by_type/dehumidifier.py +++ b/custom_components/localthings/registry/by_type/dehumidifier.py @@ -8,7 +8,7 @@ air_purifier.py's screen-on/off resource on the identical href/shape, so that's reused too rather than re-defined. """ -from ..capabilities import airconditioner, air_purifier, common, dehumidifier, ignored +from ..capabilities import air_purifier, airconditioner, common, dehumidifier, ignored from ._base import DeviceRegistry, _build REGISTRY = DeviceRegistry( diff --git a/custom_components/localthings/registry/capabilities/dryer.py b/custom_components/localthings/registry/capabilities/dryer.py index 9887bf4..592ad79 100644 --- a/custom_components/localthings/registry/capabilities/dryer.py +++ b/custom_components/localthings/registry/capabilities/dryer.py @@ -41,14 +41,14 @@ DRYER_SETTINGS = Capability( # laundry.cycle_select (options read live from /wm/editcourse/vs/0, written as # an RMW on the options array). Course display names live in translations # under entity.select.dryer_cycle (Table_03, DV5000-class, captured -# 2026-05-29). Codes 0x21 and 0x4C appear in the issue #14 DV90BB5245AES1 -# editCourseList but aren't identified yet -- they render as the raw code -# until named. Codes '01' Normal and '06' Time dry were confirmed on a +# 2026-05-29). Codes '01' Normal and '06' Time dry were confirmed on a # DVE50A8600V/A3 (also Table_03) by selecting each cycle on the physical # appliance and reading back the raw code from the entity's state (issue -# #80). The /st/dryercourse/vs/0 resource re-encodes the same selected -# course and is ignored (ignored.py) -- the mirror of how /st/washercourse/vs/0 -# is ignored for washers. +# #80). Codes '51' Eco Cotton, '53' AI Dry+, and '4e' Self Dry were +# confirmed the same way on a DV90DG6845LHU5 (issue #244). The +# /st/dryercourse/vs/0 resource re-encodes the same selected course and is +# ignored (ignored.py) -- the mirror of how /st/washercourse/vs/0 is ignored +# for washers. # # Drum Clean+ maintenance tracking (issue #258) reuses washer.py's # DrumCleanProposal_/WashingTimes_/DrumCleanLog_ tokens on this same diff --git a/custom_components/localthings/translations/cs.json b/custom_components/localthings/translations/cs.json index 1b3b139..ca69505 100644 --- a/custom_components/localthings/translations/cs.json +++ b/custom_components/localthings/translations/cs.json @@ -325,7 +325,10 @@ "1e": "Outdoor", "1f": "Smíšená náplň", "2b": "Sušení bubnu", - "4c": "Osvěžení vzduchem" + "4c": "Osvěžení vzduchem", + "51": "Eko bavlna", + "53": "AI sušení+", + "4e": "Automatické sušení" } }, "favorite_capacity": { diff --git a/custom_components/localthings/translations/en.json b/custom_components/localthings/translations/en.json index 4f85217..0a620e2 100644 --- a/custom_components/localthings/translations/en.json +++ b/custom_components/localthings/translations/en.json @@ -325,7 +325,10 @@ "1e": "Outdoor", "1f": "Mixed load", "2b": "Self Tub Dry", - "4c": "Air Refresh" + "4c": "Air Refresh", + "51": "Eco Cotton", + "53": "AI Dry+", + "4e": "Self Dry" } }, "favorite_capacity": { diff --git a/custom_components/localthings/translations/es.json b/custom_components/localthings/translations/es.json index 7a053a7..cf5782c 100644 --- a/custom_components/localthings/translations/es.json +++ b/custom_components/localthings/translations/es.json @@ -439,7 +439,10 @@ "1e": "Exterior", "1f": "Carga mixta", "2b": "Secado de tambor", - "4c": "Renovación de aire" + "4c": "Renovación de aire", + "51": "Eco algodón", + "53": "Secado IA+", + "4e": "Autosecado" } }, "favorite_capacity": { diff --git a/custom_components/localthings/translations/it.json b/custom_components/localthings/translations/it.json index cbedc3c..6fdda18 100644 --- a/custom_components/localthings/translations/it.json +++ b/custom_components/localthings/translations/it.json @@ -325,7 +325,10 @@ "1e": "Capi outdoor", "1f": "Carico misto", "2b": "Asciugatura cestello", - "4c": "Rinfresca" + "4c": "Rinfresca", + "51": "Eco cotone", + "53": "Asciugatura AI+", + "4e": "Autoasciugatura" } }, "favorite_capacity": { diff --git a/custom_components/localthings/translations/nl.json b/custom_components/localthings/translations/nl.json index eac94b7..d2ed9a7 100644 --- a/custom_components/localthings/translations/nl.json +++ b/custom_components/localthings/translations/nl.json @@ -325,7 +325,10 @@ "1e": "Outdoor", "1f": "Gemengde was", "2b": "Zelf trommel drogen", - "4c": "Opfrissen" + "4c": "Opfrissen", + "51": "Eco katoen", + "53": "AI drogen+", + "4e": "Zelf drogen" } }, "favorite_capacity": { diff --git a/tests/test_dryer_capabilities.py b/tests/test_dryer_capabilities.py index 1791efb..cb5aa42 100644 --- a/tests/test_dryer_capabilities.py +++ b/tests/test_dryer_capabilities.py @@ -94,3 +94,15 @@ def test_st_dryercourse_is_ignored(): ignored_hrefs = {c.href for c in ignored.IGNORED} assert "/st/dryercourse/vs/0" in ignored_hrefs assert "/st/washercourse/vs/0" in ignored_hrefs + + +def test_table_03_labels_confirmed_on_dv90dg6845lhu5(): + """Issue #244: codes 51/53/4e were confirmed by selecting each program + directly on the physical DV90DG6845LHU5 and reading back the resulting + raw course code -- same verification method as issue #80's '52'/'54'/'60' + on the washer's own course table.""" + from custom_components.localthings.catalog import translated_states + + known = translated_states("select", "dryer_cycle_table_03") + for code in ("51", "53", "4e", "17", "21", "4c"): + assert code in known, code From 1becd85f6ef49abbffd5e6f05169cf1ead61f90d Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:22:06 +0000 Subject: [PATCH 06/10] Fix HOMECARE_WIZARD_V2 false-positive warning and None entity_id log (#235) HOMECARE_WIZARD_V2 appears in /mode/vs/0's supportedModes on TP2X_RAC_20K units but is a capability/option flag echoed from /configuration/vs/0's airconOptionList, not a selectable HVAC mode -- the unit's current mode never reports it. Added to a new _NON_HVAC_OPTION_CODES set that's dropped silently, so hvac_mode/hvac_modes stop tripping the issue #93 unmapped-mode warning for it on every start. Also fixes _warn_unmapped logging "None: device mode ..." during setup's first discovery pass, before the entity is added to hass and entity_id is assigned -- falls back to unique_id (set eagerly in __init__), so multiple same-type devices are distinguishable in the log. --- custom_components/localthings/climate.py | 37 +++++++++-- tests/test_climate_homecare_wizard.py | 80 ++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 tests/test_climate_homecare_wizard.py diff --git a/custom_components/localthings/climate.py b/custom_components/localthings/climate.py index d81a8e3..f095f4c 100644 --- a/custom_components/localthings/climate.py +++ b/custom_components/localthings/climate.py @@ -128,6 +128,23 @@ _HVAC_TO_DEVICE = {v: k for k, v in _DEVICE_TO_HVAC.items()} _AI_COMFORT_MODE = "AIComfort" PRESET_AI_COMFORT = "ai_comfort" +# Codes that appear in /mode/vs/0's supportedModes but are option/capability +# flags rather than selectable thermodynamic operations -- dropped silently +# (no _warn_unmapped call) rather than every owner of an affected unit +# tripping the issue #93 warning on every start. +# +# HOMECARE_WIZARD_V2 (issue #235, TP2X_RAC_20K): also appears in +# /configuration/vs/0's x.com.samsung.da.airconOptionList alongside +# PRODUCT_GLOBAL/AI_3.0/SingleCommand_1 -- clearly a capability flag, not a +# mode, on that resource. The unit's own `modes` (current mode) never +# reported it as active across the reporter's logs, only ever a real +# thermodynamic mode -- consistent with it being echoed into supportedModes +# rather than genuinely selectable. Unlike _AI_COMFORT_MODE above, it isn't +# modeled as a preset: there's no confirmation it's user-selectable at all, +# so silently dropping it (rather than guessing a write contract) is the +# 'don't guess' rule applied to a mode code instead of a resource field. +_NON_HVAC_OPTION_CODES = frozenset({"HOMECARE_WIZARD_V2"}) + # Fan (wind strength): device codes "0".."4" -> HA standard fan constants where # a clean match exists so they auto-localize; "turbo" is custom (translated). _DEVICE_TO_FAN: dict[str, str] = { @@ -363,7 +380,15 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity): def _warn_unmapped(self, href: str, code: str) -> None: """Log once per (href, code) when a device-reported mode has no entry in the relevant device<->HA map, so a real device gap surfaces - in the log instead of silently vanishing (issue #93).""" + in the log instead of silently vanishing (issue #93). + + Falls back to `unique_id` when `entity_id` is unset (issue #235): + this fires during setup's first discovery pass, before the entity is + added to hass, so `entity_id` is always None at that point -- + indistinguishable across multiple same-type devices in the log. + `unique_id` is set eagerly in `__init__` (see entity.py), so it's + always available here even though `entity_id` isn't. + """ key = (href, code) if key in self._warned_unmapped: return @@ -371,7 +396,7 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity): _LOGGER.warning( "%s: device mode %r on %s has no HA mapping and was dropped; " "please file an issue with your diagnostics dump", - self.entity_id, + self.entity_id or self.unique_id, code, href, ) @@ -472,7 +497,11 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity): device = _first(self._rep(MODE_HREF).get(_MODES_FIELD)) if device == _AI_COMFORT_MODE: return HVACMode.AUTO - if device is not None and device not in _DEVICE_TO_HVAC: + if ( + device is not None + and device not in _DEVICE_TO_HVAC + and device not in _NON_HVAC_OPTION_CODES + ): self._warn_unmapped(MODE_HREF, device) return _DEVICE_TO_HVAC.get(device, HVACMode.AUTO) @@ -480,7 +509,7 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity): def hvac_modes(self) -> list[HVACMode]: modes = [HVACMode.OFF] for m in self._supported(MODE_HREF): - if m == _AI_COMFORT_MODE: + if m == _AI_COMFORT_MODE or m in _NON_HVAC_OPTION_CODES: continue mapped = _DEVICE_TO_HVAC.get(m) if mapped is None: diff --git a/tests/test_climate_homecare_wizard.py b/tests/test_climate_homecare_wizard.py new file mode 100644 index 0000000..cd4e58d --- /dev/null +++ b/tests/test_climate_homecare_wizard.py @@ -0,0 +1,80 @@ +"""Tests for issue #235: HOMECARE_WIZARD_V2 false-positive unmapped-mode +warning, and the entity_id-is-None logging gap it surfaced. + +Uses the pre-existing airconditioner_tp2x_rac_20k fixture (issue #37) -- +its /mode/vs/0 already carries the exact reporter-confirmed shape: +supportedModes includes 'HOMECARE_WIZARD_V2' alongside the real modes, the +current mode ('Cool') never is it, and /configuration/vs/0's +airconOptionList lists it alongside other plain capability flags. +""" + +from __future__ import annotations + +import logging + +import pytest +from homeassistant.components.climate import HVACMode +from homeassistant.core import HomeAssistant + +from custom_components.localthings.climate import _NON_HVAC_OPTION_CODES, LocalThingsClimate +from custom_components.localthings.registry.entities import ClimateDesc +from tests.test_subdevice_discovery import _coordinator, _discover + + +@pytest.fixture +async def climate(hass: HomeAssistant): + coordinator = _coordinator(hass) + await _discover(coordinator, "airconditioner_tp2x_rac_20k") + bound = next(b for b in coordinator.bound if isinstance(b.desc, ClimateDesc)) + return LocalThingsClimate(coordinator, bound) + + +def test_homecare_wizard_v2_excluded_from_hvac_modes(climate): + """supportedModes is ['Auto', 'Cool', 'Dry', 'Wind', 'Heat', + 'HOMECARE_WIZARD_V2'] -- five real modes plus the option flag. Every + real mode maps through; the flag contributes no extra (or bogus) + HVACMode entry.""" + modes = climate.hvac_modes + assert set(modes) == { + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.COOL, + HVACMode.DRY, + HVACMode.HEAT, + HVACMode.FAN_ONLY, + } + + +def test_homecare_wizard_v2_reports_no_warning(climate, caplog): + """Reading hvac_modes must not emit the issue #93 unmapped-mode warning + for HOMECARE_WIZARD_V2 -- that's the actual bug report: every owner of + an affected unit saw this fire on every start.""" + with caplog.at_level(logging.WARNING): + _ = climate.hvac_modes + assert "HOMECARE_WIZARD_V2" not in caplog.text + + +def test_hvac_mode_is_off_when_powered_off(climate): + """The fixture's power is off -- hvac_mode short-circuits to OFF before + ever reading /mode/vs/0's 'Cool', matching real behavior (mode is only + meaningful while the unit is on).""" + assert climate.hvac_mode == HVACMode.OFF + + +def test_homecare_wizard_v2_is_the_known_non_hvac_set(): + assert "HOMECARE_WIZARD_V2" in _NON_HVAC_OPTION_CODES + + +def test_warn_unmapped_falls_back_to_unique_id_when_entity_id_unset(climate, caplog): + """Issue #235's secondary bug: _warn_unmapped logged 'None: device mode + ...' because entity_id is unset until the entity is added to hass -- + exactly the state a fixture-backed entity is in here, without ever + calling async_add_entities. unique_id is set eagerly in __init__ + (entity.py), so it must appear in the log line instead of a bare + 'None'.""" + assert climate.entity_id is None + assert climate.unique_id is not None + with caplog.at_level(logging.WARNING): + climate._warn_unmapped("/mode/vs/0", "TotallyUnknownCode") + assert "None:" not in caplog.text + assert climate.unique_id in caplog.text From fb5ed32b0fe31ffd88028c4f6523f4adba8ae488 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:24:59 +0000 Subject: [PATCH 07/10] Add discrete freezer setpoint support for TP1X_REF_21K (issue #229) The reporter's fridge/freezer combo reports the issue #186 discrete definite-setpoint pattern on both compartments, but only the cooler half was modeled -- /temperature/definite/freezer/vs/0 was unbound. Adds DEFINITE_TEMPERATURE_FREEZER, identical shape to the existing cooler capability (same fields, just negative supportedList values). --- .../registry/by_type/refrigerator.py | 1 + .../registry/capabilities/fridge.py | 28 ++ .../localthings/translations/cs.json | 3 + .../localthings/translations/en.json | 3 + .../localthings/translations/es.json | 3 + .../localthings/translations/it.json | 3 + .../localthings/translations/nl.json | 3 + .../refrigerator_tp1x_ref_21k_definite.json | 30 ++ ...igerator_tp1x_ref_21k_definite_device.json | 467 ++++++++++++++++++ tests/test_fridge_capabilities.py | 50 ++ tests/test_golden_regression.py | 17 + 11 files changed, 608 insertions(+) create mode 100644 tests/fixtures/golden/refrigerator_tp1x_ref_21k_definite.json create mode 100644 tests/fixtures/refrigerator_tp1x_ref_21k_definite_device.json diff --git a/custom_components/localthings/registry/by_type/refrigerator.py b/custom_components/localthings/registry/by_type/refrigerator.py index 86228b7..8e4f684 100644 --- a/custom_components/localthings/registry/by_type/refrigerator.py +++ b/custom_components/localthings/registry/by_type/refrigerator.py @@ -28,6 +28,7 @@ REGISTRY = DeviceRegistry( fridge.DEFROST_DELAY_NATIVE_DUPLICATE, fridge.DEFROST_BLOCK_STATUS, fridge.DEFINITE_TEMPERATURE_COOLER, + fridge.DEFINITE_TEMPERATURE_FREEZER, fridge.DOORS_FALLBACK, fridge.TEMPERATURES_FALLBACK, fridge.ICEMAKER_STATUS_FALLBACK, diff --git a/custom_components/localthings/registry/capabilities/fridge.py b/custom_components/localthings/registry/capabilities/fridge.py index 512f0fe..42a827d 100644 --- a/custom_components/localthings/registry/capabilities/fridge.py +++ b/custom_components/localthings/registry/capabilities/fridge.py @@ -163,6 +163,34 @@ DEFINITE_TEMPERATURE_COOLER = Capability( ), ) + +def _definite_freezer_write(p, rep, href=None): + return ( + ["temperature", "definite", "freezer", "vs", "0"], + {"x.com.samsung.da.definite.desired": p}, + ) + + +# Freezer half of the same discrete-setpoint pattern (issue #229): a +# fridge/freezer combo reporting no /temperature/current|desired/freezer +# pair, only this bundled vendor resource -- identical shape to +# DEFINITE_TEMPERATURE_COOLER above (down to the field names), just negative +# supportedList values (e.g. ['-23','-21','-19','-17','-15']). +DEFINITE_TEMPERATURE_FREEZER = Capability( + href="/temperature/definite/freezer/vs/0", + poll_tier="warm", + entities=( + SelectDesc( + key="freezer_temperature_setpoint", + field="x.com.samsung.da.definite.desired", + icon="mdi:thermometer", + entity_category="config", + options_field="x.com.samsung.da.definite.supportedList", + write_fn=_definite_freezer_write, + ), + ), +) + # --------------------------------------------------------------------------- # Icemaker nighttime quiet mode # --------------------------------------------------------------------------- diff --git a/custom_components/localthings/translations/cs.json b/custom_components/localthings/translations/cs.json index ca69505..bff44e0 100644 --- a/custom_components/localthings/translations/cs.json +++ b/custom_components/localthings/translations/cs.json @@ -643,6 +643,9 @@ }, "air_filter_pm1_threshold": { "name": "Práh alarmu PM1 filtru" + }, + "freezer_temperature_setpoint": { + "name": "Teplota mrazicí zóny" } }, "sensor": { diff --git a/custom_components/localthings/translations/en.json b/custom_components/localthings/translations/en.json index 0a620e2..2ca13af 100644 --- a/custom_components/localthings/translations/en.json +++ b/custom_components/localthings/translations/en.json @@ -643,6 +643,9 @@ }, "air_filter_pm1_threshold": { "name": "PM1 filter alarm threshold" + }, + "freezer_temperature_setpoint": { + "name": "Freezer temperature" } }, "sensor": { diff --git a/custom_components/localthings/translations/es.json b/custom_components/localthings/translations/es.json index cf5782c..902b1d6 100644 --- a/custom_components/localthings/translations/es.json +++ b/custom_components/localthings/translations/es.json @@ -757,6 +757,9 @@ }, "air_filter_pm1_threshold": { "name": "Umbral de alarma del filtro PM1" + }, + "freezer_temperature_setpoint": { + "name": "Temperatura del congelador" } }, "sensor": { diff --git a/custom_components/localthings/translations/it.json b/custom_components/localthings/translations/it.json index 6fdda18..c0a5beb 100644 --- a/custom_components/localthings/translations/it.json +++ b/custom_components/localthings/translations/it.json @@ -643,6 +643,9 @@ }, "air_filter_pm1_threshold": { "name": "Soglia di allarme del filtro PM1" + }, + "freezer_temperature_setpoint": { + "name": "Temperatura freezer" } }, "sensor": { diff --git a/custom_components/localthings/translations/nl.json b/custom_components/localthings/translations/nl.json index d2ed9a7..955bfc9 100644 --- a/custom_components/localthings/translations/nl.json +++ b/custom_components/localthings/translations/nl.json @@ -643,6 +643,9 @@ }, "air_filter_pm1_threshold": { "name": "PM1-filteralarmdrempel" + }, + "freezer_temperature_setpoint": { + "name": "Temperatuur vriesgedeelte" } }, "sensor": { diff --git a/tests/fixtures/golden/refrigerator_tp1x_ref_21k_definite.json b/tests/fixtures/golden/refrigerator_tp1x_ref_21k_definite.json new file mode 100644 index 0000000..84b6148 --- /dev/null +++ b/tests/fixtures/golden/refrigerator_tp1x_ref_21k_definite.json @@ -0,0 +1,30 @@ +{ + "state_keys": [ + "ai_energy_level", + "alarm_code", + "auto_door_opener", + "brightness_level", + "cabinet_light_dim", + "cabinet_light_switch", + "cooler_temperature_setpoint", + "day_brightness", + "door_alert", + "door_cooler_open", + "door_freezer_open", + "energy_kwh", + "energy_saved_kwh", + "firmware_update", + "flex_zone_mode", + "freezer_temperature_setpoint", + "fridge_sound", + "night_end", + "night_start", + "power_energy_kwh", + "power_watts", + "rapid_freezing", + "rapid_fridge", + "selfcheck_error", + "selfcheck_result", + "selfcheck_status" + ] +} diff --git a/tests/fixtures/refrigerator_tp1x_ref_21k_definite_device.json b/tests/fixtures/refrigerator_tp1x_ref_21k_definite_device.json new file mode 100644 index 0000000..54c2cd3 --- /dev/null +++ b/tests/fixtures/refrigerator_tp1x_ref_21k_definite_device.json @@ -0,0 +1,467 @@ +{ + "device0": [ + { + "rt": [ + "x.com.samsung.devcol", + "oic.wk.col" + ], + "if": [ + "oic.if.baseline", + "oic.if.ll", + "oic.if.b" + ] + }, + { + "href": "/alarms/vs/0", + "rep": { + "href": "/alarms/vs/0", + "rt": [ + "x.com.samsung.da.alarms" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/cabinet/light/total/vs/0", + "rep": { + "x.com.samsung.da.lightLevel": "100", + "x.com.samsung.da.lightResolution": "3", + "x.com.samsung.da.lightControl.off.include": "Off", + "x.com.samsung.da.lightControl": "Off", + "x.com.samsung.da.lightControl.hide": "true", + "light.dimming.status": "Off", + "href": "/cabinet/light/total/vs/0", + "rt": [ + "x.com.samsung.da.cabinetlight" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/settings/sound/alert/door/vs/0", + "rep": { + "alert.door": "1", + "supportedAlert.door": [ + "1", + "2", + "3", + "4" + ], + "href": "/settings/sound/alert/door/vs/0", + "rt": [ + "x.com.samsung.alert.door" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/cabinet/light/enhanced/vs/0", + "rep": { + "light.control.status": "On", + "level.brightness.daytime": "100", + "level.brightness.nighttime": "66", + "night.starttime": "2026-07-31T15:30:00", + "night.duration.minute": "540", + "timezone.offset": "+05:30", + "href": "/cabinet/light/enhanced/vs/0", + "rt": [ + "x.com.samsung.da.light.enhanced" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/configuration/vs/0", + "rep": { + "x.com.samsung.da.region": "", + "x.com.samsung.da.countryCode": "", + "href": "/configuration/vs/0" + } + }, + { + "href": "/temperature/definite/freezer/vs/0", + "rep": { + "x.com.samsung.da.definite.desired": "-19", + "x.com.samsung.da.definite.supportedList": [ + "-23", + "-21", + "-19", + "-17", + "-15" + ], + "x.com.samsung.da.unit": "Celsius", + "href": "/temperature/definite/freezer/vs/0" + } + }, + { + "href": "/temperature/definite/cooler/vs/0", + "rep": { + "x.com.samsung.da.definite.desired": "4", + "x.com.samsung.da.definite.supportedList": [ + "1", + "2", + "3", + "4", + "7" + ], + "x.com.samsung.da.unit": "Celsius", + "href": "/temperature/definite/cooler/vs/0" + } + }, + { + "href": "/door/freezer/0", + "rep": { + "openState": "Close", + "href": "/door/freezer/0", + "rt": [ + "oic.r.door" + ], + "if": [ + "oic.if.baseline", + "oic.if.s" + ] + } + }, + { + "href": "/door/cooler/0", + "rep": { + "openState": "Close", + "href": "/door/cooler/0", + "rt": [ + "oic.r.door" + ], + "if": [ + "oic.if.baseline", + "oic.if.s" + ] + } + }, + { + "href": "/doors/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.openState": "Close", + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "Door" + }, + { + "x.com.samsung.da.openState": "Close", + "x.com.samsung.da.id": "1", + "x.com.samsung.da.description": "Door" + } + ], + "href": "/doors/vs/0" + } + }, + { + "href": "/drlc/vs/0", + "rep": { + "x.com.samsung.da.drlcLevel": "0", + "x.com.samsung.da.override": "Not_Supported", + "x.com.samsung.da.durationminutes": "0", + "x.com.samsung.da.realSaving": "Off", + "href": "/drlc/vs/0" + } + }, + { + "href": "/energy/consumption/vs/0", + "rep": { + "x.com.samsung.da.cumulativeConsumption": "1274", + "x.com.samsung.da.instantaneousPower": "46", + "x.com.samsung.da.cumulativePower": "34106", + "x.com.samsung.da.cumulativeSavedPower": "0", + "x.com.samsung.da.cumulativeUnit": "Wh", + "x.com.samsung.da.instantaneousPowerUnit": "W", + "href": "/energy/consumption/vs/0", + "x.com.samsung.da.cumulativeDateUTC": "1785402720" + } + }, + { + "href": "/file/information/vs/0", + "rep": { + "x.com.samsung.timeoffset": "+05:30", + "x.com.samsung.supprtedtype": 1, + "href": "/file/information/vs/0" + } + }, + { + "href": "/refrigeration/vs/0", + "rep": { + "x.com.samsung.da.rapidFridge": "Off", + "x.com.samsung.da.rapidFreezing": "Off", + "href": "/refrigeration/vs/0", + "rt": [ + "x.com.samsung.da.fridge" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/energy/ailevel/vs/0", + "rep": { + "aiLevel": "1", + "supportedAiLevel": [ + "1", + "2" + ], + "href": "/energy/ailevel/vs/0" + } + }, + { + "href": "/information/vs/0", + "rep": { + "x.com.samsung.da.modelNum": "TP1X_REF_21K|70657341|00060123001811344100000020090400", + "x.com.samsung.da.description": "TP1X_REF_21K", + "x.com.samsung.da.serialNum": "**REDACTED**", + "x.com.samsung.da.otnDUID": "**REDACTED**", + "x.com.samsung.da.diagDumpType": "file", + "x.com.samsung.da.diagEndPoint": "SSM", + "x.com.samsung.da.diagLogType": [ + "errCode", + "dump" + ], + "x.com.samsung.da.diagMnid": "0AJT", + "x.com.samsung.da.diagSetupid": "RT1", + "x.com.samsung.da.diagProtocolType": "BLE_OCF", + "x.com.samsung.da.diagMinVersion": "3.0", + "x.com.samsung.da.diagTsId": "DA01", + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.description": "WiFi Module", + "x.com.samsung.da.type": "Software", + "x.com.samsung.da.number": "260617", + "x.com.samsung.da.newVersionAvailable": "0" + }, + { + "x.com.samsung.da.id": "1", + "x.com.samsung.da.description": "Micom", + "x.com.samsung.da.type": "Firmware", + "x.com.samsung.da.number": "2602020C, FFFFFFFF, 24062000, FFFFFFFF", + "x.com.samsung.da.newVersionAvailable": "0" + } + ], + "href": "/information/vs/0" + } + }, + { + "href": "/status/lock/vs/0", + "rep": { + "x.com.samsung.da.device.sound": "On", + "x.com.samsung.da.doorAlarmSound": "On", + "href": "/status/lock/vs/0", + "rt": [ + "x.com.samsung.da.lockstatus" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/mode/vs/0", + "rep": { + "x.com.samsung.da.modes": [ + "RVACATION_OFF", + "FCONVERT_FREEZER" + ], + "x.com.samsung.da.supportedModes": [ + "HOMECARE_WIZARD_V2", + "ENERGY_REPORT_MODEL", + "18K_REF_OUTDOOR_CONTROL_V2", + "VACATION_MODE_CONTROL" + ], + "x.com.samsung.da.supportedOptions": [ + "FCONVERT_FREEZER", + "FCONVERT_FRIDGE", + "FCONVERT_OFF" + ], + "href": "/mode/vs/0", + "rt": [ + "x.com.samsung.da.mode" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/realtimenotiforclient/vs/0", + "rep": { + "x.com.samsung.da.timeforshortnoti": "0", + "x.com.samsung.da.periodicnotisubscription": "true", + "href": "/realtimenotiforclient/vs/0" + } + }, + { + "href": "/runningmode/vs/0", + "rep": { + "x.com.samsung.da.runningMode": 0, + "href": "/runningmode/vs/0" + } + }, + { + "href": "/selfcheck/vs/0", + "rep": { + "x.com.samsung.da.supportedActions": [ + "Start" + ], + "x.com.samsung.da.status": "Ready", + "x.com.samsung.da.result": "Success", + "x.com.samsung.da.error": [ + "ErrorCode_None" + ], + "href": "/selfcheck/vs/0" + } + }, + { + "href": "/rm/control/vs/0", + "rep": { + "minPeriod": "9000", + "href": "/rm/control/vs/0" + } + }, + { + "href": "/otninformation/vs/0", + "rep": { + "x.com.samsung.da.target": "", + "x.com.samsung.da.newVersionAvailable": "false", + "otnStatus": "None", + "flashingProgress": "0", + "otnCompleteDate": "noHistory", + "scheduledTime": "None", + "swVersionInfo": { + "platform": "Tizen Lite", + "oneUiVersion": "7.0 Refrigerator", + "osVersion": "4.0" + }, + "otnList": [ + { + "type": "WIFI", + "modelId": "A-RFWW-TP1-24-T4-COM", + "versions": [ + "20260617" + ], + "visVersion": "260617" + }, + { + "type": "Micom", + "modelId": "835070657341FFFFFFFF", + "versions": [ + "2602020C", + "FFFFFFFF" + ], + "visVersion": "260202" + }, + { + "type": "Micom", + "modelId": "035070657341FFFFFFFF", + "versions": [ + "2602020C", + "FFFFFFFF" + ], + "visVersion": "260202" + }, + { + "type": "Micom", + "modelId": "035070658641FFFFFFFF", + "versions": [ + "24062000", + "FFFFFFFF" + ], + "visVersion": "240620" + } + ] + } + }, + { + "href": "/timezone/vs/0", + "rep": { + "timezoneid": "Asia/Kolkata", + "offset": "+05:30", + "DST": "OFF" + } + }, + { + "href": "/connectionconfig/vs/0", + "rep": { + "autoReconnectionMinVersion": "1.0", + "autoReconnection": "true", + "autoReconnectionProtocolType": [ + "helper_hotspot", + "ble_ocf" + ], + "supportedWiFiAuthType": [ + "OPEN", + "WEP", + "WPA-PSK", + "WPA2-PSK", + "SAE" + ], + "supportedWiFiCryptoType": [ + "TKIP", + "AES", + "WEP-64", + "WEP-128" + ], + "supportedWiFiFreq": [ + "2.4G" + ], + "calmConnectionCare": { + "version": "1.0", + "role": [ + "things" + ] + } + } + }, + { + "href": "/wirelessinfo/vs/0", + "rep": { + "macaddressWiFi": "**REDACTED**", + "macaddressBLE": "**REDACTED**", + "connectedApSsid": "IoT0" + } + }, + { + "href": "/quickcontrol/info/vs/0", + "rep": { + "supportedVersion": "1.0" + } + }, + { + "href": "/dginformation/vs/0", + "rep": { + "enrolmentstatus": "UnEnrolled", + "devicestate": "Deactivated", + "lockstatus": "Unknown", + "nextduedate": "", + "workingminutes": 0, + "paymentinfo": { + "emiplan": "Unknown", + "currency": "Unknown", + "totalemi": 0, + "totalemipaid": 0 + } + } + } + ] +} diff --git a/tests/test_fridge_capabilities.py b/tests/test_fridge_capabilities.py index 3d755ae..6ad4e4b 100644 --- a/tests/test_fridge_capabilities.py +++ b/tests/test_fridge_capabilities.py @@ -329,6 +329,56 @@ class TestDefiniteTemperatureCooler: assert rep["x.com.samsung.da.definite.supportedList"] == ["1", "2", "3", "4", "7"] +class TestDefiniteTemperatureFreezer: + """Discrete freezer setpoint (TP1X_REF_21K, issue #229) -- the same + definite-setpoint pattern as DEFINITE_TEMPERATURE_COOLER above, on a + fridge/freezer combo that reports it for *both* compartments. Identical + field shape, just negative supportedList values.""" + + def test_href(self): + assert fridge.DEFINITE_TEMPERATURE_FREEZER.href == "/temperature/definite/freezer/vs/0" + + def test_write(self): + desc = next( + e for e in fridge.DEFINITE_TEMPERATURE_FREEZER.entities if isinstance(e, SelectDesc) + ) + assert desc.write_fn is not None + result = desc.write_fn("-19", {}) + assert result is not None + path, body = result + assert path == ["temperature", "definite", "freezer", "vs", "0"] + assert body == {"x.com.samsung.da.definite.desired": "-19"} + + def test_no_unbound_hrefs_and_discrete_options(self): + from custom_components.localthings.registry.adapter import flatten + from custom_components.localthings.registry.by_type import refrigerator + from custom_components.localthings.registry.discovery import discover + from tests.conftest import _load_device + + resources = _load_device("refrigerator_tp1x_ref_21k_definite") + unbound = [] + bound = discover( + resources, + refrigerator.REGISTRY.capabilities, + refrigerator.REGISTRY.pattern_capabilities, + log=unbound.append, + ) + assert unbound == [] + + state = flatten(bound, resources) + assert state["cooler_temperature_setpoint"] == "4" + assert state["freezer_temperature_setpoint"] == "-19" + + rep = resources["/temperature/definite/freezer/vs/0"] + assert rep["x.com.samsung.da.definite.supportedList"] == [ + "-23", + "-21", + "-19", + "-17", + "-15", + ] + + class TestKimchiZone: """Kimchi-refrigerator compartments (TP2X_REF_20K-class, issue #26) -- top/middle/bottom each report an identically-shaped currentMode/ diff --git a/tests/test_golden_regression.py b/tests/test_golden_regression.py index 8cfa8ac..dafc1fb 100644 --- a/tests/test_golden_regression.py +++ b/tests/test_golden_regression.py @@ -1296,6 +1296,23 @@ def test_registry_reproduces_golden_state_keys_for_dryer_tp1_21_drum_clean(): ) +def test_registry_reproduces_golden_state_keys_for_refrigerator_tp1x_ref_21k_definite(): + """TP1X_REF_21K (issue #229) reports the discrete definite-setpoint + pattern (issue #186) on *both* compartments -- previously only the + cooler half was modeled; DEFINITE_TEMPERATURE_FREEZER covers the + freezer's identical-shape /temperature/definite/freezer/vs/0.""" + from tests.conftest import _load_device + + resources = _load_device("refrigerator_tp1x_ref_21k_definite") + golden = json.loads((GOLDEN / "refrigerator_tp1x_ref_21k_definite.json").read_text()) + state_keys = _new_state_keys("refrigerator_tp1x_ref_21k_definite", resources) + assert set(state_keys) == set(golden["state_keys"]), ( + f"state_keys mismatch:\n" + f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n" + f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}" + ) + + def test_resources_from_batch_preferred_over_flat(): from tests.conftest import _resources_from_dump From e89aa4bab5374116af049fb145c564fef8e13900 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:30:02 +0000 Subject: [PATCH 08/10] Fix transposed Normal/Express 60 dishwasher cycle labels (issue #226) '83' and '86' were swapped in the dishwasher_cycle catalog. Both the original DW9000F-class fixture this table was built from and the issue #226 reporter's board share the identical DeviceType_0812, and the original fixture's own editCourseList puts the two codes back to back (positions 4-5) -- a plausible adjacent-pair transcription slip. The reporter's live confirmation (selecting 'Normal' ran the physical Express 60 program and vice versa) settles which way: '86' is Express 60, '83' is Normal. The energy-sensor part of the same issue was already resolved per the issue thread (the device genuinely doesn't report usage, so the sensor's removal was correct) -- not touched here. --- .../localthings/registry/capabilities/dishwasher.py | 12 ++++++++++++ custom_components/localthings/translations/cs.json | 4 ++-- custom_components/localthings/translations/en.json | 4 ++-- custom_components/localthings/translations/es.json | 4 ++-- custom_components/localthings/translations/it.json | 4 ++-- custom_components/localthings/translations/nl.json | 4 ++-- tests/test_dishwasher_capabilities.py | 11 +++++++++++ 7 files changed, 33 insertions(+), 10 deletions(-) diff --git a/custom_components/localthings/registry/capabilities/dishwasher.py b/custom_components/localthings/registry/capabilities/dishwasher.py index f993f63..dff15b0 100644 --- a/custom_components/localthings/registry/capabilities/dishwasher.py +++ b/custom_components/localthings/registry/capabilities/dishwasher.py @@ -48,6 +48,18 @@ DISHWASHER_SETTINGS = Capability( # boolean-toggle contract washer's bubble-soak/pre-wash/intensive switches # use). Course display names live in translations under # entity.select.dishwasher_cycle (see laundry.cycle_select). +# +# '83'/'86' were transposed in that catalog until issue #226: both the +# original DW9000F-class fixture this table was built from and the issue +# #226 reporter's board report the identical DeviceType_0812 (a real +# per-board-generation id also seen on unrelated washer/dryer fixtures, so +# this is one shared course table, not a Table_02/Table_03-style generation +# split), and the original fixture's own live editCourseList +# ('EditCourseList_0E07908683848D808E8F') puts '86' and '83' back to back at +# positions 4-5 -- exactly the kind of adjacent pair a manual screenshot +# transcription slips on. The reporter's live confirmation (selecting +# 'Normal' ran the physical Express 60 program and vice versa) settles +# which way: '86' is Express 60, '83' is Normal. # --------------------------------------------------------------------------- CYCLE_OPTIONS = Capability( diff --git a/custom_components/localthings/translations/cs.json b/custom_components/localthings/translations/cs.json index bff44e0..f2094c1 100644 --- a/custom_components/localthings/translations/cs.json +++ b/custom_components/localthings/translations/cs.json @@ -278,9 +278,9 @@ "name": "Cyklus", "state": { "80": "Jemné", - "83": "Express 60", + "83": "Normální", "84": "Intenzivní", - "86": "Normální", + "86": "Express 60", "90": "Samočištění", "0e": "AI mytí", "07": "Předoplach", diff --git a/custom_components/localthings/translations/en.json b/custom_components/localthings/translations/en.json index 2ca13af..44ec3e7 100644 --- a/custom_components/localthings/translations/en.json +++ b/custom_components/localthings/translations/en.json @@ -278,9 +278,9 @@ "name": "Cycle", "state": { "80": "Delicate", - "83": "Express 60", + "83": "Normal", "84": "Heavy", - "86": "Normal", + "86": "Express 60", "90": "Self clean", "0e": "AI Wash", "07": "Pre blast", diff --git a/custom_components/localthings/translations/es.json b/custom_components/localthings/translations/es.json index 902b1d6..71b9e3a 100644 --- a/custom_components/localthings/translations/es.json +++ b/custom_components/localthings/translations/es.json @@ -392,9 +392,9 @@ "name": "Ciclo", "state": { "80": "Delicado", - "83": "Express 60", + "83": "Normal", "84": "Intenso", - "86": "Normal", + "86": "Express 60", "90": "Autolimpieza", "0e": "Lavado IA", "07": "Prelavado intenso", diff --git a/custom_components/localthings/translations/it.json b/custom_components/localthings/translations/it.json index c0a5beb..44383cf 100644 --- a/custom_components/localthings/translations/it.json +++ b/custom_components/localthings/translations/it.json @@ -278,9 +278,9 @@ "name": "Ciclo", "state": { "80": "Delicato", - "83": "Express 60", + "83": "Normale", "84": "Intenso", - "86": "Normale", + "86": "Express 60", "90": "Autopulizia", "0e": "AI Wash", "07": "Prelavaggio intenso", diff --git a/custom_components/localthings/translations/nl.json b/custom_components/localthings/translations/nl.json index 955bfc9..c8ea62f 100644 --- a/custom_components/localthings/translations/nl.json +++ b/custom_components/localthings/translations/nl.json @@ -278,9 +278,9 @@ "name": "Programma", "state": { "80": "Delicaat", - "83": "Express 60", + "83": "Normaal", "84": "Intensief", - "86": "Normaal", + "86": "Express 60", "90": "Zelfreiniging", "0e": "AI Wash", "07": "Voorspoelen", diff --git a/tests/test_dishwasher_capabilities.py b/tests/test_dishwasher_capabilities.py index 1e9d232..7ad5599 100644 --- a/tests/test_dishwasher_capabilities.py +++ b/tests/test_dishwasher_capabilities.py @@ -33,6 +33,17 @@ class TestCycleOptions: assert body == {"x.com.samsung.da.options": ["Course_90"]} +def test_normal_and_express_60_labels_are_not_transposed(): + """Issue #226: '83'/'86' were swapped in the shipped catalog -- '86' is + Express 60, '83' is Normal (see dishwasher.py's CYCLE_OPTIONS comment for + how this was confirmed).""" + from custom_components.localthings.catalog import _ENTITY_CATALOG + + state = _ENTITY_CATALOG["select"]["dishwasher_cycle"]["state"] + assert state["83"] == "Normal" + assert state["86"] == "Express 60" + + class TestDishwasherOptions: def test_storm_wash_read_and_write(self): desc = next( From da25d567cb571020e825c461f93ffad7b69b78d6 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:32:44 +0000 Subject: [PATCH 09/10] Remove pointless catalog-literal tests; document the anti-pattern Two tests added while triaging #244/#226 just re-asserted a translation string against the catalog entry that had been written moments earlier (dryer_cycle_table_03's '51'/'53'/'4e', dishwasher_cycle's '83'/'86'). Neither exercises any code path -- they pass by construction and only break when someone later edits the label text for wording, not when the actual code/value mapping regresses. tests/test_translations.py already holds the invariants that matter for catalog data. Documents the anti-pattern in the adding-device-support skill so future translation-only fixes don't reach for this pattern again. --- .claude/skills/adding-device-support/SKILL.md | 18 ++++++++++++++++++ tests/test_dishwasher_capabilities.py | 11 ----------- tests/test_dryer_capabilities.py | 12 ------------ 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/.claude/skills/adding-device-support/SKILL.md b/.claude/skills/adding-device-support/SKILL.md index 57078d7..1feae3f 100644 --- a/.claude/skills/adding-device-support/SKILL.md +++ b/.claude/skills/adding-device-support/SKILL.md @@ -399,6 +399,24 @@ no `[%key:...%]` resolution (that's Core build tooling). Every other language must mirror `en.json` key for key — also enforced by `tests/test_translations.py`. +**Don't write a test that just re-asserts a translation string.** Adding +labels is a data change, not a logic change, and `tests/test_translations.py` +already holds the invariants that matter for data (every descriptor has a +catalog entry, every language mirrors English key-for-key, no unresolved +`[%key:...%]`). A test that loads the catalog and asserts +`catalog["select"]["foo"]["state"]["16"] == "Cotton"` right after you just +wrote that exact line into `en.json` doesn't exercise any code path — it +re-states the JSON file in Python, passes by construction, and only ever +fails when someone *correctly* edits the label later (a wording fix, a +translator's improvement). It's not a regression test, because there's no +`select.py`/`adapter.py` logic between "the JSON says X" and "the test reads +X" for it to catch drift in. If a code/label mapping is worth locking in, +test it through the code that actually consumes it instead — a write +contract (`desc.write_fn(...)` returns the right raw code), a read contract +(`flatten()` produces the right raw value from a fixture rep), or a routing +decision — never a bare literal-string comparison against the catalog you +just edited. + ## 8. Coverage discipline: bound or ignored Every href in the dump must resolve, or the repair fires. If a resource isn't diff --git a/tests/test_dishwasher_capabilities.py b/tests/test_dishwasher_capabilities.py index 7ad5599..1e9d232 100644 --- a/tests/test_dishwasher_capabilities.py +++ b/tests/test_dishwasher_capabilities.py @@ -33,17 +33,6 @@ class TestCycleOptions: assert body == {"x.com.samsung.da.options": ["Course_90"]} -def test_normal_and_express_60_labels_are_not_transposed(): - """Issue #226: '83'/'86' were swapped in the shipped catalog -- '86' is - Express 60, '83' is Normal (see dishwasher.py's CYCLE_OPTIONS comment for - how this was confirmed).""" - from custom_components.localthings.catalog import _ENTITY_CATALOG - - state = _ENTITY_CATALOG["select"]["dishwasher_cycle"]["state"] - assert state["83"] == "Normal" - assert state["86"] == "Express 60" - - class TestDishwasherOptions: def test_storm_wash_read_and_write(self): desc = next( diff --git a/tests/test_dryer_capabilities.py b/tests/test_dryer_capabilities.py index cb5aa42..1791efb 100644 --- a/tests/test_dryer_capabilities.py +++ b/tests/test_dryer_capabilities.py @@ -94,15 +94,3 @@ def test_st_dryercourse_is_ignored(): ignored_hrefs = {c.href for c in ignored.IGNORED} assert "/st/dryercourse/vs/0" in ignored_hrefs assert "/st/washercourse/vs/0" in ignored_hrefs - - -def test_table_03_labels_confirmed_on_dv90dg6845lhu5(): - """Issue #244: codes 51/53/4e were confirmed by selecting each program - directly on the physical DV90DG6845LHU5 and reading back the resulting - raw course code -- same verification method as issue #80's '52'/'54'/'60' - on the washer's own course table.""" - from custom_components.localthings.catalog import translated_states - - known = translated_states("select", "dryer_cycle_table_03") - for code in ("51", "53", "4e", "17", "21", "4c"): - assert code in known, code From 6a6eef25b8967dbbdb1aef12cb55bae6314b506b Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Mon, 3 Aug 2026 19:38:57 +0000 Subject: [PATCH 10/10] Fix ty type error in test_dryer_drum_clean.py for_device_by_model returns DeviceRegistry | None; accessing .capabilities directly off the inline call result left the None case unnarrowed. Switched to the same reg/resources-tuple helper pattern every other by-model test file in this suite already uses, which ty resolves cleanly. --- tests/test_dryer_drum_clean.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_dryer_drum_clean.py b/tests/test_dryer_drum_clean.py index bd3d7e4..fedb8d4 100644 --- a/tests/test_dryer_drum_clean.py +++ b/tests/test_dryer_drum_clean.py @@ -16,23 +16,24 @@ from custom_components.localthings.registry.discovery import discover from tests.conftest import _load_device -def _state(fixture_name): +def _dryer(fixture_name): resources = _load_device(fixture_name) info = resources["/information/vs/0"] reg = for_device_by_model( info["x.com.samsung.da.modelNum"], info["x.com.samsung.da.description"], ) + return reg, resources + + +def _state(fixture_name): + reg, resources = _dryer(fixture_name) bound = discover(resources, reg.capabilities, reg.pattern_capabilities) return flatten(bound, resources) def test_no_unbound_hrefs(): - resources = _load_device("dryer_tp1_21_drum_clean") - info = resources["/information/vs/0"] - reg = for_device_by_model( - info["x.com.samsung.da.modelNum"], info["x.com.samsung.da.description"] - ) + reg, resources = _dryer("dryer_tp1_21_drum_clean") unbound = [] discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append) assert unbound == []