Expose multitap parameters over UART/ESP-NOW (like deadzone), store per node in NVS, and allow get/set from CLI, REST, and the dashboard without poll-overwrite while editing. Co-authored-by: Cursor <cursoragent@cursor.com>
305 lines
7.7 KiB
C
305 lines
7.7 KiB
C
#include "pod_settings.h"
|
|
#include "bosch456.h"
|
|
#include "esp_log.h"
|
|
#include "nvs.h"
|
|
#include "nvs_flash.h"
|
|
#include <string.h>
|
|
|
|
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);
|
|
}
|