Skip to content

fix: break chart line on timestamp gaps caused by packet loss#388

Merged
tylerkron merged 4 commits intomainfrom
feature/chart-gap-detection
Mar 15, 2026
Merged

fix: break chart line on timestamp gaps caused by packet loss#388
tylerkron merged 4 commits intomainfrom
feature/chart-gap-detection

Conversation

@tylerkron
Copy link
Contributor

Summary

  • Detects timestamp gaps in streaming data caused by packet loss and inserts DataPoint.Undefined markers in OxyPlot so the chart renders a visible line break instead of a misleading linear connection across the missing data
  • Uses a per-channel exponential moving average (EMA, α=0.1) of timestamp deltas as the gap detector — any delta exceeding 2× the running average triggers a break
  • Resets gap-detection state on ClearPlot() to keep channels clean between sessions

How it works

PlotLogger now maintains two dictionaries keyed by (deviceSerial, channelName):

  • _lastTimestampMs — the deltaTime of the previously logged point
  • _avgDeltaMs — the EMA of observed inter-sample deltas

On each Log(DataSample) call, InsertGapIfNeeded computes the current delta, compares it to GapThresholdMultiplier * avgDelta (2×), and if exceeded inserts DataPoint.Undefined before the new real point. OxyPlot treats undefined points as line breaks, leaving a visible gap in the chart.

Test plan

  • Stream analog/digital channels at high sample rate over WiFi; introduce artificial packet loss and verify the chart shows a break rather than a linear slope across the gap
  • Verify normal continuous streaming still draws connected lines (no false positives)
  • Verify ClearPlot fully resets the logger and the next session starts fresh
  • Test across multiple simultaneous channels to ensure per-channel state is isolated

Closes #291

🤖 Generated with Claude Code

During WiFi streaming, packet loss produces missing timestamps that the
chart previously bridged with a straight line, giving a misleading linear
slope. This change detects those gaps in PlotLogger and inserts a
DataPoint.Undefined marker so OxyPlot renders a visible break instead of
connecting the points.

Detection uses an exponential moving average (EMA, alpha=0.1) of per-channel
timestamp deltas. Any delta that exceeds 2× the running average is treated as
a gap. The EMA adapts automatically to the current sample rate, so no fixed
threshold configuration is needed.

Closes #291

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tylerkron tylerkron requested a review from a team as a code owner February 28, 2026 20:07
tylerkron and others added 3 commits February 28, 2026 14:07
Extracts the EMA-based gap detection logic from PlotLogger into a
standalone TimestampGapDetector class (internal, no WPF dependencies),
then adds 11 unit tests covering:

- First and second sample never flagged as gaps (EMA bootstrap)
- Consistent cadence produces no false positives
- Delta exactly at threshold (2×) is not flagged
- Delta clearly above threshold is detected
- Delta just above threshold is detected
- Gap on one channel does not affect another (state isolation)
- EMA adapts to a gradually slowing sample rate without false positives
- After a detected gap the stream resumes cleanly
- Clear() resets all per-channel state

PlotLogger now delegates to TimestampGapDetector, keeping the gap
logic testable in isolation from WPF rendering infrastructure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link

📊 Code Coverage Report

Summary

Summary
Generated on: 3/15/2026 - 10:25:56 PM
Coverage date: 3/15/2026 - 10:25:29 PM - 3/15/2026 - 10:25:52 PM
Parser: MultiReport (4x Cobertura)
Assemblies: 3
Classes: 103
Files: 137
Line coverage: 20.2% (1085 of 5353)
Covered lines: 1085
Uncovered lines: 4268
Coverable lines: 5353
Total lines: 17007
Branch coverage: 18.7% (362 of 1931)
Covered branches: 362
Total branches: 1931
Method coverage: Feature is only available for sponsors

Coverage

DAQiFi - 19.9%
Name Line Branch
DAQiFi 19.9% 18.6%
Daqifi.Desktop.App 2.3% 0%
Daqifi.Desktop.Channel.AbstractChannel 40.9% 27.7%
Daqifi.Desktop.Channel.AnalogChannel 58.7% 25%
Daqifi.Desktop.Channel.Channel 11.5% 0%
Daqifi.Desktop.Channel.ChannelColorManager 100% 100%
Daqifi.Desktop.Channel.DataSample 90.4%
Daqifi.Desktop.Channel.DigitalChannel 65.2% 25%
Daqifi.Desktop.Commands.CompositeCommand 0% 0%
Daqifi.Desktop.Commands.HostCommands 0%
Daqifi.Desktop.Commands.WeakEventHandlerManager 0% 0%
Daqifi.Desktop.Configuration.FirewallConfiguration 90.6% 66.6%
Daqifi.Desktop.Configuration.WindowsFirewallWrapper 64% 68.4%
Daqifi.Desktop.ConnectionManager 43% 43.1%
Daqifi.Desktop.Converters.BoolToActiveStatusConverter 0% 0%
Daqifi.Desktop.Converters.BoolToConnectionStatusConverter 0% 0%
Daqifi.Desktop.Converters.BoolToStatusColorConverter 0% 0%
Daqifi.Desktop.Converters.ConnectionTypeToColorConverter 0% 0%
Daqifi.Desktop.Converters.ConnectionTypeToUsbConverter 0% 0%
Daqifi.Desktop.Converters.InvertedBoolToVisibilityConverter 0% 0%
Daqifi.Desktop.Converters.ListToStringConverter 0% 0%
Daqifi.Desktop.Converters.NotNullToVisibilityConverter 0% 0%
Daqifi.Desktop.Converters.OxyColorToBrushConverter 0% 0%
Daqifi.Desktop.Converters.StringRightConverter 0% 0%
Daqifi.Desktop.Device.AbstractStreamingDevice 47.9% 44%
Daqifi.Desktop.Device.DeviceMessage 0%
Daqifi.Desktop.Device.Firmware.BootloaderSessionStreamingDeviceAdapter 0% 0%
Daqifi.Desktop.Device.Firmware.WifiPromptDelayProcessRunner 0% 0%
Daqifi.Desktop.Device.NativeMethods 100%
Daqifi.Desktop.Device.SerialDevice.SerialStreamingDevice 27.8% 30.8%
Daqifi.Desktop.Device.WiFiDevice.DaqifiStreamingDevice 43.3% 41.6%
Daqifi.Desktop.DialogService.DialogService 0% 0%
Daqifi.Desktop.DialogService.ServiceLocator 0% 0%
Daqifi.Desktop.DuplicateDeviceCheckResult 100%
Daqifi.Desktop.Exporter.OptimizedLoggingSessionExporter 29.7% 32.9%
Daqifi.Desktop.Exporter.SampleData 0%
Daqifi.Desktop.Helpers.BooleanConverter`1 0% 0%
Daqifi.Desktop.Helpers.BooleanToInverseBoolConverter 0% 0%
Daqifi.Desktop.Helpers.BooleanToVisibilityConverter 0%
Daqifi.Desktop.Helpers.EnumDescriptionConverter 100% 100%
Daqifi.Desktop.Helpers.IntToVisibilityConverter 0% 0%
Daqifi.Desktop.Helpers.MyMultiValueConverter 0%
Daqifi.Desktop.Helpers.NaturalSortHelper 100% 100%
Daqifi.Desktop.Helpers.VersionHelper 98.2% 66.2%
Daqifi.Desktop.Logger.DatabaseLogger 0% 0%
Daqifi.Desktop.Logger.LoggedSeriesLegendItem 0% 0%
Daqifi.Desktop.Logger.LoggingContext 0%
Daqifi.Desktop.Logger.LoggingManager 0% 0%
Daqifi.Desktop.Logger.LoggingSession 26.6% 0%
Daqifi.Desktop.Logger.PlotLogger 0% 0%
Daqifi.Desktop.Logger.SummaryLogger 0% 0%
Daqifi.Desktop.Logger.TimestampGapDetector 100% 100%
Daqifi.Desktop.MainWindow 0% 0%
Daqifi.Desktop.Migrations.InitialSQLiteMigration 0%
Daqifi.Desktop.Migrations.LoggingContextModelSnapshot 0%
Daqifi.Desktop.Models.AddProfileModel 0%
Daqifi.Desktop.Models.DaqifiSettings 84% 90%
Daqifi.Desktop.Models.DebugDataCollection 6.6% 0%
Daqifi.Desktop.Models.DebugDataModel 0% 0%
Daqifi.Desktop.Models.Notifications 0%
Daqifi.Desktop.Models.SdCardFile 0%
Daqifi.Desktop.Services.WindowsPrincipalAdminChecker 0%
Daqifi.Desktop.Services.WpfMessageBoxService 0%
Daqifi.Desktop.UpdateVersion.VersionNotification 0% 0%
Daqifi.Desktop.View.AddChannelDialog 0% 0%
Daqifi.Desktop.View.AddProfileConfirmationDialog 0% 0%
Daqifi.Desktop.View.AddprofileDialog 0% 0%
Daqifi.Desktop.View.ConnectionDialog 0% 0%
Daqifi.Desktop.View.DebugWindow 0% 0%
Daqifi.Desktop.View.DeviceLogsView 0% 0%
Daqifi.Desktop.View.DuplicateDeviceDialog 0% 0%
Daqifi.Desktop.View.ErrorDialog 0% 0%
Daqifi.Desktop.View.ExportDialog 0% 0%
Daqifi.Desktop.View.FirmwareDialog 0% 0%
Daqifi.Desktop.View.Flyouts.ChannelsFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.DevicesFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.FirmwareFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.LiveGraphFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.LoggedSessionFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.NotificationsFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.SummaryFlyout 0% 0%
Daqifi.Desktop.View.Flyouts.UpdateProfileFlyout 0% 0%
Daqifi.Desktop.View.SelectColorDialog 0% 0%
Daqifi.Desktop.View.SettingsDialog 0% 0%
Daqifi.Desktop.View.SuccessDialog 0% 0%
Daqifi.Desktop.ViewModels.AddChannelDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.AddProfileConfirmationDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.AddProfileDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.ConnectionDialogViewModel 21.6% 19.5%
Daqifi.Desktop.ViewModels.DaqifiViewModel 16.8% 10.1%
Daqifi.Desktop.ViewModels.DeviceLogsViewModel 0% 0%
Daqifi.Desktop.ViewModels.DeviceSettingsViewModel 0% 0%
Daqifi.Desktop.ViewModels.DuplicateDeviceDialogViewModel 0%
Daqifi.Desktop.ViewModels.ErrorDialogViewModel 0%
Daqifi.Desktop.ViewModels.ExportDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.FirmwareDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.SelectColorDialogViewModel 0% 0%
Daqifi.Desktop.ViewModels.SettingsViewModel 0%
Daqifi.Desktop.ViewModels.SuccessDialogViewModel 85.7%
Daqifi.Desktop.WindowViewModelMapping.IWindowViewModelMappingsContract 0%
Daqifi.Desktop.WindowViewModelMapping.WindowViewModelMappings 0%
Daqifi.Desktop.Common - 39.3%
Name Line Branch
Daqifi.Desktop.Common 39.3% 27.7%
Daqifi.Desktop.Common.Loggers.AppLogger 36.8% 27.7%
Daqifi.Desktop.Common.Loggers.NoOpLogger 75%
Daqifi.Desktop.IO - 100%
Name Line Branch
Daqifi.Desktop.IO 100% ****
Daqifi.Desktop.IO.Messages.MessageEventArgs`1 100%

Coverage report generated by ReportGeneratorView full report in build artifacts

@tylerkron tylerkron merged commit 8125f3c into main Mar 15, 2026
2 checks passed
@tylerkron tylerkron deleted the feature/chart-gap-detection branch March 15, 2026 22:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chart should not connect data points across timestamp gaps (packet loss)

1 participant