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

pybricks / pybricks-micropython / 19921717556

04 Dec 2025 07:55AM UTC coverage: 56.648% (+0.07%) from 56.581%
19921717556

Pull #427

github

laurensvalk
pbio/drv/bluetooth_btstack: Allow other firmware files.

For platforms like EV3, the firmware patch is dynamically determined. We can apply the same technique for the STM32 platforms for conistency.

Also move the firmware files to the Bluetooth folder since they are not associated with one particular hub.
Pull Request #427: pbio/drv/bluetooth_btstack: Further platform generalizations.

22 of 25 new or added lines in 1 file covered. (88.0%)

1 existing line in 1 file now uncovered.

4614 of 8145 relevant lines covered (56.65%)

16522896.96 hits per line

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

38.85
/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.h"
25
#include "bluetooth_btstack.h"
26

27
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
28
#include "genhdr/pybricks_service.h"
29
#include "pybricks_service_server.h"
30
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
31

32
#ifdef PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND
33
#define HUB_KIND PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND
34
#else
35
#error "PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND is required"
36
#endif
37

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

45
#define DEBUG 0
46

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

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

69
typedef enum {
70
    DISCONNECT_REASON_NONE,
71
    DISCONNECT_REASON_TIMEOUT,
72
    DISCONNECT_REASON_CONNECT_FAILED,
73
    DISCONNECT_REASON_DISCOVER_SERVICE_FAILED,
74
    DISCONNECT_REASON_DISCOVER_CHARACTERISTIC_FAILED,
75
    DISCONNECT_REASON_CONFIGURE_CHARACTERISTIC_FAILED,
76
    DISCONNECT_REASON_SEND_SUBSCRIBE_PORT_0_FAILED,
77
    DISCONNECT_REASON_SEND_SUBSCRIBE_PORT_1_FAILED,
78
} disconnect_reason_t;
79

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

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

99
static uint8_t *event_packet;
100
static const pbdrv_bluetooth_btstack_platform_data_t *pdata = &pbdrv_bluetooth_btstack_platform_data;
101

102
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
103
static hci_con_handle_t le_con_handle = HCI_CON_HANDLE_INVALID;
104
static hci_con_handle_t pybricks_con_handle = HCI_CON_HANDLE_INVALID;
105
static hci_con_handle_t uart_con_handle = HCI_CON_HANDLE_INVALID;
106
static pup_handset_t handset;
107
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
108

109
bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection) {
34,658✔
110
    #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
111
    if (connection == PBDRV_BLUETOOTH_CONNECTION_LE && le_con_handle != HCI_CON_HANDLE_INVALID) {
34,658✔
NEW
112
        return true;
×
113
    }
114

115
    if (connection == PBDRV_BLUETOOTH_CONNECTION_PYBRICKS && pybricks_con_handle != HCI_CON_HANDLE_INVALID) {
34,658✔
116
        return true;
11✔
117
    }
118

119
    if (connection == PBDRV_BLUETOOTH_CONNECTION_UART && uart_con_handle != HCI_CON_HANDLE_INVALID) {
34,647✔
NEW
120
        return true;
×
121
    }
122

123
    if (connection == PBDRV_BLUETOOTH_CONNECTION_PERIPHERAL && peripheral_singleton.con_handle != HCI_CON_HANDLE_INVALID) {
34,647✔
NEW
124
        return true;
×
125
    }
126
    #endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
127

128
    return false;
34,647✔
129
}
130

131
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
132

133
/**
134
 * Converts BTStack error to most appropriate PBIO error.
135
 * @param [in]  status      The BTStack error
136
 * @return                  The PBIO error.
137
 */
138
static pbio_error_t att_error_to_pbio_error(uint8_t status) {
×
139
    switch (status) {
×
140
        case ATT_ERROR_SUCCESS:
×
141
            return PBIO_SUCCESS;
×
142
        case ATT_ERROR_HCI_DISCONNECT_RECEIVED:
×
143
            return PBIO_ERROR_NO_DEV;
×
144
        case ATT_ERROR_TIMEOUT:
×
145
            return PBIO_ERROR_TIMEDOUT;
×
146
        default:
×
147
            return PBIO_ERROR_FAILED;
×
148
    }
149
}
150

151
static pbio_pybricks_error_t pybricks_data_received(hci_con_handle_t tx_con_handle, const uint8_t *data, uint16_t size) {
1✔
152
    if (pbdrv_bluetooth_receive_handler) {
1✔
153
        return pbdrv_bluetooth_receive_handler(data, size);
1✔
154
    }
155

156
    return ATT_ERROR_UNLIKELY_ERROR;
×
157
}
158

159
static void pybricks_configured(hci_con_handle_t tx_con_handle, uint16_t value) {
2✔
160
    pybricks_con_handle = value ? tx_con_handle : HCI_CON_HANDLE_INVALID;
2✔
161
}
2✔
162

163
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
164

165
static pbio_os_state_t bluetooth_thread_state;
166
static pbio_os_state_t bluetooth_thread_err;
167

168
/**
169
 * Runs tasks that may be waiting for event.
170
 *
171
 * This drives the common Bluetooth process synchronously with incoming events
172
 * so that each of the protothreads can wait for states.
173
 *
174
 * @param [in]  packet  Pointer to the raw packet data.
175
 */
176
static void propagate_event(uint8_t *packet) {
34,011✔
177

178
    event_packet = packet;
34,011✔
179

180
    if (bluetooth_thread_err == PBIO_ERROR_AGAIN) {
34,011✔
181
        bluetooth_thread_err = pbdrv_bluetooth_process_thread(&bluetooth_thread_state, NULL);
34,011✔
182
    }
183

184
    event_packet = NULL;
34,011✔
185
}
34,011✔
186

187
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
188
static void nordic_spp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
×
189
    switch (packet_type) {
×
190
        case HCI_EVENT_PACKET:
×
191
            if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META) {
×
192
                break;
×
193
            }
194

195
            switch (hci_event_gattservice_meta_get_subevent_code(packet)) {
×
196
                case GATTSERVICE_SUBEVENT_SPP_SERVICE_CONNECTED:
×
197
                    uart_con_handle = gattservice_subevent_spp_service_connected_get_con_handle(packet);
×
198
                    break;
×
199
                case GATTSERVICE_SUBEVENT_SPP_SERVICE_DISCONNECTED:
×
200
                    uart_con_handle = HCI_CON_HANDLE_INVALID;
×
201
                    break;
×
202
                default:
×
203
                    break;
×
204
            }
205

206
            break;
×
207
        case RFCOMM_DATA_PACKET:
×
208
            // Not implemented.
209
            break;
×
210
        default:
×
211
            break;
×
212
    }
213

214
    propagate_event(packet);
×
215
}
×
216
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
217

218
// currently, this function just handles the Powered Up handset control.
219
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
641✔
220

221
    #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
222
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
641✔
223
    #endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
224

225
    switch (hci_event_packet_get_type(packet)) {
641✔
226
        case HCI_EVENT_COMMAND_COMPLETE: {
292✔
227
            const uint8_t *rp = hci_event_command_complete_get_return_parameters(packet);
292✔
228
            switch (hci_event_command_complete_get_command_opcode(packet)) {
292✔
229
                case HCI_OPCODE_HCI_READ_LOCAL_VERSION_INFORMATION: {
16✔
230
                    uint16_t lmp_pal_subversion = pbio_get_uint16_le(&rp[7]);
16✔
231
                    pbdrv_bluetooth_btstack_set_chipset(lmp_pal_subversion);
16✔
232

233
                    #if DEBUG
234
                    // Show version in ev3dev format.
235
                    uint16_t chip = (lmp_pal_subversion & 0x7C00) >> 10;
236
                    uint16_t min_ver = (lmp_pal_subversion & 0x007F);
237
                    uint16_t maj_ver = (lmp_pal_subversion & 0x0380) >> 7;
238
                    if (lmp_pal_subversion & 0x8000) {
239
                        maj_ver |= 0x0008;
240
                    }
241
                    DEBUG_PRINT("LMP %04x: TIInit_%d.%d.%d.bts\n", lmp_pal_subversion, chip, maj_ver, min_ver);
242
                    #endif
243
                    break;
16✔
244
                }
245
                default:
276✔
246
                    break;
276✔
247
            }
248
            break;
292✔
249
        }
250
        #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
UNCOV
251
        case GATT_EVENT_SERVICE_QUERY_RESULT: {
×
252
            // Service discovery not used.
253
            gatt_client_service_t service;
254
            gatt_event_service_query_result_get_service(packet, &service);
×
255
            break;
×
256
        }
257
        case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: {
×
258
            gatt_client_characteristic_t found_char;
259
            gatt_event_characteristic_query_result_get_characteristic(packet, &found_char);
×
260
            // We only care about the one characteristic that has at least the requested properties.
261
            if ((found_char.properties & peri->char_now->properties) == peri->char_now->properties) {
×
262
                peri->char_now->handle = found_char.value_handle;
×
263
                gatt_event_characteristic_query_result_get_characteristic(packet, &handset.current_char);
×
264
            }
265
            break;
×
266
        }
267
        case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT: {
×
268
            hci_con_handle_t handle = gatt_event_characteristic_value_query_result_get_handle(packet);
×
269
            uint16_t value_handle = gatt_event_characteristic_value_query_result_get_value_handle(packet);
×
270
            uint16_t value_length = gatt_event_characteristic_value_query_result_get_value_length(packet);
×
271
            if (peri->con_handle == handle && peri->char_now->handle == value_handle) {
×
272
                peri->char_now->value_len = gatt_event_characteristic_value_query_result_get_value_length(packet);
×
273
                memcpy(peri->char_now->value, gatt_event_characteristic_value_query_result_get_value(packet), value_length);
×
274
            }
275
            break;
×
276
        }
277
        case GATT_EVENT_QUERY_COMPLETE:
×
278
            if (handset.con_state == CON_STATE_WAIT_READ_CHARACTERISTIC) {
×
279
                // Done reading characteristic.
280
                handset.con_state = CON_STATE_READ_CHARACTERISTIC_COMPLETE;
×
281
            } else if (handset.con_state == CON_STATE_WAIT_DISCOVER_CHARACTERISTICS) {
×
282

283
                // Discovered characteristics, ready enable notifications.
284
                if (!peri->char_now->request_notification) {
×
285
                    // If no notification is requested, we are done.
286
                    handset.con_state = CON_STATE_DISCOVERY_AND_NOTIFICATIONS_COMPLETE;
×
287
                    break;
×
288
                }
289

290
                handset.btstack_error = gatt_client_write_client_characteristic_configuration(
×
291
                    packet_handler, peri->con_handle, &handset.current_char,
×
292
                    GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
293
                if (handset.btstack_error == ERROR_CODE_SUCCESS) {
×
294
                    gatt_client_listen_for_characteristic_value_updates(
×
295
                        &handset.notification, packet_handler, peri->con_handle, &handset.current_char);
×
296
                    handset.con_state = CON_STATE_WAIT_ENABLE_NOTIFICATIONS;
×
297
                } else {
298
                    // configuration failed for some reason, so disconnect
299
                    gap_disconnect(peri->con_handle);
×
300
                    handset.con_state = CON_STATE_WAIT_DISCONNECT;
×
301
                    handset.disconnect_reason = DISCONNECT_REASON_CONFIGURE_CHARACTERISTIC_FAILED;
×
302
                }
303
            } else if (handset.con_state == CON_STATE_WAIT_ENABLE_NOTIFICATIONS) {
×
304
                // Done enabling notifications.
305
                handset.con_state = CON_STATE_DISCOVERY_AND_NOTIFICATIONS_COMPLETE;
×
306
            }
307
            break;
×
308

309
        case GATT_EVENT_NOTIFICATION: {
×
310
            if (gatt_event_notification_get_handle(packet) != peri->con_handle) {
×
311
                break;
×
312
            }
313
            if (peri->config->notification_handler) {
×
314
                uint16_t length = gatt_event_notification_get_value_length(packet);
×
315
                const uint8_t *value = gatt_event_notification_get_value(packet);
×
316
                peri->config->notification_handler(value, length);
×
317
            }
318
            break;
×
319
        }
320

321
        case HCI_EVENT_LE_META:
2✔
322
            if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) {
2✔
323
                break;
1✔
324
            }
325

326
            // HCI_ROLE_SLAVE means the connecting device is the central and the hub is the peripheral
327
            // HCI_ROLE_MASTER means the connecting device is the peripheral and the hub is the central.
328
            if (hci_subevent_le_connection_complete_get_role(packet) == HCI_ROLE_SLAVE) {
1✔
329
                le_con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
1✔
330

331
                // don't start advertising again on disconnect
332
                gap_advertisements_enable(false);
1✔
333
                pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_NONE;
1✔
334
            } else {
335
                // If we aren't waiting for a peripheral connection, this must be a different connection.
336
                if (handset.con_state != CON_STATE_WAIT_CONNECT) {
×
337
                    break;
×
338
                }
339

340
                peri->con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
×
341

342
                // Request pairing if needed for this device, otherwise set
343
                // connection state to complete.
344
                if (peri->config->options & PBDRV_BLUETOOTH_PERIPHERAL_OPTIONS_PAIR) {
×
345
                    // Re-encryption doesn't seem to work reliably, so we just
346
                    // delete the bond and start over.
347
                    gap_delete_bonding(peri->bdaddr_type, peri->bdaddr);
×
348
                    sm_request_pairing(peri->con_handle);
×
349
                    handset.con_state = CON_STATE_WAIT_BONDING;
×
350
                } else {
351
                    handset.con_state = CON_STATE_CONNECTED;
×
352
                }
353
            }
354

355
            break;
1✔
356

357
        case HCI_EVENT_DISCONNECTION_COMPLETE:
×
358
            if (hci_event_disconnection_complete_get_connection_handle(packet) == le_con_handle) {
×
359
                le_con_handle = HCI_CON_HANDLE_INVALID;
×
360
                pybricks_con_handle = HCI_CON_HANDLE_INVALID;
×
361
                uart_con_handle = HCI_CON_HANDLE_INVALID;
×
362
            } else if (hci_event_disconnection_complete_get_connection_handle(packet) == peri->con_handle) {
×
363
                gatt_client_stop_listening_for_characteristic_value_updates(&handset.notification);
×
364
                peri->con_handle = HCI_CON_HANDLE_INVALID;
×
365
                handset.con_state = CON_STATE_NONE;
×
366
            }
367

368
            break;
×
369

370
        case GAP_EVENT_ADVERTISING_REPORT: {
×
371
            uint8_t event_type = gap_event_advertising_report_get_advertising_event_type(packet);
×
372
            uint8_t data_length = gap_event_advertising_report_get_data_length(packet);
×
373
            const uint8_t *data = gap_event_advertising_report_get_data(packet);
×
374
            bd_addr_t address;
375

376
            gap_event_advertising_report_get_address(packet, address);
×
377

378
            if (pbdrv_bluetooth_observe_callback) {
×
379
                int8_t rssi = gap_event_advertising_report_get_rssi(packet);
×
380
                pbdrv_bluetooth_observe_callback(event_type, data, data_length, rssi);
×
381
            }
382

383
            if (handset.con_state == CON_STATE_WAIT_ADV_IND) {
×
384
                // Match advertisement data against context-specific filter.
385
                pbdrv_bluetooth_ad_match_result_flags_t adv_flags = PBDRV_BLUETOOTH_AD_MATCH_NONE;
×
386
                if (peri->config->match_adv) {
×
387
                    adv_flags = peri->config->match_adv(event_type, data, NULL, address, peri->bdaddr);
×
388
                }
389

390
                if (adv_flags & PBDRV_BLUETOOTH_AD_MATCH_VALUE) {
×
391
                    if (adv_flags & PBDRV_BLUETOOTH_AD_MATCH_ADDRESS) {
×
392
                        // This was the same device as last time. If the scan response
393
                        // didn't match before, it probably won't match now and we
394
                        // should try a different device.
395
                        break;
×
396
                    }
397
                    // Advertising data matched, prepare for scan response.
398
                    memcpy(peri->bdaddr, address, sizeof(bd_addr_t));
×
399
                    peri->bdaddr_type = gap_event_advertising_report_get_address_type(packet);
×
400
                    handset.con_state = CON_STATE_WAIT_SCAN_RSP;
×
401
                }
402
            } else if (handset.con_state == CON_STATE_WAIT_SCAN_RSP) {
×
403

404
                char *detected_name = (char *)&data[2];
×
405
                const uint8_t max_len = sizeof(peri->name);
×
406

407
                pbdrv_bluetooth_ad_match_result_flags_t rsp_flags = PBDRV_BLUETOOTH_AD_MATCH_NONE;
×
408
                if (peri->config->match_adv_rsp) {
×
409
                    rsp_flags = peri->config->match_adv_rsp(event_type, NULL, detected_name, address, peri->bdaddr);
×
410
                }
411
                if ((rsp_flags & PBDRV_BLUETOOTH_AD_MATCH_VALUE) && (rsp_flags & PBDRV_BLUETOOTH_AD_MATCH_ADDRESS)) {
×
412

413
                    if (rsp_flags & PBDRV_BLUETOOTH_AD_MATCH_NAME_FAILED) {
×
414
                        // A name was requested but it doesn't match, so go back to scanning stage.
415
                        handset.con_state = CON_STATE_WAIT_ADV_IND;
×
416
                        break;
×
417
                    }
418

419
                    if (data[1] == BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME) {
×
420
                        memcpy(peri->name, detected_name, max_len);
×
421
                    }
422

423
                    gap_stop_scan();
×
424
                    handset.btstack_error = gap_connect(peri->bdaddr, peri->bdaddr_type);
×
425

426
                    if (handset.btstack_error == ERROR_CODE_SUCCESS) {
×
427
                        handset.con_state = CON_STATE_WAIT_CONNECT;
×
428
                    } else {
429
                        handset.con_state = CON_STATE_NONE;
×
430
                    }
431
                }
432
            }
433

434
            break;
×
435
        }
436
        #endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
437

438
        default:
347✔
439
            break;
347✔
440
    }
441

442
    propagate_event(packet);
641✔
443
}
641✔
444

445
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
446
// Security manager callbacks. This is adapted from the BTstack examples.
447
static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
2✔
448
    UNUSED(channel);
449
    UNUSED(size);
450

451
    if (packet_type != HCI_EVENT_PACKET) {
2✔
452
        return;
×
453
    }
454

455
    bd_addr_t addr;
456
    bd_addr_type_t addr_type;
457

458
    switch (hci_event_packet_get_type(packet)) {
2✔
459
        case SM_EVENT_IDENTITY_RESOLVING_STARTED:
1✔
460
            DEBUG_PRINT("IDENTITY_RESOLVING_STARTED\n");
461
            break;
1✔
462
        case SM_EVENT_IDENTITY_RESOLVING_FAILED:
1✔
463
            DEBUG_PRINT("IDENTITY_RESOLVING_FAILED\n");
464
            break;
1✔
465
        case SM_EVENT_JUST_WORKS_REQUEST:
×
466
            // This is the only expected path for known compatible peripherals.
467
            DEBUG_PRINT("Just works requested\n");
468
            sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
×
469
            break;
×
470
        case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
×
471
            DEBUG_PRINT("Confirming numeric comparison: %" PRIu32 "\n", sm_event_numeric_comparison_request_get_passkey(packet));
472
            sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
×
473
            break;
×
474
        case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
×
475
            DEBUG_PRINT("Display Passkey: %" PRIu32 "\n", sm_event_passkey_display_number_get_passkey(packet));
476
            break;
×
477
        case SM_EVENT_PASSKEY_INPUT_NUMBER: {
×
478
            const uint32_t passkey = 123456U;
×
479
            DEBUG_PRINT("Passkey Input requested\n");
480
            DEBUG_PRINT("Sending fixed passkey %" PRIu32 "\n", passkey);
481
            sm_passkey_input(sm_event_passkey_input_number_get_handle(packet), passkey);
×
482
            break;
×
483
        }
484
        case SM_EVENT_PAIRING_STARTED:
×
485
            DEBUG_PRINT("Pairing started\n");
486
            break;
×
487
        case SM_EVENT_PAIRING_COMPLETE:
×
488
            switch (sm_event_pairing_complete_get_status(packet)) {
×
489
                case ERROR_CODE_SUCCESS:
×
490
                    // This is the final state for known compatible peripherals
491
                    // with bonding under normal circumstances.
492
                    DEBUG_PRINT("Pairing complete, success\n");
493
                    handset.con_state = CON_STATE_CONNECTED;
×
494
                    break;
×
495
                case ERROR_CODE_CONNECTION_TIMEOUT:
×
496
                // fall through to disconnect.
497
                case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
498
                // fall through to disconnect.
499
                case ERROR_CODE_AUTHENTICATION_FAILURE:
500
                // fall through to disconnect.
501
                default:
502
                    DEBUG_PRINT(
503
                        "Pairing completed with error code %u and reason %u\n",
504
                        sm_event_pairing_complete_get_status(packet),
505
                        sm_event_pairing_complete_get_reason(packet)
506
                        );
507
                    gap_disconnect(sm_event_reencryption_complete_get_handle(packet));
×
508
                    break;
×
509
            }
510
            break;
×
511
        case SM_EVENT_REENCRYPTION_STARTED:
×
512
            sm_event_reencryption_complete_get_address(packet, addr);
×
513
            DEBUG_PRINT("Bonding information exists for addr type %u, identity addr %s -> start re-encryption\n",
514
                sm_event_reencryption_started_get_addr_type(packet), bd_addr_to_str(addr));
515
            break;
×
516
        case SM_EVENT_REENCRYPTION_COMPLETE:
×
517
            // BTstack supports re-encryption, but it gets the hub in a hung
518
            // state with certain peripherals. Instead we just delete the bond
519
            // just before connecting. If we still get here, we should delete
520
            // the bond and disconnect, causing the user program stop without
521
            // hanging. We rely on HCI_EVENT_DISCONNECTION_COMPLETE to set
522
            // the connection state appropriately to unblock the task.
523
            DEBUG_PRINT("Re-encryption complete. Handling not implemented.\n");
524
            sm_event_reencryption_complete_get_address(packet, addr);
×
525
            addr_type = sm_event_reencryption_started_get_addr_type(packet);
×
526
            gap_delete_bonding(addr_type, addr);
×
527
            gap_disconnect(sm_event_reencryption_complete_get_handle(packet));
×
528
            break;
×
529
        default:
×
530
            break;
×
531
    }
532
}
533

534
// ATT Client Read Callback for Dynamic Data
535
// - if buffer == NULL, don't copy data, just return size of value
536
// - if buffer != NULL, copy data and return number bytes copied
537
// @param offset defines start of attribute value
538
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) {
×
539
    uint16_t att_value_len;
540

541
    switch (attribute_handle) {
×
542
        case ATT_CHARACTERISTIC_GAP_DEVICE_NAME_01_VALUE_HANDLE:
×
543
            att_value_len = strlen(pbdrv_bluetooth_hub_name);
×
544
            if (buffer) {
×
545
                memcpy(buffer, pbdrv_bluetooth_hub_name, att_value_len);
×
546
            }
547
            return att_value_len;
×
548

549
        default:
×
550
            return 0;
×
551
    }
552
}
553

554
static void init_advertising_data(void) {
1✔
555
    bd_addr_t null_addr = { };
1✔
556
    gap_advertisements_set_params(0x30, 0x30, PBDRV_BLUETOOTH_AD_TYPE_ADV_IND, 0x00, null_addr, 0x07, 0x00);
1✔
557

558
    static const uint8_t adv_data[] = {
559
        // Flags general discoverable, BR/EDR not supported
560
        2, BLUETOOTH_DATA_TYPE_FLAGS, 0x06,
561
        // Pybricks service
562
        17, BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
563
        0xef, 0xae, 0xe4, 0x51, 0x80, 0x6d, 0xf4, 0x89, 0xda, 0x46, 0x80, 0x82, 0x01, 0x00, 0xf5, 0xc5,
564
        // Tx Power
565
        2, BLUETOOTH_DATA_TYPE_TX_POWER_LEVEL, 0,
566
    };
567

568
    _Static_assert(sizeof(adv_data) <= 31, "31 octet max");
569

570
    gap_advertisements_set_data(sizeof(adv_data), (uint8_t *)adv_data);
1✔
571

572
    static uint8_t scan_resp_data[31] = {
573
        10, BLUETOOTH_DATA_TYPE_SERVICE_DATA,
574
        // used to identify which hub - Device Information Service (DIS).
575
        // 0x2A50 - service UUID - PnP ID characteristic UUID
576
        // 0x01 - Vendor ID Source Field - Bluetooth SIG-assigned ID
577
        // 0x0397 - Vendor ID Field - LEGO company identifier
578
        // 0x00XX - Product ID Field - hub kind
579
        // 0x00XX - Product Version Field - product variant
580
        0x50, 0x2a, 0x01, 0x97, 0x03, HUB_KIND, 0x00, 0x00, 0x00,
581
        0, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME,
582
    };
583

584
    scan_resp_data[9] = HUB_VARIANT;
1✔
585

586
    uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name);
1✔
587
    scan_resp_data[11] = hub_name_len + 1;
1✔
588
    memcpy(&scan_resp_data[13], pbdrv_bluetooth_hub_name, hub_name_len);
1✔
589
    _Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max");
590

591
    gap_scan_response_set_data(13 + hub_name_len, scan_resp_data);
1✔
592
}
1✔
593

594
pbio_error_t pbdrv_bluetooth_start_advertising_func(pbio_os_state_t *state, void *context) {
13✔
595

596
    PBIO_OS_ASYNC_BEGIN(state);
13✔
597

598
    init_advertising_data();
1✔
599
    gap_advertisements_enable(true);
1✔
600

601
    PBIO_OS_AWAIT_UNTIL(state, event_packet && HCI_EVENT_IS_COMMAND_COMPLETE(event_packet, hci_le_set_advertise_enable));
13✔
602

603
    pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_ADVERTISING_PYBRICKS;
1✔
604

605
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
1✔
606
}
607

608
pbio_error_t pbdrv_bluetooth_stop_advertising_func(pbio_os_state_t *state, void *context) {
×
609

610
    PBIO_OS_ASYNC_BEGIN(state);
×
611

612
    gap_advertisements_enable(false);
×
613

614
    // REVISIT: use callback to await operation
615

616
    pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_NONE;
×
617

618
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
619
}
620

621
typedef struct {
622
    const uint8_t *data;
623
    uint16_t size;
624
    bool done;
625
} send_data_t;
626

627
static void pybricks_on_ready_to_send(void *context) {
1✔
628
    send_data_t *send = context;
1✔
629
    pybricks_service_server_send(pybricks_con_handle, send->data, send->size);
1✔
630
    send->done = true;
1✔
631
    pbio_os_request_poll();
1✔
632
}
1✔
633

634
pbio_error_t pbdrv_bluetooth_send_pybricks_value_notification(pbio_os_state_t *state, const uint8_t *data, uint16_t size) {
1✔
635

636
    static send_data_t send_data;
637

638
    static btstack_context_callback_registration_t send_request = {
639
        .callback = pybricks_on_ready_to_send,
640
        .context = &send_data,
641
    };
642
    PBIO_OS_ASYNC_BEGIN(state);
1✔
643

644
    send_data.data = data;
1✔
645
    send_data.size = size;
1✔
646
    send_data.done = false;
1✔
647
    pybricks_service_server_request_can_send_now(&send_request, pybricks_con_handle);
1✔
648
    PBIO_OS_AWAIT_UNTIL(state, send_data.done);
1✔
649

650
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
1✔
651
}
652

653
static void start_observing(void) {
×
654
    gap_set_scan_params(0, 0x30, 0x30, 0);
×
655
    gap_start_scan();
×
656
}
×
657

658
pbio_error_t pbdrv_bluetooth_peripheral_scan_and_connect_func(pbio_os_state_t *state, void *context) {
×
659

660
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
661

662
    // Scan and connect timeout, if applicable.
663
    bool timed_out = peri->config->timeout && pbio_os_timer_is_expired(&peri->timer);
×
664

665
    // Operation can be explicitly cancelled or automatically on inactivity.
666
    if (!peri->cancel) {
×
667
        peri->cancel = pbio_os_timer_is_expired(&peri->watchdog);
×
668
    }
669

670
    PBIO_OS_ASYNC_BEGIN(state);
×
671

672
    memset(&handset, 0, sizeof(handset));
×
673

674
    peri->con_handle = HCI_CON_HANDLE_INVALID;
×
675

676
    // active scanning to get scan response data.
677
    // scan interval: 48 * 0.625ms = 30ms
678
    gap_set_scan_params(1, 0x30, 0x30, 0);
×
679
    gap_start_scan();
×
680
    handset.con_state = CON_STATE_WAIT_ADV_IND;
×
681

682
    PBIO_OS_AWAIT_UNTIL(state, ({
×
683
        if (peri->cancel || timed_out) {
684
            if (handset.con_state == CON_STATE_WAIT_ADV_IND || handset.con_state == CON_STATE_WAIT_SCAN_RSP) {
685
                gap_stop_scan();
686
            } else if (handset.con_state == CON_STATE_WAIT_CONNECT) {
687
                gap_connect_cancel();
688
            } else if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
689
                gap_disconnect(peri->con_handle);
690
            }
691
            handset.con_state = CON_STATE_NONE;
692
            return timed_out ? PBIO_ERROR_TIMEDOUT : PBIO_ERROR_CANCELED;
693
        }
694
        // if there is any failure to connect or error while enumerating
695
        // attributes, con_state will be set to CON_STATE_NONE
696
        if (handset.con_state == CON_STATE_NONE) {
697
            return PBIO_ERROR_FAILED;
698
        }
699
        handset.con_state == CON_STATE_CONNECTED;
700
    }));
701

702
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
703
}
704

705
pbio_error_t pbdrv_bluetooth_peripheral_discover_characteristic_func(pbio_os_state_t *state, void *context) {
×
706

707
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
708

709
    PBIO_OS_ASYNC_BEGIN(state);
×
710

711
    if (handset.con_state != CON_STATE_CONNECTED) {
×
712
        return PBIO_ERROR_FAILED;
×
713
    }
714

715
    handset.con_state = CON_STATE_WAIT_DISCOVER_CHARACTERISTICS;
×
716
    uint16_t handle_max = peri->char_now->handle_max ? peri->char_now->handle_max : 0xffff;
×
717
    handset.btstack_error = peri->char_now->uuid16 ?
×
718
        gatt_client_discover_characteristics_for_handle_range_by_uuid16(
×
719
        packet_handler, peri->con_handle, 0x0001, handle_max, peri->char_now->uuid16) :
×
720
        gatt_client_discover_characteristics_for_handle_range_by_uuid128(
×
721
        packet_handler, peri->con_handle, 0x0001, handle_max, peri->char_now->uuid128);
×
722

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

730
    PBIO_OS_AWAIT_UNTIL(state, ({
×
731
        // if there is any error while enumerating
732
        // attributes, con_state will be set to CON_STATE_NONE
733
        if (handset.con_state == CON_STATE_NONE) {
734
            return PBIO_ERROR_FAILED;
735
        }
736
        handset.con_state == CON_STATE_DISCOVERY_AND_NOTIFICATIONS_COMPLETE;
737
    }));
738

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

742
    PBIO_OS_ASYNC_END(peri->char_now->handle ? PBIO_SUCCESS : PBIO_ERROR_FAILED);
×
743
}
744

745
pbio_error_t pbdrv_bluetooth_peripheral_read_characteristic_func(pbio_os_state_t *state, void *context) {
×
746

747
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
748

749
    PBIO_OS_ASYNC_BEGIN(state);
×
750

751
    if (handset.con_state != CON_STATE_CONNECTED) {
×
752
        return PBIO_ERROR_FAILED;
×
753
    }
754

755
    gatt_client_characteristic_t characteristic = {
×
756
        .value_handle = peri->char_now->handle,
×
757
    };
758
    handset.btstack_error = gatt_client_read_value_of_characteristic(packet_handler, peri->con_handle, &characteristic);
×
759

760
    if (handset.btstack_error == ERROR_CODE_SUCCESS) {
×
761
        handset.con_state = CON_STATE_WAIT_READ_CHARACTERISTIC;
×
762
    } else {
763
        // configuration failed for some reason, so disconnect
764
        gap_disconnect(peri->con_handle);
×
765
        handset.con_state = CON_STATE_WAIT_DISCONNECT;
×
766
        handset.disconnect_reason = DISCONNECT_REASON_DISCOVER_CHARACTERISTIC_FAILED;
×
767
    }
768

769
    PBIO_OS_AWAIT_UNTIL(state, ({
×
770
        // if there is any error while reading, con_state will be set to CON_STATE_NONE
771
        if (handset.con_state == CON_STATE_NONE) {
772
            return PBIO_ERROR_FAILED;
773
        }
774
        handset.con_state == CON_STATE_READ_CHARACTERISTIC_COMPLETE;
775
    }));
776

777
    // State state back to simply connected, so we can discover other characteristics.
778
    handset.con_state = CON_STATE_CONNECTED;
×
779

780
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
781
}
782

783
pbio_error_t pbdrv_bluetooth_peripheral_write_characteristic_func(pbio_os_state_t *state, void *context) {
×
784

785
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
786

787
    PBIO_OS_ASYNC_BEGIN(state);
×
788

789
    uint8_t err = gatt_client_write_value_of_characteristic(packet_handler,
×
790
        peri->con_handle,
×
791
        pbdrv_bluetooth_char_write_handle,
792
        pbdrv_bluetooth_char_write_size,
793
        pbdrv_bluetooth_char_write_data
794
        );
795

796
    if (err != ERROR_CODE_SUCCESS) {
×
797
        return PBIO_ERROR_FAILED;
×
798
    }
799

800
    // NB: Value buffer must remain valid until GATT_EVENT_QUERY_COMPLETE, so
801
    // this wait is not cancelable.
802
    PBIO_OS_AWAIT_UNTIL(state, ({
×
803
        if (peri->con_handle == HCI_CON_HANDLE_INVALID) {
804
            // disconnected
805
            return PBIO_ERROR_NO_DEV;
806
        }
807
        event_packet &&
808
        hci_event_packet_get_type(event_packet) == GATT_EVENT_QUERY_COMPLETE &&
809
        gatt_event_query_complete_get_handle(event_packet) == peri->con_handle;
810
    }));
811

812
    PBIO_OS_ASYNC_END(att_error_to_pbio_error(gatt_event_query_complete_get_att_status(event_packet)));
×
813
}
814

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

817
    PBIO_OS_ASYNC_BEGIN(state);
×
818

819
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
820
    if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
×
821
        gap_disconnect(peri->con_handle);
×
822
    }
823

824
    PBIO_OS_AWAIT_UNTIL(state, peri->con_handle == HCI_CON_HANDLE_INVALID);
×
825

826
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
827
}
828

829
pbio_error_t pbdrv_bluetooth_start_broadcasting_func(pbio_os_state_t *state, void *context) {
×
830

831
    PBIO_OS_ASYNC_BEGIN(state);
×
832

833
    gap_advertisements_set_data(pbdrv_bluetooth_broadcast_data_size, pbdrv_bluetooth_broadcast_data);
×
834

835
    // If already broadcasting, await set data and return.
836
    if (pbdrv_bluetooth_advertising_state == PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING) {
×
837
        PBIO_OS_AWAIT_UNTIL(state, event_packet && HCI_EVENT_IS_COMMAND_COMPLETE(event_packet, hci_le_set_advertising_data));
×
838
        return PBIO_SUCCESS;
×
839
    }
840

841
    bd_addr_t null_addr = { };
×
842
    gap_advertisements_set_params(0xA0, 0xA0, PBDRV_BLUETOOTH_AD_TYPE_ADV_NONCONN_IND, 0, null_addr, 0x7, 0);
×
843
    gap_advertisements_enable(true);
×
844

845
    PBIO_OS_AWAIT_UNTIL(state, event_packet && HCI_EVENT_IS_COMMAND_COMPLETE(event_packet, hci_le_set_advertise_enable));
×
846

847
    pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING;
×
848

849
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
850
}
851

852
pbio_error_t pbdrv_bluetooth_start_observing_func(pbio_os_state_t *state, void *context) {
×
853

854
    PBIO_OS_ASYNC_BEGIN(state);
×
855

856
    if (!pbdrv_bluetooth_is_observing) {
×
857
        start_observing();
×
858
        pbdrv_bluetooth_is_observing = true;
×
859
        // REVISIT: use callback to await operation
860
    }
861

862
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
863
}
864

865
pbio_error_t pbdrv_bluetooth_stop_observing_func(pbio_os_state_t *state, void *context) {
×
866

867
    PBIO_OS_ASYNC_BEGIN(state);
×
868

869
    if (pbdrv_bluetooth_is_observing) {
×
870
        gap_stop_scan();
×
871
        pbdrv_bluetooth_is_observing = false;
×
872
        // REVISIT: use callback to await operation
873
    }
874

875
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
876
}
877

878

879
#else // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
880

881
pbio_error_t pbdrv_bluetooth_start_broadcasting_func(pbio_os_state_t *state, void *context) {
882
    return PBIO_ERROR_NOT_SUPPORTED;
883
}
884
pbio_error_t pbdrv_bluetooth_peripheral_disconnect_func(pbio_os_state_t *state, void *context) {
885
    return PBIO_ERROR_NOT_SUPPORTED;
886
}
887
pbio_error_t pbdrv_bluetooth_peripheral_discover_characteristic_func(pbio_os_state_t *state, void *context) {
888
    return PBIO_ERROR_NOT_SUPPORTED;
889
}
890
pbio_error_t pbdrv_bluetooth_peripheral_read_characteristic_func(pbio_os_state_t *state, void *context) {
891
    return PBIO_ERROR_NOT_SUPPORTED;
892
}
893
pbio_error_t pbdrv_bluetooth_peripheral_scan_and_connect_func(pbio_os_state_t *state, void *context) {
894
    return PBIO_ERROR_NOT_SUPPORTED;
895
}
896
pbio_error_t pbdrv_bluetooth_peripheral_write_characteristic_func(pbio_os_state_t *state, void *context) {
897
    return PBIO_ERROR_NOT_SUPPORTED;
898
}
899
pbio_error_t pbdrv_bluetooth_start_advertising_func(pbio_os_state_t *state, void *context) {
900
    return PBIO_ERROR_NOT_SUPPORTED;
901
}
902
pbio_error_t pbdrv_bluetooth_stop_advertising_func(pbio_os_state_t *state, void *context) {
903
    return PBIO_ERROR_NOT_SUPPORTED;
904
}
905
pbio_error_t pbdrv_bluetooth_start_observing_func(pbio_os_state_t *state, void *context) {
906
    return PBIO_ERROR_NOT_SUPPORTED;
907
}
908
pbio_error_t pbdrv_bluetooth_stop_observing_func(pbio_os_state_t *state, void *context) {
909
    return PBIO_ERROR_NOT_SUPPORTED;
910
}
911
pbio_error_t pbdrv_bluetooth_send_pybricks_value_notification(pbio_os_state_t *state, const uint8_t *data, uint16_t size) {
912
    return PBIO_ERROR_NOT_SUPPORTED;
913
}
914

915
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
916

917
const char *pbdrv_bluetooth_get_hub_name(void) {
×
918
    return pbdrv_bluetooth_hub_name;
×
919
}
920

921
const char *pbdrv_bluetooth_get_fw_version(void) {
×
922
    // REVISIT: this should be linked to the init script as it can be updated in software
923
    // init script version
924
    return "v1.4";
×
925
}
926

927
void pbdrv_bluetooth_controller_reset_hard(void) {
×
928
    hci_power_control(HCI_POWER_OFF);
×
929
}
×
930

931
/**
932
 * btstack's hci_power_control() synchronously emits an event that would cause
933
 * it to re-enter the event loop. This would not be safe to call from within
934
 * the event loop. This wrapper ensures it is called at most once.
935
 */
936
static pbio_error_t bluetooth_btstack_handle_power_control(pbio_os_state_t *state, HCI_POWER_MODE power_mode, HCI_STATE end_state) {
704✔
937

938
    static bool busy_handling_power_control;
939

940
    PBIO_OS_ASYNC_BEGIN(state);
704✔
941

942
    if (busy_handling_power_control) {
64✔
943
        return PBIO_ERROR_AGAIN;
32✔
944
    }
945

946
    busy_handling_power_control = true;
32✔
947
    hci_power_control(power_mode); // causes synchronous re-entry.
32✔
948
    busy_handling_power_control = false;
32✔
949

950
    // Wait for the power state to take effect.
951
    PBIO_OS_AWAIT_UNTIL(state, hci_get_state() == end_state);
672✔
952

953
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
32✔
954
}
955

956
pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer) {
32✔
957

958
    static pbio_os_state_t sub;
959

960
    PBIO_OS_ASYNC_BEGIN(state);
32✔
961

962
    #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
963
    // Disconnect gracefully if connected to host.
964
    if (le_con_handle != HCI_CON_HANDLE_INVALID) {
16✔
965
        gap_disconnect(le_con_handle);
×
966
        PBIO_OS_AWAIT_UNTIL(state, le_con_handle == HCI_CON_HANDLE_INVALID);
×
967
    }
968
    #endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
969

970
    // Wait for power off.
971
    PBIO_OS_AWAIT(state, &sub, bluetooth_btstack_handle_power_control(&sub, HCI_POWER_OFF, HCI_STATE_OFF));
32✔
972

973
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
16✔
974
}
975

976
pbio_error_t pbdrv_bluetooth_controller_initialize(pbio_os_state_t *state, pbio_os_timer_t *timer) {
672✔
977

978
    static pbio_os_state_t sub;
979

980
    PBIO_OS_ASYNC_BEGIN(state);
672✔
981

982
    // Wait for power on.
983
    PBIO_OS_AWAIT(state, &sub, bluetooth_btstack_handle_power_control(&sub, HCI_POWER_ON, HCI_STATE_WORKING));
672✔
984

985
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
16✔
986
}
987

988
static void bluetooth_btstack_run_loop_init(void) {
16✔
989
    // Not used. Bluetooth process is started like a regular pbdrv process.
990
}
16✔
991

992
static btstack_linked_list_t data_sources;
993

994
static void bluetooth_btstack_run_loop_add_data_source(btstack_data_source_t *ds) {
1✔
995
    btstack_linked_list_add(&data_sources, &ds->item);
1✔
996
}
1✔
997

998
static bool bluetooth_btstack_run_loop_remove_data_source(btstack_data_source_t *ds) {
1✔
999
    return btstack_linked_list_remove(&data_sources, &ds->item);
1✔
1000
}
1001

1002
static void bluetooth_btstack_run_loop_enable_data_source_callbacks(btstack_data_source_t *ds, uint16_t callback_types) {
2✔
1003
    ds->flags |= callback_types;
2✔
1004
}
2✔
1005

1006
static void bluetooth_btstack_run_loop_disable_data_source_callbacks(btstack_data_source_t *ds, uint16_t callback_types) {
1✔
1007
    ds->flags &= ~callback_types;
1✔
1008
}
1✔
1009

1010
static btstack_linked_list_t timers;
1011

1012
static void bluetooth_btstack_run_loop_set_timer(btstack_timer_source_t *ts, uint32_t timeout_in_ms) {
136✔
1013
    ts->timeout = pbdrv_clock_get_ms() + timeout_in_ms;
136✔
1014
}
136✔
1015

1016
static void bluetooth_btstack_run_loop_add_timer(btstack_timer_source_t *ts) {
136✔
1017
    btstack_linked_item_t *it;
1018
    for (it = (void *)&timers; it->next; it = it->next) {
175✔
1019
        // don't add timer that's already in there
1020
        btstack_timer_source_t *next = (void *)it->next;
127✔
1021
        if (next == ts) {
127✔
1022
            // timer was already in the list!
1023
            assert(0);
×
1024
            return;
1025
        }
1026
        // exit if new timeout before list timeout
1027
        int32_t delta = btstack_time_delta(ts->timeout, next->timeout);
127✔
1028
        if (delta < 0) {
127✔
1029
            break;
88✔
1030
        }
1031
    }
1032

1033
    ts->item.next = it->next;
136✔
1034
    it->next = &ts->item;
136✔
1035
}
1036

1037
static bool bluetooth_btstack_run_loop_remove_timer(btstack_timer_source_t *ts) {
199✔
1038
    if (btstack_linked_list_remove(&timers, &ts->item)) {
199✔
1039
        return true;
119✔
1040
    }
1041
    return false;
80✔
1042
}
1043

1044
static void bluetooth_btstack_run_loop_execute(void) {
×
1045
    // not used
1046
}
×
1047

1048
static void bluetooth_btstack_run_loop_dump_timer(void) {
×
1049
    // not used
1050
}
×
1051

1052
static const btstack_run_loop_t bluetooth_btstack_run_loop = {
1053
    .init = bluetooth_btstack_run_loop_init,
1054
    .add_data_source = bluetooth_btstack_run_loop_add_data_source,
1055
    .remove_data_source = bluetooth_btstack_run_loop_remove_data_source,
1056
    .enable_data_source_callbacks = bluetooth_btstack_run_loop_enable_data_source_callbacks,
1057
    .disable_data_source_callbacks = bluetooth_btstack_run_loop_disable_data_source_callbacks,
1058
    .set_timer = bluetooth_btstack_run_loop_set_timer,
1059
    .add_timer = bluetooth_btstack_run_loop_add_timer,
1060
    .remove_timer = bluetooth_btstack_run_loop_remove_timer,
1061
    .execute = bluetooth_btstack_run_loop_execute,
1062
    .dump_timer = bluetooth_btstack_run_loop_dump_timer,
1063
    .get_time_ms = pbdrv_clock_get_ms,
1064
};
1065

1066
static bool do_poll_handler;
1067

1068
void pbdrv_bluetooth_btstack_run_loop_trigger(void) {
3✔
1069
    do_poll_handler = true;
3✔
1070
    pbio_os_request_poll();
3✔
1071
}
3✔
1072

1073
static pbio_os_process_t pbdrv_bluetooth_hci_process;
1074

1075
/**
1076
 * This process is slightly unusual in that it does not have a state. It is
1077
 * essentially just a poll handler.
1078
 */
1079
static pbio_error_t pbdrv_bluetooth_hci_process_thread(pbio_os_state_t *state, void *context) {
33,370✔
1080

1081
    if (do_poll_handler) {
33,370✔
1082
        do_poll_handler = false;
3✔
1083

1084
        btstack_data_source_t *ds, *next;
1085
        for (ds = (void *)data_sources; ds != NULL; ds = next) {
5✔
1086
            // cache pointer to next data_source to allow data source to remove itself
1087
            next = (void *)ds->item.next;
2✔
1088
            if (ds->flags & DATA_SOURCE_CALLBACK_POLL) {
2✔
1089
                ds->process(ds, DATA_SOURCE_CALLBACK_POLL);
1✔
1090
            }
1091
        }
1092
    }
1093

1094
    static pbio_os_timer_t btstack_timer = {
1095
        .duration = 1,
1096
    };
1097

1098
    if (pbio_os_timer_is_expired(&btstack_timer)) {
33,370✔
1099
        pbio_os_timer_extend(&btstack_timer);
32,514✔
1100

1101
        // process all BTStack timers in list that have expired
1102
        while (timers) {
32,536✔
1103
            btstack_timer_source_t *ts = (void *)timers;
32,536✔
1104
            int32_t delta = btstack_time_delta(ts->timeout, pbdrv_clock_get_ms());
32,536✔
1105
            if (delta > 0) {
32,536✔
1106
                // we have reached unexpired timers
1107
                break;
32,514✔
1108
            }
1109
            btstack_run_loop_remove_timer(ts);
22✔
1110
            ts->process(ts);
22✔
1111
        }
1112
    }
1113

1114
    // Also propagate non-btstack events like polls or timers.
1115
    propagate_event(NULL);
33,370✔
1116

1117
    return bluetooth_thread_err;
33,370✔
1118
}
1119

1120
// Have to be defined, but are not used.
1121
const uint32_t cc256x_init_script_size = 0;
1122
const uint8_t cc256x_init_script[] = {};
1123

1124
void pbdrv_bluetooth_init_hci(void) {
16✔
1125

1126
    static btstack_packet_callback_registration_t hci_event_callback_registration;
1127

1128
    #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
1129
    // don't need to init the whole struct, so doing this here
1130
    peripheral_singleton.con_handle = HCI_CON_HANDLE_INVALID;
16✔
1131
    #endif
1132

1133
    btstack_memory_init();
16✔
1134
    btstack_run_loop_init(&bluetooth_btstack_run_loop);
16✔
1135

1136
    hci_init(pdata->transport_instance(), pdata->transport_config());
16✔
1137
    hci_set_chipset(pdata->chipset_instance());
16✔
1138
    hci_set_control(pdata->control_instance());
16✔
1139

1140
    // REVISIT: do we need to call btstack_chipset_cc256x_set_power() or btstack_chipset_cc256x_set_power_vector()?
1141

1142
    hci_event_callback_registration.callback = &packet_handler;
16✔
1143
    hci_add_event_handler(&hci_event_callback_registration);
16✔
1144

1145
    l2cap_init();
16✔
1146

1147
    #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
1148
    // setup LE device DB
1149
    le_device_db_init();
16✔
1150

1151
    // setup security manager
1152
    sm_init();
16✔
1153
    sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
16✔
1154
    sm_set_authentication_requirements(SM_AUTHREQ_BONDING);
16✔
1155
    sm_set_er((uint8_t *)pdata->er_key);
16✔
1156
    sm_set_ir((uint8_t *)pdata->ir_key);
16✔
1157
    static btstack_packet_callback_registration_t sm_event_callback_registration;
1158
    sm_event_callback_registration.callback = &sm_packet_handler;
16✔
1159
    sm_add_event_handler(&sm_event_callback_registration);
16✔
1160

1161
    gap_random_address_set_mode(GAP_RANDOM_ADDRESS_NON_RESOLVABLE);
16✔
1162
    gap_set_max_number_peripheral_connections(2);
16✔
1163

1164
    // GATT Client setup
1165
    gatt_client_init();
16✔
1166

1167
    // setup ATT server
1168
    att_server_init(profile_data, att_read_callback, NULL);
16✔
1169

1170
    device_information_service_server_init();
16✔
1171
    device_information_service_server_set_firmware_revision(PBIO_VERSION_STR);
16✔
1172
    device_information_service_server_set_software_revision(PBIO_PROTOCOL_VERSION_STR);
16✔
1173
    device_information_service_server_set_pnp_id(0x01, LWP3_LEGO_COMPANY_ID, HUB_KIND, HUB_VARIANT);
16✔
1174

1175
    pybricks_service_server_init(pybricks_data_received, pybricks_configured);
16✔
1176
    nordic_spp_service_server_init(nordic_spp_packet_handler);
16✔
1177
    #endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
1178

1179
    bluetooth_thread_err = PBIO_ERROR_AGAIN;
16✔
1180
    bluetooth_thread_state = 0;
16✔
1181
    pbio_os_process_start(&pbdrv_bluetooth_hci_process, pbdrv_bluetooth_hci_process_thread, NULL);
16✔
1182
}
16✔
1183

1184
#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