Initial checkpoint: Crazyflie mission client (DearPyGui)

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>
This commit is contained in:
2026-06-23 15:44:07 -07:00
commit eed9778e59
7 changed files with 1756 additions and 0 deletions

141
README.md Normal file
View File

@@ -0,0 +1,141 @@
# 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.22.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.22.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.71 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.22.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.