Files
mobile_comm/luatos/air780epm/module/Air780EPM/demo/vd960Air/uart_app.lua
T
wangfq 28418dab66 feat(vd960Air): 配置同步改用 0x8F 本机侧私有帧;MQTT 凭据外置到非跟踪文件
- readme §2.2/§3:0x7D 帧设计稿 v0.2 作废,改写为「0x7F 业务帧字节流 + 0x8F 本机侧私有帧」
  (权威源规则 / 拉取为主+推送为辅 / 帧长上限 260B / 两侧缓冲需同步扩容),依据协议 §6.9
- readme §3.1:对比表由 0x7D 改为 0x8F,保留 0x7F 帧布局参考
- config.lua / uart_app.lua:注释中的 0x7D 计划改为 0x8F 配置同步;协议版本引用 V1.13 → V1.14
- config.lua:MQTT 账号密码不再明文入库,改从 cred.lua 读取;缺失时降级为空串(匿名连接)
- 新增 .gitignore(忽略 cred.lua)+ cred.lua.example(填写模板)
  ⚠ 原明文凭据已存在于 git 历史,商用前必须更换服务器密码
- 语法:luac5.3 -p config.lua / cred.lua / uart_app.lua 全部通过
2026-09-10 21:46:02 +08:00

171 lines
5.5 KiB
Lua

--[[
@module uart_app
@summary vd960Air 串口应用: UART1 对接 vd960DBN(0x7F 帧) → 标准 JSON 上报(方案 C)
@version 0.3
@date 2026.08.31
@usage
上行链路(方案 C):
vd960Loop --0x7F帧--> vd960DBN --原样转发(UART1)--> Air780
→ frame_parser 解析 0x7F 帧
→ 0xC0 传感帧: proto_conv 解析 → loop_data JSON(注入 link)发布
→ car_state/loop_state 沿检测: evt_queue 入队 → event_report 发布(ACK 机制)
下行链路:
MQTT 标准 JSON 命令 → (P1: 转 0x7F 帧) → UART1 → vd960DBN → vd960Loop
MVP 范围: 上行(initialize/loop_data/event_report/heartbeat)+ report_config 时钟校准;
下行命令转换与 0x8F 配置同步(拉取+推送)列入 P1。
]]
-- 依赖
local CFG = require "config"
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_app = {}
local UART_ID = CFG.CFG_UART_ID
-- ==================== 公共工具 ====================
-- 上行 JSON 注入 link 对象(4G 特有字段)
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
-- 组 loop_data JSON(协议 §5.2)
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)
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
-- ==================== 0x7F 帧回调 ====================
-- 独立 loop_data msg_id 计数
local g_ld_msg_id = 0
local function next_ld_msg_id()
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
-- 刷新 loop_status 缓存
for i = 1, #channels do
last_loop_ok[i] = channels[i].loop_ok
end
-- 沿检测 → 事件入队(返回是否需要立即发布)
local need_evt = false
local events = proto_conv.detect_events(channels)
for i = 1, #events do
need_evt = evt_queue.enqueue(events[i]) or need_evt
end
-- loop_data 上报(带 link)
local ts = clock.now()
local ld = inject_link(build_loop_data(channels, ts, next_ld_msg_id()))
sys.publish("SEND_DATA_REQ", "vd960DBN", CFG.CFG_TOPIC_UP, ld, 0)
-- 事件发布
if need_evt then
publish_event()
end
else
-- 其他 0x7F 帧(命令响应等): P1 下行命令转换实现后处理
log.info("uart_app.frame", "cmd", string.format("0x%02X", cmd), "len", payload:len())
end
end
-- ==================== UART 接收 ====================
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
frame_parser.feed(string.byte(s, i))
end
end
end
-- ==================== 对外接口 ====================
-- MQTT 重连后: 未决事件包立即重发
function uart_app.on_mqtt_reconnect()
local action = evt_queue.on_reconnect(mstick())
if action == "resend" then
publish_event()
end
end
-- 平台 ACK(事件确认)路由
function uart_app.handle_event_ack(msg_id, code)
evt_queue.handle_ack(msg_id, code)
end
-- 主循环驱动: 事件重发超时检查
function uart_app.poll()
local action = evt_queue.poll(mstick())
if action == "resend" then
publish_event()
end
end
-- 上报当前传感缓存? MVP: 心跳带 loop_status 由 mqtt_main 维护,此处暂不提供
-- heartbeat 用: 最近 4 路线圈状态(true=正常)
function uart_app.get_loop_status()
return last_loop_ok
end
-- ==================== 初始化 ====================
frame_parser.init(on_frame)
uart.setup(UART_ID, CFG.CFG_UART_BAUD, 8, 1)
uart.on(UART_ID, "receive", read)
log.info("uart_app", "init ok", "baud", CFG.CFG_UART_BAUD, "topic_up", CFG.CFG_TOPIC_UP)
return uart_app