# DoorMission 使用手册

## 目录
- [概述](#概述)
- [基本功能](#基本功能)
- [启动与停止](#启动与停止)
- [配置管理](#配置管理)
- [门控逻辑](#门控逻辑)
- [站点配置](#站点配置)
- [UI界面](#ui界面)
- [参数说明](#参数说明)
- [故障排查](#故障排查)
- [附录](#附录)

---

## 概述

`DoorMission` 是一个门控进程类，用于管理多个门控制器及其关联的门。它提供了以下核心功能：
- 多门控制器管理（支持不同类型）
- 自动门控逻辑（根据小车位置自动开关门）
- 手动控制功能（支持临时手动控制，优先级高于自动控制）
- 车辆占用管理（跟踪和清空门区域的车辆占用）
- 配置文件动态监控（支持热更新）
- 线程安全的门状态读写
- 可视化的配置和监控界面

### 架构特点
- **抽象化设计**：门控制器通过抽象基类 `BasicDoorController` 实现，支持扩展不同类型的控制器
- **线程安全**：门状态读取和控制写入分离，所有通信操作在门控制器内部线程完成
- **事件驱动**：与交通控制系统集成，响应小车进入/离开站点事件
- **热更新支持**：配置文件每10秒自动检查更新，无需重启任务
- **控制仲裁机制**：手动控制优先级高于自动控制，手动控制过期后自动恢复自动模式

---

## 基本功能

### 1. 门控制器管理

#### 1.1 门控制器类型
门控制器通过 `DoorTypeAttribute` 标记类型，系统会自动识别并创建实例。当前支持：
- **ModbusDoorController**：基于 Modbus TCP 的门控制器

#### 1.2 门控制器配置
每个门控制器包含以下配置：
- **Index**：控制器索引（唯一标识）
- **Ip**：IP地址
- **Port**：端口号（默认502）
- **Type**：控制器类型（通过 `DoorTypeAttribute.Name` 指定）
- **Doors**：门列表

#### 1.3 门配置
每个门包含以下配置：
- **Index**：门索引（在控制器内唯一）
- **ControlAddress**：开关控制信号地址（Modbus 线圈地址）
- **OpenStatusAddress**：开到位信号地址（Modbus 离散输入地址）

### 2. 自动门控逻辑

#### 2.1 门开启条件
门会在以下情况自动开启：
1. **小车即将进入区域**：当小车到达站点且站点的 `EnterDoor` 字段匹配时，门会在锁定前开启
2. **小车在区域内**：当小车已进入并锁定站点时，门保持开启状态

#### 2.2 门关闭条件
门会在以下情况自动关闭：
- 小车离开区域后，门自动关闭

#### 2.3 门标识符格式
站点配置中的门标识符格式为：**控制器索引.门索引**

**示例**：
```
"EnterDoor": "1.2"  // 表示控制器索引1，门索引2
"LeaveDoor": "2.3"  // 表示控制器索引2，门索引3
```

### 3. 配置文件动态监控

系统每10秒自动检查 `DoorConfig.json` 文件，并根据配置变化：
- **添加**：新增的门控制器会自动创建并连接
- **删除**：已移除的门控制器会自动断开并移除
- **修改**：已修改的门控制器会自动更新（IP、端口、类型或门配置变化）

---

## 启动与停止

### 1. 启动任务

```csharp
var doorMission = new DoorMission();
doorMission.Execute();  // 执行"启动进程"
```

**启动流程**：
1. 设置数据文件路径（`DoorConfig.json`）
2. 订阅交通控制事件（`BeforeLock`、`AfterLeave`、`OnLockAcquired`）
3. 立即加载一次配置（避免监控界面在首次轮询前无数据）
4. 启动配置监控任务（每10秒检查一次）
5. 启动门控逻辑监控任务（每500毫秒检查一次）

### 2. 停止任务

```csharp
doorMission.Stop();  // 执行"停止进程"
```

**停止流程**：
1. 取消事件订阅
2. 取消所有后台任务
3. 断开所有门控制器连接
4. 等待任务完成（最多等待5秒）

### 3. 公共方法

#### 3.1 读取门状态
```csharp
bool isOpen = doorMission.GetDoorState(controllerIndex, doorIndex);
```
- **参数**：
  - `controllerIndex`：门控制器索引
  - `doorIndex`：门索引
- **返回值**：`true`=打开，`false`=关闭

#### 3.2 设置门控制目标（自动模式）
```csharp
doorMission.SetDoorControlTarget(controllerIndex, doorIndex, open);
```
- **参数**：
  - `controllerIndex`：门控制器索引
  - `doorIndex`：门索引
  - `open`：`true`=打开，`false`=关闭

**注意**：此方法仅设置目标控制状态，实际通信由门控制器内部线程完成，确保线程安全。此方法会立即生效，但可能被手动控制覆盖。

#### 3.3 设置手动控制目标
```csharp
bool success = doorMission.SetManualDoorControl(controllerIndex, doorIndex, open, holdSeconds);
```
- **参数**：
  - `controllerIndex`：门控制器索引
  - `doorIndex`：门索引
  - `open`：`true`=打开，`false`=关闭
  - `holdSeconds`：手动保持秒数（可选，默认10秒）
- **返回值**：`true`=成功，`false`=失败（当有车辆占用且尝试关闭时返回false）

**功能说明**：
- 手动控制优先级高于自动控制
- 手动控制会在指定时间后自动过期，恢复自动模式
- **安全保护**：当门区域内有车辆占用时，禁止手动关闭门
- 手动控制过期后，系统自动恢复自动控制逻辑

#### 3.4 清除手动控制
```csharp
doorMission.ClearManualDoorControl(controllerIndex, doorIndex);
```
- **参数**：
  - `controllerIndex`：门控制器索引
  - `doorIndex`：门索引

**功能说明**：立即清除手动控制请求，恢复自动控制模式。

#### 3.5 清空车辆占用
```csharp
doorMission.ClearCarsInArea(controllerIndex, doorIndex);
```
- **参数**：
  - `controllerIndex`：门控制器索引
  - `doorIndex`：门索引

**功能说明**：清空指定门的车辆占用记录。清空后，如果门处于打开状态且没有其他小车需要进入，门会自动关闭。

#### 3.6 获取门控制状态
```csharp
var status = doorMission.GetDoorControlStatus(controllerIndex, doorIndex);
```
- **参数**：
  - `controllerIndex`：门控制器索引
  - `doorIndex`：门索引
- **返回值**：`DoorControlStatus` 对象，包含：
  - `Target`：当前目标状态（true=打开，false=关闭）
  - `Source`：控制来源（`ControlSource.Auto` 或 `ControlSource.Manual`）
  - `ManualRemainingSeconds`：手动控制剩余秒数（仅当Source=Manual时有效）
  - `CarsInArea`：车辆占用列表

#### 3.7 获取车辆占用情况
```csharp
var cars = doorMission.GetCarsInArea(controllerIndex, doorIndex);
```
- **参数**：
  - `controllerIndex`：门控制器索引
  - `doorIndex`：门索引
- **返回值**：车辆ID列表（`IReadOnlyList<int>`）

---

## 配置管理

### 1. 配置文件格式

配置文件 `DoorConfig.json` 位于程序根目录，格式如下：

```json
[
  {
    "Index": 1,
    "Ip": "192.168.1.100",
    "Port": 502,
    "Type": "ModbusDoorController",
    "Doors": [
      {
        "Index": 1,
        "ControlAddress": 0,
        "OpenStatusAddress": 0
      },
      {
        "Index": 2,
        "ControlAddress": 1,
        "OpenStatusAddress": 1
      }
    ]
  },
  {
    "Index": 2,
    "Ip": "192.168.1.101",
    "Port": 502,
    "Type": "ModbusDoorController",
    "Doors": [
      {
        "Index": 1,
        "ControlAddress": 0,
        "OpenStatusAddress": 0
      }
    ]
  }
]
```

### 2. 配置界面

通过调用 `DoorMission.OpenViewer()` 打开门控制器管理界面，可以：
- 添加、删除、修改门控制器
- 为每个门控制器添加、删除、修改门配置
- 保存配置到 `DoorConfig.json`

**打开配置界面**：
```csharp
DoorMission.OpenViewer();
```

---

## 门控逻辑

### 1. 事件响应流程

#### 1.1 BeforeLock 事件
当小车即将锁定站点时触发：
1. 检查站点的 `EnterDoor` 字段
2. 检查小车当前站点的 `PreEnterDoor` 字段是否匹配
3. 如果匹配，设置 `_needOpen[(controllerIndex, doorIndex)] = true`
4. 返回门的当前状态（如果门已打开则允许锁定）

#### 1.2 OnLockAcquired 事件
当小车成功锁定站点时触发：
1. 检查站点的 `EnterDoor` 字段
2. 检查小车当前站点的 `PreEnterDoor` 字段是否匹配
3. 如果匹配，将小车ID添加到 `carsInAreas[(controllerIndex, doorIndex)]`
4. 设置 `_needOpen[(controllerIndex, doorIndex)] = false`

#### 1.3 AfterLeave 事件
当小车离开站点时触发：
1. 检查站点的 `LeaveDoor` 字段
2. 检查小车当前站点的 `RearLeaveDoor` 字段是否匹配
3. 如果匹配，从 `carsInAreas[(controllerIndex, doorIndex)]` 中移除小车ID

### 2. 门控状态监控与仲裁

`MonitorDoorLogicAsync` 任务每500毫秒执行一次，检查每个门的控制逻辑并进行仲裁：

```csharp
// 自动目标：有车或需要打开
var needOpen = _needOpen.TryGetValue(key, out var open) && open;
var hasCarsInArea = carsInAreas.TryGetValue(key, out var cars) && cars.Count > 0;
var autoTarget = needOpen || hasCarsInArea;

// 手动请求仲裁：优先级 Manual > Auto，手动过期后自动恢复
bool finalTarget = autoTarget;
if (_manualRequests.TryGetValue(key, out var manual))
{
    if (manual.ExpireAt <= DateTime.Now)
    {
        _manualRequests.Remove(key);  // 手动控制过期，移除
    }
    else
    {
        finalTarget = manual.Target;  // 手动控制有效，使用手动目标
    }
}

// 设置门的目标控制状态
controller.SetDoorControlTarget(doorIndex, finalTarget);
```

**逻辑说明**：
- **自动目标计算**：
  - 如果 `_needOpen[key] = true`，门需要打开（小车即将进入）
  - 如果 `carsInAreas[key]` 中有小车，门需要保持打开（小车在区域内）
  - 其他情况，自动目标为关闭
- **控制仲裁**：
  - 手动控制优先级高于自动控制
  - 如果存在有效的手动控制请求（未过期），使用手动目标
  - 手动控制过期后，自动移除并恢复自动控制
  - 最终目标写入门控制器的 `DoorControlTargets` 字段

---

## 站点配置

### 1. 站点字段说明

站点需要配置以下字段以实现门控功能：

| 字段名 | 类型 | 说明 | 示例 |
|--------|------|------|------|
| `EnterDoor` | string | 进站门标识符（格式：控制器索引.门索引） | `"1.2"` |
| `LeaveDoor` | string | 离站门标识符（格式：控制器索引.门索引） | `"2.3"` |

### 2. 小车站点字段说明

小车当前站点需要配置以下字段：

| 字段名 | 类型 | 说明 | 示例 |
|--------|------|------|------|
| `PreEnterDoor` | string | 前方进站门标识符（与目标站点的 `EnterDoor` 匹配） | `"1.2"` |
| `RearLeaveDoor` | string | 后方离站门标识符（与目标站点的 `LeaveDoor` 匹配） | `"2.3"` |

### 3. 配置示例

**站点配置**：
```json
{
  "id": 100,
  "name": "站点A",
  "fields": {
    "EnterDoor": "1.2",
    "LeaveDoor": "2.3"
  }
}
```

**小车站点配置**：
```json
{
  "id": 50,
  "name": "小车当前位置",
  "fields": {
    "PreEnterDoor": "1.2",
    "RearLeaveDoor": "2.3"
  }
}
```

**工作流程**：
1. 小车从站点50驶向站点100
2. 到达站点100时，触发 `BeforeLock` 事件
3. 系统检查站点100的 `EnterDoor`（`"1.2"`）是否与站点50的 `PreEnterDoor`（`"1.2"`）匹配
4. 如果匹配，设置控制器1的门2为打开状态
5. 门打开后，小车锁定站点100
6. 触发 `OnLockAcquired` 事件，门保持打开状态
7. 小车离开站点100时，触发 `AfterLeave` 事件
8. 系统检查站点100的 `LeaveDoor`（`"2.3"`）是否与站点50的 `RearLeaveDoor`（`"2.3"`）匹配
9. 如果匹配，从区域内小车列表中移除该小车
10. 如果没有其他小车在区域内，门自动关闭

---

## UI界面

### 1. 配置管理界面

**打开方式**：
```csharp
DoorMission.OpenViewer();
```

**功能**：
- 门控制器列表管理（添加、删除、修改）
- 门列表管理（为每个控制器添加、删除、修改门）
- 类型选择（自动识别所有带 `DoorTypeAttribute` 的控制器类型）
- 配置保存到 `DoorConfig.json`

### 2. 监控界面

**打开方式**：
```csharp
DoorMission.OpenMonitor();
```

**功能**：
- 实时显示所有门的状态
- 显示控制器索引、门索引、当前状态、控制目标、车辆占用
- 显示控制来源和手动控制剩余时间
- 显示控制地址和开到位信号地址
- 手动控制门开关（打开/关闭，带安全保护）
- 清空车辆占用记录
- 自动刷新（默认1秒刷新间隔）

**显示信息**：
| 列名 | 说明 |
|------|------|
| 控制器编码 | 门控制器索引 |
| 门编码 | 门索引 |
| 当前状态 | 门的当前状态（开/关，带颜色标识：绿色=打开，红色=关闭） |
| 控制目标 | 门的目标控制状态（开/关，带颜色标识：绿色=开，红色=关） |
| 车辆占用 | 门区域内的小车ID列表（多个ID用逗号分隔，无车辆显示"无"） |
| 控制来源 | 当前控制来源（自动/手动） |
| 手动剩余(s) | 手动控制剩余秒数（仅当控制来源=手动时显示，自动时显示"-"） |
| 控制地址 | Modbus 线圈地址 |
| 开到位地址 | Modbus 离散输入地址 |

**手动控制功能**：
- **打开按钮**：设置手动打开控制，默认保持10秒
- **关闭按钮**：设置手动关闭控制，默认保持10秒
  - **安全保护**：当门区域内有车辆占用时，关闭按钮自动禁用，无法执行关闭操作
  - 必须先清空车辆占用，才能手动关闭门
- **清空占用按钮**：清空选中门的车辆占用记录
  - 清空后，如果门处于打开状态且没有其他小车需要进入，门会自动关闭
  - 清空占用后，可以执行手动关闭操作

**控制优先级说明**：
- 手动控制优先级高于自动控制
- 手动控制会在指定时间（默认10秒）后自动过期，恢复自动模式
- 可以通过"清空占用"按钮清空车辆占用，然后手动关闭门

---

## 参数说明

### 1. 监控间隔

| 参数 | 默认值 | 说明 |
|------|--------|------|
| 配置监控间隔 | 10秒 | 检查配置文件的间隔 |
| 门控逻辑监控间隔 | 500毫秒 | 检查门控逻辑的间隔（已优化） |
| 监控界面刷新间隔 | 1秒 | 监控界面自动刷新间隔 |
| 手动控制默认保持时间 | 10秒 | 手动控制请求的默认过期时间 |

### 2. 线程安全说明

- **门状态读取**：通过 `controller.DoorStates` 字典访问，所有读写操作受锁保护
- **门控制写入**：通过 `controller.SetDoorControlTarget()` 设置目标状态，实际通信由门控制器内部线程完成
- **配置同步**：配置变更时使用锁保护，确保线程安全
- **控制仲裁**：手动控制请求和自动控制逻辑的仲裁在同一锁内完成，确保线程安全
- **车辆占用管理**：车辆占用的增删改查操作均受锁保护

### 3. 控制仲裁机制

系统采用**控制仲裁机制**来协调手动控制和自动控制：

- **优先级**：手动控制 > 自动控制
- **手动控制过期**：手动控制请求会在指定时间（默认10秒）后自动过期，过期后恢复自动控制
- **安全保护**：当门区域内有车辆占用时，禁止手动关闭门，确保安全
- **仲裁流程**：
  1. 计算自动目标（基于车辆占用和需要打开标志）
  2. 检查是否存在有效的手动控制请求
  3. 如果手动控制未过期，使用手动目标；否则使用自动目标
  4. 将最终目标写入门控制器的 `DoorControlTargets` 字段

---

## 故障排查

### 问题1：门控制器无法连接

**排查步骤**：
1. 检查配置文件中的 IP 和端口是否正确
2. 检查网络连接是否正常
3. 查看诊断日志中的错误信息
4. 确认门控制器硬件是否在线

**诊断命令**：
```csharp
var controllers = doorMission.GetDoorControllers();
foreach (var controller in controllers)
{
    Console.WriteLine($"控制器{controller.Index}: IP={controller.Ip}, Port={controller.Port}, State={controller.State}, Error={controller.ErrorMessage}");
}
```

### 问题2：门不自动开启

**排查步骤**：
1. 确认 `DoorMission` 任务已启动
2. 检查站点的 `EnterDoor` 字段是否配置正确
3. 检查小车站点的 `PreEnterDoor` 字段是否与目标站点的 `EnterDoor` 匹配
4. 查看门控制器的连接状态是否为 `Online`
5. 检查门的状态是否正确读取

**诊断命令**：
```csharp
// 检查门状态
bool isOpen = doorMission.GetDoorState(1, 2);
Console.WriteLine($"控制器1门2的状态: {(isOpen ? "打开" : "关闭")}");

// 检查门控制器状态
var controllers = doorMission.GetDoorControllers();
var controller = controllers.FirstOrDefault(c => c.Index == 1);
if (controller != null)
{
    Console.WriteLine($"控制器状态: {controller.State}");
    Console.WriteLine($"是否在线: {controller.IsOnline}");
    Console.WriteLine($"错误信息: {controller.ErrorMessage}");
}
```

### 问题3：配置文件更新后不生效

**排查步骤**：
1. 确认配置文件格式正确（JSON格式）
2. 检查配置文件是否保存成功
3. 等待最多10秒，系统会自动检测更新
4. 查看诊断日志中的配置同步信息

### 问题4：监控界面无数据

**排查步骤**：
1. 确认 `DoorMission` 任务已启动
2. 检查是否有配置的门控制器
3. 查看门控制器是否成功连接
4. 检查监控界面的刷新间隔设置

### 问题5：手动关闭按钮无法点击

**原因**：
- 门区域内有车辆占用，系统安全保护机制禁止手动关闭

**解决方法**：
1. 先点击"清空占用"按钮，清空车辆占用记录
2. 清空后，关闭按钮会自动启用
3. 然后可以执行手动关闭操作

### 问题6：手动控制不生效

**排查步骤**：
1. 检查手动控制是否已过期（默认10秒）
2. 查看"控制来源"列，确认是否为"手动"
3. 查看"手动剩余(s)"列，确认剩余时间
4. 如果已过期，手动控制会自动恢复为自动模式
5. 可以通过监控界面重新设置手动控制

---

## 附录

### A. 门标识符解析

门标识符格式：`控制器索引.门索引`

**解析规则**：
- 必须包含一个点号（`.`）
- 点号前后必须为整数
- 解析失败时返回 `null`

**示例**：
- `"1.2"` → `(controllerIndex: 1, doorIndex: 2)` ✅
- `"10.5"` → `(controllerIndex: 10, doorIndex: 5)` ✅
- `"1"` → `null` ❌（缺少点号）
- `"1.2.3"` → `null` ❌（多个点号）
- `"a.2"` → `null` ❌（非数字）

### B. 诊断日志说明

系统会在以下情况记录诊断日志：
- 门控制器连接成功/失败
- 门控制器配置变更
- 门状态读取失败
- 门控制写入失败
- 配置加载失败

**日志位置**：系统诊断日志

### C. 类结构关系图

```
DoorMission (门控进程)
    │
    ├── BasicDoorController (抽象基类)
    │   │
    │   └── ModbusDoorController (Modbus 实现)
    │
    ├── DoorManager (配置界面)
    │
    ├── DoorMonitor (监控界面)
    │
    └── DoorModel (配置模型)
```

### D. 控制来源枚举

```csharp
public enum ControlSource
{
    Auto = 0,    // 自动控制
    Manual = 1   // 手动控制
}
```

### E. 门控制状态结构

```csharp
public class DoorControlStatus
{
    public bool Target { get; set; }                    // 当前目标状态（true=打开，false=关闭）
    public ControlSource Source { get; set; }           // 控制来源（Auto/Manual）
    public double? ManualRemainingSeconds { get; set; }  // 手动控制剩余秒数（仅当Source=Manual时有效）
    public IReadOnlyList<int> CarsInArea { get; set; }  // 车辆占用列表
}
```

### F. 版本历史

| 版本 | 日期 | 主要更新 |
|------|------|----------|
| 1.0 | 2025-01 | 初始版本，支持 Modbus 门控制器 |
| 1.1 | 2025-01 | 新增门控仲裁机制，支持手动控制与自动控制协调 |
| 1.2 | 2025-01 | 新增车辆占用管理功能，监控界面显示车辆占用情况 |
| 1.3 | 2025-01 | 优化门控逻辑监控间隔至500ms，新增安全保护机制（占用时禁止手动关闭） |

---

**文档更新时间**：2025-01-21
