基于ESP32的WiFi嗅探客流统计系统:低成本物联网实践
1. 项目概述与核心价值周末去朋友的奶茶店帮忙发现生意火爆时店员排班完全靠感觉要么人手不够忙得团团转要么客流低谷时店员闲着。这种粗放的管理方式在小型零售业里太常见了。为了解决这个问题我琢磨着能不能做个简单、低成本的客流统计工具。核心需求很明确实时、自动地统计进出店铺的人数变化并且成本要低部署要简单。传统的方案比如红外对射、摄像头视觉分析要么安装麻烦、有盲区要么涉及隐私、成本高。我想到现在几乎人人手机不离身而每台手机的WiFi模块都有一个全球唯一的MAC地址。当手机搜索或连接WiFi时会主动广播包含MAC地址的管理帧。如果我们能“听到”这些广播不就能知道附近有多少个独立的设备从而间接估算人数了吗这就是WiFi嗅探客流统计的基本原理。它完全被动接收不干扰任何设备不涉及隐私内容仅获取匿名MAC地址部署起来就是一个火柴盒大小的设备插上充电宝非常适合小店、展厅、临时活动等场景。基于这个思路我选择了ESP32这款明星级物联网芯片。它内置WiFi支持混杂模式功耗和成本都控制得极好简直是为此量身定做的。整个项目就是从硬件搭建、固件开发到数据处理的完整实现。下面我就把从构思到实现的全过程包括关键的代码细节、踩过的坑和优化心得毫无保留地分享出来。2. 系统整体设计与硬件选型2.1 技术方案选型与权衡为什么是WiFi嗅探而不是别的技术这里需要做一个简单的技术选型分析。常见的客流统计方法主要有以下几种红外/激光对射技术成熟成本低。但需要精准对位安装只能统计单一通道的“穿过”动作无法判断人的朝向也容易因摆放物品或多人并行而漏计、误计。摄像头视觉分析精度高能识别方向、滞留时间。但成本高涉及隐私问题安装复杂需角度、光照条件并且对算力有要求实时分析通常需要边缘计算设备。蓝牙信标手机蓝牙也广播唯一地址。但蓝牙信号范围小通常10米且很多用户会关闭蓝牙覆盖率不如WiFi。WiFi嗅探被动接收无需配对连接。手机WiFi开启率极高尤其在商场、店铺等场所信号覆盖范围适中ESP32可达数十米。虽然无法区分设备与人可能一人多机或设备未携带也无法判断精确位置和移动方向但对于区域内的总人数趋势统计、高峰时段分析其性价比和易用性非常突出。综合来看对于我朋友奶茶店这种需要低成本、快速部署、了解大致客流趋势的场景WiFi嗅探方案是最优解。它解决的不是“精确到个位数”的计数而是“相对变化趋势和密度”的感知。2.2 核心硬件组件解析我使用的硬件清单很简单核心就两样主控MakePython ESP32开发板选型理由ESP32芯片本身足以完成所有任务。选择“MakePython”这个型号主要是因为它板载了Micro SD卡槽这对于需要长时间、离线记录数据的应用至关重要。无需再外接复杂的存储模块大大简化了硬件连接。ESP32-WROOM-32模组提供了充足的处理能力和内存520KB SRAM, 4MB Flash。扩展板MakePython A9G GPRS/GPS扩展板注意在这个客流统计的初版中我们实际上只用了它的板载电平转换和接口并没有使用A9G的4G通信功能。选择它是因为它能完美堆叠在MakePython ESP32上方提供了稳定的5V转3.3V供电并且引出了所有GPIO方便后续扩展比如真的需要4G上传时。如果只是为了实现基础功能一块带SD卡槽的ESP32开发板加一个USB供电即可。其他一张Micro SD卡建议Class 10以上确保写入速度一根Micro USB线用于供电和编程一个普通的手机充电宝。实操心得硬件选择的“坑”最初我试过用更常见的ESP32 DevKit V1然后外接一个SD卡模块。结果遇到了两个问题一是接线多了容易接触不良在移动使用中不稳定二是外接模块的3.3V供电可能不足导致SD卡初始化失败或写入错误。所以强烈推荐使用板载SD卡槽的一体化开发板省心又稳定。如果项目后期需要网络上传再考虑集成通信模块。2.3 系统工作流程总览整个系统的工作流程可以清晰地分为设备端和数据端两部分设备端ESP32上电初始化连接SD卡创建日志文件。将WiFi设置为混杂模式开始监听空气中所有802.11数据包。设置一个回调函数对每一个捕获到的数据包进行解析。从数据包中提取源MAC地址SA或发送端MAC地址TA对于管理帧。维护一个“已见到MAC地址列表”。如果一个MAC地址是首次出现则将其加入列表并将“当前设备数”加1同时将这个MAC地址和时间戳写入SD卡。定期例如每5分钟将“当前设备数”写入文件并可选地清空或滚动记录列表以统计周期内的人数。数据端PC/Python将SD卡中的日志文件拷贝到电脑。运行Python分析脚本读取原始MAC地址和时间戳数据。进行数据清洗如过滤掉已知的固定路由器MAC。按时间窗口如每15分钟聚合统计独立设备数。使用Matplotlib等库生成客流随时间变化的折线图或柱状图。这个流程实现了从数据采集、存储到可视化分析的闭环。3. 固件开发详解ESP32上的WiFi嗅探3.1 开发环境搭建与核心库代码基于Arduino框架开发因为它生态丰富对ESP32支持完善上手快。安装ESP32开发板支持打开Arduino IDE进入“文件 - 首选项”在“附加开发板管理器网址”中添加https://espressif.github.io/arduino-esp32/package_esp32_index.json然后进入“工具 - 开发板 - 开发板管理器”搜索“esp32”安装“Espressif Systems”提供的包。核心库依赖WiFi.hESP32的WiFi核心库但我们需要使用其底层API。SD.h用于操作SD卡的标准库。FS.h文件系统抽象层。esp_wifi.hESP32的WiFi底层驱动库这是我们开启混杂模式的关键。注意关于“Promiscuous Mode”混杂模式是网络接口的一种工作方式在该模式下网卡会接收所有流经网络的数据包而不仅仅是发给自己的。这对于网络分析、嗅探是必要的。在ESP32上我们需要调用特定的ESP-IDF API来启用它。3.2 关键代码段深度解析让我们逐块分析核心代码理解每一行背后的意图。3.2.1 WiFi嗅探初始化这是整个固件的引擎启动部分。#include esp_wifi.h #include esp_event_loop.h #include nvs_flash.h // 首先定义回调函数原型 void wifi_sniffer_packet_handler(void* buf, wifi_promiscuous_pkt_type_t type); void wifi_sniffer_init(void) { // 1. 初始化NVS非易失性存储这是ESP32很多组件的基础 esp_err_t ret nvs_flash_init(); if (ret ESP_ERR_NVS_NO_FREE_PAGES) { // 如果NVS分区被污染先擦除再初始化生产代码中慎用会清空WiFi码等 ESP_ERROR_CHECK(nvs_flash_erase()); ret nvs_flash_init(); } ESP_ERROR_CHECK(ret); // 2. 初始化TCP/IP适配器层 tcpip_adapter_init(); // 3. 创建系统事件循环用于处理WiFi等事件 ESP_ERROR_CHECK(esp_event_loop_init(NULL, NULL)); // 4. 获取默认的WiFi初始化配置 wifi_init_config_t cfg WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(cfg)); // 5. 设置国家代码这决定了可用的WiFi信道1-13 for CN/US wifi_country_t wifi_country { .cc CN, .schan 1, .nchan 13, .policy WIFI_COUNTRY_POLICY_AUTO }; ESP_ERROR_CHECK(esp_wifi_set_country(wifi_country)); // 6. 设置WiFi配置存储方式为RAM这样配置不会保存到Flash ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); // 7. 设置WiFi模式为NULL模式即既不作为STA连接路由器也不作为AP热点 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL)); // 8. 启动WiFi ESP_ERROR_CHECK(esp_wifi_start()); // 9. 【核心】启用混杂模式 ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); // 10. 【核心】注册数据包接收回调函数 ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(wifi_sniffer_packet_handler)); }关键点解析WIFI_MODE_NULL这个模式很重要。它让ESP32的WiFi射频只工作于监听状态不发射任何信号从而功耗相对较低且不会干扰周围的无线网络。set_country设置国家码以确定合法信道。不同国家规定的可用信道和最大功率不同必须正确设置。set_promiscuous_rx_cb这是灵魂所在。它告诉底层驱动每收到一个数据包就调用我们自定义的wifi_sniffer_packet_handler函数。3.2.2 数据包回调函数与MAC地址处理这是数据处理的“心脏”。每当有数据包飞过这个函数就被调用。// 定义一个二维数组来存储捕获到的MAC地址假设最多存储500个 uint8_t mac_lib[500][6]; int mac_count 0; // 回调函数 void wifi_sniffer_packet_handler(void* buf, wifi_promiscuous_pkt_type_t type) { // 首先进行类型转换获取标准的数据包结构 const wifi_promiscuous_pkt_t* ppkt (wifi_promiscuous_pkt_t*)buf; const wifi_ieee80211_packet_t* ipkt (wifi_ieee80211_packet_t*)ppkt-payload; const wifi_ieee80211_mac_hdr_t* hdr ipkt-hdr; // 只处理管理帧如信标帧、探针请求和数据帧控制帧通常不携带有用的终端MAC if (type WIFI_PKT_MGMT || type WIFI_PKT_DATA) { // 提取发送该帧的设备的MAC地址。 // 对于管理帧如手机发出的探针请求帧控制字段表明它是从手机到广播地址的所以源地址SA就是手机的MAC。 // 这里更通用的做法是取地址1接收地址或地址2发送地址需根据帧类型判断。一个简化的方法是取地址2。 const uint8_t* mac_addr hdr-addr2; // 检查这个MAC是否已经在我们的列表中 if (check_mac_only(mac_addr) 1) { // 如果是新MAC计数增加并记录到SD卡 mac_count; // 注意check_mac_only函数内部已经增加了mac_count这里实际可以省略仅作示意。 char log_line[64]; sprintf(log_line, %02X:%02X:%02X:%02X:%02X:%02X,%lu, mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], millis() / 1000); // 记录时间戳秒 writeFile(SD, /log.txt, log_line); // 写入SD卡 } } }关键点解析帧类型过滤WiFi数据包分管理帧、控制帧、数据帧。我们主要关心管理帧如Probe Request探针请求这是手机主动搜索网络时广播的最能代表一个活跃的设备。数据帧也可能包含手机MAC但手机需关联到AP才会产生。代码中简单过滤了控制帧。MAC地址提取802.11帧头有多个地址字段Addr1, Addr2, Addr3, Addr4含义随帧类型和子类型变化。对于最常见的探针请求帧Addr2是发送端手机的MAC地址。这是一个需要仔细处理的地方不正确的解析会导致大量重复或漏计。去重逻辑check_mac_only函数遍历已有的mac_lib数组检查新MAC是否已存在。这是内存中的实时去重用于计算“当前在线”设备数。但为了分析历史趋势所有MAC包括重复的连同时间戳都应写入日志供后期软件去重分析。3.2.3 MAC地址去重函数实现int check_mac_only(const uint8_t addr[6]) { for (int i 0; i mac_count; i) { bool is_same true; for (int j 0; j 6; j) { if (mac_lib[i][j] ! addr[j]) { is_same false; break; } } if (is_same) { return 0; // 已存在返回0 } } // 不存在则添加到库中 for (int j 0; j 6; j) { mac_lib[mac_count][j] addr[j]; } mac_count; // 注意这里增加了全局计数 return 1; // 是新MAC返回1 }这个函数是典型的O(n)遍历查找当mac_count很大时比如超过1000会比较耗时可能影响ESP32捕获数据包的性能。在实际应用中需要考虑优化例如使用哈希表思想或者定期如每10分钟清空一次列表只统计周期内的新设备。3.2.4 SD卡数据写入void writeFile(fs::FS fs, const char* path, const char* message) { Serial.printf(Writing to: %s\n, path); File file fs.open(path, FILE_APPEND); // 使用FILE_APPEND模式追加写入而不是FILE_WRITE覆盖 if (!file) { Serial.println(Failed to open file for appending); return; } if (file.println(message)) { // println会自动添加换行符 // Serial.println(Message appended); } else { Serial.println(Append failed); } file.close(); // 务必关闭文件确保数据写入物理存储 }重要注意事项FILE_APPEND非常重要必须用追加模式否则每次写入都会覆盖之前的全部数据。文件关闭file.close()是必须的。Arduino的SD库通常有写缓冲区close()操作会触发物理写入。如果不关闭在断电时可能导致最后几条数据丢失。写入频率如果每个新MAC都立即写入一次文件SD卡的写入操作会非常频繁影响寿命和性能。更好的做法是在内存中缓存一批记录比如一个字符串数组每隔一定时间如30秒或缓存达到一定数量如50条后一次性写入文件。这能显著提升稳定性和SD卡寿命。3.3 固件优化与高级设置基础的嗅探功能实现后还有几个关键点可以优化让系统更实用。信道切换与扫描 WiFi有多个信道2.4GHz下1-13。设备通常只在某个信道上广播。为了捕获更多设备可以让ESP32周期性地在所有信道上切换监听。void channel_hop() { static uint8_t current_channel 1; esp_wifi_set_channel(current_channel, WIFI_SECOND_CHAN_NONE); current_channel (current_channel % 13) 1; // 在1-13信道间循环 } // 在loop()中每隔几秒调用一次channel_hop()注意快速信道切换可能会丢失一些数据包需要根据实际场景调整切换间隔如200ms-500ms。RSSI过滤与距离估算 数据包结构中的ppkt-rx_ctrl.rssi字段表示接收信号强度指示。值越负信号越弱。可以设置一个阈值如-80dBm只记录信号强度大于此阈值的设备从而将统计范围限定在物理距离较近的域如店铺内过滤掉店外路过的信号。if (ppkt-rx_ctrl.rssi -80) { // 处理该数据包 }功耗优化在wifi_sniffer_init中已使用WIFI_MODE_NULL这是基础。可以启用ESP32的轻量睡眠模式在两次嗅探间隔让CPU休眠。但这需要精确的定时唤醒且睡眠期间无法嗅探可能丢失数据需要权衡。最有效的省电方法是降低嗅探占空比。例如每秒只工作100毫秒其余900毫秒关闭射频。这可以通过在loop()中控制esp_wifi_set_promiscuous(true/false)来实现。数据滚动与内存管理mac_lib数组大小有限。需要实现一个滚动机制。例如当mac_count达到数组上限的90%时将最早的一部分记录移除比如移除前50%或者每小时清空一次重新开始计数。这取决于你是想统计“历史累计不重复设备”还是“周期内活跃设备”。4. 数据处理与可视化从原始日志到洞察图表设备运行一段时间后SD卡里的log.txt文件会积累大量原始数据格式如“A4:C3:61:12:34:56,1633021234”MAC地址,时间戳。这些数据需要经过处理才能变成有意义的客流曲线。4.1 Python数据分析脚本核心我们使用Python的pandas和matplotlib库进行处理。假设文件中有10万条记录。import pandas as pd import matplotlib.pyplot as plt from datetime import datetime, timedelta import re # 1. 读取数据 file_path ./log.txt df pd.read_csv(file_path, headerNone, names[mac, timestamp]) print(f原始数据共 {len(df)} 条记录) # 2. 数据清洗 # 2.1 去除可能的无效行 df.dropna(inplaceTrue) # 2.2 过滤掉已知的固定设备如店铺自己的路由器、打印机 # 假设路由器的MAC是 B8:27:EB:xx:xx:xx (树莓派常见) 和 D4:CA:6D:xx:xx:xx fixed_mac_prefixes [B8:27:EB, D4:CA:6D] def is_fixed_mac(mac): for prefix in fixed_mac_prefixes: if mac.startswith(prefix): return True return False df df[~df[mac].apply(is_fixed_mac)].copy() print(f过滤固定设备后剩余 {len(df)} 条记录) # 2.3 将时间戳转换为可读的datetime对象 # 注意ESP32记录的时间戳可能是上电后的毫秒数(millis())或秒数。 # 如果是秒数且记录了开始时间可以转换。这里假设记录的是Unix时间戳秒。 # 如果ESP32记录的是millis()则需要知道设备启动的基准真实时间才能转换。 # 本例假设ESP32通过NTP或手动设定了RTC记录了Unix时间戳。 df[datetime] pd.to_datetime(df[timestamp], units) # 3. 按时间窗口统计独立设备数 # 设置分析的时间窗口例如15分钟 window_size 15T # T代表分钟H代表小时D代表天 # 方法以时间窗口为分组对每个窗口内的MAC地址进行去重计数 # 由于一个设备可能在窗口内出现多次我们需要用集合(Set)去重 def count_unique_mac(group): return group[mac].nunique() # pandas的nunique函数直接计算唯一值数量 # 使用resample进行重采样和分组聚合 df.set_index(datetime, inplaceTrue) 客流数据 df.resample(window_size).apply(count_unique_mac) 客流数据.rename(columns{mac: unique_devices}, inplaceTrue) # 4. 生成可视化图表 plt.figure(figsize(15, 6)) plt.plot(客流数据.index, 客流数据[unique_devices], markero, linestyle-, linewidth2) plt.title(店铺客流趋势分析 (15分钟窗口), fontsize16) plt.xlabel(时间, fontsize14) plt.ylabel(独立设备数 (估算人数), fontsize14) plt.grid(True, whichboth, linestyle--, alpha0.7) plt.xticks(rotation45) plt.tight_layout() # 标记高峰时段 max_flow 客流数据[unique_devices].max() max_time 客流数据[unique_devices].idxmax() plt.annotate(f高峰: {max_flow}人\n{max_time.strftime(%H:%M)}, xy(max_time, max_flow), xytext(max_time timedelta(hours1), max_flow*0.9), arrowpropsdict(facecolorred, shrink0.05), fontsize12, colorred) plt.savefig(客流趋势图.png, dpi300) plt.show() # 5. 输出基础统计信息 print(\n 客流统计摘要 ) print(f分析时段: {客流数据.index[0]} 至 {客流数据.index[-1]}) print(f总到访独立设备数: {df[mac].nunique()}) print(f平均每{window_size}客流: {客流数据[unique_devices].mean():.1f}) print(f客流最高时段: {max_time.strftime(%Y-%m-%d %H:%M)}, 人数: {max_flow}) print(f客流最低时段: {客流数据[unique_devices].idxmin().strftime(%Y-%m-%d %H:%M)}, 人数: {客流数据[unique_devices].min()})4.2 数据解读与业务洞察运行脚本后你会得到一张清晰的折线图和一份数据摘要。以奶茶店为例你可以从中解读出每日高峰时段图表会明确显示客流在中午12:00-13:00和晚上18:00-19:00出现峰值。这验证了午餐和晚餐后的“茶饮需求高峰”。工作日与周末对比如果你连续记录多日数据可以将周末和工作日的曲线叠加对比会发现周末的峰值更高、持续时间更长。这为排班提供了直接依据周末需要安排更多店员。营销活动效果评估在做“第二杯半价”活动的当天对比活动前后相同时段的客流曲线可以直观看到活动带来的引流效果。客流转化率估算需结合交易数据虽然不能精确匹配但可以将每小时的平均客流数与该时间段的实际订单数进行对比估算出一个大致的“客流-成交”转化比例用于评估店铺的吸引力和运营效率。数据处理心得应对MAC地址随机化现代操作系统iOS 8, Android 6为了隐私保护在发送WiFi探针请求时会使用随机化的MAC地址。这意味着同一台手机在不同时间、不同地点可能会使用不同的MAC。这会对我们的“独立设备计数”造成严重干扰导致统计数量虚高。应对策略时间窗口去重我们统计的是“每15分钟内的独立设备数”而不是“全天累计不重复设备数”。随机化MAC通常在短时间内如几分钟到几小时保持稳定因此短时间窗口内的统计受影响较小。关注相对趋势即使绝对数字因随机化而不准但客流变化的趋势和相对比例仍然是有效的。例如周末比工作日高50%这个比例信息依然有价值。过滤OUI前缀可以过滤掉一些已知的、属于虚拟或随机化地址范围的MAC地址组织唯一标识符前缀但这方法并不完全可靠。 这是WiFi嗅探方案目前最大的局限性在评估结果时需要心中有数。5. 系统部署、问题排查与进阶思路5.1 现场部署实操要点设备放置高度建议放置在2-2.5米高度避免被人群遮挡。位置尽量靠近店铺中心或正对入口/主要通道。避免紧贴墙壁或金属物体以免信号被屏蔽。电源使用大容量充电宝20000mAh以上确保可连续工作数天。注意检查充电宝的自动关机功能有些充电宝在低电流输出一段时间后会自动断电需要选择支持“小电流模式”或常开的产品。参数调优RSSI阈值根据店铺大小调整。在店铺内移动手机观察ESP32串口输出的RSSI值确定一个能较好区分店内外的阈值例如-75dBm。统计周期在固件中设置一个定时器如每5分钟将当前mac_count写入文件然后重置mac_count和mac_lib数组。这样SD卡里会同时有原始的详细日志和聚合后的周期计数方便不同粒度的分析。数据获取定期拷贝最简单的方案是每天营业结束后取下SD卡用读卡器将日志文件拷贝到电脑。网络上传进阶如果使用带4G模块的A9G板子可以修改代码定期通过HTTP POST或MQTT协议将聚合后的数据发送到云服务器如阿里云、腾讯云IoT平台或自建的服务端实现远程实时查看。5.2 常见问题与排查技巧下表总结了开发和使用过程中可能遇到的典型问题及解决方法问题现象可能原因排查步骤与解决方案ESP32启动失败不断重启1. 电源供电不足。2. SD卡初始化失败导致程序卡死。3. 代码存在内存泄漏或数组越界。1. 使用质量好的USB线或5V/2A以上的电源适配器。2. 检查SD卡格式FAT32尝试更换SD卡。3. 在setup()开始时加入Serial.begin(115200);和大量Serial.println进行调试定位崩溃点。使用heap_caps_check_integrity_all(true);检查堆内存。SD卡无法写入或文件丢失1. 文件未正确关闭。2. SD卡接触不良。3. 写入过于频繁。1. 确保每次file.print()后都执行file.close()。2. 重新插拔SD卡或用酒精擦拭金手指。3. 实现写入缓冲减少物理写入次数。捕获到的设备数量极少1. WiFi信道固定而周围设备在别的信道。2. RSSI阈值设置过高。3. ESP32天线性能差或放置位置不佳。1. 实现信道切换功能循环扫描1-13信道。2. 逐步调低RSSI阈值如从-70调到-85。3. 尝试使用外接天线版本的ESP32或将设备移至更开阔的位置。统计人数远高于实际客流1. MAC地址随机化导致一台手机被识别为多个设备。2. 统计范围过大包含了店外行人。3. 附近存在大量固定IoT设备如智能灯、插座。1.接受这个误差专注于趋势分析而非绝对数值。2. 提高RSSI阈值或结合多设备三角定位复杂来缩小范围。3. 建立“白名单”在数据分析阶段过滤掉已知的固定设备MAC前缀。设备运行一段时间后死机1. 内存耗尽mac_lib数组溢出。2. Watchdog超时某个任务阻塞太久。1. 实现滚动存储逻辑定期清空或覆盖旧MAC记录。2. 在loop()中加入delay(1)或使用yield()确保看门狗被喂食。检查回调函数是否执行时间过长。5.3 项目进阶与扩展思路基础版本实现后这个系统还有很大的优化和扩展空间方向判断进出单个设备很难判断方向。但可以部署两个ESP32嗅探器分别放在入口内侧和外侧。通过比较同一MAC地址在两个设备上出现的时间先后和信号强度变化可以粗略推断移动方向。这需要两个设备时间同步并在后端进行数据融合分析。数据云端化与实时看板利用A9G模块的GPRS/4G功能将实时统计的人数定时上传到云平台如ThingsBoard、阿里云IoT。然后搭建一个简单的Web仪表盘实时显示当前店内人数和历史曲线老板在手机上就能随时查看。与交易系统联动如果能获取到POS机的交易时间戳数据需要接口可以将客流曲线与成交订单曲线叠加计算各时段的转化率这是更深入的商业分析。低功耗长期监测针对户外、无市电的场景如公园摊位统计可以深度优化功耗使用深度睡眠模式每小时唤醒工作5分钟进行嗅探然后通过NB-IoT比4G更省电上传数据。这样配合大容量电池可以工作数月。这个基于ESP32的WiFi嗅探客流统计系统从一个具体的店铺管理痛点出发用较低的成本和硬件实现了一个有价值的商业数据采集工具。它的核心价值不在于百分百的精确计数而在于提供了一种便捷、持续、数据化的“环境感知”能力。从技术实现上看它涉及了嵌入式开发、无线通信协议、数据分析和可视化等多个环节是一个非常好的物联网全栈入门实践项目。在实际部署中理解并接受其局限性如MAC随机化善用其趋势分析的优势就能让它真正为商业决策提供有力的数据支撑。