From 3d1eba4d6a0da34dbb072661c348b986d61e6c0e Mon Sep 17 00:00:00 2001 From: martyn-vesternet Date: Fri, 7 Nov 2025 16:01:53 +0000 Subject: [PATCH 01/10] Add Candeo SR5BR 5-button remote with rotating dial Adds support for Candeo SR5BR 5-button remote with rotating dial --- .../scene_switch_remote_5_button_rotary.py | 206 ++++++++++++++++++ zhaquirks/const.py | 2 + 2 files changed, 208 insertions(+) create mode 100644 zhaquirks/candeo/scene_switch_remote_5_button_rotary.py diff --git a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py new file mode 100644 index 0000000000..7ac6973b09 --- /dev/null +++ b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py @@ -0,0 +1,206 @@ +"""Candeo c-zb-sr5br 5-button remote with rotating dial.""" + +from zigpy.quirks.v2 import QuirkBuilder + +from zigpy.zcl.clusters.general import Ota + +from candeo import CANDEO + +from zhaquirks.const import ( + ARGS, + BUTTON, + BUTTON_1, + BUTTON_2, + BUTTON_3, + BUTTON_4, + BUTTON_CENTRE, + COMMAND, + COMMAND_CONTINUED_ROTATING, + COMMAND_DOUBLE, + COMMAND_HOLD, + COMMAND_PRESS, + COMMAND_RELEASE, + COMMAND_STARTED_ROTATING, + COMMAND_STOPPED_ROTATING, + CONTINUED_ROTATING, + DOUBLE_PRESS, + ENDPOINT_ID, + LEFT, + LONG_PRESS, + LONG_RELEASE, + RIGHT, + ROTATED, + SHORT_PRESS, + STARTED_ROTATING, + STOPPED_ROTATING_WITH_DIRECTION, + ZHA_SEND_EVENT +) + +from typing import Optional, Union, Final +from zigpy.zcl import foundation +from zigpy.quirks import CustomCluster +import zigpy.types as t +from zigpy.zcl.foundation import BaseCommandDefs, ZCLCommandDef + + +class CandeoSceneSwitchRemoteMessageType(t.enum8): + """Candeo Scene Switch Remote Message Type.""" + + button_press = 0x01 + ring_rotation = 0x03 + + +class CandeoSceneSwitchRemoteButtonNumberMap(t.enum8): + """Candeo Scene Switch Remote Button Number Map.""" + + button_1 = 0x01 + button_2 = 0x02 + button_3 = 0x04 + button_4 = 0x08 + button_centre = 0x10 + + +class CandeoSceneSwitchRemoteButtonActionMap(t.enum8): + """Candeo Scene Switch Remote Button Action Map.""" + + press = 0x01 + double_press = 0x02 + hold = 0x03 + release = 0x04 + + +class CandeoSceneSwitchRemoteRingDirectionMap(t.enum8): + """Candeo Scene Switch Remote Ring Direction Map.""" + + right = 0x01 + left = 0x02 + + +class CandeoSceneSwitchRemoteRingActionMap(t.enum8): + """Candeo Scene Switch Remote Ring Action Map.""" + + started_rotating = 0x01 + stopped_rotating = 0x02 + continued_rotating = 0x03 + + +class CandeoSceneSwitchRemoteClusterCommand(t.Struct): + """CandeoSceneSwitchRemoteClusterCommand.""" + + message_type: CandeoSceneSwitchRemoteMessageType + field_1: t.uint8_t + field_2: t.uint8_t + field_3: t.uint8_t + + +class CandeoSceneSwitchRemoteCluster(CustomCluster): + """CandeoSceneSwitchRemoteCluster: fire events corresponding to button press or ring rotation.""" + + cluster_id: Final[t.uint16_t] = 0xFF03 + name = "CandeoSceneSwitchRemoteCluster_Cluster" + ep_attribute = "CandeoSceneSwitchRemoteCluster_Cluster" + + + class ServerCommandDefs(BaseCommandDefs): + """overwrite ServerCommandDefs.""" + + candeo_scene_switch_remote: Final = ZCLCommandDef( + id=0x01, + schema=CandeoSceneSwitchRemoteClusterCommand, + is_manufacturer_specific=True, + ) + + async def apply_custom_configuration(self, *args, **kwargs): + """apply custom configuration to bind cluster.""" + await self.bind() + + def __init__( + self, + *args, + **kwargs + ): + """__init___""" + self.last_tsn = -1 + self.previous_rotation_direction = "unknown" + self.previous_rotation_event = COMMAND_STOPPED_ROTATING + super().__init__(*args, **kwargs) + + def handle_cluster_request( + self, + hdr: foundation.ZCLHeader, + args: tuple[CandeoSceneSwitchRemoteClusterCommand], + *, + dst_addressing: Optional[ + Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] + ] = None, + ): + """overwrite handle_cluster_request to custom process this cluster.""" + if not hdr.frame_control.disable_default_response: + self.send_default_rsp(hdr, status=foundation.Status.SUCCESS) + if hdr.tsn == self.last_tsn: + return + self.last_tsn = hdr.tsn + if hdr.command_id == self.ServerCommandDefs.candeo_scene_switch_remote.id and CandeoSceneSwitchRemoteMessageType(args.message_type) and args.field_1 is not None and args.field_2 is not None and args.field_3 is not None: + if args.message_type == CandeoSceneSwitchRemoteMessageType.button_press and CandeoSceneSwitchRemoteButtonNumberMap(args.field_2) and CandeoSceneSwitchRemoteButtonActionMap(args.field_3): + button_number = CandeoSceneSwitchRemoteButtonNumberMap(args.field_2).name + button_action = CandeoSceneSwitchRemoteButtonActionMap(args.field_3).name + self.listener_event(ZHA_SEND_EVENT, button_action, {BUTTON: button_number}) + elif args.message_type == CandeoSceneSwitchRemoteMessageType.ring_rotation and CandeoSceneSwitchRemoteRingActionMap(args.field_2): + ring_action = CandeoSceneSwitchRemoteRingActionMap(args.field_2).name + if ring_action == COMMAND_STOPPED_ROTATING: + if self.previous_rotation_direction != "unknown": + self.listener_event(ZHA_SEND_EVENT, COMMAND_STOPPED_ROTATING, {ROTATED: self.previous_rotation_direction}) + self.previous_rotation_event = COMMAND_STOPPED_ROTATING + elif CandeoSceneSwitchRemoteRingDirectionMap(args.field_1): + ring_direction = CandeoSceneSwitchRemoteRingDirectionMap(args.field_1).name + ring_clicks = args.field_3 + if self.previous_rotation_event == COMMAND_STOPPED_ROTATING: + self.listener_event(ZHA_SEND_EVENT, COMMAND_STARTED_ROTATING, {ROTATED: ring_direction}) + self.previous_rotation_event = COMMAND_STARTED_ROTATING + if ring_clicks > 1: + for x in range(1, ring_clicks): + self.listener_event(ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, {ROTATED: ring_direction}) + self.previous_rotation_event = COMMAND_CONTINUED_ROTATING + elif self.previous_rotation_event == COMMAND_STARTED_ROTATING or self.previous_rotation_event == COMMAND_CONTINUED_ROTATING: + self.listener_event(ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, {ROTATED: ring_direction}) + if ring_clicks > 1: + for x in range(1, ring_clicks): + self.listener_event(ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, {ROTATED: ring_direction}) + self.previous_rotation_event = COMMAND_CONTINUED_ROTATING + self.previous_rotation_direction = ring_direction + +( + QuirkBuilder(CANDEO, "C-ZB-SR5BR") + .replaces(CandeoSceneSwitchRemoteCluster) + .device_automation_triggers( + { + (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_1}}, + (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_1}}, + (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_1}}, + (LONG_RELEASE, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_1}}, + (SHORT_PRESS, BUTTON_2): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_2}}, + (DOUBLE_PRESS, BUTTON_2): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_2}}, + (LONG_PRESS, BUTTON_2): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_2}}, + (LONG_RELEASE, BUTTON_2): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_2}}, + (SHORT_PRESS, BUTTON_3): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_3}}, + (DOUBLE_PRESS, BUTTON_3): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_3}}, + (LONG_PRESS, BUTTON_3): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_3}}, + (LONG_RELEASE, BUTTON_3): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_3}}, + (SHORT_PRESS, BUTTON_4): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_4}}, + (DOUBLE_PRESS, BUTTON_4): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_4}}, + (LONG_PRESS, BUTTON_4): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_4}}, + (LONG_RELEASE, BUTTON_4): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_4}}, + (SHORT_PRESS, BUTTON_CENTRE): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_CENTRE}}, + (DOUBLE_PRESS, BUTTON_CENTRE): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_CENTRE}}, + (LONG_PRESS, BUTTON_CENTRE): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_CENTRE}}, + (LONG_RELEASE, BUTTON_CENTRE): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_CENTRE}}, + (STARTED_ROTATING, LEFT): {ENDPOINT_ID: 1, COMMAND: COMMAND_STARTED_ROTATING, ARGS: {ROTATED: LEFT}}, + (CONTINUED_ROTATING, LEFT): {ENDPOINT_ID: 1, COMMAND: COMMAND_CONTINUED_ROTATING, ARGS: {ROTATED: LEFT}}, + (STOPPED_ROTATING_WITH_DIRECTION, LEFT): {ENDPOINT_ID: 1, COMMAND: COMMAND_STOPPED_ROTATING, ARGS: {ROTATED: LEFT}}, + (STARTED_ROTATING, RIGHT): {ENDPOINT_ID: 1, COMMAND: COMMAND_STARTED_ROTATING, ARGS: {ROTATED: RIGHT}}, + (CONTINUED_ROTATING, RIGHT): {ENDPOINT_ID: 1, COMMAND: COMMAND_CONTINUED_ROTATING, ARGS: {ROTATED: RIGHT}}, + (STOPPED_ROTATING_WITH_DIRECTION, RIGHT): {ENDPOINT_ID: 1, COMMAND: COMMAND_STOPPED_ROTATING, ARGS: {ROTATED: RIGHT}}, + } + ) + .add_to_registry() +) diff --git a/zhaquirks/const.py b/zhaquirks/const.py index 12bd29a5fc..7faddd2e51 100644 --- a/zhaquirks/const.py +++ b/zhaquirks/const.py @@ -25,6 +25,7 @@ BUTTON_4 = "button_4" BUTTON_5 = "button_5" BUTTON_6 = "button_6" +BUTTON_CENTRE = "button_centre" CLICK_TYPE = "click_type" CLOSE = "close" CLUSTER_COMMAND = "cluster_command" @@ -133,6 +134,7 @@ STARTED_ROTATING = "rotary_knob_started_rotating" CONTINUED_ROTATING = "rotary_knob_continued_rotating" STOPPED_ROTATING = "rotary_knob_stopped_rotating" +STOPPED_ROTATING_WITH_DIRECTION = "rotary_knob_stopped_rotating_with_direction" class BatterySize(t.enum8): From ba0e16142d21160811c2ac635f51364b7847c861 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:16:59 +0000 Subject: [PATCH 02/10] Apply pre-commit auto fixes --- .../scene_switch_remote_5_button_rotary.py | 289 +++++++++++++----- 1 file changed, 215 insertions(+), 74 deletions(-) diff --git a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py index 7ac6973b09..7018e2670c 100644 --- a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py +++ b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py @@ -1,17 +1,20 @@ """Candeo c-zb-sr5br 5-button remote with rotating dial.""" -from zigpy.quirks.v2 import QuirkBuilder - -from zigpy.zcl.clusters.general import Ota +from typing import Final, Optional, Union from candeo import CANDEO +from zigpy.quirks import CustomCluster +from zigpy.quirks.v2 import QuirkBuilder +import zigpy.types as t +from zigpy.zcl import foundation +from zigpy.zcl.foundation import BaseCommandDefs, ZCLCommandDef from zhaquirks.const import ( ARGS, BUTTON, - BUTTON_1, - BUTTON_2, - BUTTON_3, + BUTTON_1, + BUTTON_2, + BUTTON_3, BUTTON_4, BUTTON_CENTRE, COMMAND, @@ -32,22 +35,16 @@ ROTATED, SHORT_PRESS, STARTED_ROTATING, - STOPPED_ROTATING_WITH_DIRECTION, - ZHA_SEND_EVENT + STOPPED_ROTATING_WITH_DIRECTION, + ZHA_SEND_EVENT, ) -from typing import Optional, Union, Final -from zigpy.zcl import foundation -from zigpy.quirks import CustomCluster -import zigpy.types as t -from zigpy.zcl.foundation import BaseCommandDefs, ZCLCommandDef - class CandeoSceneSwitchRemoteMessageType(t.enum8): """Candeo Scene Switch Remote Message Type.""" button_press = 0x01 - ring_rotation = 0x03 + ring_rotation = 0x03 class CandeoSceneSwitchRemoteButtonNumberMap(t.enum8): @@ -94,12 +91,11 @@ class CandeoSceneSwitchRemoteClusterCommand(t.Struct): class CandeoSceneSwitchRemoteCluster(CustomCluster): - """CandeoSceneSwitchRemoteCluster: fire events corresponding to button press or ring rotation.""" + """CandeoSceneSwitchRemoteCluster: fire events corresponding to button press or ring rotation.""" cluster_id: Final[t.uint16_t] = 0xFF03 name = "CandeoSceneSwitchRemoteCluster_Cluster" - ep_attribute = "CandeoSceneSwitchRemoteCluster_Cluster" - + ep_attribute = "CandeoSceneSwitchRemoteCluster_Cluster" class ServerCommandDefs(BaseCommandDefs): """overwrite ServerCommandDefs.""" @@ -109,16 +105,12 @@ class ServerCommandDefs(BaseCommandDefs): schema=CandeoSceneSwitchRemoteClusterCommand, is_manufacturer_specific=True, ) - + async def apply_custom_configuration(self, *args, **kwargs): - """apply custom configuration to bind cluster.""" + """Apply custom configuration to bind cluster.""" await self.bind() - def __init__( - self, - *args, - **kwargs - ): + def __init__(self, *args, **kwargs): """__init___""" self.last_tsn = -1 self.previous_rotation_direction = "unknown" @@ -134,72 +126,221 @@ def handle_cluster_request( Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] ] = None, ): - """overwrite handle_cluster_request to custom process this cluster.""" - if not hdr.frame_control.disable_default_response: + """Overwrite handle_cluster_request to custom process this cluster.""" + if not hdr.frame_control.disable_default_response: self.send_default_rsp(hdr, status=foundation.Status.SUCCESS) - if hdr.tsn == self.last_tsn: + if hdr.tsn == self.last_tsn: return self.last_tsn = hdr.tsn - if hdr.command_id == self.ServerCommandDefs.candeo_scene_switch_remote.id and CandeoSceneSwitchRemoteMessageType(args.message_type) and args.field_1 is not None and args.field_2 is not None and args.field_3 is not None: - if args.message_type == CandeoSceneSwitchRemoteMessageType.button_press and CandeoSceneSwitchRemoteButtonNumberMap(args.field_2) and CandeoSceneSwitchRemoteButtonActionMap(args.field_3): - button_number = CandeoSceneSwitchRemoteButtonNumberMap(args.field_2).name - button_action = CandeoSceneSwitchRemoteButtonActionMap(args.field_3).name - self.listener_event(ZHA_SEND_EVENT, button_action, {BUTTON: button_number}) - elif args.message_type == CandeoSceneSwitchRemoteMessageType.ring_rotation and CandeoSceneSwitchRemoteRingActionMap(args.field_2): - ring_action = CandeoSceneSwitchRemoteRingActionMap(args.field_2).name - if ring_action == COMMAND_STOPPED_ROTATING: - if self.previous_rotation_direction != "unknown": - self.listener_event(ZHA_SEND_EVENT, COMMAND_STOPPED_ROTATING, {ROTATED: self.previous_rotation_direction}) + if ( + hdr.command_id == self.ServerCommandDefs.candeo_scene_switch_remote.id + and CandeoSceneSwitchRemoteMessageType(args.message_type) + and args.field_1 is not None + and args.field_2 is not None + and args.field_3 is not None + ): + if ( + args.message_type == CandeoSceneSwitchRemoteMessageType.button_press + and CandeoSceneSwitchRemoteButtonNumberMap(args.field_2) + and CandeoSceneSwitchRemoteButtonActionMap(args.field_3) + ): + button_number = CandeoSceneSwitchRemoteButtonNumberMap( + args.field_2 + ).name + button_action = CandeoSceneSwitchRemoteButtonActionMap( + args.field_3 + ).name + self.listener_event( + ZHA_SEND_EVENT, button_action, {BUTTON: button_number} + ) + elif ( + args.message_type == CandeoSceneSwitchRemoteMessageType.ring_rotation + and CandeoSceneSwitchRemoteRingActionMap(args.field_2) + ): + ring_action = CandeoSceneSwitchRemoteRingActionMap(args.field_2).name + if ring_action == COMMAND_STOPPED_ROTATING: + if self.previous_rotation_direction != "unknown": + self.listener_event( + ZHA_SEND_EVENT, + COMMAND_STOPPED_ROTATING, + {ROTATED: self.previous_rotation_direction}, + ) self.previous_rotation_event = COMMAND_STOPPED_ROTATING elif CandeoSceneSwitchRemoteRingDirectionMap(args.field_1): - ring_direction = CandeoSceneSwitchRemoteRingDirectionMap(args.field_1).name + ring_direction = CandeoSceneSwitchRemoteRingDirectionMap( + args.field_1 + ).name ring_clicks = args.field_3 - if self.previous_rotation_event == COMMAND_STOPPED_ROTATING: - self.listener_event(ZHA_SEND_EVENT, COMMAND_STARTED_ROTATING, {ROTATED: ring_direction}) + if self.previous_rotation_event == COMMAND_STOPPED_ROTATING: + self.listener_event( + ZHA_SEND_EVENT, + COMMAND_STARTED_ROTATING, + {ROTATED: ring_direction}, + ) self.previous_rotation_event = COMMAND_STARTED_ROTATING if ring_clicks > 1: - for x in range(1, ring_clicks): - self.listener_event(ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, {ROTATED: ring_direction}) + for x in range(1, ring_clicks): + self.listener_event( + ZHA_SEND_EVENT, + COMMAND_CONTINUED_ROTATING, + {ROTATED: ring_direction}, + ) self.previous_rotation_event = COMMAND_CONTINUED_ROTATING - elif self.previous_rotation_event == COMMAND_STARTED_ROTATING or self.previous_rotation_event == COMMAND_CONTINUED_ROTATING: - self.listener_event(ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, {ROTATED: ring_direction}) + elif ( + self.previous_rotation_event == COMMAND_STARTED_ROTATING + or self.previous_rotation_event == COMMAND_CONTINUED_ROTATING + ): + self.listener_event( + ZHA_SEND_EVENT, + COMMAND_CONTINUED_ROTATING, + {ROTATED: ring_direction}, + ) if ring_clicks > 1: - for x in range(1, ring_clicks): - self.listener_event(ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, {ROTATED: ring_direction}) + for x in range(1, ring_clicks): + self.listener_event( + ZHA_SEND_EVENT, + COMMAND_CONTINUED_ROTATING, + {ROTATED: ring_direction}, + ) self.previous_rotation_event = COMMAND_CONTINUED_ROTATING - self.previous_rotation_direction = ring_direction + self.previous_rotation_direction = ring_direction + ( QuirkBuilder(CANDEO, "C-ZB-SR5BR") .replaces(CandeoSceneSwitchRemoteCluster) .device_automation_triggers( { - (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_1}}, - (DOUBLE_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_1}}, - (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_1}}, - (LONG_RELEASE, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_1}}, - (SHORT_PRESS, BUTTON_2): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_2}}, - (DOUBLE_PRESS, BUTTON_2): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_2}}, - (LONG_PRESS, BUTTON_2): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_2}}, - (LONG_RELEASE, BUTTON_2): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_2}}, - (SHORT_PRESS, BUTTON_3): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_3}}, - (DOUBLE_PRESS, BUTTON_3): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_3}}, - (LONG_PRESS, BUTTON_3): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_3}}, - (LONG_RELEASE, BUTTON_3): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_3}}, - (SHORT_PRESS, BUTTON_4): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_4}}, - (DOUBLE_PRESS, BUTTON_4): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_4}}, - (LONG_PRESS, BUTTON_4): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_4}}, - (LONG_RELEASE, BUTTON_4): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_4}}, - (SHORT_PRESS, BUTTON_CENTRE): {ENDPOINT_ID: 1, COMMAND: COMMAND_PRESS, ARGS: {BUTTON: BUTTON_CENTRE}}, - (DOUBLE_PRESS, BUTTON_CENTRE): {ENDPOINT_ID: 1, COMMAND: COMMAND_DOUBLE, ARGS: {BUTTON: BUTTON_CENTRE}}, - (LONG_PRESS, BUTTON_CENTRE): {ENDPOINT_ID: 1, COMMAND: COMMAND_HOLD, ARGS: {BUTTON: BUTTON_CENTRE}}, - (LONG_RELEASE, BUTTON_CENTRE): {ENDPOINT_ID: 1, COMMAND: COMMAND_RELEASE, ARGS: {BUTTON: BUTTON_CENTRE}}, - (STARTED_ROTATING, LEFT): {ENDPOINT_ID: 1, COMMAND: COMMAND_STARTED_ROTATING, ARGS: {ROTATED: LEFT}}, - (CONTINUED_ROTATING, LEFT): {ENDPOINT_ID: 1, COMMAND: COMMAND_CONTINUED_ROTATING, ARGS: {ROTATED: LEFT}}, - (STOPPED_ROTATING_WITH_DIRECTION, LEFT): {ENDPOINT_ID: 1, COMMAND: COMMAND_STOPPED_ROTATING, ARGS: {ROTATED: LEFT}}, - (STARTED_ROTATING, RIGHT): {ENDPOINT_ID: 1, COMMAND: COMMAND_STARTED_ROTATING, ARGS: {ROTATED: RIGHT}}, - (CONTINUED_ROTATING, RIGHT): {ENDPOINT_ID: 1, COMMAND: COMMAND_CONTINUED_ROTATING, ARGS: {ROTATED: RIGHT}}, - (STOPPED_ROTATING_WITH_DIRECTION, RIGHT): {ENDPOINT_ID: 1, COMMAND: COMMAND_STOPPED_ROTATING, ARGS: {ROTATED: RIGHT}}, + (SHORT_PRESS, BUTTON_1): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_PRESS, + ARGS: {BUTTON: BUTTON_1}, + }, + (DOUBLE_PRESS, BUTTON_1): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_DOUBLE, + ARGS: {BUTTON: BUTTON_1}, + }, + (LONG_PRESS, BUTTON_1): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_HOLD, + ARGS: {BUTTON: BUTTON_1}, + }, + (LONG_RELEASE, BUTTON_1): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_RELEASE, + ARGS: {BUTTON: BUTTON_1}, + }, + (SHORT_PRESS, BUTTON_2): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_PRESS, + ARGS: {BUTTON: BUTTON_2}, + }, + (DOUBLE_PRESS, BUTTON_2): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_DOUBLE, + ARGS: {BUTTON: BUTTON_2}, + }, + (LONG_PRESS, BUTTON_2): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_HOLD, + ARGS: {BUTTON: BUTTON_2}, + }, + (LONG_RELEASE, BUTTON_2): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_RELEASE, + ARGS: {BUTTON: BUTTON_2}, + }, + (SHORT_PRESS, BUTTON_3): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_PRESS, + ARGS: {BUTTON: BUTTON_3}, + }, + (DOUBLE_PRESS, BUTTON_3): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_DOUBLE, + ARGS: {BUTTON: BUTTON_3}, + }, + (LONG_PRESS, BUTTON_3): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_HOLD, + ARGS: {BUTTON: BUTTON_3}, + }, + (LONG_RELEASE, BUTTON_3): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_RELEASE, + ARGS: {BUTTON: BUTTON_3}, + }, + (SHORT_PRESS, BUTTON_4): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_PRESS, + ARGS: {BUTTON: BUTTON_4}, + }, + (DOUBLE_PRESS, BUTTON_4): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_DOUBLE, + ARGS: {BUTTON: BUTTON_4}, + }, + (LONG_PRESS, BUTTON_4): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_HOLD, + ARGS: {BUTTON: BUTTON_4}, + }, + (LONG_RELEASE, BUTTON_4): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_RELEASE, + ARGS: {BUTTON: BUTTON_4}, + }, + (SHORT_PRESS, BUTTON_CENTRE): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_PRESS, + ARGS: {BUTTON: BUTTON_CENTRE}, + }, + (DOUBLE_PRESS, BUTTON_CENTRE): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_DOUBLE, + ARGS: {BUTTON: BUTTON_CENTRE}, + }, + (LONG_PRESS, BUTTON_CENTRE): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_HOLD, + ARGS: {BUTTON: BUTTON_CENTRE}, + }, + (LONG_RELEASE, BUTTON_CENTRE): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_RELEASE, + ARGS: {BUTTON: BUTTON_CENTRE}, + }, + (STARTED_ROTATING, LEFT): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_STARTED_ROTATING, + ARGS: {ROTATED: LEFT}, + }, + (CONTINUED_ROTATING, LEFT): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_CONTINUED_ROTATING, + ARGS: {ROTATED: LEFT}, + }, + (STOPPED_ROTATING_WITH_DIRECTION, LEFT): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_STOPPED_ROTATING, + ARGS: {ROTATED: LEFT}, + }, + (STARTED_ROTATING, RIGHT): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_STARTED_ROTATING, + ARGS: {ROTATED: RIGHT}, + }, + (CONTINUED_ROTATING, RIGHT): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_CONTINUED_ROTATING, + ARGS: {ROTATED: RIGHT}, + }, + (STOPPED_ROTATING_WITH_DIRECTION, RIGHT): { + ENDPOINT_ID: 1, + COMMAND: COMMAND_STOPPED_ROTATING, + ARGS: {ROTATED: RIGHT}, + }, } ) .add_to_registry() From 529ff59b87f169db3e9244ab8fdf7daf4a1a5afd Mon Sep 17 00:00:00 2001 From: martyn-vesternet Date: Fri, 7 Nov 2025 16:24:44 +0000 Subject: [PATCH 03/10] Update scene_switch_remote_5_button_rotary.py Implement CI code improvement suggestions --- zhaquirks/candeo/scene_switch_remote_5_button_rotary.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py index 7018e2670c..09d1851125 100644 --- a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py +++ b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py @@ -111,7 +111,7 @@ async def apply_custom_configuration(self, *args, **kwargs): await self.bind() def __init__(self, *args, **kwargs): - """__init___""" + """__init___.""" self.last_tsn = -1 self.previous_rotation_direction = "unknown" self.previous_rotation_event = COMMAND_STOPPED_ROTATING @@ -179,7 +179,7 @@ def handle_cluster_request( ) self.previous_rotation_event = COMMAND_STARTED_ROTATING if ring_clicks > 1: - for x in range(1, ring_clicks): + for _x in range(1, ring_clicks): self.listener_event( ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, @@ -187,8 +187,7 @@ def handle_cluster_request( ) self.previous_rotation_event = COMMAND_CONTINUED_ROTATING elif ( - self.previous_rotation_event == COMMAND_STARTED_ROTATING - or self.previous_rotation_event == COMMAND_CONTINUED_ROTATING + self.previous_rotation_event in {COMMAND_STARTED_ROTATING, COMMAND_CONTINUED_ROTATING} ): self.listener_event( ZHA_SEND_EVENT, @@ -196,7 +195,7 @@ def handle_cluster_request( {ROTATED: ring_direction}, ) if ring_clicks > 1: - for x in range(1, ring_clicks): + for _x in range(1, ring_clicks): self.listener_event( ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, From 879c667a70d0df9fa5186de33bc81571a049ae84 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:24:55 +0000 Subject: [PATCH 04/10] Apply pre-commit auto fixes --- zhaquirks/candeo/scene_switch_remote_5_button_rotary.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py index 09d1851125..f8777a37d8 100644 --- a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py +++ b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py @@ -186,9 +186,10 @@ def handle_cluster_request( {ROTATED: ring_direction}, ) self.previous_rotation_event = COMMAND_CONTINUED_ROTATING - elif ( - self.previous_rotation_event in {COMMAND_STARTED_ROTATING, COMMAND_CONTINUED_ROTATING} - ): + elif self.previous_rotation_event in { + COMMAND_STARTED_ROTATING, + COMMAND_CONTINUED_ROTATING, + }: self.listener_event( ZHA_SEND_EVENT, COMMAND_CONTINUED_ROTATING, From 09e2028bb59436ff7e38ffbfabf63e2f5d115310 Mon Sep 17 00:00:00 2001 From: martyn-vesternet Date: Fri, 7 Nov 2025 16:29:56 +0000 Subject: [PATCH 05/10] Update scene_switch_remote_5_button_rotary.py Add missing import path --- zhaquirks/candeo/scene_switch_remote_5_button_rotary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py index f8777a37d8..f9f13d2017 100644 --- a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py +++ b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py @@ -2,7 +2,7 @@ from typing import Final, Optional, Union -from candeo import CANDEO +from zhaquirks.candeo import CANDEO from zigpy.quirks import CustomCluster from zigpy.quirks.v2 import QuirkBuilder import zigpy.types as t From 69f472e60f0caddc9960970c503c7796ca5bc82f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:31:56 +0000 Subject: [PATCH 06/10] Apply pre-commit auto fixes --- zhaquirks/candeo/scene_switch_remote_5_button_rotary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py index f9f13d2017..199d7e398c 100644 --- a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py +++ b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py @@ -2,13 +2,13 @@ from typing import Final, Optional, Union -from zhaquirks.candeo import CANDEO from zigpy.quirks import CustomCluster from zigpy.quirks.v2 import QuirkBuilder import zigpy.types as t from zigpy.zcl import foundation from zigpy.zcl.foundation import BaseCommandDefs, ZCLCommandDef +from zhaquirks.candeo import CANDEO from zhaquirks.const import ( ARGS, BUTTON, From faa82a28ae12d4a3427716acd401ef3ca8f9b37d Mon Sep 17 00:00:00 2001 From: martyn-vesternet Date: Tue, 11 Nov 2025 17:03:21 +0000 Subject: [PATCH 07/10] Update scene_switch_remote_5_button_rotary.py Updated some code while we are working on unit tests --- .../candeo/scene_switch_remote_5_button_rotary.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py index 199d7e398c..5286dbedbc 100644 --- a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py +++ b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py @@ -61,7 +61,7 @@ class CandeoSceneSwitchRemoteButtonActionMap(t.enum8): """Candeo Scene Switch Remote Button Action Map.""" press = 0x01 - double_press = 0x02 + double = 0x02 hold = 0x03 release = 0x04 @@ -134,15 +134,15 @@ def handle_cluster_request( self.last_tsn = hdr.tsn if ( hdr.command_id == self.ServerCommandDefs.candeo_scene_switch_remote.id - and CandeoSceneSwitchRemoteMessageType(args.message_type) + and args.message_type is not None and args.field_1 is not None and args.field_2 is not None and args.field_3 is not None ): if ( args.message_type == CandeoSceneSwitchRemoteMessageType.button_press - and CandeoSceneSwitchRemoteButtonNumberMap(args.field_2) - and CandeoSceneSwitchRemoteButtonActionMap(args.field_3) + and args.field_2 in CandeoSceneSwitchRemoteButtonNumberMap._value2member_map_ + and args.field_3 in CandeoSceneSwitchRemoteButtonActionMap._value2member_map_ ): button_number = CandeoSceneSwitchRemoteButtonNumberMap( args.field_2 @@ -155,7 +155,7 @@ def handle_cluster_request( ) elif ( args.message_type == CandeoSceneSwitchRemoteMessageType.ring_rotation - and CandeoSceneSwitchRemoteRingActionMap(args.field_2) + and args.field_2 in CandeoSceneSwitchRemoteRingActionMap._value2member_map_ ): ring_action = CandeoSceneSwitchRemoteRingActionMap(args.field_2).name if ring_action == COMMAND_STOPPED_ROTATING: @@ -166,7 +166,7 @@ def handle_cluster_request( {ROTATED: self.previous_rotation_direction}, ) self.previous_rotation_event = COMMAND_STOPPED_ROTATING - elif CandeoSceneSwitchRemoteRingDirectionMap(args.field_1): + elif args.field_1 in CandeoSceneSwitchRemoteRingDirectionMap._value2member_map_: ring_direction = CandeoSceneSwitchRemoteRingDirectionMap( args.field_1 ).name From 7e2a62d614b1a52b1ddf538c768f2626c435d029 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 17:03:32 +0000 Subject: [PATCH 08/10] Apply pre-commit auto fixes --- .../candeo/scene_switch_remote_5_button_rotary.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py index 5286dbedbc..9b2f0f7176 100644 --- a/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py +++ b/zhaquirks/candeo/scene_switch_remote_5_button_rotary.py @@ -141,8 +141,10 @@ def handle_cluster_request( ): if ( args.message_type == CandeoSceneSwitchRemoteMessageType.button_press - and args.field_2 in CandeoSceneSwitchRemoteButtonNumberMap._value2member_map_ - and args.field_3 in CandeoSceneSwitchRemoteButtonActionMap._value2member_map_ + and args.field_2 + in CandeoSceneSwitchRemoteButtonNumberMap._value2member_map_ + and args.field_3 + in CandeoSceneSwitchRemoteButtonActionMap._value2member_map_ ): button_number = CandeoSceneSwitchRemoteButtonNumberMap( args.field_2 @@ -155,7 +157,8 @@ def handle_cluster_request( ) elif ( args.message_type == CandeoSceneSwitchRemoteMessageType.ring_rotation - and args.field_2 in CandeoSceneSwitchRemoteRingActionMap._value2member_map_ + and args.field_2 + in CandeoSceneSwitchRemoteRingActionMap._value2member_map_ ): ring_action = CandeoSceneSwitchRemoteRingActionMap(args.field_2).name if ring_action == COMMAND_STOPPED_ROTATING: @@ -166,7 +169,10 @@ def handle_cluster_request( {ROTATED: self.previous_rotation_direction}, ) self.previous_rotation_event = COMMAND_STOPPED_ROTATING - elif args.field_1 in CandeoSceneSwitchRemoteRingDirectionMap._value2member_map_: + elif ( + args.field_1 + in CandeoSceneSwitchRemoteRingDirectionMap._value2member_map_ + ): ring_direction = CandeoSceneSwitchRemoteRingDirectionMap( args.field_1 ).name From 5b12841b98f09d7b078529e5b32bad26fc398a1d Mon Sep 17 00:00:00 2001 From: martyn-vesternet Date: Wed, 12 Nov 2025 13:15:00 +0000 Subject: [PATCH 09/10] Update test_candeo.py Added unit tests for the Candeo SR5BR 5-button remote with rotating dial --- tests/test_candeo.py | 432 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 431 insertions(+), 1 deletion(-) diff --git a/tests/test_candeo.py b/tests/test_candeo.py index 2494d5173a..3eb3f6dc31 100644 --- a/tests/test_candeo.py +++ b/tests/test_candeo.py @@ -1,15 +1,56 @@ """Tests for Candeo.""" import pytest + +from unittest import mock + +from zigpy.zcl import foundation + from zigpy.zcl.clusters.measurement import IlluminanceMeasurement from tests.common import ClusterListener import zhaquirks + +import zigpy.types as t + from zhaquirks.candeo import CANDEO -zhaquirks.setup() +from zhaquirks.candeo.scene_switch_remote_5_button_rotary import ( + CandeoSceneSwitchRemoteCluster, + CandeoSceneSwitchRemoteClusterCommand, + CandeoSceneSwitchRemoteMessageType, + CandeoSceneSwitchRemoteButtonNumberMap, + CandeoSceneSwitchRemoteButtonActionMap, + CandeoSceneSwitchRemoteRingDirectionMap, + CandeoSceneSwitchRemoteRingActionMap, +) +from zhaquirks.const import ( + BUTTON, + BUTTON_1, + BUTTON_2, + BUTTON_3, + BUTTON_4, + BUTTON_CENTRE, + COMMAND_CONTINUED_ROTATING, + COMMAND_DOUBLE, + COMMAND_HOLD, + COMMAND_PRESS, + COMMAND_RELEASE, + COMMAND_STARTED_ROTATING, + COMMAND_STOPPED_ROTATING, + COMMAND_DOUBLE, + LEFT, + COMMAND_HOLD, + COMMAND_RELEASE, + RIGHT, + ROTATED, + COMMAND_PRESS, +) + +zhaquirks.setup() +# candeo motion tests @pytest.mark.parametrize( "lux_in, lux_out", ( @@ -32,3 +73,392 @@ async def test_candeo_motion_illuminance(zigpy_device_from_v2_quirk, lux_in, lux assert len(illuminance_listener.attribute_updates) == 1 assert illuminance_listener.attribute_updates[0][0] == illuminance_attr_id assert illuminance_listener.attribute_updates[0][1] == lux_out + + +# candeo scene switch remote 5 button rotarty tests + +@pytest.mark.asyncio +async def test_CandeoSceneSwitchRemoteCluster_apply_custom_configuration(zigpy_device_from_v2_quirk): + """Test apply custom configuration is called and calls bind on the cluster.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + cluster.bind = mock.AsyncMock() + await cluster.apply_custom_configuration() + cluster.bind.assert_awaited_once() + + +def test_CandeoSceneSwitchRemoteCluster_duplicate_sequence_number(zigpy_device_from_v2_quirk): + """Test duplicate sequence number ignored.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + header.tsn = 5 + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.button_press, 0x0, CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonNumberMap.button_1) + + cluster.handle_cluster_request(header, args) + cluster.handle_cluster_request(header, args) + + assert cluster.send_default_rsp.call_count == 2 + assert listener.zha_send_event.call_count == 1 + + +def test_CandeoSceneSwitchRemoteCluster_unknown_command_id(zigpy_device_from_v2_quirk): + """Test unknown command id.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = 0x99 + header.frame_control = foundation.FrameControl.cluster() + + cluster.handle_cluster_request(header, []) + + assert listener.zha_send_event.call_count == 0 + + +def test_CandeoSceneSwitchRemoteCluster_missing_schema_fields(zigpy_device_from_v2_quirk): + """Test missing CandeoSceneSwitchRemoteClusterCommand schema fields.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + + args = CandeoSceneSwitchRemoteClusterCommand(None, None, None, None) + + cluster.handle_cluster_request(header, args) + + assert listener.zha_send_event.call_count == 0 + + +def test_CandeoSceneSwitchRemoteCluster_unknown_message_type(zigpy_device_from_v2_quirk): + """Test unknown CandeoSceneSwitchRemoteMessageType.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + + args = CandeoSceneSwitchRemoteClusterCommand(0x99, 0x0, CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.press) + + cluster.handle_cluster_request(header, args) + + assert listener.zha_send_event.call_count == 0 + + +@pytest.mark.parametrize( + "button_number, button_action, expected_button_name, expected_button_action_name", + [ + (CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_1, COMMAND_PRESS), + (CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_1, COMMAND_DOUBLE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_1, COMMAND_HOLD), + (CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_1, COMMAND_RELEASE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_2, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_2, COMMAND_PRESS), + (CandeoSceneSwitchRemoteButtonNumberMap.button_2, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_2, COMMAND_DOUBLE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_2, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_2, COMMAND_HOLD), + (CandeoSceneSwitchRemoteButtonNumberMap.button_2, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_2, COMMAND_RELEASE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_3, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_3, COMMAND_PRESS), + (CandeoSceneSwitchRemoteButtonNumberMap.button_3, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_3, COMMAND_DOUBLE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_3, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_3, COMMAND_HOLD), + (CandeoSceneSwitchRemoteButtonNumberMap.button_3, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_3, COMMAND_RELEASE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_4, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_4, COMMAND_PRESS), + (CandeoSceneSwitchRemoteButtonNumberMap.button_4, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_4, COMMAND_DOUBLE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_4, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_4, COMMAND_HOLD), + (CandeoSceneSwitchRemoteButtonNumberMap.button_4, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_4, COMMAND_RELEASE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_centre, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_CENTRE, COMMAND_PRESS), + (CandeoSceneSwitchRemoteButtonNumberMap.button_centre, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_CENTRE, COMMAND_DOUBLE), + (CandeoSceneSwitchRemoteButtonNumberMap.button_centre, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_CENTRE, COMMAND_HOLD), + (CandeoSceneSwitchRemoteButtonNumberMap.button_centre, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_CENTRE, COMMAND_RELEASE) + ], +) +def test_CandeoSceneSwitchRemoteCluster__button_number_and_button_action_combinations(zigpy_device_from_v2_quirk, button_number, button_action, expected_button_name, expected_button_action_name): + """Test button numbers and button actions generate events correctly.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.button_press, 0x0, button_number, button_action) + + cluster.handle_cluster_request(header, args) + + button_event = listener.zha_send_event.call_args[0] + + assert button_event[0] == expected_button_action_name + + assert button_event[1][BUTTON] == expected_button_name + + +@pytest.mark.parametrize( + "button_number, button_action", + [ + (0x99, CandeoSceneSwitchRemoteButtonActionMap.press), + (CandeoSceneSwitchRemoteButtonNumberMap.button_1, 0x99), + ], +) +def test_CandeoSceneSwitchRemoteCluster_unknown_button_number_or_button_action(zigpy_device_from_v2_quirk, button_number, button_action): + """Test unknown button numbers and button actiona are ignored.""" + + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.button_press, 0x0, button_number, button_action) + + cluster.handle_cluster_request(header, args) + + assert listener.zha_send_event.call_count == 0 + + +@pytest.mark.parametrize( + "ring_direction", + [ + (CandeoSceneSwitchRemoteRingDirectionMap.left), + (CandeoSceneSwitchRemoteRingDirectionMap.right), + ], +) +def test_CandeoSceneSwitchRemoteCluster_ring_started_rotating(zigpy_device_from_v2_quirk, ring_direction): + """Test ring started rotating actions generate events correctly.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, ring_direction, CandeoSceneSwitchRemoteRingActionMap.started_rotating, 0x01) + + cluster.handle_cluster_request(header, args) + + ring_event = listener.zha_send_event.call_args[0] + + assert ring_event[0] == COMMAND_STARTED_ROTATING + + expected_ring_direction_name = LEFT if ring_direction == CandeoSceneSwitchRemoteRingDirectionMap.left else RIGHT + + assert ring_event[1][ROTATED] == expected_ring_direction_name + + assert listener.zha_send_event.call_count == 1 + + +@pytest.mark.parametrize( + "ring_direction, ring_action", + [ + (0x99, CandeoSceneSwitchRemoteRingActionMap.started_rotating), + (CandeoSceneSwitchRemoteRingDirectionMap.right, 0x99), + ], +) +def test_CandeoSceneSwitchRemoteCluster_unknown_ring_direction_or_ring_action(zigpy_device_from_v2_quirk, ring_direction, ring_action): + """Test unknown ring directions and ring actions are ignored.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, ring_direction, ring_action, 0x01) + + cluster.handle_cluster_request(header, args) + + assert listener.zha_send_event.call_count == 0 + + +@pytest.mark.parametrize( + "ring_direction, ring_clicks", + [ + (CandeoSceneSwitchRemoteRingDirectionMap.left, 0x02), + (CandeoSceneSwitchRemoteRingDirectionMap.right, 0x03), + (CandeoSceneSwitchRemoteRingDirectionMap.left, 0x09), + (CandeoSceneSwitchRemoteRingDirectionMap.right, 0x06), + ], +) +def test_CandeoSceneSwitchRemoteCluster_ring_continued_rotating(zigpy_device_from_v2_quirk, ring_direction, ring_clicks): + """Test ring continued rotating actions generate events correctly.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, ring_direction, CandeoSceneSwitchRemoteRingActionMap.started_rotating, ring_clicks) + + cluster.handle_cluster_request(header, args) + + for x in range(0, ring_clicks, -1): + ring_event = listener.zha_send_event.call_args[x] + + expected_ring_action_name = COMMAND_STARTED_ROTATING if x == 0 else COMMAND_CONTINUED_ROTATING + + assert ring_event[0] == expected_ring_action_name + + expected_ring_direction_name = LEFT if ring_direction == CandeoSceneSwitchRemoteRingDirectionMap.left else RIGHT + + assert ring_event[1][ROTATED] == expected_ring_direction_name + + assert listener.zha_send_event.call_count == ring_clicks + + +def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persistence(zigpy_device_from_v2_quirk): + """Test ring continued rotating actions generate events correctly.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + header.tsn = 1 + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, CandeoSceneSwitchRemoteRingDirectionMap.left, CandeoSceneSwitchRemoteRingActionMap.started_rotating, 0x01) + + cluster.handle_cluster_request(header, args) + + assert cluster.previous_rotation_direction == LEFT + assert cluster.previous_rotation_event == COMMAND_STARTED_ROTATING + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, 0x0, CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, 0x0) + + header.tsn = 2 + + cluster.handle_cluster_request(header, args) + + assert cluster.previous_rotation_direction == LEFT + assert cluster.previous_rotation_event == COMMAND_STOPPED_ROTATING + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, CandeoSceneSwitchRemoteRingDirectionMap.right, CandeoSceneSwitchRemoteRingActionMap.started_rotating, 0x01) + + header.tsn = 3 + + cluster.handle_cluster_request(header, args) + + assert cluster.previous_rotation_direction == RIGHT + assert cluster.previous_rotation_event == COMMAND_STARTED_ROTATING + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, 0x0, CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, 0x0) + + header.tsn = 4 + + cluster.handle_cluster_request(header, args) + + assert cluster.previous_rotation_direction == RIGHT + assert cluster.previous_rotation_event == COMMAND_STOPPED_ROTATING + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, CandeoSceneSwitchRemoteRingDirectionMap.left, CandeoSceneSwitchRemoteRingActionMap.started_rotating, 0x03) + + header.tsn = 5 + + cluster.handle_cluster_request(header, args) + + assert cluster.previous_rotation_direction == LEFT + assert cluster.previous_rotation_event == COMMAND_CONTINUED_ROTATING + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, 0x0, CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, 0x0) + + header.tsn = 6 + + cluster.handle_cluster_request(header, args) + + assert cluster.previous_rotation_direction == LEFT + assert cluster.previous_rotation_event == COMMAND_STOPPED_ROTATING + + +@pytest.mark.parametrize( + "previous_rotation_direction, previous_rotation_event", + [ + (LEFT, COMMAND_STARTED_ROTATING), + (RIGHT, COMMAND_STARTED_ROTATING), + (LEFT, COMMAND_CONTINUED_ROTATING), + (RIGHT, COMMAND_CONTINUED_ROTATING), + ], +) +def test_CandeoSceneSwitchRemoteCluster_ring_stopped_rotating(zigpy_device_from_v2_quirk, previous_rotation_direction, previous_rotation_event): + """Test ring stopped rotating actions generate events correctly.""" + device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") + + cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster + listener = mock.MagicMock() + cluster.add_listener(listener) + + cluster.send_default_rsp = mock.MagicMock() + + cluster.previous_rotation_direction = previous_rotation_direction + cluster.previous_rotation_event = previous_rotation_event + + header = foundation.ZCLHeader() + header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.frame_control = foundation.FrameControl.cluster() + + args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, 0x0, CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, 0x0) + + cluster.handle_cluster_request(header, args) + + ring_event = listener.zha_send_event.call_args[0] + + assert ring_event[0] == COMMAND_STOPPED_ROTATING + + assert ring_event[1][ROTATED] == previous_rotation_direction + + assert listener.zha_send_event.call_count == 1 + From f08de42874375af061941a82806138f892172747 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:15:11 +0000 Subject: [PATCH 10/10] Apply pre-commit auto fixes --- tests/test_candeo.py | 364 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 290 insertions(+), 74 deletions(-) diff --git a/tests/test_candeo.py b/tests/test_candeo.py index 3eb3f6dc31..41e083d51e 100644 --- a/tests/test_candeo.py +++ b/tests/test_candeo.py @@ -1,30 +1,23 @@ """Tests for Candeo.""" -import pytest - from unittest import mock +import pytest from zigpy.zcl import foundation - from zigpy.zcl.clusters.measurement import IlluminanceMeasurement from tests.common import ClusterListener import zhaquirks - -import zigpy.types as t - from zhaquirks.candeo import CANDEO - from zhaquirks.candeo.scene_switch_remote_5_button_rotary import ( + CandeoSceneSwitchRemoteButtonActionMap, + CandeoSceneSwitchRemoteButtonNumberMap, CandeoSceneSwitchRemoteCluster, CandeoSceneSwitchRemoteClusterCommand, CandeoSceneSwitchRemoteMessageType, - CandeoSceneSwitchRemoteButtonNumberMap, - CandeoSceneSwitchRemoteButtonActionMap, - CandeoSceneSwitchRemoteRingDirectionMap, CandeoSceneSwitchRemoteRingActionMap, + CandeoSceneSwitchRemoteRingDirectionMap, ) - from zhaquirks.const import ( BUTTON, BUTTON_1, @@ -39,17 +32,14 @@ COMMAND_RELEASE, COMMAND_STARTED_ROTATING, COMMAND_STOPPED_ROTATING, - COMMAND_DOUBLE, LEFT, - COMMAND_HOLD, - COMMAND_RELEASE, RIGHT, ROTATED, - COMMAND_PRESS, ) zhaquirks.setup() + # candeo motion tests @pytest.mark.parametrize( "lux_in, lux_out", @@ -77,8 +67,11 @@ async def test_candeo_motion_illuminance(zigpy_device_from_v2_quirk, lux_in, lux # candeo scene switch remote 5 button rotarty tests + @pytest.mark.asyncio -async def test_CandeoSceneSwitchRemoteCluster_apply_custom_configuration(zigpy_device_from_v2_quirk): +async def test_CandeoSceneSwitchRemoteCluster_apply_custom_configuration( + zigpy_device_from_v2_quirk, +): """Test apply custom configuration is called and calls bind on the cluster.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") cluster = device.endpoints[1].CandeoSceneSwitchRemoteCluster_Cluster @@ -87,7 +80,9 @@ async def test_CandeoSceneSwitchRemoteCluster_apply_custom_configuration(zigpy_d cluster.bind.assert_awaited_once() -def test_CandeoSceneSwitchRemoteCluster_duplicate_sequence_number(zigpy_device_from_v2_quirk): +def test_CandeoSceneSwitchRemoteCluster_duplicate_sequence_number( + zigpy_device_from_v2_quirk, +): """Test duplicate sequence number ignored.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -98,11 +93,18 @@ def test_CandeoSceneSwitchRemoteCluster_duplicate_sequence_number(zigpy_device_f cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() header.tsn = 5 - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.button_press, 0x0, CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonNumberMap.button_1) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.button_press, + 0x0, + CandeoSceneSwitchRemoteButtonNumberMap.button_1, + CandeoSceneSwitchRemoteButtonNumberMap.button_1, + ) cluster.handle_cluster_request(header, args) cluster.handle_cluster_request(header, args) @@ -130,7 +132,9 @@ def test_CandeoSceneSwitchRemoteCluster_unknown_command_id(zigpy_device_from_v2_ assert listener.zha_send_event.call_count == 0 -def test_CandeoSceneSwitchRemoteCluster_missing_schema_fields(zigpy_device_from_v2_quirk): +def test_CandeoSceneSwitchRemoteCluster_missing_schema_fields( + zigpy_device_from_v2_quirk, +): """Test missing CandeoSceneSwitchRemoteClusterCommand schema fields.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -141,7 +145,9 @@ def test_CandeoSceneSwitchRemoteCluster_missing_schema_fields(zigpy_device_from_ cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() args = CandeoSceneSwitchRemoteClusterCommand(None, None, None, None) @@ -151,7 +157,9 @@ def test_CandeoSceneSwitchRemoteCluster_missing_schema_fields(zigpy_device_from_ assert listener.zha_send_event.call_count == 0 -def test_CandeoSceneSwitchRemoteCluster_unknown_message_type(zigpy_device_from_v2_quirk): +def test_CandeoSceneSwitchRemoteCluster_unknown_message_type( + zigpy_device_from_v2_quirk, +): """Test unknown CandeoSceneSwitchRemoteMessageType.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -162,10 +170,17 @@ def test_CandeoSceneSwitchRemoteCluster_unknown_message_type(zigpy_device_from_v cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() - args = CandeoSceneSwitchRemoteClusterCommand(0x99, 0x0, CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.press) + args = CandeoSceneSwitchRemoteClusterCommand( + 0x99, + 0x0, + CandeoSceneSwitchRemoteButtonNumberMap.button_1, + CandeoSceneSwitchRemoteButtonActionMap.press, + ) cluster.handle_cluster_request(header, args) @@ -175,29 +190,135 @@ def test_CandeoSceneSwitchRemoteCluster_unknown_message_type(zigpy_device_from_v @pytest.mark.parametrize( "button_number, button_action, expected_button_name, expected_button_action_name", [ - (CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_1, COMMAND_PRESS), - (CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_1, COMMAND_DOUBLE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_1, COMMAND_HOLD), - (CandeoSceneSwitchRemoteButtonNumberMap.button_1, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_1, COMMAND_RELEASE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_2, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_2, COMMAND_PRESS), - (CandeoSceneSwitchRemoteButtonNumberMap.button_2, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_2, COMMAND_DOUBLE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_2, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_2, COMMAND_HOLD), - (CandeoSceneSwitchRemoteButtonNumberMap.button_2, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_2, COMMAND_RELEASE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_3, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_3, COMMAND_PRESS), - (CandeoSceneSwitchRemoteButtonNumberMap.button_3, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_3, COMMAND_DOUBLE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_3, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_3, COMMAND_HOLD), - (CandeoSceneSwitchRemoteButtonNumberMap.button_3, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_3, COMMAND_RELEASE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_4, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_4, COMMAND_PRESS), - (CandeoSceneSwitchRemoteButtonNumberMap.button_4, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_4, COMMAND_DOUBLE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_4, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_4, COMMAND_HOLD), - (CandeoSceneSwitchRemoteButtonNumberMap.button_4, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_4, COMMAND_RELEASE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_centre, CandeoSceneSwitchRemoteButtonActionMap.press, BUTTON_CENTRE, COMMAND_PRESS), - (CandeoSceneSwitchRemoteButtonNumberMap.button_centre, CandeoSceneSwitchRemoteButtonActionMap.double, BUTTON_CENTRE, COMMAND_DOUBLE), - (CandeoSceneSwitchRemoteButtonNumberMap.button_centre, CandeoSceneSwitchRemoteButtonActionMap.hold, BUTTON_CENTRE, COMMAND_HOLD), - (CandeoSceneSwitchRemoteButtonNumberMap.button_centre, CandeoSceneSwitchRemoteButtonActionMap.release, BUTTON_CENTRE, COMMAND_RELEASE) + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_1, + CandeoSceneSwitchRemoteButtonActionMap.press, + BUTTON_1, + COMMAND_PRESS, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_1, + CandeoSceneSwitchRemoteButtonActionMap.double, + BUTTON_1, + COMMAND_DOUBLE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_1, + CandeoSceneSwitchRemoteButtonActionMap.hold, + BUTTON_1, + COMMAND_HOLD, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_1, + CandeoSceneSwitchRemoteButtonActionMap.release, + BUTTON_1, + COMMAND_RELEASE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_2, + CandeoSceneSwitchRemoteButtonActionMap.press, + BUTTON_2, + COMMAND_PRESS, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_2, + CandeoSceneSwitchRemoteButtonActionMap.double, + BUTTON_2, + COMMAND_DOUBLE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_2, + CandeoSceneSwitchRemoteButtonActionMap.hold, + BUTTON_2, + COMMAND_HOLD, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_2, + CandeoSceneSwitchRemoteButtonActionMap.release, + BUTTON_2, + COMMAND_RELEASE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_3, + CandeoSceneSwitchRemoteButtonActionMap.press, + BUTTON_3, + COMMAND_PRESS, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_3, + CandeoSceneSwitchRemoteButtonActionMap.double, + BUTTON_3, + COMMAND_DOUBLE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_3, + CandeoSceneSwitchRemoteButtonActionMap.hold, + BUTTON_3, + COMMAND_HOLD, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_3, + CandeoSceneSwitchRemoteButtonActionMap.release, + BUTTON_3, + COMMAND_RELEASE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_4, + CandeoSceneSwitchRemoteButtonActionMap.press, + BUTTON_4, + COMMAND_PRESS, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_4, + CandeoSceneSwitchRemoteButtonActionMap.double, + BUTTON_4, + COMMAND_DOUBLE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_4, + CandeoSceneSwitchRemoteButtonActionMap.hold, + BUTTON_4, + COMMAND_HOLD, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_4, + CandeoSceneSwitchRemoteButtonActionMap.release, + BUTTON_4, + COMMAND_RELEASE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_centre, + CandeoSceneSwitchRemoteButtonActionMap.press, + BUTTON_CENTRE, + COMMAND_PRESS, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_centre, + CandeoSceneSwitchRemoteButtonActionMap.double, + BUTTON_CENTRE, + COMMAND_DOUBLE, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_centre, + CandeoSceneSwitchRemoteButtonActionMap.hold, + BUTTON_CENTRE, + COMMAND_HOLD, + ), + ( + CandeoSceneSwitchRemoteButtonNumberMap.button_centre, + CandeoSceneSwitchRemoteButtonActionMap.release, + BUTTON_CENTRE, + COMMAND_RELEASE, + ), ], ) -def test_CandeoSceneSwitchRemoteCluster__button_number_and_button_action_combinations(zigpy_device_from_v2_quirk, button_number, button_action, expected_button_name, expected_button_action_name): +def test_CandeoSceneSwitchRemoteCluster__button_number_and_button_action_combinations( + zigpy_device_from_v2_quirk, + button_number, + button_action, + expected_button_name, + expected_button_action_name, +): """Test button numbers and button actions generate events correctly.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -208,10 +329,17 @@ def test_CandeoSceneSwitchRemoteCluster__button_number_and_button_action_combina cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.button_press, 0x0, button_number, button_action) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.button_press, + 0x0, + button_number, + button_action, + ) cluster.handle_cluster_request(header, args) @@ -229,7 +357,9 @@ def test_CandeoSceneSwitchRemoteCluster__button_number_and_button_action_combina (CandeoSceneSwitchRemoteButtonNumberMap.button_1, 0x99), ], ) -def test_CandeoSceneSwitchRemoteCluster_unknown_button_number_or_button_action(zigpy_device_from_v2_quirk, button_number, button_action): +def test_CandeoSceneSwitchRemoteCluster_unknown_button_number_or_button_action( + zigpy_device_from_v2_quirk, button_number, button_action +): """Test unknown button numbers and button actiona are ignored.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -241,10 +371,17 @@ def test_CandeoSceneSwitchRemoteCluster_unknown_button_number_or_button_action(z cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.button_press, 0x0, button_number, button_action) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.button_press, + 0x0, + button_number, + button_action, + ) cluster.handle_cluster_request(header, args) @@ -258,7 +395,9 @@ def test_CandeoSceneSwitchRemoteCluster_unknown_button_number_or_button_action(z (CandeoSceneSwitchRemoteRingDirectionMap.right), ], ) -def test_CandeoSceneSwitchRemoteCluster_ring_started_rotating(zigpy_device_from_v2_quirk, ring_direction): +def test_CandeoSceneSwitchRemoteCluster_ring_started_rotating( + zigpy_device_from_v2_quirk, ring_direction +): """Test ring started rotating actions generate events correctly.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -269,10 +408,17 @@ def test_CandeoSceneSwitchRemoteCluster_ring_started_rotating(zigpy_device_from_ cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, ring_direction, CandeoSceneSwitchRemoteRingActionMap.started_rotating, 0x01) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + ring_direction, + CandeoSceneSwitchRemoteRingActionMap.started_rotating, + 0x01, + ) cluster.handle_cluster_request(header, args) @@ -280,7 +426,11 @@ def test_CandeoSceneSwitchRemoteCluster_ring_started_rotating(zigpy_device_from_ assert ring_event[0] == COMMAND_STARTED_ROTATING - expected_ring_direction_name = LEFT if ring_direction == CandeoSceneSwitchRemoteRingDirectionMap.left else RIGHT + expected_ring_direction_name = ( + LEFT + if ring_direction == CandeoSceneSwitchRemoteRingDirectionMap.left + else RIGHT + ) assert ring_event[1][ROTATED] == expected_ring_direction_name @@ -294,7 +444,9 @@ def test_CandeoSceneSwitchRemoteCluster_ring_started_rotating(zigpy_device_from_ (CandeoSceneSwitchRemoteRingDirectionMap.right, 0x99), ], ) -def test_CandeoSceneSwitchRemoteCluster_unknown_ring_direction_or_ring_action(zigpy_device_from_v2_quirk, ring_direction, ring_action): +def test_CandeoSceneSwitchRemoteCluster_unknown_ring_direction_or_ring_action( + zigpy_device_from_v2_quirk, ring_direction, ring_action +): """Test unknown ring directions and ring actions are ignored.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -305,10 +457,17 @@ def test_CandeoSceneSwitchRemoteCluster_unknown_ring_direction_or_ring_action(zi cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, ring_direction, ring_action, 0x01) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + ring_direction, + ring_action, + 0x01, + ) cluster.handle_cluster_request(header, args) @@ -324,7 +483,9 @@ def test_CandeoSceneSwitchRemoteCluster_unknown_ring_direction_or_ring_action(zi (CandeoSceneSwitchRemoteRingDirectionMap.right, 0x06), ], ) -def test_CandeoSceneSwitchRemoteCluster_ring_continued_rotating(zigpy_device_from_v2_quirk, ring_direction, ring_clicks): +def test_CandeoSceneSwitchRemoteCluster_ring_continued_rotating( + zigpy_device_from_v2_quirk, ring_direction, ring_clicks +): """Test ring continued rotating actions generate events correctly.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -335,28 +496,43 @@ def test_CandeoSceneSwitchRemoteCluster_ring_continued_rotating(zigpy_device_fro cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, ring_direction, CandeoSceneSwitchRemoteRingActionMap.started_rotating, ring_clicks) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + ring_direction, + CandeoSceneSwitchRemoteRingActionMap.started_rotating, + ring_clicks, + ) cluster.handle_cluster_request(header, args) for x in range(0, ring_clicks, -1): ring_event = listener.zha_send_event.call_args[x] - expected_ring_action_name = COMMAND_STARTED_ROTATING if x == 0 else COMMAND_CONTINUED_ROTATING + expected_ring_action_name = ( + COMMAND_STARTED_ROTATING if x == 0 else COMMAND_CONTINUED_ROTATING + ) assert ring_event[0] == expected_ring_action_name - expected_ring_direction_name = LEFT if ring_direction == CandeoSceneSwitchRemoteRingDirectionMap.left else RIGHT + expected_ring_direction_name = ( + LEFT + if ring_direction == CandeoSceneSwitchRemoteRingDirectionMap.left + else RIGHT + ) assert ring_event[1][ROTATED] == expected_ring_direction_name assert listener.zha_send_event.call_count == ring_clicks -def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persistence(zigpy_device_from_v2_quirk): +def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persistence( + zigpy_device_from_v2_quirk, +): """Test ring continued rotating actions generate events correctly.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -367,18 +543,30 @@ def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persisten cluster.send_default_rsp = mock.MagicMock() header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() header.tsn = 1 - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, CandeoSceneSwitchRemoteRingDirectionMap.left, CandeoSceneSwitchRemoteRingActionMap.started_rotating, 0x01) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + CandeoSceneSwitchRemoteRingDirectionMap.left, + CandeoSceneSwitchRemoteRingActionMap.started_rotating, + 0x01, + ) cluster.handle_cluster_request(header, args) assert cluster.previous_rotation_direction == LEFT assert cluster.previous_rotation_event == COMMAND_STARTED_ROTATING - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, 0x0, CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, 0x0) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + 0x0, + CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, + 0x0, + ) header.tsn = 2 @@ -387,7 +575,12 @@ def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persisten assert cluster.previous_rotation_direction == LEFT assert cluster.previous_rotation_event == COMMAND_STOPPED_ROTATING - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, CandeoSceneSwitchRemoteRingDirectionMap.right, CandeoSceneSwitchRemoteRingActionMap.started_rotating, 0x01) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + CandeoSceneSwitchRemoteRingDirectionMap.right, + CandeoSceneSwitchRemoteRingActionMap.started_rotating, + 0x01, + ) header.tsn = 3 @@ -396,7 +589,12 @@ def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persisten assert cluster.previous_rotation_direction == RIGHT assert cluster.previous_rotation_event == COMMAND_STARTED_ROTATING - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, 0x0, CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, 0x0) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + 0x0, + CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, + 0x0, + ) header.tsn = 4 @@ -405,7 +603,12 @@ def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persisten assert cluster.previous_rotation_direction == RIGHT assert cluster.previous_rotation_event == COMMAND_STOPPED_ROTATING - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, CandeoSceneSwitchRemoteRingDirectionMap.left, CandeoSceneSwitchRemoteRingActionMap.started_rotating, 0x03) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + CandeoSceneSwitchRemoteRingDirectionMap.left, + CandeoSceneSwitchRemoteRingActionMap.started_rotating, + 0x03, + ) header.tsn = 5 @@ -414,7 +617,12 @@ def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persisten assert cluster.previous_rotation_direction == LEFT assert cluster.previous_rotation_event == COMMAND_CONTINUED_ROTATING - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, 0x0, CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, 0x0) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + 0x0, + CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, + 0x0, + ) header.tsn = 6 @@ -433,7 +641,9 @@ def test_CandeoSceneSwitchRemoteCluster_ring_direction_and_ring_action_persisten (RIGHT, COMMAND_CONTINUED_ROTATING), ], ) -def test_CandeoSceneSwitchRemoteCluster_ring_stopped_rotating(zigpy_device_from_v2_quirk, previous_rotation_direction, previous_rotation_event): +def test_CandeoSceneSwitchRemoteCluster_ring_stopped_rotating( + zigpy_device_from_v2_quirk, previous_rotation_direction, previous_rotation_event +): """Test ring stopped rotating actions generate events correctly.""" device = zigpy_device_from_v2_quirk(manufacturer=CANDEO, model="C-ZB-SR5BR") @@ -447,10 +657,17 @@ def test_CandeoSceneSwitchRemoteCluster_ring_stopped_rotating(zigpy_device_from_ cluster.previous_rotation_event = previous_rotation_event header = foundation.ZCLHeader() - header.command_id = CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + header.command_id = ( + CandeoSceneSwitchRemoteCluster.ServerCommandDefs.candeo_scene_switch_remote.id + ) header.frame_control = foundation.FrameControl.cluster() - args = CandeoSceneSwitchRemoteClusterCommand(CandeoSceneSwitchRemoteMessageType.ring_rotation, 0x0, CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, 0x0) + args = CandeoSceneSwitchRemoteClusterCommand( + CandeoSceneSwitchRemoteMessageType.ring_rotation, + 0x0, + CandeoSceneSwitchRemoteRingActionMap.stopped_rotating, + 0x0, + ) cluster.handle_cluster_request(header, args) @@ -461,4 +678,3 @@ def test_CandeoSceneSwitchRemoteCluster_ring_stopped_rotating(zigpy_device_from_ assert ring_event[1][ROTATED] == previous_rotation_direction assert listener.zha_send_event.call_count == 1 -