Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -15,7 +15,5 @@ components:
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- preferenceId: reverse
explicit: true
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
local capabilities = require "st.capabilities"
--- @type st.zwave.CommandClass.SwitchMultilevel
local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version=3 })
local window_preset_defaults = require "window_preset_defaults"

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

function capability_handlers.preset_position(driver, device)
set_shade_level_helper(driver, device, device.preferences.presetPosition or 50)
function capability_handlers.preset_position(driver, device, command)
local level = device:get_latest_state(command.component, "windowShadePreset", "position") or
device:get_field(window_preset_defaults.PRESET_LEVEL_KEY) or
(device.preferences ~= nil and device.preferences.presetPosition) or 50
set_shade_level_helper(driver, device, level)
end

local iblinds_window_treatment = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ local function can_handle_iblinds_window_treatment_v3(opts, driver, device, ...)
return false
end

local function init_handler(self, device)
if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and
device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.supportedCommands.NAME) == nil then

-- setPresetPosition is not supported (device uses a separate preference)
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition"}, { visibility = { displayed = false }}))
end
end

local capability_handlers = {}

function capability_handlers.close(driver, device)
Expand Down Expand Up @@ -65,6 +74,9 @@ function capability_handlers.preset_position(driver, device)
end

local iblinds_window_treatment_v3 = {
lifecycle_handlers = {
init = init_handler
},
capability_handlers = {
[capabilities.windowShade.ID] = {
[capabilities.windowShade.commands.close.NAME] = capability_handlers.close
Expand Down
19 changes: 19 additions & 0 deletions drivers/SmartThings/zwave-window-treatment/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ local ZwaveDriver = require "st.zwave.driver"
--- @type st.zwave.CommandClass.Configuration
local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 })
local preferencesMap = require "preferences"
local window_preset_defaults = require "window_preset_defaults"

local function init_handler(self, device)
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_preset_defaults.PRESET_LEVEL_KEY) or
(device.preferences ~= nil and device.preferences.presetPosition) or
window_preset_defaults.PRESET_LEVEL

device:emit_event(capabilities.windowShadePreset.position(preset_position, { visibility = {displayed = false}}))
device:set_field(window_preset_defaults.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 Down Expand Up @@ -56,6 +74,7 @@ local driver_template = {
capabilities.battery
},
lifecycle_handlers = {
init = init_handler,
added = added_handler,
infoChanged = info_changed
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ local function can_handle_springs_window_fashion_shade(opts, driver, device, ...
return false
end

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.

I think this sub driver doesn't need to be modified because it doesn't have any UI(like preference setting) related with 'presetPosition'.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This device allows the user to set a preset position with the included remote that can then be recalled via a MultiLevelSwitch:0xFF command. I did this to be certain that setPresetPosition is not supported, as it would be if I did not override the base driver's init handler.

-- This device has a preset position set in hardware, so we need to override the base driver
if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and
device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.supportedCommands.NAME) == nil then

-- setPresetPosition is not supported
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition"}, { visibility = { displayed = false }}))
end
end

local capability_handlers = {}

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

local springs_window_fashion_shade = {
lifecycle_handlers = {
init = init_handler
},
capability_handlers = {
[capabilities.windowShadePreset.ID] = {
[capabilities.windowShadePreset.commands.presetPosition.NAME] = capability_handlers.preset_position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ test.register_coroutine_test(
test.socket.capability:__queue_receive(
{
mock_fibaro_roller_shutter.id,
{ capability = "windowShadePreset", command = "presetPosition", args = {} }
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
test.socket.zwave:__expect_send(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ test.register_coroutine_test(
test.socket.capability:__queue_receive(
{
mock_qubino_flush_shutter.id,
{ capability = "windowShadePreset", command = "presetPosition", args = {} }
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
test.socket.zwave:__expect_send(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ local mock_blind_v3 = test.mock_device.build_test_zwave_device({
local function test_init()
test.mock_device.add_test_device(mock_blind)
test.mock_device.add_test_device(mock_blind_v3)
test.socket.capability:__expect_send(
mock_blind:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_blind:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_blind_v3:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition"}, {visibility = {displayed=false}}))
)
end
test.set_test_init_function(test_init)

Expand Down Expand Up @@ -214,7 +223,7 @@ test.register_coroutine_test(
test.socket.capability:__queue_receive(
{
mock_blind.id,
{ capability = "windowShadePreset", command = "presetPosition", args = {} }
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
test.socket.capability:__expect_send(
Expand All @@ -240,18 +249,20 @@ test.register_coroutine_test(
test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot")
test.socket.zwave:__set_channel_ordering("relaxed")
test.socket.device_lifecycle():__queue_receive({mock_blind.id, "init"})
test.socket.device_lifecycle():__queue_receive(mock_blind:generate_info_changed(
{
preferences = {
presetPosition = 35
}
}
))
test.socket.capability:__queue_receive(
{
mock_blind.id,
{ capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = {35} }
}
)
test.socket.capability:__expect_send(
mock_blind:generate_test_message("main", capabilities.windowShadePreset.position(35))
)
test.wait_for_events()
test.socket.capability:__queue_receive(
{
mock_blind.id,
{ capability = "windowShadePreset", command = "presetPosition", args = {} }
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
test.socket.capability:__expect_send(
Expand Down Expand Up @@ -374,7 +385,7 @@ test.register_coroutine_test(
test.socket.capability:__queue_receive(
{
mock_blind_v3.id,
{ capability = "windowShadePreset", command = "presetPosition", args = {} }
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
test.socket.capability:__expect_send(
Expand Down Expand Up @@ -415,7 +426,7 @@ test.register_coroutine_test(
test.socket.capability:__queue_receive(
{
mock_blind_v3.id,
{ capability = "windowShadePreset", command = "presetPosition", args = {} }
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
test.socket.capability:__expect_send(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ local zw = require "st.zwave"
local zw_test_utils = require "integration_test.zwave_test_utils"
local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ version=4 })
local t_utils = require "integration_test.utils"
local capabilities = require "st.capabilities"

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

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

Expand All @@ -48,7 +52,7 @@ test.register_coroutine_test(
test.socket.capability:__queue_receive(
{
mock_springs_window_fashion_shade.id,
{ capability = "windowShadePreset", command = "presetPosition", args = {} }
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
test.socket.zwave:__expect_send(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ local mock_window_shade_switch_multilevel = test.mock_device.build_test_zwave_de
local function test_init()
test.mock_device.add_test_device(mock_window_shade_basic)
test.mock_device.add_test_device(mock_window_shade_switch_multilevel)
test.socket.capability:__expect_send(
mock_window_shade_basic:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_window_shade_basic:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}}))
)
test.socket.capability:__expect_send(
mock_window_shade_switch_multilevel:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}}))
)
end
test.set_test_init_function(test_init)

Expand Down Expand Up @@ -304,7 +316,7 @@ test.register_coroutine_test(
test.socket.capability:__queue_receive(
{
mock_window_shade_switch_multilevel.id,
{ capability = "windowShadePreset", command = "presetPosition", args = {} }
{ capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} }
}
)
test.socket.zwave:__expect_send(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- Copyright 2025 SmartThings
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.


-- These were added to scripting engine, but this file is to make sure drivers
-- runing on older versions of scripting engine can still access these values
local constants = {}

--- WINDOW SHADE PRESET CONSTANTS
constants.PRESET_LEVEL = 50
constants.PRESET_LEVEL_KEY = "_presetLevel"

return constants
Loading