blob: 9309b5a78aaa9c31ed995180484d97cde99513cd [file] [log] [blame]
/*
* Copyright 2016-2021, Cypress Semiconductor Corporation (an Infineon company) or
* an affiliate of Cypress Semiconductor Corporation. All rights reserved.
*
* This software, including source code, documentation and related
* materials ("Software") is owned by Cypress Semiconductor Corporation
* or one of its affiliates ("Cypress") and is protected by and subject to
* worldwide patent protection (United States and foreign),
* United States copyright laws and international treaty provisions.
* Therefore, you may use this Software only as provided in the license
* agreement accompanying the software package from which you
* obtained this Software ("EULA").
* If no EULA applies, Cypress hereby grants you a personal, non-exclusive,
* non-transferable license to copy, modify, and compile the Software
* source code solely for use in connection with Cypress's
* integrated circuit products. Any reproduction, modification, translation,
* compilation, or representation of this Software except as specified
* above is prohibited without the express written permission of Cypress.
*
* Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress
* reserves the right to make changes to the Software without notice. Cypress
* does not assume any liability arising out of the application or use of the
* Software or any product or circuit described in the Software. Cypress does
* not authorize its products for use in any products where a malfunction or
* failure of the Cypress product may reasonably be expected to result in
* significant property damage, injury or death ("High Risk Product"). By
* including Cypress's product in a High Risk Product, the manufacturer
* of such system or application assumes all risk of such use and in doing
* so agrees to indemnify Cypress against all liability.
*/
/** @file
*
* Button manager implements generic interface for button events and button type configurations.
* It exposes interface to configure platform button events (like click,long press) with user configurable timing.
*/
#include "wiced_button_manager.h"
#include "clock_timer.h"
#include "platform_button.h"
#include "string.h"
#include "wiced_bt_trace.h"
#ifdef CYW55572
#include "wiced_memory.h"
#include "wiced_misc_rtos_utils.h"
#endif
/******************************************************
* Macros
******************************************************/
#define BUTTON_TIMER_TIMEOUT (100) /*msec*/
/******************************************************
* Constants
******************************************************/
/******************************************************
* Enumerations
******************************************************/
/******************************************************
* Type Definitions
******************************************************/
#if BTSTACK_VER >= 0x03000001
#define TIMER_PARAM_TYPE WICED_TIMER_PARAM_TYPE
#endif
#ifdef CYW55572
#define BUTTON_EVENT_QUEUE_DEPTH 16
#endif
/******************************************************
* Structures
******************************************************/
#ifdef CYW55572
typedef struct
{
button_manager_button_t * p_button;
button_manager_event_t event;
} button_event_defer_to_mpaf_t;
#endif
/******************************************************
* Function Declarations
******************************************************/
static void button_state_change_callback(platform_button_t id, wiced_bool_t new_state);
static wiced_result_t button_pressed_event_handler(void * arg);
static wiced_result_t button_released_event_handler(void * arg);
static wiced_result_t deferred_button_timer_handler(void * arg);
static wiced_bool_t button_check_event_mask(button_manager_button_t * button, uint16_t new_event);
static void button_check_for_double_click(button_manager_button_t * button, button_manager_event_t * new_event);
static button_manager_event_t button_deduce_duration_event(button_manager_button_t * button, uint32_t current_interval);
static button_manager_button_t * get_button(platform_button_t id);
#ifdef CYW55572
static void button_event_defer_to_mpaf(void * arg);
#endif
/******************************************************
* Variables Definitions
******************************************************/
static button_manager_t * button_manager;
#ifdef CYW55572
static wiced_mutex_t * p_mutex_button_event;
static wiced_bt_buffer_q_t button_event_queue;
static wiced_bt_pool_t * p_button_event_pool = NULL;
#endif
/******************************************************
* Function Definitions
******************************************************/
/*
* button_long_press_detect_timeout_handler
*
* Timeout handler for button long press detect timer.
*
* The execution duration of this utility is defined in BUTTON_TIMER_TIMEOUT.
*/
static void button_long_press_detect_timeout_handler(TIMER_PARAM_TYPE arg)
{
button_manager_button_t * p_button = (button_manager_button_t *) arg;
/* Check if button is under debouncing state. */
if (p_button->debouncing)
{
return;
}
/* Get current button state. */
p_button->current_state = (button_manager_button_state_t) platform_button_get_value(p_button->configuration->button);
if (p_button->current_state != BUTTON_STATE_HELD)
{
WICED_BT_TRACE("Err: Button %d is already released\n", p_button->configuration->button);
if (wiced_is_timer_in_use(&p_button->long_press_timer))
{
wiced_stop_timer(&p_button->long_press_timer);
}
return;
}
/* Get current timestatmp. */
p_button->timer_timestamp = clock_SystemTimeMicroseconds64();
deferred_button_timer_handler((void *) p_button);
}
/*
* button_debounce_timeout_handler
*
* Timeout handler for button debounce timer.
*/
static void button_debounce_timeout_handler(TIMER_PARAM_TYPE arg)
{
button_manager_button_t * p_button = (button_manager_button_t *) arg;
wiced_result_t result;
// WICED_BT_TRACE("button_debounce_timeout_handler (%d, %d)\n", p_button->configuration->button, p_button->debounce_counter);
if (p_button->debounce_counter > 0)
{
/* Reset the button debounce counter. */
p_button->debounce_counter = 0;
button_pressed_event_handler((void *) p_button);
}
else
{
if (wiced_is_timer_in_use(&p_button->long_press_timer))
{
wiced_stop_timer(&p_button->long_press_timer);
}
}
/* Reset the button debounce state. */
p_button->debouncing = WICED_FALSE;
}
/**
* The application should call this function to Initialize the Button Manager
*/
wiced_result_t __attribute__((weak))
wiced_button_manager_init(button_manager_t * manager, const wiced_button_manager_configuration_t * configuration,
button_manager_button_t * buttons, uint32_t number_of_buttons)
{
uint32_t a;
memset(manager, 0, sizeof(*manager));
manager->configuration = configuration;
manager->buttons = buttons;
manager->number_of_buttons = number_of_buttons;
button_manager = manager;
for (a = 0; a < number_of_buttons; a++)
{
platform_button_init(buttons[a].configuration->button);
platform_button_enable(buttons[a].configuration->button);
buttons[a].current_state = BUTTON_STATE_RELEASED;
buttons[a].repeat = 0;
buttons[a].debounce_counter = 0;
buttons[a].debouncing = WICED_FALSE;
}
platform_button_register_state_change_callback(button_state_change_callback);
/* Initialize the timers used for detecting the long press event. */
for (a = 0; a < number_of_buttons; a++)
{
wiced_init_timer(&buttons[a].long_press_timer, button_long_press_detect_timeout_handler, (TIMER_PARAM_TYPE) &buttons[a],
WICED_MILLI_SECONDS_PERIODIC_TIMER);
}
/* Initialize the timers used for de-bounce. */
for (a = 0; a < number_of_buttons; a++)
{
wiced_init_timer(&buttons[a].debounce_timer, button_debounce_timeout_handler, (TIMER_PARAM_TYPE) &buttons[a],
WICED_MILLI_SECONDS_TIMER);
}
#ifdef CYW55572
p_button_event_pool =
wiced_bt_create_pool("Button Event", sizeof(button_event_defer_to_mpaf_t), BUTTON_EVENT_QUEUE_DEPTH, NULL);
if (!p_button_event_pool)
{
WICED_BT_TRACE("%s: Fail to create pool.\n", __FUNCTION__);
return WICED_ERROR;
}
wiced_bt_init_q(&button_event_queue, NULL);
p_mutex_button_event = wiced_rtos_create_mutex();
if (p_mutex_button_event == NULL)
{
WICED_BT_TRACE("%s: Fail to create mutex.\n", __FUNCTION__);
return WICED_ERROR;
}
if (wiced_rtos_init_mutex(p_mutex_button_event) != WICED_SUCCESS)
{
WICED_BT_TRACE("%s: Fail to init. mutex.\n", __FUNCTION__);
return WICED_ERROR;
}
#endif
return WICED_SUCCESS;
}
/**
* The application should call this function to de-Initialize the Button Manager
*
* @param manager : Pointer to button manager to de-initialize.
* @return : result.
*/
wiced_result_t wiced_button_manager_deinit(button_manager_t * manager)
{
uint32_t a;
for (a = 0; a < manager->number_of_buttons; a++)
{
platform_button_disable(manager->buttons[a].configuration->button);
platform_button_deinit(manager->buttons[a].configuration->button);
}
for (a = 0; a < manager->number_of_buttons; a++)
{
if (WICED_TRUE == wiced_is_timer_in_use(&manager->buttons[a].debounce_timer))
{
wiced_stop_timer(&manager->buttons[a].debounce_timer);
}
wiced_deinit_timer(&manager->buttons[a].debounce_timer);
if (WICED_TRUE == wiced_is_timer_in_use(&manager->buttons[a].long_press_timer))
{
wiced_stop_timer(&manager->buttons[a].long_press_timer);
}
wiced_deinit_timer(&manager->buttons[a].long_press_timer);
}
button_manager = NULL;
return WICED_SUCCESS;
}
/**
* Deferred Handler initiated from timer handler
*
* @param arg : Arguments passed by the timer framework to timer handler
* @return : result
*/
static wiced_result_t deferred_button_timer_handler(void * arg)
{
button_manager_button_t * p_button = (button_manager_button_t *) arg;
uint64_t duration; // us
button_manager_event_t new_held_event = 0;
/* Check current button state. */
if (p_button->current_state == BUTTON_STATE_RELEASED)
{
return WICED_SUCCESS;
}
/* Calculate the time difference. */
duration = p_button->timer_timestamp - p_button->pressed_timestamp; // us
duration = duration / 1000; // ms
/* deduce the event depending on the duration */
new_held_event = button_deduce_duration_event(p_button, (uint32_t) duration);
/*
* timers should be mainly interested in duration-specific events;
* let release_handler only report Click events to the application
*/
if (new_held_event == BUTTON_CLICK_EVENT)
{
return WICED_SUCCESS;
}
if (button_check_event_mask(p_button, new_held_event))
{
if (p_button->last_sent_event != BUTTON_HOLDING_EVENT)
{
if (p_button->last_sent_event != new_held_event)
{
button_manager->configuration->event_handler(p_button, new_held_event, p_button->current_state);
p_button->last_sent_event = new_held_event;
}
}
else
{
button_manager->configuration->event_handler(p_button, new_held_event, p_button->current_state);
p_button->last_sent_event = new_held_event;
}
}
return WICED_SUCCESS;
}
static void button_state_change_callback_pressed(button_manager_button_t * p_button)
{
/* Check if the button is under de-bounce state. */
if (p_button->debouncing)
{ // under de-bounce state
p_button->debounce_counter++;
}
else
{
/* ignore pressed event for already pressed button*/
if (p_button->current_state == BUTTON_STATE_HELD)
{
return;
}
/* Get current timestamp for pressed event. */
p_button->pressed_timestamp = clock_SystemTimeMicroseconds64();
/* Start the button debounce timer. */
wiced_start_timer(&p_button->debounce_timer, (uint32_t) button_manager->configuration->debounce_duration);
/* Start the long pressed event detect timer. */
wiced_start_timer(&p_button->long_press_timer, BUTTON_TIMER_TIMEOUT);
/* Update information. */
p_button->debouncing = WICED_TRUE;
p_button->debounce_counter = 1;
}
}
static void button_state_change_callback_released(button_manager_button_t * p_button)
{
wiced_result_t result;
/* Check if the button is under de-bounce state. */
if (p_button->debouncing)
{ // under de-bounce state
p_button->debounce_counter--;
}
else
{
/* ignore released event for already released button */
if (p_button->current_state == BUTTON_STATE_RELEASED)
{
return;
}
/* Get current timestamp for release event. */
p_button->released_timestamp = clock_SystemTimeMicroseconds64();
/* Stop the long pressed event detect timer. */
if (wiced_is_timer_in_use(&p_button->long_press_timer))
{
wiced_stop_timer(&p_button->long_press_timer);
}
button_released_event_handler((void *) p_button);
}
}
/**
* Call back received when button state is changed.
*
* @param id : id of the button.
* @param new_state : new state of the button.
* @return void : no return value is expected.
*/
static void button_state_change_callback(platform_button_t id, wiced_bool_t new_state)
{
button_manager_button_t * button = get_button(id);
#if 0
WICED_BT_TRACE("button_state_change_callback (button %d %s, %s, %d, %d)\n",
id,
button->current_state == BUTTON_STATE_HELD ? "H" : "R",
button->debouncing ? "D" : "-",
new_state,
button->debounce_counter);
#endif
/* Check module state.*/
if (button == NULL || button_manager == NULL)
{
WICED_BT_TRACE("button manager not initialized\n");
return;
}
if (new_state == WICED_TRUE)
{
button_state_change_callback_pressed(button);
}
else
{
button_state_change_callback_released(button);
}
}
/**
* Event handler for button press event.
*
* @param arg : Arguments passed by the event manager
* @return void : No return value expected.
*/
static wiced_result_t button_pressed_event_handler(void * arg)
{
button_manager_button_t * button = (button_manager_button_t *) arg;
if (button->current_state == BUTTON_STATE_HELD)
{
return WICED_SUCCESS;
}
/** Button is pressed; update the state so that timer-handlers know it */
button->current_state = BUTTON_STATE_HELD;
return WICED_SUCCESS;
}
/**
* Event handler for button release event.
*
* @param arg : Arguments passed by the event manager
* @return void : No return value expected.
*/
static wiced_result_t button_released_event_handler(void * arg)
{
button_manager_button_t * button = (button_manager_button_t *) arg;
button_manager_event_t new_release_event = 0;
uint64_t duration; // us
if (button->current_state == BUTTON_STATE_RELEASED)
{
return WICED_SUCCESS;
}
button->current_state = BUTTON_STATE_RELEASED;
/* Calculate the time difference. */
duration = button->released_timestamp - button->pressed_timestamp; // us
duration = duration / 1000; // ms
/** If release event comes before debounce duration, ignore it */
if (duration <= button_manager->configuration->debounce_duration)
{
return WICED_SUCCESS;
}
/** deduce the event depending on the duration */
new_release_event = button_deduce_duration_event(button, (uint32_t) duration);
/** Check if this Release is from 2nd click of a double-click event */
button_check_for_double_click(button, &new_release_event);
/**
* As the new state is Release and application has asked for this kind of event,
* send it irrespective of whether timer-handler
* had sent it previously
*/
if (button_check_event_mask(button, new_release_event))
{
#ifndef CYW55572
button_manager->configuration->event_handler(button, new_release_event, button->current_state);
#else
/*
* Button released event is handled by another thread, it needs defer to mpaf thread.
* Deferred_button_timer_handler(long press) is handled by timer which is in mpaf thread,
* it does not need defer.
*/
wiced_result_t result;
wiced_rtos_lock_mutex(p_mutex_button_event);
button_event_defer_to_mpaf_t * p_data = (button_event_defer_to_mpaf_t *) wiced_bt_get_buffer_from_pool(p_button_event_pool);
if (!p_data)
{
wiced_rtos_unlock_mutex(p_mutex_button_event);
WICED_BT_TRACE("Err: release event_handler no memory \n");
goto DEFER_MPAF_ERROR;
}
p_data->p_button = button;
p_data->event = new_release_event;
wiced_bt_enqueue(&button_event_queue, (wiced_bt_buffer_t *) p_data);
wiced_rtos_unlock_mutex(p_mutex_button_event);
result = wiced_rtos_defer_execution(WICED_RTOS_DEFER_TO_MPAF_THREAD, &button_event_defer_to_mpaf, NULL);
if (result != WICED_SUCCESS)
{
wiced_rtos_lock_mutex(p_mutex_button_event);
button_event_defer_to_mpaf_t * p_buf = (button_event_defer_to_mpaf_t *) wiced_bt_dequeue(&button_event_queue);
wiced_bt_free_buffer(p_buf);
wiced_rtos_unlock_mutex(p_mutex_button_event);
WICED_BT_TRACE("Err: release event_handler wiced_rtos_defer_execution (%d)\n", result);
goto DEFER_MPAF_ERROR;
}
#endif
}
#ifdef CYW55572
DEFER_MPAF_ERROR:
#endif
/** reset the button's last-sent so that a new press/held after this release is handled properly */
button->last_sent_event = 0;
return WICED_SUCCESS;
}
/**
* Checks if the event is a double click event.
*
* @param button : button information.
* @param new_event : new event generated for the button.
* @return void : no return value is expected.
*/
static void button_check_for_double_click(button_manager_button_t * button, button_manager_event_t * new_event)
{
if (!button_check_event_mask(button, BUTTON_DOUBLE_CLICK_EVENT) || *new_event != BUTTON_CLICK_EVENT)
{
return;
}
/** figure out the time-difference in two-releases */
if ((button->released_timestamp - button->last_released_timestamp) <= button_manager->configuration->double_click_interval)
{
/** morph it as DOUBLE_CLICK */
*new_event = BUTTON_DOUBLE_CLICK_EVENT;
}
button->last_released_timestamp = button->released_timestamp;
return;
}
/**
* Checks the event mask for the button
*
* @param button : button information.
* @param new_event : new event generated for the button.
* @return wiced_bool_t : returns true/false based on the new event.
*/
static wiced_bool_t button_check_event_mask(button_manager_button_t * button, uint16_t new_event)
{
if (!button)
{
return WICED_FALSE;
}
return ((new_event & button->configuration->button_event_mask) ? WICED_TRUE : WICED_FALSE);
}
/**
* Checks duration of the event
*
* @param button : the button that been triggered
* @param current_interval : current time interval
* @return button_manager_event_t : returns button manager event.
*/
static button_manager_event_t button_deduce_duration_event(button_manager_button_t * button, uint32_t current_interval)
{
button_manager_event_t new_event = 0;
uint32_t target_hold_interval;
if (current_interval <= button_manager->configuration->debounce_duration)
{
return (button_manager_event_t) 0;
}
else if (current_interval > button_manager->configuration->debounce_duration &&
current_interval <= button_manager->configuration->short_hold_duration)
{
return BUTTON_CLICK_EVENT;
}
else if (current_interval > button_manager->configuration->short_hold_duration &&
current_interval <= button_manager->configuration->medium_hold_duration)
{
return BUTTON_SHORT_DURATION_EVENT;
}
else if (current_interval > button_manager->configuration->medium_hold_duration &&
current_interval <= button_manager->configuration->long_hold_duration)
{
return BUTTON_MEDIUM_DURATION_EVENT;
}
else if (current_interval > button_manager->configuration->long_hold_duration &&
current_interval <= button_manager->configuration->very_long_hold_duration)
{
button->repeat = 0;
return BUTTON_LONG_DURATION_EVENT;
}
else
{ // current_interval > button_manager->configuration->very_long_hold_duration
if (button_manager->configuration->continuous_hold_detect == WICED_FALSE)
{
return BUTTON_VERY_LONG_DURATION_EVENT;
}
else
{
target_hold_interval = (button->repeat + 2) * button_manager->configuration->long_hold_duration;
if (current_interval > target_hold_interval)
{
button->repeat++;
return BUTTON_HOLDING_EVENT;
}
else
{
return (button_manager_event_t) 0;
}
}
}
}
/**
* returns button based on the button id
*
* @param id : id of the buttonl
* @return button_manager_button_t : returns button.
*/
static button_manager_button_t * get_button(platform_button_t id)
{
uint8_t a;
for (a = 0; a < button_manager->number_of_buttons; a++)
{
if (button_manager->buttons[a].configuration->button == id)
{
return &button_manager->buttons[a];
}
}
return NULL;
}
#ifdef CYW55572
static void button_event_defer_to_mpaf(void * arg)
{
button_event_defer_to_mpaf_t button_event_buf;
wiced_rtos_lock_mutex(p_mutex_button_event);
button_event_defer_to_mpaf_t * p_buf = (button_event_defer_to_mpaf_t *) wiced_bt_dequeue(&button_event_queue);
memcpy(&button_event_buf, p_buf, sizeof(button_event_defer_to_mpaf_t));
wiced_bt_free_buffer(p_buf);
wiced_rtos_unlock_mutex(p_mutex_button_event);
button_manager->configuration->event_handler(button_event_buf.p_button, button_event_buf.event,
button_event_buf.p_button->current_state);
}
#endif