//******************************************************************************************************
// ActionAdapterBase.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.
// 02/08/2011 - J. Ritchie Carroll
// Added invokable ExamineQueueState method to analyze real-time concentrator frame queue state.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading;
using GSF.Annotations;
using GSF.Diagnostics;
namespace GSF.TimeSeries.Adapters
{
///
/// Represents the base class for action adapters.
///
///
/// This base class acts as a measurement concentrator which time aligns all incoming measurements for proper processing.
/// Derived classes are expected to override to handle time aligned measurements
/// and call for any new measurements that may get created.
///
[SuppressMessage("SonarQube.Methods", "S1206", Justification = "Class life-cycle is controlled through library.")]
public abstract class ActionAdapterBase : ConcentratorBase, IActionAdapter
{
#region [ Members ]
// Events
///
/// Provides status messages to consumer.
///
///
/// is new status message.
///
public event EventHandler> StatusMessage;
///
/// Event is raised when are updated.
///
public event EventHandler InputMeasurementKeysUpdated;
///
/// Event is raised when are updated.
///
public event EventHandler OutputMeasurementsUpdated;
///
/// Event is raised when adapter is aware of a configuration change.
///
public event EventHandler ConfigurationChanged;
///
/// Provides new measurements from action adapter.
///
///
/// is a collection of new measurements for host to process.
///
public event EventHandler>> NewMeasurements;
// Fields
private string m_name;
private uint m_id;
private string m_connectionString;
private Dictionary m_settings;
private DataSet m_dataSource;
private int m_initializationTimeout;
private bool m_autoStart;
private bool m_respectInputDemands;
private bool m_respectOutputDemands;
private MeasurementKey[] m_inputMeasurementKeys;
private IMeasurement[] m_outputMeasurements;
private MeasurementKey[] m_requestedInputMeasurementKeys;
private MeasurementKey[] m_requestedOutputMeasurementKeys;
private List m_inputSourceIDs;
private List m_outputSourceIDs;
private int m_minimumMeasurementsToUse;
private DateTime m_startTimeConstraint;
private DateTime m_stopTimeConstraint;
private int m_hashCode;
private bool m_initialized;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Creates a new .
///
protected ActionAdapterBase()
{
m_name = this.GetType().Name;
Log.InitialStackMessages = Log.InitialStackMessages.Union("AdapterName", m_name);
m_settings = new Dictionary();
m_startTimeConstraint = DateTime.MinValue;
m_stopTimeConstraint = DateTime.MaxValue;
GenHashCode();
// Set incoming measurements to none by default
m_inputMeasurementKeys = new MeasurementKey[0];
// For most implementations millisecond resolution will be sufficient
base.TimeResolution = Ticks.PerMillisecond;
}
#endregion
#region [ Properties ]
///
/// Gets name of the action adapter.
///
public virtual string Name
{
get
{
return m_name;
}
set
{
m_name = value;
Log.InitialStackMessages = Log.InitialStackMessages.Union("AdapterName", m_name);
GenHashCode();
}
}
///
/// Gets or sets numeric ID associated with this action adapter.
///
public virtual uint ID
{
get
{
return m_id;
}
set
{
m_id = value;
GenHashCode();
}
}
///
/// Gets or sets key/value pair connection information specific to action adapter.
///
///
///
/// Example connection string using manually defined measurements:
/// framesPerSecond=30; lagTime=1.0; leadTime=0.5; minimumMeasurementsToUse=-1;
/// useLocalClockAsRealTime=true; allowSortsByArrival=false;
/// inputMeasurementKeys={P1:1245;P1:1247;P2:1335};
/// outputMeasurements={P3:1345,60.0,1.0;P3:1346;P3:1347}
/// When defined manually, elements in key:
/// * inputMeasurementKeys are defined as "ArchiveSource:PointID"
/// * outputMeasurements are defined as "ArchiveSource:PointID,Adder,Multiplier", the adder and multiplier are optional
/// defaulting to 0.0 and 1.0 respectively.
///
///
///
/// Example connection string using measurements defined in a table:
/// framesPerSecond=30; lagTime=1.0; leadTime=0.5; minimumMeasurementsToUse=-1;
/// useLocalClockAsRealTime=true; allowSortsByArrival=false;
/// inputMeasurementKeys={FILTER ActiveMeasurements WHERE Company='GSF' AND SignalType='FREQ' ORDER BY ID};
/// outputMeasurements={FILTER ActiveMeasurements WHERE SignalType IN ('IPHA','VPHA') AND Phase='+'}
///
/// Basic filtering syntax is as follows:
///
/// {FILTER <TableName> WHERE <Expression> [ORDER BY <SortField>]}
///
/// Source tables are expected to have at least the following fields:
///
///
/// Name
/// Type
/// Description.
///
/// -
/// ID
/// NVARCHAR
/// Measurement key formatted as: ArchiveSource:PointID.
///
/// -
/// PointTag
/// NVARCHAR
/// Point tag of measurement.
///
/// -
/// Adder
/// FLOAT
/// Adder to apply to value, if any (default to 0.0).
///
/// -
/// Multiplier
/// FLOAT
/// Multiplier to apply to value, if any (default to 1.0).
///
///
///
///
/// Note that framesPerSecond, lagTime and leadTime are required parameters, all other parameters are optional.
///
///
public virtual string ConnectionString
{
get
{
return m_connectionString;
}
set
{
m_connectionString = value;
// Pre-parse settings upon connection string assignment
if (string.IsNullOrWhiteSpace(m_connectionString))
m_settings = new Dictionary();
else
m_settings = m_connectionString.ParseKeyValuePairs();
}
}
///
/// Gets connection info for adapter, if any.
///
///
/// For example, this could return IP or host name of source connection.
///
public virtual string ConnectionInfo => null;
///
/// Gets or sets based data source available to this .
///
public virtual DataSet DataSource
{
get
{
return m_dataSource;
}
set
{
m_dataSource = value;
}
}
///
/// Gets or sets maximum time system will wait during for initialization.
///
///
/// Set to to wait indefinitely.
///
public virtual int InitializationTimeout
{
get
{
return m_initializationTimeout;
}
set
{
m_initializationTimeout = value;
}
}
///
/// Gets or sets flag indicating if adapter should automatically start or otherwise connect on demand.
///
public virtual bool AutoStart
{
get
{
return m_autoStart;
}
set
{
m_autoStart = value;
}
}
///
/// Gets or sets flag indicating if action adapter should respect auto-start requests based on input demands.
///
///
/// Action adapters are in the curious position of being able to both consume and produce points, as such the user needs to be able to control how their
/// adapter will behave concerning routing demands when the adapter is setup to connect on demand. In the case of respecting auto-start input demands,
/// as an example, this would be false for an action adapter that calculated measurement, but true for an action adapter used to archive inputs.
///
public virtual bool RespectInputDemands
{
get
{
return m_respectInputDemands;
}
set
{
m_respectInputDemands = value;
}
}
///
/// Gets or sets flag indicating if action adapter should respect auto-start requests based on output demands.
///
///
/// Action adapters are in the curious position of being able to both consume and produce points, as such the user needs to be able to control how their
/// adapter will behave concerning routing demands when the adapter is setup to connect on demand. In the case of respecting auto-start output demands,
/// as an example, this would be true for an action adapter that calculated measurement, but false for an action adapter used to archive inputs.
///
public virtual bool RespectOutputDemands
{
get
{
return m_respectOutputDemands;
}
set
{
m_respectOutputDemands = value;
}
}
///
/// Gets or sets the number of frames per second.
///
///
/// Valid frame rates for a are greater than 0 frames per second.
///
[ConnectionStringParameter,
Description("Defines the number of frames per second expected by the adapter.")]
public new int FramesPerSecond
{
get
{
return base.FramesPerSecond;
}
set
{
base.FramesPerSecond = value;
}
}
///
/// 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.
/// This becomes the amount of delay introduced by the concentrator to allow time for data to flow into the system.
///
/// LagTime must be greater than zero, but it can be less than one.
[ConnectionStringParameter,
Description("Defines the allowed past time deviation tolerance, in seconds (can be sub-second).")]
public new double LagTime
{
get
{
return base.LagTime;
}
set
{
base.LagTime = value;
}
}
///
/// 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.
/// This becomes the tolerated +/- accuracy of the local clock to real-time.
///
/// LeadTime must be greater than zero, but it can be less than one.
[ConnectionStringParameter,
Description("Defines the allowed future time deviation tolerance, in seconds (can be sub-second).")]
public new double LeadTime
{
get
{
return base.LeadTime;
}
set
{
base.LeadTime = value;
}
}
///
/// Gets or sets primary keys of input measurements the action adapter expects.
///
///
/// If your adapter needs to receive all measurements, you must explicitly set InputMeasurementKeys to null.
///
[ConnectionStringParameter,
DefaultValue(null),
Description("Defines primary keys of input 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 virtual MeasurementKey[] InputMeasurementKeys
{
get
{
return m_inputMeasurementKeys;
}
set
{
if (m_inputMeasurementKeys == value)
return;
if ((object)m_inputMeasurementKeys != null && (object)value != null)
{
if (new HashSet(m_inputMeasurementKeys).SetEquals(value))
return;
}
m_inputMeasurementKeys = value;
// The input measurements typically define the "expected measurements" of the action adapter, so
// we use the number of these items to define the expected measurement count
ExpectedMeasurements = (object)m_inputMeasurementKeys != null ? m_inputMeasurementKeys.Length : 0;
OnInputMeasurementKeysUpdated();
}
}
///
/// Gets or sets output measurements that the action adapter will produce, if any.
///
[ConnectionStringParameter,
DefaultValue(null),
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 virtual IMeasurement[] OutputMeasurements
{
get
{
return m_outputMeasurements;
}
set
{
m_outputMeasurements = value;
OnOutputMeasurementsUpdated();
}
}
///
/// Gets or sets values used to filter input measurement keys.
///
///
/// This allows an adapter to associate itself with entire collections of measurements based on the source of the measurement keys.
/// Set to null apply no filter.
///
public virtual string[] InputSourceIDs
{
get
{
if ((object)m_inputSourceIDs == null)
return null;
return m_inputSourceIDs.ToArray();
}
set
{
if ((object)value == null)
{
m_inputSourceIDs = null;
}
else
{
m_inputSourceIDs = new List(value);
m_inputSourceIDs.Sort();
}
// Filter measurements to list of specified source IDs
AdapterBase.LoadInputSourceIDs(this);
}
}
///
/// Gets or sets values used to filter output measurements.
///
///
/// This allows an adapter to associate itself with entire collections of measurements based on the source of the measurement keys.
/// Set to null apply no filter.
///
public virtual string[] OutputSourceIDs
{
get
{
if ((object)m_outputSourceIDs == null)
return null;
return m_outputSourceIDs.ToArray();
}
set
{
if ((object)value == null)
{
m_outputSourceIDs = null;
}
else
{
m_outputSourceIDs = new List(value);
m_outputSourceIDs.Sort();
}
// Filter measurements to list of specified source IDs
AdapterBase.LoadOutputSourceIDs(this);
}
}
///
/// Gets or sets input measurement keys that are requested by other adapters based on what adapter says it can provide.
///
public virtual MeasurementKey[] RequestedInputMeasurementKeys
{
get
{
return m_requestedInputMeasurementKeys;
}
set
{
m_requestedInputMeasurementKeys = value;
}
}
///
/// Gets or sets output measurement keys that are requested by other adapters based on what adapter says it can provide.
///
public virtual MeasurementKey[] RequestedOutputMeasurementKeys
{
get
{
return m_requestedOutputMeasurementKeys;
}
set
{
m_requestedOutputMeasurementKeys = value;
}
}
///
/// Gets the flag indicating if this adapter supports temporal processing.
///
public abstract bool SupportsTemporalProcessing
{
get;
}
///
/// Gets the start time temporal processing constraint defined by call to .
///
///
/// This value will be when start time constraint is not set - meaning the adapter
/// is processing data in real-time.
///
public virtual DateTime StartTimeConstraint => m_startTimeConstraint;
///
/// Gets the stop time temporal processing constraint defined by call to .
///
///
/// This value will be when stop time constraint is not set - meaning the adapter
/// is processing data in real-time.
///
public virtual DateTime StopTimeConstraint => m_stopTimeConstraint;
///
/// Gets or sets minimum number of input measurements required for adapter. Set to -1 to require all.
///
public virtual int MinimumMeasurementsToUse
{
get
{
// Default to all measurements if minimum is not specified
if (m_minimumMeasurementsToUse < 1 && (object)InputMeasurementKeys != null)
return InputMeasurementKeys.Length;
return m_minimumMeasurementsToUse;
}
set
{
m_minimumMeasurementsToUse = value;
}
}
///
/// Gets or sets flag indicating if the action adapter has been initialized successfully.
///
public virtual bool Initialized
{
get
{
return m_initialized;
}
set
{
m_initialized = value;
}
}
///
/// Gets settings parsed when was assigned.
///
public Dictionary Settings => m_settings;
///
/// Returns the detailed status of the action adapter.
///
///
/// Derived classes should extend status with implementation specific information.
///
public override string Status
{
get
{
const int MaxMeasurementsToShow = 10;
StringBuilder status = new StringBuilder();
DataSet dataSource = this.DataSource;
status.AppendFormat(" Data source defined: {0}", (object)dataSource != null);
status.AppendLine();
if ((object)dataSource != null)
{
status.AppendFormat(" Referenced data source: {0}, {1:N0} tables", dataSource.DataSetName, dataSource.Tables.Count);
status.AppendLine();
}
status.AppendFormat(" Initialization timeout: {0}", InitializationTimeout < 0 ? "Infinite" : InitializationTimeout + " milliseconds");
status.AppendLine();
status.AppendFormat(" Adapter initialized: {0}", Initialized);
status.AppendLine();
status.AppendFormat(" Operational state: {0}", Enabled ? "Running" : "Stopped");
status.AppendLine();
status.AppendFormat(" Connect on demand: {0}", !AutoStart);
status.AppendLine();
status.AppendFormat(" Respecting input demands: {0}", RespectInputDemands);
status.AppendLine();
status.AppendFormat(" Respecting output demands: {0}", RespectOutputDemands);
status.AppendLine();
status.AppendFormat(" Processed measurements: {0}", ProcessedMeasurements);
status.AppendLine();
status.AppendFormat(" Total adapter run time: {0}", RunTime.ToString(3));
status.AppendLine();
status.AppendFormat(" Temporal processing: {0}", SupportsTemporalProcessing ? "Supported" : "Unsupported");
status.AppendLine();
if (SupportsTemporalProcessing)
{
status.AppendFormat(" Start time constraint: {0}", StartTimeConstraint == DateTime.MinValue ? "Unspecified" : StartTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss.fff"));
status.AppendLine();
status.AppendFormat(" Stop time constraint: {0}", StopTimeConstraint == DateTime.MaxValue ? "Unspecified" : StopTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss.fff"));
status.AppendLine();
status.AppendFormat(" Processing interval: {0}", ProcessingInterval < 0 ? "Default" : (ProcessingInterval == 0 ? "As fast as possible" : ProcessingInterval.ToString("N0") + " milliseconds"));
status.AppendLine();
}
status.AppendFormat(" Adapter ID: {0}", ID);
status.AppendLine();
Dictionary keyValuePairs = Settings;
char[] keyChars;
string value;
status.AppendFormat(" Connection string: {0:N0} key/value pairs", keyValuePairs.Count);
// 1 2 3 4 5 6 7
// 123456789012345678901234567890123456789012345678901234567890123456789012345678
// Key = Value
// 1 2 3 4 5
// 12345678901234567890123456789012345678901234567890
status.AppendLine();
status.AppendLine();
foreach (KeyValuePair item in keyValuePairs)
{
keyChars = item.Key.Trim().ToCharArray();
keyChars[0] = char.ToUpper(keyChars[0]);
value = item.Value.Trim();
if (value.Length > 50)
value = value.TruncateRight(47) + "...";
status.AppendFormat("{0} = {1}", new string(keyChars).TruncateRight(25).PadLeft(25), value.PadRight(50));
status.AppendLine();
}
status.AppendLine();
if ((object)OutputMeasurements != null && OutputMeasurements.Length > OutputMeasurements.Count(m => m.Key == MeasurementKey.Undefined))
{
status.AppendFormat(" Output measurements: {0:N0} defined measurements", OutputMeasurements.Length);
status.AppendLine();
status.AppendLine();
for (int i = 0; i < Math.Min(OutputMeasurements.Length, MaxMeasurementsToShow); i++)
{
status.Append(OutputMeasurements[i].ToString().TruncateRight(40).PadLeft(40));
status.Append(" ");
status.AppendLine(OutputMeasurements[i].ID.ToString());
}
if (OutputMeasurements.Length > MaxMeasurementsToShow)
status.AppendLine("...".PadLeft(26));
status.AppendLine();
}
if ((object)InputMeasurementKeys != null && InputMeasurementKeys.Length > InputMeasurementKeys.Count(k => k == MeasurementKey.Undefined))
{
status.AppendFormat(" Input measurements: {0:N0} defined measurements", InputMeasurementKeys.Length);
status.AppendLine();
status.AppendLine();
for (int i = 0; i < Math.Min(InputMeasurementKeys.Length, MaxMeasurementsToShow); i++)
{
status.AppendLine(InputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50));
}
if (InputMeasurementKeys.Length > MaxMeasurementsToShow)
status.AppendLine("...".CenterText(50));
status.AppendLine();
}
if ((object)RequestedInputMeasurementKeys != null && RequestedInputMeasurementKeys.Length > 0)
{
status.AppendFormat(" Requested input keys: {0:N0} defined measurements", RequestedInputMeasurementKeys.Length);
status.AppendLine();
status.AppendLine();
for (int i = 0; i < Math.Min(RequestedInputMeasurementKeys.Length, MaxMeasurementsToShow); i++)
{
status.AppendLine(RequestedInputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50));
}
if (RequestedInputMeasurementKeys.Length > MaxMeasurementsToShow)
status.AppendLine("...".CenterText(50));
status.AppendLine();
}
if ((object)RequestedOutputMeasurementKeys != null && RequestedOutputMeasurementKeys.Length > 0)
{
status.AppendFormat(" Requested output keys: {0:N0} defined measurements", RequestedOutputMeasurementKeys.Length);
status.AppendLine();
status.AppendLine();
for (int i = 0; i < Math.Min(RequestedOutputMeasurementKeys.Length, MaxMeasurementsToShow); i++)
{
status.AppendLine(RequestedOutputMeasurementKeys[i].ToString().TruncateRight(25).CenterText(50));
}
if (RequestedOutputMeasurementKeys.Length > MaxMeasurementsToShow)
status.AppendLine("...".CenterText(50));
status.AppendLine();
}
status.AppendFormat(" Minimum measurements used: {0:N0}", MinimumMeasurementsToUse);
status.AppendLine();
status.Append(base.Status);
return status.ToString();
}
}
#endregion
#region [ Methods ]
///
/// 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 override void Dispose(bool disposing)
{
if (!m_disposed)
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
///
/// Initializes .
///
public virtual void Initialize()
{
Initialized = false;
Dictionary settings = Settings;
const string errorMessage = "{0} is missing from Settings - Example: framesPerSecond=30; lagTime=3; leadTime=1";
string setting;
// Load required parameters
if (!settings.TryGetValue("framesPerSecond", out setting))
throw new ArgumentException(string.Format(errorMessage, "framesPerSecond"));
base.FramesPerSecond = int.Parse(setting);
if (!settings.TryGetValue("lagTime", out setting))
throw new ArgumentException(string.Format(errorMessage, "lagTime"));
base.LagTime = double.Parse(setting);
if (!settings.TryGetValue("leadTime", out setting))
throw new ArgumentException(string.Format(errorMessage, "leadTime"));
base.LeadTime = double.Parse(setting);
// Load optional parameters
if (settings.TryGetValue("usePrecisionTimer", out setting))
UsePrecisionTimer = setting.ParseBoolean();
if (settings.TryGetValue("useLocalClockAsRealTime", out setting))
UseLocalClockAsRealTime = setting.ParseBoolean();
if (settings.TryGetValue("ignoreBadTimestamps", out setting))
IgnoreBadTimestamps = setting.ParseBoolean();
if (settings.TryGetValue("allowSortsByArrival", out setting))
AllowSortsByArrival = setting.ParseBoolean();
if (settings.TryGetValue("inputMeasurementKeys", out setting))
InputMeasurementKeys = AdapterBase.ParseInputMeasurementKeys(DataSource, true, setting);
else
InputMeasurementKeys = new MeasurementKey[0];
if (settings.TryGetValue("outputMeasurements", out setting))
OutputMeasurements = AdapterBase.ParseOutputMeasurements(DataSource, true, setting);
if (settings.TryGetValue("minimumMeasurementsToUse", out setting))
MinimumMeasurementsToUse = int.Parse(setting);
if (settings.TryGetValue("timeResolution", out setting))
TimeResolution = long.Parse(setting);
if (settings.TryGetValue("roundToNearestTimestamp", out setting))
RoundToNearestTimestamp = setting.ParseBoolean();
if (settings.TryGetValue("allowPreemptivePublishing", out setting))
AllowPreemptivePublishing = setting.ParseBoolean();
if (settings.TryGetValue("performTimestampReasonabilityCheck", out setting))
PerformTimestampReasonabilityCheck = setting.ParseBoolean();
if (settings.TryGetValue("processByReceivedTimestamp", out setting))
ProcessByReceivedTimestamp = setting.ParseBoolean();
if (settings.TryGetValue("maximumPublicationTimeout", out setting))
MaximumPublicationTimeout = int.Parse(setting);
if (settings.TryGetValue("downsamplingMethod", out setting))
{
DownsamplingMethod method;
if (Enum.TryParse(setting, true, out method))
{
DownsamplingMethod = method;
}
else
{
OnStatusMessage(MessageLevel.Info, $"No down-sampling method labeled \"{setting}\" exists, \"LastReceived\" method was selected.", flags: MessageFlags.UsageIssue);
DownsamplingMethod = DownsamplingMethod.LastReceived;
}
}
if (settings.TryGetValue("inputSourceIDs", out setting))
InputSourceIDs = setting.Split(',');
if (settings.TryGetValue("outputSourceIDs", out setting))
OutputSourceIDs = setting.Split(',');
if (settings.TryGetValue("connectOnDemand", out setting))
AutoStart = !setting.ParseBoolean();
else
AutoStart = true;
if (settings.TryGetValue("respectInputDemands", out setting))
RespectInputDemands = setting.ParseBoolean();
else
RespectInputDemands = false;
if (settings.TryGetValue("respectOutputDemands", out setting))
RespectOutputDemands = setting.ParseBoolean();
else
RespectOutputDemands = true;
string startTime, stopTime, parameters;
bool startTimeDefined = settings.TryGetValue("startTimeConstraint", out startTime);
bool stopTimeDefined = settings.TryGetValue("stopTimeConstraint", out stopTime);
if (startTimeDefined || stopTimeDefined)
{
settings.TryGetValue("timeConstraintParameters", out parameters);
SetTemporalConstraint(startTime, stopTime, parameters);
}
int processingInterval;
if (settings.TryGetValue("processingInterval", out setting) && !string.IsNullOrWhiteSpace(setting) && int.TryParse(setting, out processingInterval))
ProcessingInterval = processingInterval;
}
///
/// Gets a flag that indicates whether the object has been disposed.
///
public bool IsDisposed => m_disposed;
///
/// Starts the or restarts it if it is already running.
///
[AdapterCommand("Starts the action adapter or restarts it if it is already running.", "Administrator", "Editor")]
public override void Start()
{
// Make sure we are stopped (e.g., disconnected) before attempting to start (e.g., connect)
if (Enabled)
Stop();
base.Start();
}
///
/// Stops the .
///
[AdapterCommand("Stops the action adapter.", "Administrator", "Editor")]
public override void Stop()
{
base.Stop();
}
///
/// Examines the concentrator frame queue state of the .
///
[AdapterCommand("Examines concentration frame queue state.", "Administrator", "Editor", "Viewer")]
public void ExamineQueueState()
{
OnStatusMessage(MessageLevel.Info, QueueState);
}
///
/// Resets the statistics of the .
///
[AdapterCommand("Resets the statistics of the action adapter.", "Administrator", "Editor")]
public override void ResetStatistics()
{
base.ResetStatistics();
if (Enabled)
OnStatusMessage(MessageLevel.Info, "Action adapter concentration statistics have been reset.");
}
///
/// Manually sets the initialized state of the .
///
/// Desired initialized state.
[AdapterCommand("Manually sets the initialized state of the action adapter.", "Administrator", "Editor")]
public virtual void SetInitializedState(bool initialized)
{
this.Initialized = initialized;
}
///
/// Gets a short one-line status of this .
///
/// Maximum number of available characters for display.
/// A short one-line summary of the current status of this .
public virtual string GetShortStatus(int maxLength)
{
int inputCount = 0, outputCount = 0;
if ((object)InputMeasurementKeys != null)
inputCount = InputMeasurementKeys.Length;
if ((object)OutputMeasurements != null)
outputCount = OutputMeasurements.Length;
return $"Total input measurements: {inputCount}, total output measurements: {outputCount}".PadLeft(maxLength);
}
///
/// Queues a collection of measurements for processing. Measurements are automatically filtered to the defined .
///
/// Collection of measurements to queue for processing.
///
/// Measurements are filtered against the defined .
///
public virtual void QueueMeasurementsForProcessing(IEnumerable measurements)
{
if (m_disposed)
return;
SortMeasurements(measurements);
}
///
/// Defines a temporal processing constraint for the adapter.
///
/// Defines a relative or exact start time for the temporal constraint.
/// Defines a relative or exact stop time for the temporal constraint.
/// Defines any temporal parameters related to the constraint.
///
///
/// This method defines a temporal processing constraint for an adapter, i.e., the start and stop time over which an
/// adapter will process data. Actual implementation of the constraint will be adapter specific. Implementations
/// should be able to dynamically handle multiple calls to this function with new constraints. Passing in null
/// for the and should cancel the temporal constraint and
/// return the adapter to standard / real-time operation.
///
///
/// The and parameters can be specified in one of the
/// following formats:
///
///
/// Time Format
/// Format Description
///
/// -
/// 12-30-2000 23:59:59.033
/// Absolute date and time.
///
/// -
/// *
/// Evaluates to .
///
/// -
/// *-20s
/// Evaluates to 20 seconds before .
///
/// -
/// *-10m
/// Evaluates to 10 minutes before .
///
/// -
/// *-1h
/// Evaluates to 1 hour before .
///
/// -
/// *-1d
/// Evaluates to 1 day before .
///
///
///
///
[AdapterCommand("Defines a temporal processing constraint for the adapter.", "Administrator", "Editor", "Viewer")]
public virtual void SetTemporalConstraint(string startTime, string stopTime, string constraintParameters)
{
if (!string.IsNullOrWhiteSpace(startTime))
m_startTimeConstraint = AdapterBase.ParseTimeTag(startTime);
else
m_startTimeConstraint = DateTime.MinValue;
if (!string.IsNullOrWhiteSpace(stopTime))
m_stopTimeConstraint = AdapterBase.ParseTimeTag(stopTime);
else
m_stopTimeConstraint = DateTime.MaxValue;
// When processing historical data, timestamps should not be evaluated for reasonability
if (this.TemporalConstraintIsDefined())
{
PerformTimestampReasonabilityCheck = false;
LeadTime = double.MaxValue;
}
}
///
/// Serves as a hash function for the current .
///
/// A hash code for the current .
// ReSharper disable once NonReadonlyMemberInGetHashCode
public override int GetHashCode() => m_hashCode;
///
/// Attempts to retrieve the minimum needed number of measurements from the frame (as specified by MinimumMeasurementsToUse)
///
/// Source frame for the measurements
/// Return array of measurements
/// True if minimum needed number of measurements were returned in measurement array
///
///
/// Remember this function will *only* return the minimum needed number of measurements, no more. If you want to use
/// all available measurements in your adapter you should just use Frame.Measurements.Values directly.
///
///
/// Note that the measurements array parameter will be created if the reference is null, otherwise if caller creates
/// array it must be sized to MinimumMeasurementsToUse
///
///
protected virtual bool TryGetMinimumNeededMeasurements(IFrame frame, ref IMeasurement[] measurements)
{
int index = 0, minNeeded = MinimumMeasurementsToUse;
IDictionary frameMeasurements = frame.Measurements;
MeasurementKey[] measurementKeys = InputMeasurementKeys;
if ((object)measurements == null || measurements.Length < minNeeded)
measurements = new IMeasurement[minNeeded];
if ((object)measurementKeys == null)
{
// No input measurements are defined, just get first set of measurements in this frame
foreach (IMeasurement measurement in frameMeasurements.Values)
{
measurements[index++] = measurement;
if (index == minNeeded)
break;
}
}
else
{
// Loop through all input measurements to see if they exist in this frame
IMeasurement measurement;
foreach (MeasurementKey key in measurementKeys)
{
if (frameMeasurements.TryGetValue(key, out measurement))
{
measurements[index++] = measurement;
if (index == minNeeded)
break;
}
}
}
return index == minNeeded;
}
///
/// Raises the event.
///
protected virtual void OnNewMeasurements(ICollection measurements)
{
try
{
NewMeasurements?.Invoke(this, new EventArgs>(measurements));
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for NewMeasurements event: {ex.Message}", ex), "ConsumerEventException");
}
}
///
/// Raises the event.
///
/// New status message.
[Obsolete("Switch to using overload with MessageLevel parameter - this method may be removed from future builds.", false)]
protected void OnStatusMessage(string status)
{
OnStatusMessage(MessageLevel.Info, status, "Unclassified Status");
}
///
/// Raises the event with a formatted status message.
///
/// Formatted status message.
/// Arguments for .
///
/// This overload combines string.Format and SendStatusMessage for convenience.
///
[StringFormatMethod("formattedStatus"), Obsolete("Switch to using overload with MessageLevel parameter - this method may be removed from future builds.", false)]
protected void OnStatusMessage(string formattedStatus, params object[] args)
{
OnStatusMessage(MessageLevel.Info, string.Format(formattedStatus, args), "Unclassified Status");
}
///
/// Raises the event and sends this data to the .
///
/// The to assign to this message
/// New status message.
/// A fixed string to classify this event; defaults to null.
/// to use, if any; defaults to .
///
/// should be a constant string value associated with what type of message is being
/// generated. In general, there should only be a few dozen distinct event names per class. Exceeding this
/// threshold will cause the EventName to be replaced with a general warning that a usage issue has occurred.
///
protected virtual void OnStatusMessage(MessageLevel level, string status, string eventName = null, MessageFlags flags = MessageFlags.None)
{
try
{
Log.Publish(level, flags, eventName, status);
using (Logger.SuppressLogMessages())
StatusMessage?.Invoke(this, new EventArgs(AdapterBase.GetStatusWithMessageLevelPrefix(status, level)));
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for StatusMessage event: {ex.Message}", ex), "ConsumerEventException");
}
}
///
/// Raises event.
///
protected virtual void OnInputMeasurementKeysUpdated()
{
try
{
InputMeasurementKeysUpdated?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for InputMeasurementKeysUpdated event: {ex.Message}", ex), "ConsumerEventException");
}
}
///
/// Raises event.
///
protected virtual void OnOutputMeasurementsUpdated()
{
try
{
OutputMeasurementsUpdated?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for OutputMeasurementsUpdated event: {ex.Message}", ex), "ConsumerEventException");
}
}
///
/// Raises event.
///
protected virtual void OnConfigurationChanged()
{
try
{
ConfigurationChanged?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
// We protect our code from consumer thrown exceptions
OnProcessException(MessageLevel.Info, new InvalidOperationException($"Exception in consumer handler for ConfigurationChanged event: {ex.Message}", ex), "ConsumerEventException");
}
}
private void GenHashCode()
{
// We cache hash code during construction or after element value change to speed usage
m_hashCode = (Name + ID).GetHashCode();
}
#endregion
}
}