Send ESP-NOW accel only on deadzone change and harden BLE+WiFi OTA coex.
Quiet pods no longer spam 100 Hz samples; mute wifi logs and throttle BLE notifies during slave distribution so the wifi task stack does not panic. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -315,7 +315,7 @@ Alle Master-Sends laufen über `send_message()` / `send_message_ex()`:
|
||||
|
||||
| Nachricht | Task / Trigger | Master-Ziel |
|
||||
|-----------|----------------|-------------|
|
||||
| `ESPNOW_ACCEL_SAMPLE` | `slave_accel_stream_task` 10 ms, 1 ms slots via Discover `master_time_us` | `client_registry_update_accel()` |
|
||||
| `ESPNOW_ACCEL_SAMPLE` | `accel_stream_task` poll 10 ms + deadzone (nur bei Änderung), 1 ms slots via Discover `master_time_us` | `client_registry_update_accel()` |
|
||||
| `ESPNOW_TAP_EVENT` | BMA456-IRQ → `on_bma456_tap` | `client_registry_update_tap()` |
|
||||
| `ESPNOW_BATTERY_REPORT` | Heartbeat + nach Join | `client_registry_update_battery()` |
|
||||
| `ESPNOW_SLAVE_INFO` / `HEARTBEAT` | `slave_tx_task` / Heartbeat-Task | `client_registry_heartbeat()` |
|
||||
@@ -366,7 +366,7 @@ flowchart TB
|
||||
| `espnow_mon` | Master | 1 s Timeout + Master-Battery |
|
||||
| `espnow_stx` | Slave | Queue: SLAVE_INFO, Battery nach Join |
|
||||
| `espnow_hb` | Slave | 1 s Heartbeat + 30 s Battery |
|
||||
| `espnow_accel` | Slave | 10 ms Accel in ID-Slot (1 ms) wenn Stream an |
|
||||
| `espnow_accel` | Slave | Accel poll 10 ms / TX bei Deadzone-Änderung, ID-Slot 1 ms wenn Stream an |
|
||||
| `bma456_poll` | Beide | 10 Hz (`bosch456.c`) |
|
||||
| `vTaskLedRing` | Beide | LED-Ring-Befehle |
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ UART_CMD_REQ(&uart_msg, alox_UartMessage_accel_deadzone_request_tag, accel_deadz
|
||||
| `ESPNOW_HEARTBEAT` | S→M | `heartbeat` | Keepalive 1 s |
|
||||
| `ESPNOW_SET_ACCEL_DEADZONE` | M→S | `accel_deadzone` | Deadzone LSB |
|
||||
| `ESPNOW_SET_TAP_CONFIG` | M→S | `tap_config` | Multitap-Parameter + NVS |
|
||||
| `ESPNOW_SET_ACCEL_STREAM` | M→S | `accel_stream` | Stream ~10 ms (best-effort) |
|
||||
| `ESPNOW_SET_ACCEL_STREAM` | M→S | `accel_stream` | Stream poll ~10 ms, TX on deadzone change |
|
||||
| `ESPNOW_ACCEL_SAMPLE` | S→M | `accel_sample` | x/y/z Roh-LSB |
|
||||
| `ESPNOW_SET_TAP_NOTIFY` | M→S | `tap_notify` | Tap-Arten filtern |
|
||||
| `ESPNOW_TAP_EVENT` | S→M | `tap_event` | kind 1/2/3 |
|
||||
@@ -199,7 +199,7 @@ UART_CMD_REQ(&uart_msg, alox_UartMessage_accel_deadzone_request_tag, accel_deadz
|
||||
| Heartbeat | 1000 ms |
|
||||
| Client-Timeout (Master) | 3 × Heartbeat = 3 s → `available=false` |
|
||||
| Master-Verlust (Slave) | 5 s ohne Discover |
|
||||
| Accel-Stream | 10 ms (best-effort, kein ACK-Wait; = FreeRTOS-Tick) |
|
||||
| Accel-Stream | Poll 10 ms; TX nur bei Deadzone-Änderung (best-effort) |
|
||||
| Batterie-Report | 30 s (+ einmal 150 ms nach Join) |
|
||||
| Echo-Ping Timeout (Master) | 500 ms (`ESPNOW_ECHO_PING_TIMEOUT_MS`) |
|
||||
|
||||
@@ -262,7 +262,7 @@ Nur auf dem **Master** registriert (`powerpod.c`). IDs aus `MessageType` in `uar
|
||||
|
||||
- Optional `period_ms` (10–16): BLE-Host-Push-Intervall für `BLE_STREAM_TICK` (UART ignoriert; Default 10).
|
||||
- Master setzt `client_registry_set_accel_stream` und sendet `ESPNOW_SET_ACCEL_STREAM`.
|
||||
- Slave-Task `slave_accel_stream_task` sendet `ESPNOW_ACCEL_SAMPLE` alle 10 ms (best-effort / `send_best_effort`).
|
||||
- Slave-Task `accel_stream_task` pollt alle 10 ms; sendet `ESPNOW_ACCEL_SAMPLE` nur wenn eine Achse die Accel-Deadzone seit dem letzten gesendeten Sample überschreitet (best-effort / `send_best_effort`; Baseline-Reset beim Stream-Enable).
|
||||
|
||||
### 7.5 TAP_NOTIFY (27)
|
||||
|
||||
@@ -371,6 +371,7 @@ Implementierung: `ota_uart.c`, Steuerung `cmd/cmd_ota.c`. Gleiches Protokoll üb
|
||||
- Inaktive Partition: `esp_ota_get_next_update_partition()`.
|
||||
- LED-Ring zeigt Fortschritt (blau Schreiben, grün Verteilung).
|
||||
- BLE: ATT MTU ≥ ~219 (preferred 517); große Antworten werden über mehrere Notifies gestückelt; `BLE_STREAM_TICK` pausiert während `ota_session_busy()`.
|
||||
- **BLE+ESP-NOW Coex:** Tag `wifi` auf `ESP_LOG_NONE` (WiFi-Task-Stack + Error-Logs → Overflow/Panic); während ESP-NOW-Dist `OTA_STATUS` DISTRIBUTING max. ~4/s und kürzere BLE-Notify-Retries.
|
||||
- **App-Rollback** (`CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE`): neues Image bootet als `PENDING_VERIFY`. `ota_confirm` markiert nach ~8 s stabilem Lauf `mark_app_valid`; Crash/Reset davor → Bootloader rollt auf die vorherige App zurück. Während `PENDING_VERIFY` ist ein neues `OTA_START` blockiert.
|
||||
|
||||
### 8.2 ESP-NOW-OTA (Master → Slaves)
|
||||
@@ -382,7 +383,7 @@ Implementierung: `ota_uart.c`, Steuerung `cmd/cmd_ota.c`. Gleiches Protokoll üb
|
||||
3. `ESPNOW_OTA_END` → Boot-Partition setzen + automatischer Reboot
|
||||
4. Slave antwortet `ESPNOW_OTA_STATUS` (mit `send_message_ex` + Semaphore auf Master)
|
||||
|
||||
Nach Erfolg: Master und Slaves starten automatisch neu (Rollback-Confirm siehe 8.1).
|
||||
Während Distribution: keine Client-Timeouts in der Registry (Heartbeats werden im recv_cb verworfen). Nach Erfolg: Master und Slaves starten automatisch neu (Rollback-Confirm siehe 8.1).
|
||||
|
||||
### 8.3 OTA_SLAVE_PROGRESS (21)
|
||||
|
||||
|
||||
+12
-2
@@ -3,6 +3,7 @@
|
||||
#include "esp_timer.h"
|
||||
#include "host_transport.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "ota_espnow.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -26,9 +27,12 @@ static const char *DEVICE_NAME = "Powerpod";
|
||||
/** Prefer large ATT MTU so CLIENT_INFO / BATTERY fit in fewer notify chunks. */
|
||||
#define BLE_PREFERRED_ATT_MTU 517
|
||||
#define BLE_NOTIFY_CHUNK_RETRIES 24
|
||||
/** During ESP-NOW OTA dist, fail notify sooner so WiFi/ESP-NOW keeps airtime. */
|
||||
#define BLE_NOTIFY_CHUNK_RETRIES_OTA_DIST 4
|
||||
#define BLE_NOTIFY_CHUNK_RETRY_MS 5
|
||||
/** Pace multi-chunk notifies so the central parser sees contiguous frame bytes. */
|
||||
#define BLE_NOTIFY_CHUNK_GAP_MS 8
|
||||
#define BLE_NOTIFY_CHUNK_GAP_OTA_DIST_MS 2
|
||||
|
||||
/*
|
||||
* Nordic UART style:
|
||||
@@ -73,7 +77,10 @@ uint16_t ble_host_max_payload(void) {
|
||||
* One ATT notification (≤ MTU-3). Retries on transient NimBLE buffer pressure.
|
||||
*/
|
||||
static esp_err_t ble_notify_chunk(const uint8_t *data, uint16_t len) {
|
||||
for (int attempt = 0; attempt < BLE_NOTIFY_CHUNK_RETRIES; attempt++) {
|
||||
const int max_retries = ota_espnow_distribution_active()
|
||||
? BLE_NOTIFY_CHUNK_RETRIES_OTA_DIST
|
||||
: BLE_NOTIFY_CHUNK_RETRIES;
|
||||
for (int attempt = 0; attempt < max_retries; attempt++) {
|
||||
struct os_mbuf *om = ble_hs_mbuf_from_flat(data, len);
|
||||
if (om == NULL) {
|
||||
vTaskDelay(pdMS_TO_TICKS(BLE_NOTIFY_CHUNK_RETRY_MS));
|
||||
@@ -140,7 +147,10 @@ static esp_err_t ble_host_tx(const uint8_t *frame, size_t frame_len) {
|
||||
}
|
||||
off += n;
|
||||
if (off < frame_len) {
|
||||
vTaskDelay(pdMS_TO_TICKS(BLE_NOTIFY_CHUNK_GAP_MS));
|
||||
const uint32_t gap_ms = ota_espnow_distribution_active()
|
||||
? BLE_NOTIFY_CHUNK_GAP_OTA_DIST_MS
|
||||
: BLE_NOTIFY_CHUNK_GAP_MS;
|
||||
vTaskDelay(pdMS_TO_TICKS(gap_ms));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-1
@@ -2,7 +2,7 @@
|
||||
* BMA456H integration for Powerpod (ESP-IDF I2C master + Bosch SensorAPI).
|
||||
*
|
||||
* Polls accelerometer at 10 Hz; tap events arrive on BMA456_INT_GPIO.
|
||||
* Accel logging is filtered in software (deadzone); slaves stream samples via ESP-NOW.
|
||||
* Accel log + ESP-NOW stream share a software deadzone filter (emit on change).
|
||||
*/
|
||||
|
||||
#include "bosch456.h"
|
||||
@@ -33,6 +33,10 @@ static int16_t s_last_x;
|
||||
static int16_t s_last_y;
|
||||
static int16_t s_last_z;
|
||||
static bool s_have_last_sample;
|
||||
static int16_t s_stream_last_x;
|
||||
static int16_t s_stream_last_y;
|
||||
static int16_t s_stream_last_z;
|
||||
static bool s_stream_have_last;
|
||||
|
||||
static volatile bool s_int_pending;
|
||||
static SemaphoreHandle_t s_accel_mutex;
|
||||
@@ -119,6 +123,7 @@ bool bma456_is_ready(void) { return s_bma456_ready; }
|
||||
void bma456_set_accel_deadzone(uint32_t deadzone_lsb) {
|
||||
s_accel_deadzone = deadzone_lsb;
|
||||
s_have_last_sample = false;
|
||||
s_stream_have_last = false;
|
||||
if (s_bma456_ready) {
|
||||
ESP_LOGI(TAG, "accel deadzone %lu LSB", (unsigned long)deadzone_lsb);
|
||||
}
|
||||
@@ -126,6 +131,23 @@ void bma456_set_accel_deadzone(uint32_t deadzone_lsb) {
|
||||
|
||||
uint32_t bma456_get_accel_deadzone(void) { return s_accel_deadzone; }
|
||||
|
||||
void bma456_accel_stream_reset_baseline(void) { s_stream_have_last = false; }
|
||||
|
||||
bool bma456_accel_stream_take_if_changed(int16_t x, int16_t y, int16_t z) {
|
||||
if (s_stream_have_last) {
|
||||
if (axis_delta(x, s_stream_last_x) <= (int16_t)s_accel_deadzone &&
|
||||
axis_delta(y, s_stream_last_y) <= (int16_t)s_accel_deadzone &&
|
||||
axis_delta(z, s_stream_last_z) <= (int16_t)s_accel_deadzone) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
s_stream_last_x = x;
|
||||
s_stream_last_y = y;
|
||||
s_stream_last_z = z;
|
||||
s_stream_have_last = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bma456_tap_config_valid(const bma456_tap_config_t *cfg) {
|
||||
if (cfg == NULL) {
|
||||
return false;
|
||||
|
||||
+11
-1
@@ -19,7 +19,7 @@
|
||||
/** Sensor interrupt line → ESP32 GPIO (active high, rising edge). */
|
||||
#define BMA456_INT_GPIO 10
|
||||
|
||||
/** Software filter: log accel only when |axis - last| > deadzone (raw LSB). */
|
||||
/** Software filter: emit accel only when |axis - last| > deadzone (raw LSB). */
|
||||
#define BMA456_DEFAULT_ACCEL_DEADZONE 100u
|
||||
|
||||
/**
|
||||
@@ -81,6 +81,16 @@ bool bma456_is_ready(void);
|
||||
void bma456_set_accel_deadzone(uint32_t deadzone_lsb);
|
||||
uint32_t bma456_get_accel_deadzone(void);
|
||||
|
||||
/**
|
||||
* ESP-NOW stream filter: true if sample exceeds deadzone vs last streamed
|
||||
* sample (or no baseline yet). On true, commits (x,y,z) as stream baseline.
|
||||
* Independent of the log filter baseline.
|
||||
*/
|
||||
bool bma456_accel_stream_take_if_changed(int16_t x, int16_t y, int16_t z);
|
||||
|
||||
/** Force next stream take to accept (call when accel stream is enabled). */
|
||||
void bma456_accel_stream_reset_baseline(void);
|
||||
|
||||
/** Log accel when any axis moved more than deadzone since last reported sample. */
|
||||
void bma456_report_accel_if_changed(int16_t x, int16_t y, int16_t z);
|
||||
|
||||
|
||||
+36
-1
@@ -5,8 +5,10 @@
|
||||
#include "pod_reboot.h"
|
||||
#include "uart_cmd.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/idf_additions.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -32,6 +34,14 @@ typedef struct {
|
||||
uint32_t written;
|
||||
int slot;
|
||||
} ota_dist_job_t;
|
||||
|
||||
/** Cap host DISTRIBUTING pushes (BLE notifies compete with ESP-NOW OTA TX). */
|
||||
#define OTA_DIST_NOTIFY_MIN_US (250 * 1000ull)
|
||||
|
||||
static int64_t s_last_dist_agg_us;
|
||||
static int64_t s_last_dist_slave_us;
|
||||
static uint32_t s_last_dist_agg_bytes = UINT32_MAX;
|
||||
|
||||
static void send_ota_status(ota_uart_status_t status, uint32_t err_code) {
|
||||
alox_UartMessage response;
|
||||
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
|
||||
@@ -64,15 +74,33 @@ static void send_ota_distributing(uint32_t kind, uint32_t bytes_done,
|
||||
|
||||
static void ota_dist_aggregate(uint32_t bytes_done, uint32_t total_bytes,
|
||||
uint8_t slave_count) {
|
||||
(void)slave_count;
|
||||
led_ring_show_ota_progress(bytes_done, total_bytes, OTA_LED_ESPNOW_TX_R,
|
||||
OTA_LED_ESPNOW_TX_G, OTA_LED_ESPNOW_TX_B);
|
||||
|
||||
const int64_t now = esp_timer_get_time();
|
||||
const bool first = (s_last_dist_agg_bytes == UINT32_MAX);
|
||||
const bool boundary =
|
||||
(bytes_done == 0) || (total_bytes > 0 && bytes_done >= total_bytes);
|
||||
const bool paced =
|
||||
(now - s_last_dist_agg_us) >= (int64_t)OTA_DIST_NOTIFY_MIN_US;
|
||||
if (!first && !boundary && !paced) {
|
||||
return;
|
||||
}
|
||||
s_last_dist_agg_us = now;
|
||||
s_last_dist_agg_bytes = bytes_done;
|
||||
send_ota_distributing(OTA_DIST_AGGREGATE, bytes_done, (uint32_t)slave_count);
|
||||
}
|
||||
|
||||
static void ota_dist_per_slave(uint32_t slave_id, uint32_t bytes_done,
|
||||
uint32_t total_bytes) {
|
||||
(void)total_bytes;
|
||||
/* Host also polls OTA_SLAVE_PROGRESS; keep per-slave pushes rare on BLE. */
|
||||
const int64_t now = esp_timer_get_time();
|
||||
if ((now - s_last_dist_slave_us) < (int64_t)OTA_DIST_NOTIFY_MIN_US &&
|
||||
bytes_done > 0) {
|
||||
return;
|
||||
}
|
||||
s_last_dist_slave_us = now;
|
||||
send_ota_distributing(OTA_DIST_PER_SLAVE, bytes_done, slave_id);
|
||||
}
|
||||
|
||||
@@ -225,6 +253,9 @@ static void ota_distribute_task(void *param) {
|
||||
|
||||
led_ring_show_ota_progress(0, image_size, OTA_LED_ESPNOW_TX_R, OTA_LED_ESPNOW_TX_G,
|
||||
OTA_LED_ESPNOW_TX_B);
|
||||
s_last_dist_agg_us = 0;
|
||||
s_last_dist_slave_us = 0;
|
||||
s_last_dist_agg_bytes = UINT32_MAX;
|
||||
send_ota_distributing(OTA_DIST_AGGREGATE, 0, 0);
|
||||
|
||||
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
|
||||
@@ -315,6 +346,10 @@ static void ota_start_espnow_task(void *param) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_last_dist_agg_us = 0;
|
||||
s_last_dist_slave_us = 0;
|
||||
s_last_dist_agg_bytes = UINT32_MAX;
|
||||
|
||||
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
|
||||
if (err != ESP_OK) {
|
||||
send_ota_failed(42);
|
||||
|
||||
@@ -280,6 +280,14 @@ esp_err_t esp_now_core_init_radio(uint8_t channel) {
|
||||
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE));
|
||||
|
||||
/* WiFi task stack is tiny; ERROR logs from pp/coex (e.g. under BLE+ESP-NOW
|
||||
* OTA) call esp_log_write on that stack and can overflow → panic reboot.
|
||||
* Mute after esp_wifi_init (which resets the wifi tag to default level). */
|
||||
esp_log_level_set("wifi", ESP_LOG_NONE);
|
||||
esp_log_level_set("wifi_init", ESP_LOG_WARN);
|
||||
esp_log_level_set("coex", ESP_LOG_WARN);
|
||||
esp_log_level_set("NimBLE", ESP_LOG_WARN);
|
||||
|
||||
ESP_ERROR_CHECK(esp_read_mac(s_own_mac, ESP_MAC_WIFI_STA));
|
||||
s_wifi_channel = channel;
|
||||
return ESP_OK;
|
||||
|
||||
@@ -780,7 +780,11 @@ static void monitor_task(void *param) {
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(ESPNOW_HEARTBEAT_INTERVAL_MS));
|
||||
client_registry_check_timeouts(ESPNOW_CLIENT_TIMEOUT_MS);
|
||||
/* During ESP-NOW OTA dist the recv_cb drops heartbeats; do not wipe
|
||||
* targets from the registry while they are still being updated. */
|
||||
if (!ota_espnow_distribution_active()) {
|
||||
client_registry_check_timeouts(ESPNOW_CLIENT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
uint32_t t = esp_now_core_now_ms();
|
||||
if (t - last_local_battery_ms >= ESPNOW_BATTERY_INTERVAL_MS) {
|
||||
|
||||
+10
-2
@@ -26,7 +26,8 @@
|
||||
/** While master or slave OTA is in progress (discover may be sparse). */
|
||||
#define SLAVE_MASTER_OTA_GRACE_MS 300000u
|
||||
/**
|
||||
* Accel stream cadence (10 ms frame). TX path is selectable (INPUT_SEND_MODE).
|
||||
* Accel stream poll cadence (10 ms frame). TX only when sample exceeds the
|
||||
* BMA456 deadzone vs last sent sample. TX path is selectable (INPUT_SEND_MODE).
|
||||
* With master Discover.master_time_us, slaves phase into 1 ms slots:
|
||||
* offset_ms = (assigned_id - 1) % 8. Slot wait uses esp_timer (FreeRTOS tick
|
||||
* is 10 ms — too coarse for intra-frame offsets).
|
||||
@@ -516,6 +517,9 @@ static void handle_accel_stream(const uint8_t *master_mac,
|
||||
return;
|
||||
}
|
||||
s_accel_stream_enabled = cfg->enable;
|
||||
if (cfg->enable) {
|
||||
bma456_accel_stream_reset_baseline();
|
||||
}
|
||||
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)",
|
||||
@@ -743,7 +747,8 @@ static void accel_stream_task(void *param) {
|
||||
(void)param;
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"accel stream task (interval %u ms, %u×%llu us slots, mode selectable)",
|
||||
"accel stream task (poll %u ms, deadzone TX, %u×%llu us slots, "
|
||||
"mode selectable)",
|
||||
(unsigned)ESPNOW_ACCEL_INTERVAL_MS, (unsigned)ESPNOW_ACCEL_SLOT_COUNT,
|
||||
(unsigned long long)ESPNOW_ACCEL_SLOT_US);
|
||||
|
||||
@@ -759,6 +764,9 @@ static void accel_stream_task(void *param) {
|
||||
if (bma456_read_accel(&x, &y, &z) != ESP_OK) {
|
||||
continue;
|
||||
}
|
||||
if (!bma456_accel_stream_take_if_changed(x, y, z)) {
|
||||
continue;
|
||||
}
|
||||
(void)send_accel_sample(s_master_mac, s_assigned_id, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user