Add reliable Android BLE ride recorder
This commit is contained in:
+163
-6
@@ -8,6 +8,8 @@
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/ble_uuid.h"
|
||||
#include "host/util/util.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_timer.h"
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
@@ -22,11 +24,24 @@
|
||||
#define TRIKKE_BLE_MAX_ATT_PAYLOAD 253
|
||||
#define TRIKKE_BLE_FRAGMENTS_PER_POLL 8
|
||||
#define TRIKKE_BLE_ACK_TIMEOUT_US 1000000
|
||||
#define TRIKKE_BLE_SESSION_RTC_MAGIC UINT32_C(0x54524b53)
|
||||
#define TRIKKE_BLE_SESSION_RTC_XOR UINT32_C(0xa93cf17e)
|
||||
#define TRIKKE_BLE_SESSION_RESET_DELAY_MS 50
|
||||
|
||||
static trikke_ble_transport_t *s_ble;
|
||||
static uint8_t s_own_address_type;
|
||||
static uint16_t s_data_value_handle;
|
||||
static uint16_t s_ack_value_handle;
|
||||
static uint16_t s_control_value_handle;
|
||||
|
||||
typedef struct {
|
||||
uint32_t token_low;
|
||||
uint32_t token_high;
|
||||
uint32_t checksum;
|
||||
uint32_t magic;
|
||||
} trikke_ble_rtc_session_t;
|
||||
|
||||
RTC_NOINIT_ATTR static trikke_ble_rtc_session_t s_rtc_session;
|
||||
|
||||
// 7d2ea000-f75b-4a9b-8fbe-3d4c2a1e9c10 and adjacent characteristic UUIDs.
|
||||
static const ble_uuid128_t TRIKKE_SERVICE_UUID =
|
||||
@@ -38,6 +53,9 @@ static const ble_uuid128_t TRIKKE_DATA_UUID =
|
||||
static const ble_uuid128_t TRIKKE_ACK_UUID =
|
||||
BLE_UUID128_INIT(0x12, 0x9c, 0x1e, 0x2a, 0x4c, 0x3d, 0xbe, 0x8f,
|
||||
0x9b, 0x4a, 0x5b, 0xf7, 0x00, 0xa0, 0x2e, 0x7d);
|
||||
static const ble_uuid128_t TRIKKE_CONTROL_UUID =
|
||||
BLE_UUID128_INIT(0x13, 0x9c, 0x1e, 0x2a, 0x4c, 0x3d, 0xbe, 0x8f,
|
||||
0x9b, 0x4a, 0x5b, 0xf7, 0x00, 0xa0, 0x2e, 0x7d);
|
||||
|
||||
static uint32_t get_u32_le(const uint8_t *input)
|
||||
{
|
||||
@@ -45,6 +63,63 @@ static uint32_t get_u32_le(const uint8_t *input)
|
||||
((uint32_t)input[2] << 16) | ((uint32_t)input[3] << 24);
|
||||
}
|
||||
|
||||
// Caller must hold ble->lock. Keep the raw CCCD state independent from session
|
||||
// authorization so either legal write order converges on the same effective
|
||||
// subscription state.
|
||||
static void update_subscribed_locked(trikke_ble_transport_t *ble)
|
||||
{
|
||||
if (trikke_ble_update_subscription(
|
||||
ble->notify_enabled,
|
||||
ble->session_connection_ready,
|
||||
&ble->subscribed)) {
|
||||
++ble->delivery_epoch;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t rtc_session_checksum(uint32_t low, uint32_t high)
|
||||
{
|
||||
return TRIKKE_BLE_SESSION_RTC_MAGIC ^ low ^ high ^
|
||||
TRIKKE_BLE_SESSION_RTC_XOR;
|
||||
}
|
||||
|
||||
static void rtc_session_clear(void)
|
||||
{
|
||||
s_rtc_session.magic = 0;
|
||||
s_rtc_session.token_low = 0;
|
||||
s_rtc_session.token_high = 0;
|
||||
s_rtc_session.checksum = 0;
|
||||
}
|
||||
|
||||
static void rtc_session_store(uint64_t token)
|
||||
{
|
||||
const uint32_t low = (uint32_t)token;
|
||||
const uint32_t high = (uint32_t)(token >> 32);
|
||||
s_rtc_session.magic = 0;
|
||||
s_rtc_session.token_low = low;
|
||||
s_rtc_session.token_high = high;
|
||||
s_rtc_session.checksum = rtc_session_checksum(low, high);
|
||||
s_rtc_session.magic = TRIKKE_BLE_SESSION_RTC_MAGIC;
|
||||
}
|
||||
|
||||
static bool rtc_session_load(uint64_t *token)
|
||||
{
|
||||
if (token == NULL || s_rtc_session.magic != TRIKKE_BLE_SESSION_RTC_MAGIC ||
|
||||
s_rtc_session.checksum != rtc_session_checksum(
|
||||
s_rtc_session.token_low, s_rtc_session.token_high)) {
|
||||
return false;
|
||||
}
|
||||
*token = (uint64_t)s_rtc_session.token_low |
|
||||
((uint64_t)s_rtc_session.token_high << 32);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void session_restart_task(void *argument)
|
||||
{
|
||||
(void)argument;
|
||||
vTaskDelay(pdMS_TO_TICKS(TRIKKE_BLE_SESSION_RESET_DELAY_MS));
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
static int data_access(
|
||||
uint16_t connection_handle,
|
||||
uint16_t attribute_handle,
|
||||
@@ -102,6 +177,69 @@ static int ack_access(
|
||||
return accepted ? 0 : BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
static int session_access(
|
||||
uint16_t connection_handle,
|
||||
uint16_t attribute_handle,
|
||||
struct ble_gatt_access_ctxt *context,
|
||||
void *argument)
|
||||
{
|
||||
(void)attribute_handle;
|
||||
(void)argument;
|
||||
trikke_ble_transport_t *ble = s_ble;
|
||||
if (ble == NULL || context->op != BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
uint8_t command[TRIKKE_BLE_BEGIN_SESSION_SIZE] = {0};
|
||||
uint16_t command_size = 0;
|
||||
uint64_t requested_token = 0;
|
||||
if (OS_MBUF_PKTLEN(context->om) != sizeof(command) ||
|
||||
ble_hs_mbuf_to_flat(
|
||||
context->om, command, sizeof(command), &command_size) != 0 ||
|
||||
!trikke_ble_decode_begin_session(
|
||||
command, command_size, &requested_token)) {
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
|
||||
bool notify_session_ready = false;
|
||||
bool restart = false;
|
||||
portENTER_CRITICAL(&ble->lock);
|
||||
if (!ble->connected || ble->connection_handle != connection_handle ||
|
||||
ble->session_restart_pending) {
|
||||
portEXIT_CRITICAL(&ble->lock);
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
if (ble->session_token_valid && ble->session_token == requested_token) {
|
||||
ble->session_connection_ready = true;
|
||||
update_subscribed_locked(ble);
|
||||
if (!ble->session_started) {
|
||||
ble->session_started = true;
|
||||
notify_session_ready = true;
|
||||
}
|
||||
} else {
|
||||
ble->session_connection_ready = false;
|
||||
update_subscribed_locked(ble);
|
||||
ble->session_restart_pending = true;
|
||||
rtc_session_store(requested_token);
|
||||
restart = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&ble->lock);
|
||||
|
||||
if (notify_session_ready && ble->session_ready != NULL) {
|
||||
ble->session_ready(ble->session_ready_context);
|
||||
}
|
||||
if (restart && xTaskCreate(
|
||||
session_restart_task, "trikke_session_reset", 2048, NULL,
|
||||
configMAX_PRIORITIES - 1, NULL) != pdPASS) {
|
||||
rtc_session_clear();
|
||||
portENTER_CRITICAL(&ble->lock);
|
||||
ble->session_restart_pending = false;
|
||||
portEXIT_CRITICAL(&ble->lock);
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ble_gatt_svc_def TRIKKE_GATT_SERVICES[] = {
|
||||
{
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
@@ -119,6 +257,12 @@ static const struct ble_gatt_svc_def TRIKKE_GATT_SERVICES[] = {
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
|
||||
.val_handle = &s_ack_value_handle,
|
||||
},
|
||||
{
|
||||
.uuid = &TRIKKE_CONTROL_UUID.u,
|
||||
.access_cb = session_access,
|
||||
.flags = BLE_GATT_CHR_F_WRITE,
|
||||
.val_handle = &s_control_value_handle,
|
||||
},
|
||||
{0},
|
||||
},
|
||||
},
|
||||
@@ -169,7 +313,9 @@ static void on_reset(int reason)
|
||||
++ble->counters.disconnect_count;
|
||||
}
|
||||
ble->connected = false;
|
||||
ble->notify_enabled = false;
|
||||
ble->subscribed = false;
|
||||
ble->session_connection_ready = false;
|
||||
ble->connection_handle = BLE_HS_CONN_HANDLE_NONE;
|
||||
++ble->delivery_epoch;
|
||||
portEXIT_CRITICAL(&ble->lock);
|
||||
@@ -197,7 +343,9 @@ static int gap_event(struct ble_gap_event *event, void *argument)
|
||||
if (event->connect.status == 0) {
|
||||
portENTER_CRITICAL(&ble->lock);
|
||||
ble->connected = true;
|
||||
ble->notify_enabled = false;
|
||||
ble->subscribed = false;
|
||||
ble->session_connection_ready = false;
|
||||
ble->connection_handle = event->connect.conn_handle;
|
||||
++ble->delivery_epoch;
|
||||
portEXIT_CRITICAL(&ble->lock);
|
||||
@@ -212,7 +360,9 @@ static int gap_event(struct ble_gap_event *event, void *argument)
|
||||
++ble->counters.disconnect_count;
|
||||
}
|
||||
ble->connected = false;
|
||||
ble->notify_enabled = false;
|
||||
ble->subscribed = false;
|
||||
ble->session_connection_ready = false;
|
||||
ble->connection_handle = BLE_HS_CONN_HANDLE_NONE;
|
||||
++ble->delivery_epoch;
|
||||
portEXIT_CRITICAL(&ble->lock);
|
||||
@@ -222,11 +372,8 @@ static int gap_event(struct ble_gap_event *event, void *argument)
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
if (event->subscribe.attr_handle == s_data_value_handle) {
|
||||
portENTER_CRITICAL(&ble->lock);
|
||||
const bool subscribed = event->subscribe.cur_notify != 0;
|
||||
if (subscribed != ble->subscribed) {
|
||||
ble->subscribed = subscribed;
|
||||
++ble->delivery_epoch;
|
||||
}
|
||||
ble->notify_enabled = event->subscribe.cur_notify != 0;
|
||||
update_subscribed_locked(ble);
|
||||
portEXIT_CRITICAL(&ble->lock);
|
||||
}
|
||||
return 0;
|
||||
@@ -431,7 +578,9 @@ static trikke_transport_status_t ble_poll_packet(void *context)
|
||||
|
||||
esp_err_t trikke_ble_transport_init(
|
||||
trikke_ble_transport_t *ble,
|
||||
trikke_transport_t *transport)
|
||||
trikke_transport_t *transport,
|
||||
trikke_ble_session_ready_fn session_ready,
|
||||
void *session_ready_context)
|
||||
{
|
||||
if (ble == NULL || transport == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
@@ -443,6 +592,14 @@ esp_err_t trikke_ble_transport_init(
|
||||
memset(ble, 0, sizeof(*ble));
|
||||
ble->lock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
ble->connection_handle = BLE_HS_CONN_HANDLE_NONE;
|
||||
ble->session_ready = session_ready;
|
||||
ble->session_ready_context = session_ready_context;
|
||||
if (esp_reset_reason() == ESP_RST_SW &&
|
||||
rtc_session_load(&ble->session_token)) {
|
||||
ble->session_token_valid = true;
|
||||
} else {
|
||||
rtc_session_clear();
|
||||
}
|
||||
s_ble = ble;
|
||||
|
||||
esp_err_t error = nvs_flash_init();
|
||||
|
||||
Reference in New Issue
Block a user