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 index a6a7ce9168..dbad323784 100644 --- 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 @@ -17,7 +17,9 @@ local function rotate_amount_event_helper(device, endpoint_id, num_presses_to_ha 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})) + if event_utils.is_valid_scroll_amount(device, scroll_amount) then + device:emit_event_for_endpoint(endpoint_id, capabilities.knob.rotateAmount(scroll_amount, {state_change = true})) + end end -- Used by ENDPOINTS_UP_SCROLL and ENDPOINTS_DOWN_SCROLL, not ENDPOINTS_PUSH diff --git a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua index c838ad5c67..8a7a684cf6 100644 --- a/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua +++ b/drivers/SmartThings/matter-switch/src/sub_drivers/ikea_scroll/scroll_utils/event_utils.lua @@ -1,10 +1,36 @@ -- Copyright © 2026 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local st_utils = require "st.utils" local clusters = require "st.matter.clusters" +local scroll_fields = require "sub_drivers.ikea_scroll.scroll_utils.fields" local IkeaScrollEventUtils = {} + +function IkeaScrollEventUtils.requeue_clear_scroll_state(device) + -- cancel any previously queued clear state actions to prevent unintended clears + if device:get_field(scroll_fields.CLEAR_STATE_TIMER) then + device.thread:cancel_timer(device:get_field(scroll_fields.CLEAR_STATE_TIMER)) + end + local new_timer = device.thread:call_with_delay(scroll_fields.CLEAR_STATE_DELAY_S, function() + device:set_field(scroll_fields.GLOBAL_ROTATE_AMOUNT_STATE, 0) + end) + device:set_field(scroll_fields.CLEAR_STATE_TIMER, new_timer) +end + +function IkeaScrollEventUtils.is_valid_scroll_amount(device, scroll_amount) + local global_rotate_amount_state = device:get_field(scroll_fields.GLOBAL_ROTATE_AMOUNT_STATE) or 0 + local is_rotate_amount_state_at_bounds = (scroll_amount < 0 and global_rotate_amount_state <= -100) or (scroll_amount > 0 and global_rotate_amount_state >= 100) + if is_rotate_amount_state_at_bounds then + return false + end + + device:set_field(scroll_fields.GLOBAL_ROTATE_AMOUNT_STATE, st_utils.clamp_value(global_rotate_amount_state + scroll_amount, -100, 100)) + IkeaScrollEventUtils.requeue_clear_scroll_state(device) + return true +end + -- inspect all info blocks to find the last one that is not an InitialPress event. We will -- only try to emit a rotateAmount event if the current info block being handled is that last one. function IkeaScrollEventUtils.is_last_valid_info_block(cur_info_block_event_id, cur_info_block_value, info_blocks) 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 451111f0fb..27e0dbcd6d 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 @@ -24,6 +24,15 @@ IkeaScrollFields.PER_SCROLL_EVENT_ROTATION = 6 -- Field to track the latest number of presses handled during a single scroll event sequence IkeaScrollFields.LATEST_NUMBER_OF_PRESSES_HANDLED = "__latest_number_of_presses_handled" +-- Field to track the global rotate amount state for the device to ensure no scroll events mapped outside of state bounds are emitted +IkeaScrollFields.GLOBAL_ROTATE_AMOUNT_STATE = "__global_rotate_amount_state" + +-- Stores a timer object, which is required to cancel a timer early +IkeaScrollFields.CLEAR_STATE_TIMER = "__clear_state_timer" + +-- Delay in seconds to wait before clearing the global rotate amount state after the last scroll event +IkeaScrollFields.CLEAR_STATE_DELAY_S = 8 + -- Required Events for the ENDPOINTS_PUSH. IkeaScrollFields.switch_press_subscribed_events = { clusters.Switch.events.InitialPress.ID,