//****************************************************************************************************** // SynchronizeLocalClock.cs - Gbtc // // Copyright © 2018, 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: // ---------------------------------------------------------------------------------------------------- // 12/20/2018 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** // ReSharper disable InconsistentNaming // ReSharper disable PossibleMultipleEnumeration // ReSharper disable NotAccessedField.Local // ReSharper disable UnusedMember.Global using System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using System.Timers; using GSF; using GSF.Diagnostics; using GSF.TimeSeries; using GSF.TimeSeries.Adapters; namespace TestingAdapters { /// /// Represents a class used to synchronize the local system clock time to the time from input measurements. /// [Description("System Time Sync: Synchronizes local system clock time to the time from input measurements")] public class SynchronizeLocalClock : FacileActionAdapterBase { #region [ Members ] // Nested Types #pragma warning disable 169 #pragma warning disable 414 #pragma warning disable 649 private struct SYSTEMTIME { public ushort wYear; public ushort wMonth; public ushort wDayOfWeek; public ushort wDay; public ushort wHour; public ushort wMinute; public ushort wSecond; public ushort wMilliseconds; } #pragma warning restore 169 #pragma warning restore 414 #pragma warning restore 649 // Constants /// /// Default value for the property. /// public const int DefaultUpdateFrequency = 1000; // Fields private Timer m_updateTimer; private long m_clockUpdates; private bool m_goodSourceTime; private bool m_disposed; #endregion #region [ Properties ] /// /// Gets or sets the largest possible value generated by the adapter. /// [ConnectionStringParameter] [DefaultValue(DefaultUpdateFrequency)] [Description("Defines the update frequency, in milliseconds, for setting the local system clock time.")] public int UpdateFrequency { get; set; } = DefaultUpdateFrequency; /// /// Gets or sets flag that determines whether or not to use the local clock time as real time. /// /// /// Use your local system clock as real time only if the time is locally GPS-synchronized, /// or if the measurement values being sorted were not measured relative to a GPS-synchronized clock. /// Turn this off if the class is intended to process historical data. /// [EditorBrowsable(EditorBrowsableState.Never)] // Redeclared to hide property - value should not be changed by user public override bool UseLocalClockAsRealTime { get => base.UseLocalClockAsRealTime; set => base.UseLocalClockAsRealTime = value; } /// /// Gets or sets flag to start tracking the absolute latest received measurement values. /// /// /// Latest received measurement value will be available via the property. /// [EditorBrowsable(EditorBrowsableState.Never)] // Redeclared to hide property - value should not be changed by user public override bool TrackLatestMeasurements { get => base.TrackLatestMeasurements; set => base.TrackLatestMeasurements = value; } /// /// Gets the flag indicating if this adapter supports temporal processing. /// public override bool SupportsTemporalProcessing => false; #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) { try { if (disposing) { if (m_updateTimer != null) { m_updateTimer.Stop(); m_updateTimer.Elapsed -= m_updateTimer_Elapsed; m_updateTimer.Dispose(); } } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } /// /// Initializes . /// public override void Initialize() { Dictionary settings = Settings; // Force facile adapter to use incoming timestamps as time source, i.e., "RealTime" settings[nameof(UseLocalClockAsRealTime)] = false.ToString(); settings[nameof(TrackLatestMeasurements)] = true.ToString(); base.Initialize(); if (settings.TryGetValue(nameof(UpdateFrequency), out string setting)) { if (int.TryParse(setting, out int updateFrequency) && updateFrequency > 0) UpdateFrequency = updateFrequency; } m_updateTimer = new Timer(UpdateFrequency); m_updateTimer.Elapsed += m_updateTimer_Elapsed; m_updateTimer.Start(); } /// /// 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) => $"Updated clock {m_clockUpdates:N3} times so far...".CenterText(maxLength); /// /// Queues a collection of measurements for processing. /// /// Measurements to queue for processing. public override void QueueMeasurementsForProcessing(IEnumerable measurements) { List measurementsWithGoodTime = new List(); foreach (IMeasurement measurement in measurements) { if (measurement.TimestampQualityIsGood()) measurementsWithGoodTime.Add(measurement); } if (measurementsWithGoodTime.Count == 0) { base.QueueMeasurementsForProcessing(measurements); m_goodSourceTime = false; } else { base.QueueMeasurementsForProcessing(measurementsWithGoodTime); m_goodSourceTime = true; } } private void m_updateTimer_Elapsed(object sender, ElapsedEventArgs e) { try { Ticks newSystemTime = RealTime; if (newSystemTime <= 0L) return; SetSystemTime(RealTime); m_clockUpdates++; if (!m_goodSourceTime) OnStatusMessage(MessageLevel.Warning, "WARNING: Clock set with measurement that has bad quality -- increase measurement sources."); } catch (Exception ex) { OnProcessException(MessageLevel.Error, ex); } } #endregion #region [ Static ] // Static Methods [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetSystemTime(ref SYSTEMTIME lpSystemTime); private static void SetSystemTime(DateTime newSystemTime) { SYSTEMTIME systime = new SYSTEMTIME { wYear = (ushort)newSystemTime.Year, wMonth = (ushort)newSystemTime.Month, wDay = (ushort)newSystemTime.Day, wHour = (ushort)newSystemTime.Hour, wMinute = (ushort)newSystemTime.Minute, wSecond = (ushort)newSystemTime.Second, wMilliseconds = (ushort)newSystemTime.Millisecond }; if (!SetSystemTime(ref systime)) throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to set local system time, verify service has SE_SYSTEMTIME_NAME privilege."); } #endregion } }