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

25 KiB
Raw Permalink Blame History

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 必须实现的方法(抽象方法)

/// <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
{
    // ...
}

注意DoorTypeAttributeName 参数将显示在配置界面的类型下拉框中,并用于配置文件中的类型标识。

步骤3:实现抽象方法

实现 ReadDoorStateWriteDoorControl 方法:

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

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

步骤4:重写 Connect/Disconnect 方法

如果需要初始化连接、启动后台任务等,重写 ConnectDisconnect 方法:

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);
    }
}

实现示例

示例1ModbusDoorController(完整实现)

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
{
    // ...
}

作用

  1. 类型标识Name 参数作为类型的唯一标识,用于配置文件中指定类型
  2. UI显示:配置界面会自动识别并显示所有带此特性的控制器类型
  3. 动态实例化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 后台任务访问共享资源

如果后台任务会访问 DoorStatesDoorControlTargets 等共享资源,必须加锁:

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. 错误处理

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

示例

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