Prefer ATT MTU 517 and chunk oversized responses so CLIENT_INFO/BATTERY work with many pods; reset the goTool parser after timeouts so partial frames do not poison the next exchange. Co-authored-by: Cursor <cursoragent@cursor.com>
197 lines
5.6 KiB
C
197 lines
5.6 KiB
C
#include "ble_stream.h"
|
|
#include "app_config.h"
|
|
#include "ble_host.h"
|
|
#include "client_registry.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "ota_session.h"
|
|
#include "uart_cmd.h"
|
|
#include "uart_messages.pb.h"
|
|
#include "uart_proto.h"
|
|
#include <string.h>
|
|
|
|
static const char *TAG = "[BLE_STREAM]";
|
|
|
|
/* Min matches FreeRTOS tick (HZ=100 → 10 ms); 8 ms would delay 0 ticks. */
|
|
#define BLE_STREAM_PERIOD_MIN_MS 10u
|
|
#define BLE_STREAM_PERIOD_MAX_MS 16u
|
|
#define BLE_STREAM_PERIOD_DEFAULT_MS 10u
|
|
|
|
static uint32_t s_period_ms = BLE_STREAM_PERIOD_DEFAULT_MS;
|
|
static TaskHandle_t s_task;
|
|
static volatile bool s_running;
|
|
|
|
static bool tap_notify_any(const client_info_t *client) {
|
|
return client != NULL &&
|
|
(client->tap_notify_single || client->tap_notify_double ||
|
|
client->tap_notify_triple);
|
|
}
|
|
|
|
static alox_TapKind tap_kind_from_registry(uint32_t kind) {
|
|
switch (kind) {
|
|
case 1:
|
|
return alox_TapKind_TAP_SINGLE;
|
|
case 2:
|
|
return alox_TapKind_TAP_DOUBLE;
|
|
case 3:
|
|
return alox_TapKind_TAP_TRIPLE;
|
|
default:
|
|
return alox_TapKind_TAP_NONE;
|
|
}
|
|
}
|
|
|
|
static void fill_clients(alox_BleStreamTick *tick) {
|
|
tick->clients_count = 0;
|
|
size_t count = client_registry_count();
|
|
for (size_t i = 0; i < count; i++) {
|
|
const client_info_t *client = client_registry_at(i);
|
|
if (client == NULL) {
|
|
continue;
|
|
}
|
|
const bool want_accel = client->accel_stream_enabled;
|
|
const bool want_tap = tap_notify_any(client);
|
|
if (!want_accel && !want_tap) {
|
|
continue;
|
|
}
|
|
if (tick->clients_count >=
|
|
sizeof(tick->clients) / sizeof(tick->clients[0])) {
|
|
break;
|
|
}
|
|
|
|
alox_CacheClientStatus *entry = &tick->clients[tick->clients_count++];
|
|
entry->client_id = client->id;
|
|
entry->has_accel = false;
|
|
entry->has_tap = false;
|
|
|
|
if (want_accel) {
|
|
entry->has_accel = true;
|
|
entry->accel.valid = client->accel_valid;
|
|
if (client->accel_valid) {
|
|
entry->accel.x = client->accel_x;
|
|
entry->accel.y = client->accel_y;
|
|
entry->accel.z = client->accel_z;
|
|
entry->accel.age_ms =
|
|
client_registry_ms_since(client->accel_updated_at);
|
|
}
|
|
}
|
|
|
|
if (want_tap) {
|
|
uint32_t kind = 0;
|
|
uint32_t age_ms = 0;
|
|
if (client_registry_take_tap(client->id, &kind, &age_ms)) {
|
|
entry->has_tap = true;
|
|
entry->tap.kind = tap_kind_from_registry(kind);
|
|
entry->tap.age_ms = age_ms;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool any_accel_stream(void) {
|
|
size_t count = client_registry_count();
|
|
for (size_t i = 0; i < count; i++) {
|
|
const client_info_t *client = client_registry_at(i);
|
|
if (client != NULL && client->accel_stream_enabled) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static esp_err_t send_tick_truncated(alox_UartMessage *msg) {
|
|
/*
|
|
* ble_host_tx splits frames larger than ATT MTU-3 across notifies. Only
|
|
* shrink the tick if it cannot encode into MAX_PAYLOAD_SIZE.
|
|
*/
|
|
while (true) {
|
|
esp_err_t err = uart_send_uart_message(msg);
|
|
if (err == ESP_OK) {
|
|
return ESP_OK;
|
|
}
|
|
if (msg->payload.ble_stream_tick.clients_count == 0) {
|
|
ESP_LOGW(TAG, "tick too large even with 0 clients: %s",
|
|
esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
msg->payload.ble_stream_tick.clients_count--;
|
|
ESP_LOGW(TAG, "truncating tick clients to %u for encode buffer",
|
|
(unsigned)msg->payload.ble_stream_tick.clients_count);
|
|
}
|
|
}
|
|
|
|
static void ble_stream_task(void *param) {
|
|
(void)param;
|
|
ESP_LOGI(TAG, "BLE stream task started period_ms=%lu",
|
|
(unsigned long)s_period_ms);
|
|
|
|
while (s_running) {
|
|
uint32_t period = s_period_ms;
|
|
if (period < BLE_STREAM_PERIOD_MIN_MS) {
|
|
period = BLE_STREAM_PERIOD_MIN_MS;
|
|
}
|
|
if (period > BLE_STREAM_PERIOD_MAX_MS) {
|
|
period = BLE_STREAM_PERIOD_MAX_MS;
|
|
}
|
|
|
|
/* Pause ticks during host OTA / ESP-NOW distribute so ATT bandwidth
|
|
* stays available for OTA_STATUS notifies. */
|
|
if (ble_host_ready() && any_accel_stream() && !ota_session_busy()) {
|
|
alox_UartMessage msg = alox_UartMessage_init_zero;
|
|
uart_cmd_init_response(&msg, alox_MessageType_BLE_STREAM_TICK,
|
|
alox_UartMessage_ble_stream_tick_tag);
|
|
msg.payload.ble_stream_tick.esp_timestamp_us =
|
|
(uint64_t)esp_timer_get_time();
|
|
msg.payload.ble_stream_tick.period_ms = period;
|
|
fill_clients(&msg.payload.ble_stream_tick);
|
|
|
|
esp_err_t err = send_tick_truncated(&msg);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGW(TAG, "tick send failed: %s", esp_err_to_name(err));
|
|
}
|
|
}
|
|
|
|
TickType_t delay_ticks = pdMS_TO_TICKS(period);
|
|
if (delay_ticks < 1) {
|
|
delay_ticks = 1;
|
|
}
|
|
vTaskDelay(delay_ticks);
|
|
}
|
|
|
|
s_task = NULL;
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
void ble_stream_set_period_ms(uint32_t period_ms) {
|
|
if (period_ms == 0) {
|
|
period_ms = BLE_STREAM_PERIOD_DEFAULT_MS;
|
|
}
|
|
if (period_ms < BLE_STREAM_PERIOD_MIN_MS) {
|
|
period_ms = BLE_STREAM_PERIOD_MIN_MS;
|
|
}
|
|
if (period_ms > BLE_STREAM_PERIOD_MAX_MS) {
|
|
period_ms = BLE_STREAM_PERIOD_MAX_MS;
|
|
}
|
|
s_period_ms = period_ms;
|
|
}
|
|
|
|
uint32_t ble_stream_period_ms(void) { return s_period_ms; }
|
|
|
|
void ble_stream_update(void) {
|
|
const app_config_t *cfg = app_config_get();
|
|
bool want = cfg != NULL && cfg->host_ble && any_accel_stream();
|
|
|
|
if (want && !s_running) {
|
|
s_running = true;
|
|
if (xTaskCreate(ble_stream_task, "ble_stream", 8192, NULL, 5, &s_task) !=
|
|
pdPASS) {
|
|
s_running = false;
|
|
s_task = NULL;
|
|
ESP_LOGE(TAG, "failed to create ble_stream task");
|
|
}
|
|
} else if (!want && s_running) {
|
|
s_running = false;
|
|
}
|
|
}
|