1. 项目缘起与整体设计思路
快递放在门口被顺手牵羊,这事儿我自己就遇到过两回。一次是楼下邻居误拿,另一次就再也没找回来。跟几个做智能家居的朋友聊起来,发现大家都有类似痛点:包裹送达时人不在家,放门口不放心,放快递柜又经常满格。市面上虽然有带锁的快递箱,但价格动辄上千,而且没法记录“谁在什么时候放了多重的包裹”这种关键信息。
这个项目的出发点很直接:用一块 ESP32 做主控,搭配 HX711 称重模块和压力传感器,做一个能识别“包裹是否放上来、放了多重”的智能秤台,再通过蓝牙把数据推给手机 App。手机端用 Android Studio 开发一个轻量应用,实时显示重量变化并记录时间戳。外壳部分用 3D 打印机自己打,尺寸和结构完全按需定制。整套方案的成本控制在两百元以内,核心逻辑就是:重量突变即事件触发。
为什么选 ESP32 而不是 Arduino Uno?原因有三个。第一,ESP32 自带蓝牙和 Wi-Fi,省掉外接模块的麻烦和额外开销;第二,它的双核处理器在处理称重数据的同时还能跑蓝牙协议栈,不会因为采样延迟导致数据丢包;第三,ESP32 的深度睡眠模式功耗极低,用电池供电也能撑很久。HX711 是称重传感器专用的 24 位模数转换芯片,内置放大器,直接读应变片桥式传感器的微弱信号,精度足够分辨几十克的差异。Android 端用 Java 或 Kotlin 写一个蓝牙 SPP 客户端,接收 ESP32 发来的重量字符串,解析后更新 UI 并存入本地数据库。
整个系统的数据流是这样的:压力传感器受到包裹重力产生微弱电压变化,HX711 把这个模拟信号转换成数字量,ESP32 通过 GPIO 读取 HX711 的输出,经过校准计算得到实际重量值,再通过蓝牙串口协议发送到手机。手机 App 收到数据后判断重量是否超过预设阈值,如果超过就记录一条“包裹已放置”事件,同时可以触发本地通知。如果重量从有到无,则记录“包裹已取走”。这样一来,即使人不在家,也能知道包裹是什么时候到的、大概多重、什么时候被拿走的。
这个项目适合谁呢?如果你有基本的 Arduino 编程经验,会用烙铁焊几个排针,能照着教程安装 Android Studio 并跑通一个 Hello World,那就可以动手做。不需要精通电路设计,也不需要会写复杂的 Android 架构,核心代码加起来不超过五百行。3D 打印部分如果没有打印机,也可以找代打服务,或者用亚克力板手工切割替代。
2. 硬件选型与核心电路搭建
2.1 ESP32 开发板的选择与引脚分配
ESP32 的型号很多,我手头用的是 ESP32-DevKitC V4,核心是 ESP32-WROOM-32 模组。选它是因为引脚引出完整,USB 转串口芯片是 CP2102,在 Windows 和 macOS 上驱动都很好找。国内购买的话,注意选带屏蔽罩的版本,信号稳定性会好一些。开发环境我用的 Arduino IDE,需要额外安装 ESP32 的开发板支持包。国内下载慢的问题可以通过添加国内镜像源解决,在 Arduino IDE 的“附加开发板管理器网址”里填入国内镜像地址即可,具体地址搜“esp32 arduino 国内镜像源”就能找到最新的。
引脚分配上,HX711 需要两个 GPIO:一个用于数据输出(DT),一个用于时钟输入(SCK)。我选的是 GPIO16 和 GPIO17,这两个引脚在 DevKitC 上位置方便接线,而且不涉及启动时的特殊功能。注意不要用 GPIO0、GPIO2、GPIO12 这些在启动时会影响芯片模式的引脚,否则可能出现烧录失败或者启动异常。蓝牙部分不需要额外引脚,ESP32 内置的蓝牙协议栈直接使用。
供电方面,ESP32 开发板可以通过 Micro USB 或者 5V 引脚供电。HX711 模块和称重传感器需要 5V 供电,但 HX711 的信号输出电平是 5V,而 ESP32 的 GPIO 是 3.3V 容忍的。这里有个坑:HX711 的 DT 引脚输出是 5V 电平,直接接 ESP32 的 GPIO 长期运行可能损伤芯片。稳妥的做法是在 DT 和 ESP32 之间加一个电平转换模块,或者用电阻分压。我实测用 1k 和 2k 电阻分压后,信号读取正常,连续跑了一周没有出现问题。
2.2 HX711 与称重传感器的接线要点
称重传感器我选的是 5kg 量程的铝合金悬臂梁式,精度等级 C3,满足包裹称重需求。传感器有四根线:红、黑、白、绿。红线接 HX711 的 E+,黑线接 E-,白线接 A-,绿线接 A+。不同厂家的线色可能不同,拿到手先用万用表测一下桥臂电阻,确认哪两根是激励、哪两根是信号。一般来说,激励线之间的电阻是 1kΩ 左右,信号线之间的电阻也是 1kΩ 左右,激励和信号之间的电阻约 800Ω。
HX711 模块和 ESP32 的连接:VCC 接 5V,GND 接 GND,DT 接 GPIO16,SCK 接 GPIO17。这里注意 HX711 的 VCC 一定要接 5V,接 3.3V 的话芯片工作不正常,读数会乱跳。我一开始图省事接了 3.3V,结果读数一直在漂移,换了 5V 之后立刻稳定了。
称重传感器的安装方式直接影响测量精度。悬臂梁传感器需要一端固定、一端悬空受力。我用 3D 打印了一个底座,把传感器的一端用 M4 螺丝固定在底座上,另一端悬空,上面装一个承重平台。承重平台不能碰到其他任何东西,否则会产生侧向力导致读数不准。这个细节很关键,我第一版设计时平台边缘蹭到了外壳,读数一直偏大,后来把外壳开口扩大了两毫米才解决。
2.3 3D 打印外壳的设计与打印参数
外壳我用 Fusion 360 画的,分上下两部分:底座用来固定传感器和 ESP32,上盖用来放包裹。底座内部留了走线槽,HX711 模块和 ESP32 用尼龙柱固定。上盖和传感器之间用一个 M5 螺丝连接,螺丝拧进传感器悬空端的螺纹孔里。整个外壳尺寸是 200mm x 150mm x 40mm,足够放下一个标准快递盒。
打印参数方面,我用的是 PLA 材料,层高 0.2mm,填充率 20%,打印温度 210°C,热床温度 60°C。底座需要承受包裹重量,所以填充率可以提高到 30%,壁厚设为 1.2mm。上盖不需要承重,20% 填充就够了。打印时间大概各需要四五个小时,具体看打印机速度。
如果手头没有 3D 打印机,可以用亚克力板切割一个类似结构,或者直接买一个现成的电子秤拆掉表头,把传感器信号引出来接到 HX711。不过自己打印的好处是尺寸完全可控,后续想改大改小都方便。
3. 固件开发与重量校准实战
3.1 Arduino 环境配置与 HX711 库的使用
Arduino IDE 安装 ESP32 支持包的步骤:打开“文件”->“首选项”,在“附加开发板管理器网址”里填入国内镜像地址,然后打开“工具”->“开发板”->“开发板管理器”,搜索“esp32”,安装最新版本。安装完成后,在开发板列表里选择“ESP32 Dev Module”。如果下载速度慢,可以在首选项里把“编译输出”设为详细,观察具体卡在哪一步。
HX711 库我用的是 bogde 的 HX711 Arduino Library,在库管理器里直接搜索安装即可。这个库封装了读取原始值和校准的功能,用起来很简单。初始化代码如下:
#include "HX711.h" HX711 scale; const int LOADCELL_DOUT_PIN = 16; const int LOADCELL_SCK_PIN = 17; void setup() { Serial.begin(115200); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); scale.set_scale(); // 先不设比例系数,后面校准 scale.tare(); // 去皮,清零 }读取重量时,先调用scale.read_average(10)取十次平均,减少噪声。然后除以比例系数得到实际重量。比例系数需要校准,下面详细说。
3.2 校准比例系数的计算与实操
校准是称重项目里最关键的一步。HX711 输出的是原始 ADC 值,需要除以一个比例系数才能变成克或千克。这个系数因传感器和安装方式而异,必须实测。
具体操作:先放一个已知重量的物体,比如一袋 500g 的盐。读取此时的原始值,假设是 420000。然后拿掉物体,读取空载原始值,假设是 2000。那么比例系数 = (420000 - 2000) / 500 = 836。这个系数就是每克对应的 ADC 变化量。在代码里用scale.set_scale(836)设置,之后scale.get_units(10)返回的就是克数。
但实际使用中,我发现线性度在大量程时会有偏差。比如 500g 时校准好了,放到 3kg 时读数偏小 2%。解决办法是用两个校准点:先用 500g 校准,再用 3kg 校准,取平均系数。或者用多点校准,在代码里做分段线性拟合。对于包裹称重来说,精度要求不高,±50g 都能接受,所以单点校准足够了。
注意:校准前一定要先
tare()去皮,把空载时的原始值清零。如果传感器安装有预紧力,空载原始值可能很大,去皮后就没问题了。
3.3 蓝牙串口通信的配置与数据格式
ESP32 的蓝牙串口用BluetoothSerial库,几行代码就能搞定:
#include "BluetoothSerial.h" BluetoothSerial SerialBT; void setup() { SerialBT.begin("SmartParcelScale"); // 蓝牙设备名 } void loop() { float weight = scale.get_units(10); SerialBT.println(weight); delay(200); }数据格式我用的就是纯文本,每行一个浮点数,单位是克。手机端按行读取,解析成 float 即可。为什么不用 JSON?因为解析开销大,而且蓝牙串口带宽有限,纯文本最省事。200ms 发一次,既能实时反映重量变化,又不会刷屏太快。
蓝牙设备名设成“SmartParcelScale”,方便在手机端识别。配对时不需要 PIN 码,直接连接即可。注意 ESP32 的蓝牙和 Wi-Fi 共用射频,如果同时开 Wi-Fi 和蓝牙,可能会有干扰。这个项目只用蓝牙,所以没这个问题。
4. Android 端 App 开发与联调
4.1 Android Studio 项目创建与蓝牙权限配置
Android Studio 我用的 2023.1.1 版本,创建项目时选“Empty Views Activity”,语言选 Java。项目建好后,在AndroidManifest.xml里添加蓝牙权限:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />Android 12 及以上需要BLUETOOTH_CONNECT和BLUETOOTH_SCAN权限,并且要在运行时动态申请。我一开始只加了前两个,在 Android 13 手机上一直连不上,后来补上后两个才正常。
界面布局很简单:一个 TextView 显示当前重量,一个 Button 用来连接设备,一个 RecyclerView 显示历史记录。连接按钮点击后弹出已配对设备列表,选中“SmartParcelScale”后建立 SPP 连接。
4.2 蓝牙 SPP 连接与数据接收线程
蓝牙 SPP 连接的核心代码:
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice device = adapter.getRemoteDevice(address); BluetoothSocket socket = device.createRfcommSocketToServiceRecord( UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); socket.connect(); InputStream inputStream = socket.getInputStream();接收数据要放在独立线程里,不能在主线程做阻塞读取。我用一个Thread循环读取inputStream,按换行符分割,每读到一行就通过runOnUiThread更新 UI。这里有个细节:read()方法返回 -1 表示连接断开,要及时处理并提示用户重连。
数据解析就是Float.parseFloat(line),然后判断重量是否超过阈值。我设的阈值是 50g,超过就认为有包裹放上来了。记录逻辑是:如果上一次重量小于阈值、这一次大于阈值,就记一条“包裹已放置”;反过来就记“包裹已取走”。时间戳用System.currentTimeMillis()。
4.3 数据持久化与通知提醒
历史记录我用 Room 数据库存,建一个实体类ParcelEvent,字段包括 id、重量、事件类型、时间戳。Room 的配置在 Android Studio 里很方便,加三个依赖:room-runtime、room-compiler、room-common。编译时用注解处理器生成代码。
通知提醒用NotificationManager,在重量超过阈值时发一条本地通知。Android 13 需要POST_NOTIFICATIONS权限,记得在 Manifest 里声明并动态申请。通知内容就是“包裹已放置,重量 XXX 克”。
调试过程中遇到一个坑:蓝牙连接在 App 切到后台后会被系统断开。解决办法是把接收线程放在Service里,用前台服务保活。不过对于这个项目来说,App 在前台运行就够了,所以没做 Service。
5. 常见问题排查与避坑经验
5.1 称重读数不稳定或漂移
这是最常见的问题,原因通常有三个。第一,HX711 供电不足,检查 VCC 是否接了 5V,GND 是否共地。第二,传感器安装有侧向力,检查承重平台是否碰到其他物体。第三,电源噪声,USB 供电时电脑的开关电源噪声会串进来,可以加一个 100uF 的电解电容在 HX711 的 VCC 和 GND 之间。
我遇到过一次读数每隔几秒跳一次,后来发现是 ESP32 的蓝牙发送和 HX711 读取在同一时刻冲突了。解决办法是在读取 HX711 时暂停蓝牙发送,或者把读取频率降低到 10Hz 以下。HX711 的默认输出速率是 10Hz 或 80Hz,用 10Hz 模式更稳定。
5.2 蓝牙连接失败或频繁断开
Android 端连不上 ESP32 蓝牙,先检查设备是否已配对。如果配对列表里没有“SmartParcelScale”,先在系统设置里手动配对一次。如果配对后 App 里连不上,检查 UUID 是否正确,SPP 的标准 UUID 是00001101-0000-1000-8000-00805F9B34FB。
频繁断开通常是信号干扰或距离太远。ESP32 的蓝牙 Class 2 有效距离约 10 米,中间有墙的话会缩短。把手机和秤台放在同一房间,基本不会断。另外,ESP32 的蓝牙天线是板载 PCB 天线,如果装在金属外壳里信号会很差,所以外壳我用的是 PLA 塑料,没有屏蔽问题。
5.3 Android Studio 编译报错与依赖冲突
Android Studio 新手最容易卡在 Gradle 同步失败。国内网络环境下,把 Gradle 仓库换成国内镜像会快很多。在settings.gradle里把google()和mavenCentral()替换成国内镜像地址即可。具体地址搜“Android Studio 国内镜像”能找到最新的。
另一个常见问题是minSdkVersion设得太低,导致蓝牙 API 不可用。建议设成 21(Android 5.0)以上,如果要支持 Android 12 的新蓝牙权限,targetSdkVersion要设成 31 以上。我一开始targetSdkVersion设的 30,在 Android 13 手机上跑的时候权限申请一直失败,改成 33 后正常了。
5.4 3D 打印件强度不足或变形
PLA 打印的承重件在长期受力下会发生蠕变,也就是慢慢变形。如果包裹比较重,建议用 PETG 或 ABS 材料,强度更好。另外,打印时把承重方向的层线设置成垂直于受力方向,可以显著提高强度。比如底座受力是垂直向下的,打印时让层线水平走,不要竖着打。
还有一个细节:螺丝孔要留公差。我第一版螺丝孔按 4.0mm 画的,结果 M4 螺丝拧不进去,因为 PLA 打印出来孔会缩小约 0.2mm。后来改成 4.3mm 就刚好。如果不想反复试,可以在切片软件里设置“孔洞补偿”,一般设 0.1-0.2mm 就行。
6. 系统联调与实测数据记录
6.1 端到端联调步骤
联调按这个顺序来,不容易乱:第一步,单独测试 ESP32 和 HX711,用串口监视器看重量读数是否正常。第二步,打开手机蓝牙,配对“SmartParcelScale”,用任意蓝牙串口 App 测试数据接收。第三步,运行自己写的 Android App,确认能收到数据并正确解析。第四步,放上不同重量的物体,观察 App 显示值和实际值是否一致。第五步,测试事件记录逻辑,放上、拿走,看数据库里是否生成对应记录。
我实测下来,500g 以内的误差在 ±5g,1kg 到 3kg 的误差在 ±20g,5kg 满量程误差约 ±50g。这个精度对于判断“有没有包裹”完全够用,甚至能大致判断包裹里是文件还是重物。
6.2 功耗测试与供电方案
用 USB 供电时,ESP32 加 HX711 的电流约 80mA,蓝牙发送时峰值到 120mA。如果用电池供电,2000mAh 的锂电池大概能撑 20 小时。如果要延长续航,可以用 ESP32 的深度睡眠模式,每隔几秒唤醒一次读取重量,读完立刻睡。不过深度睡眠时蓝牙会断开,需要重新连接,所以不适合实时性要求高的场景。
我最终的方案是插 USB 电源长期供电,放在门口鞋柜上。线从鞋柜后面走,基本看不见。如果门口没有插座,可以用充电宝供电,10000mAh 的充电宝能撑三四天。
6.3 实际使用场景与改进方向
用了两个月下来,最大的感受是:重量阈值不能设太低。我一开始设的 20g,结果风吹动门帘碰到秤台都会触发记录。后来改成 100g,误报就少多了。另外,如果快递员把包裹斜靠在秤台边缘,重量读数会偏小,可能低于阈值。解决办法是把秤台面积做大一点,让包裹必须完全放在上面才能稳定。
后续改进我想加一个功能:用 ESP32 的 Wi-Fi 连到家庭网络,把数据同步到本地服务器,这样手机不在家也能查记录。不过这就涉及到网络配置和安全问题,复杂度会高不少。目前蓝牙方案的好处是简单、私密、不依赖外网,适合对隐私比较在意的用户。
还有一个想法是用多个传感器做阵列,判断包裹的重心位置,这样能区分“包裹”和“其他重物”。不过成本会翻倍,而且算法复杂很多,暂时没做。
7. 关键参数速查与物料清单
7.1 核心物料清单与参考价格
| 物料 | 型号/规格 | 数量 | 参考价格 |
|---|---|---|---|
| ESP32 开发板 | ESP32-DevKitC V4 | 1 | 35 元 |
| 称重传感器 | 5kg 悬臂梁 | 1 | 25 元 |
| HX711 模块 | 24 位 ADC | 1 | 8 元 |
| 电平转换 | 双向电平模块 | 1 | 5 元 |
| 3D 打印耗材 | PLA 1.75mm | 约 100g | 15 元 |
| 螺丝螺母 | M4/M5 套装 | 若干 | 10 元 |
| Micro USB 线 | 数据线 | 1 | 5 元 |
| 合计 | 约 103 元 |
如果加上 USB 电源适配器,总成本约 120 元。比市面上的智能快递箱便宜很多,而且功能完全自定义。
7.2 关键参数速查表
| 参数 | 推荐值 | 说明 |
|---|---|---|
| HX711 供电 | 5V | 3.3V 会导致读数不稳 |
| 采样速率 | 10Hz | 80Hz 模式噪声大 |
| 蓝牙发送间隔 | 200ms | 太快会丢包 |
| 重量阈值 | 100g | 低于此值不触发记录 |
| 校准点 | 500g 和 3kg | 两点校准线性度更好 |
| 打印填充率 | 底座 30%,上盖 20% | 兼顾强度和耗材 |
| 螺丝孔公差 | +0.3mm | 补偿打印收缩 |
7.3 固件与 App 的关键代码片段索引
ESP32 端核心就是三个部分:HX711 初始化与读取、蓝牙初始化与发送、主循环逻辑。Android 端核心是蓝牙连接线程、数据解析、Room 数据库操作。代码都不长,加起来不到 500 行。我把关键片段都放在上面了,照着拼起来就能跑。
如果你在复现过程中遇到问题,优先检查三个地方:HX711 的供电是不是 5V、蓝牙 UUID 是不是标准 SPP UUID、Android 权限是不是都申请了。这三个地方对了,基本就能跑通。剩下的就是校准和调试精度,耐心调几次就能达到可用状态。