#include "pod_settings.h" #include "bosch456.h" #include "esp_log.h" #include "nvs.h" #include "nvs_flash.h" #include static const char *TAG = "[SETTINGS]"; static const char *NS = "powerpod"; static const char *KEY_ACCEL_DZ = "accel_dz"; static const char *KEY_UV_LATCH = "uv_latch"; static const char *KEY_TAP_CFG = "tap_cfg"; #define ACCEL_DEADZONE_MAX 4095u #define TAP_CFG_NVS_VERSION 1u /** Packed layout for NVS blob (stable across rebuilds). */ typedef struct __attribute__((packed)) { uint8_t version; uint16_t tap_sens_thres; uint16_t max_gest_dur; uint16_t tap_shock_dur; uint16_t quite_time_after_gest; uint16_t wait_for_timeout; uint16_t axis_sel; uint8_t enable_flags; /* bit0=single, bit1=double, bit2=triple */ } tap_config_nvs_t; static bool s_nvs_ready; static esp_err_t ensure_nvs(void) { if (s_nvs_ready) { return ESP_OK; } return pod_settings_init(); } esp_err_t pod_settings_init(void) { if (s_nvs_ready) { return ESP_OK; } esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_LOGW(TAG, "NVS erase and re-init"); ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init(); } if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_flash_init failed: %s", esp_err_to_name(err)); return err; } s_nvs_ready = true; ESP_LOGI(TAG, "NVS ready, accel deadzone %lu LSB (stored)", (unsigned long)pod_settings_load_accel_deadzone()); return ESP_OK; } uint32_t pod_settings_load_accel_deadzone(void) { if (!s_nvs_ready) { return BMA456_DEFAULT_ACCEL_DEADZONE; } nvs_handle_t handle; esp_err_t err = nvs_open(NS, NVS_READONLY, &handle); if (err != ESP_OK) { return BMA456_DEFAULT_ACCEL_DEADZONE; } uint32_t value = BMA456_DEFAULT_ACCEL_DEADZONE; err = nvs_get_u32(handle, KEY_ACCEL_DZ, &value); nvs_close(handle); if (err == ESP_ERR_NVS_NOT_FOUND) { return BMA456_DEFAULT_ACCEL_DEADZONE; } if (err != ESP_OK) { ESP_LOGW(TAG, "read accel_dz failed: %s", esp_err_to_name(err)); return BMA456_DEFAULT_ACCEL_DEADZONE; } if (value > ACCEL_DEADZONE_MAX) { return BMA456_DEFAULT_ACCEL_DEADZONE; } return value; } esp_err_t pod_settings_save_accel_deadzone(uint32_t deadzone_lsb) { if (deadzone_lsb > ACCEL_DEADZONE_MAX) { return ESP_ERR_INVALID_ARG; } esp_err_t err = ensure_nvs(); if (err != ESP_OK) { return err; } nvs_handle_t handle; err = nvs_open(NS, NVS_READWRITE, &handle); if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err)); return err; } err = nvs_set_u32(handle, KEY_ACCEL_DZ, deadzone_lsb); if (err == ESP_OK) { err = nvs_commit(handle); } nvs_close(handle); if (err != ESP_OK) { ESP_LOGE(TAG, "save accel_dz failed: %s", esp_err_to_name(err)); return err; } ESP_LOGI(TAG, "saved accel deadzone %lu LSB", (unsigned long)deadzone_lsb); return ESP_OK; } void pod_settings_apply_accel_deadzone(void) { uint32_t deadzone = pod_settings_load_accel_deadzone(); if (bma456_is_ready()) { bma456_set_accel_deadzone(deadzone); } } static void tap_config_to_nvs(const bma456_tap_config_t *cfg, tap_config_nvs_t *out) { memset(out, 0, sizeof(*out)); out->version = TAP_CFG_NVS_VERSION; out->tap_sens_thres = cfg->tap_sens_thres; out->max_gest_dur = cfg->max_gest_dur; out->tap_shock_dur = cfg->tap_shock_dur; out->quite_time_after_gest = cfg->quite_time_after_gest; out->wait_for_timeout = cfg->wait_for_timeout; out->axis_sel = cfg->axis_sel; if (cfg->enable_single) { out->enable_flags |= 0x01u; } if (cfg->enable_double) { out->enable_flags |= 0x02u; } if (cfg->enable_triple) { out->enable_flags |= 0x04u; } } static bool tap_config_from_nvs(const tap_config_nvs_t *in, bma456_tap_config_t *cfg) { if (in->version != TAP_CFG_NVS_VERSION) { return false; } cfg->tap_sens_thres = in->tap_sens_thres; cfg->max_gest_dur = in->max_gest_dur; cfg->tap_shock_dur = in->tap_shock_dur; cfg->quite_time_after_gest = in->quite_time_after_gest; cfg->wait_for_timeout = in->wait_for_timeout; cfg->axis_sel = in->axis_sel; cfg->enable_single = (in->enable_flags & 0x01u) != 0; cfg->enable_double = (in->enable_flags & 0x02u) != 0; cfg->enable_triple = (in->enable_flags & 0x04u) != 0; return bma456_tap_config_valid(cfg); } bool pod_settings_load_tap_config(bma456_tap_config_t *cfg_out) { static const bma456_tap_config_t k_default = BMA456_TAP_CONFIG_DEFAULT; if (cfg_out == NULL) { return false; } *cfg_out = k_default; if (!s_nvs_ready) { return false; } nvs_handle_t handle; esp_err_t err = nvs_open(NS, NVS_READONLY, &handle); if (err != ESP_OK) { return false; } tap_config_nvs_t blob = {0}; size_t len = sizeof(blob); err = nvs_get_blob(handle, KEY_TAP_CFG, &blob, &len); nvs_close(handle); if (err == ESP_ERR_NVS_NOT_FOUND) { return false; } if (err != ESP_OK || len != sizeof(blob)) { ESP_LOGW(TAG, "read tap_cfg failed: %s (len=%u)", esp_err_to_name(err), (unsigned)len); return false; } if (!tap_config_from_nvs(&blob, cfg_out)) { ESP_LOGW(TAG, "tap_cfg blob invalid; using defaults"); *cfg_out = k_default; return false; } return true; } esp_err_t pod_settings_save_tap_config(const bma456_tap_config_t *cfg) { if (!bma456_tap_config_valid(cfg)) { return ESP_ERR_INVALID_ARG; } esp_err_t err = ensure_nvs(); if (err != ESP_OK) { return err; } nvs_handle_t handle; err = nvs_open(NS, NVS_READWRITE, &handle); if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err)); return err; } tap_config_nvs_t blob; tap_config_to_nvs(cfg, &blob); err = nvs_set_blob(handle, KEY_TAP_CFG, &blob, sizeof(blob)); if (err == ESP_OK) { err = nvs_commit(handle); } nvs_close(handle); if (err != ESP_OK) { ESP_LOGE(TAG, "save tap_cfg failed: %s", esp_err_to_name(err)); return err; } ESP_LOGI(TAG, "saved tap config sens=%u max_gest=%u shock=%u quiet=%u wait=%u " "axis=%u (S=%d D=%d T=%d)", (unsigned)cfg->tap_sens_thres, (unsigned)cfg->max_gest_dur, (unsigned)cfg->tap_shock_dur, (unsigned)cfg->quite_time_after_gest, (unsigned)cfg->wait_for_timeout, (unsigned)cfg->axis_sel, cfg->enable_single, cfg->enable_double, cfg->enable_triple); return ESP_OK; } void pod_settings_apply_tap_config(void) { bma456_tap_config_t cfg; if (!pod_settings_load_tap_config(&cfg)) { return; } if (!bma456_is_ready()) { return; } if (bma456_set_tap_config(&cfg) != ESP_OK) { ESP_LOGW(TAG, "failed to apply NVS tap config"); } } bool pod_settings_is_uv_latched(void) { if (!s_nvs_ready) { return false; } nvs_handle_t handle; esp_err_t err = nvs_open(NS, NVS_READONLY, &handle); if (err != ESP_OK) { return false; } uint8_t value = 0; err = nvs_get_u8(handle, KEY_UV_LATCH, &value); nvs_close(handle); return err == ESP_OK && value != 0; } esp_err_t pod_settings_set_uv_latched(bool latched) { esp_err_t err = ensure_nvs(); if (err != ESP_OK) { return err; } nvs_handle_t handle; err = nvs_open(NS, NVS_READWRITE, &handle); if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err)); return err; } err = nvs_set_u8(handle, KEY_UV_LATCH, latched ? 1u : 0u); if (err == ESP_OK) { err = nvs_commit(handle); } nvs_close(handle); if (err != ESP_OK) { ESP_LOGE(TAG, "save uv_latch failed: %s", esp_err_to_name(err)); return err; } ESP_LOGI(TAG, "UV latch %s", latched ? "set" : "cleared"); return ESP_OK; } esp_err_t pod_settings_clear_uv_latched(void) { return pod_settings_set_uv_latched(false); }