A live ADS-B radar for an ESP32-C3 Super Mini + a 1.28" round GC9A01 display (240x240, non-touch). Shows aircraft around your location on a sonar-style scope, with a WiFiManager captive portal for setup and a theme-reactive phone companion app served straight off the device.
This is a fork of MatixYo/ESP32-Plane-Radar (MIT). See upstream for the 3D-printed case (STL + assembly) and wiring.
- 7 radar themes, cycled from the BOOT button or the phone app, persisted to flash: Midnight, Phosphor (green CRT + sweep), Amber CRT, Vice (neon), Mission Control (navy/gold + starfield), Silent Running (submarine night-vision red), and CIC (combat-information-center vector scope).
- Altitude color-coding on aircraft (warm low -> cool/bright high).
- Fading position trails behind moving aircraft.
- CIC scope mode: pure black background, green instrument chrome (a bearing ring labeled 000/045/.../315, minor ticks every 15°, and a faint square grid), amber bracket targets/tags for maximum contrast against the chrome and map layers, and a real regional map (interstates, rivers and lakes, county lines, town markers) baked from OpenStreetMap and drawn in natural cartographic colors (gray-white roads, blue water, gray county lines, warm-white towns). No sweep in this mode.
- Corrected geometry: longitude distances now scale by cos(latitude), so roads, runways, and plane positions are E-W accurate (not stretched ~24% at mid latitudes).
- Range presets 5 / 10 / 15 / 25 / 50 km.
- Phone companion app at
plane-radar.local: terminal-styled UI whose palette re-tints live to match the device's active theme, aircraft list, per-aircraft dossier (photo + route), select/deselect, and settings (theme via mini-radar swatches, range, units, runways, and lat/lon editing).
- Download the merged
.binfrom Releases. - Open web.esphome.io (or esptool-js) in Chrome/Edge.
- Put the board in download mode (hold BOOT, tap RESET), connect over USB-C, and flash the image at offset 0x0 (ESP32-C3, 4 MB). There is no OTA partition -- every update is a USB reflash.
pio run -e supermini -t upload
pio device monitor -e supermini # 115200 baudLovyanGFX is pinned to exactly
1.2.21inplatformio.ini. Do not bump it:1.2.24+adds a globalfontsalias that collides with this project's, and^1.2.7(or anything before1.2.21) predates the two-argloadFont()this code uses. This fork also relies on a hand-rolledlerpRgb565(seeinclude/ui/color_blend.h) because that pin has noalphaBlendhelper.
There is no OTA partition configured, so both paths above end in a USB flash
-- either the merged .bin at offset 0x0, or pio run -e supermini -t upload.
- On first power-up (no saved Wi-Fi) the device starts an AP named
PlaneRadar-Setup. - Join it, then open
http://plane-radar.local(orhttp://192.168.4.1). - Set your home Wi-Fi, latitude/longitude, units, and runway overlay; save. The device reconnects and starts the radar.
Reconfigure later at http://plane-radar.local (or the device IP) while it
is on your network.
| Action | Effect |
|---|---|
| Tap (< 1 s) | Cycle range preset (5 -> 10 -> 15 -> 25 -> 50 km); saved |
| Hold ~1 s then release | Cycle theme (7 themes); saved |
| Hold 8 s | Clear Wi-Fi + location + units; reboot into the setup portal |
The 8 s reset is deliberate so an overshot theme-hold never wipes your Wi-Fi.
(kBootThemeHoldMs = 1000 ms, kBootResetHoldMs = 8000 ms in
include/config.h.)
Browse http://plane-radar.local on the same network. The app is a
terminal-styled UI that re-tints live to match whichever theme is active on
the device, polls every 3 s, shows a stale banner if the device stops
responding, lists aircraft by distance, and expands a dossier per aircraft
(photo via planespotters, route via adsbdb). The CFG drawer sets theme
(mini-radar swatches showing each theme's palette), range (incl. 50 km),
miles/km, runways, and location (latitude/longitude fields).
Note: changing location moves the planes, but the baked CIC map stays put -- regenerate + reflash to move the map (below). If your configured location is more than ~100 km from the baked map center, the map hides itself automatically until you rebuild it for your region.
The CIC map is baked at build time from OpenStreetMap for a fixed center and
radius: interstate-class roads only (motorway -- trunk/primary are fetched
but excluded from the scope by design, since they read as noise at radar
scale; e.g. I-40/I-74/I-77/I-85 in the default region), rivers/lakes, county
lines, and town markers. The committed example data is centered on downtown
Denver, CO (39.7392, -104.9903, 80 km radius) purely as a neutral demo
region, chosen to have no geographic connection to the fork author.
The script's built-in defaults deliberately point at open ocean, so always
pass your own coordinates when rebuilding:
python3 scripts/build_region_map.py --lat <LAT> --lon <LON> --radius 80
pio run -e supermini -t uploadPrivacy tip: use a nearby public landmark (downtown, an airport) rather than your exact address — the map looks identical at radar scale, and the center coordinates are committed to the repo.
The generator fetches roads/water/boundaries/towns via the Overpass API
(trying a primary endpoint, then a public mirror, per layer), sends a
User-Agent header as required by Overpass usage policy, caches raw
responses under scripts/cache/ (gitignored, so reruns for the same
location/radius are free and work offline), simplifies the geometry, and
emits src/data/region_map_data.cpp (committed). It enforces a 96 KB
flash budget via a deterministic simplification ladder and prints per-layer
byte/segment telemetry (warning above 5,000 segments — the real constraint is
per-frame draw cost, not flash). Overpass rate-limits; if a fetch fails, wait
~30 s and re-run (the cache makes repeats free).
To restore trunk/primary roads instead of interstates-only, edit the
classes = ["motorway"] line in scripts/build_region_map.py -- see the
comment directly above it (# Owner decision (2026-07-04): interstates only on the scope...) for how to bring the other classes back.
As an alternative to baking a map at build time, the phone app's CFG drawer can fetch a personalized map for your saved location over the air ("Rebuild map for my location") and store it on the device's flash -- no reflash required. The firmware ships pointing at the fork owner's public map service, free to use with no signup. The first build for a new location takes the Worker roughly 1-2 minutes (it has to fetch and process OpenStreetMap data); the app shows a "building on server" status and retries automatically until the map is ready. Later rebuilds for a cached location return almost instantly.
If you'd rather run your own map service (your own Cloudflare account, your
own rate limits), deploy the Worker in worker/ (see worker/README.md) and
point the app at it: CFG drawer -> Map service URL -> Save URL.
| Setting | What it does | Where to change it | Survives reboot? |
|---|---|---|---|
| Wi-Fi credentials | Network the radar joins | Setup portal only (first boot, or after an 8 s BOOT reset) | Yes |
| Latitude / Longitude | Center of the radar (what traffic is fetched/shown) | Setup portal, or phone app CFG drawer (Save location) | Yes |
| Theme (7) | Full display restyle | BOOT hold ~1 s, or phone app swatches | Yes |
| Range (5/10/15/25/50 km) | Scope radius; map + rings zoom together | BOOT tap, or phone app CFG drawer | Yes |
| Units (km / miles) | Distance display on device + app | Setup portal, or phone app CFG drawer | Yes |
| Runway overlay | Major-airport runways on the scope | Setup portal, or phone app CFG drawer | Yes |
| Selected aircraft | Highlight ring + info card on device | Tap a plane in the phone app (tap again to clear) | No — auto-clears ~30 s after the app closes |
| Factory reset | Wipes Wi-Fi + location + units, reopens setup portal | Hold BOOT 8 s | — |
Note: changing latitude/longitude moves the planes immediately; the CIC map is baked at build time and stays put until you regenerate + reflash (see below).
Served on the device's web server (same host as the setup portal). See
src/services/web_app.cpp for the handlers.
| Method + path | Purpose |
|---|---|
GET / |
The phone companion app (gzipped) |
GET /api/aircraft |
Snapshot: state (lat/lon, theme, range, units, runways, selected) + aircraft list with distance |
POST /api/select |
{"hex":"a1b2c3"} to highlight on-device; {"hex":null} to clear |
POST /api/settings |
{theme, rangeIdx, useMiles, showRunways, lat, lon} (any subset) |
GET /api/status |
{uptime_s, rssi, heap, ip} |
pio run -e supermini
pio run -t merge -e supermini # -> .pio/build/supermini/firmware-merged.binFlash the merged image at 0x0.
Build uses PlatformIO (env supermini); resource use on that env is roughly
17.5% RAM and 41% flash. The native unit-test suite (pio test -e native, 44 tests) covers theme data, geometry, and map logic without
touching hardware.
- Upstream firmware & hardware: MatixYo/ESP32-Plane-Radar (MIT).
- Scope aesthetic inspired by Capsule Radar (by socquique).
- ADS-B data: adsb.fi (free tier, ~1 request/s courtesy limit, non-commercial use, attribution).
- Aircraft photos: planespotters.net (photographer credit shown per photo, required by their API terms; non-commercial use).
- Routes: adsbdb (attribution).
- Map data: © OpenStreetMap contributors, ODbL, fetched via the Overpass API.
- Libraries: LovyanGFX, WiFiManager, ArduinoJson.