【ESP32项目】智能门锁

开发板使用的是尚硅谷的智能门锁开发板,代码也是参考的尚硅谷的代码,感谢尚硅谷提供的资料。freertos看的是韦东山老师的教程,讲的很好。写这篇文章相当于对这个项目的总结吧。

项目代码下载地址:https://gitee.com/xiao-liu-harbors-dreams/smart_lock.git

网盘地址: https://pan.baidu.com/s/1nLOX6wYEfF2ptGSNR8mSMA?pwd=liu6 提取码: liu6

连接onenet和OTA部分可以看另一篇博客 https://www.cnblogs.com/266liuliuliu/articles/19019479 ,里面详细讲解了如何使用esp32连接onenet并实现OTA。这篇文章作为实战案例。

系统操作介绍:https://www.cnblogs.com/liu6liu/articles/19040637

一. 项目需求

本项目是实现一款智能门锁中的智能控制部分, 可以应用在家庭, 办公室等任何使用门锁的场所。

1. 项目概述

1.1 密码相关功能

  • 按键配置密码:支持通过设备按键直接完成密码的设置与修改,操作便捷直观。
  • 按键输入密码开锁:通过按键输入密码即可解锁,具备 20 位防偷窥虚位密码功能,有效防止密码被旁窥窃取。
  • 临时密码功能:可远程创建临时密码,该密码在使用次数用完后自动失效,适用于临时访客场景。
  • 周期性密码功能:能够设定密码的有效时段,在指定时间范围内可使用该密码开锁,满足固定周期内的授权需求。
  • 开锁日志上报:当开锁时同步上报至oennet平台开锁信息。
  • 密码错误警报机制:当连续多次输入错误密码时,将触发本地警报,并同步上报至onenet平台,及时通知用户异常情况。
  • 远程密码管理:可以通过中移和物APP进行远程密码管理,和开锁等操作。
  • 蓝牙输入密码开锁:可借助蓝牙连接输入密码实现开锁,提升操作的灵活性与便捷性。

1.2 指纹相关功能

  • 指纹录入:支持录入用户指纹信息,完成指纹库的建立与管理。

  • 指纹开锁:已录入的指纹可直接用于解锁,识别快速准确。

  • 胁迫指纹功能:预先设置胁迫指纹,当遇到危机情况时,使用该指纹开锁,既能顺利解锁,又能自动向onenet平台发送报警状态通知,保障用户安全。

1.3 连接与升级功能

  • WIFI 连接与 OTA 升级:通过 WIFI 连接至 onenet 平台,实现设备的在线 OTA 升级,便捷获取功能更新与优化。

1.4 辅助功能

  • 音量设置:可根据用户需求调节设备的提示音量,适应不同环境。

  • 语音播报模块:配备语音播报功能,操作过程中同步进行语音提示,提升使用体验。

2. 功能概述

2.1 主控芯片

采用ESP32-C3 芯片作为核心控制单元,为各项功能的实现提供稳定可靠的硬件支撑。

2.2 功能模块

  • 电容触摸按键:配备 12 个电容触摸按键,按键标识分别为数字 0-9、M 键及 #键,为用户操作提供实体交互接口。

  • 单总线全彩 LED:每个电容触摸按键均对应配置一个单总线全彩 LED,当按键被触发按下时,LED 会发出灯光提示,直观反馈操作状态。

  • 指纹模块:具备指纹采集功能,可精准捕捉用户指纹信息,为指纹开锁、胁迫指纹等功能提供数据支持。

  • 蓝牙模块:由 ESP32-C3 芯片内置提供,能够接收用户手机通过蓝牙传输的密码信息,在密码匹配成功后,驱动执行开锁动作。

  • 语音播报模块:当用户完成相关操作(如密码输入、指纹识别、开锁等)后,该模块会进行相应的语音播报提示,提升用户操作体验。

  • WIFI 模块:依托 ESP32-C3 芯片实现,主要用于连接网络进行 OTA(在线升级)操作,下载最新固件,确保门锁功能持续优化更新。

  • 电机:通过 ESP32-C3 的 GPIO 接口进行控制,接收开锁指令后驱动电机转动,从而实现门锁的开启动作。

二. 硬件选型

1. 主控芯片

核心控制芯片采用 ESP32-C3,这是一款具备安全特性的低功耗、低成本 RISC-V 架构 MCU。其在保障高性能运行的同时,能精准控制能耗与成本,为设备长效稳定工作奠定基础。该芯片原生支持 2.4GHz Wi-Fi 和 Bluetooth 5(LE)双模无线连接,可满足各类物联网产品的无线通信需求,适配场景极为广泛。

2. 蓝牙模块

直接采用 ESP32-C3 内置的蓝牙功能,可实时接收用户手机通过蓝牙传输的密码信息,在完成密码校验匹配后,立即驱动执行机构完成开锁动作,实现便捷的无线解锁体验。

3. Wi-Fi模块

Wi-Fi 模块同样基于 ESP32-C3 的内置 Wi-Fi 功能,主要承担设备的网络连接职责,支持 OTA(在线升级)操作。通过无线网络下载最新固件,能持续优化门锁功能,确保设备始终保持最佳性能状态。

4. 触摸感应模块

选用 SC12B 容性触摸感应器,支持 12 路按键输入,具备自动校正功能,可精准识别每一个感应键盘的触摸操作。该感应器能穿透玻璃、塑料等非导电介质检测电容变化,显著提升产品的科技感与附加值。感应器通过 I2C 总线与 ESP32-C3 通讯,按键布局包含数字 0-9、M 键及 #键,为用户提供直观的实体交互接口。
驱动介绍:https://www.cnblogs.com/liu6liu/articles/19039582

5. 单总线全彩 LED模块

每个触摸按键均对应配置 WS2812B-2020 单总线全彩 LED。这款 LED 是集成控制电路与发光元件的智能外控光源,采用最新 molding 封装工艺,将 IC 与发光芯片浓缩于 2020 规格的微小封装内,单个元件即可作为独立像素点。其内部包含智能数字接口、数据锁存、信号整形放大驱动电路,以及高精度内部振荡器和可编程定电流控制模块,能确保各像素点的发光颜色高度一致。当按键被触发时,LED 会即时亮起对应灯光,为用户提供清晰的操作状态反馈。
驱动介绍:https://www.cnblogs.com/liu6liu/articles/19039180

6. 指纹识别模块

采用 HLK-FPM383F 新型面阵式半导体指纹模组,相较于传统产品,具有体积小巧、功耗低微、识别速度快、准确率高等显著优势,尤其适合门锁、读卡器、保险箱等小型化、电池供电设备。在低功耗模式下,仍能保持优异的响应性能与高速识别效率(官方提供驱动参考代码)。该模组具备高精度指纹采集功能,可精准捕捉用户指纹信息,为指纹开锁、胁迫指纹报警等核心功能提供可靠的数据支撑。
驱动介绍:https://www.cnblogs.com/liu6liu/articles/19130073

7.语音提示模块

搭载WTN6170(170 秒语音时长)该芯片采用低功耗、高性能 CMOS 工艺。芯片音频采样率最高可达 32kHz,支持 16 级音量调节,提供 PWM 和 DAC 两种音频输出方式,内置精度达 ±1% 的振荡器,无需外部震荡元件,且具备超低功耗待机模式。当用户完成密码输入、指纹识别、开锁等操作时,模块会自动播放对应的语音提示,有效提升用户操作体验。
驱动介绍:https://www.cnblogs.com/liu6liu/articles/19036917

8. 直流电机模块

采用 BDR6120S 直流有刷电机驱动 IC,适用于电子锁、电动牙刷、玩具、消费类产品等低压或电池供电的运动控制设备。电机通过 ESP32-C3 的 GPIO 接口接收控制指令,在驱动 IC 的作用下完成转动动作,最终实现门锁的开启功能。
驱动介绍:https://www.cnblogs.com/liu6liu/articles/19036915

三.开发环境与项目架构

1. ESP-IDF 开发环境搭建

开发环境是根据宸芯IOT配置的的ubuntu,因为windows实在是编译太慢了。

2. 项目架构

image-20250929142332701

  • 硬件抽象层HAL:主要是初始化各种外设其中包括GPIO,I2C,NVS,RMT,UART,蓝牙,封装底层硬件,提供统一的API供驱动层使用。这样可以使上层代码不依赖具体硬件,更换芯片或开发板时只需重写HAL层。
  • 驱动层Driver:基于HAL层接口,实现具体硬件设备的操作逻辑,具体包括DBR6120S,FPM383,SC12B,store,WS2812,WTN6170。其中store就是NVS,封装两层是因为比如未来更换存储方案,只需修改底层封装,业务代码无需变动,还有很多优点就不一一列举了。
  • 网络层Network:负责实现蓝牙,MQTT和HTTP通信并提供统一的API供OTA层使用,蓝牙用于与手机app通信,MQTT客户端用于与onenew服务器通信,HTTP客户端用于OTA升级调用。
  • 中间层FreeRTOS层:提供任务调度,操作系统服务。
  • 应用层App:用于门锁核心功能的实现。

四. 应用层结构和部分代码解析

image-20251008101609111

1. 按键任务

image-20251008101729946

这部分代码实现了在按键按下时记录按下的按键,按到结束键“#”时结束输入并发送给系统控制任务,并实现了按下按键开启键盘灯,长时间没按自动关闭键盘灯。

/**
 * @brief 读取按键
 *
 * @param key_value          返回读取到的按键值
 *
 * @return                   COM_OK 读取成功
 *                           COM_OK 读取失败
 */
com_status_t app_read_key(uint8_t *key_value)
{
    /* 记录上次按键是否是超时退出 */
    static BaseType_t prev_key_status = pdFALSE;
    /* 按键输入位置 */
    uint8_t key_input_index = 0;
    while (1)
    {
        /* 等待按下手指 */
        if (pdTRUE != prev_key_status)
        {
            /* 如果是超时退出就一直阻塞 */
            prev_key_status = xSemaphoreTake(g_xBinarySemaphoreTouchKey,
                                             portMAX_DELAY);
            /* 按下手指开灯 */
            WS2812_set_led_strip_allcolor(color_white);
        }
        else
        {
            /* 如果不是就阻塞10s */
            prev_key_status = xSemaphoreTake(g_xBinarySemaphoreTouchKey,
                                             pdMS_TO_TICKS(10000));
            if (pdTRUE != prev_key_status)
            {
                /* 超时退出关灯 */
                WS2812_set_led_strip_allcolor(color_black);
                MY_LOGW("长时间没有输入退出");
                /* 返回超时     */
                return COM_TIMEOUT;
            }
        }
        touch_key_t key = SC12B_read_key();
        WTN6170_send_voice(WTN6170_VOICE_WATER_EFFECT);
        MY_LOGI("输入: %d", key);
        switch (key)
        {
        case KEY_M:
            /* 退出                  */
            key_input_index = 0;
            return COM_ERROR;
            break;
        case KEY_SHARP:
            /* 输出结束,处理按键值    */
            MY_LOGW("输入: #");
            key_value[key_input_index] = '\0';
            key_input_index = 0;
            return COM_OK;
            break;
        default:
            /* 将按键数值转为对应字符 */
            key_value[key_input_index++] = key + 48;
            break;
        }
        /* 长度等于20自动退出 */
        if (STORAGE_PASSWORD_LEN - 1 == key_input_index)
        {
            key_value[key_input_index] = '\0';
            key_input_index = 0;
            return COM_OK;
        }
    }
}

/**
 * @brief 扫描按键任务
 *
 * @param params             创建任务时传递的参数
 *
 * @return 无
 */
void app_scan_key_task(void *params)
{
    static uint8_t key_value[KEY_INPUT_MAX_LEN];
    static com_status_t com_status = COM_ERROR;
    /* 创建按键输入队列 */
    s_xQueuekeyInput = xQueueCreate(KEY_QUEUE_LEN, sizeof(key_value));
    while (1)
    {
        /* 进入函数前清空数组 */
        memset(key_value,
               0, 
               sizeof(key_value));
        com_status = app_read_key(key_value);

        /* 超时退出 */
        if(COM_TIMEOUT == com_status)
        {
            memset(key_value,
                   0, 
                   sizeof(key_value));
            WS2812_set_led_strip_allcolor(color_black);
        } 
        /* 输入错误 */
        else if(COM_ERROR == com_status)
        {
            memset(key_value,
                   0, 
                   sizeof(key_value));
        } 
        /* 写队列发送给系统控制任务 */
        xQueueSend(s_xQueuekeyInput,
                   key_value,
                   portMAX_DELAY);   
    }
}

2. 指纹任务

image-20251008101803083

这部分代码通过事件组驱动,通过不同的事件做相应的操作,并返回操作结果指纹ID,再发送给系统控制任务。

#define FINGERPRINT_QUEUE_LEN               2
 
#define GET_FINGERPRINT_FAILD              -1

/* 指纹用途 */
typedef enum
{
    FP_ACTION_UNLOCK,                              /* 开锁          */ 
    FP_ACTION_REG_ADMIN,                           /* 注册管理员指纹 */ 
    FP_ACTION_REG_USER,                            /* 注册用户指纹   */ 
    FP_ACTION_VERIFY_ADMIN,                        /* 验证身份       */ 
    FP_ACTION_OTHER                                /* 其他           */ 
}fingerprint_action;


/* 指纹队列数据类型 */
typedef struct 
{
    int8_t fingerprint_id;                          /* 指纹ID       */
    fingerprint_action fp_action;                   /* 指纹作用     */
} fingerprint_data_t;  

/**
 * @brief 扫描指纹任务
 *
 * @param params        创建任务时传递的参数
 *
 * @return 无
 */
void app_scan_fingerprint_task(void *params)
{
    /* 创建指纹输入队列 */
    s_xQueueFingerprint = xQueueCreate(FINGERPRINT_QUEUE_LEN, 
                                       sizeof(fingerprint_data_t));
    fingerprint_data_t fingerprint_data = {0};
    EventBits_t event_bit = 0;
    int8_t fingerprint_id = 0;
    while(1)
    {
        event_bit = xEventGroupWaitBits(
            g_xEventGroupTouchFingerprint,
            EVENT_REG_ADMIN_FINGERPRINT 
            | EVENT_REG_USER_FINGERPRINT 
            | EVENT_TOUCH_FINGERPRINT
            | EVENT_VERIFY_ADMIN_FINGERPRINT,
            pdTRUE,
            pdFALSE,
            portMAX_DELAY);
        switch (event_bit)
        {
            case EVENT_TOUCH_FINGERPRINT:
                fingerprint_data.fp_action = FP_ACTION_UNLOCK;
                app_verify_fingerprint(&fingerprint_id);          /* 验证指纹     */
                break;
            case EVENT_REG_ADMIN_FINGERPRINT:
                fingerprint_data.fp_action = FP_ACTION_REG_ADMIN;
                app_reg_fingerprint(FINGERPRINT_IDENTITY_ADMIN,
                                    &fingerprint_id);             /* 注册管理员指纹 */
                break;
            case EVENT_REG_USER_FINGERPRINT:
                fingerprint_data.fp_action = FP_ACTION_REG_USER;
                app_reg_fingerprint(FINGERPRINT_IDENTITY_USER,
                                    &fingerprint_id);             /* 注册用户指纹   */
                break;
            case EVENT_VERIFY_ADMIN_FINGERPRINT:
                fingerprint_data.fp_action = FP_ACTION_VERIFY_ADMIN;
                app_verify_admin_fingerprint(&fingerprint_id);    /* 验证管理员指纹 */
                break; 
            default:
                WTN6170_send_voice(WTN6170_VOICE_OPERATION_INVALID);
                MY_LOGE("错误事件");
                fingerprint_data.fp_action = FP_ACTION_OTHER;     /* 错误事件     */
                break;
        }
        fingerprint_data.fingerprint_id = fingerprint_id;
        
        /* 写指纹ID队列 */
        xQueueSend(s_xQueueFingerprint,
                   &fingerprint_data,
                   portMAX_DELAY);
    }
}

3. 蓝牙任务

image-20251008101825896

蓝牙部分代码刚开始准备做和mqtt部分相同的功能,后来在网上查了一下,现在智能门锁要么支持蓝牙进行远程操作但需要智能网关来给门锁发送命令,优点是低功耗。要么是通过wifi连接,手机直接就可以控制门锁,无需额外设备,但是功耗较高。所以现在智能门锁现在蓝牙功能暂时设计了只能开锁,在没有wifi时可以通过蓝牙开锁。后续有新功能可以再来添加。

这部分代码实现了蓝牙开锁功能,如果检测到客户端的写开锁密码事件,会读取gatt描述表中,开锁的特征值,将密码发送到系统控制任务。

//智能门锁控制 gatt描述表
static const esp_gatts_attr_db_t lock_control_gatt_db[SV1_IDX_NB] =
{
    //智能门锁控制 服务声明
    [SV1_IDX_SVC]        =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
      sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_SMART_LOCK_CONTROL), (uint8_t *)&GATTS_SERVICE_UUID_SMART_LOCK_CONTROL}},

    //特征1 开锁
    //特征声明  自动回复
    [SV1_CH1_IDX_CHAR]     =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_write}},   //特征值1允许写
    //特征值  自动回复 
    [SV1_CH1_IDX_CHAR_VAL] =
    {{ESP_GATT_RSP_BY_APP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_UNLOCK, ESP_GATT_PERM_WRITE, 
      sizeof(lock_control_value), sizeof(lock_control_value), (uint8_t *)lock_control_value}}, /* 只与允许写 */
    //特征描述->客户端特征配置
    [SV1_CH1_IDX_CHAR_CFG]  =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      sizeof(uint16_t), sizeof(lock_control_client_cfg), (uint8_t *)lock_control_client_cfg}},
};

/**
 * gatt事件回调函数
 * @param event 事件ID
 * @param gatts_if gatt接口,一个profile对应一个
 * @param param 事件参数
 * @return 无
 */
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
    switch (event) {
        case ESP_GATTS_WRITE_EVT:   //收到客户端的写事件
            if (!param->write.is_prep){
                ESP_LOGI(TAG, "GATT_WRITE_EVT, handle = %d, value len = %d, value :", param->write.handle, param->write.len);
                esp_log_buffer_hex(TAG, param->write.value, param->write.len);
                if(param->write.handle == sv1_handle_table[SV1_CH1_IDX_CHAR_CFG])    //特征1客户端特征配置
                {
                    lock_control_client_cfg[0] = param->write.value[0];
                    lock_control_client_cfg[1] = param->write.value[1];
                }
                if(param->write.handle == sv1_handle_table[SV1_CH1_IDX_CHAR_VAL])    //特征1客户端特征值
                {
                    bluetooth_data_t bluetooth_data;
                    memset(&bluetooth_data,
                           0,
                           sizeof(bluetooth_data));

                    bluetooth_data.op = UNLOCK;

                    memcpy(bluetooth_data.password,
                           param->write.value,
                           strlen((char*)param->write.value));
                    /* 发送队列 */
                    xQueueSend(s_xQueueBluetooth,
                               &bluetooth_data,
                               pdMS_TO_TICKS(500));
                }
                if (param->write.need_rsp){
                    esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
                }
            }
      	    break;
        default:
            break;
    }
}

/**
 * @brief 蓝牙任务
 *
 * @param params: 创建任务时传递的参数
 *
 * @return 无
 */
void app_bluetooth_task(void *params)
{
    /* 蓝牙初始化 */
    ble_cfg_net_init();
    /* 创建蓝牙队列 */
    s_xQueueBluetooth = xQueueCreate(BLUETOOTH_QUEUE_LEN,sizeof(bluetooth_data_t));
    vTaskDelete(NULL);
    while(1)
    {
        vTaskDelay(10000);
    }
}

4. onenet任务

image-20251008102108596

这部分代码实现了 通过 mqtt 连接 onenet ,通过注册回调函数来处理onenet下发的各种数据,通过简单处理后发送给系统控制任务。收到系统控制任务发来的开锁日志和报警信息后上传onenet。

/**
 * @brief mqtt 连接事件回调函数
 *
 * @param client             mqtt 客户端句柄
 *
 * @return 无
 */
static void mqtt_connect_cb(esp_mqtt_client_handle_t client)
{
    char property[128];
    /* 订阅mqtt各种报文 */
    sprintf(property,
            "$sys/%s/%s/thing/property/post/reply",
            ONENET_PRODUCT_ID,
            ONENET_DRVICE_NAME);                      /* 属性上报响应 */
    esp_mqtt_client_subscribe_single(client, property, 0);

    sprintf(property,
            "$sys/%s/%s/thing/property/set",
            ONENET_PRODUCT_ID,
            ONENET_DRVICE_NAME);                      /* 属性设置请求 */
    esp_mqtt_client_subscribe_single(client, property, 0);

    sprintf(property,
            "$sys/%s/%s/thing/property/get",
            ONENET_PRODUCT_ID,
            ONENET_DRVICE_NAME);                      /* 属性获取请求 */
    esp_mqtt_client_subscribe_single(client, property, 0);

    sprintf(property,
            "$sys/%s/%s/thing/event/post/reply",
            ONENET_PRODUCT_ID,
            ONENET_DRVICE_NAME);                      /* 事件上报响应 */
    esp_mqtt_client_subscribe_single(client, property, 0);

    sprintf(property,
            "$sys/%s/%s/thing/service/%s/invoke",
            ONENET_PRODUCT_ID,
            ONENET_DRVICE_NAME,
            ONENET_UNLOCK_SERVICE);                   /* 开锁调用请求           */
    esp_mqtt_client_subscribe_single(client, property, 0);

    sprintf(property,
            "$sys/%s/%s/thing/service/%s/invoke",
            ONENET_PRODUCT_ID,
            ONENET_DRVICE_NAME,
            ONENET_GET_PWD_INFO_SERVICE);            /* 获取所有密码信息调用请求 */
    esp_mqtt_client_subscribe_single(client, property, 0);

    sprintf(property,
            "$sys/%s/%s/thing/service/%s/invoke",
            ONENET_PRODUCT_ID,
            ONENET_DRVICE_NAME,
            ONENET_OTA_SERVICE);                     /* ota调用请求            */
    esp_mqtt_client_subscribe_single(client, property, 0);

    sprintf(property,
            "$sys/%s/%s/ota/inform",
            ONENET_PRODUCT_ID, 
            ONENET_DRVICE_NAME);                      /* OTA升级通知           */
    esp_mqtt_client_subscribe_single(client,property, 0);
    
    static uint8_t ota_flag = OTA_DISENABLE;
    if(NULL == g_xOTATaskHandle)
    {
        MY_LOGI("开始上报版本号");
        xTaskCreatePinnedToCore(app_ota_task,
                                "app_ota_task",
                                4096, 
                                &ota_flag,
                                8, 
                                &g_xOTATaskHandle, 
                                tskNO_AFFINITY);
    }    
}

/**
 * @brief mqtt 数据回调函数
 *
 * @param esp_mqtt_client_handle_t mqtt 客户端句柄
 * @param event_data               数据指针
 *
 * @return 无
 */
static void mqtt_data_cb(esp_mqtt_client_handle_t client, void* event_data)
{
    esp_mqtt_event_t* Data = (esp_mqtt_event_t*)event_data;
    MY_LOGI("Topic:%.*s",Data->topic_len,Data->topic);
    MY_LOGI("data:%.*s",Data->data_len,Data->data);
    /* 属性设置请求 */
    if(strstr(Data->topic,"/property/set"))
    {
        /* 获取属性设置回复id和参数 */
        cJSON* data_js = cJSON_Parse(Data->data);
        char* property_ack_id = 
            cJSON_GetStringValue(cJSON_GetObjectItem(data_js,ONENET_ID));
        cJSON* params_js = cJSON_GetObjectItem(data_js, ONENET_PARAMS); 
       
        if(NULL != params_js)
        {
            cJSON* property_name_js = params_js->child;
            while(property_name_js)
            {
                /* 处理属性回复   */
                onenet_process_property_set(client,
                                            property_name_js,
                                            property_name_js->string,
                                            property_ack_id);         
                property_name_js = property_name_js->next;
            }
        }
        cJSON_Delete(data_js);
    }
    /* 属性获取请求 */
    else if(strstr(Data->topic,"/property/get"))
    {
        /* 获取属性获取回复id和参数 */
        cJSON* data_js = cJSON_Parse(Data->data);
        char* property_ack_id = 
            cJSON_GetStringValue(cJSON_GetObjectItem(data_js,ONENET_ID));
        cJSON* params_js = cJSON_GetObjectItem(data_js, ONENET_PARAMS); 
       
        if(NULL != params_js)
        {
            cJSON* property_name_js = params_js->child;
            while(property_name_js)
            {
                /* 处理属性回复   */
                onenet_process_property_get(client,
                                            property_name_js,
                                            property_name_js->string,
                                            property_ack_id);                
                property_name_js = property_name_js->next;
            }
        }
        cJSON_Delete(data_js);
    }
    /* 事件上报回应 */
    else if(strstr(Data->topic,"/event/post/reply"))
    {
        uint8_t event_code = 0;

        cJSON* data_js = cJSON_Parse(Data->data);
        char* event_ack_id = 
            cJSON_GetStringValue(cJSON_GetObjectItem(data_js,ONENET_ID));

        event_code = 
            cJSON_GetNumberValue(cJSON_GetObjectItem(data_js,OENNET_CODE));
        char* event_msg = 
            cJSON_GetStringValue(cJSON_GetObjectItem(data_js,ONENET_MSG));

        if(ONENET_REPLY_SUCCESS != event_code)
            MY_LOGE("事件上报失败");
        else
            MY_LOGI("事件上报成功");

        MY_LOGI("id: %s,code: %d,msg: %s",
                event_ack_id, 
                event_code, 
                event_msg);
        cJSON_Delete(data_js);
    }
    /* 服务调用请求 */
    else if(strstr(Data->topic,"/invoke"))
    {
        /* 获取服务调用回复id和参数 */
        cJSON* data_js = cJSON_Parse(Data->data);
        char* event_ack_id = 
            cJSON_GetStringValue(cJSON_GetObjectItem(data_js,ONENET_ID));
        cJSON* params_js = cJSON_GetObjectItem(data_js, ONENET_PARAMS); 
       
        if(NULL != params_js)
        {
            cJSON* property_name_js = params_js->child;
            while(property_name_js)
            {
                /* 处理服务调用回复   */
                onenet_process_service_invoke(client,
                                              property_name_js,
                                              property_name_js->string,
                                              event_ack_id);                  
                property_name_js = property_name_js->next;
            }
        }
        cJSON_Delete(data_js);
    }
    /* ota升级回复 */
    else if(strstr(Data->topic,"/ota/inform"))
    {
        /* 获取ota回复id和参数 */
        cJSON* data_js = cJSON_Parse(Data->data);
        char* ota_ack_id = 
            cJSON_GetStringValue(cJSON_GetObjectItem(data_js,ONENET_ID));
        cJSON* params_js = cJSON_GetObjectItem(data_js, ONENET_PARAMS);             

        cJSON* property_name_js = params_js->child;
        onenet_process_ota_inform(client,
                                  property_name_js,
                                  property_name_js->string,
                                  ota_ack_id);
    }
}

/**
 * @brief mqtt 断开连接事件回调函数
 *
 * @param client             mqtt 客户端句柄
 *
 * @return 无
 */
static void mqtt_disconnect_cb(esp_mqtt_client_handle_t client)
{
    char token[180];
    memset(&token, 0, sizeof(token));
    onenet_get_Token(token);
    esp_mqtt_client_config_t mqtt_client_config;
    memset(&mqtt_client_config,0,sizeof(mqtt_client_config));
    onenet_get_connect_params(&mqtt_client_config, token); 
    net_mqtt_updata_config(&mqtt_client_config);
}

/**
 * @brief onenet 任务
 *
 * @param params             创建任务时传递的参数
 *
 * @return 无
 */
void onenet_task(void* params)
{
    /* 创建 onenet 队列 */
    g_xQueueOnenet = xQueueCreate(ONENET_QUEUE_LEN, 
                                  sizeof(onenet_data_t));
    /* 创建开锁日志队列 */
    g_xQueueUnlockLog = xQueueCreate(UNLOCK_LOG_QUEUE_LEN, 
                                     sizeof(unlock_result_info_t));

    /* 创建处理 onenet 数据事件组  */
    g_xEventProcessOnenetData = xEventGroupCreate();

    xEventGroupWaitBits(g_xEventWifiConnect,
                        WIFI_CONNECT_BIT, 
                        pdFALSE,
                        pdFALSE,
                        portMAX_DELAY);                    /* 等待wifi连接完成    */

    char token[180];
    memset(&token, 0, sizeof(token));
    onenet_get_Token(token);                               /* 获取连接token       */
    esp_mqtt_client_config_t mqtt_client_config;
    memset(&mqtt_client_config, 0, sizeof(mqtt_client_config));
    onenet_get_connect_params(&mqtt_client_config, token); /* 获取 onenet 连接参数 */ 

    net_mqtt_init(mqtt_client_config);                    /* mqtt 初始化           */
    mqtt_register_connect_callback(mqtt_connect_cb);      /* 注册 mqtt 连接回调函数 */
    mqtt_register_disconnect_callback(mqtt_disconnect_cb);/* 注册 mqtt 数据回调函数 */
    mqtt_register_data_callback(mqtt_data_cb);            /* 注册 mqtt 数据回调函数 */

    xEventGroupWaitBits(g_xEventWifiConnect,
                        WIFI_CONNECT_BIT, 
                        pdTRUE,
                        pdFALSE,
                        portMAX_DELAY);                   /* 等待wifi连接完成      */
    net_mqtt_start();                                     /* 启动mqtt              */
    while(1)
    {
        unlock_result_info_t unlock_log;
        memset(&unlock_log,
               0,
               sizeof(unlock_log));
        xQueueReceive(g_xQueueUnlockLog,
                      &unlock_log,
                      portMAX_DELAY);

        /* 上报胁迫指纹信息 */
        if(UNLOCK_FINGERPRINT == unlock_log.unlock_mode
            && FINGERPRINT_TYPE_COERCIVE == unlock_log.fp_unlock_info.fp_type)
        {
            onenet_post_coercive_event(mqtt_get_client_handle(),
                                       unlock_log);
        }

        /* 上报多次开锁失败信息 */
        if(UNLOCK_ALARM_MIN_TRY_COUNT <= unlock_log.unlock_count
            && UNLOCK_FAILED == unlock_log.unlock_result)
            onenet_post_unlock_faild_event(mqtt_get_client_handle(),
                                           unlock_log);
        else
            /* 上报开锁信息 */
            onenet_post_unlock_event(mqtt_get_client_handle(),
                                 unlock_log);
    }
}

5. 存储任务

image-20251008102129172

这部分代码通过队列集实现了,密码和指纹的存储和删除操作。通过定时器定时更新密码位图,密码数量和指纹数量

/**
 * @brief 更新存储回调函数
 *
 * @param xTimer             定时器ID
 *
 * @return 无
 */
void updataStorageTimerFunc( TimerHandle_t xTimer )
{
    static uint8_t hour_count = 0;               /* 计时1小时加1,24小时更新一次 */
    static uint64_t password_bitmap_last = 0;          /* 记录上次密码位图数据   */
    static uint8_t current_admin_password_count = 0;   /* 记录当前管理员密码数量 */
    static uint8_t last_admin_password_count = 0;      /* 记录上次管理员密码数量 */
    static uint8_t current_user_password_count = 0;    /* 记录当前用户密码数量   */
    static uint8_t last_user_password_count = 0;       /* 记录上次用户密码数量   */
    static uint8_t current_cycle_password_count = 0;   /* 记录当前周期密码数量   */
    static uint8_t last_cycle_password_count = 0;      /* 记录上次周期密码数量   */
    static uint8_t current_temp_password_count = 0;    /* 记录当前临时密码数量   */
    static uint8_t last_temp_password_count = 0;       /* 记录上次临时密码数量   */
    static uint8_t current_admin_fingerprint_count = 0;/* 记录当前管理员指纹数量 */
    static uint8_t last_admin_fingerprint_count = 0;   /* 记录上次管理员指纹数量 */
    static uint8_t current_user_fingerprint_count = 0; /* 记录当前用户指纹数量   */
    static uint8_t last_user_fingerprint_count = 0;    /* 记录上次用户指纹数量   */
    static password_queue_count_t password_queue_type = {0};
    static fingerprint_queue_count_t fingerprint_queue_type = {0};
    uint8_t i = 0;
    hour_count++;
    if(TIMER_UPDATA_PERIOD == hour_count 
       || DEL_ALL_PASSWORD_TIMERID == (uint32_t)pvTimerGetTimerID(xTimer)
       || DEL_ALL_FINGERPRINT_TIMERID == (uint32_t)pvTimerGetTimerID(xTimer))
    {
        hour_count = 0;

        /* 写密码位图 */
        xSemaphoreTake(g_xMutexPasswordBitmap, 0); 
        if(password_bitmap_last 
            != g_password_bitmap)                 /* 如果上次和这次不一样才不更新 */
        {
            xQueueSendFromISR(s_xQueuePasswordBitmap,
                              &g_password_bitmap,
                              NULL);              /* 写密码位图                  */
            password_bitmap_last = g_password_bitmap;
            MY_LOGI("写密码位图");
        } 
        xSemaphoreGive(g_xMutexPasswordBitmap);

        /* 密码数量 */
        static count_entry_t password_entries[] = 
        {
            {
                &g_xSemaphoreAdminPassword,  
                &s_xQueuePasswordCount, 
                &current_admin_password_count, 
                &last_admin_password_count, 
                {
                    PASSWORD_IDENTITY_ADMIN
                },
                "写管理员密码信号量" 
            },
            {   
                &g_xSemaphoreUserPassword,   
                &s_xQueuePasswordCount, 
                &current_user_password_count,
                &last_user_password_count,
                {
                    PASSWORD_IDENTITY_USER
                },
                "写用户密码信号量" 
            },
            { 
                &g_xSemaphoreCyclePassword,  
                &s_xQueuePasswordCount,
                &current_cycle_password_count, 
                &last_cycle_password_count,
                {
                    PASSWORD_IDENTITY_CYCLE
                },
                "写周期密码信号量" 
            },
            { 
                &g_xSemaphoreTempPassword,   
                &s_xQueuePasswordCount, 
                &current_temp_password_count, 
                &last_temp_password_count,
                {
                    PASSWORD_IDENTITY_TEMP
                },
                "写临时密码信号量" 
            },
        };

        for(i = 0; i < 4; i++)
        {
            *(password_entries[i].current_count) = 
                uxSemaphoreGetCount(*(password_entries[i].xSemaphore));
            if(*(password_entries[i].last_count) != *(password_entries[i].current_count))
            {
                password_queue_type.identity = password_entries[i].password_identity;
                password_queue_type.password_count = *(password_entries[i].current_count);
                xQueueSendFromISR(*(password_entries[i].xQueue), 
                                  &password_queue_type, 
                                  NULL);
                *(password_entries[i].last_count) = *(password_entries[i].current_count);
                MY_LOGI("%s",password_entries[i].log_message);
            }
        }

        /* 指纹数量 */
        static count_entry_t fingerprint_entries[] = 
        {
            { 
                &g_xSemaphoreAdminFingerprint,  
                &s_xQueueFingerprintCount,
                &current_admin_fingerprint_count, 
                &last_admin_fingerprint_count,
                {
                    FINGERPRINT_IDENTITY_ADMIN
                },
                "写管理员指纹信号量" 
            },
            { 
                &g_xSemaphoreUserFingerprint,   
                &s_xQueueFingerprintCount, 
                &current_user_fingerprint_count, 
                &last_user_fingerprint_count,
                {
                    FINGERPRINT_IDENTITY_USER
                },
                "写用户指纹信号量" 
            },
        };

        for(i = 0; i < 2; i++)
        {
            *(fingerprint_entries[i].current_count) = 
                uxSemaphoreGetCount(*(fingerprint_entries[i].xSemaphore));
            if(*(fingerprint_entries[i].last_count) != *(fingerprint_entries[i].current_count))
            {
                fingerprint_queue_type.identity = fingerprint_entries[i].fingerprint_identity;
                fingerprint_queue_type.fingerprint_count = *(fingerprint_entries[i].current_count);
                xQueueSendFromISR(*(fingerprint_entries[i].xQueue),
                                  &fingerprint_queue_type,
                                  NULL);
                *(fingerprint_entries[i].last_count) = *(fingerprint_entries[i].current_count);
                MY_LOGI("%s",fingerprint_entries[i].log_message);
            }
        }
    }
}

/**
 * @brief 存储任务
 *
 * @param params               创建任务时传递的参数
 *
 * @return 无
 */
void app_storage_task(void* params)
{   
    /* 存储初始化 */
    app_storage_init();
    QueueHandle_t xQueueHandle;
    while(1)
    {
        /* 读队列集获取队列句柄 */
        xQueueHandle = xQueueSelectFromSet(s_xQueueSetStorage, portMAX_DELAY);
        if(xQueueHandle == s_xQueuePasswordBitmap)
        {
            /* 处理密码位图数据 */
            app_process_bitmap();
        }
        else if(xQueueHandle == s_xQueuePasswordCount)
        {
            /* 处理密码数量数据 */
            app_process_password_count();
        }
        else if(xQueueHandle == s_xQueueFingerprintCount)
        {
            /* 处理指纹数量数据 */
            app_process_fingerprint_count();
        }
        else if(xQueueHandle == s_xQueueStorageMsg)
        {
            /* 处理存储消息数据 */
            app_process_storage_msg();
        }
        else
            MY_LOGE("读取队列失败");
    }
}

6. 开锁任务

image-20251008102139473

这部分代码通过队列集实现了接收各种方式的开锁数据,并进行相应验证,将验证结果发送给系统控制任务。

/**
 * @brief 开锁任务
 *
 * @param params             创建任务时传入的参数
 *
 * @return 无
 */
void app_unlock_task(void *params)
{
    com_status_t com_status = COM_ERROR;
    /* 创建按键开锁信息队列 */
    g_xQueueKeyUnlockInfo = xQueueCreate(KEY_UNLOCK_QUEUE_LEN, sizeof(key_unlock_t));
    /* 创建指纹开锁信息队列 */
    g_xQueueFingerprintUnlockInfo = xQueueCreate(FINGERPRINT_UNLOCK_QUEUE_LEN, sizeof(fingerprint_identity_t));
    /* 创建onenet开锁信息队列 */
    g_xQueueOnenetUnlockInfo = xQueueCreate(ONENET_UNLOCK_QUEUE_LEN, sizeof(onenet_unlock_t));
    /* 创建蓝牙开锁信息队列 */
    g_xQueueBluetoothUnlockInfo = xQueueCreate(BLUETOOTH_UNLOCK_QUEUE_LEN, sizeof(bluetooth_unlock_t));
    /* 创建开锁信息队列集 */
    s_xQueueSetUnlockInfo = xQueueCreateSet(KEY_UNLOCK_QUEUE_LEN
                                            + FINGERPRINT_UNLOCK_QUEUE_LEN
                                            + ONENET_UNLOCK_QUEUE_LEN
                                            + BLUETOOTH_UNLOCK_QUEUE_LEN);
    
    /* 创建开锁结果队列 */
    s_xQueueUnlockResult = xQueueCreate(UNLOCK_RESULT_QUEUE_LEN, sizeof(unlock_result_info_t));
    
    /* 将各个队列加入队列集 */
    xQueueAddToSet(g_xQueueKeyUnlockInfo, s_xQueueSetUnlockInfo);
    xQueueAddToSet(g_xQueueFingerprintUnlockInfo, s_xQueueSetUnlockInfo);
    xQueueAddToSet(g_xQueueOnenetUnlockInfo, s_xQueueSetUnlockInfo);
    xQueueAddToSet(g_xQueueBluetoothUnlockInfo, s_xQueueSetUnlockInfo);
    
    QueueHandle_t xQueueHandle;
    unlock_result_info_t unlock_result;

    while(1)
    {
        memset(&unlock_result,
               0,
               sizeof(unlock_result));
            
        /* 读队列集 */
        xQueueHandle = xQueueSelectFromSet(s_xQueueSetUnlockInfo,
                                           portMAX_DELAY);
        /* 根据队列句柄处理数据 */
        if(xQueueHandle == g_xQueueKeyUnlockInfo)
        {
            unlock_result.unlock_mode = UNLOCK_KEY;
            com_status = app_process_key_Info(&unlock_result);
        }
        else if(xQueueHandle == g_xQueueFingerprintUnlockInfo)
        {
            unlock_result.unlock_mode = UNLOCK_FINGERPRINT;
            com_status = app_process_fingerprint_Info(&unlock_result);
        }
        else if(xQueueHandle == g_xQueueOnenetUnlockInfo)
        {
            unlock_result.unlock_mode = UNLOCK_ONENET;
            com_status = app_process_onenet_Info(&unlock_result);
        }
        else if(xQueueHandle == g_xQueueBluetoothUnlockInfo)
        {
            unlock_result.unlock_mode = UNLOCK_BLUETOOTH;
            com_status = app_process_bluetooth_Info(&unlock_result);
        }
        else
        {
            com_status = COM_OK;
            MY_LOGE("发生错误");
        }

        /* 开锁成功 */
        if(COM_OK == com_status)
        {
            BDR6120S_open_lock();
            MY_LOGI("开锁");
            WTN6170_send_voice(WTN6170_VOICE_DOOR_OPEN_EFFECT);

            unlock_result.unlock_result = UNLOCK_SUCCESS;

            /* 创建定时器,开启所有中断 */
            TimerHandle_t on_itor = xTimerCreate("onIntrTimerFunc",
                                                 pdMS_TO_TICKS(10),
                                                 pdFALSE,
                                                 (void *)ON_ALL_INTR,
                                                 onIntrTimerFunc);
            /* 启动定时器 */
            if(on_itor != NULL)
            {
                MY_LOGI("定时器创建成功");
                if(pdPASS == xTimerStart(on_itor,0))
                {
                    MY_LOGI("定时器启动成功");
                }
            }
        }
        else
        {
            WTN6170_send_voice(WTN6170_VOICE_FAILURE_EFFECT);
            MY_LOGE("开锁失败");
            unlock_result.unlock_result = UNLOCK_FAILED;
        }

        unlock_result.unlock_time = get_system_time();

        /* 写开锁结果队列 */
        xQueueSend(s_xQueueUnlockResult,
                   &unlock_result,
                   portMAX_DELAY);
    }
}

7. ota任务

image-20251008102151360

这部分代码通过http连接onenet,上报版本号并检测是否有升级任务。如果有ota升级任务则进行ota升级。

/**
 * @brief ota升级任务
 *
 * @param params             创建任务时传递的参数
 *
 * @return 无
 */
void app_ota_task(void *params)
{    
    MY_LOGI("开始ota");
    vTaskDelay(1000);
    EventBits_t ota_task_flag;
    bool result = false;
    ota_task_t ota_task;                       /* ota任务数据        */
    /* 创建ota任务事件组 */
    s_xEventOtaTask = xEventGroupCreate();

    /* 初始化 ota */
    esp_err_t http_err = ESP_OK;

    /* 初始化http客户端 */
    app_http_client_init();

    /* 上报版本号 */
    result = ota_post_version();
    if(false == result)
    {
        MY_LOGE("版本号上报失败");
    }
            MY_LOGI("ota_flag %d",*(uint8_t*)params);
    /* 如果ota标志不为OTA_ENABLE,则上报完版本号结束任务 */
    if(OTA_ENABLE != *(uint8_t*)params)
    {
        MY_LOGI("上报版本号完成");
        http_client_cleanup();  
        g_xOTATaskHandle = NULL;  
        vTaskDelete(NULL);
        return;
    }

    /* 查看是否有固件升级任务 */
    result = ota_check_ota_task(FIRMWARE_VERSION,&ota_task);
    if(false == result)
    {
        MY_LOGW("没有固件升级任务");
        /* 查看是否有软件升级任务 */
        result = ota_check_ota_task(SOFTWARE_VERSION,&ota_task);
    }
    if(false == result)
    {
        MY_LOGW("没有软件升级任务");
    }
    /* 关闭http连接并释放资源*/
    http_client_cleanup();

    while (1)
    {
        /* 等待1分钟,后结束任务*/
        ota_task_flag = xEventGroupWaitBits(s_xEventOtaTask,    
                                            OTA_TASK_FLAG,
                                            pdTRUE,
                                            pdFALSE,
                                            pdMS_TO_TICKS(3000));
        if(OTA_TASK_FLAG == ota_task_flag)
        {
            MY_LOGI("ota start");
            static char http_url[128];
            memset(http_url,
                   0,
                   sizeof(http_url));
            sprintf(http_url,
                    "%s/%s/%s/%lu/download", 
                    HTTP_URL, 
                    ONENET_PRODUCT_ID, 
                    ONENET_DRVICE_NAME, 
                    ota_task.ota_task_tid);
            MY_LOGI("%s",http_url);

            /* 重新配置http 连接参数 */
            esp_http_client_config_t http_client_ota_config = 
            {
                .url = http_url,                                /* 下载地址  */
                .port = 443,                                    /* 通信端口 */
                .method = HTTP_METHOD_GET,
                .transport_type = HTTP_TRANSPORT_OVER_SSL,      /* 传输方式  */  
                .cert_pem = (const char *)g_onenet_ca,          /* 服务CA证书 */
                .skip_cert_common_name_check = false,           /* 域名验证 */
                .event_handler = http_event_callback,           /* 事件回调函数 */
                .timeout_ms = 60*1000,                          /* 超时时间 */
                .keep_alive_enable = true,                      /* 保持连接 */

            };

            /* 配置http ota */
            esp_https_ota_config_t https_ota = 
            {
                .http_config = &http_client_ota_config,
            };

            /* 开始ota */
            http_err = esp_https_ota(&https_ota);
            if(http_err == ESP_OK)
            {
                MY_LOGI("ota升级成功");
            }
            else
            {
                MY_LOGE("OTA fail:%d",http_err);
            } 
            http_client_cleanup();  
        }
        MY_LOGW("not ota task");

        storage_io_t storage_io;
        uint8_t ota_flag = OTA_DISENABLE;
        memset(&storage_io,
               0,
               sizeof(storage_io));

        storage_io.ns = STORAGE_TYPE_OTA;
        storage_io.key = OTA_FLAG_KEY;
        storage_io.value = &ota_flag;
        if (COM_OK == storage_write_u8(&storage_io))
        {
            MY_LOGI("写入ota标志位成功,2s后重启系统");
            esp_wifi_disconnect();          // 先断开连接
            vTaskDelay(pdMS_TO_TICKS(100)); // 短暂延时,确保断开事件被处理
            esp_wifi_stop();                // 再停止WiFi
            vTaskDelay(pdMS_TO_TICKS(100)); // 再次延时,确保WiFi完全停止
            esp_wifi_deinit();              // 最后反初始化
            vTaskDelay(pdMS_TO_TICKS(2000));
            g_xOTATaskHandle = NULL;   
            esp_restart();
        }
    }
}

8. 系统控制任务

image-20251008102209933

这部分代码通过队列集实现了读取各个任务传来的数据并做相应处理。

/**
 * @brief 系统控制任务
 *
 * @param 无
 *
 * @return 无
 */
void App_system_controller_task(void* params)
{
    /* 获取存储消息队列句柄 */
    s_xQueueStorageMsg = app_get_queue_storage_msg();

    /* 获取按键队列句柄 */
    s_xQueueKey = app_get_queue_key();
    /* 获取指纹id队列句柄 */
    s_xQueueFingerprint = app_get_queue_fingerprint();
    /* 获取onenet队列句柄 */
    s_xQueueOnenet = app_get_queue_onenet();
    /* 获取蓝牙队列句柄 */
    s_xQueueBluetooth = app_get_queue_bluetooth();
    /* 获取存储结果队列句柄 */
    s_xQueueStorageResult = app_get_queue_storage_result();
    /* 获取开锁结果队列句柄 */
    s_xQueueUnlockResult = app_get_queue_unlock_result();

    /* 创建输入队列集 */
    s_xQueueSetInput = xQueueCreateSet(INPUT_QUEUESET_LEN);
    /* 将按键输入队列加入队列集 */
    xQueueAddToSet(s_xQueueKey, s_xQueueSetInput);
    /* 将指纹ID队列加入队列集 */
    xQueueAddToSet(s_xQueueFingerprint, s_xQueueSetInput);
    /* 将onenet队列加入队列集 */
    xQueueAddToSet(s_xQueueOnenet, s_xQueueSetInput);
    /* 将蓝牙队列加入队列集 */
    xQueueAddToSet(s_xQueueBluetooth, s_xQueueSetInput);
    /* 将存储结果队列加入队列集 */
    xQueueAddToSet(s_xQueueStorageResult, s_xQueueSetInput);
    /* 将开锁结果队列加入队列集 */
    xQueueAddToSet(s_xQueueUnlockResult, s_xQueueSetInput);

    QueueHandle_t xQueueHandle;
    /* 初始化命令表 */
    cmd_table_init();

    while(1)
    {
        /* 读队列集获取队列句柄 */
        xQueueHandle = xQueueSelectFromSet(s_xQueueSetInput, portMAX_DELAY);
        if(xQueueHandle == s_xQueueKey)
        {
            /* 处理按键数据 */
            app_process_key();
        }
        else if(xQueueHandle == s_xQueueFingerprint)
        {
            /* 处理指纹ID数据 */
            app_process_fingerprint_id();
        }
        else if(xQueueHandle == s_xQueueOnenet)
        {
            /* 处理onenet数据 */
            app_process_onenet();
        }
        else if(xQueueHandle == s_xQueueBluetooth)
        {
            /* 处理蓝牙数据 */
            app_process_bluetooth();
        }
        else if(xQueueHandle == s_xQueueStorageResult)
        {
            /* 处理存储结果数据 */
            app_process_storage_result();
        }
        else if(xQueueHandle == s_xQueueUnlockResult)
        {
            /* 处理开锁结果数据 */
            app_process_unlock_result();
        }
        else
            MY_LOGE("读取队列失败");
    }
}

按键处理函数通过状态机实现根据读取的值进行状态的转换。

/* 状态类型 */
typedef struct state state;
/* 状态类型函数指针 */
typedef state* (*state_handler)(void* context);

/* 定义状态机的状态 */
struct state
{
    state_handler on_enter;    /* 进入状态时自动调用      */
    state_handler on_input;    /* 进入状态时接收到输入调用 */
};

/* 当前状态是否是输入 */
typedef enum 
{
    CURRENT_IS_NOT_INPUT = 0,
    CURRENT_IS_INPUT
}current_is_input;

/**
 * @brief 处理按键数据
 *
 * @param  无 上一次
 *
 * @return 无
 */
static void app_process_key(void)
{
    static state_machine_ctx_t state_machine_ctx = {.system_parent_state = STATE_IDLE};
    static state* current_state = &stateIdle;            /* 当前状态   */
    static state* next_state = &stateIdle;               /* 下一个状态 */
    static current_is_input is_input = CURRENT_IS_INPUT; /* 默认当前是输入,因为空闲状态是输入 */
    memset(state_machine_ctx.temp_data,
           0,
           sizeof(state_machine_ctx.temp_data));
    /* 读按键队列 */
    xQueueReceive(s_xQueueKey,
                  state_machine_ctx.temp_data,
                  0);
    if('\0' == state_machine_ctx.temp_data[0])  
    {
        MY_LOGW("输入错误或输入超时");
        is_input = CURRENT_IS_INPUT;               /* 将当前状态改为是输入 */
        current_state = &stateIdle;
        return;
    }
    while(1)
    {
        if(CURRENT_IS_NOT_INPUT== is_input )       /* 如果不是输入          */
            next_state = current_state->on_enter(&state_machine_ctx);
        else                                       /* 如果是输入,只有执行完enter函数并且有input函数才有可能进入 */
        {
            if(NULL == current_state->on_input)    /* 如果input 函数为空说明发生错误 */
            {
                is_input = CURRENT_IS_NOT_INPUT;  
                current_state = &stateError;
                break;
            }
            next_state = current_state->on_input(&state_machine_ctx);
            is_input = CURRENT_IS_NOT_INPUT;      /* 将是否是输入改为不是输入 */
        }
        if(next_state == &stateIdle)              /* 如果状态为空闲状态说明命令执行结束 */
        {        
            is_input = CURRENT_IS_INPUT;          /* 将是否是输入改为是输入 */        
            current_state = &stateIdle;           /* 将当前状态变为空闲状态 */
            break;
        }
        else if(next_state == current_state)      /* 如果当前状态和下一个状态相同说明下一次是输入 */
        {
            is_input = CURRENT_IS_INPUT;          /* 将是否是输入改为是输入 */    
            break;
        }    
        else                                      /* 当前状态和下一次状态不相同 */
            current_state = next_state;           /* 将当前状态变为下一个状态 */
    }
}

指纹处理函数根据读取指纹队列中指纹的作用,将指纹ID发送给相应的队列处理。

/* 指纹队列数据类型 */
typedef struct 
{
    int8_t fingerprint_id;                          /* 指纹ID       */
    fingerprint_action fp_action;                   /* 指纹作用     */
} fingerprint_data_t;  

/**
 * @brief 处理指纹ID数据
 *
 * @param 无
 *
 * @return 无
 */
static void app_process_fingerprint_id(void)
{
    fingerprint_data_t fingerprint_data = {0};
    uint8_t ID[INPUT_MAX_LEN];
    memset(ID, 0, sizeof(ID));
    /* 读指纹ID队列 */
    xQueueReceive(s_xQueueFingerprint,
                  &fingerprint_data,
                  0);
    switch (fingerprint_data.fp_action)
    {
    case FP_ACTION_REG_ADMIN:
    case FP_ACTION_REG_USER:
    case FP_ACTION_VERIFY_ADMIN:
        MY_LOGI("%d",fingerprint_data.fingerprint_id);
        if(GET_FINGERPRINT_FAILD != fingerprint_data.fingerprint_id)
            sprintf((char*)ID, 
                    "%hhu", 
                    (uint8_t)fingerprint_data.fingerprint_id);
        /* 写按键队列 */
        xQueueSend(s_xQueueKey,
                   ID,
                   portMAX_DELAY);
        break;
    case FP_ACTION_UNLOCK:
        fingerprint_unlock_t fingerprint_unlock = {0};
        fingerprint_unlock.fingerprintId = fingerprint_data.fingerprint_id;
        MY_LOGI("%d",fingerprint_unlock.fingerprintId);
        /* 写指纹开锁队列 */
        xQueueSend(g_xQueueFingerprintUnlockInfo,
                   &fingerprint_unlock,
                   portMAX_DELAY);
        break;
    default:
        // WTN6170_send_voice(WTN6170_VOICE_OPERATION_FAILURE);
        MY_LOGE("错误类型");
        break;
    }
}

蓝牙处理函数根据读取到的密码发送给开锁队列处理。后续如有新功能只需在操作类型中添加新的功能即可。

/* 操作类型 */
typedef enum
{
    UNLOCK = 0,           /* 开锁 */
}operation_t;

/* 蓝牙数据类型 */
typedef struct 
{
    operation_t op;
    uint8_t password[STORAGE_MAX_LEN];
}bluetooth_data_t;

/**
 * @brief 处理蓝牙数据
 *
 * @param 无
 *
 * @return 无
 */
static void app_process_bluetooth(void)
{
    bluetooth_data_t bluetooth_data;                /* 蓝牙数据     */
    memset(&bluetooth_data,
            0,
            sizeof(bluetooth_data));
    
    /* 读队列 */
    xQueueReceive(s_xQueueBluetooth,
                  &bluetooth_data,
                  0);
            
    if(UNLOCK == bluetooth_data.op)     
        app_bluetooth_unlock(&bluetooth_data);
}

onenet 处理函数通过读取队列中的操作类型做相应操作。

/* onenet 数据类型 */
typedef struct 
{
    uint8_t storage_info[33];                /* 密码或指纹ID            */
    uint8_t storage_info_len;                /* 密码或指纹ID长度        */
    uint8_t storage_name[STORAGE_NAME_LEN];  /* 存储名称                */
    uint8_t storage_name_len;                /* 存储名称长度            */
    union       
    {      
        password_info_t password_info;       /* 密码信息                */
        fingerprint_info_t fingerprint_info; /* 指纹信息                */
    };
    operation_type_t operation_type;         /* 操作类型                */
} onenet_data_t;

/**
 * @brief 处理onenet数据
 *
 * @param 无
 *
 * @return 无
 */
static void app_process_onenet(void)
{
    onenet_data_t onenet_data;                      /* onenet数据 */
    storage_msg_t storage_msg = {0};                /* 存储消息   */

    memset(&storage_msg,
           0,
           sizeof(storage_msg));

    xQueueReceive(s_xQueueOnenet,
                  &onenet_data,
                  0);

    switch (onenet_data.operation_type)
    {
    case OPERATION_ADD_PASSWORD:
        app_onenet_add_password(&onenet_data);
        break;
    case OPERATION_DEL_PASSWORD:
        app_onenet_del_password(&onenet_data);
        break;   
    case OPERATION_DEL_FINGERPRINT:
        app_onenet_del_fingerprint(&onenet_data);
        break;   
    case OPERATION_UNLOCK:
        app_onenet_unlock(&onenet_data);      
        break;
    case OPERATION_MANUAL_OTA:
        app_onenet_manual_ota();
        break;
    case OPERATION_OTA_INFORM:
        app_onenet_auto_ota();
        break;
    default:
        MY_LOGE("错误类型");
        break;
    } 
}

存储结果处理函数通过读取存储结果队列中的存储数据来源,将存储结果返回给各个来源。

/* 存储状态 */
typedef enum
{ 
    STORAGE_SUCCESS = 1,                                      /* 存储成功               */
    STORAGE_FAILD,                                            /* 存储失败               */
}storage_status_t;

/* 存储数据来源 */
typedef enum
{ 
    DATA_SOURCE_KEY = 1,                                      /* 数据来源按键            */
    DATA_SOURCE_BLUETOOTH,                                    /* 数据来源蓝牙            */
    DATA_SOURCE_ONENET,                                       /* 数据来源onenet          */
    DATA_SOURCE_UNLOCK,                                       /* 数据来源开锁            */
}storage_data_source_t;

/* 存储结果类型 */
typedef struct
{
    storage_status_t storage_status;                         /* 存储状态           */
    storage_data_source_t storage_data_source;               /* 存储数据来源       */
}storage_result_t;

/**
 * @brief 处理存储结果事件数据
 *
 * @param 无
 *
 * @return 无
 */
static void app_process_storage_result(void)
{
    storage_result_t storage_result;               /* 存储结果 */
    uint8_t storage_status[INPUT_MAX_LEN];         /* 存储状态 */
    xQueueReceive(s_xQueueStorageResult,
                  &storage_result,
                  0);
          
    switch (storage_result.storage_data_source)
    {
    case DATA_SOURCE_KEY:
        MY_LOGI("收到按键存储回复");
        sprintf((char*)storage_status, 
                "%hhd", 
                (uint8_t)storage_result.storage_status);
        /* 写按键队列 */
        xQueueSend(s_xQueueKey,
                   storage_status,
                   portMAX_DELAY);  
        break;
    case DATA_SOURCE_BLUETOOTH:
        
        break;
    case DATA_SOURCE_ONENET:
        MY_LOGI("收到onenet存储回复");
        if(STORAGE_SUCCESS != storage_result.storage_status)
            xEventGroupSetBits(g_xEventProcessOnenetData,
                               EVENT_REPLY_STORAGE_FAILD);
        else 
            xEventGroupSetBits(g_xEventProcessOnenetData,
                               EVENT_REPLY_SUCCESS);
        break;
    case DATA_SOURCE_UNLOCK:
        MY_LOGI("临时密码存储成功");
        break;        
    default:
        // WTN6170_send_voice(WTN6170_VOICE_OPERATION_FAILURE);
        MY_LOGE("发生错误");
        break;
    }
}

开锁结果处理函数将开锁信息写入开锁日志队列,onenet读取后上传。

/* 开锁结果信息 */
typedef struct 
{
    uint8_t unlock_name[STORAGE_NAME_LEN];        /* 开锁人名称名称          */
    unlock_mode_t unlock_mode;                    /* 开锁方式               */
    uint64_t unlock_time;                         /* 开锁时间               */
    unlock_result_t unlock_result;                /* 开锁结果               */
    uint8_t unlock_count;                         /* 开锁次数               */
    union
    {
        struct
        {
            password_identity_t pwd_identity;     /* 密码身份               */
        }pwd_unlock_info;
        struct 
        {
            fingerprint_identity_t fp_identity;   /* 指纹身份               */
            fingerprint_type_t fp_type;           /* 指纹类型               */
        }fp_unlock_info;
        struct
        {
            password_identity_t pwd_identity;     /* 密码身份               */
        }onenet_unlock_info;
        struct
        {
            password_identity_t pwd_identity;     /* 密码身份               */
        }bt_unlock_info;
    };
}unlock_result_info_t; 

/**
 * @brief 处理开锁结果数据
 *
 * @param 无
 *
 * @return 无
 */
static void app_process_unlock_result(void)
{
    unlock_result_info_t unlock_result;

    /* 读开锁结果队列 */
    xQueueReceive(s_xQueueUnlockResult,
                  &unlock_result,
                  0);

    /* 写开锁日志队列 */
    xQueueSend(g_xQueueUnlockLog,
               &unlock_result,
               portMAX_DELAY);
}

五. main 函数

因为如果同时开启蓝牙,wifi等,ota任务申请内存时有时内存不够,导致ota失败,所以在ota升级时会重启,只开启ota任务进行升级。

/* 存储任务句柄*/
TaskHandle_t g_xSystemControllerTaskHandle = NULL;
/* 按键任务句柄*/
TaskHandle_t g_xKeyTaskHandle              = NULL;
/* 指纹任务句柄*/
TaskHandle_t g_xFingerprintTaskHandle      = NULL;
/* onenet任务句柄*/
TaskHandle_t g_xOnenetTaskHandle           = NULL;
/* 蓝牙任务句柄*/
TaskHandle_t g_xBluetoothTaskHandle        = NULL;
/* 开锁任务句柄*/
TaskHandle_t g_xUnlockTaskHandle           = NULL;
/* 存储任务句柄*/ 
TaskHandle_t g_xStorageTaskHandle          = NULL;
/* ota任务句柄*/ 
TaskHandle_t g_xOTATaskHandle              = NULL;
/* 测试任务句柄*/ 
TaskHandle_t g_xTestHandle                 = NULL;

void app_main(void)
{        
    app_io_Init();
    /* 读取标志位查看是否是因为ota进入的重启 */
    static uint8_t ota_flag = OTA_DISENABLE;
    ota_flag = app_get_ota_flag();

    MY_LOGI("ota_flag %d",ota_flag);
    if(OTA_ENABLE == ota_flag)
    {
        MY_LOGE("ota模式");
        xTaskCreatePinnedToCore(app_ota_task,
                                "app_ota_task",
                                20480, 
                                &ota_flag,
                                8, 
                                &g_xOTATaskHandle, 
                                tskNO_AFFINITY);
        if(NULL == g_xOTATaskHandle)
        {
            MY_LOGE("OTA任务创建失败");
        }
    }
    else
    {
        MY_LOGE("普通模式");
        MY_LOGI("%d",ota_flag);
        xTaskCreatePinnedToCore(app_storage_task,
                                "app_storage_task",
                                4096,
                                NULL,
                                10,
                                &g_xStorageTaskHandle,
                                tskNO_AFFINITY);

        xTaskCreatePinnedToCore(app_scan_key_task,
                                "sacn_key_task",
                                2048,
                                NULL,
                                5,
                                &g_xKeyTaskHandle,
                                tskNO_AFFINITY);

        xTaskCreatePinnedToCore(app_scan_fingerprint_task,
                                "app_scan_fingerprint_task",
                                2048,
                                NULL,
                                3,
                                &g_xFingerprintTaskHandle,
                                tskNO_AFFINITY);

        xTaskCreatePinnedToCore(onenet_task,
                                "onenet_task",
                                5120,
                                NULL,
                                5,
                                &g_xOnenetTaskHandle,
                                tskNO_AFFINITY);

        xTaskCreatePinnedToCore(app_bluetooth_task,
                                "app_bluetooth_task",
                                2048,
                                NULL,
                                5,
                                &g_xBluetoothTaskHandle,
                                tskNO_AFFINITY);

        xTaskCreatePinnedToCore(app_unlock_task,
                                "app_unlock_task",
                                3072,
                                NULL,
                                6,
                                &g_xUnlockTaskHandle,
                                tskNO_AFFINITY);
        
        xTaskCreatePinnedToCore(App_system_controller_task,
                                "App_system_controller_task",
                                5120,
                                NULL,
                                4,
                                &g_xSystemControllerTaskHandle,
                                tskNO_AFFINITY);

        if(NULL == g_xKeyTaskHandle)
        {
            MY_LOGE("按键任务创建失败");
        }

        if(NULL == g_xFingerprintTaskHandle)
        {
            MY_LOGE("指纹任务创建失败");
        }

        if(NULL == g_xOnenetTaskHandle)
        {
            MY_LOGE("onenet任务创建失败");
        }

        if(NULL == g_xBluetoothTaskHandle)
        {
            MY_LOGE("蓝牙任务创建失败");
        }

        if(NULL == g_xStorageTaskHandle)
        {
            MY_LOGE("存储任务创建失败");
        }

        if(NULL == g_xUnlockTaskHandle)
        {
            MY_LOGE("开锁任务创建失败");
        }

        if(NULL == g_xSystemControllerTaskHandle)
        {
            MY_LOGE("系统控制任务创建失败");
        }
    }
}

六. 系统错误和低功耗

在栈溢出或内存分配失败时会通过freertos的钩子函数和回调函数进行重启操作。

/**
 * @brief 内存分配失败回调函数
 *
 * @param requested_size     申请大小
 * @param caps               内存区域标志
 * @param func_name          调用函数名
 *
 * @return 无
 */
void my_malloc_failed_hook(size_t requested_size, uint32_t caps, const char *func_name)
{
    MY_LOGE("Malloc failed! Requested: %d bytes, Caps: %ld, Called by: %s", 
            requested_size, 
            caps, 
            func_name);
    MY_LOGW("%ld",esp_get_free_heap_size());

    size_t largest_free_block = heap_caps_get_largest_free_block(caps);
    MY_LOGW("Largest free block: %d bytes", largest_free_block);
    
    // 打印所有类型内存的详细信息
    heap_caps_print_heap_info(MALLOC_CAP_INTERNAL); // 内部RAM

    esp_wifi_disconnect();          // 先断开连接
    vTaskDelay(pdMS_TO_TICKS(100)); // 短暂延时,确保断开事件被处理
    esp_wifi_stop();                // 再停止WiFi
    vTaskDelay(pdMS_TO_TICKS(100)); // 再次延时,确保WiFi完全停止
    esp_wifi_deinit();              // 最后反初始化
    disable_bluetooth();            // 关闭蓝牙
    net_mqtt_stop();                // 关闭mqtt
    vTaskDelay(2000);
    /* 复位系统 */
    esp_restart(); 
}

/**
 * @brief 栈溢出钩子函数
 *
 * @param xTask              任务句柄
 * @param pcTaskName         任务名
 *
 * @return 无
 */
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) 
{
    MY_LOGE("Stack overflow in task: %s", pcTaskName);

    esp_wifi_disconnect();          // 先断开连接
    vTaskDelay(pdMS_TO_TICKS(100)); // 短暂延时,确保断开事件被处理
    esp_wifi_stop();                // 再停止WiFi
    vTaskDelay(pdMS_TO_TICKS(100)); // 再次延时,确保WiFi完全停止
    esp_wifi_deinit();              // 最后反初始化
    disable_bluetooth();            // 关闭蓝牙
    net_mqtt_stop();                // 关闭mqtt
    vTaskDelay(2000);
    /* 重启 */
    esp_restart();
}

低功耗方面,通过 freertos 内核提供的自动低功耗机制,在所有任务都在挂起状态时,触发低功耗模式,使esp32进入Light-sleep 模式:CPU 暂停运行。任何唤醒事件(MAC、主机、RTC 定时器或外部中断)都会唤醒芯片。Wi-Fi 或 Bluetooth LE 可保持连接。
WTN6170语言模块会在播放完语言后自动进入休眠状态,
SC12B按键模块在一段时间内没有检测到按键并且SDA端口一直保持高电平,芯片会自动进入省电模式。
FPM383指纹模块在发送睡眠指令后会进入休眠模式。

从网盘下载后,在onenet平台创建产品->设置物模型->导入物模型->上传文件,将model.json文件上传即可。

image-20251008195053651

image-20251008195110873

image-20251008195138732

image-20251008195254267

然后将 App_onenet.h 中产品ID,设备名称,设备密钥修改为自己即可使用。

image-20251008195554534

posted @ 2025-10-08 20:01  2666666  阅读(460)  评论(0)    收藏  举报