zephyr/subsys/bluetooth/shell/gatt.c
Lingao Meng 786b9a0ad4 Bluetooth: Host: Add const prefix for UUID
Add const prefix for service uuid and char uuid.

Since Service UUID and Char UUID should not change in the service
definition, they are most reasonably defined as rodata, also for
save some ram footprint.

The field `attr->user_data` type is `void *`, as this PR change
all Service UUID to rodata, so there must add (void *) to avoid warning.

Signed-off-by: Lingao Meng <menglingao@xiaomi.com>
2023-12-07 16:16:43 +00:00

1361 lines
33 KiB
C

/** @file
* @brief Bluetooth GATT shell functions
*
*/
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/types.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/shell/shell_string_conv.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/shell/shell.h>
#include "bt.h"
#if defined(CONFIG_BT_GATT_CLIENT) || defined(CONFIG_BT_GATT_DYNAMIC_DB)
extern uint8_t selected_id;
static struct write_stats {
uint32_t count;
uint32_t len;
uint32_t total;
uint32_t rate;
} write_stats;
static void update_write_stats(uint16_t len)
{
static uint32_t cycle_stamp;
uint32_t delta;
delta = k_cycle_get_32() - cycle_stamp;
delta = (uint32_t)k_cyc_to_ns_floor64(delta);
if (!delta) {
delta = 1;
}
write_stats.count++;
write_stats.total += len;
/* if last data rx-ed was greater than 1 second in the past,
* reset the metrics.
*/
if (delta > 1000000000) {
write_stats.len = 0U;
write_stats.rate = 0U;
cycle_stamp = k_cycle_get_32();
} else {
write_stats.len += len;
write_stats.rate = ((uint64_t)write_stats.len << 3) *
1000000000U / delta;
}
}
#if defined(CONFIG_BT_EATT)
#define SET_CHAN_OPT_ANY(params) (params).chan_opt = BT_ATT_CHAN_OPT_NONE
#else
#define SET_CHAN_OPT_ANY(params)
#endif /* CONFIG_BT_EATT */
static void print_write_stats(void)
{
shell_print(ctx_shell, "Write #%u: %u bytes (%u bps)",
write_stats.count, write_stats.total, write_stats.rate);
}
#endif /* CONFIG_BT_GATT_CLIENT || CONFIG_BT_GATT_DYNAMIC_DB */
#if defined(CONFIG_BT_GATT_CLIENT)
static void reset_write_stats(void)
{
memset(&write_stats, 0, sizeof(write_stats));
}
/* This variable is write-locked when `(exchange_params.func != NULL)`.
* Must be zero-initialized when unlocked.
*/
static struct bt_gatt_exchange_params exchange_params;
static void exchange_func(struct bt_conn *conn, uint8_t err,
struct bt_gatt_exchange_params *params)
{
shell_print(ctx_shell, "Exchange %s", err == 0U ? "successful" :
"failed");
/* Release global `exchange_params`. */
__ASSERT_NO_MSG(params == &exchange_params);
(void)memset(params, 0, sizeof(*params));
}
static int cmd_exchange_mtu(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
if (!default_conn) {
shell_print(sh, "Not connected");
return -ENOEXEC;
}
if (exchange_params.func) {
shell_print(sh, "Shell command busy. A previous invocation is in progress.");
return -EBUSY;
}
exchange_params.func = exchange_func;
err = bt_gatt_exchange_mtu(default_conn, &exchange_params);
if (err) {
/* Release global `exchange_params`. */
exchange_params.func = NULL;
}
if (err == -EALREADY) {
shell_print(sh, "Already exchanged");
} else if (err) {
shell_print(sh, "Exchange failed (err %d)", err);
} else {
shell_print(sh, "Exchange pending");
}
return err;
}
static struct bt_gatt_discover_params discover_params;
static struct bt_uuid_16 uuid = BT_UUID_INIT_16(0);
static void print_chrc_props(const struct shell *sh, uint8_t properties)
{
shell_print(sh, "Properties: ");
if (properties & BT_GATT_CHRC_BROADCAST) {
shell_print(sh, "[bcast]");
}
if (properties & BT_GATT_CHRC_READ) {
shell_print(sh, "[read]");
}
if (properties & BT_GATT_CHRC_WRITE) {
shell_print(sh, "[write]");
}
if (properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) {
shell_print(sh, "[write w/w rsp]");
}
if (properties & BT_GATT_CHRC_NOTIFY) {
shell_print(sh, "[notify]");
}
if (properties & BT_GATT_CHRC_INDICATE) {
shell_print(sh, "[indicate]");
}
if (properties & BT_GATT_CHRC_AUTH) {
shell_print(sh, "[auth]");
}
if (properties & BT_GATT_CHRC_EXT_PROP) {
shell_print(sh, "[ext prop]");
}
shell_print(sh, "");
}
static uint8_t discover_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_service_val *gatt_service;
struct bt_gatt_chrc *gatt_chrc;
struct bt_gatt_include *gatt_include;
char str[BT_UUID_STR_LEN];
if (!attr) {
shell_print(ctx_shell, "Discover complete");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
switch (params->type) {
case BT_GATT_DISCOVER_SECONDARY:
case BT_GATT_DISCOVER_PRIMARY:
gatt_service = attr->user_data;
bt_uuid_to_str(gatt_service->uuid, str, sizeof(str));
shell_print(ctx_shell, "Service %s found: start handle %x, "
"end_handle %x", str, attr->handle,
gatt_service->end_handle);
break;
case BT_GATT_DISCOVER_CHARACTERISTIC:
gatt_chrc = attr->user_data;
bt_uuid_to_str(gatt_chrc->uuid, str, sizeof(str));
shell_print(ctx_shell, "Characteristic %s found: handle %x",
str, attr->handle);
print_chrc_props(ctx_shell, gatt_chrc->properties);
break;
case BT_GATT_DISCOVER_INCLUDE:
gatt_include = attr->user_data;
bt_uuid_to_str(gatt_include->uuid, str, sizeof(str));
shell_print(ctx_shell, "Include %s found: handle %x, start %x, "
"end %x", str, attr->handle,
gatt_include->start_handle,
gatt_include->end_handle);
break;
default:
bt_uuid_to_str(attr->uuid, str, sizeof(str));
shell_print(ctx_shell, "Descriptor %s found: handle %x", str,
attr->handle);
break;
}
return BT_GATT_ITER_CONTINUE;
}
static int cmd_discover(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (discover_params.func) {
shell_print(sh, "Discover ongoing");
return -ENOEXEC;
}
discover_params.func = discover_func;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
SET_CHAN_OPT_ANY(discover_params);
if (argc > 1) {
/* Only set the UUID if the value is valid (non zero) */
uuid.val = strtoul(argv[1], NULL, 16);
if (uuid.val) {
discover_params.uuid = &uuid.uuid;
}
}
if (argc > 2) {
discover_params.start_handle = strtoul(argv[2], NULL, 16);
if (argc > 3) {
discover_params.end_handle = strtoul(argv[3], NULL, 16);
}
}
if (!strcmp(argv[0], "discover")) {
discover_params.type = BT_GATT_DISCOVER_ATTRIBUTE;
} else if (!strcmp(argv[0], "discover-secondary")) {
discover_params.type = BT_GATT_DISCOVER_SECONDARY;
} else if (!strcmp(argv[0], "discover-include")) {
discover_params.type = BT_GATT_DISCOVER_INCLUDE;
} else if (!strcmp(argv[0], "discover-characteristic")) {
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
} else if (!strcmp(argv[0], "discover-descriptor")) {
discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
} else {
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
}
err = bt_gatt_discover(default_conn, &discover_params);
if (err) {
shell_error(sh, "Discover failed (err %d)", err);
} else {
shell_print(sh, "Discover pending");
}
return err;
}
static struct bt_gatt_read_params read_params;
static uint8_t read_func(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
shell_print(ctx_shell, "Read complete: err 0x%02x length %u", err, length);
if (!data) {
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
} else {
shell_hexdump(ctx_shell, data, length);
}
return BT_GATT_ITER_CONTINUE;
}
static int cmd_read(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (read_params.func) {
shell_print(sh, "Read ongoing");
return -ENOEXEC;
}
read_params.func = read_func;
read_params.handle_count = 1;
read_params.single.handle = strtoul(argv[1], NULL, 16);
read_params.single.offset = 0U;
SET_CHAN_OPT_ANY(read_params);
if (argc > 2) {
read_params.single.offset = strtoul(argv[2], NULL, 16);
}
err = bt_gatt_read(default_conn, &read_params);
if (err) {
shell_error(sh, "Read failed (err %d)", err);
} else {
shell_print(sh, "Read pending");
}
return err;
}
static int cmd_mread(const struct shell *sh, size_t argc, char *argv[])
{
uint16_t h[8];
size_t i;
int err;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (read_params.func) {
shell_print(sh, "Read ongoing");
return -ENOEXEC;
}
if ((argc - 1) > ARRAY_SIZE(h)) {
shell_print(sh, "Enter max %zu handle items to read",
ARRAY_SIZE(h));
return -EINVAL;
}
for (i = 0; i < argc - 1; i++) {
h[i] = strtoul(argv[i + 1], NULL, 16);
}
read_params.func = read_func;
read_params.handle_count = i;
read_params.multiple.handles = h;
read_params.multiple.variable = true;
SET_CHAN_OPT_ANY(read_params);
err = bt_gatt_read(default_conn, &read_params);
if (err) {
shell_error(sh, "GATT multiple read request failed (err %d)",
err);
}
return err;
}
static int cmd_read_uuid(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (read_params.func) {
shell_print(sh, "Read ongoing");
return -ENOEXEC;
}
read_params.func = read_func;
read_params.handle_count = 0;
read_params.by_uuid.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
read_params.by_uuid.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
SET_CHAN_OPT_ANY(read_params);
if (argc > 1) {
uuid.val = strtoul(argv[1], NULL, 16);
if (uuid.val) {
read_params.by_uuid.uuid = &uuid.uuid;
}
}
if (argc > 2) {
read_params.by_uuid.start_handle = strtoul(argv[2], NULL, 16);
if (argc > 3) {
read_params.by_uuid.end_handle = strtoul(argv[3],
NULL, 16);
}
}
err = bt_gatt_read(default_conn, &read_params);
if (err) {
shell_error(sh, "Read failed (err %d)", err);
} else {
shell_print(sh, "Read pending");
}
return err;
}
static struct bt_gatt_write_params write_params;
static uint8_t gatt_write_buf[BT_ATT_MAX_ATTRIBUTE_LEN];
static void write_func(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
shell_print(ctx_shell, "Write complete: err 0x%02x", err);
(void)memset(&write_params, 0, sizeof(write_params));
}
static int cmd_write(const struct shell *sh, size_t argc, char *argv[])
{
int err;
uint16_t handle, offset;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (write_params.func) {
shell_error(sh, "Write ongoing");
return -ENOEXEC;
}
handle = strtoul(argv[1], NULL, 16);
offset = strtoul(argv[2], NULL, 16);
write_params.length = hex2bin(argv[3], strlen(argv[3]),
gatt_write_buf, sizeof(gatt_write_buf));
if (write_params.length == 0) {
shell_error(sh, "No data set");
return -ENOEXEC;
}
write_params.data = gatt_write_buf;
write_params.handle = handle;
write_params.offset = offset;
write_params.func = write_func;
SET_CHAN_OPT_ANY(write_params);
err = bt_gatt_write(default_conn, &write_params);
if (err) {
write_params.func = NULL;
shell_error(sh, "Write failed (err %d)", err);
} else {
shell_print(sh, "Write pending");
}
return err;
}
static void write_without_rsp_cb(struct bt_conn *conn, void *user_data)
{
uint16_t len = POINTER_TO_UINT(user_data);
update_write_stats(len);
print_write_stats();
}
static int cmd_write_without_rsp(const struct shell *sh,
size_t argc, char *argv[])
{
uint16_t handle;
uint16_t repeat;
int err;
uint16_t len;
bool sign;
bt_gatt_complete_func_t func = NULL;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
sign = !strcmp(argv[0], "signed-write");
if (!sign) {
if (!strcmp(argv[0], "write-without-response-cb")) {
func = write_without_rsp_cb;
reset_write_stats();
}
}
handle = strtoul(argv[1], NULL, 16);
gatt_write_buf[0] = strtoul(argv[2], NULL, 16);
len = 1U;
if (argc > 3) {
int i;
len = MIN(strtoul(argv[3], NULL, 16), sizeof(gatt_write_buf));
for (i = 1; i < len; i++) {
gatt_write_buf[i] = gatt_write_buf[0];
}
}
repeat = 0U;
err = 0;
if (argc > 4) {
repeat = strtoul(argv[4], NULL, 16);
}
if (!repeat) {
repeat = 1U;
}
while (repeat--) {
err = bt_gatt_write_without_response_cb(default_conn, handle,
gatt_write_buf, len,
sign, func,
UINT_TO_POINTER(len));
if (err) {
break;
}
k_yield();
}
shell_print(sh, "Write Complete (err %d)", err);
return err;
}
static struct bt_gatt_subscribe_params subscribe_params;
static uint8_t notify_func(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
if (!data) {
shell_print(ctx_shell, "Unsubscribed");
params->value_handle = 0U;
return BT_GATT_ITER_STOP;
}
shell_print(ctx_shell, "Notification: value_handle %u, length %u",
params->value_handle, length);
shell_hexdump(ctx_shell, data, length);
return BT_GATT_ITER_CONTINUE;
}
static int cmd_subscribe(const struct shell *sh, size_t argc, char *argv[])
{
int err;
if (subscribe_params.value_handle) {
shell_error(sh, "Cannot subscribe: subscription to %x "
"already exists", subscribe_params.value_handle);
return -ENOEXEC;
}
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
subscribe_params.ccc_handle = strtoul(argv[1], NULL, 16);
subscribe_params.value_handle = strtoul(argv[2], NULL, 16);
subscribe_params.value = BT_GATT_CCC_NOTIFY;
subscribe_params.notify = notify_func;
SET_CHAN_OPT_ANY(subscribe_params);
#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC)
if (subscribe_params.ccc_handle == 0) {
static struct bt_gatt_discover_params disc_params;
subscribe_params.disc_params = &disc_params;
subscribe_params.end_handle = 0xFFFF;
}
#endif /* CONFIG_BT_GATT_AUTO_DISCOVER_CCC */
if (argc > 3 && !strcmp(argv[3], "ind")) {
subscribe_params.value = BT_GATT_CCC_INDICATE;
}
err = bt_gatt_subscribe(default_conn, &subscribe_params);
if (err) {
subscribe_params.value_handle = 0U;
shell_error(sh, "Subscribe failed (err %d)", err);
} else {
shell_print(sh, "Subscribed");
}
return err;
}
static int cmd_resubscribe(const struct shell *sh, size_t argc,
char *argv[])
{
bt_addr_le_t addr;
int err;
if (subscribe_params.value_handle) {
shell_error(sh, "Cannot resubscribe: subscription to %x"
" already exists", subscribe_params.value_handle);
return -ENOEXEC;
}
err = bt_addr_le_from_str(argv[1], argv[2], &addr);
if (err) {
shell_error(sh, "Invalid peer address (err %d)", err);
return -ENOEXEC;
}
subscribe_params.ccc_handle = strtoul(argv[3], NULL, 16);
subscribe_params.value_handle = strtoul(argv[4], NULL, 16);
subscribe_params.value = BT_GATT_CCC_NOTIFY;
subscribe_params.notify = notify_func;
SET_CHAN_OPT_ANY(subscribe_params);
if (argc > 5 && !strcmp(argv[5], "ind")) {
subscribe_params.value = BT_GATT_CCC_INDICATE;
}
err = bt_gatt_resubscribe(selected_id, &addr, &subscribe_params);
if (err) {
subscribe_params.value_handle = 0U;
shell_error(sh, "Resubscribe failed (err %d)", err);
} else {
shell_print(sh, "Resubscribed");
}
return err;
}
static int cmd_unsubscribe(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
if (!default_conn) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}
if (!subscribe_params.value_handle) {
shell_error(sh, "No subscription found");
return -ENOEXEC;
}
err = bt_gatt_unsubscribe(default_conn, &subscribe_params);
if (err) {
shell_error(sh, "Unsubscribe failed (err %d)", err);
} else {
shell_print(sh, "Unsubscribe success");
}
return err;
}
#endif /* CONFIG_BT_GATT_CLIENT */
static struct db_stats {
uint16_t svc_count;
uint16_t attr_count;
uint16_t chrc_count;
uint16_t ccc_count;
} stats;
static uint8_t print_attr(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
const struct shell *sh = user_data;
char str[BT_UUID_STR_LEN];
stats.attr_count++;
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) ||
!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY)) {
stats.svc_count++;
}
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC)) {
stats.chrc_count++;
}
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) &&
attr->write == bt_gatt_attr_write_ccc) {
stats.ccc_count++;
}
bt_uuid_to_str(attr->uuid, str, sizeof(str));
shell_print(sh, "attr %p handle 0x%04x uuid %s perm 0x%02x",
attr, handle, str, attr->perm);
return BT_GATT_ITER_CONTINUE;
}
static int cmd_show_db(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_uuid_16 uuid16;
size_t total_len;
memset(&stats, 0, sizeof(stats));
if (argc > 1) {
uint16_t num_matches = 0;
uuid16.uuid.type = BT_UUID_TYPE_16;
uuid16.val = strtoul(argv[1], NULL, 16);
if (argc > 2) {
num_matches = strtoul(argv[2], NULL, 10);
}
bt_gatt_foreach_attr_type(0x0001, 0xffff, &uuid16.uuid, NULL,
num_matches, print_attr,
(void *)sh);
return 0;
}
bt_gatt_foreach_attr(0x0001, 0xffff, print_attr, (void *)sh);
if (!stats.attr_count) {
shell_print(sh, "No attribute found");
return 0;
}
total_len = stats.svc_count * sizeof(struct bt_gatt_service);
total_len += stats.chrc_count * sizeof(struct bt_gatt_chrc);
total_len += stats.attr_count * sizeof(struct bt_gatt_attr);
total_len += stats.ccc_count * sizeof(struct _bt_gatt_ccc);
shell_print(sh, "=================================================");
shell_print(sh, "Total: %u services %u attributes (%zu bytes)",
stats.svc_count, stats.attr_count, total_len);
return 0;
}
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
/* Custom Service Variables */
static const struct bt_uuid_128 vnd_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0));
static const struct bt_uuid_128 vnd_auth_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef2));
static const struct bt_uuid_128 vnd_long_uuid1 = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef3));
static const struct bt_uuid_128 vnd_long_uuid2 = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x12340, 0x5678cefaadde));
static uint8_t vnd_value[] = { 'V', 'e', 'n', 'd', 'o', 'r' };
static const struct bt_uuid_128 vnd1_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x12340, 0x56789abcdef4));
static const struct bt_uuid_128 vnd1_echo_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x12340, 0x56789abcdef5));
static uint8_t echo_enabled;
static void vnd1_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
echo_enabled = (value == BT_GATT_CCC_NOTIFY) ? 1 : 0;
}
static ssize_t write_vnd1(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
if (echo_enabled) {
shell_print(ctx_shell, "Echo attr len %u", len);
bt_gatt_notify(conn, attr, buf, len);
}
return len;
}
static ssize_t read_vnd(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const char *value = attr->user_data;
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
strlen(value));
}
static ssize_t write_vnd(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
uint8_t *value = attr->user_data;
if (offset + len > sizeof(vnd_value)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
memcpy(value + offset, buf, len);
return len;
}
#define MAX_DATA 30
static uint8_t vnd_long_value1[MAX_DATA] = { 'V', 'e', 'n', 'd', 'o', 'r' };
static uint8_t vnd_long_value2[MAX_DATA] = { 'S', 't', 'r', 'i', 'n', 'g' };
static ssize_t read_long_vnd(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint8_t *value = attr->user_data;
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
sizeof(vnd_long_value1));
}
static ssize_t write_long_vnd(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const void *buf,
uint16_t len, uint16_t offset, uint8_t flags)
{
uint8_t *value = attr->user_data;
if (flags & BT_GATT_WRITE_FLAG_PREPARE) {
return 0;
}
if (offset + len > sizeof(vnd_long_value1)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
/* Copy to buffer */
memcpy(value + offset, buf, len);
return len;
}
static struct bt_gatt_attr vnd_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&vnd_uuid),
BT_GATT_CHARACTERISTIC(&vnd_auth_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ_AUTHEN |
BT_GATT_PERM_WRITE_AUTHEN,
read_vnd, write_vnd, vnd_value),
BT_GATT_CHARACTERISTIC(&vnd_long_uuid1.uuid, BT_GATT_CHRC_READ |
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_EXT_PROP,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE |
BT_GATT_PERM_PREPARE_WRITE,
read_long_vnd, write_long_vnd,
&vnd_long_value1),
BT_GATT_CHARACTERISTIC(&vnd_long_uuid2.uuid, BT_GATT_CHRC_READ |
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_EXT_PROP,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE |
BT_GATT_PERM_PREPARE_WRITE,
read_long_vnd, write_long_vnd,
&vnd_long_value2),
};
static struct bt_gatt_service vnd_svc = BT_GATT_SERVICE(vnd_attrs);
static struct bt_gatt_attr vnd1_attrs[] = {
/* Vendor Primary Service Declaration */
BT_GATT_PRIMARY_SERVICE(&vnd1_uuid),
BT_GATT_CHARACTERISTIC(&vnd1_echo_uuid.uuid,
BT_GATT_CHRC_WRITE_WITHOUT_RESP |
BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_WRITE, NULL, write_vnd1, NULL),
BT_GATT_CCC(vnd1_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
static struct bt_gatt_service vnd1_svc = BT_GATT_SERVICE(vnd1_attrs);
static int cmd_register_test_svc(const struct shell *sh,
size_t argc, char *argv[])
{
char str[BT_UUID_STR_LEN];
int err;
bt_uuid_to_str(&vnd_uuid.uuid, str, sizeof(str));
err = bt_gatt_service_register(&vnd_svc);
if (!err) {
shell_print(sh, "Registered test vendor service %s", str);
} else {
shell_error(sh, "Failed to register test vendor service %s (%d)", str, err);
}
bt_uuid_to_str(&vnd1_uuid.uuid, str, sizeof(str));
err = bt_gatt_service_register(&vnd1_svc);
if (!err) {
shell_print(sh, "Registered test vendor service %s", str);
} else {
shell_error(sh, "Failed to register test vendor service %s (%d)", str, err);
}
return 0;
}
static int cmd_unregister_test_svc(const struct shell *sh,
size_t argc, char *argv[])
{
char str[BT_UUID_STR_LEN];
int err;
bt_uuid_to_str(&vnd_uuid.uuid, str, sizeof(str));
err = bt_gatt_service_unregister(&vnd_svc);
if (!err) {
shell_print(sh, "Unregistered test vendor service %s", str);
} else {
shell_error(sh, "Failed to unregister test vendor service %s (%d)", str, err);
}
bt_uuid_to_str(&vnd1_uuid.uuid, str, sizeof(str));
err = bt_gatt_service_unregister(&vnd1_svc);
if (!err) {
shell_print(sh, "Unregistered test vendor service %s", str);
} else {
shell_error(sh, "Failed to unregister test vendor service %s (%d)", str, err);
}
return 0;
}
static uint8_t found_attr(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
const struct bt_gatt_attr **found = user_data;
*found = attr;
return BT_GATT_ITER_STOP;
}
static const struct bt_gatt_attr *find_attr(uint16_t handle)
{
const struct bt_gatt_attr *attr = NULL;
bt_gatt_foreach_attr(handle, handle, found_attr, &attr);
return attr;
}
static int cmd_notify(const struct shell *sh, size_t argc, char *argv[])
{
const struct bt_gatt_attr *attr;
int err;
size_t data_len;
unsigned long handle;
static char data[BT_ATT_MAX_ATTRIBUTE_LEN];
const char *arg_handle = argv[1];
const char *arg_data = argv[2];
size_t arg_data_len = strlen(arg_data);
err = 0;
handle = shell_strtoul(arg_handle, 16, &err);
if (err) {
shell_error(sh, "Handle '%s': Not a valid hex number.", arg_handle);
return -EINVAL;
}
if (!IN_RANGE(handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, BT_ATT_LAST_ATTRIBUTE_HANDLE)) {
shell_error(sh, "Handle 0x%lx: Impossible value.", handle);
return -EINVAL;
}
if ((arg_data_len / 2) > BT_ATT_MAX_ATTRIBUTE_LEN) {
shell_error(sh, "Data: Size exceeds legal attribute size.");
return -EINVAL;
}
data_len = hex2bin(arg_data, arg_data_len, data, sizeof(data));
if (data_len == 0 && arg_data_len != 0) {
shell_error(sh, "Data: Bad hex.");
return -EINVAL;
}
attr = find_attr(handle);
if (!attr) {
shell_error(sh, "Handle 0x%lx: Local attribute not found.", handle);
return -EINVAL;
}
err = bt_gatt_notify(NULL, attr, data, data_len);
if (err) {
shell_error(sh, "bt_gatt_notify errno %d (%s)", -err, strerror(-err));
}
return err;
}
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
static void notify_cb(struct bt_conn *conn, void *user_data)
{
const struct shell *sh = user_data;
shell_print(sh, "Nofication sent to conn %p", conn);
}
static int cmd_notify_mult(const struct shell *sh, size_t argc, char *argv[])
{
const size_t max_cnt = CONFIG_BT_L2CAP_TX_BUF_COUNT;
struct bt_gatt_notify_params params[max_cnt];
const size_t min_cnt = 1U;
unsigned long data;
unsigned long cnt;
uint16_t cnt_u16;
int err = 0;
if (!default_conn) {
shell_error(sh, "Not connected.");
return -ENOEXEC;
}
if (!echo_enabled) {
shell_error(sh, "No clients have enabled notifications for the vnd1_echo CCC.");
return -ENOEXEC;
}
cnt = shell_strtoul(argv[1], 10, &err);
if (err != 0) {
shell_error(sh, "Invalid count parameter: %s", argv[1]);
return -err;
}
if (!IN_RANGE(cnt, min_cnt, max_cnt)) {
shell_error(sh, "Invalid count value %lu (range %zu to %zu)",
cnt, min_cnt, max_cnt);
return -ENOEXEC;
}
cnt_u16 = (uint16_t)cnt;
if (argc > 2) {
data = shell_strtoul(argv[2], 16, &err);
if (err != 0) {
shell_error(sh, "Invalid data parameter: %s", argv[1]);
return -err;
}
}
(void)memset(params, 0, sizeof(params));
for (uint16_t i = 0U; i < cnt_u16; i++) {
params[i].uuid = 0;
params[i].attr = vnd1_attrs;
params[i].data = &data;
params[i].len = sizeof(data);
params[i].func = notify_cb;
params[i].user_data = (void *)sh;
}
err = bt_gatt_notify_multiple(default_conn, cnt_u16, params);
if (err != 0) {
shell_error(sh, "bt_gatt_notify_multiple failed: %d", err);
} else {
shell_print(sh, "Send %u notifications", cnt_u16);
}
return err;
}
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
static const struct bt_uuid_128 met_svc_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcde01));
static const struct bt_uuid_128 met_char_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcde02));
static uint8_t met_char_value[BT_ATT_MAX_ATTRIBUTE_LEN] = {
'M', 'e', 't', 'r', 'i', 'c', 's' };
static ssize_t read_met(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const char *value = attr->user_data;
uint16_t value_len;
value_len = MIN(strlen(value), BT_ATT_MAX_ATTRIBUTE_LEN);
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
value_len);
}
static ssize_t write_met(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
uint8_t *value = attr->user_data;
if (offset + len > sizeof(met_char_value)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
memcpy(value + offset, buf, len);
update_write_stats(len);
return len;
}
static struct bt_gatt_attr met_attrs[] = {
BT_GATT_PRIMARY_SERVICE(&met_svc_uuid),
BT_GATT_CHARACTERISTIC(&met_char_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_met, write_met, met_char_value),
};
static struct bt_gatt_service met_svc = BT_GATT_SERVICE(met_attrs);
static int cmd_metrics(const struct shell *sh, size_t argc, char *argv[])
{
int err = 0;
if (argc < 2) {
print_write_stats();
return 0;
}
if (!strcmp(argv[1], "on")) {
shell_print(sh, "Registering GATT metrics test Service.");
err = bt_gatt_service_register(&met_svc);
} else if (!strcmp(argv[1], "off")) {
shell_print(sh, "Unregistering GATT metrics test Service.");
err = bt_gatt_service_unregister(&met_svc);
} else {
shell_error(sh, "Incorrect value: %s", argv[1]);
shell_help(sh);
return -ENOEXEC;
}
if (!err) {
shell_print(sh, "GATT write cmd metrics %s.", argv[1]);
}
return err;
}
#endif /* CONFIG_BT_GATT_DYNAMIC_DB */
static uint8_t get_cb(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct shell *sh = user_data;
uint8_t buf[256];
ssize_t ret;
char str[BT_UUID_STR_LEN];
bt_uuid_to_str(attr->uuid, str, sizeof(str));
shell_print(sh, "attr %p uuid %s perm 0x%02x", attr, str,
attr->perm);
if (!attr->read) {
return BT_GATT_ITER_CONTINUE;
}
ret = attr->read(NULL, attr, (void *)buf, sizeof(buf), 0);
if (ret < 0) {
shell_print(sh, "Failed to read: %zd", ret);
return BT_GATT_ITER_STOP;
}
shell_hexdump(sh, buf, ret);
return BT_GATT_ITER_CONTINUE;
}
static int cmd_get(const struct shell *sh, size_t argc, char *argv[])
{
uint16_t start, end;
start = strtoul(argv[1], NULL, 16);
end = start;
if (argc > 2) {
end = strtoul(argv[2], NULL, 16);
}
bt_gatt_foreach_attr(start, end, get_cb, (void *)sh);
return 0;
}
struct set_data {
const struct shell *sh;
size_t argc;
char **argv;
int err;
};
static uint8_t set_cb(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct set_data *data = user_data;
uint8_t buf[256];
size_t i;
ssize_t ret;
if (!attr->write) {
shell_error(data->sh, "Write not supported");
data->err = -ENOENT;
return BT_GATT_ITER_CONTINUE;
}
for (i = 0; i < data->argc; i++) {
buf[i] = strtoul(data->argv[i], NULL, 16);
}
ret = attr->write(NULL, attr, (void *)buf, i, 0, 0);
if (ret < 0) {
data->err = ret;
shell_error(data->sh, "Failed to write: %zd", ret);
return BT_GATT_ITER_STOP;
}
return BT_GATT_ITER_CONTINUE;
}
static int cmd_set(const struct shell *sh, size_t argc, char *argv[])
{
uint16_t handle;
struct set_data data;
handle = strtoul(argv[1], NULL, 16);
data.sh = sh;
data.argc = argc - 2;
data.argv = argv + 2;
data.err = 0;
bt_gatt_foreach_attr(handle, handle, set_cb, &data);
if (data.err < 0) {
return -ENOEXEC;
}
bt_gatt_foreach_attr(handle, handle, get_cb, (void *)sh);
return 0;
}
int cmd_att_mtu(const struct shell *sh, size_t argc, char *argv[])
{
uint16_t mtu;
if (default_conn) {
mtu = bt_gatt_get_mtu(default_conn);
shell_print(sh, "MTU size: %u", mtu);
} else {
shell_print(sh, "No default connection");
}
return 0;
}
#define HELP_NONE "[none]"
#define HELP_ADDR_LE "<address: XX:XX:XX:XX:XX:XX> <type: (public|random)>"
SHELL_STATIC_SUBCMD_SET_CREATE(gatt_cmds,
#if defined(CONFIG_BT_GATT_CLIENT)
SHELL_CMD_ARG(discover, NULL,
"[UUID] [start handle] [end handle]", cmd_discover, 1, 3),
SHELL_CMD_ARG(discover-characteristic, NULL,
"[UUID] [start handle] [end handle]", cmd_discover, 1, 3),
SHELL_CMD_ARG(discover-descriptor, NULL,
"[UUID] [start handle] [end handle]", cmd_discover, 1, 3),
SHELL_CMD_ARG(discover-include, NULL,
"[UUID] [start handle] [end handle]", cmd_discover, 1, 3),
SHELL_CMD_ARG(discover-primary, NULL,
"[UUID] [start handle] [end handle]", cmd_discover, 1, 3),
SHELL_CMD_ARG(discover-secondary, NULL,
"[UUID] [start handle] [end handle]", cmd_discover, 1, 3),
SHELL_CMD_ARG(exchange-mtu, NULL, HELP_NONE, cmd_exchange_mtu, 1, 0),
SHELL_CMD_ARG(read, NULL, "<handle> [offset]", cmd_read, 2, 1),
SHELL_CMD_ARG(read-uuid, NULL, "<UUID> [start handle] [end handle]",
cmd_read_uuid, 2, 2),
SHELL_CMD_ARG(read-multiple, NULL, "<handle 1> <handle 2> ...",
cmd_mread, 2, -1),
SHELL_CMD_ARG(signed-write, NULL, "<handle> <data> [length] [repeat]",
cmd_write_without_rsp, 3, 2),
SHELL_CMD_ARG(subscribe, NULL, "<CCC handle> <value handle> [ind]",
cmd_subscribe, 3, 1),
SHELL_CMD_ARG(resubscribe, NULL, HELP_ADDR_LE" <CCC handle> "
"<value handle> [ind]", cmd_resubscribe, 5, 1),
SHELL_CMD_ARG(write, NULL, "<handle> <offset> <data>", cmd_write, 4, 0),
SHELL_CMD_ARG(write-without-response, NULL,
"<handle> <data> [length] [repeat]",
cmd_write_without_rsp, 3, 2),
SHELL_CMD_ARG(write-without-response-cb, NULL,
"<handle> <data> [length] [repeat]",
cmd_write_without_rsp, 3, 2),
SHELL_CMD_ARG(unsubscribe, NULL, HELP_NONE, cmd_unsubscribe, 1, 0),
#endif /* CONFIG_BT_GATT_CLIENT */
SHELL_CMD_ARG(get, NULL, "<start handle> [end handle]", cmd_get, 2, 1),
SHELL_CMD_ARG(set, NULL, "<handle> [data...]", cmd_set, 2, 255),
SHELL_CMD_ARG(show-db, NULL, "[uuid] [num_matches]", cmd_show_db, 1, 2),
SHELL_CMD_ARG(att_mtu, NULL, "Output ATT MTU size", cmd_att_mtu, 1, 0),
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
SHELL_CMD_ARG(metrics, NULL, "[value: on, off]", cmd_metrics, 1, 1),
SHELL_CMD_ARG(register, NULL,
"register pre-predefined test service",
cmd_register_test_svc, 1, 0),
SHELL_CMD_ARG(unregister, NULL,
"unregister pre-predefined test service",
cmd_unregister_test_svc, 1, 0),
SHELL_CMD_ARG(notify, NULL, "<handle> <data>", cmd_notify, 3, 0),
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
SHELL_CMD_ARG(notify-mult, NULL, "count [data]", cmd_notify_mult, 2, 1),
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
#endif /* CONFIG_BT_GATT_DYNAMIC_DB */
SHELL_SUBCMD_SET_END
);
static int cmd_gatt(const struct shell *sh, size_t argc, char **argv)
{
if (argc == 1) {
shell_help(sh);
/* shell returns 1 when help is printed */
return 1;
}
shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
return -EINVAL;
}
SHELL_CMD_ARG_REGISTER(gatt, &gatt_cmds, "Bluetooth GATT shell commands",
cmd_gatt, 1, 1);