☰
ESP32-Paxcounter:用WiFi和BLE被动扫描实现低成本客流统计
2026/10/7 14:48:40 网站建设 项目流程

如果你开了一家店、负责一个展位,或者单纯想搞明白会议室一天里到底有多少人进出,又不愿意架摄像头、不想碰人脸识别,那cyberman54/ESP32-Paxcounter这个开源项目会是个很对胃口的方案。它用一块几十块钱的 ESP32 开发板,靠 WiFi 和蓝牙的被动扫描,把周围手机、手环发出的无线广播帧统计成一条"实时流量曲线"。项目基于 ESP-IDF 编写,不依赖云平台,数据可以走串口、OLED 屏,也能通过 LoRaWAN 传到远端服务器。这篇文章适合准备做客流统计、空间占用检测或者环境人数监测的开发者,我会从原理、硬件、配置到实际部署一路讲下去,顺带把我在现场踩过的坑也交代清楚。

1. 先搞清 Paxcounter 的原理:它靠什么统计人数

1.1 为什么不是摄像头方案,而是无线嗅探

常见的人数统计方案大概有三类:红外对射、摄像头加视觉算法、以及 WiFi 探针。红外对射只能统计单通道穿越,两个人并排走就会漏;摄像头方案成本高,还要考虑隐私和存储;WiFi 探针则是很多商场和连锁店已经用过的老办法,核心在于手机在后台会不断寻找可连接的 WiFi 热点,这个过程会主动向外发送一种叫 Probe Request 的报文,里面带有设备自身的 MAC 地址。

Paxcounter 的思路就是让 ESP32 当一个迷你版 WiFi 探针,同时再把 BLE 蓝牙广播也一起收进来。它不连接任何设备,也不解析除了 MAC 和信号强度以外的内容,只是在某个信道上监听空气里的广播帧,然后对设备 MAC 去重,算出"这一时刻我看到了多少个无线设备"。项目名称里 Paxcounter 直译过来就是"乘客计数器",最早确实带着公共交通场景的影子,不过用在店铺客流、展会人流、工位占用检测上也非常合适。

这种方案最大的优点是便宜、安装简单、没有摄像头那种隐私压力。缺点也很明显:手机 MAC 随机化会带来计数波动,它本质上是一个相对趋势统计器,不是航空级精确计数器。理解了这一点,后面调参和看数据的时候就不会心态崩坏。

1.2 ESP32 如何实现"被动扫描"

ESP32 的 WiFi 芯片可以被配置成混杂模式(promiscuous mode),意思是网卡不管收到的帧是不是发给自己的,只要在同一个信道里就全部接住。Paxcounter 在 ESP-IDF 里调用esp_wifi_set_promiscuous(true),然后注册一个 sniffer 回调函数,每收到一帧就解析出 IEEE 802.11 MAC 头里的发送者地址和信号强度 RSSI,再丢进计数模块。

蓝牙侧用的是 BLE 观察者模式,ESP32 的蓝牙控制器会接收周围设备主动广播的 ADV 数据包。现在几乎所有人身上的手机、手表、手环、TWS 耳机都在以不同频率向外广播,广播包里同样带 MAC 地址。两侧数据汇合之后,会进入一个按时间窗口划分的去重结构,同一个时间片内同一个 MAC 只保留一次。输出的时候,你拿到的就是类似timestamp, wifi_devices, ble_devices, total这样的结构。

这里有个概念容易混淆:WiFi 探针和 BLE 广播不是一回事。WiFi 探针是手机主动找网络时发出的,而 BLE 广播是设备周期性向外宣告自己存在。前者更依赖手机厂商和当前网络状态,后者更像是"我在这里"的小广告。Paxcounter 把两者都抓下来,互补性很强,尤其在手机不主动扫 WiFi 的时间段,BLE 广播还能继续补数据。

1.3 为什么选 ESP32 而不是树莓派或 Arduino

很多人会问:树莓派也能做嗅探,为什么用 ESP32?答案其实很直白:树莓派贵、功耗高、启动时间长,放在店门口长期运行不划算。Arduino Uno 没有 WiFi 也没有蓝牙,得外挂一个 ESP8266 或单独的蓝牙模块,绕一圈回来还不如直接用 ESP32。

ESP32 集成 2.4GHz WiFi 和双模蓝牙,官方 ESP-IDF 提供了完整混杂模式接口,价格不到一杯咖啡,Flash 和 RAM 也足够跑去重逻辑。更关键的是它能耗可控,ESP32 可以在扫描间隙进入 light sleep,用电池或者太阳能板也有机会支撑长期部署。如果要做远距离数据传输,还可以在 SPI 总线上接一片 SX1276 LoRa 模块,把统计结果发送到几公里外的网关,这也是 Pico 或 Arduino 生态里很少能一口气给你集成完整方案的地方。

2. 动手前准备:硬件清单与 ESP-IDF 开发环境

2.1 硬件选型:从 ESP32 开发板到 LoRa/GPS 模块

先给一份我在实际项目中验证过的硬件清单。基础款不需要 LoRa,只需要一块 ESP32 开发板和一个串口调试器。如果后面要走无线远程上报,再考虑扩展模块。

部件建议规格用途
ESP32 开发板ESP32-WROOM-32,4MB Flash,推荐板载天线或不带天线的型号运行 Paxcounter 主程序
LoRa 扩展SX1276/SX1278,频率根据当地频段选择 433/470/868/915 MHz远程数据上报
OLED 屏SSD1306,0.96 英寸或 1.3 英寸,I2C 接口现场直接看实时人数
GPS 模块可选,串口输出 NMEA移动场景下的时间同步
电池方案18650 锂电池,TP4056 充电板,LDO 稳压无市电环境部署
外置天线2.4GHz 外置棒状天线或玻璃钢天线,带 IPEX 转接头提高扫描灵敏度

选开发板时特别注意两点:一是尽量选板载 USB 转串口芯片为 CP2102 或 CH340 的常见型号,驱动好找,烧录稳定;二是如果计划长期固定安装,最好不要用带一堆排针和跳线的裸板,直接选带外壳或自己打一个 ABS 盒子更省心。Paxcounter 对 Flash 要求不高,4MB 够用,但别买那些只有 1MB Flash 的老旧模组,编译后固件加上配置分区容易放不下。

散热和天线布局很容易被忽略。ESP32 全开扫描时射频前端和 PA 会发热,放在密闭金属盒里温度会很高,最后导致频繁重启。外壳尽量用塑料或带散热孔的铝盒,天线位置要保证辐射方向朝向人流通道。

2.2 搭建 ESP-IDF 环境:为什么不能用 Arduino IDE

很多新手第一次接触这个项目会问:能不能用 Arduino IDE 加离线包直接编译?答案是不建议,而且官方也不支持。Paxcounter 用到了 ESP-IDF 里的 WiFi promiscuous 模式、蓝牙 Controller 的 observer 能力、低功耗管理、以及 LoRaWAN 协议栈,这些在 Arduino core 里要么没有封装,要么封装得不完整。你用 Arduino IDE 强行改造,会遇到大量底层 API 缺失和编译错误,最后还是要回到 ESP-IDF。

推荐的开发环境是 ESP-IDF v5.x,支持 Windows、Linux 和 macOS。拉取和编译流程大致是安装工具链,然后idf.py set-target esp32,再idf.py build。项目仓库里有完整的子模块依赖,拉代码时记得带上--recursive参数,否则components下面会缺一堆东西。

如果以前没碰过 ESP-IDF,建议先跑一个通用的 Hello World 例程,确认自己的编译链和开发板通信正常,再开始编译 Paxcounter。这样能把"环境问题"和"项目问题"分开,后面排错会少很多。

2.3 编译提速与离线包使用经验

ESP-IDF 首次编译要下载工具链和 Python 依赖,几 GB 的下载量很常见。Windows 上编译慢,多半是杀毒软件实时扫描占用了 IO,或者工具链目录放在 OneDrive、同步盘这种会频繁同步的路径下。我的做法是把整个 ESP-IDF 和项目放在纯英文路径的本地 SSD,加入杀毒软件白名单,然后开启 ccache。

如果网络下载不稳定,可以尝试在白天网络空闲时段慢慢把依赖拉完,或者找国内代码托管平台的镜像。PlatformIO 用户也有离线包方案:先在能联网的机器上把platformio/espressif32平台和工具链包预下载好,然后复制到目标机器的~/.platformio/packages目录,之后离线编译就不会反复卡下载。

编译命令可以指定并行度:idf.py build -j8会比默认快不少。不过并行度也不是越高越好,内存小的机器可能编译到一半 OOM。我的经验是 8GB 内存的笔记本用-j4到-j6比较稳,16GB 内存可以用-j8。

3. 核心配置逐项拆解:让计数器按你的场景工作

3.1 配置文件与菜单配置项

Paxcounter 把大量参数放到了 Kconfig 里,也就是用idf.py menuconfig打开的那个图形配置界面。你可以通过搜索PAXCOUNTER直接把所有相关项过滤出来。重点看这几类:WiFi 扫描开关、BLE 扫描开关、扫描周期和休眠周期、RSSI 阈值、去重时间窗口、以及 LoRaWAN 相关配置。

不同场景推荐配置差异很大。如果做展会门口的人流趋势统计,WiFi 和 BLE 都要开,扫描周期短一点,5 到 10 秒扫一轮,RSSI 阈值设在 -75 dBm 左右。如果做办公室工位占用检测,设备离人非常近,扫描周期可以长到 30 秒,RSSI 阈值提高一些,避免把隔着走廊的人也算进来。

这里不推荐照搬任何默认参数。先以默认配置编译刷机,跑一天看原始数据,再做针对性调整。每一次改动只需要重新idf.py build flash monitor,整个流程几分钟就能完成,调参成本很低。

3.2 去重与 RSSI 阈值:数值虚高怎么压

Paxcounter 统计的是"当前哪些设备在附近",而不是"今天总共有多少人来过"。所以你会看到同一个 MAC 在多个时间片里反复出现,这没问题,因为它每个时间窗口内做了一次去重。但手机厂商这些年推行 MAC 随机化:手机在发出 Probe Request 时,可能每次都用不同 MAC,这就导致同一个真实的人被当成好几个设备。

实际解决思路有三个。第一,合理设置 RSSI 阈值,只统计信号较强的设备,过滤掉穿过玻璃墙的路人。第二,控制去重时间窗口。窗口太短,设备挪动一下就被重复计;窗口太长,短暂停留的人会被漏掉。第三,把 Paxcounter 当趋势指标用,不要纠结单次绝对值。我在办公室实测时发现,更稳定的做法是统计一个时间段内的平均可见设备数,而不是某个秒级瞬间的最大值。

我见过不少部署翻车场景,都是因为把阈值调得过于激进。RSSI 阈值设在 -90 dBm,结果把隔壁店的人和街上路过的车都算进来了,数值直接爆表。建议先用默认值跑一天,用串口记录原始 RSSI 分布,再决定砍到多少。

3.3 省电与扫描节奏:长期部署的关键

如果你打算用电池供电,扫描节奏就是整个项目里最需要算计的地方。ESP32 在 WiFi 和蓝牙同时扫描时,电流很容易跑到 150 到 200mA,连续扫一天,18650 电池也撑不了太久。但扫描本身又是 Paxcounter 的灵魂,没法省掉,所以只能靠"间歇性扫描"来降功耗。

一个常见的节奏是:扫描 10 秒,休眠 50 秒,这样平均电流能降到 20 到 30mA,配合 3000mAh 电池,理论续航能到几天甚至一周以上,具体取决于休眠配置和模块静态功耗。ESP32 在扫描间隙可以进入 light sleep,但注意 WiFi 混杂模式和 BLE 观察者模式都不支持深度睡眠,所以别指望一次睡到天荒地老。真要超低功耗,只能用定时器唤醒,醒来扫一阵再睡。

市电供电的部署反而要小心过热。连续 24 小时全速扫描,模块温度比我预想高不少,最后我给开发板加了个小散热片,温度和稳定性立刻好了。如果设备安装在户外或者阳光直射的地方,最好还是选择外置天线方案,把射频部分和发热元件分开。

3.4 数据上报方式:串口、OLED 与 LoRaWAN

Paxcounter 的默认输出非常朴素但实用:UART 串口以 CSV 格式打印每个统计周期的结果,大概包括时间戳、WiFi 设备数、BLE 设备数、总数等字段。你只需要用 USB 线连接电脑,或者用另一个单片机/USB 转串口模块去读,就能直接在终端看到实时数据。

现场需要直接看数字时,可以开 OLED 显示。SSD1306 通过 I2C 接到 ESP32,配置好引脚之后,屏幕上会滚动显示当前计数和累计计数,非常适合放在店门口或者展位内侧,不需要额外显示器。OLED 的刷新频率不高,主要起到现场可视化的作用。

远程部署或者数据要进云端的话,LoRaWAN 是官方的首选方案。接上 SX1276 后,可以在 menuconfig 里填入 OTAA 或 ABP 参数。LoRaWAN 的优点是功耗低、距离远,城市环境里到网关几百米到几公里都算正常。缺点是配置比串口麻烦,网络里需要网关、服务器和 payload 解析器。我建议前期调试阶段先用串口把计数逻辑跑稳,再把 LoRa 加进来,不然问题叠加起来,你根本分不清是计数出错还是网络链路出错。

4. 实操记录:从拉取代码到上线部署

4.1 拉取项目并配置目标芯片

先从仓库拉最新稳定代码,注意带上子模块:

git clone --recursive https://github.com/cyberman54/ESP32-Paxcounter.git cd ESP32-Paxcounter idf.py set-target esp32

然后打开配置菜单:

idf.py menuconfig

在菜单里找到 Paxcounter 的配置项,按需调整。第一次建议保持所有默认选项,只用串口输出,等确认硬件没问题再扩展。如果编译报缺少子模块,多半是 clone 时漏了--recursive,在项目目录里补一句git submodule update --init --recursive即可。

4.2 编译、烧录与串口验证

配置完成后进入构建循环:

idf.py build idf.py -p /dev/ttyUSB0 flash monitor

Windows 下串口号一般是COM3或COM4,Linux 下常见/dev/ttyUSB0。如果开发板进入下载模式失败,先按住板上的 BOOT 键,再插 USB 或按复位键,很多时候能强制进入烧录状态。

启动后串口输出会先显示固件版本、设备 MAC 地址和当前配置摘要。正常情况下,几秒钟后就开始按设定周期打印计数数据。你可以拿一部手机,开关一次 WiFi,或者开启蓝牙,在开发板附近走动,观察数值变化。这里有个心态上的提醒:第一次测试数值偏低不代表设备坏了,因为手机在屏幕熄灭状态下不一定马上主动扫描。多切换几次手机 WiFi 开关,很容易看到计数器响应。

4.3 现场部署的摆放与天线细节

安装位置直接影响数据质量。我的建议是离地面 1.5 到 2 米,面向人流通道,周围不要有大型金属物体。如果开发板是 PCB 天线,天线方向要尽量对着人流来的方向,但不要让整块 PCB 紧贴墙面或者贴着铁架。实测下来,同样的板子放在塑料支架上和不加任何固定直接挂在铁皮配电箱旁边,计数差异能到 20% 以上,信号弱的人的设备直接就被 RSSI 阈值滤掉了。

多个 Paxcounter 同时部署时,保持设备间距 5 米以上,避免同一个人的手机同时被两台设备非常强地看到,造成数据冗余。你可能会觉得这又不是冲突,但现场做趋势分析时要维护多个点位,重复看到会让不同入口的数据相关性变得模糊。最好的方案是每个入口单独一台,后台再对总量做归一化和去重处理。

5. 常见问题与排查速查表

5.1 编译与刷写问题

我第一次编译 Paxcounter 时最常碰到的几个报错,基本都能归到环境问题。下面这个表可以帮你快速定位。

现象常见原因排查与解决
idf.py: command not found没有 source 环境脚本在 ESP-IDF 目录执行. ./export.sh或source export.sh
编译时找不到头文件子模块缺失git submodule update --init --recursive
烧录时报连接超时驱动没装或 BOOT 模式没进入换数据线,按住 BOOT 键再烧录
Windows 编译极慢杀毒软件扫描工具链目录把 ESP-IDF 和项目加入白名单,开启 ccache
编译到最后内存不足老版本 ESP-IDF 与项目不匹配切到官方要求的 ESP-IDF v5.x 分支

给新手一个额外建议:不要使用项目仓库的 master 分支直接跑生产,除非你已经很清楚自己在干什么。开发分支可能包含新功能,也意味着更频繁的接口变动,编译报错会多。找一个稳定的 release 版本,按官方文档推荐的方式拉取,能少走很多弯路。

5.2 计数异常排查

如果你发现设备跑起来了,但数据一直不对,先对照下面几种情况。

  • 一直为 0:先看启动日志里 WiFi 和 BLE 扫描有没有被开启,再确认 RSSI 阈值是不是设得太高。可以在 menuconfig 里把阈值临时调到很低,比如 -100 dBm,如果还是没有数据,就要怀疑天线和射频部分了。
  • 数值非常低:手机屏幕熄灭且 WiFi 关闭时,Paxcounter 能收到的信号本来就少。测试时记得把手机 WiFi 打开,或者让手机保持亮屏,模拟真实活跃用户。
  • 数值明显虚高:大概率是 MAC 随机化把同一个人算成了多个人,或者 RSSI 阈值太低,把外围信号都收进来了。逐步调高 RSSI 阈值,配合观察原始数据里的信号强度分布,会好很多。
  • 数据一直不稳定:附近 WiFi/蓝牙干扰严重。ESP32 支持把 WiFi 扫描限定到特定信道,比如 1、6、11 这三个常用信道中的一个,减少和其他无线路由器的串扰。

不要一上来就怀疑 LoRa 或 OLED 模块,先把串口输出的底层数据看明白,再往上一层排查。

5.3 LoRaWAN 入网/数据上云问题

LoRaWAN 配置出错是最容易让人抓狂的,因为很多参数需要在网关、网络服务器和设备端三处保持一致。最常见的坑包括:LoRa 模块频率和当地网关频率不匹配、SPI 引脚接错、OTAA 的 AppEUI/DevEUI/AppKey 填错、以及 payload 格式没有在服务器端做解析。

先确认硬件引脚。Paxcounter 默认对 SX1276 的 NSS、SCLK、MOSI、MISO、RST、DIO0 都是有定义和可以在 menuconfig 里改的。如果你用非默认的 LoRa 模块型号,务必对照模块原理图逐项检查,尤其是 DIO0 中断引脚,接错会导致发送事件永远不来。

LoRaWAN 入网时,建议先用一个已经跑通过的通用 LoRaWAN 示例代码验证模块硬件,再切回 Paxcounter。这样可以排除"模块本身是坏的还是配置问题"。Payload 解析在 TTN 或 ChirpStack 这类服务器上处理时,记得使用项目提供的 decoder 函数,而不是自己猜测字节序。

5.4 排障心得

最后聊几句我的实操心得。用 Paxcounter 做采集,最忌讳的是数据链路太长才排查。先串口,再 OLED,最后再上 LoRaWAN,这个顺序不要乱。LoRaWAN 一旦加入,数据流里多了一段无线传输,出错面立刻扩大,前期没调稳的话,问题会被层层掩盖。

我在现场测试时习惯先记录一段十余分钟的原始数据,观察信号强度波动,再去修改阈值和扫描周期。另一个好用的小技巧是,把手机固定放在测试点不动,开着屏幕,看计数器是否稳定为一个较小的波动值;再让人带着手机走动,看计数器是否明显上升。通过这种 A/B 测试,能快速确认部署方向和参数是否合理。

如果你是准备做一个长期运行的部署,建议每隔几天拉一次串口日志备份,同时记录设备温度和网络情况。这个项目本身很稳,但现场环境的复杂度远超实验室,定期回头看数据,才能在问题扩大之前发现端倪。合规方面,Paxcounter 只处理公开广播帧中的 MAC 和信号强度,不解析用户数据,但公共场所部署时还是要提前告知相关人员,给使用者留出知情权。我自己最深的感受是,别把它当高精度人口传感器,它更擅长告诉你"趋势发生了什么变化",这已经能解决很多实际问题了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询