ROS 2 platform plugin that bridges Aerostack2 with Crazyswarm2, enabling single-drone and swarm control of Crazyflie UAVs through the AS2 framework.
Aerostack2 (missions, planners, controllers)
│
▼
as2_platform_crazyswarm ←─── this package
│
▼
Crazyswarm2 server (crazyflie_server node)
│
▼
Crazyflie hardware (via USB Crazyradio PA)
The platform node subscribes to AS2 control commands and translates them to Crazyswarm2 topics/services. Sensor data flows in the opposite direction: Crazyswarm2 publishes odometry, status, and IMU; this node reformats and re-publishes them as AS2 sensor streams.
For motion-capture setups an external mocap bridge node is also required — it translates rigid-body poses from your mocap system into the /poses topic that Crazyswarm2 consumes to feed the drone's Kalman filter.
The minimum set of ROS 2 packages required to build and run this platform is:
| Package | Source repo | How to get |
|---|---|---|
crazyflie_server_py |
Crazyswarm2 | build from source (no apt package) |
crazyflie_interfaces |
Crazyswarm2 | build from source (no apt package) |
motion_capture_tracking_interfaces |
motion_capture_tracking | build from source (no apt package) |
as2_platform_crazyswarm |
this repo | build from source |
crazyflie_server_py and crazyflie_interfaces both live inside the crazyswarm2 repository — clone the whole repo and colcon will build both packages from it. None of these four packages are available via apt.
All other items below are either transitive dependencies pulled in by these packages or optional system libraries.
| Dependency | Notes |
|---|---|
| ROS 2 Humble or later | Tested on Humble |
| Aerostack2 | as2_core, as2_msgs |
| Crazyswarm2 | crazyflie_interfaces, crazyflie_server_py |
| motion_capture_tracking | motion_capture_tracking_interfaces |
cflib (pip install cflib) |
Crazyflie Python driver, required by crazyflie_server_py |
transforms3d (apt install python3-transforms3d or pip install transforms3d) |
Rotation math used by crazyflie_server_py |
ros-humble-tf-transformations (apt install ros-humble-tf-transformations) |
ROS 2 TF transformation utilities |
| Eigen3 | Standard system package (apt install libeigen3-dev) |
| Crazyflie firmware 2026.04 or later | Earlier firmware causes EKF instability |
Follow the official instructions for ROS 2 Humble (or later). Make sure ros-humble-desktop and python3-colcon-common-extensions are installed.
sudo apt install ros-humble-as2-core ros-humble-as2-msgsOr build from source:
mkdir -p ~/crazyflie_ws/src && cd ~/crazyflie_ws/src
git clone https://github.com/aerostack2/aerostack2.gitCrazyswarm2 and its motion_capture_tracking dependency are not on apt — clone both into your workspace:
cd ~/crazyflie_ws/src
git clone https://github.com/IMRCLab/crazyswarm2.git
git clone https://github.com/IMRCLab/motion_capture_tracking.gitInstall the Crazyflie Python driver (cflib) and transforms3d, which are required by the Crazyswarm2 Python server:
pip install cflib transforms3dcrazyflie_interfaces, crazyflie_server_py, and motion_capture_tracking_interfaces are not available via apt and must be compiled from the repos cloned above:
cd ~/crazyflie_ws
colcon build --packages-select crazyflie_server_py
source install/setup.bashThe Crazyradio PA needs a udev rule so it is accessible without root:
# Download and install the udev rule from the cflib repo
sudo curl -fsSL https://raw.githubusercontent.com/bitcraze/crazyflie-lib-python/master/udev/99-bitcraze.rules \
-o /etc/udev/rules.d/99-bitcraze.rules
sudo udevadm control --reload-rules && sudo udevadm triggerLog out and back in (or run sudo usermod -aG plugdev $USER) for the rule to take effect.
cd ~/crazyflie_ws/src
git clone https://github.com/aerostack2/as2_platform_crazyswarm.gitcd ~/crazyflie_ws
rosdep install --from-paths src --ignore-src -r -ycolcon build --packages-up-to as2_platform_crazyswarm
source install/setup.bashIn a real project you maintain two config files. This package only provides default templates — in practice you always pass your own files via launch arguments.
This is the single file that every AS2 node in your project reads. The platform node reads only the cf_name and mocap_id fields from each drone's section; other fields are consumed by the state estimator, motion controller, and behavior nodes.
# Global defaults for all nodes
/**:
platform:
ros__parameters:
cmd_freq: 100.0
info_freq: 10.0
connection_timeout: 1.0
multi_ranger_deck: false
# One block per drone — key is the AS2 namespace
drone0:
platform:
ros__parameters:
cf_name: "cf1" # must match a key under robots: in crazyflies.yaml
mocap_id: "34" # rigid-body streaming ID in Motive (only needed with mocap)
drone1:
platform:
ros__parameters:
cf_name: "cf2"
mocap_id: "35"The swarm launch discovers drone namespaces by reading every top-level key in this file that is not /**.
This file is read by the Crazyswarm2 server, not by this platform node. It maps each cf_name to its radio URI and configures firmware logging.
fileversion: 3
robots:
cf1:
enabled: true
uri: radio://0/80/2M/E7E7E7E701 # Crazyradio address printed on the drone
initial_position: [0.0, 0.0, 0.0]
type: cf21
firmware_logging:
enabled: true
default_topics:
odom:
frequency: 50 # Hz — pose + velocity consumed by this platform
status:
frequency: 10 # Hz — battery voltage consumed by this platform
custom_topics:
imu:
frequency: 100 # Hz — acc (G) and gyro (deg/s) consumed by this platform
vars: [acc.x, acc.y, acc.z, gyro.x, gyro.y, gyro.z]Each drone has three identifiers that must be kept consistent across the two config files above:
| Identifier | What it is | Where it is set | Consumed by |
|---|---|---|---|
mocap_id |
Streaming ID of the rigid body in Motive (e.g. "34") |
config.yaml per-drone block |
mocap_bridge node |
cf_name |
Logical name in Crazyswarm2 (e.g. "cf1") |
config.yaml per-drone block and crazyflies.yaml robot key |
This platform node + Crazyswarm2 server |
| Radio URI | Hardware address of the Crazyradio link | crazyflies.yaml under robots.<cf_name>.uri |
Crazyswarm2 server |
The full chain for one drone:
OptiTrack Motive config.yaml crazyflies.yaml
──────────────── ─────────── ───────────────
rigid body "34" ─────▶ drone0: robots:
platform: cf1:
cf_name: "cf1" ────────────▶ uri: radio://0/80/2M/E7E7E7E701 ──▶ hardware
mocap_id: "34"
When you add or swap a drone, update both files in sync:
- Assign a rigid-body streaming ID in Motive and note it as
mocap_id. - Add the drone to
crazyflies.yamlwith itscf_nameand radio URI. - Add the drone namespace to
config.yamlwith matchingcf_nameandmocap_id.
Add your drone with its radio URI and the required firmware logging (see template above).
ros2 launch crazyflie launch.pyros2 launch as2_platform_crazyswarm crazyswarm_launch.py \
namespace:=drone0 \
cf_name:=cf1cf_name must match the key in crazyflies.yaml. If omitted it defaults to namespace.
Add all drones to crazyflies.yaml and to your project config.yaml.
Without mocap:
ros2 launch crazyflie launch.pyWith mocap (also starts the mocap_bridge node):
ros2 launch as2_platform_crazyswarm crazyswarm_server.launch.py \
mocap:=true \
mocap_config_file:=/path/to/your/config.yamlros2 launch as2_platform_crazyswarm crazyswarm_swarm_launch.py \
swarm_config_file:=config/config.yamlOne CrazyswarmPlatform node is spawned per drone namespace found in config.yaml. The /** block provides shared defaults; per-drone blocks provide cf_name (and optionally mocap_id).
The mocap_bridge node is bundled in this package (as2_platform_crazyswarm). It translates rigid-body poses from your mocap system into the /poses topic that Crazyswarm2 uses to feed each drone's Kalman filter.
It subscribes to /mocap/rigid_bodies (mocap4r2_msgs/RigidBodies) and republishes on /poses (motion_capture_tracking_interfaces/NamedPoseArray). At startup it reads the file passed via mocap_config_file, building a mocap_id → cf_name mapping from each drone's platform.ros__parameters block. Rigid bodies whose streaming ID is not in the map are forwarded with their original name unchanged.
Launch it together with the Crazyswarm2 server via crazyswarm_server.launch.py:
ros2 launch as2_platform_crazyswarm crazyswarm_server.launch.py \
mocap:=true \
mocap_config_file:=/path/to/your/config.yamlThe mocap_bridge must receive poses at 100 Hz or faster — the Crazyswarm2 server subscribes to /poses with a 100 Hz QoS deadline.
These parameters live under <namespace>.platform.ros__parameters in your config.yaml (or in the package's config/platform_config_file.yaml for the single-drone launch).
| Parameter | Type | Default | Description |
|---|---|---|---|
cf_name |
string |
namespace (no /) |
Crazyswarm2 robot name. Must match a key under robots: in crazyflies.yaml. |
mocap_id |
string |
— | Rigid-body streaming ID in Motive. Only read by the mocap_bridge node. |
cmd_freq |
double |
100.0 |
Rate (Hz) at which command setpoints are sent to Crazyswarm2. |
info_freq |
double |
10.0 |
Rate (Hz) at which platform info (state, control mode) is published. |
connection_timeout |
double |
1.0 |
Seconds without an odometry message before the drone is declared disconnected. |
multi_ranger_deck |
bool |
false |
Set to true if a Multi-ranger deck is mounted. Enables the LaserScan sensor stream. |
| Argument | Default | Description |
|---|---|---|
namespace |
drone0 |
AS2 namespace for this drone |
cf_name |
"" (→ namespace) |
Crazyswarm2 robot name |
control_modes_file |
package default | Path to control_modes.yaml |
platform_config_file |
package default | Path to platform_config_file.yaml |
| Argument | Default | Description |
|---|---|---|
swarm_config_file |
package default | Path to the project config.yaml (or any YAML with the same structure) |
control_modes_file |
package default | Path to control_modes.yaml |
platform_config_file |
package default | Path to platform_config_file.yaml (provides shared defaults) |
| Argument | Default | Description |
|---|---|---|
crazyflies_yaml_file |
config/crazyflies.yaml |
Path to the Crazyswarm2 drone registry YAML |
server_yaml_file |
config/crazyswarm_server.yaml |
Path to the crazyflie server defaults YAML |
urdf_file |
crazyswarm2 package URDF | Path to the Crazyflie URDF description |
mocap |
false |
Set to true to also launch the mocap_bridge node |
mocap_config_file |
"" |
Path to the project config.yaml used by mocap_bridge to build the mocap_id → cf_name mapping. Required when mocap:=true. |
| Topic | Type | Description |
|---|---|---|
sensor_measurements/imu |
sensor_msgs/Imu |
IMU (accelerometer + gyroscope) |
sensor_measurements/odom |
nav_msgs/Odometry |
Odometry from Crazyswarm2 |
sensor_measurements/battery |
sensor_msgs/BatteryState |
Battery voltage |
sensor_measurements/lidar/scan |
sensor_msgs/LaserScan |
Multi-ranger scan (only if multi_ranger_deck: true) |
platform/info |
as2_msgs/PlatformInfo |
Platform state (armed, offboard, control mode) |
| Topic | Type | Description |
|---|---|---|
/<cf_name>/odom |
nav_msgs/Odometry |
Odometry from Crazyswarm2 server |
/<cf_name>/status |
crazyflie_interfaces/Status |
Battery and status from firmware |
/<cf_name>/imu |
crazyflie_interfaces/LogDataGeneric |
IMU log block from firmware |
/<cf_name>/scan |
sensor_msgs/LaserScan |
Multi-ranger scan (optional) |
| Topic | Type | Description |
|---|---|---|
/<cf_name>/cmd_position |
crazyflie_interfaces/Position |
Position + yaw setpoint |
/<cf_name>/cmd_velocity_world |
crazyflie_interfaces/VelocityWorld |
World-frame velocity setpoint |
/<cf_name>/cmd_hover |
crazyflie_interfaces/Hover |
Horizontal velocity + altitude hold |
| Service | Type | Description |
|---|---|---|
/<cf_name>/arm |
crazyflie_interfaces/Arm |
Arm/disarm the drone |
/<cf_name>/emergency |
std_srvs/Empty |
Kill switch — cuts motors immediately |
/<cf_name>/notify_setpoints_stop |
crazyflie_interfaces/NotifySetpointsStop |
Signal end of offboard setpoints |
BSD-3-Clause. See LICENSE for details.
Copyright 2024 Universidad Politécnica de Madrid.