Files

945 lines
25 KiB
Markdown
Raw Permalink Normal View History

2026-06-14 11:19:15 +08:00
# 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);
}
}
```
---
## 实现示例
### 示例1ModbusDoorController(完整实现)
```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