Skip to main content

Crazyflie drone light show choreography library

Project description

flaight

light → flight → flaight.

By Dominic Neuburg — MIT License

PyPI version Python versions License: MIT CI

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flaight-0.1.0.tar.gz (28.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flaight-0.1.0-py3-none-any.whl (23.4 kB view details)

Uploaded Python 3

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

Hashes for flaight-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c599e61af1925d5c9fc21d27dd94e074b4c5a992bce463cb026c3dbd7be0e779
MD5 2ee110a72b7c74851270891fe916da7f
BLAKE2b-256 b114dfa57c5849615aa1b6dd9331f5bf0ea0dc8cb7bbfb1e6f4f3da69ee15cbb

See more details on using hashes here.

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

Hashes for flaight-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4b9ca9abcb4d3250ba933858e7dfa944ba42d39195b2acd3016bf0a2c1c30d8f
MD5 e4320f8b2faeb1343e99a1c5ca2acaae
BLAKE2b-256 2591dca62aa84b819ba6effbc3f3e80c16331df4f3a087a8196fc5deae7f9342

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page