vibe code the initial impl
This commit is contained in:
+227
@@ -0,0 +1,227 @@
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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 <param> <value> (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)", "<param> <value>", 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;
|
||||
}
|
||||
Reference in New Issue
Block a user