修正Interlock可能过早触发的问题。

优化IIoBuffer接口和IoManager对象
- 增加DOMap、DIMap等属性,使私有的_doMap等字段可以被外部对象访问,准备好分离IoManager和InterlockManager。
- 移除初始化InterlockManager的代码。
- 移除OnTimer方法,以及调用OnTimer的后台县城。

优化IIoProvider接口、IoProvider对象
- 新增IsSynced属性,用于检查IoProvider对象是否已经和PLC进行了数据同步。
- 优化IoProvider对象的OnTimer方法中的代码,整理结构并删除多余的代码。

优化IoProviderManager对象
- 新增WaitFirstSync方法,用于检测是否所有的IoProvider均同步了PLC数据。

优化InterlockManagerBase和InterlockManager对象
- Initialize方法返回值由bool变更为void。
- Initialize方法参数表中的doMap、diMap等参数变更为IIoBuffer对象。
- 新增OnTimer方法以及PeriodicJob变量,用于背景线程中执行互锁检查。

优化代码格式
- IoDataCache对象中增加一些注释。
This commit is contained in:
SL 2024-03-24 12:09:11 +08:00
parent 18a96c27ae
commit 2dd2854ecd
9 changed files with 206 additions and 143 deletions

View File

@ -6,6 +6,7 @@ using Aitex.Core.RT.Event;
using Aitex.Core.RT.IOCore.Interlock.Actions;
using Aitex.Core.RT.SCCore;
using MECF.Framework.Common.Equipment;
using MECF.Framework.RT.Core.IoProviders;
namespace Aitex.Core.RT.IOCore.Interlock
{
@ -41,33 +42,20 @@ namespace Aitex.Core.RT.IOCore.Interlock
}
#endregion
#region Methods
/// <summary>
/// 初始化互锁管理器。
/// </summary>
/// <param name="configFie">互锁配置文件。</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>
/// <param name="ioManager">点表。</param>ioManager
/// <returns></returns>
public override bool Initialize(string configFie,
Dictionary<string, DOAccessor> doMap,
Dictionary<string, DIAccessor> diMap,
Dictionary<string, AIAccessor> aiMap,
Dictionary<string, AOAccessor> aoMap,
out string reason)
public override void Initialize(string configFie, IIoBuffer ioManager)
{
reason = "";
var succeeded = base.Initialize(configFie, doMap, diMap, aiMap, aoMap, out reason);
if (!succeeded)
return false;
_dicModulePostInfo = _dictINTLKActionsPerModule.Keys.ToDictionary(x => x, x => false);
return true;
base.Initialize(configFie, ioManager);
_dicModulePostInfo = _dictINTLKActionsPerModule?.Keys.ToDictionary(x => x, x => false) ?? new();
}
/// <summary>

View File

@ -9,7 +9,9 @@ using System.Xml;
using Aitex.Core.RT.IOCore.Interlock.DataProvider;
using Aitex.Core.RT.IOCore.Interlock.Limits;
using Aitex.Core.RT.Log;
using Aitex.Core.Util;
using MECF.Framework.Common.Equipment;
using MECF.Framework.RT.Core.IoProviders;
namespace Aitex.Core.RT.IOCore.Interlock;
@ -18,10 +20,10 @@ public abstract class InterlockManagerBase<TAction>
{
#region Variables
private static List<IIOAccessor> _diMap;
private static List<IIOAccessor> _doMap;
private static List<IIOAccessor> _aiMap;
private static List<IIOAccessor> _aoMap;
private List<IIOAccessor> _diMap;
private List<IIOAccessor> _doMap;
private List<IIOAccessor> _aiMap;
private List<IIOAccessor> _aoMap;
protected string RootNodeName;
@ -42,6 +44,9 @@ public abstract class InterlockManagerBase<TAction>
/// </summary>
protected readonly Dictionary<ModuleName, List<IInterlockLimit>> _dictLIMTPerModule;
private PeriodicJob _monitorThread;
private R_TRIG _rTrigMonitorError;
#endregion
#region Constructors
@ -65,37 +70,23 @@ public abstract class InterlockManagerBase<TAction>
/// 初始化互锁管理器。
/// </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>
/// <param name="ioManager">点表。</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)
public virtual void Initialize(string configFile, IIoBuffer ioManager)
{
reason = "";
Debug.Assert(!string.IsNullOrEmpty(configFile));
Debug.Assert(ioManager != null);
if (string.IsNullOrEmpty(configFile))
{
reason = "interlock daemon config file does not specified";
return false;
LOG.Error("Interlock config file does not specified.");
return;
}
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();
_doMap = ioManager.DOMap.Values.Cast<IIOAccessor>().ToList();
_diMap = ioManager.DIMap.Values.Cast<IIOAccessor>().ToList();
_aiMap = ioManager.AIMap.Values.Cast<IIOAccessor>().ToList();
_aoMap = ioManager.AOMap.Values.Cast<IIOAccessor>().ToList();
var sbReason = new StringBuilder();
try
@ -107,10 +98,10 @@ public abstract class InterlockManagerBase<TAction>
if (xmlNode == null) // 如果Interlock节点不存在
{
var err =
$"Failed to load interlock daemon file, the node 'Daemon' is not found in file {configFile}.";
$"Failed to load interlock config file, the node '{RootNodeName}' is not found in {configFile}.";
sbReason.AppendLine(err);
LOG.Error(err);
return false;
return;
}
foreach (XmlNode childNode in xmlNode.ChildNodes)
@ -139,14 +130,14 @@ public abstract class InterlockManagerBase<TAction>
var doValue = Convert.ToBoolean(actionNode.GetAttribute("value"));
var tip = string.Empty;
var dicTips = new Dictionary<string, string>();
if (!doMap.ContainsKey(doName))
if (!ioManager.DOMap.ContainsKey(doName))
{
sbReason.AppendLine("action node " + doName + " no such DO defined");
continue;
}
// 获取DO实例
var targetDo = doMap[doName];
var targetDo = ioManager.DOMap[doName];
if (targetDo == null)
{
// 如果DO不存在则读取下一个Action节点
@ -163,11 +154,11 @@ public abstract class InterlockManagerBase<TAction>
if (actionNode.HasAttribute("tip.en-US"))
dicTips["en-US"] = actionNode.GetAttribute("tip.en-US");
var ignoreReverse_All = false;//单个Action的全局ignore为True时所有Limit条件不反向控制
var ignoreReverseAll = false; //单个Action的全局ignore为True时所有Limit条件不反向控制
if (actionNode.HasAttribute("ignoreReverse"))
{
var strIgnoreReverse = actionNode.GetAttribute("ignoreReverse");
if (!bool.TryParse(strIgnoreReverse, out ignoreReverse_All))
if (!bool.TryParse(strIgnoreReverse, out ignoreReverseAll))
LOG.Error($"Unable to convert attribute 'ignoreReverse' of action '{doName}' to bool.");
}
@ -217,22 +208,22 @@ public abstract class InterlockManagerBase<TAction>
continue;
}
if(!_dictINTLKActionsFileLoader.TryAdd((action.ActionName, doValue), action))
if (!_dictINTLKActionsFileLoader.TryAdd((action.ActionName, doValue), action))
LOG.Error($"Unable to add {action} to the ConcurrentDictionary");
// 如果当前Action设置了忽略翻转则不要注册Limit到当前Action的映射避免Limit触发导致Action翻转。
if (!ignoreReverse_All)
if (!ignoreReverseAll)
{
foreach (var limit in action.Limits)
{
if (!limit.IgnoreReverse)//如果单个limit也忽略反转不要注册Limit到当前Action的映射避免Limit触发导致Action翻转。
if (!limit.IgnoreReverse) //如果单个limit也忽略反转不要注册Limit到当前Action的映射避免Limit触发导致Action翻转。
{
// 创建以Limit分组的Action字典
if (!_dictLIMT2INTLKActionMap.ContainsKey(limit))
_dictLIMT2INTLKActionMap[limit] = new List<IInterlockAction>();
_dictLIMT2INTLKActionMap[limit].Add(action);
}
}
}
}
@ -240,7 +231,7 @@ public abstract class InterlockManagerBase<TAction>
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}");
}
@ -250,11 +241,25 @@ public abstract class InterlockManagerBase<TAction>
}
finally
{
if (sbReason.Length > 0)
reason = sbReason.ToString().TrimEnd('\n', '\r');
_monitorThread ??= new PeriodicJob(200, OnTimer, $"{GetType().Name} Monitor Thread", isStartNow: true);
}
}
private bool OnTimer()
{
try
{
Monitor();
_rTrigMonitorError.CLK = false;
}
catch (Exception ex)
{
_rTrigMonitorError.CLK = true;
if (_rTrigMonitorError.Q)
LOG.Write(ex);
}
return sbReason.Length == 0;
return true;
}
/// <summary>
@ -424,7 +429,7 @@ public abstract class InterlockManagerBase<TAction>
/// <returns></returns>
/// <exception cref="InvalidIoTypeExeption">无效的IO类型。</exception>
/// <exception cref="IoNotFoundException">未找到IO。</exception>
private static IIOAccessor GetIoByIoType(IOType type, string ioName)
private IIOAccessor GetIoByIoType(IOType type, string ioName)
{
List<IIOAccessor> dictMap;
switch (type)

View File

@ -2,6 +2,11 @@ using System.Collections.Generic;
namespace MECF.Framework.Common.IOCore;
/// <summary>
/// Key: Offset
/// Value: Buffer
/// </summary>
/// <typeparam name="T"></typeparam>
public class IoDataCachePerOffset<T> : Dictionary<int, T>, IIoDataCache
{
public IoDataCachePerOffset(string providerName)

View File

@ -7,7 +7,6 @@ using Aitex.Core.RT.DataCenter;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.IOCore;
using Aitex.Core.RT.IOCore.Interlock;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.OperationCenter;
using Aitex.Core.RT.SCCore;
using Aitex.Core.Util;
@ -17,48 +16,42 @@ namespace MECF.Framework.Common.IOCore
{
public class IoManager : Singleton<IoManager>, IIoBuffer
{
private Dictionary<string, DIAccessor> _diMap = new();
private Dictionary<string, DOAccessor> _doMap = new();
private Dictionary<string, AIAccessor> _aiMap = new();
private Dictionary<string, AOAccessor> _aoMap = new();
private Dictionary<string, IoDataCachePerOffset<bool[]>> _diBuffer = new();
private Dictionary<string, IoDataCachePerOffset<bool[]>> _doBuffer = new();
private Dictionary<string, IoDataCachePerOffset<float[]>> _aiBuffer = new();
private Dictionary<string, IoDataCachePerOffset<float[]>> _aoBuffer = new();
private Dictionary<string, IoDataCachePerOffset<Type>> _aiBufferType = new();
private Dictionary<string, IoDataCachePerOffset<Type>> _aoBufferType = new();
private Dictionary<string, List<DIAccessor>> _diListPerPlc = new();
private Dictionary<string, List<DOAccessor>> _doListPerPlc = new();
private Dictionary<string, List<AIAccessor>> _aiListPerPlc = new();
private Dictionary<string, List<AOAccessor>> _aoListPerPlc = new();
private Dictionary<string, List<NotifiableIoItem>> _ioItemList = new();
private PeriodicJob _monitorThread;
private readonly Dictionary<string, DIAccessor> _diMap = new();
private readonly Dictionary<string, DOAccessor> _doMap = new();
private readonly Dictionary<string, AIAccessor> _aiMap = new();
private readonly Dictionary<string, AOAccessor> _aoMap = new();
private readonly Dictionary<string, IoDataCachePerOffset<bool[]>> _diBuffer = new();
private readonly Dictionary<string, IoDataCachePerOffset<bool[]>> _doBuffer = new();
private readonly Dictionary<string, IoDataCachePerOffset<float[]>> _aiBuffer = new();
private readonly Dictionary<string, IoDataCachePerOffset<float[]>> _aoBuffer = new();
private readonly Dictionary<string, IoDataCachePerOffset<Type>> _aiBufferType = new();
private readonly Dictionary<string, IoDataCachePerOffset<Type>> _aoBufferType = new();
private readonly Dictionary<string, List<DIAccessor>> _diListPerPlc = new();
private readonly Dictionary<string, List<DOAccessor>> _doListPerPlc = new();
private readonly Dictionary<string, List<AIAccessor>> _aiListPerPlc = new();
private readonly Dictionary<string, List<AOAccessor>> _aoListPerPlc = new();
private readonly Dictionary<string, List<NotifiableIoItem>> _ioItemList = new();
//private PeriodicJob _monitorThread;
/// <summary>
/// 是否使用在模拟器中。
/// </summary>
protected bool IsSimulator;
public void Initialize(string interlockConfigFile, string daemonConfigFile = "")
public IReadOnlyDictionary<string, DIAccessor> DIMap => _diMap;
public IReadOnlyDictionary<string, DOAccessor> DOMap => _doMap;
public IReadOnlyDictionary<string, AIAccessor> AIMap => _aiMap;
public IReadOnlyDictionary<string, AOAccessor> AOMap => _aoMap;
public void Initialize()
{
if (!Singleton<InterlockManager>.Instance.Initialize(interlockConfigFile, _doMap, _diMap, _aiMap, _aoMap,
out var reason1))
throw new Exception($"init {nameof(InterlockManager)} error: \r\n{reason1}");
//if (!Singleton<InterlockManager>.Instance.Initialize(interlockConfigFile, _doMap, _diMap, _aiMap, _aoMap,
// out var reason1))
// throw new Exception($"init {nameof(InterlockManager)} error: \r\n{reason1}");
/*if (!Singleton<InterlockDaemonManager>.Instance.Initialize(daemonConfigFile, _doMap, _diMap, _aiMap, _aoMap,
out var reason2))
@ -69,23 +62,23 @@ namespace MECF.Framework.Common.IOCore
LOG.Error($"init {nameof(InterlockDaemonManager)} error: \r\n{reason2}");
}*/
if(_monitorThread == null)
_monitorThread = new PeriodicJob(200, OnTimer, "IOManager Monitor Thread", isStartNow: true);
//if(_monitorThread == null)
// _monitorThread = new PeriodicJob(200, OnTimer, "IOManager Monitor Thread", isStartNow: true);
}
private bool OnTimer()
{
try
{
Singleton<InterlockManager>.Instance.Monitor();
// Singleton<InterlockDaemonManager>.Instance.Monitor();
}
catch (Exception ex)
{
LOG.Write(ex);
}
return true;
}
//private bool OnTimer()
//{
// try
// {
// Singleton<InterlockManager>.Instance.Monitor();
// // Singleton<InterlockDaemonManager>.Instance.Monitor();
// }
// catch (Exception ex)
// {
// LOG.Write(ex);
// }
// return true;
//}
internal List<Tuple<int, int, string>> GetIONameList(string group, IOType ioType)
{

View File

@ -1,11 +1,20 @@
using System.Collections.Generic;
using Aitex.Core.RT.IOCore;
using MECF.Framework.Common.IOCore;
namespace MECF.Framework.RT.Core.IoProviders
{
public interface IIoBuffer
{
void SetBufferBlock(string provider, List<IoBlockItem> lstBlocks);
IReadOnlyDictionary<string, DIAccessor> DIMap { get; }
IReadOnlyDictionary<string, DOAccessor> DOMap { get; }
IReadOnlyDictionary<string, AIAccessor> AIMap { get; }
IReadOnlyDictionary<string, AOAccessor> AOMap { get; }
void SetBufferBlock(string provider, List<IoBlockItem> lstBlocks);
void SetIoMap(string provider, int blockOffset, List<DIAccessor> ioList);
@ -26,8 +35,8 @@ namespace MECF.Framework.RT.Core.IoProviders
Dictionary<int, float[]> GetAoBuffer(string source);
Dictionary<int, float[]> GetAiBuffer(string source);
void SetDiBuffer(string source, int offset, bool[] buffer);
void SetDiBuffer(string source, int offset, bool[] buffer);
void SetDoBuffer(string provider, int offset, bool[] buffer);

View File

@ -11,6 +11,8 @@ namespace MECF.Framework.RT.Core.IoProviders
string Module { get; set; }
bool IsOpened { get; }
bool IsSynced { get; }
void Initialize(string module, string name, List<IoBlockItem> lstBuffers, IIoBuffer buffer, XmlElement nodeParameter, Dictionary<int, string> ioMappingPathFile);

View File

@ -33,6 +33,40 @@ namespace MECF.Framework.RT.Core.IoProviders
public bool IsOpened => State == IoProviderStateEnum.Opened;
/// <summary>
/// 根据DI的值判断是否已经和PLC同步数据。
/// </summary>
public bool IsSynced
{
get
{
try
{
var dict = _buffer.GetDiBuffer(_source);
if (dict == null)
return false;
if (dict.TryGetValue(0, out var diBuff) == false)
return false;
if(diBuff == null)
return false;
if (Array.TrueForAll(diBuff, x => x == true) ||
Array.TrueForAll(diBuff, x => x == false))
return false;
return true;
return true;
}
catch (Exception e)
{
return false;
}
}
}
public IoProviderStateEnum State { get; set; }
protected abstract void SetParameter(XmlElement nodeParameter);
@ -118,49 +152,38 @@ namespace MECF.Framework.RT.Core.IoProviders
{
try
{
bool flag = false;
foreach (IoBlockItem blockSection in _blockSections)
{
if (blockSection.Type == IoType.DI)
{
bool[] array = ReadDi(blockSection.Offset, blockSection.Size);
if (array != null)
{
_buffer.SetDiBuffer(_source, blockSection.Offset, array);
}
var diBuffer = ReadDi(blockSection.Offset, blockSection.Size);
if (diBuffer != null)
_buffer.SetDiBuffer(_source, blockSection.Offset, diBuffer);
}
else
{
if (blockSection.Type != IoType.AI)
if (blockSection.Type == IoType.AI)
{
continue;
var aiBuffer = ReadAi(blockSection.Offset, blockSection.Size);
if (aiBuffer != null)
_buffer.SetAiBuffer(_source, blockSection.Offset,
aiBuffer.Select(s => (float)s).ToArray());
}
var array2 = ReadAi(blockSection.Offset, blockSection.Size);
if (array2 == null)
{
continue;
}
_buffer.SetAiBuffer(_source, blockSection.Offset, array2.Select(s => (float)s).ToArray());
}
}
var aoBuffer = _buffer.GetAoBuffer(_source);
if (aoBuffer != null)
{
foreach (var item in aoBuffer)
{
WriteAo(item.Key, item.Value.Select(x=>(short)x).ToArray());
if (flag)
WriteAoFloat(item.Key, item.Value);
}
WriteAo(item.Key, item.Value.Select(x => (short)x).ToArray());
}
Dictionary<int, bool[]> doBuffer = _buffer.GetDoBuffer(_source);
var doBuffer = _buffer.GetDoBuffer(_source);
if (doBuffer != null)
{
foreach (KeyValuePair<int, bool[]> item2 in doBuffer)
{
WriteDo(item2.Key, item2.Value);
}
}
}
catch (Exception ex)

View File

@ -1,20 +1,25 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
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 MECF.Framework.Common.IOCore;
namespace MECF.Framework.RT.Core.IoProviders
{
public class IoProviderManager : Singleton<IoProviderManager>
{
private List<IIoProvider> _providers = new List<IIoProvider>();
private readonly List<IIoProvider> _providers = new ();
private Dictionary<string, IIoProvider> _dicProviders = new Dictionary<string, IIoProvider>();
private readonly Dictionary<string, IIoProvider> _dicProviders = new ();
public List<IIoProvider> Providers => _providers;
@ -257,5 +262,35 @@ namespace MECF.Framework.RT.Core.IoProviders
LOG.Write(ex);
}
}
/// <summary>
/// 等待PLC第一次同步数据以免缓存数据默认值导致误报警。
/// </summary>
/// <param name="timeout"></param>
public void WaitFirstSync(int timeout = 3000)
{
var sw = new Stopwatch();
sw.Start();
LOG.Info($"{nameof(IoProviderManager)} Waiting for the first data synchronization from the PLC.");
while (true)
{
var notSyncedPlc = _providers.Where(x => x.IsSynced == false).ToArray();
if (notSyncedPlc.Length == 0)
-break;
if (sw.ElapsedMilliseconds > timeout)
{
var plcNames = string.Join(", ", notSyncedPlc.Select(x => $"{x.Module}.{x.Name}").ToArray());
EV.PostWarningLog(ModuleName.System.ToString(),
$"The PLC(s) [{plcNames}] has not synchronized data in {timeout}ms, " +
$"which may lead to erroneous warnings or alarms in the system.");
break;
}
// keep waiting
Thread.Sleep(10);
}
}
}
}

View File

@ -1,5 +1,7 @@
using Xunit;
using Aitex.Core.RT.IOCore.Interlock;
using Xunit;
using Aitex.Core.RT.SCCore;
using Aitex.Core.Util;
using MECF.Framework.Common.IOCore;
using MECF.Framework.Common.SCCore;
using MECF.Framework.RT.Core.IoProviders;
@ -23,7 +25,8 @@ namespace Aitex.Core.RT.IOCore.Tests
SC.Manager = mock.Object;
IoProviderManager.Instance.Initialize(FN_IO_PROVIDER);
IoManager.Instance.Initialize(FN_INTERLOCK);
IoManager.Instance.Initialize();
Singleton<InterlockManager>.Instance.Initialize(FN_INTERLOCK, IoManager.Instance, out var reason);
Assert.True(false, "This test needs an implementation");
}
}