命令处理¶
sim-hw 订阅 inklet/dev/{thingName}/down/cmd MQTT 主题,处理来自后端的命令。每个命令通过 kind 字段决定处理方式。
命令参考¶
| 类型 | 显示操作 | 描述 |
|---|---|---|
text |
将文本渲染为 800x480 位图,推送到 sim-dashboard | 用户发送的文本内容,用于电子墨水屏显示 |
claim_code |
渲染大尺寸的居中配对码,推送到显示 | 用于设备绑定的 6 位配对码 |
bound |
显示"Device bound successfully"消息 | 绑定完成的通知 |
unbound |
清除显示内容,通过 MQTT 重新请求配对码 | 设备已解绑的通知 |
already_bound |
不进行显示操作(仅记录日志) | 请求配对码时设备已绑定的响应 |
text¶
主要的命令类型。将用户提供的文本内容渲染到模拟电子墨水屏上。
MQTT 载荷:
{
"kind": "text",
"id": "01912345-9999-7abc-def0-aaaaaaaaaaaa",
"text": "Hello from Inklet!\n\nThis is a multi-line message that will be word-wrapped to fit the 800×480 display."
}
行为:
- 从
down/cmd主题接收命令载荷 - 提取
text字段 - 使用 Pillow 将文本渲染为 800x480 的 1-bit(单色)位图
- 对过长的行自动换行以适应显示宽度
- 将渲染后的图像导出为 PNG
- 通过 HTTP POST 将 PNG 发送到 sim-dashboard 的 Fastify 服务器
- sim-dashboard 将新的帧缓冲广播给所有已连接的 WebSocket 客户端
渲染细节:
- 画布尺寸:800x480 像素
- 色彩模式:1-bit(黑白),与真实电子墨水屏能力一致
- 背景色:白色(
1) - 文字颜色:黑色(
0) - 字体:系统默认字体(或内置等宽字体)
- 自动换行:根据字符宽度和画布尺寸自动换行
- 内边距:四周留有边距,防止文字紧贴边缘
claim_code¶
在显示屏上醒目地渲染 6 位配对码。用户在门户或 sim-dashboard 中输入该码即可绑定设备。
MQTT 载荷:
行为:
- 从后端接收配对码
- 在 800x480 画布上以大号居中字体渲染配对码
- 包含提示文字(例如"Enter this code to pair your device")
- 将渲染后的 PNG 推送到 sim-dashboard
显示布局:
┌──────────────────────────────────────────┐
│ │
│ │
│ Enter this code │
│ to pair your device │
│ │
│ A 3 X 9 K 2 │
│ │
│ │
│ │
└──────────────────────────────────────────┘
提示
配对码使用大尺寸的等宽高对比度字体,确保在模拟显示屏上也具有良好的可读性,即使 sim-dashboard 应用了电子墨水效果也是如此。
bound¶
设备成功绑定到用户账户时显示。
MQTT 载荷:
行为:
- 渲染确认消息:"Device bound successfully"
- 将渲染后的 PNG 推送到 sim-dashboard
- 设备现在可以接收来自所有者的
text命令
unbound¶
设备所有者解绑设备时接收。设备应清除显示内容并回到配对状态。
MQTT 载荷:
行为:
- 清除当前显示内容
- 向
inklet/dev/{thingName}/up/request_claim发布request_claim消息 - 后端生成新的配对码并通过
claim_code命令发送回来 - 设备显示新的配对码,准备重新配对
这形成了一个自动循环:解绑触发配对码请求,配对码请求触发配对码显示。
already_bound¶
当设备发送 request_claim 消息但已绑定到用户时接收。
MQTT 载荷:
行为:
- 记录日志:
INFO Device is already bound, ignoring claim request - 不进行显示操作
- 设备继续正常运行
这通常发生在设备重启后,在收到首次心跳响应之前发送了 request_claim。后端识别出设备已绑定,发送 already_bound 而非配对码。
显示渲染流水线¶
所有产生显示输出的命令遵循相同的渲染流水线:
接收命令(MQTT)
│
▼
从载荷中提取文本/配对码
│
▼
Pillow Image.new("1", (800, 480), 1) ← 白色背景
│
▼
ImageDraw.text() with word wrapping ← 黑色文字
│
▼
image.save(buffer, format="PNG") ← 导出为 PNG
│
▼
HTTP POST to sim-dashboard ← 推送帧缓冲
│
▼
WebSocket broadcast to all clients ← 实时显示更新
使用 Pillow 渲染¶
sim-hw 使用 Pillow 库生成 1-bit 单色图像。核心渲染逻辑:
from PIL import Image, ImageDraw, ImageFont
# Create a 1-bit white canvas
img = Image.new("1", (800, 480), 1)
draw = ImageDraw.Draw(img)
# Word-wrap and draw text
draw.text((20, 20), wrapped_text, fill=0, font=font)
# Export as PNG bytes
buffer = io.BytesIO()
img.save(buffer, format="PNG")
png_bytes = buffer.getvalue()
推送到 sim-dashboard¶
渲染后的 PNG 通过 HTTP POST 发送到 sim-dashboard 的 Fastify 服务器:
Fastify 服务器存储帧缓冲并广播给所有已连接的 WebSocket 客户端。React 前端以逼真的电子墨水效果渲染图像。
错误处理
如果向 sim-dashboard 的 HTTP POST 失败(例如服务器未运行),sim-hw 会记录警告并继续运行。MQTT 连接和心跳不受显示渲染失败的影响。