Crazyflie motor test stand GUI (cflib + DearPyGui)
uv-managed Python app for ramping Crazyflie 2.1 / Bolt motors on a
thrust test stand over a Crazyradio.
- cf_link.py: thread-safe cflib wrapper — scan/connect, supervisor
arming (send_arming_request) with legacy forceArm fallback, motor
override via motorPowerSet params, battery + estimator-state logging,
firmware-console capture, and a log buffer for the UI console.
- app.py: DearPyGui UI with a per-frame ramp loop.
* Linked / individual motor control, sliders + numeric entry (percent)
* Max-throttle limit, ramp rate, EMERGENCY STOP (button + SPACE)
* ARM button: disabled when disconnected, green when ready, yellow
when armed
* Telemetry: estimator-state panel (pose/yaw/flow), throttle + battery
plots that start at zero and only scroll while armed (freeze on disarm)
* Live console panel (app + cflib + firmware messages)
Benign cflib warnings (stale log blocks, missing optional params) are
filtered; safety teardown disarms on disconnect/quit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
94
README.md
Normal file
94
README.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# test-stand
|
||||
|
||||
A small GUI for ramping **Crazyflie** motors on a thrust/verification test
|
||||
stand. Connect to a Crazyflie 2.1 (`cf21`) or Bolt (`cfbl`) over a Crazyradio,
|
||||
arm the motor override, and ramp motors **individually or all together** with a
|
||||
configurable rate and a hard max-throttle limit.
|
||||
|
||||
Built with **[cflib]** (the official Crazyflie Python library) for comms and
|
||||
**[DearPyGui]** (a Dear ImGui–based UI) for the front-end.
|
||||
|
||||
> **Why Python and not C++?** There is no official, full-featured C++ Crazyflie
|
||||
> API. Bitcraze's only official C++ component is `crazyflie-link-cpp` (the radio
|
||||
> link layer, which ships *Python* bindings). The complete param/logging API
|
||||
> needed for motor control is Python-first via `cflib`; the C++ alternatives are
|
||||
> third-party and lag firmware. DearPyGui keeps the imgui feel while letting the
|
||||
> comms stay on the supported path.
|
||||
|
||||
## How motor control works
|
||||
|
||||
The firmware exposes a motor-override param group used for exactly this kind of
|
||||
bench testing:
|
||||
|
||||
| Param | Meaning |
|
||||
| ---------------------- | ---------------------------------------------------- |
|
||||
| `motorPowerSet.enable` | `0` off · `1` per-motor (m1–m4) · `2` all follow m1 |
|
||||
| `motorPowerSet.m1..m4` | raw PWM `0–65535` |
|
||||
| `system.forceArm = 1` | keeps motors armed while stationary (recent fw) |
|
||||
| `motor.batCompensation = 0` | PWM maps directly to output (repeatable tests) |
|
||||
|
||||
"Linked" mode uses `enable = 2`; "Individual" mode uses `enable = 1`. Missing
|
||||
params on older firmware are reported in the UI and otherwise skipped.
|
||||
|
||||
## Setup
|
||||
|
||||
Managed with [uv]. Requires Python 3.10+ and a Crazyradio (PA) dongle.
|
||||
|
||||
```bash
|
||||
# from the repo root — creates .venv and installs deps from pyproject.toml
|
||||
uv sync
|
||||
```
|
||||
|
||||
On Linux, allow non-root USB access to the radio (one-time) if you get
|
||||
permission errors — see Bitcraze's [USB permissions guide][udev].
|
||||
|
||||
> No uv? Install with `pip install -r requirements.txt` into a virtualenv and
|
||||
> run `python main.py` instead.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
uv run test-stand # or: uv run main.py
|
||||
```
|
||||
|
||||
1. **Scan** → pick the URI (e.g. `radio://0/80/2M/E7E7E7E7E7`) → **Connect**.
|
||||
2. Set a sensible **Max throttle limit** and **Ramp rate** first.
|
||||
3. **ARM**.
|
||||
4. Move the **Master** slider (Linked) or per-motor **M1–M4** sliders
|
||||
(Individual). Output ramps smoothly toward the slider target; live throttle
|
||||
and battery are plotted.
|
||||
|
||||
## Safety
|
||||
|
||||
- Motors never move until you press **ARM**, and throttle starts at 0.
|
||||
- The **max-throttle limit** clamps every target.
|
||||
- **EMERGENCY STOP** (button or the **SPACE** key) cuts all motors instantly.
|
||||
- Disconnecting, losing the link, or quitting the app disarms automatically.
|
||||
- Always secure the drone/motors to the stand before arming.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`Unable to find variable [system.forceArm]` / `[motor.batCompensation]`** —
|
||||
harmless. These legacy params don't exist on current firmware; the app arms
|
||||
through the supervisor (a platform arming request) instead and sets those
|
||||
params only if present. The motors are driven by `motorPowerSet.*`, which is
|
||||
what matters. (The app no longer emits this line, but older builds did.)
|
||||
- **`Error no LogEntry to handle id=N`** — a benign cflib warning: the firmware
|
||||
is streaming a *stale log block* left over from a previous run that didn't
|
||||
disconnect cleanly, or from another client (e.g. cfclient) connected to the
|
||||
same Crazyflie. It doesn't affect motor control. The app filters this line,
|
||||
but to clear it at the source: close any other client and **power-cycle the
|
||||
Crazyflie**.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
main.py entry point
|
||||
test_stand/cf_link.py cflib wrapper: connect, arm, motor PWM, telemetry
|
||||
test_stand/app.py DearPyGui UI + per-frame ramp loop
|
||||
```
|
||||
|
||||
[uv]: https://docs.astral.sh/uv/
|
||||
[cflib]: https://github.com/bitcraze/crazyflie-lib-python
|
||||
[DearPyGui]: https://github.com/hoffstadt/DearPyGui
|
||||
[udev]: https://www.bitcraze.io/documentation/repository/crazyflie-lib-python/master/installation/usb_permissions/
|
||||
Reference in New Issue
Block a user