【本文正在参与"2021爱智先行者-征文大赛"活动】https://mp.weixin.qq.com/s/I2s99dZpbP0QpMiKFneYJA
上次说了在 智能照明开关 说到憋大招,肯定不会只有这个东西,今天我们就继续吧! 智能照明开关和光照传感器 一个小场景的设备建设已经简单完成,但是为什么这么有趣的想法只做这么简单的事情呢?当然不行。你得做点什么!做大事!
以前的场景只有检查是否关灯和远程关灯的功能。无法感知家里是否有其他人在家,还是不够智能。相机太复杂太贵了,我要找一个好的传感器来感知人的存在。
硬件选择
第一,是翼辉的 边缘计算机 Spirit 1 边缘计算机,这个环境是基于这个东西的。
还有祖传的安信可 ESP32S 。
传感器存在于人体内
人体传感器的存在困扰了我一段时间,所以我准备谈谈这个传感器。我在市场上尝试了很多人体传感器,其中大多数都很常见CW多普勒体制 类似bis0001芯片 放大信号进行检测,或者青蛙眼,只能检测到运动,而且是大规模的运动(具体点:我甩手2米没用,要摇身体。淘宝买贼贵的那种 );要么误报率高,判断难度大。我不能简单地满足我的需求。当我玩游戏时,传感器无法检测到我。关灯不是很尴尬吗?
但最后,我找到了一件好事:阶跃时进 HS2BC3A 这是毫米波传感器。这个东西很有趣。采用类似雷达的原理向检测区发射 24GHz 的 FMCW 通过传感器系统中的毫米波,接收区域内所有运动、微动和极弱微动的目标反射的无线电波 MMIC 电路转换为电信号,信号处理由数字信号算法处理单元(呼吸信号)处理 提取算法),解决目标信息(存在、微动、运动、静止等状态)。
其实这个东西也是青蛙眼,也是通过检测运动来判断的,但是他的精度可以检测到人呼吸引起的运动。众所周知,人不呼吸就会死,所以这个问题就不复存在了,不仅仅是呼吸,还有人的很多大大小小的动作(呼吸都能捕捉到,更别说动手指了),很多参数可以通过串口配置。我实际上用得很好。
HS2BC3A 可以说老少皆宜, 有简单的IO数字输出满足基本使用需求,串口可以进行复杂的配置和详细的数据输出。串口的详细输出甚至可以跟踪最多8个目标,报告数量距离和信噪比(与数据可靠性有关)。还可以修改模块探测距离、灵敏度、输出模式、输出延迟时间(确定目标延迟和目标丢失延迟)、积极获取数据等功能。需要注意的是,串口的配置不会受到影响IO关闭串口的主动报告不会影响口输出IO口输出。
因为这个模块太敏感了(HS2BC3A 探测范围是100°×100°,近距离检测的范围会更大)。在测试和调试过程中,建议将灵敏度和检测距离调整到最低,然后将输出延迟时间设置为最短,将模块放置在头顶以上,以便于调试代码,否则将始终检测到调试人员的存在。此外,默认情况持续15次S如果我不能检测到信号,我就会判断没有人,这导致我在测试时无法切换到无人状态。有一段时间,我怀疑设备是否坏了 。
在实际使用中,官方默认配置非常有用(我安装在天花板上,大约2-3米高),一个传感器可以完全覆盖主卧室和客厅。手册上写着最远的7米,最多可以设置到9米,但远程探测角度会变窄。
但官方手册说,如果环境中有干扰源,可以通过降低灵敏度来避免,或者通过详细的串口获取详细的数据:点云目标输出 $JYRPO ,这个信息包含了目标序号,目标距离,目标信噪比等信息其中信噪比和可靠性有关,信噪比越大,代表当前检测到的目标可靠性越高,方便手动对数据进行筛选。
代码解析
获取代码
为了方便解释逻辑,我会打乱代码的顺序,可能会被切割。想直接拿代码跑的朋友可以直接去。 桌面秘密宝库的灵感 或直接获取代码 clone:
https://gitee.com/inspiration-desktop/DEV-lib-arduino.git
这一次,由于篇幅有限,我不会重复代码获取,代码在 human_body_induction 如果文件夹中有不能使用的朋友,可以参考上一篇文件:2021爱智先锋-智能灯光开关-CSDN社区
设备控制命令:
通过 Spirit 1 应用程序或调试工具 嗅探器 发送给传感器设备的命令:
{ "method": "get", "obj": ["rtgy"] }
设备和协议初始化流程:
基于官方 demo 主要是设备初始化、管脚配置和协议初始化。
/* * 初始化传感器 */ void sensor_init() { // 初始化 GOIP 口为输入模式,接收传感器发送的信息 pinMode(sensor_in,INPUT); // 创建传感器任务,周期性传感器的数据并发送给 EdgerOS xTaskCreate(periodic_sensor_task, "periodic_sensor_task", ESP_TASK_STACK_SIZE, NULL, ESP_TASK_PRIO, NULL); } void setup() { byte mac[6]; Serial.begin(115200); Serial.setDebugOutput(true); Serial.println(); // 初始化传感器 sensor_init(); // 清除按键状态机的状态 button.reset(); // 按键扫描线程,长按 IO0 按键,松开后ESP32 将会进入 SmartConfig 模式 sddc_printf("进入长按键 Smartconfig...\n"); button.attachLongPressStop(esp_io0_key_task); xTaskCreate(esp_tick_task, "button_tick", ESP_TASK_STACK_SIZE, NULL, ESP_TASK_PRIO, NULL); // 启动 WiFi 并连接网络 WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // 获取并打印 IP 地址 Serial.println(""); Serial.println("WiFi connected"); Serial.print("'ip :"); Serial.print(WiFi.localIP()); Serial.println("' to connect"); // sddc协议初始化 sddc_lib_main(&sys_cfg); // 获取并打印网卡 mac 地址 WiFi.macAddress(mac); sddc_printf("MAC addr: x:x:x:x:x:x\n", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]); // 使用网卡 mac 地址设置设备唯一标识 UID sddc_set_uid(G_sddc, mac); } void loop() { // 运行 SDDC 协议循环 while (1) { sddc_printf("SDDC running...\n"); sddc_run(G_sddc); sddc_printf("SDDC quit!\n"); } // 销毁 SDDC 协议 sddc_destroy(G_sddc); }
配置设备信息
这部分代码可以配置 WiFi 名字和 WiFi 密码,引脚要用,设备要配置 Spirit 1 上述信息:
#define SDDC_CFG_PORT 680U // SDDC 协议中使用的端口号 #define PIN_INPUT 0 // 选择 IO0 进行控制 #define ESP_TASK_STACK_SIZE 4096 #define ESP_TASK_PRIO 25 static const int sensor_in = 34; // 数据输入引脚 static const char* ssid = "EOS-Tenda"; // WiFi 名 static const char* password = "1234567890"; // WiFi 密码 static int rtgy_state = 1; static int xTicksToDlay = 1000; // 周期延时时间
OneButton button(PIN_INPUT, true);
/*
* 系统对象状态获取注册
*/
DEV_STATE_GET dev_state_get_reg[] = {
{"rtgy", DEV_IO_TYPE, get_sensor_state},
};
/*
* 当前设备的信息定义
*/
DEV_INFO dev_info = {
.name = "人体感应模块",
.type = "device.rtgy",
.excl = SDDC_FALSE,
.desc = "ESP-32S",
.model = "IDRTGY01B",
.vendor = "inspiration-desktop",
};
/*
* 系统注册对象汇聚
*/
SDDC_CONFIG_INFO sys_cfg = {
.token = "1234567890", // 设备密码
.devinfo = &dev_info,
.io_dev_reg = io_dev,
.io_dev_reg_num = ARRAY_SIZE(io_dev),
.num_dev_reg = num_dev,
.num_dev_reg_num = ARRAY_SIZE(num_dev),
.state_get_reg = dev_state_get_reg,
.state_get_reg_num = ARRAY_SIZE(dev_state_get_reg),
.dis_dev_reg = dis_dev,
.dis_dev_num = ARRAY_SIZE(dis_dev),
};
回调函数注册
这是收到命令后回调函数注册的位置,在这里注册的函数才能被 SDK 正确的调用,执行正确的动作。
具体 SDK 的解析可以参考 同人逼死官方系列!基于sddc 协议的SDK框架 sddc_sdk_lib 解析 和 同人逼死官方系列!从 DDC 嗅探器到 sddc_sdk_lib 的数据解析
/*
* 数字量设备对象函数与处理方法注册
*/
NUM_DEV_REGINFO num_dev[] = {
// {"set_num_demo", demo}, // 字符串为输入命令,demo为命令处理函数
};
/*
* 显示设备对象函数与处理方法注册
*/
DIS_DEV_REGINFO dis_dev[] = {
// {"set_dis_demo", demo}, // 字符串为输入命令,demo为命令处理函数
};
/*
* IO设备对象设置函数与处理方法注册
*/
IO_DEV_REGINFO io_dev[] = {
// {"set_io_demo", demo}, // 字符串为输入命令,demo为命令处理函数
{"SW_ctrl", SW_ctrl},
};
/*
* 系统对象状态获取注册
*/
DEV_STATE_GET dev_state_get_reg[] = {
// {"demo", DEV_NUM_TYPE, num_get_demo}, // demo为输入命令,字符串为命令处理函数
// {"demo", DEV_IO_TYPE, io_get_demo},
// {"demo", DEV_DISPLAY_TYPE, dis_get_demo},
{"rtgy", DEV_IO_TYPE, get_sensor_state},
};
数据获取与上报流程
这里是我们自己编写的处理流程 ,可以根据你的需求自己更改,收到 set 或者 get 后根据前面的注册的函数,进入对应的处理函数。
/* * 周期上报函数 */ static void periodic_sensor_task(void *arg) { int newval = 0; int oldval = 0; int i = 0; // 监控
锁开启和关闭状态 while(1) { newval = digitalRead(sensor_in); if (newval == 0) { i++; } else { i = 0; rtgy_state = 1; } if( i > 15) { if (rtgy_state != 0){ rtgy_state = 0; report_sensor_state(); } i = 0; } // 任务创建之后,设定延时周期 delay(xTicksToDelay); } } /* * 主动数据上报函数 */ static void report_sensor_state() { int sensorValue = 0; cJSON *value; cJSON *root; char *msg; value = cJSON_CreateArray(); root = cJSON_CreateObject(); sddc_return_if_fail(value); sddc_return_if_fail(root); sddc_return_if_fail(value); // 获取传感器数据 cJSON_AddItemToArray(value, cJSON_CreateString("rtgy")); // 这里的字符串要和系统对象状态获取注册结构体里的对应 cJSON_AddItemToObject(root, "obj", value); // 发送数据给 EdgerOS msg = cJSON_Print(root); printf("触发上报: %s\n",msg); object_report(root); cJSON_Delete(value); cJSON_free(msg); } /* * 单次获取数据 */ sddc_bool_t get_sensor_state(char *objvalue, int value_len) { if(rtgy_state) { strncpy(objvalue, "ON", value_len); }else { strncpy(objvalue, "OFF", value_len); } return SDDC_TRUE; }
总结
这只是最简单的通过读的应用,本来考虑使用串口进行配置与获取详细数据的,但是在具体实现的时候遇到一点BUG,就先用IO凑合一下,之后有时间再把复杂功能完善。