Skip to content

About

Open-source ESP32 firmware for a 1.28″ round display that shows live ADS-B aircraft around your location as a sonar-style plane radar.

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Latest commit

 

History

101 Commits

Folders and files

Repository files navigation

Plane Radar

plane-radar

3D printed case (STL + assembly): MakerWorld · Firmware: Releases

Firmware for an ESP32-C3 Super Mini and a 1.28″ round GC9A01 display (240×240). Shows a circular ADS-B radar around your configured location, with WiFiManager for first-time setup.

What it does

  1. Wi‑Fi setup (if needed) — captive portal on AP PlaneRadar-Setup
  2. Radar — live aircraft from adsb.fi on a sonar-style grid

After Wi‑Fi is saved, the device reconnects automatically; the radar runs in the main loop with periodic ADS-B updates (~5 s).

Controls (BOOT, GPIO 9, active LOW)

Action Effect
Short tap Cycle range preset (5 → 10 → 15 → 25 km); saved to flash
Double tap Stop following the flight (follow_id cleared). Only with a flight followed: otherwise the second tap is swallowed, never a double range change
Hold 3 s Clear Wi‑Fi, location, units, follow target and MQTT config; reboot into setup portal

A single tap is held back for 350 ms (kDoubleTapWindowMs) so a second one can cancel it; that is why the range changes just after the tap, not during it.

During setup you can also hold BOOT at power-on to force a credential reset (same as the long press).

Wi‑Fi setup portal

First-time setup (no saved Wi‑Fi):

  1. Connect to PlaneRadar-Setup
  2. Open http://plane-radar.local (preferred) or http://192.168.4.1 — both are shown on the yellow setup screen; captive portal may open automatically
  3. Set home Wi‑Fi, then save

Reconfigure anytime (after the device is on your network):

  1. Open http://plane-radar.local or http://<device-ip> (e.g. from your router or serial log at boot)
  2. Change Wi‑Fi, location, units, or runway overlay; save

The same portal runs on the setup AP and on the device’s LAN IP while connected to Wi‑Fi. mDNS hostname is plane-radar → plane-radar.local (kPortalHostname in config.h). Some clients resolve .local slowly; use the IP if needed.

Custom fields (stored in NVS):

Field Purpose
Latitude / Longitude Radar center and ADS-B query position (defaults in config.h until set)
Display distances in miles Ring scale label in mi instead of km (e.g. 6mi vs 10km)
Show airport runways Major-airport runway overlay on the radar (off to hide)
Follow flight Callsign (e.g. GLO1724) or Mode-S hex (6 hex digits) of the flight to follow. Blank = off
Publish to Home Assistant (MQTT) Master switch. Off by default: with no broker host the radar never tries to connect
MQTT broker host / port Broker address; port empty = 1883. Plain TCP only (see below)
MQTT username / password Broker credentials, stored in NVS. Sent only to the broker
MQTT base topic Empty = planeradar/<chip-id>. Two radars need two different topics
HA discovery prefix Empty = homeassistant. Change it only if your HA uses a custom one
Device name in Home Assistant Empty = Plane Radar

Every MQTT text field starts empty on purpose: the firmware ships no broker address, and the hint is shown as a placeholder, never as a pre-filled value.

After a reset, the device reboots and shows the setup screen immediately (no “Connecting” loop on stale credentials).

Radar display

Grid

  • Dark blue background, subdued green rings and crosshairs
  • White N / S / E / W at the bezel; range label on the east spoke (ring 3 = ¾ of outer radius)
  • White center dot

Layout and colors: include/ui/radar_theme.h.

Range presets

Ring 3 label Outer radius (aircraft scale)
5 km / 3 mi ~6.7 km
10 km / 6 mi ~13.3 km (default)
15 km / 9 mi ~20 km
25 km / 16 mi ~33.3 km

Preset and miles/km choice persist across reboot (planeradar NVS namespace).

Runways

  • Major airports from OurAirports (large_airport); all open runway strips in range (helipads excluded)
  • Teal runway lines with one ICAO label per airport (e.g. KJFK); toggle in the Wi‑Fi setup portal
  • Update the embedded list: python3 scripts/build_large_airports.py

Aircraft

  • Inside the outer ring — red heading triangle, magenta speed vector (clipped at the ring), callsign / type / altitude tags
  • Outside the ring (still within ADS-B fetch) — small red dot on the screen rim at the correct bearing (direction cue; not distance-accurate past the ring)
  • Tags — placed toward the center: west (left) → tag on the right of the symbol; east (right) → tag on the left

As range decreases (or aircraft approach), targets move inward; beyond-ring dots become full symbols when they cross the outer ring.

ADS-B

  • Source: https://opendata.adsb.fi/api/v3/
  • Fetch radius: ui::radar::fetchRadiusKm() — scales with the active preset to roughly the screen edge (so rim dots have data)
  • Poll interval: kAdsbFetchIntervalMs (5 s) in config.h
  • Ground aircraft hidden by default (kAdsbShowGroundAircraft)

Follow a flight

Set Follow flight in the portal and the radar stops watching the neighbourhood and starts watching one aircraft: the scope recentres on it, its path is drawn behind it, and a readout replaces the empty space below it.

  • Recentring — every frame, with a 6 px dead-band so a jittery fix cannot make the rings crawl. The whole scope moves: rings, runways, projection and fetch centre.
  • Trail — the last 64 fixes (about 5 minutes at the 5 s poll), drawn dim amber. A sample outside the scope breaks the line rather than being clipped onto the rim.
  • Readout — two lines: callsign and state on top (in the aircraft colour), then the current numbers.
State Meaning Readout
LIVE Airborne now (ground speed ≥ 40 kt, not reported on the ground) gs 412 kt
ON GROUND Seen at the airport, never airborne yet this session gs 0 kt
NOT LIVE Configured but not in the feed no position plus the age of the last fix
LANDED Was airborne this session, now on the ground or gone block 1h05

Data sources. Live positions come only from adsb.fi (opendata.adsb.fi), one request per second at most — the followed flight is looked up every poll, the surrounding traffic every third one. Nothing else is queried: the firmware shows what the feed reports, so there is no origin, no destination and no ETA. Both were dropped on purpose — callsign-to-route databases are static snapshots, and a reused callsign made them name the wrong city pair with confidence.

No wall clock. The firmware has no SNTP and no RTC, so the readout shows durations, never clock times: block is the time observed airborne, and no position carries the age of the last fix. "Landed at 14:32" cannot be printed without adding a time source.

Credit. ADS-B data courtesy of adsb.fi — please consider feeding them a receiver.

Configuration

Edit include/config.h for hardware and behavior:

Area Keys / notes
Portal kPortalApName, kPortalIp, kPortalHostname / kPortalHostUrl (mDNS; needs -DWM_MDNS in platformio.ini)
Wi‑Fi timing connect attempts, reconnect grace, portal timeout (0 = no timeout)
BOOT kBootPin, kBootResetHoldMs, kBootTapMinMs
Display SPI pins, kDisplayInvert, kDisplayRgbOrder, kDisplaySpiWriteHz
Default location kDefaultRadarLat, kDefaultRadarLon (until portal overrides)
ADS-B kAdsbFetchIntervalMs, kAdsbShowGroundAircraft
MQTT kMqttDefaultPort, kMqttDefaultTopicPrefix, kMqttDefaultDiscoveryPrefix, kMqttDefaultDeviceName, kMqttStateIntervalMs, kMqttMinFreeHeap, kMqttPacketSize (must equal -DMQTT_MAX_PACKET_SIZE)

Range presets: include/ui/radar_range.h (kRangePresets).

Home Assistant (MQTT)

The radar can publish itself to Home Assistant over MQTT with auto-discovery: no YAML on the HA side, no entity to declare by hand. Fill in the MQTT fields in the config portal (see above), enable Publish to Home Assistant, and the device appears as one HA device with twelve entities.

  • Discovery topics: <prefix>/<component>/<node>/<object>/config (retained), where <node> is the base topic with / replaced by _, e.g. homeassistant/select/planeradar_a1b2c3/range/config.
  • Topics: state on <base>/state/<key>, commands on <base>/cmd/<key>, availability on <base>/status (online / offline via MQTT last will).
  • Reload: discovery is re-published on every connect, so the entities survive a broker restart.
Entity Type What it does
Range select Radar range preset (5 / 10 / 15 / 25 km)
Miles switch Ring labels in mi instead of km
Runways switch Airport runway overlay
Debug overlay switch Fetch-in-progress dot + Wi-Fi dBm on screen
Latitude / Longitude number Radar center; the grid redraws immediately
Aircraft in range sensor Aircraft currently in the ADS-B snapshot
Nearest aircraft sensor Callsign, with type/altitude/distance/speed/track as attributes
Wi-Fi RSSI sensor dBm
Free heap sensor Bytes; the radar skips telemetry below kMqttMinFreeHeap
Radar info sensor IP as state, SSID/uptime/firmware as attributes
Follow flight text Callsign (e.g. GLO1724) or Mode-S hex of the flight to follow; blank = off. Same field as the portal, settable from HA

Commands are applied to NVS and re-published as state, so HA always shows the value the radar actually accepted (an out-of-range coordinate is rejected, not echoed back).

Broker requirements: plain TCP, no TLS. The ESP32-C3 already runs a TLS client for the ADS-B fetch (~50 KB of context); a second TLS handshake for MQTT does not fit the heap. Point it at port 1883. A dedicated broker user with write access limited to planeradar/# and homeassistant/# is recommended — the credentials are stored unencrypted in NVS, and the LAN portal on port 80 is not authenticated.

Project layout

include/
  config.h
  hardware/
    lgfx_config.hpp
    display.h
    display_font.h
  data/
    large_airports.h
  ui/
    radar_theme.h
    radar_range.h
    radar_display.h
    runway_overlay.h
    status_screens.h
  services/
    wifi_setup.h
    radar_location.h
    adsb_client.h
    fetch_watchdog.h
    mqtt_client.h          — HA discovery, commands, telemetry
    mqtt_config.h          — the 8 portal fields in NVS
    mqtt_discovery.h       — pure discovery payload builders (host-tested)
    nearest_aircraft.h     — pure distance math (host-tested)
    flight_follow.h        — follow-target identity, session state, trail, ETA math (host-tested)
    flight_follow.cpp      — the same, plus NVS and the live state
data/
  ui_font.vlw              — embedded smooth UI font (Noto Sans Bold)
scripts/
  build_large_airports.py
src/
  main.cpp
  data/
    large_airports_data.cpp
  hardware/
  ui/
  services/

Wiring (GC9A01 ↔ ESP32-C3 Super Mini)

Display ESP32-C3
VCC 3V3
GND GND
RST GPIO 0
CS GPIO 1
DC GPIO 10
SDA (MOSI) GPIO 3
SCL (SCLK) GPIO 4
BOOT (user) GPIO 9

Build

pio run -t upload
pio device monitor
  • PlatformIO env: supermini
  • Serial: 115200 baud
  • USB CDC on boot enabled in platformio.ini for the Super Mini

Web-flashable release image

Single .bin for esptool-js and similar tools (ESP32-C3, 4 MB, flash at 0x0):

chmod +x scripts/merge-firmware.sh   # once
./scripts/merge-firmware.sh

Writes release/plane-radar-merged.bin. Skip rebuild if firmware is already built:

./scripts/merge-firmware.sh --no-build

Or via PlatformIO only (output: .pio/build/supermini/firmware-merged.bin):

pio run -e supermini
pio run -t merge -e supermini

Put the board in download mode (hold BOOT, tap RESET), then flash with Chrome/Edge over USB.

CI and releases (GitHub Actions)

Workflow When Output
Build Push / PR to main Artifact plane-radar-supermini (merged + split .bin files, ~90 days)
Release Git tag v* (e.g. v1.0.0) GitHub Release asset plane-radar-v1.0.0.bin + .sha256

To ship a version users can download:

git tag v1.0.0
git push origin v1.0.0

The release workflow builds firmware in CI and attaches the merged image to the release. Download from Releases on GitHub, then flash at 0x0 (ESP32-C3, 4 MB).

Dependencies

  • LovyanGFX
  • WiFiManager
  • ArduinoJson
  • PubSubClient — MQTT. Built with -DMQTT_MAX_PACKET_SIZE=768: the default (256 B) truncates discovery payloads silently, because the ceiling covers topic + payload + header together.

About

Open-source ESP32 firmware for a 1.28″ round display that shows live ADS-B aircraft around your location as a sonar-style plane radar.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages