Add S3 GATT client support

This allows you to connect to GATT services on the other device.
It also adds connection initiation (GAP central).

More progress on #5926
This commit is contained in:
Scott Shawcroft 2022-02-10 11:31:57 -08:00
parent 5355092e4a
commit 8958e7ef08
No known key found for this signature in database
GPG Key ID: 0DFD512649C052DA
18 changed files with 985 additions and 74 deletions

View File

@ -2427,6 +2427,16 @@ msgstr ""
msgid "Unhandled ESP TLS error %d %d %x %d"
msgstr ""
#: ports/espressif/common-hal/_bleio/__init__.c
#, c-format
msgid "Unknown BLE error at %s:%d: %d"
msgstr ""
#: ports/espressif/common-hal/_bleio/__init__.c
#, c-format
msgid "Unknown BLE error: %d"
msgstr ""
#: shared-bindings/wifi/Radio.c
#, c-format
msgid "Unknown failure %d"
@ -2498,12 +2508,14 @@ msgstr ""
msgid "Update Failed"
msgstr ""
#: ports/espressif/common-hal/_bleio/Characteristic.c
#: 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/Characteristic.c
#: ports/espressif/common-hal/_bleio/Descriptor.c
#: ports/nrf/common-hal/_bleio/Characteristic.c
#: ports/nrf/common-hal/_bleio/Descriptor.c
@ -3784,6 +3796,7 @@ msgstr ""
msgid "non-Device in %q"
msgstr ""
#: ports/espressif/common-hal/_bleio/Connection.c
#: ports/nrf/common-hal/_bleio/Connection.c
msgid "non-UUID found in service_uuids_whitelist"
msgstr ""

View File

@ -268,6 +268,10 @@ ifneq ($(CIRCUITPY_USB),0)
SRC_C += lib/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c
endif
ifneq ($(CIRCUITPY_BLEIO),0)
SRC_C += common-hal/_bleio/ble_events.c
endif
SRC_COMMON_HAL_EXPANDED = \
$(addprefix shared-bindings/, $(SRC_COMMON_HAL)) \
$(addprefix shared-bindings/, $(SRC_BINDINGS_ENUMS)) \

View File

@ -256,6 +256,17 @@ STATIC void _convert_address(const bleio_address_obj_t *address, ble_addr_t *nim
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);
@ -275,12 +286,96 @@ STATIC void _new_connection(uint16_t conn_handle) {
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) {
mp_raise_NotImplementedError(NULL);
// 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"));

View File

@ -42,40 +42,22 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self,
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,
const char *user_description) {
mp_raise_NotImplementedError(NULL);
self->service = service;
self->uuid = uuid;
self->handle = BLEIO_HANDLE_INVALID;
self->cccd_handle = BLEIO_HANDLE_INVALID;
self->sccd_handle = BLEIO_HANDLE_INVALID;
self->props = props;
self->read_perm = read_perm;
self->write_perm = write_perm;
self->initial_value_len = 0;
self->initial_value = NULL;
if (initial_value_bufinfo != NULL) {
// Copy the initial value if it's on the heap. Otherwise it's internal and we may not be able
// to allocate.
self->initial_value_len = initial_value_bufinfo->len;
if (gc_alloc_possible()) {
if (gc_nbytes(initial_value_bufinfo->buf) > 0) {
uint8_t *initial_value = m_malloc(self->initial_value_len, false);
memcpy(initial_value, initial_value_bufinfo->buf, self->initial_value_len);
self->initial_value = initial_value;
} else {
self->initial_value = initial_value_bufinfo->buf;
}
self->descriptor_list = mp_obj_new_list(0, NULL);
} else {
self->initial_value = initial_value_bufinfo->buf;
self->descriptor_list = NULL;
}
common_hal_bleio_characteristic_set_value(self, initial_value_bufinfo);
if (gc_alloc_possible()) {
self->descriptor_list = mp_obj_new_list(0, NULL);
} else {
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");
// }
// TODO: Implement this.
self->max_length = max_length;
self->fixed_length = fixed_length;
@ -97,8 +79,60 @@ bleio_service_obj_t *common_hal_bleio_characteristic_get_service(bleio_character
return self->service;
}
typedef struct {
TaskHandle_t task;
uint8_t *buf;
uint16_t len;
} _read_info_t;
STATIC int _read_cb(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg) {
_read_info_t *read_info = (_read_info_t *)arg;
switch (error->status) {
case 0: {
int len = MIN(read_info->len, OS_MBUF_PKTLEN(attr->om));
os_mbuf_copydata(attr->om, attr->offset, len, read_info->buf);
read_info->len = len;
}
MP_FALLTHROUGH;
default:
#if CIRCUITPY_VERBOSE_BLE
// For debugging.
mp_printf(&mp_plat_print, "Read status: %d\n", error->status);
#endif
xTaskNotify(read_info->task, error->status, eSetValueWithOverwrite);
break;
}
return 0;
}
size_t common_hal_bleio_characteristic_get_value(bleio_characteristic_obj_t *self, uint8_t *buf, size_t len) {
// TODO: Implement this.
// Do GATT operations only if this characteristic has been added to a registered service.
if (self->handle == BLEIO_HANDLE_INVALID) {
return 0;
}
uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection);
if (common_hal_bleio_service_get_is_remote(self->service)) {
_read_info_t read_info = {
.task = xTaskGetCurrentTaskHandle(),
.buf = buf,
.len = len
};
CHECK_NIMBLE_ERROR(ble_gattc_read(conn_handle, self->handle, _read_cb, &read_info));
int error_code;
xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200);
CHECK_BLE_ERROR(error_code);
return read_info.len;
} else {
len = MIN(self->current_value_len, len);
memcpy(buf, self->current_value, len);
return len;
}
return 0;
}
@ -106,8 +140,62 @@ size_t common_hal_bleio_characteristic_get_max_length(bleio_characteristic_obj_t
return self->max_length;
}
STATIC int _write_cb(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg) {
TaskHandle_t task = (TaskHandle_t)arg;
xTaskNotify(task, error->status, eSetValueWithOverwrite);
return 0;
}
void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_buffer_info_t *bufinfo) {
// TODO: Implement this.
if (common_hal_bleio_service_get_is_remote(self->service)) {
uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection);
if ((self->props & CHAR_PROP_WRITE_NO_RESPONSE) != 0) {
CHECK_NIMBLE_ERROR(ble_gattc_write_no_rsp_flat(conn_handle, self->handle, bufinfo->buf, bufinfo->len));
} else {
CHECK_NIMBLE_ERROR(ble_gattc_write_flat(conn_handle, self->handle, bufinfo->buf, bufinfo->len, _write_cb, xTaskGetCurrentTaskHandle()));
int error_code;
xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200);
CHECK_BLE_ERROR(error_code);
}
} else {
// Validate data length for local characteristics only.
// TODO: Test this once we can get servers going.
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"));
}
if (bufinfo == NULL) {
self->current_value_len = 0;
ble_gatts_chr_updated(self->handle);
return;
}
self->current_value_len = bufinfo->len;
// If we've already allocated an internal buffer or the provided buffer
// is on the heap, then copy into the internal buffer.
if (self->current_value_alloc > 0 || gc_nbytes(bufinfo->buf) > 0) {
if (self->current_value_alloc < bufinfo->len) {
self->current_value = m_realloc(self->current_value, bufinfo->len);
// Get the number of bytes from the heap because it may be more
// than the len due to gc block size.
self->current_value_alloc = gc_nbytes(self->current_value);
}
memcpy(self->current_value, bufinfo->buf, bufinfo->len);
} else {
// Otherwise, use the provided buffer to delay any heap allocation.
self->current_value = bufinfo->buf;
self->current_value_alloc = 0;
}
ble_gatts_chr_updated(self->handle);
}
}
bleio_uuid_obj_t *common_hal_bleio_characteristic_get_uuid(bleio_characteristic_obj_t *self) {
@ -118,10 +206,32 @@ bleio_characteristic_properties_t common_hal_bleio_characteristic_get_properties
return self->props;
}
void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self, bleio_descriptor_obj_t *descriptor) {
void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self,
bleio_descriptor_obj_t *descriptor) {
// TODO: Implement this.
mp_obj_list_append(MP_OBJ_FROM_PTR(self->descriptor_list),
MP_OBJ_FROM_PTR(descriptor));
}
void common_hal_bleio_characteristic_set_cccd(bleio_characteristic_obj_t *self, bool notify, bool indicate) {
// TODO: Implement this.
if (self->cccd_handle == BLEIO_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 ? 1 << 0 : 0) |
(indicate ? 1 << 1: 0);
CHECK_NIMBLE_ERROR(ble_gattc_write_flat(conn_handle, self->cccd_handle, &cccd_value, 2, _write_cb, xTaskGetCurrentTaskHandle()));
int error_code;
xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200);
CHECK_BLE_ERROR(error_code);
}

View File

@ -39,9 +39,13 @@ typedef struct _bleio_characteristic_obj {
// Will be MP_OBJ_NULL before being assigned to a Service.
bleio_service_obj_t *service;
bleio_uuid_obj_t *uuid;
const uint8_t *initial_value;
uint16_t initial_value_len;
uint8_t *current_value;
uint16_t current_value_len;
// Our internal allocation length. If > 0, then current_value is managed by
// this characteristic.
uint16_t current_value_alloc;
uint16_t max_length;
uint16_t def_handle;
uint16_t handle;
bleio_characteristic_properties_t props;
bleio_attribute_security_mode_t read_perm;

View File

@ -28,6 +28,7 @@
#include <stdio.h>
#include "shared/runtime/interrupt_char.h"
#include "py/ringbuf.h"
#include "py/runtime.h"
#include "py/stream.h"
@ -37,14 +38,38 @@
#include "common-hal/_bleio/CharacteristicBuffer.h"
#include "shared-bindings/_bleio/CharacteristicBuffer.h"
STATIC int characteristic_buffer_on_ble_evt(struct ble_gap_event *event, void *param) {
bleio_characteristic_buffer_obj_t *self = (bleio_characteristic_buffer_obj_t *)param;
switch (event->type) {
case BLE_GAP_EVENT_NOTIFY_RX: {
// A remote service wrote to this characteristic.
// Must be a notification, and event handle must match the handle for my characteristic.
if (event->notify_rx.indication == 0 &&
event->notify_rx.attr_handle == self->characteristic->handle) {
const struct os_mbuf *m = event->notify_rx.om;
while (m != NULL) {
ringbuf_put_n(&self->ringbuf, m->om_data, m->om_len);
m = SLIST_NEXT(m, om_next);
}
}
break;
}
default:
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "Unhandled gap event %d\n", event->type);
#endif
return 0;
break;
}
return 0;
}
void _common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffer_obj_t *self,
bleio_characteristic_obj_t *characteristic,
mp_float_t timeout,
uint8_t *buffer, size_t buffer_size,
void *static_handler_entry) {
mp_raise_NotImplementedError(NULL);
self->characteristic = characteristic;
self->timeout_ms = timeout * 1000;
@ -53,6 +78,11 @@ void _common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buff
self->ringbuf.iget = 0;
self->ringbuf.iput = 0;
if (static_handler_entry != NULL) {
ble_event_add_handler_entry((ble_event_handler_entry_t *)static_handler_entry, characteristic_buffer_on_ble_evt, self);
} else {
ble_event_add_handler(characteristic_buffer_on_ble_evt, self);
}
}
// Assumes that timeout and buffer_size have been validated before call.
@ -65,17 +95,32 @@ void common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffe
}
uint32_t common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self, uint8_t *data, size_t len, int *errcode) {
// TODO: Implement this.
return 0;
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;
}
}
uint32_t num_bytes_read = ringbuf_get_n(&self->ringbuf, data, len);
return num_bytes_read;
}
// NOTE: The nRF port has protection around these operations because the ringbuf
// is filled from an interrupt. On ESP the ringbuf is filled from the BLE host
// task that won't interrupt us.
uint32_t common_hal_bleio_characteristic_buffer_rx_characters_available(bleio_characteristic_buffer_obj_t *self) {
// TODO: Implement this.
return 0;
return ringbuf_num_filled(&self->ringbuf);
}
void common_hal_bleio_characteristic_buffer_clear_rx_buffer(bleio_characteristic_buffer_obj_t *self) {
// TODO: Implement this.
ringbuf_clear(&self->ringbuf);
}
bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer_obj_t *self) {
@ -83,7 +128,10 @@ bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer
}
void common_hal_bleio_characteristic_buffer_deinit(bleio_characteristic_buffer_obj_t *self) {
// TODO: Implement this.
if (!common_hal_bleio_characteristic_buffer_deinited(self)) {
ble_event_remove_handler(characteristic_buffer_on_ble_evt, self);
self->characteristic = NULL;
}
}
bool common_hal_bleio_characteristic_buffer_connected(bleio_characteristic_buffer_obj_t *self) {

View File

@ -48,6 +48,9 @@
#include "host/ble_att.h"
// Give 20 seconds for discovery
#define DISCOVERY_TIMEOUT_MS 20000
int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) {
bleio_connection_internal_t *connection = (bleio_connection_internal_t *)connection_in;
@ -69,9 +72,35 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in)
}
case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: {
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "TODO connection event: PHY update complete\n");
#endif
break;
}
case BLE_GAP_EVENT_CONN_UPDATE: {
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "TODO connection event: connection update\n");
#endif
break;
}
case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: {
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "TODO connection event: l2cap update request\n");
#endif
break;
}
// These events are actually att specific so forward to all registered
// handlers for them. The handlers themselves decide whether an event
// is interesting to them.
case BLE_GAP_EVENT_NOTIFY_RX:
MP_FALLTHROUGH;
case BLE_GAP_EVENT_NOTIFY_TX:
MP_FALLTHROUGH;
case BLE_GAP_EVENT_SUBSCRIBE:
return ble_event_run_handlers(event);
default:
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "Unhandled connection event: %d\n", event->type);
@ -96,7 +125,7 @@ bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) {
}
void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self) {
// TODO: Implement this.
ble_gap_terminate(self->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
void common_hal_bleio_connection_pair(bleio_connection_internal_t *self, bool bond) {
@ -121,7 +150,265 @@ void common_hal_bleio_connection_set_connection_interval(bleio_connection_intern
// TODO: Implement this.
}
STATIC volatile int _last_discovery_status;
static TaskHandle_t discovery_task = NULL;
STATIC int _discovered_service_cb(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_svc *svc,
void *arg) {
bleio_connection_internal_t *self = (bleio_connection_internal_t *)arg;
if (error->status != BLE_ERR_SUCCESS) {
// Keep the first error in case it's due to memory.
if (_last_discovery_status == BLE_ERR_SUCCESS) {
_last_discovery_status = error->status;
xTaskNotifyGive(discovery_task);
}
return 0;
}
// If any of these memory allocations fail, we set _last_discovery_status
// and let the process continue.
if (_last_discovery_status != BLE_ERR_SUCCESS) {
return 0;
}
bleio_service_obj_t *service = m_new_obj(bleio_service_obj_t);
if (service == NULL) {
_last_discovery_status = BLE_ERR_MEM_CAPACITY;
return 0;
}
service->base.type = &bleio_service_type;
// Initialize several fields at once.
bleio_service_from_connection(service, bleio_connection_new_from_internal(self));
service->is_remote = true;
service->start_handle = svc->start_handle;
service->end_handle = svc->end_handle;
service->handle = svc->start_handle;
bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t);
if (uuid == NULL) {
_last_discovery_status = BLE_ERR_MEM_CAPACITY;
return 0;
}
uuid->base.type = &bleio_uuid_type;
uuid->nimble_ble_uuid = svc->uuid;
service->uuid = uuid;
mp_obj_list_append(MP_OBJ_FROM_PTR(self->remote_service_list),
MP_OBJ_FROM_PTR(service));
return 0;
}
STATIC int _discovered_characteristic_cb(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr,
void *arg) {
bleio_service_obj_t *service = (bleio_service_obj_t *)arg;
if (error->status != BLE_ERR_SUCCESS) {
// Keep the first error in case it's due to memory.
if (_last_discovery_status == BLE_ERR_SUCCESS) {
_last_discovery_status = error->status;
xTaskNotifyGive(discovery_task);
}
}
// If any of these memory allocations fail, we set _last_discovery_status
// and let the process continue.
if (_last_discovery_status != BLE_ERR_SUCCESS) {
return 0;
}
bleio_characteristic_obj_t *characteristic = m_new_obj(bleio_characteristic_obj_t);
if (characteristic == NULL) {
_last_discovery_status = BLE_ERR_MEM_CAPACITY;
return 0;
}
characteristic->base.type = &bleio_characteristic_type;
// Known characteristic UUID.
bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t);
if (uuid == NULL) {
_last_discovery_status = BLE_ERR_MEM_CAPACITY;
return 0;
}
uuid->base.type = &bleio_uuid_type;
uuid->nimble_ble_uuid = chr->uuid;
bleio_characteristic_properties_t props =
((chr->properties & BLE_GATT_CHR_PROP_BROADCAST) != 0 ? CHAR_PROP_BROADCAST : 0) |
((chr->properties & BLE_GATT_CHR_PROP_INDICATE) != 0 ? CHAR_PROP_INDICATE : 0) |
((chr->properties & BLE_GATT_CHR_PROP_NOTIFY) != 0 ? CHAR_PROP_NOTIFY : 0) |
((chr->properties & BLE_GATT_CHR_PROP_READ) != 0 ? CHAR_PROP_READ : 0) |
((chr->properties & BLE_GATT_CHR_PROP_WRITE) != 0 ? CHAR_PROP_WRITE : 0) |
((chr->properties & BLE_GATT_CHR_PROP_WRITE_NO_RSP) != 0 ? 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, service, chr->val_handle, uuid,
props, SECURITY_MODE_OPEN, SECURITY_MODE_OPEN,
0, false, // max_length, fixed_length: values don't matter for gattc
mp_const_empty_bytes,
NULL);
// Set def_handle directly since it is only used in discovery.
characteristic->def_handle = chr->def_handle;
mp_obj_list_append(MP_OBJ_FROM_PTR(service->characteristic_list),
MP_OBJ_FROM_PTR(characteristic));
return 0;
}
STATIC int _discovered_descriptor_cb(uint16_t conn_handle,
const struct ble_gatt_error *error,
uint16_t chr_val_handle,
const struct ble_gatt_dsc *dsc,
void *arg) {
bleio_characteristic_obj_t *characteristic = (bleio_characteristic_obj_t *)arg;
if (error->status != BLE_ERR_SUCCESS) {
// Keep the first error in case it's due to memory.
if (_last_discovery_status == BLE_ERR_SUCCESS) {
_last_discovery_status = error->status;
}
xTaskNotifyGive(discovery_task);
}
// If any of these memory allocations fail, we set _last_discovery_status
// and let the process continue.
if (_last_discovery_status != BLE_ERR_SUCCESS) {
return 0;
}
// Remember handles for certain well-known descriptors.
switch (dsc->uuid.u16.value) {
case 0x2902:
characteristic->cccd_handle = dsc->handle;
break;
case 0x2903:
characteristic->sccd_handle = dsc->handle;
break;
case 0x2901:
characteristic->user_desc_handle = dsc->handle;
break;
default:
break;
}
bleio_descriptor_obj_t *descriptor = m_new_obj(bleio_descriptor_obj_t);
if (descriptor == NULL) {
_last_discovery_status = BLE_ERR_MEM_CAPACITY;
return 0;
}
descriptor->base.type = &bleio_descriptor_type;
bleio_uuid_obj_t *uuid = m_new_obj(bleio_uuid_obj_t);
if (uuid == NULL) {
_last_discovery_status = BLE_ERR_MEM_CAPACITY;
return 0;
}
uuid->base.type = &bleio_uuid_type;
uuid->nimble_ble_uuid = dsc->uuid;
common_hal_bleio_descriptor_construct(
descriptor, characteristic, uuid,
SECURITY_MODE_OPEN, SECURITY_MODE_OPEN,
0, false, mp_const_empty_bytes);
descriptor->handle = dsc->handle;
mp_obj_list_append(MP_OBJ_FROM_PTR(characteristic->descriptor_list),
MP_OBJ_FROM_PTR(descriptor));
return 0;
}
STATIC void discover_remote_services(bleio_connection_internal_t *self, mp_obj_t service_uuids_whitelist) {
// Start over with an empty list.
self->remote_service_list = mp_obj_new_list(0, NULL);
discovery_task = xTaskGetCurrentTaskHandle();
if (service_uuids_whitelist == mp_const_none) {
_last_discovery_status = BLE_ERR_SUCCESS;
CHECK_NIMBLE_ERROR(ble_gattc_disc_all_svcs(self->conn_handle, _discovered_service_cb, self));
// Wait for sync.
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(DISCOVERY_TIMEOUT_MS));
if (_last_discovery_status != BLE_HS_EDONE) {
CHECK_BLE_ERROR(_last_discovery_status);
}
} 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);
_last_discovery_status = BLE_ERR_SUCCESS;
// Make sure we start with a clean notification state
ulTaskNotifyValueClear(discovery_task, 0xffffffff);
CHECK_NIMBLE_ERROR(ble_gattc_disc_svc_by_uuid(self->conn_handle, &uuid->nimble_ble_uuid.u,
_discovered_service_cb, self));
// Wait for sync.
CHECK_NOTIFY(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(DISCOVERY_TIMEOUT_MS)));
if (_last_discovery_status != BLE_HS_EDONE) {
CHECK_BLE_ERROR(_last_discovery_status);
}
}
}
for (size_t i = 0; i < self->remote_service_list->len; i++) {
bleio_service_obj_t *service = MP_OBJ_TO_PTR(self->remote_service_list->items[i]);
_last_discovery_status = BLE_ERR_SUCCESS;
CHECK_NIMBLE_ERROR(ble_gattc_disc_all_chrs(self->conn_handle,
service->start_handle,
service->end_handle,
_discovered_characteristic_cb,
service));
// Wait for sync.
CHECK_NOTIFY(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(DISCOVERY_TIMEOUT_MS)));
if (_last_discovery_status != BLE_HS_EDONE) {
CHECK_BLE_ERROR(_last_discovery_status);
}
// 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]);
// Determine the handle range for the given characteristic's descriptors.
// The end of the range is dictated by the next characteristic or the end
// handle of the service.
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]);
uint16_t end_handle = next_characteristic == NULL
? service->end_handle
: next_characteristic->def_handle - 1;
_last_discovery_status = BLE_ERR_SUCCESS;
CHECK_NIMBLE_ERROR(ble_gattc_disc_all_dscs(self->conn_handle, characteristic->handle,
end_handle,
_discovered_descriptor_cb, characteristic));
// Wait for sync.
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(DISCOVERY_TIMEOUT_MS));
if (_last_discovery_status != BLE_HS_EDONE) {
CHECK_BLE_ERROR(_last_discovery_status);
}
}
}
}
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 =
@ -129,7 +416,6 @@ mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_conne
self->connection->remote_service_list->items);
mp_obj_list_clear(MP_OBJ_FROM_PTR(self->connection->remote_service_list));
// TODO: Implement this.
return services_tuple;
}

View File

@ -36,7 +36,6 @@
#include "host/ble_att.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) {
mp_raise_NotImplementedError(NULL);
self->characteristic = characteristic;
self->uuid = uuid;
self->handle = BLEIO_HANDLE_INVALID;

View File

@ -33,6 +33,8 @@
#include "common-hal/_bleio/UUID.h"
#include "host/ble_gatt.h"
// Forward declare characteristic because it includes a Descriptor.
struct _bleio_characteristic_obj;
@ -45,6 +47,7 @@ typedef struct _bleio_descriptor_obj {
uint16_t max_length;
bool fixed_length;
uint16_t handle;
struct ble_gatt_dsc_def def;
bleio_attribute_security_mode_t read_perm;
bleio_attribute_security_mode_t write_perm;
} bleio_descriptor_obj_t;

View File

@ -40,13 +40,113 @@
#include "host/ble_att.h"
STATIC void write_to_ringbuf(bleio_packet_buffer_obj_t *self, const struct os_mbuf *mbuf) {
size_t len = OS_MBUF_PKTLEN(mbuf);
if (len + sizeof(uint16_t) > ringbuf_capacity(&self->ringbuf)) {
// This shouldn't happen but can if our buffer size was much smaller than
// the writes the client actually makes.
return;
}
// 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));
while (mbuf != NULL) {
ringbuf_put_n(&self->ringbuf, mbuf->om_data, mbuf->om_len);
mbuf = SLIST_NEXT(mbuf, om_next);
}
}
STATIC int packet_buffer_on_ble_client_evt(struct ble_gap_event *event, void *param);
STATIC int queue_next_write(bleio_packet_buffer_obj_t *self);
STATIC int _write_cb(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg) {
if (error->status != 0) {
mp_printf(&mp_plat_print, "write failed %d\n", error->status);
}
bleio_packet_buffer_obj_t *self = (bleio_packet_buffer_obj_t *)arg;
queue_next_write(self);
return 0;
}
STATIC int 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;
int err_code = NIMBLE_OK;
if (self->client) {
if (self->write_type == CHAR_PROP_WRITE_NO_RESPONSE) {
err_code = ble_gattc_write_no_rsp_flat(conn_handle,
self->characteristic->handle,
self->outgoing[self->pending_index],
self->pending_size);
// We don't set packet_queued because we NimBLE will buffer our
// outgoing packets.
} else {
err_code = ble_gattc_write_flat(conn_handle,
self->characteristic->handle,
self->outgoing[self->pending_index],
self->pending_size,
_write_cb, self);
self->pending_index = (self->pending_index + 1) % 2;
self->packet_queued = true;
}
self->pending_size = 0;
} else {
// TODO: Notify because we're the server.
}
if (err_code != NIMBLE_OK) {
// On error, simply skip updating the pending buffers so that the next HVC or WRITE
// complete event triggers another attempt.
return err_code;
}
}
return NIMBLE_OK;
}
STATIC int packet_buffer_on_ble_client_evt(struct ble_gap_event *event, void *param) {
bleio_packet_buffer_obj_t *self = (bleio_packet_buffer_obj_t *)param;
if (event->type == BLE_GAP_EVENT_DISCONNECT && self->conn_handle == event->disconnect.conn.conn_handle) {
self->conn_handle = BLEIO_HANDLE_INVALID;
}
switch (event->type) {
case BLE_GAP_EVENT_NOTIFY_RX: {
if (event->notify_rx.conn_handle != self->conn_handle) {
return false;
}
// Must be a notification, and event handle must match the handle for my characteristic.
if (event->notify_rx.attr_handle == self->characteristic->handle) {
write_to_ringbuf(self, event->notify_rx.om);
}
break;
}
default:
return false;
break;
}
return true;
}
void _common_hal_bleio_packet_buffer_construct(
bleio_packet_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic,
uint32_t *incoming_buffer, size_t incoming_buffer_size,
uint32_t *outgoing_buffer1, uint32_t *outgoing_buffer2, size_t max_packet_size,
void *static_handler_entry) {
mp_raise_NotImplementedError(NULL);
self->characteristic = characteristic;
self->client = self->characteristic->service->is_remote;
self->max_packet_size = max_packet_size;
@ -76,6 +176,29 @@ void _common_hal_bleio_packet_buffer_construct(
self->outgoing[0] = outgoing_buffer1;
self->outgoing[1] = outgoing_buffer2;
if (self->client) {
if (static_handler_entry != NULL) {
ble_event_add_handler_entry((ble_event_handler_entry_t *)static_handler_entry, packet_buffer_on_ble_client_evt, self);
} else {
ble_event_add_handler(packet_buffer_on_ble_client_evt, self);
}
if (incoming) {
// Prefer notify if both are available.
if (incoming & CHAR_PROP_NOTIFY) {
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 = CHAR_PROP_WRITE;
if (outgoing & CHAR_PROP_WRITE_NO_RESPONSE) {
self->write_type = CHAR_PROP_WRITE_NO_RESPONSE;
}
}
} else {
// TODO: Setup for server.
}
}
void common_hal_bleio_packet_buffer_construct(
@ -104,7 +227,12 @@ void common_hal_bleio_packet_buffer_construct(
uint32_t *outgoing2 = NULL;
if (outgoing) {
outgoing1 = m_malloc(max_packet_size, false);
outgoing2 = m_malloc(max_packet_size, false);
// Only allocate the second buffer if we are doing writes with responses.
// Without responses, we just write as quickly as we can.
if (outgoing == CHAR_PROP_WRITE) {
outgoing2 = m_malloc(max_packet_size, false);
}
}
_common_hal_bleio_packet_buffer_construct(self, characteristic,
incoming_buffer, incoming_buffer_size,
@ -117,9 +245,25 @@ mp_int_t common_hal_bleio_packet_buffer_readinto(bleio_packet_buffer_obj_t *self
return 0;
}
// Copy received data. Lock out write interrupt handler while copying.
// TODO: Implement this.
return 0;
// 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;
}
return ret;
}
mp_int_t common_hal_bleio_packet_buffer_write(bleio_packet_buffer_obj_t *self, const uint8_t *data, size_t len, uint8_t *header, size_t header_len) {
@ -172,10 +316,9 @@ mp_int_t common_hal_bleio_packet_buffer_write(bleio_packet_buffer_obj_t *self, c
self->pending_size += len;
num_bytes_written += len;
// TODO: Implement this.
// If no writes are queued then sneak in this data.
if (!self->packet_queued) {
CHECK_NIMBLE_ERROR(queue_next_write(self));
}
return num_bytes_written;
}
@ -270,6 +413,6 @@ bool common_hal_bleio_packet_buffer_deinited(bleio_packet_buffer_obj_t *self) {
void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self) {
if (!common_hal_bleio_packet_buffer_deinited(self)) {
ble_event_remove_handler(packet_buffer_on_ble_client_evt, self);
}
// TODO: Implement this.
}

View File

@ -41,12 +41,6 @@ uint32_t _common_hal_bleio_service_construct(bleio_service_obj_t *self, bleio_uu
self->is_remote = false;
self->connection = NULL;
self->is_secondary = is_secondary;
// uint8_t service_type = BLE_GATT_SVC_TYPE_PRIMARY;
// if (is_secondary) {
// service_type = BLE_GATT_SVC_TYPE_SECONDARY;
// }
return 0;
}
@ -57,7 +51,7 @@ void common_hal_bleio_service_construct(bleio_service_obj_t *self, bleio_uuid_ob
}
void bleio_service_from_connection(bleio_service_obj_t *self, mp_obj_t connection) {
self->handle = 0xFFFF;
self->handle = BLEIO_HANDLE_INVALID;
self->uuid = NULL;
self->characteristic_list = mp_obj_new_list(0, NULL);
self->is_remote = true;

View File

@ -40,6 +40,7 @@
#include "common-hal/_bleio/__init__.h"
// #include "common-hal/_bleio/bonding.h"
#include "common-hal/_bleio/ble_events.h"
// Turn off BLE on a reset or reload.
void bleio_reset() {
@ -50,6 +51,7 @@ void bleio_reset() {
}
supervisor_stop_bluetooth();
ble_event_reset();
bleio_adapter_reset(&common_hal_bleio_adapter_obj);
common_hal_bleio_adapter_set_enabled(&common_hal_bleio_adapter_obj, false);
supervisor_start_bluetooth();
@ -97,3 +99,36 @@ void check_nimble_error(int rc, const char *file, size_t line) {
break;
}
}
void check_ble_error(int error_code, const char *file, size_t line) {
if (error_code == BLE_ERR_SUCCESS) {
return;
}
switch (error_code) {
default:
#if CIRCUITPY_VERBOSE_BLE
if (file) {
mp_raise_bleio_BluetoothError(translate("Unknown BLE error at %s:%d: %d"), file, line, error_code);
}
#else
(void)file;
(void)line;
mp_raise_bleio_BluetoothError(translate("Unknown BLE error: %d"), error_code);
#endif
break;
}
}
void check_notify(BaseType_t result) {
if (result == pdTRUE) {
return;
}
mp_raise_msg(&mp_type_TimeoutError, NULL);
}
void common_hal_bleio_check_connected(uint16_t conn_handle) {
if (conn_handle == BLEIO_HANDLE_INVALID) {
mp_raise_ConnectionError(translate("Not connected"));
}
}

View File

@ -27,6 +27,8 @@
#ifndef MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_BLEIO_INIT_H
#define MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL_BLEIO_INIT_H
#include "FreeRTOS.h"
void bleio_background(void);
// typedef struct {
@ -43,6 +45,10 @@ void bleio_background(void);
void check_nimble_error(int rc, const char *file, size_t line);
#define CHECK_NIMBLE_ERROR(rc) check_nimble_error(rc, __FILE__, __LINE__)
void check_ble_error(int error_code, const char *file, size_t line);
#define CHECK_BLE_ERROR(error_code) check_ble_error(error_code, __FILE__, __LINE__)
void check_notify(BaseType_t result);
#define CHECK_NOTIFY(result) check_notify(result)
#define MSEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000) / (RESOLUTION))
#define SEC_TO_UNITS(TIME, RESOLUTION) (((TIME) * 1000000) / (RESOLUTION))
@ -51,6 +57,7 @@ void check_nimble_error(int rc, const char *file, size_t line);
#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_MS (1000)
#define UNIT_1_25_MS (1250)
#define UNIT_10_MS (10000)

View File

@ -0,0 +1,116 @@
/*
* 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 "common-hal/_bleio/ble_events.h"
#include <stdbool.h>
#include <stdio.h>
#include "py/misc.h"
#include "py/mpstate.h"
#include "py/runtime.h"
#if CIRCUITPY_SERIAL_BLE && CIRCUITPY_VERBOSE_BLE
#include "supervisor/shared/bluetooth/serial.h"
#endif
void ble_event_reset(void) {
// Linked list items will be gc'd.
MP_STATE_VM(ble_event_handler_entries) = NULL;
}
void ble_event_add_handler_entry(ble_event_handler_entry_t *entry,
ble_gap_event_fn *func, void *param) {
ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries);
while (it != NULL) {
// If event handler and its corresponding param are already on the list, don't add again.
if ((it->func == func) && (it->param == param)) {
return;
}
it = it->next;
}
entry->next = MP_STATE_VM(ble_event_handler_entries);
entry->param = param;
entry->func = func;
MP_STATE_VM(ble_event_handler_entries) = entry;
}
void ble_event_add_handler(ble_gap_event_fn *func, void *param) {
ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries);
while (it != NULL) {
// If event handler and its corresponding param are already on the list, don't add again.
if ((it->func == func) && (it->param == param)) {
return;
}
it = it->next;
}
// Add a new handler to the front of the list
ble_event_handler_entry_t *handler = m_new_ll(ble_event_handler_entry_t, 1);
ble_event_add_handler_entry(handler, func, param);
}
void ble_event_remove_handler(ble_gap_event_fn *func, void *param) {
ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries);
ble_event_handler_entry_t **prev = &MP_STATE_VM(ble_event_handler_entries);
while (it != NULL) {
if ((it->func == func) && (it->param == param)) {
// Splice out the matching handler.
*prev = it->next;
// Clear next of the removed node so it's clearly not in a list.
it->next = NULL;
return;
}
prev = &(it->next);
it = it->next;
}
}
int ble_event_run_handlers(struct ble_gap_event *event) {
#if CIRCUITPY_SERIAL_BLE && CIRCUITPY_VERBOSE_BLE
ble_serial_disable();
#endif
#if CIRCUITPY_VERBOSE_BLE
mp_printf(&mp_plat_print, "BLE GAP event: 0x%04x\n", event->type);
#endif
ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries);
bool done = false;
while (it != NULL) {
// Capture next before calling the function in case it removes itself from the list.
ble_event_handler_entry_t *next = it->next;
done = it->func(event, it->param) || done;
it = next;
}
#if CIRCUITPY_SERIAL_BLE && CIRCUITPY_VERBOSE_BLE
ble_serial_enable();
#endif
return 0;
}

View File

@ -0,0 +1,51 @@
/*
* 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_ESPRESSIF_COMMON_HAL__BLEIO_BLE_EVENTS_H
#define MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL__BLEIO_BLE_EVENTS_H
#include <stdbool.h>
#include "host/ble_gap.h"
typedef struct ble_event_handler_entry {
struct ble_event_handler_entry *next;
void *param;
ble_gap_event_fn *func;
} ble_event_handler_entry_t;
void ble_event_reset(void);
void ble_event_add_handler(ble_gap_event_fn *func, void *param);
void ble_event_remove_handler(ble_gap_event_fn *func, void *param);
// Allow for user provided entries to prevent allocations outside the VM.
void ble_event_add_handler_entry(ble_event_handler_entry_t *entry, ble_gap_event_fn *func, void *param);
int ble_event_run_handlers(struct ble_gap_event *event);
#endif // MICROPY_INCLUDED_ESPRESSIF_COMMON_HAL__BLEIO_BLE_EVENTS_H

View File

@ -35,8 +35,19 @@
#include "py/circuitpy_mpconfig.h"
#if CIRCUITPY_BLEIO
#include "common-hal/_bleio/ble_events.h"
#endif
#if CIRCUITPY_BLEIO
#define MICROPY_PORT_ROOT_POINTERS \
CIRCUITPY_COMMON_ROOT_POINTERS \
ble_event_handler_entry_t *ble_event_handler_entries;
#else
#define MICROPY_PORT_ROOT_POINTERS \
CIRCUITPY_COMMON_ROOT_POINTERS
#endif
#define MICROPY_NLR_SETJMP (1)
#define CIRCUITPY_DEFAULT_STACK_SIZE 0x6000
@ -61,4 +72,5 @@
#ifndef CIRCUITPY_I2C_ALLOW_INTERNAL_PULL_UP
#define CIRCUITPY_I2C_ALLOW_INTERNAL_PULL_UP (0)
#endif
#endif // MICROPY_INCLUDED_ESPRESSIF_MPCONFIGPORT_H

View File

@ -158,6 +158,7 @@ bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer
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);
self->characteristic = NULL;
}
}

View File

@ -4,6 +4,7 @@
#include "shared-module/vectorio/__init__.h"
#include "shared-bindings/vectorio/VectorShape.h"
#include "py/misc.h"
#include "py/runtime.h"
#include "shared-bindings/time/__init__.h"
#include "shared-bindings/displayio/ColorConverter.h"
@ -61,17 +62,6 @@
(u32 & 0x2 ? '1' : '0'), \
(u32 & 0x1 ? '1' : '0')
inline __attribute__((always_inline))
static int32_t max(int32_t a, int32_t b) {
return a > b ? a : b;
}
inline __attribute__((always_inline))
static uint32_t min(uint32_t a, uint32_t b) {
return a < b ? a : b;
}
static void short_bound_check(mp_int_t i, qstr name) {
if (i < SHRT_MIN || i > SHRT_MAX) {
mp_raise_ValueError_varg(translate("%q must be between %d and %d"), name, SHRT_MIN, SHRT_MAX);
@ -456,7 +446,7 @@ bool vectorio_vector_shape_fill_area(vectorio_vector_shape_t *self, const _displ
mp_obj_get_type_str(self->ishape.shape),
(overlap.x2 - overlap.x1) * (overlap.y2 - overlap.y1),
(double)((end - start) / 1000000.0),
(double)(max(1, pixels * (1000000000.0 / (end - start)))),
(double)(MAX(1, pixels * (1000000000.0 / (end - start)))),
(double)(pixel_time / 1000.0),
(double)(pixel_time / 1000.0 / pixels)
);
@ -514,7 +504,7 @@ displayio_area_t *vectorio_vector_shape_get_refresh_areas(vectorio_vector_shape_
union_size, dirty_size, current_size, overlap_size, (int32_t)union_size - dirty_size - current_size + overlap_size
);
if ((int32_t)union_size - dirty_size - current_size + overlap_size <= min(dirty_size, current_size)) {
if ((int32_t)union_size - dirty_size - current_size + overlap_size <= MIN(dirty_size, current_size)) {
// The excluded / non-overlapping area from the disjoint dirty and current areas is smaller
// than the smallest area we need to draw. Redrawing the overlapping area would cost more
// than just drawing the union disjoint area once.