#include #include #include #include #include "esp_console.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "esp_system.h" #include "esp_timer.h" #include "sdkconfig.h" #include "audio_in.h" #include "calibration.h" #include "config_store.h" #include "console.h" #include "fingerprint.h" #include "matcher.h" #include "notifier.h" #include "processing.h" #include "test_vector.h" static int cmd_selftest(int argc, char **argv) { #if !TEST_VECTOR_VALID printf("no test vector compiled in. Run tools/make_template.py and rebuild.\n"); return 0; #else static features_ctx_t ctx; // own scratch: never shares with the processing task float fp[NUM_BANDS], rms = 0; bool ok = features_compute(&ctx, TEST_VECTOR_PCM, fp, &rms); float maxerr = 0; for (int b = 0; b < NUM_BANDS; b++) { float e = fabsf(fp[b] - TEST_VECTOR_FP[b]); if (!(e <= maxerr)) maxerr = e; // NaN-safe } float rms_rel = fabsf(rms - TEST_VECTOR_RMS) / fmaxf(TEST_VECTOR_RMS, 1.0f); printf("fingerprint max abs error: %.3e (tol 1e-3)\n", maxerr); printf("rms: firmware %.3f, expected %.3f (rel err %.2e)\n", rms, TEST_VECTOR_RMS, rms_rel); bool pass = ok && maxerr < 1e-3f && rms_rel < 1e-3f; printf("selftest: %s\n", pass ? "PASS" : "FAIL"); if (!pass) printf("Parity broken: feature_params.h changed since make_template.py ran? Re-run it and rebuild.\n"); return 0; #endif } static int cmd_selfcheck(int argc, char **argv) { calibration_run_test(true); return 0; } static int cmd_test(int argc, char **argv) { calibration_run_test(false); return 0; } static int cmd_background(int argc, char **argv) { calibration_run_background(); return 0; } static int cmd_autothresh(int argc, char **argv) { calibration_autothresh(); return 0; } static int cmd_learn(int argc, char **argv) { if (argc >= 2 && strcmp(argv[1], "reset") == 0) { esp_err_t e = matcher_reset(); printf("learn reset: %s. Active template: %s\n", e == ESP_OK ? "NVS template cleared" : esp_err_to_name(e), matcher_source_name()); return 0; } calibration_run_learn(false, 0.0f); return 0; } static int cmd_adapt(int argc, char **argv) { app_config_t cfg; config_get(&cfg); float w = cfg.adapt_weight; if (argc >= 2) { char *end; w = strtof(argv[1], &end); if (*end != '\0' || !(w >= 0.0f && w <= 1.0f)) { printf("usage: adapt [weight 0..1] (weight of the NEW template)\n"); return 0; } } calibration_run_learn(true, w); return 0; } static int cmd_monitor(int argc, char **argv) { if (argc < 2 || (strcmp(argv[1], "on") && strcmp(argv[1], "off"))) { printf("usage: monitor on|off (currently %s)\n", processing_get_monitor() ? "on" : "off"); return 0; } processing_set_monitor(strcmp(argv[1], "on") == 0); printf("monitor %s\n", argv[1]); return 0; } static int cmd_set(int argc, char **argv) { if (argc != 3) { printf("usage: set (see `get`)\n"); return 0; } char err[96]; if (config_set_param(argv[1], argv[2], err, sizeof(err)) == ESP_OK) { printf("ok, saved\n"); } else { printf("error: %s\n", err); } return 0; } static int cmd_get(int argc, char **argv) { char line[200]; for (int i = 0; i < config_param_count(); i++) { if (argc >= 2 && strcmp(argv[1], config_param_name(i)) != 0) continue; config_param_format(i, line, sizeof(line)); printf("%s\n", line); } return 0; } static int cmd_status(int argc, char **argv) { proc_stats_t ps; processing_get_stats(&ps); notifier_stats_t ns; notifier_get_stats(&ns); app_config_t cfg; config_get(&cfg); float sug; printf("uptime: %llu s\n", (unsigned long long)(esp_timer_get_time() / 1000000)); printf("mode / state: %s / %s\n", calibration_mode_name(calibration_get_mode()), detector_state_name(ps.state)); 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, (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("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, (unsigned long)ps.proc_max_us, HOP_LEN * 1000000 / FS_HZ); printf("wifi: %s%s%s\n", ns.wifi_configured ? (ns.wifi_connected ? "connected " : "DISCONNECTED") : "not configured", ns.wifi_connected ? ns.ip : "", ""); printf("notifications: ok %lu, failed %lu, dropped %lu, topic %s%s\n", (unsigned long)ns.sent_ok, (unsigned long)ns.sent_failed, (unsigned long)ns.dropped, CONFIG_BUZZ_NTFY_TOPIC, cfg.heartbeat_h ? " (heartbeat on)" : ""); printf("heap free: %lu bytes\n", (unsigned long)esp_get_free_heap_size()); return 0; } static int cmd_fingerprint(int argc, char **argv) { float live[NUM_BANDS], tpl[NUM_BANDS]; processing_get_last_fp(live); matcher_get_template(tpl); const int *e = features_band_edges(); printf("band bins live template (%s)\n", matcher_source_name()); for (int b = 0; b < NUM_BANDS; b++) { printf("%3d %3d-%-3d %+.4f %+.4f\n", b, e[b], e[b + 1] - 1, live[b], tpl[b]); } return 0; } static int cmd_alert(int argc, char **argv) { printf(notifier_send(NOTIF_TEST, "Test notification") ? "queued\n" : "queue full\n"); return 0; } static int cmd_reboot(int argc, char **argv) { printf("rebooting...\n"); fflush(stdout); esp_restart(); return 0; } static void reg(const char *name, const char *help, const char *hint, esp_console_cmd_func_t fn) { const esp_console_cmd_t c = { .command = name, .help = help, .hint = hint, .func = fn }; esp_err_t err = esp_console_cmd_register(&c); if (err != ESP_OK) ESP_LOGE("console", "register %s: %s", name, esp_err_to_name(err)); } esp_err_t console_start(void) { esp_console_repl_t *repl = NULL; esp_console_repl_config_t rc = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); rc.prompt = "buzz> "; rc.max_cmdline_length = 128; rc.task_stack_size = 8192; esp_err_t err; #if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM esp_console_dev_uart_config_t hw = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); err = esp_console_new_repl_uart(&hw, &rc, &repl); #elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG esp_console_dev_usb_serial_jtag_config_t hw = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT(); err = esp_console_new_repl_usb_serial_jtag(&hw, &rc, &repl); #elif CONFIG_ESP_CONSOLE_USB_CDC esp_console_dev_usb_cdc_config_t hw = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT(); err = esp_console_new_repl_usb_cdc(&hw, &rc, &repl); #else #error "Unsupported console transport" #endif if (err != ESP_OK) { ESP_LOGE("console", "REPL create: %s", esp_err_to_name(err)); return err; } esp_console_register_help_command(); reg("selftest", "Run firmware feature pipeline on the built-in test vector (C/Python parity)", NULL, cmd_selftest); reg("selfcheck", "10 s: press the real buzzer; score vs ACTIVE template + verdict", NULL, cmd_selfcheck); reg("learn", "60 s: learn template from the real mic (`learn reset` = clear NVS template)", "[reset]", cmd_learn); reg("adapt", "60 s: blend newly heard buzzer into the active template", "[weight 0..1]", cmd_adapt); reg("background", "60 s ambient score measurement vs active template", NULL, cmd_background); reg("test", "10 s: press the buzzer; score distribution of loud frames", NULL, cmd_test); reg("autothresh", "set threshold between background p99 and buzzer p5", NULL, cmd_autothresh); reg("monitor", "per-frame live line on/off", "on|off", cmd_monitor); reg("set", "set a parameter (persisted)", " ", cmd_set); reg("get", "show parameters", "[param]", cmd_get); reg("status", "template source, detector state, timing, Wi-Fi", NULL, cmd_status); reg("fingerprint", "print live fingerprint and active template", NULL, cmd_fingerprint); reg("alert", "send a test notification", NULL, cmd_alert); reg("reboot", "restart", NULL, cmd_reboot); err = esp_console_start_repl(repl); if (err != ESP_OK) ESP_LOGE("console", "start_repl: %s", esp_err_to_name(err)); return err; }