mirror of
https://github.com/ruvnet/RuView
synced 2026-06-23 12:33:18 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef69a52624 |
@@ -1,98 +0,0 @@
|
||||
# ADR-183: Onboard LED as a 40 Hz Gamma Stimulus, Colour-Mapped from Live CSI via `ruv-neural-viz`
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Accepted — implemented & hardware-confirmed on ESP32-S3 N16R8 (COM8) |
|
||||
| **Date** | 2026-06-17 |
|
||||
| **Deciders** | ruv |
|
||||
| **Codename** | **GAMMA-VIZ** |
|
||||
| **Builds on** | `ruv-neural-viz::ColorMap` (now `no_std` — ruvnet/ruv-neural#3 / RuView#1126), the ESP32 edge `motion_energy` metric (`edge_processing.c`), PR #962 (WS2812 on GPIO 48) |
|
||||
|
||||
## Context
|
||||
|
||||
Two threads converged. (1) `ruv-neural-viz::ColorMap` — the viridis/cool-warm
|
||||
palette the rUv-Neural stack uses to render brain-topology graphs — was `std`-only,
|
||||
so it couldn't run on the ESP32. (2) The onboard WS2812 on the S3 CSI node was dead
|
||||
weight: the firmware only cleared it on boot (and on the wrong pin for N16R8 — GPIO
|
||||
38 vs the actual 48, see #962).
|
||||
|
||||
The ask: make the LED do something real and honest, using the project's own visual
|
||||
capability — not a decorative blink. The natural fit is a **40 Hz gamma stimulus**
|
||||
(the GENUS gamma-entrainment frequency from Alzheimer's light-therapy research)
|
||||
whose **colour is driven by live sensed motion**, so the node's front panel is both
|
||||
a known bio-stimulus waveform and a truthful readout of what the CSI is detecting.
|
||||
|
||||
## Decision
|
||||
|
||||
### Part A — make `ColorMap` `no_std`
|
||||
|
||||
`colormap.rs` is self-contained (no cross-crate deps), so expose it on `no_std`
|
||||
targets. The only blockers were two `std`-only `f64` ops:
|
||||
|
||||
- `f64::round` / `f64::abs` → replaced with `core`+`alloc`-safe helpers `fround`
|
||||
(round via `f64 as i64` truncation — a `core` cast, no `libm`) and `fabs`.
|
||||
- `Vec`/`String`/`format!` → from `alloc`.
|
||||
|
||||
The graph-bound modules (`animation`/`ascii`/`export`/`layout`) and their heavy deps
|
||||
move behind a default `std` feature; `--no-default-features` builds the crate `no_std`
|
||||
and exposes only `colormap`. Output is **byte-identical** (8/8 colormap tests pass with
|
||||
the same RGB values), so this is a pure portability change.
|
||||
|
||||
### Part B — the LED stimulus (firmware)
|
||||
|
||||
`firmware/esp32-csi-node/main/main.c`, on boot:
|
||||
|
||||
- WS2812 on **GPIO 48** (N16R8 / DevKitC-1 v1.1; GPIO 8 on C6).
|
||||
- An `esp_timer` periodic at **12 500 µs toggles a square wave → 40 Hz, 50 % duty**
|
||||
(full-on / full-off — a *perceptible* gamma flicker, not a colour drift).
|
||||
- **ON-phase colour = live CSI motion.** Each ON phase reads `edge_get_vitals().motion_energy`,
|
||||
normalises it (`/ LED_MOTION_FULLSCALE`, clamped `[0,1]`), and indexes a **60-step
|
||||
viridis LUT generated from `ColorMap::viridis().map()`** — still = dark purple,
|
||||
strong motion = yellow.
|
||||
|
||||
The LUT is baked from the real crate (Part A makes the same `ColorMap` embeddable
|
||||
for a future direct FFI path once the ESP Rust toolchain is in CI). The colours are
|
||||
therefore provably `ruv-neural-viz`'s, and the motion is provably real.
|
||||
|
||||
## Honesty (what it is and is not)
|
||||
|
||||
- **40 Hz is a real square-wave stimulus** (12.5 ms on / 12.5 ms off), not a label on
|
||||
a colour sweep. It is *not* tied to any measured 40 Hz brain rhythm — it is an
|
||||
*output* stimulus at the gamma frequency, not a readout of neural gamma.
|
||||
- **Colour is a real CSI readout** — `motion_energy` is the on-device phase-variance
|
||||
motion metric the node already computes; no fabrication. At rest the LED sits at the
|
||||
purple (low) end and flickers there.
|
||||
- No therapeutic claim is made. 40 Hz GENUS entrainment is cited as the *origin of the
|
||||
frequency choice*, not as a validated medical effect of this device.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive**
|
||||
- The LED is now an honest front-panel: gamma-frequency flicker + a live motion readout.
|
||||
- `ColorMap` is embeddable (`no_std`), unblocking on-device use of the rUv-Neural
|
||||
palette beyond this LED.
|
||||
- Confirms #962's GPIO-48 fix visually (the LED lights on N16R8).
|
||||
|
||||
**Negative / risks**
|
||||
- Changes the *default* firmware behaviour: the onboard LED animates instead of staying
|
||||
off. Now **gated by `CONFIG_LED_GAMMA_VIZ`** (default `y`); set it `n` for a dark,
|
||||
lower-power boot (the LED is just cleared) — no source change needed.
|
||||
- A 40 Hz flicker can be an issue for photosensitive users; document on the enclosure
|
||||
and disable `CONFIG_LED_GAMMA_VIZ` in those deployments.
|
||||
- The saturation point is now `CONFIG_LED_MOTION_FULLSCALE_MILLI` (default 250 = 0.25),
|
||||
operator-tunable; still not auto-calibrated per-environment.
|
||||
- The colour uses a baked LUT, not the live Rust `ColorMap` (FFI path deferred — needs
|
||||
the ESP Rust/xtensa toolchain, not yet in CI).
|
||||
|
||||
## Validation
|
||||
|
||||
- `ruv-neural-viz`: `cargo build` (std) ✓, `cargo test colormap` 8/8 ✓ (identical RGB),
|
||||
`cargo build --no-default-features` compiles `no_std` ✓.
|
||||
- Firmware: built (1.13 MB), flashed to ESP32-S3 N16R8 (COM8). Boot log:
|
||||
`Onboard WS2812: 40 Hz gamma flicker (GENUS), colour=CSI motion via ruv-neural-viz, GPIO 48`;
|
||||
CSI continues (27–38 pps), `motion=0.00` at rest → purple flicker as designed.
|
||||
- Full on-device (xtensa) Rust build of `ColorMap` not run — ESP Rust toolchain absent.
|
||||
|
||||
## References
|
||||
- ruvnet/ruv-neural#3 (ColorMap no_std), RuView#1126 (submodule bump), #962 (GPIO 48).
|
||||
- Singer/Tsai GENUS 40 Hz gamma entrainment (origin of the frequency, not a device claim).
|
||||
@@ -468,29 +468,3 @@ menu "Mock CSI (QEMU Testing)"
|
||||
depends on CSI_MOCK_ENABLED
|
||||
default n
|
||||
endmenu
|
||||
|
||||
menu "Onboard LED (ADR-183)"
|
||||
|
||||
config LED_GAMMA_VIZ
|
||||
bool "Onboard WS2812: 40 Hz gamma flicker + CSI-motion colour"
|
||||
default y
|
||||
help
|
||||
Drive the onboard WS2812 as a GENUS-style 40 Hz gamma square wave
|
||||
(12.5 ms on / 12.5 ms off, 50% duty). The ON-phase colour is live
|
||||
CSI motion (edge motion_energy) mapped through the ruv-neural-viz
|
||||
viridis colormap (still=purple, moving=yellow).
|
||||
|
||||
Disable to leave the LED off at boot — lower power, no flicker.
|
||||
NOTE: a 40 Hz flicker can affect photosensitive users; disable or
|
||||
shield the LED in those environments. Not a medical device.
|
||||
|
||||
config LED_MOTION_FULLSCALE_MILLI
|
||||
int "Motion value (x1000) that saturates the colormap to yellow"
|
||||
depends on LED_GAMMA_VIZ
|
||||
default 250
|
||||
range 1 100000
|
||||
help
|
||||
edge motion_energy that maps to the top (yellow) of the viridis
|
||||
colormap, in milli-units (250 = 0.25). Lower = more sensitive
|
||||
(reaches yellow with less motion).
|
||||
endmenu
|
||||
|
||||
@@ -144,54 +144,6 @@ static void wifi_init_sta(void)
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_LED_GAMMA_VIZ
|
||||
/* Viridis colormap (60 steps), generated from ruv-neural-viz::ColorMap::viridis()
|
||||
* — the rUv-Neural brain-topology colormap, now no_std (ruvnet/ruv-neural#3 /
|
||||
* RuView#1126). Used as the ON-phase colour of the 40 Hz gamma flicker below:
|
||||
* dark-purple (still) -> teal -> green -> yellow (strong motion). */
|
||||
static const uint8_t VIRIDIS_LUT[60][3] = {
|
||||
{ 68, 1, 84},{ 67, 6, 88},{ 67, 12, 91},{ 66, 17, 95},{ 66, 23, 99},
|
||||
{ 65, 28,103},{ 64, 34,106},{ 64, 39,110},{ 63, 45,114},{ 63, 50,118},
|
||||
{ 62, 56,121},{ 61, 61,125},{ 61, 67,129},{ 60, 72,132},{ 59, 78,136},
|
||||
{ 59, 83,139},{ 57, 87,139},{ 55, 92,139},{ 53, 96,139},{ 52,100,139},
|
||||
{ 50,104,139},{ 48,109,139},{ 46,113,139},{ 44,117,140},{ 43,122,140},
|
||||
{ 41,126,140},{ 39,130,140},{ 37,134,140},{ 36,139,140},{ 34,143,140},
|
||||
{ 35,147,139},{ 39,151,136},{ 43,154,133},{ 47,158,130},{ 52,162,127},
|
||||
{ 56,166,124},{ 60,170,121},{ 64,173,119},{ 68,177,116},{ 72,181,113},
|
||||
{ 76,185,110},{ 81,189,107},{ 85,192,104},{ 89,196,102},{ 93,200, 99},
|
||||
{102,203, 95},{113,205, 91},{124,207, 87},{134,209, 82},{145,211, 78},
|
||||
{156,213, 74},{167,215, 70},{178,217, 66},{188,219, 62},{199,221, 58},
|
||||
{210,223, 54},{221,225, 49},{231,227, 45},{242,229, 41},{253,231, 37},
|
||||
};
|
||||
static led_strip_handle_t s_viz_led;
|
||||
|
||||
/* motion_energy that saturates the colormap to yellow (CONFIG, milli-units). */
|
||||
#define LED_MOTION_FULLSCALE ((float)CONFIG_LED_MOTION_FULLSCALE_MILLI / 1000.0f)
|
||||
|
||||
/* GENUS-style 40 Hz gamma flicker: full on/off square wave, 50% duty (toggled
|
||||
* every 12.5 ms → 40 Hz). The ON colour is live CSI motion (edge motion_energy)
|
||||
* mapped through the ruv-neural-viz viridis LUT — still=purple, moving=yellow.
|
||||
* So the LED is a real 40 Hz gamma stimulus whose hue tracks sensed motion. */
|
||||
static void led_gamma_40hz_cb(void *arg)
|
||||
{
|
||||
static bool on = false;
|
||||
on = !on;
|
||||
if (on) {
|
||||
edge_vitals_pkt_t v;
|
||||
float m = edge_get_vitals(&v) ? v.motion_energy : 0.0f;
|
||||
float norm = m / LED_MOTION_FULLSCALE;
|
||||
if (norm < 0.0f) norm = 0.0f;
|
||||
if (norm > 1.0f) norm = 1.0f;
|
||||
int idx = (int)(norm * 59.0f + 0.5f);
|
||||
const uint8_t *c = VIRIDIS_LUT[idx];
|
||||
led_strip_set_pixel(s_viz_led, 0, c[0], c[1], c[2]); /* R,G,B (driver maps to GRB) */
|
||||
} else {
|
||||
led_strip_set_pixel(s_viz_led, 0, 0, 0, 0); /* off phase */
|
||||
}
|
||||
led_strip_refresh(s_viz_led);
|
||||
}
|
||||
#endif /* CONFIG_LED_GAMMA_VIZ */
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Initialize NVS */
|
||||
@@ -221,16 +173,15 @@ void app_main(void)
|
||||
ESP_LOGI(TAG, "%s CSI Node (ADR-018 / ADR-110) — v%s — Node ID: %d",
|
||||
target_name, app_desc->version, g_nvs_config.node_id);
|
||||
|
||||
/* Onboard WS2812. C6 wires the LED to GPIO 8; S3 to GPIO 38 (DevKitC-1 v1.0)
|
||||
* or GPIO 48 (DevKitC-1 v1.1 / N16R8 — see #962). On S3 we drive 48 (the
|
||||
* common module). On C6, GPIO 38/48 don't exist (only 0-30) — gate by target.
|
||||
* Behaviour is set by CONFIG_LED_GAMMA_VIZ (ADR-183): on = 40 Hz gamma flicker
|
||||
* coloured by CSI motion; off = clear the LED at boot. */
|
||||
/* Turn off onboard WS2812 LED.
|
||||
* S3 dev boards put the LED on GPIO 38; C6 dev boards on GPIO 8.
|
||||
* On C6, GPIO 38 doesn't exist (only 0-30) — gate the init by target. */
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
const int led_gpio = 8;
|
||||
#else
|
||||
const int led_gpio = 48;
|
||||
const int led_gpio = 38;
|
||||
#endif
|
||||
led_strip_handle_t led_strip;
|
||||
led_strip_config_t strip_config = {
|
||||
.strip_gpio_num = led_gpio,
|
||||
.max_leds = 1,
|
||||
@@ -242,26 +193,9 @@ void app_main(void)
|
||||
.resolution_hz = 10 * 1000 * 1000, // 10MHz
|
||||
.flags.with_dma = false,
|
||||
};
|
||||
#if CONFIG_LED_GAMMA_VIZ
|
||||
if (led_strip_new_rmt_device(&strip_config, &rmt_config, &s_viz_led) == ESP_OK) {
|
||||
const esp_timer_create_args_t viz_args = {
|
||||
.callback = &led_gamma_40hz_cb,
|
||||
.name = "led_gamma_40hz",
|
||||
};
|
||||
esp_timer_handle_t viz_timer;
|
||||
if (esp_timer_create(&viz_args, &viz_timer) == ESP_OK) {
|
||||
esp_timer_start_periodic(viz_timer, 12500); // 12.5 ms toggle → 40 Hz square wave
|
||||
ESP_LOGI(TAG, "Onboard WS2812: 40 Hz gamma flicker (GENUS), colour=CSI motion via ruv-neural-viz, GPIO %d", led_gpio);
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* Viz disabled — clear the onboard LED at boot and release the RMT channel. */
|
||||
led_strip_handle_t led_strip;
|
||||
if (led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip) == ESP_OK) {
|
||||
led_strip_clear(led_strip);
|
||||
led_strip_del(led_strip);
|
||||
}
|
||||
#endif /* CONFIG_LED_GAMMA_VIZ */
|
||||
|
||||
/* ADR-110 P4: 802.15.4 mesh time-sync (C6 only).
|
||||
* Initialized BEFORE WiFi so it's available even when WiFi STA can't
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @file mmwave_detect.h
|
||||
* @brief Pure (host-testable) mmWave frame-validation predicates for probe-time
|
||||
* sensor detection. No ESP-IDF deps — safe to #include in a host unit test.
|
||||
*
|
||||
* Detection must validate a *full* frame, never a bare header byte/pattern: a
|
||||
* floating UART with no sensor reads line noise that can contain header-looking
|
||||
* bytes, which the old loose checks mistook for a real sensor (#1107 MR60,
|
||||
* #1135 LD2410). These predicates are the validate-before-trust gate.
|
||||
*/
|
||||
#ifndef MMWAVE_DETECT_H
|
||||
#define MMWAVE_DETECT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* True iff buf[i..] begins a *validated* LD2410 report frame within [0,len):
|
||||
* F4 F3 F2 F1 | len(LE,2) | data[len] | F8 F7 F6 F5
|
||||
* Requires the head magic, a sane intra-frame length, AND the matching tail at
|
||||
* head+6+len. Pure noise that merely contains 0xF4F3F2F1 fails the tail check.
|
||||
*/
|
||||
static inline bool mmwave_ld2410_valid_at(const uint8_t *buf, int i, int len)
|
||||
{
|
||||
if (i < 0 || i + 5 >= len) return false;
|
||||
if (!(buf[i] == 0xF4 && buf[i+1] == 0xF3 && buf[i+2] == 0xF2 && buf[i+3] == 0xF1))
|
||||
return false;
|
||||
uint16_t flen = (uint16_t)buf[i+4] | ((uint16_t)buf[i+5] << 8);
|
||||
/* Real LD2410 report frames are small (basic=13, engineering=35). */
|
||||
if (flen < 1 || flen > 64) return false;
|
||||
int tail = i + 6 + (int)flen;
|
||||
if (tail + 3 >= len) return false;
|
||||
return buf[tail] == 0xF8 && buf[tail+1] == 0xF7
|
||||
&& buf[tail+2] == 0xF6 && buf[tail+3] == 0xF5;
|
||||
}
|
||||
|
||||
#endif /* MMWAVE_DETECT_H */
|
||||
@@ -26,7 +26,6 @@
|
||||
*/
|
||||
|
||||
#include "mmwave_sensor.h"
|
||||
#include "mmwave_detect.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
@@ -402,12 +401,10 @@ static mmwave_type_t probe_at_baud(uint32_t baud)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* LD2410: require a *full validated* report frame, not just the
|
||||
* 4-byte head. A floating UART1 at 256000 baud can emit the head
|
||||
* pattern 0xF4F3F2F1 from line noise (#1135 bug #2). The shared
|
||||
* predicate (host-unit-tested in mmwave_detect.h) demands a sane
|
||||
* intra-frame length AND the matching tail 0xF8F7F6F5. */
|
||||
if (baud == MMWAVE_LD2410_BAUD && mmwave_ld2410_valid_at(buf, i, len)) {
|
||||
/* LD2410: 4-byte header 0xF4F3F2F1 (already specific enough). */
|
||||
if (i + 3 < len && buf[i] == 0xF4 && buf[i+1] == 0xF3
|
||||
&& buf[i+2] == 0xF2 && buf[i+3] == 0xF1
|
||||
&& baud == MMWAVE_LD2410_BAUD) {
|
||||
ld2410_header_seen++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,9 @@ static struct sockaddr_in s_dest_addr;
|
||||
* rapid-fire CSI callbacks can exhaust the pbuf pool and crash the device.
|
||||
*/
|
||||
static int64_t s_backoff_until_us = 0; /* esp_timer timestamp to resume */
|
||||
#define ENOMEM_COOLDOWN_MS 100 /* base backoff; doubles per streak */
|
||||
#define ENOMEM_COOLDOWN_MAX_MS 2000 /* cap on the exponential backoff */
|
||||
#define ENOMEM_COOLDOWN_MS 100 /* suppress sends for 100 ms */
|
||||
#define ENOMEM_LOG_INTERVAL 50 /* log every Nth suppressed send */
|
||||
static uint32_t s_enomem_suppressed = 0;
|
||||
/* Consecutive ENOMEM episodes without an intervening successful send. A fixed
|
||||
* 100 ms backoff is too short to drain sustained lwIP/WiFi buffer pressure
|
||||
* (#1135 bug #1: tier-2 + concurrent TX keeps the node stuck), so the backoff
|
||||
* grows 100→200→400→…→2000 ms per streak and resets on the first send that
|
||||
* succeeds. */
|
||||
static uint32_t s_enomem_streak = 0;
|
||||
|
||||
static int sender_init_internal(const char *ip, uint16_t port)
|
||||
{
|
||||
@@ -100,24 +93,16 @@ int stream_sender_send(const uint8_t *data, size_t len)
|
||||
(struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));
|
||||
if (sent < 0) {
|
||||
if (errno == ENOMEM) {
|
||||
/* Exponential backoff: double the cooldown each consecutive ENOMEM
|
||||
* (capped) so sustained buffer pressure actually drains instead of
|
||||
* the node re-failing every 100 ms forever (#1135 bug #1). */
|
||||
uint32_t shift = s_enomem_streak < 5 ? s_enomem_streak : 5;
|
||||
uint32_t cooldown = ENOMEM_COOLDOWN_MS << shift;
|
||||
if (cooldown > ENOMEM_COOLDOWN_MAX_MS) cooldown = ENOMEM_COOLDOWN_MAX_MS;
|
||||
s_enomem_streak++;
|
||||
s_backoff_until_us = esp_timer_get_time() + (int64_t)cooldown * 1000;
|
||||
ESP_LOGW(TAG, "sendto ENOMEM — backing off for %lu ms (streak %lu)",
|
||||
(unsigned long)cooldown, (unsigned long)s_enomem_streak);
|
||||
/* Start backoff to let lwIP reclaim buffers */
|
||||
s_backoff_until_us = esp_timer_get_time() +
|
||||
(int64_t)ENOMEM_COOLDOWN_MS * 1000;
|
||||
ESP_LOGW(TAG, "sendto ENOMEM — backing off for %d ms", ENOMEM_COOLDOWN_MS);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "sendto failed: errno %d", errno);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A send got through — buffer pressure cleared; reset the backoff streak. */
|
||||
s_enomem_streak = 0;
|
||||
return sent;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ FUZZ_DURATION ?= 30
|
||||
FUZZ_JOBS ?= 1
|
||||
|
||||
.PHONY: all clean run_serialize run_edge run_nvs run_all test_adr110 run_adr110 \
|
||||
test_vitals run_vitals test_mmwave_detect run_mmwave_detect host_tests
|
||||
test_vitals run_vitals host_tests
|
||||
|
||||
all: fuzz_serialize fuzz_edge fuzz_nvs test_adr110 test_vitals test_mmwave_detect
|
||||
all: fuzz_serialize fuzz_edge fuzz_nvs test_adr110 test_vitals
|
||||
|
||||
# --- ADR-110 encoding unit tests ---
|
||||
# Host-side, no libFuzzer needed — plain C99 deterministic table tests
|
||||
@@ -69,19 +69,8 @@ test_vitals: test_vitals_count_presence.c $(MAIN_DIR)/edge_processing.h
|
||||
run_vitals: test_vitals
|
||||
./test_vitals
|
||||
|
||||
# --- mmWave LD2410 detection predicate (#1135 bug #2) ---
|
||||
# Host-side, no libFuzzer. Proves a floating-UART head pattern (0xF4F3F2F1)
|
||||
# without a valid frame length+tail is REJECTED, so a phantom LD2410 is never
|
||||
# detected on a node with no sensor wired. Tests the real predicate the
|
||||
# firmware uses (../main/mmwave_detect.h) — test and firmware can't disagree.
|
||||
test_mmwave_detect: test_mmwave_detect.c $(MAIN_DIR)/mmwave_detect.h
|
||||
cc -std=c99 -Wall -Wextra -I$(MAIN_DIR) -o $@ $<
|
||||
|
||||
run_mmwave_detect: test_mmwave_detect
|
||||
./test_mmwave_detect
|
||||
|
||||
host_tests: run_adr110 run_vitals run_mmwave_detect
|
||||
@echo "Host tests passed (ADR-110 + vitals #998/#996 + mmwave detect #1135)"
|
||||
host_tests: run_adr110 run_vitals
|
||||
@echo "Host tests passed (ADR-110 + vitals #998/#996)"
|
||||
|
||||
# --- Serialize fuzzer ---
|
||||
# Tests csi_serialize_frame() with random wifi_csi_info_t inputs.
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* @file test_mmwave_detect.c
|
||||
* @brief Host-side unit tests for the LD2410 frame-validation predicate (#1135).
|
||||
*
|
||||
* Proves the phantom-detection fix: a floating UART can emit the 4-byte head
|
||||
* 0xF4F3F2F1, but the predicate rejects it unless a sane length + matching tail
|
||||
* 0xF8F7F6F5 are also present. Tests the REAL predicate from mmwave_detect.h
|
||||
* (the same code the firmware's probe_at_baud calls).
|
||||
*
|
||||
* cc -std=c99 -Wall -I../main -o test_mmwave_detect test_mmwave_detect.c && ./test_mmwave_detect
|
||||
*
|
||||
* Exits 0 on all-pass; prints the failing case otherwise.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mmwave_detect.h"
|
||||
|
||||
static int failures = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (!(cond)) { printf("FAIL: %s\n", msg); failures++; } \
|
||||
else { printf("ok: %s\n", msg); } \
|
||||
} while (0)
|
||||
|
||||
/* Build a valid LD2410 report frame: F4F3F2F1 | len(LE) | data[len] | F8F7F6F5 */
|
||||
static int make_frame(uint8_t *out, uint16_t dlen)
|
||||
{
|
||||
int n = 0;
|
||||
out[n++] = 0xF4; out[n++] = 0xF3; out[n++] = 0xF2; out[n++] = 0xF1;
|
||||
out[n++] = (uint8_t)(dlen & 0xFF); out[n++] = (uint8_t)(dlen >> 8);
|
||||
for (uint16_t k = 0; k < dlen; k++) out[n++] = (uint8_t)(0xAA ^ k);
|
||||
out[n++] = 0xF8; out[n++] = 0xF7; out[n++] = 0xF6; out[n++] = 0xF5;
|
||||
return n;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint8_t buf[256];
|
||||
|
||||
/* 1. A real basic-report frame (data len 13) validates. */
|
||||
int n = make_frame(buf, 13);
|
||||
CHECK(mmwave_ld2410_valid_at(buf, 0, n), "valid basic frame (len=13) accepted");
|
||||
|
||||
/* 2. A real engineering-report frame (data len 35) validates. */
|
||||
n = make_frame(buf, 35);
|
||||
CHECK(mmwave_ld2410_valid_at(buf, 0, n), "valid engineering frame (len=35) accepted");
|
||||
|
||||
/* 3. Head magic present but NO valid tail — the #1135 phantom case. */
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
buf[0]=0xF4; buf[1]=0xF3; buf[2]=0xF2; buf[3]=0xF1; buf[4]=13; buf[5]=0;
|
||||
/* data present but tail is zeros, not F8F7F6F5 */
|
||||
CHECK(!mmwave_ld2410_valid_at(buf, 0, 64), "head magic without valid tail REJECTED (#1135)");
|
||||
|
||||
/* 4. Head magic with insane length is rejected. */
|
||||
memset(buf, 0xFF, sizeof(buf));
|
||||
buf[0]=0xF4; buf[1]=0xF3; buf[2]=0xF2; buf[3]=0xF1; buf[4]=0xFF; buf[5]=0xFF; /* len=65535 */
|
||||
CHECK(!mmwave_ld2410_valid_at(buf, 0, 200), "head magic with oversized length REJECTED");
|
||||
|
||||
/* 5. Pure noise (no head) is rejected. */
|
||||
for (int k = 0; k < 64; k++) buf[k] = (uint8_t)(0x5A + k);
|
||||
CHECK(!mmwave_ld2410_valid_at(buf, 0, 64), "non-header noise REJECTED");
|
||||
|
||||
/* 6. Truncated frame (tail would run past the buffer) is rejected. */
|
||||
n = make_frame(buf, 13);
|
||||
CHECK(!mmwave_ld2410_valid_at(buf, 0, n - 2), "truncated frame (tail past buffer) REJECTED");
|
||||
|
||||
/* 7. Valid frame at a non-zero offset still validates. */
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
n = make_frame(buf + 7, 13);
|
||||
CHECK(mmwave_ld2410_valid_at(buf, 7, 7 + n), "valid frame at offset 7 accepted");
|
||||
|
||||
/* 8. Repeated head bytes without a frame (worst-case noise) rejected. */
|
||||
for (int k = 0; k + 3 < 64; k += 4) {
|
||||
buf[k]=0xF4; buf[k+1]=0xF3; buf[k+2]=0xF2; buf[k+3]=0xF1;
|
||||
}
|
||||
CHECK(!mmwave_ld2410_valid_at(buf, 0, 64), "repeated bare head bytes REJECTED");
|
||||
|
||||
printf("\n%s (%d failures)\n", failures ? "FAILED" : "ALL PASS", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
+1
-1
Submodule v2/crates/ruv-neural updated: c9638faaf8...81be9e1e19
Reference in New Issue
Block a user