using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Xml; using Aitex.Core.RT.IOCore.Interlock.DataProvider; using Aitex.Core.RT.IOCore.Interlock.Limits; using Aitex.Core.RT.Log; using MECF.Framework.Common.Equipment; namespace Aitex.Core.RT.IOCore.Interlock; public abstract class InterlockManagerBase where TAction : IInterlockAction { #region Variables private static List _diMap; private static List _doMap; private static List _aiMap; private static List _aoMap; protected string RootNodeName; protected readonly ConcurrentDictionary<(string actionName, bool doValue), TAction> _dictINTLKActionsFileLoader; /// /// 每个Limit对应的DoAction集合。 /// protected readonly Dictionary> _dictLIMT2INTLKActionMap; /// /// 每个Module对应的DoAction集合。 /// protected readonly Dictionary> _dictINTLKActionsPerModule; /// /// 每个Module对应的Limit集合。 /// protected readonly Dictionary> _dictLIMTPerModule; #endregion #region Constructors /// /// 互锁管理器的构造函数。 /// protected InterlockManagerBase() { _dictINTLKActionsFileLoader = new(); _dictINTLKActionsPerModule = new(); _dictLIMT2INTLKActionMap = new (); _dictLIMTPerModule = new (); } #endregion #region Methods /// /// 初始化互锁管理器。 /// /// 互锁配置文件。 /// DO点表。 /// DI点表。 /// AO点表。 /// AI点表。 /// 初始化失败原因。 /// public virtual bool Initialize(string configFile, Dictionary doMap, Dictionary diMap, Dictionary aiMap, Dictionary aoMap, out string reason) { reason = ""; if (string.IsNullOrEmpty(configFile)) { reason = "interlock daemon config file does not specified"; return false; } if (!File.Exists(configFile)) { reason = $"interlock daemon config file {configFile} does not exist"; return false; } _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(configFile); var xmlNode = doc.SelectSingleNode(RootNodeName); if (xmlNode == null) // 如果Interlock节点不存在 { var err = $"Failed to load interlock daemon file, the node 'Daemon' is not found in file {configFile}."; sbReason.AppendLine(err); LOG.Error(err); return false; } foreach (XmlNode childNode in xmlNode.ChildNodes) { // 遍历'Action'节点 if (!CheckIsXmlElement(childNode, out var actionNode)) continue; if (actionNode.Name != "Action") { if (actionNode.NodeType != XmlNodeType.Comment) LOG.Write("interlock daemon file contains no comments content, " + actionNode.InnerXml); continue; } // Action节点仅支持对DO进行配置 if (!actionNode.HasAttribute("do") || !actionNode.HasAttribute("value")) { sbReason.AppendLine("action node has no [do] or [value] attribute"); continue; } var doName = actionNode.GetAttribute("do"); var doValue = Convert.ToBoolean(actionNode.GetAttribute("value")); var tip = string.Empty; var dicTips = new Dictionary(); 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 (actionNode.HasAttribute("tip")) tip = actionNode.GetAttribute("tip"); if (actionNode.HasAttribute("tip.zh-CN")) dicTips["zh-CN"] = actionNode.GetAttribute("tip.zh-CN"); if (actionNode.HasAttribute("tip.en-US")) dicTips["en-US"] = actionNode.GetAttribute("tip.en-US"); var ignoreReverse_All = false;//单个Action的全局ignore,为True时所有Limit条件不反向控制 if (actionNode.HasAttribute("ignoreReverse")) { var strIgnoreReverse = actionNode.GetAttribute("ignoreReverse"); if (!bool.TryParse(strIgnoreReverse, out ignoreReverse_All)) LOG.Error($"Unable to convert attribute 'ignoreReverse' of action '{doName}' to bool."); } var moduleName = GetModuleFromIo(targetDo.Name); if (!_dictINTLKActionsPerModule.ContainsKey(moduleName)) _dictINTLKActionsPerModule[moduleName] = new List(); // 创建InterlockAction对象,首先判断该动作是否已经存在,若存在,则报错 var action = (TAction)Activator.CreateInstance(typeof(TAction), moduleName.ToString(), targetDo, doValue, tip, dicTips); if (_dictINTLKActionsFileLoader.ContainsKey((action.ActionName, doValue))) { var err = $"Interlock Action {action.ActionName}={doValue} duplicated in {configFile}"; LOG.Error(err); Debug.Assert(false, err); } // 遍历Action下的Limit节点,并创建InterlockLimit对象。 foreach (XmlNode limitNode in actionNode.ChildNodes) { if (!CheckIsXmlElement(limitNode, out var node)) continue; if (node.Name.ToLower() == "or") { var groupOr = CreateOrLimitGroup(node, out var err0); action.AddLogicOrGroup(groupOr); } else { // 获取InterlockLimit对象。 var limit = CreateInterlockLimit(node, out var err); if (limit != null) action.AddLimit(limit); else sbReason.AppendLine(err); } } _dictINTLKActionsPerModule[moduleName].Add(action); if (_dictINTLKActionsFileLoader.ContainsKey((action.ActionName, doValue))) { LOG.Error($"Duplicate interlock action definition: {action.ActionName}, {doValue}"); continue; } if(!_dictINTLKActionsFileLoader.TryAdd((action.ActionName, doValue), action)) LOG.Error($"Unable to add {action} to the ConcurrentDictionary"); // 如果当前Action设置了忽略翻转,则不要注册Limit到当前Action的映射,避免Limit触发导致Action翻转。 if (!ignoreReverse_All) { foreach (var limit in action.Limits) { if (!limit.IgnoreReverse)//如果单个limit也忽略反转,不要注册Limit到当前Action的映射,避免Limit触发导致Action翻转。 { // 创建以Limit分组的Action字典 if (!_dictLIMT2INTLKActionMap.ContainsKey(limit)) _dictLIMT2INTLKActionMap[limit] = new List(); _dictLIMT2INTLKActionMap[limit].Add(action); } } } } Debug.Assert(!_dictINTLKActionsPerModule.ContainsKey(ModuleName.UnDefined), $"InterlockManager {nameof(_dictINTLKActionsPerModule)} contains key {ModuleName.UnDefined}"); Debug.Assert(!_dictLIMTPerModule.ContainsKey(ModuleName.UnDefined), $"InterlockManager {nameof(_dictLIMTPerModule)} contains key {ModuleName.UnDefined}"); } catch (Exception ex) { sbReason.AppendLine(ex.Message); } finally { if (sbReason.Length > 0) reason = sbReason.ToString().TrimEnd('\n', '\r'); } return sbReason.Length == 0; } /// /// 背景扫描线程执行的任务。 /// public abstract void Monitor(); /// /// 通过XML配置创建互锁限制条件对象。 /// /// 包含Limit定义的Xml节点。 /// 创建失败并返回null的原因。 /// private IInterlockLimit CreateInterlockLimit(XmlElement limitNode, out string reason) { reason = ""; // 检查节点名称是否为Limit if (limitNode.Name != "Limit") // 节点名称不是Limit { reason = "the name of xml node is not 'Limit'"; if (limitNode.NodeType != XmlNodeType.Comment) { reason = "interlock config file contains no comments content, " + limitNode.InnerXml; LOG.Write(reason); } return null; } string limitValue; if (limitNode.HasAttribute("value")) limitValue = limitNode.GetAttribute("value"); else { reason = "limit node lack of value attribute"; return null; } var tip = string.Empty; var dicLimitTips = new Dictionary(); if (limitNode.HasAttribute("tip")) tip = limitNode.GetAttribute("tip"); if (limitNode.HasAttribute("tip.zh-CN")) { dicLimitTips["zh-CN"] = limitNode.GetAttribute("tip.zh-CN"); if (string.IsNullOrEmpty(tip)) tip = dicLimitTips["zh-CN"]; } if (limitNode.HasAttribute("tip.en-US")) { dicLimitTips["en-US"] = limitNode.GetAttribute("tip.en-US"); if (string.IsNullOrEmpty(tip)) tip = dicLimitTips["en-US"]; } var ignoreReverse = false; if (limitNode.HasAttribute("ignoreReverse")) { var strIgnoreReverse = limitNode.GetAttribute("ignoreReverse"); if (!bool.TryParse(strIgnoreReverse, out ignoreReverse)) LOG.Error($"Unable to convert attribute 'ignoreReverse' of Limit to bool."); } // 节点不包含‘di’、‘do’、‘ai’、‘ao’,或者不包含‘value’属性 IInterlockLimit limit; if (limitNode.HasAttribute("di")) { var limitName = limitNode.GetAttribute("di"); var io = GetIoByIoType(IOType.DI, limitName); var provider = new DiValueProvider((DIAccessor)io); limit = new DiLimit(provider, limitValue, tip, dicLimitTips, ignoreReverse); } else if (limitNode.HasAttribute("do")) { var limitName = limitNode.GetAttribute("do"); var io = GetIoByIoType(IOType.DO, limitName); var provider = new DoValueProvider((DOAccessor)io); limit = new DoLimit(provider, limitValue, tip, dicLimitTips, ignoreReverse); } else if (limitNode.HasAttribute("ai")) { var limitName = limitNode.GetAttribute("ai"); var io = GetIoByIoType(IOType.AI, limitName); var provider = new AiValueProvider((AIAccessor)io); limit = new AiLimit(provider, limitValue, tip, dicLimitTips, ignoreReverse); } else if (limitNode.HasAttribute("ao")) { var limitName = limitNode.GetAttribute("ao"); var io = GetIoByIoType(IOType.AO, limitName); var provider = new AoValueProvider((AOAccessor)io); limit = new AoLimit(provider, limitValue, tip, dicLimitTips, ignoreReverse); } else if (limitNode.HasAttribute("polldouble")) { var limitName = limitNode.GetAttribute("polldouble"); var provider = new DoubleDataPollProvider(limitName); limit = new DoubleDataPollLimit(provider, limitValue, tip, dicLimitTips, ignoreReverse); } else if (limitNode.HasAttribute("pollbool")) { var limitName = limitNode.GetAttribute("pollbool"); var provider = new BoolDataPollProvider(limitName); limit = new BoolDataPollLimit(provider, limitValue, tip, dicLimitTips, ignoreReverse); } else { reason = "limit node lack of di/do/ai/ao/io/pollbool/polldouble attribute"; return null; } // 从_dictLIMTPerModule字典中查找当前创建的Limit是否已经存在; // 如果存在,直接返回已存在的Limit实例; // 否则,将当前创建的Limit放到字典中,然后返回实例; var moduleName = GetModuleFromIo(limit.Name); if (_dictLIMTPerModule.TryGetValue(moduleName, out var list)) { var existLimit = list.FirstOrDefault(x => x.UniqueId == limit.UniqueId); if(existLimit != null) limit = existLimit; // 返回字典中的Limit实例 else list.Add(limit); // 将当前Limit加入字典 } else { // 在字典中创建Module,并将Limit实例放入字典 _dictLIMTPerModule[moduleName] = new List(new[] { limit }); } return limit; } /// /// 创建“OR”定义的Limit组。 /// /// /// /// private List CreateOrLimitGroup(XmlNode xmlNodeOr, out string reason) { reason = ""; var limitList = new List(); foreach (XmlNode childNode in xmlNodeOr.ChildNodes) { if (!CheckIsXmlElement(childNode, out var node)) continue; var limit = CreateInterlockLimit(node, out reason); if (limit != null) limitList.Add(limit); else return null; } return limitList; } /// /// 根据给定的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所属的模组。 /// /// /// protected 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); } /// /// 检查制定的XmlNode是否为XmlElement. /// /// /// /// private static bool CheckIsXmlElement(XmlNode node, out XmlElement element) { element = null; if (node.NodeType != XmlNodeType.Comment && node is XmlElement) { element = (XmlElement)node; return true; } return false; } #endregion }