From 8958e7ef087c86030590fe936738b85360f9d25d Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Thu, 10 Feb 2022 11:31:57 -0800 Subject: [PATCH] Add S3 GATT client support This allows you to connect to GATT services on the other device. It also adds connection initiation (GAP central). More progress on #5926 --- locale/circuitpython.pot | 13 + ports/espressif/Makefile | 4 + ports/espressif/common-hal/_bleio/Adapter.c | 97 +++++- .../common-hal/_bleio/Characteristic.c | 170 ++++++++-- .../common-hal/_bleio/Characteristic.h | 8 +- .../common-hal/_bleio/CharacteristicBuffer.c | 66 +++- .../espressif/common-hal/_bleio/Connection.c | 290 +++++++++++++++++- .../espressif/common-hal/_bleio/Descriptor.c | 1 - .../espressif/common-hal/_bleio/Descriptor.h | 3 + .../common-hal/_bleio/PacketBuffer.c | 161 +++++++++- ports/espressif/common-hal/_bleio/Service.c | 8 +- ports/espressif/common-hal/_bleio/__init__.c | 35 +++ ports/espressif/common-hal/_bleio/__init__.h | 7 + .../espressif/common-hal/_bleio/ble_events.c | 116 +++++++ .../espressif/common-hal/_bleio/ble_events.h | 51 +++ ports/espressif/mpconfigport.h | 12 + .../common-hal/_bleio/CharacteristicBuffer.c | 1 + shared-module/vectorio/VectorShape.c | 16 +- 18 files changed, 985 insertions(+), 74 deletions(-) create mode 100644 ports/espressif/common-hal/_bleio/ble_events.c create mode 100644 ports/espressif/common-hal/_bleio/ble_events.h diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 893e1b339e..f5f82a3001 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -2427,6 +2427,16 @@ msgstr "" msgid "Unhandled ESP TLS error %d %d %x %d" msgstr "" +#: ports/espressif/common-hal/_bleio/__init__.c +#, c-format +msgid "Unknown BLE error at %s:%d: %d" +msgstr "" + +#: ports/espressif/common-hal/_bleio/__init__.c +#, c-format +msgid "Unknown BLE error: %d" +msgstr "" + #: shared-bindings/wifi/Radio.c #, c-format msgid "Unknown failure %d" @@ -2498,12 +2508,14 @@ msgstr "" msgid "Update Failed" msgstr "" +#: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Descriptor.c #: ports/nrf/common-hal/_bleio/Characteristic.c #: ports/nrf/common-hal/_bleio/Descriptor.c msgid "Value length != required fixed length" msgstr "" +#: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/espressif/common-hal/_bleio/Descriptor.c #: ports/nrf/common-hal/_bleio/Characteristic.c #: ports/nrf/common-hal/_bleio/Descriptor.c @@ -3784,6 +3796,7 @@ msgstr "" msgid "non-Device in %q" msgstr "" +#: ports/espressif/common-hal/_bleio/Connection.c #: ports/nrf/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" msgstr "" diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index d95812ad63..f086e8eb06 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -268,6 +268,10 @@ ifneq ($(CIRCUITPY_USB),0) SRC_C += lib/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c endif +ifneq ($(CIRCUITPY_BLEIO),0) +SRC_C += common-hal/_bleio/ble_events.c +endif + SRC_COMMON_HAL_EXPANDED = \ $(addprefix shared-bindings/, $(SRC_COMMON_HAL)) \ $(addprefix shared-bindings/, $(SRC_BINDINGS_ENUMS)) \ diff --git a/ports/espressif/common-hal/_bleio/Adapter.c b/ports/espressif/common-hal/_bleio/Adapter.c index 288a908546..d72f9dbf02 100644 --- a/ports/espressif/common-hal/_bleio/Adapter.c +++ b/ports/espressif/common-hal/_bleio/Adapter.c @@ -256,6 +256,17 @@ STATIC void _convert_address(const bleio_address_obj_t *address, ble_addr_t *nim memcpy(nimble_address->val, (uint8_t *)address_buf_info.buf, NUM_BLEIO_ADDRESS_BYTES); } +STATIC int _mtu_reply(uint16_t conn_handle, + const struct ble_gatt_error *error, + uint16_t mtu, void *arg) { + bleio_connection_internal_t *connection = (bleio_connection_internal_t *)arg; + if (conn_handle != connection->conn_handle || error->status != 0) { + return 0; + } + connection->mtu = mtu; + return 0; +} + STATIC void _new_connection(uint16_t conn_handle) { // Set the tx_power for the connection higher than the advertisement. esp_ble_tx_power_set(conn_handle, ESP_PWR_LVL_N0); @@ -275,12 +286,96 @@ STATIC void _new_connection(uint16_t conn_handle) { connection->pair_status = PAIR_NOT_PAIRED; connection->mtu = 0; + ble_gattc_exchange_mtu(conn_handle, _mtu_reply, connection); + // Change the callback for the connection. ble_gap_set_event_cb(conn_handle, bleio_connection_event_cb, connection); } +static int _connect_event(struct ble_gap_event *event, void *self_in) { + bleio_adapter_obj_t *self = (bleio_adapter_obj_t *)self_in; + + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "Connect event: %d\n", event->type); + #endif + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + if (event->connect.status == 0) { + _new_connection(event->connect.conn_handle); + // Set connections objs back to NULL since we have a new + // connection and need a new tuple. + self->connection_objs = NULL; + xTaskNotify(cp_task, event->connect.conn_handle, eSetValueWithOverwrite); + } else { + xTaskNotify(cp_task, -event->connect.status, eSetValueWithOverwrite); + } + break; + + default: + #if CIRCUITPY_VERBOSE_BLE + // For debugging. + mp_printf(&mp_plat_print, "Unhandled connect event: %d\n", event->type); + #endif + break; + } + return 0; +} + mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self, bleio_address_obj_t *address, mp_float_t timeout) { - mp_raise_NotImplementedError(NULL); + // Stop any active scan. + if (self->scan_results != NULL) { + common_hal_bleio_adapter_stop_scan(self); + } + + struct ble_gap_conn_params conn_params = { + .scan_itvl = MSEC_TO_UNITS(100, UNIT_0_625_MS), + .scan_window = MSEC_TO_UNITS(100, UNIT_0_625_MS), + .itvl_min = MSEC_TO_UNITS(15, UNIT_1_25_MS), + .itvl_max = MSEC_TO_UNITS(300, UNIT_1_25_MS), + .latency = 0, + .supervision_timeout = MSEC_TO_UNITS(4000, UNIT_10_MS), + .min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN, + .max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN + }; + + uint8_t own_addr_type; + // TODO: Use a resolvable address if the peer has our key. + CHECK_NIMBLE_ERROR(ble_hs_id_infer_auto(false, &own_addr_type)); + + ble_addr_t addr; + _convert_address(address, &addr); + + cp_task = xTaskGetCurrentTaskHandle(); + // Make sure we don't have a pending notification from a previous time. This + // can happen if a previous wait timed out before the notification was given. + xTaskNotifyStateClear(cp_task); + CHECK_NIMBLE_ERROR( + ble_gap_connect(own_addr_type, &addr, + SEC_TO_UNITS(timeout, UNIT_1_MS) + 0.5f, + &conn_params, + _connect_event, self)); + + int error_code; + CHECK_NOTIFY(xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200)); + // Negative values are error codes, connection handle otherwise. + if (error_code < 0) { + CHECK_BLE_ERROR(-error_code); + } + uint16_t conn_handle = error_code; + + // TODO: If we have keys, then try and encrypt the connection. + + // TODO: Negotiate for better PHY and data lengths since we are the central. These are + // nice-to-haves so ignore any errors. + + // Make the connection object and return it. + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + bleio_connection_internal_t *connection = &bleio_connections[i]; + if (connection->conn_handle == conn_handle) { + connection->is_central = true; + return bleio_connection_new_from_internal(connection); + } + } mp_raise_bleio_BluetoothError(translate("Failed to connect: internal error")); diff --git a/ports/espressif/common-hal/_bleio/Characteristic.c b/ports/espressif/common-hal/_bleio/Characteristic.c index 161e17ea1a..0344e38df1 100644 --- a/ports/espressif/common-hal/_bleio/Characteristic.c +++ b/ports/espressif/common-hal/_bleio/Characteristic.c @@ -42,40 +42,22 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo, const char *user_description) { - mp_raise_NotImplementedError(NULL); self->service = service; self->uuid = uuid; self->handle = BLEIO_HANDLE_INVALID; + self->cccd_handle = BLEIO_HANDLE_INVALID; + self->sccd_handle = BLEIO_HANDLE_INVALID; self->props = props; self->read_perm = read_perm; self->write_perm = write_perm; - self->initial_value_len = 0; - self->initial_value = NULL; - if (initial_value_bufinfo != NULL) { - // Copy the initial value if it's on the heap. Otherwise it's internal and we may not be able - // to allocate. - self->initial_value_len = initial_value_bufinfo->len; - if (gc_alloc_possible()) { - if (gc_nbytes(initial_value_bufinfo->buf) > 0) { - uint8_t *initial_value = m_malloc(self->initial_value_len, false); - memcpy(initial_value, initial_value_bufinfo->buf, self->initial_value_len); - self->initial_value = initial_value; - } else { - self->initial_value = initial_value_bufinfo->buf; - } - self->descriptor_list = mp_obj_new_list(0, NULL); - } else { - self->initial_value = initial_value_bufinfo->buf; - self->descriptor_list = NULL; - } + common_hal_bleio_characteristic_set_value(self, initial_value_bufinfo); + + if (gc_alloc_possible()) { + self->descriptor_list = mp_obj_new_list(0, NULL); + } else { + self->descriptor_list = NULL; } - // const mp_int_t max_length_max = fixed_length ? BLE_GATTS_FIX_ATTR_LEN_MAX : BLE_GATTS_VAR_ATTR_LEN_MAX; - // if (max_length < 0 || max_length > max_length_max) { - // mp_raise_ValueError_varg(translate("max_length must be 0-%d when fixed_length is %s"), - // max_length_max, fixed_length ? "True" : "False"); - // } - // TODO: Implement this. self->max_length = max_length; self->fixed_length = fixed_length; @@ -97,8 +79,60 @@ bleio_service_obj_t *common_hal_bleio_characteristic_get_service(bleio_character return self->service; } +typedef struct { + TaskHandle_t task; + uint8_t *buf; + uint16_t len; +} _read_info_t; + +STATIC int _read_cb(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) { + _read_info_t *read_info = (_read_info_t *)arg; + switch (error->status) { + case 0: { + int len = MIN(read_info->len, OS_MBUF_PKTLEN(attr->om)); + os_mbuf_copydata(attr->om, attr->offset, len, read_info->buf); + read_info->len = len; + } + MP_FALLTHROUGH; + + default: + #if CIRCUITPY_VERBOSE_BLE + // For debugging. + mp_printf(&mp_plat_print, "Read status: %d\n", error->status); + #endif + xTaskNotify(read_info->task, error->status, eSetValueWithOverwrite); + break; + } + + return 0; +} + size_t common_hal_bleio_characteristic_get_value(bleio_characteristic_obj_t *self, uint8_t *buf, size_t len) { - // TODO: Implement this. + // Do GATT operations only if this characteristic has been added to a registered service. + if (self->handle == BLEIO_HANDLE_INVALID) { + return 0; + } + uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection); + if (common_hal_bleio_service_get_is_remote(self->service)) { + _read_info_t read_info = { + .task = xTaskGetCurrentTaskHandle(), + .buf = buf, + .len = len + }; + CHECK_NIMBLE_ERROR(ble_gattc_read(conn_handle, self->handle, _read_cb, &read_info)); + int error_code; + xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200); + CHECK_BLE_ERROR(error_code); + return read_info.len; + } else { + len = MIN(self->current_value_len, len); + memcpy(buf, self->current_value, len); + return len; + } + return 0; } @@ -106,8 +140,62 @@ size_t common_hal_bleio_characteristic_get_max_length(bleio_characteristic_obj_t return self->max_length; } +STATIC int _write_cb(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) { + TaskHandle_t task = (TaskHandle_t)arg; + xTaskNotify(task, error->status, eSetValueWithOverwrite); + + return 0; +} + void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_buffer_info_t *bufinfo) { - // TODO: Implement this. + if (common_hal_bleio_service_get_is_remote(self->service)) { + uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection); + if ((self->props & CHAR_PROP_WRITE_NO_RESPONSE) != 0) { + CHECK_NIMBLE_ERROR(ble_gattc_write_no_rsp_flat(conn_handle, self->handle, bufinfo->buf, bufinfo->len)); + } else { + CHECK_NIMBLE_ERROR(ble_gattc_write_flat(conn_handle, self->handle, bufinfo->buf, bufinfo->len, _write_cb, xTaskGetCurrentTaskHandle())); + int error_code; + xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200); + CHECK_BLE_ERROR(error_code); + } + } else { + // Validate data length for local characteristics only. + // TODO: Test this once we can get servers going. + if (self->fixed_length && bufinfo->len != self->max_length) { + mp_raise_ValueError(translate("Value length != required fixed length")); + } + if (bufinfo->len > self->max_length) { + mp_raise_ValueError(translate("Value length > max_length")); + } + + if (bufinfo == NULL) { + self->current_value_len = 0; + ble_gatts_chr_updated(self->handle); + return; + } + + self->current_value_len = bufinfo->len; + // If we've already allocated an internal buffer or the provided buffer + // is on the heap, then copy into the internal buffer. + if (self->current_value_alloc > 0 || gc_nbytes(bufinfo->buf) > 0) { + if (self->current_value_alloc < bufinfo->len) { + self->current_value = m_realloc(self->current_value, bufinfo->len); + // Get the number of bytes from the heap because it may be more + // than the len due to gc block size. + self->current_value_alloc = gc_nbytes(self->current_value); + } + memcpy(self->current_value, bufinfo->buf, bufinfo->len); + } else { + // Otherwise, use the provided buffer to delay any heap allocation. + self->current_value = bufinfo->buf; + self->current_value_alloc = 0; + } + + ble_gatts_chr_updated(self->handle); + } } bleio_uuid_obj_t *common_hal_bleio_characteristic_get_uuid(bleio_characteristic_obj_t *self) { @@ -118,10 +206,32 @@ bleio_characteristic_properties_t common_hal_bleio_characteristic_get_properties return self->props; } -void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self, bleio_descriptor_obj_t *descriptor) { +void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self, + bleio_descriptor_obj_t *descriptor) { // TODO: Implement this. + + mp_obj_list_append(MP_OBJ_FROM_PTR(self->descriptor_list), + MP_OBJ_FROM_PTR(descriptor)); } void common_hal_bleio_characteristic_set_cccd(bleio_characteristic_obj_t *self, bool notify, bool indicate) { - // TODO: Implement this. + if (self->cccd_handle == BLEIO_HANDLE_INVALID) { + mp_raise_bleio_BluetoothError(translate("No CCCD for this Characteristic")); + } + + if (!common_hal_bleio_service_get_is_remote(self->service)) { + mp_raise_bleio_RoleError(translate("Can't set CCCD on local Characteristic")); + } + + const uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection); + common_hal_bleio_check_connected(conn_handle); + + uint16_t cccd_value = + (notify ? 1 << 0 : 0) | + (indicate ? 1 << 1: 0); + + CHECK_NIMBLE_ERROR(ble_gattc_write_flat(conn_handle, self->cccd_handle, &cccd_value, 2, _write_cb, xTaskGetCurrentTaskHandle())); + int error_code; + xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200); + CHECK_BLE_ERROR(error_code); } diff --git a/ports/espressif/common-hal/_bleio/Characteristic.h b/ports/espressif/common-hal/_bleio/Characteristic.h index 57195ea265..ed812a9805 100644 --- a/ports/espressif/common-hal/_bleio/Characteristic.h +++ b/ports/espressif/common-hal/_bleio/Characteristic.h @@ -39,9 +39,13 @@ typedef struct _bleio_characteristic_obj { // Will be MP_OBJ_NULL before being assigned to a Service. bleio_service_obj_t *service; bleio_uuid_obj_t *uuid; - const uint8_t *initial_value; - uint16_t initial_value_len; + uint8_t *current_value; + uint16_t current_value_len; + // Our internal allocation length. If > 0, then current_value is managed by + // this characteristic. + uint16_t current_value_alloc; uint16_t max_length; + uint16_t def_handle; uint16_t handle; bleio_characteristic_properties_t props; bleio_attribute_security_mode_t read_perm; diff --git a/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c b/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c index bc6619cef4..fc3d0bbf06 100644 --- a/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c +++ b/ports/espressif/common-hal/_bleio/CharacteristicBuffer.c @@ -28,6 +28,7 @@ #include #include "shared/runtime/interrupt_char.h" +#include "py/ringbuf.h" #include "py/runtime.h" #include "py/stream.h" @@ -37,14 +38,38 @@ #include "common-hal/_bleio/CharacteristicBuffer.h" #include "shared-bindings/_bleio/CharacteristicBuffer.h" +STATIC int characteristic_buffer_on_ble_evt(struct ble_gap_event *event, void *param) { + bleio_characteristic_buffer_obj_t *self = (bleio_characteristic_buffer_obj_t *)param; + switch (event->type) { + case BLE_GAP_EVENT_NOTIFY_RX: { + // A remote service wrote to this characteristic. + + // Must be a notification, and event handle must match the handle for my characteristic. + if (event->notify_rx.indication == 0 && + event->notify_rx.attr_handle == self->characteristic->handle) { + const struct os_mbuf *m = event->notify_rx.om; + while (m != NULL) { + ringbuf_put_n(&self->ringbuf, m->om_data, m->om_len); + m = SLIST_NEXT(m, om_next); + } + } + break; + } + default: + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "Unhandled gap event %d\n", event->type); + #endif + return 0; + break; + } + return 0; +} + void _common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic, mp_float_t timeout, uint8_t *buffer, size_t buffer_size, void *static_handler_entry) { - - mp_raise_NotImplementedError(NULL); - self->characteristic = characteristic; self->timeout_ms = timeout * 1000; @@ -53,6 +78,11 @@ void _common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buff self->ringbuf.iget = 0; self->ringbuf.iput = 0; + if (static_handler_entry != NULL) { + ble_event_add_handler_entry((ble_event_handler_entry_t *)static_handler_entry, characteristic_buffer_on_ble_evt, self); + } else { + ble_event_add_handler(characteristic_buffer_on_ble_evt, self); + } } // Assumes that timeout and buffer_size have been validated before call. @@ -65,17 +95,32 @@ void common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffe } uint32_t common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self, uint8_t *data, size_t len, int *errcode) { - // TODO: Implement this. - return 0; + uint64_t start_ticks = supervisor_ticks_ms64(); + + // Wait for all bytes received or timeout + while ((ringbuf_num_filled(&self->ringbuf) < len) && (supervisor_ticks_ms64() - start_ticks < self->timeout_ms)) { + RUN_BACKGROUND_TASKS; + // Allow user to break out of a timeout with a KeyboardInterrupt. + if (mp_hal_is_interrupted()) { + return 0; + } + } + + uint32_t num_bytes_read = ringbuf_get_n(&self->ringbuf, data, len); + + return num_bytes_read; } +// NOTE: The nRF port has protection around these operations because the ringbuf +// is filled from an interrupt. On ESP the ringbuf is filled from the BLE host +// task that won't interrupt us. + uint32_t common_hal_bleio_characteristic_buffer_rx_characters_available(bleio_characteristic_buffer_obj_t *self) { - // TODO: Implement this. - return 0; + return ringbuf_num_filled(&self->ringbuf); } void common_hal_bleio_characteristic_buffer_clear_rx_buffer(bleio_characteristic_buffer_obj_t *self) { - // TODO: Implement this. + ringbuf_clear(&self->ringbuf); } bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer_obj_t *self) { @@ -83,7 +128,10 @@ bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer } void common_hal_bleio_characteristic_buffer_deinit(bleio_characteristic_buffer_obj_t *self) { - // TODO: Implement this. + if (!common_hal_bleio_characteristic_buffer_deinited(self)) { + ble_event_remove_handler(characteristic_buffer_on_ble_evt, self); + self->characteristic = NULL; + } } bool common_hal_bleio_characteristic_buffer_connected(bleio_characteristic_buffer_obj_t *self) { diff --git a/ports/espressif/common-hal/_bleio/Connection.c b/ports/espressif/common-hal/_bleio/Connection.c index a3668073a8..7dbcbd6e5f 100644 --- a/ports/espressif/common-hal/_bleio/Connection.c +++ b/ports/espressif/common-hal/_bleio/Connection.c @@ -48,6 +48,9 @@ #include "host/ble_att.h" +// Give 20 seconds for discovery +#define DISCOVERY_TIMEOUT_MS 20000 + int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) { bleio_connection_internal_t *connection = (bleio_connection_internal_t *)connection_in; @@ -69,9 +72,35 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) } case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: { + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "TODO connection event: PHY update complete\n"); + #endif break; } + case BLE_GAP_EVENT_CONN_UPDATE: { + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "TODO connection event: connection update\n"); + #endif + break; + } + case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: { + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "TODO connection event: l2cap update request\n"); + #endif + break; + } + + // These events are actually att specific so forward to all registered + // handlers for them. The handlers themselves decide whether an event + // is interesting to them. + case BLE_GAP_EVENT_NOTIFY_RX: + MP_FALLTHROUGH; + case BLE_GAP_EVENT_NOTIFY_TX: + MP_FALLTHROUGH; + case BLE_GAP_EVENT_SUBSCRIBE: + return ble_event_run_handlers(event); + default: #if CIRCUITPY_VERBOSE_BLE mp_printf(&mp_plat_print, "Unhandled connection event: %d\n", event->type); @@ -96,7 +125,7 @@ bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { } void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self) { - // TODO: Implement this. + ble_gap_terminate(self->conn_handle, BLE_ERR_REM_USER_CONN_TERM); } void common_hal_bleio_connection_pair(bleio_connection_internal_t *self, bool bond) { @@ -121,7 +150,265 @@ void common_hal_bleio_connection_set_connection_interval(bleio_connection_intern // TODO: Implement this. } +STATIC volatile int _last_discovery_status; +static TaskHandle_t discovery_task = NULL; + +STATIC int _discovered_service_cb(uint16_t conn_handle, + const struct ble_gatt_error *error, + const struct ble_gatt_svc *svc, + void *arg) { + bleio_connection_internal_t *self = (bleio_connection_internal_t *)arg; + + if (error->status != BLE_ERR_SUCCESS) { + // Keep the first error in case it's due to memory. + if (_last_discovery_status == BLE_ERR_SUCCESS) { + _last_discovery_status = error->status; + xTaskNotifyGive(discovery_task); + } + return 0; + } + + // If any of these memory allocations fail, we set _last_discovery_status + // and let the process continue. + if (_last_discovery_status != BLE_ERR_SUCCESS) { + return 0; + } + bleio_service_obj_t *service = m_new_obj(bleio_service_obj_t); + if (service == NULL) { + _last_discovery_status = BLE_ERR_MEM_CAPACITY; + return 0; + } + service->base.type = &bleio_service_type; + + // Initialize several fields at once. + bleio_service_from_connection(service, bleio_connection_new_from_internal(self)); + + service->is_remote = true; + service->start_handle = svc->start_handle; + service->end_handle = svc->end_handle; + service->handle = svc->start_handle; + + bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t); + if (uuid == NULL) { + _last_discovery_status = BLE_ERR_MEM_CAPACITY; + return 0; + } + uuid->base.type = &bleio_uuid_type; + uuid->nimble_ble_uuid = svc->uuid; + service->uuid = uuid; + + mp_obj_list_append(MP_OBJ_FROM_PTR(self->remote_service_list), + MP_OBJ_FROM_PTR(service)); + return 0; +} + +STATIC int _discovered_characteristic_cb(uint16_t conn_handle, + const struct ble_gatt_error *error, + const struct ble_gatt_chr *chr, + void *arg) { + bleio_service_obj_t *service = (bleio_service_obj_t *)arg; + + if (error->status != BLE_ERR_SUCCESS) { + // Keep the first error in case it's due to memory. + if (_last_discovery_status == BLE_ERR_SUCCESS) { + _last_discovery_status = error->status; + xTaskNotifyGive(discovery_task); + } + } + // If any of these memory allocations fail, we set _last_discovery_status + // and let the process continue. + if (_last_discovery_status != BLE_ERR_SUCCESS) { + return 0; + } + + bleio_characteristic_obj_t *characteristic = m_new_obj(bleio_characteristic_obj_t); + if (characteristic == NULL) { + _last_discovery_status = BLE_ERR_MEM_CAPACITY; + return 0; + } + characteristic->base.type = &bleio_characteristic_type; + + // Known characteristic UUID. + bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t); + if (uuid == NULL) { + _last_discovery_status = BLE_ERR_MEM_CAPACITY; + return 0; + } + uuid->base.type = &bleio_uuid_type; + uuid->nimble_ble_uuid = chr->uuid; + + bleio_characteristic_properties_t props = + ((chr->properties & BLE_GATT_CHR_PROP_BROADCAST) != 0 ? CHAR_PROP_BROADCAST : 0) | + ((chr->properties & BLE_GATT_CHR_PROP_INDICATE) != 0 ? CHAR_PROP_INDICATE : 0) | + ((chr->properties & BLE_GATT_CHR_PROP_NOTIFY) != 0 ? CHAR_PROP_NOTIFY : 0) | + ((chr->properties & BLE_GATT_CHR_PROP_READ) != 0 ? CHAR_PROP_READ : 0) | + ((chr->properties & BLE_GATT_CHR_PROP_WRITE) != 0 ? CHAR_PROP_WRITE : 0) | + ((chr->properties & BLE_GATT_CHR_PROP_WRITE_NO_RSP) != 0 ? CHAR_PROP_WRITE_NO_RESPONSE : 0); + + // Call common_hal_bleio_characteristic_construct() to initalize some fields and set up evt handler. + common_hal_bleio_characteristic_construct( + characteristic, service, chr->val_handle, uuid, + props, SECURITY_MODE_OPEN, SECURITY_MODE_OPEN, + 0, false, // max_length, fixed_length: values don't matter for gattc + mp_const_empty_bytes, + NULL); + // Set def_handle directly since it is only used in discovery. + characteristic->def_handle = chr->def_handle; + + mp_obj_list_append(MP_OBJ_FROM_PTR(service->characteristic_list), + MP_OBJ_FROM_PTR(characteristic)); + return 0; +} + +STATIC int _discovered_descriptor_cb(uint16_t conn_handle, + const struct ble_gatt_error *error, + uint16_t chr_val_handle, + const struct ble_gatt_dsc *dsc, + void *arg) { + bleio_characteristic_obj_t *characteristic = (bleio_characteristic_obj_t *)arg; + + if (error->status != BLE_ERR_SUCCESS) { + // Keep the first error in case it's due to memory. + if (_last_discovery_status == BLE_ERR_SUCCESS) { + _last_discovery_status = error->status; + } + xTaskNotifyGive(discovery_task); + } + // If any of these memory allocations fail, we set _last_discovery_status + // and let the process continue. + if (_last_discovery_status != BLE_ERR_SUCCESS) { + return 0; + } + + // Remember handles for certain well-known descriptors. + switch (dsc->uuid.u16.value) { + case 0x2902: + characteristic->cccd_handle = dsc->handle; + break; + + case 0x2903: + characteristic->sccd_handle = dsc->handle; + break; + + case 0x2901: + characteristic->user_desc_handle = dsc->handle; + break; + + default: + break; + } + + bleio_descriptor_obj_t *descriptor = m_new_obj(bleio_descriptor_obj_t); + if (descriptor == NULL) { + _last_discovery_status = BLE_ERR_MEM_CAPACITY; + return 0; + } + descriptor->base.type = &bleio_descriptor_type; + + bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t); + if (uuid == NULL) { + _last_discovery_status = BLE_ERR_MEM_CAPACITY; + return 0; + } + uuid->base.type = &bleio_uuid_type; + uuid->nimble_ble_uuid = dsc->uuid; + + common_hal_bleio_descriptor_construct( + descriptor, characteristic, uuid, + SECURITY_MODE_OPEN, SECURITY_MODE_OPEN, + 0, false, mp_const_empty_bytes); + descriptor->handle = dsc->handle; + + mp_obj_list_append(MP_OBJ_FROM_PTR(characteristic->descriptor_list), + MP_OBJ_FROM_PTR(descriptor)); + return 0; +} + +STATIC void discover_remote_services(bleio_connection_internal_t *self, mp_obj_t service_uuids_whitelist) { + // Start over with an empty list. + self->remote_service_list = mp_obj_new_list(0, NULL); + + discovery_task = xTaskGetCurrentTaskHandle(); + if (service_uuids_whitelist == mp_const_none) { + _last_discovery_status = BLE_ERR_SUCCESS; + CHECK_NIMBLE_ERROR(ble_gattc_disc_all_svcs(self->conn_handle, _discovered_service_cb, self)); + + // Wait for sync. + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(DISCOVERY_TIMEOUT_MS)); + if (_last_discovery_status != BLE_HS_EDONE) { + CHECK_BLE_ERROR(_last_discovery_status); + } + } else { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(service_uuids_whitelist, &iter_buf); + mp_obj_t uuid_obj; + while ((uuid_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if (!mp_obj_is_type(uuid_obj, &bleio_uuid_type)) { + mp_raise_TypeError(translate("non-UUID found in service_uuids_whitelist")); + } + bleio_uuid_obj_t *uuid = MP_OBJ_TO_PTR(uuid_obj); + + _last_discovery_status = BLE_ERR_SUCCESS; + // Make sure we start with a clean notification state + ulTaskNotifyValueClear(discovery_task, 0xffffffff); + CHECK_NIMBLE_ERROR(ble_gattc_disc_svc_by_uuid(self->conn_handle, &uuid->nimble_ble_uuid.u, + _discovered_service_cb, self)); + // Wait for sync. + CHECK_NOTIFY(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(DISCOVERY_TIMEOUT_MS))); + if (_last_discovery_status != BLE_HS_EDONE) { + CHECK_BLE_ERROR(_last_discovery_status); + } + } + } + + for (size_t i = 0; i < self->remote_service_list->len; i++) { + bleio_service_obj_t *service = MP_OBJ_TO_PTR(self->remote_service_list->items[i]); + + _last_discovery_status = BLE_ERR_SUCCESS; + CHECK_NIMBLE_ERROR(ble_gattc_disc_all_chrs(self->conn_handle, + service->start_handle, + service->end_handle, + _discovered_characteristic_cb, + service)); + + // Wait for sync. + CHECK_NOTIFY(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(DISCOVERY_TIMEOUT_MS))); + if (_last_discovery_status != BLE_HS_EDONE) { + CHECK_BLE_ERROR(_last_discovery_status); + } + + // Got characteristics for this service. Now discover descriptors for each characteristic. + size_t char_list_len = service->characteristic_list->len; + for (size_t char_idx = 0; char_idx < char_list_len; ++char_idx) { + bleio_characteristic_obj_t *characteristic = + MP_OBJ_TO_PTR(service->characteristic_list->items[char_idx]); + // Determine the handle range for the given characteristic's descriptors. + // The end of the range is dictated by the next characteristic or the end + // handle of the service. + const bool last_characteristic = char_idx == char_list_len - 1; + bleio_characteristic_obj_t *next_characteristic = last_characteristic + ? NULL + : MP_OBJ_TO_PTR(service->characteristic_list->items[char_idx + 1]); + + uint16_t end_handle = next_characteristic == NULL + ? service->end_handle + : next_characteristic->def_handle - 1; + + _last_discovery_status = BLE_ERR_SUCCESS; + CHECK_NIMBLE_ERROR(ble_gattc_disc_all_dscs(self->conn_handle, characteristic->handle, + end_handle, + _discovered_descriptor_cb, characteristic)); + // Wait for sync. + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(DISCOVERY_TIMEOUT_MS)); + if (_last_discovery_status != BLE_HS_EDONE) { + CHECK_BLE_ERROR(_last_discovery_status); + } + } + } +} + mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist) { + discover_remote_services(self->connection, service_uuids_whitelist); bleio_connection_ensure_connected(self); // Convert to a tuple and then clear the list so the callee will take ownership. mp_obj_tuple_t *services_tuple = @@ -129,7 +416,6 @@ mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_conne self->connection->remote_service_list->items); mp_obj_list_clear(MP_OBJ_FROM_PTR(self->connection->remote_service_list)); - // TODO: Implement this. return services_tuple; } diff --git a/ports/espressif/common-hal/_bleio/Descriptor.c b/ports/espressif/common-hal/_bleio/Descriptor.c index 016626dbf9..31bdca51b0 100644 --- a/ports/espressif/common-hal/_bleio/Descriptor.c +++ b/ports/espressif/common-hal/_bleio/Descriptor.c @@ -36,7 +36,6 @@ #include "host/ble_att.h" void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_characteristic_obj_t *characteristic, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo) { - mp_raise_NotImplementedError(NULL); self->characteristic = characteristic; self->uuid = uuid; self->handle = BLEIO_HANDLE_INVALID; diff --git a/ports/espressif/common-hal/_bleio/Descriptor.h b/ports/espressif/common-hal/_bleio/Descriptor.h index 4a2b298ca2..e52251ce82 100644 --- a/ports/espressif/common-hal/_bleio/Descriptor.h +++ b/ports/espressif/common-hal/_bleio/Descriptor.h @@ -33,6 +33,8 @@ #include "common-hal/_bleio/UUID.h" +#include "host/ble_gatt.h" + // Forward declare characteristic because it includes a Descriptor. struct _bleio_characteristic_obj; @@ -45,6 +47,7 @@ typedef struct _bleio_descriptor_obj { uint16_t max_length; bool fixed_length; uint16_t handle; + struct ble_gatt_dsc_def def; bleio_attribute_security_mode_t read_perm; bleio_attribute_security_mode_t write_perm; } bleio_descriptor_obj_t; diff --git a/ports/espressif/common-hal/_bleio/PacketBuffer.c b/ports/espressif/common-hal/_bleio/PacketBuffer.c index b318ad27d7..4e65bf309c 100644 --- a/ports/espressif/common-hal/_bleio/PacketBuffer.c +++ b/ports/espressif/common-hal/_bleio/PacketBuffer.c @@ -40,13 +40,113 @@ #include "host/ble_att.h" +STATIC void write_to_ringbuf(bleio_packet_buffer_obj_t *self, const struct os_mbuf *mbuf) { + size_t len = OS_MBUF_PKTLEN(mbuf); + if (len + sizeof(uint16_t) > ringbuf_capacity(&self->ringbuf)) { + // This shouldn't happen but can if our buffer size was much smaller than + // the writes the client actually makes. + return; + } + // Make room for the new value by dropping the oldest packets first. + while (ringbuf_capacity(&self->ringbuf) - ringbuf_num_filled(&self->ringbuf) < len + sizeof(uint16_t)) { + uint16_t packet_length; + ringbuf_get_n(&self->ringbuf, (uint8_t *)&packet_length, sizeof(uint16_t)); + for (uint16_t i = 0; i < packet_length; i++) { + ringbuf_get(&self->ringbuf); + } + // set an overflow flag? + } + ringbuf_put_n(&self->ringbuf, (uint8_t *)&len, sizeof(uint16_t)); + while (mbuf != NULL) { + ringbuf_put_n(&self->ringbuf, mbuf->om_data, mbuf->om_len); + mbuf = SLIST_NEXT(mbuf, om_next); + } +} + +STATIC int packet_buffer_on_ble_client_evt(struct ble_gap_event *event, void *param); +STATIC int queue_next_write(bleio_packet_buffer_obj_t *self); + +STATIC int _write_cb(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) { + if (error->status != 0) { + mp_printf(&mp_plat_print, "write failed %d\n", error->status); + } + bleio_packet_buffer_obj_t *self = (bleio_packet_buffer_obj_t *)arg; + queue_next_write(self); + + return 0; +} + +STATIC int queue_next_write(bleio_packet_buffer_obj_t *self) { + // Queue up the next outgoing buffer. We use two, one that has been passed to the SD for + // transmission (when packet_queued is true) and the other is `pending` and can still be + // modified. By primarily appending to the `pending` buffer we can reduce the protocol overhead + // of the lower level link and ATT layers. + self->packet_queued = false; + if (self->pending_size > 0) { + uint16_t conn_handle = self->conn_handle; + int err_code = NIMBLE_OK; + if (self->client) { + if (self->write_type == CHAR_PROP_WRITE_NO_RESPONSE) { + err_code = ble_gattc_write_no_rsp_flat(conn_handle, + self->characteristic->handle, + self->outgoing[self->pending_index], + self->pending_size); + // We don't set packet_queued because we NimBLE will buffer our + // outgoing packets. + } else { + err_code = ble_gattc_write_flat(conn_handle, + self->characteristic->handle, + self->outgoing[self->pending_index], + self->pending_size, + _write_cb, self); + self->pending_index = (self->pending_index + 1) % 2; + self->packet_queued = true; + } + self->pending_size = 0; + } else { + // TODO: Notify because we're the server. + } + if (err_code != NIMBLE_OK) { + // On error, simply skip updating the pending buffers so that the next HVC or WRITE + // complete event triggers another attempt. + return err_code; + } + } + return NIMBLE_OK; +} + +STATIC int packet_buffer_on_ble_client_evt(struct ble_gap_event *event, void *param) { + bleio_packet_buffer_obj_t *self = (bleio_packet_buffer_obj_t *)param; + if (event->type == BLE_GAP_EVENT_DISCONNECT && self->conn_handle == event->disconnect.conn.conn_handle) { + self->conn_handle = BLEIO_HANDLE_INVALID; + } + + switch (event->type) { + case BLE_GAP_EVENT_NOTIFY_RX: { + if (event->notify_rx.conn_handle != self->conn_handle) { + return false; + } + // Must be a notification, and event handle must match the handle for my characteristic. + if (event->notify_rx.attr_handle == self->characteristic->handle) { + write_to_ringbuf(self, event->notify_rx.om); + } + break; + } + default: + return false; + break; + } + return true; +} + void _common_hal_bleio_packet_buffer_construct( bleio_packet_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic, uint32_t *incoming_buffer, size_t incoming_buffer_size, uint32_t *outgoing_buffer1, uint32_t *outgoing_buffer2, size_t max_packet_size, void *static_handler_entry) { - - mp_raise_NotImplementedError(NULL); self->characteristic = characteristic; self->client = self->characteristic->service->is_remote; self->max_packet_size = max_packet_size; @@ -76,6 +176,29 @@ void _common_hal_bleio_packet_buffer_construct( self->outgoing[0] = outgoing_buffer1; self->outgoing[1] = outgoing_buffer2; + if (self->client) { + if (static_handler_entry != NULL) { + ble_event_add_handler_entry((ble_event_handler_entry_t *)static_handler_entry, packet_buffer_on_ble_client_evt, self); + } else { + ble_event_add_handler(packet_buffer_on_ble_client_evt, self); + } + if (incoming) { + // Prefer notify if both are available. + if (incoming & CHAR_PROP_NOTIFY) { + common_hal_bleio_characteristic_set_cccd(self->characteristic, true, false); + } else { + common_hal_bleio_characteristic_set_cccd(self->characteristic, false, true); + } + } + if (outgoing) { + self->write_type = CHAR_PROP_WRITE; + if (outgoing & CHAR_PROP_WRITE_NO_RESPONSE) { + self->write_type = CHAR_PROP_WRITE_NO_RESPONSE; + } + } + } else { + // TODO: Setup for server. + } } void common_hal_bleio_packet_buffer_construct( @@ -104,7 +227,12 @@ void common_hal_bleio_packet_buffer_construct( uint32_t *outgoing2 = NULL; if (outgoing) { outgoing1 = m_malloc(max_packet_size, false); - outgoing2 = m_malloc(max_packet_size, false); + // Only allocate the second buffer if we are doing writes with responses. + // Without responses, we just write as quickly as we can. + if (outgoing == CHAR_PROP_WRITE) { + outgoing2 = m_malloc(max_packet_size, false); + } + } _common_hal_bleio_packet_buffer_construct(self, characteristic, incoming_buffer, incoming_buffer_size, @@ -117,9 +245,25 @@ mp_int_t common_hal_bleio_packet_buffer_readinto(bleio_packet_buffer_obj_t *self return 0; } - // Copy received data. Lock out write interrupt handler while copying. - // TODO: Implement this. - return 0; + // Get packet length, which is in first two bytes of packet. + uint16_t packet_length; + ringbuf_get_n(&self->ringbuf, (uint8_t *)&packet_length, sizeof(uint16_t)); + + mp_int_t ret; + if (packet_length > len) { + // Packet is longer than requested. Return negative of overrun value. + ret = len - packet_length; + // Discard the packet if it's too large. Don't fill data. + while (packet_length--) { + (void)ringbuf_get(&self->ringbuf); + } + } else { + // Read as much as possible, but might be shorter than len. + ringbuf_get_n(&self->ringbuf, data, packet_length); + ret = packet_length; + } + + return ret; } mp_int_t common_hal_bleio_packet_buffer_write(bleio_packet_buffer_obj_t *self, const uint8_t *data, size_t len, uint8_t *header, size_t header_len) { @@ -172,10 +316,9 @@ mp_int_t common_hal_bleio_packet_buffer_write(bleio_packet_buffer_obj_t *self, c self->pending_size += len; num_bytes_written += len; - // TODO: Implement this. - // If no writes are queued then sneak in this data. if (!self->packet_queued) { + CHECK_NIMBLE_ERROR(queue_next_write(self)); } return num_bytes_written; } @@ -270,6 +413,6 @@ bool common_hal_bleio_packet_buffer_deinited(bleio_packet_buffer_obj_t *self) { void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self) { if (!common_hal_bleio_packet_buffer_deinited(self)) { + ble_event_remove_handler(packet_buffer_on_ble_client_evt, self); } - // TODO: Implement this. } diff --git a/ports/espressif/common-hal/_bleio/Service.c b/ports/espressif/common-hal/_bleio/Service.c index beaf930136..7aff2905e3 100644 --- a/ports/espressif/common-hal/_bleio/Service.c +++ b/ports/espressif/common-hal/_bleio/Service.c @@ -41,12 +41,6 @@ uint32_t _common_hal_bleio_service_construct(bleio_service_obj_t *self, bleio_uu self->is_remote = false; self->connection = NULL; self->is_secondary = is_secondary; - - // uint8_t service_type = BLE_GATT_SVC_TYPE_PRIMARY; - // if (is_secondary) { - // service_type = BLE_GATT_SVC_TYPE_SECONDARY; - // } - return 0; } @@ -57,7 +51,7 @@ void common_hal_bleio_service_construct(bleio_service_obj_t *self, bleio_uuid_ob } void bleio_service_from_connection(bleio_service_obj_t *self, mp_obj_t connection) { - self->handle = 0xFFFF; + self->handle = BLEIO_HANDLE_INVALID; self->uuid = NULL; self->characteristic_list = mp_obj_new_list(0, NULL); self->is_remote = true; diff --git a/ports/espressif/common-hal/_bleio/__init__.c b/ports/espressif/common-hal/_bleio/__init__.c index bd10b0a519..7eba47d6a1 100644 --- a/ports/espressif/common-hal/_bleio/__init__.c +++ b/ports/espressif/common-hal/_bleio/__init__.c @@ -40,6 +40,7 @@ #include "common-hal/_bleio/__init__.h" // #include "common-hal/_bleio/bonding.h" +#include "common-hal/_bleio/ble_events.h" // Turn off BLE on a reset or reload. void bleio_reset() { @@ -50,6 +51,7 @@ void bleio_reset() { } supervisor_stop_bluetooth(); + ble_event_reset(); bleio_adapter_reset(&common_hal_bleio_adapter_obj); common_hal_bleio_adapter_set_enabled(&common_hal_bleio_adapter_obj, false); supervisor_start_bluetooth(); @@ -97,3 +99,36 @@ void check_nimble_error(int rc, const char *file, size_t line) { break; } } + +void check_ble_error(int error_code, const char *file, size_t line) { + if (error_code == BLE_ERR_SUCCESS) { + return; + } + switch (error_code) { + default: + #if CIRCUITPY_VERBOSE_BLE + if (file) { + mp_raise_bleio_BluetoothError(translate("Unknown BLE error at %s:%d: %d"), file, line, error_code); + } + #else + (void)file; + (void)line; + mp_raise_bleio_BluetoothError(translate("Unknown BLE error: %d"), error_code); + #endif + + break; + } +} + +void check_notify(BaseType_t result) { + if (result == pdTRUE) { + return; + } + mp_raise_msg(&mp_type_TimeoutError, NULL); +} + +void common_hal_bleio_check_connected(uint16_t conn_handle) { + if (conn_handle == BLEIO_HANDLE_INVALID) { + mp_raise_ConnectionError(translate("Not connected")); + } +} diff --git a/ports/espressif/common-hal/_bleio/__init__.h b/ports/espressif/common-hal/_bleio/__init__.h index 6c32bd5d4e..41e9792603 100644 --- a/ports/espressif/common-hal/_bleio/__init__.h +++ b/ports/espressif/common-hal/_bleio/__init__.h @@ -27,6 +27,8 @@ #ifndef MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_BLEIO_INIT_H #define MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_BLEIO_INIT_H +#include "FreeRTOS.h" + void bleio_background(void); // typedef struct { @@ -43,6 +45,10 @@ void bleio_background(void); void check_nimble_error(int rc, const char *file, size_t line); #define CHECK_NIMBLE_ERROR(rc) check_nimble_error(rc, __FILE__, __LINE__) +void check_ble_error(int error_code, const char *file, size_t line); +#define CHECK_BLE_ERROR(error_code) check_ble_error(error_code, __FILE__, __LINE__) +void check_notify(BaseType_t result); +#define CHECK_NOTIFY(result) check_notify(result) #define MSEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000) / (RESOLUTION)) #define SEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000000) / (RESOLUTION)) @@ -51,6 +57,7 @@ void check_nimble_error(int rc, const char *file, size_t line); #define ADV_INTERVAL_UNIT_FLOAT_SECS (0.000625) // Microseconds is the base unit. The macros above know that. #define UNIT_0_625_MS (625) +#define UNIT_1_MS (1000) #define UNIT_1_25_MS (1250) #define UNIT_10_MS (10000) diff --git a/ports/espressif/common-hal/_bleio/ble_events.c b/ports/espressif/common-hal/_bleio/ble_events.c new file mode 100644 index 0000000000..1ccd72cb61 --- /dev/null +++ b/ports/espressif/common-hal/_bleio/ble_events.c @@ -0,0 +1,116 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Dan Halbert for Adafruit Industries + * Copyright (c) 2018 Artur Pacholec + * Copyright (c) 2016 Glenn Ruben Bakke + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "common-hal/_bleio/ble_events.h" + +#include +#include + +#include "py/misc.h" +#include "py/mpstate.h" +#include "py/runtime.h" + +#if CIRCUITPY_SERIAL_BLE && CIRCUITPY_VERBOSE_BLE +#include "supervisor/shared/bluetooth/serial.h" +#endif + +void ble_event_reset(void) { + // Linked list items will be gc'd. + MP_STATE_VM(ble_event_handler_entries) = NULL; +} + +void ble_event_add_handler_entry(ble_event_handler_entry_t *entry, + ble_gap_event_fn *func, void *param) { + ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries); + while (it != NULL) { + // If event handler and its corresponding param are already on the list, don't add again. + if ((it->func == func) && (it->param == param)) { + return; + } + it = it->next; + } + entry->next = MP_STATE_VM(ble_event_handler_entries); + entry->param = param; + entry->func = func; + + MP_STATE_VM(ble_event_handler_entries) = entry; +} + +void ble_event_add_handler(ble_gap_event_fn *func, void *param) { + ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries); + while (it != NULL) { + // If event handler and its corresponding param are already on the list, don't add again. + if ((it->func == func) && (it->param == param)) { + return; + } + it = it->next; + } + + // Add a new handler to the front of the list + ble_event_handler_entry_t *handler = m_new_ll(ble_event_handler_entry_t, 1); + ble_event_add_handler_entry(handler, func, param); +} + +void ble_event_remove_handler(ble_gap_event_fn *func, void *param) { + ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries); + ble_event_handler_entry_t **prev = &MP_STATE_VM(ble_event_handler_entries); + while (it != NULL) { + if ((it->func == func) && (it->param == param)) { + // Splice out the matching handler. + *prev = it->next; + // Clear next of the removed node so it's clearly not in a list. + it->next = NULL; + return; + } + prev = &(it->next); + it = it->next; + } +} + +int ble_event_run_handlers(struct ble_gap_event *event) { + #if CIRCUITPY_SERIAL_BLE && CIRCUITPY_VERBOSE_BLE + ble_serial_disable(); + #endif + + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "BLE GAP event: 0x%04x\n", event->type); + #endif + + ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries); + bool done = false; + while (it != NULL) { + // Capture next before calling the function in case it removes itself from the list. + ble_event_handler_entry_t *next = it->next; + done = it->func(event, it->param) || done; + it = next; + } + #if CIRCUITPY_SERIAL_BLE && CIRCUITPY_VERBOSE_BLE + ble_serial_enable(); + #endif + return 0; +} diff --git a/ports/espressif/common-hal/_bleio/ble_events.h b/ports/espressif/common-hal/_bleio/ble_events.h new file mode 100644 index 0000000000..03ff351118 --- /dev/null +++ b/ports/espressif/common-hal/_bleio/ble_events.h @@ -0,0 +1,51 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Dan Halbert for Adafruit Industries + * Copyright (c) 2018 Artur Pacholec + * Copyright (c) 2016 Glenn Ruben Bakke + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL__BLEIO_BLE_EVENTS_H +#define MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL__BLEIO_BLE_EVENTS_H + +#include + +#include "host/ble_gap.h" + +typedef struct ble_event_handler_entry { + struct ble_event_handler_entry *next; + void *param; + ble_gap_event_fn *func; +} ble_event_handler_entry_t; + +void ble_event_reset(void); +void ble_event_add_handler(ble_gap_event_fn *func, void *param); +void ble_event_remove_handler(ble_gap_event_fn *func, void *param); + +// Allow for user provided entries to prevent allocations outside the VM. +void ble_event_add_handler_entry(ble_event_handler_entry_t *entry, ble_gap_event_fn *func, void *param); + +int ble_event_run_handlers(struct ble_gap_event *event); + +#endif // MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL__BLEIO_BLE_EVENTS_H diff --git a/ports/espressif/mpconfigport.h b/ports/espressif/mpconfigport.h index 62e37d9374..1c5f1b1463 100644 --- a/ports/espressif/mpconfigport.h +++ b/ports/espressif/mpconfigport.h @@ -35,8 +35,19 @@ #include "py/circuitpy_mpconfig.h" +#if CIRCUITPY_BLEIO +#include "common-hal/_bleio/ble_events.h" +#endif + +#if CIRCUITPY_BLEIO +#define MICROPY_PORT_ROOT_POINTERS \ + CIRCUITPY_COMMON_ROOT_POINTERS \ + ble_event_handler_entry_t *ble_event_handler_entries; +#else #define MICROPY_PORT_ROOT_POINTERS \ CIRCUITPY_COMMON_ROOT_POINTERS +#endif + #define MICROPY_NLR_SETJMP (1) #define CIRCUITPY_DEFAULT_STACK_SIZE 0x6000 @@ -61,4 +72,5 @@ #ifndef CIRCUITPY_I2C_ALLOW_INTERNAL_PULL_UP #define CIRCUITPY_I2C_ALLOW_INTERNAL_PULL_UP (0) #endif + #endif // MICROPY_INCLUDED_ESPRESSIF_MPCONFIGPORT_H diff --git a/ports/nrf/common-hal/_bleio/CharacteristicBuffer.c b/ports/nrf/common-hal/_bleio/CharacteristicBuffer.c index df389df0f8..5772616ee5 100644 --- a/ports/nrf/common-hal/_bleio/CharacteristicBuffer.c +++ b/ports/nrf/common-hal/_bleio/CharacteristicBuffer.c @@ -158,6 +158,7 @@ bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer void common_hal_bleio_characteristic_buffer_deinit(bleio_characteristic_buffer_obj_t *self) { if (!common_hal_bleio_characteristic_buffer_deinited(self)) { ble_drv_remove_event_handler(characteristic_buffer_on_ble_evt, self); + self->characteristic = NULL; } } diff --git a/shared-module/vectorio/VectorShape.c b/shared-module/vectorio/VectorShape.c index 52d3354946..04213d36e7 100644 --- a/shared-module/vectorio/VectorShape.c +++ b/shared-module/vectorio/VectorShape.c @@ -4,6 +4,7 @@ #include "shared-module/vectorio/__init__.h" #include "shared-bindings/vectorio/VectorShape.h" +#include "py/misc.h" #include "py/runtime.h" #include "shared-bindings/time/__init__.h" #include "shared-bindings/displayio/ColorConverter.h" @@ -61,17 +62,6 @@ (u32 & 0x2 ? '1' : '0'), \ (u32 & 0x1 ? '1' : '0') - -inline __attribute__((always_inline)) -static int32_t max(int32_t a, int32_t b) { - return a > b ? a : b; -} - -inline __attribute__((always_inline)) -static uint32_t min(uint32_t a, uint32_t b) { - return a < b ? a : b; -} - static void short_bound_check(mp_int_t i, qstr name) { if (i < SHRT_MIN || i > SHRT_MAX) { mp_raise_ValueError_varg(translate("%q must be between %d and %d"), name, SHRT_MIN, SHRT_MAX); @@ -456,7 +446,7 @@ bool vectorio_vector_shape_fill_area(vectorio_vector_shape_t *self, const _displ mp_obj_get_type_str(self->ishape.shape), (overlap.x2 - overlap.x1) * (overlap.y2 - overlap.y1), (double)((end - start) / 1000000.0), - (double)(max(1, pixels * (1000000000.0 / (end - start)))), + (double)(MAX(1, pixels * (1000000000.0 / (end - start)))), (double)(pixel_time / 1000.0), (double)(pixel_time / 1000.0 / pixels) ); @@ -514,7 +504,7 @@ displayio_area_t *vectorio_vector_shape_get_refresh_areas(vectorio_vector_shape_ union_size, dirty_size, current_size, overlap_size, (int32_t)union_size - dirty_size - current_size + overlap_size ); - if ((int32_t)union_size - dirty_size - current_size + overlap_size <= min(dirty_size, current_size)) { + if ((int32_t)union_size - dirty_size - current_size + overlap_size <= MIN(dirty_size, current_size)) { // The excluded / non-overlapping area from the disjoint dirty and current areas is smaller // than the smallest area we need to draw. Redrawing the overlapping area would cost more // than just drawing the union disjoint area once.