Add selectable ESP-NOW send path for accel/tap input.

Expose best_effort/fast/wait/reliable via UART, dashboard, and CLI so
loss vs latency can be compared under real multi-pod load.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-01 22:34:03 +01:00
co-authored by Cursor
parent 178ab46cec
commit 1adaddd6c8
25 changed files with 1178 additions and 255 deletions
+66 -7
View File
@@ -24,9 +24,9 @@
/** While master or slave OTA is in progress (discover may be sparse). */
#define SLAVE_MASTER_OTA_GRACE_MS 300000u
/**
* Best-effort gaming input cadence (loss OK; send_best_effort).
* Must be >= one FreeRTOS tick (CONFIG_FREERTOS_HZ=100 → 10 ms); shorter
* values become 0 ticks and assert in vTaskDelayUntil.
* Accel stream cadence. TX path is selectable (INPUT_SEND_MODE; default
* best_effort). Must be >= one FreeRTOS tick (CONFIG_FREERTOS_HZ=100 → 10 ms);
* shorter values become 0 ticks and assert in vTaskDelayUntil.
*/
#define ESPNOW_ACCEL_INTERVAL_MS 10
#define ESPNOW_BATTERY_INTERVAL_MS 30000
@@ -40,6 +40,9 @@ static bool s_accel_stream_enabled;
static bool s_tap_notify_single;
static bool s_tap_notify_double;
static bool s_tap_notify_triple;
/** Accel/tap TX path; default matches historical best-effort gaming input. */
static uint32_t s_input_send_mode =
(uint32_t)alox_InputSendMode_INPUT_SEND_BEST_EFFORT;
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). */
@@ -161,6 +164,21 @@ static void send_presence(const uint8_t *dest_mac, alox_EspNowMessageType type)
esp_now_core_send(dest_mac, &msg);
}
static esp_err_t send_input_message(const uint8_t *dest_mac,
const alox_EspNowMessage *msg) {
switch (s_input_send_mode) {
case (uint32_t)alox_InputSendMode_INPUT_SEND_FAST:
return esp_now_core_send_fast(dest_mac, msg);
case (uint32_t)alox_InputSendMode_INPUT_SEND_WAIT:
return esp_now_core_send_wait(dest_mac, msg);
case (uint32_t)alox_InputSendMode_INPUT_SEND_RELIABLE:
return esp_now_core_send_reliable(dest_mac, msg);
case (uint32_t)alox_InputSendMode_INPUT_SEND_BEST_EFFORT:
default:
return esp_now_core_send_best_effort(dest_mac, msg);
}
}
static esp_err_t send_accel_sample(const uint8_t *dest_mac, uint32_t slave_id,
int16_t x, int16_t y, int16_t z) {
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
@@ -170,8 +188,7 @@ static esp_err_t send_accel_sample(const uint8_t *dest_mac, uint32_t slave_id,
msg.payload.accel_sample.x = x;
msg.payload.accel_sample.y = y;
msg.payload.accel_sample.z = z;
/* Fire-and-forget: controller input prefers low latency over delivery. */
return esp_now_core_send_best_effort(dest_mac, &msg);
return send_input_message(dest_mac, &msg);
}
static esp_err_t send_tap_event(const uint8_t *dest_mac, uint32_t slave_id,
@@ -181,7 +198,7 @@ static esp_err_t send_tap_event(const uint8_t *dest_mac, uint32_t slave_id,
msg.which_payload = alox_EspNowMessage_tap_event_tag;
msg.payload.tap_event.slave_id = slave_id;
msg.payload.tap_event.kind = kind;
return esp_now_core_send_best_effort(dest_mac, &msg);
return send_input_message(dest_mac, &msg);
}
static esp_err_t send_battery_report(const uint8_t *dest_mac,
@@ -429,6 +446,43 @@ static void handle_accel_stream(const uint8_t *master_mac,
cfg->enable ? "on" : "off", mac_str, (unsigned long)my_client_id());
}
static const char *input_send_mode_name(uint32_t mode) {
switch (mode) {
case (uint32_t)alox_InputSendMode_INPUT_SEND_BEST_EFFORT:
return "best_effort";
case (uint32_t)alox_InputSendMode_INPUT_SEND_FAST:
return "fast";
case (uint32_t)alox_InputSendMode_INPUT_SEND_WAIT:
return "wait";
case (uint32_t)alox_InputSendMode_INPUT_SEND_RELIABLE:
return "reliable";
default:
return "unknown";
}
}
static void handle_input_send_mode(const uint8_t *master_mac,
const alox_EspNowInputSendMode *cfg) {
if (!client_id_matches(cfg->client_id)) {
return;
}
if (s_joined && !esp_now_core_mac_equal(master_mac, s_master_mac)) {
return;
}
if (cfg->mode > (uint32_t)alox_InputSendMode_INPUT_SEND_RELIABLE) {
ESP_LOGW(TAG, "input send mode %lu invalid (id=%lu)",
(unsigned long)cfg->mode, (unsigned long)my_client_id());
return;
}
s_input_send_mode = cfg->mode;
char mac_str[18];
esp_now_core_mac_to_str(master_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "input send mode %s (%lu) from master %s (id=%lu)",
input_send_mode_name(s_input_send_mode),
(unsigned long)s_input_send_mode, mac_str,
(unsigned long)my_client_id());
}
static void handle_tap_notify(const uint8_t *master_mac,
const alox_EspNowTapNotify *cfg) {
if (!client_id_matches(cfg->client_id)) {
@@ -585,7 +639,7 @@ static void check_master_timeout(void) {
static void accel_stream_task(void *param) {
(void)param;
ESP_LOGI(TAG, "accel stream task (interval %u ms, best-effort)",
ESP_LOGI(TAG, "accel stream task (interval %u ms, mode selectable)",
(unsigned)ESPNOW_ACCEL_INTERVAL_MS);
TickType_t last_wake = xTaskGetTickCount();
@@ -733,6 +787,11 @@ void esp_now_slave_on_recv(const esp_now_recv_info_t *info, const uint8_t *data,
handle_tap_notify(info->src_addr, &msg.payload.tap_notify);
}
break;
case alox_EspNowMessage_input_send_mode_tag:
if (from_joined_master(info->src_addr)) {
handle_input_send_mode(info->src_addr, &msg.payload.input_send_mode);
}
break;
case alox_EspNowMessage_battery_query_tag:
if (from_joined_master(info->src_addr)) {
handle_battery_query(info->src_addr, &msg.payload.battery_query);