Skip to main content

Crate robust_ntrip_client

Crate robust_ntrip_client 

Source
Expand description

§robust-ntrip-client - Robust NTRIP Client

This crate provides a client to connect to a Network Transport of RTCM via Internet Protocol (NTRIP) server. RTCM stands for Radio Technical Commission for Maritime services and is the message type carrying GNSS correction signals to enable centimeter-resolution GNSS position finding.

The implementation in this crate attempts to be robust against network interruptions and other transient errors. The RobustNtripClient handles the low-level interaction with the NTRIP server and would allow plugging an RTCM parsing library. The ParsingNtripClient wraps this low-level client and parses and validates the RTCM messages.

§Reconnection policy: we follow QGroundControl

RTCM 10410.1, the NTRIP 2.0 standard, is paywalled, and the free RTCM guidance paper for client developers (“NTRIP Client Devices / Best Practices”, 2023-SC104-1344) stops short of saying which failures to retry. Surveying shipping clients found no agreement either: pygnssutils treats every HTTP error as final, gpsd fails the attempt and leaves reconnection to its caller, and QGroundControl retries all but one status.

Rather than invent a fifth policy, this crate copies QGroundControl’s, on the grounds that QGC does the same job – streaming RTCM corrections to a drone autopilot – and that matching one real implementation exactly is easier to defend and to re-check. Specifically, from QGroundControl v5.0.3-1265-gf1883f93a (f1883f93a5ba0b5e9c1ec88b3815b290e2608697, 2026-08-12):

BehaviourOursQGC
HTTP 401returned to the caller, never retriedNTRIPHttpTransport.cc:388-392 maps it to AuthFailed, which isRetryable() refuses (NTRIPManager.cc:82-91)
Any other non-2xx status, 404 includedretriedNTRIPHttpTransport.cc:406 maps it to HttpError, which is retryable
Transport errors (DNS, refused, reset, timeout)retriedretryable by isRetryable()’s default arm
Backoff1, 2, 4, 8, 16, 30, 30, … secondskMinReconnectMs * (1 << qMin(attempts, 5)) capped at kMaxReconnectMs, NTRIPManager.cc:367-370 and NTRIPManager.h:159-161
Giving upafter 100 attempts that reached the caster – see belowkMaxReconnectAttempts, NTRIPManager.h:161
Ntrip-VersionNtrip/2.0NTRIPHttpTransport.cc:77
The caster’s error reply bodycaptured on CasterHttpError, tidied the same way for displayNTRIPHttpTransport.cc:394-406
Resetting the ladderon data arriving, not on handshake – see belowNTRIPManager.cc:326

RobustNtripClientOptions::max_backoff_duration plays QGC’s kMaxReconnectMs and defaults to the same 30 seconds; the other two values are constants, as they are in QGC. The revision and every file:line above are repeated in comments next to the code that mirrors them, so the values can be cross-checked against a QGC checkout.

Two deliberate departures, both because QGC’s caller is an operator watching a GUI and ours is unattended software.

Failures that never reached the caster – no route, DNS down, connection refused: what an uplink that is not up yet looks like – are retried indefinitely rather than counting towards the limit. QGC can afford to stop, because stopping puts a message in front of someone who is sitting there. A rig that boots before its uplink comes up should be streaming corrections an hour later, not holding an error nobody read. The limit still applies to attempts the caster answered, which are the ones where something may need correcting and where RTCM warns about hammering.

Second, QGC resets its attempt counter when the HTTP handshake completes (NTRIPManager.cc:326) rather than when data arrives, so a caster which accepts a connection and then stays silent is retried at the minimum interval forever – the hammering RTCM’s guidance warns gets a client banned. We reset on data instead, so a silent caster climbs the same ladder as a refused one and eventually exhausts MAX_CONNECT_ATTEMPTS. Where the reset sits looks like an artefact of QGC’s state machine rather than a considered choice.

See also the ntrip-client crate. I was unaware of this other crate at the time I began writing robust-ntrip-client.

§Example usage

#[tokio::main]
async fn main() -> eyre::Result<()> {
   let raw_client = robust_ntrip_client::RobustNtripClient::new(
       "ntrip://username:password@example-ntrip-server.com/mountpoint",
       Default::default()
   ).await?;
   let mut ntrip = robust_ntrip_client::ParsingNtripClient::new(raw_client);

   loop {
       let msg = ntrip.next().await?;
       println!(
           "message {}: {} bytes",
           msg.message_number(),
           msg.frame_data().len()
       );
   }
}

Structs§

CasterHttpError
An error reply from the NTRIP caster.
FrameData
One valid frame of RTCM data from the NTRIP server.
ParsingNtripClient
A client which parses RTCM messages from the NTRIP stream.
RobustNtripClient
A client which automatically reconnects to an NTRIP server in case of interruption.
RobustNtripClientOptions
Options for connecting to the NTRIP server.

Constants§

ERROR_BODY_MAX_CHARS
How much of an error reply body is kept. QGC’s body.left(500).
MAX_CONNECT_ATTEMPTS
Attempts after which we stop trying and return an error. QGC kMaxReconnectAttempts.
QGC_REVISION
The QGroundControl revision whose reconnection policy this crate mirrors.