Skip to content

Commit 0172af6

Browse files
committed
generic blind devices logic for ikea and tuya
1 parent a8c85d4 commit 0172af6

3 files changed

Lines changed: 85 additions & 16 deletions

File tree

drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ components:
88
version: 1
99
- id: windowShadeLevel
1010
version: 1
11+
- id: statelessSwitchLevelStep
12+
version: 1
1113
- id: battery
1214
version: 1
1315
- id: firmwareUpdate

drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/init.lua

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,26 @@
55
local capabilities = require "st.capabilities"
66
local zcl_clusters = require "st.zigbee.zcl.clusters"
77
local window_shade_utils = require "window_shade_utils"
8+
local utils = require "st.utils"
89

910
local WindowCovering = zcl_clusters.WindowCovering
1011

1112
local SHADE_SET_STATUS = "shade_set_status"
1213

1314
local 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)
7184
end
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+
73119
local 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"),

drivers/Unofficial/tuya-zigbee/src/curtain/init.lua

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,19 @@ local function tuya_cluster_handler(driver, device, zb_rx)
139139
-- dp means data point in tuya payload format
140140
local dp = raw:byte(3)
141141
local dp_data = raw:byte(10)
142-
if dp == 0x03 then
142+
if dp == 0x03 then
143+
local knob_target = device:get_field("knob_target_level")
144+
log.info("---------->tuya curtain report dp_data:", dp_data)
145+
log.info("---------->knob_target:", knob_target)
146+
if knob_target then
147+
-- Allow ±1 degree tolerance for reaching target
148+
if math.abs(knob_target - dp_data) <= 1 then
149+
device:set_field("knob_target_level", nil)
150+
log.info("tuya curtain reached target, clearing knob_target")
151+
end
152+
end
153+
154+
-- Emit events with device-reported level (dp_data)
143155
window_shade_level_event = capabilities.windowShadeLevel.shadeLevel(dp_data)
144156
if dp_data == 0 then
145157
window_shade_val_event = capabilities.windowShade.windowShade("open")
@@ -156,25 +168,31 @@ local function tuya_cluster_handler(driver, device, zb_rx)
156168
end
157169

158170
local function knob_to_window_shade_step_cmd(driver, device, command)
159-
-- step1: get the rotateAmount
160171
local step = command.args.stepSize
161-
-- step2: get the current_level
162-
local current_level = device:get_latest_state("main", capabilities.windowShadeLevel.ID, capabilities.windowShadeLevel.shadeLevel.NAME) or 0
163-
-- calcultate the target_level
164-
-- Tuya curtain devices use INVERTED position logic,
165-
-- if we want to set to "target level" = "current_level" + "step"
166-
-- we should send (100 - target level) to device
167-
local target_level = 100-(current_level + step)
168-
if target_level > 100 then
169-
target_level = 100
170-
elseif target_level < 0 then
171-
target_level = 0
172+
log.info("------------->knob step size:", step)
173+
174+
-- Priority: use knob_target_level if exists
175+
local knob_target = device:get_field("knob_target_level")
176+
local current_level = knob_target or
177+
device:get_latest_state("main", capabilities.windowShadeLevel.ID,
178+
capabilities.windowShadeLevel.shadeLevel.NAME) or 0
179+
180+
-- Calculate new target (user level: 0-100, 0=closed, 100=open)
181+
log.info("------------->current_level:", current_level)
182+
local target_level = current_level + step
183+
if target_level > 100 then target_level = 100
184+
elseif target_level < 0 then target_level = 0
172185
end
173-
target_level = utils.round(target_level)
186+
log.info("------------->target_level:", target_level)
187+
188+
-- Update tracking state
189+
device:set_field("knob_target_level", target_level)
190+
191+
target_level = utils.round(100 - target_level)
192+
log.info("------------->sending level:", target_level)
193+
174194
tuya_utils.send_tuya_command(device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00'..string.pack(">I2", target_level), packet_id)
175195
packet_id = increase_packet_id(packet_id)
176-
log.info("-------------------target_level", 100-target_level)
177-
device:emit_event(capabilities.windowShadeLevel.shadeLevel(100-target_level))
178196
end
179197

180198
local tuya_curtain_driver = {

0 commit comments

Comments
 (0)