Enhance buzzer detection logic to group intermittent matches with a 500 ms quiet tail, updating relevant configurations and documentation.
This commit is contained in:
@@ -65,8 +65,8 @@ menu "Door Buzzer Detector"
|
||||
default 10
|
||||
range 1 32
|
||||
config BUZZ_COOLDOWN_MS
|
||||
int "Cooldown after an alert (milliseconds)"
|
||||
default 5
|
||||
int "Quiet tail to group buzz frames into one event (milliseconds)"
|
||||
default 500
|
||||
range 0 86400000
|
||||
config BUZZ_NOISE_MULT_X10
|
||||
int "Learn/adapt gate: noise floor multiplier x10"
|
||||
|
||||
+13
-3
@@ -13,7 +13,8 @@
|
||||
static const char *TAG = "config";
|
||||
#define NVS_NS "buzz"
|
||||
#define CFG_KEY "cfg"
|
||||
#define CFG_MAGIC 0xB0220002u
|
||||
#define CFG_MAGIC 0xB0220003u
|
||||
#define CFG_MAGIC_PRE_500MS 0xB0220002u
|
||||
#define CFG_MAGIC_PRE_COOLDOWN_MS 0xB0220001u
|
||||
|
||||
typedef struct { uint32_t magic; app_config_t cfg; } cfg_blob_t;
|
||||
@@ -33,7 +34,7 @@ static const param_t s_params[] = {
|
||||
P(min_rms, PT_FLOAT, 0.0, 32767.0, "loudness gate (int16 RMS)"),
|
||||
P(win_n, PT_UINT, 1, 32, "sliding window length N (frames)"),
|
||||
P(win_m, PT_UINT, 1, 32, "matches M needed in window (<= N)"),
|
||||
P(cooldown_ms, PT_UINT, 0, 86400000, "cooldown after alert (ms)"),
|
||||
P(cooldown_ms, PT_UINT, 0, 86400000, "quiet tail used to group buzzer frames (ms)"),
|
||||
P(noise_mult, PT_FLOAT, 1.0, 100.0, "learn/adapt gate = noise floor x this"),
|
||||
P(adapt_weight, PT_FLOAT, 0.0, 1.0, "weight of new template in adapt"),
|
||||
P(monitor_ms, PT_UINT, 10, 60000, "min interval between monitor lines (ms)"),
|
||||
@@ -102,7 +103,16 @@ esp_err_t config_init(void)
|
||||
} else if (blob.magic == CFG_MAGIC_PRE_COOLDOWN_MS) {
|
||||
s_cfg = blob.cfg;
|
||||
s_cfg.cooldown_ms = CONFIG_BUZZ_COOLDOWN_MS;
|
||||
ESP_LOGI(TAG, "migrating saved config to millisecond cooldown");
|
||||
ESP_LOGI(TAG, "migrating saved config to event-grouping cooldown");
|
||||
err = save_locked();
|
||||
if (err != ESP_OK) {
|
||||
nvs_close(h);
|
||||
return err;
|
||||
}
|
||||
} else if (blob.magic == CFG_MAGIC_PRE_500MS) {
|
||||
s_cfg = blob.cfg;
|
||||
s_cfg.cooldown_ms = CONFIG_BUZZ_COOLDOWN_MS;
|
||||
ESP_LOGI(TAG, "updating saved cooldown for buzz-event grouping");
|
||||
err = save_locked();
|
||||
if (err != ESP_OK) {
|
||||
nvs_close(h);
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ typedef struct {
|
||||
float min_rms; // loudness gate (int16 RMS); quieter frames never match
|
||||
uint32_t win_n; // sliding window length (frames), 1..32
|
||||
uint32_t win_m; // matches needed inside the window, 1..win_n
|
||||
uint32_t cooldown_ms; // quiet period after an alert
|
||||
uint32_t cooldown_ms; // quiet tail that groups matches into one buzz event
|
||||
float noise_mult; // learn/adapt gate = noise floor * this
|
||||
float adapt_weight; // weight of the NEW template in `adapt` (0..1)
|
||||
uint32_t monitor_ms; // min interval between `monitor` lines
|
||||
|
||||
+2
-1
@@ -128,10 +128,11 @@ static int cmd_status(int argc, char **argv)
|
||||
printf("template: %s, %lu frames%s\n", matcher_source_name(), (unsigned long)matcher_frame_count(),
|
||||
matcher_has_template() ? "" : " <-- run tools/make_template.py + rebuild, or `learn`");
|
||||
if (matcher_suggested_threshold(&sug)) printf(" template.h suggests threshold %.3f\n", sug);
|
||||
printf("detector: threshold %.3f, min_rms %.0f, window %lu/%lu, cooldown %lu ms\n", cfg.threshold, cfg.min_rms,
|
||||
printf("detector: threshold %.3f, min_rms %.0f, window %lu/%lu, buzz quiet tail %lu ms\n", cfg.threshold, cfg.min_rms,
|
||||
(unsigned long)cfg.win_m, (unsigned long)cfg.win_n, (unsigned long)cfg.cooldown_ms);
|
||||
printf("last frame: rms %.1f, score %+.3f, match %d\n", ps.last_rms, ps.last_score, ps.last_match);
|
||||
printf("triggers: %lu (last at uptime %lu s)\n", (unsigned long)ps.triggers, (unsigned long)ps.last_trigger_s);
|
||||
printf("last buzz: %lu ms (final quiet tail excluded)\n", (unsigned long)ps.last_buzz_ms);
|
||||
printf("frames: processed %lu, captured %lu, dropped %lu\n", (unsigned long)ps.frames,
|
||||
(unsigned long)audio_in_frames_captured(), (unsigned long)audio_in_frames_dropped());
|
||||
printf("proc time: avg %lu us, max %lu us (budget %d us per frame)\n", (unsigned long)ps.proc_avg_us,
|
||||
|
||||
+49
-17
@@ -1,39 +1,71 @@
|
||||
#include <string.h>
|
||||
#include "detector.h"
|
||||
#include "feature_params.h"
|
||||
|
||||
void detector_init(detector_t *d) { memset(d, 0, sizeof(*d)); d->state = DET_IDLE; }
|
||||
void detector_reset(detector_t *d) { d->state = DET_IDLE; d->hist = 0; }
|
||||
void detector_reset(detector_t *d)
|
||||
{
|
||||
d->state = DET_IDLE;
|
||||
d->hist = 0;
|
||||
d->frame_index = 0;
|
||||
}
|
||||
|
||||
bool detector_is_match(float rms, float score, float min_rms, float threshold)
|
||||
{
|
||||
return rms >= min_rms && score >= threshold;
|
||||
}
|
||||
|
||||
bool detector_update(detector_t *d, bool match, uint32_t now_ms, const detector_cfg_t *cfg)
|
||||
static void update_idle(detector_t *d, bool match, uint32_t now_ms,
|
||||
uint32_t n, uint32_t m)
|
||||
{
|
||||
uint32_t slot = d->frame_index % 32;
|
||||
d->match_time_ms[slot] = now_ms;
|
||||
d->hist = (d->hist << 1) | (match ? 1u : 0u);
|
||||
|
||||
uint32_t mask = (n == 32) ? 0xFFFFFFFFu : ((1u << n) - 1u);
|
||||
if ((uint32_t)__builtin_popcount(d->hist & mask) >= m) {
|
||||
uint32_t matches_seen = 0;
|
||||
for (uint32_t offset = 0; offset < n; offset++) {
|
||||
if ((d->hist & (1u << offset)) && ++matches_seen == m) {
|
||||
uint32_t first_slot = (d->frame_index - offset) % 32;
|
||||
d->buzz_start_ms = d->match_time_ms[first_slot];
|
||||
break;
|
||||
}
|
||||
}
|
||||
d->last_match_ms = now_ms;
|
||||
d->state = DET_TRIGGERED;
|
||||
d->triggers++;
|
||||
}
|
||||
d->frame_index++;
|
||||
}
|
||||
|
||||
bool detector_update(detector_t *d, bool match, uint32_t now_ms, const detector_cfg_t *cfg,
|
||||
uint32_t *buzz_duration_ms)
|
||||
{
|
||||
uint32_t n = cfg->win_n < 1 ? 1 : (cfg->win_n > 32 ? 32 : cfg->win_n);
|
||||
uint32_t m = cfg->win_m < 1 ? 1 : (cfg->win_m > n ? n : cfg->win_m);
|
||||
|
||||
switch (d->state) {
|
||||
case DET_IDLE: {
|
||||
d->hist = (d->hist << 1) | (match ? 1u : 0u);
|
||||
uint32_t mask = (n == 32) ? 0xFFFFFFFFu : ((1u << n) - 1u);
|
||||
if ((uint32_t)__builtin_popcount(d->hist & mask) >= m) {
|
||||
d->state = DET_TRIGGERED;
|
||||
d->triggers++;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DET_IDLE:
|
||||
update_idle(d, match, now_ms, n, m);
|
||||
return false;
|
||||
case DET_TRIGGERED:
|
||||
d->state = DET_COOLDOWN;
|
||||
d->cooldown_start_ms = now_ms;
|
||||
d->hist = 0;
|
||||
break;
|
||||
case DET_COOLDOWN:
|
||||
if ((uint32_t)(now_ms - d->cooldown_start_ms) >= cfg->cooldown_ms) {
|
||||
if (match && (uint32_t)(now_ms - d->last_match_ms) < cfg->cooldown_ms) {
|
||||
d->last_match_ms = now_ms;
|
||||
d->state = DET_COOLDOWN;
|
||||
} else {
|
||||
d->state = DET_COOLDOWN;
|
||||
if ((uint32_t)(now_ms - d->last_match_ms) < cfg->cooldown_ms) break;
|
||||
d->last_completed_start_ms = d->buzz_start_ms;
|
||||
if (buzz_duration_ms) {
|
||||
*buzz_duration_ms = (uint32_t)(d->last_match_ms - d->buzz_start_ms) +
|
||||
(1000u * HOP_LEN / FS_HZ);
|
||||
}
|
||||
d->state = DET_IDLE;
|
||||
d->hist = 0;
|
||||
if (match) update_idle(d, true, now_ms, n, m);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+10
-5
@@ -14,7 +14,11 @@ typedef struct {
|
||||
typedef struct {
|
||||
det_state_t state;
|
||||
uint32_t hist; // bit 0 = newest frame, 1 = matched
|
||||
uint32_t cooldown_start_ms;
|
||||
uint32_t frame_index;
|
||||
uint32_t match_time_ms[32];
|
||||
uint32_t buzz_start_ms;
|
||||
uint32_t last_match_ms;
|
||||
uint32_t last_completed_start_ms;
|
||||
uint32_t triggers;
|
||||
} detector_t;
|
||||
|
||||
@@ -24,8 +28,9 @@ void detector_reset(detector_t *d); // back to IDLE with empty history
|
||||
// Loudness gate AND score >= threshold.
|
||||
bool detector_is_match(float rms, float score, float min_rms, float threshold);
|
||||
|
||||
// Feed one frame. Returns true exactly once per trigger (IDLE -> TRIGGERED).
|
||||
// TRIGGERED lasts one frame, then COOLDOWN until cooldown_ms elapsed, then IDLE
|
||||
// with a cleared history (the buzzer's tail cannot re-trigger).
|
||||
bool detector_update(detector_t *d, bool match, uint32_t now_ms, const detector_cfg_t *cfg);
|
||||
// Feed one frame. Returns true when a buzz event ends and writes its duration.
|
||||
// Matches within cooldown_ms extend the same event; the final quiet tail is
|
||||
// excluded from its duration.
|
||||
bool detector_update(detector_t *d, bool match, uint32_t now_ms, const detector_cfg_t *cfg,
|
||||
uint32_t *buzz_duration_ms);
|
||||
const char *detector_state_name(det_state_t s);
|
||||
|
||||
+15
-6
@@ -92,11 +92,12 @@ static void processing_task(void *arg)
|
||||
const app_mode_t mode = calibration_get_mode();
|
||||
const bool match = has_tpl && detector_is_match(rms, score, cfg.min_rms, cfg.threshold);
|
||||
|
||||
bool trig = false;
|
||||
bool buzz_ended = false;
|
||||
uint32_t buzz_duration_ms = 0;
|
||||
if (mode == MODE_NORMAL) {
|
||||
if (has_tpl) {
|
||||
detector_cfg_t dc = { cfg.win_n, cfg.win_m, cfg.cooldown_ms };
|
||||
trig = detector_update(&det, match, now_ms, &dc);
|
||||
buzz_ended = detector_update(&det, match, now_ms, &dc, &buzz_duration_ms);
|
||||
} else {
|
||||
detector_reset(&det);
|
||||
}
|
||||
@@ -104,9 +105,13 @@ static void processing_task(void *arg)
|
||||
calibration_on_frame(fp, valid, rms, score); // detector paused: no alerts
|
||||
detector_reset(&det);
|
||||
}
|
||||
if (trig) {
|
||||
ESP_LOGW(TAG, "BUZZER DETECTED (rms %.0f, score %.3f)", rms, score);
|
||||
notifier_send(NOTIF_ALERT, "Buzzer detected");
|
||||
if (buzz_ended) {
|
||||
char message[96];
|
||||
snprintf(message, sizeof(message), "Buzzer detected: %lu ms",
|
||||
(unsigned long)buzz_duration_ms);
|
||||
ESP_LOGW(TAG, "BUZZER EVENT ended, detected duration %lu ms",
|
||||
(unsigned long)buzz_duration_ms);
|
||||
notifier_send(NOTIF_ALERT, message);
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&s_lock);
|
||||
@@ -118,7 +123,11 @@ static void processing_task(void *arg)
|
||||
s_stats.last_match = match;
|
||||
s_stats.state = det.state;
|
||||
s_stats.triggers = det.triggers;
|
||||
if (trig) s_stats.last_trigger_s = now_ms / 1000 ? now_ms / 1000 : 1;
|
||||
if (buzz_ended) {
|
||||
s_stats.last_trigger_s = det.last_completed_start_ms / 1000;
|
||||
if (s_stats.last_trigger_s == 0) s_stats.last_trigger_s = 1;
|
||||
s_stats.last_buzz_ms = buzz_duration_ms;
|
||||
}
|
||||
memcpy(s_last_fp, fp, sizeof(s_last_fp));
|
||||
portEXIT_CRITICAL(&s_lock);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ typedef struct {
|
||||
det_state_t state;
|
||||
uint32_t triggers;
|
||||
uint32_t last_trigger_s; // uptime seconds of the last trigger, 0 = never
|
||||
uint32_t last_buzz_ms; // detected duration, excluding final quiet tail
|
||||
} proc_stats_t;
|
||||
|
||||
esp_err_t processing_start(void);
|
||||
|
||||
Reference in New Issue
Block a user