- uart_app.lua: 接收状态机 RX_XOR/RX_SUM 双校验(从 LEN_H 起,不含 HEAD) 下行组帧加 XOR+SUM 两字节,与 DBN↔Loop 0x7F 完全一致 - config.lua/readme: 帧协议 v0.2 定稿标注,移除"待确认"
175 lines
6.1 KiB
Lua
175 lines
6.1 KiB
Lua
--[[
|
|
@module uart_app
|
|
@summary vd960Air 串口应用功能模块: UART1 <-> vd960DBN 帧协议对接
|
|
@version 0.2
|
|
@date 2026.08.31
|
|
@usage
|
|
核心职责:
|
|
1、打开 UART1(对接 vd960DBN CH32V208 串口1 PB6/PB7);
|
|
2、上行: 接收 vd960DBN 的 JSON 帧(复用《DLD960_IoT_MQTT协议》命令面),
|
|
校验后注入 link 对象 → 发布 SEND_DATA_REQ → mqtt_sender publish 到 dld960/{sn}/dev;
|
|
3、下行: 订阅 RECV_DATA_FROM_SERVER 消息,将 MQTT 下行 JSON 组帧后经 UART1 下发 vd960DBN。
|
|
|
|
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 字节数
|
|
- XOR: XOR 校验,从 LEN_H 开始到 PAYLOAD 末字节(不含 HEAD,与 0x7F 同风格)
|
|
- SUM: 累加和校验,范围同上
|
|
- 方向: 双向对称(vd960DBN 发同样帧格式)
|
|
|
|
状态机: IDLE → HEAD → LEN_H → LEN_L → DATA → XOR → SUM → COMPLETE
|
|
注意: 状态机按 LEN 精确吃字节,payload 内出现的 0x7D 不影响帧同步;
|
|
仅 IDLE 状态寻找 HEAD;校验失败 / 超长帧 → 回 IDLE 丢弃。
|
|
]]
|
|
|
|
-- 加载配置
|
|
local CFG = require "config"
|
|
-- 4G 链路信息采集(IMEI/ICCID/CSQ)
|
|
local link_info = require "link_info"
|
|
|
|
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_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_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
|
|
|
|
-- 一帧完整且校验通过 → 发布到 MQTT 发送队列
|
|
local function on_frame_ok(payload)
|
|
log.info("uart_app.frame", "len", payload:len())
|
|
|
|
-- 4G 特有字段注入(用户决策 2026-08-31):
|
|
-- 上行 JSON 顶层附加 link 对象 {imei, iccid, imsi, msisdn, csq, net},
|
|
-- 方便平台管理 4G 通道设备(流量卡识别/信号监控)。不破坏主协议结构,老平台忽略即可。
|
|
if CFG.CFG_LINK_ENABLE then
|
|
local ok, obj = pcall(json.decode, payload)
|
|
if ok and type(obj) == "table" then
|
|
obj.link = link_info.get()
|
|
payload = json.encode(obj)
|
|
else
|
|
-- JSON 解析失败: 原样转发,不阻塞上行
|
|
log.warn("uart_app.frame", "json decode fail, forward raw")
|
|
end
|
|
end
|
|
|
|
-- SEND_DATA_REQ(tag, topic, payload, qos)
|
|
-- 上行统一发布到 dev 主题(平台侧与有线通道无差别)
|
|
sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, payload, 1)
|
|
end
|
|
|
|
-- 逐字节喂入接收状态机
|
|
local function feed_byte(b)
|
|
if rx_state == RX_IDLE then
|
|
if b == CFG.CFG_FRAME_HEAD then
|
|
rx_state = RX_HEAD
|
|
end
|
|
elseif rx_state == RX_HEAD then
|
|
rx_len = 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)
|
|
rx_reset()
|
|
else
|
|
rx_buf = ""
|
|
rx_state = RX_LENL
|
|
end
|
|
elseif rx_state == RX_LENL then
|
|
-- 0 长度帧非法
|
|
if rx_len == 0 then
|
|
rx_reset()
|
|
else
|
|
rx_state = RX_DATA
|
|
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", "sum fail")
|
|
end
|
|
rx_reset()
|
|
end
|
|
end
|
|
|
|
-- UART1 接收中断处理
|
|
local function read()
|
|
local s
|
|
while true do
|
|
s = uart.read(UART_ID, 1024)
|
|
if not s or s:len() == 0 then
|
|
break
|
|
end
|
|
for i = 1, s:len() do
|
|
feed_byte(string.byte(s, i))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ==================== 下行: MQTT → UART1 ====================
|
|
-- mqtt_receiver 发布 RECV_DATA_FROM_SERVER(topic, payload)
|
|
local function recv_data_from_server_proc(topic, payload)
|
|
if not payload or payload:len() == 0 then
|
|
return
|
|
end
|
|
-- 组帧: HEAD + LEN(2B BE) + PAYLOAD + XOR + SUM
|
|
-- 校验范围: 从 LEN_H 起(不含 HEAD,与 0x7F 同风格)
|
|
local len = payload:len()
|
|
local hi = math.floor(len / 256)
|
|
local lo = len % 256
|
|
local x = hi ~ lo
|
|
local s = (hi + lo) % 256
|
|
for i = 1, len do
|
|
local b = string.byte(payload, i)
|
|
x = x ~ b
|
|
s = (s + b) % 256
|
|
end
|
|
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
|
|
|
|
-- ==================== 初始化 ====================
|
|
uart.setup(UART_ID, CFG.CFG_UART_BAUD, 8, 1)
|
|
uart.on(UART_ID, "receive", read)
|
|
sys.subscribe("RECV_DATA_FROM_SERVER", recv_data_from_server_proc)
|
|
|
|
log.info("uart_app", "init ok", "baud", CFG.CFG_UART_BAUD, "topic_up", CFG.CFG_TOPIC_UP)
|