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