//******************************************************************************************************
// 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
}
}