Skip to content

Add gap mode for brief PPS signal loss - #308

Open
jclark wants to merge 4 commits into
masterfrom
gap-mode
Open

Add gap mode for brief PPS signal loss#308
jclark wants to merge 4 commits into
masterfrom
gap-mode

Conversation

@jclark

@jclark jclark commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Fixes #188.

Tracking mode now exits to a new gap mode on any missing sample, switching the PHC to the averaged frequency. The controller preserves the live tracking processor (servo, MAD window, avgFreq, bad-sample bookkeeping) across tracking<->gap transitions and restores it when the signal returns, instead of rebuilding processors as other transitions do. Gap mode counts as in sync: PTP clock quality is unchanged during a gap.

After a gap of at least recoveryThreshold missing samples, returning samples go through MAD recovery: the adaptive MAD threshold is relaxed by driftLimit so legitimate samples reflecting clock drift across the gap are accepted and collected, then the MAD window is re-baselined by the median drift and seeded with the collected samples. If the median shift exceeds driftLimit the controller falls back to reset. The new [sync.gap] table configures recoveryThreshold, driftLimit and recoverySamples; a holdoverThreshold and the transition to holdover mode are deferred to #199.

The user-visible payoff is that sync.track.badSampleRunLimit can now usefully be raised on hardware whose clock drifts slowly during signal loss: without recovery, post-gap samples showing legitimate drift are rejected as outliers and force a reset anyway. Simulated outage sweeps (20 gap windows per duration, requiring every gap to recover with worst in-sync offset within ~100ns) put the safe gap at 30s for the TimeHAT TCXO, 10s for the CM4, and ~6s for the i225-T1, so the default of 5 stands and the reference configs in configs/syncsim carry the per-hardware values (README documents the derivation).

Verified entirely in syncsim: new test cases cover post-gap drift acceptance, outlier rejection during recovery, the short-gap fast path, and the excessive-drift reset, with new mode-sequence and gap-sample-count assertions backing them. The satpulse.toml man page documents the new mode and the tuning knob.

jclark added 3 commits June 10, 2026 11:38
Tracking mode now exits to a new gap mode on any missing sample, switching the PHC to the averaged frequency. The controller preserves the live tracking processor (servo, MAD window, avgFreq, bad-sample bookkeeping) across tracking<->gap transitions and restores it when the signal returns, instead of rebuilding processors as other transitions do.

After a gap of at least recoveryThreshold missing samples, returning samples go through MAD recovery: the adaptive MAD threshold is relaxed by driftLimit so legitimate samples reflecting clock drift across the gap are accepted, fed to the servo, and collected; the MAD window is then re-baselined by the median drift (median.Window.ShiftAll) and seeded with the collected samples. If the median shift exceeds driftLimit, the controller falls back to reset. The unconditional outlier gate still applies throughout, and gap mode shares tracking's bad-sample limits.

Gap mode counts as in sync: PTP clock quality is unchanged during a gap. The new [sync.gap] table configures recoveryThreshold, driftLimit, and recoverySamples; validation requires recoveryThreshold < track.badSampleRunLimit. A holdoverThreshold and a transition to holdover mode are deferred to #199.

The syncsim mode observer now records the full mode sequence, with new expectModeSequence and expectGapSamples test assertions.

Fixes #188
The sync table section now lists the gap subtable, describes the tracking -> gap -> tracking flow for brief PPS signal loss (#188), and adds track.badSampleRunLimit to the commonly-changed keys, pointing at the per-hardware values in configs/syncsim.
Gap mode (#188) makes it useful to raise track.badSampleRunLimit on hardware whose clock drifts slowly during signal loss, riding out longer gaps at full synchronization quality. Simulated outage sweeps (20 gap windows per duration, requiring every gap to recover without a reset and the worst in-sync offset to stay within ~100ns) give: timehat-f9t ~2-3 ppb drift, safe to 30s; cm4-f9t ~8-13 ppb, safe to 10s; i225-m8t ~25 ppb, safe only to ~6s, so the default of 5 stands.

The timehat and cm4 configs carry the derived values (the timehat also raises badSampleWindow so the bad-sample ratio limit does not preempt the run limit during a long gap), the README documents the derivation and warns that gap.driftLimit encodes the accuracy budget and should not be raised to extend gap tolerance, and the NEWS entry for gap mode mentions the payoff.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5e30ad80b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 127 to +128
func (m Mode) InSync() bool {
return m == ModeTracking
return m == ModeTracking || m == ModeGap

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep NTP samples enabled during gap recovery

In deployments using the NTP refclock or SHM outputs, marking ModeGap as in-sync here leaves those outputs inconsistent: time/internal/gpsevent/dispatcher.go:391 still returns unless d.controller.Mode() == phcsync.ModeTracking, so the first valid PPS samples after an outage are suppressed for the whole gap-recovery period even though PTP/metrics report the clock as synchronized. Update that gate to use InSync() (or avoid advertising gap as synchronized) so NTP clients don't see an avoidable sample outage after PPS returns.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b389d58

The refclock/SHM sample gate in sysSample still required tracking mode, so during gap mode (#188) the NTP outputs suppressed samples while PTP clock quality and metrics reported the clock as synchronized, including the first valid samples after the PPS signal returned. Gate on Mode.InSync() instead, which is the condition every other consumer uses. The samples are GPS-message-time vs system-clock offsets, not PHC-derived, so they remain valid during gap recovery.
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.

MAD rejects legitimate samples after missing samples due to clock drift

1 participant