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

pybricks / pybricks-micropython / 8559323062

04 Apr 2024 06:10PM UTC coverage: 56.572% (+0.5%) from 56.071%
8559323062

Pull #245

github

web-flow
Merge 1d0f9874a into a0d063b56
Pull Request #245: Unify three bluetooth de-init tasks and allow exit on shutdown request

0 of 14 new or added lines in 1 file covered. (0.0%)

9 existing lines in 2 files now uncovered.

3749 of 6627 relevant lines covered (56.57%)

20344156.98 hits per line

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

21.91
/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 <inttypes.h>
11

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

18
#include <pbdrv/bluetooth.h>
19
#include <pbio/protocol.h>
20
#include <pbio/task.h>
21
#include <pbio/version.h>
22

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

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

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

42
#if 0
43
#include <pbdrv/../../drv/ioport/ioport_debug_uart.h>
44
#define DEBUG_PRINT pbdrv_ioport_debug_uart_printf
45
#else
46
#define DEBUG_PRINT(...)
47
#endif
48

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

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

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

88
// The peripheral singleton. Used to connect to a device like the LEGO Remote.
89
pbdrv_bluetooth_peripheral_t peripheral_singleton;
90

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

97
LIST(task_queue);
98
static hci_con_handle_t le_con_handle = HCI_CON_HANDLE_INVALID;
99
static hci_con_handle_t pybricks_con_handle = HCI_CON_HANDLE_INVALID;
100
static hci_con_handle_t uart_con_handle = HCI_CON_HANDLE_INVALID;
101
static pbdrv_bluetooth_on_event_t bluetooth_on_event;
102
static pbdrv_bluetooth_receive_handler_t receive_handler;
103
static pup_handset_t handset;
104
static uint8_t *event_packet;
105
static const pbdrv_bluetooth_btstack_platform_data_t *pdata = &pbdrv_bluetooth_btstack_platform_data;
106

107
static bool is_broadcasting;
108
static bool is_observing;
109
static pbdrv_bluetooth_start_observing_callback_t observe_callback;
110

111
// note on baud rate: with a 48MHz clock, 3000000 baud is the highest we can
112
// go with LL_USART_OVERSAMPLING_16. With LL_USART_OVERSAMPLING_8 we could go
113
// to 4000000, which is the max rating of the CC2564C.
114

115
static const hci_transport_config_uart_t config = {
116
    .type = HCI_TRANSPORT_CONFIG_UART,
117
    .baudrate_init = 115200,
118
    .baudrate_main = 3000000,
119
    .flowcontrol = 1,
120
    .device_name = NULL,
121
};
122

123
/**
124
 * Converts BTStack error to most appropriate PBIO error.
125
 * @param [in]  status      The BTStack error
126
 * @return                  The PBIO error.
127
 */
128
static pbio_error_t att_error_to_pbio_error(uint8_t status) {
×
129
    switch (status) {
×
130
        case ATT_ERROR_SUCCESS:
×
131
            return PBIO_SUCCESS;
×
132
        case ATT_ERROR_HCI_DISCONNECT_RECEIVED:
×
133
            return PBIO_ERROR_NO_DEV;
×
134
        case ATT_ERROR_TIMEOUT:
×
135
            return PBIO_ERROR_TIMEDOUT;
×
136
        default:
×
137
            return PBIO_ERROR_FAILED;
×
138
    }
139
}
140

141
static void pybricks_can_send(void *context) {
4✔
142
    pbdrv_bluetooth_send_context_t *send = context;
4✔
143

144
    pybricks_service_server_send(pybricks_con_handle, send->data, send->size);
4✔
145
    send->done();
4✔
146
}
4✔
147

148
static pbio_pybricks_error_t pybricks_data_received(hci_con_handle_t tx_con_handle, const uint8_t *data, uint16_t size) {
1✔
149
    if (receive_handler) {
1✔
150
        return receive_handler(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS, data, size);
1✔
151
    }
152

153
    return ATT_ERROR_UNLIKELY_ERROR;
×
154
}
155

156
static void pybricks_configured(hci_con_handle_t tx_con_handle, uint16_t value) {
2✔
157
    pybricks_con_handle = value ? tx_con_handle : HCI_CON_HANDLE_INVALID;
2✔
158
}
2✔
159

160
static void nordic_can_send(void *context) {
×
161
    pbdrv_bluetooth_send_context_t *send = context;
×
162

163
    nordic_spp_service_server_send(uart_con_handle, send->data, send->size);
×
164
    send->done();
×
165
}
×
166

167
/**
168
 * Queues a tasks and runs the first iteration if there is no other task already
169
 * running.
170
 *
171
 * @param [in]  task    An uninitialized task.
172
 * @param [in]  thread  The task thread to attach to the task.
173
 * @param [in]  context The context to attach to the task.
174
 */
175
static void start_task(pbio_task_t *task, pbio_task_thread_t thread, void *context) {
×
176
    pbio_task_init(task, thread, context);
×
177

178
    if (list_head(task_queue) != NULL || !pbio_task_run_once(task)) {
×
179
        list_add(task_queue, task);
×
180
    }
181
}
×
182

183
/**
184
 * Runs tasks that may be waiting for event and notifies external subscriber.
185
 *
186
 * @param [in]  packet  Pointer to the raw packet data.
187
 */
188
static void propagate_event(uint8_t *packet) {
148✔
189
    event_packet = packet;
148✔
190

191
    for (;;) {
×
192
        pbio_task_t *current_task = list_head(task_queue);
148✔
193

194
        if (!current_task) {
148✔
195
            break;
148✔
196
        }
197

198
        if (current_task->status != PBIO_ERROR_AGAIN || pbio_task_run_once(current_task)) {
×
199
            // remove the task from the queue only if the task is complete
200
            list_remove(task_queue, current_task);
×
201
            // then start the next task
202
            continue;
×
203
        }
204

205
        break;
×
206
    }
207

208
    event_packet = NULL;
148✔
209

210
    if (bluetooth_on_event) {
148✔
211
        bluetooth_on_event();
148✔
212
    }
213
}
148✔
214

215
static void nordic_spp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
×
216
    switch (packet_type) {
×
217
        case HCI_EVENT_PACKET:
×
218
            if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META) {
×
219
                break;
×
220
            }
221

222
            switch (hci_event_gattservice_meta_get_subevent_code(packet)) {
×
223
                case GATTSERVICE_SUBEVENT_SPP_SERVICE_CONNECTED:
×
224
                    uart_con_handle = gattservice_subevent_spp_service_connected_get_con_handle(packet);
×
225
                    break;
×
226
                case GATTSERVICE_SUBEVENT_SPP_SERVICE_DISCONNECTED:
×
227
                    uart_con_handle = HCI_CON_HANDLE_INVALID;
×
228
                    break;
×
229
                default:
×
230
                    break;
×
231
            }
232

233
            break;
×
234
        case RFCOMM_DATA_PACKET:
×
235
            if (receive_handler) {
×
236
                receive_handler(PBDRV_BLUETOOTH_CONNECTION_UART, packet, size);
×
237
            }
238
            break;
×
239
        default:
×
240
            break;
×
241
    }
242

243
    propagate_event(packet);
×
244
}
×
245

246
// currently, this function just handles the Powered Up handset control.
247
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
148✔
248

249
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
148✔
250

251
    switch (hci_event_packet_get_type(packet)) {
148✔
252
        case GATT_EVENT_SERVICE_QUERY_RESULT: {
×
253
            // Service discovery not used.
254
            gatt_client_service_t service;
255
            gatt_event_service_query_result_get_service(packet, &service);
×
256
            break;
×
257
        }
258
        case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: {
×
259
            gatt_client_characteristic_t found_char;
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.
262
            if ((found_char.properties & peri->char_now->properties) == peri->char_now->properties) {
×
263
                peri->char_now->handle = found_char.value_handle;
×
264
                gatt_event_characteristic_query_result_get_characteristic(packet, &handset.current_char);
×
265
            }
266
            break;
×
267
        }
268
        case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT: {
×
269
            hci_con_handle_t handle = gatt_event_characteristic_value_query_result_get_handle(packet);
×
270
            uint16_t value_handle = gatt_event_characteristic_value_query_result_get_value_handle(packet);
×
271
            uint16_t value_length = gatt_event_characteristic_value_query_result_get_value_length(packet);
×
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
            }
276
            break;
×
277
        }
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;
×
282
            } else if (handset.con_state == CON_STATE_WAIT_DISCOVER_CHARACTERISTICS) {
×
283

284
                // Discovered characteristics, ready enable notifications.
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;
×
288
                    break;
×
289
                }
290

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);
294
                if (handset.btstack_error == ERROR_CODE_SUCCESS) {
×
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
                }
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
            }
308
            break;
×
309

310
        case GATT_EVENT_NOTIFICATION: {
×
311
            if (gatt_event_notification_get_handle(packet) != peri->con_handle) {
×
312
                break;
×
313
            }
314
            if (peri->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->notification_handler(PBDRV_BLUETOOTH_CONNECTION_PERIPHERAL, 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
            } 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->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 (observe_callback) {
×
379
                int8_t rssi = gap_event_advertising_report_get_rssi(packet);
×
380
                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->match_adv) {
×
387
                    adv_flags = peri->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->match_adv_rsp) {
×
409
                    rsp_flags = peri->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

437
        default:
146✔
438
            break;
146✔
439
    }
440

441
    propagate_event(packet);
148✔
442
}
148✔
443

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

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

453
    bd_addr_t addr;
454
    bd_addr_type_t addr_type;
455

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

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

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

547
        default:
×
548
            return 0;
×
549
    }
550
}
551

552
void pbdrv_bluetooth_init(void) {
15✔
553
    static btstack_packet_callback_registration_t hci_event_callback_registration;
554
    static btstack_packet_callback_registration_t sm_event_callback_registration;
555

556
    // don't need to init the whole struct, so doing this here
557
    peripheral_singleton.con_handle = HCI_CON_HANDLE_INVALID;
15✔
558

559
    btstack_memory_init();
15✔
560
    btstack_run_loop_init(pbdrv_bluetooth_btstack_run_loop_contiki_get_instance());
15✔
561

562
    hci_init(hci_transport_h4_instance_for_uart(pdata->uart_block_instance()), &config);
15✔
563
    hci_set_chipset(pdata->chipset_instance());
15✔
564
    hci_set_control(pdata->control_instance());
15✔
565

566
    // REVISIT: do we need to call btstack_chipset_cc256x_set_power() or btstack_chipset_cc256x_set_power_vector()?
567

568
    hci_event_callback_registration.callback = &packet_handler;
15✔
569
    hci_add_event_handler(&hci_event_callback_registration);
15✔
570

571
    l2cap_init();
15✔
572

573
    // setup LE device DB
574
    le_device_db_init();
15✔
575

576
    // setup security manager
577
    sm_init();
15✔
578
    sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
15✔
579
    sm_set_authentication_requirements(SM_AUTHREQ_BONDING);
15✔
580
    sm_set_er((uint8_t *)pdata->er_key);
15✔
581
    sm_set_ir((uint8_t *)pdata->ir_key);
15✔
582
    sm_event_callback_registration.callback = &sm_packet_handler;
15✔
583
    sm_add_event_handler(&sm_event_callback_registration);
15✔
584

585
    gap_random_address_set_mode(GAP_RANDOM_ADDRESS_NON_RESOLVABLE);
15✔
586
    gap_set_max_number_peripheral_connections(2);
15✔
587

588
    // GATT Client setup
589
    gatt_client_init();
15✔
590

591
    // setup ATT server
592
    att_server_init(profile_data, att_read_callback, NULL);
15✔
593

594
    device_information_service_server_init();
15✔
595
    device_information_service_server_set_firmware_revision(PBIO_VERSION_STR);
15✔
596
    device_information_service_server_set_software_revision(PBIO_PROTOCOL_VERSION_STR);
15✔
597
    device_information_service_server_set_pnp_id(0x01, LWP3_LEGO_COMPANY_ID, HUB_KIND, HUB_VARIANT);
15✔
598

599
    pybricks_service_server_init(pybricks_data_received, pybricks_configured);
15✔
600
    nordic_spp_service_server_init(nordic_spp_packet_handler);
15✔
601
}
15✔
602

603
void pbdrv_bluetooth_power_on(bool on) {
1✔
604
    hci_power_control(on ? HCI_POWER_ON : HCI_POWER_OFF);
1✔
605

606
    // When powering off, cancel all pending tasks.
607
    if (!on) {
1✔
608
        pbio_task_t *task;
609

610
        while ((task = list_pop(task_queue)) != NULL) {
×
611
            if (task->status == PBIO_ERROR_AGAIN) {
×
612
                task->status = PBIO_ERROR_CANCELED;
×
613
            }
614
        }
615
    }
616
}
1✔
617

618
bool pbdrv_bluetooth_is_ready(void) {
1✔
619
    return hci_get_state() != HCI_STATE_OFF;
1✔
620
}
621

622
const char *pbdrv_bluetooth_get_hub_name(void) {
×
623
    return pbdrv_bluetooth_hub_name;
×
624
}
625

626
const char *pbdrv_bluetooth_get_fw_version(void) {
×
627
    // REVISIT: this should be linked to the init script as it can be updated in software
628
    // init script version
629
    return "v1.4";
×
630
}
631

NEW
632
static PT_THREAD(noop_task(struct pt *pt, pbio_task_t *task)) {
×
NEW
633
    PT_BEGIN(pt);
×
NEW
634
    task->status = PBIO_SUCCESS;
×
NEW
635
    PT_END(pt);
×
636
}
637

NEW
638
void pbdrv_bluetooth_queue_noop(pbio_task_t *task) {
×
NEW
639
    start_task(task, noop_task, NULL);
×
NEW
640
}
×
641

642
static void init_advertising_data(void) {
1✔
643
    bd_addr_t null_addr = { };
1✔
644
    gap_advertisements_set_params(0x30, 0x30, PBDRV_BLUETOOTH_AD_TYPE_ADV_IND, 0x00, null_addr, 0x07, 0x00);
1✔
645

646
    static const uint8_t adv_data[] = {
647
        // Flags general discoverable, BR/EDR not supported
648
        2, BLUETOOTH_DATA_TYPE_FLAGS, 0x06,
649
        // Pybricks service
650
        17, BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
651
        0xef, 0xae, 0xe4, 0x51, 0x80, 0x6d, 0xf4, 0x89, 0xda, 0x46, 0x80, 0x82, 0x01, 0x00, 0xf5, 0xc5,
652
        // Tx Power
653
        2, BLUETOOTH_DATA_TYPE_TX_POWER_LEVEL, 0,
654
    };
655

656
    _Static_assert(sizeof(adv_data) <= 31, "31 octet max");
657

658
    gap_advertisements_set_data(sizeof(adv_data), (uint8_t *)adv_data);
1✔
659

660
    static uint8_t scan_resp_data[31] = {
661
        10, BLUETOOTH_DATA_TYPE_SERVICE_DATA,
662
        // used to identify which hub - Device Information Service (DIS).
663
        // 0x2A50 - service UUID - PnP ID characteristic UUID
664
        // 0x01 - Vendor ID Source Field - Bluetooth SIG-assigned ID
665
        // 0x0397 - Vendor ID Field - LEGO company identifier
666
        // 0x00XX - Product ID Field - hub kind
667
        // 0x00XX - Product Version Field - product variant
668
        0x50, 0x2a, 0x01, 0x97, 0x03, HUB_KIND, 0x00, 0x00, 0x00,
669
        0, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME,
670
    };
671

672
    scan_resp_data[9] = HUB_VARIANT;
1✔
673

674
    uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name);
1✔
675
    scan_resp_data[11] = hub_name_len + 1;
1✔
676
    memcpy(&scan_resp_data[13], pbdrv_bluetooth_hub_name, hub_name_len);
1✔
677
    _Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max");
678

679
    gap_scan_response_set_data(13 + hub_name_len, scan_resp_data);
1✔
680
}
1✔
681

682
void pbdrv_bluetooth_start_advertising(void) {
1✔
683
    init_advertising_data();
1✔
684
    gap_advertisements_enable(true);
1✔
685
}
1✔
686

687
void pbdrv_bluetooth_stop_advertising(void) {
×
688
    gap_advertisements_enable(false);
×
689
}
×
690

691
bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection) {
94✔
692
    if (connection == PBDRV_BLUETOOTH_CONNECTION_LE && le_con_handle != HCI_CON_HANDLE_INVALID) {
94✔
693
        return true;
11✔
694
    }
695

696
    if (connection == PBDRV_BLUETOOTH_CONNECTION_PYBRICKS && pybricks_con_handle != HCI_CON_HANDLE_INVALID) {
83✔
697
        return true;
10✔
698
    }
699

700
    if (connection == PBDRV_BLUETOOTH_CONNECTION_UART && uart_con_handle != HCI_CON_HANDLE_INVALID) {
73✔
701
        return true;
×
702
    }
703

704
    if (connection == PBDRV_BLUETOOTH_CONNECTION_PERIPHERAL && peripheral_singleton.con_handle != HCI_CON_HANDLE_INVALID) {
73✔
705
        return true;
×
706
    }
707

708
    return false;
73✔
709
}
710

711
void pbdrv_bluetooth_set_on_event(pbdrv_bluetooth_on_event_t on_event) {
1✔
712
    bluetooth_on_event = on_event;
1✔
713
}
1✔
714

715
void pbdrv_bluetooth_send(pbdrv_bluetooth_send_context_t *context) {
4✔
716
    static btstack_context_callback_registration_t send_request;
717

718
    send_request.context = context;
4✔
719

720
    if (context->connection == PBDRV_BLUETOOTH_CONNECTION_PYBRICKS) {
4✔
721
        send_request.callback = &pybricks_can_send;
4✔
722
        pybricks_service_server_request_can_send_now(&send_request, pybricks_con_handle);
4✔
723
    } else if (context->connection == PBDRV_BLUETOOTH_CONNECTION_UART) {
×
724
        send_request.callback = &nordic_can_send;
×
725
        nordic_spp_service_server_request_can_send_now(&send_request, uart_con_handle);
×
726
    }
727
}
4✔
728

729
void pbdrv_bluetooth_set_receive_handler(pbdrv_bluetooth_receive_handler_t handler) {
1✔
730
    receive_handler = handler;
1✔
731
}
1✔
732

733
static void start_observing(void) {
×
734
    gap_set_scan_params(0, 0x30, 0x30, 0);
×
735
    gap_start_scan();
×
736
}
×
737

738
static PT_THREAD(peripheral_scan_and_connect_task(struct pt *pt, pbio_task_t *task)) {
×
739

740
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
741

742
    PT_BEGIN(pt);
×
743

744
    memset(&handset, 0, sizeof(handset));
×
745

746
    peri->con_handle = HCI_CON_HANDLE_INVALID;
×
747

748
    // active scanning to get scan response data.
749
    // scan interval: 48 * 0.625ms = 30ms
750
    gap_set_scan_params(1, 0x30, 0x30, 0);
×
751
    gap_start_scan();
×
752
    handset.con_state = CON_STATE_WAIT_ADV_IND;
×
753

754
    PT_WAIT_UNTIL(pt, ({
×
755
        if (task->cancel) {
756
            goto cancel;
757
        }
758

759
        // if there is any failure to connect or error while enumerating
760
        // attributes, con_state will be set to CON_STATE_NONE
761
        if (handset.con_state == CON_STATE_NONE) {
762
            task->status = PBIO_ERROR_FAILED;
763
            PT_EXIT(pt);
764
        }
765

766
        handset.con_state == CON_STATE_CONNECTED;
767
    }));
768

769
    task->status = PBIO_SUCCESS;
×
770
    goto out;
×
771

772
cancel:
×
773
    if (handset.con_state == CON_STATE_WAIT_ADV_IND || handset.con_state == CON_STATE_WAIT_SCAN_RSP) {
×
774
        gap_stop_scan();
×
775
    } else if (handset.con_state == CON_STATE_WAIT_CONNECT) {
×
776
        gap_connect_cancel();
×
777
    } else if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
×
778
        gap_disconnect(peri->con_handle);
×
779
    }
780
    handset.con_state = CON_STATE_NONE;
×
781
    task->status = PBIO_ERROR_CANCELED;
×
782

783
out:
×
784
    // restore observing state
785
    if (is_observing) {
×
786
        start_observing();
×
787
    }
788

789
    PT_END(pt);
×
790
}
791

792
static PT_THREAD(periperal_discover_characteristic_task(struct pt *pt, pbio_task_t *task)) {
×
793
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
794
    PT_BEGIN(pt);
×
795

796
    if (handset.con_state != CON_STATE_CONNECTED) {
×
797
        task->status = PBIO_ERROR_FAILED;
×
798
        PT_EXIT(pt);
×
799
    }
800

801
    handset.con_state = CON_STATE_WAIT_DISCOVER_CHARACTERISTICS;
×
802
    uint16_t handle_max = peri->char_now->handle_max ? peri->char_now->handle_max : 0xffff;
×
803
    handset.btstack_error = peri->char_now->uuid16 ?
×
804
        gatt_client_discover_characteristics_for_handle_range_by_uuid16(
×
805
        packet_handler, peri->con_handle, 0x0001, handle_max, peri->char_now->uuid16) :
×
806
        gatt_client_discover_characteristics_for_handle_range_by_uuid128(
×
807
        packet_handler, peri->con_handle, 0x0001, handle_max, peri->char_now->uuid128);
×
808

809
    if (handset.btstack_error != ERROR_CODE_SUCCESS) {
×
810
        // configuration failed for some reason, so disconnect
811
        gap_disconnect(peri->con_handle);
×
812
        handset.con_state = CON_STATE_WAIT_DISCONNECT;
×
813
        handset.disconnect_reason = DISCONNECT_REASON_DISCOVER_CHARACTERISTIC_FAILED;
×
814
    }
815

816
    PT_WAIT_UNTIL(pt, ({
×
817
        if (task->cancel) {
818
            goto cancel;
819
        }
820

821
        // if there is any error while enumerating
822
        // attributes, con_state will be set to CON_STATE_NONE
823
        if (handset.con_state == CON_STATE_NONE) {
824
            task->status = PBIO_ERROR_FAILED;
825
            PT_EXIT(pt);
826
        }
827

828
        handset.con_state == CON_STATE_DISCOVERY_AND_NOTIFICATIONS_COMPLETE;
829
    }));
830

831
    // State state back to simply connected, so we can discover other characteristics.
832
    handset.con_state = CON_STATE_CONNECTED;
×
833

834
    task->status = peri->char_now->handle ? PBIO_SUCCESS : PBIO_ERROR_FAILED;
×
835
    PT_EXIT(pt);
×
836

837
cancel:
×
838
    if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
×
839
        gap_disconnect(peri->con_handle);
×
840
    }
841
    handset.con_state = CON_STATE_NONE;
×
842
    task->status = PBIO_ERROR_CANCELED;
×
843

844
    PT_END(pt);
×
845
}
846

847
void pbdrv_bluetooth_peripheral_scan_and_connect(pbio_task_t *task, pbdrv_bluetooth_ad_match_t match_adv, pbdrv_bluetooth_ad_match_t match_adv_rsp, pbdrv_bluetooth_receive_handler_t notification_handler, pbdrv_bluetooth_peripheral_options_t options) {
×
848
    // Unset previous bluetooth addresses and other state variables.
849
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
850
    memset(peri, 0, sizeof(pbdrv_bluetooth_peripheral_t));
×
851

852
    // Set scan filters and notification handler, then start scannning.
853
    peri->match_adv = match_adv;
×
854
    peri->match_adv_rsp = match_adv_rsp;
×
855
    peri->notification_handler = notification_handler;
×
856
    peri->options = options;
×
857
    start_task(task, peripheral_scan_and_connect_task, NULL);
×
858
}
×
859

860
void pbdrv_bluetooth_periperal_discover_characteristic(pbio_task_t *task, pbdrv_bluetooth_peripheral_char_t *characteristic) {
×
861
    characteristic->handle = 0;
×
862
    peripheral_singleton.char_now = characteristic;
×
863
    start_task(task, periperal_discover_characteristic_task, NULL);
×
864
}
×
865

866
static PT_THREAD(periperal_read_characteristic_task(struct pt *pt, pbio_task_t *task)) {
×
867
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
868
    PT_BEGIN(pt);
×
869

870
    if (handset.con_state != CON_STATE_CONNECTED) {
×
871
        task->status = PBIO_ERROR_FAILED;
×
872
        PT_EXIT(pt);
×
873
    }
874

875
    gatt_client_characteristic_t characteristic = {
×
876
        .value_handle = peri->char_now->handle,
×
877
    };
878
    handset.btstack_error = gatt_client_read_value_of_characteristic(packet_handler, peri->con_handle, &characteristic);
×
879

880
    if (handset.btstack_error == ERROR_CODE_SUCCESS) {
×
881
        handset.con_state = CON_STATE_WAIT_READ_CHARACTERISTIC;
×
882
    } else {
883
        // configuration failed for some reason, so disconnect
884
        gap_disconnect(peri->con_handle);
×
885
        handset.con_state = CON_STATE_WAIT_DISCONNECT;
×
886
        handset.disconnect_reason = DISCONNECT_REASON_DISCOVER_CHARACTERISTIC_FAILED;
×
887
    }
888

889
    PT_WAIT_UNTIL(pt, ({
×
890
        if (task->cancel) {
891
            goto cancel;
892
        }
893

894
        // if there is any error while reading, con_state will be set to CON_STATE_NONE
895
        if (handset.con_state == CON_STATE_NONE) {
896
            task->status = PBIO_ERROR_FAILED;
897
            PT_EXIT(pt);
898
        }
899

900
        handset.con_state == CON_STATE_READ_CHARACTERISTIC_COMPLETE;
901
    }));
902

903
    // State state back to simply connected, so we can discover other characteristics.
904
    handset.con_state = CON_STATE_CONNECTED;
×
905

906
    task->status = PBIO_SUCCESS;
×
907
    PT_EXIT(pt);
×
908

909
cancel:
×
910
    if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
×
911
        gap_disconnect(peri->con_handle);
×
912
    }
913
    handset.con_state = CON_STATE_NONE;
×
914
    task->status = PBIO_ERROR_CANCELED;
×
915

916
    PT_END(pt);
×
917
}
918

919
void pbdrv_bluetooth_periperal_read_characteristic(pbio_task_t *task, pbdrv_bluetooth_peripheral_char_t *characteristic) {
×
920
    peripheral_singleton.char_now = characteristic;
×
921
    start_task(task, periperal_read_characteristic_task, NULL);
×
922
}
×
923

924
const char *pbdrv_bluetooth_peripheral_get_name(void) {
×
925
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
926
    return peri->name;
×
927
}
928

929
static PT_THREAD(peripheral_write_task(struct pt *pt, pbio_task_t *task)) {
×
930
    pbdrv_bluetooth_value_t *value = task->context;
×
931

932
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
933

934
    PT_BEGIN(pt);
×
935

936
    uint8_t err = gatt_client_write_value_of_characteristic(packet_handler,
×
937
        peri->con_handle, pbio_get_uint16_le(value->handle), value->size, value->data);
×
938

939
    if (err != ERROR_CODE_SUCCESS) {
×
940
        task->status = PBIO_ERROR_FAILED;
×
941
        PT_EXIT(pt);
×
942
    }
943

944
    // NB: Value buffer must remain valid until GATT_EVENT_QUERY_COMPLETE, so
945
    // this wait is not cancelable.
946
    PT_WAIT_UNTIL(pt, ({
×
947
        if (peri->con_handle == HCI_CON_HANDLE_INVALID) {
948
            // disconnected
949
            task->status = PBIO_ERROR_NO_DEV;
950
            PT_EXIT(pt);
951
        }
952
        event_packet &&
953
        hci_event_packet_get_type(event_packet) == GATT_EVENT_QUERY_COMPLETE &&
954
        gatt_event_query_complete_get_handle(event_packet) == peri->con_handle;
955
    }));
956

957
    uint8_t status = gatt_event_query_complete_get_att_status(event_packet);
×
958
    task->status = att_error_to_pbio_error(status);
×
959

960
    PT_END(pt);
×
961
}
962

963
void pbdrv_bluetooth_peripheral_write(pbio_task_t *task, pbdrv_bluetooth_value_t *value) {
×
964
    start_task(task, peripheral_write_task, value);
×
965
}
×
966

NEW
967
static PT_THREAD(peripheral_disconnect_task(struct pt *pt, pbio_task_t *task)) {
×
NEW
968
    PT_BEGIN(pt);
×
969

970
    pbdrv_bluetooth_peripheral_t *peri = &peripheral_singleton;
×
971
    if (peri->con_handle != HCI_CON_HANDLE_INVALID) {
×
972
        gap_disconnect(peri->con_handle);
×
973
    }
974

NEW
975
    PT_WAIT_UNTIL(pt, peri->con_handle == HCI_CON_HANDLE_INVALID);
×
976

NEW
977
    task->status = PBIO_SUCCESS;
×
NEW
978
    PT_END(pt);
×
979
}
980

NEW
981
void pbdrv_bluetooth_peripheral_disconnect(pbio_task_t *task) {
×
NEW
982
    start_task(task, peripheral_disconnect_task, NULL);
×
UNCOV
983
}
×
984

985
static PT_THREAD(start_broadcasting_task(struct pt *pt, pbio_task_t *task)) {
×
986
    pbdrv_bluetooth_value_t *value = task->context;
×
987

988
    PT_BEGIN(pt);
×
989

990
    if (value->size > LE_ADVERTISING_DATA_SIZE) {
×
991
        task->status = PBIO_ERROR_INVALID_ARG;
×
992
        PT_EXIT(pt);
×
993
    }
994

995
    // have to keep copy of data here since BTStack doesn't copy
996
    static uint8_t static_data[LE_ADVERTISING_DATA_SIZE];
997
    memcpy(static_data, value->data, value->size);
×
998

999
    gap_advertisements_set_data(value->size, static_data);
×
1000

1001
    if (!is_broadcasting) {
×
1002
        bd_addr_t null_addr = { };
×
1003
        gap_advertisements_set_params(0xA0, 0xA0, PBDRV_BLUETOOTH_AD_TYPE_ADV_NONCONN_IND, 0, null_addr, 0x7, 0);
×
1004
        gap_advertisements_enable(true);
×
1005
        is_broadcasting = true;
×
1006
    }
1007

1008
    // Wait advertising enable command to complete.
1009
    PT_WAIT_UNTIL(pt, event_packet && HCI_EVENT_IS_COMMAND_COMPLETE(event_packet, hci_le_set_advertising_data));
×
1010

1011
    task->status = PBIO_SUCCESS;
×
1012

1013
    PT_END(pt);
×
1014
}
1015

1016
void pbdrv_bluetooth_start_broadcasting(pbio_task_t *task, pbdrv_bluetooth_value_t *value) {
×
1017
    start_task(task, start_broadcasting_task, value);
×
1018
}
×
1019

1020
static PT_THREAD(stop_broadcasting_task(struct pt *pt, pbio_task_t *task)) {
×
1021
    PT_BEGIN(pt);
×
1022

1023
    if (is_broadcasting) {
×
1024
        gap_advertisements_enable(false);
×
1025
        is_broadcasting = false;
×
1026
    }
1027

1028
    // REVISIT: use callback to actually wait for stop?
1029
    task->status = PBIO_SUCCESS;
×
1030

1031
    PT_END(pt);
×
1032
}
1033

1034
void pbdrv_bluetooth_stop_broadcasting(pbio_task_t *task) {
×
1035
    start_task(task, stop_broadcasting_task, NULL);
×
1036
}
×
1037

1038
static PT_THREAD(start_observing_task(struct pt *pt, pbio_task_t *task)) {
×
1039
    pbdrv_bluetooth_start_observing_callback_t callback = task->context;
×
1040

1041
    PT_BEGIN(pt);
×
1042

1043
    observe_callback = callback;
×
1044

1045
    if (!is_observing) {
×
1046
        start_observing();
×
1047
        is_observing = true;
×
1048
    }
1049

1050
    // REVISIT: use callback to actually wait for start?
1051
    task->status = PBIO_SUCCESS;
×
1052

1053
    PT_END(pt);
×
1054
}
1055

1056
void pbdrv_bluetooth_start_observing(pbio_task_t *task, pbdrv_bluetooth_start_observing_callback_t callback) {
×
1057
    start_task(task, start_observing_task, callback);
×
1058
}
×
1059

1060
static PT_THREAD(stop_observing_task(struct pt *pt, pbio_task_t *task)) {
×
1061
    PT_BEGIN(pt);
×
1062

1063
    if (is_observing) {
×
1064
        gap_stop_scan();
×
1065
        is_observing = false;
×
1066
    }
1067

1068
    // REVISIT: use callback to actually wait for stop?
1069
    task->status = PBIO_SUCCESS;
×
1070

1071
    PT_END(pt);
×
1072
}
1073

1074
void pbdrv_bluetooth_stop_observing(pbio_task_t *task) {
×
1075
    observe_callback = NULL;
×
1076
    start_task(task, stop_observing_task, NULL);
×
1077
}
×
1078

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