home  /  projects  /  hackrf
live

SIGINT Dashboard

A browser-first signal intelligence workstation for the HackRF One + PortaPack H2 (Mayhem firmware v2.0.2). A FastAPI + WebSocket backend talks to the device over USB-serial and drives hackrf_sweep. A single-page tactical dashboard renders a live spectrum + waterfall, detects and labels signals against 30+ US band allocations, and remote-controls the PortaPack.

The HackRF SIGINT dashboard: live spectrum and waterfall, active-signal table with labelled bands, and PortaPack controls on the left.
even if you're not a radio person

What's actually in the air around me?

A software-defined radio is a small USB gadget that listens to the radio spectrum. Plug in a HackRF and you can, in principle, watch any signal between 1 MHz and 6 GHz — FM stations, weather satellites, keyless-entry remotes, Wi-Fi, police radio, whatever's nearby.

The catch is that "in principle" does a lot of work in that sentence. Raw SDR tools are unforgiving. This dashboard is the friendlier face on top: a live picture of what's out there, with the known bands labelled, signals that come and go logged with timestamps, a row of quick-launch buttons for common bands, and a way to tune the attached PortaPack to listen in.

The story worth telling isn't the code

It's how the code came to exist. Before the dashboard, the interesting work was collaborative reverse-engineering of the device itself — poking every ChibiOS shell command on a live HackRF + PortaPack, watching what worked, what crashed the USB stack, what the output format actually was. HACKRF_TECHNICAL_REFERENCE.md (590 lines of device-verified facts) is the product of that. Every feature in the dashboard traces back to something we discovered and wrote down.

Hardware

Because PortaPack mode changes the USB identity, the standard hackrf_* tools can't see the device when it's running. The dashboard's serial-control layer is what lets both sides coexist.

Architecture

Browser  ◀──── WebSocket ────▶  FastAPI (sigint_server.py)
                                   │
                                   ├──▶ portapack.py ──▶ /dev/ttyACMx
                                   │                   (ChibiOS shell, 115200 8N1)
                                   └──▶ hackrf_sweep subprocess
                                            └── or simulate_sweep() fallback

One process. One WebSocket. Two background threads (sweep worker + PortaPack I/O, protected by a lock). One async broadcast loop pushing state at 2.5 Hz.

What ships

portapack.py

ChibiOS shell wrapper, reconnect-on-USB-re-enumerate

Auto-detect by VID/PID with a fallback /dev/ttyACM* scan. Context-manager interface. On OSError the library closes, rediscovers the port (the ACM number may have changed), reopens, and retries. Strips shell echo and the trailing ch> prompt. Structured parsers for info, radioinfo, applist.

scanner.py

hackrf_sweep wrapper + peak detection + band ID

Configurable threshold, minimum-gap merge, a static table of 30 US band allocations (KNOWN_BANDS) plus identify_signal(freq_mhz) → label. Quick-scan presets (all · fm · air · vhf · uhf · cell · ism900 · gps · wifi · wifi5 · lte · 5g). CSV export. CLI with an ASCII bar chart for quick sweeps.

sigint_server.py

FastAPI + WebSocket + sweep engine

Sweep worker runs hackrf_sweep as a subprocess, streams stdout, buckets bins into a per-sweep map, emits completed sweeps and appends a down-sampled row to the waterfall. Signal detection walks the sorted spectrum, tracks peaks by quantized frequency, emits NEW_SIGNAL / SIGNAL_LOST with duration. Dead sockets pruned inline. Idle self-shutdown at 60 s of zero clients.

simulate_sweep

Realistic fallback when the radio's not on the bus

Triggered when hackrf_sweep exits immediately (device in PortaPack mode) or throws. Generates a noise floor plus 16 canonical Gaussian-shaped signals (FM, 2m, NOAA, ISM…) at ~3 Hz so the UI stays usable for dev work and demos without the radio attached.

dashboard.html

Single-file SPA, dark tactical theme, no framework

Four regions. Top bar: brand, connection dot, sweep dot, CONNECT / SWEEP / STOP, UTC clock. Left panel: live device info, sweep config, 21 band presets, PortaPack remote (D-pad + rotary + Read Screen), 20-app quick-launch grid. Center: canvas spectrum with peak-hold markers and click-to-tune, 200-row waterfall with a 6-stop color ramp. Right: active signals table, event log, shell console. Bottom: frequency entry + 7 modulation modes + VU meter.

Close-up of the dashboard center canvas: spectrum plot with labelled peaks on top, waterfall below.
launcher

Tray icon that tears itself down when idle

System-tray icon spawns the server, waits for health, opens the browser, and tears everything down on idle. One entry point — launch-sigint.sh — that's idempotent: kills any prior instance before spawning.

Footprint

No build step. No npm.

Vanilla JS, one HTML file, one launcher. If you can't run it, that's a 5-minute debug. If it breaks, it breaks in a place you can read. A radio dashboard doesn't need 400 MB of node_modules.