/**
  **********************************************************************************************************************
  * @file    cot_key.c
  * @brief   该文件提供按键动作识别功能
  * @author  const_zpc  any question please send mail to const_zpc@163.com
  * @version V1.0.0
  * @date    2023-11-05
  *
  * @details  功能详细说明：
  *           + 按键初始化
  *           + 按键动作监听
  *           + 按键动作信息获取
  *           + 按键轮询任务
  *
  **********************************************************************************************************************
  * 源码路径：https://gitee.com/cot_package/cot_key.git 具体问题及建议可在该网址填写 Issue
  *
  * 使用方式:
  *    1、使用前初始化函数 KEY_Init
  *    2、周期调用函数 KEY_Scan, 用来处理按键动作识别和事件触发
  *    3、使用其他函数对获取按键动作附加信息
  *
  **********************************************************************************************************************
  */

/* Includes ----------------------------------------------------------------------------------------------------------*/
#include "cot_key.h"

/* Private typedef ---------------------------------------------------------------------------------------------------*/
/* Private define ----------------------------------------------------------------------------------------------------*/

// 按键工作模式定义
#define KEY_MODE_NORMAL         0    // 普通模式 - 支持按下/松开触发
#define KEY_MODE_LONG_PRESS     1    // 长按模式 - 支持长按检测和周期触发
#define KEY_MODE_CLICK          2    // 连击模式 - 支持连续点击次数检测
#define KEY_MODE_CLICK_COMP     3    // 连击复合模式 - 支持连击和长按的复合检测

// 按键状态机状态定义
#define KEY_STATE_IDLE          0    // 空闲状态 - 等待按键按下
#define KEY_STATE_PRESS         1    // 按下状态 - 按键被按下但未达到长按时间
#define KEY_STATE_LOSSEN        2    // 松开状态 - 按键松开但在连击间隔内
#define KEY_STATE_LONG_PRESS    3    // 长按状态 - 按键按下超过长按时间阈值
#define KEY_STATE_LONG_LOSSEN   4    // 长按松开状态 - 长按后松开

/* Private macro -----------------------------------------------------------------------------------------------------*/

// 时间累加宏,防止溢出
#define TIME_MAX_VAL            (uint32_t)0xffff   // 时间计数最大值
#define TIME_ADD(tic, time)     (TIME_MAX_VAL - (tic) < (time) ? ((tic) = 0xffff) : (tic += time))

/* Private variables -------------------------------------------------------------------------------------------------*/

static cotKeyCfg_t *sg_pKeyTable = NULL;    // 按键配置表指针
static size_t sg_keyNum = 0;                // 按键数量

/* Private function prototypes ---------------------------------------------------------------------------------------*/
/* Private function --------------------------------------------------------------------------------------------------*/

/**
  * @brief      按键初始化
  *
  * @note       按键默认为普通模式，按下和松开都会触发回调函数
  * @return     0,成功; -1,失败
  */
int cotKey_Init(cotKeyCfg_t pCfgTable[], size_t num)
{
    uint8_t key;

    // 保存按键配置表和按键数量
    sg_pKeyTable = pCfgTable;
    sg_keyNum = num;

    // 遍历初始化所有按键
    for (key = 0; key < num; key++)
    {
        // 初始化按键计数器和状态
        sg_pKeyTable[key].proc.clickCnt = 0;     // 清零连击计数
        sg_pKeyTable[key].proc.uiPressTic = 0;   // 清零按下计时器
        sg_pKeyTable[key].proc.uiLossenTic = 0;  // 清零松开计时器
        sg_pKeyTable[key].proc.eKeyStateBak = COT_KEY_OFF;  // 设置初始状态为松开
        sg_pKeyTable[key].proc.state = KEY_STATE_IDLE;      // 设置初始状态为空闲

        // 清除回调函数并恢复默认配置
        cotKey_Listen((cotKey_t)key, NULL);
        cotKey_ResetDefaultConfig((cotKey_t)key);
    }

    return 0;
}

/**
  * @brief      监听指定按键状态，触发回调函数
  *
  * @param[in]  eKey 指定按键
  * @param[in]  pfnKeyFun 监听触发回调函数
  * @return     0,成功; -1,失败
  */
int cotKey_Listen(cotKey_t eKey, cotKeyListen_f pfnKeyFun)
{
    // 参数检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    // 更新新配置中的回调函数
    sg_pKeyTable[eKey].proc.newConfig.pfnEventCallback = pfnKeyFun;
    
    // 如果按键处于空闲状态,立即更新当前配置
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    // 直接更新当前配置的回调函数
    sg_pKeyTable[eKey].proc.curConfig.pfnEventCallback = pfnKeyFun;
#endif
    return 0;
}

/**
  * @brief      配置按键恢复为默认模式
  * 
  * @note       默认普通模式，按下和松开都会触发回调函数
  * @param[in]  eKey 指定按键
  * @return     0,成功; -1,失败
  */
int cotKey_ResetDefaultConfig(cotKey_t eKey)
{
    // 参数检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    // 设置新配置为默认值
    sg_pKeyTable[eKey].proc.newConfig.triggerWay = (COT_KEY_TRIGGER_WAY_PRESS | COT_KEY_TRIGGER_WAY_LOSSEN);  // 双边触发
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_NORMAL;        // 普通模式
    sg_pKeyTable[eKey].proc.newConfig.longTime = 0;                  // 无长按检测
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = 0;          // 无重复触发
#else
    // 直接设置当前配置为默认值
    sg_pKeyTable[eKey].proc.curConfig.triggerWay = (COT_KEY_TRIGGER_WAY_PRESS | COT_KEY_TRIGGER_WAY_LOSSEN);
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_NORMAL;
    sg_pKeyTable[eKey].proc.curConfig.longTime = 0;
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = 0;
#endif
    return 0;
}

/**
  * @brief      配置按键为普通模式
  * 
  * @note       该模式是只有按下/松开状态
  * @param[in]  eKey 指定按键
  * @param[in]  triggerWay 触发方式, 取值可以是 COT_KEY_TRIGGER_WAY_PRESS 和 COT_KEY_TRIGGER_WAY_LOSSEN 的任意组合
  * @param[in]  triggerInterval 按下不松开时重复触发事件的间隔时长, 为0则只触发一次, 单位毫秒
  * @return     0,成功; -1,失败
  */
int cotKey_ConfigNormalMode(cotKey_t eKey, uint8_t triggerWay, uint16_t triggerInterval)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    // 配置新的按键参数
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_NORMAL;           // 设置为普通模式
    sg_pKeyTable[eKey].proc.newConfig.triggerWay = triggerWay;         // 设置触发方式
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = triggerInterval;// 设置触发间隔
    
    // 如果按键当前空闲,则立即更新配置
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    // 直接更新当前配置
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_NORMAL;
    sg_pKeyTable[eKey].proc.curConfig.triggerWay = triggerWay;
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = triggerInterval;
#endif
    return 0;
}

/**
  * @brief      配置按键为长按模式
  * 
  * @note       该模式是区分短按和长按两种状态，默认双边触发模式（按下触发/松开触发）
  * @param[in]  eKey 指定按键
  * @param[in]  longPressTime   用来区分短按和长按的时间, 单位毫秒
  * @param[in]  triggerInterval 当确认为长按时，按下不松开时重复触发事件的间隔时长, 为0则只触发一次, 单位毫秒
  * @return     0,成功; -1,失败
  */
int cotKey_ConfigLongPressMode(cotKey_t eKey, uint16_t longPressTime, uint16_t triggerInterval)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    // 配置新的按键参数
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_LONG_PRESS;      // 设置为长按模式
    sg_pKeyTable[eKey].proc.newConfig.longTime = longPressTime;        // 设置长按时间阈值
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = triggerInterval;// 设置触发间隔
    
    // 如果按键当前空闲,则立即更新配置
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    // 直接更新当前配置
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_LONG_PRESS;
    sg_pKeyTable[eKey].proc.curConfig.longTime = longPressTime;
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = triggerInterval;
#endif
    return 0;
}

/**
  * @brief      配置按键为连击模式
  * 
  * @note       该模式是用于区分单击、双击和三击等一定时间内多次连击的场景使用
  * @param[in]  eKey 指定按键
  * @param[in]  triggerInterval 两次点击的间隔时间差, 单位毫秒
  * @return     0,成功; -1,失败
  */
int cotKey_ConfigClickMode(cotKey_t eKey, uint16_t triggerInterval)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    // 配置新的按键参数
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_CLICK;           // 设置为连击模式
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = triggerInterval;// 设置连击间隔时间
    
    // 如果按键当前空闲,则立即更新配置
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    // 直接更新当前配置
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_CLICK;
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = triggerInterval;
#endif
    return 0;
}

/**
  * @brief      配置按键为连击普通复合模式
  * 
  * @note       该模式是用于区分长时间按下、单击、双击和三击等一定时间内多次连击的场景使用
  *             按键空闲状态下当按下超过一定时长时，则不再触发连击模式，变为普通模式
  * @param[in]  eKey 指定按键
  * @param[in]  triggerInterval 两次点击的间隔时间差, 单位毫秒
  * @param[in]  pressMaxTime    按下超时时长, 单位毫秒
  * @param[in]  triggerWay      触发方式, 取值可以是 COT_KEY_TRIGGER_WAY_PRESS 和 COT_KEY_TRIGGER_WAY_LOSSEN 的任意组合
  * @return     0,成功; -1,失败
  */
int cotKey_ConfigClickCompMode(cotKey_t eKey, uint16_t triggerInterval, uint16_t pressMaxTime, uint8_t triggerWay)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return -1;
    }

#if defined(_CONFIG_DELAY_UPDATE_)
    // 配置新的按键参数
    sg_pKeyTable[eKey].proc.newConfig.mode = KEY_MODE_CLICK_COMP;      // 设置为连击复合模式
    sg_pKeyTable[eKey].proc.newConfig.triggerInterval = triggerInterval;// 设置连击间隔时间
    sg_pKeyTable[eKey].proc.newConfig.triggerWay = triggerWay;         // 设置触发方式
    sg_pKeyTable[eKey].proc.newConfig.longTime = pressMaxTime;         // 设置长按时间阈值
    
    // 如果按键当前空闲,则立即更新配置
    if (sg_pKeyTable[eKey].proc.state == KEY_STATE_IDLE)
    {
        sg_pKeyTable[eKey].proc.curConfig = sg_pKeyTable[eKey].proc.newConfig;
    }
#else
    // 直接更新当前配置
    sg_pKeyTable[eKey].proc.curConfig.mode = KEY_MODE_CLICK_COMP;      // 设置为连击复合模式
    sg_pKeyTable[eKey].proc.curConfig.triggerInterval = triggerInterval;// 设置连击间隔时间
    sg_pKeyTable[eKey].proc.curConfig.triggerWay = triggerWay;         // 设置触发方式
    sg_pKeyTable[eKey].proc.curConfig.longTime = pressMaxTime;         // 设置长按时间阈值
#endif
    return 0;
}

/**
  * @brief      获取指定按键的点击次数
  * 
  * @attention  需要将按键配置为连击模式或连击普通复合模式
  * @param      eKey 指定按键, 取值为 @reg cotKey_t
  * @return     点击次数 
  */
uint8_t cotKey_GetClickCount(cotKey_t eKey)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return 0;
    }

    // 检查按键模式是否支持连击计数
    if (sg_pKeyTable[eKey].proc.curConfig.mode != KEY_MODE_CLICK &&
        sg_pKeyTable[eKey].proc.curConfig.mode != KEY_MODE_CLICK_COMP )
    {
        return 0;
    }

    // 返回当前连击次数
    return sg_pKeyTable[eKey].proc.clickCnt;
}

/**
  * @brief      判断按键点击次数是否符合要求
  * 
  * @attention  需要将按键配置为连击模式
  * @param[in]  eKey 指定按键
  * @param[in]  count 点击次数
  * @retval     TRUE,符合; FALSE,不符合.
  */
uint8_t cotKey_IsClickCount(cotKey_t eKey, uint8_t count)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return FALSE;
    }

    // 检查是否为连击模式
    if (sg_pKeyTable[eKey].proc.curConfig.mode != KEY_MODE_CLICK)
    {
        return FALSE;
    }
    
    // 比较连击次数是否匹配
    if (sg_pKeyTable[eKey].proc.clickCnt != count)
    {
        return FALSE;
    }
    
    return TRUE;
}

/**
  * @brief      判断按键状态是否为触发了长按状态
  * 
  * @attention  需要将按键配置为长按模式
  * @param      eKey 指定按键, 取值为 @reg cotKey_t
  * @return     TRUE 长按
  * @return     FALSE 短按
  */
uint8_t cotKey_IsLongPress(cotKey_t eKey)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return FALSE;
    }

    // 检查是否为长按模式
    if (sg_pKeyTable[eKey].proc.curConfig.mode != KEY_MODE_LONG_PRESS)
    {
        return FALSE;
    }

    // 检查是否处于长按状态
    if (!(sg_pKeyTable[eKey].proc.state == KEY_STATE_LONG_PRESS || 
          sg_pKeyTable[eKey].proc.state == KEY_STATE_LONG_LOSSEN))
    {
        return FALSE;
    }

    return TRUE;
}

/**
  * @brief      判断按键状态是否符合要求
  *
  * @attention  适用于普通模式和长按模式，长按模式时需要通过调用函数cotKey_IsLongPress判断是否处于长按
  * @param[in]  eKey 指定按键
  * @param[in]  triggerWay 按键触发状态, 取值可以是 COT_KEY_TRIGGER_WAY_PRESS 和 COT_KEY_TRIGGER_WAY_LOSSEN 的任意组合
  * @retval     TRUE,符合; FALSE,不符合.
  */
uint8_t cotKey_IsTriggerWayMet(cotKey_t eKey, uint8_t triggerWay)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return FALSE;
    }
    
    // 检查是否满足按下触发条件
    if ((triggerWay & COT_KEY_TRIGGER_WAY_PRESS) && 
        (sg_pKeyTable[eKey].proc.state == KEY_STATE_PRESS || 
         sg_pKeyTable[eKey].proc.state == KEY_STATE_LONG_PRESS))
    {
        return TRUE;
    }

    // 检查是否满足松开触发条件
    if ((triggerWay & COT_KEY_TRIGGER_WAY_LOSSEN) && 
        (sg_pKeyTable[eKey].proc.state == KEY_STATE_LOSSEN || 
         sg_pKeyTable[eKey].proc.state == KEY_STATE_LONG_LOSSEN))
    {
        return TRUE;
    }
    
    return FALSE;
}

/**
  * @brief      判断按键状态和按下时长是否符合要求
  *
  * @param[in]  eKey 指定按键
  * @param[in]  triggerWay 按键触发状态, 取值可以是 COT_KEY_TRIGGER_WAY_PRESS 和 COT_KEY_TRIGGER_WAY_LOSSEN 的任意组合
  * @param[in]  time       按下时长
  * @retval     TRUE,符合; FALSE,不符合.
  */
uint8_t cotKey_IsPressTimeMet(cotKey_t eKey, uint8_t triggerWay, uint16_t time)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return FALSE;
    }
    
    // 检查触发方式是否满足
    if (!cotKey_IsTriggerWayMet(eKey, triggerWay))
    {
        return FALSE;
    }
    
    // 检查按下时间是否达到要求
    if (sg_pKeyTable[eKey].proc.uiPressTic < time)
    {
        return FALSE;
    }
    
    return TRUE;
}


/**
  * @brief      判断按键状态和松开时长是否符合要求
  *
  * @param[in]  eKey 指定按键
  * @param[in]  triggerWay 按键触发状态, 取值可以是 COT_KEY_TRIGGER_WAY_PRESS 和 COT_KEY_TRIGGER_WAY_LOSSEN 的任意组合
  * @param[in]  time       松开时长
  * @retval     TRUE,符合; FALSE,不符合.
  */
uint8_t cotKey_IsLossenTimeMet(cotKey_t eKey, uint8_t triggerWay, uint16_t time)
{
    // 参数有效性检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0 || eKey >= sg_keyNum)
    {
        return FALSE;
    }
    
    // 检查触发方式是否满足
    if (!cotKey_IsTriggerWayMet(eKey, triggerWay))
    {
        return FALSE;
    }
    
    // 检查松开时间是否达到要求
    if (sg_pKeyTable[eKey].proc.uiLossenTic < time)
    {
        return FALSE;
    }
    
    return TRUE;
}

/**
 * 处理普通按键的状态变化
 * 
 * @param pKey 指向按键处理结构体的指针，用于跟踪按键状态和配置
 * @param newState 当前按键的新状态，是按下还是松开
 * @param intervalTime 自上次状态变化以来的时间间隔，用于处理重复触发
 * @return uint8_t 表示是否需要根据当前状态变化调用回调函数
 * 
 * 此函数根据按键的当前状态和配置，决定是否需要调用回调函数，并更新按键的状态
 * 它处理四种主要的按键状态变化：空闲、按下、松开以及重复触发
 */
static uint8_t ProcessNormalKeyState(cotKeyProc_t *pKey, cotKey_t newState, uint16_t intervalTime)
{
    uint8_t isCallFun = FALSE;    // 标记是否需要触发回调函数

    // 根据当前按键状态执行相应的处理
    switch (pKey->state)
    {
        case KEY_STATE_IDLE:    // 空闲状态处理
            if (newState == COT_KEY_ON)    // 检测到按键按下
            {
                pKey->state = KEY_STATE_PRESS;    // 切换到按下状态
                pKey->uiPressTic = 0;             // 清零按下计时器
                
                // 如果配置了按下触发,则标记需要回调
                if (pKey->curConfig.triggerWay & COT_KEY_TRIGGER_WAY_PRESS)
                {
                    isCallFun = TRUE;
                }
            }
        break;
        case KEY_STATE_PRESS:    // 按下状态处理
            if (newState == COT_KEY_OFF)    // 检测到按键松开
            {
                pKey->state = KEY_STATE_LOSSEN;    // 切换到松开状态
                
                // 如果配置了松开触发,则标记需要回调
                if (pKey->curConfig.triggerWay & COT_KEY_TRIGGER_WAY_LOSSEN)
                {
                    isCallFun = TRUE;
                }
            }
            else    // 按键持续按下
            {
                // 如果配置了重复触发间隔
                if (0 != pKey->curConfig.triggerInterval)
                {
                    TIME_ADD(pKey->uiPressTic, intervalTime);    // 累加按下时间

                    // 达到重复触发间隔,触发回调并重新计时
                    if (pKey->uiPressTic > pKey->curConfig.triggerInterval)
                    {
                        pKey->uiPressTic = 0;
                        isCallFun = TRUE;
                    }
                }
            }
        break;
        case KEY_STATE_LOSSEN:    // 松开状态处理
            pKey->state = KEY_STATE_IDLE;    // 直接切换回空闲状态
        break;
    }
    
    return isCallFun;
}

/**
 * 处理长按按键状态
 * 
 * 该函数根据当前按键状态和新的按键事件，处理按键的长按逻辑，并确定是否需要触发回调函数
 * 
 * @param pKey 指向按键处理结构体的指针，包含按键状态和配置信息
 * @param newState 新的按键状态，表示按键当前是按下还是松开
 * @param intervalTime 自上次按键状态变化以来的时间间隔，用于计算长按时间
 * 
 * @return 返回一个布尔值，表示是否需要触发按键回调函数
 */
static uint8_t ProcessLongPressKeyState(cotKeyProc_t *pKey, cotKey_t newState, uint16_t intervalTime)
{
    uint8_t isCallFun = FALSE;    // 标记是否需要触发回调函数

    switch (pKey->state)
    {
        case KEY_STATE_IDLE:    // 空闲状态处理
            if (newState == COT_KEY_ON)    // 检测到按键按下
            {
                pKey->state = KEY_STATE_PRESS;    // 切换到按下状态
                pKey->uiPressTic = 0;             // 清零按下计时器

                // 如果配置了按下触发,则标记需要回调
                if (pKey->curConfig.triggerWay & COT_KEY_TRIGGER_WAY_PRESS)
                {
                    isCallFun = TRUE;
                }
            }
        break;
        case KEY_STATE_PRESS:    // 按下状态处理
            if (newState == COT_KEY_OFF)    // 检测到按键松开(短按)
            {
                pKey->state = KEY_STATE_LOSSEN;    // 切换到松开状态
                isCallFun = TRUE;                  // 触发短按回调
            }
            else    // 按键持续按下
            {
                TIME_ADD(pKey->uiPressTic, intervalTime);    // 累加按下时间

                // 达到长按时间阈值
                if (pKey->uiPressTic >= pKey->curConfig.longTime)
                {
                    pKey->state = KEY_STATE_LONG_PRESS;    // 切换到长按状态
                    pKey->uiPressTic = 0;                  // 清零计时器
                    
                    // 如果配置了按下触发,则标记需要回调
                    if (pKey->curConfig.triggerWay & COT_KEY_TRIGGER_WAY_PRESS)
                    {
                        isCallFun = TRUE;
                    }
                }
            }
        break;
        case KEY_STATE_LOSSEN:    // 短按松开状态处理
            pKey->state = KEY_STATE_IDLE;    // 直接切换回空闲状态
        break;
        case KEY_STATE_LONG_PRESS:    // 长按状态处理
            if (newState == COT_KEY_OFF)    // 检测到按键松开
            {
                pKey->state = KEY_STATE_LONG_LOSSEN;    // 切换到长按松开状态

                // 如果配置了松开触发,则标记需要回调
                if (pKey->curConfig.triggerWay & COT_KEY_TRIGGER_WAY_LOSSEN)
                {
                    isCallFun = TRUE;
                }
            }
            else    // 按键持续长按
            {
                TIME_ADD(pKey->uiPressTic, intervalTime);    // 累加长按时间

                // 如果配置了重复触发间隔
                if (0 != pKey->curConfig.triggerInterval)
                {
                    // 达到重复触发间隔,触发回调并重新计时
                    if (pKey->uiPressTic > pKey->curConfig.triggerInterval)
                    {
                        pKey->uiPressTic = 0;
                        isCallFun = TRUE;
                    }
                }
            }
        break;
        case KEY_STATE_LONG_LOSSEN:    // 长按松开状态处理
            pKey->state = KEY_STATE_IDLE;    // 直接切换回空闲状态
        break;
    }
    
    return isCallFun;
}

/**
 * 处理长按按键状态
 * 
 * 该函数根据当前按键状态和新的按键事件，处理按键的长按逻辑，并确定是否需要触发回调函数
 * 
 * @param pKey 指向按键处理结构体的指针，包含按键状态和配置信息
 * @param newState 新的按键状态，表示按键当前是按下还是松开
 * @param intervalTime 自上次按键状态变化以来的时间间隔，用于计算长按时间
 * 
 * @return 返回一个布尔值，表示是否需要触发按键回调函数
 */
static uint8_t ProcessClickKeyState(cotKeyProc_t *pKey, cotKey_t newState, uint16_t intervalTime)
{
    uint8_t isCallFun = FALSE;    // 标记是否需要触发回调函数

    switch (pKey->state)
    {
        case KEY_STATE_IDLE:    // 空闲状态处理
            if (newState == COT_KEY_ON)    // 检测到按键按下
            {
                pKey->state = KEY_STATE_PRESS;    // 切换到按下状态
                pKey->uiPressTic = 0;             // 清零按下计时器
                pKey->uiLossenTic = 0;           // 清零松开计时器
                pKey->clickCnt = 0;              // 清零连击计数
            }
        break;
        case KEY_STATE_PRESS:    // 按下状态处理
            if (newState == COT_KEY_OFF)    // 检测到按键松开
            {
                pKey->state = KEY_STATE_LOSSEN;    // 切换到松开状态
                pKey->uiLossenTic = 0;            // 清零松开计时器
                pKey->clickCnt++;                 // 增加连击计数
            }
        break;
        case KEY_STATE_LOSSEN:    // 松开状态处理
            TIME_ADD(pKey->uiLossenTic, intervalTime);    // 累加松开时间
        
            // 松开时间超过连击间隔
            if (pKey->uiLossenTic > pKey->curConfig.triggerInterval)
            {
                isCallFun = TRUE;                // 触发连击回调
                pKey->state = KEY_STATE_IDLE;    // 切换回空闲状态
            }
            else    // 在连击间隔内
            {
                if (newState == COT_KEY_ON)    // 检测到新的按下
                {
                    pKey->state = KEY_STATE_PRESS;    // 切换回按下状态继续检测连击
                }
            }
        break;
    }
    
    return isCallFun;
}

/**
 * 处理点击组合键状态
 * 
 * @param pKey 键盘处理结构体指针，用于记录按键处理状态
 * @param newState 当前按键状态，是按下还是松开
 * @param intervalTime 自上次按键状态变化以来的时间间隔
 * 
 * @return uint8_t 表示是否需要触发按键回调函数
 * 
 * 此函数根据按键的当前状态和上次状态，决定是否需要触发按键回调函数，并更新按键处理状态。
 * 它通过状态机的方式处理按键的连击和长按逻辑。
 */
static uint8_t ProcessClickCompKeyState(cotKeyProc_t *pKey, cotKey_t newState, uint16_t intervalTime)
{
    uint8_t isCallFun = FALSE;    // 标记是否需要触发回调函数

    switch (pKey->state)
    {
        case KEY_STATE_IDLE:    // 空闲状态处理
            if (newState == COT_KEY_ON)    // 检测到按键按下
            {
                pKey->state = KEY_STATE_PRESS;    // 切换到按下状态
                pKey->uiPressTic = 0;             // 清零按下计时器
                pKey->uiLossenTic = 0;           // 清零松开计时器
                pKey->clickCnt = 0;              // 清零连击计数
            }
        break;
        case KEY_STATE_PRESS:    // 按下状态处理
            if (newState == COT_KEY_OFF)    // 检测到按键松开
            {
                pKey->state = KEY_STATE_LOSSEN;    // 切换到松开状态
                pKey->uiLossenTic = 0;            // 清零松开计时器

                // 按下时间小于长按阈值,视为连击
                if (pKey->uiPressTic < pKey->curConfig.longTime)
                {
                    pKey->clickCnt++;    // 增加连击计数
                }
                else    // 按下时间超过长按阈值,视为长按
                {
                    // 如果配置了松开触发,则标记需要回调
                    if (pKey->curConfig.triggerWay & COT_KEY_TRIGGER_WAY_LOSSEN)
                    {
                        isCallFun = TRUE;
                    }
                }
            }
            else    // 按键持续按下
            {
                // 还未发生连击时才检测长按
                if (pKey->clickCnt == 0)
                {
                    TIME_ADD(pKey->uiPressTic, intervalTime);    // 累加按下时间

                    // 达到长按阈值且配置了按下触发
                    if (pKey->uiPressTic >= pKey->curConfig.longTime && 
                        (pKey->curConfig.triggerWay & COT_KEY_TRIGGER_WAY_PRESS))
                    {
                        isCallFun = TRUE;    // 触发长按回调
                    }
                }
            }
        break;
        case KEY_STATE_LOSSEN:    // 松开状态处理
            TIME_ADD(pKey->uiLossenTic, intervalTime);    // 累加松开时间
        
            // 松开时间超过连击间隔
            if (pKey->uiLossenTic > pKey->curConfig.triggerInterval)
            {
                isCallFun = TRUE;                // 触发连击回调
                pKey->state = KEY_STATE_IDLE;    // 切换回空闲状态
            }
            else    // 在连击间隔内
            {
                if (newState == COT_KEY_ON)    // 检测到新的按下
                {
                    pKey->state = KEY_STATE_PRESS;    // 切换回按下状态继续检测连击
                }
            }
        break;
    }
    
    return isCallFun;
}

/**
  * @brief      按键扫描周期任务
  *
  * @param[in]  sysTime 系统运行时间, 单位毫秒
  * @return     0,成功; -1,失败
  */
int cotKey_Scan(uint32_t sysTime)
{
    uint8_t key;
    cotKey_t eKeyNewState;
    uint8_t isCallFun = FALSE;
    static uint32_t s_sysTimeBak = 0;    // 上次扫描的系统时间

    // 参数检查
    if (sg_pKeyTable == NULL || sg_keyNum == 0)
    {
        return -1;
    }

    // 首次调用初始化时间戳
    if (s_sysTimeBak == 0)
    {
        s_sysTimeBak = sysTime;
    }

    // 获取所有按键的当前状态
    for (key = 0; key < sg_keyNum; key++)
    {
        sg_pKeyTable[key].proc.eKeyState = sg_pKeyTable[key].pfnKeyState();
    }

    // 处理每个按键的状态
    for (key = 0; key < sg_keyNum; key++)
    {
        // 检测按键状态变化
        if (sg_pKeyTable[key].proc.eKeyStateBak != sg_pKeyTable[key].proc.eKeyState)
        {
            eKeyNewState = sg_pKeyTable[key].proc.eKeyStateBak;
        }
        else
        {
            eKeyNewState = sg_pKeyTable[key].proc.eKeyState;
        }
        
        // 根据按键模式调用对应的处理函数
        switch (sg_pKeyTable[key].proc.curConfig.mode)
        {
            case KEY_MODE_NORMAL:
                isCallFun = ProcessNormalKeyState(&sg_pKeyTable[key].proc, eKeyNewState, sysTime - s_sysTimeBak);
            break;
            case KEY_MODE_LONG_PRESS:
                isCallFun = ProcessLongPressKeyState(&sg_pKeyTable[key].proc, eKeyNewState, sysTime - s_sysTimeBak);
            break;
            case KEY_MODE_CLICK:
                isCallFun = ProcessClickKeyState(&sg_pKeyTable[key].proc, eKeyNewState, sysTime - s_sysTimeBak);
            break;
            case KEY_MODE_CLICK_COMP:
                isCallFun = ProcessClickCompKeyState(&sg_pKeyTable[key].proc, eKeyNewState, sysTime - s_sysTimeBak);
            break;
            default:
                
            break;
        }

        // 如果需要触发回调且回调函数存在,则调用回调
        if (isCallFun && NULL != sg_pKeyTable[key].proc.curConfig.pfnEventCallback)
        {
            sg_pKeyTable[key].proc.curConfig.pfnEventCallback((cotKey_t)key);
        }

        // 按键空闲时更新配置和清除计数
        if (sg_pKeyTable[key].proc.state == KEY_STATE_IDLE)
        {
#if defined(_CONFIG_DELAY_UPDATE_)
            sg_pKeyTable[key].proc.curConfig = sg_pKeyTable[key].proc.newConfig;  // 按键空闲时才更新配置
#endif
            sg_pKeyTable[key].proc.clickCnt = 0;  // 清零连击计数
        }

        // 保存当前状态为上次状态
        sg_pKeyTable[key].proc.eKeyStateBak = sg_pKeyTable[key].proc.eKeyState;
    }
    
    // 更新系统时间
    s_sysTimeBak = sysTime;
    return 0;
}
