# 实时故障显示需求说明

日期：2026-07-23

来源：`新需求.png`

## 需求目标

当前故障需要在实时监控页即时、准确、明确地显示出来，不能再依赖 10 分钟一次的历史扫描后才出现在报警预览中。

本次需求涉及四块：

1. 0x17 回路分控器参数解析调整
2. 实时监控页新增当前故障显示
3. 报警预览历史记录即时写入
4. 未定义回路不参与轮询

## 当前问题

### 实时监控页没有故障信息

当前实时监控页只展示回路开关状态、电压、电流、功率、调光值等信息，没有展示当前故障条目。

结果是：即使设备已经返回故障，用户也无法在实时监控页直接看到。

### 报警预览显示太慢

后台当前通过 `ScanM9z` 定时扫描故障，定时周期是 10 分钟：

```go
AddTaskByFunc("ScanM9z", "ScanM9z", "@every 10m", m9zTtyApi.ScanM9z)
```

所以用户看到“十几分钟后报警预览才有故障内容”，和当前代码行为一致。

### 故障文案不够准确

当前故障文案包含：

```text
欠压/短路故障
过压/开路故障
电源故障
未知异常
```

新需求要求按实际故障类型显示，不再混用“欠压/过压”描述。

### 回路电流解析需要调整

当前 0x17 解析中，回路总电流按 `0.1A` 处理。

新需求示例：

```text
01 BF = 0.447A
```

也就是 `0x01BF = 447`，应按 `0.001A` 解析。

## 当前故障显示区域

“当前故障显示区域”指实时监控页上一块专门展示当前故障的区域。

它不是历史报警列表，也不是报警预览页，而是当前设备正在发生的故障提示。

建议位置：

```text
实时监控页右上方空白区域
或实时监控页底部空白区域
```

建议显示内容：

```text
当前故障：
回路1短路故障
回路2开路故障
回路3设备1/2电源故障
```

没有故障时显示：

```text
当前无故障
```

建议在该区域增加一个手动刷新按钮：

```text
刷新当前故障
```

按钮用途：

1. 现场人员可以立即重新读取当前故障，不必等待下一次自动刷新
2. 调试和验收时可以快速确认故障是否已恢复
3. 当通信短暂异常或页面刚恢复时，可以主动刷新当前状态

交互建议：

1. 点击后调用实时监控接口重新读取当前故障
2. 按钮请求期间显示 loading 或禁用，避免连续点击造成串口请求堆积
3. 刷新成功后更新当前故障区域
4. 刷新失败时提示“当前故障刷新失败”或保留原状态并显示错误提示

每条故障建议包含：

| 字段 | 说明 |
| --- | --- |
| 回路号 | 例如：回路1、回路2 |
| 故障类型 | 短路故障、开路故障、电源故障 |
| 设备号 | 仅电源故障需要显示，例如：设备1、设备2 |
| 故障码 | 可选显示，例如：01、02、04 01、04 03 |

## 故障码解析规则

0x17 返回的故障码按以下规则解析：

| 故障码 | 含义 | 显示文案 |
| --- | --- | --- |
| `0x01` | 短路故障 | 回路n短路故障 |
| `0x02` | 开路故障 | 回路n开路故障 |
| `0x04 + 设备掩码` | 电源故障 | 回路n设备x电源故障 |

电源故障示例：

| 故障码 | 含义 | 显示文案 |
| --- | --- | --- |
| `04 01` | 设备1电源故障 | 回路n设备1电源故障 |
| `04 02` | 设备2电源故障 | 回路n设备2电源故障 |
| `04 03` | 设备1/2电源故障 | 回路n设备1/2电源故障 |

如果同一回路同时存在多类故障，应拆成多条故障显示，或合并为一条清晰文案。

## 收到故障无需确认

“收到故障无需确认”指设备返回故障数据后，系统直接显示并记录，不弹窗询问用户是否确认。

正确流程：

```text
设备返回故障 -> 后端解析 -> 实时监控显示 -> 写入报警历史
```

不需要：

```text
设备返回故障 -> 弹窗问用户是否确认 -> 用户确认后才显示/记录
```

报警预览页里“批量删除”的确认弹窗可以保留，因为它属于用户删除操作确认，不属于故障进入系统前的确认。

## 未定义设备不轮询

当前代码固定轮询 10 个回路：

```go
for i := 0; i < 10; i++ {
    // read 0x17
}
```

新需求要求未定义设备不参与轮询。

建议行为：

```text
配置了 4 路：只轮询 0~3
配置了 8 路：只轮询 0~7
配置了 10 路：轮询 0~9
```

好处：

1. 减少串口请求
2. 避免无效设备超时
3. 提升实时监控响应速度
4. 报警预览可以更快出现有效故障

如果暂时没有回路数量配置，建议新增配置项，例如：

```json
{
  "deviceInfo": {
    "loopCount": 10
  }
}
```

没有配置时默认 `10`，但现场可以按实际回路数量调整。

后端实时接口需要返回当前实际参与扫描的设备数，便于前端展示和现场核对当前扫描范围。

建议字段：

```json
{
  "scan_device_count": 4
}
```

字段含义：

| 字段 | 说明 |
| --- | --- |
| `scan_device_count` | 本次按配置实际参与 0x17 轮询的设备/回路数量 |

如果后续需要区分“参与扫描数量”和“成功读取数量”，可以再增加：

```json
{
  "scan_device_count": 4,
  "scan_success_count": 3
}
```

## 后端改动点

### 1. 调整 0x17 数据解析

文件：

```text
internal/library/m9z/m9z_SubLoopParameters.go
```

改动：

1. 回路总电流从 `0.1A` 比例调整为 `0.001A`
2. 保留 `AlarmStatus` 和 `AlarmCode`
3. 增加结构化故障列表，例如 `Faults []LoopFault`

建议结构：

```go
type LoopFault struct {
    LoopIdx   uint   `json:"loop_idx"`
    DeviceIdx uint   `json:"device_idx,omitempty"`
    Type      string `json:"type"`
    Message   string `json:"message"`
    Code      string `json:"code"`
}
```

### 2. 统一故障文案生成

建议新增公共函数，供实时接口和历史扫描共用：

```go
func BuildLoopFaults(loopIdx uint, alarmStatus uint16, alarmCode string) []LoopFault
```

输出示例：

```text
回路1短路故障
回路2开路故障
回路3设备1/2电源故障
```

### 3. 实时接口返回故障字段

文件：

```text
internal/module/m9zTtyApi/read.go
```

接口：

```text
/m9z/getDeviceStatus2
```

每个回路建议增加字段：

```json
{
  "scan_device_count": 4,
  "Loops": [
    {
      "has_fault": true,
      "fault_msg": "回路1短路故障",
      "fault_code": "01",
      "faults": []
    }
  ]
}
```

其中每个回路对象包含：

```json
{
  "has_fault": true,
  "fault_msg": "回路1短路故障",
  "fault_code": "01",
  "faults": []
}
```

顶层 `scan_device_count` 表示当前后端实际参与扫描的设备/回路数量。

同时把实时监控中的回路电流改为直接使用 0x17 的回路总电流字段，不再累加子模块电流。

### 4. 实时写入报警历史

文件：

```text
internal/module/m9zTtyApi/cron.go
```

当前历史报警只由 `ScanM9z` 10 分钟扫描写入。新需求应改为：

```text
实时接口读到故障 -> 立即写入 data/data.json
```

同时需要做去重，避免实时接口每次刷新都重复插入同一条故障。

建议去重维度：

```text
comm_uid + loop_idx + fault_code + fault_msg + 未处理状态
```

### 5. 轮询数量配置化

把以下固定循环：

```go
for i := 0; i < 10; i++ {
}
```

改成：

```go
for i := 0; i < loopCount; i++ {
}
```

`loopCount` 从配置读取，未配置默认 10。

实时接口返回值需要带上当前扫描数量：

```json
{
  "scan_device_count": 10
}
```

涉及位置：

```text
internal/module/m9zTtyApi/read.go
internal/module/m9zTtyApi/cron.go
```

## 前端改动点

### 1. 实时监控页增加当前故障区域

页面：

```text
实时监控
```

展示：

```text
当前故障：
回路1短路故障
回路2开路故障
```

无故障时：

```text
当前无故障
```

当前故障区域需要提供手动刷新按钮：

```text
刷新当前故障
```

点击后重新调用 `/m9z/getDeviceStatus2`，只刷新实时监控状态和当前故障显示，不需要弹确认框。

按钮请求期间应显示 loading 或禁用，避免连续点击造成重复串口请求。

### 2. 适配实时接口字段

调用接口：

```text
/m9z/getDeviceStatus2
```

读取字段：

```text
scan_device_count
Loops[].has_fault
Loops[].fault_msg
Loops[].fault_code
Loops[].faults
```

前端可在当前故障区域或调试信息中展示当前扫描数量，例如：

```text
当前扫描设备数：4
```

### 3. 报警预览保持历史列表

页面：

```text
报警预览
```

接口继续使用：

```text
/m9z/fault/list
```

后端实时写入后，用户刷新报警预览即可更快看到记录。

### 4. 删除确认保留

报警预览页的批量删除确认弹窗可以保留。

“收到故障无需确认”只针对故障进入系统，不针对删除操作。

## 建议实施顺序

1. 后端先完成 0x17 解析和故障文案统一
2. 后端改 `/m9z/getDeviceStatus2` 返回当前故障
3. 后端增加实时写入历史和去重
4. 后端增加 `loopCount` 配置
5. 前端实时监控页增加当前故障区域和“刷新当前故障”按钮
6. 前端适配接口字段
7. 联调真实 0x17 报文：`01`、`02`、`04 01`、`04 02`、`04 03`

## 待确认点

1. `loopCount` 放在哪个配置节点：建议放在 `deviceInfo.loopCount`
2. 电源故障的设备掩码最大支持几个设备：目前图片只展示设备1、设备2
3. 同一回路多故障时前端展示为多行，还是合并为一行
