Files
2026-06-14 11:19:15 +08:00

945 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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