Skip to main content
GNSS HAT Library Documentation
Shop GitHub Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

RTK Base, Rover & NTRIP Server

Complete RTK setup for centimeter-level positioning: a base station, a rover with an NTRIP client, and a variant that pushes corrections to a remote NTRIP caster instead of serving them locally.

Source: examples/cpp/rtk/, examples/c/rtk/, examples/python/rtk_base.py, examples/python/rtk_rover.py, examples/python/ntrip_server.py

Note
HAT Compatibility — RTK is available only on the L1/L5 RTK HAT (NEO-F9P).
Note
Looking for a ready-to-deploy service instead of example source code? See RTK Base Station — a production gnsshat-rtk-base CLI tool with TOML config, systemd service, and its own NTRIP caster/server, covering the same functionality as the rtk-base/rtk-server examples below without writing any code.

Base Station (Survey-In)

config.rtk = RtkConfig {
    .mode = ERtkMode::Base,
    .base = BaseConfig {
        .mode = BaseConfig::SurveyIn {
            .minimumObservationTime_s = 60,
            .requiredPositionAccuracy_m = 2.0
        }
    }
};
hat_base->start(config);
auto corrections = hat_base->rtk()->base()->getTinyCorrections();
from jimmypaputto import gnsshat

config = {
    'measurement_rate_hz': 1,
    'dynamic_model': gnsshat.DynamicModel.STATIONARY,
    'timepulse_pin_config': {
        'active': True,
        'fixed_pulse': {'frequency': 1, 'pulse_width': 0.1},
        'pulse_when_no_fix': None,
        'polarity': gnsshat.TimepulsePolarity.RISING_EDGE
    },
    'geofencing': None,
    'rtk': {
        'mode': gnsshat.RtkMode.BASE,
        'base': {
            'base_mode': gnsshat.BaseMode.SURVEY_IN,
            'survey_in': {
                'minimum_observation_time_s': 120,
                'required_position_accuracy_m': 50.0
            }
        }
    }
}

hat = gnsshat.GnssHat()
hat.start(config)

rtk_base.py also includes ready-made Fixed Position (LLA and ECEF) config builders (create_fixed_position_lla_config() / create_fixed_position_ecef_config()) — swap which one main() calls to try them instead of Survey-In. --port/--mountpoint select the local NTRIP caster’s bind port and mountpoint.

Rover (NTRIP Client)

The rover connects to an NTRIP caster with the library’s built-in NtripClient, then feeds every received RTCM3 frame into rtk_apply_corrections() in a background thread while the main loop keeps reading navigation:

config.rtk = RtkConfig { .mode = ERtkMode::Rover };
hat_rover->start(config);
hat_rover->rtk()->rover()->applyCorrections(corrections);
from jimmypaputto import gnsshat

config = {
    'measurement_rate_hz': 1,
    'dynamic_model': gnsshat.DynamicModel.STATIONARY,
    'timepulse_pin_config': {
        'active': True,
        'fixed_pulse': {'frequency': 1, 'pulse_width': 0.1},
        'pulse_when_no_fix': None,
        'polarity': gnsshat.TimepulsePolarity.RISING_EDGE
    },
    'geofencing': None,
    'rtk': {'mode': gnsshat.RtkMode.ROVER}
}

hat = gnsshat.GnssHat()
hat.start(config)

ntrip = gnsshat.NtripClient(host, port, mountpoint, username, password)
ntrip.connect()

# Background thread: forward every received frame to the receiver
while running:
    frames = ntrip.receive()
    if frames:
        hat.rtk_apply_corrections(frames)

# Main thread: read fix quality
nav = hat.wait_and_get_fresh_navigation()
# nav.pvt.fix_quality == FixQuality.FIXED_RTK → ~1-2 cm accuracy

Push to a Remote Caster (NTRIP Server)

Instead of accepting incoming rover connections, the base can push its corrections outward to a remote NTRIP caster using NtripServer — useful when the base station is behind NAT/a firewall and can’t accept inbound connections:

NtripServer server(host, port, mountpoint, username, password);
server.setAutoReconnect(true, 1000, 30000);
server.connect();

auto corrections = hat->rtk()->base()->getFullCorrections();
if (!corrections.empty())
    server.feed(corrections);
server = gnsshat.NtripServer(
    host=caster_host, port=caster_port, mountpoint=mountpoint,
    username=username, password=password)
server.set_auto_reconnect(True, 1000, 30000)
server.connect()

while running:
    corrections = hat.rtk_get_full_corrections()
    if corrections:
        server.feed(corrections)

Both RtkServer (C++) and ntrip_server.py (Python) wait for a TimeOnlyFix before pushing corrections and use exponential-backoff auto-reconnect if the connection to the remote caster drops.

Running

# rtk-base and rtk-rover are symlinked into examples/bin/
sudo examples/bin/rtk-base
sudo examples/bin/rtk-rover

# rtk-server is not symlinked — run it from the build tree
sudo build/examples/cpp/rtk/rtk-server <caster_host> <port> <mountpoint> [username] [password]
sudo build/examples/c/rtk/rtk-base
sudo build/examples/c/rtk/rtk-rover

The C example does not include an NTRIP-server (push-to-remote-caster) variant — use the C++ or Python one.

# Base station (runs a local caster the rover connects to directly)
python3 examples/python/rtk_base.py

# Rover — connects to a caster and applies corrections
python3 examples/python/rtk_rover.py --host <caster-host> --port 2101 --mountpoint <mount>

# Push corrections from the base to a remote caster
python3 examples/python/ntrip_server.py --caster-host <host> --caster-port 2101 \
    --mountpoint <mount> --password <pwd>

Fix Quality Progression

  1. GPS 2D/3D — standard fix (~2–5 m)
  2. Float RTK — corrections applied, ambiguities resolving (~0.2–1 m)
  3. Fixed RTK — full centimeter accuracy (~1–2 cm)

See RTK Feature and RTK Config.