Solo Build1 person2 weeks

Overhead

A Raspberry Pi–powered LED matrix that listens to live aircraft transponders and narrates what's flying over our flat — in the voice of David Attenborough.

Raspberry PiRTL-SDRPythonClaude APIHardware

The idea

My girlfriend loves planes. Every time we're out and about I'll notice her looking at her phone, then up at the sky. And this can only mean one thing — she's on Flight Radar, looking at what's going overhead. It's an adorable special interest, and something I've started taking more interest in myself.

I wanted to make her something. A way to see what's flying over our flat, at a glance, the moment it happens, without reaching for her phone. Back in university I'd built a spectrum analyser on a 64x64 Adafruit LED matrix — 4,096 tiny LEDs, each one capable of any colour, about the size of your palm. So the seed of the idea was there: could I get plane data onto one of these boards?

My first attempt was just a Mac app hooked up to a free API that sent a push notification whenever a plane was nearby. It worked. It was also really annoying. But it proved the concept, and it made me think — what if instead of borrowing someone else's data over the internet, we could listen to the planes ourselves?

Every commercial aircraft constantly broadcasts its position, altitude, speed, and heading via radio transponder. A USB receiver called an RTL-SDR dongle, paired with a 1090MHz antenna, can pick all of it up. Real signals, from real planes, received live from a flat in North London. I had no idea if this would actually be feasible. Turns out it was shockingly easy.

I ordered everything — a Raspberry Pi 5, the LED matrix, the antenna, a bonnet board to wire it all together — and spent the week before it arrived building the software pipeline on my Mac against a simulated version of the matrix. The moment hardware started showing up, I was locked in.

The RTL-SDR V4 dongle — a USB radio receiver that picks up aircraft transponder signals
The Adafruit Matrix Bonnet — bridges the Pi's GPIO pins to the LED matrix
The 1090MHz antenna on a mini tripod on a desk
Raspberry Pi 5 with the RTL-SDR dongle plugged into a USB port
The 64x64 LED matrix face-up on a desk, powered off
The kit. RTL-SDR dongle, Adafruit bonnet, 1090MHz antenna, Raspberry Pi 5, and 64x64 LED matrix.

Getting it working

The first few days were pure problem-solving. The Pi wouldn't connect to Wi-Fi because macOS had silently put garbage on the clipboard instead of my actual password — the Pi accepted it without complaint and failed silently. The LED matrix library didn't support the Pi 5's chip architecture, so I had to swap it out and write a translation layer. The matrix and Pi kept crashing because they were sharing a power supply and bright pixels drew too much current. Each problem felt like a wall until it didn't. Reflash the card. Switch the library. Separate the power supplies.

The back of the LED matrix with the Raspberry Pi wired up via ribbon cable and the RTL-SDR dongle attached
The guts. Pi, bonnet, SDR dongle, and ribbon cable — all wired to the back of the matrix.

Within a couple of days, the antenna was decoding live aircraft signals, the Pi was processing them, and the matrix was lighting up. The pipeline worked. But what it was showing was ugly.

The LED matrix showing early flight data in a crude yellow font — callsign, altitude, speed
First real data on the board. It worked. It was also hideous.

Making it feel right

Getting data onto a screen is an engineering problem. Making it feel like something you'd actually want to look at is a design problem. And it's much harder.

The raw transponder signal gives you almost nothing useful — a hex code, a cryptic callsign like "BAW226", altitude in feet, speed in knots. No airline name, no aircraft type, no route. So I built layers of translation: callsign prefixes mapped to airlines, type codes decoded into aircraft names, and flight databases cross-referenced for origin and destination. Even then there was a London-specific problem — "going to London" is useless when you live here. Which airport? That needed its own lookup.

I laid it all out on the board like a departures board — callsign, carrier, route, altitude, speed. Rows and columns, crossfades between pages. It worked. It also felt like a spreadsheet with animations.

The departures board approach. Structured, animated, and ultimately the wrong solution.

The fonts were bothering me too. The defaults looked terrible at this size — inconsistent widths, poor spacing, no personality. I happened to be playing Pokemon Fire Red on the Switch, and noticed how lovely the in-game font was — designed for the Game Boy Advance's tiny 240x160 screen, every pixel carefully considered for legibility at low resolution. I got a TrueType version rendering on the matrix with anti-aliased edges. On a normal screen you wouldn't notice, but on an LED matrix where every pixel is physically visible, those soft edges are transformative. Suddenly the board had a visual identity.

Side-by-side comparison of the default LED library font versus the Pokémon-inspired anti-aliased font
Before and after. The default LED library font on the left, the Pokémon Fire Red–inspired font on the right — with anti-aliased edges and enriched data.

But the layout was still the problem. And then it clicked: what if I stopped trying to arrange data and started writing sentences?

The matrix displaying a natural-language sentence about an overhead flight, typed out character by character
Instead of structured data, the board types out sentences. Colour-coded highlights pick out airlines, airports, and aircraft types.

The personality

With the typewriter working, the question became: what should it sound like?

The first personality I tried was Matty Matheson — loud, unhinged chef energy. "HOLY CRAP a British Airways A350 just TORE out of Heathrow!" It was funny for about a day, then it got exhausting. Every sentence was at an 11. The structure got repetitive.

So I switched to David Attenborough. And everything fell into place. Planes became wildlife. Routes became migration paths. Airlines became species. A 747 is "a noble elder, increasingly rare in these parts." A Ryanair 737 is "the hardy sparrow, thriving where others dare not venture." An A380 is "the apex predator of these skies." Same data, completely different character.

Three different Attenborough-style narrations showing varied tone and language for different aircraft
Three sightings, three different tones. The LLM adapts its narration to the airline, aircraft, and route.

When a new plane enters range, the board doesn't just update — it does a full-screen colour splash inspired by Pokemon wild encounters. The screen fills with the airline's brand colour and a word like "EYES UP" or "SPOTTED" appears knocked out in black. There are five animation styles that cycle randomly. It turns each sighting into an event — a moment that makes you look up.

The five encounter animation styles — scan wipe, vertical blinds, horizontal split, spiral, and strobe
Five encounter styles cycle randomly. In production, each airline gets its own brand colour.
A complete sighting cycle — encounter splash, typewriter narration, and fade back to idle
The full cycle. Encounter splash, Attenborough narration, then calm.

The board remembers what it's seen, too. Every aircraft gets logged in a local database. When a repeat visitor shows up, the LLM knows and reacts warmly. First-timers get introduced. At set times through the day it shows wrap-up summaries — a morning report, an afternoon update, an evening wind-down — each narrated by Attenborough against the real stats.

Wrap-up summaries from different times of day — morning, afternoon, evening, and night
Periodic summaries throughout the day. Morning report, afternoon update, evening wind-down, late-night whispers.

After 10pm, the personality shifts. He's still there, but whispering. Conspiratorial. Cargo flights become "nocturnal hunters." It's funny because nobody's watching.

Side-by-side comparison of daytime and nighttime encounter styles and narration tone
Day vs. night. After 10pm the tone shifts — quieter colours, conspiratorial language, nocturnal hunters.

Two Claudes

One workflow detail worth calling out: I had Claude running on both my Mac and on the Pi simultaneously. Mac Claude was the architect — designing systems, writing the renderer, managing code. Pi Claude was the field engineer — testing against real hardware, introspecting live APIs, catching things that only break on ARM. When one hit a wall, I'd relay the problem to the other. When Mac Claude pushed a fix, Pi Claude would pull and test. I was the bridge between two AI instances troubleshooting the same problem from different angles. It cut what would've been hours of manual hardware debugging into minutes, and it became a genuinely interesting creative rhythm.

Pi Claude and Mac Claude — two AI instances working the same problem from different machines
Two Claudes. One on the Pi with the antenna, one on the Mac with the nice screen.

Building the frame

The software was done. Now it needed to stop looking like a science project.

The original plan was a handmade oak enclosure — mitre joints, Danish oil, the works. But I wanted something precise, and I wanted it quickly. So I went parametric. I designed the frame in OpenSCAD, writing the CAD model through Claude Code the same way I'd write any other code — iterating on dimensions, tolerances, and assembly logic in a text editor, previewing renders, and fixing problems as they came up.

OpenSCAD render of the T-side piece — outer face showing countersunk screw holes and filleted base
OpenSCAD render of the T-side piece — inner face showing the acrylic groove and matrix retaining lip
OpenSCAD render of the top bar with countersunk antenna bolt hole
OpenSCAD render of the bottom bar showing acrylic and diffuser grooves
The four frame pieces, designed in OpenSCAD. Two T-sides own the corners; top and bottom bars fit between them.

The design is a 4-piece T-frame — two side pieces with built-in feet, and top and bottom bars that slot between them. The sides own the rounded corners. Eight M3 screws hold it all together. Inside, two parallel grooves run the full perimeter — the front groove holds a 3mm smoked acrylic panel, the rear groove holds a white opal diffuser. The matrix panel sits behind both, held in place by retaining lips. An M5 bolt through the top bar provides a clean mount point for the antenna.

I sent the STL files to JLCPCB's 3D printing service. Four pieces, SLA 9600 resin, matte white — intended as a prototype before ordering the final version in black. About £60 all-in, with shipping costing more than the parts themselves. They arrived about a week later, and the first surprise was the weight. SLA resin is dense — the pieces felt like ceramic, not plastic. Substantial in a way that made the whole thing feel more like a real object than a prototype.

The four 3D-printed frame pieces laid out — two T-sides, top bar, and bottom bar in matte white resin
The four pieces as they arrived from JLCPCB. SLA resin, matte white — heavier than expected, more ceramic than plastic.

Assembly was mostly straightforward — the acrylic and diffuser slid into their grooves, and the screws pulled the corners tight. One snag: the retaining lips that hold the matrix were about 4mm too long where the pieces met, causing overlap at the joints. But the resin filed down easily, and after a few minutes with a hand file everything sat flush. The 0.4mm-per-side tolerance on the main opening worked perfectly — the matrix dropped in snug, no forcing.

The matte white was never supposed to be the final colour. But once I saw the black M3 screw heads against the white resin — eight tiny black dots at the corners — I loved the contrast too much to reorder. The prototype became the product.


The result

The finished Overhead unit in daylight — matte white resin frame with black M3 screws visible at each corner, smoked acrylic face, antenna on top
The finished Overhead unit at night — smoked acrylic glowing with the clock display, warm desk lighting
Day and night. The black M3 screws against matte white resin were an accident — the prototype was supposed to be reordered in black.

The smoked acrylic does exactly what I hoped — it hides the 4,096 individual LEDs when they're off, so the face reads as a single dark panel. When pixels light up, the colour passes through cleanly, and the white opal diffuser behind softens each dot into a glow. The dipole antenna screws directly into the top bar via the M5 bolt, and a single USB-C cable runs out the back for power. Everything else — the Pi, the bonnet, the PSU, the SDR dongle, all the wiring — is hidden inside.

It weighs more than it looks. People pick it up expecting plastic and get something closer to a ceramic bookend. That weight, plus the matte finish and the visible screw hardware, gives it a presence that a 3D print has no right having. It doesn't feel like a prototype. It feels like a product.

The resting state — a minimal clock face with a tiny pixel plane orbiting the perimeter
The resting state. Time, date, temperature — and a tiny pixel plane circling the perimeter every thirty seconds.

Laura loves it. It hasn't curbed her time on Flight Radar — she now uses it as a supplementary tool. A heads-up that something's going over the flat, before she reaches for her phone to find out more.