static void Main(string[] args) { using (Connection connection = new Connection("127.0.0.1", "PPA")) { // Query for specific time-range and points at desired down-sampled resolution ulong pointID = 2; Resolution resolution = Resolution.EverySecond; DateTime stopTime = DateTime.UtcNow.BaselinedTimestamp(BaselineTimeInterval.Second); DateTime startTime = stopTime.AddMinutes(-1.0D); Console.WriteLine("Point to query: {0}", pointID); Console.WriteLine(" Resolution: {0}", resolution); Console.WriteLine(" Start time = {0:yyyy-MMM-dd HH:mm:ss.ffffff}", startTime); Console.WriteLine(" Stop time = {0:yyyy-MMM-dd HH:mm:ss.ffffff}", stopTime); Console.WriteLine(); Console.WriteLine("Press any key to begin..."); Console.ReadKey(); int count = 0; // Query data for point over specified time range and data resolution foreach (IMeasurement measurement in MeasurementAPI.GetHistorianData(connection, startTime, stopTime, pointID.ToString(), resolution)) Console.WriteLine("[{0:N0}] {1}:{2} @ {3:yyyy-MMM-dd HH:mm:ss.ffffff} = {4}, quality: {5}", ++count, measurement.Key.Source, measurement.Key.ID, measurement.Timestamp, measurement.Value, measurement.StateFlags); count = 0; // Get contiguous data regions for specified point foreach (Tuple<DateTime, DateTime> region in MeasurementAPI.GetContiguousDataRegions(connection, startTime, stopTime, pointID, resolution)) Console.WriteLine("Data region {0:N0}: from {1:yyyy-MMM-dd HH:mm:ss.ffffff} to {2:yyyy-MMM-dd HH:mm:ss.ffffff}", ++count, region.Item1, region.Item2); } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); }
/// <summary> /// Read historian data from server. /// </summary> /// <param name="connection">openHistorian connection.</param> /// <param name="startTime">Start time of query.</param> /// <param name="stopTime">Stop time of query.</param> /// <param name="measurementID">Measurement ID to test for data continuity.</param> /// <param name="resolution">Resolution for testing data.</param> /// <param name="expectedFullResolutionTicks">Expected number of ticks per interval at full resolution, e.g., 33,333 = 1/30 of a second representing a sampling interval of 30 times per second.</param> /// <returns>Enumeration of valid data ranges for specified time range.</returns> /// <remarks> /// 1 tick = 100 nanoseconds. /// </remarks> public static IEnumerable<Tuple<DateTime, DateTime>> GetContiguousDataRegions(Connection connection, DateTime startTime, DateTime stopTime, ulong measurementID, Resolution resolution, long expectedFullResolutionTicks = 333333) { // Setup time-range and point ID selections SeekFilterBase<HistorianKey> timeFilter; MatchFilterBase<HistorianKey, HistorianValue> pointFilter = PointIdMatchFilter.CreateFromPointID<HistorianKey, HistorianValue>(measurementID); HistorianKey key = new HistorianKey(); HistorianValue value = new HistorianValue(); TimeSpan interval, tolerance; // Set data scan resolution if (resolution == Resolution.Full) { interval = new TimeSpan(expectedFullResolutionTicks); timeFilter = TimestampSeekFilter.CreateFromRange<HistorianKey>(startTime, stopTime); } else { interval = resolution.GetInterval(); timeFilter = TimestampSeekFilter.CreateFromIntervalData<HistorianKey>(startTime, stopTime, interval, new TimeSpan(TimeSpan.TicksPerMillisecond)); } // PMUs times may float a little - provide a one millisecond tolerance window above the standard interval tolerance = interval.Add(TimeSpan.FromMilliseconds(1.0D)); DateTime lastStartTime = startTime; DateTime lastStopTime = startTime; DateTime nextExpectedTime = startTime; DateTime currentTime; long totalRegions = 0; // Start stream reader for the provided time window and selected points using (Database database = connection.OpenDatabase()) { TreeStream<HistorianKey, HistorianValue> stream = database.Read(SortedTreeEngineReaderOptions.Default, timeFilter, pointFilter); // Scan historian stream for given point over specified time range and data resolution while (stream.Read(key, value)) { currentTime = key.TimestampAsDate; // See if current time was not expected time and gap is larger than resolution tolerance - could simply // be user started with a time that was not aligned with desired resolution, hence the tolerance check if (currentTime != nextExpectedTime && currentTime - nextExpectedTime > tolerance) { if (lastStartTime != lastStopTime) { // Detected a data gap, return last contiguous region totalRegions++; yield return new Tuple<DateTime, DateTime>(lastStartTime, lastStopTime); } // Move start time to current value lastStartTime = currentTime; lastStopTime = lastStartTime; nextExpectedTime = lastStartTime + interval; } else { // Setup next expected timestamp nextExpectedTime += interval; lastStopTime = currentTime; } } // If no data gaps were detected, return a single value for full region for where there was data if (totalRegions == 0 && lastStartTime != lastStopTime) yield return new Tuple<DateTime, DateTime>(lastStartTime, lastStopTime); } }
/// <summary> /// Read historian data from server. /// </summary> /// <param name="connection">openHistorian connection.</param> /// <param name="startTime">Start time of query.</param> /// <param name="stopTime">Stop time of query.</param> /// <param name="measurementIDs">Comma separated list of measurement IDs to query - or <c>null</c> for all available points.</param> /// <param name="resolution">Resolution for data query.</param> /// <returns>Enumeration of <see cref="IMeasurement"/> values read for time range.</returns> /// <example> /// <code> /// using (var connection = new Connection("127.0.0.1", "PPA")) /// foreach(var measurement in GetHistorianData(connection, DateTime.UtcNow.AddMinutes(-1.0D), DateTime.UtcNow)) /// Console.WriteLine("{0}:{1} @ {2} = {3}, quality: {4}", measurement.Key.Source, measurement.Key.ID, measurement.Timestamp, measurement.Value, measurement.StateFlags); /// </code> /// </example> public static IEnumerable<IMeasurement> GetHistorianData(Connection connection, DateTime startTime, DateTime stopTime, string measurementIDs = null, Resolution resolution = Resolution.Full) { SeekFilterBase<HistorianKey> timeFilter; MatchFilterBase<HistorianKey, HistorianValue> pointFilter = null; HistorianKey key = new HistorianKey(); HistorianValue value = new HistorianValue(); // Set data scan resolution if (resolution == Resolution.Full) timeFilter = TimestampSeekFilter.CreateFromRange<HistorianKey>(startTime, stopTime); else timeFilter = TimestampSeekFilter.CreateFromIntervalData<HistorianKey>(startTime, stopTime, resolution.GetInterval(), new TimeSpan(TimeSpan.TicksPerMillisecond)); // Setup point ID selections if (!string.IsNullOrEmpty(measurementIDs)) pointFilter = PointIdMatchFilter.CreateFromList<HistorianKey, HistorianValue>(measurementIDs.Split(',').Select(ulong.Parse)); // Start stream reader for the provided time window and selected points using (Database database = connection.OpenDatabase()) { TreeStream<HistorianKey, HistorianValue> stream = database.Read(SortedTreeEngineReaderOptions.Default, timeFilter, pointFilter); while (stream.Read(key, value)) yield return new Measurement() { Metadata = MeasurementKey.LookUpOrCreate(connection.InstanceName, (uint)key.PointID).Metadata, Timestamp = key.TimestampAsDate, Value = value.AsSingle, StateFlags = (MeasurementStateFlags)value.Value3 }; } }