Aeris — Ground Control Station for an ESP32-S3 drone
A desktop ground station that speaks the drone's own protocol over Wi-Fi. Telemetry, manual flight, PID tuning and flight recording — without touching a line of the drone's firmware.
The drone could fly. Nothing could tell me why it flew badly.
A small ESP32-S3 quadcopter running Crazyflie-compatible firmware will take commands and stay in the air, but a bare link gives you no visibility: no attitude history, no link quality, no way to change gains without a reflash, and no record of what happened on the flight that just ended badly.
Aeris is the missing half of that system — a ground control station built to make the drone observable and tunable in flight, communicating over Wi-Fi using the drone's existing CRTP protocol so that no firmware modification was required.
Drone to desktop, over the air
Commands and telemetry share a single Wi-Fi link. The station opens a CRTP connection through cflib, subscribes to the drone's log variables, and fans that stream out to every panel that needs it — plots, instruments, the recorder — while control input flows back the other way at interactive rates.
Architecture diagram — drawn for this case study
What I actually built
- A modular four-layer architecture — communication, control, data and UI — so a change to the link protocol never reaches into a plotting widget, and the recorder can be driven by the same stream that feeds the graphs.
- Live multi-channel telemetry graphing built on PyQtGraph, plotting roll, pitch and yaw against a moving time axis with per-signal tabs for gyro, accelerometer, motors, battery and link.
- Manual flight control with three interchangeable input sources — keyboard, on-screen virtual sticks, and a physical gamepad — feeding a shared thrust / yaw / pitch / roll command channel.
- A PID tuning panel that changes gains against the live aircraft, so a tuning pass is a conversation with the drone rather than a reflash-and-hope cycle.
- A flight recorder that captures the telemetry stream and exports it to CSV or JSON for post-flight analysis.
- An operational dashboard — link state, battery voltage, link quality, latency, packet rate, protocol version, arm state and uptime — plus an always-reachable emergency stop.
The whole loop, closed
Gamepad in hand, station on the laptop, aircraft in the air. The same link carrying the stick commands is carrying the attitude stream back into the plot.
Decisions worth defending
- Speak the drone's protocol, don't invent one. Implementing against CRTP through cflib meant the aircraft's firmware stayed untouched — the station is a client of an existing, tested interface rather than a second thing that can be wrong.
- Separate the transport from the view. Telemetry arrives once and is distributed; panels subscribe. Adding the flight recorder was a new consumer, not a rewrite.
- Make the link itself visible. Latency, packet rate and link quality are first-class readouts. On a Wi-Fi control link, the health of the connection is flight-critical information.
- Instruments over numbers where it matters. An artificial horizon and compass communicate attitude faster than three decimal values ever will — the numbers stay available underneath.
Outcome
The station connects, streams live attitude data, flies the aircraft manually from three different input devices, tunes PID gains against the running drone and records the session for later analysis — verified in indoor and outdoor flight, on video above.
Application
- Python
- PySide6
- PyQtGraph
Link
- cflib
- CRTP
- Wi-Fi
Aircraft
- ESP32-S3
- IMU
- PID control
Data
- CSV
- JSON
- Flight recorder