1. 从快递丢件说起这个项目到底要解决什么问题先讲个我自己的糟心事。去年黑五我在网上抢了一台打折的显示器物流显示已签收可我人在公司家里根本没人。打电话问快递对方说放门口了照片为证。等我晚上到家门口空空如也。找平台、找快递、找物业折腾了半个月最后平台赔了一半剩下的一半自己认栽。这种事你大概率也遇到过——包裹被放在门口、被邻居误拿、被顺手牵羊而整个链条里没有任何一个环节能证明包裹到底有没有真正交到该收的人手里。这个Smart Parcel Scale System for Secure Amazon Deliveries智能包裹称重系统用于安全的包裹投递就是冲着这个痛点去的。它的核心思路非常朴素在包裹被放下的一瞬间用重量这个物理量来确认东西确实到了。具体来说就是在门口或指定收件点放一个带称重功能的智能底座快递员把包裹放上去系统自动读取重量、拍照、记录时间戳然后通过手机 App 推送给收件人。收件人确认后系统才认为这次投递完成。如果包裹被人拿走重量归零系统同样会记录并告警。这套东西能做什么简单说三件事称重留证、异常告警、远程确认。它解决的是最后一米的信任问题——不是解决快递送不送而是解决送了但你没收到这种扯皮。适合谁来参考我觉得有三类人一是喜欢折腾智能家居的 DIY 玩家二是做物联网课程设计或毕业设计的学生三是想给自家门口加一道物理证据链的普通用户。技术栈上它把ESP32、HX711 称重模块、Android App、3D 打印外壳这几样东西串成了一个完整闭环门槛不算高但每个环节都有坑。我前后花了大概三周时间把这套系统从零搭到能跑中间踩的坑比想象中多。下面我把整个设计思路、硬件选型、代码实现、App 开发和外壳制作全部拆开讲尽量让你看完就能照着复现。2. 整体方案怎么搭为什么是 ESP32 HX711 Android 这套组合2.1 核心架构拆解一个称重终端 云端 手机端的三层结构这套系统的架构其实不复杂我把它分成三层来看。第一层是感知层也就是放在门口的称重终端。它的核心是一块 ESP32 开发板加一个 HX711 称重模块再配一个称重传感器我用的 5kg 量程的电阻应变式传感器。ESP32 负责读取重量数据、控制状态指示灯、通过 Wi-Fi 把数据发出去。为什么选 ESP32 而不是 Arduino Uno 加 Wi-Fi 模块因为 ESP32 自带 Wi-Fi 和蓝牙双核 240MHz价格还便宜一块开发板三十来块钱就能搞定省去了额外配网络模块的麻烦和成本。而且 ESP32 的 GPIO 资源足够后面想加个超声波传感器或者摄像头都有余量。第二层是通信层我用的是 MQTT 协议。ESP32 把重量数据、时间戳、设备状态打包成 JSON通过 MQTT 发到 broker。为什么不用 HTTP 轮询因为称重场景需要的是事件触发而不是定时查询——包裹放上去的那一刻要立刻上报HTTP 轮询要么延迟高要么耗电。MQTT 的长连接加发布订阅模型天然适合这种场景而且消息体小ESP32 处理起来毫无压力。Broker 我一开始用的是公共测试服务器后来换成了自己搭的稳定性好很多。第三层是应用层也就是 Android App。App 订阅 MQTT 主题收到消息后解析重量、时间、设备 ID展示在界面上同时触发通知。用户可以在 App 里确认收货、查看历史记录、设置重量阈值告警。为什么用 Android 而不是做个网页因为推送通知在移动端体验更好而且 Android Studio 开发原生 App 对蓝牙、本地存储、后台服务的支持更完整。如果你只想快速验证用 Web 端也行但要做成能日常用的东西原生 App 更靠谱。2.2 为什么选 HX711 而不是其他称重方案称重这块市面上常见的有几种方案一是直接用带数字输出的电子秤模块二是用 ADS1232 这类高精度 ADC三是用 HX711。我选 HX711 的理由很实在。HX711 是专门为电子秤设计的 24 位模数转换芯片内置了增益可调的放大器直接接电阻应变式传感器就能用外围电路极其简单——基本上传感器四根线接上去HX711 两根线接 ESP32 就能工作。它的精度对于包裹称重这个场景完全够用5kg 量程下能分辨到 1g 左右实际测试中放一个 200g 的手机上去读数稳定在 199~201g 之间波动这个精度足够了。价格上HX711 模块也就几块钱比 ADS1232 便宜一个数量级。当然它也有缺点。HX711 的采样率默认是 10Hz也就是每秒 10 次读数对于静态称重够用但如果你想做动态称重比如传送带上称就不够了。另外它的温漂比较明显夏天和冬天读数会有差异这个后面在软件里做补偿。还有个坑是 HX711 的供电电压会影响零点所以整个系统的供电要稳定不能用那种劣质充电头。2.3 3D 打印外壳不只是好看更是功能的一部分很多人觉得外壳就是套个壳子但这个项目里外壳承担了实际功能。称重传感器需要固定在底座和承重板之间如果固定不牢包裹放上去的时候传感器会晃动读数就会跳。我用 3D 打印做了一个三明治结构底层是固定传感器的底座中间是传感器上层是承重板。承重板通过四个角的螺丝柱和传感器连接保证受力均匀。材料上我用的是 PLA打印厚度 0.2mm填充率 20%。为什么不用更结实的 PETG因为 PLA 硬度够、打印容易、成本低而且这个外壳不承受高温和户外暴晒我放在门口有遮雨棚的地方。如果你要放完全露天建议用 ASA 或者 PETG耐候性好一些。打印的时候有个细节承重板和底座的接触面要留 0.5mm 的间隙不然打印出来会粘在一起装传感器的时候掰不开。3. 硬件选型与电路连接每个元件为什么这么选3.1 核心元件清单与选型理由我把整个系统的硬件清单列出来每一项都说说为什么选它。元件型号/规格选型理由参考价格主控ESP32-WROOM-32 开发板自带 Wi-Fi/蓝牙双核GPIO 充足30-40 元称重模块HX711 模块24 位 ADC专为电子秤设计接线简单5-8 元称重传感器电阻应变式5kg 量程量程覆盖大多数包裹精度够用15-25 元电源5V 2A 适配器 AMS1117-3.3稳定供电避免读数漂移10 元指示灯WS2812B 灯珠单线控制颜色可编程2 元外壳PLA 3D 打印固定传感器保护电路材料费约 15 元这里重点说几个选型细节。称重传感器量程我选 5kg因为大多数快递包裹在 0.5kg 到 3kg 之间5kg 留了余量。如果你经常收大件可以换 10kg 或 20kg 的但量程越大小重量下的精度越差这是物理规律。电源这块我踩过坑一开始用手机充电头供电读数一直跳后来换了个带滤波的 5V 2A 适配器再加一级 AMS1117 稳压到 3.3V 给 HX711 和 ESP32 供电读数才稳下来。HX711 对电源纹波很敏感这个钱不能省。3.2 接线图与关键连接说明接线这块我直接说清楚不画图了你照着接就行。HX711 和 ESP32 的连接HX711 的 VCC 接 ESP32 的 3.3VHX711 的 GND 接 ESP32 的 GNDHX711 的 DT数据接 ESP32 的 GPIO 16HX711 的 SCK时钟接 ESP32 的 GPIO 4HX711 和称重传感器的连接传感器红线激励正接 HX711 的 E传感器黑线激励负接 HX711 的 E-传感器白线信号正接 HX711 的 A传感器绿线信号负接 HX711 的 A-WS2812B 指示灯DIN 接 ESP32 的 GPIO 5VCC 接 5VGND 接 GND注意HX711 的 DT 和 SCK 不要接错接反了读出来全是 0 或者乱码。另外传感器那四根线的颜色不同厂家可能不一样拿到手先用万用表量一下确认哪两根是激励、哪两根是信号别死记颜色。3.3 供电与抗干扰处理供电这块我要单独拎出来讲因为它是整个系统稳定性的基础。ESP32 在 Wi-Fi 发射瞬间电流会冲到 200mA 以上如果电源响应慢电压会瞬间跌落导致 HX711 读数跳变甚至 ESP32 重启。我的做法是5V 适配器进来后先并一个 470uF 的电解电容做储能再经过 AMS1117-3.3 稳压给 HX711 和 ESP32 的 3.3V 引脚供电。ESP32 的 5V 引脚单独从适配器取电不走稳压器。另外HX711 的模拟部分和数字部分要分开走线传感器信号线尽量短最好用屏蔽线。我在传感器和 HX711 之间加了磁珠和 0.1uF 电容做滤波读数稳定性提升很明显。这些细节看起来麻烦但如果你不做后面调试的时候会怀疑人生。4. ESP32 固件开发从读数到上报的完整实现4.1 开发环境搭建与国内源配置ESP32 的开发环境我用的是 Arduino IDE因为库多、上手快。但国内下载 ESP32 支持包经常卡住这里说下怎么配国内源。首先在 Arduino IDE 的首选项里把附加开发板管理器网址改成国内的镜像地址。然后在开发板管理器里搜索 esp32安装最新版本。如果还是慢可以手动下载离线包解压到 Arduino 的 hardware 目录下。我实测用国内源之后下载速度从几 KB/s 提升到几 MB/s几分钟就装完了。库方面需要装三个HX711称重、PubSubClientMQTT、ArduinoJsonJSON 打包。这三个库在库管理器里直接搜就能装注意 HX711 有好几个版本我用的 Bogdan Necula 那个版本API 比较稳定。4.2 称重读数与校准HX711 的皮重和比例系数怎么算HX711 读出来的原始值是一个 24 位整数需要转换成克数。转换公式是重量 (原始值 - 皮重值) / 比例系数。皮重值就是空载时的读数比例系数是每克对应的原始值变化量。校准步骤我详细说下。第一步空载读 20 次原始值取平均记为tare。第二步放一个已知重量的物体我用的是 500g 标准砝码没有的话用一袋 500g 的盐也行读 20 次取平均记为raw。第三步比例系数 (raw - tare) / 500。把这个系数写进代码里。#include HX711.h HX711 scale; float calibration_factor -2280.0; // 根据实际校准调整 float tare_offset 0; void setup() { scale.begin(16, 4); // DT16, SCK4 scale.set_scale(calibration_factor); delay(1000); tare_offset scale.get_units(20); scale.tare(); } float readWeight() { if (scale.is_ready()) { float w scale.get_units(10) - tare_offset; return (w 0) ? 0 : w; // 负值归零 } return -1; }注意校准系数是负数还是正数取决于传感器受力方向如果读出来重量是负的把系数符号反过来就行。另外每次重启后建议重新去皮因为温漂会导致零点偏移。4.3 MQTT 上报与 JSON 数据格式设计数据上报我用的是 MQTT主题设计成parcel/scale/{device_id}/weight。消息体是 JSON包含重量、时间戳、设备状态。#include WiFi.h #include PubSubClient.h #include ArduinoJson.h const char* ssid your_wifi; const char* password your_password; const char* mqtt_server your_broker_ip; WiFiClient espClient; PubSubClient client(espClient); void publishWeight(float weight) { StaticJsonDocument200 doc; doc[device_id] scale_01; doc[weight_g] weight; doc[timestamp] millis(); doc[status] (weight 50) ? occupied : empty; char buffer[256]; serializeJson(doc, buffer); client.publish(parcel/scale/scale_01/weight, buffer); }为什么用 JSON 而不是纯文本因为后面 App 端要解析多个字段JSON 结构清晰扩展也方便。比如以后想加温度、湿度直接往 doc 里塞就行App 端加个字段解析即可。时间戳我用的是millis()因为 ESP32 没有 RTC联网后可以用 NTP 同步真实时间这个后面在 App 端做转换。4.4 状态机设计空载、称重中、已确认三种状态怎么切换系统不能一直上报数据那样太耗电也刷屏。我设计了一个简单的状态机只有状态变化时才上报。空载状态重量小于 50g指示灯绿色常亮。称重中重量大于 50g 且持续 3 秒指示灯蓝色闪烁上报一次数据。已确认App 端确认收货后通过 MQTT 下发确认指令ESP32 收到后指示灯绿色慢闪进入待机。状态切换的代码逻辑用switch-case实现每 100ms 检测一次重量连续 30 次都大于阈值才认为稳定放置避免手放上去的瞬间误触发。enum State { EMPTY, WEIGHING, CONFIRMED }; State currentState EMPTY; void loop() { float w readWeight(); static int stableCount 0; if (w 50) { stableCount; if (stableCount 30 currentState EMPTY) { currentState WEIGHING; publishWeight(w); setLED(0, 0, 255); // 蓝色 } } else { stableCount 0; if (currentState WEIGHING) { currentState EMPTY; setLED(0, 255, 0); // 绿色 } } client.loop(); delay(100); }5. Android App 开发从 MQTT 订阅到界面展示5.1 Android Studio 环境准备与项目创建Android Studio 的安装我就不啰嗦了官网下载安装包一路下一步就行。国内下载慢的话用国内镜像站下载。安装完之后新建一个 Empty Activity 项目语言选 Kotlin最低 SDK 版本选 API 24Android 7.0这样覆盖面够广。项目建好后在build.gradle里加 MQTT 库的依赖。我用的是 Eclipse Paho 的 Android 客户端稳定且文档多。dependencies { implementation org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5 implementation org.eclipse.paho:org.eclipse.paho.android.service:1.1.1 }注意Paho 的 Android Service 在新版本 Android 上有后台限制需要在 Manifest 里声明前台服务否则 App 切到后台就收不到消息了。这个坑我踩过调试了半天才发现是后台限制的问题。5.2 MQTT 连接与消息订阅实现App 端连接 MQTT broker 的代码不复杂核心是设置好 broker 地址、端口、客户端 ID然后订阅主题。val clientId android_ System.currentTimeMillis() val client MqttAndroidClient(this, tcp://your_broker_ip:1883, clientId) val options MqttConnectOptions().apply { isAutomaticReconnect true isCleanSession true } client.setCallback(object : MqttCallback { override fun messageArrived(topic: String?, message: MqttMessage?) { val payload String(message!!.payload) val json JSONObject(payload) val weight json.getDouble(weight_g) val status json.getString(status) runOnUiThread { updateUI(weight, status) } } override fun connectionLost(cause: Throwable?) { } override fun deliveryComplete(token: IMqttDeliveryToken?) { } }) client.connect(options, null, object : IMqttActionListener { override fun onSuccess(asyncActionToken: IMqttToken?) { client.subscribe(parcel/scale/scale_01/weight, 1) } override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) { } })这里有个细节messageArrived是在后台线程回调的更新 UI 必须切到主线程用runOnUiThread包一下不然会崩。5.3 界面设计与交互逻辑界面我做得比较简单一个主页面显示当前重量、状态、时间下面一个列表显示历史记录再下面两个按钮确认收货和清空记录。主页面用ConstraintLayout布局顶部是一个大号数字显示重量中间是状态文字底部是按钮。历史记录用RecyclerView每条记录显示时间、重量、状态。确认收货按钮点击后App 通过 MQTT 向parcel/scale/scale_01/confirm主题发一条消息ESP32 收到后切换状态。confirmButton.setOnClickListener { val message JSONObject().apply { put(action, confirm) put(timestamp, System.currentTimeMillis()) }.toString() client.publish(parcel/scale/scale_01/confirm, message.toByteArray(), 1, false) Toast.makeText(this, 已确认收货, Toast.LENGTH_SHORT).show() }界面配色我用的是深色主题因为门口的设备晚上看的时候不刺眼。重量数字用大号字体一眼就能看清。状态文字用颜色区分绿色是空载蓝色是称重中黄色是已确认。5.4 通知与后台服务App 切到后台后MQTT 连接可能会断。为了保证能收到推送我用了一个前台服务在通知栏常驻一个包裹监控中的通知。这样系统不会杀掉服务MQTT 连接也能保持。class MqttService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val notification NotificationCompat.Builder(this, parcel_channel) .setContentTitle(包裹监控中) .setContentText(正在等待包裹投递) .setSmallIcon(R.drawable.ic_notification) .build() startForeground(1, notification) // 初始化 MQTT 连接 return START_STICKY } }注意Android 10 以后前台服务需要在 Manifest 里声明foregroundServiceType不然启动会报错。另外通知渠道要在 Android 8.0 以上创建不然通知不显示。6. 3D 打印外壳从建模到装配的完整流程6.1 建模要点与结构设计外壳我用 Fusion 360 建模分三个零件底座、承重板、电路仓盖。底座是核心它要固定称重传感器。传感器两端各有两个 M4 螺丝孔底座上对应位置留孔用螺丝把传感器一端固定在底座上另一端悬空。悬空那端连接承重板。底座内部挖空一个仓放 ESP32 和 HX711侧面开孔走线。承重板是一块平板四个角有螺丝柱其中两个角连接传感器的悬空端另外两个角用弹簧或者橡胶垫支撑保证受力均匀。承重板表面我贴了一层防滑垫防止包裹滑动。电路仓盖就是一块薄板卡在底座底部保护电路。上面开散热孔因为 ESP32 工作时会发热。建模的时候有个关键尺寸传感器悬空端到固定端的距离。这个距离决定了力臂长度力臂越长同样重量下传感器形变越大读数越灵敏。但太长的话结构不稳。我试了几个尺寸最后定在 60mm5kg 量程下读数很稳。6.2 打印参数与后处理打印参数我列一下你可以直接抄。参数设置值说明材料PLA硬度够打印容易层高0.2mm精度和速度平衡填充率20%承重板可以加到 40%打印温度200°CPLA 标准温度热床温度60°C保证附着力支撑需要底座悬空部分要支撑打印完之后把支撑拆掉用砂纸打磨一下接触面。螺丝孔如果偏小用钻头扩一下。承重板和底座的接触面要涂一点润滑脂减少摩擦让传感器受力更纯粹。6.3 装配与传感器固定技巧装配顺序很重要。先把传感器固定在底座上螺丝不要拧太紧留一点活动余量。然后把承重板装上去调整四个角的支撑高度让承重板水平。最后把 ESP32 和 HX711 放进电路仓接好线盖上盖子。注意传感器固定螺丝拧太紧会导致传感器预变形读数不准。我的经验是拧到刚好不晃动就行然后用热熔胶点一下防松。另外承重板装好后空载读数应该接近零如果偏差大说明装配有应力需要重新调整。7. 常见问题与排查技巧实录7.1 称重读数不稳定怎么办这是最常见的问题。读数跳动的可能原因有四个电源纹波大、传感器固定不牢、HX711 接线太长、附近有振动源。排查顺序先换一个质量好的 5V 电源加滤波电容然后检查传感器螺丝是否松动再把 HX711 和传感器的连线缩短最好用屏蔽线最后把整个装置放在平稳的桌面上远离音箱、风扇等振动源。我实测下来电源和固定这两项解决了 80% 的跳动问题。7.2 MQTT 连接频繁断开怎么处理ESP32 端 MQTT 断开通常是 Wi-Fi 信号弱或者 broker 设置了心跳超时。解决办法把 Wi-Fi 路由器挪近一点或者在代码里设置client.setKeepAlive(60)把心跳间隔调大。App 端断开通常是后台限制用前台服务解决。7.3 App 收不到推送通知的排查先确认 App 有没有通知权限Android 13 以后通知权限要动态申请。然后确认前台服务有没有启动通知渠道有没有创建。最后检查 MQTT 订阅的主题和 ESP32 发布的主题是否一致大小写敏感。7.4 常见问题速查表问题现象可能原因解决方法读数一直为 0HX711 接线错误检查 DT/SCK 是否接反读数跳动大电源纹波/固定不牢换电源加滤波紧固螺丝重量偏负校准系数符号错把系数改成负数MQTT 连不上broker 地址/端口错检查 IP 和端口确认 broker 运行App 闪退后台线程更新 UI用 runOnUiThread 包裹通知不显示通知渠道未创建在 onCreate 里创建渠道7.5 几个我踩过的坑和独家技巧第一个坑是 HX711 的供电。我一开始把 HX711 的 VCC 接到 ESP32 的 5V 引脚结果读数一直漂。后来查手册才知道 HX711 的模拟部分最好用 3.3V 供电数字部分可以用 5V但模块上一般是统一供电的所以直接接 3.3V 最稳。第二个坑是 MQTT 的 QoS 设置。我一开始用 QoS 0消息偶尔丢。后来改成 QoS 1虽然会重复但至少不丢。对于称重这种场景重复比丢失好处理App 端做个去重就行。第三个技巧是校准的时候用多次平均。单次读数波动大我读 50 次取中位数比取平均值更抗异常值。这个技巧在传感器读数处理里很通用。第四个技巧是 App 端做本地缓存。网络断了的时候把收到的消息存到本地数据库网络恢复后再同步。我用的是 Room 数据库简单几行代码就能实现。8. 系统联调与实测数据8.1 联调步骤与验证方法联调我按这个顺序来先单独测 ESP32 读数串口打印重量确认校准正确然后测 MQTT 上报用 MQTT 客户端工具订阅主题看能不能收到 JSON接着测 App 订阅确认界面能更新最后测确认收货的闭环App 发确认指令ESP32 收到后状态切换。每一步都要单独验证不要跳步。我一开始想省事直接全联调结果出了问题不知道是哪一层的排查起来很痛苦。8.2 实测数据与精度分析我用标准砝码测了一组数据结果如下。砝码重量读数平均值误差100g101.2g1.2%500g498.7g-0.26%1000g1002.3g0.23%2000g1996.8g-0.16%小重量下误差偏大这是因为传感器在低量程段非线性明显。实际使用中包裹重量一般大于 200g误差在 0.5% 以内完全够用。如果要做高精度可以换 1kg 量程的传感器但那样大包裹就超量程了需要权衡。8.3 功耗与续航考虑如果要做电池供电版本功耗是个问题。ESP32 在 Wi-Fi 常连状态下平均电流约 80mA5V 2A 的适配器供电没问题但用电池的话撑不了多久。我的建议是如果放门口有插座直接插电如果没有可以用深度睡眠模式称重传感器用中断唤醒但 HX711 本身没有中断输出需要加一个比较器电路。这个我还没做算是后续扩展方向。9. 后续扩展与个人体会这套系统跑了一个多月中间收了大大小小十几个包裹整体稳定。有两次快递员把包裹放上去后没确认就走了App 推送了通知我远程确认了记录里都有据可查。还有一次邻居误拿重量归零后 App 告警我下楼一看果然是邻居拿错了当场就还回来了。这种物理证据链的价值用过才知道。后续我想加几个功能一是加一个摄像头模块称重的同时拍照证据更完整二是加一个蜂鸣器包裹放上去的时候响一声提醒快递员已记录三是把数据存到云端数据库做长期统计看看快递员一般几点来、包裹平均多重。这些扩展都不难ESP32 的资源和 MQTT 的扩展性都够。最后分享一个小技巧如果你也做类似的项目先把最小闭环跑通再优化。我最开始想一步到位结果卡在 HX711 校准上两天。后来退一步先用串口打印读数确认硬件没问题再往上加 MQTT 和 App半天就通了。这个思路在物联网项目里特别管用因为涉及硬件、网络、应用三层任何一层出问题都会让你怀疑人生分层验证是最省时间的做法。 SEO 优化官网定制响应式建站教育培训建站