From ae63dfa8089aa7afd39920e0ed0614b00f1e0bda Mon Sep 17 00:00:00 2001 From: Revilo91 Date: Sat, 27 Dec 2025 17:31:04 +0100 Subject: [PATCH 01/11] Add support for scenarios and control modes, improve error handling, and update the availability of the air conditioner. --- custom_components/comfoclime/climate.py | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) mode change 100644 => 100755 custom_components/comfoclime/climate.py diff --git a/custom_components/comfoclime/climate.py b/custom_components/comfoclime/climate.py old mode 100644 new mode 100755 index 2e56e6f..9bfe62b --- a/custom_components/comfoclime/climate.py +++ b/custom_components/comfoclime/climate.py @@ -8,6 +8,7 @@ FAN_LOW, FAN_MEDIUM, FAN_OFF, + PRESET_AWAY, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO, @@ -47,6 +48,33 @@ # Add manual preset mode (status=0) PRESET_MANUAL = PRESET_NONE # "none" preset means manual temperature control +# Scenario Modes - special operating modes +SCENARIO_COOKING = 4 # Kochen - 30 minutes high ventilation +SCENARIO_PARTY = 5 # Party - 30 minutes high ventilation +SCENARIO_HOLIDAY = 7 # Urlaub - 24 hours reduced mode +SCENARIO_BOOST_MODE = 8 # Boost - 30 minutes maximum power + +PRESET_SCENARIO_COOKING = "cooking" +PRESET_SCENARIO_PARTY = "party" + +# Scenario mapping +SCENARIO_MAPPING = { + SCENARIO_COOKING: PRESET_SCENARIO_COOKING, + SCENARIO_PARTY: PRESET_SCENARIO_PARTY, + SCENARIO_HOLIDAY: PRESET_AWAY, + SCENARIO_BOOST_MODE: PRESET_BOOST, +} + +SCENARIO_REVERSE_MAPPING = {v: k for k, v in SCENARIO_MAPPING.items()} + +# Default durations for scenarios in seconds (based on Mode_info.json) +SCENARIO_DEFAULT_DURATIONS = { + SCENARIO_COOKING: 30, + SCENARIO_PARTY: 30, + SCENARIO_HOLIDAY: 1440, # 24 hours + SCENARIO_BOOST_MODE: 30, +} + # Fan Mode Mapping (based on fan.py implementation) # fanSpeed from dashboard: 0, 1, 2, 3 FAN_MODE_MAPPING = { @@ -464,6 +492,9 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: Setting PRESET_MANUAL (none) switches to manual temperature control mode. Setting other presets (comfort/boost/eco) activates automatic mode with both seasonProfile and temperatureProfile set to the selected preset value. + + Args: + preset_mode: The preset mode to activate """ try: # Manual mode: User wants to use manual temperature control @@ -540,6 +571,7 @@ def extra_state_attributes(self) -> dict[str, Any]: Exposes all available data from the ComfoClime Dashboard API interface: - Dashboard data from /system/{UUID}/dashboard + - Scenario time left (remaining duration of active scenario in seconds) """ attrs = {} @@ -547,6 +579,24 @@ def extra_state_attributes(self) -> dict[str, Any]: if self.coordinator.data: attrs["dashboard"] = self.coordinator.data + # Add scenario time left as a separate attribute for easier access + scenario_time_left = self.coordinator.data.get("scenarioTimeLeft") + if scenario_time_left is not None: + attrs["scenario_time_left"] = scenario_time_left + # Convert to human-readable format + hours, remainder = divmod(scenario_time_left, 3600) + minutes, seconds = divmod(remainder, 60) + if hours > 0: + attrs["scenario_time_left_formatted"] = ( + f"{int(hours)}h {int(minutes)}m" + ) + elif minutes > 0: + attrs["scenario_time_left_formatted"] = ( + f"{int(minutes)}m {int(seconds)}s" + ) + else: + attrs["scenario_time_left_formatted"] = f"{int(seconds)}s" + # For transparency: expose last_manual_temperature from thermal profile if available tp = getattr(self._thermalprofile_coordinator, "data", None) or {} manual_temp = (tp.get("temperature") or {}).get("manualTemperature") From e9a53d394d981ffba207c3d36d5a3355bf542392 Mon Sep 17 00:00:00 2001 From: Revilo91 Date: Sat, 27 Dec 2025 17:37:31 +0100 Subject: [PATCH 02/11] formating --- .../comfoclime/translations/de.json | 492 ++++++++--------- .../comfoclime/translations/en.json | 509 +++++++++--------- 2 files changed, 505 insertions(+), 496 deletions(-) mode change 100644 => 100755 custom_components/comfoclime/translations/de.json diff --git a/custom_components/comfoclime/translations/de.json b/custom_components/comfoclime/translations/de.json old mode 100644 new mode 100755 index 44e10c4..725b799 --- a/custom_components/comfoclime/translations/de.json +++ b/custom_components/comfoclime/translations/de.json @@ -1,282 +1,282 @@ { "entity": { - "sensor": { - "indoor_temperature": { - "name": "Innentemperatur" - }, - "outdoor_temperature": { - "name": "Außentemperatur" - }, - "exhaust_temperature": { - "name": "Fortluft Temperatur" - }, - "extract_humidity": { - "name": "Abluft Feuchtigkeit" - }, - "exhaust_humidity": { - "name": "Fortluft Feuchtigkeit" - }, - "outdoor_humidity": { - "name": "Außenluft Feuchtigkeit" - }, - "supply_humidity": { - "name": "Zuluft Feuchtigkeit" - }, - "fan_speed": { - "name": "Lüfterstufe" - }, - "supply_air_temperature": { - "name": "Zulufttemperatur" - }, - "supply_coil_temperature": { - "name": "Zuluft Gastemperatur" - }, - "exhaust_coil_temperature": { - "name": "Abluft Gastemperatur" - }, - "tpma_temperature": { - "name": "Mittlere Außentemperatur" - }, - "current_comfort_temperature": { - "name": "Aktuelle Zieltemperatur" - }, - "power_heatpump": { - "name": "Wärmepumpe Leistungsaufnahme" - }, - "power_ventilation": { - "name": "Lüftung Leistungsaufnahme" - }, - "bypass_state": { - "name": "Bypass Zustand" - }, - "exhaust_fan_duty": { - "name": "Abluft Lüfter Ansteuerung" - }, - "supply_fan_duty": { - "name": "Zuluft Lüfter Ansteuerung" - }, - "exhaust_fan_speed": { - "name": "Abluft Lüfter Drehzahl" - }, - "supply_fan_speed": { - "name": "Zuluft Lüfter Drehzahl" - }, - "exhaust_air_flow": { - "name": "Abluft Volumenstrom" - }, - "supply_air_flow": { - "name": "Zuluft Volumenstrom" - }, - "energy_ytd": { - "name": "Lüftung Energie Jahr" - }, - "energy_total": { - "name": "Lüftung Energie gesamt" - }, - "ventilation_disbalance": { - "name": "Lüftung Ungleichgewicht" - }, - "heat_pump_status": { - "name": "Wärmepumpe Status" - }, - "season": { - "name": "Jahreszeit", - "state": { - "heating": "Heizperiode", - "cooling": "Kühlperiode", - "transitional": "Übergangszeit" - } - }, - "temperature_profile_status": { - "name": "Aktives Temperaturprofil", - "state": { - "comfort": "Komfort", - "eco": "Eco", - "power": "Power" - } - }, - "temp_rmot": { - "name": "Lüftung Mittlere Außentemperatur (RMOT)" - }, - "device_mode_status": { - "name": "Aktueller Betriebsmodus", - "state": { - "0": "Aus", - "1": "Heizen", - "2": "Kühlen" - } - }, - "schedule_status": { - "name": "Aktiver Zeitplan" - }, - "dashboard_status": { - "name": "Status-Feld des Dashboard API" - }, - "device_power_status": { - "name": "Einschaltstatus", - "state": { - "false": "An", - "true": "Standby" + "sensor": { + "indoor_temperature": { + "name": "Innentemperatur" + }, + "outdoor_temperature": { + "name": "Außentemperatur" + }, + "exhaust_temperature": { + "name": "Fortluft Temperatur" + }, + "extract_humidity": { + "name": "Abluft Feuchtigkeit" + }, + "exhaust_humidity": { + "name": "Fortluft Feuchtigkeit" + }, + "outdoor_humidity": { + "name": "Außenluft Feuchtigkeit" + }, + "supply_humidity": { + "name": "Zuluft Feuchtigkeit" + }, + "fan_speed": { + "name": "Lüfterstufe" + }, + "supply_air_temperature": { + "name": "Zulufttemperatur" + }, + "supply_coil_temperature": { + "name": "Zuluft Gastemperatur" + }, + "exhaust_coil_temperature": { + "name": "Abluft Gastemperatur" + }, + "tpma_temperature": { + "name": "Mittlere Außentemperatur" + }, + "current_comfort_temperature": { + "name": "Aktuelle Zieltemperatur" + }, + "power_heatpump": { + "name": "Wärmepumpe Leistungsaufnahme" + }, + "power_ventilation": { + "name": "Lüftung Leistungsaufnahme" + }, + "bypass_state": { + "name": "Bypass Zustand" + }, + "exhaust_fan_duty": { + "name": "Abluft Lüfter Ansteuerung" + }, + "supply_fan_duty": { + "name": "Zuluft Lüfter Ansteuerung" + }, + "exhaust_fan_speed": { + "name": "Abluft Lüfter Drehzahl" + }, + "supply_fan_speed": { + "name": "Zuluft Lüfter Drehzahl" + }, + "exhaust_air_flow": { + "name": "Abluft Volumenstrom" + }, + "supply_air_flow": { + "name": "Zuluft Volumenstrom" + }, + "energy_ytd": { + "name": "Lüftung Energie Jahr" + }, + "energy_total": { + "name": "Lüftung Energie gesamt" + }, + "ventilation_disbalance": { + "name": "Lüftung Ungleichgewicht" + }, + "heat_pump_status": { + "name": "Wärmepumpe Status" + }, + "season": { + "name": "Jahreszeit", + "state": { + "heating": "Heizperiode", + "cooling": "Kühlperiode", + "transitional": "Übergangszeit" + } + }, + "temperature_profile_status": { + "name": "Aktives Temperaturprofil", + "state": { + "comfort": "Komfort", + "eco": "Eco", + "power": "Power" + } + }, + "temp_rmot": { + "name": "Lüftung Mittlere Außentemperatur (RMOT)" + }, + "device_mode_status": { + "name": "Aktueller Betriebsmodus", + "state": { + "0": "Aus", + "1": "Heizen", + "2": "Kühlen" + } + }, + "schedule_status": { + "name": "Aktiver Zeitplan" + }, + "dashboard_status": { + "name": "Status-Feld des Dashboard API" + }, + "device_power_status": { + "name": "Einschaltstatus", + "state": { + "false": "An", + "true": "Standby" + } + }, + "free_cooling_status": { + "name": "Free Cooling", + "state": { + "false": "Aus", + "true": "An" + } } }, - "free_cooling_status": { - "name": "Free Cooling", - "state": { - "false": "Aus", - "true": "An" - } - } - }, - "select": { - "temperature_profile": { - "name": "Temperaturprofil", - "state": { - "comfort": "Komfort", - "eco": "Eco", - "power": "Power" + "select": { + "temperature_profile": { + "name": "Temperaturprofil", + "state": { + "comfort": "Komfort", + "eco": "Eco", + "power": "Power" + } + }, + "season_mode": { + "name": "Jahreszeitwahl", + "state": { + "heating": "Heizperiode", + "cooling": "Kühlperiode", + "transition": "Übergangszeit" + } + }, + "humidity_comfort_control": { + "name": "Lüftung Feuchteregelung", + "state": { + "off": "Aus", + "autoonly": "Nur bei Auto", + "on": "An" + } + }, + "humidity_protection": { + "name": "Lüftung Feuchteschutz", + "state": { + "off": "Aus", + "autoonly": "Nur bei Auto", + "on": "An" + } } }, - "season_mode": { - "name": "Jahreszeitwahl", - "state": { - "heating": "Heizperiode", - "cooling": "Kühlperiode", - "transition": "Übergangszeit" + "number": { + "heating_comfort_temperature": { + "name": "Komforttemperatur Heizen" + }, + "heating_knee_point": { + "name": "Knickpunkt Heizkurve Heizen" + }, + "heating_reduction_delta": { + "name": "Temperaturreduktion Heizen" + }, + "cooling_comfort_temperature": { + "name": "Komforttemperatur Kühlen" + }, + "cooling_knee_point": { + "name": "Knickpunkt Heizkurve Kühlen" + }, + "cooling_temperature_limit": { + "name": "Temperaturlimit Kühlen" + }, + "manual_comfort_temperature": { + "name": "Manuelle Komforttemperatur" + }, + "heating_threshold": { + "name": "Heizgrenze" + }, + "cooling_threshold": { + "name": "Kühlgrenze" + }, + "rmot_heating_threshold": { + "name": "Lüftung Heizgrenze RMOT" + }, + "rmot_cooling_threshold": { + "name": "Lüftung Kühlgrenze RMOT" + }, + "heatpump_min_temp": { + "name": "Wärmepumpe minimale Kühltemperatur" + }, + "heatpump_max_temp": { + "name": "Wärmepumpe maximale Heiztemperatur" } }, - "humidity_comfort_control": { - "name": "Lüftung Feuchteregelung", - "state": { - "off": "Aus", - "autoonly": "Nur bei Auto", - "on": "An" + "switch": { + "automatic_season_detection": { + "name": "Automatische Jahreszeiterkennung" + }, + "automatic_comfort_temperature": { + "name": "Automatische Komforttemperatur" + }, + "heatpump_onoff": { + "name": "Wärmepumpe an/aus" } }, - "humidity_protection": { - "name": "Lüftung Feuchteschutz", - "state": { - "off": "Aus", - "autoonly": "Nur bei Auto", - "on": "An" + "fan": { + "fan_speed": { + "name": "Lüfter Stufe", + "state": { + "off": "Abwesend", + "low": "Niedrig", + "medium": "Mittel", + "high": "Hoch" + } } - } - }, - "number": { - "heating_comfort_temperature": { - "name": "Komforttemperatur Heizen" - }, - "heating_knee_point": { - "name": "Knickpunkt Heizkurve Heizen" - }, - "heating_reduction_delta": { - "name": "Temperaturreduktion Heizen" - }, - "cooling_comfort_temperature": { - "name": "Komforttemperatur Kühlen" - }, - "cooling_knee_point": { - "name": "Knickpunkt Heizkurve Kühlen" }, - "cooling_temperature_limit": { - "name": "Temperaturlimit Kühlen" - }, - "manual_comfort_temperature": { - "name": "Manuelle Komforttemperatur" - }, - "heating_threshold": { - "name": "Heizgrenze" - }, - "cooling_threshold": { - "name": "Kühlgrenze" - }, - "rmot_heating_threshold": { - "name": "Lüftung Heizgrenze RMOT" - }, - "rmot_cooling_threshold": { - "name": "Lüftung Kühlgrenze RMOT" - }, - "heatpump_min_temp": { - "name": "Wärmepumpe minimale Kühltemperatur" - }, - "heatpump_max_temp": { - "name": "Wärmepumpe maximale Heiztemperatur" - } - }, - "switch": { - "automatic_season_detection": { - "name": "Automatische Jahreszeiterkennung" - }, - "automatic_comfort_temperature": { - "name": "Automatische Komforttemperatur" - }, - "heatpump_onoff": { - "name": "Wärmepumpe an/aus" - } - }, - "fan": { - "fan_speed": { - "name": "Lüfter Stufe", - "state": { - "off": "Abwesend", - "low": "Niedrig", - "medium": "Mittel", - "high": "Hoch" - } - } - }, - "climate": { "climate": { - "name": "Klimasteuerung", - "state_attributes": { - "preset_mode": { - "state": { - "comfort": "Komfort", - "power": "Power", - "eco": "Eco" - } - }, - "fan_mode": { - "state": { - "off": "Aus", - "low": "Niedrig", - "medium": "Mittel", - "high": "Hoch" + "climate": { + "name": "Klimasteuerung", + "state_attributes": { + "preset_mode": { + "state": { + "comfort": "Komfort", + "power": "Power", + "eco": "Eco" + } + }, + "fan_mode": { + "state": { + "off": "Aus", + "low": "Niedrig", + "medium": "Mittel", + "high": "Hoch" + } } } } } - } }, "config": { "step": { - "user": { - "title": "Zehnder ComfoClime verbinden", - "description": "Gib die IP-Adresse oder den Hostnamen deiner ComfoClime Einheit ein", - "data": { - "host": "Host oder IP-Adresse" + "user": { + "title": "Zehnder ComfoClime verbinden", + "description": "Gib die IP-Adresse oder den Hostnamen deiner ComfoClime Einheit ein", + "data": { + "host": "Host oder IP-Adresse" + } } - } }, "error": { "cannot_connect": "Verbindung fehlgeschlagen.", "no_uuid": "Antwort erhalten, aber keine UUID gefunden.", "no_response": "Das Gerät hat nicht korrekt geantwortet." }, - "abort": { + "abort": { "already_configured": "Dieses Gerät ist bereits konfiguriert." } }, "options": { "step": { - "init": { - "title": "Optionen", - "description": "Erweiterte Einstellungen für die Integration", - "data": { - "enable_diagnostics": "Diagnose-Sensoren aktivieren", - "minimal_mode": "Keine ComfoNet Abfragen (Testzwecke)", - "throttle_comfonet": "Wartezeit von 10ms zwischen ComfoNet Abfragen" + "init": { + "title": "Optionen", + "description": "Erweiterte Einstellungen für die Integration", + "data": { + "enable_diagnostics": "Diagnose-Sensoren aktivieren", + "minimal_mode": "Keine ComfoNet Abfragen (Testzwecke)", + "throttle_comfonet": "Wartezeit von 10ms zwischen ComfoNet Abfragen" + } } - } } } } \ No newline at end of file diff --git a/custom_components/comfoclime/translations/en.json b/custom_components/comfoclime/translations/en.json index 3badcb4..130fc6a 100644 --- a/custom_components/comfoclime/translations/en.json +++ b/custom_components/comfoclime/translations/en.json @@ -1,285 +1,294 @@ { "entity": { - "sensor": { - "indoor_temperature": { - "name": "Indoor Temperature" - }, - "outdoor_temperature": { - "name": "Outdoor Temperature" - }, - "exhaust_temperature": { - "name": "Exhaust Temperature" - }, - "extract_humidity": { - "name": "Extract Humidity" - }, - "exhaust_humidity": { - "name": "Exhaust Humidity" - }, - "outdoor_humidity": { - "name": "Outdoor Humidity" - }, - "supply_humidity": { - "name": "Supply Humidity" - }, - "fan_speed": { - "name": "Fan Speed Level" - }, - "supply_air_temperature": { - "name": "Supply Air Temperature" - }, - "supply_coil_temperature": { - "name": "Supply Coil Temperature" - }, - "exhaust_coil_temperature": { - "name": "Supply Coil Temperature" - }, - "tpma_temperature": { - "name": "Mean Outdoor Temperature" - }, - "comfo_clime_status": { - "name": "ComfoClime Status" - }, - "current_comfort_temperature": { - "name": "Current Comfort Temperature" - }, - "power_heatpump": { - "name": "Power Heatpump" - }, - "power_ventilation": { - "name": "Power Ventilation" - }, - "bypass_state": { - "name": "Bypass State" - }, - "exhaust_fan_duty": { - "name": "Exhaust Fan Duty" - }, - "supply_fan_duty": { - "name": "Supply Fan Duty" - }, - "exhaust_fan_speed": { - "name": "Exhaust Fan Speed" - }, - "supply_fan_speed": { - "name": "Supply Fan Speed" - }, - "exhaust_air_flow": { - "name": "Exhaust Air Flow" - }, - "supply_air_flow": { - "name": "Supply Air Flow" - }, - "energy_ytd": { - "name": "Ventilation Energy Year" - }, - "energy_total": { - "name": "Ventilation Energy Total" - }, - "ventilation_disbalance": { - "name": "Ventilation Disbalance" - }, - "heat_pump_status": { - "name": "Heat Pump State Code" - }, - "season": { - "name": "Season", - "state": { - "heating": "Heating period", - "cooling": "Cooling period", - "transition": "Transitional period" - } - }, - "temperature_profile_status": { - "name": "Current Temperature Profile", - "state": { - "comfort": "Comfort", - "eco": "Eco", - "power": "Power" - } - }, - "temp_rmot": { - "name": "Ventilation Mean Outdoor Temperature (RMOT)" - }, - "device_mode_status": { - "name": "Current Operating Mode", - "state": { - "0": "Off", - "1": "Heating", - "2": "Cooling" - } - }, - "schedule_status": { - "name": "Current Schedule" - }, - "dashboard_status": { - "name": "Status Field of the Dashboard API" - }, - "device_power_status": { - "name": "Power Status", - "state": { - "false": "On", - "true": "Standby" + "sensor": { + "indoor_temperature": { + "name": "Indoor Temperature" + }, + "outdoor_temperature": { + "name": "Outdoor Temperature" + }, + "exhaust_temperature": { + "name": "Exhaust Temperature" + }, + "extract_humidity": { + "name": "Extract Humidity" + }, + "exhaust_humidity": { + "name": "Exhaust Humidity" + }, + "outdoor_humidity": { + "name": "Outdoor Humidity" + }, + "supply_humidity": { + "name": "Supply Humidity" + }, + "fan_speed": { + "name": "Fan Speed Level" + }, + "supply_air_temperature": { + "name": "Supply Air Temperature" + }, + "supply_coil_temperature": { + "name": "Supply Coil Temperature" + }, + "exhaust_coil_temperature": { + "name": "Supply Coil Temperature" + }, + "tpma_temperature": { + "name": "Mean Outdoor Temperature" + }, + "comfo_clime_status": { + "name": "ComfoClime Status" + }, + "current_comfort_temperature": { + "name": "Current Comfort Temperature" + }, + "power_heatpump": { + "name": "Power Heatpump" + }, + "power_ventilation": { + "name": "Power Ventilation" + }, + "bypass_state": { + "name": "Bypass State" + }, + "exhaust_fan_duty": { + "name": "Exhaust Fan Duty" + }, + "supply_fan_duty": { + "name": "Supply Fan Duty" + }, + "exhaust_fan_speed": { + "name": "Exhaust Fan Speed" + }, + "supply_fan_speed": { + "name": "Supply Fan Speed" + }, + "exhaust_air_flow": { + "name": "Exhaust Air Flow" + }, + "supply_air_flow": { + "name": "Supply Air Flow" + }, + "energy_ytd": { + "name": "Ventilation Energy Year" + }, + "energy_total": { + "name": "Ventilation Energy Total" + }, + "ventilation_disbalance": { + "name": "Ventilation Disbalance" + }, + "heat_pump_status": { + "name": "Heat Pump State Code" + }, + "season": { + "name": "Season", + "state": { + "heating": "Heating period", + "cooling": "Cooling period", + "transition": "Transitional period" + } + }, + "temperature_profile_status": { + "name": "Current Temperature Profile", + "state": { + "comfort": "Comfort", + "eco": "Eco", + "power": "Power" + } + }, + "temp_rmot": { + "name": "Ventilation Mean Outdoor Temperature (RMOT)" + }, + "device_mode_status": { + "name": "Current Operating Mode", + "state": { + "0": "Off", + "1": "Heating", + "2": "Cooling" + } + }, + "schedule_status": { + "name": "Current Schedule" + }, + "dashboard_status": { + "name": "Status Field of the Dashboard API" + }, + "device_power_status": { + "name": "Power Status", + "state": { + "false": "On", + "true": "Standby" + } + }, + "free_cooling_status": { + "name": "Free Cooling", + "state": { + "false": "Off", + "true": "On" + } + }, + "scenario_time_left": { + "name": "Scenario Time Left" + }, + "scenario": { + "name": "Active Scenario" } }, - "free_cooling_status": { - "name": "Free Cooling", - "state": { - "false": "Off", - "true": "On" + "select": { + "temperature_profile": { + "name": "Temperature Profile", + "state": { + "comfort": "Comfort", + "eco": "Eco", + "power": "Power" + } + }, + "season_mode": { + "name": "Season Mode", + "state": { + "heating": "Heating period", + "cooling": "Cooling period", + "transition": "Transitional period" + } + }, + "humidity_comfort_control": { + "name": "Ventilation Humidity Control", + "state": { + "off": "Off", + "autoonly": "Auto Only", + "on": "On" + } + }, + "humidity_protection": { + "name": "Ventilation Humidity Protection", + "state": { + "off": "Off", + "autoonly": "Auto Only", + "on": "On" + } } - } - }, - "select": { - "temperature_profile": { - "name": "Temperature Profile", - "state": { - "comfort": "Comfort", - "eco": "Eco", - "power": "Power" - } }, - "season_mode": { - "name": "Season Mode", - "state": { - "heating": "Heating period", - "cooling": "Cooling period", - "transition": "Transitional period" + "number": { + "heating_comfort_temperature": { + "name": "Heating Comfort Temperature" + }, + "heating_knee_point": { + "name": "Heating Curve Knee Point" + }, + "heating_reduction_delta": { + "name": "Heating Reduction Delta" + }, + "cooling_comfort_temperature": { + "name": "Cooling Comfort Temperature" + }, + "cooling_knee_point": { + "name": "Cooling Curve Knee Point" + }, + "cooling_temperature_limit": { + "name": "Cooling Temperaturelimit" + }, + "manual_comfort_temperature": { + "name": "Manual Comfort Temperature" + }, + "heating_threshold": { + "name": "Heating Threshold" + }, + "cooling_threshold": { + "name": "Cooling Threshold" + }, + "rmot_heating_threshold": { + "name": "Ventilation heating threshold RMOT" + }, + "rmot_cooling_threshold": { + "name": "Ventilation cooling threshold RMOT" + }, + "heatpump_min_temp": { + "name": "Heatpump minimum cooling temperature" + }, + "heatpump_max_temp": { + "name": "Heatpumpt maximum heating temperature" } }, - "humidity_comfort_control": { - "name": "Ventilation Humidity Control", - "state": { - "off": "Off", - "autoonly": "Auto Only", - "on": "On" + "switch": { + "automatic_season_detection": { + "name": "Automatic Season Detection" + }, + "automatic_comfort_temperature": { + "name": "Automatic Comfort Temperature" + }, + "heatpump_onoff": { + "name": "Heatpump on/off" } }, - "humidity_protection": { - "name": "Ventilation Humidity Protection", - "state": { - "off": "Off", - "autoonly": "Auto Only", - "on": "On" + "fan": { + "fan_speed": { + "name": "Fan Speed", + "state": { + "off": "Away", + "low": "Low", + "medium": "Medium", + "high": "High" + } } - } - }, - "number": { - "heating_comfort_temperature": { - "name": "Heating Comfort Temperature" - }, - "heating_knee_point": { - "name": "Heating Curve Knee Point" - }, - "heating_reduction_delta": { - "name": "Heating Reduction Delta" - }, - "cooling_comfort_temperature": { - "name": "Cooling Comfort Temperature" - }, - "cooling_knee_point": { - "name": "Cooling Curve Knee Point" }, - "cooling_temperature_limit": { - "name": "Cooling Temperaturelimit" - }, - "manual_comfort_temperature": { - "name": "Manual Comfort Temperature" - }, - "heating_threshold": { - "name": "Heating Threshold" - }, - "cooling_threshold": { - "name": "Cooling Threshold" - }, - "rmot_heating_threshold": { - "name": "Ventilation heating threshold RMOT" - }, - "rmot_cooling_threshold": { - "name": "Ventilation cooling threshold RMOT" - }, - "heatpump_min_temp": { - "name": "Heatpump minimum cooling temperature" - }, - "heatpump_max_temp": { - "name": "Heatpumpt maximum heating temperature" - } - }, - "switch": { - "automatic_season_detection": { - "name": "Automatic Season Detection" - }, - "automatic_comfort_temperature": { - "name": "Automatic Comfort Temperature" - }, - "heatpump_onoff": { - "name": "Heatpump on/off" - } - }, - "fan": { - "fan_speed": { - "name": "Fan Speed", - "state": { - "off": "Away", - "low": "Low", - "medium": "Medium", - "high": "High" - } - } - }, - "climate": { "climate": { - "name": "Climate Control", - "state_attributes": { - "preset_mode": { - "state": { - "comfort": "Comfort", - "power": "Power", - "eco": "Eco" - } - }, - "fan_mode": { - "state": { - "off": "Off", - "low": "Low", - "medium": "Medium", - "high": "High" + "climate": { + "name": "Climate Control", + "state_attributes": { + "preset_mode": { + "name": "Preset mode", + "state": { + "comfort": "Comfort", + "power": "Power", + "eco": "Eco", + "none": "Manual" + } + }, + "fan_mode": { + "name": "Fan mode", + "state": { + "off": "Off", + "low": "Low", + "medium": "Medium", + "high": "High" + } } } } } - } }, "config": { "step": { - "user": { - "title": "Connect to Zehnder ComfoClime", - "description": "Enter the IP-Address or hostname of your ComfoClime unit", - "data": { - "host": "Host or IP-Address" + "user": { + "title": "Connect to Zehnder ComfoClime", + "description": "Enter the IP-Address or hostname of your ComfoClime unit", + "data": { + "host": "Host or IP-Address" + } } - } }, "error": { "cannot_connect": "Connection failed, cannot connect.", "no_uuid": "Device online, but didn't get a serial number.", "no_response": "Device didn't respond with valid data." }, - "abort": { + "abort": { "already_configured": "This device is already configured." } }, "options": { "step": { - "init": { - "title": "Options", - "description": "Advanced integration options", - "data": { - "enable_diagnostics": "Activate diagnosis sensors", - "minimal_mode": "No communication with ComfoNet Bus (testing)", - "throttle_comfonet": "Add a 10ms pause between ComfoNet polls" + "init": { + "title": "Options", + "description": "Advanced integration options", + "data": { + "enable_diagnostics": "Activate diagnosis sensors", + "minimal_mode": "No communication with ComfoNet Bus (testing)", + "throttle_comfonet": "Add a 10ms pause between ComfoNet polls" + } } - } } } - } \ No newline at end of file +} \ No newline at end of file From 7914e0d98895fc52ceba7910bff469f5bc41704a Mon Sep 17 00:00:00 2001 From: Revilo91 Date: Sat, 27 Dec 2025 17:42:43 +0100 Subject: [PATCH 03/11] Add support for scenario modes in the ComfoClime integration, including API changes, service handlers, and sensor definitions. --- SCENARIO_MODES.md | 206 ++++++++++++++++++ custom_components/comfoclime/__init__.py | 81 +++++++ .../comfoclime/comfoclime_api.py | 25 ++- .../comfoclime/entities/sensor_definitions.py | 14 ++ 4 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 SCENARIO_MODES.md diff --git a/SCENARIO_MODES.md b/SCENARIO_MODES.md new file mode 100644 index 0000000..66f021f --- /dev/null +++ b/SCENARIO_MODES.md @@ -0,0 +1,206 @@ +# Szenario-Modi für ComfoClime Climate Entity + +## Übersicht + +Die ComfoClime Integration unterstützt jetzt Szenario-Modi als Preset-Optionen in der Climate Entity. Diese Modi ermöglichen zeitlich begrenzte Betriebsmodi für spezielle Situationen. + +## Verfügbare Szenario-Modi + +| Szenario | Preset Name | Wert | Standard-Dauer | Beschreibung | +| ----------- | ----------- | ---- | -------------------- | ---------------------------------------- | +| 🍳 Kochen | `cooking` | 4 | 30 Minuten | Hohe Lüftung für Kochaktivitäten | +| 🎉 Party | `party` | 5 | 30 Minuten | Hohe Lüftung für Gäste | +| 🏖️ Urlaub | `away` | 7 | 24 Stunden (1440min) | Reduzierter Betrieb während Abwesenheit | +| ⚡ Boost | `boost` | 8 | 30 Minuten | Maximale Leistung | + +## Verwendung + +### Via Home Assistant UI + +1. Öffne die Climate Entity (z.B. `climate.comfoclime`) +2. Wähle einen Szenario-Modus aus dem Preset-Dropdown +3. Der Modus wird mit der Standard-Dauer aktiviert + +### Via Service Call + +#### Methode 1: Standard Climate Service (Standard-Dauer) + +```yaml +service: climate.set_preset_mode +target: + entity_id: climate.comfoclime +data: + preset_mode: cooking +``` + +#### Methode 2: Custom Service (mit benutzerdefinierter Dauer) + +```yaml +service: comfoclime.set_scenario_mode +data: + entity_id: climate.comfoclime + scenario: cooking + duration: 60 # 1 Stunde in Sekunden +``` + +### Via Automation + +```yaml +automation: + - alias: "Kochen-Modus aktivieren" + trigger: + - platform: state + entity_id: binary_sensor.kuche_bewegung + to: "on" + action: + - service: climate.set_preset_mode + target: + entity_id: climate.comfoclime + data: + preset_mode: cooking +``` + +### Benutzerdefinierte Dauer + +Für eine benutzerdefinierte Dauer verwende den `comfoclime.set_scenario_mode` Service: + +```yaml +# Beispiel: Party-Modus für 2 Stunden +service: comfoclime.set_scenario_mode +data: + entity_id: climate.comfoclime + scenario: party + duration: 120 +``` + +## Restzeit-Anzeige + +Die verbleibende Zeit des aktiven Szenarios wird als Attribut der Climate Entity angezeigt: + +```yaml +# Attribute der Climate Entity: +scenario_time_left: 1798 # in Sekunden +scenario_time_left_formatted: "29m 58s" # lesbares Format +``` + +### Template Sensor für Restzeit + +```yaml +template: + - sensor: + - name: "ComfoClime Szenario Restzeit" + state: > + {{ state_attr('climate.comfoclime', 'scenario_time_left_formatted') | default('Kein aktives Szenario') }} + icon: mdi:timer-outline +``` + +## Dashboard-Integration + +### Beispiel Lovelace Card + +```yaml +type: entities +title: ComfoClime Steuerung +entities: + - entity: climate.comfoclime + type: climate + - type: attribute + entity: climate.comfoclime + attribute: scenario_time_left_formatted + name: Szenario Restzeit + icon: mdi:timer-outline +``` + +### Button Card für schnellen Zugriff + +```yaml +type: horizontal-stack +cards: + - type: button + name: Kochen + icon: mdi:pot-steam + tap_action: + action: call-service + service: climate.set_preset_mode + service_data: + entity_id: climate.comfoclime + preset_mode: cooking + - type: button + name: Party + icon: mdi:party-popper + tap_action: + action: call-service + service: climate.set_preset_mode + service_data: + entity_id: climate.comfoclime + preset_mode: party + - type: button + name: Urlaub + icon: mdi:beach + tap_action: + action: call-service + service: climate.set_preset_mode + service_data: + entity_id: climate.comfoclime + preset_mode: away + - type: button + name: Boost + icon: mdi:rocket-launch + tap_action: + action: call-service + service: climate.set_preset_mode + service_data: + entity_id: climate.comfoclime + preset_mode: boost +``` + +## API Details + +### Dashboard API Parameter + +Beim Aktivieren eines Szenarios werden folgende Parameter an die Dashboard API gesendet: + +```python +{ + "scenario": 4, # Szenario-Wert (4, 5, 7, oder 8) + "scenarioTimeLeft": 1800, # Dauer in Sekunden (interner API-Parameter; wird im Code aus Minuten * 60 berechnet, Benutzer geben die Dauer in Minuten an) + "timestamp": "2024-11-12T10:30:00Z" +} +``` + +### Standard-Dauern (in Code definiert) + +```python +SCENARIO_DEFAULT_DURATIONS = { + 4: 30, # Kochen: 30 Minuten + 5: 30, # Party: 30 Minuten + 7: 1440, # Urlaub: 24 Stunden + 8: 30, # Boost: 30 Minuten +} +``` + +## Hinweise + +- ⏱️ Die Restzeit wird automatisch vom Gerät heruntergezählt +- 🔄 Ein aktives Szenario kann jederzeit durch ein anderes Preset überschrieben werden +- 🛑 Um ein Szenario vorzeitig zu beenden, wähle ein anderes Preset (z.B. "Komfort") +- 📊 Die Restzeit wird über die Dashboard API abgerufen und alle 30 Sekunden aktualisiert + +## Troubleshooting + +### Szenario wird nicht aktiviert + +1. Prüfe die Logs: `Settings -> System -> Logs -> Custom Component: comfoclime` +2. Stelle sicher, dass die Climate Entity verfügbar ist +3. Überprüfe die Netzwerkverbindung zum ComfoClime-Gerät + +### Restzeit wird nicht angezeigt + +Die Restzeit wird als Attribut der Climate Entity bereitgestellt. Stelle sicher, dass: + +- Die Dashboard-Daten erfolgreich abgerufen werden +- Der Coordinator läuft (Update-Intervall: 30 Sekunden) + +### Benutzerdefinierte Dauer funktioniert nicht + +Verwende den `comfoclime.set_scenario_mode` Service statt `climate.set_preset_mode` für benutzerdefinierte Dauern. diff --git a/custom_components/comfoclime/__init__.py b/custom_components/comfoclime/__init__.py index bbd737b..466718b 100644 --- a/custom_components/comfoclime/__init__.py +++ b/custom_components/comfoclime/__init__.py @@ -87,8 +87,89 @@ async def handle_reset_system_service(call: ServiceCall): _LOGGER.error(f"Fehler beim Neustart des Geräts: {e}") raise HomeAssistantError(f"Fehler beim Neustart des Geräts: {e}") + async def handle_set_scenario_mode_service(call: ServiceCall): + """Handle set_scenario_mode service call. + + This service activates special operating modes (scenarios) on the ComfoClime + climate entity with optional custom duration. + + Supported scenarios: + - cooking: High ventilation for cooking (default: 30 min) + - party: High ventilation for parties (default: 30 min) + - holiday: Reduced mode for vacation (default: 24 hours) + - boost_mode: Maximum power boost (default: 30 min) + """ + entity_id = call.data["entity_id"] + scenario = call.data["scenario"] + duration = call.data.get("duration") + start_delay = call.data.get("start_delay") + + # Validate scenario parameter + from .climate import SCENARIO_REVERSE_MAPPING + + valid_scenarios = list(SCENARIO_REVERSE_MAPPING.keys()) + if scenario not in valid_scenarios: + raise HomeAssistantError( + f"Invalid scenario '{scenario}'. Must be one of: {', '.join(valid_scenarios)}" + ) + + # Validate duration if provided + if duration is not None: + if not isinstance(duration, (int, float)) or duration <= 0: + raise HomeAssistantError( + f"Duration must be a positive number, got: {duration}" + ) + + # Validate start_delay format if provided + if start_delay is not None: + if not isinstance(start_delay, str): + raise HomeAssistantError( + f"start_delay must be a datetime string (e.g. '2025-11-21 12:00:00'), got: {type(start_delay).__name__}" + ) + + _LOGGER.debug( + f"Service call: set_scenario_mode for {entity_id}, " + f"scenario={scenario}, duration={duration}, start_delay={start_delay}" + ) + + # Get climate entity from component + # Access the entity via the state machine's entities + component = hass.data.get("entity_components", {}).get("climate") + if component: + climate_entity = component.get_entity(entity_id) + + if climate_entity and hasattr(climate_entity, "async_set_scenario_mode"): + try: + await climate_entity.async_set_scenario_mode( + scenario_mode=scenario, + duration=duration, + start_delay=start_delay, + ) + except Exception as e: + _LOGGER.exception( + f"Error setting scenario mode '{scenario}' on {entity_id}" + ) + raise HomeAssistantError( + f"Failed to set scenario mode '{scenario}'" + ) from e + else: + _LOGGER.info( + f"Scenario mode '{scenario}' activated for {entity_id} " + f"with duration {duration} min and start_delay {start_delay}" + ) + return + + # Entity not found or doesn't support scenarios + raise HomeAssistantError( + f"Climate entity '{entity_id}' not found or does not support scenario modes. " + f"Make sure the entity exists and belongs to the ComfoClime integration." + ) + hass.services.async_register(DOMAIN, "set_property", handle_set_property_service) hass.services.async_register(DOMAIN, "reset_system", handle_reset_system_service) + hass.services.async_register( + DOMAIN, "set_scenario_mode", handle_set_scenario_mode_service + ) return True diff --git a/custom_components/comfoclime/comfoclime_api.py b/custom_components/comfoclime/comfoclime_api.py index 8292bdb..1f7a43f 100644 --- a/custom_components/comfoclime/comfoclime_api.py +++ b/custom_components/comfoclime/comfoclime_api.py @@ -295,6 +295,9 @@ def update_dashboard( temperature_profile: int | None = None, season_profile: int | None = None, status: int | None = None, + scenario: int | None = None, + scenario_time_left: int | None = None, + scenario_start_delay: int | None = None, ) -> dict: """Update dashboard settings via API. @@ -317,12 +320,21 @@ def update_dashboard( "scenarioTimeLeft": None, "season": season, "schedule": None, + "scenario": None, + "scenarioTimeLeft": None, + "scenarioStartDelay": None } The API distinguishes between two modes: - Automatic mode (status=1): Uses preset profiles (seasonProfile, temperatureProfile) - Manual mode (status=0): Uses manual temperature (setPointTemperature) + Scenario modes: + - 4: Kochen (Cooking) - 30 minutes high ventilation + - 5: Party - 30 minutes high ventilation + - 7: Urlaub (Holiday) - 24 hours reduced mode + - 8: Boost - 30 minutes maximum power + Args: set_point_temperature: Target temperature (°C) - activates manual mode fan_speed: Fan speed (0-3) @@ -332,6 +344,9 @@ def update_dashboard( temperature_profile: Temperature profile/preset (0=comfort, 1=boost, 2=eco) season_profile: Season profile/preset (0=comfort, 1=boost, 2=eco) status: Temperature control mode (0=manual, 1=automatic) + scenario: Scenario mode (4=Kochen, 5=Party, 7=Urlaub, 8=Boost) + scenario_time_left: Duration for scenario in seconds (e.g., 1800 for 30min) + scenario_start_delay: Start delay for scenario in seconds (optional) Returns: Response JSON from the API @@ -374,8 +389,14 @@ def update_dashboard( payload["seasonProfile"] = season_profile if status is not None: payload["status"] = status - if hp_standby is not None: - payload["hpStandby"] = hp_standby + if hpStandby is not None: + payload["hpStandby"] = hpStandby + if scenario is not None: + payload["scenario"] = scenario + if scenario_time_left is not None: + payload["scenarioTimeLeft"] = scenario_time_left + if scenario_start_delay is not None: + payload["scenarioStartDelay"] = scenario_start_delay if not payload: _LOGGER.debug( diff --git a/custom_components/comfoclime/entities/sensor_definitions.py b/custom_components/comfoclime/entities/sensor_definitions.py index 0f3ee5c..bc50979 100644 --- a/custom_components/comfoclime/entities/sensor_definitions.py +++ b/custom_components/comfoclime/entities/sensor_definitions.py @@ -71,6 +71,20 @@ "name": "Free Cooling Status", "translation_key": "free_cooling_status", }, + { + "key": "scenarioTimeLeft", + "name": "Scenario Time Left", + "translation_key": "scenario_time_left", + "unit": "s", + "device_class": "duration", + "state_class": "measurement", + }, + { + "key": "scenario", + "name": "Scenario", + "translation_key": "scenario", + "entity_category": "diagnostic", + }, ] TELEMETRY_SENSORS = [] From 8d504266ceceea483029376f541975e8a60a66d6 Mon Sep 17 00:00:00 2001 From: Revilo91 Date: Sat, 27 Dec 2025 17:53:26 +0100 Subject: [PATCH 04/11] bugfix --- custom_components/comfoclime/comfoclime_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/comfoclime/comfoclime_api.py b/custom_components/comfoclime/comfoclime_api.py index 1f7a43f..acb23e0 100644 --- a/custom_components/comfoclime/comfoclime_api.py +++ b/custom_components/comfoclime/comfoclime_api.py @@ -389,8 +389,8 @@ def update_dashboard( payload["seasonProfile"] = season_profile if status is not None: payload["status"] = status - if hpStandby is not None: - payload["hpStandby"] = hpStandby + if hp_standby is not None: + payload["hpStandby"] = hp_standby if scenario is not None: payload["scenario"] = scenario if scenario_time_left is not None: From 3c3e3b6cb4a1e51a1d25cdf7bb2238ed9f1fb1e3 Mon Sep 17 00:00:00 2001 From: Revilo91 Date: Sat, 27 Dec 2025 18:23:43 +0100 Subject: [PATCH 05/11] Update scenario modes in the ComfoClime integration: Remove outdated automation examples, adjust timestamp format, and improve the translations for scenario modes in German and English. --- SCENARIO_MODES.md | 30 +------------------ custom_components/comfoclime/__init__.py | 4 +-- custom_components/comfoclime/climate.py | 2 +- .../comfoclime/translations/de.json | 7 ++++- .../comfoclime/translations/en.json | 4 ++- 5 files changed, 13 insertions(+), 34 deletions(-) diff --git a/SCENARIO_MODES.md b/SCENARIO_MODES.md index 66f021f..df74407 100644 --- a/SCENARIO_MODES.md +++ b/SCENARIO_MODES.md @@ -43,35 +43,7 @@ data: duration: 60 # 1 Stunde in Sekunden ``` -### Via Automation -```yaml -automation: - - alias: "Kochen-Modus aktivieren" - trigger: - - platform: state - entity_id: binary_sensor.kuche_bewegung - to: "on" - action: - - service: climate.set_preset_mode - target: - entity_id: climate.comfoclime - data: - preset_mode: cooking -``` - -### Benutzerdefinierte Dauer - -Für eine benutzerdefinierte Dauer verwende den `comfoclime.set_scenario_mode` Service: - -```yaml -# Beispiel: Party-Modus für 2 Stunden -service: comfoclime.set_scenario_mode -data: - entity_id: climate.comfoclime - scenario: party - duration: 120 -``` ## Restzeit-Anzeige @@ -164,7 +136,7 @@ Beim Aktivieren eines Szenarios werden folgende Parameter an die Dashboard API g { "scenario": 4, # Szenario-Wert (4, 5, 7, oder 8) "scenarioTimeLeft": 1800, # Dauer in Sekunden (interner API-Parameter; wird im Code aus Minuten * 60 berechnet, Benutzer geben die Dauer in Minuten an) - "timestamp": "2024-11-12T10:30:00Z" + "timestamp": "YYYY-MM-DDTHH:MM:SSZ" } ``` diff --git a/custom_components/comfoclime/__init__.py b/custom_components/comfoclime/__init__.py index 466718b..217a1e8 100644 --- a/custom_components/comfoclime/__init__.py +++ b/custom_components/comfoclime/__init__.py @@ -124,7 +124,7 @@ async def handle_set_scenario_mode_service(call: ServiceCall): if start_delay is not None: if not isinstance(start_delay, str): raise HomeAssistantError( - f"start_delay must be a datetime string (e.g. '2025-11-21 12:00:00'), got: {type(start_delay).__name__}" + f"start_delay must be a datetime string (e.g. 'YYYY-MM-DD HH:MM:SS'), got: {type(start_delay).__name__}" ) _LOGGER.debug( @@ -155,7 +155,7 @@ async def handle_set_scenario_mode_service(call: ServiceCall): else: _LOGGER.info( f"Scenario mode '{scenario}' activated for {entity_id} " - f"with duration {duration} min and start_delay {start_delay}" + f"with duration {duration} and start_delay {start_delay}" ) return diff --git a/custom_components/comfoclime/climate.py b/custom_components/comfoclime/climate.py index 9bfe62b..0af3e6e 100755 --- a/custom_components/comfoclime/climate.py +++ b/custom_components/comfoclime/climate.py @@ -67,7 +67,7 @@ SCENARIO_REVERSE_MAPPING = {v: k for k, v in SCENARIO_MAPPING.items()} -# Default durations for scenarios in seconds (based on Mode_info.json) +# Default durations for scenarios in minutes SCENARIO_DEFAULT_DURATIONS = { SCENARIO_COOKING: 30, SCENARIO_PARTY: 30, diff --git a/custom_components/comfoclime/translations/de.json b/custom_components/comfoclime/translations/de.json index 725b799..ef6ff85 100755 --- a/custom_components/comfoclime/translations/de.json +++ b/custom_components/comfoclime/translations/de.json @@ -229,10 +229,15 @@ "name": "Klimasteuerung", "state_attributes": { "preset_mode": { + "name": "Szenario", "state": { "comfort": "Komfort", "power": "Power", - "eco": "Eco" + "eco": "Eco", + "none": "Manuell", + "cooking": "Kochen", + "party": "Party", + "away": "Abwesend" } }, "fan_mode": { diff --git a/custom_components/comfoclime/translations/en.json b/custom_components/comfoclime/translations/en.json index 130fc6a..152ff5b 100644 --- a/custom_components/comfoclime/translations/en.json +++ b/custom_components/comfoclime/translations/en.json @@ -243,7 +243,9 @@ "comfort": "Comfort", "power": "Power", "eco": "Eco", - "none": "Manual" + "none": "Manual", + "cooking": "Cooking", + "party": "Party" } }, "fan_mode": { From 8fd4c9cc605dea124115b64074ee684629b0f950 Mon Sep 17 00:00:00 2001 From: Revilo91 Date: Sat, 27 Dec 2025 18:33:50 +0100 Subject: [PATCH 06/11] Add support for scenario modes in the ComfoClime integration: Implement the function for setting scenario modes and update the associated service definitions in services.yaml. --- custom_components/comfoclime/climate.py | 82 ++++++++++++++++++++++ custom_components/comfoclime/services.yaml | 44 ++++++++++++ 2 files changed, 126 insertions(+) diff --git a/custom_components/comfoclime/climate.py b/custom_components/comfoclime/climate.py index 0af3e6e..792c503 100755 --- a/custom_components/comfoclime/climate.py +++ b/custom_components/comfoclime/climate.py @@ -565,6 +565,88 @@ async def async_set_fan_mode(self, fan_mode: str) -> None: except Exception: _LOGGER.exception(f"Failed to set fan mode {fan_mode}") + async def async_set_scenario_mode( + self, + scenario_mode: str, + duration: int | float | None = None, + start_delay: str | None = None, + ) -> None: + """Set scenario mode via dashboard API. + + Activates a special operating mode (scenario) on the ComfoClime device. + + Supported scenarios: + - cooking: High ventilation for cooking (default: 30 min) + - party: High ventilation for parties (default: 30 min) + - away: Reduced mode for vacation (default: 24 hours) + - boost: Maximum power boost (default: 30 min) + + Args: + scenario_mode: The scenario mode to activate (cooking, party, away, boost) + duration: Optional duration in minutes. If not provided, uses default. + start_delay: Optional start delay as datetime string (YYYY-MM-DD HH:MM:SS) + """ + if scenario_mode not in SCENARIO_REVERSE_MAPPING: + _LOGGER.error(f"Unknown scenario mode: {scenario_mode}") + return + + try: + # Map scenario mode to API value + scenario_value = SCENARIO_REVERSE_MAPPING[scenario_mode] + + # Calculate duration in seconds + if duration is not None: + # User provided duration in minutes, convert to seconds + scenario_time_left = int(duration * 60) + else: + # Use default duration from mapping (already in minutes) + default_duration = SCENARIO_DEFAULT_DURATIONS.get(scenario_value, 30) + scenario_time_left = default_duration * 60 + + # Calculate start delay in seconds if provided + scenario_start_delay = None + if start_delay is not None: + try: + from datetime import datetime + from zoneinfo import ZoneInfo + + # Parse the datetime string + tz = ZoneInfo(self.hass.config.time_zone) + start_time = datetime.strptime(start_delay, "%Y-%m-%d %H:%M:%S") + start_time = start_time.replace(tzinfo=tz) + now = datetime.now(tz) + + # Calculate delay in seconds from now + delay_seconds = int((start_time - now).total_seconds()) + if delay_seconds > 0: + scenario_start_delay = delay_seconds + else: + _LOGGER.warning( + f"Start delay {start_delay} is in the past, starting immediately" + ) + except ValueError: + _LOGGER.exception(f"Invalid start_delay format '{start_delay}'") + raise + + _LOGGER.debug( + f"Setting scenario mode to {scenario_mode} (value={scenario_value}) " + f"with duration={scenario_time_left}s, start_delay={scenario_start_delay}s" + ) + + # Update scenario via dashboard API + await self.async_update_dashboard( + scenario=scenario_value, + scenario_time_left=scenario_time_left, + scenario_start_delay=scenario_start_delay, + ) + + # Schedule non-blocking refresh of coordinators + await self._async_refresh_coordinators() + + except Exception: + _LOGGER.exception(f"Failed to set scenario mode {scenario_mode}") + raise + @property def extra_state_attributes(self) -> dict[str, Any]: """Return dashboard data as extra state attributes. diff --git a/custom_components/comfoclime/services.yaml b/custom_components/comfoclime/services.yaml index c88809f..da01eb3 100644 --- a/custom_components/comfoclime/services.yaml +++ b/custom_components/comfoclime/services.yaml @@ -52,3 +52,47 @@ set_property: reset_system: name: Reset System description: Startet das ComfoClime-Gerät neu. + +set_scenario_mode: + name: Set Scenario Mode + description: Aktiviert einen speziellen Betriebsmodus (Szenario) auf der ComfoClime Klimaanlage. + fields: + entity_id: + name: Klimaanlage + description: Die ComfoClime Klimaanlage-Entität + required: true + selector: + entity: + integration: comfoclime + domain: climate + scenario: + name: Szenario + description: "Der zu aktivierende Szenario-Modus: cooking (Kochen), party (Party), away (Urlaub), boost (Boost)" + required: true + selector: + select: + options: + - label: Kochen (30 Min Hochlüftung) + value: cooking + - label: Party (30 Min Hochlüftung) + value: party + - label: Urlaub (24h reduzierter Modus) + value: away + - label: Boost (30 Min maximale Leistung) + value: boost + duration: + name: Dauer (Minuten) + description: Optionale Dauer in Minuten. Falls nicht angegeben, wird die Standarddauer verwendet. + required: false + selector: + number: + min: 1 + max: 1440 + step: 1 + unit_of_measurement: min + start_delay: + name: Startzeit + description: Optionale verzögerte Startzeit im Format YYYY-MM-DD HH:MM:SS + required: false + selector: + text: From 7ce368b99cfaecf0409750c91ad43601bb67bb61 Mon Sep 17 00:00:00 2001 From: Revilo91 <39066472+Revilo91@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:49:29 +0100 Subject: [PATCH 07/11] Update SCENARIO_MODES.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SCENARIO_MODES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCENARIO_MODES.md b/SCENARIO_MODES.md index df74407..4177a4e 100644 --- a/SCENARIO_MODES.md +++ b/SCENARIO_MODES.md @@ -40,7 +40,7 @@ service: comfoclime.set_scenario_mode data: entity_id: climate.comfoclime scenario: cooking - duration: 60 # 1 Stunde in Sekunden + duration: 60 # Dauer in Minuten, z.B. 60 für 1 Stunde ``` From 6dc92bba429db786c6b0622f849da6ee8be4ae3d Mon Sep 17 00:00:00 2001 From: Revilo91 <39066472+Revilo91@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:49:38 +0100 Subject: [PATCH 08/11] Update custom_components/comfoclime/comfoclime_api.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- custom_components/comfoclime/comfoclime_api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/comfoclime/comfoclime_api.py b/custom_components/comfoclime/comfoclime_api.py index acb23e0..1d3e48d 100644 --- a/custom_components/comfoclime/comfoclime_api.py +++ b/custom_components/comfoclime/comfoclime_api.py @@ -320,8 +320,6 @@ def update_dashboard( "scenarioTimeLeft": None, "season": season, "schedule": None, - "scenario": None, - "scenarioTimeLeft": None, "scenarioStartDelay": None } From 4387a33490fbe26ffe2aae782406a609bd0c164e Mon Sep 17 00:00:00 2001 From: Revilo91 <39066472+Revilo91@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:49:53 +0100 Subject: [PATCH 09/11] Update SCENARIO_MODES.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- SCENARIO_MODES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SCENARIO_MODES.md b/SCENARIO_MODES.md index 4177a4e..3867f47 100644 --- a/SCENARIO_MODES.md +++ b/SCENARIO_MODES.md @@ -135,7 +135,7 @@ Beim Aktivieren eines Szenarios werden folgende Parameter an die Dashboard API g ```python { "scenario": 4, # Szenario-Wert (4, 5, 7, oder 8) - "scenarioTimeLeft": 1800, # Dauer in Sekunden (interner API-Parameter; wird im Code aus Minuten * 60 berechnet, Benutzer geben die Dauer in Minuten an) + "scenarioTimeLeft": 1800, # Dauer (API-Parameter in Sekunden; wird im Code aus Minuten * 60 berechnet, Benutzer geben die Dauer in Minuten an) "timestamp": "YYYY-MM-DDTHH:MM:SSZ" } ``` From 2a07d959b8a7d47905ba6bfe94e04c34f8a8599d Mon Sep 17 00:00:00 2001 From: Revilo91 <39066472+Revilo91@users.noreply.github.com> Date: Sat, 27 Dec 2025 18:50:00 +0100 Subject: [PATCH 10/11] Update custom_components/comfoclime/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- custom_components/comfoclime/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/comfoclime/__init__.py b/custom_components/comfoclime/__init__.py index 217a1e8..54fe0d9 100644 --- a/custom_components/comfoclime/__init__.py +++ b/custom_components/comfoclime/__init__.py @@ -96,8 +96,8 @@ async def handle_set_scenario_mode_service(call: ServiceCall): Supported scenarios: - cooking: High ventilation for cooking (default: 30 min) - party: High ventilation for parties (default: 30 min) - - holiday: Reduced mode for vacation (default: 24 hours) - - boost_mode: Maximum power boost (default: 30 min) + - away: Reduced mode for vacation (default: 24 hours) + - boost: Maximum power boost (default: 30 min) """ entity_id = call.data["entity_id"] scenario = call.data["scenario"] From 374d4708d46a30ba5bcefb86cea87fcf16392a7f Mon Sep 17 00:00:00 2001 From: Revilo91 Date: Sat, 27 Dec 2025 18:54:45 +0100 Subject: [PATCH 11/11] Change the scenario name for Boost mode in the ComfoClime integration and update the assignment in the climate control. --- custom_components/comfoclime/__init__.py | 2 +- custom_components/comfoclime/climate.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/custom_components/comfoclime/__init__.py b/custom_components/comfoclime/__init__.py index 54fe0d9..16ba5eb 100644 --- a/custom_components/comfoclime/__init__.py +++ b/custom_components/comfoclime/__init__.py @@ -97,7 +97,7 @@ async def handle_set_scenario_mode_service(call: ServiceCall): - cooking: High ventilation for cooking (default: 30 min) - party: High ventilation for parties (default: 30 min) - away: Reduced mode for vacation (default: 24 hours) - - boost: Maximum power boost (default: 30 min) + - scenario_boost: Maximum power boost (default: 30 min) """ entity_id = call.data["entity_id"] scenario = call.data["scenario"] diff --git a/custom_components/comfoclime/climate.py b/custom_components/comfoclime/climate.py index 792c503..543a0b6 100755 --- a/custom_components/comfoclime/climate.py +++ b/custom_components/comfoclime/climate.py @@ -56,13 +56,15 @@ PRESET_SCENARIO_COOKING = "cooking" PRESET_SCENARIO_PARTY = "party" +PRESET_SCENARIO_AWAY = "away" +PRESET_SCENARIO_BOOST = "scenario_boost" -# Scenario mapping +# Scenario mapping - uses unique preset names to avoid conflicts with PRESET_MAPPING SCENARIO_MAPPING = { SCENARIO_COOKING: PRESET_SCENARIO_COOKING, SCENARIO_PARTY: PRESET_SCENARIO_PARTY, - SCENARIO_HOLIDAY: PRESET_AWAY, - SCENARIO_BOOST_MODE: PRESET_BOOST, + SCENARIO_HOLIDAY: PRESET_SCENARIO_AWAY, + SCENARIO_BOOST_MODE: PRESET_SCENARIO_BOOST, } SCENARIO_REVERSE_MAPPING = {v: k for k, v in SCENARIO_MAPPING.items()}