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