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

pybricks / pybricks-micropython / 20463105558

23 Dec 2025 02:17PM UTC coverage: 55.82% (+0.004%) from 55.816%
20463105558

Pull #442

github

web-flow
Merge b3c670223 into d92d07d08
Pull Request #442: pbio/drv/bluetooth_btstack: Generalizations to prepare for POSIX variant.

-46 of 7 new or added lines in 5 files covered. (-657.14%)

133 existing lines in 1 file now uncovered.

4613 of 8264 relevant lines covered (55.82%)

16285644.64 hits per line

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

34.4
/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
#include <btstack_run_loop.h>
17

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

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

25
#include "bluetooth.h"
26
#include "bluetooth_btstack.h"
27

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

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

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

46
#define DEBUG 0
47

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

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

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

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

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

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

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

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

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

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

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

129
    return false;
34,601✔
130
}
131

132
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
133

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

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

157
    return ATT_ERROR_UNLIKELY_ERROR;
×
158
}
159

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

164
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
165

166
static pbio_os_state_t bluetooth_thread_state;
167
static pbio_os_state_t bluetooth_thread_err;
168

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

179
    event_packet = packet;
33,966✔
180

181
    if (bluetooth_thread_err == PBIO_ERROR_AGAIN) {
33,966✔
182
        bluetooth_thread_err = pbdrv_bluetooth_process_thread(&bluetooth_thread_state, NULL);
33,966✔
183
    }
184

185
    event_packet = NULL;
33,966✔
186
}
33,966✔
187

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

356
            break;
1✔
357

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

369
            break;
×
370

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

377
            gap_event_advertising_report_get_address(packet, address);
×
378

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

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

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

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

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

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

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

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

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

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

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

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

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

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

456
    bd_addr_t addr;
457
    bd_addr_type_t addr_type;
458

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

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

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

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

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

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

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

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

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

585
    scan_resp_data[9] = HUB_VARIANT;
1✔
586

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

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

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

597
    PBIO_OS_ASYNC_BEGIN(state);
13✔
598

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

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

604
    pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_ADVERTISING_PYBRICKS;
1✔
605

606
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
1✔
607
}
608

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

UNCOV
611
    PBIO_OS_ASYNC_BEGIN(state);
×
612

UNCOV
613
    gap_advertisements_enable(false);
×
614

615
    // REVISIT: use callback to await operation
616

UNCOV
617
    pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_NONE;
×
618

UNCOV
619
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
620
}
621

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

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

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

637
    static send_data_t send_data;
638

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

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

651
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
1✔
652
}
653

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

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

661
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
662

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

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

UNCOV
671
    PBIO_OS_ASYNC_BEGIN(state);
×
672

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

UNCOV
675
    peri->con_handle = HCI_CON_HANDLE_INVALID;
×
676

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

UNCOV
683
    PBIO_OS_AWAIT_UNTIL(state, ({
×
684
        if (peri->cancel || timed_out) {
685
            if (handset.con_state == CON_STATE_WAIT_ADV_IND || handset.con_state == CON_STATE_WAIT_SCAN_RSP) {
686
                gap_stop_scan();
687
            } else if (handset.con_state == CON_STATE_WAIT_CONNECT) {
688
                gap_connect_cancel();
689
            } else if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
690
                gap_disconnect(peri->con_handle);
691
            }
692
            handset.con_state = CON_STATE_NONE;
693
            return timed_out ? PBIO_ERROR_TIMEDOUT : PBIO_ERROR_CANCELED;
694
        }
695
        // if there is any failure to connect or 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_CONNECTED;
701
    }));
702

UNCOV
703
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
704
}
705

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

708
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
709

UNCOV
710
    PBIO_OS_ASYNC_BEGIN(state);
×
711

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

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

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

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

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

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

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

748
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
749

UNCOV
750
    PBIO_OS_ASYNC_BEGIN(state);
×
751

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

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

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

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

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

UNCOV
781
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
782
}
783

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

786
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
787

UNCOV
788
    PBIO_OS_ASYNC_BEGIN(state);
×
789

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

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

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

UNCOV
813
    PBIO_OS_ASYNC_END(att_error_to_pbio_error(gatt_event_query_complete_get_att_status(event_packet)));
×
814
}
815

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

818
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
819

UNCOV
820
    PBIO_OS_ASYNC_BEGIN(state);
×
821

UNCOV
822
    if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
×
823
        gap_disconnect(peri->con_handle);
×
824
    }
825

UNCOV
826
    PBIO_OS_AWAIT_UNTIL(state, peri->con_handle == HCI_CON_HANDLE_INVALID);
×
827

828
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
829
}
830

831
pbio_error_t pbdrv_bluetooth_start_broadcasting_func(pbio_os_state_t *state, void *context) {
×
832

833
    PBIO_OS_ASYNC_BEGIN(state);
×
834

UNCOV
835
    gap_advertisements_set_data(pbdrv_bluetooth_broadcast_data_size, pbdrv_bluetooth_broadcast_data);
×
836

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

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

UNCOV
847
    PBIO_OS_AWAIT_UNTIL(state, event_packet && HCI_EVENT_IS_COMMAND_COMPLETE(event_packet, hci_le_set_advertise_enable));
×
848

849
    pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING;
×
850

UNCOV
851
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
852
}
853

854
pbio_error_t pbdrv_bluetooth_start_observing_func(pbio_os_state_t *state, void *context) {
×
855

856
    PBIO_OS_ASYNC_BEGIN(state);
×
857

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

864
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
865
}
866

UNCOV
867
pbio_error_t pbdrv_bluetooth_stop_observing_func(pbio_os_state_t *state, void *context) {
×
868

869
    PBIO_OS_ASYNC_BEGIN(state);
×
870

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

877
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
×
878
}
879

880

881
#else // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
882

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

917
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
918

UNCOV
919
const char *pbdrv_bluetooth_get_hub_name(void) {
×
UNCOV
920
    return pbdrv_bluetooth_hub_name;
×
921
}
922

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

UNCOV
929
void pbdrv_bluetooth_controller_reset_hard(void) {
×
UNCOV
930
    hci_power_control(HCI_POWER_OFF);
×
931
}
×
932

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

940
    static bool busy_handling_power_control;
941

942
    PBIO_OS_ASYNC_BEGIN(state);
704✔
943

944
    if (busy_handling_power_control) {
64✔
945
        return PBIO_ERROR_AGAIN;
32✔
946
    }
947

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

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

955
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
32✔
956
}
957

958
pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer) {
32✔
959

960
    #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
961
    static pbio_os_state_t sub;
962

963
    PBIO_OS_ASYNC_BEGIN(state);
32✔
964

965
    // Disconnect gracefully if connected to host.
966
    if (le_con_handle != HCI_CON_HANDLE_INVALID) {
16✔
UNCOV
967
        gap_disconnect(le_con_handle);
×
UNCOV
968
        PBIO_OS_AWAIT_UNTIL(state, le_con_handle == HCI_CON_HANDLE_INVALID);
×
969
    }
970

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

974
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
16✔
975

976
    #else
977
    // Revisit: CC2560X seems to dislike the reset command, but this is not
978
    // LE specific. Find a way to properly reset.
979
    return PBIO_ERROR_NOT_IMPLEMENTED;
980
    #endif
981
}
982

983
pbio_error_t pbdrv_bluetooth_controller_initialize(pbio_os_state_t *state, pbio_os_timer_t *timer) {
672✔
984

985
    static pbio_os_state_t sub;
986

987
    PBIO_OS_ASYNC_BEGIN(state);
672✔
988

989
    // Wait for power on.
990
    PBIO_OS_AWAIT(state, &sub, bluetooth_btstack_handle_power_control(&sub, HCI_POWER_ON, HCI_STATE_WORKING));
672✔
991

992
    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
16✔
993
}
994

995
static void bluetooth_btstack_run_loop_set_timer(btstack_timer_source_t *ts, uint32_t timeout_in_ms) {
136✔
996
    ts->timeout = pbdrv_clock_get_ms() + timeout_in_ms;
136✔
997
}
136✔
998

UNCOV
999
static void bluetooth_btstack_run_loop_execute(void) {
×
1000
    // not used
UNCOV
1001
}
×
1002

1003
static bool do_poll_handler;
1004

1005
static void pbdrv_bluetooth_btstack_run_loop_trigger(void) {
3✔
1006
    do_poll_handler = true;
3✔
1007
    pbio_os_request_poll();
3✔
1008
}
3✔
1009

1010
static const btstack_run_loop_t bluetooth_btstack_run_loop = {
1011
    .init = btstack_run_loop_base_init,
1012
    .add_data_source = btstack_run_loop_base_add_data_source,
1013
    .remove_data_source = btstack_run_loop_base_remove_data_source,
1014
    .enable_data_source_callbacks = btstack_run_loop_base_enable_data_source_callbacks,
1015
    .disable_data_source_callbacks = btstack_run_loop_base_disable_data_source_callbacks,
1016
    .set_timer = bluetooth_btstack_run_loop_set_timer,
1017
    .add_timer = btstack_run_loop_base_add_timer,
1018
    .remove_timer = btstack_run_loop_base_remove_timer,
1019
    .execute = bluetooth_btstack_run_loop_execute,
1020
    .dump_timer = btstack_run_loop_base_dump_timer,
1021
    .get_time_ms = pbdrv_clock_get_ms,
1022
    .poll_data_sources_from_irq = pbdrv_bluetooth_btstack_run_loop_trigger,
1023
};
1024

1025
static pbio_os_process_t pbdrv_bluetooth_hci_process;
1026

1027
/**
1028
 * This process is slightly unusual in that it does not have a state. It is
1029
 * essentially just a poll handler.
1030
 */
1031
static pbio_error_t pbdrv_bluetooth_hci_process_thread(pbio_os_state_t *state, void *context) {
33,325✔
1032

1033
    if (do_poll_handler) {
33,325✔
1034
        do_poll_handler = false;
3✔
1035
        btstack_run_loop_base_poll_data_sources();
3✔
1036
    }
1037

1038
    pbdrv_bluetooth_btstack_platform_poll();
33,325✔
1039

1040
    static pbio_os_timer_t btstack_timer = {
1041
        .duration = 1,
1042
    };
1043

1044
    if (pbio_os_timer_is_expired(&btstack_timer)) {
33,325✔
1045
        pbio_os_timer_extend(&btstack_timer);
32,469✔
1046
        btstack_run_loop_base_process_timers(pbdrv_clock_get_ms());
32,469✔
1047
    }
1048

1049
    // Also propagate non-btstack events like polls or timers.
1050
    propagate_event(NULL);
33,325✔
1051

1052
    return bluetooth_thread_err;
33,325✔
1053
}
1054

1055
// Have to be defined, but are not used.
1056
const uint32_t cc256x_init_script_size = 0;
1057
const uint8_t cc256x_init_script[] = {};
1058

1059
void pbdrv_bluetooth_init_hci(void) {
16✔
1060

1061
    // Proceed to start Bluetooth process only if platform init passes.
1062
    pbio_error_t err = pbdrv_bluetooth_btstack_platform_init();
16✔
1063
    if (err != PBIO_SUCCESS) {
16✔
NEW
UNCOV
1064
        return;
×
1065
    }
1066

1067
    static btstack_packet_callback_registration_t hci_event_callback_registration;
1068

1069
    #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
1070
    // don't need to init the whole struct, so doing this here
1071
    peripheral_singleton.con_handle = HCI_CON_HANDLE_INVALID;
16✔
1072
    #endif
1073

1074
    btstack_memory_init();
16✔
1075
    btstack_run_loop_init(&bluetooth_btstack_run_loop);
16✔
1076

1077
    hci_init(pdata->transport_instance(), pdata->transport_config());
16✔
1078
    hci_set_chipset(pdata->chipset_instance());
16✔
1079
    hci_set_control(pdata->control_instance());
16✔
1080

1081
    // REVISIT: do we need to call btstack_chipset_cc256x_set_power() or btstack_chipset_cc256x_set_power_vector()?
1082

1083
    hci_event_callback_registration.callback = &packet_handler;
16✔
1084
    hci_add_event_handler(&hci_event_callback_registration);
16✔
1085

1086
    l2cap_init();
16✔
1087

1088
    #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
1089
    // setup LE device DB
1090
    le_device_db_init();
16✔
1091

1092
    // setup security manager
1093
    sm_init();
16✔
1094
    sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
16✔
1095
    sm_set_authentication_requirements(SM_AUTHREQ_BONDING);
16✔
1096
    sm_set_er((uint8_t *)pdata->er_key);
16✔
1097
    sm_set_ir((uint8_t *)pdata->ir_key);
16✔
1098
    static btstack_packet_callback_registration_t sm_event_callback_registration;
1099
    sm_event_callback_registration.callback = &sm_packet_handler;
16✔
1100
    sm_add_event_handler(&sm_event_callback_registration);
16✔
1101

1102
    gap_random_address_set_mode(GAP_RANDOM_ADDRESS_NON_RESOLVABLE);
16✔
1103
    gap_set_max_number_peripheral_connections(2);
16✔
1104

1105
    // GATT Client setup
1106
    gatt_client_init();
16✔
1107

1108
    // setup ATT server
1109
    att_server_init(profile_data, att_read_callback, NULL);
16✔
1110

1111
    device_information_service_server_init();
16✔
1112
    device_information_service_server_set_firmware_revision(PBIO_VERSION_STR);
16✔
1113
    device_information_service_server_set_software_revision(PBIO_PROTOCOL_VERSION_STR);
16✔
1114
    device_information_service_server_set_pnp_id(0x01, LWP3_LEGO_COMPANY_ID, HUB_KIND, HUB_VARIANT);
16✔
1115

1116
    pybricks_service_server_init(pybricks_data_received, pybricks_configured);
16✔
1117
    nordic_spp_service_server_init(nordic_spp_packet_handler);
16✔
1118
    #endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
1119

1120
    bluetooth_thread_err = PBIO_ERROR_AGAIN;
16✔
1121
    bluetooth_thread_state = 0;
16✔
1122
    pbio_os_process_start(&pbdrv_bluetooth_hci_process, pbdrv_bluetooth_hci_process_thread, NULL);
16✔
1123
}
1124

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