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:
@@ -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
|
||||
Reference in New Issue
Block a user