CircuitPython application for an Adafruit PyPortal that watches a fixed circular area around a configured center point, remembers aircraft seen in the last few minutes, and shows the most relevant nearby planes on the display.
- Polls OpenSky for live aircraft state vectors near a configured watch point
- Applies a true circular radius filter on-device, defaulting to 3 miles
- Keeps a rolling recent-aircraft memory so a plane can remain visible after it passes through the radius
- Enriches the most relevant aircraft with ADSBDB metadata such as registration, aircraft type, airline, and route when available
- Renders a compact aviation-style dashboard for the PyPortal's 320x240 display, including a mini radar view, status badges, and a recent-traffic column
- Top bar: app title, source status, and IP / source summary
- Left radar tile: nearby aircraft plotted by bearing and relative distance from your configured watch point
- Featured aircraft card: callsign, route, type / operator, distance, altitude, speed, heading, and vertical trend
- Status badges: live/recent state and climb/descent trend
- Right column: compact recent / nearby aircraft list
- Footer: refresh summary plus non-fatal notes such as delayed enrichment
Quiet-state behavior:
- Before the app has ever seen a nearby aircraft, the status panel says
Waiting for first nearby aircraft - After aircraft have been seen and later leave the radius / recent window, the status panel switches to a quiet-sky message instead of pretending the app has never seen any traffic
Featured card abbreviations:
LIVE/RECE: the aircraft is live in the current refresh or only recently seenCLB/DSC/LVL: climbing, descending, or roughly levelMI: miles from the configured watch pointKFT: altitude in thousands of feetKT: speed in knotsBRG: bearing from the watch point to the aircraftHDG: aircraft headingVS: vertical speed in feet per minute
Route and metadata fallbacks:
- If route enrichment succeeds, the route is shown as a compact badge such as
SEA>SFO - If no route could be resolved, the app shows
NO ROUTE - If no specific aircraft type could be resolved, the app falls back to a broader aircraft category
This is the first implementation pass.
- Live OpenSky polling: implemented
- Local recent-flight memory: implemented
- ADSBDB enrichment: implemented
- Radar-style live display: implemented
- Altitude color coding and compact status badges: implemented
- Experimental image support: removed in favor of a cleaner radar-first display
Copy these libraries from the matching Adafruit CircuitPython bundle into CIRCUITPY/lib:
adafruit_connection_manager.mpyadafruit_requests.mpyadafruit_display_text/adafruit_esp32spi/
The project uses built-in board, displayio, and terminalio, so no extra font or image assets are required for the initial version.
- Install CircuitPython on the PyPortal.
- Copy the required libraries into
CIRCUITPY/lib. - Copy code.py and the app folder to the root of
CIRCUITPY. - Copy settings.toml.example to
settings.tomlonCIRCUITPYand fill in your values. - Reset the board.
If the app fails during boot or refresh, open the serial console to read lines beginning with Plane Portal error:.
The app reads configuration from settings.toml.
Required:
CIRCUITPY_WIFI_SSIDCIRCUITPY_WIFI_PASSWORDPLANEPORTAL_HOME_LATITUDEPLANEPORTAL_HOME_LONGITUDE
These existing key names are kept for compatibility, but they represent the app's watch point or center point, not necessarily a home location.
Recommended:
OPENSKY_CLIENT_IDOPENSKY_CLIENT_SECRET
Optional:
PLANEPORTAL_RADIUS_MILESPLANEPORTAL_REFRESH_SECONDSPLANEPORTAL_RECENT_WINDOW_MINUTESPLANEPORTAL_ENRICHMENT_LIMITPLANEPORTAL_ADSB_CACHE_SECONDSPLANEPORTAL_DEBUG
Notes:
settings.tomldoes not support float literals, so latitude, longitude, and radius should be stored as quoted strings.- Without OpenSky credentials the app still works in anonymous mode, but with lower rate limits.
- OpenSky supplies the live aircraft position and movement data: callsign, location, altitude, speed, heading, vertical rate, and broad aircraft category.
- ADSBDB supplies best-effort aircraft metadata such as specific type, registration, route, airline, and operator.
- The current default recent window is 10 minutes.
Minimal example:
CIRCUITPY_WIFI_SSID = "your_wifi_name"
CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"
PLANEPORTAL_HOME_LATITUDE = "47.6062"
PLANEPORTAL_HOME_LONGITUDE = "-122.3321"
PLANEPORTAL_RADIUS_MILES = "3"
PLANEPORTAL_REFRESH_SECONDS = 120
PLANEPORTAL_RECENT_WINDOW_MINUTES = 10- OpenSky now uses OAuth2 client credentials, so valid OpenSky API credentials are strongly recommended.
- The "recently overhead" list is maintained locally from prior refreshes. It is not historical flight data pulled from the API.
- ADSBDB metadata is best-effort only. Some aircraft have no route information or type details.
- The app intentionally does not attempt aircraft photo rendering on-device.
- Some live fields can be present even when route/type enrichment is missing, so the featured aircraft may still render with partial metadata.
- code.py: top-level CircuitPython entrypoint
- app/config.py: settings parsing and defaults
- app/network.py: PyPortal WiFi session management
- app/opensky_client.py: OpenSky OAuth and live aircraft fetches
- app/adsbdb_client.py: ADSBDB enrichment and caching
- app/tracker.py: radius filtering, distance math, and recent-flight tracking
- app/ui.py: display layout and rendering
- app/main.py: application loop