RAVEN·RF // BUILD WALKTHROUGH ARCHITECTURE CURRENT STATE RESUME ↗
portable SDR signal-intelligence device · build walkthrough

How I built Raven RF

Raven RF is a portable signal-intelligence device I built to teach myself the radio spectrum from the antenna up. It runs on a Raspberry Pi with its own screen and battery, and turns a software-defined radio into a set of tools — one for each band I wanted to understand. This is how it came together: where it started, the turns it took, the ideas that didn't survive contact with reality, and what I learned versus what I leaned on AI to build.

HW HackRF Pro · RTL-SDR V4 DSP custom · no external decode libs DEPLOY Pi 5 · Kali ARM
00 · WHERE IT STARTED

The attic feeder

My first project was the attic feeder. I wanted to understand aircraft signals, so I bought two overkill 9 dBi antennas — one for 1090 MHz, one for 978 — connected each to its own SDR, and fed both into a single Raspberry Pi running FlightAware. Being close to DFW and Alliance, it pulled in millions of messages past 250 nautical miles and reached top 500 in the world for 1090, 978, and MLAT.

But I was running someone else's decoder and only seeing the result. I wanted to understand the full chain myself — from the raw signal to the aircraft on the map — and that's what pushed me into everything that came next.

PHOTO
FlightAware feeder stats — top 500 worldwide, 250+ nm range
PHOTO
attic install — 9 dBi 1090 / 978 MHz verticals
01 · THE FIRST DETOUR

A real-time 3D sky

From there I wanted to get deeper into object-oriented programming and become a better developer, so I built an environment in Unreal Engine 5 that showed the aircraft overhead in real-time 3D space, each one rendered with its type and flight info. Behind it I planned a middleware application that piped the message data in — so it wasn't just my feed; other people could pipe in their own messages and see the same thing.

I set it aside to chase something broader and more ambitious. It did what I needed, though: it made me a stronger developer, and that carried into everything after it.

REC · LIVE
Unreal Engine 5 — overhead aircraft in real-time 3D
02 · THE PIVOT

From a plane tracker to a whole device

With more exposure to the Aero Cyber Range I wanted into the cyber world — DEFCON, hands-on offensive work, OSCP prep — while also learning how aircraft transmit and receive and the frequency bands we use to communicate. What really pulled me was the spirit of DEFCON and the cyberdeck scene: standalone, offline, do-it-yourself RF devices like the Flipper Zero, the PortaPack H2, and Hak5's WiFi Pineapple. I wanted something in that spirit but with more capability and a heavier RF focus. That set the constraints — offline, self-contained, its own screen and power — and it's why this was never going to be a desktop app.

03 · IDENTITY

Giving it a body and a name

Once the direction was set, I wanted the device to have a real identity before I filled it with capabilities. I only had a few shots at a good print on my brother's 3D printer, so I made the first one count: I bought calipers, measured every component as precisely as I could, and used AI to turn those measurements and my chosen orientations into an STL, which I refined in Fusion before printing.

The logo carries the meaning. Two ravens face each other — Huginn and Muninn, thought and memory — the Norse idea of sensing the world and remembering it, which is what a SIGINT device does. They're mirrored on purpose, for reflection, and as a nod to the digital-twin work I still want to finish. Lightning runs through it for electromagnetic energy and for the F-35. The runes across the top read listen and learn in Younger Futhark, sigint sits at the bottom, and it all wears the same purple as the UI.

PHOTO
the printed housing — Pi 5, 7″ screen, HackRF, purple raven identity
04 · THE SPECTRUM PLAN

One radio, split into bands to learn it

Here's the actual method behind the whole device. The HackRF Pro covers roughly 100 kHz to 6 GHz, and instead of learning that range in the abstract, I split it into bands and built a working capability for each one. Every tab is a band I taught myself — the modulation it uses, how to demodulate it, and what's transmitting there. The bar below is that plan; it stays pinned as you scroll, so you can see where each capability sits in the spectrum.

VHF118–137M
SAT137M
MIL-UHF225–400M
ISM390/915M
ADS-B978/1090M
CELL0.7–2.7G
BLE/WiFi2.4–5G
118–137 MHz VHF air + weather

Voice on the air band

The first band past the aircraft transponders was voice. I built an AM/NBFM receiver for the VHF air band and local services — Alliance tower on 118.700, DFW approach, NOAA weather on 162.400 — and the decoder also handles P25, DMR, and APRS. This is where I actually learned the audio chain: pull a 12.5 kHz channel out of a 2 MHz capture, decimate it down in stages, run it through an FM discriminator, and push it to the speaker.

FIG. 0 — IQ samples resolving into symbols as the receiver locks on
Problem · RF front-end
Every NBFM demod had a spike at the center frequency.

The audio tabs kept showing a hard spike dead center. It was the HackRF's DC offset sitting exactly on the tuned frequency. The fix was to tune 250 kHz off-target and shift the signal back into place in IQ, so the offset lands outside the channel I'm actually listening to.

REC · LIVE
VHF / COMMS tab — live air-band audio
137 MHz weather satellites

Pulling images out of the sky

Just above the air band, the NOAA weather satellites transmit APT imagery around 137 MHz as they pass overhead. The satellite tab predicts passes with SGP4, and when I arm a pass it captures the downlink to IQ for the few minutes the bird is in view, then decodes it into a live image. It's one of the most satisfying things the device does — point it at the sky on a schedule and get a picture back.

REC · LIVE
Satellite tab — armed NOAA pass → decoded image
225–400 MHz military UHF

Military UHF, right off the JRB

Living near NAS Fort Worth JRB, the 225–400 MHz military UHF band is busy. I added an AM decoder for it with presets for the base tower and known allocations, and awareness of the modes that live there — HAVE QUICK frequency hopping, SINCGARS, and UHF SATCOM. Most of it isn't meant to be pretty; the point was covering the band and understanding what's transmitting in it.

REC · LIVE
MIL-UHF tab — 225–400 MHz presets
390 / 915 MHz sub-GHz ISM

Sub-GHz — and the first time I transmitted

The sub-GHz ISM bands are where the everyday devices live: garage remotes on 390 MHz, LoRa and Meshtastic nodes and sensors around 915. This band is also where I first used the HackRF to transmit instead of just listen — replaying my own garage door's code, which uses a De Bruijn rolling-code sequence. Receiving teaches you a band; transmitting into it, on my own hardware, is a different level of understanding the modulation.

REC · LIVE
Sub-GHz tab — 390 MHz OOK capture + TX
978 / 1090 MHz aircraft · the Airspace tab

Back to aircraft — the whole chain, mine this time

This is the band the project started on, and the one I went deepest into. The HackRF decodes Mode S on 1090 MHz directly; the RTL-SDR with an LNA handles UAT on 978. Positions arrive noisy and irregular, so every track runs through an IMM (Interacting Multiple Model) Kalman filter — constant-velocity, turn, and acceleration models weighted in parallel — which keeps a track stable through both cruise and hard maneuvers, and lets me draw 30- and 60-second extrapolations of where each aircraft is headed. On top of my own receiver, I pull in the 200+ aircraft from my networked feed, so the map shows far more than one antenna could.

FIG. 1 — raw fixes vs IMM-filtered track

This is also where I stopped using other people's decode libraries and wrote my own — Reed–Solomon over GF(2⁸) with Berlekamp–Massey, a Mode S decoder with CPR position recovery, and ctypes bindings straight to libhackrf and librtlsdr. It was the hardest part of the project and the part I learned the most from.

dsp/reed_solomon.pypython
# GF(2^8) syndrome decode — no external libraries
def correct(self, data: bytes) -> bytes:
    syn = self._syndromes(data)
    if not any(syn):
        return data                   # frame already clean
    loc = self._berlekamp_massey(syn)  # error locator
    pos = self._chien_search(loc)      # find positions
    return self._forney(data, syn, pos)  # correct in place
Problem · error correction
My Reed–Solomon was mangling the frames it was supposed to repair.

Clean frames decoded fine, but any frame carrying a real bit error came out worse than it went in. The bug was in the Forney step — it computed the error magnitude with the wrong exponent and corrected the wrong symbol. It stayed invisible until I pushed a known-good test vector through and compared byte for byte against a reference. One wrong exponent, hidden until the data was actually broken.

Problem · threading
The UI froze under load, and it wasn't the decoder.

My first assumption was that decoding was too slow. A freeze watchdog I wrote showed the real cause: a 10,000-row SQLite query running on the main thread. I capped the query and moved the decode loop into its own process to get it off the GIL. Measuring before fixing saved me from optimizing the wrong thing.

REC · LIVE
Airspace — TRACKS with IMM extrapolation + networked aircraft
0.7–2.7 GHz cellular survey

Sweeping the cellular bands

Higher up, I added a cellular survey that sweeps 700–2700 MHz with hackrf_sweep and lays it out as a waterfall, centered on LTE Band 3 downlink around 1842.5 MHz. This one is survey, not decode — the goal was seeing the shape of the cellular environment and where the energy is, not demodulating it.

REC · LIVE
Cellular tab — 700–2700 MHz sweep waterfall
2.4 / 5 GHz Bluetooth · Wi-Fi · drones

The crowded top of the band

At 2.4 GHz everything is packed together, so this is several tools at once. BLE Hunter maps Bluetooth LE devices nearby and — on my own hardware — runs attacks: deauth, advertising floods, connection and clone modes. The Wi-Fi tab puts an ALFA adapter in monitor mode, and its WPA side finds networks and works to recover the password on my own network, which is where a lot of my OSCP-track, Linux-command practice actually happened. Drone detection sits up here too, watching for the control and video links common RF drones use.

REC · LIVE
BLE Hunter — device map + attack grid
REC · LIVE
Wi-Fi / WPA tab — monitor mode + handshake capture
05 · ARCHITECTURE

One DataBus holding it together

By the time there were this many bands, each its own tab, they couldn't all talk to the hardware directly. Everything runs through a single DataBus: the SDR workers and decoders publish, the views subscribe. Hardware never touches the UI. Adding a band means subscribing to a signal, not rewiring the app — that's the decision that kept it maintainable as the tab count grew.

FIG. 2 · System architecture
One DataBus between hardware and UI
SDR Workers
HackRF · RTL-SDR · multiprocessing
Custom DSP
Reed–Solomon · Mode S · CPR
BLE / Wi-Fi Capture
bleak · scapy
NRX Classifier
Conv1D + Transformer · ZMQ
DATABUS
adsb_frame_decoded
aircraft_updated
signal_classified
pub / sub · Qt signals
Airspace · RAW/SIG/TRACKS
native PyQt5 + IMM Kalman
Radar / TSD
rectangular tactical display
BLE Hunter
device attack grid
OSINT Globe
CesiumJS · WebSocket
Producers publish, views subscribe. Hardware never touches the UI directly — so adding a band means subscribing to a signal, not rewiring the app. This is the decision that kept the tab count maintainable.
06 · SYSTEM

Knowing the whole rig is alive

With this much hardware on one Pi — HackRF, RTL-SDR, BLE, Wi-Fi, GPS — I needed to see the state of everything at a glance. The system tab shows every device live and connected, and a preflight check runs before launch like a full system test: it verifies the radios, the decoders, the config, and the environment before the app even starts. The GPS module ties it together, placing me on the same map as everything I'm receiving.

PHOTO
hardware stack — HackRF Pro on the Pi (1090), RTL-SDR V4 + LNA on 978, BLE, Geekworm X1202 UPS, GPS
Problem · data propagation
The radar tracked aircraft, but three sub-tabs stayed empty — and my fix didn't fix them.

Aircraft plotted on the radar while the SIGNALS, TRACKS, and RAW tabs showed nothing. I traced the path one hop at a time and found the break: the decoder runs in its own process, and that worker was never putting per-frame items on the queue the UI reads. I patched it — and the tabs were still empty. The code was correct; it just wasn't the code running. The file kept landing in the wrong folder, so every launch reloaded the old version. The lesson stuck: verify what's actually on disk and running, not what you think you deployed.

07 · DEAD ENDS + WHAT'S NEXT

The ideas that fought back

Not everything made it. I trained an AI model to classify electromagnetic signals — a Conv1D autoencoder plus Transformer over 13 modulation types — starting on synthetic IQ I generated myself. It runs, but I quickly learned how enormously hard real-world signal classification is; the gap between synthetic data and the messy air is huge, and that one is still humbling. I also built a smaller model that used the BLE hardware to identify a device by the signal it puts out on start-up.

Reading around this led me to the TempestSDR project — reconstructing a screen from its unintentional RF emissions — and to an NVIDIA publication on 6G signal reflections and their Aerial Omniverse digital twin. That pointed at the real bottleneck: the Pi. The next direction is a digital twin that offloads the heavy processing to my 4090 laptop or 3090 desktop instead of the Pi, and pulling in open-source pieces to extend what the device can do. That's still on the roadmap.

08 · REFLECTION

What I learned, and what was hard

Raven RF started as a way to learn SDR and turned into a portable device that covers most of the chain — RF capture, a custom decode library, track filtering, a dozen bands, deployment, and a modulation classifier. Being honest about it: the parts I understand deeply are the ones I struggled through by hand — IQ and demodulation, the Kalman filtering, the Reed–Solomon math. AI did a lot of heavy lifting elsewhere — scaffolding UI, generating the enclosure geometry, moving me quickly through code I'd have taken far longer to write alone. The signal classification is still the thing I understand least and want to understand most. It's in active development, and I'm not done with it.

70K
Lines · Python/PyQt5
14
Live tabs
0
External decode libs
6+
RF / SDR devices