diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index 206bc98f9f..5b2aec67da 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 (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 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; } - 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 diff --git a/extmod/btstack/modbluetooth_btstack.h b/extmod/btstack/modbluetooth_btstack.h index 3bdb5f271e..c943b6d72d 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 da40a15485..d94e5aec91 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; + if (entry) { + *value = entry->data; + *value_len = entry->data_len; + if (entry->append) { + entry->data_len = 0; + } } - - *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; + } + } + + memcpy(entry->data, value, value_len); + entry->data_len = value_len; } - - 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; - - 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 c8b8dc63f2..a232ee2d1d 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 be7d13d9e6..2e94685131 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 33d92b823b..fd6fd46724 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 d89842ef7b..7f66a4d907 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