summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--extmod/btstack/modbluetooth_btstack.c284
-rw-r--r--extmod/btstack/modbluetooth_btstack.h3
-rw-r--r--extmod/modbluetooth.c85
-rw-r--r--extmod/modbluetooth.h2
-rw-r--r--extmod/nimble/modbluetooth_nimble.c6
-rw-r--r--tests/multi_bluetooth/ble_characteristic.py24
-rw-r--r--tests/multi_bluetooth/ble_characteristic.py.exp9
7 files changed, 353 insertions, 60 deletions
diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c
index 206bc98f9..5b2aec67d 100644
--- a/extmod/btstack/modbluetooth_btstack.c
+++ b/extmod/btstack/modbluetooth_btstack.c
@@ -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;
-STATIC btstack_packet_callback_registration_t hci_event_callback_registration;
-
STATIC int btstack_error_to_errno(int err) {
DEBUG_EVENT_printf(" --> btstack error: %d\n", err);
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;
}
+// 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) {
DEBUG_EVENT_printf("btstack_packet_handler(packet_type=%u, packet=%p)\n", packet_type, 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);
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
} 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 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) {
- // TODO there is no value_handle available to pass here
- mp_bluetooth_gattc_on_read_write_status(irq, conn_handle, 0, status);
+ // TODO there is no value_handle available to pass here.
+ // 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 ||
irq == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_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);
mp_bluetooth_gattc_on_data_available_chunk(data, len);
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
} else {
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);
}
+STATIC btstack_packet_callback_registration_t hci_event_callback_registration = {
+ .callback = &btstack_packet_handler_generic
+};
+
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// 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) {
@@ -326,7 +496,6 @@ int mp_bluetooth_init(void) {
#endif
// Register for HCI events.
- hci_event_callback_registration.callback = &btstack_packet_handler_generic;
hci_add_event_handler(&hci_event_callback_registration);
// 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) {
- mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, buf, len);
- return 0;
+ return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, BTSTACK_GAP_DEVICE_NAME_HANDLE, buf, 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)) {
// 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_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]);
++handle_index;
@@ -607,19 +778,63 @@ int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) {
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);
- 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");
- // 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);
- return btstack_error_to_errno(err);
+
+ // 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);
+ }
}
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) {
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) {
@@ -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) {
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
- // until the write is done, so there should be some kind of buffering done here.
+ // We should be distinguishing between gatt_client_write_value_of_characteristic vs
+ // 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) {
- // TODO need to call gatt_client_request_can_write_without_response_event then do
- // the actual write on the callback from that.
- return btstack_error_to_errno(gatt_client_write_value_of_characteristic_without_response(conn_handle, value_handle, *value_len, (uint8_t *)value));
+ // If possible, this will send immediately, copying the buffer directly to the ACL buffer.
+ err = 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);
+ }
+ } else if (mode == MP_BLUETOOTH_WRITE_MODE_WITH_RESPONSE) {
+ // 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;
}
- 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));
+
+ if (pending_op && err != ERROR_CODE_SUCCESS) {
+ // Failure. Unref and free the pending operation.
+ btstack_remove_pending_operation(pending_op, true /* del */);
}
- return MP_EINVAL;
+ return btstack_error_to_errno(err);
}
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
diff --git a/extmod/btstack/modbluetooth_btstack.h b/extmod/btstack/modbluetooth_btstack.h
index 3bdb5f271..c943b6d72 100644
--- a/extmod/btstack/modbluetooth_btstack.h
+++ b/extmod/btstack/modbluetooth_btstack.h
@@ -33,6 +33,8 @@
#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 {
// This stores both the advertising data and the scan response data, concatenated together.
uint8_t *adv_data;
@@ -45,6 +47,7 @@ typedef struct _mp_bluetooth_btstack_root_pointers_t {
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// Registration for notify/indicate events.
gatt_client_notification_t notification;
+ btstack_linked_list_t pending_ops;
#endif
} mp_bluetooth_btstack_root_pointers_t;
diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c
index da40a1548..d94e5aec9 100644
--- a/extmod/modbluetooth.c
+++ b/extmod/modbluetooth.c
@@ -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: {
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);
+ bluetooth_handle_errno(mp_bluetooth_gap_set_device_name(bufinfo.buf, bufinfo.len));
break;
}
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) {
mp_buffer_info_t bufinfo = {0};
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, &len);
+ int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, bufinfo.len);
bluetooth_handle_errno(err);
- return MP_OBJ_NEW_SMALL_INT(len);
+ return mp_const_none;
} else {
int err = mp_bluetooth_gatts_notify(conn_handle, value_handle);
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_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) {
mp_int_t value_handle = mp_obj_get_int(args[1]);
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_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_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) },
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
// 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) {
+ MICROPY_PY_BLUETOOTH_ENTER
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
- if (!entry) {
- return MP_EINVAL;
- }
-
- *value = entry->data;
- *value_len = entry->data_len;
- if (entry->append) {
- entry->data_len = 0;
+ if (entry) {
+ *value = entry->data;
+ *value_len = entry->data_len;
+ if (entry->append) {
+ 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) {
+ MICROPY_PY_BLUETOOTH_ENTER
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
- if (!entry) {
- return MP_EINVAL;
- }
+ if (entry) {
+ if (value_len > entry->data_alloc) {
+ uint8_t *data = m_new_maybe(uint8_t, value_len);
+ if (data) {
+ entry->data = data;
+ entry->data_alloc = value_len;
+ } else {
+ MICROPY_PY_BLUETOOTH_EXIT
+ return MP_ENOMEM;
+ }
+ }
- if (value_len > entry->data_alloc) {
- entry->data = m_new(uint8_t, value_len);
- entry->data_alloc = value_len;
+ memcpy(entry->data, value, value_len);
+ entry->data_len = value_len;
}
-
- memcpy(entry->data, value, 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) {
+ MICROPY_PY_BLUETOOTH_ENTER
mp_bluetooth_gatts_db_entry_t *entry = mp_bluetooth_gatts_db_lookup(db, handle);
- if (!entry) {
- return MP_EINVAL;
+ if (entry) {
+ uint8_t *data = m_renew_maybe(uint8_t, entry->data, entry->data_alloc, len, true);
+ if (data) {
+ entry->data = data;
+ entry->data_alloc = len;
+ entry->data_len = 0;
+ entry->append = append;
+ } else {
+ MICROPY_PY_BLUETOOTH_EXIT
+ return MP_ENOMEM;
+ }
}
- entry->data = m_renew(uint8_t, entry->data, entry->data_alloc, len);
- entry->data_alloc = len;
- entry->data_len = 0;
- entry->append = append;
- return 0;
+ MICROPY_PY_BLUETOOTH_EXIT
+ return entry ? 0 : MP_EINVAL;
}
#endif // MICROPY_PY_BLUETOOTH
diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h
index c8b8dc63f..a232ee2d1 100644
--- a/extmod/modbluetooth.h
+++ b/extmod/modbluetooth.h
@@ -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.
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).
-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.
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle);
diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c
index be7d13d9e..2e9468513 100644
--- a/extmod/nimble/modbluetooth_nimble.c
+++ b/extmod/nimble/modbluetooth_nimble.c
@@ -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));
}
-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()) {
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) {
- return -1;
+ return MP_ENOMEM;
}
// 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));
diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py
index 33d92b823..fd6fd4672 100644
--- a/tests/multi_bluetooth/ble_characteristic.py
+++ b/tests/multi_bluetooth/ble_characteristic.py
@@ -16,6 +16,7 @@ _IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
+_IRQ_GATTC_INDICATE = const(19)
SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A")
CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444")
@@ -59,6 +60,8 @@ def irq(event, data):
print("_IRQ_GATTC_WRITE_DONE", data[-1])
elif event == _IRQ_GATTC_NOTIFY:
print("_IRQ_GATTC_NOTIFY", data[-1])
+ elif event == _IRQ_GATTC_INDICATE:
+ print("_IRQ_GATTC_INDICATE", data[-1])
if waiting_event is not None:
if (isinstance(waiting_event, int) and event == waiting_event) or (
@@ -115,6 +118,16 @@ def instance0():
print("gatts_notify")
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_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS)
finally:
@@ -163,6 +176,17 @@ def instance1():
ble.gattc_read(conn_handle, value_handle)
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.
print("gap_disconnect:", ble.gap_disconnect(conn_handle))
wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS)
diff --git a/tests/multi_bluetooth/ble_characteristic.py.exp b/tests/multi_bluetooth/ble_characteristic.py.exp
index d89842ef7..7f66a4d90 100644
--- a/tests/multi_bluetooth/ble_characteristic.py.exp
+++ b/tests/multi_bluetooth/ble_characteristic.py.exp
@@ -6,6 +6,9 @@ gatts_write
gatts_notify
_IRQ_GATTS_WRITE b'central1'
gatts_notify
+_IRQ_GATTS_WRITE b'central2'
+gatts_write
+gatts_indicate
_IRQ_CENTRAL_DISCONNECT
--- instance1 ---
gap_connect
@@ -26,5 +29,11 @@ _IRQ_GATTC_NOTIFY b'periph2'
gattc_read
_IRQ_GATTC_READ_RESULT b'central1'
_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
_IRQ_PERIPHERAL_DISCONNECT