mirror of
https://github.com/ruvnet/RuView
synced 2026-06-17 11:33:19 +00:00
0c2b1c16cc
* fix(firmware): gate phantom persons + add presence hysteresis (#998, #996) Two ESP32 edge-vitals logic bugs in edge_processing.c. Both are robustness/logic fixes — NOT validated-accuracy claims. True count/PCK vs labelled ground truth remains hardware/data-gated (COM9 ESP32-S3). #998 — n_persons over-counted (reported 4 for one person): update_multi_person_vitals() split top-K subcarriers into top_k_count/2 groups and marked EVERY group active, so one body's multipath always read the full EDGE_MAX_PERSONS. Added two pure, host-testable helpers: - count_distinct_persons(): per-group energy gate (EDGE_PERSON_MIN_ENERGY_RATIO) + spatial dedup (EDGE_PERSON_MIN_SC_SEP) so weak/adjacent multipath groups don't count as separate bodies. Strongest group always counts (>=1). - person_count_debounce(): a gated count must hold EDGE_PERSON_PERSIST_FRAMES consecutive frames before it's emitted, so a single noisy frame can't promote a phantom. The active flags now mark only the strongest stable_count groups. #996 — presence flag flickered at ~50cm despite high presence_score: the bare `score > threshold` compare chattered on a noisy score (field-observed 2.6-26.7 frame-to-frame). Replaced with a Schmitt trigger + clear-debounce (presence_flag_update): assert above threshold, hold in the dead band down to threshold * EDGE_PRESENCE_HYST_RATIO, clear only after EDGE_PRESENCE_CLEAR_FRAMES consecutive sub-low frames. presence_score itself is unchanged and still emitted for consumer-side thresholding. All thresholds are named, documented constants in edge_processing.h. Firmware builds clean for esp32s3 (idf.py build RC=0). Co-Authored-By: claude-flow <ruv@ruv.net> * test(firmware): host C99 tests for vitals count + presence logic (#998, #996) test/test_vitals_count_presence.c pins the two fixes with deterministic host-buildable tests (no ESP-IDF needed). 13 cases / 22 assertions, all passing under gcc 13 -Wall -Wextra: #998 count gate: single strong signature + multipath -> count==1; two well-separated -> 2; two strong-but-adjacent -> 1 (dedup); no signal -> 0; three well-separated -> 3. #998 debounce: transient spike rejected; sustained change accepted; flapping count stays stable. #996 presence: dithering trace -> stable flag (no flicker); brief dips held by clear-debounce; genuine departure clears within hold window; dead-band holds state. The named tuning constants are #include'd from the real edge_processing.h so the test and firmware can never disagree on thresholds. `make run_vitals` / `make host_tests` added; binaries gitignored. Hardware-gated caveat documented in the test header: these pin the decision LOGIC; the exact energy/separation/hysteresis values that best match a real room vs labelled occupancy remain on-device tuning. Co-Authored-By: claude-flow <ruv@ruv.net> * docs: record ESP32 vitals count/presence fixes (#998, #996) CHANGELOG [Unreleased] Fixed: root cause + fix + named constants + test + explicit hardware/data-gated caveat for both bugs. ADR-021 Implementation Notes: dated 2026-06 entry noting the edge-path person-count + presence-flicker fixes are boolean/count emission-logic fixes, not a validated-accuracy claim; thresholds pending on-device calibration. Co-Authored-By: claude-flow <ruv@ruv.net> * fix(sensing-server): emit real field-derived person position/motion to /ws/sensing (#1050) The Observatory 3D figure never animated because the sensing_update WS frame carried no per-person position/motion_score/pose — only image-space keypoints. The FigurePool/PoseSystem (and demo-data.js's own contract) animate each figure from persons[i].position (room-world), .motion_score (0..100), and .pose; none were on the live stream. Honest scope (Case 2): the pipeline has no calibrated per-person room localizer or per-person skeletal pose. New field_localize module extracts the strongest peak(s) from the real signal_field grid (subcarrier variances x motion-band power) and maps the peak cell to Observatory world coords with the exact _buildSignalField transform. motion_score is the measured motion_band_power passed through; pose is set only from a real aggregate posture estimate, else None (never a fabricated skeleton). Empty/below-threshold field -> persons: [] (no phantom); present person with no resolvable peak keeps position [0,0,0], not invented coords. attach_field_positions runs after the tracker step at all five broadcast sites. New position/motion_score/pose fields added to both PersonDetection structs. No UI change needed — the Observatory already reads these fields. Tests: field_localize peak/coordinate/empty/separation units + observatory_persons_field_position_tests (known-peak -> emitted position, empty-room -> no phantom, pose real-or-None, below-threshold honesty). sensing-server bin 441->451, 0 failed. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(changelog): record #1050 Observatory persons position/motion fix Co-Authored-By: claude-flow <ruv@ruv.net>
111 lines
4.3 KiB
Makefile
111 lines
4.3 KiB
Makefile
# Makefile for ESP32 CSI firmware fuzz testing targets (ADR-061 Layer 6).
|
|
#
|
|
# Requirements:
|
|
# - clang with libFuzzer support (clang 6.0+)
|
|
# - Linux or macOS (host-based fuzzing, no ESP-IDF needed)
|
|
#
|
|
# Usage:
|
|
# make all # Build all fuzz targets
|
|
# make fuzz_serialize # Build serialize target only
|
|
# make fuzz_edge # Build edge enqueue target only
|
|
# make fuzz_nvs # Build NVS config target only
|
|
# make run_serialize # Build and run serialize fuzzer (30s)
|
|
# make run_edge # Build and run edge fuzzer (30s)
|
|
# make run_nvs # Build and run NVS fuzzer (30s)
|
|
# make run_all # Run all fuzzers (30s each)
|
|
# make clean # Remove build artifacts
|
|
#
|
|
# Environment variables:
|
|
# FUZZ_DURATION=60 # Override fuzz duration in seconds
|
|
# FUZZ_JOBS=4 # Parallel fuzzing jobs
|
|
|
|
CC = clang
|
|
# ADR-110: -DCONFIG_CSI_FRAME_HE_TAGGING=1 enables the byte-18/19 HE path
|
|
# in csi_collector.c so the fuzzer exercises that code as well as the
|
|
# legacy zero-fill path. CONFIG_SOC_WIFI_HE_SUPPORT is left UNSET to
|
|
# exercise the legacy S3 branch (sig_mode/cwb/stbc). Add it to CFLAGS for
|
|
# a parallel HE-stub build if you want fuzz coverage of the C6 branch.
|
|
CFLAGS = -fsanitize=fuzzer,address,undefined -g -O1 \
|
|
-Istubs -I../main \
|
|
-DCONFIG_CSI_NODE_ID=1 \
|
|
-DCONFIG_CSI_WIFI_CHANNEL=6 \
|
|
-DCONFIG_CSI_WIFI_SSID=\"test\" \
|
|
-DCONFIG_CSI_TARGET_IP=\"192.168.1.1\" \
|
|
-DCONFIG_CSI_TARGET_PORT=5500 \
|
|
-DCONFIG_ESP_WIFI_CSI_ENABLED=1 \
|
|
-DCONFIG_CSI_FRAME_HE_TAGGING=1 \
|
|
-Wno-unused-function
|
|
|
|
STUBS_SRC = stubs/esp_stubs.c
|
|
MAIN_DIR = ../main
|
|
|
|
# Default fuzz duration (seconds) and jobs
|
|
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 host_tests
|
|
|
|
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
|
|
# for mac_to_eui64() and PPDU-type → ADR-018 byte 18 mapping.
|
|
# Builds with stock cc/gcc/clang — runs in CI on Ubuntu.
|
|
test_adr110: test_adr110_encoding.c
|
|
cc -std=c99 -Wall -Wextra -o $@ $<
|
|
|
|
run_adr110: test_adr110
|
|
./test_adr110
|
|
|
|
# --- Vitals count + presence logic unit tests (issue #998 / #996) ---
|
|
# Host-side, no libFuzzer. Pins the person-count gate (no over-count for one
|
|
# body) and the presence hysteresis (no flicker on a dithering score). Pulls
|
|
# the named tuning constants from ../main/edge_processing.h so the test and the
|
|
# firmware can never disagree on thresholds.
|
|
test_vitals: test_vitals_count_presence.c $(MAIN_DIR)/edge_processing.h
|
|
cc -std=c99 -Wall -Wextra -Istubs -I$(MAIN_DIR) -o $@ $< -lm
|
|
|
|
run_vitals: test_vitals
|
|
./test_vitals
|
|
|
|
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.
|
|
# Links against the real csi_collector.c (with stubs for ESP-IDF).
|
|
fuzz_serialize: fuzz_csi_serialize.c $(MAIN_DIR)/csi_collector.c $(STUBS_SRC)
|
|
$(CC) $(CFLAGS) $^ -o $@ -lm
|
|
|
|
# --- Edge enqueue fuzzer ---
|
|
# Tests the SPSC ring buffer push/pop logic with rapid-fire enqueues.
|
|
# Self-contained: reproduces ring buffer logic from edge_processing.c.
|
|
fuzz_edge: fuzz_edge_enqueue.c $(STUBS_SRC)
|
|
$(CC) $(CFLAGS) $^ -o $@ -lm
|
|
|
|
# --- NVS config validation fuzzer ---
|
|
# Tests all NVS config validation ranges with random values.
|
|
# Self-contained: reproduces validation logic from nvs_config.c.
|
|
fuzz_nvs: fuzz_nvs_config.c $(STUBS_SRC)
|
|
$(CC) $(CFLAGS) $^ -o $@ -lm
|
|
|
|
# --- Run targets ---
|
|
run_serialize: fuzz_serialize
|
|
@mkdir -p corpus_serialize
|
|
./fuzz_serialize corpus_serialize/ -max_total_time=$(FUZZ_DURATION) -max_len=2048 -jobs=$(FUZZ_JOBS)
|
|
|
|
run_edge: fuzz_edge
|
|
@mkdir -p corpus_edge
|
|
./fuzz_edge corpus_edge/ -max_total_time=$(FUZZ_DURATION) -max_len=4096 -jobs=$(FUZZ_JOBS)
|
|
|
|
run_nvs: fuzz_nvs
|
|
@mkdir -p corpus_nvs
|
|
./fuzz_nvs corpus_nvs/ -max_total_time=$(FUZZ_DURATION) -max_len=256 -jobs=$(FUZZ_JOBS)
|
|
|
|
run_all: run_serialize run_edge run_nvs
|
|
|
|
clean:
|
|
rm -f fuzz_serialize fuzz_edge fuzz_nvs test_adr110 test_vitals
|
|
rm -rf corpus_serialize/ corpus_edge/ corpus_nvs/
|