☰
ESP32+HX711智能快递秤:防丢告警与Android App全链路实战
2026/11/4 1:04:42 网站建设 项目流程

1. 从快递被顺走说起:这个秤到底要解决什么问题

住在公寓楼里的人大概都有过这种经历:快递员把包裹往门口一放,拍张照走人,等你下班回家,门口空空如也。找快递公司,对方甩出那张"已送达"的照片,你哑口无言。找物业调监控,流程繁琐不说,很多时候角度根本拍不到。这个痛点催生了大量"门口快递防盗"的需求,而我要分享的这套Smart Parcel Scale System,思路和常见的摄像头方案完全不同——它不靠"看",靠"称"。

核心逻辑非常朴素:在门口放一块压力感应平台,快递员把包裹放上去的瞬间,系统记录下重量,同时通过手机 App 推送通知给你。包裹被拿走时,重量归零,系统再次记录时间点。如果这个"拿走"发生在你本人确认之前,那就是异常事件,App 立刻告警。整套系统的关键词是ESP32、IoT、HX711、Android Studio、3D Printer——主控用 ESP32,称重传感器信号调理用 HX711 模块,手机端用 Android Studio 开发原生 App,外壳和承重结构用 3D 打印机自己做。

这套东西适合谁?我认为有三类人值得动手:一是住在公寓、经常收快递但门口无人看管的上班族;二是想系统学习 ESP32 + 传感器 + 移动端全链路 IoT 项目的开发者;三是喜欢用 3D 打印解决实际生活问题的手工爱好者。它不需要你精通射频或嵌入式底层,但需要你有基本的接线能力、能看懂 Arduino 代码、愿意折腾 Android 环境。下面我会把整个项目从硬件选型、结构设计、固件逻辑到 App 通信,一层层拆开讲清楚,包括我踩过的坑和最后怎么绕过去的。

2. 硬件选型:为什么是 ESP32 + HX711 这套组合

2.1 ESP32 在这个项目里承担的角色

选 ESP32 而不是 Arduino Uno 或树莓派,理由很实际。这个系统需要同时做三件事:读取称重传感器、维持 Wi-Fi 连接并和手机通信、在本地做简单的状态判断。Arduino Uno 没有原生 Wi-Fi,加 ESP8266 模块会让接线和代码复杂度翻倍;树莓派功耗高、成本高,而且对于"读一个传感器 + 发个通知"这种任务属于杀鸡用牛刀。ESP32 自带 Wi-Fi 和蓝牙双模,双核处理器,价格在二十到四十块之间,深度睡眠模式下功耗可以压到微安级——这对一个需要 24 小时通电、但又不想天天充电的设备来说太关键了。

具体到型号,我建议用ESP32-WROOM-32模组开发板,也就是市面上最常见的那个版本。它引出足够多的 GPIO,供电范围 3.0V 到 3.6V,板载 USB 转串口芯片方便烧录。如果你手头有 ESP32-S3 或 C3,也能用,但要注意引脚定义不同,HX711 的数据引脚和时钟引脚需要重新映射。我实测下来,WROOM-32 的社区资料最全,遇到问题最容易搜到答案,新手别一上来就选冷门型号。

2.2 HX711 与称重传感器的匹配计算

HX711 是一颗 24 位模数转换芯片,专门为高精度称重设计。它内部集成了稳压器和时钟,只需要两个 GPIO 就能和 ESP32 通信,一个负责数据(DT),一个负责时钟(SCK)。称重传感器我选的是50kg 量程的电阻应变式传感器,也就是常见的"人体秤传感器"那种长条金属块。

这里有个关键计算必须做:HX711 是 24 位 ADC,但有效位数受噪声影响,实际可用大约 20 位左右。50kg 量程分摊到 20 位,理论分辨率是 50000g / 2^20 ≈ 0.048g。听起来很美好对吧?但实际因为传感器本身的非线性、温漂和机械安装误差,真实可用精度大概在 5g 到 10g 之间。对于判断"包裹在不在"这个需求,5g 精度绰绰有余——一个空纸箱都不止 50g。所以选 50kg 量程是合理的,既覆盖了大件快递,又保证了足够的分辨率。如果你只收小件,可以换 5kg 或 10kg 传感器,精度会更高,但量程余量就小了。

接线方面,传感器有四根线:红(E+)、黑(E-)、白(A-)、绿(A+)。红黑接 HX711 的 E+ 和 E-,白绿接 A- 和 A+。注意激励电压,HX711 的 VCC 接 ESP32 的 3.3V 还是 5V 会影响输出灵敏度,我建议接 3.3V,和 ESP32 逻辑电平一致,避免电平转换的麻烦。HX711 的 DT 接 ESP32 的 GPIO 16,SCK 接 GPIO 4,这两个引脚在 WROOM-32 上都是普通 IO,没有特殊功能冲突。

2.3 供电与结构件的取舍

供电我用的是5V 2A 的 USB 电源适配器,通过开发板的 USB 口供电。为什么不加电池?因为门口这个位置通常有插座,而且 Wi-Fi 常连的功耗不低,电池方案需要频繁充电,体验很差。如果你确实需要电池供电,可以加一块 18650 电池和 TP4056 充电模块,但代码里必须加入深度睡眠逻辑——没有包裹时每 30 秒唤醒一次读重量,有包裹时保持常连。这个改动会让固件复杂度上升不少,新手建议先跑通有线供电版本。

结构件是这套系统里最容易被低估的部分。称重传感器必须被正确安装才能准确读数:它的一端固定在底座上,另一端悬空承受平台重量。如果两端都被螺丝锁死,传感器无法形变,读数就是废的。我用3D 打印机打了一个底座和一个承重平台,底座上留了传感器一端的固定孔,平台则压在传感器悬空端。材料用 PLA 就行,但要注意平台面积不要太大,否则杠杆效应会放大误差。我第一版平台做了 20cm x 20cm,结果放偏一点读数就飘,后来缩到 15cm x 15cm,稳定性明显好转。

3. 固件开发:HX711 读数、滤波与状态机设计

3.1 让 HX711 稳定出数的三个关键设置

HX711 的 Arduino 库有好几个版本,我推荐用bogde/HX711这个库,稳定且文档清晰。初始化时有两个参数必须设置:set_scale()和tare()。set_scale()是校准系数,需要你用已知重量的物体反复测试得出。我的做法是:先放一个 500g 的砝码,读出原始值,再放 1000g 砝码,读出原始值,用差值算出每克对应的原始计数,取倒数就是 scale 值。这个过程我重复了五次取平均,最后得到的系数让 500g 砝码的读数误差控制在 2g 以内。

tare()是去皮,必须在系统启动、平台空载时调用。但这里有个坑:如果启动时平台上恰好有东西,去皮就会把那个重量当成零点。我的解决方案是在固件里加一个启动检查——如果去皮后的读数绝对值大于 20g,就闪烁板载 LED 提示"请清空平台",等待 5 秒后重新去皮。这个逻辑看起来简单,但实际部署时救了我好几次,因为快递员可能在你重启设备的瞬间正好放了个包裹。

第三个关键是采样率和滤波。HX711 默认输出速率是 10Hz(每秒 10 个样本),可以通过修改寄存器改成 80Hz,但速率越高噪声越大。我的做法是保持 10Hz,然后在代码里做滑动平均:连续读 10 个样本,去掉最大值和最小值,剩下 8 个取平均。这样一次有效读数需要 1 秒,对于称重场景完全够用。实测下来,滤波后的读数波动在 ±3g 以内,比原始数据稳定了一个数量级。

3.2 状态机:从"重量变化"到"事件"的映射

光有重量数据没用,系统需要把重量变化翻译成有意义的事件。我设计了一个四状态机:IDLE(空闲)、PLACED(已放置)、CONFIRMED(已确认)、ALERT(告警)。状态转换逻辑如下:

  • IDLE 状态下,如果重量从接近 0 跳变到大于 50g,进入 PLACED,记录时间戳和重量,通过 Wi-Fi 推送"有新包裹"通知到手机。
  • PLACED 状态下,如果重量回到接近 0,说明包裹被拿走。此时检查手机端是否发来过"确认收货"指令。如果有,进入 CONFIRMED,然后回到 IDLE;如果没有,进入 ALERT,推送"包裹异常移动"告警。
  • ALERT 状态下,持续推送告警直到用户在 App 里手动解除,然后回到 IDLE。

这个状态机的关键在于阈值设定。50g 的放置阈值是为了过滤掉风吹、猫踩、平台自身热胀冷缩带来的微小波动。而"接近 0"我定义为小于 20g,留出一点余量。这两个阈值我调了大概一周,最后发现 50g 和 20g 的组合误报率最低。如果你住在风大的走廊,可以把放置阈值提到 100g,但那样小件快递可能识别不到,需要自己权衡。

3.3 Wi-Fi 通信协议:MQTT 还是 HTTP

ESP32 和手机通信有两条路:MQTT 和 HTTP。我一开始用 HTTP,ESP32 作为服务器,手机定时轮询。问题很明显:轮询间隔短了费电,间隔长了事件延迟大,而且手机在后台时轮询会被系统限制。后来换成MQTT,ESP32 作为客户端连接到公共 MQTT 代理(比如 EMQX 的免费实例),手机也连同一个代理,订阅同一个主题。这样事件是推送式的,延迟在 1 秒以内,而且 ESP32 不需要一直保持 HTTP 连接,功耗更低。

MQTT 的主题设计我用了三层结构:parcel/scale01/event用于事件推送,parcel/scale01/command用于手机下发指令。消息体用 JSON,比如{"type":"placed","weight":1250,"ts":1690000000}。这里要注意QoS 等级,我选的是 QoS 1(至少送达一次),因为包裹事件丢一条可能就意味着一次误判。QoS 2 虽然更可靠但开销大,对于这个场景没必要。

还有一个细节:ESP32 的 MQTT 客户端要设置keepalive和last will。keepalive 我设了 60 秒,last will 主题设为parcel/scale01/status,消息为offline。这样如果 ESP32 掉线,手机会收到离线通知,而不是傻等。这个设计在实际使用中很有用,有一次我家路由器重启,手机立刻收到了离线告警,而不是以为包裹一直没动。

4. Android App:从 MQTT 订阅到告警通知的完整链路

4.1 用 Android Studio 搭建最小可用工程

Android 端我用Android Studio开发,语言选 Kotlin,最低 API 等级设到 26(Android 8.0),这样能覆盖绝大多数在用设备,同时可以用通知渠道(Notification Channel)等新特性。工程结构很简单:一个 MainActivity 显示当前状态和重量,一个 MqttService 负责后台连接和消息接收,一个 NotificationHelper 负责推送告警。

MQTT 客户端库我用的是Eclipse Paho Android,在build.gradle里加依赖org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5和org.eclipse.paho:org.eclipse.paho.android.service:1.1.1。注意 Paho 的 Android Service 版本比较老,在新版 Android 上需要手动处理前台服务权限,否则后台连接会被杀。我的做法是把 MqttService 设为前台服务,在通知栏常驻一个"快递秤运行中"的通知,这样系统就不会回收它。

连接参数方面,broker 地址、端口、用户名密码我放在local.properties里,不硬编码在代码中。端口用 8883(MQTT over TLS),虽然公共代理的免费实例不一定支持 TLS,但如果你自己搭 broker,强烈建议开 TLS,否则重量数据在公网上裸奔。客户端 ID 用android-加设备唯一标识,避免多个手机连同一个 broker 时互相踢下线。

4.2 消息解析与本地状态同步

App 收到 MQTT 消息后,需要解析 JSON 并更新 UI。这里有个常见问题:MQTT 回调在后台线程,直接更新 UI 会崩溃。我的做法是用LiveData或StateFlow把消息从 Service 传到 Activity。具体来说,MqttService 里维护一个MutableLiveData<ParcelEvent>,收到消息后postValue,Activity 里observe这个 LiveData 并更新界面。这样线程安全,代码也清晰。

本地状态同步是另一个容易出问题的地方。App 需要知道当前是"有包裹"还是"无包裹",这个状态不能只靠最后一条消息判断,因为消息可能丢失。我的方案是在 App 本地维护一个状态变量,每次收到placed事件就设为"有包裹",收到removed事件就设为"无包裹",同时记录时间戳。如果超过 24 小时没有收到任何事件,就显示"状态未知,请检查设备"。这个兜底逻辑避免了 App 显示的状态和实际不符。

4.3 告警通知的优先级与免打扰处理

告警通知是这套系统的核心价值,如果通知被系统静音或折叠,整个项目就白做了。我在 NotificationHelper 里做了三件事:第一,创建独立的通知渠道,重要性设为HIGH,这样通知会弹出横幅并响铃;第二,设置setCategory(Notification.CATEGORY_ALARM),让系统知道这是告警类通知;第三,对于"包裹异常移动"这种紧急事件,使用setFullScreenIntent在锁屏上直接显示。

但这里有个平衡:如果用户在家,包裹被家人拿进屋,也会触发告警。我的解决方案是在 App 里加一个"我已知晓"按钮,用户点击后,App 向parcel/scale01/command发送{"action":"confirm"},ESP32 收到后进入 CONFIRMED 状态,不再告警。同时 App 本地记录这次确认,如果 5 分钟内再次发生重量变化,不再重复告警。这个逻辑把误报的干扰降到了最低。

还有一个细节:Android 的通知权限在 API 33 之后需要动态申请,我在 MainActivity 启动时就请求POST_NOTIFICATIONS权限,如果用户拒绝,就在界面上显示一个红色横幅提示"通知权限未开启,告警功能不可用"。这个提示很直白,但确实有效,我测试时有好几次就是因为没给权限导致以为系统坏了。

5. 3D 打印结构件:从建模到装配的实战细节

5.1 底座与承重平台的建模要点

3D 打印部分我用的是Fusion 360建模,导出 STL 后用 Cura 切片。底座的设计目标是:稳固、能固定传感器一端、走线方便。我画了一个 120mm x 80mm x 15mm 的矩形底座,内部挖空减轻重量,四个角留了 M3 螺丝孔用于固定传感器。传感器的一端用两颗 M3 螺丝锁在底座的凸台上,另一端悬空,悬空端上方留出 5mm 间隙,让承重平台可以压下去。

承重平台是一个 150mm x 150mm x 8mm 的平板,底部有一个凸起的圆柱,正好压在传感器的悬空端。这个圆柱的位置很关键:必须对准传感器的受力点,否则会产生侧向力矩,导致读数不准。我第一版把圆柱放在平台正中央,结果传感器受力不均,读数漂移严重。后来把圆柱移到平台中心偏传感器一侧 10mm 的位置,读数立刻稳定了。这个偏移量需要根据你的传感器安装位置实际调整,没有固定公式。

5.2 打印参数与材料选择

打印参数我用的是一套比较保守的设置:层高 0.2mm,壁厚 1.2mm(3 层),填充 20% 的网格。底座和平台都用PLA,因为室内使用,PLA 的刚性和尺寸稳定性足够,而且打印容易。如果你住在温差大的地方,可以考虑 PETG,韧性更好,但打印温度要高一些。

这里有个坑:PLA 在持续受力下会发生蠕变,也就是慢慢变形。承重平台如果长期压着传感器,几个月后可能会轻微弯曲,导致零点漂移。我的应对方法是把平台设计得厚一点(8mm),并且在固件里加入"自动重新去皮"逻辑——每天凌晨 3 点,如果重量读数在 ±10g 以内,就自动执行一次tare()。这个逻辑运行了半年,零点漂移基本被控制住了。

装配时,传感器的固定螺丝不要拧太紧。我一开始用蛮力拧紧,结果传感器金属体被压变形,读数直接废了。正确的做法是拧到感觉有阻力就停,然后用螺纹胶固定,防止松动。这个教训很深刻,一颗传感器十几块钱,但重新拆装和校准花了我一个下午。

5.3 防水与线缆管理

门口这个位置,防水是个现实问题。我的做法是在底座底部贴一圈橡胶垫,把电路板藏在底座内部的空腔里,只留一个 USB 线出口。USB 线出口朝下,防止雨水倒灌。ESP32 和 HX711 板子我用热熔胶固定在底座内壁,避免晃动。如果你住在多雨地区,可以在底座上盖一块亚克力板,用螺丝固定,但要注意不要挡住传感器。

线缆管理方面,传感器到 HX711 的四根线我用热缩管包好,沿着底座内壁走线,用扎带固定。电源线从底座侧面引出,留出 10cm 的余量,避免拉扯。整个装配完成后,我用手按压平台各个角落,确认读数一致性在 ±5g 以内,才算合格。这个测试很重要,因为如果平台某个角受力时读数偏差大,说明传感器安装有侧向应力,需要重新调整。

6. 校准、测试与长期运行中的真实问题

6.1 校准流程:从原始值到可信重量

校准是这套系统精度的生命线。我的校准流程分三步:第一步,空载去皮,记录零点原始值;第二步,放 500g 标准砝码,记录原始值,计算scale = (raw_500 - raw_0) / 500;第三步,放 2000g 砝码验证,如果误差超过 10g,就调整 scale 值重新验证。这个过程我重复了三轮,最后 scale 值稳定在 2280 左右(不同传感器这个值不同)。

但校准不是一劳永逸的。温度变化会影响传感器的输出,我实测在 10°C 到 30°C 的范围内,零点漂移大约 15g。对于判断包裹有无来说,15g 的漂移可以接受,但如果你要精确称重,就需要加温度补偿。我的做法是在固件里加一个简单的线性补偿:如果温度传感器读数低于 15°C,零点阈值上调 10g;高于 25°C,下调 10g。这个补偿很粗糙,但实际用下来,误报率从每周两三次降到了每月一次。

6.2 实测中遇到的三个典型问题

问题一:Wi-Fi 断连后不重连。ESP32 的 Wi-Fi 库在路由器重启后有时不会自动重连。我的解决方案是在loop()里加一个看门狗逻辑:每 30 秒检查一次 Wi-Fi 状态,如果断开就调用WiFi.reconnect(),连续失败 5 次就重启 ESP32。这个逻辑加上后,再也没出现过"设备在线但收不到消息"的情况。

问题二:MQTT 消息重复。因为用了 QoS 1,消息可能重复送达。App 端如果不做去重,会收到两条"有新包裹"通知。我的做法是在消息体里加一个msgId,App 维护一个最近 100 条 msgId 的集合,收到重复的就丢弃。这个改动很小,但体验提升明显。

问题三:手机后台被杀。国产 Android 系统对后台服务管控很严,Paho 的 Service 经常被清理。我的应对是引导用户在系统设置里把 App 加入"电池优化白名单",同时在 App 里加一个"保持后台运行"的开关,开启后显示常驻通知。这个方案不完美,但比什么都不做强。

6.3 长期运行的数据观察与优化

这套系统我连续运行了 8 个月,积累了一些数据。平均每天触发 3 到 5 次事件,其中误报大约每周 1 次,主要来源是清洁阿姨打扫时碰到平台、以及大风天气。针对清洁阿姨的问题,我在 App 里加了"临时静默"功能,用户可以设置一个时间段(比如上午 9 点到 11 点),期间不推送告警,只记录事件。这个功能很实用,建议你开发时就加上。

功耗方面,ESP32 常连 Wi-Fi 的电流大约 80mA,加上 HX711 和传感器,整体约 100mA。5V 2A 的电源绰绰有余,但如果你用劣质电源,电压波动会导致 HX711 读数不稳。我换了一个带稳压的电源后,读数噪声明显降低。这个细节容易被忽略,但影响很大。

7. 这套系统还能怎么扩展

如果你已经跑通了基础版本,有几个扩展方向值得尝试。第一个是加装摄像头,用 ESP32-CAM 在重量变化时拍一张照片,通过 MQTT 把图片的 Base64 编码发给手机。这样你不仅知道包裹到了,还能看到包裹长什么样。但 ESP32-CAM 和普通 ESP32 的引脚冲突较多,需要换用带 PSRAM 的型号,代码也要重新整合。

第二个是多设备组网。如果你有多个门口或多个住户共用,可以用多个秤节点,每个节点用不同的 MQTT 主题,App 端做设备切换。这个改动主要在 App 端,固件只需要改主题前缀。

第三个是接入语音助手。通过 IFTTT 或类似的自动化平台,把 MQTT 事件转发到语音助手,实现"包裹到了"的语音播报。这个方案需要额外的云服务配置,但不需要改硬件。

我个人最推荐的扩展是第一个,因为视觉确认能极大降低误报的困扰。不过要注意隐私问题,摄像头角度要调好,只拍包裹区域,不要拍到公共走廊或邻居门口。这一点在公寓环境里尤其重要,动手前最好和物业沟通一下。

最后分享一个我在调试阶段用的小技巧:在 ESP32 固件里加一个串口命令接口,通过 USB 串口发送tare、scale=xxxx、status等命令,可以不用重新烧录就完成校准和状态查询。这个接口在前期调试时节省了大量时间,强烈建议你在开发阶段就加上。

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

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

立即咨询