//******************************************************************************************************// AlarmAdapter.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.// 12/13/2012 - Starlynn Danyelle Gilliam// Modified Header.// 02/02/2021 - C. Lackner// Added Settings for Alarm Retention////****************************************************************************************************** using System;using System.Collections.Generic;using System.Collections.ObjectModel;using System.ComponentModel;using System.Data;using System.Linq;using System.Text;using System.Threading;using DataQualityMonitoring.Services;using GSF;using GSF.Collections;using GSF.Data;using GSF.Diagnostics;using GSF.Threading;using GSF.TimeSeries;using GSF.TimeSeries.Adapters;using GSF.TimeSeries.Statistics; namespace DataQualityMonitoring{ /// <summary> /// Action adapter that generates alarm measurements based on alarm definitions from the database. /// </summary> [Description("Alarm: Generates alarm events for alarms defined in the database")] public class AlarmAdapter : FacileActionAdapterBase { #region [ Members ] // Nested Types private class SignalAlarms { public string Name; public List<Alarm> Alarms; public AlarmStatistics Statistics; } private class StateChange { public Guid SignalID; public int? OldStateID; public int? NewStateID; public DateTime Timestamp; public double Value; } // Constants private const int UpToDate = 0; private const int Modified = 1; private const int DefaultAlarmRetention = 24; // Fields private readonly object m_alarmLock; private readonly DoubleBufferedQueue<IMeasurement> m_measurementQueue; private readonly MixedSynchronizedOperation m_processMeasurementsOperation; private DataSet m_alarmDataSet; private int m_dataSourceState; private Dictionary<Guid, SignalAlarms> m_alarmLookup; private AlarmService m_alarmService; private long m_eventCount; private readonly LongSynchronizedOperation m_alarmLogOperation; private readonly DoubleBufferedQueue<StateChange> m_stateChanges; private bool m_useAlarmLog; private int m_bulkInsertLimit; private int m_logProcessingDelay; private bool m_supportsTemporalProcessing; private bool m_disposed; private int m_alarmRetention; #endregion #region [ Constructors ] /// <summary> /// Creates a new instance of the <see cref="AlarmAdapter"/> class. /// </summary> public AlarmAdapter() { m_alarmLock = new object(); m_alarmLookup = new Dictionary<Guid, SignalAlarms>(); m_measurementQueue = new DoubleBufferedQueue<IMeasurement>(); m_processMeasurementsOperation = new MixedSynchronizedOperation(ProcessMeasurements, ex => OnProcessException(MessageLevel.Warning, ex)); m_alarmLogOperation = new LongSynchronizedOperation(LogStateChanges, ex => OnProcessException(MessageLevel.Warning, ex)); m_stateChanges = new DoubleBufferedQueue<StateChange>(); m_alarmLogOperation.IsBackground = true; Default = this; } #endregion #region [ Properties ] /// <summary> /// Gets or sets <see cref="DataSet"/> based data source available to this <see cref="AdapterBase"/>. /// </summary> public override DataSet DataSource { get { return base.DataSource; } set { base.DataSource = value; if (Interlocked.CompareExchange(ref m_dataSourceState, Modified, UpToDate) == UpToDate && Initialized) { m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long; m_processMeasurementsOperation.RunOnceAsync(); } } } /// <summary> /// Gets the flag indicating if this adapter supports temporal processing. /// </summary> [ConnectionStringParameter, Description("Define the flag indicating if this adapter supports temporal processing."), DefaultValue(false)] public override bool SupportsTemporalProcessing => m_supportsTemporalProcessing; /// <summary> /// Gets or sets the flag indicating whether the alarm adapter should /// use the alarm log to track recently modified alarm states. /// </summary> [ConnectionStringParameter, Description("Define the flag indicating whether to use the alarm log to track recently modified alarm states."), DefaultValue(false)] public bool UseAlarmLog { get { return m_useAlarmLog; } set { m_useAlarmLog = value; } } /// <summary> /// Gets or sets the maximum number of alarm state changes to pack into one alarm log insert query. /// </summary> [ConnectionStringParameter, Description("Define the maximum number of alarm state changes to pack into one alarm log insert query."), DefaultValue(300)] public int BulkInsertLimit { get { return m_bulkInsertLimit; } set { m_bulkInsertLimit = value; } } /// <summary> /// Gets or sets the amount of time, in seconds, to wait for alarm log state changes between bulk inserts. /// </summary> [ConnectionStringParameter, Description("Define the amount of time, in seconds, to wait for alarm log state changes between bulk inserts."), DefaultValue(1.0D)] public double LogProcessingDelay { get { return m_logProcessingDelay / 1000.0D; } set { m_logProcessingDelay = (int)(value * 1000.0D); } } /// <summary> /// Gets or sets the amount of time, in hours, an Alarm change will be kept n the database. /// </summary> [ConnectionStringParameter, Description("Define the amount of time, in hour, the alarms will be kept in the AlarmTable."), DefaultValue(false)] public int AlarmRetention { get { return m_alarmRetention; } set { m_alarmRetention = value; } } /// <summary> /// Returns the detailed status of the data input source. /// </summary> public override string Status { get { StringBuilder statusBuilder = new StringBuilder(base.Status); statusBuilder.Append(m_alarmService.Status); return statusBuilder.ToString(); } } #endregion #region [ Methods ] /// <summary> /// Initializes the <see cref="AlarmAdapter"/>. /// </summary> public override void Initialize() { Dictionary<string, string> settings; string setting; double logProcessingDelay; // Run base class initialization base.Initialize(); settings = Settings; // Load optional parameters if (settings.TryGetValue("supportsTemporalProcessing", out setting)) m_supportsTemporalProcessing = setting.ParseBoolean(); else m_supportsTemporalProcessing = false; if (settings.TryGetValue("useAlarmLog", out setting)) m_useAlarmLog = setting.ParseBoolean(); else m_useAlarmLog = false; if (!settings.TryGetValue("bulkInsertLimit", out setting) || !int.TryParse(setting, out m_bulkInsertLimit)) m_bulkInsertLimit = 300; if (!settings.TryGetValue("alarmRetention", out setting) || !int.TryParse(setting, out m_alarmRetention)) m_alarmRetention = DefaultAlarmRetention; if (settings.TryGetValue("logProcessingDelay", out setting) && double.TryParse(setting, out logProcessingDelay)) m_logProcessingDelay = (int)(logProcessingDelay * 1000.0D); else m_logProcessingDelay = 1000; try { // Set up alarm service m_alarmService = new AlarmService(this); m_alarmService.SettingsCategory = base.Name.Replace("!", "").ToLower() + m_alarmService.SettingsCategory; m_alarmService.ServiceProcessException += AlarmService_ServiceProcessException; m_alarmService.PersistSettings = true; m_alarmService.Initialize(); } catch (Exception ex) { OnProcessException(MessageLevel.Error, new InvalidOperationException($"Unable to initialize alarm service due to exception: {ex.Message}", ex)); } // Run the process measurements operation to ensure that the alarm configuration is up to date if (Interlocked.CompareExchange(ref m_dataSourceState, Modified, Modified) == Modified) { m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long; m_processMeasurementsOperation.RunOnceAsync(); } } /// <summary> /// Starts the <see cref="AlarmAdapter"/>, or restarts it if it is already running. /// </summary> public override void Start() { base.Start(); Interlocked.Exchange(ref m_eventCount, 0L); } /// <summary> /// Queues a collection of measurements for processing. /// </summary> /// <param name="measurements">Measurements to queue for processing.</param> public override void QueueMeasurementsForProcessing(IEnumerable<IMeasurement> measurements) { m_measurementQueue.Enqueue(measurements); m_processMeasurementsOperation.RunOnceAsync(); } /// <summary> /// Gets a short one-line status of this <see cref="AdapterBase"/>. /// </summary> /// <param name="maxLength">Maximum number of available characters for display.</param> /// <returns>A short one-line summary of the current status of this <see cref="AdapterBase"/>.</returns> public override string GetShortStatus(int maxLength) { return $"{Interlocked.Read(ref m_eventCount)} events processed since last start".CenterText(maxLength); } /// <summary> /// Gets a collection containing all the raised alarms in the system. /// </summary> /// <returns>A collection containing all the raised alarms.</returns> [AdapterCommand("Gets a collection containing all the raised alarms in the system.", "Administrator", "Editor", "Viewer")] public ICollection<Alarm> GetRaisedAlarms() { lock (m_alarmLock) { return m_alarmLookup .SelectMany(kvp => kvp.Value.Alarms) .Where(alarm => alarm.State == AlarmState.Raised) .Select(alarm => alarm.Clone()) .ToList(); } } /// <summary> /// Gets a collection containing raised alarms with the highest severity for each signal in the system. /// </summary> /// <returns>A collection containing all the highest severity raised alarms.</returns> [AdapterCommand("Gets a collection containing raised alarms with the highest severity for each signal in the system.", "Administrator", "Editor", "Viewer")] public ICollection<Alarm> GetHighestSeverityAlarms() { lock (m_alarmLock) { return m_alarmLookup .Select(kvp => kvp.Value.Alarms.FirstOrDefault(alarm => alarm.State == AlarmState.Raised)) .Where(alarm => (object)alarm != null) .Select(alarm => alarm.Clone()) .ToList(); } } /// <summary> /// Gets Status of an Alarm Based on PointID. /// </summary> /// <param name="PointID"> PointID of the point the alarm is for. </param> /// <returns>A List of <see cref="Alarm"/> object including the state for a specific PointID.</returns> public ICollection<Alarm> GetAlarmStatus(Guid PointID) { lock (m_alarmLock) { if (m_alarmLookup.ContainsKey(PointID)) return m_alarmLookup .Where(kvp => kvp.Key == PointID).Select(kvp => kvp.Value.Alarms) .Where(alarm => (object)alarm != null).First() .Select(alarm => alarm.Clone()) .ToList(); else return new List<Alarm>(); } } /// <summary> /// Releases the unmanaged resources used by the <see cref="AlarmAdapter"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { if (m_alarmService != null) { m_alarmService.ServiceProcessException -= AlarmService_ServiceProcessException; m_alarmService.Dispose(); } lock (m_alarmLock) { foreach (SignalAlarms signalAlarms in m_alarmLookup.Values) StatisticsEngine.Unregister(signalAlarms.Statistics); } } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } // Creates an alarm using data defined in the database. private Alarm CreateAlarm(DataRow row) { object associatedMeasurementId = row.Field<object>("AssociatedMeasurementID"); return new Alarm((AlarmOperation)row.ConvertField<int>("Operation")) { ID = row.ConvertField<int>("ID"), TagName = row.Field<object>("TagName").ToString(), SignalID = Guid.Parse(row.Field<object>("SignalID").ToString()), AssociatedMeasurementID = ((object)associatedMeasurementId != null) ? Guid.Parse(associatedMeasurementId.ToString()) : (Guid?)null, Description = row.Field<object>("Description").ToNonNullString(), Severity = row.ConvertField<int>("Severity").GetEnumValueOrDefault<AlarmSeverity>(AlarmSeverity.None), SetPoint = row.ConvertNullableField<double>("SetPoint"), Tolerance = row.ConvertNullableField<double>("Tolerance"), Delay = row.ConvertNullableField<double>("Delay"), Hysteresis = row.ConvertNullableField<double>("Hysteresis") }; } // Dequeues measurements from the measurement queue and processes them. private void ProcessMeasurements() { IList<IMeasurement> measurements; int dataSourceState; // Get the current state of the data source dataSourceState = Interlocked.CompareExchange(ref m_dataSourceState, Modified, Modified); // If the data source has been modified, elevate the synchronized operation // to a dedicated thread and ensure that it runs at least one more time if (dataSourceState == Modified) { if (m_processMeasurementsOperation.CurrentExecutionMode != AsynchronousExecutionMode.Long) { m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long; m_processMeasurementsOperation.RunOnceAsync(); return; } Interlocked.Exchange(ref m_dataSourceState, UpToDate); UpdateAlarmDefinitions(); } // Attempt to dequeue measurements from the measurement queue and, // if there are measurements left in the queue, ensure that the // process measurements operation runs again if (m_measurementQueue.TryDequeue(out measurements) > 0) m_processMeasurementsOperation.RunOnceAsync(); // If items were successfully dequeued, process them; // otherwise, switch to short asynchronous execution mode so that // we can keep attempting to dequeue in an asynchronous loop if (measurements.Count > 0) ProcessMeasurements(measurements); else m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Short; // If there are no pending operations, switch back to the thread // pool so that the next operation will start up quicker if (!m_processMeasurementsOperation.IsPending) m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Short; } // Updates alarm definitions when the data source has been updated. private void UpdateAlarmDefinitions() { DateTime now; DataSet alarmDataSet; Dictionary<int, Alarm> definedAlarmsLookup; Dictionary<Guid, SignalAlarms> newAlarmLookup; //List<IMeasurement> alarmEvents; // Get the current time in case we need // to generate alarm events for cleared alarms now = DateTime.UtcNow; Lines 1–500 of 852. Load more
//****************************************************************************************************** // AlarmAdapter.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. // 12/13/2012 - Starlynn Danyelle Gilliam // Modified Header. // 02/02/2021 - C. Lackner // Added Settings for Alarm Retention // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; using System.Linq; using System.Text; using System.Threading; using DataQualityMonitoring.Services; using GSF; using GSF.Collections; using GSF.Data; using GSF.Diagnostics; using GSF.Threading; using GSF.TimeSeries; using GSF.TimeSeries.Adapters; using GSF.TimeSeries.Statistics; namespace DataQualityMonitoring { /// <summary> /// Action adapter that generates alarm measurements based on alarm definitions from the database. /// </summary> [Description("Alarm: Generates alarm events for alarms defined in the database")] public class AlarmAdapter : FacileActionAdapterBase { #region [ Members ] // Nested Types private class SignalAlarms { public string Name; public List<Alarm> Alarms; public AlarmStatistics Statistics; } private class StateChange { public Guid SignalID; public int? OldStateID; public int? NewStateID; public DateTime Timestamp; public double Value; } // Constants private const int UpToDate = 0; private const int Modified = 1; private const int DefaultAlarmRetention = 24; // Fields private readonly object m_alarmLock; private readonly DoubleBufferedQueue<IMeasurement> m_measurementQueue; private readonly MixedSynchronizedOperation m_processMeasurementsOperation; private DataSet m_alarmDataSet; private int m_dataSourceState; private Dictionary<Guid, SignalAlarms> m_alarmLookup; private AlarmService m_alarmService; private long m_eventCount; private readonly LongSynchronizedOperation m_alarmLogOperation; private readonly DoubleBufferedQueue<StateChange> m_stateChanges; private bool m_useAlarmLog; private int m_bulkInsertLimit; private int m_logProcessingDelay; private bool m_supportsTemporalProcessing; private bool m_disposed; private int m_alarmRetention; #endregion #region [ Constructors ] /// <summary> /// Creates a new instance of the <see cref="AlarmAdapter"/> class. /// </summary> public AlarmAdapter() { m_alarmLock = new object(); m_alarmLookup = new Dictionary<Guid, SignalAlarms>(); m_measurementQueue = new DoubleBufferedQueue<IMeasurement>(); m_processMeasurementsOperation = new MixedSynchronizedOperation(ProcessMeasurements, ex => OnProcessException(MessageLevel.Warning, ex)); m_alarmLogOperation = new LongSynchronizedOperation(LogStateChanges, ex => OnProcessException(MessageLevel.Warning, ex)); m_stateChanges = new DoubleBufferedQueue<StateChange>(); m_alarmLogOperation.IsBackground = true; Default = this; } #endregion #region [ Properties ] /// <summary> /// Gets or sets <see cref="DataSet"/> based data source available to this <see cref="AdapterBase"/>. /// </summary> public override DataSet DataSource { get { return base.DataSource; } set { base.DataSource = value; if (Interlocked.CompareExchange(ref m_dataSourceState, Modified, UpToDate) == UpToDate && Initialized) { m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long; m_processMeasurementsOperation.RunOnceAsync(); } } } /// <summary> /// Gets the flag indicating if this adapter supports temporal processing. /// </summary> [ConnectionStringParameter, Description("Define the flag indicating if this adapter supports temporal processing."), DefaultValue(false)] public override bool SupportsTemporalProcessing => m_supportsTemporalProcessing; /// <summary> /// Gets or sets the flag indicating whether the alarm adapter should /// use the alarm log to track recently modified alarm states. /// </summary> [ConnectionStringParameter, Description("Define the flag indicating whether to use the alarm log to track recently modified alarm states."), DefaultValue(false)] public bool UseAlarmLog { get { return m_useAlarmLog; } set { m_useAlarmLog = value; } } /// <summary> /// Gets or sets the maximum number of alarm state changes to pack into one alarm log insert query. /// </summary> [ConnectionStringParameter, Description("Define the maximum number of alarm state changes to pack into one alarm log insert query."), DefaultValue(300)] public int BulkInsertLimit { get { return m_bulkInsertLimit; } set { m_bulkInsertLimit = value; } } /// <summary> /// Gets or sets the amount of time, in seconds, to wait for alarm log state changes between bulk inserts. /// </summary> [ConnectionStringParameter, Description("Define the amount of time, in seconds, to wait for alarm log state changes between bulk inserts."), DefaultValue(1.0D)] public double LogProcessingDelay { get { return m_logProcessingDelay / 1000.0D; } set { m_logProcessingDelay = (int)(value * 1000.0D); } } /// <summary> /// Gets or sets the amount of time, in hours, an Alarm change will be kept n the database. /// </summary> [ConnectionStringParameter, Description("Define the amount of time, in hour, the alarms will be kept in the AlarmTable."), DefaultValue(false)] public int AlarmRetention { get { return m_alarmRetention; } set { m_alarmRetention = value; } } /// <summary> /// Returns the detailed status of the data input source. /// </summary> public override string Status { get { StringBuilder statusBuilder = new StringBuilder(base.Status); statusBuilder.Append(m_alarmService.Status); return statusBuilder.ToString(); } } #endregion #region [ Methods ] /// <summary> /// Initializes the <see cref="AlarmAdapter"/>. /// </summary> public override void Initialize() { Dictionary<string, string> settings; string setting; double logProcessingDelay; // Run base class initialization base.Initialize(); settings = Settings; // Load optional parameters if (settings.TryGetValue("supportsTemporalProcessing", out setting)) m_supportsTemporalProcessing = setting.ParseBoolean(); else m_supportsTemporalProcessing = false; if (settings.TryGetValue("useAlarmLog", out setting)) m_useAlarmLog = setting.ParseBoolean(); else m_useAlarmLog = false; if (!settings.TryGetValue("bulkInsertLimit", out setting) || !int.TryParse(setting, out m_bulkInsertLimit)) m_bulkInsertLimit = 300; if (!settings.TryGetValue("alarmRetention", out setting) || !int.TryParse(setting, out m_alarmRetention)) m_alarmRetention = DefaultAlarmRetention; if (settings.TryGetValue("logProcessingDelay", out setting) && double.TryParse(setting, out logProcessingDelay)) m_logProcessingDelay = (int)(logProcessingDelay * 1000.0D); else m_logProcessingDelay = 1000; try { // Set up alarm service m_alarmService = new AlarmService(this); m_alarmService.SettingsCategory = base.Name.Replace("!", "").ToLower() + m_alarmService.SettingsCategory; m_alarmService.ServiceProcessException += AlarmService_ServiceProcessException; m_alarmService.PersistSettings = true; m_alarmService.Initialize(); } catch (Exception ex) { OnProcessException(MessageLevel.Error, new InvalidOperationException($"Unable to initialize alarm service due to exception: {ex.Message}", ex)); } // Run the process measurements operation to ensure that the alarm configuration is up to date if (Interlocked.CompareExchange(ref m_dataSourceState, Modified, Modified) == Modified) { m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long; m_processMeasurementsOperation.RunOnceAsync(); } } /// <summary> /// Starts the <see cref="AlarmAdapter"/>, or restarts it if it is already running. /// </summary> public override void Start() { base.Start(); Interlocked.Exchange(ref m_eventCount, 0L); } /// <summary> /// Queues a collection of measurements for processing. /// </summary> /// <param name="measurements">Measurements to queue for processing.</param> public override void QueueMeasurementsForProcessing(IEnumerable<IMeasurement> measurements) { m_measurementQueue.Enqueue(measurements); m_processMeasurementsOperation.RunOnceAsync(); } /// <summary> /// Gets a short one-line status of this <see cref="AdapterBase"/>. /// </summary> /// <param name="maxLength">Maximum number of available characters for display.</param> /// <returns>A short one-line summary of the current status of this <see cref="AdapterBase"/>.</returns> public override string GetShortStatus(int maxLength) { return $"{Interlocked.Read(ref m_eventCount)} events processed since last start".CenterText(maxLength); } /// <summary> /// Gets a collection containing all the raised alarms in the system. /// </summary> /// <returns>A collection containing all the raised alarms.</returns> [AdapterCommand("Gets a collection containing all the raised alarms in the system.", "Administrator", "Editor", "Viewer")] public ICollection<Alarm> GetRaisedAlarms() { lock (m_alarmLock) { return m_alarmLookup .SelectMany(kvp => kvp.Value.Alarms) .Where(alarm => alarm.State == AlarmState.Raised) .Select(alarm => alarm.Clone()) .ToList(); } } /// <summary> /// Gets a collection containing raised alarms with the highest severity for each signal in the system. /// </summary> /// <returns>A collection containing all the highest severity raised alarms.</returns> [AdapterCommand("Gets a collection containing raised alarms with the highest severity for each signal in the system.", "Administrator", "Editor", "Viewer")] public ICollection<Alarm> GetHighestSeverityAlarms() { lock (m_alarmLock) { return m_alarmLookup .Select(kvp => kvp.Value.Alarms.FirstOrDefault(alarm => alarm.State == AlarmState.Raised)) .Where(alarm => (object)alarm != null) .Select(alarm => alarm.Clone()) .ToList(); } } /// <summary> /// Gets Status of an Alarm Based on PointID. /// </summary> /// <param name="PointID"> PointID of the point the alarm is for. </param> /// <returns>A List of <see cref="Alarm"/> object including the state for a specific PointID.</returns> public ICollection<Alarm> GetAlarmStatus(Guid PointID) { lock (m_alarmLock) { if (m_alarmLookup.ContainsKey(PointID)) return m_alarmLookup .Where(kvp => kvp.Key == PointID).Select(kvp => kvp.Value.Alarms) .Where(alarm => (object)alarm != null).First() .Select(alarm => alarm.Clone()) .ToList(); else return new List<Alarm>(); } } /// <summary> /// Releases the unmanaged resources used by the <see cref="AlarmAdapter"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { if (m_alarmService != null) { m_alarmService.ServiceProcessException -= AlarmService_ServiceProcessException; m_alarmService.Dispose(); } lock (m_alarmLock) { foreach (SignalAlarms signalAlarms in m_alarmLookup.Values) StatisticsEngine.Unregister(signalAlarms.Statistics); } } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } // Creates an alarm using data defined in the database. private Alarm CreateAlarm(DataRow row) { object associatedMeasurementId = row.Field<object>("AssociatedMeasurementID"); return new Alarm((AlarmOperation)row.ConvertField<int>("Operation")) { ID = row.ConvertField<int>("ID"), TagName = row.Field<object>("TagName").ToString(), SignalID = Guid.Parse(row.Field<object>("SignalID").ToString()), AssociatedMeasurementID = ((object)associatedMeasurementId != null) ? Guid.Parse(associatedMeasurementId.ToString()) : (Guid?)null, Description = row.Field<object>("Description").ToNonNullString(), Severity = row.ConvertField<int>("Severity").GetEnumValueOrDefault<AlarmSeverity>(AlarmSeverity.None), SetPoint = row.ConvertNullableField<double>("SetPoint"), Tolerance = row.ConvertNullableField<double>("Tolerance"), Delay = row.ConvertNullableField<double>("Delay"), Hysteresis = row.ConvertNullableField<double>("Hysteresis") }; } // Dequeues measurements from the measurement queue and processes them. private void ProcessMeasurements() { IList<IMeasurement> measurements; int dataSourceState; // Get the current state of the data source dataSourceState = Interlocked.CompareExchange(ref m_dataSourceState, Modified, Modified); // If the data source has been modified, elevate the synchronized operation // to a dedicated thread and ensure that it runs at least one more time if (dataSourceState == Modified) { if (m_processMeasurementsOperation.CurrentExecutionMode != AsynchronousExecutionMode.Long) { m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long; m_processMeasurementsOperation.RunOnceAsync(); return; } Interlocked.Exchange(ref m_dataSourceState, UpToDate); UpdateAlarmDefinitions(); } // Attempt to dequeue measurements from the measurement queue and, // if there are measurements left in the queue, ensure that the // process measurements operation runs again if (m_measurementQueue.TryDequeue(out measurements) > 0) m_processMeasurementsOperation.RunOnceAsync(); // If items were successfully dequeued, process them; // otherwise, switch to short asynchronous execution mode so that // we can keep attempting to dequeue in an asynchronous loop if (measurements.Count > 0) ProcessMeasurements(measurements); else m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Short; // If there are no pending operations, switch back to the thread // pool so that the next operation will start up quicker if (!m_processMeasurementsOperation.IsPending) m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Short; } // Updates alarm definitions when the data source has been updated. private void UpdateAlarmDefinitions() { DateTime now; DataSet alarmDataSet; Dictionary<int, Alarm> definedAlarmsLookup; Dictionary<Guid, SignalAlarms> newAlarmLookup; //List<IMeasurement> alarmEvents; // Get the current time in case we need // to generate alarm events for cleared alarms now = DateTime.UtcNow;