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

591 lines
19 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.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
{
/// <summary>
/// 互锁管理器。
/// <remarks>
/// 互锁管理器作为独立工作的设备,被系统的后台循环调度。
/// <br/>
/// 当监测到某个InterlockLimit被触发时和该Limit相关的所有Action中定义的DO均被
/// 置为Action节点Value属性中定义电平的反向电平。
/// </remarks>
/// </summary>
public class InterlockManager : Singleton<InterlockManager>
{
#region Variables
private readonly List<InterlockAction> _lstActions;
private static List<IIOAccessor> _diMap;
private static List<IIOAccessor> _doMap;
private static List<IIOAccessor> _aiMap;
private static List<IIOAccessor> _aoMap;
/// <summary>
/// 以Module为单位当设置该Module所属的DO并触发互锁时是否输出Info而不是Warning。
/// </summary>
private Dictionary<ModuleName, bool> _dicModulePostInfo;
/// <summary>
/// 每个Module对应的Limit集合。
/// </summary>
private readonly Dictionary<ModuleName, List<IInterlockLimit>> _dicLimitsPerModule;
/// <summary>
/// 每个Module对应的DoAction集合。
/// </summary>
private readonly Dictionary<ModuleName, List<InterlockAction>> _dicActionsPerModule;
/// <summary>
/// 每个Limit对应的DoAction集合。
/// </summary>
private readonly Dictionary<IInterlockLimit, List<InterlockAction>> _dicLimitToActionMap;
#endregion
#region Constructors
/// <summary>
/// 互锁管理器的构造函数。
/// </summary>
public InterlockManager()
{
_lstActions = new();
_dicLimitToActionMap = new ();
_dicActionsPerModule = new();
_dicLimitsPerModule = new ();
_dicModulePostInfo = new();
}
#endregion
#region Methods
/// <summary>
/// 初始化互锁管理器。
/// </summary>
/// <param name="interlockFile">互锁配置文件。</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 bool Initialize(string interlockFile,
Dictionary<string, DOAccessor> doMap,
Dictionary<string, DIAccessor> diMap,
Dictionary<string, AIAccessor> aiMap,
Dictionary<string, AOAccessor> aoMap,
out string reason)
{
reason = "";
_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(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<string, string>();
var doActionLimits = new List<IInterlockLimit>();
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<InterlockAction>();
_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;
}
/// <summary>
/// 背景扫描线程执行的任务。
/// </summary>
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'));
}
}
}
}
/// <summary>
/// 设置指定Module的Interlock打印信息等级。
/// </summary>
/// <param name="module">模组名称</param>
/// <param name="isInfo">是否以Info等级打印信息</param>
public void SetEventLevel(string module, bool isInfo)
{
var moduleName = ModuleHelper.Converter(module);
if (_dicModulePostInfo.ContainsKey(moduleName))
_dicModulePostInfo[moduleName] = isInfo;
}
/// <summary>
/// 对指定的DO的操作是否满足互锁条件。
/// </summary>
/// <param name="doName">待操作的DO名称。</param>
/// <param name="onOff">
/// 指定的输出。
/// <value>True输出有效电平</value>
/// <br/>
/// <value>False清除有效电平输出</value>
/// </param>
/// <param name="reason">
/// 如果触发互锁限制,输出互锁限制的原因。
/// </param>
/// <returns></returns>
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;
}
/// <summary>
/// 获取系统配置中指定Module的ByPassInterlock参数设置值。
/// </summary>
/// <param name="module">
/// 模组名称, <see cref="ModuleName"/>
/// </param>
/// <returns></returns>
public static bool GetScBypassInterlockValue(ModuleName module)
{
return GetScBypassInterlockValue(module.ToString());
}
/// <summary>
/// 获取系统配置中指定Module的ByPassInterlock参数设置值。
/// </summary>
/// <param name="module">模组名称</param>
/// <returns></returns>
public static bool GetScBypassInterlockValue(string module)
{
if (ModuleHelper.IsPm(module))
return SC.SafeGetValue($"PM.{module}.BypassInterlock", false);
else
return SC.SafeGetValue($"{module}.BypassInterlock", false);
}
/// <summary>
/// 创建互锁显示条件对象。
/// </summary>
/// <remarks>
/// 注意对于同一个IO、相同状态的互锁限制条件该函数确保仅生成一个实例不会针对同一条件创建不同实例。
/// </remarks>
/// <param name="ioType">IO类型请参考<see cref="IOType"/>。</param>
/// <param name="ioName">IO名称。</param>
/// <param name="limitValue">当前限制条件中的IO状态。</param>
/// <param name="tip">默认语言提示信息。</param>
/// <param name="cultureTip">多国语言提示信息。</param>
/// <param name="reason">创建失败并返回null的原因。</param>
/// <returns>互锁限制条件对象的实例。</returns>
private IInterlockLimit CreateInterlockLimit(IOType ioType, string ioName, string limitValue,
string tip, Dictionary<string, string> cultureTip, out string reason)
{
Debug.Assert(Enum.TryParse<IOType>(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<InterlockAction>());
var moduleName = GetModuleFromIo(ioName);
if (!_dicLimitsPerModule.ContainsKey(moduleName))
_dicLimitsPerModule[moduleName] = new List<IInterlockLimit>();
_dicLimitsPerModule[moduleName].Add(limit);
return limit;
}
// 返回已存在的对象。
return limitExist;
}
/// <summary>
/// 通过XML配置创建互锁限制条件对象。
/// </summary>
/// <param name="xmlNodeLimit">包含Limit定义的Xml节点。</param>
/// <param name="reason">创建失败并返回null的原因。</param>
/// <returns></returns>
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;
}
// 节点不包含didoaiao或者不包含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<string, string>();
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;
}
/// <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>
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
}
}