Crazyflie drone light show choreography library
Project description
flaight
light → flight → flaight.
By Dominic Neuburg — MIT License
Choreography library for Crazyflie drone light shows. This project is maintained with the help of vibe coding. Define timed sequences of movements and light effects in Python, simulate them without hardware, then fly them for real.
The centrepiece feature: describe a show in plain language — "two drones take off, fly apart and alternate between red and blue" — and flaight uses a local language model to turn that description directly into a Show object, ready to simulate or fly.
Installation
pip install -e .
Requires Python 3.10+. Hardware flight requires a Crazyradio USB dongle and cflib. The natural-language parser requires a running Ollama server.
Core concepts
A Show holds one or more named drones, each with a DroneTrack — an ordered list of (time, action) pairs called keyframes. All times are in seconds from show start. Multiple actions on the same drone at the same time are allowed (e.g. Takeoff and SetColor simultaneously).
Quick start
from flaight import Color, FadeColor, FlightRunner, Hover, Land, MoveTo, SetColor, Show, Takeoff
show = Show()
d1 = show.add_drone("cf1", uri="radio://0/80/2M/E7E7E7E701")
d1.at(0.0, Takeoff(height=1.0, duration=2.0))
d1.at(0.0, SetColor(Color.RED))
d1.at(2.0, MoveTo(x=1.0, y=0.0, z=1.0, duration=3.0))
d1.at(5.0, FadeColor(color=Color.BLUE, duration=1.5))
d1.at(6.5, Land(duration=2.0))
d1.at(6.5, SetColor(Color.OFF))
Simulate without hardware:
from flaight import DryRunRunner
DryRunRunner(show).run() # instant printout
DryRunRunner(show, realtime=True).run() # sleep-accurate timing
Fly for real:
FlightRunner(show).run()
FlightRunner runs a collision check before connecting. If two drones come within 20 cm of each other it raises CollisionError and aborts.
Actions
| Action | Key parameters | Notes |
|---|---|---|
Takeoff(height, duration) |
height in metres, duration in seconds | First action for any flying drone |
Land(duration) |
duration in seconds | Last action for any flying drone |
Hover(duration) |
duration in seconds | Stay in place |
MoveTo(x, y, z, yaw, duration) |
x/y/z in metres from origin, yaw in degrees | Absolute world-frame position |
SetColor(color) |
Color instance |
Instant LED change |
FadeColor(color, duration, steps) |
target color, fade time, interpolation steps | Smooth LED transition |
Colors
from flaight import Color
# Named presets
Color.RED, Color.GREEN, Color.BLUE
Color.WHITE, Color.OFF
Color.YELLOW, Color.CYAN, Color.MAGENTA
Color.ORANGE, Color.PURPLE
# Custom RGB (0–255)
teal = Color(r=0, g=180, b=160)
# Interpolate between two colors
mid = Color.RED.lerp(Color.BLUE, 0.5) # t in 0..1
Two-drone example
from flaight import Color, FadeColor, FlightRunner, Hover, Land, MoveTo, SetColor, Show, Takeoff
show = Show()
d1 = show.add_drone("cf1", uri="radio://0/80/2M/E7E7E7E7E7")
d2 = show.add_drone("cf2", uri="radio://0/81/2M/E7E7E7E7E7")
# cf1 — left drone, starts blue
d1.at(0.0, Takeoff(height=1.0, duration=2.0))
d1.at(0.0, SetColor(Color.BLUE))
d1.at(2.5, MoveTo(x=-0.5, y=0.0, z=1.2, duration=2.0))
d1.at(4.5, FadeColor(color=Color.RED, duration=1.5))
d1.at(6.0, Hover(duration=2.0))
d1.at(8.0, Land(duration=2.0))
d1.at(8.0, SetColor(Color.OFF))
# cf2 — right drone, mirrors cf1 with swapped colors
d2.at(0.0, Takeoff(height=1.0, duration=2.0))
d2.at(0.0, SetColor(Color.RED))
d2.at(2.5, MoveTo(x=0.5, y=0.0, z=1.2, duration=2.0))
d2.at(4.5, FadeColor(color=Color.BLUE, duration=1.5))
d2.at(6.0, Hover(duration=2.0))
d2.at(8.0, Land(duration=2.0))
d2.at(8.0, SetColor(Color.OFF))
if __name__ == "__main__":
import sys
if "--fly" in sys.argv:
FlightRunner(show).run()
else:
from flaight import DryRunRunner
DryRunRunner(show, realtime="--realtime" in sys.argv).run()
Collision validation
from flaight import validate, CollisionError
try:
validate(show) # default safety distance: 20 cm
validate(show, safety=0.5) # custom distance in metres
except CollisionError as e:
print(e)
FlightRunner and DryRunRunner both call validate automatically before running.
Generating shows from natural language (AI parser)
The centrepiece of flaight: describe a drone show in plain words — in any language — and a local language model (via Ollama) translates that description directly into flaight objects. No exec(), no generated code being run: the model returns structured JSON that flaight safely converts into a Show.
There are two modes of input:
| Mode | What you provide | What the model does |
|---|---|---|
Direct (parse) |
Explicit choreography instructions | Follows them literally — no added creativity |
Thematic (parse_thematic) |
A theme, mood, or emotion | Freely designs formations, movement arcs, and a colour palette to express it |
Prerequisites
Ollama must be running locally with a model pulled, e.g.:
ollama pull llama3.2
ollama serve
Mode 1 — direct choreography
Describe exactly what the drones should do. The model follows your instructions without adding anything not asked for.
from flaight import DryRunRunner, ShowParser
parser = ShowParser(host="http://localhost:11434", model="llama3.2")
show = parser.parse(
"Two drones take off, fly apart to opposite sides, alternate red and blue, then land.",
uris=["radio://0/80/2M/E7E7E7E701", "radio://0/80/2M/E7E7E7E702"],
)
DryRunRunner(show).run()
Mode 2 — thematic / emotional
Describe a theme, mood, or feeling. The model acts as a creative choreographer: it picks formations, movement arcs, altitude dynamics, and a colour palette that express the theme.
show = parser.parse_thematic(
"Melancholy — a lone farewell slowly fading into darkness.",
uris=["radio://0/80/2M/E7E7E7E701", "radio://0/80/2M/E7E7E7E702",
"radio://0/80/2M/E7E7E7E703"],
)
DryRunRunner(show, realtime=True).run()
Other examples of thematic prompts:
"Euphoria — an explosion of energy and colour""A thunderstorm building and breaking""Sunrise over the mountains""Tense standoff, then sudden release"
Summary before generating
Before building the full choreography, ask the model for a plain-text preview — useful to check that the description was understood or that the creative interpretation fits your vision:
# Mode 1 — summarizes the explicit instructions
summary = parser.summarize(description, uris=uris)
# Mode 2 — describes the creative concept the model would build
summary = parser.summarize_thematic(description, uris=uris)
print(summary)
Interactive session (REPL)
run_interactive starts a loop: enter a description → confirm the summary → generate the show → simulate or fly → next show.
parser.run_interactive(
uris=["radio://0/80/2M/E7E7E7E701", "radio://0/80/2M/E7E7E7E702"],
fly=False, # True → real hardware instead of dry-run
realtime=True, # play dry-run in real time
)
Saving generated shows as scripts
Any AI-generated show can be exported as a standalone Python script — for archiving, manual editing, or re-running later:
from flaight import save_script
path = save_script(show) # → ai_generated/show_<timestamp>.py
print(f"Saved: {path}")
The saved script is fully self-contained and supports the same --fly / --realtime flags as the bundled examples.
Exporting shows as scripts
Any Show — whether hand-crafted or AI-generated — can be exported as a standalone Python script:
from flaight import save_script, to_script
print(to_script(show)) # print to stdout
path = save_script(show) # ai_generated/show_<timestamp>.py
path = save_script(show, output_dir="shows") # custom directory
Coordinate system
- Origin (0, 0, 0) is the centre of the flying area.
- x/y range: roughly −2 m to +2 m.
- z = 0 is the floor; typical flying height is 1.0 m.
- Yaw is in degrees; 0° faces the positive x direction.
CLI reference
flaight — AI parser (installed command)
After pip install -e . the flaight command is available directly in your shell — no script to create.
At the start of each iteration you choose between the two input modes:
[1] Describe the choreography directly
[2] Describe a theme / emotion — AI designs the show
Mode 1 follows your instructions literally; mode 2 lets the model act as a creative choreographer and design formations, movement arcs, and a colour palette from a theme or emotion. After choosing a mode you enter a description, review a plain-text summary, confirm, and the show is generated and run.
# Basic interactive session
flaight
# Custom Ollama host and model
flaight --host http://localhost:11434 --model llama3.2
# Remote Ollama server on the local network
flaight --host http://192.168.1.10:11434 --model mistral
# Three drones with explicit URIs
flaight --uris radio://0/80/2M/E7E7E7E701 radio://0/80/2M/E7E7E7E702 radio://0/80/2M/E7E7E7E703
# Real-time dry-run
flaight --realtime
# Fly for real
flaight --fly
# Print raw LLM response for debugging
flaight --verbose
| Flag | Default | Description |
|---|---|---|
--host |
http://localhost:11434 |
Ollama server URL |
--model |
llama3.2 |
Ollama model name |
--uris |
two placeholder URIs | Ordered list of Crazyflie URIs |
--realtime |
off | Play dry-run in real time |
--fly |
off | Run on real hardware |
--verbose |
off | Log raw LLM JSON response |
--timeout |
300 |
Ollama request timeout in seconds |
Example scripts
The examples/ directory contains additional runnable scripts. All share a common set of flags:
| Flag | Description |
|---|---|
| (none) | Dry-run: print all events instantly, no hardware needed |
--realtime |
Dry-run with real-time delays (sleeps between events) |
--fly |
Execute on real Crazyflie hardware via Crazyradio |
examples/two_drones.py — hand-coded show: synchronized takeoff, mirrored movement, cross-fading colors.
python examples/two_drones.py
python examples/two_drones.py --realtime
python examples/two_drones.py --fly
examples/color_test.py — cycles both drones through eight colors using FadeColor, with a 1-second wave offset. Drones stay on the ground.
python examples/color_test.py
python examples/color_test.py --realtime
python examples/color_test.py --fly
examples/ai_show.py — generates two shows programmatically: one from explicit instructions (direct mode) and one from a theme (thematic mode). Each result is saved as a script via save_script().
python examples/ai_show.py
python examples/ai_show.py --realtime
python examples/ai_show.py --fly
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flaight-0.1.0.tar.gz.
File metadata
- Download URL: flaight-0.1.0.tar.gz
- Upload date:
- Size: 28.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c599e61af1925d5c9fc21d27dd94e074b4c5a992bce463cb026c3dbd7be0e779
|
|
| MD5 |
2ee110a72b7c74851270891fe916da7f
|
|
| BLAKE2b-256 |
b114dfa57c5849615aa1b6dd9331f5bf0ea0dc8cb7bbfb1e6f4f3da69ee15cbb
|
File details
Details for the file flaight-0.1.0-py3-none-any.whl.
File metadata
- Download URL: flaight-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b9ca9abcb4d3250ba933858e7dfa944ba42d39195b2acd3016bf0a2c1c30d8f
|
|
| MD5 |
e4320f8b2faeb1343e99a1c5ca2acaae
|
|
| BLAKE2b-256 |
2591dca62aa84b819ba6effbc3f3e80c16331df4f3a087a8196fc5deae7f9342
|