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

Zephyr Central to Peripheral communication

Hi,

I have some issues understanding exactly how a BT central acting as a Client can get the info it wants from a BT peripheral acting as a Server. I have a custom periheral (running zephyr BLE on a nrf52840) that I want to communicate with a custom central (same thing, nrf52840).

The custom peripheral I have managed to get working, and it consist of a single service where a client can write to one characteristic and the client can read what has been written in another characteristic. The code is as follows:

Peripheral:

read_write_service.c:

#define BT_UUID_READ_WRITE_SERVICE                                          \
  BT_UUID_DECLARE_128(0x3E, 0x09, 0x99, 0x10, 0x29, 0x3F, 0x11, 0xE4, 0x93, \
                      0xBD, 0xAF, 0xD0, 0xFE, 0x6D, 0x1D, 0xA0)
// input to the peripheral device
#define BT_UUID_INPUT                                                       \
  BT_UUID_DECLARE_128(0x3E, 0x09, 0x99, 0x11, 0x29, 0x3F, 0x11, 0xE4, 0x93, \
                      0xBD, 0xAF, 0xD0, 0xFE, 0x6D, 0x1D, 0xA1)
// output from the peripheral device
#define BT_UUID_OUTPUT                                                      \
  BT_UUID_DECLARE_128(0x3E, 0x09, 0x99, 0x11, 0x29, 0x3F, 0x11, 0xE4, 0x93, \
                      0xBD, 0xAF, 0xD0, 0xFE, 0x6D, 0x1D, 0xA2)
                     
static struct bt_gatt_attr attrs[] = {
    BT_GATT_PRIMARY_SERVICE(BT_UUID_READ_WRITE_SERVICE),
    BT_GATT_CHARACTERISTIC(BT_UUID_INPUT, BT_GATT_CHRC_WRITE,
                           BT_GATT_PERM_WRITE, NULL, write_input, NULL),
    BT_GATT_CHARACTERISTIC(BT_UUID_OUTPUT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
                           read_output, NULL, NULL),
};

static struct bt_gatt_service read_write_service = BT_GATT_SERVICE(attrs);

u8_t a_number = 0;

void read_write_service_init() {
  bt_gatt_service_register(&read_write_service);
}

ssize_t write_input(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                    const void *buf, u16_t len, u16_t offset, u8_t flags) {
  const u8_t *new_number = buf;
  if (!len) {
    return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
  }

  if (*new_number >= 0 && *new_number <= 10) {
    a_number = *new_number;
  } else {
    return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED);
  }

  printk("write_input [%d]\n", a_number);
  return len;
}

ssize_t read_output(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                    void *buf, u16_t len, u16_t offset) {
  printk("read_output\n");
  return bt_gatt_attr_read(conn, attr, buf, len, offset, &a_number,
                           sizeof(a_number));
}
main.c:
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)

struct bt_conn *default_conn;

static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};

static void connected(struct bt_conn *conn, u8_t err) {
  if (err) {
    printk("Connection failed (err %u)\n", err);
  } else {
    printk("Connected\n");
    default_conn = bt_conn_ref(conn);
  }
}

static void disconnected(struct bt_conn *conn, u8_t reason) {
  printk("Disconnected (reason %u)\n", reason);

  if (default_conn) {
    bt_conn_unref(default_conn);
    default_conn = NULL;
  }
}

static struct bt_conn_cb conn_callbacks = {
    .connected = connected,
    .disconnected = disconnected,
};

static void bt_ready(int err) {
  if (err) {
    printk("Bluetooth init failed (err %d)\n", err);
    return;
  }

  read_write_service_init();

  /*if (IS_ENABLED(CONFIG_SETTINGS)) {
    settings_load();
  }*/

  err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), NULL, 0);
  if (err) {
    printk("Advertising failed to start (err %d)\n", err);
    return;
  }

  printk("Advertising successfully started\n");
}

void main(void) {
  printk("starting in main \n\n");

  int err;

  err = bt_enable(bt_ready);
  if (err) {
    printk("Bluetooth initialization failed.");
    return;
  }

  bt_conn_cb_register(&conn_callbacks);
}
Now, this is where my question begins: How can I read and write to the custom peripheral with a custom central? There are several perihperal guides and virtually none for a zephyr BLE central, just example code which is a bit hard to grapple. What I want to do with my custom central is:
- Do a service discovery and prompt to terminal a list of all services
- Do a characteristic discovery inside each service and prompt to terminal which characteristics can be read or written to
- Do an actual read and write to characteristics in the peripheral from the central.
This is how far I've gotten on the central side:
#define BT_UUID_READ_WRITE_SERVICE                                             \
	BT_UUID_DECLARE_128(0x3E, 0x09, 0x99, 0x10, 0x29, 0x3F, 0x11, 0xE4,    \
			    0x93, 0xBD, 0xAF, 0xD0, 0xFE, 0x6D, 0x1D, 0xA0)
// input to the peripheral device
#define BT_UUID_INPUT                                                          \
	BT_UUID_DECLARE_128(0x3E, 0x09, 0x99, 0x11, 0x29, 0x3F, 0x11, 0xE4,    \
			    0x93, 0xBD, 0xAF, 0xD0, 0xFE, 0x6D, 0x1D, 0xA1)
// output from the peripheral device
#define BT_UUID_OUTPUT                                                         \
	BT_UUID_DECLARE_128(0x3E, 0x09, 0x99, 0x11, 0x29, 0x3F, 0x11, 0xE4,    \
			    0x93, 0xBD, 0xAF, 0xD0, 0xFE, 0x6D, 0x1D, 0xA2)

#define BT_UUID_NAMING_CHARACTERISTIC                                          \
	BT_UUID_DECLARE_128(0x3E, 0x09, 0x99, 0x11, 0x29, 0x3F, 0x11, 0xE4,    \
			    0x93, 0xBD, 0xAF, 0xD0, 0xFE, 0x6D, 0x1D, 0xA3)

static void start_scan(void);

static struct bt_conn *default_conn;

static u8_t discovered(struct bt_conn *conn, const struct bt_gatt_attr *attr,
		       struct bt_gatt_discover_params *params);

static struct bt_gatt_discover_params discover_params;
static struct bt_uuid_128 uuid = BT_UUID_INIT_128(0);

static void device_found(const bt_addr_le_t *addr, s8_t rssi, u8_t type,
			 struct net_buf_simple *ad)
{
	char addr_str[BT_ADDR_LE_STR_LEN];
	int err;

	if (default_conn) {
		return;
	}

	/* We're only interested in connectable events */
	if (type != BT_GAP_ADV_TYPE_ADV_IND &&
	    type != BT_GAP_ADV_TYPE_ADV_DIRECT_IND) {
		return;
	}

	bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
	printk("Device found: %s (RSSI %d)\n", addr_str, rssi);

	/* connect only to devices in close proximity */
	if (rssi < -50) {
		return;
	}

	if (bt_le_scan_stop()) {
		return;
	}

	err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
				BT_LE_CONN_PARAM_DEFAULT, &default_conn);
	if (err) {
		printk("Create conn to %s failed (%u)\n", addr_str, err);
		start_scan();
	}
}

static void start_scan(void)
{
	int err;

	/* This demo doesn't require active scan */
	err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
	if (err) {
		printk("Scanning failed to start (err %d)\n", err);
		return;
	}

	printk("Scanning successfully started\n");
}

static u8_t discovered(struct bt_conn *conn, const struct bt_gatt_attr *attr,
		       struct bt_gatt_discover_params *params)
{
	int err;

	if (!attr) {
		printk("Discover complete\n");
		memset(params, 0, sizeof(*params));
		return BT_GATT_ITER_STOP;
	}

	if (bt_uuid_cmp(discover_params.uuid, BT_UUID_OUTPUT)) {
		memcpy(&uuid, BT_UUID_OUTPUT, sizeof(uuid));
		discover_params.uuid = &uuid.uuid;
		discover_params.start_handle = attr->handle + 1;
		discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;

		err = bt_gatt_discover(conn, &discover_params);
		if (err) {
			printk("Discover failed (err %d)\n", err);
		}
		char dest2[150];
		bt_uuid_to_str(params->uuid, dest2, sizeof(dest2));
		printk("Discovered attribute again- uuid: %s, handle: %u\n",
		       dest2, attr->handle);
		bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
		return BT_GATT_ITER_STOP;
	}

	char dest[150];
	bt_uuid_to_str(attr->uuid, dest, sizeof(dest));
	printk("Discovered attribute - uuid: %s, handle: %u\n", dest,
	       attr->handle);
	bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
	return BT_GATT_ITER_STOP;
}

static void connected(struct bt_conn *conn, u8_t err)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	if (err) {
		printk("Failed to connect to %s (%u)\n", addr, err);

		bt_conn_unref(default_conn);
		default_conn = NULL;

		start_scan();
		return;
	}

	if (conn != default_conn) {
		return;
	}

	printk("Connected: %s\n", addr);

	discover_params.func = discovered;
	discover_params.start_handle = 0x0001;
	discover_params.end_handle = 0xFFFF;
	discover_params.type = BT_GATT_DISCOVER_PRIMARY;
	
	bt_gatt_discover(conn, &discover_params);
}

static void disconnected(struct bt_conn *conn, u8_t reason)
{
	char addr[BT_ADDR_LE_STR_LEN];

	if (conn != default_conn) {
		return;
	}

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);

	bt_conn_unref(default_conn);
	default_conn = NULL;

	start_scan();
}

static struct bt_conn_cb conn_callbacks = {
	.connected = connected,
	.disconnected = disconnected,
};

void main(void)
{
	int err;

	err = bt_enable(NULL);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return;
	}

	printk("Bluetooth initialized\n");

	bt_conn_cb_register(&conn_callbacks);

	start_scan();
}
Im not sure if the central-code is perfectly correct either, but I dont know where to go after doing a service discovery to get to the results i wanted as stated above. Hope any of you can help and would kindheartedly advice that one of your next zephyr/NCS-guides on this site could pertain a BLE Central guide.
Looking forward to hearing from you!
Best regards,
Jonas
Related