Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ components:
version: 1
- id: windowShadeLevel
version: 1
- id: statelessSwitchLevelStep
version: 1
- id: battery
version: 1
- id: firmwareUpdate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@
local capabilities = require "st.capabilities"
local zcl_clusters = require "st.zigbee.zcl.clusters"
local window_shade_utils = require "window_shade_utils"
local utils = require "st.utils"
local log = require "log"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing this

local log = require "log"

local WindowCovering = zcl_clusters.WindowCovering

local SHADE_SET_STATUS = "shade_set_status"
local TARGET_REACH_TOLERANCE = 1 -- ±1 degree tolerance for reaching target

local function current_position_attr_handler(driver, device, value, zb_rx)
local level = 100 - value.value

local last_target_level = device:get_field("last_target_level")
log.info("---------->IKEA curtain report level:", level, "last_target_level:", last_target_level)
if last_target_level then
if math.abs(level - last_target_level) <= TARGET_REACH_TOLERANCE then
device:set_field("last_target_level", nil)
log.info("----------->IKEA curtain reached target, clearing last_target_level")
end
end

local current_level = device:get_latest_state("main", capabilities.windowShadeLevel.ID, capabilities.windowShadeLevel.shadeLevel.NAME)
local windowShade = capabilities.windowShade.windowShade
if level == -155 then -- unknown position
Expand Down Expand Up @@ -57,6 +70,7 @@ local function current_position_attr_handler(driver, device, value, zb_rx)
end

local function set_shade_level(device, value, command)
device:set_field("last_target_level", nil)
local level = 100 - value
device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, level))
end
Expand All @@ -70,6 +84,39 @@ local function window_shade_preset_cmd(driver, device, command)
set_shade_level(device, level, command)
end

local function window_shade_step_level_cmd(driver, device, command)
local step = command.args.stepSize
log.info("------------->IKEA step size:", step)

-- Priority: use last_target_level if exists
local last_target_level = device:get_field("last_target_level")
local current_level = last_target_level or
device:get_latest_state("main", capabilities.windowShadeLevel.ID,
capabilities.windowShadeLevel.shadeLevel.NAME) or 0

log.info("------------->IKEA current_level:", current_level, "from last_target_level:", last_target_level ~= nil)

-- Calculate new target (user level: 0-100, 0=closed, 100=open)
local target_level = current_level + step
if target_level > 100 then target_level = 100
elseif target_level < 0 then target_level = 0
end
target_level = utils.round(target_level)

log.info("------------->IKEA target_level:", target_level)

-- Update tracking state
device:set_field("last_target_level", target_level)

-- Invert for IKEA: user level → device level
local device_level = 100 - target_level

log.info("------------->IKEA sending device_level:", device_level)

device:send_to_component(command.component,
WindowCovering.server.commands.GoToLiftPercentage(device, device_level))
end

local ikea_window_treatment = {
NAME = "inverted lift percentage",
zigbee_handlers = {
Expand All @@ -85,6 +132,9 @@ local ikea_window_treatment = {
},
[capabilities.windowShadePreset.ID] = {
[capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_preset_cmd
},
[capabilities.statelessSwitchLevelStep.ID] = {
[capabilities.statelessSwitchLevelStep.commands.stepLevel.NAME] = window_shade_step_level_cmd
}
},
can_handle = require("invert-lift-percentage.can_handle"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ components:
capabilities:
- id: windowShade
version: 1
- id: statelessSwitchLevelStep
version: 1
- id: windowShadeLevel
version: 1
- id: windowShadePreset
Expand Down
47 changes: 46 additions & 1 deletion drivers/Unofficial/tuya-zigbee/src/curtain/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ local device_management = require "st.zigbee.device_management"
local tuya_utils = require "tuya_utils"
local Basic = clusters.Basic
local packet_id = 0
local log = require "log"

local PRESET_LEVEL = 50
local PRESET_LEVEL_KEY = "_presetLevel"
Expand Down Expand Up @@ -109,6 +110,7 @@ local function window_shade_level(driver, device, command)
if level > 100 then
level = 100
end
log.info("capability handler level ------------->", level)
level = utils.round(level)
if device:get_manufacturer() == "_TZE284_nladmfvf" then
level = 100 - level -- specific for _TZE284_nladmfvf
Expand Down Expand Up @@ -137,7 +139,19 @@ local function tuya_cluster_handler(driver, device, zb_rx)
-- dp means data point in tuya payload format
local dp = raw:byte(3)
local dp_data = raw:byte(10)
if dp == 0x03 then
if dp == 0x03 then
local knob_target = device:get_field("knob_target_level")
log.info("---------->tuya curtain report dp_data:", dp_data)
log.info("---------->knob_target:", knob_target)
if knob_target then
-- Allow ±1 degree tolerance for reaching target
if math.abs(knob_target - dp_data) <= 1 then
device:set_field("knob_target_level", nil)
log.info("tuya curtain reached target, clearing knob_target")
end
end

-- Emit events with device-reported level (dp_data)
window_shade_level_event = capabilities.windowShadeLevel.shadeLevel(dp_data)
if dp_data == 0 then
window_shade_val_event = capabilities.windowShade.windowShade("open")
Expand All @@ -153,6 +167,34 @@ local function tuya_cluster_handler(driver, device, zb_rx)
end
end

local function knob_to_window_shade_step_cmd(driver, device, command)
local step = command.args.stepSize
log.info("------------->knob step size:", step)

-- Priority: use knob_target_level if exists
local knob_target = device:get_field("knob_target_level")
local current_level = knob_target or
device:get_latest_state("main", capabilities.windowShadeLevel.ID,
capabilities.windowShadeLevel.shadeLevel.NAME) or 0

-- Calculate new target (user level: 0-100, 0=closed, 100=open)
log.info("------------->current_level:", current_level)
local target_level = current_level + step
if target_level > 100 then target_level = 100
elseif target_level < 0 then target_level = 0
end
log.info("------------->target_level:", target_level)

-- Update tracking state
device:set_field("knob_target_level", target_level)

target_level = utils.round(100 - target_level)
log.info("------------->sending level:", target_level)

tuya_utils.send_tuya_command(device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00'..string.pack(">I2", target_level), packet_id)
packet_id = increase_packet_id(packet_id)
end

local tuya_curtain_driver = {
NAME = "tuya curtain",
lifecycle_handlers = {
Expand All @@ -173,6 +215,9 @@ local tuya_curtain_driver = {
[capabilities.windowShadePreset.ID] = {
[capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_preset,
[capabilities.windowShadePreset.commands.setPresetPosition.NAME] = set_preset_position_cmd
},
[capabilities.statelessSwitchLevelStep.ID] = {
[capabilities.statelessSwitchLevelStep.commands.stepLevel.NAME] = knob_to_window_shade_step_cmd
}
},
zigbee_handlers = {
Expand Down
Loading