# 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.