Closes the format contract on the firmware side. Source-only — Phase 5
toolchain blocker still prevents actually compiling, but when it
unblocks this is one less thing to write under time pressure.
- src/weights.rs — no_std mirror of v2/.../weights.rs. Same magic
('RVNE'), same version 1, same CRC32-IEEE polynomial (matches the C
side in temporal_task.c). Bit-for-bit lockstep with the host: a
blob produced by host WeightBlob::serialize() parses here as a
WeightBlobView byte-for-byte.
Borrowed-slice parse design: the firmware loader receives weights
via mmap'd EMBED_FILES or NVS read into a heap buffer. The parser
takes &[u8] with no copy — view fields point into the caller's
buffer. Caller is responsible for keeping the buffer alive for the
view's lifetime.
Loader errors map to esp_err_t-style codes via
weight_load_err_to_esp() so the C ABI can surface specific failure
modes (ESP_ERR_INVALID_ARG for magic/version/size, ESP_ERR_INVALID_CRC
for corruption, ESP_ERR_INVALID_SIZE for shape validation failures).
- src/lib.rs — ruv_temporal_init now optionally validates a non-NULL
weights blob. NULL pointer is still allowed during the Phase 4/5
bring-up window (kernel forward isn't actually consuming weights
yet), but when caller passes a real blob we parse + sanity-check
declared dims against runtime arguments. Catches deploy bugs at
init() rather than at first classify() — the firmware Tmr Svc work
in v0.6.4 taught us that classify-time crashes are the worst kind.
- README.md — Phase 6 marked done (verified by 8MB firmware build with
feature off in commit 7994af822). Added module map table covering
lib.rs / window.rs / weights.rs / ruv_temporal.h / shim.c.
What's deliberately NOT in this commit:
- Cross-compile validation. Same toolchain blocker as before.
- Kernel-side wiring of weights into the forward pass. That's
Phase 6+ of the firmware roadmap — once the kernel is wired,
weights become a required arg, not an optional one.
- Tests on the firmware side. They'd need build-std working to run;
16/16 host tests cover the format end-to-end via the lockstep
polynomial.
Co-Authored-By: claude-flow <ruv@ruv.net>
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>
Phase 4 of the #513 roadmap: ESP-IDF component skeleton at
`firmware/esp32-csi-node/components/ruv_temporal/`. Source is complete
and self-consistent; cross-compile to xtensa-esp32s3-none-elf is
blocked by a known-broken esp-rs nightly snapshot (details in the
component README).
What's in the scaffold:
- `Cargo.toml` — staticlib, no_std + alloc, deps on the path-vendored
`ruvllm_sparse_attention` (matching ADR-096's host-side dep) and
`esp-alloc`/`critical-section` for the no_std allocator and lock
primitives.
- `src/lib.rs` — public C ABI (init / push / classify / destroy /
self_test) with `#[no_mangle]` exports, a `[#used]` keepalive table
to defeat aggressive linker stripping, esp-alloc as the global
allocator (heap region added at runtime by the firmware), and a
loop-on-panic handler (Phase 5 will route through esp_system_abort).
- `src/window.rs` — `FrameRing`, the rolling-window buffer that
`ruv_temporal_push` writes to. Chronological iteration via
`iter_chronological()` so the kernel sees oldest-first.
- `include/ruv_temporal.h` — the public C header consumed by
edge_processing.c. Threading contract documented inline (single
dedicated FreeRTOS task, no internal locks).
- `CMakeLists.txt` — runs `cargo +esp build` as an ESP-IDF
pre-component-register step, then registers the static library
through `idf_component_register` + `target_link_libraries(...
INTERFACE ...)`. `shim.c` exists only because
`idf_component_register` requires SRCS.
- `.cargo/config.toml` + `rust-toolchain.toml` — pin the build to
`xtensa-esp32s3-none-elf` and the `esp` toolchain channel so
`cargo build` without flags Just Works once the toolchain is
unblocked.
- `README.md` — Phase status table, Phase 5 toolchain blocker
explanation, and the espup install fix.
ABI calls into edge_processing.c (Phase 6) and COM8 validation
(Phase 7) follow once the cross-compile is unblocked.
Closes nothing yet; advances #513.
Co-Authored-By: claude-flow <ruv@ruv.net>