Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 55 additions & 22 deletions firmware/esp32-csi-node/main/edge_processing.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ static int64_t s_fall_last_alert_us; /**< Timestamp of last fall alert (deboun

/** Adaptive calibration state. */
static bool s_calibrated;
static float s_floor;
static float s_calib_sum;
static float s_calib_sum_sq;
static uint32_t s_calib_count;
Expand Down Expand Up @@ -533,33 +534,65 @@ static bool presence_flag_update(bool prev, float score, float threshold,
return true; /* Still within the hold window — keep asserting. */
}

/* ======================================================================
* Adaptive Presence Calibration
* ====================================================================== */

/* Continuously track the quiet floor instead of calibrating once at boot.
*
* The old scheme averaged the first EDGE_CALIB_FRAMES frames and latched the
* result for the lifetime of the boot. That cannot be made to work: the node
* is calibrating in the seconds right after a human walked over and plugged it
* in, so the window is contaminated by construction -- and nobody can leave a
* room fast enough to fix it. Whatever the room looked like during that one
* minute became "normal" permanently, and a node commissioned during a busy
* afternoon stayed desensitised for weeks.
*
* A leaky minimum needs no clean room and no cooperation. It drops instantly
* to any new low, so it finds the quiet by itself whenever quiet next happens
* -- minutes after a bad start, not never. It rises only slowly, so a genuine
* change in the room (furniture moved, a fan installed) is eventually adopted
* while a person standing still for a while is not mistaken for the new floor.
*
* Measured support for the shape: on this fleet the room's true floor is about
* 14x below where the animals sit and the settling period after human activity
* runs a couple of hours. A tracker that only ever descends to real quiet, and
* climbs at well under a percent a minute, resolves that; an average over one
* arbitrary minute cannot.
*/
static void calibration_update(float motion)
{
if (s_calibrated) return;

s_calib_sum += motion;
s_calib_sum_sq += motion * motion;
s_calib_count++;
/* Warm-up: seed from the first frames so the floor starts somewhere
* plausible rather than at whatever the very first sample happened to be. */
if (!s_calibrated) {
s_calib_sum += motion;
s_calib_count++;
if (s_calib_count >= EDGE_CALIB_FRAMES) {
s_floor = s_calib_sum / (float)s_calib_count;
s_calibrated = true;
ESP_LOGI(TAG, "floor seeded at %.4f from %lu frames; tracking "
"continuously from here (no clean-room assumption)",
s_floor, (unsigned long)s_calib_count);
}
return;
}

if (s_calib_count >= EDGE_CALIB_FRAMES) {
float mean = s_calib_sum / (float)s_calib_count;
float var = (s_calib_sum_sq / (float)s_calib_count) - (mean * mean);
float sigma = (var > 0.0f) ? sqrtf(var) : 0.001f;
/* Descend immediately to any new low; climb slowly otherwise. */
if (motion < s_floor) {
s_floor = motion;
} else {
s_floor *= EDGE_FLOOR_LEAK;
}
if (s_floor < 1e-5f) {
s_floor = 1e-5f;
}

s_adaptive_threshold = mean + EDGE_CALIB_SIGMA_MULT * sigma;
if (s_adaptive_threshold < 0.01f) {
s_adaptive_threshold = 0.01f;
}
s_adaptive_threshold = s_floor * EDGE_FLOOR_MULT;
if (s_adaptive_threshold < 0.01f) {
s_adaptive_threshold = 0.01f;
}

s_calibrated = true;
ESP_LOGI(TAG, "Adaptive calibration complete: mean=%.4f sigma=%.4f "
"threshold=%.4f (from %lu frames)",
mean, sigma, s_adaptive_threshold,
(unsigned long)s_calib_count);
/* Report occasionally: a floor that never descends means the room is never
* quiet, which is a siting problem rather than a firmware one. */
static uint32_t s_floor_log;
if ((++s_floor_log % 2000) == 0) {
ESP_LOGI(TAG, "floor %.4f threshold %.4f", s_floor, s_adaptive_threshold);
}
}

Expand Down
4 changes: 4 additions & 0 deletions firmware/esp32-csi-node/main/edge_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ static inline uint8_t edge_evidence_person_count(bool presence, uint8_t active_c
#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. */

#define EDGE_FLOOR_LEAK 1.000002f
/* Presence threshold as a multiple of the tracked floor. */
#define EDGE_FLOOR_MULT 4.0f

/* ---- 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. */
Expand Down
Loading