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
+132 -36
View File
@@ -37,6 +37,11 @@ static bool s_tap_notify_double;
static bool s_tap_notify_triple;
static uint8_t s_master_mac[ESP_NOW_ETH_ALEN];
static uint32_t s_last_discover_ms;
/** Logical id assigned by current/last master (0 = unbound). */
static uint32_t s_assigned_id;
/** Master MAC from NVS binding (may differ from current s_master_mac). */
static uint8_t s_bound_master_mac[ESP_NOW_ETH_ALEN];
static bool s_has_binding;
typedef enum {
SLAVE_TX_SLAVE_INFO = 1,
@@ -45,15 +50,91 @@ typedef enum {
static QueueHandle_t s_tx_queue;
static void queue_tx(slave_tx_op_t op);
static bool from_joined_master(const uint8_t *master_mac) {
return s_joined && esp_now_core_mac_equal(master_mac, s_master_mac);
}
static uint32_t my_client_id(void) { return s_assigned_id; }
/** True when command targets this slave (0 = broadcast / any). */
static bool client_id_matches(uint32_t client_id) {
if (client_id == 0) {
return true;
}
return s_assigned_id != 0 && client_id == s_assigned_id;
}
static uint32_t preferred_id_for_master(void) {
if (!s_has_binding || s_assigned_id == 0) {
return 0;
}
if (!s_joined || !esp_now_core_mac_equal(s_master_mac, s_bound_master_mac)) {
return 0;
}
return s_assigned_id;
}
static void load_id_binding(void) {
uint8_t mac[ESP_NOW_ETH_ALEN];
uint32_t id = 0;
s_has_binding = pod_settings_load_id_binding(mac, &id);
if (s_has_binding) {
memcpy(s_bound_master_mac, mac, ESP_NOW_ETH_ALEN);
s_assigned_id = id;
ESP_LOGI(TAG, "loaded id binding id=%lu master=%02x:…:%02x",
(unsigned long)id, mac[0], mac[5]);
} else {
memset(s_bound_master_mac, 0, sizeof(s_bound_master_mac));
s_assigned_id = 0;
}
}
static void apply_assigned_id(uint32_t assigned_id) {
if (assigned_id < POD_CLIENT_ID_MIN || assigned_id > POD_CLIENT_ID_MAX) {
ESP_LOGW(TAG, "ignore invalid assigned_id=%lu", (unsigned long)assigned_id);
return;
}
const bool same =
s_has_binding && s_assigned_id == assigned_id &&
esp_now_core_mac_equal(s_bound_master_mac, s_master_mac);
s_assigned_id = assigned_id;
memcpy(s_bound_master_mac, s_master_mac, ESP_NOW_ETH_ALEN);
s_has_binding = true;
if (same) {
return;
}
if (pod_settings_save_id_binding(s_master_mac, assigned_id) != ESP_OK) {
ESP_LOGW(TAG, "assigned id=%lu but NVS save failed",
(unsigned long)assigned_id);
} else {
ESP_LOGI(TAG, "assigned id=%lu (persisted)", (unsigned long)assigned_id);
}
queue_tx(SLAVE_TX_BATTERY);
}
static void clear_id_binding_local(void) {
s_assigned_id = 0;
s_has_binding = false;
memset(s_bound_master_mac, 0, sizeof(s_bound_master_mac));
(void)pod_settings_clear_id_binding();
ESP_LOGI(TAG, "cleared local id binding");
}
esp_err_t esp_now_comm_clear_local_id_binding(void) {
if (esp_now_core_is_master()) {
return ESP_OK;
}
clear_id_binding_local();
return ESP_OK;
}
static void fill_presence(alox_EspNowSlavePresence *presence) {
const uint8_t *own = esp_now_core_own_mac();
presence->network = esp_now_core_network();
presence->version = POWERPOD_FW_VERSION;
presence->slave_id = own[5];
presence->slave_id = preferred_id_for_master();
presence->available = true;
presence->used = false;
esp_now_proto_setup_presence_encode(presence, own);
@@ -139,7 +220,7 @@ static void queue_tx(slave_tx_op_t op) {
}
static void send_battery_to_master(void) {
if (!s_joined) {
if (!s_joined || s_assigned_id == 0) {
return;
}
@@ -147,7 +228,7 @@ static void send_battery_to_master(void) {
board_input_read_lipo(&reading);
alox_EspNowBatteryReport report = alox_EspNowBatteryReport_init_zero;
report.client_id = esp_now_core_own_mac()[5];
report.client_id = s_assigned_id;
report.lipo1_valid = reading.lipo1_valid;
report.lipo2_valid = reading.lipo2_valid;
report.lipo1_mv = reading.lipo1_mv;
@@ -261,10 +342,7 @@ static void handle_echo_ping(const uint8_t *master_mac,
static void handle_restart(const uint8_t *master_mac,
const alox_EspNowRestart *req) {
const uint8_t *own = esp_now_core_own_mac();
uint32_t my_id = own[5];
if (req->client_id != 0 && req->client_id != my_id) {
if (!client_id_matches(req->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -273,14 +351,14 @@ static void handle_restart(const uint8_t *master_mac,
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "RESTART from master %s (id=%lu)", mac_str, (unsigned long)my_id);
ESP_LOGI(TAG, "RESTART from master %s (id=%lu)", mac_str,
(unsigned long)my_client_id());
pod_schedule_restart();
}
static void handle_battery_query(const uint8_t *master_mac,
const alox_EspNowBatteryQuery *query) {
uint32_t my_id = esp_now_core_own_mac()[5];
if (query->client_id != 0 && query->client_id != my_id) {
if (!client_id_matches(query->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -291,8 +369,7 @@ static void handle_battery_query(const uint8_t *master_mac,
static void handle_led_ring(const uint8_t *master_mac,
const alox_EspNowLedRing *msg) {
uint32_t my_id = esp_now_core_own_mac()[5];
if (msg->client_id != 0 && msg->client_id != my_id) {
if (!client_id_matches(msg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -313,13 +390,12 @@ static void handle_led_ring(const uint8_t *master_mac,
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "LED_RING mode %lu from master %s (id=%lu)",
(unsigned long)req.mode, mac_str, (unsigned long)my_id);
(unsigned long)req.mode, mac_str, (unsigned long)my_client_id());
cmd_led_ring_apply(&req);
}
static void handle_find_me(const uint8_t *master_mac, const alox_EspNowFindMe *req) {
uint32_t my_id = esp_now_core_own_mac()[5];
if (req->client_id != 0 && req->client_id != my_id) {
if (!client_id_matches(req->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -327,14 +403,14 @@ static void handle_find_me(const uint8_t *master_mac, const alox_EspNowFindMe *r
}
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "FIND_ME from master %s (id=%lu)", mac_str, (unsigned long)my_id);
ESP_LOGI(TAG, "FIND_ME from master %s (id=%lu)", mac_str,
(unsigned long)my_client_id());
led_ring_find_me();
}
static void handle_accel_stream(const uint8_t *master_mac,
const alox_EspNowAccelStream *cfg) {
uint32_t my_id = esp_now_core_own_mac()[5];
if (cfg->client_id != 0 && cfg->client_id != my_id) {
if (!client_id_matches(cfg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -344,13 +420,12 @@ static void handle_accel_stream(const uint8_t *master_mac,
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "accel stream %s from master %s (id=%lu)",
cfg->enable ? "on" : "off", mac_str, (unsigned long)my_id);
cfg->enable ? "on" : "off", mac_str, (unsigned long)my_client_id());
}
static void handle_tap_notify(const uint8_t *master_mac,
const alox_EspNowTapNotify *cfg) {
uint32_t my_id = esp_now_core_own_mac()[5];
if (cfg->client_id != 0 && cfg->client_id != my_id) {
if (!client_id_matches(cfg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -364,13 +439,12 @@ static void handle_tap_notify(const uint8_t *master_mac,
ESP_LOGI(TAG,
"tap notify single=%d double=%d triple=%d from master %s (id=%lu)",
cfg->single, cfg->double_tap, cfg->triple, mac_str,
(unsigned long)my_id);
(unsigned long)my_client_id());
}
static void handle_accel_deadzone(const uint8_t *master_mac,
const alox_EspNowAccelDeadzone *cfg) {
uint32_t my_id = esp_now_core_own_mac()[5];
if (cfg->client_id != 0 && cfg->client_id != my_id) {
if (!client_id_matches(cfg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -380,7 +454,7 @@ static void handle_accel_deadzone(const uint8_t *master_mac,
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG,
"accel deadzone from master %s: %lu LSB id=%lu (sensor %s)", mac_str,
(unsigned long)cfg->deadzone, (unsigned long)my_id,
(unsigned long)cfg->deadzone, (unsigned long)my_client_id(),
bma456_is_ready() ? "ok" : "not installed");
bma456_set_accel_deadzone(cfg->deadzone);
if (pod_settings_save_accel_deadzone(cfg->deadzone) != ESP_OK) {
@@ -391,8 +465,7 @@ static void handle_accel_deadzone(const uint8_t *master_mac,
static void handle_tap_config(const uint8_t *master_mac,
const alox_EspNowTapConfig *msg) {
uint32_t my_id = esp_now_core_own_mac()[5];
if (msg->client_id != 0 && msg->client_id != my_id) {
if (!client_id_matches(msg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
@@ -415,7 +488,7 @@ static void handle_tap_config(const uint8_t *master_mac,
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG,
"tap config from master %s: sens=%u id=%lu (sensor %s)", mac_str,
(unsigned)cfg.tap_sens_thres, (unsigned long)my_id,
(unsigned)cfg.tap_sens_thres, (unsigned long)my_client_id(),
bma456_is_ready() ? "ok" : "not installed");
if (!bma456_tap_config_valid(&cfg)) {
@@ -431,6 +504,21 @@ static void handle_tap_config(const uint8_t *master_mac,
}
}
static void handle_assign_id(const uint8_t *master_mac,
const alox_EspNowAssignId *msg) {
if (!from_joined_master(master_mac)) {
return;
}
apply_assigned_id(msg->assigned_id);
}
static void handle_clear_id_binding(const uint8_t *master_mac) {
if (!from_joined_master(master_mac)) {
return;
}
clear_id_binding_local();
}
static void handle_discover(const uint8_t *sender_mac,
const alox_EspNowDiscover *discover) {
if (discover->network != esp_now_core_network()) {
@@ -464,11 +552,12 @@ static void handle_discover(const uint8_t *sender_mac,
char mac_str[18];
esp_now_core_mac_to_str(sender_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "joined network %u, master %s", (unsigned)discover->network,
mac_str);
ESP_LOGI(TAG, "joined network %u, master %s (preferred_id=%lu)",
(unsigned)discover->network, mac_str,
(unsigned long)preferred_id_for_master());
queue_tx(SLAVE_TX_SLAVE_INFO);
queue_tx(SLAVE_TX_BATTERY);
/* Battery after ASSIGN_ID so reports carry the definitive client_id. */
}
static void check_master_timeout(void) {
@@ -489,14 +578,14 @@ static void check_master_timeout(void) {
static void accel_stream_task(void *param) {
(void)param;
const uint8_t *own = esp_now_core_own_mac();
ESP_LOGI(TAG, "accel stream task (interval %u ms)",
(unsigned)ESPNOW_ACCEL_INTERVAL_MS);
while (1) {
vTaskDelay(pdMS_TO_TICKS(ESPNOW_ACCEL_INTERVAL_MS));
if (!s_joined || !s_accel_stream_enabled || !bma456_is_ready()) {
if (!s_joined || s_assigned_id == 0 || !s_accel_stream_enabled ||
!bma456_is_ready()) {
continue;
}
int16_t x = 0;
@@ -505,13 +594,13 @@ static void accel_stream_task(void *param) {
if (bma456_read_accel(&x, &y, &z) != ESP_OK) {
continue;
}
(void)send_accel_sample(s_master_mac, own[5], x, y, z);
(void)send_accel_sample(s_master_mac, s_assigned_id, x, y, z);
}
}
static void on_bma456_tap(bma456_tap_kind_t kind, void *ctx) {
(void)ctx;
if (!s_joined) {
if (!s_joined || s_assigned_id == 0) {
return;
}
bool enabled = false;
@@ -531,7 +620,7 @@ static void on_bma456_tap(bma456_tap_kind_t kind, void *ctx) {
if (!enabled) {
return;
}
(void)send_tap_event(s_master_mac, esp_now_core_own_mac()[5], (uint32_t)kind);
(void)send_tap_event(s_master_mac, s_assigned_id, (uint32_t)kind);
}
static void heartbeat_task(void *param) {
@@ -654,6 +743,12 @@ void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
handle_restart(info->src_addr, &msg.payload.restart);
}
break;
case alox_EspNowMessage_assign_id_tag:
handle_assign_id(info->src_addr, &msg.payload.assign_id);
break;
case alox_EspNowMessage_clear_id_binding_tag:
handle_clear_id_binding(info->src_addr);
break;
case alox_EspNowMessage_ota_start_tag:
if (from_joined_master(info->src_addr)) {
ota_espnow_slave_on_start(info->src_addr, &msg.payload.ota_start);
@@ -668,6 +763,7 @@ void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
esp_err_t esp_now_slave_start(void) {
reset_join();
load_id_binding();
s_tx_queue = xQueueCreate(4, sizeof(slave_tx_op_t));
if (s_tx_queue == NULL) {