WiFi 也能测心跳?不用传感器!ESP32 CSI 教程
Author: Ming
看到这个标题,我就知道你是满脸的不相信,觉得这又是那种听起来很唬人的“学术噱头”?说实话,我第一次听说WiFi能感知环境甚至测量心跳时,反应和你一模一样:这太玄幻了,怎么可能呢?
但当我真正深入这个领域,才发现 WiFi 的潜力远不止“上网”这么简单。有些研究甚至让我觉得“离谱”:比如,当你身处一个 WiFi 信号覆盖下,就能判断你是否在睡觉、测量你的呼吸频率;能“数”出一个房间里有几个人;能识别你是在走路、跳跃,还是静止不动;甚至——你或许更想不到——在你使用手机时,还能推测出你正在运行什么APP。
这一切听起来像是“玄学”,但背后其实是一项真实存在且正在快速发展的技术——WiFi 信道状态信息感知,也就是我们常说的 CSI(Channel State Information)。
你可能会觉得即使这个技术真实存在,也离我们日常生活很远,其实它没有你想象的那么复杂。要实现它,你并不需要昂贵的设备或高深的专业背景。只需要一个普通的路由器,和一块价格不到 20 元的 ESP32 物联网开发板。没错,就是这两样随处可见的东西,就能让你亲手触摸到这项“黑科技”。
它的原理也非常简单,首先,我们需要改变对Wi-Fi信号的看法。我们通常认为Wi-Fi信号只是一个用来上网的“数据管道”,但实质上,它是在我们空间中穿梭的无线电波。它从路由器发出,并不是直线飞到你的手机上,而是在空间中经历反射、衍射、穿透物体……最终以多条路径、不同时间抵达接收端。正是这些“曲折”的旅程,让 WiFi 信号携带上了丰富的环境信息。
而 CSI,就是记录这些路径特征的“身份卡”。它精确描述了每一条路径上信号的振幅和相位——振幅告诉我们信号在这条路上衰减了多少,相位则记录了信号延迟了多久。换句话说,CSI 数据就像是一张“环境指纹”,我们只要学会解读它,就能反推出空间中发生了什么。
在实际应用中,我们接收到的 CSI 数据,是一串随时间变化的数值序列。比如下面这段,就是某一时刻t捕捉到的 CSI 样本:(在一秒钟之内,你可以接收到上百条这样的数据)
CSI[t] = [
-20, 15, -20, 15, -50, 15, 16, 0, -86, 15, 38, 0,
-120, 15, 37, 0, 119, 15, 24, 0, 125, 15, -2, 15,
-107, 15, -19, 15, -73, 15, -14, 15, -44, 15, 14, 0,
-34, 15, 49, 0, -43, 15, 80, 0, -62, 15, 98, 0,
-84, 15, 91, 0, -81, 15, 58, 0, -55, 15, 51, 0,
-26, 15, 52, 0, -7, 15, 72, 0, -2, 15, 88, 0,
-9, 15, 101, 0, -22, 15, 100, 0, -19, 15, 87, 0,
-14, 15, 73, 0, 2, 0, 62, 0, 18, 0, 61, 0,
41, 0, 69, 0, 23, 0, 32, 0, -32, 0
]
这些数字并非乱码,它们代表了不同子载波上的振幅和相位信息。每两个数字为一组,共同描绘出信号在某个子载波上的状态。
举个CSI检测心跳的例子:
我们要知道,微小的移动,足以改变无线电波的传播路径。
人体本身就是一个“干扰体”。当 WiFi 信号穿过或被身体反射时,我们的胸腔随着心跳产生的微小起伏——哪怕只有几毫米——都会改变某条传播路径的长度。这一点点变化,反映在 CSI 数据中,就表现为相位的周期性波动。
这个过程,有点像你对墙拍球:如果墙悄悄移动了一点点,球弹回来的时间也会发生微小而有规律的变化。在这里,你的胸腔就是那面“移动的墙”,而 WiFi 信号就是那个“球”。
当然,环境中存在大量干扰:别人的走动、空气流动、设备噪声……这些都会混入 CSI 数据中,形成背景“噪音”。但心跳的频率是有特征的,成年人静息状态下通常介于 0.8~2.0 Hz 之间。通过傅里叶变换等信号处理技术,我们可以将 CSI 数据从时域转换到频域,从而在纷杂的噪声中“揪”出那个代表心跳的频率尖峰。
整个过程可以概括为: WiFi 信号发射 → 在空间中经历多路径传播(其中一路被人体的胸腔反射) → 心跳引起胸腔周期性微动 → 改变反射路径长度 → 导致 CSI 相位周期性变化 → ESP32 捕获原始 CSI 数据 → 通过算法滤除噪声、提取相位规律 → 最终转换为心跳频率。
当然,如果环境复杂、目标行为细微,传统信号处理方法可能就力不从心了。这时,我们可以借助人工智能——比如 LSTM、GRU 或 Transformer 这类时序神经网络,对 CSI 数据进行有监督学习,让模型自己学会从混沌中识别出我们关心的“信号”。
你肯定有一个疑问,就是非得用ESP32作为接收端吗,手机电脑连上网络后不也可以作为接收端接收CSI信息吗?你想得没错,从原理上讲,任何能连接WiFi的设备(手机、电脑、网卡)都在接收CSI数据,因为这是OFDM系统(WiFi的技术基础)进行正常通信所必需的信息。
但是,“能接收”和“能获取”是两回事。这正是ESP32在此领域无可替代的原因
我们的日常设备,如手机和笔记本电脑,它们的WiFi芯片被操作系统“保护”起来了。制造商为了确保网络的稳定和安全,隐藏了像CSI这样的底层原始数据,只向我们开放“能否上网”的结果,而不暴露信号传播的“过程”。
而ESP32则不同,它是一款为开发者设计的开源硬件。我们可以通过编程直接与WiFi芯片“对话”,命令它把每一帧数据包附带的CSI信息都完整地吐出来。这种“上帝视角”的权限,是我们在手机和电脑上难以获得的。
正是这种开放性,加上它极低的成本(不到20元)和活跃的开发者社区,使得ESP32成为了进入WiFi感知领域最理想、也几乎是唯一面向大众的“入场券”。
既然它这么厉害,为什么我们在日常生活中没怎么见到这种技术的应用呢?这可算是问到点子上了,因为它有几个暂时还绕不过去的坎。
CSI数据对环境干扰极为敏感,特别是对微小运动感知至关重要的相位信息,非常容易受到环境干扰。日常环境中的无线电频率干扰,包括同频段的Wi-Fi网络、蓝牙设备及其他无线信号源,都会引入显著的噪声。更复杂的是,室内环境的任何细微变化——如家具移动、门窗开闭甚至空气流动导致的热胀冷缩——都会改变既有的信道特性,造成信号基线的漂移。即使是AI分析,也很难训练。
更棘手的是,这项技术还处在“定制化”阶段,远未达到“通用化”的程度。今天在你家书房里训练好的、能精准识别你坐下动作的模型,明天把它放到我家的客厅里,可能就完全失灵了。因为它已经熟悉了你书房里那面书墙、那张木桌带来的独特信号反射环境。换个空间,所有的“参照物”都变了,它就得从头学起。这种“水土不服”的特性,让它很难像传统传感器那样,出厂设定好就能在任何地方即插即用。
再者,虽然ESP32硬件本身很便宜,但要让整个系统真正可靠地工作,背后的“隐性成本”相当高。你需要专门来规划和布设设备位置、调试AI算法、处理海量数据。而这,显然不如直接安装一个几十块钱的传感器来得简单直接,而且精度还更高。
在了解了它的这些局限性之后,如果你内心的好奇之火并未熄灭,渴望亲手尝试这个技术,那么,接下来就是教程环节。下面会教学如何利用ESP32设备,让这些无形的CSI信号数据能够通过MQTT协议,像源源不断的溪流一样,实时传输到你的电脑上并保存,为后续的数据处理和分析做好准备。注意,本篇教程并不涉及后续的数据处理和分析环节,这就要你自己去探索了。(这需要你有一些数据处理和人工智能基础)
首先就是设备的准备,一个负责持续发射Wi-Fi信号的信源,和一个用于捕捉信道状态信息的接收端。在日常生活中,最便捷的方案就是使用手机开启热点作为发射端;当然,你也可以使用路由器。接收端则采用ESP32开发板。
关于ESP32的选型,乐鑫官方对不同芯片的CSI性能有一个推荐顺序,大致为:ESP32-C5 > ESP32-C6 > ESP32-C3 ≈ ESP32-S3 > ESP32。不过请注意,对于初次实验或基础应用而言,即便是最经典的ESP32型号也完全足够,你无需过分追求最新型号。
在实际的实验操作中,我强烈推荐大家采用手机热点的方案。这样做有一个明显的好处:你可以自由且灵活地摆放信号发射源的位置。同时,请务必留意热点的AP频段设置——部分较早的ESP32型号可能不支持连接5GHz网络,因此建议在手机热点设置中将其调整为2.4GHz频段,以确保兼容性。
下图便是一个简单的实物连接演示,你可以用类似的方式布置你的实验环境:
为了让你对整体数据流有一个清晰的认识,我们先用下面这张架构图来俯瞰整个系统:
整个工作流程非常直观:你的手机开启一个Wi-Fi热点,ESP32开发板和你的电脑都作为客户端连接到这个同一网络。ESP32负责捕捉CSI数据,而你的电脑则作为一个MQTT服务器(或称Broker)运行。一旦系统开始工作,ESP32便会将采集到的数据持续发送至电脑上的这个服务器并进行保存。
你可能会问,为什么选择MQTT,而不是更常见的串口通信或者HTTP协议呢?这背后有非常实际的考量。
首先,串口通信虽然简单,但它有一条物理“锁链”——ESP32必须通过USB数据线始终与电脑相连。这不仅限制了设备的布置自由,更与未来我们希望的、能够独立部署进行实时数据分析的应用场景相去甚远。
其次,对于HTTP协议而言,它虽然无处不在,但其设计并不适合高频、连续的数据流。每次传输都伴随着冗长的请求头与响应头,就像每次寄送一个小零件,却都要用一个巨大的、填充了泡沫塑料的箱子来包装,显得十分臃肿且效率低下。
而MQTT则完美地规避了这些问题。它生来就是为了连接物理世界的传感器与网络,是一种轻量级、低功耗的“机器对话”协议。它专为持续不断的数据流设计,协议开销极小,传输效率高。
一切硬件就绪后,接下来就是给这个ESP32写程序了,这里必须使用乐鑫官方推出的ESP-IDF框架。你可能会问,为什么不能用更简单的Arduino或MicroPython呢?原因在于,ESP-IDF是乐鑫官方的物联网开发框架,提供了最底层的WiFi驱动和CSI数据接口,而其他的框架并没有提供这么底层的接口。
如何在你的电脑上安装ESP-IDF呢,很简单,现在安装ESP-IDF已经变得非常简单。无论你使用的是Windows、macOS还是Linux系统,我都强烈推荐通过在VSCode中安装ESP-IDF插件来完成环境搭建。对,ESP-IDF已经可以作为一个VScode的插件在VSCode中使用了。这个插件将整个工具链、编译器和烧录工具都集成在了一起,大大简化了配置过程。
你可以参考官方安装指南。如果你觉得官方文档不够直观,也可以在网络上搜索“VSCode ESP-IDF 安装教程”,有许多社区开发者制作的优质图文或视频教程,能帮你绕过不少坑。
程序很死板,就是接受CSI数据并通过MQTT转发。,但是要如何把程序烧录进ESP32
安装完成后,就新建一个空项目,按照如下的步骤来
项目创建成功后,我们先来熟悉一下标准的ESP-IDF项目结构,它通常会包含以下关键部分:
main目录:这是核心,我们的应用程序代码主要在这里。CMakeLists.txt文件:告诉构建系统如何编译你的项目。- 其他组件(component)目录:用于存放项目依赖的库。
接下来,你需要从我提供的Gitee仓库将项目文件下载到本地。请将仓库中的project documents里面的文件直接复制到你刚刚创建的项目文件夹中,覆盖原有的文件即可。
现在,打开 main 目录下的 main.c 源文件,找到第49行到63行附近的WiFi和MQTT配置参数部分,这是整个程序的“控制中枢”,你需要根据你的实际情况进行修改:
// WiFi配置参数
#define WIFI_SSID "你的WIFI网络名" // 请填写你的手机热点或路由器名称
#define WIFI_PASS "WIFI密码" // 对应的WiFi密码
#define WIFI_MAX_RETRY 5 // 连接失败的重试次数
// MQTT配置参数
#define MQTT_BROKER_URL "mqtt://192.168.80.102:1883" // 你的电脑(MQTT服务器)的IP地址
#define MQTT_USERNAME "esp_ikd4s" // 自定义的设备用户名
#define MQTT_PASSWORD "1234567890" // MQTT连接密码(若服务器无需密码可忽略)
#define MQTT_CLIENT_ID "ecc1-esp1" // 此设备在MQTT网络中的唯一ID
#define MQTT_TOPIC "electric/vi" // 用于发送CSI数据的主题名称
特别注意:MQTT_BROKER_URL 中的IP地址需要修改为你运行MQTT服务器的电脑的IP地址,确保ESP32和电脑在同一网络中能够相互通信。
配置完成后,使用VSCode底部状态栏的按钮(或终端命令)进行编译和烧录。如果一切顺利,打开串口监视器,你将看到ESP32尝试连接WiFi并最终成功连接到MQTT服务器的日志。当看到CSI数据开始源源不断地发送时,那么恭喜你,最困难的部分已经完成了!
现在,CSI数据已经从ESP32发出,我们需要在电脑上搭建一个接收平台。
对于熟悉MQTT的开发者,你可以轻松地搭建一个Mosquitto或其他MQTT服务器,并订阅 electric/vi 主题来接收数据。
如果你不熟悉MQTT但会使用JavaScript/Node.js,我在Gitee仓库中已经为你准备了一个简单易用的MQTT服务器套件,就是那个名为 MQTT_Server.7z 的文件。解压后,你会看到几个JS文件,其中 mqttServer.mjs 和 app.mjs 是核心。
在运行之前,有两个地方你可能需要自定义:
// 在mqttServer.mjs第45行可以更改MQTT服务端口
const server = createServer(broker.handle);
const port = 1883; //设置MQTT服务端口
server.listen(port, () => {
const msg = `MQTT Broker Start, listening port ${port}`;
logToFile(0,msg);
});
// 在app.mjs第61行~69行对接收到的数据进行后续的处理
client.on("message", (topic, message) => {
let msg = message.toString();
if (msg) {
let datas = JSON.parse(msg)
if (datas) {
// 这里的datas就是接收到的CSI数据,可以在这里继续后续处理
}
}
});
配置好后,在终端运行 node app.mjs 启动服务。最后提醒一点:请暂时关闭电脑的防火墙,或者为对应的端口添加入站规则,否则可能无法正常接收到数据。
以我使用的ESP32-C5为例,在成功运行后,每秒大约可以接收到120条CSI数据,每条数据都包含一个长度为106的CSI复数数组,这为后续的心跳、呼吸等生理信号分析提供了丰富的数据基础。
最后,如果你感兴趣并且想深入了解这项技术的前沿动态,我推荐你关注GitHub上的 Awesome-WiFi-CSI-Sensing 仓库。这个项目收集了大量关于利用WiFi CSI进行感知的学术论文和有趣项目,从基础的人体检测到精细的手势识别、呼吸心跳监测,甚至情感识别,你会发现这个领域有非常多“神奇”的项目。