Sic.Framework-Nanjing-Baishi/MECF.Framework.Common/Aitex/Core/RT/IOCore/Interlock/InterlockManagerBase.cs

502 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<TAction>
where TAction : IInterlockAction
{
#region Variables
private static List<IIOAccessor> _diMap;
private static List<IIOAccessor> _doMap;
private static List<IIOAccessor> _aiMap;
private static List<IIOAccessor> _aoMap;
protected string RootNodeName;
protected readonly ConcurrentDictionary<(string actionName, bool doValue), TAction> _dictINTLKActionsFileLoader;
/// <summary>
/// 每个Limit对应的DoAction集合。
/// </summary>
protected readonly Dictionary<IInterlockLimit, List<IInterlockAction>> _dictLIMT2INTLKActionMap;
/// <summary>
/// 每个Module对应的DoAction集合。
/// </summary>
protected readonly Dictionary<ModuleName, List<TAction>> _dictINTLKActionsPerModule;
/// <summary>
/// 每个Module对应的Limit集合。
/// </summary>
protected readonly Dictionary<ModuleName, List<IInterlockLimit>> _dictLIMTPerModule;
#endregion
#region Constructors
/// <summary>
/// 互锁管理器的构造函数。
/// </summary>
protected InterlockManagerBase()
{
_dictINTLKActionsFileLoader = new();
_dictINTLKActionsPerModule = new();
_dictLIMT2INTLKActionMap = new ();
_dictLIMTPerModule = new ();
}
#endregion
#region Methods
/// <summary>
/// 初始化互锁管理器。
/// </summary>
/// <param name="configFile">互锁配置文件。</param>
/// <param name="doMap">DO点表。</param>
/// <param name="diMap">DI点表。</param>
/// <param name="aoMap">AO点表。</param>
/// <param name="aiMap">AI点表。</param>
/// <param name="reason">初始化失败原因。</param>
/// <returns></returns>
public virtual bool Initialize(string configFile,
Dictionary<string, DOAccessor> doMap,
Dictionary<string, DIAccessor> diMap,
Dictionary<string, AIAccessor> aiMap,
Dictionary<string, AOAccessor> 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<IIOAccessor>().ToList();
_diMap = diMap.Values.Cast<IIOAccessor>().ToList();
_aiMap = aiMap.Values.Cast<IIOAccessor>().ToList();
_aoMap = aoMap.Values.Cast<IIOAccessor>().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<string, string>();
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<TAction>();
// 创建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<IInterlockAction>();
_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;
}
/// <summary>
/// 背景扫描线程执行的任务。
/// </summary>
public abstract void Monitor();
/// <summary>
/// 通过XML配置创建互锁限制条件对象。
/// </summary>
/// <param name="limitNode">包含Limit定义的Xml节点。</param>
/// <param name="reason">创建失败并返回null的原因。</param>
/// <returns></returns>
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<string, string>();
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.");
}
// 节点不包含didoaiao或者不包含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<IInterlockLimit>(new[] { limit });
}
return limit;
}
/// <summary>
/// 创建“OR”定义的Limit组。
/// </summary>
/// <param name="xmlNodeOr"></param>
/// <param name="reason"></param>
/// <returns></returns>
private List<IInterlockLimit> CreateOrLimitGroup(XmlNode xmlNodeOr, out string reason)
{
reason = "";
var limitList = new List<IInterlockLimit>();
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;
}
/// <summary>
/// 根据给定的IO类型和IO名称从IO列表中获取IO对象实例。
/// </summary>
/// <param name="type">IO类型请参考<see cref="IOType"/>。</param>
/// <param name="ioName">IO名称。</param>
/// <returns></returns>
/// <exception cref="InvalidIoTypeExeption">无效的IO类型。</exception>
/// <exception cref="IoNotFoundException">未找到IO。</exception>
private static IIOAccessor GetIoByIoType(IOType type, string ioName)
{
List<IIOAccessor> 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);
}
/// <summary>
/// 从指定的IO中解析该IO所属的模组。
/// </summary>
/// <param name="ioName"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 检查制定的XmlNode是否为XmlElement.
/// </summary>
/// <param name="node"></param>
/// <param name="element"></param>
/// <returns></returns>
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
}