using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Xml; using Aitex.Core.RT.Event; using Aitex.Core.RT.Log; using Aitex.Core.RT.SCCore; using Aitex.Core.Util; using MECF.Framework.Common.Equipment; using DictLimitToActionMap = System.Collections.Generic.Dictionary>; namespace Aitex.Core.RT.IOCore { /// /// 互锁管理器。 /// /// 互锁管理器作为独立工作的设备,被系统的后台循环调度。 ///
/// 当监测到某个InterlockLimit被触发时,和该Limit相关的所有Action中定义的DO均被 /// 置为Action节点Value属性中定义电平的反向电平。 ///
///
public class InterlockManager : Singleton { #region Variables private readonly List _lstActions; private readonly DictLimitToActionMap _dicLimitToActionMap; private static List _diMap; private static List _doMap; private static List _aiMap; private static List _aoMap; #endregion #region Constructors /// /// 互锁管理器的构造函数。 /// public InterlockManager() { _lstActions = new List(); _dicLimitToActionMap = new DictLimitToActionMap(); } #endregion #region Methods /// /// 初始化互锁管理器。 /// /// 互锁配置文件。 /// DO点表。 /// DI点表。 /// AO点表。 /// AI点表。 /// 初始化失败原因。 /// public bool Initialize(string interlockFile, Dictionary doMap, Dictionary diMap, Dictionary aiMap, Dictionary aoMap, out string reason) { reason = ""; _doMap = doMap.Values.Cast().ToList(); _diMap = diMap.Values.Cast().ToList(); _aiMap = aiMap.Values.Cast().ToList(); _aoMap = aoMap.Values.Cast().ToList(); var sbReason = new StringBuilder(); try { var doc = new XmlDocument(); doc.Load(interlockFile); var xmlNode = doc.SelectSingleNode("Interlock"); if(xmlNode == null) // 如果Interlock节点不存在 { var err = $"Failed to load interlock file, the node 'Interlock' is not found in file {interlockFile}."; sbReason.AppendLine(err); LOG.Error(err); return false; } foreach (XmlNode childNode in xmlNode.ChildNodes) { // 遍历'Action'节点 if (childNode.NodeType == XmlNodeType.Comment || childNode is not XmlElement xmlElement) continue; if (xmlElement.Name != "Action") { if (xmlElement.NodeType != XmlNodeType.Comment) LOG.Write("interlock config file contains no comments content, " + xmlElement.InnerXml); continue; } // Action节点仅支持对DO进行配置 if (!xmlElement.HasAttribute("do") || !xmlElement.HasAttribute("value")) { sbReason.AppendLine("action node has no [do] or [value] attribute"); continue; } var doName = xmlElement.GetAttribute("do"); var doValue = Convert.ToBoolean(xmlElement.GetAttribute("value")); var tip = string.Empty; var dicTips = new Dictionary(); var lstLimit = new List(); if (!doMap.ContainsKey(doName)) { sbReason.AppendLine("action node " + doName + " no such DO defined"); continue; } // 获取DO实例 var doAction = doMap[doName]; if (doAction == null) { // 如果DO不存在,则读取下一个Action节点 sbReason.AppendLine("action node " + doName + " no such DO defined"); continue; } if (xmlElement.HasAttribute("tip")) tip = xmlElement.GetAttribute("tip"); if (xmlElement.HasAttribute("tip.zh-CN")) dicTips["zh-CN"] = xmlElement.GetAttribute("tip.zh-CN"); if (xmlElement.HasAttribute("tip.en-US")) dicTips["en-US"] = xmlElement.GetAttribute("tip.en-US"); // 遍历Action下的Limit节点 foreach (XmlElement nodeLimit in xmlElement.ChildNodes) { // 获取InterlockLimit对象。 var limit = CreateInterlockLimit(nodeLimit, out var err); if(limit != null) lstLimit.Add(limit); else sbReason.AppendLine(err); } // 创建InterlockAction对象 var newAction = new InterlockAction(doAction, doValue, tip, dicTips, lstLimit); _lstActions.Add(newAction); // 创建InterlockLimit到被使用的InterlockAction映射字典 foreach (var limit in lstLimit) { // 检查InterlockLimit是否已经存在于字典中 var limitInDic = _dicLimitToActionMap.Keys.FirstOrDefault(x => x.UniqueId == limit.UniqueId); Debug.Assert(limitInDic != null, "The condition should never be hit"); // 存在则插入InterlockAction;不存在则新建 if (limitInDic != null) _dicLimitToActionMap[limitInDic].Add(newAction); else { _dicLimitToActionMap[limit] = new List { newAction }; } } } } catch (Exception ex) { sbReason.AppendLine(ex.Message); } if (sbReason.Length > 0) { reason = sbReason.ToString().TrimEnd('\n', '\r'); return false; } return true; } /// /// 背景扫描线程执行的任务。 /// public void Monitor() { // 如果系统设置中旁路了互锁,则不监测互锁条件 if (SC.ContainsItem("System.BypassInterlock") && SC.GetValue("System.BypassInterlock")) return; // 遍历每一个InterlockLimit对象。 var limits = _dicLimitToActionMap.Keys.ToList(); foreach (var limit in limits) { // 如果互锁没被触发 if (!limit.IsTriggered()) continue; var reverseInfo = new StringBuilder(); var module = "System"; var actions = _dicLimitToActionMap[limit].ToList(); foreach (var action in actions) { // 尝试根据Action定义复位该互锁限制条件对应的所有DO的电平 if (action.TryReverse(out var reason)) { var ss = action.ActionName.Split('.'); if (ss.Length > 1 && ModuleHelper.IsPm(ss[0])) module = ss[0]; reverseInfo.AppendLine(reason); } } // 如果PM腔有被恢复的DO,则打印信息并报警。 if (reverseInfo.Length > 0) { reverseInfo.Insert(0, $"Due to the {limit.Tip}, {limit.Name} is not [{limit.GetLimitValue()}]\r\n"); EV.PostWarningLog(module, reverseInfo.ToString().TrimEnd('\r', '\n')); } } } /// /// 对指定的DO的操作是否满足互锁条件。 /// /// 待操作的DO名称。 /// /// 指定的输出。 /// True:输出有效电平 ///
/// False:清除有效电平输出 /// /// /// 如果触发互锁限制,输出互锁限制的原因。 /// /// public bool CanSetDo(string doName, bool onOff, out string reason) { reason = string.Empty; if (SC.ContainsItem("System.BypassInterlock") && SC.GetValue("System.BypassInterlock")) { return true; } foreach (var action in _lstActions.Where(action => action.IsSame(doName, onOff))) { return action.CanDo(out reason); } return true; } /// /// 创建互锁显示条件对象。 /// /// /// 注意:对于同一个IO、相同状态的互锁限制条件,该函数确保仅生成一个实例,不会针对同一条件创建不同实例。 /// /// IO类型,请参考。 /// IO名称。 /// 当前限制条件中的IO状态。 /// 默认语言提示信息。 /// 多国语言提示信息。 /// 创建失败并返回null的原因。 /// 互锁限制条件对象的实例。 private IInterlockLimit CreateInterlockLimit(IOType ioType, string ioName, string limitValue, string tip, Dictionary cultureTip, out string reason) { Debug.Assert(Enum.TryParse(ioType.ToString(), out _),"Undefined IO Type"); Debug.Assert(!string.IsNullOrEmpty(ioName),"IO Name can not be empty"); Debug.Assert(!string.IsNullOrEmpty(limitValue),"LimitValue can not be empty"); reason = ""; // 创建一个InterlockLimit实例。 IIOAccessor io; IInterlockLimit limit; try { io = GetIoByIoType(ioType, ioName); } catch (IoNotFoundException) { reason = $"limit node {ioName} no such {ioType} defined"; return null; } catch (InvalidIoTypeExeption) { reason = $"limit node {ioName} no such io type defined"; return null; } switch (ioType) { case IOType.DI: limit = new DiLimit((DIAccessor)io, limitValue, tip, cultureTip); break; case IOType.DO: limit = new DoLimit((DOAccessor)io, limitValue, tip, cultureTip); break; case IOType.AI: limit = new AiLimit((AIAccessor)io, limitValue, tip, cultureTip); break; case IOType.AO: limit = new AoLimit((AOAccessor)io, limitValue, tip, cultureTip); break; default: throw new InvalidIoTypeExeption(); } // 检查limit是否已经存在。 var limitExist = _dicLimitToActionMap.Keys.FirstOrDefault(x => x.UniqueId == limit.UniqueId); // 如果不存在,则返回新创建的对象。 if (limitExist == null) { _dicLimitToActionMap.Add(limit, new List()); return limit; } // 返回已存在的对象。 return limitExist; } /// /// 通过XML配置创建互锁限制条件对象。 /// /// 包含Limit定义的Xml节点。 /// 创建失败并返回null的原因。 /// private IInterlockLimit CreateInterlockLimit(XmlElement xmlNodeLimit, out string reason) { reason = ""; // 检查节点名称是否为Limit if (xmlNodeLimit.Name != "Limit") // 节点名称不是Limit { reason = "the name of xml node is not 'Limit'"; if (xmlNodeLimit.NodeType != XmlNodeType.Comment) { reason = "interlock config file contains no comments content, " + xmlNodeLimit.InnerXml; LOG.Write(reason); } return null; } // 节点不包含‘di’、‘do’、‘ai’、‘ao’,或者不包含‘value’属性 string limitIoName; IOType limitIoType; if (xmlNodeLimit.HasAttribute("di")) { limitIoName = xmlNodeLimit.GetAttribute("di"); limitIoType = IOType.DI; } else if (xmlNodeLimit.HasAttribute("do")) { limitIoName = xmlNodeLimit.GetAttribute("do"); limitIoType = IOType.DO; } else if (xmlNodeLimit.HasAttribute("ai")) { limitIoName = xmlNodeLimit.GetAttribute("ai"); limitIoType = IOType.AI; } else if (xmlNodeLimit.HasAttribute("ao")) { limitIoName = xmlNodeLimit.GetAttribute("ao"); limitIoType = IOType.AO; } else { reason = "limit node lack of di/do/ai/ao/io attribute"; return null; } string limitValue; if (xmlNodeLimit.HasAttribute("value")) limitValue = xmlNodeLimit.GetAttribute("value"); else { reason = "limit node lack of value attribute"; return null; } var tip = string.Empty; var dicLimitTips = new Dictionary(); if (xmlNodeLimit.HasAttribute("tip")) tip = xmlNodeLimit.GetAttribute("tip"); if (xmlNodeLimit.HasAttribute("tip.zh-CN")) { dicLimitTips["zh-CN"] = xmlNodeLimit.GetAttribute("tip.zh-CN"); if (string.IsNullOrEmpty(tip)) tip = dicLimitTips["zh-CN"]; } if (xmlNodeLimit.HasAttribute("tip.en-US")) { dicLimitTips["en-US"] = xmlNodeLimit.GetAttribute("tip.en-US"); if (string.IsNullOrEmpty(tip)) tip = dicLimitTips["en-US"]; } var limit = CreateInterlockLimit(limitIoType, limitIoName, limitValue, tip, dicLimitTips, out reason); return limit; } /// /// 根据给定的IO类型和IO名称,从IO列表中获取IO对象实例。 /// /// IO类型,请参考。 /// IO名称。 /// /// 无效的IO类型。 /// 未找到IO。 private static IIOAccessor GetIoByIoType(IOType type, string ioName) { List dictMap; switch (type) { case IOType.DI: dictMap = _diMap; break; case IOType.DO: dictMap = _doMap; break; case IOType.AI: dictMap = _aiMap; break; case IOType.AO: dictMap = _aoMap; break; default: throw new InvalidIoTypeExeption(); } var io = dictMap.FirstOrDefault(x => x.Name == ioName); if (io != null) return io; throw new IoNotFoundException(type, ioName); } #endregion } }