using System; using System.Diagnostics; using System.Xml; using Aitex.Core.Common.DeviceData; using Aitex.Core.RT.DataCenter; using Aitex.Core.RT.Event; using Aitex.Core.RT.IOCore; using Aitex.Core.RT.Log; using Aitex.Core.RT.SCCore; using Aitex.Core.RT.Tolerance; using MECF.Framework.Common.Event; using MECF.Framework.Common.SicMath; namespace Aitex.Core.RT.Device.Devices { public class IoSensorAnalog : BaseDevice, IDevice { #region Variables private readonly AIAccessor _ai; private readonly AOAccessor _ao; private readonly DOAccessor _doWarningLow; private readonly DOAccessor _doWarningHigh; private readonly DOAccessor _doAlarmLow; private readonly DOAccessor _doAlarmHigh; private readonly bool _doActiveLevel; private readonly TimeDomainArithmeticMeanFilter _tdamFilter; private int _warningDelaySec; private int _alarmDelaySec; protected readonly string _scNameWarnLow; protected readonly string _scNameWarnHigh; protected readonly string _scNameAlarmLow; protected readonly string _scNameAlarmHigh; protected readonly string _scNameWarnDelay; protected readonly string _scNameAlarmDelay; protected readonly string _scNameFilterDuration; protected double _warningLow; protected double _warningHigh; protected double _alarmLow; protected double _alarmHigh; protected int _filterDurationS; /// /// 误差超限警告检查器 /// protected readonly ToleranceChecker _warningChecker; /// /// 误差超限报警检查器 /// protected readonly ToleranceChecker _alarmChecker; protected readonly AlarmEventItem _alarmEvent; protected readonly AlarmEventItem _warningEvent; #endregion /// /// 获取Sensor实时反馈值。 /// 注意:如果绑定的AI或者AO为空,返回 /// public double Value { get { if (_ai != null) return _ai.FloatValue; if (_ao != null) return _ao.FloatValue; return double.NaN; } } private AITSensorAnalogData DeviceData { get { var data = new AITSensorAnalogData() { DeviceName = Name, DeviceSchematicId = DeviceID, DisplayName = Display, WarningLimitLow = _warningLow, WarningLimitHigh = _warningHigh, AlarmLimitLow = _alarmLow, AlarmLimitHigh = _alarmHigh, Value = Value, IsWarning = _warningEvent.IsTriggered, IsAlarm = _alarmEvent.IsTriggered }; return data; } } public IoSensorAnalog(string module, XmlElement node, string ioModule = "") : base(module, node, ioModule) { var scWarningLow = node.GetAttribute("scWarningLow"); var scWarningHigh = node.GetAttribute("scWarningHigh"); var scAlarmLow = node.GetAttribute("scAlarmLow"); var scAlarmHigh = node.GetAttribute("scAlarmHigh"); var scFilterDuration = node.GetAttribute("scFilterDuration"); _scNameWarnLow = $"{ScBasePath}.{scWarningLow}"; _scNameWarnHigh = $"{ScBasePath}.{scWarningHigh}"; _scNameAlarmLow = $"{ScBasePath}.{scAlarmLow}"; _scNameAlarmHigh = $"{ScBasePath}.{scAlarmHigh}"; _scNameWarnDelay = $"{ScBasePath}.WarningDelay"; _scNameAlarmDelay = $"{ScBasePath}.AlarmDelay"; _scNameFilterDuration = $"{ScBasePath}.{scFilterDuration}"; _ai = ParseAiNode("ai", node, ioModule); _ao = ParseAoNode("ao", node, ioModule); // DO输出的有效电平,当Warn或Alarm触发时,相应的DO输出此电平 var activeLevel = node.GetAttribute("doActiveLevel"); if (bool.TryParse(activeLevel, out var bLevel)) _doActiveLevel = bLevel; else _doActiveLevel = true; _doWarningLow = ParseDoNode("doWarningLow", node, ioModule); _doWarningHigh = ParseDoNode("doWarningHigh", node, ioModule); _doAlarmLow = ParseDoNode("doAlarmLow", node, ioModule); _doAlarmHigh = ParseDoNode("doAlarmHigh", node, ioModule); // AI和AO不能同时配置 Debug.Assert((_ai != null && _ao == null) || (_ai == null && _ao != null), "Incorrect configuration."); LoadConfig(); _warningChecker = new ToleranceChecker(); _alarmChecker = new ToleranceChecker(); _warningEvent = SubscribeAlarm($"{Module}.{Display}.ToleranceWarning", "", ResetWarningChecker, EventLevel.Warning); _alarmEvent = SubscribeAlarm($"{Module}.{Display}.ToleranceAlarm", "", ResetAlarmChecker); _tdamFilter = new TimeDomainArithmeticMeanFilter(_filterDurationS); } public virtual bool Initialize() { DATA.Subscribe($"{Module}.{Name}.DeviceData", () => DeviceData); DATA.Subscribe($"{Module}.{Name}.Value", () => Value); return true; } public virtual void Terminate() { } public virtual void Monitor() { try { // 对输入量滤波 _tdamFilter.Feed(Value); var filtered = _tdamFilter.Filter(); _alarmChecker.Monitor(filtered, _alarmLow, _alarmHigh, _alarmDelaySec); if (_alarmChecker.Trig) { // 根据条件设定指定的DO if (filtered < _alarmLow) { _alarmEvent.Description = $"{Display} feedback ({filtered:F1}{Unit}) is below {_alarmLow:F1} in {_alarmDelaySec:F0} seconds"; _doAlarmLow?.SetValue(_doActiveLevel, out _); _doAlarmHigh?.SetValue(!_doActiveLevel, out _); } else if (filtered > _alarmHigh) { _alarmEvent.Description = $"{Display} feedback ({filtered:F1}{Unit}) is above {_alarmHigh:F1} in {_alarmDelaySec:F0} seconds"; _doAlarmLow?.SetValue(!_doActiveLevel, out _); _doAlarmHigh?.SetValue(_doActiveLevel, out _); } _alarmEvent.Set(); } // 如果报Alarm,不再检查Warning。 if (_alarmEvent.IsTriggered) return; // Out-of-rang warning detection _warningChecker.Monitor(filtered, _warningLow, _warningHigh, _warningDelaySec); if (_warningChecker.Trig) { // 根据条件设定指定的DO if (filtered < _warningLow) { _warningEvent.Description = $"{Display} feedback ({filtered:F1}{Unit}) is below {_warningLow:F1} in {_warningDelaySec:F0} seconds"; _doWarningLow?.SetValue(_doActiveLevel, out _); _doWarningHigh?.SetValue(!_doActiveLevel, out _); } else if (filtered > _warningHigh) { _warningEvent.Description = $"{Display} feedback ({filtered:F1}{Unit}) is above {_warningHigh:F1} in {_warningDelaySec:F0} seconds"; _doWarningLow?.SetValue(!_doActiveLevel, out _); _doWarningHigh?.SetValue(_doActiveLevel, out _); } _warningEvent.Set(); } } catch (Exception ex) { LOG.Write(ex); } } public virtual void Reset() { _warningChecker.Reset(_warningDelaySec); _alarmChecker.Reset(_alarmDelaySec); _warningEvent.Reset(); _alarmEvent.Reset(); _doAlarmLow?.SetValue(!_doActiveLevel, out _); _doAlarmHigh?.SetValue(!_doActiveLevel, out _); } #region Private Methods private bool ResetWarningChecker() { _warningChecker.Reset(_warningDelaySec); _doWarningLow?.SetValue(!_doActiveLevel, out _); _doWarningHigh?.SetValue(!_doActiveLevel, out _); return true; } private bool ResetAlarmChecker() { _alarmChecker.Reset(_alarmDelaySec); _doAlarmLow?.SetValue(!_doActiveLevel, out _); _doAlarmHigh?.SetValue(!_doActiveLevel, out _); return true; } protected virtual void LoadConfig() { _warningLow = SC.SafeGetValue(_scNameWarnLow, double.MinValue); _warningHigh = SC.SafeGetValue(_scNameWarnHigh, double.MaxValue); _alarmLow = SC.SafeGetValue(_scNameAlarmLow, double.MinValue); _alarmHigh = SC.SafeGetValue(_scNameAlarmHigh, double.MaxValue); _warningDelaySec = SC.SafeGetValue(_scNameWarnDelay, 0); _alarmDelaySec = SC.SafeGetValue(_scNameAlarmDelay, 0); _filterDurationS = SC.SafeGetValue(_scNameFilterDuration, 0); // 当设置值变化时自动重载 SC.RegisterValueChangedCallback(_scNameWarnLow, v => _warningLow = (double)v); SC.RegisterValueChangedCallback(_scNameWarnHigh, v => _warningHigh = (double)v); SC.RegisterValueChangedCallback(_scNameAlarmLow, v => _alarmLow = (double)v); SC.RegisterValueChangedCallback(_scNameAlarmHigh, v => _alarmHigh = (double)v); SC.RegisterValueChangedCallback(_scNameWarnDelay, v => _warningDelaySec = (int)v); SC.RegisterValueChangedCallback(_scNameAlarmDelay, v => _alarmDelaySec = (int)v); SC.RegisterValueChangedCallback(_scNameFilterDuration, v => { _filterDurationS = (int)v; _tdamFilter.FilterDurationInSec = _filterDurationS; }); } #endregion } }