Best practice for transfering 6+KB of data using BLE

Hi Nordic,

I'm developing a BLE protocol on an nRF54L05, and is trying to send (On request or by notifying) up to 750 logs of data, each log being 8 bytes (1 byte event type, 3 bytes data, and 4 byte timestamp) + a count for amount of logs sent. Is the maximum value I can send the ATT payload of 244 bytes? It would have to be multiple writes if so.

For the rest of the BLE protocol I'm using SMP, MCUMgr and CBor, but I'm pretty sure this would be too hefty a payload for that protocol.

Do you have any advice on how to go from here? What is the best practise?

My current idea is something like this:

#include <zephyr/mgmt/mcumgr/mgmt/mgmt_defines.h>
#include <zcbor_encode.h>
#include <zcbor_decode.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/mgmt/mcumgr/transport/smp_bt.h>
#include "values.h"
#include "ble.h"
#include "logging_module.h"

#define ATT_HEADER_SIZE 3
#define ATT_MAX_PAYLOAD_SIZE (CONFIG_BT_L2CAP_TX_MTU - ATT_HEADER_SIZE)

#define NOTIFY_HEADER_SIZE 6 // 1 byte for notification ID, 1 byte for num logs and 4 bytes for start idx
#define MAX_LOGS_PER_RESPONSE ((ATT_MAX_PAYLOAD_SIZE - NOTIFY_HEADER_SIZE) / sizeof(logging_record_t))

static int get_max_logs_per_response_for_conn(struct bt_conn *conn)
{
    uint16_t mtu;
    int max_logs;

    if (conn == NULL) {
        return MAX_LOGS_PER_RESPONSE;
    }

    mtu = bt_gatt_get_mtu(conn);

    if (mtu <= ATT_HEADER_SIZE + NOTIFY_HEADER_SIZE) {
        return 1;
    }

    max_logs = (mtu - ATT_HEADER_SIZE - NOTIFY_HEADER_SIZE) / sizeof(logging_record_t);
    return MAX(1, MIN(MAX_LOGS_PER_RESPONSE, max_logs));
}

int log_read(struct smp_streamer *ctxt)
{
    zcbor_state_t *zse = ctxt->writer->zs;  /* encoder state */
    bool ok;

    ok = zcbor_tstr_put_lit(zse, "rc");
    ok &= zcbor_int32_put(zse, MGMT_ERR_EOK);

    ok &= zcbor_tstr_put_lit(zse, "oldest_idx");
    ok &= zcbor_uint32_put(zse, logging_get_oldest_index());

    ok &= zcbor_tstr_put_lit(zse, "num_logs");
    ok &= zcbor_uint32_put(zse, logging_get_num_logs());

    ok &= zcbor_tstr_put_lit(zse, "capacity");
    ok &= zcbor_uint32_put(zse, logging_get_capacity());

    /* On encoding failure, return “message too big / encoding error” */
    return ok ? MGMT_ERR_EOK : MGMT_ERR_EUNKNOWN;
}

int log_write(struct smp_streamer *ctxt)
{
    zcbor_state_t *zsd = ctxt->reader->zs;
    uint32_t n_logs;
    bool ok;

    ok = zcbor_tstr_expect_lit(zsd, "log_count");
    ok &= zcbor_uint32_decode(zsd, &n_logs);

    if (!ok) {
        return MGMT_ERR_EINVAL;
    }

    struct bt_conn *conn = NULL;
    ble_get_active_connection(&conn);
    if (conn == NULL) {
        return MGMT_ERR_EUNKNOWN;
    }

    uint32_t r, w;
    int err = logging_get_index(&r, &w);
    if (err < 0 || n_logs == 0 || n_logs > (w - r)) {
        bt_conn_unref(conn);
        return MGMT_ERR_EINVAL;
    }

    uint8_t smp_buffer[ATT_MAX_PAYLOAD_SIZE];
    logging_record_t log[MAX_LOGS_PER_RESPONSE];
    int max_logs = get_max_logs_per_response_for_conn(conn);
    uint32_t i = r;
    uint32_t sent_count = 0;

    while (sent_count < n_logs) {
        uint32_t remaining = n_logs - sent_count;
        uint32_t batch = MIN((uint32_t)max_logs, remaining);

        int chunk_count = logging_read_multiple_log_events(i, log, (uint8_t)batch);
        if (chunk_count <= 0) {
            bt_conn_unref(conn);
            return MGMT_ERR_EUNKNOWN;
        }

        size_t payload_len = (size_t)chunk_count * sizeof(logging_record_t);
        size_t notify_len = payload_len + NOTIFY_HEADER_SIZE;

        if (notify_len > sizeof(smp_buffer)) {
            bt_conn_unref(conn);
            return MGMT_ERR_EUNKNOWN;
        }

        /* Construct SMP notification payload 
         * 0: Notification ID
         * 1: Number of logs
         * 2-5: Start index
         * 6-...: Log records
         */
        smp_buffer[0] = MGMT_ID_NOTIFY;
        smp_buffer[1] = (uint8_t)chunk_count;
        smp_buffer[2] = (uint8_t)(i >> 24);
        smp_buffer[3] = (uint8_t)(i >> 16);
        smp_buffer[4] = (uint8_t)(i >> 8);
        smp_buffer[5] = (uint8_t)(i & 0xFF);
        memcpy(&smp_buffer[6], log, payload_len);

        err = smp_bt_notify(conn, smp_buffer, notify_len);
        if (err < 0) {
            bt_conn_unref(conn);
            return MGMT_ERR_EUNKNOWN;
        }

        i += (uint32_t)chunk_count;
        sent_count += (uint32_t)chunk_count;
        logging_remove_events_up_to_index(i); // Purge logs that have been sent
    }
    bt_conn_unref(conn);

    return MGMT_ERR_EOK;
}

Related