ESP32 SDDC 连接器设备开发
本章将介绍如何在 ESP32 上使用 FreeRTOS 和 libsddc 开发一个能接入到 EdgerOS 的低成本人脸识别智能门锁。
硬件准备
ESP32 开发板
ESP32 是乐鑫科技推出的一款面向物联网应用的高性能低功耗的高性价比、高度集成的 Wi-Fi & 蓝牙 MCU。
本实验使用的是一块 ESP32-CAM
开发板,它带有一个摄像头,如下图所示:
方案设计
ESP32-CAM
开发板带有 Wi-Fi,可以使用 SDDC 协议无线连接 EdgerOS,板载的摄像头用于捕捉图像,使用一个 GPIO 连接人体感应器,使用另一个 GPIO 控制电磁锁(如 IO12)。
当人站在门前,人体感应器检测到人后,抓拍一张人的头像,然后发送给运行在 EdgerOS 上的 智能门锁 App, 智能门锁 App 通过 facenn
模块识别人脸信息和进行活体检测,如果识别出正确的人,智能门锁 App 发送开门消息给 ESP32-CAM
,ESP32-CAM
控制电磁锁开门,超时时间到达后控制电磁锁关门。
软件准备
ESP32 开发环境搭建
请先按照 ESP32 快速入门 完成 ESP32 开发环境的搭建和开发入门。
获取 esp32_sddc
使用 git 工具从 MS-RTOS github 社区 下载 esp32_sddc
,命令如下:
git clone https://github.com/ms-rtos/esp32_sddc.git
下载完毕后,进入下载目录,可以看到 sddc_template
模板工程和 sddc_smart_lock
人脸识别智能门锁工程:
配置工程
首先通过 Windows 开始菜单中的 ESP-IDF 4.2 CMD
或 ESP-IDF 4.2 PowerShell
打开命令行工具:
然后通过 cd
命令进入到 sddc_smart_lock
工程的所在目录:
在命令行输入以下两个命令,进入配置界面:
idf.py set-target esp32
idf.py menuconfig
如下图所示:
进入 Example Connection Configuration
,配置需要连接到的 Spirit 1 的 Wi-Fi AP SSID 与密码,如下图所示:
最后退出配置,并等待配置完成。
编译工程
在命令行输入以下命令去编译工程:
idf.py build
编译完成后,如下图所示:
烧写镜像
在命令行输入如下命令将编译生成的镜像烧录到设备:
idf.py -p COM4 flash
烧写完成后,如下图所示:
验证功能
在命令行输入如下命令启动监视器:
idf.py -p COM4 monitor
可以看到 ESP32-CAM
开发板成功连接到 Spirit 1 的 Wi-Fi AP:
请参考《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");
}