今天这篇结果贴,将介绍一个基于 MQTT 协议的多设备体重称重监控系统。
项目概述体重称重监控系统的设计目标很简单:用 M5Paper 实时显示多台秤的称重数据。用户可以一眼看到家里各台秤的当前读数、在线状态,而不用逐个去查看。
核心亮点:
2x2 网格布局:同时监控最多 4 台称重设备
MQTT 实时通信:通过 公共 Broker 进行数据交换
自动设备发现:I2C 地址扫描,即插即用
E-Ink 灰度优化:浅底深字的墨水屏友好配色
离线超时检测:15 秒无数据自动标记离线
整个系统分为三层:
┌─────────────────────────────────────────────────────┐ │ M5Paper 显示层 │ │ ┌──────────┐ ┌──────────┐ │ │ │ Slot 1 │ │ Slot 2 │ 2x2 网格显示 │ │ │ 123.4g │ │ 456.7g │ Canvas 渲染 │ │ │ online │ │ online │ 5秒刷新间隔 │ │ ├──────────┤ ├──────────┤ │ │ │ Slot 3 │ │ Slot 4 │ │ │ │ --- │ │ 789.0g │ │ │ │ offline │ │ online │ │ │ └──────────┘ └──────────┘ │ └──────────────────────┬──────────────────────────────┘ │ MQTT Subscribe ▼ ┌─────────────────────────────────────────────────────┐ │ MQTT Broker (broker.emqx.io) │ │ Topic: scales/bridge/+/weight │ └──────────────────────┬──────────────────────────────┘ │ MQTT Publish ▼ ┌─────────────────────────────────────────────────────┐ │ 称重传感器节点 (ESP32 + MiniScale) │ │ I2C 地址: 0x26 ~ 0x2D │ │ 每 2 秒发布一次称重数据 │ └─────────────────────────────────────────────────────┘硬件连接
M5Paper 通过 PORT.A(HY2.0-4P 接口)连接外部称重传感器:
Yellow | G25 (SDA) | I2C 数据线 |
White | G32 (SCL) | I2C 时钟线 |
Red | 5V | 电源正极 |
Black | GND | 电源负极 |
I2C 会自动扫描以下已知地址范围的称重传感器:
KNOWN_WEIGHT_SENSOR_ADDRESSES = [ 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x30 ]代码架构详解
整个应用采用单文件结构,按功能模块划分:
# === 1. 配置区 === MQTT_SERVER = "broker.emqx.io" MQTT_PORT = 1883 MQTT_TOPIC_PREFIX = "scales/bridge" MQTT_SUB_TOPIC = "scales/bridge/+/weight" DISPLAY_INTERVAL = 5 # E-Ink 刷新间隔(秒) OFFLINE_TIMEOUT = 15 # 离线超时(秒) # 屏幕参数 SCREEN_W = 960 SCREEN_H = 540 COLS = 2 ROWS = 2 GAP = 8 MARGIN = 8 # === 2. MQTT 回调 === def mqtt_event(topic, msg): """接收远程称重数据,更新缓存""" data = json.loads(msg) weight = float(data['weight']) device_id = data['device_id'] # 查找空闲 slot 或更新已有设备 for i in range(4): if remote_devices[i] == device_id: remote_weights[i] = weight remote_states[i] = "online" break
核心数据结构:
# 4 个 slot 的状态 remote_weights = [0.0, 0.0, 0.0, 0.0] # 重量值 remote_states = ["offline"] * 4 # 在线状态 remote_devices = ["", "", "", ""] # 设备 ID remote_last_time = [0, 0, 0, 0] # 最后更新时间2x2 网格渲染
M5Paper 的 E-Ink 屏幕必须通过 Canvas 缓冲才能正确显示:
def setup_ui():
"""创建 2x2 网格 UI"""
global canvas
Display.setEpdMode(EPD_MODE_TEXT)
canvas = M5.Lcd.newCanvas(SCREEN_W, SCREEN_H, 8, True)
canvas.fillScreen(COLOR_BG)
for row in range(ROWS):
for col in range(COLS):
idx = row * COLS + col
x = MARGIN + col * (CELL_W + GAP)
y = MARGIN + row * (CELL_H + GAP)
draw_cell(canvas, idx, x, y, CELL_W, CELL_H, "", 0.0, "offline")
canvas.push(0, 0)
def draw_cell(canvas, idx, x, y, w, h, device_id, weight_val, state):
"""绘制单个设备格子"""
bg_color = BG_CELL[idx]
canvas.fillRect(x, y, w, h, bg_color)
canvas.drawRect(x, y, w, h, COLOR_DARK_GRAY)
# 标题
canvas.setTextColor(CLR_TITLE, bg_color)
canvas.setTextSize(3)
canvas.setCursor(x + 10, y + 10)
canvas.print("Slot {}".format(idx + 1))
# 设备 ID
canvas.setTextColor(CLR_ID, bg_color)
canvas.setTextSize(2)
canvas.setCursor(x + 10, y + 40)
canvas.print("ID: {}".format(device_id if device_id else "---"))
# 重量数值
canvas.setTextColor(CLR_WEIGHT, bg_color)
canvas.setTextSize(3)
canvas.setCursor(x + 10, y + h // 2)
canvas.print("{:.1f} g".format(weight_val))
# 在线状态
status_color = CLR_STATUS_ONLINE if state == "online" else CLR_STATUS_OFFLINE
canvas.setTextColor(status_color, bg_color)
canvas.setTextSize(2)
canvas.setCursor(x + 10, y + h - 35)
canvas.print("Status: {}".format(state))每个格子的视觉层次从高到低:
标题(Slot 1):深灰色,指引用户
设备 ID:中灰色,信息性内容
重量数值(123.4g):黑色粗体,最醒目
状态(online/offline):绿色/灰色,状态指示
称重监控场景采用两级 EPD 刷新模式:
初始绘制 | EPD_TEXT (1) | 高质量首次显示 |
数据更新 | EPD_FAST (2) | 每 5 秒快速更新 |
设备上线/离线 | EPD_TEXT (1) | 状态变化时清晰显示 |
def update_display(): """更新 E-Ink 显示""" Display.setEpdMode(EPD_MODE_FAST) for i in range(4): row = i // COLS col = i % COLS x = MARGIN + col * (CELL_W + GAP) y = MARGIN + row * (CELL_H + GAP) draw_cell(canvas, i, x, y, CELL_W, CELL_H, remote_devices[i], remote_weights[i], remote_states[i]) canvas.push(0, 0)MQTT 通信协议
系统使用轻量级的 MQTT 协议进行设备间通信:
发布主题:scales/bridge/{device_id}/weight
消息格式(JSON):
{
"weight": 123.4,
"unit": "g",
"timestamp": 1714000000000,
"device_id": "device_26",
"status": "normal"
}订阅主题:scales/bridge/+/weight(+ 为通配符,匹配任意设备)
自动设备发现M5Paper 启动时会自动扫描 I2C 总线上的设备:
def discover_weight_sensors():
"""自动发现连接到 I2C 总线的称重传感器"""
sensors = []
devices = i2c0.scan()
for addr in devices:
if addr in KNOWN_WEIGHT_SENSOR_ADDRESSES:
try:
data = i2c0.readfrom_mem(addr, 0x00, 4)
weight_raw = int.from_bytes(data, 'big', True)
if -50000000 < weight_raw < 50000000:
sensors.append({
'addr': addr,
'name': "device_{:02X}".format(addr)
})
except:
pass
return sensors离线检测机制15 秒内没有收到某设备的数据更新,自动标记为离线:
# 主循环中的离线检测 for i in range(4): if remote_states[i] == "online" and remote_last_time[i] > 0: if current_time - remote_last_time[i] >= OFFLINE_TIMEOUT: remote_states[i] = "offline" remote_weights[i] = 0.0实际运行效果
M5Paper 通过 WiFi 连接到 MQTT Broker 后,会自动订阅所有称重设备的数据。当有称重数据发布时,在 2x2 网格中对应的 slot 会实时更新重量值和状态。
屏幕采用横屏 960x540 显示,放在桌面上可以一目了然地看到所有设备的状态。
踩坑记录1. I2C 引脚复用问题
M5Paper 有两组 I2C:内部(G21/G22)和外部(G25/G32)。一开始我用错了引脚,连接到内部 I2C 上,和 GT911 触摸芯片冲突了。
解决:确认 PORT.A 的 I2C 引脚为 G25(SDA)和 G32(SCL)。
2. E-Ink 反色问题
首次绘制时,Canvas 背景色设置不当,导致格子背景和文字反色显示。
解决:采用浅底深字配色方案,背景用 0xeeeeee,文字用 0x000000。
3. MQTT 重连
WiFi 偶尔断连,MQTT 客户端需要自动重连。
解决:加入重连机制,每 5 秒检查一次连接状态。
总结与展望体重称重监控系统充分利用了 M5Paper 的 ESP32 WiFi 能力和 E-Ink 低功耗特性,作为一个桌面信息终端非常实用。
后续可扩展的方向:
接入 HomeAssistant 智能家居平台
增加历史数据趋势图(利用 E-Ink 灰度显示)
集成 Web 配置界面,无需修改代码即可配置 MQTT 参数
添加称重数据超限报警功能
感谢大家阅读,欢迎在评论区交流讨论!
我要赚赏金
