ADS-B / Aviation
GopherTrunk decodes Automatic Dependent Surveillance — Broadcast (ADS-B) messages aircraft transponders broadcast on 1090 MHz. ADS-B is the ICAO-mandated cooperative aviation surveillance protocol — every commercial passenger flight, most general- aviation, and all military aircraft over US / EU airspace continuously broadcasts: ICAO 24-bit address, callsign, barometric or GNSS altitude, lat/lon, ground speed, vertical rate, true heading. This is the same data feeding every public flight-tracking service (FlightRadar24, FlightAware, adsb.lol, OpenSky); GopherTrunk now has the protocol layer to decode it end-to-end on the operator’s own SDR.
This page documents the end-to-end pipeline: two ways frames reach the bus — a native PPM DSP frontend on the operator’s own 1090 MHz SDR, and a BEAST upstream that consumes a separately- running dump1090 / readsb — plus what’s persisted, queryable, and rendered on the web panel.
What’s wired
Events bus
events.KindAircraftReport— published once per decoded Mode-S frame off the 1090 MHz channel. Payload is astorage.AircraftReportcarrying the 24-bit ICAO address + message-kind-specific fields.
Storage
- New SQLite table
aircraft_log(append-only migration alongsidevessel_log,dsc_log,aprs_log,pager_log):received_at,icao,icao_hex,kind,body,crc_valid,callsign,category,latitude,longitude,altitude_ft,has_position,has_altitude,ground_speed_kn,track_deg,vertical_rate_fpm,raw_hex- Indexes on
(received_at)and(icao, received_at)
storage.AircraftLogbus subscriber writes one row perKindAircraftReportevent.
REST
GET /api/v1/adsb/aircraft?limit=N— most recent reports, newest first. Default 200, max 5000. ADS-B is the highest- rate decoder (2-3 msg/s per aircraft on a busy channel) so tighter limits make sense for live rendering.
Web panel
/adsb— live list with columns:- Received (HH:MM:SS, daemon clock)
- ICAO (6-char hex, the standard “tail identifier”)
- Kind (ident / airborne-pos / surface-pos / velocity / …)
- Callsign (for identification messages)
- Lat / Lon (for position messages with a successful CPR global decode)
- Alt (ft) (for airborne / surface position)
- GS / Track (for velocity messages, subtype 1 / 2)
- VR (fpm) (vertical rate, signed)
- CRC-failed frames highlight yellow.
Protocol layer (internal/radio/adsb)
Pure-Go Mode-S parser. The bit-stream layer above (the native
PPM receiver or a BEAST upstream) hands it complete Mode-S
frames with the trailing 24-bit CRC included. Both sources go
through one shared path — adsb.ProcessFrame(frame, tracker,
now) decodes, gates on CRC, runs the CPR tracker, and returns
the storage.AircraftReport — so a frame off the air and the
same frame from dump1090 produce identical rows.
- CRC-24 codec (
parse.go) — Mode-S CRC with polynomial 0xFFF409. Verifies DF 11 / 17 / 18 frames directly (zero residue overmessage || crc); for DF 0 / 4 / 5 / 20 / 21 the trailing 24 bits =CRC XOR ICAO, so the parser recovers the ICAO address by XORing the computed CRC. - DF dispatch (
adsb.go) — recognises every documented downlink format; fully decodes DF 17 / 18 extended squitter (the operator-visible majority) and tags the others with the raw payload preserved. - TC dispatch for extended squitter ME payloads:
- TC 1-4: Identification — 8-char callsign decoded from the 6-bit ICAO alphabet (A-Z, space, 0-9, with trailing spaces / underscores stripped).
- TC 5-8: Surface position — CPR-encoded.
- TC 9-18, 20-22: Airborne position — CPR-encoded lat/lon + 12-bit Q-bit altitude (Q=1 = 25 ft resolution).
- TC 19: Airborne velocity — subtypes 1/2 = ground speed + track, subtypes 3/4 = air speed + heading; common vertical-rate field across all subtypes.
- CPR decode (
cpr.go) — globally-unambiguous lat/lon recovery from an even + odd CPR pair (DO-260B §2.2.3.2.3.7). The NL (number of longitude zones) lookup table mirrors the dump1090 reference implementation. The locally-referenced decode (single message against a known receiver location) is the obvious follow-up once the daemon caches an operator-configured reference position.
Validated against the canonical dump1090 / mode-s.org reference samples:
- Identification
8D4840D6202CC371C32CE0576098→ ICAO 4840D6, callsign “KLM1023”. - Airborne-position CPR pair
8D40621D58C382D690C8AC2863A78D40621D58C386435CC412692AD6→ ICAO 40621D, lat 52.2572 N, lon 3.91937 E, alt 38,000 ft.
- Airborne velocity
8D485020994409940838175B284F→ ICAO 485020, GS 159 kn, track ≈ 183°, VR -832 fpm.
Spec references:
- ICAO Annex 10 Volume IV (Aeronautical Telecommunications — Surveillance and Collision Avoidance Systems), Chapter 3 (Mode-S).
- RTCA DO-260B / EUROCAE ED-102A — ADS-B 1090 ES Minimum Operational Performance Standards.
https://mode-s.org/decode/— the de-facto reference parser documentation, cross-checked against real on-the-air payloads.
BEAST upstream (internal/radio/adsb/beast)
GopherTrunk consumes Mode-S frames from any BEAST-protocol upstream — the de-facto wire format dump1090, readsb, BeastSplitter, and every commercial ADS-B hub speak. Operators keep their existing 1090 MHz receive chain (RTL-SDR + filter + LNA + dump1090) and point GopherTrunk at it over TCP. No native 1 Msps PPM demod required.
adsb:
beast_upstreams:
- addr: "127.0.0.1:30005" # dump1090 default BEAST port
name: "local-dump1090"
- addr: "rooftop-pi:30005" # pi-at-the-antenna setup
name: "rooftop"
Frame layout (`0x1A