Hi everyone,I am working on a project where a DA14531 device on a custom board needs to connect to a generic external BLE sensor, not part of this device family. I am using the template provided by the example BLE_SDK6_examples/connectivity/central/. Running the example, the sensor is obviously scanned (I modified some filtering settings compared to the default SCAN_FILTER_NAME with "DLG-PROXR"), but I can't figure out how to modify the code so that, when the sensor is scanned, a connection procedure starts (similar to what happens by default in target_apps/ble_examples/prox_reporter). The name ad BD address of the sensor are known.Thanks for your time!
Hi Seba,Thank you for posting your question online.Let me describe the procedure below:1) The DA14531 starts scanning. After booting up, we configure the BLE stack. When the BLE stack has been properly configured, the user_app_on_set_dev_config_complete callback function will be executed. In that callback we call the ble_scan_for_devices API.
/** **************************************************************************************** * @brief Callback from SDK once stack is configured * @return void **************************************************************************************** */ void user_app_on_set_dev_config_complete(void) { /*Advertising starts within this default handler, but we have made the callback NULL to avoid advertising. * alternate method, would be to copy the default handler code into here, removing the advertising call */ default_app_on_set_dev_config_complete(); ble_scan_for_devices(); spi_flash_power_down(); //power down flash before entering sleep }
2) While we scan, we receive the advertising reports. The reports are being handled in the user_on_adv_report_ind callback function. By default, we have set some SCAN macros so you can filter the devices you have found.
/** **************************************************************************************** * @brief Callback from SDK when we get the advertising report. * @param[in] report advertising report information * @return void **************************************************************************************** */ void user_on_adv_report_ind(struct gapm_adv_report_ind const * adv_ind) { bool dev_found = false; #if CONNECT_TO_PERIPHERAL == 1 bool conn_to_device = false; struct bd_addr dev_addr = adv_ind->report.adv_addr; #endif uint8_t num_ad_elements = user_ble_gap_get_adv_num_elements(adv_ind->report.data, adv_ind->report.data_len); ble_gap_adv_struct_t adv_data_structs[num_ad_elements]; user_ble_gap_parse_adv_data(adv_ind->report.data_len, adv_ind->report.data, adv_data_structs); uint8_t i; for(i = 0 ; i < num_ad_elements; i++) { switch(adv_data_structs[i].type) { #if SCAN_FILTER == SCAN_FILTER_NONE case GAP_AD_TYPE_FLAGS: { dev_found = true; dbg_block_printf("GAP FLAGS: %s\r\n", format_hex_string(adv_data_structs[i].data, adv_data_structs[i].len) ); break; } #endif #if (SCAN_FILTER == SCAN_FILTER_NONE) case GAP_AD_TYPE_MORE_16_BIT_UUID: { dev_found = true; dbg_block_printf("INCOMP LIST 16-BIT SVC: %s\r\n", format_hex_string(adv_data_structs[i].data, adv_data_structs[i].len) ); break; } #endif #if (SCAN_FILTER == (SCAN_FILTER_NAME) || SCAN_FILTER == SCAN_FILTER_NONE) case GAP_AD_TYPE_COMPLETE_NAME: { dev_found = true; char local_name[adv_data_structs[i].len + 1]; format_adv_string(adv_data_structs[i].data,adv_data_structs[i].len, local_name); dbg_block_printf("Device Local Name: %s\r\n", local_name); #if CONNECT_TO_PERIPHERAL == 1 conn_to_device = memcmp(PERIPH_MATCH_DATA, adv_data_structs[i].data, PERIPH_MATCH_DATA_LEN) ? false : true; #endif break; } #endif #if SCAN_FILTER == SCAN_FILTER_NONE case GAP_AD_TYPE_TRANSMIT_POWER: { dev_found = true; int8_t power; memcpy(&power, adv_data_structs[i].data, 1); dbg_block_printf("TX_POWER: %i dBm\r\n", power); break; } #endif #if (SCAN_FILTER == SCAN_FILTER_16_BIT_SVC_DATA || SCAN_FILTER == SCAN_FILTER_NONE) case GAP_AD_TYPE_SERVICE_16_BIT_DATA: { dev_found = true; uint16_t UUID; memcpy(&UUID, adv_data_structs[i].data, sizeof(uint16_t)); dbg_block_printf("GAP_TYPE: SVC_DATA_16_BIT: UUID:%04X DATA:%s\r\n", UUID, format_hex_string(adv_data_structs[i].data+2, adv_data_structs[i].len-2) ); break; } #endif #if (SCAN_FILTER == SCAN_FILTER_MFG_DATA || SCAN_FILTER == SCAN_FILTER_NONE) case GAP_AD_TYPE_MANU_SPECIFIC_DATA: { dev_found = true; dbg_block_printf("MFG_SPECIFIC_DATA: %s\r\n", format_hex_string(adv_data_structs[i].data, adv_data_structs[i].len) ); break; } #endif default: { #if SCAN_FILTER == SCAN_FILTER_NONE dev_found = true; dbg_block_printf("GAP Type: %02x, Data: %s\r\n", adv_data_structs[i].type, format_hex_string(adv_data_structs[i].data, adv_data_structs[i].len) ); #endif break; } } } if(dev_found){ uint8_t rssi_abs = 256 - adv_ind->report.rssi; dbg_block_printf("RSSI: -%d\r\n", rssi_abs); dbg_block_printf("BD_ADDR:%s \r\n---------------END_ADV-----------\r\n", format_bd_address(&adv_ind->report.adv_addr) ); } #if CONNECT_TO_PERIPHERAL if(conn_to_device) { dbg_block_printf("Connecting to Device...\r\n", NULL); central_app_env.connect_to_periph = true; memcpy(¢ral_app_env.connect_to_addr, &dev_addr, sizeof(struct bd_addr)); central_app_env.connect_to_addr_type = adv_ind->report.adv_addr_type; user_ble_gap_stop_scan(); } #endif }
When we find the correct name that matches with the one declared on PERIPH_MATCH_DATA we set the conn_to_device variable to TRUE. This will result in storing this specific advertising report information and call the user_ble_gap_stop_scan API to stop the scan procedure.
3) When the Scan procedure stops, the user_on_scanning_completed callback function is executed. In that callback function we use the user_ble_gap_connect API to initiate the connection.
/** **************************************************************************************** * @brief Callback from SDK when scan completed. * @param[in] reason reason for stopping the scan procedure * @return void **************************************************************************************** */ void user_on_scanning_completed(uint8_t reason) { if(reason == GAP_ERR_CANCELED) { user_ble_gap_connect(central_app_env.connect_to_addr.addr, central_app_env.connect_to_addr_type); central_app_env.connection_timer = app_easy_timer(CONNECTION_TIMEOUT_10MS, connection_timeout_cb); } else{ dbg_printf("%s: ERR: reason: %d\r\n", __func__, reason); } }
Thank you very much; I can now connect the DA14531 and the external sensor.
The external sensor operates via AT commands, so I want to periodically send certain AT commands, receive the responses, and pass them via UART to a host microcontroller. My approach is as follows:
void user_on_connection(uint8_t connection_idx, struct gapc_connection_req_ind const *param) { [...] user_gatt_discover_all_services(connection_idx, 1); periodic_timer = app_easy_timer(5000, timer_callback_function); }
timer_callback_function
From the output terminal, I see that the sensor connection index is 0x0000, the service of interest is 0xfff0, and the characteristic of interest is 0xfff2, which is the only one I can write to. For istance, the output terminal shows:
0x0000
0xfff0
0xfff2
handle_svc_ind: conn_idx=0000 start_h=0013 end_h=0018 0xfff0: 0015 char 0xfff1 prop=12 (-R--N---) 0018 char 0xfff2 prop=0c (--XW----) Completion Event: GATTC_DISC_ALL_SVC: status: 0000 Completion Event: GATTC_WRITE
So, for timer_callback_function(), my plan is to:
timer_callback_function()
- Write the AT commands, example:
user_ble_gatt_write(GATTC_WRITE_NO_RESPONSE, 0x0000, 0xfff2, at_command, data_len);
periodic_timer = app_easy_timer(5000, timer_callback_function);
dbg_printf
Thank youfor your time,
Regards.
Hi Seba,Thank you for the reply.
seba21 said:At the end of the connection, start a timer after discovering all the services:
I would personally start or call directly a function to control the AT commands on the static void handle_service_disc_finished(uint8_t con_idx) callback function that will be executed when all available services have been discovered.
You are also utilizing the user_ble_gatt_write API the wrong way. Please refer on the user_ble_gatt.c/h files:
/** **************************************************************************************** * @brief Perform a gatt write * @param[in] con_idx - connection identifier * @param[in] handle - attribute handle to write * @param[in] data - data to write * @param[in] data_len - data len * @return void **************************************************************************************** */ void user_ble_gatt_write(uint8_t op, uint8_t con_idx, uint16_t handle, uint8_t *data, uint16_t data_len);
user_ble_gatt_write(GATTC_WRITE_NO_RESPONSE, connection_idx, 0x0018, at_command, data_len);
Where connection_idx variable has been taken from the user_on_connection callback function and 0x0018 corresponds to the char 0xfff2 with the Write Permission.Best Regards,OV_Renesas
All clear, thanks!
To recap, I need to work with the following characteristics (verified through a smartphone app):
For my goal of sending AT commands and directly printing the responses on the terminal, I imagine I need to enable the notification service.
Currently, based on your suggestion, I have only added the following line:
user_ble_gatt_write(GATTC_WRITE_NO_RESPONSE, con_idx, 0x0018, at_command, data_len);
in the static void handle_service_disc_finished(uint8_t con_idx) function.
static void handle_service_disc_finished(uint8_t con_idx)
This alone of course doesn’t produce any output on the terminal.
By adding the following command:
user_gatt_read_simple(con_idx, 0x0015);
along with the modification in user_catch_rest_hndl:
user_catch_rest_hndl
case GATTC_READ_IND: { struct gattc_read_ind const *ind = (struct gattc_read_ind const*)param; #ifdef ENABLE_BAS if(ind->handle == central_app_env.periph_devices[conn_idx].serv_disc.bas_char.c.value_handle) { dbg_printf("Battery Level Read: %d \r\n", ind->value[0]); }else #endif { dbg_printf("GATTC_READ_IND: handle: %04x\r\n", ind->handle); for (int i = 0; i < ind->length; i++) { dbg_printf("%02X ", ind->value[i]); } } }break;
I get the following on the terminal:
Completion Event: GATTC_WRITE
GATTC_READ_IND: handle: 0015
GATTC_READ: status: 0000
So, the reading is successful.
Is there a way to achieve this using the notification service, without having to perform a direct read?Thank you, and I apologize for the continued disturbance.
seba21 said:For my goal of sending AT commands and directly printing the responses on the terminal, I imagine I need to enable the notification service.
Just for clarification: Notifications are not a service. Notify is a Permission of the Service or the Characteristics. From the central side you can subscribe or unsubscribe in order to receive Notifications.This can be achieved with the following API:
/** **************************************************************************************** * @brief Register for notifications/indications for specific service * @param[in] con_idx - connection id * @param[in] svc - service to parse * @return void **************************************************************************************** */ void user_register_notification(uint8_t conidx, struct gattc_sdp_svc_ind const *disc_svc) { if( disc_svc->start_hdl != ATT_INVALID_HANDLE) { //register profile task in gatt for indication/notifications struct gattc_reg_to_peer_evt_cmd * reg = KE_MSG_ALLOC(GATTC_REG_TO_PEER_EVT_CMD, KE_BUILD_ID(TASK_GATTC, conidx), TASK_APP, gattc_reg_to_peer_evt_cmd); reg->operation = GATTC_REGISTER; reg->start_hdl = disc_svc->start_hdl; reg->end_hdl = disc_svc->end_hdl; ke_msg_send(reg); } } //On the header file: void user_register_notification(uint8_t conidx, struct gattc_sdp_svc_ind const *disc_svc);
Good Morning,To recap, I inserted the function you suggested here in user_catch_rest_hndl: case GATTC_SDP_SVC_IND: { struct gattc_sdp_svc_ind const *disc_svc = (struct gattc_sdp_svc_ind const *)(param); uint8_t con_idx = KE_IDX_GET(src_id); if (disc_svc->start_hdl == 0x0013 && disc_svc->end_hdl == 0x0018) { dbg_printf("Service 0xfff0 found, registering for notifications...\r\n", NULL); user_register_notification(con_idx, disc_svc); } handle_svc_ind(con_idx, disc_svc); }break;
case GATTC_SDP_SVC_IND: { struct gattc_sdp_svc_ind const *disc_svc = (struct gattc_sdp_svc_ind const *)(param); uint8_t con_idx = KE_IDX_GET(src_id); if (disc_svc->start_hdl == 0x0013 && disc_svc->end_hdl == 0x0018) { dbg_printf("Service 0xfff0 found, registering for notifications...\r\n", NULL); user_register_notification(con_idx, disc_svc); } handle_svc_ind(con_idx, disc_svc); }break;
with the goal of subscribing to the service discussed in previous questions for receiving notification.
In the static void handle_service_disc_finished function, I inserted the usual command:
static void handle_service_disc_finished
user_ble_gatt_write(GATTC_WRITE, 0x0000, 0x0018, &temp, sizeof(temp));
which I am sure works correctly, as the sensor receives it and lights up some LEDs.
At this point, with notifications enabled and the following modified section:
case GATTC_READ_IND: { struct gattc_read_ind const *ind = (struct gattc_read_ind const*)param; #ifdef ENABLE_BAS if(ind->handle == central_app_env.periph_devices[conn_idx].serv_disc.bas_char.c.value_handle) { dbg_printf("Battery Level Read: %d \r\n", ind->value[0]); }else #endif { dbg_printf("GATTC_READ_IND: handle: %04x\r\n", ind->handle); for (int i = 0; i < ind->length; i++) { dbg_printf("%04x\r\n", ind->value[i]); // Stampa i dati in formato esadecimale } } }break;
Should I expect that the data from the characteristic with handle 0x0015 will be notified and printed on the screen?This is very important because I need to interface a host microcontroller with the external sensor via BLE. Therefore, I have to monitor the entire process to see and understand what data are being sent.Thank for your time.
0x0015
Hi Seba,Thank you for the reply.I would personally subscribe on the notifications of this specific service on the handle_svc_ind function when we discover the services. But I believe your implementation should work as well.To verify if it worked, please add the following case on the user_catch_rest_hndl:
case GATTC_REGISTER: { dbg_printf("GATTC_REGISTER: status: %04x \r\n", evt->status); } break;
seba21 said:Should I expect that the data from the characteristic with handle 0x0015 will be notified and printed on the screen?
You are checking on the case related to the Read request.You should check on the GATTC_EVENT_IND
case GATTC_EVENT_IND: { struct gattc_event_ind const *ind = (struct gattc_event_ind const *)param; #ifdef ENABLE_BAS if(ind->handle == central_app_env.periph_devices[conn_idx].serv_disc.bas_char.c.value_handle) { dbg_printf("Battery Level Changed: %d \r\n", ind->value[0]); } #endif }break;
The Proximity reporter example has the Battery Service implemented, which will sent notifications when the Battery level has changed. The Central receives the Notification on the GATTC_EVENT_IND case. Please add another if statement that compares your characteristic UUID with the ind->handle and then you can print out the data you received from the ind->value variable.Best Regards,OV_Renesas
Thank you, this was very useful. Now I can send AT commands and view the corresponding responses. Thank you very much for your valuable guidance!!Regards.