//****************************************************************************************************** // Alarm.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 01/31/2012 - Stephen C. Wills // Generated original version of source code. // 02/10/2012 - Stephen C. Wills // Moved to TimeSeriesFramework project. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.ComponentModel; namespace GSF.TimeSeries { #region [ Enumerations ] /// /// Represents the two states that an alarm can be in: raised or cleared. /// public enum AlarmState { /// /// Indicates that an alarm is cleared. /// Cleared = 0, /// /// Indicates that an alarm has been raised. /// Raised = 1 } /// /// Represents the severity of alarms. /// public enum AlarmSeverity { /// /// Indicates that an alarm is of no importance. /// [Description("NONE")] None = 0, /// /// Indicates that an alarm is informative, but not dangerous. /// [Description("INFO")] Information = 50, /// /// Indicates that an alarm is not very important. /// [Description("LOW")] Low = 150, /// /// Indicates that an alarm is somewhat important. /// [Description("MEDLOW")] MediumLow = 300, /// /// Indicates that an alarm is moderately importance. /// [Description("MED")] Medium = 500, /// /// Indicates that an alarm is important. /// [Description("MEDHIGH")] MediumHigh = 700, /// /// Indicates that an alarm is very important. /// [Description("HIGH")] High = 850, /// /// Indicates an alarm for a value that is unreasonable. /// [Description("RANGE")] Unreasonable = 900, /// /// Indicates than an alarm signifies a dangerous situation. /// [Description("CRITICAL")] Critical = 950, /// /// Indicates an alarm for a value that is latched, i.e., flat-lined. /// [Description("FLATLINE")] Latched = 980, /// /// Indicates that an alarm reports bad data. /// [Description("ERROR")] Error = 1000 } /// /// Represents the operation to be performed /// when testing values from an incoming signal. /// public enum AlarmOperation { /// /// Internal range test /// [Description("Equal to")] Equal = 1, /// /// External range test /// [Description("Not equal to")] NotEqual = 2, /// /// Upper bound /// [Description("Greater than or equal to")] GreaterOrEqual = 11, /// /// Lower bound /// [Description("Less than or equal to")] LessOrEqual = 21, /// /// Upper limit /// [Description("Greater than")] GreaterThan = 12, /// /// Lower limit /// [Description("Less than")] LessThan = 22, /// /// Latched value /// [Description("Latched")] Flatline = 3 } #endregion /// /// Represents an alarm that tests the values of /// an incoming signal to determine the state of alarm. /// [Serializable] public class Alarm : ICloneable { #region [ Members ] // Fields private int m_id; private string m_tagName; private Guid m_signalID; private Guid? m_associatedMeasurementID; private string m_description; private AlarmSeverity m_severity; private AlarmOperation m_operation; private double? m_setPoint; private double? m_tolerance; private double? m_delay; private double? m_hysteresis; private AlarmState m_state; private Ticks m_timeRaised; [NonSerialized] private Ticks m_lastNegative; [NonSerialized] private double m_lastValue; [NonSerialized] private Ticks m_lastChanged; [NonSerialized] private IMeasurement m_cause; [NonSerialized] private Func m_raiseTest; [NonSerialized] private Func m_clearTest; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public Alarm() : this(AlarmOperation.Equal) { } /// /// Creates a new instance of the class. /// /// The operation to be performed when testing values from the incoming signal. public Alarm(AlarmOperation operation) { Operation = operation; } #endregion #region [ Properties ] /// /// Gets or sets the identification number of the alarm. /// public int ID { get { return m_id; } set { m_id = value; } } /// /// Gets or sets the tag name of the alarm. /// public string TagName { get { return m_tagName; } set { m_tagName = value; } } /// /// Gets or sets the identification number of the /// signal whose value is monitored by the alarm. /// public Guid SignalID { get { return m_signalID; } set { m_signalID = value; } } /// /// Gets or sets the identification number of /// the measurements generated for alarm events. /// public Guid? AssociatedMeasurementID { get { return m_associatedMeasurementID; } set { m_associatedMeasurementID = value; } } /// /// Gets or sets the description of the alarm. /// public string Description { get { return m_description; } set { m_description = value; } } /// /// Gets or sets the operation to be performed /// when testing values from the incoming signal. /// public AlarmOperation Operation { get { return m_operation; } set { m_operation = value; m_raiseTest = GetRaiseTest(); m_clearTest = GetClearTest(); } } /// /// Gets or sets the severity of the alarm. /// public AlarmSeverity Severity { get { return m_severity; } set { m_severity = value; } } /// /// Gets or sets the value to be compared against /// the signal to determine whether to raise the /// alarm. This value is irrelevant for the /// operation. /// public double? SetPoint { get { return m_setPoint; } set { m_setPoint = value; } } /// /// Gets or sets a tolerance window around the /// to use when comparing /// against the value of the signal. This value /// is only relevant for the /// and operations. /// /// /// The equal and not equal operations are actually /// internal and external range tests based on the setpoint /// and the tolerance. The two tests are performed as follows. /// /// /// Equal: (value >= SetPoint - Tolerance) && (value <= SetPoint + Tolerance) /// Not equal: (value < SetPoint - Tolerance) || (value > SetPoint + Tolerance) /// /// public double? Tolerance { get { return m_tolerance; } set { m_tolerance = value; } } /// /// Gets or sets the amount of time that the /// signal must be exhibiting alarming behavior /// before the alarm is raised. /// public double? Delay { get { return m_delay; } set { m_delay = value; } } /// /// Gets or sets the hysteresis used when clearing /// alarms. This value is only relevant in greater /// than (or equal) and less than (or equal) operations. /// /// /// The hysteresis is an offset that provides padding between /// the point at which the alarm is raised and the point at /// which the alarm is cleared. For example, in the case of the /// operation: /// /// /// Raised: value >= SetPoint /// Cleared: value < SetPoint - Hysteresis /// /// /// The direction of the offset depends on whether the /// operation is greater than (or equal) or less than (or equal). /// The hysteresis must be greater than zero. /// public double? Hysteresis { get { return m_hysteresis; } set { m_hysteresis = value; } } /// /// Gets or sets the current state of the alarm (raised or cleared). /// public AlarmState State { get { return m_state; } set { m_state = value; } } /// /// Gets or sets the timestamp of the most recent /// measurement that caused the alarm to be raised. /// public Ticks TimeRaised { get { return m_timeRaised; } set { m_timeRaised = value; } } /// /// Gets the most recent measurement /// that caused the alarm to be raised. /// public IMeasurement Cause { get { return m_cause; } private set { m_cause = value; if ((object)m_cause != null) m_timeRaised = m_cause.Timestamp; } } #endregion #region [ Methods ] /// /// Tests the value of the given measurement to determine /// whether the alarm should be raised or cleared. /// /// The measurement whose value is to be tested. /// true if the alarm's state changed; false otherwise public bool Test(IMeasurement measurement) { if (m_state == AlarmState.Raised && m_clearTest(measurement)) { m_state = AlarmState.Cleared; return true; } if (m_state == AlarmState.Cleared && m_raiseTest(measurement)) { m_state = AlarmState.Raised; Cause = measurement; return true; } return false; } /// /// Creates a new alarm that is a copy of the current instance. /// /// /// A new alarm that is a copy of this instance. /// /// 2 public Alarm Clone() { return (Alarm)MemberwiseClone(); } /// /// Creates a new object that is a copy of the current instance. /// /// /// A new object that is a copy of this instance. /// /// 2 object ICloneable.Clone() { return MemberwiseClone(); } // Returns the function used to determine when the alarm is raised. private Func GetRaiseTest() { switch (m_operation) { case AlarmOperation.Equal: return RaiseIfEqual; case AlarmOperation.NotEqual: return RaiseIfNotEqual; case AlarmOperation.GreaterOrEqual: return RaiseIfGreaterOrEqual; case AlarmOperation.LessOrEqual: return RaiseIfLessOrEqual; case AlarmOperation.GreaterThan: return RaiseIfGreaterThan; case AlarmOperation.LessThan: return RaiseIfLessThan; case AlarmOperation.Flatline: return RaiseIfFlatline; default: throw new ArgumentOutOfRangeException(); } } // Returns the function used to determine when the alarm is cleared. private Func GetClearTest() { switch (m_operation) { case AlarmOperation.Equal: return ClearIfNotEqual; case AlarmOperation.NotEqual: return ClearIfNotNotEqual; case AlarmOperation.GreaterOrEqual: return ClearIfNotGreaterOrEqual; case AlarmOperation.LessOrEqual: return ClearIfNotLessOrEqual; case AlarmOperation.GreaterThan: return ClearIfNotGreaterThan; case AlarmOperation.LessThan: return ClearIfNotLessThan; case AlarmOperation.Flatline: return ClearIfNotFlatline; default: throw new ArgumentOutOfRangeException(); } } // Indicates whether the given measurement is // equal to the set point within the tolerance. private bool RaiseIfEqual(IMeasurement measurement) { bool isEqual = (measurement.Value <= m_setPoint + m_tolerance) && (measurement.Value >= m_setPoint - m_tolerance); return CheckDelay(measurement, isEqual); } // Indicates whether the given measurement is outside // the range defined by the set point and tolerance. private bool RaiseIfNotEqual(IMeasurement measurement) { bool isNotEqual = (measurement.Value < m_setPoint - m_tolerance) || (measurement.Value > m_setPoint + m_tolerance); return CheckDelay(measurement, isNotEqual); } // Indicates whether the given measurement // is greater than or equal to the set point. private bool RaiseIfGreaterOrEqual(IMeasurement measurement) { return CheckDelay(measurement, measurement.Value >= m_setPoint); } // Indicates whether the given measurement // is less than or equal to the set point. private bool RaiseIfLessOrEqual(IMeasurement measurement) { return CheckDelay(measurement, measurement.Value <= m_setPoint); } // Indicates whether the given measurement // is greater than the set point. private bool RaiseIfGreaterThan(IMeasurement measurement) { return CheckDelay(measurement, measurement.Value > m_setPoint); } // Indicates whether the given measurement // is less than the set point. private bool RaiseIfLessThan(IMeasurement measurement) { return CheckDelay(measurement, measurement.Value < m_setPoint); } // Indicates whether the given measurement has maintained the same // value for at least a number of seconds defined by the delay. private bool RaiseIfFlatline(IMeasurement measurement) { long dist, diff; if (measurement.Value != m_lastValue) { m_lastChanged = measurement.Timestamp; m_lastValue = measurement.Value; } dist = Ticks.FromSeconds(Delay.GetValueOrDefault()); diff = measurement.Timestamp - m_lastChanged; return diff >= dist; } // Indicates whether the given measurement is not // equal to the set point within the tolerance. private bool ClearIfNotEqual(IMeasurement measurement) { return (measurement.Value < m_setPoint - m_tolerance) || (measurement.Value > m_setPoint + m_tolerance); } // Indicates whether the given measurement is not outside // the range defined by the set point and tolerance. private bool ClearIfNotNotEqual(IMeasurement measurement) { return (measurement.Value <= m_setPoint + m_tolerance) && (measurement.Value >= m_setPoint - m_tolerance); } // Indicates whether the given measurement is not greater // than or equal to the set point, offset by the hysteresis. private bool ClearIfNotGreaterOrEqual(IMeasurement measurement) { return measurement.Value < m_setPoint - m_hysteresis; } // Indicates whether the given measurement is not less // than or equal to the set point, offset by the hysteresis. private bool ClearIfNotLessOrEqual(IMeasurement measurement) { return measurement.Value > m_setPoint + m_hysteresis; } // Indicates whether the given measurement is not greater // than the set point, offset by the hysteresis. private bool ClearIfNotGreaterThan(IMeasurement measurement) { return measurement.Value <= m_setPoint - m_hysteresis; } // Indicates whether the given measurement is not less // than the set point, offset by the hysteresis. private bool ClearIfNotLessThan(IMeasurement measurement) { return measurement.Value >= m_setPoint + m_hysteresis; } // Indicates whether the given measurement's value has changed. private bool ClearIfNotFlatline(IMeasurement measurement) { if (measurement.Value != m_lastValue) { m_lastChanged = measurement.Timestamp; m_lastValue = measurement.Value; return true; } return false; } // Keeps track of the signal's timestamps to determine whether a given // measurement is eligible to raise the alarm based on the delay. private bool CheckDelay(IMeasurement measurement, bool raiseCondition) { Ticks dist; if (!raiseCondition) { // Keep track of the last time // the signal failed the raise test m_lastNegative = measurement.Timestamp; } else { // Get the amount of time since the last // time the signal failed the raise test dist = measurement.Timestamp - m_lastNegative; // If the amount of time is larger than // the delay threshold, raise the alarm if (dist >= Ticks.FromSeconds(m_delay.GetValueOrDefault())) return true; } return false; } #endregion } }