From 9d823a5d9a6730edde8e1df1e5ff4add1ad17094 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Mon, 20 Jul 2020 16:58:10 +1000 Subject: [PATCH] extmod/modbluetooth: Add event for "indicate acknowledgement". This commit adds the IRQ_GATTS_INDICATE_DONE BLE event which will be raised with the status of gatts_indicate (unlike notify, indications require acknowledgement). An example of its use is added to ble_temperature.py, and to the multitests in ble_characteristic.py. Implemented for btstack and nimble bindings, tested in both directions between unix/btstack and pybd/nimble. --- examples/bluetooth/ble_temperature.py | 19 +++++--- extmod/btstack/modbluetooth_btstack.c | 48 ++++++++++++++++--- extmod/modbluetooth.c | 15 ++++++ extmod/modbluetooth.h | 6 +++ extmod/nimble/modbluetooth_nimble.c | 13 ++++- tests/multi_bluetooth/ble_characteristic.py | 8 +++- .../multi_bluetooth/ble_characteristic.py.exp | 1 + 7 files changed, 94 insertions(+), 16 deletions(-) diff --git a/examples/bluetooth/ble_temperature.py b/examples/bluetooth/ble_temperature.py index fd24e74d74..0e2da2239d 100644 --- a/examples/bluetooth/ble_temperature.py +++ b/examples/bluetooth/ble_temperature.py @@ -13,13 +13,14 @@ from micropython import const _IRQ_CENTRAL_CONNECT = const(1) _IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_INDICATE_DONE = const(20) # org.bluetooth.service.environmental_sensing _ENV_SENSE_UUID = bluetooth.UUID(0x181A) # org.bluetooth.characteristic.temperature _TEMP_CHAR = ( bluetooth.UUID(0x2A6E), - bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY, + bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE, ) _ENV_SENSE_SERVICE = ( _ENV_SENSE_UUID, @@ -52,15 +53,21 @@ class BLETemperature: self._connections.remove(conn_handle) # Start advertising again to allow a new connection. self._advertise() + elif event == _IRQ_GATTS_INDICATE_DONE: + conn_handle, value_handle, status, = data - def set_temperature(self, temp_deg_c, notify=False): + def set_temperature(self, temp_deg_c, notify=False, indicate=False): # Data is sint16 in degrees Celsius with a resolution of 0.01 degrees Celsius. # Write the local value, ready for a central to read. self._ble.gatts_write(self._handle, struct.pack(" att connected\n"); + // The ATT_EVENT_*CONNECTED events are fired for both peripheral and central role, with no way to tell which. + // So we use the HCI_EVENT_LE_META event directly in the main packet handler. + } else if (event_type == ATT_EVENT_DISCONNECTED) { + DEBUG_EVENT_printf(" --> att disconnected\n"); + } else if (event_type == ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE) { + DEBUG_EVENT_printf(" --> att indication complete\n"); + uint16_t conn_handle = att_event_handle_value_indication_complete_get_conn_handle(packet); + uint16_t value_handle = att_event_handle_value_indication_complete_get_attribute_handle(packet); + uint8_t status = att_event_handle_value_indication_complete_get_status(packet); + mp_bluetooth_gatts_on_indicate_complete(conn_handle, value_handle, status); + } else if (event_type == HCI_EVENT_LE_META || event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { + // Ignore, duplicated by att_server.c. + } else { + DEBUG_EVENT_printf(" --> hci att server event type: unknown (0x%02x)\n", event_type); + } +} + 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) { @@ -245,11 +276,8 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t } uint8_t event_type = hci_event_packet_get_type(packet); - if (event_type == ATT_EVENT_CONNECTED) { - DEBUG_EVENT_printf(" --> att connected\n"); - } else if (event_type == ATT_EVENT_DISCONNECTED) { - DEBUG_EVENT_printf(" --> att disconnected\n"); - } else if (event_type == HCI_EVENT_LE_META) { + + if (event_type == HCI_EVENT_LE_META) { DEBUG_EVENT_printf(" --> hci le meta\n"); if (hci_event_le_meta_get_subevent_code(packet) == HCI_SUBEVENT_LE_CONNECTION_COMPLETE) { uint16_t conn_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); @@ -277,7 +305,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; } } else if (event_type == HCI_EVENT_TRANSPORT_PACKET_SENT) { - DEBUG_EVENT_printf(" --> hci transport packet set\n"); + DEBUG_EVENT_printf(" --> hci transport packet sent\n"); } else if (event_type == HCI_EVENT_COMMAND_COMPLETE) { DEBUG_EVENT_printf(" --> hci command complete\n"); } else if (event_type == HCI_EVENT_COMMAND_STATUS) { @@ -288,6 +316,8 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t DEBUG_EVENT_printf(" --> btstack # conns changed\n"); } else if (event_type == HCI_EVENT_VENDOR_SPECIFIC) { DEBUG_EVENT_printf(" --> hci vendor specific\n"); + } else if (event_type == GATT_EVENT_MTU) { + DEBUG_EVENT_printf(" --> hci MTU\n"); } else if (event_type == HCI_EVENT_DISCONNECTION_COMPLETE) { DEBUG_EVENT_printf(" --> hci disconnect complete\n"); uint16_t conn_handle = hci_event_disconnection_complete_get_connection_handle(packet); @@ -500,6 +530,9 @@ int mp_bluetooth_init(void) { // Register for HCI events. hci_add_event_handler(&hci_event_callback_registration); + // Register for ATT server events. + att_server_register_packet_handler(&btstack_packet_handler_att_server); + // Set a timeout for HCI initialisation. btstack_run_loop_set_timer(&btstack_init_deinit_timeout, BTSTACK_INIT_DEINIT_TIMEOUT_MS); btstack_run_loop_set_timer_handler(&btstack_init_deinit_timeout, btstack_init_deinit_timeout_handler); @@ -816,7 +849,8 @@ int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { size_t len = 0; mp_bluetooth_gatts_db_read(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, &data, &len); - // TODO: Handle ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE to generate acknowledgment event. + // Indicate will raise ATT_EVENT_HANDLE_VALUE_INDICATION_COMPLETE when + // acknowledged (or timeout/error). // Attempt to send immediately, will copy buffer. MICROPY_PY_BLUETOOTH_ENTER diff --git a/extmod/modbluetooth.c b/extmod/modbluetooth.c index fb5c6ac8c9..7bfb774782 100644 --- a/extmod/modbluetooth.c +++ b/extmod/modbluetooth.c @@ -800,6 +800,7 @@ STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) }, { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) }, { MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_INDICATE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE) }, { MP_ROM_QSTR(MP_QSTR_FLAG_WRITE_NO_RESPONSE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE) }, }; @@ -887,6 +888,9 @@ STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) { } else if (event == MP_BLUETOOTH_IRQ_GATTS_WRITE) { // conn_handle, value_handle ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, NULL, NULL); + } else if (event == MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE) { + // conn_handle, value_handle, status + ringbuf_extract(&o->ringbuf, data_tuple, 2, 1, NULL, 0, NULL, NULL); #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE } else if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT) { // addr_type, addr, adv_type, rssi, adv_data @@ -999,6 +1003,17 @@ void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) { schedule_ringbuf(atomic_state); } +void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status) { + MICROPY_PY_BLUETOOTH_ENTER + mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth)); + if (enqueue_irq(o, 2 + 2 + 1, MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE)) { + ringbuf_put16(&o->ringbuf, conn_handle); + ringbuf_put16(&o->ringbuf, value_handle); + ringbuf_put(&o->ringbuf, status); + } + schedule_ringbuf(atomic_state); +} + #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE void mp_bluetooth_gap_on_scan_complete(void) { MICROPY_PY_BLUETOOTH_ENTER diff --git a/extmod/modbluetooth.h b/extmod/modbluetooth.h index a232ee2d1d..cdb86e5e63 100644 --- a/extmod/modbluetooth.h +++ b/extmod/modbluetooth.h @@ -68,6 +68,7 @@ #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE_NO_RESPONSE (1 << 2) #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE (1 << 3) #define MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY (1 << 4) +#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE (1 << 5) // For mp_bluetooth_gattc_write, the mode parameter #define MP_BLUETOOTH_WRITE_MODE_NO_RESPONSE (0) @@ -107,6 +108,7 @@ #define MP_BLUETOOTH_IRQ_GATTC_WRITE_DONE (17) #define MP_BLUETOOTH_IRQ_GATTC_NOTIFY (18) #define MP_BLUETOOTH_IRQ_GATTC_INDICATE (19) +#define MP_BLUETOOTH_IRQ_GATTS_INDICATE_DONE (20) /* These aren't included in the module for space reasons, but can be used @@ -132,6 +134,7 @@ _IRQ_GATTC_READ_DONE = const(16) _IRQ_GATTC_WRITE_DONE = const(17) _IRQ_GATTC_NOTIFY = const(18) _IRQ_GATTC_INDICATE = const(19) +_IRQ_GATTS_INDICATE_DONE = const(20) */ // Common UUID type. @@ -245,6 +248,9 @@ void mp_bluetooth_gap_on_connected_disconnected(uint8_t event, uint16_t conn_han // Call this when a characteristic is written to. void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle); +// Call this when an acknowledgment is received for an indication. +void mp_bluetooth_gatts_on_indicate_complete(uint16_t conn_handle, uint16_t value_handle, uint8_t status); + #if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK // Call this when a characteristic is read from. Return false to deny the read. bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle); diff --git a/extmod/nimble/modbluetooth_nimble.c b/extmod/nimble/modbluetooth_nimble.c index d60e23ecb4..843989b2b8 100644 --- a/extmod/nimble/modbluetooth_nimble.c +++ b/extmod/nimble/modbluetooth_nimble.c @@ -248,6 +248,15 @@ STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) { reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val); mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr); break; + + case BLE_GAP_EVENT_NOTIFY_TX: { + DEBUG_EVENT_printf("gap_event_cb: notify_tx: %d %d\n", event->notify_tx.indication, event->notify_tx.status); + // This event corresponds to either a sent notify/indicate (status == 0), or an indication confirmation (status != 0). + if (event->notify_tx.indication && event->notify_tx.status != 0) { + // Map "done/ack" to 0, otherwise pass the status directly. + mp_bluetooth_gatts_on_indicate_complete(event->notify_tx.conn_handle, event->notify_tx.attr_handle, event->notify_tx.status == BLE_HS_EDONE ? 0 : event->notify_tx.status); + } + } } return 0; @@ -603,8 +612,8 @@ int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) { if (!mp_bluetooth_is_active()) { return ERRNO_BLUETOOTH_NOT_ACTIVE; } - // TODO: catch BLE_GAP_EVENT_NOTIFY_TX to raise an event for completed - // indication transaction. + // This will raise BLE_GAP_EVENT_NOTIFY_TX with a status when it is + // acknowledged (or timeout/error). return ble_hs_err_to_errno(ble_gattc_indicate(conn_handle, value_handle)); } diff --git a/tests/multi_bluetooth/ble_characteristic.py b/tests/multi_bluetooth/ble_characteristic.py index fd6fd46724..0d72c181fd 100644 --- a/tests/multi_bluetooth/ble_characteristic.py +++ b/tests/multi_bluetooth/ble_characteristic.py @@ -17,12 +17,13 @@ _IRQ_GATTC_READ_DONE = const(16) _IRQ_GATTC_WRITE_DONE = const(17) _IRQ_GATTC_NOTIFY = const(18) _IRQ_GATTC_INDICATE = const(19) +_IRQ_GATTS_INDICATE_DONE = const(20) SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") CHAR = ( CHAR_UUID, - bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY, + bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY | bluetooth.FLAG_INDICATE, ) SERVICE = ( SERVICE_UUID, @@ -62,6 +63,8 @@ def irq(event, data): print("_IRQ_GATTC_NOTIFY", data[-1]) elif event == _IRQ_GATTC_INDICATE: print("_IRQ_GATTC_INDICATE", data[-1]) + elif event == _IRQ_GATTS_INDICATE_DONE: + print("_IRQ_GATTS_INDICATE_DONE", data[-1]) if waiting_event is not None: if (isinstance(waiting_event, int) and event == waiting_event) or ( @@ -128,6 +131,9 @@ def instance0(): print("gatts_indicate") ble.gatts_indicate(conn_handle, char_handle) + # Wait for the indicate ack. + wait_for_event(_IRQ_GATTS_INDICATE_DONE, TIMEOUT_MS) + # Wait for the central to disconnect. wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) finally: diff --git a/tests/multi_bluetooth/ble_characteristic.py.exp b/tests/multi_bluetooth/ble_characteristic.py.exp index 7f66a4d907..25b5544efc 100644 --- a/tests/multi_bluetooth/ble_characteristic.py.exp +++ b/tests/multi_bluetooth/ble_characteristic.py.exp @@ -9,6 +9,7 @@ gatts_notify _IRQ_GATTS_WRITE b'central2' gatts_write gatts_indicate +_IRQ_GATTS_INDICATE_DONE 0 _IRQ_CENTRAL_DISCONNECT --- instance1 --- gap_connect