Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
20 changes: 20 additions & 0 deletions fixtures/anna_v4_no_modules/data.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
8 changes: 8 additions & 0 deletions plugwise/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (
Expand Down
20 changes: 11 additions & 9 deletions plugwise/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 7 additions & 6 deletions plugwise/legacy/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 22 additions & 0 deletions tests/data/anna/anna_v4_no_modules.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
21 changes: 21 additions & 0 deletions tests/test_anna.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading