1
0
Fork 0
mirror of https://github.com/imjasonh/esp32 synced 2026-07-08 08:25:02 +00:00
Commit graph

4 commits

Author SHA1 Message Date
01c8c2da75 provision OTA repo/tag/poll_secs; tag logs+metrics with fw_version
Two related additions for fleet observability + tunability:

1. `[ota]` block in provisioning.toml. Optional, individually-optional
   fields: `repo`, `tag`, `poll_secs`. Writes to the existing `ota` NVS
   namespace (key names match src/ota.rs's NVS_REPO / NVS_TAG /
   NVS_POLL_SECS). Missing keys leave the firmware on its compile-time
   defaults (ghcr.io/imjasonh/esp32:latest, 60 s poll).

2. `fw_version` (the GIT_SHA baked in at build time) on every log
   entry and every metric series, so behaviour can be correlated with
   a release across the fleet.
   - cloud_log: jsonPayload.fw_version on every entry.
   - metrics: metric.labels.fw_version on every TimeSeries (resource
     labels can't carry it — `generic_node` has a fixed schema; metric
     labels let queries split heap, rssi, etc. by fw).

Both threaded from main.rs's existing FW_VERSION constant; no new
config plumbing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:56:26 -04:00
99da9c6b87 metrics: ship Cloud Monitoring time series; refactor gcp_auth
Implements monitoring-plan.md.

- src/gcp_auth.rs: shared TokenProvider. Mints a multi-scope
  (logging.write + monitoring.write) JWT once and caches the bearer;
  cloud_log + metrics threads share it. Also moves http_post,
  parse_signing_key, b64url, now_unix_secs, unix_to_rfc3339,
  device_mac out of cloud_log.rs.
- src/cloud_log.rs: cloud_log::run takes Arc<TokenProvider> instead
  of owning the JWT/token state. LogQueue::stats() exposes depth +
  lifetime drop count for the metrics path.
- src/metrics.rs: periodic snapshot → POST /v3/projects/{id}/timeSeries.
  GAUGE INT64 metrics under custom.googleapis.com/esp32/<name>:
  free_heap, free_heap_internal, min_free_heap, largest_free_block,
  stack_hwm (label task=main|ota|cloud_log|metrics), wifi_rssi,
  wifi_channel, cpu_freq_mhz, uptime_secs, nvs_used/free_entries,
  cloud_log_queue_depth, cloud_log_dropped_total. Auto-creates
  MetricDescriptors on first POST. Cadence via NVS metric_intvl
  (gcp namespace), default 300s, 0 disables.
- Each spawned thread publishes its TaskHandle_t into a module-level
  AtomicUsize so metrics can read uxTaskGetStackHighWaterMark per
  task without holding handles for them. (Xtensa StackType_t is
  uint8_t, so the API returns bytes — no word-size multiplication.)

Plus:
- sdkconfig: enable CONFIG_MBEDTLS_DYNAMIC_BUFFER (+ free_config_data,
  free_ca_cert) so three concurrent TLS clients (cloud_log + metrics
  + OTA) don't blow our heap budget. Recovers ~15-25 KB per session.
- Makefile: `make flash` and `make run` now pass --partition-table.
  Without it, espflash 4.x writes a *default* (factory-only) partition
  table over our OTA layout, silently bricking OTA on the device.
- README + provisioning.toml.example: gcloud command for
  roles/monitoring.metricWriter; document metrics_interval_secs.
- tools/provision: write metric_intvl NVS u32 (default 300).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:38:04 -04:00
7cb4e8c75c cloud_log: NVS-loaded config, tracing layer, queue, stub sender
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>
2026-05-02 17:06:00 -04:00
1928dcfd47 Provision Wi-Fi creds + trust roots via NVS instead of compile-time embed
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>
2026-05-02 16:14:26 -04:00