From d0c68fb5ac9ffa225055aa4007b31b67808d2e85 Mon Sep 17 00:00:00 2001 From: Petr Divis Date: Fri, 31 Jul 2026 16:02:13 +0200 Subject: [PATCH] AC: bind SmartCoolClean odor-controller option tokens; add Czech translation - airconditioner.py: odor_controller_active binary_sensor + odor_controller_progress sensor, read from the /mode/vs/0 SmartCoolClean_/ProgressSmartClean_ option tokens (SmartThings cloud custom.airConditionerOdorController capability). Read-only -- no confirmed write command. - Reporter's TP1X_DA-AC-RAC-01001_0000 dump: fan speed and WindFree preset already bind via the existing composite climate entity, no code change needed there. - en.json/nl.json: new entity names for the two additions. - cs.json: new, full Czech translation catalog (mirrors en.json topology 1:1). - New fixture + golden + tests locking in the odor-controller behavior. --- .../registry/capabilities/airconditioner.py | 38 + .../localthings/translations/cs.json | 1215 +++++++++++++++++ .../localthings/translations/en.json | 6 + .../localthings/translations/nl.json | 6 + ...ioner_tp1x_rac_odor_controller_device.json | 575 ++++++++ ...rconditioner_tp1x_rac_odor_controller.json | 26 + tests/test_airconditioner_capabilities.py | 80 ++ tests/test_golden_regression.py | 21 + 8 files changed, 1967 insertions(+) create mode 100644 custom_components/localthings/translations/cs.json create mode 100644 tests/fixtures/airconditioner_tp1x_rac_odor_controller_device.json create mode 100644 tests/fixtures/golden/airconditioner_tp1x_rac_odor_controller.json diff --git a/custom_components/localthings/registry/capabilities/airconditioner.py b/custom_components/localthings/registry/capabilities/airconditioner.py index b753039..1abb750 100644 --- a/custom_components/localthings/registry/capabilities/airconditioner.py +++ b/custom_components/localthings/registry/capabilities/airconditioner.py @@ -380,6 +380,29 @@ def _option_number_write(prefix): return write +def _odor_controller_active(rep): + """Odor-controller self-clean on/off, from the `SmartCoolClean_` + /mode/vs/0 option token -- corroborated against the SmartThings cloud + custom.airConditionerOdorController capability's airConditionerOdorController + State field, same on/off vocabulary and a matching name ("Smart Cool + Clean" mirrors "odor controller"). Unconditional across board + generations, same as beep above -- no evidence ties this token to the + legacy-vs-newer split _has_option_token gates on. Read-only: no command + capability is confirmed, so this stays a sensor rather than a guessed + write (the 'don't guess' rule).""" + tok = _option_token(rep, 'SmartCoolClean') + if tok is None: + return None + return tok == 'On' + + +def _odor_controller_progress(rep): + """0-100 progress of the odor-controller self-clean cycle, from the + `ProgressSmartClean_` option token -- mirrors the cloud + airConditionerOdorControllerProgress field.""" + return _int(_option_token(rep, 'ProgressSmartClean')) + + def _humidity(rep): """Relative humidity, preferring the 5%-rounded field where it exists. @@ -582,6 +605,21 @@ CLIMATE = Capability( exists_fn=_has_option_token('FilterTime'), device_class='duration', unit='h', state_class='measurement', icon='mdi:air-filter'), + # Odor-controller ("Smart Cool Clean") state + progress -- see + # _odor_controller_active's docstring for the cloud-capability + # correspondence. Present on TP1X_DA-AC-RAC-01001 (issue reporter's + # dump); gating is token presence only, not board generation. + BinarySensorDesc(key='odor_controller_active', + rep_fn=_odor_controller_active, + exists_fn=lambda rep, resources: ( + _option_token(rep, 'SmartCoolClean') is not None), + icon='mdi:air-filter', entity_category='diagnostic'), + SensorDesc(key='odor_controller_progress', + rep_fn=_odor_controller_progress, + exists_fn=lambda rep, resources: ( + _option_token(rep, 'ProgressSmartClean') is not None), + unit='%', state_class='measurement', + icon='mdi:progress-check', entity_category='diagnostic'), ), ) diff --git a/custom_components/localthings/translations/cs.json b/custom_components/localthings/translations/cs.json new file mode 100644 index 0000000..6e49f8f --- /dev/null +++ b/custom_components/localthings/translations/cs.json @@ -0,0 +1,1215 @@ +{ + "entity": { + "binary_sensor": { + "after_run_active": { + "name": "Dosoušení aktivní" + }, + "any_burner_active": { + "name": "Hořák aktivní" + }, + "automatic_operation": { + "name": "Automatický provoz" + }, + "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" + }, + "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" + } + }, + "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", + "18": "Syntetika", + "19": "Jemné prádlo", + "20": "Žehlení", + "23": "Rychlé sušení 35", + "24": "Studený vzduch", + "25": "Teplý vzduch", + "27": "Časové sušení", + "1a": "Vlna", + "1b": "Ložní prádlo", + "1c": "Košile", + "1d": "Ručníky", + "1e": "Outdoor", + "1f": "Smíšená náplň" + } + }, + "favorite_capacity": { + "name": "Oblíbené množství" + }, + "favorite_hotwater_temperature": { + "name": "Oblíbená teplota horké vody" + }, + "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ě" + } + } + }, + "sensor": { + "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_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í" + }, + "burner_state": { + "name": "Stav hořáku {number}" + }, + "clean_level": { + "name": "Úroveň čištění" + }, + "coffee_brew_status": { + "name": "Stav přípravy kávy" + }, + "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" + } + } + }, + "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" + }, + "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í" + } + }, + "time": { + "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í" + } + } + }, + "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é" + } + }, + "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 5d718da..61cb4e2 100644 --- a/custom_components/localthings/translations/en.json +++ b/custom_components/localthings/translations/en.json @@ -34,6 +34,9 @@ "overload_protection_active": { "name": "Overload protection active" }, + "odor_controller_active": { + "name": "Odor controller active" + }, "cycle_active": { "name": "Running" }, @@ -646,6 +649,9 @@ "outdoor_temperature": { "name": "Outdoor temperature" }, + "odor_controller_progress": { + "name": "Odor controller progress" + }, "panel_status": { "name": "Panel status" }, diff --git a/custom_components/localthings/translations/nl.json b/custom_components/localthings/translations/nl.json index f6d59f9..c01178d 100644 --- a/custom_components/localthings/translations/nl.json +++ b/custom_components/localthings/translations/nl.json @@ -34,6 +34,9 @@ "overload_protection_active": { "name": "Overbelastingsbeveiliging actief" }, + "odor_controller_active": { + "name": "Geurbeheersing actief" + }, "cycle_active": { "name": "Actief" }, @@ -646,6 +649,9 @@ "outdoor_temperature": { "name": "Buitentemperatuur" }, + "odor_controller_progress": { + "name": "Voortgang geurbeheersing" + }, "panel_status": { "name": "Paneelstatus" }, diff --git a/tests/fixtures/airconditioner_tp1x_rac_odor_controller_device.json b/tests/fixtures/airconditioner_tp1x_rac_odor_controller_device.json new file mode 100644 index 0000000..7015e02 --- /dev/null +++ b/tests/fixtures/airconditioner_tp1x_rac_odor_controller_device.json @@ -0,0 +1,575 @@ +{ + "device0": [ + {}, + { + "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": "/alarms/vs/0", + "rep": { + "x.com.samsung.da.items": [ + { + "x.com.samsung.da.id": "0", + "x.com.samsung.da.alarmType": "Device", + "x.com.samsung.da.code": "ErrorCode_OFF", + "x.com.samsung.da.triggeredTime": "2026-07-31T12:54:20", + "x.com.samsung.da.state": "Deleted" + }, + { + "x.com.samsung.da.id": "1", + "x.com.samsung.da.alarmType": "Device", + "x.com.samsung.da.code": "FilterAlarm_OFF", + "x.com.samsung.da.triggeredTime": "2026-07-31T12:54:20", + "x.com.samsung.da.state": "Deleted" + } + ], + "rt": [ + "x.com.samsung.da.alarms" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/availablecontrolsets/vs/0", + "rep": { + "x.com.samsung.da.sets": "AE3F00A0012C01610248000F0000", + "x.com.samsung.da.id": "RAC", + "x.com.samsung.da.version": "1.0" + } + }, + { + "href": "/configuration/vs/0", + "rep": { + "x.com.samsung.da.region": "0000000000", + "x.com.samsung.da.airconOptionList": [ + "PRODUCT_GLOBAL", + "AI_GLOBAL_HEATPUMP_4.0", + "AI_3.0", + "Auto_To_AI", + "AI_Heat", + "ENERGY_2.0", + "HOMECARE_WIZARD_V2", + "DR", + "SingleCommand_1" + ] + } + }, + { + "href": "/da/softreset/vs/0", + "rep": { + "x.com.samsung.da.softwarereset": "false" + } + }, + { + "href": "/drlc/vs/0", + "rep": { + "x.com.samsung.da.start": "1970-01-01T00:00:00Z", + "x.com.samsung.da.override": "Off", + "x.com.samsung.da.durationminutes": "0", + "x.com.samsung.da.drlcLevel": "0" + } + }, + { + "href": "/electriccurrent/vs/0", + "rep": { + "operation": "Off", + "modes": "3", + "supportedModes": [ + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ] + } + }, + { + "href": "/energy/consumption/vs/0", + "rep": { + "x.com.samsung.da.cumulativeDate": "1785502435", + "x.com.samsung.da.cumulativeUnit": "Wh", + "x.com.samsung.da.instantaneousPowerUnit": "W" + } + }, + { + "href": "/file/information/vs/0", + "rep": { + "x.com.samsung.timeoffset": "+02:00", + "x.com.samsung.supprtedtype": 1 + } + }, + { + "href": "/filter/airdustfilter/vs/0", + "rep": { + "x.com.samsung.da.filterUsageResolution": "1", + "x.com.samsung.da.filterCapacity": "500", + "x.com.samsung.da.filterCapacityUnit": "Hour", + "x.com.samsung.da.filterResetType": [ + "replaceable", + "washable" + ], + "x.com.samsung.da.filterStatus": "normal", + "x.com.samsung.da.filterUsage": "44", + "x.com.samsung.da.filterDesiredUsage": "500", + "x.com.samsung.da.supportedFilterDesiredUsage": [ + "180", + "300", + "500", + "700" + ] + } + }, + { + "href": "/humidity/vs/0", + "rep": { + "x.com.samsung.da.humidity": "0", + "x.com.samsung.da.fivepercentHumidity": "53" + } + }, + { + "href": "/information/vs/0", + "rep": { + "x.com.samsung.da.otnDUID": "**REDACTED**", + "x.com.samsung.da.modelNum": "TP1X_DA-AC-RAC-01001_0000|10267841|60010514001911010E00492200902000", + "x.com.samsung.da.description": "TP1X_DA-AC-RAC-01001_0000", + "x.com.samsung.da.serialNum": "**REDACTED**", + "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": "02762A260401", + "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.newVersionAvailable": "0", + "x.com.samsung.da.number": "02678A24092500,FFFFFFFFFFFFFF" + }, + { + "x.com.samsung.da.id": "2", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Firmware", + "x.com.samsung.da.newVersionAvailable": "0", + "x.com.samsung.da.number": "FFFFFFFFFFFFFF,FFFFFFFFFFFFFF" + }, + { + "x.com.samsung.da.id": "3", + "x.com.samsung.da.description": "Version", + "x.com.samsung.da.type": "Firmware", + "x.com.samsung.da.newVersionAvailable": "0", + "x.com.samsung.da.number": "FFFFFFFFFFFFFF,FFFFFFFFFFFFFF" + } + ], + "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": "AR2", + "x.com.samsung.da.diagProtocolType": "BLE_OCF", + "x.com.samsung.da.diagMinVersion": "3.0", + "x.com.samsung.da.diagTsId": "DA01" + } + }, + { + "href": "/keepnormalstate/vs/0", + "rep": { + "x.com.samsung.da.keepnormal": 1 + } + }, + { + "href": "/light/vs/0", + "rep": { + "mode": "On", + "supportedModes": [ + "On", + "Off" + ] + } + }, + { + "href": "/mds/absencemonitoring/vs/0", + "rep": {} + }, + { + "href": "/mds/absencestate/vs/0", + "rep": {} + }, + { + "href": "/mode/convenient/vs/0", + "rep": { + "x.com.samsung.da.modes": "Off", + "x.com.samsung.da.supportedModes": [ + "Off", + "Sleep", + "Quiet", + "Speed", + "Nano", + "NanoSleep" + ] + } + }, + { + "href": "/mode/vs/0", + "rep": { + "x.com.samsung.da.modes": [ + "Cool" + ], + "workingMode": "Cool", + "x.com.samsung.da.supportedModes": [ + "Auto", + "Cool", + "Dry", + "Fan", + "Heat" + ], + "x.com.samsung.da.options": [ + "OptionCode_35880", + "Sleep_0", + "ArtificialWorking_Off", + "ComfortAICooling_Off", + "AiTempChanged_Off", + "AiTemp_240", + "Volume_Mute", + "StopAutoClean_Idle", + "SmartCoolClean_Off", + "ProgressSmartClean_0", + "FreezeAlarmSetting_Off", + "DesiredFreezeAlarm_240", + "WashAlarm_Off", + "ExtendOptionCode_215693", + "UpdateAllow_NotAllowed", + "DurationOn_240", + "OutdoorTemp_99", + "CoolCapa_20", + "WarmCapa_22", + "Autoclean_On", + "WelcomeCoolingState_Off", + "WelcomeHeatingState_Off", + "OutdoorConnection_Connected" + ], + "rt": [ + "x.com.samsung.da.mode" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/option/airpurify/vs/0", + "rep": { + "x.com.samsung.da.modes": "Off", + "x.com.samsung.da.supportedModes": [ + "On", + "Off" + ], + "rt": [ + "x.com.samsung.da.option.airpurify" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/option/autoclean/vs/0", + "rep": { + "x.com.samsung.da.status": "Stop", + "x.com.samsung.da.supportedStatus": [ + "Start", + "Stop" + ], + "x.com.samsung.da.progress": "0", + "x.com.samsung.da.settingStatus": "On", + "x.com.samsung.da.supportedSettingStatus": [ + "On", + "Off" + ] + } + }, + { + "href": "/option/muteonce/vs/0", + "rep": { + "muteonce": "Off", + "rt": [ + "x.com.samsung.da.option.muteonce" + ], + "if": [ + "oic.if.baseline", + "oic.if.a" + ] + } + }, + { + "href": "/personality/presence/vs/0", + "rep": {} + }, + { + "href": "/power/vs/0", + "rep": { + "x.com.samsung.da.power": "On", + "operationNumber": "4" + } + }, + { + "href": "/realtimenotiforclient/vs/0", + "rep": { + "x.com.samsung.da.timeforshortnoti": "0", + "x.com.samsung.da.longnotisubscription": "false", + "x.com.samsung.da.periodicnotisubscription": "true" + } + }, + { + "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": "/reserverulesets/vs/0", + "rep": { + "x.com.samsung.da.sets": "AEFFFFFFFF101E121EFFFF101EFFFF000001001F00010000001F0000009C00FFFF1E00", + "x.com.samsung.da.id": "RAC", + "x.com.samsung.da.version": "1.0" + } + }, + { + "href": "/selfcheck/vs/0", + "rep": { + "x.com.samsung.da.supportedActions": [ + "Start", + "Cancel" + ], + "x.com.samsung.da.error": [ + "DA_ERROR_NONE" + ] + } + }, + { + "href": "/temperature/control/vs/0", + "rep": { + "x.com.samsung.da.increment": "1" + } + }, + { + "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.current": "24.0", + "x.com.samsung.da.desired": "24.0", + "x.com.samsung.da.minimum": "16", + "x.com.samsung.da.maximum": "30", + "x.com.samsung.da.increment": "1", + "x.com.samsung.da.unit": "Celsius" + } + ] + } + }, + { + "href": "/welcome/temperature/vs/0", + "rep": { + "operatingStatus": "None", + "requestId": "0000" + } + }, + { + "href": "/wind/direction/vs/0", + "rep": { + "x.com.samsung.da.modes": "All", + "x.com.samsung.da.supportedModes": [ + "Fix", + "Up_And_Low", + "Left_And_Right", + "All" + ] + } + }, + { + "href": "/wind/strength/vs/0", + "rep": { + "x.com.samsung.da.modes": "3", + "x.com.samsung.da.supportedModes": [ + "0", + "1", + "2", + "3", + "4" + ], + "x.com.samsung.da.modesName": [ + "Auto", + "Low", + "Mid", + "High", + "Turbo" + ] + } + }, + { + "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 Air conditioner", + "osVersion": "4.0" + }, + "otnList": [ + { + "type": "WIFI", + "modelId": "ARA-KR-TP1-25-ARXX00", + "versions": [ + "11260401" + ], + "visVersion": "260401" + }, + { + "type": "Micom", + "modelId": "845210267841FFFFFFFF", + "versions": [ + "24092500", + "FFFFFFFF" + ], + "visVersion": "240925" + }, + { + "type": "Micom", + "modelId": "045210267841FFFFFFFF", + "versions": [ + "24092500", + "FFFFFFFF" + ], + "visVersion": "240925" + }, + { + "type": "Micom", + "modelId": "FGCAN2", + "versions": [ + "", + "" + ], + "visVersion": "999999" + }, + { + "type": "Micom", + "modelId": "FGCAN3", + "versions": [ + "", + "" + ], + "visVersion": "999999" + } + ] + } + }, + { + "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**", + "connectedApSsid": "REDACTED_SSID" + } + }, + { + "href": "/quickcontrol/info/vs/0", + "rep": { + "supportedVersion": "1.0" + } + }, + { + "href": "/dginformation/vs/0", + "rep": { + "enrolmentstatus": "Unknown", + "devicestate": "Unknown", + "lockstatus": "Unknown", + "nextduedate": "", + "workingminutes": 0, + "paymentinfo": { + "emiplan": "Unknown", + "currency": "Unknown", + "totalemi": 0, + "totalemipaid": 0 + } + } + } + ] +} diff --git a/tests/fixtures/golden/airconditioner_tp1x_rac_odor_controller.json b/tests/fixtures/golden/airconditioner_tp1x_rac_odor_controller.json new file mode 100644 index 0000000..055f6b3 --- /dev/null +++ b/tests/fixtures/golden/airconditioner_tp1x_rac_odor_controller.json @@ -0,0 +1,26 @@ +{ + "state_keys": [ + "air_filter_status", + "air_filter_threshold", + "air_filter_usage", + "air_filter_usage_hours", + "air_purify", + "alarm_code", + "auto_clean", + "beep", + "climate", + "current_limit_enabled", + "current_limit_level", + "current_temperature_c", + "display_light", + "firmware_update", + "humidity", + "mute_once", + "odor_controller_active", + "odor_controller_progress", + "selfcheck_error", + "selfcheck_result", + "selfcheck_status", + "tropical_night_mode" + ] +} diff --git a/tests/test_airconditioner_capabilities.py b/tests/test_airconditioner_capabilities.py index e97bb25..57d7161 100644 --- a/tests/test_airconditioner_capabilities.py +++ b/tests/test_airconditioner_capabilities.py @@ -872,3 +872,83 @@ def test_non_legacy_board_energy_kwh_still_uses_plain_wh_scale(): state = flatten( discover(resources, reg.capabilities, reg.pattern_capabilities), resources) assert state['energy_kwh'] == round(1686632 / 1000.0, 2) + + +# --------------------------------------------------------------------------- +# TP1X_DA-AC-RAC-01001_0000, reporter's dump. Reported "fan and WindFree are +# missing" -- both already work: /wind/strength/vs/0's 0-4 codes match +# _DEVICE_TO_FAN exactly, and /mode/convenient/vs/0's Nano/NanoSleep codes +# already resolve dynamically via _preset_to_ha + the existing "nano"/ +# "nanosleep" -> "WindFree"/"WindFree sleep" translation labels -- confirmed +# below by zero unbound hrefs and the climate entity binding with its usual +# FAN_MODE/PRESET_MODE features. The real, previously-uncaptured gap was +# /mode/vs/0's SmartCoolClean_/ProgressSmartClean_ option tokens (the cloud +# custom.airConditionerOdorController capability) -- see +# _odor_controller_active's docstring. +# --------------------------------------------------------------------------- + +def _ac_odor_controller(): + return _resolve('airconditioner_tp1x_rac_odor_controller') + + +def test_odor_controller_no_unbound_hrefs(): + reg, resources = _ac_odor_controller() + unbound = [] + discover(resources, reg.capabilities, reg.pattern_capabilities, log=unbound.append) + assert unbound == [] + + +def test_odor_controller_fan_and_windfree_already_bind(): + """The actually-reported gap wasn't real: fan speed and the WindFree + preset both come from the composite climate entity, which is bound here + with its normal fan/preset feature set -- no code change needed for + either.""" + reg, resources = _ac_odor_controller() + bound = discover(resources, reg.capabilities, reg.pattern_capabilities) + climate = [b for b in bound if isinstance(b.desc, ClimateDesc)] + assert len(climate) == 1 and climate[0].href == '/mode/vs/0' + assert resources['/wind/strength/vs/0']['x.com.samsung.da.supportedModes'] == [ + '0', '1', '2', '3', '4'] + assert resources['/mode/convenient/vs/0']['x.com.samsung.da.supportedModes'] == [ + 'Off', 'Sleep', 'Quiet', 'Speed', 'Nano', 'NanoSleep'] + + +def test_odor_controller_state_and_progress_present(): + reg, resources = _ac_odor_controller() + state = flatten( + discover(resources, reg.capabilities, reg.pattern_capabilities), resources) + assert state['odor_controller_active'] is False # SmartCoolClean_Off + assert state['odor_controller_progress'] == 0 # ProgressSmartClean_0 + + +def test_odor_controller_read_from_option_tokens(): + assert airconditioner._odor_controller_active( + {'x.com.samsung.da.options': ['SmartCoolClean_On']}) is True + assert airconditioner._odor_controller_active( + {'x.com.samsung.da.options': ['SmartCoolClean_Off']}) is False + assert airconditioner._odor_controller_active( + {'x.com.samsung.da.options': ['Volume_100']}) is None + assert airconditioner._odor_controller_progress( + {'x.com.samsung.da.options': ['ProgressSmartClean_42']}) == 42 + assert airconditioner._odor_controller_progress({}) is None + + +def test_odor_controller_absent_when_no_smartcoolclean_token(): + """The original issue #17 dump's /mode/vs/0 options carry no + SmartCoolClean_/ProgressSmartClean_ tokens -- neither entity binds.""" + reg, resources = _ac() + state = flatten( + discover(resources, reg.capabilities, reg.pattern_capabilities), resources) + assert 'odor_controller_active' not in state + assert 'odor_controller_progress' not in state + + +def test_odor_controller_is_read_only(): + """No command capability is confirmed for SmartCoolClean -- exposed + read-only, same 'don't guess' precedent as CURRENT_LIMIT/ANOMALY_LOAD.""" + active = next(e for e in airconditioner.CLIMATE.entities + if e.key == 'odor_controller_active') + progress = next(e for e in airconditioner.CLIMATE.entities + if e.key == 'odor_controller_progress') + assert not hasattr(active, 'write_fn') or active.write_fn is None + assert not hasattr(progress, 'write_fn') or progress.write_fn is None diff --git a/tests/test_golden_regression.py b/tests/test_golden_regression.py index 0815283..351545f 100644 --- a/tests/test_golden_regression.py +++ b/tests/test_golden_regression.py @@ -810,6 +810,27 @@ def test_registry_reproduces_golden_state_keys_for_airconditioner_tp1x_rac_01001 ) +def test_registry_reproduces_golden_state_keys_for_airconditioner_tp1x_rac_odor_controller(): + """TP1X_DA-AC-RAC-01001_0000 -- reporter's dump: fan (0-4 wind-strength + scale) and WindFree (Nano/NanoSleep convenient-mode codes) already bind + via the composite climate entity, no code change needed there. The + genuine gap was /mode/vs/0's SmartCoolClean_/ProgressSmartClean_ option + tokens (the cloud custom.airConditionerOdorController capability), + previously unbound to any entity -- now odor_controller_active/ + odor_controller_progress.""" + from tests.conftest import _load_device + resources = _load_device('airconditioner_tp1x_rac_odor_controller') + golden = json.loads( + (GOLDEN / 'airconditioner_tp1x_rac_odor_controller.json').read_text() + ) + state_keys = _new_state_keys('airconditioner_tp1x_rac_odor_controller', 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_registry_reproduces_golden_state_keys_for_air_dresser(): """DA_DF_A51_20_COMMON AirDresser (model DF8600T, issue #162) -- reports no oneUiVersion; resolved via the '_DF_' modelNum token fallback. Has no