//******************************************************************************************************
// FrameBasedRandomValueInputAdapter.cs - Gbtc
//
// Copyright © 2014, 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:
// ----------------------------------------------------------------------------------------------------
// 04/26/2014 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using GSF;
using GSF.NumericalAnalysis;
using GSF.Threading;
using GSF.TimeSeries;
using GSF.TimeSeries.Adapters;
namespace TestingAdapters
{
///
/// Represents a class used to stream frames of measurements with random values meant to simulate a frame-based input source.
///
[Description("Random Frames: Streams frames of measurements with random values meant to simulate a frame-based input source")]
public class FrameBasedRandomValueInputAdapter : InputAdapterBase
{
#region [ Members ]
// Constants
///
/// Default value for the property.
///
public const double DefaultPublishRate = 30.0;
///
/// The default number of milliseconds for the dealy;
///
public const double DefaultLatency = 125.0;
///
/// The default jitter in the channel (1 standard deviation);
///
public const double DefaultJitter = 30;
// Fields
private double m_publishRate;
private GaussianDistribution m_latency;
private ScheduledTask m_timer;
private ScheduledTask m_statusUpdate;
private Random m_random;
private long m_nextPublicationTime;
private long m_nextPublicationTimeWithLatency;
private int m_unprocessedMeasurements;
private bool m_disposed;
#endregion
#region [ Properties ]
///
/// Gets or sets the number of frames generated by the adapter per second.
///
[ConnectionStringParameter,
DefaultValue(DefaultPublishRate),
Description("Defines the number of frames generated by the adapter per second.")]
public double PublishRate
{
get
{
return m_publishRate;
}
set
{
m_publishRate = value;
}
}
///
/// Gets the flag indicating if this adapter supports temporal processing.
///
public override bool SupportsTemporalProcessing
{
get
{
return false;
}
}
///
/// Gets flag that determines if the data input connects asynchronously.
///
///
/// Derived classes should return true when data input source is connects asynchronously, otherwise return false.
///
protected override bool UseAsyncConnect
{
get
{
return false;
}
}
#endregion
#region [ Methods ]
///
/// Initializes .
///
public override void Initialize()
{
m_random = new Random(Guid.NewGuid().GetHashCode());
Dictionary settings;
string setting;
base.Initialize();
settings = Settings;
if (!settings.TryGetValue("publishRate", out setting) || !double.TryParse(setting, out m_publishRate))
m_publishRate = DefaultPublishRate;
if (m_publishRate <= 0.0D)
throw new InvalidOperationException(string.Format("publishRate({0}) must be greater than zero", m_publishRate));
double latency;
double jitter;
if (!settings.TryGetValue("latency", out setting) || !double.TryParse(setting, out latency))
latency = DefaultLatency;
if (!settings.TryGetValue("jitter", out setting) || !double.TryParse(setting, out jitter))
jitter = DefaultJitter;
//Clips at 3 time the latency and 1/3 latency
m_latency = new GaussianDistribution(latency, jitter, latency / 3, latency * 3);
}
///
/// 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 override string GetShortStatus(int maxLength)
{
return string.Format("{0} random values generated so far...", ProcessedMeasurements).CenterText(maxLength);
}
///
/// Attempts to connect to data input source.
///
protected override void AttemptConnection()
{
m_nextPublicationTime = ToPublicationTime(DateTime.UtcNow.Ticks);
m_nextPublicationTimeWithLatency = m_nextPublicationTime + (long)(m_latency.Next() * TimeSpan.TicksPerMillisecond);
if ((object)m_timer == null)
{
m_timer = new ScheduledTask(ThreadingMode.ThreadPool);
m_timer.Running += m_timer_Running;
}
if ((object)m_statusUpdate == null)
{
m_statusUpdate = new ScheduledTask(ThreadingMode.ThreadPool);
m_statusUpdate.Running += m_statusUpdate_Running;
}
m_timer.Start();
m_statusUpdate.Start(10000);
}
void m_timer_Running(object sender, EventArgs eventArgs)
{
if (eventArgs.Argument == ScheduledTaskRunningReason.Disposing)
return;
PublishFrames();
}
void m_statusUpdate_Running(object sender, EventArgs eventArgs)
{
if (eventArgs.Argument == ScheduledTaskRunningReason.Disposing)
return;
if (!Enabled)
return;
if (m_unprocessedMeasurements > 10 * m_publishRate)
{
OnStatusMessage(string.Format("{0} unprocessed messages", m_unprocessedMeasurements));
}
m_statusUpdate.Start(10000);
}
///
/// Attempts to disconnect from data input source.
///
protected override void AttemptDisconnection()
{
if ((object)m_timer != null)
{
m_timer.Dispose();
m_timer = null;
}
if ((object)m_statusUpdate != null)
{
m_statusUpdate.Dispose();
m_statusUpdate = null;
}
}
///
/// 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)
{
try
{
if (disposing)
{
if ((object)m_timer != null)
{
m_timer.Dispose();
m_timer = null;
}
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
private void PublishFrames()
{
while (m_nextPublicationTimeWithLatency - DateTime.UtcNow.Ticks < 1 * TimeSpan.TicksPerMillisecond)
{
if (!Enabled)
return;
IMeasurement[] outputMeasurements = OutputMeasurements;
IMeasurement[] newMeasurements = new IMeasurement[outputMeasurements.Length];
for (int x = 0; x < outputMeasurements.Length; x++)
{
newMeasurements[x] = Measurement.Clone(outputMeasurements[x], m_random.NextDouble(), m_nextPublicationTime);
}
OnNewMeasurements(newMeasurements);
m_nextPublicationTime = GetNextPublicationTime(m_nextPublicationTime);
m_nextPublicationTimeWithLatency = m_nextPublicationTime + (long)(m_latency.Next() * TimeSpan.TicksPerMillisecond);
m_unprocessedMeasurements = (int)((DateTime.UtcNow.Ticks - m_nextPublicationTime) / (Ticks.PerSecond / m_publishRate));
}
if (!Enabled)
return;
long delay = ((m_nextPublicationTimeWithLatency - DateTime.UtcNow.Ticks) / TimeSpan.TicksPerMillisecond);
if (delay < 1)
m_timer.Start();
else if (delay > 10000)
m_timer.Start(10000);
else
m_timer.Start((int)delay);
}
private long GetNextPublicationTime(long time)
{
double interval = Ticks.PerSecond / m_publishRate;
long nextTime = (long)Math.Round(time + interval);
return ToPublicationTime(nextTime);
}
private long ToPublicationTime(long time)
{
double interval = Ticks.PerSecond / m_publishRate;
long seconds = time / Ticks.PerSecond;
long subseconds = time % Ticks.PerSecond;
long index = (long)Math.Round(subseconds / interval);
return (seconds * Ticks.PerSecond) + (long)Math.Round(index * interval);
}
#endregion
}
}