feat(vd960Air): 帧校验统一 XOR+SUM(用户决策 2026-08-31)
- uart_app.lua: 接收状态机 RX_XOR/RX_SUM 双校验(从 LEN_H 起,不含 HEAD) 下行组帧加 XOR+SUM 两字节,与 DBN↔Loop 0x7F 完全一致 - config.lua/readme: 帧协议 v0.2 定稿标注,移除"待确认"
This commit is contained in:
@@ -33,8 +33,9 @@ CFG_MQTT_CLIENT_ID = "vd960Air-" .. mobile.imei()
|
||||
CFG_UART_ID = 1
|
||||
CFG_UART_BAUD = 115200
|
||||
|
||||
-- 帧协议参数(4G <-> vd960DBN UART 链路帧,设计稿 v0.1)
|
||||
-- 帧 = [0x7D] [LEN_H] [LEN_L] [PAYLOAD...] [SUM], 详见 uart_app.lua 头注释
|
||||
-- 帧协议参数(4G <-> vd960DBN UART 链路帧,设计稿 v0.2 — 用户决策 2026-08-31 校验统一 XOR+SUM)
|
||||
-- 帧 = [0x7D] [LEN_H] [LEN_L] [PAYLOAD...] [XOR] [SUM]
|
||||
-- 校验范围: 从 LEN_H 起到 PAYLOAD 末字节(不含 HEAD,与 DBN↔Loop 0x7F 同风格)
|
||||
CFG_FRAME_HEAD = 0x7D
|
||||
CFG_FRAME_MAXLEN = 2048 -- PAYLOAD 最大字节数(防御: 超长判非法帧)
|
||||
|
||||
|
||||
@@ -83,13 +83,14 @@
|
||||
- JSON 解析失败时原样转发,不阻塞上行
|
||||
- 开关: `config.lua` `CFG_LINK_ENABLE`
|
||||
|
||||
## 3 UART 链路帧协议(设计稿 v0.2)
|
||||
## 3 UART 链路帧协议(设计稿 v0.2,校验已定稿)
|
||||
|
||||
```
|
||||
帧 = [HEAD 0x7D] [LEN_H] [LEN_L] [PAYLOAD...] [SUM]
|
||||
帧 = [HEAD 0x7D] [LEN_H] [LEN_L] [PAYLOAD...] [XOR] [SUM]
|
||||
- HEAD: 0x7D(避开 0x7F DBN↔Loop / 0x8F DBN 专有 / 0x9F OTA 已占用魔数)
|
||||
- LEN: uint16 大端, PAYLOAD(JSON) 字节数, ≤2048
|
||||
- SUM: uint8 累加和(HEAD+LEN_H+LEN_L+PAYLOAD 全字节取低 8 位)
|
||||
- XOR: XOR 校验,从 LEN_H 起到 PAYLOAD 末字节(不含 HEAD)
|
||||
- SUM: 累加和校验,范围同上
|
||||
- 双向对称
|
||||
```
|
||||
|
||||
@@ -108,13 +109,13 @@ vd960DBN 与地感 MCU 的串口逻辑(loop_uart_proto.c,UART2,0x7F 帧):
|
||||
|---|---|---|
|
||||
| LEN 字段 | **1B**,Value 0~64B(协议明确上限) | **2B**,≤2048B |
|
||||
| 魔数 | 0x7F | 0x7D |
|
||||
| 校验 | XOR + SUM(从 Addr 起) | SUM(设计稿;待定稿确认是否统一 XOR+SUM) |
|
||||
| 状态机 | IDLE→HEADER→VALUE→CHECK | 同构(IDLE→HEAD→LEN→DATA→CHECK) |
|
||||
| 校验 | XOR + SUM(从 Addr 起) | **XOR + SUM(从 LEN_H 起,用户决策 2026-08-31)** |
|
||||
| 状态机 | IDLE→HEADER→VALUE→CHECK | 同构(IDLE→HEAD→LEN→DATA→XOR→SUM) |
|
||||
| 用途 | 命令-响应短帧 | JSON 报文流(loop_data 数百字节) |
|
||||
|
||||
- 为什么不用 0x7F 帧: LEN 1B 上限 64B,装不下 MQTT JSON
|
||||
- 状态机同构 → vd960DBN 侧 UART1 通道可复用 `lup_feed_byte` 解析骨架
|
||||
- 待确认: 校验是否统一为 XOR+SUM(与 0x7F 风格一致)
|
||||
- 状态机同构 + 校验统一 → vd960DBN 侧 UART1 通道可直接复用 `lup_feed_byte` 解析骨架
|
||||
- ✅ **校验已定稿为 XOR+SUM**(2026-08-31 用户确认),与 0x7F 完全一致
|
||||
|
||||
## 4 文件结构
|
||||
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
--[[
|
||||
@module uart_app
|
||||
@summary vd960Air 串口应用功能模块: UART1 <-> vd960DBN 帧协议对接
|
||||
@version 0.1
|
||||
@version 0.2
|
||||
@date 2026.08.31
|
||||
@usage
|
||||
核心职责:
|
||||
1、打开 UART1(对接 vd960DBN CH32V208 串口1 PB6/PB7);
|
||||
2、上行: 接收 vd960DBN 的 JSON 帧(复用《DLD960_IoT_MQTT协议》命令面),
|
||||
校验后发布 SEND_DATA_REQ 消息 → mqtt_sender publish 到 dld960/{sn}/dev;
|
||||
校验后注入 link 对象 → 发布 SEND_DATA_REQ → mqtt_sender publish 到 dld960/{sn}/dev;
|
||||
3、下行: 订阅 RECV_DATA_FROM_SERVER 消息,将 MQTT 下行 JSON 组帧后经 UART1 下发 vd960DBN。
|
||||
|
||||
UART 链路帧格式(设计稿 v0.1,待与 vd960DBN 侧联调确认):
|
||||
┌──────┬───────┬───────┬────────────────────┬──────┐
|
||||
│ HEAD │ LEN_H │ LEN_L │ PAYLOAD │ SUM │
|
||||
│ 0x7D │ 1B │ 1B │ JSON(≤2048B) │ 1B │
|
||||
└──────┴───────┴───────┴────────────────────┴──────┘
|
||||
- HEAD: 0x7D(与 DBN↔Loop 的 0x7F 帧区分,4G 链路独立魔数)
|
||||
UART 链路帧格式(v0.2,校验统一 XOR+SUM — 用户决策 2026-08-31,与 DBN↔Loop 0x7F 一致):
|
||||
┌──────┬───────┬───────┬────────────────────┬──────┬──────┐
|
||||
│ HEAD │ LEN_H │ LEN_L │ PAYLOAD │ XOR │ SUM │
|
||||
│ 0x7D │ 1B │ 1B │ JSON(≤2048B) │ 1B │ 1B │
|
||||
└──────┴───────┴───────┴────────────────────┴──────┴──────┘
|
||||
- HEAD: 0x7D(避开 0x7F DBN↔Loop / 0x8F DBN 专有 / 0x9F OTA 已占用魔数)
|
||||
- LEN: uint16 大端,PAYLOAD 字节数
|
||||
- SUM: uint8 累加和,覆盖 HEAD+LEN_H+LEN_L+PAYLOAD 全部字节取低 8 位
|
||||
- XOR: XOR 校验,从 LEN_H 开始到 PAYLOAD 末字节(不含 HEAD,与 0x7F 同风格)
|
||||
- SUM: 累加和校验,范围同上
|
||||
- 方向: 双向对称(vd960DBN 发同样帧格式)
|
||||
|
||||
状态机: IDLE → HEAD → LEN_H → LEN_L → DATA → SUM → COMPLETE
|
||||
状态机: IDLE → HEAD → LEN_H → LEN_L → DATA → XOR → SUM → COMPLETE
|
||||
注意: 状态机按 LEN 精确吃字节,payload 内出现的 0x7D 不影响帧同步;
|
||||
仅 IDLE 状态寻找 HEAD;SUM 校验失败 / 超长帧 → 回 IDLE 丢弃。
|
||||
仅 IDLE 状态寻找 HEAD;校验失败 / 超长帧 → 回 IDLE 丢弃。
|
||||
]]
|
||||
|
||||
-- 加载配置
|
||||
@@ -34,16 +35,18 @@ local UART_ID = CFG.CFG_UART_ID
|
||||
local MAX_LEN = CFG.CFG_FRAME_MAXLEN
|
||||
|
||||
-- ==================== 接收状态机 ====================
|
||||
local RX_IDLE, RX_HEAD, RX_LENH, RX_LENL, RX_DATA, RX_SUM = 0, 1, 2, 3, 4, 5
|
||||
local RX_IDLE, RX_HEAD, RX_LENH, RX_LENL, RX_DATA, RX_XOR, RX_SUM = 0, 1, 2, 3, 4, 5, 6
|
||||
local rx_state = RX_IDLE
|
||||
local rx_len = 0 -- 当前帧 PAYLOAD 长度
|
||||
local rx_buf = "" -- PAYLOAD 缓冲
|
||||
local rx_sum = 0 -- 累加和(覆盖 HEAD 起所有字节)
|
||||
local rx_xor = 0 -- XOR 校验(从 LEN_H 起累加)
|
||||
local rx_sum = 0 -- SUM 校验(从 LEN_H 起累加)
|
||||
|
||||
local function rx_reset()
|
||||
rx_state = RX_IDLE
|
||||
rx_len = 0
|
||||
rx_buf = ""
|
||||
rx_xor = 0
|
||||
rx_sum = 0
|
||||
end
|
||||
|
||||
@@ -75,14 +78,15 @@ local function feed_byte(b)
|
||||
if rx_state == RX_IDLE then
|
||||
if b == CFG.CFG_FRAME_HEAD then
|
||||
rx_state = RX_HEAD
|
||||
rx_sum = b
|
||||
end
|
||||
elseif rx_state == RX_HEAD then
|
||||
rx_len = b * 256
|
||||
rx_sum = (rx_sum + b) % 256
|
||||
rx_xor = b
|
||||
rx_sum = b
|
||||
rx_state = RX_LENH
|
||||
elseif rx_state == RX_LENH then
|
||||
rx_len = rx_len + b
|
||||
rx_xor = rx_xor ~ b -- XOR 累加(bitwise)
|
||||
rx_sum = (rx_sum + b) % 256
|
||||
if rx_len > MAX_LEN then
|
||||
log.warn("uart_app.feed", "frame too long", rx_len)
|
||||
@@ -100,15 +104,23 @@ local function feed_byte(b)
|
||||
end
|
||||
elseif rx_state == RX_DATA then
|
||||
rx_buf = rx_buf .. string.char(b)
|
||||
rx_xor = rx_xor ~ b
|
||||
rx_sum = (rx_sum + b) % 256
|
||||
if rx_buf:len() >= rx_len then
|
||||
rx_state = RX_XOR
|
||||
end
|
||||
elseif rx_state == RX_XOR then
|
||||
if b ~= rx_xor then
|
||||
log.warn("uart_app.feed", "xor fail")
|
||||
rx_reset()
|
||||
else
|
||||
rx_state = RX_SUM
|
||||
end
|
||||
elseif rx_state == RX_SUM then
|
||||
if b == rx_sum then
|
||||
on_frame_ok(rx_buf)
|
||||
else
|
||||
log.warn("uart_app.feed", "checksum fail")
|
||||
log.warn("uart_app.feed", "sum fail")
|
||||
end
|
||||
rx_reset()
|
||||
end
|
||||
@@ -134,18 +146,22 @@ local function recv_data_from_server_proc(topic, payload)
|
||||
if not payload or payload:len() == 0 then
|
||||
return
|
||||
end
|
||||
-- 组帧: HEAD + LEN(2B BE) + PAYLOAD + SUM
|
||||
-- 组帧: HEAD + LEN(2B BE) + PAYLOAD + XOR + SUM
|
||||
-- 校验范围: 从 LEN_H 起(不含 HEAD,与 0x7F 同风格)
|
||||
local len = payload:len()
|
||||
local frame = string.char(CFG.CFG_FRAME_HEAD)
|
||||
.. string.char(math.floor(len / 256)) .. string.char(len % 256)
|
||||
.. payload
|
||||
local sum = CFG.CFG_FRAME_HEAD
|
||||
sum = (sum + math.floor(len / 256)) % 256
|
||||
sum = (sum + len % 256) % 256
|
||||
local hi = math.floor(len / 256)
|
||||
local lo = len % 256
|
||||
local x = hi ~ lo
|
||||
local s = (hi + lo) % 256
|
||||
for i = 1, len do
|
||||
sum = (sum + string.byte(payload, i)) % 256
|
||||
local b = string.byte(payload, i)
|
||||
x = x ~ b
|
||||
s = (s + b) % 256
|
||||
end
|
||||
frame = frame .. string.char(sum)
|
||||
local frame = string.char(CFG.CFG_FRAME_HEAD)
|
||||
.. string.char(hi) .. string.char(lo)
|
||||
.. payload
|
||||
.. string.char(x) .. string.char(s)
|
||||
uart.write(UART_ID, frame)
|
||||
log.info("uart_app.down", "len", len)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user