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

526 lines
21 KiB
C#
Raw Normal View History

using Aitex.Common.Util;
2023-04-13 11:51:03 +08:00
using Aitex.Core.Common.DeviceData;
using Aitex.Core.RT.DataCenter;
using Aitex.Core.RT.Device;
using Aitex.Core.RT.IOCore;
2023-04-13 11:51:03 +08:00
using Aitex.Core.RT.Log;
using Aitex.Core.RT.OperationCenter;
using Aitex.Core.Util;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml;
2023-04-13 11:51:03 +08:00
using DicEventActions = System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<MECF.Framework.Common.Device.Bases.STEventAction>>;
using DicLightActions = System.Collections.Generic.Dictionary<MECF.Framework.Common.Device.Bases.LightType,MECF.Framework.Common.Device.Bases.STEventAction>;
2023-04-13 11:51:03 +08:00
namespace MECF.Framework.Common.Device.Bases
{
/// <summary>
/// 信号塔对象。
/// </summary>
public class SignalTowerBase : BaseDevice, IDevice
{
#region Variables
/// <summary>
/// 系统保留的工作模式配置项,用于工艺结束后蜂鸣器发出提示音。
/// </summary>
protected const string KEY_PATTERN_JOB_DONE = "JobDone";
/// <summary>
/// 是否关闭蜂鸣器输出。
/// <value>True蜂鸣器被手动关闭满足事件条件也不要打开蜂鸣器。</value>
/// <value>False根据当前系统状态和配置事件自动输出蜂鸣器状态。</value>
/// </summary>
private bool _switchBuzzerOff;
/// <summary>
/// 信号塔事件字典,从配置文件中读取。
/// <remarks>
/// 字典的Key表示事件名称对应STEvent配置文件中的name属性。
/// </remarks>
/// <br/>
/// <remarks>
/// 注意:操作该字典时需要加锁,因为当前对象支持实时监测配置文件并重载配置文件的改动;配置文件
/// 内容改动后会重新创建该字典此时有可能Monitor()方法正在使用此字典。
/// </remarks>
/// </summary>
private DicEventActions _dicPreDefinedStEvents;
/// <summary>
/// 信号塔扩展事件字典。
/// <remarks>
/// 除STEvents配置中预设的事件外还有一类事件通常由RT触发这类事件也需要参与三色灯输出状态判断。
/// </remarks>
/// </summary>
private readonly DicEventActions _dicRtGeneratedStEvents;
/// <summary>
/// 信号塔中组件字典。
/// </summary>
private readonly Dictionary<LightType, SignalTowerPartBase> _dicStParts;
/// <summary>
/// 信号塔组件动作字典。
/// </summary>
private Dictionary<LightType, STEventAction> _dicMergedActions;
/// <summary>
/// 上一次Monitor周期触发的事件列表。
/// 用于下一次Monitor周期判断是否有新的事件产生已解决蜂鸣器被手动关闭后如果未按下Reset则下次新事件产生时无法响蜂鸣器的问题。
/// </summary>
private readonly List<string> _lastEventsWithBuzzer = new List<string>();
/// <summary>
/// STEvents配置文件解析对象。
/// </summary>
private STEvents _originStEvents;
#endregion
#region Constructors
/// <summary>
/// 构建信号塔对象的实例。
/// </summary>
/// <param name="module">当前模组名称。</param>
/// <param name="node">设备配置文件。</param>
/// <param name="ioModule">所属Module的名称。</param>
public SignalTowerBase(string module, XmlElement node, string ioModule = "")
: base(module, node, ioModule)
{
_dicStParts = new Dictionary<LightType, SignalTowerPartBase>();
_dicRtGeneratedStEvents = new ();
var doRedLight = ParseDoNode("doRed", node, ioModule);
var doYellowLight = ParseDoNode("doYellow", node, ioModule);
var doGreenLight = ParseDoNode("doGreen", node, ioModule);
var doBlueLight = ParseDoNode("doBlue", node, ioModule);
var doWhiteLight = ParseDoNode("doWhite", node, ioModule);
var doBuzzer = ParseDoNode("doBuzzer", node, ioModule);
var doBuzzer1 = ParseDoNode("doBuzzer1", node, ioModule);
var doBuzzer2 = ParseDoNode("doBuzzer2", node, ioModule);
var doBuzzer3 = ParseDoNode("doBuzzer3", node, ioModule);
var doBuzzer4 = ParseDoNode("doBuzzer4", node, ioModule);
var doBuzzer5 = ParseDoNode("doBuzzer5", node, ioModule);
var aoBuzzerBlinkFreq = ParseAoNode("aoBuzzerBlinkFreq", node, ioModule);
var eventFile = node.GetAttribute("eventFile");
// 红、黄、绿、蜂鸣器是必须定义的元件。
Debug.Assert(doRedLight != null, "DO RedLight is not valid");
Debug.Assert(doYellowLight != null, "DO RedLight is not valid");
Debug.Assert(doGreenLight != null, "DO RedLight is not valid");
Debug.Assert(doBuzzer != null, "DO RedLight is not valid");
// 创建三色灯控制元件。
CreateSignalPart(LightType.Red, doRedLight);
CreateSignalPart(LightType.Yellow, doYellowLight);
CreateSignalPart(LightType.Green, doGreenLight);
CreateSignalPart(LightType.Blue, doBlueLight);
CreateSignalPart(LightType.White, doWhiteLight);
CreateSignalPart(LightType.Buzzer, doBuzzer, aoBuzzerBlinkFreq);
CreateSignalPart(LightType.Buzzer1, doBuzzer1);
CreateSignalPart(LightType.Buzzer2, doBuzzer2);
CreateSignalPart(LightType.Buzzer3, doBuzzer3);
CreateSignalPart(LightType.Buzzer4, doBuzzer4);
CreateSignalPart(LightType.Buzzer5, doBuzzer5);
// 添加文件到监视器,当文件内容发生变化时重新加载配置。
var fullFn = PathManager.GetCfgDir() + eventFile;
FileSystemWatcherManager.Instance.Register(fullFn, fn =>
{
ParseSignalTowerEvent(fn);
});
//解析三色灯Event
ParseSignalTowerEvent(fullFn);
}
#endregion
#region Properties
/// <summary>
/// 返回信号塔当前状态数据集。
/// <remarks>该属性用于RT到UI间的数据传递。</remarks>
/// </summary>
public AITSignalTowerData DeviceData => new AITSignalTowerData
{
DeviceName = Name,
DeviceSchematicId = DeviceID,
DisplayName = Display,
IsGreenLightOn = GetSignalTowerPartValue(LightType.Green),
IsRedLightOn = GetSignalTowerPartValue(LightType.Red),
IsYellowLightOn = GetSignalTowerPartValue(LightType.Yellow),
IsBlueLightOn = GetSignalTowerPartValue(LightType.Blue),
IsWhiteLightOn = GetSignalTowerPartValue(LightType.White),
IsBuzzerOn = GetSignalTowerPartValue(LightType.Buzzer),
IsBuzzer1On = GetSignalTowerPartValue(LightType.Buzzer1),
IsBuzzer2On = GetSignalTowerPartValue(LightType.Buzzer2),
IsBuzzer3On = GetSignalTowerPartValue(LightType.Buzzer3),
IsBuzzer4On = GetSignalTowerPartValue(LightType.Buzzer4),
IsBuzzer5On = GetSignalTowerPartValue(LightType.Buzzer5),
};
#endregion
#region Methods
/// <summary>
/// 创建一个信号塔元件,并添加到字典中。
/// </summary>
/// <param name="light">信号塔元件类型,请参考<see cref="LightType"/>。</param>
/// <param name="doSw">控制元件开关的DO。</param>
/// <param name="aoBlinkFreq">控制Blink频率的AO。</param>
private void CreateSignalPart(LightType light, DOAccessor doSw, AOAccessor aoBlinkFreq = null)
{
if (doSw != null)
_dicStParts.Add(light, new SignalTowerPartBase(light, doSw, aoBlinkFreq));
}
/// <summary>
/// 从指定的配置文件中解析信号塔事件。
/// </summary>
/// <param name="fileName">包含完整路径的配置文件文件名。</param>
/// <returns></returns>
private bool ParseSignalTowerEvent(string fileName)
{
try
{
if (!File.Exists(fileName))
{
LOG.Error($"Unable to find signal tower events config file {fileName}.");
return false;
}
_originStEvents = CustomXmlSerializer.Deserialize<STEvents>(new FileInfo(fileName));
_originStEvents.ParseEvents(_dicStParts, out var events);
if (events == null)
LOG.Error("Unable to parse the signal tower events from config file.");
lock (SyncRoot)
{
_dicPreDefinedStEvents = events;
return _dicPreDefinedStEvents != null;
}
}
catch (Exception ex)
{
LOG.Error(ex.ToString());
return false;
}
}
/// <summary>
/// 初始化信号塔对象实例。
/// </summary>
/// <remarks>
/// 注册SwitchOffBuzzer操作和DeviceData数据。
/// </remarks>
/// <returns></returns>
public bool Initialize()
{
OP.Subscribe($"{Module}.{Name}.{AITSignalTowerOperation.SwitchOffBuzzer}", SwitchOffBuzzer);
DATA.Subscribe(Module + "." + Name + ".DeviceData", () => DeviceData);
return true;
}
/// <summary>
/// 复位信号塔状态及其各个组件。
/// </summary>
public void Reset()
{
_switchBuzzerOff = false;
_dicRtGeneratedStEvents?.Clear();
foreach (var light in _dicStParts.Values)
light.Reset();
}
/// <summary>
/// 终止信号塔对象实例。
/// </summary>
public void Terminate()
{
foreach (var light in _dicStParts.Values)
light.Terminate();
}
/// <summary>
/// 周期性扫描信号塔以执行后台任务。
/// </summary>
protected override void HandleMonitor()
{
// 创建上次发生的事件列表副本
List<string> events1;
lock (SyncRoot)
{
// 遍历所有预设的事件,决定信号塔各元件的输出状态。
_dicMergedActions = ConvertEventsToActions(_dicPreDefinedStEvents, out events1);
}
// 遍历所有扩展的事件,决定信号塔各元件的输出状态。
_dicMergedActions = ConvertEventsToActions(_dicRtGeneratedStEvents, out var events2, _dicMergedActions, false);
// 判断是否有新事件产生如果有需要复位_switchBuzzerOff状态以重新使能蜂鸣器解决蜂鸣器被手动关闭后如果不按Reset则无法重启启用的问题。
var eventCombined = new List<string>(events1.Concat(events2));
var newEvents = _lastEventsWithBuzzer.Except(eventCombined);
if (newEvents.Any())
{
_lastEventsWithBuzzer.Clear();
_lastEventsWithBuzzer.AddRange(eventCombined);
_switchBuzzerOff = false;
}
// 设置信号塔每个组件的输出。
foreach (var kvp in _dicMergedActions)
{
var action = kvp.Value;
if (action == null)
continue;
if (action.StPart == null)
continue;
// 创建动作副本因为后续可能根据_switchBuzzerOff调整输出状态避免覆盖原配置。
var cloned = (STEventAction)action.Clone();
// 如果蜂鸣器被强制关闭则将待执行动作中的状态修改为Off
if (action.StPart.IsBuzzer && _switchBuzzerOff)
cloned.Status = TowerLightStatus.Off;
action.StPart.SetAction(cloned);
}
// 扫描信号塔组件状态
foreach (var light in _dicStParts.Values)
light?.Monitor();
}
/// <summary>
/// 以指定的模式闪烁指定的信号塔元件。
/// </summary>
/// <param name="light">指定的信号塔元件。</param>
/// <param name="pattern">闪烁模式。</param>
/// <returns>True启动闪烁成功False启动闪烁失败。</returns>
public bool Blink(LightType light, STBlinkPattern pattern)
{
if (pattern == null)
{
LOG.Error($"{light} Blink Pattern can not be null");
return false;
}
CreateRtEvent(new STEventAction("Rt Triggered Blink", _dicStParts[light], TowerLightStatus.Customized, pattern));
return true;
}
/// <summary>
/// 当Wafer回到Cassette后打开蜂鸣器以指示ProcessJob结束。
/// </summary>
/// <param name="reason">如果打开蜂鸣器失败,提示失败原因。</param>
/// <returns></returns>
public bool SwitchOnBuzzerForJobDone(out string reason)
{
reason = "";
SignalTowerPartBase targetBuzzer = null;
STBlinkPattern blinkPattern = null;
// 读取STEvents配置文件中的JobDone模式的配置。
var settings = _originStEvents.PatternsSettings.FirstOrDefault(x => x.Name == KEY_PATTERN_JOB_DONE);
if (settings != null)
{
// 解析配置中的Buzzer如果未指定Buzzer或指定为除Buzzer以外的信号灯则默认使用Buzzer。
var targetPart = settings.Part;
if (Enum.TryParse(targetPart, out LightType lightType)
&& _dicStParts.TryGetValue(lightType, out var buzzer)
&& buzzer.IsBuzzer)
targetBuzzer = buzzer;
blinkPattern = new STBlinkPattern(settings.Pattern, priority: settings.Priority, cycle: settings.Cycles);
}
else
{
// 没找到JobDone的工作模式配置生成默认配置
blinkPattern = STBlinkPattern.GetJobDonePattern();
LOG.Warning($"Unable to find pre-defined pattern for {KEY_PATTERN_JOB_DONE}");
}
CreateRtEvent(new STEventAction(KEY_PATTERN_JOB_DONE, targetBuzzer, TowerLightStatus.Customized, blinkPattern));
return true;
}
/// <summary>
/// 打开或关闭蜂鸣器。
/// </summary>
/// <param name="isOff"></param>
/// <returns></returns>
public bool SwitchOffBuzzer(bool isOff)
{
_switchBuzzerOff = isOff;
return true;
}
/// <summary>
/// 关闭蜂鸣器。
/// </summary>
/// <param name="arg1"></param>
/// <param name="arg2"></param>
/// <returns></returns>
private bool SwitchOffBuzzer(string arg1, object[] arg2)
{
_switchBuzzerOff = true;
return true;
}
/// <summary>
/// 合并信号事件。
/// <remarks>
/// 根据<see cref="STEventAction.Status"/>的优先级决定执行的动作。
/// <br/>
/// 此属性的数值越小,表示优先级越高。
/// </remarks>
/// </summary>
/// <param name="currentAction">当前输出状态。</param>
/// <param name="nextAction">期望的输出状态。</param>
/// <returns>合并后的信号灯输出状态。</returns>
private STEventAction MergeAction(STEventAction currentAction, STEventAction nextAction)
{
if (currentAction == null)
return nextAction;
if (nextAction == null)
return currentAction;
// 获取当前动作的优先级
var curPrior = currentAction.Status == TowerLightStatus.Customized
? currentAction.BlinkPattern.Priority
: (int)currentAction.Status;
// 获取下一个动作的优先级
var nextPrior = nextAction.Status == TowerLightStatus.Customized
? nextAction.BlinkPattern.Priority
: (int)nextAction.Status;
return curPrior < nextPrior ? currentAction : nextAction;
}
/// <summary>
/// 获取指定信号塔元件的输出状态。
/// </summary>
/// <param name="light"></param>
/// <returns></returns>
private bool GetSignalTowerPartValue(LightType light)
{
if (_dicStParts.TryGetValue(light, out var part))
return part.GetValue();
return false;
}
/// <summary>
/// 获取RT中已触发的事件列表。
/// </summary>
/// <returns></returns>
private static DicEventActions PollRtAlarms(DicEventActions dicEvents)
{
if (dicEvents == null)
return new DicEventActions();
var data = dicEvents.Keys.ToList();
var polled = DATA.PollData(data);
var eventTriggered = polled.Where(x => x.Value is true).Select(x => x.Key).ToList();
return dicEvents.Where(x => eventTriggered.Contains(x.Key)).ToDictionary(x => x.Key, v => v.Value);
}
/// <summary>
/// 更具RT当前状态合并所有事件的动作决定信号塔组件的输出状态。
/// </summary>
/// <param name="dicEvents">事件字典。</param>
/// <param name="eventsWithBuzzerOn">返回需要触发Buzzer的事件的名称列表。</param>
/// <param name="initActions">信号塔组件初始动作字典。</param>
/// <param name="isPollDataNeeded">
/// 是否从RT拉去事件对应的属性值。
/// <remarks>
/// 对于<see cref="_dicRtGeneratedStEvents"/>事件列表不需要从RT拉去数据直接合并动作即可。
/// </remarks>
/// </param>
/// <returns></returns>
private DicLightActions ConvertEventsToActions(DicEventActions dicEvents, out List<string> eventsWithBuzzerOn, DicLightActions initActions = null, bool isPollDataNeeded = true)
{
// 创建初始动作字典
initActions ??= _dicStParts.ToDictionary(
x => x.Key,
v => new STEventAction("", v.Value, TowerLightStatus.Off));
// 对于STEvent配置文件中的事件我们只需要关注那些条件为True的事件。
// 创建一个临时字典筛选条件为True的事件或直接使用_dicRtGeneratedEvents事件字典。
var dicEventsTmp = dicEvents;
// 需要从RT拉取事件状态
if (isPollDataNeeded)
dicEventsTmp = PollRtAlarms(dicEvents);
// 保存触发的事件名称列表。
var eventsBuzzerNotOff = dicEventsTmp
.Where(x => x.Value
.Any(v => v.StPart.IsBuzzer && v.Status != TowerLightStatus.Off))
.Select(x => x.Key);
eventsWithBuzzerOn = new List<string>(eventsBuzzerNotOff);
// 遍历所有预设的事件,决定信号塔各元件的输出状态。
foreach (var stEvent in dicEventsTmp)
{
foreach (var newAction in stEvent.Value)
{
if (!_dicStParts.TryGetValue(newAction.StPart.Light, out var stPart))
continue;
// 根据当前设备状态,判断下一步信号组件的输出动作是什么
var mergedAction = MergeAction(initActions[stPart.Light], newAction);
initActions[stPart.Light] = mergedAction;
}
}
return initActions;
}
/// <summary>
/// 创建一个自定义的RT事件以驱动指定的信号塔组件。
/// <remarks>
/// 自动创建Guid作为事件的Key以和STEvent配置字典的格式保持一致以为Monitor方法中需要同时对这两个字典处理格式的统一有助于统一处理任务的逻辑。
/// </remarks>
/// </summary>
/// <param name="actions">该事件执行的动作列表。
/// <remarks>
/// 列表的每一个元素对应一个信号塔组件需执行的动作。
/// </remarks>
/// </param>
private void CreateRtEvent(params STEventAction[] actions)
{
_dicRtGeneratedStEvents.Add(Guid.NewGuid().ToString(), actions.ToList());
SwitchOffBuzzer(false);
}
#endregion
}
2023-04-13 11:51:03 +08:00
}