Skip to content

CHAD-15784 Zigbee: WindowShadePreset capability updates #2282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ lua_libs-api_*
tools/test_output/*
tools/coverage_output/*
.DS_Store
.venv/
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,3 @@ components:
version: 1
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,4 @@ components:
- id: firmwareUpdate
version: 1
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- name: Blind
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,4 @@ components:
- id: refresh
version: 1
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- name: Blind
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,3 @@ components:
version: 1
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true

Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,3 @@ components:
preferences:
- preferenceId: reverse
explicit: true
- preferenceId: presetPosition
explicit: true
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
-- limitations under the License.

local capabilities = require "st.capabilities"
local window_preset_defaults = require "st.zigbee.defaults.windowShadePreset_defaults"
local window_shade_utils = require "window_shade_utils"
local zcl_clusters = require "st.zigbee.zcl.clusters"
local utils = require "st.utils"

Expand Down Expand Up @@ -52,7 +52,7 @@ local function window_shade_level_cmd_handler(driver, device, command)
end

local function window_shade_preset_cmd(driver, device, command)
local level = device.preferences and device.preferences.presetPosition or window_preset_defaults.PRESET_LEVEL
local level = window_shade_utils.get_preset_level(device, command.component)
window_shade_set_level(device, command, level)
end

Expand Down
4 changes: 2 additions & 2 deletions drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

local capabilities = require "st.capabilities"
local device_management = require "st.zigbee.device_management"
local window_preset_defaults = require "st.zigbee.defaults.windowShadePreset_defaults"
local window_shade_utils = require "window_shade_utils"
local zcl_clusters = require "st.zigbee.zcl.clusters"
local utils = require "st.utils"

Expand Down Expand Up @@ -50,7 +50,7 @@ local function window_shade_level_cmd_handler(driver, device, command)
end

local function window_shade_preset_cmd(driver, device, command)
local level = device.preferences and device.preferences.presetPosition or window_preset_defaults.PRESET_LEVEL
local level = window_shade_utils.get_preset_level(device, command.component)
window_shade_set_level(device, command, level)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

local capabilities = require "st.capabilities"
local zcl_clusters = require "st.zigbee.zcl.clusters"
local window_preset_defaults = require "st.zigbee.defaults.windowShadePreset_defaults"
local window_shade_utils = require "window_shade_utils"
local window_shade_defaults = require "st.zigbee.defaults.windowShade_defaults"
local device_management = require "st.zigbee.device_management"
local Level = zcl_clusters.Level
Expand Down Expand Up @@ -50,7 +50,7 @@ local function level_attr_handler(driver, device, value, zb_rx)
end

local function window_shade_preset_cmd(driver, device, command)
local level = device.preferences.presetPosition or device:get_field(window_preset_defaults.PRESET_LEVEL_KEY) or window_preset_defaults.PRESET_LEVEL
local level = window_shade_utils.get_preset_level(device, command.component)
set_shade_level(device, level, command.component)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ local Messages = require "st.zigbee.messages"
local data_types = require "st.zigbee.data_types"
local ZigbeeConstants = require "st.zigbee.constants"
local generic_body = require "st.zigbee.generic_body"
local window_preset_defaults = require "st.zigbee.defaults.windowShadePreset_defaults"
local window_shade_utils = require "window_shade_utils"

local TUYA_CLUSTER = 0xEF00
local DP_TYPE_VALUE = "\x02"
Expand Down Expand Up @@ -148,7 +148,7 @@ local function SetShadeLevelHandler(driver, device, capability_command)
end

local function PresetPositionHandler(driver, device, capability_command)
local level = device.preferences.presetPosition or device:get_field(window_preset_defaults.PRESET_LEVEL_KEY) or window_preset_defaults.PRESET_LEVEL
local level = window_shade_utils.get_preset_level(device, capability_command.component)
SetShadeLevelHandler(driver, device, {args = { shadeLevel = level }})
end

Expand Down
25 changes: 25 additions & 0 deletions drivers/SmartThings/zigbee-window-treatment/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@
local capabilities = require "st.capabilities"
local ZigbeeDriver = require "st.zigbee"
local defaults = require "st.zigbee.defaults"
local window_shade_utils = require "window_shade_utils"

local function init_handler(self, device)
Copy link
Contributor

Choose a reason for hiding this comment

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

Like I mentioned in the CHAD-15784, we need this.
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, { visibility = { displayed = false }})) in init handler.

Copy link
Contributor

@inasail inasail Jul 25, 2025

Choose a reason for hiding this comment

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

if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and
device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME) == nil then

-- These should only ever be nil once (and at the same time) for already-installed devices
-- It can be relocated to `added` after migration is complete
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, { visibility = { displayed = false }}))

local preset_position = device:get_field(window_shade_utils.PRESET_LEVEL_KEY) or
(device.preferences ~= nil and device.preferences.presetPosition) or
window_shade_utils.PRESET_LEVEL

device:emit_event(capabilities.windowShadePreset.position(preset_position, { visibility = {displayed = false}}))
device:set_field(window_shade_utils.PRESET_LEVEL_KEY, preset_position, {persist = true})
end
end

local function added_handler(self, device)
device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({"open", "close", "pause"}, { visibility = { displayed = false }}))
Expand All @@ -40,8 +58,15 @@ local zigbee_window_treatment_driver_template = {
require("hanssem"),
require("screen-innovations")},
lifecycle_handlers = {
init = init_handler,
added = added_handler
},
capability_handlers = {
[capabilities.windowShadePreset.ID] = {
[capabilities.windowShadePreset.commands.setPresetPosition.NAME] = window_shade_utils.set_preset_position_cmd,
[capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_utils.window_shade_preset_cmd,
}
},
health_check = false,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

local capabilities = require "st.capabilities"
local zcl_clusters = require "st.zigbee.zcl.clusters"
local window_shade_utils = require "window_shade_utils"

local WindowCovering = zcl_clusters.WindowCovering

Expand Down Expand Up @@ -75,9 +76,8 @@ local function window_shade_level_cmd(driver, device, command)
end

local function window_shade_preset_cmd(driver, device, command)
if device.preferences ~= nil and device.preferences.presetPosition ~= nil then
set_shade_level(device, device.preferences.presetPosition, command)
end
local level = window_shade_utils.get_preset_level(device, command.component)
set_shade_level(device, level, command)
end

local ikea_window_treatment = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
-- require st provided libraries
local capabilities = require "st.capabilities"
local clusters = require "st.zigbee.zcl.clusters"
local window_preset_defaults = require "st.zigbee.defaults.windowShadePreset_defaults"
local window_shade_utils = require "window_shade_utils"
local device_management = require "st.zigbee.device_management"
local utils = require "st.utils"

Expand Down Expand Up @@ -52,9 +52,9 @@ end

-- this is window_shade_preset_cmd
local function window_shade_preset_cmd(driver, device, command)
local go_to_level = device.preferences.presetPosition or device:get_field(window_preset_defaults.PRESET_LEVEL_KEY) or window_preset_defaults.PRESET_LEVEL
local level = window_shade_utils.get_preset_level(device, command.component)
-- send levels without inverting as: 0% closed (i.e., open) to 100% closed
device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, go_to_level))
device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, level))
end

-- this is device_added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

local capabilities = require "st.capabilities"
local utils = require "st.utils"
local window_preset_defaults = require "st.zigbee.defaults.windowShadePreset_defaults"
local window_shade_utils = require "window_shade_utils"
local zcl_clusters = require "st.zigbee.zcl.clusters"
local WindowCovering = zcl_clusters.WindowCovering

Expand Down Expand Up @@ -109,7 +109,7 @@ local function window_shade_level_cmd(driver, device, command)
end

local function window_shade_preset_cmd(driver, device, command)
local level = device.preferences.presetPosition or device:get_field(window_preset_defaults.PRESET_LEVEL_KEY) or window_preset_defaults.PRESET_LEVEL
local level = window_shade_utils.get_preset_level(device, command.component)
command.args.shadeLevel = level
window_shade_level_cmd(driver, device, command)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ local mock_device = test.mock_device.build_test_zigbee_device(

zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
test.mock_device.add_test_device(mock_device)end
test.mock_device.add_test_device(mock_device)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
)
end

test.set_test_init_function(test_init)

Expand Down Expand Up @@ -162,7 +169,13 @@ test.register_coroutine_test(
test.register_coroutine_test(
"windowShadePreset capability should be handled",
function()
test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 30}}))
test.socket.capability:__queue_receive(
{
mock_device.id,
{ capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = {30}}
}
)
test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadePreset.position(30)))
test.wait_for_events()
test.socket.capability:__queue_receive(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,14 @@ end

zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
test.mock_device.add_test_device(mock_device)end
test.mock_device.add_test_device(mock_device)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
)
end

test.set_test_init_function(test_init)

Expand Down Expand Up @@ -136,9 +143,10 @@ test.register_coroutine_test(
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
-- newly added devices will ignore the preference
test.socket.zigbee:__expect_send({
mock_device.id,
WindowCovering.server.commands.GoToLiftPercentage(mock_device, 70)
WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50)
})
end
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ local mock_device = test.mock_device.build_test_zigbee_device(

zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
test.mock_device.add_test_device(mock_device)end
test.mock_device.add_test_device(mock_device)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
)
end

test.set_test_init_function(test_init)

Expand Down Expand Up @@ -219,7 +226,42 @@ test.register_message_test(
mock_device.id,
clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50)
}
}
},
{
channel = "capability",
direction = "receive",
message = {
mock_device.id,
{
capability = "windowShadePreset", component = "main",
command = "setPresetPosition", args = {20}
}
}
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.windowShadePreset.position(20))
},
{
channel = "capability",
direction = "receive",
message = {
mock_device.id,
{
capability = "windowShadePreset", component = "main",
command = "presetPosition", args = {}
}
}
},
{
channel = "zigbee",
direction = "send",
message = {
mock_device.id,
clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 20)
}
},
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ local mock_device = test.mock_device.build_test_zigbee_device(

zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
test.mock_device.add_test_device(mock_device)end
test.mock_device.add_test_device(mock_device)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
)
end

test.set_test_init_function(test_init)

Expand Down Expand Up @@ -458,16 +465,17 @@ test.register_coroutine_test(
}
)
test.socket.capability:__set_channel_ordering("relaxed")
-- freshly joined devices will ignore the preference value
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(30))
mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50))
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening())
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100 - 30)
WindowCovering.server.commands.GoToLiftPercentage(mock_device, 100 - 50)
}
)
end
Expand Down
Loading
Loading