设备-云端协议 v1
传输
| 项 | 值 |
|---|---|
| 协议 | MQTT 3.1.1(生产环境跑在 WSS 之上) |
| client_id / username | device_id |
| password | device_secret,32 字节随机数,出厂写入 NVS |
| keepalive | 建议 60 s |
主题
| 主题 | 方向 | QoS | Retain | 载荷 |
|---|---|---|---|---|
logs/{device_id} | 设备 → 云 | 1 | 否 | 批量日志 JSON |
status/{device_id} | 设备 → 云 | 1 | 是 | online / offline |
status 必须配 LWT:遗嘱载荷 offline、retain = 1。云端靠这条 retained 消息判断在线状态,设备掉电时由 broker 代发。
日志批次载荷
{
"device_id": "SW-A1B2C3",
"boot_id": "a1b2c3d4",
"seq": 12345,
"lines": [
{"ts": 123456, "lvl": "I", "msg": "wifi: connected, rssi=-52"}
]
}
| 字段 | 说明 |
|---|---|
device_id | SW- + MAC 后 6 位大写 hex,首次启动生成后写 NVS,终身不变 |
boot_id | 每次重启随机 8 位 hex,用于区分重启前后 |
seq | 批次序号,全局单调递增,每 5 分钟落一次 NVS,掉电可续 |
lines[].ts | 毫秒,开机相对时间 |
lines[].lvl | E/W/D/I,单字符 |
lines[].msg | 单行文本,上限 512 字节,超长固件侧截断 |
seq 语义(关键)
粒度是批次不是行,一个 seq 对应一条 MQTT 消息。服务端据此做三件事:
- 去重 ——
(device_id, seq)唯一。QoS 1 重发、离线回放与实时流重叠,都只入库一次。 - 查缺口 —— 若
seq > max_seq + 1,则[max_seq+1, seq-1]记为缺失批次。这是「离线缓冲是否无损补齐」的客观判据。 - 排序 —— 历史回放时保证顺序。
回放历史不误判:seq <= max_seq 时服务端不判缺口,视为迟到或重发,只走去重路径。
级别解析(固件侧)
含 ERROR / ERR / F / [E] → E
含 WARN / W → W
含 DEBUG / D → D
其余 → I
服务端只接受单字符,多字符一律归一为 I(不取首字母)。
攒批策略
攒满 50 行或 200 ms 到达即发。服务端单批次硬上限 512 行,超出部分截断并记录告警。
异常行为
| 场景 | 设备行为 |
|---|---|
| WiFi 断连 | 继续抓取入 RAM → 落 littlefs,界面/LED 显示离线缓冲 |
| broker 不可达 | 指数退避重连 1→2→5→10→30 s + 随机抖动,期间不丢日志 |
| CONNACK rc=4/5 | 认证失败,记 E 级日志,清 WiFi 配置进配网(保留 device_id/secret) |
| PUBACK 超时 | 重发该批次(服务端靠 seq 去重,幂等) |
服务端约束:MQTT 回调里只做解析和入队,绝不碰数据库。回调线程一旦被磁盘 IO 卡住,QoS 1 的 PUBACK 就会超时,设备端无限重发,最终雪崩。落库集中在单线程串行执行;队列上限 20,000,满了宁可丢批次也不让内存爆。