Persist accelerometer deadzone in NVS across reboots.

Each node saves its local deadzone on UART or ESP-NOW set; pod_settings loads and applies it after BMA456 init.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
simon 2026-05-19 22:29:00 +02:00
parent 2e88358c53
commit 5c3cf65bca
7 changed files with 158 additions and 16 deletions

View File

@ -30,6 +30,7 @@ idf_component_register(
"esp_now_proto.c"
"bosch456.c"
"board_input.c"
"pod_settings.c"
"proto/uart_messages.pb.c"
"proto/esp_now_messages.pb.c"
"proto/pb_encode.c"

View File

@ -82,12 +82,14 @@ Powerpod uses the Bosch **BMA456H** (hearable) variant, not the generic `bma456w
**Accel logging:** Samples are printed only when any axis changes by more than the **deadzone** (raw LSB) since the last logged sample (default **100**). This is a **software** filter on top of the sensor; it does not change BMA456 hardware thresholds.
**Persistence:** The local deadzone is stored in the **`nvs`** partition (namespace `powerpod`, key `accel_dz`) via `pod_settings.c`. Each node (master or slave) keeps its own value across reboot. Loaded at boot after `init_bma456()`; saved when set locally (UART `client_id = 0`, `all_clients` on master, or ESP-NOW deadzone on a slave).
**Configuration paths:**
| Path | Effect |
|------|--------|
| UART `ACCEL_DEADZONE` with `client_id = 0` | `bma456_set_accel_deadzone()` on the local node |
| ESP-NOW `SET_ACCEL_DEADZONE` | Same on a slave (no-op log path if sensor not installed) |
| UART `ACCEL_DEADZONE` with `client_id = 0` | Set + save local deadzone |
| ESP-NOW `SET_ACCEL_DEADZONE` | Set + save on the receiving slave |
| `make gotool-deadzone-set DEADZONE=… CLIENT=0` | Host shortcut for local deadzone |
**Logs:** `[BMA456] ACC X=… Y=… Z=…` when deadzone exceeded; `[BMA456] tap: single|double|triple` on interrupt.
@ -440,6 +442,7 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running `
| `client_registry.c/h` | Registered slave table |
| `bosch456.c/h` | BMA456H I2C driver, accel poll, tap INT, deadzone filter |
| `board_input.c/h` | Taster GPIO12, LiPo ADC on GPIO1 / GPIO12 |
| `pod_settings.c/h` | NVS persistence (accel deadzone, …) |
| `led_ring.c/h` | LED ring (digit display, progress bar) |
| `cmd_led_ring.c` | UART `LED_RING` progress command |
| `proto/uart_messages.proto` | UART protocol schema |

View File

@ -3,6 +3,7 @@
#include "cmd_accel_deadzone.h"
#include "esp_log.h"
#include "esp_now_comm.h"
#include "pod_settings.h"
#include "uart_cmd.h"
static const char *TAG = "[ACCEL_DZ]";
@ -19,6 +20,14 @@ static void reply(uint32_t deadzone, uint32_t client_id, bool success,
uart_cmd_send(&response, TAG);
}
static void apply_local_deadzone(uint32_t deadzone) {
bma456_set_accel_deadzone(deadzone);
if (pod_settings_save_accel_deadzone(deadzone) != ESP_OK) {
ESP_LOGW(TAG, "deadzone %lu applied but not saved to NVS",
(unsigned long)deadzone);
}
}
static esp_err_t push_deadzone_to_slave(const client_info_t *client,
uint32_t deadzone) {
if (client == NULL) {
@ -67,7 +76,7 @@ static void handle_accel_deadzone(const uint8_t *data, size_t len) {
}
if (bma456_is_ready()) {
bma456_set_accel_deadzone(req.deadzone);
apply_local_deadzone(req.deadzone);
}
ESP_LOGI(TAG, "set deadzone %lu via unicast to %u/%u slaves",
@ -77,7 +86,7 @@ static void handle_accel_deadzone(const uint8_t *data, size_t len) {
}
if (req.client_id == 0) {
bma456_set_accel_deadzone(req.deadzone);
apply_local_deadzone(req.deadzone);
ESP_LOGI(TAG, "set local deadzone %lu (no ESP-NOW; use -client or -all "
"for slaves)",
(unsigned long)req.deadzone);

View File

@ -3,6 +3,7 @@
#include "esp_now_comm.h"
#include "led_ring.h"
#include "ota_espnow.h"
#include "pod_settings.h"
#include "esp_now_proto.h"
#include "esp_err.h"
#include "esp_event.h"
@ -13,7 +14,6 @@
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/idf_additions.h"
#include "nvs_flash.h"
#include "ota_uart.h"
#include <stdio.h>
#include <string.h>
@ -399,6 +399,10 @@ static void handle_slave_accel_deadzone(const uint8_t *master_mac,
bma456_is_ready() ? "ok" : "not installed");
bma456_set_accel_deadzone(cfg->deadzone);
if (pod_settings_save_accel_deadzone(cfg->deadzone) != ESP_OK) {
ESP_LOGW(TAG, "slave deadzone %lu applied but not saved to NVS",
(unsigned long)cfg->deadzone);
}
}
static void handle_client_presence(const alox_EspNowSlavePresence *presence,
@ -607,17 +611,6 @@ static void master_discover_task(void *param) {
}
static esp_err_t init_wifi_stack(uint8_t channel) {
esp_err_t err;
err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
if (err != ESP_OK) {
return err;
}
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());

110
main/pod_settings.c Normal file
View File

@ -0,0 +1,110 @@
#include "pod_settings.h"
#include "bosch456.h"
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"
static const char *TAG = "[SETTINGS]";
static const char *NS = "powerpod";
static const char *KEY_ACCEL_DZ = "accel_dz";
#define ACCEL_DEADZONE_MAX 4095u
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);
}
}

19
main/pod_settings.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef POD_SETTINGS_H
#define POD_SETTINGS_H
#include "esp_err.h"
#include <stdint.h>
/** Initialize NVS (idempotent) and log stored settings. Call once early in app_main. */
esp_err_t pod_settings_init(void);
/** Persist local accelerometer deadzone (LSB per axis). */
esp_err_t pod_settings_save_accel_deadzone(uint32_t deadzone_lsb);
/** Load deadzone from NVS, or BMA456_DEFAULT_ACCEL_DEADZONE if unset. */
uint32_t pod_settings_load_accel_deadzone(void);
/** Apply NVS deadzone to BMA456 when the sensor is present. */
void pod_settings_apply_accel_deadzone(void);
#endif

View File

@ -22,6 +22,7 @@
#include "board_input.h"
#include "bosch456.h"
#include "led_ring.h"
#include "pod_settings.h"
#include "uart.h"
#include <stdint.h>
@ -66,6 +67,10 @@ uint8_t reverse_high_nibble_lut(uint8_t n) {
}
void app_main(void) {
if (pod_settings_init() != ESP_OK) {
ESP_LOGW(TAG, "settings NVS init failed; using defaults");
}
// Get Master Mode Pin
gpio_reset_pin(DIP_MASTER);
gpio_set_direction(DIP_MASTER, GPIO_MODE_INPUT);
@ -125,6 +130,8 @@ void app_main(void) {
esp_err_t bma_err = init_bma456(bus_handle);
if (bma_err != ESP_OK) {
ESP_LOGI(TAG, "BMA456 init skipped: %s", esp_err_to_name(bma_err));
} else {
pod_settings_apply_accel_deadzone();
}
}