Unified · Split
| @@ -103,6 +103,23 @@ namespace PIAdapters · 102 unmodified lines | ||
| 103 | 103 | { |
| 104 | 104 | #region [ Members ] |
| 105 | 105 | |
| 106 | + // Constants | |
| 107 | + | |
| 108 | + /// <summary> | |
| 109 | + /// Defines the default value for <see cref="EnableTimeReasonabilityCheck"/>. | |
| 110 | + /// </summary> | |
| 111 | + public const bool DefaultEnableTimeReasonabilityCheck = false; | |
| 112 | + | |
| 113 | + /// <summary> | |
| 114 | + /// Defines the default value for <see cref="PastTimeReasonabilityLimit"/>. | |
| 115 | + /// </summary> | |
| 116 | + public const double DefaultPastTimeReasonabilityLimit = 43200.0D; | |
| 117 | + | |
| 118 | + /// <summary> | |
| 119 | + /// Defines the default value for <see cref="FutureTimeReasonabilityLimit"/>. | |
| 120 | + /// </summary> | |
| 121 | + public const double DefaultFutureTimeReasonabilityLimit = 43200.0D; | |
| 122 | + | |
| 106 | 123 | // Fields |
| 107 | 124 | |
| 108 | 125 | // Define cached mapping between GSFSchema measurements and PI points |
| @@ -116,6 +133,8 @@ namespace PIAdapters · 7 unmodified lines | ||
| 116 | 133 | private readonly LongSynchronizedOperation m_handleTagRemoval; // Tag removal operation, if any |
| 117 | 134 | private Dictionary<Guid, Ticks> m_lastArchiveTimes; // Cache of last point archive times |
| 118 | 135 | private PIConnection m_connection; // PI server connection for meta-data synchronization |
| 136 | + private long m_pastTimeReasonabilityLimit; // Past-timestamp reasonability limit | |
| 137 | + private long m_futureTimeReasonabilityLimit; // Future-timestamp reasonability limit | |
| 119 | 138 | private DateTime m_lastMetadataRefresh; // Tracks time of last meta-data refresh |
| 120 | 139 | private long m_processedMappings; // Total number of mappings processed so far |
| 121 | 140 | private long m_processedMeasurements; // Total number of measurements processed so far |
| @@ -288,6 +307,38 @@ namespace PIAdapters · 166 unmodified lines | ||
| 288 | 307 | [DefaultValue(0.0D)] |
| 289 | 308 | public double MaximumPointResolution { get; set; } |
| 290 | 309 | |
| 310 | + /// <summary> | |
| 311 | + /// Gets or sets flag that indicates if incoming timestamps to the historian should be validated for reasonability. | |
| 312 | + /// </summary> | |
| 313 | + [ConnectionStringParameter] | |
| 314 | + [Description("Define the flag that indicates if incoming timestamps to the historian should be validated for reasonability.")] | |
| 315 | + [DefaultValue(DefaultEnableTimeReasonabilityCheck)] | |
| 316 | + public bool EnableTimeReasonabilityCheck { get; set; } | |
| 317 | + | |
| 318 | + /// <summary> | |
| 319 | + /// Gets or sets the maximum number of seconds that a past timestamp, as compared to local clock, will be considered valid. | |
| 320 | + /// </summary> | |
| 321 | + [ConnectionStringParameter] | |
| 322 | + [Description("Define the maximum number of seconds that a past timestamp, as compared to local clock, will be considered valid.")] | |
| 323 | + [DefaultValue(DefaultPastTimeReasonabilityLimit)] | |
| 324 | + public double PastTimeReasonabilityLimit | |
| 325 | + { | |
| 326 | + get => new Ticks(m_pastTimeReasonabilityLimit).ToSeconds(); | |
| 327 | + set => m_pastTimeReasonabilityLimit = Ticks.FromSeconds(Math.Abs(value)); | |
| 328 | + } | |
| 329 | + | |
| 330 | + /// <summary> | |
| 331 | + /// Gets or sets the maximum number of seconds that a future timestamp, as compared to local clock, will be considered valid. | |
| 332 | + /// </summary> | |
| 333 | + [ConnectionStringParameter] | |
| 334 | + [Description("Define the maximum number of seconds that a future timestamp, as compared to local clock, will be considered valid.")] | |
| 335 | + [DefaultValue(DefaultFutureTimeReasonabilityLimit)] | |
| 336 | + public double FutureTimeReasonabilityLimit | |
| 337 | + { | |
| 338 | + get => new Ticks(m_futureTimeReasonabilityLimit).ToSeconds(); | |
| 339 | + set => m_futureTimeReasonabilityLimit = Ticks.FromSeconds(Math.Abs(value)); | |
| 340 | + } | |
| 341 | + | |
| 291 | 342 | /// <summary> |
| 292 | 343 | /// Returns the detailed status of the data output source. |
| 293 | 344 | /// </summary> |
| @@ -303,6 +354,14 @@ namespace PIAdapters · 9 unmodified lines | ||
| 303 | 354 | status.AppendLine($" Connected to server: {(m_connection?.Connected ?? false ? "Yes" : "No")}"); |
| 304 | 355 | status.AppendLine($" Using compression: {UseCompression}"); |
| 305 | 356 | status.AppendLine($" Maximum point resolution: {MaximumPointResolution:N3} seconds{(MaximumPointResolution <= 0.0D ? " - all data will be archived" : "")}"); |
| 357 | + status.AppendLine($" Time reasonability check: {(EnableTimeReasonabilityCheck ? "Enabled" : "Not Enabled")}"); | |
| 358 | + | |
| 359 | + if (EnableTimeReasonabilityCheck) | |
| 360 | + { | |
| 361 | + status.AppendLine($" Maximum past time limit: {PastTimeReasonabilityLimit:N4}s, i.e., {new Ticks(m_pastTimeReasonabilityLimit).ToElapsedTimeString(4)}"); | |
| 362 | + status.AppendLine($" Maximum future time limit: {FutureTimeReasonabilityLimit:N4}s, i.e., {new Ticks(m_futureTimeReasonabilityLimit).ToElapsedTimeString(4)}"); | |
| 363 | + } | |
| 364 | + | |
| 306 | 365 | status.AppendLine($" Meta-data sync enabled: {RunMetadataSync}"); |
| 307 | 366 | |
| 308 | 367 | if (RunMetadataSync) |
| @@ -562,6 +621,15 @@ namespace PIAdapters · 253 unmodified lines | ||
| 562 | 621 | if (!Enabled) |
| 563 | 622 | return; |
| 564 | 623 | |
| 624 | + // Validate timestamp reasonability as compared to local clock, when enabled | |
| 625 | + if (EnableTimeReasonabilityCheck) | |
| 626 | + { | |
| 627 | + long deviation = DateTime.UtcNow.Ticks - measurement.Timestamp.Value; | |
| 628 | + | |
| 629 | + if (deviation < -m_futureTimeReasonabilityLimit || deviation > m_pastTimeReasonabilityLimit) | |
| 630 | + continue; | |
| 631 | + } | |
| 632 | + | |
| 565 | 633 | // Lookup connection point mapping for this measurement, if it wasn't found - go ahead and exit |
| 566 | 634 | if (!m_mappedPIPoints.TryGetValue(measurement.Key, out PIPoint point)) |
| 567 | 635 | continue; |