Thanks for your interest in contributing! This guide covers everything you need to get a local development environment running and start submitting changes.
You need one of the following installed:
| Tool | Purpose |
|---|---|
| Nix (recommended) | Single command setup — provides all toolchains, targets, and tools |
| Rust (manual) | If you prefer managing toolchains yourself |
Install Nix with flakes enabled:
# Install Nix (multi-user, recommended)
sh <(curl -L https://nixos.org/nix/install) --daemon
# Enable flakes (add to ~/.config/nix/nix.conf)
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.confThen enter the dev environment:
cd ferrite-sdk
nix developThis gives you everything: stable Rust with all 7 embedded targets + WASM, probe-rs, dx CLI, cbindgen, arm-none-eabi-gcc, cross, QEMU, Node.js for docs, and more. No further setup needed.
Optional: install direnv for automatic shell activation:
# Install direnv and hook it into your shell
nix profile install nixpkgs#direnv
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc # or zsh
# Allow the project's .envrc
cd ferrite-sdk
direnv allowNow the dev environment activates automatically when you cd into the project.
The ESP32-C3 needs a pinned nightly toolchain (2025-04-15). Use the separate shell:
nix develop .#esp
cd examples/embassy-esp32c3
cargo build --releaseIf you prefer not to use Nix, install these manually:
# 1. Rust stable + embedded targets
rustup default stable
rustup target add thumbv6m-none-eabi # RP2040
rustup target add thumbv7m-none-eabi # Cortex-M3
rustup target add thumbv7em-none-eabi # STM32WL55 (no FPU)
rustup target add thumbv7em-none-eabihf # STM32L4A6, nRF52840
rustup target add thumbv8m.main-none-eabi # nRF5340 (no FPU)
rustup target add thumbv8m.main-none-eabihf # STM32H563
rustup target add wasm32-unknown-unknown # Dashboard
rustup component add clippy rustfmt rust-analyzer
# 2. Embedded tools
cargo install probe-rs-tools # Flash & debug
cargo install cargo-binutils # objcopy, nm, size
cargo install cbindgen # C FFI headers
cargo install cross # RPi cross-compilation
cargo install dioxus-cli@0.6.3 # Dashboard dev server
# 3. System packages (Debian/Ubuntu)
sudo apt install pkg-config libssl-dev libudev-dev libdbus-1-dev \
gcc-arm-none-eabi qemu-system-arm nodejs npm
# 4. ESP32-C3 (optional, needs nightly)
rustup toolchain install nightly-2025-04-15
rustup target add riscv32imc-unknown-none-elf --toolchain nightly-2025-04-15
pip install esptool# SDK tests (single-threaded — global state requires it)
cargo test -p ferrite-sdk --no-default-features -- --test-threads=1
# Server tests
cargo test -p ferrite-server
# Formatting and linting
cargo fmt --all -- --check
cargo clippy -p ferrite-sdk --no-default-features -- -D warnings
cargo clippy -p ferrite-server -- -D warnings# Build SDK for Cortex-M4F
cargo build -p ferrite-sdk --features cortex-m,defmt,embassy --target thumbv7em-none-eabihf
# Build a board example
cargo build --manifest-path examples/embassy-nrf52840/Cargo.toml --target thumbv7em-none-eabihf
# QEMU integration tests (lm3s6965evb)
rustup target add thumbv7m-none-eabi
cd tests/qemu && cargo run --release# Terminal 1: start the server
cargo run -p ferrite-server
# Terminal 2: start the dashboard dev server (proxies API to localhost:4000)
cd ferrite-dashboard && dx serveThe dashboard opens at http://localhost:8080. Default login: admin / admin.
cd docs
npm install
npm run dev # Dev server at localhost:5173
npm run build # Production buildferrite-sdk/ Core no_std SDK (crashes, metrics, trace, chunks)
ferrite-server/ Axum ingestion server (auth, alerting, Prometheus)
ferrite-dashboard/ Dioxus WASM dashboard (fleet monitoring UI)
ferrite-gateway/ Edge gateway (BLE/USB/LoRa → server bridge)
ferrite-embassy/ Embassy async upload task
ferrite-rtic/ RTIC blocking upload wrapper
ferrite-ffi/ C FFI static library (Zephyr/FreeRTOS)
ferrite-provision/ Device provisioning CLI
examples/ Board-specific firmware examples
tests/qemu/ QEMU integration tests (excluded from workspace)
docs/ VitePress documentation site
deploy/ Deployment configs (RPi gateway, Docker)
- Fork the repo and create a feature branch from
main - Make your changes
- Run the relevant tests (see above)
- Ensure
cargo fmt --allandcargo clippypass - Open a pull request against
main
We use conventional commits:
feat: add BLE transport for nRF5340
fix: correct CRC calculation for chunks > 255 bytes
docs: add gateway deployment guide
refactor: extract chunk encoding into separate module
These are non-negotiable for the SDK:
- No alloc —
ferrite-sdkmust remain#![no_std]with no allocator - No panics — production code paths must never panic (tests are fine)
- Feature-gated hardware — all MCU-specific code behind feature flags
- Global state via
CriticalSectionMutex— tests must run single-threaded - CRC-16/CCITT-FALSE — chunk integrity uses this specific CRC variant
- Add variant to
ChunkTypeenum inferrite-sdk/src/chunks/types.rs - Add encode function in
ferrite-sdk/src/chunks/encoder.rs - Add decode match arm in
ferrite-sdk/src/chunks/decoder.rs - Add SQL column in
ferrite-server/src/store.rs - Add test in
ferrite-sdk/src/chunks/encoder.rs
- Create
ferrite-sdk/src/transport/my_transport.rs - Implement
ChunkTransport(blocking) orAsyncChunkTransport(async) - Gate behind a feature flag in
ferrite-sdk/Cargo.toml - Add gateway support in
ferrite-gateway/if bridging is needed - Document in
docs/guide/transports.md
The dashboard uses Dioxus 0.7 with Tailwind CSS. Key patterns:
- State:
use_context_provider()to provide,use_context::<Signal<T>>()to consume - Auth: discovered at startup via
GET /auth/mode - API client:
src/api/client.rs— all requests go throughApiClient - Routing: defined in
src/main.rs, guarded byAppLayout
The server uses Axum with SQLite (via rusqlite). Key patterns:
- Auth middleware in
src/auth_middleware.rs— must passOPTIONSfor CORS - Layer order matters: auth middleware before CORS layer
- Config:
AuthConfig::from_env()withBox::leakfor&'staticlifetime - All new endpoints need entries in
src/main.rsrouter and CI proxy config
If you have physical dev boards:
# STM32 boards (probe-rs)
probe-rs download --chip STM32L4A6ZGTx target/thumbv7em-none-eabihf/release/binary
probe-rs reset --chip STM32L4A6ZGTx
# STM32WL55 (st-flash — probe-rs can't connect without reset)
arm-none-eabi-objcopy -O binary target/.../release/binary /tmp/fw.bin
st-flash --connect-under-reset write /tmp/fw.bin 0x8000000
# ESP32-C3
espflash flash --chip esp32c3 target/riscv32imc-unknown-none-elf/release/binary
# nRF5340 (J-Link)
nrfjprog --program build/zephyr/zephyr.hex -f NRF53 --coprocessor CP_APPLICATION --sectorerase --resetSee CLAUDE.md for detailed flash instructions and common pitfalls.
Every PR runs:
| Job | What it checks |
|---|---|
| Host tests | cargo fmt, clippy, SDK tests, server tests |
| Keycloak tests | Server integration with real Keycloak container |
| Embedded builds | SDK compiles for nRF52840, RP2040, STM32F4 targets |
| QEMU tests | Integration tests on emulated Cortex-M3 |
The deploy job triggers a Coolify webhook on merge to main.
Open an issue at github.com/mighty840/ferrite-sdk/issues.