diff --git a/app/ble/ble_service.c b/app/ble/ble_service.c index a4af173..acc0d20 100644 --- a/app/ble/ble_service.c +++ b/app/ble/ble_service.c @@ -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, ¬ification_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, ¬if_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(¬ification_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, ¬ification_data)) + { + TLS_BT_APPL_TRACE_ERROR("Failed to send next notification chunk, aborting"NEW_LINE); + reset_data_being_sent(¬ification_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(¬ification_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); @@ -630,4 +772,12 @@ static void print_conn_desc(const struct ble_gap_conn_desc *desc) desc->sec_state.encrypted, 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)); + } } \ No newline at end of file diff --git a/app/ble/ble_service.h b/app/ble/ble_service.h index a1db06f..bcc0412 100644 --- a/app/ble/ble_service.h +++ b/app/ble/ble_service.h @@ -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 \ No newline at end of file