25 KiB
25 KiB
DoorController 开发手册
目录
概述
DoorController 是门控制系统的核心组件,采用抽象基类设计,支持扩展不同类型的门控制器实现。本手册指导开发者如何创建自定义的门控制器。
核心概念
- BasicDoorController:门控制器抽象基类,定义通用接口和属性
- DoorTypeAttribute:类型特性,用于标记控制器类型,支持动态实例化
- DoorState:门状态枚举(Closed/Open/Unknown)
- DoorControllerState:控制器状态枚举(Offline/Online/Connecting/Error)
设计原则
- 抽象化:所有通信细节封装在具体实现类中
- 线程安全:状态读取和控制写入分离,通信操作在内部线程完成
- 可扩展性:通过
DoorTypeAttribute实现类型自动识别和动态加载 - 热更新支持:配置变更无需重启任务
基础架构
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 必须实现的方法(抽象方法)
/// <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 可重写的方法(虚方法)
/// <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:
using StandardScene.ExtendDevice.Door;
namespace StandardScene.ExtendDevice.Door
{
public class MyDoorController : BasicDoorController
{
// 实现抽象方法
}
}
步骤2:添加 DoorTypeAttribute
使用 DoorTypeAttribute 标记控制器类型:
[DoorType("MyDoorController")]
public class MyDoorController : BasicDoorController
{
// ...
}
注意:DoorTypeAttribute 的 Name 参数将显示在配置界面的类型下拉框中,并用于配置文件中的类型标识。
步骤3:实现抽象方法
实现 ReadDoorState 和 WriteDoorControl 方法:
public override bool ReadDoorState(int doorIndex)
{
// 读取门状态逻辑
// 返回 true=打开,false=关闭
}
public override void WriteDoorControl(int doorIndex, bool open)
{
// 写入门控制信号逻辑
}
步骤4:重写 Connect/Disconnect 方法
如果需要初始化连接、启动后台任务等,重写 Connect 和 Disconnect 方法:
public override void Connect()
{
base.Connect(); // 调用基类方法更新状态
// 初始化连接
// 启动后台任务
// 读取初始状态
}
public override void Disconnect()
{
// 停止后台任务
// 关闭连接
base.Disconnect(); // 调用基类方法更新状态
}
步骤5:实现线程安全的控制逻辑
如果需要定时读取状态或根据 DoorControlTargets 下发控制指令,实现后台任务:
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(完整实现)
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:简单门控制器(最小实现)
如果不需要定时读取或后台任务,可以实现最简单的版本:
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 用于标记门控制器类型,支持动态实例化。
定义:
[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));
}
}
使用:
[DoorType("MyDoorController")]
public class MyDoorController : BasicDoorController
{
// ...
}
作用:
- 类型标识:
Name参数作为类型的唯一标识,用于配置文件中指定类型 - UI显示:配置界面会自动识别并显示所有带此特性的控制器类型
- 动态实例化:
DoorMission根据Name动态查找并创建实例
注意:
Name必须唯一- 建议使用类名作为
Name Name会显示在配置界面的类型下拉框中
线程安全
1. 设计原则
门控制器采用读写分离的线程安全设计:
- 读取:
DoorMission通过controller.DoorStates直接访问门状态(受锁保护) - 写入:
DoorMission通过controller.SetDoorControlTarget()设置目标状态,实际通信由门控制器内部线程完成
2. 线程安全要求
2.1 SetDoorControlTarget 方法
如果多个线程可能同时调用 SetDoorControlTarget,必须加锁保护:
private readonly object _syncLock = new object();
public override void SetDoorControlTarget(int doorIndex, bool open)
{
lock (_syncLock)
{
base.SetDoorControlTarget(doorIndex, open);
}
}
2.2 ReadDoorState 和 WriteDoorControl 方法
如果这些方法会被多个线程调用,必须加锁保护:
public override bool ReadDoorState(int doorIndex)
{
lock (_syncLock)
{
// 读取逻辑
}
}
public override void WriteDoorControl(int doorIndex, bool open)
{
lock (_syncLock)
{
// 写入逻辑
}
}
2.3 后台任务访问共享资源
如果后台任务会访问 DoorStates、DoorControlTargets 等共享资源,必须加锁:
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. 推荐实现模式
推荐使用单一锁对象保护所有共享资源:
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,并记录错误信息 - 读取错误:记录日志,但不抛出异常,返回默认值或保持当前状态
- 写入错误:记录日志,尝试重连,但不影响其他门的操作
示例:
public override bool ReadDoorState(int doorIndex)
{
try
{
// 读取逻辑
}
catch (Exception ex)
{
Diagnosis.Log($"读取门{doorIndex}状态失败: {ex.Message}", "MyDoorController", true);
return false; // 返回默认值
}
}
2. 状态管理
- 及时更新状态:在连接、断开、错误时及时调用
UpdateState() - 更新最后更新时间:在状态变化时更新
LastUpdateTime - 错误信息:在错误时记录详细的错误信息到
ErrorMessage
示例:
try
{
_client.Connect();
UpdateState(DoorControllerState.Online);
}
catch (Exception ex)
{
UpdateState(DoorControllerState.Error, $"连接失败: {ex.Message}");
}
3. 资源释放
- 实现 Disconnect:确保正确关闭连接和释放资源
- 实现析构函数:作为最后的安全网,确保资源释放
示例:
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() 中验证配置的完整性:
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 说明
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 枚举
public enum DoorState
{
Closed = 0, // 关闭
Open = 1, // 打开
Unknown = 2 // 未知状态
}
C. DoorControllerState 枚举
public enum DoorControllerState
{
Offline = 0, // 离线
Online = 1, // 在线
Connecting = 2, // 连接中
Error = 3 // 错误
}
D. 开发检查清单
- 继承
BasicDoorController - 添加
DoorTypeAttribute特性 - 实现
ReadDoorState方法 - 实现
WriteDoorControl方法 - 重写
Connect方法(如需要) - 重写
Disconnect方法(如需要) - 实现线程安全(如需要)
- 添加错误处理
- 添加资源释放逻辑
- 添加诊断日志
- 测试连接/断开
- 测试读取状态
- 测试控制写入
- 测试配置热更新
文档更新时间:2025-01-09