feat(vd960DBN)+fix(DBNMQTTool): log_query 改 hex 原始字节上报 (2026-08-18)

背景: MQTT 快照流实测 MQTTSerialize_publish failed — JSON 化快照记录 ~810B/条 超 800B 发送缓冲
方案(用户拍板): 对齐 BLE 通道, 原始字节 hex 上报

固件 (V4.3):
- offlog.c/h: 新增 offlog_evt_to_hex() (32B→64 hex)
- snapshot.c/h: 新增 snap_rec_to_hex() (64B→128 hex); 删 SNAP_MAX_QUERY_JSON, 恢复 count=2
- tcp_json_srv.c / iot_mqtt_srv.c: log_query 改 {"seq":N,"hex":"..."}; SEND_BUF 保持 800
- 2 条快照 hex 响应 406B < 800B

文档: TCP JSON V1.03 / MQTT V1.07 §4.17 records 改 hex + 解析表引用 BLE §6.4/§7

工具: parse_offlog_hex/parse_snap_hex/offlog_payload_desc + hex 展示; 验证: gcc 9 断言 + 工具解析全过 + offscreen UI
This commit is contained in:
wangfq
2026-08-18 14:04:26 +08:00
parent 5e9b188c6a
commit 5a1893cd1c
15 changed files with 331 additions and 180 deletions
+90 -1
View File
@@ -8,6 +8,7 @@ DLD960 IoT MQTT 协议定义
import json
import time
import struct
from typing import Optional, Any
from dataclasses import dataclass, field, asdict
@@ -250,7 +251,7 @@ def data_report_config(sensor_type: int = 12, enable: bool = True,
def data_log_query(start_seq: int = 1, count: int = 4, stream: str = STREAM_EVENT) -> dict:
"""V1.07 log_query 分页拉取脱机日志
stream=event: 事件流, count 上限 4 (设备侧超限按 4 处理)
stream=snapshot: 快照流, count 上限 2 (64B×2 记录); 显式传 stream 字段
stream=snapshot: 快照流, count 上限 2 (hex 原始字节上报); 显式传 stream 字段
"""
d: dict[str, Any] = {
"start_seq": max(1, int(start_seq)),
@@ -282,3 +283,91 @@ def parse_topic_dev_serial(topic: str) -> Optional[str]:
if len(parts) >= 2 and parts[0] == "dld960":
return parts[1]
return None
# ============================================================
# 脱机日志 hex 解析 (V1.07: log_query 响应 records[].hex 原始字节)
# 字段表与《DLD960 BLE 协议》§6.4 (SnapRec) / §7 (OfflogEvt) 一致
# ============================================================
# OfflogEvt 事件类型 (BLE 协议 §7 事件类型表)
OFFLOG_TYPE_NAMES = {
0x01: "boot", 0x10: "iot_connect", 0x11: "iot_ready", 0x12: "iot_disconnect",
0x13: "iot_reconn", 0x30: "evt_retry", 0x31: "evt_giveup", 0x40: "coil",
0x50: "time_anchor", 0x70: "log_clear",
}
_OFFLOG_SUB_NAMES = {1: "car_enter", 2: "car_leave", 3: "loop_cut", 4: "loop_restore"}
_OFFLOG_DISC_REASONS = {1: "断开", 2: "超时", 3: "CONNACK拒绝", 4: "连接超时"}
def parse_offlog_hex(h: str) -> dict:
"""OfflogEvt 32B hex → dict (magic/type/seq/ts_ms/unix_ts/boot_seq/payload)"""
b = bytes.fromhex(h)
if len(b) != 32:
return {"error": f"bad len {len(b)}"}
magic, etype, elen, flags, seq, ts_ms, unix_ts, boot_seq, rsvd, payload = \
struct.unpack('<BBBBIIIHH12s', b)
return {"magic": magic, "type": etype, "len": elen, "flags": flags, "seq": seq,
"ts_ms": ts_ms, "unix_ts": unix_ts, "boot_seq": boot_seq, "payload": payload}
def offlog_payload_desc(etype: int, payload: bytes) -> str:
"""事件 payload 人类可读描述 (BLE 协议 §7 payload 定义, 大端)"""
if etype == 0x01: # boot: 复位原因寄存器
rst = int.from_bytes(payload[0:4], 'big') if len(payload) >= 4 else 0
bits = [n for n, b in [("LPWR", 31), ("WWDG", 30), ("IWDG", 29),
("SFT", 28), ("POR", 27), ("NRST", 26)] if rst & (1 << b)]
return f"rst=0x{rst:08X} ({'+'.join(bits) if bits else 'none'})"
if etype == 0x12: # iot_disconnect
r = payload[0] if payload else 0
return f"reason={r} ({_OFFLOG_DISC_REASONS.get(r, '?')})"
if etype == 0x13: # iot_reconn
return f"backoff_ms={int.from_bytes(payload[0:4], 'big')}" if len(payload) >= 4 else ""
if etype == 0x30: # evt_retry
mid = int.from_bytes(payload[0:4], 'big') if len(payload) >= 4 else 0
retry = payload[4] if len(payload) >= 5 else 0
return f"msg_id={mid} retry={retry}"
if etype == 0x31: # evt_giveup
mid = int.from_bytes(payload[0:4], 'big') if len(payload) >= 4 else 0
return f"msg_id={mid}"
if etype == 0x40: # coil
sub = _OFFLOG_SUB_NAMES.get(payload[0], payload[0]) if payload else "?"
ch = payload[1] if len(payload) >= 2 else 0
val = int.from_bytes(payload[2:6], 'big') if len(payload) >= 6 else 0
return f"sub={sub} ch={ch} value={val}"
if etype in (0x10, 0x11, 0x50, 0x70): # 无 payload
return ""
return payload.hex() if payload else ""
def parse_snap_hex(h: str) -> dict:
"""SnapRec 64B hex → dict (seq/boot_seq/ts_ms/coil_count/channels[])"""
b = bytes.fromhex(h)
if len(b) != 64:
return {"error": f"bad len {len(b)}"}
magic, slen, flags, rsvd, seq, ts_ms, boot_seq, rsvd2, coils = \
struct.unpack('<BBBBIIHH48s', b)
coil_count = min(slen // 12, 4)
channels = []
for c in range(coil_count):
p = coils[c * 12:(c + 1) * 12]
cfg, cond = p[0], p[1]
freq = p[2] | (p[3] << 8) | (p[4] << 16)
var = p[5] | (p[6] << 8) | (p[7] << 16)
if var & 0x800000:
var -= 0x1000000
misc = int.from_bytes(p[8:12], 'little')
channels.append({
"ch": c + 1,
"freq_level": ("high", "mid_high", "mid_low", "low")[(cfg >> 6) & 3],
"direction": (cfg >> 5) & 1,
"freq_type": (cfg >> 4) & 1,
"sensitivity": cfg & 0x0F,
"condition": (cond >> 4) & 0x0F,
"loop_ok": not ((cond >> 3) & 1),
"has_car": bool((cond >> 2) & 1),
"misc_type": ("time", "cut_count", "flow_count", "relay_count")[cond & 3],
"freq": freq, "variation": var, "misc": misc,
})
return {"seq": seq, "boot_seq": boot_seq, "ts_ms": ts_ms,
"coil_count": coil_count, "channels": channels}