341 lines
12 KiB
C#
341 lines
12 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
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 TowerLightStatus _action;
|
||
|
||
/// <summary>
|
||
/// 闪烁次数计数器。
|
||
/// <remarks>当该计数器归零时,结束闪烁。</remarks>
|
||
/// </summary>
|
||
private uint _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 Type => _lightType;
|
||
|
||
#endregion
|
||
|
||
#region Monitor Methods
|
||
|
||
public void Monitor()
|
||
{
|
||
/*
|
||
* 实现一个基于switch-case的建议状态机,用于实现Blink ON-OFF切换。
|
||
* 注意:请避免使用线程实现Blink模式,不符合程序架构,不严谨的线程处理可能造成意外结果。
|
||
*/
|
||
|
||
// 如果时Blinking模式,实现Blinking逻辑
|
||
MonitorBlink();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行Blink逻辑。
|
||
/// </summary>
|
||
private void MonitorBlink()
|
||
{
|
||
// 如果信号塔元件的状态已经转为Off,但状态机没有进入Idle,说明可能人为取消了信号,需要复位状态机。
|
||
if (_action == TowerLightStatus.Off && _blinkStage != FsmStateBlink.Idle)
|
||
{
|
||
Reset();
|
||
}
|
||
|
||
if (_blinkCycleDownCounter > 0
|
||
&& (_action == TowerLightStatus.Blinking || _action == TowerLightStatus.Alarm ||
|
||
_action == TowerLightStatus.Warning))
|
||
{
|
||
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);
|
||
|
||
_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 = 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 TowerLightStatus 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>
|
||
/// <param name="blinkPattern">闪烁模式,当<see cref="action"/>为<see cref="TowerLightStatus.Blinking"/>时有效。</param>
|
||
public void SetValue(TowerLightStatus action, STBlinkPattern blinkPattern = null)
|
||
{
|
||
// 不要重复设置状态
|
||
if(_action == action)
|
||
return;
|
||
|
||
_action = action;
|
||
|
||
switch (_action)
|
||
{
|
||
case TowerLightStatus.On:
|
||
_blinkCycleDownCounter = 0;
|
||
_doLight.Value = true;
|
||
break;
|
||
|
||
case TowerLightStatus.Off:
|
||
_blinkCycleDownCounter = 0;
|
||
_doLight.Value = false;
|
||
Reset(); // 复位状态机
|
||
break;
|
||
|
||
case TowerLightStatus.Alarm:
|
||
case TowerLightStatus.Warning:
|
||
case TowerLightStatus.Blinking:
|
||
// 当工作在闪烁状态时,如果没有指定闪烁模式,则创建一个默认模式。
|
||
_blinkPattern = blinkPattern;
|
||
_blinkPattern ??= new STBlinkPattern();
|
||
|
||
// PLC控制模式
|
||
if (_blinkPattern.IsPlcMode && _aoBlinkFreqHz != null)
|
||
{
|
||
_aoBlinkFreqHz.FloatValue = _blinkPattern.FrequencyHz;
|
||
break;
|
||
}
|
||
|
||
// 当循环次数小于等于0时,无限循环。
|
||
_blinkCycleDownCounter = _blinkPattern.TotalCycles <= 0 ? int.MaxValue : _blinkPattern.TotalCycles;
|
||
|
||
// 解析闪烁模式数据。
|
||
if (!_blinkPattern.Parse(out _blinkData, out var reason))
|
||
{
|
||
// 如果闪烁模式解析错误,则常亮
|
||
LOG.Error($"Unable to set {Type} to {action}, {reason}");
|
||
_blinkData = new List<BlinkDataType>(new[] { new BlinkDataType(TowerLightStatus.On, 100) });
|
||
}
|
||
|
||
// 使能状态机
|
||
//! 注意:此步需放在最后,因为状态机异步使能,相关数据可能还未准备好
|
||
_blinkStage = FsmStateBlink.Init;
|
||
|
||
break;
|
||
|
||
case TowerLightStatus.Unknown:
|
||
LOG.Error($"{Type} Undefined output status");
|
||
break;
|
||
|
||
default:
|
||
throw new ArgumentOutOfRangeException();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 复位信号塔元件。
|
||
/// </summary>
|
||
public void Reset()
|
||
{
|
||
_blinkStage = FsmStateBlink.Idle;
|
||
|
||
_blinkCycleDownCounter = 0;
|
||
_action = TowerLightStatus.Off;
|
||
_doLight.Value = false;
|
||
|
||
_qBlinkData?.Clear();
|
||
_timBlinkOn?.Stop();
|
||
_timBlinkOff?.Stop();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 终止信号塔元件。
|
||
/// </summary>
|
||
public void Terminate()
|
||
{
|
||
_doLight = null;
|
||
_timBlinkOn.Stop();
|
||
_timBlinkOff.Stop();
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return $"{_lightType}";
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|