1. 这条“隐藏通路”到底是什么
先说说我自己的经历。几年前第一次拿到ESP32做产品原型,按官方手册老老实实折腾WiFi连接、HTTP请求,感觉这块芯片就是个“能联网的Arduino”。直到有一天调试一个无线门铃干扰问题,抱着试试看的心态开了ESP32的混杂监听模式,才发现这颗芯片内部根本不止“连接网络”这一条路。它能直接在2.4GHz频段上接收周围所有的WiFi无线帧和蓝牙广播包,不连接任何路由器、不配对任何设备,只安静地“听空气”。这套能力和官方文档里那些“连接、扫描、配网”的例程完全是两个世界,却恰恰是很多复杂无线场景的钥匙。
ESP32的射频前端本身就是为2.4GHz频段设计的,天线进来之后是WiFi和蓝牙共用的接收链路,基带里同时支持802.11b/g/n以及BLE。官方技术手册里虽然写了相关的寄存器、PHY参数、调制方式,但绝大多数开发者看到的是“如何使用WiFi连接”和“如何使用BLE通信”的API,很少有人意识到,这些API底层还有一个不依赖连接的接收通道。说它“连官方都没写进手册”其实不太准确,准确说是“官方从没在应用手册里告诉你还能这么用”。Datasheet里有字段,API Reference里有函数,但整本手册读下来,你不会找到一条清晰的学习路径,告诉你“把芯片变成一台无线电监听设备”。这部分空白,就是这篇文章想填上的。
**简单理解这条通路:**普通模式是“加入网络才能通信”,而这条隐藏通路是“不加入任何网络,直接接收空气里的射频能量和信息”。就像你平时打电话必须拨号建立通话,但如果你有一台收音机,可以直接听见周围所有电台的广播。ESP32的监听模式,就是那台收音机。
1.1 一个被官方文档忽略的射频能力
ESP32内部有一整套完整的射频前端:低噪声放大器、混频器、本振、基带滤波器,以及802.11协议栈和BLE协议栈。WiFi和蓝牙共用同一个2.4GHz天线,但在基带层面是“时分复用”的,同一时刻只能跑一种协议,切换却非常快,快到人感觉不到。这个设计本意是为了在WiFi和BLE之间共享硬件成本,但它带来一个副产品:射频前端一旦初始化,实际上就能随时调谐到任意2.4GHz信道,把该信道上所有调制信号解调出来。
我们平时所说的Sniffer(嗅探),在ESP32上对应的API叫Promiscuous Mode。官方把esp_wifi_set_promiscuous(true)列在WiFi驱动接口里,但几乎没有解释它到底能做什么。开启这个模式之后,WiFi硬件会停止“只接收与自己相关帧”的过滤逻辑,把信道上收到的每一个802.11帧——无论是谁发的、发给谁的——都回调到你的用户代码里,同时附带一组接收控制信息:信号强度RSSI、信道、数据速率、帧类型等。
这套能力不是第三方破解的,是芯片原生的功能。射频芯片本身不区分“这是不是发给我的”,区分动作发生在基带协议层。Promiscuous模式只是向上层开放了原始接收通道,所以根本不需要额外硬件。同样的道理也适用于BLE:esp_ble_gap_start_scanning这个API,名义上是“扫描设备”,实际上就是一条完全开放的BLE广播通道接收器。BLE设备在广播信道上持续发出的广播包,本来就是为了让周围设备发现它们,ESP32只需要开机监听就能收到。
这些功能官方API都有,但官方例程里最接近的也就是“WiFiScan”和“BLEScan”,扫到设备后立刻结束,没人告诉你如何持续接收原始802.11管理帧、如何从帧头提取信息、如何把WiFi和BLE两个接收通路同时打开做环境感知。这些东西不会写在入门手册里,只会藏在社区论坛、老外博客和一次次报错之后的源码阅读里。所以我说它是“藏着”的。
1.2 为什么说“连官方都没写进手册”
如果你只读过乐鑫官方的基础文档,大概率会以为ESP32的WiFi模块就三个功能:连接路由、建热点、扫描周边网络。BLE模块也就三件事:广播、扫描、连接。这些内容构成了官方文档的主体,配套的例程也都围绕“设备上云”“手机配网”展开,目的很明确:让嵌入式开发者尽快完成联网功能。
但“无线电通路”这个视角,官方几乎没有任何系统叙述。技术参考手册(Technical Reference Manual)里能查到的更多是PHY层寄存器、射频校准参数、共存仲裁器这样偏底层的碎片;而编程手册里,Promiscuous Mode只有一段干巴巴的说明。没有应用场景,没有示例代码,没有常见问题。这种“API存在但语义留白”的状态,给了民间大量解读空间,也让很多人误以为必须用SDR(软件定义无线电)才能听2.4GHz,其实手边这块几块钱的ESP32就够了。
更麻烦的是,连很多中文化教程都只教“连接”和“扫描”,导致绝大多数人压根不知道还能这么玩。我见过有人为了做“室内人员感知”,专门去买红外传感器、毫米波雷达,结果他桌上就躺着好几块ESP32。后来我帮他打开Promiscuous Mode,统计周围WiFi设备心跳包,效果出奇地好。这就是认知差。
当然,我并不是说官方刻意隐藏什么。多半是因为这类用途太小众,官方没精力为它写完整教程。再加上“监听”这个词本身容易被误解,厂商自然不愿意宣传。理解了这一点,你就知道下面这些实操内容,实际上是在替官方文档“补课”。
2. 为什么要折腾一条“非官方”无线电通路
很多朋友第一个问题是:我直接连接WiFi不就行了,监听有什么用?这里最大的区别在于“主动连接”和“被动感知”。主动连接需要目标网络的SSID和密码,需要完成802.11关联、四次握手,整个过程还会被目标AP记录。被动感知则完全不发送连接请求,只是接收环境中已有的无线电信号,不需要任何凭据,也不会改变网络的状态,对附近设备完全透明。
在大量物联网场景里,我们真正需要的不是“跟某个设备通信”,而是“知道周围存在什么、正在发生什么”。比如一个人走进房间,手机还没连上任何WiFi,但它在不断发送Probe Request探测请求,试图寻找之前连接过的WiFi网络。ESP32只要开着监听,就能捕捉这些探测请求。不要觉得这很玄,实际上手机在WiFi关闭状态下不会发,但一旦开了WiFi(即使不连接任何网络),就会周期性发出广播请求。这种信号天然存在,我们只是“听”而已。
再说信道的价值。家庭环境里微波炉、无线鼠标、蓝牙耳机、邻居路由器、各种zigbee设备,都挤在2.4GHz频段。当你设计的WiFi产品出现丢包、延迟高时,一个能“看见”整个频段占用情况的工具,远比反复修改代码有用。ESP32监听模式能告诉你每个信道的信号密度、数据包速率、RSSI分布,这等于给你配了一台简易频谱仪。在很多工程场景里,“知道环境堵不堵”比“知道协议栈报什么错”重要得多。
合法性上,被动监听2.4GHz频段里公开的广播包、Beacon帧、管理帧、BLE广播包,在很多司法辖区属于无线电监测行为,但仍需注意不要进行数据破解或追踪特定个人。我们后面专门讲合规边界,这里先强调:正路是环境感知与设备统计,而不是截取内容。
2.1 现实场景:无线环境感知比连接更重要
先说商业场景。商场想统计客流,传统方案是红外对射、摄像头视觉,成本高且涉及隐私。如果用ESP32监听WiFi和BLE广播,只需要在出入口部署若干节点,被动收集手机发出的探测请求和蓝牙广播包,经过去重和MAC随机化处理,就能得到客流趋势、驻留时间、区域热力分布。这类方案不接触任何通信内容,只统计匿名信号特征,是目前智慧零售里相当主流的技术路线。
办公室场景里,可以用ESP32判断会议室是否有人。手机在会议过程中会周期性地发出WiFi探测和BLE广播,壁挂式ESP32采集这些信号,统计设备数量,再配合门磁或红外确认,能比纯红外更准确。因为红外只能感应人体热源,对静止的人经常误判,而无线信号只要设备在线就一直在。如果灵巧一点,还能在Esp32上跑轻量分类模型,识别“一个人安静坐着”和“空房间但有电脑在跑任务”的区别。
个人玩家也能用起来。我家里搭了一个小节点,每30秒扫描一次周边WiFi设备数量,把数据上报到本地MQTT,再用小屏幕画出24小时信号强度曲线。晚上睡觉前瞄一眼,就能知道邻居是不是还在打游戏刷视频——当然这里只统计信号活跃度,不关心具体内容。调试自己的智能家居时,一个能实时显示本机信号质量、同频干扰、丢包趋势的“环境仪表盘”,价值远高于几十块钱的网络调试工具。
2.2 直接听包能解决哪些实际问题
第一个实际问题是“WiFi信道选哪个”。家用路由器默认Auto,但一个房子里几十个AP互踩信道是常态。ESP32监听模式可以在几个固定信道上分别采集一段时间,统计Beacon帧和Probe Request的数量,直接得到每个信道的拥挤程度。我自己调试智能家居网关时,就用这招找到了相对干净的信道,把原来天天掉线的设备稳住了。这种诊断能力,普通手机App给不了,因为它们看不到原始802.11帧。
第二个问题是“人来了灯亮”的无感触发。传统的PIR(人体红外)存在死区,人坐着不动半分钟就熄灭。用ESP32监听周围的WiFi/BLE信号,只要检测到固定帧率上升或新设备MAC出现,就能触发自动化。而且它不是只响应一次,而是持续感知“环境中活动信号是否变化”,非常适合做长时间存在检测。比如书房里人安静看书,电脑和手机依然会周期性联网,信号特征稳定,系统不会误判为无人。
第三个问题是低成本的BLE网关替代。许多蓝牙传感器(温湿度计、防丢器、运动手环)会周期性广播数据。这类广播不需要连接,任何扫描设备都能收到。ESP32开着BLE扫描再通过WiFi把数据发到服务器,就是一个典型的BLE网关。市面上成品网关动辄几百块,而ESP32开发板加一个电源只要几十块,还能自定义数据解析逻辑。这个用途虽然不算完全“非官方”,但大量开发者只知道“BLE连接”,不知道“BLE广播监听+WiFi上报”这套组合拳的价值,值得单独拿出来说。
3. 实操打开ESP32的“监听模式”
操作系统层面,ESP32的监听能力通过WiFi驱动暴露,Arduino库也封了一层,但很多细节需要自己翻源码。我下面给出两个最常用的实现路径:Arduino快速原型和ESP-IDF精细控制。开发环境方面,无论你用Arduino IDE、PlatformIO还是ESP-IDF,核心API都一样,区别只在于工程配置。新手建议直接从Arduino开始,因为代码量最小;想深入理解射频行为,再迁移到ESP-IDF。
3.1 基于Arduino搭建最小监听通道
用Arduino库实现监听,只需要几行代码。打开你的Arduino IDE,确保ESP32开发板支持包已安装到2.x版本。新建工程,贴入下面的完整代码:
#include "WiFi.h" #include "esp_wifi.h" void sniffCallback(void* buf, wifi_promiscuous_pkt_type_t type) { wifi_promiscuous_pkt_t* pkt = (wifi_promiscuous_pkt_t*)buf; wifi_pkt_rx_ctrl_t* ctrl = (wifi_pkt_rx_ctrl_t*)&pkt->rx_ctrl; if (type == WIFI_PKT_MGMT) { uint8_t* frame = pkt->payload; uint8_t frameControl = frame[0]; uint8_t typeSubtype = frame[0] & 0x0C; // 取type if ((frameControl & 0x0C) == 0) { // 管理帧 Serial.print("MGMT RSSI="); Serial.print(ctrl->rssi); Serial.print(" CH="); Serial.print(ctrl->channel); Serial.print(" len="); Serial.println(pkt->rx_ctrl.sig_len); } } } void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); esp_wifi_set_promiscuous(true); esp_wifi_set_promiscuous_rx_cb(&sniffCallback); esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE); Serial.println("Start listening on channel 1..."); } void loop() { delay(1000); }这段代码做的事情很简单:把ESP32设为STA模式(不需要连接任何网络),开Promiscuous,注册回调,然后把射频调到信道1。每次收到一个802.11管理帧,串口就会打印RSSI、信道和帧长度。代码里注释的typeSubtype变量暂时没用,但后续解析帧类型时你会经常用到。
注意:esp_wifi_set_promiscuous(true)在Arduino环境下必须放在WiFi.mode(WIFI_STA)之后,否则驱动还没初始化就调用会直接报错或复位。另外,信道设置要在打开混杂模式之后。如果你想扫所有信道,可以在loop()里每隔几百毫秒切换信道,比如1到13循环,但切换信道会丢掉切换瞬间的数据。实际项目里多采用“固定信道采集中”的方式,需要全信道扫描时再做周期切换。
3.2 用ESP-IDF实现更精细的控制
Arduino适合验证,真要做一个长期运行的监听节点,我更推荐ESP-IDF,因为你能直接控制WiFi过滤器和原始数据流。同样打开Promiscuous,但IDF里还能设置帧过滤器,让硬件只上报某种类型的帧,显著降低中断频率和内存占用。
关键函数是esp_wifi_set_promiscuous_filter。举个例子,如果只关心Beacon帧和管理帧,可以这样写:
#include "esp_wifi.h" #include "esp_event.h" static void sniffer_cb(void* buf, wifi_promiscuous_pkt_type_t type) { // 同Arduino回调逻辑,这里直接处理buf } void app_main(void) { nvs_flash_init(); esp_netif_init(); esp_event_loop_create_default(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); esp_wifi_set_mode(WIFI_MODE_STA); wifi_promiscuous_filter_t filter = { .filter_mask = WIFI_PROMIS_FILTER_MGMT }; esp_wifi_set_promiscuous_filter(&filter); esp_wifi_set_promiscuous_rx_cb(sniffer_cb); esp_wifi_set_promiscuous(true); esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE); }这里的WIFI_PROMIS_FILTER_MGMT表示只接收管理帧(Beacon、Probe Request/Response等)。如果你还关心数据帧,可以改成WIFI_PROMIS_FILTER_MGMT | WIFI_PROMIS_FILTER_DATA,但你没法解密加密数据帧的有效载荷,能拿到的主要是帧头和RSSI,信息量已经足够。
从驱动层面看,帧回调是在WiFi任务上下文中执行的,频率很高时会在很短时间内被连续调用。回调里不要做任何耗时的操作,比如printf到串口当数据量大时会卡死中断流程。正确做法是把帧头拷贝到环形缓冲区,再让另一个任务慢慢解析。我推荐直接用FreeRTOS的Queue或Ringbuffer,IDF里自带ringbuf组件,非常合适。
这是我踩过的最大的坑:第一次用Arduino快速测试时,串口打印一多,整个芯片直接崩溃。后来把所有打印全部去掉,只在回调里拷贝关键字段,问题立刻消失。监听频率高时,一秒钟可能收到几百上千帧,你必须在“采集完整数据”和“保证系统稳定”之间做取舍。
4. 从“听到”到“读懂”:解析802.11帧里的信息
打开监听模式只是第一步,“听”到的东西只是原始字节流,像收音机收到的杂音,必须解调出内容才有价值。802.11帧结构比TCP/IP复杂一些,但对我们有用的主要是管理帧。管理帧里最典型的就是Beacon和Probe Request。本节教你怎么把帧头解析成能看懂的信息。
802.11帧最前面的两个字节是Frame Control,其中低两位Bit0-1表示Protocol Version,固定为0;Bit2-3表示Type:00是管理帧、01是控制帧、10是数据帧。管理帧的Subtype在Bit4-7:0x08是Beacon、0x04是Probe Request、0x05是Probe Response。判断帧类型时,先读字节0,再按位与0x0C取出Type。后续的字段比如Duration、Address1(目的地MAC)、Address2(源MAC)、Address3(BSSID),都在固定偏移上。我的经验是不要自己逐字节算,直接用结构体偏移或解析库,比如esp_wifi.h里不直接给完整帧结构,网上有现成的802.11解析代码可以借鉴,自己写也很快。
4.1 Beacon帧与隐藏网络
先说Beacon帧,它由AP周期性广播,相当于无线网络的“心跳”。Beacon帧里包含SSID、支持的速率、信道、加密方式等信息。监听模式下,我们在回调里拿到pkt->payload,指向802.11帧的起始位置。Beacon帧的Header长度是24字节,紧跟的是Fixed Parameters(12字节),之后是Tagged Parameters。SSID就藏在Tag 0里面:Tag编号0表示SSID,后面1字节是长度,再后面是SSID字符。
提取SSID的C代码思路如下:
uint8_t* tag = frame + 36; // 跳过Header和Fixed Parameters uint8_t tagLen = tag[1]; if (tag[0] == 0 && tagLen > 0) { char ssid[33] = {0}; memcpy(ssid, &tag[2], tagLen); // 此时ssid就是可见SSID }如果你扫描到一个“空的SSID”,也就是Beacon帧里Tag 0长度为0,那说明这是一个隐藏网络。很多人以为隐藏网络是“完全消失”,实际上它只是对普通设备隐藏了SSID,但Beacon帧还是会周期性广播,只是SSID字段留空。ESP32照样能看到它的BSSID(MAC地址)、信道、加密方式、甚至信号强度。隐藏更多是心理安慰,做不到真正隐身。
解析Beacon帧还有一个应用:统计周边AP数量、信道分布、品牌指纹。MAC地址前三个字节称为OUI,代表网卡厂商。通过OUI可以粗略判断AP是TP-Link、小米、华为还是别的品牌。这对调试家庭网络非常有用——你能发现隔壁的AP在哪个信道、信号多强,从而选一个干扰最小的信道。不过要注意,新版WiFi标准里很多设备开启了MAC随机化,OUI分析只能用于评估,不要过度依赖。
4.2 Probe Request:低成本人员活动探测
Probe Request是客户端(手机、电脑)主动发出的探测帧,用来询问周围有哪些AP可连接。手机即使没有连接任何WiFi,只要WiFi开关处于打开状态,就会周期性发送Probe Request。这个特性让Probe Request成为“被动人流感知”的主力信号源。
与Beacon帧不同,Probe Request的帧体里通常带一个可选的SSID字段。如果手机曾经连接过某个叫“CoffeeShop”的网络,它可能每隔几十秒发送一个Probe Request (SSID=CoffeeShop),告诉对应的AP“我来了,快应答”。这个数据从隐私角度很敏感,我们能从监听到的帧里看到可能性网络名称,所以必须只做统计用途,不要把历史SSID内容落地归档或公开。
一个更实用、更隐私友好的做法是只统计“Probe Request帧的数量”,不去解析里面的SSID。毕竟我们的目的是判断环境中有没有“活跃的WiFi设备”。结合RSSI变化,甚至能大致判断设备是在靠近还是远离。我在一个10平方米的测试间里放了一块ESP32,人体静止坐着时,周围设备(手机+手表)每分钟产生的Probe Request数量基本稳定在2到4条;人走动说话时,数量会跳到10条以上。这个波动可以用来做活动检测,但需结合时间段滤波,防止误报。
解析Probe Request时,还要注意很多现代Android手机和iOS设备都启用了MAC随机化,每次广播可能用不同的MAC地址,所以单纯用MAC去重会严重高估设备数量。业界常用的做法是提取帧中的“信息元素”做指纹,或者干脆只用信号活跃度做趋势统计,不做计数。这个经验对于做人员计数的朋友很重要。
5. 蓝牙也是一条并行通路
说完WiFi,再聊同频段另一条“通路”:BLE。ESP32内部WiFi和BLE共用2.4GHz射频前端,但协议栈层是双栈独立的。也就是说,你可以在同一块芯片上,一边用Promiscuous模式监听WiFi,一边用BLE扫描接收广播包。两条通路同时开着,互相会有一定干扰,但通过共存仲裁,大部分场景下都能正常工作。这相当于把一块ESP32变成了一个“2.4GHz全频段环境感知无线电”。
BLE的工作方式更有意思。它不像WiFi有十几个信道,BLE在2.4GHz里专门划出40个信道(0到39),其中3个是广播信道:37(2402MHz)、38(2426MHz)、39(2480MHz),其余37个为数据信道。所有BLE广播设备只在广播信道上发广播包,扫描器只需要轮流在这三个信道上监听,就能拿到几乎所有广播数据。这个机制比WiFi的Beacon监听更简单高效。
5.1 BLE广播信道与观察者模式
BLE扫描在官方文档里叫“Observer Role”(观察者模式),这个名字很贴切:只观察,不参与。不是主设备,也不是从设备,只是被动接收广播。在上层API里,esp_ble_gap_start_scanning配合esp_ble_gap_set_scan_params就能开始扫描。但默认扫描是“扫到设备就回调一次”,更像“发现”,而我们想要的是“持续接收所有广播包”,所以需要把扫描参数里的scan_duplicate设为DUPLICATE_DISABLE,关闭去重逻辑。
典型ESP-IDF代码片段:
esp_ble_scan_params_t scan_params = { .scan_type = BLE_SCAN_TYPE_PASSIVE, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, .scan_interval = 100, .scan_window = 99, .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE }; esp_ble_gap_set_scan_params(&scan_params); esp_ble_gap_start_scanning(0);这里scan_type必须是BLE_SCAN_TYPE_PASSIVE,表示被动扫描,我们不主动发送扫描请求,只接收广播包。主动扫描(BLE_SCAN_TYPE_ACTIVE)会向广播者发送扫描请求,虽然能获得更多信息,但会向环境中暴露自己的存在。在环境感知场景里,尽量用被动扫描,低调且合规。
回调里会拿到esp_ble_gap_cb_param_t结构,其中包含广播者的MAC地址、RSSI、广播数据。广播数据是强类型结构,前两字节是长度和类型,后面是载荷。比如类型0x01是Flags,0xFF是Manufacturer Specific Data,很多温湿度传感器会把温度湿度塞在厂商自定义字段里。解析这些字段,就能直接获得传感器数据。当然,不同厂商格式各异,需要对着DataSheet或者抓包分析。我建议先抓几组原始数据存到串口,人工对比后总结规律。
5.2 双频段双通路协同
有人问,既然WiFi和BLE共用射频,能同时监听吗?答案是能,但有代价。ESP32的WiFi和BLE协议栈通过一个称为“Coexistence”的机制协调射频使用权,两者不能真正同时发送,但可以交替使用,时间片轮转。监听场景下我们几乎不发送数据,主要是接收,所以冲突比发送小得多。实测在ESP32上同时开WiFi Promiscuous和BLE Observer,仍能稳定收到两边的包,但会丢失少量WiFi帧,尤其是信道繁忙时。
我的做法是给两块功能分配优先级。如果当前任务更关注BLE传感器数据,就把WiFi监听只放在信道较空闲的某几个固定信道,减少频繁切换;反过来如果重点在WiFi环境分析,则降低BLE扫描占空比(调大scan_interval,调小scan_window)。ESP32在射频调度上做得不错,但不要指望它像专门的SDR那样毫无遗漏。
协同监听的典型应用是室内定位标签。给每个人发一个BLE防丢器,它在广播包中带有自身ID;同时ESP32监听周围WiFi信号,建立一个“WiFi基线”。当一个人带着防丢器走进房间,ESP32立刻收到BLE广播,同时观察到周边环境WiFi信号强度变化,两个信号一综合,比单靠BLE更可靠,因为WiFi信号能提供背景参考。当然,这只是思路,具体区分还得靠算法。
另一个实际用途是调试蓝牙耳机干扰。耳机走的是蓝牙,手机可能走WiFi,两者都挤在2.4GHz。同时监听两个协议栈,你就能看到“WiFi信道上某个时刻数据包大增,紧接着BLE广播包丢失”这样的因果数据。通过这种实验,我找到过智能音箱在WiFi 5G不起作用时总是掉线的真正原因——2.4GHz信道被邻居的USB 3.0设备严重干扰。这种跨协议的诊断能力,在传统网络工具里非常少见。
6. 常见问题与避坑清单
玩这套操作时,我几乎把能踩的坑都踩了一遍。下面按“越想越气”的顺序列出来,每个背后都是一段折腾史。还是老规矩,先讲症状,再给解法。
6.1 为什么收不到任何数据包
最诡异的现象是:代码完全按照官方说明写,回调却一次都不触发。排查顺序很重要。第一件事查信道:ESP32不同版本的默认国家码可能影响可用的信道。中国大陆支持信道1到13,如果代码里设置14信道,会直接失败或收不到数据。解决办法是把esp_wifi_set_country明确设为ESP_WIFI_COUNTRY_CN,或直接选信道1到11最保守。
第二件事查“是不是没开Promiscuous”。Arduino环境中,esp_wifi_set_promiscuous(true)和esp_wifi_set_promiscuous_rx_cb的调用顺序有讲究,必须先开Promiscuous再设置回调,顺序反了回调指针不被记录。IDF里没这个问题,但Arduino封装确实有人踩过。第三件事是天线和位置。ESP32模块的天线非常小,增益有限,放在金属盒子里、靠近USB线缆,都可能让灵敏度急剧下降。我在铁皮配电箱旁边测试,几乎收不到任何Beacon,把板子移到箱子外10厘米立刻恢复。别高估ESP32的接收能力,它毕竟不是专业接收机。
第四件事是回调里是不是Block住了。如果回调里用了delay、Serial.print大量输出、malloc,很可能导致WiFi驱动任务看门狗复位。此时现象是“刚开始能打印几条,随后芯片重启”。用串口监视器看,会看到“Guru Meditation Error”或“Task watchdog timeout”。解决方案是把回调里的操作全部简化,只拷贝头部字段到全局结构,解析放到loop()里。
6.2 信号数据可信度与校准
RSSI是个好东西,但别把它当成精确的距离测量。RSSI的单位是dBm,受发射功率、天线方向、遮挡物、多径效应影响极大。同一块板子,人站在旁边和站在三米外,RSSI可能只差4到5dB,而墙壁一隔就是15dB。所以,“用RSSI判断距离”在室内基本不靠谱,更适合判断“相对强弱”。
我做过最基础的校准实验:把ESP32放在固定位置,用手机靠近和远离,记录RSSI分布。手机在0.5米时RSSI约-42dBm,2米时约-55dBm,5米时约-66dBm,隔一堵墙后约-78dBm。这些数据只能作为参考,换一台手机,发射功率不同,结果就会变化。因此实际项目中,我更倾向于用“RSSI变化趋势”而不是绝对值,比如“过去一分钟RSSI均值上升了10dB”表示有人靠近。
还要注意干扰。2.4GHz频段非常拥挤,微波炉、USB 3.0、无线鼠标都会产生突发噪声,导致RSSI瞬时跳动。滤波是必须的,简单移动平均就够,窗口取5到10个数据点。更讲究一点可以用中值滤波,能有效消除离群尖峰。这些数据处理经验,比盲目追求“距离精度”更有价值。
6.3 合规性与道德边界
这块必须单独说,不能含糊。ESP32监听能力确实强大,但不代表可以乱用。先说法律底线:在绝大多数地区,未经授权对加密通信内容进行解密、还原,属于违法行为。ESP32的Promiscuous Mode接收到的加密数据帧,你理论上可以抓到密文,但没有密钥破解不了,如果你去跑字典攻击或注入包去试探,性质就完全不同了。所以,正经玩法只限于分析明文管理帧、Beacon、广播包,以及统计RSSI等元数据。
第二个边界是隐私。Probe Request里可能携带手机历史连接过的SSID,BLE广播里可能有MAC地址甚至自定义数据。做技术研究时,不要把这些信息收集到数据库里长期保存,更不要写成文章发布。合理做法是只提取“信号存在性”“帧速率”“RSSI”这类聚合特征。比如我做的环境感知方案,只在内存里保留最近30秒的帧数,数据处理完就丢弃,不回传原始MAC。
第三个边界是“不要用别人网络做测试”。如果你在一个商场、办公室、别人家里部署监听设备,务必提前告知场所管理方,并遵守当地无线电管理规定。个人在家调试自己的设备完全没问题,但把ESP32放到公共场所长时间采集信号,就可能涉及隐私合规风险。我的建议是:做成教学演示设备时,限制扫描时长,只统计数量,不存储任何能关联到个人身份的字段。
说到底,ESP32这条“隐藏无线电通路”最有价值的用途,是让我们用低成本、低门槛的方式理解电磁环境。它适合做环境监测、信道诊断、存在感知、物联网网关,而不是去窥探别人。把它用在正道上,你会发现这颗芯片比你想象中聪明得多。每次听到回调里新帧到来的那一刻,都像多了一只藏在空气中的耳朵,而且这耳朵,手册上还真没明说怎么用。