55local capabilities = require " st.capabilities"
66local zcl_clusters = require " st.zigbee.zcl.clusters"
77local window_shade_utils = require " window_shade_utils"
8+ local utils = require " st.utils"
89
910local WindowCovering = zcl_clusters .WindowCovering
1011
1112local SHADE_SET_STATUS = " shade_set_status"
1213
1314local function current_position_attr_handler (driver , device , value , zb_rx )
1415 local level = 100 - value .value
16+
17+ -- Knob control logic
18+ local knob_target = device :get_field (" knob_target_level" )
19+ log .info (" ---------->IKEA curtain report level:" , level , " knob_target:" , knob_target )
20+ if knob_target then
21+ -- Allow ±1 degree tolerance for reaching target
22+ if math.abs (level - knob_target ) <= 1 then
23+ device :set_field (" knob_target_level" , nil )
24+ log .info (" ----------->IKEA curtain reached target, clearing knob_target" )
25+ end
26+ end
27+
1528 local current_level = device :get_latest_state (" main" , capabilities .windowShadeLevel .ID , capabilities .windowShadeLevel .shadeLevel .NAME )
1629 local windowShade = capabilities .windowShade .windowShade
1730 if level == - 155 then -- unknown position
@@ -70,6 +83,39 @@ local function window_shade_preset_cmd(driver, device, command)
7083 set_shade_level (device , level , command )
7184end
7285
86+ local function knob_to_window_shade_step_cmd (driver , device , command )
87+ local step = command .args .stepSize
88+ log .info (" ------------->IKEA knob step size:" , step )
89+
90+ -- Priority: use knob_target_level if exists
91+ local knob_target = device :get_field (" knob_target_level" )
92+ local current_level = knob_target or
93+ device :get_latest_state (" main" , capabilities .windowShadeLevel .ID ,
94+ capabilities .windowShadeLevel .shadeLevel .NAME ) or 0
95+
96+ log .info (" ------------->IKEA current_level:" , current_level , " from knob_target:" , knob_target ~= nil )
97+
98+ -- Calculate new target (user level: 0-100, 0=closed, 100=open)
99+ local target_level = current_level + step
100+ if target_level > 100 then target_level = 100
101+ elseif target_level < 0 then target_level = 0
102+ end
103+ target_level = utils .round (target_level )
104+
105+ log .info (" ------------->IKEA target_level:" , target_level )
106+
107+ -- Update tracking state
108+ device :set_field (" knob_target_level" , target_level )
109+
110+ -- Invert for IKEA: user level → device level
111+ local device_level = 100 - target_level
112+
113+ log .info (" ------------->IKEA sending device_level:" , device_level )
114+
115+ device :send_to_component (command .component ,
116+ WindowCovering .server .commands .GoToLiftPercentage (device , device_level ))
117+ end
118+
73119local ikea_window_treatment = {
74120 NAME = " inverted lift percentage" ,
75121 zigbee_handlers = {
@@ -85,6 +131,9 @@ local ikea_window_treatment = {
85131 },
86132 [capabilities .windowShadePreset .ID ] = {
87133 [capabilities .windowShadePreset .commands .presetPosition .NAME ] = window_shade_preset_cmd
134+ },
135+ [capabilities .statelessSwitchLevelStep .ID ] = {
136+ [capabilities .statelessSwitchLevelStep .commands .stepLevel .NAME ] = knob_to_window_shade_step_cmd
88137 }
89138 },
90139 can_handle = require (" invert-lift-percentage.can_handle" ),
0 commit comments