Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@

## [Unreleased]

### Added
- Added `connect=True` parameter to the `Remote`, `LWP3Device`
and `XboxController` classes, along with a `connect()` method to optionally
connect later ([support#1800]).
- Added `timeout` and `name` parameters to the `XboxController`.

### Changed
- Changed the default `XboxController` connection timeout from indefinite
to 10 seconds, consistent with the `Remote`.
- Devices like the `Remote`, `LWP3Device`, and the `XboxController` now stay
connected when the program ends ([support#1382]).

[support#1382]: https://github.com/pybricks/support/issues/1382
[support#1800]: https://github.com/pybricks/support/issues/1800

## [4.0.0b5] - 2026-01-30

### Added
Expand Down
114 changes: 63 additions & 51 deletions lib/pbio/drv/bluetooth/bluetooth.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ pbio_error_t pbdrv_bluetooth_peripheral_get_available(pbdrv_bluetooth_peripheral
pbdrv_bluetooth_peripheral_t *peri = pbdrv_bluetooth_peripheral_get_by_index(i);

// Test if not already in use, not connected, and not busy.
if (!pbdrv_bluetooth_peripheral_is_connected(peri) && !peri->user && !peri->func) {
if (!pbdrv_bluetooth_peripheral_is_connected(peri) && !peri->func) {
// Claim this device for new user.
peri->user = user;
*peripheral = peri;
Expand All @@ -179,6 +179,37 @@ pbio_error_t pbdrv_bluetooth_peripheral_get_available(pbdrv_bluetooth_peripheral
return PBIO_ERROR_BUSY;
}

pbio_error_t pbdrv_bluetooth_peripheral_get_connected(pbdrv_bluetooth_peripheral_t **peripheral, void *user, pbdrv_bluetooth_peripheral_connect_config_t *config) {
for (uint8_t i = 0; i < PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS; i++) {
pbdrv_bluetooth_peripheral_t *peri = pbdrv_bluetooth_peripheral_get_by_index(i);

// Should be connected and not already in use.
if (!pbdrv_bluetooth_peripheral_is_connected(peri) || peri->user) {
continue;
}

// Callbacks must be the same, and still match with the given user.
// This ensures that we fail as intended when the same classes are used
// but the user has configured different filters such as the name.
if (peri->config.match_adv != config->match_adv ||
peri->config.match_adv_rsp != config->match_adv_rsp ||
peri->config.notification_handler != config->notification_handler ||
!peri->config.match_adv(user, peri->config.match_adv_data, peri->config.match_adv_data_len) ||
!peri->config.match_adv_rsp(user, peri->config.match_adv_rsp_data, peri->config.match_adv_rsp_data_len)) {
continue;
}

// Claim this device for new user.
peri->user = user;
*peripheral = peri;
return PBIO_SUCCESS;
}

// No more connected devices available.
*peripheral = NULL;
return PBIO_ERROR_NO_DEV;
}

void pbdrv_bluetooth_peripheral_release(pbdrv_bluetooth_peripheral_t *peripheral, void *user) {
// Only release if the user matches. A new user may have already safely
// claimed it, and this call to release may come from an orphaned user.
Expand Down Expand Up @@ -212,7 +243,7 @@ pbio_error_t pbdrv_bluetooth_peripheral_scan_and_connect(pbdrv_bluetooth_periphe
memset(peri->bdaddr, 0, sizeof(peri->bdaddr));

// Initialize operation for handling on the main thread.
peri->config = config;
peri->config = *config;
peri->func = pbdrv_bluetooth_peripheral_scan_and_connect_func;
peri->err = PBIO_ERROR_AGAIN;
peri->cancel = false;
Expand Down Expand Up @@ -550,20 +581,8 @@ pbio_error_t pbdrv_bluetooth_await_classic_task(pbio_os_state_t *state, void *co
}
#endif // PBDRV_CONFIG_BLUETOOTH_NUM_CLASSIC_CONNECTIONS

void pbdrv_bluetooth_cancel_operation_request(void) {
// Only some peripheral actions support cancellation.
DEBUG_PRINT("Bluetooth operation cancel requested.\n");
for (uint8_t i = 0; i < PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS; i++) {
pbdrv_bluetooth_peripheral_t *peri = pbdrv_bluetooth_peripheral_get_by_index(i);
peri->cancel = true;
}
#if PBDRV_CONFIG_BLUETOOTH_NUM_CLASSIC_CONNECTIONS
// Revisit: Cancel all.
pbdrv_bluetooth_classic_task_context.cancel = true;
#endif // PBDRV_CONFIG_BLUETOOTH_NUM_CLASSIC_CONNECTIONS
}

static bool shutting_down;
static bool pbdrv_bluetooth_shutting_down;
static pbio_os_timer_t pbdrv_bluetooth_shutting_down_watchdog;

/**
* This is the main high level pbdrv/bluetooth thread. It is driven forward by
Expand All @@ -588,6 +607,11 @@ pbio_error_t pbdrv_bluetooth_process_thread(pbio_os_state_t *state, void *contex
static uint8_t peri_index;
static pbdrv_bluetooth_peripheral_t *peri;

// Force shutdown if Bluetooth fails to deinit gracefully.
if (pbdrv_bluetooth_shutting_down && pbio_os_timer_is_expired(&pbdrv_bluetooth_shutting_down_watchdog)) {
goto shutdown;
}

PBIO_OS_ASYNC_BEGIN(state);

init:
Expand All @@ -607,7 +631,7 @@ pbio_error_t pbdrv_bluetooth_process_thread(pbio_os_state_t *state, void *contex
DEBUG_PRINT("Bluetooth is now on and initialized.\n");

// Service scheduled tasks as long as Bluetooth is enabled.
while (!shutting_down) {
while (!pbdrv_bluetooth_shutting_down) {

// In principle, this wait is only needed if there is nothing to do.
// In practice, leaving it here helps rather than hurts since it
Expand Down Expand Up @@ -668,14 +692,22 @@ pbio_error_t pbdrv_bluetooth_process_thread(pbio_os_state_t *state, void *contex

DEBUG_PRINT("Shutdown requested.\n");

// Power down the chip. This will disconnect from the host first.
// The peripheral has already been disconnected in the cleanup that runs after
// every program. If we change that behavior, we can do the disconnect here.
// Gracefully disconnect from the hosts and peripherals.
PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_disconnect_all(&sub));

DEBUG_PRINT("Hosts and peripherals disconnected. Resetting Bluetooth controller.\n");

shutdown:

PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_controller_reset(&sub, &timer));

pbdrv_bluetooth_shutting_down = false;
pbio_busy_count_down();

// Due to the nested logic of the Bluetooth process, this may be called
// again after completion. Re-enter here if that happens for safety, so we
// don't run the code since the last checkpoint over and over.
PBIO_OS_ASYNC_SET_CHECKPOINT(state);
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
}

Expand All @@ -693,21 +725,18 @@ pbio_error_t pbdrv_bluetooth_close_user_tasks(pbio_os_state_t *state, pbio_os_ti

PBIO_OS_ASYNC_BEGIN(state);

// Requests peripheral operations to cancel, if they support it.
pbdrv_bluetooth_cancel_operation_request();

for (peri_index = 0; peri_index < PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS; peri_index++) {
peri = pbdrv_bluetooth_peripheral_get_by_index(peri_index);

// Await ongoing peripheral user task.
// Await ongoing peripheral user task, requesting cancellation to
// expedite this if the task supports it. Peripherals remain connected.
peri->cancel = true;
PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_peripheral_command(&sub, peri));

// Disconnect peripheral.
pbdrv_bluetooth_peripheral_disconnect(peri);
PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_peripheral_command(&sub, peri));
// Allow peripheral to be used again next time, even if still connected.
peri->user = NULL;
}

// Let ongoing user task finish first.
// Let ongoing user advertising or scan task finish first.
PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL));

// Stop scanning.
Expand All @@ -718,6 +747,8 @@ pbio_error_t pbdrv_bluetooth_close_user_tasks(pbio_os_state_t *state, pbio_os_ti
pbdrv_bluetooth_start_broadcasting(NULL, 0);
PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL));

// TODO: Close Bluetooth classic tasks.

PBIO_OS_ASYNC_END(PBIO_SUCCESS);
}

Expand All @@ -728,29 +759,10 @@ void pbdrv_bluetooth_deinit(void) {
return;
}

// Under normal operation ::pbdrv_bluetooth_close_user_tasks completes
// normally and there should be no user activity at this point. If there
// is, a task got stuck, so exit forcefully.
bool failed_to_stop = advertising_or_scan_err == PBIO_ERROR_AGAIN;
for (uint8_t i = 0; i < PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS; i++) {
pbdrv_bluetooth_peripheral_t *peri = pbdrv_bluetooth_peripheral_get_by_index(i);
if (peri->err == PBIO_ERROR_AGAIN) {
failed_to_stop = true;
break;
}
}

if (failed_to_stop) {
// Hard reset without waiting on completion of any process.
DEBUG_PRINT("Bluetooth deinit: forcing hard reset due to busy tasks.\n");
pbdrv_bluetooth_controller_reset_hard();
return;
}

// Gracefully disconnect from host and power down.
// Ask Bluetooth process to proceed to shutdown.
pbio_os_timer_set(&pbdrv_bluetooth_shutting_down_watchdog, 3000);
pbdrv_bluetooth_shutting_down = true;
pbio_busy_count_up();
pbdrv_bluetooth_cancel_operation_request();
shutting_down = true;
pbio_os_request_poll();
}

Expand Down
2 changes: 1 addition & 1 deletion lib/pbio/drv/bluetooth/bluetooth.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
#include <stdint.h>

void pbdrv_bluetooth_init_hci(void);
void pbdrv_bluetooth_controller_reset_hard(void);
pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer);
pbio_error_t pbdrv_bluetooth_controller_initialize(pbio_os_state_t *state, pbio_os_timer_t *timer);
pbio_error_t pbdrv_bluetooth_disconnect_all(pbio_os_state_t *state);

pbio_error_t pbdrv_bluetooth_start_broadcasting_func(pbio_os_state_t *state, void *context);
pbio_error_t pbdrv_bluetooth_start_advertising_func(pbio_os_state_t *state, void *context);
Expand Down
Loading