Gesture-controlled flight system for a Crazyflie 2.1 equipped with an AI-deck — custom STM32 and GAP8 firmware, plus a Python ground station.
The AI-deck's GAP8 processor captures a 324×244 grayscale camera feed and streams it over WiFi to the ground station, where MediaPipe classifies hand gestures in real time. Recognised gestures trigger flight commands; hand position drives lateral, altitude, and distance tracking. Telemetry is logged to InfluxDB 3 Core and visualised in Grafana.
The ground station is a multi-process Python application. Nodes communicate via iceoryx2 shared-memory IPC, a zero-copy pub/sub for high-throughput data (images, telemetry) and a typed blackboard for live runtime configuration.
Pub/sub topics (iceoryx2):
| Publisher | Topic | Subscriber(s) |
|---|---|---|
control_node |
/action |
logger_node, wifi_node |
gui_node |
/command |
control_node |
vision_node |
/perception |
control_node, gui_node, logger_node |
wifi_node |
/telemetry |
gui_node, logger_node |
wifi_node |
/image |
gui_node, logger_node, vision_node |
Blackboard (iceoryx2 key-value, runtime config):
| Writer | Service | Readers |
|---|---|---|
gui_node |
/config |
control_node, vision_node, logger_node |
19 typed entries (flight speed, yaw rates, altitude limits, gesture thresholds, CLAHE toggle, tracking gains). All editable live via Settings → Settings… without restarting nodes.
Application state machine:
- Python 3.12+
- Crazyradio PA USB dongle
- Docker
- Grafana
- InfluxDB 3 Core
- Build tools (make, gcc, etc).
Create virtual environment and install dependencies:
python3 -m venv .venv
source .venv/bin/activate
pip install .Download HandGestureClassifier and place it in the data folder.
Follow steps in Install Grafana.
To start Grafana, execute the following statements to configure Grafana to start automatically using systemd:
sudo /bin/systemctl daemon-reload
sudo /bin/systemctl enable grafana-serverStart grafana-server by executing:
sudo /bin/systemctl start grafana-serverOpen Grafana server.
Grafana UI: http://localhost:3000
To connect InfluxDB 3 as a datasource:
- Connections → Data sources → Add → InfluxDB
- Set Query language:
InfluxQL - Set URL:
http://localhost:8181 - Set Database:
crazyflie - Set HTTP Method:
GET - Under Custom HTTP Headers, add header
Authorizationwith valueBearer <your_token>(token from.env) - Click Save & test
Import the dashboard: Dashboards → Import → Upload JSON → select grafana/dashboard-influxdb3.json. The client writes to three tables: telemetry, action, perception.
InfluxDB 3 Core is an open-source time-series database (MIT licence). Install:
curl -O https://www.influxdata.com/d/install_influxdb3.sh && sh install_influxdb3.shmain.py starts InfluxDB 3 automatically on launch. To start manually:
INFLUXDB3_NODE_ID=your-node-id influxdb3 serve --node-id-from-env=INFLUXDB3_NODE_ID \
--object-store=file \
--data-dir ~/.influxdb \
> ~/.influxdb/logs/server.log 2>&1 &Create an admin token on first run and store it in .env:
influxdb3 create token --adminCreate .env in the project root with:
INFLUXDB3_TOKEN=apiv3_...
INFLUXDB3_NODE_ID=your-node-idInfluxDB 3 API: http://localhost:8181
This firmware depends on the Crazyflie firmware repository. First clone it:
git clone https://github.com/bitcraze/crazyflie-firmware.git ~/git/PUBLIC/crazyflie-firmwareCompile app firmware:
cd firmware/app
make clean
make -j
# Override default CRAZYFLIE_BASE location
make CRAZYFLIE_BASE=/path/to/repoFlash app firmware using Crazyradio:
- Turn the Crazyflie off.
- Hold the power button ~3 seconds to enter bootloader mode (blue LEDs blink).
- Run:
make cloadPull the Docker image:
docker pull bitcraze/aideckBuild the AI-deck firmware (inside Docker):
cd firmware
# incremental build
make build
# full rebuild
make rebuild
# build with debugging enabled
make build DEBUG=1
# full rebuild with debugging
make rebuild DEBUG=1Output image:
ai/BUILD/GAP8_V2/GCC_RISCV_FREERTOS/target.board.devices.flash.img
Controlled by flags in firmware/ai/Makefile. Default is RAW. To switch to JPEG:
# Comment out RAW, uncomment JPEG (CONFIG_GAP_LIB_JPEG stays on either way):
# APP_CFLAGS += -DRAW_ENCODING
APP_CFLAGS += -DJPEG_ENCODING
CONFIG_GAP_LIB_JPEG = 1Then rebuild with make rebuild.
We support two modes of flashing via the Crazyradio PA dongle:
-
Warm Boot (
make flash) [Default]: Use this if the Crazyflie is already running its normal firmware. It will connect to the firmware, automatically reboot the drone into bootloader mode, flash the AI-deck, and restart it.make flash # Override radio URI (default is radio://0/80/2M/E7E7E7E7E7) make flash URI=radio://0/80/2M/E7E7E7E7E7 -
Cold Boot (
make flash-cold): Use this if the Crazyflie is already in bootloader mode (e.g. blue M2 LED blinking, listening on bootloader channel0) or if a previous flashing attempt was interrupted.make flash-cold
To flash directly using a JTAG cable (e.g. Olimex ARM-USB-OCD-H):
make flash-jtagmake cleanBefore running the client, connect your host to the AI-deck's WiFi access point. The AI-deck broadcasts its own AP when powered on. The client connects via TCP to 192.168.4.1:5000.
python -m client.main
python -m client.main --sim
python -m client.main --help- Process images (
Ctrl+P) — enables gesture recognition and hand tracking overlay. - Settings (
Settings → Settings…) — live-tune flight speed, yaw, altitude limits, gesture thresholds, and tracking gains without restarting. - Drone takes off directly into tracking mode (hand following). Press
Tto toggle tracking off/on. - Simulation mode (
--sim) — runs without a drone: synthetic telemetry, no WiFi required. Useful for testing the UI and control logic.
Images and processed frames are saved to:
data/images/ # raw frames (when Save Images is enabled)
data/processed/ # vision overlay frames (gesture landmarks, labels)
Telemetry, actions, and perception events are streamed to InfluxDB 3 in real time.
Recording and analysis scripts used to capture and evaluate performance data (gesture accuracy, latency, JPEG quality, resource usage) are in tools/.
With Process Images enabled, MediaPipe classifies hand gestures from the live camera feed.
| Gesture | Action |
|---|---|
Thumb_Up |
Take off (from ground only) |
Thumb_Down |
Land |
Victory |
Toggle hand-tracking mode on / off |
Gesture confidence threshold, debounce, and hysteresis are tunable live in Settings → Gesture.
| Key | Action |
|---|---|
Space |
Take off / Land |
Esc |
Emergency stop — cuts motors immediately |
T |
Toggle hand-tracking mode |
C |
Stabilise — stop lateral motion, hold altitude |
M |
Motor test — spins briefly on ground, won't lift |
| Key | Action |
|---|---|
↑ / ↓ |
Forward / Backward |
← / → |
Strafe left / right |
Shift + ↑↓←→ |
Fast forward / backward / strafe |
A / D |
Yaw left / right |
Shift + A / D |
Fast yaw |
W / S |
Altitude up / down |
Shift + W / S |
Larger altitude step |
| Key | Action |
|---|---|
Ctrl+P |
Toggle image processing (gesture recognition) |
Ctrl+S |
Toggle image saving |
Ctrl+/ |
Show keyboard shortcuts |
Ctrl+Q |
Exit |
Note:
client.mainruns this cleanup automatically at startup. Use these commands only when running nodes manually.
rm -rf /tmp/iceoryx2/*
rm -rf /dev/shm/iox2_*If you get Failed to flash: [Errno 13] Access denied (insufficient permissions) when trying to run cfloader or flash:
-
Ensure the Bitcraze
udevrules are installed in/etc/udev/rules.d/99-bitcraze.rules:# Crazyradio (normal operation) SUBSYSTEM=="usb", ATTRS{idVendor}=="1915", ATTRS{idProduct}=="7777", MODE="0664", GROUP="plugdev" # Bootloader SUBSYSTEM=="usb", ATTRS{idVendor}=="1915", ATTRS{idProduct}=="0101", MODE="0664", GROUP="plugdev"
-
Make sure your user is in the
plugdevgroup:sudo usermod -aG plugdev $USER -
Unplug the Crazyradio dongle from the USB port and plug it back in so that the permissions are applied to the active device node.
