--[[ @module uart_app @summary vd960Air 串口应用功能模块: UART1 <-> vd960DBN 帧协议对接 @version 0.1 @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; 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 链路独立魔数) - LEN: uint16 大端,PAYLOAD 字节数 - SUM: uint8 累加和,覆盖 HEAD+LEN_H+LEN_L+PAYLOAD 全部字节取低 8 位 - 方向: 双向对称(vd960DBN 发同样帧格式) 状态机: IDLE → HEAD → LEN_H → LEN_L → DATA → SUM → COMPLETE 注意: 状态机按 LEN 精确吃字节,payload 内出现的 0x7D 不影响帧同步; 仅 IDLE 状态寻找 HEAD;SUM 校验失败 / 超长帧 → 回 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_SUM = 0, 1, 2, 3, 4, 5 local rx_state = RX_IDLE local rx_len = 0 -- 当前帧 PAYLOAD 长度 local rx_buf = "" -- PAYLOAD 缓冲 local rx_sum = 0 -- 累加和(覆盖 HEAD 起所有字节) local function rx_reset() rx_state = RX_IDLE rx_len = 0 rx_buf = "" 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 rx_sum = b end elseif rx_state == RX_HEAD then rx_len = b * 256 rx_sum = (rx_sum + b) % 256 rx_state = RX_LENH elseif rx_state == RX_LENH then rx_len = rx_len + b 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_sum = (rx_sum + b) % 256 if rx_buf:len() >= rx_len then 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") 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 + SUM 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 for i = 1, len do sum = (sum + string.byte(payload, i)) % 256 end frame = frame .. string.char(sum) 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)