ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于ESP8266与HC-SR501的无线人数统计器:从硬件搭建到Grafana可视化

2026/8/20 2:52:06 拓冰建站 浏览量
基于ESP8266与HC-SR501的无线人数统计器:从硬件搭建到Grafana可视化 1. 项目缘起从“数人头”到智能感知你有没有想过家里的客厅一天到底有多少人经过工作室的工位使用率到底高不高或者只是想做一个酷炫的、能实时显示访客数量的“电子门牌”几年前要实现这些想法你可能需要一台树莓派、一个摄像头再加上复杂的图像识别算法不仅成本高隐私和算力也都是问题。但现在情况完全不同了。一个成本不到20块钱的ESP8266芯片加上一个同样廉价的HC-SR501人体红外感应模块就能轻松搭建一个无线人数统计器。这个项目的核心思路非常巧妙它不识别“谁”只感知“有物体经过”并通过逻辑判断是“进入”还是“离开”。我把这个项目叫做“ESP8266 WiFi People Counter”它完美诠释了如何用最低的成本和最简单的逻辑解决一个实际的物理世界感知问题。我最初做这个是为了监控一个小型会议室的使用情况避免不必要的预订冲突。后来发现它的应用场景远不止于此智能家居的联动人走灯灭、人来空调开、商铺的客流分析、图书馆自习室座位占用监测甚至是你家宠物一天进出阳台的次数都可以用它来统计。整个过程完全离线处理数据通过WiFi上传无需涉及任何复杂的图像处理也彻底杜绝了隐私泄露的担忧。接下来我将手把手带你从零开始完成这个软硬件结合的项目。我们会涵盖硬件选型与连接、ESP8266固件开发、数据上报协议以及最终在Grafana上实现酷炫的数据可视化看板。你会发现物联网入门并没有想象中那么难。2. 硬件选型与电路连接为什么是它们工欲善其事必先利其器。硬件是项目的基石选对组件项目就成功了一半。这里的选择背后都有其明确的理由并非随意搭配。2.1 核心控制器ESP8266 NodeMCU开发板为什么是ESP8266而不是更强大的ESP32或者更简单的Arduino Uno首先WiFi是刚需。我们需要设备能够自主联网并发送数据ESP8266内置了完整的TCP/IP协议栈和WiFi模块单芯片解决通信问题这是Arduino Uno加WiFi扩展板无法比拟的集成度和性价比。其次足够的计算与IO能力。人数统计的逻辑判断进入/离开需要持续监测传感器状态并进行去抖、计时判断ESP8266的80MHz主频和数十KB的RAM绰绰有余。它提供了多个GPIO口方便连接传感器和可能的显示设备。最后丰富的开发生态与极低的成本。基于Arduino Core for ESP8266的开发环境成熟度极高资料丰富社区活跃。一块NodeMCU开发板价格通常在15元人民币以内是性价比之王。注意市面上ESP8266开发板型号繁多推荐使用NodeMCU基于ESP-12E/F模块或Wemos D1 mini。它们自带USB转串口芯片方便供电和编程且引脚布局规整。避免使用仅焊有ESP-01模块的板子其GPIO口太少扩展性差。2.2 感知单元HC-SR501人体红外传感器这是项目的“眼睛”。它通过探测人体发出的特定波长红外线变化来触发。选择它而非其他传感器如超声波、激光对射的原因如下非接触与隐私友好它不发出任何射线仅被动接收且无法识别个体特征完美符合隐私要求。主动式输出模块本身集成了信号处理电路直接输出高/低电平的数字信号极大简化了程序设计。我们不需要处理模拟信号或复杂的波形分析。可调节模块上的两个电位器可以调节感应距离约3-7米和触发后高电平的持续时间约0.3秒~5分钟。这对于我们调整感应区域和防误触发至关重要。成本极低单价通常在5元以内。关键限制与应对HC-SR501只能检测移动的热源。如果人完全静止它会认为目标消失。这对于门口统计场景影响不大因为很少有人会在门槛上完全静止。但需要知晓这一特性。2.3 电路连接简单的三线制连接非常简单但顺序和细节决定成败。供电将HC-SR501的VCC和GND分别连接到NodeMCU的3.3V和GND引脚。严禁接到5V引脚虽然模块标称支持5V但为了稳定性和安全性统一使用3.3V。信号线将HC-SR501的OUT引脚连接到NodeMCU的某个GPIO口例如D5对应GPIO14。NodeMCU的引脚标注D0, D1, D2...是Arduino IDE中的编号需要映射到实际的GPIO号这在代码中会体现。传感器配置首次使用前建议将HC-SR501上的跳线帽设置为可重复触发模式通常标记为H或Repeatable。在这个模式下只要有人在感应范围内移动输出就会持续保持高电平人离开后延迟一段时间才恢复低电平。这比单次触发模式更适合计数逻辑。同时将延时电位器Time Delay逆时针旋到最小延时最短避免一次触发影响后续计数。整个硬件连接示意图如下实际上就是三根线NodeMCU -- HC-SR501 3.3V -- VCC GND -- GND D5(GPIO14) -- OUT硬件搭建好后可以先不通程序用手在传感器前移动观察NodeMCU板载LED或使用万用表测量OUT引脚电压确认传感器能正常输出高电平约3V。这是硬件调试的第一步。3. 核心逻辑与固件开发如何准确“数人头”硬件感知到“有人经过”但如何判断是“进入”还是“离开”这需要设计一个简单的状态机逻辑并处理传感器信号的各种毛刺抖动。我们将使用Arduino IDE进行开发。3.1 开发环境搭建安装Arduino IDE1.8.x或2.0版本均可。在文件-首选项的“附加开发板管理器网址”中添加ESP8266的板管理地址http://arduino.esp8266.com/stable/package_esp8266com_index.json。打开工具-开发板-开发板管理器搜索“esp8266”安装“esp8266 by ESP8266 Community”。安装完成后在工具-开发板中选择你的板子型号例如“NodeMCU 1.0 (ESP-12E Module)”。设置正确的端口工具-端口。3.2 计数逻辑设计与实现最常见的实现方式是使用两个传感器组成一个“方向检测通道”。传感器A和B间隔一定距离如20厘米安装。当人依次触发A、B时判定为进入依次触发B、A时判定为离开。这是最准确的方式。但对于很多低成本、低精度要求的场景如统计房间内大致人数使用单个传感器配合“进出区域”逻辑也是一种简化方案。其核心思想是传感器触发有人- 持续监测 - 传感器释放无人并超过一个短暂延时 - 计为一次“进出”人数1或-1需要依赖额外的上下文如进门按出门按-。但这种方法在多人同时进出时极易误判。本项目采用更可靠的双传感器方案。逻辑状态图如下初始状态: 等待 触发A - 状态: A触发等待B 若超时未触发B - 返回等待可能是误触发或反向移动 若触发B - 判定为【进入】人数1返回等待 触发B - 状态: B触发等待A 若超时未触发A - 返回等待 若触发A - 判定为【离开】人数-1返回等待3.3 固件代码详解以下是基于双传感器接在GPIO14/D5和GPIO12/D6的完整Arduino代码框架包含了WiFi连接、MQTT发布和核心计数逻辑。#include ESP8266WiFi.h #include PubSubClient.h // 需要安装PubSubClient库 // WiFi配置 const char* ssid 你的WiFi名称; const char* password 你的WiFi密码; // MQTT配置 const char* mqtt_server 你的MQTT服务器IP; const int mqtt_port 1883; const char* mqtt_topic_count home/people_counter/count; const char* mqtt_topic_raw home/people_counter/raw; // 可选用于调试 // 引脚定义 #define SENSOR_A_PIN D5 // GPIO14 #define SENSOR_B_PIN D6 // GPIO12 // 全局变量 volatile int peopleCount 0; // 当前人数volatile防止中断中被错误优化 unsigned long lastTriggerTime 0; const unsigned long DEBOUNCE_DELAY 200; // 防抖延时毫秒 const unsigned long DIRECTION_TIMEOUT 2000; // 方向判定超时毫秒 enum State { WAITING, A_TRIGGERED, B_TRIGGERED }; State currentState WAITING; WiFiClient espClient; PubSubClient client(espClient); // 中断服务函数简化版实际建议用标志位在主循环处理 void IRAM_ATTR handleSensorA() { static unsigned long lastInterruptTime 0; unsigned long interruptTime millis(); if (interruptTime - lastInterruptTime DEBOUNCE_DELAY) { processSensor(A); } lastInterruptTime interruptTime; } void IRAM_ATTR handleSensorB() { static unsigned long lastInterruptTime 0; unsigned long interruptTime millis(); if (interruptTime - lastInterruptTime DEBOUNCE_DELAY) { processSensor(B); } lastInterruptTime interruptTime; } void processSensor(char sensor) { unsigned long now millis(); switch(currentState) { case WAITING: if (sensor A) { currentState A_TRIGGERED; lastTriggerTime now; Serial.println(状态: A触发等待B); } else if (sensor B) { currentState B_TRIGGERED; lastTriggerTime now; Serial.println(状态: B触发等待A); } break; case A_TRIGGERED: if (sensor B) { // A先B后判定为进入 peopleCount; Serial.printf(有人进入当前人数: %d\n, peopleCount); publishCount(); currentState WAITING; } else if (sensor A) { // 再次触发A可能是抖动或人在A前徘徊重置计时 lastTriggerTime now; } // 检查超时 if (now - lastTriggerTime DIRECTION_TIMEOUT) { Serial.println(A-B 方向判定超时重置); currentState WAITING; } break; case B_TRIGGERED: if (sensor A) { // B先A后判定为离开 peopleCount--; if (peopleCount 0) peopleCount 0; // 防止负数 Serial.printf(有人离开当前人数: %d\n, peopleCount); publishCount(); currentState WAITING; } else if (sensor B) { lastTriggerTime now; } if (now - lastTriggerTime DIRECTION_TIMEOUT) { Serial.println(B-A 方向判定超时重置); currentState WAITING; } break; } } void publishCount() { char msg[50]; snprintf(msg, 50, {\count\: %d, \timestamp\: %lu}, peopleCount, millis()); if (client.publish(mqtt_topic_count, msg)) { Serial.print(MQTT发布成功: ); Serial.println(msg); } else { Serial.println(MQTT发布失败); } } void setup() { Serial.begin(115200); pinMode(SENSOR_A_PIN, INPUT); pinMode(SENSOR_B_PIN, INPUT); // 连接WiFi WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(\nWiFi连接成功); // 配置MQTT client.setServer(mqtt_server, mqtt_port); // 连接MQTT Broker while (!client.connected()) { Serial.print(尝试连接MQTT...); if (client.connect(ESP8266PeopleCounter)) { Serial.println(MQTT连接成功); } else { Serial.print(失败 rc); Serial.print(client.state()); Serial.println( 5秒后重试); delay(5000); } } // 配置中断注意使用中断需谨慎此处为示例。对于HC-SR501因其输出变化较慢在主循环中轮询digitalRead()可能更稳定 // attachInterrupt(digitalPinToInterrupt(SENSOR_A_PIN), handleSensorA, CHANGE); // attachInterrupt(digitalPinToInterrupt(SENSOR_B_PIN), handleSensorB, CHANGE); Serial.println(系统初始化完成开始监控...); } void loop() { client.loop(); // 维持MQTT连接 // 方法二在主循环中轮询传感器状态更稳定推荐 static int lastStateA LOW, lastStateB LOW; int currentStateA digitalRead(SENSOR_A_PIN); int currentStateB digitalRead(SENSOR_B_PIN); unsigned long now millis(); static unsigned long lastPollTime 0; if (now - lastPollTime 50) { // 每50ms轮询一次 lastPollTime now; if (currentStateA HIGH lastStateA LOW) { processSensor(A); } if (currentStateB HIGH lastStateB LOW) { processSensor(B); } lastStateA currentStateA; lastStateB currentStateB; } // 处理状态超时 if (currentState ! WAITING (now - lastTriggerTime DIRECTION_TIMEOUT)) { Serial.println(全局状态超时重置); currentState WAITING; } // 每隔一段时间如10秒主动上报一次当前人数防止数据丢失 static unsigned long lastReportTime 0; if (now - lastReportTime 10000) { lastReportTime now; publishCount(); } }代码关键点解析防抖处理无论是中断还是轮询都必须对传感器信号进行防抖。HC-SR501模块自身有一定稳定性但电路噪声仍可能引起误触发。代码中通过时间间隔判断DEBOUNCE_DELAY来过滤掉过快的连续触发。状态机超时DIRECTION_TIMEOUT至关重要。如果触发A后很久都没触发B可能人只是靠近又退回去了状态机必须能自动重置否则会卡死。中断 vs 轮询对于HC-SR501其输出状态变化频率很低人移动速度决定使用millis()进行软件轮询足够可靠且避免了中断服务程序ISR中处理复杂逻辑和网络操作带来的风险。因此示例中注释掉了中断推荐使用轮询。数据上报除了在人数变化时立即发布MQTT消息还设置了定时10秒上报。这是为了应对网络瞬时中断导致的消息丢失确保服务端数据最终一致性。人数为负处理在逻辑上当peopleCount小于0时我们将其重置为0。这在系统初始化或误触发时能提供一个合理的下限。你也可以选择记录负值用于调试。实操心得双传感器的安装位置和间距需要实地测试。间距太近10cm可能无法区分进入和离开的顺序间距太远30cm又可能因为人行走速度慢导致触发间隔超过DIRECTION_TIMEOUT而误判。建议先用代码打印出原始的触发序列在安装位置反复测试调整找到最合适的间距和超时时间。4. 数据链路搭建MQTT与Grafana的可视化设备计数是第一步让数据产生价值才是目的。我们需要一个轻量、可靠的数据传输协议MQTT和一个强大的可视化工具Grafana。4.1 MQTT Broker选择与搭建MQTT是一种基于发布/订阅模式的物联网消息协议非常适合ESP8266这类资源受限的设备。选择一公共Broker。如test.mosquitto.org仅用于测试或EMQX提供的在线试用版。优点是无需部署缺点是延迟、稳定性不可控且存在安全风险。选择二本地私有化部署推荐。对于家庭或内部项目在局域网内的一台常开机的电脑树莓派、旧笔记本、NAS或云服务器上部署MQTT Broker是最佳选择。Mosquitto最轻量、最流行的开源MQTT Broker。在Ubuntu/Debian上安装只需一条命令sudo apt install mosquitto mosquitto-clients。安装后即自动运行默认端口1883。EMQX功能更强大性能更高提供Web管理界面。适合对集群、规则引擎有更高要求的场景。这里以Mosquitto为例部署后需要确认防火墙开放了1883端口并考虑设置用户名密码mosquitto_passwd命令。ESP8266代码中的mqtt_server就填写运行Mosquitto的机器的局域网IP地址。4.2 数据桥接MQTT到数据库Grafana本身不长期存储数据它需要从数据源中查询。因此我们需要一个“中间人”来订阅MQTT主题并将收到的JSON数据解析、写入时序数据库。方案选择Telegraf InfluxDB这是当前物联网数据处理的经典组合。InfluxDB专为时序数据优化的数据库写入和查询速度极快数据模型Measurement, Tag, Field, Time与传感器数据天然契合。Telegraf强大的数据收集代理内置了mqtt_consumer插件可以轻松订阅MQTT主题并写入InfluxDB、Prometheus等多种输出。部署与配置步骤安装InfluxDB v2.x参照官网教程。安装后通过Web界面默认http://localhost:8086完成初始化创建一个Bucket例如iot_data并生成一个All-Access Token备用。安装Telegraf同样参照官网。配置Telegraf编辑Telegraf配置文件如/etc/telegraf/telegraf.conf。# /etc/telegraf/telegraf.conf [[inputs.mqtt_consumer]] servers [tcp://localhost:1883] # MQTT Broker地址 topics [home/people_counter/count] data_format json json_time_key timestamp json_time_format unix_ms # 根据代码中timestamp格式调整 [[outputs.influxdb_v2]] urls [http://localhost:8086] token 你的All-Access Token organization 你的组织名 bucket iot_data启动Telegraf服务sudo systemctl start telegraf。使用sudo systemctl status telegraf查看状态使用sudo journalctl -u telegraf -f查看实时日志确保它成功连接到了MQTT Broker和InfluxDB。此时当ESP8266发布消息到home/people_counter/count时Telegraf会将其抓取并写入InfluxDB的iot_data桶中。你可以在InfluxDB的Data Explorer中查询到名为mqtt_consumer的measurement里面包含了count字段。4.3 Grafana可视化看板搭建Grafana是画龙点睛之笔。安装Grafana官网下载安装包或使用Docker部署。添加数据源登录Grafana默认http://localhost:3000admin/admin在Configuration-Data Sources中添加InfluxDB数据源。URL:http://localhost:8086Auth: 勾选With Credentials取消Basic Auth。在InfluxDB Details中填写Organization、Token和Default Bucketiot_data。点击Save Test显示成功即可。创建Dashboard和Panel点击Create-Dashboard-Add new panel。在Query编辑器中选择数据源为刚添加的InfluxDB。FROM: 选择mqtt_consumer_field选择count。GROUP BY: 按_time进行分组例如1m1分钟均值或10s10秒均值。SELECT: 聚合函数选择last()因为我们关心的是每个时间点上最新的那个count值。这样就能画出一条实时的人数变化曲线。丰富看板内容当前人数Stat再添加一个Panel查询方式类似但在SELECT后选择last()并在OVERALL中选择current时间范围。在Visualization中选择Stat类型可以显示一个大大的数字。今日进出流量需要更复杂的查询。可以尝试从原始数据中计算出“进入”和“离开”的事件这需要在ESP8266代码中发布更详细的事件消息而不仅仅是总人数或者通过计算人数曲线的上升沿和下降沿来近似。历史热力图使用Heatmap可视化可以直观展示一天中哪些时段人数最多。踩坑记录Grafana查询时时间范围的选择非常重要。如果你发现面板上没有数据首先检查右上角的时间范围是否覆盖了数据产生的时间。对于实时数据可以选择Last 5 minutes或Last 1 hour。另外InfluxDB中存储的时间是UTC时间Grafana会根据浏览器的时区进行转换通常无需担心但如果发现时间对不上可以检查时区设置。5. 部署优化与进阶思考一个能跑起来的原型和一个稳定可靠的产品之间还有很长的路要走。以下是几个关键的优化方向和进阶思路。5.1 设备端稳定性增强WiFi连接健壮性目前的代码在WiFi或MQTT断开后尝试重连的逻辑比较简单。在生产环境中需要更强大的重连机制和故障恢复。可以使用WiFi.setAutoReconnect(true)和WiFi.persistent(true)。对于MQTT可以在loop()中检查连接状态并在断开时执行重连。看门狗与复位ESP8266内置了软件看门狗WDT但复杂的网络操作可能使其触发。可以在loop()中定期喂狗ESP.wdtFeed()。对于无法恢复的故障可以考虑在连续多次连接失败后执行软件重启ESP.restart()。配置管理将WiFi SSID、密码、MQTT服务器地址等配置信息存储在EEPROM或文件系统LittleFS中并提供一个配网页面如WiFiManager库这样就不需要每次修改都重新刷写固件。OTA升级实现空中升级功能让你可以通过网络远程更新设备固件这对于部署在多个地点的计数器至关重要。5.2 传感器安装与校准的学问硬件项目的成败一半在软件一半在安装。安装高度与角度HC-SR501的最佳安装高度是离地1.2-1.5米与人体腰部平齐。传感器透镜前的菲涅尔透镜有其探测范围约110度锥角安装时应使其探测锥角覆盖需要监测的通道并尽量避免侧面或远处的干扰热源如暖气、窗户。环境干扰避免将传感器正对空调出风口、加热器或阳光直射的窗户。这些快速变化的热源会导致误触发。可以调整传感器上的灵敏度电位器适当降低灵敏度。双传感器对齐两个传感器必须平行安装且探测区域要有足够的重叠确保一个人正常通过时能依次触发两个传感器。可以使用纸板等物品临时固定进行大量步行测试用串口监视器观察触发顺序微调位置直至稳定。应对多人并排这是双传感器方案的一个局限。如果两人紧挨着同时进出可能会被识别为一次事件。对于需要高精度计数的场景如闸机需要更专业的方案如红外对射阵列或3D视觉传感器。5.3 数据流与告警扩展当前架构是“设备 - MQTT - Telegraf - InfluxDB - Grafana”。我们可以在这个链条上增加更多环节。数据预处理Telegraf功能强大可以在数据写入数据库前进行简单的处理比如重命名字段、添加标签如locationfront_door。设置告警Grafana Alerting功能可以基于面板查询设置告警规则。例如当“当前人数”超过某个阈值如会议室容量或当“今日累计进入人数”达到目标时通过邮件、Slack、钉钉等渠道发送通知。与其他系统集成通过MQTT这个人数数据可以轻松接入Home Assistant等智能家居平台实现自动化联动。比如当客厅人数从0变为1时自动打开主灯和空调。长期数据聚合与分析InfluxDB可以配置定期任务Tasks将高精度的原始数据每秒一点聚合成每小时、每天的统计值最大值、平均值、累计进出次数存入新的Bucket用于长期趋势分析和节省存储空间。这个“ESP8266 WiFi People Counter”项目从一个简单的想法出发串联起了嵌入式开发、网络通信、数据采集和可视化等多个物联网核心领域。它就像一把钥匙帮你打开了物联网实践的大门。当你成功看到Grafana面板上那条随着你进出房间而跳动的曲线时那种亲手将物理世界信号转化为数字世界洞察的成就感正是创客精神的精髓所在。我建议你在实现基础功能后不妨试试给它加个小屏幕实时显示或者改成电池供电探索它在更多场景下的可能性。