feat(vd960Air): 方案 C MVP — 0x7F 帧解析 + 标准 JSON 上报(2026-08-31)

协议依据:《DLD960_IoT_MQTT协议》V1.13(4G 通道适配方案 C)

新增模块:
- frame_parser.lua: 0x7F 帧解析状态机(Lua 版 lup_feed_byte, XOR+SUM 校验)
- proto_conv.lua: 0xC0 传感帧 → loop_data + car_state/loop_state 沿检测(4 类线圈事件)
- evt_queue.lua: event_report 队列 + ACK 状态机(16深/5s×3/跨重连同 msg_id,对齐 DBN)
- clock.lua: report_config 时钟校准(协议 §2.3)
- app_iot.lua: initialize(extra_info imei/iccid + link)/ heartbeat / poll 驱动
- tools/sim_frame_test.py: 帧协议参考验证(56B 帧/4 路解析/沿检测/JSON 格式,全过)

改造:
- uart_app.lua: 0x7D 帧 → 0x7F 帧驱动;loop_data/event_report 组包 + link 注入
- mqtt_receiver.lua: 下行分发(ACK 路由 + 时钟校准)
- mqtt_main.lua: 连接成功 → initialize + 未决事件重发
- config.lua/main.lua: 方案 C 配置与入口

MVP 范围: 上行(initialize/loop_data/event_report/heartbeat)+ report_config;
下行命令转换与 0x7D 配置同步列入 P1。
This commit is contained in:
wangfq
2026-08-31 15:47:43 +08:00
parent 6d763f02fe
commit 3ad7ae8f09
11 changed files with 796 additions and 207 deletions
@@ -0,0 +1,98 @@
--[[
@module app_iot
@summary vd960Air 设备业务: initialize 上线 / heartbeat 心跳 / 主循环驱动
@version 0.1
@date 2026.08.31
@usage
- on_connected(): MQTT 连接成功后调用 → 发 initialize(extra_info imei/iccid + link)
- heartbeat 定时任务: 60s 发 heartbeat(协议 §5.4,带 link)
- poll(): 主循环驱动 uart_app 事件重发检查(可挂 sys.timerLoopStart)
]]
local CFG = require "config"
local link_info = require "link_info"
local clock = require "clock"
local app_iot = {}
local HEARTBEAT_INTERVAL = CFG.CFG_HEARTBEAT_INTERVAL or 60
-- 上行 JSON 注入 link
local function inject_link(payload)
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()
return json.encode(obj)
end
end
return payload
end
-- 组 initialize(协议 §5.1,extra_info 含 imei/iccid V1.13)
local function build_initialize()
local info = link_info.get()
local payload = string.format(
'{"msg_id":1,"cmd":"initialize","ts":%d,'
.. '"data":{"dev_serial":"%s","model":"%s","hard_ver":"%s","soft_ver":"%s",'
.. '"loop_ver":"%s","loop_hw_ver":"%s",'
.. '"extra_info":{"code":"%s","imei":"%s","iccid":"%s","csq":"%s","location":""}}}',
clock.now(),
CFG.CFG_DEV_SERIAL, CFG.CFG_MODEL or "DLD960", CFG.CFG_HW_VER or "1.0", CFG.CFG_SW_VER or "1.0",
CFG.CFG_LOOP_VER or "", CFG.CFG_LOOP_HW_VER or "",
info.imsi, info.imei, info.iccid, tostring(info.csq))
return inject_link(payload)
end
-- 组 heartbeat(协议 §5.4)
local function build_heartbeat()
local uart_app = require "uart_app"
local loop_ok = uart_app.get_loop_status()
local ls = string.format('[%s,%s,%s,%s]',
tostring(loop_ok[1]), tostring(loop_ok[2]),
tostring(loop_ok[3]), tostring(loop_ok[4]))
local payload = string.format(
'{"msg_id":%d,"cmd":"heartbeat","ts":%d,'
.. '"data":{"uptime":%d,"loop_status":%s,"net_status":true,"iot_status":true}}',
math.floor(mstick() / 1000) % 100000 + 1000, clock.now(),
math.floor(mstick() / 1000), ls)
return inject_link(payload)
end
-- MQTT 连接成功后调用
function app_iot.on_connected()
-- 发 initialize(上线)
local payload = build_initialize()
sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, payload, 1)
log.info("app_iot", "initialize sent")
end
-- 心跳定时任务
local function heartbeat_task()
while true do
sys.wait(HEARTBEAT_INTERVAL * 1000)
local payload = build_heartbeat()
sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, payload, 0)
log.info("app_iot", "heartbeat sent")
end
end
-- 主循环驱动: 事件重发检查(500ms 粒度)
local function poll_task()
local uart_app = require "uart_app"
while true do
sys.wait(500)
uart_app.poll()
end
end
-- 初始化(evt_queue 复位 + 启动定时任务)
function app_iot.init()
local evt_queue = require "evt_queue"
evt_queue.init()
sys.taskInit(heartbeat_task)
sys.taskInit(poll_task)
log.info("app_iot", "init ok")
end
return app_iot
@@ -0,0 +1,44 @@
--[[
@module clock
@summary 设备时钟: 平台 report_config 校准后输出真实 Unix 秒
@version 0.1
@date 2026.08.31
@usage
对齐《DLD960_IoT_MQTT协议》§2.3 时钟同步语义:
- 未校准: 返回上电秒数(mstick()/1000,值很小,平台识别"未校准")
- 校准: base_unix + (now_ms - base_ms)/1000(真实 Unix 秒)
- 合法性门槛 ≥1600000000(2020-09 之后),非法值忽略
- 重启后需重新校准(无掉电保持)
]]
local clock = {}
local base_unix = 0
local base_ms = 0
-- 校准(平台 report_config 下发 ts 时调用)
-- @param unix_ts number: 平台下发的 Unix 秒
-- @param now_ms number: 当前 mstick()
function clock.sync(unix_ts, now_ms)
if not unix_ts or unix_ts < 1600000000 then
return -- 非法值忽略
end
base_unix = unix_ts
base_ms = now_ms or mstick()
end
-- 当前 Unix 秒(未校准返回上电秒数)
function clock.now(now_ms)
now_ms = now_ms or mstick()
if base_unix >= 1600000000 then
return base_unix + math.floor((now_ms - base_ms) / 1000)
end
return math.floor(now_ms / 1000)
end
-- 是否已校准
function clock.synced()
return base_unix >= 1600000000
end
return clock
@@ -1,45 +1,52 @@
--[[ --[[
@module config @module config
@summary vd960Air 配置: MQTT 服务器 / 设备序列号 / UART 参数 @summary vd960Air 配置: MQTT 服务器 / 设备标识 / 主题 / UART / 上报参数
@date 2026.08.31 @date 2026.08.31
@usage @usage
vd960Air 是 Air8781P 整板(Air780EPM 核心模组)上的 LuatOS 应用, vd960Air 是 Air8781P 整板(Air780EPM 核心模组)上的 LuatOS 应用,
作为 vd960DBN 车检器在有线网络失效时的 4G 兜底通道(UART <-> MQTT 桥)。 作为 vd960DBN 车检器在有线网络失效时的 4G 兜底通道(方案 C: Air780 协议转换)。
协议依据:《DLD960_IoT_MQTT协议》V1.10(命令面/JSON 结构与有线通道完全一致) 协议依据:《DLD960_IoT_MQTT协议》V1.13(4G 通道适配方案 C,标准 JSON 命令面)
]] ]]
-- ==================== MQTT 服务器 ==================== -- ==================== MQTT 服务器 ====================
-- 商用服务器地址和端口(替换为实际值;官方 demo 地址仅测试用) -- 商用服务器地址和端口(替换为实际值)
-- 注: 后续 vd960DBN 更新版本后,经 BLE 设置的服务器参数会同步下发覆盖此处(配置同步, P1)
CFG_MQTT_HOST = "lbsmqtt.airm2m.com" CFG_MQTT_HOST = "lbsmqtt.airm2m.com"
CFG_MQTT_PORT = 1884 CFG_MQTT_PORT = 1884
-- ==================== 设备标识 ==================== -- ==================== 设备标识 ====================
-- 设备序列码: 6 字节十六进制字符串(与 vd960DBN dev_serial 一致) -- 设备序列码: 6 字节十六进制字符串(与 vd960DBN dev_serial 一致,有线/4G 同一设备)
-- 说明: 有线通道的 Topic 用 dev_serial 生成;4G 通道必须同一主题族,平台才能统一管理。
-- 【待确认】是否由 vd960DBN 经 UART 握手动态下发,而非本地写死(见 readme 待办)
CFG_DEV_SERIAL = "A1B2C3D4E5F6" CFG_DEV_SERIAL = "A1B2C3D4E5F6"
-- 产品信息(initialize 上报,协议 §5.1)
CFG_MODEL = "DLD960"
CFG_HW_VER = "1.0"
CFG_SW_VER = "1.0"
CFG_LOOP_VER = "" -- 地感 Loop 固件版本(0x4A 查询 P1 后填充)
CFG_LOOP_HW_VER = "" -- 地感 Loop 硬件版本
-- MQTT Topic(与《DLD960_IoT_MQTT协议》§1.2 一致) -- MQTT Topic(与《DLD960_IoT_MQTT协议》§1.2 一致)
CFG_TOPIC_UP = "dld960/" .. CFG_DEV_SERIAL .. "/dev" -- 设备上报(4G publish) CFG_TOPIC_UP = "dld960/" .. CFG_DEV_SERIAL .. "/dev" -- 设备上报
CFG_TOPIC_DOWN = "dld960/" .. CFG_DEV_SERIAL .. "/srv" -- 服务器下发(4G subscribe) CFG_TOPIC_DOWN = "dld960/" .. CFG_DEV_SERIAL .. "/srv" -- 服务器下发
-- MQTT ClientID: 用 IMEI 保证唯一(与序列码解耦;鉴权待确认) -- MQTT ClientID: 用 IMEI 保证唯一(与序列码解耦;鉴权待确认)
CFG_MQTT_CLIENT_ID = "vd960Air-" .. mobile.imei() CFG_MQTT_CLIENT_ID = "vd960Air-" .. mobile.imei()
-- ==================== UART(对接 vd960DBN) ==================== -- ==================== UART(对接 vd960DBN) ====================
-- vd960DBN 侧: CH32V208 串口1(PB6=TX, PB7=RX) -- vd960DBN 侧: CH32V208 串口1(PB6=TX, PB7=RX)
-- 波特率: 官方 demo 默认 115200;vd960DBN UART1 预留口速率【待确认】 -- 波特率: 115200(用户决策 2026-08-31,后续如需再调)
CFG_UART_ID = 1 CFG_UART_ID = 1
CFG_UART_BAUD = 115200 CFG_UART_BAUD = 115200
-- 帧协议参数(4G <-> vd960DBN UART 链路帧,设计稿 v0.2 — 用户决策 2026-08-31 校验统一 XOR+SUM) -- 链路帧: 0x7F 帧字节流(方案 C,数据面透传 Loop 地感协议)
-- 帧 = [0x7D] [LEN_H] [LEN_L] [PAYLOAD...] [XOR] [SUM] -- 0x7D 帧仅用于 DBN↔Air780 配置同步/握手(P1)
-- 校验范围: 从 LEN_H 起到 PAYLOAD 末字节(不含 HEAD,与 DBN↔Loop 0x7F 同风格)
CFG_FRAME_HEAD = 0x7D -- ==================== 上报节奏 ====================
CFG_FRAME_MAXLEN = 2048 -- PAYLOAD 最大字节数(防御: 超长判非法帧) CFG_HEARTBEAT_INTERVAL = 60 -- 心跳周期(秒,协议 §5.4 默认 60)
CFG_REPORT_ALWAYS = true -- MVP: 每收到 0xC0 帧都上报 loop_data;P1 按协议三档节流
-- ==================== 4G 特有字段注入 ==================== -- ==================== 4G 特有字段注入 ====================
-- 用户决策 2026-08-31: 4G 上报附加 IMEI/流量卡ICCID/IMSI/MSISDN/CSQ 字段 -- 用户决策 2026-08-31: 上行附加 link 对象 + initialize extra_info 含 imei/iccid
-- true = 上行 JSON 顶层注入 link 对象(见 link_info.lua) -- true = 上行 JSON 顶层注入 link 对象(见 link_info.lua)
CFG_LINK_ENABLE = true CFG_LINK_ENABLE = true
@@ -0,0 +1,169 @@
--[[
@module evt_queue
@summary event_report 待发队列 + ACK 状态机(Lua 版 iot_evt_*,对齐 DBN 语义)
@version 0.1
@date 2026.08.31
@usage
复刻 vd960DBN iot_mqtt_srv.c 的 event_report 机制(协议 V1.04 / §5.3):
- 16 深环形队列,溢出丢最旧
- 平台 ACK(code=0, 回显 msg_id) → 出队
- 5s 超时重发,同 msg_id/原始 ts,最多 3 次;耗尽丢弃
- 跨 MQTT 重连保持同 msg_id 立即重发(平台按 (sn,msg_id) 去重)
- 多事件合并一条 publish
- 独立 msg_id(不复用 MQTT packet id)
用法:
local eq = require "evt_queue"
eq.init() -- 上电初始化
-- 沿检测出事件:
local need_pub = eq.enqueue({type="car_enter", ch=1, value=350})
if need_pub then local ok = publish_now() end
-- 主循环/定时器驱动:
local action = eq.poll(now_ms, now_ts) -- "resend" | nil
-- MQTT 重连后:
local action = eq.on_reconnect(now_ms)
-- 收到平台 ACK:
eq.handle_ack(msg_id, code)
]]
local evt_queue = {}
-- 事件类型名(与协议 §5.3 一致)
local EVT_NAMES = { "car_enter", "car_leave", "loop_cut", "loop_restore" }
local DEPTH = 16
local ACK_TIMEOUT_MS = 5000
local MAX_RETRY = 3
-- 环形队列
local queue = {} -- table,逻辑索引 0..DEPTH-1
local head = 0 -- 队首
local count = 0 -- 有效条数
-- 待确认包状态
local msg_id = 0 -- event_report 独立 msg_id(递增)
local pend_id = 0 -- 待确认包 msg_id,0=无
local pend_n = 0 -- 待确认包含事件数
local pend_ts = 0 -- 原始 ts(重发不刷新)
local retry = 0 -- 已重发次数
local sent_ms = 0 -- 上次发送时刻
function evt_queue.init()
queue = {}
head = 0
count = 0
msg_id = 0
pend_id = 0
pend_n = 0
pend_ts = 0
retry = 0
sent_ms = 0
end
-- 入队;返回 true = 当前无未决包,可立即发布
function evt_queue.enqueue(evt)
if count >= DEPTH then
head = (head + 1) % DEPTH -- 溢出丢最旧
count = count - 1
end
queue[(head + count) % DEPTH] = evt
count = count + 1
return (pend_id == 0)
end
-- 组 event_report JSON(合并队列中全部事件)
-- @param ts number: 时间戳(首发用 now_ts;重发用 pend_ts 由调用方传入)
-- @return payload string 或 nil(队列空)
function evt_queue.build_report(ts)
if count == 0 then
return nil
end
msg_id = msg_id + 1
local parts = {}
for i = 0, count - 1 do
local e = queue[(head + i) % DEPTH]
local tname = EVT_NAMES[e.type] or "car_enter"
parts[#parts + 1] = string.format('{"type":"%s","ch":%d,"value":%d}',
tname, e.ch, e.value or 0)
end
local payload = string.format('{"msg_id":%d,"cmd":"event_report","ts":%d,'
.. '"data":{"events":[%s]}}',
msg_id, ts, table.concat(parts, ","))
return payload
end
-- 发送后登记待确认(首发/重发后调用)
function evt_queue.mark_sent(id, n, ts, now_ms)
pend_id = id
pend_n = n
pend_ts = ts
retry = 0
sent_ms = now_ms
end
-- 平台 ACK 处理(协议 §5.3: 平台回显 msg_id + code)
function evt_queue.handle_ack(ack_id, code)
if pend_id == 0 or ack_id ~= pend_id then
return
end
if code ~= 0 then
return -- 非 0 保持挂起
end
head = (head + pend_n) % DEPTH
count = count - pend_n
if count < 0 then count = 0 end
pend_id = 0
pend_n = 0
retry = 0
end
-- 主循环/定时器驱动: 超时重发判定
-- @return "resend"(需要重发) | nil
function evt_queue.poll(now_ms, now_ts)
if pend_id == 0 then
return nil
end
if now_ms - sent_ms < ACK_TIMEOUT_MS then
return nil
end
if retry >= MAX_RETRY then
-- 重试耗尽: 丢弃本包(可扩展 offlog 记录)
head = (head + pend_n) % DEPTH
count = count - pend_n
if count < 0 then count = 0 end
pend_id = 0
pend_n = 0
retry = 0
return nil
end
retry = retry + 1
sent_ms = now_ms
return "resend"
end
-- MQTT 重连后: 未决包立即重发(同 msg_id/原始 ts)
function evt_queue.on_reconnect(now_ms)
if pend_id ~= 0 then
retry = 0
sent_ms = now_ms
return "resend"
end
return nil
end
-- 取待确认信息(重发时用)
function evt_queue.pending_info()
return pend_id, pend_n, pend_ts
end
-- 最近一次 build_report 生成的 msg_id(发布后登记用)
function evt_queue.last_msg_id()
return msg_id
end
-- 队列事件数
function evt_queue.count()
return count
end
return evt_queue
@@ -0,0 +1,125 @@
--[[
@module frame_parser
@summary 0x7F 帧解析状态机(Lua 版 lup_feed_byte)
@version 0.1
@date 2026.08.31
@usage
解析 vd960DBN 经 UART1 转发的 0x7F 帧(Loop 地感协议),只切包不懂内容:
- 帧格式: [7F][Addr][LEN][CMD][Value: LEN-1B][XOR][SUM]
- LEN = 1 + data_len(含 CMD 字节)
- 校验: XOR + SUM,从 Addr 起(不含 magic 0x7F),与 DBN↔Loop 完全一致
- 状态机: IDLE → HEADER(Addr/LEN/CMD) → VALUE(LEN-1B) → CHECK(XOR/SUM) → COMPLETE
- 完整帧通过回调 on_frame(cmd, payload) 交付;校验失败/超长帧回 IDLE 丢弃
用法:
local fp = require "frame_parser"
fp.init(function(cmd, payload)
-- cmd: 命令码(number)
-- payload: 除 CMD 外的数据字节(string, 可为空)
end)
-- UART 收到字节流逐字节喂入:
fp.feed(string.byte(s, i))
]]
local frame_parser = {}
-- 状态
local ST_IDLE, ST_ADDR, ST_LEN, ST_CMD, ST_VALUE, ST_CHECK_X, ST_CHECK_S =
0, 1, 2, 3, 4, 5, 6
-- 常量(与 DLD960Loop 协议一致)
local MAGIC_0x7F = 0x7F
local MAX_FRAME_VALUE = 64 -- 协议约定 Value 上限(防御)
local state = ST_IDLE
local cmd = 0
local value_len = 0 -- LEN - 1(除 CMD 外的数据字节数)
local value_buf = "" -- 数据缓冲
local chk_xor = 0 -- 从 Addr 起累计
local chk_sum = 0
local on_frame = nil -- 回调
local function reset()
state = ST_IDLE
cmd = 0
value_len = 0
value_buf = ""
chk_xor = 0
chk_sum = 0
end
-- 完整帧校验通过 → 回调
local function complete()
if on_frame then
on_frame(cmd, value_buf)
end
reset()
end
-- 逐字节喂入
function frame_parser.feed(b)
if state == ST_IDLE then
if b == MAGIC_0x7F then
state = ST_ADDR
end
elseif state == ST_ADDR then
chk_xor = b
chk_sum = b
state = ST_LEN
elseif state == ST_LEN then
chk_xor = chk_xor ~ b
chk_sum = (chk_sum + b) % 256
if b < 1 then
reset() -- LEN=0 非法
else
value_len = b - 1 -- 减 CMD 字节
if value_len > MAX_FRAME_VALUE then
reset() -- 超长帧丢弃
else
value_buf = ""
state = ST_CMD
end
end
elseif state == ST_CMD then
chk_xor = chk_xor ~ b
chk_sum = (chk_sum + b) % 256
cmd = b
if value_len == 0 then
state = ST_CHECK_X
else
state = ST_VALUE
end
elseif state == ST_VALUE then
chk_xor = chk_xor ~ b
chk_sum = (chk_sum + b) % 256
value_buf = value_buf .. string.char(b)
if value_buf:len() >= value_len then
state = ST_CHECK_X
end
elseif state == ST_CHECK_X then
if b ~= chk_xor then
reset()
else
state = ST_CHECK_S
end
elseif state == ST_CHECK_S then
if b == chk_sum then
complete()
else
reset()
end
end
end
-- 初始化回调
function frame_parser.init(cb)
on_frame = cb
reset()
end
-- 复位(链路异常/切换时调用)
function frame_parser.reset()
reset()
end
return frame_parser
@@ -1,17 +1,17 @@
--[[ --[[
@module main @module main
@summary vd960Air — Air8781P(Air780EPM) 4G 模块 LuatOS 应用入口 @summary vd960Air — Air8781P(Air780EPM) 4G 模块 LuatOS 应用入口(方案 C MVP)
@version 0.1 @version 0.3
@date 2026.08.31 @date 2026.08.31
@usage @usage
vd960DBN 车检器在有线网络效时的 4G 兜底通道(UART <-> MQTT 桥): vd960DBN 车检器在有线网络效时的 4G 兜底通道(方案 C: Air780 协议转换):
1、UART1 对接 vd960DBN(CH32V208 串口1 PB6/PB7),帧协议见 uart_app.lua; 1、UART1 对接 vd960DBN(CH32V208 串口1 PB6/PB7),0x7F 帧字节流透传;
2、上行: vd960DBN JSON 帧 → 4G → MQTT publish 到 dld960/{sn}/dev 2、0x7F 帧解析 → 0xC0 传感 → loop_data JSON(标准,注入 link)→ MQTT dld960/{sn}/dev;
(JSON 命令面与《DLD960_IoT_MQTT协议》V1.10 完全一致,平台侧无感); 3、car_state/loop_state 沿检测 → event_report(ACK + 5s×3 重发,对齐 DBN 语义);
3、下行: MQTT 订阅 dld960/{sn}/srv → 4G → 组帧 → UART1 下发 vd960DBN; 4、下行: report_config 时钟校准 + event_report ACK;业务命令转换 P1;
4、网络看门狗: 长时间无网络业务则重启系统(官方 network_watchdog); 5、initialize(extra_info imei/iccid + link)/ heartbeat 周期上报;
5、网卡: 仅 4G(netdrv_4g)。 6、网络看门狗 + 4G 网卡(官方组件)。
]] ]]
PROJECT = "vd960Air" PROJECT = "vd960Air"
VERSION = "001.999.000" VERSION = "001.999.000"
@@ -28,9 +28,13 @@ require "network_watchdog"
-- ==================== 网卡驱动(仅 4G) ==================== -- ==================== 网卡驱动(仅 4G) ====================
require "netdrv_device" require "netdrv_device"
-- ==================== 串口应用(UART1 <-> vd960DBN) ==================== -- ==================== 串口应用(UART1 <-> vd960DBN,0x7F 帧) ====================
require "uart_app" require "uart_app"
-- ==================== 设备业务(initialize/heartbeat/poll) ====================
local app_iot = require "app_iot"
app_iot.init()
-- ==================== MQTT 客户端(单路, 主题 dld960/{sn}/srv + dev) ==================== -- ==================== MQTT 客户端(单路, 主题 dld960/{sn}/srv + dev) ====================
require "mqtt_main" require "mqtt_main"
@@ -22,6 +22,8 @@ local mqtt_receiver = require "mqtt_receiver"
local mqtt_sender = require "mqtt_sender" local mqtt_sender = require "mqtt_sender"
-- 加载vd960Air配置 -- 加载vd960Air配置
local CFG = require "config" local CFG = require "config"
-- 设备业务(initialize/heartbeat)
local app_iot = require "app_iot"
-- mqtt服务器地址和端口(来自config.lua,商用服务器) -- mqtt服务器地址和端口(来自config.lua,商用服务器)
local SERVER_ADDR = CFG.CFG_MQTT_HOST local SERVER_ADDR = CFG.CFG_MQTT_HOST
@@ -191,6 +193,10 @@ local function mqtt_client_main_task_func()
log.info("mqtt_client_main_task_func", "connect success") log.info("mqtt_client_main_task_func", "connect success")
-- 通知mqtt sender数据发送应用模块的task,MQTT连接成功 -- 通知mqtt sender数据发送应用模块的task,MQTT连接成功
sys.sendMsg(mqtt_sender.TASK_NAME, "MQTT_EVENT", "CONNECT_OK", mqtt_client) sys.sendMsg(mqtt_sender.TASK_NAME, "MQTT_EVENT", "CONNECT_OK", mqtt_client)
-- vd960Air: 上线发 initialize + 未决事件包立即重发
app_iot.on_connected()
local uart_app = require "uart_app"
uart_app.on_mqtt_reconnect()
-- mqtt连接失败 -- mqtt连接失败
else else
log.info("mqtt_client_main_task_func", "connect error") log.info("mqtt_client_main_task_func", "connect error")
@@ -1,64 +1,55 @@
--[[ --[[
@module mqtt_receiver @module mqtt_receiver
@summary mqtt client数据接收处理应用功能模块 @summary MQTT 下行命令分发(方案 C)
@version 1.0 @version 0.2
@date 2025.07.29 @date 2026.08.31
@author 马梦阳
@usage @usage
本文件为mqtt client 数据接收应用功能模块,核心业务逻辑为: 平台下行(dld960/{sn}/srv)处理:
处理接收到的publish数据,同时将数据发送给其他应用功能模块做进一步处理; - event_report ACK(回显 msg_id + code=0)→ evt_queue 出队
- report_config → 时钟校准(信封 ts,协议 §2.3)
本文件的对外接口有2个: - 其他标准 JSON 命令(loop_param_*/log_*/ota_* 等)→ P1: 转 0x7F 帧下发 vd960Loop(待实现)
1、mqtt_receiver.proc(topic, payload, metas)publish数据处理入口,在mqtt_main.lua中调用; - frame_cmd(可选兜底)→ P1: hex 解码 → UART1 透传(待实现)
2、sys.publish("RECV_DATA_FROM_SERVER", "recv from mqtt server: ", topic, payload)
将接收到的publish中的topic和payload数据通过消息"RECV_DATA_FROM_SERVER"发布出去;
需要处理数据的应用功能模块订阅处理此消息即可,本demo项目中uart_app.lua中订阅处理了本消息;
]] ]]
local mqtt_receiver = {} local mqtt_receiver = {}
local function proc(topic, payload, metas)
if not payload or payload:len() == 0 then
return
end
local ok, obj = pcall(json.decode, payload)
if not ok or type(obj) ~= "table" then
log.warn("mqtt_receiver", "bad json")
return
end
--[[ local cmd = obj.cmd
处理接收到的publish数据 local msg_id = obj.msg_id or 0
local code = obj.code
@api mqtt_receiver.proc(topic, payload, metas) if cmd == "event_report" then
-- 平台 ACK: 回显 msg_id + code;code=0 → 出队
local uart_app = require "uart_app"
uart_app.handle_event_ack(msg_id, code or 0)
log.info("mqtt_receiver", "evt ack", msg_id, code)
@param1 topic string elseif cmd == "report_config" then
表示publish主题 -- 时钟校准(信封 ts 填当前 Unix 秒)
local clock = require "clock"
clock.sync(obj.ts or 0, mstick())
log.info("mqtt_receiver", "clock sync", obj.ts)
@param2 payload string else
表示publish数据负载 -- P1: 下行命令转换(loop_param_*/log_*/ota_* 等)暂未实现
log.info("mqtt_receiver", "down cmd", cmd, "msg_id", msg_id)
end
@param2 payload string -- 喂网络看门狗
表示publish数据负载
@param3 metas table
表示publish报文的一些参数;格式如下:
{
qos: number类型,取值范围0,1,2
retainnumber类型,取值范围0,1
dupnumber类型,取值范围0,1
message_id: number类型
}
@return1 result nil
@usage
mqtt_receiver.proc(topic, payload, metas)
]]
function mqtt_receiver.proc(topic, payload, metas)
log.info("mqtt_receiver.proc", topic, payload:len(), json.encode(metas))
-- 接收到数据,通知网络环境检测看门狗功能模块进行喂狗
sys.publish("FEED_NETWORK_WATCHDOG") sys.publish("FEED_NETWORK_WATCHDOG")
end
-- 将topic和payload通过"RECV_DATA_FROM_SERVER"消息publish出去,给其他应用模块处理 function mqtt_receiver.proc(topic, payload, metas)
-- vd960Air: 直接传原始 payload,由 uart_app 组帧后下发 vd960DBN proc(topic, payload, metas)
sys.publish("RECV_DATA_FROM_SERVER", topic, payload)
-- 也可以直接在此处编写代码,处理topic和payload
end end
return mqtt_receiver return mqtt_receiver
@@ -0,0 +1,141 @@
--[[
@module proto_conv
@summary 0x7F 帧 ↔ 标准 JSON 协议转换(方案 C 核心)
@version 0.1
@date 2026.08.31
@usage
Air780 解析 vd960DBN 转发的 0x7F 帧,转换为《DLD960_IoT_MQTT协议》标准 JSON 命令:
- 0xC0 传感帧(多线圈) → loop_data JSON
- car_state / loop_state 沿检测 → event_report 事件(car_enter/car_leave/loop_cut/loop_restore)
- 4G 事件面 = 仅线圈事件(不含 DBN 内部网络事件,协议 §6.4)
0xC0 帧 payload(cmd=0xC0 之后):
[SensType(1)][SubPkgFlag(1)][SensData: 4×12B]
每路线圈单元 12B(与《DLD960Loop_串口通信协议》§3.07 一致):
config(1): freq_level(bit6-7) + direction(bit5) + freq_type(bit4) + sens(bit0-3)
eval(1): condition(bit4-7) + loop_state(bit3) + car_state(bit2) + misc_type(bit0-1)
freq(3B LE 无符号) + variation(3B LE 有符号) + misc(4B LE)
]]
local proto_conv = {}
-- 线圈高低频档位 → level 字符串
local LEVEL_NAMES = { "high", "mid_high", "mid_low", "low" }
-- 杂项类型 → type 字符串
local MISC_NAMES = { "time", "cut_count", "flow_count", "relay_count" }
-- 3B 小端无符号
local function u24le(s, off)
return string.byte(s, off)
+ string.byte(s, off + 1) * 256
+ string.byte(s, off + 2) * 65536
end
-- 3B 小端有符号(补码)
local function i24le(s, off)
local v = u24le(s, off)
if v >= 0x800000 then
v = v - 0x1000000
end
return v
end
-- 4B 小端无符号
local function u32le(s, off)
return string.byte(s, off)
+ string.byte(s, off + 1) * 256
+ string.byte(s, off + 2) * 65536
+ string.byte(s, off + 3) * 16777216
end
-- 解析 0xC0 传感帧 payload → 4 路通道表
-- @param payload string: cmd 之后的字节(SensType + SubPkgFlag + SensData)
-- @return channels table or nil: [{ch, level, iscar, loop_ok, freq, diff, sens, cndtn, misc}]
function proto_conv.parse_sensor(payload)
if not payload or payload:len() < 2 then
return nil
end
local sens_type = string.byte(payload, 1)
if sens_type ~= 0x0C then
return nil -- 非多线圈传感类型
end
-- payload: [SensType(1)][SubPkgFlag(1)][SensData...]
local data = payload:sub(3)
local channels = {}
local coil_count = 4 -- 960Loop 固定 4 路
for i = 1, coil_count do
local off = (i - 1) * 12 + 1
if data:len() < off + 11 then
break
end
local cfg = string.byte(data, off)
local evl = string.byte(data, off + 1)
local freq_level = (cfg >> 6) & 0x03
local sens = cfg & 0x0F
local condition = (evl >> 4) & 0x0F
local loop_state = (evl >> 3) & 0x01
local car_state = (evl >> 2) & 0x01
local misc_type = evl & 0x03
local freq = u24le(data, off + 2)
local diff = i24le(data, off + 5)
local misc = u32le(data, off + 8)
channels[i] = {
ch = i,
level = LEVEL_NAMES[freq_level + 1] or "high",
iscar = (car_state == 1),
loop_ok = (loop_state == 0),
freq = freq,
diff = diff,
sens = sens,
cndtn = condition,
misc = { type = MISC_NAMES[misc_type + 1] or "time", value = misc },
}
end
return channels
end
-- ==================== 事件沿检测 ====================
-- 保存上一帧每路 car_state / loop_state
local prev_car = { 0, 0, 0, 0 }
local prev_loop = { 0, 0, 0, 0 }
-- 沿检测: 输入当前通道表,输出事件列表
-- @return events: [{type, ch, value}]
function proto_conv.detect_events(channels)
local events = {}
if not channels then
return events
end
for i = 1, #channels do
local ch = channels[i]
local cur_car = ch.iscar and 1 or 0
local cur_loop = ch.loop_ok and 0 or 1 -- loop_ok=false → 断开=1
if i <= 4 then
-- car 沿(线圈断开期间屏蔽,与 DBN 同策略)
if prev_loop[i] == 0 and cur_loop == 0 then
if prev_car[i] == 0 and cur_car == 1 then
events[#events + 1] = { type = "car_enter", ch = i, value = ch.misc.value }
elseif prev_car[i] == 1 and cur_car == 0 then
events[#events + 1] = { type = "car_leave", ch = i, value = ch.misc.value }
end
end
-- loop 沿
if prev_loop[i] == 0 and cur_loop == 1 then
events[#events + 1] = { type = "loop_cut", ch = i, value = 0 }
elseif prev_loop[i] == 1 and cur_loop == 0 then
events[#events + 1] = { type = "loop_restore", ch = i, value = 0 }
end
prev_car[i] = cur_car
prev_loop[i] = cur_loop
end
end
return events
end
-- 复位沿检测状态(上电/MQTT 重连后避免误报)
function proto_conv.reset_events()
prev_car = { 0, 0, 0, 0 }
prev_loop = { 0, 0, 0, 0 }
end
return proto_conv
@@ -193,16 +193,23 @@ vd960DBN 与地感 MCU 的串口逻辑(loop_uart_proto.c,UART2,0x7F 帧):
``` ```
vd960Air/ vd960Air/
├── main.lua # 入口: 加载 config/看门狗/4G网卡/uart_app/mqtt_main ├── main.lua # 入口: 加载 config/看门狗/4G网卡/uart_app/app_iot/mqtt_main
├── config.lua # 配置: MQTT 服务器/序列号/主题/UART/帧参数/link 注入开关 ├── config.lua # 配置: MQTT 服务器/序列号/主题/UART/上报节奏/link 开关
├── uart_app.lua # UART1 <-> vd960DBN 帧协议(接收状态机 + 下行组帧 + link 注入) ├── frame_parser.lua # ★ 0x7F 帧解析状态机(Lua 版 lup_feed_byte,IDLE→HEADER→VALUE→CHECK)
├── proto_conv.lua # ★ 0xC0 传感帧 → loop_data + car_state/loop_state 沿检测
├── evt_queue.lua # ★ event_report 队列 + ACK 状态机(16深/5s×3/跨重连同 msg_id)
├── clock.lua # 设备时钟: report_config 校准 → 真实 Unix 秒(协议 §2.3)
├── app_iot.lua # initialize(extra_info imei/iccid + link)/ heartbeat / poll 驱动
├── uart_app.lua # UART1 接收 → frame_parser → 上行 JSON 发布;ACK 路由
├── link_info.lua # 4G 链路信息采集(IMEI/ICCID/IMSI/MSISDN/CSQ) ├── link_info.lua # 4G 链路信息采集(IMEI/ICCID/IMSI/MSISDN/CSQ)
├── netdrv_device.lua # 网卡: 仅 4G(官方 netdrv_4g) ├── netdrv_device.lua # 网卡: 仅 4G(官方 netdrv_4g)
├── network_watchdog.lua # 网络看门狗(官方原样) ├── network_watchdog.lua # 网络看门狗(官方原样)
├── tools/
│ └── sim_frame_test.py # 帧协议参考验证(Python 1:1 复刻解析公式,字节级断言)
└── mqtt/ └── mqtt/
├── mqtt_main.lua # MQTT 客户端(单路, 主题改 dld960/{sn}/srv + dev) ├── mqtt_main.lua # MQTT 客户端(连接成功 → initialize + 未决事件重发)
├── mqtt_receiver.lua # 下行 → RECV_DATA_FROM_SERVER(去掉演示前缀) ├── mqtt_receiver.lua # 下行分发: event_report ACK 路由 + report_config 时钟校准
└── mqtt_sender.lua # 上行队列 + publish(去掉演示前缀) └── mqtt_sender.lua # 上行队列 + publish(官方改造)
``` ```
## 5 vd960DBN 对接要点 ## 5 vd960DBN 对接要点
@@ -246,10 +253,11 @@ vd960Air/
| 阶段 | 内容 | 状态 | | 阶段 | 内容 | 状态 |
|------|------|------| |------|------|------|
| P0 | 工程骨架 + 帧协议 + MQTT 单路桥接 + link 字段注入 | ✅ 完成 | | P0 | 工程骨架 + 帧协议 + MQTT 桥接 + link 注入 | ✅ 完成 |
| P1 | 协议适配文档(4G 章节)+ vd960DBN 侧 UART1 通道 + **配置同步下发(Air780 存储更新)** | ⏳ 待文档/待 vd960DBN 版本 | | **P0.5** | **方案 C MVP: 0x7F 帧解析 + 0xC0→loop_data + 沿检测→event_report + ACK 状态机 + initialize/heartbeat + 时钟校准**(2026-08-31) | ✅ 代码完成,待板级 |
| P1 | 下行命令转换(loop_param_*/log_*/ota_* 转 0x7F)+ 0x7D 配置同步下发 + vd960DBN UART1 通道联调 | ⏳ 待板/待 vd960DBN 版本 |
| P2 | 板级联调: 帧校验、背压、掉线重连、看门狗 | ⏳ 待板 | | P2 | 板级联调: 帧校验、背压、掉线重连、看门狗 | ⏳ 待板 |
| P3 | 4G 状态上报 + 通道切换(策略待讨论)+ 脱机日志/OTA 走 4G | ⏳ 规划 | | P3 | 通道切换(策略待讨论)+ 脱机日志/OTA 走 4G | ⏳ 规划 |
**vd960DBN 侧开发计划(2026-08-31 用户确认列入):** **vd960DBN 侧开发计划(2026-08-31 用户确认列入):**
- [ ] UART1(PB6/PB7)通道: 帧收发 + 0x7F 帧魔数分流(0x7F → UART2 透传 / 0x8F → 本地) - [ ] UART1(PB6/PB7)通道: 帧收发 + 0x7F 帧魔数分流(0x7F → UART2 透传 / 0x8F → 本地)
@@ -1,132 +1,123 @@
--[[ --[[
@module uart_app @module uart_app
@summary vd960Air 串口应用功能模块: UART1 <-> vd960DBN 帧协议对接 @summary vd960Air 串口应用: UART1 对接 vd960DBN(0x7F 帧) → 标准 JSON 上报(方案 C)
@version 0.2 @version 0.3
@date 2026.08.31 @date 2026.08.31
@usage @usage
核心职责: 上行链路(方案 C):
1、打开 UART1(对接 vd960DBN CH32V208 串口1 PB6/PB7); vd960Loop --0x7F帧--> vd960DBN --原样转发(UART1)--> Air780
2、上行: 接收 vd960DBN 的 JSON 帧(复用《DLD960_IoT_MQTT协议》命令面), → frame_parser 解析 0x7F 帧
校验后注入 link 对象 → 发布 SEND_DATA_REQ → mqtt_sender publish 到 dld960/{sn}/dev; → 0xC0 传感帧: proto_conv 解析 → loop_data JSON(注入 link)发布
3、下行: 订阅 RECV_DATA_FROM_SERVER 消息,将 MQTT 下行 JSON 组帧后经 UART1 下发 vd960DBN。 → car_state/loop_state 沿检测: evt_queue 入队 → event_report 发布(ACK 机制)
下行链路:
UART 链路帧格式(v0.2,校验统一 XOR+SUM — 用户决策 2026-08-31,与 DBN↔Loop 0x7F 一致): MQTT 标准 JSON 命令 → (P1: 转 0x7F 帧) → UART1 → vd960DBN → vd960Loop
┌──────┬───────┬───────┬────────────────────┬──────┬──────┐ MVP 范围: 上行(initialize/loop_data/event_report/heartbeat)+ report_config 时钟校准;
│ HEAD │ LEN_H │ LEN_L │ PAYLOAD │ XOR │ SUM │ 下行命令转换与 0x7D 配置同步列入 P1。
│ 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" local CFG = require "config"
-- 4G 链路信息采集(IMEI/ICCID/CSQ)
local link_info = require "link_info" local link_info = require "link_info"
local frame_parser = require "frame_parser"
local proto_conv = require "proto_conv"
local evt_queue = require "evt_queue"
local clock = require "clock"
local UART_ID = CFG.CFG_UART_ID local uart_app = {}
local MAX_LEN = CFG.CFG_FRAME_MAXLEN
-- ==================== 接收状态机 ==================== local UART_ID = CFG.CFG_UART_ID
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 -- 上行 JSON 注入 link 对象(4G 特有字段)
rx_len = 0 local function inject_link(payload)
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 if CFG.CFG_LINK_ENABLE then
local ok, obj = pcall(json.decode, payload) local ok, obj = pcall(json.decode, payload)
if ok and type(obj) == "table" then if ok and type(obj) == "table" then
obj.link = link_info.get() obj.link = link_info.get()
payload = json.encode(obj) return json.encode(obj)
else
-- JSON 解析失败: 原样转发,不阻塞上行
log.warn("uart_app.frame", "json decode fail, forward raw")
end end
end end
return payload
end
-- SEND_DATA_REQ(tag, topic, payload, qos) -- 组 loop_data JSON(协议 §5.2)
-- 上行统一发布到 dev 主题(平台侧与有线通道无差别) local function build_loop_data(channels, ts, msg_id)
local parts = {}
for i = 1, #channels do
local c = channels[i]
parts[i] = string.format(
'{"ch":%d,"level":"%s","iscar":%s,"loop_ok":%s,"freq":%d,"diff":%d,'
.. '"sens":%d,"cndtn":%d,"misc":{"type":"%s","value":%d}}',
c.ch, c.level, tostring(c.iscar), tostring(c.loop_ok),
c.freq, c.diff, c.sens, c.cndtn, c.misc.type, c.misc.value)
end
return string.format('{"msg_id":%d,"cmd":"loop_data","ts":%d,"data":{"channels":[%s]}}',
msg_id, ts, table.concat(parts, ","))
end
-- 发布 event_report(新发或重发)
local function publish_event()
local pend_id, pend_n, pend_ts = evt_queue.pending_info()
local ts
if pend_n and pend_n > 0 then
ts = pend_ts -- 重发: 原始 ts,msg_id 不变
else
ts = clock.now() -- 新发
end
local payload = evt_queue.build_report(ts)
if not payload then
return
end
payload = inject_link(payload)
sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, payload, 1) sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, payload, 1)
local id = evt_queue.last_msg_id()
evt_queue.mark_sent(id, evt_queue.count(), ts, mstick())
log.info("uart_app.evt", "pub id", id, "n", evt_queue.count(), "ts", ts)
end end
-- 逐字节喂入接收状态机 -- ==================== 0x7F 帧回调 ====================
local function feed_byte(b) -- 独立 loop_data msg_id 计数
if rx_state == RX_IDLE then local g_ld_msg_id = 0
if b == CFG.CFG_FRAME_HEAD then local function next_ld_msg_id()
rx_state = RX_HEAD g_ld_msg_id = g_ld_msg_id + 1
return g_ld_msg_id
end
-- 最近通道状态缓存(heartbeat 的 loop_status 用)
local last_loop_ok = { true, true, true, true }
local function on_frame(cmd, payload)
if cmd == 0xC0 then
-- 多线圈传感帧 → loop_data + 沿检测事件
local channels = proto_conv.parse_sensor(payload)
if not channels then
return
end end
elseif rx_state == RX_HEAD then -- 刷新 loop_status 缓存
rx_len = b * 256 for i = 1, #channels do
rx_xor = b last_loop_ok[i] = channels[i].loop_ok
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 end
elseif rx_state == RX_LENL then -- 沿检测 → 事件入队(返回是否需要立即发布)
-- 0 长度帧非法 local need_evt = false
if rx_len == 0 then local events = proto_conv.detect_events(channels)
rx_reset() for i = 1, #events do
else need_evt = evt_queue.enqueue(events[i]) or need_evt
rx_state = RX_DATA
end end
elseif rx_state == RX_DATA then -- loop_data 上报(带 link)
rx_buf = rx_buf .. string.char(b) local ts = clock.now()
rx_xor = rx_xor ~ b local ld = inject_link(build_loop_data(channels, ts, next_ld_msg_id()))
rx_sum = (rx_sum + b) % 256 sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, ld, 0)
if rx_buf:len() >= rx_len then -- 事件发布
rx_state = RX_XOR if need_evt then
publish_event()
end end
elseif rx_state == RX_XOR then else
if b ~= rx_xor then -- 其他 0x7F 帧(命令响应等): P1 下行命令转换实现后处理
log.warn("uart_app.feed", "xor fail") log.info("uart_app.frame", "cmd", string.format("0x%02X", cmd), "len", payload:len())
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
end end
-- UART1 接收中断处理 -- ==================== UART 接收 ====================
local function read() local function read()
local s local s
while true do while true do
@@ -135,40 +126,45 @@ local function read()
break break
end end
for i = 1, s:len() do for i = 1, s:len() do
feed_byte(string.byte(s, i)) frame_parser.feed(string.byte(s, i))
end end
end end
end end
-- ==================== 下行: MQTT → UART1 ==================== -- ==================== 对外接口 ====================
-- mqtt_receiver 发布 RECV_DATA_FROM_SERVER(topic, payload) -- MQTT 重连后: 未决事件包立即重发
local function recv_data_from_server_proc(topic, payload) function uart_app.on_mqtt_reconnect()
if not payload or payload:len() == 0 then local action = evt_queue.on_reconnect(mstick())
return if action == "resend" then
publish_event()
end end
-- 组帧: HEAD + LEN(2B BE) + PAYLOAD + XOR + SUM end
-- 校验范围: 从 LEN_H 起(不含 HEAD,与 0x7F 同风格)
local len = payload:len() -- 平台 ACK(事件确认)路由
local hi = math.floor(len / 256) function uart_app.handle_event_ack(msg_id, code)
local lo = len % 256 evt_queue.handle_ack(msg_id, code)
local x = hi ~ lo end
local s = (hi + lo) % 256
for i = 1, len do -- 主循环驱动: 事件重发超时检查
local b = string.byte(payload, i) function uart_app.poll()
x = x ~ b local action = evt_queue.poll(mstick())
s = (s + b) % 256 if action == "resend" then
publish_event()
end end
local frame = string.char(CFG.CFG_FRAME_HEAD) end
.. string.char(hi) .. string.char(lo)
.. payload -- 上报当前传感缓存? MVP: 心跳带 loop_status 由 mqtt_main 维护,此处暂不提供
.. string.char(x) .. string.char(s)
uart.write(UART_ID, frame) -- heartbeat 用: 最近 4 路线圈状态(true=正常)
log.info("uart_app.down", "len", len) function uart_app.get_loop_status()
return last_loop_ok
end end
-- ==================== 初始化 ==================== -- ==================== 初始化 ====================
frame_parser.init(on_frame)
uart.setup(UART_ID, CFG.CFG_UART_BAUD, 8, 1) uart.setup(UART_ID, CFG.CFG_UART_BAUD, 8, 1)
uart.on(UART_ID, "receive", read) 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) log.info("uart_app", "init ok", "baud", CFG.CFG_UART_BAUD, "topic_up", CFG.CFG_TOPIC_UP)
return uart_app