From a76377e28b792e06b9019a1addbca12e07e63e62 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 1 Jan 2026 12:34:14 +0100 Subject: [PATCH 1/7] Rework _get_groups() --- plugwise/common.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/plugwise/common.py b/plugwise/common.py index 1024c8218..0c36be74f 100644 --- a/plugwise/common.py +++ b/plugwise/common.py @@ -198,17 +198,16 @@ def _get_groups(self) -> None: return for group in self._domain_objects.findall("./group"): - members: list[str] = [] group_id = group.get("id") + if group_id is None: + continue # pragma: no cover + + if not (members := self._collect_members(group)): + continue + group_name = group.find("name").text group_type = group.find("type").text - group_appliances = group.findall("appliances/appliance") - for item in group_appliances: - # Check if members are not orphaned - stretch - if item.get("id") in self.gw_entities: - members.append(item.get("id")) - - if group_type in GROUP_TYPES and members and group_id: + if group_type in GROUP_TYPES: self.gw_entities[group_id] = { "dev_class": group_type, "model": "Group", @@ -218,6 +217,16 @@ def _get_groups(self) -> None: } self._count += 5 + def _collect_members(self, element: etree.Element) -> list[str]: + """Check and collect members.""" + members: list[str] = [] + group_appliances = element.findall("appliances/appliance") + for item in group_appliances: + if (member_id := item.get("id")) in self.gw_entities: + members.append(member_id) + + return members + def _get_lock_state( self, xml: etree.Element, data: GwEntityData, stretch_v2: bool = False ) -> None: From 981b944784777b580c8abb13363819439874953c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 1 Jan 2026 12:36:31 +0100 Subject: [PATCH 2/7] Rework _add_or_update_notifications() --- plugwise/data.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/plugwise/data.py b/plugwise/data.py index fc45138fb..e240b51a1 100644 --- a/plugwise/data.py +++ b/plugwise/data.py @@ -106,19 +106,18 @@ def _detect_low_batteries(self) -> list[str]: def _add_or_update_notifications( self, entity_id: str, entity: GwEntityData ) -> None: - """Helper-function adding or updating the Plugwise notifications.""" - if ( - entity_id == self._gateway_id - and (self._is_thermostat or self.smile.type == "power") - ) or ( - "binary_sensors" in entity - and "plugwise_notification" in entity["binary_sensors"] - ): - entity["binary_sensors"]["plugwise_notification"] = bool( - self._notifications - ) - entity["notifications"] = self._notifications - self._count += 2 + """Helper-function adding or updating the Plugwise notifications to the gateway.""" + + if entity_id != self._gateway_id: + return # pragma: no cover + + if self._is_thermostat or self.smile.type == "power": + if "plugwise_notification" not in entity["binary_sensors"]: + entity["binary_sensors"].update( + {"plugwise_notification": bool(self._notifications)} + ) + entity.update({"notifications": self._notifications}) + self._count += 2 def _update_for_cooling(self, entity: GwEntityData) -> None: """Helper-function for adding/updating various cooling-related values.""" From 7d998818b0642b4c04e907f6d65ee5e054732d38 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 1 Jan 2026 12:40:59 +0100 Subject: [PATCH 3/7] Rework _get_entity_data() --- plugwise/data.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/plugwise/data.py b/plugwise/data.py index e240b51a1..eb244f0f1 100644 --- a/plugwise/data.py +++ b/plugwise/data.py @@ -183,29 +183,28 @@ def _get_entity_data(self, entity_id: str, entity: GwEntityData) -> None: Provide entity-data, based on appliance_id (= entity_id). """ self._get_measurement_data(entity_id, entity) - - # Check availability of wired-connected entities - # Smartmeter - self._check_availability( - entity, "smartmeter", "P1 does not seem to be connected" - ) - # OpenTherm entity - if entity["name"] != "OnOff": - self._check_availability( - entity, "heater_central", "no OpenTherm communication" - ) - - # Switching groups data - self._entity_switching_group(entity) # Adam data if self.check_name(ADAM): self._get_adam_data(entity) + # Update switching-group status + self._entity_switching_group(entity) # Thermostat data for Anna (presets, temperatures etc) if self.check_name(ANNA) and entity["dev_class"] == "thermostat": self._climate_data(entity_id, entity) self._get_anna_control_state(entity) + # Check availability of wired entities: + # - Smartmeter + self._check_availability( + entity, "smartmeter", "P1 does not seem to be connected" + ) + # - OpenTherm entity + if entity["name"] != "OnOff": + self._check_availability( + entity, "heater_central", "no OpenTherm communication" + ) + def _check_availability( self, entity: GwEntityData, dev_class: str, message: str ) -> None: From 1d0d5b66f718ddd8979c7a4ca61462c95432359b Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 1 Jan 2026 12:57:03 +0100 Subject: [PATCH 4/7] Improve comment, move function --- plugwise/helper.py | 13 +------------ plugwise/smile.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 88d80e8b6..3c0d94840 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -145,7 +145,7 @@ def _get_appliances(self) -> None: elif appl.pwclass not in THERMOSTAT_CLASSES: appl.location = self._home_loc_id - # Don't show orphaned thermostat-types + # Don't show orphaned (no location) thermostat-types if appl.pwclass in THERMOSTAT_CLASSES and appl.location is None: continue @@ -316,17 +316,6 @@ def _get_appl_actuator_modes( return mode_list - def _get_appliances_with_offset_functionality(self) -> list[str]: - """Helper-function collecting all appliance that have offset_functionality.""" - therm_list: list[str] = [] - offset_appls = self._domain_objects.findall( - './/actuator_functionalities/offset_functionality[type="temperature_offset"]/offset/../../..' - ) - for item in offset_appls: - therm_list.append(item.get("id")) - - return therm_list - def _get_zone_data(self, loc_id: str, zone: GwEntityData) -> None: """Helper-function for smile.py: _get_entity_data(). diff --git a/plugwise/smile.py b/plugwise/smile.py index d6c10f826..edd3fcae4 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -116,6 +116,17 @@ def get_all_gateway_entities(self) -> None: self._get_groups() self._all_entity_data() + def _get_appliances_with_offset_functionality(self) -> list[str]: + """Helper-function collecting all appliance that have offset_functionality.""" + therm_list: list[str] = [] + offset_appls = self._domain_objects.findall( + './/actuator_functionalities/offset_functionality[type="temperature_offset"]/offset/../../..' + ) + for item in offset_appls: + therm_list.append(item.get("id")) + + return therm_list + async def async_update(self) -> dict[str, GwEntityData]: """Perform an full update: re-collect all gateway entities and their data and states. From 0a7946f77001b394275716ba7be1b17cb224e564 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 1 Jan 2026 13:01:12 +0100 Subject: [PATCH 5/7] Pass err-reason with raise for better troubleshooting --- plugwise/__init__.py | 2 +- plugwise/legacy/smile.py | 6 ++---- plugwise/smile.py | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/plugwise/__init__.py b/plugwise/__init__.py index bf4f3b04b..9c4cd84dd 100644 --- a/plugwise/__init__.py +++ b/plugwise/__init__.py @@ -332,7 +332,7 @@ async def async_update(self) -> dict[str, GwEntityData]: try: data = await self._smile_api.async_update() except (DataMissingError, KeyError) as err: - raise PlugwiseError("No Plugwise data received") from err + raise PlugwiseError(f"No Plugwise data received: {err}") from err return data diff --git a/plugwise/legacy/smile.py b/plugwise/legacy/smile.py index 9b97f2501..81ed20d90 100644 --- a/plugwise/legacy/smile.py +++ b/plugwise/legacy/smile.py @@ -101,9 +101,7 @@ async def async_update(self) -> dict[str, GwEntityData]: # 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(f"No (full) legacy data: {err}") from err else: try: self._domain_objects = await self._request(DOMAIN_OBJECTS) @@ -117,7 +115,7 @@ async def async_update(self) -> dict[str, GwEntityData]: # Detect failed data-retrieval _ = self.gw_entities[self.gateway_id]["location"] except KeyError as err: # pragma: no cover - raise DataMissingError("No legacy Plugwise data received") from err + raise DataMissingError(f"No legacy data: {err}") from err self._first_update = False self._previous_day_number = day_number diff --git a/plugwise/smile.py b/plugwise/smile.py index edd3fcae4..62effac66 100644 --- a/plugwise/smile.py +++ b/plugwise/smile.py @@ -149,7 +149,7 @@ async def async_update(self) -> dict[str, GwEntityData]: "cooling_enabled" ] except KeyError as err: - raise DataMissingError("No Plugwise actual data received") from err + raise DataMissingError(f"No data: {err}") from err return self.gw_entities From 1fcba63f8c6ea9b0384fb8d8c1f2b8f043ba3c28 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 1 Jan 2026 13:07:19 +0100 Subject: [PATCH 6/7] Test for no changes in thermostats after update --- .../adam/adam_plus_anna_new_UPDATED_DATA.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/data/adam/adam_plus_anna_new_UPDATED_DATA.json b/tests/data/adam/adam_plus_anna_new_UPDATED_DATA.json index 65bbf87d9..6e6588d9e 100644 --- a/tests/data/adam/adam_plus_anna_new_UPDATED_DATA.json +++ b/tests/data/adam/adam_plus_anna_new_UPDATED_DATA.json @@ -41,5 +41,21 @@ "switches": { "relay": false } + }, + "f2bf9048bef64cc5b6d5110154e33c81": { + "thermostats": { + "primary": [ + "ad4838d7d35c4d6ea796ee12ae5aedf8", + "14df5c4dc8cb4ba69f9d1ac0eaf7c5c6", + "da575e9e09b947e281fb6e3ebce3b174" + ], + "secondary": [] + } + }, + "f871b8c4d63549319221e294e4f88074": { + "thermostats": { + "primary": ["e2f4322d57924fa090fbbc48b3a140dc"], + "secondary": ["1772a4ea304041adb83f357b751341ff"] + } } } From 9e939d8c22d8774915f547a957a846cf501405e9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 1 Jan 2026 13:14:58 +0100 Subject: [PATCH 7/7] Split init loc_data dict --- plugwise/helper.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 3c0d94840..3af88e2b7 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -212,12 +212,7 @@ def _get_locations(self) -> None: loc.loc_id = location.get("id") loc.name = location.find("name").text loc._type = location.find("type").text - self._loc_data[loc.loc_id] = { - "name": loc.name, - "primary": [], - "primary_prio": 0, - "secondary": [], - } + self._loc_data[loc.loc_id] = {"name": loc.name} # Home location is of type building if loc._type == "building": counter += 1 @@ -777,6 +772,7 @@ def _match_and_rank_thermostats(self) -> None: Match thermostat-appliances with locations, rank them for locations with multiple thermostats. """ for location_id, location in self._loc_data.items(): + location.update({"primary": [], "primary_prio": 0, "secondary": []}) for entity_id, entity in self.gw_entities.items(): self._rank_thermostat( entity_id, entity, location_id, location, THERMO_MATCHING