From 221787437405c8f76e2dd8cb6efba21c436f3d5a Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 14 Oct 2025 19:53:04 +0200 Subject: [PATCH 01/11] Stop thermostat appliance collection when the module is not present --- plugwise/common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugwise/common.py b/plugwise/common.py index 69ed227c8..e20712260 100644 --- a/plugwise/common.py +++ b/plugwise/common.py @@ -117,6 +117,9 @@ def _appl_thermostat_info( locator = "./logs/point_log[type='thermostat']/thermostat" xml_2 = return_valid(xml_2, self._domain_objects) module_data = self._get_module_data(xml_1, locator, xml_2) + if not module_data["contents"]: + return Munch() # no module-data present means the device has been removed + appl.vendor_name = module_data["vendor_name"] appl.model = module_data["vendor_model"] if ( From a4909e7971a0b0c624e284ca67b83ec013cf4483 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 14 Oct 2025 19:57:24 +0200 Subject: [PATCH 02/11] Bump to v1.8.1a0 for testing --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e495fc355..8a88c8b3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "plugwise" -version = "1.8.0" +version = "1.8.1a0" license = "MIT" description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" From d85224283e78dd006b4e10f8d86cc7a6a0efa417 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 14 Oct 2025 20:18:39 +0200 Subject: [PATCH 03/11] Implement more none module_data guarding --- plugwise/common.py | 2 ++ plugwise/helper.py | 4 ++-- plugwise/legacy/helper.py | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugwise/common.py b/plugwise/common.py index e20712260..e7d2cc93b 100644 --- a/plugwise/common.py +++ b/plugwise/common.py @@ -101,6 +101,8 @@ def _appl_heater_central_info( module_data = self._get_module_data(xml_1, locator_1, xml_3) if not module_data["contents"]: module_data = self._get_module_data(xml_1, locator_2, xml_3) + if not module_data["contents"]: + return Munch() # no module-data present means the device has been removed appl.vendor_name = module_data["vendor_name"] appl.hardware = module_data["hardware_version"] appl.model_id = module_data["vendor_model"] if not legacy else None diff --git a/plugwise/helper.py b/plugwise/helper.py index 7ebb4738b..c7dc6c3ce 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -171,8 +171,8 @@ def _get_p1_smartmeter_info(self) -> None: locator = MODULE_LOCATOR module_data = self._get_module_data(self._home_location, locator) if not module_data["contents"]: - LOGGER.error("No module data found for SmartMeter") # pragma: no cover - return # pragma: no cover + return Munch() # no module-data present means the device has been removed + appl.available = None appl.entity_id = self._gateway_id appl.firmware = module_data["firmware_version"] diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index d5e6918ab..8c40173be 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -214,6 +214,9 @@ def _energy_entity_info_finder(self, appliance: etree, appl: Munch) -> Munch: module_data = self._get_module_data( appliance, locator, self._modules, legacy=True ) + if not module_data["contents"]: + return Munch() # no module-data present means the device has been removed + appl.zigbee_mac = module_data["zigbee_mac_address"] # Filter appliance without zigbee_mac, it's an orphaned device if appl.zigbee_mac is None and self.smile.type != "power": From bb6ece5716ea00299eb6a6e30bf5a91583f1b50a Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 Oct 2025 15:16:49 +0200 Subject: [PATCH 04/11] Keep the existing pragma's for the smartmeter --- plugwise/helper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugwise/helper.py b/plugwise/helper.py index c7dc6c3ce..884952780 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -170,8 +170,9 @@ def _get_p1_smartmeter_info(self) -> None: appl = Munch() locator = MODULE_LOCATOR module_data = self._get_module_data(self._home_location, locator) - if not module_data["contents"]: - return Munch() # no module-data present means the device has been removed + # No module-data present means the device has been removed + if not module_data["contents"]: # pragma: no cover + return Munch() # pragma: no cover appl.available = None appl.entity_id = self._gateway_id From 85ccb6f03aeb356026219818e06094772e97d1de Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 16 Oct 2025 15:25:30 +0200 Subject: [PATCH 05/11] Add testcoverage for missing modules --- tests/data/anna/anna_v4_no_modules.json | 22 + tests/test_anna.py | 21 + .../core.domain_objects.xml | 1023 +++++++++++++++++ 3 files changed, 1066 insertions(+) create mode 100644 tests/data/anna/anna_v4_no_modules.json create mode 100644 userdata/anna_v4_no_modules/core.domain_objects.xml diff --git a/tests/data/anna/anna_v4_no_modules.json b/tests/data/anna/anna_v4_no_modules.json new file mode 100644 index 000000000..4117440c8 --- /dev/null +++ b/tests/data/anna/anna_v4_no_modules.json @@ -0,0 +1,22 @@ +{ + "devices": { + "0466eae8520144c78afb29628384edeb": { + "binary_sensors": { + "plugwise_notification": false + }, + "dev_class": "gateway", + "firmware": "4.0.15", + "hardware": "AME Smile 2.0 board", + "location": "94c107dc6ac84ed98e9f68c0dd06bf71", + "mac_address": "012345670001", + "model": "Gateway", + "model_id": "smile_thermo", + "name": "Smile Anna", + "notifications": {}, + "sensors": { + "outdoor_temperature": 7.44 + }, + "vendor": "Plugwise" + } + } +} diff --git a/tests/test_anna.py b/tests/test_anna.py index ced0a22ff..063984838 100644 --- a/tests/test_anna.py +++ b/tests/test_anna.py @@ -546,3 +546,24 @@ async def test_connect_anna_loria_driessens(self): await api.close_connection() await self.disconnect(server, client) + + @pytest.mark.asyncio + async def test_connect_anna_v4_no_modules(self): + """Test an Anna v4 with removed Anna and OpenTherm device.""" + self.smile_setup = "anna_v4_no_modules" + + testdata = await self.load_testdata(SMILE_TYPE, self.smile_setup) + server, api, client = await self.connect_wrapper() + assert api.smile.hostname == "smile000000" + + self.validate_test_basics( + _LOGGER, + api, + smile_version=None, + ) + + await self.device_test(api, "2022-05-16 00:00:01", testdata) + assert self.entity_items == 12 + + await api.close_connection() + await self.disconnect(server, client) \ No newline at end of file diff --git a/userdata/anna_v4_no_modules/core.domain_objects.xml b/userdata/anna_v4_no_modules/core.domain_objects.xml new file mode 100644 index 000000000..b919bd577 --- /dev/null +++ b/userdata/anna_v4_no_modules/core.domain_objects.xml @@ -0,0 +1,1023 @@ + + + + + Plugwise + ThermoExtension + 6539-1301-2304 + 2018-02-08T11:15:57+01:00 + 2019-01-31T10:06:34.781+01:00 + 2019-05-14T20:03:36.442+02:00 + + + + + + + + Thermostat presets + Provides presets for a Location. +