1+ -- Copyright 2026 SmartThings, Inc.
2+ -- Licensed under the Apache License, Version 2.0
3+
4+ local battery_defaults = require " st.zigbee.defaults.battery_defaults"
5+ local clusters = require " st.zigbee.zcl.clusters"
6+ local cluster_base = require " st.zigbee.cluster_base"
7+ local data_types = require " st.zigbee.data_types"
8+ local capabilities = require " st.capabilities"
9+ local utils = require " st.utils"
10+ local button_utils = require " button_utils"
11+
12+ local PowerConfiguration = clusters .PowerConfiguration
13+ local PRIVATE_CLUSTER_ID = 0xFCC0
14+ local PRIVATE_ATTRIBUTE_ID = 0x0009
15+ local MFG_CODE = 0x115F
16+ local MULTISTATE_INPUT_CLUSTER_ID = 0x0012
17+ local PRESENT_ATTRIBUTE_ID = 0x0055
18+ local ROTATION_MONITOR_ID = 0x0232
19+
20+ local AQARA_KNOB = {
21+ [" lumi.remote.rkba01" ] = { mfr = " LUMI" , type = " CR2032" , quantity = 2 }, -- Aqara Wireless Knob Switch H1
22+ }
23+
24+ local function device_init (driver , device )
25+ local configuration = {
26+ {
27+ cluster = PowerConfiguration .ID ,
28+ attribute = PowerConfiguration .attributes .BatteryVoltage .ID ,
29+ minimum_interval = 30 ,
30+ maximum_interval = 3600 ,
31+ data_type = PowerConfiguration .attributes .BatteryVoltage .base_type ,
32+ reportable_change = 1
33+ }
34+ }
35+
36+ battery_defaults .build_linear_voltage_init (2.6 , 3.0 )(driver , device )
37+ for _ , attribute in ipairs (configuration ) do
38+ device :add_configured_attribute (attribute )
39+ end
40+ end
41+
42+ local function device_added (self , device )
43+ local model = device :get_model ()
44+ local type = AQARA_KNOB [model ].type or " CR2032"
45+ local quantity = AQARA_KNOB [model ].quantity or 1
46+
47+ device :emit_event (capabilities .button .supportedButtonValues ({ " pushed" , " held" , " double" }, { visibility = { displayed = false } }))
48+ device :emit_event (capabilities .button .numberOfButtons ({ value = 1 }))
49+ button_utils .emit_event_if_latest_state_missing (device , " main" , capabilities .button ,
50+ capabilities .button .button .NAME , capabilities .button .button .pushed ({state_change = false }))
51+ device :emit_event (capabilities .batteryLevel .battery .normal ())
52+ device :emit_event (capabilities .batteryLevel .type (type ))
53+ device :emit_event (capabilities .batteryLevel .quantity (quantity ))
54+ device :emit_event (capabilities .knob .rotateAmount (0 ))
55+ device :emit_event (capabilities .knob .heldRotateAmount (0 ))
56+ end
57+
58+ local function do_configure (driver , device )
59+ device :configure ()
60+ -- Set manufacturer-specific attribute to enable "Operation Mode" for rotation reports
61+ device :send (cluster_base .write_manufacturer_specific_attribute (device ,
62+ PRIVATE_CLUSTER_ID , PRIVATE_ATTRIBUTE_ID , MFG_CODE , data_types .Uint8 , 1 ))
63+ device :emit_event (capabilities .knob .supportedAttributes ({" rotateAmount" , " heldRotateAmount" }, {state_change = true }))
64+ end
65+
66+ local function button_monitor_handler (driver , device , value , zb_rx )
67+ local val = value .value
68+
69+ if val == 1 then -- push
70+ device :emit_event (capabilities .button .button .pushed ({ state_change = true }))
71+ elseif val == 2 then -- double push
72+ device :emit_event (capabilities .button .button .double ({ state_change = true }))
73+ elseif val == 0 then -- down_hold
74+ device :emit_event (capabilities .button .button .held ({ state_change = true }))
75+ end
76+ end
77+
78+ local function rotation_monitor_per_handler (driver , device , value , zb_rx )
79+ local SENSITIVITY_KEY = " stse.knobSensitivity"
80+ local SENSITIVITY_FACTORS = {0.5 , 1.0 , 2.0 }
81+
82+ local end_point = zb_rx .address_header .src_endpoint .value
83+ local raw_val = utils .round (value .value )
84+
85+ if raw_val > 0x7FFF then
86+ raw_val = raw_val - 0x10000
87+ end
88+
89+ local sensitivity = tonumber (device .preferences [SENSITIVITY_KEY ])
90+ local factor = SENSITIVITY_FACTORS [sensitivity ] or 1.0
91+ local intermediate_val = raw_val * factor
92+ local sign = (intermediate_val > 0 and 1 ) or (intermediate_val < 0 and - 1 ) or 0
93+ local val = math.floor (math.abs (intermediate_val ) + 0.5 ) * sign
94+ val = math.max (- 100 , math.min (100 , val ))
95+
96+ if val == 0 then
97+ return
98+ elseif end_point == 0x47 then -- normal
99+ device :emit_event (capabilities .knob .rotateAmount ({value = val }, {state_change = true }))
100+ elseif end_point == 0x48 then -- press
101+ device :emit_event (capabilities .knob .heldRotateAmount ({value = val }, {state_change = true }))
102+ end
103+ end
104+
105+ local function battery_level_handler (driver , device , value , zb_rx )
106+ local voltage = value .value
107+ local batteryLevel = " normal"
108+
109+ if voltage <= 25 then
110+ batteryLevel = " critical"
111+ elseif voltage < 28 then
112+ batteryLevel = " warning"
113+ end
114+
115+ device :emit_event (capabilities .batteryLevel .battery (batteryLevel ))
116+ end
117+
118+ local aqara_knob_switch_handler = {
119+ NAME = " Aqara Wireless Knob Switch Handler" ,
120+ lifecycle_handlers = {
121+ init = device_init ,
122+ added = device_added ,
123+ doConfigure = do_configure
124+ },
125+ zigbee_handlers = {
126+ attr = {
127+ [MULTISTATE_INPUT_CLUSTER_ID ] = {
128+ [PRESENT_ATTRIBUTE_ID ] = button_monitor_handler
129+ },
130+ [PRIVATE_CLUSTER_ID ] = {
131+ [ROTATION_MONITOR_ID ] = rotation_monitor_per_handler
132+ },
133+ [PowerConfiguration .ID ] = {
134+ [PowerConfiguration .attributes .BatteryVoltage .ID ] = battery_level_handler
135+ },
136+ }
137+ },
138+ can_handle = require (" aqara-knob.can_handle" ),
139+ }
140+
141+ return aqara_knob_switch_handler
0 commit comments