From cd621bdcbdc79dd79a8ca181ebbe14e4c826bd6a Mon Sep 17 00:00:00 2001 From: martyn-vesternet Date: Thu, 20 Aug 2026 11:42:10 +0100 Subject: [PATCH 1/4] Add support for Candeo RD1P device with v2 firmware --- zhaquirks/candeo/__init__.py | 51 ------- zhaquirks/candeo/rotary_dimmer_switches.py | 159 ++++++++++++++++++++- 2 files changed, 155 insertions(+), 55 deletions(-) diff --git a/zhaquirks/candeo/__init__.py b/zhaquirks/candeo/__init__.py index 2722066ec8..f44c3ef612 100644 --- a/zhaquirks/candeo/__init__.py +++ b/zhaquirks/candeo/__init__.py @@ -28,13 +28,6 @@ class CandeoSwitchType(t.enum8): Toggle = 0x01 -class CandeoRemoteDirection(t.enum8): - """Candeo Remote Direction.""" - - Right = 0x00 - Left = 0x01 - - class CandeoIlluminanceMeasurementCluster(IlluminanceMeasurement, CustomCluster): """Candeo Illuminance Measurement Cluster.""" @@ -118,47 +111,3 @@ class CandeoRGBCCTColorCluster(Color, CustomCluster): Color.AttributeDefs.color_capabilities.id: Color.ColorCapabilities.XY_attributes + Color.ColorCapabilities.Color_temperature } - - -class CandeoOnOffRemoteCluster(OnOff, CustomCluster): - """Candeo OnOff Remote Cluster.""" - - class ServerCommandDefs(BaseCommandDefs): - """overwrite ServerCommandDefs.""" - - double: Final = ZCLCommandDef( - id=0x00, - schema={}, - ) - press: Final = ZCLCommandDef( - id=0x01, - schema={}, - ) - hold: Final = ZCLCommandDef( - id=0x02, - schema={}, - ) - release: Final = ZCLCommandDef( - id=0x03, - schema={}, - ) - - -class CandeoLevelControlRemoteCluster(LevelControl, CustomCluster): - """Candeo LevelControl Remote Cluster.""" - - class ServerCommandDefs(BaseCommandDefs): - """overwrite ServerCommandDefs.""" - - started_rotating: Final = ZCLCommandDef( - id=0x05, - schema={"direction": CandeoRemoteDirection}, - ) - continued_rotating: Final = ZCLCommandDef( - id=0x06, - schema={"direction": CandeoRemoteDirection}, - ) - stopped_rotating: Final = ZCLCommandDef( - id=0x03, - schema={}, - ) diff --git a/zhaquirks/candeo/rotary_dimmer_switches.py b/zhaquirks/candeo/rotary_dimmer_switches.py index 37d3692af5..d39bdf33cc 100644 --- a/zhaquirks/candeo/rotary_dimmer_switches.py +++ b/zhaquirks/candeo/rotary_dimmer_switches.py @@ -1,14 +1,16 @@ """Candeo rotary dimmer switches.""" +from typing import Final +from zigpy.quirks import CustomCluster +import zigpy.types as t from zigpy.zcl import ClusterType -from zigpy.zcl.clusters.general import Identify, Ota - +from zigpy.zcl.clusters.general import Identify, Ota, OnOff, LevelControl +from zigpy.zcl.foundation import DataTypeId, ZCLAttributeDef, BaseCommandDefs, ZCLCommandDef from zhaquirks.builder import QuirkBuilder from zhaquirks.candeo import ( CANDEO, - CandeoLevelControlRemoteCluster, - CandeoOnOffRemoteCluster, ) + from zhaquirks.const import ( CLUSTER_ID, COMMAND, @@ -33,6 +35,146 @@ STOPPED_ROTATING, ) +class CandeoRemoteDirection(t.enum8): + """Candeo Remote Direction.""" + + Right = 0x00 + Left = 0x01 + + +class CandeoRemoteLiteEP2Functionality(t.enum8): + """Candeo remote lite EP2 functionality enum.""" + + disabled = False + enabled = True + + +class CandeoOnOffRemoteCluster(OnOff, CustomCluster): + """Candeo OnOff Remote Cluster.""" + + class ServerCommandDefs(BaseCommandDefs): + """overwrite ServerCommandDefs.""" + + double: Final = ZCLCommandDef( + id=0x00, + schema={}, + ) + press: Final = ZCLCommandDef( + id=0x01, + schema={}, + ) + hold: Final = ZCLCommandDef( + id=0x02, + schema={}, + ) + release: Final = ZCLCommandDef( + id=0x03, + schema={}, + ) + + +class CandeoLevelControlRemoteCluster(LevelControl, CustomCluster): + """Candeo LevelControl Remote Cluster.""" + + class ServerCommandDefs(BaseCommandDefs): + """overwrite ServerCommandDefs.""" + + started_rotating: Final = ZCLCommandDef( + id=0x05, + schema={"direction": CandeoRemoteDirection}, + ) + continued_rotating: Final = ZCLCommandDef( + id=0x06, + schema={"direction": CandeoRemoteDirection}, + ) + stopped_rotating: Final = ZCLCommandDef( + id=0x03, + schema={}, + ) + + +class CandeoOnOffRemoteLiteEP2FunctionalityCluster(OnOff, CustomCluster): + """Candeo OnOff Remote Lite EP2 Functionality Cluster.""" + + class AttributeDefs(OnOff.AttributeDefs): + """Attribute Definitions.""" + + rem_lite_ep2_functionality = ZCLAttributeDef( + id=0x8000, + type=CandeoRemoteLiteEP2Functionality, + zcl_type=DataTypeId.bool_, + access="rw", + ) + + _VALID_ATTRIBUTES = { + AttributeDefs.rem_lite_ep2_functionality.id, + } + + +class CandeoOnOffRemoteLiteCluster(OnOff, CustomCluster): + """Candeo OnOff Remote Lite Cluster.""" + + class ServerCommandDefs(BaseCommandDefs): + """overwrite ServerCommandDefs.""" + + double: Final = ZCLCommandDef( + id=0x00, + schema={}, + ) + hold: Final = ZCLCommandDef( + id=0x02, + schema={}, + ) + release: Final = ZCLCommandDef( + id=0x03, + schema={}, + ) + + +remote_lite_quirk = ( + QuirkBuilder() + .replaces( + CandeoOnOffRemoteLiteEP2FunctionalityCluster, + endpoint_id=1, + ) + .replaces( + CandeoOnOffRemoteLiteCluster, + endpoint_id=2, + cluster_type=ClusterType.Client, + ) + .removes( + OnOff.cluster_id, + endpoint_id=3, + ) + .enum( + attribute_name=CandeoOnOffRemoteLiteEP2FunctionalityCluster.AttributeDefs.rem_lite_ep2_functionality.name, + cluster_id=CandeoOnOffRemoteLiteEP2FunctionalityCluster.cluster_id, + endpoint_id=1, + translation_key="rem_lite_ep2_functionality", + fallback_name="Extra button commands", + enum_class=CandeoRemoteLiteEP2Functionality, + ) + .device_automation_triggers( + { + (DOUBLE_PRESS, ROTARY_KNOB): { + COMMAND: COMMAND_DOUBLE, + CLUSTER_ID: 6, + ENDPOINT_ID: 2, + }, + (LONG_PRESS, ROTARY_KNOB): { + COMMAND: COMMAND_HOLD, + CLUSTER_ID: 6, + ENDPOINT_ID: 2, + }, + (LONG_RELEASE, ROTARY_KNOB): { + COMMAND: COMMAND_RELEASE, + CLUSTER_ID: 6, + ENDPOINT_ID: 2, + }, + } + ) +) + remote_quirk = ( QuirkBuilder() .replaces( @@ -107,9 +249,17 @@ .add_to_registry() ) +( + remote_lite_quirk.clone() + .applies_to(CANDEO, "C-ZB-RD1Pv2-DIM") + .removes(Ota.cluster_id) + .add_to_registry() +) + ( remote_quirk.clone() .applies_to(CANDEO, "C-ZB-RD1P-REM") + .applies_to(CANDEO, "C-ZB-RD1Pv2-REM") .removes(Identify.cluster_id, endpoint_id=1) .removes(Identify.cluster_id, endpoint_id=2) .removes(Ota.cluster_id) @@ -119,6 +269,7 @@ ( remote_quirk.clone() .applies_to(CANDEO, "C-ZB-RD1P-DPM") + .applies_to(CANDEO, "C-ZB-RD1Pv2-DPM") .removes(Ota.cluster_id) .add_to_registry() ) From 703f350fc27dc997a39992b0601a49d512385b6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:23:53 +0000 Subject: [PATCH 2/4] Apply pre-commit auto fixes --- zhaquirks/candeo/rotary_dimmer_switches.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/zhaquirks/candeo/rotary_dimmer_switches.py b/zhaquirks/candeo/rotary_dimmer_switches.py index d39bdf33cc..49ba29d2f7 100644 --- a/zhaquirks/candeo/rotary_dimmer_switches.py +++ b/zhaquirks/candeo/rotary_dimmer_switches.py @@ -1,16 +1,20 @@ """Candeo rotary dimmer switches.""" from typing import Final + from zigpy.quirks import CustomCluster import zigpy.types as t from zigpy.zcl import ClusterType -from zigpy.zcl.clusters.general import Identify, Ota, OnOff, LevelControl -from zigpy.zcl.foundation import DataTypeId, ZCLAttributeDef, BaseCommandDefs, ZCLCommandDef -from zhaquirks.builder import QuirkBuilder -from zhaquirks.candeo import ( - CANDEO, +from zigpy.zcl.clusters.general import Identify, LevelControl, OnOff, Ota +from zigpy.zcl.foundation import ( + BaseCommandDefs, + DataTypeId, + ZCLAttributeDef, + ZCLCommandDef, ) +from zhaquirks.builder import QuirkBuilder +from zhaquirks.candeo import CANDEO from zhaquirks.const import ( CLUSTER_ID, COMMAND, @@ -35,6 +39,7 @@ STOPPED_ROTATING, ) + class CandeoRemoteDirection(t.enum8): """Candeo Remote Direction.""" From f0d6e393673d9e94bc84480799a2a26fa4f8ef5f Mon Sep 17 00:00:00 2001 From: martyn-vesternet Date: Thu, 20 Aug 2026 12:26:59 +0100 Subject: [PATCH 3/4] Update __init__.py Fix pre-commit errors --- zhaquirks/candeo/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/zhaquirks/candeo/__init__.py b/zhaquirks/candeo/__init__.py index f44c3ef612..3c270fe5a7 100644 --- a/zhaquirks/candeo/__init__.py +++ b/zhaquirks/candeo/__init__.py @@ -1,18 +1,15 @@ """Module for Candeo quirks implementations.""" import math -from typing import Final import zigpy.types as t -from zigpy.zcl.clusters.general import Basic, LevelControl, OnOff +from zigpy.zcl.clusters.general import Basic from zigpy.zcl.clusters.lighting import Color from zigpy.zcl.clusters.measurement import IlluminanceMeasurement from zigpy.zcl.clusters.security import IasZone from zigpy.zcl.foundation import ( - BaseCommandDefs, DataTypeId, ZCLAttributeDef, - ZCLCommandDef, ) from zhaquirks.clusters import CustomCluster From e08a61da3129f7dc7b137a7a9e6c76963ac79cd2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:27:11 +0000 Subject: [PATCH 4/4] Apply pre-commit auto fixes --- zhaquirks/candeo/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/zhaquirks/candeo/__init__.py b/zhaquirks/candeo/__init__.py index 3c270fe5a7..546177e141 100644 --- a/zhaquirks/candeo/__init__.py +++ b/zhaquirks/candeo/__init__.py @@ -7,10 +7,7 @@ from zigpy.zcl.clusters.lighting import Color from zigpy.zcl.clusters.measurement import IlluminanceMeasurement from zigpy.zcl.clusters.security import IasZone -from zigpy.zcl.foundation import ( - DataTypeId, - ZCLAttributeDef, -) +from zigpy.zcl.foundation import DataTypeId, ZCLAttributeDef from zhaquirks.clusters import CustomCluster from zhaquirks.const import ZONE_TYPE