I'm writing a central app using the pc-ble-driver project with an nRF51 dongle. This is to test our product, which is a peripheral using the nRF51822. The test central needs to do service discovery for our own vendor-specific service only, see four characteristics on that service, enable notifications on one of them, the log some output.
The problem is that after calling sd_ble_gattc_characteristics_discover() with a range of characteristics, I only get a characteristic discovery response with one characteristic on it.
Here's the printf() output:
Adv packet from: 0xF802D8BD76AC
Attempting connection to: 0xF802D8BD76AC
Adv packet from: 0xF802D8BD76AC
Attempting connection to: 0xF802D8BD76AC
Connected.
Discovering primary services.
Got service discovery response.
Discovered Bike Tracker service. UUID: 0x1523, start handle: 0x0015, end handle:
0x001E
Discovering characteristics.
Got characteristic discovery response. Characteristics count: 1
Characteristic handle: 0x0016, UUID: 0x1525.
And here's the central code, mostly based on the heart rate collector example in the pc-ble-driver repo.
#include "ble.h"
#include "sd_rpc.h"
#include "ble_central.h"
#include "ble_bts.h"
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
// We're not interested in warnings about sprintf() and strcat().
#pragma warning(disable:4996)
// Defined in main.c.
extern adapter_t * m_adapter;
extern char * m_peripheral_mac_address;
static uint8_t m_connected_devices = 0;
static uint16_t m_connection_handle = 0;
static uint16_t m_service_start_handle = 0;
static uint16_t m_service_end_handle = 0;
static uint16_t m_bts_log_char_handle = 0;
static uint16_t m_bts_log_cccd_handle = 0;
static bool m_connection_is_in_progress = false;
static const ble_gap_scan_params_t m_scan_param =
{
1, // Active scanning set.
0, // Selective scanning not set.
NULL, // White-list not set.
(uint16_t)SCAN_INTERVAL,
(uint16_t)SCAN_WINDOW,
(uint16_t)SCAN_TIMEOUT
};
static const ble_gap_conn_params_t m_connection_param =
{
(uint16_t)MIN_CONNECTION_INTERVAL,
(uint16_t)MAX_CONNECTION_INTERVAL,
(uint16_t)SLAVE_LATENCY,
(uint16_t)CONNECTION_SUPERVISION_TIMEOUT
};
static void on_adv_report(const ble_gap_evt_t * const p_ble_gap_evt)
{
uint32_t err_code;
uint8_t str[STRING_BUFFER_SIZE] = { 0 };
// Log the MAC address for each advertisement packet received.
ble_address_to_string_convert(p_ble_gap_evt->params.adv_report.peer_addr, str);
printf("Adv packet from: 0x%s\n", str);
fflush(stdout);
// If this packet is from the peripheral with the MAC address passed in on the command line, connect to it.
if (strcmp(str, m_peripheral_mac_address) == 0)
{
printf("Attempting connection to: 0x%s\n", str);
fflush(stdout);
if (m_connected_devices >= MAX_PEER_COUNT || m_connection_is_in_progress)
{
return;
}
err_code = sd_ble_gap_connect(m_adapter, &(p_ble_gap_evt->params.adv_report.peer_addr), &m_scan_param, &m_connection_param);
if (err_code != NRF_SUCCESS)
{
printf("Connection request failed. Reason: %d\n", err_code);
fflush(stdout);
return;
}
m_connection_is_in_progress = true;
}
// Now expect a call to on_connected(), via ble_evt_dispatch().
}
static void on_timeout(const ble_gap_evt_t * const p_ble_gap_evt)
{
if (p_ble_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN)
{
m_connection_is_in_progress = false;
}
else if (p_ble_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_SCAN)
{
scan_start();
}
}
static void on_connected(const ble_gap_evt_t * const p_ble_gap_evt)
{
printf("Connected.\n");
fflush(stdout);
m_connected_devices++;
m_connection_handle = p_ble_gap_evt->conn_handle;
m_connection_is_in_progress = false;
// Discover services.
service_discovery_start();
}
static void on_service_discovery_response(const ble_gattc_evt_t * const p_ble_gattc_evt)
{
printf("Got service discovery response.\n");
fflush(stdout);
int count;
int service_index;
const ble_gattc_service_t * service;
if (p_ble_gattc_evt->gatt_status != NRF_SUCCESS)
{
printf("Service discovery failed. Error code 0x%X\n", p_ble_gattc_evt->gatt_status);
fflush(stdout);
return;
}
count = p_ble_gattc_evt->params.prim_srvc_disc_rsp.count;
if (count == 0)
{
printf("Service not found\n");
fflush(stdout);
return;
}
if (count > 1)
{
printf("Warning, discovered multiple primary services. Ignoring all but the first.\n");
fflush(stdout);
}
// We expect to discover only the Bike Tracker service as requested.
service_index = 0;
service = &(p_ble_gattc_evt->params.prim_srvc_disc_rsp.services[service_index]);
if (service->uuid.uuid != BTS_UUID_SERVICE)
{
printf("Wrong service discovered with UUID: 0x%04X\n", service->uuid.uuid);
fflush(stdout);
return;
}
m_service_start_handle = service->handle_range.start_handle;
m_service_end_handle = service->handle_range.end_handle;
printf("Discovered Bike Tracker service. UUID: 0x%04X, start handle: 0x%04X, end handle: 0x%04X\n", service->uuid.uuid, m_service_start_handle, m_service_end_handle);
fflush(stdout);
// Start characteristic discovery for this service.
char_discovery_start();
}
// TODO: We only get the first characteristic come through here. Why? What happens to the rest?
static void on_characteristic_discovery_response(const ble_gattc_evt_t * const p_ble_gattc_evt)
{
int count = p_ble_gattc_evt->params.char_disc_rsp.count;
printf("Got characteristic discovery response. Characteristics count: %d\n", count);
fflush(stdout);
if (p_ble_gattc_evt->gatt_status != NRF_SUCCESS)
{
printf("Characteristic discovery failed. Error: 0x%X.\n", p_ble_gattc_evt->gatt_status);
fflush(stdout);
return;
}
ble_gattc_char_t c;
for (int i = 0; i < count; i++)
{
c = p_ble_gattc_evt->params.char_disc_rsp.chars[i];
printf("Characteristic handle: 0x%04X, UUID: 0x%04X.\n", c.handle_decl, c.uuid.uuid);
fflush(stdout);
if (p_ble_gattc_evt->params.char_disc_rsp.chars[i].uuid.uuid == BTS_UUID_LOG_CHAR)
{
printf("Bike Tracker log characteristic.\n");
fflush(stdout);
m_bts_log_char_handle = p_ble_gattc_evt->params.char_disc_rsp.chars[i].handle_decl;
// Only the log characteristic has a descriptor, I think. The others don't. Don't try to discovery descriptors until we've seen the log characteristic.
descr_discovery_start();
}
}
// Wrong. Endless loop.
/*
if (c.handle_value < m_service_end_handle)
{
// Discover the next characteristic.
char_discovery_start();
}
*/
}
static void on_descriptor_discovery_response(const ble_gattc_evt_t * const p_ble_gattc_evt)
{
int count = p_ble_gattc_evt->params.desc_disc_rsp.count;
printf("Got descriptor discovery response. Descriptor count: %d\n", count);
fflush(stdout);
if (p_ble_gattc_evt->gatt_status != NRF_SUCCESS)
{
printf("Descriptor discovery failed. Error code 0x%X\n", p_ble_gattc_evt->gatt_status);
fflush(stdout);
return;
}
for (int i = 0; i < count; i++)
{
printf("Descriptor handle: 0x%04X, UUID: 0x%04X\n", p_ble_gattc_evt->params.desc_disc_rsp.descs[i].handle, p_ble_gattc_evt->params.desc_disc_rsp.descs[i].uuid.uuid);
fflush(stdout);
if (p_ble_gattc_evt->params.desc_disc_rsp.descs[i].uuid.uuid == BLE_UUID_CCCD)
{
m_bts_log_cccd_handle = p_ble_gattc_evt->params.desc_disc_rsp.descs[i].handle;
printf("Press enter to toggle notifications on the log characteristic.\n");
fflush(stdout);
}
}
}
static void on_write_response(const ble_gattc_evt_t * const p_ble_gattc_evt)
{
printf("Got write response.\n");
fflush(stdout);
if (p_ble_gattc_evt->gatt_status != NRF_SUCCESS)
{
printf("Error. Write operation failed. Error code 0x%X\n", p_ble_gattc_evt->gatt_status);
fflush(stdout);
}
}
static void on_hvx(const ble_gattc_evt_t * const p_ble_gattc_evt)
{
// Bike Tracker log characteristic and CCCD.
if (p_ble_gattc_evt->params.hvx.handle >= m_bts_log_char_handle || p_ble_gattc_evt->params.hvx.handle <= m_bts_log_cccd_handle)
{
// TODO: Handle variable length log notifications. Do whatever the Android app does here.
printf("Received heart rate measurement: %d\n", p_ble_gattc_evt->params.hvx.data[1]);
}
else
{
printf("Un-parsed data received on handle: %04X\n", p_ble_gattc_evt->params.hvx.handle);
}
fflush(stdout);
}
static void on_conn_params_update_request(const ble_gap_evt_t * const p_ble_gap_evt)
{
uint32_t err_code = sd_ble_gap_conn_param_update(m_adapter, m_connection_handle, &(p_ble_gap_evt->params.conn_param_update_request.conn_params));
if (err_code != NRF_SUCCESS)
{
printf("Conn params update failed. Error: %d\n", err_code);
fflush(stdout);
}
}
static void ble_address_to_string_convert(ble_gap_addr_t address, uint8_t * string_buffer)
{
const int address_length = 6;
char temp_str[3];
for (int i = address_length - 1; i >= 0; --i)
{
sprintf(temp_str, "%02X", address.addr[i]);
strcat((char *)string_buffer, temp_str);
}
}
static uint32_t adv_report_parse(uint8_t type, data_t * p_advdata, data_t * p_typedata)
{
uint32_t index = 0;
uint8_t * p_data;
p_data = p_advdata->p_data;
while (index < p_advdata->data_len)
{
uint8_t field_length = p_data[index];
uint8_t field_type = p_data[index + 1];
if (field_type == type)
{
p_typedata->p_data = &p_data[index + 2];
p_typedata->data_len = field_length - 1;
return NRF_SUCCESS;
}
index += field_length + 1;
}
return NRF_ERROR_NOT_FOUND;
}
adapter_t * adapter_init(char * serial_port)
{
physical_layer_t * phy;
data_link_layer_t * data_link_layer;
transport_layer_t * transport_layer;
phy = sd_rpc_physical_layer_create_uart(serial_port, BAUD_RATE, SD_RPC_FLOW_CONTROL_NONE, SD_RPC_PARITY_NONE);
data_link_layer = sd_rpc_data_link_layer_create_bt_three_wire(phy, 100);
transport_layer = sd_rpc_transport_layer_create(data_link_layer, 100);
return sd_rpc_adapter_create(transport_layer);
}
uint32_t ble_stack_init()
{
uint32_t err_code;
ble_enable_params_t ble_enable_params;
uint32_t * app_ram_base = NULL;
memset(&ble_enable_params, 0, sizeof(ble_enable_params));
ble_enable_params.gatts_enable_params.attr_tab_size = BLE_GATTS_ATTR_TAB_SIZE_DEFAULT;
ble_enable_params.gatts_enable_params.service_changed = false;
ble_enable_params.gap_enable_params.periph_conn_count = 1;
ble_enable_params.gap_enable_params.central_conn_count = 1;
ble_enable_params.gap_enable_params.central_sec_count = 1;
ble_enable_params.common_enable_params.p_conn_bw_counts = NULL;
ble_enable_params.common_enable_params.vs_uuid_count = 1;
err_code = sd_ble_enable(m_adapter, &ble_enable_params, app_ram_base);
switch (err_code) {
case NRF_SUCCESS:
break;
case NRF_ERROR_INVALID_STATE:
printf("BLE stack already enabled.\n");
fflush(stdout);
break;
default:
printf("Failed to enable BLE stack. Error code: %d\n", err_code);
fflush(stdout);
break;
}
return err_code;
}
uint32_t ble_options_set()
{
ble_opt_t opt;
ble_common_opt_t common_opt;
common_opt.conn_bw.role = BLE_GAP_ROLE_CENTRAL;
common_opt.conn_bw.conn_bw.conn_bw_rx = BLE_CONN_BW_HIGH;
common_opt.conn_bw.conn_bw.conn_bw_tx = BLE_CONN_BW_HIGH;
opt.common_opt = common_opt;
return sd_ble_opt_set(m_adapter, BLE_COMMON_OPT_CONN_BW, &opt);
}
uint32_t scan_start()
{
uint32_t error_code = sd_ble_gap_scan_start(m_adapter, &m_scan_param);
if (error_code != NRF_SUCCESS)
{
printf("Scan start failed.\n");
fflush(stdout);
}
else
{
printf("Scan started.\n");
fflush(stdout);
}
return error_code;
}
static uint32_t service_discovery_start()
{
uint32_t err_code;
uint16_t start_handle = 0x01;
ble_uuid_t srvc_uuid;
printf("Discovering primary services.\n");
fflush(stdout);
ble_uuid128_t base_uuid = { BTS_UUID_BASE };
srvc_uuid.type = BLE_UUID_TYPE_VENDOR_BEGIN;
srvc_uuid.uuid = BTS_UUID_SERVICE;
err_code = sd_ble_uuid_vs_add(m_adapter, &base_uuid, &srvc_uuid.type);
if (err_code != NRF_SUCCESS)
{
printf("Failed to add the vendor-specific Bike Tracker service.\n");
fflush(stdout);
return 4002;
}
// Find the Bike Tracker service. Calls back to on_service_discovery_response() via ble_evt_dispatch().
err_code = sd_ble_gattc_primary_services_discover(m_adapter, m_connection_handle, start_handle, &srvc_uuid);
if (err_code != NRF_SUCCESS)
{
printf("Failed to start GATT primary service discovery.\n");
fflush(stdout);
}
return err_code;
}
static uint32_t char_discovery_start()
{
ble_gattc_handle_range_t handle_range;
printf("Discovering characteristics.\n");
fflush(stdout);
handle_range.start_handle = m_service_start_handle; // 0x0015
handle_range.end_handle = m_service_end_handle; // 0x001E
// Calls back to on_characteristic_discovery_response(), via ble_evt_dispatch().
return sd_ble_gattc_characteristics_discover(m_adapter, m_connection_handle, &handle_range);
}
static uint32_t descr_discovery_start()
{
ble_gattc_handle_range_t handle_range;
printf("Discovering characteristic's descriptors.\n");
fflush(stdout);
if (m_bts_log_char_handle == 0)
{
printf("No Bike Tracker log characteristic handle found.\n");
fflush(stdout);
return NRF_ERROR_INVALID_STATE;
}
handle_range.start_handle = m_bts_log_char_handle;
handle_range.end_handle = m_service_end_handle;
// Calls back to on_descriptor_discovery_response(), via ble_evt_dispatch().
return sd_ble_gattc_descriptors_discover(m_adapter, m_connection_handle, &handle_range);
}
// TODO: Convert to equivalent for our log characteristic.
uint32_t log_cccd_set(uint8_t value)
{
ble_gattc_write_params_t write_params;
uint8_t cccd_value[2] = { value, 0 };
printf("Setting log CCCD.\n");
fflush(stdout);
if (m_bts_log_cccd_handle == 0)
{
printf("Error. No CCCD handle has been found.\n");
fflush(stdout);
return NRF_ERROR_INVALID_STATE;
}
write_params.handle = m_bts_log_cccd_handle;
write_params.len = 2;
write_params.p_value = cccd_value;
write_params.write_op = BLE_GATT_OP_WRITE_REQ;
write_params.offset = 0;
return sd_ble_gattc_write(m_adapter, m_connection_handle, &write_params);
}
void ble_evt_dispatch(adapter_t * adapter, ble_evt_t * p_ble_evt)
{
if (p_ble_evt == NULL)
{
printf("Received an empty BLE event.\n");
fflush(stdout);
return;
}
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
on_connected(&(p_ble_evt->evt.gap_evt));
break;
case BLE_GAP_EVT_DISCONNECTED:
printf("Disconnected. Reason: 0x%02X\n", p_ble_evt->evt.gap_evt.params.disconnected.reason);
fflush(stdout);
m_connected_devices--;
m_connection_handle = 0;
break;
case BLE_GAP_EVT_ADV_REPORT:
on_adv_report(&(p_ble_evt->evt.gap_evt));
break;
case BLE_GAP_EVT_TIMEOUT:
on_timeout(&(p_ble_evt->evt.gap_evt));
break;
case BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP:
on_service_discovery_response(&(p_ble_evt->evt.gattc_evt));
break;
case BLE_GATTC_EVT_CHAR_DISC_RSP:
on_characteristic_discovery_response(&(p_ble_evt->evt.gattc_evt));
break;
case BLE_GATTC_EVT_DESC_DISC_RSP:
on_descriptor_discovery_response(&(p_ble_evt->evt.gattc_evt));
break;
case BLE_GATTC_EVT_WRITE_RSP:
on_write_response(&(p_ble_evt->evt.gattc_evt));
break;
case BLE_GATTC_EVT_HVX:
on_hvx(&(p_ble_evt->evt.gattc_evt));
break;
case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
on_conn_params_update_request(&(p_ble_evt->evt.gap_evt));
break;
default:
printf("Unhandled BLE event with ID: %d\n", p_ble_evt->header.evt_id);
fflush(stdout);
break;
}
}
And here's what nRF Connect sees on the peripheral.