668 lines · 26.7 KB · C#
RawDownload
//******************************************************************************************************//  DynamicCalculator.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/06/2012 - Stephen C. Wills//       Generated original version of source code.//  12/13/2012 - Starlynn Danyelle Gilliam//       Modified Header.////****************************************************************************************************** using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Linq;using System.Reflection;using System.Text;using Ciloci.Flee;using GSF;using GSF.Configuration;using GSF.Diagnostics;using GSF.Threading;using GSF.TimeSeries;using GSF.TimeSeries.Adapters; namespace DynamicCalculator{    /// <summary>    /// Represents the source of a timestamp.    /// </summary>    public enum TimestampSource    {        /// <summary>        /// An incoming frame timestamp.        /// </summary>        Frame,         /// <summary>        /// Real-time as defined by the concentration engine.        /// </summary>        RealTime,         /// <summary>        /// The system's local clock.        /// </summary>        LocalClock    }     /// <summary>    /// The DynamicCalculator is an action adapter which takes multiple    /// input measurements and performs a calculation on those measurements    /// to generate its own calculated measurement.    /// </summary>    [Description("Dynamic Calculator: Performs arithmetic operations on multiple input signals")]    public class DynamicCalculator : ActionAdapterBase    {        #region [ Members ]         // Constants         /// <summary>        /// Defines the default value for <see cref="Imports"/> property.        /// </summary>        public const string DefaultImports = "AssemblyName=mscorlib, TypeName=System.Math; AssemblyName=mscorlib, TypeName=System.DateTime";         private const string TimeVariable = "TIME";        private const string UtcTimeVariable = "UTCTIME";        private const string LocalTimeVariable = "LOCALTIME";        private const string SystemNameVariable = "SYSTEMNAME";         private static readonly string[] ReservedVariableNames = { TimeVariable, UtcTimeVariable, LocalTimeVariable, SystemNameVariable };         // Fields        private string m_expressionText;        private string m_variableList;        private string m_imports;        private bool m_supportsTemporalProcessing;         private readonly ImmediateMeasurements m_latestMeasurements;        private Ticks m_latestTimestamp;        private double m_latestValue;         private readonly HashSet<string> m_variableNames;        private readonly Dictionary<MeasurementKey, string> m_keyMapping;        private readonly SortedDictionary<int, string> m_nonAliasedTokens;         private string m_aliasedExpressionText;        private readonly ExpressionContext m_expressionContext;        private IDynamicExpression m_expression;         private readonly DelayedSynchronizedOperation m_timerOperation;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new instance of the <see cref="DynamicCalculator"/>.        /// </summary>        public DynamicCalculator()        {            m_latestMeasurements = new ImmediateMeasurements            {                RealTimeFunction = () => RealTime            };             m_variableNames = new HashSet<string>();            m_keyMapping = new Dictionary<MeasurementKey, string>();            m_nonAliasedTokens = new SortedDictionary<int, string>();            m_expressionContext = new ExpressionContext();             m_timerOperation = new DelayedSynchronizedOperation(ProcessLatestMeasurements, ex => OnProcessException(MessageLevel.Error, ex));        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets output measurements that the action adapter will produce, if any.        /// </summary>        [ConnectionStringParameter]        [Description("Defines primary keys of output measurements the action adapter expects; can be one of a filter expression, measurement key, point tag or Guid.")]        [CustomConfigurationEditor("GSF.TimeSeries.UI.WPF.dll", "GSF.TimeSeries.UI.Editors.MeasurementEditor")]        public override IMeasurement[] OutputMeasurements        {            get => base.OutputMeasurements;            set => base.OutputMeasurements = value;        }         /// <summary>        /// Gets or sets the textual representation of the expression.        /// </summary>        [ConnectionStringParameter]        [Description("Define the arithmetic expression used to perform calculations.")]        public string ExpressionText        {            get => m_expressionText;            set            {                m_expressionText = value;                PerformAliasReplacement();            }        }         /// <summary>        /// Gets or sets the list of variables used in the expression.        /// </summary>        [ConnectionStringParameter]        [Description("Define the unique list of variables used in the expression. Any defined aliased variables must be unique per defined dynamic calculator or e-mail notifier instance. Note that \"TIME\" is reserved, this returns the current timestamp for the calculation in ticks.")]        public string VariableList        {            get => m_variableList;            set            {                if (m_variableList == value)                    return;                 // Store the variable list for get operations                m_variableList = value;                 // Empty the collection of variable names                m_variableNames.Clear();                m_keyMapping.Clear();                m_nonAliasedTokens.Clear();                 // If the value is null, do not attempt to process it                if (value is null)                    return;                 // Build the collection of variable names with the new value                foreach (string token in value.Split(';'))                    AddVariable(token);                 // Perform alias replacement on tokens that were not explicitly aliased                PerformAliasReplacement();                 // Build the key list which will define the input measurements for this adapter                string keyList = m_keyMapping.Keys.Select(key => key.ToString())                            .Aggregate((runningKeyList, nextKey) => runningKeyList + ";" + nextKey);                 // Set the input measurements for this adapter                InputMeasurementKeys = AdapterBase.ParseInputMeasurementKeys(DataSource, true, keyList);            }        }         /// <summary>        /// Gets or sets the list of types which define        /// methods to be imported into the expression parser.        /// </summary>        [ConnectionStringParameter]        [Description("Define the list of types which define methods to be imported into the expression parser.")]        [DefaultValue(DefaultImports)]        public string Imports        {            get => m_imports;            set            {                foreach (string typeDef in value.Split(';'))                {                    try                    {                        Dictionary<string, string> parsedTypeDef = typeDef.ParseKeyValuePairs(',');                        string assemblyName = parsedTypeDef["assemblyName"];                        string typeName = parsedTypeDef["typeName"];                        Assembly assembly = Assembly.Load(new AssemblyName(assemblyName));                        Type type = assembly.GetType(typeName);                         m_expressionContext.Imports.AddType(type);                    }                    catch (Exception ex)                    {                        string message = $"Unable to load type from assembly: {typeDef}";                        OnProcessException(MessageLevel.Error, new ArgumentException(message, ex));                    }                }                 m_imports = value;            }        }         /// <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 to use the latest        /// received values to fill in values missing from the current frame.        /// </summary>        [ConnectionStringParameter]        [Description("Define the flag indicating if this adapter should use latest values for missing measurements.")]        [DefaultValue(true)]        public bool UseLatestValues { get; set; }         /// <summary>        /// Gets or sets the flag indicating whether to skip processing of an output with a value of NaN.        /// </summary>        [ConnectionStringParameter]        [Description("Define the flag indicating whether to skip processing of an output with a value of NaN.")]        [DefaultValue(false)]        public bool SkipNaNOutput { get; set; }         /// <summary>        /// Gets or sets the value used when no other value can be determined for a variable.        /// </summary>        [ConnectionStringParameter]        [Description("Defines the value used when no other value can be determined for a variable.")]        [DefaultValue(double.NaN)]        public double SentinelValue { get; set; }         /// <summary>        /// Gets or sets the interval at which the adapter should calculate values.        /// </summary>        /// <remarks>        /// Set to zero to disable the timer and calculate values upon receipt of input data.        /// </remarks>        [ConnectionStringParameter]        [Description("Define the interval, in seconds, at which the adapter should calculate values. Zero value executes calculations at received data rate.")]        [DefaultValue(0)]        public double CalculationInterval        {            get => m_timerOperation.Delay / 1000.0D;            set => m_timerOperation.Delay = (int)(value * 1000.0D);        }         /// <summary>        /// Gets or sets the source of the timestamps of the calculated values.        /// </summary>        [ConnectionStringParameter]        [Description("Define the source of the timestamps of the calculated values.")]        [DefaultValue(TimestampSource.Frame)]        public TimestampSource TimestampSource { get; set; }         /// <summary>        /// Gets or sets primary keys of input measurements the dynamic calculator expects.        /// </summary>        [EditorBrowsable(EditorBrowsableState.Never)]        public override MeasurementKey[] InputMeasurementKeys        {            get => base.InputMeasurementKeys;            set            {                base.InputMeasurementKeys = value;                m_latestMeasurements.ClearMeasurementCache();            }        }         /// <summary>        /// Gets flag that determines if the implementation of the <see cref="DynamicCalculator"/> requires an output measurement.        /// </summary>        protected virtual bool ExpectsOutputMeasurement => true;         /// <summary>        /// Gets defined variable collection with current values.        /// </summary>        protected IDictionary<string, object> Variables => m_expressionContext.Variables;                /// <summary>        /// Returns the detailed status of the data input source.        /// </summary>        public override string Status        {            get            {                StringBuilder status = new StringBuilder();                 status.Append(base.Status);                status.AppendLine();                status.AppendLine($"           Expression Text: {ExpressionText}");                status.AppendLine($"             Variable List: {VariableList}");                status.AppendLine($"         Use Latest Values: {UseLatestValues}");                status.AppendLine($"      Calculation Interval: {(CalculationInterval == 0.0D ? "At received data rate" : $"{CalculationInterval:N3} seconds")}");                status.AppendLine($"          Timestamp Source: {TimestampSource}");                status.AppendLine($"           Skip NaN Values: {SkipNaNOutput}");                status.AppendLine($"            Sentinel Value: {SentinelValue}");                 if (ExpectsOutputMeasurement)                    status.AppendLine($"     Last Calculated Value: {m_latestValue}");                 List<string> imports = new List<string>();                 if (!string.IsNullOrWhiteSpace(Imports))                {                    foreach (string typeDef in Imports.Split(';'))                    {                        Dictionary<string, string> parsedTypeDef = typeDef.ParseKeyValuePairs(',');                         if (parsedTypeDef.TryGetValue("typeName", out string typeName))                            imports.Add(typeName);                    }                }                 status.AppendLine();                status.AppendLine($"Imported .NET Types ({imports.Count:N0} total):");                 foreach (string import in imports)                    status.AppendLine($"        {import.TruncateRight(70)}");                 return status.ToString();            }        }         private new Ticks RealTime        {            get            {                switch (TimestampSource)                {                    case TimestampSource.RealTime:                        return base.RealTime;                     case TimestampSource.LocalClock:                        return DateTime.UtcNow;                     case TimestampSource.Frame:                        return m_latestTimestamp;                     default:                        return m_latestTimestamp;                }            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Initializes <see cref="DynamicCalculator"/>.        /// </summary>        public override void Initialize()        {            const string ErrorMessage = "{0} is missing from Settings - Example: expressionText=x+y; variableList={{x = PPA:1; y = PPA:2}}";             Dictionary<string, string> settings = Settings;            base.Initialize();             if (ExpectsOutputMeasurement && OutputMeasurements?.Length != 1)                throw new ArgumentException($"Exactly one output measurement must be defined. Amount defined: {OutputMeasurements?.Length ?? 0}");             // Load required parameters            if (!settings.TryGetValue(nameof(VariableList), out string setting))                throw new ArgumentException(string.Format(ErrorMessage, nameof(VariableList)));             VariableList = setting;             if (!settings.TryGetValue(nameof(ExpressionText), out setting))                throw new ArgumentException(string.Format(ErrorMessage, nameof(ExpressionText)));             ExpressionText = setting;             // Load optional parameters            Imports = settings.TryGetValue(nameof(Imports), out setting) ? setting : DefaultImports;            m_supportsTemporalProcessing = settings.TryGetValue(nameof(SupportsTemporalProcessing), out setting) && setting.ParseBoolean();             SkipNaNOutput = settings.TryGetValue(nameof(SkipNaNOutput), out setting) && setting.ParseBoolean();            TimestampSource = settings.TryGetValue(nameof(TimestampSource), out setting) && Enum.TryParse(setting, out TimestampSource timestampSource) ? timestampSource : TimestampSource.Frame;            CalculationInterval = settings.TryGetValue(nameof(CalculationInterval), out setting) ? double.Parse(setting) : 0;            UseLatestValues = !settings.TryGetValue(nameof(UseLatestValues), out setting) || setting.ParseBoolean();            SentinelValue = settings.TryGetValue(nameof(SentinelValue), out setting) ? double.Parse(setting) : double.NaN;             m_latestMeasurements.LagTime = LagTime;            m_latestMeasurements.LeadTime = LeadTime;        }         /// <summary>        /// Starts the <see cref="DynamicCalculator"/> or restarts it if it is already running.        /// </summary>        [AdapterCommand("Starts the action adapter or restarts it if it is already running.", "Administrator", "Editor")]        public override void Start()        {            base.Start();             if (UseLatestValues && m_timerOperation.Delay > 0)                m_timerOperation.RunOnceAsync();        }         /// <summary>        /// Publish <see cref="IFrame"/> of time-aligned collection of <see cref="IMeasurement"/> values that arrived within the        /// concentrator's defined <see cref="ConcentratorBase.LagTime"/>.        /// </summary>        /// <param name="frame"><see cref="IFrame"/> of measurements with the same timestamp that arrived within <see cref="ConcentratorBase.LagTime"/> that are ready for processing.</param>        /// <param name="index">Index of <see cref="IFrame"/> within a second ranging from zero to <c><see cref="ConcentratorBase.FramesPerSecond"/> - 1</c>.</param>        protected override void PublishFrame(IFrame frame, int index)        {            m_latestTimestamp = frame.Timestamp;             if (UseLatestValues)                ProcessMeasurements(frame.Measurements.Values);            else                Calculate(frame.Measurements);        }         /// <summary>        /// Handler for the values calculated by the <see cref="DynamicCalculator"/>.        /// </summary>        /// <param name="value">The value calculated by the <see cref="DynamicCalculator"/>.</param>        protected virtual void HandleCalculatedValue(object value)        {            // Evaluate the expression and generate the measurement            GenerateCalculatedMeasurement(RealTime, value as IConvertible);        }         private void ProcessMeasurements(IEnumerable<IMeasurement> measurements)        {            foreach (IMeasurement measurement in measurements)            {                m_latestMeasurements.UpdateMeasurementValue(measurement);                 if (measurement.Timestamp > m_latestTimestamp && measurement.Timestamp.UtcTimeIsValid(LagTime, LeadTime))                    m_latestTimestamp = measurement.Timestamp;            }             if (m_timerOperation.Delay <= 0)                ProcessLatestMeasurements();        }         private void ProcessLatestMeasurements()        {            if (!Enabled)                return;             IDictionary<MeasurementKey, IMeasurement> measurementLookup = m_latestMeasurements                .Cast<IMeasurement>()                .ToDictionary(measurement => measurement.Key);             Calculate(measurementLookup);             if (m_timerOperation.Delay > 0)                m_timerOperation.RunOnceAsync();        }         private void Calculate(IDictionary<MeasurementKey, IMeasurement> measurements)        {            m_expressionContext.Variables.Clear();             // Set the values of variables in the expression            foreach (MeasurementKey key in m_keyMapping.Keys)            {                string name = m_keyMapping[key]; 

Lines 1–500 of 668.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t