Skip to content

Commit

Permalink
Add S3 GATT client support
Browse files Browse the repository at this point in the history
This allows you to connect to GATT services on the other device.
It also adds connection initiation (GAP central).

More progress on #5926
  • Loading branch information
tannewt committed Feb 10, 2022
1 parent 5355092 commit 8958e7e
Show file tree
Hide file tree
Showing 18 changed files with 985 additions and 74 deletions.
13 changes: 13 additions & 0 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 ""
Expand Down
4 changes: 4 additions & 0 deletions ports/espressif/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)) \
Expand Down
97 changes: 96 additions & 1 deletion ports/espressif/common-hal/_bleio/Adapter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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"));

Expand Down
170 changes: 140 additions & 30 deletions ports/espressif/common-hal/_bleio/Characteristic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -97,17 +79,123 @@ 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;
}

size_t common_hal_bleio_characteristic_get_max_length(bleio_characteristic_obj_t *self) {
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) {
Expand All @@ -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);
}
8 changes: 6 additions & 2 deletions ports/espressif/common-hal/_bleio/Characteristic.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 8958e7e

Please sign in to comment.