hackrf-webui is a local-first web interface for HackRF.
It runs on the user's own machine, works offline at runtime, exposes radio controls in a browser UI, and currently ships with seven real modules:
SIGINT: local capture review, AI triage and persisted ADS-B / AIS route replayFM: browser listening with a large country-sharded station catalogPMR: narrowband channel presets with manual listen and automatic scanningAIRBAND: AM airband listening with local presets, manual tuning and shared HackRF audio controlsMARITIME: marine VHF voice listening with starter channel packs, manual tuning and autoscanAIS: native dual-channel HackRF decoding with a live vessel map and offline-capable basemapsADS-B: live aircraft tracking with a manageddump1090-fabackend, a local aircraft map and offline-capable basemaps
There is no cloud layer, no account system, and no remote device bridge.
The app also has a single global location model shared across modules:
- a
catalog scopefor country / city-aware features such as FM filters and regional scan decks - an
exact positionfor map-centric modules such asAISandADS-B - three exact-position modes:
Catalog centroidMap pinGPSD live
WFMlistening through the browser- region / country / city / text filtering
- on-demand country shard loading to keep the UI responsive
- global coverage metadata with a dedicated coverage page
- custom presets stored locally in the browser
- filters can be initialized from the shared global location, but browsing FM does not require changing the exact operating position
NFMlistening through the browser- built-in channel packs for:
PMR446FRSUHF CBMURS
- manual channel tuning
- automatic scanning with:
- sequential or random scan mode
- squelch threshold
- dwell time
- lock-on-activity behavior
- activity log
- local activity log persistence in
SQLite, so hits survive reloads and restarts - automatic activity capture while listening or scanning:
- demodulated
WAV - raw
IQ .cs8 - files are linked back to each contact entry
- demodulated
- in-place retune of the active PMR stream without restarting the browser audio pipeline
AMlistening across the civil VHF airbandAlldeck that merges saved presets with the built-in common and guard channels- local-first saved preset bank in the browser
- manual tuning with quick retune steps
- starter channel packs for global common and guard frequencies
- PMR-style scanning with dwell, squelch and lock-on-activity
- local activity log persistence in
SQLite - automatic activity capture while listening or scanning, with linked
WAV+ rawIQ .cs8files per contact - shared RF/audio controls with the other live audio modules
NFMlistening across common marine VHF voice channelsAlldeck that merges saved presets with built-in global and regional marine groups- local-first saved preset bank in the browser
- manual tuning with quick retune steps
- starter channel packs for distress, port ops, working voice, selected Spain port channels, UK MSI, U.S. coastal safety, U.S. VTS and NOAA weather watch
- PMR-style scanning with dwell, squelch and lock-on-activity
- local activity log persistence in
SQLite - automatic activity capture while listening or scanning, with linked
WAV+ rawIQ .cs8files per contact - smart
Allscanning that can prefer global channels plus the shared city or country catalog scope - focused on analog voice traffic; digital AIS remains in the dedicated AIS module
- a dedicated intelligence workspace above the radio modules
- capture review queue backed by local
SQLite - shared evidence detail for
PMR,AIRBANDandMARITIME - local AI audio triage for linked
WAVcaptures:- background queue with backfill for old captures
- hybrid
YAMNet + WebRTC VADscoring for low-resource local voice triage - broad classification into
speech,noise,musicorunknown - separate
voice detectedandambient scenesummaries retained alongside each capture - top-label evidence retained alongside each capture
- persisted
ADS-BandAISroute replay from local track history - analyst review state, notes and priority per capture
- live AIS decoding from the HackRF across channels A and B at
161.975 MHz/162.025 MHz - native demodulation and message parsing inside
hackrf-webui, withoutSDRangel - vessel map with offline-capable dark basemaps
- managed PMTiles layers, plus a default worldwide Protomaps dark extract served from
public/tiles/osm - persistent local position history in
SQLite, so each vessel can accumulate a route log instead of only exposing the latest point
- live ADS-B / Mode S decoding at
1090 MHz - managed
dump1090-fabackend driven byhackrf-webui - aircraft map with the same offline-capable basemap system used by AIS
- local start / stop control, receiver stats and aircraft detail inside the dashboard
- persistent local position history in
SQLitefor route reconstruction and later analysis
git clone [email protected]:MikelCalvo/hackrf-webui.git
cd hackrf-webui
./start.shBy default, start.sh:
- installs missing system dependencies on common Linux distributions
- installs
gpsdpackages when the distribution exposes them, so live GPS positioning is ready when you want it - installs Node dependencies
- prepares the local
SQLiteruntime storage underdb/and runs pending migrations automatically - downloads the local AI model assets into
assets/ai/if they are missing - installs a local AI runtime under
runtime/usinguv, a managedPython 3.13,ai-edge-litertandwebrtcvad-wheels - ensures a managed offline map stack unless
--skip-mapsis used - installs a dark global basemap capped near
4 GBby default - optionally adds one high-detail country overlay when
--map-countryis set - prompts for a country overlay in interactive terminals if none is configured yet
- installs or updates the local
dump1090-faADS-B backend unless--skip-adsb-runtimeis used - builds the native
HackRFreceiver binaries - builds the Next.js app
- starts the web UI in production mode
Default address:
http://127.0.0.1:3000
Module routes:
/sigint/fm/pmr/airband/maritime/ais/adsb
The root route / redirects to the last module used in the browser when available, and otherwise falls back to /fm.
Useful options:
./start.sh --check
./start.sh --host 0.0.0.0 --port 4000
./start.sh --skip-system-deps
./start.sh --skip-npm --skip-build
./start.sh --skip-ai
./start.sh --map-global-budget 4G
./start.sh --map-global-zoom 10
./start.sh --map-country ES
./start.sh --map-country ES --map-country-zoom 14
./start.sh --skip-adsb-runtime
./start.sh --reinstall-adsb-runtime
./start.sh --rebuild
HACKRF_WEBUI_GPSD_HOST=127.0.0.1 HACKRF_WEBUI_GPSD_PORT=2947 ./start.shWhat they do:
--checkvalidates the local setup and prints a status report without changing the machine--hostand--portoverride the bind address--skip-system-depsavoids package-manager changes--skip-npmand--skip-buildreuse existing local artifacts--skip-aileaves the local SIGINT AI runtime untouched or absent--map-global-budgetcontrols the target size of the shared global basemap layer--map-global-zoomforces the shared global basemap max zoom--map-countryinstalls or refreshes one high-detail country overlay on top of the shared world layer--map-country-zoomcontrols the overlay max zoom for that country--skip-mapskeeps AIS and ADS-B in live-tile mode--skip-adsb-runtimekeeps the existing ADS-B backend untouched or skips it entirely--reinstall-adsb-runtimerebuilds the pinned localdump1090-fabackend--reinstall-airebuilds the local SIGINT AI runtime and Python packages--rebuildforces a freshnpm ciand production rebuildHACKRF_WEBUI_GPSD_HOSTandHACKRF_WEBUI_GPSD_PORTpoint the app at a non-default GPSD listener when needed
Environment overrides also work:
HOST=0.0.0.0 PORT=4000 ./start.sh
MAP_GLOBAL_BUDGET=4G ./start.sh
MAP_GLOBAL_MAX_ZOOM=10 ./start.sh
MAP_COUNTRY=ES MAP_COUNTRY_MAX_ZOOM=14 ./start.sh
DUMP1090_FA_REINSTALL=1 ./start.sh
AI_REINSTALL=1 ./start.sh
HACKRF_WEBUI_AI_PYTHON=3.13 ./start.sh
HACKRF_WEBUI_GPSD_PORT=2947 ./start.shIf the default port is busy and you did not explicitly force a port, the script automatically falls forward to the next free one it can find.
Managed map model:
- the global layer is chosen from a size budget, not a hardcoded profile
- the default
4 GBbudget currently lands on a world extract up toz10 - country overlays start at the first zoom above the global layer and extend to the configured country zoom
- maps are stored under
public/tiles/osm/globalandpublic/tiles/osm/countries
hackrf-webui is local-first, so runtime evidence is stored on disk next to the project:
db/app.sqlite- the local
SQLitedatabase - stores module activity events, route history for
AIS/ADS-B, capture metadata for linked audio / IQ evidence, andSIGINTreview / AI state
- the local
data/captures/- stores activity-triggered
WAVaudio and rawIQ .cs8captures forPMR,AIRBANDandMARITIME - is organized per day and per stream session under the project tree
- keeps large binaries on disk while
SQLitestores the linkage and metadata
- stores activity-triggered
runtime/- stores the local AI toolchain
- includes the managed
uvbootstrap, the pinnedPython 3.13install, the AI virtualenv and its cache
What already persists today:
PMR,AIRBAND,MARITIMEactivity logsPMR,AIRBAND,MARITIMEactivity-linkedWAVand rawIQcaptures- local AI queue state, classifications and tags for
PMR,AIRBANDandMARITIMEcaptures AISvessel position historyADS-Baircraft position history
The FM station catalog is intentionally not stored in the database. It remains a sharded static catalog under public/catalog.
aptfor Debian / Ubuntudnffor Fedora / RHEL-like systemspacmanfor Arch-based systemszypperfor openSUSE
If your distribution does not expose one of the required packages in its enabled repositories, the script stops with a clear error so you can install that package manually and rerun it.
If you prefer to handle dependencies yourself:
npm ci
npm run db:migrate
node ./scripts/install-dump1090-fa.mjs
npm run build
npm run start -- --hostname 127.0.0.1 --port 3000If you also want the local SIGINT AI runtime without using start.sh, install it under the project tree:
export UV_UNMANAGED_INSTALL="$PWD/runtime/tools/uv"
curl -fsSL https://astral.sh/uv/install.sh | sh
runtime/tools/uv/uv python install --install-dir runtime/python 3.13
runtime/tools/uv/uv venv --python 3.13 runtime/ai-venv
runtime/tools/uv/uv pip install --python runtime/ai-venv/bin/python -r scripts/ai/requirements.txt
mkdir -p assets/ai
curl -fsSL https://storage.googleapis.com/mediapipe-models/audio_classifier/yamnet/float32/latest/yamnet.tflite -o assets/ai/yamnet.tflite
curl -fsSL https://raw.githubusercontent.com/tensorflow/models/master/research/audioset/yamnet/yamnet_class_map.csv -o assets/ai/yamnet_class_map.csv
runtime/ai-venv/bin/python scripts/ai/audio_tagger.py --check --model assets/ai/yamnet.tflite --labels assets/ai/yamnet_class_map.csvOptional offline maps can also be prepared manually:
./manage_maps.sh ensure
./manage_maps.sh add-country ES
./manage_maps.sh statusYou can also validate the environment without starting the server:
./start.sh --checkFor normal usage, the app needs:
HackRFuserspace tools, includinghackrf_infolibhackrfdevelopment headers so the bundled native binary can be builtffmpegccpkg-configncursesdevelopment headersNode.js20+npmcurl
Optional but supported:
gpsd- a compatible GPS receiver, such as a USB
u-blox, if you want live physical positioning
start.sh tries to install gpsd on the supported Linux package managers, but the app still runs without it if you only want fixed manual positions.
The top bar exposes a shared location button on every module. That dialog controls two separate things:
Catalog scope- country and optional city
- used by FM defaults and regional scan logic such as Maritime Smart Local
Exact position- used by map-centric views and any feature that needs true operating coordinates
- can come from:
- the selected city centroid
- an exact map pin you place manually
- a live fix from local
gpsd
This split matters:
- you can keep
Bilbaoas the catalog scope for FM and Maritime - while using a more exact rooftop antenna pin for map centering
- or while letting
gpsddrive the exact coordinates when operating mobile
The exact position picker uses the same offline-capable basemap stack as AIS and ADS-B, so it stays usable without internet once the local map pack is installed.
The bundled native receivers are built from:
The managed ADS-B backend is built separately into:
The AIS module tunes the HackRF directly, demodulates AIS in the native binary, validates frames, parses AIS messages in the backend and renders decoded vessels on the map in real time.
Offline basemaps are served from public/tiles/osm. By default, ./start.sh ensures a managed PMTiles stack made of:
- one shared dark world layer sized by
--map-global-budgetor--map-global-zoom - zero or more country overlays managed by
./manage_maps.sh
The AIS map behavior is:
- if a global exact position is configured, AIS starts from that point
- otherwise it falls back to decoded AIS bounds when traffic exists
- if there is still no traffic, it falls back to the selected country overlay or the installed basemap bounds
The ADS-B module uses the same local-first dashboard model as AIS, but the decoder backend is dump1090-fa compiled locally and managed by hackrf-webui.
The runtime:
- claims the
HackRFexclusively while ADS-B is active - starts
dump1090-fawithHackRFinput at1090 MHz - reads
receiver.json,aircraft.jsonandstats.jsonfrom.cache/adsb-runtime/json - normalizes those files into the app's own ADS-B snapshot and UI state
- reuses the same offline basemap pipeline as AIS
The ADS-B map behavior is:
- if a global exact position is configured, ADS-B starts from that point
- otherwise it falls back to live aircraft bounds when traffic exists
- if there is still no aircraft position, it falls back to receiver coordinates if configured
- if there is still no location hint, it falls back to the selected country overlay or the installed basemap bounds
The map stack is managed by manage_maps.sh, which wraps scripts/manage-maps.mjs.
Useful commands:
./manage_maps.sh status
./manage_maps.sh ensure
./manage_maps.sh ensure --global-budget 4G
./manage_maps.sh add-country ES
./manage_maps.sh add-country ES --country-max-zoom 14
./manage_maps.sh remove-country ES
./manage_maps.sh list-countries
./manage_maps.sh cleanUpgrade examples:
./manage_maps.sh install-global --global-max-zoom 11 --reinstall
./manage_maps.sh add-country ES --country-max-zoom 14 --reinstall
./manage_maps.sh ensure --global-max-zoom 11 --country ES --country-max-zoom 14 --reinstallBehavior:
- if a layer already exists at the same or higher zoom, the manager keeps it
- use
--reinstallwhen you want to force a rebuild or move to a higher target zoom - with the current Protomaps
v4.pmtilessource, the practical hard limit isz15
The managed manifest lives at public/tiles/osm/manifest.json and uses a single clean layered schema:
version: 1- one global PMTiles layer
- zero or more country PMTiles overlays
There is no legacy compatibility layer for earlier map manifest formats in the current tree.
Useful environment overrides for the ADS-B backend:
ADSB_SAMPLE_RATE=2400000 ./start.sh
ADSB_LNA_GAIN=32 ADSB_VGA_GAIN=50 ./start.sh
ADSB_ENABLE_AMP=1 ./start.sh
ADSB_ENABLE_ANTENNA_POWER=1 ./start.sh
ADSB_RECEIVER_LAT=40.4168 ADSB_RECEIVER_LON=-3.7038 ./start.shIf you prefer to build only the ADS-B backend manually:
node ./scripts/install-dump1090-fa.mjsTo remove local generated artifacts and leave the repo close to a fresh clone:
./clean.sh
./clean.sh --dry-run./clean.sh asks for confirmation by default. Use ./clean.sh --yes in non-interactive environments.
npm ci
npm run devOpen http://localhost:3000.
npm run dev uses webpack by default because it has been more stable than Turbopack in synchronized folders. If you want to test Turbopack, use:
npm run dev:turboIf you only want to clear the Next.js build output:
npm run cleanThe FM runtime catalog is split into:
src/data/catalog/manifest.jsonpublic/catalog/manifest.jsonpublic/catalog/countries/*.json
This keeps first load small and only fetches the active country shard.
Manual fallback data lives in:
src/data/catalog/manual/countries.jsonsrc/data/catalog/manual/cities.jsonsrc/data/catalog/manual/fm-stations.json
That manual layer is intentionally small now and only covers true fallback cases that still lack a clean importer.
The FM catalog builder is for contributors, not for normal runtime use.
npm run catalog:buildOptional extra tools for some catalog importers:
pdftotextpdftohtml
The builder currently combines:
GeoNamesfor geographic normalization- a large set of official regulator / public-sector FM sources
- a small manual fallback layer where no reproducible official importer is available yet
- cached shard fallback when a previously landed source is temporarily unavailable
Country coverage metadata is embedded in the manifest, including:
coverageStatuscoverageTiercoverageScopesourceQualitysourceCountnotesPath
The PMR module does not use the FM coverage catalog.
Instead, it uses static channel packs defined in pmr-channels.ts for license-free or common short-range voice bands. The current PMR runtime is designed around:
- narrowband FM audio
The AIRBAND module uses static starter packs defined in airband-channels.ts plus browser-local saved presets. The runtime is designed around:
- civil VHF airband AM audio
- a global starter deck, not a country-specific airport database
- an
Allview that merges saved, common and guard channels - quick local tuning rather than an airport database
- in-place retune of an active AM stream when possible
- shared HackRF audio ownership with FM and PMR
- fast retune of an existing stream
- browser-local preset storage and manual notes
- scan / lock / resume workflows on the active channel group
The MARITIME module uses static starter packs defined in maritime-channels.ts plus browser-local saved presets. The runtime is designed around:
- marine VHF narrowband FM voice audio
- a global starter deck rather than a port-specific database
- an
Allview that merges saved, distress, port ops, working, regional and weather groups - quick local tuning rather than a harbor directory
- in-place retune of an active NFM stream when possible
- shared HackRF audio ownership with FM, PMR and AIRBAND
- browser-local preset storage and manual notes
- scan / lock / resume workflows on the active channel group
- keeping AIS / DSC digital traffic out of this voice-focused module
- smart scan scoping for
All, using the saved FM city / country when available - curated regional packs for Spanish ports, UK MSI and selected U.S. VTS / Coast Guard channels
FM coverage planning and blocker notes live under docs/fm.
Useful entry points:
docs/fm/global-fm-coverage-plan.mddocs/fm/europe-coverage-status.mddocs/fm/uk-source-notes.mddocs/fm/spain-source-notes.md
At the moment, those docs are FM-specific. PMR does not need the same coverage-tracking model because it is channel-pack based rather than station-registry based.
- Runtime use is local and offline-friendly.
- The app does not depend on remote frontend assets.
- The current radio runtime is focused on
HackRF. - SIGINT, FM, PMR, AIRBAND, MARITIME, AIS and ADS-B are the landed modules today.
- The catalog and band modules are intended to keep growing through importer work and targeted PRs.