Added ble payload fragmentation handling depending on the current MTU size, added a few other function to interact with the ble service

This commit is contained in:
anschrammh 2023-03-12 21:50:19 +01:00
parent 2854c45848
commit ef0660c499
2 changed files with 237 additions and 54 deletions

View File

@ -6,10 +6,33 @@
#include "wm_bt_util.h"
#include "bluetooth_sig_values.h"
#define USABLE_DEFAULT_MTU (20) //23 - 3 of header bytes
/* ble service internal workings attributes */
static volatile ble_service_state_e _ble_service_state = BLE_SERVICE_MODE_STOPPED;
static nus_data_rx_fn_t _ble_service_nus_data_rx_cb = NULL;
static volatile ble_service_state_t ble_service_state = BLE_SERVICE_MODE_STOPPED;
/* Connection handle to the connected device : only one simultaneous connection */
static uint16_t ble_device_conn_handle = BLE_HS_CONN_HANDLE_NONE;
static uint16_t usable_mtu = USABLE_DEFAULT_MTU;
/**
* @brief Structure used to store the various data to carry out a chunked notification or indication data transfer
*
*/
typedef struct
{
uint16_t length; // The length of the data being sent
uint16_t offset; // The offset used to be ableto send the next chunk of data in the array
uint16_t sent_chunk_size; // The size of the chunk sent
const uint8_t *data; // The address of the data to send
bool transfer_in_progress; // Is a transfer already in progress ?
} data_being_sent_t;
// Only one transfer of a type () can occur at any given time
static data_being_sent_t notification_data = {.data = NULL, .length = 0, .offset = 0, .transfer_in_progress = false};
static data_being_sent_t indication_data = {.data = NULL, .length = 0, .offset = 0, .transfer_in_progress = false};
static struct ble_gap_event_listener ble_gap_event_listener;
static int battery_level_char_access_cb(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);
@ -78,15 +101,38 @@ static bool ble_service_advertise(bool enable);
static int ble_gap_event_cb(struct ble_gap_event *event, void *arg);
static int ble_advertise_gap_event_cb(struct ble_gap_event *event, void *arg);
static void print_conn_desc(const struct ble_gap_conn_desc *desc);
// Raw because it doesn't handle payload fragmentation if mtu size is smaller than the payload size
static bool ble_service_send_raw_custom_notification(uint16_t characteristic_handle, const uint8_t *data, uint16_t length);
static bool ble_service_send_custom_notification(uint16_t characteristic_handle, data_being_sent_t * const data);
static void reset_data_being_sent(data_being_sent_t * const data);
// Needed to get the reponse after a mtu exchange request
static int ble_gatt_mtu_cb(uint16_t conn_handle, const struct ble_gatt_error *error, uint16_t mtu, void *arg);
const char *ble_service_state_2_str(ble_service_state_e state)
{
switch(state) {
CASE_RETURN_STR(BLE_SERVICE_MODE_STOPPED)
CASE_RETURN_STR(BLE_SERVICE_MODE_IDLE)
CASE_RETURN_STR(BLE_SERVICE_MODE_ADVERTISING)
CASE_RETURN_STR(BLE_SERVICE_MODE_CONNECTED)
CASE_RETURN_STR(BLE_SERVICE_MODE_INDICATING)
CASE_RETURN_STR(BLE_SERVICE_MODE_EXITING)
default:
return "unkown ble service state";
}
}
/**
* PUBLIC FUNCTION DEFINITION
*/
bool ble_service_start(void)
{
int status = BLE_HS_ENOERR;
// 1 We first check if the BLE service is stopped
if(ble_service_state != BLE_SERVICE_MODE_STOPPED)
if(_ble_service_state != BLE_SERVICE_MODE_STOPPED)
{
TLS_BT_APPL_TRACE_WARNING("%s, ble service already running"NEW_LINE, __FUNCTION__);
return true;
@ -137,7 +183,7 @@ bool ble_service_start(void)
return false;
}
ble_service_state = BLE_SERVICE_MODE_ADVERTISING;
_ble_service_state = BLE_SERVICE_MODE_ADVERTISING;
return true;
}
@ -147,7 +193,7 @@ bool ble_service_stop(void)
int status = BLE_HS_ENOERR;
// 1 We first check if the BLE service is not stopped
if(ble_service_state == BLE_SERVICE_MODE_STOPPED)
if(_ble_service_state == BLE_SERVICE_MODE_STOPPED)
{
TLS_BT_APPL_TRACE_WARNING("%s, ble service already stopped"NEW_LINE, __FUNCTION__);
return true;
@ -160,19 +206,19 @@ bool ble_service_stop(void)
return false;
}
switch(ble_service_state)
switch(_ble_service_state)
{
case BLE_SERVICE_MODE_CONNECTED:
case BLE_SERVICE_MODE_INDICATING:
status = ble_gap_terminate(ble_device_conn_handle, BLE_ERR_REM_USER_CONN_TERM);
if(status == BLE_HS_ENOERR)
{
ble_service_state = BLE_SERVICE_MODE_EXITING;
_ble_service_state = BLE_SERVICE_MODE_EXITING;
}
else //BLE_HS_EDISABLED || BLE_HS_ENOTCONN or any other error
{
TLS_BT_APPL_TRACE_WARNING("%s, ble_gap_terminate %s"NEW_LINE, __FUNCTION__, tls_bt_rc_2_str(status));
ble_service_state = BLE_SERVICE_MODE_STOPPED;
_ble_service_state = BLE_SERVICE_MODE_STOPPED;
//Unregister gap event listener
if((status = ble_gap_event_listener_unregister(&ble_gap_event_listener)) != BLE_HS_ENOERR)
{
@ -187,7 +233,7 @@ bool ble_service_stop(void)
}
else
{
ble_service_state = BLE_SERVICE_MODE_STOPPED;
_ble_service_state = BLE_SERVICE_MODE_STOPPED;
//Unregister gap event listener
if((status = ble_gap_event_listener_unregister(&ble_gap_event_listener)) != BLE_HS_ENOERR)
{
@ -199,7 +245,7 @@ bool ble_service_stop(void)
break;
}
if(ble_service_state == BLE_SERVICE_MODE_STOPPED)
if(_ble_service_state == BLE_SERVICE_MODE_STOPPED)
{
// We finally clean the gatt registered services
if((status = ble_gatts_reset()) != BLE_HS_ENOERR)
@ -212,6 +258,21 @@ bool ble_service_stop(void)
return true;
}
bool ble_service_is_started(void)
{
return _ble_service_state != BLE_SERVICE_MODE_STOPPED;
}
bool ble_service_is_device_connected(void)
{
return _ble_service_state == BLE_SERVICE_MODE_CONNECTED;
}
ble_service_state_e ble_service_get_state(void)
{
return _ble_service_state;
}
bool ble_service_update_connection_parameters(
uint16_t itvl_min,
uint16_t itvl_max,
@ -245,7 +306,51 @@ bool ble_service_update_connection_parameters(
return true;
}
bool ble_service_send_custom_notification(const uint8_t *data, uint16_t length)
bool ble_service_request_mtu_exchange(void)
{
if(BLE_HS_CONN_HANDLE_NONE == ble_device_conn_handle)
{
TLS_BT_APPL_TRACE_ERROR("%s, no active connection" NEW_LINE, __FUNCTION__);
return false;
}
int status = BLE_HS_ENOERR;
if((status = ble_gattc_exchange_mtu(ble_device_conn_handle, &(ble_gatt_mtu_cb), NULL)) != BLE_HS_ENOERR)
{
TLS_BT_APPL_TRACE_ERROR("%s, ble_gattc_exchange_mtu %s" NEW_LINE, __FUNCTION__, tls_bt_rc_2_str(status));
return false;
}
return true;
}
bool ble_service_nus_send_data(const uint8_t *data, uint16_t length)
{
// The NUS is TX is using notification
if(notification_data.transfer_in_progress)
{
TLS_BT_APPL_TRACE_WARNING("%s, a transfer is already in progress"NEW_LINE, __FUNCTION__);
return false;
}
notification_data.transfer_in_progress = true;
notification_data.data = data;
notification_data.length = length;
return ble_service_send_custom_notification(gatt_nus_char_tx_handle, &notification_data);
}
void ble_service_nus_register_data_rx_cb(nus_data_rx_fn_t nus_data_rx_cb)
{
_ble_service_nus_data_rx_cb = nus_data_rx_cb;
}
/**
* PRIVATE FUNCTION DEFINITION
* Used for the internal workings of the service
*/
static bool ble_service_send_raw_custom_notification(uint16_t characteristic_handle, const uint8_t *data, uint16_t length)
{
if(BLE_HS_CONN_HANDLE_NONE == ble_device_conn_handle)
{
@ -268,7 +373,7 @@ bool ble_service_send_custom_notification(const uint8_t *data, uint16_t length)
return false;
}
if((status = ble_gattc_notify_custom(ble_device_conn_handle, gatt_nus_char_tx_handle, om_buf)) != BLE_HS_ENOERR)
if((status = ble_gattc_notify_custom(ble_device_conn_handle, characteristic_handle, om_buf)) != BLE_HS_ENOERR)
{
TLS_BT_APPL_TRACE_ERROR("%s, ble_hs_mbuf_from_flat %s" NEW_LINE, __FUNCTION__, tls_bt_rc_2_str(status));
return false;
@ -277,19 +382,22 @@ bool ble_service_send_custom_notification(const uint8_t *data, uint16_t length)
return true;
}
bool ble_service_request_mtu_exchange(void)
static bool ble_service_send_custom_notification(uint16_t characteristic_handle, data_being_sent_t * const notif_data)
{
if(BLE_HS_CONN_HANDLE_NONE == ble_device_conn_handle)
if(!notif_data)
{
TLS_BT_APPL_TRACE_ERROR("%s, no active connection" NEW_LINE, __FUNCTION__);
TLS_BT_APPL_TRACE_WARNING("%s, notif_data is NULL"NEW_LINE, __FUNCTION__);
return false;
}
int status = BLE_HS_ENOERR;
// We compute the maximum size of the data we can send:
uint16_t remaining_data_to_send = notif_data->length - notif_data->offset;
notif_data->sent_chunk_size = remaining_data_to_send <= usable_mtu ? remaining_data_to_send : usable_mtu;
if((status = ble_gattc_exchange_mtu(ble_device_conn_handle, &(ble_gatt_mtu_cb), NULL)) != BLE_HS_ENOERR)
if(!ble_service_send_raw_custom_notification(characteristic_handle, &notif_data->data[notif_data->offset], notif_data->sent_chunk_size))
{
TLS_BT_APPL_TRACE_ERROR("%s, ble_gattc_exchange_mtu %s" NEW_LINE, __FUNCTION__, tls_bt_rc_2_str(status));
//Transfer failed :
reset_data_being_sent(notif_data);
return false;
}
@ -305,6 +413,7 @@ int ble_gatt_mtu_cb(uint16_t conn_handle, const struct ble_gatt_error *error, ui
case 0:
TLS_BT_APPL_TRACE_DEBUG("mtu exchange complete: conn_handle=%d mtu=%d"NEW_LINE,
conn_handle, mtu);
usable_mtu = mtu - 3;
break;
default:
TLS_BT_APPL_TRACE_ERROR("Update MTU failed...error->status=%d"NEW_LINE, error->status);
@ -314,20 +423,6 @@ int ble_gatt_mtu_cb(uint16_t conn_handle, const struct ble_gatt_error *error, ui
return BLE_HS_ENOERR;
}
const char *ble_service_state_2_str(uint8_t state)
{
switch(state) {
CASE_RETURN_STR(BLE_SERVICE_MODE_STOPPED)
CASE_RETURN_STR(BLE_SERVICE_MODE_IDLE)
CASE_RETURN_STR(BLE_SERVICE_MODE_ADVERTISING)
CASE_RETURN_STR(BLE_SERVICE_MODE_CONNECTED)
CASE_RETURN_STR(BLE_SERVICE_MODE_INDICATING)
CASE_RETURN_STR(BLE_SERVICE_MODE_EXITING)
default:
return "unkown ble service state";
}
}
static bool ble_service_define_gatt(const struct ble_gatt_svc_def *gatt_svc)
{
int status = BLE_HS_ENOERR;
@ -459,7 +554,8 @@ static int ble_gap_event_cb(struct ble_gap_event *event, void *arg)
print_conn_desc(&desc);
if(desc.role == BLE_GAP_ROLE_SLAVE)
{
ble_service_state = BLE_SERVICE_MODE_CONNECTED;
_ble_service_state = BLE_SERVICE_MODE_CONNECTED;
usable_mtu = USABLE_DEFAULT_MTU;
ble_device_conn_handle = event->connect.conn_handle;
}
else
@ -483,24 +579,24 @@ static int ble_gap_event_cb(struct ble_gap_event *event, void *arg)
TLS_BT_APPL_TRACE_VERBOSE("Remote device failed to connect, advertise again"NEW_LINE);
if(!ble_service_advertise(true))
{
ble_service_state = BLE_SERVICE_MODE_IDLE;
_ble_service_state = BLE_SERVICE_MODE_IDLE;
return BLE_HS_EUNKNOWN;
}
ble_service_state = BLE_SERVICE_MODE_ADVERTISING;
_ble_service_state = BLE_SERVICE_MODE_ADVERTISING;
}
break;
case BLE_GAP_EVENT_DISCONNECT:
if(event->disconnect.conn.role != BLE_GAP_ROLE_SLAVE) return 0;
TLS_BT_APPL_TRACE_DEBUG("Server disconnect reason=%d[0x%02x],state=%s"NEW_LINE, event->disconnect.reason,event->disconnect.reason-0x200,
ble_service_state_2_str(ble_service_state));
ble_service_state_2_str(_ble_service_state));
// Don't forget to invalidate the connection handle :
ble_device_conn_handle = BLE_HS_CONN_HANDLE_NONE;
if(ble_service_state == BLE_SERVICE_MODE_EXITING)
if(_ble_service_state == BLE_SERVICE_MODE_EXITING)
{
ble_service_state = BLE_SERVICE_MODE_STOPPED;
_ble_service_state = BLE_SERVICE_MODE_STOPPED;
if((status = ble_gap_event_listener_unregister(&ble_gap_event_listener)) != BLE_HS_ENOERR)
{
TLS_BT_APPL_TRACE_WARNING("%s, ble_gap_event_listener_unregister %s"NEW_LINE, __FUNCTION__, tls_bt_rc_2_str(status));
@ -518,10 +614,10 @@ static int ble_gap_event_cb(struct ble_gap_event *event, void *arg)
TLS_BT_APPL_TRACE_VERBOSE("Service disconnect event, advertise again"NEW_LINE);
if(!ble_service_advertise(true))
{
ble_service_state = BLE_SERVICE_MODE_IDLE;
_ble_service_state = BLE_SERVICE_MODE_IDLE;
return BLE_HS_EUNKNOWN;
}
ble_service_state = BLE_SERVICE_MODE_ADVERTISING;
_ble_service_state = BLE_SERVICE_MODE_ADVERTISING;
}
break;
case BLE_GAP_EVENT_CONN_UPDATE:
@ -550,6 +646,47 @@ static int ble_gap_event_cb(struct ble_gap_event *event, void *arg)
break;
case BLE_GAP_EVENT_MTU:
TLS_BT_APPL_TRACE_VERBOSE("MTU update : %u"NEW_LINE, event->mtu.value);
usable_mtu = event->mtu.value - 3;
break;
case BLE_GAP_EVENT_NOTIFY_TX:
if(event->notify_tx.indication == 0) // Notification
{
TLS_BT_APPL_TRACE_VERBOSE("Type : notification"NEW_LINE);
if(event->notify_tx.status != BLE_HS_ENOERR)
{
TLS_BT_APPL_TRACE_WARNING("%s, notify_tx notification error %d, transfer in progress : %u"NEW_LINE, __FUNCTION__, event->notify_tx.status, notification_data.transfer_in_progress);
reset_data_being_sent(&notification_data);
return BLE_HS_EUNKNOWN;
}
TLS_BT_APPL_TRACE_DEBUG("transfer : %u"NEW_LINE"length : %u"NEW_LINE"offset : %u"NEW_LINE,
notification_data.transfer_in_progress,
notification_data.length,
notification_data.offset);
// We update the offset :
notification_data.offset += notification_data.sent_chunk_size;
if(notification_data.offset < notification_data.length) // Still data to send ?
{
if(!ble_service_send_custom_notification(event->notify_tx.attr_handle, &notification_data))
{
TLS_BT_APPL_TRACE_ERROR("Failed to send next notification chunk, aborting"NEW_LINE);
reset_data_being_sent(&notification_data);
return BLE_HS_EUNKNOWN;
}
}
else
{
TLS_BT_APPL_TRACE_VERBOSE("last data chunk sent, end of the transfer"NEW_LINE);
// All data has been sent, end of the transfer
reset_data_being_sent(&notification_data);
}
}
else // Indication
{
TLS_BT_APPL_TRACE_WARNING("Indication not yet handled"NEW_LINE);
}
break;
default:
TLS_BT_APPL_TRACE_WARNING("unhandled event !"NEW_LINE);
@ -595,16 +732,21 @@ static int gatt_nus_char_access_cb(uint16_t conn_handle, uint16_t attr_handle, s
case BLE_GATT_ACCESS_OP_WRITE_CHR:
{
struct os_mbuf *om_buf = ctxt->om;
TLS_BT_APPL_TRACE_VERBOSE("Received data : "NEW_LINE);
//TLS_BT_APPL_TRACE_VERBOSE("Received data : "NEW_LINE);
while(om_buf)
{
for(uint16_t i = 0; i < om_buf->om_len; i++)
/*for(uint16_t i = 0; i < om_buf->om_len; i++)
{
if(om_buf->om_data[i] < 32)
printf("[%u]", om_buf->om_data[i]);
else
putchar(om_buf->om_data[i]);
}
}*/
// Call the nus rx cb function if one is registered
if(_ble_service_nus_data_rx_cb)
_ble_service_nus_data_rx_cb(om_buf->om_data, om_buf->om_len);
om_buf = SLIST_NEXT(om_buf, om_next);
}
printf(NEW_LINE);
@ -631,3 +773,11 @@ static void print_conn_desc(const struct ble_gap_conn_desc *desc)
desc->sec_state.authenticated,
desc->sec_state.bonded);
}
static void reset_data_being_sent(data_being_sent_t * const data)
{
if(data)
{
memset(data, 0, sizeof(data_being_sent_t));
}
}

View File

@ -7,6 +7,8 @@
#define CASE_RETURN_STR(const) case const: return #const;
#endif
typedef void (*nus_data_rx_fn_t)(const uint8_t *data, uint16_t length);
typedef enum
{
BLE_SERVICE_MODE_STOPPED = 0x00,
@ -15,7 +17,7 @@ typedef enum
BLE_SERVICE_MODE_CONNECTED,
BLE_SERVICE_MODE_INDICATING,
BLE_SERVICE_MODE_EXITING
} ble_service_state_t;
} ble_service_state_e;
/**
* @brief Resturns the corresponding enum name as a string
@ -23,7 +25,7 @@ typedef enum
* @param state the enum value
* @return const char* the enum name as a string
*/
const char *ble_service_state_2_str(uint8_t state);
const char *ble_service_state_2_str(ble_service_state_e state);
/**
* @brief Configures and starts the BLE service
@ -41,6 +43,29 @@ bool ble_service_start(void);
*/
bool ble_service_stop(void);
/**
* @brief Check whether the ble service is running or not
*
* @return true if it is running
* @return false if it is stopped
*/
bool ble_service_is_started(void);
/**
* @brief Check whether a device is connected to the ble service or not
*
* @return true if a device is connected
* @return false if no device is connected
*/
bool ble_service_is_device_connected(void);
/**
* @brief Returns the current state of the ble service
*
* @return ble_service_state_e
*/
ble_service_state_e ble_service_get_state(void);
/**
* @brief Asks to update the current connection parameters
* /!\ A connection should be already active before calling this function.
@ -62,16 +87,6 @@ bool ble_service_update_connection_parameters(
uint16_t min_ce_len,
uint16_t max_ce_len);
/**
* @brief Sends a custom notification with the provided payload of size length
*
* @param data the data to send in the notification
* @param length the lenght in byte of the data to send
* @return true on success
* @return false on failure
*/
bool ble_service_send_custom_notification(const uint8_t *data, uint16_t length);
/**
* @brief Requests a MTU (Maximum Transmission Unit) update in order to hopefully, get something bigger than 20 bytes ...
*
@ -80,4 +95,22 @@ bool ble_service_send_custom_notification(const uint8_t *data, uint16_t length);
*/
bool ble_service_request_mtu_exchange(void);
/**
* @brief Sends the provided payload of size length using the NUS (Nordic UART Service) TX characteristic
*
* @param data the data to send through the NUS
* @param length the lenght in byte of the data to send
* @return true on success
* @return false on failure
*/
bool ble_service_nus_send_data(const uint8_t *data, uint16_t length);
/**
* @brief Registers a function which will be called every time data is received by the nus rx
* @note To unregister a callback, simply pass NULL to the function
*
* @param nus_data_rx_cb a pointer to the function to call of type nus_data_rx_fn_t
*/
void ble_service_nus_register_data_rx_cb(nus_data_rx_fn_t nus_data_rx_cb);
#endif //BLE_APP_H