diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d69dd56c..0fdd4d39b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.8.1 + +- Improve detection of orphaned/removed devices via [#802](https://github.com/plugwise/python-plugwise/pull/802) + ## v1.8.0 - Test/validate for Python 3.14 diff --git a/fixtures/anna_v4_no_modules/data.json b/fixtures/anna_v4_no_modules/data.json new file mode 100644 index 000000000..c8875bed4 --- /dev/null +++ b/fixtures/anna_v4_no_modules/data.json @@ -0,0 +1,20 @@ +{ + "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/plugwise/common.py b/plugwise/common.py index 69ed227c8..2e6e46ae0 100644 --- a/plugwise/common.py +++ b/plugwise/common.py @@ -101,6 +101,11 @@ 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"]: + self._heater_id = NONE + 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 @@ -117,6 +122,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 ( diff --git a/plugwise/helper.py b/plugwise/helper.py index 7ebb4738b..e2a13fe06 100644 --- a/plugwise/helper.py +++ b/plugwise/helper.py @@ -170,9 +170,10 @@ 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"]: - LOGGER.error("No module data found for SmartMeter") # pragma: no cover - return # pragma: no cover + # No module-data present means the device has been removed + if not module_data["contents"]: # pragma: no cover + return + appl.available = None appl.entity_id = self._gateway_id appl.firmware = module_data["firmware_version"] @@ -219,15 +220,16 @@ def _appliance_info_finder(self, appl: Munch, appliance: etree.Element) -> Munch return self._appl_thermostat_info(appl, appliance) case "heater_central": # Collect heater_central entity info - self._appl_heater_central_info( - appl, appliance, False - ) # False means non-legacy entity + # 251016: the added guarding below also solves Core Issue #104433 + if not ( + appl := self._appl_heater_central_info( + appl, appliance, False + ) + ): # False means non-legacy entity + return Munch() self._dhw_allowed_modes = self._get_appl_actuator_modes( appliance, "domestic_hot_water_mode_control_functionality" ) - # Skip orphaned heater_central (Core Issue #104433) - if appl.entity_id != self.heater_id: - return Munch() return appl case _ as s if s.endswith("_plug"): # Collect info from plug-types (Plug, Aqara Smart Plug) diff --git a/plugwise/legacy/helper.py b/plugwise/legacy/helper.py index d5e6918ab..7185376b6 100644 --- a/plugwise/legacy/helper.py +++ b/plugwise/legacy/helper.py @@ -214,18 +214,19 @@ def _energy_entity_info_finder(self, appliance: etree, appl: Munch) -> Munch: module_data = self._get_module_data( appliance, locator, self._modules, legacy=True ) - 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": - return None + if not module_data["contents"]: + return ( + Munch() + ) # no module-data present means the device has been removed + appl.firmware = module_data["firmware_version"] appl.hardware = module_data["hardware_version"] appl.model = module_data["vendor_model"] - appl.vendor_name = module_data["vendor_name"] if appl.hardware is not None: hw_version = appl.hardware.replace("-", "") appl.model = version_to_model(hw_version) - appl.firmware = module_data["firmware_version"] + appl.vendor_name = module_data["vendor_name"] + appl.zigbee_mac = module_data["zigbee_mac_address"] return appl diff --git a/pyproject.toml b/pyproject.toml index e495fc355..16ed3ee95 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.1" license = "MIT" description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3." readme = "README.md" 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..f577a0bc0 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) 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. +