Stream slave accel via ESP-NOW with master snapshot cache.
Slaves push BMA456 samples at 16ms when enabled; the master caches per client and exposes ACCEL_SNAPSHOT and ACCEL_STREAM over UART. goTool adds dashboard stream controls, HTTP accel-stream routes, and an external WebSocket API with per-connection receive/interval and slave stream commands. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
#define ESPNOW_CLIENT_TIMEOUT_MS \
|
||||
(ESPNOW_HEARTBEAT_INTERVAL_MS * ESPNOW_HEARTBEAT_MISS_COUNT)
|
||||
#define SLAVE_MASTER_LOST_MS (ESPNOW_HEARTBEAT_INTERVAL_MS * 5)
|
||||
#define ESPNOW_ACCEL_INTERVAL_MS 16
|
||||
|
||||
static const uint8_t ESPNOW_BCAST[ESP_NOW_ETH_ALEN] = {0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff};
|
||||
@@ -39,6 +40,7 @@ static app_config_t s_config;
|
||||
static uint8_t s_wifi_channel;
|
||||
static uint8_t s_own_mac[ESP_NOW_ETH_ALEN];
|
||||
static bool s_slave_joined;
|
||||
static bool s_accel_stream_enabled;
|
||||
static uint8_t s_master_mac[ESP_NOW_ETH_ALEN];
|
||||
static uint32_t s_last_discover_ms;
|
||||
|
||||
@@ -111,6 +113,18 @@ static esp_err_t send_message(const uint8_t *dest_mac,
|
||||
return send_message_ex(dest_mac, msg, false);
|
||||
}
|
||||
|
||||
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;
|
||||
msg.type = alox_EspNowMessageType_ESPNOW_ACCEL_SAMPLE;
|
||||
msg.which_payload = alox_EspNowMessage_accel_sample_tag;
|
||||
msg.payload.accel_sample.slave_id = slave_id;
|
||||
msg.payload.accel_sample.x = x;
|
||||
msg.payload.accel_sample.y = y;
|
||||
msg.payload.accel_sample.z = z;
|
||||
return send_message(dest_mac, &msg);
|
||||
}
|
||||
|
||||
static esp_err_t send_message_ex(const uint8_t *dest_mac,
|
||||
const alox_EspNowMessage *msg, bool wait_done) {
|
||||
uint8_t buf[ESPNOW_PB_MAX_SIZE];
|
||||
@@ -151,6 +165,18 @@ static esp_err_t send_message_ex(const uint8_t *dest_mac,
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t send_accel_stream(const uint8_t *dest_mac, uint32_t client_id,
|
||||
bool enable) {
|
||||
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||
|
||||
msg.type = alox_EspNowMessageType_ESPNOW_SET_ACCEL_STREAM;
|
||||
msg.which_payload = alox_EspNowMessage_accel_stream_tag;
|
||||
msg.payload.accel_stream.enable = enable;
|
||||
msg.payload.accel_stream.client_id = client_id;
|
||||
|
||||
return send_message(dest_mac, &msg);
|
||||
}
|
||||
|
||||
static esp_err_t send_accel_deadzone(const uint8_t *dest_mac, uint32_t client_id,
|
||||
uint32_t deadzone) {
|
||||
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||
@@ -338,6 +364,25 @@ esp_err_t esp_now_comm_send_unicast_test(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_now_comm_send_accel_stream(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
uint32_t client_id, bool enable) {
|
||||
if (mac == NULL || !s_config.master) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
char mac_str[18];
|
||||
mac_to_str(mac, mac_str, sizeof(mac_str));
|
||||
esp_err_t err = send_accel_stream(mac, client_id, enable);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "unicast SET_ACCEL_STREAM to %s: %s client_id=%lu", mac_str,
|
||||
enable ? "on" : "off", (unsigned long)client_id);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "unicast SET_ACCEL_STREAM to %s failed: %s", mac_str,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_now_comm_send_accel_deadzone(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
uint32_t client_id, uint32_t deadzone) {
|
||||
if (mac == NULL || !s_config.master) {
|
||||
@@ -377,6 +422,7 @@ static void send_presence(const uint8_t *dest_mac,
|
||||
|
||||
static void slave_reset_join(void) {
|
||||
s_slave_joined = false;
|
||||
s_accel_stream_enabled = false;
|
||||
memset(s_master_mac, 0, sizeof(s_master_mac));
|
||||
s_last_discover_ms = 0;
|
||||
}
|
||||
@@ -426,6 +472,25 @@ static void handle_slave_find_me(const uint8_t *master_mac,
|
||||
led_ring_find_me();
|
||||
}
|
||||
|
||||
static void handle_slave_accel_stream(const uint8_t *master_mac,
|
||||
const alox_EspNowAccelStream *cfg) {
|
||||
uint32_t my_id = s_own_mac[5];
|
||||
|
||||
if (cfg->client_id != 0 && cfg->client_id != my_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_slave_joined && !mac_equal(master_mac, s_master_mac)) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_accel_stream_enabled = cfg->enable;
|
||||
char mac_str[18];
|
||||
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);
|
||||
}
|
||||
|
||||
static void handle_slave_accel_deadzone(const uint8_t *master_mac,
|
||||
const alox_EspNowAccelDeadzone *cfg) {
|
||||
uint32_t my_id = s_own_mac[5];
|
||||
@@ -453,6 +518,23 @@ static void handle_slave_accel_deadzone(const uint8_t *master_mac,
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_master_accel_sample(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
const alox_EspNowAccelSample *sample) {
|
||||
if (sample == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = client_registry_update_accel(
|
||||
mac, sample->slave_id, (int16_t)sample->x, (int16_t)sample->y,
|
||||
(int16_t)sample->z);
|
||||
if (err == ESP_ERR_NOT_FOUND) {
|
||||
return;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "accel sample id mismatch from %02x:…:%02x", mac[0], mac[5]);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_client_presence(const alox_EspNowSlavePresence *presence,
|
||||
const uint8_t mac[CLIENT_MAC_LEN]) {
|
||||
if (presence->network != s_config.network) {
|
||||
@@ -533,6 +615,30 @@ static void slave_check_master_timeout(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void slave_accel_stream_task(void *param) {
|
||||
(void)param;
|
||||
|
||||
ESP_LOGI(TAG, "slave accel stream task (interval %u ms)",
|
||||
(unsigned)ESPNOW_ACCEL_INTERVAL_MS);
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(ESPNOW_ACCEL_INTERVAL_MS));
|
||||
|
||||
if (!s_slave_joined || !s_accel_stream_enabled || !bma456_is_ready()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int16_t x = 0;
|
||||
int16_t y = 0;
|
||||
int16_t z = 0;
|
||||
if (bma456_read_accel(&x, &y, &z) != ESP_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
(void)send_accel_sample(s_master_mac, s_own_mac[5], x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
static void slave_heartbeat_task(void *param) {
|
||||
(void)param;
|
||||
|
||||
@@ -592,6 +698,12 @@ static void espnow_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data,
|
||||
case alox_EspNowMessage_accel_deadzone_tag:
|
||||
handle_slave_accel_deadzone(info->src_addr, &msg.payload.accel_deadzone);
|
||||
break;
|
||||
case alox_EspNowMessage_accel_stream_tag:
|
||||
if (!s_slave_joined || !mac_equal(info->src_addr, s_master_mac)) {
|
||||
break;
|
||||
}
|
||||
handle_slave_accel_stream(info->src_addr, &msg.payload.accel_stream);
|
||||
break;
|
||||
case alox_EspNowMessage_find_me_tag:
|
||||
if (!s_slave_joined || !mac_equal(info->src_addr, s_master_mac)) {
|
||||
break;
|
||||
@@ -639,6 +751,12 @@ static void espnow_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data,
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.which_payload == alox_EspNowMessage_accel_sample_tag) {
|
||||
ensure_peer(info->src_addr);
|
||||
handle_master_accel_sample(info->src_addr, &msg.payload.accel_sample);
|
||||
return;
|
||||
}
|
||||
|
||||
const alox_EspNowSlavePresence *presence = esp_now_proto_get_presence(&msg);
|
||||
if (presence != NULL) {
|
||||
/* Registry key is the ESP-NOW sender MAC, not the optional protobuf mac field. */
|
||||
@@ -739,6 +857,11 @@ esp_err_t esp_now_comm_init(const app_config_t *config) {
|
||||
ESP_LOGE(TAG, "failed to create heartbeat task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (xTaskCreate(slave_accel_stream_task, "espnow_accel", 4096, NULL, 5,
|
||||
NULL) != pdPASS) {
|
||||
ESP_LOGE(TAG, "failed to create accel stream task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
Reference in New Issue
Block a user