# DoorController 开发手册

## 目录
- [概述](#概述)
- [基础架构](#基础架构)
- [开发步骤](#开发步骤)
- [实现示例](#实现示例)
- [特性说明](#特性说明)
- [线程安全](#线程安全)
- [最佳实践](#最佳实践)
- [注意事项](#注意事项)
- [附录](#附录)

---

## 概述

`DoorController` 是门控制系统的核心组件，采用抽象基类设计，支持扩展不同类型的门控制器实现。本手册指导开发者如何创建自定义的门控制器。

### 核心概念

- **BasicDoorController**：门控制器抽象基类，定义通用接口和属性
- **DoorTypeAttribute**：类型特性，用于标记控制器类型，支持动态实例化
- **DoorState**：门状态枚举（Closed/Open/Unknown）
- **DoorControllerState**：控制器状态枚举（Offline/Online/Connecting/Error）

### 设计原则

1. **抽象化**：所有通信细节封装在具体实现类中
2. **线程安全**：状态读取和控制写入分离，通信操作在内部线程完成
3. **可扩展性**：通过 `DoorTypeAttribute` 实现类型自动识别和动态加载
4. **热更新支持**：配置变更无需重启任务

---

## 基础架构

### 1. 类继承关系

```
BasicDoorController (抽象基类)
    │
    └── ModbusDoorController (Modbus TCP 实现)
        └── [其他实现...]
```

### 2. BasicDoorController 核心属性

| 属性 | 类型 | 说明 |
|------|------|------|
| `Index` | int | 控制器索引（唯一标识） |
| `Ip` | string | IP地址 |
| `Port` | int | 端口号 |
| `State` | DoorControllerState | 控制器状态 |
| `IsOnline` | bool | 是否在线（只读） |
| `DoorStates` | Dictionary<int, DoorState> | 门状态字典 |
| `DoorControlTargets` | Dictionary<int, bool> | 门目标控制状态字典 |
| `DoorConfigs` | Dictionary<int, DoorModel> | 门配置信息字典 |
| `LastUpdateTime` | DateTime | 最后更新时间 |
| `ErrorMessage` | string | 错误信息 |

### 3. 核心方法

#### 3.1 必须实现的方法（抽象方法）

```csharp
/// <summary>
/// 读取门状态（开到位信号）
/// </summary>
/// <param name="doorIndex">门索引</param>
/// <returns>true=打开，false=关闭</returns>
public abstract bool ReadDoorState(int doorIndex);

/// <summary>
/// 写入门控制信号（开关控制）
/// </summary>
/// <param name="doorIndex">门索引</param>
/// <param name="open">true=打开，false=关闭</param>
public abstract void WriteDoorControl(int doorIndex, bool open);
```

#### 3.2 可重写的方法（虚方法）

```csharp
/// <summary>
/// 连接门控制器
/// </summary>
public virtual void Connect() { }

/// <summary>
/// 断开连接
/// </summary>
public virtual void Disconnect() { }

/// <summary>
/// 更新控制器状态
/// </summary>
public virtual void UpdateState(DoorControllerState newState, string errorMessage = "") { }

/// <summary>
/// 设置门的目标控制状态
/// </summary>
public virtual void SetDoorControlTarget(int doorIndex, bool open) { }
```

---

## 开发步骤

### 步骤1：创建控制器类

创建新类并继承 `BasicDoorController`：

```csharp
using StandardScene.ExtendDevice.Door;

namespace StandardScene.ExtendDevice.Door
{
    public class MyDoorController : BasicDoorController
    {
        // 实现抽象方法
    }
}
```

### 步骤2：添加 DoorTypeAttribute

使用 `DoorTypeAttribute` 标记控制器类型：

```csharp
[DoorType("MyDoorController")]
public class MyDoorController : BasicDoorController
{
    // ...
}
```

**注意**：`DoorTypeAttribute` 的 `Name` 参数将显示在配置界面的类型下拉框中，并用于配置文件中的类型标识。

### 步骤3：实现抽象方法

实现 `ReadDoorState` 和 `WriteDoorControl` 方法：

```csharp
public override bool ReadDoorState(int doorIndex)
{
    // 读取门状态逻辑
    // 返回 true=打开，false=关闭
}

public override void WriteDoorControl(int doorIndex, bool open)
{
    // 写入门控制信号逻辑
}
```

### 步骤4：重写 Connect/Disconnect 方法

如果需要初始化连接、启动后台任务等，重写 `Connect` 和 `Disconnect` 方法：

```csharp
public override void Connect()
{
    base.Connect();  // 调用基类方法更新状态
    
    // 初始化连接
    // 启动后台任务
    // 读取初始状态
}

public override void Disconnect()
{
    // 停止后台任务
    // 关闭连接
    
    base.Disconnect();  // 调用基类方法更新状态
}
```

### 步骤5：实现线程安全的控制逻辑

如果需要定时读取状态或根据 `DoorControlTargets` 下发控制指令，实现后台任务：

```csharp
private CancellationTokenSource _cancellationTokenSource;
private Task _readTask;

public override void Connect()
{
    base.Connect();
    
    // 启动定时读取任务
    _cancellationTokenSource = new CancellationTokenSource();
    _readTask = Task.Run(() => ReadDoorStatesLoop(_cancellationTokenSource.Token));
}

private void ReadDoorStatesLoop(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        // 读取所有门的状态
        ReadAllDoorStates();
        
        // 根据 DoorControlTargets 下发控制指令
        ApplyDoorControlTargets();
        
        Thread.Sleep(ReadInterval);
    }
}
```

---

## 实现示例

### 示例1：ModbusDoorController（完整实现）

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StandardScene.Utils;
using SimpleCore.Library;

namespace StandardScene.ExtendDevice.Door
{
    /// <summary>
    /// Modbus 门控制器实现
    /// </summary>
    [DoorType("ModbusDoorController")]
    public class ModbusDoorController : BasicDoorController
    {
        private ModbusRtu _modbusClient;
        private readonly object _syncLock = new object();
        private bool _isStarted = false;
        private readonly Dictionary<int, bool> _lastSentControl = new Dictionary<int, bool>();
        private CancellationTokenSource _cancellationTokenSource;
        private Task _readTask;

        // 可配置参数
        public int ReadInterval { get; set; } = 1000;  // 读取间隔（毫秒）
        public int ReconnectInterval { get; set; } = 3000;  // 重连间隔（毫秒）
        public byte SlaveAddress { get; set; } = 1;  // Modbus 从站地址

        /// <summary>
        /// 线程安全的设置门控制目标
        /// </summary>
        public override void SetDoorControlTarget(int doorIndex, bool open)
        {
            lock (_syncLock)
            {
                base.SetDoorControlTarget(doorIndex, open);
            }
        }

        /// <summary>
        /// 连接门控制器
        /// </summary>
        public override void Connect()
        {
            lock (_syncLock)
            {
                if (_isStarted) return;

                try
                {
                    UpdateState(DoorControllerState.Connecting);

                    // 初始化门状态
                    var doorIndices = DoorConfigs.Keys.OrderBy(k => k).ToList();
                    InitializeDoors(doorIndices);

                    // 初始化最近一次已下发的控制状态
                    _lastSentControl.Clear();
                    foreach (var index in doorIndices)
                    {
                        _lastSentControl[index] = false;
                        if (!DoorControlTargets.ContainsKey(index))
                        {
                            DoorControlTargets[index] = false;
                        }
                    }

                    // 连接 Modbus TCP
                    try
                    {
                        _modbusClient = new ModbusRtu();
                        _modbusClient.StartTcpRtu(Ip, Port);
                        UpdateState(DoorControllerState.Online);
                    }
                    catch (Exception ex)
                    {
                        UpdateState(DoorControllerState.Connecting);
                        Diagnosis.Log($"ModbusDoorController[{Index}] 初次连接失败: {ex.Message}", "ModbusDoorController", true);
                    }

                    // 启动定时读取任务
                    _cancellationTokenSource = new CancellationTokenSource();
                    _readTask = Task.Run(() => ReadDoorStatesLoop(_cancellationTokenSource.Token));

                    _isStarted = true;
                }
                catch (Exception ex)
                {
                    UpdateState(DoorControllerState.Error, $"初始化失败: {ex.Message}");
                    _isStarted = false;
                }
            }
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public override void Disconnect()
        {
            lock (_syncLock)
            {
                if (!_isStarted) return;

                try
                {
                    _cancellationTokenSource?.Cancel();
                    _readTask?.Wait(1000);

                    _modbusClient?.Close();
                    _modbusClient = null;

                    _isStarted = false;
                    UpdateState(DoorControllerState.Offline);
                }
                catch (Exception ex)
                {
                    UpdateState(DoorControllerState.Error, $"断开连接失败: {ex.Message}");
                }
            }
        }

        /// <summary>
        /// 定时读取门状态循环
        /// </summary>
        private void ReadDoorStatesLoop(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    if (!_isStarted) break;

                    // 检查连接状态
                    bool isConnected = _modbusClient?.modbusRtu?.Connected ?? false;
                    if (_modbusClient == null || !isConnected)
                    {
                        UpdateState(DoorControllerState.Connecting);
                        TryReconnect();
                        isConnected = _modbusClient?.modbusRtu?.Connected ?? false;
                        if (!isConnected)
                        {
                            Thread.Sleep(ReadInterval);
                            continue;
                        }
                    }

                    // 读取所有门的状态
                    ReadAllDoorStates();

                    // 根据目标控制状态下发控制指令
                    ApplyDoorControlTargets();

                    UpdateState(DoorControllerState.Online);
                }
                catch (Exception ex)
                {
                    Diagnosis.Log($"ModbusDoorController[{Index}] 读取状态失败: {ex.Message}", "ModbusDoorController", true);
                    UpdateState(DoorControllerState.Error, $"读取状态失败: {ex.Message}");
                    TryReconnect();
                }

                Thread.Sleep(ReadInterval);
            }
        }

        /// <summary>
        /// 读取所有门的状态
        /// </summary>
        private void ReadAllDoorStates()
        {
            lock (_syncLock)
            {
                foreach (var doorConfig in DoorConfigs.Values)
                {
                    try
                    {
                        var state = ReadDoorState(doorConfig.Index);
                        UpdateDoorState(doorConfig.Index, state ? DoorState.Open : DoorState.Closed);
                    }
                    catch (Exception ex)
                    {
                        Diagnosis.Log($"ModbusDoorController[{Index}] 读取门{doorConfig.Index}状态失败: {ex.Message}", "ModbusDoorController", true);
                    }
                }
            }
        }

        /// <summary>
        /// 根据 DoorControlTargets 下发控制指令
        /// </summary>
        private void ApplyDoorControlTargets()
        {
            lock (_syncLock)
            {
                foreach (var doorConfig in DoorConfigs.Values)
                {
                    var doorIndex = doorConfig.Index;

                    // 获取目标控制状态
                    bool target = false;
                    DoorControlTargets.TryGetValue(doorIndex, out target);

                    // 获取上一次已下发的状态
                    bool last;
                    var hasLast = _lastSentControl.TryGetValue(doorIndex, out last);

                    // 如果没有记录或状态发生变化，则下发控制
                    if (!hasLast || last != target)
                    {
                        try
                        {
                            WriteDoorControl(doorIndex, target);
                            _lastSentControl[doorIndex] = target;
                        }
                        catch (Exception ex)
                        {
                            Diagnosis.Log($"ModbusDoorController[{Index}] 下发门{doorIndex}控制指令失败: {ex.Message}", "ModbusDoorController", true);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 读取门状态（开到位信号）
        /// </summary>
        public override bool ReadDoorState(int doorIndex)
        {
            lock (_syncLock)
            {
                if (!DoorConfigs.TryGetValue(doorIndex, out var doorConfig))
                {
                    throw new ArgumentException($"门{doorIndex}不存在");
                }

                if (_modbusClient == null || !_modbusClient.modbusRtu.Connected)
                {
                    throw new InvalidOperationException("Modbus连接未建立");
                }

                // 读取开到位信号（离散输入）
                var data = _modbusClient.ReadDiscreteInputs_02(SlaveAddress, doorConfig.OpenStatusAddress, 1);
                return data != null && data.Length > 0 && data[0];
            }
        }

        /// <summary>
        /// 写入门控制信号（开关控制）
        /// </summary>
        public override void WriteDoorControl(int doorIndex, bool open)
        {
            lock (_syncLock)
            {
                if (!DoorConfigs.TryGetValue(doorIndex, out var doorConfig))
                {
                    throw new ArgumentException($"门{doorIndex}不存在");
                }

                if (_modbusClient == null || !_modbusClient.modbusRtu.Connected)
                {
                    TryReconnect();
                    if (_modbusClient == null || !_modbusClient.modbusRtu.Connected)
                    {
                        throw new InvalidOperationException("Modbus连接未建立");
                    }
                }

                // 写入开关控制信号（线圈）
                _modbusClient.WriteMultipleCoils_15(SlaveAddress, doorConfig.ControlAddress, new[] { open });
            }
        }

        private void TryReconnect()
        {
            // 重连逻辑...
        }
    }
}
```

### 示例2：简单门控制器（最小实现）

如果不需要定时读取或后台任务，可以实现最简单的版本：

```csharp
using System;
using StandardScene.ExtendDevice.Door;

namespace StandardScene.ExtendDevice.Door
{
    /// <summary>
    /// 简单门控制器实现（同步模式）
    /// </summary>
    [DoorType("SimpleDoorController")]
    public class SimpleDoorController : BasicDoorController
    {
        private SimpleDoorClient _client;

        public override void Connect()
        {
            base.Connect();
            _client = new SimpleDoorClient(Ip, Port);
            UpdateState(DoorControllerState.Online);
        }

        public override void Disconnect()
        {
            _client?.Close();
            _client = null;
            base.Disconnect();
        }

        public override bool ReadDoorState(int doorIndex)
        {
            if (!DoorConfigs.TryGetValue(doorIndex, out var doorConfig))
            {
                throw new ArgumentException($"门{doorIndex}不存在");
            }

            if (_client == null || !_client.IsConnected)
            {
                throw new InvalidOperationException("连接未建立");
            }

            // 读取门状态
            return _client.ReadDoorStatus(doorConfig.OpenStatusAddress);
        }

        public override void WriteDoorControl(int doorIndex, bool open)
        {
            if (!DoorConfigs.TryGetValue(doorIndex, out var doorConfig))
            {
                throw new ArgumentException($"门{doorIndex}不存在");
            }

            if (_client == null || !_client.IsConnected)
            {
                throw new InvalidOperationException("连接未建立");
            }

            // 写入控制信号
            _client.WriteDoorControl(doorConfig.ControlAddress, open);
            
            // 同步更新门状态（可选）
            var state = _client.ReadDoorStatus(doorConfig.OpenStatusAddress);
            UpdateDoorState(doorIndex, state ? DoorState.Open : DoorState.Closed);
        }

        /// <summary>
        /// 重写 SetDoorControlTarget 以立即执行控制
        /// </summary>
        public override void SetDoorControlTarget(int doorIndex, bool open)
        {
            base.SetDoorControlTarget(doorIndex, open);
            
            // 立即执行控制（同步模式）
            try
            {
                WriteDoorControl(doorIndex, open);
            }
            catch (Exception ex)
            {
                Diagnosis.Log($"SimpleDoorController[{Index}] 控制门{doorIndex}失败: {ex.Message}", "SimpleDoorController", true);
            }
        }
    }
}
```

---

## 特性说明

### DoorTypeAttribute

`DoorTypeAttribute` 用于标记门控制器类型，支持动态实例化。

**定义**：
```csharp
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class DoorTypeAttribute : Attribute
{
    public string Name { get; }
    
    public DoorTypeAttribute(string name)
    {
        Name = name ?? throw new ArgumentNullException(nameof(name));
    }
}
```

**使用**：
```csharp
[DoorType("MyDoorController")]
public class MyDoorController : BasicDoorController
{
    // ...
}
```

**作用**：
1. **类型标识**：`Name` 参数作为类型的唯一标识，用于配置文件中指定类型
2. **UI显示**：配置界面会自动识别并显示所有带此特性的控制器类型
3. **动态实例化**：`DoorMission` 根据 `Name` 动态查找并创建实例

**注意**：
- `Name` 必须唯一
- 建议使用类名作为 `Name`
- `Name` 会显示在配置界面的类型下拉框中

---

## 线程安全

### 1. 设计原则

门控制器采用**读写分离**的线程安全设计：
- **读取**：`DoorMission` 通过 `controller.DoorStates` 直接访问门状态（受锁保护）
- **写入**：`DoorMission` 通过 `controller.SetDoorControlTarget()` 设置目标状态，实际通信由门控制器内部线程完成

### 2. 线程安全要求

#### 2.1 SetDoorControlTarget 方法

如果多个线程可能同时调用 `SetDoorControlTarget`，必须加锁保护：

```csharp
private readonly object _syncLock = new object();

public override void SetDoorControlTarget(int doorIndex, bool open)
{
    lock (_syncLock)
    {
        base.SetDoorControlTarget(doorIndex, open);
    }
}
```

#### 2.2 ReadDoorState 和 WriteDoorControl 方法

如果这些方法会被多个线程调用，必须加锁保护：

```csharp
public override bool ReadDoorState(int doorIndex)
{
    lock (_syncLock)
    {
        // 读取逻辑
    }
}

public override void WriteDoorControl(int doorIndex, bool open)
{
    lock (_syncLock)
    {
        // 写入逻辑
    }
}
```

#### 2.3 后台任务访问共享资源

如果后台任务会访问 `DoorStates`、`DoorControlTargets` 等共享资源，必须加锁：

```csharp
private void ReadAllDoorStates()
{
    lock (_syncLock)
    {
        foreach (var doorConfig in DoorConfigs.Values)
        {
            var state = ReadDoorState(doorConfig.Index);
            UpdateDoorState(doorConfig.Index, state ? DoorState.Open : DoorState.Closed);
        }
    }
}

private void ApplyDoorControlTargets()
{
    lock (_syncLock)
    {
        foreach (var doorConfig in DoorConfigs.Values)
        {
            // 访问 DoorControlTargets
            // 调用 WriteDoorControl
        }
    }
}
```

### 3. 推荐实现模式

推荐使用单一锁对象保护所有共享资源：

```csharp
public class MyDoorController : BasicDoorController
{
    private readonly object _syncLock = new object();
    
    // 所有访问共享资源的方法都使用同一个锁
    public override void SetDoorControlTarget(int doorIndex, bool open)
    {
        lock (_syncLock) { /* ... */ }
    }
    
    public override bool ReadDoorState(int doorIndex)
    {
        lock (_syncLock) { /* ... */ }
    }
    
    public override void WriteDoorControl(int doorIndex, bool open)
    {
        lock (_syncLock) { /* ... */ }
    }
    
    private void ReadAllDoorStates()
    {
        lock (_syncLock) { /* ... */ }
    }
    
    private void ApplyDoorControlTargets()
    {
        lock (_syncLock) { /* ... */ }
    }
}
```

---

## 最佳实践

### 1. 错误处理

- **连接错误**：设置状态为 `Connecting` 或 `Error`，并记录错误信息
- **读取错误**：记录日志，但不抛出异常，返回默认值或保持当前状态
- **写入错误**：记录日志，尝试重连，但不影响其他门的操作

**示例**：
```csharp
public override bool ReadDoorState(int doorIndex)
{
    try
    {
        // 读取逻辑
    }
    catch (Exception ex)
    {
        Diagnosis.Log($"读取门{doorIndex}状态失败: {ex.Message}", "MyDoorController", true);
        return false;  // 返回默认值
    }
}
```

### 2. 状态管理

- **及时更新状态**：在连接、断开、错误时及时调用 `UpdateState()`
- **更新最后更新时间**：在状态变化时更新 `LastUpdateTime`
- **错误信息**：在错误时记录详细的错误信息到 `ErrorMessage`

**示例**：
```csharp
try
{
    _client.Connect();
    UpdateState(DoorControllerState.Online);
}
catch (Exception ex)
{
    UpdateState(DoorControllerState.Error, $"连接失败: {ex.Message}");
}
```

### 3. 资源释放

- **实现 Disconnect**：确保正确关闭连接和释放资源
- **实现析构函数**：作为最后的安全网，确保资源释放

**示例**：
```csharp
public override void Disconnect()
{
    try
    {
        _cancellationTokenSource?.Cancel();
        _readTask?.Wait(1000);
        _client?.Close();
        _client = null;
        UpdateState(DoorControllerState.Offline);
    }
    catch (Exception ex)
    {
        UpdateState(DoorControllerState.Error, $"断开连接失败: {ex.Message}");
    }
}

~MyDoorController()
{
    Disconnect();
}
```

### 4. 配置验证

在 `Connect()` 中验证配置的完整性：

```csharp
public override void Connect()
{
    if (string.IsNullOrWhiteSpace(Ip))
    {
        UpdateState(DoorControllerState.Error, "IP地址未配置");
        return;
    }

    if (Port <= 0 || Port > 65535)
    {
        UpdateState(DoorControllerState.Error, "端口号无效");
        return;
    }

    if (DoorConfigs.Count == 0)
    {
        UpdateState(DoorControllerState.Error, "未配置门");
        return;
    }

    // 连接逻辑...
}
```

---

## 注意事项

### 1. DoorTypeAttribute 命名

- `Name` 必须与配置文件中使用的类型名称一致
- 建议使用类名作为 `Name`
- 避免使用特殊字符

### 2. 异常处理

- **不要抛出未处理的异常**：所有异常都应该被捕获并记录
- **不要阻塞线程**：长时间操作应该在后台线程中执行
- **提供错误信息**：通过 `ErrorMessage` 属性提供详细的错误信息

### 3. 性能考虑

- **避免频繁的连接/断开**：保持连接持久化
- **批量读取**：如果可能，批量读取多个门的状态
- **控制读取频率**：根据实际需求设置合理的读取间隔

### 4. 兼容性

- **向后兼容**：新版本应该兼容旧版本的配置格式
- **版本标识**：如果需要，可以在实现中添加版本检查

---

## 附录

### A. DoorModel 说明

```csharp
public class DoorModel
{
    /// <summary>
    /// 门索引
    /// </summary>
    public int Index { get; set; }

    /// <summary>
    /// 开关控制信号地址
    /// </summary>
    public ushort ControlAddress { get; set; }

    /// <summary>
    /// 开到位信号地址
    /// </summary>
    public ushort OpenStatusAddress { get; set; }
}
```

### B. DoorState 枚举

```csharp
public enum DoorState
{
    Closed = 0,  // 关闭
    Open = 1,    // 打开
    Unknown = 2  // 未知状态
}
```

### C. DoorControllerState 枚举

```csharp
public enum DoorControllerState
{
    Offline = 0,     // 离线
    Online = 1,      // 在线
    Connecting = 2,  // 连接中
    Error = 3        // 错误
}
```

### D. 开发检查清单

- [ ] 继承 `BasicDoorController`
- [ ] 添加 `DoorTypeAttribute` 特性
- [ ] 实现 `ReadDoorState` 方法
- [ ] 实现 `WriteDoorControl` 方法
- [ ] 重写 `Connect` 方法（如需要）
- [ ] 重写 `Disconnect` 方法（如需要）
- [ ] 实现线程安全（如需要）
- [ ] 添加错误处理
- [ ] 添加资源释放逻辑
- [ ] 添加诊断日志
- [ ] 测试连接/断开
- [ ] 测试读取状态
- [ ] 测试控制写入
- [ ] 测试配置热更新

---

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