Merge pull request #5936 from tannewt/esp_ble_advertise

Support BLE advertising on ESP
This commit is contained in:
Dan Halbert 2022-01-27 22:33:35 -05:00 committed by GitHub
commit 14ed33c99d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 349 additions and 28 deletions

View File

@ -446,6 +446,7 @@ msgstr ""
msgid "All timers in use"
msgstr ""
#: ports/espressif/common-hal/_bleio/Adapter.c
#: ports/nrf/common-hal/_bleio/Adapter.c
msgid "Already advertising."
msgstr ""
@ -908,10 +909,12 @@ msgstr ""
msgid "Data chunk must follow fmt chunk"
msgstr ""
#: ports/espressif/common-hal/_bleio/Adapter.c
#: ports/nrf/common-hal/_bleio/Adapter.c
msgid "Data not supported with directed advertising"
msgstr ""
#: ports/espressif/common-hal/_bleio/Adapter.c
#: ports/nrf/common-hal/_bleio/Adapter.c
msgid "Data too large for advertisement packet"
msgstr ""
@ -992,6 +995,7 @@ msgstr ""
msgid "Expected tuple of length %d, got %d"
msgstr ""
#: ports/espressif/common-hal/_bleio/Adapter.c
#: ports/nrf/common-hal/_bleio/Adapter.c
msgid "Extended advertisements with scan response not supported."
msgstr ""
@ -1045,6 +1049,7 @@ msgstr ""
msgid "Failed to buffer the sample"
msgstr ""
#: ports/espressif/common-hal/_bleio/Adapter.c
#: ports/nrf/common-hal/_bleio/Adapter.c
msgid "Failed to connect: internal error"
msgstr ""
@ -1276,6 +1281,7 @@ msgstr ""
msgid "Invalid AuthMode"
msgstr ""
#: ports/espressif/common-hal/_bleio/__init__.c
#: ports/nrf/common-hal/_bleio/__init__.c
msgid "Invalid BLE parameter"
msgstr ""
@ -1584,6 +1590,10 @@ msgstr ""
msgid "Name too long"
msgstr ""
#: ports/espressif/common-hal/_bleio/__init__.c
msgid "Nimble out of memory"
msgstr ""
#: ports/nrf/common-hal/_bleio/Characteristic.c
msgid "No CCCD for this Characteristic"
msgstr ""
@ -1731,6 +1741,7 @@ msgstr ""
msgid "Not a valid IP string"
msgstr ""
#: ports/espressif/common-hal/_bleio/__init__.c
#: ports/nrf/common-hal/_bleio/__init__.c
#: shared-bindings/_bleio/CharacteristicBuffer.c
msgid "Not connected"
@ -2110,6 +2121,7 @@ msgstr ""
msgid "Scale dimensions must divide by 3"
msgstr ""
#: ports/espressif/common-hal/_bleio/Adapter.c
#: ports/nrf/common-hal/_bleio/Adapter.c
msgid "Scan already in progess. Stop with stop_scan."
msgstr ""
@ -2268,6 +2280,7 @@ msgstr ""
msgid "Time is in the past."
msgstr ""
#: ports/espressif/common-hal/_bleio/Adapter.c
#: ports/nrf/common-hal/_bleio/Adapter.c
#, c-format
msgid "Timeout is too long: Maximum timeout length is %d seconds"
@ -2290,6 +2303,7 @@ msgstr ""
msgid "Too many displays"
msgstr ""
#: ports/espressif/common-hal/_bleio/PacketBuffer.c
#: ports/nrf/common-hal/_bleio/PacketBuffer.c
msgid "Total data to write is larger than %q"
msgstr ""
@ -2421,11 +2435,21 @@ msgstr ""
msgid "Unknown security error: 0x%04x"
msgstr ""
#: ports/espressif/common-hal/_bleio/__init__.c
#, c-format
msgid "Unknown system firmware error at %s:%d: %d"
msgstr ""
#: ports/nrf/common-hal/_bleio/__init__.c
#, c-format
msgid "Unknown system firmware error: %04x"
msgstr ""
#: ports/espressif/common-hal/_bleio/__init__.c
#, c-format
msgid "Unknown system firmware error: %d"
msgstr ""
#: shared-bindings/adafruit_pixelbuf/PixelBuf.c
#, c-format
msgid "Unmatched number of items on RHS (expected %d, got %d)."
@ -2463,11 +2487,13 @@ msgstr ""
msgid "Update Failed"
msgstr ""
#: ports/espressif/common-hal/_bleio/Descriptor.c
#: ports/nrf/common-hal/_bleio/Characteristic.c
#: ports/nrf/common-hal/_bleio/Descriptor.c
msgid "Value length != required fixed length"
msgstr ""
#: ports/espressif/common-hal/_bleio/Descriptor.c
#: ports/nrf/common-hal/_bleio/Characteristic.c
#: ports/nrf/common-hal/_bleio/Descriptor.c
msgid "Value length > max_length"
@ -2525,6 +2551,7 @@ msgstr ""
msgid "Woken up by alarm.\n"
msgstr ""
#: ports/espressif/common-hal/_bleio/PacketBuffer.c
#: ports/nrf/common-hal/_bleio/PacketBuffer.c
msgid "Writes not supported on Characteristic"
msgstr ""
@ -3592,6 +3619,7 @@ msgstr ""
msgid "matrix is not positive definite"
msgstr ""
#: ports/espressif/common-hal/_bleio/Descriptor.c
#: ports/nrf/common-hal/_bleio/Characteristic.c
#: ports/nrf/common-hal/_bleio/Descriptor.c
#, c-format

View File

@ -86,7 +86,9 @@ INC += \
-isystem esp-idf \
-isystem esp-idf/components/app_update/include \
-isystem esp-idf/components/bootloader_support/include \
-isystem esp-idf/components/bt/include/$(IDF_TARGET)/include \
-isystem esp-idf/components/bt/host/nimble/esp-hci/include \
-isystem esp-idf/components/bt/host/nimble/nimble/nimble/controller/include \
-isystem esp-idf/components/bt/host/nimble/nimble/nimble/host/include \
-isystem esp-idf/components/bt/host/nimble/nimble/nimble/host/services/gap/include \
-isystem esp-idf/components/bt/host/nimble/nimble/nimble/include \

View File

@ -46,6 +46,7 @@
#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"
@ -53,12 +54,11 @@
#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"
#include "esp_log.h"
const char *TAG = "BLE adapter";
bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
// static void bluetooth_adapter_background(void *data) {
@ -98,6 +98,13 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable
// ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_svc_gap_device_name_set("CIRCUITPY");
// 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);
@ -158,7 +165,9 @@ static int _scan_event(struct ble_gap_event *event, void *scan_results_in) {
}
if (event->type != BLE_GAP_EVENT_DISC && event->type != BLE_GAP_EVENT_EXT_DISC) {
ESP_LOGI(TAG, "unsupported scan event %d", event->type);
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "Unsupported scan event %d\n", event->type);
#endif
return 0;
}
@ -208,16 +217,10 @@ mp_obj_t common_hal_bleio_adapter_start_scan(bleio_adapter_obj_t *self, uint8_t
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
int rc;
/* Figure out address to use while advertising (no privacy for now) */
int privacy = 0;
rc = ble_hs_id_infer_auto(privacy, &own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "id error");
// Error. TODO: Make function to translate into exceptions.
}
CHECK_NIMBLE_ERROR(ble_hs_id_infer_auto(privacy, &own_addr_type));
disc_params.filter_duplicates = 0;
disc_params.passive = !active;
@ -226,11 +229,8 @@ mp_obj_t common_hal_bleio_adapter_start_scan(bleio_adapter_obj_t *self, uint8_t
disc_params.filter_policy = 0;
disc_params.limited = 0;
rc = ble_gap_disc(own_addr_type, timeout * 1000, &disc_params,
_scan_event, self->scan_results);
if (rc != 0) {
ESP_LOGE(TAG, "scan error %d", rc);
}
CHECK_NIMBLE_ERROR(ble_gap_disc(own_addr_type, timeout * 1000, &disc_params,
_scan_event, self->scan_results));
return MP_OBJ_FROM_PTR(self->scan_results);
}
@ -244,6 +244,36 @@ void common_hal_bleio_adapter_stop_scan(bleio_adapter_obj_t *self) {
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 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;
// Change the callback for the connection.
ble_gap_set_event_cb(conn_handle, bleio_connection_event_cb, connection);
}
mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self, bleio_address_obj_t *address, mp_float_t timeout) {
mp_raise_NotImplementedError(NULL);
@ -252,29 +282,210 @@ mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self, bleio_addre
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) {
mp_raise_NotImplementedError(NULL);
return -1;
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) {
mp_raise_NotImplementedError(NULL);
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) {
mp_raise_NotImplementedError(NULL);
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 self->current_advertising_data != NULL;
return ble_gap_adv_active();
}
bool common_hal_bleio_adapter_get_connected(bleio_adapter_obj_t *self) {
@ -328,7 +539,7 @@ void bleio_adapter_gc_collect(bleio_adapter_obj_t *adapter) {
void bleio_adapter_reset(bleio_adapter_obj_t *adapter) {
common_hal_bleio_adapter_stop_scan(adapter);
if (adapter->current_advertising_data != NULL) {
if (common_hal_bleio_adapter_get_advertising(adapter)) {
common_hal_bleio_adapter_stop_advertising(adapter);
}

View File

@ -47,11 +47,6 @@ extern bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUN
typedef struct {
mp_obj_base_t base;
// Pointer to buffers we maintain so that the data is long lived.
uint8_t *advertising_data;
uint8_t *scan_response_data;
// Pointer to current data.
const uint8_t *current_advertising_data;
bleio_scanresults_obj_t *scan_results;
mp_obj_t name;
mp_obj_tuple_t *connection_objs;

View File

@ -48,6 +48,38 @@
#include "host/ble_att.h"
int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) {
bleio_connection_internal_t *connection = (bleio_connection_internal_t *)connection_in;
switch (event->type) {
case BLE_GAP_EVENT_DISCONNECT: {
connection->conn_handle = BLEIO_HANDLE_INVALID;
connection->pair_status = PAIR_NOT_PAIRED;
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "disconnected %02x\n", event->disconnect.reason);
#endif
if (connection->connection_obj != mp_const_none) {
bleio_connection_obj_t *obj = connection->connection_obj;
obj->connection = NULL;
obj->disconnect_reason = event->disconnect.reason;
}
break;
}
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: {
break;
}
default:
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "Unhandled connection event: %d\n", event->type);
#endif
return 0;
}
return 0;
}
bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) {
if (self->connection == NULL) {

View File

@ -38,6 +38,8 @@
#include "shared-module/_bleio/Address.h"
#include "common-hal/_bleio/Service.h"
#include "host/ble_gap.h"
typedef enum {
PAIR_NOT_PAIRED,
PAIR_WAITING,
@ -81,6 +83,8 @@ typedef struct {
void bleio_connection_clear(bleio_connection_internal_t *self);
int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_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);

View File

@ -42,6 +42,8 @@ void common_hal_bleio_uuid_construct(bleio_uuid_obj_t *self, mp_int_t uuid16, co
ble_uuid_init_from_buf(&self->nimble_ble_uuid, (uint8_t *)&uuid16, 2);
} else {
ble_uuid_init_from_buf(&self->nimble_ble_uuid, uuid128, 16);
self->nimble_ble_uuid.u128.value[12] = uuid16 & 0xff;
self->nimble_ble_uuid.u128.value[13] = (uuid16 >> 8) & 0xff;
}
}

View File

@ -65,3 +65,35 @@ void bleio_background(void) {
void common_hal_bleio_gc_collect(void) {
bleio_adapter_gc_collect(&common_hal_bleio_adapter_obj);
}
void check_nimble_error(int rc, const char *file, size_t line) {
if (rc == NIMBLE_OK) {
return;
}
switch (rc) {
case BLE_HS_ENOMEM:
mp_raise_msg(&mp_type_MemoryError, translate("Nimble out of memory"));
return;
case BLE_HS_ETIMEOUT:
mp_raise_msg(&mp_type_TimeoutError, NULL);
return;
case BLE_HS_EINVAL:
mp_raise_ValueError(translate("Invalid BLE parameter"));
return;
case BLE_HS_ENOTCONN:
mp_raise_ConnectionError(translate("Not connected"));
return;
default:
#if CIRCUITPY_VERBOSE_BLE
if (file) {
mp_raise_bleio_BluetoothError(translate("Unknown system firmware error at %s:%d: %d"), file, line, rc);
}
#else
(void)file;
(void)line;
mp_raise_bleio_BluetoothError(translate("Unknown system firmware error: %d"), rc);
#endif
break;
}
}

View File

@ -39,4 +39,19 @@ void bleio_background(void);
// 20 bytes max (23 - 3).
#define GATT_MAX_DATA_LENGTH (BLE_GATT_ATT_MTU_DEFAULT - 3)
#define NIMBLE_OK (0)
void check_nimble_error(int rc, const char *file, size_t line);
#define CHECK_NIMBLE_ERROR(rc) check_nimble_error(rc, __FILE__, __LINE__)
#define MSEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000) / (RESOLUTION))
#define SEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000000) / (RESOLUTION))
#define UNITS_TO_SEC(TIME, RESOLUTION) (((TIME)*(RESOLUTION)) / 1000000)
// 0.625 msecs (625 usecs)
#define ADV_INTERVAL_UNIT_FLOAT_SECS (0.000625)
// Microseconds is the base unit. The macros above know that.
#define UNIT_0_625_MS (625)
#define UNIT_1_25_MS (1250)
#define UNIT_10_MS (10000)
#endif // MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_BLEIO_INIT_H