336 lines · 13.2 KB · C#
RawDownload
//******************************************************************************************************//  BestValueSelector.cs - Gbtc////  Copyright © 2017, 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://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://  ----------------------------------------------------------------------------------------------------//  11/06/2017 - Stephen C. Wills//       Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.ComponentModel;using System.Linq;using System.Text;using GSF;using GSF.TimeSeries;using GSF.TimeSeries.Adapters; namespace DataQualityMonitoring{    /// <summary>    /// Produces a new signal by selecting the best values    /// from a collection of concentrated data points.    /// </summary>    [Description("Best Value: Produces a new signal by selecting only the best values from a collection of input signals.")]    public class BestValueSelector : ActionAdapterBase    {        #region [ Members ]         // Constants         /// <summary>        /// Defines the default value for the <see cref="PublishBadValues"/> property.        /// </summary>        public const bool DefaultPublishBadValues = false;         /// <summary>        /// Defines the default value for the <see cref="DefaultBadFlags"/> property.        /// </summary>        public const MeasurementStateFlags DefaultBadFlags =            MeasurementStateFlags.AlarmHigh |            MeasurementStateFlags.AlarmLow |            MeasurementStateFlags.BadData |            MeasurementStateFlags.CalculationError |            MeasurementStateFlags.ComparisonAlarm |            MeasurementStateFlags.FlatlineAlarm |            MeasurementStateFlags.MeasurementError |            MeasurementStateFlags.OverRangeError |            MeasurementStateFlags.ReceivedAsBad |            MeasurementStateFlags.SuspectData |            MeasurementStateFlags.UnderRangeError;         /// <summary>        /// Defines the default value for the <see cref="HandleZeroAsBad"/> property.        /// </summary>        public const bool DefaultHandleZeroAsBad = false;         /// <summary>        /// Defines the default value for the <see cref="HandleNaNAsBad"/> property.        /// </summary>        public const bool DefaultHandleNaNAsBad = true;         /// <summary>        /// Defines the default value for the <see cref="SupportsTemporalProcessing"/> property.        /// </summary>        public const bool DefaultSupportsTemporalProcessing = false;         // Fields        private bool m_publishBadValues;        private MeasurementStateFlags m_badFlags;        private bool m_handleZeroAsBad;        private bool m_handleNaNAsBad;        private bool m_supportsTemporalProcessing;        private MeasurementKey m_lastSelectedMeasurement;         #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            {                return base.OutputMeasurements;            }            set            {                base.OutputMeasurements = value;            }        }         /// <summary>        /// Gets or sets the value that determines whether the adapter should        /// publish measurements if all of its inputs are found to be bad.        /// </summary>        [ConnectionStringParameter,        DefaultValue(DefaultPublishBadValues),        Description("Define the flag that indicates whether this adapter should publish measurements if all inputs are bad.")]        public bool PublishBadValues        {            get            {                return m_publishBadValues;            }            set            {                m_publishBadValues = value;            }        }         /// <summary>        /// Gets or sets the flags that should be considered bad by this adapter.        /// </summary>        [ConnectionStringParameter,        Description("Define the flags that are considered bad by this adapter."),        DefaultValue(DefaultBadFlags)]        public MeasurementStateFlags BadFlags        {            get            {                return m_badFlags;            }            set            {                m_badFlags = value;            }        }         /// <summary>        /// Gets the flag indicating if this adapter considers zero to be a bad value.        /// </summary>        [ConnectionStringParameter,        Description("Define the flag indicating if this adapter considers zero to be a bad value."),        DefaultValue(DefaultHandleZeroAsBad)]        public bool HandleZeroAsBad        {            get            {                return m_handleZeroAsBad;            }            set            {                m_handleZeroAsBad = value;            }        }         /// <summary>        /// Gets the flag indicating if this adapter considers not-a-number to be a bad value.        /// </summary>        [ConnectionStringParameter,        Description("Define the flag indicating if this adapter considers not-a-number to be a bad value."),        DefaultValue(DefaultHandleNaNAsBad)]        public bool HandleNaNAsBad        {            get            {                return m_handleNaNAsBad;            }            set            {                m_handleNaNAsBad = 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(DefaultSupportsTemporalProcessing)]        public override bool SupportsTemporalProcessing => m_supportsTemporalProcessing;         /// <summary>        /// Returns the detailed status of the action adapter.        /// </summary>        public override string Status        {            get            {                StringBuilder statusBuilder = new StringBuilder(base.Status);                 IEnumerable<MeasurementStateFlags> badFlags = Enum.GetValues(typeof(MeasurementStateFlags))                    .Cast<MeasurementStateFlags>()                    .Where(flag => flag != MeasurementStateFlags.Normal &&  m_badFlags.HasFlag(flag));                 string badFlagsText = string.Join("," + Environment.NewLine + "                              ", badFlags);                 statusBuilder.AppendLine();                statusBuilder.AppendLine($"          Publish bad values: {(m_publishBadValues ? "Yes" : "No")}");                statusBuilder.AppendLine($"                   Bad Flags: {badFlagsText}");                statusBuilder.AppendLine($"          Handle Zero As Bad: {(m_handleZeroAsBad ? "Yes" : "No")}");                statusBuilder.AppendLine($"           Handle NaN As Bad: {(m_handleNaNAsBad ? "Yes" : "No")}");                statusBuilder.AppendLine($"Supports Temporal Processing: {(m_supportsTemporalProcessing ? "Yes" : "No")}");                statusBuilder.AppendLine($"   Last Selected Measurement: {m_lastSelectedMeasurement}");                 return statusBuilder.ToString();            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Initializes the <see cref="BestValueSelector"/>.        /// </summary>        public override void Initialize()        {            base.Initialize();             if (OutputMeasurements?.Length != 1 && OutputMeasurements?.Length != 2)                throw new ArgumentException($"Exactly one or two output measurement must be defined. Amount defined: {OutputMeasurements?.Length ?? 0}");             Dictionary<string, string> settings = Settings;            string setting;             if (settings.TryGetValue(nameof(PublishBadValues), out setting))                m_publishBadValues = setting.ParseBoolean();            else                m_publishBadValues = DefaultHandleZeroAsBad;             if (!settings.TryGetValue(nameof(BadFlags), out setting) || !Enum.TryParse(setting, out m_badFlags))                m_badFlags = DefaultBadFlags;             if (settings.TryGetValue(nameof(HandleZeroAsBad), out setting))                m_handleZeroAsBad = setting.ParseBoolean();            else                m_handleZeroAsBad = DefaultHandleZeroAsBad;             if (settings.TryGetValue(nameof(HandleNaNAsBad), out setting))                m_handleNaNAsBad = setting.ParseBoolean();            else                m_handleNaNAsBad = DefaultHandleNaNAsBad;             if (settings.TryGetValue(nameof(SupportsTemporalProcessing), out setting))                m_supportsTemporalProcessing = setting.ParseBoolean();            else                m_supportsTemporalProcessing = DefaultSupportsTemporalProcessing;        }         /// <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)        {            IMeasurement bestMeasurement = GetBestMeasurement(frame);             if ((object)bestMeasurement == null)                return;             IMeasurement[] newMeasurements = OutputMeasurements                .Select(measurement => Measurement.Clone(measurement))                .ToArray();             newMeasurements[0].Timestamp = frame.Timestamp;            newMeasurements[0].StateFlags = bestMeasurement.StateFlags;            newMeasurements[0].Value = bestMeasurement.Value;             if (newMeasurements.Length > 1)            {                newMeasurements[1].Timestamp = frame.Timestamp;                newMeasurements[1].StateFlags = bestMeasurement.StateFlags;                newMeasurements[1].Value = Array.IndexOf(InputMeasurementKeys, bestMeasurement.Key) + 1;            }             OnNewMeasurements(newMeasurements);             m_lastSelectedMeasurement = bestMeasurement.Key;        }         private IMeasurement GetBestMeasurement(IFrame frame)        {            IMeasurement measurement;             if (m_publishBadValues)            {                return InputMeasurementKeys                    .Select(key => frame.Measurements.TryGetValue(key, out measurement) ? measurement : null)                    .Where(m => (object)m != null)                    .OrderBy(m => (m_handleNaNAsBad && double.IsNaN(m.Value)) ? 1 : 0)                    .ThenBy(m => (m_handleZeroAsBad && m.Value == 0.0D) ? 1 : 0)                    .ThenBy(m => CountFlags(m.StateFlags & m_badFlags))                    .FirstOrDefault();            }             return InputMeasurementKeys                .Select(key => frame.Measurements.TryGetValue(key, out measurement) ? measurement : null)                .Where(m => (object)m != null)                .Where(m => !m_handleNaNAsBad || !double.IsNaN(m.Value))                .Where(m => !m_handleZeroAsBad || m.Value != 0.0D)                .Where(m => (m.StateFlags & m_badFlags) == MeasurementStateFlags.Normal)                .FirstOrDefault();        }         private int CountFlags(MeasurementStateFlags flags)        {            if (flags == MeasurementStateFlags.Normal)                return 0;             uint count = (uint)flags;             count = count - ((count >> 1) & 0x55555555u);            count = ((count >> 2) & 0x33333333u) + (count & 0x33333333u);            count = ((count >> 4) + count) & 0x0F0F0F0Fu;            count = ((count >> 8) + count) & 0x00FF00FFu;            count = ((count >> 16) + count) & 0x0000FFFFu;             return (int)count;        }         #endregion    }}

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