wifi-densepose/firmware/esp32-csi-node/components/ruv_temporal
ruv 7994af8221 feat(firmware): wire temporal_task.c + Kconfig + ruv_temporal component (Phase 6, #513)
Phase 6 of #513: C-side wiring for the on-device temporal head. Builds
cleanly with feature OFF (default); 8MB binary delta is +96 bytes vs
v0.6.4-esp32 — that's the no-op shim path. Feature ON depends on the
Rust component (Phase 5, currently blocked by upstream esp-rs nightly).

Files:

- main/temporal_task.{c,h} — owns the FreeRTOS task lifecycle. Per
  ADR-095 §3.3 the task has its own 16 KB stack pinned to Core 1 and
  is fed via a 32-deep FreeRTOS queue. With feature OFF the .c file
  collapses to three ESP_ERR_NOT_SUPPORTED stubs so callers don't
  need #ifdefs at every call site.
- main/temporal_task.h — defines rv_temporal_pkt_t (40 bytes,
  magic 0xC5110007 — next free in the existing 0xC5110001..0006
  family) and the task lifecycle API. Build-time _Static_assert
  pins the wire format.
- main/Kconfig.projbuild — new menu "On-device temporal head
  (ADR-095, #513)" with CONFIG_CSI_TEMPORAL_HEAD_ENABLED (default n)
  plus four runtime-tuneable knobs: TEMPORAL_INPUT_DIM (16),
  TEMPORAL_WINDOW_LEN (256), TEMPORAL_N_CLASSES (4), and
  TEMPORAL_CLASSIFY_PERIOD_MS (1000).
- main/CMakeLists.txt — adds temporal_task.c to SRCS unconditionally
  (the .c file feature-gates internally), and adds ruv_temporal to
  REQUIRES only when the feature is enabled so default builds don't
  pull in the Rust component.
- main/adaptive_controller.c — fast_loop_cb now extracts the 9
  feature floats from the pkt it just built and pushes them into
  temporal_task_push_frame after the existing stream_sender_send.
  Non-blocking; queue-full drops are coalesced and logged 1/sec.
- main/main.c — temporal_task_start() called right after
  adaptive_controller_init(). Wrapped in #ifdef so feature-off
  builds don't reference the (no-op-anyway) function.
- components/ruv_temporal/CMakeLists.txt — restructured. Top-level
  Kconfig guard registers an empty component when the feature is
  off (avoids running cargo without a working toolchain).
  add_custom_command moved AFTER idf_component_register so it
  doesn't fire in script mode (required by ESP-IDF v5.4).

Validation:
- Firmware builds clean with default config (feature OFF) on
  ESP-IDF v5.4 / esp32s3 target. Binary 1062 KiB / 2 MiB partition,
  48 % free.
- Static assertion catches wire-format drift (rv_temporal_pkt_t size).
- Host-side `cargo test -p wifi-densepose-temporal` still 5/5 from
  the earlier commit (no regression, this commit only touches
  firmware/).

Phase 7 (flash to COM8 + soak) deferred this iteration — board is
currently not enumerating on COM8; will pick up next iteration when
the ESP32 is reattached.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-08 11:28:11 -04:00
..
.cargo feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
include feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
src feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
CMakeLists.txt feat(firmware): wire temporal_task.c + Kconfig + ruv_temporal component (Phase 6, #513) 2026-05-08 11:28:11 -04:00
Cargo.lock feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
Cargo.toml feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
README.md feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
rust-toolchain.toml feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
shim.c feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00

README.md

ruv_temporal — ESP32-S3 on-device temporal head

ESP-IDF component implementing ADR-095 (#513). The Rust staticlib at src/lib.rs wraps ruvllm_sparse_attention (vendored at vendor/ruvector/crates/ruvllm_sparse_attention) and exposes a narrow C ABI declared in include/ruv_temporal.h.

Status

Phase Scope State
4 — Scaffold Cargo.toml, src/{lib.rs,window.rs}, include/ruv_temporal.h, CMakeLists.txt, .cargo/config.toml Done. Source compiles host-side syntax check; not yet cross-compiled to xtensa.
5 — Cross-compile cargo +esp build --release --target xtensa-esp32s3-none-elf produces libruv_temporal.a. Blocked — see below.
6 — Wire from edge_processing.c FreeRTOS task on Core 1, queue from adaptive_controller fast loop, push() in fast tick, classify() at 1 Hz, emit 0xC5110007 packet. Not started.
7 — COM8 validation Flash 8MB build with CONFIG_CSI_TEMPORAL_HEAD_ENABLED=y, soak ≥5 min, check no Tmr Svc / task_wdt overflow. Not started.

Phase 5 blocker — esp toolchain rust-src bug

The system esp toolchain at C:\Users\ruv\.rustup\toolchains\esp has no precompiled core for xtensa-esp32s3-none-elf. It requires -Z build-std=core,alloc, but the bundled rust-src snapshot (esp channel, nightly 2025-09-16) hits two known bugs when build-std compiles core:

  1. library/portable-simd/crates/core_simd/src/simd/ptr/mut_ptr.rsCopy trait and size_of not in scope, ~16,000 errors.
  2. library/core itself — "cannot resolve a prelude import", "attributes starting with rustc are reserved", concat! macro not found.

These are upstream Rust nightly snapshot regressions, not anything this component is doing wrong. The fix is to refresh the esp toolchain to a newer nightly:

C:/Users/ruv/.cargo/bin/espup.exe install
# (re-source export-esp.ps1 / export-esp.sh after install)

espup install pulls the latest pinned esp Rust + LLVM. It is a ~1.5 GB download and ~5-10 min install. That step lands in the next loop iteration of #513 implementation work.

Build (once Phase 5 unblocks)

From this directory:

cargo +esp build --release --target xtensa-esp32s3-none-elf

Output: target/xtensa-esp32s3-none-elf/release/libruv_temporal.a.

ESP-IDF's idf.py build will pick this up via CMakeLists.txtadd_custom_command runs the cargo build before idf_component_register consumes the static library.

C ABI summary

esp_err_t ruv_temporal_init(const uint8_t *weights, size_t wlen,
                            uint32_t input_dim, uint32_t window_len,
                            uint32_t n_classes,
                            ruv_temporal_ctx_t **out_ctx);
esp_err_t ruv_temporal_push(ruv_temporal_ctx_t *ctx, const float *frame);
esp_err_t ruv_temporal_classify(ruv_temporal_ctx_t *ctx,
                                float *logits, uint32_t n_classes);
void      ruv_temporal_destroy(ruv_temporal_ctx_t *ctx);
esp_err_t ruv_temporal_kernel_self_test(void);

Threading: caller is responsible. Per ADR-095 §3.3, the firmware will spawn a single dedicated FreeRTOS task that owns the context and serialises all calls — push() and classify() are not internally synchronised.