circuitpython/ports/silabs/common-hal/_bleio/Adapter.c

652 lines
21 KiB
C

/*
* This file is part of Adafruit for EFR32 project
*
* The MIT License (MIT)
*
* Copyright 2023 Silicon Laboratories Inc. www.silabs.com
*
* 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 "shared/runtime/interrupt_char.h"
#include "shared-bindings/_bleio/Address.h"
#include "shared-bindings/_bleio/Connection.h"
#include "shared-bindings/_bleio/ScanEntry.h"
#include "shared-bindings/_bleio/__init__.h"
#include "shared-bindings/time/__init__.h"
#include "common-hal/_bleio/Connection.h"
#include "supervisor/shared/tick.h"
#define PUBLIC_ADDRESS 0
#define STATIC_ADDRESS 1
#define BLE_EXT_ADV_MAX_SIZE 32
#define UNIT_0_625_MS (625)
#define UNIT_1_25_MS (1250)
#define UNIT_10_MS (10000)
#define SCAN_TIMEOUT_MS_DEFAUT 3000
#define BLE_GAP_SCAN_BUFFER_EXTENDED_MAX_SUPPORTED 256
#define BLE_GAP_SCAN_BUFFER_MAX 32
#define DEVICE_NAME_LEN 15
EventGroupHandle_t xscan_event;
device_scan_info_t scan_info;
bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
// Set scan data form sl_bt_on_event
void set_scan_device_info_on_ble_evt(bd_addr address,
uint8_t address_type,
int8_t rssi,
uint8array *data) {
scan_info.address = address;
scan_info.address_type = address_type;
scan_info.rssi = rssi;
memcpy(scan_info.data, data->data, data->len);
}
// Get state of the BLE adapter.
bool common_hal_bleio_adapter_get_enabled(bleio_adapter_obj_t *self) {
return self->is_enable;
}
// Set state of the BLE adapter
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);
bd_addr get_address;
uint8_t address_type;
uint8_t conn_index;
bleio_connection_internal_t *connection;
sl_status_t sc = SL_STATUS_FAIL;
uint8_t device_name[DEVICE_NAME_LEN + 1];
memset(device_name, 0, DEVICE_NAME_LEN);
// Don't enable or disable twice
if (is_enabled == enabled) {
return;
}
sc = sl_bt_system_get_identity_address(&get_address, &address_type);
if (SL_STATUS_OK != sc) {
mp_raise_bleio_BluetoothError(translate("Get address fail."));
}
snprintf((char *)device_name, DEVICE_NAME_LEN + 1,
"CIRCUITPY-%X%X", get_address.addr[1], get_address.addr[0]);
if (enabled) {
sl_bt_gatt_server_write_attribute_value(gattdb_device_name,
0,
DEVICE_NAME_LEN,
device_name);
// Clear all of the internal connection objects.
for (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
// Reset connection.
connection->conn_handle = BLEIO_HANDLE_INVALID;
}
} else {
self->is_enable = false;
}
}
// Get mac address of the BLE adapter
bleio_address_obj_t *common_hal_bleio_adapter_get_address(bleio_adapter_obj_t *self) {
bd_addr get_address;
uint8_t address_type;
sl_status_t sc = SL_STATUS_FAIL;
bleio_address_obj_t *address;
sc = sl_bt_system_get_identity_address(&get_address, &address_type);
if (SL_STATUS_OK != sc) {
return NULL;
}
address = m_new_obj(bleio_address_obj_t);
address->base.type = &bleio_address_type;
common_hal_bleio_address_construct(address, get_address.addr,
BLEIO_ADDRESS_TYPE_RANDOM_STATIC);
return address;
}
// Set identity address
bool common_hal_bleio_adapter_set_address(bleio_adapter_obj_t *self,
bleio_address_obj_t *address) {
sl_status_t sc = SL_STATUS_FAIL;
mp_buffer_info_t bufinfo;
bd_addr ble_addr;
if (NULL == address) {
return false;
}
if (!mp_get_buffer(address->bytes, &bufinfo, MP_BUFFER_READ)) {
return false;
}
memcpy(ble_addr.addr, bufinfo.buf, 6);
sl_bt_system_set_identity_address(ble_addr, PUBLIC_ADDRESS);
return sc == SL_STATUS_OK;
}
// Get name of the BLE adapter
mp_obj_str_t *common_hal_bleio_adapter_get_name(bleio_adapter_obj_t *self) {
char name[DEVICE_NAME_LEN];
size_t value_len = 0;
uint8_t sc;
memset(name, 0, DEVICE_NAME_LEN);
sc = sl_bt_gatt_server_read_attribute_value(gattdb_device_name,
0,
DEVICE_NAME_LEN,
&value_len,
(uint8_t *)name);
if (SL_STATUS_OK != sc) {
return NULL;
}
return mp_obj_new_str(name, strlen(name));
}
// Set name of the BLE adapter
void common_hal_bleio_adapter_set_name(bleio_adapter_obj_t *self,
const char *name) {
sl_bt_gatt_server_write_attribute_value(gattdb_device_name,
0,
DEVICE_NAME_LEN,
(const uint8_t *)name);
}
// starts a BLE scan
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) {
sl_status_t sc;
uint64_t start_ticks = supervisor_ticks_ms64();
uint64_t current_ticks = start_ticks;
uint32_t timeout_ms = timeout * 1000;
if (timeout_ms == 0) {
timeout_ms = SCAN_TIMEOUT_MS_DEFAUT;
}
if (self->scan_results != NULL) {
if (!shared_module_bleio_scanresults_get_done(self->scan_results)) {
mp_raise_bleio_BluetoothError(
translate("Scan already in progress. Stop with stop_scan."));
}
self->scan_results = NULL;
}
sl_bt_scanner_stop();
self->scan_results = shared_module_bleio_new_scanresults(buffer_size,
prefixes,
prefix_length,
minimum_rssi);
xscan_event = xEventGroupCreate();
if (xscan_event != NULL) {
xEventGroupClearBits(xscan_event, 1 << 0);
}
sc = sl_bt_scanner_start(sl_bt_scanner_scan_phy_1m,
sl_bt_scanner_discover_generic);
if (SL_STATUS_OK != sc) {
self->scan_results = NULL;
}
// Busy-wait until timeout or until we've read enough chars.
while (current_ticks - start_ticks <= timeout_ms) {
if (xscan_event != NULL) {
xEventGroupWaitBits(xscan_event, 1 << 0, pdTRUE, pdFALSE,
timeout_ms / portTICK_PERIOD_MS);
}
self->scan_results->prefix_length = 0;
scan_info.data_len = 28;
shared_module_bleio_scanresults_append(
self->scan_results,
supervisor_ticks_ms64(),
true,
true,
scan_info.rssi,
scan_info.address.addr,
scan_info.address_type,
scan_info.data,
scan_info.data_len);
current_ticks = supervisor_ticks_ms64();
// Allow user to break out of a timeout with a KeyboardInterrupt.
if (mp_hal_is_interrupted()) {
break;
}
}
shared_module_bleio_scanresults_set_done(self->scan_results, true);
vEventGroupDelete(xscan_event);
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;
}
sl_bt_scanner_stop();
shared_module_bleio_scanresults_set_done(self->scan_results, true);
self->scan_results = NULL;
}
// Start the advertising on an advertising set with specified
// discovery and connection modes
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) {
sl_status_t sc = SL_STATUS_FAIL;
int16_t power = tx_power * 10; // TX power in 0.1 dBm steps
int16_t set_power;
uint32_t interval_min = (uint32_t)(interval * 1600); // (milliseconds * 1.6)
uint32_t interval_max = (uint32_t)(interval * 1600); // (milliseconds * 1.6)
bd_addr address;
uint8_t address_type;
uint8_t system_id[8];
uint8_t enable_connect;
if (self->user_advertising) {
return SL_STATUS_BUSY;
}
sc = sl_bt_advertiser_create_set(&self->advertising_handle);
if (SL_STATUS_OK != sc) {
mp_raise_bleio_BluetoothError(translate("Create_set fail."));
return sc;
}
sc = sl_bt_advertiser_set_channel_map(self->advertising_handle, 7);
if (SL_STATUS_OK != sc) {
return sc;
}
sc = sl_bt_system_get_identity_address(&address, &address_type);
if (SL_STATUS_OK != sc) {
mp_raise_bleio_BluetoothError(translate("Get address fail."));
return sc;
}
// Pad and reverse unique ID to get System ID.
system_id[0] = address.addr[5];
system_id[1] = address.addr[4];
system_id[2] = address.addr[3];
system_id[3] = 0xFF;
system_id[4] = 0xFE;
system_id[5] = address.addr[2];
system_id[6] = address.addr[1];
system_id[7] = address.addr[0];
sc = sl_bt_gatt_server_write_attribute_value(gattdb_system_id,
0,
sizeof(system_id),
system_id);
if (SL_STATUS_OK != sc) {
return sc;
}
sc = sl_bt_advertiser_set_tx_power(self->advertising_handle,
power,
&set_power);
if (SL_STATUS_OK != sc) {
return sc;
}
// Set advertising interval.
sc = sl_bt_advertiser_set_timing(
self->advertising_handle,
interval_min, // min. adv. interval (milliseconds * 1.6)
interval_max, // max. adv. interval (milliseconds * 1.6)
0, // adv. duration
0); // max. num. adv. events
if (SL_STATUS_OK != sc) {
mp_raise_bleio_BluetoothError(translate("Set_timing fail."));
return sc;
}
sc = sl_bt_legacy_advertiser_set_data(
self->advertising_handle,
sl_bt_advertiser_advertising_data_packet,
advertising_data_len,
advertising_data);
if (SL_STATUS_OK != sc) {
mp_raise_bleio_BluetoothError(translate("Set data fail."));
return sc;
}
// Start advertising and enable connections.
enable_connect = sl_bt_legacy_advertiser_scannable;
if (true == connectable) {
enable_connect = sl_bt_advertiser_connectable_scannable;
}
sc = sl_bt_legacy_advertiser_start(self->advertising_handle,
enable_connect);
if (SL_STATUS_OK != sc) {
mp_raise_bleio_BluetoothError(translate("Start advertise fail."));
} else {
self->user_advertising = true;
}
return sc;
}
// Check size of packet advertising to send
STATIC void check_data_fit(size_t data_len, bool connectable) {
if (data_len > BLE_EXT_ADV_MAX_SIZE ||
(connectable && data_len > BLE_EXT_ADV_MAX_SIZE)) {
mp_raise_ValueError(
translate("Data too large for advertisement packet"));
}
}
// Start advertising
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) {
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 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 = INT32_MAX;
} else if (timeout > INT32_MAX) {
mp_raise_bleio_BluetoothError(
translate("Maximum timeout length is %d seconds"), INT32_MAX / 1000);
}
_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);
}
// Stop advertising
void common_hal_bleio_adapter_stop_advertising(bleio_adapter_obj_t *self) {
if (!common_hal_bleio_adapter_get_advertising(self)) {
return;
}
sl_bt_advertiser_delete_set(self->advertising_handle);
sl_bt_advertiser_stop(0);
self->user_advertising = false;
}
// Get status of advertising
bool common_hal_bleio_adapter_get_advertising(bleio_adapter_obj_t *self) {
return self->user_advertising;
}
// Convert mac address of remote device to connect
STATIC void _convert_address(const bleio_address_obj_t *address,
bd_addr *sd_address, uint8_t *addr_type) {
mp_buffer_info_t address_buf_info;
*addr_type = address->type;
mp_get_buffer_raise(address->bytes, &address_buf_info, MP_BUFFER_READ);
memcpy(sd_address->addr, (uint8_t *)address_buf_info.buf, 6);
}
// Add new connection into connection list
void _new_connection(uint16_t conn_handle) {
// Find an empty connection. One must always be available because the SD has the same
// total connection limit.
bleio_connection_internal_t *connection;
uint8_t conn_index;
for (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
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;
}
// Attempts a connection to the device with the given address.
mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self,
bleio_address_obj_t *address,
mp_float_t timeout) {
bd_addr addr;
uint8_t address_type;
sl_status_t error_code;
uint8_t conn_handle;
uint8_t conn_index;
bleio_connection_internal_t *connection;
if (self->scan_results != NULL) {
common_hal_bleio_adapter_stop_scan(self);
}
conn_handle = common_hal_bleio_adapter_get_connected(self);
timeout = MSEC_TO_UNITS(timeout, UNIT_10_MS);
_convert_address(address, &addr, &address_type);
error_code = sl_bt_connection_open(addr,
address_type,
sl_bt_gap_phy_1m,
&conn_handle);
// Negative values are error codes, connection handle otherwise.
if (SL_STATUS_OK != error_code) {
return mp_const_none;
}
_new_connection(conn_handle);
// 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 (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
if (connection->conn_handle == conn_handle) {
connection->is_central = true;
connection->pair_status = PAIR_PAIRED;
return bleio_connection_new_from_internal(connection);
}
}
mp_raise_bleio_BluetoothError(
translate("Failed to connect: internal error"));
return mp_const_none;
}
// Get connected status
bool common_hal_bleio_adapter_get_connected(bleio_adapter_obj_t *self) {
uint8_t conn_index;
bleio_connection_internal_t *connection;
for (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
if (connection->conn_handle != BLEIO_HANDLE_INVALID) {
return true;
}
}
return false;
}
// Get connection object
mp_obj_t common_hal_bleio_adapter_get_connections(bleio_adapter_obj_t *self) {
size_t total_connected = 0;
mp_obj_t items[BLEIO_TOTAL_CONNECTION_COUNT];
uint8_t conn_index;
bleio_connection_internal_t *connection;
if (self->connection_objs != NULL) {
return self->connection_objs;
}
for (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
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_remove_connection(uint8_t conn_handle) {
uint8_t conn_index;
bleio_connection_internal_t *connection;
osMutexAcquire(bluetooth_connection_mutex_id, osWaitForever);
for (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
if (conn_handle == connection->conn_handle) {
connection->connection_obj = NULL;
connection->conn_handle = BLEIO_HANDLE_INVALID;
}
}
osMutexRelease(bluetooth_connection_mutex_id);
}
// Delete all bonding
void common_hal_bleio_adapter_erase_bonding(bleio_adapter_obj_t *self) {
sl_status_t sc;
sc = sl_bt_sm_delete_bondings();
if (SL_STATUS_OK != sc) {
mp_raise_bleio_BluetoothError(translate("All bonding deleted fail."));
}
}
// Get status bonding to central
bool common_hal_bleio_adapter_is_bonded_to_central(bleio_adapter_obj_t *self) {
bleio_connection_internal_t *connection;
uint8_t conn_index;
for (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
if (connection->conn_handle != BLEIO_HANDLE_INVALID) {
return true;
}
}
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)));
}
// Reset the BLE adapter
void bleio_adapter_reset(bleio_adapter_obj_t *adapter) {
bool any_connected = false;
uint64_t start_ticks;
uint8_t conn_index;
bleio_connection_internal_t *connection;
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 (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
// 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.
start_ticks = supervisor_ticks_ms64();
while (any_connected && supervisor_ticks_ms64() - start_ticks < 128) {
any_connected = false;
for (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) {
connection = &bleio_connections[conn_index];
any_connected |= connection->conn_handle != BLEIO_HANDLE_INVALID;
}
}
}