Hello experts!
I have been trying for some time now setting up my own custom service GATTServer and GATTClient with two nrf52840 DK.
On the server-side I have modified the BLE peripheral sample project and it works as it should when I try it with the NRF Connect app.
This is the main.c for that one:
/* main.c - Application main entry point */
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#define NOTIFY_INTERVAL 500
#define VND_MAX_LEN 20
static uint8_t rxdata_value[VND_MAX_LEN + 1] = {'R','x','D', 'a', 't', 'a'};
static uint16_t txcredit_cli_value[VND_MAX_LEN + 1] = { 'T', 'x', 'C', 'r', 'e', 'd' };
static uint8_t frog_txd_frames = 100U;
static uint16_t frog_rxc_frames = 2U;
static bool frog_txdnotify_enabled;
static bool frog_rxcnotify_enabled;
#define STACKSIZE 1024
#define PRIORITY 7
/* ---------------------- FROG SERVICE UUIDs -------------------------- */
#define BT_UUID_FROG_SERVICE_VAL \
BT_UUID_128_ENCODE(0x2016ec20, 0xb2eb, 0x11e5, 0xa307, 0x0002a5d5c51b)
static const struct bt_uuid_128 frog_uuid = BT_UUID_INIT_128(
BT_UUID_FROG_SERVICE_VAL);
static const struct bt_uuid_128 frog_txcredit_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x2c1b1820, 0xb2eb, 0x11e5, 0x8f66, 0x0002a5d5c51b));
static const struct bt_uuid_128 frog_txdata_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x388706a0, 0xb2eb, 0x11e5, 0xa790, 0x0002a5d5c51b));
static const struct bt_uuid_128 frog_rxdata_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x2016ec20, 0xb2eb, 0x11e5, 0xa307, 0x0002a5d5c51b));
static const struct bt_uuid_128 frog_rxcredit_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x43802b40, 0xb2eb, 0x11e5, 0xb6e5, 0x0002a5d5c51b));
// Setting up CCCD
static void frog_txdccc_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
frog_txdnotify_enabled = (value == BT_GATT_CCC_NOTIFY);
}
static void frog_rxcccc_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
frog_rxcnotify_enabled = (value == BT_GATT_CCC_NOTIFY);
}
/* ---------------------------- END ------------------------------------*/
static ssize_t write_without_rsp_vnd(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
uint16_t *value = attr->user_data;
printk("user_data: %p \n", attr->user_data);
if (!(flags & BT_GATT_WRITE_FLAG_CMD)) {
/* Write Request received. Reject it since this Characteristic
* only accepts Write Without Response.
*/
return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED);
}
if (offset + len > VND_MAX_LEN) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
memcpy(value + offset, buf, len);
value[offset + len] = 0;
return len;
}
/* ----------- FROG SERVICE DECLARATION ----------- */
BT_GATT_SERVICE_DEFINE(frog_srv,
BT_GATT_PRIMARY_SERVICE(&frog_uuid),
BT_GATT_CHARACTERISTIC(&frog_txdata_uuid.uuid, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ, NULL, NULL, NULL),
BT_GATT_CCC(frog_txdccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CHARACTERISTIC(&frog_rxcredit_uuid.uuid, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ, NULL, NULL, NULL),
BT_GATT_CCC(frog_rxcccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
BT_GATT_CHARACTERISTIC(&frog_txcredit_uuid.uuid, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE, NULL, write_without_rsp_vnd, &txcredit_cli_value),
BT_GATT_CHARACTERISTIC(&frog_rxdata_uuid.uuid, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE, NULL, write_without_rsp_vnd, &rxdata_value),
);
/*-------------------- END ------------------------ */
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_FROG_SERVICE_VAL),
};
static const struct bt_data sd[] = {
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};
/* Update Maximum Transfer Unit */
void mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
printk("Updated MTU: TX: %d RX: %d bytes\n", tx, rx);
}
/* Register callback functions */
static struct bt_gatt_cb gatt_callbacks = {
.att_mtu_updated = mtu_updated
};
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed (err 0x%02x)\n", err);
} else {
printk("Connected\n");
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason 0x%02x)\n", reason);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
static void bt_ready(void)
{
int err;
printk("Bluetooth initialized\n");
if (IS_ENABLED(CONFIG_SETTINGS)) {
settings_load();
}
err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err) {
printk("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
}
// Notify for frog
int frog_txd_notify(uint8_t txd_val)
{
if (!frog_txdnotify_enabled) {
return -EACCES;
}
return bt_gatt_notify(NULL, &frog_srv.attrs[1],
&txd_val,
sizeof(txd_val));
}
int frog_rxc_notify(uint8_t rxc_val)
{
if (!frog_rxcnotify_enabled) {
return -EACCES;
}
return bt_gatt_notify(NULL, &frog_srv.attrs[4],
&rxc_val,
sizeof(rxc_val));
}
static void simulate_data(void)
{
frog_txd_frames++;
if (frog_txd_frames == 200) {
frog_txd_frames = 100;
}
frog_rxc_frames++;
if (frog_rxc_frames == 150)
{
frog_rxc_frames = 2;
}
}
void send_data_thread(void)
{
while(1){
/* Simulate data */
simulate_data();
/* Send notification, the function sends notifications only if a client is subscribed */
frog_txd_notify(frog_txd_frames);
frog_rxc_notify(frog_rxc_frames);
k_sleep(K_MSEC(NOTIFY_INTERVAL));
}
}
int main(void)
{
int err;
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return 0;
}
bt_ready();
bt_gatt_cb_register(&gatt_callbacks);
/* Implement notification. At the moment there is no suitable way
* of starting delayed work so we do it here
*/
while (1) {
k_sleep(K_SECONDS(1));
/* Heartrate measurements simulation */
//hrs_notify();
/* Battery level simulation */
//bas_notify();
//printk("frog_txd_frames: %d \n", frog_txd_frames);
//printk("txcredit_cli_value: %d \n", *txcredit_cli_value);
//printk("rxdata_value: %d \n\n\n", *rxdata_value);
//printk("frog_rxc_frames: %d \n\n\n", frog_rxc_frames);
/* Vendor indication simulation */
}
return 0;
}
K_THREAD_DEFINE(send_data_thread_id, STACKSIZE, send_data_thread, NULL, NULL,
NULL, PRIORITY, 0, 0);
My first question is:
1 a) I want to remove all other services, and I have been able to remove the hr-service and battery-service, however I can not remove the cts-service because then the project won't build properly. Meaning I can't remove the cts.c and cts.h files without something going wrong, can this be fixed somehow?
Now to the GATTClient, I have used the central_hr sample to try and set up my central/client device. This is what that code looks like:
/* main.c - Application main entry point */
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/byteorder.h>
/* --------------------- START OF FROG UUID:s ---------------- */
#define FROG_SERVICE BT_UUID_128_ENCODE(0x2016ec20, 0xb2eb, 0x11e5, 0xa307, 0x0002a5d5c51b)
// Notify characteristics
#define FROG_TXDATA_UUID BT_UUID_128_ENCODE(0x388706a0, 0xb2eb, 0x11e5, 0xa790, 0x0002a5d5c51b)
// Write without response characteristics
#define FROG_RXCREDIT BT_UUID_128_ENCODE(0x43802b40, 0xb2eb, 0x11e5, 0xb6e5, 0x0002a5d5c51b)
#define BT_UUID_FROG BT_UUID_DECLARE_128(FROG_SERVICE)
#define BT_UUID_TXDATA BT_UUID_DECLARE_128(FROG_TXDATA_UUID)
#define BT_UUID_RXCREDIT BT_UUID_DECLARE_128(FROG_RXCREDIT)
/* ----------------------------- END ------------------------- */
static void start_scan(void);
static struct bt_conn *default_conn;
static struct bt_uuid_128 discover_frog_uuid = BT_UUID_INIT_128(0);
static const struct bt_uuid *ccc_uuid = BT_UUID_GATT_CCC;
static struct bt_gatt_discover_params discover_params;
static struct bt_gatt_subscribe_params subscribe_params;
uint64_t total_rx_count; /* This value is exposed to test code */
static uint8_t notify_func(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
if (!data) {
printk("[UNSUBSCRIBED]\n");
params->value_handle = 0U;
return BT_GATT_ITER_STOP;
}
printk("[NOTIFICATION] data %p length %u\n", data, length);
total_rx_count++;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t discover_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
if (!attr) {
printk("Discover complete\n");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
printk("[ATTRIBUTE] handle %u\n", attr->handle);
if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_FROG)) {
printk("Discovering TxDATA\n");
memcpy(&discover_frog_uuid, BT_UUID_TXDATA, sizeof(discover_frog_uuid));
discover_params.uuid = &discover_frog_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);
}
} else if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_FROG)) {
printk("Discovering CCC\n");
//memcpy(&discover_frog_uuid, ccc_uuid, sizeof(*ccc_uuid));
discover_params.uuid = ccc_uuid;
discover_params.start_handle = attr->handle + 2;
discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);
err = bt_gatt_discover(conn, &discover_params);
if (err) {
printk("Discover failed (err %d)\n", err);
}
} else {
subscribe_params.notify = notify_func;
subscribe_params.value = BT_GATT_CCC_NOTIFY;
subscribe_params.ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, &subscribe_params);
if (err && err != -EALREADY) {
printk("Subscribe failed (err %d)\n", err);
} else {
printk("[SUBSCRIBED]\n");
}
return BT_GATT_ITER_STOP;
}
return BT_GATT_ITER_STOP;
}
static bool eir_found(struct bt_data *data, void *user_data)
{
bt_addr_le_t *addr = user_data;
int i;
printk("[AD]: %u data_len %u\n", data->type, data->data_len);
switch (data->type) {
case BT_DATA_UUID128_SOME:
case BT_DATA_UUID128_ALL:
if (data->data_len % sizeof(uint16_t) != 0U) {
printk("AD malformed\n");
return true;
}
for (i = 0; i < data->data_len; i += sizeof(uint16_t)) {
struct bt_conn_le_create_param *create_param;
struct bt_le_conn_param *param;
const struct bt_uuid *uuid;
uint16_t u16;
int err;
memcpy(&u16, &data->data[i], sizeof(u16));
uuid = BT_UUID_DECLARE_128(sys_le16_to_cpu(u16));
if (bt_uuid_cmp(uuid, BT_UUID_FROG)) {
continue;
}
err = bt_le_scan_stop();
if (err) {
printk("Stop LE scan failed (err %d)\n", err);
continue;
}
printk("Creating connection with Coded PHY support\n");
param = BT_LE_CONN_PARAM_DEFAULT;
create_param = BT_CONN_LE_CREATE_CONN;
create_param->options |= BT_CONN_LE_OPT_CODED;
err = bt_conn_le_create(addr, create_param, param,
&default_conn);
if (err) {
printk("Create connection with Coded PHY support failed (err %d)\n",
err);
printk("Creating non-Coded PHY connection\n");
create_param->options &= ~BT_CONN_LE_OPT_CODED;
err = bt_conn_le_create(addr, create_param,
param, &default_conn);
if (err) {
printk("Create connection failed (err %d)\n", err);
start_scan();
}
}
return false;
}
}
return true;
}
static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
struct net_buf_simple *ad)
{
char dev[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(addr, dev, sizeof(dev));
printk("[DEVICE]: %s, AD evt type %u, AD data len %u, RSSI %i\n",
dev, type, ad->len, rssi);
/* We're only interested in legacy connectable events or
* possible extended advertising that are connectable.
*/
if (type == BT_GAP_ADV_TYPE_ADV_IND ||
type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND ||
type == BT_GAP_ADV_TYPE_EXT_ADV) {
bt_data_parse(ad, eir_found, (void *)addr);
}
}
static void start_scan(void)
{
int err;
/* Use active scanning and disable duplicate filtering to handle any
* devices that might update their advertising data at runtime. */
struct bt_le_scan_param scan_param = {
.type = BT_LE_SCAN_TYPE_ACTIVE,
.options = BT_LE_SCAN_OPT_CODED,
.interval = BT_GAP_SCAN_FAST_INTERVAL,
.window = BT_GAP_SCAN_FAST_WINDOW,
};
err = bt_le_scan_start(&scan_param, device_found);
if (err) {
printk("Scanning with Coded PHY support failed (err %d)\n", err);
printk("Scanning without Coded PHY\n");
scan_param.options &= ~BT_LE_SCAN_OPT_CODED;
err = bt_le_scan_start(&scan_param, device_found);
if (err) {
printk("Scanning failed to start (err %d)\n", err);
return;
}
}
printk("Scanning successfully started\n");
}
static void connected(struct bt_conn *conn, uint8_t conn_err)
{
char addr[BT_ADDR_LE_STR_LEN];
int err;
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (conn_err) {
printk("Failed to connect to %s (%u)\n", addr, conn_err);
bt_conn_unref(default_conn);
default_conn = NULL;
start_scan();
return;
}
printk("Connected: %s\n", addr);
total_rx_count = 0U;
if (conn == default_conn) {
memcpy(&discover_frog_uuid, BT_UUID_FROG, sizeof(discover_frog_uuid));
discover_params.uuid = &discover_frog_uuid.uuid;
discover_params.func = discover_func;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
err = bt_gatt_discover(default_conn, &discover_params);
if (err) {
printk("Discover failed(err %d)\n", err);
return;
}
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
if (default_conn != conn) {
return;
}
bt_conn_unref(default_conn);
default_conn = NULL;
start_scan();
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
int main(void)
{
int err;
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return 0;
}
printk("Bluetooth initialized\n");
start_scan();
return 0;
}
My questions here are many because I can't get a proper handle on how this sample works:
2 a) Will this code that I have, find ALL my characteristics and subscribe to both my notification characteristics (only one is present in the above client code?
b) When I do print-outs from the client I see that it detects a lot of devices, but I don't know if it is the server I have set up, how can I find my device address easiest or is this always randomly generated from the server when it starts advertising?
Also, in the central_hr sample it says that it should automatically connect to the peripheral_hr sample but my units are not connecting automatically, have I set some security-level wrong?
If possible, please provide answers that tell me explicitly how to fix things since I am a student and have only worked with this for a month or so so everything is very new.
