691 lines
24 KiB
C
691 lines
24 KiB
C
/*
|
|
* 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) 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 "py/gc.h"
|
|
#include "py/objstr.h"
|
|
#include "py/runtime.h"
|
|
#include "supervisor/shared/bluetooth/bluetooth.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"
|
|
|
|
#include "controller/ble_ll_adv.h"
|
|
#include "nimble/hci_common.h"
|
|
#include "nimble/nimble_port.h"
|
|
#include "nimble/nimble_port_freertos.h"
|
|
#include "host/ble_gap.h"
|
|
#include "host/util/util.h"
|
|
#include "services/gap/ble_svc_gap.h"
|
|
|
|
#include "common-hal/_bleio/Connection.h"
|
|
|
|
#include "esp_bt.h"
|
|
#include "esp_nimble_hci.h"
|
|
|
|
#if CIRCUITPY_DOTENV
|
|
#include "shared-module/dotenv/__init__.h"
|
|
#endif
|
|
|
|
bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
|
|
|
|
// static void bluetooth_adapter_background(void *data) {
|
|
// supervisor_bluetooth_background();
|
|
// bleio_background();
|
|
// }
|
|
|
|
bool ble_active = false;
|
|
|
|
static void nimble_host_task(void *param) {
|
|
nimble_port_run();
|
|
nimble_port_freertos_deinit();
|
|
}
|
|
|
|
static TaskHandle_t cp_task = NULL;
|
|
|
|
static void _on_sync(void) {
|
|
int rc = ble_hs_util_ensure_addr(0);
|
|
assert(rc == 0);
|
|
|
|
xTaskNotifyGive(cp_task);
|
|
}
|
|
|
|
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) {
|
|
esp_nimble_hci_and_controller_init();
|
|
nimble_port_init();
|
|
// ble_hs_cfg.reset_cb = blecent_on_reset;
|
|
ble_hs_cfg.sync_cb = _on_sync;
|
|
// ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
|
|
|
#if CIRCUITPY_DOTENV
|
|
mp_int_t name_len = 0;
|
|
char ble_name[32];
|
|
name_len = dotenv_get_key("/.env", "CIRCUITPY_BLE_NAME", ble_name, sizeof(ble_name) - 1);
|
|
if (name_len > 0) {
|
|
if (name_len > MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH) {
|
|
name_len = MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH;
|
|
}
|
|
ble_name[name_len] = '\0';
|
|
ble_svc_gap_device_name_set(ble_name);
|
|
} else {
|
|
ble_svc_gap_device_name_set("CIRCUITPY");
|
|
}
|
|
#else
|
|
ble_svc_gap_device_name_set("CIRCUITPY");
|
|
#endif
|
|
|
|
// Clear all of the internal connection objects.
|
|
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
|
|
bleio_connection_internal_t *connection = &bleio_connections[i];
|
|
// Reset connection.
|
|
connection->conn_handle = BLEIO_HANDLE_INVALID;
|
|
}
|
|
|
|
cp_task = xTaskGetCurrentTaskHandle();
|
|
|
|
nimble_port_freertos_init(nimble_host_task);
|
|
// Wait for sync.
|
|
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(200));
|
|
} else {
|
|
nimble_port_stop();
|
|
}
|
|
}
|
|
|
|
bool common_hal_bleio_adapter_get_enabled(bleio_adapter_obj_t *self) {
|
|
return xTaskGetHandle("ble") != NULL;
|
|
}
|
|
|
|
bleio_address_obj_t *common_hal_bleio_adapter_get_address(bleio_adapter_obj_t *self) {
|
|
uint8_t address_bytes[6];
|
|
uint8_t address_type = BLE_ADDR_RANDOM;
|
|
ble_hs_id_infer_auto(0, &address_type);
|
|
int result = ble_hs_id_copy_addr(address_type, address_bytes, NULL);
|
|
if (result != 0) {
|
|
return NULL;
|
|
}
|
|
|
|
bleio_address_obj_t *address = m_new_obj(bleio_address_obj_t);
|
|
address->base.type = &bleio_address_type;
|
|
common_hal_bleio_address_construct(address, address_bytes, BLEIO_ADDRESS_TYPE_RANDOM_STATIC);
|
|
return address;
|
|
}
|
|
|
|
bool common_hal_bleio_adapter_set_address(bleio_adapter_obj_t *self, bleio_address_obj_t *address) {
|
|
if (address->type != BLEIO_ADDRESS_TYPE_RANDOM_STATIC) {
|
|
return false;
|
|
}
|
|
mp_buffer_info_t bufinfo;
|
|
if (!mp_get_buffer(address->bytes, &bufinfo, MP_BUFFER_READ)) {
|
|
return false;
|
|
}
|
|
int result = ble_hs_id_set_rnd(bufinfo.buf);
|
|
return result == 0;
|
|
}
|
|
|
|
mp_obj_str_t *common_hal_bleio_adapter_get_name(bleio_adapter_obj_t *self) {
|
|
const char *name = ble_svc_gap_device_name();
|
|
|
|
return mp_obj_new_str(name, strlen(name));
|
|
}
|
|
|
|
void common_hal_bleio_adapter_set_name(bleio_adapter_obj_t *self, const char *name) {
|
|
ble_svc_gap_device_name_set(name);
|
|
}
|
|
|
|
static int _scan_event(struct ble_gap_event *event, void *scan_results_in) {
|
|
bleio_scanresults_obj_t *scan_results = (bleio_scanresults_obj_t *)scan_results_in;
|
|
|
|
if (event->type == BLE_GAP_EVENT_DISC_COMPLETE) {
|
|
shared_module_bleio_scanresults_set_done(scan_results, true);
|
|
return 0;
|
|
}
|
|
|
|
if (event->type != BLE_GAP_EVENT_DISC && event->type != BLE_GAP_EVENT_EXT_DISC) {
|
|
#if CIRCUITPY_VERBOSE_BLE
|
|
mp_printf(&mp_plat_print, "Unsupported scan event %d\n", event->type);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
if (event->type == BLE_GAP_EVENT_DISC) {
|
|
// Legacy advertisement
|
|
struct ble_gap_disc_desc *disc = &event->disc;
|
|
bool connectable = disc->event_type == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND ||
|
|
disc->event_type == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND;
|
|
shared_module_bleio_scanresults_append(scan_results,
|
|
supervisor_ticks_ms64(),
|
|
connectable,
|
|
disc->event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP,
|
|
disc->rssi,
|
|
disc->addr.val,
|
|
disc->addr.type,
|
|
disc->data,
|
|
disc->length_data);
|
|
} else {
|
|
// Extended advertisement
|
|
struct ble_gap_ext_disc_desc *disc = &event->ext_disc;
|
|
shared_module_bleio_scanresults_append(scan_results,
|
|
supervisor_ticks_ms64(),
|
|
(disc->props & BLE_HCI_ADV_CONN_MASK) != 0,
|
|
(disc->props & BLE_HCI_ADV_SCAN_MASK) != 0,
|
|
disc->rssi,
|
|
disc->addr.val,
|
|
disc->addr.type,
|
|
disc->data,
|
|
disc->length_data);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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_HCI_MAX_EXT_ADV_DATA_LEN : BLE_HCI_MAX_ADV_DATA_LEN;
|
|
|
|
uint8_t own_addr_type;
|
|
struct ble_gap_disc_params disc_params;
|
|
|
|
/* Figure out address to use while advertising (no privacy for now) */
|
|
int privacy = 0;
|
|
CHECK_NIMBLE_ERROR(ble_hs_id_infer_auto(privacy, &own_addr_type));
|
|
|
|
disc_params.filter_duplicates = 0;
|
|
disc_params.passive = !active;
|
|
disc_params.itvl = interval / 0.625;
|
|
disc_params.window = window / 0.625;
|
|
disc_params.filter_policy = 0;
|
|
disc_params.limited = 0;
|
|
|
|
size_t duration_ms = timeout * 1000;
|
|
if (duration_ms == 0) {
|
|
duration_ms = BLE_HS_FOREVER;
|
|
}
|
|
|
|
CHECK_NIMBLE_ERROR(ble_gap_disc(own_addr_type, duration_ms, &disc_params,
|
|
_scan_event, self->scan_results));
|
|
|
|
return MP_OBJ_FROM_PTR(self->scan_results);
|
|
}
|
|
|
|
void common_hal_bleio_adapter_stop_scan(bleio_adapter_obj_t *self) {
|
|
if (self->scan_results == NULL) {
|
|
return;
|
|
}
|
|
ble_gap_disc_cancel();
|
|
shared_module_bleio_scanresults_set_done(self->scan_results, true);
|
|
self->scan_results = NULL;
|
|
}
|
|
|
|
STATIC void _convert_address(const bleio_address_obj_t *address, ble_addr_t *nimble_address) {
|
|
nimble_address->type = address->type;
|
|
mp_buffer_info_t address_buf_info;
|
|
mp_get_buffer_raise(address->bytes, &address_buf_info, MP_BUFFER_READ);
|
|
memcpy(nimble_address->val, (uint8_t *)address_buf_info.buf, NUM_BLEIO_ADDRESS_BYTES);
|
|
}
|
|
|
|
STATIC int _mtu_reply(uint16_t conn_handle,
|
|
const struct ble_gatt_error *error,
|
|
uint16_t mtu, void *arg) {
|
|
bleio_connection_internal_t *connection = (bleio_connection_internal_t *)arg;
|
|
if (conn_handle != connection->conn_handle || error->status != 0) {
|
|
return 0;
|
|
}
|
|
connection->mtu = mtu;
|
|
return 0;
|
|
}
|
|
|
|
STATIC void _new_connection(uint16_t conn_handle) {
|
|
// Set the tx_power for the connection higher than the advertisement.
|
|
esp_ble_tx_power_set(conn_handle, ESP_PWR_LVL_N0);
|
|
|
|
|
|
// 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 == BLEIO_HANDLE_INVALID) {
|
|
break;
|
|
}
|
|
}
|
|
connection->conn_handle = conn_handle;
|
|
connection->connection_obj = mp_const_none;
|
|
connection->pair_status = PAIR_NOT_PAIRED;
|
|
connection->mtu = 0;
|
|
|
|
ble_gattc_exchange_mtu(conn_handle, _mtu_reply, connection);
|
|
|
|
// Change the callback for the connection.
|
|
ble_gap_set_event_cb(conn_handle, bleio_connection_event_cb, connection);
|
|
}
|
|
|
|
static int _connect_event(struct ble_gap_event *event, void *self_in) {
|
|
bleio_adapter_obj_t *self = (bleio_adapter_obj_t *)self_in;
|
|
|
|
#if CIRCUITPY_VERBOSE_BLE
|
|
mp_printf(&mp_plat_print, "Connect event: %d\n", event->type);
|
|
#endif
|
|
switch (event->type) {
|
|
case BLE_GAP_EVENT_CONNECT:
|
|
if (event->connect.status == 0) {
|
|
_new_connection(event->connect.conn_handle);
|
|
// Set connections objs back to NULL since we have a new
|
|
// connection and need a new tuple.
|
|
self->connection_objs = NULL;
|
|
xTaskNotify(cp_task, event->connect.conn_handle, eSetValueWithOverwrite);
|
|
} else {
|
|
xTaskNotify(cp_task, -event->connect.status, eSetValueWithOverwrite);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
#if CIRCUITPY_VERBOSE_BLE
|
|
// For debugging.
|
|
mp_printf(&mp_plat_print, "Unhandled connect event: %d\n", event->type);
|
|
#endif
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self, bleio_address_obj_t *address, mp_float_t timeout) {
|
|
// Stop any active scan.
|
|
if (self->scan_results != NULL) {
|
|
common_hal_bleio_adapter_stop_scan(self);
|
|
}
|
|
|
|
struct ble_gap_conn_params conn_params = {
|
|
.scan_itvl = MSEC_TO_UNITS(100, UNIT_0_625_MS),
|
|
.scan_window = MSEC_TO_UNITS(100, UNIT_0_625_MS),
|
|
.itvl_min = MSEC_TO_UNITS(15, UNIT_1_25_MS),
|
|
.itvl_max = MSEC_TO_UNITS(300, UNIT_1_25_MS),
|
|
.latency = 0,
|
|
.supervision_timeout = MSEC_TO_UNITS(4000, UNIT_10_MS),
|
|
.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
|
|
.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN
|
|
};
|
|
|
|
uint8_t own_addr_type;
|
|
// TODO: Use a resolvable address if the peer has our key.
|
|
CHECK_NIMBLE_ERROR(ble_hs_id_infer_auto(false, &own_addr_type));
|
|
|
|
ble_addr_t addr;
|
|
_convert_address(address, &addr);
|
|
|
|
cp_task = xTaskGetCurrentTaskHandle();
|
|
// Make sure we don't have a pending notification from a previous time. This
|
|
// can happen if a previous wait timed out before the notification was given.
|
|
xTaskNotifyStateClear(cp_task);
|
|
CHECK_NIMBLE_ERROR(
|
|
ble_gap_connect(own_addr_type, &addr,
|
|
SEC_TO_UNITS(timeout, UNIT_1_MS) + 0.5f,
|
|
&conn_params,
|
|
_connect_event, self));
|
|
|
|
int error_code;
|
|
CHECK_NOTIFY(xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200));
|
|
// Negative values are error codes, connection handle otherwise.
|
|
if (error_code < 0) {
|
|
CHECK_BLE_ERROR(-error_code);
|
|
}
|
|
uint16_t conn_handle = error_code;
|
|
|
|
// TODO: If we have keys, then try and encrypt the connection.
|
|
|
|
// TODO: Negotiate for better PHY and data lengths since we are the central. These are
|
|
// nice-to-haves so ignore any errors.
|
|
|
|
// Make the connection object and return it.
|
|
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
|
|
bleio_connection_internal_t *connection = &bleio_connections[i];
|
|
if (connection->conn_handle == conn_handle) {
|
|
connection->is_central = true;
|
|
return bleio_connection_new_from_internal(connection);
|
|
}
|
|
}
|
|
|
|
mp_raise_bleio_BluetoothError(translate("Failed to connect: internal error"));
|
|
|
|
return mp_const_none;
|
|
}
|
|
|
|
typedef struct {
|
|
struct os_mbuf mbuf;
|
|
struct os_mbuf_pkthdr hdr;
|
|
} os_mbuf_t;
|
|
|
|
STATIC void _wrap_in_mbuf(const uint8_t *data, uint16_t len, os_mbuf_t *buf) {
|
|
struct os_mbuf *mbuf = &buf->mbuf;
|
|
mbuf->om_data = (uint8_t *)data,
|
|
mbuf->om_flags = 0;
|
|
mbuf->om_pkthdr_len = 0;
|
|
mbuf->om_len = len;
|
|
mbuf->om_next.sle_next = NULL;
|
|
|
|
buf->hdr.omp_len = len;
|
|
|
|
// Setting the pool to NULL will cause frees to fail. Hopefully that failure
|
|
// is ignored.
|
|
mbuf->om_omp = NULL;
|
|
}
|
|
|
|
static int _advertising_event(struct ble_gap_event *event, void *self_in) {
|
|
bleio_adapter_obj_t *self = (bleio_adapter_obj_t *)self_in;
|
|
|
|
#if CIRCUITPY_VERBOSE_BLE
|
|
mp_printf(&mp_plat_print, "Advertising event: %d\n", event->type);
|
|
#endif
|
|
switch (event->type) {
|
|
case BLE_GAP_EVENT_CONNECT:
|
|
// Spurious connect events can happen.
|
|
break;
|
|
|
|
case BLE_GAP_EVENT_ADV_COMPLETE:
|
|
if (event->adv_complete.reason == NIMBLE_OK) {
|
|
_new_connection(event->adv_complete.conn_handle);
|
|
// Set connections objs back to NULL since we have a new
|
|
// connection and need a new tuple.
|
|
self->connection_objs = NULL;
|
|
}
|
|
// Other statuses indicate timeout or preemption.
|
|
common_hal_bleio_adapter_stop_advertising(self);
|
|
break;
|
|
|
|
default:
|
|
#if CIRCUITPY_VERBOSE_BLE
|
|
// For debugging.
|
|
mp_printf(&mp_plat_print, "Unhandled advertising event: %d\n", event->type);
|
|
#endif
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self,
|
|
bool connectable, bool anonymous, uint32_t timeout, float interval,
|
|
const uint8_t *advertising_data, uint16_t advertising_data_len,
|
|
const uint8_t *scan_response_data, uint16_t scan_response_data_len,
|
|
mp_int_t tx_power, const bleio_address_obj_t *directed_to) {
|
|
|
|
if (ble_gap_adv_active() && !self->user_advertising) {
|
|
return BLE_HS_EBUSY;
|
|
}
|
|
|
|
uint32_t rc;
|
|
bool extended = advertising_data_len > BLE_ADV_LEGACY_DATA_MAX_LEN ||
|
|
scan_response_data_len > BLE_ADV_LEGACY_DATA_MAX_LEN;
|
|
|
|
ble_addr_t peer;
|
|
if (directed_to != NULL) {
|
|
_convert_address(directed_to, &peer);
|
|
}
|
|
|
|
uint8_t own_addr_type;
|
|
// Anonymous addresses are still resolvable. (Following what the NRF
|
|
// implementation does.)
|
|
rc = ble_hs_id_infer_auto(anonymous, &own_addr_type);
|
|
if (rc != NIMBLE_OK) {
|
|
return rc;
|
|
}
|
|
|
|
bool high_duty_directed = directed_to != NULL && interval <= 3.5 && timeout <= 1.3;
|
|
|
|
struct ble_gap_ext_adv_params adv_params = {
|
|
.connectable = connectable,
|
|
.scannable = scan_response_data_len > 0,
|
|
.directed = directed_to != NULL,
|
|
.high_duty_directed = high_duty_directed,
|
|
.legacy_pdu = !extended,
|
|
.anonymous = anonymous,
|
|
.include_tx_power = extended,
|
|
.scan_req_notif = false,
|
|
.itvl_min = SEC_TO_UNITS(interval, UNIT_0_625_MS) + 0.5f,
|
|
.itvl_max = SEC_TO_UNITS(interval, UNIT_0_625_MS) + 0.5f,
|
|
.channel_map = 0,
|
|
.own_addr_type = own_addr_type,
|
|
.peer = peer,
|
|
.filter_policy = BLE_HCI_CONN_FILT_NO_WL,
|
|
.primary_phy = BLE_HCI_LE_PHY_1M,
|
|
.secondary_phy = BLE_HCI_LE_PHY_1M,
|
|
.tx_power = tx_power,
|
|
.sid = 0,
|
|
};
|
|
|
|
// Configure must come before setting payloads.
|
|
rc = ble_gap_ext_adv_configure(0,
|
|
&adv_params,
|
|
NULL,
|
|
_advertising_event, self);
|
|
if (rc != NIMBLE_OK) {
|
|
return rc;
|
|
}
|
|
|
|
os_mbuf_t buf;
|
|
_wrap_in_mbuf(advertising_data, advertising_data_len, &buf);
|
|
// This copies the advertising data into buffers to send to the controller.
|
|
// Therefore, we don't need to worry about the lifetime of our copy.
|
|
rc = ble_gap_ext_adv_set_data(0, &buf.mbuf);
|
|
if (rc != NIMBLE_OK) {
|
|
return rc;
|
|
}
|
|
|
|
if (scan_response_data_len > 0) {
|
|
_wrap_in_mbuf(scan_response_data, scan_response_data_len, &buf);
|
|
// This copies the advertising data into buffers to send to the controller.
|
|
// Therefore, we don't need to worry about the lifetime of our copy.
|
|
rc = ble_gap_ext_adv_rsp_set_data(0, &buf.mbuf);
|
|
if (rc != NIMBLE_OK) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = ble_gap_ext_adv_start(0, timeout / 10, 0);
|
|
if (rc != NIMBLE_OK) {
|
|
return rc;
|
|
}
|
|
|
|
return NIMBLE_OK;
|
|
}
|
|
|
|
STATIC void check_data_fit(size_t data_len, bool connectable) {
|
|
if (data_len > MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE) ||
|
|
(connectable && data_len > MYNEWT_VAL(BLE_EXT_ADV_MAX_SIZE))) {
|
|
mp_raise_ValueError(translate("Data too large for advertisement packet"));
|
|
}
|
|
}
|
|
|
|
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,
|
|
mp_int_t tx_power, const bleio_address_obj_t *directed_to) {
|
|
if (self->user_advertising) {
|
|
mp_raise_bleio_BluetoothError(translate("Already advertising."));
|
|
} else {
|
|
// If the user isn't advertising, then the background is. So, stop the
|
|
// background advertising so the user can.
|
|
common_hal_bleio_adapter_stop_advertising(self);
|
|
}
|
|
// 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."));
|
|
}
|
|
|
|
|
|
if (advertising_data_bufinfo->len > 0 && directed_to != NULL) {
|
|
mp_raise_bleio_BluetoothError(translate("Data not supported with directed advertising"));
|
|
}
|
|
|
|
if (anonymous) {
|
|
mp_raise_NotImplementedError(NULL);
|
|
}
|
|
|
|
if (!timeout) {
|
|
timeout = BLE_HS_FOREVER;
|
|
} else if (timeout > INT32_MAX) {
|
|
mp_raise_bleio_BluetoothError(translate("Timeout is too long: Maximum timeout length is %d seconds"),
|
|
INT32_MAX / 1000);
|
|
}
|
|
|
|
CHECK_NIMBLE_ERROR(_common_hal_bleio_adapter_start_advertising(self, connectable, anonymous, timeout, interval,
|
|
advertising_data_bufinfo->buf,
|
|
advertising_data_bufinfo->len,
|
|
scan_response_data_bufinfo->buf,
|
|
scan_response_data_bufinfo->len,
|
|
tx_power,
|
|
directed_to));
|
|
self->user_advertising = true;
|
|
}
|
|
|
|
void common_hal_bleio_adapter_stop_advertising(bleio_adapter_obj_t *self) {
|
|
if (!common_hal_bleio_adapter_get_advertising(self)) {
|
|
return;
|
|
}
|
|
int err_code = ble_gap_ext_adv_stop(0);
|
|
self->user_advertising = false;
|
|
|
|
if ((err_code != NIMBLE_OK) &&
|
|
(err_code != BLE_HS_EALREADY) &&
|
|
(err_code != BLE_HS_EINVAL)) {
|
|
CHECK_NIMBLE_ERROR(err_code);
|
|
}
|
|
}
|
|
|
|
bool common_hal_bleio_adapter_get_advertising(bleio_adapter_obj_t *self) {
|
|
return ble_gap_adv_active();
|
|
}
|
|
|
|
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 != BLEIO_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 != BLEIO_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) {
|
|
mp_raise_NotImplementedError(NULL);
|
|
// bonding_erase_storage();
|
|
}
|
|
|
|
bool common_hal_bleio_adapter_is_bonded_to_central(bleio_adapter_obj_t *self) {
|
|
mp_raise_NotImplementedError(NULL);
|
|
// return bonding_peripheral_bond_count() > 0;
|
|
return false;
|
|
}
|
|
|
|
void bleio_adapter_gc_collect(bleio_adapter_obj_t *adapter) {
|
|
// We divide by size_t so that we can scan each 32-bit aligned value to see
|
|
// if it is a pointer. This allows us to change the structs without worrying
|
|
// about collecting new pointers.
|
|
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 (common_hal_bleio_adapter_get_advertising(adapter)) {
|
|
common_hal_bleio_adapter_stop_advertising(adapter);
|
|
}
|
|
|
|
adapter->connection_objs = NULL;
|
|
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
|
|
bleio_connection_internal_t *connection = &bleio_connections[i];
|
|
// Disconnect all connections cleanly.
|
|
if (connection->conn_handle != BLEIO_HANDLE_INVALID) {
|
|
common_hal_bleio_connection_disconnect(connection);
|
|
}
|
|
connection->connection_obj = mp_const_none;
|
|
}
|
|
|
|
// Wait up to 125 ms (128 ticks) for disconnect to complete. This should be
|
|
// greater than most connection intervals.
|
|
bool any_connected = false;
|
|
uint64_t start_ticks = supervisor_ticks_ms64();
|
|
while (any_connected && supervisor_ticks_ms64() - start_ticks < 128) {
|
|
any_connected = false;
|
|
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
|
|
bleio_connection_internal_t *connection = &bleio_connections[i];
|
|
any_connected |= connection->conn_handle != BLEIO_HANDLE_INVALID;
|
|
}
|
|
}
|
|
}
|