From 6b8cd2cd03804842765b37519622849d7c5ddb80 Mon Sep 17 00:00:00 2001 From: Jose Antonio Date: Thu, 26 Jan 2023 17:05:12 +0100 Subject: [PATCH 01/28] add two manufacturers to TS0501bs (#2136) --- zhaquirks/tuya/ts0501bs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zhaquirks/tuya/ts0501bs.py b/zhaquirks/tuya/ts0501bs.py index 8fdaaf1613..48b946ea6d 100644 --- a/zhaquirks/tuya/ts0501bs.py +++ b/zhaquirks/tuya/ts0501bs.py @@ -30,7 +30,11 @@ class DimmableLedController(CustomDevice): """Tuya dimmable led controller single channel.""" signature = { - MODELS_INFO: [("_TZ3210_9q49basr", "TS0501B")], + MODELS_INFO: [ + ("_TZ3210_9q49basr", "TS0501B"), + ("_TZ3210_4zinq6io", "TS0501B"), + ("_TZ3210_e5t9bfdv", "TS0501B"), + ], ENDPOINTS: { # Date: Thu, 26 Jan 2023 22:50:42 +0100 Subject: [PATCH 02/28] Add Z3 1st gen IKEA motion sensor signature (E1525) (#2137) * E1525 Zigbee 3 update ### Now IKEA is all Zigbee 3. As expected it have loosing group binding and reporting wrong battery %. Delete the device in ZHA and waiting one minute and adding it new and reconfigure it then its OK pared and its start reporting OK and also is doing check ins every 55 minute. * Fix class name and dock strings. * Update motionzha.py --- zhaquirks/ikea/motionzha.py | 68 ++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/zhaquirks/ikea/motionzha.py b/zhaquirks/ikea/motionzha.py index 79954815cb..f07baadf1b 100644 --- a/zhaquirks/ikea/motionzha.py +++ b/zhaquirks/ikea/motionzha.py @@ -26,13 +26,14 @@ from zhaquirks.ikea import ( IKEA, IKEA_CLUSTER_ID, + WWAH_CLUSTER_ID, LightLinkCluster, PowerConfiguration2CRCluster, ) -class IkeaTradfriMotion(CustomDevice): - """Custom device representing IKEA of Sweden TRADFRI remote control.""" +class IkeaTradfriMotionE1745_Var01(CustomDevice): + """Custom device representing IKEA of Sweden TRADFRI motion sensor E1745 second gen variation 1.""" signature = { # + MODELS_INFO: [(IKEA, "TRADFRI motion sensor")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_SENSOR, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + PollControl.cluster_id, + LightLink.cluster_id, + IKEA_CLUSTER_ID, + WWAH_CLUSTER_ID, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + OnOff.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + }, + } + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_SENSOR, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration2CRCluster, + Identify.cluster_id, + PollControl.cluster_id, + LightLinkCluster, + IKEA_CLUSTER_ID, + WWAH_CLUSTER_ID, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + OnOff.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + } + } From 8a82aab6b95d402b45a51d89d795565326be4122 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Sat, 28 Jan 2023 18:55:46 +0100 Subject: [PATCH 03/28] Bump isort to 5.12.0 in pre-commit config (#2147) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4fc3ef59a7..6dd5700c83 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: - pydocstyle==6.1.1 - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort From 1e726667a158129eea2c4a3a3808b0be35fa15fa Mon Sep 17 00:00:00 2001 From: MattWestb <49618193+MattWestb@users.noreply.github.com> Date: Sat, 28 Jan 2023 18:56:26 +0100 Subject: [PATCH 04/28] Deleting _TZE200_2ekuz3dz (#2138) This ID is wrong its one Thermostat and have one working quirk. Then the thermostat is loaded before the TRV is also matching the thermostat and is not the TRV so its not braking any device onlyy fixing one old error. --- zhaquirks/tuya/ts0601_trv_sas.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zhaquirks/tuya/ts0601_trv_sas.py b/zhaquirks/tuya/ts0601_trv_sas.py index bd444a92d8..e5c204fc2f 100644 --- a/zhaquirks/tuya/ts0601_trv_sas.py +++ b/zhaquirks/tuya/ts0601_trv_sas.py @@ -277,7 +277,6 @@ def __init__(self, *args, **kwargs): ("_TZE200_yw7cahqs", "TS0601"), ("_TZE200_9gvruqf5", "TS0601"), ("_TZE200_zuhszj9s", "TS0601"), - ("_TZE200_2ekuz3dz", "TS0601"), ("_TZE200_zr9c0day", "TS0601"), ("_TZE200_0dvm9mva", "TS0601"), ("_TZE200_h4cgnbzg", "TS0601"), From 9ec37e07cbfa357f8abb70817197e5bc79544218 Mon Sep 17 00:00:00 2001 From: MattWestb <49618193+MattWestb@users.noreply.github.com> Date: Sat, 28 Jan 2023 20:18:53 +0100 Subject: [PATCH 05/28] Add Zonnsmart TRV IDs and comment with names (#2146) --- zhaquirks/tuya/ts0601_trv.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/zhaquirks/tuya/ts0601_trv.py b/zhaquirks/tuya/ts0601_trv.py index 6d4a959277..12b7278a3c 100644 --- a/zhaquirks/tuya/ts0601_trv.py +++ b/zhaquirks/tuya/ts0601_trv.py @@ -1677,14 +1677,16 @@ def __init__(self, *args, **kwargs): # endpoint=1 profile=260 device_type=81 device_version=0 input_clusters=[0, 4, 5, 61184] # output_clusters=[10, 25]> MODELS_INFO: [ - ("_TZE200_7yoranx2", "TS0601"), # MOES TRV TV01 ZTRV-ZX-TV01-MS - ("_TZE200_e9ba97vf", "TS0601"), - ("_TZE200_hue3yfsn", "TS0601"), # TV02-ZG - ("_TZE200_husqqvux", "TS0601"), - ("_TZE200_kly8gjlz", "TS0601"), - ("_TZE200_lnbfnyxd", "TS0601"), - ("_TZE200_mudxchsu", "TS0601"), - ("_TZE200_kds0pmmv", "TS0601"), # MOES TRV TV02 + ("_TZE200_7yoranx2", "TS0601"), # MOES TV01 ZTRV-ZX-TV01-MS + ("_TZE200_e9ba97vf", "TS0601"), # Zonnsmart TV01-ZG + ("_TZE200_hue3yfsn", "TS0601"), # Zonnsmart TV02-ZG + ("_TZE200_husqqvux", "TS0601"), # Tesla Smart TSL-TRV-TV01ZG + ("_TZE200_kly8gjlz", "TS0601"), # EARU TV05-ZG + ("_TZE200_lnbfnyxd", "TS0601"), # Tesla Smart TSL-TRV-TV01ZG + ("_TZE200_mudxchsu", "TS0601"), # Foluu TV05 + ("_TZE200_kds0pmmv", "TS0601"), # MOES TV02 + ("_TZE200_sur6q7ko", "TS0601"), # LSC Smart Connect 3012732 + ("_TZE200_lllliz3p", "TS0601"), # tuya TV02-Zigbee2 ], ENDPOINTS: { 1: { From 7e81dcb29faedd3e38a23f8ff700bc33dfb5573a Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Tue, 31 Jan 2023 16:41:48 +0100 Subject: [PATCH 06/28] Refactor IKEA five button remote quirks (#2155) * Merge fivebtnremote.py and fivebtnremotezha.py * Rename quirk classes * Share device automation triggers whilst keeping originals * Share device automation triggers, remove `"param1"=537` for IkeaTradfriRemote2 * Update signature comment for IkeaTradfriRemote3 * Update doc comments --- zhaquirks/ikea/fivebtnremote.py | 196 ++++++++++++++- zhaquirks/ikea/fivebtnremotezha.py | 385 ----------------------------- 2 files changed, 192 insertions(+), 389 deletions(-) delete mode 100644 zhaquirks/ikea/fivebtnremotezha.py diff --git a/zhaquirks/ikea/fivebtnremote.py b/zhaquirks/ikea/fivebtnremote.py index b96860f1bc..b48958ff8e 100644 --- a/zhaquirks/ikea/fivebtnremote.py +++ b/zhaquirks/ikea/fivebtnremote.py @@ -1,5 +1,5 @@ """Device handler for IKEA of Sweden TRADFRI remote control.""" -from zigpy.profiles import zll +from zigpy.profiles import zha, zll from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import ( Alarms, @@ -9,6 +9,7 @@ LevelControl, OnOff, Ota, + PollControl, PowerConfiguration, Scenes, ) @@ -43,11 +44,17 @@ SHORT_PRESS, TURN_ON, ) -from zhaquirks.ikea import IKEA, LightLinkCluster, ScenesCluster +from zhaquirks.ikea import ( + IKEA, + IKEA_CLUSTER_ID, + LightLinkCluster, + PowerConfiguration1CRCluster, + ScenesCluster, +) -class IkeaTradfriRemote(CustomDevice): - """Custom device representing IKEA of Sweden TRADFRI remote control.""" +class IkeaTradfriRemote1(CustomDevice): + """Custom device representing ZLL version of IKEA five button remote.""" signature = { # + MODELS_INFO: [(IKEA, "TRADFRI remote control")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + PollControl.cluster_id, + LightLink.cluster_id, + IKEA_CLUSTER_ID, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + OnOff.cluster_id, + LevelControl.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + }, + } + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration1CRCluster, + Identify.cluster_id, + PollControl.cluster_id, + LightLinkCluster, + IKEA_CLUSTER_ID, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + ScenesCluster, + OnOff.cluster_id, + LevelControl.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + } + } + + +class IkeaTradfriRemote3(IkeaTradfriRemote1): + """Custom device representing variation of IKEA five button remote.""" + + signature = { + # + MODELS_INFO: [(IKEA, "TRADFRI remote control")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.COLOR_SCENE_CONTROLLER, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + Alarms.cluster_id, + Diagnostic.cluster_id, + LightLink.cluster_id, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + ScenesCluster.cluster_id, + OnOff.cluster_id, + LevelControl.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + }, + } + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.COLOR_SCENE_CONTROLLER, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration1CRCluster, + Identify.cluster_id, + Alarms.cluster_id, + LightLinkCluster, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + ScenesCluster, + OnOff.cluster_id, + LevelControl.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + } + } + + +class IkeaTradfriRemote4(IkeaTradfriRemote1): + """Custom device representing variation of IKEA five button remote.""" + + signature = { + # + MODELS_INFO: [(IKEA, "TRADFRI remote control")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + PollControl.cluster_id, + LightLink.cluster_id, + IKEA_CLUSTER_ID, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + ScenesCluster.cluster_id, + OnOff.cluster_id, + LevelControl.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + }, + } + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration1CRCluster, + Identify.cluster_id, + PollControl.cluster_id, + LightLinkCluster, + IKEA_CLUSTER_ID, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + ScenesCluster, + OnOff.cluster_id, + LevelControl.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + } + } diff --git a/zhaquirks/ikea/fivebtnremotezha.py b/zhaquirks/ikea/fivebtnremotezha.py deleted file mode 100644 index cffe216c3d..0000000000 --- a/zhaquirks/ikea/fivebtnremotezha.py +++ /dev/null @@ -1,385 +0,0 @@ -"""Device handler for IKEA of Sweden TRADFRI remote control.""" -from zigpy.profiles import zha -from zigpy.quirks import CustomDevice -from zigpy.zcl.clusters.general import ( - Alarms, - Basic, - Groups, - Identify, - LevelControl, - OnOff, - Ota, - PollControl, - PowerConfiguration, -) -from zigpy.zcl.clusters.homeautomation import Diagnostic -from zigpy.zcl.clusters.lightlink import LightLink - -from zhaquirks.const import ( - CLUSTER_ID, - COMMAND, - COMMAND_HOLD, - COMMAND_MOVE, - COMMAND_MOVE_ON_OFF, - COMMAND_PRESS, - COMMAND_RELEASE, - COMMAND_STEP, - COMMAND_STEP_ON_OFF, - COMMAND_TOGGLE, - DEVICE_TYPE, - DIM_DOWN, - DIM_UP, - ENDPOINT_ID, - ENDPOINTS, - INPUT_CLUSTERS, - LEFT, - LONG_PRESS, - MODELS_INFO, - OUTPUT_CLUSTERS, - PARAMS, - PROFILE_ID, - RIGHT, - SHORT_PRESS, - TURN_ON, -) -from zhaquirks.ikea import ( - IKEA, - IKEA_CLUSTER_ID, - LightLinkCluster, - PowerConfiguration1CRCluster, - ScenesCluster, -) - - -class IkeaTradfriRemote1(CustomDevice): - """Custom device representing IKEA of Sweden TRADFRI remote control.""" - - signature = { - # - MODELS_INFO: [(IKEA, "TRADFRI remote control")], - ENDPOINTS: { - 1: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - PollControl.cluster_id, - LightLink.cluster_id, - IKEA_CLUSTER_ID, - ], - OUTPUT_CLUSTERS: [ - Identify.cluster_id, - Groups.cluster_id, - OnOff.cluster_id, - LevelControl.cluster_id, - Ota.cluster_id, - LightLink.cluster_id, - ], - } - }, - } - - replacement = { - ENDPOINTS: { - 1: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration1CRCluster, - Identify.cluster_id, - PollControl.cluster_id, - LightLinkCluster, - IKEA_CLUSTER_ID, - ], - OUTPUT_CLUSTERS: [ - Identify.cluster_id, - Groups.cluster_id, - ScenesCluster, - OnOff.cluster_id, - LevelControl.cluster_id, - Ota.cluster_id, - LightLink.cluster_id, - ], - } - } - } - - device_automation_triggers = { - (SHORT_PRESS, TURN_ON): { - COMMAND: COMMAND_TOGGLE, - CLUSTER_ID: 6, - ENDPOINT_ID: 1, - }, - (LONG_PRESS, TURN_ON): { - COMMAND: COMMAND_RELEASE, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: {"param1": 537}, - }, - (SHORT_PRESS, DIM_UP): { - COMMAND: COMMAND_STEP_ON_OFF, - CLUSTER_ID: 8, - ENDPOINT_ID: 1, - PARAMS: {"step_mode": 0}, - }, - (LONG_PRESS, DIM_UP): { - COMMAND: COMMAND_MOVE_ON_OFF, - CLUSTER_ID: 8, - ENDPOINT_ID: 1, - PARAMS: {"move_mode": 0}, - }, - (SHORT_PRESS, DIM_DOWN): { - COMMAND: COMMAND_STEP, - CLUSTER_ID: 8, - ENDPOINT_ID: 1, - PARAMS: {"step_mode": 1}, - }, - (LONG_PRESS, DIM_DOWN): { - COMMAND: COMMAND_MOVE, - CLUSTER_ID: 8, - ENDPOINT_ID: 1, - PARAMS: {"move_mode": 1}, - }, - (SHORT_PRESS, LEFT): { - COMMAND: COMMAND_PRESS, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: { - "param1": 257, - "param2": 13, - "param3": 0, - }, - }, - (LONG_PRESS, LEFT): { - COMMAND: COMMAND_HOLD, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: { - "param1": 3329, - "param2": 0, - }, - }, - (SHORT_PRESS, RIGHT): { - COMMAND: COMMAND_PRESS, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: { - "param1": 256, - "param2": 13, - "param3": 0, - }, - }, - (LONG_PRESS, RIGHT): { - COMMAND: COMMAND_HOLD, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: { - "param1": 3328, - "param2": 0, - }, - }, - } - - -class IkeaTradfriRemote2(IkeaTradfriRemote1): - """Custom device representing IKEA of Sweden TRADFRI 5 button remote control.""" - - signature = { - # - MODELS_INFO: [(IKEA, "TRADFRI remote control")], - ENDPOINTS: { - 1: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration.cluster_id, - Identify.cluster_id, - PollControl.cluster_id, - LightLink.cluster_id, - IKEA_CLUSTER_ID, - ], - OUTPUT_CLUSTERS: [ - Identify.cluster_id, - Groups.cluster_id, - ScenesCluster.cluster_id, - OnOff.cluster_id, - LevelControl.cluster_id, - Ota.cluster_id, - LightLink.cluster_id, - ], - } - }, - } - - replacement = { - ENDPOINTS: { - 1: { - PROFILE_ID: zha.PROFILE_ID, - DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, - INPUT_CLUSTERS: [ - Basic.cluster_id, - PowerConfiguration1CRCluster, - Identify.cluster_id, - PollControl.cluster_id, - LightLinkCluster, - IKEA_CLUSTER_ID, - ], - OUTPUT_CLUSTERS: [ - Identify.cluster_id, - Groups.cluster_id, - ScenesCluster, - OnOff.cluster_id, - LevelControl.cluster_id, - Ota.cluster_id, - LightLink.cluster_id, - ], - } - } - } - - device_automation_triggers = { - (SHORT_PRESS, TURN_ON): { - COMMAND: COMMAND_TOGGLE, - CLUSTER_ID: 6, - ENDPOINT_ID: 1, - }, - (LONG_PRESS, TURN_ON): { - COMMAND: COMMAND_RELEASE, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: {"param1": 0}, - }, - (SHORT_PRESS, DIM_UP): { - COMMAND: COMMAND_STEP_ON_OFF, - CLUSTER_ID: 8, - ENDPOINT_ID: 1, - PARAMS: {"step_mode": 0}, - }, - (LONG_PRESS, DIM_UP): { - COMMAND: COMMAND_MOVE_ON_OFF, - CLUSTER_ID: 8, - ENDPOINT_ID: 1, - PARAMS: {"move_mode": 0}, - }, - (SHORT_PRESS, DIM_DOWN): { - COMMAND: COMMAND_STEP, - CLUSTER_ID: 8, - ENDPOINT_ID: 1, - PARAMS: {"step_mode": 1}, - }, - (LONG_PRESS, DIM_DOWN): { - COMMAND: COMMAND_MOVE, - CLUSTER_ID: 8, - ENDPOINT_ID: 1, - PARAMS: {"move_mode": 1}, - }, - (SHORT_PRESS, LEFT): { - COMMAND: COMMAND_PRESS, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: { - "param1": 257, - "param2": 13, - "param3": 0, - }, - }, - (LONG_PRESS, LEFT): { - COMMAND: COMMAND_HOLD, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: { - "param1": 3329, - "param2": 0, - }, - }, - (SHORT_PRESS, RIGHT): { - COMMAND: COMMAND_PRESS, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: { - "param1": 256, - "param2": 13, - "param3": 0, - }, - }, - (LONG_PRESS, RIGHT): { - COMMAND: COMMAND_HOLD, - CLUSTER_ID: 5, - ENDPOINT_ID: 1, - PARAMS: { - "param1": 3328, - "param2": 0, - }, - }, - } From 5b81cd0b723fe2171a7646340a1a6a142a7a462e Mon Sep 17 00:00:00 2001 From: stickpin <630000+stickpin@users.noreply.github.com> Date: Tue, 31 Jan 2023 17:13:29 +0100 Subject: [PATCH 07/28] Add v24.4.5 fw support for IKEA 5 button remote (#2156) --- zhaquirks/ikea/fivebtnremote.py | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/zhaquirks/ikea/fivebtnremote.py b/zhaquirks/ikea/fivebtnremote.py index b48958ff8e..5e909ef2e9 100644 --- a/zhaquirks/ikea/fivebtnremote.py +++ b/zhaquirks/ikea/fivebtnremote.py @@ -47,6 +47,7 @@ from zhaquirks.ikea import ( IKEA, IKEA_CLUSTER_ID, + WWAH_CLUSTER_ID, LightLinkCluster, PowerConfiguration1CRCluster, ScenesCluster, @@ -369,3 +370,66 @@ class IkeaTradfriRemote4(IkeaTradfriRemote1): } } } + + +class IkeaTradfriRemote5(IkeaTradfriRemote1): + """Custom device representing variation of IKEA five button remote.""" + + signature = { + # + MODELS_INFO: [(IKEA, "TRADFRI remote control")], + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + PollControl.cluster_id, + LightLink.cluster_id, + WWAH_CLUSTER_ID, + IKEA_CLUSTER_ID, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + ScenesCluster.cluster_id, + OnOff.cluster_id, + LevelControl.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + }, + } + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.NON_COLOR_CONTROLLER, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + PollControl.cluster_id, + LightLinkCluster, + WWAH_CLUSTER_ID, + IKEA_CLUSTER_ID, + ], + OUTPUT_CLUSTERS: [ + Identify.cluster_id, + Groups.cluster_id, + ScenesCluster, + OnOff.cluster_id, + LevelControl.cluster_id, + Ota.cluster_id, + LightLink.cluster_id, + ], + } + } + } From 11d693e5991a7f4514ba3e56f6a4dd0dc167e9fb Mon Sep 17 00:00:00 2001 From: stickpin <630000+stickpin@users.noreply.github.com> Date: Fri, 3 Feb 2023 01:00:52 +0100 Subject: [PATCH 08/28] HEIMAN SmokeSensor-N-3.0 support, update imports (#2157) * Add quirk for HEIMAN SmokeSensor-N-3.0 * fix missing Diagnostic import * fix isort error * remove node descriptor replacement node descriptor replacement is not required for this device --- zhaquirks/heiman/smoke.py | 81 ++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/zhaquirks/heiman/smoke.py b/zhaquirks/heiman/smoke.py index 3c540d8700..c391926e12 100644 --- a/zhaquirks/heiman/smoke.py +++ b/zhaquirks/heiman/smoke.py @@ -1,8 +1,9 @@ """Smoke Sensor.""" -import zigpy.profiles.zha +from zigpy.profiles import zha from zigpy.quirks import CustomDevice from zigpy.zcl.clusters.general import Alarms, Basic, Identify, Ota, PowerConfiguration +from zigpy.zcl.clusters.homeautomation import Diagnostic from zigpy.zcl.clusters.security import IasWd, IasZone import zigpy.zdo.types @@ -30,8 +31,8 @@ class HeimanSmokYDLV10(CustomDevice): MODELS_INFO: [(HEIMAN, "SMOK_YDLV10")], ENDPOINTS: { 1: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, @@ -53,8 +54,8 @@ class HeimanSmokYDLV10(CustomDevice): ), ENDPOINTS: { 1: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, @@ -89,8 +90,8 @@ class HeimanSmokCO_V15(CustomDevice): # "in_clusters": ["0x0000","0x0001","0x0003","0x0009","0x0500"], # "out_clusters": ["0x0019"] 1: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, @@ -111,8 +112,8 @@ class HeimanSmokCO_V15(CustomDevice): ), ENDPOINTS: { 1: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, @@ -139,8 +140,8 @@ class HeimanSmokCO_CTPG(CustomDevice): # "device_type": "0x0402", # "in_clusters": ["0x0000","0x0001","0x0003","0x0009","0x0500"] # "out_clusters": ["0x0019"] - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, @@ -173,8 +174,8 @@ class HeimanSmokCO_CTPG(CustomDevice): ), ENDPOINTS: { 1: { - PROFILE_ID: zigpy.profiles.zha.PROFILE_ID, - DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE, + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, @@ -188,3 +189,57 @@ class HeimanSmokCO_CTPG(CustomDevice): }, }, } + + +class HeimanSmokeN30(CustomDevice): + """SmokeN30 quirk.""" + + # NodeDescriptor( + # logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, + # frequency_band=, mac_capability_flags=, + # manufacturer_code=4619, maximum_buffer_size=127, maximum_incoming_transfer_size=100, server_mask=11264, maximum_outgoing_transfer_size=100, + # descriptor_capability_field=, + # *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, + # *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False) + signature = { + MODELS_INFO: [("HEIMAN", "SmokeSensor-N-3.0")], + ENDPOINTS: { + # "profile_id": 260,"device_type": "0x0402", + # "in_clusters": ["0x0000","0x0001","0x0003","0x0500","0x0502","0x0b05"], + # "out_clusters": ["0x0019"] + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + IasZone.cluster_id, + IasWd.cluster_id, + Diagnostic.cluster_id, + ], + OUTPUT_CLUSTERS: [ + Ota.cluster_id, + ], + }, + }, + } + + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.IAS_ZONE, + INPUT_CLUSTERS: [ + Basic.cluster_id, + PowerConfiguration.cluster_id, + Identify.cluster_id, + IasZone.cluster_id, + Diagnostic.cluster_id, + ], + OUTPUT_CLUSTERS: [ + Ota.cluster_id, + ], + }, + }, + } From c4f016c3f06224a0e6017ff9ce42237c65c4dc31 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 4 Feb 2023 14:44:50 +0200 Subject: [PATCH 09/28] bump minimum zigpy version (#2167) --- requirements_test_all.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0c0911c66e..0970e70214 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -13,4 +13,4 @@ pytest-sugar pytest-timeout pytest-asyncio pytest>=7.1.3 -zigpy>=0.52 +zigpy>=0.53 diff --git a/setup.py b/setup.py index 49c3bbc7c8..94830a5c0a 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,6 @@ keywords="zha quirks homeassistant hass", packages=find_packages(exclude=["tests"]), python_requires=">=3.8", - install_requires=["zigpy>=0.52"], + install_requires=["zigpy>=0.53"], tests_require=["pytest"], ) From b8368c561fe3046ec7ccddec707853777ecca287 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 4 Feb 2023 14:46:16 +0200 Subject: [PATCH 10/28] Set TuyaData.dp_type automatically (#2043) * Set TuyaData.dp_type automatically * set function even when value is None * default TuyaData.dp_type to RAW --- zhaquirks/tuya/__init__.py | 26 ++++++++++++++++++++++ zhaquirks/tuya/mcu/__init__.py | 32 ++-------------------------- zhaquirks/tuya/ts0601_garage.py | 9 +------- zhaquirks/tuya/ts0601_illuminance.py | 4 +--- zhaquirks/tuya/ts0601_rcbo.py | 28 ++---------------------- zhaquirks/tuya/ts0601_sensor.py | 8 +------ zhaquirks/tuya/ts0601_siren.py | 6 ------ zhaquirks/tuya/ts0601_valve.py | 24 --------------------- 8 files changed, 33 insertions(+), 104 deletions(-) diff --git a/zhaquirks/tuya/__init__.py b/zhaquirks/tuya/__init__.py index 7f721a9401..398d288eeb 100644 --- a/zhaquirks/tuya/__init__.py +++ b/zhaquirks/tuya/__init__.py @@ -1,6 +1,7 @@ """Tuya devices.""" import dataclasses import datetime +import enum import logging from typing import Any, Callable, Dict, List, Optional, Tuple, Union @@ -226,6 +227,31 @@ def payload(self, value): else: raise ValueError(f"Unknown {self.dp_type} datapoint type") + def __new__(cls, *args, **kwargs): + """Disable copy constrctor.""" + return super().__new__(cls) + + def __init__(self, value=None, function=0, *args, **kwargs): + """Convert from a zigpy typed value to a tuya data payload.""" + self.function = function + + if value is None: + return + elif isinstance(value, (t.bitmap8, t.bitmap16, t.bitmap32)): + self.dp_type = TuyaDPType.BITMAP + elif isinstance(value, (bool, t.Bool)): + self.dp_type = TuyaDPType.BOOL + elif isinstance(value, enum.Enum): + self.dp_type = TuyaDPType.ENUM + elif isinstance(value, int): + self.dp_type = TuyaDPType.VALUE + elif isinstance(value, str): + self.dp_type = TuyaDPType.STRING + else: + self.dp_type = TuyaDPType.RAW + + self.payload = value + class Data(t.List, item_type=t.uint8_t): """list of uint8_t.""" diff --git a/zhaquirks/tuya/mcu/__init__.py b/zhaquirks/tuya/mcu/__init__.py index fb79141bd9..2b9309ac90 100644 --- a/zhaquirks/tuya/mcu/__init__.py +++ b/zhaquirks/tuya/mcu/__init__.py @@ -18,9 +18,7 @@ NoManufacturerCluster, PowerOnState, TuyaCommand, - TuyaData, TuyaDatapointData, - TuyaDPType, TuyaLocalCluster, TuyaNewManufCluster, TuyaTimePayload, @@ -39,7 +37,6 @@ class DPToAttributeMapping: ep_attribute: str attribute_name: Union[str, tuple] - dp_type: TuyaDPType converter: Optional[ Callable[ [ @@ -229,7 +226,6 @@ def from_cluster_data(self, data: TuyaClusterData) -> Optional[TuyaCommand]: cmd_payload.status = 0 cmd_payload.tsn = self.endpoint.device.application.get_sequence() - datapoint_type = mapping.dp_type val = data.attr_value if mapping.dp_converter: args = [] @@ -247,12 +243,8 @@ def from_cluster_data(self, data: TuyaClusterData) -> Optional[TuyaCommand]: val = mapping.dp_converter(*args) self.debug("value: %s", val) - tuya_data = TuyaData() - tuya_data.dp_type = datapoint_type - tuya_data.function = 0 - tuya_data.payload = val - self.debug("raw: %s", tuya_data.raw) - dpd = TuyaDatapointData(dp, tuya_data) + dpd = TuyaDatapointData(dp, val) + self.debug("raw: %s", dpd.data.raw) cmd_payload.datapoints = [dpd] tuya_commands.append(cmd_payload) @@ -417,36 +409,30 @@ class TuyaOnOffManufCluster(TuyaMCUCluster): 1: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, ), 2: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, endpoint_id=2, ), 3: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, endpoint_id=3, ), 4: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, endpoint_id=4, ), 5: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, endpoint_id=5, ), 6: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, endpoint_id=6, ), } @@ -480,7 +466,6 @@ class MoesSwitchManufCluster(TuyaOnOffManufCluster): 14: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "power_on_state", - dp_type=TuyaDPType.ENUM, converter=lambda x: PowerOnState(x), ) } @@ -490,7 +475,6 @@ class MoesSwitchManufCluster(TuyaOnOffManufCluster): 15: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "backlight_mode", - dp_type=TuyaDPType.ENUM, converter=lambda x: MoesBacklight(x), ), } @@ -596,37 +580,31 @@ class TuyaLevelControlManufCluster(TuyaMCUCluster): 1: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, ), 2: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "current_level", - dp_type=TuyaDPType.VALUE, converter=lambda x: (x * 255) // 1000, dp_converter=lambda x: (x * 1000) // 255, ), 3: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "minimum_level", - dp_type=TuyaDPType.VALUE, converter=lambda x: (x * 255) // 1000, dp_converter=lambda x: (x * 1000) // 255, ), 4: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "bulb_type", - dp_type=TuyaDPType.ENUM, ), 7: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, endpoint_id=2, ), 8: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "current_level", - dp_type=TuyaDPType.VALUE, converter=lambda x: (x * 255) // 1000, dp_converter=lambda x: (x * 1000) // 255, endpoint_id=2, @@ -634,7 +612,6 @@ class TuyaLevelControlManufCluster(TuyaMCUCluster): 9: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "minimum_level", - dp_type=TuyaDPType.VALUE, converter=lambda x: (x * 255) // 1000, dp_converter=lambda x: (x * 1000) // 255, endpoint_id=2, @@ -642,19 +619,16 @@ class TuyaLevelControlManufCluster(TuyaMCUCluster): 10: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "bulb_type", - dp_type=TuyaDPType.ENUM, endpoint_id=2, ), 15: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, endpoint_id=3, ), 16: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "current_level", - dp_type=TuyaDPType.VALUE, converter=lambda x: (x * 255) // 1000, dp_converter=lambda x: (x * 1000) // 255, endpoint_id=3, @@ -662,7 +636,6 @@ class TuyaLevelControlManufCluster(TuyaMCUCluster): 17: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "minimum_level", - dp_type=TuyaDPType.VALUE, converter=lambda x: (x * 255) // 1000, dp_converter=lambda x: (x * 1000) // 255, endpoint_id=3, @@ -670,7 +643,6 @@ class TuyaLevelControlManufCluster(TuyaMCUCluster): 18: DPToAttributeMapping( TuyaLevelControl.ep_attribute, "bulb_type", - dp_type=TuyaDPType.ENUM, endpoint_id=3, ), } diff --git a/zhaquirks/tuya/ts0601_garage.py b/zhaquirks/tuya/ts0601_garage.py index 1221e899a2..2143f2d7e7 100644 --- a/zhaquirks/tuya/ts0601_garage.py +++ b/zhaquirks/tuya/ts0601_garage.py @@ -15,7 +15,7 @@ PROFILE_ID, ) from zhaquirks.tuya import NoManufacturerCluster -from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaDPType, TuyaMCUCluster +from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster TUYA_MANUFACTURER_GARAGE = "tuya_manufacturer_garage" @@ -44,38 +44,31 @@ class TuyaGarageManufCluster(NoManufacturerCluster, TuyaMCUCluster): 1: DPToAttributeMapping( TUYA_MANUFACTURER_GARAGE, "button", - dp_type=TuyaDPType.BOOL, ), 2: DPToAttributeMapping( TUYA_MANUFACTURER_GARAGE, "dp_2", - dp_type=TuyaDPType.VALUE, ), 3: DPToAttributeMapping( TUYA_MANUFACTURER_GARAGE, "contact_sensor", - dp_type=TuyaDPType.BOOL, ), 4: DPToAttributeMapping( TUYA_MANUFACTURER_GARAGE, "dp_4", - dp_type=TuyaDPType.VALUE, ), 5: DPToAttributeMapping( TUYA_MANUFACTURER_GARAGE, "dp_5", - dp_type=TuyaDPType.VALUE, ), 11: DPToAttributeMapping( TUYA_MANUFACTURER_GARAGE, "dp_11", - dp_type=TuyaDPType.BOOL, ), # garage door status (open, closed, ...) 12: DPToAttributeMapping( TUYA_MANUFACTURER_GARAGE, "dp_12", - dp_type=TuyaDPType.ENUM, ), } diff --git a/zhaquirks/tuya/ts0601_illuminance.py b/zhaquirks/tuya/ts0601_illuminance.py index 98277fcf57..4c8b5ef1d5 100644 --- a/zhaquirks/tuya/ts0601_illuminance.py +++ b/zhaquirks/tuya/ts0601_illuminance.py @@ -18,7 +18,7 @@ PROFILE_ID, ) from zhaquirks.tuya import TuyaLocalCluster -from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaDPType, TuyaMCUCluster +from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster TUYA_BRIGHTNESS_LEVEL_DP = 0x01 # 0-2 "Low, Medium, High" TUYA_ILLUMINANCE_DP = 0x02 # [0, 0, 3, 232] illuminance @@ -50,13 +50,11 @@ class TuyaIlluminanceCluster(TuyaMCUCluster): TUYA_BRIGHTNESS_LEVEL_DP: DPToAttributeMapping( TuyaIlluminanceMeasurement.ep_attribute, "manufacturer_brightness_level", - dp_type=TuyaDPType.ENUM, converter=lambda x: BrightnessLevel(x), ), TUYA_ILLUMINANCE_DP: DPToAttributeMapping( TuyaIlluminanceMeasurement.ep_attribute, "measured_value", - dp_type=TuyaDPType.VALUE, converter=lambda x: (10000.0 * math.log10(x) + 1.0 if x != 0 else 0), ), } diff --git a/zhaquirks/tuya/ts0601_rcbo.py b/zhaquirks/tuya/ts0601_rcbo.py index b4312d6eb9..6db34d3816 100644 --- a/zhaquirks/tuya/ts0601_rcbo.py +++ b/zhaquirks/tuya/ts0601_rcbo.py @@ -34,7 +34,6 @@ DPToAttributeMapping, TuyaAttributesCluster, TuyaClusterData, - TuyaDPType, TuyaMCUCluster, TuyaOnOff, ) @@ -356,68 +355,56 @@ class TuyaRCBOManufCluster(TuyaMCUCluster): TUYA_DP_STATE: DPToAttributeMapping( TuyaRCBOOnOff.ep_attribute, "on_off", - TuyaDPType.BOOL, ), TUYA_DP_COUNTDOWN_TIMER: DPToAttributeMapping( TuyaRCBOOnOff.ep_attribute, "countdown_timer", - TuyaDPType.VALUE, ), TUYA_DP_FAULT_CODE: DPToAttributeMapping( TuyaRCBOElectricalMeasurement.ep_attribute, "alarm", - TuyaDPType.ENUM, lambda x: FaultCode(x), ), TUYA_DP_RELAY_STATUS: DPToAttributeMapping( TuyaRCBOOnOff.ep_attribute, "power_on_state", - TuyaDPType.ENUM, lambda x: PowerOnState(x), ), TUYA_DP_CHILD_LOCK: DPToAttributeMapping( TuyaRCBOOnOff.ep_attribute, "child_lock", - TuyaDPType.BOOL, ), TUYA_DP_VOLTAGE: DPToAttributeMapping( TuyaRCBOElectricalMeasurement.ep_attribute, "rms_voltage", - TuyaDPType.RAW, lambda x: x[1] | x[0] << 8, ), TUYA_DP_CURRENT: DPToAttributeMapping( TuyaRCBOElectricalMeasurement.ep_attribute, "rms_current", - TuyaDPType.RAW, lambda x: x[2] | x[1] << 8, ), TUYA_DP_ACTIVE_POWER: DPToAttributeMapping( TuyaRCBOElectricalMeasurement.ep_attribute, "active_power", - TuyaDPType.RAW, lambda x: x[2] | x[1] << 8, ), TUYA_DP_LEAKAGE_CURRENT: DPToAttributeMapping( TuyaRCBOElectricalMeasurement.ep_attribute, "leakage_current", - TuyaDPType.VALUE, ), TUYA_DP_TEMPERATURE: DPToAttributeMapping( TuyaRCBODeviceTemperature.ep_attribute, "current_temperature", - TuyaDPType.VALUE, lambda x: x * 100, ), TUYA_DP_REMAINING_ENERGY: DPToAttributeMapping( TuyaRCBOMetering.ep_attribute, "remaining_energy", - TuyaDPType.VALUE, ), TUYA_DP_COST_PARAMETERS: DPToAttributeMapping( TuyaRCBOMetering.ep_attribute, ("cost_parameters", "cost_parameters_enabled"), - TuyaDPType.RAW, lambda x: (x[1] | x[0] << 8, x[2]), lambda *fields: CostParameters(*fields), ), @@ -432,7 +419,6 @@ class TuyaRCBOManufCluster(TuyaMCUCluster): "over_leakage_current_alarm", "self_test", ), - TuyaDPType.RAW, lambda x: (x[0], x[1], x[2], x[4] | x[3] << 8, x[5], x[6], SelfTest(x[7])), lambda *fields: LeakageParameters(*fields), ), @@ -445,7 +431,6 @@ class TuyaRCBOManufCluster(TuyaMCUCluster): "rms_extreme_under_voltage", "under_voltage_trip", ), - TuyaDPType.RAW, lambda x: ( x[1] | x[0] << 8, x[2], @@ -465,7 +450,6 @@ class TuyaRCBOManufCluster(TuyaMCUCluster): TUYA_DP_CURRENT_THRESHOLD: DPToAttributeMapping( TuyaRCBOElectricalMeasurement.ep_attribute, ("ac_current_overload", "over_current_trip", "ac_alarms_mask"), - TuyaDPType.RAW, lambda x: ( (x[2] | x[1] << 8 | x[0] << 16), x[3], @@ -478,42 +462,34 @@ class TuyaRCBOManufCluster(TuyaMCUCluster): TUYA_DP_TEMPERATURE_THRESHOLD: DPToAttributeMapping( TuyaRCBODeviceTemperature.ep_attribute, ("high_temp_thres", "over_temp_trip", "dev_temp_alarm_mask"), - TuyaDPType.RAW, lambda x: (x[0] if x[0] <= 127 else x[0] - 256, x[1], x[2] << 1), lambda x, y, z: TemperatureSetting(x, y, bool(z & 0x02)), ), TUYA_DP_TOTAL_ACTIVE_POWER: DPToAttributeMapping( TuyaRCBOMetering.ep_attribute, "current_summ_delivered", - TuyaDPType.VALUE, ), TUYA_DP_EQUIPMENT_NUMBER_AND_TYPE: DPToAttributeMapping( TuyaRCBOMetering.ep_attribute, "meter_number", - TuyaDPType.STRING, lambda x: x.rstrip(), ), TUYA_DP_CLEAR_ENERGY: DPToAttributeMapping( - TuyaRCBOMetering.ep_attribute, "clear_device_data", TuyaDPType.BOOL - ), - TUYA_DP_LOCKING: DPToAttributeMapping( - TuyaRCBOOnOff.ep_attribute, "trip", TuyaDPType.BOOL + TuyaRCBOMetering.ep_attribute, "clear_device_data" ), + TUYA_DP_LOCKING: DPToAttributeMapping(TuyaRCBOOnOff.ep_attribute, "trip"), TUYA_DP_TOTAL_REVERSE_ACTIVE_POWER: DPToAttributeMapping( TuyaRCBOMetering.ep_attribute, "current_summ_received", - TuyaDPType.VALUE, ), TUYA_DP_HISTORICAL_VOLTAGE: DPToAttributeMapping( TuyaRCBOElectricalMeasurement.ep_attribute, "rms_historical_voltage", - TuyaDPType.RAW, lambda x: x[1] | x[0] << 8, ), TUYA_DP_HISTORICAL_CURRENT: DPToAttributeMapping( TuyaRCBOElectricalMeasurement.ep_attribute, "rms_historical_current", - TuyaDPType.RAW, lambda x: x[2] | x[1] << 8, ), } diff --git a/zhaquirks/tuya/ts0601_sensor.py b/zhaquirks/tuya/ts0601_sensor.py index 3112cdfcbf..9bf740859c 100644 --- a/zhaquirks/tuya/ts0601_sensor.py +++ b/zhaquirks/tuya/ts0601_sensor.py @@ -21,7 +21,7 @@ SKIP_CONFIGURATION, ) from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA -from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaDPType, TuyaMCUCluster +from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster): @@ -54,19 +54,16 @@ class TemperatureHumidityManufCluster(TuyaMCUCluster): 1: DPToAttributeMapping( TuyaTemperatureMeasurement.ep_attribute, "measured_value", - dp_type=TuyaDPType.VALUE, converter=lambda x: x * 10, # decidegree to centidegree ), 2: DPToAttributeMapping( TuyaRelativeHumidity.ep_attribute, "measured_value", - dp_type=TuyaDPType.VALUE, # converter=lambda x: x * 10, --> move conversion to TuyaRelativeHumidity cluster ), 4: DPToAttributeMapping( TuyaPowerConfigurationCluster2AAA.ep_attribute, "battery_percentage_remaining", - dp_type=TuyaDPType.VALUE, converter=lambda x: x * 2, # double reported percentage ), } @@ -227,19 +224,16 @@ class SoilManufCluster(TuyaMCUCluster): 5: DPToAttributeMapping( TuyaTemperatureMeasurement.ep_attribute, "measured_value", - dp_type=TuyaDPType.VALUE, converter=lambda x: x * 100, ), 3: DPToAttributeMapping( TuyaSoilMoisture.ep_attribute, "measured_value", - dp_type=TuyaDPType.VALUE, converter=lambda x: x * 100, ), 15: DPToAttributeMapping( TuyaPowerConfigurationCluster2AAA.ep_attribute, "battery_percentage_remaining", - dp_type=TuyaDPType.VALUE, converter=lambda x: x * 2, # double reported percentage ), } diff --git a/zhaquirks/tuya/ts0601_siren.py b/zhaquirks/tuya/ts0601_siren.py index dd37c2ff5c..dc73503f78 100644 --- a/zhaquirks/tuya/ts0601_siren.py +++ b/zhaquirks/tuya/ts0601_siren.py @@ -33,7 +33,6 @@ DPToAttributeMapping, TuyaAttributesCluster, TuyaClusterData, - TuyaDPType, TuyaMCUCluster, ) @@ -319,28 +318,23 @@ class NeoSirenManufCluster(TuyaMCUCluster): 5: DPToAttributeMapping( TuyaMCUSiren.ep_attribute, "volume", - dp_type=TuyaDPType.ENUM, converter=lambda x: NeoAlarmVolume(x), ), 7: DPToAttributeMapping( TuyaMCUSiren.ep_attribute, "alarm_duration", - dp_type=TuyaDPType.VALUE, ), 13: DPToAttributeMapping( TuyaMCUSiren.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, ), 15: DPToAttributeMapping( TuyaMCUSiren.ep_attribute, "battery", - dp_type=TuyaDPType.VALUE, ), 21: DPToAttributeMapping( TuyaMCUSiren.ep_attribute, "melody", - dp_type=TuyaDPType.ENUM, converter=lambda x: NeoAlarmMelody(x), ), } diff --git a/zhaquirks/tuya/ts0601_valve.py b/zhaquirks/tuya/ts0601_valve.py index cda4a131ff..54e28e6f43 100644 --- a/zhaquirks/tuya/ts0601_valve.py +++ b/zhaquirks/tuya/ts0601_valve.py @@ -21,7 +21,6 @@ from zhaquirks.tuya.mcu import ( DPToAttributeMapping, EnchantedDevice, - TuyaDPType, TuyaMCUCluster, TuyaOnOff, TuyaOnOffNM, @@ -59,37 +58,30 @@ class TuyaValveManufCluster(TuyaMCUCluster): 1: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, ), 5: DPToAttributeMapping( TuyaValveWaterConsumed.ep_attribute, "current_summ_delivered", - TuyaDPType.VALUE, ), 6: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "dp_6", - TuyaDPType.VALUE, ), 7: DPToAttributeMapping( DoublingPowerConfigurationCluster.ep_attribute, "battery_percentage_remaining", - TuyaDPType.VALUE, ), 11: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "time_left", - TuyaDPType.VALUE, ), 12: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "state", - TuyaDPType.VALUE, ), 15: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "last_valve_open_duration", - TuyaDPType.VALUE, ), } @@ -162,33 +154,27 @@ class ParksideTuyaValveManufCluster(TuyaMCUCluster): 1: DPToAttributeMapping( TuyaOnOff.ep_attribute, "on_off", - TuyaDPType.BOOL, ), 5: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "timer_duration", - TuyaDPType.VALUE, ), 6: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "timer_time_left", - TuyaDPType.VALUE, ), 11: DPToAttributeMapping( TuyaPowerConfigurationCluster.ep_attribute, "battery_percentage_remaining", - TuyaDPType.VALUE, ), 108: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "frost_lock", - TuyaDPType.BOOL, lambda x: not x, # invert for lock entity ), 109: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "frost_lock_reset", - TuyaDPType.BOOL, ), } @@ -289,52 +275,42 @@ class GiexValveManufCluster(TuyaMCUCluster): 1: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "irrigation_mode", - dp_type=TuyaDPType.BOOL, ), 2: DPToAttributeMapping( TuyaOnOffNM.ep_attribute, "on_off", - dp_type=TuyaDPType.BOOL, ), 101: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "irrigation_start_time", - dp_type=TuyaDPType.VALUE, ), 102: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "irrigation_end_time", - dp_type=TuyaDPType.VALUE, ), 103: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "irrigation_num_times", - dp_type=TuyaDPType.VALUE, ), 104: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "irrigation_target", - dp_type=TuyaDPType.VALUE, ), 105: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "irrigation_interval", - dp_type=TuyaDPType.VALUE, ), 108: DPToAttributeMapping( TuyaPowerConfigurationCluster.ep_attribute, "battery_percentage_remaining", - dp_type=TuyaDPType.VALUE, ), 111: DPToAttributeMapping( TuyaValveWaterConsumed.ep_attribute, "current_summ_delivered", - dp_type=TuyaDPType.VALUE, ), 114: DPToAttributeMapping( TuyaMCUCluster.ep_attribute, "irrigation_duration", - dp_type=TuyaDPType.VALUE, ), } From acb6ea1b9e24befbaa555d8d566822334a0ddbee Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 4 Feb 2023 16:56:40 +0200 Subject: [PATCH 11/28] tuya: use zigpy big endian types (#2168) --- tests/test_tuya_clusters.py | 3 +-- zhaquirks/tuya/__init__.py | 33 ++++----------------------------- zhaquirks/tuya/ts0601_rcbo.py | 28 ++++++---------------------- 3 files changed, 11 insertions(+), 53 deletions(-) diff --git a/tests/test_tuya_clusters.py b/tests/test_tuya_clusters.py index 9fe4b9e53d..68d3737a41 100644 --- a/tests/test_tuya_clusters.py +++ b/tests/test_tuya_clusters.py @@ -11,7 +11,6 @@ TUYA_GET_DATA, TUYA_SET_DATA_RESPONSE, TUYA_SET_TIME, - BigEndianInt16, TuyaCommand, TuyaData, TuyaDatapointData, @@ -33,7 +32,7 @@ def test_tuya_data_raw(): class Test(t.Struct): test_bool: t.Bool - test_uint16_t_be: BigEndianInt16 + test_uint16_t_be: t.uint16_t_be data = b"\x00\x00\x03\x01\x02\x46" extra = b"extra data" diff --git a/zhaquirks/tuya/__init__.py b/zhaquirks/tuya/__init__.py index 398d288eeb..5b8e852d4a 100644 --- a/zhaquirks/tuya/__init__.py +++ b/zhaquirks/tuya/__init__.py @@ -23,7 +23,6 @@ SHORT_PRESS, ZHA_SEND_EVENT, ) -from zhaquirks.xbee.types import uint32_t as uint32_t_be # --------------------------------------------------------- # Tuya Custom Cluster ID @@ -128,31 +127,7 @@ _LOGGER = logging.getLogger(__name__) -class BigEndianInt16(int): - """Helper class to represent big endian 16 bit value.""" - - def serialize(self) -> bytes: - """Value serialisation.""" - - try: - return self.to_bytes(2, "big", signed=False) - except OverflowError as e: - # OverflowError is not a subclass of ValueError, making it annoying to catch - raise ValueError(str(e)) from e - - @classmethod - def deserialize(cls, data: bytes) -> Tuple["BigEndianInt16", bytes]: - """Value deserialisation.""" - - if len(data) < 2: - raise ValueError(f"Data is too short to contain {cls._size} bytes") - - r = cls.from_bytes(data[:2], "big", signed=False) - data = data[2:] - return r, data - - -class TuyaTimePayload(t.LVList, item_type=t.uint8_t, length_type=BigEndianInt16): +class TuyaTimePayload(t.LVList, item_type=t.uint8_t, length_type=t.uint16_t_be): """Tuya set time payload definition.""" @@ -178,7 +153,7 @@ class TuyaData(t.Struct): def payload( self, ) -> Union[ - uint32_t_be, + t.uint32_t_be, t.Bool, t.CharacterString, t.enum8, @@ -189,7 +164,7 @@ def payload( ]: """Payload accordingly to data point type.""" if self.dp_type == TuyaDPType.VALUE: - return uint32_t_be.deserialize(self.raw)[0] + return t.uint32_t_be.deserialize(self.raw)[0] elif self.dp_type == TuyaDPType.BOOL: return t.Bool.deserialize(self.raw)[0] elif self.dp_type == TuyaDPType.STRING: @@ -211,7 +186,7 @@ def payload( def payload(self, value): """Set payload accordingly to data point type.""" if self.dp_type == TuyaDPType.VALUE: - self.raw = uint32_t_be(value).serialize() + self.raw = t.uint32_t_be(value).serialize() elif self.dp_type == TuyaDPType.BOOL: self.raw = t.Bool(value).serialize() elif self.dp_type == TuyaDPType.STRING: diff --git a/zhaquirks/tuya/ts0601_rcbo.py b/zhaquirks/tuya/ts0601_rcbo.py index 6db34d3816..185c14b676 100644 --- a/zhaquirks/tuya/ts0601_rcbo.py +++ b/zhaquirks/tuya/ts0601_rcbo.py @@ -24,12 +24,7 @@ OUTPUT_CLUSTERS, PROFILE_ID, ) -from zhaquirks.tuya import ( - TUYA_MCU_COMMAND, - AttributeWithMask, - BigEndianInt16, - PowerOnState, -) +from zhaquirks.tuya import TUYA_MCU_COMMAND, AttributeWithMask, PowerOnState from zhaquirks.tuya.mcu import ( DPToAttributeMapping, TuyaAttributesCluster, @@ -37,9 +32,6 @@ TuyaMCUCluster, TuyaOnOff, ) -from zhaquirks.xbee.types import ( - uint_t as uint_t_be, # Temporary workaround until zigpy/zigpy#1124 is merged -) TUYA_DP_STATE = 1 TUYA_DP_COUNTDOWN_TIMER = 9 @@ -90,7 +82,7 @@ class SelfTest(t.enum8): class CostParameters(t.Struct): """Tuya cost parameters.""" - cost_parameters: BigEndianInt16 + cost_parameters: t.uint16_t_be cost_parameters_enabled: t.Bool @@ -100,7 +92,7 @@ class LeakageParameters(t.Struct): self_test_auto_days: t.uint8_t self_test_auto_hours: t.uint8_t self_test_auto: t.Bool - over_leakage_current_threshold: BigEndianInt16 + over_leakage_current_threshold: t.uint16_t_be over_leakage_current_trip: t.Bool over_leakage_current_alarm: t.Bool self_test: SelfTest @@ -109,26 +101,18 @@ class LeakageParameters(t.Struct): class VoltageParameters(t.Struct): """Tuya voltage parameters.""" - over_voltage_threshold: BigEndianInt16 + over_voltage_threshold: t.uint16_t_be over_voltage_trip: t.Bool over_voltage_alarm: t.Bool - under_voltage_threshold: BigEndianInt16 + under_voltage_threshold: t.uint16_t_be under_voltage_trip: t.Bool under_voltage_alarm: t.Bool -class uint24_t_be( - uint_t_be -): # TODO: Replace with zigpy big endian type once zigpy/zigpy#1124 is merged - """Unsigned int 24 bit big-endian type.""" - - _size = 3 - - class CurrentParameters(t.Struct): """Tuya current parameters.""" - over_current_threshold: uint24_t_be + over_current_threshold: t.uint24_t_be over_current_trip: t.Bool over_current_alarm: t.Bool From bc642deceb0d41eea11ce0877bd8e0c78e30a7a8 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Sat, 4 Feb 2023 16:56:15 +0100 Subject: [PATCH 12/28] Deduplicate Xiaomi plug constructor code (#2169) --- zhaquirks/xiaomi/aqara/plug.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/zhaquirks/xiaomi/aqara/plug.py b/zhaquirks/xiaomi/aqara/plug.py index ea7f97a7ce..6c8d63bf72 100644 --- a/zhaquirks/xiaomi/aqara/plug.py +++ b/zhaquirks/xiaomi/aqara/plug.py @@ -129,16 +129,9 @@ def __init__(self, *args, **kwargs): } -class Plug2(XiaomiCustomDevice): +class Plug2(Plug): """lumi.plug with alternative signature.""" - def __init__(self, *args, **kwargs): - """Init.""" - self.voltage_bus = Bus() - self.consumption_bus = Bus() - self.power_bus = Bus() - super().__init__(*args, **kwargs) - signature = { MODELS_INFO: Plug.signature[MODELS_INFO], ENDPOINTS: { From 84d02be7abde55a6cee80fa155f0cbbc20347c40 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sat, 4 Feb 2023 17:57:17 +0200 Subject: [PATCH 13/28] xiaomi: use zigpy big endian types (#2170) --- zhaquirks/xiaomi/aqara/feeder_acn001.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/zhaquirks/xiaomi/aqara/feeder_acn001.py b/zhaquirks/xiaomi/aqara/feeder_acn001.py index a9c328a55d..d6df9a0cda 100644 --- a/zhaquirks/xiaomi/aqara/feeder_acn001.py +++ b/zhaquirks/xiaomi/aqara/feeder_acn001.py @@ -145,8 +145,7 @@ def _update_feeder_attribute(self, attrid: int, value: Any) -> None: def _parse_feeder_attribute(self, value: bytes) -> None: """Parse the feeder attribute.""" - # attribute is big endian, so we need to reverse the bytes - attribute, _ = types.int32s.deserialize(bytes(reversed(value[3:7]))) + attribute, _ = types.int32s_be.deserialize(value[3:7]) LOGGER.debug("OppleCluster._parse_feeder_attribute: attribute: %s", attribute) length, _ = types.uint8_t.deserialize(value[7:8]) LOGGER.debug("OppleCluster._parse_feeder_attribute: length: %s", length) @@ -164,14 +163,10 @@ def _parse_feeder_attribute(self, value: bytes) -> None: ) self._update_attribute(ZCL_LAST_FEEDING_SIZE, int(feeding_size)) elif attribute == PORTIONS_DISPENSED: - portions_per_day, _ = types.uint16_t.deserialize( - bytes(reversed(attribute_value)) - ) + portions_per_day, _ = types.uint16_t_be.deserialize(attribute_value) self._update_attribute(ZCL_PORTIONS_DISPENSED, portions_per_day) elif attribute == WEIGHT_DISPENSED: - weight_per_day, _ = types.uint32_t.deserialize( - bytes(reversed(attribute_value)) - ) + weight_per_day, _ = types.uint32_t_be.deserialize(attribute_value) self._update_attribute(ZCL_WEIGHT_DISPENSED, weight_per_day) elif attribute == SCHEDULING_STRING: LOGGER.debug( @@ -199,16 +194,16 @@ def _build_feeder_attribute( self._send_sequence = ((self._send_sequence or 0) + 1) % 256 val = bytes([0x00, 0x02, self._send_sequence]) self._send_sequence += 1 - val += bytes(reversed(types.int32s(attribute_id).serialize())) + val += types.int32s_be(attribute_id).serialize() if length is not None and value is not None: - val += bytes(reversed(types.uint8_t(length).serialize())) + val += types.uint8_t(length).serialize() if value is not None: if length == 1: val += types.uint8_t(value).serialize() elif length == 2: - val += bytes(reversed(types.uint16_t(value).serialize())) + val += types.uint16_t_be(value).serialize() elif length == 4: - val += bytes(reversed(types.uint32_t(value).serialize())) + val += types.uint32_t_be(value).serialize() else: val += value LOGGER.debug( From 98b2c61e36052671f20119ca754a04f599b5ed92 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 5 Feb 2023 00:13:45 +0200 Subject: [PATCH 14/28] xbee: use zigpy big endian types (#2173) --- tests/test_xbee.py | 4 +- zhaquirks/xbee/__init__.py | 118 ++++++++++++++++++------------------- zhaquirks/xbee/types.py | 109 +--------------------------------- 3 files changed, 59 insertions(+), 172 deletions(-) diff --git a/tests/test_xbee.py b/tests/test_xbee.py index ddf81d3e38..24b21e394e 100644 --- a/tests/test_xbee.py +++ b/tests/test_xbee.py @@ -199,7 +199,7 @@ async def test_receive_serial_data(zigpy_device_from_quirk): 67, None, b"2\x00\x02\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfeTP", - b"\x01TP\x00\x18", + b"\x01TP\x00\x00\x18", "tp_command_response", 24, ), @@ -208,7 +208,7 @@ async def test_receive_serial_data(zigpy_device_from_quirk): 67, None, b"2\x00\x02\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfeTP", - b"\x01TP\x00\xfc", + b"\x01TP\x00\xff\xfc", "tp_command_response", -4, ), diff --git a/zhaquirks/xbee/__init__.py b/zhaquirks/xbee/__init__.py index ee17538c4d..5280e04500 100644 --- a/zhaquirks/xbee/__init__.py +++ b/zhaquirks/xbee/__init__.py @@ -6,10 +6,10 @@ import asyncio import enum import logging -from typing import Any, List, Optional, Union +from typing import Any, List, Optional from zigpy.quirks import CustomDevice -import zigpy.types as zt +import zigpy.types as t from zigpy.zcl import foundation from zigpy.zcl.clusters.general import ( AnalogInput, @@ -23,7 +23,7 @@ from zhaquirks import EventableCluster, LocalDataCluster from zhaquirks.const import ENDPOINTS, INPUT_CLUSTERS, OUTPUT_CLUSTERS -from . import types as t +from .types import ATCommand, BinaryString, Bytes, IOSample _LOGGER = logging.getLogger(__name__) @@ -52,46 +52,46 @@ # https://github.com/zigpy/zigpy-xbee/blob/dev/zigpy_xbee/api.py AT_COMMANDS = { # Addressing commands - "DH": t.uint32_t, - "DL": t.uint32_t, - "MY": t.uint16_t, - "MP": t.uint16_t, - "NC": t.uint32_t, # 0 - MAX_CHILDREN. - "SH": t.uint32_t, - "SL": t.uint32_t, - "NI": t.Bytes, # 20 byte printable ascii string + "DH": t.uint32_t_be, + "DL": t.uint32_t_be, + "MY": t.uint16_t_be, + "MP": t.uint16_t_be, + "NC": t.uint32_t_be, # 0 - MAX_CHILDREN. + "SH": t.uint32_t_be, + "SL": t.uint32_t_be, + "NI": Bytes, # 20 byte printable ascii string # "SE": t.uint8_t, # "DE": t.uint8_t, - # "CI": t.uint16_t, + # "CI": t.uint16_t_be, "TO": t.uint8_t, - "NP": t.uint16_t, - "DD": t.uint32_t, + "NP": t.uint16_t_be, + "DD": t.uint32_t_be, "CR": t.uint8_t, # 0 - 0x3F # Networking commands "CH": t.uint8_t, # 0x0B - 0x1A "DA": None, # no param - # "ID": t.uint64_t, - "OP": t.uint64_t, + # "ID": t.uint64_t_be, + "OP": t.uint64_t_be, "NH": t.uint8_t, "BH": t.uint8_t, # 0 - 0x1E - "OI": t.uint16_t, + "OI": t.uint16_t_be, "NT": t.uint8_t, # 0x20 - 0xFF "NO": t.uint8_t, # bitfield, 0 - 3 - "SC": t.uint16_t, # 1 - 0xFFFF + "SC": t.uint16_t_be, # 1 - 0xFFFF "SD": t.uint8_t, # 0 - 7 # "ZS": t.uint8_t, # 0 - 2 "NJ": t.uint8_t, "JV": t.Bool, - "NW": t.uint16_t, # 0 - 0x64FF + "NW": t.uint16_t_be, # 0 - 0x64FF "JN": t.Bool, "AR": t.uint8_t, "DJ": t.Bool, # WTF, docs - "II": t.uint16_t, + "II": t.uint16_t_be, # Security commands # "EE": t.Bool, # "EO": t.uint8_t, - # "NK": t.Bytes, # 128-bit value - # "KY": t.Bytes, # 128-bit value + # "NK": Bytes, # 128-bit value + # "KY": Bytes, # 128-bit value # RF interfacing commands "PL": t.uint8_t, # 0 - 4 (basically an Enum) "PM": t.Bool, @@ -108,8 +108,8 @@ "P3": t.uint8_t, # 0 - 5 (an Enum) "P4": t.uint8_t, # 0 - 5 (an Enum) # I/O commands - "IR": t.uint16_t, - "IC": t.uint16_t, + "IR": t.uint16_t_be, + "IC": t.uint16_t_be, "D0": t.uint8_t, # 0 - 5 (an Enum) "D1": t.uint8_t, # 0 - 5 (an Enum) "D2": t.uint8_t, # 0 - 5 (an Enum) @@ -127,31 +127,31 @@ "P8": t.uint8_t, # 0 - 5 (an Enum) "P9": t.uint8_t, # 0 - 5 (an Enum) "LT": t.uint8_t, - "PR": t.uint16_t, + "PR": t.uint16_t_be, "RP": t.uint8_t, - "%V": t.uint16_t, # read only - "V+": t.uint16_t, - "TP": t.int16_t, - "M0": t.uint16_t, # 0 - 0x3FF - "M1": t.uint16_t, # 0 - 0x3FF + "%V": t.uint16_t_be, # read only + "V+": t.uint16_t_be, + "TP": t.int16s_be, + "M0": t.uint16_t_be, # 0 - 0x3FF + "M1": t.uint16_t_be, # 0 - 0x3FF # Diagnostics commands - "VR": t.uint16_t, - "HV": t.uint16_t, + "VR": t.uint16_t_be, + "HV": t.uint16_t_be, "AI": t.uint8_t, # AT command options - "CT": t.uint16_t, # 2 - 0x028F + "CT": t.uint16_t_be, # 2 - 0x028F "CN": None, - "GT": t.uint16_t, + "GT": t.uint16_t_be, "CC": t.uint8_t, # Sleep commands "SM": t.uint8_t, - "SN": t.uint16_t, - "SP": t.uint16_t, - "ST": t.uint16_t, + "SN": t.uint16_t_be, + "SP": t.uint16_t_be, + "ST": t.uint16_t_be, "SO": t.uint8_t, - "WH": t.uint16_t, + "WH": t.uint16_t_be, "SI": None, - "PO": t.uint16_t, # 0 - 0x3E8 + "PO": t.uint16_t_be, # 0 - 0x3E8 # Execution commands "AC": None, "WR": None, @@ -159,8 +159,8 @@ "FR": None, "NR": t.Bool, "CB": t.uint8_t, - "DN": t.Bytes, # "up to 20-Byte printable ASCII string" - "IS": t.IOSample, + "DN": Bytes, # "up to 20-Byte printable ASCII string" + "IS": IOSample, "AS": None, # Stuff I've guessed # "CE": t.uint8_t, @@ -314,7 +314,7 @@ def remote_at_command(self, cmd_name, *args, apply_changes=True, **kwargs): async def _remote_at_command(self, options, name, *args): _LOGGER.debug("Remote AT command: %s %s", name, args) - data = zt.serialize(args, (AT_COMMANDS[name],)) + data = t.serialize(args, (AT_COMMANDS[name],)) try: return await asyncio.wait_for( await self._command(options, name.encode("ascii"), data, *args), @@ -334,17 +334,17 @@ async def _command(self, options, command, data, *args): t.uint8_t, t.uint8_t, t.EUI64, - t.NWK, - t.Bytes, - t.Bytes, + t.uint16_t_be, + Bytes, + Bytes, ) - data = zt.serialize( + data = t.serialize( ( 0x32, 0x00, options, frame_id, - self._endpoint.device.application.state.node_info.ieee, + self._endpoint.device.application.state.node_info.ieee[::-1], self._endpoint.device.application.state.node_info.nwk, command, data, @@ -432,9 +432,7 @@ def handle_cluster_request( hdr: foundation.ZCLHeader, args: List[Any], *, - dst_addressing: Optional[ - Union[zt.Addressing.Group, zt.Addressing.IEEE, zt.Addressing.NWK] - ] = None, + dst_addressing: Optional[t.AddrMode] = None, ): """Handle AT response.""" if hdr.command_id == DATA_IN_CMD: @@ -471,9 +469,9 @@ def handle_cluster_request( name="remote_at_response", schema={ "frame_id": t.uint8_t, - "cmd": t.ATCommand, + "cmd": ATCommand, "status": t.uint8_t, - "value": t.Bytes, + "value": Bytes, }, is_manufacturer_specific=True, ) @@ -490,9 +488,7 @@ def handle_cluster_request( hdr: foundation.ZCLHeader, args: List[Any], *, - dst_addressing: Optional[ - Union[zt.Addressing.Group, zt.Addressing.IEEE, zt.Addressing.NWK] - ] = None, + dst_addressing: Optional[t.AddrMode] = None, ): """Handle the cluster request. @@ -529,7 +525,7 @@ def handle_cluster_request( server_commands = { SAMPLE_DATA_CMD: foundation.ZCLCommandDef( name="io_sample", - schema={"io_sample": t.IOSample}, + schema={"io_sample": IOSample}, is_manufacturer_specific=True, ) } @@ -571,7 +567,7 @@ async def command( tsn=None, ): """Handle outgoing data.""" - data = t.BinaryString(data).serialize() + data = BinaryString(data).serialize() return foundation.GENERAL_COMMANDS[ foundation.GeneralCommand.Default_Response ].schema( @@ -595,9 +591,7 @@ def handle_cluster_request( hdr: foundation.ZCLHeader, args: List[Any], *, - dst_addressing: Optional[ - Union[zt.Addressing.Group, zt.Addressing.IEEE, zt.Addressing.NWK] - ] = None, + dst_addressing: Optional[t.AddrMode] = None, ): """Handle incoming data.""" if hdr.command_id == DATA_IN_CMD: @@ -611,14 +605,14 @@ def handle_cluster_request( client_commands = { SERIAL_DATA_CMD: foundation.ZCLCommandDef( name="send_data", - schema={"data": t.BinaryString}, + schema={"data": BinaryString}, is_manufacturer_specific=True, ) } server_commands = { SERIAL_DATA_CMD: foundation.ZCLCommandDef( name="receive_data", - schema={"data": t.BinaryString}, + schema={"data": BinaryString}, is_manufacturer_specific=True, ) } diff --git a/zhaquirks/xbee/types.py b/zhaquirks/xbee/types.py index 80a1c5ab9c..7a54c56cc9 100644 --- a/zhaquirks/xbee/types.py +++ b/zhaquirks/xbee/types.py @@ -1,11 +1,4 @@ -"""Types used to serialize and deserialize XBee commands. - -Most of them are taken from https://github.com/zigpy/zigpy-xbee/blob/dev/zigpy_xbee/types.py -""" - -import enum - -import zigpy.types +"""Types used to serialize and deserialize XBee commands.""" class Bytes(bytes): @@ -30,106 +23,6 @@ def deserialize(cls, data): return cls(data[:2]), data[2:] -class int_t(int): - """Signed int type.""" - - _signed = True - - def serialize(self): - """Serialize int_t.""" - return self.to_bytes(self._size, "big", signed=self._signed) - - @classmethod - def deserialize(cls, data): - """Deserialize int_t.""" - # Work around https://bugs.python.org/issue23640 - r = cls(int.from_bytes(data[: cls._size], "big", signed=cls._signed)) - data = data[cls._size :] - return r, data - - -class uint_t(int_t): - """Unsigned int type.""" - - _signed = False - - -class uint8_t(uint_t): - """Unsigned int 8 bit type.""" - - _size = 1 - - -class int16_t(int_t): - """Signed int 16 bit type.""" - - _size = 2 - - -class uint16_t(uint_t): - """Unsigned int 16 bit type.""" - - _size = 2 - - -class uint32_t(uint_t): - """Unsigned int 32 bit type.""" - - _size = 4 - - -class uint64_t(uint_t): - """Unsigned int 64 bit type.""" - - _size = 8 - - -class Bool(uint8_t, enum.Enum): - """Boolean type with values true and false.""" - - false = 0x00 # An alias for zero, used for clarity. - true = 0x01 # An alias for one, used for clarity. - - -class EUI64(zigpy.types.EUI64): - """EUI64 serializable class.""" - - @classmethod - def deserialize(cls, data): - """Deserialize EUI64.""" - r, data = super().deserialize(data) - return cls(r[::-1]), data - - def serialize(self): - """Serialize EUI64.""" - assert self._length == len(self) - return super().serialize()[::-1] - - -class FrameId(uint8_t): - """Frame ID type.""" - - pass - - -class NWK(int): - """Network address serializable class.""" - - _signed = False - _size = 2 - - def serialize(self): - """Serialize NWK.""" - return self.to_bytes(self._size, "big", signed=self._signed) - - @classmethod - def deserialize(cls, data): - """Deserialize NWK.""" - r = cls(int.from_bytes(data[: cls._size], "big", signed=cls._signed)) - data = data[cls._size :] - return r, data - - class BinaryString(str): """Class to parse and serialize binary data as string.""" From 1bfe93211342b370aa0c3ab37f384c35fceb4c32 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Sun, 5 Feb 2023 01:14:12 +0200 Subject: [PATCH 15/28] tuya: fix negative values for some devices (#2175) * switch to signed value * Test negative values To test the negative values --------- Co-authored-by: javicalle <31999997+javicalle@users.noreply.github.com> --- tests/test_tuya_clusters.py | 14 ++++++++++++++ zhaquirks/tuya/__init__.py | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/test_tuya_clusters.py b/tests/test_tuya_clusters.py index 68d3737a41..0a3b15223c 100644 --- a/tests/test_tuya_clusters.py +++ b/tests/test_tuya_clusters.py @@ -65,6 +65,20 @@ def test_tuya_data_value(): assert r.raw == b"\x00\x00\x02\x46" +def test_tuya_negative_value(): + """Test tuya negative "Value" datatype.""" + + data = b"\x02\x00\x04\xff\xff\xff\xf8" + extra = b"extra data" + + r, rest = TuyaData.deserialize(data + extra) + assert rest == extra + + assert r.dp_type == 2 + assert r.raw == b"\xff\xff\xff\xf8" + assert r.payload == -8 + + def test_tuya_data_bool(): """Test tuya Bool datatype.""" diff --git a/zhaquirks/tuya/__init__.py b/zhaquirks/tuya/__init__.py index 5b8e852d4a..42ddf75569 100644 --- a/zhaquirks/tuya/__init__.py +++ b/zhaquirks/tuya/__init__.py @@ -153,7 +153,7 @@ class TuyaData(t.Struct): def payload( self, ) -> Union[ - t.uint32_t_be, + t.int32s_be, t.Bool, t.CharacterString, t.enum8, @@ -164,7 +164,7 @@ def payload( ]: """Payload accordingly to data point type.""" if self.dp_type == TuyaDPType.VALUE: - return t.uint32_t_be.deserialize(self.raw)[0] + return t.int32s_be.deserialize(self.raw)[0] elif self.dp_type == TuyaDPType.BOOL: return t.Bool.deserialize(self.raw)[0] elif self.dp_type == TuyaDPType.STRING: @@ -186,7 +186,7 @@ def payload( def payload(self, value): """Set payload accordingly to data point type.""" if self.dp_type == TuyaDPType.VALUE: - self.raw = t.uint32_t_be(value).serialize() + self.raw = t.int32s_be(value).serialize() elif self.dp_type == TuyaDPType.BOOL: self.raw = t.Bool(value).serialize() elif self.dp_type == TuyaDPType.STRING: From 959a3b706bfb4f81e783f0a635bba94d9bc12d76 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Mon, 6 Feb 2023 13:26:23 +0100 Subject: [PATCH 16/28] Add missing window_detection_bus to Tuya TRV MoesHY368_Type2 (#2171) Fixes #2166 --- zhaquirks/tuya/ts0601_trv.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zhaquirks/tuya/ts0601_trv.py b/zhaquirks/tuya/ts0601_trv.py index 12b7278a3c..dc626199d5 100644 --- a/zhaquirks/tuya/ts0601_trv.py +++ b/zhaquirks/tuya/ts0601_trv.py @@ -1621,6 +1621,11 @@ def __init__(self, *args, **kwargs): class MoesHY368_Type2(TuyaThermostat): """MoesHY368 Thermostatic radiator valve (2nd cluster signature).""" + def __init__(self, *args, **kwargs): + """Init device.""" + self.window_detection_bus = Bus() + super().__init__(*args, **kwargs) + signature = { # endpoint=1 profile=260 device_type=0 device_version=0 input_clusters=[0, 3] # output_clusters=[3, 25]> From 2af3bff0ff0301bf7222ecdcd9702ec99e74162d Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Mon, 6 Feb 2023 13:27:57 +0100 Subject: [PATCH 17/28] Add MIR-TE100-TY sensor (#2179) Add the device signature for MIR-TE100-TY temp and humidity sensor: * _TZE200_zl1kmjqx Added to `TuyaTempHumiditySensor` quirk. --- zhaquirks/tuya/ts0601_sensor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zhaquirks/tuya/ts0601_sensor.py b/zhaquirks/tuya/ts0601_sensor.py index 9bf740859c..4f255a5360 100644 --- a/zhaquirks/tuya/ts0601_sensor.py +++ b/zhaquirks/tuya/ts0601_sensor.py @@ -86,7 +86,10 @@ class TuyaTempHumiditySensor(CustomDevice): # device_version=1 # input_clusters=[4, 5, 61184, 0] # output_clusters=[25, 10]> - MODELS_INFO: [("_TZE200_bjawzodf", "TS0601")], + MODELS_INFO: [ + ("_TZE200_bjawzodf", "TS0601"), + ("_TZE200_zl1kmjqx", "TS0601"), + ], ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, From a9892336464c9de4771f3ca1ce6b796039f4262b Mon Sep 17 00:00:00 2001 From: b2un0 <2155498+b2un0@users.noreply.github.com> Date: Wed, 8 Feb 2023 14:52:39 +0100 Subject: [PATCH 18/28] Add Tuya siren _TZE204_t1blo2bj (#2185) fix #zigpy/zha-device-handlers/issues/2035 --- zhaquirks/tuya/ts0601_siren.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zhaquirks/tuya/ts0601_siren.py b/zhaquirks/tuya/ts0601_siren.py index dc73503f78..d05ad16445 100644 --- a/zhaquirks/tuya/ts0601_siren.py +++ b/zhaquirks/tuya/ts0601_siren.py @@ -354,7 +354,10 @@ class TuyaSirenGPP_NoSensors(CustomDevice): signature = { # endpoint=1 profile=260 device_type=81 device_version=1 input_clusters=[0, 4, 5, 61184] # output_clusters=[25, 10]> - MODELS_INFO: [("_TZE200_t1blo2bj", "TS0601")], + MODELS_INFO: [ + ("_TZE200_t1blo2bj", "TS0601"), + ("_TZE204_t1blo2bj", "TS0601"), + ], ENDPOINTS: { 1: { PROFILE_ID: zha.PROFILE_ID, From 1eae9c285ec612aa25a2383177feda3611525199 Mon Sep 17 00:00:00 2001 From: Jose Antonio Date: Thu, 9 Feb 2023 00:49:52 +0100 Subject: [PATCH 19/28] Add attribute "motor_mode" to TS130F (#2186) --- zhaquirks/tuya/ts130f.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zhaquirks/tuya/ts130f.py b/zhaquirks/tuya/ts130f.py index 4e7d6a52df..eb0eef3b5d 100644 --- a/zhaquirks/tuya/ts130f.py +++ b/zhaquirks/tuya/ts130f.py @@ -39,10 +39,18 @@ class TuyaWithBacklightOnOffCluster(CustomCluster): attributes = {0x8001: ("backlight_mode", t.enum8)} +class MotorMode(t.enum8): + """Tuya motor mode enum.""" + + STRONG_MOTOR = 0x00 + WEAK_MOTOR = 0x01 + + class TuyaCoveringCluster(CustomCluster, WindowCovering): """TuyaSmartCurtainWindowCoveringCluster: Allow to setup Window covering tuya devices.""" attributes = WindowCovering.attributes.copy() + attributes.update({0x8000: ("motor_mode", MotorMode)}) attributes.update({0xF000: ("tuya_moving_state", t.enum8)}) attributes.update({0xF001: ("calibration", t.enum8)}) attributes.update({0xF002: ("motor_reversal", t.enum8)}) From 31913069b2b8442a5507228db4f9f6b3ed2e47a2 Mon Sep 17 00:00:00 2001 From: Mathieu Velten Date: Wed, 15 Feb 2023 20:35:12 +0100 Subject: [PATCH 20/28] Add up/down long release event triggers for IKEA five button remote (#2123) --- zhaquirks/ikea/fivebtnremote.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/zhaquirks/ikea/fivebtnremote.py b/zhaquirks/ikea/fivebtnremote.py index 5e909ef2e9..9532acca05 100644 --- a/zhaquirks/ikea/fivebtnremote.py +++ b/zhaquirks/ikea/fivebtnremote.py @@ -27,6 +27,8 @@ COMMAND_RELEASE, COMMAND_STEP, COMMAND_STEP_ON_OFF, + COMMAND_STOP, + COMMAND_STOP_ON_OFF, COMMAND_TOGGLE, DEVICE_TYPE, DIM_DOWN, @@ -36,6 +38,7 @@ INPUT_CLUSTERS, LEFT, LONG_PRESS, + LONG_RELEASE, MODELS_INFO, OUTPUT_CLUSTERS, PARAMS, @@ -138,6 +141,11 @@ class IkeaTradfriRemote1(CustomDevice): ENDPOINT_ID: 1, PARAMS: {"move_mode": 0}, }, + (LONG_RELEASE, DIM_UP): { + COMMAND: COMMAND_STOP_ON_OFF, + CLUSTER_ID: 8, + ENDPOINT_ID: 1, + }, (SHORT_PRESS, DIM_DOWN): { COMMAND: COMMAND_STEP, CLUSTER_ID: 8, @@ -150,6 +158,11 @@ class IkeaTradfriRemote1(CustomDevice): ENDPOINT_ID: 1, PARAMS: {"move_mode": 1}, }, + (LONG_RELEASE, DIM_DOWN): { + COMMAND: COMMAND_STOP, + CLUSTER_ID: 8, + ENDPOINT_ID: 1, + }, (SHORT_PRESS, LEFT): { COMMAND: COMMAND_PRESS, CLUSTER_ID: 5, From e12184311f9677b345fc14051a102ef5e5bbe96a Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Thu, 16 Feb 2023 01:48:36 +0100 Subject: [PATCH 21/28] Add "consumer connected" attribute to Xiaomi EU plugs (#2198) --- zhaquirks/xiaomi/aqara/plug_eu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zhaquirks/xiaomi/aqara/plug_eu.py b/zhaquirks/xiaomi/aqara/plug_eu.py index 7c5a38a913..365350e726 100644 --- a/zhaquirks/xiaomi/aqara/plug_eu.py +++ b/zhaquirks/xiaomi/aqara/plug_eu.py @@ -68,6 +68,7 @@ class OppleCluster(XiaomiAqaraE1Cluster): attributes = { 0x0009: ("mode", types.uint8_t, True), 0x0201: ("power_outage_memory", types.Bool, True), + 0x0207: ("consumer_connected", types.Bool, True), } # This only exists on older firmware versions. Newer versions always have the behavior as if this was set to true attr_config = {0x0009: 0x01} From b3b7a58b6921adb0c3da08d5b2ba94a1809afaeb Mon Sep 17 00:00:00 2001 From: MattWestb <49618193+MattWestb@users.noreply.github.com> Date: Thu, 16 Feb 2023 01:54:01 +0100 Subject: [PATCH 22/28] Add timesync response for Siterwell TRVs (#2197) Fixes #2196 --- zhaquirks/tuya/ts0601_trv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zhaquirks/tuya/ts0601_trv.py b/zhaquirks/tuya/ts0601_trv.py index dc626199d5..9cc6c0ad46 100644 --- a/zhaquirks/tuya/ts0601_trv.py +++ b/zhaquirks/tuya/ts0601_trv.py @@ -52,6 +52,8 @@ class SiterwellManufCluster(TuyaManufClusterAttributes): """Manufacturer Specific Cluster of some thermostatic valves.""" + set_time_offset = 1970 + attributes = TuyaManufClusterAttributes.attributes.copy() attributes.update( { From 8122e9283589f4fff585e4cf03892bf87587e345 Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Thu, 16 Feb 2023 20:52:37 +0100 Subject: [PATCH 23/28] Add `_TZE200_m9skfctm` to TuyaSmokeDetector0601 (#2201) --- zhaquirks/tuya/ts0601_smoke.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zhaquirks/tuya/ts0601_smoke.py b/zhaquirks/tuya/ts0601_smoke.py index fc1697d534..f7ec98af8e 100644 --- a/zhaquirks/tuya/ts0601_smoke.py +++ b/zhaquirks/tuya/ts0601_smoke.py @@ -77,9 +77,10 @@ def __init__(self, *args, **kwargs): signature = { MODELS_INFO: [ ("_TZE200_aycxwiau", "TS0601"), + ("_TZE200_dq1mfjug", "TS0601"), + ("_TZE200_m9skfctm", "TS0601"), ("_TZE200_ntcy3xu1", "TS0601"), ("_TZE200_vzekyi4c", "TS0601"), - ("_TZE200_dq1mfjug", "TS0601"), ], ENDPOINTS: { 1: { From f5ed78de1f3ca95169124b1989719ff104b274e6 Mon Sep 17 00:00:00 2001 From: javicalle <31999997+javicalle@users.noreply.github.com> Date: Sat, 18 Feb 2023 22:38:48 +0100 Subject: [PATCH 24/28] Add `TuyaTripleGang_var05` for `TS0013` devices (#2207) * Add `TuyaTripleGang_var05` for `TS0013` devices New `TS0013` quirk variation. Fixes: #2200 --- zhaquirks/tuya/ts001x.py | 98 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/zhaquirks/tuya/ts001x.py b/zhaquirks/tuya/ts001x.py index 43ac156d0b..d77ac55f5b 100644 --- a/zhaquirks/tuya/ts001x.py +++ b/zhaquirks/tuya/ts001x.py @@ -948,3 +948,101 @@ class Tuya_Triple_No_N_Plus(EnchantedDevice, TuyaSwitch): }, }, } + + +class TuyaTripleGang_var05(EnchantedDevice, TuyaSwitch): + """Tuya 3 gang light switch (variation 05).""" + + signature = { + # "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, + # user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, + # mac_capability_flags=, manufacturer_code=4417, maximum_buffer_size=66, + # maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, + # descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, + # *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=False, + # *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)", + MODEL: "TS0013", + ENDPOINTS: { + # "profile_id": 260, "device_type": "0x0100", + # "in_clusters": ["0x0000","0x0003","0x0004","0x0005","0x0006","0xe000","0xe001"], + # "out_clusters": ["0x000a","0x0019"] + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Identify.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + TuyaZBE000Cluster.cluster_id, + TuyaZBExternalSwitchTypeCluster.cluster_id, + ], + OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id], + }, + # "profile_id": 260, "device_type": "0x0100", + # "in_clusters": ["0x0004","0x0005","0x0006"], + # "out_clusters": [] + 2: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT, + INPUT_CLUSTERS: [ + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + ], + OUTPUT_CLUSTERS: [], + }, + # "profile_id": 260, "device_type": "0x0100", + # "in_clusters": ["0x0004","0x0005","0x0006"], + # "out_clusters": [] + 3: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT, + INPUT_CLUSTERS: [ + Groups.cluster_id, + Scenes.cluster_id, + OnOff.cluster_id, + ], + OUTPUT_CLUSTERS: [], + }, + }, + } + replacement = { + ENDPOINTS: { + 1: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT, + INPUT_CLUSTERS: [ + Basic.cluster_id, + Identify.cluster_id, + Groups.cluster_id, + Scenes.cluster_id, + TuyaZBOnOffAttributeCluster, + TuyaZBE000Cluster, + TuyaZBExternalSwitchTypeCluster, + ], + OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id], + }, + 2: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT, + INPUT_CLUSTERS: [ + Groups.cluster_id, + Scenes.cluster_id, + TuyaZBOnOffAttributeCluster, + ], + OUTPUT_CLUSTERS: [], + }, + 3: { + PROFILE_ID: zha.PROFILE_ID, + DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT, + INPUT_CLUSTERS: [ + Groups.cluster_id, + Scenes.cluster_id, + TuyaZBOnOffAttributeCluster, + ], + OUTPUT_CLUSTERS: [], + }, + }, + } From e1b82c206a2b5ca24e11d2c0ac3f79eb81c44e1f Mon Sep 17 00:00:00 2001 From: MattWestb <49618193+MattWestb@users.noreply.github.com> Date: Sun, 19 Feb 2023 19:19:46 +0100 Subject: [PATCH 25/28] Add Tuya Appartme APRM-04-001 TRV (#2210) * Add Appartme APRM-04-001 TRV _TZE200_04yfvweb https://github.com/Koenkk/zigbee-herdsman-converters/pull/5497/files --- zhaquirks/tuya/ts0601_trv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zhaquirks/tuya/ts0601_trv.py b/zhaquirks/tuya/ts0601_trv.py index 9cc6c0ad46..1e9b5bcd84 100644 --- a/zhaquirks/tuya/ts0601_trv.py +++ b/zhaquirks/tuya/ts0601_trv.py @@ -1475,6 +1475,7 @@ class SiterwellGS361_Type2(TuyaThermostat): ("_TZE200_8daqwrsj", "TS0601"), ("_TZE200_czk78ptr", "TS0601"), ("_TZE200_2cs6g9i7", "TS0601"), # Brennenstuhl Zigbee Connect 01 + ("_TZE200_04yfvweb", "TS0601"), # Appartme APRM-04-001 ], ENDPOINTS: { 1: { From 4794cc2f4c1957f517e76a83fee90593813038bb Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Wed, 22 Feb 2023 17:43:19 +0100 Subject: [PATCH 26/28] Add support for linky_status (MOTDETAT "Etat du Linky (From V13)") introduced in firmware v13 for historical mode only (#2213) --- zhaquirks/lixee/zlinky.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zhaquirks/lixee/zlinky.py b/zhaquirks/lixee/zlinky.py index cbfb662d07..a2e29e49dc 100644 --- a/zhaquirks/lixee/zlinky.py +++ b/zhaquirks/lixee/zlinky.py @@ -60,6 +60,8 @@ class ZLinkyTICManufacturerCluster(CustomCluster, ManufacturerSpecificCluster): 0x0007: ("hist_current_exceeding_warning_phase_2", t.uint16_t, True), # Historical mode: ADIR3 "Avertissement de Dépassement D'intensité phase 3" / Uint16 3 car 0x0008: ("hist_current_exceeding_warning_phase_3", t.uint16_t, True), + # Historical mode: MOTDETAT "Etat du Linky (From V13)" / String 6 car + 0x0009: ("linky_status", t.LimitedCharString(6), True), # Historical and Standard mode: "Linky acquisition time (From V7)"" / Uint8 1 car 0x0100: ("linky_acquisition_time", t.uint8_t, True), # Standard mode: LTARF "Libellé tarif fournisseur en cours" / String 16 car From 2318fda777bdbe7238cf224c06012debcd13e6f1 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Wed, 22 Feb 2023 19:46:37 +0300 Subject: [PATCH 27/28] Tuya data refactoring (implicit conversion of `tuya.Data`) (#2017) --- tests/test_tuya.py | 12 ++++++------ zhaquirks/tuya/__init__.py | 37 +++++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/tests/test_tuya.py b/tests/test_tuya.py index 1110addd5a..658ea4199c 100644 --- a/tests/test_tuya.py +++ b/tests/test_tuya.py @@ -276,12 +276,12 @@ def test_ts0121_signature(assert_signature_matches_quirk): async def test_tuya_data_conversion(): """Test tuya conversion from Data to ztype and reverse.""" - assert Data([4, 0, 0, 1, 39]).to_value(t.uint32_t) == 295 - assert Data([4, 0, 0, 0, 220]).to_value(t.uint32_t) == 220 - assert Data([4, 255, 255, 255, 236]).to_value(t.int32s) == -20 - assert Data.from_value(t.uint32_t(295)) == [4, 0, 0, 1, 39] - assert Data.from_value(t.uint32_t(220)) == [4, 0, 0, 0, 220] - assert Data.from_value(t.int32s(-20)) == [4, 255, 255, 255, 236] + assert t.uint32_t(Data([4, 0, 0, 1, 39])) == 295 + assert t.uint32_t(Data([4, 0, 0, 0, 220])) == 220 + assert t.int32s(Data([4, 255, 255, 255, 236])) == -20 + assert Data(t.uint32_t(295)) == [4, 0, 0, 1, 39] + assert Data(t.uint32_t(220)) == [4, 0, 0, 0, 220] + assert Data(t.int32s(-20)) == [4, 255, 255, 255, 236] class TuyaTestManufCluster(TuyaManufClusterAttributes): diff --git a/zhaquirks/tuya/__init__.py b/zhaquirks/tuya/__init__.py index 42ddf75569..12e6d2196f 100644 --- a/zhaquirks/tuya/__init__.py +++ b/zhaquirks/tuya/__init__.py @@ -231,22 +231,35 @@ def __init__(self, value=None, function=0, *args, **kwargs): class Data(t.List, item_type=t.uint8_t): """list of uint8_t.""" - @classmethod - def from_value(cls, value): + def __init__(self, value=None): """Convert from a zigpy typed value to a tuya data payload.""" + if value is None: + super().__init__() + return + if type(value) is list or type(value) is bytes: + super().__init__(value) + return # serialized in little-endian by zigpy - data = cls(value.serialize()) + super().__init__(value.serialize()) # we want big-endian, with length prepended - data.append(len(data)) - data.reverse() - return data + self.append(len(self)) + self.reverse() - def to_value(self, ztype): - """Convert from a tuya data payload to a zigpy typed value.""" + def __int__(self): + """Convert from a tuya data payload to an int typed value.""" # first uint8_t is the length of the remaining data # tuya data is in big endian whereas ztypes use little endian - value, _ = ztype.deserialize(bytes(reversed(self[1:]))) - return value + ints = {1: t.int8s, 2: t.int16s, 3: t.int24s, 4: t.int32s} + return ints[self[0]].deserialize(bytes(reversed(self[1:])))[0] + + def __iter__(self): + """Convert from a tuya data payload to a list typed value.""" + return iter(reversed(self[1:])) + + def serialize(self) -> bytes: + """Overload serialize to avoid prior implicit conversion to list.""" + assert self._item_type is not None + return b"".join([self._item_type(i).serialize() for i in self[:]]) class TuyaDatapointData(t.Struct): @@ -469,7 +482,7 @@ def handle_cluster_request( return ztype = self.attributes[tuya_cmd].type - zvalue = tuya_data.to_value(ztype) + zvalue = ztype(tuya_data) self._update_attribute(tuya_cmd, zvalue) def read_attributes( @@ -492,7 +505,7 @@ async def write_attributes(self, attributes, manufacturer=None): cmd_payload.tsn = self.endpoint.device.application.get_sequence() cmd_payload.command_id = record.attrid cmd_payload.function = 0 - cmd_payload.data = Data.from_value(record.value.value) + cmd_payload.data = record.value.value await super().command( TUYA_SET_DATA, From 00d128e0cae0891a4ca73e7eb85865d5abfd9eb8 Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Wed, 22 Feb 2023 12:55:43 -0500 Subject: [PATCH 28/28] bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 94830a5c0a..9f027d882e 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import find_packages, setup -VERSION = "0.0.92" +VERSION = "0.0.93" setup(