using System; using System.Collections.Generic; using System.Diagnostics; using Aitex.Core.RT.Device; using Aitex.Core.RT.IOCore; using Aitex.Core.RT.Log; using Aitex.Core.Util; using BlinkDataType = System.Collections.Generic.KeyValuePair; namespace MECF.Framework.Common.Device.Bases { /// /// 信号塔元件对象,包含灯和蜂鸣器。 /// 受支持的元件请参考。 /// /// 信号塔元件的Blink模式基于一个简单的Switch-Case状态机实现。 /// /// public class SignalTowerPartBase : BaseDevice, IDevice { #region Variables private enum FsmStateBlink { Init, PrepareOn, On, PrepareOff, Off, ActionDone, CycleFinished, Cleanup, Idle } private DOAccessor _doLight; private AOAccessor _aoBlinkFreqHz; private readonly LightType _lightType; private STEventAction _action; /// /// 闪烁次数计数器。 /// 当该计数器归零时,结束闪烁。 /// private int _blinkCycleDownCounter; private STBlinkPattern _blinkPattern; private List _blinkData; private Queue _qBlinkData; private BlinkDataType _nextBlinkData; private FsmStateBlink _blinkStage; private readonly DeviceTimer _timBlinkOn; private readonly DeviceTimer _timBlinkOff; #endregion #region Constructors /// /// 构建信号塔元件对象的实例。 /// /// 信号塔元件类型,请参考。 /// 控制元件开关的DO。 /// 控制Blink频率的AO。 public SignalTowerPartBase(LightType light, DOAccessor doSwitch, AOAccessor aoBlinkFreq) { _lightType = light; _doLight = doSwitch; _aoBlinkFreqHz = aoBlinkFreq; _timBlinkOn = new DeviceTimer(); _timBlinkOff = new DeviceTimer(); } #endregion #region Properties /// /// 返回信号塔元件类型。 /// public LightType Light => _lightType; /// /// 返回当前信号塔组件是否为蜂鸣器。 /// public bool IsBuzzer => _lightType.ToString().Contains("Buzz"); #endregion #region Monitor Methods /// /// 后台扫描线程执行的方法。 /// public void Monitor() { /* * 实现一个基于switch-case的建议状态机,用于实现Blink ON-OFF切换。 * 注意:请避免使用线程实现Blink模式,不符合程序架构,不严谨的线程处理可能造成意外结果。 */ // 如果时Blinking模式,实现Blinking逻辑 MonitorBlink(); } /// /// 执行Blink逻辑。 /// private void MonitorBlink() { if (_action == null) return; // 如果信号塔元件的状态已经转为Off,但状态机没有进入Idle,说明可能人为取消了信号,需要复位状态机。 if (_action.Status == TowerLightStatus.Off && _blinkStage != FsmStateBlink.Idle) { Reset(); } // 如果动作状态指定使用自定义模式,并且循环次数未用完,则根据Pattern进行闪烁 if (_action.Status == TowerLightStatus.Customized && _blinkCycleDownCounter > 0) { switch (_blinkStage) { case FsmStateBlink.Idle: // 空闲状态 // 注意:该状态不应反复进入,因为_action和_blinkCycleDownCounter在首次进入Idle // 状态时,会被清除,从而进入Switch-Case状态机的条件无法满足 break; case FsmStateBlink.Cleanup: // 控制周期结束 Reset(); break; case FsmStateBlink.Init: // 如果闪烁数据列队为空,可能: // 1、第一次进入状态机 // 2、上个Cycle已经结束,需要执行下个Cycle if (_qBlinkData == null || _qBlinkData.Count <= 0) _qBlinkData = new Queue(_blinkData); _nextBlinkData = _qBlinkData.Dequeue(); if (_nextBlinkData.Key == TowerLightStatus.On) _blinkStage = FsmStateBlink.PrepareOn; else if (_nextBlinkData.Key == TowerLightStatus.Off) _blinkStage = FsmStateBlink.PrepareOff; else { LOG.Error($"Incorrect status {_nextBlinkData.Key} in blink pattern"); _blinkStage = FsmStateBlink.Idle; } _doLight.Value = false; _timBlinkOn.Stop(); _timBlinkOff.Stop(); break; case FsmStateBlink.PrepareOn: _doLight.Value = true; _timBlinkOn.Start(_nextBlinkData.Value); _timBlinkOff.Stop(); _blinkStage = FsmStateBlink.On; break; case FsmStateBlink.On: // 等待输出ON状态结束 if (_timBlinkOn.IsTimeout()) _blinkStage = FsmStateBlink.ActionDone; break; case FsmStateBlink.PrepareOff: _doLight.Value = false; _timBlinkOn.Stop(); _timBlinkOff.Start(_nextBlinkData.Value); _blinkStage = FsmStateBlink.Off; break; case FsmStateBlink.Off: // 等待输出OFF状态结束 if (_timBlinkOff.IsTimeout()) _blinkStage = FsmStateBlink.ActionDone; break; case FsmStateBlink.ActionDone: // 上个输出完成后,判断动作列队是否为空。 // 如果不为空,则继续执行; // 否则,表示一个循环已完成,跳转到CycleFinished状态 if (_qBlinkData.Count <= 0) _blinkStage = FsmStateBlink.CycleFinished; else _blinkStage = FsmStateBlink.Init; break; case FsmStateBlink.CycleFinished: _blinkCycleDownCounter--; if (_blinkCycleDownCounter <= 0) { // Blink循环结束,关闭输出 _doLight.Value = false; _action.Status = TowerLightStatus.Off; _blinkStage = FsmStateBlink.Cleanup; } else { // 进入下一个循环。 _blinkStage = FsmStateBlink.Init; } break; } } } #endregion #region Methods /// /// 初始化信号塔元件。 /// /// True:初始化成功;False:初始化失败。 public bool Initialize() { _blinkStage = FsmStateBlink.Idle; return true; } /// /// 获取信号塔元件当前正在执行的动作。 /// /// public STEventAction GetAction() { return _action; } /// /// 获取信号塔元件映射PLC IO的输出状态。 /// /// public bool GetValue() { return _doLight != null && _doLight.Value; } /// /// 设置信号塔元件动作。 /// 请参考枚举获取支持的动作类型。 /// /// 信号灯的动作。 public void SetAction(STEventAction action) { if (action == null) { _action = null; return; } // 不要重复设置状态 if (_action == null || action.CompareTo(_action) != 0) _action = action; else return; // 如果没有指定动作,则默认关闭组件 if (_action == null) { _blinkCycleDownCounter = 0; _doLight.Value = false; Reset(); // 复位状态机 return; } // 执行动作 switch (_action.Status) { case TowerLightStatus.On: _blinkCycleDownCounter = 0; _doLight.Value = true; break; case TowerLightStatus.Off: _blinkCycleDownCounter = 0; _doLight.Value = false; Reset(); // 复位状态机 break; case TowerLightStatus.Customized: // 当工作在闪烁状态时,如果没有指定闪烁模式,则创建一个默认模式。 _blinkPattern = _action.BlinkPattern; _blinkPattern ??= new STBlinkPattern(); // PLC控制模式 if (_blinkPattern.IsPlcMode && _aoBlinkFreqHz != null) { _aoBlinkFreqHz.FloatValue = _blinkPattern.FrequencyHz; } else { // 当循环次数小于等于0时,无限循环。 _blinkCycleDownCounter = _blinkPattern.TotalCycles <= 0 ? int.MaxValue : _blinkPattern.TotalCycles; // 解析闪烁模式数据。 if (!STBlinkPattern.GetBlinkData(_blinkPattern, out _blinkData, out var reason)) { // 如果解析闪烁数据,如果解析错误,使用默认的闪烁模式。 LOG.Error($"Unable to set {Light} to {action}, {reason}"); STBlinkPattern.GetBlinkData(STBlinkPattern.GetDefaultPattern(), out _blinkData, out _); Debug.Assert(_blinkData != null); } // 使能状态机 //! 注意:此步需放在最后,因为状态机异步使能,相关数据可能还未准备好 _blinkStage = FsmStateBlink.Init; } break; case TowerLightStatus.Unknown: LOG.Error($"{Light} Undefined output status"); break; default: throw new ArgumentOutOfRangeException(); } } /// /// 复位信号塔元件。 /// public void Reset() { _blinkStage = FsmStateBlink.Idle; _blinkCycleDownCounter = 0; _action.Status = TowerLightStatus.Off; _doLight.Value = false; _qBlinkData?.Clear(); _timBlinkOn?.Stop(); _timBlinkOff?.Stop(); } /// /// 终止信号塔元件。 /// public void Terminate() { _doLight = null; _timBlinkOn.Stop(); _timBlinkOff.Stop(); } /// public override string ToString() { return $"{_lightType}"; } #endregion } }