Skip to content

Commit 9f0815b

Browse files
committed
* Fix double devices
* Correct unique ids and names for integration and devices * Add new senors heat_energy_input (parameters.Unknown_Parameter_1136) and domestic_water_energy_input (parameters.Unknown_Parameter_1137)
1 parent cefad40 commit 9f0815b

File tree

4 files changed

+140
-35
lines changed

4 files changed

+140
-35
lines changed

custom_components/luxtronik/__init__.py

+74-32
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from homeassistant.config_entries import ConfigEntry
77
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
88
from homeassistant.core import Event, HomeAssistant
9+
from homeassistant.helpers import device_registry as dr
910
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
1011
from homeassistant.helpers.typing import ConfigType
11-
1212
from luxtronik import LOGGER as LuxLogger
1313

1414
from .const import (
@@ -131,39 +131,32 @@ def setup_internal(hass, data, conf):
131131
hass.data[f"{DOMAIN}_conf"] = conf
132132

133133
# Create DeviceInfos:
134-
serial_number_date = luxtronik.get_value("parameters.ID_WP_SerienNummer_DATUM")
135-
serial_number_hex = hex(
136-
int(luxtronik.get_value("parameters.ID_WP_SerienNummer_HEX"))
137-
)
138-
serial_number = f"{serial_number_date}-{serial_number_hex}".replace("x", "")
139-
model = luxtronik.get_value("calculations.ID_WEB_Code_WP_akt")
140-
141134
hass.data[f"{DOMAIN}_DeviceInfo"] = build_device_info(
142-
luxtronik, serial_number, text_heatpump, data[CONF_HOST]
135+
luxtronik, text_heatpump, data[CONF_HOST]
143136
)
144137
hass.data[f"{DOMAIN}_DeviceInfo_Domestic_Water"] = DeviceInfo(
145-
identifiers={(DOMAIN, "Domestic_Water", serial_number)},
138+
identifiers={(DOMAIN, f"{luxtronik.unique_id}_domestic_water")},
146139
configuration_url="https://www.heatpump24.com/",
147140
default_name=text_domestic_water,
148141
name=text_domestic_water,
149-
manufacturer=get_manufacturer_by_model(model),
150-
model=model,
142+
manufacturer=luxtronik.manufacturer,
143+
model=luxtronik.model,
151144
)
152145
hass.data[f"{DOMAIN}_DeviceInfo_Heating"] = DeviceInfo(
153-
identifiers={(DOMAIN, "Heating", serial_number)},
154-
configuration_url=get_manufacturer_firmware_url_by_model(model),
146+
identifiers={(DOMAIN, f"{luxtronik.unique_id}_heating")},
147+
configuration_url=get_manufacturer_firmware_url_by_model(luxtronik.model),
155148
default_name=text_heating,
156149
name=text_heating,
157-
manufacturer=get_manufacturer_by_model(model),
158-
model=model,
150+
manufacturer=luxtronik.manufacturer,
151+
model=luxtronik.model,
159152
)
160153
hass.data[f"{DOMAIN}_DeviceInfo_Cooling"] = (
161154
DeviceInfo(
162-
identifiers={(DOMAIN, "Cooling", serial_number)},
155+
identifiers={(DOMAIN, f"{luxtronik.unique_id}_cooling")},
163156
default_name=text_cooling,
164157
name=text_cooling,
165-
manufacturer=get_manufacturer_by_model(model),
166-
model=model,
158+
manufacturer=luxtronik.manufacturer,
159+
model=luxtronik.model,
167160
)
168161
if luxtronik.detect_cooling_present()
169162
else None
@@ -185,39 +178,88 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
185178
if luxtronik is None:
186179
return True
187180

181+
unload_ok = False
188182
try:
189183
await hass.async_add_executor_job(luxtronik.disconnect)
190184

191185
await hass.services.async_remove(DOMAIN, SERVICE_WRITE)
186+
187+
unload_ok = await hass.config_entries.async_unload_platforms(
188+
config_entry, PLATFORMS
189+
)
190+
if unload_ok:
191+
hass.data[DOMAIN] = None
192+
hass.data.pop(DOMAIN)
193+
192194
except Exception as e:
193195
LOGGER.critical("Remove service!", e, exc_info=True)
194196

195-
unload_ok = await hass.config_entries.async_unload_platforms(
196-
config_entry, PLATFORMS
197-
)
198-
if unload_ok:
199-
hass.data[DOMAIN] = None
200-
hass.data.pop(DOMAIN)
201-
202197
return unload_ok
203198

204199

205200
def build_device_info(
206-
luxtronik: LuxtronikDevice, sn: str, name: str, ip_host: str
201+
luxtronik: LuxtronikDevice, name: str, ip_host: str
207202
) -> DeviceInfo:
208203
"""Build luxtronik device info."""
209-
model = luxtronik.get_value("calculations.ID_WEB_Code_WP_akt")
210204
device_info = DeviceInfo(
211-
identifiers={(DOMAIN, "Heatpump", sn)}, # type: ignore
205+
identifiers={
206+
(
207+
DOMAIN,
208+
f"{luxtronik.unique_id}_heatpump",
209+
)
210+
},
212211
configuration_url=f"http://{ip_host}/",
213-
name=f"{name} S/N {sn}",
212+
name=f"{name} {luxtronik.serial_number}",
214213
default_name=name,
215214
default_manufacturer="Alpha Innotec",
216-
manufacturer=get_manufacturer_by_model(model),
215+
manufacturer=luxtronik.manufacturer,
217216
default_model="",
218-
model=model,
217+
model=luxtronik.model,
219218
suggested_area="Utility room",
220219
sw_version=luxtronik.get_value("calculations.ID_WEB_SoftStand"),
221220
)
222221
LOGGER.debug("build_device_info '%s'", device_info)
223222
return device_info
223+
224+
225+
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
226+
"""Migrate old entry."""
227+
LOGGER.debug("Migrating from version %s", config_entry.version)
228+
229+
if config_entry.version == 1:
230+
new = {**config_entry.data}
231+
luxtronik = LuxtronikDevice.connect(new[CONF_HOST], new[CONF_PORT])
232+
233+
_delete_legacy_devices(hass, config_entry, luxtronik.unique_id)
234+
config_entry.unique_id = luxtronik.unique_id
235+
config_entry.title = f"{luxtronik.manufacturer} {luxtronik.model} {luxtronik.serial_number}"
236+
config_entry.version = 2
237+
hass.config_entries.async_update_entry(config_entry, data=new)
238+
239+
LOGGER.info("Migration to version %s successful", config_entry.version)
240+
241+
return True
242+
243+
244+
def _identifiers_exists(
245+
identifiers_list: list[set[tuple[str, str]]], identifiers: set[tuple[str, str]]
246+
) -> bool:
247+
for ident in identifiers_list:
248+
if ident == identifiers:
249+
return True
250+
return False
251+
252+
253+
def _delete_legacy_devices(hass: HomeAssistant, config_entry: ConfigEntry, unique_id: str):
254+
dr_instance = dr.async_get(hass)
255+
devices: list[dr.DeviceEntry] = dr.async_entries_for_config_entry(
256+
dr_instance, config_entry.entry_id
257+
)
258+
identifiers_list = list()
259+
identifiers_list.append({(DOMAIN, f"{unique_id}_heatpump")})
260+
identifiers_list.append({(DOMAIN, f"{unique_id}_domestic_water")})
261+
identifiers_list.append({(DOMAIN, f"{unique_id}_heating")})
262+
identifiers_list.append({(DOMAIN, f"{unique_id}_cooling")})
263+
for device in devices:
264+
if not _identifiers_exists(identifiers_list, device.identifiers):
265+
dr_instance.async_remove_device(device.id)

custom_components/luxtronik/config_flow.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@
2323
LOGGER,
2424
)
2525
from .helpers.lux_helper import discover
26+
from .luxtronik_device import LuxtronikDevice
2627

2728
# endregion Imports
2829

2930

3031
class LuxtronikFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
3132
"""Handle a Luxtronik heatpump controller config flow."""
3233

33-
VERSION = 1
34+
VERSION = 2
3435
_hassio_discovery = None
3536
_discovery_host = None
3637
_discovery_port = None
@@ -61,7 +62,9 @@ async def async_step_dhcp(self, discovery_info) -> FlowResult:
6162
broadcast_discover_ip, broadcast_discover_port = discover()
6263
if broadcast_discover_ip != discovery_info.ip:
6364
return None
64-
await self.async_set_unique_id(discovery_info.hostname)
65+
66+
luxtronik = LuxtronikDevice.connect(broadcast_discover_ip, broadcast_discover_port)
67+
await self.async_set_unique_id(luxtronik.unique_id)
6568
self._abort_if_unique_id_configured()
6669

6770
self._discovery_host = discovery_info.ip
@@ -103,7 +106,11 @@ async def async_step_user(
103106
}
104107
self._async_abort_entries_match(data)
105108

106-
return self.async_create_entry(title=user_input[CONF_HOST], data=data)
109+
luxtronik = LuxtronikDevice.connect(user_input[CONF_HOST], user_input[CONF_PORT])
110+
111+
await self.async_set_unique_id(luxtronik.unique_id)
112+
self._abort_if_unique_id_configured()
113+
return self.async_create_entry(title=f"{luxtronik.manufacturer} {luxtronik.model} {luxtronik.serial_number}", data=data)
107114

108115
@staticmethod
109116
@callback

custom_components/luxtronik/luxtronik_device.py

+30
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
MIN_TIME_BETWEEN_UPDATES,
2121
)
2222
from .helpers.debounce import debounce
23+
from .helpers.lux_helper import get_manufacturer_by_model
2324

2425
# endregion Imports
2526

@@ -38,6 +39,11 @@ def __init__(self, host: str, port: int, safe: bool, lock_timeout_sec: int) -> N
3839
self._luxtronik = Lux(host, port, safe)
3940
self.update()
4041

42+
@staticmethod
43+
def connect(host: str, port: int):
44+
"""Connect to heatpump."""
45+
return LuxtronikDevice(host, port, False, 30)
46+
4147
async def async_will_remove_from_hass(self):
4248
"""Disconnect from Luxtronik by stopping monitor."""
4349
self.disconnect()
@@ -73,6 +79,30 @@ def get_sensor(self, group, sensor_id):
7379
sensor = self._luxtronik.visibilities.get(sensor_id)
7480
return sensor
7581

82+
@property
83+
def serial_number(self) -> str:
84+
"""Return the serial number."""
85+
serial_number_date = self.get_value("parameters.ID_WP_SerienNummer_DATUM")
86+
serial_number_hex = hex(
87+
int(self.get_value("parameters.ID_WP_SerienNummer_HEX"))
88+
)
89+
return f"{serial_number_date}-{serial_number_hex}".replace("x", "")
90+
91+
@property
92+
def unique_id(self) -> str:
93+
"""Return the unique id."""
94+
return self.serial_number.lower().replace("-", "_")
95+
96+
@property
97+
def model(self) -> str:
98+
"""Return the heatpump model."""
99+
return self.get_value("calculations.ID_WEB_Code_WP_akt")
100+
101+
@property
102+
def manufacturer(self) -> str:
103+
"""Return the heatpump manufacturer."""
104+
return get_manufacturer_by_model(self.model)
105+
76106
@property
77107
def has_second_heat_generator(self) -> bool:
78108
"""Is second heat generator activated 1=electrical heater"""

custom_components/luxtronik/sensor.py

+26
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,19 @@ async def async_setup_entry(
565565
unit_of_measurement=ENERGY_KILO_WATT_HOUR,
566566
entity_category=EntityCategory.DIAGNOSTIC,
567567
),
568+
LuxtronikSensor(
569+
luxtronik,
570+
device_info_heating,
571+
sensor_key="parameters.Unknown_Parameter_1136",
572+
unique_id="heat_energy_input",
573+
name="Heat energy input",
574+
icon="mdi:circle-slice-3",
575+
device_class=DEVICE_CLASS_ENERGY,
576+
state_class=STATE_CLASS_TOTAL_INCREASING,
577+
unit_of_measurement=ENERGY_KILO_WATT_HOUR,
578+
entity_category=EntityCategory.DIAGNOSTIC,
579+
factor=0.01,
580+
),
568581
]
569582

570583
if luxtronik.get_value("calculations.ID_WEB_Temperatur_TRL_ext") != 5.0:
@@ -627,6 +640,19 @@ async def async_setup_entry(
627640
unit_of_measurement=ENERGY_KILO_WATT_HOUR,
628641
entity_category=EntityCategory.DIAGNOSTIC,
629642
),
643+
LuxtronikSensor(
644+
luxtronik,
645+
device_info_domestic_water,
646+
sensor_key="parameters.Unknown_Parameter_1137",
647+
unique_id="domestic_water_energy_input",
648+
name="Domestic water energy input",
649+
icon="mdi:circle-slice-3",
650+
device_class=DEVICE_CLASS_ENERGY,
651+
state_class=STATE_CLASS_TOTAL_INCREASING,
652+
unit_of_measurement=ENERGY_KILO_WATT_HOUR,
653+
entity_category=EntityCategory.DIAGNOSTIC,
654+
factor=0.01,
655+
),
630656
]
631657

632658
# Temp. disabled:

0 commit comments

Comments
 (0)