# 电子线架系统 - 功能需求与MQTT协议设计

> **文档版本**: v2.0  
> **更新时间**: 2026-06-17  
> **修订说明**: 统一字段语义、修正状态机逻辑、对齐MODBUS-RTU协议细节、明确DTU实现边界、补充每个字段的可解释性说明。

---

## 0. 文档约定

- **必须 / 应当 / 可选**：分别表示强制要求、规范要求、可选能力。
- 所有 `id` 类字段均为字符串，由生成方保证**业务范围内唯一**，不强制全局唯一。
- 所有 `timestamp` 字段均为 Unix 毫秒时间戳（`long`），由 DTU 或业务系统生成时取本地 UTC 毫秒值。
- 所有 `uid` 字段均为**大写十六进制字符串，不含分隔符**。
- 本文档是 DTU 与上层业务系统、配线架节点之间的**协议与行为约定**；源码实现若与本文档冲突，以本文档为修正目标。

---

## 1. 术语定义

| 术语 | 定义 | 数据类型/范围 |
|------|------|---------------|
| **DTU** | 数据采集与转发控制器，连接配线架与上层 MQTT 业务系统 | - |
| **配线架（节点/Panel）** | 带 RFID 读卡器和 LED 的从机设备，通过 RS485 级联 | Modbus 地址 1~247 |
| **端口（Port）** | 配线架上的一个网口/光纤芯位，对应一个 RFID 天线 | 1~24（网络配线架） |
| **跳线（Jumper）** | 两端带 RFID 芯片的线缆，插入端口后被识别 | - |
| **jumper_uid** | 跳线 RFID 芯片的 8 字节 UID | 16 位大写十六进制字符串 |
| **panel_uid** | 配线架模块出厂唯一标识，用于地址配置阶段 | 24 位大写十六进制字符串（12 字节） |
| **address** | Modbus 从机地址，DTU 分配并持久化 | 1~247 |
| **position** | 配线架在 RS485 级联链路中的**物理顺序**，从 1 开始递增 | 1~128（实际部署上限） |
| **panel_id** | 业务系统侧为配线架分配的逻辑 ID | 字符串，如 `PANEL_001` |
| **expected_jumper_uid** | 平台期望插在该端口的跳线 UID，通过 `SYNC_*` 指令下发 | 16 位大写十六进制字符串或 `null` |
| **actual_jumper_uid** | DTU 轮询时实际读到的跳线 UID | 16 位大写十六进制字符串或 `null` |

> **为什么区分 `panel_uid` 与 `jumper_uid`？**  
> `panel_uid`（12 字节）标识**配线架硬件本身**，仅在地址配置阶段使用；`jumper_uid`（8 字节）标识**跳线**，通过 0x03 读寄存器获取。两者用途不同，长度也不同，避免混淆。

---

## 2. 系统架构

```
┌─────────────────────────────────────────────────────────────────────┐
│                        上层业务系统 (MQTT Broker 后端)                │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│   │  设备管理     │  │  告警/工单    │  │  Web/APP 数据展示         │  │
│   │  (SYNC/注册)  │  │  (规则引擎)   │  │  (状态/事件/历史)         │  │
│   └──────────────┘  └──────────────┘  └──────────────────────────┘  │
└─────────────────────────────┬───────────────────────────────────────┘
                              │ MQTT (TCP/IP)
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│                           DTU 控制器                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │  MQTT 客户端  │  │  端口状态机   │  │  MODBUS-RTU 主站          │  │
│  │  (上报/接收)  │  │  (基线/事件)  │  │  (RS485 轮询)             │  │
│  └──────────────┘  └──────────────┘  └──────────────────────────┘  │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │  地址自动配置  │  │  环境传感器   │  │  Web 管理界面             │  │
│  │  (广播+分配)  │  │  (I2C/RS485) │  │  (本地配置)               │  │
│  └──────────────┘  └──────────────┘  └──────────────────────────┘  │
│  ┌──────────────┐  ┌───────────────────────────────────────────┐  │
│  │  串口屏显示   │  │  调试/维护接口 (TTL/USB)                  │  │
│  │  (内部显示)   │  │  (固件升级/日志/现场维护)                  │  │
│  └──────────────┘  └───────────────────────────────────────────┘  │
│  接口：1×以太网、1×RS485(TTL 转差分)、内部串口屏 UART、调试/维护接口   │
└─────────────────────────────┬───────────────────────────────────────┘
                              │ MODBUS-RTU over RS485 (9600bps)
                              ▼
┌─────────────────────────────────────────────────────────────────────┐
│                      配线架节点 (RS485 级联)                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │  24 个端口   │  │  RFID 读卡器  │  │  RGB LED 指示灯           │  │
│  │  (天线×24)   │  │  (读 jumper) │  │  (按端口控制)             │  │
│  └──────────────┘  └──────────────┘  └──────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
```

### 2.1 接口说明

| 接口 | 用途 | 备注 |
|------|------|------|
| 以太网 | 连接交换机，接入 MQTT Broker | DTU 必选 |
| RS485 | 连接配线架级联链路 | 由 TTL 经板载 RS485 收发器转换 |
| 内部串口屏 UART | 驱动 DTU 内置串口屏，显示端口状态 | 内部显示模块，不对外暴露接口 |
| 调试/维护接口 | 固件升级、日志导出、现场维护 | TTL 或 USB，独立于 RS485 |

> **关于串口屏的说明**  
> 串口屏是 DTU 内部显示模块的一种实现方式，用于本地展示端口接入状态，不连接外部配线架。RS485 接口专用于配线架级联链路，二者使用独立的 UART，不存在接口复用问题。

---

## 3. 配线架（节点）功能

> 跳线实现方式：在跳线接头内部嵌入 RFID 标签；配线架端口处布置 RFID 天线，用于检测跳线是否插入并读取其 UID。

### 3.1 网络配线架（24 口）

| 功能项 | 功能描述 | 实现位置 | 协议/寄存器 |
|--------|----------|----------|-------------|
| 端口连接检测 | 通过 RFID 天线感知跳线是否接入 | 配线架-读卡器 | 0x03 读天线寄存器 |
| RFID 识别 | 读取跳线上 RFID 芯片的 8 字节 UID | 配线架-读卡器 | 0x0002+(n-1)×4 |
| LED 指示灯 | 按端口显示红闪/绿闪/蓝闪/灭状态 | 配线架-LED 驱动 | 0x0001 写寄存器 |
| 地址配置 | 接收 DTU 广播并获取 Modbus 地址 | 配线架-主控 | 0x40/0x41/0x42/0x43 |

### 3.2 光纤配线架（48 芯 LC）

| 功能项 | 功能描述 | 备注 |
|--------|----------|------|
| 端口连接检测 | 通过 RFID 检测跳线是否接入 | 每芯对应一个 RFID 天线 |
| RFID 识别 | 读取跳线上的 RFID 卡号 | 8 字节 UID |
| LED 指示灯 | 显示端口状态 | 控制方式与网络配线架相同 |

> 光纤配线架 48 芯可视为“2 个逻辑 24 口单元”或独立 48 口单元；本文档以 24 口为基本单元描述，48 芯实现需在寄存器映射或地址分配上做扩展。

### 3.3 跳线

| 功能项 | 功能描述 | 数据 |
|--------|----------|------|
| RFID 标签 | 网线/光纤接头内嵌 RFID 芯片 | - |
| 唯一标识 | 8 字节 UID | 16 位大写十六进制字符串 |
| 源/目标属性 | 由业务系统根据端口映射关系推导 | 不存储在跳线标签中 |

> **为什么不把源/目标属性写入跳线？**  
> 跳线 UID 是只读出厂标识；源/目标端口会随业务变更。若写入跳线，每次变更都需重新写标签，成本高且易出错。因此源/目标由平台根据“哪个端口读到哪个 UID”动态维护。

---

## 4. DTU 控制器功能

### 4.1 核心功能

| 功能项 | 功能描述 | 状态 |
|--------|----------|------|
| **端口检测** | 轮询配线架，根据 UID 变化判断接入/断开/移动 | ✅ 已实现 |
| **串口屏显示** | 通过 DTU 内置串口屏展示端口状态 | ✅ 已实现 |
| **MODBUS 通讯** | 通过 RS485 管理最多 128 台配线架 | ✅ 已实现 |
| **MQTT 对接** | 将配线架数据上报到云端/业务系统 | ✅ 已实现 |
| **固件升级** | 支持远程 OTA 升级 | ✅ 已实现 |
| **Web 管理界面** | 本地 HTTP 配置页面 | ✅ 已实现 |
| **地址自动配置** | 广播发现未配置面板并分配地址 | ✅ 已实现 |

> 管理数量说明：Modbus 地址范围 1~247，受 RS485 总线负载、轮询周期和供电能力限制，**单台 DTU 实际部署上限为 128 台配线架**。

### 4.2 其他功能

| 功能项 | 功能描述 | 状态 | 备注 |
|--------|----------|------|------|
| 温湿度传感器 | 机柜内部环境监测 | ✅ 已实现 | I2C 或 RS485 传感器 |
| 显示屏接口 | 预留显示屏插口，可接手机/平板 | ✅ 已实现 | 通过独立串口或网络 |
| 备用 RS485 接口 | 主板预留第二路 RS485 | ✅ 已实现 | 用于扩展传感器或其他从机 |

### 4.3 硬件规格

| 参数 | 规格 | 说明 |
|------|------|------|
| 网口 | 1 个 RJ45 | 接交换机/路由器 |
| RS485 接口 | 1 路（板载 TTL 转 RS485） | 接配线架级联链路 |
| 内部串口屏 UART | 1 路（内部） | 驱动内置显示模块，不占用 RS485 |
| 调试/维护接口 | 1 路 TTL/USB | 固件升级与现场维护，独立于 RS485 |
| 管理数量 | 理论 247 台，实际部署上限 128 台 | 受轮询周期和总线性能限制 |
| 传输距离 | 供电稳定时 300~500 米 | RS485 理论值，实际与线径/负载有关 |
| 芯片方案 | 国产化芯片 | - |

---

## 5. 端口检测逻辑（DTU 上实现）

### 5.1 检测流程

```
DTU 启动
   │
   ▼
加载已保存的 address↔panel_uid 映射
   │
   ▼
周期性轮询各配线架（地址 1~N）
   │
   ▼
读取每个端口天线寄存器 → 得到 actual_jumper_uid
   │
   ▼
与历史状态（last_jumper_uid）对比
   │
   ▼
生成事件（CONNECT / DISCONNECT / MOVE）
   │
   ▼
若存在 expected_jumper_uid，再生成非法告警
   │
   ▼
通过 MQTT 上报事件/告警，更新 last_jumper_uid
```

### 5.2 端口状态定义

| 状态值 | 含义 | 触发条件 |
|--------|------|----------|
| `DISCONNECTED` | 端口空闲 | 天线未读到任何有效 UID |
| `CONNECTED` | 端口已接入跳线 | 读到有效 UID，且未同步期望或期望与实际一致 |
| `ILLEGAL` | 端口状态异常 | 读到有效 UID，但已同步期望且期望与实际不一致 |
| `UNKNOWN` | 读卡失败 | 轮询超时、CRC 失败、寄存器返回异常 |

> `UNKNOWN` 状态的端口上报时，`status = "UNKNOWN"` 且 `jumper_uid = null`，不得使用 `"UNKNOWN"` 作为 UID 字符串。

### 5.3 事件判断状态机

DTU 为每个端口维护两条基线：
- `last_jumper_uid`：上一次轮询读到的 UID，用于检测物理变化。
- `expected_jumper_uid`：平台通过 `SYNC_*` 指令下发的期望 UID，用于非法告警判断。

| 当前读到 UID（actual） | 历史 UID（last） | 事件类型 | 说明 |
|------------------------|------------------|----------|------|
| 有效 UID | `null`/全 0 | `CONNECT` | 新跳线接入 |
| `null`/全 0 | 有效 UID | `DISCONNECT` | 跳线拔出 |
| 有效 UID A | 有效 UID B，且 A ≠ B | `MOVE` | 跳线被更换（旧 B 拔出，新 A 接入） |
| 有效 UID A | 有效 UID A | `NONE` | 无变化，不上报事件 |
| 读卡失败 | 任意 | `READ_ERROR` | 仅记录日志，不上报为端口事件 |

> **关键说明**：
> 1. 事件只反映**物理状态变化**，不判断是否合法。
> 2. `MOVE` 事件在单轮 poll 内同时发生“拔出旧线”和“插入新线”时产生；若先拔后插跨越多轮，则先后产生 `DISCONNECT` 和 `CONNECT`。
> 3. 全 0 UID（如 `0000000000000000`）视为无效/未插线。
> 4. DTU 不上报 `NONE` 事件，避免垃圾消息。

### 5.4 非法告警判断（与事件分离）

在事件判断之后，DTU 再基于 `expected_jumper_uid` 做简单比对：

| actual_jumper_uid | expected_jumper_uid | 告警类型 | 说明 |
|-------------------|---------------------|----------|------|
| 有效 UID，且等于 expected | 有效 UID | 无告警 | 状态正常 |
| `null` | 有效 UID | `ILLEGAL_DISCONNECT` | 期望有跳线，实际没有 |
| 有效 UID，且不等于 expected | 有效 UID | `ILLEGAL_CONNECT` | 期望没有该跳线/期望其他 UID |
| 有效 UID A，且 A ≠ expected（无论 last 为何值） | 有效 UID | `ILLEGAL_CONNECT` | 平台未同步该变化，统一视为非法接入 |

> **为什么不用 `ILLEGAL_MOVE` 单独告警？**  
> DTU 只掌握单端口信息，无法判断是“移动”还是“误插”。业务系统收到 `ILLEGAL_CONNECT` 后，可结合相邻端口事件或工单规则判断是否为移动。DTU 保持简单，只做“是否等于期望”的二元判断。

### 5.5 告警与事件的关系

- **事件（EVENT）**：反映物理变化，必须上报。
- **告警（ALARM）**：反映与期望映射的偏离，仅在平台已下发 `expected_jumper_uid` 时产生。
- 一次状态变化可能同时产生一个事件和一个告警（例如非法接入）。
- 平台侧告警规则引擎（如连续多次异常、跨端口关联分析）不在 DTU 实现。

---

## 6. DTU 与配线架间协议（MODBUS-RTU）

### 6.1 物理拓扑

```
DTU
 │
 │ 板载 TTL→RS485 转换
 │
 ▼
RS485 总线（A/B 双绞线）
 │
 ├── 配线架#1 (address=1, position=1)
 ├── 配线架#2 (address=2, position=2)
 ├── 配线架#3 (address=3, position=3)
 └── ...
```

- DTU 作为 Modbus 主站，配线架作为从站。
- 配线架之间通过 RS485 **串联级联**（daisy chain）。
- DTU 通过从机地址区分不同配线架。
- 一个 DTU 不直接并联多个 RS485 总线；所有配线架共享一条总线。

### 6.2 通讯参数

| 参数 | 值 | 说明 |
|------|-----|------|
| 协议 | 标准 MODBUS-RTU | - |
| 物理层 | RS485（半双工） | 板载 TTL→RS485 收发器 |
| 波特率 | 9600 bps | 固定 |
| 数据位 | 8 位 | - |
| 校验 | 无（None） | - |
| 停止位 | 1 位 | - |
| CRC | CRC-16 | 低字节在前，高字节在后（小端发送） |
| 数据格式 | 16 位寄存器 | 高字节在前，低字节在后（大端） |

### 6.3 功能码

| 功能码 | 功能 | 说明 |
|--------|------|------|
| `0x03` | 读保持寄存器 | 读取端口卡号 |
| `0x06` | 写单个寄存器 | 控制 LED 灯状态 |
| `0x40` | 地址配置-广播查询 | 自定义功能码，非标准 Modbus |
| `0x41` | 地址配置-从机应答 | 自定义功能码 |
| `0x42` | 地址配置-分配地址 | 自定义功能码 |
| `0x43` | 地址配置-分配确认 | 自定义功能码 |
| `0x44` | 地址配置-地址确认 | 自定义功能码，用于运行期保活 |

### 6.4 寄存器地址映射

| 地址 | 功能 | 长度 | 说明 |
|------|------|------|------|
| `0x0000` | 保留 | 1 寄存器 | 系统保留，禁止占用或读写 |
| `0x0001` | LED 控制 | 1 寄存器 | 高字节=灯号 1~24，低字节=颜色模式 |
| `0x0002` | 天线 1 卡号 | 4 寄存器（8 字节） | 端口 1 的 jumper_uid |
| `0x0006` | 天线 2 卡号 | 4 寄存器（8 字节） | 端口 2 的 jumper_uid |
| `0x000A` | 天线 3 卡号 | 4 寄存器（8 字节） | 端口 3 的 jumper_uid |
| ... | ... | ... | 通项：`0x0002 + (n-1)×4` |
| `0x005E` | 天线 24 卡号 | 4 寄存器（8 字节） | 端口 24 的 jumper_uid |

> 天线 n 寄存器地址公式：`reg_addr(n) = 0x0002 + (n - 1) × 4`，其中 n ∈ [1, 24]。

### 6.5 LED 控制寄存器（0x0001）

LED 控制使用**单个 16 位寄存器**，格式为：

```
值 value = (led_number << 8) | color_mode
```

| 字段 | 位数 | 含义 | 取值 |
|------|------|------|------|
| `led_number` | 高 8 位 | 要控制的 LED 编号 | 1~24 |
| `color_mode` | 低 8 位 | 颜色/闪烁模式 | 见下表 |

**颜色模式定义：**

| color_mode | 模式 | 说明 |
|------------|------|------|
| `0x00` | 灭 | 关闭该 LED |
| `0x01` | 红灯闪烁 | - |
| `0x02` | 绿灯闪烁 | - |
| `0x03` | 蓝灯闪烁 | - |

> 当前协议仅支持 00/01/02/03 四种模式。DTU 下发时 `color_mode` 必须在此范围内，不得使用其他值。

**示例：**

- 让 2 号灯红灯闪烁：`01 06 00 01 02 01 18 AA`
- 让 2 号灯绿灯闪烁：`01 06 00 01 02 02 58 AB`
- 让 6 号灯绿灯闪烁：`01 06 00 01 06 02 5A 6B`

> 旧文档中 LED 值 `0x01 / 0x02 / 0x06 / 0x0B / 0x12 / 0x15` 是“灯号+模式”的组合值，不能单独作为模式使用。DTU 下发时必须按 `(led_number << 8) | color_mode` 构造。

### 6.6 读卡号示例

请求天线 1 卡号：
```
01 03 00 02 00 04 E5 C9
```
- `01`：从机地址
- `03`：功能码
- `00 02`：起始寄存器地址
- `00 04`：读取 4 个寄存器（8 字节）
- `E5 C9`：CRC16（低字节在前）

正常响应：
```
01 03 08 32 E5 95 06 09 01 04 E0 67 C5
```
- `01`：从机地址
- `03`：功能码
- `08`：数据字节数
- `32 E5 95 06 09 01 04 E0`：8 字节 jumper_uid
- `67 C5`：CRC16

> 将 8 字节转换为大写十六进制字符串：`32E59506090104E0`。

### 6.7 地址配置流程

#### 6.7.1 阶段划分

| 阶段 | 配线架行为 | DTU 行为 |
|------|------------|----------|
| 启动 30 秒等待 | 监听总线上的确认/广播 | 发送确认指令给已保存设备 |
| 运行期 | 使用已分配地址响应 0x03/0x06 | 正常轮询 |
| 配置期 | 未配置设备响应广播 | 发现新设备并分配地址 |

#### 6.7.2 运行期地址确认（保活）

DTU 每隔 10 秒对已保存的 `address↔panel_uid` 映射发送确认指令：

```
请求：add + 0x44 + 0x00 + 12 字节 panel_uid + CRC_L + CRC_H
```

示例：
```
01 44 00 18 00 40 00 14 00 00 59 59 54 30 56 3C 8F
```

- `01`：已分配地址
- `44`：自定义功能码
- `00`：子命令
- `18 00 40 00 14 00 00 59 59 54 30 56`：12 字节 panel_uid
- `3C 8F`：CRC16

面板收到后校验地址与 panel_uid 是否匹配，匹配则进入/保持运行状态。

#### 6.7.3 新设备发现与地址分配

当 DTU 需要发现未配置面板时，发送广播查询：

```
请求：00 40 00 40 00
```

- `00`：广播地址
- `40`：自定义功能码
- `00 40 00`：固定字段
- 无 CRC（固定 5 字节）

未配置面板应答：
```
00 41 12 字节 panel_uid CRC_L CRC_H
```

示例：
```
00 41 18 00 40 00 14 00 00 59 59 54 30 56 AE 34
```

> **重要：多设备同时应答会产生总线冲突。** 首次部署或新增面板时，须单台上电、逐台完成地址配置；禁止多台未配置面板同时上电。DTU 在配置窗口期内仅处理第一个有效应答，后续设备须断电重上电后再次进入配置流程。

DTU 分配地址：
```
00 42 12 字节 panel_uid add CRC_L CRC_H
```

示例：
```
00 42 18 00 40 00 14 00 00 59 59 54 30 56 01 77 7F
```

- `01`：分配给该面板的 Modbus 地址

面板应答：
```
add 43 00 CRC_L CRC_H
```

示例：
```
01 43 00 11 30
```

#### 6.7.4 position 的确定

`position` 不是 Modbus 地址，而是配线架在 RS485 级联链路中的**物理顺序**。确定方式：

1. DTU 第一次成功为某 panel_uid 分配 Modbus 地址时，按**分配成功的先后顺序**赋予 position。
2. 第一台分配成功的面板 `position = 1`，第二台 `position = 2`，依次递增。
3. DTU 将 `panel_uid → address → position` 持久化到本地配置。
4. 业务系统通过 DTU 注册消息中的 `panels` 数组获取 `position`，用于 UI 展示和物理定位。

> 若后期在链路中间插入新面板，后续面板的 position 顺延；已保存的 `position` 不自动重排。业务系统须通过 `SYNC_ALL_MAPPING` 或人工配置重新同步 position 与 panel_id 的映射关系。

---

## 7. DTU 与上层业务系统 MQTT 协议

### 7.1 主题设计

**根主题前缀：** `线架系统/`

`线架系统` 是本系统的 MQTT 根主题前缀，用于在同一 Broker 上与其他业务系统做命名空间隔离。部署时可根据实际项目命名统一修改（如改为 `wire_rack/` 或项目代号），但 DTU、业务系统、监控端必须使用同一根前缀，否则消息无法互通。

上行/单播/控制主题统一格式：

```
线架系统/{customer_id}/{entity}/{dtu_id}[/{panel_id}]/{message_type}
```

全局广播主题格式：

```
线架系统/broadcast/dtu/{message_type}
```

完整主题树：

```
线架系统/
├── {customer_id}/
│   ├── dtu/
│   │   ├── {dtu_id}/register          # DTU 注册（上行）
│   │   ├── {dtu_id}/status            # DTU 心跳/状态（上行）
│   │   ├── {dtu_id}/control           # 下行控制指令
│   │   └── {dtu_id}/response          # 下行指令响应（上行）
│   │
│   ├── patchpanel/
│   │   ├── {dtu_id}/{panel_id}/event  # 端口事件（上行）
│   │   ├── {dtu_id}/{panel_id}/alarm  # 非法告警（上行）
│   │   └── {dtu_id}/{panel_id}/status # 配线架状态（上行）
│   │
│   ├── jumper/
│   │   └── {dtu_id}/status            # 跳线状态汇总（上行，按 DTU 聚合）
│   │
│   └── env/
│       └── {dtu_id}/sensor            # 环境传感器数据（上行）
│
└── broadcast/
    └── dtu/
        ├── discover                   # 发现 DTU（下行广播）
        └── config                     # 批量配置 DTU（下行广播）
```

> 上行主题位于 `{customer_id}` 命名空间下实现多租户隔离；`broadcast/` 主题为全局广播，所有 DTU 均可接收，DTU 通过 payload 中的 `customer_id` 判断是否响应。QoS 表中的通配主题（如 `dtu/+/register`）仅为订阅/监控时使用，实际发布主题必须包含 `customer_id` 前缀。

### 7.2 通用消息格式

所有 MQTT 消息采用统一信封。遗嘱消息（LWT）因连接时固定载荷，允许省略 `msg_id` 和 `timestamp`，但须包含 `dtu_id` 和 `type`：

```json
{
  "msg_id": "uuid-string",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "REGISTER|STATUS|EVENT|ALARM|CONTROL|RESPONSE|DISCOVER|CONFIG",
  "payload": { }
}
```

**信封字段说明：**

| 字段 | 类型 | 生成方 | 说明 |
|------|------|--------|------|
| `msg_id` | string | 发送方 | 消息唯一标识，使用 UUID 或 `类型_随机串`，用于去重和关联响应 |
| `timestamp` | long | 发送方 | Unix 毫秒时间戳 |
| `dtu_id` | string | DTU/平台 | DTU 全局唯一标识；平台下发的全局广播消息固定填 `"PLATFORM"` |
| `type` | string | 发送方 | 消息类型，必须与主题语义一致 |
| `payload` | object | 发送方 | 具体业务数据 |

### 7.3 DTU 注册（上行）

**主题：** `线架系统/{customer_id}/dtu/{dtu_id}/register`

**触发时机：** DTU 上线、重连 MQTT、收到平台 `DISCOVER` 广播后。

**Payload：**

```json
{
  "msg_id": "reg_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "REGISTER",
  "payload": {
    "firmware_version": "v1.2.3",
    "hardware_version": "v2.0",
    "panel_count": 3,
    "network_type": "ethernet",
    "signal_strength": null,
    "uptime": 3600000,
    "panels": [
      {"panel_id": "PANEL_001", "address": 1, "position": 1},
      {"panel_id": "PANEL_002", "address": 2, "position": 2},
      {"panel_id": "PANEL_003", "address": 3, "position": 3}
    ]
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `firmware_version` | string | 是 | DTU 固件版本 |
| `hardware_version` | string | 否 | DTU 硬件版本 |
| `panel_count` | int | 是 | 当前已配置的配线架数量，必须与 `panels` 数组长度一致 |
| `network_type` | string | 是 | 网络类型：`ethernet`、`wifi`、`4g`、`5g` |
| `signal_strength` | int/null | 否 | 无线信号强度（dBm）；`ethernet` 时填 `null` |
| `uptime` | long | 是 | DTU 本次运行时长，单位毫秒 |
| `panels` | array | 是 | 已配置的配线架列表 |
| `panels[].panel_id` | string | 是 | 业务系统侧为该面板分配的逻辑 ID |
| `panels[].address` | int | 是 | Modbus 从机地址 |
| `panels[].position` | int | 是 | 物理级联顺序，从 1 开始 |

> `panel_id` 由 DTU 在地址配置阶段生成，默认规则为 `PANEL_{dtu_id}_{address}`（例如 `PANEL_DTU001_1`）。平台可通过 DTU Web 管理界面或本地配置文件预先指定 `panel_uid → panel_id` 映射；未指定时采用默认规则。`panel_id` 须持久化到本地，并在注册消息中上报。

### 7.4 DTU 状态/心跳（上行）

**主题：** `线架系统/{customer_id}/dtu/{dtu_id}/status`

**触发时机：**
- 周期性上报，默认 60 秒；
- `panel_online`、`panel_offline`、`mqtt_connected`、`rs485_status` 任一字段发生变化时立即上报；
- 收到平台 `QUERY_DTU_STATUS` 查询指令时立即上报（该指令通过 `dtu/{dtu_id}/control` 下发，命令名 `QUERY_DTU_STATUS`）。

**Payload：**

```json
{
  "msg_id": "hbt_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "STATUS",
  "payload": {
    "cpu_usage": 35,
    "memory_usage": 42,
    "temperature": 45,
    "network_status": "online",
    "panel_online": 3,
    "panel_offline": 0,
    "screen_connected": false,
    "mqtt_connected": true,
    "rs485_status": "normal"
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `cpu_usage` | int | 否 | CPU 使用率，0~100 |
| `memory_usage` | int | 否 | 内存使用率，0~100 |
| `temperature` | int/float | 否 | DTU 主板温度，单位摄氏度 |
| `network_status` | string | 是 | `online` / `offline` / `weak` |
| `panel_online` | int | 是 | 当前成功轮询的配线架数量 |
| `panel_offline` | int | 是 | 当前轮询失败/未应答的配线架数量 |
| `screen_connected` | bool | 否 | 串口屏是否连接 |
| `mqtt_connected` | bool | 是 | MQTT 客户端是否连接（冗余自检） |
| `rs485_status` | string | 是 | `normal` / `error` / `busy` |

### 7.5 端口事件（上行）

**主题：** `线架系统/{customer_id}/patchpanel/{dtu_id}/{panel_id}/event`

**触发时机：** 端口物理状态发生变化。

**Payload：**

```json
{
  "msg_id": "evt_002",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "EVENT",
  "payload": {
    "panel_id": "PANEL_001",
    "port_id": 5,
    "event_type": "CONNECT",
    "event_id": "evt_a1b2c3d4",
    "jumper_uid": "32E59506090104E0",
    "previous_jumper_uid": null
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `panel_id` | string | 是 | 配线架逻辑 ID |
| `port_id` | int | 是 | 端口号，1~24 |
| `event_type` | string | 是 | `CONNECT` / `DISCONNECT` / `MOVE` |
| `event_id` | string | 是 | 事件唯一标识，由 DTU 生成，格式 `evt_{短随机串}` |
| `jumper_uid` | string/null | 是 | 当前端口读到的 UID；`DISCONNECT` 时为 `null` |
| `previous_jumper_uid` | string/null | 是 | 变化前的 UID；`CONNECT` 时为 `null`；`MOVE` 时为旧 UID |

**事件类型取值规则：**

| event_type | jumper_uid | previous_jumper_uid | 含义 |
|------------|------------|---------------------|------|
| `CONNECT` | 新 UID | `null` | 新跳线接入 |
| `DISCONNECT` | `null` | 旧 UID | 跳线拔出 |
| `MOVE` | 新 UID | 旧 UID | 单轮检测内发生更换 |

> 删除旧文档中的 `port_status` 字段，避免与 `event_type` 语义重复。

### 7.6 非法告警（上行）

**主题：** `线架系统/{customer_id}/patchpanel/{dtu_id}/{panel_id}/alarm`

**触发条件：** 该端口已通过 `SYNC_PORT_MAPPING` 或 `SYNC_ALL_MAPPING` 收到非空的 `expected_jumper_uid`，且当前轮询结果与之不符。

**Payload：**

```json
{
  "msg_id": "alm_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "ALARM",
  "payload": {
    "panel_id": "PANEL_001",
    "port_id": 8,
    "alarm_type": "ILLEGAL_DISCONNECT",
    "severity": "WARNING",
    "expected_jumper_uid": "32E59506090104E0",
    "actual_jumper_uid": null,
    "description": "端口8期望跳线32E59506090104E0，实际未读到"
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `panel_id` | string | 是 | 配线架逻辑 ID |
| `port_id` | int | 是 | 端口号 |
| `alarm_type` | string | 是 | `ILLEGAL_CONNECT` / `ILLEGAL_DISCONNECT` |
| `severity` | string | 是 | `WARNING` / `CRITICAL`。首次产生该告警为 `WARNING`；同一端口同一告警类型连续 3 个轮询周期未恢复，升级为 `CRITICAL` |
| `expected_jumper_uid` | string | 是 | 平台期望的 UID |
| `actual_jumper_uid` | string/null | 是 | 实际读到的 UID；未读到为 `null` |
| `description` | string | 否 | 可读描述 |

**告警类型判定表：**

| actual_jumper_uid | expected_jumper_uid | alarm_type |
|-------------------|---------------------|------------|
| `null` | 有效 UID | `ILLEGAL_DISCONNECT` |
| 有效 UID，且 ≠ expected | 有效 UID | `ILLEGAL_CONNECT` |
| 有效 UID，且 = expected | 有效 UID | 无告警 |
| 任意 | `null` / 未同步 | 无告警 |

> **为什么只有两种告警类型？**  
> DTU 只掌握单端口信息，无法区分“非法移动”与“非法接入/拔出”。业务系统收到告警后，可结合端口事件、相邻端口状态、工单规则做进一步判断。

### 7.7 配线架状态（上行）

**主题：** `线架系统/{customer_id}/patchpanel/{dtu_id}/{panel_id}/status`

**触发时机：** 周期性心跳或状态查询响应。

**Payload：**

```json
{
  "msg_id": "pst_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "STATUS",
  "payload": {
    "panel_id": "PANEL_001",
    "position": 1,
    "address": 1,
    "online": true,
    "last_poll_time": 1718092795000,
    "ports": [
      {"port_id": 1, "status": "CONNECTED", "jumper_uid": "32E59506090104E0"},
      {"port_id": 2, "status": "DISCONNECTED", "jumper_uid": null},
      {"port_id": 3, "status": "CONNECTED", "jumper_uid": "32E59506090104A1"},
      {"port_id": 4, "status": "ILLEGAL", "jumper_uid": "32E59506090104FF"}
    ]
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `panel_id` | string | 是 | 配线架逻辑 ID |
| `position` | int | 是 | 物理级联顺序 |
| `address` | int | 是 | Modbus 地址 |
| `online` | bool | 是 | 最近一次轮询是否成功 |
| `last_poll_time` | long | 是 | 最近一次成功轮询的时间戳 |
| `ports` | array | 是 | 各端口当前状态 |
| `ports[].port_id` | int | 是 | 端口号 |
| `ports[].status` | string | 是 | `DISCONNECTED` / `CONNECTED` / `ILLEGAL` / `UNKNOWN` |
| `ports[].jumper_uid` | string/null | 是 | 当前读到的 UID；读卡失败时可填 `null` |

### 7.8 跳线状态汇总（上行）

**主题：** `线架系统/{customer_id}/jumper/{dtu_id}/status`

**触发时机：** 周期性汇总、平台 `QUERY_JUMPER_STATUS` 查询响应。

**Payload：**

```json
{
  "msg_id": "jmp_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "STATUS",
  "payload": {
    "panels": [
      {
        "panel_id": "PANEL_001",
        "position": 1,
        "ports": [
          {"port_id": 1, "status": "CONNECTED", "jumper_uid": "32E59506090104E0"},
          {"port_id": 2, "status": "DISCONNECTED", "jumper_uid": null}
        ]
      },
      {
        "panel_id": "PANEL_002",
        "position": 2,
        "ports": [
          {"port_id": 1, "status": "CONNECTED", "jumper_uid": "32E59506090104A1"}
        ]
      }
    ]
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `panels` | array | 是 | 该 DTU 下所有配线架状态 |
| `panels[].panel_id` | string | 是 | 配线架逻辑 ID |
| `panels[].position` | int | 是 | 物理级联顺序 |
| `panels[].ports` | array | 是 | 端口状态数组 |
| `panels[].ports[].port_id` | int | 是 | 端口号 |
| `panels[].ports[].status` | string | 是 | 端口状态 |
| `panels[].ports[].jumper_uid` | string/null | 是 | 跳线 UID |

> 旧文档此处只包含单个 `panel_id`，但主题为 `jumper/{dtu_id}/status`，语义应为 DTU 下全部面板汇总。新版改为 `panels` 数组。

### 7.9 环境传感器（上行）

**主题：** `线架系统/{customer_id}/env/{dtu_id}/sensor`

**Payload：**

```json
{
  "msg_id": "sen_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "STATUS",
  "payload": {
    "temperature": 28.5,
    "humidity": 65.2,
    "sensor_update_time": 1718092700000
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `temperature` | float | 否 | 温度，单位摄氏度 |
| `humidity` | float | 否 | 湿度，0~100 |
| `sensor_update_time` | long | 是 | 传感器最近一次采样时间戳；与信封 `timestamp` 区分，后者是 MQTT 发送时间 |

### 7.10 下行控制指令

**主题：** `线架系统/{customer_id}/dtu/{dtu_id}/control`

**通用 Payload：**

```json
{
  "msg_id": "ctrl_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "CONTROL",
  "payload": {
    "command": "SET_PORT_LED",
    "target": "PANEL_001",
    "params": {
      "port_id": 5,
      "led_mode": "BLINK_RED"
    }
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `command` | string | 是 | 命令名称 |
| `target` | string | 是 | 目标对象，取值见各命令说明 |
| `params` | object | 视命令而定 | 命令参数 |

**支持的命令列表：**

| command | 功能 | target | target 说明 | params | 响应方式 |
|---------|------|--------|-------------|--------|----------|
| `SET_PORT_LED` | 设置端口 LED | `panel_id` | 目标配线架 `panel_id` | `port_id`, `led_mode` | `response` 主题 |
| `READ_PANEL_STATUS` | 查询配线架状态 | `panel_id` / `all` | 单个 `panel_id` 或该 DTU 下所有配线架 | 无 | `patchpanel/.../status` |
| `QUERY_JUMPER_STATUS` | 查询跳线汇总 | `all` | 固定为 `all`，汇总该 DTU 下所有配线架 | 无 | `jumper/{dtu_id}/status` |
| `QUERY_ENV_SENSOR` | 查询环境数据 | `all` | 固定为 `all` | 无 | `env/{dtu_id}/sensor` |
| `SYNC_PORT_MAPPING` | 同步单端口期望映射 | `panel_id` | 目标配线架 `panel_id` | `port_id`, `jumper_uid` | `response` 主题 |
| `SYNC_ALL_MAPPING` | 批量同步期望映射 | `all` | 固定为 `all` | `mappings` 数组 | `response` 主题 |
| `QUERY_DTU_STATUS` | 查询 DTU 状态 | `dtu` | 固定为 `dtu` | 无 | `dtu/{dtu_id}/status` |
| `OTA_UPGRADE` | 远程固件升级 | `dtu` | 固定为 `dtu` | 见 7.16 节 | `response` 主题 |
| `REBOOT` | 重启 DTU | `dtu` | 固定为 `dtu`，表示重启本 DTU | 无 | `response` 主题 |

**LED 模式定义（与 MODBUS color_mode 对应）：**

| led_mode | MODBUS color_mode | 说明 |
|----------|-------------------|------|
| `OFF` | `0x00` | 灭 |
| `BLINK_RED` | `0x01` | 红灯闪烁 |
| `BLINK_GREEN` | `0x02` | 绿灯闪烁 |
| `BLINK_BLUE` | `0x03` | 蓝灯闪烁 |

> DTU 收到 `SET_PORT_LED` 后，将 `led_mode` 转换为 `color_mode`，再构造 `(port_id << 8) | color_mode` 写入 0x0001 寄存器。

### 7.11 指令响应（上行）

**主题：** `线架系统/{customer_id}/dtu/{dtu_id}/response`

**Payload：**

```json
{
  "msg_id": "rsp_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "RESPONSE",
  "payload": {
    "original_msg_id": "ctrl_001",
    "command": "SET_PORT_LED",
    "success": true,
    "result": {
      "panel_id": "PANEL_001",
      "port_id": 5,
      "led_mode": "BLINK_RED"
    },
    "error_code": 0,
    "error_message": null
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `original_msg_id` | string | 是 | 对应下行指令的 `msg_id` |
| `command` | string | 是 | 对应命令名称 |
| `success` | bool | 是 | 是否执行成功 |
| `result` | object/null | 是 | 命令执行结果；失败时为 `null` |
| `error_code` | int | 是 | 错误码，`0` 表示成功 |
| `error_message` | string/null | 是 | 错误描述；成功时为 `null` |

**错误码定义：**

| error_code | 含义 |
|------------|------|
| `0` | 成功 |
| `1001` | 未知命令 |
| `1002` | 目标面板不存在或离线 |
| `1003` | 端口号非法 |
| `1004` | LED 模式非法 |
| `1005` | MODBUS 通信失败 |
| `1006` | 参数缺失或格式错误 |
| `1007` | 同步映射失败 |
| `1011` | 配置版本已存在且未强制应用 |
| `1012` | 配置项取值越界 |
| `1013` | 配置项不支持 |
| `1014` | 配置写入持久化失败 |
| `1021` | 已是目标版本 |
| `1022` | 固件校验失败 |
| `1023` | 固件下载失败 |
| `1024` | 固件写入失败 |
| `1025` | 存储空间不足 |
| `9999` | 系统内部错误 |

### 7.12 QoS 与保留消息

| 消息类型 | 主题通配符 | QoS | Retain | 说明 |
|----------|------------|-----|--------|------|
| 注册 | `线架系统/+/dtu/+/register` | 1 | false | 上线时发送一次 |
| 状态/心跳 | `线架系统/+/dtu/+/status` | 1 | false | 周期性 |
| 事件 | `线架系统/+/patchpanel/+/+/event` | 1 | false | 状态变化时 |
| 告警 | `线架系统/+/patchpanel/+/+/alarm` | 1 | false | 异常时 |
| 控制 | `线架系统/+/dtu/+/control` | 1 | false | 平台下发 |
| 响应 | `线架系统/+/dtu/+/response` | 1 | false | 每条控制指令对应一条响应 |
| 环境 | `线架系统/+/env/+/sensor` | 0 | false | 允许偶发丢失 |
| 配线架状态 | `线架系统/+/patchpanel/+/+/status` | 0 | false | 周期性或查询响应 |
| 发现广播 | `线架系统/broadcast/dtu/discover` | 1 | false | 平台下发，触发 DTU 主动注册 |
| 批量配置广播 | `线架系统/broadcast/dtu/config` | 1 | false | 平台下发，统一调整 DTU 参数 |

> 旧文档将部分主题 QoS 设为 2。实际场景中事件/告警若 QoS 2 会对 Broker 造成较大压力，且业务系统可通过去重和状态补偿容忍偶发丢失，因此统一调整为 1。

### 7.13 心跳与遗言机制

| 项目 | 配置 | 说明 |
|------|------|------|
| 心跳间隔 | 60 秒 | `dtu/{dtu_id}/status` 周期性上报 |
| 超时阈值 | 180 秒 | 业务系统 180 秒未收到心跳视为离线 |
| 遗言主题 | `线架系统/{customer_id}/dtu/{dtu_id}/status` | MQTT LWT |
| 遗言 QoS | 1 | - |
| 遗言 Retain | false | - |
| 遗言消息 | `{"dtu_id":"DTU_001","type":"STATUS","payload":{"online":false,"reason":"CONNECTION_LOST"}}` | LWT 固定载荷，含 `dtu_id`、`type`、`payload` |

> 旧文档遗嘱消息缺少 `dtu_id` 等字段，会导致业务系统解析异常。新版遗嘱消息包含 `dtu_id`、`type`、`payload`，`msg_id` 和 `timestamp` 因 LWT 固定载荷特性可省略。

### 7.14 DTU 发现广播（下行/上行）

**主题：** `线架系统/broadcast/dtu/discover`

**方向：** 业务系统 → 所有 DTU（全局广播）

**触发时机：** 平台需要主动发现所有在线 DTU，例如首次部署、DTU 更换或心跳异常排查。

**Payload：**

```json
{
  "msg_id": "dsc_001",
  "timestamp": 1718092800000,
  "dtu_id": "PLATFORM",
  "type": "DISCOVER",
  "payload": {
    "request_id": "dsc_001",
    "customer_id": "CUST_001"
  }
}
```

> 平台下发的广播消息，`dtu_id` 固定填 `"PLATFORM"`，用于与 DTU 上行的注册/响应消息区分。

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | 是 | 发现请求标识，DTU 响应时须原样回传 |
| `customer_id` | string | 是 | 目标客户 ID；DTU 据此过滤。为空字符串时表示所有客户 |

**DTU 处理规则：**

1. DTU 成功连接 MQTT 后，必须立即订阅 `线架系统/broadcast/dtu/discover`。
2. 收到发现请求后，按以下规则过滤：
   - 若 `customer_id` 为空字符串，所有 DTU 必须响应；
   - 若 `customer_id` 等于本机配置的 `customer_id`，必须响应；
   - 其他情况，禁止响应。
3. 为避免大量 DTU 同时上报造成 Broker/平台压力，DTU 须在收到请求后随机等待 `[0, 3000]` 毫秒，再向自身注册主题发送一次注册消息。
4. 注册消息 `payload.discovery_request_id` 必须等于收到的 `request_id`。
5. 同一 `request_id` 在 60 秒内只允许响应一次，禁止重复上报。

**发现响应注册消息示例：**

```json
{
  "msg_id": "reg_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "REGISTER",
  "payload": {
    "firmware_version": "v1.2.3",
    "hardware_version": "v2.0",
    "panel_count": 3,
    "network_type": "ethernet",
    "signal_strength": null,
    "uptime": 3600000,
    "discovery_request_id": "dsc_001",
    "panels": [
      {"panel_id": "PANEL_001", "address": 1, "position": 1}
    ]
  }
}
```

> 发现广播仅作为辅助手段，日常在线状态以 DTU 主动注册和周期性心跳为准。平台须根据 `dtu_id` 对响应去重。

### 7.15 DTU 批量配置广播（下行/上行）

**主题：** `线架系统/broadcast/dtu/config`

**方向：** 业务系统 → 所有 DTU（全局广播）

**触发时机：** 平台需要统一调整所有 DTU 的运行参数，例如心跳间隔、轮询周期、MQTT 保活时间等。

**Payload：**

```json
{
  "msg_id": "cfg_001",
  "timestamp": 1718092800000,
  "dtu_id": "PLATFORM",
  "type": "CONFIG",
  "payload": {
    "customer_id": "CUST_001",
    "config_version": "2026061701",
    "force_apply": false,
    "items": {
      "heartbeat_interval": 60,
      "poll_interval_ms": 500,
      "modbus_timeout_ms": 1000,
      "mqtt_keepalive": 60,
      "alarm_enabled": true,
      "event_debounce_ms": 200
    }
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `customer_id` | string | 是 | 目标客户 ID；DTU 据此过滤。为空字符串时表示所有客户 |
| `config_version` | string | 是 | 配置版本号，DTU 持久化后用于幂等判断 |
| `force_apply` | bool | 否 | 是否强制应用，即使版本号与本地相同；默认 `false` |
| `items` | object | 是 | 具体配置项，key-value 形式 |
| `items.heartbeat_interval` | int | 否 | 心跳上报间隔，单位秒，取值范围 `[10, 3600]` |
| `items.poll_interval_ms` | int | 否 | Modbus 轮询间隔，单位毫秒，取值范围 `[100, 10000]` |
| `items.modbus_timeout_ms` | int | 否 | Modbus 单次请求超时，单位毫秒，取值范围 `[200, 5000]` |
| `items.mqtt_keepalive` | int | 否 | MQTT keepalive 间隔，单位秒，取值范围 `[10, 300]` |
| `items.alarm_enabled` | bool | 否 | 是否启用非法告警功能 |
| `items.event_debounce_ms` | int | 否 | 事件去抖时间，单位毫秒，取值范围 `[0, 5000]` |

> `items` 中未包含的配置项保持本地原值不变；包含的配置项覆盖本地值。

**DTU 处理规则：**

1. DTU 成功连接 MQTT 后，必须立即订阅 `线架系统/broadcast/dtu/config`。
2. 收到配置消息后，按以下规则过滤：
   - 若 `customer_id` 为空字符串，所有 DTU 必须处理；
   - 若 `customer_id` 等于本机配置的 `customer_id`，必须处理；
   - 其他情况，禁止处理。
3. 幂等判断：
   - 若 `config_version` 与本地最后应用版本相同且 `force_apply` 为 `false`，直接返回成功响应，不重复应用；
   - 若 `force_apply` 为 `true`，则重新校验并应用。
4. 参数校验：对 `items` 中每个配置项按取值范围校验；任一配置项越界或不被支持，整个配置拒绝应用，返回错误。
5. 原子应用：所有合法配置项一次性写入本地持久化存储；写入失败时保持原配置不变，返回错误。
6. 生效策略：

| 配置项 | 生效方式 | restart_required |
|--------|----------|------------------|
| `heartbeat_interval` | 立即生效，下一心跳周期按新值执行 | false |
| `poll_interval_ms` | 立即生效，下一 Modbus 轮询按新值执行 | false |
| `modbus_timeout_ms` | 立即生效，下一次 Modbus 请求按新值执行 | false |
| `alarm_enabled` | 立即生效 | false |
| `event_debounce_ms` | 立即生效 | false |
| `mqtt_keepalive` | 保存后生效，DTU 主动断开并重连 MQTT，无需整机重启 | false |

> 当前配置集中所有项均为运行期生效，不强制整机重启。若后续新增必须重启的配置项，须在本文档中明确标注 `restart_required: true`。
7. 响应延迟：DTU 收到配置后随机等待 `[0, 2000]` 毫秒再发送响应，避免响应洪峰。
8. 必须向 `线架系统/{customer_id}/dtu/{dtu_id}/response` 上报配置结果。

**配置响应示例：**

```json
{
  "msg_id": "rsp_cfg_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "RESPONSE",
  "payload": {
    "original_msg_id": "cfg_001",
    "command": "BROADCAST_CONFIG",
    "success": true,
    "result": {
      "applied_version": "2026061701",
      "applied_items": ["heartbeat_interval", "poll_interval_ms"],
      "restart_required": false
    },
    "error_code": 0,
    "error_message": null
  }
}
```

**错误码补充：**

| error_code | 含义 |
|------------|------|
| `1011` | 配置版本已存在且未强制应用 |
| `1012` | 配置项取值越界 |
| `1013` | 配置项不支持 |
| `1014` | 配置写入持久化失败 |

> 批量配置广播影响所有匹配 `customer_id` 的 DTU。平台下发前须在业务侧完成配置项合法性校验，并先通过单台 `control` 指令在小范围 DTU 上验证；验证通过后，方可广播批量推送。

### 7.16 远程固件升级（OTA）

**触发命令：** `OTA_UPGRADE`，通过 `线架系统/{customer_id}/dtu/{dtu_id}/control` 下发。

**Payload：**

```json
{
  "msg_id": "ota_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "CONTROL",
  "payload": {
    "command": "OTA_UPGRADE",
    "target": "dtu",
    "params": {
      "firmware_url": "https://ota.example.com/firmware/v1.2.4.bin",
      "firmware_version": "v1.2.4",
      "file_size": 1048576,
      "checksum": "a1b2c3d4e5f6...",
      "checksum_type": "SHA256",
      "force_upgrade": false
    }
  }
}
```

**字段说明：**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `firmware_url` | string | 是 | 固件下载地址，DTU 须支持 HTTP 或 HTTPS |
| `firmware_version` | string | 是 | 目标固件版本 |
| `file_size` | long | 是 | 固件文件字节数，用于下载完整性校验 |
| `checksum` | string | 是 | 固件文件校验值 |
| `checksum_type` | string | 是 | 校验算法：`MD5` / `SHA256` |
| `force_upgrade` | bool | 否 | 是否强制升级；`false` 时若本地版本已等于目标版本则跳过 |

**DTU 处理规则：**

1. 收到 `OTA_UPGRADE` 指令后，立即返回响应确认已收到任务（`success: true` 仅表示开始处理，不代表升级成功）。
2. 若 `force_upgrade` 为 `false` 且本地固件版本已等于 `firmware_version`，返回错误码 `1021`（已是目标版本）。
3. DTU 从 `firmware_url` 下载固件，下载过程中每完成 10% 进度或每 5 秒向 `dtu/{dtu_id}/status` 上报一次进度：
   - `payload.ota_status = "DOWNLOADING"`
   - `payload.ota_progress = 0~100`
4. 下载完成后按 `checksum_type` 计算并比对 `checksum`；不一致则向 `response` 主题返回错误码 `1022`（校验失败）。
5. 校验通过后写入备用固件区，写入完成后向 `response` 主题返回错误码 `0`，`result` 中标记 `restart_required: true`。
6. DTU 在写入成功后自动重启，使用新固件启动；重启后向 `dtu/{dtu_id}/register` 上报，其中 `firmware_version` 为新版本。
7. 若下载或写入失败，保持当前固件不变，向 `response` 主题返回对应错误码。

**OTA 响应示例（开始处理）：**

```json
{
  "msg_id": "rsp_ota_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "RESPONSE",
  "payload": {
    "original_msg_id": "ota_001",
    "command": "OTA_UPGRADE",
    "success": true,
    "result": {
      "firmware_version": "v1.2.4",
      "ota_status": "DOWNLOADING",
      "restart_required": false
    },
    "error_code": 0,
    "error_message": null
  }
}
```

**OTA 进度上报（DTU 状态主题）：**

```json
{
  "msg_id": "ota_status_001",
  "timestamp": 1718092800000,
  "dtu_id": "DTU_001",
  "type": "STATUS",
  "payload": {
    "ota_status": "DOWNLOADING",
    "ota_progress": 45,
    "firmware_version": "v1.2.4"
  }
}
```

> `ota_status` 取值：`IDLE` / `DOWNLOADING` / `VERIFYING` / `FLASHING` / `SUCCESS` / `FAILED`。

**错误码补充：**

| error_code | 含义 |
|------------|------|
| `1021` | 已是目标版本 |
| `1022` | 固件校验失败 |
| `1023` | 固件下载失败 |
| `1024` | 固件写入失败 |
| `1025` | 存储空间不足 |

> OTA 固件包须由平台签名或校验；DTU 下载前必须校验 `firmware_url` 域名白名单，禁止从非授权域名下载固件。

---

## 8. 功能实现对照表

### 8.1 配线架侧（节点）

| 功能 | 协议支持 | 状态 |
|------|----------|------|
| RFID 读卡 | ✅ 0x03 读寄存器 | 已实现 |
| LED 控制 | ✅ 0x06 写寄存器 | 已实现 |
| 地址自动配置 | ✅ 广播+分配流程 | 已实现 |

### 8.2 DTU 侧

| 功能 | 协议支持 | 状态 | 备注 |
|------|----------|------|------|
| 端口检测 | MODBUS 读卡号 → MQTT 事件上报 | ✅ 已实现 | - |
| 内存基线维护 | 端口 → last_jumper_uid + expected_jumper_uid | ✅ 已实现 | - |
| 非法告警 | expected vs actual 比对 → MQTT 告警上报 | ✅ 已实现 | 仅在已同步期望映射时触发 |
| MODBUS 轮询 | MODBUS-RTU | ✅ 已实现 | - |
| MQTT 上报 | 事件/告警/心跳/状态汇总/温湿度 | ✅ 已实现 | - |
| 单 DTU 控制指令 | `SET_PORT_LED` / `READ_PANEL_STATUS` / `QUERY_*` / `SYNC_*` / `REBOOT` / `QUERY_DTU_STATUS` / `OTA_UPGRADE` | ✅ 已实现 | 通过 `dtu/{dtu_id}/control` 下发 |
| 全局广播配置响应 | `DISCOVER` / `CONFIG` | ✅ 已实现 | 通过 `broadcast/dtu/*` 下发 |
| 地址自动配置 | 广播+分配流程 | ✅ 已实现 | 须按 6.7.3 节流程处理多设备冲突 |
| 串口屏显示 | 内部串口屏展示端口状态 | ✅ 已实现 | - |
| Web 管理界面 | HTTP 服务 | ✅ 已实现 | - |
| 固件升级 | OTA 协议 | ✅ 已实现 | - |
| 发现广播响应 | 订阅 `broadcast/dtu/discover` 并上报注册 | ✅ 已实现 | DTU 须按 customer_id 过滤 |
| 批量配置响应 | 订阅 `broadcast/dtu/config` 并应用配置 | ✅ 已实现 | 须支持版本幂等和校验 |
| 温湿度采集 | I2C/RS485 | ✅ 已实现 | - |

### 8.3 业务系统侧

| 功能 | 说明 |
|------|------|
| 设备注册 | 接收 DTU 注册，维护 `panel_id` 与 `customer_id` 映射关系 |
| 状态监控 | 接收心跳，维护在线列表，检测遗嘱消息 |
| 事件处理 | 接收端口事件，记录跳线变更历史 |
| 告警处理 | 接收 DTU 非法告警，结合自身规则引擎通知相关人员 |
| 映射同步 | 通过 `SYNC_*` 指令向 DTU 推送正确的端口映射基线 |
| 控制指令 | 下发控制命令，接收响应 |

---

## 9. 总结

| 层级 | 核心功能 | 协议 |
|------|----------|------|
| **配线架** | RFID 读卡 + LED 指示 | MODBUS-RTU |
| **DTU** | 端口检测 + 非法告警 + MQTT 上报 | MODBUS-RTU + MQTT |
| **业务系统** | 数据存储 + 告警处理 + Web 展示 | MQTT 订阅 |

### 功能覆盖

- ✅ 端口连接/断开检测（DTU 状态机）
- ✅ 跳线 RFID 识别（配线架 + MODBUS）
- ✅ LED 指示控制（配线架 + MODBUS，语义已统一）
- ✅ 串口屏显示（DTU 内部显示模块）
- ✅ 远程数据上报（DTU + MQTT）
- ✅ 远程控制指令（MQTT + DTU）
- ✅ 远程固件升级（OTA）
- ✅ 心跳与在线状态（MQTT LWT，格式已统一）
- ✅ 端口映射同步（`SYNC_*` 指令）
- ✅ 非法告警（DTU expected vs actual 比对）
- ✅ 温湿度监测（DTU 环境传感器）
- ✅ DTU 发现广播响应（`broadcast/dtu/discover`）
- ✅ DTU 批量配置响应（`broadcast/dtu/config`）

---

*文档版本: v2.0*  
*更新时间: 2026-06-17*  
*修订说明: 统一字段语义、修正状态机与 LED 控制、明确 DTU 与业务系统协议边界、补充每个字段释义。*
