SatTrack: One Python Daemon, a Whole Satellite Ground Station
This is SatTrack — an open-source ground-station automation stack that pulls TLEs, predicts passes with Skyfield, tunes an RTL-SDR only while a target is above the horizon, decodes the right signal for each satellite (APT weather, Meteor LRPT, ISS APRS/SSTV, FUNcube telemetry, and more), logs SNR and Doppler to SQLite, and serves a live 3D dashboard. One python run.py replaces the usual Orbitron + cron + manual receiver workflow. The production instance is live at sattracker.luciuswayne.com.
What it solves
Running a satellite ground station used to mean juggling half a dozen tools: TLE fetchers, pass predictors like Orbitron, manual frequency tuning, separate decoders for NOAA APT vs Meteor LRPT vs amateur packet, and maybe Grafana if you wanted historical telemetry. Each pass was a small ops exercise — and if you also wanted ADS-B aircraft tracking on the same RTL-SDR dongle, you were constantly fighting for the radio.
SatTrack is the glue that ties it together. It sits on top of two excellent open-source SDR projects — Kismet for continuous ADS-B aircraft tracking and SatDump for live satellite capture and decode — and orchestrates them so one dongle can do both, automatically.
What It Does
Point SatTrack at your coordinates, define a fleet of NORAD IDs and downlink frequencies, and the daemon handles the rest:
- Predict acquisition of signal (AOS), max elevation, and loss of signal (LOS) for every target in your registry.
- Capture RF only during the pass window — no wasted SDR time staring at empty sky.
- Decode with the right backend per satellite: SatDump live pipelines, direwolf for APRS, gr-satellites for amateur telemetry, pysstv for ISS SSTV, and more.
- Log pass quality, SNR, and Doppler curves to SQLite for SSA-style trending.
- Visualize everything on a built-in FastAPI web dashboard — 3D globe, next-pass reticle, live phase (waiting / recording / decoding), and Kismet aircraft overlay.
No RTL-SDR plugged in? It runs in dry-run mode automatically — full prediction and scheduling, capture stubbed. Same codebase deploys unchanged from a Windows dev laptop to a Kali box with a dongle attached.
The Pipeline
Six modules run in sequence — from fresh TLEs to a finished capture on disk:
- TLE ingestion —
sattrack/tle.pypulls GP/TLE from CelesTrak for your NORAD IDs, caches by content hash, and falls back offline when the network is down. - Registry —
sattrack/registry.pyjoins those TLEs with your curated frequencies and decoders into one authoritative{name: {tle, freq}}map. - Prediction —
sattrack/predict.pyuses Skyfield to compute AOS, max elevation, LOS, and a per-pass Doppler curve for every target. - Capture + decode —
sattrack/capture.pyruns event-driven, AOS→LOS only. Default backend issatdump_live; fallback isrtl_fm→ sox → aptdec. - Telemetry —
sattrack/telemetry.pywrites passes, captures, SNR, Doppler, and quality scores to SQLite. - Daemon —
sattrack/watcher.pyis the sleep-until-AOS loop that ties it all together.
While the daemon runs, it prints a heartbeat every few seconds so you always know what’s next:
18:50:02 INFO sattrack.watcher: [waiting] next: NOAA 19 | AOS in 12:48 | max el 47° | 137.1000 MHz | 3 pass(es) queued
19:02:55 INFO sattrack.watcher: [recording] NOAA 19 | el(max) 47° | 137.1000 MHz | elapsed 02:01 | ends in 06:12
Try It Live
The dashboard below is the production instance at Lucius & Wayne — predicted orbits, pass schedule, and (when the daemon is running) live capture status. Open it full-screen if the embed is cramped.
Quick Start
Plug in an RTL-SDR on Linux, then from the repo root:
bash install.sh
source .venv/bin/activate
python run.py doctor
python run.py
The installer walks you through a setup wizard — station name, coordinates, satellite fleet, Kismet/ADS-B toggles, gain/PPM — and writes config.json. No manual JSON editing required. Re-run anytime with python3 scripts/configure_wizard.py.
Key CLI commands:
python run.py passes # next 24h of passes (no capture)
python run.py doppler "NOAA 19" # Doppler curve for its next pass
python run.py stats # telemetry summary
python run.py serve # web dashboard on :8082
On Windows or without an SDR, pip install -r requirements.txt and python run.py doctor gets you prediction + dashboard dev mode.
Decoders for Every Target
Each satellite’s decoder field picks how RF becomes data. Six backends cover most of what you’ll actually hear on a VHF/UHF whip:
satdump— SatDump live pipeline. Meteor LRPT imagery, CubeSat telemetry, HRPT, LRIT.noaa_apt— SatDump’s NOAA APT path. Classic weather-fax passes from NOAA 15/18/19.aprs— NBFM into direwolf for AX.25 decode. ISS digipeater at 145.825 MHz, packet sats.sstv— NBFM audio into pysstv. ISS Slow Scan Television events at 145.800 MHz.gr_satellites— FM audio or raw IQ into gr-satellites. AO-91 DUV, AO-73/FUNcube BPSK, and 280+ amateur birds.fm— NBFM audio archive only, no auto-decode. SO-50 voice, beacons, anything you want to pull into a WAV yourself.
Add any bird by NORAD ID + downlink frequency. Toggle "enabled": false to park one until you need it (e.g. ISS SSTV between scheduled events).
Sharing One RTL-SDR with Kismet
install.sh configures Kismet with an rtladsb source on your dongle. SatTrack stops Kismet before each satellite pass and starts it again afterward — one dongle does ADS-B between passes and satellite capture during passes:
release_command → settle → capture → reacquire_command
A watchdog keeps Kismet up whenever the daemon isn’t recording. Decode runs after the SDR is handed back, so offline decoders never block ADS-B recovery. Skip Kismet entirely with bash install.sh --skip-kismet if you want a dedicated satellite-only rig.
Telemetry & Pass Quality
Everything lands in SQLite (passes, captures, doppler). Point Grafana at it, or use the built-in dashboard. compute_pass_score() blends peak elevation (60%), dwell time (40%), and measured SNR into a 0–100 figure of merit — useful for SSA-style trending: which passes were worth staying up for, and whether your antenna changes actually helped.
Production routing: Cloudflare Tunnel → 127.0.0.1:8082. Run the watcher in another terminal (or as satwatch.service via systemd) so the dashboard reflects live phase and capture progress via status.json heartbeats.
Honest Scope
SatTrack decodes public RF only — amateur/ISS downlinks, weather imagery, beacons. No classified telemetry.
A few practical limits worth knowing upfront:
- UHF birds (e.g. SO-50 @ 436 MHz) want a directional or turnstile antenna; low passes on a whip will mostly be noise.
- Single RTL-SDR → overlapping passes are handled earliest-AOS-first; the loser is skipped and logged.
fmtargets are recorded and archived without auto-decode — that’s intentional for voice/beacons.- Continuous terrestrial targets (APRS igates, radiosondes) aren’t wired into the pass scheduler yet — they need a second SDR or a dedicated always-on decoder.
What Made It Work
Event-driven capture. Tuning the SDR only during AOS→LOS means the daemon can coexist with Kismet and doesn’t burn CPU decoding empty spectrum.
Decoder registry. One config file maps NORAD ID → frequency → decoder → pipeline params. Adding Meteor-M2 3 is a single JSON entry, not a new script.
Dry-run parity. Windows devs get the same prediction, scheduling, and dashboard code paths as the Linux production box — no #ifdef platform branching.
Content-addressed TLE cache. CelesTrak outages don’t stop the scheduler; stale TLEs beat no TLEs, and the hash lets you know when they refresh.
Final Thoughts
SatTrack started as a way to stop babysitting passes manually. It grew into a small SSA stack: predict, capture, decode, score, visualize — with ADS-B running in the gaps. The repo is MIT-licensed and builds on Kismet and SatDump without bundling their source; install.sh pulls what your distro needs.
If you’ve got an RTL-SDR in a drawer and have always meant to decode a NOAA pass or hear the ISS digipeater, this is the shortest path I’ve found from dongle to dashboard.