From 433d5a02bfab4da1fb4b1cdce7c3ae08d5c89980 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Dec 2024 19:06:04 +0100 Subject: [PATCH 01/21] Cover P1 data-retrieval error detection --- plugwise/smile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise/smile.py b/plugwise/smile.py index 9f20bfd3d..96ea9bf49 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -131,7 +131,7 @@ async def async_update(self) -> PlugwiseData: try: await self.full_xml_update() self.get_all_gateway_entities() - # Set self._cooling_enabled -required for set_temperature, + # Set self._cooling_enabled - required for set_temperature, # also, check for a failed data-retrieval if "heater_id" in self.gw_data: heat_cooler = self.gw_entities[self.gw_data["heater_id"]] @@ -142,6 +142,8 @@ async def async_update(self) -> PlugwiseData: self._cooling_enabled = heat_cooler["binary_sensors"][ "cooling_enabled" ] + else: # cover failed data-retrieval for P1 + _ = self.gw_entities[self.gateway_id]["location"] except KeyError as err: raise DataMissingError("No Plugwise data received") from err From 4f35d895530f18200dce8a31c9df0bdaf1b54a45 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Dec 2024 19:11:04 +0100 Subject: [PATCH 02/21] Add testcase for P1 data-retrieval error --- tests/test_p1.py | 9 ++++++++- userdata/error/p1v4_442_single/core.domain_objects.xml | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 userdata/error/p1v4_442_single/core.domain_objects.xml diff --git a/tests/test_p1.py b/tests/test_p1.py index 3cd72257e..25980a885 100644 --- a/tests/test_p1.py +++ b/tests/test_p1.py @@ -2,7 +2,7 @@ import pytest -from .test_init import _LOGGER, TestPlugwise +from .test_init import _LOGGER, TestPlugwise, pw_exceptions SMILE_TYPE = "p1" @@ -41,6 +41,13 @@ async def test_connect_p1v4_442_single(self): smile, "2022-05-16 00:00:01", testdata_updated, initialize=False ) + # Simulate receiving xml-data with + self.smile_setup = "error/p1v4_442_single" + try: + await self.device_test(smile, initialize=False) + except pw_exceptions.ResponseError: + _LOGGER.debug("Receiving error-data from the Gateway") + await smile.close_connection() await self.disconnect(server, client) diff --git a/userdata/error/p1v4_442_single/core.domain_objects.xml b/userdata/error/p1v4_442_single/core.domain_objects.xml new file mode 100644 index 000000000..ff93be936 --- /dev/null +++ b/userdata/error/p1v4_442_single/core.domain_objects.xml @@ -0,0 +1,5 @@ + + + + + From a1dd80a67560fd637afee462f1e8e1fcc04879b1 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Dec 2024 19:18:34 +0100 Subject: [PATCH 03/21] Cover legacy data-retrieval error detection --- plugwise/legacy/smile.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 5afe5690d..1e8bd75dd 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -25,7 +25,7 @@ PlugwiseData, ThermoLoc, ) -from plugwise.exceptions import ConnectionFailedError, PlugwiseError +from plugwise.exceptions import ConnectionFailedError, DataMissingError, PlugwiseError from plugwise.legacy.data import SmileLegacyData import aiohttp @@ -120,18 +120,28 @@ async def async_update(self) -> PlugwiseData: ) self.gw_data: GatewayData = {} self.gw_entities: dict[str, GwEntityData] = {} - await self.full_xml_update() - self.get_all_gateway_entities() + try: + await self.full_xml_update() + self.get_all_gateway_entities() + # Detect failed data-retrieval + _ = self.gw_entities[self.gateway_id]["location"] + except KeyError as err: + raise DataMissingError("No Plugwise data received") from err # Otherwise perform an incremental update else: - self._domain_objects = await self.request(DOMAIN_OBJECTS) - match self._target_smile: - case "smile_v2": - self._modules = await self.request(MODULES) - case self._target_smile if self._target_smile in REQUIRE_APPLIANCES: - self._appliances = await self.request(APPLIANCES) - - self._update_gw_entities() + try: + self._domain_objects = await self.request(DOMAIN_OBJECTS) + match self._target_smile: + case "smile_v2": + self._modules = await self.request(MODULES) + case self._target_smile if self._target_smile in REQUIRE_APPLIANCES: + self._appliances = await self.request(APPLIANCES) + + self._update_gw_entities() + # Detect failed data-retrieval + _ = self.gw_entities[self.gateway_id]["location"] + except KeyError as err: + raise DataMissingError("No Plugwise data received") from err self._previous_day_number = day_number return PlugwiseData( From 47ca1669f134b0438987a36d8caa29f657a04a1c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Mon, 9 Dec 2024 19:31:10 +0100 Subject: [PATCH 04/21] Update P1 test with data missing after a reboot Try --- plugwise/helper.py | 3 +++ tests/test_adam.py | 4 ++-- tests/test_p1.py | 7 +++++++ userdata/reboot/p1v4_442_single/core.domain_objects.xml | 3 +++ 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 userdata/reboot/p1v4_442_single/core.domain_objects.xml diff --git a/plugwise/helper.py b/plugwise/helper.py index cea2f130f..187605eb5 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -337,6 +337,9 @@ def _all_appliances(self) -> None: self._create_gw_entities(appl) + ###################################################### + #TODO: at this indent appl is not available/defined!!# + ###################################################### # For P1 collect the connected SmartMeter info if self.smile_type == "power": self._p1_smartmeter_info_finder(appl) diff --git a/tests/test_adam.py b/tests/test_adam.py index 94a9075c0..9a90931ea 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -137,8 +137,8 @@ async def test_connect_adam_plus_anna_new(self): self.smile_setup = "reboot/adam_plus_anna_new" try: await self.device_test(smile, initialize=False) - except pw_exceptions.PlugwiseError: - _LOGGER.debug("Receiving no data after a reboot is properly handled") + except pw_exceptions.PlugwiseError as err: + _LOGGER.debug(f"Receiving no data after a reboot is properly handled: {err}") # Simulate receiving xml-data with self.smile_setup = "error/adam_plus_anna_new" diff --git a/tests/test_p1.py b/tests/test_p1.py index 25980a885..6b6f7160c 100644 --- a/tests/test_p1.py +++ b/tests/test_p1.py @@ -41,6 +41,13 @@ async def test_connect_p1v4_442_single(self): smile, "2022-05-16 00:00:01", testdata_updated, initialize=False ) + # Simulate receiving no xml-data after a requesting a reboot of the gateway + self.smile_setup = "reboot/p1v4_442_single" + try: + await self.device_test(smile, initialize=False) + except pw_exceptions.PlugwiseError as err: + _LOGGER.debug(f"Receiving no data after a reboot is properly handled: {err}") + # Simulate receiving xml-data with self.smile_setup = "error/p1v4_442_single" try: diff --git a/userdata/reboot/p1v4_442_single/core.domain_objects.xml b/userdata/reboot/p1v4_442_single/core.domain_objects.xml new file mode 100644 index 000000000..5cd8e19de --- /dev/null +++ b/userdata/reboot/p1v4_442_single/core.domain_objects.xml @@ -0,0 +1,3 @@ + + + From 84790f86369c559cd7e1b316406ecf5dfde49617 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:17:02 +0100 Subject: [PATCH 05/21] Fix UnboundLocalError --- plugwise/helper.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 187605eb5..a23cbf335 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -337,12 +337,9 @@ def _all_appliances(self) -> None: self._create_gw_entities(appl) - ###################################################### - #TODO: at this indent appl is not available/defined!!# - ###################################################### # For P1 collect the connected SmartMeter info if self.smile_type == "power": - self._p1_smartmeter_info_finder(appl) + self._p1_smartmeter_info_finder() # P1: for gateway and smartmeter switch entity_id - part 2 for item in self.gw_entities: if item != self.gateway_id: @@ -372,16 +369,19 @@ def _all_locations(self) -> None: self._loc_data[loc.loc_id] = {"name": loc.name} - def _p1_smartmeter_info_finder(self, appl: Munch) -> None: + def _p1_smartmeter_info_finder(self) -> None: """Collect P1 DSMR SmartMeter info.""" + appl = Munch() loc_id = next(iter(self._loc_data.keys())) - location = self._domain_objects.find(f'./location[@id="{loc_id}"]') + if (location := self._domain_objects.find(f'./location[@id="{loc_id}"]')) is None: + return None + locator = MODULE_LOCATOR module_data = self._get_module_data(location, locator) if not module_data["contents"]: LOGGER.error("No module data found for SmartMeter") # pragma: no cover return None # pragma: no cover - + appl.available = None appl.entity_id = self.gateway_id appl.firmware = module_data["firmware_version"] appl.hardware = module_data["hardware_version"] From 4ada7a8b845a4050169b8768cab88794a952fb61 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:18:31 +0100 Subject: [PATCH 06/21] Ruff format fixes --- plugwise/helper.py | 4 +++- tests/test_adam.py | 4 +++- tests/test_p1.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index a23cbf335..3995afc85 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -373,7 +373,9 @@ def _p1_smartmeter_info_finder(self) -> None: """Collect P1 DSMR SmartMeter info.""" appl = Munch() loc_id = next(iter(self._loc_data.keys())) - if (location := self._domain_objects.find(f'./location[@id="{loc_id}"]')) is None: + if ( + location := self._domain_objects.find(f'./location[@id="{loc_id}"]') + ) is None: return None locator = MODULE_LOCATOR diff --git a/tests/test_adam.py b/tests/test_adam.py index 9a90931ea..bc521104f 100644 --- a/tests/test_adam.py +++ b/tests/test_adam.py @@ -138,7 +138,9 @@ async def test_connect_adam_plus_anna_new(self): try: await self.device_test(smile, initialize=False) except pw_exceptions.PlugwiseError as err: - _LOGGER.debug(f"Receiving no data after a reboot is properly handled: {err}") + _LOGGER.debug( + f"Receiving no data after a reboot is properly handled: {err}" + ) # Simulate receiving xml-data with self.smile_setup = "error/adam_plus_anna_new" diff --git a/tests/test_p1.py b/tests/test_p1.py index 6b6f7160c..ff102ba26 100644 --- a/tests/test_p1.py +++ b/tests/test_p1.py @@ -46,7 +46,9 @@ async def test_connect_p1v4_442_single(self): try: await self.device_test(smile, initialize=False) except pw_exceptions.PlugwiseError as err: - _LOGGER.debug(f"Receiving no data after a reboot is properly handled: {err}") + _LOGGER.debug( + f"Receiving no data after a reboot is properly handled: {err}" + ) # Simulate receiving xml-data with self.smile_setup = "error/p1v4_442_single" From 2eda0cee0c7ae5aead001df26e266a8c18bcc019 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:21:39 +0100 Subject: [PATCH 07/21] Improve comment --- plugwise/helper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 3995afc85..483cb389c 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -337,7 +337,8 @@ def _all_appliances(self) -> None: self._create_gw_entities(appl) - # For P1 collect the connected SmartMeter info + # For P1 collect the connected SmartMeter info from the Home/building + # location, there is no appliance available for this device. if self.smile_type == "power": self._p1_smartmeter_info_finder() # P1: for gateway and smartmeter switch entity_id - part 2 From 90a8125f61e36ffb0ee9eb1b1b80360bb41e20af Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:26:59 +0100 Subject: [PATCH 08/21] Break-out two more functions --- plugwise/helper.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 483cb389c..aa3676863 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -337,8 +337,14 @@ def _all_appliances(self) -> None: self._create_gw_entities(appl) - # For P1 collect the connected SmartMeter info from the Home/building - # location, there is no appliance available for this device. + self._get_smartmeter_info() + self._sort_gw_entities() + + def _get_smartmeter_info(self) -> None: + """For P1 collect the connected SmartMeter info from the Home/buildinglocation. + + There is no appliance available for this device. + """ if self.smile_type == "power": self._p1_smartmeter_info_finder() # P1: for gateway and smartmeter switch entity_id - part 2 @@ -348,7 +354,8 @@ def _all_appliances(self) -> None: # Leave for-loop to avoid a 2nd device_id switch break - # Place the gateway and optional heater_central devices as 1st and 2nd + def _sort_gw_entities(self) -> None: + """Place the gateway and optional heater_central devices as 1st and 2nd.""" for dev_class in ("heater_central", "gateway"): for entity_id, entity in dict(self.gw_entities).items(): if entity["dev_class"] == dev_class: From a77b753af7838f5e2d16adf160594f24277cd92f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:30:51 +0100 Subject: [PATCH 09/21] Update raise message to show the origin --- plugwise/legacy/smile.py | 2 +- plugwise/smile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 1e8bd75dd..87fc0b526 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -141,7 +141,7 @@ async def async_update(self) -> PlugwiseData: # Detect failed data-retrieval _ = self.gw_entities[self.gateway_id]["location"] except KeyError as err: - raise DataMissingError("No Plugwise data received") from err + raise DataMissingError("No legacy Plugwise data received") from err self._previous_day_number = day_number return PlugwiseData( diff --git a/plugwise/smile.py b/plugwise/smile.py index 96ea9bf49..94bfebcfb 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -145,7 +145,7 @@ async def async_update(self) -> PlugwiseData: else: # cover failed data-retrieval for P1 _ = self.gw_entities[self.gateway_id]["location"] except KeyError as err: - raise DataMissingError("No Plugwise data received") from err + raise DataMissingError("No Plugwise actual data received") from err return PlugwiseData( devices=self.gw_entities, From 9a0316ef8797f0b97bb6a05a7d2c948b9f9c179e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:45:34 +0100 Subject: [PATCH 10/21] Update missed legacy raise-message --- plugwise/legacy/smile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 87fc0b526..a634293e0 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -126,7 +126,7 @@ async def async_update(self) -> PlugwiseData: # Detect failed data-retrieval _ = self.gw_entities[self.gateway_id]["location"] except KeyError as err: - raise DataMissingError("No Plugwise data received") from err + raise DataMissingError("No (full) Plugwise legacy data received") from err # Otherwise perform an incremental update else: try: From bb1baa19776dfdce34c7b57cd8d58246837a1c89 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:48:59 +0100 Subject: [PATCH 11/21] Add pragma-no covers --- plugwise/legacy/smile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index a634293e0..bb244ab6d 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -125,7 +125,7 @@ async def async_update(self) -> PlugwiseData: self.get_all_gateway_entities() # Detect failed data-retrieval _ = self.gw_entities[self.gateway_id]["location"] - except KeyError as err: + except KeyError as err: # pragma: no cover raise DataMissingError("No (full) Plugwise legacy data received") from err # Otherwise perform an incremental update else: @@ -140,7 +140,7 @@ async def async_update(self) -> PlugwiseData: self._update_gw_entities() # Detect failed data-retrieval _ = self.gw_entities[self.gateway_id]["location"] - except KeyError as err: + except KeyError as err: # pragma: no cover raise DataMissingError("No legacy Plugwise data received") from err self._previous_day_number = day_number From 464ce2512763b5aab42b15588aad0eee9f84e47c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:49:49 +0100 Subject: [PATCH 12/21] Ruff format fix --- plugwise/legacy/smile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index bb244ab6d..d5be6832e 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -126,7 +126,9 @@ async def async_update(self) -> PlugwiseData: # Detect failed data-retrieval _ = self.gw_entities[self.gateway_id]["location"] except KeyError as err: # pragma: no cover - raise DataMissingError("No (full) Plugwise legacy data received") from err + raise DataMissingError( + "No (full) Plugwise legacy data received" + ) from err # Otherwise perform an incremental update else: try: From fa40c5055f23e137f6d5bd9a3757d0daf8770d52 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 19:52:41 +0100 Subject: [PATCH 13/21] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03609593e..5f52119e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Rework tooling [#664](https://github.com/plugwise/python-plugwise/pull/664) - Archive p1v4 userdata [#666](https://github.com/plugwise/python-plugwise/pull/666) - Correct manual_fixtures script [#668](https://github.com/plugwise/python-plugwise/pull/668) +- Improve P1 fault-handling [#670](https://github.com/plugwise/python-plugwise/pull/670) ## v1.6.3 From 5a9e17bccd317467819b060fdc8544e427fa89bb Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 10 Dec 2024 20:15:45 +0100 Subject: [PATCH 14/21] Improve _sort_gw_entities(), partly following suggestion --- plugwise/helper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index aa3676863..45f02274c 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -359,11 +359,11 @@ def _sort_gw_entities(self) -> None: for dev_class in ("heater_central", "gateway"): for entity_id, entity in dict(self.gw_entities).items(): if entity["dev_class"] == dev_class: - tmp_entity = entity + priority_entity = entity self.gw_entities.pop(entity_id) - cleared_dict = self.gw_entities - add_to_front = {entity_id: tmp_entity} - self.gw_entities = {**add_to_front, **cleared_dict} + other_entities = self.gw_entities + priority_entities = {entity_id: priority_entity} + self.gw_entities = {**priority_entities, **other_entities} def _all_locations(self) -> None: """Collect all locations.""" From 066e30ec0ff59c92da6166057027c6551c27fc02 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Wed, 11 Dec 2024 10:30:44 +0100 Subject: [PATCH 15/21] Add clarifying notes to _process_on_off_device_c_heating_state() --- plugwise/helper.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugwise/helper.py b/plugwise/helper.py index 45f02274c..bcfa9bc5e 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -814,12 +814,16 @@ def _process_on_off_device_c_heating_state(self, data: GwEntityData) -> None: data["binary_sensors"]["heating_state"] = data["c_heating_state"] if self.smile(ADAM): + # First count when not present, then create and init to False. + # When present init to False if "heating_state" not in data["binary_sensors"]: self._count += 1 data["binary_sensors"]["heating_state"] = False + if "cooling_state" not in data["binary_sensors"]: self._count += 1 data["binary_sensors"]["cooling_state"] = False + if self._cooling_enabled: data["binary_sensors"]["cooling_state"] = data["c_heating_state"] else: From 47de0e2dbc8173464267cd5e781f5e633b9982dd Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 12 Dec 2024 13:33:33 +0100 Subject: [PATCH 16/21] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f52119e9..778305295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Rework tooling [#664](https://github.com/plugwise/python-plugwise/pull/664) - Archive p1v4 userdata [#666](https://github.com/plugwise/python-plugwise/pull/666) - Correct manual_fixtures script [#668](https://github.com/plugwise/python-plugwise/pull/668) -- Improve P1 fault-handling [#670](https://github.com/plugwise/python-plugwise/pull/670) +- Improve P1 fault-handling, continuous improvements [#670](https://github.com/plugwise/python-plugwise/pull/670) ## v1.6.3 From 8bdcf7f70e3b41633279ba3fc885210e025532c9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 12 Dec 2024 13:44:28 +0100 Subject: [PATCH 17/21] Implement improvement suggestions --- plugwise/constants.py | 1 + plugwise/helper.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugwise/constants.py b/plugwise/constants.py index 63c6af3e9..232da091b 100644 --- a/plugwise/constants.py +++ b/plugwise/constants.py @@ -85,6 +85,7 @@ MODULE_LOCATOR: Final = "./logs/point_log/*[@id]" NONE: Final = "None" OFF: Final = "off" +PRIORITY_DEVICE_CLASSES = ("heater_central", "gateway") # XML data paths APPLIANCES: Final = "/core/appliances" diff --git a/plugwise/helper.py b/plugwise/helper.py index bcfa9bc5e..b1580f0c9 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -29,6 +29,7 @@ NONE, OFF, P1_MEASUREMENTS, + PRIORITY_DEVICE_CLASSES, TEMP_CELSIUS, THERMOSTAT_CLASSES, TOGGLES, @@ -344,6 +345,8 @@ def _get_smartmeter_info(self) -> None: """For P1 collect the connected SmartMeter info from the Home/buildinglocation. There is no appliance available for this device. + Note: For P1 devices, we switch the gateway_id to the smartmeter device_id + to maintain backward compatibility with existing implementations. """ if self.smile_type == "power": self._p1_smartmeter_info_finder() @@ -356,7 +359,7 @@ def _get_smartmeter_info(self) -> None: def _sort_gw_entities(self) -> None: """Place the gateway and optional heater_central devices as 1st and 2nd.""" - for dev_class in ("heater_central", "gateway"): + for dev_class in PRIORITY_DEVICE_CLASSES: for entity_id, entity in dict(self.gw_entities).items(): if entity["dev_class"] == dev_class: priority_entity = entity From 5fc725a3c5fa44171fb2038c23105c8f4ffd4e6a Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 12 Dec 2024 14:11:58 +0100 Subject: [PATCH 18/21] Improve doc-strings, add comments --- plugwise/helper.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index b1580f0c9..0bdcc51ff 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -279,7 +279,13 @@ def __init__(self) -> None: SmileCommon.__init__(self) def _all_appliances(self) -> None: - """Collect all appliances with relevant info.""" + """Collect all appliances with relevant info. + + Also, collect the P1 smartmeter info from a location + as this one is not available as an appliance. + Note: For P1 devices, the entity_id for the gateway and smartmeter are + switched to maintain backward compatibility with existing implementations. + """ self._count = 0 self._all_locations() @@ -338,16 +344,14 @@ def _all_appliances(self) -> None: self._create_gw_entities(appl) + # Collect P1 Smartmeter info self._get_smartmeter_info() + + # Sort the gw_entities self._sort_gw_entities() def _get_smartmeter_info(self) -> None: - """For P1 collect the connected SmartMeter info from the Home/buildinglocation. - - There is no appliance available for this device. - Note: For P1 devices, we switch the gateway_id to the smartmeter device_id - to maintain backward compatibility with existing implementations. - """ + """For P1 collect the connected SmartMeter info from the Home/building location.""" if self.smile_type == "power": self._p1_smartmeter_info_finder() # P1: for gateway and smartmeter switch entity_id - part 2 From 5e35ae82b82ea981b9b64ac174e10ab9b826a741 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 12 Dec 2024 14:17:26 +0100 Subject: [PATCH 19/21] device -> entity corrections --- plugwise/helper.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 0bdcc51ff..e43b37d3d 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -283,7 +283,7 @@ def _all_appliances(self) -> None: Also, collect the P1 smartmeter info from a location as this one is not available as an appliance. - Note: For P1 devices, the entity_id for the gateway and smartmeter are + Note: For P1, the entity_id for the gateway and smartmeter are switched to maintain backward compatibility with existing implementations. """ self._count = 0 @@ -358,11 +358,11 @@ def _get_smartmeter_info(self) -> None: for item in self.gw_entities: if item != self.gateway_id: self.gateway_id = item - # Leave for-loop to avoid a 2nd device_id switch + # Leave for-loop to avoid a 2nd entity_id switch break def _sort_gw_entities(self) -> None: - """Place the gateway and optional heater_central devices as 1st and 2nd.""" + """Place the gateway and optional heater_central entities as 1st and 2nd.""" for dev_class in PRIORITY_DEVICE_CLASSES: for entity_id, entity in dict(self.gw_entities).items(): if entity["dev_class"] == dev_class: @@ -417,16 +417,16 @@ def _appliance_info_finder(self, appl: Munch, appliance: etree) -> Munch: """Collect info for all appliances found.""" match appl.pwclass: case "gateway": - # Collect gateway device info + # Collect gateway entity info return self._appl_gateway_info(appl, appliance) case _ as dev_class if dev_class in THERMOSTAT_CLASSES: - # Collect thermostat device info + # Collect thermostat entity info return self._appl_thermostat_info(appl, appliance) case "heater_central": - # Collect heater_central device info + # Collect heater_central entity info self._appl_heater_central_info( appl, appliance, False - ) # False means non-legacy device + ) # False means non-legacy entity self._dhw_allowed_modes = self._get_appl_actuator_modes( appliance, "domestic_hot_water_mode_control_functionality" ) @@ -837,7 +837,7 @@ def _process_on_off_device_c_heating_state(self, data: GwEntityData) -> None: data["binary_sensors"]["heating_state"] = data["c_heating_state"] def _update_anna_cooling(self, entity_id: str, data: GwEntityData) -> None: - """Update the Anna heater_central device for cooling. + """Update the Anna heater_central entity for cooling. Support added for Techneco Elga and Thercon Loria/Thermastage. """ From 25177a7d25bbaa891520b843c0412699d3524bd1 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 12 Dec 2024 17:18:09 +0100 Subject: [PATCH 20/21] Combine entity_id-switch in one function --- plugwise/helper.py | 78 ++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index e43b37d3d..966cc2f34 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -283,8 +283,6 @@ def _all_appliances(self) -> None: Also, collect the P1 smartmeter info from a location as this one is not available as an appliance. - Note: For P1, the entity_id for the gateway and smartmeter are - switched to maintain backward compatibility with existing implementations. """ self._count = 0 self._all_locations() @@ -337,55 +335,20 @@ def _all_appliances(self) -> None: if not (appl := self._appliance_info_finder(appl, appliance)): continue - # P1: for gateway and smartmeter switch entity_id - part 1 - # This is done to avoid breakage in HA Core - if appl.pwclass == "gateway" and self.smile_type == "power": - appl.entity_id = appl.location - self._create_gw_entities(appl) - # Collect P1 Smartmeter info - self._get_smartmeter_info() + if self.smile_type == "power": + self._get_p1_smartmeter_info() # Sort the gw_entities self._sort_gw_entities() - def _get_smartmeter_info(self) -> None: - """For P1 collect the connected SmartMeter info from the Home/building location.""" - if self.smile_type == "power": - self._p1_smartmeter_info_finder() - # P1: for gateway and smartmeter switch entity_id - part 2 - for item in self.gw_entities: - if item != self.gateway_id: - self.gateway_id = item - # Leave for-loop to avoid a 2nd entity_id switch - break - - def _sort_gw_entities(self) -> None: - """Place the gateway and optional heater_central entities as 1st and 2nd.""" - for dev_class in PRIORITY_DEVICE_CLASSES: - for entity_id, entity in dict(self.gw_entities).items(): - if entity["dev_class"] == dev_class: - priority_entity = entity - self.gw_entities.pop(entity_id) - other_entities = self.gw_entities - priority_entities = {entity_id: priority_entity} - self.gw_entities = {**priority_entities, **other_entities} - - def _all_locations(self) -> None: - """Collect all locations.""" - loc = Munch() - locations = self._domain_objects.findall("./location") - for location in locations: - loc.name = location.find("name").text - loc.loc_id = location.attrib["id"] - if loc.name == "Home": - self._home_location = loc.loc_id - - self._loc_data[loc.loc_id] = {"name": loc.name} + def _get_p1_smartmeter_info(self) -> None: + """For P1 collect the connected SmartMeter info from the Home/building location. - def _p1_smartmeter_info_finder(self) -> None: - """Collect P1 DSMR SmartMeter info.""" + Note: For P1, the entity_id for the gateway and smartmeter are + switched to maintain backward compatibility with existing implementations. + """ appl = Munch() loc_id = next(iter(self._loc_data.keys())) if ( @@ -411,8 +374,35 @@ def _p1_smartmeter_info_finder(self) -> None: appl.vendor_name = module_data["vendor_name"] appl.zigbee_mac = None + # Replace the entity_id of the gateway by the smartmeter location_id + self.gw_entities[loc_id] = self.gw_entities.pop(self.gateway_id) + self.gateway_id = loc_id + self._create_gw_entities(appl) + def _sort_gw_entities(self) -> None: + """Place the gateway and optional heater_central entities as 1st and 2nd.""" + for dev_class in PRIORITY_DEVICE_CLASSES: + for entity_id, entity in dict(self.gw_entities).items(): + if entity["dev_class"] == dev_class: + priority_entity = entity + self.gw_entities.pop(entity_id) + other_entities = self.gw_entities + priority_entities = {entity_id: priority_entity} + self.gw_entities = {**priority_entities, **other_entities} + + def _all_locations(self) -> None: + """Collect all locations.""" + loc = Munch() + locations = self._domain_objects.findall("./location") + for location in locations: + loc.name = location.find("name").text + loc.loc_id = location.attrib["id"] + if loc.name == "Home": + self._home_location = loc.loc_id + + self._loc_data[loc.loc_id] = {"name": loc.name} + def _appliance_info_finder(self, appl: Munch, appliance: etree) -> Munch: """Collect info for all appliances found.""" match appl.pwclass: From 5bcc77840d72d193964dcf7593d9b5974de5385a Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 12 Dec 2024 17:24:47 +0100 Subject: [PATCH 21/21] Fix returns --- plugwise/helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 966cc2f34..58e499e0c 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -354,13 +354,13 @@ def _get_p1_smartmeter_info(self) -> None: if ( location := self._domain_objects.find(f'./location[@id="{loc_id}"]') ) is None: - return None + return locator = MODULE_LOCATOR module_data = self._get_module_data(location, locator) if not module_data["contents"]: LOGGER.error("No module data found for SmartMeter") # pragma: no cover - return None # pragma: no cover + return # pragma: no cover appl.available = None appl.entity_id = self.gateway_id appl.firmware = module_data["firmware_version"]