samples: bluetooth: Added sample for Extended Advertisements
Both advertiser and scanner demonstrate the use of extended advertising and scanning, and how to gracefully restart the extended advertisements through the use of recycle() callback. In the sample, the advertiser initiates a connectable advertisement set, which prompts the scanner to connect. After the connection is established, the advertiser waits for 5 seconds to disconnect. After the connection is dropped, the advertiser immediately restarts broadcasting, while the scanner cools-down for 5 seconds to restart its process. Signed-off-by: Luis Ubieda <luisf@croxel.com>
This commit is contained in:
parent
7e5b2a79fc
commit
2745f6a885
9 changed files with 476 additions and 0 deletions
96
samples/bluetooth/extended_adv/README.rst
Normal file
96
samples/bluetooth/extended_adv/README.rst
Normal file
|
@ -0,0 +1,96 @@
|
|||
.. _bluetooth_extended_advertising_sample:
|
||||
|
||||
Bluetooth: Extended Advertising
|
||||
################################
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
This sample demonstrates the use of the extended advertising feature, by:
|
||||
|
||||
- Outlining the steps required to initialize an extended advertising application.
|
||||
- Demo how to gracefully restart the functionality, after a disconnect.
|
||||
|
||||
The sample consists of the advertiser initiating a connectable advertisement set,
|
||||
which prompts the scanner to connect after scanning for extended advertisements.
|
||||
Once the connection is established, the advertiser waits for 5 seconds to disconnect.
|
||||
After the connection is dropped, the advertiser immediately restarts broadcasting,
|
||||
while the scanner cools-down for 5 seconds to restart its process.
|
||||
|
||||
This sample handles all actions in a separate thread, to promote good design
|
||||
practices. Even though it is not strictly required, scheduling from another context is
|
||||
strongly recommended (e.g. using a work item), as re-starting an advertiser or
|
||||
scanner from within the `recycled` callback exposes the application to deadlocking.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
* Two boards with Bluetooth Low Energy support
|
||||
|
||||
Building and Running
|
||||
********************
|
||||
|
||||
This sample can be found under
|
||||
:zephyr_file:`samples/bluetooth/extended_adv` in the Zephyr tree.
|
||||
|
||||
See :ref:`bluetooth samples section <bluetooth-samples>` for details.
|
||||
|
||||
This sample uses two applications, so two devices need to be setup.
|
||||
Flash one device with the scanner application, and another device with the
|
||||
advertiser application.
|
||||
|
||||
The two devices should automatically connect if they are close enough.
|
||||
|
||||
Here are the outputs you should get by default:
|
||||
|
||||
Advertiser:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
*** Booting Zephyr OS build zephyr-v3.5.0-4935-gfc7972183da5 ***
|
||||
Starting Extended Advertising Demo
|
||||
Starting Extended Advertising
|
||||
Connected (err 0x00)
|
||||
Connected state!
|
||||
Initiating disconnect within 5 seconds...
|
||||
Disconnected (reason 0x16)
|
||||
Connection object available from previous conn. Disconnect is complete!
|
||||
Disconnected state! Restarting advertising
|
||||
Starting Extended Advertising
|
||||
Connected (err 0x00)
|
||||
Connected state!
|
||||
Initiating disconnect within 5 seconds...
|
||||
Disconnected (reason 0x16)
|
||||
Connection object available from previous conn. Disconnect is complete!
|
||||
Disconnected state! Restarting advertising
|
||||
Starting Extended Advertising
|
||||
|
||||
Scanner:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
*** Booting Zephyr OS build zephyr-v3.5.0-4935-ge3308caf97bc ***
|
||||
Starting Extended Advertising Demo [Scanner]
|
||||
Found extended advertisement packet!
|
||||
Stopping scan
|
||||
Connected (err 0x00)
|
||||
Connected state!
|
||||
Disconnected (reason 0x13)
|
||||
Recycled cb called!
|
||||
Disconnected, cooldown for 5 seconds!
|
||||
Starting to scan for extended adv
|
||||
Found extended advertisement packet!
|
||||
Stopping scan
|
||||
Connected (err 0x00)
|
||||
Connected state!
|
||||
Disconnected (reason 0x13)
|
||||
Recycled cb called!
|
||||
Disconnected, cooldown for 5 seconds!
|
||||
Starting to scan for extended adv
|
||||
Found extended advertisement packet!
|
||||
Stopping scan
|
||||
Connected (err 0x00)
|
||||
Connected state!
|
||||
Disconnected (reason 0x13)
|
||||
Recycled cb called!
|
||||
Disconnected, cooldown for 5 seconds!
|
8
samples/bluetooth/extended_adv/advertiser/CMakeLists.txt
Normal file
8
samples/bluetooth/extended_adv/advertiser/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright 2024 Croxel, Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(extended_adv)
|
||||
|
||||
target_sources(app PRIVATE src/main.c)
|
6
samples/bluetooth/extended_adv/advertiser/prj.conf
Normal file
6
samples/bluetooth/extended_adv/advertiser/prj.conf
Normal file
|
@ -0,0 +1,6 @@
|
|||
CONFIG_BT=y
|
||||
CONFIG_BT_DEVICE_NAME="test_ext_adv"
|
||||
CONFIG_BT_PERIPHERAL=y
|
||||
CONFIG_BT_EXT_ADV=y
|
||||
|
||||
CONFIG_ASSERT=y
|
7
samples/bluetooth/extended_adv/advertiser/sample.yaml
Normal file
7
samples/bluetooth/extended_adv/advertiser/sample.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
sample:
|
||||
name: Bluetooth Extended Advertising Advertiser
|
||||
tests:
|
||||
sample.bluetooth.extended_advertising.advertiser:
|
||||
harness: bluetooth
|
||||
platform_allow: nrf52840dk_nrf52840
|
||||
tags: bluetooth
|
144
samples/bluetooth/extended_adv/advertiser/src/main.c
Normal file
144
samples/bluetooth/extended_adv/advertiser/src/main.c
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* Copyright (c) 2024 Croxel, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/gap.h>
|
||||
|
||||
static struct bt_conn *default_conn;
|
||||
|
||||
enum bt_sample_adv_evt {
|
||||
BT_SAMPLE_EVT_CONNECTED,
|
||||
BT_SAMPLE_EVT_DISCONNECTED,
|
||||
BT_SAMPLE_EVT_MAX,
|
||||
};
|
||||
|
||||
enum bt_sample_adv_st {
|
||||
BT_SAMPLE_ST_ADV,
|
||||
BT_SAMPLE_ST_CONNECTED,
|
||||
};
|
||||
|
||||
static ATOMIC_DEFINE(evt_bitmask, BT_SAMPLE_EVT_MAX);
|
||||
|
||||
static volatile enum bt_sample_adv_st app_st = BT_SAMPLE_ST_ADV;
|
||||
|
||||
static struct k_poll_signal poll_sig = K_POLL_SIGNAL_INITIALIZER(poll_sig);
|
||||
static struct k_poll_event poll_evt = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL,
|
||||
K_POLL_MODE_NOTIFY_ONLY, &poll_sig);
|
||||
|
||||
static void raise_evt(enum bt_sample_adv_evt evt)
|
||||
{
|
||||
(void)atomic_set_bit(evt_bitmask, evt);
|
||||
k_poll_signal_raise(poll_evt.signal, 1);
|
||||
}
|
||||
|
||||
static void connected_cb(struct bt_conn *conn, uint8_t err)
|
||||
{
|
||||
printk("Connected (err 0x%02X)\n", err);
|
||||
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
__ASSERT(!default_conn, "Attempting to override existing connection object!");
|
||||
default_conn = bt_conn_ref(conn);
|
||||
|
||||
raise_evt(BT_SAMPLE_EVT_CONNECTED);
|
||||
}
|
||||
|
||||
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
|
||||
{
|
||||
printk("Disconnected (reason 0x%02X)\n", reason);
|
||||
|
||||
__ASSERT(conn == default_conn, "Unexpected disconnected callback");
|
||||
|
||||
bt_conn_unref(default_conn);
|
||||
default_conn = NULL;
|
||||
}
|
||||
|
||||
static void recycled_cb(void)
|
||||
{
|
||||
printk("Connection object available from previous conn. Disconnect is complete!\n");
|
||||
raise_evt(BT_SAMPLE_EVT_DISCONNECTED);
|
||||
}
|
||||
|
||||
BT_CONN_CB_DEFINE(conn_cb) = {
|
||||
.connected = connected_cb,
|
||||
.disconnected = disconnected_cb,
|
||||
.recycled = recycled_cb,
|
||||
};
|
||||
|
||||
static int start_advertising(struct bt_le_ext_adv *adv)
|
||||
{
|
||||
int err;
|
||||
|
||||
printk("Starting Extended Advertising\n");
|
||||
err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT);
|
||||
if (err) {
|
||||
printk("Failed to start extended advertising (err %d)\n", err);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int err;
|
||||
struct bt_le_ext_adv *adv;
|
||||
|
||||
printk("Starting Extended Advertising Demo\n");
|
||||
|
||||
/* Initialize the Bluetooth Subsystem */
|
||||
err = bt_enable(NULL);
|
||||
if (err) {
|
||||
printk("Bluetooth init failed (err %d)\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Create a non-connectable non-scannable advertising set */
|
||||
err = bt_le_ext_adv_create(BT_LE_EXT_ADV_CONN_NAME, NULL, &adv);
|
||||
if (err) {
|
||||
printk("Failed to create advertising set (err %d)\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = start_advertising(adv);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
k_poll(&poll_evt, 1, K_FOREVER);
|
||||
|
||||
k_poll_signal_reset(poll_evt.signal);
|
||||
poll_evt.state = K_POLL_STATE_NOT_READY;
|
||||
|
||||
/* Identify event and act upon if applicable */
|
||||
if (atomic_test_and_clear_bit(evt_bitmask, BT_SAMPLE_EVT_CONNECTED) &&
|
||||
app_st == BT_SAMPLE_ST_ADV) {
|
||||
|
||||
printk("Connected state!\n");
|
||||
app_st = BT_SAMPLE_ST_CONNECTED;
|
||||
|
||||
printk("Initiating disconnect within 5 seconds...\n");
|
||||
k_sleep(K_SECONDS(5));
|
||||
|
||||
bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
||||
|
||||
} else if (atomic_test_and_clear_bit(evt_bitmask, BT_SAMPLE_EVT_DISCONNECTED) &&
|
||||
app_st == BT_SAMPLE_ST_CONNECTED) {
|
||||
|
||||
printk("Disconnected state! Restarting advertising\n");
|
||||
app_st = BT_SAMPLE_ST_ADV;
|
||||
err = start_advertising(adv);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
8
samples/bluetooth/extended_adv/scanner/CMakeLists.txt
Normal file
8
samples/bluetooth/extended_adv/scanner/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright 2024 Croxel, Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(extended_adv_scanner)
|
||||
|
||||
target_sources(app PRIVATE src/main.c)
|
5
samples/bluetooth/extended_adv/scanner/prj.conf
Normal file
5
samples/bluetooth/extended_adv/scanner/prj.conf
Normal file
|
@ -0,0 +1,5 @@
|
|||
CONFIG_BT=y
|
||||
CONFIG_BT_CENTRAL=y
|
||||
CONFIG_BT_EXT_ADV=y
|
||||
|
||||
CONFIG_ASSERT=y
|
7
samples/bluetooth/extended_adv/scanner/sample.yaml
Normal file
7
samples/bluetooth/extended_adv/scanner/sample.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
sample:
|
||||
name: Bluetooth Extended Advertising Scanner
|
||||
tests:
|
||||
sample.bluetooth.extended_advertising.scanner:
|
||||
harness: bluetooth
|
||||
platform_allow: nrf52840dk_nrf52840
|
||||
tags: bluetooth
|
195
samples/bluetooth/extended_adv/scanner/src/main.c
Normal file
195
samples/bluetooth/extended_adv/scanner/src/main.c
Normal file
|
@ -0,0 +1,195 @@
|
|||
/**
|
||||
* Copyright (c) 2024 Croxel, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
|
||||
#define NAME_LEN 30
|
||||
|
||||
static struct bt_conn *default_conn;
|
||||
bt_addr_le_t ext_addr;
|
||||
|
||||
enum bt_sample_scan_evt {
|
||||
BT_SAMPLE_EVT_EXT_ADV_FOUND,
|
||||
BT_SAMPLE_EVT_CONNECTED,
|
||||
BT_SAMPLE_EVT_DISCONNECTED,
|
||||
BT_SAMPLE_EVT_SCAN_DUE,
|
||||
BT_SAMPLE_EVT_MAX,
|
||||
};
|
||||
|
||||
enum bt_sample_scan_st {
|
||||
BT_SAMPLE_ST_SCANNING,
|
||||
BT_SAMPLE_ST_CONNECTING,
|
||||
BT_SAMPLE_ST_CONNECTED,
|
||||
BT_SAMPLE_ST_COOLDOWN,
|
||||
};
|
||||
|
||||
static volatile enum bt_sample_scan_st app_st = BT_SAMPLE_ST_SCANNING;
|
||||
|
||||
static ATOMIC_DEFINE(evt_bitmask, BT_SAMPLE_EVT_MAX);
|
||||
|
||||
static struct k_poll_signal poll_sig = K_POLL_SIGNAL_INITIALIZER(poll_sig);
|
||||
static struct k_poll_event poll_evt = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL,
|
||||
K_POLL_MODE_NOTIFY_ONLY, &poll_sig);
|
||||
|
||||
static void raise_evt(enum bt_sample_scan_evt evt)
|
||||
{
|
||||
(void)atomic_set_bit(evt_bitmask, evt);
|
||||
k_poll_signal_raise(poll_evt.signal, 1);
|
||||
}
|
||||
|
||||
static void connected_cb(struct bt_conn *conn, uint8_t err)
|
||||
{
|
||||
printk("Connected (err 0x%02X)\n", err);
|
||||
|
||||
if (err) {
|
||||
bt_conn_unref(default_conn);
|
||||
default_conn = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
raise_evt(BT_SAMPLE_EVT_CONNECTED);
|
||||
}
|
||||
|
||||
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
|
||||
{
|
||||
bt_conn_unref(default_conn);
|
||||
default_conn = NULL;
|
||||
|
||||
printk("Disconnected (reason 0x%02X)\n", reason);
|
||||
}
|
||||
|
||||
static void recycled_cb(void)
|
||||
{
|
||||
printk("Connection object available from previous conn. Disconnect is complete!\n");
|
||||
raise_evt(BT_SAMPLE_EVT_DISCONNECTED);
|
||||
}
|
||||
|
||||
BT_CONN_CB_DEFINE(conn_cb) = {
|
||||
.connected = connected_cb,
|
||||
.disconnected = disconnected_cb,
|
||||
.recycled = recycled_cb,
|
||||
};
|
||||
|
||||
|
||||
static void scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf)
|
||||
{
|
||||
if (info->adv_type & BT_GAP_ADV_TYPE_EXT_ADV &&
|
||||
info->adv_props & BT_GAP_ADV_PROP_EXT_ADV &&
|
||||
info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) {
|
||||
/* Attempt connection request for device with extended advertisements */
|
||||
memcpy(&ext_addr, info->addr, sizeof(ext_addr));
|
||||
raise_evt(BT_SAMPLE_EVT_EXT_ADV_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_le_scan_cb scan_callbacks = {
|
||||
.recv = scan_recv,
|
||||
};
|
||||
|
||||
static inline int attempt_connection(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
printk("Stopping scan\n");
|
||||
err = bt_le_scan_stop();
|
||||
if (err) {
|
||||
printk("Failed to stop scan: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = bt_conn_le_create(&ext_addr, BT_CONN_LE_CREATE_CONN,
|
||||
BT_LE_CONN_PARAM_DEFAULT, &default_conn);
|
||||
if (err) {
|
||||
printk("Failed to establish conn: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int start_scanning(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
|
||||
if (err) {
|
||||
printk("failed (err %d)\n", err);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
printk("Starting Extended Advertising Demo [Scanner]\n");
|
||||
|
||||
/* Initialize the Bluetooth Subsystem */
|
||||
err = bt_enable(NULL);
|
||||
if (err) {
|
||||
printk("Bluetooth init failed (err %d)\n", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bt_le_scan_cb_register(&scan_callbacks);
|
||||
|
||||
err = start_scanning();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
(void)k_poll(&poll_evt, 1, K_FOREVER);
|
||||
|
||||
k_poll_signal_reset(poll_evt.signal);
|
||||
poll_evt.state = K_POLL_STATE_NOT_READY;
|
||||
|
||||
/* Identify event and act upon if applicable */
|
||||
if (atomic_test_and_clear_bit(evt_bitmask, BT_SAMPLE_EVT_EXT_ADV_FOUND) &&
|
||||
app_st == BT_SAMPLE_ST_SCANNING) {
|
||||
|
||||
printk("Found extended advertisement packet!\n");
|
||||
app_st = BT_SAMPLE_ST_CONNECTING;
|
||||
err = attempt_connection();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
} else if (atomic_test_and_clear_bit(evt_bitmask, BT_SAMPLE_EVT_CONNECTED) &&
|
||||
(app_st == BT_SAMPLE_ST_CONNECTING)) {
|
||||
|
||||
printk("Connected state!\n");
|
||||
app_st = BT_SAMPLE_ST_CONNECTED;
|
||||
|
||||
} else if (atomic_test_and_clear_bit(evt_bitmask, BT_SAMPLE_EVT_DISCONNECTED) &&
|
||||
(app_st == BT_SAMPLE_ST_CONNECTED)) {
|
||||
|
||||
printk("Disconnected, cooldown for 5 seconds!\n");
|
||||
app_st = BT_SAMPLE_ST_COOLDOWN;
|
||||
|
||||
/* Wait a few seconds before starting to re-scan again... */
|
||||
k_sleep(K_SECONDS(5));
|
||||
|
||||
raise_evt(BT_SAMPLE_EVT_SCAN_DUE);
|
||||
|
||||
} else if (atomic_test_and_clear_bit(evt_bitmask, BT_SAMPLE_EVT_SCAN_DUE) &&
|
||||
(app_st == BT_SAMPLE_ST_COOLDOWN)) {
|
||||
|
||||
printk("Starting to scan for extended adv\n");
|
||||
app_st = BT_SAMPLE_ST_SCANNING;
|
||||
err = start_scanning();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue