Earlier this year we wrote abouthow we welcome contributions, and we still do! Every pull request that makes it into our repositories makes the Crazyflie ecosystem better for everyone. Today we want to tell you about our updated contribution guidelines, and why we changed them.
Over the last months more and more of the pull requests we are receiving are written with the help of AI tools. Many of them are great. But we have also noticed a pattern that makes things hard for both you and us.
A lot of the issues we have open on GitHub are not just waiting for someone to write the code. They need decisions first: how should this be solved, how does it fit with the rest of the system, and what will it mean for the future? We usually discuss these things as a team, and that discussion is often most of the work. When a pull request shows up with a ready-made solution, we have to have that discussion anyway, and sometimes the answer is that we want to go in a different direction. That is frustrating for everyone, especially the person who spent time on the code.
On top of that, we work with hardware. Code that compiles and passes CI is a good start, but it doesn’t tell us whether the Crazyflie actually behaves as expected. We always test pull requests on our platforms before merging, and it helps a lot to know what you have already tried.
Talk to us first. Comment on the issue with your proposed approach, and open a pull request once we agree. If there is no issue for your change, open one first. Fixes that don’t involve any design decision, such as typos or documentation corrections, can go straight to a pull request.
Tell us how you tested it. Let us know what hardware tests you ran and what you observed.
AI tools are welcome, but you are the author. You should understand your change and be able to discuss it in your own words.
Clear expectations. Pull requests that don’t follow the guidelines may be converted to a draft or closed. It’s never personal, it lets us spend our review time where it counts.
Where to find them
The guidelines live in one place, the General guidelines for code contributions page on our website. To make them easy to find, we are also updating the Contribute section in the README of our repositories so it links straight to them. For other ways to help out, like reporting bugs, improving the documentation or sharing your projects, have a look at our Contribute page.
Building it together
None of this is meant to scare anyone away. It’s the opposite: we want contributing to the Crazyflie ecosystem to be a good experience, where your effort ends up in the codebase instead of in a closed pull request. If something in the guidelines is unclear, or you think we got something wrong, please let us know. And as always, thank you to everyone who has contributed so far. We can’t wait to see what you build next!
49 Crazyflie 2.1 Brushless lifting off their charging docks, flying formations, and landing back to recharge, all driven from a single Crazyradio 2.0. That’s what we have been flying lately, and it’s a great showcase of what the Crazyflie ecosystem can do with a big swarm.
If 49 Crazyflies sounds familiar, you might remember 49 Crazyflies, 1 Crazyradio, and cflib2. This time the formations are a lot more dynamic. They come from a collaboration with the Institute for Data Science in Mechanical Engineering (DSME) at RWTH Aachen University, and we recorded some videos we’re really happy with.
Where the formations come from
The choreographies were generated with SWAN, a pipeline developed at DSME that turns text promt into drone trajectories. In short:
A generative video model turns the prompt into a reference video.
A point tracker converts the motion in that video into trajectories for the swarm.
A planner assigns them to individual drones, and a safety filter makes sure they are collision-free.
What makes these formations fun for us is that they are not static shapes: they move in ways that would be hard to animate by hand. That also makes them a demanding test for the swarm.
Taking it to the real world
The generated SWAN trajectories are turned into splines and deployed on a dense swarm of 49 Crazyflie 2.1 Brushless, flying under the Lighthouse positioning system and lit up by bottom-mounted Color LED decks. We won’t go through the whole setup again (covered by the earlier post), but two parts of the Crazyflie ecosystem made this kind of work practical:
Built on cflib2. All communication with the swarm goes through cflib2 over a single Crazyradio 2.0. Before takeoff, we use cflib2 to upload each drone’s trajectory to its memory, and during the show, the High-Level Commander executes it onboard. While they are flying, we log every drone’s position every 50ms, giving us a record of how closely the swarm followed the choreography.
No battery swapping. Each Crazyflie takes off and lands on its charging dock. When you want to fly the swarm many times back to back, not having to handle 49 batteries makes a huge difference.
The result is a setup where any idea can go from the computer to a real swarm without having to build the infrastructure yourself.
The videos
Enough talking, here is what it looks like. The video below shows the formations we flew: a ballerina (49 drones), a karate fighter (29 drones), and a hand making an “I love you” (ILY) sign (49 drones).
Wrapping up
Flying 49 Crazyflies through dynamic formations, with one radio, full position logging and drones that recharge themselves between flights shows how far the Crazyflie ecosystem has come for large swarms.
A big thank you to Pascal Reinhold, Alexander Gräfe and Sebastian Trimpe at DSME for their awesome work, and an extra special thanks to Alexander, who coordinated the whole project during his internship with us at Bitcraze. If you want to learn more about SWAN, check out their paper, Generative AI for Safe and Photorealistic Drone Light Shows. And if you want to fly your own swarm, cflib2 is ready to try.
Since a while back now we’ve been developing our Crazyflie CLI, a command-line client used for all things Crazyflie. The goal of the project has been to easily do simpler things with the Crazyflie without having to open the UI all the time during our development. During the development and testing of the new Crazyflie Rust library the CLI has also been used to exercise lots of the functionality before porting our main client over to it.
Features
Since the CLI has mainly worked as an internal tool for us, the feature set is growing to accommodate daily development use for us. That being said there’s lots of features that’s useful for all of our users outside Bitcraze as well. Here’s a few highlighted features:
Display the Crazyflie console
Logging and parameter sub-system access (i.e log variables and get/set parameters)
Flash firmware (nRF51/STM32 and decks)
Memory sub-system access (i.e read, write and display memory contents)
Access to Lighthouse / Loco configuration (i.e read, write and display configuration settings)
Power management (i.e on, off, sleep, reboot)
Interactivity and usability
To make CLI quick and easy to use, even for seldom used commands, we’ve added the following design features:
Documentation built it: The CLI itself has documentation and examples for commands, they can be access by writing the command+help or just leaving the command blank, for example just writing cfcli platform will list all the platform sub-commands.
Auto-complete on commands, arguments and parameter/log names: Although the CLI has documentation build it can be quicker to use tab to list commands and options if you already know what the commands do but you forgot what they are exactly or if you’re in a hurry.
Prompt the user if there’s multiple options: If you do not explicitly select an option, and there’s more than one, then you will be prompted. One example is the command cfcli log print, where if you do not specify what variables to log you will get an interactive list. Another example is cfcli bootload flash –bin firmware.bin, where it’s not specified where to flash the binary so you will be prompted to select where to flash it.
Stable CSV printouts: Although formatting of command outputs might change over time, the goal is to keep the CSV output stable to more easily use the out of the CLI as input for scripts and other programs. To enable CSV output format add the –csv option at the end of a command, like cfcli log print stabilizer.roll,stabilizer.pitch,stabilizer.yaw –csv
Package distribution
To make it easier for our users to install the CLI, and more importantly keep it up to date, we’ve decided to distribute it via package managers instead of only via a binary. On the projct page on Github you can find documentation on how to add the apt repo and brew tap. Remember to restart the terminal to get auto completion.
Examples
The intended way of using the cfcli is to first set it to use a specific Crazyflie (either via USB or radio) after which you do not have to specify the URI anymore, here’s a typical example of loading the 2026.08 release onto a Crazyflie which is connected via USB and you have a Crazyradio attached (you need this for flashing STM32/nRF firmware):
What does it take to bring a new coordination stack to the Crazyflie? In this guest post, Jim Steele, founder of Tapestry OS, shares his experience building a Zephyr-based firmware port for the Crazyflie 2.1 Brushless and using it to explore decentralized coordination with three drones. We’re happy to welcome Jimto the blog to share the work and what he learned along the way.
The Crazyflie platform has an excellent answer to the question of how to fly a drone autonomously. The firmware is well-documented and actively maintained with hardware abstraction, sensor drivers, and state estimators providing solid infrastructure that a research team can build on confidently.
The question I kept running into is one layer above that: once you have drones that can fly reliably, how do you coordinate what a collective of them does together? Not at the trajectory level (Crazyswarm2 handles that well) but at the level of distributed state: what does each drone know about where the others are, how fresh is that information, and how does the collective continue functioning correctly when a drone fails or communication is interrupted?
Most research teams answer this question by writing custom coordination firmware specific to their experiment. The result works, but it does not transfer to the next experiment or the next platform. I wanted an answer that would.
Tapestry OS provides this layer as an open-source coordination stack built on Zephyr RTOS. It includes a distributed world model, gossip-based state propagation, fault-tolerant consensus, and a declarative application API that lets domain experts express collective behavior without touching the underlying firmware. This post is about porting Tapestry to the Crazyflie 2.1 Brushless and validating its core coordination claim on three drones flying in collective formation.
The port
Tapestry’s stack has seven layers, L1 through L7. For the Crazyflie port, the work lives in L1: the Physical Substrate Interface, Tapestry’s equivalent of a board support package. We began by developing a native Zephyr port for the Crazyflie 2.1 Brushless. This had to include the flight control itself: attitude (rate + angle), altitude-hold, position-hold loops, sensor drivers, and a Lighthouse V2 deck driver. Bitcraze stock firmware was an invaluable reference (the control cascade architecture and the Lighthouse calibration math especially) though Tapestry’s implementation is written from scratch and shares no code with it.
Bring-up including L2 had some surprises, including two real bugs in Zephyr’s STM32 I2C driver that the Tapestry repository carries as patches until they are upstreamed. Streaming console output over the Crazyradio early in the port made debugging much less painful.
Lighthouse positioning took the longest. The interesting problems were pairing the base station’s sweep planes correctly from raw deck timestamps, and rejecting phantom rays from specular room reflections. The eventual implemented solution gates rays against the base station’s optical field of view (a hardware constant that survives recalibration) plus triangulation-consistency and rate-of-motion checks. Once resolved, position accuracy is solid.
Tapestry’s higher layers, including the L3 gossip transport and the L4 Collective State Manager, required no changes. That is the point: once the foundation is in place, the coordination logic does not care whether it is running on a ground robot or a drone.
The formation demo
Three Crazyflie 2.1 Brushless drones, each running Zephyr with the Tapestry substrate and collective world model, were flown in a simple formation. Each drone reads its own absolute position from the Lighthouse deck and gossips it to its peers every 500 ms over the nRF51’s peer-to-peer radio channel. The resulting world model holds fresh entries for its two peers, with staleness tracking, and continually compares its distance to each fresh peer against a target spacing: too close pushes away, too far pulls closer.
The example formation in the following video shows two drones hover and align in equilibrium. When a third powers on and flies among them, the three maintain separation until the third drone ends its mission, lands itself, and the remaining two close back into their line before landing.
There is no leader, no ground station, and no phase script: each drone runs Tapestry’s decentralized world model and shares only its own position over gossip. The recovery behavior is the part to watch closely. When one drone ends its mission, it lands and goes silent. The remaining two re-form their line about ten seconds after the departure, without any instruction from an external system. This is deliberately the same code path that would handle a real mid-flight interruption.
This is Tapestry’s first demonstration on real Crazyflie drones: a coherent shared understanding of collective state, maintained entirely through peer-to-peer gossip, driving behavior through local rules. The demo logic can be expressed as a declarative L7 Choreo with the Tapestry SDK without touching firmware.
What is available
The Crazyflie 2.1 Brushless support is in the Tapestry OS repository under Apache 2.0. It includes the steps to recreate this demo: per-drone configuration, Lighthouse calibration, and ESC provisioning.
This is an initial port. We chose Zephyr to unlock a standardized, scalable ecosystem that allows the Crazyflie to integrate with industrial-grade RTOS tooling. We chose standard PWM for this port to simplify the initial Zephyr hardware abstraction with a stable and reliable baseline for our formation coordination logic. A number of Crazyflie capabilities are not yet available on the Zephyr-based stack (Kalman estimation, flow deck, and DSHOT among them), and contributions are welcome.
If you are doing swarm research on the Crazyflie and want coordination infrastructure you do not have to build yourself, the Tapestry repository is a good starting point. Try the port, open an issue if you hit hardware-specific bugs, and share your formation experiments on GitHub or in the Bitcraze forum.
If you have set up a Lighthouse positioning system with the Crazyflie before, you are familiar with the geometry estimation step. Walking around the room holding a Crazyflie, clicking a few buttons in a wizard dialog, and hoping that at the end you get a working configuration defining a global coordinate system.
The last part – the hoping – is what we have been working on fixing.
The problem with the old wizard
The old geometry estimation wizard did the job, but it felt like a black box. You would follow the steps, add multiple samples, click “estimate geometry” and wait. If the wizard failed, either by returning a clearly wrong estimation or by not returning anything at all, you got very little information to go on. Was it a bad sample? Was the line of sight of a base station blocked? The only real solution was to clear everything and start the entire process from scratch.
We wanted geometry estimation to feel less unpredictable, and more like a process you can actually see, steer, and understand.
What’s new?
Continuous geometry estimation.
Sample collection and modification.
Live feedback during geometry estimation.
Verification samples.
Updated UI.
Continuous geometry estimation
One of the biggest changes is found underneath: geometry estimation now runs continuously in the background. Every time you add a sample, the solver recomputes the geometry right away, using whatever data you’ve collected so far. That means you get live feedback instead of a final verdict, helping you understand if the last change you made improved the geometry estimation or not.
Sample collection and modification
The sample collection process has become more controlled. XYZ-space samples are no longer logged passively as you move the Crazyflie around the room. Instead, you collect each one separately by quickly rotating the Crazyflie at the required point.
Additionally, you can now access the data used for the geometry estimation. All recorded samples remain available, so you can modify them at any time. Any changes are automatically reflected in the geometry estimation, making it easy to correct mistakes or refine the result.
Being able to modify the samples, opens up workflows that were previously impossible. For example, moving the origin of the coordinate system or re-aligning the X-axis becomes as simple as collecting new samples. Similarly, if a base station is replaced or has moved, you can delete it and add the new or updated one by collecting only a few additional samples.
Live feedback during geometry estimation
When setting up the lighthouse system, a status line tells you where things stand at all times: “Not enough samples” when the solver needs more data, “Updating…” when the solver is updating the geometry, “Uploading…” when the updated geometry is being written to the Crazyflie, and “Uploaded” when it’s confirmed. No more waiting with bated breath.
Whilst the live feedback avoids a heavily weighed pass-fail condition, much like the old system it does not guarantee that a truly accurate estimation has been uploaded to the Crazyflie, this is where the verification samples come into play.
Verification samples
This is one of the biggest advantages of this update. In addition to the samples used to estimate the geometry (estimation samples), you can now take verification samples. These are extra samples taken the same way, but not used for the estimation, giving you an independent check on how well the base stations agree on each verification sample’s position.
Previously, the only way to know if your geometry was actually good enough to fly on was to fly and see. Now the max verification sample error gives you a rough read on the quality of the geometry before the Crazyflie takes off. Then, if a particular part of the flight space isn’t well covered, you can add a few more XYZ-space samples to improve it.
Updated UI
The new workflow required a major update to the UI. We aimed to make it easier to adapt the system to your needs. This is done by having the ability to retrace your steps, allowing for more control during the set-up process. The design also has larger focus on guidance and feedback with more detailed graphics to guide you through and extra large visual confirmation for when you are collecting samples away form your screen. Additionally, the buzzer deck and any of the LED decks can now also provide confirmation.
The geometry estimation now lives directly in the Lighthouse Positioning tab instead of in a separate dialog. Click “Start set up” and the tab expands with a sample collection section, a 3D view of your base stations and samples, and (optionally) detailed tables of everything that’s been recorded.
We’re happy to announce that release 2026.08 is now available. This update ships a reworked lighthouse geometry estimator with a redesigned interactive Lighthouse tab, bidirectional DSHOT RPM telemetry, and a new deck supervisor for runtime deck health monitoring, along with a number of smaller bug fixes and quality-of-life improvements. Alongside the release, we’ve also shipped a fix for a long-standing lighthouse FPGA decode issue affecting simultaneous sensor hits. Thanks to our community contributors for their valuable additions to this release.
Major updates
Lighthouse geometry estimator rework A large rework of the lighthouse base station geometry estimation (cflib + cfclient), replacing the old batch estimation script with a continuous, interactive workflow built into the client. Base stations and samples can now be added, removed, and verified live during a session, while the updated UI provides a live status feedback. Thanks to @krichardsson and @cafeciaojoe for their work in this project.
Bidirectional DSHOT and RPM telemetry Motors driven over bidirectional DSHOT can now report their measured RPM back to the firmware. This feeds both telemetry and a new arming check: each motor is spun up and confirmed to actually be turning at the requested RPM before arming is allowed, catching a disconnected or seized motor before takeoff instead of after. Thanks to @EliaCareda for this contribution.
Deck supervisor A new deck supervisor subsystem continuously monitors deck health at runtime, mirroring the role the platform supervisor plays for the flight controller itself. A deck that stops responding or reports a fault now surfaces an explicit error code instead of a simple alive/not-alive flag, so problems can be diagnosed rather than just detected.
Radio platform-packet handling moved into the nRF51 interrupt All null CRTP platform packets — P2P, battery-voltage polling, bootloader, radio commands, safelink init — are now handled entirely inside the nRF51’s radio interrupt handler instead of being passed through safelink to the STM32. Battery-voltage queries in particular are answered directly from a packet rebuilt every ADC cycle, keeping this traffic off the application processor and giving these commands a fast, consistent response path.
Lighthouse FPGA: simultaneous sensor hit decode fix Fixes a bug where a sweep crossing two sensors at (nearly) the same instant broke LFSR channel decoding and caused the firmware to discard the entire sweep block. This update belongs to the lighthouse-fpga repository. You can update your Lighthouse deck by flashing the 2026.08 release firmware via cfclient.
Persistent parameters settable from apps Parameters marked PARAM_PERSISTENT can now be set from firmware apps, not just from a connected client, and their stored value is enforced/restored at boot.
For a long time, communication inside the Crazyflie was fairly simple. The main STM32 microcontroller handled flight control, sensors, motors and most of the system logic. A second microcontroller handled the radio and power management. The two processors communicated using Syslink, a small packet protocol over UART. Syslink was designed for one fixed connection between two known processors. Both sides were developed together, the number of packet types was limited, and there was little need for discovery or elaborate flow control.
This worked quite well for the system it was designed for.
Crazyflie 2.0 system architecture
Over time, however, the Crazyflie platform has changed. More and more expansion decks contain their own processors, firmware and internal state. Some decks are no longer simply collections of sensors connected over I2C or SPI. They are small embedded systems of their own. This has made communication between processors a recurring part of the platform rather than a special case.
Most of what we are talking about in this blog post is very much work in progress: this is a work that we are doing as part of yet another deck that will have an microcontroller.
From Syslink to CPX
Syslink was created for one link between two processors. Later, when developing the AI deck, we encountered a different problem. The system contained several processors and several communication links. Packets could travel between the Crazyflie STM32, the ESP32, the GAP8 and a computer connected either through Wi-Fi or through the normal Crazyradio path. This led to CPX, the Crazyflie Packet eXchange protocol. One of its main roles is routing packets between processors and transports.
CPX solved an important practical problem, and routing is still useful. At the same time, our experience with it has shown that not every processor link needs the whole routed communication model. For a connection between two known processors, requiring routing and application functions to be part of the same protocol can make the stack heavier than necessary.
The current work is therefore not intended to replace CPX. Instead, we are trying to separate the different responsibilities more clearly. CPX could eventually be one of the services transported over a lower-level peer-to-peer link when routing is required.
Yet another processor on a deck
We are currently working on a new deck with a processor that needs to communicate with the Crazyflie. The fastest solution would have been to create another deck-specific UART protocol. The endpoints are known, the initial set of commands is fairly small, and such a protocol would probably have been finished much earlier. That would have been a completely reasonable engineering choice but it would also have added one more custom processor protocol to the platform.
We expect processor-based decks to become more common, so we decided to use the this new deck as an opportunity to work on something more reusable. The new deck does not strictly require all the infrastructure we are building. Rather, it provides a real product in which we can develop and test a communication model that may also be useful elsewhere.
Building the stack in layers
One lesson from Syslink and CPX is that we should avoid putting every communication concern into one protocol. The current new design is split into layers:
physical transport ↓ transport binding ↓ peer-to-peer Link ↓ services such as Control, CPX or Console
The physical transport may be UART, SPI, I2C or another suitable connection.
A transport binding handles the details required to move frames over that transport, such as framing, integrity checks and resynchronization.
Above this is the Link between two embedded peers. It is responsible for establishing a communication session, negotiating compatible parameters, discovering available services and managing bounded buffers.
The Link does not need to understand Wi-Fi configuration, console text or routed CPX packets. These belong to services above it. This separation is important. It allows a simple product to expose only the services it needs, while a more complex system can add routing, structured control, console traffic or other services without changing the basic Link.
The initial implementation uses UART for our new deck, but the communication model itself is not intended to be UART-specific.
A first useful example: Console
Since starting this work, we have implemented a first Console service on top of the new Link.
This also led to a more generic console interface in the Crazyflie firmware. Instead of assuming that all console output comes from the main STM32, the Crazyflie can now expose several console sources. With the current prototype, a client can receive the Crazyflie console, the new deck console, or both. The available consoles can be queried, and by default the normal Crazyflie console continues to behave as before. This is a fairly small feature, but it is a good example of what the architecture is intended to make possible.
Today, the nRF51 radio microcontroller is largely opaque from a debugging point of view. This was quite intentional in the original Crazyflie architecture: the STM32 was the processor we expected users to develop on, while the nRF51 mainly provided supporting functions such as radio and power management. That boundary has become less clear over time. The radio firmware has grown more capable, and both we and some users increasingly work on it directly. If the nRF51 eventually uses the same Link model, it could expose the same Console service. The existing client-side console infrastructure could then show its output without requiring another custom protocol and another special debugging path.
This is the kind of reuse we are looking for.
Control and reusable capabilities
Another service we are exploring is Control.
Control provides a structured way to get values, set values and call commands. It is somewhat similar to an RPC interface, but it also exposes organized sets of readable and writable state. Within Control, related functionality can be described using versioned contracts.
Wi-Fi provisioning is a useful example: adding Wi-Fi support to the AI deck was not only a matter of implementing a command in the deck firmware. The Crazyflie needed to forward the operation, the client library needed an API, and a user-facing tool was required to configure the connection. A future Wi-Fi-enabled deck would otherwise need another product-specific implementation of much of the same path.
With a common Wi-Fi contract, a deck could advertise through its Control service that it supports Wi-Fi provisioning. Generic software could discover this capability and use an existing interface for it, without needing to know which deck or processor implements it. More importantly this means that we can tie Wifi credential to a Crazyflie and not to the deck: for our current work we have decided to store the wifi credential in the Crazyflie. This means that, once setup, the Crazyflie will connect the same wifi network independent of which wifi-capable deck is attached to it. It makes the system much easier to work with. We may update the AI deck to follow the same model in the future.
The same idea can apply to much simpler features. Several decks contain visible RGB LEDs. Instead of every deck exposing a different way of setting a color, they could implement one common LED-control contract and use the same client API.
This is the higher-level counterpart to reusing the Link. The Link avoids rebuilding processor-to-processor communication, while services and contracts make it possible to reuse functionality above it.
Where this could lead
The protocol developed for our new deck is not yet a complete Syslink2 specification for the whole Crazyflie ecosystem. The current work mainly demonstrates the point-to-point Link architecture: sessions, service discovery, versioning and flow control based on actual available buffers. Console already provides a concrete service on top of it, while Control and its contracts are still more exploratory. There are also larger questions we are deliberately leaving outside the first Link. Routing remains a separate concern, and CPX may continue to provide it where it is needed.
More generally, discoverable services could make parts of the Crazyflie platform easier to extend. Today, replacing or experimenting with a subsystem often requires changes across several parts of the stack simply because both ends need to know exactly what hardware is present. If processors can instead advertise standard services and capabilities, some of those dependencies could become looser. For example, a future radio processor could expose a radio service instead of being identified only by its position in the hardware architecture. In principle, another processor or even a deck could expose a compatible service while the rest of the system continues to use the same interface.
This is still very much a direction rather than a finished architecture, but it is one of the possibilities that makes this work interesting to us. The new deck gave us a practical place to start. So far, the layered model seems to fit both our immediate use case and some of the communication problems we expect to encounter more often in future Crazyflie platforms.
Inheriting an experiment is often harder than building one. A PhD student graduates. A postdoc moves on. A sensor gets discontinued, and six months later, reproducing the earlier work means reconstructing a software environment, rebuilding a hardware setup, and re-deriving assumptions that were never written down anywhere. The paper is still there, but the experiment usually isn’t.
Reproducibility is normally discussed in terms of published results: can someone else get the same numbers from the same method, but in practice, most labs run into a different problem first, and it’s a more mundane one. Continuity. A new student needs to take over a project without starting from scratch, or a collaborating lab needs to replicate a setup without rebuilding the surrounding infrastructure. Then the experiment itself needs room to evolve without dragging everything underneath it into a rewrite.
Preserving context, not just code
Code and datasets are easier to share now than they’ve ever been, and that’s real progress. But a repository is not the same thing as an experiment. What a new student actually inherits is a pile of things that rarely make it into a GitHub repo: hardware configurations, positioning system calibration, flight scripts tuned by trial and error, a specific combination of dependency versions that happens to work, and a layer of tacit lab knowledge. That last part is usually the stuff someone would tell you in five minutes at the whiteboard. It never gets written down.
The problem compounds as the work moves forward. A sensor gets replaced. ROS moves to a new version. New questions branch off from the original project, and each one drags a little more infrastructure along with it.
A stable platform doesn’t solve reproducibility. Poorly documented assumptions are still poorly documented assumptions. But it can remove one source of uncertainty: the platform underneath the experiment hasn’t become something completely different. That matters when the next researcher needs to understand what was built before they can build on it.
Compatibility over time has research value
Infrastructure that survives across multiple projects tends to become more useful with time. A platform a lab can keep using across student cohorts and research directions accumulates knowledge around it: working configurations, scripts, extensions, troubleshooting experience and, eventually, people who know how the pieces fit together.
That’s one of the less obvious benefits of the Crazyflie® ecosystem. The platform has changed considerably over the years, as it should. Hardware has evolved, APIs have developed and tools have come and gone. But much of the underlying architecture and the way the pieces relate to each other remains recognizable.
That technical lineage matters. Someone returning to Crazyflie research from five or even ten years ago won’t find exactly the same system, but neither will they find an unrelated one. The communication model, firmware architecture, development tools and approach to hardware extensions have evolved incrementally rather than being repeatedly replaced.
It means older work can remain useful as a starting point rather than simply becoming a historical reference.
Reproducibility between people, not just between runs
Reproducing an experimental result and continuing someone else’s research aren’t quite the same problem. But they share much of the same infrastructure.
Robotics has irreducible uncertainty in it, and no platform removes that. What a well-designed, long-lived platform can do is lower the cost of understanding, reconstructing and extending previous work.
It’s a less visible kind of value than a benchmark result. But in labs where a project outlives the people who started it, that continuity is often what makes cumulative research possible at all
Did you know?
Today’s firmware still supports Crazyflie 2.0 (apart from certin recent features)
You can take over someone else’s sysid work. If you would build your own platform you would need to redo that. (When the Crazyflie 2.1 Brushless came out immediately people rushed to get out their sys id papers. There’s multiple out there now already.)
CRTP (the radio protocol) has been stable for over a decade.
Param/log TOC lets the Crazyflie tell a client at runtime what parameters/logs are available. This (among other things) allows years-old scripts to maintain compatibility with newer firmwares and even across different platforms in the ecosystem.
The firmware reports its own firmware revision so user logs can include this.
The deck port (the pins) are unchanged. The deck working on your Crazyflie 2.0 works on your Crazyflie 2.1 Brushless.
Out-of-tree extension points; allows writing apps controllers and estimators for the community to separate their work from our firmware version.
A few years ago, we started looking at Rust as a possible foundation for parts of the Crazyflie software ecosystem. At first, this was mostly exploratory work: experimenting with Rust on embedded systems, looking at WebAssembly, and investigating whether one communication implementation could be shared between several platforms and programming languages.
In Rust, Wasm and the Crazyflie, we explored what a Rust implementation of the Crazyflie communication stack could look like. Later, in Rust at Bitcraze, we described a somewhat more concrete direction: implementing the low-level Crazyflie communication and subsystem drivers in Rust and making them available to Python, C++, JavaScript and other environments.
At the time, this was still mostly a plan.
Since then, the Rust Crazyflie library has moved from a side project to an official Bitcraze project. It is used in production, has supported a number of tools and demonstrations, and is now becoming the foundation for the next version of our Python library.
With the first release of cflib2, this seems like a good time to look at where we are and how it relates to the direction we described a few years ago.
Why a shared Rust library?
The Crazyflie Python library, cflib, has been around for a long time and is used by a large part of the community. It is the reference library and so it supports most Crazyflie functionality and is the foundation of tools such as the Crazyflie client.
Over the years, we have also felt some of the limitations of its architecture. In particular, it has proved difficult to reach the level of communication performance we want for larger swarms. Improving this within the existing pure-Python implementation would require fairly deep changes which would require quite deep user-facing change (like going async) without a certainty of results.
A separate problem is supporting users working in languages other than Python. One community solution has been to maintain a semi-independent C++ implementation of the Crazyflie communication stack, the one used by Crazyswarm2. While this provides access from C++, it also means maintaining another implementation. New functionality usually lands in the Python library first and is only ported to C++ later, often when its absence becomes a pain point. Over time, this makes the ecosystem harder to keep consistent and maintain.
The Rust library addresses these two problems from different directions. It gives us a new foundation on which we can build a more efficient communication architecture, including better support for swarms. At the same time, it can act as a shared reference implementation for several languages. Native Rust applications can use it directly, while bindings can expose the same functionality to Python, C++ and other environments.
These two goals are mostly independent, but they reinforce each other. Instead of improving performance in one library and then reproducing the same work elsewhere, we can implement the communication and subsystem logic once and make it available to users regardless of the language they choose.
This does not mean that all Crazyflie users are expected to start writing Rust. Quite the opposite: one of the main goals is to make the advantages of the Rust implementation available while still allowing users to work in the language and environment that suit their application.
From an experiment to a useful library
The Rust Crazyflie library has gradually moved from being an experiment to something we can use in real applications.
It has been used in production test systems, development tools, demos and swarm experiments. In these applications, we have started to see some of the improvements we originally hoped for.
The library uses an asynchronous architecture, which makes it easier to manage multiple Crazyflies and several ongoing communication flows at the same time. It also gives us more freedom to improve how information is transferred between a Crazyflie and a computer.
For example, parameters no longer need to be downloaded during the initial connection; instead, they can be loaded lazily when they are first requested. This is the kind of functionality for which Rust’s type system and language features make it possible to build a clean architecture with a relatively straightforward implementation.
Individually, this kind of changes may sound fairly small, but together they can make connections faster and reduce the amount of radio communication required. This becomes especially visible when working with larger groups of Crazyflies.
We have already used the Rust library in demonstrations involving many Crazyflies connected through one Crazyradio 2.0. It is still an evolving implementation, but these experiments have shown that the new architecture can make a practical difference.
Bringing the Rust library to Python
Python remains one of the most important ways of working with the Crazyflie. For this reason, one of the main goals of the Rust library has always been to make it available through a Python API.
This is what we are now doing with cflib2.
cflib2 is a new, async-first Python library built on top of the Rust Crazyflie library. Its first release is now available on PyPI and the source code is available on GitHub.
Our intention is for cflib2 to become the official Python library for the Crazyflie and, in time, replace the current pure-Python cflib. We are not there yet, though. This is the first public release, made at a point where the library has started to see more use both internally at Bitcraze and by a number of early users.
cflib2 is not designed as a drop-in replacement for cflib. Its API has been designed again around an asynchronous model, and we are using the opportunity to reconsider some earlier design decisions.
Most of the communication and Crazyflie subsystem handling in cflib2 is implemented by the Rust library. The Python layer exposes this functionality in a form that should feel natural to Python users. This gives Python applications access to the same connection handling, caching and asynchronous communication used by native Rust applications.
We and a number of early users have already seen significant improvements when moving applications to cflib2. One visible example was our recent demonstration in which 49 Crazyflies were controlled through one Crazyradio 2.0. This was made possible by a combination of the new radio functionality and the architecture used by the Rust library and cflib2.
For now, cflib2 should still be considered a preview. A large part of the functionality is already available, and it is being used in real applications, but the API is not yet stable and breaking changes should be expected. The existing cflib therefore remains the official and established Python library today.
This first release is an important step towards making cflib2 the official library. Releasing it now allows more users to try it in real applications and gives us feedback while it is still possible to make larger changes to the API and architecture.
More than Python
Python is the first binding we are working seriously on, but it is not the only environment we have in mind.
The original idea was to use the Rust library as a common foundation for several languages and platforms. C++, WebAssembly and mobile applications are still possible directions.
In particular, we hope that the same Rust communication backend can eventually be used by future mobile Crazyflie clients. Sharing the implementation between platforms could make these clients easier to develop and maintain.
There is still work to do before all of this becomes available, and the exact shape of the different bindings is not decided. For now, cflib2 is the most concrete example of how the architecture can be used outside Rust itself.
The next step in the journey
Looking back at what we wrote in 2021 and 2023, the overall direction has remained surprisingly consistent.
We wanted to implement the lower levels of the Crazyflie communication stack once, use Rust where its properties were useful, and expose the result to the languages and platforms used by the community.
The Rust Crazyflie library and cflib2 do not complete that journey, but they are a significant next step.
For the first time, Python users can install a library based on the shared Rust implementation and start using it in their own applications. At the same time, we can continue improving the communication stack without having to reproduce every improvement separately in each language.
We are still learning what works well, and both the Rust library and cflib2 will continue to change. Feedback, bug reports and experiments using the new library are very welcome.
Flying formations with a swarm is always fun to develop and watch, but it is also a great way to stress-test the software behind it. As part of the development of cflib2, we put it through one of its toughest tests yet: flying 49 Crazyflies in coordinated formations.
The entire swarm was controlled using a single Crazyradio 2.0, highlighting the combined improvements in cflib2 and Crazyradio firmware over the past few months.
The setup
The hardware configuration was relatively straightforward. We used 49 Crazyflie 2.1 Brushless drones, each one equipped with a bottom-mounted Color LED deck for vivid lighting effects. For positioning, we used the Lighthouse positioning system, covering the entire flight area, which was roughly 5x5x2m. Its accuracy allowed the Crazyflies to fly grid formations with just 0.4m spacing between them.
One of the big practical challenges when managing a large swarm is swapping the depleted batteries for charged ones. Thanks to the Crazyflie 2.1 Brushless PCB design, each drone can now charge while sitting on its charging dock, making it much easier to prepare the swarm for the next flight.
Flying the formations
The swarm performed a sequence of synchronized formations under the control of a central PC. Rather than streaming the full trajectories to every drone, the formations are built using the Crazyflie’s High-Level Commander. Each Crazyflie receives simple motion commands such as go to or spiral, and executes the corresponding trajectory onboard. At the same time, it receives commands for changing the color of the Color LED deck.
Using cflib2, these commands can be sent to all 49 Crazyflies through a singe Crazyradio 2.0, which was not possible with cflib.
Looking ahead
This demonstration is an exciting milestone for cflib2 and showcases what the new library makes possible. While controlling 49 Crazyflies is an impressive demonstration, cflib2 is designed to benefit projects of every size. Whether you are flying a single Crazyflie or coordinating a large swarm, the goal is to provide a faster, more scalable, and more robust communication library.
Most of the functionalities from cflib have already been migrated to cflib2, and development is continuing. For many applications, cflib2 is already ready to use, so if you would like to try it out, you can find the repository here.