ADS-B Flight Tracking Platform

Table of Contents

Overview

Personal ADS-B (Automatic Dependent Surveillance-Broadcast) flight tracking platform built on FreeBSD, located 0.8 km from Boston Logan International Airport (KBOS).

  • FlightAware: jwalsh4271
  • Platform Version: 0.2.0
  • Host: hydra (FreeBSD 14.3-RELEASE)

Location

Property Value
Coordinates 42.37°N, 71.04°W
Altitude 29m
Distance to KBOS ~0.8 km
Neighborhood East Boston

Why This Location Matters

Per FlightAware's engineering blog, receivers within 5 miles of airports provide enhanced surface-level tracking. Our location provides:

  • Direct line-of-sight to all KBOS runways
  • Surface movement detection (taxiing aircraft)
  • Low-altitude approach/departure coverage
  • Harbor traffic visibility

Architecture

  hydra (FreeBSD 14.3)                    pi (Debian)
  ├── RTL-SDR Blog V4 + dipole            ├── NESDR SMArt v5 + 1090MHz antenna
  ├── dump1090 :30003                     ├── dump1090-mutability :30003
  ├── adsb-processor (v1.4.0)             └── piaware → FlightAware (Site 264028)
  ├── adsb-server :8081
  ├── adsb-websocket :8083
  └── sbs-logger → /mnt/usb/adsb/raw/

Data Flow

  1. RTL-SDR receives 1090 MHz signals
  2. dump1090 decodes Mode S / ADS-B messages
  3. adsb-processor aggregates from multiple receivers
  4. SQLite database stores aircraft + metadata
  5. adsb-server provides HTTP API
  6. Web UI displays real-time map

Components

Hardware

Component Receiver Location
RTL-SDR Blog V4 + 1090MHz dipole hydra Indoor, window (raised)
NESDR SMArt v5 + magnetic mount pi Indoor, top of bookshelf

Software Stack

Component Version Description
adsb-processor 1.4.0 Multi-receiver message aggregator
sbs-logger 1.1.0 Raw CSV message logger
adsb-server 1.0.0 HTTP API server
adsb-websocket 1.0.0 Real-time WebSocket updates
Database schema v4 SQLite with 30-column metadata

External Integrations

OpenSky Network

  • REST API for aircraft metadata enrichment
  • binCraft binary format for area queries
  • Trino access for historical data (pending application)
# Test OpenSky metadata
curl -s "http://localhost:8081/aircraft-metadata/A0AF06" | jq

FlightAware

  • PiAware feeding from pi.lan (Site 264028)
  • AeroAPI available for commercial queries

ADS-B Exchange

Database Design

Decision: SQLite vs PostgreSQL

Status: Stay with SQLite (ADR-001)

Metric Current PostgreSQL Trigger
Database size 312 KB >10 GB
Message rate 38/sec >1000/sec
Concurrent writers 1 >1

SQLite advantages for this use case:

  • Zero administration
  • Single file deployment
  • Python built-in support
  • WAL mode for concurrent reads

Schema (v4)

CREATE TABLE aircraft (
    icao TEXT PRIMARY KEY,
    callsign TEXT,
    first_seen TEXT,
    last_seen TEXT,
    total_messages INTEGER,
    total_positions INTEGER,
    receivers TEXT
);

CREATE TABLE aircraft_metadata (
    icao TEXT PRIMARY KEY,
    registration TEXT,
    type_code TEXT,
    type_desc TEXT,
    manufacturer_name TEXT,
    operator TEXT,
    owner TEXT,
    -- ... 20+ additional columns
    fetched_at TEXT,
    source TEXT
);

CREATE TABLE callsign_history (
    icao TEXT,
    callsign TEXT,
    first_seen TEXT,
    last_seen TEXT,
    PRIMARY KEY (icao, callsign)
);

Features

Aircraft Swap Detection

Track when flight numbers switch aircraft (e.g., AAL2763 from A321 to B738).

SELECT * FROM flight_aircraft_history WHERE callsign = 'AAL2763';

OpenSky Enrichment

  • Metadata caching (permanent, no TTL)
  • USE_OPENSKY flag for offline operation
  • Rate limiting (2 sec between calls)
  • Retry failed lookups after 24 hours

Real-time Web UI

  • Leaflet map with aircraft icons
  • Selection circles and tracking
  • Altitude color coding
  • External links (ADSBx, FlightAware)
  • Copy aircraft info to clipboard

Route Analysis

Sample routes tracked (KSEA ↔ KBOS):

Direction Primary Route Distance
KSEA→KBOS MONTN2…PONCT JFUND2 2550 sm
KBOS→KSEA HYLND7…MLP GLASR3 2523-2628 sm

Data files:

  • data/routes/KSEA-KBOS-routes.md
  • data/routes/KBOS-KSEA-routes.md
  • data/routes/KSEA-KBOS-routes.mmd (Mermaid diagram)

Visualizations

Generated from 3 days of data (Dec 26-28, 2025). Full gallery at aygp-dr/adsb-reports.

Geographic Coverage

05_geographic_heatmap.png

Figure 1: Position density heatmap showing receiver coverage

Flight Tracks

08_flight_tracks.png

Figure 2: Top aircraft flight paths over 24 hours

KBOS Approach Analysis

11_approach_overview.png

Figure 3: Approach traffic patterns for Boston Logan

Fleet Composition

20_fleet_composition.png

Figure 4: Aircraft types and classes observed

Multi-Receiver Comparison

22_receiver_comparison.png

Figure 5: Coverage overlap between hydra and pi receivers

Daily Traffic Comparison

24_daily_comparison.png

Figure 6: Message volume across Dec 26-28

Statistics

Daily performance (typical):

Metric Value
Aircraft tracked 700-900
Messages processed 1.5-2M
Message rate 35-40/sec
Metadata cache 800+ aircraft
Database size ~300 KB

References

Technical Documentation

Project Files

  • hydra.toml - Platform configuration (IaC)
  • VERSION - Platform version
  • docs/adr/ADR-001-sqlite-vs-postgresql.md - Database decision
  • docs/CHANGELOG-0.2.0.md - Release notes

Future Work

Planned

  • Playwright UI testing (hydra-setup-2nd)
  • FlightAware AeroAPI integration (hydra-setup-bn1)
  • Apache Baremaps custom aviation maps (hydra-setup-hdc)
  • Route frequency analysis

Potential ML Projects

  • Callsign anomaly detection
  • Flight path prediction
  • Aircraft type classification from signatures
  • Delay pattern analysis

Local Commands

# Check system status
pgrep -f dump1090 && pgrep -f adsb-processor && echo "Running"

# Database stats
sqlite3 /mnt/usb/adsb/adsb.db "SELECT COUNT(*) FROM aircraft"

# Recent aircraft
sqlite3 /mnt/usb/adsb/adsb.db \
  "SELECT icao, callsign, altitude FROM aircraft ORDER BY last_seen DESC LIMIT 10"

# OpenSky enrichment test
curl -s "http://localhost:8081/opensky-bincraft?box=41.5,43.0,-72.0,-69.5" | \
  python3 -c "import sys,json; print(len(json.load(sys.stdin).get('aircraft',[])))"

Testing Procedures

Antenna Baseline Testing

Quick baseline for message rate and aircraft count:

# Message rate (10-second sample)
timeout 10 nc localhost 30003 | wc -l

# Aircraft count from dump1090
curl -s http://localhost:8080/data.json | jq '. | length'

# Pi receiver check
ssh pi.lan 'curl -s http://localhost:8504/data/aircraft.json | \
  jq "{aircraft: (.aircraft | length), with_position: ([.aircraft[] | select(.lat)] | length)}"'

Multi-Receiver Comparison

Capture from both receivers simultaneously and compare coverage:

# Capture 15 seconds from both
timeout 15 nc localhost 30003 > /tmp/hydra_msgs.txt &
timeout 15 nc pi.lan 30003 > /tmp/pi_msgs.txt &
wait

# Compare message counts
echo "Hydra: $(wc -l < /tmp/hydra_msgs.txt) messages"
echo "Pi:    $(wc -l < /tmp/pi_msgs.txt) messages"

# Extract unique ICAOs and diff
cut -d, -f5 /tmp/hydra_msgs.txt | sort -u > /tmp/hydra_icao.txt
cut -d, -f5 /tmp/pi_msgs.txt | sort -u > /tmp/pi_icao.txt

# What each receiver sees exclusively
comm -23 /tmp/hydra_icao.txt /tmp/pi_icao.txt  # Hydra only
comm -13 /tmp/hydra_icao.txt /tmp/pi_icao.txt  # Pi only
comm -12 /tmp/hydra_icao.txt /tmp/pi_icao.txt  # Both

Antenna Swap Procedure

When swapping antennas:

  1. Run quick baseline (pre-swap)
  2. Swap antenna (USB may disconnect)
  3. Check service status: systemctl status dump1090-mutability
  4. Restart if needed: systemctl restart dump1090-mutability
  5. Wait 30 seconds for stabilization
  6. Run new baseline (post-swap)

Critical: USB disconnect causes dump1090 to lose device handle even though service stays running. Always restart after physical antenna changes.

Hourly Comparison Test

For extended A/B testing during antenna swaps:

# Start hourly test (samples every 60 seconds for 1 hour)
cat > /tmp/receiver-comparison.sh << 'EOF'
#!/usr/bin/env bash
LOG=logs/receiver-comparison-hourly.log
echo "Time           H-msgs     H-ac   P-msgs     P-ac" > $LOG
for i in $(seq 1 60); do
  timeout 10 nc localhost 30003 > /tmp/h.txt &
  timeout 10 nc pi.lan 30003 > /tmp/p.txt &
  wait
  printf "%-12s %8s %8s %8s %8s\n" "$(date +%H:%M:%S)" \
    "$(wc -l < /tmp/h.txt)" "$(cut -d, -f5 /tmp/h.txt | sort -u | wc -l)" \
    "$(wc -l < /tmp/p.txt)" "$(cut -d, -f5 /tmp/p.txt | sort -u | wc -l)" >> $LOG
  sleep 50
done
EOF
nohup bash /tmp/receiver-comparison.sh &

Performance Benchmarks

Observed results from 2025-12-28 testing:

Antenna Config Location Msgs/10sec Aircraft
978MHz Indoor ~12 3-5
Rabbit ears Indoor ~34 3-4
1090MHz dipole Window ~30 4
1090MHz dipole Window (raised) ~150 4-6
Magnetic mount Bookshelf ~440 7-9

Key findings:

  • Elevation matters more than antenna tuning
  • Proper 1090MHz tuning provides more consistent reception
  • Bookshelf placement (pi) outperforms window placement (hydra) by ~3x

Changelog

Date Version Changes
2025-12-28 0.2.0 OpenSky integration, metadata expansion, callsign history
2025-12-27 0.1.x Multi-receiver support, basic tracking
2025-12-26 0.1.0 Initial setup

Author: Jason Walsh

jwalsh@nexus

Last Updated: 2026-01-02 12:29:02

build: 2026-01-11 18:31 | sha: eb805a8