Skip to content

Commit 8958e7e

Browse files
committed
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
1 parent 5355092 commit 8958e7e

File tree

18 files changed

+985
-74
lines changed

18 files changed

+985
-74
lines changed

locale/circuitpython.pot

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,6 +2427,16 @@ msgstr ""
24272427
msgid "Unhandled ESP TLS error %d %d %x %d"
24282428
msgstr ""
24292429

2430+
#: ports/espressif/common-hal/_bleio/__init__.c
2431+
#, c-format
2432+
msgid "Unknown BLE error at %s:%d: %d"
2433+
msgstr ""
2434+
2435+
#: ports/espressif/common-hal/_bleio/__init__.c
2436+
#, c-format
2437+
msgid "Unknown BLE error: %d"
2438+
msgstr ""
2439+
24302440
#: shared-bindings/wifi/Radio.c
24312441
#, c-format
24322442
msgid "Unknown failure %d"
@@ -2498,12 +2508,14 @@ msgstr ""
24982508
msgid "Update Failed"
24992509
msgstr ""
25002510

2511+
#: ports/espressif/common-hal/_bleio/Characteristic.c
25012512
#: ports/espressif/common-hal/_bleio/Descriptor.c
25022513
#: ports/nrf/common-hal/_bleio/Characteristic.c
25032514
#: ports/nrf/common-hal/_bleio/Descriptor.c
25042515
msgid "Value length != required fixed length"
25052516
msgstr ""
25062517

2518+
#: ports/espressif/common-hal/_bleio/Characteristic.c
25072519
#: ports/espressif/common-hal/_bleio/Descriptor.c
25082520
#: ports/nrf/common-hal/_bleio/Characteristic.c
25092521
#: ports/nrf/common-hal/_bleio/Descriptor.c
@@ -3784,6 +3796,7 @@ msgstr ""
37843796
msgid "non-Device in %q"
37853797
msgstr ""
37863798

3799+
#: ports/espressif/common-hal/_bleio/Connection.c
37873800
#: ports/nrf/common-hal/_bleio/Connection.c
37883801
msgid "non-UUID found in service_uuids_whitelist"
37893802
msgstr ""

ports/espressif/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,10 @@ ifneq ($(CIRCUITPY_USB),0)
268268
SRC_C += lib/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c
269269
endif
270270

271+
ifneq ($(CIRCUITPY_BLEIO),0)
272+
SRC_C += common-hal/_bleio/ble_events.c
273+
endif
274+
271275
SRC_COMMON_HAL_EXPANDED = \
272276
$(addprefix shared-bindings/, $(SRC_COMMON_HAL)) \
273277
$(addprefix shared-bindings/, $(SRC_BINDINGS_ENUMS)) \

ports/espressif/common-hal/_bleio/Adapter.c

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,17 @@ STATIC void _convert_address(const bleio_address_obj_t *address, ble_addr_t *nim
256256
memcpy(nimble_address->val, (uint8_t *)address_buf_info.buf, NUM_BLEIO_ADDRESS_BYTES);
257257
}
258258

259+
STATIC int _mtu_reply(uint16_t conn_handle,
260+
const struct ble_gatt_error *error,
261+
uint16_t mtu, void *arg) {
262+
bleio_connection_internal_t *connection = (bleio_connection_internal_t *)arg;
263+
if (conn_handle != connection->conn_handle || error->status != 0) {
264+
return 0;
265+
}
266+
connection->mtu = mtu;
267+
return 0;
268+
}
269+
259270
STATIC void _new_connection(uint16_t conn_handle) {
260271
// Set the tx_power for the connection higher than the advertisement.
261272
esp_ble_tx_power_set(conn_handle, ESP_PWR_LVL_N0);
@@ -275,12 +286,96 @@ STATIC void _new_connection(uint16_t conn_handle) {
275286
connection->pair_status = PAIR_NOT_PAIRED;
276287
connection->mtu = 0;
277288

289+
ble_gattc_exchange_mtu(conn_handle, _mtu_reply, connection);
290+
278291
// Change the callback for the connection.
279292
ble_gap_set_event_cb(conn_handle, bleio_connection_event_cb, connection);
280293
}
281294

295+
static int _connect_event(struct ble_gap_event *event, void *self_in) {
296+
bleio_adapter_obj_t *self = (bleio_adapter_obj_t *)self_in;
297+
298+
#if CIRCUITPY_VERBOSE_BLE
299+
mp_printf(&mp_plat_print, "Connect event: %d\n", event->type);
300+
#endif
301+
switch (event->type) {
302+
case BLE_GAP_EVENT_CONNECT:
303+
if (event->connect.status == 0) {
304+
_new_connection(event->connect.conn_handle);
305+
// Set connections objs back to NULL since we have a new
306+
// connection and need a new tuple.
307+
self->connection_objs = NULL;
308+
xTaskNotify(cp_task, event->connect.conn_handle, eSetValueWithOverwrite);
309+
} else {
310+
xTaskNotify(cp_task, -event->connect.status, eSetValueWithOverwrite);
311+
}
312+
break;
313+
314+
default:
315+
#if CIRCUITPY_VERBOSE_BLE
316+
// For debugging.
317+
mp_printf(&mp_plat_print, "Unhandled connect event: %d\n", event->type);
318+
#endif
319+
break;
320+
}
321+
return 0;
322+
}
323+
282324
mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self, bleio_address_obj_t *address, mp_float_t timeout) {
283-
mp_raise_NotImplementedError(NULL);
325+
// Stop any active scan.
326+
if (self->scan_results != NULL) {
327+
common_hal_bleio_adapter_stop_scan(self);
328+
}
329+
330+
struct ble_gap_conn_params conn_params = {
331+
.scan_itvl = MSEC_TO_UNITS(100, UNIT_0_625_MS),
332+
.scan_window = MSEC_TO_UNITS(100, UNIT_0_625_MS),
333+
.itvl_min = MSEC_TO_UNITS(15, UNIT_1_25_MS),
334+
.itvl_max = MSEC_TO_UNITS(300, UNIT_1_25_MS),
335+
.latency = 0,
336+
.supervision_timeout = MSEC_TO_UNITS(4000, UNIT_10_MS),
337+
.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
338+
.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN
339+
};
340+
341+
uint8_t own_addr_type;
342+
// TODO: Use a resolvable address if the peer has our key.
343+
CHECK_NIMBLE_ERROR(ble_hs_id_infer_auto(false, &own_addr_type));
344+
345+
ble_addr_t addr;
346+
_convert_address(address, &addr);
347+
348+
cp_task = xTaskGetCurrentTaskHandle();
349+
// Make sure we don't have a pending notification from a previous time. This
350+
// can happen if a previous wait timed out before the notification was given.
351+
xTaskNotifyStateClear(cp_task);
352+
CHECK_NIMBLE_ERROR(
353+
ble_gap_connect(own_addr_type, &addr,
354+
SEC_TO_UNITS(timeout, UNIT_1_MS) + 0.5f,
355+
&conn_params,
356+
_connect_event, self));
357+
358+
int error_code;
359+
CHECK_NOTIFY(xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200));
360+
// Negative values are error codes, connection handle otherwise.
361+
if (error_code < 0) {
362+
CHECK_BLE_ERROR(-error_code);
363+
}
364+
uint16_t conn_handle = error_code;
365+
366+
// TODO: If we have keys, then try and encrypt the connection.
367+
368+
// TODO: Negotiate for better PHY and data lengths since we are the central. These are
369+
// nice-to-haves so ignore any errors.
370+
371+
// Make the connection object and return it.
372+
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
373+
bleio_connection_internal_t *connection = &bleio_connections[i];
374+
if (connection->conn_handle == conn_handle) {
375+
connection->is_central = true;
376+
return bleio_connection_new_from_internal(connection);
377+
}
378+
}
284379

285380
mp_raise_bleio_BluetoothError(translate("Failed to connect: internal error"));
286381

ports/espressif/common-hal/_bleio/Characteristic.c

Lines changed: 140 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,40 +42,22 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self,
4242
bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm,
4343
mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo,
4444
const char *user_description) {
45-
mp_raise_NotImplementedError(NULL);
4645
self->service = service;
4746
self->uuid = uuid;
4847
self->handle = BLEIO_HANDLE_INVALID;
48+
self->cccd_handle = BLEIO_HANDLE_INVALID;
49+
self->sccd_handle = BLEIO_HANDLE_INVALID;
4950
self->props = props;
5051
self->read_perm = read_perm;
5152
self->write_perm = write_perm;
52-
self->initial_value_len = 0;
53-
self->initial_value = NULL;
54-
if (initial_value_bufinfo != NULL) {
55-
// Copy the initial value if it's on the heap. Otherwise it's internal and we may not be able
56-
// to allocate.
57-
self->initial_value_len = initial_value_bufinfo->len;
58-
if (gc_alloc_possible()) {
59-
if (gc_nbytes(initial_value_bufinfo->buf) > 0) {
60-
uint8_t *initial_value = m_malloc(self->initial_value_len, false);
61-
memcpy(initial_value, initial_value_bufinfo->buf, self->initial_value_len);
62-
self->initial_value = initial_value;
63-
} else {
64-
self->initial_value = initial_value_bufinfo->buf;
65-
}
66-
self->descriptor_list = mp_obj_new_list(0, NULL);
67-
} else {
68-
self->initial_value = initial_value_bufinfo->buf;
69-
self->descriptor_list = NULL;
70-
}
53+
common_hal_bleio_characteristic_set_value(self, initial_value_bufinfo);
54+
55+
if (gc_alloc_possible()) {
56+
self->descriptor_list = mp_obj_new_list(0, NULL);
57+
} else {
58+
self->descriptor_list = NULL;
7159
}
7260

73-
// const mp_int_t max_length_max = fixed_length ? BLE_GATTS_FIX_ATTR_LEN_MAX : BLE_GATTS_VAR_ATTR_LEN_MAX;
74-
// if (max_length < 0 || max_length > max_length_max) {
75-
// mp_raise_ValueError_varg(translate("max_length must be 0-%d when fixed_length is %s"),
76-
// max_length_max, fixed_length ? "True" : "False");
77-
// }
78-
// TODO: Implement this.
7961
self->max_length = max_length;
8062
self->fixed_length = fixed_length;
8163

@@ -97,17 +79,123 @@ bleio_service_obj_t *common_hal_bleio_characteristic_get_service(bleio_character
9779
return self->service;
9880
}
9981

82+
typedef struct {
83+
TaskHandle_t task;
84+
uint8_t *buf;
85+
uint16_t len;
86+
} _read_info_t;
87+
88+
STATIC int _read_cb(uint16_t conn_handle,
89+
const struct ble_gatt_error *error,
90+
struct ble_gatt_attr *attr,
91+
void *arg) {
92+
_read_info_t *read_info = (_read_info_t *)arg;
93+
switch (error->status) {
94+
case 0: {
95+
int len = MIN(read_info->len, OS_MBUF_PKTLEN(attr->om));
96+
os_mbuf_copydata(attr->om, attr->offset, len, read_info->buf);
97+
read_info->len = len;
98+
}
99+
MP_FALLTHROUGH;
100+
101+
default:
102+
#if CIRCUITPY_VERBOSE_BLE
103+
// For debugging.
104+
mp_printf(&mp_plat_print, "Read status: %d\n", error->status);
105+
#endif
106+
xTaskNotify(read_info->task, error->status, eSetValueWithOverwrite);
107+
break;
108+
}
109+
110+
return 0;
111+
}
112+
100113
size_t common_hal_bleio_characteristic_get_value(bleio_characteristic_obj_t *self, uint8_t *buf, size_t len) {
101-
// TODO: Implement this.
114+
// Do GATT operations only if this characteristic has been added to a registered service.
115+
if (self->handle == BLEIO_HANDLE_INVALID) {
116+
return 0;
117+
}
118+
uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection);
119+
if (common_hal_bleio_service_get_is_remote(self->service)) {
120+
_read_info_t read_info = {
121+
.task = xTaskGetCurrentTaskHandle(),
122+
.buf = buf,
123+
.len = len
124+
};
125+
CHECK_NIMBLE_ERROR(ble_gattc_read(conn_handle, self->handle, _read_cb, &read_info));
126+
int error_code;
127+
xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200);
128+
CHECK_BLE_ERROR(error_code);
129+
return read_info.len;
130+
} else {
131+
len = MIN(self->current_value_len, len);
132+
memcpy(buf, self->current_value, len);
133+
return len;
134+
}
135+
102136
return 0;
103137
}
104138

105139
size_t common_hal_bleio_characteristic_get_max_length(bleio_characteristic_obj_t *self) {
106140
return self->max_length;
107141
}
108142

143+
STATIC int _write_cb(uint16_t conn_handle,
144+
const struct ble_gatt_error *error,
145+
struct ble_gatt_attr *attr,
146+
void *arg) {
147+
TaskHandle_t task = (TaskHandle_t)arg;
148+
xTaskNotify(task, error->status, eSetValueWithOverwrite);
149+
150+
return 0;
151+
}
152+
109153
void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self, mp_buffer_info_t *bufinfo) {
110-
// TODO: Implement this.
154+
if (common_hal_bleio_service_get_is_remote(self->service)) {
155+
uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection);
156+
if ((self->props & CHAR_PROP_WRITE_NO_RESPONSE) != 0) {
157+
CHECK_NIMBLE_ERROR(ble_gattc_write_no_rsp_flat(conn_handle, self->handle, bufinfo->buf, bufinfo->len));
158+
} else {
159+
CHECK_NIMBLE_ERROR(ble_gattc_write_flat(conn_handle, self->handle, bufinfo->buf, bufinfo->len, _write_cb, xTaskGetCurrentTaskHandle()));
160+
int error_code;
161+
xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200);
162+
CHECK_BLE_ERROR(error_code);
163+
}
164+
} else {
165+
// Validate data length for local characteristics only.
166+
// TODO: Test this once we can get servers going.
167+
if (self->fixed_length && bufinfo->len != self->max_length) {
168+
mp_raise_ValueError(translate("Value length != required fixed length"));
169+
}
170+
if (bufinfo->len > self->max_length) {
171+
mp_raise_ValueError(translate("Value length > max_length"));
172+
}
173+
174+
if (bufinfo == NULL) {
175+
self->current_value_len = 0;
176+
ble_gatts_chr_updated(self->handle);
177+
return;
178+
}
179+
180+
self->current_value_len = bufinfo->len;
181+
// If we've already allocated an internal buffer or the provided buffer
182+
// is on the heap, then copy into the internal buffer.
183+
if (self->current_value_alloc > 0 || gc_nbytes(bufinfo->buf) > 0) {
184+
if (self->current_value_alloc < bufinfo->len) {
185+
self->current_value = m_realloc(self->current_value, bufinfo->len);
186+
// Get the number of bytes from the heap because it may be more
187+
// than the len due to gc block size.
188+
self->current_value_alloc = gc_nbytes(self->current_value);
189+
}
190+
memcpy(self->current_value, bufinfo->buf, bufinfo->len);
191+
} else {
192+
// Otherwise, use the provided buffer to delay any heap allocation.
193+
self->current_value = bufinfo->buf;
194+
self->current_value_alloc = 0;
195+
}
196+
197+
ble_gatts_chr_updated(self->handle);
198+
}
111199
}
112200

113201
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
118206
return self->props;
119207
}
120208

121-
void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self, bleio_descriptor_obj_t *descriptor) {
209+
void common_hal_bleio_characteristic_add_descriptor(bleio_characteristic_obj_t *self,
210+
bleio_descriptor_obj_t *descriptor) {
122211
// TODO: Implement this.
212+
213+
mp_obj_list_append(MP_OBJ_FROM_PTR(self->descriptor_list),
214+
MP_OBJ_FROM_PTR(descriptor));
123215
}
124216

125217
void common_hal_bleio_characteristic_set_cccd(bleio_characteristic_obj_t *self, bool notify, bool indicate) {
126-
// TODO: Implement this.
218+
if (self->cccd_handle == BLEIO_HANDLE_INVALID) {
219+
mp_raise_bleio_BluetoothError(translate("No CCCD for this Characteristic"));
220+
}
221+
222+
if (!common_hal_bleio_service_get_is_remote(self->service)) {
223+
mp_raise_bleio_RoleError(translate("Can't set CCCD on local Characteristic"));
224+
}
225+
226+
const uint16_t conn_handle = bleio_connection_get_conn_handle(self->service->connection);
227+
common_hal_bleio_check_connected(conn_handle);
228+
229+
uint16_t cccd_value =
230+
(notify ? 1 << 0 : 0) |
231+
(indicate ? 1 << 1: 0);
232+
233+
CHECK_NIMBLE_ERROR(ble_gattc_write_flat(conn_handle, self->cccd_handle, &cccd_value, 2, _write_cb, xTaskGetCurrentTaskHandle()));
234+
int error_code;
235+
xTaskNotifyWait(0, 0, (uint32_t *)&error_code, 200);
236+
CHECK_BLE_ERROR(error_code);
127237
}

ports/espressif/common-hal/_bleio/Characteristic.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,13 @@ typedef struct _bleio_characteristic_obj {
3939
// Will be MP_OBJ_NULL before being assigned to a Service.
4040
bleio_service_obj_t *service;
4141
bleio_uuid_obj_t *uuid;
42-
const uint8_t *initial_value;
43-
uint16_t initial_value_len;
42+
uint8_t *current_value;
43+
uint16_t current_value_len;
44+
// Our internal allocation length. If > 0, then current_value is managed by
45+
// this characteristic.
46+
uint16_t current_value_alloc;
4447
uint16_t max_length;
48+
uint16_t def_handle;
4549
uint16_t handle;
4650
bleio_characteristic_properties_t props;
4751
bleio_attribute_security_mode_t read_perm;

0 commit comments

Comments
 (0)