ESP32 SDDC 连接器设备开发

更新时间:
2023-11-30
下载文档

ESP32 SDDC 连接器设备开发

本章将介绍如何在 ESP32 上使用 FreeRTOS 和 libsddc 开发一个能接入到 EdgerOS 的低成本人脸识别智能门锁。

硬件准备

ESP32 开发板

ESP32 是乐鑫科技推出的一款面向物联网应用的高性能低功耗的高性价比、高度集成的 Wi-Fi & 蓝牙 MCU。

本实验使用的是一块 ESP32-CAM 开发板,它带有一个摄像头,如下图所示:

avatar

方案设计

ESP32-CAM 开发板带有 Wi-Fi,可以使用 SDDC 协议无线连接 EdgerOS,板载的摄像头用于捕捉图像,使用一个 GPIO 连接人体感应器,使用另一个 GPIO 控制电磁锁(如 IO12)。

avatar

当人站在门前,人体感应器检测到人后,抓拍一张人的头像,然后发送给运行在 EdgerOS 上的 智能门锁 App, 智能门锁 App 通过 facenn 模块识别人脸信息和进行活体检测,如果识别出正确的人,智能门锁 App 发送开门消息给 ESP32-CAMESP32-CAM 控制电磁锁开门,超时时间到达后控制电磁锁关门。

001 001

软件准备

ESP32 开发环境搭建

请先按照 ESP32 快速入门open in new window 完成 ESP32 开发环境的搭建和开发入门。

获取 esp32_sddc

使用 git 工具从 MS-RTOS github 社区open in new window 下载 esp32_sddc,命令如下:

git clone https://github.com/ms-rtos/esp32_sddc.git

下载完毕后,进入下载目录,可以看到 sddc_template 模板工程和 sddc_smart_lock 人脸识别智能门锁工程:

avatar

配置工程

首先通过 Windows 开始菜单中的 ESP-IDF 4.2 CMDESP-IDF 4.2 PowerShell 打开命令行工具:

avatar

avatar

然后通过 cd 命令进入到 sddc_smart_lock 工程的所在目录:

avatar

在命令行输入以下两个命令,进入配置界面:

idf.py set-target esp32

idf.py menuconfig

如下图所示:

avatar

进入 Example Connection Configuration,配置需要连接到的 Spirit 1 的 Wi-Fi AP SSID 与密码,如下图所示:

avatar

最后退出配置,并等待配置完成。

编译工程

在命令行输入以下命令去编译工程:

idf.py build

编译完成后,如下图所示:

avatar

烧写镜像

在命令行输入如下命令将编译生成的镜像烧录到设备:

idf.py -p COM4 flash

烧写完成后,如下图所示:

avatar

验证功能

在命令行输入如下命令启动监视器:

idf.py -p COM4 monitor

可以看到 ESP32-CAM 开发板成功连接到 Spirit 1 的 Wi-Fi AP:

avatar

请参考《SDDC introduction》章节的“添加设备流程”去添加 ESP32-CAM 开发板。

接下来,即可使用 EdgerOS 上的 智能门锁 App 操作智能门锁。

sddc_smart_lock 代码分析

sddc_smart_lock 代码位于:esp32_sddc\sddc_smart_lock\main\sddc_esp32_smart_lock.c

app_main 函数完成硬件初始化(限于篇幅,以下代码做了裁减,仅用于说明代码逻辑):

void app_main(void)
{
    // 初始化 FLASH
    ESP_ERROR_CHECK(nvs_flash_init());
    // 初始化 NETIF
    ESP_ERROR_CHECK(esp_netif_init());
    // 创建缺省的事件循环
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 连接 Wi-Fi AP
    ESP_ERROR_CHECK(example_connect());

    // 初始化 GPIO
    ESP_ERROR_CHECK(esp_gpio_init());
    // 初始化摄像头
    ESP_ERROR_CHECK(esp_cam_init());

    // 创建消息队列(存放 SDDC 连接器指针)
    conn_mqueue_handle = xQueueCreate(4, sizeof(sddc_connector_t *));

    // 创建关门定时器
    lock_timer_handle  = xTimerCreate("lock_timer",
                                      2000 / portTICK_RATE_MS,
                                      pdFALSE,
                                      0,
                                      esp_lock_timer_callback);
    // 创建 SDDC 对象
    sddc_t *sddc = sddc_create(SDDC_CFG_PORT);

    // 创建 SDDC 协议任务
    xTaskCreate(esp_sddc_task, "sddc_task", ESP_SDDC_TASK_STACK_SIZE, sddc, ESP_SDDC_TASK_PRIO, NULL);
    // 创建按键检测任务
    xTaskCreate(esp_key_task, "key_task", ESP_KEY_TASK_STACK_SIZE, sddc, ESP_KEY_TASK_PRIO, NULL);
    // 创建 SDDC 连接器消息处理服务任务
    xTaskCreate(esp_connector_task, "connector_task1", ESP_CONNECTOR_TASK_STACK_SIZE, sddc, ESP_CONNECTOR_TASK_PRIO, NULL);
}

esp_sddc_task 函数为 SDDC 协议任务(限于篇幅,以下代码做了裁减,仅用于说明代码逻辑):

static void esp_sddc_task(void *arg)
{
    sddc_t *sddc = arg;

    // 设置事件回调函数
    sddc_set_on_message(sddc, esp_on_message);
    sddc_set_on_message_ack(sddc, esp_on_message_ack);
    sddc_set_on_message_lost(sddc, esp_on_message_lost);
    sddc_set_on_invite(sddc, esp_on_invite);
    sddc_set_on_invite_end(sddc, esp_on_invite_end);
    sddc_set_on_update(sddc, esp_on_update);
    sddc_set_on_edgeros_lost(sddc, esp_on_edgeros_lost);

    // 运行 SDDC 协议
    while (1) {
        sddc_run(sddc);
    }
}

esp_key_task 函数为按键检测任务(限于篇幅,以下代码做了裁减,仅用于说明代码逻辑),当检测到 IO12 KEY 按了一下,它将给运行在 EdgerOS 上的智能门锁 App 发送一个消息,通知智能门锁 App 可以接收图像(真实的智能门锁,应该通过人体感应器去检测人的接近):

static void esp_key_task(void *arg)
{
    sddc_t *sddc = arg;
    int i = 0;

    while (1) {
        vTaskDelay(50 / portTICK_RATE_MS);

        if (!gpio_get_level(GPIO_INPUT_IO_SMARTCOFNIG)) {
            // IO12 KEY 按下
            i++;
            if (i > (3 * 20)) {
                // IO12 KEY 按下超过 3 秒
                i = 0;
                // 启动 SmartConfig
                sddc_printf("Start SmartConfig....\n");
                example_smart_config();
            }
        } else {
            // IO12 KEY 抬起
            if (i > 0) {
                // IO12 KEY 按下一次
                cJSON *root = NULL;
                char *str;
                size_t size;
                int ret;

                // 摄像头捕捉一帧图像
                ret = camera_run();
                sddc_goto_error_if_fail(ret == ESP_OK);

                gettimeofday(&last_capture_time, NULL);
                
                // 获得图像数据大小
                size = camera_get_data_size();

                // 创建 cJSON 对象
                root = cJSON_CreateObject();
                sddc_goto_error_if_fail(root);

                // cmd 为 recv
                cJSON_AddStringToObject(root, "cmd", "recv");
                // size 为图像数据大小
                cJSON_AddNumberToObject(root, "size", size);

                sddc_printf("Send picture to EdgerOS, file size %d\n", size);

                // cJSON 对象转换成字符串
                str = cJSON_Print(root);
                sddc_goto_error_if_fail(str);

                // 发送消息给智能门锁 App
                sddc_broadcast_message(sddc, str, strlen(str), 1, SDDC_FALSE, NULL);
                cJSON_free(str);

error:
                cJSON_Delete(root);
            }
            i = 0;
        }
    }

    vTaskDelete(NULL);
}

esp_on_message 函数为 SDDC 消息处理函数:

static sddc_bool_t esp_on_message(sddc_t *sddc, const uint8_t *uid, const char *message, size_t len)
{
    // 分析消息
    cJSON *root = cJSON_Parse(message);
    cJSON *cmd;
    char *str;

    sddc_return_value_if_fail(root, SDDC_TRUE);

    // 打印消息
    str = cJSON_Print(root);
    sddc_goto_error_if_fail(str);

    sddc_printf("esp_on_message: %s\n", str);
    cJSON_free(str);

    cmd = cJSON_GetObjectItem(root, "cmd");
    if (cJSON_IsString(cmd)) {
        int ret;

        if (strcmp(cmd->valuestring, "recv") == 0) {
            // 命令为 recv,智能门锁 App 开始接收图像,智能门锁 App 接收完图像后,
            // 进行人脸识别及活体检测,如果识别出正确的人,
            // 智能门锁 App 将给智能门锁发送 unlock 消息

        } else if (strcmp(cmd->valuestring, "unlock") == 0) {
            // 命令为 unlock,智能门锁 App 通知智能门锁开门

            // 获得关门时间
            cJSON *timeout = cJSON_GetObjectItem(root, "timeout");
            uint32_t timeout_ms;

            if (timeout && cJSON_IsNumber(timeout)) {
                timeout_ms = timeout->valuedouble;
                if (timeout_ms < 2000) {
                    timeout_ms = 2000;
                }
            } else {
                timeout_ms = 5000;
            }

            // 修改关门定时器时间
            xTimerChangePeriod(lock_timer_handle, timeout_ms / portTICK_RATE_MS, 1);
            // 启动关门定时器
            xTimerStart(lock_timer_handle, 0);

            // 提示开门,真实的智能门锁,应该在这里控制电磁锁去打开门
            sddc_printf("Open the door, timeout %dms!\n", timeout_ms);
            goto done;
        } else {
            sddc_printf("Command no support!\n");
            goto error;
        }

        // 获得 SDDC 连接器信息
        cJSON *connector = cJSON_GetObjectItem(root, "connector");
        sddc_goto_error_if_fail(cJSON_IsObject(connector));

        cJSON *port = cJSON_GetObjectItem(connector, "port");
        sddc_goto_error_if_fail(cJSON_IsNumber(port));

        cJSON *token = cJSON_GetObjectItem(connector, "token");
        sddc_goto_error_if_fail(!token || cJSON_IsString(token));

        // 创建 SDDC 连接器
        sddc_connector_t *conn = sddc_connector_create(sddc, uid, port->valuedouble, token ? token->valuestring : NULL, SDDC_FLASE);
        sddc_goto_error_if_fail(conn);

        // 发消息(SDDC 连接器对象指针)到消息队列,由服务任务处理
        ret = xQueueSend(conn_mqueue_handle, &conn, 0);
        if (ret != pdTRUE) {
            sddc_connector_destroy(conn);
            sddc_goto_error_if_fail(ret == pdTRUE);
        }
    } else {
        sddc_printf("Command no specify!\n");
    }

done:
error:
    cJSON_Delete(root);

    return SDDC_TRUE;
}

esp_connector_task 函数为 SDDC 连接器消息处理服务任务函数:

static void esp_connector_task(void *arg)
{
    sddc_connector_t *conn;
    BaseType_t ret;

    while (1) {
        // 从消息队列里接收消息(SDDC 连接器对象指针)
        ret = xQueueReceive(conn_mqueue_handle, &conn, portMAX_DELAY);
        if (ret == pdTRUE) {
            // 发送图像
            esp_send_image(conn);
            // 销毁 SDDC 连接器
            sddc_connector_destroy(conn);
        }
    }

    vTaskDelete(NULL);
}

esp_send_image 函数为发送图像函数:

static void esp_send_image(sddc_connector_t *conn)
{
    void *data;
    size_t size;
    size_t totol_len = 0;
    size_t len;
    int ret;
    struct timeval cur_time;
    struct timeval diff_time;
    long diff_msec;

    // 获得当前时间
    gettimeofday(&cur_time, NULL);

    timersub(&cur_time, &last_capture_time, &diff_time);
    diff_msec = (diff_time.tv_sec * 1000) + (diff_time.tv_usec / 1000);
    if (diff_msec >= 500) {
        // 如果上一次捕捉图像的时间距离当前久于 500 ms,则重新捕捉图像
        camera_run();
        last_capture_time = cur_time;
    }

    // 获得图像的数据和大小
    data = camera_get_fb();
    size = camera_get_data_size();

    // 把图像的数据 put 到连接器
    while (totol_len < size) {
        len = min((size - totol_len), (1460 - 16));

        ret = sddc_connector_put(conn, data, len, (totol_len + len) == size);
        if (ret < 0) {
            sddc_printf("Failed to put!\n");
            break;
        }
        totol_len += len;
        data += len;

        sddc_printf("Put %d byte\n", len);
    }

    sddc_printf("Total put %d byte\n", totol_len);
}

esp_lock_timer_callback 为关门定时器到期处理函数:

static void esp_lock_timer_callback(TimerHandle_t handle)
{
    // 提示关门,真实的智能门锁,应该在这里控制电磁锁去关闭门
    sddc_printf("Close the door!\n");
}
文档内容是否对您有所帮助?
有帮助
没帮助