ESP32 WiFi感知实战:无传感器人体检测的RSSI与CSI原理
开头我先交代一下背景这段时间我一直在玩ESP32本来只是做点温湿度采集、开关控制这类常规项目。直到我看到一个方向让我当场决定停下手头的活花了一整个周末去验证——让一块不带任何传感器的ESP32开发板只靠WiFi信号本身去感知房间里有没有人、有没有人走动。听起来像玄学但等我真正跑起来之后确实被这个“第六感”式的实现折服了。这篇文章就是我对这个方向从原理到实践的一次完整复盘。我会讲清楚WiFi感知到底是怎么工作的为什么ESP32这种廉价芯片就能做具体代码怎么组织以及我在实测中踩过的坑和调整参数的经验。不管你是刚接触Arduino的小白还是已经玩过一阵子ESP32的爱好者只要你家里有几个物联网设备、一台路由就可以复现这套实验。它做不了军用级别的雷达但作为无传感器人体感知的入门性价比高得吓人。1. 先搞清楚WiFi“第六感”到底在做什么1.1 为什么“没有传感器”也能感知人体你可能每天都被几十个WiFi信号包围着。手机、路由器、智能音箱、电视、门锁它们总在不定期地往外发各种管理与数据帧。这些信号在到达接收端之后会经过直射、墙面反射、家具反射甚至穿过人体。问题来了人体主要由水构成而水在2.4GHz这个频段附近对电磁波有很强的吸收作用同时人体本身又是一个比墙壁更复杂的反射体。所以人一旦进入某个WiFi链路的传播路径接收到的信号强度会发生变化体现在多个维度的细节上整体强度可能衰减几个dB相位和幅度矩阵也会出现波动。如果人还在走动这些反射路径一直在变信号就会呈现明显的抖动。于是只要我们把一个能接收WiFi信号、又能编程处理数据的设备放在房间里它就可以从周围的WiFi信号变化中反推出“有没有人”和“人是不是在动”。这种方案最大的价值在于零部署成本。不需要额外买红外传感器、毫米波雷达不需要布线和供电而是直接复用环境里已经存在的大量WiFi通信。传统红外容易受热源干扰毫米波雷达又贵而这个方案对ESP32这种十块钱级别的芯片来说只是软件层面的事。1.2 典型应用场景我最终决定实验的两个方向我身边很多朋友第一时间想到的是智能家居联动人离开书房超过五分钟自动关灯人进来又重新打开晚上客厅只有路由器在亮灯检测到人走动后自动开启夜灯。这类需求用传感器也能做但用WiFi感知的好处是“什么硬件都不用加”对改造老房子尤其友好。另一个我认为更有意义的场景是独居老人看护。不是说把它当成医学设备而是当一个“低成本活动异常提醒器”如果一个房间长时间检测不到任何“有人”或“有人活动”的信号变化可以推送给家属一个简单的通知。它不需要老人佩戴任何终端不侵犯视觉隐私这在某些环境下比摄像头更有适用性。当然它只能做存在性判断不能做行为识别这是它的边界。我在测试时也同步确认了一个原则这套东西只适用于自己可控的家庭环境不用来探测邻居家里的情况也不做任何穿墙窥探式的应用。它的价值是服务自己的生活场景这一点无论如何都要守住。2. 想弄明白原理RSSI、CSI和人体之间的关系2.1 两个关键信号指标RSSI和CSI在WiFi感知里有两个高频出现的词RSSI和CSI。RSSI是接收信号强度指示也就是接收端测到的信号功率单位通常是dBm。它的优点是获取成本极低几乎所有WiFi硬件都提供这个数值缺点是信息量有限抗干扰能力弱一个门打开关上、微波炉转一下都可能让它抖动。CSI是信道状态信息它能描述信号从发射端到接收端经过多个子载波时的幅度与相位变化相当于把“整条路径像拍X光片一样拆开看”。在PC级别的网卡上CSI的维度可以做得很细对动作识别效果远好于RSSI。可惜的是大多数消费级IoT芯片对CSI的支持并不完善ESP32虽然有社区和实验性质的驱动但属于“能用但别抱太高期望”的状态。指标含义输出维度ESP32获取成本抗干扰能力典型用途RSSI接收信号强度单一数值dBm极低较弱存在检测、移动检测CSI信道状态信息多子载波幅度/相位较低实验驱动较强动作识别、手势识别对初学者来说第一步永远是从RSSI开始。因为即使只有这单一数值也已经包含了“人体遮挡导致衰减”和“人体移动导致波动”两层信息。理解透RSSI之后再考虑CSI会比较靠谱。2.2 人体到底是怎么“干扰”WiFi信号的把WiFi信号想象成水波。房间里没有人的时候水波从发射端推开沿着固定的方式从各种物体表面反射回来接收端看到的是一个相对稳定的图案。人进入房间相当于在水面上放了一块浸了水的海面一部分波被吸走一部分波的反射路径整个变了。站在那儿不动图案发生了一次整体改变来回走动图案就持续扰动。从物理数值上看2.4GHz波的波长为12.5厘米左右人身体的尺寸远远大于波长所以人体对WiFi信号的散射非常显著。再加上人体含水量高会使部分电磁能量被吸收转化成热量。于是在数据曲线上静止的人会让你看到RSSI平均值发生阶梯式偏移通常比无人时低几个dB走动的人让你看到RSSI方差剧烈增大短时间内跳动幅度可能超过510dB。这也是为什么我说“存在检测”和“运动检测”本质上应当用两种不同的判据。只看平均值可能会把“有人静坐”漏掉只看波动又可能把“站在墙角不动的人”忽略掉。最理想的状态是把两者结合起来平均值反映环境的基线漂移方差反映环境的变化剧烈程度。2.3 为什么ESP32特别适合干这件事市面上能接收WiFi信号的芯片很多手机和路由器肯定不行不好改程序树莓派可以但成本高、体积大、功耗也高。ESP32的优势恰好在这个“中间地带”单个芯片集成了2.4GHz WiFi的双模能力支持promiscuous混杂模式——也就是说它可以不连接任何网络、不参与数据传输只默默收听空间里的无线帧并把每一帧的信号强度、信道、源MAC地址等信息取回来。更关键的是它便宜、功耗低、社区资料爆炸。我开发的时候用Arduino IDE就能直接调esp_wifi底层接口不需要碰复杂的协议栈。市面上常见的ESP32 DevKitC板子一片二三十块钱就算烧坏了也不心疼。可以说ESP32是“WiFi感知”这门技术最亲民的硬件入口。3. 硬件与环境准备我搭的实验台3.1 选哪块ESP32开发板先说结论如果你手头有最常见的ESP32 DevKitC直接用它不需要额外采购。如果还没买建议买带外置天线的模组版本例如ESP32-WROOM-32E的模组配合外置天线座感知距离和稳定性会好一些。ESP32-C3和ESP32-S3也能跑但它们对WiFi底层驱动的实现与经典ESP32略有差异。我在测试中发现C3在小区隙模式下回调频率和稳定性并不差但部分Arduino核心版本的接口行为不一致新手容易迷路。ESP32-S3算力更强适合后面接机器学习但价格略高。芯片价格区间WiFi能力混杂模式支持我的推荐度ESP32-WROOM-32低完整支持好高ESP32-C3极低2.4GHz支持较好中ESP32-S3中2.4GHz支持较好中高适合进阶3.2 Arduino IDE环境与所需库我用的是Arduino IDE安装ESP32开发板包之后代码里只需要包含WiFi.h即可其余底层调用都在esp_wifi.h里。具体来说在“开发板管理器”里搜索esp32选择乐鑫官方包版本我用的3.x实测没有问题。不需要额外下载复杂的库因为混杂模式是ESP32无线接口原生支持的。唯一要注意的是烧录时的波特率我习惯用115200速度合适调试信息也不容易乱码。串口监视器保持打开就行后面看RSSI采样日志全靠它。如果你是ESP-IDF的用户当然也可以直接在组件里调用esp_wifi_set_promiscuous原理一致。这篇文章后面的代码演示我用Arduino语法但移植到ESP-IDF只是换函数名和回调声明方式逻辑完全通用。3.3 摆放位置、天线方向与供电避坑我踩过的第一个坑就是把板子放在金属电脑主机旁边。结果RSSI波动大得离谱甚至影响了我校准基线的判断。后来把板子挪到书桌靠墙、周围没有大块金属物体的地方数据才稳定下来。天线的摆放比你想的更重要。ESP32的PCB天线在板子边缘尽量让天线垂直朝上不要横躺着贴着桌面。整块板子也不要塞进塑料收纳箱里太深塑料虽然影响小但旁边要是叠了金属外壳的充电器会把信号挡得严严实实。电源方面不要用劣质的USB充电头供电。我之前用过一个老旧充电头输出纹波大导致板载稳压后的3.3V带有明显噪声。这种噪声不一定直接影响射频前端但我会一直怀疑数据是不是被电源干扰了。换成稳定的5V适配器后同样位置的测试曲线干净了许多。注意如果你用USB延长线供电尽量让板子和插座保持一定距离。充电线里的电流回路也会形成电磁干扰特别是在进行高灵敏度RSSI实验时数据会比较“脏”。4. 核心实现单ESP32监听WiFi信号波动的完整方案4.1 方案总览被动听不主动发我采用的方案是单节点被动监听。ESP32会进入混杂模式持续收听周围WiFi设备发出的管理帧。手机待机时的probe request、路由器广播的beacon都是现成的“信号源”。ESP32只负责把每帧的RSSI记录到一个滑动窗口里然后每秒计算一次统计特征。这样做的好处是部署极其简单一个节点独立工作不用成对配置不需要专门的发射器。缺点是信号来源不可控周围设备不一定持续发包所以检测的实时性会受环境影响。如果某段时间空间里确实没有任何WiFi活动采集不到样本你就得判断为“无数据”而不是“无人”。那么为了稳定做实验我家里会放一台手机或智能插座作为“信标”。它们会周期性发数据帧这样ESP32始终有信号可测。你可能关心它在混杂模式下听多少信道默认只监听当前信道。除非你有特殊需求否则固定在信道1就可以了移动电话大多会自动跳信道不用管它。4.2 源码逐段解读从混杂模式到RSSI统计下面这段代码是我在Arduino环境下跑通的最简版本。它做的就是一件事采集RSSI样本计算每秒滑动窗口的均值与标准差然后通过串口输出。#include WiFi.h #include esp_wifi.h #define CHANNEL 1 // 固定监听信道 #define WINDOW_SIZE 80 // 滑动窗口样本数 #define STD_THRESHOLD 2.0 // 标准差阈值单位dBm #define BASELINE_OFFSET 5.0 // 基线偏移阈值 float rssiBuffer[WINDOW_SIZE]; int bufferIndex 0; int sampleCount 0; float baselineMean -50.0; // 初始基线后续会校准 bool systemReady false; void snifferCallback(void* buf, wifi_promiscuous_pkt_type_t type) { wifi_promiscuous_pkt_t *pkt (wifi_promiscuous_pkt_t*)buf; int8_t rssi pkt-rx_ctrl.rssi; rssiBuffer[bufferIndex] rssi; bufferIndex (bufferIndex 1) % WINDOW_SIZE; if (sampleCount WINDOW_SIZE) sampleCount; } float computeMean() { float sum 0; int n (sampleCount WINDOW_SIZE) ? sampleCount : WINDOW_SIZE; for (int i 0; i n; i) sum rssiBuffer[i]; return sum / n; } float computeStd() { int n (sampleCount WINDOW_SIZE) ? sampleCount : WINDOW_SIZE; if (n 0) return 0; float mean computeMean(); float sum 0; for (int i 0; i n; i) { float diff rssiBuffer[i] - mean; sum diff * diff; } return sqrt(sum / n); } void setupWiFiSniffer() { WiFi.mode(WIFI_STA); // 站点模式但不连接AP esp_wifi_set_promiscuous(true); esp_wifi_set_promiscuous_rx_cb(snifferCallback); esp_wifi_set_channel(CHANNEL, WIFI_SECOND_CHAN_NONE); } void setup() { Serial.begin(115200); delay(500); setupWiFiSniffer(); Serial.println(Sniffer started...); delay(2000); // 跳过刚开机的噪声 baselineMean computeMean(); // 简单校准假设此时无人 systemReady true; } void loop() { if (systemReady millis() % 1000 0) { float meanRssi computeMean(); float stdRssi computeStd(); bool moving stdRssi STD_THRESHOLD; bool blocking (baselineMean - meanRssi) BASELINE_OFFSET; if (moving) { Serial.println(STATE: MOVING); } else if (blocking) { Serial.println(STATE: PRESENT_STILL); } else if (sampleCount WINDOW_SIZE) { Serial.println(STATE: EMPTY); } else { Serial.println(STATE: CALIBRATING); } Serial.printf(mean%.1f std%.1f baseline%.1f\n, meanRssi, stdRssi, baselineMean); } }这段代码并不复杂但有几个细节我得专门说明。如果你之前的ESP32程序都是连接路由器做客户端那么现在你会遇到一个反直觉点WiFi.mode(WIFI_STA)并不代表必须连上某个热点它只是把射频部分初始化成“站点模式”。在这个模式下开启混杂监听是可以正常收到信道上的管理帧的。回调函数的频率取决于周围设备的发包间隔。手机息屏后probe request一般几百毫秒到几秒一次路由器beacon每100ms一次所以窗口80个样本对应大约510秒的数据可以接受。如果你发现sampleCount长时间不增长说明当前信道太安静要么换信道要么在旁边放一台手机亮屏刷一下或者给路由器配置成固定1信道。4.3 为什么判定逻辑是这样设计的很多人拿到代码会问为什么既要算标准差又要对比基线先理解两个物理现象。第一个现象是“运动引起的波动”。当人走动时多径反射路径不断改变短时间内RSSI忽大忽小标准差会明显上升。这个特征对运动极其灵敏哪怕在隔壁房间隔着轻质隔墙只要路径上有点变化也能看出波动。我实测中安静环境的标准差通常小于1.0dBm人走过时经常飙到3.0以上。第二个现象是“静止人体造成的衰减”。当人坐下不动RSSI的波动反而会变小但因为人体遮挡了一部分信号平均值会比无人时低几dB。如果只盯着标准差就会把“有人静坐”误判成“无人”。所以我的判定是一个简单的两分支先看标准差它代表“激烈的环境变化”如果标准差不够再看当前均值与基线均值的偏差它代表“整体信号路径被改变”。这个逻辑不是唯一解但它用最少的计算量覆盖了最常见的两种情况。在阈值选择上STD_THRESHOLD取2.0dBm是我在自己书房的折中值。如果一个环境里AP安装位置特殊日常信号本身就特别抖比如旁边有散热风扇的涡流干扰这个阈值可能要调到2.5以上。BASELINE_OFFSET取5.0dBm则比较保守因为单独一个人静止站立时衰减大约48dB5是一个能稳定触发又不至于被门锁开关误触发的数值。5. 实测结果与调参记录不同场景下的真实表现5.1 三个实验场景的对比我选了三个典型场景做对照测试每个场景记录5分钟人工定时标记状态。场景环境描述均值变化标准差变化我的判定结果无人书房门窗关闭人在客厅几乎不变0.40.9稳定判定EMPTY有人静坐人坐书桌前看手机下降4.2dB0.61.2稳定判定PRESENT_STILL有人走动人在房间内来回走动波动明显1.84.5稳定判定MOVING无人状态是最容易识别的只要四周没有宠物和异常物体移动RSSI的标准差会稳定在1.0以下。有人静坐时均值的偏移需要环境稳定才能看出来如果这时候路由器的信道受到微波炉干扰均值本身就会漂来漂去所以我把该状态设计成“均值偏移且标准差不高”才触发。有人走动时的表现最直观只要看到串口里std持续大于2.0几乎不会判错。不过有一次我端着热水经过实验桌热水本身也会吸收信号走路时标准差异常升高判定是MOVING这从应用角度也没错热水确实“在动”。5.2 误报漏报与典型干扰源实测下来最大的误报源有两个。第一个是同一空间里其他WiFi设备并发比如电脑后台忽然开始大量下载路由器和电脑之间的通信流量骤增。虽然管理帧和beacon是稳定发射的但数据帧多了之后同信道的碰撞和退避会导致RSSI样本本身的分布幅度变大。解决方法是监听时只关注beacon和probe帧或者直接把数据帧过滤掉只看MAC地址是路由器的那部分。第二个误报源是环境气流准确说是空调出风口的冷风。冷风形成的空气密度变化会影响电磁波折射路径加上风本身会带动窗帘和小物件在无人的卧室里甚至会让标准差超过2.5直接触发MOVING。这让我一度以为自己算法有问题后来关掉空调一测回归平静。漏报的情况也有主要发生在有人静坐但坐姿恰好没有遮挡信号传播主路径的时候。比如人贴着窗户坐而WiFi链路是从路由器经天花板反射到ESP32人体不在主反射路径上衰减就可能不到3dB。这种时候我就把ESP32从墙角改放到桌面靠近窗边让主要路径更贴近人的背部漏报率明显下降。5.3 现场调参建议我最终留下的几条经验先不设阈值直接看原始数据。把mean和std打印在串口图上运行半天你才能知道这个环境的“噪声花纹”是什么样的。我强烈建议所有新手先做这一步。调基线时人必须彻底离开室内而且持续至少30秒。我刚开机时设定基线为2秒内的均值结果恰好有邻居家的WiFi设备在高频发包基线偏高导致后来的静止检测全部失败。改成30秒校准后才好转。窗口长度不要一味求大。窗口越平滑误报越少但反应速度会变慢。如果用来做智能灯控3秒内响应比较理想滑动窗口样本数建议50100。如果做大范围无人巡检可以加到200重在稳定性。如果判定结果频繁在EMPTY和PRESENT_STILL之间跳动多半是阈值得到了临界值附近。我建议加一个“滞回”逻辑即从无人到有人需要连续3个采样周期都超过阈值从有人到无人则只需要连续2个周期。这样能过滤掉瞬时突刺。6. 从“有无”到“行为”CSI、机器学习和多节点融合6.1 esp-csi把单个RSSI升级成多子载波信息当RSSI方案玩明白以后你可能会想要更丰富的信息。乐鑫社区里有一个被称为esp-csi的实验性方案它能在ESP32上提取WiFi CSI也就是把信号在多个OFDM子载波上的幅度和相位信息读出来。我试过在ESP32上编译esp-csi示例确实能拿到一条条子载波幅度的曲线。动作识别时这些曲线的几何形状会随着手势、走路姿态发生明显变化信息量比单个RSSI高出不少。但需要说明ESP32的RF前端是为通信设计的不是为感知设计的CSI数据的相位连续性一般需要做不少校准和滤波而且与Arduino框架的整合不是开箱即用更适合有一定嵌入式基础的人去折腾。6.2 接上机器学习识别走路、坐下、挥手RSSI原始序列虽然适合作存在检测但你要想区分“走路”和“挥手”或者区分“坐下”和“站起来”光靠平均值和标准差就不够了。这时候可以把RSSI窗口转成特征向量比如包含均值、方差、峰值数、过零率等再丢进Edge Impulse或TinyML框架训练一个轻量分类模型。我尝试过一次小实验收集了三种动作的RSSI时序数据每种80个样本训练一个简单的决策树在ESP32-S3上运行准确率大约在75%。如果换成多节点数据融合即两个ESP32放在不同位置用它们的RSSI特征拼成一个更长的向量准确率可以接近90%。代价是部署复杂度和同步问题这些东西适合当进阶课题去深入。6.3 我对这个方向的边界理解说到底WiFi感知的核心魅力不是“我想做什么”而是“我只用软件就能复用基础设施”。它的天然边界也很明显无法精确统计人数无法区分人和体型相近的大型宠物受环境影响大长期稳定性不如专用传感器。所以我的实际态度是在需要低成本、低隐私侵入、环境相对稳定的场景里它值得当主力方案在要求高可靠性的地方就用它做辅助信号比如与门磁或红外传感器互为补充。把期望值放对这技术带来的惊喜远比失望多。最后说一句我的具体操作体会如果你也想复现不妨先别急着优化代码把板子通电放桌上开着串口监视器安静观察一整个白天。等你真正看懂了自己房间的RSSI“背景噪音”长什么样再谈阈值怎么调、算法怎么改就一切顺理成章了。ESP32给我们的只是入口后面这段路靠的是耐心。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →