99 lines
3.7 KiB
C
99 lines
3.7 KiB
C
/* SPDX-License-Identifier: MIT
|
|
*
|
|
* temporal_task.h — On-device temporal head FreeRTOS task (ADR-095, #513).
|
|
*
|
|
* Owns the lifecycle of the `ruv_temporal_ctx_t` from
|
|
* components/ruv_temporal/include/ruv_temporal.h. Exposes:
|
|
*
|
|
* 1. `temporal_task_start()` — spawn the task with its own 16 KB stack
|
|
* pinned to Core 1, allocate a feed queue. Caller (main.c) ignores
|
|
* ESP_ERR_NOT_SUPPORTED when CONFIG_CSI_TEMPORAL_HEAD_ENABLED is off.
|
|
* 2. `temporal_task_push_frame()` — non-blocking enqueue from the
|
|
* adaptive_controller fast loop. Drops on full queue (logs once
|
|
* per second) — the temporal head is best-effort, the physics-only
|
|
* path keeps producing vitals regardless.
|
|
* 3. `temporal_task_stop()` — cleanly tear down (currently used only
|
|
* for tests; production firmware never calls this).
|
|
*
|
|
* Thread safety: per ADR-095 §3.3 the temporal task itself is the
|
|
* single owner of the underlying `ruv_temporal_ctx_t`. Callers
|
|
* communicate exclusively via the FreeRTOS queue.
|
|
*
|
|
* Output: every ~1 s the task runs `ruv_temporal_classify` and emits a
|
|
* `0xC5110007 RV_TEMPORAL_CLASSIFICATION` packet via stream_sender.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
#include "esp_err.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* Magic for the classification packet (ADR-095 §3.5). 0xC5110001..0006
|
|
* are taken; 0007 is the next free slot. */
|
|
#define RV_TEMPORAL_PKT_MAGIC 0xC5110007u
|
|
|
|
/* On-the-wire packet for one classification result. Little-endian.
|
|
* Size: 40 bytes. CRC covers everything before it.
|
|
*
|
|
* Field layout (bytes):
|
|
* [00..04) magic 4
|
|
* [04..06) version 2
|
|
* [06..08) n_classes 2
|
|
* [08..09) node_id 1
|
|
* [09..0C) reserved 3
|
|
* [0C..14) ts_us 8
|
|
* [14..18) seq 4
|
|
* [18..19) argmax 1
|
|
* [19..1C) reserved2 3
|
|
* [1C..20) top_logit 4
|
|
* [20..24) top1_minus_top2 4
|
|
* [24..28) crc32 4
|
|
* total: 40
|
|
*/
|
|
typedef struct __attribute__((packed)) {
|
|
uint32_t magic; /* 0xC5110007 */
|
|
uint16_t version; /* 1 */
|
|
uint16_t n_classes; /* matches init() value */
|
|
uint8_t node_id; /* csi_collector_get_node_id() */
|
|
uint8_t reserved[3];
|
|
uint64_t ts_us; /* esp_timer_get_time() at classify */
|
|
uint32_t seq; /* monotonic, increments per emit */
|
|
uint8_t argmax; /* highest-logit class */
|
|
uint8_t reserved2[3];
|
|
float top_logit; /* logits[argmax] */
|
|
float top1_minus_top2; /* margin — useful for downstream gating */
|
|
uint32_t crc32;
|
|
} rv_temporal_pkt_t;
|
|
|
|
/* Build-time guard so the wire format never silently changes. */
|
|
_Static_assert(sizeof(rv_temporal_pkt_t) == 40,
|
|
"rv_temporal_pkt_t must be 40 bytes (ADR-095 §3.5)");
|
|
|
|
/* Start the temporal task. Returns ESP_ERR_NOT_SUPPORTED when the
|
|
* feature is compiled out — caller should treat that as a non-error
|
|
* and continue. Returns ESP_OK on success.
|
|
*
|
|
* input_dim : feature dimension per frame (e.g. 60 for rv_feature_state_t)
|
|
* window_len : rolling window in frames (e.g. 256)
|
|
* n_classes : number of output logits the model produces (e.g. 4)
|
|
*/
|
|
esp_err_t temporal_task_start(uint32_t input_dim,
|
|
uint32_t window_len,
|
|
uint32_t n_classes);
|
|
|
|
/* Non-blocking push from the adaptive_controller fast loop. Returns
|
|
* ESP_OK on enqueue, ESP_ERR_NOT_FOUND if the task isn't running,
|
|
* ESP_ERR_TIMEOUT if the queue was full. Never blocks the caller. */
|
|
esp_err_t temporal_task_push_frame(const float *frame, uint32_t frame_len);
|
|
|
|
/* Optional teardown — currently unit-test only. */
|
|
void temporal_task_stop(void);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|