From a5039ac1e498e456626891b964a4855b690043e3 Mon Sep 17 00:00:00 2001 From: siedi Date: Thu, 1 Jan 2026 14:12:36 +0000 Subject: [PATCH 1/6] feat: #500 Add EVU2 and SmartGrid status sensors - Add EVU2 binary sensor (calculation ID 185: ID_WEB_HZIO_EVU2) - Add SmartGrid status sensor with 4 states based on EVU+EVU2 - SmartGrid status: locked, reduced, normal, increased operation - Dynamic state-based icons for SmartGrid status - Sensor becomes unavailable when SmartGrid switch is disabled - Add translations for DE, EN, CS, NL, PL --- .../binary_sensor_entities_predefined.py | 8 ++- custom_components/luxtronik/const.py | 19 +++++++ custom_components/luxtronik/sensor.py | 56 ++++++++++++++++++- .../luxtronik/sensor_entities_predefined.py | 10 ++++ .../luxtronik/translations/cs.json | 12 ++++ .../luxtronik/translations/de.json | 12 ++++ .../luxtronik/translations/en.json | 12 ++++ .../luxtronik/translations/nl.json | 12 ++++ .../luxtronik/translations/pl.json | 12 ++++ 9 files changed, 149 insertions(+), 4 deletions(-) diff --git a/custom_components/luxtronik/binary_sensor_entities_predefined.py b/custom_components/luxtronik/binary_sensor_entities_predefined.py index d7eca7ef..01151bce 100644 --- a/custom_components/luxtronik/binary_sensor_entities_predefined.py +++ b/custom_components/luxtronik/binary_sensor_entities_predefined.py @@ -10,10 +10,14 @@ LuxtronikBinarySensorEntityDescription( key=SensorKey.EVU_UNLOCKED, luxtronik_key=LC.C0031_EVU_UNLOCKED, - icon="mdi:lock", - device_class=BinarySensorDeviceClass.LOCK, + icon="mdi:transmission-tower", visibility=LV.V0121_EVU_LOCKED, ), + LuxtronikBinarySensorEntityDescription( + key=SensorKey.EVU2, + luxtronik_key=LC.C0185_EVU2, + icon="mdi:transmission-tower", + ), LuxtronikBinarySensorEntityDescription( key=SensorKey.COMPRESSOR, luxtronik_key=LC.C0044_COMPRESSOR, diff --git a/custom_components/luxtronik/const.py b/custom_components/luxtronik/const.py index 1084c702..f60da29f 100644 --- a/custom_components/luxtronik/const.py +++ b/custom_components/luxtronik/const.py @@ -151,6 +151,15 @@ class LuxMode(StrEnum): holidays: Final = "Holidays" +class LuxSmartGridStatus(StrEnum): + """SmartGrid status based on EVU and EVU2 inputs.""" + + locked: Final = "evu_locked" # EVU=1, EVU2=0 - Status 1 - EVU lock + reduced: Final = "reduced_operation" # EVU=0, EVU2=0 - Status 2 - Reduced operation + normal: Final = "normal_operation" # EVU=0, EVU2=1 - Status 3 - Normal operation + increased: Final = "increased_operation" # EVU=1, EVU2=1 - Status 4 - Increased operation + + class LuxStatus1Option(StrEnum): """LuxStatus1 option defrost etc.""" @@ -271,6 +280,13 @@ class LuxSwitchoffReason(Enum): LuxOperationMode.cooling.value: "mdi:air-conditioner", } +LUX_SMART_GRID_ICON_MAP: Final[dict[StateType | date | datetime | Decimal, str]] = { + LuxSmartGridStatus.locked.value: "mdi:cancel", # EVU lock - Blocked + LuxSmartGridStatus.reduced.value: "mdi:arrow-down-circle", # Reduced operation + LuxSmartGridStatus.normal.value: "mdi:check-circle", # Normal operation + LuxSmartGridStatus.increased.value: "mdi:arrow-up-circle", # Increased operation +} + LUX_MODELS_ALPHA_INNOTEC = ["LWP", "LWV", "MSW", "SWC", "SWP"] LUX_MODELS_NOVELAN = ["BW", "LA", "LD", "LI", "SI", "ZLW"] LUX_MODELS_OTHER = ["CB", "CI", "CN", "CS"] @@ -591,6 +607,7 @@ class LuxCalculation(StrEnum): C0180_HIGH_PRESSURE: Final = "calculations.ID_WEB_LIN_HD" C0181_LOW_PRESSURE: Final = "calculations.ID_WEB_LIN_ND" C0182_COMPRESSOR_HEATER: Final = "calculations.ID_WEB_LIN_VDH_out" + C0185_EVU2: Final = "calculations.ID_WEB_HZIO_EVU2" # C0187_CURRENT_OUTPUT: Final = "calculations.ID_WEB_SEC_Qh_Soll" # C0188_CURRENT_OUTPUT: Final = "calculations.ID_WEB_SEC_Qh_Ist" C0204_HEAT_SOURCE_INPUT_TEMPERATURE: Final = "calculations.ID_WEB_Temperatur_TWE" @@ -825,6 +842,8 @@ class SensorKey(StrEnum): ) SOLAR_PUMP_MAX_TEMPERATURE_COLLECTOR = "solar_pump_max_temperature_collector" EVU_UNLOCKED = "evu_unlocked" + EVU2 = "evu2" + SMART_GRID_STATUS = "smart_grid_status" COMPRESSOR = "compressor" COMPRESSOR2 = "compressor2" PUMP_FLOW = "pump_flow" diff --git a/custom_components/luxtronik/sensor.py b/custom_components/luxtronik/sensor.py index dad83ef2..c92cf7af 100644 --- a/custom_components/luxtronik/sensor.py +++ b/custom_components/luxtronik/sensor.py @@ -27,9 +27,11 @@ DeviceKey, LuxCalculation as LC, LuxOperationMode, + LuxSmartGridStatus, LuxStatus1Option, LuxStatus3Option, SensorAttrKey as SA, + SensorKey, ) from .coordinator import LuxtronikCoordinator, LuxtronikCoordinatorData from .evu_helper import LuxtronikEVUTracker @@ -84,7 +86,10 @@ async def async_setup_entry( for description in SENSORS_STATUS if ( coordinator.entity_active(description) - and key_exists(coordinator.data, description.luxtronik_key) + and ( + description.luxtronik_key == LC.UNSET + or key_exists(coordinator.data, description.luxtronik_key) + ) ) ], True, @@ -208,7 +213,54 @@ def _handle_coordinator_update( self, data: LuxtronikCoordinatorData | None = None ) -> None: """Handle updated data from the coordinator.""" - super()._handle_coordinator_update(data) + # Calculate SmartGrid Status from EVU and EVU2 inputs + if self.entity_description.key == SensorKey.SMART_GRID_STATUS: + # Check if SmartGrid is enabled (P1030) + from .const import LuxParameter as LP + smartgrid_enabled = self._get_value(LP.P1030_SMART_GRID_SWITCH) + + # If SmartGrid is disabled, set sensor to unavailable + if not smartgrid_enabled or smartgrid_enabled in [False, 0, "false", "False"]: + self._attr_available = False + self._attr_native_value = None + else: + self._attr_available = True + + evu = self._get_value(LC.C0031_EVU_UNLOCKED) + evu2 = self._get_value(LC.C0185_EVU2) + + # Convert to boolean (handle True/False/1/0/"true"/"false") + evu_on = evu in [True, 1, "true", "True"] + evu2_on = evu2 in [True, 1, "true", "True"] + + # Determine SmartGrid status based on EVU and EVU2 inputs + # EVU=0, EVU2=0 → Status 2 (reduced operation) + # EVU=1, EVU2=0 → Status 1 (EVU locked) + # EVU=0, EVU2=1 → Status 3 (normal operation) + # EVU=1, EVU2=1 → Status 4 (increased operation) + if evu_on and not evu2_on: + self._attr_native_value = LuxSmartGridStatus.locked.value # Status 1 + elif not evu_on and not evu2_on: + self._attr_native_value = LuxSmartGridStatus.reduced.value # Status 2 + elif not evu_on and evu2_on: + self._attr_native_value = LuxSmartGridStatus.normal.value # Status 3 + else: # evu_on and evu2_on + self._attr_native_value = LuxSmartGridStatus.increased.value # Status 4 + + # Set icon based on current state + descr = self.entity_description + if descr.icon_by_state and self._attr_native_value in descr.icon_by_state: + self._attr_icon = descr.icon_by_state.get(self._attr_native_value) + elif descr.icon: + self._attr_icon = descr.icon + + # Don't call super() to avoid setting value to None (luxtronik_key=UNSET) + self._enrich_extra_attributes() + self.async_write_ha_state() + else: + # For normal status sensors, use the parent's update logic + super()._handle_coordinator_update(data) + self._evu_tracker.update(self._attr_native_value) if self._attr_native_value is None or self._last_state is None: diff --git a/custom_components/luxtronik/sensor_entities_predefined.py b/custom_components/luxtronik/sensor_entities_predefined.py index 82146645..b22c13fe 100644 --- a/custom_components/luxtronik/sensor_entities_predefined.py +++ b/custom_components/luxtronik/sensor_entities_predefined.py @@ -14,6 +14,7 @@ from homeassistant.helpers.entity import EntityCategory from .const import ( + LUX_SMART_GRID_ICON_MAP, LUX_STATE_ICON_MAP, SECOND_TO_HOUR_FACTOR, UPDATE_INTERVAL_NORMAL, @@ -23,6 +24,7 @@ LuxCalculation as LC, LuxOperationMode, LuxParameter as LP, + LuxSmartGridStatus, LuxStatus1Option, LuxStatus3Option, LuxSwitchoffReason, @@ -56,6 +58,14 @@ options=[e.value for e in LuxOperationMode], update_interval=UPDATE_INTERVAL_NORMAL, ), + descr( + key=SensorKey.SMART_GRID_STATUS, + luxtronik_key=LC.UNSET, # Calculated from EVU and EVU2 inputs + icon_by_state=LUX_SMART_GRID_ICON_MAP, + device_class=SensorDeviceClass.ENUM, + options=[e.value for e in LuxSmartGridStatus], + update_interval=UPDATE_INTERVAL_NORMAL, + ), ] SENSORS_INDEX: list[descr] = [ diff --git a/custom_components/luxtronik/translations/cs.json b/custom_components/luxtronik/translations/cs.json index 0be7fdba..7127a0be 100644 --- a/custom_components/luxtronik/translations/cs.json +++ b/custom_components/luxtronik/translations/cs.json @@ -4,6 +4,9 @@ "evu_unlocked": { "name": "HDO" }, + "evu2": { + "name": "EVU2" + }, "compressor": { "name": "Kompresor" }, @@ -270,6 +273,15 @@ } } }, + "smart_grid_status": { + "name": "Stav SmartGrid", + "state": { + "evu_locked": "Blokováno EVU", + "reduced_operation": "Snížený provoz", + "normal_operation": "Normální provoz", + "increased_operation": "Zvýšený provoz" + } + }, "error_reason": { "name": "Posledn\u00ed chyba", "state": { diff --git a/custom_components/luxtronik/translations/de.json b/custom_components/luxtronik/translations/de.json index f1124eac..6a080936 100644 --- a/custom_components/luxtronik/translations/de.json +++ b/custom_components/luxtronik/translations/de.json @@ -4,6 +4,9 @@ "evu_unlocked": { "name": "Sperrzeit Freigabe" }, + "evu2": { + "name": "EVU2" + }, "compressor": { "name": "Verdichter" }, @@ -270,6 +273,15 @@ } } }, + "smart_grid_status": { + "name": "SmartGrid Status", + "state": { + "evu_locked": "EVU-Sperre", + "reduced_operation": "abgesenkte Betriebsweise", + "normal_operation": "Normalbetrieb", + "increased_operation": "erhöhte Betriebsweise" + } + }, "error_reason": { "name": "Letzter Fehler", "state": { diff --git a/custom_components/luxtronik/translations/en.json b/custom_components/luxtronik/translations/en.json index 115f8f91..7cad2c26 100644 --- a/custom_components/luxtronik/translations/en.json +++ b/custom_components/luxtronik/translations/en.json @@ -4,6 +4,9 @@ "evu_unlocked": { "name": "Locktime" }, + "evu2": { + "name": "EVU2" + }, "compressor": { "name": "Compressor" }, @@ -273,6 +276,15 @@ } } }, + "smart_grid_status": { + "name": "SmartGrid Status", + "state": { + "evu_locked": "EVU locked", + "reduced_operation": "Reduced operation", + "normal_operation": "Normal operation", + "increased_operation": "Increased operation" + } + }, "error_reason": { "name": "Last error", "state": { diff --git a/custom_components/luxtronik/translations/nl.json b/custom_components/luxtronik/translations/nl.json index 2e278f7a..9675c6e4 100644 --- a/custom_components/luxtronik/translations/nl.json +++ b/custom_components/luxtronik/translations/nl.json @@ -4,6 +4,9 @@ "evu_unlocked": { "name": "Tijdvrijgave blokkeren" }, + "evu2": { + "name": "EVU2" + }, "compressor": { "name": "Compressor" }, @@ -267,6 +270,15 @@ } } }, + "smart_grid_status": { + "name": "SmartGrid Status", + "state": { + "evu_locked": "EVU geblokkeerd", + "reduced_operation": "Verminderde werking", + "normal_operation": "Normale werking", + "increased_operation": "Verhoogde werking" + } + }, "error_reason": { "name": "Laatste fout", "state": { diff --git a/custom_components/luxtronik/translations/pl.json b/custom_components/luxtronik/translations/pl.json index 186a5fa4..252d5b15 100644 --- a/custom_components/luxtronik/translations/pl.json +++ b/custom_components/luxtronik/translations/pl.json @@ -4,6 +4,9 @@ "evu_unlocked": { "name": "Czas blokady" }, + "evu2": { + "name": "EVU2" + }, "compressor": { "name": "Kompresor" }, @@ -261,6 +264,15 @@ } } }, + "smart_grid_status": { + "name": "Status SmartGrid", + "state": { + "evu_locked": "Zablokowane EVU", + "reduced_operation": "Praca obniżona", + "normal_operation": "Praca normalna", + "increased_operation": "Praca zwiększona" + } + }, "error_reason": { "name": "Ostatni b\u0142\u0105d", "state": { From bceb844174c2d4448032af7d073a5dc241e7f654 Mon Sep 17 00:00:00 2001 From: siedi <4923159+siedi@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:21:25 +0000 Subject: [PATCH 2/6] chore: format code with ruff --- custom_components/luxtronik/const.py | 4 +++- custom_components/luxtronik/sensor.py | 29 +++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/custom_components/luxtronik/const.py b/custom_components/luxtronik/const.py index f60da29f..339012b6 100644 --- a/custom_components/luxtronik/const.py +++ b/custom_components/luxtronik/const.py @@ -157,7 +157,9 @@ class LuxSmartGridStatus(StrEnum): locked: Final = "evu_locked" # EVU=1, EVU2=0 - Status 1 - EVU lock reduced: Final = "reduced_operation" # EVU=0, EVU2=0 - Status 2 - Reduced operation normal: Final = "normal_operation" # EVU=0, EVU2=1 - Status 3 - Normal operation - increased: Final = "increased_operation" # EVU=1, EVU2=1 - Status 4 - Increased operation + increased: Final = ( + "increased_operation" # EVU=1, EVU2=1 - Status 4 - Increased operation + ) class LuxStatus1Option(StrEnum): diff --git a/custom_components/luxtronik/sensor.py b/custom_components/luxtronik/sensor.py index c92cf7af..59d5283d 100644 --- a/custom_components/luxtronik/sensor.py +++ b/custom_components/luxtronik/sensor.py @@ -217,10 +217,16 @@ def _handle_coordinator_update( if self.entity_description.key == SensorKey.SMART_GRID_STATUS: # Check if SmartGrid is enabled (P1030) from .const import LuxParameter as LP + smartgrid_enabled = self._get_value(LP.P1030_SMART_GRID_SWITCH) # If SmartGrid is disabled, set sensor to unavailable - if not smartgrid_enabled or smartgrid_enabled in [False, 0, "false", "False"]: + if not smartgrid_enabled or smartgrid_enabled in [ + False, + 0, + "false", + "False", + ]: self._attr_available = False self._attr_native_value = None else: @@ -239,17 +245,28 @@ def _handle_coordinator_update( # EVU=0, EVU2=1 → Status 3 (normal operation) # EVU=1, EVU2=1 → Status 4 (increased operation) if evu_on and not evu2_on: - self._attr_native_value = LuxSmartGridStatus.locked.value # Status 1 + self._attr_native_value = ( + LuxSmartGridStatus.locked.value + ) # Status 1 elif not evu_on and not evu2_on: - self._attr_native_value = LuxSmartGridStatus.reduced.value # Status 2 + self._attr_native_value = ( + LuxSmartGridStatus.reduced.value + ) # Status 2 elif not evu_on and evu2_on: - self._attr_native_value = LuxSmartGridStatus.normal.value # Status 3 + self._attr_native_value = ( + LuxSmartGridStatus.normal.value + ) # Status 3 else: # evu_on and evu2_on - self._attr_native_value = LuxSmartGridStatus.increased.value # Status 4 + self._attr_native_value = ( + LuxSmartGridStatus.increased.value + ) # Status 4 # Set icon based on current state descr = self.entity_description - if descr.icon_by_state and self._attr_native_value in descr.icon_by_state: + if ( + descr.icon_by_state + and self._attr_native_value in descr.icon_by_state + ): self._attr_icon = descr.icon_by_state.get(self._attr_native_value) elif descr.icon: self._attr_icon = descr.icon From ff664f4f7fc99a57f4c3380583ca9f915c8e307f Mon Sep 17 00:00:00 2001 From: siedi Date: Tue, 6 Jan 2026 20:37:22 +0100 Subject: [PATCH 3/6] chore: use Unicode escapes in DE/CS/PL translations --- .../luxtronik/translations/cs.json | 15 +++++++-------- .../luxtronik/translations/de.json | 11 +++++------ .../luxtronik/translations/pl.json | 17 ++++++++--------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/custom_components/luxtronik/translations/cs.json b/custom_components/luxtronik/translations/cs.json index 7127a0be..24fba5f6 100644 --- a/custom_components/luxtronik/translations/cs.json +++ b/custom_components/luxtronik/translations/cs.json @@ -134,8 +134,7 @@ }, "thermal_power_limitation_water": { "name": "Thermal power limit: Water" - } - , + }, "thermal_power_limitation_cooling": { "name": "Thermal power limit: Cooling" }, @@ -276,10 +275,10 @@ "smart_grid_status": { "name": "Stav SmartGrid", "state": { - "evu_locked": "Blokováno EVU", - "reduced_operation": "Snížený provoz", - "normal_operation": "Normální provoz", - "increased_operation": "Zvýšený provoz" + "evu_locked": "Blokov\u00e1no EVU", + "reduced_operation": "Sn\u00ed\u017een\u00fd provoz", + "normal_operation": "Norm\u00e1ln\u00ed provoz", + "increased_operation": "Zv\u00fd\u0161en\u00fd provoz" } }, "error_reason": { @@ -921,10 +920,10 @@ }, "select_devices": { "title": "Vyberte za\u0159\u00edzen\u00ed k nakonfigurov\u00e1n\u00ed", - "description": "Vyberte jedno nebo v\u00edce za\u0159\u00edzen\u00ed k p\u0159id\u00e1n\u00ed. Ji\u017e nakonfigurovan\u00e1 za\u0159\u00edzen\u00ed: {configured}", + "description": "Vyberte jedno nebo v\u00edce za\u0159\u00edzen\u00ed k p\u0159id\u00e1n\u00ed. Ji\u017e nakonfigurovan\u00e1 za\u0159\u00edzen\u00ed: {configured}", "data": { "select_device_to_configure": "Vyberte za\u0159\u00edzen\u00ed, kter\u00e9 chcete nakonfigurovat" - } + } }, "options": { "description": "Nastaven\u00ed pro tepeln\u00e9 \u010derpadlo Luxtronik {name}.\nTato nastaven\u00ed lze pozd\u011bji zm\u011bnit v sekci Integrace.", diff --git a/custom_components/luxtronik/translations/de.json b/custom_components/luxtronik/translations/de.json index 6a080936..05c615ca 100644 --- a/custom_components/luxtronik/translations/de.json +++ b/custom_components/luxtronik/translations/de.json @@ -134,10 +134,9 @@ }, "thermal_power_limitation_water": { "name": "Therm. Leistung max: Warmwasser" - } - , + }, "thermal_power_limitation_cooling": { - "name": "Therm. Leistung max: Kühlung" + "name": "Therm. Leistung max: K\u00fchlung" }, "heating_threshold": { "name": "Heizgrenze" @@ -279,7 +278,7 @@ "evu_locked": "EVU-Sperre", "reduced_operation": "abgesenkte Betriebsweise", "normal_operation": "Normalbetrieb", - "increased_operation": "erhöhte Betriebsweise" + "increased_operation": "erh\u00f6hte Betriebsweise" } }, "error_reason": { @@ -929,7 +928,7 @@ "description": "W\u00e4hlen Sie ein oder mehrere Ger\u00e4te zur Konfiguration aus. Bereits konfigurierte Ger\u00e4te: {configured}", "data": { "select_device_to_configure": "W\u00e4hlen Sie das Ger\u00e4t aus, das Sie konfigurieren m\u00f6chten" - } + } }, "options": { "description": "Einstellungen f\u00fcr die Luxtronik-W\u00e4rmepumpe {name}.\nDiese Einstellungen k\u00f6nnen sp\u00e4ter unter Integrationen ge\u00e4ndert werden.", @@ -964,4 +963,4 @@ } } } -} +} \ No newline at end of file diff --git a/custom_components/luxtronik/translations/pl.json b/custom_components/luxtronik/translations/pl.json index 252d5b15..6505ab97 100644 --- a/custom_components/luxtronik/translations/pl.json +++ b/custom_components/luxtronik/translations/pl.json @@ -134,8 +134,7 @@ }, "thermal_power_limitation_water": { "name": "Thermal power limit: Water" - } - , + }, "thermal_power_limitation_cooling": { "name": "Thermal power limit: Cooling" }, @@ -268,9 +267,9 @@ "name": "Status SmartGrid", "state": { "evu_locked": "Zablokowane EVU", - "reduced_operation": "Praca obniżona", + "reduced_operation": "Praca obni\u017cona", "normal_operation": "Praca normalna", - "increased_operation": "Praca zwiększona" + "increased_operation": "Praca zwi\u0119kszona" } }, "error_reason": { @@ -585,13 +584,13 @@ }, "date": { "away_dhw_startdate": { - "name": "Data rozpoczęcia trybu nieobecno\u015bci - ciep\u0142a woda" + "name": "Data rozpocz\u0119cia trybu nieobecno\u015bci - ciep\u0142a woda" }, "away_dhw_enddate": { "name": "Data zako\u0144czenia trybu nieobecno\u015bci - ciep\u0142a woda" }, "away_heating_startdate": { - "name": "Data rozpoczęcia trybu nieobecno\u015bci - ogrzewanie" + "name": "Data rozpocz\u0119cia trybu nieobecno\u015bci - ogrzewanie" }, "away_heating_enddate": { "name": "Data zako\u0144czenia trybu nieobecno\u015bci - ogrzewanie" @@ -604,9 +603,9 @@ "None": "Brak", "Monday": "Poniedzia\u0142ek", "Tuesday": "Wtorek", - "Wednesday": "Środa", + "Wednesday": "\u015aroda", "Thursday": "Czwartek", - "Friday": "Piątek", + "Friday": "Pi\u0105tek", "Saturday": "Sobota", "Sunday": "Niedziela" } @@ -665,7 +664,7 @@ }, "select_devices": { "title": "Wybierz urz\u0105dzenia do skonfigurowania", - "description": "Wybierz jedno lub wi\u0119cej urz\u0105dze\u0144 do dodania. Ju\u017c skonfigurowane urz\u0105dzenia: {configured}", + "description": "Wybierz jedno lub wi\u0119cej urz\u0105dze\u0144 do dodania. Ju\u017c skonfigurowane urz\u0105dzenia: {configured}", "data": { "select_device_to_configure": "Wybierz urz\u0105dzenie, kt\u00f3re chcesz skonfigurowa\u0107" } From 71beaf0dcc65c3f3384d1d7c7b0336f3d6d4480c Mon Sep 17 00:00:00 2001 From: siedi Date: Wed, 7 Jan 2026 09:20:09 +0100 Subject: [PATCH 4/6] refactor: extract SmartGrid status calculation into separate method --- custom_components/luxtronik/sensor.py | 107 +++++++++++--------------- 1 file changed, 47 insertions(+), 60 deletions(-) diff --git a/custom_components/luxtronik/sensor.py b/custom_components/luxtronik/sensor.py index 59d5283d..36058a9d 100644 --- a/custom_components/luxtronik/sensor.py +++ b/custom_components/luxtronik/sensor.py @@ -213,67 +213,8 @@ def _handle_coordinator_update( self, data: LuxtronikCoordinatorData | None = None ) -> None: """Handle updated data from the coordinator.""" - # Calculate SmartGrid Status from EVU and EVU2 inputs if self.entity_description.key == SensorKey.SMART_GRID_STATUS: - # Check if SmartGrid is enabled (P1030) - from .const import LuxParameter as LP - - smartgrid_enabled = self._get_value(LP.P1030_SMART_GRID_SWITCH) - - # If SmartGrid is disabled, set sensor to unavailable - if not smartgrid_enabled or smartgrid_enabled in [ - False, - 0, - "false", - "False", - ]: - self._attr_available = False - self._attr_native_value = None - else: - self._attr_available = True - - evu = self._get_value(LC.C0031_EVU_UNLOCKED) - evu2 = self._get_value(LC.C0185_EVU2) - - # Convert to boolean (handle True/False/1/0/"true"/"false") - evu_on = evu in [True, 1, "true", "True"] - evu2_on = evu2 in [True, 1, "true", "True"] - - # Determine SmartGrid status based on EVU and EVU2 inputs - # EVU=0, EVU2=0 → Status 2 (reduced operation) - # EVU=1, EVU2=0 → Status 1 (EVU locked) - # EVU=0, EVU2=1 → Status 3 (normal operation) - # EVU=1, EVU2=1 → Status 4 (increased operation) - if evu_on and not evu2_on: - self._attr_native_value = ( - LuxSmartGridStatus.locked.value - ) # Status 1 - elif not evu_on and not evu2_on: - self._attr_native_value = ( - LuxSmartGridStatus.reduced.value - ) # Status 2 - elif not evu_on and evu2_on: - self._attr_native_value = ( - LuxSmartGridStatus.normal.value - ) # Status 3 - else: # evu_on and evu2_on - self._attr_native_value = ( - LuxSmartGridStatus.increased.value - ) # Status 4 - - # Set icon based on current state - descr = self.entity_description - if ( - descr.icon_by_state - and self._attr_native_value in descr.icon_by_state - ): - self._attr_icon = descr.icon_by_state.get(self._attr_native_value) - elif descr.icon: - self._attr_icon = descr.icon - - # Don't call super() to avoid setting value to None (luxtronik_key=UNSET) - self._enrich_extra_attributes() - self.async_write_ha_state() + self._update_smart_grid_status() else: # For normal status sensors, use the parent's update logic super()._handle_coordinator_update(data) @@ -379,6 +320,52 @@ def _build_status_text(self) -> str: else f"{line_1} {line_2} {status_time}." ) + def _update_smart_grid_status(self) -> None: + """Calculate and update SmartGrid status based on EVU and EVU2 inputs.""" + from .const import LuxParameter as LP + + # Check if SmartGrid is enabled (P1030) + smartgrid_enabled = self._get_value(LP.P1030_SMART_GRID_SWITCH) + + # If SmartGrid is disabled, set sensor to unavailable + if not smartgrid_enabled or smartgrid_enabled in [False, 0, "false", "False"]: + self._attr_available = False + self._attr_native_value = None + else: + self._attr_available = True + + evu = self._get_value(LC.C0031_EVU_UNLOCKED) + evu2 = self._get_value(LC.C0185_EVU2) + + # Convert to boolean (handle True/False/1/0/"true"/"false") + evu_on = evu in [True, 1, "true", "True"] + evu2_on = evu2 in [True, 1, "true", "True"] + + # Determine SmartGrid status based on EVU and EVU2 inputs + # EVU=0, EVU2=0 → Status 2 (reduced operation) + # EVU=1, EVU2=0 → Status 1 (EVU locked) + # EVU=0, EVU2=1 → Status 3 (normal operation) + # EVU=1, EVU2=1 → Status 4 (increased operation) + if evu_on and not evu2_on: + self._attr_native_value = LuxSmartGridStatus.locked.value # Status 1 + elif not evu_on and not evu2_on: + self._attr_native_value = LuxSmartGridStatus.reduced.value # Status 2 + elif not evu_on and evu2_on: + self._attr_native_value = LuxSmartGridStatus.normal.value # Status 3 + else: # evu_on and evu2_on + self._attr_native_value = LuxSmartGridStatus.increased.value # Status 4 + + # Set icon based on current state + descr = self.entity_description + if descr.icon_by_state and self._attr_native_value in descr.icon_by_state: + self._attr_icon = descr.icon_by_state.get(self._attr_native_value) + elif descr.icon: + self._attr_icon = descr.icon + + # Don't call super() to avoid setting value to None (luxtronik_key=UNSET) + self._enrich_extra_attributes() + self.async_write_ha_state() + class LuxtronikIndexSensor(LuxtronikSensorEntity, SensorEntity): _min_index = 0 From 23762525f23a7dee4166ab179fbaad4a51716567 Mon Sep 17 00:00:00 2001 From: siedi Date: Wed, 7 Jan 2026 09:29:57 +0100 Subject: [PATCH 5/6] fix: add firmware compatibility check for SmartGrid status sensor --- custom_components/luxtronik/sensor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/custom_components/luxtronik/sensor.py b/custom_components/luxtronik/sensor.py index 36058a9d..2f6e76a0 100644 --- a/custom_components/luxtronik/sensor.py +++ b/custom_components/luxtronik/sensor.py @@ -27,6 +27,7 @@ DeviceKey, LuxCalculation as LC, LuxOperationMode, + LuxParameter as LP, LuxSmartGridStatus, LuxStatus1Option, LuxStatus3Option, @@ -90,6 +91,15 @@ async def async_setup_entry( description.luxtronik_key == LC.UNSET or key_exists(coordinator.data, description.luxtronik_key) ) + and ( + # For SmartGrid status sensor, check if required parameters exist + description.key != SensorKey.SMART_GRID_STATUS + or ( + key_exists(coordinator.data, LP.P1030_SMART_GRID_SWITCH) + and key_exists(coordinator.data, LC.C0031_EVU_UNLOCKED) + and key_exists(coordinator.data, LC.C0185_EVU2) + ) + ) ) ], True, From a6967e7aba2b0920848868c08eeaae5870c69d14 Mon Sep 17 00:00:00 2001 From: siedi Date: Fri, 9 Jan 2026 17:59:09 +0100 Subject: [PATCH 6/6] fix: prevent irrelevant attributes on SmartGrid status sensor --- custom_components/luxtronik/sensor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/custom_components/luxtronik/sensor.py b/custom_components/luxtronik/sensor.py index 2f6e76a0..293feabd 100644 --- a/custom_components/luxtronik/sensor.py +++ b/custom_components/luxtronik/sensor.py @@ -225,9 +225,10 @@ def _handle_coordinator_update( """Handle updated data from the coordinator.""" if self.entity_description.key == SensorKey.SMART_GRID_STATUS: self._update_smart_grid_status() - else: - # For normal status sensors, use the parent's update logic - super()._handle_coordinator_update(data) + return + + # For normal status sensors, use the parent's update logic + super()._handle_coordinator_update(data) self._evu_tracker.update(self._attr_native_value)