方案 B(原始帧透传 + hex 封装)正式并入主协议: - 新增 §6 4G 通道适配: 架构(0x7F帧透传)/命令面差异/上行 frame_report/下行 frame_cmd/不支持命令/link 对象/平台要求 - §3 命令详表加 frame_cmd + frame_report(4G 通道专用标注) - 头部版本行 + 修订记录 V1.11 - 4G 通道: 不使用有线标准 JSON 业务命令;网络配置类命令回 code=4 - 实施主体: Air8781P(Air780EPM) vd960Air 工程;vd960DBN UART1 通道列入计划(固件未实现,如实标注) - 同步: README 索引 V1.11 + devlog 置顶条目(协议先行)
53 KiB
DLD960 IoT 接口协议(MQTT + JSON)
基于《DLD960 串口通信协议》V1.01,将设备管理、参数配置、数据上报映射到 MQTT 协议。 交互格式:JSON。 版本:V1.11(2026-08-31,4G 通道适配)
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
设备订阅此主题,接收服务器下发的所有命令(配置设置、查询、控制等)。 服务器发布到此主题。
对应串口 CMD:0x09 ~ 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 时间:
- 设备上电连接成功后发布
initialize(此时ts= 上电秒数,值很小,平台据此识别"未校准"); - 平台收到
initialize后,下发report_config命令,其信封ts字段填当前 Unix 时间; - 设备收到后以该
ts校准本地时钟基准,之后所有上行消息(loop_data/event_report/ 心跳 / 命令响应)的ts均为真实 Unix 时间。
补充约定:
report_config为约定的主同步点;设备亦接受任意下行命令中合法(≥ 1600000000,即 2020-09 之后)的ts进行校准,非法/过小值被忽略。- 校准前(含首个
initialize),ts为上电秒数;平台应能容忍并可按数量级区分。 - 设备重启后需重新校准(无掉电保持)。平台在每次设备
initialize后都应下发一次带ts的report_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 封装,V1.11) | 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 封装,V1.11) | dev→srv | — |
⚠
frame_cmd/frame_report为 4G 通道专用(方案 B:原始帧透传,见 §6),有线通道不使用。4G 通道不使用有线标准 JSON 业务命令(loop_data / event_report 等)。
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.10):
loop_ver/loop_hw_ver是缓存值(设备上电后自动经 Loop0x4A查询一次,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 |
响应 data(stream=event):
{
"stream": "event",
"enabled": true,
"boot_seq": 2,
"count": 1234,
"capacity": 16256,
"seq_first": 100,
"seq_last": 1333
}
响应 data(stream=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 + 1,count=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 协议》§7:magic(0xA5)/type/len/flags/seq/ts_ms/unix_ts/boot_seq/payload(12B),事件类型与 payload 定义同表 |
snapshot |
SnapRec 64B | 《DLD960 BLE 协议》§6.4:magic(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_stat的count归 1(仅剩审计记录),seq_last继续递增(序号不复位)。阻塞 ~2.8s(63 个数据扇区 SPI 擦除),请勿高频调用。stream=snapshot:成功后log_stat的count归 0,seq_last继续递增;清除动作写入事件流审计(log_clear,payload 标记快照流)。阻塞 ~45ms(逻辑清除 + 当前写扇区擦除,其余扇区由环形写覆盖时自动擦)。
4.19 开启 OTA 会话 ota_begin
Topic:
dld960/{sn}/srv依据《DLD960_MQTT_OTA协议.md》设计稿 V1.01(ROADMAP P1.4 ①:Loop MCU 远程 OTA,先存后刷)。 能力探测:老固件(V1.07 及以下)无ota_*命令,收到回code=4;平台下发前用ota_status或dev_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 = 96KB;Slot 数据区 100KB 预留 4KB 边界余量) |
crc32 |
uint32 | 全镜像 CRC32(十进制,算法见 §4.19.1) |
version |
string | 目标固件版本(写入元数据,审计用;bootloader 不校验版本) |
force |
bool | true = 覆盖现有暂存镜像 / 忽略版本冲突(默认 false) |
设备行为:
- 读暂存元数据:若已有镜像且
size+crc32与本次一致 →state=ready→ 返回offset=size(平台可直接ota_flash)state=downloading→ 返回offset=received(断点续传)
- 不一致 → 分配 Slot(A 当前 / B 回滚),写元数据
state=downloading, received=0,返回offset=0 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-HDLC:poly 0x04C11DB7(reflected 0xEDB88320),init 0xFFFFFFFF,refin/refout true,xorout 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 原始字节小写 hex,512 字符 |
设备行为:
offset == received(顺序片)→ 单片 CRC32 校验 → 写 W25Qxx 暂存(256B 页对齐)→received += 256(≥size 截断为 size)→code=0offset < received(重复片,平台重发)→ 幂等回code=0,不重写offset > received(缺片/乱序)→code=1, err_code=1+data.offset=received(指示平台从该处续传;协议不要求乱序重组)- 单片 CRC 失败 →
code=1, err_code=1(平台重发本片;连续失败平台可ota_abort) - 会话未开始 / 状态非 downloading →
code=3, err_code=2 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
}
}
设备行为:
received != size→code=1+data.offset=received(不完整,续传)received == size→ 读回暂存区全镜像计算 CRC32,与 ota_begin 声明值比对- 一致 → 元数据
state=ready, last_result=0→code=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 = 跳过安全窗口检查(高风险,平台授权) |
设备行为(同步检查 → 异步刷写):
- 安全窗口检查(force=false 时):4 通道 Loop 有车(VD_FLAG 任一置位)→
code=3, err_code=3(有车,拒绝;平台提示"车辆离开后重试")。刷写期间会阻断检测与继电器控制 → 平台建议低峰执行 - 元数据非 ready →
code=3(先ota_end完成校验) - 通过 → 立即回
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 地址帧(
0x080034004 字节大端)→ addr_ok - 从 W25Qxx 暂存读镜像,按 ≤254B/块发 A7(非阻塞 tick 驱动:每轮主循环发送 1~2 块并检查 ACK,绝不阻塞主循环,保证刷写窗口内 MQTT PINGREQ/心跳/IWDG 喂狗正常);停等 ACK,1s 超时重发 ×3
- 末块(sub_amount=1)→ bootloader 写剩余 → 清 flag → 复位跑新 APP
- 写 offlog 事件日志:
- 进度经
ota_report上行(§5.5);失败重试 ×3 仍失败 → 元数据state=flash_failed+event_report{type:ota_error}告警(平台必答) - 刷写结果(设备侧,V1.09 明确):
- 成功(末块 ACK)→ 元数据
state=idle(镜像保留:size/crc32/version/slot不变,last_result=0,flash_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中间态
- 成功(末块 ACK)→ 元数据
响应: 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 / aborted;idle 且 size>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 }
设备行为(异步):
- 经 UART2 向 Loop MCU 发
0x4A查询帧(7F 00 01 4A ...,见DLD960Loop_串口通信协议.md§3.01) - 收到 Loop 响应后解析(Soft/Hard 各三段)→ 更新本地缓存 → 立即回包
- Loop 无响应/超时 → 回
code=5,loop_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_flash前loop_version_query记录旧版本 → 刷写完成后再次查询核对新版本是否生效。
5 设备主动上报
5.1 设备上电登陆信息 initialize
Topic:
dld960/{sn}/devQoS: 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",
"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.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}/devQoS: 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}/srvQoS: 1(建议) ⚠️ 本指令要求平台必须应答(区别于loop_data/heartbeat的单向上报)
设备检测到事件时主动上报,非周期性。事件(进车/出车/线圈断开等)是不可再生的关键数据,MQTT QoS 仅保证 broker 收到,不代表平台业务层已处理入库,因此引入应用层应答 + 设备重发机制闭环确认。
注:
event_report不受report_config.enable门控——设备上电即检测并上报事件。report_config的enable/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 = 处理失败(设备视同未确认,进入重发) |
设备端确认与重发机制:
- 发出
event_report后启动确认定时器,5s 内未收到msg_id匹配且code=0的应答 → 重发; - 重发使用相同的
msg_id与原始ts(首次事件发生时间,不随重发刷新),平台以此去重; - 最多重发 3 次(含首发共 4 次)。仍未确认 → 事件保留在待发队列,待 MQTT 重连成功或下一次事件触发时合并上报;
- 等待确认期间新产生的事件,合并进下一包的
events数组(使用新msg_id)。单包长度受发布缓冲限制(≤500B),超出时拆包分批; - 待发队列建议深度 ≥16 条事件,溢出时丢弃最旧事件(先保新鲜数据)。
平台端要求:
- 收到
event_report先落库、后应答,应答即承诺数据已持久化; - 按
(dev_serial, msg_id)在近期窗口(建议 10 分钟)内去重——重复包为设备未收到应答所致,直接应答code=0,不重复入库(设备重启后msg_id从头递增,去重必须限定时间窗口); - 应答报文应控制在最小集(不带
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 通道适配(方案 B:原始帧透传 + hex 封装,V1.11 新增)
适用场景:vd960DBN 有线网络失效时,经 Air8781P 整板(Air780EPM 4G 模组,LuatOS vd960Air 工程) 兜底上报。 4G 通道与有线通道命令面不同(见 §6.2),平台须按通道区分解析。
6.1 通道架构
上行: vd960Loop --0x7F帧(UART2)--> vd960DBN --原样转发(UART1)--> Air780 --MQTT frame_report--> 平台
下行: 平台 --MQTT frame_cmd--> Air780 --原样(UART1)--> vd960DBN --0x7F帧(UART2)--> vd960Loop
- 魔数分流(在 vd960DBN 侧):
0x7F帧 → 转发 UART2(vd960Loop 地感);0x8F帧 → DBN 本地处理(BLE/串口配置命令同源) - Air780 职责:0x7F 帧切包(Lua 状态机,只切包不懂内容)+ MQTT 收发 + hex 封装
- vd960DBN 职责:UART2↔UART1 双向转发(复用
manage_dbn_ble_transparent透传模式;列入 vd960DBN 开发计划,固件未实现) - 链路层(UART):数据面 0x7F 帧字节流透传(0x7F 帧自带 LEN/校验,Air780 按帧切包);
0x7D帧仅用于 DBN↔Air780 配置同步下发/握手(详见 vd960Air 工程 readme §3)
6.2 命令面差异
| 通道 | 上行(dev→srv) | 下行(srv→dev) |
|---|---|---|
| 有线(ETH MQTT) | initialize / loop_data / event_report / heartbeat / ota_report(标准 JSON) | dev_serial_set / ssc_net_* / iot_net_* / loop_param_* / report_config / log_* / ota_* / loop_version_query(标准 JSON) |
| 4G(Air780 透传) | frame_report(hex 帧,§6.3)+ initialize(JSON,§6.3.1) | frame_cmd(hex 帧,§6.4) |
- 4G 通道不使用有线通道的标准 JSON 业务命令(loop_data / event_report 等)
- 4G 通道不适用的网络配置类命令:
ssc_net_set/ssc_net_query/iot_net_set/iot_net_query/iot_topic_set/iot_topic_query(4G 接入网是 SIM 运营商网络,APN/主题由 4G 侧管理;经 4G 通道下发返回code=4) - 其余命令(loop_param_、log_、ota_、pwd_、report_config、device_reset 等)可由平台经
frame_cmd下发0x8F帧由 DBN 本地处理,或0x7F帧转发 vd960Loop——设备侧指令面不变
6.3 上行 frame_report
Topic:
dld960/{sn}/dev· QoS 1 · 每个 0x7F 帧一条
{
"msg_id": 123,
"cmd": "frame_report",
"ts": 1719000000,
"data": { "frame": "7f00c0060102030405aabb" },
"link": { "imei": "860012345678901", "iccid": "89860012345678901234", "imsi": "460001234567890", "msisdn": "", "csq": 23, "net": "4G" }
}
| data 字段 | 类型 | 说明 |
|---|---|---|
frame |
string(hex) | 0x7F 帧完整字节 hex(含魔数 0x7F/校验字节),小写;平台按《DLD960Loop_串口通信协议》解析(0xC0 传感 / 0x0C 响应 / 事件等) |
link为 4G 特有字段(§6.6),每条上行携带msg_id/ts由 Air780 生成;ts校准遵循 §2.3(见 §6.3.1)
6.3.1 4G 通道 initialize(JSON)
4G 通道的 initialize 由 Air780 在 MQTT 连接成功后发送(标准 JSON 形态 + link),用于:① 平台识别 4G 设备上线;② 平台下发 report_config 校准 ts(§2.3)。
{
"msg_id": 1,
"cmd": "initialize",
"ts": 3,
"data": { "loop_ver": "1.0.5", "loop_hw_ver": "1.0" },
"link": { "imei": "860012345678901", "iccid": "89860012345678901234", "imsi": "460001234567890", "msisdn": "", "csq": 23, "net": "4G" }
}
loop_ver/loop_hw_ver:地感 Loop 版本(与 §4.25 同语义;vd960DBN 未实现 UART1 通道前可省略)- 后续传感/事件/响应数据一律经
frame_report
6.4 下行 frame_cmd
Topic:
dld960/{sn}/srv· QoS 1
{
"msg_id": 456,
"cmd": "frame_cmd",
"ts": 1719000100,
"data": { "frame": "7f000809010203040506" }
}
| data 字段 | 类型 | 说明 |
|---|---|---|
frame |
string(hex) | 0x7F 帧(→ vd960Loop 地感指令)或 0x8F 帧(→ vd960DBN 本地配置)hex |
- Air780 收到 → hex 解码 → 原始字节 → UART1 → vd960DBN 魔数分流
- 地感/设备响应经
frame_report上行
6.5 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 配置) |
经 4G 通道下发以上命令:设备回
code=4 unsupported。
6.6 link 对象(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.7 平台侧要求
- 双通道区分:有线通道按标准 JSON 业务命令解析;4G 通道按
frame_report/frame_cmd解析(可依link.net或报文形态区分) - 新增解析依赖:《DLD960Loop_串口通信协议》(hex → 0x7F 帧 → 字段)
- 设备唯一标识:
dev_serial与有线通道同一序列号(Topic 族一致,平台认同一台设备);link.imei/link.iccid辅助 4G 设备/流量卡管理 - 时钟校准:Air780 上线发
initialize(JSON + link)后,平台照常下发report_config校准ts(§2.3)
修订记录
| 版本 | 修订时间 | 修订说明 | 修订人 |
|---|---|---|---|
| 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):设备无 RTC,initialize 上线后平台经 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≤1(4 通道记录 JSON ~810B 超发送缓冲,实测修正;BLE 原始通道仍 ≤2)、快照清除审计留痕;capacity/count 类型修正为 uint32(W25Q256 事件流 130944 超 16bit) |
wangfq |
| V1.11 | 2026-08-31 | 4G 通道适配(方案 B:原始帧透传 + hex 封装):新增 frame_report(dev→srv,§6.3)/ frame_cmd(srv→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);实施主体:Air8781P(Air780EPM)vd960Air 工程,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 远程 OTA(ROADMAP P1.4 ①,先存后刷):命令 ota_begin / ota_data / ota_end / ota_abort / ota_flash / ota_status(srv→dev)+ ota_report(dev→srv);单片 256B + 单片/全镜像 CRC32(ISO-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 |