From f7c9019a1bd7088bd31378f0a6d0a16ca676cf03 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 18 Dec 2025 18:46:24 +0100 Subject: [PATCH 1/8] Try different approach --- plugwise/common.py | 4 +-- plugwise/data.py | 86 ++++++++++++++++++++++------------------------ plugwise/helper.py | 9 ++--- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/plugwise/common.py b/plugwise/common.py index 9722975e6..795d68ed3 100644 --- a/plugwise/common.py +++ b/plugwise/common.py @@ -175,7 +175,7 @@ def _reorder_devices(self) -> None: break self.gw_entities = {**reordered, **self.gw_entities} - def _entity_switching_group(self, entity: GwEntityData, data: GwEntityData) -> None: + def _entity_switching_group(self, entity: GwEntityData) -> None: """Helper-function for _get_device_zone_data(). Determine switching group device data. @@ -185,7 +185,7 @@ def _entity_switching_group(self, entity: GwEntityData, data: GwEntityData) -> N for member in entity["members"]: if self.gw_entities[member]["switches"].get("relay"): counter += 1 - data["switches"]["relay"] = counter != 0 + entity["switches"]["relay"] = counter != 0 self._count += 1 def _get_groups(self) -> dict[str, GwEntityData]: diff --git a/plugwise/data.py b/plugwise/data.py index 3ab9f53f4..630c47997 100644 --- a/plugwise/data.py +++ b/plugwise/data.py @@ -55,12 +55,11 @@ def _update_gw_entities(self) -> None: """ mac_list: list[str] = [] for entity_id, entity in self.gw_entities.items(): - data = self._get_entity_data(entity_id) + self._get_entity_data(entity_id, entity) if entity_id == self._gateway_id: mac_list = self._detect_low_batteries() - self._add_or_update_notifications(entity_id, entity, data) + self._add_or_update_notifications(entity_id, entity) - entity.update(data) is_battery_low = ( mac_list and "low_battery" in entity["binary_sensors"] @@ -106,7 +105,7 @@ def _detect_low_batteries(self) -> list[str]: return mac_address_list def _add_or_update_notifications( - self, entity_id: str, entity: GwEntityData, data: GwEntityData + self, entity_id: str, entity: GwEntityData ) -> None: """Helper-function adding or updating the Plugwise notifications.""" if ( @@ -116,8 +115,8 @@ def _add_or_update_notifications( "binary_sensors" in entity and "plugwise_notification" in entity["binary_sensors"] ): - data["binary_sensors"]["plugwise_notification"] = bool(self._notifications) - data["notifications"] = self._notifications + entity["binary_sensors"]["plugwise_notification"] = bool(self._notifications) + entity["notifications"] = self._notifications self._count += 2 def _update_for_cooling(self, entity: GwEntityData) -> None: @@ -177,59 +176,56 @@ def _get_location_data(self, loc_id: str) -> GwEntityData: self._count -= 1 # Thermostat data (presets, temperatures etc) - self._climate_data(loc_id, zone, data) + self._climate_data(loc_id, zone) return data - def _get_entity_data(self, entity_id: str) -> GwEntityData: + def _get_entity_data(self, entity_id: str, entity: GwEntityData) -> None: """Helper-function for _update_gw_entities() and async_update(). Provide entity-data, based on appliance_id (= entity_id). """ - entity = self.gw_entities[entity_id] - data = self._get_measurement_data(entity_id) + self._get_measurement_data(entity_id, entity) # Check availability of wired-connected entities # Smartmeter self._check_availability( - entity, "smartmeter", data, "P1 does not seem to be connected" + entity, "smartmeter", "P1 does not seem to be connected" ) # OpenTherm entity if entity["name"] != "OnOff": self._check_availability( - entity, "heater_central", data, "no OpenTherm communication" + entity, "heater_central", "no OpenTherm communication" ) # Switching groups data - self._entity_switching_group(entity, data) + self._entity_switching_group(entity) # Adam data if self.check_name(ADAM): - self._get_adam_data(entity, data) + self._get_adam_data(entity) # Thermostat data for Anna (presets, temperatures etc) if self.check_name(ANNA) and entity["dev_class"] == "thermostat": - self._climate_data(entity_id, entity, data) - self._get_anna_control_state(data) - - return data + self._climate_data(entity_id, entity) + self._get_anna_control_state(entity) def _check_availability( - self, entity: GwEntityData, dev_class: str, data: GwEntityData, message: str + self, entity: GwEntityData, dev_class: str, message: str ) -> None: """Helper-function for _get_entity_data(). Provide availability status for the wired-connected devices. """ if entity["dev_class"] == dev_class: - data["available"] = True + entity["available"] = True self._count += 1 for item in self._notifications.values(): for msg in item.values(): if message in msg: - data["available"] = False + entity["available"] = False break - def _get_adam_data(self, entity: GwEntityData, data: GwEntityData) -> None: + def _get_adam_data(self, entity: GwEntityData) -> None: """Helper-function for _get_entity_data(). Determine Adam heating-status for on-off heating via valves, @@ -239,27 +235,27 @@ def _get_adam_data(self, entity: GwEntityData, data: GwEntityData) -> None: if entity["dev_class"] == "heater_central": # Indicate heating_state based on valves being open in case of city-provided heating if self._on_off_device and isinstance(self._heating_valves(), int): - data["binary_sensors"]["heating_state"] = self._heating_valves() != 0 + entity["binary_sensors"]["heating_state"] = self._heating_valves() != 0 # Add cooling_enabled binary_sensor if ( - "binary_sensors" in data - and "cooling_enabled" not in data["binary_sensors"] + "binary_sensors" in entity + and "cooling_enabled" not in entity["binary_sensors"] and self._cooling_present ): - data["binary_sensors"]["cooling_enabled"] = self._cooling_enabled + entity["binary_sensors"]["cooling_enabled"] = self._cooling_enabled self._count += 1 # Show the allowed regulation_modes and gateway_modes if entity["dev_class"] == "gateway": if self._reg_allowed_modes: - data["regulation_modes"] = self._reg_allowed_modes + entity["regulation_modes"] = self._reg_allowed_modes self._count += 1 if self._gw_allowed_modes: - data["gateway_modes"] = self._gw_allowed_modes + entity["gateway_modes"] = self._gw_allowed_modes self._count += 1 def _climate_data( - self, location_id: str, entity: GwEntityData, data: GwEntityData + self, location_id: str, entity: GwEntityData ) -> None: """Helper-function for _get_entity_data(). @@ -270,38 +266,38 @@ def _climate_data( loc_id = entity["location"] # Presets - data["preset_modes"] = None - data["active_preset"] = None + entity["preset_modes"] = None + entity["active_preset"] = None self._count += 2 if presets := self._presets(loc_id): - data["preset_modes"] = list(presets) - data["active_preset"] = self._preset(loc_id) + entity["preset_modes"] = list(presets) + entity["active_preset"] = self._preset(loc_id) # Schedule - data["available_schedules"] = [] - data["select_schedule"] = None + entity["available_schedules"] = [] + entity["select_schedule"] = None self._count += 2 avail_schedules, sel_schedule = self._schedules(loc_id) if avail_schedules != [NONE]: - data["available_schedules"] = avail_schedules - data["select_schedule"] = sel_schedule + entity["available_schedules"] = avail_schedules + entity["select_schedule"] = sel_schedule # Set HA climate HVACMode: auto, heat, heat_cool, cool and off - data["climate_mode"] = "auto" + entity["climate_mode"] = "auto" self._count += 1 if sel_schedule in (NONE, OFF): - data["climate_mode"] = "heat" + entity["climate_mode"] = "heat" if self._cooling_present: - data["climate_mode"] = ( + entity["climate_mode"] = ( "cool" if self.check_reg_mode("cooling") else "heat_cool" ) if self.check_reg_mode("off"): - data["climate_mode"] = "off" + entity["climate_mode"] = "off" if NONE not in avail_schedules: self._get_schedule_states_with_off( - loc_id, avail_schedules, sel_schedule, data + loc_id, avail_schedules, sel_schedule, entity ) def check_reg_mode(self, mode: str) -> bool: @@ -326,7 +322,7 @@ def _get_anna_control_state(self, data: GwEntityData) -> None: data["control_state"] = "cooling" def _get_schedule_states_with_off( - self, location: str, schedules: list[str], selected: str, data: GwEntityData + self, location: str, schedules: list[str], selected: str, entity: GwEntityData ) -> None: """Collect schedules with states for each thermostat. @@ -335,11 +331,11 @@ def _get_schedule_states_with_off( all_off = True self._schedule_old_states[location] = {} for schedule in schedules: - active: bool = schedule == selected and data["climate_mode"] == "auto" + active: bool = schedule == selected and entity["climate_mode"] == "auto" self._schedule_old_states[location][schedule] = "off" if active: self._schedule_old_states[location][schedule] = "on" all_off = False if all_off: - data["select_schedule"] = OFF + entity["select_schedule"] = OFF diff --git a/plugwise/helper.py b/plugwise/helper.py index 475eabd32..6652fc563 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -321,13 +321,12 @@ def _get_zone_data(self, loc_id: str) -> GwEntityData: return data - def _get_measurement_data(self, entity_id: str) -> GwEntityData: + def _get_measurement_data(self, entity_id: str, entity: GwEntityData) -> None: """Helper-function for smile.py: _get_entity_data(). Collect the appliance-data based on entity_id. """ data: GwEntityData = {"binary_sensors": {}, "sensors": {}, "switches": {}} - entity = self.gw_entities[entity_id] # Get P1 smartmeter data from LOCATIONS smile_is_power = self.smile.type == "power" @@ -337,7 +336,8 @@ def _get_measurement_data(self, entity_id: str) -> GwEntityData: data.update(self._power_data_from_location()) if smile_is_power and not self.smile.anna_p1: - return data + entity.update(data) + return # Get group data if "members" in entity: @@ -372,7 +372,8 @@ def _get_measurement_data(self, entity_id: str) -> GwEntityData: self._cleanup_data(data) - return data + entity.update(data) + return def _collect_group_sensors( self, From b15d5a1627a18765b6b940a39085d5374fa44d00 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 19 Dec 2025 08:06:05 +0100 Subject: [PATCH 2/8] Rework legacy --- plugwise/legacy/data.py | 52 ++++++++++++++++++--------------------- plugwise/legacy/helper.py | 9 ++++--- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/plugwise/legacy/data.py b/plugwise/legacy/data.py index c5cde29a7..ace3fa135 100644 --- a/plugwise/legacy/data.py +++ b/plugwise/legacy/data.py @@ -28,67 +28,63 @@ def _update_gw_entities(self) -> None: Collect data for each entity and add to self.gw_entities. """ for entity_id, entity in self.gw_entities.items(): - data = self._get_entity_data(entity_id) - entity.update(data) + self._get_entity_data(entity_id, entity) remove_empty_platform_dicts(entity) - def _get_entity_data(self, entity_id: str) -> GwEntityData: + def _get_entity_data(self, entity_id: str, entity: GwEntityData) -> None: """Helper-function for _all_entity_data() and async_update(). Provide entity-data, based on Location ID (= entity_id), from APPLIANCES. """ - entity = self.gw_entities[entity_id] - data = self._get_measurement_data(entity_id) + self._get_measurement_data(entity_id, entity) # Switching groups data - self._entity_switching_group(entity, data) + self._entity_switching_group(entity) # Skip obtaining data when not a thermostat if entity["dev_class"] != "thermostat": - return data + return # Thermostat data (presets, temperatures etc) - self._climate_data(entity, data) - self._get_anna_control_state(data) + self._climate_data(entity) + self._get_anna_control_state(entity) - return data - - def _climate_data(self, entity: GwEntityData, data: GwEntityData) -> None: + def _climate_data(self, entity: GwEntityData) -> None: """Helper-function for _get_entity_data(). Determine climate-control entity data. """ # Presets - data["preset_modes"] = None - data["active_preset"] = None + entity["preset_modes"] = None + entity["active_preset"] = None self._count += 2 if presets := self._presets(): - data["preset_modes"] = list(presets) - data["active_preset"] = self._preset() + entity["preset_modes"] = list(presets) + entity["active_preset"] = self._preset() # Schedule - data["available_schedules"] = [] - data["select_schedule"] = None + entity["available_schedules"] = [] + entity["select_schedule"] = None self._count += 2 avail_schedules, sel_schedule = self._schedules() if avail_schedules != [NONE]: - data["available_schedules"] = avail_schedules - data["select_schedule"] = sel_schedule + entity["available_schedules"] = avail_schedules + entity["select_schedule"] = sel_schedule # Set HA climate HVACMode: auto, heat - data["climate_mode"] = "auto" + entity["climate_mode"] = "auto" self._count += 1 if sel_schedule in (NONE, OFF): - data["climate_mode"] = "heat" + entity["climate_mode"] = "heat" - def _get_anna_control_state(self, data: GwEntityData) -> None: + def _get_anna_control_state(self, entity: GwEntityData) -> None: """Set the thermostat control_state based on the opentherm/onoff device state.""" - data["control_state"] = "idle" + entity["control_state"] = "idle" self._count += 1 - for entity in self.gw_entities.values(): - if entity["dev_class"] != "heater_central": + for device in self.gw_entities.values(): + if device["dev_class"] != "heater_central": continue - binary_sensors = entity["binary_sensors"] + binary_sensors = device["binary_sensors"] if binary_sensors["heating_state"]: - data["control_state"] = "heating" + entity["control_state"] = "heating" diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index f095466d8..c38e5abad 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -249,20 +249,20 @@ def _p1_smartmeter_info_finder(self, appl: Munch) -> None: self._create_gw_entities(appl) - def _get_measurement_data(self, entity_id: str) -> GwEntityData: + def _get_measurement_data(self, entity_id: str, entity: GwEntityData) -> None: """Helper-function for smile.py: _get_entity_data(). Collect the appliance-data based on entity_id. """ data: GwEntityData = {"binary_sensors": {}, "sensors": {}, "switches": {}} # Get P1 smartmeter data from MODULES - entity = self.gw_entities[entity_id] # !! DON'T CHANGE below two if-lines, will break stuff !! if self.smile.type == "power": if entity["dev_class"] == "smartmeter": data.update(self._power_data_from_modules()) - return data + entity.update(data) + return measurements = DEVICE_MEASUREMENTS if self._is_thermostat and entity_id == self.heater_id: @@ -290,7 +290,8 @@ def _get_measurement_data(self, entity_id: str) -> GwEntityData: data.pop("c_heating_state") self._count -= 1 - return data + entity.update(data) + return def _power_data_from_modules(self) -> GwEntityData: """Helper-function for smile.py: _get_entity_data(). From a18165f1b321c41ec6bc39198121693ef2670cad Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 19 Dec 2025 08:20:57 +0100 Subject: [PATCH 3/8] Ruffed --- plugwise/data.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugwise/data.py b/plugwise/data.py index 630c47997..068a69c5d 100644 --- a/plugwise/data.py +++ b/plugwise/data.py @@ -115,7 +115,9 @@ def _add_or_update_notifications( "binary_sensors" in entity and "plugwise_notification" in entity["binary_sensors"] ): - entity["binary_sensors"]["plugwise_notification"] = bool(self._notifications) + entity["binary_sensors"]["plugwise_notification"] = bool( + self._notifications + ) entity["notifications"] = self._notifications self._count += 2 @@ -254,9 +256,7 @@ def _get_adam_data(self, entity: GwEntityData) -> None: entity["gateway_modes"] = self._gw_allowed_modes self._count += 1 - def _climate_data( - self, location_id: str, entity: GwEntityData - ) -> None: + def _climate_data(self, location_id: str, entity: GwEntityData) -> None: """Helper-function for _get_entity_data(). Determine climate-control entity data. From f544a7127ef42874e2a40c9cc5520368299047c5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Dec 2025 17:30:01 +0100 Subject: [PATCH 4/8] Improve code clarity --- plugwise/helper.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index 6652fc563..f6633d8ed 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -329,10 +329,9 @@ def _get_measurement_data(self, entity_id: str, entity: GwEntityData) -> None: data: GwEntityData = {"binary_sensors": {}, "sensors": {}, "switches": {}} # Get P1 smartmeter data from LOCATIONS + is_smartmeter = entity.get("dev_class") == "smartmeter" smile_is_power = self.smile.type == "power" - if (smile_is_power or self.smile.anna_p1) and entity.get( - "dev_class" - ) == "smartmeter": + if is_smartmeter and (smile_is_power or self.smile.anna_p1): data.update(self._power_data_from_location()) if smile_is_power and not self.smile.anna_p1: From 2a507062d862124889c0f45022fb345bcc45ba53 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Dec 2025 17:41:28 +0100 Subject: [PATCH 5/8] Use different approach for collection zone-data too --- plugwise/data.py | 22 +++++++++------------- plugwise/helper.py | 7 +++---- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/plugwise/data.py b/plugwise/data.py index 068a69c5d..fc45138fb 100644 --- a/plugwise/data.py +++ b/plugwise/data.py @@ -45,8 +45,7 @@ def _update_zones(self) -> None: Collect data for each zone/location and add to self._zones. """ for location_id, zone in self._zones.items(): - data = self._get_location_data(location_id) - zone.update(data) + self._get_location_data(location_id, zone) def _update_gw_entities(self) -> None: """Helper-function for _all_entities_data() and async_update(). @@ -155,33 +154,30 @@ def _update_for_cooling(self, entity: GwEntityData) -> None: 3 # add 4 total, remove 1, count the conditional remove separately ) - def _get_location_data(self, loc_id: str) -> GwEntityData: + def _get_location_data(self, loc_id: str, zone: GwEntityData) -> None: """Helper-function for _all_entity_data() and async_update(). Provide entity-data, based on Location ID (= loc_id). """ - zone = self._zones[loc_id] - data = self._get_zone_data(loc_id) - self._regulation_control(data) + self._get_zone_data(loc_id, zone) + self._regulation_control(zone) - data["control_state"] = "idle" + zone["control_state"] = "idle" self._count += 1 - if (ctrl_state := self._control_state(data)) and ctrl_state in ( + if (ctrl_state := self._control_state(zone)) and ctrl_state in ( "cooling", "heating", "preheating", ): - data["control_state"] = str(ctrl_state) + zone["control_state"] = str(ctrl_state) - if "setpoint" in data["sensors"]: - data["sensors"].pop("setpoint") # remove, only used in _control_state() + if "setpoint" in zone["sensors"]: + zone["sensors"].pop("setpoint") # remove, only used in _control_state() self._count -= 1 # Thermostat data (presets, temperatures etc) self._climate_data(loc_id, zone) - return data - def _get_entity_data(self, entity_id: str, entity: GwEntityData) -> None: """Helper-function for _update_gw_entities() and async_update(). diff --git a/plugwise/helper.py b/plugwise/helper.py index f6633d8ed..dc1373fbb 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -305,13 +305,12 @@ def _get_appliances_with_offset_functionality(self) -> list[str]: return therm_list - def _get_zone_data(self, loc_id: str) -> GwEntityData: + def _get_zone_data(self, loc_id: str, zone: GwEntityData) -> None: """Helper-function for smile.py: _get_entity_data(). - Collect the location-data based on location id. + Collect the location/zone-data based on location id. """ data: GwEntityData = {"sensors": {}} - zone = self._zones[loc_id] measurements = ZONE_MEASUREMENTS if ( location := self._domain_objects.find(f'./location[@id="{loc_id}"]') @@ -319,7 +318,7 @@ def _get_zone_data(self, loc_id: str) -> GwEntityData: self._appliance_measurements(location, data, measurements) self._get_actuator_functionalities(location, zone, data) - return data + zone.update(data) def _get_measurement_data(self, entity_id: str, entity: GwEntityData) -> None: """Helper-function for smile.py: _get_entity_data(). From ab97c11bcdf401f7e325ad2cc3dcd45dfb018d47 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Dec 2025 17:43:32 +0100 Subject: [PATCH 6/8] Clean up --- plugwise/helper.py | 1 - plugwise/legacy/helper.py | 1 - 2 files changed, 2 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index dc1373fbb..0a05864ae 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -371,7 +371,6 @@ def _get_measurement_data(self, entity_id: str, entity: GwEntityData) -> None: self._cleanup_data(data) entity.update(data) - return def _collect_group_sensors( self, diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index c38e5abad..0d47cc58f 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -291,7 +291,6 @@ def _get_measurement_data(self, entity_id: str, entity: GwEntityData) -> None: self._count -= 1 entity.update(data) - return def _power_data_from_modules(self) -> GwEntityData: """Helper-function for smile.py: _get_entity_data(). From 285f121dee7c9040c50f8121c4b6c1bde00c440f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Dec 2025 17:53:57 +0100 Subject: [PATCH 7/8] Implement .get() as suggested --- plugwise/legacy/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index 0d47cc58f..112ef6e8e 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -258,7 +258,7 @@ def _get_measurement_data(self, entity_id: str, entity: GwEntityData) -> None: # Get P1 smartmeter data from MODULES # !! DON'T CHANGE below two if-lines, will break stuff !! if self.smile.type == "power": - if entity["dev_class"] == "smartmeter": + if entity.get("dev_class") == "smartmeter": data.update(self._power_data_from_modules()) entity.update(data) From 8487ea71b18b918a8c4cc4b6992ec0af3714cc64 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Dec 2025 18:03:17 +0100 Subject: [PATCH 8/8] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f97c0f90..a52eff569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Ongoing + +- Code optimizations via PR [#837](https://github.com/plugwise/python-plugwise/pull/837) + ## v1.11.0 - Extend feature: support pumping group, add group sensors