#include #include #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "nvs.h" #include "fingerprint.h" #include "matcher.h" #include "template.h" _Static_assert(TEMPLATE_NUM_BANDS == NUM_BANDS, "template.h was generated with a different NUM_BANDS: rerun tools/make_template.py"); static const char *TAG = "matcher"; #define NVS_NS "buzz" #define TPL_KEY "tpl" #define TPL_MAGIC 0x42555A54u // 'BUZT' typedef struct { uint32_t magic; uint32_t num_bands; uint32_t frame_count; float fp[NUM_BANDS]; } tpl_blob_t; static SemaphoreHandle_t s_mx; static float s_tpl[NUM_BANDS]; static tpl_source_t s_src = TPL_NONE; static uint32_t s_count; static bool valid_unit_vector(const float *v) { float s = 0; for (int i = 0; i < NUM_BANDS; i++) { if (!isfinite(v[i])) return false; s += v[i] * v[i]; } return fabsf(sqrtf(s) - 1.0f) < 0.01f; } static bool load_nvs(void) { nvs_handle_t h; if (nvs_open(NVS_NS, NVS_READONLY, &h) != ESP_OK) return false; tpl_blob_t b; size_t len = sizeof(b); esp_err_t e = nvs_get_blob(h, TPL_KEY, &b, &len); nvs_close(h); if (e != ESP_OK || len != sizeof(b) || b.magic != TPL_MAGIC || b.num_bands != NUM_BANDS || !valid_unit_vector(b.fp)) { return false; } memcpy(s_tpl, b.fp, sizeof(s_tpl)); s_count = b.frame_count; s_src = TPL_NVS; return true; } static void load_builtin_or_none(void) { #if TEMPLATE_VALID memcpy(s_tpl, TEMPLATE_FP, sizeof(s_tpl)); if (valid_unit_vector(s_tpl)) { s_count = TEMPLATE_FRAME_COUNT; s_src = TPL_BUILTIN; return; } ESP_LOGE(TAG, "compiled-in template is not a unit vector, ignoring it"); #endif memset(s_tpl, 0, sizeof(s_tpl)); s_count = 0; s_src = TPL_NONE; } esp_err_t matcher_init(void) { s_mx = xSemaphoreCreateMutex(); if (!s_mx) return ESP_ERR_NO_MEM; if (!load_nvs()) load_builtin_or_none(); ESP_LOGI(TAG, "template source: %s (%lu frames)", matcher_source_name(), (unsigned long)s_count); return ESP_OK; } bool matcher_has_template(void) { return s_src != TPL_NONE; } tpl_source_t matcher_source(void) { return s_src; } uint32_t matcher_frame_count(void) { return s_count; } const char *matcher_source_name(void) { switch (s_src) { case TPL_NVS: return "learned on-device (NVS)"; case TPL_BUILTIN: return "compiled-in template.h"; default: return "NONE (needs template)"; } } bool matcher_suggested_threshold(float *thr) { #if TEMPLATE_HAS_SUGGESTED_THRESHOLD *thr = TEMPLATE_SUGGESTED_THRESHOLD; return true; #else (void)thr; return false; #endif } float matcher_score(const float fp[NUM_BANDS]) { float s = 0.0f; xSemaphoreTake(s_mx, portMAX_DELAY); if (s_src != TPL_NONE) s = features_dot(fp, s_tpl); xSemaphoreGive(s_mx); return s; } void matcher_get_template(float out[NUM_BANDS]) { xSemaphoreTake(s_mx, portMAX_DELAY); memcpy(out, s_tpl, sizeof(s_tpl)); xSemaphoreGive(s_mx); } esp_err_t matcher_set_template(const float fp[NUM_BANDS], uint32_t frame_count) { if (!valid_unit_vector(fp)) return ESP_ERR_INVALID_ARG; nvs_handle_t h; esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h); if (err != ESP_OK) return err; tpl_blob_t b = { .magic = TPL_MAGIC, .num_bands = NUM_BANDS, .frame_count = frame_count }; memcpy(b.fp, fp, sizeof(b.fp)); err = nvs_set_blob(h, TPL_KEY, &b, sizeof(b)); if (err == ESP_OK) err = nvs_commit(h); nvs_close(h); if (err != ESP_OK) { ESP_LOGE(TAG, "saving template: %s", esp_err_to_name(err)); return err; } xSemaphoreTake(s_mx, portMAX_DELAY); memcpy(s_tpl, fp, sizeof(s_tpl)); s_count = frame_count; s_src = TPL_NVS; xSemaphoreGive(s_mx); return ESP_OK; } esp_err_t matcher_reset(void) { nvs_handle_t h; esp_err_t err = nvs_open(NVS_NS, NVS_READWRITE, &h); if (err != ESP_OK) return err; err = nvs_erase_key(h, TPL_KEY); if (err == ESP_ERR_NVS_NOT_FOUND) err = ESP_OK; if (err == ESP_OK) err = nvs_commit(h); nvs_close(h); if (err != ESP_OK) return err; xSemaphoreTake(s_mx, portMAX_DELAY); load_builtin_or_none(); xSemaphoreGive(s_mx); return ESP_OK; }