443 lines
17 KiB
C#
443 lines
17 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Linq;
|
||
using System.Xml;
|
||
using Aitex.Core.RT.Event;
|
||
using Aitex.Core.RT.IOCore;
|
||
using Aitex.Core.RT.Log;
|
||
using Aitex.Core.RT.SCCore;
|
||
using MECF.Framework.Common.Equipment;
|
||
using MECF.Framework.Common.Event;
|
||
|
||
namespace Aitex.Core.RT.Device
|
||
{
|
||
/// <summary>
|
||
/// 设备类型的基类。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 该类描述了系统使用的设备对象。该设备对象为系统中最小控制单元,所有模组(Module)通过此设备对象对实际的硬件设备进行控制。
|
||
/// </remarks>
|
||
public abstract class BaseDevice : IAlarmHandler
|
||
{
|
||
#region Variables
|
||
|
||
/// <summary>
|
||
/// 当设备报警状态发生变化时,调用此事件。
|
||
/// </summary>
|
||
public event Action<string, AlarmEventItem> OnDeviceAlarmStateChanged;
|
||
|
||
/// <summary>
|
||
/// 设备线程锁。
|
||
/// </summary>
|
||
protected readonly object SyncRoot;
|
||
|
||
/// <summary>
|
||
/// 报警信息字典。
|
||
/// </summary>
|
||
protected readonly Dictionary<string, AlarmEventItem> DicAlarms;
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
/// <summary>
|
||
/// 创建设备对象的实例。
|
||
/// </summary>
|
||
protected BaseDevice()
|
||
{
|
||
SyncRoot = new object();
|
||
ScBasePath = ModuleName.System.ToString();
|
||
IoBasePath = ModuleName.System.ToString();
|
||
DicAlarms = new Dictionary<string, AlarmEventItem>();
|
||
IsEnabled = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建设备对象的实例。
|
||
/// </summary>
|
||
/// <param name="module">设备所属的模组名称。</param>
|
||
/// <param name="name">设备名称。</param>
|
||
/// <param name="display">设备在用户界面的显示名称。</param>
|
||
/// <param name="id">设备编号。</param>
|
||
protected BaseDevice(string module, string name, string display, string id) : this()
|
||
{
|
||
display = (string.IsNullOrEmpty(display) ? name : display);
|
||
id = (string.IsNullOrEmpty(id) ? name : id);
|
||
Module = module;
|
||
Name = name;
|
||
Display = display;
|
||
DeviceID = id;
|
||
UniqueName = module + "." + name;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据XML的配置描述创建设备对象的实例。
|
||
/// </summary>
|
||
/// <param name="module">设备所属的模组名称。</param>
|
||
/// <param name="node">配置文件描述本设备的Xml节点。</param>
|
||
/// <param name="ioModule"></param>
|
||
protected BaseDevice(string module, XmlElement node, string ioModule = "") : this()
|
||
{
|
||
var attrModule = node.GetAttribute("module");
|
||
Module = string.IsNullOrEmpty(attrModule) ? module : attrModule;
|
||
Unit = node.GetAttribute("unit");
|
||
Name = node.GetAttribute("id");
|
||
Display = node.GetAttribute("display");
|
||
DeviceID = node.GetAttribute("schematicId");
|
||
UniqueName = module + "." + Name;
|
||
|
||
var scBasePath = node.GetAttribute("scBasePath");
|
||
scBasePath = string.IsNullOrEmpty(scBasePath) ?
|
||
$"{Module}.{Name}"
|
||
: scBasePath.Replace("{module}", Module);
|
||
|
||
ScBasePath = scBasePath;
|
||
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备是否被使能。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 默认情况下,当设备被创建时自动使能;但如果设备对应的Module被配置为UnInstalled,则该设备
|
||
/// 会被其所属Module禁用,以提高RT性能,及避免产生不必要的报警信息。
|
||
/// </remarks>
|
||
public bool IsEnabled { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备在系统中唯一的名称。
|
||
/// </summary>
|
||
public string UniqueName { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备所属的模组名称。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 关于系统中有效的模组名称,请参考<see cref="ModuleName"/>。
|
||
/// </remarks>
|
||
public string Module { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备名称。
|
||
/// </summary>
|
||
public string Name { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备向用户显示的名称。
|
||
/// </summary>
|
||
public string Display { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备编号。
|
||
/// </summary>
|
||
public string DeviceID { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备的计量单位。
|
||
/// </summary>
|
||
public string Unit { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备配置信息位于系统配置的节点名称。
|
||
/// </summary>
|
||
public string ScBasePath { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回当前设备所使用的IO Provider。
|
||
/// </summary>
|
||
public string IoBasePath { get; set; }
|
||
|
||
/// <summary>
|
||
/// 设置或返回设备是否产生报警。
|
||
/// </summary>
|
||
public bool HasAlarm =>
|
||
DicAlarms?.Values.FirstOrDefault((AlarmEventItem x) => !x.IsAcknowledged && x.Level == EventLevel.Alarm) !=
|
||
null;
|
||
|
||
#endregion
|
||
|
||
#region Methods
|
||
|
||
/// <summary>
|
||
/// 加载指定的系统配置。
|
||
/// </summary>
|
||
/// <param name="scPath">指定的系统配置路径</param>
|
||
/// <param name="scPathFallback">当指定的系统配置路径不存在时,使用默认路径</param>
|
||
/// <param name="valueFallback">当指定的系统配置值数据类型错误时,使用此默认值</param>
|
||
/// <param name="valueHolder">存放系统配置的变量</param>
|
||
/// <param name="scValueChangedCallback">监测系统配置是否发生变化,当发生变化时,执行此回调函数</param>
|
||
/// <typeparam name="T"></typeparam>
|
||
protected void LoadSC<T>(string scPath, string scPathFallback, T valueFallback, ref T valueHolder, Action<T> scValueChangedCallback = null)
|
||
{
|
||
if (scPath == null) throw new ArgumentNullException(nameof(scPath));
|
||
if (scPathFallback == null) throw new ArgumentNullException(nameof(scPathFallback));
|
||
if (valueFallback == null) throw new ArgumentNullException(nameof(valueFallback));
|
||
if (valueHolder == null) throw new ArgumentNullException(nameof(valueHolder));
|
||
|
||
// 读取系统配置
|
||
var path = scPath;
|
||
var scItem = SC.GetConfigItem(path);
|
||
if (scItem == null)
|
||
{
|
||
// 没找到配置,用默认路径找
|
||
path = scPathFallback;
|
||
scItem = SC.GetConfigItem(path);
|
||
}
|
||
|
||
if (scItem != null)
|
||
{
|
||
// 找到系统配置,尝试转换并赋值到指定的字段
|
||
try
|
||
{
|
||
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||
valueHolder = (T)converter.ConvertFromString(scItem.Value.ToString());
|
||
|
||
// 如果指定了该回调,则表明需要监测当前系统配置值
|
||
if (scValueChangedCallback != null)
|
||
{
|
||
SC.RegisterValueChangedCallback(path, v =>
|
||
{
|
||
var val = (T)converter.ConvertFromString(scItem.Value.ToString());
|
||
scValueChangedCallback(val);
|
||
});
|
||
}
|
||
}
|
||
catch (Exception)
|
||
{
|
||
EV.PostWarningLog(Module,
|
||
$"System config {path} type mismatched, set to default value");
|
||
valueHolder = valueFallback;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 指定路径和默认路径均找不到配置,使用默认配置
|
||
EV.PostWarningLog(Module,
|
||
$"System config {path} type mismatched, set to default value");
|
||
valueHolder = valueFallback;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 订阅报警事件。
|
||
/// </summary>
|
||
/// <param name="name">报警事件名称。</param>
|
||
/// <param name="description">报警事件描述。</param>
|
||
/// <param name="resetChecker">报警复位检查回调函数。</param>
|
||
/// <param name="level">报警等级,请参考<see cref="EventLevel"/></param>
|
||
/// <returns></returns>
|
||
protected AlarmEventItem SubscribeAlarm(string name, string description, Func<bool> resetChecker, EventLevel level = EventLevel.Alarm)
|
||
{
|
||
var ae = new AlarmEventItem(Module, name, description, resetChecker, this);
|
||
ae.Level = level;
|
||
|
||
DicAlarms[name] = ae;
|
||
EV.Subscribe(ae);
|
||
return ae;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除当前设备的报警信息。
|
||
/// </summary>
|
||
protected void ResetAlarm()
|
||
{
|
||
foreach (var alarm in DicAlarms)
|
||
alarm.Value.Reset();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前对象的子类实现的扫描任务。
|
||
/// </summary>
|
||
protected virtual void HandleMonitor()
|
||
{
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行当前设备扫描周期任务。
|
||
/// </summary>
|
||
public void Monitor()
|
||
{
|
||
// 如果当前设备被标记为禁用,则不要执行周期性扫描任务。
|
||
if(IsEnabled)
|
||
HandleMonitor();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通知当前设备的报警信息发生了变化。
|
||
/// </summary>
|
||
/// <param name="args"></param>
|
||
public void AlarmStateChanged(AlarmEventItem args)
|
||
{
|
||
OnDeviceAlarmStateChanged?.Invoke(UniqueName ?? "", args);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从XML配置描述中解析指定的DO对象。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 通过该方法从XML配置中获取指定的DO名称,从系统点表中查找并返回指定名称的DO实例。
|
||
/// </remarks>
|
||
/// <param name="name">XML配置节点中定义DO的属性的名称。</param>
|
||
/// <param name="node">XML配置节点。</param>
|
||
/// <param name="ioModule">IO Provider名称。</param>
|
||
/// <returns>
|
||
/// 指定名称的<see cref="DOAccessor"/>对象。
|
||
/// <br/>
|
||
/// 如果未找到指定名称的DO,则返回null。
|
||
/// </returns>
|
||
public DOAccessor ParseDoNode(string name, XmlElement node, string ioModule = "")
|
||
{
|
||
if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
|
||
{
|
||
return IO.DO[string.IsNullOrEmpty(ioModule) ? node.GetAttribute(name).Trim() : (ioModule + "." + node.GetAttribute(name).Trim())];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从XML配置描述中解析指定的DI对象。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 通过该方法从XML配置中获取指定的DI名称,从系统点表中查找并返回指定名称的DI实例。
|
||
/// </remarks>
|
||
/// <param name="name">XML配置节点中定义DI的属性的名称。</param>
|
||
/// <param name="node">XML配置节点。</param>
|
||
/// <param name="ioModule">IO Provider名称。</param>
|
||
/// <returns>
|
||
/// 指定名称的<see cref="DIAccessor"/>对象。
|
||
/// <br/>
|
||
/// 如果未找到指定名称的DI,则返回null。
|
||
/// </returns>
|
||
public DIAccessor ParseDiNode(string name, XmlElement node, string ioModule = "")
|
||
{
|
||
if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
|
||
{
|
||
return IO.DI[string.IsNullOrEmpty(ioModule) ? node.GetAttribute(name).Trim() : (ioModule + "." + node.GetAttribute(name).Trim())];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从XML配置描述中解析指定的AO对象。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 通过该方法从XML配置中获取指定的AO名称,从系统点表中查找并返回指定名称的AO实例。
|
||
/// </remarks>
|
||
/// <param name="name">XML配置节点中定义AO的属性的名称。</param>
|
||
/// <param name="node">XML配置节点。</param>
|
||
/// <param name="ioModule">IO Provider名称。</param>
|
||
/// <returns>
|
||
/// 指定名称的<see cref="AOAccessor"/>对象。
|
||
/// <br/>
|
||
/// 如果未找到指定名称的AO,则返回null。
|
||
/// </returns>
|
||
public AOAccessor ParseAoNode(string name, XmlElement node, string ioModule = "")
|
||
{
|
||
if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
|
||
{
|
||
return IO.AO[string.IsNullOrEmpty(ioModule) ? node.GetAttribute(name).Trim() : (ioModule + "." + node.GetAttribute(name).Trim())];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从XML配置描述中解析指定的AI对象。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 通过该方法从XML配置中获取指定的AI名称,从系统点表中查找并返回指定名称的AI实例。
|
||
/// </remarks>
|
||
/// <param name="name">XML配置节点中定义AI的属性的名称。</param>
|
||
/// <param name="node">XML配置节点。</param>
|
||
/// <param name="ioModule">IO Provider名称。</param>
|
||
/// <returns>
|
||
/// 指定名称的<see cref="AIAccessor"/>对象。
|
||
/// <br/>
|
||
/// 如果未找到指定名称的AI,则返回null。
|
||
/// </returns>
|
||
public AIAccessor ParseAiNode(string name, XmlElement node, string ioModule = "")
|
||
{
|
||
if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
|
||
{
|
||
return IO.AI[string.IsNullOrEmpty(ioModule) ? node.GetAttribute(name).Trim() : (ioModule + "." + node.GetAttribute(name).Trim())];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从XML配置描述中解析当前设备配置信息位于系统配置中的节点。
|
||
/// </summary>
|
||
/// <param name="name">XML配置节点中定义系统配置节点的属性的名称。</param>
|
||
/// <param name="node">XML配置节点。</param>
|
||
/// <param name="ioModule">IO Provider名称。</param>
|
||
/// <param name="defaultScPath">如果未找到name指定的系统配置节点,则使用此默认系统配置节点。</param>
|
||
/// <returns>
|
||
/// <see cref="SCConfigItem"/>。
|
||
/// </returns>
|
||
public SCConfigItem ParseScNode(string name, XmlElement node, string ioModule = "", string defaultScPath = "")
|
||
{
|
||
SCConfigItem sCConfigItem = null;
|
||
if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
|
||
{
|
||
sCConfigItem = SC.GetConfigItem(node.GetAttribute(name));
|
||
}
|
||
if (sCConfigItem == null && !string.IsNullOrEmpty(defaultScPath) && SC.ContainsItem(defaultScPath))
|
||
{
|
||
sCConfigItem = SC.GetConfigItem(defaultScPath);
|
||
}
|
||
return sCConfigItem;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从XML配置描述中解析设备名称,并从系统设备列表中查找该设备。
|
||
/// </summary>
|
||
/// <typeparam name="T">待查找的设备的类型。</typeparam>
|
||
/// <param name="name">XML配置节点中定义设备名称的属性的名称。</param>
|
||
/// <param name="node">XML配置节点。</param>
|
||
/// <returns>
|
||
/// 指定的设备实例。
|
||
/// <br/>
|
||
/// 如果未找到指定的设备,则返回null。
|
||
/// </returns>
|
||
public static T ParseDeviceNode<T>(string name, XmlElement node) where T : class, IDevice
|
||
{
|
||
if (!string.IsNullOrEmpty(node.GetAttribute(name).Trim()))
|
||
{
|
||
return DEVICE.GetDevice<T>(node.GetAttribute(name));
|
||
}
|
||
LOG.Write($"{node.InnerXml},未定义{name}");
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从XML配置描述中解析设备名称,并从系统设备列表中查找该设备。
|
||
/// </summary>
|
||
/// <typeparam name="T">待查找的设备的类型。</typeparam>
|
||
/// <param name="module">设备所属模组。</param>
|
||
/// <param name="name">XML配置节点中定义设备名称的属性的名称。</param>
|
||
/// <param name="node">XML配置节点。</param>
|
||
/// <returns>
|
||
/// 指定的设备实例。
|
||
/// <br/>
|
||
/// 如果未找到指定的设备,则返回null。
|
||
/// </returns>
|
||
public static T ParseDeviceNode<T>(string module, string name, XmlElement node) where T : class, IDevice
|
||
{
|
||
var attribute = node.GetAttribute(name);
|
||
if (!string.IsNullOrEmpty(attribute) && !string.IsNullOrEmpty(attribute.Trim()))
|
||
{
|
||
return DEVICE.GetDevice<T>(module + "." + attribute);
|
||
}
|
||
LOG.Write($"{node.InnerXml},未定义{name}");
|
||
return null;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|