Skip to content

Commit 240eb32

Browse files
authored
CHAD-15785 Z-Wave: WindowShadePreset capability updates (#2292)
* CHAD-15785 Z-Wave: WindowShadePreset capability updates * update driver to work with earlier versions of lua_libs
1 parent e6f59f7 commit 240eb32

File tree

9 files changed

+155
-11
lines changed

9 files changed

+155
-11
lines changed

drivers/SmartThings/zwave-window-treatment/profiles/window-treatment-preset-reverse.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,5 @@ components:
1515
categories:
1616
- name: Blind
1717
preferences:
18-
- preferenceId: presetPosition
19-
explicit: true
2018
- preferenceId: reverse
2119
explicit: true

drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/init.lua

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
local capabilities = require "st.capabilities"
1616
--- @type st.zwave.CommandClass.SwitchMultilevel
1717
local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version=3 })
18+
local window_preset_defaults = require "window_preset_defaults"
1819

1920
local IBLINDS_WINDOW_TREATMENT_FINGERPRINTS = {
2021
{mfr = 0x0287, prod = 0x0003, model = 0x000D}, -- iBlinds Window Treatment v1 / v2
@@ -70,8 +71,11 @@ function capability_handlers.set_shade_level(driver, device, command)
7071
set_shade_level_helper(driver, device, command.args.shadeLevel)
7172
end
7273

73-
function capability_handlers.preset_position(driver, device)
74-
set_shade_level_helper(driver, device, device.preferences.presetPosition or 50)
74+
function capability_handlers.preset_position(driver, device, command)
75+
local level = device:get_latest_state(command.component, "windowShadePreset", "position") or
76+
device:get_field(window_preset_defaults.PRESET_LEVEL_KEY) or
77+
(device.preferences ~= nil and device.preferences.presetPosition) or 50
78+
set_shade_level_helper(driver, device, level)
7579
end
7680

7781
local iblinds_window_treatment = {

drivers/SmartThings/zwave-window-treatment/src/iblinds-window-treatment/v3.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ local function can_handle_iblinds_window_treatment_v3(opts, driver, device, ...)
3535
return false
3636
end
3737

38+
local function init_handler(self, device)
39+
if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and
40+
device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.supportedCommands.NAME) == nil then
41+
42+
-- setPresetPosition is not supported (device uses a separate preference)
43+
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition"}, { visibility = { displayed = false }}))
44+
end
45+
end
46+
3847
local capability_handlers = {}
3948

4049
function capability_handlers.close(driver, device)
@@ -65,6 +74,9 @@ function capability_handlers.preset_position(driver, device)
6574
end
6675

6776
local iblinds_window_treatment_v3 = {
77+
lifecycle_handlers = {
78+
init = init_handler
79+
},
6880
capability_handlers = {
6981
[capabilities.windowShade.ID] = {
7082
[capabilities.windowShade.commands.close.NAME] = capability_handlers.close

drivers/SmartThings/zwave-window-treatment/src/init.lua

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@ local ZwaveDriver = require "st.zwave.driver"
2020
--- @type st.zwave.CommandClass.Configuration
2121
local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 })
2222
local preferencesMap = require "preferences"
23+
local window_preset_defaults = require "window_preset_defaults"
24+
25+
local function init_handler(self, device)
26+
if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and
27+
device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME) == nil then
28+
29+
-- These should only ever be nil once (and at the same time) for already-installed devices
30+
-- It can be relocated to `added` after migration is complete
31+
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, { visibility = { displayed = false }}))
32+
33+
local preset_position = device:get_field(window_preset_defaults.PRESET_LEVEL_KEY) or
34+
(device.preferences ~= nil and device.preferences.presetPosition) or
35+
window_preset_defaults.PRESET_LEVEL
36+
37+
device:emit_event(capabilities.windowShadePreset.position(preset_position, { visibility = {displayed = false}}))
38+
device:set_field(window_preset_defaults.PRESET_LEVEL_KEY, preset_position, {persist = true})
39+
end
40+
end
2341

2442
local function added_handler(self, device)
2543
device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({"open", "close", "pause"}, { visibility = { displayed = false } }))
@@ -56,9 +74,16 @@ local driver_template = {
5674
capabilities.battery
5775
},
5876
lifecycle_handlers = {
77+
init = init_handler,
5978
added = added_handler,
6079
infoChanged = info_changed
6180
},
81+
capability_handlers = {
82+
[capabilities.windowShadePreset.ID] = {
83+
[capabilities.windowShadePreset.commands.setPresetPosition.NAME] = window_preset_defaults.set_preset_position_cmd,
84+
[capabilities.windowShadePreset.commands.presetPosition.NAME] = window_preset_defaults.window_shade_preset_cmd,
85+
}
86+
},
6287
sub_drivers = {
6388
require("springs-window-fashion-shade"),
6489
require("iblinds-window-treatment"),

drivers/SmartThings/zwave-window-treatment/src/springs-window-fashion-shade/init.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ local function can_handle_springs_window_fashion_shade(opts, driver, device, ...
3737
return false
3838
end
3939

40+
local function init_handler(self, device)
41+
-- This device has a preset position set in hardware, so we need to override the base driver
42+
if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and
43+
device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.supportedCommands.NAME) == nil then
44+
45+
-- setPresetPosition is not supported
46+
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition"}, { visibility = { displayed = false }}))
47+
end
48+
end
49+
4050
local capability_handlers = {}
4151

4252
--- Issue a window shade preset position command to the specified device.
@@ -58,6 +68,9 @@ function capability_handlers.preset_position(driver, device)
5868
end
5969

6070
local springs_window_fashion_shade = {
71+
lifecycle_handlers = {
72+
init = init_handler
73+
},
6174
capability_handlers = {
6275
[capabilities.windowShadePreset.ID] = {
6376
[capabilities.windowShadePreset.commands.presetPosition.NAME] = capability_handlers.preset_position

drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ local mock_blind_v3 = test.mock_device.build_test_zwave_device({
4949
local function test_init()
5050
test.mock_device.add_test_device(mock_blind)
5151
test.mock_device.add_test_device(mock_blind_v3)
52+
test.socket.capability:__expect_send(
53+
mock_blind:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
54+
)
55+
test.socket.capability:__expect_send(
56+
mock_blind:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
57+
)
58+
test.socket.capability:__expect_send(
59+
mock_blind_v3:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition"}, {visibility = {displayed=false}}))
60+
)
5261
end
5362
test.set_test_init_function(test_init)
5463

@@ -240,13 +249,15 @@ test.register_coroutine_test(
240249
test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot")
241250
test.socket.zwave:__set_channel_ordering("relaxed")
242251
test.socket.device_lifecycle():__queue_receive({mock_blind.id, "init"})
243-
test.socket.device_lifecycle():__queue_receive(mock_blind:generate_info_changed(
244-
{
245-
preferences = {
246-
presetPosition = 35
247-
}
248-
}
249-
))
252+
test.socket.capability:__queue_receive(
253+
{
254+
mock_blind.id,
255+
{ capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = {35} }
256+
}
257+
)
258+
test.socket.capability:__expect_send(
259+
mock_blind:generate_test_message("main", capabilities.windowShadePreset.position(35))
260+
)
250261
test.wait_for_events()
251262
test.socket.capability:__queue_receive(
252263
{

drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ local zw = require "st.zwave"
1818
local zw_test_utils = require "integration_test.zwave_test_utils"
1919
local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version=4 })
2020
local t_utils = require "integration_test.utils"
21+
local capabilities = require "st.capabilities"
2122

2223
-- supported comand classes: SWITCH_MULTILEVEL
2324
local window_shade_switch_multilevel_endpoints = {
@@ -38,6 +39,9 @@ local mock_springs_window_fashion_shade = test.mock_device.build_test_zwave_devi
3839

3940
local function test_init()
4041
test.mock_device.add_test_device(mock_springs_window_fashion_shade)
42+
test.socket.capability:__expect_send(
43+
mock_springs_window_fashion_shade:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition"}, {visibility = {displayed=false}}))
44+
)
4145
end
4246
test.set_test_init_function(test_init)
4347

drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ local mock_window_shade_switch_multilevel = test.mock_device.build_test_zwave_de
5454
local function test_init()
5555
test.mock_device.add_test_device(mock_window_shade_basic)
5656
test.mock_device.add_test_device(mock_window_shade_switch_multilevel)
57+
test.socket.capability:__expect_send(
58+
mock_window_shade_basic:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
59+
)
60+
test.socket.capability:__expect_send(
61+
mock_window_shade_basic:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
62+
)
63+
test.socket.capability:__expect_send(
64+
mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
65+
)
66+
test.socket.capability:__expect_send(
67+
mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
68+
)
5769
end
5870
test.set_test_init_function(test_init)
5971

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
-- Copyright 2025 SmartThings
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
15+
16+
-- These were added to scripting engine, but this file is to make sure drivers
17+
-- runing on older versions of scripting engine can still access these values
18+
local capabilities = require "st.capabilities"
19+
local constants = require "st.zwave.constants"
20+
21+
--- @type st.zwave.CommandClass
22+
local cc = require "st.zwave.CommandClass"
23+
--- @type st.zwave.CommandClass.SwitchMultilevel
24+
local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version = 4 })
25+
--- @type st.zwave.CommandClass.Basic
26+
local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 })
27+
28+
local defaults = {}
29+
30+
--- WINDOW SHADE PRESET CONSTANTS
31+
defaults.PRESET_LEVEL = 50
32+
defaults.PRESET_LEVEL_KEY = "_presetLevel"
33+
34+
defaults.set_preset_position_cmd = function(driver, device, command)
35+
device:emit_component_event({id = command.component}, capabilities.windowShadePreset.position(command.args.position))
36+
device:set_field(defaults.PRESET_LEVEL_KEY, command.args.position, {persist = true})
37+
end
38+
39+
defaults.window_shade_preset_cmd = function(driver, device, command)
40+
local set
41+
local get
42+
local preset_level = device:get_latest_state(command.component, "windowShadePreset", "position") or
43+
device:get_field(constants.PRESET_LEVEL_KEY) or
44+
(device.preferences ~= nil and device.preferences.presetPosition) or
45+
defaults.PRESET_LEVEL
46+
if device:is_cc_supported(cc.SWITCH_MULTILEVEL) then
47+
set = SwitchMultilevel:Set({
48+
value = preset_level,
49+
duration = constants.DEFAULT_DIMMING_DURATION
50+
})
51+
get = SwitchMultilevel:Get({})
52+
else
53+
set = Basic:Set({
54+
value = preset_level
55+
})
56+
get = Basic:Get({})
57+
end
58+
device:send_to_component(set, command.component)
59+
local query_device = function()
60+
device:send_to_component(get, command.component)
61+
end
62+
device.thread:call_with_delay(constants.MIN_DIMMING_GET_STATUS_DELAY, query_device)
63+
end
64+
65+
return defaults

0 commit comments

Comments
 (0)