add reliable BLE telemetry transport

This commit is contained in:
Jay
2026-08-18 06:24:10 -04:00
parent 00f52ecf0f
commit 7617010d8e
26 changed files with 1499 additions and 47 deletions
+93 -19
View File
@@ -15,7 +15,11 @@
#include "l3g4200d.h"
#include "trikke_protocol.h"
#include "trikke_transport.h"
#if CONFIG_TRIKKE_TRANSPORT_BLE
#include "trikke_ble_transport.h"
#else
#include "trikke_usb_transport.h"
#endif
// Seeed Studio XIAO ESP32-C3: D4/SDA = GPIO6, D5/SCL = GPIO7.
#define TRIKKE_I2C_PORT I2C_NUM_0
@@ -26,6 +30,7 @@
#define TRIKKE_SAMPLE_TICKS pdMS_TO_TICKS(1000 / TRIKKE_SAMPLE_RATE_HZ)
#define TRIKKE_SAMPLE_QUEUE_DEPTH 512
#define TRIKKE_METADATA_INTERVAL_PACKETS 64
#define TRIKKE_STATUS_INTERVAL_PACKETS 64
#define TRIKKE_TRANSPORT_RETRY_DELAY_MS 10
// Software calibration from the 2026-08-17 enclosure six-face capture.
@@ -54,10 +59,15 @@ typedef struct {
adxl345_t accelerometer;
l3g4200d_t gyroscope;
QueueHandle_t sample_queue;
atomic_uint_least32_t dropped_sample_count;
atomic_uint_least32_t sensor_read_failure_count;
atomic_uint_least32_t queue_overflow_count;
atomic_uint_least32_t loop_overrun_count;
trikke_transport_t transport;
#if CONFIG_TRIKKE_TRANSPORT_BLE
trikke_ble_transport_t ble_transport;
#else
trikke_usb_transport_t usb_transport;
#endif
} trikke_context_t;
static trikke_context_t s_context;
@@ -109,6 +119,34 @@ static trikke_axes_sample_t map_gyro_to_enclosure(const l3g4200d_sample_t *nativ
};
}
static uint32_t dropped_sample_count(const trikke_context_t *context)
{
return atomic_load(&context->sensor_read_failure_count) +
atomic_load(&context->queue_overflow_count);
}
static trikke_wire_status_t status_snapshot(
trikke_context_t *context,
const trikke_transport_sender_t *sender)
{
trikke_wire_status_t status = {
.sensor_read_failure_count =
atomic_load(&context->sensor_read_failure_count),
.queue_overflow_count = atomic_load(&context->queue_overflow_count),
.transport_begin_retry_count = sender->begin_retry_count,
};
#if CONFIG_TRIKKE_TRANSPORT_BLE
trikke_ble_transport_counters_t ble_counters = {0};
trikke_ble_transport_get_counters(
&context->ble_transport, &ble_counters);
status.transport_disconnect_count = ble_counters.disconnect_count;
status.transport_send_failure_count = ble_counters.send_failure_count;
status.transport_replay_count = ble_counters.replay_count;
status.transport_invalid_ack_count = ble_counters.invalid_ack_count;
#endif
return status;
}
static void acquisition_task(void *argument)
{
trikke_context_t *context = argument;
@@ -146,10 +184,10 @@ static void acquisition_task(void *argument)
.gyro_status = gyro_status,
};
if (xQueueSend(context->sample_queue, &sample, 0) != pdPASS) {
atomic_fetch_add(&context->dropped_sample_count, 1);
atomic_fetch_add(&context->queue_overflow_count, 1);
}
} else {
atomic_fetch_add(&context->dropped_sample_count, 1);
atomic_fetch_add(&context->sensor_read_failure_count, 1);
}
++sequence;
@@ -207,10 +245,17 @@ static void output_task(void *argument)
size_t packet_size = trikke_encode_metadata_packet(
packet, sizeof(packet), packet_sequence++, esp_timer_get_time(),
atomic_load(&context->dropped_sample_count),
dropped_sample_count(context),
atomic_load(&context->loop_overrun_count), &TRIKKE_METADATA);
write_binary_packet_until_sent(context, &sender, packet, packet_size);
trikke_wire_status_t status = status_snapshot(context, &sender);
packet_size = trikke_encode_status_packet(
packet, sizeof(packet), packet_sequence++, esp_timer_get_time(),
dropped_sample_count(context),
atomic_load(&context->loop_overrun_count), &status);
write_binary_packet_until_sent(context, &sender, packet, packet_size);
while (true) {
trikke_wire_sample_t samples[TRIKKE_WIRE_MAX_RECORDS] = {0};
size_t sample_count = 0;
@@ -244,15 +289,26 @@ static void output_task(void *argument)
sample_packet_count % TRIKKE_METADATA_INTERVAL_PACKETS == 0) {
packet_size = trikke_encode_metadata_packet(
packet, sizeof(packet), packet_sequence++, esp_timer_get_time(),
atomic_load(&context->dropped_sample_count),
dropped_sample_count(context),
atomic_load(&context->loop_overrun_count), &TRIKKE_METADATA);
write_binary_packet_until_sent(
context, &sender, packet, packet_size);
}
if (sample_packet_count > 0 &&
sample_packet_count % TRIKKE_STATUS_INTERVAL_PACKETS == 0) {
status = status_snapshot(context, &sender);
packet_size = trikke_encode_status_packet(
packet, sizeof(packet), packet_sequence++, esp_timer_get_time(),
dropped_sample_count(context),
atomic_load(&context->loop_overrun_count), &status);
write_binary_packet_until_sent(
context, &sender, packet, packet_size);
}
packet_size = trikke_encode_sample_packet(
packet, sizeof(packet), packet_sequence++,
atomic_load(&context->dropped_sample_count),
dropped_sample_count(context),
atomic_load(&context->loop_overrun_count), samples, sample_count);
write_binary_packet_until_sent(context, &sender, packet, packet_size);
++sample_packet_count;
@@ -338,27 +394,22 @@ void app_main(void)
"records_per_packet=%d\n",
TRIKKE_WIRE_VERSION, TRIKKE_WIRE_SAMPLE_RECORD_SIZE,
TRIKKE_WIRE_MAX_RECORDS);
#if CONFIG_TRIKKE_TRANSPORT_BLE
printf("# transport=ble,device_name=TrikkeSensor\n");
#else
printf("# transport=usb_serial_jtag\n");
#endif
fflush(stdout);
#if !CONFIG_TRIKKE_TRANSPORT_BLE
// The console defaults to CRLF conversion, which would insert bytes into
// binary frames whenever a payload byte equals LF.
usb_serial_jtag_vfs_set_tx_line_endings(ESP_LINE_ENDINGS_LF);
err = trikke_usb_transport_init(
&s_context.usb_transport, &s_context.transport);
if (err != ESP_OK) {
ESP_LOGE(TAG, "USB transport initialization failed: %s",
esp_err_to_name(err));
l3g4200d_deinit(&s_context.gyroscope);
adxl345_deinit(&s_context.accelerometer);
i2c_del_master_bus(bus);
return;
}
#endif
s_context.sample_queue =
xQueueCreate(TRIKKE_SAMPLE_QUEUE_DEPTH, sizeof(trikke_wire_sample_t));
if (s_context.sample_queue == NULL) {
trikke_usb_transport_deinit(&s_context.usb_transport);
ESP_LOGE(TAG, "sample queue allocation failed");
l3g4200d_deinit(&s_context.gyroscope);
adxl345_deinit(&s_context.accelerometer);
@@ -379,7 +430,6 @@ void app_main(void)
vTaskDelete(acquisition_task_handle);
}
vQueueDelete(s_context.sample_queue);
trikke_usb_transport_deinit(&s_context.usb_transport);
ESP_LOGE(TAG, "telemetry task creation failed");
l3g4200d_deinit(&s_context.gyroscope);
adxl345_deinit(&s_context.accelerometer);
@@ -387,6 +437,30 @@ void app_main(void)
return;
}
#if CONFIG_TRIKKE_TRANSPORT_BLE
err = trikke_ble_transport_init(
&s_context.ble_transport, &s_context.transport);
#else
err = trikke_usb_transport_init(
&s_context.usb_transport, &s_context.transport);
#endif
if (err != ESP_OK) {
vTaskDelete(output_task_handle);
vTaskDelete(acquisition_task_handle);
vQueueDelete(s_context.sample_queue);
#if CONFIG_TRIKKE_TRANSPORT_BLE
ESP_LOGE(TAG, "BLE transport initialization failed: %s",
esp_err_to_name(err));
#else
ESP_LOGE(TAG, "USB transport initialization failed: %s",
esp_err_to_name(err));
#endif
l3g4200d_deinit(&s_context.gyroscope);
adxl345_deinit(&s_context.accelerometer);
i2c_del_master_bus(bus);
return;
}
// No text may share the byte stream once framed binary output begins.
esp_log_level_set("*", ESP_LOG_NONE);
xTaskNotifyGive(output_task_handle);