chore: update tooling (mise, justfile, examples)

Add mise.toml for tool version management, rename Justfile to justfile,
add examples/justfile for running Python examples, and update docs.
This commit is contained in:
swaits committed 2026-03-31 17:31:47 -06:00
1 parent 9560349547
commit 83a76d4b48
7 files changed
+235 -159

No files matched your search

+5 -4
View File
@@ -6,7 +6,7 @@ DongLoRa is designed to make adding new boards easy:
1. Create `src/board/your_board.rs` implementing the `init()` function
2. Add a Cargo feature in `Cargo.toml` with the board's dependencies
3. Add the board's feature/target/chip to the `Justfile` boards list
3. Add the board's feature/target/chip to the `justfile` boards list
4. `build.rs` auto-discovers the new file and generates the module selector
See existing board files (`heltec_v3.rs`, `rak_wisblock_4631.rs`) as templates.
@@ -23,13 +23,14 @@ common tasks consume. No shared board code — each board is self-contained.
## Building and Testing
```sh
just check-all # Compile-check all boards (skips unavailable toolchains)
just setup # Install all tools and toolchains (one-time)
just check-all # Compile-check all boards
just build-all # Build release firmware for all boards
just clippy <board> # Lint a specific board
```
Xtensa boards (Heltec V3/V4) require the ESP toolchain via `espup`.
ARM boards (RAK 4631) build with stock `rustup`.
Tool versions (espup, espflash, probe-rs) are pinned in `mise.toml`.
`just setup` installs everything; individual commands also auto-install as needed.
## Commits
Generated
+1 -3
View File
@@ -1818,9 +1818,7 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "ucobs"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12e019ee3a487897e4b704b5702deb9f2181f7fc210558c9fb5b18aacb12e3f7"
version = "0.3.1"
[[package]]
name = "ufmt-write"
-133
View File
@@ -1,133 +0,0 @@
set shell := ["bash", "-c"]
# Board definitions: feature target chip
# Xtensa targets use nightly cargo + esp rustc + -Zbuild-std=core.
# check-all gracefully skips boards whose toolchain isn't installed.
heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3"
heltec_v4 := "heltec_v4 xtensa-esp32s3-none-elf esp32s3"
rak_wisblock_4631 := "rak_wisblock_4631 thumbv7em-none-eabihf nRF52840_xxAA"
firmware_dir := "firmware"
# All known boards
boards := "heltec_v3 heltec_v4 rak_wisblock_4631"
# Build release firmware for all boards with available toolchains
build-all:
@trap 'exit 130' INT; \
for board in {{boards}}; do \
if just _can_build $board 2>/dev/null; then \
echo "── building $board ──"; \
just build $board || exit $?; \
else \
echo "── skipping $board (toolchain not available) ──"; \
fi; \
done
# Check all boards that can build with the available toolchain
check-all:
@trap 'exit 130' INT; \
for board in {{boards}}; do \
if just _can_build $board 2>/dev/null; then \
echo "── checking $board ──"; \
just check $board || exit $?; \
else \
echo "── skipping $board (toolchain not available) ──"; \
fi; \
done
# Check a single board compiles
check board:
@just _cargo {{board}} check
# Run clippy on a single board
clippy board:
@just _cargo {{board}} "clippy" "-- -D warnings"
# Build release firmware and copy to firmware/ with a readable name
build board profile="release":
@just _cargo {{board}} "build --{{profile}}"
@just _copy_firmware {{board}} {{profile}}
# Build and flash a board (espflash for Xtensa, probe-rs for ARM)
flash board:
@just _require_flash_tool {{board}}
@just build {{board}} release
@read -r feat target chip <<< "$(just _info {{board}})"; \
case "$target" in \
xtensa-*) espflash flash "{{firmware_dir}}/donglora-{{board}}-release.elf" ;; \
*) probe-rs run --chip $chip "{{firmware_dir}}/donglora-{{board}}-release.elf" ;; \
esac
# Show binary size for a release build
size board:
@just _cargo {{board}} "size --release"
# ── Private helpers ───────────────────────────────────────────────────
# Run a cargo command for a board.
# Xtensa: nightly cargo + esp rustc (via RUSTC override) + -Zbuild-std=core
[private]
_cargo board cmd extra_args="":
@read -r feat target chip <<< "$(just _info {{board}})"; \
env=""; extra=""; \
case "$target" in xtensa-*) \
just _require_esp_toolchain; \
[ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \
env="RUSTC=$(rustup which rustc --toolchain esp)"; extra="+nightly"; buildstd="-Zbuild-std=core,alloc";; \
esac; \
eval $env cargo $extra {{cmd}} --target "$target" --features "$feat" $buildstd {{extra_args}}
# Check if a board's toolchain is available (silent, for build-all/check-all skipping)
[private]
_can_build board:
@read -r feat target chip <<< "$(just _info {{board}})"; \
case "$target" in \
xtensa-*) rustup toolchain list | grep -q "^esp" ;; \
*) rustup target list --installed | grep -q "^$target$" || rustup target add "$target" >/dev/null 2>&1 ;; \
esac
# Verify the flash tool is installed for a board, with install instructions if not
[private]
_require_flash_tool board:
@read -r feat target chip <<< "$(just _info {{board}})"; \
case "$target" in \
xtensa-*) \
if ! command -v espflash >/dev/null 2>&1; then \
echo "error: espflash is required to flash ESP32 boards" >&2; \
echo " install: cargo install espflash" >&2; \
exit 1; \
fi ;; \
*) \
if ! command -v probe-rs >/dev/null 2>&1; then \
echo "error: probe-rs is required to flash ARM boards" >&2; \
echo " install: cargo install probe-rs-tools" >&2; \
exit 1; \
fi ;; \
esac
# Verify the esp toolchain is installed, with install instructions if not
[private]
_require_esp_toolchain:
@if ! rustup toolchain list | grep -q "^esp"; then \
echo "error: ESP toolchain not found" >&2; \
echo " install:" >&2; \
echo " cargo install espup" >&2; \
echo " espup install" >&2; \
exit 1; \
fi
[private]
_copy_firmware board profile:
@mkdir -p {{firmware_dir}}
@read -r feat target chip <<< "$(just _info {{board}})"; \
src="target/$target/{{profile}}/donglora"; \
dst="{{firmware_dir}}/donglora-{{board}}-{{profile}}"; \
if [ -f "$src" ]; then cp "$src" "$dst.elf"; echo "→ $dst.elf"; fi
[private]
_info name:
@if [ "{{name}}" == "heltec_v3" ]; then echo "{{heltec_v3}}"; \
elif [ "{{name}}" == "heltec_v4" ]; then echo "{{heltec_v4}}"; \
elif [ "{{name}}" == "rak_wisblock_4631" ]; then echo "{{rak_wisblock_4631}}"; \
else echo "Unknown board: {{name}}" >&2; exit 1; fi
+21 -19
View File
@@ -10,9 +10,9 @@ and gets out of the way. No mesh logic, no protocol opinions, no config files.
## Quick Start
1. Get a [supported board](#supported-boards)
2. Install toolchain (see [Building](#building))
3. Build and flash: `just flash heltec_v4`
4. Run an example: `uv run examples/simple_rx.py`
2. `just setup` (installs all tools and toolchains)
3. `just flash heltec_v4`
4. `just ex rx`
5. See packets
## Supported Boards
@@ -34,41 +34,43 @@ specification with worked examples.
## Examples
All examples use [uv](https://docs.astral.sh/uv/) for dependency management:
Python dependencies are handled automatically — just run:
```sh
just ex rx # receive packets
just ex tx # transmit a packet
just ex ping-pong --role tx # two-dongle ping-pong demo
just ex test-commands # exercise all DongLoRa commands
just ex bridge --mode server # LoRa bridge over TCP
just ex meshcore # MeshCore packet decoder
just ex run simple_rx # run any example by name
```
| Script | Description |
|--------|-------------|
| [`all_commands.py`](examples/all_commands.py) | Exercise all 8 commands (Ping, SetConfig, GetConfig, StartRx, StopRx, Transmit, DisplayOn/Off) |
| [`simple_rx.py`](examples/simple_rx.py) | Configure radio, receive and print packets |
| [`simple_tx.py`](examples/simple_tx.py) | Transmit a single packet |
| [`ping_pong.py`](examples/ping_pong.py) | Two-dongle demo (`--role tx` / `--role rx`) |
| [`all_commands.py`](examples/all_commands.py) | Exercise all 8 commands (Ping, SetConfig, GetConfig, StartRx, StopRx, Transmit, DisplayOn/Off) |
| [`lora_bridge.py`](examples/lora_bridge.py) | Two-way LoRa bridge over TCP (works over Tailscale, WireGuard, etc.) |
| [`meshcore/`](examples/meshcore/) | Full MeshCore packet decoder (advanced example) |
## Building
Requires [just](https://github.com/casey/just) and Rust.
Requires [just](https://github.com/casey/just), [mise](https://mise.jdx.dev/), and Rust.
```sh
just build-all # build firmware for all boards (skips unavailable toolchains)
just setup # install all tools and toolchains (one-time)
just build-all # build firmware for all boards
just build heltec_v4 # build a specific board
just check-all # compile-check only (no firmware output)
just flash heltec_v4 # build + flash
just clippy heltec_v4 # lint
```
### Xtensa boards (Heltec V3/V4)
Requires the [ESP toolchain](https://github.com/esp-rs/espup):
```sh
cargo install espup
espup install --toolchain-version 1.82.0.3
```
### ARM boards (RAK 4631)
Stock Rust stable. Targets are installed automatically.
`just setup` handles everything: mise-managed tools (espup, espflash,
probe-rs), the ESP Xtensa toolchain, nightly rust-src, and ARM targets.
Individual build commands will also auto-install missing tools as needed.
<details>
<summary><strong>Board Roadmap</strong></summary>
+29
View File
@@ -0,0 +1,29 @@
set shell := ["bash", "-c"]
# Receive LoRa packets (Ctrl-C to stop)
rx *args:
@mise install && uv run {{source_directory()}}/simple_rx.py {{args}}
# Transmit a single packet
tx *args:
@mise install && uv run {{source_directory()}}/simple_tx.py {{args}}
# Two-dongle ping-pong demo (--role tx|rx)
ping-pong *args:
@mise install && uv run {{source_directory()}}/ping_pong.py {{args}}
# Exercise all DongLoRa commands
test-commands *args:
@mise install && uv run {{source_directory()}}/all_commands.py {{args}}
# Two-way LoRa bridge over TCP
bridge *args:
@mise install && uv run {{source_directory()}}/lora_bridge.py {{args}}
# MeshCore packet decoder/monitor
meshcore *args:
@mise install && uv run {{source_directory()}}/meshcore/meshcore_rx.py {{args}}
# Run any example by name (e.g. just ex run simple_rx)
run script *args:
@mise install && uv run {{source_directory()}}/{{script}}.py {{args}}
+169
View File
@@ -0,0 +1,169 @@
set shell := ["bash", "-c"]
# Board definitions: feature target chip
# Xtensa targets use nightly cargo + esp rustc + -Zbuild-std=core.
# Tool versions are pinned in mise.toml; run `just setup` to install everything.
heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3"
heltec_v4 := "heltec_v4 xtensa-esp32s3-none-elf esp32s3"
rak_wisblock_4631 := "rak_wisblock_4631 thumbv7em-none-eabihf nRF52840_xxAA"
firmware_dir := "firmware"
# All known boards
boards := "heltec_v3 heltec_v4 rak_wisblock_4631"
# Install all required tools and toolchains
setup:
mise trust --yes
mise install
@# ESP Xtensa toolchain (not managed by mise)
@if ! rustup toolchain list | grep -q "^esp"; then \
echo "ESP toolchain not found, installing via espup (this may take a while)..."; \
espup install || { echo "error: espup install failed" >&2; exit 1; }; \
fi
@# nightly rust-src for -Zbuild-std
@if ! rustup component list --toolchain nightly --installed 2>/dev/null | grep -q "^rust-src"; then \
rustup component add rust-src --toolchain nightly; \
fi
@# ARM target
@rustup target list --installed | grep -q "^thumbv7em-none-eabihf$" || rustup target add thumbv7em-none-eabihf
# Build release firmware for all boards, installing toolchains as needed
build-all:
@trap 'exit 130' INT; \
for board in {{boards}}; do \
if just _can_build $board; then \
echo "── building $board ──"; \
just build $board || exit $?; \
else \
echo "── skipping $board (toolchain install failed) ──"; \
fi; \
done
# Check all boards compile, installing toolchains as needed
check-all:
@trap 'exit 130' INT; \
for board in {{boards}}; do \
if just _can_build $board; then \
echo "── checking $board ──"; \
just check $board || exit $?; \
else \
echo "── skipping $board (toolchain install failed) ──"; \
fi; \
done
# Check a single board compiles
check board:
@just _cargo {{board}} check
# Run clippy on a single board
clippy board:
@just _cargo {{board}} "clippy" "-- -D warnings"
# Build release firmware and copy to firmware/ with a readable name
build board profile="release":
@just _cargo {{board}} "build --{{profile}}"
@just _copy_firmware {{board}} {{profile}}
# Build and flash a board (espflash for Xtensa, probe-rs for ARM)
flash board:
@just _ensure_tools
@just build {{board}} release
@read -r feat target chip <<< "$(just _info {{board}})"; \
case "$target" in \
xtensa-*) \
port=$(just _find_port "303a:1001" "1209:5741"); \
if [ -n "$port" ]; then \
echo "Using $port"; \
espflash flash -p "$port" "{{firmware_dir}}/donglora-{{board}}-release.elf"; \
else \
echo "No port found, falling back to espflash auto-detection..." >&2; \
espflash flash "{{firmware_dir}}/donglora-{{board}}-release.elf"; \
fi ;; \
*) probe-rs run --chip $chip "{{firmware_dir}}/donglora-{{board}}-release.elf" ;; \
esac
# Show binary size for a release build
size board:
@just _cargo {{board}} "size --release"
# Run host-side protocol unit tests
test:
DONGLORA_HOST_TEST=1 cargo test
# Example scripts (just ex rx, just ex tx, just ex meshcore, ...)
mod ex 'examples/justfile'
# ── Private helpers ───────────────────────────────────────────────────
# Install mise-managed tools if any are missing (fast no-op when current)
[private]
_ensure_tools:
@mise install
# Run a cargo command for a board.
# Xtensa: nightly cargo + esp rustc (via RUSTC override) + -Zbuild-std=core
[private]
_cargo board cmd extra_args="":
@just _ensure_tools
@read -r feat target chip <<< "$(just _info {{board}})"; \
env=""; extra=""; \
case "$target" in xtensa-*) \
just _require_esp_toolchain; \
[ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \
env="RUSTC=$(rustup which rustc --toolchain esp)"; extra="+nightly"; buildstd="-Zbuild-std=core,alloc";; \
esac; \
eval $env cargo $extra {{cmd}} --target "$target" --features "$feat" $buildstd {{extra_args}}
# Ensure a board's toolchain is available, auto-installing if needed
[private]
_can_build board:
@read -r feat target chip <<< "$(just _info {{board}})"; \
case "$target" in \
xtensa-*) just _require_esp_toolchain ;; \
*) rustup target list --installed | grep -q "^$target$" || rustup target add "$target" ;; \
esac
# Ensure the ESP Xtensa toolchain is installed (espup installed via mise)
[private]
_require_esp_toolchain:
@just _ensure_tools
@if ! rustup toolchain list | grep -q "^esp"; then \
echo "ESP toolchain not found, installing via espup (this may take a while)..." >&2; \
espup install || { echo "error: espup install failed" >&2; exit 1; }; \
fi; \
if ! rustup component list --toolchain nightly --installed 2>/dev/null | grep -q "^rust-src"; then \
echo "Installing nightly rust-src (needed for -Zbuild-std)..." >&2; \
rustup component add rust-src --toolchain nightly || { echo "error: failed to install rust-src" >&2; exit 1; }; \
fi
# Find a serial port matching any of the given VID:PID pairs (checked in order)
[private]
_find_port +vid_pids:
@for vidpid in {{vid_pids}}; do \
vid="${vidpid%%:*}"; pid="${vidpid##*:}"; \
for dev in /dev/ttyACM* /dev/ttyUSB*; do \
[ -e "$dev" ] || continue; \
info=$(udevadm info --query=property --name="$dev" 2>/dev/null) || continue; \
dev_vid=$(echo "$info" | sed -n 's/^ID_VENDOR_ID=//p' | tr '[:upper:]' '[:lower:]'); \
dev_pid=$(echo "$info" | sed -n 's/^ID_MODEL_ID=//p' | tr '[:upper:]' '[:lower:]'); \
if [ "$dev_vid" = "$vid" ] && [ "$dev_pid" = "$pid" ]; then \
echo "$dev"; exit 0; \
fi; \
done; \
done
[private]
_copy_firmware board profile:
@mkdir -p {{firmware_dir}}
@read -r feat target chip <<< "$(just _info {{board}})"; \
src="target/$target/{{profile}}/donglora"; \
dst="{{firmware_dir}}/donglora-{{board}}-{{profile}}"; \
if [ -f "$src" ]; then cp "$src" "$dst.elf"; echo "→ $dst.elf"; fi
[private]
_info name:
@if [ "{{name}}" == "heltec_v3" ]; then echo "{{heltec_v3}}"; \
elif [ "{{name}}" == "heltec_v4" ]; then echo "{{heltec_v4}}"; \
elif [ "{{name}}" == "rak_wisblock_4631" ]; then echo "{{rak_wisblock_4631}}"; \
else echo "Unknown board: {{name}}" >&2; exit 1; fi
+10
View File
@@ -0,0 +1,10 @@
[tools]
rust = "1.93"
python = "3.14"
uv = "latest"
just = "latest"
jj = "latest"
"cargo:espup" = "0.16"
"cargo:espflash" = "3" # 4.x has broken nix dependency on current stable Rust
"cargo:probe-rs-tools" = "0.31"