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,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