A pure-Go ADS-B stack
I rewrote the ADS-B receive chain in Go as four separate layers, to learn how it works and to do it my way

I like Go, I like radio, and I like aviation. ADS-B sits exactly where those three meet.
Every aircraft overhead is continuously broadcasting who it is, where it is, how high and how fast, in the clear, on 1090 MHz. Structured telemetry, published by the plane itself, is free to anyone with thirty euros of receiver . It just tickles my brain.
There has also been a trend of very nice hardware built on ADS-B lately. Skylight puts a Raspberry Pi 5, an RTL-SDR and a short-throw projector together to paint the traffic overhead onto the ceiling , comet trails and all. ESP32 Plane Radar does a sonar-style sweep on a 1.28 inch round display , and has since been cloned onto AliExpress as a desk gadget.
I initially started uAirwaves a few years ago, pointed at a BEAST feed on my network. But seeing those inspiring projects is what pushed me to make my own stack. I wanted to know how ADS-B reception actually works layer by layer. The result is four repositories, all pure Go, each one a library that comes with its own tooling for (visual) learning.
I did at one point drive to an airport to test, because from my roof every aircraft is already in the air, so the receiver had never once heard one sitting on the ground. On the ground there is no altitude field at all, and in the air it can be metric, or Gray-coded on a scheme from the 1950s. Which is how I learned that altitude is not always a number. Sometimes it is a string.
Four layers
rtl2832u
drives the USB dongle. It
speaks in USB to the RTL2832U demodulator and the R820T2 / R860 tuner, and produces
raw IQ samples. The rtl-probe tool opens the tuner, reports what the front end
is doing, and captures IQ to a file. This can help you tweak your antenna setup.

demod1090 turns IQ into frames. Magnitude, preamble detection, bit decisions, CRC-24 validation. Its TUI shows what survived: 1284 frames out of 1322 preambles here, a 97 % yield, the downlink-format mix, and the frames scrolling past with their magnitudes.

modes
turns frames into meaning.
Message types, callsigns, altitudes, velocities, and position through Compact
Position Reporting. It implements ICAO Annex 10 Volume IV and RTCA DO-260B, has
no TUI, but its modes-decode reads hex frames on stdin.
uAirwaves draws the radar and owns everything stateful: per-aircraft tracking, GPS, self-locate, and the coverage picture.

The seams between the layers are formats rather than function calls. IQ files between driver and demodulator, Beast over TCP between demodulator and decoder. I can capture at any seam, replay it, and get the same decode every time, which is also how the stack tests itself. beastmux lives on the Beast seam and merges several receivers into one deduplicated feed.
Why not fork dump1090
I wanted to learn how this works, and forking for me skips the part worth learning.
dump1090 and readsb are C as well, so starting there would have meant carrying
libusb and CGo into everything built on top. Plus my C isn’t any good.
dump1090 was published in January 2013, and readsb is four forks downstream of it: Malcolm Robb, then dump1090-mutability, then dump1090-fa, then Mictronics, then wiedehopf. Thirteen years and five maintainers.
It also comes with thirteen years of decisions that have stopped reading as
decisions. readsb for example scans preambles coarsely and skips
multi-phase decode, which was correct for the hardware it was written for. That
hardware is several generations old now, and the CPU it was rationing is no
longer scarce.
I did run a direct comparison, anecdotally, of course. Two identical Pi 4s, each with their own dongle and the same antenna model, on one roof, recording and then replaying the same 90-second captures through both so neither was listening to a different minute of sky.
Averaged over four captures, demod1090 resolved 30 unique aircraft against readsb’s 27,
roughly 12 % more. demod1090 spent 63 seconds of wall time doing it against readsb’s
3. It buys the extra aircraft with CPU. Since each capture holds 90 seconds of
signal, taking 63 seconds to work through one means it would still keep up
with a live receiver, with about a third of the time to spare.
The only ’legacy’ choice I made is transliterating the gain tables from osmocom/rtl-sdr with attribution in the source.
No CGo
rtl2832u speaks Linux usbfs directly through golang.org/x/sys/unix ioctls. No
librtlsdr, no libusb, no CGo anywhere in the stack.
Every tool is therefore one statically linked file. Stripped, for arm64:
modes-decode is 1.8 MB, rtl-probe 4.1 MB, demod1090 4.9 MB, uAirwaves
4.9 MB. Copying one onto a machine is the entire install. Nothing to
apt-get, no shared library version to match, no drift between the box it was
built on and the box it runs on. That is the part I care about: a single file and it runs.
There is one more thing it does not need on the target. These dongles are
repurposed DVB-T television receivers, so Linux binds dvb_usb_rtl28xxu to them
the moment they are plugged in and holds the device; the usual first step with
any RTL-SDR is to blacklist that module in /etc/modprobe.d and reboot before
anything can open it. rtl2832u detaches the kernel driver over usbfs when it
opens the device, so there is no blacklist to write and no reboot to sit
through.
In the field
On the uConsole with the HackerGadgets AIO board it is a real handheld: SDR and GNSS already on the board, a small active 1090Mhz antenna on the socket, and the dongle’s bias-tee feeding that antenna for the best ‘view’.
uAirwaves demodulates and decodes everything locally, and it works
out where it is from gpsd (if present) and failing that, from triangulation of knowing where the airplanes are.
That’s possible because the radio antenna’s field is basically a large inverted conical shape with the antenna itself at the bottom. The airplanes do carry GPS on board and knowing their location, we can roughly estimate our own.
It is, of course, also able to read BEAST TCP streams and also able to deduce the antenna’s location from its data.
How I built it
I wrote most of uAirwaves myself.
The three layers under it were built LLM-assisted, against strict rules: 100 % line coverage,
golangci-lint with default: all and revive at enable-all-rules, no
third-party dependencies in the decoder, and every register write in the driver
cited to the RTL2832U or R860 datasheet.
Nothing counted as finished until it had run on hardware. Every layer has pulled real aircraft off a real antenna, and the replay corpus exists so that a regression shows up as a decode that changed rather than a test that turned red in isolation.
Feedback
I’m open to feedback and improvements on these four projects, feel free to reach on Github !