Samples: Bluetooth: Add samples for PAwR

Adds samples for both advertiser and sync.

Signed-off-by: Herman Berget <herman.berget@nordicsemi.no>
This commit is contained in:
Herman Berget 2023-03-30 13:04:55 +02:00 committed by Carles Cufí
parent 95bc5cf8e5
commit 627b340780
10 changed files with 786 additions and 0 deletions

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(periodic_adv)
target_sources(app PRIVATE src/main.c)

View file

@ -0,0 +1,38 @@
.. _bluetooth-periodic-advertising-rsp-sample:
Bluetooth: Periodic Advertising with Responses (PAwR) Advertiser
################################################################
Overview
********
A simple application demonstrating the BLE Periodic Advertising with
Responses Advertiser functionality.
This sample will scan for the corresponding sync sample and send the required
synchronization info to it. The advertising data is a counter that increases
for each subevent.
Multiple devices can synchronize and respond to one advertiser.
Which subevent to listen to and in which response slot to respond is
application specific. In this sample it is decided by the PAwR advertiser.
Upon connection it will write to a GATT characteristic
the assigned subevent and response slot.
Requirements
************
* A board with BLE support
* A controller that supports the Periodic Advertising with Responses (PAwR) - Advertiser feature
Building and Running
********************
This sample can be found under :zephyr_file:`samples/bluetooth/periodic_adv_rsp` in
the Zephyr tree.
Use the sample found under :zephyr_file:`samples/bluetooth/periodic_sync_rsp` in the
Zephyr tree that will synchronize and respond to this sample.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,16 @@
CONFIG_BT=y
CONFIG_BT_BROADCASTER=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_PER_ADV=y
CONFIG_BT_DEVICE_NAME="PAwR adv sample"
CONFIG_BT_MAX_CONN=1
CONFIG_BT_CENTRAL=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER=y
CONFIG_BT_PER_ADV_RSP=y
CONFIG_BT_REMOTE_INFO=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_LOG=y

View file

@ -0,0 +1,12 @@
sample:
name: Bluetooth Periodic Advertising with Responses Advertiser
tests:
sample.bluetooth.periodic_adv:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86 nrf52840dk_nrf52840
tags: bluetooth
integration_platforms:
- nrf52840dk_nrf52840
extra_configs:
- CONFIG_BT_CTLR=n
- CONFIG_BT_NO_DRIVER=y

View file

@ -0,0 +1,382 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#define NUM_RSP_SLOTS 5
#define NUM_SUBEVENTS 5
#define PACKET_SIZE 5
#define NAME_LEN 30
static K_SEM_DEFINE(sem_connected, 0, 1);
static K_SEM_DEFINE(sem_discovered, 0, 1);
static K_SEM_DEFINE(sem_written, 0, 1);
static K_SEM_DEFINE(sem_disconnected, 0, 1);
static struct bt_uuid_128 pawr_char_uuid =
BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1));
static uint16_t pawr_attr_handle;
static const struct bt_le_per_adv_param per_adv_params = {
.interval_min = 0xFF,
.interval_max = 0xFF,
.options = 0,
.num_subevents = NUM_SUBEVENTS,
.subevent_interval = 0x30,
.response_slot_delay = 0x5,
.response_slot_spacing = 0x50,
.num_response_slots = NUM_RSP_SLOTS,
};
static struct bt_le_per_adv_subevent_data_params subevent_data_params[NUM_SUBEVENTS];
static struct net_buf_simple bufs[NUM_SUBEVENTS];
static uint8_t backing_store[PACKET_SIZE][NUM_SUBEVENTS];
BUILD_ASSERT(ARRAY_SIZE(bufs) == ARRAY_SIZE(subevent_data_params));
BUILD_ASSERT(ARRAY_SIZE(backing_store) == ARRAY_SIZE(subevent_data_params));
static uint8_t counter;
static void request_cb(struct bt_le_ext_adv *adv, const struct bt_le_per_adv_data_request *request)
{
int err;
uint8_t to_send;
struct net_buf_simple *buf;
to_send = MIN(request->count, ARRAY_SIZE(subevent_data_params));
for (size_t i = 0; i < to_send; i++) {
buf = &bufs[i];
buf->data[buf->len - 1] = counter++;
subevent_data_params[i].subevent =
(request->start + i) % per_adv_params.num_subevents;
subevent_data_params[i].response_slot_start = 0;
subevent_data_params[i].response_slot_count = NUM_RSP_SLOTS;
subevent_data_params[i].data = buf;
}
err = bt_le_per_adv_set_subevent_data(adv, to_send, subevent_data_params);
if (err) {
printk("Failed to set subevent data (err %d)\n", err);
} else {
printk("Subevent data set %d\n", counter);
}
}
static bool print_ad_field(struct bt_data *data, void *user_data)
{
ARG_UNUSED(user_data);
printk(" 0x%02X: ", data->type);
for (size_t i = 0; i < data->data_len; i++) {
printk("%02X", data->data[i]);
}
printk("\n");
return true;
}
static struct bt_conn *default_conn;
static void response_cb(struct bt_le_ext_adv *adv, struct bt_le_per_adv_response_info *info,
struct net_buf_simple *buf)
{
if (buf) {
printk("Response: subevent %d, slot %d\n", info->subevent, info->response_slot);
bt_data_parse(buf, print_ad_field, NULL);
} else {
printk("Failed to receive response: subevent %d, slot %d\n", info->subevent,
info->response_slot);
}
}
static const struct bt_le_ext_adv_cb adv_cb = {
.pawr_data_request = request_cb,
.pawr_response = response_cb,
};
void connected_cb(struct bt_conn *conn, uint8_t err)
{
printk("Connected (err 0x%02X)\n", err);
__ASSERT(conn == default_conn, "Unexpected connected callback");
if (err) {
bt_conn_unref(default_conn);
default_conn = NULL;
}
}
void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason 0x%02X)\n", reason);
bt_conn_unref(default_conn);
default_conn = NULL;
k_sem_give(&sem_disconnected);
}
void remote_info_available_cb(struct bt_conn *conn, struct bt_conn_remote_info *remote_info)
{
/* Need to wait for remote info before initiating PAST */
k_sem_give(&sem_connected);
}
BT_CONN_CB_DEFINE(conn_cb) = {
.connected = connected_cb,
.disconnected = disconnected_cb,
.remote_info_available = remote_info_available_cb,
};
static bool data_cb(struct bt_data *data, void *user_data)
{
char *name = user_data;
uint8_t len;
switch (data->type) {
case BT_DATA_NAME_SHORTENED:
case BT_DATA_NAME_COMPLETE:
len = MIN(data->data_len, NAME_LEN - 1);
memcpy(name, data->data, len);
name[len] = '\0';
return false;
default:
return true;
}
}
static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
struct net_buf_simple *ad)
{
char addr_str[BT_ADDR_LE_STR_LEN];
char name[NAME_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;
}
(void)memset(name, 0, sizeof(name));
bt_data_parse(ad, data_cb, name);
if (strcmp(name, "PAwR sync sample")) {
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);
}
}
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_chrc *chrc;
char str[BT_UUID_STR_LEN];
printk("Discovery: attr %p\n", attr);
if (!attr) {
return BT_GATT_ITER_STOP;
}
chrc = (struct bt_gatt_chrc *)attr->user_data;
bt_uuid_to_str(chrc->uuid, str, sizeof(str));
printk("UUID %s\n", str);
if (!bt_uuid_cmp(chrc->uuid, &pawr_char_uuid.uuid)) {
pawr_attr_handle = chrc->value_handle;
printk("Characteristic handle: %d\n", pawr_attr_handle);
k_sem_give(&sem_discovered);
}
return BT_GATT_ITER_STOP;
}
static void write_func(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params)
{
if (err) {
printk("Write failed (err %d)\n", err);
return;
}
k_sem_give(&sem_written);
}
void init_bufs(void)
{
for (size_t i = 0; i < ARRAY_SIZE(backing_store); i++) {
backing_store[i][0] = ARRAY_SIZE(backing_store[i]) - 1;
backing_store[i][1] = BT_DATA_MANUFACTURER_DATA;
backing_store[i][2] = 0x59; /* Nordic */
backing_store[i][3] = 0x00;
net_buf_simple_init_with_data(&bufs[i], &backing_store[i],
ARRAY_SIZE(backing_store[i]));
}
}
#define MAX_SYNCS (NUM_SUBEVENTS * NUM_RSP_SLOTS)
struct pawr_timing {
uint8_t subevent;
uint8_t response_slot;
} __packed;
static uint8_t num_synced;
void main(void)
{
int err;
struct bt_le_ext_adv *pawr_adv;
struct bt_gatt_discover_params discover_params;
struct bt_gatt_write_params write_params;
struct pawr_timing sync_config;
init_bufs();
printk("Starting Periodic Advertising Demo\n");
/* Initialize the Bluetooth Subsystem */
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
/* Create a non-connectable non-scannable advertising set */
err = bt_le_ext_adv_create(BT_LE_EXT_ADV_NCONN, &adv_cb, &pawr_adv);
if (err) {
printk("Failed to create advertising set (err %d)\n", err);
return;
}
/* Set periodic advertising parameters */
err = bt_le_per_adv_set_param(pawr_adv, &per_adv_params);
if (err) {
printk("Failed to set periodic advertising parameters (err %d)\n", err);
return;
}
/* Enable Periodic Advertising */
err = bt_le_per_adv_start(pawr_adv);
if (err) {
printk("Failed to enable periodic advertising (err %d)\n", err);
return;
}
printk("Start Periodic Advertising\n");
err = bt_le_ext_adv_start(pawr_adv, BT_LE_EXT_ADV_START_DEFAULT);
if (err) {
printk("Failed to start extended advertising (err %d)\n", err);
return;
}
while (num_synced < MAX_SYNCS) {
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");
k_sem_take(&sem_connected, K_FOREVER);
err = bt_le_per_adv_set_info_transfer(pawr_adv, default_conn, 0);
if (err) {
printk("Failed to send PAST (err %d)\n", err);
goto disconnect;
}
printk("PAST sent\n");
discover_params.uuid = &pawr_char_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_CHARACTERISTIC;
err = bt_gatt_discover(default_conn, &discover_params);
if (err) {
printk("Discovery failed (err %d)\n", err);
goto disconnect;
}
printk("Discovery started\n");
err = k_sem_take(&sem_discovered, K_SECONDS(10));
if (err) {
printk("Timed out during GATT discovery\n");
goto disconnect;
}
sync_config.subevent = num_synced % NUM_SUBEVENTS;
sync_config.response_slot = num_synced / NUM_RSP_SLOTS;
num_synced++;
write_params.func = write_func;
write_params.handle = pawr_attr_handle;
write_params.offset = 0;
write_params.data = &sync_config;
write_params.length = sizeof(sync_config);
err = bt_gatt_write(default_conn, &write_params);
if (err) {
printk("Write failed (err %d)\n", err);
num_synced--;
goto disconnect;
}
printk("Write started\n");
err = k_sem_take(&sem_written, K_SECONDS(10));
if (err) {
printk("Timed out during GATT write\n");
num_synced--;
goto disconnect;
}
printk("PAwR config written to sync %d, disconnecting\n", num_synced - 1);
disconnect:
err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err) {
return;
}
k_sem_take(&sem_disconnected, K_FOREVER);
}
printk("Maximum numnber of syncs onboarded\n");
while (true) {
k_sleep(K_SECONDS(1));
}
}

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(periodic_sync)
target_sources(app PRIVATE src/main.c)

View file

@ -0,0 +1,40 @@
.. _bluetooth-periodic-advertising-sync-rsp-sample:
Bluetooth: Periodic Advertising with Responses (PAwR) Synchronization
#####################################################################
Overview
********
A simple application demonstrating the BLE Periodic Advertising with
Responses Synchronization functionality.
This sample will echo the data received in subevent indications back to the
advertiser.
Which subevent to listen to and in which response slot to respond is
application specific. In this sample it is decided by the PAwR advertiser.
Upon connection it will write to a GATT characteristic
the assigned subevent and response slot.
Flash this sample to multiple devices and they will be given different
subevents and response slots, to that they can communicate with the
advertiser concurrently.
Requirements
************
* A board with BLE support
* A controller that supports the Periodic Advertising with Responses (PAwR) - Scanner feature
Building and Running
********************
This sample can be found under :zephyr_file:`samples/bluetooth/periodic_sync_rsp` in
the Zephyr tree.
Use the sample found under :zephyr_file:`samples/bluetooth/periodic_adv_rsp` on
another board that will start periodic advertising, which will connect to this
sample and transfer the synchronization info.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,16 @@
CONFIG_BT=y
CONFIG_BT_OBSERVER=y
CONFIG_BT_BROADCASTER=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_PER_ADV_SYNC=y
CONFIG_BT_DEVICE_NAME="PAwR sync sample"
CONFIG_BT_MAX_CONN=1
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER=y
CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER=y
CONFIG_BT_PER_ADV_SYNC_RSP=y
CONFIG_BT_PER_ADV_SYNC_BUF_SIZE=247
CONFIG_LOG=y

View file

@ -0,0 +1,12 @@
sample:
name: Bluetooth Periodic Advertising with Responses Synchronization
tests:
sample.bluetooth.periodic_sync:
harness: bluetooth
platform_allow: qemu_cortex_m3 qemu_x86 nrf52840dk_nrf52840
tags: bluetooth
integration_platforms:
- nrf52840dk_nrf52840
extra_configs:
- CONFIG_BT_CTLR=n
- CONFIG_BT_NO_DRIVER=y

View file

@ -0,0 +1,256 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/sys/util.h>
#define NAME_LEN 30
static K_SEM_DEFINE(sem_per_adv, 0, 1);
static K_SEM_DEFINE(sem_per_sync, 0, 1);
static K_SEM_DEFINE(sem_per_sync_lost, 0, 1);
static struct bt_conn *default_conn;
static struct bt_le_per_adv_sync *default_sync;
static struct __packed {
uint8_t subevent;
uint8_t response_slot;
} pawr_timing;
static void sync_cb(struct bt_le_per_adv_sync *sync, struct bt_le_per_adv_sync_synced_info *info)
{
struct bt_le_per_adv_sync_subevent_params params;
uint8_t subevents[1];
char le_addr[BT_ADDR_LE_STR_LEN];
int err;
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
printk("Synced to %s with %d subevents\n", le_addr, info->num_subevents);
default_sync = sync;
params.properties = 0;
params.num_subevents = 1;
params.subevents = subevents;
subevents[0] = pawr_timing.subevent;
err = bt_le_per_adv_sync_subevent(sync, &params);
if (err) {
printk("Failed to set subevents to sync to (err %d)\n", err);
}
k_sem_give(&sem_per_sync);
}
static void term_cb(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_term_info *info)
{
char le_addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
printk("Sync terminated (reason %d)\n", info->reason);
default_sync = NULL;
k_sem_give(&sem_per_sync_lost);
}
static bool print_ad_field(struct bt_data *data, void *user_data)
{
ARG_UNUSED(user_data);
printk(" 0x%02X: ", data->type);
for (size_t i = 0; i < data->data_len; i++) {
printk("%02X", data->data[i]);
}
printk("\n");
return true;
}
int bt_le_per_adv_set_response_data(struct bt_le_per_adv_sync *per_adv_sync,
const struct bt_le_per_adv_response_params *params,
const struct net_buf_simple *data);
static struct bt_le_per_adv_response_params rsp_params;
NET_BUF_SIMPLE_DEFINE_STATIC(rsp_buf, 247);
static void recv_cb(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_recv_info *info, struct net_buf_simple *buf)
{
int err;
if (buf && buf->len) {
/* Echo the data back to the advertiser */
net_buf_simple_reset(&rsp_buf);
net_buf_simple_add_mem(&rsp_buf, buf->data, buf->len);
rsp_params.request_event = info->periodic_event_counter;
rsp_params.request_subevent = info->subevent;
/* Respond in current subevent and assigned response slot */
rsp_params.response_subevent = info->subevent;
rsp_params.response_slot = pawr_timing.response_slot;
printk("Indication: subevent %d, responding in slot %d\n", info->subevent,
pawr_timing.response_slot);
bt_data_parse(buf, print_ad_field, NULL);
err = bt_le_per_adv_set_response_data(sync, &rsp_params, &rsp_buf);
if (err) {
printk("Failed to send response (err %d)\n", err);
}
} else if (buf) {
printk("Received empty indication: subevent %d\n", info->subevent);
} else {
printk("Failed to receive indication: subevent %d\n", info->subevent);
}
}
static struct bt_le_per_adv_sync_cb sync_callbacks = {
.synced = sync_cb,
.term = term_cb,
.recv = recv_cb,
};
static struct bt_uuid_128 pawr_svc_uuid =
BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0));
static struct bt_uuid_128 pawr_char_uuid =
BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1));
static ssize_t write_timing(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf,
uint16_t len, uint16_t offset, uint8_t flags)
{
if (offset) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (len != sizeof(pawr_timing)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
memcpy(&pawr_timing, buf, len);
printk("New timing: subevent %d, response slot %d\n", pawr_timing.subevent,
pawr_timing.response_slot);
struct bt_le_per_adv_sync_subevent_params params;
uint8_t subevents[1];
int err;
params.properties = 0;
params.num_subevents = 1;
params.subevents = subevents;
subevents[0] = pawr_timing.subevent;
if (default_sync) {
err = bt_le_per_adv_sync_subevent(default_sync, &params);
if (err) {
printk("Failed to set subevents to sync to (err %d)\n", err);
}
} else {
printk("Not synced yet\n");
}
return len;
}
BT_GATT_SERVICE_DEFINE(pawr_svc, BT_GATT_PRIMARY_SERVICE(&pawr_svc_uuid.uuid),
BT_GATT_CHARACTERISTIC(&pawr_char_uuid.uuid, BT_GATT_CHRC_WRITE,
BT_GATT_PERM_WRITE, NULL, write_timing,
&pawr_timing));
void connected(struct bt_conn *conn, uint8_t err)
{
printk("Connected (err 0x%02X)\n", err);
if (err) {
default_conn = NULL;
return;
}
default_conn = bt_conn_ref(conn);
}
void disconnected(struct bt_conn *conn, uint8_t reason)
{
bt_conn_unref(default_conn);
default_conn = NULL;
printk("Disconnected (reason 0x%02X)\n", reason);
}
BT_CONN_CB_DEFINE(conn_cb) = {
.connected = connected,
.disconnected = disconnected,
};
void main(void)
{
struct bt_le_per_adv_sync_transfer_param past_param;
int err;
printk("Starting Periodic Advertising with Responses Synchronization Demo\n");
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
bt_le_per_adv_sync_cb_register(&sync_callbacks);
past_param.skip = 1;
past_param.timeout = 1000; /* 10 seconds */
past_param.options = BT_LE_PER_ADV_SYNC_TRANSFER_OPT_NONE;
err = bt_le_per_adv_sync_transfer_subscribe(NULL, &past_param);
if (err) {
printk("PAST subscribe failed (err %d)\n", err);
return;
}
do {
err = bt_le_adv_start(
BT_LE_ADV_PARAM(BT_LE_ADV_OPT_ONE_TIME | BT_LE_ADV_OPT_CONNECTABLE |
BT_LE_ADV_OPT_USE_NAME |
BT_LE_ADV_OPT_FORCE_NAME_IN_AD,
BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL),
NULL, 0, NULL, 0);
if (err && err != -EALREADY) {
printk("Advertising failed to start (err %d)\n", err);
return;
}
printk("Waiting for periodic sync...\n");
err = k_sem_take(&sem_per_sync, K_SECONDS(10));
if (err) {
printk("Timed out while synchronizing\n");
continue;
}
printk("Periodic sync established.\n");
err = k_sem_take(&sem_per_sync_lost, K_FOREVER);
if (err) {
printk("failed (err %d)\n", err);
return;
}
printk("Periodic sync lost.\n");
} while (true);
}