Skip to content

Commit e022c4b

Browse files
committed
pybricks/bluetooth: Add bt classic inquiry scans.
Any Pybricks device running btstack can now issue Bluetooth classic inquiry scans to find and report discoverable devices. This API is experimental and we can change it later if we don't like it. But it's nice because it can be used to concretely demonstrate that the EV3's BTStack layer is doing something! This also should give some indication of the structure by which we'll add further classic functionality. If it's preferred to have it in the same file as the LE code, we can, but this structure is scarcely more expensive and a little more neatly organized.
1 parent 16e461e commit e022c4b

File tree

10 files changed

+368
-1
lines changed

10 files changed

+368
-1
lines changed

bricks/_common/qstrdefs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Q(pybricks.ev3devices)
1515
Q(pybricks.experimental)
1616
#endif
1717

18+
#if PYBRICKS_PY_COMMON_BTC
19+
Q(pybricks.experimental.btc)
20+
#endif
21+
1822
#if MICROPY_PY_BUILTINS_FLOAT // backwards compatibility with Pybricks V3.2, maps to pybricks.tools
1923
Q(pybricks.geometry)
2024
#endif

bricks/_common/sources.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ PYBRICKS_PYBRICKS_SRC_C = $(addprefix pybricks/,\
3333
ev3devices/pb_type_ev3devices_infraredsensor.c \
3434
ev3devices/pb_type_ev3devices_touchsensor.c \
3535
ev3devices/pb_type_ev3devices_ultrasonicsensor.c \
36+
experimental/pb_module_btc.c \
3637
experimental/pb_module_experimental.c \
3738
hubs/pb_module_hubs.c \
3839
hubs/pb_type_buildhat.c \
@@ -116,6 +117,7 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\
116117
drv/bluetooth/bluetooth.c \
117118
drv/bluetooth/bluetooth_btstack_stm32_hal.c \
118119
drv/bluetooth/bluetooth_btstack.c \
120+
drv/bluetooth/bluetooth_btstack_classic.c \
119121
drv/bluetooth/bluetooth_btstack_ev3.c \
120122
drv/bluetooth/bluetooth_simulation.c \
121123
drv/bluetooth/bluetooth_stm32_bluenrg.c \

bricks/ev3/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// Pybricks modules
1717
#define PYBRICKS_PY_COMMON (1)
1818
#define PYBRICKS_PY_COMMON_BLE (0)
19+
#define PYBRICKS_PY_COMMON_BTC (1)
1920
#define PYBRICKS_PY_COMMON_CHARGER (0)
2021
#define PYBRICKS_PY_COMMON_COLOR_LIGHT (1)
2122
#define PYBRICKS_PY_COMMON_CONTROL (1)

lib/pbio/drv/bluetooth/bluetooth_btstack.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "bluetooth.h"
2525
#include "bluetooth_btstack.h"
26+
#include "bluetooth_btstack_classic.h"
2627

2728
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
2829
#include "genhdr/pybricks_service.h"
@@ -1181,6 +1182,10 @@ void pbdrv_bluetooth_init_hci(void) {
11811182
nordic_spp_service_server_init(nordic_spp_packet_handler);
11821183
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE
11831184

1185+
#if PBDRV_CONFIG_BLUETOOTH_CLASSIC
1186+
pbdrv_bluetooth_classic_init();
1187+
#endif
1188+
11841189
bluetooth_thread_err = PBIO_ERROR_AGAIN;
11851190
bluetooth_thread_state = 0;
11861191
pbio_os_process_start(&pbdrv_bluetooth_hci_process, pbdrv_bluetooth_hci_process_thread, NULL);
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2025 The Pybricks Authors
3+
4+
// This file defines the functions required to implement Pybricks' Bluetooth
5+
// classic functionality using BTStack.
6+
7+
#include <pbdrv/config.h>
8+
9+
#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_CLASSIC
10+
11+
#include <pbdrv/bluetooth.h>
12+
13+
#include <inttypes.h>
14+
#include <stdint.h>
15+
#include <stdio.h>
16+
17+
#include <btstack.h>
18+
19+
#include <pbdrv/bluetooth.h>
20+
21+
static int32_t pending_inquiry_response_count;
22+
static int32_t pending_inquiry_response_limit;
23+
static void* pending_inquiry_result_handler_context;
24+
static pbdrv_bluetooth_inquiry_result_handler_t pending_inquiry_result_handler;
25+
26+
static void handle_hci_event_packet(uint8_t* packet, uint16_t size);
27+
28+
void pbdrv_bluetooth_classic_handle_packet(uint8_t packet_type, uint16_t channel, uint8_t* packet, uint16_t size) {
29+
switch (packet_type) {
30+
case HCI_EVENT_PACKET:
31+
handle_hci_event_packet(packet, size);
32+
}
33+
}
34+
35+
static void handle_hci_event_packet(uint8_t* packet, uint16_t size) {
36+
switch (hci_event_packet_get_type(packet)) {
37+
case GAP_EVENT_INQUIRY_RESULT: {
38+
if (!pending_inquiry_result_handler) {
39+
return;
40+
}
41+
pbdrv_bluetooth_inquiry_result_t result;
42+
gap_event_inquiry_result_get_bd_addr(packet, result.bdaddr);
43+
if (gap_event_inquiry_result_get_rssi_available(packet)) {
44+
result.rssi = gap_event_inquiry_result_get_rssi(packet);
45+
}
46+
if (gap_event_inquiry_result_get_name_available(packet)) {
47+
const uint8_t* name = gap_event_inquiry_result_get_name(packet);
48+
const size_t name_len = gap_event_inquiry_result_get_name_len(packet);
49+
snprintf(result.name, sizeof(result.name), "%.*s", (int)name_len, name);
50+
}
51+
result.class_of_device = gap_event_inquiry_result_get_class_of_device(packet);
52+
pending_inquiry_result_handler(pending_inquiry_result_handler_context, &result);
53+
if (pending_inquiry_response_limit > 0) {
54+
pending_inquiry_response_count++;
55+
if (pending_inquiry_response_count >= pending_inquiry_response_limit) {
56+
gap_inquiry_stop();
57+
}
58+
}
59+
break;
60+
}
61+
case GAP_EVENT_INQUIRY_COMPLETE: {
62+
if (pending_inquiry_result_handler) {
63+
pending_inquiry_result_handler = NULL;
64+
pending_inquiry_result_handler_context = NULL;
65+
pbio_os_request_poll();
66+
}
67+
break;
68+
}
69+
default:
70+
break;
71+
}
72+
}
73+
74+
void pbdrv_bluetooth_classic_init() {
75+
static btstack_packet_callback_registration_t hci_event_handler_registration;
76+
hci_event_handler_registration.callback = pbdrv_bluetooth_classic_handle_packet;
77+
hci_add_event_handler(&hci_event_handler_registration);
78+
}
79+
80+
pbio_error_t pbdrv_bluetooth_inquiry_scan(
81+
pbio_os_state_t* state,
82+
int32_t max_responses,
83+
int32_t timeout,
84+
void* context,
85+
pbdrv_bluetooth_inquiry_result_handler_t result_handler) {
86+
PBIO_OS_ASYNC_BEGIN(state);
87+
if (pending_inquiry_result_handler) {
88+
return PBIO_ERROR_BUSY;
89+
}
90+
PBIO_OS_AWAIT_UNTIL(state, hci_get_state() == HCI_STATE_WORKING);
91+
pending_inquiry_response_count = 0;
92+
pending_inquiry_response_limit = max_responses;
93+
pending_inquiry_result_handler = result_handler;
94+
pending_inquiry_result_handler_context = context;
95+
gap_inquiry_start(timeout);
96+
97+
PBIO_OS_AWAIT_UNTIL(state, !pending_inquiry_result_handler);
98+
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
99+
}
100+
101+
#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_CLASSIC
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#ifndef PBDRV_BLUETOOTH_BLUETOOTH_BTSTACK_CLASSIC_H
2+
#define PBDRV_BLUETOOTH_BLUETOOTH_BTSTACK_CLASSIC_H
3+
#include <pbdrvconfig.h>
4+
5+
#if PBDRV_CONFIG_BLUETOOTH_CLASSIC
6+
7+
#include <stdint.h>
8+
9+
void pbdrv_bluetooth_classic_init();
10+
11+
#endif // PBDRV_CONFIG_BLUETOOTH_CLASSIC
12+
13+
#endif // PBDRV_BLUETOOTH_BLUETOOTH_BTSTACK_CLASSIC_H
14+

lib/pbio/include/pbdrv/bluetooth.h

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,50 @@ static inline pbio_error_t pbdrv_bluetooth_close_user_tasks(pbio_os_state_t *sta
607607
return PBIO_SUCCESS;
608608
}
609609

610-
611610
#endif // PBDRV_CONFIG_BLUETOOTH
612611

612+
#if PBDRV_CONFIG_BLUETOOTH_CLASSIC
613+
614+
// A single result from an inquiry scan.
615+
typedef struct {
616+
uint8_t bdaddr[6];
617+
int8_t rssi;
618+
char name[249];
619+
uint32_t class_of_device;
620+
} pbdrv_bluetooth_inquiry_result_t;
621+
622+
// Callback for handling inquiry results.
623+
typedef void (*pbdrv_bluetooth_inquiry_result_handler_t)(void* context, const pbdrv_bluetooth_inquiry_result_t* result);
624+
625+
/**
626+
* Runs a bluetooth inquiry scan. Only one such scan can be active at a time.
627+
*
628+
* @param [in] state Protothread state.
629+
* @param [in] max_responses Maximum number of responses to report. Use -1 for unlimited.
630+
* @param [in] timeout Timeout in units of 1.28 seconds. Values less than one will be coerced to one.
631+
* @param [in] context Context pointer to be passed to the result handler.
632+
* @param [in] result_handler Callback that will be called for each inquiry result.
633+
*/
634+
pbio_error_t pbdrv_bluetooth_inquiry_scan(
635+
pbio_os_state_t* state,
636+
int32_t max_responses,
637+
int32_t timeout,
638+
void* context,
639+
pbdrv_bluetooth_inquiry_result_handler_t result_handler);
640+
641+
#else // PBDRV_CONFIG_BLUETOOTH_CLASSIC
642+
643+
static inline pbio_error_t pbdrv_bluetooth_inquiry_scan(
644+
pbio_os_state_t* state,
645+
int32_t max_responses,
646+
int32_t timeout,
647+
void* context,
648+
void* result_handler) {
649+
return PBIO_ERROR_NOT_SUPPORTED;
650+
}
651+
652+
#endif // PBDRV_CONFIG_BLUETOOTH_CLASSIC
653+
613654
#endif // _PBDRV_BLUETOOTH_H_
614655

615656
/** @} */

lib/pbio/platform/ev3/pbdrvconfig.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#define PBDRV_CONFIG_I2C_EV3 (1)
4444

4545
#define PBDRV_CONFIG_BLUETOOTH (1)
46+
#define PBDRV_CONFIG_BLUETOOTH_CLASSIC (1)
4647
#define PBDRV_CONFIG_BLUETOOTH_MAX_MTU_SIZE 515
4748
#define PBDRV_CONFIG_BLUETOOTH_BTSTACK (1)
4849
#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_CLASSIC (1)

0 commit comments

Comments
 (0)