Files
betaflight/.devcontainer/README.md
T
2026-08-03 16:37:10 +08:00

15 KiB
Raw Blame History

Using devcontainers

NOTE: Unless otherwise specified all commands below should be run from the main workspace folder of the betaflight repository.

NOTE: All examples here are using a Moblite7 1S tiny whoop based on STM32F411CEU6. The betaflight target is CRAZYBEEF4DX.

Why use devcontainers?

Devcontainers provide a consistent, reproducible development environment that works the same across machines. They eliminate “works on my machine” issues by packaging tools, dependencies, and configurations inside a container. This makes onboarding faster, simplifies collaboration, and ensures your development setup stays clean and isolated.

How is this different from the betaflight/cloudbuild repository?

The betaflight/cloudbuild repository provides a containerized CI/CD environment for building and testing Betaflight firmware in the cloud. It is optimized for automated builds and may not include all the tools and configurations needed for local development. This devcontainer includes DFU utilities and OpenOCD debugger for example. While similar to the cloudbuild container it is tailored for interactive development and debugging and avoids disruptions caused by changes in the cloudbuild repo.

Defining UDEV rules on the host

Devcontainers for HW development are a bit special, as they require HW access to the device. It can get tricky to make this universal, because not all Linux distributions use the same group numbers to control permissions. Typically to do HW development on an STM32 board the user would need at least dialout and plugdev permissions. The dialout groups for example can be number 11, 18 or 20 on Void Linux, Fedora and Debian, respectively.

The easiest way to deal with this is to define custom udev rules. To allow the container access to the HW, the device would need to be configured with the right permissions when plugged in. Here are the steps required:

  1. plug in the device and check its IDs and default permissions

    # List the USB connected devices
    $ lsusb
    Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    Bus 001 Device 002: ID 0483:5740 STMicroelectronics Virtual COM Port   <==== this is our FC! Note the `{idVendor}` and `{idProduct}` IDs!
    Bus 001 Device 003: ID 1fd2:8005 Melfas LGDisplay Incell Touch
    Bus 001 Device 004: ID 04f2:b681 Chicony Electronics Co., Ltd ThinkPad T490 Webcam
    Bus 001 Device 005: ID 06cb:00bd Synaptics, Inc. Prometheus MIS Touch Fingerprint Reader
    Bus 001 Device 006: ID 8087:0aaa Intel Corp. Bluetooth 9460/9560 Jefferson Peak (JfP)
    Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
    
    # Check the default permissions
    $ ls -la /dev/ttyACM0
    crw-rw---- 1 root dialout 166, 0 Jan 24 09:35 /dev/ttyACM0     <==== device is owned by root with access via the `dialout` group.
    
  2. create a udev rule for the device by creating this file: sudo nano /etc/udev/rules.d/99-betaflight.rules

    SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", MODE="0666", TAG+="uaccess"
    SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="df11", MODE="0666"
    

    The {idVendor} and {idProduct} IDs must match those of the device. The two SUBSYSTEM rules cover both the normal operation and DFU mode of the flight controller.

  3. plug the device out and in again. You should see:

    $ ls -la /dev/ttyACM0
    crw-rw-rw- 1 root dialout 166, 0 Jan 24 09:39 /dev/ttyACM0   <==== note the third `rw` allowing access to all
    

NOTE: this example is with a ready made flight controller on UART, but the same procedure works for STLink debuggers for example.

Installation of tools

Two examples are given here - one using Docker and the other using Podman. For the Docker example we will use Microsoft's VSCode with the Dev Containers extension. For the Podman example we will use fully open source development chain with devpod and VSCodium.

Installing Podman

Podman is a rootless, daemonless alternative to Docker that provides a compatible commandline interface for managing containers. Installation instructions for all supported platforms are available in the official Podman documentation. There are no special daemons to be run or group permissions to be added. Validate the installation with:

    podman run docker.io/library/hello-world

Installing Docker (CLI)

Docker provides the docker CLI for building and running containers.

Follow the official installation guide.

After installation usually the docker service needs to be started and the user should be added to the docker group. Details on how to do this is linux distro dependent - refer to your distribution manual for details on how to achieve this. Below is an example that should work on distributions with systemd (Debian and derivatives, RedHat and derivatives, etc.).

    sudo systemctl enable --now docker  # Enable and start the service
    sudo usermod -aG docker $USERNAME  # you might need to log out and back in after this step
    docker run docker.io/library/hello-world  # Run an example to validate the install

Installing VS Code and the Dev Containers Extension

To use devcontainers more effectively, you can install Visual Studio Code along with the Dev Containers extension.

You can install VS Code by following the official instructions provided for your operating system.

Once VS Code is installed, add the Dev Containers extension using the official guide.

With these tools installed, you can open the betaflight repository in VS Code and use the Dev Containers extension to build and run the devcontainer defined in the .devcontainer folder.

NOTE: Dev Containers extension in VSCode comes pre-configured for the docker binary. You can simply type podman in the extension setting to change the container provider.

Fully open-source solution via devpod and VSCodium

VSCode from Microsoft and its Dev Containers Extension are not fully open-source.

The VSCodium project builds from the open-source repository of VSCode stripping Microsoft proprietary code. Similarly the devpod project provides a wrapper similar to the Microsoft Dev Containers Extension around the open development container specification. The two can be used together to achieve the same as Microsoft's proprietary solution.

Example of running with devpod:

    $ podman --version  # check podman version
    podman version 5.7.1
    $ devpod version  # check devpod version
    v0.6.15
    $ devpod provider list  # check that the podman provider is configured

     NAME  | VERSION | DEFAULT | INITIALIZED |   DESCRIPTION
  ---------+---------+---------+-------------+-------------------
    docker | v0.0.1  | false   | true        | DevPod on Docker
    podman | v0.0.1  | true    | true        | DevPod on Podman
    $ devpod ide list  # check that the VSCodium IDE is configured

         NAME       | DEFAULT
  ------------------+----------
    clion           | false
    codium          | true
    cursor          | false
    dataspell       | false
    fleet           | false
    goland          | false
    intellij        | false
    jupyternotebook | false
    none            | false
    openvscode      | false
    phpstorm        | false
    positron        | false
    pycharm         | false
    rider           | false
    rstudio         | false
    rubymine        | false
    rustrover       | false
    vscode          | false
    vscode-insiders | false
    webstorm        | false
    zed             | false
    $ devpod up .  # start the devcontainer in the current folder

The last command will open VSCodium in the devcontainer context.

Example usage inside the container

Once inside the devcontainer terminal you can check the device access:

    devpod@bdaab82778ae:/workspaces/betaflight$ ls -la /dev/ttyACM0
    crw-rw-rw- 1 nobody nogroup 166, 0 Jan 24 09:24 /dev/ttyACM0

You can then build and flash the firmware as usual:

    devpod@bdaab82778ae:/workspaces/betaflight$ make clean
    devpod@bdaab82778ae:/workspaces/betaflight$ make CRAZYBEEF4DX
        Building target config CRAZYBEEF4DX
        make --no-print-directory fwo CONFIG=CRAZYBEEF4DX
        EF HASH -> ./obj/main/STM32F411_CRAZYBEEF4DX/.efhash_4e3f673b7beeb9bf243f745d779b10e0
        %% startup_stm32f411xe.s
        %% (speed optimised) ./src/platform/common/stm32/system.c
        .
        .
        .
        %% (optimised) ./src/main/drivers/usb_io.c
        Linking STM32F411_CRAZYBEEF4DX
        Memory region         Used Size  Region Size  %age Used
                FLASH:        8268 B        16 KB     50.46%
            FLASH_CONFIG:           0 B        16 KB      0.00%
                FLASH1:      371710 B       480 KB     75.62%
        SYSTEM_MEMORY:           0 B        29 KB      0.00%
                    RAM:       81960 B       128 KB     62.53%
            MEMORY_B1:           0 B          0 B
        text	   data	    bss	    dec	    hex	filename
        374254	   5724	  75824	 455802	  6f47a	./obj/main/betaflight_STM32F411_CRAZYBEEF4DX.elf
        Creating HEX ./obj/betaflight_2025.12.0-beta_STM32F411_CRAZYBEEF4DX.hex
        Building target config CRAZYBEEF4DX succeeded.
    devpod@bdaab82778ae:/workspaces/betaflight$ make dfu_flash CONFIG=CRAZYBEEF4DX
        # potentially this is because the MCU already is in DFU mode, try anyway
        echo -n 'R' > /dev/ttyACM0
        sleep 1
        make --no-print-directory ./obj/betaflight_2025.12.0-beta_STM32F411_CRAZYBEEF4DX.dfu
        Creating DFU ./obj/betaflight_2025.12.0-beta_STM32F411_CRAZYBEEF4DX.dfu
        dfu-util -a 0 -D ./obj/betaflight_2025.12.0-beta_STM32F411_CRAZYBEEF4DX.dfu -s :leave
        dfu-util 0.11

        Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
        Copyright 2010-2021 Tormod Volden and Stefan Schmidt
        This program is Free Software and has ABSOLUTELY NO WARRANTY
        Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

        Match vendor ID from file: 0483
        Match product ID from file: df11
        Multiple alternate interfaces for DfuSe file
        Opening DFU capable USB device...
        Device ID 0483:df11
        Device DFU version 011a
        Claiming USB DFU Interface...
        Setting Alternate Interface #0 ...
        Determining device status...
        DFU state(10) = dfuERROR, status(10) = Device's firmware is corrupt. It cannot return to run-time (non-DFU) operations
        Clearing status
        Determining device status...
        DFU state(2) = dfuIDLE, status(0) = No error condition is present
        DFU mode device DFU version 011a
        Device returned transfer size 2048
        DfuSe interface name: "Internal Flash  "
        File contains 1 DFU images
        Parsing DFU image 1
        Target name: ST...
        Image for alternate setting 0, (2 elements, total size = 379994)
        Setting Alternate Interface #0 ...
        Parsing element 1, address = 0x08000000, size = 8268
        Erase   	[=========================] 100%         8268 bytes
        Erase    done.
        Download	[=========================] 100%         8268 bytes
        Download done.
        Parsing element 2, address = 0x08008000, size = 371710
        Erase   	[=========================] 100%       371710 bytes
        Erase    done.
        Download	[=========================] 100%       371710 bytes
        Download done.
        Done parsing DfuSe file
        Submitting leave request...
        Transitioning to dfuMANIFEST state

Gazebo SITL Simulation

A separate container is provided for running Betaflight SITL with Gazebo Harmonic simulation. This allows testing firmware changes without physical hardware.

Architecture

┌─────────────────────────────────────────────────────┐
│  Gazebo Container                                   │
│                                                     │
│  ┌──────────────┐  UDP 9002/9003  ┌──────────────┐  │
│  │   Gazebo      │◄──────────────►│ Betaflight   │  │
│  │   Harmonic    │  motor/sensor  │ SITL (.elf)  │  │
│  │   + Bridge    │                │              │  │
│  └──────────────┘                └──────┬───────┘  │
│                                         │ TCP 5761 │
└─────────────────────────────────────────┼──────────┘
                                          │
                              ┌───────────▼──────────┐
                              │  Betaflight App      │
                              │  ws://localhost:6761  │
                              └──────────────────────┘

Building the Gazebo container

docker build -t bf-dev-gazebo -f .devcontainer/containerfile.gazebo .devcontainer/

Running a simulation

The simulation requires three processes running concurrently inside the container. Use three separate host terminals — the first starts the container, the others attach to it.

Note: Gazebo's GUI requires a display. On Linux the docker run command below forwards your host X11 socket into the container. On macOS, install XQuartz and run xhost +localhost first.

Host terminal 1 — start the container and build SITL:

docker run -it --rm \
  --name bf-gazebo \
  --network=host \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -v "$(pwd)":/workspace \
  bf-dev-gazebo

# Inside the container — build SITL (only needed once per workspace)
make TARGET=SITL

# Then start SITL
./obj/main/betaflight_SITL.elf

Host terminal 2 — attach and start Gazebo:

docker exec -it bf-gazebo bash

# Inside the container
gz sim -r ~/aeroloop_gazebo/worlds/betaloop_iris_betaflight_demo_harmonic.sdf

Host terminal 3 — attach and start the WebSocket proxy:

docker exec -it bf-gazebo bash

# Inside the container
websockify 127.0.0.1:6761 127.0.0.1:5761

Then connect the Betaflight App to ws://127.0.0.1:6761.

Ports

Port Protocol Purpose
9002 UDP SITL → Gazebo (motor commands)
9003 UDP Gazebo → SITL (sensor data)
9004 UDP External → SITL (RC input)
5761 TCP SITL UART1 (MSP serial proxy)
6761 TCP WebSocket proxy for Betaflight App