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):
| Behaviour | Ours | QGC |
|---|---|---|
| HTTP 401 | returned to the caller, never retried | NTRIPHttpTransport.cc:388-392 maps it to AuthFailed, which isRetryable() refuses (NTRIPManager.cc:82-91) |
| Any other non-2xx status, 404 included | retried | NTRIPHttpTransport.cc:406 maps it to HttpError, which is retryable |
| Transport errors (DNS, refused, reset, timeout) | retried | retryable by isRetryable()’s default arm |
| Backoff | 1, 2, 4, 8, 16, 30, 30, … seconds | kMinReconnectMs * (1 << qMin(attempts, 5)) capped at kMaxReconnectMs, NTRIPManager.cc:367-370 and NTRIPManager.h:159-161 |
| Giving up | after 100 attempts that reached the caster – see below | kMaxReconnectAttempts, NTRIPManager.h:161 |
Ntrip-Version | Ntrip/2.0 | NTRIPHttpTransport.cc:77 |
| The caster’s error reply body | captured on CasterHttpError, tidied the same way for display | NTRIPHttpTransport.cc:394-406 |
| Resetting the ladder | on data arriving, not on handshake – see below | NTRIPManager.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§
- Caster
Http Error - An error reply from the NTRIP caster.
- Frame
Data - One valid frame of RTCM data from the NTRIP server.
- Parsing
Ntrip Client - A client which parses RTCM messages from the NTRIP stream.
- Robust
Ntrip Client - A client which automatically reconnects to an NTRIP server in case of interruption.
- Robust
Ntrip Client Options - 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.