summaryrefslogtreecommitdiff
path: root/devices/ble_hci
diff options
context:
space:
mode:
authorDan Halbert <halbert@halwitz.org>2020-06-25 20:57:17 -0400
committerDan Halbert <halbert@halwitz.org>2020-06-25 20:57:17 -0400
commit759929c24a9966b64075bedfb365f3dfc66ad681 (patch)
tree928afac1054566f50d51afce84b32aac7d5067ee /devices/ble_hci
parentbc4c74517a261e2eb80a1829877d4ad3b29763d2 (diff)
hci early wip; refactor supervisor bluetooth.c for nrf: tested
Diffstat (limited to 'devices/ble_hci')
-rw-r--r--devices/ble_hci/common-hal/_bleio/Adapter.c717
-rw-r--r--devices/ble_hci/common-hal/_bleio/Adapter.h71
-rw-r--r--devices/ble_hci/common-hal/_bleio/Attribute.c60
-rw-r--r--devices/ble_hci/common-hal/_bleio/Attribute.h34
-rw-r--r--devices/ble_hci/common-hal/_bleio/Characteristic.c266
-rw-r--r--devices/ble_hci/common-hal/_bleio/Characteristic.h55
-rw-r--r--devices/ble_hci/common-hal/_bleio/CharacteristicBuffer.c151
-rw-r--r--devices/ble_hci/common-hal/_bleio/CharacteristicBuffer.h41
-rw-r--r--devices/ble_hci/common-hal/_bleio/Connection.c768
-rw-r--r--devices/ble_hci/common-hal/_bleio/Connection.h90
-rw-r--r--devices/ble_hci/common-hal/_bleio/Descriptor.c97
-rw-r--r--devices/ble_hci/common-hal/_bleio/Descriptor.h53
-rw-r--r--devices/ble_hci/common-hal/_bleio/PacketBuffer.c402
-rw-r--r--devices/ble_hci/common-hal/_bleio/PacketBuffer.h51
-rw-r--r--devices/ble_hci/common-hal/_bleio/Service.c147
-rw-r--r--devices/ble_hci/common-hal/_bleio/Service.h54
-rw-r--r--devices/ble_hci/common-hal/_bleio/UUID.c88
-rw-r--r--devices/ble_hci/common-hal/_bleio/UUID.h47
-rw-r--r--devices/ble_hci/common-hal/_bleio/__init__.c246
-rw-r--r--devices/ble_hci/common-hal/_bleio/__init__.h50
-rw-r--r--devices/ble_hci/common-hal/_bleio/bonding.c306
-rw-r--r--devices/ble_hci/common-hal/_bleio/bonding.h83
-rw-r--r--devices/ble_hci/common-hal/_bleio/hci.c675
-rw-r--r--devices/ble_hci/supervisor/bluetooth.c48
-rw-r--r--devices/ble_hci/supervisor/bluetooth.h34
25 files changed, 4634 insertions, 0 deletions
diff --git a/devices/ble_hci/common-hal/_bleio/Adapter.c b/devices/ble_hci/common-hal/_bleio/Adapter.c
new file mode 100644
index 000000000..7e1bfe992
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Adapter.c
@@ -0,0 +1,717 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Dan Halbert for Adafruit Industries
+ * Copyright (c) 2016 Glenn Ruben Bakke
+ * 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 <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "bonding.h"
+
+#include "py/gc.h"
+#include "py/objstr.h"
+#include "py/runtime.h"
+#include "supervisor/shared/safe_mode.h"
+#include "supervisor/shared/tick.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/nvm/ByteArray.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)
+#define BLE_SLAVE_LATENCY 0
+#define BLE_CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS)
+
+#ifndef BLEIO_VS_UUID_COUNT
+#define BLEIO_VS_UUID_COUNT 75
+#endif
+
+#ifndef BLEIO_HVN_TX_QUEUE_SIZE
+#define BLEIO_HVN_TX_QUEUE_SIZE 9
+#endif
+
+#ifndef BLEIO_CENTRAL_ROLE_COUNT
+#define BLEIO_CENTRAL_ROLE_COUNT 4
+#endif
+
+#ifndef BLEIO_PERIPH_ROLE_COUNT
+#define BLEIO_PERIPH_ROLE_COUNT 4
+#endif
+
+#ifndef BLEIO_ATTR_TAB_SIZE
+#define BLEIO_ATTR_TAB_SIZE (BLE_GATTS_ATTR_TAB_SIZE_DEFAULT * 5)
+#endif
+
+bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
+
+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. One must always be available because the SD has the same
+ // // total connection limit.
+ // bleio_connection_internal_t *connection;
+ // for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
+ // connection = &bleio_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;
+ // connection->pair_status = PAIR_NOT_PAIRED;
+ // connection->mtu = 0;
+
+ // ble_drv_add_event_handler_entry(&connection->handler_entry, connection_on_ble_evt, connection);
+ // self->connection_objs = NULL;
+
+ // // Save the current connection parameters.
+ // memcpy(&connection->conn_params, &connected->conn_params, sizeof(ble_gap_conn_params_t));
+
+ // #if CIRCUITPY_VERBOSE_BLE
+ // ble_gap_conn_params_t *cp = &connected->conn_params;
+ // mp_printf(&mp_plat_print, "conn params: min_ci %d max_ci %d s_l %d sup_timeout %d\n", cp->min_conn_interval, cp->max_conn_interval, cp->slave_latency, cp->conn_sup_timeout);
+ // #endif
+
+ // // 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 = &bleio_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;
+ // connection->pair_status = PAIR_NOT_PAIRED;
+ // 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) {
+// check_nrf_error(sd_ble_gap_addr_get(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_construct(bleio_adapter_obj_t *self, mcu_pin_obj_t *tx, mcu_pin_obj_t *rx, mcu_pin_obj_t *rts, mcu_pin_obj_t *cts, uint32_t baudrate, uint32_t buffer_size, mcu_pin_obj_t* spi_cs, mcu_pin_obj_t* gpio0, mcu_pin_obj_t *reset, bool reset_high) {
+ self->tx = tx;
+ self->rx = rx;
+ self->rts = rts;
+ self->cts = cts;
+ self->baudrate = baudrate;
+ self->buffer_size = buffer_size;
+ self->spi_cs = spi_cs;
+ self->gpio0 = gpio0;
+ self->reset = reset;
+ self->reset_high = reset_high;
+ self->enabled = false;
+}
+
+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) {
+ return;
+ }
+
+ if (enabled) {
+ // Enable adapter.
+
+ // common_hal UART takes rts and cts, but is currently not implemented for many ports.
+ // In addition, rts and cts may be pins that are not part of the serial peripheral
+ // used for tx and rx, so use GPIO for them.
+ common_hal_busio_uart_construct(&self->hci_uart, tx, rx, NULL, NULL, NULL, false,
+ BLEIO_HCI_BAUDRATE, 8, PARITY_NONE, 1, 0.0f,
+ BLEIO_HCI_BUFFER_SIZE, NULL, false);
+
+ // RTS is output, active high
+ common_hal_digitalio_digitalinout_construct(&self->rts_digitalio, self->rts);
+ common_hal_digitalio_digitalinout_switch_to_output(&self->rts_digitalio, false, DRIVE_MODE_PUSH_PULL);
+
+ // CTS is input.
+ common_hal_digitalio_digitalinout_construct(&self->cts_digitalio, self->cts);
+
+ // SPI_CS and GPI0 are used to signal entering BLE mode.
+ // SPI_CS should be low, and GPI0 should be high
+ common_hal_digitalio_digitalinout_construct(&self->spi_cs_digitalio, self->spi_cs);
+ common_hal_digitalio_digitalinout_construct(&self->gpio0_digitalio, self->gpi0);
+ common_hal_digitalio_digitalinout_switch_to_output(&self->spi_cs_digitalio, false, DRIVE_MODE_PUSH_PULL);
+ common_hal_digitalio_digitalinout_switch_to_output(&self->gpio0_digitalio, true DRIVE_MODE_PUSH_PULL);
+
+ // RESET is output, start in non-reset state.
+ common_hal_digitalio_digitalinout_construct(&self->reset_digitalio, self->reset);
+ common_hal_digitalio_digitalinout_switch_to_output(&self->reset_digitalio,
+ !self->reset_high, DRIVE_MODE_PUSH_PULL);
+
+ // Adapter will enter BLE mode on reset, based on SPI_CS and GPIO0 settings.
+ // Reset HCI processor. Assert reset for 100ms, then wait 750ms for reset to complete.
+ common_hal_digitalio_digitalinout_set_value(&self->reset_digitalio, self->reset_high);
+ mp_hal_delay_ms(100);
+ common_hal_digitalio_digitalinout_set_value(&self->reset_digitalio, !self->reset_high);
+ mp_hal_delay_ms(750);
+
+ // After reset, set SPI_CS high.
+ common_hal_digitalio_digitalinout_set_value(&self->spi_cs_digitalio, true);
+
+ return;
+ }
+
+ // Disable.
+ common_hal_digitalio_digitalinout_set_value(&self->reset_digitalio, self->reset_high);
+ mp_hal_delay_ms(100);
+ common_hal_digitalio_digitalinout_set_value(&self->reset_digitalio, !self->reset_high);
+
+ // Free all pins.
+ common_hal_busio_uart_deinit(&self->hci_uart);
+ common_hal_digitalio_digitalinout_deinit(&self->rts_digitalio);
+ common_hal_digitalio_digitalinout_deinit(&self->cts_digitalio);
+ common_hal_digitalio_digitalinout_deinit(&self->spi_cs_digitalio);
+ common_hal_digitalio_digitalinout_deinit(&self->gpi0_digitalio);
+ common_hal_digitalio_digitalinout_deinit(&self->reset_digitalio);
+}
+
+bool common_hal_bleio_adapter_get_enabled(bleio_adapter_obj_t *self) {
+ return self->enabled;
+}
+
+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,
+// supervisor_ticks_ms64(),
+// 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, size_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_bleio_BluetoothError(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;
+ // vm_used_ble = true;
+ // err_code = sd_ble_gap_scan_start(&scan_params, sd_data);
+
+ // if (err_code != NRF_SUCCESS) {
+ // self->scan_results = NULL;
+ // ble_drv_remove_event_handler(scan_on_ble_evt, self->scan_results);
+ // check_nrf_error(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) {
+
+ // 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;
+
+ vm_used_ble = true;
+ // uint32_t err_code = sd_ble_gap_connect(&addr, &scan_params, &conn_params, BLE_CONN_CFG_TAG_CUSTOM);
+
+ // if (err_code != NRF_SUCCESS) {
+ // ble_drv_remove_event_handler(connect_on_ble_evt, &event_info);
+ // check_nrf_error(err_code);
+ // }
+
+ // while (!event_info.done) {
+ // RUN_BACKGROUND_TASKS;
+ // }
+
+ // ble_drv_remove_event_handler(connect_on_ble_evt, &event_info);
+
+ // uint16_t conn_handle = event_info.conn_handle;
+ // if (conn_handle == BLE_CONN_HANDLE_INVALID) {
+ // mp_raise_bleio_BluetoothError(translate("Failed to connect: timeout"));
+ // }
+
+ // // Negotiate for better PHY, larger MTU and data lengths since we are the central. These are
+ // // nice-to-haves so ignore any errors.
+ // ble_gap_phys_t const phys = {
+ // .rx_phys = BLE_GAP_PHY_AUTO,
+ // .tx_phys = BLE_GAP_PHY_AUTO,
+ // };
+ // sd_ble_gap_phy_update(conn_handle, &phys);
+ // sd_ble_gattc_exchange_mtu_request(conn_handle, BLE_GATTS_VAR_ATTR_LEN_MAX);
+ // sd_ble_gap_data_length_update(conn_handle, NULL, NULL);
+
+ // Make the connection object and return it.
+ for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
+ bleio_connection_internal_t *connection = &bleio_connections[i];
+ if (connection->conn_handle == conn_handle) {
+ return bleio_connection_new_from_internal(connection);
+ }
+ }
+
+ mp_raise_bleio_BluetoothError(translate("Failed to connect: internal error"));
+
+ return mp_const_none;
+}
+
+// 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;
+
+STATIC void check_data_fit(size_t data_len, bool connectable) {
+ if (data_len > BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_MAX_SUPPORTED ||
+ (connectable && data_len > BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED)) {
+ mp_raise_ValueError(translate("Data too large for advertisement packet"));
+ }
+}
+
+// STATIC bool advertising_on_ble_evt(ble_evt_t *ble_evt, void *self_in) {
+// bleio_adapter_obj_t *self = (bleio_adapter_obj_t*)self_in;
+
+// switch (ble_evt->header.evt_id) {
+// case BLE_GAP_EVT_ADV_SET_TERMINATED:
+// common_hal_bleio_adapter_stop_advertising(self);
+// ble_drv_remove_event_handler(advertising_on_ble_evt, self_in);
+// break;
+
+// default:
+// // For debugging.
+// // mp_printf(&mp_plat_print, "Unhandled advertising event: 0x%04x\n", ble_evt->header.evt_id);
+// return false;
+// break;
+// }
+// return true;
+// }
+
+uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, bool connectable, bool anonymous, uint32_t timeout, 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;
+ // }
+
+ // // 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;
+ // bool extended = advertising_data_len > BLE_GAP_ADV_SET_DATA_SIZE_MAX ||
+ // scan_response_data_len > BLE_GAP_ADV_SET_DATA_SIZE_MAX;
+
+ // uint8_t adv_type;
+ // if (extended) {
+ // if (connectable) {
+ // adv_type = BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED;
+ // } else if (scan_response_data_len > 0) {
+ // adv_type = BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED;
+ // } else {
+ // adv_type = BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED;
+ // }
+ // } else if (connectable) {
+ // adv_type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;
+ // } else if (scan_response_data_len > 0) {
+ // adv_type = BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED;
+ // } else {
+ // adv_type = BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED;
+ // }
+
+ // if (anonymous) {
+ // ble_gap_privacy_params_t privacy = {
+ // .privacy_mode = BLE_GAP_PRIVACY_MODE_DEVICE_PRIVACY,
+ // .private_addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE,
+ // // Rotate the keys one second after we're scheduled to stop
+ // // advertising. This prevents a potential race condition where we
+ // // fire off a beacon with the same advertising data but a new MAC
+ // // address just as we tear down the connection.
+ // .private_addr_cycle_s = timeout + 1,
+ // .p_device_irk = NULL,
+ // };
+ // err_code = sd_ble_gap_privacy_set(&privacy);
+ // } else {
+ // ble_gap_privacy_params_t privacy = {
+ // .privacy_mode = BLE_GAP_PRIVACY_MODE_OFF,
+ // .private_addr_type = BLE_GAP_ADDR_TYPE_PUBLIC,
+ // .private_addr_cycle_s = 0,
+ // .p_device_irk = NULL,
+ // };
+ // err_code = sd_ble_gap_privacy_set(&privacy);
+ // }
+ // if (err_code != NRF_SUCCESS) {
+ // return err_code;
+ // }
+
+ // ble_gap_adv_params_t adv_params = {
+ // .interval = SEC_TO_UNITS(interval, UNIT_0_625_MS),
+ // .properties.type = adv_type,
+ // .duration = SEC_TO_UNITS(timeout, UNIT_10_MS),
+ // .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;
+ // }
+
+ // ble_drv_add_event_handler(advertising_on_ble_evt, self);
+
+ // vm_used_ble = true;
+ // 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;
+ return 0;
+}
+
+
+void common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, bool connectable, bool anonymous, uint32_t timeout, 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_bleio_BluetoothError(translate("Already advertising."));
+ }
+ // interval value has already been validated.
+
+ check_data_fit(advertising_data_bufinfo->len, connectable);
+ check_data_fit(scan_response_data_bufinfo->len, connectable);
+
+ if (advertising_data_bufinfo->len > 31 && scan_response_data_bufinfo->len > 0) {
+ mp_raise_bleio_BluetoothError(translate("Extended advertisements with scan response not supported."));
+ }
+
+ // Anonymous mode requires a timeout so that we don't continue to broadcast
+ // the same data while cycling the MAC address -- otherwise, what's the
+ // point of randomizing the MAC address?
+ if (!timeout) {
+ if (anonymous) {
+ // The Nordic macro is in units of 10ms. Convert to seconds.
+ uint32_t adv_timeout_max_secs = UNITS_TO_SEC(BLE_GAP_ADV_TIMEOUT_LIMITED_MAX, UNIT_10_MS);
+ uint32_t rotate_timeout_max_secs = BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S;
+ timeout = MIN(adv_timeout_max_secs, rotate_timeout_max_secs);
+ }
+ else {
+ timeout = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED;
+ }
+ } else {
+ if (SEC_TO_UNITS(timeout, UNIT_10_MS) > BLE_GAP_ADV_TIMEOUT_LIMITED_MAX) {
+ mp_raise_bleio_BluetoothError(translate("Timeout is too long: Maximum timeout length is %d seconds"),
+ UNITS_TO_SEC(BLE_GAP_ADV_TIMEOUT_LIMITED_MAX, UNIT_10_MS));
+ }
+ }
+
+ // 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_EXTENDED_MAX_SUPPORTED * 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_EXTENDED_MAX_SUPPORTED * 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);
+
+ // check_nrf_error(_common_hal_bleio_adapter_start_advertising(self, connectable, anonymous, timeout, interval,
+ // self->advertising_data,
+ // advertising_data_bufinfo->len,
+ // self->scan_response_data,
+ // scan_response_data_bufinfo->len));
+}
+
+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)) {
+ // check_nrf_error(err_code);
+ // }
+}
+
+bool common_hal_bleio_adapter_get_advertising(bleio_adapter_obj_t *self) {
+ return self->current_advertising_data != NULL;
+}
+
+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 = &bleio_connections[i];
+ if (connection->conn_handle != BLE_CONN_HANDLE_INVALID) {
+ return true;
+ }
+ }
+ return false;
+}
+
+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 = &bleio_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 common_hal_bleio_adapter_erase_bonding(bleio_adapter_obj_t *self) {
+ bonding_erase_storage();
+}
+
+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**)bleio_connections, sizeof(bleio_connections) / sizeof(size_t));
+}
+
+void bleio_adapter_reset(bleio_adapter_obj_t* adapter) {
+ common_hal_bleio_adapter_stop_scan(adapter);
+ if (adapter->current_advertising_data != NULL) {
+ 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 = &bleio_connections[i];
+ // Disconnect all connections with Python state cleanly. Keep any supervisor-only connections.
+ if (connection->connection_obj != mp_const_none &&
+ connection->conn_handle != BLE_CONN_HANDLE_INVALID) {
+ common_hal_bleio_connection_disconnect(connection);
+ }
+ connection->connection_obj = mp_const_none;
+ }
+}
diff --git a/devices/ble_hci/common-hal/_bleio/Adapter.h b/devices/ble_hci/common-hal/_bleio/Adapter.h
new file mode 100644
index 000000000..c12e9105c
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Adapter.h
@@ -0,0 +1,71 @@
+/*
+ * 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
+ * Copyright (c) 2016 Glenn Ruben Bakke
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_ADAPTER_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_ADAPTER_H
+
+#include "py/obj.h"
+#include "py/objtuple.h"
+
+#include "shared-bindings/_bleio/Connection.h"
+#include "shared-bindings/_bleio/ScanResults.h"
+#include "shared-bindings/microcontroller/Pin.h"
+
+extern bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
+
+typedef struct {
+ mp_obj_base_t base;
+ 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;
+ mcu_pin_obj_t* tx;
+ mcu_pin_obj_t* rx;
+ mcu_pin_obj_t* rts;
+ mcu_pin_obj_t* cts;
+ uint32_t baudrate;
+ uint16_t buffer_size;
+ mcu_pin_obj_t* spi_cs;
+ mcu_pin_obj_t* gpio0;
+ mcu_pin_obj_t* reset;
+ bool reset_high;
+ busio_uart_obj_t hci_uart;
+ digitalio_digitalinout_obj_t rts_digitalio;
+ digitalio_digitalinout_obj_t cts_digitalio;
+ digitalio_digitalinout_obj_t spi_cs_digitalio;
+ digitalio_digitalinout_obj_t gpio0_digitalio;
+ digitalio_digitalinout_obj_t reset_digitalio;
+ bool enabled;
+} 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_BLE_HCI_COMMON_HAL_ADAPTER_H
diff --git a/devices/ble_hci/common-hal/_bleio/Attribute.c b/devices/ble_hci/common-hal/_bleio/Attribute.c
new file mode 100644
index 000000000..c55914b10
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Attribute.c
@@ -0,0 +1,60 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Dan Halbert for Adafruit Industries
+ *
+ * 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/Attribute.h"
+
+// Convert a _bleio security mode to a ble_gap_conn_sec_mode_t setting.
+void bleio_attribute_gatts_set_security_mode(ble_gap_conn_sec_mode_t *perm, bleio_attribute_security_mode_t security_mode) {
+ switch (security_mode) {
+ case SECURITY_MODE_NO_ACCESS:
+ BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(perm);
+ break;
+
+ case SECURITY_MODE_OPEN:
+ BLE_GAP_CONN_SEC_MODE_SET_OPEN(perm);
+ break;
+
+ case SECURITY_MODE_ENC_NO_MITM:
+ BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM(perm);
+ break;
+
+ case SECURITY_MODE_ENC_WITH_MITM:
+ BLE_GAP_CONN_SEC_MODE_SET_ENC_WITH_MITM(perm);
+ break;
+
+ case SECURITY_MODE_LESC_ENC_WITH_MITM:
+ BLE_GAP_CONN_SEC_MODE_SET_LESC_ENC_WITH_MITM(perm);
+ break;
+
+ case SECURITY_MODE_SIGNED_NO_MITM:
+ BLE_GAP_CONN_SEC_MODE_SET_SIGNED_NO_MITM(perm);
+ break;
+
+ case SECURITY_MODE_SIGNED_WITH_MITM:
+ BLE_GAP_CONN_SEC_MODE_SET_SIGNED_WITH_MITM(perm);
+ break;
+ }
+}
diff --git a/devices/ble_hci/common-hal/_bleio/Attribute.h b/devices/ble_hci/common-hal/_bleio/Attribute.h
new file mode 100644
index 000000000..cd9c86ca3
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Attribute.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Dan Halbert for Adafruit Industries
+ *
+ * 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_BLE_HCI_COMMON_HAL_ATTRIBUTE_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_ATTRIBUTE_H
+
+#include "shared-module/_bleio/Attribute.h"
+
+extern void bleio_attribute_gatts_set_security_mode(ble_gap_conn_sec_mode_t *perm, bleio_attribute_security_mode_t security_mode);
+
+#endif // MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_ATTRIBUTE_H
diff --git a/devices/ble_hci/common-hal/_bleio/Characteristic.c b/devices/ble_hci/common-hal/_bleio/Characteristic.c
new file mode 100644
index 000000000..d507cecca
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Characteristic.c
@@ -0,0 +1,266 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 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 "py/runtime.h"
+
+#include "shared-bindings/_bleio/__init__.h"
+#include "shared-bindings/_bleio/Characteristic.h"
+#include "shared-bindings/_bleio/Descriptor.h"
+#include "shared-bindings/_bleio/Service.h"
+
+#include "common-hal/_bleio/Adapter.h"
+#include "common-hal/_bleio/bonding.h"
+
+STATIC uint16_t characteristic_get_cccd(uint16_t cccd_handle, uint16_t conn_handle) {
+ uint16_t cccd;
+ ble_gatts_value_t value = {
+ .p_value = (uint8_t*) &cccd,
+ .len = 2,
+ };
+
+ const uint32_t err_code = sd_ble_gatts_value_get(conn_handle, cccd_handle, &value);
+
+ if (err_code == BLE_ERROR_GATTS_SYS_ATTR_MISSING) {
+ // CCCD is not set, so say that neither Notify nor Indicate is enabled.
+ cccd = 0;
+ } else {
+ check_nrf_error(err_code);
+ }
+
+ return cccd;
+}
+
+
+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;
+
+ ble_gatts_hvx_params_t hvx_params = {
+ .handle = handle,
+ .type = hvx_type,
+ .offset = 0,
+ .p_len = &hvx_len,
+ .p_data = bufinfo->buf,
+ };
+
+ while (1) {
+ const uint32_t err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params);
+ if (err_code == NRF_SUCCESS) {
+ break;
+ }
+ // TX buffer is full
+ // We could wait for an event indicating the write is complete, but just retrying is easier.
+ if (err_code == NRF_ERROR_RESOURCES) {
+ RUN_BACKGROUND_TASKS;
+ continue;
+ }
+
+ // Some real error has occurred.
+ check_nrf_error(err_code);
+ }
+}
+
+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 = NULL;
+
+ const mp_int_t max_length_max = fixed_length ? BLE_GATTS_FIX_ATTR_LEN_MAX : BLE_GATTS_VAR_ATTR_LEN_MAX;
+ if (max_length < 0 || max_length > max_length_max) {
+ mp_raise_ValueError_varg(translate("max_length must be 0-%d when fixed_length is %s"),
+ max_length_max, fixed_length ? "True" : "False");
+ }
+ self->max_length = max_length;
+ self->fixed_length = fixed_length;
+
+ 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);
+ }
+}
+
+bleio_descriptor_obj_t *common_hal_bleio_characteristic_get_descriptor_list(bleio_characteristic_obj_t *self) {
+ return self->descriptor_list;
+}
+
+bleio_service_obj_t *common_hal_bleio_characteristic_get_service(bleio_characteristic_obj_t *self) {
+ return self->service;
+}
+
+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 = 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.
+ return common_hal_bleio_gattc_read(self->handle, conn_handle, buf, len);
+ } else {
+ // conn_handle is ignored for non-system attributes.
+ return common_hal_bleio_gatts_read(self->handle, conn_handle, buf, len);
+ }
+ }
+
+ return 0;
+}
+
+void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_buffer_info_t *bufinfo) {
+ if (self->fixed_length && bufinfo->len != self->max_length) {
+ mp_raise_ValueError(translate("Value length != required fixed length"));
+ }
+ if (bufinfo->len > self->max_length) {
+ mp_raise_ValueError(translate("Value length > max_length"));
+ }
+
+ // Do GATT operations only if this characteristic has been added to a registered service.
+ if (self->handle != BLE_GATT_HANDLE_INVALID) {
+
+ 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 {
+ // Always write the value locally even if no connections are active.
+ // conn_handle is ignored for non-system attributes, so we use BLE_CONN_HANDLE_INVALID.
+ 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 = &bleio_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);
+ }
+ }
+ }
+ }
+}
+
+bleio_uuid_obj_t *common_hal_bleio_characteristic_get_uuid(bleio_characteristic_obj_t *self) {
+ return self->uuid;
+}
+
+bleio_characteristic_properties_t common_hal_bleio_characteristic_get_properties(bleio_characteristic_obj_t *self) {
+ return self->props;
+}
+
+void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self, bleio_descriptor_obj_t *descriptor) {
+ ble_uuid_t desc_uuid;
+ bleio_uuid_convert_to_nrf_ble_uuid(descriptor->uuid, &desc_uuid);
+
+ ble_gatts_attr_md_t desc_attr_md = {
+ // Data passed is not in a permanent location and should be copied.
+ .vloc = BLE_GATTS_VLOC_STACK,
+ .vlen = !descriptor->fixed_length,
+ };
+
+ bleio_attribute_gatts_set_security_mode(&desc_attr_md.read_perm, descriptor->read_perm);
+ bleio_attribute_gatts_set_security_mode(&desc_attr_md.write_perm, descriptor->write_perm);
+
+ mp_buffer_info_t desc_value_bufinfo;
+ mp_get_buffer_raise(descriptor->value, &desc_value_bufinfo, MP_BUFFER_READ);
+
+ ble_gatts_attr_t desc_attr = {
+ .p_uuid = &desc_uuid,
+ .p_attr_md = &desc_attr_md,
+ .init_len = desc_value_bufinfo.len,
+ .p_value = desc_value_bufinfo.buf,
+ .init_offs = 0,
+ .max_len = descriptor->max_length,
+ };
+
+ check_nrf_error(sd_ble_gatts_descriptor_add(self->handle, &desc_attr, &descriptor->handle));
+
+ 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) {
+ if (self->cccd_handle == BLE_GATT_HANDLE_INVALID) {
+ mp_raise_bleio_BluetoothError(translate("No CCCD for this Characteristic"));
+ }
+
+ if (!common_hal_bleio_service_get_is_remote(self->service)) {
+ mp_raise_bleio_RoleError(translate("Can't set CCCD on local Characteristic"));
+ }
+
+ const uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection);
+ common_hal_bleio_check_connected(conn_handle);
+
+ uint16_t cccd_value =
+ (notify ? BLE_GATT_HVX_NOTIFICATION : 0) |
+ (indicate ? BLE_GATT_HVX_INDICATION : 0);
+
+ ble_gattc_write_params_t write_params = {
+ .write_op = BLE_GATT_OP_WRITE_REQ,
+ .handle = self->cccd_handle,
+ .p_value = (uint8_t *) &cccd_value,
+ .len = 2,
+ };
+
+ while (1) {
+ uint32_t err_code = sd_ble_gattc_write(conn_handle, &write_params);
+ if (err_code == NRF_SUCCESS) {
+ break;
+ }
+
+ // Write with response will return NRF_ERROR_BUSY if the response has not been received.
+ // Write without reponse will return NRF_ERROR_RESOURCES if too many writes are pending.
+ if (err_code == NRF_ERROR_BUSY || err_code == NRF_ERROR_RESOURCES) {
+ // We could wait for an event indicating the write is complete, but just retrying is easier.
+ RUN_BACKGROUND_TASKS;
+ continue;
+ }
+
+ // Some real error occurred.
+ check_nrf_error(err_code);
+ }
+
+}
diff --git a/devices/ble_hci/common-hal/_bleio/Characteristic.h b/devices/ble_hci/common-hal/_bleio/Characteristic.h
new file mode 100644
index 000000000..4d5fa02f0
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Characteristic.h
@@ -0,0 +1,55 @@
+/*
+ * 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_BLE_HCI_COMMON_HAL_CHARACTERISTIC_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_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 _bleio_characteristic_obj {
+ mp_obj_base_t base;
+ // Will be MP_OBJ_NULL before being assigned to a Service.
+ bleio_service_obj_t *service;
+ bleio_uuid_obj_t *uuid;
+ mp_obj_t value;
+ uint16_t max_length;
+ bool fixed_length;
+ uint16_t handle;
+ bleio_characteristic_properties_t props;
+ bleio_attribute_security_mode_t read_perm;
+ bleio_attribute_security_mode_t write_perm;
+ bleio_descriptor_obj_t *descriptor_list;
+ uint16_t user_desc_handle;
+ uint16_t cccd_handle;
+ uint16_t sccd_handle;
+} bleio_characteristic_obj_t;
+
+#endif // MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_CHARACTERISTIC_H
diff --git a/devices/ble_hci/common-hal/_bleio/CharacteristicBuffer.c b/devices/ble_hci/common-hal/_bleio/CharacteristicBuffer.c
new file mode 100644
index 000000000..6f848f858
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/CharacteristicBuffer.c
@@ -0,0 +1,151 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Dan Halbert for Adafruit Industries
+ *
+ * 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 "lib/utils/interrupt_char.h"
+#include "py/runtime.h"
+#include "py/stream.h"
+
+#include "shared-bindings/_bleio/__init__.h"
+#include "shared-bindings/_bleio/Connection.h"
+#include "supervisor/shared/tick.h"
+#include "common-hal/_bleio/CharacteristicBuffer.h"
+
+// Push all the data onto the ring buffer. When the buffer is full, new bytes will be dropped.
+STATIC void write_to_ringbuf(bleio_characteristic_buffer_obj_t *self, uint8_t *data, uint16_t len) {
+ uint8_t is_nested_critical_region;
+ sd_nvic_critical_region_enter(&is_nested_critical_region);
+ ringbuf_put_n(&self->ringbuf, data, len);
+ sd_nvic_critical_region_exit(is_nested_critical_region);
+}
+
+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: {
+ // A client wrote to this server characteristic.
+
+ ble_gatts_evt_write_t *evt_write = &ble_evt->evt.gatts_evt.params.write;
+ // Event handle must match the handle for my characteristic.
+ if (evt_write->handle == self->characteristic->handle) {
+ write_to_ringbuf(self, evt_write->data, evt_write->len);
+ }
+ break;
+ }
+
+ case BLE_GATTC_EVT_HVX: {
+ // A remote service wrote to this characteristic.
+
+ ble_gattc_evt_hvx_t* evt_hvx = &ble_evt->evt.gattc_evt.params.hvx;
+ // Must be a notification, and event handle must match the handle for my characteristic.
+ if (evt_hvx->type == BLE_GATT_HVX_NOTIFICATION &&
+ evt_hvx->handle == self->characteristic->handle) {
+ write_to_ringbuf(self, evt_hvx->data, evt_hvx->len);
+ }
+ break;
+ }
+ default:
+ return false;
+ break;
+ }
+ return true;
+}
+
+// Assumes that timeout and buffer_size have been validated before call.
+void common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffer_obj_t *self,
+ bleio_characteristic_obj_t *characteristic,
+ mp_float_t timeout,
+ size_t buffer_size) {
+
+ self->characteristic = characteristic;
+ self->timeout_ms = timeout * 1000;
+ // This is a macro.
+ // true means long-lived, so it won't be moved.
+ ringbuf_alloc(&self->ringbuf, buffer_size, true);
+
+ ble_drv_add_event_handler(characteristic_buffer_on_ble_evt, self);
+
+}
+
+uint32_t common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self, uint8_t *data, size_t len, int *errcode) {
+ uint64_t start_ticks = supervisor_ticks_ms64();
+
+ // Wait for all bytes received or timeout
+ while ( (ringbuf_num_filled(&self->ringbuf) < len) && (supervisor_ticks_ms64() - start_ticks < self->timeout_ms) ) {
+ RUN_BACKGROUND_TASKS;
+ // Allow user to break out of a timeout with a KeyboardInterrupt.
+ if ( mp_hal_is_interrupted() ) {
+ return 0;
+ }
+ }
+
+ // Copy received data. Lock out write interrupt handler while copying.
+ uint8_t is_nested_critical_region;
+ sd_nvic_critical_region_enter(&is_nested_critical_region);
+
+ uint32_t num_bytes_read = ringbuf_get_n(&self->ringbuf, data, len);
+
+ // Writes now OK.
+ sd_nvic_critical_region_exit(is_nested_critical_region);
+
+ return num_bytes_read;
+}
+
+uint32_t common_hal_bleio_characteristic_buffer_rx_characters_available(bleio_characteristic_buffer_obj_t *self) {
+ uint8_t is_nested_critical_region;
+ sd_nvic_critical_region_enter(&is_nested_critical_region);
+ uint16_t count = ringbuf_num_filled(&self->ringbuf);
+ sd_nvic_critical_region_exit(is_nested_critical_region);
+ return count;
+}
+
+void common_hal_bleio_characteristic_buffer_clear_rx_buffer(bleio_characteristic_buffer_obj_t *self) {
+ // prevent conflict with uart irq
+ uint8_t is_nested_critical_region;
+ sd_nvic_critical_region_enter(&is_nested_critical_region);
+ ringbuf_clear(&self->ringbuf);
+ sd_nvic_critical_region_exit(is_nested_critical_region);
+}
+
+bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer_obj_t *self) {
+ return self->characteristic == NULL;
+}
+
+void common_hal_bleio_characteristic_buffer_deinit(bleio_characteristic_buffer_obj_t *self) {
+ if (!common_hal_bleio_characteristic_buffer_deinited(self)) {
+ ble_drv_remove_event_handler(characteristic_buffer_on_ble_evt, self);
+ }
+}
+
+bool common_hal_bleio_characteristic_buffer_connected(bleio_characteristic_buffer_obj_t *self) {
+ return self->characteristic != NULL &&
+ self->characteristic->service != NULL &&
+ (!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/devices/ble_hci/common-hal/_bleio/CharacteristicBuffer.h b/devices/ble_hci/common-hal/_bleio/CharacteristicBuffer.h
new file mode 100644
index 000000000..8738d2c59
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/CharacteristicBuffer.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Dan Halbert for Adafruit Industries
+ *
+ * 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_BLE_HCI_COMMON_HAL_CHARACTERISTICBUFFER_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_CHARACTERISTICBUFFER_H
+
+#include "py/ringbuf.h"
+#include "shared-bindings/_bleio/Characteristic.h"
+
+typedef struct {
+ mp_obj_base_t base;
+ bleio_characteristic_obj_t *characteristic;
+ uint32_t timeout_ms;
+ // Ring buffer storing consecutive incoming values.
+ ringbuf_t ringbuf;
+} bleio_characteristic_buffer_obj_t;
+
+#endif // MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_CHARACTERISTICBUFFER_H
diff --git a/devices/ble_hci/common-hal/_bleio/Connection.c b/devices/ble_hci/common-hal/_bleio/Connection.c
new file mode 100644
index 000000000..6c63f4261
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Connection.c
@@ -0,0 +1,768 @@
+/*
+ * 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 "lib/utils/interrupt_char.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"
+#include "supervisor/shared/tick.h"
+
+#include "common-hal/_bleio/bonding.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},
+};
+
+#define CONNECTION_DEBUG (1)
+#if CONNECTION_DEBUG
+ #define CONNECTION_DEBUG_PRINTF(...) printf(__VA_ARGS__)
+#else
+ #define CONNECTION_DEBUG_PRINTF(...)
+#endif
+
+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 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;
+ }
+
+ switch (ble_evt->header.evt_id) {
+ case BLE_GAP_EVT_DISCONNECTED:
+ // Adapter.c does the work for this event.
+ 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: {
+ ble_gatts_evt_exchange_mtu_request_t *request =
+ &ble_evt->evt.gatts_evt.params.exchange_mtu_request;
+
+ uint16_t new_mtu = BLE_GATTS_VAR_ATTR_LEN_MAX;
+ if (request->client_rx_mtu < new_mtu) {
+ new_mtu = request->client_rx_mtu;
+ }
+ if (new_mtu < BLE_GATT_ATT_MTU_DEFAULT) {
+ new_mtu = BLE_GATT_ATT_MTU_DEFAULT;
+ }
+ if (self->mtu > 0) {
+ new_mtu = self->mtu;
+ }
+
+ self->mtu = new_mtu;
+ sd_ble_gatts_exchange_mtu_reply(self->conn_handle, new_mtu);
+ break;
+ }
+
+
+ case BLE_GATTC_EVT_EXCHANGE_MTU_RSP: {
+ ble_gattc_evt_exchange_mtu_rsp_t *response =
+ &ble_evt->evt.gattc_evt.params.exchange_mtu_rsp;
+
+ self->mtu = response->server_rx_mtu;
+ break;
+ }
+
+ case BLE_GATTS_EVT_WRITE:
+ // A client wrote a value.
+ // If we are bonded and it's a CCCD (UUID 0x2902), store the CCCD value.
+ if (self->conn_handle != BLE_CONN_HANDLE_INVALID &&
+ self->pair_status == PAIR_PAIRED &&
+ ble_evt->evt.gatts_evt.params.write.uuid.type == BLE_UUID_TYPE_BLE &&
+ ble_evt->evt.gatts_evt.params.write.uuid.uuid == 0x2902) {
+ //
+ // Save sys_attr data (CCCD state) in bonding area at
+ // next opportunity, but also remember time of this
+ // request, so we can consolidate closely-spaced requests.
+ self->do_bond_cccds = true;
+ self->do_bond_cccds_request_time = supervisor_ticks_ms64();
+ }
+ // Return false so other handlers get this event as well.
+ return false;
+
+ case BLE_GATTS_EVT_SYS_ATTR_MISSING:
+ sd_ble_gatts_sys_attr_set(self->conn_handle, NULL, 0, 0);
+ break;
+
+ #if CIRCUITPY_VERBOSE_BLE
+ // Use read authorization to snoop on all reads when doing verbose debugging.
+ case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST: {
+
+ ble_gatts_evt_rw_authorize_request_t *request =
+ &ble_evt->evt.gatts_evt.params.authorize_request;
+
+ mp_printf(&mp_plat_print, "Read %x offset %d ", request->request.read.handle, request->request.read.offset);
+ uint8_t value_bytes[22];
+ ble_gatts_value_t value;
+ value.offset = request->request.read.offset;
+ value.len = 22;
+ value.p_value = value_bytes;
+
+ sd_ble_gatts_value_get(self->conn_handle, request->request.read.handle, &value);
+ size_t len = value.len;
+ if (len > 22) {
+ len = 22;
+ }
+ for (uint8_t i = 0; i < len; i++) {
+ mp_printf(&mp_plat_print, " %02x", value_bytes[i]);
+ }
+ mp_printf(&mp_plat_print, "\n");
+ ble_gatts_rw_authorize_reply_params_t reply;
+ reply.type = request->type;
+ reply.params.read.gatt_status = BLE_GATT_STATUS_SUCCESS;
+ reply.params.read.update = false;
+ reply.params.read.offset = request->request.read.offset;
+ sd_ble_gatts_rw_authorize_reply(self->conn_handle, &reply);
+ break;
+ }
+ #endif
+
+ case BLE_GATTS_EVT_HVN_TX_COMPLETE: // Capture this for now. 0x55
+ break;
+ case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: {
+ self->conn_params_updating = true;
+ 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_CONN_PARAM_UPDATE: { // 0x12
+ ble_gap_evt_conn_param_update_t *result =
+ &ble_evt->evt.gap_evt.params.conn_param_update;
+
+ #if CIRCUITPY_VERBOSE_BLE
+ ble_gap_conn_params_t *cp = &ble_evt->evt.gap_evt.params.conn_param_update.conn_params;
+ mp_printf(&mp_plat_print, "conn params updated: min_ci %d max_ci %d s_l %d sup_timeout %d\n", cp->min_conn_interval, cp->max_conn_interval, cp->slave_latency, cp->conn_sup_timeout);
+ #endif
+
+ memcpy(&self->conn_params, &result->conn_params, sizeof(ble_gap_conn_params_t));
+ self->conn_params_updating = false;
+ break;
+ }
+ case BLE_GAP_EVT_SEC_PARAMS_REQUEST: {
+ // First time pairing.
+ // 1. Either we or peer initiate the process
+ // 2. Peer asks for security parameters using BLE_GAP_EVT_SEC_PARAMS_REQUEST.
+ // 3. Pair Key exchange ("just works" implemented now; TODO: out-of-band key pairing)
+ // 4. Connection is secured: BLE_GAP_EVT_CONN_SEC_UPDATE
+ // 5. Long-term Keys exchanged: BLE_GAP_EVT_AUTH_STATUS
+
+ bonding_clear_keys(&self->bonding_keys);
+ self->ediv = EDIV_INVALID;
+ 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,
+ self->is_central ? NULL : &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
+ // Key exchange completed.
+ ble_gap_evt_auth_status_t* status = &ble_evt->evt.gap_evt.params.auth_status;
+ self->sec_status = status->auth_status;
+ if (status->auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
+ self->ediv = self->bonding_keys.own_enc.master_id.ediv;
+ self->pair_status = PAIR_PAIRED;
+ // Save keys in bonding area at next opportunity.
+ self->do_bond_keys = true;
+ } else {
+ // Inform busy-waiter pairing has failed.
+ 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 ( bonding_load_keys(self->is_central, sec_info_request->master_id.ediv, &self->bonding_keys) ) {
+ sd_ble_gap_sec_info_reply(
+ self->conn_handle,
+ &self->bonding_keys.own_enc.enc_info,
+ &self->bonding_keys.peer_id.id_info,
+ NULL);
+ self->ediv = self->bonding_keys.own_enc.master_id.ediv;
+ } else {
+ // We don't have stored keys. Ask for keys.
+ sd_ble_gap_sec_info_reply(self->conn_handle, NULL, NULL, NULL);
+ }
+ break;
+ }
+
+ case BLE_GAP_EVT_CONN_SEC_UPDATE: { // 0x1a
+ // We get this both on first-time pairing and on subsequent pairings using stored keys.
+ 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 (bonding_load_cccd_info(self->is_central, self->conn_handle, self->ediv)) {
+ // Did an sd_ble_gatts_sys_attr_set() with the stored sys_attr values.
+ } else {
+ // No matching bonding found, so use fresh system attributes.
+ sd_ble_gatts_sys_attr_set(self->conn_handle, NULL, 0, 0);
+ }
+ self->pair_status = PAIR_PAIRED;
+ }
+ break;
+ }
+
+ default:
+ 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;
+ bonding_clear_keys(&self->bonding_keys);
+}
+
+bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) {
+ if (self->connection == NULL) {
+ return false;
+ }
+ return self->connection->pair_status == PAIR_PAIRED;
+}
+
+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, bool bond) {
+ self->pair_status = PAIR_WAITING;
+
+ check_nrf_error(sd_ble_gap_authenticate(self->conn_handle, &pairing_sec_params));
+
+ while (self->pair_status == PAIR_WAITING && !mp_hal_is_interrupted()) {
+ RUN_BACKGROUND_TASKS;
+ }
+ if (mp_hal_is_interrupted()) {
+ return;
+ }
+ check_sec_status(self->sec_status);
+}
+
+mp_float_t common_hal_bleio_connection_get_connection_interval(bleio_connection_internal_t *self) {
+ while (self->conn_params_updating && !mp_hal_is_interrupted()) {
+ RUN_BACKGROUND_TASKS;
+ }
+ return 1.25f * self->conn_params.min_conn_interval;
+}
+
+// Return the current negotiated MTU length, minus overhead.
+mp_int_t common_hal_bleio_connection_get_max_packet_length(bleio_connection_internal_t *self) {
+ return (self->mtu == 0 ? BLE_GATT_ATT_MTU_DEFAULT : self->mtu) - 3;
+}
+
+void common_hal_bleio_connection_set_connection_interval(bleio_connection_internal_t *self, mp_float_t new_interval) {
+ self->conn_params_updating = true;
+ uint16_t interval = new_interval / 1.25f;
+ self->conn_params.min_conn_interval = interval;
+ self->conn_params.max_conn_interval = interval;
+ uint32_t status = NRF_ERROR_BUSY;
+ while (status == NRF_ERROR_BUSY) {
+ status = sd_ble_gap_conn_param_update(self->conn_handle, &self->conn_params);
+ RUN_BACKGROUND_TASKS;
+ }
+ check_nrf_error(status);
+}
+
+// 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 nrf_err = NRF_ERROR_BUSY;
+ while (nrf_err == NRF_ERROR_BUSY) {
+ nrf_err = sd_ble_gattc_primary_services_discover(connection->conn_handle, start_handle, service_uuid);
+ }
+ check_nrf_error(nrf_err);
+
+ // 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:
+ // CONNECTION_DEBUG_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_TypeError(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);
+ bleio_connection_ensure_connected(self);
+ // Convert to a tuple and then clear the list so the callee will take ownership.
+ mp_obj_tuple_t *services_tuple = 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);
+}
+
+// Find the connection that uses the given conn_handle. Return NULL if not found.
+bleio_connection_internal_t *bleio_conn_handle_to_connection(uint16_t conn_handle) {
+ bleio_connection_internal_t *connection;
+ for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
+ connection = &bleio_connections[i];
+ if (connection->conn_handle == conn_handle) {
+ return connection;
+ }
+ }
+
+ return NULL;
+}
diff --git a/devices/ble_hci/common-hal/_bleio/Connection.h b/devices/ble_hci/common-hal/_bleio/Connection.h
new file mode 100644
index 000000000..1b6616ab1
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Connection.h
@@ -0,0 +1,90 @@
+/*
+ * 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_BLE_HCI_COMMON_HAL_CONNECTION_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_CONNECTION_H
+
+#include <stdbool.h>
+
+#include "py/obj.h"
+#include "py/objlist.h"
+
+#include "common-hal/_bleio/__init__.h"
+#include "common-hal/_bleio/bonding.h"
+#include "shared-module/_bleio/Address.h"
+#include "common-hal/_bleio/Service.h"
+
+typedef enum {
+ PAIR_NOT_PAIRED,
+ PAIR_WAITING,
+ 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 {
+ uint16_t conn_handle;
+ bool is_central;
+ // Remote services discovered when this peripheral is acting as a client.
+ 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).
+ bonding_keys_t bonding_keys;
+ // EDIV: Encrypted Diversifier: Identifies LTK during legacy pairing.
+ uint16_t ediv;
+ volatile pair_status_t pair_status;
+ uint8_t sec_status; // Internal security status.
+ mp_obj_t connection_obj;
+ ble_drv_evt_handler_entry_t handler_entry;
+ ble_gap_conn_params_t conn_params;
+ volatile bool conn_params_updating;
+ uint16_t mtu;
+ // Request that CCCD values for this conenction be saved, using sys_attr values.
+ volatile bool do_bond_cccds;
+ // Request that security key info for this connection be saved.
+ volatile bool do_bond_keys;
+ // Time of setting do_bond_ccds: we delay a bit to consolidate multiple CCCD changes
+ // into one write. Time is currently in ticks_ms.
+ uint64_t do_bond_cccds_request_time;
+} 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);
+bleio_connection_internal_t *bleio_conn_handle_to_connection(uint16_t conn_handle);
+
+#endif // MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_CONNECTION_H
diff --git a/devices/ble_hci/common-hal/_bleio/Descriptor.c b/devices/ble_hci/common-hal/_bleio/Descriptor.c
new file mode 100644
index 000000000..faf50c658
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Descriptor.c
@@ -0,0 +1,97 @@
+/*
+ * 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 "py/runtime.h"
+
+#include "shared-bindings/_bleio/__init__.h"
+#include "shared-bindings/_bleio/Descriptor.h"
+#include "shared-bindings/_bleio/Service.h"
+#include "shared-bindings/_bleio/UUID.h"
+
+void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_characteristic_obj_t *characteristic, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo) {
+ self->characteristic = characteristic;
+ self->uuid = uuid;
+ self->handle = BLE_GATT_HANDLE_INVALID;
+ self->read_perm = read_perm;
+ self->write_perm = write_perm;
+
+ const mp_int_t max_length_max = fixed_length ? BLE_GATTS_FIX_ATTR_LEN_MAX : BLE_GATTS_VAR_ATTR_LEN_MAX;
+ if (max_length < 0 || max_length > max_length_max) {
+ mp_raise_ValueError_varg(translate("max_length must be 0-%d when fixed_length is %s"),
+ max_length_max, fixed_length ? "True" : "False");
+ }
+ self->max_length = max_length;
+ self->fixed_length = fixed_length;
+
+ common_hal_bleio_descriptor_set_value(self, initial_value_bufinfo);
+}
+
+bleio_uuid_obj_t *common_hal_bleio_descriptor_get_uuid(bleio_descriptor_obj_t *self) {
+ return self->uuid;
+}
+
+bleio_characteristic_obj_t *common_hal_bleio_descriptor_get_characteristic(bleio_descriptor_obj_t *self) {
+ return self->characteristic;
+}
+
+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)) {
+ return common_hal_bleio_gattc_read(self->handle, conn_handle, buf, len);
+ } else {
+ return common_hal_bleio_gatts_read(self->handle, conn_handle, buf, len);
+ }
+ }
+
+ return 0;
+}
+
+void common_hal_bleio_descriptor_set_value(bleio_descriptor_obj_t *self, mp_buffer_info_t *bufinfo) {
+ if (self->fixed_length && bufinfo->len != self->max_length) {
+ mp_raise_ValueError(translate("Value length != required fixed length"));
+ }
+ if (bufinfo->len > self->max_length) {
+ mp_raise_ValueError(translate("Value length > max_length"));
+ }
+
+ self->value = mp_obj_new_bytes(bufinfo->buf, bufinfo->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)) {
+ // false means WRITE_REQ, not write-no-response
+ common_hal_bleio_gattc_write(self->handle, conn_handle, bufinfo, false);
+ } else {
+ common_hal_bleio_gatts_write(self->handle, conn_handle, bufinfo);
+ }
+ }
+
+}
diff --git a/devices/ble_hci/common-hal/_bleio/Descriptor.h b/devices/ble_hci/common-hal/_bleio/Descriptor.h
new file mode 100644
index 000000000..097a49f8e
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Descriptor.h
@@ -0,0 +1,53 @@
+/*
+ * 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
+ * Copyright (c) 2016 Glenn Ruben Bakke
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_DESCRIPTOR_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_DESCRIPTOR_H
+
+#include "py/obj.h"
+
+#include "common-hal/_bleio/UUID.h"
+
+// 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.
+ struct _bleio_characteristic_obj *characteristic;
+ bleio_uuid_obj_t *uuid;
+ mp_obj_t value;
+ uint16_t max_length;
+ bool fixed_length;
+ 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_BLE_HCI_COMMON_HAL_DESCRIPTOR_H
diff --git a/devices/ble_hci/common-hal/_bleio/PacketBuffer.c b/devices/ble_hci/common-hal/_bleio/PacketBuffer.c
new file mode 100644
index 000000000..c3f41bc9d
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/PacketBuffer.c
@@ -0,0 +1,402 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019-2020 Scott Shawcroft for Adafruit Industries
+ *
+ * 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 "lib/utils/interrupt_char.h"
+#include "py/runtime.h"
+#include "py/stream.h"
+
+#include "shared-bindings/_bleio/__init__.h"
+#include "shared-bindings/_bleio/Connection.h"
+#include "shared-bindings/_bleio/PacketBuffer.h"
+#include "supervisor/shared/tick.h"
+
+STATIC void write_to_ringbuf(bleio_packet_buffer_obj_t *self, uint8_t *data, uint16_t len) {
+ if (len + sizeof(uint16_t) > ringbuf_capacity(&self->ringbuf)) {
+ // This shouldn't happen.
+ return;
+ }
+ // Push all the data onto the ring buffer.
+ uint8_t is_nested_critical_region;
+ sd_nvic_critical_region_enter(&is_nested_critical_region);
+ // Make room for the new value by dropping the oldest packets first.
+ while (ringbuf_capacity(&self->ringbuf) - ringbuf_num_filled(&self->ringbuf) < len + sizeof(uint16_t)) {
+ uint16_t packet_length;
+ ringbuf_get_n(&self->ringbuf, (uint8_t*) &packet_length, sizeof(uint16_t));
+ for (uint16_t i = 0; i < packet_length; i++) {
+ ringbuf_get(&self->ringbuf);
+ }
+ // set an overflow flag?
+ }
+ ringbuf_put_n(&self->ringbuf, (uint8_t*) &len, sizeof(uint16_t));
+ ringbuf_put_n(&self->ringbuf, data, len);
+ sd_nvic_critical_region_exit(is_nested_critical_region);
+}
+
+STATIC uint32_t queue_next_write(bleio_packet_buffer_obj_t *self) {
+ // Queue up the next outgoing buffer. We use two, one that has been passed to the SD for
+ // transmission (when packet_queued is true) and the other is `pending` and can still be
+ // modified. By primarily appending to the `pending` buffer we can reduce the protocol overhead
+ // of the lower level link and ATT layers.
+ self->packet_queued = false;
+ if (self->pending_size > 0) {
+ uint16_t conn_handle = self->conn_handle;
+ uint32_t err_code;
+ if (self->client) {
+ ble_gattc_write_params_t write_params = {
+ .write_op = self->write_type,
+ .handle = self->characteristic->handle,
+ .p_value = self->outgoing[self->pending_index],
+ .len = self->pending_size,
+ };
+
+ err_code = sd_ble_gattc_write(conn_handle, &write_params);
+ } else {
+ uint16_t hvx_len = self->pending_size;
+
+ ble_gatts_hvx_params_t hvx_params = {
+ .handle = self->characteristic->handle,
+ .type = self->write_type,
+ .offset = 0,
+ .p_len = &hvx_len,
+ .p_data = self->outgoing[self->pending_index],
+ };
+ err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params);
+ }
+ if (err_code != NRF_SUCCESS) {
+ // On error, simply skip updating the pending buffers so that the next HVC or WRITE
+ // complete event triggers another attempt.
+ return err_code;
+ }
+ self->pending_size = 0;
+ self->pending_index = (self->pending_index + 1) % 2;
+ self->packet_queued = true;
+ }
+ return NRF_SUCCESS;
+}
+
+STATIC bool packet_buffer_on_ble_client_evt(ble_evt_t *ble_evt, void *param) {
+ const uint16_t evt_id = ble_evt->header.evt_id;
+ // Check if this is a GATTC event so we can make sure the conn_handle is valid.
+ if (evt_id < BLE_GATTC_EVT_BASE || evt_id > BLE_GATTC_EVT_LAST) {
+ return false;
+ }
+
+ uint16_t conn_handle = ble_evt->evt.gattc_evt.conn_handle;
+ bleio_packet_buffer_obj_t *self = (bleio_packet_buffer_obj_t *) param;
+ if (conn_handle != self->conn_handle) {
+ return false;
+ }
+ switch (evt_id) {
+ case BLE_GATTC_EVT_HVX: {
+ // A remote service wrote to this characteristic.
+ ble_gattc_evt_hvx_t* evt_hvx = &ble_evt->evt.gattc_evt.params.hvx;
+ // Must be a notification, and event handle must match the handle for my characteristic.
+ if (evt_hvx->handle == self->characteristic->handle) {
+ write_to_ringbuf(self, evt_hvx->data, evt_hvx->len);
+ if (evt_hvx->type == BLE_GATT_HVX_INDICATION) {
+ sd_ble_gattc_hv_confirm(conn_handle, evt_hvx->handle);
+ }
+ }
+ break;
+ }
+ case BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE: {
+ queue_next_write(self);
+ break;
+ }
+ case BLE_GATTC_EVT_WRITE_RSP: {
+ queue_next_write(self);
+ break;
+ }
+ default:
+ return false;
+ break;
+ }
+ return true;
+}
+
+STATIC bool packet_buffer_on_ble_server_evt(ble_evt_t *ble_evt, void *param) {
+ bleio_packet_buffer_obj_t *self = (bleio_packet_buffer_obj_t *) param;
+ switch (ble_evt->header.evt_id) {
+ case BLE_GATTS_EVT_WRITE: {
+ uint16_t conn_handle = ble_evt->evt.gatts_evt.conn_handle;
+ // A client wrote to this server characteristic.
+
+ ble_gatts_evt_write_t *evt_write = &ble_evt->evt.gatts_evt.params.write;
+
+ // Event handle must match the handle for my characteristic.
+ if (evt_write->handle == self->characteristic->handle) {
+ if (self->conn_handle == BLE_CONN_HANDLE_INVALID) {
+ self->conn_handle = conn_handle;
+ } else if (self->conn_handle != conn_handle) {
+ return false;
+ }
+ write_to_ringbuf(self, evt_write->data, evt_write->len);
+ } else if (evt_write->handle == self->characteristic->cccd_handle) {
+ uint16_t cccd = *((uint16_t*) evt_write->data);
+ if (cccd & BLE_GATT_HVX_NOTIFICATION) {
+ self->conn_handle = conn_handle;
+ } else {
+ self->conn_handle = BLE_CONN_HANDLE_INVALID;
+ }
+ }
+ break;
+ }
+ case BLE_GAP_EVT_DISCONNECTED: {
+ if (self->conn_handle == ble_evt->evt.gap_evt.conn_handle) {
+ self->conn_handle = BLE_CONN_HANDLE_INVALID;
+ }
+ }
+ case BLE_GATTS_EVT_HVN_TX_COMPLETE: {
+ queue_next_write(self);
+ }
+ default:
+ return false;
+ break;
+ }
+ return true;
+}
+
+void common_hal_bleio_packet_buffer_construct(
+ bleio_packet_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic,
+ size_t buffer_size) {
+
+ self->characteristic = characteristic;
+ self->client = self->characteristic->service->is_remote;
+ bleio_characteristic_properties_t incoming = self->characteristic->props & (CHAR_PROP_WRITE_NO_RESPONSE | CHAR_PROP_WRITE);
+ bleio_characteristic_properties_t outgoing = self->characteristic->props & (CHAR_PROP_NOTIFY | CHAR_PROP_INDICATE);
+
+ if (self->client) {
+ // Swap if we're the client.
+ bleio_characteristic_properties_t temp = incoming;
+ incoming = outgoing;
+ outgoing = temp;
+ self->conn_handle = bleio_connection_get_conn_handle(MP_OBJ_TO_PTR(self->characteristic->service->connection));
+ } else {
+ self->conn_handle = BLE_CONN_HANDLE_INVALID;
+ }
+
+ if (incoming) {
+ if (!ringbuf_alloc(&self->ringbuf, buffer_size * (sizeof(uint16_t) + characteristic->max_length), false)) {
+ mp_raise_ValueError(translate("Buffer too large and unable to allocate"));
+ }
+ }
+
+ if (outgoing) {
+ self->packet_queued = false;
+ self->pending_index = 0;
+ self->pending_size = 0;
+ self->outgoing[0] = m_malloc(characteristic->max_length, false);
+ self->outgoing[1] = m_malloc(characteristic->max_length, false);
+ } else {
+ self->outgoing[0] = NULL;
+ self->outgoing[1] = NULL;
+ }
+
+ if (self->client) {
+ ble_drv_add_event_handler(packet_buffer_on_ble_client_evt, self);
+ if (incoming) {
+ // Prefer notify if both are available.
+ if (incoming & CHAR_PROP_NOTIFY) {
+ self->write_type = BLE_GATT_HVX_NOTIFICATION;
+ common_hal_bleio_characteristic_set_cccd(self->characteristic, true, false);
+ } else {
+ common_hal_bleio_characteristic_set_cccd(self->characteristic, false, true);
+ }
+ }
+ if (outgoing) {
+ self->write_type = BLE_GATT_OP_WRITE_REQ;
+ if (outgoing & CHAR_PROP_WRITE_NO_RESPONSE) {
+ self->write_type = BLE_GATT_OP_WRITE_CMD;
+ }
+ }
+ } else {
+ ble_drv_add_event_handler(packet_buffer_on_ble_server_evt, self);
+ if (outgoing) {
+ self->write_type = BLE_GATT_HVX_INDICATION;
+ if (outgoing & CHAR_PROP_NOTIFY) {
+ self->write_type = BLE_GATT_HVX_NOTIFICATION;
+ }
+ }
+ }
+}
+
+mp_int_t common_hal_bleio_packet_buffer_readinto(bleio_packet_buffer_obj_t *self, uint8_t *data, size_t len) {
+ if (ringbuf_num_filled(&self->ringbuf) < 2) {
+ return 0;
+ }
+
+ // Copy received data. Lock out write interrupt handler while copying.
+ uint8_t is_nested_critical_region;
+ sd_nvic_critical_region_enter(&is_nested_critical_region);
+
+ // Get packet length, which is in first two bytes of packet.
+ uint16_t packet_length;
+ ringbuf_get_n(&self->ringbuf, (uint8_t*) &packet_length, sizeof(uint16_t));
+
+ mp_int_t ret;
+ if (packet_length > len) {
+ // Packet is longer than requested. Return negative of overrun value.
+ ret = len - packet_length;
+ // Discard the packet if it's too large. Don't fill data.
+ while (packet_length--) {
+ (void) ringbuf_get(&self->ringbuf);
+ }
+ } else {
+ // Read as much as possible, but might be shorter than len.
+ ringbuf_get_n(&self->ringbuf, data, packet_length);
+ ret = packet_length;
+ }
+
+ // Writes now OK.
+ sd_nvic_critical_region_exit(is_nested_critical_region);
+
+ return ret;
+}
+
+mp_int_t common_hal_bleio_packet_buffer_write(bleio_packet_buffer_obj_t *self, uint8_t *data, size_t len, uint8_t* header, size_t header_len) {
+ if (self->outgoing[0] == NULL) {
+ mp_raise_bleio_BluetoothError(translate("Writes not supported on Characteristic"));
+ }
+ if (self->conn_handle == BLE_CONN_HANDLE_INVALID) {
+ return -1;
+ }
+ uint16_t outgoing_packet_length = common_hal_bleio_packet_buffer_get_outgoing_packet_length(self);
+
+ if (len + header_len > outgoing_packet_length) {
+ // Supplied data will not fit in a single BLE packet.
+ mp_raise_ValueError(translate("Total data to write is larger than outgoing_packet_length"));
+ }
+
+ if (len + self->pending_size > outgoing_packet_length) {
+ // No room to append len bytes to packet. Wait until we get a free buffer,
+ // and keep checking that we haven't been disconnected.
+ while (self->pending_size != 0 && self->conn_handle != BLE_CONN_HANDLE_INVALID) {
+ RUN_BACKGROUND_TASKS;
+ }
+ }
+ if (self->conn_handle == BLE_CONN_HANDLE_INVALID) {
+ return -1;
+ }
+
+ size_t num_bytes_written = 0;
+
+ uint8_t is_nested_critical_region;
+ sd_nvic_critical_region_enter(&is_nested_critical_region);
+
+ uint8_t* pending = self->outgoing[self->pending_index];
+
+ if (self->pending_size == 0) {
+ memcpy(pending, header, header_len);
+ self->pending_size += header_len;
+ num_bytes_written += header_len;
+ }
+ memcpy(pending + self->pending_size, data, len);
+ self->pending_size += len;
+ num_bytes_written += len;
+
+ sd_nvic_critical_region_exit(is_nested_critical_region);
+
+ // If no writes are queued then sneak in this data.
+ if (!self->packet_queued) {
+ queue_next_write(self);
+ }
+ return num_bytes_written;
+}
+
+mp_int_t common_hal_bleio_packet_buffer_get_incoming_packet_length(bleio_packet_buffer_obj_t *self) {
+ // If this PacketBuffer is coming from a remote service via NOTIFY or INDICATE
+ // the maximum size is what can be sent in one
+ // BLE packet. But we must be connected to know that value.
+ //
+ // Otherwise it can be as long as the characteristic
+ // will permit, whether or not we're connected.
+
+ if (self->characteristic == NULL) {
+ return -1;
+ }
+
+ if (self->characteristic->service != NULL &&
+ self->characteristic->service->is_remote &&
+ (common_hal_bleio_characteristic_get_properties(self->characteristic) &
+ (CHAR_PROP_INDICATE | CHAR_PROP_NOTIFY))) {
+ // We are talking to a remote service, and data is arriving via NOTIFY or INDICATE.
+ if (self->conn_handle != BLE_CONN_HANDLE_INVALID) {
+ bleio_connection_internal_t *connection = bleio_conn_handle_to_connection(self->conn_handle);
+ if (connection) {
+ return MIN(common_hal_bleio_connection_get_max_packet_length(connection),
+ self->characteristic->max_length);
+ }
+ }
+ // There's no current connection, so we don't know the MTU, and
+ // we can't tell what the largest incoming packet length would be.
+ return -1;
+ }
+ return self->characteristic->max_length;
+}
+
+mp_int_t common_hal_bleio_packet_buffer_get_outgoing_packet_length(bleio_packet_buffer_obj_t *self) {
+ // If we are sending data via NOTIFY or INDICATE, the maximum size
+ // is what can be sent in one BLE packet. But we must be connected
+ // to know that value.
+ //
+ // Otherwise it can be as long as the characteristic
+ // will permit, whether or not we're connected.
+
+ if (self->characteristic == NULL) {
+ return -1;
+ }
+
+ if (self->characteristic->service != NULL &&
+ !self->characteristic->service->is_remote &&
+ (common_hal_bleio_characteristic_get_properties(self->characteristic) &
+ (CHAR_PROP_INDICATE | CHAR_PROP_NOTIFY))) {
+ // We are sending to a client, via NOTIFY or INDICATE.
+ if (self->conn_handle != BLE_CONN_HANDLE_INVALID) {
+ bleio_connection_internal_t *connection = bleio_conn_handle_to_connection(self->conn_handle);
+ if (connection) {
+ return MIN(common_hal_bleio_connection_get_max_packet_length(connection),
+ self->characteristic->max_length);
+ }
+ }
+ // There's no current connection, so we don't know the MTU, and
+ // we can't tell what the largest outgoing packet length would be.
+ return -1;
+ }
+ return self->characteristic->max_length;
+}
+
+bool common_hal_bleio_packet_buffer_deinited(bleio_packet_buffer_obj_t *self) {
+ return self->characteristic == NULL;
+}
+
+void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self) {
+ if (!common_hal_bleio_packet_buffer_deinited(self)) {
+ ble_drv_remove_event_handler(packet_buffer_on_ble_client_evt, self);
+ }
+}
diff --git a/devices/ble_hci/common-hal/_bleio/PacketBuffer.h b/devices/ble_hci/common-hal/_bleio/PacketBuffer.h
new file mode 100644
index 000000000..e1577a995
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/PacketBuffer.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019-2020 Scott Shawcroft for Adafruit Industries
+ *
+ * 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_BLE_HCI_COMMON_HAL_PACKETBUFFER_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_PACKETBUFFER_H
+
+#include "py/ringbuf.h"
+#include "shared-bindings/_bleio/Characteristic.h"
+
+typedef struct {
+ mp_obj_base_t base;
+ bleio_characteristic_obj_t *characteristic;
+ // Ring buffer storing consecutive incoming values.
+ ringbuf_t ringbuf;
+ // Two outgoing buffers to alternate between. One will be queued for transmission by the SD and
+ // the other is waiting to be queued and can be extended.
+ uint8_t* outgoing[2];
+ volatile uint16_t pending_size;
+ // We remember the conn_handle so we can do a NOTIFY/INDICATE to a client.
+ // We can find out the conn_handle on a Characteristic write or a CCCD write (but not a read).
+ volatile uint16_t conn_handle;
+ uint8_t pending_index;
+ uint8_t write_type;
+ bool client;
+ bool packet_queued;
+} bleio_packet_buffer_obj_t;
+
+#endif // MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_PACKETBUFFER_H
diff --git a/devices/ble_hci/common-hal/_bleio/Service.c b/devices/ble_hci/common-hal/_bleio/Service.c
new file mode 100644
index 000000000..f194a95dd
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Service.c
@@ -0,0 +1,147 @@
+/*
+ * 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 "py/runtime.h"
+#include "common-hal/_bleio/__init__.h"
+#include "shared-bindings/_bleio/Characteristic.h"
+#include "shared-bindings/_bleio/Descriptor.h"
+#include "shared-bindings/_bleio/Service.h"
+#include "shared-bindings/_bleio/Adapter.h"
+
+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 = 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) {
+ check_nrf_error(_common_hal_bleio_service_construct(self, uuid, is_secondary,
+ mp_obj_new_list(0, NULL)));
+}
+
+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) {
+ return self->uuid;
+}
+
+mp_obj_list_t *common_hal_bleio_service_get_characteristic_list(bleio_service_obj_t *self) {
+ return self->characteristic_list;
+}
+
+bool common_hal_bleio_service_get_is_remote(bleio_service_obj_t *self) {
+ return self->is_remote;
+}
+
+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,
+ 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,
+ .char_props.write_wo_resp = (characteristic->props & CHAR_PROP_WRITE_NO_RESPONSE) ? 1 : 0,
+ .char_props.write = (characteristic->props & CHAR_PROP_WRITE) ? 1 : 0,
+ .char_props.notify = (characteristic->props & CHAR_PROP_NOTIFY) ? 1 : 0,
+ .char_props.indicate = (characteristic->props & CHAR_PROP_INDICATE) ? 1 : 0,
+ };
+
+ ble_gatts_attr_md_t cccd_md = {
+ .vloc = BLE_GATTS_VLOC_STACK,
+ };
+
+ ble_uuid_t char_uuid;
+ bleio_uuid_convert_to_nrf_ble_uuid(characteristic->uuid, &char_uuid);
+
+ ble_gatts_attr_md_t char_attr_md = {
+ .vloc = BLE_GATTS_VLOC_STACK,
+ .vlen = !characteristic->fixed_length,
+ };
+
+ if (char_md.char_props.notify || char_md.char_props.indicate) {
+ BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
+ // Make CCCD write permission match characteristic read permission.
+ bleio_attribute_gatts_set_security_mode(&cccd_md.write_perm, characteristic->read_perm);
+
+ char_md.p_cccd_md = &cccd_md;
+ }
+
+ 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);
+ #if CIRCUITPY_VERBOSE_BLE
+ // Turn on read authorization so that we receive an event to print on every read.
+ char_attr_md.rd_auth = true;
+ #endif
+
+ ble_gatts_attr_t char_attr = {
+ .p_uuid = &char_uuid,
+ .p_attr_md = &char_attr_md,
+ .init_len = 0,
+ .p_value = NULL,
+ .init_offs = 0,
+ .max_len = characteristic->max_length,
+ };
+
+ ble_gatts_char_handles_t char_handles;
+
+ check_nrf_error(sd_ble_gatts_characteristic_add(self->handle, &char_md, &char_attr, &char_handles));
+
+ characteristic->user_desc_handle = char_handles.user_desc_handle;
+ characteristic->cccd_handle = char_handles.cccd_handle;
+ characteristic->sccd_handle = char_handles.sccd_handle;
+ characteristic->handle = char_handles.value_handle;
+ #if CIRCUITPY_VERBOSE_BLE
+ mp_printf(&mp_plat_print, "Char handle %x user %x cccd %x sccd %x\n", characteristic->handle, characteristic->user_desc_handle, characteristic->cccd_handle, characteristic->sccd_handle);
+ #endif
+
+ mp_obj_list_append(self->characteristic_list, MP_OBJ_FROM_PTR(characteristic));
+}
diff --git a/devices/ble_hci/common-hal/_bleio/Service.h b/devices/ble_hci/common-hal/_bleio/Service.h
new file mode 100644
index 000000000..bb8bc61ed
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/Service.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#ifndef MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_SERVICE_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_SERVICE_H
+
+#include "py/objlist.h"
+#include "common-hal/_bleio/UUID.h"
+
+typedef struct bleio_service_obj {
+ mp_obj_base_t base;
+ // Handle for the local service.
+ uint16_t handle;
+ // True if created during discovery.
+ bool is_remote;
+ bool is_secondary;
+ bleio_uuid_obj_t *uuid;
+ // The connection object is set only when this is a remote service.
+ // A local service doesn't know the connection.
+ mp_obj_t connection;
+ mp_obj_list_t *characteristic_list;
+ // 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_BLE_HCI_COMMON_HAL_SERVICE_H
diff --git a/devices/ble_hci/common-hal/_bleio/UUID.c b/devices/ble_hci/common-hal/_bleio/UUID.c
new file mode 100644
index 000000000..1c6c35e47
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/UUID.c
@@ -0,0 +1,88 @@
+/*
+ * 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
+ * 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 "py/runtime.h"
+#include "common-hal/_bleio/UUID.h"
+#include "shared-bindings/_bleio/__init__.h"
+#include "shared-bindings/_bleio/Adapter.h"
+
+// 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, const uint8_t uuid128[]) {
+ self->nrf_ble_uuid.uuid = uuid16;
+ if (uuid128 == NULL) {
+ self->nrf_ble_uuid.type = BLE_UUID_TYPE_BLE;
+ } else {
+ ble_uuid128_t vs_uuid;
+ memcpy(vs_uuid.uuid128, uuid128, sizeof(vs_uuid.uuid128));
+
+ // Register this vendor-specific UUID. Bytes 12 and 13 will be zero.
+ check_nrf_error(sd_ble_uuid_vs_add(&vs_uuid, &self->nrf_ble_uuid.type));
+ vm_used_ble = true;
+ }
+}
+
+uint32_t common_hal_bleio_uuid_get_size(bleio_uuid_obj_t *self) {
+ // return self->nrf_ble_uuid.type == BLE_UUID_TYPE_BLE ? 16 : 128;
+}
+
+uint32_t common_hal_bleio_uuid_get_uuid16(bleio_uuid_obj_t *self) {
+ // return self->nrf_ble_uuid.uuid;
+ return 0;
+}
+
+void common_hal_bleio_uuid_get_uuid128(bleio_uuid_obj_t *self, uint8_t uuid128[16]) {
+ uint8_t length;
+ // check_nrf_error(sd_ble_uuid_encode(&self->nrf_ble_uuid, &length, uuid128));
+}
+
+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_bleio_BluetoothError(translate("Unexpected nrfx uuid type"));
+// }
+// self->nrf_ble_uuid.uuid = nrf_ble_uuid->uuid;
+// self->nrf_ble_uuid.type = nrf_ble_uuid->type;
+// }
+
+// // Fill in a ble_uuid_t from my values.
+// void bleio_uuid_convert_to_nrf_ble_uuid(bleio_uuid_obj_t *self, ble_uuid_t *nrf_ble_uuid) {
+// nrf_ble_uuid->uuid = self->nrf_ble_uuid.uuid;
+// nrf_ble_uuid->type = self->nrf_ble_uuid.type;
+// }
diff --git a/devices/ble_hci/common-hal/_bleio/UUID.h b/devices/ble_hci/common-hal/_bleio/UUID.h
new file mode 100644
index 000000000..584a28960
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/UUID.h
@@ -0,0 +1,47 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Dan Halbert for Adafruit Industries
+ * Copyright (c) 2018 Artur Pacholec
+ * Copyright (c) 2016 Glenn Ruben Bakke
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_UUID_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_UUID_H
+
+#include "py/obj.h"
+
+typedef struct {
+ mp_obj_base_t base;
+ // Use the native way of storing UUID's:
+ // - ble_uuid_t.uuid is a 16-bit uuid.
+ // - ble_uuid_t.type is BLE_UUID_TYPE_BLE if it's a 16-bit Bluetooth SIG UUID.
+ // or is BLE_UUID_TYPE_VENDOR_BEGIN and higher, which indexes into a table of registered
+ // 128-bit UUIDs.
+ // ble_uuid_t nrf_ble_uuid;
+} bleio_uuid_obj_t;
+
+// void bleio_uuid_construct_from_nrf_ble_uuid(bleio_uuid_obj_t *self, ble_uuid_t *nrf_uuid);
+// void bleio_uuid_convert_to_nrf_ble_uuid(bleio_uuid_obj_t *self, ble_uuid_t *nrf_uuid);
+
+#endif // MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_UUID_H
diff --git a/devices/ble_hci/common-hal/_bleio/__init__.c b/devices/ble_hci/common-hal/_bleio/__init__.c
new file mode 100644
index 000000000..e84bba662
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/__init__.c
@@ -0,0 +1,246 @@
+/*
+ * 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
+ * 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 "py/runtime.h"
+#include "shared-bindings/_bleio/__init__.h"
+#include "shared-bindings/_bleio/Adapter.h"
+#include "shared-bindings/_bleio/Characteristic.h"
+#include "shared-bindings/_bleio/Connection.h"
+#include "shared-bindings/_bleio/Descriptor.h"
+#include "shared-bindings/_bleio/Service.h"
+#include "shared-bindings/_bleio/UUID.h"
+#include "supervisor/shared/bluetooth.h"
+
+#include "common-hal/_bleio/__init__.h"
+
+void check_nrf_error(uint32_t err_code) {
+ if (err_code == NRF_SUCCESS) {
+ return;
+ }
+ switch (err_code) {
+ case NRF_ERROR_TIMEOUT:
+ mp_raise_msg(&mp_type_TimeoutError, NULL);
+ return;
+ case BLE_ERROR_INVALID_CONN_HANDLE:
+ mp_raise_bleio_ConnectionError(translate("Not connected"));
+ return;
+ default:
+ mp_raise_bleio_BluetoothError(translate("Unknown soft device error: %04x"), err_code);
+ break;
+ }
+}
+
+void check_gatt_status(uint16_t gatt_status) {
+ if (gatt_status == BLE_GATT_STATUS_SUCCESS) {
+ return;
+ }
+ switch (gatt_status) {
+ case BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION:
+ mp_raise_bleio_SecurityError(translate("Insufficient authentication"));
+ return;
+ case BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION:
+ mp_raise_bleio_SecurityError(translate("Insufficient encryption"));
+ return;
+ default:
+ mp_raise_bleio_BluetoothError(translate("Unknown gatt error: 0x%04x"), gatt_status);
+ }
+}
+
+void check_sec_status(uint8_t sec_status) {
+ if (sec_status == BLE_GAP_SEC_STATUS_SUCCESS) {
+ return;
+ }
+
+ switch (sec_status) {
+ case BLE_GAP_SEC_STATUS_UNSPECIFIED:
+ mp_raise_bleio_SecurityError(translate("Unspecified issue. Can be that the pairing prompt on the other device was declined or ignored."));
+ return;
+ default:
+ mp_raise_bleio_SecurityError(translate("Unknown security error: 0x%04x"), sec_status);
+ }
+}
+
+// Turn off BLE on a reset or reload.
+void bleio_reset() {
+ if (!common_hal_bleio_adapter_get_enabled(&common_hal_bleio_adapter_obj)) {
+ return;
+ }
+ bleio_adapter_reset(&common_hal_bleio_adapter_obj);
+ if (!vm_used_ble) {
+ // No user-code BLE operations were done, so we can maintain the supervisor state.
+ return;
+ }
+ common_hal_bleio_adapter_set_enabled(&common_hal_bleio_adapter_obj, false);
+ bonding_reset();
+ supervisor_start_bluetooth();
+}
+
+// The singleton _bleio.Adapter object, bound to _bleio.adapter
+// It currently only has properties and no state
+bleio_adapter_obj_t common_hal_bleio_adapter_obj = {
+ .base = {
+ .type = &bleio_adapter_type,
+ },
+};
+
+void common_hal_bleio_check_connected(uint16_t conn_handle) {
+ if (conn_handle == BLE_CONN_HANDLE_INVALID) {
+ mp_raise_bleio_ConnectionError(translate("Not connected"));
+ }
+}
+
+// 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 is ignored unless this is a system attribute.
+ // If we're not connected, that's OK, because we can still read and write the local value.
+
+ ble_gatts_value_t gatts_value = {
+ .p_value = buf,
+ .len = len,
+ };
+
+ check_nrf_error(sd_ble_gatts_value_get(conn_handle, handle, &gatts_value));
+
+ return gatts_value.len;
+}
+
+void common_hal_bleio_gatts_write(uint16_t handle, uint16_t conn_handle, mp_buffer_info_t *bufinfo) {
+ // conn_handle is ignored unless this is a system attribute.
+ // If we're not connected, 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,
+ };
+
+ check_nrf_error(sd_ble_gatts_value_set(conn_handle, handle, &gatts_value));
+}
+
+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) {
+
+ // 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 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);
+
+ 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);
+
+ uint32_t nrf_error = NRF_ERROR_BUSY;
+ while (nrf_error == NRF_ERROR_BUSY) {
+ nrf_error = sd_ble_gattc_read(conn_handle, handle, 0);
+ }
+ if (nrf_error != NRF_SUCCESS) {
+ ble_drv_remove_event_handler(_on_gattc_read_rsp_evt, &read_info);
+ check_nrf_error(nrf_error);
+ }
+
+ while (!read_info.done) {
+ RUN_BACKGROUND_TASKS;
+ }
+
+ ble_drv_remove_event_handler(_on_gattc_read_rsp_evt, &read_info);
+ check_gatt_status(read_info.status);
+ 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) {
+ common_hal_bleio_check_connected(conn_handle);
+
+ ble_gattc_write_params_t write_params = {
+ .write_op = write_no_response ? BLE_GATT_OP_WRITE_CMD: BLE_GATT_OP_WRITE_REQ,
+ .handle = handle,
+ .p_value = bufinfo->buf,
+ .len = bufinfo->len,
+ };
+
+ while (1) {
+ uint32_t err_code = sd_ble_gattc_write(conn_handle, &write_params);
+ if (err_code == NRF_SUCCESS) {
+ break;
+ }
+
+ // Write with response will return NRF_ERROR_BUSY if the response has not been received.
+ // Write without reponse will return NRF_ERROR_RESOURCES if too many writes are pending.
+ if (err_code == NRF_ERROR_BUSY || err_code == NRF_ERROR_RESOURCES) {
+ // We could wait for an event indicating the write is complete, but just retrying is easier.
+ MICROPY_VM_HOOK_LOOP;
+ continue;
+ }
+
+ // Some real error occurred.
+ check_nrf_error(err_code);
+ }
+
+}
+
+void common_hal_bleio_gc_collect(void) {
+ bleio_adapter_gc_collect(&common_hal_bleio_adapter_obj);
+}
diff --git a/devices/ble_hci/common-hal/_bleio/__init__.h b/devices/ble_hci/common-hal/_bleio/__init__.h
new file mode 100644
index 000000000..77548dac1
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/__init__.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Dan Halbert for Adafruit Industries
+ *
+ * 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_BLE_HCI_COMMON_HAL_INIT_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_INIT_H
+
+void bleio_reset(void);
+
+typedef struct {
+ // ble_gap_enc_key_t own_enc;
+ // ble_gap_enc_key_t peer_enc;
+ // ble_gap_id_key_t peer_id;
+} bonding_keys_t;
+
+// We assume variable length data.
+// 20 bytes max (23 - 3).
+#define GATT_MAX_DATA_LENGTH (BLE_GATT_ATT_MTU_DEFAULT - 3)
+
+// These helpers raise the appropriate exceptions if the code doesn't equal success.
+void check_nrf_error(uint32_t err_code);
+void check_gatt_status(uint16_t gatt_status);
+void check_sec_status(uint8_t sec_status);
+
+// 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_BLE_HCI_COMMON_HAL_INIT_H
diff --git a/devices/ble_hci/common-hal/_bleio/bonding.c b/devices/ble_hci/common-hal/_bleio/bonding.c
new file mode 100644
index 000000000..d03e418f7
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/bonding.c
@@ -0,0 +1,306 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Dan Halbert for Adafruit Industries
+ *
+ * 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 <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "shared-bindings/_bleio/__init__.h"
+#include "shared-bindings/_bleio/Adapter.h"
+#include "shared-bindings/nvm/ByteArray.h"
+#include "supervisor/shared/tick.h"
+
+#include "bonding.h"
+
+// Internal flash area reserved for bonding storage.
+#define BONDING_PAGES_START_ADDR CIRCUITPY_BLE_CONFIG_START_ADDR
+#define BONDING_PAGES_END_ADDR (CIRCUITPY_BLE_CONFIG_START_ADDR + CIRCUITPY_BLE_CONFIG_SIZE)
+
+// First and last four bytes are magic bytes for id and version. Data is in between.
+// 'BD01'
+const uint32_t BONDING_FLAG = ('1' | '0' << 8 | 'D' << 16 | 'B' << 24);
+
+#define BONDING_DATA_START_ADDR (BONDING_PAGES_START_ADDR + sizeof(BONDING_FLAG))
+#define BONDING_DATA_END_ADDR (BONDING_PAGES_END_ADDR - sizeof(BONDING_FLAG))
+
+#define BONDING_START_FLAG_ADDR BONDING_PAGES_START_ADDR
+#define BONDING_END_FLAG_ADDR BONDING_DATA_END_ADDR
+
+// Save both system and user service info.
+#define SYS_ATTR_FLAGS (BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS | BLE_GATTS_SYS_ATTR_FLAG_USR_SRVCS)
+
+#if BONDING_DEBUG
+void bonding_print_block(bonding_block_t *block) {
+ printf("at 0x%08lx: is_central: %1d, type: 0x%x, ediv: 0x%04x, data_length: %d\n",
+ (uint32_t) block, block->is_central, block->type, block->ediv, block->data_length);
+}
+
+void bonding_print_keys(bonding_keys_t *keys) {
+ for (size_t i = 0; i < sizeof(bonding_keys_t); i ++) {
+ printf("%x", ((uint8_t*) keys)[i]);
+ }
+ printf("\n");
+}
+#endif
+
+STATIC size_t compute_block_size(uint16_t data_length) {
+ // Round data size up to the nearest 32-bit address.
+ return sizeof(bonding_block_t) + ((data_length + 3) & ~0x3);
+}
+
+void bonding_erase_storage(void) {
+ // Erase all pages in the bonding area.
+ for(uint32_t page_address = BONDING_PAGES_START_ADDR;
+ page_address < BONDING_PAGES_END_ADDR;
+ page_address += FLASH_PAGE_SIZE) {
+ // Argument is page number, not address.
+ sd_flash_page_erase_sync(page_address / FLASH_PAGE_SIZE);
+ }
+ // Write marker words at the beginning and the end of the bonding area.
+ uint32_t flag = BONDING_FLAG;
+ sd_flash_write_sync((uint32_t *) BONDING_START_FLAG_ADDR, &flag, 1);
+ sd_flash_write_sync((uint32_t *) BONDING_END_FLAG_ADDR, &flag, 1);
+}
+
+// Given NULL to start or block address, return the address of the next valid block.
+// The last block returned is the unused block at the end.
+// Return NULL if we have run off the end of the bonding space.
+
+STATIC bonding_block_t *next_block(bonding_block_t *block) {
+ while (1) {
+ // Advance to next block.
+ if (block == NULL) {
+ return (bonding_block_t *) BONDING_DATA_START_ADDR;
+ } else if (block->type == BLOCK_UNUSED) {
+ // Already at last block (the unused block).
+ return NULL;
+ }
+
+ // Advance to next block.
+ block = (bonding_block_t *) ((uint8_t *) block + compute_block_size(block->data_length));
+
+ if (block >= (bonding_block_t *) BONDING_DATA_END_ADDR) {
+ // Went past end of bonding space.
+ return NULL;
+ }
+ if (block->type != BLOCK_INVALID) {
+ // Found an empty or a valid block.
+ return block;
+ }
+ // Invalid block (was erased); try again.
+ }
+}
+
+// Find the block with given is_central, type and ediv value.
+// If type == BLOCK_UNUSED, ediv is ignored and the the sole unused block at the end is returned.
+// If not found, return NULL.
+STATIC bonding_block_t *find_existing_block(bool is_central, bonding_block_type_t type, uint16_t ediv) {
+ bonding_block_t *block = NULL;
+ while (1) {
+ block = next_block(block);
+ if (block == NULL) {
+ return NULL;
+ }
+ // If types match, and block is unused, just return it.
+ // Otherwise check that is_central and ediv match.
+ if (type == block->type) {
+ if (type == BLOCK_UNUSED ||
+ (is_central == block->is_central && ediv == block->ediv)) {
+ return block;
+ }
+ }
+ }
+}
+
+// Get an empty block large enough to store data_length data.
+STATIC bonding_block_t* find_unused_block(uint16_t data_length) {
+ bonding_block_t *unused_block = find_existing_block(true, BLOCK_UNUSED, EDIV_INVALID);
+ // If no more room, erase all existing blocks and start over.
+ if (!unused_block ||
+ (uint8_t *) unused_block + compute_block_size(data_length) >= (uint8_t *) BONDING_DATA_END_ADDR) {
+ bonding_erase_storage();
+ unused_block = (bonding_block_t *) BONDING_DATA_START_ADDR;
+ }
+ return unused_block;
+}
+
+// Set the header word to all 0's, to mark the block as invalid.
+// We don't change data_length, so we can still skip over this block.
+STATIC void invalidate_block(bonding_block_t *block) {
+ uint32_t zero = 0;
+ sd_flash_write_sync((uint32_t *) block, &zero, 1);
+}
+
+// Write bonding block header.
+STATIC void write_block_header(bonding_block_t *dest_block, bonding_block_t *source_block_header) {
+ sd_flash_write_sync((uint32_t *) dest_block, (uint32_t *) source_block_header, sizeof(bonding_block_t) / 4);
+}
+
+// Write variable-length data at end of bonding block.
+STATIC void write_block_data(bonding_block_t *dest_block, uint8_t *data, uint16_t data_length) {
+ // Minimize the number of writes. Datasheet says no more than two writes per word before erasing again.
+
+ // Start writing after the current header.
+ uint32_t *flash_word_p = (uint32_t *) ((uint8_t *) dest_block + sizeof(bonding_block_t));
+ while (1) {
+ uint32_t word = 0xffffffff;
+ memcpy(&word, data, data_length >= 4 ? 4 : data_length);
+ sd_flash_write_sync(flash_word_p, &word, 1);
+ if (data_length <= 4) {
+ break;
+ }
+ data_length -= 4;
+ data += 4;
+ // Increment by word size.
+ flash_word_p++;
+ }
+}
+
+STATIC void write_sys_attr_block(bleio_connection_internal_t *connection) {
+ uint16_t length = 0;
+ // First find out how big a buffer we need, then fetch the data.
+ if(sd_ble_gatts_sys_attr_get(connection->conn_handle, NULL, &length, SYS_ATTR_FLAGS) != NRF_SUCCESS) {
+ return;
+ }
+ uint8_t sys_attr[length];
+ if(sd_ble_gatts_sys_attr_get(connection->conn_handle, sys_attr, &length, SYS_ATTR_FLAGS) != NRF_SUCCESS) {
+ return;
+ }
+
+ // Is there an existing sys_attr block that matches the current sys_attr data?
+ bonding_block_t *existing_block =
+ find_existing_block(connection->is_central, BLOCK_SYS_ATTR, connection->ediv);
+ if (existing_block) {
+ if (length == existing_block->data_length &&
+ memcmp(sys_attr, existing_block->data, length) == 0) {
+ // Identical block found. No need to store again.
+ return;
+ }
+ // Data doesn't match. Invalidate block and store a new one.
+ invalidate_block(existing_block);
+ }
+
+ bonding_block_t block_header = {
+ .is_central = connection->is_central,
+ .type = BLOCK_SYS_ATTR,
+ .ediv = connection->ediv,
+ .conn_handle = connection->conn_handle,
+ .data_length = length,
+ };
+ bonding_block_t *new_block = find_unused_block(length);
+ write_block_header(new_block, &block_header);
+ write_block_data(new_block, sys_attr, length);
+ return;
+}
+
+STATIC void write_keys_block(bleio_connection_internal_t *connection) {
+ uint16_t const ediv = connection->is_central
+ ? connection->bonding_keys.peer_enc.master_id.ediv
+ : connection->bonding_keys.own_enc.master_id.ediv;
+
+ // Is there an existing keys block that matches?
+ bonding_block_t *existing_block = find_existing_block(connection->is_central, BLOCK_KEYS, ediv);
+ if (existing_block) {
+ if (existing_block->data_length == sizeof(bonding_keys_t) &&
+ memcmp(existing_block->data, &connection->bonding_keys, sizeof(bonding_keys_t)) == 0) {
+ // Identical block found. No need to store again.
+ return;
+ }
+ // Data doesn't match. Invalidate block and store a new one.
+ invalidate_block(existing_block);
+ }
+
+ bonding_block_t block_header = {
+ .is_central = connection->is_central,
+ .type = BLOCK_KEYS,
+ .ediv = ediv,
+ .conn_handle = connection->conn_handle,
+ .data_length = sizeof(bonding_keys_t),
+ };
+ bonding_block_t *new_block = find_unused_block(sizeof(bonding_keys_t));
+ write_block_header(new_block, &block_header);
+ write_block_data(new_block, (uint8_t *) &connection->bonding_keys, sizeof(bonding_keys_t));
+}
+
+void bonding_clear_keys(bonding_keys_t *bonding_keys) {
+ memset((uint8_t*) bonding_keys, 0, sizeof(bonding_keys_t));
+}
+
+void bonding_reset(void) {
+ if (BONDING_FLAG != *((uint32_t *) BONDING_START_FLAG_ADDR) ||
+ BONDING_FLAG != *((uint32_t *) BONDING_END_FLAG_ADDR)) {
+ bonding_erase_storage();
+ }
+}
+
+// Write bonding blocks to flash. Requests have been queued during evt handlers.
+void bonding_background(void) {
+ // A paired connection will request that its keys and CCCD values be stored.
+ // The CCCD store whenever a CCCD value is written.
+ for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
+ bleio_connection_internal_t *connection = &bleio_connections[i];
+
+ // Wait at least one second before saving CCCD, to consolidate
+ // writes that involve multiple CCCDs. For instance, for HID,
+ // three CCCD's are set in short succession by the HID client.
+ if (connection->do_bond_cccds) {
+ uint64_t current_ticks_ms = supervisor_ticks_ms64();
+ if (current_ticks_ms - connection->do_bond_cccds_request_time >= 1000) {
+ write_sys_attr_block(connection);
+ connection->do_bond_cccds = false;
+ }
+ }
+
+ if (connection->do_bond_keys) {
+ write_keys_block(connection);
+ connection->do_bond_keys = false;
+ }
+ }
+}
+
+bool bonding_load_cccd_info(bool is_central, uint16_t conn_handle, uint16_t ediv) {
+ bonding_block_t *block = find_existing_block(is_central, BLOCK_SYS_ATTR, ediv);
+ if (block == NULL) {
+ return false;
+ }
+
+ return NRF_SUCCESS ==
+ sd_ble_gatts_sys_attr_set(conn_handle, block->data, block->data_length, SYS_ATTR_FLAGS);
+}
+
+bool bonding_load_keys(bool is_central, uint16_t ediv, bonding_keys_t *bonding_keys) {
+ bonding_block_t *block = find_existing_block(is_central, BLOCK_KEYS, ediv);
+ if (block == NULL) {
+ return false;
+ }
+ if (sizeof(bonding_keys_t) != block->data_length) {
+ // bonding_keys_t is a fixed length, so lengths should match.
+ return false;
+ }
+
+ memcpy(bonding_keys, block->data, block->data_length);
+ return true;
+}
diff --git a/devices/ble_hci/common-hal/_bleio/bonding.h b/devices/ble_hci/common-hal/_bleio/bonding.h
new file mode 100644
index 000000000..7fa66972d
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/bonding.h
@@ -0,0 +1,83 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Dan Halbert for Adafruit Industries
+ *
+ * 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_BLE_HCI_COMMON_HAL_BONDING_H
+#define MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_BONDING_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "common-hal/_bleio/__init__.h"
+
+#define EDIV_INVALID (0xffff)
+
+#define BONDING_DEBUG (1)
+#if BONDING_DEBUG
+ #define BONDING_DEBUG_PRINTF(...) printf(__VA_ARGS__)
+ #define BONDING_DEBUG_PRINT_BLOCK(block) bonding_print_block(block)
+ #define BONDING_DEBUG_PRINT_KEYS(keys) bonding_print_keys(keys)
+#else
+ #define BONDING_DEBUG_PRINTF(...)
+ #define BONDING_DEBUG_PRINT_BLOCK(block)
+ #define BONDING_DEBUG_PRINT_KEYS(keys)
+#endif
+
+// Bonding data is stored in variable-length blocks consecutively in
+// erased flash (all 1's). The blocks are 32-bit aligned, though the
+// data may be any number of bytes. We hop through the blocks using
+// the size field to find the next block. When we hit a word that is
+// all 1's, we have reached the end of the blocks. We can write a new
+// block there.
+
+typedef enum {
+ BLOCK_INVALID = 0, // Ignore this block
+ BLOCK_KEYS = 1, // Block contains bonding keys.
+ BLOCK_SYS_ATTR = 2, // Block contains sys_attr values (CCCD settings, etc.).
+ BLOCK_UNUSED = 0xff, // Initial erased value.
+} bonding_block_type_t;
+
+typedef struct {
+ bool is_central: 1; // 1 if data is for a central role.
+ uint16_t reserved: 7; // Not currently used
+ bonding_block_type_t type: 8; // What kind of data is stored in.
+ uint16_t ediv; // ediv value; used as a lookup key.
+ uint16_t conn_handle; // Connection handle: used when a BLOCK_SYS_ATTR is queued to write.
+ // Not used as a key, etc.
+ uint16_t data_length; // Length of data in bytes, including ediv, not including padding.
+ // End of block header. 32-bit boundary here.
+ uint8_t data[]; // Rest of data in the block. Needs to be 32-bit aligned.
+ // Block is padded to 32-bit alignment.
+} bonding_block_t;
+
+void bonding_background(void);
+void bonding_erase_storage(void);
+void bonding_reset(void);
+void bonding_clear_keys(bonding_keys_t *bonding_keys);
+bool bonding_load_cccd_info(bool is_central, uint16_t conn_handle, uint16_t ediv);
+bool bonding_load_keys(bool is_central, uint16_t ediv, bonding_keys_t *bonding_keys);
+
+#endif // MICROPY_INCLUDED_BLE_HCI_COMMON_HAL_BONDING_H
diff --git a/devices/ble_hci/common-hal/_bleio/hci.c b/devices/ble_hci/common-hal/_bleio/hci.c
new file mode 100644
index 000000000..8ff69f202
--- /dev/null
+++ b/devices/ble_hci/common-hal/_bleio/hci.c
@@ -0,0 +1,675 @@
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "HCI.h"
+
+#define HCI_COMMAND_PKT 0x01
+#define HCI_ACLDATA_PKT 0x02
+#define HCI_EVENT_PKT 0x04
+
+#define EVT_DISCONN_COMPLETE 0x05
+#define EVT_CMD_COMPLETE 0xe
+#define EVT_CMD_STATUS 0x0f
+#define EVT_NUM_COMP_PKTS 0x13
+#define EVT_LE_META_EVENT 0x3e
+
+#define EVT_LE_CONN_COMPLETE 0x01
+#define EVT_LE_ADVERTISING_REPORT 0x02
+
+#define OGF_LINK_CTL 0x01
+#define OGF_HOST_CTL 0x03
+#define OGF_INFO_PARAM 0x04
+#define OGF_STATUS_PARAM 0x05
+#define OGF_LE_CTL 0x08
+
+// OGF_LINK_CTL
+#define OCF_DISCONNECT 0x0006
+
+// OGF_HOST_CTL
+#define OCF_SET_EVENT_MASK 0x0001
+#define OCF_RESET 0x0003
+
+// OGF_INFO_PARAM
+#define OCF_READ_LOCAL_VERSION 0x0001
+#define OCF_READ_BD_ADDR 0x0009
+
+// OGF_STATUS_PARAM
+#define OCF_READ_RSSI 0x0005
+
+// OGF_LE_CTL
+#define OCF_LE_READ_BUFFER_SIZE 0x0002
+#define OCF_LE_SET_RANDOM_ADDRESS 0x0005
+#define OCF_LE_SET_ADVERTISING_PARAMETERS 0x0006
+#define OCF_LE_SET_ADVERTISING_DATA 0x0008
+#define OCF_LE_SET_SCAN_RESPONSE_DATA 0x0009
+#define OCF_LE_SET_ADVERTISE_ENABLE 0x000a
+#define OCF_LE_SET_SCAN_PARAMETERS 0x000b
+#define OCF_LE_SET_SCAN_ENABLE 0x000c
+#define OCF_LE_CREATE_CONN 0x000d
+#define OCF_LE_CANCEL_CONN 0x000e
+#define OCF_LE_CONN_UPDATE 0x0013
+
+#define HCI_OE_USER_ENDED_CONNECTION 0x13
+
+HCIClass::HCIClass() :
+ _debug(NULL),
+ _recvIndex(0),
+ _pendingPkt(0)
+{
+}
+
+HCIClass::~HCIClass()
+{
+}
+
+int HCIClass::begin()
+{
+ _recvIndex = 0;
+
+ return HCITransport.begin();
+}
+
+void HCIClass::end()
+{
+ HCITransport.end();
+}
+
+void HCIClass::poll()
+{
+ poll(0);
+}
+
+void HCIClass::poll(unsigned long timeout)
+{
+#if defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_METRO_M4_AIRLIFT_LITE)
+ digitalWrite(NINA_RTS, LOW);
+#endif
+
+ if (timeout) {
+ HCITransport.wait(timeout);
+ }
+
+ while (HCITransport.available()) {
+ byte b = HCITransport.read();
+
+ _recvBuffer[_recvIndex++] = b;
+
+ if (_recvBuffer[0] == HCI_ACLDATA_PKT) {
+ if (_recvIndex > 5 && _recvIndex >= (5 + (_recvBuffer[3] + (_recvBuffer[4] << 8)))) {
+ if (_debug) {
+ dumpPkt("HCI ACLDATA RX <- ", _recvIndex, _recvBuffer);
+ }
+#if defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_METRO_M4_AIRLIFT_LITE)
+ digitalWrite(NINA_RTS, HIGH);
+#endif
+ int pktLen = _recvIndex - 1;
+ _recvIndex = 0;
+
+ handleAclDataPkt(pktLen, &_recvBuffer[1]);
+
+#if defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_METRO_M4_AIRLIFT_LITE)
+ digitalWrite(NINA_RTS, LOW);
+#endif
+ }
+ } else if (_recvBuffer[0] == HCI_EVENT_PKT) {
+ if (_recvIndex > 3 && _recvIndex >= (3 + _recvBuffer[2])) {
+ if (_debug) {
+ dumpPkt("HCI EVENT RX <- ", _recvIndex, _recvBuffer);
+ }
+#if defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_METRO_M4_AIRLIFT_LITE)
+ digitalWrite(NINA_RTS, HIGH);
+#endif
+ // received full event
+ int pktLen = _recvIndex - 1;
+ _recvIndex = 0;
+
+ handleEventPkt(pktLen, &_recvBuffer[1]);
+
+#if defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_METRO_M4_AIRLIFT_LITE)
+ digitalWrite(NINA_RTS, LOW);
+#endif
+ }
+ } else {
+ _recvIndex = 0;
+
+ if (_debug) {
+ _debug->println(b, HEX);
+ }
+ }
+ }
+
+#if defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_METRO_M4_AIRLIFT_LITE)
+ digitalWrite(NINA_RTS, HIGH);
+#endif
+}
+
+int HCIClass::reset()
+{
+ return sendCommand(OGF_HOST_CTL << 10 | OCF_RESET);
+}
+
+int HCIClass::readLocalVersion(uint8_t& hciVer, uint16_t& hciRev, uint8_t& lmpVer, uint16_t& manufacturer, uint16_t& lmpSubVer)
+{
+ int result = sendCommand(OGF_INFO_PARAM << 10 | OCF_READ_LOCAL_VERSION);
+
+ if (result == 0) {
+ struct __attribute__ ((packed)) HCILocalVersion {
+ uint8_t hciVer;
+ uint16_t hciRev;
+ uint8_t lmpVer;
+ uint16_t manufacturer;
+ uint16_t lmpSubVer;
+ } *localVersion = (HCILocalVersion*)_cmdResponse;
+
+ hciVer = localVersion->hciVer;
+ hciRev = localVersion->hciRev;
+ lmpVer = localVersion->lmpVer;
+ manufacturer = localVersion->manufacturer;
+ lmpSubVer = localVersion->lmpSubVer;
+ }
+
+ return result;
+}
+
+int HCIClass::readBdAddr(uint8_t addr[6])
+{
+ int result = sendCommand(OGF_INFO_PARAM << 10 | OCF_READ_BD_ADDR);
+
+ if (result == 0) {
+ memcpy(addr, _cmdResponse, 6);
+ }
+
+ return result;
+}
+
+int HCIClass::readRssi(uint16_t handle)
+{
+ int result = sendCommand(OGF_STATUS_PARAM << 10 | OCF_READ_RSSI, sizeof(handle), &handle);
+ int rssi = 127;
+
+ if (result == 0) {
+ struct __attribute__ ((packed)) HCIReadRssi {
+ uint16_t handle;
+ int8_t rssi;
+ } *readRssi = (HCIReadRssi*)_cmdResponse;
+
+ if (readRssi->handle == handle) {
+ rssi = readRssi->rssi;
+ }
+ }
+
+ return rssi;
+}
+
+int HCIClass::setEventMask(uint64_t eventMask)
+{
+ return sendCommand(OGF_HOST_CTL << 10 | OCF_SET_EVENT_MASK, sizeof(eventMask), &eventMask);
+}
+
+int HCIClass::readLeBufferSize(uint16_t& pktLen, uint8_t& maxPkt)
+{
+ int result = sendCommand(OGF_LE_CTL << 10 | OCF_LE_READ_BUFFER_SIZE);
+
+ if (result == 0) {
+ struct __attribute__ ((packed)) HCILeBufferSize {
+ uint16_t pktLen;
+ uint8_t maxPkt;
+ } *leBufferSize = (HCILeBufferSize*)_cmdResponse;
+
+ pktLen = leBufferSize->pktLen;
+ _maxPkt = maxPkt = leBufferSize->maxPkt;
+
+#ifndef __AVR__
+ ATT.setMaxMtu(pktLen - 9); // max pkt len - ACL header size
+#endif
+ }
+
+ return result;
+}
+
+int HCIClass::leSetRandomAddress(uint8_t addr[6])
+{
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_RANDOM_ADDRESS, 6, addr);
+}
+
+int HCIClass::leSetAdvertisingParameters(uint16_t minInterval, uint16_t maxInterval,
+ uint8_t advType, uint8_t ownBdaddrType,
+ uint8_t directBdaddrType, uint8_t directBdaddr[6],
+ uint8_t chanMap,
+ uint8_t filter)
+{
+ struct __attribute__ ((packed)) HCILeAdvertisingParameters {
+ uint16_t minInterval;
+ uint16_t maxInterval;
+ uint8_t advType;
+ uint8_t ownBdaddrType;
+ uint8_t directBdaddrType;
+ uint8_t directBdaddr[6];
+ uint8_t chanMap;
+ uint8_t filter;
+ } leAdvertisingParamters;
+
+ leAdvertisingParamters.minInterval = minInterval;
+ leAdvertisingParamters.maxInterval = maxInterval;
+ leAdvertisingParamters.advType = advType;
+ leAdvertisingParamters.ownBdaddrType = ownBdaddrType;
+ leAdvertisingParamters.directBdaddrType = directBdaddrType;
+ memcpy(leAdvertisingParamters.directBdaddr, directBdaddr, 6);
+ leAdvertisingParamters.chanMap = chanMap;
+ leAdvertisingParamters.filter = filter;
+
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_ADVERTISING_PARAMETERS, sizeof(leAdvertisingParamters), &leAdvertisingParamters);
+}
+
+int HCIClass::leSetAdvertisingData(uint8_t length, uint8_t data[])
+{
+ struct __attribute__ ((packed)) HCILeAdvertisingData {
+ uint8_t length;
+ uint8_t data[31];
+ } leAdvertisingData;
+
+ memset(&leAdvertisingData, 0, sizeof(leAdvertisingData));
+ leAdvertisingData.length = length;
+ memcpy(leAdvertisingData.data, data, length);
+
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_ADVERTISING_DATA, sizeof(leAdvertisingData), &leAdvertisingData);
+}
+
+int HCIClass::leSetScanResponseData(uint8_t length, uint8_t data[])
+{
+ struct __attribute__ ((packed)) HCILeScanResponseData {
+ uint8_t length;
+ uint8_t data[31];
+ } leScanResponseData;
+
+ memset(&leScanResponseData, 0, sizeof(leScanResponseData));
+ leScanResponseData.length = length;
+ memcpy(leScanResponseData.data, data, length);
+
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_SCAN_RESPONSE_DATA, sizeof(leScanResponseData), &leScanResponseData);
+}
+
+int HCIClass::leSetAdvertiseEnable(uint8_t enable)
+{
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_ADVERTISE_ENABLE, sizeof(enable), &enable);
+}
+
+int HCIClass::leSetScanParameters(uint8_t type, uint16_t interval, uint16_t window,
+ uint8_t ownBdaddrType, uint8_t filter)
+{
+ struct __attribute__ ((packed)) HCILeSetScanParameters {
+ uint8_t type;
+ uint16_t interval;
+ uint16_t window;
+ uint8_t ownBdaddrType;
+ uint8_t filter;
+ } leScanParameters;
+
+ leScanParameters.type = type;
+ leScanParameters.interval = interval;
+ leScanParameters.window = window;
+ leScanParameters.ownBdaddrType = ownBdaddrType;
+ leScanParameters.filter = filter;
+
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_SCAN_PARAMETERS, sizeof(leScanParameters), &leScanParameters);
+}
+
+int HCIClass::leSetScanEnable(uint8_t enabled, uint8_t duplicates)
+{
+ struct __attribute__ ((packed)) HCILeSetScanEnableData {
+ uint8_t enabled;
+ uint8_t duplicates;
+ } leScanEnableData;
+
+ leScanEnableData.enabled = enabled;
+ leScanEnableData.duplicates = duplicates;
+
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_SET_SCAN_ENABLE, sizeof(leScanEnableData), &leScanEnableData);
+}
+
+int HCIClass::leCreateConn(uint16_t interval, uint16_t window, uint8_t initiatorFilter,
+ uint8_t peerBdaddrType, uint8_t peerBdaddr[6], uint8_t ownBdaddrType,
+ uint16_t minInterval, uint16_t maxInterval, uint16_t latency,
+ uint16_t supervisionTimeout, uint16_t minCeLength, uint16_t maxCeLength)
+{
+ struct __attribute__ ((packed)) HCILeCreateConnData {
+ uint16_t interval;
+ uint16_t window;
+ uint8_t initiatorFilter;
+ uint8_t peerBdaddrType;
+ uint8_t peerBdaddr[6];
+ uint8_t ownBdaddrType;
+ uint16_t minInterval;
+ uint16_t maxInterval;
+ uint16_t latency;
+ uint16_t supervisionTimeout;
+ uint16_t minCeLength;
+ uint16_t maxCeLength;
+ } leCreateConnData;
+
+ leCreateConnData.interval = interval;
+ leCreateConnData.window = window;
+ leCreateConnData.initiatorFilter = initiatorFilter;
+ leCreateConnData.peerBdaddrType = peerBdaddrType;
+ memcpy(leCreateConnData.peerBdaddr, peerBdaddr, sizeof(leCreateConnData.peerBdaddr));
+ leCreateConnData.ownBdaddrType = ownBdaddrType;
+ leCreateConnData.minInterval = minInterval;
+ leCreateConnData.maxInterval = maxInterval;
+ leCreateConnData.latency = latency;
+ leCreateConnData.supervisionTimeout = supervisionTimeout;
+ leCreateConnData.minCeLength = minCeLength;
+ leCreateConnData.maxCeLength = maxCeLength;
+
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_CREATE_CONN, sizeof(leCreateConnData), &leCreateConnData);
+}
+
+int HCIClass::leCancelConn()
+{
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_CANCEL_CONN, 0, NULL);
+}
+
+int HCIClass::leConnUpdate(uint16_t handle, uint16_t minInterval, uint16_t maxInterval,
+ uint16_t latency, uint16_t supervisionTimeout)
+{
+ struct __attribute__ ((packed)) HCILeConnUpdateData {
+ uint16_t handle;
+ uint16_t minInterval;
+ uint16_t maxInterval;
+ uint16_t latency;
+ uint16_t supervisionTimeout;
+ uint16_t minCeLength;
+ uint16_t maxCeLength;
+ } leConnUpdateData;
+
+ leConnUpdateData.handle = handle;
+ leConnUpdateData.minInterval = minInterval;
+ leConnUpdateData.maxInterval = maxInterval;
+ leConnUpdateData.latency = latency;
+ leConnUpdateData.supervisionTimeout = supervisionTimeout;
+ leConnUpdateData.minCeLength = 0x0004;
+ leConnUpdateData.maxCeLength = 0x0006;
+
+ return sendCommand(OGF_LE_CTL << 10 | OCF_LE_CONN_UPDATE, sizeof(leConnUpdateData), &leConnUpdateData);
+}
+
+int HCIClass::sendAclPkt(uint16_t handle, uint8_t cid, uint8_t plen, void* data)
+{
+ while (_pendingPkt >= _maxPkt) {
+ poll();
+ }
+
+ struct __attribute__ ((packed)) HCIACLHdr {
+ uint8_t pktType;
+ uint16_t handle;
+ uint16_t dlen;
+ uint16_t plen;
+ uint16_t cid;
+ } aclHdr = { HCI_ACLDATA_PKT, handle, uint8_t(plen + 4), plen, cid };
+
+ uint8_t txBuffer[sizeof(aclHdr) + plen];
+ memcpy(txBuffer, &aclHdr, sizeof(aclHdr));
+ memcpy(&txBuffer[sizeof(aclHdr)], data, plen);
+
+ if (_debug) {
+ dumpPkt("HCI ACLDATA TX -> ", sizeof(aclHdr) + plen, txBuffer);
+ }
+
+ _pendingPkt++;
+ HCITransport.write(txBuffer, sizeof(aclHdr) + plen);
+
+ return 0;
+}
+
+int HCIClass::disconnect(uint16_t handle)
+{
+ struct __attribute__ ((packed)) HCIDisconnectData {
+ uint16_t handle;
+ uint8_t reason;
+ } disconnectData = { handle, HCI_OE_USER_ENDED_CONNECTION };
+
+ return sendCommand(OGF_LINK_CTL << 10 | OCF_DISCONNECT, sizeof(disconnectData), &disconnectData);
+}
+
+void HCIClass::debug(Stream& stream)
+{
+ _debug = &stream;
+}
+
+void HCIClass::noDebug()
+{
+ _debug = NULL;
+}
+
+int HCIClass::sendCommand(uint16_t opcode, uint8_t plen, void* parameters)
+{
+ struct __attribute__ ((packed)) {
+ uint8_t pktType;
+ uint16_t opcode;
+ uint8_t plen;
+ } pktHdr = {HCI_COMMAND_PKT, opcode, plen};
+
+ uint8_t txBuffer[sizeof(pktHdr) + plen];
+ memcpy(txBuffer, &pktHdr, sizeof(pktHdr));
+ memcpy(&txBuffer[sizeof(pktHdr)], parameters, plen);
+
+ if (_debug) {
+ dumpPkt("HCI COMMAND TX -> ", sizeof(pktHdr) + plen, txBuffer);
+ }
+
+ HCITransport.write(txBuffer, sizeof(pktHdr) + plen);
+
+ _cmdCompleteOpcode = 0xffff;
+ _cmdCompleteStatus = -1;
+
+ for (unsigned long start = millis(); _cmdCompleteOpcode != opcode && millis() < (start + 1000);) {
+ poll();
+ }
+
+ return _cmdCompleteStatus;
+}
+
+void HCIClass::handleAclDataPkt(uint8_t /*plen*/, uint8_t pdata[])
+{
+ struct __attribute__ ((packed)) HCIACLHdr {
+ uint16_t handle;
+ uint16_t dlen;
+ uint16_t len;
+ uint16_t cid;
+ } *aclHdr = (HCIACLHdr*)pdata;
+
+ uint16_t aclFlags = (aclHdr->handle & 0xf000) >> 12;
+
+ if ((aclHdr->dlen - 4) != aclHdr->len) {
+ // packet is fragmented
+ if (aclFlags != 0x01) {
+ // copy into ACL buffer
+ memcpy(_aclPktBuffer, &_recvBuffer[1], sizeof(HCIACLHdr) + aclHdr->dlen - 4);
+ } else {
+ // copy next chunk into the buffer
+ HCIACLHdr* aclBufferHeader = (HCIACLHdr*)_aclPktBuffer;
+
+ memcpy(&_aclPktBuffer[sizeof(HCIACLHdr) + aclBufferHeader->dlen - 4], &_recvBuffer[1 + sizeof(aclHdr->handle) + sizeof(aclHdr->dlen)], aclHdr->dlen);
+
+ aclBufferHeader->dlen += aclHdr->dlen;
+ aclHdr = aclBufferHeader;
+ }
+ }
+
+ if ((aclHdr->dlen - 4) != aclHdr->len) {
+ // don't have the full packet yet
+ return;
+ }
+
+ if (aclHdr->cid == ATT_CID) {
+ if (aclFlags == 0x01) {
+ // use buffered packet
+ ATT.handleData(aclHdr->handle & 0x0fff, aclHdr->len, &_aclPktBuffer[sizeof(HCIACLHdr)]);
+ } else {
+ // use the recv buffer
+ ATT.handleData(aclHdr->handle & 0x0fff, aclHdr->len, &_recvBuffer[1 + sizeof(HCIACLHdr)]);
+ }
+ } else if (aclHdr->cid == SIGNALING_CID) {
+ L2CAPSignaling.handleData(aclHdr->handle & 0x0fff, aclHdr->len, &_recvBuffer[1 + sizeof(HCIACLHdr)]);
+ } else {
+ struct __attribute__ ((packed)) {
+ uint8_t op;
+ uint8_t id;
+ uint16_t length;
+ uint16_t reason;
+ uint16_t localCid;
+ uint16_t remoteCid;
+ } l2capRejectCid= { 0x01, 0x00, 0x006, 0x0002, aclHdr->cid, 0x0000 };
+
+ sendAclPkt(aclHdr->handle & 0x0fff, 0x0005, sizeof(l2capRejectCid), &l2capRejectCid);
+ }
+}
+
+void HCIClass::handleNumCompPkts(uint16_t /*handle*/, uint16_t numPkts)
+{
+ if (numPkts && _pendingPkt > numPkts) {
+ _pendingPkt -= numPkts;
+ } else {
+ _pendingPkt = 0;
+ }
+}
+
+void HCIClass::handleEventPkt(uint8_t /*plen*/, uint8_t pdata[])
+{
+ struct __attribute__ ((packed)) HCIEventHdr {
+ uint8_t evt;
+ uint8_t plen;
+ } *eventHdr = (HCIEventHdr*)pdata;
+
+ if (eventHdr->evt == EVT_DISCONN_COMPLETE) {
+ struct __attribute__ ((packed)) DisconnComplete {
+ uint8_t status;
+ uint16_t handle;
+ uint8_t reason;
+ } *disconnComplete = (DisconnComplete*)&pdata[sizeof(HCIEventHdr)];
+
+ ATT.removeConnection(disconnComplete->handle, disconnComplete->reason);
+ L2CAPSignaling.removeConnection(disconnComplete->handle, disconnComplete->reason);
+
+ HCI.leSetAdvertiseEnable(0x01);
+ } else if (eventHdr->evt == EVT_CMD_COMPLETE) {
+ struct __attribute__ ((packed)) CmdComplete {
+ uint8_t ncmd;
+ uint16_t opcode;
+ uint8_t status;
+ } *cmdCompleteHeader = (CmdComplete*)&pdata[sizeof(HCIEventHdr)];
+
+ _cmdCompleteOpcode = cmdCompleteHeader->opcode;
+ _cmdCompleteStatus = cmdCompleteHeader->status;
+ _cmdResponseLen = pdata[1] - sizeof(CmdComplete);
+ _cmdResponse = &pdata[sizeof(HCIEventHdr) + sizeof(CmdComplete)];
+
+ } else if (eventHdr->evt == EVT_CMD_STATUS) {
+ struct __attribute__ ((packed)) CmdStatus {
+ uint8_t status;
+ uint8_t ncmd;
+ uint16_t opcode;
+ } *cmdStatusHeader = (CmdStatus*)&pdata[sizeof(HCIEventHdr)];
+
+ _cmdCompleteOpcode = cmdStatusHeader->opcode;
+ _cmdCompleteStatus = cmdStatusHeader->status;
+ _cmdResponseLen = 0;
+ } else if (eventHdr->evt == EVT_NUM_COMP_PKTS) {
+ uint8_t numHandles = pdata[sizeof(HCIEventHdr)];
+ uint16_t* data = (uint16_t*)&pdata[sizeof(HCIEventHdr) + sizeof(numHandles)];
+
+ for (uint8_t i = 0; i < numHandles; i++) {
+ handleNumCompPkts(data[0], data[1]);
+
+ data += 2;
+ }
+ } else if (eventHdr->evt == EVT_LE_META_EVENT) {
+ struct __attribute__ ((packed)) LeMetaEventHeader {
+ uint8_t subevent;
+ } *leMetaHeader = (LeMetaEventHeader*)&pdata[sizeof(HCIEventHdr)];
+
+ if (leMetaHeader->subevent == EVT_LE_CONN_COMPLETE) {
+ struct __attribute__ ((packed)) EvtLeConnectionComplete {
+ uint8_t status;
+ uint16_t handle;
+ uint8_t role;
+ uint8_t peerBdaddrType;
+ uint8_t peerBdaddr[6];
+ uint16_t interval;
+ uint16_t latency;
+ uint16_t supervisionTimeout;
+ uint8_t masterClockAccuracy;
+ } *leConnectionComplete = (EvtLeConnectionComplete*)&pdata[sizeof(HCIEventHdr) + sizeof(LeMetaEventHeader)];
+
+ if (leConnectionComplete->status == 0x00) {
+ ATT.addConnection(leConnectionComplete->handle,
+ leConnectionComplete->role,
+ leConnectionComplete->peerBdaddrType,
+ leConnectionComplete->peerBdaddr,
+ leConnectionComplete->interval,
+ leConnectionComplete->latency,
+ leConnectionComplete->supervisionTimeout,
+ leConnectionComplete->masterClockAccuracy);
+
+ L2CAPSignaling.addConnection(leConnectionComplete->handle,
+ leConnectionComplete->role,
+ leConnectionComplete->peerBdaddrType,
+ leConnectionComplete->peerBdaddr,
+ leConnectionComplete->interval,
+ leConnectionComplete->latency,
+ leConnectionComplete->supervisionTimeout,
+ leConnectionComplete->masterClockAccuracy);
+ }
+ } else if (leMetaHeader->subevent == EVT_LE_ADVERTISING_REPORT) {
+ struct __attribute__ ((packed)) EvtLeAdvertisingReport {
+ uint8_t status;
+ uint8_t type;
+ uint8_t peerBdaddrType;
+ uint8_t peerBdaddr[6];
+ uint8_t eirLength;
+ uint8_t eirData[31];
+ } *leAdvertisingReport = (EvtLeAdvertisingReport*)&pdata[sizeof(HCIEventHdr) + sizeof(LeMetaEventHeader)];
+
+ if (leAdvertisingReport->status == 0x01) {
+ // last byte is RSSI
+ int8_t rssi = leAdvertisingReport->eirData[leAdvertisingReport->eirLength];
+
+ GAP.handleLeAdvertisingReport(leAdvertisingReport->type,
+ leAdvertisingReport->peerBdaddrType,
+ leAdvertisingReport->peerBdaddr,
+ leAdvertisingReport->eirLength,
+ leAdvertisingReport->eirData,
+ rssi);
+
+ }
+ }
+ }
+}
+
+void HCIClass::dumpPkt(const char* prefix, uint8_t plen, uint8_t pdata[])
+{
+ if (_debug) {
+ _debug->print(prefix);
+
+ for (uint8_t i = 0; i < plen; i++) {
+ byte b = pdata[i];
+
+ if (b < 16) {
+ _debug->print("0");
+ }
+
+ _debug->print(b, HEX);
+ }
+
+ _debug->println();
+ _debug->flush();
+ }
+}
+`
diff --git a/devices/ble_hci/supervisor/bluetooth.c b/devices/ble_hci/supervisor/bluetooth.c
new file mode 100644
index 000000000..7a0b77564
--- /dev/null
+++ b/devices/ble_hci/supervisor/bluetooth.c
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Scott Shawcroft for Adafruit Industries
+ *
+ * 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.
+ */
+
+#if CIRCUITPY_BLE_FILE_SERVICE
+#error CIRCUITPY_BLE_FILE_SERVICE not implemented for CIRCUITPY_BLEIO_HCI
+#endif
+
+void supervisor_bluetooth_start_advertising(void) {
+}
+
+void supervisor_start_bluetooth(void) {
+}
+
+FIL active_file;
+volatile bool new_filename;
+volatile bool run_ble_background;
+bool was_connected;
+
+void supervisor_bluetooth_background(void) {
+}
+
+// This happens in an interrupt so we need to be quick.
+bool supervisor_bluetooth_hook(ble_evt_t *ble_evt) {
+ return false;
+}
diff --git a/devices/ble_hci/supervisor/bluetooth.h b/devices/ble_hci/supervisor/bluetooth.h
new file mode 100644
index 000000000..03645829b
--- /dev/null
+++ b/devices/ble_hci/supervisor/bluetooth.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Scott Shawcroft for Adafruit Industries
+ *
+ * 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_DEVICE_BLE_HCI_SUPERVISOR_BLUETOOTH_H
+#define MICROPY_INCLUDED_DEVICE_BLE_HCI_SUPERVISOR_BLUETOOTH_H
+
+void supervisor_start_bluetooth(void);
+bool supervisor_bluetooth_hook(ble_evt_t *ble_evt);
+void supervisor_bluetooth_background(void);
+
+#endif // MICROPY_INCLUDED_DEVICE_BLE_HCI_SUPERVISOR_BLUETOOTH_H