extmod/modbluetooth: Add support for changing the GAP device name.

This commit allows the user to set/get the GAP device name used by service
0x1800, characteristic 0x2a00.  The usage is:

    BLE.config(gap_name="myname")
    print(BLE.config("gap_name"))

As part of this change the compile-time setting
MICROPY_PY_BLUETOOTH_DEFAULT_NAME is renamed to
MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME to emphasise its link to GAP and this
new "gap_name" config value.  And the default value of this for the NimBLE
bindings is changed from "PYBD" to "MPY NIMBLE" to be more generic.
This commit is contained in:
Damien George 2020-05-08 13:54:10 +10:00
parent f385b7bfa8
commit 3b6c9119eb
8 changed files with 219 additions and 5 deletions

View File

@ -51,6 +51,10 @@ Configuration
Note: on some ports, accessing this value requires that the interface is
active (so that the MAC address can be queried from the controller).
- ``'gap_name'``: Get/set the GAP device name used by service 0x1800,
characteristic 0x2a00. This can be set at any time and changed multiple
times.
- ``'rxbuf'``: Get/set the size in bytes of the internal buffer used to store
incoming events. This buffer is global to the entire BLE driver and so
handles incoming data for all events, including all characteristics.

View File

@ -37,10 +37,20 @@
#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__)
#ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME
#define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY BTSTACK"
#endif
// How long to wait for a controller to init/deinit.
// Some controllers can take up to 5-6 seconds in normal operation.
STATIC const uint32_t BTSTACK_INIT_DEINIT_TIMEOUT_MS = 15000;
// We need to know the attribute handle for the GAP device name (see GAP_DEVICE_NAME_UUID)
// so it can be put into the gatts_db before registering the services, and accessed
// efficiently when requesting an attribute in att_read_callback. Because this is the
// first characteristic of the first service, it always has a handle value of 3.
STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3;
volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF;
STATIC btstack_packet_callback_registration_t hci_event_callback_registration;
@ -250,6 +260,12 @@ int mp_bluetooth_init(void) {
MP_STATE_PORT(bluetooth_btstack_root_pointers) = m_new0(mp_bluetooth_btstack_root_pointers_t, 1);
mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db);
// Set the default GAP device name.
const char *gap_name = MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME;
size_t gap_len = strlen(gap_name);
mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, gap_len);
mp_bluetooth_gap_set_device_name((const uint8_t *)gap_name, gap_len);
mp_bluetooth_btstack_port_init();
mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_STARTING;
@ -344,6 +360,19 @@ void mp_bluetooth_get_device_addr(uint8_t *addr) {
mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr);
}
size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) {
uint8_t *value = NULL;
size_t value_len = 0;
mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, &value, &value_len);
*buf = value;
return value_len;
}
int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) {
mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, buf, len);
return 0;
}
int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) {
DEBUG_EVENT_printf("mp_bluetooth_gap_advertise_start\n");
uint16_t adv_int_min = interval_us / 625;
@ -396,7 +425,9 @@ int mp_bluetooth_gatts_register_service_begin(bool append) {
att_db_util_init();
att_db_util_add_service_uuid16(GAP_SERVICE_UUID);
att_db_util_add_characteristic_uuid16(GAP_DEVICE_NAME_UUID, ATT_PROPERTY_READ, ATT_SECURITY_NONE, ATT_SECURITY_NONE, (uint8_t *)"MPY BTSTACK", 11);
uint16_t handle = att_db_util_add_characteristic_uuid16(GAP_DEVICE_NAME_UUID, ATT_PROPERTY_READ | ATT_PROPERTY_DYNAMIC, ATT_SECURITY_NONE, ATT_SECURITY_NONE, NULL, 0);
assert(handle == BTSTACK_GAP_DEVICE_NAME_HANDLE);
(void)handle;
att_db_util_add_service_uuid16(0x1801);
att_db_util_add_characteristic_uuid16(0x2a05, ATT_PROPERTY_READ, ATT_SECURITY_NONE, ATT_SECURITY_NONE, NULL, 0);

View File

@ -295,6 +295,11 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map
}
switch (mp_obj_str_get_qstr(args[1])) {
case MP_QSTR_gap_name: {
const uint8_t *buf;
size_t len = mp_bluetooth_gap_get_device_name(&buf);
return mp_obj_new_bytes(buf, len);
}
case MP_QSTR_mac: {
uint8_t addr[6];
mp_bluetooth_get_device_addr(addr);
@ -315,6 +320,13 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map
if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) {
mp_map_elem_t *e = &kwargs->table[i];
switch (mp_obj_str_get_qstr(e->key)) {
case MP_QSTR_gap_name: {
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(e->value, &bufinfo, MP_BUFFER_READ);
int ret = mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len);
bluetooth_handle_errno(ret);
break;
}
case MP_QSTR_rxbuf: {
// Determine new buffer sizes
mp_int_t ringbuf_alloc = mp_obj_get_int(e->value);

View File

@ -171,6 +171,10 @@ bool mp_bluetooth_is_active(void);
// Gets the MAC addr of this device in big-endian format.
void mp_bluetooth_get_device_addr(uint8_t *addr);
// Get or set the GAP device name that will be used by service 0x1800, characteristic 0x2a00.
size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf);
int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len);
// Start advertisement. Will re-start advertisement when already enabled.
// Returns errno on failure.
int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len);

View File

@ -40,8 +40,8 @@
#include "nimble/nimble_port.h"
#include "services/gap/ble_svc_gap.h"
#ifndef MICROPY_PY_BLUETOOTH_DEFAULT_NAME
#define MICROPY_PY_BLUETOOTH_DEFAULT_NAME "PYBD"
#ifndef MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME
#define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME "MPY NIMBLE"
#endif
#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__)
@ -191,7 +191,7 @@ STATIC void sync_cb(void) {
assert(rc == 0);
}
ble_svc_gap_device_name_set(MICROPY_PY_BLUETOOTH_DEFAULT_NAME);
ble_svc_gap_device_name_set(MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME);
mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE;
}
@ -352,6 +352,22 @@ void mp_bluetooth_get_device_addr(uint8_t *addr) {
#endif
}
size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf) {
const char *name = ble_svc_gap_device_name();
*buf = (const uint8_t *)name;
return strlen(name);
}
int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len) {
char tmp_buf[MYNEWT_VAL(BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH) + 1];
if (len + 1 > sizeof(tmp_buf)) {
return MP_EINVAL;
}
memcpy(tmp_buf, buf, len);
tmp_buf[len] = '\0';
return ble_hs_err_to_errno(ble_svc_gap_device_name_set(tmp_buf));
}
int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) {
if (!mp_bluetooth_is_active()) {
return ERRNO_BLUETOOTH_NOT_ACTIVE;

View File

@ -161,7 +161,7 @@
#define MICROPY_PY_FRAMEBUF (1)
#define MICROPY_PY_USOCKET_EVENTS (MICROPY_PY_WEBREPL)
#define MICROPY_PY_BLUETOOTH_RANDOM_ADDR (1)
#define MICROPY_PY_BLUETOOTH_DEFAULT_NAME ("ESP32")
#define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME ("ESP32")
// fatfs configuration
#define MICROPY_FATFS_ENABLE_LFN (1)

View File

@ -0,0 +1,123 @@
# Test BLE GAP device name get/set
from micropython import const
import time, machine, bluetooth
TIMEOUT_MS = 5000
_IRQ_CENTRAL_CONNECT = const(1 << 0)
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
_IRQ_PERIPHERAL_CONNECT = const(1 << 6)
_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9)
_IRQ_GATTC_READ_RESULT = const(1 << 11)
GAP_DEVICE_NAME_UUID = bluetooth.UUID(0x2A00)
last_event = None
last_data = None
value_handle = 0
def irq(event, data):
global last_event, last_data, value_handle
last_event = event
last_data = data
if event == _IRQ_CENTRAL_CONNECT:
print("_IRQ_CENTRAL_CONNECT")
elif event == _IRQ_CENTRAL_DISCONNECT:
print("_IRQ_CENTRAL_DISCONNECT")
elif event == _IRQ_PERIPHERAL_CONNECT:
print("_IRQ_PERIPHERAL_CONNECT")
elif event == _IRQ_PERIPHERAL_DISCONNECT:
print("_IRQ_PERIPHERAL_DISCONNECT")
elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT:
if data[-1] == GAP_DEVICE_NAME_UUID:
print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1])
value_handle = data[2]
elif event == _IRQ_GATTC_READ_RESULT:
print("_IRQ_GATTC_READ_RESULT", data[-1])
def wait_for_event(event, timeout_ms):
t0 = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms:
if isinstance(event, int):
if last_event == event:
break
elif event():
break
machine.idle()
# Acting in peripheral role.
def instance0():
multitest.globals(BDADDR=ble.config("mac"))
# Test setting and getting the GAP device name before registering services.
ble.config(gap_name="GAP_NAME")
print(ble.config("gap_name"))
# Create an empty service and start advertising.
ble.gatts_register_services([])
print("gap_advertise")
multitest.next()
try:
# Do multiple iterations to test changing the name.
for iteration in range(2):
# Set the GAP device name and start advertising.
ble.config(gap_name="GAP_NAME{}".format(iteration))
print(ble.config("gap_name"))
ble.gap_advertise(20_000)
# Wait for central to connect, then wait for it to disconnect.
wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS)
wait_for_event(_IRQ_CENTRAL_DISCONNECT, 4 * TIMEOUT_MS)
if last_event != _IRQ_CENTRAL_DISCONNECT:
return
finally:
ble.active(0)
# Acting in central role.
def instance1():
multitest.next()
try:
for iteration in range(2):
# Wait for peripheral to start advertising.
time.sleep_ms(500)
# Connect to peripheral.
print("gap_connect")
ble.gap_connect(0, BDADDR)
wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS)
if last_event != _IRQ_PERIPHERAL_CONNECT:
return
conn_handle, _, _ = last_data
if iteration == 0:
print("gattc_discover_characteristics")
ble.gattc_discover_characteristics(conn_handle, 1, 65535)
wait_for_event(lambda: value_handle, TIMEOUT_MS)
# Wait for all characteristic results to come in (there should be an event for it).
time.sleep_ms(500)
# Read the peripheral's GAP device name.
print("gattc_read")
ble.gattc_read(conn_handle, value_handle)
wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS)
# Disconnect from peripheral.
print("gap_disconnect:", ble.gap_disconnect(conn_handle))
wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS)
if last_event != _IRQ_PERIPHERAL_DISCONNECT:
return
finally:
ble.active(0)
ble = bluetooth.BLE()
ble.active(1)
ble.irq(irq)

View File

@ -0,0 +1,24 @@
--- instance0 ---
b'GAP_NAME'
gap_advertise
b'GAP_NAME0'
_IRQ_CENTRAL_CONNECT
_IRQ_CENTRAL_DISCONNECT
b'GAP_NAME1'
_IRQ_CENTRAL_CONNECT
_IRQ_CENTRAL_DISCONNECT
--- instance1 ---
gap_connect
_IRQ_PERIPHERAL_CONNECT
gattc_discover_characteristics
_IRQ_GATTC_CHARACTERISTIC_RESULT UUID16(0x2a00)
gattc_read
_IRQ_GATTC_READ_RESULT b'GAP_NAME0'
gap_disconnect: True
_IRQ_PERIPHERAL_DISCONNECT
gap_connect
_IRQ_PERIPHERAL_CONNECT
gattc_read
_IRQ_GATTC_READ_RESULT b'GAP_NAME1'
gap_disconnect: True
_IRQ_PERIPHERAL_DISCONNECT