Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix for Adam: improve recognition of unknown zigbee devices #651

Merged
merged 18 commits into from
Nov 16, 2024
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.5.2

- Bugfix for Adam: improve recognition of unknown zigbee devices.

## v1.5.1

- Fix typing and rounding of P1 and thermostat sensors, energy-device-related code improvements.
Expand Down
14 changes: 13 additions & 1 deletion fixtures/adam_plus_anna_new/all_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@
"dhw_cm_switch": false
}
},
"10016900610d4c7481df78c89606ef22": {
"available": true,
"dev_class": "valve_actuator_plug",
"location": "d9786723dbcf4f19b5c629a54629f9c7",
"model_id": "TS0011",
"name": "Aanvoer water afsluiter (nous lz3)",
"switches": {
"relay": false
},
"vendor": "_TZ3000_abjodzas",
"zigbee_mac_address": "A4C13862AF9917B1"
},
"1772a4ea304041adb83f357b751341ff": {
"available": true,
"binary_sensors": {
Expand Down Expand Up @@ -266,7 +278,7 @@
"cooling_present": false,
"gateway_id": "da224107914542988a88561b4452b0f6",
"heater_id": "056ee145a816487eaa69243c3280f8bf",
"item_count": 157,
"item_count": 165,
"notifications": {},
"reboot": true,
"smile_name": "Adam"
Expand Down
2 changes: 1 addition & 1 deletion fixtures/m_adam_cooling/all_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@
"cooling_present": true,
"gateway_id": "da224107914542988a88561b4452b0f6",
"heater_id": "056ee145a816487eaa69243c3280f8bf",
"item_count": 157,
"item_count": 89,
"notifications": {},
"reboot": true,
"smile_name": "Adam"
Expand Down
2 changes: 1 addition & 1 deletion fixtures/m_adam_heating/all_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
"cooling_present": false,
"gateway_id": "da224107914542988a88561b4452b0f6",
"heater_id": "056ee145a816487eaa69243c3280f8bf",
"item_count": 157,
"item_count": 89,
"notifications": {},
"reboot": true,
"smile_name": "Adam"
Expand Down
18 changes: 8 additions & 10 deletions plugwise/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,12 @@ def _appl_heater_central_info(
appl.name = "OpenTherm"
locator_1 = "./logs/point_log[type='flame_state']/boiler_state"
locator_2 = "./services/boiler_state"
mod_type = "boiler_state"
# xml_1: appliance
# xml_3: self._modules for legacy, self._domain_objects for actual
xml_3 = return_valid(xml_3, self._domain_objects)
module_data = self._get_module_data(xml_1, locator_1, mod_type, xml_3)
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, mod_type, xml_3)
module_data = self._get_module_data(xml_1, locator_2, xml_3)
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 @@ -94,15 +93,15 @@ def _appl_heater_central_info(
def _appl_thermostat_info(self, appl: Munch, xml_1: etree, xml_2: etree = None) -> Munch:
"""Helper-function for _appliance_info_finder()."""
locator = "./logs/point_log[type='thermostat']/thermostat"
mod_type = "thermostat"
xml_2 = return_valid(xml_2, self._domain_objects)
module_data = self._get_module_data(xml_1, locator, mod_type, xml_2)
module_data = self._get_module_data(xml_1, locator, xml_2)
appl.vendor_name = module_data["vendor_name"]
appl.model = module_data["vendor_model"]
if appl.model != "ThermoTouch": # model_id for Anna not present as stand-alone device
appl.model_id = appl.model
appl.model = check_model(appl.model, appl.vendor_name)

appl.available = module_data["reachable"]
appl.hardware = module_data["hardware_version"]
appl.firmware = module_data["firmware_version"]
appl.zigbee_mac = module_data["zigbee_mac_address"]
Expand Down Expand Up @@ -192,6 +191,7 @@ def _create_gw_devices(self, appl: Munch) -> None:
self.gw_devices[appl.dev_id] = {"dev_class": appl.pwclass}
self._count += 1
for key, value in {
"available": appl.available,
"firmware": appl.firmware,
"hardware": appl.hardware,
"location": appl.location,
Expand Down Expand Up @@ -278,7 +278,6 @@ def _get_module_data(
self,
xml_1: etree,
locator: str,
mod_type: str,
xml_2: etree = None,
legacy: bool = False,
) -> ModelData:
Expand All @@ -295,12 +294,11 @@ def _get_module_data(
"vendor_model": None,
"zigbee_mac_address": None,
}
# xml_1: appliance

if (appl_search := xml_1.find(locator)) is not None:
link_tag = appl_search.tag
link_id = appl_search.attrib["id"]
loc = f".//services/{mod_type}[@id='{link_id}']...."
if legacy:
loc = f".//{mod_type}[@id='{link_id}']...."
loc = f".//services/{link_tag}[@id='{link_id}']...."
# Not possible to walrus for some reason...
# xml_2: self._modules for legacy, self._domain_objects for actual
search = return_valid(xml_2, self._domain_objects)
Expand Down
1 change: 1 addition & 0 deletions plugwise/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@

MAX_SETPOINT: Final[float] = 30.0
MIN_SETPOINT: Final[float] = 4.0
MODULE_LOCATOR: Final = "./logs/point_log/*[@id]"
NONE: Final = "None"
OFF: Final = "off"

Expand Down
39 changes: 7 additions & 32 deletions plugwise/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
LIMITS,
LOCATIONS,
LOGGER,
MODULE_LOCATOR,
NONE,
OFF,
P1_MEASUREMENTS,
Expand Down Expand Up @@ -299,6 +300,7 @@ def _all_appliances(self) -> None:
if appl.pwclass in THERMOSTAT_CLASSES and appl.location is None:
continue

appl.available = None
appl.dev_id = appliance.attrib["id"]
appl.name = appliance.find("name").text
appl.model = None
Expand Down Expand Up @@ -356,9 +358,8 @@ def _p1_smartmeter_info_finder(self, appl: Munch) -> None:
"""Collect P1 DSMR SmartMeter info."""
loc_id = next(iter(self.loc_data.keys()))
location = self._domain_objects.find(f'./location[@id="{loc_id}"]')
locator = "./logs/point_log/electricity_point_meter"
mod_type = "electricity_point_meter"
module_data = self._get_module_data(location, locator, mod_type)
locator = MODULE_LOCATOR
module_data = self._get_module_data(location, locator)
if not module_data["contents"]:
LOGGER.error("No module data found for SmartMeter") # pragma: no cover
return None # pragma: no cover
Expand Down Expand Up @@ -396,13 +397,13 @@ def _appliance_info_finder(self, appl: Munch, appliance: etree) -> Munch:
return appl
case _ as s if s.endswith("_plug"):
# Collect info from plug-types (Plug, Aqara Smart Plug)
locator = "./logs/interval_log/electricity_interval_meter"
mod_type = "electricity_interval_meter"
module_data = self._get_module_data(appliance, locator, mod_type)
locator = MODULE_LOCATOR
module_data = self._get_module_data(appliance, locator)
# A plug without module-data is orphaned/ no present
if not module_data["contents"]:
return Munch()

appl.available = module_data["reachable"]
appl.firmware = module_data["firmware_version"]
appl.hardware = module_data["hardware_version"]
appl.model_id = module_data["vendor_model"]
Expand Down Expand Up @@ -515,9 +516,6 @@ def _get_measurement_data(self, dev_id: str) -> DeviceData:
if appliance.find("type").text in ACTUATOR_CLASSES:
self._get_actuator_functionalities(appliance, device, data)

# Collect availability-status for wireless connected devices to Adam
self._wireless_availability(appliance, data)

if dev_id == self.gateway_id and self.smile(ADAM):
self._get_regulation_mode(appliance, data)
self._get_gateway_mode(appliance, data)
Expand Down Expand Up @@ -710,29 +708,6 @@ def _get_actuator_functionalities(
act_item = cast(ActuatorType, item)
data[act_item] = temp_dict

def _wireless_availability(self, appliance: etree, data: DeviceData) -> None:
"""Helper-function for _get_measurement_data().

Collect the availability-status for wireless connected devices.
"""
if self.smile(ADAM):
# Try collecting for a Plug
locator = "./logs/interval_log/electricity_interval_meter"
mod_type = "electricity_interval_meter"
module_data = self._get_module_data(appliance, locator, mod_type)
if not module_data["contents"]:
# Try collecting for a wireless thermostat
locator = "./logs/point_log[type='thermostat']/thermostat"
mod_type = "thermostat"
module_data = self._get_module_data(appliance, locator, mod_type)
if not module_data["contents"]:
LOGGER.error("No module data found for Plug or wireless thermostat") # pragma: no cover
return None # pragma: no cover

if module_data["reachable"] is not None:
data["available"] = module_data["reachable"]
self._count += 1

def _get_regulation_mode(self, appliance: etree, data: DeviceData) -> None:
"""Helper-function for _get_measurement_data().

Expand Down
6 changes: 3 additions & 3 deletions plugwise/legacy/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def _all_appliances(self) -> None:
appl.pwclass = "heater_central_plug"

appl.model = appl.pwclass.replace("_", " ").title()
appl.available = None
appl.model_id = None
appl.firmware = None
appl.hardware = None
Expand Down Expand Up @@ -230,9 +231,7 @@ def _energy_device_info_finder(self, appliance: etree, appl: Munch) -> Munch:
"""
if self.smile_type in ("power", "stretch"):
locator = "./services/electricity_point_meter"
mod_type = "electricity_point_meter"

module_data = self._get_module_data(appliance, locator, mod_type, self._modules, legacy=True)
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":
Expand All @@ -253,6 +252,7 @@ def _energy_device_info_finder(self, appliance: etree, appl: Munch) -> Munch:
def _p1_smartmeter_info_finder(self, appl: Munch) -> None:
"""Collect P1 DSMR Smartmeter info."""
loc_id = next(iter(self.loc_data.keys()))
appl.available = None
appl.dev_id = loc_id
appl.location = loc_id
appl.mac = None
Expand Down
2 changes: 1 addition & 1 deletion plugwise/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def check_model(name: str | None, vendor_name: str | None) -> str | None:
if name is not None and "lumi.plug" in name:
return "Aqara Smart Plug"

return name # pragma: no cover
return None


def common_match_cases(
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.5.1"
version = "1.5.2"
license = {file = "LICENSE"}
description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3."
readme = "README.md"
Expand Down
6 changes: 4 additions & 2 deletions scripts/manual_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ def json_writer(manual_name: str, all_data: dict) -> None:

m_adam_cooling = base.copy()

# Set cooling_present to true
# Set cooling_present to true, item_count to 89
m_adam_cooling["gateway"]["cooling_present"] = True
m_adam_cooling["gateway"]["item_count"] = 89

# Remove device "67d73d0bd469422db25a618a5fb8eeb0" from anywhere
# Remove devices "67d73d0bd469422db25a618a5fb8eeb0" and "10016900610d4c7481df78c89606ef22" from anywhere
bouwew marked this conversation as resolved.
Show resolved Hide resolved
m_adam_cooling["devices"].pop("67d73d0bd469422db25a618a5fb8eeb0")
m_adam_cooling["devices"].pop("10016900610d4c7481df78c89606ef22")
bouwew marked this conversation as resolved.
Show resolved Hide resolved

# Correct setpoint for "ad4838d7d35c4d6ea796ee12ae5aedf8"
m_adam_cooling["devices"]["ad4838d7d35c4d6ea796ee12ae5aedf8"]["thermostat"][
Expand Down
3 changes: 2 additions & 1 deletion tests/test_adam.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ async def test_connect_adam_plus_anna_new(self):
assert smile.gateway_id == "da224107914542988a88561b4452b0f6"
assert smile._last_active["f2bf9048bef64cc5b6d5110154e33c81"] == "Weekschema"
assert smile._last_active["f871b8c4d63549319221e294e4f88074"] == "Badkamer"
assert self.device_items == 157
assert self.device_items == 165
assert self.device_list == [
"da224107914542988a88561b4452b0f6",
"056ee145a816487eaa69243c3280f8bf",
"10016900610d4c7481df78c89606ef22",
"67d73d0bd469422db25a618a5fb8eeb0",
"e2f4322d57924fa090fbbc48b3a140dc",
"29542b2b6a6a4169acecc15c72a599b8",
Expand Down
69 changes: 69 additions & 0 deletions userdata/adam_plus_anna_new/core.domain_objects.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<domain_objects>
<appliance id="10016900610d4c7481df78c89606ef22">
<name>Aanvoer water afsluiter (nous lz3)</name>
<description>A device that communicates through the ZigBee protocol.</description>
<type>valve_actuator</type>
<created_date>2024-11-11T20:31:49.734+01:00</created_date>
<modified_date>2024-11-15T13:55:19.574+01:00</modified_date>
<deleted_date/>
<location id="d9786723dbcf4f19b5c629a54629f9c7"/>
<groups>
<group id="195983e6a2ac4567a0acd14afff21ae0"/>
</groups>
<logs>
<point_log id="8ce65a98d6ba4ca9a531272927891014">
<type>relay</type>
<unit/>
<updated_date>2024-11-15T13:55:19.571+01:00</updated_date>
<last_consecutive_log_date>2024-11-15T13:55:19.571+01:00</last_consecutive_log_date>
<interval/>
<relay id="e837f6f0f2014b0c92c81e65981e1de5"/>
<period start_date="2024-11-15T13:55:19.571+01:00" end_date="2024-11-15T13:55:19.571+01:00">
<measurement log_date="2024-11-15T13:55:19.571+01:00">off</measurement>
</period>
</point_log>
</logs>
<actuator_functionalities>
<relay_functionality id="8ebe8457de324c3d82d7e08ba7ec62f4">
<updated_date>2024-11-15T13:55:19.571+01:00</updated_date>
<lock>false</lock>
<state>off</state>
<relay id="e837f6f0f2014b0c92c81e65981e1de5"/>
</relay_functionality>
</actuator_functionalities>
</appliance>
<module id="c1f98356f09346b28e26a6ebc0f69275">
<vendor_name>_TZ3000_abjodzas</vendor_name>
<vendor_model>TS0011</vendor_model>
<hardware_version/>
<firmware_version/>
<upgrade/>
<created_date>2024-11-11T20:31:46.189+01:00</created_date>
<modified_date>2024-11-11T20:31:49.706+01:00</modified_date>
<deleted_date/>
<services>
<relay id="e837f6f0f2014b0c92c81e65981e1de5" log_type="relay">
<functionalities>
<point_log id="8ce65a98d6ba4ca9a531272927891014"/>
<relay_functionality id="8ebe8457de324c3d82d7e08ba7ec62f4"/>
</functionalities>
</relay>
</services>
<protocols>
<zig_bee_node id="7959e6435fd742f0bcdafefd192c951d">
<mac_address>A4C13862AF9917B1</mac_address>
<type>end_device</type>
<reachable>true</reachable>
<power_source>battery</power_source>
<zig_bee_coordinator id="6e9b4e783b05409c944aaf8e14cc1f30"/>
<neighbors>
<neighbor mac_address="54EF4410004DD396">
<lqi>221</lqi>
<depth>1</depth>
<relationship>parent</relationship>
</neighbor>
</neighbors>
<last_neighbor_table_received>2024-11-15T14:07:23+01:00</last_neighbor_table_received>
<neighbor_table_support>true</neighbor_table_support>
</zig_bee_node>
</protocols>
</module>
<appliance id='67d73d0bd469422db25a618a5fb8eeb0'>
<name>SmartPlug Floor 0</name>
<description>A device that communicates through the ZigBee protocol.</description>
Expand Down
Loading