mirror of
https://github.com/imjasonh/esp32
synced 2026-07-06 23:52:24 +00:00
README: refresh to reflect operational OTA, add CI badges
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
15a19eb46c
commit
0ebc860e6f
1 changed files with 50 additions and 79 deletions
129
README.md
129
README.md
|
|
@ -1,117 +1,88 @@
|
|||
# ESP32 Rust
|
||||
|
||||
Std Rust on an Inland ESP-WROOM-32 dev board. Currently a Wi-Fi + HTTPS
|
||||
demo; e-ink display work coming next (see [`eink-plan.md`](eink-plan.md)).
|
||||
[](https://github.com/imjasonh/esp32/actions/workflows/ci.yml)
|
||||
[](https://github.com/imjasonh/esp32/actions/workflows/publish.yml)
|
||||
|
||||
Std Rust on an Inland ESP-WROOM-32 dev board with end-to-end OTA over
|
||||
GHCR + cosign keyless signing. E-ink display work coming next (see
|
||||
[`eink-plan.md`](eink-plan.md)).
|
||||
|
||||
## What it does today
|
||||
|
||||
- Connects to Wi-Fi, fetches public IP and weather over HTTPS.
|
||||
- Polls `ghcr.io/imjasonh/esp32:latest` every ~60s for new firmware.
|
||||
- For each new digest, fetches the cosign Sigstore Bundle, verifies the
|
||||
signature, the cert chain to the bundled Sigstore root, and that the
|
||||
signer's identity matches the compile-time allowlist in
|
||||
[`src/trust.rs`](src/trust.rs).
|
||||
- On verify-pass, streams the layer to the inactive OTA partition,
|
||||
reboots, and only marks the new image valid after Wi-Fi + registry
|
||||
bringup checks pass. Any post-OTA failure auto-rolls back via the
|
||||
bootloader.
|
||||
|
||||
Push to `main` → CI builds → publish workflow pushes a signed image to
|
||||
GHCR → device picks it up on its next poll. See
|
||||
[`ota-plan.md`](ota-plan.md) for the full design.
|
||||
|
||||
## Hardware
|
||||
|
||||
- **Board**: Inland ESP-WROOM-32 (Micro Center SKU 027466). Connects as
|
||||
`/dev/cu.usbserial-0001` on macOS via the onboard CP210x USB-UART.
|
||||
- **No user LED**: this specific board variant only has a power LED ("D1",
|
||||
always on). There is no software-controllable LED. For visible output,
|
||||
attach an external LED + ~330Ω resistor to a GPIO, or watch logs via
|
||||
- **No user LED** on this variant — only a power LED. Watch logs via
|
||||
`make monitor`.
|
||||
|
||||
## Prerequisites (one-time, macOS)
|
||||
|
||||
```bash
|
||||
# Cargo tools:
|
||||
# espup installs the Xtensa Rust toolchain (rustc fork + LLVM + GCC)
|
||||
# espflash flashes firmware over USB serial
|
||||
# ldproxy linker shim used by esp-idf-sys
|
||||
cargo install espup espflash ldproxy
|
||||
|
||||
# C build deps for the ESP-IDF SDK
|
||||
brew install cmake ninja dfu-util
|
||||
|
||||
# Xtensa Rust toolchain + GCC + esp-clang, installed under
|
||||
# ~/.rustup/toolchains/esp/. The Makefile finds them via wildcards
|
||||
# so future espup updates don't require any path edits.
|
||||
brew install cmake ninja dfu-util cosign
|
||||
espup install --targets esp32
|
||||
|
||||
# uv (the Makefile uses it to install Python 3.12 and shim it into PATH
|
||||
# so the ESP-IDF Python venv is bootstrapped against 3.12 instead of
|
||||
# Apple's 3.9). Skip if you already have uv.
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh # if you don't have uv
|
||||
```
|
||||
|
||||
## First build
|
||||
## First flash (USB)
|
||||
|
||||
```bash
|
||||
make wifi.env # creates wifi.env from template
|
||||
$EDITOR wifi.env # fill in WIFI_SSID and WIFI_PASS
|
||||
make run # build, flash, open serial monitor
|
||||
make flash-all run # full erase + bootloader + partition + app, then monitor
|
||||
```
|
||||
|
||||
The first build clones ESP-IDF v5.2.2 into `.embuild/` and bootstraps a
|
||||
Python venv inside it. Plan on 5–10 minutes. Subsequent builds are fast.
|
||||
The first build clones ESP-IDF v5.2.2 into `.embuild/` (5–10 min).
|
||||
Subsequent builds are fast.
|
||||
|
||||
## Day-to-day
|
||||
|
||||
```
|
||||
make build Compile (requires wifi.env)
|
||||
make flash Build (if needed) and flash app to PORT
|
||||
make flash-all Erase + write bootloader, partition table, and app
|
||||
make flash Build + flash app (use flash-all after partitions change)
|
||||
make monitor Open serial monitor; Ctrl+C to exit
|
||||
make run Build + flash + monitor
|
||||
make publish Build, push OCI artifact to ghcr.io/imjasonh/esp32, cosign sign
|
||||
make clean cargo clean
|
||||
|
||||
make flash PORT=/dev/cu.usbserial-XXXX # override the port
|
||||
```
|
||||
|
||||
Use `make flash-all` after partition-table changes, or as the recovery
|
||||
path if both OTA slots end up bad and the bootloader can't auto-revert
|
||||
(see "USB recovery" in [`ota-plan.md`](ota-plan.md)). Day-to-day use is
|
||||
just `make flash` or `make run`.
|
||||
|
||||
## Publishing firmware to GHCR (OTA, phase 1)
|
||||
|
||||
The firmware is packaged as an OCI artifact and pushed to
|
||||
`ghcr.io/imjasonh/esp32`. The device will pull it back down (phase 2,
|
||||
not yet implemented).
|
||||
|
||||
One-time setup:
|
||||
|
||||
1. Mint a classic PAT at https://github.com/settings/tokens/new with the
|
||||
`write:packages` scope. (Fine-grained PATs don't currently expose a
|
||||
Packages permission for user-owned packages.)
|
||||
2. `echo 'export GH_TOKEN=ghp_...' > gh.env` (gitignored)
|
||||
3. After the first push, set the package's visibility to public at
|
||||
https://github.com/users/imjasonh/packages/container/esp32/settings —
|
||||
the device will pull anonymously, no token on the ESP32.
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
make publish # build firmware, save .bin, push :latest and :sha-<short>
|
||||
```
|
||||
|
||||
The publisher tool lives at [`tools/publisher/`](tools/publisher) — Rust,
|
||||
uses `oci-distribution`. It's a separate cargo project so it builds for
|
||||
the host, not for the ESP32.
|
||||
|
||||
## What the firmware does
|
||||
|
||||
`src/main.rs` connects to Wi-Fi using the credentials baked in at compile
|
||||
time from `wifi.env`, then issues two HTTPS GETs and logs the responses
|
||||
over serial:
|
||||
|
||||
- `https://api.ipify.org?format=json` — public IP, JSON
|
||||
- `https://wttr.in/?format=3` — one-line weather for your location
|
||||
`make publish` requires `gh.env` (see [`ota-plan.md`](ota-plan.md) for
|
||||
PAT setup) and a real cosign OIDC flow the first time per ~10min window
|
||||
— a browser pops to authenticate. CI does this automatically via the
|
||||
GitHub Actions workflow's ambient OIDC token.
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
src/main.rs firmware
|
||||
Cargo.toml deps: esp-idf-svc, embedded-svc, log, anyhow
|
||||
build.rs embuild + cargo:rerun-if-env-changed for WIFI_*
|
||||
.cargo/config.toml target, linker, runner, build-std
|
||||
rust-toolchain.toml pins the `esp` rustc toolchain
|
||||
sdkconfig.defaults ESP-IDF kconfig overrides (stack, mbedtls bundle)
|
||||
Makefile canonical build / flash / monitor entrypoint
|
||||
src/main.rs firmware entrypoint, Wi-Fi + HTTPS + OTA loop
|
||||
src/ota.rs OTA polling, manifest fetch, blob streaming
|
||||
src/sig.rs cosign Sigstore Bundle verification
|
||||
src/trust.rs compile-time allowlist of signer identities
|
||||
trust/ bundled Sigstore Fulcio root + intermediate certs
|
||||
tools/publisher/ host-side Rust tool that pushes signed OCI artifacts
|
||||
.github/workflows/ ci.yml (PRs) + publish.yml (push to main)
|
||||
Cargo.toml firmware deps
|
||||
partitions.csv OTA-capable partition table (1.75 MB app slots)
|
||||
sdkconfig.defaults.in ESP-IDF kconfig (Makefile substitutes paths)
|
||||
Makefile build / flash / monitor / publish entrypoints
|
||||
wifi.env.example template for compile-time Wi-Fi creds
|
||||
eink-plan.md plan for the upcoming e-ink display work
|
||||
ota-plan.md plan for OCI-artifact-based OTA updates
|
||||
tools/publisher/ Rust tool that pushes firmware as an OCI artifact
|
||||
notes.txt internal design notes and issues hit during setup
|
||||
eink-plan.md planned e-ink display work
|
||||
ota-plan.md full OTA design + per-phase status
|
||||
notes.txt internal design notes and setup gotchas
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue