// features -> score -> detector, or calibration while a session is active. #include #include #include "esp_log.h" #include "esp_task_wdt.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "audio_in.h" #include "calibration.h" #include "config_store.h" #include "detector.h" #include "fingerprint.h" #include "led.h" #include "matcher.h" #include "notifier.h" #include "processing.h" static const char *TAG = "proc"; #define PROC_TASK_CORE 1 #define PROC_TASK_PRIO 8 #define PROC_TASK_STACK 6144 static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED; static proc_stats_t s_stats; static float s_last_fp[NUM_BANDS]; static volatile bool s_monitor; static features_ctx_t s_ctx; void processing_get_stats(proc_stats_t *out) { portENTER_CRITICAL(&s_lock); *out = s_stats; portEXIT_CRITICAL(&s_lock); } void processing_get_last_fp(float fp[NUM_BANDS]) { portENTER_CRITICAL(&s_lock); memcpy(fp, s_last_fp, sizeof(s_last_fp)); portEXIT_CRITICAL(&s_lock); } void processing_set_monitor(bool on) { s_monitor = on; } bool processing_get_monitor(void) { return s_monitor; } static void update_led(app_mode_t mode, det_state_t st, bool has_tpl) { static int last = -1; int code; if (!has_tpl) code = 1; else if (mode != MODE_NORMAL) code = 2; else if (st != DET_IDLE) code = 3; else code = 0; if (code == last) return; last = code; switch (code) { case 1: led_set(20, 0, 0); break; // red: needs template case 2: led_set(20, 12, 0); break; // amber: calibration session case 3: led_set(20, 0, 20); break; // magenta: alert / cooldown default: led_set(0, 0, 6); break; // dim blue: listening } } static void processing_task(void *arg) { ESP_ERROR_CHECK(esp_task_wdt_add(NULL)); detector_t det; detector_init(&det); float fp[NUM_BANDS]; uint32_t last_mon_ms = 0; for (;;) { esp_task_wdt_reset(); audio_frame_t fr; if (!audio_in_get_frame(&fr, pdMS_TO_TICKS(1000))) { ESP_LOGW(TAG, "no audio frame for 1 s"); continue; } int64_t t0 = esp_timer_get_time(); float rms = 0.0f; bool valid = features_compute(&s_ctx, fr.pcm, fp, &rms); const bool has_tpl = matcher_has_template(); const float score = (valid && has_tpl) ? matcher_score(fp) : 0.0f; uint32_t dt_us = (uint32_t)(esp_timer_get_time() - t0); audio_in_release_frame(fr.idx); // pool slot back as early as possible const uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000); app_config_t cfg; config_get(&cfg); const app_mode_t mode = calibration_get_mode(); const bool match = has_tpl && detector_is_match(rms, score, cfg.min_rms, cfg.threshold); 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 }; buzz_ended = detector_update(&det, match, now_ms, &dc, &buzz_duration_ms); } else { detector_reset(&det); } } else { calibration_on_frame(fp, valid, rms, score); // detector paused: no alerts detector_reset(&det); } 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); s_stats.frames++; s_stats.proc_avg_us = s_stats.frames == 1 ? dt_us : (s_stats.proc_avg_us * 15u + dt_us) / 16u; if (dt_us > s_stats.proc_max_us) s_stats.proc_max_us = dt_us; s_stats.last_rms = rms; s_stats.last_score = score; s_stats.last_match = match; s_stats.state = det.state; s_stats.triggers = det.triggers; 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); update_led(mode, det.state, has_tpl); if (s_monitor && (uint32_t)(now_ms - last_mon_ms) >= cfg.monitor_ms) { last_mon_ms = now_ms; printf("[%8lu ms] rms=%7.1f score=%+.3f %s %s\n", (unsigned long)now_ms, rms, score, match ? "MATCH" : " - ", mode == MODE_NORMAL ? detector_state_name(det.state) : calibration_mode_name(mode)); } } } esp_err_t processing_start(void) { esp_err_t err = features_init(); if (err != ESP_OK) return err; BaseType_t ok = xTaskCreatePinnedToCore(processing_task, "proc", PROC_TASK_STACK, NULL, PROC_TASK_PRIO, NULL, PROC_TASK_CORE); return ok == pdPASS ? ESP_OK : ESP_ERR_NO_MEM; }