Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading