演示视频
PPT
一、背景介绍
传统的公司会议室预约管理系统存在诸多问题,如:
- 部分人员不预定即使用,系统形同虚设。
- 提前结束会议后,很难准确释放会议室资源,造成浪费。
- 会议存在超时后,未及时预约或被抢先预约,被后来人打扰,浪费双方时间。
- 随性预定,实则未使用,实际使用率低。
- 管理人员难以管理,大量耗费人力管理成本。
- 难以统计真实使用情况,无法为管理提供有效数据。
诸如会议室等隐私场所,不能通过单纯的安装监控进行管理,实际管理中却又想得知是否有人在使用。为解决此类问题,会议室使用小助手横空出世。
二、项目介绍
本设备既可以单独使用,借助腾讯云平台也可以对接其他会议室管理系统使用,具有以下亮点:
- 支持人体检测,判断范围内有无人员活动
- 本地运行模型,图像用完即销毁,且无法导出,不存在隐私泄露问题
- 识别数据上传腾讯云,可接入第三方设备使用
- 支持腾讯连连小程序查看会议室使用情况与配置设备
- 支持定时唤醒识别(可配置开关,间隔时间)
- 支持声音触发识别(可配置开关,声音采样时间)
- 支持外接两个传感器或控制器使用(独立配置,支持设置触发与开关模式)
- 低功耗设计,电池供电(受限于板卡设计暂不支持)
- 支持屏幕显示图像与识别情况(调试使用,实际部署没有屏幕)
三、整体框图
四、硬件部分
4.1 硬件框图
4.2 硬件组件
4.2.1 TencentOS Tiny AIoT开发套件
内置TencentOS Tiny开源物联网操作系统。
- 核心板采用的RT1062处理器属于i.MX RT 系列 MCU,是由 NXP 推出的跨界处理器,跨界是指该系列MCU的定位既非传统的微控制器、也非传统的微处理器,i.MX RT 系列 MCU 则综合了两者的优势,既具备高频率(最高主频600M)、高处理性能,也具备中断响应迅速、实时性高的特点。
- 1M RAM 16M SDRAM 64MB qspi flash 128MB spi flash。
- 板载Type-C接口CMSIS DAP仿真器。
- 板载PCIE接口,可扩展4G类物联网模组。
- 板载物联网俱乐部WAN Interface接口,可支持NB-IoT、WiFi、4G cat1、LoRa等模组。
- 板载物联网俱乐部E53 Interface接口,可扩展全系E53传感器。
- 板载标准24P DVP摄像头接口,可支持最高500万像素摄像头。
- 板载RGB显示接口,可转换HDMI输出。
- 板载高性能音频解码芯片,可做语音识别测试。
- 预留SD卡、用户按键、SPI Flash。
4.2.2 OV5640
OV5640图像传感器是一种低电压、高性能、1/4英寸、500万像素、的CMOS图像传感器。该传感器支持输出最大为500万像素的图像 (2592x1944分辨率),支持使用VGA时序输出图像数据,输出图像的数据格式支持YUV(422/420)、YCbCr422、RGB565以及JPEG格式,若直接输出JPEG格式的图像时可大大减少数据量,方便网络传输。它还可以对采集得的图像进行补偿,支持伽玛曲线、白平衡、饱和度、色度等基础处理。根据不同的分辨率配置,传感器输出图像数据的帧率从15-60帧可调,工作时功率在150mW-200mW之间。
4.2.3 LCD屏幕
- 4.3寸电容触摸屏,800×480高清分辨率
- 支持I2C接口控制电容触摸,5点触控,支持中断
- 支持24位RGB接口显示,可显示1678万种色彩,刷新速度快
4.2.4 ESP8266模组
ESP8266模块小熊派开发板套件提供的用于通过Wi-Fi传输数据的通信扩展板,该拓展板采用的是乐鑫ESP8266 Wi-Fi通信模组,支持常见的IPv4/TCP/UDP/HTTP/FTP等通信协议。
4.2.5 外部传感器
振动传感器、光敏传感器、热释电传感器......数字信号输出的均可以。
五、软件部分
5.1 代码逻辑框图
5.2 代码组件
5.2.1 TencentOS tiny物联网操作系统
TencentOS tiny是腾讯面向物联网领域开发的实时操作系统,现已捐赠给开放原子开源基金会进行孵化,具有低功耗,低资源占用,模块化,安全可靠等特点,可有效提升物联网终端产品开发效率。TencentOS tiny 提供精简的 RTOS 内核,内核组件可裁剪可配置,可快速移植到多种主流 MCU (如NXP Arm Cortex-M 全系列)及模组芯片上。而且,基于RTOS内核提供了丰富的物联网组件,内部集成主流物联网协议栈(如 CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT 等),可助力物联网终端设备及业务快速接入腾讯云物联网平台。
- 资源占用极少
TencentOS Tiny 内核具有超低资源占用的特点,RAM 0.8KB,ROM 1.8KB;在类似烟感和红外等实际场景下,TencentOS tiny 的资源占用仅为:RAM 2.69KB、ROM 12.38KB。
- 高效功耗管理框架
完整包含 MCU 和外围设备功耗管理,用户可以根据业务场景选择可参考的低功耗方案,有效降低设备耗电,延长设备寿命。
- 自动移植工具
TencentOS tiny 提供多种编译器快速移植指南和移植工具,可实现向新硬件开发板的一键移植,省时省力,有效提升开发效率。
- 最后一屏调试工具
TencentOS tiny 可以自动获取故障现场信息,并保持在端侧存储设备中,触发重启后会自动上传故障信息,可有效解决远程物联网设备故障信息获取难题,提升故障分析解决效率。
- 安全分级方案
TencentOS tiny 提供了多个等级的 IoT 安全方案。您可以根据业务场景和成本要求选择合适的安全解决方案,方便客户在安全需求和成本控制之间进行有效平衡。
5.2.2 TensorFlow Lite for Microcontrollers
TensorFlow Lite for Microcontrollers 是 TensorFlow Lite 的一个实验性移植版本,它适用于微控制器和其他一些仅有数千字节内存的设备。
适用于微控制器的 TensorFlow Lite 专门用于在微控制器和其他只有几千字节内存的设备上运行机器学习模型。核心运行时可以放入 Arm Cortex M3 上 16 KB 的存储空间中,并且可以运行许多基本模型。它不需要操作系统支持、任何标准 C/C 库或动态内存分配。
它可以直接在“裸机”上运行,不需要操作系统支持、任何标准 C/C 库和动态内存分配。核心运行时(core runtime)在 Cortex M3 上运行时仅需 16KB,加上足以用来运行语音关键字检测模型的操作,也只需 22KB 的空间。
功能和组件
- C API,其运行时(runtime)在 Cortex M3 上仅需 16KB
- 使用标准的 TensorFlow Lite FlatBuffer 架构(schema)
- 为 Arduino、Keil 和 Mbed 等较为流行的嵌入式开发平台预生成的项目文件
- 针对多个嵌入式平台优化
- 演示口语热词检测的示例代码
5.2.3 其他组件
MQTT、cJSON由TencentOS提供,用于MQTT通信实现,JSON数据打包解包。
5.3 任务代码实现
5.3.1 主函数
- 初始化外设
- 初始化任务
- 启动TencentOS
int main(void)
{
board_init();
person_detect_init();
PRINTF("Welcome to TencentOS tiny(%s)rn", TOS_VERSION);
tos_knl_init();
user_variable_init();
tos_task_create(&mqttclient_task, "mqttclient_task", mqttclient_entry, NULL, 4, mqttclient_task_stk, MQTTCLIENT_TASK_STK_SIZE, 0);
tos_task_create(&detection_task, "detection_task", detection_entry, NULL, 3, detection_task_stk, DETECTION_TASK_STK_SIZE, 0);
tos_task_create(&codec_task, "codec_task", codec_entry, NULL, 4, codec_task_stk, CODEC_TASK_STK_SIZE, 0);
tos_task_create(&default_task, "default_task", default_entry, NULL, 4, default_task_stk, DEFAULT_TASK_STK_SIZE, 0);
tos_knl_start();
while (1);
}
5.3.2 默认任务
- 获取两个外接传感器电平状态
- 根据当前配置选项以及数据状态确定是否触发人体识别
- 休眠(当前暂未实现)
void default_entry(void *arg)
{
uint8_t sensor1 = 0,sensor1_past = 0;
uint8_t sensor2 = 0,sensor2_past = 0;
uint8_t interval_time = 0;
uint8_t trigger_flag = 0;
k_event_flag_t default_flag_match = 0;
while (1)
{
//获取外部传递信息,不等待并清空标志
default_flag_match = 0;
tos_event_pend(&event_default, 0xFFFFFFFF, &default_flag_match, TOS_TIME_NOWAIT, TOS_OPT_EVENT_PEND_ANY);
sensor1_past = sensor1;
sensor2_past = sensor2;
if(GPIO_PinRead(BOARD_INITPINS_E53_GPIO1_GPIO, BOARD_INITPINS_E53_GPIO1_GPIO_PIN) != 0) sensor1 = 1;
else sensor1 = 0;
if(GPIO_PinRead(BOARD_INITPINS_E53_GPIO3_GPIO, BOARD_INITPINS_E53_GPIO3_GPIO_PIN) != 0) sensor2 = 1;
else sensor2 = 0;
if(((user_configure.sensor1_mode != MODE_SWITCH) || (sensor1 == 0)) &&
((user_configure.sensor2_mode != MODE_SWITCH) || (sensor2 == 0)))
{
//传感器处于触发模式,且发生了1-0电平跳变
if(((user_configure.sensor1_mode == MODE_TRIGGER) && (sensor1 == 0) && (sensor1_past == 1))||
((user_configure.sensor2_mode == MODE_TRIGGER) && (sensor2 == 0) && (sensor2_past == 1)))
{
trigger_flag = 1;
}
//定时触发识别
if(user_configure.interval_mode == MODE_ON)
{
interval_time ;
if(interval_time >= (user_configure.interval_time * 1000 / DEFAULT_DELAY_TIME_MS))
{
interval_time = 0;
trigger_flag = 1;
}
}
//声音触发识别
if(user_configure.sound_judge_mode == MODE_ON)
{
uint8_t room_status = user_get_room_status();
uint8_t sound_state = user_get_sound_state();
if((default_flag_match & EVENT_DEFAULT_SOUND) &&
(((room_status == 0) && (sound_state == 1)) || ((room_status == 1) && (sound_state == 0))))
{
trigger_flag = 1;
}
}
}
if(trigger_flag == 1)
{
trigger_flag = 0;
tos_event_post(&event_detection, EVENT_PERSON_DETECTION_MODEL); //启动模型
}
tos_task_delay(DEFAULT_DELAY_TIME_MS);
}
}
5.3.3 识别任务
- 进行摄像头图像采集
- 运行人体识别模型
- 将识别完成信号传递给mqtt数据上传任务
- 显示摄像头图像,与人体识别情况,调试用
void detection_entry(void *arg)
{
uint32_t cameraReceivedFrameAddr;
void *lcdFrameAddr;
uint8_t person_count = 0;
k_event_flag_t detection_match_flag;
tos_completion_create(&completion_display);
APP_InitDisplay();
CAMERA_RECEIVER_Start(&cameraReceiver);
while (1)
{
//外部传递标志,不等待(测试用)
detection_match_flag = 0;
tos_event_pend(&event_detection, EVENT_PERSON_DETECTION_MODEL, &detection_match_flag, TOS_TIME_NOWAIT, TOS_OPT_EVENT_PEND_ANY | TOS_OPT_EVENT_PEND_CLR);
if(detection_match_flag & EVENT_PERSON_DETECTION_MODEL) //运行模型
{
for(uint8_t i=0;i<10;i )
{
uint8_t person_flag;
//获取摄像头缓冲区
while (kStatus_Success != CAMERA_RECEIVER_GetFullBuffer(&cameraReceiver, &cameraReceivedFrameAddr))
{
tos_task_delay(10);
}
//转换图像为模型可处理的大小
input_convert((uint16_t*)cameraReceivedFrameAddr,model_buffer);
person_flag = person_detect(model_buffer); //运行模型
person_count = person_flag;
tos_completion_pend(&completion_display); //等待上一帧显示完成
lcdFrameAddr = s_lcdBuffer[s_lcdActiveFbIdx ^ 1]; //切换显示缓冲区
Camera_PXP_LCD(cameraReceivedFrameAddr, lcdFrameAddr); //将摄像头缓冲区数据搬移到显示缓冲区上
CAMERA_RECEIVER_SubmitEmptyBuffer(&cameraReceiver, (uint32_t)cameraReceivedFrameAddr); //释放摄像头缓冲区
//显示识别框
draw_identify_rectangular((uint16_t *)lcdFrameAddr, (person_flag == 0)?0xffff:0xf800);
//显示新的一帧
tos_completion_reset(&completion_display);
g_dc.ops->setFrameBuffer(&g_dc, 0, lcdFrameAddr);
}
if(person_count >= 1) room_status = 1;
else room_status = 0;
tos_event_post(&event_mqttclient, EVENT_MQTT_TX_ROOM_STATUS);
person_count = 0;
}
else //不运行模型,正常显示
{
/* 低功耗:挂起任务 */
//获取摄像头缓冲区
while (kStatus_Success != CAMERA_RECEIVER_GetFullBuffer(&cameraReceiver, &cameraReceivedFrameAddr))
{
tos_task_delay(10);
}
tos_completion_pend(&completion_display); //等待上一帧显示完成
lcdFrameAddr = s_lcdBuffer[s_lcdActiveFbIdx ^ 1]; //切换显示缓冲区
Camera_PXP_LCD(cameraReceivedFrameAddr, lcdFrameAddr); //将摄像头缓冲区数据搬移到显示缓冲区上
CAMERA_RECEIVER_SubmitEmptyBuffer(&cameraReceiver, (uint32_t)cameraReceivedFrameAddr); //释放摄像头缓冲区
//显示新的一帧
tos_completion_reset(&completion_display);
g_dc.ops->setFrameBuffer(&g_dc, 0, lcdFrameAddr);
tos_task_delay(100);
}
}
}
5.3.4 mqtt数据上传任务
- 与esp8266模块进行AT通信,进行wifi连接
- 登录mqtt服务器,并订阅mqtt发布
- 根据识别任务的信号发布会议室状态消息到腾讯云
void mqttclient_entry(void)
{
int error;
mqtt_client_t *client = NULL;
mqtt_message_t msg;
k_event_flag_t match_flag;
char host_ip[20];
k_event_flag_t mqtt_match_flag;
memset(&msg, 0, sizeof(msg));
esp8266_sal_init(HAL_UART_PORT_2);
esp8266_join_ap(WIFI_SSID, WIFI_PASSWORD);
mqtt_log_init();
client = mqtt_lease();
tos_event_create(&report_result_event, (k_event_flag_t)0u);
tos_sal_module_parse_domain(MQTT_PRODUCT_ID ".iotcloud.tencentdevices.com",host_ip,sizeof(host_ip));
mqtt_set_port(client, "1883");
mqtt_set_host(client, host_ip);
mqtt_set_client_id(client, MQTT_CLIENT_ID);
mqtt_set_user_name(client, MQTT_USR_NAME);
mqtt_set_password(client, MQTT_PASSWORD);
mqtt_set_clean_session(client, 1);
error = mqtt_connect(client);
MQTT_LOG_D("mqtt connect error is %#0x", error);
error = mqtt_subscribe(client, "$thing/down/property/" MQTT_SUBSCRIBE_TOPIC, QOS0, tos_topic_handler);
MQTT_LOG_D("mqtt subscribe error is %#0x", error);
while (1)
{
//死等外部传递标志
mqtt_match_flag = 0;
tos_event_pend(&event_mqttclient, 0xFFFFFFFF, &mqtt_match_flag, TOS_TIME_FOREVER, TOS_OPT_EVENT_PEND_ANY | TOS_OPT_EVENT_PEND_CLR);
if(mqtt_match_flag & EVENT_MQTT_TX_ROOM_STATUS)
{
uint8_t room_state = user_get_room_status();
memset(&msg, 0, sizeof(msg));
snprintf(report_buf, sizeof(report_buf), REPORT_DATA_TEMPLATE, room_state);
msg.qos = QOS0;
msg.payload = (void *) report_buf;
error = mqtt_publish(client, "$thing/up/property/" MQTT_PUBLISH_TOPIC, &msg);
MQTT_LOG_D("mqtt publish error is %#0x", error);
tos_event_pend(&report_result_event,
report_success|report_fail,
&match_flag,
TOS_TIME_FOREVER,
TOS_OPT_EVENT_PEND_ANY | TOS_OPT_EVENT_PEND_CLR);
if (match_flag == report_success)
{
printf("report to Tencent IoT Explorer successrn");
}
else if (match_flag == report_fail)
{
printf("report to Tencent IoT Explorer failrn");
}
}
tos_task_delay(2000);
}
}
5.3.5 mqtt数据接受任务
- 接受并解析下行的MQTT数据
- 有效解析结果配置到设备设置
static void tos_topic_handler(void* client, message_data_t* msg)
{
(void) client;
cJSON* cjson_root = NULL;
cJSON* cjson_status = NULL;
cJSON* cjson_params = NULL;
char* status = NULL;
k_event_flag_t event_flag = report_fail;
MQTT_LOG_I("-----------------------------------------------------------------------------------");
MQTT_LOG_I("%s:%d %s()...ntopic: %s, qos: %d. nmessage:nt%sn", __FILE__, __LINE__, __FUNCTION__,
msg->topic_name, msg->message->qos, (char*)msg->message->payload);
MQTT_LOG_I("-----------------------------------------------------------------------------------n");
cjson_root = cJSON_Parse((char*)msg->message->payload);
if (cjson_root == NULL) {
printf("report reply message parser failrn");
event_flag = report_fail;
goto exit;
}
cjson_params = cJSON_GetObjectItem(cjson_root, "params");
if (cjson_params != NULL)
{
cJSON* cjson_interval_mode = cJSON_GetObjectItem(cjson_params, "interval_mode");
if(cjson_interval_mode != NULL) user_configure.interval_mode = cjson_interval_mode->valueint;
cJSON* cjson_sound_judge_mode = cJSON_GetObjectItem(cjson_params, "sound_judge_mode");
if(cjson_sound_judge_mode != NULL) user_configure.sound_judge_mode = cjson_sound_judge_mode->valueint;
cJSON* cjson_sensor1_mode = cJSON_GetObjectItem(cjson_params, "sensor1_mode");
if(cjson_sensor1_mode != NULL) user_configure.sensor1_mode = cjson_sensor1_mode->valueint;
cJSON* cjson_sensor2_mode = cJSON_GetObjectItem(cjson_params, "sensor2_mode");
if(cjson_sensor2_mode != NULL) user_configure.sensor2_mode = cjson_sensor2_mode->valueint;
cJSON* cjson_interval_time = cJSON_GetObjectItem(cjson_params, "interval_time");
if(cjson_interval_time != NULL) user_configure.interval_time = cjson_interval_time->valueint;
cJSON* cjson_sound_judge_time = cJSON_GetObjectItem(cjson_params, "sound_judge_time");
if(cjson_sound_judge_time != NULL) user_configure.sound_judge_time = cjson_sound_judge_time->valueint;
}
/* 提取status状态 */
cjson_status = cJSON_GetObjectItem(cjson_root, "status");
status = cJSON_GetStringValue(cjson_status);
if (cjson_status == NULL || status == NULL) {
printf("report reply status parser failrn");
event_flag = report_fail;
goto exit;
}
/* 判断status状态 */
if (strstr(status,"success")) {
event_flag = report_success;
}else {
event_flag = report_fail;
}
exit:
cJSON_Delete(cjson_root);
cjson_root = NULL;
status = NULL;
tos_event_post(&report_result_event, event_flag);
return;
}
5.3.6 声音采集任务
- 进行声音的采集
- 确定当前外部声音状态,并将完成信号传递给默认任务
void codec_entry(void *arg)
{
sai_transfer_t xfer;
uint8_t sound_flag = 0;
uint8_t count = 0;
tos_completion_create(&completion_codec_rx);
tos_task_delay(DEMO_CODEC_INIT_DELAY_MS);
while(1)
{
if(user_configure.sound_judge_mode == MODE_ON)
{
//启动音频采集
xfer.data = Buffer;
xfer.dataSize = BUFFER_SIZE;
if (kStatus_Success == SAI_TransferReceiveEDMA(DEMO_SAI, &rxHandle, &xfer))
{
tos_completion_reset(&completion_codec_rx);
//等音频采集完成
tos_completion_pend(&completion_codec_rx);
//判断是否较大声音
if(sound_flag == 0)
{
for(uint16_t i=0;i<BUFFER_SIZE;i =2)
{
if( Buffer[i 1] > 0)
{
sound_flag = 1;
break;
}
}
}
count ;
}
if(count >= (user_configure.sound_judge_time * 5))
{
//更新当前环境声音状态
sound_state = sound_flag;
tos_event_post(&event_default, EVENT_DEFAULT_SOUND);
count = 0;
sound_flag = 0;
PRINTF("flag:%drn",sound_flag);
}
}
else
{
tos_task_delay(300);
}
}
}
六、服务器部分
6.1 云端
云端采用腾讯云IoT explorer平台,设备通过esp8266模块连接wifi入网。
6.2 客户端
客户端腾讯连连微信小程序,开发使用了标准面板,可以说超级超级简单了,而且效果还不错。
七、后记
在开发中感觉TencentOS tiny特点,就是简单,无论是API的设计,还是部署的复杂度上都大大降低,源码的可读性也比较高,配套资料也很齐全,新手也能快速凭借文档入门。
腾讯云IoT explorer平台可以说是做到一站式开发,让我这个没有接触过云的新手也能快速上手,而且效果还不错,进阶开发也提供了相对应的接口,挺好的,之后的量产阶段并没有体验,不过看起来也是十分完善。
这个项目定位就是接入现有的会议室系统,让整个系统能够更好的形成闭环,因此在界面设计的时候就比较草率,不过好像也能考虑单独使用啊。
本来也想对手上的项目进一步优化,但是无奈年后实在没时间,只能暂时到此了,后期自行设计板卡时,会增加电子开关关断外部外设,追加蓝牙通信方式,增加深度休眠功能,充分发挥MCU低功耗特点,让设备也能部署于供电不方便的地方。