首页 / 资讯中心 / 文章详情

9美元单片机上手:从零搭一套“不装摄像头“的房间存在检测

9美元单片机上手:从零搭一套“不装摄像头“的房间存在检测 ★ FEATURED ARTICLE
9美元单片机上手从零搭一套不装摄像头的房间存在检测【免费下载链接】RuViewπ RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.项目地址: https://gitcode.com/GitHub_Trending/wi/RuView过去两年WiFi 感知从实验室论文走进了开源仓库。RuView 这类项目最吸引人的一句话是一块约 9 美元的 ESP32-S3就能把房间里的无线电反射变成有人/无人、在动/静止的判断不需要摄像头、不需要穿戴设备、数据不出局域网。但热度之外真正动手的人会遇到三个具体问题这块板子到底买到什么规格固件烧完为什么 CSI 速率是 0阈值到底该填多少才不会半夜误报。这篇文章不谈愿景只沿着仓库里真实存在的代码路径把采集—清洗—特征—阈值—面板告警这条最小链路走一遍并明确标出哪些是工程实现、哪些只是启发式规则。开头先说结论这套方案能可靠交付的是存在与运动不是姿态与身份。仓库自己在固件文档里写得很直白——设备端不跑训练好的神经网络n_persons是基于子载波分组的槽位容量启发式不是学习出来的计数器。把它当成一个不拍脸的 PIR 升级版预期就是对的把它当成隔墙雷达做跌倒医疗判断预期就会崩。一、先看清9 美元买到的是什么仓库 README 的硬件对比表把成本与能力拆得很清楚方案硬件成本完整 CSI能力边界ESP32 单节点ESP32-S3约 9 美元是存在、运动、呼吸/心率实验性、跌倒标志ESP32 网状网3–6× ESP32-S3 路由器约 54 美元是同上几何覆盖更好可做多链路融合ESP32-C6 研究节点ESP32-C6-DevKit约 6–10 美元是Wi-Fi 6研究目标非 S3 的等价替代研究级网卡Intel 5300 / Atheros AR9580约 50–100 美元是3×3 MIMO完整 MIMO CSI任意笔记本Windows/macOS/Linux0否只有 RSSI粗略存在与运动关键差别在 CSIChannel State Information与 RSSI。RSSI 是一个把整个信道压成一个数字的标量人体走动引起的多径变化很容易被其他因素淹没CSI 保留每个子载波的幅度与相位人的呼吸、走动会在相位序列上留下可提取的周期性扰动。这也是为什么零成本笔记本方案只能做粗粒度判断。固件文档对生产节点的要求是明确的ESP32-S3QFN56双核 Xtensa LX7 240 MHz、8 MB Flash、8 MB PSRAM、CP210x USB 桥。推荐板型是 ESP32-S3-DevKitC-1 与 XIAO ESP32-S3。仓库还专门写了一条散热警告硬币大小的 ESP32-S3-Zero / SuperMini 类克隆板在WIFI_PS_NONE连续 CSI 采集必须关闭 modem sleep Core 1 全量 DSP 板载 LED 驱动的持续负载下会明显发热已有现场报告出现供电稳压器损伤。做长期部署别为了省两美元选最小板。二、烧录与上线两个会让人卡一整晚的坑固件构建官方只认一条路——ESP-IDF v5.4 容器因为 ESP-IDF 在 Windows 的 Git Bash/MSYS2 下会被MSYSTEM检测逻辑绕开MSYS_NO_PATHCONV1 docker run --rm \ -v $(pwd)/firmware/esp32-csi-node:/project -w /project \ espressif/idf:v5.4 bash -c \ rm -rf build sdkconfig idf.py set-target esp32s3 idf.py build坑一无屏板必须换 overlay。默认构建会把显示支持编进去运行时面板探测在无屏板上误判为有屏从而关闭 MGMTDATA CSI 升级路径结果就是 CSI 速率塌到 0 pps串口看起来一切正常但收不到任何帧。用 DevKitC-1 这类无屏板时要按firmware/esp32-csi-node/sdkconfig.defaults.devkitc里给出的命令叠加该配置重新构建。坑二网关 ping 通不等于有 CSI。有些 AP 用 802.11b 的 DSSS/CCK 速率回 ICMP这类帧根本不携带 CSI。固件文档给出的诊断手段是从有线主机打一串有界 UDP 流量去制造 CSI 机会python scripts/csi-udp-poke.py 192.168.1.100 --hz 50 --seconds 60节点侧不需要跑任何 UDP 服务脚本只负责发包速率上限 50 Hz、到期自动停。跑之前和跑之后分别比对实际收到的帧数才能判断是链路问题还是 AP 发射速率选择问题。烧录偏移必须与分区表一致bootloader0x0、partition-table0x8000、otadata0xf000、app(ota_0)0x20000。完整发行包不含 NVS所以四段式写入不会清掉已配好的 WiFi 与节点参数。配置走 NVS不用重新编译。provision.py的语义值得注意它会把本次命令行参数与上一次的状态合并写入而不是整体覆盖早期版本会清掉没传的键让用户反复踩坑。状态按板子的 MAC 归档所以换端口不会继承别人的 node_id要彻底重置用--reset。密码建议走文件避免出现在ps和 shell 历史里printf %s\n YourPass ~/.wifi-pass chmod 600 ~/.wifi-pass python firmware/esp32-csi-node/provision.py --port /dev/ttyUSB0 \ --ssid YourSSID --password-file ~/.wifi-pass \ --target-ip 192.168.1.20 --node-id 1 --edge-tier 2多节点部署还要配 TDM 时隙避免几个节点同时发射互相打架--tdm-slot 0 --tdm-total 3、--tdm-slot 1 --tdm-total 3依次类推。三、板子内部到底做了什么从 I/Q 到存在分数固件采用分层流水线edge_tier2才打开存在与体征。Core 0 只负责把 CSI 帧塞进一个 16 槽的无锁 SPSC 环形缓冲Core 1 上的 DSP 任务消费这些帧依次做从 I/Q 对用atan2f(q, i)取相位相位解卷绕消除 2π 跳变每个子载波用 Welford 增量统计算方差按方差选出 Top-K 子载波EDGE_TOP_K为 8双二阶biquad巴特沃斯带通呼吸 0.1–0.5 Hz心率 0.8–2.0 Hz过零/自相关估计 BPM存在检测取最近 20 帧相位的方差作为presence_score。第 7 步是整个存在检测的全部内核。它没有分类器、没有网络权重就是一个滑动窗口方差/* firmware/esp32-csi-node/main/edge_processing.cStep 8/9 节选 */ float mean sum / (float)window; /* window 20 */ s_motion_energy (sum2 / (float)window) - (mean * mean); if (s_motion_energy 0.0f) s_motion_energy 0.0f; s_presence_score s_motion_energy;这一点必须先接受后面的调参才有意义分数高只说明相位在动不区分是人、是风扇还是隔壁 AP 在调整发射功率。顺带一个容易被忽略的实现细节--subk-count 32传到固件后会被update_top_k()截断到EDGE_TOP_K8。参数能写进去但运行时只跟踪 8 个最优子载波。这类配置项存在但被内部上限夹住的地方是排查改了没效果时首先要查的。上行链路是两种 UDP 包靠包头 magic 区分Magic名称速率大小内容0xC5110001CSI 原始帧ADR-018约 20 Hz变长每子载波每天线的 I/Q0xC5110002体征包1 Hz32 字节presence 标志、呼吸、心率、人数、presence_score0xC5110003特征向量1 Hz48 字节8 维归一化特征供向量库/训练用ADR-018 头是 20 字节定长字段顺序为 magic、node_id、天线数、子载波数、中心频率、序列号、RSSI、噪声底、PPDU 类型、标志位之后才是 I/Q 字节流。仓库里scripts/record-csi-udp.py用的就是这套布局CSI_MAGIC 0xC5110001 CSI_HDR_FMT IBBHIIbbBB # 20 字节 # flags: bit040MHz, bit2STBC, bit4sync valid, bit5首字被清零采集端不需要写任何 Rustpython scripts/record-csi-udp.py --port 5005 --duration 300 --output data/recordings输出是 JSONL每行含ts_ns、node_id、seq、rssi、freq_mhz、channel、n_subcarriers、amplitudes与原始iq_hex。amplitudes已经是sqrt(I²Q²)做离线分析时可以直接用。如果要接官方服务端注意一个默认值陷阱UDP 监听默认只绑回环局域网节点必须同时给绑定地址和来源白名单。cd v2 cargo run -p wifi-densepose-sensing-server -- --http-port 3000 --source auto \ --udp-bind 0.0.0.0 --udp-allow 192.168.1.0/24四、阈值怎么定三分之一定律之外的真实参数存在检测的阈值有两条路径一条在板子上一条在主机侧不要混用。板子上的自适应阈值。定标窗口是 1200 帧。在 S3 的 20 Hz 采样率下约 60 秒在 C6 上 DSP 固定 8 Hz就变成约 150 秒——同一个常量在不同芯片上代表不同的物理时间这是多机型混布时第一个要对齐的东西。#define EDGE_CALIB_FRAMES 1200 /* ~60 s 20 Hz */ #define EDGE_CALIB_SIGMA_MULT 3.0f /* 阈值 空场景均值 3σ */ s_adaptive_threshold mean EDGE_CALIB_SIGMA_MULT * sigma; if (s_adaptive_threshold 0.01f) s_adaptive_threshold 0.01f;也就是说上电时房间必须是空的。它用前 1200 帧的方差分布学一个环境噪声基线阈值取均值加三倍标准差。定标完成前使用固定的 0.05 保底值一旦pres_thresh被显式写过非零值自适应逻辑就不再生效。NVS 里的pres_thresh是 u16 毫单位存储读出来除以 1000/* firmware/esp32-csi-node/main/nvs_config.c */ if (nvs_get_u16(handle, pres_thresh, pres_thresh_val) ESP_OK) { cfg-presence_thresh (float)pres_thresh_val / 1000.0f; }所以--pres-thresh 50等于 0.05与代码里的保底默认一致数值越小越敏感。跌倒阈值的单位是毫15000即 15.0 rad/s²——文档给的参照是正常走动 2–5、真实跌倒 20 以上。单阈值一定会抖。这是仓库里修过的真实 bug。注释记录得很具体同一个人静止坐着时帧间presence_score曾在 2.6 到 26.7 之间跳布尔标志在边界来回翻转。修复方式是施密特触发器加清除去抖#define EDGE_PRESENCE_HYST_RATIO 0.5f /* 低阈值 0.5 × 高阈值 */ #define EDGE_PRESENCE_CLEAR_FRAMES 5 /* 连续 5 帧低于低阈值才清除 */ static bool presence_flag_update(bool prev, float score, float threshold, uint8_t *below_count) { float low_thresh threshold * EDGE_PRESENCE_HYST_RATIO; if (score threshold) { *below_count 0; return true; } /* 立即进入 */ if (score low_thresh) { *below_count 0; return prev; } /* 死区保持 */ if (*below_count 0xFF) (*below_count); if (!prev) return false; if (*below_count EDGE_PRESENCE_CLEAR_FRAMES) { *below_count 0; return false; } return true; /* 保持窗口内不落旗 */ }这个状态机的行为可以直接读出来进入立即、离开滞后。代价是真实离场会晚几帧才反映出来收益是没人坐在沙发上时状态不会疯狂闪烁。比例 0.5 是代码里的默认值不是普适最优房间的分数尺度不同就该重调。误报来源是明确列出的不是玄学。固件文档点名三类天线旁边的风扇、微波炉的占空比周期、邻居 AP 的发射功率波动。它们的共同点是都会让相位方差越过阈值。文档给的处置办法很土但有效——空房间上电重定标。人数统计还有一层门控防止一个身体的多径被算成四个人分组方差必须达到最强组的 35%EDGE_PERSON_MIN_ENERGY_RATIO代表子载波间距至少 4EDGE_PERSON_MIN_SC_SEP且连续 3 帧判定一致才输出EDGE_PERSON_PERSIST_FRAMES。更关键的是存在门本身必须为真人数才允许非零static inline uint8_t edge_evidence_person_count(bool presence, uint8_t active_count) { if (!presence) return 0; /* 房间没人时人数只能是 0 */ return active_count EDGE_MAX_PERSONS ? EDGE_MAX_PERSONS : active_count; }这是一条线级不变量早期版本会同时输出presencefalse和n_persons4自相矛盾所以把约束写进了公共头文件让主机侧测试也能跑同一份实现。主机侧还有一条更严格的路径。如果不用固件的启发式而是走房间标定 CLI门限是硬的wifi-densepose calibrate --port /dev/ttyUSB0 # 空房间基线至少 600 帧 wifi-densepose enroll --room living # 8 个引导锚点约 4 分钟 wifi-densepose train-room --room living # 训练专家库 wifi-densepose room-status --room living # 看哪些专家真的训出来了 wifi-densepose room-watch --room living # 实时推理calibrate的 600 帧下限是代码强制DEFAULT_MIN_FRAMES帧数不够直接报insufficient frames: have X, need 600并失败不会生成一个降级的基线。--duration-s默认 30 秒如果节点实际速率低于假设的 20 Hz30 秒可能凑不满 600 帧——这时应该加时长而不是用--min-frames压低门槛后者会打印放宽了相位集中度保证勿用于生产的警告。这里必须诚实标注一个已知局限文档明说代码无法区分宠物与人。猫跳上沙发引起的相位扰动在方差特征上和一个人小幅挪动没有本质差别。要做宠物免疫得靠额外的多链路几何或训练数据不是调个阈值能解决的。还有一层权威性区分值得知道主机侧存在空房间引导基线bootstrap baseline它的 authority 字段明确写着bootstrap_only——可以在启动阶段抑制背景扰动但永远不授权校准证据与体征输出。这种数据带权限标签的设计在把感知结果接入自动化时很重要低权限的基线不能悄悄变成高权限的结论。五、接进自己的面板与告警服务端把结果摊开在三类接口上REST/api/v1/sensing/latest、/api/v1/vital-signs、/api/v1/mesh、WebSocket/ws/sensing、以及 Prometheus 文本格式/api/v1/mesh/metrics。自建面板最省事的组合是 WebSocket 推实时状态、Prometheus 拉长期指标。Home Assistant 用户不必自己写面板。一个--mqtt参数就够cargo run --release -p wifi-densepose-sensing-server --features mqtt -- \ --source esp32 --mqtt --mqtt-host 192.168.1.10 \ --mqtt-username homeassistant --mqtt-password-env MQTT_PASSWORD每个节点会自动出现一组实体原始信号 9 个presence、person_count、motion_level、presence_score、motion_energy、rssi、heart_rate、breathing_rate、fall加 6 个语义原语room_active、no_movement、elderly_inactivity_anomaly、someone_sleeping、possible_distress、fall_risk_elevated。语义原语是服务端每节点一套的小状态机启动后前 60 秒为预热期期间不可用。几个工程上很实用的细节每个实体有独立的 availability 主题没有真实数据源时实体是offlineHA 显示不可用而不是编一个值。运行中节点停发帧下一个 30 秒心跳就会掉线。做告警时可以用这个失联做真正的设备级异常比看数值更可靠。presence早期版本每帧都发实测 37.9 条/秒现在只在变化时发加 60 秒心跳。如果你在自建面板里做高频采样别指望 MQTT 给你 20 Hz。心率/呼吸只在显式校准、单一占用者、质量门通过后才发布--privacy-mode会直接把这两项从发现与状态里摘掉。Matter 桥只映射标准簇存在走OccupancySensing(0x0406)/OccupancySensor(0x0107)跌倒事件走GenericSwitch(0x000F)。心率和姿态在 Matter 里没有对应簇只能走 MQTT——这是协议限制不是实现偷懒。告警策略上优先用语义原语 时长而不是瞬时分数。仓库自带的monitoring/alerting-rules.yml是应用层规则服务存活、5xx 比例、P95 延迟感知层告警更适合挂在确定性更高的布尔量上# 示例以 HA 生成的 entity_id 为准实体 slug 为 no_movement automation: - alias: 客厅长时间无移动提醒 trigger: - platform: state entity_id: binary_sensor.ruview_living_room_no_movement to: on for: 00:30:00 action: - service: notify.mobile_app_phone data: message: 客厅已连续 30 分钟无人移动no_movement的定义是存在为真且运动低于 1% 持续 30 分钟本身就带时长过滤不会因为一次静止误报。相比之下直接对presence_score设阈值告警在风扇或微波炉场景下几乎必然误报。需要说明的是docs/integrations/home-assistant.md里对语义原语的精确率/召回率标注为 CLAIMED——它引用的回放工具不在仓库内。这不妨碍把它当作可用信号但不要对外宣称经实测 95% 准确。六、成本、收益与必须承认的边界整套最小系统的钱花在这些地方一块 ESP32-S3 约 9 美元一个能开 CSI 的 2.4 GHz AP家里现有的路由器通常就够一台常开的 Linux 主机跑 sensing server。单节点够判断房间里有没有人、在不在动要谈位置、多人分离就得按文档建议上 3–6 个空间分散的节点因为单条射频链路的几何信息量不够。关于这个项目本身社区里存在两种极端声音一边把它描述成用 WiFi 穿墙看穿一切另一边直接质疑项目造假。仓库对此的回应是PROOF.md里的三级标注体系——MEASURED本机可复现、有失败于修复前代码的测试、CLAIMED有出处但未在自动化框架内复现、DATA-GATED/HARDWARE-GATED代码路径真实但精度依赖未随仓库发布的数据或硬件。它甚至公开记录了几次撤回单类录音上的100% 存在检测、上游 checkpoint 只跑到 0.08% PCK 的复现结果。对做工程的人来说这种标注方式比任何宣传语都有用——它告诉你哪些数字可以直接用在设计里哪些必须先自己验。所以回到标题里的从零搭一套真正能稳定落地的形态是——一个 9 美元的节点edge-tier 2空房间上电完成约 60 秒定标把presence_score和presence通过 1 Hz 体征包送到局域网内的主机主机侧用room-watch或 MQTT 接进面板告警只挂在带时长过滤的布尔量上风扇、微波炉、AP 功率波动导致的幽灵检测通过重新定标和抬高pres_thresh处理涉及心率、呼吸、姿态的结论一律先看它有没有被标成 MEASURED。不装摄像头这件事9 美元就能做到。剩下的部分取决于你是否愿意接受它是一个方差检测器这个事实并据此设计你的自动化。【免费下载链接】RuViewπ RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.项目地址: https://gitcode.com/GitHub_Trending/wi/RuView创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站