Skip to content

ntpstats.ubx

u-blox UBX timing messages and PPS sawtooth correction.

GNSS timing receivers (u-blox ZED-F9T, LEA-M8T and others, also on the OCP Time Card) report how good their time is in binary UBX messages. ntpstats reads three of them from a raw receiver log (UBX frames, possibly mixed with NMEA text), with the layouts of the u-blox interface description (e.g. ZED-F9T, UBX-18053584):

UBX-NAV-CLOCK (0x01 0x22) receiver clock bias clkB (ns) and drift clkD (ns/s), time accuracy tAcc (ns) and frequency accuracy fAcc (ps/s) of each navigation epoch. The series is the clock bias as reported, so its stability is that of the receiver's own oscillator as GNSS sees it. UBX-NAV-TIMEUTC (0x01 0x21) the UTC date and time of each navigation epoch, used to time-stamp the other messages, and its time accuracy estimate. UBX-TIM-TP (0x0D 0x01) the time of the next time pulse and its quantization error qErr (ps): the receiver can only place the pulse on an edge of its internal clock, so the PPS is off by up to half a clock period, and the error drifts as a "sawtooth". qErr predicts it.

Times come from NAV-TIMEUTC when present (same iTOW); otherwise from the GPS week of TIM-TP, with GPS - UTC = leap_seconds (18 s since 2017).

:func:apply_qerr removes the sawtooth from a time-interval-counter measurement of the receiver's PPS. u-blox does not state the sign with which qErr applies, so by default the sign that removes the sawtooth from the data (the smaller sample-to-sample variance) is chosen and reported; it can be fixed with sign=+1 or -1.

frames(data, max_len=4096)

Valid UBX frames (class, id, payload); anything else (NMEA, RTCM, noise) is skipped.

is_ubx(head)

True if head (the start of a file) contains a valid UBX frame.

parse_ubx(data, name='ubx', leap_seconds=LEAP_SECONDS)

Series of a UBX log: the receiver clock (NAV-CLOCK) and the time-pulse quantization error (TIM-TP).

apply_qerr(pps, qerr, sign='auto', max_dt=0.5)

Remove the quantization sawtooth from a PPS measurement with the receiver's qErr.

pps is a time-interval-counter series of the receiver's PPS against a reference (any sign convention); each sample is paired with the qErr of the pulse nearest in time (within max_dt s). The result is pps + sign * qErr; sign="auto" picks the sign that minimises the sample-to-sample variance (the sawtooth is the fastest component), and the choice and the improvement are reported in meta.