Two-step ARM -> LAUNCH -> LAND flight flow for a Crazyflie + Flow v2 deck: - controller.py: cflib link + flight logic on one worker thread with a command queue. Supervisor arming, Kalman estimator reset/convergence wait, high-level-commander takeoff/land, configurable hover height (0.2-2.0 m), emergency stop, graceful land-on-shutdown, thread-safe snapshot()/scan(). States: DISCONNECTED/CONNECTING/READY/ARMING/ARMED/ FLYING/LANDING/EMERGENCY/ERROR. - mission_client.py: DearPyGui front-end, manual render loop polling snapshot() each frame. Context button ARM(green)->LAUNCH(blue)->LAND (yellow), secondary Disarm, hover-height slider, console log, and an Estimator state panel (x/y/z, yaw, flow counts) for drift inspection. - uv-managed (pyproject.toml + uv.lock); run `uv run mission_client.py`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
142 lines
7.3 KiB
Markdown
142 lines
7.3 KiB
Markdown
# Crazyflie Mission Client
|
||
|
||
A small [DearPyGui](https://github.com/hoffstadt/DearPyGui) cockpit for flying a
|
||
Bitcraze **Crazyflie** with a **Flow v2 deck** (same GUI binding as the
|
||
test-stand app). Arm it and the drone lifts off to **1 m** above the ground and
|
||
holds position; disarm to land gently; emergency-stop to cut the motors
|
||
instantly.
|
||
|
||
```
|
||
┌──────────────────────────────────────────────┐
|
||
│ Connection │
|
||
│ [ radio://0/80/2M/E7E7E7E7E7 ▾ ] [Scan][Connect]│
|
||
│ ──────────────────────────────────────────────│
|
||
│ Status │
|
||
│ Hovering at 1.40 m ← colour-coded │
|
||
│ Battery: 3.92 V Height: 1.41 m │
|
||
│ Flow v2 deck: attached │
|
||
│ Supervisor: armed, flying │
|
||
│ ──────────────────────────────────────────────│
|
||
│ Estimator state │
|
||
│ Pos (m): x +0.02 y -0.01 z +1.00 │
|
||
│ Yaw: +1.4 deg │
|
||
│ Flow counts: dx +5 dy -3 │
|
||
│ ──────────────────────────────────────────────│
|
||
│ Flight │
|
||
│ Hover height [====●========] 1.40 m │
|
||
│ [ LAUNCH (to 1.40 m) ] ← ARM→LAUNCH→LAND │
|
||
│ [ Disarm ] ← only while ARMED │
|
||
│ [ EMERGENCY STOP (SPACE) ] │
|
||
│ ──────────────────────────────────────────────│
|
||
│ Console [Clear] │
|
||
│ 12:00:01 I controller: [READY] … │
|
||
│ 12:00:03 I controller: [ARMED] ready to LAUNCH │
|
||
│ 12:00:08 I controller: [FLYING] Hovering 1.40 m│
|
||
│ 12:00:09 I crazyflie.console: ...fw output... │
|
||
└──────────────────────────────────────────────┘
|
||
```
|
||
|
||
Flight is a **two-step flow** with one context button:
|
||
|
||
| State | Button | Colour | Action |
|
||
|-------|--------|--------|--------|
|
||
| Ready | **ARM** | green | reset estimator + arm; drone idles armed on the ground |
|
||
| Armed | **LAUNCH (to N m)** | blue | take off to the hover height and hold |
|
||
| Flying | **LAND** | yellow | gentle descent + disarm |
|
||
|
||
While **ARMED** on the ground a separate **Disarm** button backs out without
|
||
launching (the firmware also auto-disarms after ~30 s if you never launch). The
|
||
**Hover height** slider (0.2–2.0 m) can be adjusted right up until LAUNCH (it
|
||
latches there) and locks once airborne. Below everything, a **console log** of
|
||
roughly the same height shows mission events, cflib messages, and firmware
|
||
console output (auto-scrolls while at the bottom; **Clear** empties it). Use
|
||
**Scan** to discover URIs, or type/select one in the combo before connecting.
|
||
|
||
## Hardware
|
||
|
||
- Crazyflie 2.x with up-to-date firmware (protocol v7+ recommended for the
|
||
supervisor arming system; older firmware falls back to the thrust-unlock path).
|
||
- **Flow v2 deck** mounted underneath (provides optical-flow XY + laser Z hold).
|
||
- Crazyradio PA / 2.0 plugged into the host.
|
||
- Charged battery, propellers in good shape, and **clear space above the drone**.
|
||
|
||
## Install
|
||
|
||
This project uses [uv](https://docs.astral.sh/uv/). From `autover/`:
|
||
|
||
```bash
|
||
uv sync
|
||
```
|
||
|
||
That creates `.venv/` and installs `cflib` and `dearpygui` (a self-contained
|
||
wheel with its own GL/windowing backend — no system GUI packages needed).
|
||
|
||
On Linux you also need udev permissions for the Crazyradio (see the
|
||
[cflib USB docs](https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/installation/usb_permissions/)).
|
||
|
||
> `requirements.txt` is kept for non-uv (`pip install -r requirements.txt`) setups;
|
||
> `pyproject.toml` is the source of truth for uv.
|
||
|
||
## Run
|
||
|
||
```bash
|
||
uv run mission_client.py
|
||
# Use a non-default radio URI:
|
||
CFLIB_URI=radio://0/80/2M/E7E7E7E7E7 uv run mission_client.py
|
||
```
|
||
|
||
## Flying
|
||
|
||
1. **Connect** — opens the radio link, starts telemetry, and probes for the
|
||
Flow deck. (Optionally **Scan** first and pick a URI from the combo.)
|
||
2. **ARM** — runs pre-flight checks (Flow deck present, battery ≥ 3.10 V, not
|
||
tumbled/locked), resets and waits for the Kalman estimator to converge, and
|
||
arms the motors. The drone then **idles armed on the ground** (props at idle)
|
||
in the ARMED state; the button turns blue and reads **LAUNCH**. (Back out any
|
||
time with **Disarm**; the firmware also auto-disarms after ~30 s if idle.)
|
||
3. **Set the hover height** with the slider (0.2–2.0 m, default 1 m) — adjustable
|
||
until launch — then **LAUNCH**: takes off to the chosen height (climb timed
|
||
for a gentle ~0.35 m/s) and hovers. The button turns yellow and reads **LAND**.
|
||
4. **LAND** — descends gently, stops the commander, and disarms.
|
||
5. **EMERGENCY STOP** (button or **SPACE**) — cuts the motors immediately.
|
||
On protocol-v7 firmware this latches the supervisor, so you must **reboot the
|
||
drone** before it will fly again.
|
||
|
||
Closing the window (or **Ctrl-C**) lands the drone first (if airborne) before
|
||
disconnecting.
|
||
|
||
## Drift inspection
|
||
|
||
The **Estimator state** panel streams the estimator pose and raw optical-flow counts
|
||
(`stateEstimate.x/y` + `stateEstimate.z`, `stabilizer.yaw`, `motion.deltaX/deltaY`)
|
||
so you can diagnose a small circular/looping drift ("toilet-bowl effect"):
|
||
|
||
- **Yaw creeping steadily** while hovering → heading-estimate drift; the position
|
||
hold corrects in a rotated frame and orbits. Power-cycle for a clean gyro start.
|
||
- **x/y tracing a circle** rather than jittering around zero → same orbiting hold.
|
||
- **Flow counts near zero or erratic** while the drone is clearly moving → poor
|
||
surface/lighting for the deck (try a textured, matte, well-lit floor at ~0.7–1 m).
|
||
|
||
## Safety notes
|
||
|
||
- Place the drone on a flat, level surface before arming — the estimator and
|
||
supervisor expect it level and stationary.
|
||
- The Flow deck needs a textured floor and reasonable light to hold position;
|
||
over a blank/reflective surface it can drift.
|
||
- This client commands a stationary hover at the configured height (0.2–2.0 m)
|
||
with no horizontal movement. Keep a hand near the emergency stop (spacebar)
|
||
the first few flights.
|
||
|
||
## Layout
|
||
|
||
| File | Responsibility |
|
||
|---------------------|-------------------------------------------------------------|
|
||
| `controller.py` | All flight logic + cflib link on one worker thread. |
|
||
| `mission_client.py` | DearPyGui front-end; polls `controller.snapshot()` per frame.|
|
||
| `requirements.txt` | Python dependencies. |
|
||
|
||
`controller.py` has no UI dependencies, so the flight logic can be reviewed and
|
||
driven independently of the GUI. The UI reads state through the thread-safe
|
||
`snapshot()` each frame and issues commands (`connect`/`arm`/`disarm`/
|
||
`emergency_stop`) that the worker thread executes.
|