Assign sequential pod IDs from the master with NVS-stable bindings.

Replaces MAC-byte self-IDs with master allocation (1–255), slave preferred-ID reconnect, debug reset via UART/UI, and rate-limited ASSIGN to avoid ESP-NOW NO_MEM storms.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-01 17:25:07 +01:00
co-authored by Cursor
parent af20635047
commit 567dab6d0a
26 changed files with 1261 additions and 163 deletions
+251
View File
@@ -10,9 +10,31 @@ 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";
static const char *KEY_ID_MAP = "id_map";
static const char *KEY_ID_BIND = "id_bind";
#define ACCEL_DEADZONE_MAX 4095u
#define TAP_CFG_NVS_VERSION 1u
#define ID_MAP_NVS_VERSION 1u
#define ID_BIND_NVS_VERSION 1u
typedef struct __attribute__((packed)) {
uint8_t mac[POD_ID_MAC_LEN];
uint8_t id;
} id_map_entry_nvs_t;
typedef struct __attribute__((packed)) {
uint8_t version;
uint8_t next_id;
uint8_t count;
id_map_entry_nvs_t entries[POD_ID_MAP_MAX];
} id_map_nvs_t;
typedef struct __attribute__((packed)) {
uint8_t version;
uint8_t master_mac[POD_ID_MAC_LEN];
uint8_t assigned_id;
} id_bind_nvs_t;
/** Packed layout for NVS blob (stable across rebuilds). */
typedef struct __attribute__((packed)) {
@@ -302,3 +324,232 @@ esp_err_t pod_settings_set_uv_latched(bool latched) {
esp_err_t pod_settings_clear_uv_latched(void) {
return pod_settings_set_uv_latched(false);
}
esp_err_t pod_settings_load_id_map(pod_id_map_t *out) {
if (out == NULL) {
return ESP_ERR_INVALID_ARG;
}
memset(out, 0, sizeof(*out));
out->next_id = (uint8_t)POD_CLIENT_ID_MIN;
if (!s_nvs_ready) {
return ESP_OK;
}
nvs_handle_t handle;
esp_err_t err = nvs_open(NS, NVS_READONLY, &handle);
if (err != ESP_OK) {
return ESP_OK;
}
id_map_nvs_t blob;
memset(&blob, 0, sizeof(blob));
size_t len = sizeof(blob);
err = nvs_get_blob(handle, KEY_ID_MAP, &blob, &len);
nvs_close(handle);
if (err == ESP_ERR_NVS_NOT_FOUND) {
return ESP_OK;
}
if (err != ESP_OK || len < 3 || blob.version != ID_MAP_NVS_VERSION) {
ESP_LOGW(TAG, "read id_map failed: %s (len=%u)", esp_err_to_name(err),
(unsigned)len);
return ESP_OK;
}
if (blob.count > POD_ID_MAP_MAX) {
ESP_LOGW(TAG, "id_map count %u invalid", (unsigned)blob.count);
return ESP_OK;
}
if (blob.next_id < POD_CLIENT_ID_MIN) {
blob.next_id = (uint8_t)POD_CLIENT_ID_MIN;
}
out->next_id = blob.next_id;
out->count = blob.count;
for (uint8_t i = 0; i < blob.count; i++) {
memcpy(out->entries[i].mac, blob.entries[i].mac, POD_ID_MAC_LEN);
out->entries[i].id = blob.entries[i].id;
}
ESP_LOGI(TAG, "loaded id_map count=%u next_id=%u", (unsigned)out->count,
(unsigned)out->next_id);
return ESP_OK;
}
esp_err_t pod_settings_save_id_map(const pod_id_map_t *map) {
if (map == NULL || map->count > POD_ID_MAP_MAX) {
return ESP_ERR_INVALID_ARG;
}
if (map->next_id < POD_CLIENT_ID_MIN) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = ensure_nvs();
if (err != ESP_OK) {
return err;
}
id_map_nvs_t blob;
memset(&blob, 0, sizeof(blob));
blob.version = ID_MAP_NVS_VERSION;
blob.next_id = map->next_id;
blob.count = map->count;
for (uint8_t i = 0; i < map->count; i++) {
memcpy(blob.entries[i].mac, map->entries[i].mac, POD_ID_MAC_LEN);
blob.entries[i].id = map->entries[i].id;
}
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_blob(handle, KEY_ID_MAP, &blob, sizeof(blob));
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "save id_map failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG, "saved id_map count=%u next_id=%u", (unsigned)map->count,
(unsigned)map->next_id);
return ESP_OK;
}
esp_err_t pod_settings_clear_id_map(void) {
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_erase_key(handle, KEY_ID_MAP);
if (err == ESP_ERR_NVS_NOT_FOUND) {
err = ESP_OK;
}
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "clear id_map failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG, "cleared id_map");
return ESP_OK;
}
bool pod_settings_load_id_binding(uint8_t master_mac[POD_ID_MAC_LEN],
uint32_t *assigned_id_out) {
if (master_mac == NULL || assigned_id_out == NULL) {
return false;
}
memset(master_mac, 0, POD_ID_MAC_LEN);
*assigned_id_out = 0;
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;
}
id_bind_nvs_t blob = {0};
size_t len = sizeof(blob);
err = nvs_get_blob(handle, KEY_ID_BIND, &blob, &len);
nvs_close(handle);
if (err != ESP_OK || len != sizeof(blob) ||
blob.version != ID_BIND_NVS_VERSION) {
return false;
}
if (blob.assigned_id < POD_CLIENT_ID_MIN) {
return false;
}
memcpy(master_mac, blob.master_mac, POD_ID_MAC_LEN);
*assigned_id_out = blob.assigned_id;
return true;
}
esp_err_t pod_settings_save_id_binding(const uint8_t master_mac[POD_ID_MAC_LEN],
uint32_t assigned_id) {
if (master_mac == NULL || assigned_id < POD_CLIENT_ID_MIN ||
assigned_id > POD_CLIENT_ID_MAX) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = ensure_nvs();
if (err != ESP_OK) {
return err;
}
id_bind_nvs_t blob = {
.version = ID_BIND_NVS_VERSION,
.assigned_id = (uint8_t)assigned_id,
};
memcpy(blob.master_mac, master_mac, POD_ID_MAC_LEN);
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_blob(handle, KEY_ID_BIND, &blob, sizeof(blob));
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "save id_bind failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG, "saved id_bind id=%lu master=%02x:…:%02x",
(unsigned long)assigned_id, master_mac[0], master_mac[5]);
return ESP_OK;
}
esp_err_t pod_settings_clear_id_binding(void) {
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_erase_key(handle, KEY_ID_BIND);
if (err == ESP_ERR_NVS_NOT_FOUND) {
err = ESP_OK;
}
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "clear id_bind failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG, "cleared id_bind");
return ESP_OK;
}