Bluetooth: samples: Added dynamic Tx power ctrl sample (#17731)

This commit provides a sample dummy application demonstrating
the usage of the added dynamic Tx power control over the HCI
commands and HCI interfaces.

Signed-off-by: Andrei Stoica <stoica.razvan.andrei@gmail.com>
This commit is contained in:
Andrei Stoica 2019-10-22 14:07:02 +02:00 committed by Carles Cufí
parent cc19e72497
commit f72d5577f9
5 changed files with 379 additions and 0 deletions

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
project(hci_pwr_ctrl)
target_sources(app PRIVATE src/main.c)

View file

@ -0,0 +1,37 @@
.. _bluetooth-hci-pwr-ctrl-sample:
Bluetooth: HCI Power Control
############################
Overview
********
This sample application demonstrates the dynamic Tx power control over the LL
of the BLE controller via Zephyr HCI VS commands. The application implements a
peripheral advertising with varying Tx power. The initial advertiser TX power
for the first 5s of the application is the Kconfig set default TX power. Then,
the TX power variation of the advertiser is a repeatedly descending staircase
pattern ranging from -4 dB to -30 dB where the Tx power levels decrease every
5s.
Upon sucessful connection, the connection RSSI strength is being monitored and
the Tx power of the peripheral device is modulated per connection accordingly
such that energy is being saved depending on how powerful the RSSI of the
connection is. The peripheral implements a simple GATT profile exposing the
HR service notifying connected centrals about a dummy HR each 2s.
Requirements
************
* BlueZ running on the host, or
* A board with BLE support
* A central device & monitor (e.g. nRF Connect) to check the RSSI values
obtained from the peripheral.
Building and Running
********************
This sample can be found under :zephyr_file:`samples/bluetooth/hci_pwr_ctrl`
in the Zephyr tree.
See :ref:`bluetooth samples section <bluetooth-samples>` for details.

View file

@ -0,0 +1,11 @@
CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_PERIPHERAL=y
CONFIG_MAIN_STACK_SIZE=512
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=768
CONFIG_BT_GATT_HRS=y
CONFIG_BT_DEVICE_APPEARANCE=833
CONFIG_BT_DEVICE_NAME="Dynamic test beacon"
CONFIG_BT_CTLR_ADVANCED_FEATURES=y
CONFIG_BT_CTLR_CONN_RSSI=y
CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y

View file

@ -0,0 +1,7 @@
sample:
name: Bluetooth HCI-based Dynamic Power Control
tests:
sample.bluetooth.hci_pwr_ctrl:
harness: bluetooth
platform_whitelist: bbc_microbit nrf51_pca10028 nrf52_pca10040 qemu_cortex_m3 qemu_x86
tags: bluetooth

View file

@ -0,0 +1,317 @@
/* main.c - Application main entry point */
/*
* Copyright (c) 2019 Andrei Stoica
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <sys/printk.h>
#include <sys/util.h>
#include <sys/byteorder.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_vs.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
#include <bluetooth/services/hrs.h>
static struct bt_conn *default_conn;
static u16_t default_conn_handle;
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_UUID16_ALL, 0x0d, 0x18),
};
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
#define DEVICE_BEACON_TXPOWER_NUM 8
static struct k_thread pwr_thread_data;
static K_THREAD_STACK_DEFINE(pwr_thread_stack, 320);
static const s8_t txp[DEVICE_BEACON_TXPOWER_NUM] = {4, 0, -3, -8,
-15, -18, -23, -30};
static const struct bt_le_adv_param *param = BT_LE_ADV_PARAM
(BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME,
0x0020, 0x0020);
static void read_conn_rssi(u16_t handle, s8_t *rssi)
{
struct net_buf *buf, *rsp = NULL;
struct bt_hci_cp_read_rssi *cp;
struct bt_hci_rp_read_rssi *rp;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_READ_RSSI, sizeof(*cp));
if (!buf) {
printk("Unable to allocate command buffer\n");
return;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(handle);
err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_RSSI, buf, &rsp);
if (err) {
u8_t reason = rsp ?
((struct bt_hci_rp_read_rssi *)rsp->data)->status : 0;
printk("Read RSSI err: %d reason 0x%02x\n", err, reason);
return;
}
rp = (void *)rsp->data;
*rssi = rp->rssi;
net_buf_unref(rsp);
}
static void set_tx_power(u8_t handle_type, u16_t handle, s8_t tx_pwr_lvl)
{
struct bt_hci_cp_vs_write_tx_power_level *cp;
struct bt_hci_rp_vs_write_tx_power_level *rp;
struct net_buf *buf, *rsp = NULL;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
sizeof(*cp));
if (!buf) {
printk("Unable to allocate command buffer\n");
return;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(handle);
cp->handle_type = handle_type;
cp->tx_power_level = tx_pwr_lvl;
err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
buf, &rsp);
if (err) {
u8_t reason = rsp ?
((struct bt_hci_rp_vs_write_tx_power_level *)
rsp->data)->status : 0;
printk("Set Tx power err: %d reason 0x%02x\n", err, reason);
return;
}
rp = (void *)rsp->data;
printk("Actual Tx Power: %d\n", rp->selected_tx_power);
net_buf_unref(rsp);
}
static void get_tx_power(u8_t handle_type, u16_t handle, s8_t *tx_pwr_lvl)
{
struct bt_hci_cp_vs_read_tx_power_level *cp;
struct bt_hci_rp_vs_read_tx_power_level *rp;
struct net_buf *buf, *rsp = NULL;
int err;
*tx_pwr_lvl = 0xFF;
buf = bt_hci_cmd_create(BT_HCI_OP_VS_READ_TX_POWER_LEVEL,
sizeof(*cp));
if (!buf) {
printk("Unable to allocate command buffer\n");
return;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(handle);
cp->handle_type = handle_type;
err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_TX_POWER_LEVEL,
buf, &rsp);
if (err) {
u8_t reason = rsp ?
((struct bt_hci_rp_vs_read_tx_power_level *)
rsp->data)->status : 0;
printk("Read Tx power err: %d reason 0x%02x\n", err, reason);
return;
}
rp = (void *)rsp->data;
*tx_pwr_lvl = rp->tx_power_level;
net_buf_unref(rsp);
}
static void connected(struct bt_conn *conn, u8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
s8_t txp;
int ret;
if (err) {
printk("Connection failed (err 0x%02x)\n", err);
} else {
default_conn = bt_conn_ref(conn);
ret = bt_hci_get_conn_handle(default_conn,
&default_conn_handle);
if (ret) {
printk("No connection handle (err %d)\n", ret);
} else {
/* Send first at the default selected power */
bt_addr_le_to_str(bt_conn_get_dst(conn),
addr, sizeof(addr));
printk("Connected via connection (%d) at %s\n",
default_conn_handle, addr);
get_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN,
default_conn_handle, &txp);
printk("Connection (%d) - Initial Tx Power = %d\n",
default_conn_handle, txp);
set_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN,
default_conn_handle,
BT_HCI_VS_LL_TX_POWER_LEVEL_NO_PREF);
get_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN,
default_conn_handle, &txp);
printk("Connection (%d) - Tx Power = %d\n",
default_conn_handle, txp);
}
}
}
static void disconnected(struct bt_conn *conn, u8_t reason)
{
printk("Disconnected (reason 0x%02x)\n", reason);
if (default_conn) {
bt_conn_unref(default_conn);
default_conn = NULL;
}
}
static struct bt_conn_cb conn_callbacks = {
.connected = connected,
.disconnected = disconnected,
};
static void bt_ready(int err)
{
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
/* Start advertising */
err = bt_le_adv_start(param, ad, ARRAY_SIZE(ad),
NULL, 0);
if (err) {
printk("Advertising failed to start (err %d)\n", err);
return;
}
printk("Dynamic Tx power Beacon started\n");
}
static void hrs_notify(void)
{
static u8_t heartrate = 90U;
/* Heartrate measurements simulation */
heartrate++;
if (heartrate == 160U) {
heartrate = 90U;
}
bt_gatt_hrs_notify(heartrate);
}
void modulate_tx_power(void *p1, void *p2, void *p3)
{
s8_t txp_get = 0;
u8_t idx = 0;
while (1) {
if (!default_conn) {
printk("Set Tx power level to %d\n", txp[idx]);
set_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_ADV,
0, txp[idx]);
k_sleep(K_SECONDS(5));
printk("Get Tx power level -> ");
get_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_ADV,
0, &txp_get);
printk("TXP = %d\n", txp_get);
idx = (idx+1) % DEVICE_BEACON_TXPOWER_NUM;
} else {
s8_t rssi = 0xFF;
s8_t txp_adaptive;
idx = 0;
read_conn_rssi(default_conn_handle, &rssi);
printk("Connected (%d) - RSSI = %d\n",
default_conn_handle, rssi);
if (rssi > -70) {
txp_adaptive = -20;
} else if (rssi > -90) {
txp_adaptive = -12;
} else {
txp_adaptive = -4;
}
printk("Adaptive Tx power selected = %d\n",
txp_adaptive);
set_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN,
default_conn_handle, txp_adaptive);
get_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_CONN,
default_conn_handle, &txp_get);
printk("Connection (%d) TXP = %d\n",
default_conn_handle, txp_get);
k_sleep(K_SECONDS(1));
}
}
}
void main(void)
{
s8_t txp_get = 0xFF;
int err;
default_conn = NULL;
printk("Starting Dynamic Tx Power Beacon Demo\n");
/* Initialize the Bluetooth Subsystem */
err = bt_enable(bt_ready);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
}
printk("Get Tx power level ->");
get_tx_power(BT_HCI_VS_LL_HANDLE_TYPE_ADV, 0, &txp_get);
printk("-> default TXP = %d\n", txp_get);
bt_conn_cb_register(&conn_callbacks);
/* Wait for 5 seconds to give a chance users/testers
* to check that default Tx power is indeed the one
* selected in Kconfig.
*/
k_sleep(K_SECONDS(5));
k_thread_create(&pwr_thread_data, pwr_thread_stack,
K_THREAD_STACK_SIZEOF(pwr_thread_stack),
modulate_tx_power, NULL, NULL, NULL,
K_PRIO_COOP(10),
0, K_NO_WAIT);
k_thread_name_set(&pwr_thread_data, "DYN TX");
while (1) {
hrs_notify();
k_sleep(K_SECONDS(2));
}
}