- 协议 §6.9:BLE→DBN→Air780 配置同步(权威源 / 载体 0x8F / 命令复用 / 载荷拆两条 / 拉取为主+推送为辅 / 明确不同步项 / 异常处理 / 实施状态) - 版本 V1.13 → V1.14,修订记录加行(插 V1.13 前);§6.6 增补充说明 - 行尾:全文纯 LF,改动零行尾污染 - devlog:新增 2026-09-10 条目(结论 + 6 条代码事实级硬约束 + 文件改动 + 待办)
1703 lines
66 KiB
Markdown
1703 lines
66 KiB
Markdown
# DLD960 IoT 接口协议(MQTT + JSON)
|
||
|
||
> 基于《DLD960 串口通信协议》V1.01,将设备管理、参数配置、数据上报映射到 MQTT 协议。
|
||
> 交互格式:JSON。
|
||
> 版本:V1.14(2026-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`
|
||
|
||
设备订阅此主题,接收服务器下发的所有命令(配置设置、查询、控制等)。
|
||
服务器发布到此主题。
|
||
|
||
对应串口 CMD:0x09 ~ 0x1F, 0x63, 0x64, 0xC5 等。
|
||
|
||
### 1.2.2 设备上报 Topic — `dld960/{sn}/dev`
|
||
|
||
设备发布到此主题,上报线圈数据、事件、心跳及命令响应。
|
||
服务器订阅此主题接收所有设备上行消息。
|
||
|
||
服务器端可订阅通配符 `dld960/+/dev` 监听所有设备。
|
||
|
||
---
|
||
|
||
# 2 JSON 消息格式
|
||
|
||
## 2.1 通用结构
|
||
|
||
```json
|
||
{
|
||
"msg_id": 12345,
|
||
"cmd": "dev_info_query",
|
||
"ts": 1719000000,
|
||
"data": { ... }
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `msg_id` | uint32 | 消息序列号,递增,用于请求-响应匹配(由发起方生成) |
|
||
| `cmd` | string | 命令标识符 |
|
||
| `ts` | uint32 | Unix 时间戳(秒),发起方填充 |
|
||
| `data` | object | 命令参数,结构依 cmd 而定 |
|
||
|
||
## 2.2 响应通用结构
|
||
|
||
```json
|
||
{
|
||
"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` 后都应下发一次带 `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 封装,可选兜底) | 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`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 1,
|
||
"cmd": "dev_serial_set",
|
||
"ts": 1719000000,
|
||
"data": {
|
||
"dev_serial": "A1B2C3D4E5F6"
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应:** Topic: `dld960/{sn}/dev`
|
||
|
||
```json
|
||
{
|
||
"msg_id": 1,
|
||
"cmd": "dev_serial_set",
|
||
"ts": 1719000001,
|
||
"code": 0,
|
||
"msg": "success"
|
||
}
|
||
```
|
||
|
||
## 4.2 查询设备信息 `dev_info_query`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 2,
|
||
"cmd": "dev_info_query",
|
||
"ts": 1719000000
|
||
}
|
||
```
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"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` 是**缓存值**(设备上电后自动经 Loop `0x4A` 查询一次,OTA 刷写成功后刷新);MQTT 命令处理为同步回包,不等 0x4A 异步响应,故查询未完成/失败时回空。平台需最新版本时用 `loop_version_query`(§4.25)实时查询。
|
||
|
||
## 4.3 设置 SSC 网络配置 `ssc_net_set`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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 | 端口号 |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 3,
|
||
"cmd": "ssc_net_set",
|
||
"ts": 1719000001,
|
||
"code": 0,
|
||
"msg": "success"
|
||
}
|
||
```
|
||
|
||
## 4.4 查询 SSC 网络配置 `ssc_net_query`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 4,
|
||
"cmd": "ssc_net_query",
|
||
"ts": 1719000000
|
||
}
|
||
```
|
||
|
||
**响应:** 返回字段同 4.3 的 `data`。
|
||
|
||
## 4.5 设置 IoT 网络配置 `iot_net_set`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 6,
|
||
"cmd": "iot_net_query",
|
||
"ts": 1719000000
|
||
}
|
||
```
|
||
|
||
**响应:** 返回字段同 4.5 的 `data`。
|
||
|
||
## 4.7 设置设备 Topic `iot_topic_set`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 8,
|
||
"cmd": "iot_topic_query",
|
||
"ts": 1719000000
|
||
}
|
||
```
|
||
|
||
**响应:** 返回字段同 4.7 的 `data`。
|
||
|
||
## 4.9 验证设备密码 `pwd_verify`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 9,
|
||
"cmd": "pwd_verify",
|
||
"ts": 1719000000,
|
||
"data": {
|
||
"password": "123456"
|
||
}
|
||
}
|
||
```
|
||
|
||
| data 字段 | 类型 | 说明 |
|
||
|-----------|------|------|
|
||
| `password` | string | 6 位数字密码 |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 9,
|
||
"cmd": "pwd_verify",
|
||
"ts": 1719000001,
|
||
"code": 0,
|
||
"msg": "success"
|
||
}
|
||
```
|
||
|
||
## 4.10 设置设备密码 `pwd_set`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 11,
|
||
"cmd": "factory_reset",
|
||
"ts": 1719000000
|
||
}
|
||
```
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 11,
|
||
"cmd": "factory_reset",
|
||
"ts": 1719000001,
|
||
"code": 0,
|
||
"msg": "success"
|
||
}
|
||
```
|
||
|
||
## 4.12 设备复位 `device_reset`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 12,
|
||
"cmd": "device_reset",
|
||
"ts": 1719000000
|
||
}
|
||
```
|
||
|
||
无响应(设备复位后断开连接)。
|
||
|
||
## 4.13 设置车检器多路参数 `loop_param_set`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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, 功能模式 |
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 13,
|
||
"cmd": "loop_param_set",
|
||
"ts": 1719000001,
|
||
"code": 0,
|
||
"msg": "success"
|
||
}
|
||
```
|
||
|
||
## 4.14 读取车检器多路参数 `loop_param_query`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 14,
|
||
"cmd": "loop_param_query",
|
||
"ts": 1719000000
|
||
}
|
||
```
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"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` 后下发一次。
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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`(传感快照)。
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 16,
|
||
"cmd": "log_stat",
|
||
"ts": 1719000000,
|
||
"data": {
|
||
"stream": "event"
|
||
}
|
||
}
|
||
```
|
||
|
||
| data 字段 | 类型 | 说明 |
|
||
|-----------|------|------|
|
||
| `stream` | string | 日志流:`event`(缺省,可省略)或 `snapshot` |
|
||
|
||
**响应 data(stream=event):**
|
||
|
||
```json
|
||
{
|
||
"stream": "event",
|
||
"enabled": true,
|
||
"boot_seq": 2,
|
||
"count": 1234,
|
||
"capacity": 16256,
|
||
"seq_first": 100,
|
||
"seq_last": 1333
|
||
}
|
||
```
|
||
|
||
**响应 data(stream=snapshot):**
|
||
|
||
```json
|
||
{
|
||
"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 协议》字段表解析。
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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:**
|
||
|
||
```json
|
||
{
|
||
"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` 审计——谁在何时清了日志,留痕不可清除)。平台侧应做权限控制。
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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` 判断。
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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) |
|
||
|
||
**设备行为:**
|
||
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:**
|
||
|
||
```json
|
||
{
|
||
"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。
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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 字符 |
|
||
|
||
**设备行为:**
|
||
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`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"msg_id": 403,
|
||
"cmd": "ota_end",
|
||
"ts": 1719000003,
|
||
"data": {
|
||
"target": "loop",
|
||
"crc32": 305419896
|
||
}
|
||
}
|
||
```
|
||
|
||
**设备行为:**
|
||
1. `received != size` → `code=1` + `data.offset=received`(不完整,续传)
|
||
2. `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`
|
||
|
||
```json
|
||
{
|
||
"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`
|
||
> ⚠ **会车安全关键命令**:平台应确认现场允许(无车压线圈、非高峰)再下发。
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{
|
||
"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=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` 中间态
|
||
|
||
**响应:** `code=0` 仅表示已启动,不代表刷写成功——结果以 `ota_report` / `ota_status` 为准。
|
||
|
||
---
|
||
|
||
## 4.24 查询状态 `ota_status`
|
||
|
||
> Topic: `dld960/{sn}/srv`
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{ "msg_id": 406, "cmd": "ota_status", "ts": 1719000006 }
|
||
```
|
||
|
||
**响应 data:**
|
||
|
||
```json
|
||
{
|
||
"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 做升级前后版本核对)。
|
||
|
||
**请求:**
|
||
|
||
```json
|
||
{ "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=5`,`loop_ver` 保持缓存值(可为空)
|
||
|
||
**响应:**
|
||
|
||
```json
|
||
{
|
||
"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}/dev`
|
||
> QoS: 0/1
|
||
|
||
```json
|
||
{
|
||
"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)。
|
||
|
||
```json
|
||
{
|
||
"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_config` 的 `enable`/`interval` 仅控制 `loop_data` 周期上报,与事件上报解耦。
|
||
|
||
**设备上报(dev topic):**
|
||
|
||
```json
|
||
{
|
||
"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,收到后必须立即回复):**
|
||
|
||
```json
|
||
{
|
||
"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 秒
|
||
|
||
```json
|
||
{
|
||
"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`)。
|
||
|
||
**上报:**
|
||
|
||
```json
|
||
{
|
||
"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 通道适配(方案 C:Air780 协议转换,V1.12 修订)
|
||
|
||
> 适用场景:vd960DBN 有线网络失效时,经 **Air8781P 整板(Air780EPM 4G 模组,LuatOS vd960Air 工程)** 兜底上报。
|
||
> **方案 C**:Air780 解析 0x7F 帧并转换为**标准 JSON 命令**(与有线通道一致),平台无感。
|
||
> 修订说明:V1.11 曾定方案 B(原始帧 hex 透传 frame_report/frame_cmd),V1.12 改为方案 C,frame_* 降级为可选兜底。
|
||
|
||
## 6.1 通道架构
|
||
|
||
```
|
||
上行: vd960Loop --0x7F帧(UART2)--> vd960DBN --原样转发(UART1)--> Air780 --解析转JSON--> MQTT(loop_data/event_report/响应)
|
||
下行: 平台 --标准JSON命令--> Air780 --转换0x7F帧--> vd960DBN --0x7F帧(UART2)--> vd960Loop
|
||
```
|
||
|
||
- **vd960DBN**:UART2↔UART1 双向转发(魔数分流:`0x7F` 帧 → 转发 UART2(vd960Loop);`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) |
|
||
| 4G(Air780 转换) | **标准 JSON**(initialize / loop_data / event_report / heartbeat,Air780 从 0x7F 帧生成)+ 可选 frame_report | **标准 JSON**(Air780 转换为 0x7F 帧下发)+ 可选 frame_cmd |
|
||
|
||
- 4G 通道上行 JSON 结构与有线通道**完全一致**(平台按同一解析逻辑处理)
|
||
- 4G 通道 `initialize` 的 `extra_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):逻辑块全部可映射 Lua(table 队列 / 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 下发配置(否则会改掉自己正在使用的连接参数,形成第二权威源)。
|
||
|
||
## 6.7 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.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 → Air780,V1.14 新增)
|
||
|
||
### 6.9.1 背景与权威源
|
||
|
||
4G 通道的 MQTT 连接参数(服务器地址、端口、ClientID、账号、密码、发布/订阅主题)由**蓝牙小程序经 BLE 写入 DBN**。§6.6 已规定这些配置**不允许经 4G 通道下发**(回 `code=4`),因此 **BLE 是网络配置的唯一入口**。若 DBN 不同步给 Air780,Air780 只能使用自身固件内的默认值,**平台侧改配置无效**。
|
||
|
||
配置权威源 = **DBN 侧 flash**(`IOT_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 + 5`;`XOR` / `SUM` 覆盖 = `Addr` 起 `2 + LEN` 字节。
|
||
|
||
**UART1 魔数语义(链路两端一致)**
|
||
|
||
| 魔数 | 处理 |
|
||
|------|------|
|
||
| `0x7F` | 业务帧:转发 UART2(Loop) |
|
||
| `0x8F` | **本机侧私有帧**:链路两端各自本地消费,**均不转发** |
|
||
| 其他 | 丢弃,并回找最近魔数重新同步(resync) |
|
||
|
||
**⚠ 帧长上限(两侧必须同步扩容)**
|
||
|
||
| 帧类型 | 帧长上限 | 说明 |
|
||
|--------|----------|------|
|
||
| `0x7F` Loop 业务帧 | **70 B**(`LUP_MAX_PKG_LEN`,不变) | 与 Loop 协议隔离,维持原纪律 |
|
||
| `0x8F` 本机侧同步帧 | **260 B**(`LEN` ≤ 255) | 覆盖最坏载荷(见 §6.9.4.3),**DBN 帧缓冲需由 70 B 扩至 260 B,Air780 parser 缓冲同步 ≥ 260 B** |
|
||
|
||
> 注:`LEN` 为 1 字节,理论上限 255,故同步帧最大 260 B(255 + 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 net(CMD `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 topic(CMD `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 B**,`luat_fskv_set` 限制)安全:net 229 B < 255 B ✓,topic 129 B ✓。
|
||
|
||
Air780 侧建议**直接存原始载荷字节串(不转 JSON)**,加载时按 `0x00` 拆分 —— 与 DBN 侧构造器逐字节同源。
|
||
|
||
### 6.9.5 交互流程
|
||
|
||
**① 拉取(主:兜底一切不一致)**
|
||
|
||
```
|
||
Air780 上电 / 链路建立:
|
||
① fskv.init() → 失败则降级为纯拉取模式(功能不受影响)
|
||
② 读 fskv 缓存 → 有则立即用缓存发起 MQTT 连接(不必等 DBN 就绪)
|
||
③ 链路建立后 → 发 0x8F CMD=0x14 / 0x16(DATA 空)
|
||
④ 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_SERIAL`(`0x09`) | 设备序列号 | **必须** | Air780 的 Topic 族与上报 JSON 的 `dev_serial` 均由序列号派生。序列号变更 → Topic 族变更,Air780 需重算并重连 |
|
||
| `CMD_DBN_SET_SUB_CODE`(`0x22`) | `iot_enable` 位 | **必须** | IoT 通道总使能;DBN 有线 WCHNET MQTT 与 Air780 4G 通道均受此位控制。不同步 → DBN 已停发而 Air780 仍在连接(白耗流量),平台侧表现为“在线无数据” |
|
||
| `CMD_DBN_RW_UART_BAUD`(`0x31`) | UART1 波特率 | **禁止在线修改** | 该命令可改任意串口波特率,含 UART1(4G 链路)。在线改动而两侧未同步 = **链路永久失联,只能拆机重刷**。建议固件侧对 `uart_num == 1` 直接拒绝 |
|
||
|
||
### 6.9.7 明确不同步的配置
|
||
|
||
| 配置 | 原因 |
|
||
|------|------|
|
||
| `local_net_cfg`(有线 IP/网关/掩码 + 各端口)、`net_center_info`(`lssc_ip` / `tcp_port`) | 有线以太网通道专用,与 4G 模组无关 |
|
||
| `CMD_DBN_SET_CJQ_*`(车检器参数)、`CMD_DBN_LOOP_*` | 经 UART2 下发给 Loop,方向与 4G 无关 |
|
||
| `CMD_DBN_SET_SUB_CODE` 除 `iot_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.mode`(IP / DNS 标志)。实测该字段**无 BLE 写入路径**、恒为 `0`(`IOT_Addr_IP_Mode`),当前仅 DBN 有线 MQTT 路径读取。Air780 侧按主机串自行判别地址类型(LuatOS `socket.connect(host, port)` 对 IP 与域名均兼容),无需同步。
|
||
|
||
### 6.9.8 异常处理
|
||
|
||
| 场景 | 行为 |
|
||
|------|------|
|
||
| 0x8F 帧校验失败 | 丢弃 + 回找最近魔数 resync;**不回错**(配置链路静默重试成本低) |
|
||
| `LEN` 非法 / 帧超上限 | 丢弃并重新同步(`LEN < 1` 或 `LEN + 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 | 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):设备无 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.14 | 2026-09-10 | 新增 **§6.9 4G 配置同步(BLE → DBN → Air780)**:明确配置权威源为 DBN flash(Air780 侧 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_CODE` 的 `iot_enable` 位 / **禁改 UART1 波特率**;V1.11 的 **0x7D 帧配置同步方案作废**;`iot_net_info.mode` 实测无 BLE 写入路径、恒为 IP 模式,故不随载荷同步 | wangfq |
|
||
| V1.13 | 2026-08-31 | `initialize` 的 `extra_info` 增加可选字段 **`imei`** / **`iccid`**(4G 模块 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_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 |
|
||
|