Sic.Framework/MECF.Framework.Common/MECF/Framework/Common/Device/Bases/SignalTowerPartBase.cs

380 lines
13 KiB
C#
Raw Normal View History

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<MECF.Framework.Common.Device.Bases.TowerLightStatus, uint>;
namespace MECF.Framework.Common.Device.Bases
{
/// <summary>
/// 信号塔元件对象,包含灯和蜂鸣器。
/// 受支持的元件请参考<see cref="LightType"/>。
/// <remarks>
/// 信号塔元件的Blink模式基于一个简单的Switch-Case状态机实现。
/// </remarks>
/// </summary>
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;
/// <summary>
/// 闪烁次数计数器。
/// <remarks>当该计数器归零时,结束闪烁。</remarks>
/// </summary>
private int _blinkCycleDownCounter;
private STBlinkPattern _blinkPattern;
private List<BlinkDataType> _blinkData;
private Queue<BlinkDataType> _qBlinkData;
private BlinkDataType _nextBlinkData;
private FsmStateBlink _blinkStage;
private readonly DeviceTimer _timBlinkOn;
private readonly DeviceTimer _timBlinkOff;
#endregion
#region Constructors
/// <summary>
/// 构建信号塔元件对象的实例。
/// </summary>
/// <param name="light">信号塔元件类型,请参考<see cref="LightType"/>。</param>
/// <param name="doSwitch">控制元件开关的DO。</param>
/// <param name="aoBlinkFreq">控制Blink频率的AO。</param>
public SignalTowerPartBase(LightType light, DOAccessor doSwitch, AOAccessor aoBlinkFreq)
{
_lightType = light;
_doLight = doSwitch;
_aoBlinkFreqHz = aoBlinkFreq;
_timBlinkOn = new DeviceTimer();
_timBlinkOff = new DeviceTimer();
}
#endregion
#region Properties
/// <summary>
/// 返回信号塔元件类型。
/// </summary>
public LightType Light => _lightType;
/// <summary>
/// 返回当前信号塔组件是否为蜂鸣器。
/// </summary>
public bool IsBuzzer => _lightType.ToString().Contains("Buzz");
#endregion
#region Monitor Methods
/// <summary>
/// 后台扫描线程执行的方法。
/// </summary>
protected override void HandleMonitor()
{
/*
* switch-case的建议状态机Blink ON-OFF切换
* 使线Blink模式线
*/
// 如果时Blinking模式实现Blinking逻辑
MonitorBlink();
}
/// <summary>
/// 执行Blink逻辑。
/// </summary>
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<BlinkDataType>(_blinkData);
// 如果未能获取闪烁数据,则终止状态机
if (_qBlinkData.Count == 0)
{
_blinkStage = FsmStateBlink.Cleanup;
break;
}
_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.Cleanup;
break;
}
_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
/// <summary>
/// 初始化信号塔元件。
/// </summary>
/// <returns>True初始化成功False初始化失败。</returns>
public bool Initialize()
{
_blinkStage = FsmStateBlink.Idle;
return true;
}
/// <summary>
/// 获取信号塔元件当前正在执行的动作。
/// </summary>
/// <returns></returns>
public STEventAction GetAction()
{
return _action;
}
/// <summary>
/// 获取信号塔元件映射PLC IO的输出状态。
/// </summary>
/// <returns></returns>
public bool GetValue()
{
return _doLight != null && _doLight.Value;
}
/// <summary>
/// 设置信号塔元件动作。
/// <para>请参考<see cref="TowerLightStatus"/>枚举获取支持的动作类型。</para>
/// </summary>
/// <param name="action">信号灯的动作。</param>
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();
}
}
/// <summary>
/// 复位信号塔元件。
/// </summary>
public void Reset()
{
// 复位后状态机回到Idle状态
_blinkStage = FsmStateBlink.Idle;
_blinkCycleDownCounter = 0;
_action.Status = TowerLightStatus.Off;
_doLight.Value = false;
_qBlinkData?.Clear();
_timBlinkOn?.Stop();
_timBlinkOff?.Stop();
}
/// <summary>
/// 终止信号塔元件。
/// </summary>
public void Terminate()
{
_doLight = null;
_timBlinkOn.Stop();
_timBlinkOff.Stop();
}
/// <inheritdoc />
public override string ToString()
{
return $"{_lightType}";
}
#endregion
}
}