Skip to main content

SP-RTK-Base-Relay

A Python service that relays RTCM correction data from RTK GPS base stations to multiple destinations simultaneously — Sure-Path servers, NTRIP casters, and local TCP clients.

CI Release codecov PyPI version Python versions PyPI Downloads License: MIT Ruff Conventional Commits

Overview

SP-Base-Relay v2.1 is a production-ready multi-destination RTCM relay and embeddable Python library. A single GPS input source (TCP, serial, or Bluetooth) feeds correction data to any combination of destinations — proprietary Sure-Path servers, NTRIP v1.0/v2.0 casters (RTK2go, Onocoy, rtkdirect), and local TCP rebroadcast servers.

Key Features

  • 🔀 Multi-Destination Broadcast: Fan-out RTCM data to 1–N destinations simultaneously
  • 🌐 NTRIP v1.0 + v2.0: Push corrections to any NTRIP caster (RTK2go, Onocoy, rtkdirect)
  • 📡 TCP Rebroadcast Server: Serve corrections to LAN rovers via TCP
  • 🔐 Sure-Path Protocol: Custom INIT:user:pass* auth with $HB$ heartbeat monitoring
  • 🔌 Multiple Input Sources: TCP (RTKBase), Serial UART, USB Serial, Bluetooth GPS
  • 🎯 Per-Destination Filtering: Pass-all, allowlist, or blocklist RTCM message types per destination
  • 📊 Per-Destination Prometheus Metrics: Individual throughput, errors, queue depth per destination
  • 🔄 Automatic Recovery: Exponential backoff reconnection per destination — independent fault isolation
  • 🔧 Self-Healing Bluetooth: Automatic Bluetooth GPS recovery without manual intervention
  • 🛡️ Production Ready: Systemd integration, structured logging, comprehensive error handling
  • 🧪 Well Tested: 88% code coverage with 1,145 passing tests
  • 🧩 Embeddable Library (v2.1): RelayEngine API for programmatic control by external applications
  • 📢 Real-Time Events (v2.1): EventBus with typed events for state change notifications
  • 🔌 Hot Plug Destinations (v2.1): Add/remove/start/stop destinations while running

Architecture

                              ┌──▶ [SurePath Thread] ──▶ Sure-Path Server
[Input Source] ──▶ [BroadcastHub] ─┤──▶ [NTRIP Thread]   ──▶ RTK2go / Onocoy / etc.
  TCP / Serial       Fan-out    │──▶ [NTRIP Thread]   ──▶ rtkdirect
  / Bluetooth       + Filter    └──▶ [TCP Srv Thread]  ──▶ LAN Rover Clients
                                        │
                                [Prometheus Metrics] ◄──── per-destination labels

Each destination runs in its own thread with an independent queue, so a failure in one destination never affects the others.

Quick Start

Install as a library

For embedding the RelayEngine API in another Python application (e.g., a GPS configuration UI):

uv add sp-rtk-base-relay        # or: pip install sp-rtk-base-relay

See Embedded Usage below for the programmatic API.

Install as a service

For running the standalone systemd-managed relay on a Linux host:

git clone https://github.com/rodenj1/sp-rtk-base-relay.git
cd sp-rtk-base-relay
sudo ./tools/install.sh

Configuration

Edit /etc/sp-rtk-base-relay/config.yaml (or see config.example.yaml):

input:
  source: "tcp"
  config:
    host: "192.168.1.100"
    port: 3000

destinations:
  - name: surepath
    type: surepath
    enabled: true
    filter:
      mode: pass_all
    config:
      host: "server.example.com"
      port: 50010
      username: "USER01"
      password: "abc1"

  - name: rtk2go
    type: ntrip
    enabled: true
    filter:
      mode: pass_all
    config:
      caster: "rtk2go.com"
      port: 2101
      mountpoint: "MY_MOUNT"
      password: "my_password"
      version: "2.0"

  - name: local_tcp
    type: tcp_server
    enabled: false
    filter:
      mode: pass_all
    config:
      host: "0.0.0.0"
      port: 5016
      max_clients: 10

metrics:
  enabled: true
  port: 8080

logging:
  level: "INFO"
  format: "json"

Running

# As a systemd service
sudo systemctl start sp-rtk-base-relay

# Or foreground
sp-rtk-base-relay --config config.yaml --foreground

Destination Types

Sure-Path (surepath)

Custom proprietary protocol with INIT:user:pass* authentication and $HB$ heartbeat monitoring. Wraps the battle-tested v1.x RTCMClient.

NTRIP (ntrip)

Pushes RTCM corrections to NTRIP casters. Supports both protocol versions:

  • v2.0 (default): HTTP POST + Basic auth + chunked transfer encoding
  • v1.0: SOURCE auth + raw binary streaming

Tested against RTK2go, Onocoy, and rtkdirect.

TCP Server (tcp_server)

Local TCP rebroadcast server for LAN clients. Multiple rovers can connect and receive corrections simultaneously. Features max_clients enforcement and per-client write timeout for backpressure handling.

Message Filtering

Each destination can independently filter RTCM messages by type ID:

filter:
  mode: pass_all          # Forward everything (zero overhead)

filter:
  mode: allowlist         # Only forward these message types
  message_ids: [1005, 1077, 1087, 1097, 1127]

filter:
  mode: blocklist         # Forward everything except these
  message_ids: [4072]     # Drop proprietary messages

Monitoring

Prometheus Metrics

Per-destination metrics with {destination="..."} labels:

Metric Type Description
sp_rtk_base_relay_dest_bytes_sent_total Counter Bytes sent per destination
sp_rtk_base_relay_dest_messages_sent_total Counter Messages sent per destination
sp_rtk_base_relay_dest_messages_dropped_total Counter Queue overflow drops per destination
sp_rtk_base_relay_dest_connection_status Gauge Connection state (1/0) per destination
sp_rtk_base_relay_dest_errors_total Counter Errors per destination
sp_rtk_base_relay_dest_queue_depth Gauge Queue depth per destination
sp_rtk_base_relay_input_connection_status Gauge Input source connection state
sp_rtk_base_relay_input_seconds_since_last_data Gauge No-data watchdog
sp_rtk_base_relay_tcp_server_connected_clients Gauge TCP server client count
sp_rtk_base_relay_service_uptime_seconds Gauge Service uptime

Grafana Dashboard

Import templates/grafana_dashboard.json for a pre-built v2 dashboard with per-destination panels, throughput graphs, and the no-data watchdog.

Input Sources

Source Use Case Config Key
tcp RTKBase integration, network base stations host, port, timeout
serial Direct GNSS receiver via UART port, baudrate
usb_serial USB-to-serial adapters port, baudrate
bluetooth Bluetooth GPS devices device_address, channel

Project Structure

sp-rtk-base-relay/
├── src/sp_rtk_base_relay/
│   ├── main.py                      # Service orchestration (v2)
│   ├── engine.py                    # RelayEngine — v2.1 embeddable facade
│   ├── config.py                    # YAML config with destinations: list
│   ├── metrics.py                   # Per-destination Prometheus metrics
│   ├── exceptions.py                # DestinationError, NtripError, etc.
│   ├── logger.py                    # Structured logging
│   ├── rtcm_decoder.py             # RTCM 3.x frame parser
│   └── core/
│       ├── broadcast_hub.py         # Fan-out coordinator (input → N queues)
│       ├── message_filter.py        # Per-destination RTCM filtering
│       ├── rtcm_client.py           # Sure-Path protocol client
│       ├── connection_states.py     # Connection state machine
│       ├── bluetooth_manager.py     # Bluetooth GPS recovery
│       ├── events.py                # EventBus + EventSubscription (v2.1)
│       ├── status.py                # Typed status snapshots (v2.1)
│       ├── destinations/
│       │   ├── base_destination.py       # ABC + queue + stats
│       │   ├── destination_factory.py    # Registry-based factory
│       │   ├── surepath_destination.py   # Sure-Path server
│       │   ├── ntrip_destination.py      # NTRIP v1.0 + v2.0
│       │   └── tcp_server_destination.py # Async TCP rebroadcast
│       └── input_sources/
│           ├── base_input.py        # Input ABC
│           ├── tcp_input.py         # TCP input
│           ├── serial_input.py      # Serial input
│           └── bluetooth_input.py   # Bluetooth input
├── tests/                           # 1,145 tests, 88% coverage
│   ├── unit/                        # Unit tests (30 test files)
│   ├── integration/                 # Hardware integration tests
│   ├── manual/                      # Manual production tests
│   └── fixtures/                    # Mock servers and generators
├── tools/                           # Install/uninstall, systemd, bluetooth
├── templates/                       # Grafana dashboard v2
├── docs/                            # Documentation
│   ├── v2-architecture-plan.md      # Full architecture + design decisions
│   ├── deployment-guide.md          # Installation & deployment
│   ├── metrics-guide.md             # Prometheus metrics reference
│   └── bluetooth-*.md               # Bluetooth GPS guides
└── config.example.yaml              # v2 configuration template

Development

# Clone and install dev environment + pre-commit hooks (one shot)
git clone https://github.com/rodenj1/sp-rtk-base-relay.git
cd sp-rtk-base-relay
./tools/install-dev.sh

# Run tests
uv run pytest

# Run with coverage
uv run pytest --cov=src/sp_rtk_base_relay --cov-report=html

# Run every pre-commit hook against every file
uv run pre-commit run --all-files

The tools/install-dev.sh script syncs the uv-managed venv with --all-extras and installs the project's pre-commit hooks (pre-commit, commit-msg, and pre-push stages).

Code Quality Standards

  • Python 3.10+ with modern type hints (dict, list, X | None)
  • PEP8 code style, pyright strict mode
  • 88% test coverage (1,145 tests)

  • UV package management
  • Pre-commit hooks block secrets (gitleaks), enforce lint/format (ruff), validate commit messages (Conventional Commits 1.0.0 via commitizen), and run mypy + pyright + pytest on push.

Contributing

See CONTRIBUTING.md for the full workflow — pre-commit pipeline, commit-message format, allowed types/scopes, and release process.

CLI Usage

sp-rtk-base-relay [OPTIONS]

Options:
  --version                Show version and exit
  -c, --config PATH        Configuration file path
  --validate               Validate configuration and exit
  --generate-config        Generate example configuration
  --foreground             Run in foreground
  --log-level LEVEL        Override log level

Embedded Usage (v2.1)

SP-Base-Relay can be used as a Python library by external applications (e.g., GPS configuration UIs). The RelayEngine facade provides full programmatic control:

from sp_rtk_base_relay import RelayEngine
from sp_rtk_base_relay.config import InputConfig, DestinationConfig

# 1. Create engine with input source
engine = RelayEngine(InputConfig(source="tcp", config={"host": "192.168.1.100", "port": 3000}))

# 2. Start with destinations
engine.start([
    DestinationConfig(name="rtk2go", type="ntrip", enabled=True,
                      config={"caster": "rtk2go.com", "port": 2101,
                              "mountpoint": "MY_MOUNT", "password": "pass"}),
])

# 3. Hot-add a destination while running
engine.add_destination(DestinationConfig(
    name="local_tcp", type="tcp_server", enabled=True,
    config={"host": "0.0.0.0", "port": 5016}
))

# 4. Get typed status snapshot
status = engine.get_status()
print(f"Running: {status.is_running}, Destinations: {len(status.destinations)}")

# 5. Subscribe to real-time events
sub = engine.subscribe_events()
event = sub.get_event(timeout=1.0)
if event:
    print(f"Event: {event.event_type} — {event.message}")
sub.close()

# 6. Stop when done (releases serial port for other tools)
engine.stop()

Exported API

Symbol Description
RelayEngine High-level facade — start/stop/manage relay
EventBus Pub/sub event system
EventSubscription Per-subscriber event queue (iterable)
RelayEvent Typed event (event_type, message, timestamp, payload)
RelayStatus Frozen status snapshot
DestinationStatus Per-destination status
InputStatus Input source status

See Relay Engine API Spec for the full technical specification.

Migration from v1.x

v2.0 is a breaking change. Key differences:

v1.x v2.0
server: config key destinations: list
Single destination 1–N destinations
Global metrics Per-destination {destination="..."} labels
DataPipelineCoordinator BroadcastHub + DestinationFactory
v1 Grafana dashboard v2 dashboard with $destination variable

See config.example.yaml for the new format. Old server: configs are detected with a clear migration error message.

Releasing

Releases to PyPI are fully automated via .github/workflows/release.yml. Bump the version in pyproject.toml, push a vX.Y.Z tag, then publish a GitHub Release — the workflow re-runs the full test matrix, builds, and publishes to PyPI via Trusted Publishing (OIDC, no API tokens). See docs/release-process.md for the per-release checklist and the one-time PyPI / GitHub-environment setup.

Documentation

License

MIT License — see LICENSE.

Project Status

Current Version: 2.1.3

  • ✅ Multi-destination broadcast (Sure-Path, NTRIP v1.0/v2.0, TCP server)
  • ✅ Per-destination message filtering and Prometheus metrics
  • ✅ BroadcastHub fan-out architecture with independent fault isolation
  • ✅ Embeddable RelayEngine API with EventBus and typed status (v2.1)
  • ✅ 1,145 tests, 88% coverage, production-stable

Made with ❤️ for the RTK GPS community

Metadata

Release files for sp-rtk-base-relay 3.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sp-rtk-base-relay 3.2.0
File Size Uploaded
sp_rtk_base_relay-3.2.0.tar.gz 105.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sp-rtk-base-relay 3.2.0
File Interpreter ABI Platform
sp_rtk_base_relay-3.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 224.3 kB

Release files / sp_rtk_base_relay-3.2.0.tar.gz

Download URL sp_rtk_base_relay-3.2.0.tar.gz
Size 105.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d1adce1478a374bc229643c02016d8514ceb4dd25774d476be921aba9889fb65
BLAKE2b-256 checksum
How to use checksums
240d5415331c6d427b44d19befa26bd0a596b774dcb69efa7622f7593954a643
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / sp_rtk_base_relay-3.2.0-py3-none-any.whl

Download URL sp_rtk_base_relay-3.2.0-py3-none-any.whl
Size 118.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
66a05d0c4eaa6963c21fb0c25db5799871fcba9be69a85db35ac20f9664a6da1
BLAKE2b-256 checksum
How to use checksums
60706feababc461104bbade8a11aa7ac765ac48016180259d89c98928777822c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release history Release notifications | RSS feed

4.1.0

2 release files

4.0.0

2 release files

This release

3.2.0 This release

2 release files

3.1.1

2 release files

3.1.0

2 release files

3.0.0

2 release files

2.1.3

2 release files

2.1.2

2 release files

2.1.1

2 release files

2.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page