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:
+144
-18
@@ -1,4 +1,5 @@
|
||||
#include "client_registry.h"
|
||||
#include "pod_settings.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/idf_additions.h"
|
||||
@@ -13,12 +14,68 @@ typedef struct {
|
||||
} client_slot_t;
|
||||
|
||||
static client_slot_t s_clients[CLIENT_REGISTRY_MAX];
|
||||
static pod_id_map_t s_id_map;
|
||||
|
||||
static struct {
|
||||
board_lipo_reading_t reading;
|
||||
uint32_t updated_at;
|
||||
} s_master_battery;
|
||||
|
||||
static bool id_in_use(uint32_t id, const uint8_t *except_mac) {
|
||||
for (uint8_t i = 0; i < s_id_map.count; i++) {
|
||||
if (s_id_map.entries[i].id != id) {
|
||||
continue;
|
||||
}
|
||||
if (except_mac != NULL &&
|
||||
memcmp(s_id_map.entries[i].mac, except_mac, CLIENT_MAC_LEN) == 0) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int find_map_index(const uint8_t mac[CLIENT_MAC_LEN]) {
|
||||
for (uint8_t i = 0; i < s_id_map.count; i++) {
|
||||
if (memcmp(s_id_map.entries[i].mac, mac, CLIENT_MAC_LEN) == 0) {
|
||||
return (int)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static esp_err_t persist_id_map(void) {
|
||||
return pod_settings_save_id_map(&s_id_map);
|
||||
}
|
||||
|
||||
static uint32_t alloc_next_free_id(void) {
|
||||
if (s_id_map.count >= POD_ID_MAP_MAX) {
|
||||
return 0;
|
||||
}
|
||||
uint8_t start = s_id_map.next_id;
|
||||
if (start < POD_CLIENT_ID_MIN) {
|
||||
start = (uint8_t)POD_CLIENT_ID_MIN;
|
||||
}
|
||||
for (uint32_t n = 0; n < POD_CLIENT_ID_MAX; n++) {
|
||||
uint32_t id = (uint32_t)start + n;
|
||||
if (id > POD_CLIENT_ID_MAX) {
|
||||
id = id - POD_CLIENT_ID_MAX;
|
||||
}
|
||||
if (id < POD_CLIENT_ID_MIN) {
|
||||
continue;
|
||||
}
|
||||
if (!id_in_use(id, NULL)) {
|
||||
uint32_t next = id + 1;
|
||||
if (next > POD_CLIENT_ID_MAX) {
|
||||
next = POD_CLIENT_ID_MIN;
|
||||
}
|
||||
s_id_map.next_id = (uint8_t)next;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t client_registry_now_ms(void) {
|
||||
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
|
||||
}
|
||||
@@ -95,7 +152,84 @@ static client_slot_t *alloc_slot(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void client_registry_init(void) { memset(s_clients, 0, sizeof(s_clients)); }
|
||||
void client_registry_init(void) {
|
||||
memset(s_clients, 0, sizeof(s_clients));
|
||||
memset(&s_id_map, 0, sizeof(s_id_map));
|
||||
s_id_map.next_id = (uint8_t)POD_CLIENT_ID_MIN;
|
||||
(void)pod_settings_load_id_map(&s_id_map);
|
||||
if (s_id_map.next_id < POD_CLIENT_ID_MIN) {
|
||||
s_id_map.next_id = (uint8_t)POD_CLIENT_ID_MIN;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t client_registry_assign_id(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
uint32_t preferred_id, uint32_t *out_id) {
|
||||
if (mac == NULL || out_id == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int idx = find_map_index(mac);
|
||||
if (idx >= 0) {
|
||||
*out_id = s_id_map.entries[idx].id;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint32_t id = 0;
|
||||
if (preferred_id >= POD_CLIENT_ID_MIN && preferred_id <= POD_CLIENT_ID_MAX &&
|
||||
!id_in_use(preferred_id, mac)) {
|
||||
id = preferred_id;
|
||||
} else {
|
||||
id = alloc_next_free_id();
|
||||
}
|
||||
if (id == 0) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (s_id_map.count >= POD_ID_MAP_MAX) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
memcpy(s_id_map.entries[s_id_map.count].mac, mac, CLIENT_MAC_LEN);
|
||||
s_id_map.entries[s_id_map.count].id = (uint8_t)id;
|
||||
s_id_map.count++;
|
||||
if (id >= s_id_map.next_id) {
|
||||
uint32_t next = id + 1;
|
||||
if (next > POD_CLIENT_ID_MAX) {
|
||||
next = POD_CLIENT_ID_MIN;
|
||||
}
|
||||
s_id_map.next_id = (uint8_t)next;
|
||||
}
|
||||
|
||||
esp_err_t err = persist_id_map();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "persist id_map failed after assign id=%lu: %s",
|
||||
(unsigned long)id, esp_err_to_name(err));
|
||||
}
|
||||
*out_id = id;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void client_registry_clear_id_map(void) {
|
||||
memset(s_clients, 0, sizeof(s_clients));
|
||||
memset(&s_id_map, 0, sizeof(s_id_map));
|
||||
s_id_map.next_id = (uint8_t)POD_CLIENT_ID_MIN;
|
||||
esp_err_t err = pod_settings_clear_id_map();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "clear id_map NVS failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
ESP_LOGI(TAG, "cleared id map and live registry");
|
||||
}
|
||||
|
||||
void client_registry_mark_assign_sent(const uint8_t mac[CLIENT_MAC_LEN]) {
|
||||
if (mac == NULL) {
|
||||
return;
|
||||
}
|
||||
client_slot_t *slot = find_slot(mac);
|
||||
if (slot == NULL) {
|
||||
return;
|
||||
}
|
||||
slot->info.last_assign_sent_at = now_ms();
|
||||
}
|
||||
|
||||
const client_info_t *
|
||||
client_registry_find_by_mac(const uint8_t mac[CLIENT_MAC_LEN]) {
|
||||
@@ -171,18 +305,19 @@ void client_registry_check_timeouts(uint32_t timeout_ms) {
|
||||
uint32_t now = now_ms();
|
||||
|
||||
for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) {
|
||||
if (!s_clients[i].active || !s_clients[i].info.available) {
|
||||
if (!s_clients[i].active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t elapsed = now - s_clients[i].info.last_success_ping_at;
|
||||
if (elapsed > timeout_ms) {
|
||||
s_clients[i].info.available = false;
|
||||
char mac_str[18];
|
||||
mac_to_str(s_clients[i].info.mac, mac_str, sizeof(mac_str));
|
||||
ESP_LOGW(TAG, "client inactive id=%lu mac=%s (no heartbeat for %lu ms)",
|
||||
ESP_LOGW(TAG, "client dropped id=%lu mac=%s (no heartbeat for %lu ms)",
|
||||
(unsigned long)s_clients[i].info.id, mac_str,
|
||||
(unsigned long)elapsed);
|
||||
s_clients[i].active = false;
|
||||
memset(&s_clients[i].info, 0, sizeof(s_clients[i].info));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,24 +687,15 @@ esp_err_t client_registry_update_battery(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
|
||||
client_slot_t *slot = find_slot(mac);
|
||||
if (slot == NULL) {
|
||||
bool is_new = false;
|
||||
esp_err_t err = client_registry_upsert(mac, slave_id, 0, true, false, &is_new);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
slot = find_slot(mac);
|
||||
if (slot == NULL) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
ESP_LOGI(TAG, "battery auto-registered id=%lu (report before heartbeat)",
|
||||
(unsigned long)slave_id);
|
||||
/* Ids are assigned on presence only; ignore early battery reports. */
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (slot->info.id != slave_id) {
|
||||
ESP_LOGW(TAG, "battery id %lu → %lu for mac %02x:…:%02x",
|
||||
if (slave_id != 0 && slot->info.id != slave_id) {
|
||||
ESP_LOGW(TAG, "battery id mismatch slot=%lu report=%lu mac %02x:…:%02x",
|
||||
(unsigned long)slot->info.id, (unsigned long)slave_id, mac[0],
|
||||
mac[5]);
|
||||
slot->info.id = slave_id;
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
slot->info.lipo1_valid = lipo1_valid;
|
||||
|
||||
Reference in New Issue
Block a user