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;
}

Parents
  • Hello,

    Yes, the ATT payload is typically limited to 244 bytes (based on a commonly negotiated MTU of 247), so you cannot send all 750 logs in a single notification. While the Bluetooth specification allows an ATT MTU of up to 512 bytes (giving a maximum payload of 509 bytes), See the section Data length &PDU, and also check the  config CONFIG_BT_L2CAP_TX_MTU=512.

    Regardless of whether the payload per notification is 244 bytes or up to 509 bytes, this does not limit the total amount of data you can transfer. BLE supports sending larger datasets by splitting the data across multiple notifications. In your case, sending 750 logs is completely feasible, you simply transmit them in chunks over multiple notifications.

    Kind Regards,

    Abhijith

Reply
  • Hello,

    Yes, the ATT payload is typically limited to 244 bytes (based on a commonly negotiated MTU of 247), so you cannot send all 750 logs in a single notification. While the Bluetooth specification allows an ATT MTU of up to 512 bytes (giving a maximum payload of 509 bytes), See the section Data length &PDU, and also check the  config CONFIG_BT_L2CAP_TX_MTU=512.

    Regardless of whether the payload per notification is 244 bytes or up to 509 bytes, this does not limit the total amount of data you can transfer. BLE supports sending larger datasets by splitting the data across multiple notifications. In your case, sending 750 logs is completely feasible, you simply transmit them in chunks over multiple notifications.

    Kind Regards,

    Abhijith

Children
No Data
Related