extmod/btstack: Schedule notify/indicate/write ops for bg completion.

The goal of this commit is to allow using ble.gatts_notify() at any time,
even if the stack is not ready to send the notification right now.  It also
addresses the same issue for ble.gatts_indicate() and ble.gattc_write()
(without response).  In addition this commit fixes the case where the
buffer passed to write-with-response wasn't copied, meaning it could be
modified by the caller, affecting the in-progress write.

The changes are:

- gatts_notify/indicate will now run in the background if the ACL buffer is
  currently full, meaning that notify/indicate can be called at any time.

- gattc_write(mode=0) (no response) will now allow for one outstanding
  write.

- gattc_write(mode=1) (with response) will now copy the buffer so that it
  can't be modified by the caller while the write is in progress.

All four paths also now track the buffer while the operation is in
progress, which prevents the GC free'ing the buffer while it's still
needed.
This commit is contained in:
Jim Mussared 2020-06-16 21:53:22 +10:00 committed by Damien George
parent 07aec4681f
commit e152d0c197
7 changed files with 355 additions and 62 deletions

View File

@ -53,8 +53,6 @@ STATIC const uint16_t BTSTACK_GAP_DEVICE_NAME_HANDLE = 3;
volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; volatile int mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF;
STATIC btstack_packet_callback_registration_t hci_event_callback_registration;
STATIC int btstack_error_to_errno(int err) { STATIC int btstack_error_to_errno(int err) {
DEBUG_EVENT_printf(" --> btstack error: %d\n", err); DEBUG_EVENT_printf(" --> btstack error: %d\n", err);
if (err == ERROR_CODE_SUCCESS) { if (err == ERROR_CODE_SUCCESS) {
@ -85,6 +83,159 @@ STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uu
return result; return result;
} }
// Notes on supporting background ops (e.g. an attempt to gatts_notify while
// an existing notification is in progress):
// GATTS Notify/Indicate (att_server_notify/indicate)
// * When available, copies buffer immediately.
// * Otherwise fails with BTSTACK_ACL_BUFFERS_FULL
// * Use att_server_request_to_send_notification/indication to get callback
// * Takes btstack_context_callback_registration_t (and takes ownership) and conn_handle.
// * Callback is invoked with just the context member of the btstack_context_callback_registration_t
// GATTC Write without response (gatt_client_write_value_of_characteristic_without_response)
// * When available, copies buffer immediately.
// * Otherwise, fails with GATT_CLIENT_BUSY.
// * Use gatt_client_request_can_write_without_response_event to get callback
// * Takes btstack_packet_handler_t (function pointer) and conn_handle
// * Callback is invoked, use gatt_event_can_write_without_response_get_handle to get the conn_handle (no other context)
// * There can only be one pending gatt_client_request_can_write_without_response_event (otherwise we fail with EALREADY).
// GATTC Write with response (gatt_client_write_value_of_characteristic)
// * When peripheral is available, takes ownership of buffer.
// * Otherwise, fails with GATT_CLIENT_IN_WRONG_STATE (we fail the operation).
// * Raises GATT_EVENT_QUERY_COMPLETE to the supplied packet handler.
// For notify/indicate/write-without-response that proceed immediately, nothing extra required.
// For all other cases, buffer needs to be copied and protected from GC.
// For notify/indicate:
// * btstack_context_callback_registration_t:
// * needs to be malloc'ed
// * needs to be protected from GC
// * context arg needs to point back to the callback registration so it can be freed and un-protected
// For write-without-response
// * only the conn_handle is available in the callback
// * so we need a queue of conn_handle->(value_handle, copied buffer)
// Pending operation types.
enum {
// Queued for sending when possible.
MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY, // Waiting for context callback
MP_BLUETOOTH_BTSTACK_PENDING_INDICATE, // Waiting for context callback
MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, // Waiting for conn handle
// Hold buffer pointer until complete.
MP_BLUETOOTH_BTSTACK_PENDING_WRITE, // Waiting for write done event
};
// Pending operation:
// - Holds a GC reference to the copied outgoing buffer.
// - Provides enough information for the callback handler to execute the desired operation.
struct _mp_btstack_pending_op_t {
btstack_linked_item_t *next; // Must be first field to match btstack_linked_item.
// See enum above.
uint16_t op_type;
// For all op types.
uint16_t conn_handle;
uint16_t value_handle;
// For notify/indicate only.
// context_registration.context will point back to this struct.
btstack_context_callback_registration_t context_registration;
// For notify/indicate/write-without-response, this is the actual buffer to send.
// For write-with-response, just holding onto the buffer for GC ref.
size_t len;
uint8_t buf[];
};
// Must hold MICROPY_PY_BLUETOOTH_ENTER.
STATIC void btstack_remove_pending_operation(mp_btstack_pending_op_t *pending_op, bool del) {
bool removed = btstack_linked_list_remove(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops, (btstack_linked_item_t *)pending_op);
assert(removed);
(void)removed;
if (del) {
m_del_var(mp_btstack_pending_op_t, uint8_t, pending_op->len, pending_op);
}
}
// Called in response to a gatts_notify/indicate being unable to complete, which then calls
// att_server_request_to_send_notification.
// We now have an opportunity to re-try the operation with an empty ACL buffer.
STATIC void btstack_notify_indicate_ready_handler(void *context) {
MICROPY_PY_BLUETOOTH_ENTER
mp_btstack_pending_op_t *pending_op = (mp_btstack_pending_op_t *)context;
DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler op_type=%d conn_handle=%d value_handle=%d len=%lu\n", pending_op->op_type, pending_op->conn_handle, pending_op->value_handle, pending_op->len);
if (pending_op->op_type == MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY) {
int err = att_server_notify(pending_op->conn_handle, pending_op->value_handle, pending_op->buf, pending_op->len);
DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler: sending notification err=%d\n", err);
assert(err == ERROR_CODE_SUCCESS);
(void)err;
} else {
assert(pending_op->op_type == MP_BLUETOOTH_BTSTACK_PENDING_INDICATE);
int err = att_server_indicate(pending_op->conn_handle, pending_op->value_handle, NULL, 0);
DEBUG_EVENT_printf("btstack_notify_indicate_ready_handler: sending indication err=%d\n", err);
assert(err == ERROR_CODE_SUCCESS);
(void)err;
}
// Can't free the pending op as we're in IRQ context. Leave it for the GC.
btstack_remove_pending_operation(pending_op, false /* del */);
MICROPY_PY_BLUETOOTH_EXIT
}
// Register a pending background operation -- copies the buffer, and makes it known to the GC.
STATIC mp_btstack_pending_op_t *btstack_enqueue_pending_operation(uint16_t op_type, uint16_t conn_handle, uint16_t value_handle, const uint8_t *buf, size_t len) {
DEBUG_EVENT_printf("btstack_enqueue_pending_operation op_type=%d conn_handle=%d value_handle=%d len=%lu\n", op_type, conn_handle, value_handle, len);
mp_btstack_pending_op_t *pending_op = m_new_obj_var(mp_btstack_pending_op_t, uint8_t, len);
pending_op->op_type = op_type;
pending_op->conn_handle = conn_handle;
pending_op->value_handle = value_handle;
pending_op->len = len;
memcpy(pending_op->buf, buf, len);
if (op_type == MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY || op_type == MP_BLUETOOTH_BTSTACK_PENDING_INDICATE) {
pending_op->context_registration.callback = &btstack_notify_indicate_ready_handler;
pending_op->context_registration.context = pending_op;
}
MICROPY_PY_BLUETOOTH_ENTER
bool added = btstack_linked_list_add(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops, (btstack_linked_item_t *)pending_op);
assert(added);
(void)added;
MICROPY_PY_BLUETOOTH_EXIT
return pending_op;
}
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// Cleans up a pending op of the specified type for this conn_handle (and if specified, value_handle).
// Used by MP_BLUETOOTH_BTSTACK_PENDING_WRITE and MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE.
// At the moment, both will set value_handle=0xffff as the events do not know their value_handle.
// TODO: Can we make btstack give us the value_handle for regular write (with response) so that we
// know for sure that we're using the correct entry.
STATIC mp_btstack_pending_op_t *btstack_finish_pending_operation(uint16_t op_type, uint16_t conn_handle, uint16_t value_handle, bool del) {
MICROPY_PY_BLUETOOTH_ENTER
DEBUG_EVENT_printf("btstack_finish_pending_operation op_type=%d conn_handle=%d value_handle=%d\n", op_type, conn_handle, value_handle);
btstack_linked_list_iterator_t it;
btstack_linked_list_iterator_init(&it, &MP_STATE_PORT(bluetooth_btstack_root_pointers)->pending_ops);
while (btstack_linked_list_iterator_has_next(&it)) {
mp_btstack_pending_op_t *pending_op = (mp_btstack_pending_op_t *)btstack_linked_list_iterator_next(&it);
if (pending_op->op_type == op_type && pending_op->conn_handle == conn_handle && (value_handle == 0xffff || pending_op->value_handle == value_handle)) {
DEBUG_EVENT_printf("btstack_finish_pending_operation: found value_handle=%d len=%lu\n", pending_op->value_handle, pending_op->len);
btstack_remove_pending_operation(pending_op, del);
MICROPY_PY_BLUETOOTH_EXIT
return del ? NULL : pending_op;
}
}
DEBUG_EVENT_printf("btstack_finish_pending_operation: not found\n");
MICROPY_PY_BLUETOOTH_EXIT
return NULL;
}
#endif
STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t irq) { STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t irq) {
DEBUG_EVENT_printf("btstack_packet_handler(packet_type=%u, packet=%p)\n", packet_type, packet); DEBUG_EVENT_printf("btstack_packet_handler(packet_type=%u, packet=%p)\n", packet_type, packet);
if (packet_type != HCI_EVENT_PACKET) { if (packet_type != HCI_EVENT_PACKET) {
@ -161,12 +312,17 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t
mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, 0xff, addr); mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, 0xff, addr);
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
} else if (event_type == GATT_EVENT_QUERY_COMPLETE) { } else if (event_type == GATT_EVENT_QUERY_COMPLETE) {
DEBUG_EVENT_printf(" --> gatt query complete\n");
uint16_t conn_handle = gatt_event_query_complete_get_handle(packet); uint16_t conn_handle = gatt_event_query_complete_get_handle(packet);
uint16_t status = gatt_event_query_complete_get_att_status(packet); uint16_t status = gatt_event_query_complete_get_att_status(packet);
DEBUG_EVENT_printf(" --> gatt query complete irq=%d conn_handle=%d status=%d\n", irq, conn_handle, status);
if (irq == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || irq == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) { if (irq == MP_BLUETOOTH_IRQ_GATTC_READ_DONE || irq == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) {
// TODO there is no value_handle available to pass here // TODO there is no value_handle available to pass here.
mp_bluetooth_gattc_on_read_write_status(irq, conn_handle, 0, status); // TODO try and get this implemented in btstack.
mp_bluetooth_gattc_on_read_write_status(irq, conn_handle, 0xffff, status);
// Unref the saved buffer for the write operation on this conn_handle.
if (irq == MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE) {
btstack_finish_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE, conn_handle, 0xffff, false /* del */);
}
} else if (irq == MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE || } else if (irq == MP_BLUETOOTH_IRQ_GATTC_SERVICE_DONE ||
irq == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE || irq == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_DONE ||
irq == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE) { irq == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_DONE) {
@ -223,6 +379,16 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t
len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_INDICATE, conn_handle, value_handle, len, &atomic_state); len = mp_bluetooth_gattc_on_data_available_start(MP_BLUETOOTH_IRQ_GATTC_INDICATE, conn_handle, value_handle, len, &atomic_state);
mp_bluetooth_gattc_on_data_available_chunk(data, len); mp_bluetooth_gattc_on_data_available_chunk(data, len);
mp_bluetooth_gattc_on_data_available_end(atomic_state); mp_bluetooth_gattc_on_data_available_end(atomic_state);
} else if (event_type == GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE) {
uint16_t conn_handle = gatt_event_can_write_without_response_get_handle(packet);
DEBUG_EVENT_printf(" --> gatt can write without response %d\n", conn_handle);
mp_btstack_pending_op_t *pending_op = btstack_finish_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, conn_handle, 0xffff, false /* !del */);
if (pending_op) {
DEBUG_EVENT_printf(" --> ready for value_handle=%d len=%lu\n", pending_op->value_handle, pending_op->len);
gatt_client_write_value_of_characteristic_without_response(pending_op->conn_handle, pending_op->value_handle, pending_op->len, (uint8_t *)pending_op->buf);
// Note: Can't "del" the pending_op from IRQ context. Leave it for the GC.
}
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
} else { } else {
DEBUG_EVENT_printf(" --> hci event type: unknown (0x%02x)\n", event_type); DEBUG_EVENT_printf(" --> hci event type: unknown (0x%02x)\n", event_type);
@ -238,6 +404,10 @@ STATIC void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
btstack_packet_handler(packet_type, packet, 0); btstack_packet_handler(packet_type, packet, 0);
} }
STATIC btstack_packet_callback_registration_t hci_event_callback_registration = {
.callback = &btstack_packet_handler_generic
};
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// For when the handler is being used for service discovery. // For when the handler is being used for service discovery.
STATIC void btstack_packet_handler_discover_services(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { STATIC void btstack_packet_handler_discover_services(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
@ -326,7 +496,6 @@ int mp_bluetooth_init(void) {
#endif #endif
// Register for HCI events. // Register for HCI events.
hci_event_callback_registration.callback = &btstack_packet_handler_generic;
hci_add_event_handler(&hci_event_callback_registration); hci_add_event_handler(&hci_event_callback_registration);
// Set a timeout for HCI initialisation. // Set a timeout for HCI initialisation.
@ -410,8 +579,7 @@ 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) { 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 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) { 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) {
@ -556,7 +724,10 @@ int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, m
if (props & (ATT_PROPERTY_NOTIFY | ATT_PROPERTY_INDICATE)) { if (props & (ATT_PROPERTY_NOTIFY | ATT_PROPERTY_INDICATE)) {
// btstack creates the CCCB as the next handle. // btstack creates the CCCB as the next handle.
mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, MP_BLUETOOTH_CCCB_LEN); mp_bluetooth_gatts_db_create_entry(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, MP_BLUETOOTH_CCCB_LEN);
mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, cccb_buf, sizeof(cccb_buf)); int ret = mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, handles[handle_index] + 1, cccb_buf, sizeof(cccb_buf));
if (ret) {
return ret;
}
} }
DEBUG_EVENT_printf("Registered char with handle %u\n", handles[handle_index]); DEBUG_EVENT_printf("Registered char with handle %u\n", handles[handle_index]);
++handle_index; ++handle_index;
@ -607,19 +778,63 @@ int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) {
uint8_t *data = NULL; uint8_t *data = NULL;
size_t len = 0; size_t len = 0;
mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, &data, &len); mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, &data, &len);
return mp_bluetooth_gatts_notify_send(conn_handle, value_handle, data, &len); return mp_bluetooth_gatts_notify_send(conn_handle, value_handle, data, len);
} }
int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) { int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) {
DEBUG_EVENT_printf("mp_bluetooth_gatts_notify_send\n"); DEBUG_EVENT_printf("mp_bluetooth_gatts_notify_send\n");
// TODO: We need to use att_server_request_to_send_notification here as the stack may not be ready to send a notification.
int err = att_server_notify(conn_handle, value_handle, value, *value_len); // Attempt to send immediately. If it succeeds, btstack will copy the buffer.
MICROPY_PY_BLUETOOTH_ENTER
int err = att_server_notify(conn_handle, value_handle, value, value_len);
MICROPY_PY_BLUETOOTH_EXIT
if (err == BTSTACK_ACL_BUFFERS_FULL) {
DEBUG_EVENT_printf("mp_bluetooth_gatts_notify_send: ACL buffer full, scheduling callback\n");
// Schedule callback, making a copy of the buffer.
mp_btstack_pending_op_t *pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_NOTIFY, conn_handle, value_handle, value, value_len);
err = att_server_request_to_send_notification(&pending_op->context_registration, conn_handle);
if (err != ERROR_CODE_SUCCESS) {
// Failure. Unref and free the pending operation.
btstack_remove_pending_operation(pending_op, true /* del */);
}
return 0;
} else {
return btstack_error_to_errno(err); return btstack_error_to_errno(err);
}
} }
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) {
DEBUG_EVENT_printf("mp_bluetooth_gatts_indicate\n"); DEBUG_EVENT_printf("mp_bluetooth_gatts_indicate\n");
return btstack_error_to_errno(att_server_indicate(conn_handle, value_handle, NULL, 0));
uint8_t *data = NULL;
size_t len = 0;
mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, &data, &len);
// Attempt to send immediately, will copy buffer.
MICROPY_PY_BLUETOOTH_ENTER
int err = att_server_indicate(conn_handle, value_handle, data, len);
MICROPY_PY_BLUETOOTH_EXIT
if (err == BTSTACK_ACL_BUFFERS_FULL) {
DEBUG_EVENT_printf("mp_bluetooth_gatts_indicate: ACL buffer full, scheduling callback\n");
// Schedule callback, making a copy of the buffer.
mp_btstack_pending_op_t *pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_INDICATE, conn_handle, value_handle, data, len);
err = att_server_request_to_send_indication(&pending_op->context_registration, conn_handle);
if (err != ERROR_CODE_SUCCESS) {
// Failure. Unref and free the pending operation.
btstack_remove_pending_operation(pending_op, true /* del */);
}
return 0;
} else {
return btstack_error_to_errno(err);
}
} }
int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) { int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) {
@ -751,19 +966,42 @@ int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) {
int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode) { int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len, unsigned int mode) {
DEBUG_EVENT_printf("mp_bluetooth_gattc_write\n"); DEBUG_EVENT_printf("mp_bluetooth_gattc_write\n");
// TODO the below gatt_client functions do not copy the data and require it to be valid // We should be distinguishing between gatt_client_write_value_of_characteristic vs
// until the write is done, so there should be some kind of buffering done here. // gatt_client_write_characteristic_descriptor_using_descriptor_handle.
// However both are implemented using send_gatt_write_attribute_value_request under the hood,
// and we get the exact same event to the packet handler.
// Same story for the "without response" version.
int err;
mp_btstack_pending_op_t *pending_op = NULL;
if (mode == MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE) { if (mode == MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE) {
// TODO need to call gatt_client_request_can_write_without_response_event then do // If possible, this will send immediately, copying the buffer directly to the ACL buffer.
// the actual write on the callback from that. err = gatt_client_write_value_of_characteristic_without_response(conn_handle, value_handle, *value_len, (uint8_t *)value);
return btstack_error_to_errno(gatt_client_write_value_of_characteristic_without_response(conn_handle, value_handle, *value_len, (uint8_t *)value)); if (err == GATT_CLIENT_BUSY) {
DEBUG_EVENT_printf("mp_bluetooth_gattc_write: client busy\n");
// Can't send right now, need to take a copy of the buffer and add it to the queue.
pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE_NO_RESPONSE, conn_handle, value_handle, value, *value_len);
// Notify when this conn_handle can write.
err = gatt_client_request_can_write_without_response_event(&btstack_packet_handler_generic, conn_handle);
} else {
DEBUG_EVENT_printf("mp_bluetooth_gattc_write: other failure: %d\n", err);
} }
if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) { } else if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) {
return btstack_error_to_errno(gatt_client_write_value_of_characteristic(&btstack_packet_handler_write_with_response, conn_handle, value_handle, *value_len, (uint8_t *)value)); // Pending operation copies the value buffer and keeps a GC reference
// until the response comes back (there is always a response).
pending_op = btstack_enqueue_pending_operation(MP_BLUETOOTH_BTSTACK_PENDING_WRITE, conn_handle, value_handle, value, *value_len);
err = gatt_client_write_value_of_characteristic(&btstack_packet_handler_write_with_response, conn_handle, value_handle, pending_op->len, pending_op->buf);
} else {
return MP_EINVAL;
} }
return MP_EINVAL; if (pending_op && err != ERROR_CODE_SUCCESS) {
// Failure. Unref and free the pending operation.
btstack_remove_pending_operation(pending_op, true /* del */);
}
return btstack_error_to_errno(err);
} }
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE

View File

@ -33,6 +33,8 @@
#include "lib/btstack/src/btstack.h" #include "lib/btstack/src/btstack.h"
typedef struct _mp_btstack_pending_op_t mp_btstack_pending_op_t;
typedef struct _mp_bluetooth_btstack_root_pointers_t { typedef struct _mp_bluetooth_btstack_root_pointers_t {
// This stores both the advertising data and the scan response data, concatenated together. // This stores both the advertising data and the scan response data, concatenated together.
uint8_t *adv_data; uint8_t *adv_data;
@ -45,6 +47,7 @@ typedef struct _mp_bluetooth_btstack_root_pointers_t {
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// Registration for notify/indicate events. // Registration for notify/indicate events.
gatt_client_notification_t notification; gatt_client_notification_t notification;
btstack_linked_list_t pending_ops;
#endif #endif
} mp_bluetooth_btstack_root_pointers_t; } mp_bluetooth_btstack_root_pointers_t;

View File

@ -321,8 +321,7 @@ STATIC mp_obj_t bluetooth_ble_config(size_t n_args, const mp_obj_t *args, mp_map
case MP_QSTR_gap_name: { case MP_QSTR_gap_name: {
mp_buffer_info_t bufinfo; mp_buffer_info_t bufinfo;
mp_get_buffer_raise(e->value, &bufinfo, MP_BUFFER_READ); 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(mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len));
bluetooth_handle_errno(ret);
break; break;
} }
case MP_QSTR_rxbuf: { case MP_QSTR_rxbuf: {
@ -663,10 +662,9 @@ STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args)
if (n_args == 4) { if (n_args == 4) {
mp_buffer_info_t bufinfo = {0}; mp_buffer_info_t bufinfo = {0};
mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ);
size_t len = bufinfo.len; int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, bufinfo.len);
int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, &len);
bluetooth_handle_errno(err); bluetooth_handle_errno(err);
return MP_OBJ_NEW_SMALL_INT(len); return mp_const_none;
} else { } else {
int err = mp_bluetooth_gatts_notify(conn_handle, value_handle); int err = mp_bluetooth_gatts_notify(conn_handle, value_handle);
return bluetooth_handle_errno(err); return bluetooth_handle_errno(err);
@ -674,6 +672,15 @@ STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args)
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_notify_obj, 3, 4, bluetooth_ble_gatts_notify); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_notify_obj, 3, 4, bluetooth_ble_gatts_notify);
STATIC mp_obj_t bluetooth_ble_gatts_indicate(mp_obj_t self_in, mp_obj_t conn_handle_in, mp_obj_t value_handle_in) {
mp_int_t conn_handle = mp_obj_get_int(conn_handle_in);
mp_int_t value_handle = mp_obj_get_int(value_handle_in);
int err = mp_bluetooth_gatts_indicate(conn_handle, value_handle);
return bluetooth_handle_errno(err);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gatts_indicate_obj, bluetooth_ble_gatts_indicate);
STATIC mp_obj_t bluetooth_ble_gatts_set_buffer(size_t n_args, const mp_obj_t *args) { STATIC mp_obj_t bluetooth_ble_gatts_set_buffer(size_t n_args, const mp_obj_t *args) {
mp_int_t value_handle = mp_obj_get_int(args[1]); mp_int_t value_handle = mp_obj_get_int(args[1]);
mp_int_t len = mp_obj_get_int(args[2]); mp_int_t len = mp_obj_get_int(args[2]);
@ -765,6 +772,7 @@ STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_gatts_read), MP_ROM_PTR(&bluetooth_ble_gatts_read_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_read), MP_ROM_PTR(&bluetooth_ble_gatts_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_gatts_write), MP_ROM_PTR(&bluetooth_ble_gatts_write_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_write), MP_ROM_PTR(&bluetooth_ble_gatts_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_gatts_notify), MP_ROM_PTR(&bluetooth_ble_gatts_notify_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_notify), MP_ROM_PTR(&bluetooth_ble_gatts_notify_obj) },
{ MP_ROM_QSTR(MP_QSTR_gatts_indicate), MP_ROM_PTR(&bluetooth_ble_gatts_indicate_obj) },
{ MP_ROM_QSTR(MP_QSTR_gatts_set_buffer), MP_ROM_PTR(&bluetooth_ble_gatts_set_buffer_obj) }, { MP_ROM_QSTR(MP_QSTR_gatts_set_buffer), MP_ROM_PTR(&bluetooth_ble_gatts_set_buffer_obj) },
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// GATT Client (i.e. central/scanner role) // GATT Client (i.e. central/scanner role)
@ -1147,47 +1155,58 @@ mp_bluetooth_gatts_db_entry_t *mp_bluetooth_gatts_db_lookup(mp_gatts_db_t db, ui
} }
int mp_bluetooth_gatts_db_read(mp_gatts_db_t db, uint16_t handle, uint8_t **value, size_t *value_len) { int mp_bluetooth_gatts_db_read(mp_gatts_db_t db, uint16_t handle, uint8_t **value, size_t *value_len) {
MICROPY_PY_BLUETOOTH_ENTER
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
if (!entry) { if (entry) {
return MP_EINVAL;
}
*value = entry->data; *value = entry->data;
*value_len = entry->data_len; *value_len = entry->data_len;
if (entry->append) { if (entry->append) {
entry->data_len = 0; entry->data_len = 0;
} }
}
return 0; MICROPY_PY_BLUETOOTH_EXIT
return entry ? 0 : MP_EINVAL;
} }
int mp_bluetooth_gatts_db_write(mp_gatts_db_t db, uint16_t handle, const uint8_t *value, size_t value_len) { int mp_bluetooth_gatts_db_write(mp_gatts_db_t db, uint16_t handle, const uint8_t *value, size_t value_len) {
MICROPY_PY_BLUETOOTH_ENTER
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
if (!entry) { if (entry) {
return MP_EINVAL;
}
if (value_len > entry->data_alloc) { if (value_len > entry->data_alloc) {
entry->data = m_new(uint8_t, value_len); uint8_t *data = m_new_maybe(uint8_t, value_len);
if (data) {
entry->data = data;
entry->data_alloc = value_len; entry->data_alloc = value_len;
} else {
MICROPY_PY_BLUETOOTH_EXIT
return MP_ENOMEM;
}
} }
memcpy(entry->data, value, value_len); memcpy(entry->data, value, value_len);
entry->data_len = value_len; entry->data_len = value_len;
}
return 0; MICROPY_PY_BLUETOOTH_EXIT
return entry ? 0 : MP_EINVAL;
} }
int mp_bluetooth_gatts_db_resize(mp_gatts_db_t db, uint16_t handle, size_t len, bool append) { int mp_bluetooth_gatts_db_resize(mp_gatts_db_t db, uint16_t handle, size_t len, bool append) {
MICROPY_PY_BLUETOOTH_ENTER
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle); mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
if (!entry) { if (entry) {
return MP_EINVAL; uint8_t *data = m_renew_maybe(uint8_t, entry->data, entry->data_alloc, len, true);
} if (data) {
entry->data = m_renew(uint8_t, entry->data, entry->data_alloc, len); entry->data = data;
entry->data_alloc = len; entry->data_alloc = len;
entry->data_len = 0; entry->data_len = 0;
entry->append = append; entry->append = append;
return 0; } else {
MICROPY_PY_BLUETOOTH_EXIT
return MP_ENOMEM;
}
}
MICROPY_PY_BLUETOOTH_EXIT
return entry ? 0 : MP_EINVAL;
} }
#endif // MICROPY_PY_BLUETOOTH #endif // MICROPY_PY_BLUETOOTH

View File

@ -199,7 +199,7 @@ int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t
// Notify the central that it should do a read. // Notify the central that it should do a read.
int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle); int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle);
// Notify the central, including a data payload. (Note: does not set the gatts db value). // Notify the central, including a data payload. (Note: does not set the gatts db value).
int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len); int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len);
// Indicate the central. // Indicate the central.
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle); int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle);

View File

@ -587,13 +587,13 @@ int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) {
return ble_hs_err_to_errno(ble_gattc_notify(conn_handle, value_handle)); return ble_hs_err_to_errno(ble_gattc_notify(conn_handle, value_handle));
} }
int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) { int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t value_len) {
if (!mp_bluetooth_is_active()) { if (!mp_bluetooth_is_active()) {
return ERRNO_BLUETOOTH_NOT_ACTIVE; return ERRNO_BLUETOOTH_NOT_ACTIVE;
} }
struct os_mbuf *om = ble_hs_mbuf_from_flat(value, *value_len); struct os_mbuf *om = ble_hs_mbuf_from_flat(value, value_len);
if (om == NULL) { if (om == NULL) {
return -1; return MP_ENOMEM;
} }
// TODO: check that notify_custom takes ownership of om, if not os_mbuf_free_chain(om). // TODO: check that notify_custom takes ownership of om, if not os_mbuf_free_chain(om).
return ble_hs_err_to_errno(ble_gattc_notify_custom(conn_handle, value_handle, om)); return ble_hs_err_to_errno(ble_gattc_notify_custom(conn_handle, value_handle, om));

View File

@ -16,6 +16,7 @@ _IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16) _IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17) _IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18) _IRQ_GATTC_NOTIFY = const(18)
_IRQ_GATTC_INDICATE = const(19)
SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A")
CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444")
@ -59,6 +60,8 @@ def irq(event, data):
print("_IRQ_GATTC_WRITE_DONE", data[-1]) print("_IRQ_GATTC_WRITE_DONE", data[-1])
elif event == _IRQ_GATTC_NOTIFY: elif event == _IRQ_GATTC_NOTIFY:
print("_IRQ_GATTC_NOTIFY", data[-1]) print("_IRQ_GATTC_NOTIFY", data[-1])
elif event == _IRQ_GATTC_INDICATE:
print("_IRQ_GATTC_INDICATE", data[-1])
if waiting_event is not None: if waiting_event is not None:
if (isinstance(waiting_event, int) and event == waiting_event) or ( if (isinstance(waiting_event, int) and event == waiting_event) or (
@ -115,6 +118,16 @@ def instance0():
print("gatts_notify") print("gatts_notify")
ble.gatts_notify(conn_handle, char_handle, "periph2") ble.gatts_notify(conn_handle, char_handle, "periph2")
# Wait for a write to the characteristic from the central.
wait_for_event(_IRQ_GATTS_WRITE, TIMEOUT_MS)
# Wait a bit, then notify a new value on the characteristic.
time.sleep_ms(1000)
print("gatts_write")
ble.gatts_write(char_handle, "periph3")
print("gatts_indicate")
ble.gatts_indicate(conn_handle, char_handle)
# Wait for the central to disconnect. # Wait for the central to disconnect.
wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS)
finally: finally:
@ -163,6 +176,17 @@ def instance1():
ble.gattc_read(conn_handle, value_handle) ble.gattc_read(conn_handle, value_handle)
wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS)
# Write to the characteristic, and ask for a response.
print("gattc_write")
ble.gattc_write(conn_handle, value_handle, "central2", 1)
wait_for_event(_IRQ_GATTC_WRITE_DONE, TIMEOUT_MS)
# Wait for a indicate (should have new data), then read new value.
wait_for_event(_IRQ_GATTC_INDICATE, TIMEOUT_MS)
print("gattc_read")
ble.gattc_read(conn_handle, value_handle)
wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS)
# Disconnect from peripheral. # Disconnect from peripheral.
print("gap_disconnect:", ble.gap_disconnect(conn_handle)) print("gap_disconnect:", ble.gap_disconnect(conn_handle))
wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS)

View File

@ -6,6 +6,9 @@ gatts_write
gatts_notify gatts_notify
_IRQ_GATTS_WRITE b'central1' _IRQ_GATTS_WRITE b'central1'
gatts_notify gatts_notify
_IRQ_GATTS_WRITE b'central2'
gatts_write
gatts_indicate
_IRQ_CENTRAL_DISCONNECT _IRQ_CENTRAL_DISCONNECT
--- instance1 --- --- instance1 ---
gap_connect gap_connect
@ -26,5 +29,11 @@ _IRQ_GATTC_NOTIFY b'periph2'
gattc_read gattc_read
_IRQ_GATTC_READ_RESULT b'central1' _IRQ_GATTC_READ_RESULT b'central1'
_IRQ_GATTC_READ_DONE 0 _IRQ_GATTC_READ_DONE 0
gattc_write
_IRQ_GATTC_WRITE_DONE 0
_IRQ_GATTC_INDICATE b'periph3'
gattc_read
_IRQ_GATTC_READ_RESULT b'periph3'
_IRQ_GATTC_READ_DONE 0
gap_disconnect: True gap_disconnect: True
_IRQ_PERIPHERAL_DISCONNECT _IRQ_PERIPHERAL_DISCONNECT