Skip to content

Commit fe6e03f

Browse files
authored
Merge pull request #6013 from tannewt/esp_ble_gatt_client
Add S3 GATT client support
2 parents 16c44a4 + 8958e7e commit fe6e03f

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
@@ -3788,6 +3800,7 @@ msgstr ""
37883800
msgid "non-Device in %q"
37893801
msgstr ""
37903802

3803+
#: ports/espressif/common-hal/_bleio/Connection.c
37913804
#: ports/nrf/common-hal/_bleio/Connection.c
37923805
msgid "non-UUID found in service_uuids_whitelist"
37933806
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)