Merge branch 'develop' into feature/single-session-login
# Conflicts: # MECF.Framework.UI.Client/CenterViews/Modules/PM/PMProcessViewModel.cs
This commit is contained in:
commit
47863a000d
|
@ -20,6 +20,7 @@ namespace Aitex.Core.RT.IOCore
|
|||
: base(name, index, values)
|
||||
{
|
||||
_floatValues = floatValues;
|
||||
type = IOType.AI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace Aitex.Core.RT.IOCore
|
|||
{
|
||||
setter = SetValueSafe;
|
||||
_floatValues = floatValues;
|
||||
type = IOType.AO;
|
||||
}
|
||||
|
||||
private void SetValueSafe(int index, short value)
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace Aitex.Core.RT.IOCore
|
|||
: base(name, index, values)
|
||||
{
|
||||
rawAccessor = new IOAccessor<bool>(name, index, raws);
|
||||
type = IOType.DI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ namespace Aitex.Core.RT.IOCore
|
|||
: base(name, index, values)
|
||||
{
|
||||
setter = SetValueSafe;
|
||||
}
|
||||
type = IOType.DO;
|
||||
}
|
||||
|
||||
public bool SetValue(bool value, out string reason)
|
||||
{
|
||||
|
|
|
@ -7,6 +7,8 @@ namespace Aitex.Core.RT.IOCore
|
|||
{
|
||||
protected string name;
|
||||
|
||||
protected IOType type;
|
||||
|
||||
protected string addr;
|
||||
|
||||
protected int index;
|
||||
|
@ -33,6 +35,8 @@ namespace Aitex.Core.RT.IOCore
|
|||
|
||||
public string Name => name;
|
||||
|
||||
public IOType Type => type;
|
||||
|
||||
public int Index => index;
|
||||
|
||||
public int BlockOffset { get; set; }
|
||||
|
|
|
@ -3,6 +3,8 @@ namespace Aitex.Core.RT.IOCore;
|
|||
public interface IIOAccessor
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
IOType Type { get; }
|
||||
|
||||
int Index { get; }
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore;
|
||||
|
||||
/// <summary>
|
||||
/// 互锁动作接口。
|
||||
/// </summary>
|
||||
public interface IInterlockAction
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前动作的名称。
|
||||
/// <remarks>
|
||||
/// 通常返回当前动作对应的DO名称。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
string ActionName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前动作的限制条件。
|
||||
/// </summary>
|
||||
IEnumerable<IInterlockLimit> Limits { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前动作限制条件中的“逻辑与”组。
|
||||
/// <remarks>
|
||||
/// “逻辑与”组由多个Limit对象构成,若其中任意一个Limit命中,则当前组命中。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
IEnumerable<IEnumerable<IInterlockLimit>> LogicOrGroups { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 判断当前动作是否和给定的条件一致,若一致,则表明当前动作与特定条件的动作为同一动作。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 对比时忽略OO名称大小写。
|
||||
/// </remarks>>
|
||||
/// <param name="doName">DO名称</param>
|
||||
/// <param name="value">DO目标输出</param>
|
||||
/// <returns></returns>
|
||||
bool IsSame(string doName, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// 增加一个限制条件。
|
||||
/// </summary>
|
||||
/// <param name="limit"></param>
|
||||
void AddLimit(IInterlockLimit limit);
|
||||
|
||||
/// <summary>
|
||||
/// 增加一个“逻辑与”组。
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
void AddLogicOrGroup(List<IInterlockLimit> group);
|
||||
|
||||
/// <summary>
|
||||
/// 判断当前动作是否允许执行。
|
||||
/// <remarks>
|
||||
/// 当前动作的所有Limit均命中时,允许执行。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <param name="reason"></param>
|
||||
/// <returns></returns>
|
||||
bool CanDo(out string reason);
|
||||
|
||||
/// <summary>
|
||||
/// 扫描线程周期性执行的任务。
|
||||
/// </summary>
|
||||
void Monitor();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
|
@ -17,20 +17,35 @@ public interface IInterlockLimit
|
|||
string UniqueId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回互锁限制条件名称。
|
||||
/// 返回当前Limit对应的IO的名称。
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前Limit描述,用于输出用户信息。
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回互锁限制条件触发原因。
|
||||
/// </summary>
|
||||
string LimitReason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回守护条件满足时输出的信息。
|
||||
/// </summary>
|
||||
string DaemonReason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前互锁条件提示信息。
|
||||
/// </summary>
|
||||
string Tip { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回该Limit是否对Action进行反转
|
||||
/// </summary>
|
||||
bool IgnoreReverse { get; }
|
||||
|
||||
/*/// <summary>
|
||||
/// 判断两个互锁限制条件是否相等。
|
||||
/// </summary>
|
||||
|
@ -56,7 +71,6 @@ public interface IInterlockLimit
|
|||
/// <returns></returns>
|
||||
bool CanDo(out string reason);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定互锁限制条件中限制条件的内容。
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
namespace Aitex.Core.RT.IOCore;
|
||||
|
||||
/// <summary>
|
||||
/// 互锁限制条件数据供应器。
|
||||
/// </summary>
|
||||
public interface IInterlockLimitDataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回供应器名称。
|
||||
/// <remarks>
|
||||
/// 对于IO类型的Provider,返回IO名称,例如PMx.DO_xxxxxx。
|
||||
/// <br/>
|
||||
/// 对于DataPoll类型的Provider,返回拉取的数据路径。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回供应器名称。
|
||||
/// <remarks>
|
||||
/// 对于IO类型的Provider,返回包含地址的IO名称,例如(DO-xx)PMx.DO_xxxxxx。
|
||||
/// <br/>
|
||||
/// 对于DataPoll类型的Provider,返回拉取的数据路径。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
object GetValue();
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Actions
|
||||
{
|
||||
public class InterlockAction : InterlockActionBase
|
||||
{
|
||||
public InterlockAction(string module, DOAccessor doItem, bool value, string tip, Dictionary<string, string> cultureTip)
|
||||
: base(module, doItem, value, tip, cultureTip)
|
||||
{
|
||||
}
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 如果命中某个互锁限制条件,则将DO电平恢复到Action定义的反向电平。
|
||||
/// </summary>
|
||||
/// <param name="reason">执行恢复电平动作的信息。</param>
|
||||
/// <returns>
|
||||
/// True:执行了电平恢复操作;False:未操作电平
|
||||
/// </returns>
|
||||
public bool TryReverse(out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
// 如果DO当前电平不等于Action定义的电平,则啥也不干
|
||||
if (_do.Value != _actionValue)
|
||||
return false;
|
||||
|
||||
// 如果DO已经输出Action定义的电平,则反向
|
||||
if (_do.SetValue(!_actionValue, out reason))
|
||||
reason =
|
||||
$"Interlock Force set DO-{_do.IoTableIndex}({_do.Name}) = [{((!_actionValue) ? "ON" : "OFF")}]";
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Monitor()
|
||||
{
|
||||
throw new System.NotSupportedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Actions
|
||||
{
|
||||
public abstract class InterlockActionBase : IInterlockAction
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private readonly List<List<IInterlockLimit>> _logicOrGroups;
|
||||
private readonly List<IInterlockLimit> _limits;
|
||||
private readonly string _tip;
|
||||
private Dictionary<string, string> _cultureTip;
|
||||
|
||||
protected readonly string _module;
|
||||
protected readonly DOAccessor _do;
|
||||
protected readonly bool _actionValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
protected InterlockActionBase(string module, DOAccessor doItem, bool value, string tip, Dictionary<string, string> cultureTip)
|
||||
{
|
||||
_module = module;
|
||||
_logicOrGroups = new();
|
||||
_limits = new();
|
||||
_do = doItem;
|
||||
_actionValue = value;
|
||||
_tip = tip;
|
||||
_cultureTip = cultureTip;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockAction.ActionName"/>
|
||||
/// </summary>
|
||||
public string ActionName => (_do != null) ? _do.Name : "";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockAction.Limits"/>
|
||||
/// </summary>
|
||||
public IEnumerable<IInterlockLimit> Limits => _limits.AsReadOnly();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockAction.LogicOrGroups"/>
|
||||
/// </summary>
|
||||
public IEnumerable<IEnumerable<IInterlockLimit>> LogicOrGroups => _logicOrGroups.AsReadOnly();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockAction.AddLimit"/>
|
||||
/// </summary>
|
||||
public void AddLimit(IInterlockLimit limit)
|
||||
{
|
||||
_limits.Add(limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockAction.AddLogicOrGroup"/>
|
||||
/// </summary>
|
||||
public void AddLogicOrGroup(List<IInterlockLimit> orGroup)
|
||||
{
|
||||
_logicOrGroups.Add(orGroup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockAction.IsSame"/>
|
||||
/// </summary>
|
||||
public bool IsSame(string doName, bool value)
|
||||
{
|
||||
return string.Equals(doName, _do.Name, StringComparison.CurrentCultureIgnoreCase)
|
||||
&& _actionValue == value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockAction.CanDo"/>
|
||||
/// </summary>
|
||||
public bool CanDo(out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
var sbReason = new StringBuilder();
|
||||
var result = true;
|
||||
foreach (var limit in _limits)
|
||||
{
|
||||
if (!limit.CanDo(out var limitReason))
|
||||
{
|
||||
sbReason.AppendLine(limitReason);
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有互锁被触发,则打印互锁信息
|
||||
if (!result)
|
||||
{
|
||||
sbReason.Insert(0,
|
||||
$"Interlock triggered, DO-{_do.IoTableIndex}({_do.Name}) can not be [{(_actionValue ? "ON" : "OFF")}]{(string.IsNullOrEmpty(_tip) ? "" : $",{_tip}")} \r\n");
|
||||
reason = sbReason.ToString().TrimEnd('\r', '\n');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockAction.Monitor"/>
|
||||
/// </summary>
|
||||
public abstract void Monitor();
|
||||
|
||||
/// <inheritdoc cref="object.ToString()"/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ActionName}, Action={_actionValue}, Limit Count={Limits.Count()}, OR Group Count={LogicOrGroups.Count()}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Aitex.Core.RT.Event;
|
||||
using Aitex.Core.RT.Log;
|
||||
using Aitex.Core.Util;
|
||||
using TypeDefLimitList = System.Collections.Generic.List<Aitex.Core.RT.IOCore.IInterlockLimit>;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Actions;
|
||||
|
||||
public class InterlockDaemonAction : InterlockActionBase
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private readonly R_TRIG _rTrigSetDoFailed = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public InterlockDaemonAction(string module, DOAccessor doItem, bool value, string tip, Dictionary<string, string> cultureTip)
|
||||
: base(module, doItem, value, tip, cultureTip)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 扫描当前动作的条件,并设置相关DO的输出状态。
|
||||
/// </summary>
|
||||
public override void Monitor()
|
||||
{
|
||||
var info = new StringBuilder();
|
||||
|
||||
// 检查所有的Limit是否满足要求
|
||||
foreach (var limit in Limits)
|
||||
{
|
||||
if (!limit.CanDo(out _))
|
||||
return;
|
||||
|
||||
info.AppendLine($"{limit.DaemonReason}");
|
||||
}
|
||||
|
||||
// 扫描OR定义的Limit组,每个组中只要有一个Limit命中,则继续测试下个OR组;否则直接退出。
|
||||
foreach (var group in LogicOrGroups)
|
||||
{
|
||||
var hitLimit = group.FirstOrDefault(limit => limit.CanDo(out _));
|
||||
if (hitLimit == null) // 当前OR组中没用命中的Limit,退出Monitor
|
||||
return;
|
||||
|
||||
info.AppendLine($"{hitLimit.DaemonReason}");
|
||||
}
|
||||
|
||||
// 如果DO输出已经满足要求,则直接退出
|
||||
if (_do.Value == _actionValue)
|
||||
return;
|
||||
|
||||
// 所有的Limit和OR逻辑组均命中
|
||||
var succeeded = _do.SetValue(_actionValue, out var reason);
|
||||
_rTrigSetDoFailed.CLK = !succeeded;
|
||||
if (succeeded)
|
||||
{
|
||||
info.Insert(0,
|
||||
$"Interlock Daemon Force set DO-{_do.IoTableIndex}({_do.Name}) = [{((_actionValue) ? "ON" : "OFF")}] Due to\r\n");
|
||||
|
||||
EV.PostInfoLog( _module, info.ToString().TrimEnd('\r', '\n'));
|
||||
|
||||
_rTrigSetDoFailed.CLK = false;
|
||||
}
|
||||
else if(_rTrigSetDoFailed.Q)
|
||||
{
|
||||
// 第一次Set DO错误时打印日志
|
||||
LOG.Error($"Interlock Daemon set {_do.Name} failed, {reason}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore;
|
||||
|
||||
/// <summary>
|
||||
/// 基于AI判断的互锁限制条件。
|
||||
/// </summary>
|
||||
internal class AiLimit : InterlockLimit<AIAccessor, double>
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public AiLimit(AIAccessor io, string value, string tip, Dictionary<string, string> cultureTip)
|
||||
: base(io, value, tip, cultureTip)
|
||||
{
|
||||
LimitRange = new InterlockLimitRangeDouble(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
private InterlockLimitRangeDouble LimitRange { get; }
|
||||
|
||||
public override double CurrentValue => Io.FloatValue;
|
||||
|
||||
public override string LimitReason =>
|
||||
$"AI-{Io.IoTableIndex}({Io.Name}) out of range [{LimitRange}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return LimitRange.CheckIsInRange(CurrentValue);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore;
|
||||
|
||||
/// <summary>
|
||||
/// 基于AO判断的互锁限制条件。
|
||||
/// </summary>
|
||||
internal class AoLimit : InterlockLimit<AOAccessor, double>
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public AoLimit(AOAccessor io, string value, string tip, Dictionary<string, string> cultureTip)
|
||||
: base(io, value, tip, cultureTip)
|
||||
{
|
||||
LimitRange = new InterlockLimitRangeDouble(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
private InterlockLimitRangeDouble LimitRange { get; }
|
||||
|
||||
public override double CurrentValue => Io.FloatValue;
|
||||
|
||||
public override string LimitReason =>
|
||||
$"AO-{Io.IoTableIndex}({Io.Name}) out of range [{LimitRange}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return LimitRange.CheckIsInRange(CurrentValue);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
public class AiValueProvider : IoValueProviderBase<AIAccessor>, IInterlockLimitDataProvider
|
||||
{
|
||||
public AiValueProvider(AIAccessor io) : base(io)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回指定AO的FloatValue。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override object GetValue()
|
||||
{
|
||||
return Convert.ToDouble(Io.FloatValue);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
public class AoValueProvider : IoValueProviderBase<AOAccessor>, IInterlockLimitDataProvider
|
||||
{
|
||||
public AoValueProvider(AOAccessor io) : base(io)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回指定AI的FloatValue。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override object GetValue()
|
||||
{
|
||||
return Io.FloatValue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using Aitex.Core.RT.DataCenter;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
public class BoolDataPollProvider : DataPollProviderBase<bool>, IInterlockLimitDataProvider
|
||||
{
|
||||
public BoolDataPollProvider(string dataPath) : base(dataPath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="DataPollProviderBase{T}.HandleGetValue"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NullReferenceException"></exception>
|
||||
/// <exception cref="InvalidCastException"></exception>
|
||||
protected override object HandleGetValue()
|
||||
{
|
||||
var value = DATA.Poll(DataPath);
|
||||
if (value == null)
|
||||
throw new NullReferenceException("null is returned.");
|
||||
|
||||
if (bool.TryParse(value.ToString(), out var bValue))
|
||||
return bValue;
|
||||
|
||||
throw new InvalidCastException($"the return value {value} is not bool.");
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using Aitex.Core.RT.Log;
|
||||
using Aitex.Core.Util;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
public abstract class DataPollProviderBase<T> : IInterlockLimitDataProvider
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private readonly R_TRIG _rTrigPollFailed = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// 实例化数据拉取器
|
||||
/// </summary>
|
||||
/// <param name="dataPath"></param>
|
||||
protected DataPollProviderBase(string dataPath)
|
||||
{
|
||||
DataPath = dataPath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="DataPollProviderBase{T}.Name"/>
|
||||
/// </summary>
|
||||
public string Name => DataPath;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="DataPollProviderBase{T}.Description"/>
|
||||
/// </summary>
|
||||
public string Description => DataPath;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前数据拉取器需要拉取的数据路径。
|
||||
/// <remarks>
|
||||
/// 注意路径中不要包含Module名称。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
protected string DataPath { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 需由派生类实现的获取数据的方法。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected abstract object HandleGetValue();
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object GetValue()
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = HandleGetValue();
|
||||
_rTrigPollFailed.CLK = false;
|
||||
return value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_rTrigPollFailed.CLK = true;
|
||||
if (_rTrigPollFailed.Q)
|
||||
{
|
||||
LOG.Error($"DataPollProvider poll data {DataPath} failed, {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
public class DiValueProvider : IoValueProviderBase<DIAccessor>, IInterlockLimitDataProvider
|
||||
{
|
||||
public DiValueProvider(DIAccessor io) : base(io)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回指定DI的FloatValue。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override object GetValue()
|
||||
{
|
||||
return Io.Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
public class DoValueProvider : IoValueProviderBase<DOAccessor>, IInterlockLimitDataProvider
|
||||
{
|
||||
public DoValueProvider(DOAccessor io) : base(io)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回指定DO的FloatValue。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override object GetValue()
|
||||
{
|
||||
return Io.Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using Aitex.Core.RT.DataCenter;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
public class DoubleDataPollProvider : DataPollProviderBase<double>, IInterlockLimitDataProvider
|
||||
{
|
||||
public DoubleDataPollProvider(string dataPath) : base(dataPath)
|
||||
{
|
||||
}
|
||||
|
||||
protected override object HandleGetValue()
|
||||
{
|
||||
var value = DATA.Poll(DataPath);
|
||||
if (value == null)
|
||||
throw new NullReferenceException("unable to poll data, null is returned.");
|
||||
|
||||
if (double.TryParse(value.ToString(), out var dValue))
|
||||
return dValue;
|
||||
|
||||
throw new InvalidCastException($"the return value {value} is not a number.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using Aitex.Core.Util;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
public abstract class IoValueProviderBase<TAccessor> : IInterlockLimitDataProvider
|
||||
where TAccessor: IIOAccessor
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private readonly R_TRIG _rTrigPollFailed = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// 实例化数据拉取器
|
||||
/// </summary>
|
||||
/// <param name="io"></param>
|
||||
/// <param name="module"></param>
|
||||
protected IoValueProviderBase(TAccessor io)
|
||||
{
|
||||
Io = io;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="DataPollProviderBase{T}.Name"/>
|
||||
/// </summary>
|
||||
public string Name => Io.Name;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="DataPollProviderBase{T}.Description"/>
|
||||
/// </summary>
|
||||
public string Description => $"{Io.Type}-{Io.IoTableIndex}({Io.Name})";
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前数据拉取器需要拉取的数据路径。
|
||||
/// <remarks>
|
||||
/// 注意路径中不要包含Module名称。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public TAccessor Io { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual object GetValue()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于DI判断的互锁限制条件。
|
||||
/// </summary>
|
||||
internal class DiLimit : InterlockLimit<DIAccessor, bool>
|
||||
{
|
||||
public override bool CurrentValue => Io.Value;
|
||||
|
||||
public override string LimitReason =>
|
||||
$"DI-{Io.IoTableIndex}({Io.Name}) = [{(Io.Value ? "ON" : "OFF")}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
public DiLimit(DIAccessor diItem, string value, string tip, Dictionary<string, string> cultureTip)
|
||||
: base(diItem, value, tip, cultureTip)
|
||||
{
|
||||
if (bool.TryParse(value, out var limitValue))
|
||||
LimitValue = limitValue;
|
||||
else
|
||||
throw new InvalidCastException($"unable to convert {value} to boolean.");
|
||||
}
|
||||
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return CurrentValue == LimitValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于AO判断的互锁限制条件。
|
||||
/// </summary>
|
||||
internal class DoLimit : InterlockLimit<DOAccessor, bool>
|
||||
{
|
||||
public override bool CurrentValue => Io.Value;
|
||||
|
||||
public override string LimitReason =>
|
||||
$"DO-{Io.IoTableIndex}({Io.Name}) = [{(Io.Value ? "ON" : "OFF")}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
public DoLimit(DOAccessor doItem, string value, string tip, Dictionary<string, string> cultureTip)
|
||||
: base(doItem, value, tip, cultureTip)
|
||||
{
|
||||
if (bool.TryParse(value, out var limitValue))
|
||||
LimitValue = limitValue;
|
||||
else
|
||||
throw new InvalidCastException($"unable to convert {value} to boolean.");
|
||||
}
|
||||
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return CurrentValue == LimitValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore
|
||||
{
|
||||
internal class InterlockAction
|
||||
{
|
||||
private readonly List<IInterlockLimit> _limits;
|
||||
|
||||
private readonly DOAccessor _do;
|
||||
|
||||
private readonly bool _actionValue;
|
||||
|
||||
private readonly string _tip;
|
||||
|
||||
private Dictionary<string, string> _cultureTip;
|
||||
|
||||
public string ActionName => (_do != null) ? _do.Name : "";
|
||||
|
||||
public InterlockAction(DOAccessor doItem, bool value, string tip, Dictionary<string, string> cultureTip,
|
||||
List<IInterlockLimit> limits)
|
||||
{
|
||||
_do = doItem;
|
||||
_actionValue = value;
|
||||
_tip = tip;
|
||||
_cultureTip = cultureTip;
|
||||
_limits = limits;
|
||||
}
|
||||
|
||||
public bool IsSame(string doName, bool value)
|
||||
{
|
||||
return doName == _do.Name && _actionValue == value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果命中某个互锁限制条件,则将DO电平恢复到Action定义的反向电平。
|
||||
/// </summary>
|
||||
/// <param name="reason">执行恢复电平动作的信息。</param>
|
||||
/// <returns>
|
||||
/// True:执行了电平恢复操作;False:未操作电平
|
||||
/// </returns>
|
||||
public bool TryReverse(out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
// 如果DO当前电平不等于Action定义的电平,则啥也不干
|
||||
if (_do.Value != _actionValue)
|
||||
return false;
|
||||
|
||||
// 如果DO已经输出Action定义的电平,则反向
|
||||
if (_do.SetValue(!_actionValue, out reason))
|
||||
reason = $"Interlock Force set DO-{_do.IoTableIndex}({_do.Name}) = [{((!_actionValue) ? "ON" : "OFF")}]";
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanDo(out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
var sbReason = new StringBuilder();
|
||||
var result = true;
|
||||
foreach (var limit in _limits)
|
||||
{
|
||||
if (!limit.CanDo(out var limitReason))
|
||||
{
|
||||
sbReason.AppendLine(limitReason);
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有互锁被触发,则打印互锁信息
|
||||
if (!result)
|
||||
{
|
||||
sbReason.Insert(0,
|
||||
$"Interlock triggered, DO-{_do.IoTableIndex}({_do.Name}) can not be [{(_actionValue ? "ON" : "OFF")}]{(string.IsNullOrEmpty(_tip) ? "" : $",{_tip}")} \r\n");
|
||||
reason = sbReason.ToString().TrimEnd('\r', '\n');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ActionName}, Action={_actionValue}, Limit Count={_limits.Count}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Aitex.Core.RT.IOCore.Interlock.Actions;
|
||||
using MECF.Framework.Common.Equipment;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock;
|
||||
|
||||
public class InterlockDaemonManager : InterlockManagerBase<InterlockDaemonAction>
|
||||
{
|
||||
public InterlockDaemonManager()
|
||||
{
|
||||
RootNodeName = "Daemon";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockManagerBase{TAction}.Monitor()"/>
|
||||
/// </summary>
|
||||
public override void Monitor()
|
||||
{
|
||||
// 按Module扫描Interlock Limit
|
||||
foreach (var moduleName in _dicActionsPerModule.Keys.ToList())
|
||||
{
|
||||
Debug.Assert(moduleName != ModuleName.UnDefined,
|
||||
$"Interlock Manager CanSetDo() undesired module name {ModuleName.UnDefined}");
|
||||
|
||||
foreach (var action in _dicActionsPerModule[moduleName].ToList())
|
||||
action.Monitor();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Aitex.Core.Util;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore
|
||||
{
|
||||
/// <summary>
|
||||
/// 互锁限制条件。
|
||||
/// </summary>
|
||||
public abstract class InterlockLimit<TAccessor, TValue> : IInterlockLimit
|
||||
where TAccessor: IIOAccessor where TValue : struct
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private Dictionary<string, string> _cultureTip;
|
||||
private readonly R_TRIG _trigger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// 互锁限制条件的构造函数。
|
||||
/// </summary>
|
||||
/// <param name="io">IO对象实例。</param>
|
||||
/// <param name="value">当前限制条件中的IO状态。</param>
|
||||
/// <param name="tip">默认语言提示信息。</param>
|
||||
/// <param name="cultureTip">多国语言提示信息。</param>
|
||||
/// <exception cref="InvalidIoNameException">IO名称错误,前缀不是“DI_”、”DO_“、”AI_“或"AO_".</exception>
|
||||
protected InterlockLimit(TAccessor io, string value, string tip, Dictionary<string, string> cultureTip)
|
||||
{
|
||||
Debug.Assert(io != null, "The IO Accessor object can not be null.");
|
||||
_trigger = new R_TRIG();
|
||||
Io = io;
|
||||
Name = io.Name;
|
||||
Tip = tip;
|
||||
_cultureTip = cultureTip;
|
||||
UniqueId = $"{io.Name}.{value}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
protected TAccessor Io { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回互锁限制条件的唯一识别码。
|
||||
/// <remarks>
|
||||
/// 该唯一识别码用于创建字典时,作为字典的Key值使用。
|
||||
/// <br/>
|
||||
/// 该值由Name+LimitValue组成。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public string UniqueId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回互锁限制条件名称。
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回
|
||||
/// </summary>
|
||||
public abstract TValue CurrentValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前互锁限制条件触发的原因。
|
||||
/// </summary>
|
||||
public abstract string LimitReason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前互锁条件的信号约束值。
|
||||
/// </summary>
|
||||
public TValue LimitValue { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前互锁条件提示信息。
|
||||
/// </summary>
|
||||
public string Tip { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// 判断两个互锁限制条件是否相等。
|
||||
/// </summary>
|
||||
/// <param name="interlockLimit">待比较的互锁限制条件。</param>
|
||||
/// <returns>
|
||||
/// <para>True: 相同;False:不同。</para>
|
||||
/// </returns>
|
||||
public bool IsSame(object interlockLimit)
|
||||
{
|
||||
if (interlockLimit is not InterlockLimit<TAccessor, TValue> li)
|
||||
return false;
|
||||
return Name == li.Name && li.LimitValue == LimitValue;
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// 返回互锁限制监测的信号当前值和期望值不相等的条件是否触发。
|
||||
/// <remarks>
|
||||
/// 捕获当前值和期望值不相等信号的上升沿,当上升沿到达时触发输出Q。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsTriggered()
|
||||
{
|
||||
_trigger.CLK = !CheckInRange();
|
||||
return _trigger.Q;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据互锁条件判断是否允许DO输出。
|
||||
/// </summary>
|
||||
/// <param name="reason">如果禁止DO输出,返回互锁原因。</param>
|
||||
/// <returns></returns>
|
||||
public bool CanDo(out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
if (CheckInRange())
|
||||
return true;
|
||||
|
||||
reason = LimitReason;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定互锁限制条件中限制条件的内容。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string GetLimitValue()
|
||||
{
|
||||
return LimitValue.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定互锁限制条件中当前IO状态。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string GetCurrentValue()
|
||||
{
|
||||
return CurrentValue.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查当前值和是否超出限制值范围。
|
||||
/// </summary>
|
||||
/// <returns>True: 在范围内; False:超出范围。</returns>
|
||||
protected abstract bool CheckInRange();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc />
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}, Limit={LimitValue}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,19 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
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.Log;
|
||||
using Aitex.Core.RT.IOCore.Interlock.Actions;
|
||||
using Aitex.Core.RT.SCCore;
|
||||
using Aitex.Core.Util;
|
||||
using MECF.Framework.Common.Equipment;
|
||||
using DictLimitToActionMap =
|
||||
System.Collections.Generic.Dictionary<Aitex.Core.RT.IOCore.IInterlockLimit, System.Collections.Generic.List<Aitex.Core.RT.IOCore.InterlockAction>>;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore
|
||||
namespace Aitex.Core.RT.IOCore.Interlock
|
||||
{
|
||||
/// <summary>
|
||||
/// 互锁管理器。
|
||||
|
@ -24,17 +18,15 @@ namespace Aitex.Core.RT.IOCore
|
|||
/// 置为Action节点Value属性中定义电平的反向电平。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public class InterlockManager : Singleton<InterlockManager>
|
||||
public class InterlockManager : InterlockManagerBase<InterlockAction>
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private readonly List<InterlockAction> _lstActions;
|
||||
private readonly DictLimitToActionMap _dicLimitToActionMap;
|
||||
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;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
@ -44,197 +36,112 @@ namespace Aitex.Core.RT.IOCore
|
|||
/// </summary>
|
||||
public InterlockManager()
|
||||
{
|
||||
_lstActions = new List<InterlockAction>();
|
||||
_dicLimitToActionMap = new DictLimitToActionMap();
|
||||
RootNodeName = "Interlock";
|
||||
_dicModulePostInfo = new();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// 初始化互锁管理器。
|
||||
/// </summary>
|
||||
/// <param name="interlockFile">互锁配置文件。</param>
|
||||
/// <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>
|
||||
/// <returns></returns>
|
||||
public bool Initialize(string interlockFile,
|
||||
public override bool Initialize(string configFie,
|
||||
Dictionary<string, DOAccessor> doMap,
|
||||
Dictionary<string, DIAccessor> diMap,
|
||||
Dictionary<string, AIAccessor> aiMap,
|
||||
Dictionary<string, AOAccessor> aoMap,
|
||||
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 lstLimit = new List<IInterlockLimit>();
|
||||
if (!doMap.ContainsKey(doName))
|
||||
{
|
||||
sbReason.AppendLine("action node " + doName + " no such DO defined");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取DO实例
|
||||
var doAction = doMap[doName];
|
||||
if (doAction == 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节点
|
||||
foreach (XmlElement nodeLimit in xmlElement.ChildNodes)
|
||||
{
|
||||
// 获取InterlockLimit对象。
|
||||
var limit = CreateInterlockLimit(nodeLimit, out var err);
|
||||
if(limit != null)
|
||||
lstLimit.Add(limit);
|
||||
else
|
||||
sbReason.AppendLine(err);
|
||||
}
|
||||
|
||||
// 创建InterlockAction对象
|
||||
var newAction = new InterlockAction(doAction, doValue, tip, dicTips, lstLimit);
|
||||
_lstActions.Add(newAction);
|
||||
|
||||
// 创建InterlockLimit到被使用的InterlockAction映射字典
|
||||
foreach (var limit in lstLimit)
|
||||
{
|
||||
// 检查InterlockLimit是否已经存在于字典中
|
||||
var limitInDic = _dicLimitToActionMap.Keys.FirstOrDefault(x => x.UniqueId == limit.UniqueId);
|
||||
Debug.Assert(limitInDic != null, "The condition should never be hit");
|
||||
|
||||
// 存在则插入InterlockAction;不存在则新建
|
||||
if (limitInDic != null)
|
||||
_dicLimitToActionMap[limitInDic].Add(newAction);
|
||||
else
|
||||
{
|
||||
_dicLimitToActionMap[limit] = new List<InterlockAction> { newAction };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sbReason.AppendLine(ex.Message);
|
||||
}
|
||||
|
||||
if (sbReason.Length > 0)
|
||||
{
|
||||
reason = sbReason.ToString().TrimEnd('\n', '\r');
|
||||
var succeeded = base.Initialize(configFie, doMap, diMap, aiMap, aoMap, out reason);
|
||||
if (!succeeded)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
_dicModulePostInfo = _dicLimitsPerModule.Keys.ToDictionary(x => x, x => false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 背景扫描线程执行的任务。
|
||||
/// </summary>
|
||||
public void Monitor()
|
||||
{
|
||||
// 如果系统设置中旁路了互锁,则不监测互锁条件
|
||||
if (SC.ContainsItem("System.BypassInterlock") && SC.GetValue<bool>("System.BypassInterlock"))
|
||||
return;
|
||||
|
||||
// 遍历每一个InterlockLimit对象。
|
||||
public override 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())
|
||||
{
|
||||
|
||||
var limits = _dicLimitToActionMap.Keys.ToList();
|
||||
foreach (var limit in limits)
|
||||
{
|
||||
// 如果互锁没被触发
|
||||
if (!limit.IsTriggered())
|
||||
continue;
|
||||
// 如果互锁没被触发
|
||||
if (!limit.IsTriggered())
|
||||
continue;
|
||||
|
||||
var reverseInfo = new StringBuilder();
|
||||
var module = "System";
|
||||
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");
|
||||
var actions = _dicLimitToActionMap[limit].ToList();
|
||||
foreach (var action in actions)
|
||||
{
|
||||
// 尝试根据Action定义复位该互锁限制条件对应的所有DO的电平
|
||||
if (((InterlockAction)action).TryReverse(out var reason))
|
||||
{
|
||||
var ss = action.ActionName.Split('.');
|
||||
if (ss.Length > 1 && ModuleHelper.IsPm(ss[0]))
|
||||
module = ss[0];
|
||||
|
||||
EV.PostWarningLog(module, reverseInfo.ToString().TrimEnd('\r', '\n'));
|
||||
}
|
||||
}
|
||||
}
|
||||
reverseInfo.AppendLine(reason);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
// 如果PM腔有被恢复的DO,则打印信息并报警。
|
||||
if (reverseInfo.Length > 0)
|
||||
{
|
||||
reverseInfo.Insert(0,
|
||||
$"Due to the {limit.Tip}, {limit.Description} 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="isPostInfo">是否以Info等级打印信息</param>
|
||||
public void SetEventLevel(string module, bool isPostInfo)
|
||||
{
|
||||
var moduleName = ModuleHelper.Converter(module);
|
||||
if (_dicModulePostInfo.ContainsKey(moduleName))
|
||||
_dicModulePostInfo[moduleName] = isPostInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对指定的DO的操作是否满足互锁条件。
|
||||
/// </summary>
|
||||
/// <param name="doName">待操作的DO名称。</param>
|
||||
|
@ -251,10 +158,15 @@ namespace Aitex.Core.RT.IOCore
|
|||
public bool CanSetDo(string doName, bool onOff, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
if (SC.ContainsItem("System.BypassInterlock") && SC.GetValue<bool>("System.BypassInterlock"))
|
||||
{
|
||||
|
||||
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)))
|
||||
|
@ -264,209 +176,33 @@ namespace Aitex.Core.RT.IOCore
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建互锁显示条件对象。
|
||||
/// 获取系统配置中指定Module的ByPassInterlock参数设置值。
|
||||
/// </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((DIAccessor)io, limitValue, tip, cultureTip);
|
||||
break;
|
||||
|
||||
case IOType.DO:
|
||||
limit = new DoLimit((DOAccessor)io, limitValue, tip, cultureTip);
|
||||
break;
|
||||
|
||||
case IOType.AI:
|
||||
limit = new AiLimit((AIAccessor)io, limitValue, tip, cultureTip);
|
||||
break;
|
||||
|
||||
case IOType.AO:
|
||||
limit = new AoLimit((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>());
|
||||
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;
|
||||
}
|
||||
|
||||
// 节点不包含‘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<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);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,494 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.ServiceModel.Configuration;
|
||||
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 List<TAction> _lstActions;
|
||||
|
||||
/// <summary>
|
||||
/// 每个Limit对应的DoAction集合。
|
||||
/// </summary>
|
||||
protected readonly Dictionary<IInterlockLimit, List<IInterlockAction>> _dicLimitToActionMap;
|
||||
|
||||
/// <summary>
|
||||
/// 每个Module对应的DoAction集合。
|
||||
/// </summary>
|
||||
protected readonly Dictionary<ModuleName, List<TAction>> _dicActionsPerModule;
|
||||
|
||||
/// <summary>
|
||||
/// 每个Module对应的Limit集合。
|
||||
/// </summary>
|
||||
protected readonly Dictionary<ModuleName, List<IInterlockLimit>> _dicLimitsPerModule;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// 互锁管理器的构造函数。
|
||||
/// </summary>
|
||||
protected InterlockManagerBase()
|
||||
{
|
||||
_lstActions = new();
|
||||
_dicActionsPerModule = new();
|
||||
_dicLimitToActionMap = new ();
|
||||
_dicLimitsPerModule = 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 (!_dicActionsPerModule.ContainsKey(moduleName))
|
||||
_dicActionsPerModule[moduleName] = new List<TAction>();
|
||||
|
||||
// 创建InterlockAction对象,首先判断该动作是否已经存在,若存在,则报错
|
||||
var action = (TAction)Activator.CreateInstance(typeof(TAction), moduleName.ToString(),
|
||||
targetDo, doValue, tip, dicTips);
|
||||
|
||||
if (_lstActions.Exists(x => x.IsSame(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);
|
||||
}
|
||||
}
|
||||
|
||||
_dicActionsPerModule[moduleName].Add(action);
|
||||
_lstActions.Add(action);
|
||||
|
||||
// 如果当前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 (!_dicLimitToActionMap.ContainsKey(limit))
|
||||
_dicLimitToActionMap[limit] = new List<IInterlockAction>();
|
||||
|
||||
_dicLimitToActionMap[limit].Add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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.");
|
||||
}
|
||||
|
||||
// 节点不包含‘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;
|
||||
}
|
||||
|
||||
// 从_dicLimitsPerModule字典中查找当前创建的Limit是否已经存在;
|
||||
// 如果存在,直接返回已存在的Limit实例;
|
||||
// 否则,将当前创建的Limit放到字典中,然后返回实例;
|
||||
var moduleName = GetModuleFromIo(limit.Name);
|
||||
if (_dicLimitsPerModule.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实例放入字典
|
||||
_dicLimitsPerModule[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
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using System.Collections.Generic;
|
||||
using Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
using Aitex.Core.RT.IOCore.Interlock.Utils;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Limits;
|
||||
|
||||
/// <summary>
|
||||
/// 基于AI判断的互锁限制条件。
|
||||
/// </summary>
|
||||
internal class AiLimit : InterlockLimitBase<AiValueProvider, double>
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public AiLimit(AiValueProvider dataProvider, string value, string tip,
|
||||
Dictionary<string, string> cultureTip,bool ignoreReverse)
|
||||
: base(dataProvider, value, tip, cultureTip, ignoreReverse)
|
||||
{
|
||||
LimitRange = new InterlockLimitRangeDouble(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
private InterlockLimitRangeDouble LimitRange { get; }
|
||||
|
||||
public override double CurrentValue => (double)DataProvider.GetValue();
|
||||
|
||||
public override string LimitReason =>
|
||||
$"{DataProvider.Name} = {CurrentValue:F1}, out of range [{LimitRange}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
public override string DaemonReason =>
|
||||
$"{DataProvider.Name} = {CurrentValue:F1}, in the range [{LimitRange}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return LimitRange.CheckIsInRange(CurrentValue);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}, Limit={LimitRange}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using System.Collections.Generic;
|
||||
using Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
using Aitex.Core.RT.IOCore.Interlock.Utils;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Limits;
|
||||
|
||||
/// <summary>
|
||||
/// 基于AO判断的互锁限制条件。
|
||||
/// </summary>
|
||||
internal class AoLimit : InterlockLimitBase<AoValueProvider, double>
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public AoLimit(AoValueProvider dataProvider, string value, string tip, Dictionary<string, string> cultureTip, bool ignoreReverse)
|
||||
: base(dataProvider, value, tip, cultureTip, ignoreReverse)
|
||||
{
|
||||
LimitRange = new InterlockLimitRangeDouble(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
private InterlockLimitRangeDouble LimitRange { get; }
|
||||
|
||||
public override double CurrentValue => (double)DataProvider.GetValue();
|
||||
|
||||
public override string LimitReason =>
|
||||
$"{DataProvider.Name} = {CurrentValue:F1}, out of range [{LimitRange}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
public override string DaemonReason =>
|
||||
$"{DataProvider.Name} = {CurrentValue:F1}, in the range [{LimitRange}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return LimitRange.CheckIsInRange(CurrentValue);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}, Limit={LimitRange}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
using DocumentFormat.OpenXml.Math;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Limits;
|
||||
|
||||
public class BoolDataPollLimit : InterlockLimitBase<BoolDataPollProvider, bool>
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public BoolDataPollLimit(IInterlockLimitDataProvider dataProvider, string value, string tip,
|
||||
Dictionary<string, string> cultureTip,bool ignoreReverse) : base(dataProvider, value, tip, cultureTip, ignoreReverse)
|
||||
{
|
||||
if (bool.TryParse(value, out var limitValue))
|
||||
LimitValue = limitValue;
|
||||
else
|
||||
throw new InvalidCastException($"unable to convert {value} to boolean.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.CurrentValue"/>
|
||||
/// </summary>
|
||||
public override bool CurrentValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (DataProvider.GetValue() is bool value)
|
||||
return value;
|
||||
|
||||
// 如果Poll不到值,始终返回LimitValue相反的值,避免当前Limit被命中
|
||||
return !LimitValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.LimitReason"/>
|
||||
/// </summary>
|
||||
public override string LimitReason =>
|
||||
$"{DataProvider.Name} = [{(CurrentValue ? "ON" : "OFF")}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.DaemonReason"/>
|
||||
/// </summary>
|
||||
public override string DaemonReason =>
|
||||
$"{DataProvider.Name} = [{(LimitValue ? "ON" : "OFF")}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.CheckInRange"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return CurrentValue == LimitValue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Limits;
|
||||
|
||||
/// <summary>
|
||||
/// 基于DI判断的互锁限制条件。
|
||||
/// </summary>
|
||||
internal class DiLimit : InterlockLimitBase<DiValueProvider, bool>
|
||||
{
|
||||
public override bool CurrentValue => (bool)DataProvider.GetValue();
|
||||
|
||||
public override string LimitReason =>
|
||||
$"{DataProvider.Name} = [{(CurrentValue ? "ON" : "OFF")}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
public override string DaemonReason =>
|
||||
$"{DataProvider.Name} = [{(LimitValue ? "ON" : "OFF")}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
public DiLimit(DiValueProvider dataProvider, string value, string tip, Dictionary<string, string> cultureTip,bool ignoreReverse)
|
||||
: base(dataProvider, value, tip, cultureTip,ignoreReverse)
|
||||
{
|
||||
if (bool.TryParse(value, out var limitValue))
|
||||
LimitValue = limitValue;
|
||||
else
|
||||
throw new InvalidCastException($"unable to convert {value} to boolean.");
|
||||
}
|
||||
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return CurrentValue == LimitValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Limits;
|
||||
|
||||
/// <summary>
|
||||
/// 基于AO判断的互锁限制条件。
|
||||
/// </summary>
|
||||
internal class DoLimit : InterlockLimitBase<DoValueProvider, bool>
|
||||
{
|
||||
public override bool CurrentValue => (bool)DataProvider.GetValue();
|
||||
|
||||
public override string LimitReason =>
|
||||
$"{DataProvider.Name} = [{(CurrentValue ? "ON" : "OFF")}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
public override string DaemonReason =>
|
||||
$"{DataProvider.Name} = [{(LimitValue ? "ON" : "OFF")}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
public DoLimit(DoValueProvider dataProvider, string value, string tip, Dictionary<string, string> cultureTip, bool ignoreReverse)
|
||||
: base(dataProvider, value, tip, cultureTip, ignoreReverse)
|
||||
{
|
||||
if (bool.TryParse(value, out var limitValue))
|
||||
LimitValue = limitValue;
|
||||
else
|
||||
throw new InvalidCastException($"unable to convert {value} to boolean.");
|
||||
}
|
||||
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return CurrentValue == LimitValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using System.Collections.Generic;
|
||||
using Aitex.Core.RT.IOCore.Interlock.DataProvider;
|
||||
using Aitex.Core.RT.IOCore.Interlock.Utils;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Limits;
|
||||
|
||||
public class DoubleDataPollLimit : InterlockLimitBase<DoubleDataPollProvider, double>
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
public DoubleDataPollLimit(IInterlockLimitDataProvider dataProvider, string value, string tip,
|
||||
Dictionary<string, string> cultureTip,bool ignoreReverse) : base(dataProvider, value, tip, cultureTip, ignoreReverse)
|
||||
{
|
||||
LimitRange = new InterlockLimitRangeDouble(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
private InterlockLimitRangeDouble LimitRange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.CurrentValue"/>
|
||||
/// </summary>
|
||||
public override double CurrentValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (DataProvider.GetValue() is double value)
|
||||
return value;
|
||||
|
||||
// 如果Poll不到值,始终返回LimitValue相反的值,避免当前Limit被命中
|
||||
return double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.LimitReason"/>
|
||||
/// </summary>
|
||||
public override string LimitReason =>
|
||||
$"{DataProvider.Name} = {CurrentValue:F1}, in the range [{LimitRange}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.DaemonReason"/>
|
||||
/// </summary>
|
||||
public override string DaemonReason =>
|
||||
$"{DataProvider.Name} = {CurrentValue:F1}, in the range [{LimitRange}]{(string.IsNullOrEmpty(Tip) ? "" : $",{Tip}")}";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.CheckInRange"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override bool CheckInRange()
|
||||
{
|
||||
return LimitRange.CheckIsInRange(CurrentValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="InterlockLimitBase{TAccessor,TValue}.ToString"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}, Limit={LimitRange}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Aitex.Core.Util;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Limits;
|
||||
|
||||
/// <summary>
|
||||
/// 互锁限制条件。
|
||||
/// </summary>
|
||||
public abstract class InterlockLimitBase<TAccessor, TValue> : IInterlockLimit
|
||||
where TAccessor : IInterlockLimitDataProvider
|
||||
where TValue : struct
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private Dictionary<string, string> _cultureTip;
|
||||
private readonly R_TRIG _trigger;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// 互锁限制条件的构造函数。
|
||||
/// </summary>
|
||||
/// <param name="dataProvider">数据供应器对象实例</param>
|
||||
/// <param name="value">当前限制条件中的IO状态。</param>
|
||||
/// <param name="tip">默认语言提示信息。</param>
|
||||
/// <param name="cultureTip">多国语言提示信息。</param>
|
||||
/// <param name="ignoreReverse">是否触发Action反转</param>
|
||||
/// <exception cref="InvalidIoNameException">IO名称错误,前缀不是“DI_”、”DO_“、”AI_“或"AO_".</exception>
|
||||
protected InterlockLimitBase(IInterlockLimitDataProvider dataProvider, string value, string tip,
|
||||
Dictionary<string, string> cultureTip,bool ignoreReverse = false)
|
||||
{
|
||||
Debug.Assert(dataProvider != null, "The data provider can not be null.");
|
||||
_trigger = new R_TRIG();
|
||||
DataProvider = dataProvider;
|
||||
Tip = tip;
|
||||
_cultureTip = cultureTip;
|
||||
UniqueId = $"{dataProvider.Name}.{value}";
|
||||
IgnoreReverse = ignoreReverse;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// 放回当前Limit所对应的IO对象。
|
||||
/// </summary>
|
||||
protected IInterlockLimitDataProvider DataProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回互锁限制条件的唯一识别码。
|
||||
/// <remarks>
|
||||
/// 该唯一识别码用于创建字典时,作为字典的Key值使用。
|
||||
/// <br/>
|
||||
/// 该值由Name+LimitValue组成。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public string UniqueId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockLimit.Name"/>
|
||||
/// </summary>
|
||||
public string Name => DataProvider?.Name ?? "";
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IInterlockLimit.Description"/>
|
||||
/// </summary>
|
||||
public string Description => DataProvider?.Description ?? "";
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前Limit对应的数据值。
|
||||
/// </summary>
|
||||
public abstract TValue CurrentValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前互锁限制条件触发的原因。
|
||||
/// </summary>
|
||||
public abstract string LimitReason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回守护条件满足时输出的信息。
|
||||
/// </summary>
|
||||
public abstract string DaemonReason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前互锁条件的信号约束值。
|
||||
/// </summary>
|
||||
public TValue LimitValue { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前互锁条件提示信息。
|
||||
/// </summary>
|
||||
public string Tip { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 返回该Limit是否对Action进行反转
|
||||
/// </summary>
|
||||
public bool IgnoreReverse { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// 判断两个互锁限制条件是否相等。
|
||||
/// </summary>
|
||||
/// <param name="interlockLimit">待比较的互锁限制条件。</param>
|
||||
/// <returns>
|
||||
/// <para>True: 相同;False:不同。</para>
|
||||
/// </returns>
|
||||
public bool IsSame(object interlockLimit)
|
||||
{
|
||||
if (interlockLimit is not InterlockLimit<TAccessor, TValue> li)
|
||||
return false;
|
||||
return Name == li.Name && li.LimitValue == LimitValue;
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// 返回互锁限制监测的信号当前值和期望值不相等的条件是否触发。
|
||||
/// <remarks>
|
||||
/// 捕获当前值和期望值不相等信号的上升沿,当上升沿到达时触发输出Q。
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsTriggered()
|
||||
{
|
||||
_trigger.CLK = !CheckInRange();
|
||||
return _trigger.Q;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据互锁条件判断是否允许DO输出。
|
||||
/// </summary>
|
||||
/// <param name="reason">如果禁止DO输出,返回互锁原因。</param>
|
||||
/// <returns></returns>
|
||||
public bool CanDo(out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
if (CheckInRange())
|
||||
return true;
|
||||
|
||||
reason = LimitReason;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定互锁限制条件中限制条件的内容。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string GetLimitValue()
|
||||
{
|
||||
return LimitValue.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定互锁限制条件中当前IO状态。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual string GetCurrentValue()
|
||||
{
|
||||
return CurrentValue.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查当前值是否在指定范围内。
|
||||
/// </summary>
|
||||
/// <returns>True: 在范围内; False:超出范围。</returns>
|
||||
protected abstract bool CheckInRange();
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc />
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}, Limit={LimitValue}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using SciChart.Core.Extensions;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore;
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Utils;
|
||||
|
||||
public class InterlockLimitRangeDouble : IAnalogInterlockLimitRange<double>
|
||||
{
|
||||
|
@ -40,6 +41,9 @@ public class InterlockLimitRangeDouble : IAnalogInterlockLimitRange<double>
|
|||
|
||||
public virtual bool CheckIsInRange(double currentValue)
|
||||
{
|
||||
if (double.IsNaN(currentValue))
|
||||
return false;
|
||||
|
||||
return currentValue >= Min && currentValue <= Max;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore;
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Utils;
|
||||
|
||||
public class InterlockLimitRangeInt: IAnalogInterlockLimitRange<int>
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace Aitex.Core.RT.IOCore;
|
||||
namespace Aitex.Core.RT.IOCore.Interlock.Utils;
|
||||
|
||||
public class InterlockLimitRangeShort : IAnalogInterlockLimitRange<short>
|
||||
{
|
|
@ -48,9 +48,6 @@
|
|||
<RootNamespace>MECF.Framework.Common</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CommandLine">
|
||||
<HintPath>..\Dependencies\CommandLine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DocumentFormat.OpenXml, Version=2.10.1.0, Culture=neutral, PublicKeyToken=8fb06cb64d019a17, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Dependencies\DocumentFormat.OpenXml.dll</HintPath>
|
||||
|
@ -340,18 +337,34 @@
|
|||
<Compile Include="Aitex\Core\RT\IOCore\GetValue.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Index.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interfaces\IAnalogInterlockLimitRange.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interfaces\IInterlockAction.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interfaces\IInterlockLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interfaces\IInterlockLimitDataProvider.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interfaces\IIOAccessor.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\AiLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\AoLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DiLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DoLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\InterlockAction.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\InterlockLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\InterlockLimitRangeDouble.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\InterlockLimitRangeInt.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\InterlockLimitRangeShort.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Actions\InterlockAction.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Actions\InterlockActionBase.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Actions\InterlockDaemonAction.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DataProvider\AiValueProvider.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DataProvider\AoValueProvider.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DataProvider\BoolDataPollProvider.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DataProvider\DataPollProviderBase.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DataProvider\DiValueProvider.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DataProvider\DoubleDataPollProvider.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DataProvider\DoValueProvider.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\DataProvider\IoValueProviderBase.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\InterlockDaemonManager.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\InterlockManager.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\InterlockManagerBase.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Limits\AiLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Limits\AoLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Limits\BoolDataPollLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Limits\DiLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Limits\DoLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Limits\DoubleDataPollLimit.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Limits\InterlockLimitBase.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Utils\InterlockLimitRangeDouble.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Utils\InterlockLimitRangeInt.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\Interlock\Utils\InterlockLimitRangeShort.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\IO.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\IOAccessor.cs" />
|
||||
<Compile Include="Aitex\Core\RT\IOCore\IOType.cs" />
|
||||
|
|
|
@ -164,16 +164,16 @@ namespace MECF.Framework.Common.Equipment
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的字符串转换为模组名称枚举。
|
||||
/// 将指定的字符串转换为模组名称枚举。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 关于有效的模组名称,请参考<see cref="ModuleName"/>枚举。
|
||||
/// 关于有效的模组名称,请参考<see cref="ModuleName"/>枚举。
|
||||
/// </remarks>
|
||||
/// <param name="module">模组名称。</param>
|
||||
/// <param name="module">模组名称。</param>
|
||||
/// <returns>
|
||||
/// <see cref="ModuleName"/>。
|
||||
/// <see cref="ModuleName"/>。
|
||||
/// <br/>
|
||||
/// 如果指定的名称不存在,则返回<see cref="ModuleName.UnDefined"/>。
|
||||
/// 如果指定的名称不存在,则返回<see cref="ModuleName.UnDefined"/>。
|
||||
/// </returns>
|
||||
public static ModuleName Converter(string module)
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Xml;
|
|||
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;
|
||||
|
@ -51,21 +52,28 @@ namespace MECF.Framework.Common.IOCore
|
|||
|
||||
private PeriodicJob _monitorThread;
|
||||
|
||||
public void Initialize(string interlockConfigFile)
|
||||
public void Initialize(string interlockConfigFile, string daemonConfigFile = "")
|
||||
{
|
||||
string reason = string.Empty;
|
||||
if (!Singleton<InterlockManager>.Instance.Initialize(interlockConfigFile, _doMap, _diMap, _aiMap, _aoMap, out reason))
|
||||
{
|
||||
throw new Exception($"interlock define file found error: \r\n {reason}");
|
||||
}
|
||||
_monitorThread = new PeriodicJob(200, OnTimer, "IO Monitor Thread", isStartNow: true);
|
||||
}
|
||||
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))
|
||||
{
|
||||
//TODO 暂时允许不定义Daemon配置文件
|
||||
// throw new Exception($"init {nameof(InterlockDaemonManager)} error: \r\n {reason2}");
|
||||
|
||||
LOG.Error($"init {nameof(InterlockDaemonManager)} error: \r\n{reason2}");
|
||||
}*/
|
||||
}
|
||||
|
||||
private bool OnTimer()
|
||||
{
|
||||
try
|
||||
{
|
||||
Singleton<InterlockManager>.Instance.Monitor();
|
||||
// Singleton<InterlockDaemonManager>.Instance.Monitor();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -113,6 +121,8 @@ namespace MECF.Framework.Common.IOCore
|
|||
OP.Subscribe("System.SetAoValueWithPrivoder", InvokeSetAoWithPrivoder);
|
||||
OP.Subscribe("System.SetAiBuffer", InvokeSetAiBuffer);
|
||||
OP.Subscribe("System.SetDiBuffer", InvokeSetDiBuffer);
|
||||
|
||||
_monitorThread = new PeriodicJob(200, OnTimer, "IO Monitor Thread", isStartNow: true);
|
||||
}
|
||||
|
||||
private bool InvokeSetDo(string arg1, object[] args)
|
||||
|
|
|
@ -341,7 +341,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
_scUpLatchTimeout = ParseScNode("ChamberMoveBodyUpLatchTimeOut", node, "PM", "PM.LidMotionTimeout");
|
||||
_scForwardTimeout = ParseScNode("ChamberMoveBodyForwardTimeOut", node, "PM", "PM.LidMotionTimeout");
|
||||
_scBackwardTimeout = ParseScNode("ChamberMoveBodyBackwardTimeOut", node, "PM", "PM.LidMotionTimeout");
|
||||
_scByPassInterLock= ParseScNode("BypassInterlock", node, "PM", "System.BypassInterlock");
|
||||
_scByPassInterLock= ParseScNode("BypassInterlock", node, "PM", $"PM.{module}.BypassInterlock");
|
||||
|
||||
}
|
||||
public bool Initialize()
|
||||
|
|
|
@ -13,6 +13,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using MECF.Framework.Common.MECF.Framework.Common.Utilities;
|
||||
|
||||
namespace Aitex.Core.RT.Device.Devices
|
||||
{
|
||||
|
@ -138,6 +139,11 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回隔热罩在位判断结果,是否在高位。
|
||||
/// </summary>
|
||||
public bool RingInpUp => DoubleUtil.IsEqual(RingCurPos, RingUpPos);
|
||||
|
||||
public float RingDownPos
|
||||
{
|
||||
get
|
||||
|
@ -149,6 +155,11 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回隔热罩在位判断结果,是否在低位。
|
||||
/// </summary>
|
||||
public bool RingInpDown => DoubleUtil.IsEqual(RingCurPos, RingDownPos);
|
||||
|
||||
private DeviceTimer _timer = new DeviceTimer();
|
||||
|
||||
private SCConfigItem _scUpPos;
|
||||
|
@ -201,8 +212,12 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
DATA.Subscribe($"{Module}.{Name}.RingUpPos", () => RingUpPos);
|
||||
DATA.Subscribe($"{Module}.{Name}.RingDownPos", () => RingDownPos);
|
||||
|
||||
DATA.Subscribe($"{Module}.{Name}.RingInpUp", () => RingInpUp);
|
||||
DATA.Subscribe($"{Module}.{Name}.RingInpDown", () => RingInpDown);
|
||||
|
||||
DATA.Subscribe($"{Module}.{Name}.RingUpSensor", () => RingUpSensor);
|
||||
DATA.Subscribe($"{Module}.{Name}.RingDownSensor", () => RingDownSensor);
|
||||
|
||||
|
||||
DATA.Subscribe($"{Module}.{Name}.RingDone", () => RingDone);
|
||||
DATA.Subscribe($"{Module}.{Name}.RingIsServoOn", () => RingServoOn);
|
||||
|
|
|
@ -168,21 +168,21 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
No related alarm according to the interlock table
|
||||
<Limit ai="PM1.AI_ActualSpeed" value="0" tip="" tip.zh-CN="" tip.en-US="AI-118" /> 这个放在配置文件里面,程序会变得很慢,所以放到ChamMoveBodyUpDownEnableCanDo函数里
|
||||
*/
|
||||
[Subscription("PSU1.AllHeatEnable")]
|
||||
public bool AllHeatEnable { get; set; }
|
||||
//[Subscription("PSU1.AllHeatEnable")]
|
||||
//public bool AllHeatEnable { get; set; }
|
||||
|
||||
[Subscription("IsService")]
|
||||
public bool IsService { get; set; }
|
||||
/// <summary>
|
||||
/// DO-100
|
||||
/// </summary>
|
||||
public bool ChamMoveBodyUpDownEnable
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ChamMoveBodyUpDownEnableCanDo();
|
||||
}
|
||||
}
|
||||
//[Subscription("IsService")]
|
||||
//public bool IsService { get; set; }
|
||||
///// <summary>
|
||||
///// DO-100
|
||||
///// </summary>
|
||||
//public bool ChamMoveBodyUpDownEnable
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// return this.ChamMoveBodyUpDownEnableCanDo();
|
||||
// }
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -583,7 +583,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
|
||||
public bool Initialize()
|
||||
{
|
||||
DATA.Subscribe($"{Module}.{Name}.ChamMoveBodyUpDownEnable", () => ChamMoveBodyUpDownEnable);
|
||||
//DATA.Subscribe($"{Module}.{Name}.ChamMoveBodyUpDownEnable", () => ChamMoveBodyUpDownEnable);
|
||||
|
||||
|
||||
return true;
|
||||
|
@ -820,9 +820,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public bool DoReactorATMTransferReady
|
||||
{
|
||||
get
|
||||
|
@ -1209,18 +1207,29 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ChamMoveBodyUpDownEnableCanDo
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool ChamMoveBodyUpDownEnableCanDo()
|
||||
{
|
||||
//PSU disable
|
||||
///// <summary>
|
||||
///// ChamMoveBodyUpDownEnableCanDo
|
||||
///// </summary>
|
||||
///// <returns></returns>
|
||||
//public bool ChamMoveBodyUpDownEnableCanDo()
|
||||
//{
|
||||
// //PSU disable
|
||||
|
||||
//Service mode
|
||||
// //Service mode
|
||||
// var v39 = (bool)DATA.Poll(Module, "V39.Status");
|
||||
// var v40 = (bool)DATA.Poll(Module, "V40.Status");
|
||||
// var v41 = (bool)DATA.Poll(Module, "V41.Status");
|
||||
// var v53 = (bool)DATA.Poll(Module, "V53.Status");
|
||||
// var v54 = (bool)DATA.Poll(Module, "V54.Status");
|
||||
// var v55 = (bool)DATA.Poll(Module, "V55.Status");
|
||||
// var v59 = (bool)DATA.Poll(Module, "V59.Status");
|
||||
|
||||
return (!AllHeatEnable) && IsService && (_aiActualSpeed.Value == 0);
|
||||
}
|
||||
// var isService = (bool)DATA.Poll(Module, "IsService");
|
||||
// var heaterEnabled = (bool)DATA.Poll(Module, "PSU1.AllHeatEnable");
|
||||
|
||||
|
||||
// return (!AllHeatEnable) && IsService && (_aiActualSpeed.Value == 0);
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ using Aitex.Core.Util;
|
|||
using Aitex.Core.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
|
||||
|
@ -584,7 +585,12 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
if (isEnable)
|
||||
{
|
||||
if (!_doTVValveEnable.SetValue(isEnable, out reason))
|
||||
{
|
||||
EV.PostWarningLog(Module, $"Turn {Display} On failed for Interlock!");
|
||||
EV.PostMessage(Module, EventEnum.SwInterlock, Module, string.Format("Valve {0} was {1},Reason:{2}", Display, "Closed", reason));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Thread.Sleep(50);
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
|
||||
public class IoValve : BaseDevice, IDevice, IValve
|
||||
{
|
||||
#region Variables
|
||||
|
||||
private readonly DeviceTimer _timMfcRampTimeout = new DeviceTimer();
|
||||
private const int MFC_RAMP_DURATION_SEC = 5;
|
||||
private const int MFC_RAMP_TIMEOUT_SEC = 10;
|
||||
|
@ -26,6 +28,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
private IoMFC _mappedMfc;
|
||||
private bool _isValveOnAfterMfcRamp;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public string GVName { get { return Name; } }
|
||||
|
@ -151,13 +154,18 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
_doClose = ParseDoNode("doClose", node, ioModule);
|
||||
|
||||
_uniqueName = $"{Module}.{Name}";
|
||||
_scBypassEnableTable = ParseScNode("BypassEnableTable", node, module, "System.BypassEnableTable");
|
||||
|
||||
if(module.StartsWith("PM"))
|
||||
_scBypassEnableTable = ParseScNode("scBypassEnableTable", node, module, $"PM.{module}.BypassEnableTable");
|
||||
else
|
||||
_scBypassEnableTable = ParseScNode("scBypassEnableTable", node, module, $"{module}.BypassEnableTable");
|
||||
}
|
||||
|
||||
public bool Initialize()
|
||||
{
|
||||
DATA.Subscribe($"Device.{Module}.{GVName}", () => DeviceData);
|
||||
DATA.Subscribe($"{_uniqueName}.DeviceData", () => DeviceData);
|
||||
DATA.Subscribe($"{_uniqueName}.Status", () => Status);
|
||||
|
||||
OP.Subscribe($"{_uniqueName}.{AITValveOperation.GVTurnValve}", InvokeOpenCloseValve);
|
||||
|
||||
|
@ -227,7 +235,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
return true;
|
||||
}
|
||||
|
||||
public void Monitor()
|
||||
protected override void HandleMonitor()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -274,7 +282,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
|
||||
}
|
||||
}
|
||||
else if(ActionAfterOnOff != null)
|
||||
else if (ActionAfterOnOff != null)
|
||||
{
|
||||
ActionAfterOnOff(Status);
|
||||
}
|
||||
|
@ -306,7 +314,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
}
|
||||
|
||||
//达到一定条件,强制打开阀门
|
||||
if(FuncForceOpen!= null && !_scBypassEnableTable.BoolValue)
|
||||
if (FuncForceOpen != null && !_scBypassEnableTable.BoolValue)
|
||||
{
|
||||
forceOpenTrigger.CLK = FuncForceOpen(Status);
|
||||
if (forceOpenTrigger.Q)
|
||||
|
@ -319,9 +327,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
catch (Exception ex)
|
||||
{
|
||||
LOG.Write(ex);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -349,7 +355,8 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
{
|
||||
if (!FuncCheckInterLock(isOn))
|
||||
{
|
||||
EV.PostWarningLog(Module, $"Turn {Display} {ValveStateToString(isOn)} failed for check condition!");
|
||||
reason = "Interlock check failed!";
|
||||
//EV.PostWarningLog(Module, $"Turn {Display} {ValveStateToString(isOn)} failed for check condition!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -448,7 +455,7 @@ namespace Aitex.Core.RT.Device.Devices
|
|||
{
|
||||
EV.PostInfoLog(Module, $"Start to ramp {_mfcName} to 0.0{_mappedMfc.Unit}");
|
||||
_isValveOnAfterMfcRamp = isOn;
|
||||
_mappedMfc.Ramp(0, 5*1000);
|
||||
_mappedMfc.Ramp(0, 5 * 1000);
|
||||
_timMfcRampTimeout.Start(10 * 1000);
|
||||
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ namespace MECF.Framework.RT.EquipmentLibrary.HardwareUnits.PMs
|
|||
|
||||
public abstract bool IsService { get; }
|
||||
|
||||
public bool IsBypassInterlock => SC.GetValue<bool>($"PM.{Module}.BypassInterlock");
|
||||
|
||||
public bool IsBypassEnableTable => SC.GetValue<bool>($"PM.{Module}.BypassEnableTable");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
@ -60,20 +64,14 @@ namespace MECF.Framework.RT.EquipmentLibrary.HardwareUnits.PMs
|
|||
/// 检查是否Bypass了Interlock。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected void DoIfInterlockEnabled(Action action)
|
||||
protected void DoIfInterlockNotBypassed(Action action)
|
||||
{
|
||||
// 读取系统配置
|
||||
var byPassInterlock = SC.GetValue<bool>("System.BypassInterlock");
|
||||
var byPassEnableTable = SC.GetValue<bool>("System.BypassEnableTable");
|
||||
|
||||
if (byPassInterlock)
|
||||
EV.PostWarningLog(Module, "System.BypassInterlock is True");
|
||||
else if (byPassEnableTable)
|
||||
EV.PostWarningLog(Module, "System.BypassEnableTable is True");
|
||||
if (IsBypassInterlock)
|
||||
EV.PostWarningLog(Module, $"Interlock is bypassed");
|
||||
else if (IsBypassEnableTable)
|
||||
EV.PostWarningLog(Module, $"Enable table is bypassed");
|
||||
else
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -12,6 +12,7 @@ using Caliburn.Micro.Core;
|
|||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Aitex.Core.Common.DeviceData.IoDevice;
|
||||
using OpenSEMI.ClientBase;
|
||||
|
@ -61,7 +62,6 @@ namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
|
|||
public PMProcessViewModel()
|
||||
{
|
||||
_recipeGasFlowCalculator = IoC.Get<IRecipeGasFlowCalculator>();
|
||||
Subscribe("System.IsEngMode");
|
||||
IsBusyGasFlowSum = false;
|
||||
_busyIndicatorContentExport = new Progress<ProgressUpdatingEventArgs>(e =>
|
||||
{
|
||||
|
@ -94,9 +94,7 @@ namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
|
|||
NotifyOfPropertyChange();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEngMode { get; private set; }
|
||||
|
||||
|
||||
[Subscription("IsBusy")]
|
||||
public bool IsBusy { get; set; }
|
||||
|
||||
|
@ -398,9 +396,6 @@ namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
|
|||
{
|
||||
try
|
||||
{
|
||||
if (data.TryGetValue("System.IsEngMode", out var isEngMode) && isEngMode is bool b)
|
||||
IsEngMode = b;
|
||||
|
||||
if (_needLoadRecipe)
|
||||
_needLoadRecipe = false;
|
||||
|
||||
|
@ -829,9 +824,25 @@ namespace MECF.Framework.UI.Client.CenterViews.Modules.PM
|
|||
|
||||
public void StartProcess()
|
||||
{
|
||||
if (IsEngMode)
|
||||
var pollBypassInterlock= $"{SystemName}.IsBypassInterlock";
|
||||
var pollBypassEnableTable = $"{SystemName}.IsBypassEnableTable";
|
||||
var pollIsService = $"{SystemName}.IsService";
|
||||
|
||||
var data = QueryDataClient.Instance.Service.PollData(new[]
|
||||
{ pollBypassInterlock, pollBypassEnableTable, pollIsService });
|
||||
|
||||
Debug.Assert(data != null, "Unable to poll data from RT.");
|
||||
Debug.Assert(data.Count == 3, "The count of polled data is incorrect.");
|
||||
|
||||
if ((bool)data[pollBypassInterlock] || (bool)data[pollBypassEnableTable])
|
||||
{
|
||||
DialogBox.ShowError("Interlock is bypassed, can not start process.");
|
||||
DialogBox.ShowError("Interlock or EnableTable is bypassed, can not start process.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((bool)data[pollIsService])
|
||||
{
|
||||
DialogBox.ShowError($"{SystemName} is in Service Mode, can not start process.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<UserControl x:Class="MECF.Framework.UI.Client.Ctrlib.Controls.ModuleStatusIndicator"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MECF.Framework.UI.Client.Ctrlib.Controls"
|
||||
xmlns:converters="clr-namespace:MECF.Framework.UI.Core.Converters;assembly=MECF.Framework.UI.Core"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<converters:BoolReverseConverter x:Key="BoolReverseConverter"/>
|
||||
<converters:BoolVisibilityConverter x:Key="BoolVisibilityConverter"/>
|
||||
<local:ModuleIsOnlineToBgColorConverter x:Key="IsOnlineToBdrColor"/>
|
||||
<local:ModuleStatusToBackgroundColorConverter x:Key="StatusToBgColor"/>
|
||||
</UserControl.Resources>
|
||||
<DockPanel LastChildFill="True" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Mode=OneTime}">
|
||||
<Label
|
||||
DockPanel.Dock="Left"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Width="{Binding CaptionWidth}"
|
||||
BorderBrush="{Binding IsOnline, Converter={StaticResource IsOnlineToBdrColor}}"
|
||||
Content="{Binding Caption}"
|
||||
ToolTip="{Binding ModuleDescription}"
|
||||
Style="{DynamicResource TopLable_LeftTop}" HorizontalAlignment="Left">
|
||||
<Label.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem
|
||||
x:Name="CmSetOnline"
|
||||
Header="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.Content}"
|
||||
HeaderStringFormat="Set {0} Online"
|
||||
IsEnabled="{Binding IsOnline, Converter={StaticResource BoolReverseConverter}}"
|
||||
Click="CmSetOnline_OnClick"/>
|
||||
<MenuItem
|
||||
x:Name="CmSetOffline"
|
||||
Header="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.Content}"
|
||||
HeaderStringFormat="Set {0} Offline"
|
||||
IsEnabled="{Binding IsOnline}"
|
||||
Click="CmSetOffline_OnClick"/>
|
||||
</ContextMenu>
|
||||
</Label.ContextMenu>
|
||||
</Label>
|
||||
|
||||
<Grid>
|
||||
<Image
|
||||
Source="pack://application:,,,/MECF.Framework.UI.Core;component/Resources/SystemLog/Warning.png"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 0 10 0"
|
||||
Height="16"
|
||||
Width="16"
|
||||
Visibility="{Binding HasWarning, Converter={StaticResource BoolVisibilityConverter}, ConverterParameter=True}"
|
||||
ToolTip="{Binding WarningTip}"
|
||||
Panel.ZIndex="999"/>
|
||||
<TextBox
|
||||
VerticalContentAlignment="Center"
|
||||
Background="{Binding Status, Converter={StaticResource StatusToBgColor}}"
|
||||
Style="{StaticResource TextBox_Top}"
|
||||
Text="{Binding Status, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap" Margin="0,2,2,2" />
|
||||
</Grid>
|
||||
|
||||
</DockPanel>
|
||||
</UserControl>
|
|
@ -0,0 +1,259 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MECF.Framework.UI.Client.Ctrlib.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ModuleStatusIndicator.xaml
|
||||
/// </summary>
|
||||
public partial class ModuleStatusIndicator : UserControl
|
||||
{
|
||||
public ModuleStatusIndicator()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
#region DP - Caption
|
||||
|
||||
public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register(
|
||||
nameof(Caption), typeof(string), typeof(ModuleStatusIndicator), new PropertyMetadata(default(string)));
|
||||
|
||||
/// <summary>
|
||||
/// 设置或返回标题
|
||||
/// </summary>
|
||||
public string Caption
|
||||
{
|
||||
get => (string)GetValue(CaptionProperty);
|
||||
set => SetValue(CaptionProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - Caption Width
|
||||
|
||||
public static readonly DependencyProperty CaptionWidthProperty = DependencyProperty.Register(
|
||||
nameof(CaptionWidth), typeof(double), typeof(ModuleStatusIndicator), new PropertyMetadata(100.0d));
|
||||
|
||||
public double CaptionWidth
|
||||
{
|
||||
get => (double)GetValue(CaptionWidthProperty);
|
||||
set => SetValue(CaptionWidthProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - Status
|
||||
|
||||
public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(
|
||||
nameof(Status), typeof(string), typeof(ModuleStatusIndicator), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Status
|
||||
{
|
||||
get => (string)GetValue(StatusProperty);
|
||||
set => SetValue(StatusProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - IsOnline
|
||||
|
||||
public static readonly DependencyProperty IsOnlineProperty = DependencyProperty.Register(
|
||||
nameof(IsOnline), typeof(bool), typeof(ModuleStatusIndicator), new PropertyMetadata(default(bool)));
|
||||
|
||||
public bool IsOnline
|
||||
{
|
||||
get => (bool)GetValue(IsOnlineProperty);
|
||||
set => SetValue(IsOnlineProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - ModuleName
|
||||
|
||||
public static readonly DependencyProperty ModuleNameProperty = DependencyProperty.Register(
|
||||
nameof(ModuleName), typeof(string), typeof(ModuleStatusIndicator), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ModuleName
|
||||
{
|
||||
get => (string)GetValue(ModuleNameProperty);
|
||||
set => SetValue(ModuleNameProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - Module Description
|
||||
|
||||
public static readonly DependencyProperty ModuleDescriptionProperty = DependencyProperty.Register(
|
||||
nameof(ModuleDescription), typeof(string), typeof(ModuleStatusIndicator), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ModuleDescription
|
||||
{
|
||||
get => (string)GetValue(ModuleDescriptionProperty);
|
||||
set => SetValue(ModuleDescriptionProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - HasWarning
|
||||
|
||||
public static readonly DependencyProperty HasWarningProperty = DependencyProperty.Register(
|
||||
nameof(HasWarning), typeof(bool), typeof(ModuleStatusIndicator), new PropertyMetadata(default(bool)));
|
||||
|
||||
public bool HasWarning
|
||||
{
|
||||
get => (bool)GetValue(HasWarningProperty);
|
||||
set => SetValue(HasWarningProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - WarningTips
|
||||
|
||||
public static readonly DependencyProperty WarningTipProperty = DependencyProperty.Register(
|
||||
nameof(WarningTip), typeof(string), typeof(ModuleStatusIndicator), new PropertyMetadata(default(string)));
|
||||
|
||||
public string WarningTip
|
||||
{
|
||||
get => (string)GetValue(WarningTipProperty);
|
||||
set => SetValue(WarningTipProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - SetOnline Command
|
||||
|
||||
public static readonly DependencyProperty SetOnlineCommandProperty = DependencyProperty.Register(
|
||||
nameof(SetOnlineCommand), typeof(ICommand), typeof(ModuleStatusIndicator), new PropertyMetadata(default(ICommand)));
|
||||
|
||||
public ICommand SetOnlineCommand
|
||||
{
|
||||
get => (ICommand)GetValue(SetOnlineCommandProperty);
|
||||
set => SetValue(SetOnlineCommandProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DP - SetOffline Command
|
||||
|
||||
public static readonly DependencyProperty SetOfflineCommandProperty = DependencyProperty.Register(
|
||||
nameof(SetOfflineCommand), typeof(ICommand), typeof(ModuleStatusIndicator), new PropertyMetadata(default(ICommand)));
|
||||
|
||||
public ICommand SetOfflineCommand
|
||||
{
|
||||
get => (ICommand)GetValue(SetOfflineCommandProperty);
|
||||
set => SetValue(SetOfflineCommandProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handler
|
||||
|
||||
private void CmSetOnline_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RaiseEvent(new RoutedEventArgs(SetOnlineEvent));
|
||||
}
|
||||
|
||||
private void CmSetOffline_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RaiseEvent(new RoutedEventArgs(SetOfflineEvent));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Routed Events
|
||||
|
||||
public static readonly RoutedEvent SetOnlineEvent = EventManager.RegisterRoutedEvent(nameof(SetOnline), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl));
|
||||
|
||||
public event RoutedEventHandler SetOnline
|
||||
{
|
||||
add => AddHandler(SetOnlineEvent, value);
|
||||
remove => RemoveHandler(SetOnlineEvent, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent SetOfflineEvent = EventManager.RegisterRoutedEvent(nameof(SetOffline), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl));
|
||||
|
||||
public event RoutedEventHandler SetOffline
|
||||
{
|
||||
add => AddHandler(SetOfflineEvent, value);
|
||||
remove => RemoveHandler(SetOfflineEvent, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class ModuleIsOnlineToBgColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isOnline)
|
||||
{
|
||||
if (isOnline)
|
||||
return new SolidColorBrush(Colors.LawnGreen);
|
||||
else
|
||||
{
|
||||
return new SolidColorBrush(Colors.Gray);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ModuleStatusToBackgroundColorConverter : IValueConverter
|
||||
{
|
||||
public static Color GetStatusBackground(string status)
|
||||
{
|
||||
if (status != null)
|
||||
{
|
||||
status = status.Trim().ToLower();
|
||||
}
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case "error":
|
||||
return Colors.OrangeRed;
|
||||
case "processidle":
|
||||
case "vacidle":
|
||||
case "idle":
|
||||
case "manual":
|
||||
return Colors.White;
|
||||
case "notconnect":
|
||||
case "init":
|
||||
return Colors.Yellow;
|
||||
case "offline":
|
||||
case "notinstall":
|
||||
return Colors.Gray;
|
||||
default:
|
||||
return Colors.LawnGreen;
|
||||
}
|
||||
}
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string status)
|
||||
{
|
||||
return new SolidColorBrush(GetStatusBackground(status));
|
||||
}
|
||||
|
||||
return Colors.BlanchedAlmond;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -257,6 +257,9 @@
|
|||
<Compile Include="Ctrlib\Controls\HeaterResPresenter.xaml.cs">
|
||||
<DependentUpon>HeaterResPresenter.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Ctrlib\Controls\ModuleStatusIndicator.xaml.cs">
|
||||
<DependentUpon>ModuleStatusIndicator.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Ctrlib\Controls\PanelLocker.xaml.cs">
|
||||
<DependentUpon>PanelLocker.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -913,6 +916,10 @@
|
|||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Ctrlib\Controls\ModuleStatusIndicator.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="RecipeEditorLib\DGExtension\DataGridRecipe.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MECF.Framework.UI.Core.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// EngMode转换为主界面边框颜色
|
||||
/// </summary>
|
||||
public class EngModeToBdColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isEngMode)
|
||||
{
|
||||
return isEngMode ? new SolidColorBrush(Colors.OrangeRed) : new SolidColorBrush(Colors.LightCyan);
|
||||
}
|
||||
|
||||
return new SolidColorBrush(Colors.Red);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace MECF.Framework.UI.Core.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// EngMode转换为主界面边框宽度
|
||||
/// </summary>
|
||||
public class EngModeToBdThicknessConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
/*
|
||||
if (value is bool isEngMode)
|
||||
{
|
||||
return isEngMode ? new Thickness(5) : new Thickness(2);
|
||||
}
|
||||
*/
|
||||
|
||||
return new Thickness(2);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace MECF.Framework.UI.Core.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// EngMode转换为主界面背景水印显示状态
|
||||
/// </summary>
|
||||
public class EngModeToBgWatermarkVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool isEngMode)
|
||||
{
|
||||
return isEngMode ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -291,9 +291,6 @@
|
|||
<Compile Include="Converters\CenterBorderGapMaskConverter.cs" />
|
||||
<Compile Include="Converters\ColorConverter_IsTestOK.cs" />
|
||||
<Compile Include="Converters\ColorToBrushConverter.cs" />
|
||||
<Compile Include="Converters\EngModeToBdColorConverter.cs" />
|
||||
<Compile Include="Converters\EngModeToBdThicknessConverter.cs" />
|
||||
<Compile Include="Converters\EngModeToBgWatermarkVisibilityConverter.cs" />
|
||||
<Compile Include="Converters\GeneralConverter.cs" />
|
||||
<Compile Include="Converters\IntPermissionToIsEnabledConverter.cs" />
|
||||
<Compile Include="Converters\MarkdownToHtmlConverter.cs" />
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using Xunit;
|
||||
using Aitex.Core.RT.IOCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Aitex.Core.RT.SCCore;
|
||||
using MECF.Framework.Common.IOCore;
|
||||
using MECF.Framework.Common.SCCore;
|
||||
|
@ -19,6 +13,7 @@ namespace Aitex.Core.RT.IOCore.Tests
|
|||
private const string FN_IO_TABLE = "SupportFiles\\_ioDefinePM1.xml";
|
||||
private const string FN_DEVICE_MODEL = "SupportFiles\\DeviceModelPM1.xml";
|
||||
private const string FN_INTERLOCK = "SupportFiles\\interlockPM1.xml";
|
||||
private const string FN_DAEMON = "SupportFiles\\interlockDaemonPM1.xml";
|
||||
|
||||
[Fact()]
|
||||
public void InitializeTest()
|
||||
|
|
|
@ -62,6 +62,9 @@
|
|||
<Content Include="SupportFiles\DeviceModelPM1.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="SupportFiles\InterlockDaemonPM1.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="SupportFiles\interlockPM1.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
|
@ -556,6 +556,7 @@
|
|||
diPSU2Status="DI_PSU2Status" diPSU3Status="DI_PSU3Status" diSCR1Status="DI_SCR1Status" diSCR2Status="DI_SCR2Status" diSCR3Status="DI_SCR3Status"
|
||||
doPSU1Enable="DO_PSU1Enable" doPSU2Enable="DO_PSU2Enable" doPSU3Enable="DO_PSU3Enable" doSCR1Enable="DO_SCR1Enable"
|
||||
doSCR2Enable="DO_SCR2Enable" doSCR3Enable="DO_SCR3Enable" doTCSSupply="DO_TCSFluidInfusion" aiTempCtrl1="AI_PSUTC"
|
||||
|
||||
doReactorATMTransferReady="DO_ReactorATMTransferReady" doReactorVACTransferReady="DO_ReactorVACTransferReady" diPMATMSW="DI_PMATMSW"
|
||||
aiActualSpeed="AI_ActualSpeed" diPSUEnable="DI_PSUEnableFB" diHeaterTempBelow900CSW="DI_HeaterTempLowLimitSW"
|
||||
doPMASlitDoorClosed="DO_SlitVlvClosed" diChamLidClosed="DI_ChamLidClosed" diConfinementRingDown="DI_ConfinementRingDown"
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Daemon>
|
||||
<Action do="PM1.DO_ReactorATMTransferReady" value="true" tip="" tip.zh-CN="" tip.en-US="DO-170">
|
||||
<OR>
|
||||
<Limit di="PM1.DI_PMATMSW" value="true" tip="" tip.zh-CN="" tip.en-US="DI-9"/>
|
||||
<Limit di="PM1.DI_ConfinementRingDown" value="true" tip="" tip.zh-CN="" tip.en-US="DI-50"/>
|
||||
</OR>
|
||||
<Limit do="PM1.DO_HeaterEnable" value="false" tip="" tip.zh-CN="" tip.en-US="DO-56"/>
|
||||
<Limit ai="PM1.AI_ActualSpeed" value="-0.001:0.001" tip="" tip.zh-CN="" tip.en-US="AI-118"/>
|
||||
<Limit pollbool="PM1.ConfinementRing.RingInpDown" value="true" tip="" tip.zh-CN="" tip.en-US=""/>
|
||||
</Action>
|
||||
<Action do="PM1.DO_ReactorVACTransferReady" value="true" tip="" tip.zh-CN="" tip.en-US="DO-171">
|
||||
<Limit di="PM1.DI_PMATMSW" value="true" tip="" tip.zh-CN="" tip.en-US="DI-9"/>
|
||||
<Limit di="PM1.DI_ConfinementRingDown" value="true" tip="" tip.zh-CN="" tip.en-US="DI-50"/>
|
||||
<Limit di="PM1.DI_HeaterTempLowLimitSW" value="true" tip="" tip.zh-CN="" tip.en-US="DI-11"/>
|
||||
<Limit ai="PM1.AI_ActualSpeed" value="-0.001:0.001" tip="" tip.zh-CN="" tip.en-US="AI-118"/>
|
||||
<Limit pollbool="PM1.ConfinementRing.RingInpDown" value="true" tip="" tip.zh-CN="" tip.en-US=""/>
|
||||
</Action>
|
||||
</Daemon>
|
Loading…
Reference in New Issue