retain telemetry across transport outages
This commit is contained in:
+20
-3
@@ -85,6 +85,18 @@ static void encode_header(uint8_t *output,
|
||||
put_u32_le(output + 32, 0);
|
||||
}
|
||||
|
||||
bool trikke_wire_timestamp_delta_fits(
|
||||
int64_t previous_timestamp_us,
|
||||
int64_t timestamp_us)
|
||||
{
|
||||
if (timestamp_us < previous_timestamp_us) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t delta_us =
|
||||
(uint64_t)timestamp_us - (uint64_t)previous_timestamp_us;
|
||||
return delta_us <= TRIKKE_WIRE_MAX_TIMESTAMP_DELTA_US;
|
||||
}
|
||||
|
||||
size_t trikke_encode_metadata_packet(
|
||||
uint8_t *output,
|
||||
size_t output_size,
|
||||
@@ -159,12 +171,17 @@ size_t trikke_encode_sample_packet(
|
||||
i * TRIKKE_WIRE_SAMPLE_RECORD_SIZE;
|
||||
uint16_t timestamp_delta_10us = 0;
|
||||
if (i > 0) {
|
||||
const int64_t delta_us = samples[i].timestamp_us - previous_timestamp_us;
|
||||
if (delta_us < 0 || delta_us > (int64_t)UINT16_MAX * 10) {
|
||||
if (!trikke_wire_timestamp_delta_fits(
|
||||
previous_timestamp_us, samples[i].timestamp_us)) {
|
||||
timestamp_delta_10us = UINT16_MAX;
|
||||
packet_flags |= TRIKKE_PACKET_FLAG_TIMESTAMP_DELTA_SATURATED;
|
||||
} else {
|
||||
timestamp_delta_10us = (uint16_t)((delta_us + 5) / 10);
|
||||
const uint64_t delta_us =
|
||||
(uint64_t)samples[i].timestamp_us -
|
||||
(uint64_t)previous_timestamp_us;
|
||||
timestamp_delta_10us = (uint16_t)(
|
||||
(delta_us + TRIKKE_WIRE_TIMESTAMP_DELTA_UNIT_US / 2) /
|
||||
TRIKKE_WIRE_TIMESTAMP_DELTA_UNIT_US);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -16,6 +17,9 @@
|
||||
#define TRIKKE_PACKET_TYPE_SAMPLES 2
|
||||
|
||||
#define TRIKKE_PACKET_FLAG_TIMESTAMP_DELTA_SATURATED 0x01
|
||||
#define TRIKKE_WIRE_TIMESTAMP_DELTA_UNIT_US 10
|
||||
#define TRIKKE_WIRE_MAX_TIMESTAMP_DELTA_US \
|
||||
((uint64_t)UINT16_MAX * TRIKKE_WIRE_TIMESTAMP_DELTA_UNIT_US)
|
||||
|
||||
#define TRIKKE_METADATA_FLAG_ACCEL_Y_NEGX_Z 0x0001
|
||||
#define TRIKKE_METADATA_FLAG_GYRO_IDENTITY 0x0002
|
||||
@@ -46,6 +50,10 @@ typedef struct {
|
||||
float gyro_mdps_per_lsb;
|
||||
} trikke_wire_metadata_t;
|
||||
|
||||
bool trikke_wire_timestamp_delta_fits(
|
||||
int64_t previous_timestamp_us,
|
||||
int64_t timestamp_us);
|
||||
|
||||
size_t trikke_encode_metadata_packet(
|
||||
uint8_t *output,
|
||||
size_t output_size,
|
||||
|
||||
+37
-12
@@ -24,6 +24,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_TRANSPORT_RETRY_DELAY_MS 10
|
||||
|
||||
// Software calibration from the 2026-08-17 enclosure six-face capture.
|
||||
// Accelerometer coefficients are measured. Gyroscope scale is nominal; its
|
||||
@@ -158,14 +159,24 @@ static void acquisition_task(void *argument)
|
||||
|
||||
static bool write_binary_packet(const uint8_t *packet, size_t packet_size)
|
||||
{
|
||||
const bool complete = fwrite(packet, 1, packet_size, stdout) == packet_size;
|
||||
fflush(stdout);
|
||||
const size_t written = fwrite(packet, 1, packet_size, stdout);
|
||||
const int flush_result = fflush(stdout);
|
||||
const bool complete = written == packet_size && flush_result == 0;
|
||||
if (!complete) {
|
||||
clearerr(stdout);
|
||||
}
|
||||
return complete;
|
||||
}
|
||||
|
||||
static void write_binary_packet_until_sent(
|
||||
const uint8_t *packet,
|
||||
size_t packet_size)
|
||||
{
|
||||
while (!write_binary_packet(packet, packet_size)) {
|
||||
vTaskDelay(pdMS_TO_TICKS(TRIKKE_TRANSPORT_RETRY_DELAY_MS));
|
||||
}
|
||||
}
|
||||
|
||||
static void output_task(void *argument)
|
||||
{
|
||||
trikke_context_t *context = argument;
|
||||
@@ -179,7 +190,7 @@ static void output_task(void *argument)
|
||||
packet, sizeof(packet), packet_sequence++, esp_timer_get_time(),
|
||||
atomic_load(&context->dropped_sample_count),
|
||||
atomic_load(&context->loop_overrun_count), &TRIKKE_METADATA);
|
||||
write_binary_packet(packet, packet_size);
|
||||
write_binary_packet_until_sent(packet, packet_size);
|
||||
|
||||
while (true) {
|
||||
trikke_wire_sample_t samples[TRIKKE_WIRE_MAX_RECORDS] = {0};
|
||||
@@ -190,9 +201,21 @@ static void output_task(void *argument)
|
||||
}
|
||||
++sample_count;
|
||||
|
||||
while (sample_count < TRIKKE_WIRE_MAX_RECORDS &&
|
||||
xQueueReceive(context->sample_queue, &samples[sample_count],
|
||||
pdMS_TO_TICKS(15)) == pdPASS) {
|
||||
while (sample_count < TRIKKE_WIRE_MAX_RECORDS) {
|
||||
trikke_wire_sample_t next_sample = {0};
|
||||
if (xQueuePeek(context->sample_queue, &next_sample,
|
||||
pdMS_TO_TICKS(15)) != pdPASS) {
|
||||
break;
|
||||
}
|
||||
if (!trikke_wire_timestamp_delta_fits(
|
||||
samples[sample_count - 1].timestamp_us,
|
||||
next_sample.timestamp_us)) {
|
||||
break;
|
||||
}
|
||||
if (xQueueReceive(context->sample_queue, &samples[sample_count], 0) !=
|
||||
pdPASS) {
|
||||
break;
|
||||
}
|
||||
++sample_count;
|
||||
}
|
||||
|
||||
@@ -202,16 +225,14 @@ static void output_task(void *argument)
|
||||
packet, sizeof(packet), packet_sequence++, esp_timer_get_time(),
|
||||
atomic_load(&context->dropped_sample_count),
|
||||
atomic_load(&context->loop_overrun_count), &TRIKKE_METADATA);
|
||||
write_binary_packet(packet, packet_size);
|
||||
write_binary_packet_until_sent(packet, packet_size);
|
||||
}
|
||||
|
||||
packet_size = trikke_encode_sample_packet(
|
||||
packet, sizeof(packet), packet_sequence++,
|
||||
atomic_load(&context->dropped_sample_count),
|
||||
atomic_load(&context->loop_overrun_count), samples, sample_count);
|
||||
if (!write_binary_packet(packet, packet_size)) {
|
||||
atomic_fetch_add(&context->dropped_sample_count, sample_count);
|
||||
}
|
||||
write_binary_packet_until_sent(packet, packet_size);
|
||||
++sample_packet_count;
|
||||
}
|
||||
}
|
||||
@@ -232,8 +253,12 @@ static esp_err_t init_i2c(i2c_master_bus_handle_t *bus)
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Flush startup text by line; binary frames are flushed explicitly.
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
// Keep the byte stream unbuffered so a write result describes the complete
|
||||
// packet rather than bytes still retained inside stdio.
|
||||
if (setvbuf(stdout, NULL, _IONBF, 0) != 0) {
|
||||
ESP_LOGE(TAG, "failed to configure unbuffered telemetry output");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Trikke motion telemetry prototype v0");
|
||||
ESP_LOGI(TAG, "I2C: SDA=GPIO%d, SCL=GPIO%d, clock=%d Hz",
|
||||
|
||||
Reference in New Issue
Block a user