//****************************************************************************************************** // ImmediateMeasurements.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: // ---------------------------------------------------------------------------------------------------- // 09/02/2010 - J. Ritchie Carroll // Generated original version of source code. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Linq; namespace GSF.TimeSeries { /// /// Represents the absolute latest measurement values received by a implementation. /// public class ImmediateMeasurements : IEnumerable, IDisposable { #region [ Members ] // Fields private ConcentratorBase m_parent; private ConcurrentDictionary m_measurements; private ConcurrentDictionary> m_taggedMeasurements; private Func m_realTimeFunction; private double m_lagTime; // Allowed past time deviation tolerance, in seconds private double m_leadTime; // Allowed future time deviation tolerance, in seconds private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public ImmediateMeasurements() { m_measurements = new ConcurrentDictionary(); m_taggedMeasurements = new ConcurrentDictionary>(); m_realTimeFunction = () => DateTime.UtcNow.Ticks; } internal ImmediateMeasurements(ConcentratorBase parent) : this() { m_parent = parent; if (m_parent != null) { m_parent.LagTimeUpdated += OnLagTimeUpdated; m_parent.LeadTimeUpdated += OnLeadTimeUpdated; m_realTimeFunction = () => m_parent.RealTime; } } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~ImmediateMeasurements() { Dispose(false); } #endregion #region [ Properties ] /// We retrieve adjusted measurement values within time tolerance of concentrator real-time. /// A representing the measurement ID. /// A representing the adjusted measurement value. public double this[MeasurementKey id] { get { return Measurement(id).GetAdjustedValue(m_realTimeFunction()); } } /// Returns collection of measurement ID's. public ICollection MeasurementIDs { get { return m_measurements.Keys; } } /// Returns ID collection for measurement tags. public ICollection Tags { get { return m_taggedMeasurements.Keys; } } /// Returns the minimum value of all measurements. /// This is only useful if all measurements represent the same type of measurement. public double Minimum { get { double minValue = double.MaxValue; double measurement; foreach (MeasurementKey id in m_measurements.Keys) { measurement = this[id]; if (!double.IsNaN(measurement)) { if (measurement < minValue) { minValue = measurement; } } } return minValue; } } /// Returns the maximum value of all measurements. /// This is only useful if all measurements represent the same type of measurement. public double Maximum { get { double maxValue = double.MinValue; double measurement; foreach (MeasurementKey id in m_measurements.Keys) { measurement = this[id]; if (!double.IsNaN(measurement)) { if (measurement > maxValue) { maxValue = measurement; } } } return maxValue; } } /// /// Gets or sets function to return real-time. /// public Func RealTimeFunction { get { return m_realTimeFunction; } set { m_realTimeFunction = value ?? (() => DateTime.UtcNow.Ticks); } } /// /// Gets or sets the allowed past time deviation tolerance, in seconds (can be sub-second). /// /// /// Defines the time sensitivity to past measurement timestamps. /// The number of seconds allowed before assuming a measurement timestamp is too old. /// /// LagTime must be greater than zero, but it can be less than one. public double LagTime { get { return m_lagTime; } set { if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value), "LagTime must be greater than zero, but it can be less than one"); m_lagTime = value; foreach (MeasurementKey id in m_measurements.Keys) { Measurement(id).LagTime = m_lagTime; } } } /// /// Gets or sets the allowed future time deviation tolerance, in seconds (can be sub-second). /// /// /// Defines the time sensitivity to future measurement timestamps. /// The number of seconds allowed before assuming a measurement timestamp is too advanced. /// /// LeadTime must be greater than zero, but it can be less than one. public double LeadTime { get { return m_leadTime; } set { if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value), "LeadTime must be greater than zero, but it can be less than one"); m_leadTime = value; foreach (MeasurementKey id in m_measurements.Keys) { Measurement(id).LeadTime = m_leadTime; } } } #endregion #region [ Methods ] /// /// Releases all the resources used by the object. /// 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 (m_parent != null) { m_parent.LagTimeUpdated -= OnLagTimeUpdated; m_parent.LeadTimeUpdated -= OnLagTimeUpdated; } m_parent = null; if (m_measurements != null) m_measurements.Clear(); m_measurements = null; if (m_taggedMeasurements != null) m_taggedMeasurements.Clear(); m_taggedMeasurements = null; } } finally { m_disposed = true; // Prevent duplicate dispose. } } } /// Returns measurement list of specified tag, if it exists. /// A that indicates the tag to use. /// A collection of measurement keys. public ReadOnlyCollection TaggedMeasurementKeys(string tag) { return new ReadOnlyCollection(m_taggedMeasurements[tag]); } /// Store new measurement. /// New measurement value to update. /// Value is only stored if it is newer than the cached value. public void UpdateMeasurementValue(IMeasurement newMeasurement) { TemporalMeasurement measurement = Measurement(newMeasurement); // Set new value updating state flags if value was updated... if (measurement.SetValue(newMeasurement.Timestamp, newMeasurement.Value)) { measurement.Metadata = newMeasurement.Metadata; measurement.StateFlags = newMeasurement.StateFlags; } } /// Retrieves the specified immediate temporal measurement, creating it if needed. /// based signal ID of measurement. /// A object. public TemporalMeasurement Measurement(MeasurementKey id) { return m_measurements.GetOrAdd(id, key => new TemporalMeasurement(m_lagTime, m_leadTime) { Metadata = key.Metadata, }); } /// Retrieves the specified immediate temporal measurement, creating it if needed. /// Source value. /// A object. public TemporalMeasurement Measurement(IMeasurement measurement) { TemporalMeasurement temporalMeasurement = m_measurements.GetOrAdd(measurement.Key, key => new TemporalMeasurement(m_lagTime, m_leadTime) { Metadata = key.Metadata.ChangeAdderMultiplier(measurement.Adder, measurement.Multiplier) }); return temporalMeasurement; } /// /// Clears the existing measurement cache. /// public void ClearMeasurementCache() { m_measurements.Clear(); } /// Defines tagged measurements from a data table. /// Expects based tag field to be aliased as "Tag" and based measurement ID field to be aliased as "ID". /// A to use for defining the tagged measurements. public void DefineTaggedMeasurements(DataTable taggedMeasurements) { foreach (DataRow row in taggedMeasurements.Rows) { Guid id = row["ID"].ToNonNullString(Guid.Empty.ToString()).ConvertToType(); MeasurementKey key = MeasurementKey.LookUpBySignalID(id); if (key != MeasurementKey.Undefined) { AddTaggedMeasurement(row["Tag"].ToNonNullString("_tag_"), key); } } } /// Associates a new measurement ID with a tag, creating the new tag if needed. /// Allows you to define "grouped" points so you can aggregate certain measurements. /// A to represent the key. /// A ID to associate with the tag. public void AddTaggedMeasurement(string tag, MeasurementKey id) { // Get tag's measurement list List measurements = m_taggedMeasurements.GetOrAdd(tag, new List()); if (measurements.BinarySearch(id) < 0) { measurements.Add(id); measurements.Sort(); } } /// Calculates an average of all measurements. /// This is only useful if all measurements represent the same type of measurement. /// An value to get the count of values averaged. /// A value representing the average of the measurements. public double CalculateAverage(ref int count) { double measurement; double total = 0.0D; foreach (MeasurementKey id in m_measurements.Keys) { measurement = this[id]; if (!double.IsNaN(measurement)) { total += measurement; count++; } } return total / count; } /// Calculates an average of all measurements associated with the specified tag. /// An value to get the count of values averaged. /// The type of measurements to average. /// A value representing the average of the tags. public double CalculateTagAverage(string tag, ref int count) { double measurement; double total = 0.0D; foreach (MeasurementKey id in m_taggedMeasurements[tag]) { measurement = this[id]; if (!double.IsNaN(measurement)) { total += measurement; count++; } } return total / count; } /// Returns the minimum value of all measurements associated with the specified tag. /// A value representing the tag minimum. /// The tag group to evaluate. public double TagMinimum(string tag) { double minValue = double.MaxValue; double measurement; foreach (MeasurementKey id in m_taggedMeasurements[tag]) { measurement = this[id]; if (!double.IsNaN(measurement)) { if (measurement < minValue) { minValue = measurement; } } } return minValue; } /// Returns the maximum value of all measurements associated with the specified tag. /// A value representing the tag maximum. /// The tag group to evaluate. public double TagMaximum(string tag) { double maxValue = double.MinValue; double measurement; foreach (MeasurementKey id in m_taggedMeasurements[tag]) { measurement = this[id]; if (!double.IsNaN(measurement)) { if (measurement > maxValue) { maxValue = measurement; } } } return maxValue; } /// /// Updates the tracked temporal measurements lag time. /// /// New lag time. protected void OnLagTimeUpdated(double lagTime) { this.LagTime = lagTime; } /// /// Updates the tracked temporal measurements lead time. /// /// New lead time. protected void OnLeadTimeUpdated(double leadTime) { this.LeadTime = leadTime; } IEnumerator IEnumerable.GetEnumerator() { return m_measurements.Values.ToList().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return m_measurements.Values.ToList().GetEnumerator(); } #endregion } }