那些藏在墙角里的“听风者”——一个WiFi探针系统的技术自白

📅 2026/7/14 14:29:00
那些藏在墙角里的“听风者”——一个WiFi探针系统的技术自白
大家好我叫“WiFi探针系统”。别被这名字唬住其实我就是四个指甲盖大小的ESP8266电路板分别蹲在XX大学图书馆的四个角落里干着一件特别“八卦”的事情——偷听手机们的悄悄话。我知道你肯定会问手机们能有什么悄悄话嘿多了去了。你每打开一次WiFi开关你的手机就像个找不到家的孩子嗷嗷喊着“有WiFi吗有WiFi吗”——这句喊话在802.11协议里有个正经名字叫Probe Request帧。而我干的事情就是把这些喊话记下来谁喊的MAC地址、喊多大声RSSI信号强度、朝谁喊的BSSID目标地址。就这么简单对就这么简单。但要把这点简单的事儿干利索背后的故事可不少。一、硬件篇一个“穷鬼”的自我修养先说说我的“身体”吧。四个ESP8266 ESP01S模块单价不到15块钱。有人可能会撇嘴这么便宜能靠谱吗说实话确实不太靠谱。首先这玩意儿内存只有80KB。80KB是什么概念你手机里随便一张照片都比它大几十倍。就这么点内存我还要同时干两件事一是开着混杂模式抓包——说白了就是把耳朵竖起来听所有经过的WiFi信号不管是不是喊给我的二是连上WiFi把数据传到服务器。问题来了ESP8266在混杂模式下没法连WiFi。这不是bug是硬件层面的“生理缺陷”——就像你不能同时吃饭和唱歌一样。怎么办我们设计了一套“轮班制”采集30秒 → 关掉混杂模式 → 连上WiFi → 把攒的20条数据发出去 → 断开WiFi → 重新开混杂模式整个过程像极了打工人的日常埋头干活采集→ 抬头交差发送→ 再埋头干活。一个循环大概35秒数据延迟就这么来的。没办法穷有穷的活法。二、数据篇33个字节的“极限压缩”攒下来的数据怎么传我们没选JSON——那玩意儿虽然人类看得懂但太啰嗦了。一条记录动不动上百字节对于我这种每次只能连几分钟WiFi的“穷鬼”来说太奢侈了。我们设计了一个33字节的二进制协议。来我给你拆开看看位置内容大小说明第0字节RSSI1字节信号强度负值越大表示离得越近第1-6字节MAC地址6字节设备的“身份证号”第7-12字节BSSID6字节目标AP地址全F表示设备在“广播式扫描”第13-16字节时间戳4字节Unix时间精确到秒第17字节信道1字节当前WiFi信道1-13第20字节估算距离1字节根据RSSI粗算的距离第25字节连接状态1字节0扫描中1已连接33个字节把该记的都记了。你说这算不算“螺蛳壳里做道场”对了这里有个特别有意思的细节——BSSID字段。如果BSSID是FF:FF:FF:FF:FF:FF说明设备正在“广播式扫描”也就是还没连上任何WiFi像个无头苍蝇一样到处问“谁家有网”。如果BSSID是个具体的MAC地址说明设备已经连上某个AP了只是偶尔还发个探测帧刷存在感。这个区别很重要。因为已连接设备用的往往是真实MAC地址而那些还在扫描的设备很可能用了随机MAC——这事儿我们后面再聊。三、信道篇一个“陀螺”的自我修养WiFi有13个信道手机可能在任何信道上喊话。我只有一双耳朵一次只能听一个信道。怎么办轮巡。每500毫秒切一次信道1→2→3→...→13→1→2→...像个不知疲倦的陀螺。这方法笨但有效。不过也有翻车的时候——高峰期某些信道流量特别大500毫秒根本不够用数据就丢了。后来我们改成了“流量自适应”某个信道如果连续几次都抓不到东西就少待会儿反之就多待会儿。效果嘛提升了大概5%的捕获率——折腾了好几天就提了5%。写论文的时候我就在想这事儿到底值不值得写后来想通了“做了但效果一般”也是经验总比假装没做过强。四、定位篇加权质心的“算术魔法”好数据有了——四个节点分别报告了某个设备的RSSI值。接下来就是算位置了。我们用了一个叫加权质心的算法。名字听着唬人原理特别简单把每个节点的RSSI换算成“权重”信号越强权重越大然后拿四个节点的坐标做加权平均。公式长这样P Σ(w_i × P_i) / Σ(w_i)其中w_i 10^(RSSI_i / 10)就是把dBm换算成毫瓦。说白了就是谁的信号强设备就大概率在谁附近。这法子简单粗暴但也有硬伤——RSSI太不靠谱了。你站在同一个地方不动RSSI能上下波动10个dBm。为什么因为人走来走去会遮挡信号墙壁会反射信号形成多径效应甚至空气湿度都能影响一点点。所以我们从来不说“定位精度1米”这种大话。实测下来开阔大厅平均误差1.8米书架之间能干到3.5米最差一次6米。结论是找楼层没问题找座位别指望。为了对抗RSSI的“情绪波动”我们还加了个滑动窗口平滑——把最近5次定位结果取平均。稳定性提升了40%代价是延迟多了3秒。这买卖划算。五、去重篇MAC地址的“5秒记忆”同一个设备可能每秒发好几个Probe Request要是不去重数据就爆炸了。我们的去重策略很朴素维护一个MAC地址缓存5秒内重复出现的MAC直接忽略。bool is_duplicate(uint8_t *mac) { for (int i 0; i CACHE_SIZE; i) { if (memcmp(mac_cache[i].mac, mac, 6) 0) { if (now - mac_cache[i].timestamp 5000) { return true; // 5秒内重复扔掉 } } } // 新MAC记下来 }就这么几行代码能把数据量砍掉70%以上。有时候解决问题的办法就是这么朴实无华。六、随机化篇当“身份证”变成了“马甲”前面说过MAC地址是设备的“身份证”。但自从Android 10和iOS 14开始手机们在扫描WiFi时会随机生成MAC地址。也就是说同一部手机每隔5-10分钟就换一件“马甲”。这对我们来说是降维打击。本来想追踪“张三”在图书馆待了多久结果“张三”待了6分钟变成了“李四”“李四”又待了8分钟变成了“王五”。你还追个啥我们一开始很沮丧。后来想通了——我们到底要追“人”还是追“数”如果是追“数”——这层楼有多少设备——那随机化根本不是问题。不管马甲怎么换总数就在那里。我们把每个随机MAC都当做一个独立的“观测样本”统计总数和变化趋势。放弃个体追踪拥抱群体统计。这叫“因祸得福”——既规避了隐私风险又简化了问题。七、行为篇占座与滞留的“硬规则”有了位置数据就能做点“聪明”的事情了。占座检测的逻辑特别简单如果某个设备在某片区域连续出现超过30分钟而且RSSI几乎没怎么变说明设备没怎么移动就触发“占座疑似”告警。闭馆滞留检测更简单系统时间过了22:30定位结果还在馆内的设备统统告警。这两个逻辑硬邦邦的没有任何机器学习没有任何深度学习就是if-else。你可能会说这也太糙了吧确实糙。有一次系统在23:00告警“有人滞留”管理员跑过去一看——保洁阿姨的手机搁窗台上充电呢。手机不认识“阿姨”只认识“信号”它就老老实实报告“有设备”。这事儿告诉我们任何自动化系统都需要人工复核别指望机器替你搞定一切。八、总结不完美的实用主义回过头来看这套系统毛病一大堆分时复用导致35秒延迟RSSI抖动让定位精度飘忽不定MAC随机化让个体追踪成了笑话占座检测会把充电的手机当成“占座党”但你猜怎么着图书馆管理员老李用得很开心。因为他不需要知道“张三在哪个座位”他只需要知道“二楼现在人多不多要不要加椅子”他不需要精准到厘米他只需要知道“闭馆了还有没有人没走”。这就是我想说的技术不是为了完美是为了解决问题。用15块钱的东西、33字节的协议、加权平均的算法去回答一个管理学的核心问题——“这层楼到底有多少人”——这事儿我觉得挺值的。至于那些不完美的地方留给下一版吧。毕竟工程的真谛不是消灭所有bug而是让bug都变得可控。本文技术内容基于XX大学图书馆4节点WiFi探针系统的实际部署经验数据真实可溯。欢迎复现更欢迎吐槽。