diff --git a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml index 4dc77d026d..cce4b68d2d 100644 --- a/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml +++ b/drivers/SmartThings/matter-switch/profiles/ikea-scroll.yml @@ -5,6 +5,8 @@ components: capabilities: - id: button version: 1 + - id: knob + version: 1 - id: battery version: 1 - id: firmwareUpdate @@ -18,6 +20,8 @@ components: capabilities: - id: button version: 1 + - id: knob + version: 1 categories: - name: RemoteController - id: group3 @@ -25,5 +29,7 @@ components: capabilities: - id: button version: 1 + - id: knob + version: 1 categories: - name: RemoteController diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua index 6e21a56ac0..30aa5c332f 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/init.lua @@ -1,9 +1,11 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local clusters = require "st.matter.clusters" local switch_utils = require "switch_utils.utils" local scroll_utils = require "sub_drivers.ikea_scroll.scroll_utils.utils" local scroll_cfg = require "sub_drivers.ikea_scroll.scroll_utils.device_configuration" +local event_handlers = require "sub_drivers.ikea_scroll.scroll_handlers.event_handlers" local IkeaScrollLifecycleHandlers = {} @@ -44,7 +46,15 @@ local ikea_scroll_handler = { infoChanged = IkeaScrollLifecycleHandlers.info_changed, init = IkeaScrollLifecycleHandlers.device_init, }, + matter_handlers = { + event = { + [clusters.Switch.ID] = { + [clusters.Switch.events.InitialPress.ID] = event_handlers.initial_press_handler, + [clusters.Switch.events.MultiPressOngoing.ID] = event_handlers.multi_press_ongoing_handler, + } + } + }, can_handle = require("sub_drivers.ikea_scroll.can_handle") } -return ikea_scroll_handler +return ikea_scroll_handler \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua new file mode 100644 index 0000000000..ae742c2216 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_handlers/event_handlers.lua @@ -0,0 +1,39 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local st_utils = require "st.utils" +local capabilities = require "st.capabilities" +local switch_utils = require "switch_utils.utils" +local generic_event_handlers = require "switch_handlers.event_handlers" +local scroll_fields = require "sub_drivers.ikea_scroll.scroll_utils.fields" + +local IkeaScrollEventHandlers = {} + +local function rotate_amount_event_helper(device, endpoint_id, num_presses_to_handle) + -- to cut down on checks, we can assume that if the endpoint is not in ENDPOINTS_UP_SCROLL, it is in ENDPOINTS_DOWN_SCROLL + local scroll_direction = switch_utils.tbl_contains(scroll_fields.ENDPOINTS_UP_SCROLL, endpoint_id) and 1 or -1 + local scroll_amount = st_utils.clamp_value(scroll_direction * scroll_fields.PER_SCROLL_EVENT_ROTATION * num_presses_to_handle, -100, 100) + device:emit_event_for_endpoint(endpoint_id, capabilities.knob.rotateAmount(scroll_amount, {state_change = true})) +end + +function IkeaScrollEventHandlers.initial_press_handler(driver, device, ib, response) + -- use the generic handler logic for the push endpoints. Else, use custom logic. + if switch_utils.tbl_contains(scroll_fields.ENDPOINTS_PUSH, ib.endpoint_id) then + generic_event_handlers.initial_press_handler(driver, device, ib, response) + else + device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED, 1) + rotate_amount_event_helper(device, ib.endpoint_id, 1) + end +end + +-- Used by ENDPOINTS_UP_SCROLL and ENDPOINTS_DOWN_SCROLL, not ENDPOINTS_PUSH +function IkeaScrollEventHandlers.multi_press_ongoing_handler(driver, device, ib, response) + local cur_num_presses_counted = ib.data and ib.data.elements and ib.data.elements.current_number_of_presses_counted.value or 0 + local num_presses_to_handle = cur_num_presses_counted - (device:get_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED) or 0) + if num_presses_to_handle > 0 then + device:set_field(scroll_fields.LATEST_NUMBER_OF_PRESSES_COUNTED, cur_num_presses_counted) + rotate_amount_event_helper(device, ib.endpoint_id, num_presses_to_handle) + end +end + +return IkeaScrollEventHandlers diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/device_configuration.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/device_configuration.lua index cd2cee49ce..1d88a2f3a3 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/device_configuration.lua @@ -11,18 +11,20 @@ local IkeaScrollConfiguration = {} function IkeaScrollConfiguration.build_button_component_map(device) local component_map = { - main = scroll_fields.ENDPOINTS_PRESS[1], - group2 = scroll_fields.ENDPOINTS_PRESS[2], - group3 = scroll_fields.ENDPOINTS_PRESS[3], + main = {scroll_fields.ENDPOINTS_PUSH[1], scroll_fields.ENDPOINTS_UP_SCROLL[1], scroll_fields.ENDPOINTS_DOWN_SCROLL[1]}, + group2 = {scroll_fields.ENDPOINTS_PUSH[2], scroll_fields.ENDPOINTS_UP_SCROLL[2], scroll_fields.ENDPOINTS_DOWN_SCROLL[2]}, + group3 = {scroll_fields.ENDPOINTS_PUSH[3], scroll_fields.ENDPOINTS_UP_SCROLL[3], scroll_fields.ENDPOINTS_DOWN_SCROLL[3]}, } device:set_field(switch_fields.COMPONENT_TO_ENDPOINT_MAP, component_map, {persist = true}) end function IkeaScrollConfiguration.configure_buttons(device) - for _, ep in ipairs(scroll_fields.ENDPOINTS_PRESS) do + for _, ep in ipairs(scroll_fields.ENDPOINTS_PUSH) do device:send(clusters.Switch.attributes.MultiPressMax:read(device, ep)) switch_utils.set_field_for_endpoint(device, switch_fields.SUPPORTS_MULTI_PRESS, ep, true, {persist = true}) device:emit_event_for_endpoint(ep, capabilities.button.button.pushed({state_change = false})) + -- though unrelated to the knob capability, the push endpoints all map to components including a knob + device:emit_event_for_endpoint(ep, capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}})) end end diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/fields.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/fields.lua index fff0a1cce4..f0aeb77c5a 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/fields.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/fields.lua @@ -1,6 +1,7 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local st_utils = require "st.utils" local clusters = require "st.matter.clusters" local IkeaScrollFields = {} @@ -8,14 +9,37 @@ local IkeaScrollFields = {} -- PowerSource supported on Root Node IkeaScrollFields.ENDPOINT_POWER_SOURCE = 0 --- Switch Endpoints used for basic press functionality -IkeaScrollFields.ENDPOINTS_PRESS = {3, 6, 9} +-- Generic Switch Endpoints used for basic push functionality +IkeaScrollFields.ENDPOINTS_PUSH = {3, 6, 9} --- Required Events for the ENDPOINTS_PRESS. +-- Generic Switch Endpoints used for Up Scroll functionality +IkeaScrollFields.ENDPOINTS_UP_SCROLL = {1, 4, 7} + +-- Generic Switch Endpoints used for Down Scroll functionality +IkeaScrollFields.ENDPOINTS_DOWN_SCROLL = {2, 5, 8} + +-- Maximum number of presses at a time +IkeaScrollFields.MAX_SCROLL_PRESSES = 18 + +-- Amount to rotate per scroll event +IkeaScrollFields.PER_SCROLL_EVENT_ROTATION = st_utils.round(1 / IkeaScrollFields.MAX_SCROLL_PRESSES * 100) + +-- Field to track the latest number of presses counted during a single scroll event sequence +IkeaScrollFields.LATEST_NUMBER_OF_PRESSES_COUNTED = "__latest_number_of_presses_counted" + +-- Required Events for the ENDPOINTS_PUSH. IkeaScrollFields.switch_press_subscribed_events = { clusters.Switch.events.InitialPress.ID, clusters.Switch.events.MultiPressComplete.ID, clusters.Switch.events.LongPress.ID, } +-- Required Events for the ENDPOINTS_UP_SCROLL and ENDPOINTS_DOWN_SCROLL. Adds a +-- MultiPressOngoing subscription to handle step functionality in real-time, and +-- removes MultiPressComplete subscription due to the real-time handling +IkeaScrollFields.switch_scroll_subscribed_events = { + clusters.Switch.events.InitialPress.ID, + clusters.Switch.events.MultiPressOngoing.ID, +} + return IkeaScrollFields diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/utils.lua index 67ba2acba5..9a9f95228b 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/utils.lua @@ -7,12 +7,24 @@ local scroll_fields = require "sub_drivers.ikea_scroll.scroll_utils.fields" local IkeaScrollUtils = {} --- override subscribe function to prevent subscribing to additional events from the main driver +-- override subscribe function in the main driver function IkeaScrollUtils.subscribe(device) local subscribe_request = im.InteractionRequest(im.InteractionRequest.RequestType.SUBSCRIBE, {}) - for _, ep_press in ipairs(scroll_fields.ENDPOINTS_PRESS) do + for _, ep_push in ipairs(scroll_fields.ENDPOINTS_PUSH) do for _, switch_event in ipairs(scroll_fields.switch_press_subscribed_events) do - local ib = im.InteractionInfoBlock(ep_press, clusters.Switch.ID, nil, switch_event) + local ib = im.InteractionInfoBlock(ep_push, clusters.Switch.ID, nil, switch_event) + subscribe_request:with_info_block(ib) + end + end + for _, ep_up in ipairs(scroll_fields.ENDPOINTS_UP_SCROLL) do + for _, switch_event in ipairs(scroll_fields.switch_scroll_subscribed_events) do + local ib = im.InteractionInfoBlock(ep_up, clusters.Switch.ID, nil, switch_event) + subscribe_request:with_info_block(ib) + end + end + for _, ep_down in ipairs(scroll_fields.ENDPOINTS_DOWN_SCROLL) do + for _, switch_event in ipairs(scroll_fields.switch_scroll_subscribed_events) do + local ib = im.InteractionInfoBlock(ep_down, clusters.Switch.ID, nil, switch_event) subscribe_request:with_info_block(ib) end end diff --git a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua index b258688234..91eb4a8cc2 100644 --- a/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua +++ b/drivers/SmartThings/matter-switch/src/switch_utils/utils.lua @@ -201,7 +201,8 @@ end --- An extension of the library function endpoint_to_component, used to support a mapping scheme --- that optionally includes cluster and attribute ids so that multiple components can be mapped ---- to a single endpoint. +--- to a single endpoint. This extension also handles the case that multiple endpoints map to the +--- same component --- --- @param device any a Matter device object --- @param ep_info number|table either an ep_id or a table { endpoint_id, optional(cluster_id), optional(attribute_id) } @@ -214,10 +215,19 @@ function utils.endpoint_to_component(device, ep_info) for component, map_info in pairs(device:get_field(fields.COMPONENT_TO_ENDPOINT_MAP) or {}) do if type(map_info) == "number" and map_info == ep_info.endpoint_id then return component - elseif type(map_info) == "table" and map_info.endpoint_id == ep_info.endpoint_id - and (not map_info.cluster_id or (map_info.cluster_id == ep_info.cluster_id - and (not map_info.attribute_ids or utils.tbl_contains(map_info.attribute_ids, ep_info.attribute_id)))) then - return component + elseif type(map_info) == "table" then + if type(map_info.endpoint_id) == "number" then + map_info = {map_info} + end + for _, ep_map_info in ipairs(map_info) do + if type(ep_map_info) == "number" and ep_map_info == ep_info.endpoint_id then + return component + elseif type(ep_map_info) == "table" and ep_map_info.endpoint_id == ep_info.endpoint_id + and (not ep_map_info.cluster_id or (ep_map_info.cluster_id == ep_info.cluster_id + and (not ep_map_info.attribute_ids or utils.tbl_contains(ep_map_info.attribute_ids, ep_info.attribute_id)))) then + return component + end + end end end return "main" diff --git a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua index c560386e37..126b30d7bc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_ikea_scroll.lua @@ -122,18 +122,28 @@ local mock_ikea_scroll = test.mock_device.build_test_matter_device({ } }) -local ENDPOINTS_PRESS = { 3, 6, 9 } +local ENDPOINTS_PUSH = { 3, 6, 9 } +local ENDPOINTS_SCROLL = {1, 2, 4, 5, 7, 8} -- the ikea scroll subdriver has overriden subscribe behavior local function ikea_scroll_subscribe() - local CLUSTER_SUBSCRIBE_LIST ={ - clusters.Switch.events.InitialPress, + local CLUSTER_SUBSCRIBE_LIST_PUSH ={ + clusters.Switch.events.InitialPress, clusters.Switch.server.events.LongPress, clusters.Switch.server.events.MultiPressComplete, } - local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_ikea_scroll, ENDPOINTS_PRESS[1]) - for _, ep_press in ipairs(ENDPOINTS_PRESS) do - for _, event in ipairs(CLUSTER_SUBSCRIBE_LIST) do + local CLUSTER_SUBSCRIBE_LIST_SCROLL = { + clusters.Switch.events.InitialPress, + clusters.Switch.server.events.MultiPressOngoing, + } + local subscribe_request = CLUSTER_SUBSCRIBE_LIST_PUSH[1]:subscribe(mock_ikea_scroll, ENDPOINTS_PUSH[1]) + for _, ep_press in ipairs(ENDPOINTS_PUSH) do + for _, event in ipairs(CLUSTER_SUBSCRIBE_LIST_PUSH) do + subscribe_request:merge(event:subscribe(mock_ikea_scroll, ep_press)) + end + end + for _, ep_press in ipairs(ENDPOINTS_SCROLL) do + for _, event in ipairs(CLUSTER_SUBSCRIBE_LIST_SCROLL) do subscribe_request:merge(event:subscribe(mock_ikea_scroll, ep_press)) end end @@ -145,10 +155,13 @@ local function expect_configure_buttons() local button_attr = capabilities.button.button test.socket.matter:__expect_send({mock_ikea_scroll.id, clusters.Switch.attributes.MultiPressMax:read(mock_ikea_scroll, 3)}) test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", button_attr.pushed({state_change = false}))) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("main", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) test.socket.matter:__expect_send({mock_ikea_scroll.id, clusters.Switch.attributes.MultiPressMax:read(mock_ikea_scroll, 6)}) test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group2", button_attr.pushed({state_change = false}))) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group2", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) test.socket.matter:__expect_send({mock_ikea_scroll.id, clusters.Switch.attributes.MultiPressMax:read(mock_ikea_scroll, 9)}) test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group3", button_attr.pushed({state_change = false}))) + test.socket.capability:__expect_send(mock_ikea_scroll:generate_test_message("group3", capabilities.knob.supportedAttributes({"rotateAmount"}, {visibility = {displayed = false}}))) end local function test_init() @@ -221,4 +234,384 @@ test.register_message_test( } ) -test.run_registered_tests() \ No newline at end of file +test.register_message_test( + "Ikea Scroll Positive rotateAmount events on main are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[1], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Negative rotateAmount events on main are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[2], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.knob.rotateAmount(-18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Positive rotateAmount events on group2 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[3], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Negative rotateAmount events on group2 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[4], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[4], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[4], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.knob.rotateAmount(-18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Positive rotateAmount events on group3 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[5], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Negative rotateAmount events on group3 are emitted correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[6], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[6], {current_number_of_presses_counted = 2, new_position = 2} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(-6, {state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressOngoing:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_SCROLL[6], {current_number_of_presses_counted = 5, new_position = 5} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group3", + capabilities.knob.rotateAmount(-18, {state_change = true})) + } + } +) + +test.register_message_test( + "Ikea Scroll Long Press Push events on main are handled correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_PUSH[1], {new_position = 1} + ) + }, + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.LongPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_PUSH[1], {new_position = 1} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("main", + capabilities.button.button.held({state_change = true})) + }, + } +) + +test.register_message_test( + "Ikea Scroll MultiPressComplete Push events on group2 are handled correctly", { + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.InitialPress:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_PUSH[2], {new_position = 1} + ) + }, + }, + { + channel = "matter", + direction = "receive", + message = { + mock_ikea_scroll.id, + clusters.Switch.events.MultiPressComplete:build_test_event_report( + mock_ikea_scroll, ENDPOINTS_PUSH[2], {total_number_of_presses_counted = 1, previous_position = 0} + ) + }, + }, + { + channel = "capability", + direction = "send", + message = mock_ikea_scroll:generate_test_message("group2", + capabilities.button.button.pushed({state_change = true})) + }, + } +) + +test.run_registered_tests()