Skip to content

Commit

Permalink
Merge pull request #651 from plugwise/zigbee-valve
Browse files Browse the repository at this point in the history
Bugfix for Adam: improve recognition of unknown zigbee devices
  • Loading branch information
bouwew authored Nov 16, 2024
2 parents af09f6c + e706c47 commit a41bdb2
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 53 deletions.
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
m_adam_cooling["devices"].pop("67d73d0bd469422db25a618a5fb8eeb0")
m_adam_cooling["devices"].pop("10016900610d4c7481df78c89606ef22")

# 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

0 comments on commit a41bdb2

Please sign in to comment.