diff options
| author | Scott Shawcroft <scott@tannewt.org> | 2019-09-14 12:40:24 -0700 |
|---|---|---|
| committer | Scott Shawcroft <scott@tannewt.org> | 2019-10-21 18:57:03 -0700 |
| commit | ae30a1e5aa198bdeee7dce04194b027ff4e3d65a (patch) | |
| tree | d760570174d554598113c9349c14e34d8f710246 /ports | |
| parent | 84c0d6cdf8c0bff1b61ce2fa6edcd2b4a65b2556 (diff) | |
Refine _bleio
This PR refines the _bleio API. It was originally motivated by
the addition of a new CircuitPython service that enables reading
and modifying files on the device. Moving the BLE lifecycle outside
of the VM motivated a number of changes to remove heap allocations
in some APIs.
It also motivated unifying connection initiation to the Adapter class
rather than the Central and Peripheral classes which have been removed.
Adapter now handles the GAP portion of BLE including advertising, which
has moved but is largely unchanged, and scanning, which has been enhanced
to return an iterator of filtered results.
Once a connection is created (either by us (aka Central) or a remote
device (aka Peripheral)) it is represented by a new Connection class.
This class knows the current connection state and can discover and
instantiate remote Services along with their Characteristics and
Descriptors.
Relates to #586
Diffstat (limited to 'ports')
23 files changed, 1433 insertions, 1312 deletions
diff --git a/ports/nrf/background.c b/ports/nrf/background.c index 305f607c5..629967b3d 100644 --- a/ports/nrf/background.c +++ b/ports/nrf/background.c @@ -41,6 +41,10 @@ #include "common-hal/audiopwmio/PWMAudioOut.h" #endif +#if CIRCUITPY_BLEIO +#include "supervisor/shared/bluetooth.h" +#endif + static bool running_background_tasks = false; void background_tasks_reset(void) { @@ -62,6 +66,9 @@ void run_background_tasks(void) { i2s_background(); #endif +#if CIRCUITPY_BLEIO + supervisor_bluetooth_background(); +#endif #if CIRCUITPY_DISPLAYIO displayio_background(); diff --git a/ports/nrf/bluetooth/ble_drv.c b/ports/nrf/bluetooth/ble_drv.c index 6b17e7af2..16475e4b3 100644 --- a/ports/nrf/bluetooth/ble_drv.c +++ b/ports/nrf/bluetooth/ble_drv.c @@ -38,6 +38,8 @@ #include "py/misc.h" #include "py/mpstate.h" +#include "supervisor/shared/bluetooth.h" + nrf_nvic_state_t nrf_nvic_state = { 0 }; // Flag indicating progress of internal flash operation. @@ -52,6 +54,14 @@ void ble_drv_reset() { sd_flash_operation_status = SD_FLASH_OPERATION_DONE; } +void ble_drv_add_event_handler_entry(ble_drv_evt_handler_entry_t* entry, ble_drv_evt_handler_t func, void *param) { + entry->next = MP_STATE_VM(ble_drv_evt_handler_entries); + entry->param = param; + entry->func = func; + + MP_STATE_VM(ble_drv_evt_handler_entries) = entry; +} + void ble_drv_add_event_handler(ble_drv_evt_handler_t func, void *param) { ble_drv_evt_handler_entry_t *it = MP_STATE_VM(ble_drv_evt_handler_entries); while (it != NULL) { @@ -64,11 +74,7 @@ void ble_drv_add_event_handler(ble_drv_evt_handler_t func, void *param) { // Add a new handler to the front of the list ble_drv_evt_handler_entry_t *handler = m_new_ll(ble_drv_evt_handler_entry_t, 1); - handler->next = MP_STATE_VM(ble_drv_evt_handler_entries); - handler->param = param; - handler->func = func; - - MP_STATE_VM(ble_drv_evt_handler_entries) = handler; + ble_drv_add_event_handler_entry(handler, func, param); } void ble_drv_remove_event_handler(ble_drv_evt_handler_t func, void *param) { @@ -127,10 +133,20 @@ void SD_EVT_IRQHandler(void) { break; } + ble_evt_t* event = (ble_evt_t *)m_ble_evt_buf; + + if (supervisor_bluetooth_hook(event)) { + continue; + } + ble_drv_evt_handler_entry_t *it = MP_STATE_VM(ble_drv_evt_handler_entries); + bool done = false; while (it != NULL) { - it->func((ble_evt_t *)m_ble_evt_buf, it->param); + done = it->func(event, it->param) || done; it = it->next; } + if (!done) { + //mp_printf(&mp_plat_print, "Unhandled ble event: 0x%04x\n", event->header.evt_id); + } } } diff --git a/ports/nrf/bluetooth/ble_drv.h b/ports/nrf/bluetooth/ble_drv.h index a066f588f..7716cab8b 100644 --- a/ports/nrf/bluetooth/ble_drv.h +++ b/ports/nrf/bluetooth/ble_drv.h @@ -29,6 +29,8 @@ #ifndef MICROPY_INCLUDED_NRF_BLUETOOTH_BLE_DRV_H #define MICROPY_INCLUDED_NRF_BLUETOOTH_BLE_DRV_H +#include <stdbool.h> + #include "ble.h" #define MAX_TX_IN_PROGRESS 10 @@ -48,7 +50,7 @@ #define UNIT_1_25_MS (1250) #define UNIT_10_MS (10000) -typedef void (*ble_drv_evt_handler_t)(ble_evt_t*, void*); +typedef bool (*ble_drv_evt_handler_t)(ble_evt_t*, void*); typedef enum { SD_FLASH_OPERATION_DONE, @@ -69,4 +71,7 @@ void ble_drv_reset(void); void ble_drv_add_event_handler(ble_drv_evt_handler_t func, void *param); void ble_drv_remove_event_handler(ble_drv_evt_handler_t func, void *param); +// Allow for user provided entries to prevent allocations outside the VM. +void ble_drv_add_event_handler_entry(ble_drv_evt_handler_entry_t* entry, ble_drv_evt_handler_t func, void *param); + #endif // MICROPY_INCLUDED_NRF_BLUETOOTH_BLE_DRV_H diff --git a/ports/nrf/boards/adafruit_nrf52840_s140_v6.ld b/ports/nrf/boards/adafruit_nrf52840_s140_v6.ld index 2587a19e3..756060f96 100644 --- a/ports/nrf/boards/adafruit_nrf52840_s140_v6.ld +++ b/ports/nrf/boards/adafruit_nrf52840_s140_v6.ld @@ -16,6 +16,7 @@ 0x00000000..0x00000FFF (4KB) Master Boot Record */ + /* Specify the memory areas (S140 6.x.x) */ MEMORY { @@ -26,7 +27,10 @@ MEMORY FLASH_FATFS (r) : ORIGIN = 0x000AD000, LENGTH = 0x040000 /* 0x2000000 - RAM:ORIGIN is reserved for Softdevice */ - RAM (xrw) : ORIGIN = 0x20004000, LENGTH = 0x20040000 - 0x20004000 + /* SoftDevice 6.1.0 takes 0x1628 bytes (5.54 kb) minimum. */ + /* To measure the minimum required amount of memory for given configuration, set this number + high enough to work and then check the mutation of the value done by sd_ble_enable. */ + RAM (xrw) : ORIGIN = 0x20000000 + 16K, LENGTH = 256K - 16K } /* produce a link error if there is not this amount of RAM for these sections */ @@ -38,7 +42,8 @@ _minimum_heap_size = 0; /*_stack_end = ORIGIN(RAM) + LENGTH(RAM);*/ _estack = ORIGIN(RAM) + LENGTH(RAM); -/* RAM extents for the garbage collector */ +/* RAM extents for the garbage collector and soft device init */ +_ram_start = ORIGIN(RAM); _ram_end = ORIGIN(RAM) + LENGTH(RAM); _heap_end = 0x20020000; /* tunable */ diff --git a/ports/nrf/common-hal/_bleio/Adapter.c b/ports/nrf/common-hal/_bleio/Adapter.c index a8e1f1905..41189a86d 100644 --- a/ports/nrf/common-hal/_bleio/Adapter.c +++ b/ports/nrf/common-hal/_bleio/Adapter.c @@ -26,6 +26,7 @@ * THE SOFTWARE. */ +#include <stdint.h> #include <stdio.h> #include <string.h> @@ -34,11 +35,18 @@ #include "nrfx_power.h" #include "nrf_nvic.h" #include "nrf_sdm.h" +#include "tick.h" +#include "py/gc.h" #include "py/objstr.h" #include "py/runtime.h" +#include "supervisor/shared/safe_mode.h" #include "supervisor/usb.h" +#include "shared-bindings/_bleio/__init__.h" #include "shared-bindings/_bleio/Adapter.h" #include "shared-bindings/_bleio/Address.h" +#include "shared-bindings/_bleio/Connection.h" +#include "shared-bindings/_bleio/ScanEntry.h" +#include "shared-bindings/time/__init__.h" #define BLE_MIN_CONN_INTERVAL MSEC_TO_UNITS(15, UNIT_0_625_MS) #define BLE_MAX_CONN_INTERVAL MSEC_TO_UNITS(15, UNIT_0_625_MS) @@ -46,10 +54,13 @@ #define BLE_CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) STATIC void softdevice_assert_handler(uint32_t id, uint32_t pc, uint32_t info) { - mp_raise_msg_varg(&mp_type_AssertionError, - translate("Soft device assert, id: 0x%08lX, pc: 0x%08lX"), id, pc); + reset_into_safe_mode(NORDIC_SOFT_DEVICE_ASSERT); } +bleio_connection_internal_t connections[BLEIO_TOTAL_CONNECTION_COUNT]; + +// Linker script provided ram start. +extern uint32_t _ram_start; STATIC uint32_t ble_stack_enable(void) { nrf_clock_lf_cfg_t clock_config = { #if BOARD_HAS_32KHZ_XTAL @@ -78,34 +89,56 @@ STATIC uint32_t ble_stack_enable(void) { // Start with no event handlers, etc. ble_drv_reset(); - uint32_t app_ram_start; - app_ram_start = 0x20004000; + // Set everything up to have one persistent code editing connection and one user managed + // connection. In the future we could move .data and .bss to the other side of the stack and + // dynamically adjust for different memory requirements of the SD based on boot.py + // configuration. + uint32_t app_ram_start = (uint32_t) &_ram_start; ble_cfg_t ble_conf; ble_conf.conn_cfg.conn_cfg_tag = BLE_CONN_CFG_TAG_CUSTOM; - ble_conf.conn_cfg.params.gap_conn_cfg.conn_count = BLE_GAP_CONN_COUNT_DEFAULT; + ble_conf.conn_cfg.params.gap_conn_cfg.conn_count = BLEIO_TOTAL_CONNECTION_COUNT; + // Event length here can influence throughput so perhaps make multiple connection profiles + // available. ble_conf.conn_cfg.params.gap_conn_cfg.event_length = BLE_GAP_EVENT_LENGTH_DEFAULT; err_code = sd_ble_cfg_set(BLE_CONN_CFG_GAP, &ble_conf, app_ram_start); - if (err_code != NRF_SUCCESS) + if (err_code != NRF_SUCCESS) { return err_code; + } memset(&ble_conf, 0, sizeof(ble_conf)); - ble_conf.gap_cfg.role_count_cfg.periph_role_count = 1; + ble_conf.gap_cfg.role_count_cfg.adv_set_count = 1; + ble_conf.gap_cfg.role_count_cfg.periph_role_count = 2; ble_conf.gap_cfg.role_count_cfg.central_role_count = 1; err_code = sd_ble_cfg_set(BLE_GAP_CFG_ROLE_COUNT, &ble_conf, app_ram_start); - if (err_code != NRF_SUCCESS) + if (err_code != NRF_SUCCESS) { return err_code; + } memset(&ble_conf, 0, sizeof(ble_conf)); ble_conf.conn_cfg.conn_cfg_tag = BLE_CONN_CFG_TAG_CUSTOM; ble_conf.conn_cfg.params.gatts_conn_cfg.hvn_tx_queue_size = MAX_TX_IN_PROGRESS; err_code = sd_ble_cfg_set(BLE_CONN_CFG_GATTS, &ble_conf, app_ram_start); - if (err_code != NRF_SUCCESS) + if (err_code != NRF_SUCCESS) { return err_code; + } + // Double the GATT Server attribute size to accomodate both the CircuitPython built-in service + // and anything the user does. + memset(&ble_conf, 0, sizeof(ble_conf)); + ble_conf.gatts_cfg.attr_tab_size.attr_tab_size = BLE_GATTS_ATTR_TAB_SIZE_DEFAULT * 3; + err_code = sd_ble_cfg_set(BLE_GATTS_CFG_ATTR_TAB_SIZE, &ble_conf, app_ram_start); + if (err_code != NRF_SUCCESS) { + return err_code; + } + + // TODO set ATT_MTU so that the maximum MTU we can negotiate is higher than the default. + + // This sets app_ram_start to the minimum value needed for the settings set above. err_code = sd_ble_enable(&app_ram_start); - if (err_code != NRF_SUCCESS) + if (err_code != NRF_SUCCESS) { return err_code; + } ble_gap_conn_params_t gap_conn_params = { .min_conn_interval = BLE_MIN_CONN_INTERVAL, @@ -122,11 +155,108 @@ STATIC uint32_t ble_stack_enable(void) { return err_code; } -void common_hal_bleio_adapter_set_enabled(bool enabled) { - const bool is_enabled = common_hal_bleio_adapter_get_enabled(); +STATIC bool adapter_on_ble_evt(ble_evt_t *ble_evt, void *self_in) { + bleio_adapter_obj_t *self = (bleio_adapter_obj_t*)self_in; + + // For debugging. + // mp_printf(&mp_plat_print, "Adapter event: 0x%04x\n", ble_evt->header.evt_id); + + switch (ble_evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: { + // Find an empty connection + bleio_connection_internal_t *connection; + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + connection = &connections[i]; + if (connection->conn_handle == BLE_CONN_HANDLE_INVALID) { + break; + } + } + + // Central has connected. + ble_gap_evt_connected_t* connected = &ble_evt->evt.gap_evt.params.connected; + + connection->conn_handle = ble_evt->evt.gap_evt.conn_handle; + connection->connection_obj = mp_const_none; + ble_drv_add_event_handler_entry(&connection->handler_entry, connection_on_ble_evt, connection); + self->connection_objs = NULL; + + // See if connection interval set by Central is out of range. + // If so, negotiate our preferred range. + ble_gap_conn_params_t conn_params; + sd_ble_gap_ppcp_get(&conn_params); + if (conn_params.min_conn_interval < connected->conn_params.min_conn_interval || + conn_params.min_conn_interval > connected->conn_params.max_conn_interval) { + sd_ble_gap_conn_param_update(ble_evt->evt.gap_evt.conn_handle, &conn_params); + } + self->current_advertising_data = NULL; + break; + } + case BLE_GAP_EVT_DISCONNECTED: { + // Find the connection that was disconnected. + bleio_connection_internal_t *connection; + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + connection = &connections[i]; + if (connection->conn_handle == ble_evt->evt.gap_evt.conn_handle) { + break; + } + } + ble_drv_remove_event_handler(connection_on_ble_evt, connection); + connection->conn_handle = BLE_CONN_HANDLE_INVALID; + if (connection->connection_obj != mp_const_none) { + bleio_connection_obj_t* obj = connection->connection_obj; + obj->connection = NULL; + obj->disconnect_reason = ble_evt->evt.gap_evt.params.disconnected.reason; + } + self->connection_objs = NULL; + + break; + } + + case BLE_GAP_EVT_ADV_SET_TERMINATED: + self->current_advertising_data = NULL; + break; + + default: + // For debugging. + // mp_printf(&mp_plat_print, "Unhandled adapter event: 0x%04x\n", ble_evt->header.evt_id); + return false; + break; + } + return true; +} + +STATIC void get_address(bleio_adapter_obj_t *self, ble_gap_addr_t *address) { + uint32_t err_code; + + err_code = sd_ble_gap_addr_get(address); + + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg(translate("Failed to get local address")); + } +} + +char default_ble_name[] = { 'C', 'I', 'R', 'C', 'U', 'I', 'T', 'P', 'Y', 0, 0, 0, 0 , 0}; + +STATIC void bleio_adapter_reset_name(bleio_adapter_obj_t *self) { + uint8_t len = sizeof(default_ble_name) - 1; + + ble_gap_addr_t local_address; + get_address(self, &local_address); + + default_ble_name[len - 4] = nibble_to_hex_lower[local_address.addr[1] >> 4 & 0xf]; + default_ble_name[len - 3] = nibble_to_hex_lower[local_address.addr[1] & 0xf]; + default_ble_name[len - 2] = nibble_to_hex_lower[local_address.addr[0] >> 4 & 0xf]; + default_ble_name[len - 1] = nibble_to_hex_lower[local_address.addr[0] & 0xf]; + default_ble_name[len] = '\0'; // for now we add null for compatibility with C ASCIIZ strings + + common_hal_bleio_adapter_set_name(self, (char*) default_ble_name); +} + +void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enabled) { + const bool is_enabled = common_hal_bleio_adapter_get_enabled(self); // Don't enable or disable twice - if ((is_enabled && enabled) || (!is_enabled && !enabled)) { + if (is_enabled == enabled) { return; } @@ -137,22 +267,34 @@ void common_hal_bleio_adapter_set_enabled(bool enabled) { nrfx_power_uninit(); err_code = ble_stack_enable(); - - // Re-init USB hardware - init_usb_hardware(); } else { err_code = sd_softdevice_disable(); - - // Re-init USB hardware - init_usb_hardware(); } + // Re-init USB hardware + init_usb_hardware(); if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg(translate("Failed to change softdevice state")); + mp_raise_OSError_msg_varg(translate("Failed to change softdevice state, NRF_ERROR_%q"), MP_OBJ_QSTR_VALUE(base_error_messages[err_code - NRF_ERROR_BASE_NUM])); + } + + // Add a handler for incoming peripheral connections. + if (enabled) { + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + bleio_connection_internal_t *connection = &connections[i]; + connection->conn_handle = BLE_CONN_HANDLE_INVALID; + } + bleio_adapter_reset_name(self); + ble_drv_add_event_handler_entry(&self->handler_entry, adapter_on_ble_evt, self); + } else { + ble_drv_reset(); + self->scan_results = NULL; + self->current_advertising_data = NULL; + self->advertising_data = NULL; + self->scan_response_data = NULL; } } -bool common_hal_bleio_adapter_get_enabled(void) { +bool common_hal_bleio_adapter_get_enabled(bleio_adapter_obj_t *self) { uint8_t is_enabled; const uint32_t err_code = sd_softdevice_is_enabled(&is_enabled); @@ -163,42 +305,339 @@ bool common_hal_bleio_adapter_get_enabled(void) { return is_enabled; } -void get_address(ble_gap_addr_t *address) { +bleio_address_obj_t *common_hal_bleio_adapter_get_address(bleio_adapter_obj_t *self) { + common_hal_bleio_adapter_set_enabled(self, true); + + ble_gap_addr_t local_address; + get_address(self, &local_address); + + bleio_address_obj_t *address = m_new_obj(bleio_address_obj_t); + address->base.type = &bleio_address_type; + + common_hal_bleio_address_construct(address, local_address.addr, local_address.addr_type); + return address; +} + +mp_obj_str_t* common_hal_bleio_adapter_get_name(bleio_adapter_obj_t *self) { + uint16_t len = 0; + sd_ble_gap_device_name_get(NULL, &len); + uint8_t buf[len]; + uint32_t err_code = sd_ble_gap_device_name_get(buf, &len); + if (err_code != NRF_SUCCESS) { + return NULL; + } + return mp_obj_new_str((char*) buf, len); +} + +void common_hal_bleio_adapter_set_name(bleio_adapter_obj_t *self, const char* name) { + ble_gap_conn_sec_mode_t sec; + sec.lv = 0; + sec.sm = 0; + sd_ble_gap_device_name_set(&sec, (const uint8_t*) name, strlen(name)); +} + +STATIC bool scan_on_ble_evt(ble_evt_t *ble_evt, void *scan_results_in) { + bleio_scanresults_obj_t *scan_results = (bleio_scanresults_obj_t*)scan_results_in; + + if (ble_evt->header.evt_id == BLE_GAP_EVT_TIMEOUT && + ble_evt->evt.gap_evt.params.timeout.src == BLE_GAP_TIMEOUT_SRC_SCAN) { + shared_module_bleio_scanresults_set_done(scan_results, true); + ble_drv_remove_event_handler(scan_on_ble_evt, scan_results); + return true; + } + + if (ble_evt->header.evt_id != BLE_GAP_EVT_ADV_REPORT) { + return false; + } + ble_gap_evt_adv_report_t *report = &ble_evt->evt.gap_evt.params.adv_report; + + shared_module_bleio_scanresults_append(scan_results, + ticks_ms, + report->type.connectable, + report->type.scan_response, + report->rssi, + report->peer_addr.addr, + report->peer_addr.addr_type, + report->data.p_data, + report->data.len); + + const uint32_t err_code = sd_ble_gap_scan_start(NULL, scan_results->common_hal_data); + if (err_code != NRF_SUCCESS) { + // TODO: Pass the error into the scan results so it can throw an exception. + scan_results->done = true; + } + return true; +} + +mp_obj_t common_hal_bleio_adapter_start_scan(bleio_adapter_obj_t *self, uint8_t* prefixes, uint8_t prefix_length, bool extended, mp_int_t buffer_size, mp_float_t timeout, mp_float_t interval, mp_float_t window, mp_int_t minimum_rssi, bool active) { + if (self->scan_results != NULL) { + if (!shared_module_bleio_scanresults_get_done(self->scan_results)) { + mp_raise_RuntimeError(translate("Scan already in progess. Stop with stop_scan.")); + } + self->scan_results = NULL; + } + self->scan_results = shared_module_bleio_new_scanresults(buffer_size, prefixes, prefix_length, minimum_rssi); + size_t max_packet_size = extended ? BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED : BLE_GAP_SCAN_BUFFER_MAX; + uint8_t *raw_data = m_malloc(sizeof(ble_data_t) + max_packet_size, false); + ble_data_t * sd_data = (ble_data_t *) raw_data; + self->scan_results->common_hal_data = sd_data; + sd_data->len = max_packet_size; + sd_data->p_data = raw_data + sizeof(ble_data_t); + + ble_drv_add_event_handler(scan_on_ble_evt, self->scan_results); + + uint32_t nrf_timeout = SEC_TO_UNITS(timeout, UNIT_10_MS); + if (timeout <= 0.0001) { + nrf_timeout = BLE_GAP_SCAN_TIMEOUT_UNLIMITED; + } + + ble_gap_scan_params_t scan_params = { + .extended = extended, + .interval = SEC_TO_UNITS(interval, UNIT_0_625_MS), + .timeout = nrf_timeout, + .window = SEC_TO_UNITS(window, UNIT_0_625_MS), + .scan_phys = BLE_GAP_PHY_1MBPS, + .active = active + }; uint32_t err_code; + err_code = sd_ble_gap_scan_start(&scan_params, sd_data); - common_hal_bleio_adapter_set_enabled(true); - err_code = sd_ble_gap_addr_get(address); + if (err_code != NRF_SUCCESS) { + self->scan_results = NULL; + ble_drv_remove_event_handler(scan_on_ble_evt, self->scan_results); + mp_raise_OSError_msg_varg(translate("Failed to start scanning, err 0x%04x"), err_code); + } + + return MP_OBJ_FROM_PTR(self->scan_results); +} + +void common_hal_bleio_adapter_stop_scan(bleio_adapter_obj_t *self) { + sd_ble_gap_scan_stop(); + shared_module_bleio_scanresults_set_done(self->scan_results, true); + ble_drv_remove_event_handler(scan_on_ble_evt, self->scan_results); + self->scan_results = NULL; +} + +typedef struct { + uint16_t conn_handle; + volatile bool done; +} connect_info_t; + +STATIC bool connect_on_ble_evt(ble_evt_t *ble_evt, void *info_in) { + connect_info_t *info = (connect_info_t*)info_in; + + switch (ble_evt->header.evt_id) { + case BLE_GAP_EVT_CONNECTED: + info->conn_handle = ble_evt->evt.gap_evt.conn_handle; + info->done = true; + + break; + + case BLE_GAP_EVT_TIMEOUT: + // Handle will be invalid. + info->done = true; + break; + default: + // For debugging. + // mp_printf(&mp_plat_print, "Unhandled central event: 0x%04x\n", ble_evt->header.evt_id); + return false; + break; + } + return true; +} + +mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self, bleio_address_obj_t *address, mp_float_t timeout, bool pair) { + + ble_gap_addr_t addr; + + addr.addr_type = address->type; + mp_buffer_info_t address_buf_info; + mp_get_buffer_raise(address->bytes, &address_buf_info, MP_BUFFER_READ); + memcpy(addr.addr, (uint8_t *) address_buf_info.buf, NUM_BLEIO_ADDRESS_BYTES); + + ble_gap_scan_params_t scan_params = { + .interval = MSEC_TO_UNITS(100, UNIT_0_625_MS), + .window = MSEC_TO_UNITS(100, UNIT_0_625_MS), + .scan_phys = BLE_GAP_PHY_1MBPS, + // timeout of 0 means no timeout + .timeout = SEC_TO_UNITS(timeout, UNIT_10_MS), + }; + + ble_gap_conn_params_t conn_params = { + .conn_sup_timeout = MSEC_TO_UNITS(4000, UNIT_10_MS), + .min_conn_interval = MSEC_TO_UNITS(15, UNIT_1_25_MS), + .max_conn_interval = MSEC_TO_UNITS(300, UNIT_1_25_MS), + .slave_latency = 0, // number of conn events + }; + + connect_info_t event_info; + ble_drv_add_event_handler(connect_on_ble_evt, &event_info); + event_info.done = false; + + uint32_t err_code = sd_ble_gap_connect(&addr, &scan_params, &conn_params, BLE_CONN_CFG_TAG_CUSTOM); if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg(translate("Failed to get local address")); + ble_drv_remove_event_handler(connect_on_ble_evt, &event_info); + mp_raise_OSError_msg_varg(translate("Failed to start connecting, error 0x%04x"), err_code); + } + + while (!event_info.done) { + RUN_BACKGROUND_TASKS; + } + + ble_drv_remove_event_handler(connect_on_ble_evt, &event_info); + + if (event_info.conn_handle == BLE_CONN_HANDLE_INVALID) { + mp_raise_OSError_msg(translate("Failed to connect: timeout")); } + + // Make the connection object and return it. + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + bleio_connection_internal_t *connection = &connections[i]; + if (connection->conn_handle == event_info.conn_handle) { + return bleio_connection_new_from_internal(connection); + } + } + + mp_raise_OSError_msg(translate("Failed to connect: internal error")); + + return mp_const_none; } -bleio_address_obj_t *common_hal_bleio_adapter_get_address(void) { - common_hal_bleio_adapter_set_enabled(true); +// The nRF SD 6.1.0 can only do one concurrent advertisement so share the advertising handle. +uint8_t adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET; - ble_gap_addr_t local_address; - get_address(&local_address); +STATIC void check_data_fit(size_t data_len) { + if (data_len > BLE_GAP_ADV_SET_DATA_SIZE_MAX) { + mp_raise_ValueError(translate("Data too large for advertisement packet")); + } +} - bleio_address_obj_t *address = m_new_obj(bleio_address_obj_t); - address->base.type = &bleio_address_type; +uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, bool connectable, float interval, uint8_t *advertising_data, uint16_t advertising_data_len, uint8_t *scan_response_data, uint16_t scan_response_data_len) { + if (self->current_advertising_data != NULL && self->current_advertising_data == self->advertising_data) { + return NRF_ERROR_BUSY; + } - common_hal_bleio_address_construct(address, local_address.addr, local_address.addr_type); - return address; + // If the current advertising data isn't owned by the adapter then it must be an internal + // advertisement that we should stop. + if (self->current_advertising_data != NULL) { + common_hal_bleio_adapter_stop_advertising(self); + } + + uint32_t err_code; + ble_gap_adv_params_t adv_params = { + .interval = SEC_TO_UNITS(interval, UNIT_0_625_MS), + .properties.type = connectable ? BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED + : BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED, + .duration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, + .filter_policy = BLE_GAP_ADV_FP_ANY, + .primary_phy = BLE_GAP_PHY_1MBPS, + }; + + const ble_gap_adv_data_t ble_gap_adv_data = { + .adv_data.p_data = advertising_data, + .adv_data.len = advertising_data_len, + .scan_rsp_data.p_data = scan_response_data_len > 0 ? scan_response_data : NULL, + .scan_rsp_data.len = scan_response_data_len, + }; + + err_code = sd_ble_gap_adv_set_configure(&adv_handle, &ble_gap_adv_data, &adv_params); + if (err_code != NRF_SUCCESS) { + return err_code; + } + + err_code = sd_ble_gap_adv_start(adv_handle, BLE_CONN_CFG_TAG_CUSTOM); + if (err_code != NRF_SUCCESS) { + return err_code; + } + self->current_advertising_data = advertising_data; + return NRF_SUCCESS; } -mp_obj_t common_hal_bleio_adapter_get_default_name(void) { - common_hal_bleio_adapter_set_enabled(true); - ble_gap_addr_t local_address; - get_address(&local_address); +void common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, bool connectable, mp_float_t interval, mp_buffer_info_t *advertising_data_bufinfo, mp_buffer_info_t *scan_response_data_bufinfo) { + if (self->current_advertising_data != NULL && self->current_advertising_data == self->advertising_data) { + mp_raise_OSError_msg(translate("Already advertising.")); + } + // interval value has already been validated. + + uint32_t err_code; + + check_data_fit(advertising_data_bufinfo->len); + check_data_fit(scan_response_data_bufinfo->len); + // The advertising data buffers must not move, because the SoftDevice depends on them. + // So make them long-lived and reuse them onwards. + if (self->advertising_data == NULL) { + self->advertising_data = (uint8_t *) gc_alloc(BLE_GAP_ADV_SET_DATA_SIZE_MAX * sizeof(uint8_t), false, true); + } + if (self->scan_response_data == NULL) { + self->scan_response_data = (uint8_t *) gc_alloc(BLE_GAP_ADV_SET_DATA_SIZE_MAX * sizeof(uint8_t), false, true); + } + + memcpy(self->advertising_data, advertising_data_bufinfo->buf, advertising_data_bufinfo->len); + memcpy(self->scan_response_data, scan_response_data_bufinfo->buf, scan_response_data_bufinfo->len); + + err_code = _common_hal_bleio_adapter_start_advertising(self, connectable, interval, self->advertising_data, advertising_data_bufinfo->len, self->scan_response_data, scan_response_data_bufinfo->len); - char name[] = { 'C', 'I', 'R', 'C', 'U', 'I', 'T', 'P', 'Y', 0, 0, 0, 0 }; + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to start advertising, NRF_ERROR_%q"), MP_OBJ_QSTR_VALUE(base_error_messages[err_code - NRF_ERROR_BASE_NUM])); + } +} + +void common_hal_bleio_adapter_stop_advertising(bleio_adapter_obj_t *self) { + if (adv_handle == BLE_GAP_ADV_SET_HANDLE_NOT_SET) + return; + + // TODO: Don't actually stop. Switch to advertising CircuitPython if we don't already have a connection. + const uint32_t err_code = sd_ble_gap_adv_stop(adv_handle); + self->current_advertising_data = NULL; + + if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_INVALID_STATE)) { + mp_raise_OSError_msg_varg(translate("Failed to stop advertising, NRF_ERROR_%q"), MP_OBJ_QSTR_VALUE(base_error_messages[err_code - NRF_ERROR_BASE_NUM])); + } +} - name[sizeof(name) - 4] = nibble_to_hex_lower[local_address.addr[1] >> 4 & 0xf]; - name[sizeof(name) - 3] = nibble_to_hex_lower[local_address.addr[1] & 0xf]; - name[sizeof(name) - 2] = nibble_to_hex_lower[local_address.addr[0] >> 4 & 0xf]; - name[sizeof(name) - 1] = nibble_to_hex_lower[local_address.addr[0] & 0xf]; +bool common_hal_bleio_adapter_get_connected(bleio_adapter_obj_t *self) { + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + bleio_connection_internal_t *connection = &connections[i]; + if (connection->conn_handle != BLE_CONN_HANDLE_INVALID) { + return true; + } + } + return false; +} - return mp_obj_new_str(name, sizeof(name)); +mp_obj_t common_hal_bleio_adapter_get_connections(bleio_adapter_obj_t *self) { + if (self->connection_objs != NULL) { + return self->connection_objs; + } + size_t total_connected = 0; + mp_obj_t items[BLEIO_TOTAL_CONNECTION_COUNT]; + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + bleio_connection_internal_t *connection = &connections[i]; + if (connection->conn_handle != BLE_CONN_HANDLE_INVALID) { + if (connection->connection_obj == mp_const_none) { + connection->connection_obj = bleio_connection_new_from_internal(connection); + } + items[total_connected] = connection->connection_obj; + total_connected++; + } + } + self->connection_objs = mp_obj_new_tuple(total_connected, items); + return self->connection_objs; +} + +void bleio_adapter_gc_collect(bleio_adapter_obj_t* adapter) { + gc_collect_root((void**)adapter, sizeof(bleio_adapter_obj_t) / sizeof(size_t)); + gc_collect_root((void**)connections, sizeof(connections) / sizeof(size_t)); +} + +void bleio_adapter_reset(bleio_adapter_obj_t* adapter) { + common_hal_bleio_adapter_stop_scan(adapter); + common_hal_bleio_adapter_stop_advertising(adapter); + adapter->connection_objs = NULL; + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + bleio_connection_internal_t *connection = &connections[i]; + connection->connection_obj = mp_const_none; + } } diff --git a/ports/nrf/common-hal/_bleio/Adapter.h b/ports/nrf/common-hal/_bleio/Adapter.h index 5dcc62540..dca443990 100644 --- a/ports/nrf/common-hal/_bleio/Adapter.h +++ b/ports/nrf/common-hal/_bleio/Adapter.h @@ -30,9 +30,27 @@ #define MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_ADAPTER_H #include "py/obj.h" +#include "py/objtuple.h" + +#include "shared-bindings/_bleio/Connection.h" +#include "shared-bindings/_bleio/ScanResults.h" + +#define BLEIO_TOTAL_CONNECTION_COUNT 2 + +extern bleio_connection_internal_t connections[BLEIO_TOTAL_CONNECTION_COUNT]; typedef struct { mp_obj_base_t base; -} super_adapter_obj_t; + uint8_t* advertising_data; + uint8_t* scan_response_data; + uint8_t* current_advertising_data; + bleio_scanresults_obj_t* scan_results; + mp_obj_t name; + mp_obj_tuple_t *connection_objs; + ble_drv_evt_handler_entry_t handler_entry; +} bleio_adapter_obj_t; + +void bleio_adapter_gc_collect(bleio_adapter_obj_t* adapter); +void bleio_adapter_reset(bleio_adapter_obj_t* adapter); #endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_ADAPTER_H diff --git a/ports/nrf/common-hal/_bleio/Central.c b/ports/nrf/common-hal/_bleio/Central.c deleted file mode 100644 index db67b763c..000000000 --- a/ports/nrf/common-hal/_bleio/Central.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 - * - * 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 <string.h> -#include <stdio.h> - -#include "ble.h" -#include "ble_drv.h" -#include "ble_hci.h" -#include "nrf_soc.h" -#include "py/objstr.h" -#include "py/runtime.h" -#include "shared-bindings/_bleio/__init__.h" -#include "shared-bindings/_bleio/Adapter.h" -#include "shared-bindings/_bleio/Central.h" - -STATIC void central_on_ble_evt(ble_evt_t *ble_evt, void *central_in) { - bleio_central_obj_t *central = (bleio_central_obj_t*)central_in; - - switch (ble_evt->header.evt_id) { - case BLE_GAP_EVT_CONNECTED: - central->conn_handle = ble_evt->evt.gap_evt.conn_handle; - central->waiting_to_connect = false; - break; - - case BLE_GAP_EVT_TIMEOUT: - // Handle will be invalid. - central->waiting_to_connect = false; - break; - - case BLE_GAP_EVT_DISCONNECTED: - central->conn_handle = BLE_CONN_HANDLE_INVALID; - break; - - case BLE_GAP_EVT_SEC_PARAMS_REQUEST: - sd_ble_gap_sec_params_reply(central->conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL); - break; - - case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: { - ble_gap_evt_conn_param_update_request_t *request = - &ble_evt->evt.gap_evt.params.conn_param_update_request; - sd_ble_gap_conn_param_update(central->conn_handle, &request->conn_params); - break; - } - default: - // For debugging. - // mp_printf(&mp_plat_print, "Unhandled central event: 0x%04x\n", ble_evt->header.evt_id); - break; - } -} - -void common_hal_bleio_central_construct(bleio_central_obj_t *self) { - common_hal_bleio_adapter_set_enabled(true); - - self->remote_service_list = mp_obj_new_list(0, NULL); - self->conn_handle = BLE_CONN_HANDLE_INVALID; -} - -void common_hal_bleio_central_connect(bleio_central_obj_t *self, bleio_address_obj_t *address, mp_float_t timeout) { - common_hal_bleio_adapter_set_enabled(true); - ble_drv_add_event_handler(central_on_ble_evt, self); - - ble_gap_addr_t addr; - - addr.addr_type = address->type; - mp_buffer_info_t address_buf_info; - mp_get_buffer_raise(address->bytes, &address_buf_info, MP_BUFFER_READ); - memcpy(addr.addr, (uint8_t *) address_buf_info.buf, NUM_BLEIO_ADDRESS_BYTES); - - ble_gap_scan_params_t scan_params = { - .interval = MSEC_TO_UNITS(100, UNIT_0_625_MS), - .window = MSEC_TO_UNITS(100, UNIT_0_625_MS), - .scan_phys = BLE_GAP_PHY_1MBPS, - // timeout of 0 means no timeout - .timeout = SEC_TO_UNITS(timeout, UNIT_10_MS), - }; - - ble_gap_conn_params_t conn_params = { - .conn_sup_timeout = MSEC_TO_UNITS(4000, UNIT_10_MS), - .min_conn_interval = MSEC_TO_UNITS(15, UNIT_1_25_MS), - .max_conn_interval = MSEC_TO_UNITS(300, UNIT_1_25_MS), - .slave_latency = 0, // number of conn events - }; - - self->waiting_to_connect = true; - - uint32_t err_code = sd_ble_gap_connect(&addr, &scan_params, &conn_params, BLE_CONN_CFG_TAG_CUSTOM); - - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to start connecting, error 0x%04x"), err_code); - } - - while (self->waiting_to_connect) { - RUN_BACKGROUND_TASKS; - } - - if (self->conn_handle == BLE_CONN_HANDLE_INVALID) { - mp_raise_OSError_msg(translate("Failed to connect: timeout")); - } -} - -void common_hal_bleio_central_disconnect(bleio_central_obj_t *self) { - sd_ble_gap_disconnect(self->conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); -} - -bool common_hal_bleio_central_get_connected(bleio_central_obj_t *self) { - return self->conn_handle != BLE_CONN_HANDLE_INVALID; -} - -mp_obj_tuple_t *common_hal_bleio_central_discover_remote_services(bleio_central_obj_t *self, mp_obj_t service_uuids_whitelist) { - common_hal_bleio_device_discover_remote_services(MP_OBJ_FROM_PTR(self), service_uuids_whitelist); - // Convert to a tuple and then clear the list so the callee will take ownership. - mp_obj_tuple_t *services_tuple = mp_obj_new_tuple(self->remote_service_list->len, - self->remote_service_list->items); - mp_obj_list_clear(self->remote_service_list); - return services_tuple; -} - -mp_obj_list_t *common_hal_bleio_central_get_remote_services(bleio_central_obj_t *self) { - return self->remote_service_list; -} diff --git a/ports/nrf/common-hal/_bleio/Central.h b/ports/nrf/common-hal/_bleio/Central.h deleted file mode 100644 index 01f7faca7..000000000 --- a/ports/nrf/common-hal/_bleio/Central.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 - * - * 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_SHARED_MODULE_BLEIO_CENTRAL_H -#define MICROPY_INCLUDED_SHARED_MODULE_BLEIO_CENTRAL_H - -#include <stdbool.h> - -#include "py/objlist.h" -#include "shared-module/_bleio/Address.h" - -typedef struct { - mp_obj_base_t base; - volatile bool waiting_to_connect; - volatile uint16_t conn_handle; - // Services discovered after connecting to a remote peripheral. - mp_obj_list_t *remote_service_list; -} bleio_central_obj_t; - -#endif // MICROPY_INCLUDED_SHARED_MODULE_BLEIO_CENTRAL_H diff --git a/ports/nrf/common-hal/_bleio/Characteristic.c b/ports/nrf/common-hal/_bleio/Characteristic.c index e8454d528..ab765d1ed 100644 --- a/ports/nrf/common-hal/_bleio/Characteristic.c +++ b/ports/nrf/common-hal/_bleio/Characteristic.c @@ -32,7 +32,7 @@ #include "shared-bindings/_bleio/Descriptor.h" #include "shared-bindings/_bleio/Service.h" -static volatile bleio_characteristic_obj_t *m_read_characteristic; +#include "common-hal/_bleio/Adapter.h" STATIC uint16_t characteristic_get_cccd(uint16_t cccd_handle, uint16_t conn_handle) { uint16_t cccd; @@ -53,27 +53,6 @@ STATIC uint16_t characteristic_get_cccd(uint16_t cccd_handle, uint16_t conn_hand return cccd; } -STATIC void characteristic_on_gattc_read_rsp_evt(ble_evt_t *ble_evt, void *param) { - switch (ble_evt->header.evt_id) { - - // More events may be handled later, so keep this as a switch. - - case BLE_GATTC_EVT_READ_RSP: { - ble_gattc_evt_read_rsp_t *response = &ble_evt->evt.gattc_evt.params.read_rsp; - if (m_read_characteristic) { - m_read_characteristic->value = mp_obj_new_bytearray(response->len, response->data); - } - // Indicate to busy-wait loop that we've read the attribute value. - m_read_characteristic = NULL; - break; - } - - default: - // For debugging. - // mp_printf(&mp_plat_print, "Unhandled characteristic event: 0x%04x\n", ble_evt->header.evt_id); - break; - } -} STATIC void characteristic_gatts_notify_indicate(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo, uint16_t hvx_type) { uint16_t hvx_len = bufinfo->len; @@ -103,35 +82,14 @@ STATIC void characteristic_gatts_notify_indicate(uint16_t handle, uint16_t conn_ } } -STATIC void characteristic_gattc_read(bleio_characteristic_obj_t *characteristic) { - const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(characteristic->service->device); - common_hal_bleio_check_connected(conn_handle); - - // Set to NULL in event loop after event. - m_read_characteristic = characteristic; - - ble_drv_add_event_handler(characteristic_on_gattc_read_rsp_evt, characteristic); - - const uint32_t err_code = sd_ble_gattc_read(conn_handle, characteristic->handle, 0); - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to read attribute value, err 0x%04x"), err_code); - } - - while (m_read_characteristic != NULL) { - RUN_BACKGROUND_TASKS; - } - - ble_drv_remove_event_handler(characteristic_on_gattc_read_rsp_evt, characteristic); -} - -void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, bleio_service_obj_t *service, bleio_uuid_obj_t *uuid, bleio_characteristic_properties_t props, 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) { +void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, bleio_service_obj_t *service, uint16_t handle, bleio_uuid_obj_t *uuid, bleio_characteristic_properties_t props, 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) { self->service = service; self->uuid = uuid; self->handle = BLE_GATT_HANDLE_INVALID; self->props = props; self->read_perm = read_perm; self->write_perm = write_perm; - self->descriptor_list = mp_obj_new_list(0, NULL); + 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) { @@ -141,10 +99,18 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, self->max_length = max_length; self->fixed_length = fixed_length; - common_hal_bleio_characteristic_set_value(self, initial_value_bufinfo); + if (service->is_remote) { + self->handle = handle; + } else { + common_hal_bleio_service_add_characteristic(self->service, self, initial_value_bufinfo); + } + + if (initial_value_bufinfo != NULL) { + common_hal_bleio_characteristic_set_value(self, initial_value_bufinfo); + } } -mp_obj_list_t *common_hal_bleio_characteristic_get_descriptor_list(bleio_characteristic_obj_t *self) { +bleio_descriptor_obj_t *common_hal_bleio_characteristic_get_descriptor_list(bleio_characteristic_obj_t *self) { return self->descriptor_list; } @@ -152,19 +118,19 @@ bleio_service_obj_t *common_hal_bleio_characteristic_get_service(bleio_character return self->service; } -mp_obj_t common_hal_bleio_characteristic_get_value(bleio_characteristic_obj_t *self) { +size_t common_hal_bleio_characteristic_get_value(bleio_characteristic_obj_t *self, uint8_t* buf, size_t len) { // Do GATT operations only if this characteristic has been added to a registered service. if (self->handle != BLE_GATT_HANDLE_INVALID) { - uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->service->device); + uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection); if (common_hal_bleio_service_get_is_remote(self->service)) { // self->value is set by evt handler. - characteristic_gattc_read(self); + return common_hal_bleio_gattc_read(self->handle, conn_handle, buf, len); } else { - self->value = common_hal_bleio_gatts_read(self->handle, conn_handle); + return common_hal_bleio_gatts_read(self->handle, conn_handle, buf, len); } } - return self->value; + return 0; } void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_buffer_info_t *bufinfo) { @@ -175,39 +141,40 @@ void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_raise_ValueError(translate("Value length > max_length")); } - self->value = mp_obj_new_bytes(bufinfo->buf, bufinfo->len); - // Do GATT operations only if this characteristic has been added to a registered service. if (self->handle != BLE_GATT_HANDLE_INVALID) { - uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->service->device); if (common_hal_bleio_service_get_is_remote(self->service)) { + uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection); // Last argument is true if write-no-reponse desired. common_hal_bleio_gattc_write(self->handle, conn_handle, bufinfo, (self->props & CHAR_PROP_WRITE_NO_RESPONSE)); } else { - - bool sent = false; - uint16_t cccd = 0; - - const bool notify = self->props & CHAR_PROP_NOTIFY; - const bool indicate = self->props & CHAR_PROP_INDICATE; - if (notify | indicate) { - cccd = characteristic_get_cccd(self->cccd_handle, conn_handle); - } - - // It's possible that both notify and indicate are set. - if (notify && (cccd & BLE_GATT_HVX_NOTIFICATION)) { - characteristic_gatts_notify_indicate(self->handle, conn_handle, bufinfo, BLE_GATT_HVX_NOTIFICATION); - sent = true; - } - if (indicate && (cccd & BLE_GATT_HVX_INDICATION)) { - characteristic_gatts_notify_indicate(self->handle, conn_handle, bufinfo, BLE_GATT_HVX_INDICATION); - sent = true; - } - - if (!sent) { - common_hal_bleio_gatts_write(self->handle, conn_handle, bufinfo); + // Always write the value locally even if no connections are active. + common_hal_bleio_gatts_write(self->handle, BLE_CONN_HANDLE_INVALID, bufinfo); + // Check to see if we need to notify or indicate any active connections. + for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { + bleio_connection_internal_t *connection = &connections[i]; + uint16_t conn_handle = connection->conn_handle; + if (connection->conn_handle == BLE_CONN_HANDLE_INVALID) { + continue; + } + + uint16_t cccd = 0; + + const bool notify = self->props & CHAR_PROP_NOTIFY; + const bool indicate = self->props & CHAR_PROP_INDICATE; + if (notify | indicate) { + cccd = characteristic_get_cccd(self->cccd_handle, conn_handle); + } + + // It's possible that both notify and indicate are set. + if (notify && (cccd & BLE_GATT_HVX_NOTIFICATION)) { + characteristic_gatts_notify_indicate(self->handle, conn_handle, bufinfo, BLE_GATT_HVX_NOTIFICATION); + } + if (indicate && (cccd & BLE_GATT_HVX_INDICATION)) { + characteristic_gatts_notify_indicate(self->handle, conn_handle, bufinfo, BLE_GATT_HVX_INDICATION); + } } } } @@ -252,7 +219,8 @@ void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t * mp_raise_OSError_msg_varg(translate("Failed to add descriptor, err 0x%04x"), err_code); } - mp_obj_list_append(self->descriptor_list, MP_OBJ_FROM_PTR(descriptor)); + descriptor->next = self->descriptor_list; + self->descriptor_list = descriptor; } void common_hal_bleio_characteristic_set_cccd(bleio_characteristic_obj_t *self, bool notify, bool indicate) { @@ -264,7 +232,7 @@ void common_hal_bleio_characteristic_set_cccd(bleio_characteristic_obj_t *self, mp_raise_ValueError(translate("Can't set CCCD on local Characteristic")); } - const uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->service->device); + const uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection); common_hal_bleio_check_connected(conn_handle); uint16_t cccd_value = diff --git a/ports/nrf/common-hal/_bleio/Characteristic.h b/ports/nrf/common-hal/_bleio/Characteristic.h index 3a7691d07..bb8f28495 100644 --- a/ports/nrf/common-hal/_bleio/Characteristic.h +++ b/ports/nrf/common-hal/_bleio/Characteristic.h @@ -29,11 +29,12 @@ #define MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_CHARACTERISTIC_H #include "shared-bindings/_bleio/Attribute.h" +#include "common-hal/_bleio/Descriptor.h" #include "shared-module/_bleio/Characteristic.h" #include "common-hal/_bleio/Service.h" #include "common-hal/_bleio/UUID.h" -typedef struct { +typedef struct _bleio_characteristic_obj { mp_obj_base_t base; // Will be MP_OBJ_NULL before being assigned to a Service. bleio_service_obj_t *service; @@ -45,7 +46,7 @@ typedef struct { bleio_characteristic_properties_t props; bleio_attribute_security_mode_t read_perm; bleio_attribute_security_mode_t write_perm; - mp_obj_list_t *descriptor_list; + bleio_descriptor_obj_t *descriptor_list; uint16_t user_desc_handle; uint16_t cccd_handle; uint16_t sccd_handle; diff --git a/ports/nrf/common-hal/_bleio/CharacteristicBuffer.c b/ports/nrf/common-hal/_bleio/CharacteristicBuffer.c index 95794feec..5f280e121 100644 --- a/ports/nrf/common-hal/_bleio/CharacteristicBuffer.c +++ b/ports/nrf/common-hal/_bleio/CharacteristicBuffer.c @@ -38,6 +38,7 @@ #include "tick.h" #include "shared-bindings/_bleio/__init__.h" +#include "shared-bindings/_bleio/Connection.h" #include "common-hal/_bleio/CharacteristicBuffer.h" STATIC void write_to_ringbuf(bleio_characteristic_buffer_obj_t *self, uint8_t *data, uint16_t len) { @@ -50,7 +51,7 @@ STATIC void write_to_ringbuf(bleio_characteristic_buffer_obj_t *self, uint8_t *d sd_nvic_critical_region_exit(is_nested_critical_region); } -STATIC void characteristic_buffer_on_ble_evt(ble_evt_t *ble_evt, void *param) { +STATIC bool characteristic_buffer_on_ble_evt(ble_evt_t *ble_evt, void *param) { bleio_characteristic_buffer_obj_t *self = (bleio_characteristic_buffer_obj_t *) param; switch (ble_evt->header.evt_id) { case BLE_GATTS_EVT_WRITE: { @@ -75,7 +76,11 @@ STATIC void characteristic_buffer_on_ble_evt(ble_evt_t *ble_evt, void *param) { } break; } + default: + return false; + break; } + return true; } // Assumes that timeout and buffer_size have been validated before call. @@ -150,6 +155,7 @@ void common_hal_bleio_characteristic_buffer_deinit(bleio_characteristic_buffer_o bool common_hal_bleio_characteristic_buffer_connected(bleio_characteristic_buffer_obj_t *self) { return self->characteristic != NULL && self->characteristic->service != NULL && - self->characteristic->service->device != NULL && - common_hal_bleio_device_get_conn_handle(self->characteristic->service->device) != BLE_CONN_HANDLE_INVALID; + (!self->characteristic->service->is_remote || + (self->characteristic->service->connection != MP_OBJ_NULL && + common_hal_bleio_connection_get_connected(self->characteristic->service->connection))); } diff --git a/ports/nrf/common-hal/_bleio/Connection.c b/ports/nrf/common-hal/_bleio/Connection.c new file mode 100644 index 000000000..5d3e3ace1 --- /dev/null +++ b/ports/nrf/common-hal/_bleio/Connection.c @@ -0,0 +1,629 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Dan Halbert for Adafruit Industries + * Copyright (c) 2018 Artur Pacholec + * + * 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 "shared-bindings/_bleio/Connection.h" + +#include <string.h> +#include <stdio.h> + +#include "ble.h" +#include "ble_drv.h" +#include "ble_hci.h" +#include "nrf_soc.h" +#include "py/gc.h" +#include "py/objlist.h" +#include "py/objstr.h" +#include "py/qstr.h" +#include "py/runtime.h" +#include "shared-bindings/_bleio/__init__.h" +#include "shared-bindings/_bleio/Adapter.h" +#include "shared-bindings/_bleio/Attribute.h" +#include "shared-bindings/_bleio/Characteristic.h" +#include "shared-bindings/_bleio/Service.h" +#include "shared-bindings/_bleio/UUID.h" + +#define BLE_ADV_LENGTH_FIELD_SIZE 1 +#define BLE_ADV_AD_TYPE_FIELD_SIZE 1 +#define BLE_AD_TYPE_FLAGS_DATA_SIZE 1 + +static const ble_gap_sec_params_t pairing_sec_params = { + .bond = 1, + .mitm = 0, + .lesc = 0, + .keypress = 0, + .oob = 0, + .io_caps = BLE_GAP_IO_CAPS_NONE, + .min_key_size = 7, + .max_key_size = 16, + .kdist_own = { .enc = 1, .id = 1}, + .kdist_peer = { .enc = 1, .id = 1}, +}; + +static volatile bool m_discovery_in_process; +static volatile bool m_discovery_successful; + +static bleio_service_obj_t *m_char_discovery_service; +static bleio_characteristic_obj_t *m_desc_discovery_characteristic; + +bool dump_events = false; + +bool connection_on_ble_evt(ble_evt_t *ble_evt, void *self_in) { + bleio_connection_internal_t *self = (bleio_connection_internal_t*)self_in; + + if (BLE_GAP_EVT_BASE <= ble_evt->header.evt_id && ble_evt->header.evt_id <= BLE_GAP_EVT_LAST && + ble_evt->evt.gap_evt.conn_handle != self->conn_handle) { + return false; + } + if (BLE_GATTS_EVT_BASE <= ble_evt->header.evt_id && ble_evt->header.evt_id <= BLE_GATTS_EVT_LAST && + ble_evt->evt.gatts_evt.conn_handle != self->conn_handle) { + return false; + } + + // For debugging. + if (dump_events) { + mp_printf(&mp_plat_print, "Connection event: 0x%04x\n", ble_evt->header.evt_id); + } + + switch (ble_evt->header.evt_id) { + case BLE_GAP_EVT_DISCONNECTED: + break; + case BLE_GAP_EVT_CONN_PARAM_UPDATE: // 0x12 + break; + case BLE_GAP_EVT_PHY_UPDATE_REQUEST: { + ble_gap_phys_t const phys = { + .rx_phys = BLE_GAP_PHY_AUTO, + .tx_phys = BLE_GAP_PHY_AUTO, + }; + sd_ble_gap_phy_update(ble_evt->evt.gap_evt.conn_handle, &phys); + break; + } + + case BLE_GAP_EVT_PHY_UPDATE: // 0x22 + break; + + case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST: + // SoftDevice will respond to a length update request. + sd_ble_gap_data_length_update(self->conn_handle, NULL, NULL); + break; + + case BLE_GAP_EVT_DATA_LENGTH_UPDATE: // 0x24 + break; + + case BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST: { + // We only handle MTU of size BLE_GATT_ATT_MTU_DEFAULT. + sd_ble_gatts_exchange_mtu_reply(self->conn_handle, BLE_GATT_ATT_MTU_DEFAULT); + break; + } + + case BLE_GATTS_EVT_SYS_ATTR_MISSING: + sd_ble_gatts_sys_attr_set(self->conn_handle, NULL, 0, 0); + break; + + case BLE_GATTS_EVT_HVN_TX_COMPLETE: // Capture this for now. 0x55 + break; + + case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: { + ble_gap_evt_conn_param_update_request_t *request = + &ble_evt->evt.gap_evt.params.conn_param_update_request; + sd_ble_gap_conn_param_update(self->conn_handle, &request->conn_params); + break; + } + case BLE_GAP_EVT_SEC_PARAMS_REQUEST: { + ble_gap_sec_keyset_t keyset = { + .keys_own = { + .p_enc_key = &self->bonding_keys.own_enc, + .p_id_key = NULL, + .p_sign_key = NULL, + .p_pk = NULL + }, + + .keys_peer = { + .p_enc_key = &self->bonding_keys.peer_enc, + .p_id_key = &self->bonding_keys.peer_id, + .p_sign_key = NULL, + .p_pk = NULL + } + }; + + sd_ble_gap_sec_params_reply(self->conn_handle, BLE_GAP_SEC_STATUS_SUCCESS, + &pairing_sec_params, &keyset); + break; + } + + case BLE_GAP_EVT_LESC_DHKEY_REQUEST: + // TODO for LESC pairing: + // sd_ble_gap_lesc_dhkey_reply(...); + break; + + case BLE_GAP_EVT_AUTH_STATUS: { // 0x19 + // Pairing process completed + ble_gap_evt_auth_status_t* status = &ble_evt->evt.gap_evt.params.auth_status; + if (BLE_GAP_SEC_STATUS_SUCCESS == status->auth_status) { + // TODO _ediv = bonding_keys->own_enc.master_id.ediv; + self->pair_status = PAIR_PAIRED; + } else { + self->pair_status = PAIR_NOT_PAIRED; + } + break; + } + + case BLE_GAP_EVT_SEC_INFO_REQUEST: { // 0x14 + // Peer asks for the stored keys. + // - load key and return if bonded previously. + // - Else return NULL --> Initiate key exchange + ble_gap_evt_sec_info_request_t* sec_info_request = &ble_evt->evt.gap_evt.params.sec_info_request; + (void) sec_info_request; + //if ( bond_load_keys(_role, sec_req->master_id.ediv, &bkeys) ) { + //sd_ble_gap_sec_info_reply(_conn_hdl, &bkeys.own_enc.enc_info, &bkeys.peer_id.id_info, NULL); + // + //_ediv = bkeys.own_enc.master_id.ediv; + // } else { + sd_ble_gap_sec_info_reply(self->conn_handle, NULL, NULL, NULL); + // } + break; + } + + case BLE_GAP_EVT_CONN_SEC_UPDATE: { // 0x1a + ble_gap_conn_sec_t* conn_sec = &ble_evt->evt.gap_evt.params.conn_sec_update.conn_sec; + if (conn_sec->sec_mode.sm <= 1 && conn_sec->sec_mode.lv <= 1) { + // Security setup did not succeed: + // mode 0, level 0 means no access + // mode 1, level 1 means open link + // mode >=1 and/or level >=1 means encryption is set up + self->pair_status = PAIR_NOT_PAIRED; + } else { + //if ( !bond_load_cccd(_role, _conn_hdl, _ediv) ) { + if (true) { // TODO: no bonding yet + // Initialize system attributes fresh. + sd_ble_gatts_sys_attr_set(self->conn_handle, NULL, 0, 0); + } + // Not quite paired yet: wait for BLE_GAP_EVT_AUTH_STATUS SUCCESS. + self->ediv = self->bonding_keys.own_enc.master_id.ediv; + } + break; + } + + + default: + // For debugging. + if (dump_events) { + mp_printf(&mp_plat_print, "Unhandled connection event: 0x%04x\n", ble_evt->header.evt_id); + } + + return false; + } + return true; +} + +void bleio_connection_clear(bleio_connection_internal_t *self) { + self->remote_service_list = NULL; + + self->conn_handle = BLE_CONN_HANDLE_INVALID; + self->pair_status = PAIR_NOT_PAIRED; + + memset(&self->bonding_keys, 0, sizeof(self->bonding_keys)); +} + +bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { + if (self->connection == NULL) { + return false; + } + return self->connection->conn_handle != BLE_CONN_HANDLE_INVALID; +} + +void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self) { + sd_ble_gap_disconnect(self->conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); +} + +void common_hal_bleio_connection_pair(bleio_connection_internal_t *self) { + self->pair_status = PAIR_WAITING; + + uint32_t err_code = sd_ble_gap_authenticate(self->conn_handle, &pairing_sec_params); + + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to start pairing, NRF_ERROR_%q"), MP_OBJ_QSTR_VALUE(base_error_messages[err_code - NRF_ERROR_BASE_NUM])); + } + + while (self->pair_status == PAIR_WAITING) { + RUN_BACKGROUND_TASKS; + } + + if (self->pair_status == PAIR_NOT_PAIRED) { + mp_raise_OSError_msg(translate("Failed to pair")); + } +} + + +// service_uuid may be NULL, to discover all services. +STATIC bool discover_next_services(bleio_connection_internal_t* connection, uint16_t start_handle, ble_uuid_t *service_uuid) { + m_discovery_successful = false; + m_discovery_in_process = true; + + uint32_t err_code = sd_ble_gattc_primary_services_discover(connection->conn_handle, start_handle, service_uuid); + + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg(translate("Failed to discover services")); + } + + // Wait for a discovery event. + while (m_discovery_in_process) { + MICROPY_VM_HOOK_LOOP; + } + return m_discovery_successful; +} + +STATIC bool discover_next_characteristics(bleio_connection_internal_t* connection, bleio_service_obj_t *service, uint16_t start_handle) { + m_char_discovery_service = service; + + ble_gattc_handle_range_t handle_range; + handle_range.start_handle = start_handle; + handle_range.end_handle = service->end_handle; + + m_discovery_successful = false; + m_discovery_in_process = true; + + uint32_t err_code = sd_ble_gattc_characteristics_discover(connection->conn_handle, &handle_range); + if (err_code != NRF_SUCCESS) { + return false; + } + + // Wait for a discovery event. + while (m_discovery_in_process) { + MICROPY_VM_HOOK_LOOP; + } + return m_discovery_successful; +} + +STATIC bool discover_next_descriptors(bleio_connection_internal_t* connection, bleio_characteristic_obj_t *characteristic, uint16_t start_handle, uint16_t end_handle) { + m_desc_discovery_characteristic = characteristic; + + ble_gattc_handle_range_t handle_range; + handle_range.start_handle = start_handle; + handle_range.end_handle = end_handle; + + m_discovery_successful = false; + m_discovery_in_process = true; + + uint32_t err_code = sd_ble_gattc_descriptors_discover(connection->conn_handle, &handle_range); + if (err_code != NRF_SUCCESS) { + return false; + } + + // Wait for a discovery event. + while (m_discovery_in_process) { + MICROPY_VM_HOOK_LOOP; + } + return m_discovery_successful; +} + +STATIC void on_primary_srv_discovery_rsp(ble_gattc_evt_prim_srvc_disc_rsp_t *response, bleio_connection_internal_t* connection) { + bleio_service_obj_t* tail = connection->remote_service_list; + + for (size_t i = 0; i < response->count; ++i) { + ble_gattc_service_t *gattc_service = &response->services[i]; + + bleio_service_obj_t *service = m_new_obj(bleio_service_obj_t); + service->base.type = &bleio_service_type; + + // Initialize several fields at once. + bleio_service_from_connection(service, bleio_connection_new_from_internal(connection)); + + service->is_remote = true; + service->start_handle = gattc_service->handle_range.start_handle; + service->end_handle = gattc_service->handle_range.end_handle; + service->handle = gattc_service->handle_range.start_handle; + + if (gattc_service->uuid.type != BLE_UUID_TYPE_UNKNOWN) { + // Known service UUID. + bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t); + uuid->base.type = &bleio_uuid_type; + bleio_uuid_construct_from_nrf_ble_uuid(uuid, &gattc_service->uuid); + service->uuid = uuid; + } else { + // The discovery response contained a 128-bit UUID that has not yet been registered with the + // softdevice via sd_ble_uuid_vs_add(). We need to fetch the 128-bit value and register it. + // For now, just set the UUID to NULL. + service->uuid = NULL; + } + + service->next = tail; + tail = service; + } + + connection->remote_service_list = tail; + + if (response->count > 0) { + m_discovery_successful = true; + } + m_discovery_in_process = false; +} + +STATIC void on_char_discovery_rsp(ble_gattc_evt_char_disc_rsp_t *response, bleio_connection_internal_t* connection) { + for (size_t i = 0; i < response->count; ++i) { + ble_gattc_char_t *gattc_char = &response->chars[i]; + + bleio_characteristic_obj_t *characteristic = m_new_obj(bleio_characteristic_obj_t); + characteristic->base.type = &bleio_characteristic_type; + + bleio_uuid_obj_t *uuid = NULL; + + if (gattc_char->uuid.type != BLE_UUID_TYPE_UNKNOWN) { + // Known characteristic UUID. + uuid = m_new_obj(bleio_uuid_obj_t); + uuid->base.type = &bleio_uuid_type; + bleio_uuid_construct_from_nrf_ble_uuid(uuid, &gattc_char->uuid); + } else { + // The discovery response contained a 128-bit UUID that has not yet been registered with the + // softdevice via sd_ble_uuid_vs_add(). We need to fetch the 128-bit value and register it. + // For now, just leave the UUID as NULL. + } + + bleio_characteristic_properties_t props = + (gattc_char->char_props.broadcast ? CHAR_PROP_BROADCAST : 0) | + (gattc_char->char_props.indicate ? CHAR_PROP_INDICATE : 0) | + (gattc_char->char_props.notify ? CHAR_PROP_NOTIFY : 0) | + (gattc_char->char_props.read ? CHAR_PROP_READ : 0) | + (gattc_char->char_props.write ? CHAR_PROP_WRITE : 0) | + (gattc_char->char_props.write_wo_resp ? 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, m_char_discovery_service, gattc_char->handle_value, uuid, + props, SECURITY_MODE_OPEN, SECURITY_MODE_OPEN, + GATT_MAX_DATA_LENGTH, false, // max_length, fixed_length: values may not matter for gattc + NULL); + + mp_obj_list_append(m_char_discovery_service->characteristic_list, MP_OBJ_FROM_PTR(characteristic)); + } + + if (response->count > 0) { + m_discovery_successful = true; + } + m_discovery_in_process = false; +} + +STATIC void on_desc_discovery_rsp(ble_gattc_evt_desc_disc_rsp_t *response, bleio_connection_internal_t* connection) { + for (size_t i = 0; i < response->count; ++i) { + ble_gattc_desc_t *gattc_desc = &response->descs[i]; + + // Remember handles for certain well-known descriptors. + switch (gattc_desc->uuid.uuid) { + case BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG: + m_desc_discovery_characteristic->cccd_handle = gattc_desc->handle; + break; + + case BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG: + m_desc_discovery_characteristic->sccd_handle = gattc_desc->handle; + break; + + case BLE_UUID_DESCRIPTOR_CHAR_USER_DESC: + m_desc_discovery_characteristic->user_desc_handle = gattc_desc->handle; + break; + + default: + // TODO: sd_ble_gattc_descriptors_discover() can return things that are not descriptors, + // so ignore those. + // https://devzone.nordicsemi.com/f/nordic-q-a/49500/sd_ble_gattc_descriptors_discover-is-returning-attributes-that-are-not-descriptors + break; + } + + bleio_descriptor_obj_t *descriptor = m_new_obj(bleio_descriptor_obj_t); + descriptor->base.type = &bleio_descriptor_type; + + bleio_uuid_obj_t *uuid = NULL; + + if (gattc_desc->uuid.type != BLE_UUID_TYPE_UNKNOWN) { + // Known descriptor UUID. + uuid = m_new_obj(bleio_uuid_obj_t); + uuid->base.type = &bleio_uuid_type; + bleio_uuid_construct_from_nrf_ble_uuid(uuid, &gattc_desc->uuid); + } else { + // The discovery response contained a 128-bit UUID that has not yet been registered with the + // softdevice via sd_ble_uuid_vs_add(). We need to fetch the 128-bit value and register it. + // For now, just leave the UUID as NULL. + } + + common_hal_bleio_descriptor_construct( + descriptor, m_desc_discovery_characteristic, uuid, + SECURITY_MODE_OPEN, SECURITY_MODE_OPEN, + GATT_MAX_DATA_LENGTH, false, mp_const_empty_bytes); + descriptor->handle = gattc_desc->handle; + + mp_obj_list_append(m_desc_discovery_characteristic->descriptor_list, MP_OBJ_FROM_PTR(descriptor)); + } + + if (response->count > 0) { + m_discovery_successful = true; + } + m_discovery_in_process = false; +} + +STATIC bool discovery_on_ble_evt(ble_evt_t *ble_evt, mp_obj_t payload) { + bleio_connection_internal_t* connection = MP_OBJ_TO_PTR(payload); + switch (ble_evt->header.evt_id) { + case BLE_GAP_EVT_DISCONNECTED: + m_discovery_successful = false; + m_discovery_in_process = false; + break; + + case BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP: + on_primary_srv_discovery_rsp(&ble_evt->evt.gattc_evt.params.prim_srvc_disc_rsp, connection); + break; + + case BLE_GATTC_EVT_CHAR_DISC_RSP: + on_char_discovery_rsp(&ble_evt->evt.gattc_evt.params.char_disc_rsp, connection); + break; + + case BLE_GATTC_EVT_DESC_DISC_RSP: + on_desc_discovery_rsp(&ble_evt->evt.gattc_evt.params.desc_disc_rsp, connection); + break; + + default: + // For debugging. + // mp_printf(&mp_plat_print, "Unhandled discovery event: 0x%04x\n", ble_evt->header.evt_id); + return false; + break; + } + return true; +} + +STATIC void discover_remote_services(bleio_connection_internal_t *self, mp_obj_t service_uuids_whitelist) { + ble_drv_add_event_handler(discovery_on_ble_evt, self); + + // Start over with an empty list. + self->remote_service_list = NULL; + + if (service_uuids_whitelist == mp_const_none) { + // List of service UUID's not given, so discover all available services. + + uint16_t next_service_start_handle = BLE_GATT_HANDLE_START; + + while (discover_next_services(self, next_service_start_handle, MP_OBJ_NULL)) { + // discover_next_services() appends to remote_services_list. + + // Get the most recently discovered service, and then ask for services + // whose handles start after the last attribute handle inside that service. + const bleio_service_obj_t *service = self->remote_service_list; + next_service_start_handle = service->end_handle + 1; + } + } 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_ValueError(translate("non-UUID found in service_uuids_whitelist")); + } + bleio_uuid_obj_t *uuid = MP_OBJ_TO_PTR(uuid_obj); + + ble_uuid_t nrf_uuid; + bleio_uuid_convert_to_nrf_ble_uuid(uuid, &nrf_uuid); + + // Service might or might not be discovered; that's ok. Caller has to check + // Central.remote_services to find out. + // We only need to call this once for each service to discover. + discover_next_services(self, BLE_GATT_HANDLE_START, &nrf_uuid); + } + } + + + bleio_service_obj_t *service = self->remote_service_list; + while (service != NULL) { + // Skip the service if it had an unknown (unregistered) UUID. + if (service->uuid == NULL) { + service = service->next; + continue; + } + + uint16_t next_char_start_handle = service->start_handle; + + // Stop when we go past the end of the range of handles for this service or + // discovery call returns nothing. + // discover_next_characteristics() appends to the characteristic_list. + while (next_char_start_handle <= service->end_handle && + discover_next_characteristics(self, service, next_char_start_handle)) { + + + // Get the most recently discovered characteristic, and then ask for characteristics + // whose handles start after the last attribute handle inside that characteristic. + const bleio_characteristic_obj_t *characteristic = + MP_OBJ_TO_PTR(service->characteristic_list->items[service->characteristic_list->len - 1]); + + next_char_start_handle = characteristic->handle + 1; + } + + // 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]); + 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]); + + // Skip the characteristic if it had an unknown (unregistered) UUID. + if (characteristic->uuid == NULL) { + continue; + } + + uint16_t next_desc_start_handle = characteristic->handle + 1; + + // Don't run past the end of this service or the beginning of the next characteristic. + uint16_t next_desc_end_handle = next_characteristic == NULL + ? service->end_handle + : next_characteristic->handle - 1; + + // Stop when we go past the end of the range of handles for this service or + // discovery call returns nothing. + // discover_next_descriptors() appends to the descriptor_list. + while (next_desc_start_handle <= service->end_handle && + next_desc_start_handle < next_desc_end_handle && + discover_next_descriptors(self, characteristic, + next_desc_start_handle, next_desc_end_handle)) { + + // Get the most recently discovered descriptor, and then ask for descriptors + // whose handles start after that descriptor's handle. + const bleio_descriptor_obj_t *descriptor = characteristic->descriptor_list; + next_desc_start_handle = descriptor->handle + 1; + } + } + service = service->next; + } + + // This event handler is no longer needed. + ble_drv_remove_event_handler(discovery_on_ble_evt, self); + +} +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); + // Convert to a tuple and then clear the list so the callee will take ownership. + mp_obj_tuple_t *services_tuple = service_linked_list_to_tuple(self->connection->remote_service_list); + self->connection->remote_service_list = NULL; + + return services_tuple; +} + + +uint16_t bleio_connection_get_conn_handle(bleio_connection_obj_t *self) { + if (self == NULL || self->connection == NULL) { + return BLE_CONN_HANDLE_INVALID; + } + return self->connection->conn_handle; +} + +mp_obj_t bleio_connection_new_from_internal(bleio_connection_internal_t* internal) { + if (internal->connection_obj != mp_const_none) { + return internal->connection_obj; + } + bleio_connection_obj_t *connection = m_new_obj(bleio_connection_obj_t); + connection->base.type = &bleio_connection_type; + connection->connection = internal; + internal->connection_obj = connection; + + return MP_OBJ_FROM_PTR(connection); +} diff --git a/ports/nrf/common-hal/_bleio/Peripheral.h b/ports/nrf/common-hal/_bleio/Connection.h index a4f62d288..2d4aad0c8 100644 --- a/ports/nrf/common-hal/_bleio/Peripheral.h +++ b/ports/nrf/common-hal/_bleio/Connection.h @@ -25,8 +25,8 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_PERIPHERAL_H -#define MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_PERIPHERAL_H +#ifndef MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_CONNECTION_H +#define MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_CONNECTION_H #include <stdbool.h> @@ -37,6 +37,7 @@ #include "common-hal/_bleio/__init__.h" #include "shared-module/_bleio/Address.h" +#include "common-hal/_bleio/Service.h" typedef enum { PAIR_NOT_PAIRED, @@ -44,24 +45,35 @@ typedef enum { PAIR_PAIRED, } pair_status_t; +// We split the Connection object into two so that the internal mechanics can live outside of the +// VM. If it were one object, then we'd risk user code seeing a connection object of theirs be +// reused. typedef struct { - mp_obj_base_t base; - mp_obj_t name; - volatile uint16_t conn_handle; - // Services provided by this peripheral. - mp_obj_list_t *service_list; + uint16_t conn_handle; + bool is_central; // Remote services discovered when this peripheral is acting as a client. - mp_obj_list_t *remote_service_list; + bleio_service_obj_t *remote_service_list; // The advertising data and scan response buffers are held by us, not by the SD, so we must // maintain them and not change it. If we need to change the contents during advertising, // there are tricks to get the SD to notice (see DevZone - TBS). // EDIV: Encrypted Diversifier: Identifies LTK during legacy pairing. bonding_keys_t bonding_keys; uint16_t ediv; - uint8_t* advertising_data; - uint8_t* scan_response_data; - uint8_t adv_handle; pair_status_t pair_status; -} bleio_peripheral_obj_t; + mp_obj_t connection_obj; + ble_drv_evt_handler_entry_t handler_entry; +} bleio_connection_internal_t; + +typedef struct { + mp_obj_base_t base; + bleio_connection_internal_t* connection; + // The HCI disconnect reason. + uint8_t disconnect_reason; +} bleio_connection_obj_t; + +bool connection_on_ble_evt(ble_evt_t *ble_evt, void *self_in); + +uint16_t bleio_connection_get_conn_handle(bleio_connection_obj_t *self); +mp_obj_t bleio_connection_new_from_internal(bleio_connection_internal_t* connection); -#endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_PERIPHERAL_H +#endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_CONNECTION_H diff --git a/ports/nrf/common-hal/_bleio/Descriptor.c b/ports/nrf/common-hal/_bleio/Descriptor.c index 137f004ba..faf50c658 100644 --- a/ports/nrf/common-hal/_bleio/Descriptor.c +++ b/ports/nrf/common-hal/_bleio/Descriptor.c @@ -33,8 +33,6 @@ #include "shared-bindings/_bleio/Service.h" #include "shared-bindings/_bleio/UUID.h" -static volatile bleio_descriptor_obj_t *m_read_descriptor; - 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) { self->characteristic = characteristic; self->uuid = uuid; @@ -61,62 +59,18 @@ bleio_characteristic_obj_t *common_hal_bleio_descriptor_get_characteristic(bleio return self->characteristic; } -STATIC void descriptor_on_gattc_read_rsp_evt(ble_evt_t *ble_evt, void *param) { - switch (ble_evt->header.evt_id) { - - // More events may be handled later, so keep this as a switch. - - case BLE_GATTC_EVT_READ_RSP: { - ble_gattc_evt_read_rsp_t *response = &ble_evt->evt.gattc_evt.params.read_rsp; - if (m_read_descriptor) { - m_read_descriptor->value = mp_obj_new_bytearray(response->len, response->data); - } - // Indicate to busy-wait loop that we've read the attribute value. - m_read_descriptor = NULL; - break; - } - - default: - // For debugging. - // mp_printf(&mp_plat_print, "Unhandled descriptor event: 0x%04x\n", ble_evt->header.evt_id); - break; - } -} - -STATIC void descriptor_gattc_read(bleio_descriptor_obj_t *descriptor) { - const uint16_t conn_handle = - common_hal_bleio_device_get_conn_handle(descriptor->characteristic->service->device); - common_hal_bleio_check_connected(conn_handle); - - // Set to NULL in event loop after event. - m_read_descriptor = descriptor; - - ble_drv_add_event_handler(descriptor_on_gattc_read_rsp_evt, descriptor); - - const uint32_t err_code = sd_ble_gattc_read(conn_handle, descriptor->handle, 0); - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to read attribute value, err 0x%04x"), err_code); - } - - while (m_read_descriptor != NULL) { - MICROPY_VM_HOOK_LOOP; - } - - ble_drv_remove_event_handler(descriptor_on_gattc_read_rsp_evt, descriptor); -} - -mp_obj_t common_hal_bleio_descriptor_get_value(bleio_descriptor_obj_t *self) { +size_t common_hal_bleio_descriptor_get_value(bleio_descriptor_obj_t *self, uint8_t* buf, size_t len) { // Do GATT operations only if this descriptor has been registered if (self->handle != BLE_GATT_HANDLE_INVALID) { + uint16_t conn_handle = bleio_connection_get_conn_handle(self->characteristic->service->connection); if (common_hal_bleio_service_get_is_remote(self->characteristic->service)) { - descriptor_gattc_read(self); + return common_hal_bleio_gattc_read(self->handle, conn_handle, buf, len); } else { - self->value = common_hal_bleio_gatts_read( - self->handle, common_hal_bleio_device_get_conn_handle(self->characteristic->service->device)); + return common_hal_bleio_gatts_read(self->handle, conn_handle, buf, len); } } - return self->value; + return 0; } void common_hal_bleio_descriptor_set_value(bleio_descriptor_obj_t *self, mp_buffer_info_t *bufinfo) { @@ -131,7 +85,7 @@ void common_hal_bleio_descriptor_set_value(bleio_descriptor_obj_t *self, mp_buff // Do GATT operations only if this descriptor has been registered. if (self->handle != BLE_GATT_HANDLE_INVALID) { - uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(self->characteristic->service->device); + uint16_t conn_handle = bleio_connection_get_conn_handle(self->characteristic->service->connection); if (common_hal_bleio_service_get_is_remote(self->characteristic->service)) { // false means WRITE_REQ, not write-no-response common_hal_bleio_gattc_write(self->handle, conn_handle, bufinfo, false); diff --git a/ports/nrf/common-hal/_bleio/Descriptor.h b/ports/nrf/common-hal/_bleio/Descriptor.h index 3adb0df18..c70547c08 100644 --- a/ports/nrf/common-hal/_bleio/Descriptor.h +++ b/ports/nrf/common-hal/_bleio/Descriptor.h @@ -31,13 +31,15 @@ #include "py/obj.h" -#include "common-hal/_bleio/Characteristic.h" #include "common-hal/_bleio/UUID.h" -typedef struct { +// Forward declare characteristic because it includes a Descriptor. +struct _bleio_characteristic_obj; + +typedef struct _bleio_descriptor_obj { mp_obj_base_t base; // Will be MP_OBJ_NULL before being assigned to a Characteristic. - bleio_characteristic_obj_t *characteristic; + struct _bleio_characteristic_obj *characteristic; bleio_uuid_obj_t *uuid; mp_obj_t value; uint16_t max_length; @@ -45,6 +47,7 @@ typedef struct { uint16_t handle; bleio_attribute_security_mode_t read_perm; bleio_attribute_security_mode_t write_perm; + struct _bleio_descriptor_obj* next; } bleio_descriptor_obj_t; #endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_DESCRIPTOR_H diff --git a/ports/nrf/common-hal/_bleio/Peripheral.c b/ports/nrf/common-hal/_bleio/Peripheral.c deleted file mode 100644 index 9b0f96d48..000000000 --- a/ports/nrf/common-hal/_bleio/Peripheral.c +++ /dev/null @@ -1,361 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2018 Dan Halbert for Adafruit Industries - * Copyright (c) 2018 Artur Pacholec - * - * 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 <string.h> -#include <stdio.h> - -#include "ble.h" -#include "ble_drv.h" -#include "ble_hci.h" -#include "nrf_soc.h" -#include "py/gc.h" -#include "py/objlist.h" -#include "py/objstr.h" -#include "py/runtime.h" -#include "shared-bindings/_bleio/__init__.h" -#include "shared-bindings/_bleio/Adapter.h" -#include "shared-bindings/_bleio/Attribute.h" -#include "shared-bindings/_bleio/Characteristic.h" -#include "shared-bindings/_bleio/Peripheral.h" -#include "shared-bindings/_bleio/Service.h" -#include "shared-bindings/_bleio/UUID.h" - -#define BLE_ADV_LENGTH_FIELD_SIZE 1 -#define BLE_ADV_AD_TYPE_FIELD_SIZE 1 -#define BLE_AD_TYPE_FLAGS_DATA_SIZE 1 - -static const ble_gap_sec_params_t pairing_sec_params = { - .bond = 1, - .mitm = 0, - .lesc = 0, - .keypress = 0, - .oob = 0, - .io_caps = BLE_GAP_IO_CAPS_NONE, - .min_key_size = 7, - .max_key_size = 16, - .kdist_own = { .enc = 1, .id = 1}, - .kdist_peer = { .enc = 1, .id = 1}, -}; - -STATIC void check_data_fit(size_t data_len) { - if (data_len > BLE_GAP_ADV_SET_DATA_SIZE_MAX) { - mp_raise_ValueError(translate("Data too large for advertisement packet")); - } -} - -STATIC void peripheral_on_ble_evt(ble_evt_t *ble_evt, void *self_in) { - bleio_peripheral_obj_t *self = (bleio_peripheral_obj_t*)self_in; - - // For debugging. - // mp_printf(&mp_plat_print, "Peripheral event: 0x%04x\n", ble_evt->header.evt_id); - - switch (ble_evt->header.evt_id) { - case BLE_GAP_EVT_CONNECTED: { - // Central has connected. - ble_gap_evt_connected_t* connected = &ble_evt->evt.gap_evt.params.connected; - - self->conn_handle = ble_evt->evt.gap_evt.conn_handle; - - // See if connection interval set by Central is out of range. - // If so, negotiate our preferred range. - ble_gap_conn_params_t conn_params; - sd_ble_gap_ppcp_get(&conn_params); - if (conn_params.min_conn_interval < connected->conn_params.min_conn_interval || - conn_params.min_conn_interval > connected->conn_params.max_conn_interval) { - sd_ble_gap_conn_param_update(ble_evt->evt.gap_evt.conn_handle, &conn_params); - } - break; - } - - case BLE_GAP_EVT_DISCONNECTED: - // Central has disconnected. - self->conn_handle = BLE_CONN_HANDLE_INVALID; - break; - - case BLE_GAP_EVT_PHY_UPDATE_REQUEST: { - ble_gap_phys_t const phys = { - .rx_phys = BLE_GAP_PHY_AUTO, - .tx_phys = BLE_GAP_PHY_AUTO, - }; - sd_ble_gap_phy_update(ble_evt->evt.gap_evt.conn_handle, &phys); - break; - } - - case BLE_GAP_EVT_ADV_SET_TERMINATED: - // TODO: Someday may handle timeouts or limit reached. - break; - - case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST: - // SoftDevice will respond to a length update request. - sd_ble_gap_data_length_update(self->conn_handle, NULL, NULL); - break; - - case BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST: { - // We only handle MTU of size BLE_GATT_ATT_MTU_DEFAULT. - sd_ble_gatts_exchange_mtu_reply(self->conn_handle, BLE_GATT_ATT_MTU_DEFAULT); - break; - } - - case BLE_GATTS_EVT_SYS_ATTR_MISSING: - sd_ble_gatts_sys_attr_set(self->conn_handle, NULL, 0, 0); - break; - - case BLE_GAP_EVT_SEC_PARAMS_REQUEST: { - ble_gap_sec_keyset_t keyset = { - .keys_own = { - .p_enc_key = &self->bonding_keys.own_enc, - .p_id_key = NULL, - .p_sign_key = NULL, - .p_pk = NULL - }, - - .keys_peer = { - .p_enc_key = &self->bonding_keys.peer_enc, - .p_id_key = &self->bonding_keys.peer_id, - .p_sign_key = NULL, - .p_pk = NULL - } - }; - - sd_ble_gap_sec_params_reply(self->conn_handle, BLE_GAP_SEC_STATUS_SUCCESS, - &pairing_sec_params, &keyset); - break; - } - - case BLE_GAP_EVT_LESC_DHKEY_REQUEST: - // TODO for LESC pairing: - // sd_ble_gap_lesc_dhkey_reply(...); - break; - - case BLE_GAP_EVT_AUTH_STATUS: { - // Pairing process completed - ble_gap_evt_auth_status_t* status = &ble_evt->evt.gap_evt.params.auth_status; - if (BLE_GAP_SEC_STATUS_SUCCESS == status->auth_status) { - // TODO _ediv = bonding_keys->own_enc.master_id.ediv; - self->pair_status = PAIR_PAIRED; - } else { - self->pair_status = PAIR_NOT_PAIRED; - } - break; - } - - case BLE_GAP_EVT_SEC_INFO_REQUEST: { - // Peer asks for the stored keys. - // - load key and return if bonded previously. - // - Else return NULL --> Initiate key exchange - ble_gap_evt_sec_info_request_t* sec_info_request = &ble_evt->evt.gap_evt.params.sec_info_request; - (void) sec_info_request; - //if ( bond_load_keys(_role, sec_req->master_id.ediv, &bkeys) ) { - //sd_ble_gap_sec_info_reply(_conn_hdl, &bkeys.own_enc.enc_info, &bkeys.peer_id.id_info, NULL); - // - //_ediv = bkeys.own_enc.master_id.ediv; - // } else { - sd_ble_gap_sec_info_reply(self->conn_handle, NULL, NULL, NULL); - // } - break; - } - - case BLE_GAP_EVT_CONN_SEC_UPDATE: { - ble_gap_conn_sec_t* conn_sec = &ble_evt->evt.gap_evt.params.conn_sec_update.conn_sec; - if (conn_sec->sec_mode.sm <= 1 && conn_sec->sec_mode.lv <= 1) { - // Security setup did not succeed: - // mode 0, level 0 means no access - // mode 1, level 1 means open link - // mode >=1 and/or level >=1 means encryption is set up - self->pair_status = PAIR_NOT_PAIRED; - } else { - //if ( !bond_load_cccd(_role, _conn_hdl, _ediv) ) { - if (true) { // TODO: no bonding yet - // Initialize system attributes fresh. - sd_ble_gatts_sys_attr_set(self->conn_handle, NULL, 0, 0); - } - // Not quite paired yet: wait for BLE_GAP_EVT_AUTH_STATUS SUCCESS. - self->ediv = self->bonding_keys.own_enc.master_id.ediv; - } - break; - } - - - default: - // For debugging. - // mp_printf(&mp_plat_print, "Unhandled peripheral event: 0x%04x\n", ble_evt->header.evt_id); - break; - } -} - -void common_hal_bleio_peripheral_construct(bleio_peripheral_obj_t *self, mp_obj_t name) { - common_hal_bleio_adapter_set_enabled(true); - - self->service_list = mp_obj_new_list(0, NULL); - // Used only for discovery when acting as a client. - self->remote_service_list = mp_obj_new_list(0, NULL); - self->name = name; - - self->conn_handle = BLE_CONN_HANDLE_INVALID; - self->adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET; - self->pair_status = PAIR_NOT_PAIRED; - - memset(&self->bonding_keys, 0, sizeof(self->bonding_keys)); -} - -void common_hal_bleio_peripheral_add_service(bleio_peripheral_obj_t *self, bleio_service_obj_t *service) { - ble_uuid_t uuid; - bleio_uuid_convert_to_nrf_ble_uuid(service->uuid, &uuid); - - uint8_t service_type = BLE_GATTS_SRVC_TYPE_PRIMARY; - if (common_hal_bleio_service_get_is_secondary(service)) { - service_type = BLE_GATTS_SRVC_TYPE_SECONDARY; - } - - const uint32_t err_code = sd_ble_gatts_service_add(service_type, &uuid, &service->handle); - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to add service, err 0x%04x"), err_code); - } - - mp_obj_list_append(self->service_list, MP_OBJ_FROM_PTR(service)); -} - -mp_obj_list_t *common_hal_bleio_peripheral_get_services(bleio_peripheral_obj_t *self) { - return self->service_list; -} - -bool common_hal_bleio_peripheral_get_connected(bleio_peripheral_obj_t *self) { - return self->conn_handle != BLE_CONN_HANDLE_INVALID; -} - -mp_obj_t common_hal_bleio_peripheral_get_name(bleio_peripheral_obj_t *self) { - return self->name; -} - -void common_hal_bleio_peripheral_start_advertising(bleio_peripheral_obj_t *self, bool connectable, mp_float_t interval, mp_buffer_info_t *advertising_data_bufinfo, mp_buffer_info_t *scan_response_data_bufinfo) { - - // interval value has already been validated. - - if (connectable) { - ble_drv_add_event_handler(peripheral_on_ble_evt, self); - } - - common_hal_bleio_adapter_set_enabled(true); - - uint32_t err_code; - - GET_STR_DATA_LEN(self->name, name_data, name_len); - if (name_len > 0) { - // Set device name, and make it available to anyone. - ble_gap_conn_sec_mode_t sec_mode; - BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode); - err_code = sd_ble_gap_device_name_set(&sec_mode, name_data, name_len); - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to set device name, err 0x%04x"), err_code); - - } - } - - check_data_fit(advertising_data_bufinfo->len); - // The advertising data buffers must not move, because the SoftDevice depends on them. - // So make them long-lived. - self->advertising_data = (uint8_t *) gc_alloc(BLE_GAP_ADV_SET_DATA_SIZE_MAX * sizeof(uint8_t), false, true); - memcpy(self->advertising_data, advertising_data_bufinfo->buf, advertising_data_bufinfo->len); - - check_data_fit(scan_response_data_bufinfo->len); - self->scan_response_data = (uint8_t *) gc_alloc(BLE_GAP_ADV_SET_DATA_SIZE_MAX * sizeof(uint8_t), false, true); - memcpy(self->scan_response_data, scan_response_data_bufinfo->buf, scan_response_data_bufinfo->len); - - - ble_gap_adv_params_t adv_params = { - .interval = SEC_TO_UNITS(interval, UNIT_0_625_MS), - .properties.type = connectable ? BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED - : BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED, - .duration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, - .filter_policy = BLE_GAP_ADV_FP_ANY, - .primary_phy = BLE_GAP_PHY_1MBPS, - }; - - common_hal_bleio_peripheral_stop_advertising(self); - - const ble_gap_adv_data_t ble_gap_adv_data = { - .adv_data.p_data = self->advertising_data, - .adv_data.len = advertising_data_bufinfo->len, - .scan_rsp_data.p_data = scan_response_data_bufinfo-> len > 0 ? self->scan_response_data : NULL, - .scan_rsp_data.len = scan_response_data_bufinfo->len, - }; - - err_code = sd_ble_gap_adv_set_configure(&self->adv_handle, &ble_gap_adv_data, &adv_params); - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to configure advertising, err 0x%04x"), err_code); - } - - err_code = sd_ble_gap_adv_start(self->adv_handle, BLE_CONN_CFG_TAG_CUSTOM); - - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to start advertising, err 0x%04x"), err_code); - } -} - -void common_hal_bleio_peripheral_stop_advertising(bleio_peripheral_obj_t *self) { - if (self->adv_handle == BLE_GAP_ADV_SET_HANDLE_NOT_SET) - return; - - const uint32_t err_code = sd_ble_gap_adv_stop(self->adv_handle); - - if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_INVALID_STATE)) { - mp_raise_OSError_msg_varg(translate("Failed to stop advertising, err 0x%04x"), err_code); - } -} - -void common_hal_bleio_peripheral_disconnect(bleio_peripheral_obj_t *self) { - sd_ble_gap_disconnect(self->conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); -} - -mp_obj_tuple_t *common_hal_bleio_peripheral_discover_remote_services(bleio_peripheral_obj_t *self, mp_obj_t service_uuids_whitelist) { - common_hal_bleio_device_discover_remote_services(MP_OBJ_FROM_PTR(self), service_uuids_whitelist); - // Convert to a tuple and then clear the list so the callee will take ownership. - mp_obj_tuple_t *services_tuple = mp_obj_new_tuple(self->remote_service_list->len, - self->remote_service_list->items); - mp_obj_list_clear(self->remote_service_list); - return services_tuple; -} - -void common_hal_bleio_peripheral_pair(bleio_peripheral_obj_t *self) { - self->pair_status = PAIR_WAITING; - - uint32_t err_code = sd_ble_gap_authenticate(self->conn_handle, &pairing_sec_params); - - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to start pairing, error 0x%04x"), err_code); - } - - while (self->pair_status == PAIR_WAITING) { - RUN_BACKGROUND_TASKS; - } - - if (self->pair_status == PAIR_NOT_PAIRED) { - mp_raise_OSError_msg(translate("Failed to pair")); - } - - -} diff --git a/ports/nrf/common-hal/_bleio/Scanner.c b/ports/nrf/common-hal/_bleio/Scanner.c deleted file mode 100644 index ec73f7eb8..000000000 --- a/ports/nrf/common-hal/_bleio/Scanner.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 <string.h> - -#include "ble_drv.h" -#include "ble_gap.h" -#include "py/mphal.h" -#include "py/objlist.h" -#include "py/runtime.h" -#include "shared-bindings/_bleio/Adapter.h" -#include "shared-bindings/_bleio/ScanEntry.h" -#include "shared-bindings/_bleio/Scanner.h" -#include "shared-module/_bleio/ScanEntry.h" - -static uint8_t m_scan_buffer_data[BLE_GAP_SCAN_BUFFER_MIN]; - -static ble_data_t m_scan_buffer = { - m_scan_buffer_data, - BLE_GAP_SCAN_BUFFER_MIN -}; - -STATIC void scanner_on_ble_evt(ble_evt_t *ble_evt, void *scanner_in) { - bleio_scanner_obj_t *scanner = (bleio_scanner_obj_t*)scanner_in; - ble_gap_evt_adv_report_t *report = &ble_evt->evt.gap_evt.params.adv_report; - - if (ble_evt->header.evt_id != BLE_GAP_EVT_ADV_REPORT) { - return; - } - - bleio_scanentry_obj_t *entry = m_new_obj(bleio_scanentry_obj_t); - entry->base.type = &bleio_scanentry_type; - entry->rssi = report->rssi; - - bleio_address_obj_t *address = m_new_obj(bleio_address_obj_t); - address->base.type = &bleio_address_type; - common_hal_bleio_address_construct(MP_OBJ_TO_PTR(address), - report->peer_addr.addr, report->peer_addr.addr_type); - entry->address = address; - - entry->data = mp_obj_new_bytes(report->data.p_data, report->data.len); - - mp_obj_list_append(scanner->scan_entries, MP_OBJ_FROM_PTR(entry)); - - const uint32_t err_code = sd_ble_gap_scan_start(NULL, &m_scan_buffer); - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to continue scanning, err 0x%04x"), err_code); - } -} - -void common_hal_bleio_scanner_construct(bleio_scanner_obj_t *self) { -} - -mp_obj_t common_hal_bleio_scanner_scan(bleio_scanner_obj_t *self, mp_float_t timeout, mp_float_t interval, mp_float_t window) { - common_hal_bleio_adapter_set_enabled(true); - ble_drv_add_event_handler(scanner_on_ble_evt, self); - - ble_gap_scan_params_t scan_params = { - .interval = SEC_TO_UNITS(interval, UNIT_0_625_MS), - .window = SEC_TO_UNITS(window, UNIT_0_625_MS), - .scan_phys = BLE_GAP_PHY_1MBPS, - }; - - self->scan_entries = mp_obj_new_list(0, NULL); - - uint32_t err_code; - err_code = sd_ble_gap_scan_start(&scan_params, &m_scan_buffer); - - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to start scanning, err 0x%04x"), err_code); - } - - mp_hal_delay_ms(timeout * 1000); - sd_ble_gap_scan_stop(); - - // Return list, and don't hang on to it, so it can be GC'd. - mp_obj_t entries = self->scan_entries; - self->scan_entries = MP_OBJ_NULL; - return entries; -} diff --git a/ports/nrf/common-hal/_bleio/Scanner.h b/ports/nrf/common-hal/_bleio/Scanner.h deleted file mode 100644 index 3768a52cb..000000000 --- a/ports/nrf/common-hal/_bleio/Scanner.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 - * - * 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_NRF_COMMON_HAL_BLEIO_SCANNER_H -#define MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_SCANNER_H - -#include "py/obj.h" - -typedef struct { - mp_obj_base_t base; - mp_obj_t scan_entries; - uint16_t interval; - uint16_t window; -} bleio_scanner_obj_t; - -#endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_SCANNER_H diff --git a/ports/nrf/common-hal/_bleio/Service.c b/ports/nrf/common-hal/_bleio/Service.c index 650cdc4ec..3362ed137 100644 --- a/ports/nrf/common-hal/_bleio/Service.c +++ b/ports/nrf/common-hal/_bleio/Service.c @@ -31,17 +31,44 @@ #include "common-hal/_bleio/__init__.h" #include "shared-bindings/_bleio/Characteristic.h" #include "shared-bindings/_bleio/Descriptor.h" -#include "shared-bindings/_bleio/Peripheral.h" #include "shared-bindings/_bleio/Service.h" #include "shared-bindings/_bleio/Adapter.h" -void common_hal_bleio_service_construct(bleio_service_obj_t *self, bleio_peripheral_obj_t *peripheral, bleio_uuid_obj_t *uuid, bool is_secondary) { - self->device = MP_OBJ_FROM_PTR(peripheral); +uint32_t _common_hal_bleio_service_construct(bleio_service_obj_t *self, bleio_uuid_obj_t *uuid, bool is_secondary, mp_obj_list_t * characteristic_list) { self->handle = 0xFFFF; self->uuid = uuid; - self->characteristic_list = mp_obj_new_list(0, NULL); + self->characteristic_list = characteristic_list; self->is_remote = false; + self->connection = NULL; self->is_secondary = is_secondary; + + ble_uuid_t nordic_uuid; + bleio_uuid_convert_to_nrf_ble_uuid(uuid, &nordic_uuid); + + uint8_t service_type = BLE_GATTS_SRVC_TYPE_PRIMARY; + if (is_secondary) { + service_type = BLE_GATTS_SRVC_TYPE_SECONDARY; + } + + vm_used_ble = true; + + return sd_ble_gatts_service_add(service_type, &nordic_uuid, &self->handle); +} + +void common_hal_bleio_service_construct(bleio_service_obj_t *self, bleio_uuid_obj_t *uuid, bool is_secondary) { + const uint32_t err_code = _common_hal_bleio_service_construct(self, uuid, is_secondary, mp_obj_new_list(0, NULL)); + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to create service, NRF_ERROR_%q"), MP_OBJ_QSTR_VALUE(base_error_messages[err_code - NRF_ERROR_BASE_NUM])); + } +} + +void bleio_service_from_connection(bleio_service_obj_t *self, mp_obj_t connection) { + self->handle = 0xFFFF; + self->uuid = NULL; + self->characteristic_list = mp_obj_new_list(0, NULL); + self->is_remote = true; + self->is_secondary = false; + self->connection = connection; } bleio_uuid_obj_t *common_hal_bleio_service_get_uuid(bleio_service_obj_t *self) { @@ -60,7 +87,9 @@ bool common_hal_bleio_service_get_is_secondary(bleio_service_obj_t *self) { return self->is_secondary; } -void common_hal_bleio_service_add_characteristic(bleio_service_obj_t *self, bleio_characteristic_obj_t *characteristic) { +void common_hal_bleio_service_add_characteristic(bleio_service_obj_t *self, + bleio_characteristic_obj_t *characteristic, + mp_buffer_info_t *initial_value_bufinfo) { ble_gatts_char_md_t char_md = { .char_props.broadcast = (characteristic->props & CHAR_PROP_BROADCAST) ? 1 : 0, .char_props.read = (characteristic->props & CHAR_PROP_READ) ? 1 : 0, @@ -93,14 +122,11 @@ void common_hal_bleio_service_add_characteristic(bleio_service_obj_t *self, blei bleio_attribute_gatts_set_security_mode(&char_attr_md.read_perm, characteristic->read_perm); bleio_attribute_gatts_set_security_mode(&char_attr_md.write_perm, characteristic->write_perm); - mp_buffer_info_t char_value_bufinfo; - mp_get_buffer_raise(characteristic->value, &char_value_bufinfo, MP_BUFFER_READ); - ble_gatts_attr_t char_attr = { .p_uuid = &char_uuid, .p_attr_md = &char_attr_md, - .init_len = char_value_bufinfo.len, - .p_value = char_value_bufinfo.buf, + .init_len = 0, + .p_value = NULL, .init_offs = 0, .max_len = characteristic->max_length, }; @@ -110,7 +136,7 @@ void common_hal_bleio_service_add_characteristic(bleio_service_obj_t *self, blei uint32_t err_code; err_code = sd_ble_gatts_characteristic_add(self->handle, &char_md, &char_attr, &char_handles); if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to add characteristic, err 0x%04x"), err_code); + mp_raise_OSError_msg_varg(translate("Failed to add characteristic, NRF_ERROR_%q"), MP_OBJ_QSTR_VALUE(base_error_messages[err_code - NRF_ERROR_BASE_NUM])); } characteristic->user_desc_handle = char_handles.user_desc_handle; diff --git a/ports/nrf/common-hal/_bleio/Service.h b/ports/nrf/common-hal/_bleio/Service.h index a4614b9f5..f101bc825 100644 --- a/ports/nrf/common-hal/_bleio/Service.h +++ b/ports/nrf/common-hal/_bleio/Service.h @@ -31,20 +31,22 @@ #include "py/objlist.h" #include "common-hal/_bleio/UUID.h" -typedef struct { +typedef struct bleio_service_obj { mp_obj_base_t base; - // Handle for this service. + // Handle for the local service. uint16_t handle; // True if created during discovery. bool is_remote; bool is_secondary; bleio_uuid_obj_t *uuid; - // May be a Peripheral, Central, etc. - mp_obj_t device; + mp_obj_t connection; mp_obj_list_t *characteristic_list; - // Range of attribute handles of this service. + // Range of attribute handles of this remote service. uint16_t start_handle; uint16_t end_handle; + struct bleio_service_obj* next; } bleio_service_obj_t; +void bleio_service_from_connection(bleio_service_obj_t *self, mp_obj_t connection); + #endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_SERVICE_H diff --git a/ports/nrf/common-hal/_bleio/UUID.c b/ports/nrf/common-hal/_bleio/UUID.c index 0e65b7d58..5bf35e6a5 100644 --- a/ports/nrf/common-hal/_bleio/UUID.c +++ b/ports/nrf/common-hal/_bleio/UUID.c @@ -30,6 +30,7 @@ #include "py/runtime.h" #include "common-hal/_bleio/UUID.h" +#include "shared-bindings/_bleio/__init__.h" #include "shared-bindings/_bleio/Adapter.h" #include "ble.h" @@ -39,9 +40,7 @@ // If uuid128 is NULL, this is a Bluetooth SIG 16-bit UUID. // If uuid128 is not NULL, it's a 128-bit (16-byte) UUID, with bytes 12 and 13 zero'd out, where // the 16-bit part goes. Those 16 bits are passed in uuid16. -void common_hal_bleio_uuid_construct(bleio_uuid_obj_t *self, uint32_t uuid16, uint8_t uuid128[]) { - common_hal_bleio_adapter_set_enabled(true); - +void common_hal_bleio_uuid_construct(bleio_uuid_obj_t *self, uint32_t uuid16, const uint8_t uuid128[]) { self->nrf_ble_uuid.uuid = uuid16; if (uuid128 == NULL) { self->nrf_ble_uuid.type = BLE_UUID_TYPE_BLE; @@ -54,6 +53,7 @@ void common_hal_bleio_uuid_construct(bleio_uuid_obj_t *self, uint32_t uuid16, ui if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg_varg(translate("Failed to register Vendor-Specific UUID, err 0x%04x"), err_code); } + vm_used_ble = true; } } @@ -65,25 +65,24 @@ uint32_t common_hal_bleio_uuid_get_uuid16(bleio_uuid_obj_t *self) { return self->nrf_ble_uuid.uuid; } -// True if uuid128 has been successfully filled in. -bool common_hal_bleio_uuid_get_uuid128(bleio_uuid_obj_t *self, uint8_t uuid128[16]) { +void common_hal_bleio_uuid_get_uuid128(bleio_uuid_obj_t *self, uint8_t uuid128[16]) { uint8_t length; const uint32_t err_code = sd_ble_uuid_encode(&self->nrf_ble_uuid, &length, uuid128); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg_varg(translate("Could not decode ble_uuid, err 0x%04x"), err_code); } - // If not 16 bytes, this is not a 128-bit UUID, so return. - return length == 16; } -// Returns 0 if this is a 16-bit UUID, otherwise returns a non-zero index -// into the 128-bit uuid registration table. -uint32_t common_hal_bleio_uuid_get_uuid128_reference(bleio_uuid_obj_t *self) { - return self->nrf_ble_uuid.type == BLE_UUID_TYPE_BLE ? 0 : self->nrf_ble_uuid.type; +void common_hal_bleio_uuid_pack_into(bleio_uuid_obj_t *self, uint8_t* buf) { + if (self->nrf_ble_uuid.type == BLE_UUID_TYPE_BLE) { + buf[0] = self->nrf_ble_uuid.uuid & 0xff; + buf[1] = self->nrf_ble_uuid.uuid >> 8; + } else { + common_hal_bleio_uuid_get_uuid128(self, buf); + } } - void bleio_uuid_construct_from_nrf_ble_uuid(bleio_uuid_obj_t *self, ble_uuid_t *nrf_ble_uuid) { if (nrf_ble_uuid->type == BLE_UUID_TYPE_UNKNOWN) { mp_raise_RuntimeError(translate("Unexpected nrfx uuid type")); diff --git a/ports/nrf/common-hal/_bleio/__init__.c b/ports/nrf/common-hal/_bleio/__init__.c index 553044190..1e07ea969 100644 --- a/ports/nrf/common-hal/_bleio/__init__.c +++ b/ports/nrf/common-hal/_bleio/__init__.c @@ -26,34 +26,58 @@ * THE SOFTWARE. */ +#include <string.h> + #include "py/runtime.h" #include "shared-bindings/_bleio/__init__.h" #include "shared-bindings/_bleio/Adapter.h" -#include "shared-bindings/_bleio/Central.h" #include "shared-bindings/_bleio/Characteristic.h" +#include "shared-bindings/_bleio/Connection.h" #include "shared-bindings/_bleio/Descriptor.h" -#include "shared-bindings/_bleio/Peripheral.h" #include "shared-bindings/_bleio/Service.h" #include "shared-bindings/_bleio/UUID.h" +#include "supervisor/shared/bluetooth.h" #include "common-hal/_bleio/__init__.h" -static volatile bool m_discovery_in_process; -static volatile bool m_discovery_successful; - -static bleio_service_obj_t *m_char_discovery_service; -static bleio_characteristic_obj_t *m_desc_discovery_characteristic; +const mp_obj_t base_error_messages[20] = { + MP_ROM_QSTR(MP_QSTR_SUCCESS), + MP_ROM_QSTR(MP_QSTR_SVC_HANDLER_MISSING), + MP_ROM_QSTR(MP_QSTR_SOFTDEVICE_NOT_ENABLED), + MP_ROM_QSTR(MP_QSTR_INTERNAL), + MP_ROM_QSTR(MP_QSTR_NO_MEM), + MP_ROM_QSTR(MP_QSTR_NOT_FOUND), + MP_ROM_QSTR(MP_QSTR_NOT_SUPPORTED), + MP_ROM_QSTR(MP_QSTR_INVALID_PARAM), + MP_ROM_QSTR(MP_QSTR_INVALID_STATE), + MP_ROM_QSTR(MP_QSTR_INVALID_LENGTH), + MP_ROM_QSTR(MP_QSTR_INVALID_FLAGS), + MP_ROM_QSTR(MP_QSTR_INVALID_DATA), + MP_ROM_QSTR(MP_QSTR_DATA_SIZE), + MP_ROM_QSTR(MP_QSTR_TIMEOUT), + MP_ROM_QSTR(MP_QSTR_NULL), + MP_ROM_QSTR(MP_QSTR_FORBIDDEN), + MP_ROM_QSTR(MP_QSTR_INVALID_ADDR), + MP_ROM_QSTR(MP_QSTR_BUSY), + MP_ROM_QSTR(MP_QSTR_CONN_COUNT), + MP_ROM_QSTR(MP_QSTR_RESOURCES), +}; // Turn off BLE on a reset or reload. void bleio_reset() { - if (common_hal_bleio_adapter_get_enabled()) { - common_hal_bleio_adapter_set_enabled(false); + bleio_adapter_reset(&common_hal_bleio_adapter_obj); + if (!vm_used_ble) { + return; } + if (common_hal_bleio_adapter_get_enabled(&common_hal_bleio_adapter_obj)) { + common_hal_bleio_adapter_set_enabled(&common_hal_bleio_adapter_obj, false); + } + supervisor_start_bluetooth(); } // The singleton _bleio.Adapter object, bound to _bleio.adapter // It currently only has properties and no state -const super_adapter_obj_t common_hal_bleio_adapter_obj = { +bleio_adapter_obj_t common_hal_bleio_adapter_obj = { .base = { .type = &bleio_adapter_type, }, @@ -65,410 +89,103 @@ void common_hal_bleio_check_connected(uint16_t conn_handle) { } } -uint16_t common_hal_bleio_device_get_conn_handle(mp_obj_t device) { - if (MP_OBJ_IS_TYPE(device, &bleio_peripheral_type)) { - return ((bleio_peripheral_obj_t*) MP_OBJ_TO_PTR(device))->conn_handle; - } else if (MP_OBJ_IS_TYPE(device, &bleio_central_type)) { - return ((bleio_central_obj_t*) MP_OBJ_TO_PTR(device))->conn_handle; - } else { - return BLE_CONN_HANDLE_INVALID; - } -} - -mp_obj_list_t *common_hal_bleio_device_get_remote_service_list(mp_obj_t device) { - if (MP_OBJ_IS_TYPE(device, &bleio_peripheral_type)) { - return ((bleio_peripheral_obj_t*) MP_OBJ_TO_PTR(device))->remote_service_list; - } else if (MP_OBJ_IS_TYPE(device, &bleio_central_type)) { - return ((bleio_central_obj_t*) MP_OBJ_TO_PTR(device))->remote_service_list; - } else { - return NULL; - } -} - -// service_uuid may be NULL, to discover all services. -STATIC bool discover_next_services(mp_obj_t device, uint16_t start_handle, ble_uuid_t *service_uuid) { - m_discovery_successful = false; - m_discovery_in_process = true; - - uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(device); - uint32_t err_code = sd_ble_gattc_primary_services_discover(conn_handle, start_handle, service_uuid); - - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg(translate("Failed to discover services")); - } - - // Wait for a discovery event. - while (m_discovery_in_process) { - MICROPY_VM_HOOK_LOOP; - } - return m_discovery_successful; -} - -STATIC bool discover_next_characteristics(mp_obj_t device, bleio_service_obj_t *service, uint16_t start_handle) { - m_char_discovery_service = service; - - ble_gattc_handle_range_t handle_range; - handle_range.start_handle = start_handle; - handle_range.end_handle = service->end_handle; +// GATTS read of a Characteristic or Descriptor. +size_t common_hal_bleio_gatts_read(uint16_t handle, uint16_t conn_handle, uint8_t* buf, size_t len) { + // conn_handle might be BLE_CONN_HANDLE_INVALID if we're not connected, but that's OK, because + // we can still read and write the local value. - m_discovery_successful = false; - m_discovery_in_process = true; + ble_gatts_value_t gatts_value = { + .p_value = buf, + .len = len, + }; - uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(device); - uint32_t err_code = sd_ble_gattc_characteristics_discover(conn_handle, &handle_range); + uint32_t err_code = sd_ble_gatts_value_get(conn_handle, handle, &gatts_value); if (err_code != NRF_SUCCESS) { - return false; + mp_raise_OSError_msg_varg(translate("Failed to read gatts value, err 0x%04x"), err_code); } - // Wait for a discovery event. - while (m_discovery_in_process) { - MICROPY_VM_HOOK_LOOP; - } - return m_discovery_successful; + return gatts_value.len; } -STATIC bool discover_next_descriptors(mp_obj_t device, bleio_characteristic_obj_t *characteristic, uint16_t start_handle, uint16_t end_handle) { - m_desc_discovery_characteristic = characteristic; - - ble_gattc_handle_range_t handle_range; - handle_range.start_handle = start_handle; - handle_range.end_handle = end_handle; +void common_hal_bleio_gatts_write(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo) { + // conn_handle might be BLE_CONN_HANDLE_INVALID if we're not connected, but that's OK, because + // we can still read and write the local value. - m_discovery_successful = false; - m_discovery_in_process = true; + ble_gatts_value_t gatts_value = { + .p_value = bufinfo->buf, + .len = bufinfo->len, + }; - uint16_t conn_handle = common_hal_bleio_device_get_conn_handle(device); - uint32_t err_code = sd_ble_gattc_descriptors_discover(conn_handle, &handle_range); + const uint32_t err_code = sd_ble_gatts_value_set(conn_handle, handle, &gatts_value); if (err_code != NRF_SUCCESS) { - return false; - } - - // Wait for a discovery event. - while (m_discovery_in_process) { - MICROPY_VM_HOOK_LOOP; - } - return m_discovery_successful; -} - -STATIC void on_primary_srv_discovery_rsp(ble_gattc_evt_prim_srvc_disc_rsp_t *response, mp_obj_t device) { - for (size_t i = 0; i < response->count; ++i) { - ble_gattc_service_t *gattc_service = &response->services[i]; - - bleio_service_obj_t *service = m_new_obj(bleio_service_obj_t); - service->base.type = &bleio_service_type; - - // Initialize several fields at once. - common_hal_bleio_service_construct(service, device, NULL, false); - - service->is_remote = true; - service->start_handle = gattc_service->handle_range.start_handle; - service->end_handle = gattc_service->handle_range.end_handle; - service->handle = gattc_service->handle_range.start_handle; - - if (gattc_service->uuid.type != BLE_UUID_TYPE_UNKNOWN) { - // Known service UUID. - bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t); - uuid->base.type = &bleio_uuid_type; - bleio_uuid_construct_from_nrf_ble_uuid(uuid, &gattc_service->uuid); - service->uuid = uuid; - } else { - // The discovery response contained a 128-bit UUID that has not yet been registered with the - // softdevice via sd_ble_uuid_vs_add(). We need to fetch the 128-bit value and register it. - // For now, just set the UUID to NULL. - service->uuid = NULL; - } - - mp_obj_list_append(common_hal_bleio_device_get_remote_service_list(device), service); - } - - if (response->count > 0) { - m_discovery_successful = true; - } - m_discovery_in_process = false; -} - -STATIC void on_char_discovery_rsp(ble_gattc_evt_char_disc_rsp_t *response, mp_obj_t device) { - for (size_t i = 0; i < response->count; ++i) { - ble_gattc_char_t *gattc_char = &response->chars[i]; - - bleio_characteristic_obj_t *characteristic = m_new_obj(bleio_characteristic_obj_t); - characteristic->base.type = &bleio_characteristic_type; - - bleio_uuid_obj_t *uuid = NULL; - - if (gattc_char->uuid.type != BLE_UUID_TYPE_UNKNOWN) { - // Known characteristic UUID. - uuid = m_new_obj(bleio_uuid_obj_t); - uuid->base.type = &bleio_uuid_type; - bleio_uuid_construct_from_nrf_ble_uuid(uuid, &gattc_char->uuid); - } else { - // The discovery response contained a 128-bit UUID that has not yet been registered with the - // softdevice via sd_ble_uuid_vs_add(). We need to fetch the 128-bit value and register it. - // For now, just leave the UUID as NULL. - } - - bleio_characteristic_properties_t props = - (gattc_char->char_props.broadcast ? CHAR_PROP_BROADCAST : 0) | - (gattc_char->char_props.indicate ? CHAR_PROP_INDICATE : 0) | - (gattc_char->char_props.notify ? CHAR_PROP_NOTIFY : 0) | - (gattc_char->char_props.read ? CHAR_PROP_READ : 0) | - (gattc_char->char_props.write ? CHAR_PROP_WRITE : 0) | - (gattc_char->char_props.write_wo_resp ? 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, m_char_discovery_service, uuid, props, SECURITY_MODE_OPEN, SECURITY_MODE_OPEN, - GATT_MAX_DATA_LENGTH, false, // max_length, fixed_length: values may not matter for gattc - mp_obj_new_list(0, NULL)); - characteristic->handle = gattc_char->handle_value; - - mp_obj_list_append(m_char_discovery_service->characteristic_list, MP_OBJ_FROM_PTR(characteristic)); - } - - if (response->count > 0) { - m_discovery_successful = true; - } - m_discovery_in_process = false; -} - -STATIC void on_desc_discovery_rsp(ble_gattc_evt_desc_disc_rsp_t *response, mp_obj_t device) { - for (size_t i = 0; i < response->count; ++i) { - ble_gattc_desc_t *gattc_desc = &response->descs[i]; - - // Remember handles for certain well-known descriptors. - switch (gattc_desc->uuid.uuid) { - case BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG: - m_desc_discovery_characteristic->cccd_handle = gattc_desc->handle; - break; - - case BLE_UUID_DESCRIPTOR_SERVER_CHAR_CONFIG: - m_desc_discovery_characteristic->sccd_handle = gattc_desc->handle; - break; - - case BLE_UUID_DESCRIPTOR_CHAR_USER_DESC: - m_desc_discovery_characteristic->user_desc_handle = gattc_desc->handle; - break; - - default: - // TODO: sd_ble_gattc_descriptors_discover() can return things that are not descriptors, - // so ignore those. - // https://devzone.nordicsemi.com/f/nordic-q-a/49500/sd_ble_gattc_descriptors_discover-is-returning-attributes-that-are-not-descriptors - break; - } - - bleio_descriptor_obj_t *descriptor = m_new_obj(bleio_descriptor_obj_t); - descriptor->base.type = &bleio_descriptor_type; - - bleio_uuid_obj_t *uuid = NULL; - - if (gattc_desc->uuid.type != BLE_UUID_TYPE_UNKNOWN) { - // Known descriptor UUID. - uuid = m_new_obj(bleio_uuid_obj_t); - uuid->base.type = &bleio_uuid_type; - bleio_uuid_construct_from_nrf_ble_uuid(uuid, &gattc_desc->uuid); - } else { - // The discovery response contained a 128-bit UUID that has not yet been registered with the - // softdevice via sd_ble_uuid_vs_add(). We need to fetch the 128-bit value and register it. - // For now, just leave the UUID as NULL. - } - - common_hal_bleio_descriptor_construct( - descriptor, m_desc_discovery_characteristic, uuid, - SECURITY_MODE_OPEN, SECURITY_MODE_OPEN, - GATT_MAX_DATA_LENGTH, false, mp_const_empty_bytes); - descriptor->handle = gattc_desc->handle; - - mp_obj_list_append(m_desc_discovery_characteristic->descriptor_list, MP_OBJ_FROM_PTR(descriptor)); - } - - if (response->count > 0) { - m_discovery_successful = true; + mp_raise_OSError_msg_varg(translate("Failed to write gatts value, err 0x%04x"), err_code); } - m_discovery_in_process = false; } -STATIC void discovery_on_ble_evt(ble_evt_t *ble_evt, mp_obj_t device) { +typedef struct { + uint8_t* buf; + size_t len; + size_t final_len; + uint16_t conn_handle; + volatile uint16_t status; + volatile bool done; +} read_info_t; + +STATIC bool _on_gattc_read_rsp_evt(ble_evt_t *ble_evt, void *param) { + read_info_t* read = param; switch (ble_evt->header.evt_id) { - case BLE_GAP_EVT_DISCONNECTED: - m_discovery_successful = false; - m_discovery_in_process = false; - break; - - case BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP: - on_primary_srv_discovery_rsp(&ble_evt->evt.gattc_evt.params.prim_srvc_disc_rsp, device); - break; - - case BLE_GATTC_EVT_CHAR_DISC_RSP: - on_char_discovery_rsp(&ble_evt->evt.gattc_evt.params.char_disc_rsp, device); - break; - case BLE_GATTC_EVT_DESC_DISC_RSP: - on_desc_discovery_rsp(&ble_evt->evt.gattc_evt.params.desc_disc_rsp, device); + // More events may be handled later, so keep this as a switch. + + case BLE_GATTC_EVT_READ_RSP: { + ble_gattc_evt_t* evt = &ble_evt->evt.gattc_evt; + ble_gattc_evt_read_rsp_t *response = &evt->params.read_rsp; + if (read && evt->conn_handle == read->conn_handle) { + read->status = evt->gatt_status; + size_t len = MIN(read->len, response->len); + memcpy(read->buf, response->data, len); + read->final_len = len; + // Indicate to busy-wait loop that we've read the attribute value. + read->done = true; + } break; + } default: // For debugging. - // mp_printf(&mp_plat_print, "Unhandled discovery event: 0x%04x\n", ble_evt->header.evt_id); + // mp_printf(&mp_plat_print, "Unhandled characteristic event: 0x%04x\n", ble_evt->header.evt_id); + return false; break; } + return true; } +size_t common_hal_bleio_gattc_read(uint16_t handle, uint16_t conn_handle, uint8_t* buf, size_t len) { + common_hal_bleio_check_connected(conn_handle); -void common_hal_bleio_device_discover_remote_services(mp_obj_t device, mp_obj_t service_uuids_whitelist) { - mp_obj_list_t *remote_services_list = common_hal_bleio_device_get_remote_service_list(device); - - ble_drv_add_event_handler(discovery_on_ble_evt, device); - - // Start over with an empty list. - mp_obj_list_clear(MP_OBJ_FROM_PTR(common_hal_bleio_device_get_remote_service_list(device))); - - if (service_uuids_whitelist == mp_const_none) { - // List of service UUID's not given, so discover all available services. - - uint16_t next_service_start_handle = BLE_GATT_HANDLE_START; - - while (discover_next_services(device, next_service_start_handle, MP_OBJ_NULL)) { - // discover_next_services() appends to remote_services_list. - - // Get the most recently discovered service, and then ask for services - // whose handles start after the last attribute handle inside that service. - const bleio_service_obj_t *service = - MP_OBJ_TO_PTR(remote_services_list->items[remote_services_list->len - 1]); - next_service_start_handle = service->end_handle + 1; - } - } 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_ValueError(translate("non-UUID found in service_uuids_whitelist")); - } - bleio_uuid_obj_t *uuid = MP_OBJ_TO_PTR(uuid_obj); - - ble_uuid_t nrf_uuid; - bleio_uuid_convert_to_nrf_ble_uuid(uuid, &nrf_uuid); - - // Service might or might not be discovered; that's ok. Caller has to check - // Central.remote_services to find out. - // We only need to call this once for each service to discover. - discover_next_services(device, BLE_GATT_HANDLE_START, &nrf_uuid); - } - } - - - for (size_t service_idx = 0; service_idx < remote_services_list->len; ++service_idx) { - bleio_service_obj_t *service = MP_OBJ_TO_PTR(remote_services_list->items[service_idx]); - - // Skip the service if it had an unknown (unregistered) UUID. - if (service->uuid == NULL) { - continue; - } - - uint16_t next_char_start_handle = service->start_handle; - - // Stop when we go past the end of the range of handles for this service or - // discovery call returns nothing. - // discover_next_characteristics() appends to the characteristic_list. - while (next_char_start_handle <= service->end_handle && - discover_next_characteristics(device, service, next_char_start_handle)) { - - - // Get the most recently discovered characteristic, and then ask for characteristics - // whose handles start after the last attribute handle inside that characteristic. - const bleio_characteristic_obj_t *characteristic = - MP_OBJ_TO_PTR(service->characteristic_list->items[service->characteristic_list->len - 1]); - next_char_start_handle = characteristic->handle + 1; - } - - // 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]); - 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]); - - // Skip the characteristic if it had an unknown (unregistered) UUID. - if (characteristic->uuid == NULL) { - continue; - } - - uint16_t next_desc_start_handle = characteristic->handle + 1; - - // Don't run past the end of this service or the beginning of the next characteristic. - uint16_t next_desc_end_handle = next_characteristic == NULL - ? service->end_handle - : next_characteristic->handle - 1; - - // Stop when we go past the end of the range of handles for this service or - // discovery call returns nothing. - // discover_next_descriptors() appends to the descriptor_list. - while (next_desc_start_handle <= service->end_handle && - next_desc_start_handle < next_desc_end_handle && - discover_next_descriptors(device, characteristic, - next_desc_start_handle, next_desc_end_handle)) { + read_info_t read_info; + read_info.buf = buf; + read_info.len = len; + read_info.final_len = 0; + read_info.conn_handle = conn_handle; + // Set to true by the event handler. + read_info.done = false; + ble_drv_add_event_handler(_on_gattc_read_rsp_evt, &read_info); - // Get the most recently discovered descriptor, and then ask for descriptors - // whose handles start after that descriptor's handle. - const bleio_descriptor_obj_t *descriptor = - MP_OBJ_TO_PTR(characteristic->descriptor_list->items[characteristic->descriptor_list->len - 1]); - next_desc_start_handle = descriptor->handle + 1; - } - } + const uint32_t err_code = sd_ble_gattc_read(conn_handle, handle, 0); + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed initiate attribute read, err 0x%04x"), err_code); } - // This event handler is no longer needed. - ble_drv_remove_event_handler(discovery_on_ble_evt, device); - -} - -// GATTS read of a Characteristic or Descriptor. -mp_obj_t common_hal_bleio_gatts_read(uint16_t handle, uint16_t conn_handle) { - // conn_handle might be BLE_CONN_HANDLE_INVALID if we're not connected, but that's OK, because - // we can still read and write the local value. - - mp_buffer_info_t bufinfo; - ble_gatts_value_t gatts_value = { - .p_value = NULL, - .len = 0, - }; - - // Read once to find out what size buffer we need, then read again to fill buffer. - - mp_obj_t value = mp_const_none; - uint32_t err_code = sd_ble_gatts_value_get(conn_handle, handle, &gatts_value); - if (err_code == NRF_SUCCESS) { - value = mp_obj_new_bytearray_of_zeros(gatts_value.len); - mp_get_buffer_raise(value, &bufinfo, MP_BUFFER_WRITE); - gatts_value.p_value = bufinfo.buf; - - // Read again, with the correct size of buffer. - err_code = sd_ble_gatts_value_get(conn_handle, handle, &gatts_value); + while (!read_info.done) { + RUN_BACKGROUND_TASKS; } - - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to read gatts value, err 0x%04x"), err_code); + if (read_info.status != BLE_GATT_STATUS_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to read attribute value, err 0x%04x"), read_info.status); } - return value; -} - -void common_hal_bleio_gatts_write(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo) { - // conn_handle might be BLE_CONN_HANDLE_INVALID if we're not connected, but that's OK, because - // we can still read and write the local value. - - ble_gatts_value_t gatts_value = { - .p_value = bufinfo->buf, - .len = bufinfo->len, - }; - - const uint32_t err_code = sd_ble_gatts_value_set(conn_handle, handle, &gatts_value); - if (err_code != NRF_SUCCESS) { - mp_raise_OSError_msg_varg(translate("Failed to write gatts value, err 0x%04x"), err_code); - } + ble_drv_remove_event_handler(_on_gattc_read_rsp_evt, &read_info); + return read_info.final_len; } void common_hal_bleio_gattc_write(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo, bool write_no_response) { @@ -500,3 +217,7 @@ void common_hal_bleio_gattc_write(uint16_t handle, uint16_t conn_handle, mp_buff } } + +void common_hal_bleio_gc_collect(void) { + bleio_adapter_gc_collect(&common_hal_bleio_adapter_obj); +} diff --git a/ports/nrf/common-hal/_bleio/__init__.h b/ports/nrf/common-hal/_bleio/__init__.h index cf1a06945..5fd2769f2 100644 --- a/ports/nrf/common-hal/_bleio/__init__.h +++ b/ports/nrf/common-hal/_bleio/__init__.h @@ -39,4 +39,9 @@ typedef struct { // 20 bytes max (23 - 3). #define GATT_MAX_DATA_LENGTH (BLE_GATT_ATT_MTU_DEFAULT - 3) +const mp_obj_t base_error_messages[20]; + +// Track if the user code modified the BLE state to know if we need to undo it on reload. +bool vm_used_ble; + #endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_INIT_H |
