Files
vd_960/docs/DLD960_IoT_MQTT协议.md
T
wangfq e437dce556 docs(协议): TCP JSON V1.03 + IoT MQTT V1.07 — log_* 命令完善传感快照流 (2026-08-18)
- log_stat/log_query/log_clear 复用 stream 字段区分 event/snapshot(与 BLE 0x28/0x2A 同语义)
- 快照统计 capacity 随芯片动态(48064~449472);快照分页 count≤2(SnapRec 64B 原始结构 JSON 化,channels 对齐 0xC0)
- 快照清除审计留痕 + 阻塞时长说明(事件 2.8s / 快照 45ms)
- capacity/count 类型 uint16→uint32 修正(W25Q256 事件流 130944 超 16bit)
- README/技术规格书协议矩阵/产品手册相关文档表同步
2026-08-18 08:54:21 +08:00

1132 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DLD960 IoT 接口协议(MQTT + JSON
> 基于《DLD960 串口通信协议》V1.01,将设备管理、参数配置、数据上报映射到 MQTT 协议。
> 交互格式:JSON。
---
# 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 通用结构
```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 | 数据超长 |
## 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 | — |
| `initialize` | 设备上电初始化登陆 | dev→srv | — |
| `loop_data` | 线圈传感数据上报 | dev→srv | 0xC0 |
| `event_report` | 事件上报(**平台须应答**,见 §5.3 | dev→srv | — |
| `heartbeat` | 设备心跳 | dev→srv | — |
---
# 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",
"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 | 软件版本,格式 `"主.次"` |
| `model` | string | 产品型号,1~10 字符 |
| `product_code` | string | 产品编码,6 位数字字符串 |
| `sub_code.net` | bool | 网络功能是否启用 |
| `sub_code.iot` | bool | IoT/MQTT 功能是否启用 |
| `bus.bus1~4` | uint8 | 各总线探头数 |
## 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` |
**响应 datastream=event):**
```json
{
"stream": "event",
"enabled": true,
"boot_seq": 2,
"count": 1234,
"capacity": 16256,
"seq_first": 100,
"seq_last": 1333
}
```
**响应 datastream=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**(受 MQTT 发布 ≤500B 限制,MSS=576 教训)/ 快照流 **2**64B×2 记录)。
> 通过 `data.stream` 区分日志流:`event`(缺省)/ `snapshot`。
**请求:**
```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 按上限处理 |
**响应 datastream=event):**
```json
{
"start_seq": 1330,
"records": [
{
"seq": 1330,
"boot_seq": 2,
"ts_ms": 456789,
"unix_ts": 1784768575,
"type": "iot_ready",
"data": null
}
]
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| `seq` | uint32 | 全局序号 |
| `boot_seq` | uint16 | 所属启动段 |
| `ts_ms` | uint32 | boot 内相对时间(ms,断电归零) |
| `unix_ts` | uint32 | 已同步 Unix 秒;**0 = 未同步**(回算见 §2.3 |
| `type` | string | 事件类型名(下表) |
| `data` | object/null | 类型相关参数 |
**事件类型表:**
| type | 含义 | data 字段 |
|------|------|-----------|
| `boot` | 上电/复位 | `{"rst": 复位原因寄存器原始值}`,位解析:bit31=IWDG(看门狗) bit30=WWDG bit29=LPWR bit26=NRST引脚 bit25=POR(真断电) bit24=软件复位 |
| `iot_connect` | MQTT TCP 连接成功 | null |
| `iot_ready` | MQTT 订阅完成 → 发 initialize | null |
| `iot_disconnect` | MQTT 断连 | `{"reason": N}`1=断开 2=超时 3=CONNACK拒绝 4=连接超时 |
| `iot_reconn` | 重连退避 | `{"backoff_ms": 5000}` |
| `evt_retry` | event_report ACK 超时重发 | `{"msg_id":5,"retry":2}` |
| `evt_giveup` | event_report 重试耗尽挂起 | `{"msg_id":5}` |
| `coil` | 线圈事件 | `{"sub":"car_enter","ch":1,"value":97}`sub: car_enter/car_leave/loop_cut/loop_restorevalue 为 50ms 单位时间量 |
| `time_anchor` | 时钟同步锚点 | null`unix_ts` 即平台下发值,严格一致) |
| `log_clear` | 日志清除(审计) | null |
**响应 datastream=snapshot):**
```json
{
"start_seq": 12345,
"records": [
{
"seq": 12345,
"boot_seq": 2,
"ts_ms": 456789,
"coil_count": 4,
"channels": [
{
"ch": 1,
"freq_level": "high",
"direction": 0,
"freq_type": 1,
"sensitivity": 2,
"condition": 0,
"loop_ok": true,
"has_car": false,
"misc_type": "time",
"freq": 69418,
"variation": 7,
"misc": 0
}
]
}
]
}
```
快照记录为 64B 定长原始结构 `SnapRec`(与 0xC0 线上线圈单元逐字节一致,见《DLD960Loop 串口通信协议》§3.07),JSON 化后字段:
| 记录字段 | 类型 | 说明 |
|----------|------|------|
| `seq` | uint32 | 全局序号(跨 boot 递增) |
| `boot_seq` | uint16 | 所属启动段 |
| `ts_ms` | uint32 | boot 内相对时间(ms,采集时刻) |
| `coil_count` | uint8 | 本记录线圈数(SnapRec.len/121~4 |
| `channels` | array | 线圈传感单元(每路 12B,与 0xC0 线上格式一致) |
`channels[]` 单元字段(与 0xC0 传感单元一致):
| 字段 | 类型 | 说明 |
|------|------|------|
| `ch` | uint8 | 通道号 1~4 |
| `freq_level` | string | 频率档位:`"high"`(33nF) / `"mid_high"`(43nF) / `"mid_low"`(66nF) / `"low"`(76nF) |
| `direction` | uint8 | 0=触发模式 1=方向判别 |
| `freq_type` | uint8 | 0=初始频率 1=当前实时频率 |
| `sensitivity` | uint8 | 灵敏度等级(cfg 低四位) |
| `condition` | uint8 | 环境评估值(cond 高四位,值越大干扰越大) |
| `loop_ok` | bool | 线圈正常(SnapRec loop_state bit0=正常→true1=断开→false |
| `has_car` | bool | 有车(SnapRec car_state bit1=有车) |
| `misc_type` | string | `"time"` / `"cut_count"` / `"flow_count"` / `"relay_count"`(00 时间量 / 01 线圈断开次数 / 10 车流量 / 11 继电器输出次数) |
| `freq` | uint32 | 线圈频率(Hz3B LE 无符号) |
| `variation` | int32 | 变化量(3B LE 有符号补码,`Origin CAPVD`;正=车/裕量,负=反向漂移) |
| `misc` | uint32 | 杂项值(misc_type=time 时 = 通过时间/车间距,**50ms 单位**) |
> 绝对时间回算同事件流:用事件流 `time_anchor`boot_seq ↔ unix_ts 映射)+ 本记录 `boot_seq`/`ts_ms`;未同步段仅相对时间。
---
## 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**(逻辑清除 + 当前写扇区擦除,其余扇区由环形写覆盖时自动擦)。
---
# 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",
"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 | 固件版本 |
| `data.extra_info.code` | string | 可选,设备代码如IMSI/ICCID |
| `data.extra_info.csq` | string | 可选,当前信号强度 |
| `data.extra_info.location` | string | 可选,经纬度(经度,纬度) |
## 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) |
**平台应答(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 连接状态 |
---
# 修订记录
| 版本 | 修订时间 | 修订说明 | 修订人 |
|------|----------|----------|--------|
| 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≤2SnapRec 64B 原始结构 JSON 化)、快照清除审计留痕;`capacity`/`count` 类型修正为 uint32W25Q256 事件流 130944 超 16bit | wangfq |