using Aitex.Common.Util; using Aitex.Core.Common.DeviceData; using Aitex.Core.RT.DataCenter; using Aitex.Core.RT.Device; using Aitex.Core.RT.IOCore; 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; using DictEvent = System.Collections.Generic.Dictionary>; using DictActionsPerLight = System.Collections.Generic.Dictionary; namespace MECF.Framework.Common.Device.Bases { /// /// 信号塔对象。 /// public class SignalTowerBase : BaseDevice, IDevice { #region Variables /// /// 系统保留的工作模式配置项,用于工艺结束后蜂鸣器发出提示音。 /// protected const string KEY_PATTERN_JOB_DONE = "JobDone"; /// /// 信号灯字典。 /// private readonly Dictionary _dictStLights; /// /// 是否关闭蜂鸣器输出。 /// True:蜂鸣器被手动关闭,满足事件条件也不要打开蜂鸣器。 /// False:根据当前系统状态和配置事件自动输出蜂鸣器状态。 /// private bool _switchBuzzerOff; /// /// 信号塔事件字典,从配置文件中读取。 /// /// 字典的Key表示事件名称,对应STEvent配置文件中的name属性。 /// ///
/// /// 注意:操作该字典时需要加锁,因为当前对象支持实时监测配置文件并重载配置文件的改动;配置文件 /// 内容改动后,会重新创建该字典,此时有可能Monitor()方法正在使用此字典。 /// ///
private DictEvent _dictPreDefinedEvents; /// /// 信号塔扩展事件字典。 /// /// 除STEvents配置中预设的事件外,还有一类事件通常由RT触发,这类事件也需要参与三色灯输出状态判断。 /// /// private readonly DictEvent _dictDynamicSTLightActions; /// /// 信号塔组件动作字典。 /// private DictActionsPerLight _dictSTLightActions; /// /// 上一次Monitor周期触发的事件列表。 /// 用于下一次Monitor周期判断是否有新的事件产生,已解决蜂鸣器被手动关闭后,如果未按下Reset,则下次新事件产生时,无法响蜂鸣器的问题。 /// private readonly List _lastEventsWithBuzzer = []; /// /// STEvents配置文件解析对象。 /// private STEvents _eventsFileLoader; #endregion #region Constructors /// /// 构建信号塔对象的实例。 /// /// 当前模组名称。 /// 设备配置文件。 /// 所属Module的名称。 public SignalTowerBase(string module, XmlElement node, string ioModule = "") : base(module, node, ioModule) { _dictSTLightActions = new(); _dictStLights = new Dictionary(); _dictDynamicSTLightActions = 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 of RedLight not defined"); Debug.Assert(doYellowLight != null, "DO of YellowLight not defined"); Debug.Assert(doGreenLight != null, "DO of GreenLight not defined"); Debug.Assert(doBuzzer != null, "DO of Buzzer not defined"); // 创建三色灯控制元件。 CreateSTLight(STLightTypes.Red, doRedLight); CreateSTLight(STLightTypes.Yellow, doYellowLight); CreateSTLight(STLightTypes.Green, doGreenLight); CreateSTLight(STLightTypes.Blue, doBlueLight); CreateSTLight(STLightTypes.White, doWhiteLight); CreateSTLight(STLightTypes.Buzzer, doBuzzer, aoBuzzerBlinkFreq); CreateSTLight(STLightTypes.Buzzer1, doBuzzer1); CreateSTLight(STLightTypes.Buzzer2, doBuzzer2); CreateSTLight(STLightTypes.Buzzer3, doBuzzer3); CreateSTLight(STLightTypes.Buzzer4, doBuzzer4); CreateSTLight(STLightTypes.Buzzer5, doBuzzer5); // 添加文件到监视器,当文件内容发生变化时重新加载配置。 var fullFn = PathManager.GetCfgDir() + eventFile; FileSystemWatcherManager.Instance.Register(fullFn, fn => { ParseSTEvent(fn); }); //解析三色灯Event ParseSTEvent(fullFn); } #endregion #region Properties /// /// 返回信号塔当前状态数据集。 /// 该属性用于RT到UI间的数据传递。 /// public AITSignalTowerData DeviceData => new AITSignalTowerData { DeviceName = Name, DeviceSchematicId = DeviceID, DisplayName = Display, IsGreenLightOn = GetSignalTowerPartValue(STLightTypes.Green), IsRedLightOn = GetSignalTowerPartValue(STLightTypes.Red), IsYellowLightOn = GetSignalTowerPartValue(STLightTypes.Yellow), IsBlueLightOn = GetSignalTowerPartValue(STLightTypes.Blue), IsWhiteLightOn = GetSignalTowerPartValue(STLightTypes.White), IsBuzzerOn = GetSignalTowerPartValue(STLightTypes.Buzzer), IsBuzzer1On = GetSignalTowerPartValue(STLightTypes.Buzzer1), IsBuzzer2On = GetSignalTowerPartValue(STLightTypes.Buzzer2), IsBuzzer3On = GetSignalTowerPartValue(STLightTypes.Buzzer3), IsBuzzer4On = GetSignalTowerPartValue(STLightTypes.Buzzer4), IsBuzzer5On = GetSignalTowerPartValue(STLightTypes.Buzzer5), GreenLightEvent = _dictStLights[STLightTypes.Green]?.Action?.EventName ?? "", YellowLightEvent = _dictStLights[STLightTypes.Yellow]?.Action?.EventName ?? "", RedLightEvent = _dictStLights[STLightTypes.Red]?.Action?.EventName ?? "", BuzzerEvent = _dictStLights[STLightTypes.Buzzer]?.Action?.EventName ?? "" }; #endregion #region Methods /// /// 创建一个信号塔元件,并添加到字典中。 /// /// 信号塔元件类型,请参考。 /// 控制元件开关的DO。 /// 控制Blink频率的AO。 private void CreateSTLight(STLightTypes light, DOAccessor doSw, AOAccessor aoBlinkFreq = null) { if (doSw != null) _dictStLights.Add(light, new SignalTowerLightBase(Module, light, doSw, aoBlinkFreq)); } /// /// 从指定的配置文件中解析信号塔事件。 /// /// 包含完整路径的配置文件文件名。 /// private bool ParseSTEvent(string fileName) { try { if (!File.Exists(fileName)) { LOG.Error($"Unable to find signal tower events config file {fileName}."); return false; } _eventsFileLoader = CustomXmlSerializer.Deserialize(new FileInfo(fileName)); _eventsFileLoader.ParseEvents(_dictStLights, out var events); if (events == null) LOG.Error("Unable to parse the signal tower events from config file."); lock (SyncRoot) { _dictPreDefinedEvents = events; return _dictPreDefinedEvents != null; } } catch (Exception ex) { LOG.Error(ex.ToString()); return false; } } /// /// 初始化信号塔对象实例。 /// /// /// 注册SwitchOffBuzzer操作和DeviceData数据。 /// /// public bool Initialize() { OP.Subscribe($"{Module}.{Name}.{AITSignalTowerOperation.SwitchOffBuzzer}", SwitchOffBuzzer); DATA.Subscribe(Module + "." + Name + ".DeviceData", () => DeviceData); return true; } /// /// 复位信号塔状态及其各个组件。 /// public void Reset() { _switchBuzzerOff = false; _dictDynamicSTLightActions?.Clear(); foreach (var light in _dictStLights.Values) light.Reset(); } /// /// 终止信号塔对象实例。 /// public void Terminate() { foreach (var light in _dictStLights.Values) light.Terminate(); } /// /// 周期性扫描信号塔以执行后台任务。 /// protected override void HandleMonitor() { // 遍历所有预设的事件,决定信号塔各元件的输出状态。 MonitorEvents(out var buzzerOnEvents); // 判断是否有新的Buzzer相关事件产生,如果有,需要复位_switchBuzzerOff状态,以重新使能蜂鸣器,解决蜂鸣器被手动关闭后,如果不按Reset,则无法重启启用的问题。 var buzzerOnEventArray = buzzerOnEvents as string[] ?? buzzerOnEvents.ToArray(); var newBuzzerOnEvents = _lastEventsWithBuzzer.Except(buzzerOnEventArray); if (newBuzzerOnEvents.Any()) { _lastEventsWithBuzzer.Clear(); _lastEventsWithBuzzer.AddRange(buzzerOnEventArray); _switchBuzzerOff = false; } // 分配每个信号灯的动作。 foreach (var kvp in _dictSTLightActions) { var action = kvp.Value; if (action?.Light == null) continue; // 创建动作副本,因为后续可能根据_switchBuzzerOff调整输出状态,避免覆盖原配置。 var cloned = (STAction)action.Clone(); // 如果蜂鸣器被强制关闭,则将待执行动作中的状态修改为Off if (action.Light.IsBuzzer && _switchBuzzerOff) cloned.Output = SignalTowerActions.Off; action.Light.SetAction(cloned); } // 扫描信号塔组件状态 foreach (var light in _dictStLights.Values) { if(light == null) continue; light.Monitor(); // remove the cycle done action from dynamic STEvent dictionary var currAction = light.Action; if (currAction is { IsCycleDone: true } && _dictDynamicSTLightActions.TryGetValue(currAction.EventName, out var actions)) { for (var i = actions.Count - 1; i >= 0; i--) { if (actions[i].LightType == light.Type) actions.RemoveAt(i); } } } } /// /// 以指定的模式闪烁指定的信号塔元件。 /// /// 指定的信号塔元件。 /// 闪烁模式。 /// True:启动闪烁成功;False:启动闪烁失败。 public bool Blink(STLightTypes light, STBlinkPattern pattern) { if (pattern == null) { LOG.Error($"{light} Blink Pattern can not be null"); return false; } var evName = $"Manual_Blink_{light}_{Guid.NewGuid()}"; var blinkAction = new STAction(evName, _dictStLights[light], SignalTowerActions.Customized, pattern); CreateDynamicEvent(evName, [blinkAction]); return true; } /// /// 当Wafer回到Cassette后,打开蜂鸣器以指示ProcessJob结束。 /// /// public bool AlertJobDone() { SignalTowerLightBase targetBuzzer = null; STBlinkPattern blinkPattern = null; // 读取STEvents配置文件中的JobDone模式的配置。 var alertPattern = _eventsFileLoader.PatternsSettings.FirstOrDefault(x => x.Name == KEY_PATTERN_JOB_DONE); if (alertPattern != null) { // 解析配置中的Buzzer,如果未指定Buzzer,或指定为除Buzzer以外的信号灯,则默认使用Buzzer。 var targetLight = alertPattern.Part; if (Enum.TryParse(targetLight, out STLightTypes lightType) && _dictStLights.TryGetValue(lightType, out var buzzer) && buzzer.IsBuzzer) targetBuzzer = buzzer; blinkPattern = new STBlinkPattern(alertPattern.Pattern, priority: alertPattern.Priority, cycle: alertPattern.Cycles); } else { // 没找到JobDone的工作模式配置,生成默认配置 blinkPattern = STBlinkPattern.GetJobDonePattern(); LOG.Warning($"Unable to find pre-defined pattern for {KEY_PATTERN_JOB_DONE}"); } var buzzerAction = new STAction(KEY_PATTERN_JOB_DONE, targetBuzzer, SignalTowerActions.Customized, blinkPattern); CreateDynamicEvent(KEY_PATTERN_JOB_DONE, [buzzerAction]); return true; } /// /// 打开或关闭蜂鸣器。 /// /// /// public bool SwitchOffBuzzer(bool isOff) { _switchBuzzerOff = isOff; return true; } /// /// 关闭蜂鸣器。 /// /// /// /// private bool SwitchOffBuzzer(string arg1, object[] arg2) { _switchBuzzerOff = true; return true; } /// /// 获取优先级更高的STAction。 /// /// 根据的优先级决定执行的动作。 ///
/// 此属性的数值越小,表示优先级越高。 ///
///
/// 当前输出状态。 /// 期望的输出状态。 /// 合并后的信号灯输出状态。 private STAction PickPriorAction(STAction currentAction, STAction nextAction) { if (currentAction == null) return nextAction; if (nextAction == null) return currentAction; // 获取当前动作的优先级 var curPrior = currentAction.Output == SignalTowerActions.Customized ? currentAction.BlinkPattern.Priority : (int)currentAction.Output; // 获取下一个动作的优先级 var nextPrior = nextAction.Output == SignalTowerActions.Customized ? nextAction.BlinkPattern.Priority : (int)nextAction.Output; return curPrior < nextPrior ? currentAction : nextAction; } /// /// 获取指定信号塔元件的输出状态。 /// /// /// private bool GetSignalTowerPartValue(STLightTypes light) { if (_dictStLights.TryGetValue(light, out var part)) return part.GetValue(); return false; } /// /// 从RT拉取已发生的STEvent列表。 /// /// private static DictEvent PollRaisedEvents(DictEvent dicEvents) { if (dicEvents == null) return new(); var events = dicEvents.Keys.ToList(); var data = DATA.PollData(events); var raisedEvents = data.Where(x => x.Value is true).Select(x => x.Key).ToList(); return dicEvents.Where(x => raisedEvents.Contains(x.Key)).ToDictionary(x => x.Key, v => v.Value); } /// /// 监视STEvent.xml中定义的事件。 /// /// private void MonitorEvents(out IEnumerable eventsWithBuzzerOn) { lock (SyncRoot) { // Clear previous actions _dictSTLightActions.Clear(); // Poll raised events of RT var raisedEvents = PollRaisedEvents(_dictPreDefinedEvents); // Create dictionary '_dictSTLightActions' AssignSTActions(raisedEvents); AssignSTActions(_dictDynamicSTLightActions); // Get a list of events related to turning on a buzzer var buzzerOnEv1 = GetBuzzerOnEvents(raisedEvents).ToArray(); var buzzerOnEv2 = GetBuzzerOnEvents(_dictDynamicSTLightActions).ToArray(); eventsWithBuzzerOn = buzzerOnEv1.Concat(buzzerOnEv2.Except(buzzerOnEv1)); } } /// /// 根据已发生的STEvent分配各个信号灯的动作。 /// /// private void AssignSTActions(DictEvent raisedEvents) { foreach (var stEvent in raisedEvents) { foreach (var action in stEvent.Value) { // Maybe the target light of the action is not defined if (!_dictStLights.ContainsKey(action.LightType)) continue; // Make it easier to access dictionaries using the key var light = action.LightType; // Determine the action of light according to the event raised if (!_dictSTLightActions.TryGetValue(light, out var oldAction)) _dictSTLightActions.Add(light, action); else _dictSTLightActions[light] = PickPriorAction(oldAction, action); } } } /// /// 获取需要打开Buzzer的事件的名称。 /// /// /// private IEnumerable GetBuzzerOnEvents(DictEvent events) { // create a list for the events operating the buzzer var buzzerOnEvents = events .Where(x => x.Value .Any(v => v.Light.IsBuzzer && v.Output != SignalTowerActions.Off)) .Select(x => x.Key); return buzzerOnEvents; } /// /// 创建一个动态RT事件,以驱动指定的信号塔组件。 /// /// 自动创建Guid作为事件的Key,以和STEvent配置字典的格式保持一致,以为Monitor方法中需要同时对这两个字典处理;格式的统一有助于统一处理任务的逻辑。 /// /// /// /// 该事件执行的动作列表。 /// /// 列表的每一个元素对应一个信号塔组件需执行的动作。 /// /// private void CreateDynamicEvent(string eventName, List actions) { _dictDynamicSTLightActions[eventName] = actions; } #endregion } }