diff --git a/.gitignore b/.gitignore index ea01709e90..c5bbdc6f99 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ lua_libs-api_* tools/test_output/* tools/coverage_output/* .DS_Store +.venv/ diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml index 275697cb27..be0ae73d76 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-battery.yml @@ -16,6 +16,3 @@ components: version: 1 categories: - name: Blind -preferences: - - preferenceId: presetPosition - explicit: true diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-powerSource.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-powerSource.yml index 7c5255527f..7323ee0218 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-powerSource.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-powerSource.yml @@ -17,7 +17,4 @@ components: - id: firmwareUpdate version: 1 categories: - - name: Blind -preferences: - - preferenceId: presetPosition - explicit: true \ No newline at end of file + - name: Blind \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile-no-firmware-update.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile-no-firmware-update.yml index ded16d963b..8e641e6b42 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile-no-firmware-update.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile-no-firmware-update.yml @@ -11,7 +11,4 @@ components: - id: refresh version: 1 categories: - - name: Blind -preferences: - - preferenceId: presetPosition - explicit: true + - name: Blind \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile.yml index 933dd85321..b10a5ea101 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-profile.yml @@ -14,7 +14,3 @@ components: version: 1 categories: - name: Blind -preferences: - - preferenceId: presetPosition - explicit: true - diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-reverse.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-reverse.yml index 91b21811f9..0eb666a5d2 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-reverse.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-reverse.yml @@ -18,5 +18,3 @@ components: preferences: - preferenceId: reverse explicit: true - - preferenceId: presetPosition - explicit: true diff --git a/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/init.lua index 1bc09cbb6b..c0682d73c0 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/axis/axis_version/init.lua @@ -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" @@ -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 diff --git a/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua index acd67d2d32..a47de06df7 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/axis/init.lua @@ -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" @@ -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 diff --git a/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua index 0c1d20be11..f33bf81cbd 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/feibit/init.lua @@ -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 @@ -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 diff --git a/drivers/SmartThings/zigbee-window-treatment/src/hanssem/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/hanssem/init.lua index bc974706b6..be956d79b2 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/hanssem/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/hanssem/init.lua @@ -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" @@ -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 diff --git a/drivers/SmartThings/zigbee-window-treatment/src/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/init.lua index fc3535c043..933e0d6977 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/init.lua @@ -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) + 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 }})) @@ -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, } diff --git a/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/init.lua index 62c3afc7eb..19532bc847 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/invert-lift-percentage/init.lua @@ -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 @@ -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 = { diff --git a/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/init.lua index 11d4ff17f9..868e92af56 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/screen-innovations/init.lua @@ -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" @@ -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 diff --git a/drivers/SmartThings/zigbee-window-treatment/src/somfy/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/somfy/init.lua index c90a0c833a..ffc9541b64 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/somfy/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/somfy/init.lua @@ -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 @@ -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 diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua index 4fee02c00f..4fe49568eb 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua @@ -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) @@ -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( { diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua index 106730e7fd..d51f303378 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua @@ -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) @@ -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 ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua index b177eaacec..6380e5ce51 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment.lua @@ -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) @@ -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) + } + }, } ) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua index 3148f48965..5068f889a5 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua @@ -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) @@ -458,8 +465,9 @@ 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()) @@ -467,7 +475,7 @@ test.register_coroutine_test( 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 diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_feibit.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_feibit.lua index 747895e515..c65d38281a 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_feibit.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_feibit.lua @@ -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) @@ -221,9 +228,10 @@ test.register_coroutine_test( { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } } ) + -- newly joined devices will ignore the preference test.socket.zigbee:__expect_send({ mock_device.id, - Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(30/100 * 254)) + Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(50/100 * 254)) }) end ) @@ -257,9 +265,10 @@ test.register_coroutine_test( { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } } ) + -- newly joined devices will ignore the preference test.socket.zigbee:__expect_send({ mock_device.id, - Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(100/100 * 254)) + Level.server.commands.MoveToLevelWithOnOff(mock_device,math.floor(50/100 * 254)) }) end ) @@ -267,7 +276,16 @@ test.register_coroutine_test( test.register_coroutine_test( "windowShadePreset capability should be handled with preset value = 1 ", function() - test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 1}})) + test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 10}})) + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = {1} } + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadePreset.position(1)) + ) test.wait_for_events() test.socket.capability:__queue_receive( { @@ -285,7 +303,16 @@ test.register_coroutine_test( test.register_coroutine_test( "windowShadePreset capability should be handled with a positive preset value of < 1", function() - test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 0}})) + test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 1}})) + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = {0} } + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadePreset.position(0)) + ) test.wait_for_events() test.socket.capability:__queue_receive( { diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_hanssem.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_hanssem.lua index a0e0fbf844..23814eff28 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_hanssem.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_hanssem.lua @@ -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) @@ -278,7 +285,13 @@ test.register_coroutine_test( test.register_coroutine_test( "Preset position handler", 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.zigbee:__queue_receive({ diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua index 717731124e..b7f630cf71 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_screen_innovations.lua @@ -45,7 +45,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) diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua index 2e7c140f17..c861b3470c 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua @@ -38,7 +38,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) @@ -356,7 +363,13 @@ test.register_message_test( test.register_coroutine_test( "windowShadePreset capability should be handled with preset value of 1", function() - test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 1}})) + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = {1}} + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadePreset.position(1))) test.wait_for_events() test.socket.capability:__queue_receive( { @@ -374,7 +387,13 @@ test.register_coroutine_test( test.register_coroutine_test( "windowShadePreset capability should be handled with preset value of 100", function() - test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 100}})) + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = {100}} + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadePreset.position(100))) test.wait_for_events() test.socket.capability:__queue_receive( { @@ -408,27 +427,15 @@ test.register_coroutine_test( ) test.register_coroutine_test( - "windowShadePreset capability should be handled with preset value of > 100", + "windowShadePreset capability should be handled with preset value of < 1 (but positive)", function() - test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 101}})) - test.wait_for_events() test.socket.capability:__queue_receive( { mock_device.id, - { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } + { capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = {0}} } ) - test.socket.zigbee:__expect_send({ - mock_device.id, - WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) - }) - end -) - -test.register_coroutine_test( - "windowShadePreset capability should be handled with preset value of < 1 (but positive)", - function() - test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({preferences = {presetPosition = 0}})) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.windowShadePreset.position(0))) test.wait_for_events() test.socket.capability:__queue_receive( { diff --git a/drivers/SmartThings/zigbee-window-treatment/src/vimar/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/vimar/init.lua index 6d2d6bb11d..9fa928645a 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/vimar/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/vimar/init.lua @@ -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 local windowShade = capabilities.windowShade.windowShade @@ -124,7 +124,7 @@ end -- COMMAND HANDLER for PresetPosition local function window_shade_preset_handler(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_set_level_handler(driver, device, command) end @@ -134,6 +134,20 @@ local device_init = function(self, device) -- Reset Status device:set_field(VIMAR_SHADES_CLOSING, false) device:set_field(VIMAR_SHADES_OPENING, false) + + -- for windowshadepreset update migration + 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 removed after migration is complete + device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, { visibility = { displayed = false }})) + + local preset_position = window_shade_utils.get_preset_level(device, "main") + + 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 -- DRIVER HANDLER CONFIGURATION diff --git a/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua b/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua new file mode 100644 index 0000000000..262e549c2d --- /dev/null +++ b/drivers/SmartThings/zigbee-window-treatment/src/window_shade_utils.lua @@ -0,0 +1,44 @@ +-- 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. +local capabilities = require "st.capabilities" +local zcl_clusters = require "st.zigbee.zcl.clusters" + +local utils = {} + +utils.PRESET_LEVEL = 50 +utils.PRESET_LEVEL_KEY = "_presetLevel" + +utils.get_preset_level = function(device, component) + local level = device:get_latest_state(component, "windowShadePreset", "position") or + device:get_field(utils.PRESET_LEVEL_KEY) or + (device.preferences ~= nil and device.preferences.presetPosition) or + utils.PRESET_LEVEL + + return level +end + +utils.window_shade_preset_cmd = function(driver, device, command) + local level = device:get_latest_state(command.component, "windowShadePreset", "position") or + device:get_field(utils.PRESET_LEVEL_KEY) or + (device.preferences ~= nil and device.preferences.presetPosition) or + utils.PRESET_LEVEL + device:send_to_component(command.component, zcl_clusters.WindowCovering.server.commands.GoToLiftPercentage(device, level)) +end + +utils.set_preset_position_cmd = function(driver, device, command) + device:emit_component_event({id = command.component}, capabilities.windowShadePreset.position(command.args.position)) + device:set_field(utils.PRESET_LEVEL_KEY, command.args.position, {persist = true}) +end + +return utils \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-window-treatment/src/yoolax/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/yoolax/init.lua index d98924f54a..46cb33fed2 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/yoolax/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/yoolax/init.lua @@ -17,6 +17,7 @@ local zcl_clusters = require "st.zigbee.zcl.clusters" local zcl_global_commands = require "st.zigbee.zcl.global_commands" local Status = require "st.zigbee.generated.types.ZclStatus" local WindowCovering = zcl_clusters.WindowCovering +local window_shade_utils = require "window_shade_utils" local device_management = require "st.zigbee.device_management" @@ -79,7 +80,8 @@ local function window_shade_level_cmd(driver, device, command) end local function window_shade_preset_cmd(driver, device, command) - set_shade_level(driver, device, device.preferences.presetPosition, command) + local level = window_shade_utils.get_preset_level(device, command.component) + set_shade_level(driver, device, level, command) end local function set_window_shade_level(level) diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_fibaro_roller_shutter.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_fibaro_roller_shutter.lua index 9c800e7070..9c879806f6 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_fibaro_roller_shutter.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_fibaro_roller_shutter.lua @@ -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( diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_qubino_flush_shutter.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_qubino_flush_shutter.lua index ce5ed9fb81..5431ed1e70 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_qubino_flush_shutter.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_qubino_flush_shutter.lua @@ -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( diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua index bdc41956ec..f1a841c626 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_iblinds_window_treatment.lua @@ -214,7 +214,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( @@ -251,7 +251,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( @@ -374,7 +374,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( @@ -415,7 +415,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( diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua index 4335ae3b41..0b2d209f2a 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_springs_window_treatment.lua @@ -48,7 +48,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( diff --git a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua index bf60c1797b..bc3a087093 100644 --- a/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua +++ b/drivers/SmartThings/zwave-window-treatment/src/test/test_zwave_window_treatment.lua @@ -304,7 +304,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( diff --git a/drivers/Unofficial/tuya-zigbee/src/curtain/init.lua b/drivers/Unofficial/tuya-zigbee/src/curtain/init.lua index 1eb2e94340..bbd20396a0 100644 --- a/drivers/Unofficial/tuya-zigbee/src/curtain/init.lua +++ b/drivers/Unofficial/tuya-zigbee/src/curtain/init.lua @@ -17,10 +17,12 @@ local clusters = require "st.zigbee.zcl.clusters" local utils = require "st.utils" local device_management = require "st.zigbee.device_management" local tuya_utils = require "tuya_utils" -local window_preset_defaults = require "st.zigbee.defaults.windowShadePreset_defaults" local Basic = clusters.Basic local packet_id = 0 +local PRESET_LEVEL = 50 +local PRESET_LEVEL_KEY = "_presetLevel" + local FINGERPRINTS = { { mfr = "_TZE284_nladmfvf", model = "TS0601"} } @@ -34,6 +36,23 @@ local function is_tuya_curtain(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.position.NAME) == nil then + + -- These should only ever be nil once (and at the same time) for already-installed devices + -- It can be removed after migration is complete + device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, { visibility = { displayed = false }})) + + local preset_position = device:get_field(PRESET_LEVEL_KEY) or + (device.preferences ~= nil and device.preferences.presetPosition) or + PRESET_LEVEL + + device:emit_event(capabilities.windowShadePreset.position(preset_position, { visibility = {displayed = false}})) + device:set_field(PRESET_LEVEL_KEY, preset_position, {persist = true}) + end +end + local do_configure = function(driver, device) -- configure ApplicationVersion to keep device online, tuya hub also uses this attribute tuya_utils.send_magic_spell(device) @@ -97,11 +116,19 @@ local function window_shade_level(driver, device, command) end local function window_shade_preset(driver, device) - local level = device.preferences and device.preferences.presetPosition or window_preset_defaults.PRESET_LEVEL + local level = device:get_latest_state("main", "windowShadePreset", "position") or + device:get_field(PRESET_LEVEL_KEY) or + (device.preferences ~= nil and device.preferences.presetPosition) or + PRESET_LEVEL tuya_utils.send_tuya_command(device, '\x02', tuya_utils.DP_TYPE_VALUE, '\x00\x00'..string.pack(">I2", level), packet_id) packet_id = increase_packet_id(packet_id) end +local function set_preset_position_cmd(driver, device, command) + device:emit_component_event({id = command.component}, capabilities.windowShadePreset.position(command.args.position)) + device:set_field(PRESET_LEVEL_KEY, command.args.position, {persist = true}) +end + local function tuya_cluster_handler(driver, device, zb_rx) local window_shade_level_event, window_shade_val_event local raw = zb_rx.body.zcl_body.body_bytes @@ -127,6 +154,7 @@ end local tuya_curtain_driver = { NAME = "tuya curtain", lifecycle_handlers = { + init = init_handler, added = device_added, infoChanged = device_info_changed, doConfigure = do_configure @@ -141,7 +169,8 @@ local tuya_curtain_driver = { [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level }, [capabilities.windowShadePreset.ID] = { - [capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_preset + [capabilities.windowShadePreset.commands.presetPosition.NAME] = window_shade_preset, + [capabilities.windowShadePreset.commands.setPresetPosition.NAME] = set_preset_position_cmd } }, zigbee_handlers = { diff --git a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua index d6beda0de8..024a58cdc5 100644 --- a/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua +++ b/drivers/Unofficial/tuya-zigbee/src/test/test_tuya_curtain.lua @@ -39,7 +39,14 @@ local mock_simple_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_simple_device)end + test.mock_device.add_test_device(mock_simple_device) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}})) + ) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}})) + ) +end test.set_test_init_function(test_init) @@ -107,6 +114,12 @@ test.register_coroutine_test( capabilities.windowShade.windowShade.closed() ) ) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed=false}})) + ) + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.windowShadePreset.position(50, {visibility = {displayed=false}})) + ) end )