samples: Bluetooth: Add Synchronized Receiver sample
Add an application that initializes Bluetooth Subsystem, starts scanning for Periodic Advertising, establishes Periodic Advertising Sync, receives Periodic Advertising reports, receives BIGInfo reports and creates BIG sync. Signed-off-by: Vinayak Kariappa Chettimada <vich@nordicsemi.no> Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
parent
46700dfc49
commit
775c0deadf
5 changed files with 497 additions and 0 deletions
7
samples/bluetooth/iso_receive/CMakeLists.txt
Normal file
7
samples/bluetooth/iso_receive/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.13.1)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(iso_receive)
|
||||
|
||||
target_sources(app PRIVATE src/main.c)
|
28
samples/bluetooth/iso_receive/README.rst
Normal file
28
samples/bluetooth/iso_receive/README.rst
Normal file
|
@ -0,0 +1,28 @@
|
|||
.. _bluetooth-iso-receive-sample:
|
||||
|
||||
Bluetooth: Synchronized Receiver
|
||||
###############################################
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
A simple application demonstrating the Bluetooth Low Energy Synchronized
|
||||
Receiver functionality.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
* A board with Bluetooth Low Energy support
|
||||
|
||||
Building and Running
|
||||
********************
|
||||
|
||||
This sample can be found under :zephyr_file:`samples/bluetooth/iso_receive` in
|
||||
the Zephyr tree.
|
||||
|
||||
Use the sample found under :zephyr_file:`samples/bluetooth/iso_broadcast` on
|
||||
another board that will start periodic advertising, create BIG to which this
|
||||
sample will establish periodic advertising synchronization and synchronize to
|
||||
the Broadcast Isochronous Stream.
|
||||
|
||||
See :ref:`bluetooth samples section <bluetooth-samples>` for details.
|
11
samples/bluetooth/iso_receive/prj.conf
Normal file
11
samples/bluetooth/iso_receive/prj.conf
Normal file
|
@ -0,0 +1,11 @@
|
|||
CONFIG_BT=y
|
||||
CONFIG_BT_OBSERVER=y
|
||||
CONFIG_BT_EXT_ADV=y
|
||||
CONFIG_BT_PER_ADV_SYNC=y
|
||||
CONFIG_BT_ISO=y
|
||||
CONFIG_BT_DEBUG_LOG=y
|
||||
CONFIG_BT_DEVICE_NAME="Test ISO Receive"
|
||||
|
||||
# Temporary, enable the following to meet BT_ISO dependencies
|
||||
CONFIG_BT_ISO_BROADCAST=y
|
||||
CONFIG_BT_CENTRAL=y
|
7
samples/bluetooth/iso_receive/sample.yaml
Normal file
7
samples/bluetooth/iso_receive/sample.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
sample:
|
||||
name: Bluetooth Synchronized Receiver
|
||||
tests:
|
||||
sample.bluetooth.iso_receive:
|
||||
harness: bluetooth
|
||||
platform_allow: qemu_cortex_m3 qemu_x86 nrf52_bsim nrf52dk_nrf52832
|
||||
tags: bluetooth
|
444
samples/bluetooth/iso_receive/src/main.c
Normal file
444
samples/bluetooth/iso_receive/src/main.c
Normal file
|
@ -0,0 +1,444 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <device.h>
|
||||
#include <devicetree.h>
|
||||
#include <drivers/gpio.h>
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/iso.h>
|
||||
#include <sys/byteorder.h>
|
||||
|
||||
#define TIMEOUT_SYNC_CREATE K_SECONDS(10)
|
||||
#define NAME_LEN 30
|
||||
|
||||
#define BT_LE_SCAN_CUSTOM BT_LE_SCAN_PARAM(BT_LE_SCAN_TYPE_ACTIVE, \
|
||||
BT_LE_SCAN_OPT_NONE, \
|
||||
BT_GAP_SCAN_FAST_INTERVAL, \
|
||||
BT_GAP_SCAN_FAST_WINDOW)
|
||||
|
||||
#define BT_INTERVAL_TO_MS(interval) ((interval) * 5 / 4)
|
||||
#define PA_RETRY_COUNT 6
|
||||
|
||||
static bool per_adv_found;
|
||||
static bool per_adv_lost;
|
||||
static bt_addr_le_t per_addr;
|
||||
static uint8_t per_sid;
|
||||
static uint16_t per_interval_ms;
|
||||
|
||||
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 K_SEM_DEFINE(sem_per_big_info, 0, 1);
|
||||
static K_SEM_DEFINE(sem_big_sync, 0, 1);
|
||||
static K_SEM_DEFINE(sem_big_sync_lost, 0, 1);
|
||||
|
||||
/* The devicetree node identifier for the "led0" alias. */
|
||||
#define LED0_NODE DT_ALIAS(led0)
|
||||
|
||||
#if DT_NODE_HAS_STATUS(LED0_NODE, okay)
|
||||
#define HAS_LED 1
|
||||
#define LED0 DT_GPIO_LABEL(LED0_NODE, gpios)
|
||||
#define PIN DT_GPIO_PIN(LED0_NODE, gpios)
|
||||
#define FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios)
|
||||
#define BLINK_ONOFF K_MSEC(500)
|
||||
|
||||
static struct device const *dev;
|
||||
static struct k_delayed_work blink_work;
|
||||
static bool led_is_on;
|
||||
|
||||
static void blink_timeout(struct k_work *work)
|
||||
{
|
||||
led_is_on = !led_is_on;
|
||||
gpio_pin_set(dev, PIN, (int)led_is_on);
|
||||
|
||||
k_delayed_work_submit(&blink_work, BLINK_ONOFF);
|
||||
}
|
||||
#endif
|
||||
|
||||
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 const char *phy2str(uint8_t phy)
|
||||
{
|
||||
switch (phy) {
|
||||
case 0: return "No packets";
|
||||
case BT_GAP_LE_PHY_1M: return "LE 1M";
|
||||
case BT_GAP_LE_PHY_2M: return "LE 2M";
|
||||
case BT_GAP_LE_PHY_CODED: return "LE Coded";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void scan_recv(const struct bt_le_scan_recv_info *info,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
char le_addr[BT_ADDR_LE_STR_LEN];
|
||||
char name[NAME_LEN];
|
||||
|
||||
(void)memset(name, 0, sizeof(name));
|
||||
|
||||
bt_data_parse(buf, data_cb, name);
|
||||
|
||||
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
|
||||
printk("[DEVICE]: %s, AD evt type %u, Tx Pwr: %i, RSSI %i %s "
|
||||
"C:%u S:%u D:%u SR:%u E:%u Prim: %s, Secn: %s, "
|
||||
"Interval: 0x%04x (%u ms), SID: %u\n",
|
||||
le_addr, info->adv_type, info->tx_power, info->rssi, name,
|
||||
(info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) != 0,
|
||||
(info->adv_props & BT_GAP_ADV_PROP_SCANNABLE) != 0,
|
||||
(info->adv_props & BT_GAP_ADV_PROP_DIRECTED) != 0,
|
||||
(info->adv_props & BT_GAP_ADV_PROP_SCAN_RESPONSE) != 0,
|
||||
(info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) != 0,
|
||||
phy2str(info->primary_phy), phy2str(info->secondary_phy),
|
||||
info->interval, BT_INTERVAL_TO_MS(info->interval), info->sid);
|
||||
|
||||
if (!per_adv_found && info->interval) {
|
||||
per_adv_found = true;
|
||||
|
||||
per_sid = info->sid;
|
||||
per_interval_ms = BT_INTERVAL_TO_MS(info->interval);
|
||||
bt_addr_le_copy(&per_addr, info->addr);
|
||||
|
||||
k_sem_give(&sem_per_adv);
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_le_scan_cb scan_callbacks = {
|
||||
.recv = scan_recv,
|
||||
};
|
||||
|
||||
static void sync_cb(struct bt_le_per_adv_sync *sync,
|
||||
struct bt_le_per_adv_sync_synced_info *info)
|
||||
{
|
||||
char le_addr[BT_ADDR_LE_STR_LEN];
|
||||
|
||||
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
|
||||
|
||||
printk("PER_ADV_SYNC[%u]: [DEVICE]: %s synced, "
|
||||
"Interval 0x%04x (%u ms), PHY %s\n",
|
||||
bt_le_per_adv_sync_get_index(sync), le_addr,
|
||||
info->interval, info->interval * 5 / 4, phy2str(info->phy));
|
||||
|
||||
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("PER_ADV_SYNC[%u]: [DEVICE]: %s sync terminated\n",
|
||||
bt_le_per_adv_sync_get_index(sync), le_addr);
|
||||
|
||||
per_adv_lost = true;
|
||||
k_sem_give(&sem_per_sync_lost);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
char le_addr[BT_ADDR_LE_STR_LEN];
|
||||
char data_str[129];
|
||||
|
||||
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
|
||||
bin2hex(buf->data, buf->len, data_str, sizeof(data_str));
|
||||
|
||||
printk("PER_ADV_SYNC[%u]: [DEVICE]: %s, tx_power %i, "
|
||||
"RSSI %i, CTE %u, data length %u, data: %s\n",
|
||||
bt_le_per_adv_sync_get_index(sync), le_addr, info->tx_power,
|
||||
info->rssi, info->cte_type, buf->len, data_str);
|
||||
}
|
||||
|
||||
static void biginfo_cb(struct bt_le_per_adv_sync *sync,
|
||||
const struct bt_iso_biginfo *biginfo)
|
||||
{
|
||||
char le_addr[BT_ADDR_LE_STR_LEN];
|
||||
|
||||
bt_addr_le_to_str(biginfo->addr, le_addr, sizeof(le_addr));
|
||||
|
||||
printk("BIG INFO[%u]: [DEVICE]: %s, sid 0x%02x, "
|
||||
"num_bis %u, nse %u, interval 0x%04x (%u ms), "
|
||||
"bn %u, pto %u, irc %u, max_pdu %u, "
|
||||
"sdu_interval %u us, max_sdu %u, phy %s, "
|
||||
"%s framing, %sencrypted\n",
|
||||
bt_le_per_adv_sync_get_index(sync), le_addr, biginfo->sid,
|
||||
biginfo->num_bis, biginfo->sub_evt_count,
|
||||
biginfo->iso_interval,
|
||||
(biginfo->iso_interval * 5 / 4),
|
||||
biginfo->burst_number, biginfo->offset,
|
||||
biginfo->rep_count, biginfo->max_pdu, biginfo->sdu_interval,
|
||||
biginfo->max_sdu, phy2str(biginfo->phy),
|
||||
biginfo->framing ? "with" : "without",
|
||||
biginfo->encryption ? "" : "not ");
|
||||
|
||||
|
||||
k_sem_give(&sem_per_big_info);
|
||||
}
|
||||
|
||||
static struct bt_le_per_adv_sync_cb sync_callbacks = {
|
||||
.synced = sync_cb,
|
||||
.term = term_cb,
|
||||
.recv = recv_cb,
|
||||
.biginfo = biginfo_cb,
|
||||
};
|
||||
|
||||
#define BIS_ISO_CHAN_COUNT 1
|
||||
|
||||
static void iso_recv(struct bt_iso_chan *chan, struct net_buf *buf)
|
||||
{
|
||||
char data_str[128];
|
||||
size_t str_len;
|
||||
uint32_t count = 0; /* only valid if the data is a counter */
|
||||
|
||||
if (buf->len == sizeof(count)) {
|
||||
count = sys_get_le32(buf->data);
|
||||
}
|
||||
|
||||
str_len = bin2hex(buf->data, buf->len, data_str, sizeof(data_str));
|
||||
printk("Incoming data channel %p len %u: %s (counter value %u)\n",
|
||||
chan, buf->len, data_str, count);
|
||||
}
|
||||
|
||||
static void iso_connected(struct bt_iso_chan *chan)
|
||||
{
|
||||
printk("ISO Channel %p connected\n", chan);
|
||||
k_sem_give(&sem_big_sync);
|
||||
}
|
||||
|
||||
static void iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
|
||||
{
|
||||
printk("ISO Channel %p disconnected with reason 0x%02x\n",
|
||||
chan, reason);
|
||||
|
||||
if (reason != BT_HCI_ERR_OP_CANCELLED_BY_HOST) {
|
||||
k_sem_give(&sem_big_sync_lost);
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_iso_chan_ops iso_ops = {
|
||||
.recv = iso_recv,
|
||||
.connected = iso_connected,
|
||||
.disconnected = iso_disconnected,
|
||||
};
|
||||
|
||||
static struct bt_iso_chan_io_qos iso_rx_qos;
|
||||
|
||||
static struct bt_iso_chan_qos bis_iso_qos = {
|
||||
.rx = &iso_rx_qos,
|
||||
};
|
||||
|
||||
static struct bt_iso_chan bis_iso_chan = {
|
||||
.ops = &iso_ops,
|
||||
.qos = &bis_iso_qos,
|
||||
};
|
||||
|
||||
static struct bt_iso_chan *bis[BIS_ISO_CHAN_COUNT] = { &bis_iso_chan };
|
||||
|
||||
static struct bt_iso_big_sync_param big_sync_param = {
|
||||
.bis_channels = bis,
|
||||
.num_bis = BIS_ISO_CHAN_COUNT,
|
||||
.bis_bitfield = (BIT(BIS_ISO_CHAN_COUNT) - 1),
|
||||
.mse = 1,
|
||||
.sync_timeout = 100, /* in 10 ms units */
|
||||
};
|
||||
|
||||
void main(void)
|
||||
{
|
||||
struct bt_le_per_adv_sync_param sync_create_param;
|
||||
struct bt_le_per_adv_sync *sync;
|
||||
struct bt_iso_big *big;
|
||||
uint32_t sem_timeout;
|
||||
int err;
|
||||
|
||||
printk("Starting Synchronized Receiver Demo\n");
|
||||
|
||||
#if defined(HAS_LED)
|
||||
printk("Get reference to LED device...");
|
||||
dev = device_get_binding(LED0);
|
||||
if (!dev) {
|
||||
printk("Failed.\n");
|
||||
return;
|
||||
}
|
||||
printk("done.\n");
|
||||
|
||||
printk("Configure GPIO pin...");
|
||||
err = gpio_pin_configure(dev, PIN, GPIO_OUTPUT_ACTIVE | FLAGS);
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
printk("done.\n");
|
||||
|
||||
k_delayed_work_init(&blink_work, blink_timeout);
|
||||
#endif /* HAS_LED */
|
||||
|
||||
/* Initialize the Bluetooth Subsystem */
|
||||
err = bt_enable(NULL);
|
||||
if (err) {
|
||||
printk("Bluetooth init failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
printk("Scan callbacks register...");
|
||||
bt_le_scan_cb_register(&scan_callbacks);
|
||||
printk("success.\n");
|
||||
|
||||
printk("Periodic Advertising callbacks register...");
|
||||
bt_le_per_adv_sync_cb_register(&sync_callbacks);
|
||||
printk("Success.\n");
|
||||
|
||||
do {
|
||||
per_adv_lost = false;
|
||||
|
||||
printk("Start scanning...");
|
||||
err = bt_le_scan_start(BT_LE_SCAN_CUSTOM, NULL);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
printk("success.\n");
|
||||
|
||||
#if defined(HAS_LED)
|
||||
printk("Start blinking LED...\n");
|
||||
led_is_on = false;
|
||||
gpio_pin_set(dev, PIN, (int)led_is_on);
|
||||
k_delayed_work_submit(&blink_work, BLINK_ONOFF);
|
||||
#endif /* HAS_LED */
|
||||
|
||||
printk("Waiting for periodic advertising...\n");
|
||||
per_adv_found = false;
|
||||
err = k_sem_take(&sem_per_adv, K_FOREVER);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
printk("Found periodic advertising.\n");
|
||||
|
||||
printk("Stop scanning...");
|
||||
err = bt_le_scan_stop();
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
printk("success.\n");
|
||||
|
||||
printk("Creating Periodic Advertising Sync...");
|
||||
bt_addr_le_copy(&sync_create_param.addr, &per_addr);
|
||||
sync_create_param.options = 0;
|
||||
sync_create_param.sid = per_sid;
|
||||
sync_create_param.skip = 0;
|
||||
sync_create_param.timeout = (per_interval_ms * PA_RETRY_COUNT) / 10;
|
||||
sem_timeout = per_interval_ms * PA_RETRY_COUNT;
|
||||
err = bt_le_per_adv_sync_create(&sync_create_param, &sync);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
printk("success.\n");
|
||||
|
||||
printk("Waiting for periodic sync...\n");
|
||||
err = k_sem_take(&sem_per_sync, K_MSEC(sem_timeout));
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
|
||||
printk("Deleting Periodic Advertising Sync...");
|
||||
err = bt_le_per_adv_sync_delete(sync);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
printk("Periodic sync established.\n");
|
||||
|
||||
printk("Waiting for BIG info...\n");
|
||||
err = k_sem_take(&sem_per_big_info, K_MSEC(sem_timeout));
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
|
||||
if (per_adv_lost) {
|
||||
continue;
|
||||
}
|
||||
|
||||
printk("Deleting Periodic Advertising Sync...");
|
||||
err = bt_le_per_adv_sync_delete(sync);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
printk("Periodic sync established.\n");
|
||||
|
||||
big_sync_create:
|
||||
printk("Create BIG Sync...\n");
|
||||
err = bt_iso_big_sync(sync, &big_sync_param, &big);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
printk("success.\n");
|
||||
|
||||
printk("Waiting for BIG sync...\n");
|
||||
err = k_sem_take(&sem_big_sync, TIMEOUT_SYNC_CREATE);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
|
||||
printk("BIG Sync Terminate...");
|
||||
err = bt_iso_big_terminate(big);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
printk("done.\n");
|
||||
|
||||
goto per_sync_lost_check;
|
||||
}
|
||||
printk("BIG sync established.\n");
|
||||
|
||||
#if defined(HAS_LED)
|
||||
printk("Stop blinking LED.\n");
|
||||
k_delayed_work_cancel(&blink_work);
|
||||
|
||||
/* Keep LED on */
|
||||
led_is_on = true;
|
||||
gpio_pin_set(dev, PIN, (int)led_is_on);
|
||||
#endif /* HAS_LED */
|
||||
|
||||
printk("Waiting for BIG sync lost...\n");
|
||||
err = k_sem_take(&sem_big_sync_lost, K_FOREVER);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
return;
|
||||
}
|
||||
printk("BIG sync lost.\n");
|
||||
|
||||
per_sync_lost_check:
|
||||
printk("Check for periodic sync lost...\n");
|
||||
err = k_sem_take(&sem_per_sync_lost, K_NO_WAIT);
|
||||
if (err) {
|
||||
/* Periodic Sync active, go back to creating BIG Sync */
|
||||
goto big_sync_create;
|
||||
}
|
||||
printk("Periodic sync lost.\n");
|
||||
} while (true);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue