This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

sd_ble_gattc_characteristics_discover() only discovers one characteristic

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.

image description

Parents
  • You start with the char's attribute handle + 1 as the start and the end is the original end you used for your first call (the end handle of the service) in this case it's 0x001E.

    If you want to read more have a look at section 4.6 Vol 3 part G in the Bluetooth core spec v5.0 (note there is a small bug in the figure in the spec, I think it the second call should start with 0x0213)

Reply
  • You start with the char's attribute handle + 1 as the start and the end is the original end you used for your first call (the end handle of the service) in this case it's 0x001E.

    If you want to read more have a look at section 4.6 Vol 3 part G in the Bluetooth core spec v5.0 (note there is a small bug in the figure in the spec, I think it the second call should start with 0x0213)

Children
No Data
Related