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