• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybricks / pybricks-micropython / 18091272085

29 Sep 2025 08:31AM UTC coverage: 59.39% (-0.1%) from 59.49%
18091272085

push

github

laurensvalk
pbio/drv/bluetooth: Shut down gracefully.

Before the Bluetooth overhaul, we were just powering down. In the recent commits, this was not yet implemented, so the hub would stay connected or keep advertising once turned off and charging.

Poweroff is now back, but we also gracefully disconnect first to be nice to Pybricks Code.

5 of 19 new or added lines in 4 files covered. (26.32%)

1 existing line in 1 file now uncovered.

4228 of 7119 relevant lines covered (59.39%)

20492664.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

37.09
/lib/pbio/drv/bluetooth/bluetooth_btstack.c
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2020-2023 The Pybricks Authors
3

4
// Bluetooth driver using BlueKitchen BTStack.
5

6
#include <pbdrv/config.h>
7

8
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK
9

10
#include <assert.h>
11
#include <inttypes.h>
12

13
#include <ble/gatt-service/device_information_service_server.h>
14
#include <ble/gatt-service/nordic_spp_service_server.h>
15
#include <btstack.h>
16

17
#include <pbdrv/bluetooth.h>
18
#include <pbdrv/clock.h>
19

20
#include <pbio/os.h>
21
#include <pbio/protocol.h>
22
#include <pbio/version.h>
23

24
#include "bluetooth_btstack.h"
25
#include "genhdr/pybricks_service.h"
26
#include "hci_transport_h4.h"
27
#include "pybricks_service_server.h"
28

29
#ifdef PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND
30
#define HUB_KIND PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND
31
#else
32
#error "PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND is required"
33
#endif
34

35
// location of product variant in bootloader flash memory of Technic Large hubs
36
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_VARIANT_ADDR
37
#define HUB_VARIANT (*(const uint16_t *)PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_VARIANT_ADDR)
38
#else
39
#define HUB_VARIANT 0x0000
40
#endif
41

42
#define DEBUG 0
43

44
#if DEBUG
45
#include <pbdrv/../../drv/uart/uart_debug_first_port.h>
46
#define DEBUG_PRINT pbdrv_uart_debug_printf
47
#else
48
#define DEBUG_PRINT(...)
49
#endif
50

51
typedef enum {
52
    CON_STATE_NONE,
53
    CON_STATE_WAIT_ADV_IND,
54
    CON_STATE_WAIT_SCAN_RSP,
55
    CON_STATE_WAIT_CONNECT,
56
    CON_STATE_WAIT_BONDING,
57
    CON_STATE_CONNECTED, // End of connection state machine
58
    CON_STATE_WAIT_DISCOVER_CHARACTERISTICS,
59
    CON_STATE_WAIT_ENABLE_NOTIFICATIONS,
60
    CON_STATE_DISCOVERY_AND_NOTIFICATIONS_COMPLETE, // End of discovery state machine, goes back to CONNECTED
61
    CON_STATE_WAIT_READ_CHARACTERISTIC,
62
    CON_STATE_READ_CHARACTERISTIC_COMPLETE, // End of read state machine, goes back to CONNECTED
63
    CON_STATE_WAIT_DISCONNECT,
64
} con_state_t;
65

66
typedef enum {
67
    DISCONNECT_REASON_NONE,
68
    DISCONNECT_REASON_TIMEOUT,
69
    DISCONNECT_REASON_CONNECT_FAILED,
70
    DISCONNECT_REASON_DISCOVER_SERVICE_FAILED,
71
    DISCONNECT_REASON_DISCOVER_CHARACTERISTIC_FAILED,
72
    DISCONNECT_REASON_CONFIGURE_CHARACTERISTIC_FAILED,
73
    DISCONNECT_REASON_SEND_SUBSCRIBE_PORT_0_FAILED,
74
    DISCONNECT_REASON_SEND_SUBSCRIBE_PORT_1_FAILED,
75
} disconnect_reason_t;
76

77
// REVISIT: Most of these states should go into pbdrv_bluetooth_peripheral_t
78
typedef struct {
79
    gatt_client_notification_t notification;
80
    con_state_t con_state;
81
    disconnect_reason_t disconnect_reason;
82
    /**
83
     *  Character information used during discovery. Assuming properties and chars
84
     *  are set up such that only one char is discovered at a time
85
     */
86
    gatt_client_characteristic_t current_char;
87
    uint8_t btstack_error;
88
} pup_handset_t;
89

90
// hub name goes in special section so that it can be modified when flashing firmware
91
#if !PBIO_TEST_BUILD
92
__attribute__((section(".name")))
93
#endif
94
char pbdrv_bluetooth_hub_name[16] = "Pybricks Hub";
95

96
static hci_con_handle_t le_con_handle = HCI_CON_HANDLE_INVALID;
97
static hci_con_handle_t pybricks_con_handle = HCI_CON_HANDLE_INVALID;
98
static hci_con_handle_t uart_con_handle = HCI_CON_HANDLE_INVALID;
99
static pup_handset_t handset;
100
static uint8_t *event_packet;
101
static const pbdrv_bluetooth_btstack_platform_data_t *pdata = &pbdrv_bluetooth_btstack_platform_data;
102

103
// note on baud rate: with a 48MHz clock, 3000000 baud is the highest we can
104
// go with LL_USART_OVERSAMPLING_16. With LL_USART_OVERSAMPLING_8 we could go
105
// to 4000000, which is the max rating of the CC2564C.
106

107
static const hci_transport_config_uart_t config = {
108
    .type = HCI_TRANSPORT_CONFIG_UART,
109
    .baudrate_init = 115200,
110
    .baudrate_main = 3000000,
111
    .flowcontrol = 1,
112
    .device_name = NULL,
113
};
114

115
/**
116
 * Converts BTStack error to most appropriate PBIO error.
117
 * @param [in]  status      The BTStack error
118
 * @return                  The PBIO error.
119
 */
120
static pbio_error_t att_error_to_pbio_error(uint8_t status) {
×
121
    switch (status) {
×
122
        case ATT_ERROR_SUCCESS:
×
123
            return PBIO_SUCCESS;
×
124
        case ATT_ERROR_HCI_DISCONNECT_RECEIVED:
×
125
            return PBIO_ERROR_NO_DEV;
×
126
        case ATT_ERROR_TIMEOUT:
×
127
            return PBIO_ERROR_TIMEDOUT;
×
128
        default:
×
129
            return PBIO_ERROR_FAILED;
×
130
    }
131
}
132

133
static pbio_pybricks_error_t pybricks_data_received(hci_con_handle_t tx_con_handle, const uint8_t *data, uint16_t size) {
1✔
134
    if (pbdrv_bluetooth_receive_handler) {
1✔
135
        return pbdrv_bluetooth_receive_handler(data, size);
1✔
136
    }
137

138
    return ATT_ERROR_UNLIKELY_ERROR;
×
139
}
140

141
static void pybricks_configured(hci_con_handle_t tx_con_handle, uint16_t value) {
2✔
142
    pybricks_con_handle = value ? tx_con_handle : HCI_CON_HANDLE_INVALID;
2✔
143
}
2✔
144

145
static pbio_os_state_t bluetooth_thread_state;
146
static pbio_os_state_t bluetooth_thread_err;
147

148
/**
149
 * Runs tasks that may be waiting for event.
150
 *
151
 * This drives the common Bluetooth process synchronously with incoming events
152
 * so that each of the protothreads can wait for states.
153
 *
154
 * @param [in]  packet  Pointer to the raw packet data.
155
 */
156
static void propagate_event(uint8_t *packet) {
31,603✔
157

158
    event_packet = packet;
31,603✔
159

160
    if (bluetooth_thread_err == PBIO_ERROR_AGAIN) {
31,603✔
161
        bluetooth_thread_err = pbdrv_bluetooth_process_thread(&bluetooth_thread_state, NULL);
31,603✔
162
    }
163

164
    event_packet = NULL;
31,603✔
165
}
31,603✔
166

167
static void nordic_spp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
×
168
    switch (packet_type) {
×
169
        case HCI_EVENT_PACKET:
×
170
            if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META) {
×
171
                break;
×
172
            }
173

174
            switch (hci_event_gattservice_meta_get_subevent_code(packet)) {
×
175
                case GATTSERVICE_SUBEVENT_SPP_SERVICE_CONNECTED:
×
176
                    uart_con_handle = gattservice_subevent_spp_service_connected_get_con_handle(packet);
×
177
                    break;
×
178
                case GATTSERVICE_SUBEVENT_SPP_SERVICE_DISCONNECTED:
×
179
                    uart_con_handle = HCI_CON_HANDLE_INVALID;
×
180
                    break;
×
181
                default:
×
182
                    break;
×
183
            }
184

185
            break;
×
186
        case RFCOMM_DATA_PACKET:
×
187
            // Not implemented.
188
            break;
×
189
        default:
×
190
            break;
×
191
    }
192

193
    propagate_event(packet);
×
194
}
×
195

196
// currently, this function just handles the Powered Up handset control.
197
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
164✔
198

199
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
164✔
200

201
    switch (hci_event_packet_get_type(packet)) {
164✔
202
        case GATT_EVENT_SERVICE_QUERY_RESULT: {
×
203
            // Service discovery not used.
204
            gatt_client_service_t service;
205
            gatt_event_service_query_result_get_service(packet, &service);
×
206
            break;
×
207
        }
208
        case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: {
×
209
            gatt_client_characteristic_t found_char;
210
            gatt_event_characteristic_query_result_get_characteristic(packet, &found_char);
×
211
            // We only care about the one characteristic that has at least the requested properties.
212
            if ((found_char.properties & peri->char_now->properties) == peri->char_now->properties) {
×
213
                peri->char_now->handle = found_char.value_handle;
×
214
                gatt_event_characteristic_query_result_get_characteristic(packet, &handset.current_char);
×
215
            }
216
            break;
×
217
        }
218
        case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT: {
×
219
            hci_con_handle_t handle = gatt_event_characteristic_value_query_result_get_handle(packet);
×
220
            uint16_t value_handle = gatt_event_characteristic_value_query_result_get_value_handle(packet);
×
221
            uint16_t value_length = gatt_event_characteristic_value_query_result_get_value_length(packet);
×
222
            if (peri->con_handle == handle && peri->char_now->handle == value_handle) {
×
223
                peri->char_now->value_len = gatt_event_characteristic_value_query_result_get_value_length(packet);
×
224
                memcpy(peri->char_now->value, gatt_event_characteristic_value_query_result_get_value(packet), value_length);
×
225
            }
226
            break;
×
227
        }
228
        case GATT_EVENT_QUERY_COMPLETE:
×
229
            if (handset.con_state == CON_STATE_WAIT_READ_CHARACTERISTIC) {
×
230
                // Done reading characteristic.
231
                handset.con_state = CON_STATE_READ_CHARACTERISTIC_COMPLETE;
×
232
            } else if (handset.con_state == CON_STATE_WAIT_DISCOVER_CHARACTERISTICS) {
×
233

234
                // Discovered characteristics, ready enable notifications.
235
                if (!peri->char_now->request_notification) {
×
236
                    // If no notification is requested, we are done.
237
                    handset.con_state = CON_STATE_DISCOVERY_AND_NOTIFICATIONS_COMPLETE;
×
238
                    break;
×
239
                }
240

241
                handset.btstack_error = gatt_client_write_client_characteristic_configuration(
×
242
                    packet_handler, peri->con_handle, &handset.current_char,
×
243
                    GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
244
                if (handset.btstack_error == ERROR_CODE_SUCCESS) {
×
245
                    gatt_client_listen_for_characteristic_value_updates(
×
246
                        &handset.notification, packet_handler, peri->con_handle, &handset.current_char);
×
247
                    handset.con_state = CON_STATE_WAIT_ENABLE_NOTIFICATIONS;
×
248
                } else {
249
                    // configuration failed for some reason, so disconnect
250
                    gap_disconnect(peri->con_handle);
×
251
                    handset.con_state = CON_STATE_WAIT_DISCONNECT;
×
252
                    handset.disconnect_reason = DISCONNECT_REASON_CONFIGURE_CHARACTERISTIC_FAILED;
×
253
                }
254
            } else if (handset.con_state == CON_STATE_WAIT_ENABLE_NOTIFICATIONS) {
×
255
                // Done enabling notifications.
256
                handset.con_state = CON_STATE_DISCOVERY_AND_NOTIFICATIONS_COMPLETE;
×
257
            }
258
            break;
×
259

260
        case GATT_EVENT_NOTIFICATION: {
×
261
            if (gatt_event_notification_get_handle(packet) != peri->con_handle) {
×
262
                break;
×
263
            }
264
            if (peri->config->notification_handler) {
×
265
                uint16_t length = gatt_event_notification_get_value_length(packet);
×
266
                const uint8_t *value = gatt_event_notification_get_value(packet);
×
267
                peri->config->notification_handler(value, length);
×
268
            }
269
            break;
×
270
        }
271

272
        case HCI_EVENT_LE_META:
2✔
273
            if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) {
2✔
274
                break;
1✔
275
            }
276

277
            // HCI_ROLE_SLAVE means the connecting device is the central and the hub is the peripheral
278
            // HCI_ROLE_MASTER means the connecting device is the peripheral and the hub is the central.
279
            if (hci_subevent_le_connection_complete_get_role(packet) == HCI_ROLE_SLAVE) {
1✔
280
                le_con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
1✔
281

282
                // don't start advertising again on disconnect
283
                gap_advertisements_enable(false);
1✔
284
            } else {
285
                // If we aren't waiting for a peripheral connection, this must be a different connection.
286
                if (handset.con_state != CON_STATE_WAIT_CONNECT) {
×
287
                    break;
×
288
                }
289

290
                peri->con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
×
291

292
                // Request pairing if needed for this device, otherwise set
293
                // connection state to complete.
294
                if (peri->config->options & PBDRV_BLUETOOTH_PERIPHERAL_OPTIONS_PAIR) {
×
295
                    // Re-encryption doesn't seem to work reliably, so we just
296
                    // delete the bond and start over.
297
                    gap_delete_bonding(peri->bdaddr_type, peri->bdaddr);
×
298
                    sm_request_pairing(peri->con_handle);
×
299
                    handset.con_state = CON_STATE_WAIT_BONDING;
×
300
                } else {
301
                    handset.con_state = CON_STATE_CONNECTED;
×
302
                }
303
            }
304

305
            break;
1✔
306

307
        case HCI_EVENT_DISCONNECTION_COMPLETE:
×
308
            if (hci_event_disconnection_complete_get_connection_handle(packet) == le_con_handle) {
×
309
                le_con_handle = HCI_CON_HANDLE_INVALID;
×
310
                pybricks_con_handle = HCI_CON_HANDLE_INVALID;
×
311
                uart_con_handle = HCI_CON_HANDLE_INVALID;
×
312
            } else if (hci_event_disconnection_complete_get_connection_handle(packet) == peri->con_handle) {
×
313
                gatt_client_stop_listening_for_characteristic_value_updates(&handset.notification);
×
314
                peri->con_handle = HCI_CON_HANDLE_INVALID;
×
315
                handset.con_state = CON_STATE_NONE;
×
316
            }
317

318
            break;
×
319

320
        case GAP_EVENT_ADVERTISING_REPORT: {
×
321
            uint8_t event_type = gap_event_advertising_report_get_advertising_event_type(packet);
×
322
            uint8_t data_length = gap_event_advertising_report_get_data_length(packet);
×
323
            const uint8_t *data = gap_event_advertising_report_get_data(packet);
×
324
            bd_addr_t address;
325

326
            gap_event_advertising_report_get_address(packet, address);
×
327

328
            if (pbdrv_bluetooth_observe_callback) {
×
329
                int8_t rssi = gap_event_advertising_report_get_rssi(packet);
×
330
                pbdrv_bluetooth_observe_callback(event_type, data, data_length, rssi);
×
331
            }
332

333
            if (handset.con_state == CON_STATE_WAIT_ADV_IND) {
×
334
                // Match advertisement data against context-specific filter.
335
                pbdrv_bluetooth_ad_match_result_flags_t adv_flags = PBDRV_BLUETOOTH_AD_MATCH_NONE;
×
336
                if (peri->config->match_adv) {
×
337
                    adv_flags = peri->config->match_adv(event_type, data, NULL, address, peri->bdaddr);
×
338
                }
339

340
                if (adv_flags & PBDRV_BLUETOOTH_AD_MATCH_VALUE) {
×
341
                    if (adv_flags & PBDRV_BLUETOOTH_AD_MATCH_ADDRESS) {
×
342
                        // This was the same device as last time. If the scan response
343
                        // didn't match before, it probably won't match now and we
344
                        // should try a different device.
345
                        break;
×
346
                    }
347
                    // Advertising data matched, prepare for scan response.
348
                    memcpy(peri->bdaddr, address, sizeof(bd_addr_t));
×
349
                    peri->bdaddr_type = gap_event_advertising_report_get_address_type(packet);
×
350
                    handset.con_state = CON_STATE_WAIT_SCAN_RSP;
×
351
                }
352
            } else if (handset.con_state == CON_STATE_WAIT_SCAN_RSP) {
×
353

354
                char *detected_name = (char *)&data[2];
×
355
                const uint8_t max_len = sizeof(peri->name);
×
356

357
                pbdrv_bluetooth_ad_match_result_flags_t rsp_flags = PBDRV_BLUETOOTH_AD_MATCH_NONE;
×
358
                if (peri->config->match_adv_rsp) {
×
359
                    rsp_flags = peri->config->match_adv_rsp(event_type, NULL, detected_name, address, peri->bdaddr);
×
360
                }
361
                if ((rsp_flags & PBDRV_BLUETOOTH_AD_MATCH_VALUE) && (rsp_flags & PBDRV_BLUETOOTH_AD_MATCH_ADDRESS)) {
×
362

363
                    if (rsp_flags & PBDRV_BLUETOOTH_AD_MATCH_NAME_FAILED) {
×
364
                        // A name was requested but it doesn't match, so go back to scanning stage.
365
                        handset.con_state = CON_STATE_WAIT_ADV_IND;
×
366
                        break;
×
367
                    }
368

369
                    if (data[1] == BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME) {
×
370
                        memcpy(peri->name, detected_name, max_len);
×
371
                    }
372

373
                    gap_stop_scan();
×
374
                    handset.btstack_error = gap_connect(peri->bdaddr, peri->bdaddr_type);
×
375

376
                    if (handset.btstack_error == ERROR_CODE_SUCCESS) {
×
377
                        handset.con_state = CON_STATE_WAIT_CONNECT;
×
378
                    } else {
379
                        handset.con_state = CON_STATE_NONE;
×
380
                    }
381
                }
382
            }
383

384
            break;
×
385
        }
386

387
        default:
162✔
388
            break;
162✔
389
    }
390

391
    propagate_event(packet);
164✔
392
}
164✔
393

394
// Security manager callbacks. This is adapted from the BTstack examples.
395
static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
2✔
396
    UNUSED(channel);
397
    UNUSED(size);
398

399
    if (packet_type != HCI_EVENT_PACKET) {
2✔
400
        return;
×
401
    }
402

403
    bd_addr_t addr;
404
    bd_addr_type_t addr_type;
405

406
    switch (hci_event_packet_get_type(packet)) {
2✔
407
        case SM_EVENT_IDENTITY_RESOLVING_STARTED:
1✔
408
            DEBUG_PRINT("IDENTITY_RESOLVING_STARTED\n");
409
            break;
1✔
410
        case SM_EVENT_IDENTITY_RESOLVING_FAILED:
1✔
411
            DEBUG_PRINT("IDENTITY_RESOLVING_FAILED\n");
412
            break;
1✔
413
        case SM_EVENT_JUST_WORKS_REQUEST:
×
414
            // This is the only expected path for known compatible peripherals.
415
            DEBUG_PRINT("Just works requested\n");
416
            sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
×
417
            break;
×
418
        case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
×
419
            DEBUG_PRINT("Confirming numeric comparison: %" PRIu32 "\n", sm_event_numeric_comparison_request_get_passkey(packet));
420
            sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
×
421
            break;
×
422
        case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
×
423
            DEBUG_PRINT("Display Passkey: %" PRIu32 "\n", sm_event_passkey_display_number_get_passkey(packet));
424
            break;
×
425
        case SM_EVENT_PASSKEY_INPUT_NUMBER: {
×
426
            const uint32_t passkey = 123456U;
×
427
            DEBUG_PRINT("Passkey Input requested\n");
428
            DEBUG_PRINT("Sending fixed passkey %" PRIu32 "\n", passkey);
429
            sm_passkey_input(sm_event_passkey_input_number_get_handle(packet), passkey);
×
430
            break;
×
431
        }
432
        case SM_EVENT_PAIRING_STARTED:
×
433
            DEBUG_PRINT("Pairing started\n");
434
            break;
×
435
        case SM_EVENT_PAIRING_COMPLETE:
×
436
            switch (sm_event_pairing_complete_get_status(packet)) {
×
437
                case ERROR_CODE_SUCCESS:
×
438
                    // This is the final state for known compatible peripherals
439
                    // with bonding under normal circumstances.
440
                    DEBUG_PRINT("Pairing complete, success\n");
441
                    handset.con_state = CON_STATE_CONNECTED;
×
442
                    break;
×
443
                case ERROR_CODE_CONNECTION_TIMEOUT:
×
444
                // fall through to disconnect.
445
                case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
446
                // fall through to disconnect.
447
                case ERROR_CODE_AUTHENTICATION_FAILURE:
448
                // fall through to disconnect.
449
                default:
450
                    DEBUG_PRINT(
451
                        "Pairing completed with error code %u and reason %u\n",
452
                        sm_event_pairing_complete_get_status(packet),
453
                        sm_event_pairing_complete_get_reason(packet)
454
                        );
455
                    gap_disconnect(sm_event_reencryption_complete_get_handle(packet));
×
456
                    break;
×
457
            }
458
            break;
×
459
        case SM_EVENT_REENCRYPTION_STARTED:
×
460
            sm_event_reencryption_complete_get_address(packet, addr);
×
461
            DEBUG_PRINT("Bonding information exists for addr type %u, identity addr %s -> start re-encryption\n",
462
                sm_event_reencryption_started_get_addr_type(packet), bd_addr_to_str(addr));
463
            break;
×
464
        case SM_EVENT_REENCRYPTION_COMPLETE:
×
465
            // BTstack supports re-encryption, but it gets the hub in a hung
466
            // state with certain peripherals. Instead we just delete the bond
467
            // just before connecting. If we still get here, we should delete
468
            // the bond and disconnect, causing the user program stop without
469
            // hanging. We rely on HCI_EVENT_DISCONNECTION_COMPLETE to set
470
            // the connection state appropriately to unblock the task.
471
            DEBUG_PRINT("Re-encryption complete. Handling not implemented.\n");
472
            sm_event_reencryption_complete_get_address(packet, addr);
×
473
            addr_type = sm_event_reencryption_started_get_addr_type(packet);
×
474
            gap_delete_bonding(addr_type, addr);
×
475
            gap_disconnect(sm_event_reencryption_complete_get_handle(packet));
×
476
            break;
×
477
        default:
×
478
            break;
×
479
    }
480
}
481

482
// ATT Client Read Callback for Dynamic Data
483
// - if buffer == NULL, don't copy data, just return size of value
484
// - if buffer != NULL, copy data and return number bytes copied
485
// @param offset defines start of attribute value
486
static uint16_t att_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) {
×
487
    uint16_t att_value_len;
488

489
    switch (attribute_handle) {
×
490
        case ATT_CHARACTERISTIC_GAP_DEVICE_NAME_01_VALUE_HANDLE:
×
491
            att_value_len = strlen(pbdrv_bluetooth_hub_name);
×
492
            if (buffer) {
×
493
                memcpy(buffer, pbdrv_bluetooth_hub_name, att_value_len);
×
494
            }
495
            return att_value_len;
×
496

497
        default:
×
498
            return 0;
×
499
    }
500
}
501

502
static void init_advertising_data(void) {
1✔
503
    bd_addr_t null_addr = { };
1✔
504
    gap_advertisements_set_params(0x30, 0x30, PBDRV_BLUETOOTH_AD_TYPE_ADV_IND, 0x00, null_addr, 0x07, 0x00);
1✔
505

506
    static const uint8_t adv_data[] = {
507
        // Flags general discoverable, BR/EDR not supported
508
        2, BLUETOOTH_DATA_TYPE_FLAGS, 0x06,
509
        // Pybricks service
510
        17, BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
511
        0xef, 0xae, 0xe4, 0x51, 0x80, 0x6d, 0xf4, 0x89, 0xda, 0x46, 0x80, 0x82, 0x01, 0x00, 0xf5, 0xc5,
512
        // Tx Power
513
        2, BLUETOOTH_DATA_TYPE_TX_POWER_LEVEL, 0,
514
    };
515

516
    _Static_assert(sizeof(adv_data) <= 31, "31 octet max");
517

518
    gap_advertisements_set_data(sizeof(adv_data), (uint8_t *)adv_data);
1✔
519

520
    static uint8_t scan_resp_data[31] = {
521
        10, BLUETOOTH_DATA_TYPE_SERVICE_DATA,
522
        // used to identify which hub - Device Information Service (DIS).
523
        // 0x2A50 - service UUID - PnP ID characteristic UUID
524
        // 0x01 - Vendor ID Source Field - Bluetooth SIG-assigned ID
525
        // 0x0397 - Vendor ID Field - LEGO company identifier
526
        // 0x00XX - Product ID Field - hub kind
527
        // 0x00XX - Product Version Field - product variant
528
        0x50, 0x2a, 0x01, 0x97, 0x03, HUB_KIND, 0x00, 0x00, 0x00,
529
        0, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME,
530
    };
531

532
    scan_resp_data[9] = HUB_VARIANT;
1✔
533

534
    uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name);
1✔
535
    scan_resp_data[11] = hub_name_len + 1;
1✔
536
    memcpy(&scan_resp_data[13], pbdrv_bluetooth_hub_name, hub_name_len);
1✔
537
    _Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max");
538

539
    gap_scan_response_set_data(13 + hub_name_len, scan_resp_data);
1✔
540
}
1✔
541

542
pbio_error_t pbdrv_bluetooth_start_advertising_func(pbio_os_state_t *state, void *context) {
6✔
543

544
    PBIO_OS_ASYNC_BEGIN(state);
6✔
545

546
    init_advertising_data();
1✔
547
    gap_advertisements_enable(true);
1✔
548

549
    PBIO_OS_AWAIT_UNTIL(state, event_packet && HCI_EVENT_IS_COMMAND_COMPLETE(event_packet, hci_le_set_advertising_data));
6✔
550

551
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
1✔
552
}
553

554
pbio_error_t pbdrv_bluetooth_stop_advertising_func(pbio_os_state_t *state, void *context) {
×
555

556
    PBIO_OS_ASYNC_BEGIN(state);
×
557

558
    gap_advertisements_enable(false);
×
559
    pbdrv_bluetooth_is_broadcasting = false;
×
560
    // REVISIT: use callback to await operation
561

562
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
563
}
564

565
bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection) {
31,606✔
566
    if (connection == PBDRV_BLUETOOTH_CONNECTION_LE && le_con_handle != HCI_CON_HANDLE_INVALID) {
31,606✔
567
        return true;
×
568
    }
569

570
    if (connection == PBDRV_BLUETOOTH_CONNECTION_PYBRICKS && pybricks_con_handle != HCI_CON_HANDLE_INVALID) {
31,606✔
571
        return true;
11✔
572
    }
573

574
    if (connection == PBDRV_BLUETOOTH_CONNECTION_UART && uart_con_handle != HCI_CON_HANDLE_INVALID) {
31,595✔
575
        return true;
×
576
    }
577

578
    if (connection == PBDRV_BLUETOOTH_CONNECTION_PERIPHERAL && peripheral_singleton.con_handle != HCI_CON_HANDLE_INVALID) {
31,595✔
579
        return true;
×
580
    }
581

582
    return false;
31,595✔
583
}
584

585
typedef struct {
586
    const uint8_t *data;
587
    uint16_t size;
588
    bool done;
589
} send_data_t;
590

591
static void pybricks_on_ready_to_send(void *context) {
2✔
592
    send_data_t *send = context;
2✔
593
    pybricks_service_server_send(pybricks_con_handle, send->data, send->size);
2✔
594
    send->done = true;
2✔
595
    pbio_os_request_poll();
2✔
596
}
2✔
597

598
pbio_error_t pbdrv_bluetooth_send_pybricks_value_notification(pbio_os_state_t *state, const uint8_t *data, uint16_t size) {
4✔
599

600
    static send_data_t send_data;
601

602
    static btstack_context_callback_registration_t send_request = {
603
        .callback = pybricks_on_ready_to_send,
604
        .context = &send_data,
605
    };
606
    PBIO_OS_ASYNC_BEGIN(state);
4✔
607

608
    send_data.data = data;
2✔
609
    send_data.size = size;
2✔
610
    send_data.done = false;
2✔
611
    pybricks_service_server_request_can_send_now(&send_request, pybricks_con_handle);
2✔
612
    PBIO_OS_AWAIT_UNTIL(state, send_data.done);
4✔
613

614
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
2✔
615
}
616

617
static void start_observing(void) {
×
618
    gap_set_scan_params(0, 0x30, 0x30, 0);
×
619
    gap_start_scan();
×
620
}
×
621

622
pbio_error_t pbdrv_bluetooth_peripheral_scan_and_connect_func(pbio_os_state_t *state, void *context) {
×
623

624
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
625

626
    // Scan and connect timeout, if applicable.
627
    bool timed_out = peri->config->timeout && pbio_os_timer_is_expired(&peri->timer);
×
628

629
    // Operation can be explicitly cancelled or automatically on inactivity.
630
    if (!peri->cancel) {
×
631
        peri->cancel = pbio_os_timer_is_expired(&peri->watchdog);
×
632
    }
633

634
    PBIO_OS_ASYNC_BEGIN(state);
×
635

636
    memset(&handset, 0, sizeof(handset));
×
637

638
    peri->con_handle = HCI_CON_HANDLE_INVALID;
×
639

640
    // active scanning to get scan response data.
641
    // scan interval: 48 * 0.625ms = 30ms
642
    gap_set_scan_params(1, 0x30, 0x30, 0);
×
643
    gap_start_scan();
×
644
    handset.con_state = CON_STATE_WAIT_ADV_IND;
×
645

646
    PBIO_OS_AWAIT_UNTIL(state, ({
×
647
        if (peri->cancel || timed_out) {
648
            if (handset.con_state == CON_STATE_WAIT_ADV_IND || handset.con_state == CON_STATE_WAIT_SCAN_RSP) {
649
                gap_stop_scan();
650
            } else if (handset.con_state == CON_STATE_WAIT_CONNECT) {
651
                gap_connect_cancel();
652
            } else if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
653
                gap_disconnect(peri->con_handle);
654
            }
655
            handset.con_state = CON_STATE_NONE;
656
            return timed_out ? PBIO_ERROR_TIMEDOUT : PBIO_ERROR_CANCELED;
657
        }
658
        // if there is any failure to connect or error while enumerating
659
        // attributes, con_state will be set to CON_STATE_NONE
660
        if (handset.con_state == CON_STATE_NONE) {
661
            return PBIO_ERROR_FAILED;
662
        }
663
        handset.con_state == CON_STATE_CONNECTED;
664
    }));
665

666
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
667
}
668

669
pbio_error_t pbdrv_bluetooth_peripheral_discover_characteristic_func(pbio_os_state_t *state, void *context) {
×
670

671
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
672

673
    PBIO_OS_ASYNC_BEGIN(state);
×
674

675
    if (handset.con_state != CON_STATE_CONNECTED) {
×
676
        return PBIO_ERROR_FAILED;
×
677
    }
678

679
    handset.con_state = CON_STATE_WAIT_DISCOVER_CHARACTERISTICS;
×
680
    uint16_t handle_max = peri->char_now->handle_max ? peri->char_now->handle_max : 0xffff;
×
681
    handset.btstack_error = peri->char_now->uuid16 ?
×
682
        gatt_client_discover_characteristics_for_handle_range_by_uuid16(
×
683
        packet_handler, peri->con_handle, 0x0001, handle_max, peri->char_now->uuid16) :
×
684
        gatt_client_discover_characteristics_for_handle_range_by_uuid128(
×
685
        packet_handler, peri->con_handle, 0x0001, handle_max, peri->char_now->uuid128);
×
686

687
    if (handset.btstack_error != ERROR_CODE_SUCCESS) {
×
688
        // configuration failed for some reason, so disconnect
689
        gap_disconnect(peri->con_handle);
×
690
        handset.con_state = CON_STATE_WAIT_DISCONNECT;
×
691
        handset.disconnect_reason = DISCONNECT_REASON_DISCOVER_CHARACTERISTIC_FAILED;
×
692
    }
693

694
    PBIO_OS_AWAIT_UNTIL(state, ({
×
695
        // if there is any error while enumerating
696
        // attributes, con_state will be set to CON_STATE_NONE
697
        if (handset.con_state == CON_STATE_NONE) {
698
            return PBIO_ERROR_FAILED;
699
        }
700
        handset.con_state == CON_STATE_DISCOVERY_AND_NOTIFICATIONS_COMPLETE;
701
    }));
702

703
    // State state back to simply connected, so we can discover other characteristics.
704
    handset.con_state = CON_STATE_CONNECTED;
×
705

706
    PBIO_OS_ASYNC_END(peri->char_now->handle ? PBIO_SUCCESS : PBIO_ERROR_FAILED);
×
707
}
708

709
pbio_error_t pbdrv_bluetooth_peripheral_read_characteristic_func(pbio_os_state_t *state, void *context) {
×
710

711
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
712

713
    PBIO_OS_ASYNC_BEGIN(state);
×
714

715
    if (handset.con_state != CON_STATE_CONNECTED) {
×
716
        return PBIO_ERROR_FAILED;
×
717
    }
718

719
    gatt_client_characteristic_t characteristic = {
×
720
        .value_handle = peri->char_now->handle,
×
721
    };
722
    handset.btstack_error = gatt_client_read_value_of_characteristic(packet_handler, peri->con_handle, &characteristic);
×
723

724
    if (handset.btstack_error == ERROR_CODE_SUCCESS) {
×
725
        handset.con_state = CON_STATE_WAIT_READ_CHARACTERISTIC;
×
726
    } else {
727
        // configuration failed for some reason, so disconnect
728
        gap_disconnect(peri->con_handle);
×
729
        handset.con_state = CON_STATE_WAIT_DISCONNECT;
×
730
        handset.disconnect_reason = DISCONNECT_REASON_DISCOVER_CHARACTERISTIC_FAILED;
×
731
    }
732

733
    PBIO_OS_AWAIT_UNTIL(state, ({
×
734
        // if there is any error while reading, con_state will be set to CON_STATE_NONE
735
        if (handset.con_state == CON_STATE_NONE) {
736
            return PBIO_ERROR_FAILED;
737
        }
738
        handset.con_state == CON_STATE_READ_CHARACTERISTIC_COMPLETE;
739
    }));
740

741
    // State state back to simply connected, so we can discover other characteristics.
742
    handset.con_state = CON_STATE_CONNECTED;
×
743

744
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
745
}
746

747
pbio_error_t pbdrv_bluetooth_peripheral_write_characteristic_func(pbio_os_state_t *state, void *context) {
×
748

749
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
750

751
    PBIO_OS_ASYNC_BEGIN(state);
×
752

753
    uint8_t err = gatt_client_write_value_of_characteristic(packet_handler,
×
754
        peri->con_handle,
×
755
        pbdrv_bluetooth_char_write_handle,
756
        pbdrv_bluetooth_char_write_size,
757
        pbdrv_bluetooth_char_write_data
758
        );
759

760
    if (err != ERROR_CODE_SUCCESS) {
×
761
        return PBIO_ERROR_FAILED;
×
762
    }
763

764
    // NB: Value buffer must remain valid until GATT_EVENT_QUERY_COMPLETE, so
765
    // this wait is not cancelable.
766
    PBIO_OS_AWAIT_UNTIL(state, ({
×
767
        if (peri->con_handle == HCI_CON_HANDLE_INVALID) {
768
            // disconnected
769
            return PBIO_ERROR_NO_DEV;
770
        }
771
        event_packet &&
772
        hci_event_packet_get_type(event_packet) == GATT_EVENT_QUERY_COMPLETE &&
773
        gatt_event_query_complete_get_handle(event_packet) == peri->con_handle;
774
    }));
775

776
    PBIO_OS_ASYNC_END(att_error_to_pbio_error(gatt_event_query_complete_get_att_status(event_packet)));
×
777
}
778

779
pbio_error_t pbdrv_bluetooth_peripheral_disconnect_func(pbio_os_state_t *state, void *context) {
×
780

781
    PBIO_OS_ASYNC_BEGIN(state);
×
782

783
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
784
    if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
×
785
        gap_disconnect(peri->con_handle);
×
786
    }
787

788
    PBIO_OS_AWAIT_UNTIL(state, peri->con_handle == HCI_CON_HANDLE_INVALID);
×
789

790
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
791
}
792

793
pbio_error_t pbdrv_bluetooth_start_broadcasting_func(pbio_os_state_t *state, void *context) {
×
794

795
    PBIO_OS_ASYNC_BEGIN(state);
×
796

797
    gap_advertisements_set_data(pbdrv_bluetooth_broadcast_data_size, pbdrv_bluetooth_broadcast_data);
×
798

799
    // If already broadcasting, await set data and return.
800
    if (pbdrv_bluetooth_is_broadcasting) {
×
801
        PBIO_OS_AWAIT_UNTIL(state, event_packet && HCI_EVENT_IS_COMMAND_COMPLETE(event_packet, hci_le_set_advertising_data));
×
802
        return PBIO_SUCCESS;
×
803
    }
804

805
    pbdrv_bluetooth_is_broadcasting = true;
×
806
    bd_addr_t null_addr = { };
×
807
    gap_advertisements_set_params(0xA0, 0xA0, PBDRV_BLUETOOTH_AD_TYPE_ADV_NONCONN_IND, 0, null_addr, 0x7, 0);
×
808
    gap_advertisements_enable(true);
×
809

810
    PBIO_OS_AWAIT_UNTIL(state, event_packet && HCI_EVENT_IS_COMMAND_COMPLETE(event_packet, hci_le_set_advertise_enable));
×
811

812
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
813
}
814

815
pbio_error_t pbdrv_bluetooth_start_observing_func(pbio_os_state_t *state, void *context) {
×
816

817
    PBIO_OS_ASYNC_BEGIN(state);
×
818

819
    if (!pbdrv_bluetooth_is_observing) {
×
820
        start_observing();
×
821
        pbdrv_bluetooth_is_observing = true;
×
822
        // REVISIT: use callback to await operation
823
    }
824

825
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
826
}
827

828
pbio_error_t pbdrv_bluetooth_stop_observing_func(pbio_os_state_t *state, void *context) {
×
829

830
    PBIO_OS_ASYNC_BEGIN(state);
×
831

832
    if (pbdrv_bluetooth_is_observing) {
×
833
        gap_stop_scan();
×
834
        pbdrv_bluetooth_is_observing = false;
×
835
        // REVISIT: use callback to await operation
836
    }
837

838
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
839
}
840

841
const char *pbdrv_bluetooth_get_hub_name(void) {
×
842
    return pbdrv_bluetooth_hub_name;
×
843
}
844

845
const char *pbdrv_bluetooth_get_fw_version(void) {
×
846
    // REVISIT: this should be linked to the init script as it can be updated in software
847
    // init script version
848
    return "v1.4";
×
849
}
850

851
pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer) {
32✔
852

853
    // The event handler also pushes the bluetooth process along, but shouldn't
854
    // be needing to touch the power state.
855
    if (event_packet) {
32✔
856
        return PBIO_ERROR_AGAIN;
16✔
857
    }
858

859
    PBIO_OS_ASYNC_BEGIN(state);
16✔
860

861
    // Disconnect gracefully if connected to host.
862
    if (le_con_handle != HCI_CON_HANDLE_INVALID) {
16✔
NEW
863
        gap_disconnect(le_con_handle);
×
NEW
864
        PBIO_OS_AWAIT_UNTIL(state, le_con_handle == HCI_CON_HANDLE_INVALID);
×
865
    }
866

867
    hci_power_control(HCI_POWER_OFF);
16✔
868
    PBIO_OS_AWAIT_UNTIL(state, hci_get_state() == HCI_STATE_OFF);
16✔
869

870
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
16✔
871
}
872

873
pbio_error_t pbdrv_bluetooth_controller_initialize(pbio_os_state_t *state, pbio_os_timer_t *timer) {
133✔
874

875
    // The event handler also pushes the bluetooth process along, but shouldn't
876
    // be needing to touch the power state.
877
    if (event_packet) {
133✔
878
        return PBIO_ERROR_AGAIN;
130✔
879
    }
880

881
    PBIO_OS_ASYNC_BEGIN(state);
3✔
882

883
    hci_power_control(HCI_POWER_ON);
1✔
884
    PBIO_OS_AWAIT_UNTIL(state, hci_get_state() == HCI_STATE_WORKING);
3✔
885

886
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
1✔
887
}
888

889
static void bluetooth_btstack_run_loop_init(void) {
16✔
890
    // Not used. Bluetooth process is started like a regular pbdrv process.
891
}
16✔
892

893
static btstack_linked_list_t data_sources;
894

895
static void bluetooth_btstack_run_loop_add_data_source(btstack_data_source_t *ds) {
1✔
896
    btstack_linked_list_add(&data_sources, &ds->item);
1✔
897
}
1✔
898

899
static bool bluetooth_btstack_run_loop_remove_data_source(btstack_data_source_t *ds) {
1✔
900
    return btstack_linked_list_remove(&data_sources, &ds->item);
1✔
901
}
902

903
static void bluetooth_btstack_run_loop_enable_data_source_callbacks(btstack_data_source_t *ds, uint16_t callback_types) {
2✔
904
    ds->flags |= callback_types;
2✔
905
}
2✔
906

907
static void bluetooth_btstack_run_loop_disable_data_source_callbacks(btstack_data_source_t *ds, uint16_t callback_types) {
1✔
908
    ds->flags &= ~callback_types;
1✔
909
}
1✔
910

911
static btstack_linked_list_t timers;
912

913
static void bluetooth_btstack_run_loop_set_timer(btstack_timer_source_t *ts, uint32_t timeout_in_ms) {
46✔
914
    ts->timeout = pbdrv_clock_get_ms() + timeout_in_ms;
46✔
915
}
46✔
916

917
static void bluetooth_btstack_run_loop_add_timer(btstack_timer_source_t *ts) {
46✔
918
    btstack_linked_item_t *it;
919
    for (it = (void *)&timers; it->next; it = it->next) {
52✔
920
        // don't add timer that's already in there
921
        btstack_timer_source_t *next = (void *)it->next;
28✔
922
        if (next == ts) {
28✔
923
            // timer was already in the list!
924
            assert(0);
×
925
            return;
926
        }
927
        // exit if new timeout before list timeout
928
        int32_t delta = btstack_time_delta(ts->timeout, next->timeout);
28✔
929
        if (delta < 0) {
28✔
930
            break;
22✔
931
        }
932
    }
933

934
    ts->item.next = it->next;
46✔
935
    it->next = &ts->item;
46✔
936
}
937

938
static bool bluetooth_btstack_run_loop_remove_timer(btstack_timer_source_t *ts) {
95✔
939
    if (btstack_linked_list_remove(&timers, &ts->item)) {
95✔
940
        return true;
44✔
941
    }
942
    return false;
51✔
943
}
944

945
static void bluetooth_btstack_run_loop_execute(void) {
×
946
    // not used
947
}
×
948

949
static void bluetooth_btstack_run_loop_dump_timer(void) {
×
950
    // not used
951
}
×
952

953
static const btstack_run_loop_t bluetooth_btstack_run_loop = {
954
    .init = bluetooth_btstack_run_loop_init,
955
    .add_data_source = bluetooth_btstack_run_loop_add_data_source,
956
    .remove_data_source = bluetooth_btstack_run_loop_remove_data_source,
957
    .enable_data_source_callbacks = bluetooth_btstack_run_loop_enable_data_source_callbacks,
958
    .disable_data_source_callbacks = bluetooth_btstack_run_loop_disable_data_source_callbacks,
959
    .set_timer = bluetooth_btstack_run_loop_set_timer,
960
    .add_timer = bluetooth_btstack_run_loop_add_timer,
961
    .remove_timer = bluetooth_btstack_run_loop_remove_timer,
962
    .execute = bluetooth_btstack_run_loop_execute,
963
    .dump_timer = bluetooth_btstack_run_loop_dump_timer,
964
    .get_time_ms = pbdrv_clock_get_ms,
965
};
966

967
static bool do_poll_handler;
968

969
void pbdrv_bluetooth_btstack_run_loop_trigger(void) {
3✔
970
    do_poll_handler = true;
3✔
971
    pbio_os_request_poll();
3✔
972
}
3✔
973

974
static pbio_os_process_t pbdrv_bluetooth_hci_process;
975

976
/**
977
 * This process is slightly unusual in that it does not have a state. It is
978
 * essentially just a poll handler.
979
 */
980
static pbio_error_t pbdrv_bluetooth_hci_process_thread(pbio_os_state_t *state, void *context) {
31,439✔
981

982
    if (do_poll_handler) {
31,439✔
983
        do_poll_handler = false;
3✔
984

985
        btstack_data_source_t *ds, *next;
986
        for (ds = (void *)data_sources; ds != NULL; ds = next) {
5✔
987
            // cache pointer to next data_source to allow data source to remove itself
988
            next = (void *)ds->item.next;
2✔
989
            if (ds->flags & DATA_SOURCE_CALLBACK_POLL) {
2✔
990
                ds->process(ds, DATA_SOURCE_CALLBACK_POLL);
1✔
991
            }
992
        }
993
    }
994

995
    static pbio_os_timer_t btstack_timer = {
996
        .duration = 1,
997
    };
998

999
    if (pbio_os_timer_is_expired(&btstack_timer)) {
31,439✔
1000
        pbio_os_timer_extend(&btstack_timer);
31,428✔
1001

1002
        // process all BTStack timers in list that have expired
1003
        while (timers) {
31,451✔
1004
            btstack_timer_source_t *ts = (void *)timers;
51✔
1005
            int32_t delta = btstack_time_delta(ts->timeout, pbdrv_clock_get_ms());
51✔
1006
            if (delta > 0) {
51✔
1007
                // we have reached unexpired timers
1008
                break;
28✔
1009
            }
1010
            btstack_run_loop_remove_timer(ts);
23✔
1011
            ts->process(ts);
23✔
1012
        }
1013
    }
1014

1015
    // Also propagate non-btstack events like polls or timers.
1016
    propagate_event(NULL);
31,439✔
1017

1018
    return bluetooth_thread_err;
31,439✔
1019
}
1020

1021
void pbdrv_bluetooth_init_hci(void) {
16✔
1022

1023
    static btstack_packet_callback_registration_t hci_event_callback_registration;
1024
    static btstack_packet_callback_registration_t sm_event_callback_registration;
1025

1026
    // don't need to init the whole struct, so doing this here
1027
    peripheral_singleton.con_handle = HCI_CON_HANDLE_INVALID;
16✔
1028

1029
    btstack_memory_init();
16✔
1030
    btstack_run_loop_init(&bluetooth_btstack_run_loop);
16✔
1031

1032
    hci_init(hci_transport_h4_instance_for_uart(pdata->uart_block_instance()), &config);
16✔
1033
    hci_set_chipset(pdata->chipset_instance());
16✔
1034
    hci_set_control(pdata->control_instance());
16✔
1035

1036
    // REVISIT: do we need to call btstack_chipset_cc256x_set_power() or btstack_chipset_cc256x_set_power_vector()?
1037

1038
    hci_event_callback_registration.callback = &packet_handler;
16✔
1039
    hci_add_event_handler(&hci_event_callback_registration);
16✔
1040

1041
    l2cap_init();
16✔
1042

1043
    // setup LE device DB
1044
    le_device_db_init();
16✔
1045

1046
    // setup security manager
1047
    sm_init();
16✔
1048
    sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
16✔
1049
    sm_set_authentication_requirements(SM_AUTHREQ_BONDING);
16✔
1050
    sm_set_er((uint8_t *)pdata->er_key);
16✔
1051
    sm_set_ir((uint8_t *)pdata->ir_key);
16✔
1052
    sm_event_callback_registration.callback = &sm_packet_handler;
16✔
1053
    sm_add_event_handler(&sm_event_callback_registration);
16✔
1054

1055
    gap_random_address_set_mode(GAP_RANDOM_ADDRESS_NON_RESOLVABLE);
16✔
1056
    gap_set_max_number_peripheral_connections(2);
16✔
1057

1058
    // GATT Client setup
1059
    gatt_client_init();
16✔
1060

1061
    // setup ATT server
1062
    att_server_init(profile_data, att_read_callback, NULL);
16✔
1063

1064
    device_information_service_server_init();
16✔
1065
    device_information_service_server_set_firmware_revision(PBIO_VERSION_STR);
16✔
1066
    device_information_service_server_set_software_revision(PBIO_PROTOCOL_VERSION_STR);
16✔
1067
    device_information_service_server_set_pnp_id(0x01, LWP3_LEGO_COMPANY_ID, HUB_KIND, HUB_VARIANT);
16✔
1068

1069
    pybricks_service_server_init(pybricks_data_received, pybricks_configured);
16✔
1070
    nordic_spp_service_server_init(nordic_spp_packet_handler);
16✔
1071

1072
    bluetooth_thread_err = PBIO_ERROR_AGAIN;
16✔
1073
    bluetooth_thread_state = 0;
16✔
1074
    pbio_os_process_start(&pbdrv_bluetooth_hci_process, pbdrv_bluetooth_hci_process_thread, NULL);
16✔
1075
}
16✔
1076

1077
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc