feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513)

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>
This commit is contained in:
ruv
2026-05-08 09:44:01 -04:00
parent bfb3fdee13
commit 22d47a71e3
10 changed files with 754 additions and 0 deletions
@@ -0,0 +1,71 @@
/* SPDX-License-Identifier: MIT
*
* ESP32-S3 on-device temporal head — public C ABI (ADR-095, #513).
*
* Consumed by edge_processing.c / adaptive_controller.c. Backed by a
* Rust staticlib that wraps `ruvllm_sparse_attention`. See
* components/ruv_temporal/src/lib.rs for the implementation.
*
* Threading: NOT internally synchronised. Per ADR-095 §3.3 callers run
* a single dedicated FreeRTOS task that owns the context and
* serialises push() and classify(). init() and destroy() are NOT safe
* against concurrent push/classify on the same handle.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct RuvTemporalCtx ruv_temporal_ctx_t;
/* Allocate a temporal-head context.
*
* weights — flat-buffer of model weights (Phase 5 wires the format),
* may be NULL during Phase 4 scaffolding.
* weights_len — bytes of `weights`, 0 if weights is NULL.
* input_dim — feature dimension per frame (e.g. 60 for rv_feature_state_t).
* window_len — number of frames in the rolling window (e.g. 256).
* n_classes — output logit count (e.g. 4 for gesture, 3 for fall).
* out_ctx — receives the new context pointer on ESP_OK.
*
* Returns ESP_OK on success, ESP_ERR_INVALID_ARG for null/zero inputs,
* ESP_ERR_NO_MEM if buffer allocation fails.
*/
esp_err_t ruv_temporal_init(const uint8_t *weights,
size_t weights_len,
uint32_t input_dim,
uint32_t window_len,
uint32_t n_classes,
ruv_temporal_ctx_t **out_ctx);
/* Push one feature frame into the rolling window. Hot path — cheap,
* no allocation. `frame` must point to at least `input_dim` floats.
*/
esp_err_t ruv_temporal_push(ruv_temporal_ctx_t *ctx, const float *frame);
/* Run the temporal-head forward and write `n_classes` class logits
* into the caller-owned `logits` buffer (must be at least n_classes
* floats). `n_classes` must match the value passed to init().
*/
esp_err_t ruv_temporal_classify(ruv_temporal_ctx_t *ctx,
float *logits,
uint32_t n_classes);
/* Release a context allocated by ruv_temporal_init. Safe on NULL. */
void ruv_temporal_destroy(ruv_temporal_ctx_t *ctx);
/* Self-test — proves the upstream sparse-attention kernel links and
* runs. Returns ESP_OK on success. Useful as a smoke check on first
* boot before allocating a real context.
*/
esp_err_t ruv_temporal_kernel_self_test(void);
#ifdef __cplusplus
}
#endif