1
0
Fork 0
mirror of https://github.com/imjasonh/esp32 synced 2026-07-06 23:52:24 +00:00
esp32/docs/setup.md
Jason Hall 7e32f6decb docs: reorganise into docs/, fold logs-plan + monitoring-plan into observability.md
- New `docs/observability.md`: descriptive (present-tense) write-up
  of the Cloud Logging + Cloud Monitoring pipelines as currently
  shipped. Replaces the historical `logs-plan.md` + `monitoring-plan.md`
  planning docs.
- New `docs/setup.md`: prerequisites, first flash, day-to-day Make
  targets, and the optional GCP setup (lifted out of the README so the
  README can stay terse). Includes the Python 3.12-shim explanation
  from the old `notes.txt`.
- Move `ota.md` → `docs/ota.md`.
- Move `eink-plan.md` → `docs/eink-plan.md`. Per-feature plans still
  use the `<feature>-plan.md` name; once shipped they get rewritten
  in present tense alongside the other docs.
- Delete `logs-plan.md`, `monitoring-plan.md`, `notes.txt`. Their
  user-facing content is now in `docs/setup.md`; their LLM-relevant
  bits (architectural rationale, partition-table CMake quirk, Python
  shim, no-LED, `make` conventions, NVS key length cap) are in
  `CLAUDE.md`.
- Trim `README.md` to a top-level overview + links into `docs/`.
- Update internal cross-references (Makefile, ota.md, eink-plan.md,
  tools/provision/src/main.rs doc-comment).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 08:30:43 -04:00

5.7 KiB
Raw Permalink Blame History

Setup

End-to-end first-time setup for an Inland ESP-WROOM-32 dev board on macOS.

Hardware

  • Board: Inland ESP-WROOM-32 (Micro Center SKU 027466, rebadged Keyestudio KS0413, "mini" form factor). Connects as /dev/cu.usbserial-0001 over the onboard CP210x USB-UART.
  • No user LED on this variant — only a power LED ("D1", hardwired to 3V3, always on). The DOIT/DEVKITC-style blue LED on GPIO 2 is not populated. Watch boot via make monitor.

One-time host install

cargo install espup espflash ldproxy
brew install cmake ninja dfu-util cosign jq
espup install --targets esp32
curl -LsSf https://astral.sh/uv/install.sh | sh   # if you don't have uv

uv provides Python 3.12 for ESP-IDF's tooling — see why below.

Provisioning

make provisioning.toml             # creates from template
$EDITOR provisioning.toml          # fill in wifi creds + trust identities
make bootstrap                     # build, flash everything, write NVS
make monitor                       # watch it boot and connect

The first build clones ESP-IDF v5.2.2 into .embuild/ (510 min). Subsequent builds are fast.

The OTA-distributed firmware contains no secrets — Wi-Fi creds, Sigstore trust roots, and (optionally) GCP service-account keys all live in NVS, written via USB by make provision. See ota.md for the full design.

Day-to-day Make targets

make build      Compile firmware
make flash      Build + flash app (use flash-all after partitions change)
make flash-all  Erase + write bootloader, partition table, app
make provision  Write NVS partition from provisioning.toml over USB
make bootstrap  flash-all + provision (new device setup)
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 publish requires gh.env (see ota.md for PAT setup) and a real cosign OIDC flow the first time per ~10 min window — a browser pops to authenticate. CI does this automatically via the GitHub Actions workflow's ambient OIDC token.

Optional: GCP Cloud Logging + Monitoring

The firmware can ship structured tracing events to Cloud Logging and chip-health metrics (heap, stack, wifi, cpu, …) to Cloud Monitoring. Both are opt-in per device — without a [gcp] block in provisioning.toml, the device boots normally and emits to serial only. Full design in observability.md.

One service account and one key cover both APIs. One-time GCP setup:

PROJECT_ID=<YOUR_PROJECT_ID>
SA_NAME=<YOUR_SA_NAME>
SA_EMAIL=$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com

# Create the service account.
gcloud iam service-accounts create $SA_NAME \
    --display-name="ESP32 device logger" \
    --project=$PROJECT_ID

# Grant only logging.logWriter + monitoring.metricWriter — least
# privilege. The device can write log entries and metric points;
# nothing else.
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:$SA_EMAIL" \
    --role="roles/logging.logWriter"
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:$SA_EMAIL" \
    --role="roles/monitoring.metricWriter"

# Create + download a JSON key. Keep this file safe — anyone with it
# can write logs + metrics as this SA.
gcloud iam service-accounts keys create gcp-sa-key.json \
    --iam-account=$SA_EMAIL \
    --project=$PROJECT_ID

# Extract the RSA private key PEM and the key id into the forms
# `tools/provision/` wants. Use `jq -j` (no trailing newline) — the
# device-side PEM parser is strict.
jq -j .private_key    gcp-sa-key.json > gcp-sa-key.pem
KEY_ID=$(jq -r .private_key_id gcp-sa-key.json)
echo "sa_key_id = $KEY_ID"

Then add a [gcp] block to provisioning.toml (template in provisioning.toml.example) using $PROJECT_ID, $SA_EMAIL, the printed KEY_ID, and the path gcp-sa-key.pem. Re-run make provision and reboot the device.

Inspect what's flowing in:

# Logs (Cloud Logging)
gcloud logging read \
    'logName="projects/'"$PROJECT_ID"'/logs/esp32-firmware"' \
    --limit=20 --project=$PROJECT_ID

# Metrics (Cloud Monitoring) — one metric type at a time
TOKEN=$(gcloud auth print-access-token)
START=$(date -u -v-30M +%Y-%m-%dT%H:%M:%SZ)
END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -sG "https://monitoring.googleapis.com/v3/projects/$PROJECT_ID/timeSeries" \
    -H "Authorization: Bearer $TOKEN" \
    --data-urlencode 'filter=metric.type="custom.googleapis.com/esp32/free_heap"' \
    --data-urlencode "interval.startTime=$START" \
    --data-urlencode "interval.endTime=$END" | jq

Threat model: the SA private key sits in NVS unencrypted. Anyone with physical access to the chip can extract it. Mitigation is strict SA scoping (see roles above) and a logs/metrics-only project. Real hardening = Flash Encryption + Secure Boot v2 (deferred; see ota.md Future work).

Python 3.12 shim

embuild bootstraps the ESP-IDF venv using whatever python3 is first on PATH. On macOS that's Apple's /usr/bin/python3 (3.9.6). ESP-IDF v5.2 nominally supports 3.83.12, but pip dependency resolution on 3.9 silently drops some transitive deps (notably ruamel.yaml and its dependents) and IDF's check then fails with cryptic "Failed to run Python dependency check ... Error: 255".

Fix: the Makefile creates .embuild/python-shim/python3 as a symlink to a uv-managed Python 3.12 and prepends that directory to PATH for every recipe. The shim isn't checked in — it's regenerated by the ensure-python-shim Make target, a prerequisite of build.

If you hit the dependency-check error after upgrading or after a fresh clone:

rm -rf .embuild
make build