【RaspberryPi5开发板方案创意赛】双胞胎健康监测系统-成果贴4-遇到的坑-经验总结贴前言
在开发双胞胎健康监测系统的过程中,遇到了不少"坑",从网络配置到MQTT通信,从传感器数据处理到设备固件选择,每一步都充满了挑战。本文档记录了开发过程中遇到的主要问题及解决方案,希望能为类似项目提供参考。
一、网络访问方案的探索1.1 问题背景
树莓派部署在家庭内网环境,需要从外网访问其提供的Web界面和MQTT服务。这是IoT项目中最常见也最棘手的问题之一。
1.2 尝试的方案方案一:mDNS(Bonjour)
最初尝试使用mDNS进行服务发现:
# 树莓派上安装avahisudo apt install avahi-daemon avahi-utils# 访问地址http://hostname.local:5000
问题:
仅限局域网使用
不同WiFi网络无法解析 .local 域名
Windows需要额外安装Bonjour
Windows对mDNS支持不好,尝试了多种方案都无法正常工作:
安装Apple Bonjour服务后仍无法解析
重启电脑、重启服务多次无效
防火墙已开放端口但仍无法访问
最终只能放弃,寻找其他方案
结论: ❌ 不适用于外网访问场景
方案二:DNSPod 动态DNS
尝试使用DDNS动态更新DNS记录:
# 获取公网IP并更新DNSPod记录def update_dns(): # 获取公网IP ipv4 = get_public_ipv4() ipv6 = get_public_ipv6() # 更新DNS记录 dnspod_update(ipv4, ipv6)
问题:
运营商分配的是NAT地址,非真实公网IP
IPv6可访问但Windows客户端不支持
需要路由器配置端口转发,但运营商NAT无法配置
结论: ❌ 客户端不支持IPv6,IPv4是NAT地址
方案三:Cloudflare Tunnel
尝试使用Cloudflare Tunnel进行内网穿透:
# config.ymltunnel: YOUR_TUNNEL_IDingress: - hostname: pi5.e2ee.top service: http://localhost:5000 - hostname: mqtt.e2ee.top service: tcp://localhost:1883
问题:
返回私有IPv6地址,需要Cloudflare WARP客户端
Cardputer使用CircuitPython,无法安装WARP客户端
IoT设备无法解析Cloudflare的私有IPv6地址
结论: ⚠️ Windows客户端可用,但IoT设备无法使用
方案四:Tailscale
尝试使用Tailscale组建虚拟局域网:
# 安装Tailscalecurl -fsSL https://tailscale.com/install.sh | shsudo tailscale up
问题:
每个访问设备都需要安装Tailscale客户端
Cardputer无法安装Tailscale
仅限私有网络访问
结论: ⚠️ PC客户端可用,IoT设备无法使用
方案五:ngrok免费版
尝试使用ngrok进行内网穿透:
ngrok http 5000
问题:
免费版域名随机变化,每次重启都不同
无法使用CNAME绑定自定义域名
Cardputer每次都需要修改MQTT地址
结论: ⚠️ 可用但不稳定,无法使用自定义域名
1.3 最终解决方案
经过多次尝试,最终选择了公网MQTT服务器方案:
使用EMQX公共测试服务器 broker.emqx.io:1883
所有设备作为MQTT客户端连接公网服务器
树莓派只运行Flask API服务
优势:
无需内网穿透
所有设备都能连接
配置简单稳定
1.4 经验总结方案WindowsIoT设备公开访问稳定性
| mDNS | ❌ | ❌ | ❌ | ✅ |
| DNSPod DDNS | ❌ | ❌ | ⚠️ | ✅ |
| Cloudflare Tunnel | ✅需WARP | ❌ | ✅ | ✅ |
| Tailscale | ✅ | ❌ | ❌ | ✅ |
| ngrok免费版 | ✅ | ⚠️域名变化 | ✅ | ⚠️ |
| 公网MQTT服务器 | ✅ | ✅ | ✅ | ✅ |
建议: 对于IoT项目,优先考虑使用公网MQTT服务器,避免内网穿透的复杂性。
二、MQTT消息格式优化2.1 问题现象
设备发送的MQTT消息在后端解析失败,日志显示JSON解析错误。
2.2 问题分析
原始消息格式:
{
"board_id": "atoms3r_cam",
"weight": 150,
"data": {
"sensor_0": {
"weight": 50,
"twin": "A",
"item": "牙膏"
}
}}问题:
消息包含中文字符,编码问题导致解析失败
字段名过长,增加消息体积
部分设备发送的消息被截断
2.3 解决方案
简化消息格式,使用短字段名和英文内容:
{
"b": "atoms3r_cam",
"w": 150,
"d": {
"sensor_0": {
"w": 50,
"t": "A",
"i": "toothpaste"
}
}}字段映射:
b → board_id
w → weight
d → data
t → twin
i → item
2.4 后端兼容处理
后端同时支持新旧两种格式:
def parse_sensor_data(payload):
# 新格式
board_id = payload.get("b") or payload.get("board_id", "unknown")
sensor_data = payload.get("d") or payload.get("data", {})
for sensor_id, data in sensor_data.items():
weight = data.get("w") or data.get("weight", 0)
twin = data.get("t") or data.get("twin", "")
item = data.get("i") or data.get("item", "")2.5 经验总结
MQTT消息尽量使用ASCII字符
字段名尽量简短,减少消息体积
后端要做好向后兼容处理
三、重量传感器漂移问题3.1 问题现象
称重传感器读数随时间缓慢增加,即使没有放置物品也会显示重量。
3.2 问题分析
原始算法只检测重量变化,没有处理漂移:
# 原始代码if abs(weight - last_weight) > threshold: # 触发上传
3.3 解决方案
添加稳定性检测和漂移补偿:
def check_weight_stable(self, sensor_id, weight): """ 检测重量是否稳定 三次采样波动小于阈值认为稳定 稳定后重置历史记录,防止漂移累积 """ history = self.weight_history.get(sensor_id, []) history.append(weight) if len(history) > WEIGHT_STABLE_COUNT: history = history[-WEIGHT_STABLE_COUNT:] self.weight_history[sensor_id] = history if len(history) >= WEIGHT_STABLE_COUNT: max_diff = max(history) - min(history) if max_diff <= WEIGHT_STABLE_THRESHOLD: # 稳定后重置历史,防止漂移 self.weight_history[sensor_id] = [history[-1]] return True, history[-1] return False, 0
3.4 经验总结
称重传感器需要定期校准
添加稳定性检测可以过滤噪声
稳定后重置历史记录可以防止漂移累积
四、ESP-NOW网络配置4.1 问题现象
多台Cardputer设备无法互相通信,ESP-NOW消息发送失败。
4.2 问题分析
ESP-NOW通信需要满足以下条件:
所有设备使用相同的WiFi通道
所有设备使用相同的网络标识
需要知道对端设备的MAC地址
4.3 解决方案
实现智能网络发现机制:
# 网络标识NETWORK_ID = "TWINS_HEALTH_2026"# 消息类型MSG_TYPE_HEARTBEAT = "heartbeat"MSG_TYPE_DISCOVER = "discover"MSG_TYPE_JOIN_REQUEST = "join_request"MSG_TYPE_JOIN_ALLOW = "join_allow"def send_heartbeat():
"""网关广播心跳消息"""
msg = {
"type": MSG_TYPE_HEARTBEAT,
"network_id": NETWORK_ID,
"board_id": BOARD_ID,
"is_gateway": True
}
espnow.send(BROADCAST_MAC, json.dumps(msg))4.4 网络发现流程
设备启动 │ ├── 已配置GATEWAY_MAC? ──是──▶ 连接到配置的网关 │ └── 否 ──▶ 搜索网络 (10秒) │ ├── 发现网关 ──▶ 显示选择界面 │ │ │ ├── 单击 KEY0 → 加入网络 │ └── 双击 KEY0 → 创建新网络 │ └── 未发现网关 ──▶ 自动成为网关
4.5 经验总结
使用NETWORK_ID隔离不同的ESP-NOW网络
实现心跳机制检测网络状态
提供用户确认机制防止误加入网络
五、设备在线状态检测5.1 问题现象
Web界面显示设备"在线",但实际设备已经断开连接。
5.2 问题分析
原始实现只在收到消息时更新状态,没有超时检测:
# 原始代码def on_message(client, userdata, msg): update_board_status(board_id, "online", sensor_count)
5.3 解决方案
添加心跳超时检测:
def get_boards():
"""获取开发板状态,根据心跳时间判断在线状态"""
now = datetime.now()
offline_threshold = timedelta(seconds=60)
for board in boards:
if board.get('last_seen'):
last_seen = datetime.strptime(board['last_seen'], "%Y-%m-%d %H:%M:%S")
if now - last_seen > offline_threshold:
board['status'] = 'offline'5.4 经验总结
设备状态需要基于心跳时间动态判断
超时阈值应根据实际心跳间隔设置
前端应实时显示设备在线状态
六、配置同步问题6.1 问题现象
后端修改传感器映射后,设备仍然使用旧的映射信息。
6.2 问题分析
设备配置只在本机存储,后端修改后无法同步到设备。
6.3 解决方案
实现MQTT配置下发机制:
后端:
def on_status_message(board_id, status):
"""设备上线时自动下发配置"""
if status == "online":
mappings = get_sensor_mappings(board_id)
if mappings:
config_payload = {
"board_id": board_id,
"sensor_items": mappings }
client.publish(MQTT_TOPIC_CONFIG, json.dumps(config_payload))设备端:
def handle_mqtt_config(topic, message):
"""处理MQTT配置消息"""
config_data = json.loads(message)
if config_data.get("board_id") == BOARD_ID:
new_items = config_data.get("sensor_items", {})
for sensor_id, item_config in new_items.items():
SENSOR_ITEMS[sensor_id] = {
"twin": item_config.get("twin", ""),
"item": item_config.get("item", "")
}6.4 经验总结
使用MQTT实现配置下发
设备上线时自动同步配置
配置变更需要持久化存储
七、CircuitPython与MicroPython差异7.1 问题背景
项目同时使用了两种不同的Python实现:
Cardputer:CircuitPython
AtomS3R-CAM:MicroPython
7.2 主要差异特性CircuitPythonMicroPython
| 配置加载 | settings.toml | config.py |
| MQTT库 | adafruit_minimqtt | umqtt.simple |
| 显示驱动 | adafruit_st7789 | 内置 |
| I2C操作 | board.I2C() | machine.I2C() |
7.3 配置文件差异
CircuitPython (settings.toml):
CIRCUITPY_WIFI_SSID = "YOUR_WIFI"MQTT_BROKER = "broker.emqx.io"[sensor_items]C1 = "toothpaste"
MicroPython (config.py):
WIFI_SSID = "YOUR_WIFI"MQTT_BROKER = "broker.emqx.io"SENSOR_ITEMS = {
"sensor_0": {"item": "toothpaste", "twin": "A"}}7.4 经验总结
开发前确认设备使用的Python实现
不同实现的库和API可能不兼容
配置文件格式需要适配
八、图片传输优化8.1 问题现象
AtomS3R-CAM拍摄的图片体积过大,上传失败或超时。
8.2 问题分析
原始JPEG图片体积约30-50KB,HTTP上传容易超时。
8.3 解决方案
实现RLE5压缩算法:
def rle5_compress(image_data): """ RLE5压缩算法 将RGB565图像压缩为更小的体积 """ compressed = bytearray() prev_pixel = None count = 0 for pixel in image_data: if pixel == prev_pixel and count < 31: count += 1 else: if prev_pixel is not None: # 编码: 高3位为计数,低5位为颜色索引 compressed.append((count << 5) | color_index) prev_pixel = pixel count = 0 return bytes(compressed)
压缩后体积减少约50%,传输更稳定。
8.4 经验总结
图片传输前进行压缩
选择适合IoT设备的压缩算法
考虑网络带宽限制
九、总结9.1 主要坑点类别问题解决方案
| 网络访问 | 内网穿透复杂 | 使用公网MQTT服务器 |
| 消息格式 | 中文编码问题 | 简化格式,使用英文 |
| 传感器 | 漂移问题 | 稳定性检测+历史重置 |
| ESP-NOW | 网络配置 | 智能网络发现 |
| 状态检测 | 状态不准确 | 心跳超时检测 |
| 配置同步 | 配置不同步 | MQTT配置下发 |
| 固件差异 | API不兼容 | 区分适配 |
| 图片传输 | 体积过大 | RLE5压缩 |
9.2 开发建议
网络架构:优先使用公网服务,避免内网穿透
消息设计:简洁高效,避免编码问题
传感器处理:考虑漂移和噪声
状态管理:基于心跳动态判断
配置管理:支持远程下发
固件选择:提前确认,做好适配
9.3 项目收获
通过这个项目,深入学习了:
MQTT协议的实际应用
ESP-NOW通信机制
IoT设备开发流程
内网穿透方案对比
传感器数据处理
相关文档:
部署指南
Cloudflare Tunnel配置
MQTT迁移文档
网络方案探索
智能网络发现
十、致谢10.1 感谢主办方
感谢EEPW和e络盟联合举办的【Raspberry Pi 5 开发板方案创意赛】,提供了宝贵的树莓派5开发板和学习机会。
10.2 感谢社区
感谢开源社区提供的各种技术支持和文档资源,让项目开发更加顺利。
10.3 感谢家人
感谢家人的支持和理解,让双胞胎健康监测系统从一个想法变成现实。
十一、项目帖子汇总
本项目在EEPW论坛发布的全部帖子:
序号帖子标题链接
以及本贴。代码请见其他帖子和附件。十二、参考资料
Mosquitto MQTT:https://mosquitto.org/
EMQX MQTT Broker:https://www.emqx.io/
ECharts图表库:https://echarts.apache.org/
CircuitPython:https://docs.circuitpython.org/
MicroPython:https://micropython.org/
Flask Web框架:https://flask.palletsprojects.com/
M5Stack官方文档:https://docs.m5stack.com/
项目完成日期:2026年2月
再次感谢EEPW和e络盟提供的宝贵机会!
我要赚赏金
