Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions custom_components/luxtronik/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ 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."""

Expand Down Expand Up @@ -271,6 +282,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"]
Expand Down Expand Up @@ -591,6 +609,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"
Expand Down Expand Up @@ -825,6 +844,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"
Expand Down
69 changes: 68 additions & 1 deletion custom_components/luxtronik/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
DeviceKey,
LuxCalculation as LC,
LuxOperationMode,
LuxParameter as LP,
LuxSmartGridStatus,
LuxStatus1Option,
LuxStatus3Option,
SensorAttrKey as SA,
SensorKey,
)
from .coordinator import LuxtronikCoordinator, LuxtronikCoordinatorData
from .evu_helper import LuxtronikEVUTracker
Expand Down Expand Up @@ -84,7 +87,19 @@ 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)
)
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,
Expand Down Expand Up @@ -208,7 +223,13 @@ def _handle_coordinator_update(
self, data: LuxtronikCoordinatorData | None = None
) -> None:
"""Handle updated data from the coordinator."""
if self.entity_description.key == SensorKey.SMART_GRID_STATUS:
self._update_smart_grid_status()
return

# 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:
Expand Down Expand Up @@ -310,6 +331,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
Expand Down
10 changes: 10 additions & 0 deletions custom_components/luxtronik/sensor_entities_predefined.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -23,6 +24,7 @@
LuxCalculation as LC,
LuxOperationMode,
LuxParameter as LP,
LuxSmartGridStatus,
LuxStatus1Option,
LuxStatus3Option,
LuxSwitchoffReason,
Expand Down Expand Up @@ -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] = [
Expand Down
19 changes: 15 additions & 4 deletions custom_components/luxtronik/translations/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"evu_unlocked": {
"name": "HDO"
},
"evu2": {
"name": "EVU2"
},
"compressor": {
"name": "Kompresor"
},
Expand Down Expand Up @@ -131,8 +134,7 @@
},
"thermal_power_limitation_water": {
"name": "Thermal power limit: Water"
}
,
},
"thermal_power_limitation_cooling": {
"name": "Thermal power limit: Cooling"
},
Expand Down Expand Up @@ -270,6 +272,15 @@
}
}
},
"smart_grid_status": {
"name": "Stav SmartGrid",
"state": {
"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": {
"name": "Posledn\u00ed chyba",
"state": {
Expand Down Expand Up @@ -909,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.",
Expand Down
21 changes: 16 additions & 5 deletions custom_components/luxtronik/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"evu_unlocked": {
"name": "Sperrzeit Freigabe"
},
"evu2": {
"name": "EVU2"
},
"compressor": {
"name": "Verdichter"
},
Expand Down Expand Up @@ -131,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"
Expand Down Expand Up @@ -270,6 +272,15 @@
}
}
},
"smart_grid_status": {
"name": "SmartGrid Status",
"state": {
"evu_locked": "EVU-Sperre",
"reduced_operation": "abgesenkte Betriebsweise",
"normal_operation": "Normalbetrieb",
"increased_operation": "erh\u00f6hte Betriebsweise"
}
},
"error_reason": {
"name": "Letzter Fehler",
"state": {
Expand Down Expand Up @@ -917,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.",
Expand Down Expand Up @@ -952,4 +963,4 @@
}
}
}
}
}
12 changes: 12 additions & 0 deletions custom_components/luxtronik/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"evu_unlocked": {
"name": "Locktime"
},
"evu2": {
"name": "EVU2"
},
"compressor": {
"name": "Compressor"
},
Expand Down Expand Up @@ -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": {
Expand Down
12 changes: 12 additions & 0 deletions custom_components/luxtronik/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"evu_unlocked": {
"name": "Tijdvrijgave blokkeren"
},
"evu2": {
"name": "EVU2"
},
"compressor": {
"name": "Compressor"
},
Expand Down Expand Up @@ -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": {
Expand Down
Loading
Loading