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:
parent
95bc5cf8e5
commit
627b340780
7
samples/bluetooth/periodic_adv_rsp/CMakeLists.txt
Normal file
7
samples/bluetooth/periodic_adv_rsp/CMakeLists.txt
Normal 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)
|
38
samples/bluetooth/periodic_adv_rsp/README.rst
Normal file
38
samples/bluetooth/periodic_adv_rsp/README.rst
Normal 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.
|
16
samples/bluetooth/periodic_adv_rsp/prj.conf
Normal file
16
samples/bluetooth/periodic_adv_rsp/prj.conf
Normal 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
|
12
samples/bluetooth/periodic_adv_rsp/sample.yaml
Normal file
12
samples/bluetooth/periodic_adv_rsp/sample.yaml
Normal 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
|
382
samples/bluetooth/periodic_adv_rsp/src/main.c
Normal file
382
samples/bluetooth/periodic_adv_rsp/src/main.c
Normal 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));
|
||||
}
|
||||
}
|
7
samples/bluetooth/periodic_sync_rsp/CMakeLists.txt
Normal file
7
samples/bluetooth/periodic_sync_rsp/CMakeLists.txt
Normal 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)
|
40
samples/bluetooth/periodic_sync_rsp/README.rst
Normal file
40
samples/bluetooth/periodic_sync_rsp/README.rst
Normal 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.
|
16
samples/bluetooth/periodic_sync_rsp/prj.conf
Normal file
16
samples/bluetooth/periodic_sync_rsp/prj.conf
Normal 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
|
12
samples/bluetooth/periodic_sync_rsp/sample.yaml
Normal file
12
samples/bluetooth/periodic_sync_rsp/sample.yaml
Normal 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
|
256
samples/bluetooth/periodic_sync_rsp/src/main.c
Normal file
256
samples/bluetooth/periodic_sync_rsp/src/main.c
Normal 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, ¶ms);
|
||||
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, ¶ms);
|
||||
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);
|
||||
}
|
Loading…
Reference in a new issue