using System; 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.IOCore.Interlock; using Aitex.Core.RT.IOCore.Interlock.DataProvider; using Aitex.Core.RT.Log; using Aitex.Core.RT.SCCore; using Aitex.Core.Util; using MECF.Framework.Common.Equipment; namespace Aitex.Core.RT.IOCore { /// /// 互锁管理器。 /// /// 互锁管理器作为独立工作的设备,被系统的后台循环调度。 ///
/// 当监测到某个InterlockLimit被触发时,和该Limit相关的所有Action中定义的DO均被 /// 置为Action节点Value属性中定义电平的反向电平。 ///
///
public class InterlockManager : Singleton { #region Variables private readonly List _lstActions; private static List _diMap; private static List _doMap; private static List _aiMap; private static List _aoMap; /// /// 以Module为单位,当设置该Module所属的DO并触发互锁时,是否输出Info而不是Warning。 /// private Dictionary _dicModulePostInfo; /// /// 每个Module对应的Limit集合。 /// private readonly Dictionary> _dicLimitsPerModule; /// /// 每个Module对应的DoAction集合。 /// private readonly Dictionary> _dicActionsPerModule; /// /// 每个Limit对应的DoAction集合。 /// private readonly Dictionary> _dicLimitToActionMap; #endregion #region Constructors /// /// 互锁管理器的构造函数。 /// public InterlockManager() { _lstActions = new(); _dicLimitToActionMap = new (); _dicActionsPerModule = new(); _dicLimitsPerModule = new (); _dicModulePostInfo = new(); } #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 doActionLimits = new List(); if (!doMap.ContainsKey(doName)) { sbReason.AppendLine("action node " + doName + " no such DO defined"); continue; } // 获取DO实例 var targetDo = doMap[doName]; if (targetDo == 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节点,并创建InterlockLimit对象。 foreach (XmlElement nodeLimit in xmlElement.ChildNodes) { // 获取InterlockLimit对象。 var limit = CreateInterlockLimit(nodeLimit, out var err); if (limit != null) { doActionLimits.Add(limit); } else sbReason.AppendLine(err); } // 创建InterlockAction对象 var action = new InterlockAction(targetDo, doValue, tip, dicTips, doActionLimits); _lstActions.Add(action); var moduleName = GetModuleFromIo(targetDo.Name); if (!_dicActionsPerModule.ContainsKey(moduleName)) _dicActionsPerModule[moduleName] = new List(); _dicActionsPerModule[moduleName].Add(action); // 创建InterlockLimit到被使用的InterlockAction映射字典 foreach (var limit in doActionLimits) { // 检查InterlockLimit是否已经存在于字典中 var exists = _dicLimitToActionMap.ContainsKey(limit); Debug.Assert(exists, "The limit object is not in the mapping dictionary"); // 当前Limit是否反向绑定了其所影响的所有DoAction _dicLimitToActionMap[limit].Add(action); } } _dicModulePostInfo = _dicLimitsPerModule.Keys.ToDictionary(x => x, x=>false); Debug.Assert(!_dicActionsPerModule.ContainsKey(ModuleName.UnDefined), $"InterlockManager {nameof(_dicActionsPerModule)} contains key {ModuleName.UnDefined}"); Debug.Assert(!_dicLimitsPerModule.ContainsKey(ModuleName.UnDefined), $"InterlockManager {nameof(_dicLimitsPerModule)} contains key {ModuleName.UnDefined}"); } catch (Exception ex) { sbReason.AppendLine(ex.Message); } if (sbReason.Length > 0) { reason = sbReason.ToString().TrimEnd('\n', '\r'); return false; } return true; } /// /// 背景扫描线程执行的任务。 /// public void Monitor() { // 按Module扫描Interlock Limit foreach (var moduleName in _dicLimitsPerModule.Keys.ToList()) { Debug.Assert(moduleName != ModuleName.UnDefined, $"Interlock Manager CanSetDo() undesired module name {ModuleName.UnDefined}"); // 检查当前Module是否旁路Interlock var isBypassInterlock = GetScBypassInterlockValue(moduleName); if (isBypassInterlock) continue; foreach (var limit in _dicLimitsPerModule[moduleName].ToList()) { // 如果互锁没被触发 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"); // Post事件的类型, Info还是Warning _dicModulePostInfo.TryGetValue(moduleName, out var isPostInfo); if (isPostInfo) EV.PostInfoLog(module, reverseInfo.ToString().TrimEnd('\r', '\n')); else EV.PostWarningLog(module, reverseInfo.ToString().TrimEnd('\r', '\n')); } } } } /// /// 设置指定Module的Interlock打印信息等级。 /// /// 模组名称 /// 是否以Info等级打印信息 public void SetEventLevel(string module, bool isInfo) { var moduleName = ModuleHelper.Converter(module); if (_dicModulePostInfo.ContainsKey(moduleName)) _dicModulePostInfo[moduleName] = isInfo; } /// /// 对指定的DO的操作是否满足互锁条件。 /// /// 待操作的DO名称。 /// /// 指定的输出。 /// True:输出有效电平 ///
/// False:清除有效电平输出 /// /// /// 如果触发互锁限制,输出互锁限制的原因。 /// /// public bool CanSetDo(string doName, bool onOff, out string reason) { reason = string.Empty; var moduleName = GetModuleFromIo(doName); Debug.Assert(moduleName != ModuleName.UnDefined, $"Interlock Manager CanSetDo() undesired module name {ModuleName.UnDefined}"); var isBypassInterlock = GetScBypassInterlockValue(moduleName); if (isBypassInterlock) return true; foreach (var action in _lstActions.Where(action => action.IsSame(doName, onOff))) { return action.CanDo(out reason); } return true; } /// /// 获取系统配置中指定Module的ByPassInterlock参数设置值。 /// /// /// 模组名称, /// /// public static bool GetScBypassInterlockValue(ModuleName module) { return GetScBypassInterlockValue(module.ToString()); } /// /// 获取系统配置中指定Module的ByPassInterlock参数设置值。 /// /// 模组名称 /// public static bool GetScBypassInterlockValue(string module) { if (ModuleHelper.IsPm(module)) return SC.SafeGetValue($"PM.{module}.BypassInterlock", false); else return SC.SafeGetValue($"{module}.BypassInterlock", false); } /// /// 创建互锁显示条件对象。 /// /// /// 注意:对于同一个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(new DiValueProvider((DIAccessor)io), limitValue, tip, cultureTip); break; case IOType.DO: limit = new DoLimit(new DoValueProvider((DOAccessor)io), limitValue, tip, cultureTip); break; case IOType.AI: limit = new AiLimit(new AiValueProvider((AIAccessor)io), limitValue, tip, cultureTip); break; case IOType.AO: limit = new AoLimit(new AoValueProvider((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()); var moduleName = GetModuleFromIo(ioName); if (!_dicLimitsPerModule.ContainsKey(moduleName)) _dicLimitsPerModule[moduleName] = new List(); _dicLimitsPerModule[moduleName].Add(limit); 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); } /// /// 从指定的IO中解析该IO所属的模组。 /// /// /// private static ModuleName GetModuleFromIo(string ioName) { Debug.Assert(!string.IsNullOrEmpty(ioName), "the IO name can not be empty."); if (string.IsNullOrEmpty(ioName)) { LOG.Error($"InterlockManager GetModuleFromIo failed, the parameter ioName is empty"); return ModuleName.UnDefined; } // Simulator中TM的Io表配置中没有"TM."前缀; // 为兼容此问题,如果没有找到Module前缀,则用System代替 var posDot = ioName.IndexOf('.'); if (posDot <= 0) return ModuleName.System; var moduleName = ioName.Substring(0, posDot); return ModuleHelper.Converter(moduleName); } #endregion } }