rtk-ntrip service is the on-drone RTK (Real-Time Kinematic) corrections client. It reproduces Mission Planner’s RTK-injection behavior autonomously on the Jetson companion computer: it connects to an NTRIP caster, streams down RTCM3 correction data, and injects it into the ArduPilot flight controller as MAVLink GPS_RTCM_DATA. In return it feeds the vehicle’s own position back to the caster as NMEA GGA sentences, which is how a network-RTK caster (like Swift Navigation’s Skylark) picks the nearest base-station solution. With corrections flowing, an RTK-capable GPS (e.g. CubePilot Here4) moves from a 2–5 m 3D fix to centimetre-level RTK Fixed.
It is one profile-gated container in the SkyCore stack (docker-compose.yml, profile rtk), running network_mode: host alongside mavproxy and skyhub. It is entirely optional — a drone without RTK hardware simply omits the rtk profile — and it never runs under SITL.
The service does not talk to the FCU serial port directly. It is a plain MAVLink UDP client of the single
mavp2p router (see MAVLink Routing), which owns /dev/ttyACM0 and fans traffic out to every consumer. RTK owns the dedicated endpoint udps:0.0.0.0:14560.Data path
Two loops run concurrently: an RTCM receiver thread (caster → FCU) and a GGA sender thread (FCU → caster). The service coordinator isRTKNTRIPService in docker/rtk-ntrip/main.py, which wires two lifecycle-managed modules — MAVLinkModule (src/modules/mavlink/) and NTRIPModule (src/modules/ntrip/).
Startup sequence
RTKNTRIPService.start() (docker/rtk-ntrip/main.py:57) runs a strict ordered bring-up and exits non-zero if any step fails:
1
Boot delay
entrypoint.sh waits STARTDELAY seconds (mapped from RTK_STARTDELAY, default 10 in .env.example) before exec’ing python3 /app/main.py. This lets the GPS and mavproxy come up first.2
Connect MAVLink
MAVLinkHandler.connect() opens RTK_MAVLINK_CONNECTION (default udpout:127.0.0.1:14560), sends an initial MAV_TYPE_GCS heartbeat to punch through the mavp2p UDP endpoint, waits for a heartbeat back, then requests the MAV_DATA_STREAM_POSITION stream at 1 Hz (src/modules/mavlink/handler.py:44).3
Wait for a GPS fix
wait_for_gps_fix(GPS_FIX_TIMEOUT) polls GPS_RAW_INT for up to 60 s. A non-zero fix_type is enough to proceed; the service logs a warning but continues even if the timeout elapses.4
Connect the NTRIP caster
NTRIPClient.connect() opens a TCP socket and sends an NTRIP-over-HTTP/1.0 request: GET /<mountpoint> with Authorization: Basic <base64(user:pass)>. It succeeds only if the response contains 200 OK or ICY 200 OK (src/modules/ntrip/client.py:61).5
Start streaming
The RTCM callback is set to
_handle_rtcm (which forwards to inject_rtcm), start_receiving() spawns the RTCM thread, and the GGA sender thread is started. The main loop then idles until a SIGINT/SIGTERM clears the run flag.RTCM injection (caster → FCU)
The NTRIP socket delivers a continuous byte stream.NTRIPClient._parse_rtcm_frames() buffers it and splits it into complete RTCM3 frames by scanning for the 0xD3 preamble and reading the 10-bit payload length (total frame = 3-byte header + payload + 3-byte CRC-24Q). Each fully-formed frame is passed individually to the callback so it can be sized correctly for MAVLink.
MAVLinkHandler.inject_rtcm() (src/modules/mavlink/handler.py:134) then fragments each frame into GPS_RTCM_DATA messages, matching Mission Planner’s exact framing:
- Max 180 bytes of payload per message (
RTCM_MAX_PAYLOAD); frames larger than180 × 6 = 1080bytes are dropped. - The
flagsbyte packs: bit 0 = fragmented flag, bits 1–2 = fragment index (0–3), bits 3–7 = a 5-bit sequence id (0–31, incremented per frame and wrapped mod 32). - Each chunk is zero-padded to a full 180-byte payload; the
lenfield carries the real byte count.
GPS_RTCM_DATA messages. The injected bytes travel udpout:14560 → mavp2p → serial → FCU, and ArduPilot applies them to the connected RTK GPS.
GGA feedback (FCU → caster)
The GGA sender thread (main.py:147) runs once per second:
update_gps_position()reads the latestGLOBAL_POSITION_INT(lat/lon/alt) andGPS_RAW_INT(fix_type) from the FCU.NTRIPClient.send_gga()builds an NMEA$GPGGAsentence from that position with an XOR checksum, but only actually transmits it when there is a fix (gps_fix > 0) and at leastGGA_INTERVALseconds (default 10) have elapsed since the last send.
Configuration
Most configuration is read once at boot intoRTKConfig (src/shared/config.py) and passed through docker-compose.yml; env values in the compose file take precedence over the code defaults. The last two rows below are not RTKConfig fields — STARTDELAY is consumed by entrypoint.sh, and LOG_DIR is a Compose-level host path the Python code never reads.
Stale README defaults.
docker/rtk-ntrip/README.md still documents MAVLINK_CONNECTION | udpin:127.0.0.1:14550 and STARTDELAY 25. The live code uses the env var RTK_MAVLINK_CONNECTION defaulting to udpout:127.0.0.1:14560, and Compose sets STARTDELAY from RTK_STARTDELAY (10). Trust src/shared/config.py and docker-compose.yml over the README table.Interpreting GPS fix types
fix_type comes straight from ArduPilot’s GPS_RAW_INT. The RTK convergence you care about is types 5 and 6:
The same
fix_type surfaces to operators through the Gateway’s telemetry streams (GPS is part of the dashboard stream — see Socket.IO Telemetry Streaming).
Operating the service
- No GPS fix — the service waits up to 60 s then proceeds anyway; check that the RTK GPS is wired to the FCU and reporting via
mavproxy. - NTRIP
200 OKnever appears — bad caster host/port/mountpoint or credentials; verify network egress from the drone toNTRIP_HOST:NTRIP_PORT. GPS_RTCM_DATAflowing but stuck at Float — expected during convergence; needs clear sky view and a few minutes. Confirm GGA is being sent (the caster needs your position to serve local corrections).- Nothing reaches the FCU — confirm
mavp2pstill exposesudps:0.0.0.0:14560and thatRTK_MAVLINK_CONNECTIONpoints at it.
Related
MAVLink Routing (mavp2p)
The single MAVLink hub and its port map (14560 is RTK’s endpoint).
Microservices & Container Profiles
Where rtk-ntrip sits among the SkyCore Compose services and profiles.
e-ArUco Precision Landing
A downstream consumer of accurate positioning for RTL/AUTO auto-docking.

