First commit of cloud-logging work (logs-plan.md). Foundation only:
the actual NTP/JWT/RSA/HTTPS-POST sender is the next commit; this
sender is a stub that writes 'would POST' to serial.
src/cloud_log.rs:
- GcpConfig::load reads the optional 'gcp' NVS namespace. If any
required key (project_id / sa_email / sa_key_id / sa_key_pem) is
missing, returns Ok(None) — cloud logging is opt-in per device.
- LogQueue: Mutex<VecDeque>-backed bounded ring buffer (256 entries),
drops oldest when full and surfaces the drop count on the next push.
- CloudLogLayer: tracing_subscriber Layer that captures events,
extracts structured fields via field::Visit, applies the configured
min_severity filter, and pushes onto the queue. Uses wall-clock
time when SystemTime::now() is past 2020 (NTP synced); else None
so Cloud Logging assigns server-side timestamps.
main.rs:
- Take NVS first, before any tracing events fire.
- If the gcp NVS namespace is populated, install CloudLogLayer as
the global tracing subscriber and spawn the sender thread (32 KB
stack).
- tracing now uses the 'log-always' feature so events still emit
log records even with a subscriber installed — keeps EspLogger
writing to serial regardless.
tools/provision/:
- Optional [gcp] section in provisioning.toml. Tool emits the gcp
namespace into the NVS CSV when present, validates min_severity
spelling early.
ota.md: gcp namespace added to NVS schema docs.
provisioning.toml.example: commented [gcp] block.
Firmware size: 1.52 MB -> 1.60 MB (tracing-subscriber + cloud_log code).
Plenty of slot headroom remaining (1.94 MB).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements provisioning-plan.md. The OTA-distributed firmware no longer
contains any secrets or per-device config — those live in NVS and are
written via USB by `make provision` from a (gitignored)
`provisioning.toml`. Same firmware bytes can run on any device.
Firmware changes:
- src/main.rs reads wifi/ssid + wifi/pass from NVS namespace `wifi`.
Strict-inert (sit-and-log) when missing; no compile-time fallback.
WIFI_SSID/WIFI_PASS env! macros gone.
- src/trust.rs becomes a `TrustConfig::load(nvs)` loader. Identities
come from NVS namespace `trust`, key `identities` (JSON-encoded
array). Sigstore root + intermediate PEMs come from `trust/fulcio_root`
and `trust/fulcio_inter` blobs. include_str! gone.
- src/sig.rs and src/ota.rs thread &TrustConfig through verify_bundle
and the OTA loop instead of reading global consts.
- build.rs no longer tracks WIFI_* env changes.
New tool:
- tools/provision/ host-side cargo crate. Reads provisioning.toml,
emits an NVS CSV, shells out to ESP-IDF's nvs_partition_gen.py to
produce a binary NVS image, optionally `espflash write-bin`'s it.
Make targets:
- `make provision` — build NVS image + flash it.
- `make bootstrap` — flash-all + provision (new device setup).
- `make build` no longer requires wifi.env.
- wifi.env removed from prereqs / WIFI_ENV variable removed.
Workflows:
- ci.yml: drops the placeholder wifi.env step, adds a `build provision`
job alongside the firmware + publisher jobs.
- publish.yml: drops WIFI_SSID/WIFI_PASS secret reads. Only secret
needed is the auto-injected GITHUB_TOKEN.
Docs:
- README updated to use `make bootstrap` + `make provision` flow.
Project layout moved to a new repo-local CLAUDE.md.
- provisioning.toml.example committed as the template the user copies.
- wifi.env.example removed (no longer used).
Migration: existing devices need `make bootstrap` over USB. The new
firmware has no embedded creds; OTAing to it without provisioning
would just sit in strict-inert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>