esp32 std Rust — internal design notes
======================================

This file is internal: design decisions, issues we hit during setup, and
gotchas to remember. For prerequisites and day-to-day workflow, see
README.md.

Why std instead of no_std
-------------------------
Std Rust on ESP32 is built on top of `esp-idf-sys`, which builds and
links the ESP-IDF C SDK. That means we need the IDF C/C++ toolchain,
CMake/Ninja, and ESP-IDF's Python orchestration (kconfig, partition
tables, esptool).

The lighter alternative is no_std bare-metal with `esp-hal`. No Python,
no CMake, no C SDK — just LLVM. Worth knowing for future projects where
std isn't required. We picked std for this project because we want easy
Wi-Fi + HTTP + TLS via esp-idf-svc, plus standard threading/heap.

Why we pin Python 3.12 via a generated shim
-------------------------------------------
embuild bootstraps the 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.8–3.12, but pip dependency resolution on 3.9 silently drops
some transitive deps that the IDF check then complains about (see issue B
below).

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, which is a prerequisite of `build`.

Issues hit during initial setup
-------------------------------

A. CMake "Failed to run Python dependency check ... Error: 255",
   complaining that `esp-idf-kconfig` requires `ruamel.yaml`.
   Cause: the IDF python venv was missing `ruamel.yaml` even though it
   was declared as a transitive dep. Pip's resolution on Python 3.9
   silently dropped it.
   First-pass fix that *seemed* to work but didn't fully:
     uv pip install \
       --python ./.embuild/espressif/python_env/idf5.2_py3.9_env/bin/python \
       ruamel.yaml
   Made the import work, but the IDF check still failed for the next dep
   down (see B).

B. After installing ruamel.yaml, IDF's check still reported it missing
   for the next dep (`esp-idf-panic-decoder` -> ruamel.yaml). The real
   issue was the venv being on Python 3.9.6. Several of these resolution
   glitches go away on 3.12, which is also what newer IDF versions
   recommend.
   Real fix:
     1. install uv (one-time)
     2. The Makefile's `ensure-python-shim` target installs 3.12 via uv,
        creates `.embuild/python-shim/python3` -> uv 3.12, and the Makefile
        prepends that dir to PATH for every recipe.
     3. rm -rf .embuild  (force IDF venv recreate)
     4. make build       (embuild bootstraps a fresh 3.12 venv;
                          ensure-python-shim re-creates the shim too)

Why WIFI_SSID/WIFI_PASS are compile-time env vars
-------------------------------------------------
src/main.rs reads them via `env!()`, baked into the firmware binary.
Tradeoff: simple, no provisioning UI needed, works the moment the chip
boots. Downside: changing networks means rebuilding and reflashing.
For a single-developer playground this is the right tradeoff. If we
ever want to make this distributable, switch to NVS-stored creds with
either an initial AP-mode provisioning portal or BLE provisioning.

The build.rs has cargo:rerun-if-env-changed lines for both vars so
editing wifi.env triggers a rebuild without `cargo clean`.

Why export-esp.sh is gone, replaced by Makefile-internal env
------------------------------------------------------------
espup drops `~/export-esp.sh` with hardcoded versioned paths to the
Xtensa toolchain. Sourcing it before each cargo command was awkward.
The Makefile sets LIBCLANG_PATH and prepends to PATH itself, using
wildcards over the date-stamped subdirectories so espup version bumps
don't require editing the file. Keeps the env close to the build steps
that need it.

Custom partition table path resolution
--------------------------------------
ESP-IDF resolves `CONFIG_PARTITION_TABLE_CUSTOM_FILENAME` relative to
its top-level CMake project's source dir. For embuild's synthetic IDF
project, that's `target/.../esp-idf-sys-<hash>/out/`, not our repo root.
A bare `"partitions.csv"` in sdkconfig therefore fails:
  ninja: error: '.../out/partitions.csv', needed by 'partition-table.bin', missing

Workaround we settled on: keep `sdkconfig.defaults.in` checked in with
the placeholder `@PROJECT_DIR@/partitions.csv`, and have the Makefile
substitute the absolute path at build time, writing the real
`sdkconfig.defaults` (gitignored). The .in template is committed so
anyone cloning the repo gets the right shape; the resolved sdkconfig
is per-machine.

Other things tried that didn't pan out:
- Putting partitions.csv at the repo root and hoping IDF would walk up
  to find it — it doesn't.
- `[package.metadata.esp-idf-sys] esp_idf_sdkconfig_defaults = [...]`
  with a generated absolute-path file — chicken-and-egg with build
  ordering (esp-idf-sys's build.rs runs before any file we'd write).
- Pre-copying partitions.csv into the synthetic project's `out/` from
  the Makefile — works, but only after the synthetic project has been
  created at least once, requiring an awkward two-build first run.

Onboard LED — there isn't one on this board
-------------------------------------------
The Inland ESP-WROOM-32 we have (Micro Center SKU 027466, rebadged
Keyestudio KS0413, "mini" form factor) has only a power LED ("D1",
hardwired to 3V3, always on). The DOIT/DEVKITC-style blue LED on
GPIO 2 is NOT populated on this variant.

Confirmed by flashing a GPIO 2 toggle and watching the serial monitor:
the firmware boots, prints `blinky starting`, and the IDF gpio driver
logs reconfiguring GPIO 2 — but no LED visibly changes. To see anything
blink on this board you need either an external LED + ~330Ω resistor,
or the e-ink display we're adding (see eink-plan.md).
