Skip to content

blindp3w/readsbstats

Repository files navigation

readsbstats

Tests Release License: MIT

Extended flight history and track logging for readsb ADS-B receivers, with a web UI for browsing historical flights and statistics.

Designed to run alongside readsb, tar1090, and feed clients on a Raspberry Pi 4 without overwhelming it.

readsb and tar1090 give you a great live view — readsbstats adds the other half: a persistent SQLite history of every flight, a full-featured browser UI for exploring it, Telegram notifications for military and watchlist aircraft, and a receiver health dashboard backed by months of metrics.

🤖 About this project: readsbstats was entirely vibecoded with Claude Code. Every line of code, test, and configuration in this repository was generated by AI under the maintainer's direction — not a single line was written by hand.

Contents

Screenshots

Live map Aircraft gallery
Live map — Live / Rewind / HIST modes, bottom command bar, position heatmap, coverage overlay Gallery — military, interesting, and anonymous (non-ICAO hex) aircraft with photos
Statistics Flight details
Statistics — summary tiles, hourly activity chart, DOW×hour heatmap, top-N datasets Flight detail — photo, route, altitude/speed chart, full position log
Receiver metrics Polar range and top statistics
Metrics — 9 health checks (baseline-aware) above 10 time-series charts (ECharts, LTTB) Statistics — polar coverage range plot and top aircraft types, airlines, routes

Features

  • Collects aircraft positions every 5 seconds from readsb's aircraft.json
  • Groups positions into flights (30-minute silence gap = new flight); tracks ADS-B vs MLAT per position
  • Stores everything in SQLite — no extra database server
  • Aircraft enrichment: registration, type, operator from tar1090-db (~620k aircraft)
  • Aircraft photos: Planespotters.net → airport-data.com → hexdb.io → Wikipedia type fallback; cached 30 days
  • Military, interesting & anonymous (non-ICAO hex) aircraft detection, badges, and gallery
  • Route enrichment via adsbdb.com — origin/destination airport per flight
  • Live map with Live / Rewind / HIST modes — rewind from "now" or jump to any moment within map_history_hours via a date+time picker; bottom command bar with playback scrubber, range pills, position heatmap, and coverage range overlay; responsive down to iPhone
  • Telegram notifications for military/interesting/anonymous/watchlist/emergency-squawk events; interactive bot commands
  • Aircraft watchlist — track by ICAO hex, registration, or callsign prefix
  • Receiver metrics dashboard with 10 time-series charts (Apache ECharts canvas, cross-panel hover sync, wheel/pinch zoom, LTTB downsampling) and 9 health checks
  • Statistics dashboard — receiver-wide aggregates (top aircraft/airlines/routes/airports, hourly & daily activity, DOW×hour heatmap, polar coverage range). Defaults to the last 7 days for fast loads; the all-time view is background-warmed so it stays snappy
  • Heatmap and coverage overlays served from collector-maintained daily rollup tables — millisecond responses at any history size, and the all-time views survive raw-position retention
  • Optional VDL2 / ACARS integration — ingest VHF Data Link Mode 2 traffic from a separate SDR via vdlm2dec: a Messages tab (live feed, filters, full-text search, human-readable label names) plus an ACARS panel on flight pages with OOOI block times (Out/Off/On/In from Q-series reports) and route confirmation, a "has ACARS" badge/filter in history, and a Stats card. Fully pluggable (RSBS_VDL2_ENABLED), stored in its own database (see Operations)
    • Human-readable decoded ACARS message bodies in the Messages log (client-side @airframes/acars-decoder, lazy-loaded; raw body always retained as fallback)
    • #M1BPOS precise positions added to the map overlay; filed routes (departure/arrival/company route/SID/STAR/approach) surfaced on the message log
    • Each message row is tagged with a category chip (ACMS / engine / maintenance / route / …) so the H1-dominated feed is readable at a glance; RTE route messages also surface their filed departure→arrival
    • Selectable decoder (RSBS_VDL2_DECODER): vdlm2dec (ACARS) or dumpvdl2, which additionally decodes ATN CPDLC controller–pilot messages and reports per-frame signal level, surfaced as per-channel signal (dBFS) and SNR (dB) charts on the Metrics page (the charts self-hide on a vdlm2dec feed, which has no signal field)
  • Unit switching: Aeronautical / Metric / Imperial — persisted in browser
  • SQLite crash-safety (WAL + synchronous=NORMAL, RSBS_DB_SYNCHRONOUS=FULL for per-commit durability) with dirty-shutdown detection (fail-closed on corruption — see Operations) and weekly/monthly integrity checks via systemd timers

Requirements

  • Raspberry Pi 4 (or any Linux machine) running Ubuntu 22.04 / 24.04
  • readsb by wiedehopf writing JSON to /run/readsb/
  • Python 3.10+
  • nginx (for the /stats/ reverse proxy)

Installation

# 1. Sync source to the Pi (from your Mac/PC)
rsync -avz --delete \
  --exclude='.git' --exclude='__pycache__' --exclude='*.pyc' \
  --exclude='.venv' --exclude='*.egg-info' \
  --exclude='docs' --exclude='db' --exclude='.DS_Store' \
  --exclude='*.db' --exclude='*.db-wal' --exclude='*.db-shm' \
  --exclude='frontend/node_modules' --exclude='frontend/.vite' --exclude='frontend/coverage' \
  --exclude='internal_docs' --exclude='.claude' --exclude='CLAUDE.md' \
  /path/to/readsbstats/ pi@your-pi:/tmp/readsbstats/

# 2. SSH into the Pi and run the installer as root
ssh pi@your-pi
sudo bash /tmp/readsbstats/scripts/install.sh

The installer creates a readsbstats system user, sets up /opt/readsbstats/ with a Python virtualenv, creates the SQLite database, and installs systemd services.

After installation, set your receiver coordinates:

systemctl edit readsbstats-collector readsbstats-web
[Service]
Environment="RSBS_LAT=YOUR_LATITUDE"
Environment="RSBS_LON=YOUR_LONGITUDE"

Then restart services and open http://YOUR_PI_IP/stats/.

nginx setup

Add one line to your nginx server {} block:

server {
    include /opt/readsbstats/nginx-readsbstats.conf;
}

The conf file proxies /stats/ to uvicorn at 127.0.0.1:8080 and serves the SPA's hashed asset bundles directly with long-cache headers.

Security model

readsbstats has no built-in authentication or authorization by default. It is designed to run bound to 127.0.0.1:8080 behind nginx on a trusted LAN. Anyone who can reach the web port can read all flight data and call every mutating endpoint (watchlist edits, settings).

  • The X-Requested-With CSRF header is not authentication. It only blocks cross-site form posts from a browser; it does not identify or authorize a caller. Do not treat it as an access control.
  • If you expose the UI beyond a trusted LAN, put an authenticating reverse proxy in front (HTTP basic auth, an OAuth2 proxy, or a VPN/Tailscale). Do not publish 127.0.0.1:8080 directly to the internet.
  • RSBS_API_TOKEN — opt-in bearer-token gate for every mutating (POST/DELETE) /api/* endpoint (watchlist, settings, …). When set, every such call must carry Authorization: Bearer <token>. When unset (the default), the web server logs a one-line startup warning so the open posture is never silent. Read endpoints stay open (they were already public on the trusted LAN). Useful as a thin extra layer if the app is reachable from devices you don't fully trust on the LAN; not a substitute for a reverse-proxy auth layer when exposed to the internet.
  • Outbound HTTP (photo/route enrichment) is SSRF-guarded (http_safe.py) and provider photo URLs are host-allowlisted before caching (RSBS_PHOTO_HOST_ENFORCE) and at the API response boundary.

See Operations → Deployment security for the full trust model.

Resource usage

Service CPU quota Memory limit
collector 15% 192 MB
web server 50% 1024 MB

Database size: plateaus at ~0.6 GB at steady state with 180-day position retention (RSBS_RETENTION_DAYS=180; recommended), since the schema-v6 scaled-integer position storage. Without retention the DB grows ~20–60 MB/month unbounded.

Project structure

readsbstats/
├── src/readsbstats/            # Python package
│   ├── collector.py            # Polling daemon, flight detection, writes
│   ├── web.py                  # FastAPI app factory + lifespan + SPA-shell routes
│   ├── cache.py                # Response cache + map-cache prewarmer thread
│   ├── api/                    # Per-domain APIRouter modules
│   │   ├── _deps.py            # DB connection seam, shared SQL/allowlists, _csrf_check
│   │   ├── _photos.py          # Photo-fetch ladder + per-type async locks
│   │   ├── flights.py          # /api/flights*, /api/flights/{id}/*
│   │   ├── aircraft.py         # /api/aircraft/*, /api/airlines/*, /api/types/*
│   │   ├── stats.py            # /api/stats, /api/stats/records, /api/stats/polar
│   │   ├── map.py              # /api/map/*, /api/live
│   │   ├── feeders.py          # /api/feeders + systemd/port checkers
│   │   ├── settings.py         # /api/settings + _settings_* helpers
│   │   ├── watchlist.py        # /api/watchlist GET/POST/DELETE
│   │   ├── airspace.py         # /api/airspace
│   │   ├── health.py           # /api/health, /api/metrics, /api/metrics/health
│   │   ├── dates.py            # /api/dates
│   │   └── vdl2.py             # /api/vdl2/* (opt-in; reads the separate vdl2.db)
│   ├── database.py             # SQLite schema, WAL, migrations
│   ├── config.py               # RSBS_* env vars
│   ├── http_safe.py            # SSRF-safe HTTP helpers (HTTPS-only)
│   ├── photo_sources.py        # Planespotters → airport-data → hexdb → Wikipedia
│   ├── notifier.py             # Telegram notifications
│   ├── rollups.py              # Daily heatmap/coverage rollups (collector-maintained)
│   ├── posenc.py               # Scaled-integer codecs for positions columns (schema v6)
│   ├── health.py               # Receiver health checks
│   ├── geo.py                  # haversine_nm, bearing
│   └── vdl2/                   # Opt-in VDL2/ACARS ingest (separate vdl2.db)
├── scripts/
│   ├── install.sh              # First-time installer
│   ├── update.sh               # Code sync + restart + optional DB update
│   ├── purge_ghosts.py         # One-shot: remove ghost positions
│   ├── purge_bad_gs.py         # One-shot: null implausible gs values
│   └── purge_mlat_gs_spikes.py # One-shot: null MLAT gs spikes
├── frontend/                   # React 19 + Vite 8 SPA (440 Vitest tests)
├── tests/                      # pytest (2106 tests) + Playwright UI (84 tests)
├── static/airspace/            # Bundled airspace GeoJSON
├── systemd/                    # Service + timer unit files
└── docs/                       # Public documentation
    ├── configuration.md        # All RSBS_* environment variables
    ├── api.md                  # API endpoints + database schema
    ├── integrations.md         # Telegram setup + ghost/GS filtering
    ├── operations.md           # Updating, maintenance, useful commands
    ├── development.md          # Local dev, testing, build, deploy
    ├── decisions/              # Architecture Decision Records
    └── screenshots/            # UI screenshots

Documentation

Full guides live in docs/, grouped by reference / how-to / explanation:

Guide Contents
Configuration All 83 RSBS_* env vars, logging, airspace config
API Reference All API endpoints, SPA routes, database schema
Integrations Telegram setup, bot commands, ghost/GS filtering
Operations Updating code, DB sync, useful commands, backups
Development Local setup, tests, build, deploy to Pi
Architecture decisions ADRs for key technical choices

Contributing

Bug reports and pull requests are welcome — see CONTRIBUTING.md for setup and style guidelines. This project follows the Code of Conduct.

License

MIT — see LICENSE.

The bundled frontend (frontend/dist/) includes Apache ECharts (Apache-2.0) and other open-source libraries. Required attribution lives in THIRD_PARTY_NOTICES.md.

About

ADS-B flight history & web UI for readsb

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors