Skip to content

Commit fb715d8

Browse files
committed
Add entities for Startdate & Enddate Away mode (Heat+DHW)
Also add translations for Thermal desinfection day
1 parent a3dc5ef commit fb715d8

File tree

11 files changed

+359
-12
lines changed

11 files changed

+359
-12
lines changed

custom_components/luxtronik/base.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ def __init__(
6969
)
7070
else:
7171
self._attr_extra_state_attributes[field] = value
72-
if description.translation_key is None:
73-
description.translation_key = description.key.value
7472
if description.entity_registry_enabled_default:
7573
description.entity_registry_enabled_default = coordinator.entity_visible(
7674
description

custom_components/luxtronik/const.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
Platform.WATER_HEATER,
3434
Platform.CLIMATE,
3535
Platform.SELECT,
36+
Platform.DATE,
3637
]
3738
UPDATE_INTERVAL_FAST: Final = timedelta(seconds=10)
3839
UPDATE_INTERVAL_NORMAL: Final = timedelta(minutes=1)
@@ -460,6 +461,10 @@ class LuxParameter(StrEnum):
460461
P1158_POWER_LIMIT_SWITCH: Final = "parameters.Unknown_Parameter_1158"
461462
P1159_POWER_LIMIT_VALUE: Final = "parameters.Unknown_Parameter_1159"
462463

464+
P0731_AWAY_HEATING_STARTDATE: Final = "parameters.ID_SU_FstdHz"
465+
P0006_AWAY_HEATING_ENDDATE: Final = "parameters.ID_SU_FrkdHz"
466+
P0732_AWAY_DHW_STARTDATE: Final = "parameters.ID_SU_FstdBw"
467+
P0007_AWAY_DHW_ENDDATE: Final = "parameters.ID_SU_FrkdBw"
463468

464469
# endregion Lux parameters
465470

@@ -841,6 +846,12 @@ class SensorKey(StrEnum):
841846
PUMP_VENT_ACTIVE = "pump_vent_active"
842847
THERMAL_DESINFECTION_DAY = "thermal_desinfection_day"
843848

849+
AWAY_HEATING_STARTDATE = "away_heating_startdate"
850+
AWAY_HEATING_ENDDATE = "away_heating_enddate"
851+
AWAY_DHW_STARTDATE = "away_dhw_startdate"
852+
AWAY_DHW_ENDDATE = "away_dhw_enddate"
853+
854+
844855
# endregion Keys
845856

846857

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from __future__ import annotations
2+
3+
from datetime import date, datetime
4+
5+
from homeassistant.components.date import DateEntity, ENTITY_ID_FORMAT
6+
from homeassistant.config_entries import ConfigEntry
7+
from homeassistant.core import HomeAssistant, callback
8+
from homeassistant.exceptions import ConfigEntryNotReady
9+
from homeassistant.helpers.entity_platform import AddEntitiesCallback
10+
from homeassistant.util import dt as dt_util
11+
12+
from .base import LuxtronikEntity
13+
from .common import get_sensor_data, key_exists
14+
from .const import (
15+
CONF_COORDINATOR,
16+
CONF_HA_SENSOR_PREFIX,
17+
DOMAIN,
18+
LOGGER,
19+
DeviceKey,
20+
)
21+
from .coordinator import LuxtronikCoordinator, LuxtronikCoordinatorData
22+
from .date_entities_predefined import CALENDAR_ENTITIES
23+
from .model import LuxtronikDateEntityDescription
24+
25+
async def async_setup_entry(
26+
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
27+
) -> None:
28+
data = hass.data.get(DOMAIN, {}).get(entry.entry_id)
29+
if not data or CONF_COORDINATOR not in data:
30+
raise ConfigEntryNotReady
31+
32+
coordinator: LuxtronikCoordinator = data[CONF_COORDINATOR]
33+
34+
if not coordinator.last_update_success:
35+
raise ConfigEntryNotReady
36+
37+
unavailable_keys = [
38+
i.luxtronik_key
39+
for i in CALENDAR_ENTITIES
40+
if not key_exists(coordinator.data, i.luxtronik_key)
41+
]
42+
if unavailable_keys:
43+
LOGGER.warning("Not present in Luxtronik data, skipping: %s", unavailable_keys)
44+
45+
async_add_entities(
46+
[
47+
LuxtronikDateEntity(
48+
hass, entry, coordinator, description, description.device_key
49+
)
50+
for description in CALENDAR_ENTITIES
51+
if (
52+
coordinator.entity_active(description)
53+
and key_exists(coordinator.data, description.luxtronik_key)
54+
)
55+
],
56+
True,
57+
)
58+
59+
class LuxtronikDateEntity(LuxtronikEntity, DateEntity):
60+
"""Luxtronik Date Entity that supports user-editable dates."""
61+
62+
entity_description: LuxtronikDateEntityDescription
63+
64+
def __init__(
65+
self,
66+
hass: HomeAssistant,
67+
entry: ConfigEntry,
68+
coordinator: LuxtronikCoordinator,
69+
description: LuxtronikDateEntityDescription,
70+
device_info_ident: DeviceKey,
71+
) -> None:
72+
super().__init__(
73+
coordinator=coordinator,
74+
description=description,
75+
device_info_ident=device_info_ident,
76+
)
77+
78+
prefix = entry.data[CONF_HA_SENSOR_PREFIX]
79+
self.entity_id = ENTITY_ID_FORMAT.format(f"{prefix}_{description.key}")
80+
self._attr_unique_id = self.entity_id
81+
82+
@callback
83+
def _handle_coordinator_update(
84+
self, data: LuxtronikCoordinatorData | None = None
85+
) -> None:
86+
data = self.coordinator.data if data is None else data
87+
if data is None:
88+
return
89+
90+
value = get_sensor_data(data, self.entity_description.luxtronik_key.value)
91+
92+
if isinstance(value, (int, float)):
93+
try:
94+
dt_value = datetime.fromtimestamp(value)
95+
self._attr_native_value = dt_value.date()
96+
except (ValueError, OSError):
97+
self._attr_native_value = None
98+
elif isinstance(value, date):
99+
self._attr_native_value = value
100+
else:
101+
self._attr_native_value = None
102+
103+
self.async_write_ha_state()
104+
super()._handle_coordinator_update()
105+
106+
107+
async def async_set_value(self, value: date) -> None:
108+
"""Handle user-set date from the UI."""
109+
self._attr_native_value = value
110+
timestamp = int(datetime.combine(value, datetime.min.time()).timestamp())
111+
await self.coordinator.async_write(
112+
self.entity_description.luxtronik_key.value.split(".")[1],
113+
timestamp
114+
)
115+
self.async_write_ha_state()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
3+
from homeassistant.helpers.entity import EntityCategory
4+
5+
from .const import (
6+
DeviceKey,
7+
LuxCalculation as LC,
8+
LuxParameter as LP,
9+
LuxVisibility as LV,
10+
SensorKey as SK,
11+
)
12+
from .model import (
13+
LuxtronikEntityAttributeDescription as attr,
14+
LuxtronikDateEntityDescription,
15+
)
16+
17+
18+
CALENDAR_ENTITIES: list[LuxtronikDateEntityDescription] = [
19+
LuxtronikDateEntityDescription(
20+
key=SK.AWAY_DHW_STARTDATE,
21+
luxtronik_key=LP.P0732_AWAY_DHW_STARTDATE,
22+
device_key=DeviceKey.domestic_water,
23+
entity_category=EntityCategory.CONFIG,
24+
),
25+
LuxtronikDateEntityDescription(
26+
key=SK.AWAY_DHW_ENDDATE,
27+
luxtronik_key=LP.P0007_AWAY_DHW_ENDDATE,
28+
device_key=DeviceKey.domestic_water,
29+
entity_category=EntityCategory.CONFIG,
30+
),
31+
LuxtronikDateEntityDescription(
32+
key=SK.AWAY_HEATING_STARTDATE,
33+
luxtronik_key=LP.P0731_AWAY_HEATING_STARTDATE,
34+
device_key=DeviceKey.heating,
35+
entity_category=EntityCategory.CONFIG,
36+
),
37+
LuxtronikDateEntityDescription(
38+
key=SK.AWAY_HEATING_ENDDATE,
39+
luxtronik_key=LP.P0006_AWAY_HEATING_ENDDATE,
40+
device_key=DeviceKey.heating,
41+
entity_category=EntityCategory.CONFIG,
42+
),
43+
]

custom_components/luxtronik/model.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
ClimateEntityFeature,
1818
HVACMode,
1919
)
20+
from homeassistant.components.date import DateEntityDescription
2021
from homeassistant.components.number import NumberEntityDescription, NumberMode
2122
from homeassistant.components.sensor import SensorEntityDescription
2223
from homeassistant.components.switch import SwitchEntityDescription
@@ -220,3 +221,13 @@ class LuxtronikUpdateEntityDescription(
220221

221222
device_class = UpdateDeviceClass.FIRMWARE
222223
platform = Platform.UPDATE
224+
225+
226+
@dataclass
227+
class LuxtronikDateEntityDescription(
228+
LuxtronikEntityDescription,
229+
DateEntityDescription,
230+
):
231+
"""Class describing Luxtronik date entities."""
232+
233+
platform = Platform.DATE

custom_components/luxtronik/select.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorEntity
44
from homeassistant.components.select import SelectEntity
55
from homeassistant.config_entries import ConfigEntry
6-
from homeassistant.core import HomeAssistant
6+
from homeassistant.core import HomeAssistant, callback
77
from homeassistant.exceptions import ConfigEntryNotReady
88
from homeassistant.helpers.entity import EntityCategory
99
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -21,7 +21,7 @@
2121
DeviceKey,
2222
SensorKey as SK
2323
)
24-
from .coordinator import LuxtronikCoordinator
24+
from .coordinator import LuxtronikCoordinator, LuxtronikCoordinatorData
2525
from .model import LuxtronikEntityDescription
2626

2727

@@ -42,11 +42,17 @@ async def async_setup_entry(
4242

4343
description = LuxtronikEntityDescription(
4444
key=SK.THERMAL_DESINFECTION_DAY,
45-
name="Thermal Desinfection Day",
4645
device_key=DeviceKey.domestic_water,
4746
luxtronik_key=LuxDaySelectorParameter.MONDAY, # Just one valid key for metadata
4847
)
49-
async_add_entities([LuxtronikThermalDesinfectionDaySelector(entry, coordinator,description, description.device_key)], True)
48+
async_add_entities(
49+
[
50+
LuxtronikThermalDesinfectionDaySelector(
51+
entry, coordinator,description, description.device_key
52+
)
53+
],
54+
True
55+
)
5056

5157

5258
class LuxtronikThermalDesinfectionDaySelector(LuxtronikEntity, SelectEntity):
@@ -66,7 +72,6 @@ def __init__(
6672
device_info_ident=device_info_ident,
6773
)
6874

69-
self._attr_name = "Thermal Desinfection Day"
7075
self._attr_options = DAY_SELECTOR_OPTIONS
7176
self._attr_current_option = "None"
7277
self._attr_entity_category = EntityCategory.CONFIG
@@ -77,20 +82,49 @@ def __init__(
7782
self.entity_id = ENTITY_ID_FORMAT.format(f"{prefix}_thermal_desinfection_day")
7883
self._attr_unique_id = self.entity_id
7984

85+
@callback
86+
def _handle_coordinator_update(
87+
self, data: LuxtronikCoordinatorData | None = None
88+
) -> None:
89+
"""Handle updated data from the coordinator."""
90+
# if not self.should_update():
91+
# return
92+
93+
super()._handle_coordinator_update()
94+
data = self.coordinator.data if data is None else data
95+
if data is None:
96+
return
97+
98+
selected_day = "None"
99+
for day, param_enum in DAY_NAME_TO_PARAM.items():
100+
param = param_enum.value
101+
value = get_sensor_data(data, param)
102+
if str(value) == "1":
103+
selected_day = day
104+
break
105+
106+
if self._attr_current_option != selected_day:
107+
self._attr_current_option = selected_day
108+
self.async_write_ha_state()
109+
110+
80111
async def async_select_option(self, option: str) -> None:
81112
"""Handle selection of a new day."""
82113
self._attr_current_option = option
83-
current_data = self.coordinator.data
114+
data = self.coordinator.data if data is None else data
115+
if data is None:
116+
return
84117

85118
for day, param_enum in DAY_NAME_TO_PARAM.items():
86119
param = param_enum.value
87120
desired_value = 1 if day == option else 0
88-
current_value = int(get_sensor_data(current_data, param))
121+
current_value = int(get_sensor_data(data, param))
89122

90123
if current_value != desired_value:
91-
await self.coordinator.async_write(param, desired_value)
92-
93-
self.async_write_ha_state()
124+
data = await self.coordinator.async_write(
125+
param.split(".")[1], desired_value
126+
)
127+
self._handle_coordinator_update(data)
94128

95129
async def async_update(self) -> None:
96130
"""Read current day from heat pump and update selected option."""

custom_components/luxtronik/translations/cs.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,35 @@
814814
"name": "Rozd\u00edl pr\u016ftoku ob\u011bhov\u00e9ho \u010derpadla"
815815
}
816816
},
817+
"date": {
818+
"away_dhw_startdate": {
819+
"name": "Datum za\u010d\u00e1tku re\u017eimu mimo domov - tepl\u00e1 voda"
820+
},
821+
"away_dhw_enddate": {
822+
"name": "Datum konce re\u017eimu mimo domov - tepl\u00e1 voda"
823+
},
824+
"away_heating_startdate": {
825+
"name": "Datum za\u010d\u00e1tku re\u017eimu mimo domov - vyt\u00e1p\u011bn\u00ed"
826+
},
827+
"away_heating_enddate": {
828+
"name": "Datum konce re\u017eimu mimo domov - vyt\u00e1p\u011bn\u00ed"
829+
}
830+
},
831+
"select": {
832+
"thermal_desinfection_day": {
833+
"name": "Den termick\u00e9 dezinfekce",
834+
"state": {
835+
"None": "\u017d\u00e1dn\u00fd",
836+
"Monday": "Pond\u011bl\u00ed",
837+
"Tuesday": "\u00dater\u00fd",
838+
"Wednesday": "St\u0159eda",
839+
"Thursday": "\u010ctvrtek",
840+
"Friday": "P\u00e1tek",
841+
"Saturday": "Sobota",
842+
"Sunday": "Ned\u011ble"
843+
}
844+
}
845+
},
817846
"climate": {
818847
"heating": {
819848
"name": "Topen\u00ed"

custom_components/luxtronik/translations/de.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,35 @@
819819
"name": "Umw\u00e4lzpumpe Delta"
820820
}
821821
},
822+
"date": {
823+
"away_dhw_startdate": {
824+
"name": "Abwesenheitsmodus Warmwasser Startdatum"
825+
},
826+
"away_dhw_enddate": {
827+
"name": "Abwesenheitsmodus Warmwasser Enddatum"
828+
},
829+
"away_heating_startdate": {
830+
"name": "Abwesenheitsmodus Heizung Startdatum"
831+
},
832+
"away_heating_enddate": {
833+
"name": "Abwesenheitsmodus Heizung Enddatum"
834+
}
835+
},
836+
"select": {
837+
"thermal_desinfection_day": {
838+
"name": "Tag der thermischen Desinfektion",
839+
"state": {
840+
"None": "Keine",
841+
"Monday": "Montag",
842+
"Tuesday": "Dienstag",
843+
"Wednesday": "Mittwoch",
844+
"Thursday": "Donnerstag",
845+
"Friday": "Freitag",
846+
"Saturday": "Samstag",
847+
"Sunday": "Sonntag"
848+
}
849+
}
850+
},
822851
"climate": {
823852
"heating": {
824853
"name": "Heizung"

0 commit comments

Comments
 (0)