RTCM relay service for custom GPS correction servers
Project description
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.
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,106 passing tests
- ๐งฉ Embeddable Library (v2.1):
RelayEngineAPI 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
Installation
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)
โ โโโ 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
โ โโโ 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/ # 942 tests, 88% coverage
โ โโโ unit/ # Unit tests (26 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
git clone https://github.com/rodenj1/sp-rtk-base-relay.git
cd sp-rtk-base-relay
uv sync --all-extras
source .venv/bin/activate
# Run tests
uv run pytest
# Run with coverage
uv run pytest --cov=src/sp_rtk_base_relay --cov-report=html
Code Quality Standards
- Python 3.10+ with modern type hints (
dict,list,X | None) - PEP8 code style, pyright strict mode
-
88% test coverage (942 tests)
- UV package management
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
- Release Process: PyPI release workflow + runbook
- Relay Engine API Spec: Full v2.1 programmatic API reference
- v2.1 Architecture Plan: Embeddable relay engine design
- v2.0 Architecture Plan: Multi-destination design with 7 DRs
- Configuration Reference: Complete config guide (YAML + programmatic)
- Deployment Guide: Installation and systemd setup
- Metrics Guide: Prometheus metrics reference
- Bluetooth Recovery: Self-healing Bluetooth GPS
License
MIT License โ see LICENSE.
Project Status
Current Version: 2.1.0
- โ 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,106 tests, 88% coverage, production-stable
Made with โค๏ธ for the RTK GPS community
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sp_rtk_base_relay-2.1.0.tar.gz.
File metadata
- Download URL: sp_rtk_base_relay-2.1.0.tar.gz
- Upload date:
- Size: 87.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8dc3f6695675fe16fe19e6b964344940683715f3dded90d3623d18e0288127d6
|
|
| MD5 |
416b41f334cc374a2446880531ca9276
|
|
| BLAKE2b-256 |
f14268b26ea2ebb476ba74e78d321afdcc168178981b50fac4c71a9cfa255363
|
Provenance
The following attestation bundles were made for sp_rtk_base_relay-2.1.0.tar.gz:
Publisher:
release.yml on rodenj1/sp-rtk-base-relay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sp_rtk_base_relay-2.1.0.tar.gz -
Subject digest:
8dc3f6695675fe16fe19e6b964344940683715f3dded90d3623d18e0288127d6 - Sigstore transparency entry: 1586325163
- Sigstore integration time:
-
Permalink:
rodenj1/sp-rtk-base-relay@87f94e180c7c38a496c3f236587a6dc73f4e4d62 -
Branch / Tag:
refs/tags/v2.1.0 - Owner: https://github.com/rodenj1
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@87f94e180c7c38a496c3f236587a6dc73f4e4d62 -
Trigger Event:
release
-
Statement type:
File details
Details for the file sp_rtk_base_relay-2.1.0-py3-none-any.whl.
File metadata
- Download URL: sp_rtk_base_relay-2.1.0-py3-none-any.whl
- Upload date:
- Size: 105.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b39375e1701b8d180bb246d53a4c19d4ba6c73529c2c772343f6d0143265d27f
|
|
| MD5 |
e845b6fe8a0614a6a37460d7f43b5c45
|
|
| BLAKE2b-256 |
e45af501cc524315f1af6f43a7ae666030f45811f22fc6d85c40f15911fca4ef
|
Provenance
The following attestation bundles were made for sp_rtk_base_relay-2.1.0-py3-none-any.whl:
Publisher:
release.yml on rodenj1/sp-rtk-base-relay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sp_rtk_base_relay-2.1.0-py3-none-any.whl -
Subject digest:
b39375e1701b8d180bb246d53a4c19d4ba6c73529c2c772343f6d0143265d27f - Sigstore transparency entry: 1586325209
- Sigstore integration time:
-
Permalink:
rodenj1/sp-rtk-base-relay@87f94e180c7c38a496c3f236587a6dc73f4e4d62 -
Branch / Tag:
refs/tags/v2.1.0 - Owner: https://github.com/rodenj1
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@87f94e180c7c38a496c3f236587a6dc73f4e4d62 -
Trigger Event:
release
-
Statement type: