Files
vd_960/docs/DLD960_TCP_JSON协议.md
T
wangfq 4112b7cbbf feat(iot_mqtt): 实现 event_report 平台必答+设备重发 (协议 V1.04); 时间单位统一 50ms
时间单位:
- 串口协议 V1.06 / TCP JSON 协议: 杂项时间量、事件 value 由 5ms 修正为
  50ms, 对齐 Loop 固件 50ms tick 实现 (TMR15 5ms×10)

event_report (iot_mqtt_srv.c + net_srv.c):
- 沿检测双路汇聚: Step1 消费路径 + lup 回调覆盖 uart_srv 路径, 不漏帧
- 16 深环形队列, 事件仅 ACK(code=0) 后出队; 溢出丢最旧
- 5s 超时重发同 msg_id/原始 ts ×3 次, 耗尽挂起, 新事件/重连沿合并补报
- 帧消费提前至 READY/enable 之前: 断网期间事件入队, 重连补报
- 线圈断开期间屏蔽 car 沿; loop_restore value=本地计时/50
- msg_id 独立 uint32 计数 (g_iot_msg_id 为 uint8 混用会破坏去重窗口)
- net_srv.c ACK 路由: cmd=event_report 回显帧确认出队, 不回 unsupported
- gcc 隔离单测 8 组全过 (tests/test_event_report.c), 单包 6 条实测 317B
2026-07-15 12:27:22 +08:00

458 lines
16 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 TCP 接口协议(JSON
> 基于《DLD960 串口通信协议》V1.01,提供以太网 TCP 通道的 JSON 交互协议。
> 适用于上位机、调试工具等直接 TCP 连接场景。
---
# 1 通信说明
## 1.1 连接参数
| 项目 | 说明 |
|------|------|
| 传输层 | TCP |
| 默认端口 | 5960 |
| 编码 | UTF-8 |
| 序列化 | JSON |
| 帧分隔 | 换行符 `\n`(每个 JSON 对象占一行) |
| 最大帧长 | 4096 字节 |
## 1.2 通信模式
- **请求-响应**:客户端发送命令,设备返回响应(同步或异步)
- **主动推送**:设备可主动推送数据(线圈数据、事件)
- **连接保持**:支持长连接,60 秒无数据心跳超时断开
## 1.3 连接流程
```
Client Device (DLD960)
| |
|--- TCP Connect ------------------>|
| |
|--- pwd_verify (鉴权) ------------>|
|<-- 鉴权成功 / 失败 ---------------|
| |
|--- 命令 1 ----------------------->|
|<-- 响应 1 ------------------------|
| |
|--- 命令 2 ----------------------->|
|<-- 响应 2 ------------------------|
| |
|<-- 主动推送 (loop_data/event) ----|
| |
|--- TCP Close / 超时 ------------->|
```
---
# 2 JSON 消息格式
## 2.1 请求帧
```json
{"msg_id":1,"cmd":"dev_info_query","ts":1719000000,"data":{...}}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `msg_id` | uint32 | 是 | 消息序列号,递增,用于请求-响应匹配 |
| `cmd` | string | 是 | 命令标识符 |
| `ts` | uint32 | 否 | Unix 时间戳(秒) |
| `data` | object | 否 | 命令参数 |
## 2.2 响应帧
```json
{"msg_id":1,"cmd":"dev_info_query","ts":1719000001,"code":0,"msg":"success","data":{...}}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `msg_id` | uint32 | 是 | 对应请求的 msg_id |
| `cmd` | string | 是 | 命令标识符(与请求一致) |
| `ts` | uint32 | 否 | 响应时间戳 |
| `code` | int | 是 | 0=成功,非0=错误码 |
| `msg` | string | 是 | 结果描述 |
| `data` | object | 否 | 响应数据 |
## 2.3 主动推送帧
```json
{"msg_id":100,"cmd":"loop_data","ts":1719000100,"data":{...}}
```
> 设备主动推送无 `code`/`msg` 字段,由 `cmd` 区分类型。
## 2.4 错误码
| code | 说明 |
|------|------|
| 0 | 成功 |
| 1 | 参数错误(格式/范围不正确) |
| 2 | 密码验证失败 / 未鉴权 |
| 3 | 设备忙 |
| 4 | 不支持的命令 |
| 5 | 内部错误 |
| 6 | 数据超长 |
| 7 | 连接未鉴权(需先执行 pwd_verify |
---
# 3 命令详表
| cmd | 说明 | 需鉴权 | 对应串口 |
|-----|------|--------|----------|
| `pwd_verify` | 验证设备密码(连接鉴权) | 否 | 0x1C |
| `dev_serial_set` | 更改设备序列码 | 是 | 0x09 |
| `dev_info_query` | 查询设备信息 | 是 | 0x10 |
| `ssc_net_set` | 设置 SSC 网络配置 | 是 | 0x11 |
| `ssc_net_query` | 查询 SSC 网络配置 | 是 | 0x12 |
| `iot_net_set` | 设置 IoT 网络配置 | 是 | 0x13 |
| `iot_net_query` | 查询 IoT 网络配置 | 是 | 0x14 |
| `iot_topic_set` | 设置设备 Topic | 是 | 0x15 |
| `iot_topic_query` | 查询设备 Topic | 是 | 0x16 |
| `pwd_set` | 设置设备密码 | 是 | 0x1D |
| `factory_reset` | 设备出厂初始化 | 是 | 0x1E |
| `device_reset` | 设备复位 | 是 | 0x1F |
| `loop_param_set` | 设置车检器多路参数 | 是 | 0x63 |
| `loop_param_query` | 读取车检器多路参数 | 是 | 0x64 |
| `report_config` | 设置主动上报 | 是 | 0xC5 |
| 主动推送 | | | |
| `loop_data` | 线圈传感数据(设备→客户端) | — | 0xC0 |
| `event_report` | 事件上报(设备→客户端,**客户端须应答**,见 §5.2) | — | — |
---
# 4 命令详情
## 4.1 验证设备密码 `pwd_verify`
> 连接后首个命令,鉴权通过后才能执行其他命令。
> 60 秒内未鉴权则断开连接。
**请求:**
```json
{"msg_id":1,"cmd":"pwd_verify","ts":1719000000,"data":{"password":"123456"}}
```
| data 字段 | 类型 | 说明 |
|-----------|------|------|
| `password` | string | 6 位数字密码 |
**响应(成功):**
```json
{"msg_id":1,"cmd":"pwd_verify","ts":1719000001,"code":0,"msg":"success"}
```
**响应(失败):**
```json
{"msg_id":1,"cmd":"pwd_verify","ts":1719000001,"code":2,"msg":"password incorrect"}
```
连续 3 次密码错误,设备断开连接并锁定 60 秒。
## 4.2 更改设备序列码 `dev_serial_set`
**请求:**
```json
{"msg_id":2,"cmd":"dev_serial_set","ts":1719000002,"data":{"dev_serial":"A1B2C3D4E5F6"}}
```
**响应:**
```json
{"msg_id":2,"cmd":"dev_serial_set","ts":1719000003,"code":0,"msg":"success"}
```
## 4.3 查询设备信息 `dev_info_query`
**请求:**
```json
{"msg_id":3,"cmd":"dev_info_query","ts":1719000004}
```
**响应:**
```json
{"msg_id":3,"cmd":"dev_info_query","ts":1719000005,"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}}}
```
## 4.4 设置 SSC 网络配置 `ssc_net_set`
**请求:**
```json
{"msg_id":4,"cmd":"ssc_net_set","ts":1719000006,"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}}
```
**响应:** 标准成功/失败。
## 4.5 查询 SSC 网络配置 `ssc_net_query`
**请求:**
```json
{"msg_id":5,"cmd":"ssc_net_query","ts":1719000008}
```
**响应:** `data` 字段同 4.4。
## 4.6 设置 IoT 网络配置 `iot_net_set`
**请求:**
```json
{"msg_id":6,"cmd":"iot_net_set","ts":1719000010,"data":{"host":"mqtt.example.com","port":1883,"client_id":"dld960_A1B2C3D4E5F6","username":"admin","password":"secret"}}
```
## 4.7 查询 IoT 网络配置 `iot_net_query`
**请求:**
```json
{"msg_id":7,"cmd":"iot_net_query","ts":1719000012}
```
## 4.8 设置设备 Topic `iot_topic_set`
**请求:**
```json
{"msg_id":8,"cmd":"iot_topic_set","ts":1719000014,"data":{"client_id_enable":true,"topic_pub":"dld960/data/A1B2C3D4E5F6","topic_sub":"dld960/cmd/A1B2C3D4E5F6"}}
```
## 4.9 查询设备 Topic `iot_topic_query`
**请求:**
```json
{"msg_id":9,"cmd":"iot_topic_query","ts":1719000016}
```
## 4.10 设置设备密码 `pwd_set`
**请求:**
```json
{"msg_id":10,"cmd":"pwd_set","ts":1719000018,"data":{"old_password":"123456","new_password":"654321"}}
```
## 4.11 设备出厂初始化 `factory_reset`
**请求:**
```json
{"msg_id":11,"cmd":"factory_reset","ts":1719000020}
```
> 执行后所有配置恢复出厂值,连接可能断开。
## 4.12 设备复位 `device_reset`
**请求:**
```json
{"msg_id":12,"cmd":"device_reset","ts":1719000022}
```
> 无响应,设备立即复位。
## 4.13 设置车检器多路参数 `loop_param_set`
**请求:**
```json
{"msg_id":13,"cmd":"loop_param_set","ts":1719000024,"data":{"auto_mode":false,"channels":[{"ch":1,"sensitivity":7,"freq_level":"high","loop_delay":0,"output_mode":"exist","exist_mode":0,"direction_mode":0,"safe_mode":0,"function_mode":0},{"ch":2,"sensitivity":7,"freq_level":"mid_high","loop_delay":5,"output_mode":"enter_pulse","exist_mode":10,"direction_mode":0,"safe_mode":0,"function_mode":0},{"ch":3,"sensitivity":5,"freq_level":"mid_low","loop_delay":0,"output_mode":"exist","exist_mode":0,"direction_mode":1,"safe_mode":5,"function_mode":0},{"ch":4,"sensitivity":8,"freq_level":"low","loop_delay":10,"output_mode":"leave_pulse","exist_mode":15,"direction_mode":0,"safe_mode":0,"function_mode":0}]}}
```
| `channels[]` 字段 | 类型 | 范围 | 说明 |
|-------------------|------|------|------|
| `ch` | uint8 | 1~4 | 通道号 |
| `sensitivity` | uint8 | 0~9 | 灵敏度,默认 7 |
| `freq_level` | string | 见下 | 线圈高低频档位 |
| `loop_delay` | uint8 | 0~200 | 延时 ×0.1s,最大 20s |
| `output_mode` | string | 见下 | 输出方式 |
| `exist_mode` | uint8 | 0~255 | 0=永久,非0=分钟 |
| `direction_mode` | uint8 | 0~6 | 0=触发,1~6=方向输出 |
| `safe_mode` | uint8 | 0~255 | 0=关闭,非0=分钟 |
| `function_mode` | uint8 | 0~15 | 功能模式 |
**freq_level 枚举:**
| 值 | 说明 |
|----|------|
| `"high"` | 高频 (33nF) |
| `"mid_high"` | 中高频 (43nF) |
| `"mid_low"` | 中低频 (66nF) |
| `"low"` | 低频 (76nF) |
**output_mode 枚举:**
| 值 | 说明 |
|----|------|
| `"exist"` | 存在输出 |
| `"enter_pulse"` | 进入脉冲 |
| `"leave_pulse"` | 离开脉冲 |
| `"direction"` | 方向判别 |
## 4.14 读取车检器多路参数 `loop_param_query`
**请求:**
```json
{"msg_id":14,"cmd":"loop_param_query","ts":1719000026}
```
**响应:**
```json
{"msg_id":14,"cmd":"loop_param_query","ts":1719000027,"code":0,"msg":"success","data":{"auto_mode":false,"channels":[{"ch":1,"sensitivity":7,"freq_level":"high","loop_delay":0,"output_mode":"exist","exist_mode":0,"direction_mode":0,"safe_mode":0,"function_mode":0,"freq_initial":105300,"freq_current":105280,"freq_diff":20},{"ch":2,"sensitivity":7,"freq_level":"mid_high","loop_delay":5,"output_mode":"enter_pulse","exist_mode":10,"direction_mode":0,"safe_mode":0,"function_mode":0,"freq_initial":100200,"freq_current":98700,"freq_diff":1500},{"ch":3,"sensitivity":5,"freq_level":"mid_low","loop_delay":0,"output_mode":"exist","exist_mode":0,"direction_mode":1,"safe_mode":5,"function_mode":0,"freq_initial":78100,"freq_current":0,"freq_diff":0},{"ch":4,"sensitivity":8,"freq_level":"low","loop_delay":10,"output_mode":"leave_pulse","exist_mode":15,"direction_mode":0,"safe_mode":0,"function_mode":0,"freq_initial":62100,"freq_current":62095,"freq_diff":5}]}}
```
| 额外字段 | 类型 | 单位 | 说明 |
|----------|------|------|------|
| `freq_initial` | uint32 | Hz | 初始频率 |
| `freq_current` | uint32 | Hz | 当前实时频率(线圈断开时为 0) |
| `freq_diff` | uint32 | Hz | 频率变化量 |
## 4.15 设置主动上报 `report_config`
**请求:**
```json
{"msg_id":15,"cmd":"report_config","ts":1719000028,"data":{"sensor_type":12,"enable":true,"once":false,"env_eval":false,"interval":5,"ack_required":false,"timeout":0}}
```
| data 字段 | 类型 | 说明 |
|-----------|------|------|
| `sensor_type` | uint8 | 传感器类型,12=多路线圈(0x0C) |
| `enable` | bool | 使能主动上报 |
| `once` | bool | 仅上报一次 |
| `env_eval` | bool | 环境评估模式 |
| `interval` | uint8 | 上报间隔(秒),0=实时 |
| `ack_required` | bool | 上报是否需要客户端确认 |
| `timeout` | uint8 | 超时(分钟),0=无限制 |
---
# 5 设备主动推送
主动推送帧与请求-响应共用同一条 TCP 连接,由设备在任意时刻发出。
## 5.1 线圈传感数据 `loop_data`
```json
{"msg_id":100,"cmd":"loop_data","ts":1719000100,"data":{"channels":[{"ch":1,"freq_level":"high","has_car":false,"loop_ok":true,"freq_current":105280,"freq_diff":20,"sensitivity":7,"condition":0,"misc":{"type":"time","value":0}},{"ch":2,"freq_level":"mid_high","has_car":true,"loop_ok":true,"freq_current":98700,"freq_diff":1500,"sensitivity":7,"condition":2,"misc":{"type":"time","value":350}},{"ch":3,"freq_level":"mid_low","has_car":false,"loop_ok":false,"freq_current":0,"freq_diff":0,"sensitivity":5,"condition":0,"misc":{"type":"cut_count","value":3}},{"ch":4,"freq_level":"low","has_car":false,"loop_ok":true,"freq_current":62095,"freq_diff":5,"sensitivity":8,"condition":0,"misc":{"type":"flow_count","value":128}}]}}
```
| 通道字段 | 类型 | 说明 |
|----------|------|------|
| `ch` | uint8 | 通道号 1~4 |
| `freq_level` | string | 高低频档位 |
| `has_car` | bool | 有车/无车 |
| `loop_ok` | bool | 线圈正常/断开 |
| `freq_current` | uint32 | 当前频率 (Hz) |
| `freq_diff` | uint32 | 频率变化量 (Hz) |
| `sensitivity` | uint8 | 灵敏度等级 |
| `condition` | uint8 | 环境评估值 |
| `misc.type` | string | `"time"` / `"cut_count"` / `"flow_count"` |
| `misc.value` | uint32 | 对应数值 |
## 5.2 事件上报 `event_report`
> 方向:设备 → 客户端;**⚠️ 客户端必须应答**(区别于 `loop_data` 的单向推送)
设备检测到事件时主动推送,非周期性。事件(进车/出车/线圈断开等)是**不可再生的关键数据**,TCP 送达仅保证客户端进程收到,不代表业务层已处理入库,因此引入**应用层应答 + 设备重发**机制闭环确认(与 IoT MQTT 协议 V1.04 §5.3 机制对称)。
**设备推送:**
```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` | 说明 | `value` |
|----------------|------|---------|
| `car_enter` | 车辆进入 | 0 |
| `car_leave` | 车辆离开 | 通过时间 (×50ms) |
| `loop_cut` | 线圈断开 | 0 |
| `loop_restore` | 线圈恢复 | 断开持续时长 (×50ms) |
**客户端应答(收到后必须立即回复):**
```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 次)。仍未确认 → 事件保留在待发队列,待**连接恢复(重新鉴权通过)或下一次事件触发**时合并上报;
4. 连接断开期间产生的事件同样进入待发队列;等待确认期间新产生的事件合并进下一包 `events` 数组(使用新 `msg_id`),单帧不超过帧长限制(4096 字节);
5. 待发队列建议深度 **≥16 条事件**,溢出时丢弃最旧事件(先保新鲜数据)。
**客户端要求:**
1. 收到 `event_report` **先落库、后应答**,应答即承诺数据已持久化;
2.`msg_id` 在近期窗口(建议 10 分钟)内去重——重复帧为设备未收到应答所致,**直接应答 `code=0`,不重复入库**(设备重启后 `msg_id` 从头递增,去重必须限定时间窗口;TCP 为点对点连接,无需 `dev_serial` 维度);
3. 应答帧保持最小集(不带 `data`),且与请求同为**单行紧凑 JSON + `\n` 结尾**。
---
# 6 交互示例
## 6.1 完整会话
```
>>> {"msg_id":1,"cmd":"pwd_verify","ts":1719000000,"data":{"password":"123456"}}
<<< {"msg_id":1,"cmd":"pwd_verify","ts":1719000001,"code":0,"msg":"success"}
>>> {"msg_id":2,"cmd":"dev_info_query","ts":1719000002}
<<< {"msg_id":2,"cmd":"dev_info_query","ts":1719000003,"code":0,"msg":"success","data":{...}}
>>> {"msg_id":3,"cmd":"loop_param_query","ts":1719000004}
<<< {"msg_id":3,"cmd":"loop_param_query","ts":1719000005,"code":0,"msg":"success","data":{...}}
>>> {"msg_id":4,"cmd":"report_config","ts":1719000006,"data":{"sensor_type":12,"enable":true,"interval":5}}
<<< {"msg_id":4,"cmd":"report_config","ts":1719000007,"code":0,"msg":"success"}
... (5 秒后设备主动推送) ...
<<< {"msg_id":100,"cmd":"loop_data","ts":1719000012,"data":{...}}
<<< {"msg_id":101,"cmd":"loop_data","ts":1719000017,"data":{...}}
<<< {"msg_id":102,"cmd":"event_report","ts":1719000020,"data":{"events":[{"type":"car_enter","ch":1,"value":0}]}}
>>> {"msg_id":102,"cmd":"event_report","ts":1719000021,"code":0,"msg":"success"}
```
---
# 7 帧解析说明
- 每个 JSON 对象为一行,以 `\n` (0x0A) 结尾
- 解析时按行读取,提取完整 JSON 后解析
- JSON 内部不含换行符(紧凑格式)
- 收到非 JSON 行则丢弃并返回错误
- 帧长超过 4096 字节则断开连接
---
# 修订记录
| 版本 | 修订时间 | 修订说明 | 修订人 |
|------|----------|----------|--------|
| V1.00 | 2026-06-22 | 初始版本,基于串口协议 V1.01 | wangfq |
| V1.01 | 2026-07-15 | `event_report` 增加**客户端必答**机制:应答格式(回显 `msg_id`)、设备 5s 超时重发(同 `msg_id`/`ts`,最多 3 次)、待发队列合并上报、客户端去重与先落库后应答要求(与 MQTT 协议 V1.04 对称) | wangfq |