A modular framework for controlling Crazyflie swarms using a ROS 2 backend and a clean Python SDK.
OptiSwarmCF is designed for motion-capture-based robotics experiments where one or more Crazyflie drones are controlled through a layered architecture. The backend handles hardware communication, tracking integration, estimator updates, command execution, and diagnostics. The SDK provides a simpler Python interface for writing control loops and multi-agent experiments.
cftakeoff.mp4
Status
OptiSwarmCF is an experimental research framework. The current architecture is usable as a baseline for real tests, but higher-level tools such as a complete SDK orchestrator, backend CLI, and formal command-state machine are still under development.
- What is OptiSwarmCF?
- Who is this framework for?
- What problem does it solve?
- How is the project structured?
- How is the system organized?
- How does data flow through the framework?
- How are commands sent to the drones?
- What is the canonical pose contract?
- What are the main ROS interfaces?
- What does the backend do?
- What does the SDK do?
- What are the core SDK components?
- What are the requirements?
- How do I build the backend?
- How do I configure the system?
- How do I run an experiment?
- What does a minimal SDK script look like?
- How are logging and diagnostics handled?
- How do I troubleshoot common issues?
- How should the system be shut down?
- What are the current limitations?
- What is planned next?
- What is the recommended development workflow?
- How should I cite or describe the project?
OptiSwarmCF is a ROS 2 and Python-based software stack for controlling one or more Crazyflie drones using an external motion-capture system, such as OptiTrack, and Crazyflie communication through cflib and Crazyradio.
The framework is split into two main components:
- a ROS 2 backend, responsible for hardware-facing operations;
- a Python SDK, responsible for user-facing experiment scripting.
The main design goal is to let the user focus on control logic, trajectory tracking, and swarm coordination, while the backend manages ROS topics, services, Crazyflie communication, estimator updates, and diagnostics.
OptiSwarmCF is intended for:
- students working on experimental robotics projects;
- researchers developing feedback-control or trajectory-tracking algorithms;
- developers testing multi-agent coordination strategies;
- users who need a Python interface over a ROS 2 and Crazyflie-based experimental setup.
Typical use cases include:
- single-drone position control;
- trajectory tracking;
- swarm coordination;
- formation control;
- rendezvous and consensus experiments;
- motion-capture-based closed-loop tests.
Working with physical Crazyflie drones in a multi-agent setup usually requires several separate components:
- motion-capture acquisition;
- coordinate-frame normalization;
- ROS topic and service management;
- Crazyradio and
cflibcommunication; - external position updates for the Crazyflie estimator;
- takeoff, landing, and EKF reset procedures;
- repeated execution of experimental scripts.
OptiSwarmCF reduces this complexity by separating responsibilities clearly. The backend is responsible for stable hardware interaction, while the SDK provides high-level Python abstractions for experiments.
optiswarmcf/
├── backend_ros2/ # ROS 2 colcon workspace
│ └── src/
│ ├── mocap_bridge_ros2/
│ └── cf_bridge/
├── scripts/ # Shell scripts and launch helpers
├── sdk/ # Python SDK package
│ └── src/optiswarmcf/
├── examples/ # Example controllers and experiments
└── README.md
The repository contains both the backend and the SDK because the two parts are designed to work together, but they keep different responsibilities.
The architecture is divided into three conceptual layers.
flowchart TB
A[Experiment Layer\nUser control scripts] --> B[SDK Layer\nPython API]
B --> C[Backend ROS Layer\nROS topics, services, diagnostics]
C --> D[Crazyflie drones]
E[Motion Capture System\nOptiTrack / VRPN] --> C
The backend communicates directly with the infrastructure and hardware. It receives motion-capture data, normalizes poses, connects to Crazyflies, forwards external position updates, exposes command interfaces, and publishes diagnostics.
The SDK is a Python client library built on top of the backend ROS API. It hides publishers, service clients, executor handling, and topic-name boilerplate behind objects such as RosContext, OptiTrack, CrazyflieAgent, and Swarm.
This is where the user writes the actual control logic, trajectory generation, swarm behavior, and test scripts.
The state-estimation flow is:
flowchart LR
A[OptiTrack / VRPN] --> B[mocap_bridge_ros2]
B --> C[Canonical pose topic]
C --> D[cf_bridge]
C --> E[SDK]
D --> F[Crazyflie estimator]
E --> G[User control algorithm]
Step by step:
- The tracking system publishes raw rigid-body poses, typically through VRPN ROS topics.
mocap_bridge_ros2receives the raw motion-capture pose.mocap_bridge_ros2converts it into a canonicalPoseStampedrepresentation.cf_bridgeconsumes the canonical pose and forwards it to the Crazyflie estimator.- The SDK reads the same canonical state and exposes it to user code.
- The user algorithm computes the next command.
The command flow is:
flowchart LR
A[User code] --> B[SDK]
B --> C[ROS command topics / services]
C --> D[cf_bridge]
D --> E[cflib / High Level Commander]
E --> F[Crazyflie firmware]
The current command model is based on:
- topic-driven position commands;
- service-driven discrete actions.
Typical commands include:
- absolute position commands;
- relative position commands;
- takeoff;
- land;
- EKF reset.
When a target position is sent without an explicit duration, the backend estimates the motion duration using the configured nominal speed, minimum duration limits, and safety margins.
The canonical pose contract is one of the main architectural rules of OptiSwarmCF.
Pose normalization must happen once, and only once, inside the motion-capture bridge.
After mocap_bridge_ros2 produces the canonical pose:
cf_bridgemust treat it as final;- the SDK must treat it as final;
- user code must operate in the canonical frame;
- coordinate corrections should not be duplicated elsewhere.
This rule avoids hidden frame transformations and inconsistent coordinate fixes across the stack.
A typical canonical pose topic follows this convention:
/mocap/<drone_id>/pose
For example:
/mocap/cf1/pose
/mocap/cf2/pose
/mocap/cf3/pose
The backend exposes a minimal ROS API built around topics, services, and diagnostics.
Canonical pose topics are produced by mocap_bridge_ros2 and consumed by both cf_bridge and the SDK.
/mocap/<drone_id>/pose
Message type:
geometry_msgs/msg/PoseStamped
Absolute position command:
/<ns>/cmd_pos
Message type:
geometry_msgs/msg/PoseStamped
Relative position command:
/<ns>/cmd_pos_relative
Message type:
geometry_msgs/msg/Twist
/<ns>/takeoff
/<ns>/land
/<ns>/ekf_reset
Service type:
std_srvs/srv/Trigger
/<ns>/diag
Message type:
diagnostic_msgs/msg/DiagnosticArray
The backend is the execution layer between real hardware and user-facing control code.
It consists mainly of the following components.
The VRPN client connects to OptiTrack/Motive and publishes raw ROS topics containing the rigid-body poses.
The motion-capture bridge normalizes VRPN topics and republishes canonical pose topics:
/mocap/<drone_id>/pose
Its responsibilities include:
- selecting the canonical frame id;
- applying the configured axis convention;
- adapting source topics to the canonical topic structure;
- keeping coordinate normalization centralized.
The Crazyflie bridge interfaces with the physical drones through cflib and Crazyradio.
Its responsibilities include:
- opening and managing Crazyflie radio links;
- setting Crazyflie firmware parameters;
- subscribing to canonical motion-capture topics;
- sending external position updates to the Crazyflie estimator;
- exposing command topics and services;
- executing high-level Crazyflie commands;
- checking pose availability;
- rejecting invalid or unsafe pose data;
- publishing machine-readable diagnostics.
The backend should not contain experiment logic, swarm algorithms, or user-specific control policies.
The SDK is the user-facing Python layer.
It is not a replacement for the backend. Instead, it is a thin client library that simplifies access to the backend ROS API.
The SDK hides:
- ROS publisher creation;
- ROS service-client calls;
- executor handling inside the SDK process;
- topic naming boilerplate;
- low-level backend wiring.
The SDK exposes:
- a ROS context/session object;
- a motion-capture client;
- a single-drone agent abstraction;
- a swarm abstraction;
- structured pose and observation models.
Important
The SDK creates its own ROS node/context for communication, but it does not start the backend nodes. The backend must already be running before the SDK-based experiment script is executed.
Owns the ROS runtime used by the SDK. It initializes ROS when needed, creates a node, starts an executor in a background thread, checks executor health, and shuts down cleanly.
Subscribes to canonical motion-capture pose topics and provides convenient methods to access drone poses and wait until tracking data is available.
Represents one logical Crazyflie from the user point of view. It wraps the command topics and services exposed by cf_bridge.
Groups multiple CrazyflieAgent objects and provides operations that naturally apply to several drones.
Small immutable data models used to expose structured state to user code.
Install ROS 2 tools:
sudo apt update
sudo apt install -y python3-colcon-common-extensions python3-rosdepInitialize rosdep:
sudo rosdep init
rosdep updateInstall the Crazyflie Python library:
python3 -m pip install --user cflibDepending on your setup, you may also need:
- a working ROS 2 installation;
- access permissions for Crazyradio USB devices;
- OptiTrack/Motive running on the tracking machine;
- VRPN ROS 2 support;
- Python dependencies required by the SDK package.
cd backend_ros2
rosdep install --from-paths src --ignore-src -r -y --skip-keys ament_python
source /opt/ros/$ROS_DISTRO/setup.bash
colcon build --symlink-install
source install/setup.bashVerify that the backend packages are visible:
ros2 pkg list | grep cf_bridge
ros2 pkg list | grep mocap_bridge_ros2The project uses configuration files to describe stable backend behavior.
Configuration should describe:
- drone identities;
- Crazyflie radio URIs;
- ROS namespaces;
- motion-capture source topics;
- canonical frame settings;
- timing defaults;
- filtering and validation thresholds;
- diagnostics cadence.
Configuration files should not encode experiment-specific control logic.
This file defines the mapping from VRPN topics to canonical motion-capture topics.
frame_id: map
topic_prefix: /mocap
axis_mode: identity
sources:
cf1:
topic: "/VRPN_TOPIC_CF1"
type: "pose_stamped"The output topic is:
/mocap/cf1/pose
This file defines the Crazyflie drones and the backend behavior.
drones:
- id: "cf1"
uri: "radio://0/90/2M/E7E7E7E702"
ns: "/cf1"
mocap_topic: "/mocap/cf1/pose"
with_orient: false
start_hl: true
hl_only: true
estimator: 2
controller: 2
en_high_level: true
speed: 0.30
min_abs_duration: 1.0
min_rel_duration: 0.5
abs_duration_margin: 0.5
rel_duration_margin: 0.3
fallback_abs_duration: 1.5
diag_period_sec: 0.5
pose_stale_after_sec: 0.5
extpos_max_rate_hz: 50.0
max_pose_jump_m: 0.25
min_valid_z_m: -0.02
cache_dir: ./.cf_cacheThe framework is designed to be executed through a launcher script that starts the backend and runs the user controller.
./scripts/run_experiment.sh examples/minimal_controller.pyA typical experiment follows this sequence:
- Start the motion-capture source.
- Start the VRPN client.
- Start
mocap_bridge_ros2to normalize tracking data. - Start
cf_bridgeto connect to the Crazyflies. - Launch a Python script using the SDK.
- Wait until poses are available.
- Reset the estimator if required.
- Execute the control algorithm.
- Land the drones and shut down cleanly.
The backend can remain active while multiple experiments are executed sequentially, provided that each experiment leaves the system in a safe state.
from optiswarmcf import (
RosContext,
OptiTrack,
OptiTrackConfig,
CrazyflieAgentConfig,
Swarm,
SwarmConfig,
)
def main() -> None:
with RosContext() as ctx:
mocap = OptiTrack(
ctx,
OptiTrackConfig(
pose_topics={
"cf1": "/mocap/cf1/pose",
"cf2": "/mocap/cf2/pose",
}
),
)
swarm = Swarm(
ctx,
SwarmConfig(
mocap_pose_topics={
"cf1": "/mocap/cf1/pose",
"cf2": "/mocap/cf2/pose",
},
agents={
"cf1": CrazyflieAgentConfig.from_drone_id("cf1"),
"cf2": CrazyflieAgentConfig.from_drone_id("cf2"),
},
),
)
if not swarm.wait_all_ready(mocap, tmax=10.0):
raise RuntimeError("Not all drones received a valid pose")
swarm.takeoff_all(timeout_sec=5.0)
swarm.go_to_abs("cf1", 0.0, 0.0, 0.7)
swarm.go_to_abs("cf2", 0.5, 0.0, 0.7)
swarm.land_all(timeout_sec=5.0)
if __name__ == "__main__":
main()OptiSwarmCF distinguishes between logs and diagnostics.
Logs are used for human-readable runtime events, warnings, and failures.
Examples:
- Crazyflie connection status;
- invalid pose warnings;
- service-call failures;
- command execution errors.
Diagnostics are structured periodic status messages intended for monitoring and machine-side introspection.
They can report information such as:
- connection state;
- pose availability;
- pose age;
- pose rate;
- readiness state;
- estimator/controller configuration;
- last command.
This distinction avoids overloading the log stream and enables future dashboards, CLI tools, and health-check utilities.
Check whether ROS 2 nodes are running and whether the daemon is active:
ros2 node list
ros2 daemon statusCheck that:
- OptiTrack/Motive is running;
- the VRPN client is publishing topics;
mocap.yamlmaps the correct source topics;topic_prefixis set consistently with the SDK andcf_bridgeconfiguration.
Check that:
- Crazyradio is connected;
- the Crazyflie URI is correct;
- the Crazyflie is powered and reachable;
cf_bridge.yamluses the correct namespace and motion-capture topic;- the backend has received at least one valid pose before takeoff.
Test that cflib is installed:
python3 -c "import cflib"Check that:
- a valid motion-capture pose is being received;
- the pose topic name matches the one configured in
cf_bridge.yaml; - the pose is not stale;
- the drone is connected before calling
/ekf_reset.
From inside backend_ros2:
rm -rf build install log
colcon build --symlink-install
source install/setup.bashStop the system in this order:
cf_bridgemocap_bridge_ros2- VRPN client
- OptiTrack/Motive, if needed
This order helps avoid leaving the drones without a valid backend while the tracking system is still active.
The current architecture is functional but not final.
Known limitations include:
- the SDK still requires manual construction of context, motion-capture client, and swarm objects;
- a high-level experiment/session entry point is not yet implemented;
- backend command arbitration is still minimal;
- a formal command-state machine is not yet available;
- diagnostics are available at the backend level, while SDK-side diagnostics tooling is still limited;
- advanced low-level command streaming modes are not yet part of the stable public API.
These limitations are architectural, not accidental: the current version prioritizes a clear separation of responsibilities before adding more automation.
Planned improvements include the following.
A higher-level SDK object should automatically build and connect:
RosContext;OptiTrack;Swarm.
This would reduce boilerplate and make experiment scripts cleaner.
A future CLI could:
- validate configuration files;
- select hardware profiles;
- launch backend nodes;
- run health checks;
- inspect diagnostics.
The Crazyflie backend should evolve toward a minimal internal command-state machine to improve command arbitration and avoid unsafe mixing of command modes.
Future versions may support lower-level command streaming while keeping it clearly separated from high-level motion commands.
The architecture is designed to support additional state sources and adapters without changing the SDK-facing control API.
For the current version:
- Configure the motion-capture and Crazyflie backend files.
- Start the backend through ROS launch files or scripts.
- Run an SDK-based Python experiment.
- Check pose readiness before sending commands.
- Keep frame transformations centralized in
mocap_bridge_ros2. - Keep experiment logic out of the backend.
- Use diagnostics to verify backend health.
- Land and reset the system cleanly after each test.
A more polished future workflow should look like:
- start or configure the backend through a CLI;
- launch an experiment through a high-level SDK entry point;
- inspect system health through diagnostics tools;
- repeat experiments without restarting the backend.
A concise description of the project is:
OptiSwarmCF is a ROS 2 and Python-based framework for motion-capture-driven Crazyflie experiments, designed to separate hardware interaction, ROS backend infrastructure, and user-facing multi-agent control logic.
For thesis or academic writing, the key architectural idea is the separation between:
- the motion-capture backend;
- the Crazyflie backend;
- the user-facing SDK;
- the experiment scripts.
This separation allows the researcher to focus on control and coordination algorithms while the backend handles hardware communication, pose normalization, command execution, and diagnostics.