# 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 | 门状态字典 | | `DoorControlTargets` | Dictionary | 门目标控制状态字典 | | `DoorConfigs` | Dictionary | 门配置信息字典 | | `LastUpdateTime` | DateTime | 最后更新时间 | | `ErrorMessage` | string | 错误信息 | ### 3. 核心方法 #### 3.1 必须实现的方法(抽象方法) ```csharp /// /// 读取门状态(开到位信号) /// /// 门索引 /// true=打开,false=关闭 public abstract bool ReadDoorState(int doorIndex); /// /// 写入门控制信号(开关控制) /// /// 门索引 /// true=打开,false=关闭 public abstract void WriteDoorControl(int doorIndex, bool open); ``` #### 3.2 可重写的方法(虚方法) ```csharp /// /// 连接门控制器 /// public virtual void Connect() { } /// /// 断开连接 /// public virtual void Disconnect() { } /// /// 更新控制器状态 /// public virtual void UpdateState(DoorControllerState newState, string errorMessage = "") { } /// /// 设置门的目标控制状态 /// 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 { /// /// Modbus 门控制器实现 /// [DoorType("ModbusDoorController")] public class ModbusDoorController : BasicDoorController { private ModbusRtu _modbusClient; private readonly object _syncLock = new object(); private bool _isStarted = false; private readonly Dictionary _lastSentControl = new Dictionary(); 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 从站地址 /// /// 线程安全的设置门控制目标 /// public override void SetDoorControlTarget(int doorIndex, bool open) { lock (_syncLock) { base.SetDoorControlTarget(doorIndex, open); } } /// /// 连接门控制器 /// 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; } } } /// /// 断开连接 /// 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}"); } } } /// /// 定时读取门状态循环 /// 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); } } /// /// 读取所有门的状态 /// 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); } } } } /// /// 根据 DoorControlTargets 下发控制指令 /// 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); } } } } } /// /// 读取门状态(开到位信号) /// 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]; } } /// /// 写入门控制信号(开关控制) /// 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 { /// /// 简单门控制器实现(同步模式) /// [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); } /// /// 重写 SetDoorControlTarget 以立即执行控制 /// 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 { /// /// 门索引 /// public int Index { get; set; } /// /// 开关控制信号地址 /// public ushort ControlAddress { get; set; } /// /// 开到位信号地址 /// 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