Files
vd_960/docs/DLD960_IoT_MQTT协议.md
T
wangfq 12618578d8 docs: §6.9 补 BLE 分包承载与缓冲边界 + 4 条实现前置项 (V1.15)
- 核实 BLE 链路分包机制: 收/发双向对称, 分包头编码于 pkg[1] 高/低 4 位
  (高 4 位 = 总包数, 低 4 位 = 当前包序), 每包 dat <= 94B @MTU>=103,
  4 位总包数 => 最多 15 包; 发送侧先自增再编码, 线上首包 seq==1, 与接收侧
  low==1 起点 / high==low 收尾完全配对
- 修正 229B 载荷定性: 分包使 229B 确可送达 => 「载荷 > 131B 即越界写
  g_buf_ble_response.dat / tmp_ble_buf(132B)」属可达缺陷, 而非纸面能力
- §6.9.9 实施状态新增 4 条前置改造项: 缓冲扩 >= 260B / set_response_iot_net|topic
  补长度钳制 / 接收侧累加钳制(防 uint8 回绕) / unpack_packs 用起 len 形参
- devlog 归档本轮勘察, vd960DBN 源码零改动
2026-09-10 22:59:30 +08:00

68 KiB
Raw Blame History

DLD960 IoT 接口协议(MQTT + JSON

基于《DLD960 串口通信协议》V1.01,将设备管理、参数配置、数据上报映射到 MQTT 协议。 交互格式:JSON。 版本:V1.142026-09-10,新增 §6.9 4G 配置同步(BLE → DBN → Air780);0x8F 定义为本机侧私有帧,帧长上限 260B)


1 通信说明

1.1 连接参数

项目 说明
协议 MQTT 3.1.1 / 5.0
传输层 TCP (TLS 可选)
QoS 配置类 QoS 1,数据上报类 QoS 0/1
编码 UTF-8
序列化 JSON

1.2 Topic 结构

协议仅使用两个主题,所有消息类型通过 JSON 内 cmd 字段区分。

dld960/{dev_serial}/{direction}
字段 说明
dev_serial 设备序列码(6字节十六进制字符串,如 A1B2C3D4E5F6
direction srv = 服务器下发(设备订阅),dev = 设备上报(服务器订阅)

1.2.1 服务器下发 Topic — dld960/{sn}/srv

设备订阅此主题,接收服务器下发的所有命令(配置设置、查询、控制等)。 服务器发布到此主题。

对应串口 CMD0x09 ~ 0x1F, 0x63, 0x64, 0xC5 等。

1.2.2 设备上报 Topic — dld960/{sn}/dev

设备发布到此主题,上报线圈数据、事件、心跳及命令响应。 服务器订阅此主题接收所有设备上行消息。

服务器端可订阅通配符 dld960/+/dev 监听所有设备。


2 JSON 消息格式

2.1 通用结构

{
  "msg_id": 12345,
  "cmd": "dev_info_query",
  "ts": 1719000000,
  "data": { ... }
}
字段 类型 说明
msg_id uint32 消息序列号,递增,用于请求-响应匹配(由发起方生成)
cmd string 命令标识符
ts uint32 Unix 时间戳(秒),发起方填充
data object 命令参数,结构依 cmd 而定

2.2 响应通用结构

{
  "msg_id": 12345,
  "cmd": "dev_info_query",
  "ts": 1719000001,
  "code": 0,
  "msg": "success",
  "data": { ... }
}
字段 类型 说明
code int 0 = 成功,非 0 = 失败码
msg string 错误描述(成功时为 "success"

错误码定义

code 说明
0 成功
1 参数错误(格式/范围不正确)
2 密码验证失败
3 设备忙(操作执行中)
4 不支持的命令
5 内部错误
6 数据超长

OTA 细分错误码(V1.08

顶层 code 保持通用语义,ota_* 命令的细分错误经响应 data.err_code 表达:

err_code 场景 顶层 code
1 单片 CRC 失败 / 乱序缺片(data.offset 指示续传点) 1
2 会话状态不允许(未 begin / 非 downloading 收 ota_data 3
3 安全窗口拒绝(有车压线圈) 3
4 版本冲突(同版本且非 force 1
5 全镜像 CRC 不匹配(data.crc_ok=false 5
6 暂存区写失败(SPI 异常/满) 5

2.3 设备时钟同步(ts 语义)

设备无 RTC/SNTP 时间源,上电后本地时钟为上电秒数(从 0 递增)。为使上行数据带真实 Unix 时间:

  1. 设备上电连接成功后发布 initialize(此时 ts = 上电秒数,值很小,平台据此识别"未校准");
  2. 平台收到 initialize 后,下发 report_config 命令,其信封 ts 字段填当前 Unix 时间
  3. 设备收到后以该 ts 校准本地时钟基准,之后所有上行消息(loop_data / event_report / 心跳 / 命令响应)的 ts 均为真实 Unix 时间

补充约定:

  • report_config 为约定的主同步点;设备亦接受任意下行命令中合法(≥ 1600000000,即 2020-09 之后)的 ts 进行校准,非法/过小值被忽略。
  • 校准前(含首个 initialize),ts 为上电秒数;平台应能容忍并可按数量级区分。
  • 设备重启后需重新校准(无掉电保持)。平台在每次设备 initialize 后都应下发一次带 tsreport_config

脱机日志时间戳语义(V1.06 起)

  • 设备侧事件日志(log_query 拉取)每条记录携带双时间戳:ts_ms(boot 内相对时间,单位 ms,断电归零)+ unix_ts(已同步 Unix 秒,0 = 未同步)。
  • 设备经本协议同步成功后,其后记录的 unix_ts 均为真实 Unix 时间;同步前(含每次上电的 boot 事件)unix_ts = 0,仅 ts_ms 相对时间。
  • 回算规则:绝对时间 = 锚点.unix_ts + (记录.ts_ms - 锚点.ts_ms)/1000,锚点取该 boot 段内第一条 time_anchor 事件(unix_ts 即平台下发值,严格一致)。从未同步过的 boot 段只有相对时间。

3 命令详表

cmd 说明 方向 对应串口
dev_serial_set 更改设备序列码 srv→dev 0x09
dev_info_query 查询设备信息 srv→dev 0x10
ssc_net_set 设置 SSC 网络配置 srv→dev 0x11
ssc_net_query 查询 SSC 网络配置 srv→dev 0x12
iot_net_set 设置 IoT 网络配置 srv→dev 0x13
iot_net_query 查询 IoT 网络配置 srv→dev 0x14
iot_topic_set 设置设备 Topic srv→dev 0x15
iot_topic_query 查询设备 Topic srv→dev 0x16
pwd_verify 验证设备密码 srv→dev 0x1C
pwd_set 设置设备密码 srv→dev 0x1D
factory_reset 设备出厂初始化 srv→dev 0x1E
device_reset 设备复位 srv→dev 0x1F
loop_param_set 设置车检器多路参数 srv→dev 0x63
loop_param_query 读取车检器多路参数 srv→dev 0x64
report_config 设置主动上报 srv→dev 0xC5
log_stat 查询脱机日志统计(事件/快照流) srv→dev
log_query 分页拉取脱机日志(事件/快照流) srv→dev
log_clear 清除脱机日志(事件/快照流,审计留痕) srv→dev
ota_begin 开启 OTA 会话 / 断点续传定位 srv→dev
ota_data OTA 分片下发(256B/片,单片 CRC32) srv→dev
ota_end 结束 OTA 下载,全镜像 CRC32 复核 srv→dev
ota_abort 中止 OTA 会话,释放暂存 srv→dev
ota_flash 触发本地 ISP 刷写(仅 ready 态) srv→dev
ota_status 查询 OTA 状态(含进度) srv→dev
loop_version_query 实时查询地感 Loop MCU 版本(V1.10 srv→dev 0x4A
frame_cmd 4G 通道原始帧透传下发(hex 封装,可选兜底) srv→dev
initialize 设备上电初始化登陆 dev→srv
loop_data 线圈传感数据上报 dev→srv 0xC0
event_report 事件上报(平台须应答,见 §5.3 dev→srv
ota_report OTA 进度/结果主动上报 dev→srv
heartbeat 设备心跳 dev→srv
frame_report 4G 通道原始帧透传上报(hex 封装,可选兜底) dev→srv

4G 通道(方案 C,见 §6):上行由 Air780 解析 0x7F 帧并转换为标准 JSON 命令loop_data / event_report / initialize / heartbeat,与有线通道一致),平台无感。frame_cmd / frame_report 保留为可选兜底(Air780 未实现转换的命令 / 未识别帧透传),有线通道不使用。


4 命令详情

4.1 更改设备序列码 dev_serial_set

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 1,
  "cmd": "dev_serial_set",
  "ts": 1719000000,
  "data": {
    "dev_serial": "A1B2C3D4E5F6"
  }
}

响应: Topic: dld960/{sn}/dev

{
  "msg_id": 1,
  "cmd": "dev_serial_set",
  "ts": 1719000001,
  "code": 0,
  "msg": "success"
}

4.2 查询设备信息 dev_info_query

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 2,
  "cmd": "dev_info_query",
  "ts": 1719000000
}

响应:

{
  "msg_id": 2,
  "cmd": "dev_info_query",
  "ts": 1719000001,
  "code": 0,
  "msg": "success",
  "data": {
    "dev_serial": "A1B2C3D4E5F6",
    "hard_ver": "1.1",
    "soft_ver": "1.1",
    "loop_ver": "1.2.3",
    "loop_hw_ver": "1.0.0",
    "model": "DLD960",
    "product_code": "960001",
    "sub_code": {
      "net": true,
      "iot": true
    },
    "bus": {
      "bus1": 0,
      "bus2": 0,
      "bus3": 0,
      "bus4": 0
    }
  }
}
data 字段 类型 说明
dev_serial string 12 位十六进制序列码
hard_ver string 硬件版本(整机),格式 "主.次"
soft_ver string 软件版本(整机 DBN MCU 固件),格式 "主.次"
loop_ver string 地感 Loop MCU 固件版本V1.10),格式 "主.次.次"(如 1.2.3);来自 Loop 0x4A 查询缓存,查询未完成/失败则为空字符串
loop_hw_ver string 地感 Loop MCU 硬件版本V1.10),格式 "主.次.次";同上,可为空
model string 产品型号,1~10 字符
product_code string 产品编码,6 位数字字符串
sub_code.net bool 网络功能是否启用
sub_code.iot bool IoT/MQTT 功能是否启用
bus.bus1~4 uint8 各总线探头数

loop_ver 语义(V1.10loop_ver/loop_hw_ver缓存值(设备上电后自动经 Loop 0x4A 查询一次,OTA 刷写成功后刷新);MQTT 命令处理为同步回包,不等 0x4A 异步响应,故查询未完成/失败时回空。平台需最新版本时用 loop_version_query(§4.25)实时查询。

4.3 设置 SSC 网络配置 ssc_net_set

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 3,
  "cmd": "ssc_net_set",
  "ts": 1719000000,
  "data": {
    "dev_ip": "192.168.1.100",
    "subnet_mask": "255.255.255.0",
    "route_ip": "192.168.1.1",
    "lssc_ip": "192.168.1.200",
    "dns": "8.8.8.8",
    "port": 502
  }
}
data 字段 类型 说明
dev_ip string 设备 IP 地址
subnet_mask string 子网掩码
route_ip string 网关地址
lssc_ip string LSSC 服务器 IP
dns string DNS 服务器 IP
port uint16 端口号

响应:

{
  "msg_id": 3,
  "cmd": "ssc_net_set",
  "ts": 1719000001,
  "code": 0,
  "msg": "success"
}

4.4 查询 SSC 网络配置 ssc_net_query

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 4,
  "cmd": "ssc_net_query",
  "ts": 1719000000
}

响应: 返回字段同 4.3 的 data

4.5 设置 IoT 网络配置 iot_net_set

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 5,
  "cmd": "iot_net_set",
  "ts": 1719000000,
  "data": {
    "host": "mqtt.example.com",
    "port": 1883,
    "client_id": "dld960_A1B2C3D4E5F6",
    "username": "admin",
    "password": "secret"
  }
}
data 字段 类型 说明
host string MQTT Broker 域名或 IP
port uint16 MQTT 端口,默认 1883
client_id string MQTT Client ID,空时用空格
username string MQTT 用户名
password string MQTT 密码

响应: 标准成功/失败。

4.6 查询 IoT 网络配置 iot_net_query

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 6,
  "cmd": "iot_net_query",
  "ts": 1719000000
}

响应: 返回字段同 4.5 的 data

4.7 设置设备 Topic iot_topic_set

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 7,
  "cmd": "iot_topic_set",
  "ts": 1719000000,
  "data": {
    "client_id_enable": true,
    "topic_pub": "dld960/data/A1B2C3D4E5F6",
    "topic_sub": "dld960/cmd/A1B2C3D4E5F6"
  }
}

响应: 标准成功/失败。

4.8 查询设备 Topic iot_topic_query

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 8,
  "cmd": "iot_topic_query",
  "ts": 1719000000
}

响应: 返回字段同 4.7 的 data

4.9 验证设备密码 pwd_verify

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 9,
  "cmd": "pwd_verify",
  "ts": 1719000000,
  "data": {
    "password": "123456"
  }
}
data 字段 类型 说明
password string 6 位数字密码

响应:

{
  "msg_id": 9,
  "cmd": "pwd_verify",
  "ts": 1719000001,
  "code": 0,
  "msg": "success"
}

4.10 设置设备密码 pwd_set

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 10,
  "cmd": "pwd_set",
  "ts": 1719000000,
  "data": {
    "old_password": "123456",
    "new_password": "654321"
  }
}

响应: 标准成功/失败。旧密码错误时 code=2

4.11 设备出厂初始化 factory_reset

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 11,
  "cmd": "factory_reset",
  "ts": 1719000000
}

响应:

{
  "msg_id": 11,
  "cmd": "factory_reset",
  "ts": 1719000001,
  "code": 0,
  "msg": "success"
}

4.12 设备复位 device_reset

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 12,
  "cmd": "device_reset",
  "ts": 1719000000
}

无响应(设备复位后断开连接)。

4.13 设置车检器多路参数 loop_param_set

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 13,
  "cmd": "loop_param_set",
  "ts": 1719000000,
  "data": {
    "auto_mode": false,
    "channels": [
      {
        "ch": 1,
        "sens": 7,
        "level": "high",
        "delay": 0,
        "output": "exist",
        "exist": 0,
        "dir": 0,
        "safe": 0,
        "fun_mode": 0
      },
      {
        "ch": 2,
        "sens": 7,
        "level": "mid_high",
        "delay": 5,
        "output": "enter_pulse",
        "exist": 10,
        "dir": 0,
        "safe": 0,
        "fun_mode": 0
      },
      {
        "ch": 3,
        "sens": 5,
        "level": "mid_low",
        "delay": 0,
        "output": "exist",
        "exist": 0,
        "dir": 1,
        "safe": 5,
        "fun_mode": 0
      },
      {
        "ch": 4,
        "sens": 8,
        "level": "low",
        "delay": 10,
        "output": "leave_pulse",
        "exist": 15,
        "dir": 0,
        "safe": 0,
        "fun_mode": 0
      }
    ]
  }
}
data 字段 类型 说明
auto_mode bool 自动调频模式,默认 false
channels array 各路通道参数,1~4 路
channels[].ch uint8 通道号 1~4
channels[].sens uint8 sensitivity, 灵敏度 0~9,默认 7
channels[].level string freq_level, "high"(33nF) / "mid_high"(43nF) / "mid_low"(66nF) / "low"(76nF)
channels[].delay uint8 loop_delay, 延时时间,0~200(×0.1s),最大 20s
channels[].output string exist_mode, "exist" / "enter_pulse" / "leave_pulse" / "direction"
channels[].exist uint8 output_mode, 存在方式,0=永久,非0=分钟数
channels[].dir uint8 direction_mode, 方向判别模式,0=触发,1~6=方向输出
channels[].safe uint8 safe_mode, 安全模式,0=关闭,非0=分钟数
channels[].fun_mode uint8 function_mode, 功能模式

响应:

{
  "msg_id": 13,
  "cmd": "loop_param_set",
  "ts": 1719000001,
  "code": 0,
  "msg": "success"
}

4.14 读取车检器多路参数 loop_param_query

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 14,
  "cmd": "loop_param_query",
  "ts": 1719000000
}

响应:

{
  "msg_id": 14,
  "cmd": "loop_param_query",
  "ts": 1719000001,
  "code": 0,
  "msg": "success",
  "data": {
    "auto_mode": false,
    "channels": [
      {
        "ch": 1,
        "sens": 7,
        "level": "high",
        "delay": 0,
        "output": "exist",
        "exist": 0,
        "dir": 0,
        "safe": 0,
        "fun_mode": 0,
        "f_initial": 105300,
        "f_current": 105280,
        "diff": 20
      }
    ]
  }
}
额外字段 类型 单位 说明
f_initial uint32 Hz freq_initial, 初始频率
f_current uint32 Hz freq_current, 当前实时频率
diff uint32 Hz 变化量(绝对值)

4.15 设置主动上报 report_config

Topic: dld960/{sn}/srv ⚠️ 兼任设备时钟同步点:本命令信封 ts 须填当前 Unix 时间,设备据此校准本地时钟(见 §2.3)。平台应在每次收到设备 initialize 后下发一次。

请求:

{
  "msg_id": 15,
  "cmd": "report_config",
  "ts": 1719000000,
  "data": {
    "sensor_type": 12,
    "enable": true,
    "once": false,
    "env_eval": false,
    "interval": 5,
    "ack_required": false,
    "timeout": 0
  }
}
data 字段 类型 说明
sensor_type uint8 传感器类型,线圈=0x0C(12)
enable bool 是否使能主动上报
once bool 仅上报一次(查询模式)
env_eval bool 环境评估使能
interval uint8 上报间隔(秒),0=实时
ack_required bool 上报是否需要确认
timeout uint8 超时时间(分钟),0=无限制

响应: 标准成功/失败。


4.16 查询脱机日志统计 log_stat

Topic: dld960/{sn}/srv 设备本地 W25Qxx 环形日志(事件区/快照区容量随存储芯片动态,掉电不丢)。用于日志拉取前的分页定位。 通过 data.stream 区分日志流:event(事件日志,缺省)/ snapshot(传感快照)。

请求:

{
  "msg_id": 16,
  "cmd": "log_stat",
  "ts": 1719000000,
  "data": {
    "stream": "event"
  }
}
data 字段 类型 说明
stream string 日志流:event(缺省,可省略)或 snapshot

响应 datastream=event):

{
  "stream": "event",
  "enabled": true,
  "boot_seq": 2,
  "count": 1234,
  "capacity": 16256,
  "seq_first": 100,
  "seq_last": 1333
}

响应 datastream=snapshot):

{
  "stream": "snapshot",
  "enabled": true,
  "boot_seq": 2,
  "count": 1234,
  "capacity": 48064,
  "seq_first": 100,
  "seq_last": 1333
}
data 字段 类型 说明
stream string 日志流:event / snapshot
enabled bool 日志功能是否启用(Flash 初始化成功)
boot_seq uint16 当前启动序号(每次上电 +1,区分复位段)
count uint32 有效记录条数(0~capacity,环形覆盖后 < capacity
capacity uint32 容量上限(随存储芯片与流动态):事件流 W25Q32=16256 / Q64=32640 / Q128=65408 / Q256=130944;快照流 W25Q32=48064 / Q64=105408 / Q128=220096 / Q256=449472
seq_first uint32 逻辑首条记录全局序号(seq_last - count + 1count=0 时为 0
seq_last uint32 最新一条记录全局序号(跨 boot 单调递增)

4.17 分页拉取脱机日志 log_query

Topic: dld960/{sn}/srv 分页按全局序号,不按时间(未同步段时间不可靠)。count 上限按流区分:事件流 4 / 快照流 2(hex 原始字节上报,体积可控)。 通过 data.stream 区分日志流:event(缺省)/ snapshot记录格式为存储原始字节的小写 hex 字符串(与 BLE 通道直传的二进制同源同语义),平台按《DLD960 BLE 协议》字段表解析。

请求:

{
  "msg_id": 17,
  "cmd": "log_query",
  "ts": 1719000000,
  "data": {
    "stream": "event",
    "start_seq": 1330,
    "count": 4
  }
}
data 字段 类型 说明
stream string 日志流:event(缺省,可省略)或 snapshot
start_seq uint32 起始全局序号(含);越界(< seq_first 或 > seq_last)返回空 records
count uint8 拉取条数;事件流上限 4、快照流上限 2,超限按各自上限处理;0 按上限处理

响应 data

{
  "start_seq": 1330,
  "records": [
    {
      "seq": 1330,
      "hex": "a53200000200000001000000..."
    }
  ]
}
字段 类型 说明
seq uint32 全局序号(与 hex 内 offset 4 字段一致,便于快速定位/排序)
hex string 记录原始字节的小写 hex:事件流 OfflogEvt 32B → 64 字符;快照流 SnapRec 64B → 128 字符flash 存储字节原样,小端)

解析字段表(与 BLE 通道完全一致):

结构 字段表
event OfflogEvt 32B 《DLD960 BLE 协议》§7magic(0xA5)/type/len/flags/seq/ts_ms/unix_ts/boot_seq/payload(12B),事件类型与 payload 定义同表
snapshot SnapRec 64B 《DLD960 BLE 协议》§6.4magic(0xA6)/len/flags/seq/ts_ms/boot_seq/coils(4×12B,与 0xC0 线上格式一致)

时间戳语义同事件流:unix_ts 为已同步 Unix 秒(0=未同步),绝对时间用事件流 time_anchor 锚点回算(见 §2.3)。


4.18 清除脱机日志 log_clear

Topic: dld960/{sn}/srv高风险操作:清除动作本身写入事件流(log_clear 审计——谁在何时清了日志,留痕不可清除)。平台侧应做权限控制。

请求:

{
  "msg_id": 18,
  "cmd": "log_clear",
  "ts": 1719000000,
  "data": {
    "stream": "event"
  }
}
data 字段 类型 说明
stream string 日志流:event(缺省,可省略)或 snapshot

响应: 标准成功/失败。

  • stream=event:成功后 log_statcount 归 1(仅剩审计记录),seq_last 继续递增(序号不复位)。阻塞 ~2.8s(63 个数据扇区 SPI 擦除),请勿高频调用。
  • stream=snapshot:成功后 log_statcount 归 0seq_last 继续递增;清除动作写入事件流审计(log_clearpayload 标记快照流)。阻塞 ~45ms(逻辑清除 + 当前写扇区擦除,其余扇区由环形写覆盖时自动擦)。

4.19 开启 OTA 会话 ota_begin

Topic: dld960/{sn}/srv 依据《DLD960_MQTT_OTA协议.md》设计稿 V1.01ROADMAP P1.4 ①:Loop MCU 远程 OTA,先存后刷)。 能力探测:老固件(V1.07 及以下)无 ota_* 命令,收到回 code=4;平台下发前用 ota_statusdev_info_query.soft_ver 判断。

请求:

{
  "msg_id": 401,
  "cmd": "ota_begin",
  "ts": 1719000000,
  "data": {
    "target": "loop",
    "size": 46864,
    "crc32": 305419896,
    "version": "1.1.0",
    "force": false
  }
}
data 字段 类型 说明
target string loop(当前支持);dbn 预留
size uint32 镜像 bin 字节数(≤ 98304 = 96KBSlot 数据区 100KB 预留 4KB 边界余量)
crc32 uint32 全镜像 CRC32(十进制,算法见 §4.19.1)
version string 目标固件版本(写入元数据,审计用;bootloader 不校验版本)
force bool true = 覆盖现有暂存镜像 / 忽略版本冲突(默认 false

设备行为:

  1. 读暂存元数据:若已有镜像且 size+crc32 与本次一致 →
    • state=ready → 返回 offset=size(平台可直接 ota_flash
    • state=downloading → 返回 offset=received(断点续传)
  2. 不一致 → 分配 Slot(A 当前 / B 回滚),写元数据 state=downloading, received=0,返回 offset=0
  3. force=false 且目标版本 == 当前运行版本 → 回 code=1, err_code=4(防重复刷写,可 force 绕过)

响应 data

{
  "msg_id": 401,
  "cmd": "ota_begin",
  "ts": 1719000001,
  "code": 0,
  "msg": "success",
  "data": {
    "target": "loop",
    "slot": "a",
    "offset": 0,
    "received": 0,
    "size": 46864,
    "crc32": 305419896,
    "state": "downloading"
  }
}

4.19.1 CRC32 算法(必须双方一致)

标准 CRC-32/ISO-HDLCpoly 0x04C11DB7reflected 0xEDB88320),init 0xFFFFFFFFrefin/refout truexorout 0xFFFFFFFF。平台侧 Python zlib.crc32() / binascii.crc32() 即此算法;设备侧查表法。全镜像 CRC = offset 0 ~ size-1 连续;单片 CRC = 仅该 256B。


4.20 OTA 分片下发 ota_data

Topic: dld960/{sn}/srv 单片大小 256B 是协议常量,不接受协商;hex 512 字符 + JSON 外壳 ≈ 640B < 设备接收缓冲 1024B。

请求:

{
  "msg_id": 402,
  "cmd": "ota_data",
  "ts": 1719000002,
  "data": {
    "target": "loop",
    "offset": 0,
    "crc32": 2524764894,
    "data": "6a6173646f6e...512 hex 字符 = 256B"
  }
}
data 字段 类型 说明
target string ota_begin
offset uint32 本片在镜像中的绝对偏移(256 对齐,首片 0
crc32 uint32 本片 256B 的 CRC32(十进制)
data string 256B 原始字节小写 hex512 字符

设备行为:

  1. offset == received(顺序片)→ 单片 CRC32 校验 → 写 W25Qxx 暂存(256B 页对齐)→ received += 256(≥size 截断为 size)→ code=0
  2. offset < received(重复片,平台重发)→ 幂等回 code=0,不重写
  3. offset > received(缺片/乱序)→ code=1, err_code=1 + data.offset=received(指示平台从该处续传;协议不要求乱序重组)
  4. 单片 CRC 失败 → code=1, err_code=1(平台重发本片;连续失败平台可 ota_abort
  5. 会话未开始 / 状态非 downloading → code=3, err_code=2
  6. data 非 512 hex / offset 非 256 对齐 / 超 size → code=1 参数错误

响应: 标准成功/失败 + data 回显 {offset, received}


4.21 结束下载 ota_end

Topic: dld960/{sn}/srv

请求:

{
  "msg_id": 403,
  "cmd": "ota_end",
  "ts": 1719000003,
  "data": {
    "target": "loop",
    "crc32": 305419896
  }
}

设备行为:

  1. received != sizecode=1 + data.offset=received(不完整,续传)
  2. received == size → 读回暂存区全镜像计算 CRC32,与 ota_begin 声明值比对
    • 一致 → 元数据 state=ready, last_result=0code=0, data={crc_ok:true}
    • 不一致 → 元数据 state=downloading(保留已下载数据,可重发错片)→ code=5, err_code=5, data={crc_ok:false}

4.22 中止会话 ota_abort

Topic: dld960/{sn}/srv

{
  "msg_id": 404,
  "cmd": "ota_abort",
  "ts": 1719000004,
  "data": { "target": "loop" }
}

设备行为:元数据 state=aborted,Slot 标记可覆盖;正在刷写时 abort → 停止发送后续 A7 块(Loop 端由 bootloader 超时复位回 APP 兜底)。响应标准成功。


4.23 触发刷写 ota_flash

Topic: dld960/{sn}/srv会车安全关键命令:平台应确认现场允许(无车压线圈、非高峰)再下发。

请求:

{
  "msg_id": 405,
  "cmd": "ota_flash",
  "ts": 1719000005,
  "data": {
    "target": "loop",
    "slot": "a",
    "force": false
  }
}
data 字段 类型 说明
slot string a / b(缺省 = 当前 ready 的槽)
force bool true = 跳过安全窗口检查(高风险,平台授权)

设备行为(同步检查 → 异步刷写):

  1. 安全窗口检查force=false 时):4 通道 Loop 有车(VD_FLAG 任一置位)→ code=3, err_code=3(有车,拒绝;平台提示"车辆离开后重试")。刷写期间会阻断检测与继电器控制 → 平台建议低峰执行
  2. 元数据非 ready → code=3(先 ota_end 完成校验)
  3. 通过 → 立即回 code=0(异步),进入刷写:
    • 写 offlog 事件日志:固件升级开始target/slot/version/size
    • 暂停事件上报与脱机日志(会话期间)MQTT event_report 暂停发送(入队积压,16 深溢出丢最旧,会话结束恢复后补发);offlog/快照落盘暂停("升级开始"日志在暂停前写入、"升级结果"在恢复后补记)
    • 维持 Loop 现状:复位进 bootloader 后的 GPIO/继电器状态与现网 BLE OTA 升级一致,不额外干预
    • 9F 01 00 01 A5 A7 启动帧 → Loop APP 写 flag 复位 → bootloader 回 pre_ok
    • 发 A6 地址帧(0x08003400 4 字节大端)→ addr_ok
    • 从 W25Qxx 暂存读镜像,按 ≤254B/块发 A7(非阻塞 tick 驱动:每轮主循环发送 1~2 块并检查 ACK,绝不阻塞主循环,保证刷写窗口内 MQTT PINGREQ/心跳/IWDG 喂狗正常);停等 ACK,1s 超时重发 ×3
    • 末块(sub_amount=1)→ bootloader 写剩余 → 清 flag → 复位跑新 APP
  4. 进度经 ota_report 上行(§5.5);失败重试 ×3 仍失败 → 元数据 state=flash_failed + event_report{type:ota_error} 告警(平台必答)
  5. 刷写结果(设备侧,V1.09 明确)
    • 成功(末块 ACK)→ 元数据 state=idle镜像保留size/crc32/version/slot 不变,last_result=0flash_cnt+1)→ 立即上报 ota_report{stage:done}(重发 3 次×5s,同 msg_id/ts)→ 恢复 event_report 发送与 offlog/快照落盘
    • 失败 → 元数据 state=flash_failed + ota_report{stage:failed} + event_report{type:ota_error}done/failed 同重发策略)
    • 状态语义:刷写完成后 DBN 侧状态回 idle("本轮刷写已结束"),与"下载完成待刷(ready"严格区分——平台不得把 state=ready 判为"刷写未启动":本地刷写 70 块(17224B)实测 <1s,平台轮询间隔可能错过 flashing 中间态

响应: code=0 仅表示已启动,不代表刷写成功——结果以 ota_report / ota_status 为准。


4.24 查询状态 ota_status

Topic: dld960/{sn}/srv

请求:

{ "msg_id": 406, "cmd": "ota_status", "ts": 1719000006 }

响应 data

{
  "target": "loop",
  "state": "flashing",
  "slot": "a",
  "size": 46864,
  "received": 46864,
  "crc32": 305419896,
  "version": "1.1.0",
  "progress": { "sent": 42112, "total": 46864 },
  "last_result": 0,
  "last_error": 0
}
data 字段 类型 说明
state string idle / downloading / ready / flashing / flash_failed / abortedidlesize>0 = 已刷写完成(镜像保留可重刷)
progress.sent uint32 刷写阶段已送 Loop 的字节数
last_result uint32 上次刷写结果:0=无/成功,非 0=错误码
last_error uint32 上次失败细分错误码

设备状态机:

        ota_begin(新会话)          ota_data×N          ota_end(CRC✓)
 IDLE ─────────────────▶ DOWNLOADING ────────────▶ READY
  ▲                        │  ▲                        │
  │  ota_abort             │  │ ota_end(CRC✗)          │ ota_flash(安全检查✓)
  │  / flash_failed        │  └──────────────┐         ▼
  └────────────────────────┴─────────────────┴───── FLASHING ──成功──▶ IDLE(镜像保留, last_result=0)
                                                      │
                                                      └──失败×3──▶ FLASH_FAILED ──ota_abort/ota_begin──▶ IDLE

平台判定指引(V1.09):

  • 主依据ota_report 主动上报(stage=done 成功 / stage=failed 失败,§5.5)——ota_flash 后平台应优先等该信号
  • 兜底ota_status 查询——state=idle 且 size>0 且 last_result=0 = 刷写成功(镜像保留,可重刷);state=flash_failed = 失败;state=ready = 下载完成待刷(不是"刷写未启动"
  • 刷写窗口极短(<1s),轮询可能捕捉不到 flashing 中间态,不得以此判失败

4.25 查询地感版本 loop_version_query

Topic: dld960/{sn}/srv 用途:实时查询地感 Loop MCU 固件/硬件版本(V1.10,配合远程 OTA 做升级前后版本核对)。

请求:

{ "msg_id": 407, "cmd": "loop_version_query", "ts": 1719000007 }

设备行为(异步):

  1. 经 UART2 向 Loop MCU 发 0x4A 查询帧(7F 00 01 4A ...,见 DLD960Loop_串口通信协议.md §3.01
  2. 收到 Loop 响应后解析(Soft/Hard 各三段)→ 更新本地缓存 → 立即回包
  3. Loop 无响应/超时 → 回 code=5loop_ver 保持缓存值(可为空)

响应:

{
  "msg_id": 407,
  "cmd": "loop_version_query",
  "ts": 1719000008,
  "code": 0,
  "msg": "success",
  "data": {
    "loop_ver": "1.2.3",
    "loop_hw_ver": "1.0.0",
    "version_str": "V1.2.3 (HW:1.0.0)"
  }
}
data 字段 类型 说明
loop_ver string 地感固件版本,格式 "主.次.次"Soft_Main.Sub.SSub
loop_hw_ver string 地感硬件版本,格式 "主.次.次"Hard_Main.Sub.SSub
version_str string 人类可读串,"V{soft} (HW:{hard})"Loop 无响应时可能为空

时序与缓存:设备上电后自动查询一次并缓存;ota_report stage=done(Loop 复位跑新固件)后自动刷新缓存。平台升级流程建议:ota_flashloop_version_query 记录旧版本 → 刷写完成后再次查询核对新版本是否生效。


5 设备主动上报

5.1 设备上电登陆信息 initialize

Topic: dld960/{sn}/dev QoS: 0/1

{
  "msg_id": 1,
  "cmd": "initialize",
  "ts": 1719000000,
  "data": {
    "dev_serial":"A1B2C3D4E5F6",
    "model": "DLD960",
    "hard_ver": "1.0",
    "soft_ver": "1.0",
    "loop_ver": "1.2.3",
    "loop_hw_ver": "1.0.0",
    "extra_info": {
      "code": "869756049404948",
      "imei": "860012345678901",
      "iccid": "89860012345678901234",
      "csq": "21",
      "location": "113.9237976,022.6400375",
    }
  }
}
字段 类型 说明
data.dev_serial string 设备序列码
data.model string 产品型号
data.hard_ver string 硬件版本(整机)
data.soft_ver string 固件版本(整机 DBN MCU
data.loop_ver string 地感 Loop MCU 固件版本V1.10),格式 "主.次.次";来自 0x4A 缓存,查询未完成/失败则为空字符串
data.loop_hw_ver string 地感 Loop MCU 硬件版本V1.10),格式 "主.次.次";同上可为空
data.extra_info.code string 可选,设备代码如IMSI/ICCID
data.extra_info.imei string 可选,4G 模块 IMEI(V1.13;无 4G 模块时省略或空串;4G 通道由 Air780 填真实值,见 §6)
data.extra_info.iccid string 可选,流量卡 ICCID(V1.13;无 4G 模块时省略或空串;4G 通道由 Air780 填真实值,见 §6)
data.extra_info.csq string 可选,当前信号强度
data.extra_info.location string 可选,经纬度(经度,纬度)

V1.10 说明loop_ver/loop_hw_ver尽力携带(上电后异步 0x4A 查询,initialize 发出时可能未就绪 → 回空);平台核对版本请用 loop_version_query(§4.25)。

5.2 线圈传感数据 loop_data

Topic: dld960/{sn}/dev QoS: 0/1 上报节奏(三档):① 任一通道 iscar 翻转(进/出车)→ 立即上报,不受间隔限制;② 任一通道 |diff| 超阈值 → 300ms 快速档;③ 平稳 → 按 report_config.interval(默认 60s)。

{
  "msg_id": 100,
  "cmd": "loop_data",
  "ts": 1719000100,
  "data": {
    "channels": [
      {
        "ch": 1,
        "level": "high",
        "iscar": false,
        "loop_ok": true,
        "freq": 105280,
        "diff": 20,
        "sens": 7,
        "cndtn": 0,
        "misc": {
          "type": "time",
          "value": 0
        }
      },
      {
        "ch": 2,
        "level": "mid_high",
        "iscar": true,
        "loop_ok": true,
        "freq": 98700,
        "diff": 1500,
        "sens": 7,
        "cndtn": 2,
        "misc": {
          "type": "time",
          "value": 350
        }
      },
      {
        "ch": 3,
        "level": "mid_low",
        "iscar": false,
        "loop_ok": false,
        "freq": 0,
        "diff": 0,
        "sens": 5,
        "cndtn": 0,
        "misc": {
          "type": "cut_count",
          "value": 3
        }
      },
      {
        "ch": 4,
        "level": "low",
        "iscar": false,
        "loop_ok": true,
        "freq": 62100,
        "diff": 5,
        "sens": 8,
        "cndtn": 0,
        "misc": {
          "type": "flow_count",
          "value": 128
        }
      }
    ]
  }
}
通道字段 类型 说明
ch uint8 通道号 1~4
level string freq_level 线圈高低频档位
iscar bool 是否有车
loop_ok bool 线圈是否正常(false=断开)
freq uint32 当前频率 (Hz)
diff uint32 变化量
sens uint8 sensitivity, 当前灵敏度等级
cndtn uint8 condition, 环境状态评估值(越大干扰越大)
misc.type string "time"(时间量) / "cut_count"(断开次数) / "flow_count"(车流量)
misc.value uint32 杂项数值(时间量单位 50ms

5.3 事件上报 event_report

上报 Topic: dld960/{sn}/dev 应答 Topic: dld960/{sn}/srv QoS: 1(建议) ⚠️ 本指令要求平台必须应答(区别于 loop_data / heartbeat 的单向上报)

设备检测到事件时主动上报,非周期性。事件(进车/出车/线圈断开等)是不可再生的关键数据MQTT QoS 仅保证 broker 收到,不代表平台业务层已处理入库,因此引入应用层应答 + 设备重发机制闭环确认。

event_report 不受 report_config.enable 门控——设备上电即检测并上报事件。report_configenable/interval 仅控制 loop_data 周期上报,与事件上报解耦。

设备上报(dev topic):

{
  "msg_id": 101,
  "cmd": "event_report",
  "ts": 1719000200,
  "data": {
    "events": [
      {
        "type": "car_enter",
        "ch": 2,
        "value": 0
      },
      {
        "type": "car_leave",
        "ch": 2,
        "value": 350
      },
      {
        "type": "loop_cut",
        "ch": 3,
        "value": 0
      }
    ]
  }
}
事件类型 type 说明 value 含义
car_enter 车辆进入 0
car_leave 车辆离开 通过时间 (×50ms)
loop_cut 线圈断开 0
loop_restore 线圈恢复 断开持续时长 (×50ms)
ota_error OTA 刷写失败告警(重试×3 仍失败,V1.08) 0x1001=启动帧无响应 / 0x1002=地址帧错误 / 0x1003=数据块 ACK 超限 / 0x1004=全镜像校验失败 / 0x1005=安全窗口拒绝后强制失败

平台应答(srv topic,收到后必须立即回复):

{
  "msg_id": 101,
  "cmd": "event_report",
  "ts": 1719000201,
  "code": 0,
  "msg": "success"
}
字段 说明
msg_id 必须回显设备上报的 msg_id,设备以此匹配确认
cmd 固定 "event_report"
code 0 = 已接收并处理;非 0 = 处理失败(设备视同未确认,进入重发)

设备端确认与重发机制:

  1. 发出 event_report 后启动确认定时器,5s 内未收到 msg_id 匹配且 code=0 的应答 → 重发;
  2. 重发使用相同的 msg_id 与原始 ts(首次事件发生时间,不随重发刷新),平台以此去重;
  3. 最多重发 3 次(含首发共 4 次)。仍未确认 → 事件保留在待发队列,待 MQTT 重连成功或下一次事件触发时合并上报;
  4. 等待确认期间新产生的事件,合并进下一包的 events 数组(使用新 msg_id)。单包长度受发布缓冲限制(≤500B),超出时拆包分批;
  5. 待发队列建议深度 ≥16 条事件,溢出时丢弃最旧事件(先保新鲜数据)。

平台端要求:

  1. 收到 event_report 先落库、后应答,应答即承诺数据已持久化;
  2. (dev_serial, msg_id) 在近期窗口(建议 10 分钟)内去重——重复包为设备未收到应答所致,直接应答 code=0,不重复入库(设备重启后 msg_id 从头递增,去重必须限定时间窗口);
  3. 应答报文应控制在最小集(不带 data),减轻设备下行解析负担。

时序(正常 / 应答丢失重发):

设备                        平台
 │── event_report(id=101) ──▶│  落库
 │◀─ ack(id=101, code=0) ────│  确认, 事件出队
 │
 │── event_report(id=102) ──▶│  落库
 │      ✗ 应答丢失            │
 │  [5s 超时]                 │
 │── event_report(id=102) ──▶│  (dev_serial,102) 命中去重 → 不入库
 │◀─ ack(id=102, code=0) ────│  确认, 事件出队

5.4 设备心跳 heartbeat

Topic: dld960/{sn}/dev 周期:默认 60 秒

{
  "msg_id": 200,
  "cmd": "heartbeat",
  "ts": 1719000060,
  "data": {
    "uptime": 3600,
    "loop_status": [true, true, false, true],
    "net_status": true,
    "iot_status": true
  }
}
data 字段 类型 说明
uptime uint32 设备运行时长(秒)
loop_status bool[4] 各路线圈是否正常
net_status bool 以太网连接状态
iot_status bool MQTT 连接状态

5.5 OTA 进度/结果上报 ota_report

Topic: dld960/{sn}/dev · QoS 1 用途:OTA 会话与刷写进度/结果主动推送(V1.08)。进度可丢,结果可经 ota_status 兜底查询(设备侧元数据持久化 last_result)。

上报:

{
  "msg_id": 501,
  "cmd": "ota_report",
  "ts": 1719000007,
  "data": {
    "target": "loop",
    "stage": "flashing",
    "progress": { "sent": 42112, "total": 46864 },
    "code": 0,
    "msg": ""
  }
}
data 字段 说明
target 目标:loop(当前支持)
stage begin(会话开启)/ downloading(片落盘)/ ready(校验通过)/ flashing(刷写中)/ done(刷写成功,Loop 已重启)/ failed(刷写失败)
progress sent/total 字节(刷写阶段)
code stage 相关结果码

上报节奏begin/ready/done/failed 各 1 次;flashing 阶段按进度节流(建议每 64 块或每 8KB 一次,避免刷写期间消息风暴)。done/failed 设备侧重发 3 次(间隔 5s,同 msg_id/ts),平台去重窗口建议 10 分钟(与 event_report 同策略)。

平台判定(V1.09 明确)ota_report 是刷写结果的主依据——收到 stage=done 判成功、stage=failed 判失败,无需再轮询 ota_status。设备 ota_flash 响应 code=0 仅表示已启动(异步),不得以"轮询 ota_status 未见 flashing"或"状态持续为 ready"判"刷写未启动"——本地刷写 <1s 完成,轮询大概率错过中间态;兜底判定见 §4.24。

会话期间静默V1.08):OTA 会话期间(ota_begin ~ 结束)设备暂停 event_report 发送(入队积压,结束后补发)与 offlog/快照落盘("升级开始"日志暂停前写入、"升级结果"恢复后补记),保证刷写窗口内 MQTT 保活与 IWDG 喂狗不受影响。


6 4G 通道适配(方案 CAir780 协议转换,V1.12 修订)

适用场景:vd960DBN 有线网络失效时,经 Air8781P 整板(Air780EPM 4G 模组,LuatOS vd960Air 工程) 兜底上报。 方案 CAir780 解析 0x7F 帧并转换为标准 JSON 命令(与有线通道一致),平台无感。 修订说明:V1.11 曾定方案 B(原始帧 hex 透传 frame_report/frame_cmd),V1.12 改为方案 Cframe_* 降级为可选兜底。

6.1 通道架构

上行: vd960Loop --0x7F帧(UART2)--> vd960DBN --原样转发(UART1)--> Air780 --解析转JSON--> MQTT(loop_data/event_report/响应)
下行: 平台 --标准JSON命令--> Air780 --转换0x7F帧--> vd960DBN --0x7F帧(UART2)--> vd960Loop
  • vd960DBNUART2↔UART1 双向转发(魔数分流:0x7F 帧 → 转发 UART2vd960Loop);0x8F 帧 → DBN 本地处理)。列入 vd960DBN 开发计划,固件未实现。⚠ 转发必须不丢帧(沿检测依赖完整 0xC0 帧流)。
  • Air780:0x7F 帧解析(Lua 状态机)+ 协议转换0x7F 帧 ↔ 标准 JSON,§6.3+ MQTT(标准 JSON 命令面)
  • 平台:标准 JSON 解析,与有线通道一致(零新增依赖

6.2 命令面(4G 通道 = 标准 JSON)

通道 上行(dev→srv 下行(srv→dev
有线(ETH MQTT initialize / loop_data / event_report / heartbeat / ota_report(标准 JSON 标准 JSON 命令全表(dev_serial_set / ssc_net_* / iot_net_* / loop_param_* / report_config / log_* / ota_* / loop_version_query
4GAir780 转换) 标准 JSONinitialize / loop_data / event_report / heartbeatAir780 从 0x7F 帧生成)+ 可选 frame_report 标准 JSONAir780 转换为 0x7F 帧下发)+ 可选 frame_cmd
  • 4G 通道上行 JSON 结构与有线通道完全一致(平台按同一解析逻辑处理)
  • 4G 通道 initializeextra_info 填真实值:imei / iccid / csq(V1.13;有线通道无 4G 模块时省略或空串)
  • frame_report / frame_cmd 保留为可选兜底(§6.8):Air780 未实现转换的命令 / 未识别帧,平台可直接发/收原始帧

6.3 Air780 协议转换职责(0x7F 帧 ↔ 标准 JSON)

上行(0x7F → JSON):

0x7F 帧 转换目标 说明
0xC0 传感上报 loop_data 4 路通道字段映射(§5.2),携带 link(§6.7)
0xC0 car_state 沿 event_report 进出车事件(§6.4
0xC0 loop_state 沿 event_report 线圈断开/恢复(loop_cut / loop_restore,§6.4
0x09~0x1F 配置响应 对应命令响应 code / msg / data(按 §4 各命令响应结构)
0x63 / 0x64 车检器参数响应 loop_param_set / loop_param_query 响应 多路参数结构(§4.13 / §4.14)
0x4A 版本响应 loop_version_query 响应 / initialize.loop_ver 地感版本(§4.25

下行(JSON → 0x7F):

平台 JSON 命令 转换 0x7F 帧 说明
loop_param_set / loop_param_query 0x63 / 0x64 车检器多路参数
loop_version_query 0x4A 地感版本查询
(其余需转发的命令) 对应 0x7F 命令 按《DLD960Loop_串口通信协议》

6.4 事件上报(仅线圈事件,V1.12 明确)

  • 4G 通道事件面 = 仅线圈事件car_enter / car_leave / loop_cut / loop_restore,源自 0xC0 帧 car_state / loop_state 沿)
  • 不含 DBN 内部网络事件iot_connect / iot_ready / iot_reconn 等——Air780 无法感知 DBN 内部状态,平台勿依赖 4G 通道获取网络事件
  • event_report 语义与有线通道完全一致V1.04 机制):
    • 平台必答(回显 msg_id + code=0)→ 出队
    • 5s 超时重发,同 msg_id / 原始 ts,最多 3 次;耗尽挂起
    • 16 深环形队列,溢出丢最旧;多事件合并一条 publish
    • 跨重连保持同 msg_id(平台按 (sn, msg_id) 去重)
  • Air780 以 Lua 复刻 DBN iot_evt_* 逻辑(沿检测 + ACK 状态机 + 重发定时器),可行性已评估(2026-08-31):逻辑块全部可映射 Luatable 队列 / sys.timer / mqtt 回调 / json),无硬障碍

6.5 命令响应链路

  • 平台 JSON 命令 → Air780 转 0x7F → vd960DBN → vd960Loop → 响应帧 → vd960DBN → Air780 → JSON 回包
  • Air780 维护单命令状态机(暂存 msg_id + 超时回 code=5,与 DBN g_lup_cmd 同模式)
  • 链路 4 跳:响应超时建议与 DBN 命令超时一致(当前 300ms~1s 量级,待板级确认)

6.6 4G 通道不支持的命令(网络配置类)

cmd 说明
ssc_net_set / ssc_net_query SSC 有线网络配置(4G 不适用)
iot_net_set / iot_net_query IoT 有线网络配置(4G 不适用)
iot_topic_set / iot_topic_query Topic 配置(4G 主题由 Air780 配置,同步链路见 §6.9)

经 4G 通道下发以上命令:设备回 code=4 unsupported

补充(§6.9):这些配置仅能经 BLE 写入 DBN。DBN 作为权威源,经 UART1 把配置同步给 4G 通道; Air780 不接受平台经 4G 下发配置(否则会改掉自己正在使用的连接参数,形成第二权威源)。

字段 来源 说明
imei mobile.imei() 4G 模块 IMEI,设备唯一标识
iccid mobile.iccid() 流量卡卡号,物联网卡管理识别用(卡商未写入 → 空串)
imsi mobile.imsi() IMSI(部分卡返回空)
msisdn mobile.msisdn() 手机号(物联网卡通常拿不到 → 空串)
csq mobile.csq() 信号强度 0-31(31 最强,99/255 无信号)
net 固定 "4G" 网络制式(预留扩展)

6.8 平台侧要求

  1. 标准 JSON 解析:与有线通道一致(零新增依赖);依 link.net 或报文形态识别 4G 通道
  2. 设备唯一标识dev_serial 与有线通道同一序列号(Topic 族一致,平台认同一台设备);link.imei / link.iccid 辅助 4G 设备/流量卡管理
  3. 时钟校准Air780 上线发 initialize(JSON + link)后,平台照常下发 report_config 校准 ts(§2.3
  4. 事件面约束:4G 通道仅报线圈事件,不报 DBN 内部网络事件(§6.4)
  5. 可选兜底:若启用 frame_report / frame_cmd(Air780 未识别帧 / 未实现转换命令),平台需按《DLD960Loop_串口通信协议》解析/组帧

6.9 4G 配置同步(BLE → DBN → Air780V1.14 新增)

6.9.1 背景与权威源

4G 通道的 MQTT 连接参数(服务器地址、端口、ClientID、账号、密码、发布/订阅主题)由蓝牙小程序经 BLE 写入 DBN。§6.6 已规定这些配置不允许经 4G 通道下发(回 code=4),因此 BLE 是网络配置的唯一入口。若 DBN 不同步给 Air780,Air780 只能使用自身固件内的默认值,平台侧改配置无效

配置权威源 = DBN 侧 flashIOT_NET_INFO / IOT_Topic 结构,cfig_flash.c 持久化)。

角色 职责
蓝牙小程序 唯一配置入口,经 BLE 写 DBN
DBN 唯一权威源:持久化 + 应答拉取 + 主动推送
Air780 配置消费者fskv 缓存仅用于加速启动;唯一合法写入者 = 收到的 DBN 配置帧(平台下发、产线预置均不得写入,否则产生第二权威源)

6.9.2 帧格式

配置同步复用 0x8F 本机侧私有帧(与 BLE 侧 MAGIC_BYTE_DBN_DEFAULT 同值),沿用《DLD960Loop 串口通信协议》帧布局:

偏移 字段 说明
0 0x8F 本机侧私有帧魔数
1 Addr 设备地址
2 LEN 1 + DATA 长度(最小 1
3 CMD 见 §6.9.3
4.. DATA 载荷,见 §6.9.4
-2 XOR Addr 起算(不含魔数)
-1 SUM Addr 起算(不含魔数)

帧总长 = LEN + 5XOR / SUM 覆盖 = Addr2 + LEN 字节。

UART1 魔数语义(链路两端一致)

魔数 处理
0x7F 业务帧:转发 UART2Loop
0x8F 本机侧私有帧:链路两端各自本地消费,均不转发
其他 丢弃,并回找最近魔数重新同步(resync)

⚠ 帧长上限(两侧必须同步扩容)

帧类型 帧长上限 说明
0x7F Loop 业务帧 70 BLUP_MAX_PKG_LEN,不变) 与 Loop 协议隔离,维持原纪律
0x8F 本机侧同步帧 260 BLEN ≤ 255 覆盖最坏载荷(见 §6.9.4.3),DBN 帧缓冲需由 70 B 扩至 260 BAir780 parser 缓冲同步 ≥ 260 B

注:LEN 为 1 字节,理论上限 255,故同步帧最大 260 B255 + 5)。B 端(Air780)解析器与 A 端(DBN)装配器必须使用同一上限,否则超长帧会被判 LEN 非法丢弃。

6.9.3 命令

复用 BLE 侧既有命令码(dbn_ble_srv.h),保证同一命令在 BLE 通道与 UART1 同步通道语义一致

CMD 名称 方向 说明
0x14 GET_IOT_NET 双向 →:请求 net 配置(DATA 空);←:应答 net 配置(DATA 见 §6.9.4.1
0x16 GET_IOT_TOPIC 双向 →:请求 topic 配置(DATA 空);←:应答 topic 配置(DATA 见 §6.9.4.2

应答方向统一使用 GET_* 命令码:载荷由 DBN 侧既有构造器 set_response_iot_net() / set_response_iot_topic() 生成,与 BLE 读响应逐字节同源(零新增序列化代码,避免出现第二套口径)。DBN 主动推送时同样使用 GET_* + 对应载荷(语义 = “当前权威值”)。

6.9.4 载荷

0x00 分隔字符串序列;字符串按 strlen 紧凑发送(不补足结构体定长)。

6.9.4.1 netCMD 0x14

序号 字段 类型 最大长度 说明
1 remote_addr 字符串 63 服务器域名或 IP
2 mqtt_port ASCII 十进制 5 1883
3 client_id 字符串 63 空 = 使用本机序列号
4 username 字符串 63
5 password 字符串 31 末字段结尾 0x00

6.9.4.2 topicCMD 0x16

序号 字段 类型 最大长度 说明
1 clientid_enable ASCII '0' / '1' 1 1 = 启用自定义 ClientID
2 topic_pub 字符串 63 上报主题
3 topic_sub 字符串 63 下发主题

6.9.4.3 长度核算

载荷 典型长度 最坏长度 结论
net ≈ 59 B 63+1+5+1+63+1+63+1+31 = 229 B 单帧可传(需 260 B 帧上限 + 缓冲扩容,见下注)
topic ≈ 51 B 1+1+63+1+63 = 129 B 单帧可传

必须拆成两条独立记录net / topic):既与 BLE 两个命令的粒度对齐,也使 Air780 侧 fskv 单值(≤ 255 Bluat_fskv_set 限制)安全:net 229 B < 255 B ✓,topic 129 B ✓。

Air780 侧建议直接存原始载荷字节串(不转 JSON,加载时按 0x00 拆分 —— 与 DBN 侧构造器逐字节同源。

⚠️ 承载与缓冲前置条件(2026-09-10 补充)229 B 经 BLE 分包确可送达 —— 分包头编码于 pkg[1] 高/低 4 位(总包数 / 序号),每包 dat ≤ 94 B(MTU ≥ 103),4 位限制下最多 15 包。 但 DBN 侧 g_buf_ble_response.dat / tmp_ble_buf132 Bset_response_iot_net() / set_response_iot_topic() 无长度钳制 ⇒ 现版本载荷一旦 > 131 B 即越界写(可操作门槛:端口 4 位时 host + clientid + username + password 字符数 > 124)。 故本节的 229 B 能力以完成 §6.9.9 前置改造项 1–4 为前提,否则属纸面能力。

6.9.5 交互流程

① 拉取(主:兜底一切不一致)

Air780 上电 / 链路建立:
  ① fskv.init()      → 失败则降级为纯拉取模式(功能不受影响)
  ② 读 fskv 缓存     → 有则立即用缓存发起 MQTT 连接(不必等 DBN 就绪)
  ③ 链路建立后       → 发 0x8F CMD=0x14 / 0x16DATA 空)
  ④ DBN 应答         → 与缓存比对:相同则不动;不同则写 fskv + 断开重连重订阅

② 推送(辅:免重启立即生效)

BLE 写 SET_IOT_NET(0x13) / SET_IOT_TOPIC(0x15) / UPDATE_DEV_SERIAL(0x09) 成功
  → DBN 持久化后,若 UART1 链路可用,立即发 0x8F CMD=0x14 / 0x16 + 权威载荷
  → Air780 比对,变化则写 fskv + 重连重订阅

DBN 不维护“待同步”状态位:推送时链路不可用则不重试、不记账,等 Air780 下次拉取即可(无状态设计,避免状态机与真实链路状态不一致)。

③ 约束

  • Air780 收到配置后 先落 fskv 再重连(避免掉电后反复以旧参数重连)
  • fskv 仅在值实际变化时写入littlefs 擦写磨损,4 KB 擦写块)
  • 重连流程:断开 → 以新参数连接 → 重订阅 topic_sub → 恢复上报

6.9.6 其他触发同步的 BLE 命令

BLE 命令 是否必须同步 说明
CMD_DBN_UPDATE_DEV_SERIAL0x09 设备序列号 必须 Air780 的 Topic 族与上报 JSON 的 dev_serial 均由序列号派生。序列号变更 → Topic 族变更,Air780 需重算并重连
CMD_DBN_SET_SUB_CODE0x22 iot_enable 必须 IoT 通道总使能;DBN 有线 WCHNET MQTT 与 Air780 4G 通道均受此位控制。不同步 → DBN 已停发而 Air780 仍在连接(白耗流量),平台侧表现为“在线无数据”
CMD_DBN_RW_UART_BAUD0x31 UART1 波特率 禁止在线修改 该命令可改任意串口波特率,含 UART1(4G 链路)。在线改动而两侧未同步 = 链路永久失联,只能拆机重刷。建议固件侧对 uart_num == 1 直接拒绝

6.9.7 明确不同步的配置

配置 原因
local_net_cfg(有线 IP/网关/掩码 + 各端口)、net_center_infolssc_ip / tcp_port 有线以太网通道专用,与 4G 模组无关
CMD_DBN_SET_CJQ_*(车检器参数)、CMD_DBN_LOOP_* 经 UART2 下发给 Loop,方向与 4G 无关
CMD_DBN_SET_SUB_CODEiot_enable 外的位 局部外设使能(网口 / 雷达 / 激光 / LoRa 等)
CMD_DBN_OFFLOG_* / CMD_DBN_SNAP_* 本地 flash 读写;4G 侧仅作数据通道(§4 log_*stream=snapshot 已覆盖)
CMD_DBN_CHECK_PASS / CMD_DBN_MODIFY_PASS 蓝牙访问密码,纯本地
CMD_DBN_SET_FACTORY / CMD_DBN_RESET_DEV 本地动作;若涉及重启,链路断开重连由 Air780 自行处理
所有 GET_*(读操作) 无同步语义

不随载荷同步的字段IOT_NET_INFO.modeIP / DNS 标志)。实测该字段无 BLE 写入路径、恒为 0IOT_Addr_IP_Mode),当前仅 DBN 有线 MQTT 路径读取。Air780 侧按主机串自行判别地址类型(LuatOS socket.connect(host, port) 对 IP 与域名均兼容),无需同步。

6.9.8 异常处理

场景 行为
0x8F 帧校验失败 丢弃 + 回找最近魔数 resync不回错(配置链路静默重试成本低)
LEN 非法 / 帧超上限 丢弃并重新同步(LEN < 1LEN + 5 > 260
Air780 请求时 DBN 尚无有效配置 DBN 回出厂默认值cfig_flash.c 初始化值),保证 Air780 有明确行为而非空值
fskv 挂载失败(首刷 / 分区异常) fskv.init() 返回 false → 降级为纯拉取模式,不阻塞业务;缓存丢失不影响正确性
推送时 UART1 链路不可用 不重试、不记状态;等 Air780 下次拉取
Air780 重连失败(服务器不可达 / 鉴权失败) 按既有重连策略退避重试;MQTT 失败不影响 UART1 链路与配置同步
载荷含 0x00 的字段(如密码本身含 0x00 不支持;字段均为可见 ASCII 串

已知缺口(待办)DBN 自身经有线 WCHNET 直连 MQTT 的路径(peripheral_main.c仅实现 IP 模式,域名模式分支为空。故配置为域名时,有线通道 MQTT 不工作(4G 通道不受影响)。是否补齐由产品决定。

6.9.9 实施状态

项目 状态
DBN UART1 通道 + 0x8F 本机侧私有帧接入本机指令处理器(不转发) 已实现2026-09-10
DBN UART1 帧缓冲 70 B → 260 B 扩容 待实现
DBN ⚠️ 前置g_buf_ble_response.dat / tmp_ble_buf 132 B → ≥ 260 B(与蓝牙小程序/MRS 工程同步扩容) 待实现
DBN ⚠️ 前置set_response_iot_net/topic() 补长度钳制(对齐 set_response_buf() @dbn_ble_srv.c:288 待实现
DBN ⚠️ 前置unpack_packs() 接收侧累加加钳制(dat_len + (_len-1) > MAX_BLE_DAT_BUF_LEN 即丢包清缓冲,防 uint8_t 回绕) 待实现
DBN ⚠️ 前置unpack_packs() 用起 len 形参(现全程未引用,memcpy 长度全取自 pkg[2] 待实现
DBN 0x8F 配置请求应答 + BLE 写成功后主动推送 待实现
DBN CMD_DBN_RW_UART_BAUD 拒绝 uart_num == 1 待实现
Air780 frame_parser.lua 支持 0x8F(本地消费,不转发) 待实现
Air780 parser 缓冲 ≥ 260 B 待实现
Air780 fskv 缓存 + 启动即连 + 拉取校准 + 变化重连重订阅 待实现
Air780 配置读取由 require 期 local 改为重连时重读 待实现

修订记录

版本 修订时间 修订说明 修订人
V1.00 2026-06-22 初始版本,基于串口协议 V1.01 wangfq
V1.01 2026-07-07 Topic 压缩为双主题({sn}/srv + {sn}/dev),消息类型由 cmd 字段区分 wangfq
V1.02 2026-07-09 缩写相关字段 wangfq
V1.03 2026-07-09 增加设备上电初始化指令 wangfq
V1.04 2026-07-15 event_report 增加平台必答机制:应答格式(回显 msg_id)、设备 5s 超时重发(同 msg_id/ts,最多 3 次)、待发队列合并上报、平台去重与先落库后应答要求 wangfq
V1.05 2026-07-15 增加设备时钟同步(§2.3,方案B):设备无 RTCinitialize 上线后平台经 report_config 命令下发 Unix ts,设备据此校准,之后上行 ts 为真实 Unix 时间;校准前为上电秒数 wangfq
V1.06 2026-08-04 增加脱机事件日志命令:log_stat(统计/分页定位)、log_query(按全局序号分页,count≤4)、log_clear(清除+审计留痕);§2.3 补充日志双时间戳语义(ts_ms 相对 + unix_ts 已同步,0=未同步,锚点回算规则) wangfq
V1.07 2026-08-18 log_stat / log_query / log_clear 增加快照流支持(stream=snapshot,与 BLE 0x28/0x29/0x2A 同语义):快照统计 capacity 随芯片动态(48064~449472)、快照分页 count≤14 通道记录 JSON ~810B 超发送缓冲,实测修正;BLE 原始通道仍 ≤2)、快照清除审计留痕;capacity/count 类型修正为 uint32W25Q256 事件流 130944 超 16bit wangfq
V1.15 2026-09-10 §6.9 补充 BLE 分包承载与缓冲边界:核实 BLE 链路分包机制存在且收发双向对称(分包头编码于 pkg[1] 高/低 4 位 = 总包数/序号;每包 dat ≤ 94 B @MTU ≥ 1034 位 ⇒ 最多 15 包)⇒ 229 B 载荷确可送达,故「载荷 > 131 B 即越界写 g_buf_ble_response.dat / tmp_ble_buf132 B,构造器无钳制)」属可达缺陷而非纸面;§6.9.9 新增 4 条实现前置改造项(缓冲扩 ≥260 B / 构造器补钳制 / 接收侧累加钳制防 uint8_t 回绕 / unpack_packs 用起 len 形参);§6.9.4.3 补承载前置条件说明 wangfq
V1.14 2026-09-10 新增 §6.9 4G 配置同步(BLE → DBN → Air780:明确配置权威源为 DBN flashAir780 侧 fskv 仅作启动缓存,唯一写入者 = DBN 配置帧,避免第二权威源);复用 0x8F 本机侧私有帧(与 BLE 同魔数 MAGIC_BYTE_DBN_DEFAULT,链路两端各自本地消费、均不转发),命令复用 GET_IOT_NET(0x14) / GET_IOT_TOPIC(0x16),载荷与 BLE 读响应逐字节同源(复用 set_response_iot_net() / set_response_iot_topic(),零新增序列化)、拆 net/topic 两条(最坏 229B / 129B,均 ≤ fskv 单值 255B);0x8F 帧长上限独立定为 260B(Loop 业务帧 70B 上限不变,两侧帧缓冲需同步扩容);同步触发点补 UPDATE_DEV_SERIAL(0x09) / SET_SUB_CODEiot_enable 位 / 禁改 UART1 波特率V1.11 的 0x7D 帧配置同步方案作废iot_net_info.mode 实测无 BLE 写入路径、恒为 IP 模式,故不随载荷同步 wangfq
V1.13 2026-08-31 initializeextra_info 增加可选字段 imei / iccid4G 模块 IMEI / 流量卡 ICCID;无 4G 模块时省略或空串;4G 通道由 Air780 填真实值,§6.2 说明) wangfq
V1.12 2026-08-31 4G 通道适配修订:方案 B(hex 透传)改为方案 C(Air780 协议转换)——Air780 解析 0x7F 帧并转换为标准 JSON 命令loop_data / event_report / initialize / heartbeat,与有线通道一致),平台零改动;V1.11 的 frame_report / frame_cmd 降级为可选兜底4G 事件面 = 仅线圈事件car_enter/car_leave/loop_cut/loop_restore,不含 DBN 内部网络事件);命令响应链路 Air780 单命令状态机(超时回 code=5);link 对象保留;Air780 以 Lua 复刻 iot_event_report 逻辑(沿检测 + ACK + 5s×3 重发 + 16 深队列 + 跨重连同 msg_id),可行性已评估(2026-08-31 wangfq
V1.11 2026-08-31 4G 通道适配(方案 B:原始帧透传 + hex 封装):新增 frame_reportdev→srv,§6.3/ frame_cmdsrv→dev,§6.4)两命令(4G 通道专用);4G 通道不使用有线标准 JSON 业务命令(loop_data/event_report),不适用网络配置类命令(ssc_net_/iot_net_/iot_topic_*4G 通道下发回 code=4);上行附加 link 对象(IMEI/ICCID/IMSI/MSISDN/CSQ,§6.6);链路层数据面 0x7F 帧字节流透传(魔数分流在 vd960DBN 侧),0x7D 帧仅 DBN↔Air780 配置同步/握手;平台双通道区分解析 + 新增《DLD960Loop_串口通信协议》解析依赖(§6.7);实施主体:Air8781PAir780EPMvd960Air 工程,vd960DBN UART1 通道列入开发计划(固件未实现) wangfq
V1.10 2026-08-20 网络上报携带地感版本(配合远程 OTA 升级前后版本核对):dev_info_query 响应 + initialize 上报新增 loop_ver/loop_hw_ver(地感 Loop MCU 固件/硬件版本,格式 "主.次.次",来自 0x4A 查询缓存,尽力携带可为空);新增命令 loop_version_query(§4.25,srv→dev 实时查询,设备经 UART2 0x4A 异步查询后回包,含 loop_ver/loop_hw_ver/version_str);版本语义:soft_ver=整机 DBN 固件(主.次),loop_ver=地感 Loop 固件(主.次.次),二者区分 wangfq
V1.09 2026-08-20 OTA 刷写结果判定修复(现场:刷写物理成功但平台误判"刷写未启动"):① 刷写成功后台侧状态回 idle(镜像保留:size/crc32/version 不变,last_result=0,可重刷),与"下载完成待刷 ready"严格区分;② ota_report 升级为刷写结果主依据stage=done/failed 设备必报,重发 3 次×5s),ota_status 仅兜底;③ 明确平台判定指引——idle+size>0+last_result=0=成功,不得以轮询未见 flashing 或状态持续 ready 判"刷写未启动"(本地刷写 <1s,轮询大概率错过中间态);④ ota_begin 兼容 idle+size/crc32 一致 → 免下载直接可刷(重刷) wangfq
V1.08 2026-08-20 增加 Loop MCU 远程 OTAROADMAP P1.4 ①,先存后刷):命令 ota_begin / ota_data / ota_end / ota_abort / ota_flash / ota_statussrv→dev+ ota_reportdev→srv);单片 256B + 单片/全镜像 CRC32ISO-HDLC,§4.19.1);断点续传(ota_begin 返回 offset);Slot A/B 双槽回滚(镜像 ≤96KB);ota_flash 安全窗口检查 + 非阻塞 tick 驱动刷写(刷写窗口内 MQTT 保活/IWDG 不受影响);会话期间暂停 event_report 发送与脱机日志落盘;event_report 扩展 type=ota_error 失败告警;§2.2 补 OTA 细分错误码(err_code);老固件兼容(ota_* 回 code=4 wangfq