From 8afd76da20039845449194c6eaee5be3a9f04fcc Mon Sep 17 00:00:00 2001 From: ruv Date: Sun, 15 Mar 2026 09:44:23 -0400 Subject: [PATCH] fix(firmware): fall detection false positives + 4MB flash support (#263, #265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #263: Default fall_thresh raised from 2.0 to 15.0 rad/s² — normal walking produces accelerations of 2.5-5.0 which triggered constant false "Fall Detected" alerts. Added consecutive-frame requirement (3 frames) and 5-second cooldown debounce to prevent alert storms. Issue #265: Added partitions_4mb.csv and sdkconfig.defaults.4mb for ESP32-S3 boards with 4MB flash (e.g. SuperMini). OTA slots are 1.856MB each, fitting the ~978KB firmware binary with room to spare. Co-Authored-By: claude-flow --- .../esp32-csi-node/main/edge_processing.c | 32 ++++++++++++++++--- .../esp32-csi-node/main/edge_processing.h | 4 +++ firmware/esp32-csi-node/main/nvs_config.c | 2 +- firmware/esp32-csi-node/partitions_4mb.csv | 15 +++++++++ firmware/esp32-csi-node/provision.py | 4 ++- .../esp32-csi-node/sdkconfig.defaults.4mb | 29 +++++++++++++++++ 6 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 firmware/esp32-csi-node/partitions_4mb.csv create mode 100644 firmware/esp32-csi-node/sdkconfig.defaults.4mb diff --git a/firmware/esp32-csi-node/main/edge_processing.c b/firmware/esp32-csi-node/main/edge_processing.c index a14c4bd3..6c4e2d39 100644 --- a/firmware/esp32-csi-node/main/edge_processing.c +++ b/firmware/esp32-csi-node/main/edge_processing.c @@ -244,6 +244,10 @@ static uint32_t s_frame_count; /** Previous phase velocity for fall detection (acceleration). */ static float s_prev_phase_velocity; +/** Fall detection debounce state (issue #263). */ +static uint8_t s_fall_consec_count; /**< Consecutive frames above threshold. */ +static int64_t s_fall_last_alert_us; /**< Timestamp of last fall alert (debounce). */ + /** Adaptive calibration state. */ static bool s_calibrated; static float s_calib_sum; @@ -689,7 +693,7 @@ static void process_frame(const edge_ring_slot_t *slot) } s_presence_detected = (s_presence_score > threshold); - /* --- Step 10: Fall detection (phase acceleration) --- */ + /* --- Step 10: Fall detection (phase acceleration + debounce, issue #263) --- */ if (s_history_len >= 3) { uint16_t i0 = (s_history_idx + EDGE_PHASE_HISTORY_LEN - 1) % EDGE_PHASE_HISTORY_LEN; uint16_t i1 = (s_history_idx + EDGE_PHASE_HISTORY_LEN - 2) % EDGE_PHASE_HISTORY_LEN; @@ -697,10 +701,26 @@ static void process_frame(const edge_ring_slot_t *slot) float accel = fabsf(velocity - s_prev_phase_velocity); s_prev_phase_velocity = velocity; - s_fall_detected = (accel > s_cfg.fall_thresh); - if (s_fall_detected) { - ESP_LOGW(TAG, "Fall detected! accel=%.4f > thresh=%.4f", - accel, s_cfg.fall_thresh); + if (accel > s_cfg.fall_thresh) { + s_fall_consec_count++; + } else { + s_fall_consec_count = 0; + } + + /* Require EDGE_FALL_CONSEC_MIN consecutive frames above threshold, + * plus a cooldown period to prevent alert storms. */ + int64_t now_us = esp_timer_get_time(); + int64_t cooldown_us = (int64_t)EDGE_FALL_COOLDOWN_MS * 1000; + if (s_fall_consec_count >= EDGE_FALL_CONSEC_MIN + && (now_us - s_fall_last_alert_us) >= cooldown_us) + { + s_fall_detected = true; + s_fall_last_alert_us = now_us; + s_fall_consec_count = 0; + ESP_LOGW(TAG, "Fall detected! accel=%.4f > thresh=%.4f (consec=%u)", + accel, s_cfg.fall_thresh, EDGE_FALL_CONSEC_MIN); + } else if (s_fall_consec_count == 0) { + s_fall_detected = false; } } @@ -850,6 +870,8 @@ esp_err_t edge_processing_init(const edge_config_t *cfg) s_latest_rssi = 0; s_frame_count = 0; s_prev_phase_velocity = 0.0f; + s_fall_consec_count = 0; + s_fall_last_alert_us = 0; s_last_vitals_send_us = 0; s_has_prev_iq = false; s_prev_iq_len = 0; diff --git a/firmware/esp32-csi-node/main/edge_processing.h b/firmware/esp32-csi-node/main/edge_processing.h index 00f1e153..f3288a50 100644 --- a/firmware/esp32-csi-node/main/edge_processing.h +++ b/firmware/esp32-csi-node/main/edge_processing.h @@ -42,6 +42,10 @@ #define EDGE_CALIB_FRAMES 1200 /**< Frames for adaptive calibration (~60s at 20 Hz). */ #define EDGE_CALIB_SIGMA_MULT 3.0f /**< Threshold = mean + 3*sigma of ambient. */ +/* ---- Fall detection ---- */ +#define EDGE_FALL_COOLDOWN_MS 5000 /**< Minimum ms between fall alerts (debounce). */ +#define EDGE_FALL_CONSEC_MIN 3 /**< Consecutive frames above threshold to trigger. */ + /* ---- SPSC ring buffer slot ---- */ typedef struct { uint8_t iq_data[EDGE_MAX_IQ_BYTES]; /**< Raw I/Q bytes from CSI callback. */ diff --git a/firmware/esp32-csi-node/main/nvs_config.c b/firmware/esp32-csi-node/main/nvs_config.c index 6494d0e7..3c85e4a5 100644 --- a/firmware/esp32-csi-node/main/nvs_config.c +++ b/firmware/esp32-csi-node/main/nvs_config.c @@ -61,7 +61,7 @@ void nvs_config_load(nvs_config_t *cfg) #ifdef CONFIG_EDGE_FALL_THRESH cfg->fall_thresh = (float)CONFIG_EDGE_FALL_THRESH / 1000.0f; #else - cfg->fall_thresh = 2.0f; + cfg->fall_thresh = 15.0f; /* Default raised from 2.0 — see issue #263. */ #endif cfg->vital_window = 256; #ifdef CONFIG_EDGE_VITAL_INTERVAL_MS diff --git a/firmware/esp32-csi-node/partitions_4mb.csv b/firmware/esp32-csi-node/partitions_4mb.csv new file mode 100644 index 00000000..7b69619f --- /dev/null +++ b/firmware/esp32-csi-node/partitions_4mb.csv @@ -0,0 +1,15 @@ +# ESP32-S3 CSI Node — 4MB flash partition table (issue #265) +# For boards with 4MB flash (e.g. ESP32-S3 SuperMini 4MB). +# Binary is ~978KB so each OTA slot is 1.875MB — plenty of room. +# +# Usage: copy to partitions_display.csv OR set in sdkconfig: +# CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_4mb.csv" +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE="4MB" +# +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +otadata, data, ota, 0xF000, 0x2000, +phy_init, data, phy, 0x11000, 0x1000, +ota_0, app, ota_0, 0x20000, 0x1D0000, +ota_1, app, ota_1, 0x1F0000, 0x1D0000, diff --git a/firmware/esp32-csi-node/provision.py b/firmware/esp32-csi-node/provision.py index 83f93068..e486ef07 100644 --- a/firmware/esp32-csi-node/provision.py +++ b/firmware/esp32-csi-node/provision.py @@ -168,7 +168,9 @@ def main(): parser.add_argument("--edge-tier", type=int, choices=[0, 1, 2], help="Edge processing tier: 0=off, 1=stats, 2=vitals") parser.add_argument("--pres-thresh", type=int, help="Presence detection threshold (default: 50)") - parser.add_argument("--fall-thresh", type=int, help="Fall detection threshold (default: 500)") + parser.add_argument("--fall-thresh", type=int, help="Fall detection threshold in milli-units " + "(value/1000 = rad/s²). Default: 15000 → 15.0 rad/s². " + "Raise to reduce false positives in high-traffic areas.") parser.add_argument("--vital-win", type=int, help="Phase history window in frames (default: 300)") parser.add_argument("--vital-int", type=int, help="Vitals packet interval in ms (default: 1000)") parser.add_argument("--subk-count", type=int, help="Top-K subcarrier count (default: 32)") diff --git a/firmware/esp32-csi-node/sdkconfig.defaults.4mb b/firmware/esp32-csi-node/sdkconfig.defaults.4mb new file mode 100644 index 00000000..3a0d1d60 --- /dev/null +++ b/firmware/esp32-csi-node/sdkconfig.defaults.4mb @@ -0,0 +1,29 @@ +# ESP32-S3 CSI Node — 4MB Flash SDK Configuration (issue #265) +# For boards with 4MB flash (e.g. ESP32-S3 SuperMini 4MB). +# +# Build: cp sdkconfig.defaults.4mb sdkconfig.defaults && idf.py set-target esp32s3 && idf.py build +# Or: idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults.4mb" set-target esp32s3 && idf.py build + +CONFIG_IDF_TARGET="esp32s3" + +# 4MB flash partition table +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_4mb.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" + +# Compiler: optimize for size (critical for 4MB) +CONFIG_COMPILER_OPTIMIZATION_SIZE=y + +# CSI support +CONFIG_ESP_WIFI_CSI_ENABLED=y + +# Disable display support to save flash (ADR-045 display requires 8MB) +# CONFIG_DISPLAY_ENABLE is not set + +# Reduce logging to save flash +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL_INFO=y + +CONFIG_LWIP_SO_RCVBUF=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192