Files
cf-test-stand/README.md
K. Isom c27d91c857 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>
2026-06-23 15:12:31 -07:00

4.0 KiB
Raw Permalink Blame History

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 ImGuibased 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 (m1m4) · 2 all follow m1
motorPowerSet.m1..m4 raw PWM 065535
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.

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

No uv? Install with pip install -r requirements.txt into a virtualenv and run python main.py instead.

Run

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 M1M4 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