Files
2026-08-03 16:37:10 +08:00

330 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
# 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`
```bash
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:
```bash
$ 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](https://podman.io/docs/installation).
There are no special daemons to be run or group permissions to be added.
Validate the installation with:
```bash
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](https://docs.docker.com/engine/install/).
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.).
```bash
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](https://code.visualstudio.com/docs/setup/setup-overview) for your operating system.
Once VS Code is installed, add the Dev Containers extension using the [official guide](https://code.visualstudio.com/docs/devcontainers/containers).
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](https://vscodium.com/) builds from the open-source repository of VSCode stripping Microsoft proprietary code.
Similarly the [devpod project](https://devpod.sh/) provides a wrapper similar to the Microsoft Dev Containers Extension around the open [development container specification](https://containers.dev/).
The two can be used together to achieve the same as Microsoft's proprietary solution.
Example of running with `devpod`:
```bash
$ 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:
```bash
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:
```bash
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](https://gazebosim.org/) simulation.
This allows testing firmware changes without physical hardware.
### Architecture
```text
┌─────────────────────────────────────────────────────┐
│ Gazebo Container │
│ │
│ ┌──────────────┐ UDP 9002/9003 ┌──────────────┐ │
│ │ Gazebo │◄──────────────►│ Betaflight │ │
│ │ Harmonic │ motor/sensor │ SITL (.elf) │ │
│ │ + Bridge │ │ │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ TCP 5761 │
└─────────────────────────────────────────┼──────────┘
┌───────────▼──────────┐
│ Betaflight App │
│ ws://localhost:6761 │
└──────────────────────┘
```
### Building the Gazebo container
```bash
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](https://www.xquartz.org) and run `xhost +localhost` first.
**Host terminal 1 — start the container and build SITL:**
```bash
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:**
```bash
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:**
```bash
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 |