Skip to content

extmod/modbluetooth: Misc fixes and improvements #5211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions docs/library/bluetooth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

This module provides an interface to a Bluetooth controller on a board.
Currently this supports Bluetooth Low Energy (BLE) in Central, Peripheral,
Broadcaster, and Observer roles.
Broadcaster, and Observer roles, and a device may operate in multiple
roles concurrently.

This API is intended to match the low-level Bluetooth protocol and provide
building-blocks for higher-level abstractions such as specific device types.
Expand Down Expand Up @@ -66,6 +67,7 @@ Event Handling
elif event == _IRQ_GATTS_READ_REQUEST:
# A central has issued a read. Note: this is a hard IRQ.
# Return None to deny the read.
# Note: This event is not supported on ESP32.
conn_handle, attr_handle = data
elif event == _IRQ_SCAN_RESULT:
# A single scan result.
Expand Down Expand Up @@ -138,6 +140,11 @@ Broadcaster Role (Advertiser)
protocol (e.g. ``bytes``, ``bytearray``, ``str``). *adv_data* is included
in all broadcasts, and *resp_data* is send in reply to an active scan.

Note: if *adv_data* (or *resp_data*) is ``None``, then the data passed
to the previous call to ``gap_advertise`` will be re-used. This allows a
broadcaster to resume advertising with just ``gap_advertise(interval_us)``.
To clear the advertising payload, an empty ``bytes``, i.e. `b''`.


Observer Role (Scanner)
-----------------------
Expand Down Expand Up @@ -169,9 +176,17 @@ A BLE peripheral has a set of registered services. Each service may contain
characteristics, which each have a value. Characteristics can also contain
descriptors, which themselves have values.

These values are stored locally and can be read from or written to by a remote
central device. Additionally, a peripheral can "notify" its value to a connected
central via its connection handle.
These values are stored locally, and are accessed by their "value handle" which
is generated during service registration. They can also be read from or written
to by a remote central device. Additionally, a peripheral can "notify" a
characteristic to a connected central via a connection handle.

Characteristics and descriptors have a default maximum size of 20 bytes.
Anything written to them by a central will be truncated to this length. However,
any local write will increase the maximum size, so if you want to allow larger
writes from a central to a given characteristic, use
:meth:`gatts_write<BLE.gatts_write>` after registration. e.g.
``gatts_write(char_handle, bytes(100))``.

.. method:: BLE.gatts_register_services(services_definition)

Expand Down Expand Up @@ -247,6 +262,9 @@ Central Role (GATT Client)

On success, the ``_IRQ_PERIPHERAL_DISCONNECT`` event will be raised.

Returns ``False`` if the connection handle wasn't connected, and ``True``
otherwise.

.. method:: BLE.gattc_discover_services(conn_handle)

Query a connected peripheral for its services.
Expand Down
44 changes: 27 additions & 17 deletions extmod/modbluetooth.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "py/binary.h"
#include "py/misc.h"
#include "py/mperrno.h"
#include "py/obj.h"
#include "py/objstr.h"
#include "py/objarray.h"
Expand Down Expand Up @@ -59,6 +60,7 @@ typedef struct {
mp_obj_base_t base;
mp_obj_t irq_handler;
mp_obj_t irq_data_tuple;
uint8_t irq_addr_bytes[6];
uint8_t irq_data_bytes[MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN];
mp_obj_t irq_data_uuid;
uint16_t irq_trigger;
Expand Down Expand Up @@ -232,7 +234,7 @@ STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args,
mp_obj_bluetooth_ble_t *o = m_new_obj(mp_obj_bluetooth_ble_t);
o->base.type = &bluetooth_ble_type;
o->irq_handler = mp_const_none;
// Pre-allocated the event data tuple to prevent needing to allocate in the IRQ handler.
// Pre-allocate the event data tuple to prevent needing to allocate in the IRQ handler.
o->irq_data_tuple = mp_obj_new_tuple(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN, NULL);
mp_obj_bluetooth_uuid_t *uuid = m_new_obj(mp_obj_bluetooth_uuid_t);
uuid->base.type = &bluetooth_uuid_type;
Expand Down Expand Up @@ -287,9 +289,9 @@ STATIC mp_obj_t bluetooth_ble_irq(size_t n_args, const mp_obj_t *pos_args, mp_ma

// Update the callback.
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t* bt = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bt->irq_handler = callback;
bt->irq_trigger = args[ARG_trigger].u_int;
mp_obj_bluetooth_ble_t* o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
o->irq_handler = callback;
o->irq_trigger = args[ARG_trigger].u_int;
MICROPY_PY_BLUETOOTH_EXIT

return mp_const_none;
Expand All @@ -303,7 +305,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_irq_obj, 1, bluetooth_ble_irq);
STATIC mp_obj_t bluetooth_ble_gap_advertise(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_interval_us, ARG_adv_data, ARG_resp_data, ARG_connectable };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_interval_us, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(100)} },
{ MP_QSTR_interval_us, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(500000)} },
{ MP_QSTR_adv_data, MP_ARG_OBJ, {.u_obj = mp_const_none } },
{ MP_QSTR_resp_data, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
{ MP_QSTR_connectable, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_true } },
Expand Down Expand Up @@ -526,7 +528,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_scan_obj, 1, 4, blu
STATIC mp_obj_t bluetooth_ble_gap_disconnect(mp_obj_t self_in, mp_obj_t conn_handle_in) {
uint16_t conn_handle = mp_obj_get_int(conn_handle_in);
int err = mp_bluetooth_gap_disconnect(conn_handle);
return bluetooth_handle_errno(err);
if (err == 0) {
return mp_const_true;
} else if (err == MP_ENOTCONN) {
return mp_const_false;
} else {
return bluetooth_handle_errno(err);
}
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_disconnect_obj, bluetooth_ble_gap_disconnect);

Expand Down Expand Up @@ -693,7 +701,7 @@ STATIC void ringbuf_extract(ringbuf_t* ringbuf, mp_obj_tuple_t *data_tuple, size
if (bytes_addr) {
bytes_addr->len = 6;
for (int i = 0; i < bytes_addr->len; ++i) {
// cast away const, this is actually bt->irq_data_bytes.
// cast away const, this is actually bt->irq_addr_bytes.
((uint8_t*)bytes_addr->data)[i] = ringbuf_get(ringbuf);
}
data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_addr);
Expand All @@ -715,7 +723,7 @@ STATIC void ringbuf_extract(ringbuf_t* ringbuf, mp_obj_tuple_t *data_tuple, size
if (bytes_data) {
bytes_data->len = ringbuf_get(ringbuf);
for (int i = 0; i < bytes_data->len; ++i) {
// cast away const, this is actually bt->irq_data_bytes + 6.
// cast away const, this is actually bt->irq_data_bytes.
((uint8_t*)bytes_data->data)[i] = ringbuf_get(ringbuf);
}
data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_data);
Expand Down Expand Up @@ -746,8 +754,8 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) {

// Some events need to pass bytes objects to their handler, using the
// pre-allocated bytes array.
mp_obj_str_t irq_data_bytes_addr = {{&mp_type_bytes}, 0, 6, o->irq_data_bytes};
mp_obj_str_t irq_data_bytes_data = {{&mp_type_bytes}, 0, 0, o->irq_data_bytes + 6};
mp_obj_str_t irq_data_bytes_addr = {{&mp_type_bytes}, 0, 6, o->irq_addr_bytes};
mp_obj_str_t irq_data_bytes_data = {{&mp_type_bytes}, 0, 0, o->irq_data_bytes};

if (event == MP_BLUETOOTH_IRQ_CENTRAL_CONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT || event == MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT) {
// conn_handle, addr_type, addr
Expand Down Expand Up @@ -799,7 +807,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_inv
STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event, bool *sched) {
*sched = false;

if (ringbuf_free(&o->ringbuf) >= len + 2 && (o->irq_trigger & event) && o->irq_handler != mp_const_none) {
if (o && ringbuf_free(&o->ringbuf) >= len + 2 && (o->irq_trigger & event) && o->irq_handler != mp_const_none) {
*sched = ringbuf_avail(&o->ringbuf) == 0;
ringbuf_put16(&o->ringbuf, event);
return true;
Expand All @@ -818,7 +826,7 @@ void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_ha
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 9, event, &sched)) {
if (enqueue_irq(o, 2 + 1 + 6, event, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put(&o->ringbuf, addr_type);
for (int i = 0; i < 6; ++i) {
Expand All @@ -829,11 +837,11 @@ void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_ha
schedule_ringbuf(sched);
}

void mp_bluetooth_gatts_on_write(uint16_t value_handle, uint16_t conn_handle) {
void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) {
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 4, MP_BLUETOOTH_IRQ_GATTS_WRITE, &sched)) {
if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_GATTS_WRITE, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put16(&o->ringbuf, value_handle);
}
Expand All @@ -856,15 +864,15 @@ void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, boo
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
if (enqueue_irq(o, 1 + 6 + 1 + 1 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT, &sched)) {
data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len);
if (enqueue_irq(o, 1 + 6 + 1 + 1 + 1 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT, &sched)) {
ringbuf_put(&o->ringbuf, addr_type);
for (int i = 0; i < 6; ++i) {
ringbuf_put(&o->ringbuf, addr[i]);
}
ringbuf_put(&o->ringbuf, connectable ? 1 : 0);
// Note conversion of int8_t rssi to uint8_t. Must un-convert on the way out.
ringbuf_put(&o->ringbuf, (uint8_t)rssi);
data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len);
ringbuf_put(&o->ringbuf, data_len);
for (int i = 0; i < data_len; ++i) {
ringbuf_put(&o->ringbuf, data[i]);
Expand Down Expand Up @@ -920,10 +928,10 @@ void mp_bluetooth_gattc_on_data_available(uint16_t event, uint16_t conn_handle,
MICROPY_PY_BLUETOOTH_ENTER
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
bool sched;
data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len);
if (enqueue_irq(o, 2 + 2 + 1 + data_len, event, &sched)) {
ringbuf_put16(&o->ringbuf, conn_handle);
ringbuf_put16(&o->ringbuf, value_handle);
data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len);
ringbuf_put(&o->ringbuf, data_len);
for (int i = 0; i < data_len; ++i) {
ringbuf_put(&o->ringbuf, data[i]);
Expand All @@ -949,6 +957,8 @@ void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_han
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE

#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
// This can only be enabled when the thread invoking this is a MicroPython thread.
// On ESP32, for example, this is not the case.
bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) {
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
if ((o->irq_trigger & MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST) && o->irq_handler != mp_const_none) {
Expand Down
7 changes: 4 additions & 3 deletions extmod/modbluetooth_nimble.c
Original file line number Diff line number Diff line change
Expand Up @@ -346,14 +346,14 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons

mp_bluetooth_gap_advertise_stop();

if ((adv_data != NULL) && (adv_data_len > 0)) {
if (adv_data) {
ret = ble_gap_adv_set_data(adv_data, adv_data_len);
if (ret != 0) {
return ble_hs_err_to_errno(ret);
}
}

if ((sr_data != NULL) && (sr_data_len > 0)) {
if (sr_data) {
ret = ble_gap_adv_rsp_set_data(sr_data, sr_data_len);
if (ret != 0) {
return ble_hs_err_to_errno(ret);
Expand Down Expand Up @@ -384,6 +384,7 @@ int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, cons
if (ret == 0) {
return 0;
}
DEBUG_EVENT_printf("ble_gap_adv_start: %d\n", ret);

return ble_hs_err_to_errno(ret);
}
Expand Down Expand Up @@ -583,7 +584,7 @@ int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) {
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE

STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg) {
DEBUG_EVENT_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->disc ? event->disc.event_type : -1);
DEBUG_EVENT_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->type == BLE_GAP_EVENT_DISC ? event->disc.event_type : -1);

if (event->type == BLE_GAP_EVENT_DISC_COMPLETE) {
mp_bluetooth_gap_on_scan_complete();
Expand Down
2 changes: 1 addition & 1 deletion extmod/nimble/syscfg/syscfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ int nimble_sprintf(char *str, const char *fmt, ...);
/*** nimble */
#define MYNEWT_VAL_BLE_EXT_ADV (0)
#define MYNEWT_VAL_BLE_EXT_ADV_MAX_SIZE (31)
#define MYNEWT_VAL_BLE_MAX_CONNECTIONS (1)
#define MYNEWT_VAL_BLE_MAX_CONNECTIONS (4)
#define MYNEWT_VAL_BLE_MULTI_ADV_INSTANCES (0)
#define MYNEWT_VAL_BLE_ROLE_BROADCASTER (1)
#define MYNEWT_VAL_BLE_ROLE_CENTRAL (1)
Expand Down
8 changes: 8 additions & 0 deletions ports/esp32/boards/sdkconfig.ble
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,12 @@ CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=
CONFIG_BTDM_CTRL_MODE_BTDM=

CONFIG_BT_NIMBLE_ENABLED=y

CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4

# Pin to the same core as MP.
CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=n
CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y
CONFIG_BT_NIMBLE_PINNED_TO_CORE=1
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy