//****************************************************************************************************** // 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: // ---------------------------------------------------------------------------------------------------- // 02/10/2012 - Stephen C. Wills // Generated original version of source code. // 12/20/2012 - Starlynn Danyelle Gilliam // Modifeid Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Data; using System.Linq; using System.Text; using GSF.Data; namespace GSF.TimeSeries.UI.DataModels { /// /// Represents a record of information as defined in the database. /// public class Alarm : DataModelBase { #region [ Members ] // Fields private Guid m_nodeId; private int m_id; private string m_tagName; private Guid m_signalId; private Guid? m_associatedMeasurementID; private string m_description; private int m_severity; private int m_operation; private double? m_setPoint; private double? m_tolerance; private double? m_delay; private double? m_hysteresis; private int m_loadOrder; private bool m_enabled; private DateTime m_createdOn; private string m_createdBy; private DateTime m_updatedOn; private string m_updatedBy; private string m_operationDescription; private string m_severityName; private bool m_createAssociatedMeasurement; private bool m_setPointEnabled; private bool m_toleranceEnabled; private bool m_delayEnabled; private bool m_hysteresisEnabled; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public Alarm() { CreateAssociatedMeasurement = true; } #endregion #region [ Properties ] /// /// Gets or sets NodeID /// [Required(ErrorMessage = " Alarm node ID is a required field, please select a value.")] public Guid NodeID { get { return m_nodeId; } set { m_nodeId = value; OnPropertyChanged("NodeID"); } } /// /// Gets or sets ID /// public int ID { get { return m_id; } set { m_id = value; OnPropertyChanged("ID"); } } /// /// Gets or sets TagName /// [Required(ErrorMessage = " Alarm tag name is a required field, please select a value.")] [StringLength(200, ErrorMessage = "Alarm tag name cannot exceed 200 characters.")] public string TagName { get { return m_tagName; } set { m_tagName = value; OnPropertyChanged("TagName"); } } /// /// Gets or sets SignalID /// [Required(ErrorMessage = " Alarm signal ID is a required field, please select a value.")] public Guid SignalID { get { return m_signalId; } set { m_signalId = value; OnPropertyChanged("SignalID"); } } /// /// Gets or sets AssociatedMeasurementID /// public Guid? AssociatedMeasurementID { get { return m_associatedMeasurementID; } set { m_associatedMeasurementID = value; OnPropertyChanged("AssociatedMeasurementID"); } } /// /// Gets or sets Description /// public string Description { get { if (string.IsNullOrEmpty(m_description) && !string.IsNullOrEmpty(m_operationDescription)) return "Alarm for " + m_operationDescription; return m_description; } set { m_description = value; OnPropertyChanged("Description"); } } /// /// Gets or sets Severity /// [Required(ErrorMessage = " Alarm severity is a required field, please select a value.")] public int Severity { get { return m_severity; } set { m_severity = value; OnPropertyChanged("Severity"); } } /// /// Gets or sets Operation /// [Required(ErrorMessage = " Alarm operation is a required field, please select a value.")] public int Operation { get { return m_operation; } set { m_operation = value; OnPropertyChanged("Operation"); } } /// /// Gets or sets SetPoint /// public double? SetPoint { get { return m_setPoint; } set { m_setPoint = value; OnPropertyChanged("SetPoint"); } } /// /// Gets or sets Tolerance /// public double? Tolerance { get { return m_tolerance; } set { m_tolerance = value; OnPropertyChanged("Tolerance"); } } /// /// Gets or sets Delay /// public double? Delay { get { return m_delay; } set { m_delay = value; OnPropertyChanged("Delay"); } } /// /// Gets or sets Hysteresis /// public double? Hysteresis { get { return m_hysteresis; } set { m_hysteresis = value; OnPropertyChanged("Hysteresis"); } } /// /// Gets or sets LoadOrder /// [Required(ErrorMessage = " Alarm load order is a required field, please provide value.")] [DefaultValue(0)] public int LoadOrder { get { return m_loadOrder; } set { m_loadOrder = value; OnPropertyChanged("LoadOrder"); } } /// /// Gets or sets Enabled. /// [DefaultValue(false)] public bool Enabled { get { return m_enabled; } set { m_enabled = value; OnPropertyChanged("Enabled"); } } /// /// Gets or sets CreatedOn. /// // Field is populated by database via trigger and has no screen interaction, so no validation attributes are applied public DateTime CreatedOn { get { return m_createdOn; } set { m_createdOn = value; } } /// /// Gets or sets CreatedBy. /// // Field is populated by database via trigger and has no screen interaction, so no validation attributes are applied public string CreatedBy { get { return m_createdBy; } set { m_createdBy = value; } } /// /// Gets or sets UpdatedOn. /// // Field is populated by database via trigger and has no screen interaction, so no validation attributes are applied public DateTime UpdatedOn { get { return m_updatedOn; } set { m_updatedOn = value; } } /// /// Gets or sets UpdatedBy. /// // Field is populated by database via trigger and has no screen interaction, so no validation attributes are applied public string UpdatedBy { get { return m_updatedBy; } set { m_updatedBy = value; } } /// /// Gets or sets OperationDescription /// public string OperationDescription { get { return m_operationDescription; } set { m_operationDescription = value; OnPropertyChanged("OperationDescription"); } } /// /// Gets or sets SeverityName /// public string SeverityName { get { return m_severityName; } set { m_severityName = value; OnPropertyChanged("SeverityName"); } } /// /// Gets or sets CreateAssociatedMeasurement /// public bool CreateAssociatedMeasurement { get { return m_createAssociatedMeasurement; } set { m_createAssociatedMeasurement = value; OnPropertyChanged("CreateAssociatedMeasurement"); } } /// /// Gets or sets SetPointEnabled /// public bool SetPointEnabled { get { return m_setPointEnabled; } set { m_setPointEnabled = value; OnPropertyChanged("SetPointEnabled"); } } /// /// Gets or sets ToleranceEnabled /// public bool ToleranceEnabled { get { return m_toleranceEnabled; } set { m_toleranceEnabled = value; OnPropertyChanged("ToleranceEnabled"); } } /// /// Gets or sets DelayEnabled /// public bool DelayEnabled { get { return m_delayEnabled; } set { m_delayEnabled = value; OnPropertyChanged("DelayEnabled"); } } /// /// Gets or sets HysteresisEnabled /// public bool HysteresisEnabled { get { return m_hysteresisEnabled; } set { m_hysteresisEnabled = value; OnPropertyChanged("HysteresisEnabled"); } } #endregion #region [ Static ] // Static Methods /// /// Loads IDs as an . /// /// to connection to database. /// The field to sort by. /// ASC or DESC for ascending or descending respectively. /// Collection of . public static IList LoadKeys(AdoDataConnection database, string sortMember = "", string sortDirection = "") { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); IList alarmList = new List(); string sortClause = string.Empty; DataTable adapterTable; string query; if (!string.IsNullOrEmpty(sortMember)) sortClause = string.Format("ORDER BY {0} {1}", sortMember, sortDirection); query = database.ParameterizedQueryString(string.Format("SELECT ID FROM Alarm WHERE NodeID = {{0}} {0}", sortClause), "nodeID"); adapterTable = database.Connection.RetrieveData(database.AdapterType, query, DefaultTimeout, database.CurrentNodeID()); foreach (DataRow row in adapterTable.Rows) { alarmList.Add(row.ConvertField("ID")); } return alarmList; } finally { if (createdConnection && database != null) database.Dispose(); } } /// /// Loads information as an style list. /// /// to connection to database. /// Keys of the adapters to be loaded from the database. /// Collection of . public static ObservableCollection Load(AdoDataConnection database, IList keys) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); string query; string commaSeparatedKeys; Alarm[] alarmList = null; DataTable alarmTable; object associatedMeasurementId; int id; if ((object)keys != null && keys.Count > 0) { commaSeparatedKeys = keys.Select(key => "" + key.ToString() + "").Aggregate((str1, str2) => str1 + "," + str2); query = database.ParameterizedQueryString(string.Format("SELECT NodeID, TagName, ID, SignalID, AssociatedMeasurementID, Description, Severity, Operation, " + "SetPoint, Tolerance, Delay, Hysteresis, LoadOrder, Enabled FROM Alarm WHERE NodeID = {{0}} AND ID IN ({0})", commaSeparatedKeys), "nodeID"); alarmTable = database.Connection.RetrieveData(database.AdapterType, query, DefaultTimeout, database.CurrentNodeID()); alarmList = new Alarm[alarmTable.Rows.Count]; foreach (DataRow row in alarmTable.Rows) { id = row.ConvertField("ID"); associatedMeasurementId = row.Field("AssociatedMeasurementID"); alarmList[keys.IndexOf(id)] = new Alarm() { NodeID = database.Guid(row, "NodeID"), ID = id, TagName = row.Field("TagName"), SignalID = database.Guid(row, "SignalID"), AssociatedMeasurementID = (associatedMeasurementId != null) ? Guid.Parse(associatedMeasurementId.ToString()) : (Guid?)null, Description = row.Field("Description"), Severity = row.ConvertField("Severity"), Operation = row.ConvertField("Operation"), SetPoint = row.ConvertNullableField("SetPoint"), Tolerance = row.ConvertNullableField("Tolerance"), Delay = row.ConvertNullableField("Delay"), Hysteresis = row.ConvertNullableField("Hysteresis"), LoadOrder = row.ConvertField("LoadOrder"), Enabled = row.ConvertField("Enabled"), CreateAssociatedMeasurement = (associatedMeasurementId != null) }; } } return new ObservableCollection(alarmList ?? new Alarm[0]); } finally { if (createdConnection && database != null) database.Dispose(); } } /// /// Retrieves an information from the database based on query string filter. /// /// to connection to database. /// Filter clause to append to the SELECT query. /// information. public static Alarm GetAlarm(AdoDataConnection database, string whereClause) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); DataTable alarmTable = database.Connection.RetrieveData(database.AdapterType, "SELECT * FROM Alarm " + whereClause); if (alarmTable.Rows.Count == 0) return null; DataRow row = alarmTable.Rows[0]; object associatedMeasurementId = row.Field("AssociatedMeasurementID"); Alarm alarm = new Alarm { NodeID = database.Guid(row, "NodeID"), ID = row.ConvertField("ID"), TagName = row.Field("TagName"), SignalID = database.Guid(row, "SignalID"), AssociatedMeasurementID = ((object)associatedMeasurementId != null) ? Guid.Parse(associatedMeasurementId.ToString()) : (Guid?)null, Description = row.Field("Description"), Severity = row.ConvertField("Severity"), Operation = row.ConvertField("Operation"), SetPoint = row.ConvertNullableField("SetPoint"), Tolerance = row.ConvertNullableField("Tolerance"), Delay = row.ConvertNullableField("Delay"), Hysteresis = row.ConvertNullableField("Hysteresis"), LoadOrder = row.ConvertField("LoadOrder"), Enabled = row.ConvertField("Enabled") }; return alarm; } finally { if (createdConnection && database != null) database.Dispose(); } } /// /// Gets a style list of information. /// /// to connection to database. /// Indicates if selection on UI is optional for this collection. /// containing ID and tag name of alarms defined in the database. public static Dictionary GetLookupList(AdoDataConnection database, bool isOptional = false) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); Dictionary alarmList = new Dictionary(); if (isOptional) alarmList.Add(0, "Select Alarm"); string query = database.ParameterizedQueryString("SELECT ID, TagName FROM Alarm WHERE Enabled = {0} ORDER BY LoadOrder", "enabled"); DataTable nodeTable = database.Connection.RetrieveData(database.AdapterType, query, DefaultTimeout, database.Bool(true)); foreach (DataRow row in nodeTable.Rows) { alarmList[row.ConvertField("ID")] = row.Field("TagName"); } return alarmList; } finally { if (createdConnection && database != null) database.Dispose(); } } /// /// Saves information to database. /// /// to connection to database. /// Information about . /// String, for display use, indicating success. public static string Save(AdoDataConnection database, Alarm alarm) { bool createdConnection = false; try { createdConnection = CreateConnection(ref database); string updateQuery; Alarm createdAlarm = alarm; string successMessage = "Alarm information saved successfully"; object associatedMeasurementId = (alarm.AssociatedMeasurementID != null) ? database.Guid(alarm.AssociatedMeasurementID.Value) : DBNull.Value; AlarmMonitor monitor = AlarmMonitor.Default; if (alarm.SetPointEnabled && (object)alarm.SetPoint == null) throw new InvalidOperationException($"Set Point is required for alarms of type \"{((AlarmOperation)alarm.Operation).GetDescription()}\". Please define a Set Point."); if (alarm.ToleranceEnabled && (object)alarm.Tolerance == null) throw new InvalidOperationException($"Tolerance is required for alarms of type \"{((AlarmOperation)alarm.Operation).GetDescription()}\". Please define a Tolerance."); if (alarm.DelayEnabled && (object)alarm.Delay == null) throw new InvalidOperationException($"Delay is required for alarms of type \"{((AlarmOperation)alarm.Operation).GetDescription()}\". Please define a Delay."); if (alarm.HysteresisEnabled && (object)alarm.Hysteresis == null) throw new InvalidOperationException($"Hysteresis is required for alarms of type \"{((AlarmOperation)alarm.Operation).GetDescription()}\". Please define a Hysteresis."); if (alarm.ID == 0) { string query = database.ParameterizedQueryString("INSERT INTO Alarm (NodeID, TagName, SignalID, AssociatedMeasurementID, Description, Severity, Operation, SetPoint, Tolerance, Delay, " + "Hysteresis, LoadOrder, Enabled, UpdatedBy, UpdatedOn, CreatedBy, CreatedOn) Values ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16})", "nodeID", "tagName", "signalId", "associatedMeasurementId", "description", "severity", "operation", "setPoint", "tolerance", "delay", "hysteresis", "loadOrder", "enabled", "updatedBy", "updatedOn", "createdBy", "createdOn"); database.Connection.ExecuteNonQuery(query, DefaultTimeout, (alarm.NodeID != Guid.Empty) ? database.Guid(alarm.NodeID) : database.CurrentNodeID(), alarm.TagName.ToNotNull(), database.Guid(alarm.SignalID), associatedMeasurementId, alarm.Description.ToNotNull(), alarm.Severity, alarm.Operation, alarm.SetPoint.ToNotNull(), alarm.Tolerance.ToNotNull(), alarm.Delay.ToNotNull(), alarm.Hysteresis.ToNotNull(), alarm.LoadOrder, database.Bool(alarm.Enabled), CommonFunctions.CurrentUser, database.UtcNow, CommonFunctions.CurrentUser, database.UtcNow); createdAlarm = GetAlarm(database, string.Format("WHERE TagName = '{0}'", alarm.TagName)); } else { string query = database.ParameterizedQueryString("UPDATE Alarm SET NodeID = {0}, TagName = {1}, SignalID = {2}, AssociatedMeasurementID = {3}, Description = {4}, Severity = {5}, " + "Operation = {6}, SetPoint = {7}, Tolerance = {8}, Delay = {9}, Hysteresis = {10}, LoadOrder = {11}, Enabled = {12}, UpdatedBy = {13}, UpdatedOn = {14} WHERE ID = {15}", "nodeID", "tagName", "signalId", "associatedMeasurementId", "description", "severity", "operation", "setPoint", "tolerance", "delay", "hysteresis", "loadOrder", "enabled", "updatedBy", "updatedOn", "id"); database.Connection.ExecuteNonQuery(query, DefaultTimeout, (alarm.NodeID != Guid.Empty) ? database.Guid(alarm.NodeID) : database.CurrentNodeID(), alarm.TagName, database.Guid(alarm.SignalID), associatedMeasurementId, alarm.Description.ToNotNull(), alarm.Severity, alarm.Operation, alarm.SetPoint.ToNotNull(), alarm.Tolerance.ToNotNull(), alarm.Delay.ToNotNull(), alarm.Hysteresis.ToNotNull(), alarm.LoadOrder, database.Bool(alarm.Enabled), CommonFunctions.CurrentUser, database.UtcNow, alarm.ID); } updateQuery = database.ParameterizedQueryString("UPDATE Alarm SET AssociatedMeasurementID = {0} WHERE ID = {1}", "associatedMeasurementId", "id"); if (alarm.CreateAssociatedMeasurement && (object)alarm.AssociatedMeasurementID == null) { alarm.AssociatedMeasurementID = CreateAlarmMeasurement(database, createdAlarm); if ((object)alarm.AssociatedMeasurementID != null) database.Connection.ExecuteNonQuery(updateQuery, DefaultTimeout, database.Guid(alarm.AssociatedMeasurementID.Value), createdAlarm.ID); else successMessage += " but failed to create associated measurement"; } else if (!alarm.CreateAssociatedMeasurement && (object)alarm.AssociatedMeasurementID != null) { database.Connection.ExecuteNonQuery(updateQuery, DefaultTimeout, DBNull.Value, createdAlarm.ID); DeleteAlarmMeasurement(database, createdAlarm.AssociatedMeasurementID.Value); alarm.AssociatedMeasurementID = null; } if ((object)monitor != null) monitor.UpdateDefinedAlarms(); try { CommonFunctions.SendCommandToService("ReloadConfig"); } catch (Exception ex) { CommonFunctions.LogException(database, "Alarm Save", ex); } return successMessage; } finally { if (createdConnection && database != null) database.Dispose(); } } /// /// Deletes specified record from database. /// /// to connection to database. /// ID of the record to be deleted. /// String, for display use, indicating success. public static string Delete(AdoDataConnection database, int alarmId) { bool createdConnection = false; string query; object associatedMeasurementId; AlarmMonitor monitor = AlarmMonitor.Default; try { createdConnection = CreateConnection(ref database); // Setup current user context for any delete triggers CommonFunctions.SetCurrentUserContext(database); query = database.ParameterizedQueryString("SELECT AssociatedMeasurementID FROM Alarm WHERE ID = {0}", "alarmId"); associatedMeasurementId = database.Connection.ExecuteScalar(query, DefaultTimeout, alarmId); query = database.ParameterizedQueryString("DELETE FROM Alarm WHERE ID = {0}", "alarmId"); database.Connection.ExecuteNonQuery(query, DefaultTimeout, alarmId); if (associatedMeasurementId != null && associatedMeasurementId != DBNull.Value) { Guid signalId = Guid.Parse(associatedMeasurementId.ToString()); query = database.ParameterizedQueryString("DELETE FROM Measurement WHERE SignalID = {0}", "signalId"); database.Connection.ExecuteNonQuery(query, DefaultTimeout, signalId); } if ((object)monitor != null) monitor.UpdateDefinedAlarms(); CommonFunctions.SendCommandToService("ReloadConfig"); return "Alarm deleted successfully"; } finally { if (createdConnection && database != null) database.Dispose(); } } // Creates a measurement associated with the given alarm and returns the new measurements signal ID. private static Guid? CreateAlarmMeasurement(AdoDataConnection database, Alarm alarm) { object nodeID; Historian historian; Measurement alarmMeasurement; int signalTypeId; try { nodeID = (alarm.NodeID != Guid.Empty) ? database.Guid(alarm.NodeID) : database.CurrentNodeID(); historian = Historian.GetHistorian(database, string.Format("WHERE Acronym = 'STAT' AND NodeID = '{0}'", nodeID)); signalTypeId = Convert.ToInt32(database.Connection.ExecuteScalar("SELECT ID FROM SignalType WHERE Acronym = 'ALRM'", DefaultTimeout)); alarmMeasurement = new Measurement() { HistorianID = historian.ID, PointTag = alarm.TagName, SignalTypeID = signalTypeId, SignalReference = "ALARM!SERVICES-AL" + alarm.ID, Description = "Measurement associated with alarm " + alarm.ID, Internal = true, Enabled = true }; Measurement.Save(database, alarmMeasurement); alarmMeasurement = Measurement.GetMeasurement(database, string.Format("WHERE PointTag = '{0}'", alarm.TagName)); return alarmMeasurement.SignalID; } catch { // Return null to indicate measurement // was not saved to database return null; } } // Deletes a measurement whose signal ID matches the given measurement ID. private static void DeleteAlarmMeasurement(AdoDataConnection database, Guid measurementId) { string query = database.ParameterizedQueryString("DELETE FROM Measurement WHERE SignalID = {0}", "signalId"); database.Connection.ExecuteNonQuery(query, database.Guid(measurementId)); } /// /// Gets an operation description for an alarm. /// /// Alarm to derive description for. /// Label of associated measurement. /// Derived label. public static string GetOperationDescription(Alarm alarm, string tagName) { StringBuilder description; description = new StringBuilder(tagName); AlarmOperation operation = alarm.Operation.GetEnumValueOrDefault(AlarmOperation.Equal); switch (operation) { case AlarmOperation.Equal: description.Append(" = "); break; case AlarmOperation.NotEqual: description.Append(" != "); break; case AlarmOperation.GreaterOrEqual: description.Append(" >= "); break; case AlarmOperation.LessOrEqual: description.Append(" <= "); break; case AlarmOperation.GreaterThan: description.Append(" > "); break; case AlarmOperation.LessThan: description.Append(" < "); break; case AlarmOperation.Flatline: if ((object)alarm.Delay == null) return string.Empty; description.Append(" flat-lined for "); description.Append(alarm.Delay); description.Append(" seconds"); return description.ToString(); default: description.Append(operation.GetDescription()); break; } if (alarm.SetPoint.HasValue) description.Append(alarm.SetPoint); else description.Append("undefined"); return description.ToString(); } #endregion } }