//******************************************************************************************************
// AlarmMonitor.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/16/2012 - Stephen C. Wills
// Generated original version of source code.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Timers;
using GSF.Data;
using GSF.TimeSeries.Transport;
using GSF.TimeSeries.UI.DataModels;
using Timer = System.Timers.Timer;
namespace GSF.TimeSeries.UI
{
///
/// Represents an alarm monitor, for use inside a management UI application, that gets initial state of raised alarms
/// from an invoke based console connection command then maintains state with a data subscription to all alarms.
///
public class AlarmMonitor : IDisposable
{
#region [ Members ]
// Constants
///
/// Default refresh interval for alarm monitor.
///
public const int DefaultRefreshInterval = 10;
// For future use we may want an alarm sound for a given severity
//private const string DefaultSoundFile = "";
//private const int DefaultAlertSeverity = 850;
// Events
///
/// Event raised when alarms are refreshed.
///
public event EventHandler RefreshedAlarms;
// Fields
private AlarmStatusQuery m_alarmStatusQuery;
private DataSubscriber m_dataSubscriber;
private readonly UnsynchronizedSubscriptionInfo m_subscriptionInfo;
private volatile Dictionary m_definedAlarmsByID;
private volatile Dictionary m_definedAlarmsByMeasurement;
private readonly object m_currentAlarmsLock;
private readonly ISet m_currentAlarms;
private int m_alarmsChanged;
private Timer m_refreshTimer;
private int m_refreshInterval;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
/// Indicates whether this instance should update the global reference to become the singleton.
public AlarmMonitor(bool singleton = false)
{
object refreshInterval = IsolatedStorageManager.ReadFromIsolatedStorage("AlarmStatusRefreshInterval") ?? DefaultRefreshInterval;
if (!int.TryParse(refreshInterval.ToString(), out m_refreshInterval))
m_refreshInterval = DefaultRefreshInterval;
m_currentAlarmsLock = new object();
m_currentAlarms = new HashSet();
m_refreshTimer = new Timer(m_refreshInterval * 1000);
m_refreshTimer.Elapsed += RefreshTimer_Elapsed;
m_alarmStatusQuery = new AlarmStatusQuery();
m_alarmStatusQuery.RaisedAlarmStates += m_alarmStatusQuery_RaisedAlarmStates;
m_alarmStatusQuery.ProcessException += m_alarmStatusQuery_ProcessException;
// Load all alarms defined in the database
UpdateDefinedAlarms();
// Setup subscription to subscribe to all alarm measurements
m_subscriptionInfo = new UnsynchronizedSubscriptionInfo(false)
{
FilterExpression = "FILTER ActiveMeasurements WHERE SignalType='ALRM'"
};
m_dataSubscriber = new DataSubscriber();
m_dataSubscriber.ConnectionEstablished += m_dataSubscriber_ConnectionEstablished;
m_dataSubscriber.ReceivedServerResponse += m_dataSubscriber_ReceivedServerResponse;
m_dataSubscriber.NewMeasurements += m_dataSubscriber_NewMeasurements;
m_dataSubscriber.ProcessException += m_dataSubscriber_ProcessException;
m_dataSubscriber.ConnectionString = GetDataPublisherConnectionString();
m_dataSubscriber.OperationalModes |= OperationalModes.UseCommonSerializationFormat | OperationalModes.CompressSignalIndexCache;
m_dataSubscriber.DataLossInterval = -1;
m_dataSubscriber.Initialize();
if (singleton)
Default = this;
}
#endregion
#region [ Properties ]
///
/// Gets or sets the interval, in seconds,
/// between refreshing the alarm list.
///
public int RefreshInterval
{
get
{
return m_refreshInterval;
}
set
{
m_refreshTimer.Interval = value * 1000;
m_refreshInterval = value;
}
}
///
/// Gets the current list of raised alarms.
///
/// Current list.
public ObservableCollection GetAlarmList()
{
IEnumerable currentHighestSeverityAlarms;
lock (m_currentAlarmsLock)
{
currentHighestSeverityAlarms = m_currentAlarms
.GroupBy(alarm => alarm.SignalID)
.SelectMany(GetHighestSeverityAlarms)
.Select(CreateRaisedAlarm);
return new ObservableCollection(currentHighestSeverityAlarms);
}
}
#endregion
#region [ Methods ]
///
/// Performs application-defined tasks associated with freeing, releasing, or
/// resetting unmanaged resources.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
if ((object)m_refreshTimer != null)
{
m_refreshTimer.Dispose();
m_refreshTimer = null;
}
if ((object)m_alarmStatusQuery != null)
{
m_alarmStatusQuery.RaisedAlarmStates -= m_alarmStatusQuery_RaisedAlarmStates;
m_alarmStatusQuery.ProcessException -= m_alarmStatusQuery_ProcessException;
m_alarmStatusQuery.Dispose();
m_alarmStatusQuery = null;
}
if ((object)m_dataSubscriber != null)
{
m_dataSubscriber.ConnectionEstablished -= m_dataSubscriber_ConnectionEstablished;
m_dataSubscriber.ReceivedServerResponse -= m_dataSubscriber_ReceivedServerResponse;
m_dataSubscriber.NewMeasurements -= m_dataSubscriber_NewMeasurements;
m_dataSubscriber.ProcessException -= m_dataSubscriber_ProcessException;
m_dataSubscriber.Dispose();
m_dataSubscriber = null;
}
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
}
}
}
///
/// Starts the refresh timer that notifies consumer about the current alarm status.
///
public void Start()
{
if (!m_refreshTimer.Enabled)
{
// Start subscriber connection cycle
m_dataSubscriber.Start();
// Start the refresh timer
m_refreshTimer.Start();
}
}
///
/// Updates the collection of defined system alarms.
///
public void UpdateDefinedAlarms()
{
IEnumerable definedAlarms = LoadDefinedAlarms();
List oldCurrentAlarms;
Alarm newCurrentAlarm;
// Initialize lookup table for defined alarms by ID
m_definedAlarmsByID = definedAlarms.ToDictionary(alarm => alarm.ID);
// Initialize lookup table for defined alarms by associated measurement ID
m_definedAlarmsByMeasurement = definedAlarms
.Where(alarm => alarm.AssociatedMeasurementID.HasValue)
.GroupBy(alarm => alarm.AssociatedMeasurementID.GetValueOrDefault())
.ToDictionary(group => group.Key, group => group.First());
lock (m_currentAlarmsLock)
{
// Copy current alarms set to a list so we can iterate while removing
oldCurrentAlarms = new List(m_currentAlarms);
foreach (Alarm oldCurrentAlarm in oldCurrentAlarms)
{
// Remove the old alarm from the set of current alarms
m_currentAlarms.Remove(oldCurrentAlarm);
// If the old alarm is still defined in the updated alarms table,
// add the equivalent new defined alarm to the set of current alarms
if (m_definedAlarmsByID.TryGetValue(oldCurrentAlarm.ID, out newCurrentAlarm))
{
newCurrentAlarm.TimeRaised = oldCurrentAlarm.TimeRaised;
m_currentAlarms.Add(newCurrentAlarm);
}
}
}
// Set alarms changed state to false
Interlocked.Exchange(ref m_alarmsChanged, 0);
// Notify that the alarms have been updated
OnRefreshedAlarms();
}
///
/// Stops the refresh timer.
///
public void Stop()
{
if (m_refreshTimer.Enabled)
{
// Stop the refresh timer
m_refreshTimer.Stop();
// Stop data subscriber
m_dataSubscriber.Stop();
}
}
// Processing for the Elapsed event of the refresh timer.
private void RefreshTimer_Elapsed(object sender, ElapsedEventArgs e)
{
// No need to raise event unless current alarm state has changed
if (Interlocked.CompareExchange(ref m_alarmsChanged, 0, 1) != 0)
OnRefreshedAlarms();
}
// Gets the current data publisher connection string
private string GetDataPublisherConnectionString()
{
AdoDataConnection database = null;
try
{
// Create database connection to get the current data publisher connection string
database = new AdoDataConnection(CommonFunctions.DefaultSettingsCategory);
return database.DataPublisherConnectionString();
}
finally
{
if ((object)database != null)
database.Dispose();
}
}
// Loads existing alarms defined in the database
private IEnumerable LoadDefinedAlarms()
{
// Create alarms using definitions from the database
AdoDataConnection database = null;
try
{
// Create database connection to get currently defined alarms
database = new AdoDataConnection(CommonFunctions.DefaultSettingsCategory);
string query = database.ParameterizedQueryString("SELECT * FROM Alarm WHERE NodeID = {0} AND Enabled <> 0", "nodeID");
DataTable alarms = database.Connection.RetrieveData(database.AdapterType, query, database.CurrentNodeID());
return alarms.Rows.Cast()
.Select(CreateAlarm)
.ToList();
}
finally
{
if ((object)database != null)
database.Dispose();
}
}
// Creates an alarm using a data row queried from the database
private Alarm CreateAlarm(DataRow row)
{
object associatedMeasurementId = row.Field