Sic.Framework-Nanjing-Baishi/MECF.Framework.Common/MECF/Framework/Common/Device/Bases/SignalTowerPartBase.cs

383 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 SciChart.Charting2D.Interop;
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;
if (_action != null)
_action.Status = TowerLightStatus.Off;
_blinkCycleDownCounter = 0;
_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
}
}