diff --git a/setup.py b/setup.py index 94e7ff270d..e0a13d9c58 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import find_packages, setup -VERSION = "0.0.52" +VERSION = "0.0.53" def readme(): @@ -24,6 +24,6 @@ def readme(): keywords="zha quirks homeassistant hass", packages=find_packages(exclude=["tests"]), python_requires=">=3", - install_requires=["zigpy>=0.28.2"], + install_requires=["zigpy>=0.32.0"], tests_require=["pytest"], ) diff --git a/tests/conftest.py b/tests/conftest.py index ade83f320e..a2122dd93e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,8 @@ DEVICE_TYPE, ENDPOINTS, INPUT_CLUSTERS, + MANUFACTURER, + MODEL, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, @@ -74,11 +76,17 @@ def _dev(ieee=None, nwk=zigpy.types.NWK(0x1234)): def zigpy_device_from_quirk(MockAppController, ieee_mock): """Create zigpy device from Quirk's signature.""" - def _dev(quirk, ieee=None, nwk=zigpy.types.NWK(0x1234)): + def _dev(quirk, ieee=None, nwk=zigpy.types.NWK(0x1234), apply_quirk=True): if ieee is None: ieee = ieee_mock models_info = quirk.signature.get( - MODELS_INFO, (("Mock Manufacturer", "Mock Model"),) + MODELS_INFO, + ( + ( + quirk.signature.get(MANUFACTURER, "Mock Manufacturer"), + quirk.signature.get(MODEL, "Mock Model"), + ), + ), ) manufacturer, model = models_info[0] @@ -97,6 +105,10 @@ def _dev(quirk, ieee=None, nwk=zigpy.types.NWK(0x1234)): out_clusters = ep_data.get(OUTPUT_CLUSTERS, []) for cluster_id in out_clusters: ep.add_output_cluster(cluster_id) + + if not apply_quirk: + return raw_device + device = quirk(MockAppController, ieee, nwk, raw_device) MockAppController.devices[ieee] = device diff --git a/tests/test_tuya.py b/tests/test_tuya.py index af5c28ae9b..bec33ed963 100644 --- a/tests/test_tuya.py +++ b/tests/test_tuya.py @@ -5,7 +5,7 @@ import pytest from zigpy.profiles import zha -from zigpy.quirks import CustomDevice +from zigpy.quirks import CustomDevice, get_device import zigpy.types as t from zigpy.zcl import foundation @@ -24,6 +24,8 @@ import zhaquirks.tuya.electric_heating import zhaquirks.tuya.motion import zhaquirks.tuya.siren +import zhaquirks.tuya.ts0042 +import zhaquirks.tuya.ts0043 import zhaquirks.tuya.valve from tests.common import ClusterListener @@ -528,3 +530,29 @@ async def async_success(*args, **kwargs): status = await thermostat_cluster.command(0x0002) assert status == foundation.Status.UNSUP_CLUSTER_COMMAND + + +@pytest.mark.parametrize( + "quirk, manufacturer", + ( + (zhaquirks.tuya.ts0042.TuyaSmartRemote0042, "_TZ3000_owgcnkrh"), + (zhaquirks.tuya.ts0042.TuyaSmartRemote0042, "_TZ3400_keyjhapk"), + (zhaquirks.tuya.ts0042.TuyaSmartRemote0042, "_some_random_manuf"), + (zhaquirks.tuya.ts0042.BenexmartRemote0042, "_TZ3000_adkvzooy"), + (zhaquirks.tuya.ts0042.BenexmartRemote0042, "_TZ3400_keyjhapk"), + (zhaquirks.tuya.ts0042.BenexmartRemote0042, "another random manufacturer"), + (zhaquirks.tuya.ts0043.TuyaSmartRemote0043, "_TZ3000_bi6lpsew"), + (zhaquirks.tuya.ts0043.TuyaSmartRemote0043, "_TZ3000_a7ouggvs"), + (zhaquirks.tuya.ts0043.TuyaSmartRemote0043, "another random manufacturer"), + (zhaquirks.tuya.ts0043.BenexmartRemote0043, "_TZ3000_qzjcsmar"), + (zhaquirks.tuya.ts0043.BenexmartRemote0043, "another random manufacturer"), + ), +) +async def test_tuya_wildcard_manufacturer(zigpy_device_from_quirk, quirk, manufacturer): + """Test thermostatic valve outgoing commands.""" + + zigpy_dev = zigpy_device_from_quirk(quirk, apply_quirk=False) + zigpy_dev.manufacturer = manufacturer + + quirked_dev = get_device(zigpy_dev) + assert isinstance(quirked_dev, quirk) diff --git a/zhaquirks/__init__.py b/zhaquirks/__init__.py index 3c1b0b432e..e5a6b43282 100755 --- a/zhaquirks/__init__.py +++ b/zhaquirks/__init__.py @@ -3,11 +3,12 @@ import importlib import logging import pkgutil -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional, Union import zigpy.device import zigpy.endpoint from zigpy.quirks import CustomCluster, CustomDevice +import zigpy.types as t from zigpy.util import ListenableMixin from zigpy.zcl import foundation from zigpy.zcl.clusters.general import PowerConfiguration @@ -101,14 +102,24 @@ async def write_attributes(self, attributes, manufacturer=None): class EventableCluster(CustomCluster): """Cluster that generates events.""" - def handle_cluster_request(self, tsn, command_id, args): + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): """Send cluster requests as events.""" if ( self.server_commands is not None - and self.server_commands.get(command_id) is not None + and self.server_commands.get(hdr.command_id) is not None ): self.listener_event( - ZHA_SEND_EVENT, self.server_commands.get(command_id)[0], args + ZHA_SEND_EVENT, + self.server_commands.get(hdr.command_id, (hdr.command_id))[0], + args, ) def _update_attribute(self, attrid, value): @@ -235,9 +246,17 @@ class MotionWithReset(_Motion): send_occupancy_event: bool = False - def handle_cluster_request(self, tsn, command_id, args): + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): """Handle the cluster command.""" - if command_id == ZONE_STATE: + if hdr.command_id == ZONE_STATE: if self._timer_handle: self._timer_handle.cancel() self._timer_handle = self._loop.call_later(self.reset_s, self._turn_off) diff --git a/zhaquirks/danfoss/thermostat.py b/zhaquirks/danfoss/thermostat.py index 5edf832948..873de3b51a 100644 --- a/zhaquirks/danfoss/thermostat.py +++ b/zhaquirks/danfoss/thermostat.py @@ -35,6 +35,7 @@ class DanfossThermostatCluster(CustomCluster, Thermostat): 0x4000: ("etrv_open_windows_detection", t.enum8), 0x4003: ("external_open_windows_detected", t.Bool), 0x4014: ("orientation", t.Bool), + 0x4015: ("external_measured_room_sensor", t.int16s), } diff --git a/zhaquirks/ikea/opencloseremote.py b/zhaquirks/ikea/opencloseremote.py index fa0834e2ce..3aac2d642e 100644 --- a/zhaquirks/ikea/opencloseremote.py +++ b/zhaquirks/ikea/opencloseremote.py @@ -1,8 +1,10 @@ """Device handler for IKEA of Sweden TRADFRI remote control.""" -from typing import List +from typing import Any, List, Optional, Union from zigpy.profiles import zha from zigpy.quirks import CustomCluster, CustomDevice +import zigpy.types as t +from zigpy.zcl import foundation from zigpy.zcl.clusters.closures import WindowCovering from zigpy.zcl.clusters.general import ( Alarms, @@ -52,14 +54,20 @@ def __init__(self, *args, **kwargs): self._is_closing = None def handle_cluster_request( - self, tsn: int, command_id: int, args: List[int] + self, + hdr: foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, ) -> None: """Handle cluster specific commands. We just want to keep track of direction, to associate it with the stop command. """ - cmd_name = self.server_commands.get(command_id, [command_id])[0] + cmd_name = self.server_commands.get(hdr.command_id, [hdr.command_id])[0] if cmd_name == COMMAND_OPEN: self._is_closing = False elif cmd_name == COMMAND_CLOSE: diff --git a/zhaquirks/konke/__init__.py b/zhaquirks/konke/__init__.py index faa1f7216f..ff940e01ec 100644 --- a/zhaquirks/konke/__init__.py +++ b/zhaquirks/konke/__init__.py @@ -1,4 +1,5 @@ """Konke sensors.""" +from typing import Any, List, Optional, Union import zigpy.types as t from zigpy.zcl.clusters.general import OnOff @@ -38,7 +39,15 @@ class KonkeOnOffCluster(CustomCluster, OnOff): ep_attribute = "custom_on_off" manufacturer_attributes = {0x0000: (PRESS_TYPE, t.uint8_t)} - def handle_cluster_general_request(self, header, args): + def handle_cluster_general_request( + self, + header: zigpy.zcl.foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): """Handle the cluster command.""" self.info( "Konke general request - handle_cluster_general_request: header: %s - args: [%s]", diff --git a/zhaquirks/philips/__init__.py b/zhaquirks/philips/__init__.py index e06c950da1..4554100827 100644 --- a/zhaquirks/philips/__init__.py +++ b/zhaquirks/philips/__init__.py @@ -2,9 +2,11 @@ import asyncio import logging import time +from typing import Any, List, Optional, Union from zigpy.quirks import CustomCluster import zigpy.types as t +from zigpy.zcl import foundation from zigpy.zcl.clusters.general import Basic, LevelControl, OnOff from zigpy.zcl.clusters.lighting import Color from zigpy.zcl.clusters.measurement import OccupancySensing @@ -178,12 +180,20 @@ class PhilipsRemoteCluster(CustomCluster): button_press_queue = ButtonPressQueue() - def handle_cluster_request(self, tsn, command_id, args): + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): """Handle the cluster command.""" _LOGGER.debug( "PhilipsRemoteCluster - handle_cluster_request tsn: [%s] command id: %s - args: [%s]", - tsn, - command_id, + hdr.tsn, + hdr.command_id, args, ) @@ -193,7 +203,7 @@ def handle_cluster_request(self, tsn, command_id, args): event_args = { BUTTON: button, PRESS_TYPE: press_type, - COMMAND_ID: command_id, + COMMAND_ID: hdr.command_id, ARGS: args, } diff --git a/zhaquirks/philips/zhadimmablelight.py b/zhaquirks/philips/zhadimmablelight.py index 778d9a861c..6cac764b3b 100644 --- a/zhaquirks/philips/zhadimmablelight.py +++ b/zhaquirks/philips/zhadimmablelight.py @@ -33,7 +33,19 @@ class ZHADimmableLight(CustomDevice): """Philips ZigBee HomeAutomation dimmable bulb device.""" signature = { - MODELS_INFO: [(PHILIPS, "LWV001"), (SIGNIFY, "LWV001")], + MODELS_INFO: [ + (PHILIPS, "LWA004"), + (PHILIPS, "LWA005"), + (PHILIPS, "LWA007"), + (PHILIPS, "LWO001"), + (PHILIPS, "LWO003"), + (PHILIPS, "LWV001"), + (SIGNIFY, "LWA004"), + (SIGNIFY, "LWA005"), + (SIGNIFY, "LWO001"), + (SIGNIFY, "LWO003"), + (SIGNIFY, "LWV001"), + ], ENDPOINTS: { 11: { # commands.""" - def handle_cluster_request(self, tsn: int, command_id: int, args: Tuple) -> None: + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: Tuple, + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ) -> None: """Handle cluster request.""" - if command_id not in (0x0001, 0x0002): - return super().handle_cluster_request(tsn, command_id, args) + if hdr.command_id not in (0x0001, 0x0002): + return super().handle_cluster_request( + hdr, args, dst_addressing=dst_addressing + ) tuya_cmd = args[0].command_id tuya_data = args[0].data @@ -87,7 +97,7 @@ def handle_cluster_request(self, tsn: int, command_id: int, args: Tuple) -> None self.cluster_id, repr(tuya_data[1:]), tuya_cmd, - command_id, + hdr.command_id, ) if tuya_cmd not in self.attributes: @@ -177,12 +187,18 @@ class TuyaManufacturerClusterOnOff(TuyaManufCluster): """Manufacturer Specific Cluster of On/Off device.""" def handle_cluster_request( - self, tsn: int, command_id: int, args: Tuple[TuyaManufCluster.Command] + self, + hdr: foundation.ZCLHeader, + args: Tuple[TuyaManufCluster.Command], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, ) -> None: """Handle cluster request.""" tuya_payload = args[0] - if command_id in (0x0002, 0x0001): + if hdr.command_id in (0x0002, 0x0001): self.endpoint.device.switch_bus.listener_event( SWITCH_EVENT, tuya_payload.command_id - TUYA_CMD_BASE, @@ -374,15 +390,31 @@ def __init__(self, *args, **kwargs): 0xFD: ("press_type", (t.uint8_t,), False), } - def handle_cluster_request(self, tsn, command_id, args): + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): """Handle press_types command.""" - if tsn == self.last_tsn: + # normally if default response sent, TS004x wouldn't send such repeated zclframe (with same sequence number), + # but for stability reasons (e. g. the case the response doesn't arrive the device), we can simply ignore it + if hdr.tsn == self.last_tsn: _LOGGER.debug("TS004X: ignoring duplicate frame") return + # save last sequence number + self.last_tsn = hdr.tsn + + # send default response (as soon as possible), so avoid repeated zclframe from device + if not hdr.frame_control.disable_default_response: + self.debug("TS004X: send default response") + self.send_default_rsp(hdr, status=foundation.Status.SUCCESS) - self.last_tsn = tsn - super().handle_cluster_request(tsn, command_id, args) - if command_id == 0xFD: + # handle command + if hdr.command_id == 0xFD: press_type = args[0] self.listener_event( ZHA_SEND_EVENT, self.press_type.get(press_type, "unknown"), [] diff --git a/zhaquirks/tuya/motion.py b/zhaquirks/tuya/motion.py index 2b5883e650..72eefc625e 100755 --- a/zhaquirks/tuya/motion.py +++ b/zhaquirks/tuya/motion.py @@ -1,9 +1,11 @@ """BlitzWolf IS-3/Tuya motion rechargeable occupancy sensor.""" -from typing import Tuple +from typing import Optional, Tuple, Union from zigpy.profiles import zha from zigpy.quirks import CustomDevice +import zigpy.types as t +from zigpy.zcl import foundation from zigpy.zcl.clusters.general import Basic, Identify, Ota from zigpy.zcl.clusters.security import IasZone @@ -33,11 +35,17 @@ class TuyaManufacturerClusterMotion(TuyaManufCluster): """Manufacturer Specific Cluster of the Motion device.""" def handle_cluster_request( - self, tsn: int, command_id: int, args: Tuple[TuyaManufCluster.Command] + self, + hdr: foundation.ZCLHeader, + args: Tuple[TuyaManufCluster.Command], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, ) -> None: """Handle cluster request.""" tuya_cmd = args[0] - if command_id == 0x0001 and tuya_cmd.command_id == 1027: + if hdr.command_id == 0x0001 and tuya_cmd.command_id == 1027: self.endpoint.device.motion_bus.listener_event(MOTION_EVENT) diff --git a/zhaquirks/tuya/plug.py b/zhaquirks/tuya/plug.py new file mode 100644 index 0000000000..9cc1baec7f --- /dev/null +++ b/zhaquirks/tuya/plug.py @@ -0,0 +1,75 @@ +"""Tuya plug.""" +from zigpy.profiles import zha +from zigpy.quirks import CustomCluster, CustomDevice +import zigpy.types as t +from zigpy.zcl.clusters.general import Basic, Groups, OnOff, Ota, Scenes, Time +from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement +from zigpy.zcl.clusters.smartenergy import Metering + +from ..const import ( + DEVICE_TYPE, + ENDPOINTS, + INPUT_CLUSTERS, + MODELS_INFO, + OUTPUT_CLUSTERS, + PROFILE_ID, +) + + +class PowerOnState(t.enum8): + """Tuya power on state enum.""" + + Off = 0x00 + On = 0x01 + LastState = 0x02 + + +class OnOffRestorePowerCluster(CustomCluster, OnOff): + """Tuya on off cluster with restore state.""" + + attributes = OnOff.attributes.copy() + attributes.update({0x8002: ("power_on_state", PowerOnState)}) + + +class Plug(CustomDevice): + """Tuya plug with restore power state support.""" + + signature = { + MODELS_INFO: [("_TZ3000_g5xawfcq", "TS0121")], + ENDPOINTS: { + # + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.SMART_PLUG, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + Metering.cluster_id, + ElectricalMeasurement.cluster_id, + ], + OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], + }, + }, + } + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.SMART_PLUG, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOffRestorePowerCluster, + Metering.cluster_id, + ElectricalMeasurement.cluster_id, + ], + OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], + }, + }, + } diff --git a/zhaquirks/tuya/ts0042.py b/zhaquirks/tuya/ts0042.py index f9fe58f602..1e07558769 100644 --- a/zhaquirks/tuya/ts0042.py +++ b/zhaquirks/tuya/ts0042.py @@ -15,7 +15,7 @@ ENDPOINTS, INPUT_CLUSTERS, LONG_PRESS, - MODELS_INFO, + MODEL, OUTPUT_CLUSTERS, PROFILE_ID, SHORT_PRESS, @@ -28,7 +28,7 @@ class TuyaSmartRemote0042(CustomDevice): signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=0, device_version=1, input_clusters=[0, 10, 1, 6], output_clusters=[25])) # SizePrefixedSimpleDescriptor(endpoint=2, profile=260, device_type=0, device_version=1, input_clusters=[1, 6], output_clusters=[]) - MODELS_INFO: [("_TZ3000_owgcnkrh", "TS0042")], + MODEL: "TS0042", ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, @@ -93,10 +93,7 @@ class BenexmartRemote0042(CustomDevice): signature = { # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=0, device_version=1, input_clusters=[0, 1, 6], output_clusters=[10, 25])) # SizePrefixedSimpleDescriptor(endpoint=2, profile=260, device_type=0, device_version=1, input_clusters=[1, 6], output_clusters=[]) - MODELS_INFO: [ - ("_TZ3000_adkvzooy", "TS0042"), - ("_TZ3400_keyjhapk", "TS0042"), - ], + MODEL: "TS0042", ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, diff --git a/zhaquirks/tuya/ts0043.py b/zhaquirks/tuya/ts0043.py index 670d473efd..9051576df0 100644 --- a/zhaquirks/tuya/ts0043.py +++ b/zhaquirks/tuya/ts0043.py @@ -16,7 +16,7 @@ ENDPOINTS, INPUT_CLUSTERS, LONG_PRESS, - MODELS_INFO, + MODEL, OUTPUT_CLUSTERS, PROFILE_ID, SHORT_PRESS, @@ -30,7 +30,7 @@ class TuyaSmartRemote0043(CustomDevice): # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=0, device_version=1, input_clusters=[0, 10, 1, 6], output_clusters=[25])) # SizePrefixedSimpleDescriptor(endpoint=2, profile=260, device_type=0, device_version=1, input_clusters=[1, 6], output_clusters=[]) # SizePrefixedSimpleDescriptor(endpoint=3, profile=260, device_type=0, device_version=1, input_clusters=[1, 6], output_clusters=[]) - MODELS_INFO: [("_TZ3000_bi6lpsew", "TS0043"), ("_TZ3000_a7ouggvs", "TS0043")], + MODEL: "TS0043", ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, @@ -117,7 +117,7 @@ class BenexmartRemote0043(CustomDevice): # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=0, device_version=1, input_clusters=[0, 1, 6], output_clusters=[10, 25])) # SizePrefixedSimpleDescriptor(endpoint=2, profile=260, device_type=0, device_version=1, input_clusters=[1, 6], output_clusters=[]) # SizePrefixedSimpleDescriptor(endpoint=3, profile=260, device_type=0, device_version=1, input_clusters=[1, 6], output_clusters=[]) - MODELS_INFO: [("_TZ3000_qzjcsmar", "TS0043")], + MODEL: "TS0043", ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, diff --git a/zhaquirks/tuya/valve.py b/zhaquirks/tuya/valve.py index 92a92b19e7..5255b8e55c 100644 --- a/zhaquirks/tuya/valve.py +++ b/zhaquirks/tuya/valve.py @@ -179,6 +179,7 @@ class MoesHY368(TuyaThermostat): ("_TZE200_ckud7u2l", "TS0601"), ("_TZE200_kfvq6avy", "TS0601"), ("_TZE200_c88teujp", "TS0601"), + ("_TZE200_zivfvd7h", "TS0601"), ], ENDPOINTS: { 1: { diff --git a/zhaquirks/waxman/leaksmart.py b/zhaquirks/waxman/leaksmart.py index 7442c221ae..36a1edd7ee 100644 --- a/zhaquirks/waxman/leaksmart.py +++ b/zhaquirks/waxman/leaksmart.py @@ -1,8 +1,11 @@ """Device handler for WAXMAN leakSMART.""" # pylint: disable=W0102 +from typing import Any, List, Optional, Union + from zigpy.profiles import zha from zigpy.quirks import CustomCluster, CustomDevice import zigpy.types as t +from zigpy.zcl import foundation from zigpy.zcl.clusters.general import ( Basic, Identify, @@ -68,9 +71,17 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.endpoint.device.app_cluster = self - def handle_cluster_request(self, tsn, command_id, args): + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): """Handle a cluster command received on this cluster.""" - if command_id == WAXMAN_CMDID: + if hdr.command_id == WAXMAN_CMDID: state = bool(args[1] & 0x1000) self.endpoint.device.ias_bus.listener_event("update_state", state) diff --git a/zhaquirks/xbee/__init__.py b/zhaquirks/xbee/__init__.py index da3331b1af..df04c2f169 100644 --- a/zhaquirks/xbee/__init__.py +++ b/zhaquirks/xbee/__init__.py @@ -17,6 +17,7 @@ """ import logging +from typing import Any, List, Optional, Union from zigpy.quirks import CustomDevice import zigpy.types as t @@ -235,12 +236,20 @@ def deserialize(cls, data): data[sample_index:], ) - def handle_cluster_request(self, tsn, command_id, args): + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): """Handle the cluster request. Update the digital pin states """ - if command_id == ON_OFF_CMD: + if hdr.command_id == ON_OFF_CMD: values = args[0] if "digital_pins" in values and "digital_samples" in values: # Update digital inputs @@ -267,7 +276,7 @@ def handle_cluster_request(self, tsn, command_id, args): / (10.23 if pin != 7 else 1000), # supply voltage is in mV ) else: - super().handle_cluster_request(tsn, command_id, args) + super().handle_cluster_request(hdr, args) attributes = {0x0055: ("present_value", t.Bool)} client_commands = {0x0000: ("io_sample", (IOSample,), False)} @@ -316,14 +325,22 @@ def command( expect_reply=False, ) - def handle_cluster_request(self, tsn, command_id, args): + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: List[Any], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): """Handle incoming data.""" - if command_id == DATA_IN_CMD: + if hdr.command_id == DATA_IN_CMD: self._endpoint.out_clusters[ LevelControl.cluster_id - ].handle_cluster_request(tsn, command_id, args[0]) + ].handle_cluster_request(hdr, args[0]) else: - super().handle_cluster_request(tsn, command_id, args) + super().handle_cluster_request(hdr, args) attributes = {} client_commands = {0x0000: ("send_data", (BinaryString,), None)} diff --git a/zhaquirks/yale/realliving.py b/zhaquirks/yale/realliving.py index 47c0cc8ca7..3e653c5036 100644 --- a/zhaquirks/yale/realliving.py +++ b/zhaquirks/yale/realliving.py @@ -15,8 +15,8 @@ ) -class YaleRealLiving(CustomDevice): - """Custom device representing Yale Real Living devices.""" +class YRD210PBDB220TSLL(CustomDevice): + """Yale YRD210 PB BP and Yale YRL220 TS LL Locks.""" signature = { # + MODELS_INFO: [("Yale", "YRD220/240 TSDB")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.DOOR_LOCK, + INPUT_CLUSTERS: [ + Basic.cluster_id, + DoublingPowerConfigurationCluster.cluster_id, + Alarms.cluster_id, + Time.cluster_id, + PollControl.cluster_id, + DoorLock.cluster_id, + ], + OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], + } + }, + } + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.DOOR_LOCK, + INPUT_CLUSTERS: [ + Basic.cluster_id, + DoublingPowerConfigurationCluster, + Alarms.cluster_id, + Time.cluster_id, + PollControl.cluster_id, + DoorLock.cluster_id, + ], + OUTPUT_CLUSTERS: [DoorLock.cluster_id, Ota.cluster_id], + } + } + }