From 0b7eec0c6f386eab9ae70bfd81833a6602323736 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Wed, 14 Jan 2026 14:05:05 +0100 Subject: [PATCH 1/7] pbio/drv/bluetooth,usb: Add connection changed callback. This is helpful for the UI, which needs to wait for connection changes, and checking multiple connection states for change may not be reliable if more than one changes at once. --- lib/pbio/drv/bluetooth/bluetooth.c | 12 ++++++++++++ lib/pbio/drv/bluetooth/bluetooth.h | 2 ++ lib/pbio/drv/bluetooth/bluetooth_btstack.c | 2 ++ .../drv/bluetooth/bluetooth_stm32_bluenrg.c | 2 ++ lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c | 2 ++ lib/pbio/drv/usb/usb.c | 17 +++++++++++++++-- lib/pbio/include/pbdrv/bluetooth.h | 10 ++++++++++ lib/pbio/include/pbdrv/usb.h | 10 ++++++++++ lib/pbio/include/pbio/util.h | 2 ++ 9 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/pbio/drv/bluetooth/bluetooth.c b/lib/pbio/drv/bluetooth/bluetooth.c index 9f2cd1ffa..23f84f0a9 100644 --- a/lib/pbio/drv/bluetooth/bluetooth.c +++ b/lib/pbio/drv/bluetooth/bluetooth.c @@ -79,6 +79,18 @@ void pbdrv_bluetooth_init(void) { pbdrv_bluetooth_init_hci(); } +static pbio_util_void_callback_t pbdrv_bluetooth_host_connection_changed_callback; + +void pbdrv_bluetooth_set_host_connection_changed_callback(pbio_util_void_callback_t callback) { + pbdrv_bluetooth_host_connection_changed_callback = callback; +} + +void pbdrv_bluetooth_host_connection_changed(void) { + if (pbdrv_bluetooth_host_connection_changed_callback) { + pbdrv_bluetooth_host_connection_changed_callback(); + } +} + pbio_error_t pbdrv_bluetooth_tx(const uint8_t *data, uint32_t *size) { // make sure we have a Bluetooth connection diff --git a/lib/pbio/drv/bluetooth/bluetooth.h b/lib/pbio/drv/bluetooth/bluetooth.h index eb9495206..602d3e8a5 100644 --- a/lib/pbio/drv/bluetooth/bluetooth.h +++ b/lib/pbio/drv/bluetooth/bluetooth.h @@ -37,6 +37,8 @@ pbio_error_t pbdrv_bluetooth_peripheral_write_characteristic_func(pbio_os_state_ pbio_error_t pbdrv_bluetooth_send_pybricks_value_notification(pbio_os_state_t *state, const uint8_t *data, uint16_t size); +void pbdrv_bluetooth_host_connection_changed(void); + extern pbdrv_bluetooth_receive_handler_t pbdrv_bluetooth_receive_handler; extern uint8_t pbdrv_bluetooth_broadcast_data[PBDRV_BLUETOOTH_MAX_ADV_SIZE]; diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index 3c417c528..f7eff3f0e 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -141,6 +141,7 @@ static pbio_pybricks_error_t pybricks_data_received(hci_con_handle_t tx_con_hand static void pybricks_configured(hci_con_handle_t tx_con_handle, uint16_t value) { pybricks_con_handle = value ? tx_con_handle : HCI_CON_HANDLE_INVALID; + pbdrv_bluetooth_host_connection_changed(); } static bool hci_event_is_type(uint8_t *packet, uint8_t event_type) { @@ -370,6 +371,7 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe le_con_handle = HCI_CON_HANDLE_INVALID; pybricks_con_handle = HCI_CON_HANDLE_INVALID; uart_con_handle = HCI_CON_HANDLE_INVALID; + pbdrv_bluetooth_host_connection_changed(); } else { for (uint8_t i = 0; i < PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS; i++) { pbdrv_bluetooth_peripheral_t *peri = pbdrv_bluetooth_peripheral_get_by_index(i); diff --git a/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c index bc3209546..1af20ed76 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c +++ b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c @@ -889,6 +889,7 @@ static void handle_event(hci_event_pckt *event) { conn_handle = 0; pybricks_notify_en = false; uart_tx_notify_en = false; + pbdrv_bluetooth_host_connection_changed(); } else if (evt->handle == peri->con_handle) { peri->con_handle = 0; } @@ -946,6 +947,7 @@ static void handle_event(hci_event_pckt *event) { evt_gatt_attr_modified *subevt = (evt_gatt_attr_modified *)evt->data; if (subevt->attr_handle == pybricks_command_event_char_handle + 2) { pybricks_notify_en = subevt->att_data[0]; + pbdrv_bluetooth_host_connection_changed(); } else if (subevt->attr_handle == uart_rx_char_handle + 1) { // not implemented } else if (subevt->attr_handle == uart_tx_char_handle + 2) { diff --git a/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c index b34552a18..445fd6a7e 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c +++ b/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c @@ -1268,6 +1268,7 @@ static void handle_event(uint8_t *packet) { pybricks_notify_en = data[10]; err = PBIO_PYBRICKS_ERROR_OK; DBG("noti: %d", pybricks_notify_en); + pbdrv_bluetooth_host_connection_changed(); } else if (char_handle == uart_rx_char_handle) { // Not implemented } else if (char_handle == uart_tx_char_handle + 1) { @@ -1369,6 +1370,7 @@ static void handle_event(uint8_t *packet) { conn_handle = NO_CONNECTION; pybricks_notify_en = false; uart_tx_notify_en = false; + pbdrv_bluetooth_host_connection_changed(); } else if (peri->con_handle == connection_handle) { peri->con_handle = NO_CONNECTION; } diff --git a/lib/pbio/drv/usb/usb.c b/lib/pbio/drv/usb/usb.c index e6ad44b78..fda7e63fd 100644 --- a/lib/pbio/drv/usb/usb.c +++ b/lib/pbio/drv/usb/usb.c @@ -23,11 +23,23 @@ #include +static pbio_util_void_callback_t pbdrv_usb_host_connection_changed_callback; + +void pbdrv_usb_set_host_connection_changed_callback(pbio_util_void_callback_t callback) { + pbdrv_usb_host_connection_changed_callback = callback; +} /** * Host is subscribed to our outgoing event messages. */ static bool pbdrv_usb_events_subscribed; +static void pbdrv_usb_events_subscribed_set(bool subscribed) { + pbdrv_usb_events_subscribed = subscribed; + if (pbdrv_usb_host_connection_changed_callback) { + pbdrv_usb_host_connection_changed_callback(); + } +} + bool pbdrv_usb_connection_is_active(void) { return pbdrv_usb_events_subscribed && pbdrv_usb_is_ready(); } @@ -226,7 +238,7 @@ static void pbdrv_usb_handle_data_in(void) { switch (data_in[0]) { case PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE: - pbdrv_usb_events_subscribed = data_in[1]; + pbdrv_usb_events_subscribed_set(data_in[1]); pbdrv_usb_respond_result = PBIO_PYBRICKS_ERROR_OK; pbdrv_usb_respond_soon = true; @@ -243,7 +255,7 @@ static void pbdrv_usb_handle_data_in(void) { } static void pbdrv_usb_reset_state(void) { - pbdrv_usb_events_subscribed = false; + pbdrv_usb_events_subscribed_set(false); pbdrv_usb_respond_soon = false; pbdrv_usb_status_data_pending = false; lwrb_reset(&pbdrv_usb_stdout_ring_buf); @@ -302,6 +314,7 @@ static pbio_error_t pbdrv_usb_process_thread(pbio_os_state_t *state, void *conte } PBIO_OS_AWAIT_WHILE(state, pbdrv_usb_is_ready()); + pbdrv_usb_reset_state(); PBIO_OS_AWAIT(state, &sub, pbdrv_usb_tx_reset(&sub)); } diff --git a/lib/pbio/include/pbdrv/bluetooth.h b/lib/pbio/include/pbdrv/bluetooth.h index b3f72a33f..f88805a60 100644 --- a/lib/pbio/include/pbdrv/bluetooth.h +++ b/lib/pbio/include/pbdrv/bluetooth.h @@ -308,6 +308,13 @@ const char *pbdrv_bluetooth_get_fw_version(void); */ bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection); +/** + * Sets a callback to be called when a Bluetooth host is connected or disconnected. + * + * @param [in] callback The function that will be called. + */ +void pbdrv_bluetooth_set_host_connection_changed_callback(pbio_util_void_callback_t callback); + /** * Registers a callback that will be called when Pybricks data is received via a * characteristic write. @@ -635,6 +642,9 @@ static inline bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t con return false; } +static inline void pbdrv_bluetooth_set_host_connection_changed_callback(pbio_util_void_callback_t callback) { +} + static inline void pbdrv_bluetooth_set_receive_handler(pbdrv_bluetooth_receive_handler_t handler) { } diff --git a/lib/pbio/include/pbdrv/usb.h b/lib/pbio/include/pbdrv/usb.h index c68ef857f..be4a0424e 100644 --- a/lib/pbio/include/pbdrv/usb.h +++ b/lib/pbio/include/pbdrv/usb.h @@ -59,6 +59,13 @@ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void); */ void pbdrv_usb_set_receive_handler(pbdrv_usb_receive_handler_t handler); +/** + * Sets a callback to be called when a USB host is connected or disconnected. + * + * @param [in] callback The function that will be called. + */ +void pbdrv_usb_set_host_connection_changed_callback(pbio_util_void_callback_t callback); + /** * Schedules Pybricks status to be sent soon. * @@ -131,6 +138,9 @@ static inline pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { static inline void pbdrv_usb_set_receive_handler(pbdrv_usb_receive_handler_t handler) { } +static inline void pbdrv_usb_set_host_connection_changed_callback(pbio_util_void_callback_t callback) { +} + static inline void pbdrv_usb_schedule_status_update(const uint8_t *status_msg) { } diff --git a/lib/pbio/include/pbio/util.h b/lib/pbio/include/pbio/util.h index f41f1c49a..56d9de9ef 100644 --- a/lib/pbio/include/pbio/util.h +++ b/lib/pbio/include/pbio/util.h @@ -122,6 +122,8 @@ void pbio_uuid128_le_copy(uint8_t *dst, const uint8_t *src); bool pbio_uuid128_reverse_compare(const uint8_t *uuid1, const uint8_t *uuid2); void pbio_uuid128_reverse_copy(uint8_t *dst, const uint8_t *src); +typedef void (*pbio_util_void_callback_t)(void); + /** * Declares a new oneshot state variable. * @param [in] name The name of the variable. From 893467cd9b02120e2ce3f12a2f04bda2a3455732 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 15 Jan 2026 10:08:21 +0100 Subject: [PATCH 2/7] pbio/sys/light: Change priority of USB light. We will allow simultaneous Bluetooth and USB connections, so it no longer makes sense to have USB override other states. Advertising takes priority since it is a temporary state. BLE and USB have equal priority, and we mix their color if both are connected. --- lib/pbio/sys/light.c | 45 +++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/pbio/sys/light.c b/lib/pbio/sys/light.c index ccca20ef6..c3a8f4b69 100644 --- a/lib/pbio/sys/light.c +++ b/lib/pbio/sys/light.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -27,10 +28,11 @@ typedef enum { } pbsys_status_light_indication_warning_t; typedef enum { - PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_NONE, - PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_ADVERTISING, - PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_CONNECTED, - PBSYS_STATUS_LIGHT_INDICATION_USB_ACTIVE_BLE_ANY, + PBSYS_STATUS_LIGHT_INDICATION_NONE, + PBSYS_STATUS_LIGHT_INDICATION_BLE_ADVERTISING, + PBSYS_STATUS_LIGHT_INDICATION_BLE_CONNECTED, + PBSYS_STATUS_LIGHT_INDICATION_USB_CONNECTED, + PBSYS_STATUS_LIGHT_INDICATION_USB_AND_BLE_CONNECTED, } pbsys_status_light_indication_usb_ble_t; /** A single element of a status light indication pattern. */ @@ -94,12 +96,12 @@ pbsys_status_light_indication_pattern_warning[] = { static const pbsys_status_light_indication_pattern_element_t *const pbsys_status_light_indication_pattern_ble[] = { - [PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_NONE] = + [PBSYS_STATUS_LIGHT_INDICATION_NONE] = (const pbsys_status_light_indication_pattern_element_t[]) { PBSYS_STATUS_LIGHT_INDICATION_PATTERN_FOREVER(PBIO_COLOR_NONE), }, // Two blue blinks, pause, then repeat. - [PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_ADVERTISING] = + [PBSYS_STATUS_LIGHT_INDICATION_BLE_ADVERTISING] = (const pbsys_status_light_indication_pattern_element_t[]) { { .color = PBIO_COLOR_BLUE, .duration = 2 }, { .color = PBIO_COLOR_BLACK, .duration = 2 }, @@ -108,15 +110,22 @@ pbsys_status_light_indication_pattern_ble[] = { PBSYS_STATUS_LIGHT_INDICATION_PATTERN_REPEAT }, // Blue, always on. - [PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_CONNECTED] = + [PBSYS_STATUS_LIGHT_INDICATION_BLE_CONNECTED] = (const pbsys_status_light_indication_pattern_element_t[]) { PBSYS_STATUS_LIGHT_INDICATION_PATTERN_FOREVER(PBIO_COLOR_BLUE), }, + #if PBDRV_CONFIG_USB // Green, always on. - [PBSYS_STATUS_LIGHT_INDICATION_USB_ACTIVE_BLE_ANY] = + [PBSYS_STATUS_LIGHT_INDICATION_USB_CONNECTED] = (const pbsys_status_light_indication_pattern_element_t[]) { PBSYS_STATUS_LIGHT_INDICATION_PATTERN_FOREVER(PBIO_COLOR_GREEN), }, + // Cyan, always on. + [PBSYS_STATUS_LIGHT_INDICATION_USB_AND_BLE_CONNECTED] = + (const pbsys_status_light_indication_pattern_element_t[]) { + PBSYS_STATUS_LIGHT_INDICATION_PATTERN_FOREVER(PBIO_COLOR_CYAN), + }, + #endif }; typedef struct { @@ -203,19 +212,25 @@ void pbsys_status_light_handle_status_change(void) { } // USB/BLE pattern precedence. - pbsys_status_light_indication_usb_ble_t usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_NONE; - if (pbsys_status_test(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED)) { - usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_USB_ACTIVE_BLE_ANY; - } else if (pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING)) { - usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_ADVERTISING; + pbsys_status_light_indication_usb_ble_t usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_NONE; + + // Advertising gets precedence because mixing it in with the other states + // is confusing. It is generally a temporary state, so eventually the + // solid color or off state will return. + if (pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING)) { + usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_BLE_ADVERTISING; + } else if (pbsys_status_test(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED) && pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED)) { + usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_USB_AND_BLE_CONNECTED; + } else if (pbsys_status_test(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED)) { + usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_USB_CONNECTED; } else if (pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED)) { - usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_CONNECTED; + usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_BLE_CONNECTED; } #if !PBSYS_CONFIG_STATUS_LIGHT_BLUETOOTH // Hubs without Bluetooth light don't show connectivity state if program is running. if (pbsys_status_test(PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING)) { - usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_USB_NONE_BLE_NONE; + usb_ble_indication = PBSYS_STATUS_LIGHT_INDICATION_NONE; } #endif From 0103f3935960f033e2509a74f703f74ed91b3f81 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 15 Jan 2026 10:17:23 +0100 Subject: [PATCH 3/7] pbio/drv/bluetooth_btstack: Don't advertise if connected. Otherwise we never get the completion command, so this task keeps waiting. This lets the UI call the start advertising command safely in any state. --- lib/pbio/drv/bluetooth/bluetooth_btstack.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index f7eff3f0e..470c93612 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -538,6 +538,13 @@ pbio_error_t pbdrv_bluetooth_start_advertising_func(pbio_os_state_t *state, void PBIO_OS_ASYNC_BEGIN(state); + // Don't advertise if already connected. BTstack also protects against this, + // but it means we never get the HCI event complete command because it is + // never given. + if (le_con_handle != HCI_CON_HANDLE_INVALID) { + return PBIO_ERROR_INVALID_OP; + } + init_advertising_data(); gap_advertisements_enable(true); From 5ea0795e830eff40552324ac435b23c0431a6b5b Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 15 Jan 2026 10:42:39 +0100 Subject: [PATCH 4/7] pbio/sys/hmi: Support simultaneous USB and BLE host. This was already supported implicitly by connecting to Bluetooth first and then plugging in USB. It is more intuitive if we could just enable BLE advertising using the bluetooth button even if USB is already connected. --- lib/pbio/sys/core.c | 21 ------ lib/pbio/sys/hmi_pup.c | 147 +++++++++++++++++++++++++++++++++-------- 2 files changed, 120 insertions(+), 48 deletions(-) diff --git a/lib/pbio/sys/core.c b/lib/pbio/sys/core.c index 7d7c7027a..2216e8b5b 100644 --- a/lib/pbio/sys/core.c +++ b/lib/pbio/sys/core.c @@ -42,25 +42,6 @@ static pbio_error_t pbsys_system_poll_process_thread(pbio_os_state_t *state, voi // keep the hub from resetting itself pbdrv_watchdog_update(); - - if (pbsys_system_poll_process.request == PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL) { - // After shutdown we only poll critical system processes. - continue; - } - - // Monitor USB state. - if (pbdrv_usb_connection_is_active()) { - pbsys_status_set(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED); - } else { - pbsys_status_clear(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED); - } - - // Monitor BLE state. - if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { - pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); - } else { - pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); - } } // Unreachable @@ -90,8 +71,6 @@ void pbsys_deinit(void) { // Used by status indications during shutdown. pbsys_status_set(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST); - pbio_os_process_make_request(&pbsys_system_poll_process, PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL); - pbsys_storage_deinit(); pbsys_hmi_deinit(); diff --git a/lib/pbio/sys/hmi_pup.c b/lib/pbio/sys/hmi_pup.c index defb9606a..5068f450b 100644 --- a/lib/pbio/sys/hmi_pup.c +++ b/lib/pbio/sys/hmi_pup.c @@ -134,9 +134,43 @@ static pbio_error_t boot_animation_process_shutdown_thread(pbio_os_state_t *stat #endif +static void pbsys_hmi_host_update_indications(void) { + // Update USB light indication. + if (pbdrv_usb_connection_is_active()) { + pbsys_status_set(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED); + } else { + pbsys_status_clear(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED); + } + + // Update BLE light indication. + if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { + pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); + } else { + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); + } +} + +static bool pbsys_hmi_handle_connection_change; + +/** + * Called from the USB and Bluetooth driver if a host connection changes. + * + * This pertains to the actual Pybricks profile/events (un)subscribe, not + * necessarily the physical link. + */ +static void pbsys_hmi_connection_changed_callback(void) { + DEBUG_PRINT("A host connected or disconnected.\n"); + pbsys_hmi_handle_connection_change = true; + pbsys_hmi_host_update_indications(); +} + static pbio_os_process_t boot_animation_process; void pbsys_hmi_init(void) { + + pbdrv_usb_set_host_connection_changed_callback(pbsys_hmi_connection_changed_callback); + pbdrv_bluetooth_set_host_connection_changed_callback(pbsys_hmi_connection_changed_callback); + #if PBIO_CONFIG_LIGHT_MATRIX pbio_busy_count_up(); pbio_error_t err = pbio_light_matrix_get_dev(0, 5, &pbsys_hub_light_matrix); @@ -149,6 +183,10 @@ void pbsys_hmi_init(void) { } void pbsys_hmi_deinit(void) { + + pbdrv_usb_set_host_connection_changed_callback(NULL); + pbdrv_bluetooth_set_host_connection_changed_callback(NULL); + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); pbsys_status_clear(PBIO_PYBRICKS_STATUS_USB_HOST_CONNECTED); @@ -157,6 +195,35 @@ void pbsys_hmi_deinit(void) { pbio_os_process_start(&boot_animation_process, boot_animation_process_shutdown_thread, (void *)false); } +/** + * Convenience wrapper to start or stop advertising, set status, and await command. + * + * The system is allowed to be in the requested state already. Then this is a no-op. + * + * @param [in] state The protothread state. + * @param [in] advertise true to start advertising, false to stop. + */ +static pbio_error_t start_advertising(pbio_os_state_t *state, bool advertise) { + + pbio_error_t err; + pbio_os_state_t unused; + + PBIO_OS_ASYNC_BEGIN(state); + + pbdrv_bluetooth_start_advertising(advertise); + PBIO_OS_AWAIT(state, &unused, err = pbdrv_bluetooth_await_advertise_or_scan_command(&unused, NULL)); + + if (advertise && err == PBIO_SUCCESS) { + pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + } else { + pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + } + + DEBUG_PRINT("BLE advertising is: %d (requested %d) with error %d. \n", pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING), advertise, err); + + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + /** * The HMI is a loop running the following steps: * @@ -174,12 +241,18 @@ static pbio_error_t run_ui(pbio_os_state_t *state) { static pbio_os_state_t sub; static pbio_os_timer_t idle_timer; - static pbio_os_timer_t input_timer; + + // Used as a local variable in a few places, but the value needs to persist + // during the async start advertising call. + static bool should_advertise; PBIO_OS_ASYNC_BEGIN(state); pbio_os_timer_set(&idle_timer, PBSYS_CONFIG_HMI_IDLE_TIMEOUT_MS); + // Always start by setting initial connection state indications. + pbsys_hmi_handle_connection_change = true; + for (;;) { DEBUG_PRINT("Start HMI loop\n"); @@ -189,6 +262,8 @@ static pbio_error_t run_ui(pbio_os_state_t *state) { light_matrix_show_idle_ui(100); #endif + pbsys_hmi_host_update_indications(); + // Buttons could be pressed at the end of the user program, so wait for // a release and then a new press, or until we have to exit early. DEBUG_PRINT("Waiting for initial button release.\n"); @@ -203,7 +278,7 @@ static pbio_error_t run_ui(pbio_os_state_t *state) { // Wait on a button, external program start, or connection change. Stop // waiting on timeout or shutdown. - while (!pbdrv_button_get_pressed() && !pbsys_main_program_start_is_requested()) { + PBIO_OS_AWAIT_UNTIL(state, ({ // Shutdown may be requested by a background process such as critical // battery or holding the power button. @@ -218,18 +293,18 @@ static pbio_error_t run_ui(pbio_os_state_t *state) { return PBIO_ERROR_TIMEDOUT; } - // Advertise if BLE enabled and there is no host connection. - bool advertise = pbsys_storage_settings_bluetooth_enabled_get() && !pbsys_host_is_connected(); - if (advertise) { - pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); - } else { - pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); - } - pbdrv_bluetooth_start_advertising(advertise); - PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); + // Wait condition: button pressed, program start requested, or connection change. + pbdrv_button_get_pressed() || pbsys_main_program_start_is_requested() || pbsys_hmi_handle_connection_change; + })); - // Don't block the loop. - PBIO_OS_AWAIT_MS(state, &input_timer, 10); + // On setting or closing a connection, start from a clean slate: + // Begin advertising if Bluetooth enabled and there is no host + // connection, otherwise disable. + if (pbsys_hmi_handle_connection_change) { + pbsys_hmi_handle_connection_change = false; + should_advertise = pbsys_storage_settings_bluetooth_enabled_get() && !pbsys_host_is_connected(); + PBIO_OS_AWAIT(state, &sub, start_advertising(&sub, should_advertise)); + continue; } // External program request takes precedence over buttons. @@ -239,10 +314,34 @@ static pbio_error_t run_ui(pbio_os_state_t *state) { } #if PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON - // Toggle Bluetooth enable setting if Bluetooth button pressed. Only when disconnected. - if ((pbdrv_button_get_pressed() & PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON) && !pbsys_host_is_connected()) { - pbsys_storage_settings_bluetooth_enabled_set(!pbsys_storage_settings_bluetooth_enabled_get()); - DEBUG_PRINT("Toggling BLE advertising to: %s. \n", pbsys_storage_settings_bluetooth_enabled_get() ? "on" : "off"); + // Handle Bluetooth button press. + if (pbdrv_button_get_pressed() & PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON) { + + should_advertise = false; + + // Button behavior depends on whether we're already connected. + if (pbsys_host_is_connected()) { + + // If already connected, pressing toggles advertising to allow + // for a secondary host connection. + should_advertise = !pbsys_status_test(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + + // We could be connected to USB and BLE might have been + // configured off previously. So turn back on. + if (!pbsys_storage_settings_bluetooth_enabled_get()) { + pbsys_storage_settings_bluetooth_enabled_set(true); + } + } else { + // When disconnected, pressing the button toggles Bluetooth enable + // state, and Bluetooth advertising is updated to match. + pbsys_storage_settings_bluetooth_enabled_set(!pbsys_storage_settings_bluetooth_enabled_get()); + should_advertise = pbsys_storage_settings_bluetooth_enabled_get(); + } + + // Set requested state. + PBIO_OS_AWAIT(state, &sub, start_advertising(&sub, should_advertise)); + + // Go back to wait for button release and other input. continue; } #endif // PBSYS_CONFIG_HMI_PUP_BLUETOOTH_BUTTON @@ -275,11 +374,6 @@ static pbio_error_t run_ui(pbio_os_state_t *state) { DEBUG_PRINT("No valid action selected, start over.\n"); } - // Stop advertising if we are still doing so. - DEBUG_PRINT("Stop advertising on HMI exit.\n"); - pbdrv_bluetooth_start_advertising(false); - PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); - // Wait for all buttons to be released so the user doesn't accidentally // push their robot off course. DEBUG_PRINT("Waiting for final button release.\n"); @@ -290,10 +384,9 @@ static pbio_error_t run_ui(pbio_os_state_t *state) { pbdrv_button_get_pressed(); })); - // We have already stopped advertising above, but it is nicer to keep the - // visual active until you release the button. Otherwise, it appears as if - // the hub is already off when you are shutting down. - pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_ADVERTISING); + // Stop advertising if we are still doing so. + DEBUG_PRINT("Stop advertising on HMI exit.\n"); + PBIO_OS_AWAIT(state, &sub, start_advertising(&sub, false)); // Start run animations #if PBIO_CONFIG_LIGHT_MATRIX @@ -302,7 +395,7 @@ static pbio_error_t run_ui(pbio_os_state_t *state) { #if PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS pbio_color_light_start_breathe_animation(pbsys_status_light_main, PBSYS_CONFIG_STATUS_LIGHT_STATE_ANIMATIONS_HUE); - #else + #elif PBSYS_CONFIG_STATUS_LIGHT pbio_color_light_off(pbsys_status_light_main); #endif From 398b62769d8d3fc969dbefe3a60a93795727710f Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 15 Jan 2026 13:44:52 +0100 Subject: [PATCH 5/7] pbio/drv/bluetooth: Split out connection checks. We had already split off the peripheral since it has more than one instance. The same will now apply to the host. The check for LE connections without Pybricks was never used, and NUS is not supported, so this function can go. --- lib/pbio/drv/bluetooth/bluetooth.c | 22 +++++------ lib/pbio/drv/bluetooth/bluetooth_btstack.c | 38 +++++-------------- .../drv/bluetooth/bluetooth_stm32_bluenrg.c | 30 ++++----------- .../drv/bluetooth/bluetooth_stm32_cc2640.c | 33 +++++----------- lib/pbio/include/pbdrv/bluetooth.h | 37 +++++++++--------- lib/pbio/sys/hmi_pup.c | 2 +- lib/pbio/sys/host.c | 2 +- .../pb_type_iodevices_xbox_controller.c | 2 +- 8 files changed, 58 insertions(+), 108 deletions(-) diff --git a/lib/pbio/drv/bluetooth/bluetooth.c b/lib/pbio/drv/bluetooth/bluetooth.c index 23f84f0a9..f1d03f46b 100644 --- a/lib/pbio/drv/bluetooth/bluetooth.c +++ b/lib/pbio/drv/bluetooth/bluetooth.c @@ -94,7 +94,7 @@ void pbdrv_bluetooth_host_connection_changed(void) { pbio_error_t pbdrv_bluetooth_tx(const uint8_t *data, uint32_t *size) { // make sure we have a Bluetooth connection - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { + if (!pbdrv_bluetooth_host_is_connected()) { return PBIO_ERROR_INVALID_OP; } @@ -112,7 +112,7 @@ pbio_error_t pbdrv_bluetooth_tx(const uint8_t *data, uint32_t *size) { } uint32_t pbdrv_bluetooth_tx_available(void) { - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { + if (!pbdrv_bluetooth_host_is_connected()) { return UINT32_MAX; } @@ -120,7 +120,7 @@ uint32_t pbdrv_bluetooth_tx_available(void) { } bool pbdrv_bluetooth_tx_is_idle(void) { - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { + if (!pbdrv_bluetooth_host_is_connected()) { return true; } @@ -130,7 +130,7 @@ bool pbdrv_bluetooth_tx_is_idle(void) { pbio_error_t pbdrv_bluetooth_send_event_notification(pbio_os_state_t *state, pbio_pybricks_event_t event_type, const uint8_t *data, size_t size) { PBIO_OS_ASYNC_BEGIN(state); - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { + if (!pbdrv_bluetooth_host_is_connected()) { return PBIO_ERROR_INVALID_OP; } @@ -194,7 +194,7 @@ const char *pbdrv_bluetooth_peripheral_get_name(pbdrv_bluetooth_peripheral_t *pe pbio_error_t pbdrv_bluetooth_peripheral_scan_and_connect(pbdrv_bluetooth_peripheral_t *peri, pbdrv_bluetooth_peripheral_connect_config_t *config) { - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_HCI)) { + if (!pbdrv_bluetooth_hci_is_enabled()) { return PBIO_ERROR_INVALID_OP; } @@ -332,7 +332,7 @@ pbdrv_bluetooth_advertising_state_t pbdrv_bluetooth_advertising_state; pbio_error_t pbdrv_bluetooth_start_advertising(bool start) { - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_HCI)) { + if (!pbdrv_bluetooth_hci_is_enabled()) { return PBIO_ERROR_INVALID_OP; } @@ -367,7 +367,7 @@ uint8_t pbdrv_bluetooth_broadcast_data_size; pbio_error_t pbdrv_bluetooth_start_broadcasting(const uint8_t *data, size_t size) { - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_HCI)) { + if (!pbdrv_bluetooth_hci_is_enabled()) { return PBIO_ERROR_INVALID_OP; } @@ -417,7 +417,7 @@ pbdrv_bluetooth_start_observing_callback_t pbdrv_bluetooth_observe_callback; pbio_error_t pbdrv_bluetooth_start_observing(pbdrv_bluetooth_start_observing_callback_t callback) { - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_HCI)) { + if (!pbdrv_bluetooth_hci_is_enabled()) { return PBIO_ERROR_INVALID_OP; } @@ -514,7 +514,7 @@ static pbdrv_bluetooth_classic_task_context_t pbdrv_bluetooth_classic_task_conte pbio_error_t pbdrv_bluetooth_start_inquiry_scan(pbdrv_bluetooth_inquiry_result_t *results, uint32_t *results_count, uint32_t *results_count_max, uint32_t duration_ms) { - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_HCI)) { + if (!pbdrv_bluetooth_hci_is_enabled()) { return PBIO_ERROR_INVALID_OP; } @@ -582,7 +582,7 @@ pbio_error_t pbdrv_bluetooth_process_thread(pbio_os_state_t *state, void *contex pbio_error_t err; // Shorthand notation accessible throughout. - bool can_send = pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS); + bool can_send = pbdrv_bluetooth_host_is_connected(); // For looping over peripherals. static uint8_t peri_index; @@ -724,7 +724,7 @@ pbio_error_t pbdrv_bluetooth_close_user_tasks(pbio_os_state_t *state, pbio_os_ti void pbdrv_bluetooth_deinit(void) { // If Bluetooth is not even initialized, nothing to do. - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_HCI)) { + if (!pbdrv_bluetooth_hci_is_enabled()) { return; } diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index 470c93612..3d8cafb61 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -185,12 +185,6 @@ static void pbdrv_bluetooth_peripheral_disconnect_now(pbdrv_bluetooth_peripheral gap_disconnect(peri->con_handle); } -/** - * Checks if the given peripheral is connected. - */ -bool pbdrv_bluetooth_peripheral_is_connected(pbdrv_bluetooth_peripheral_t *peri) { - return peri->con_handle != HCI_CON_HANDLE_INVALID; -} static pbio_os_state_t bluetooth_thread_state; static pbio_os_state_t bluetooth_thread_err; @@ -214,30 +208,16 @@ static void propagate_event(uint8_t *packet) { event_packet = NULL; } -bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection) { - - // Nothing connected if HCI is not running. - if (bluetooth_thread_err != PBIO_ERROR_AGAIN) { - return false; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_HCI) { - return true; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_LE && le_con_handle != HCI_CON_HANDLE_INVALID) { - return true; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_PYBRICKS && pybricks_con_handle != HCI_CON_HANDLE_INVALID) { - return true; - } +bool pbdrv_bluetooth_peripheral_is_connected(pbdrv_bluetooth_peripheral_t *peri) { + return peri->con_handle != HCI_CON_HANDLE_INVALID; +} - if (connection == PBDRV_BLUETOOTH_CONNECTION_UART && uart_con_handle != HCI_CON_HANDLE_INVALID) { - return true; - } +bool pbdrv_bluetooth_host_is_connected(void) { + return pybricks_con_handle != HCI_CON_HANDLE_INVALID; +} - return false; +bool pbdrv_bluetooth_hci_is_enabled(void) { + return bluetooth_thread_err == PBIO_ERROR_AGAIN; } static void nordic_spp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { @@ -1203,7 +1183,7 @@ static pbio_error_t bluetooth_btstack_handle_power_control(pbio_os_state_t *stat pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer) { - if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_HCI)) { + if (pbdrv_bluetooth_hci_is_enabled()) { return PBIO_ERROR_INVALID_OP; } diff --git a/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c index 1af20ed76..aee534487 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c +++ b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c @@ -137,6 +137,14 @@ bool pbdrv_bluetooth_peripheral_is_connected(pbdrv_bluetooth_peripheral_t *peri) return peri == &peripheral_singleton && peri->con_handle != 0; } +bool pbdrv_bluetooth_host_is_connected(void) { + return pybricks_notify_en; +} + +bool pbdrv_bluetooth_hci_is_enabled(void) { + return true; +} + /** * Converts a BlueNRG-MS error code to a PBIO error code. * @param [in] status The BlueNRG-MS error code. @@ -301,28 +309,6 @@ pbio_error_t pbdrv_bluetooth_stop_advertising_func(pbio_os_state_t *state, void PBIO_OS_ASYNC_END(PBIO_SUCCESS); } - -bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection) { - - if (connection == PBDRV_BLUETOOTH_CONNECTION_HCI) { - return true; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_LE && conn_handle) { - return true; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_PYBRICKS && pybricks_notify_en) { - return true; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_UART && uart_tx_notify_en) { - return true; - } - - return false; -} - pbio_error_t pbdrv_bluetooth_send_pybricks_value_notification(pbio_os_state_t *state, const uint8_t *data, uint16_t size) { PBIO_OS_ASYNC_BEGIN(state); diff --git a/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c index 445fd6a7e..bc597b67b 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c +++ b/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c @@ -156,6 +156,14 @@ bool pbdrv_bluetooth_peripheral_is_connected(pbdrv_bluetooth_peripheral_t *peri) return peri == &peripheral_singleton && peri->con_handle != NO_CONNECTION; } +bool pbdrv_bluetooth_host_is_connected(void) { + return pybricks_notify_en && !busy_disconnecting; +} + +bool pbdrv_bluetooth_hci_is_enabled(void) { + return true; +} + /** * Converts a ble error code to the most appropriate pbio error code. * @param [in] status The ble error code. @@ -343,27 +351,6 @@ pbio_error_t pbdrv_bluetooth_stop_advertising_func(pbio_os_state_t *state, void PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection) { - - if (connection == PBDRV_BLUETOOTH_CONNECTION_HCI) { - return true; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_LE && conn_handle != NO_CONNECTION && !busy_disconnecting) { - return true; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_PYBRICKS && pybricks_notify_en) { - return true; - } - - if (connection == PBDRV_BLUETOOTH_CONNECTION_UART && uart_tx_notify_en) { - return true; - } - - return false; -} - pbio_error_t pbdrv_bluetooth_send_pybricks_value_notification(pbio_os_state_t *state, const uint8_t *data, uint16_t size) { static attHandleValueNoti_t notification; @@ -403,8 +390,8 @@ pbio_error_t pbdrv_bluetooth_peripheral_scan_and_connect_func(pbio_os_state_t *s if (conn_handle != NO_CONNECTION && (peri->config->options & PBDRV_BLUETOOTH_PERIPHERAL_OPTIONS_DISCONNECT_HOST)) { DEBUG_PRINT("Disconnect from Pybricks code (%d).\n", conn_handle); - // Guard used in pbdrv_bluetooth_is_connected so higher level processes - // won't try to send anything while we are disconnecting. + // Guard used in pbdrv_bluetooth_host_is_connected so higher level + // processes won't try to send anything while we are disconnecting. busy_disconnecting = true; PBIO_OS_AWAIT_WHILE(state, write_xfer_size); GAP_TerminateLinkReq(conn_handle, 0x13); diff --git a/lib/pbio/include/pbdrv/bluetooth.h b/lib/pbio/include/pbdrv/bluetooth.h index f88805a60..b9823fdbd 100644 --- a/lib/pbio/include/pbdrv/bluetooth.h +++ b/lib/pbio/include/pbdrv/bluetooth.h @@ -22,20 +22,6 @@ #define PBDRV_BLUETOOTH_MAX_CHAR_SIZE 20 #define PBDRV_BLUETOOTH_MAX_ADV_SIZE 31 -/** - * BLE characteristic connection identifiers. - */ -typedef enum { - /* Whether Bluetooth is present and enabled on this platform. */ - PBDRV_BLUETOOTH_CONNECTION_HCI, - /* A low energy device connection. */ - PBDRV_BLUETOOTH_CONNECTION_LE, - /** The Pybricks service. */ - PBDRV_BLUETOOTH_CONNECTION_PYBRICKS, - /** The Nordic UART service. */ - PBDRV_BLUETOOTH_CONNECTION_UART, -} pbdrv_bluetooth_connection_t; - /** Data structure that holds context needed for sending BLE notifications. */ typedef struct _pbdrv_bluetooth_send_context_t pbdrv_bluetooth_send_context_t; @@ -85,8 +71,6 @@ struct _pbdrv_bluetooth_send_context_t { const uint8_t *data; /** The size of @p data. */ uint8_t size; - /** The connection to use. Only characteristics with notify capability are allowed. */ - pbdrv_bluetooth_connection_t connection; }; /** @@ -301,12 +285,21 @@ const char *pbdrv_bluetooth_get_hub_name(void); const char *pbdrv_bluetooth_get_fw_version(void); /** - * Tests if a central is connected to the Bluetooth chip. + * Tests if a central is connected to the Bluetooth chip and subscribed to + * Pybricks events. + * * @param [in] connection The type of connection of interest. - * @return True if the requested connection type is present, + * @return True if Pybricks host connected, otherwise false. + */ +bool pbdrv_bluetooth_host_is_connected(void); + +/** + * Tests if the Bluetooth controller is up and running. + * + * @return True if running, * otherwise false. */ -bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection); +bool pbdrv_bluetooth_hci_is_enabled(void); /** * Sets a callback to be called when a Bluetooth host is connected or disconnected. @@ -638,7 +631,11 @@ static inline const char *pbdrv_bluetooth_get_fw_version(void) { return ""; } -static inline bool pbdrv_bluetooth_is_connected(pbdrv_bluetooth_connection_t connection) { +static inline bool pbdrv_bluetooth_host_is_connected(void) { + return false; +} + +static inline bool pbdrv_bluetooth_hci_is_enabled(void) { return false; } diff --git a/lib/pbio/sys/hmi_pup.c b/lib/pbio/sys/hmi_pup.c index 5068f450b..4ef8d0cd9 100644 --- a/lib/pbio/sys/hmi_pup.c +++ b/lib/pbio/sys/hmi_pup.c @@ -143,7 +143,7 @@ static void pbsys_hmi_host_update_indications(void) { } // Update BLE light indication. - if (pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { + if (pbdrv_bluetooth_host_is_connected()) { pbsys_status_set(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); } else { pbsys_status_clear(PBIO_PYBRICKS_STATUS_BLE_HOST_CONNECTED); diff --git a/lib/pbio/sys/host.c b/lib/pbio/sys/host.c index fb169c2eb..d08bb65ef 100644 --- a/lib/pbio/sys/host.c +++ b/lib/pbio/sys/host.c @@ -47,7 +47,7 @@ void pbsys_host_schedule_status_update(const uint8_t *buf) { * @return @c true if connection is active, else @c false. */ bool pbsys_host_is_connected(void) { - return pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS) || + return pbdrv_bluetooth_host_is_connected() || pbdrv_usb_connection_is_active(); } diff --git a/pybricks/iodevices/pb_type_iodevices_xbox_controller.c b/pybricks/iodevices/pb_type_iodevices_xbox_controller.c index 1663ff9be..44b82c19e 100644 --- a/pybricks/iodevices/pb_type_iodevices_xbox_controller.c +++ b/pybricks/iodevices/pb_type_iodevices_xbox_controller.c @@ -453,7 +453,7 @@ static mp_obj_t pb_type_xbox_make_new(const mp_obj_type_t *type, size_t n_args, // By default, disconnect Technic Hub from host, as this is required for // most hosts. Stay connected only if the user explicitly requests it. #if PYBRICKS_HUB_TECHNICHUB - if (!mp_obj_is_true(stay_connected_in) && pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { + if (!mp_obj_is_true(stay_connected_in) && pbdrv_bluetooth_host_is_connected()) { scan_config.options |= PBDRV_BLUETOOTH_PERIPHERAL_OPTIONS_DISCONNECT_HOST; mp_printf(&mp_plat_print, "The hub may disconnect from the computer for better connectivity with the controller.\n"); mp_hal_delay_ms(500); From 4002d268798a2d42912553b7d96c122929124a2e Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 15 Jan 2026 15:51:20 +0100 Subject: [PATCH 6/7] pbio/drv/bluetooth_btstack: Allow multiple hosts. This allows using a secondary device like a phone to monitor the connection. --- CHANGELOG.md | 2 + lib/pbio/drv/bluetooth/bluetooth_btstack.c | 186 +++++++++++++----- .../drv/bluetooth/pybricks_service_server.c | 4 +- lib/pbio/include/pbdrv/bluetooth.h | 4 +- lib/pbio/platform/essential_hub/pbdrvconfig.h | 2 +- lib/pbio/platform/ev3/pbdrvconfig.h | 2 +- lib/pbio/platform/prime_hub/pbdrvconfig.h | 6 +- lib/pbio/platform/test/pbdrvconfig.h | 2 +- lib/pbio/platform/virtual_hub/pbdrvconfig.h | 2 +- 9 files changed, 150 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12e4e371d..dcd20404e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ - Reduced user stack size to `12 KB` on SPIKE Prime Hub to make it the same as SPIKE Essential Hub. This frees up some RAM for system resources, and we never use this much in practice. +- Allow simultaneous USB and up to two Bluetooth connections to SPIKE Prime. + Press the Bluetooth button to allow a connection when already connected. ### Fixed - Fixed `race=False` ignored in `pybricks.tools.multitask()` ([support#2468]). diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index 3d8cafb61..83bf39400 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -105,9 +105,42 @@ char pbdrv_bluetooth_hub_name[16] = "Pybricks Hub"; static uint8_t *event_packet; static const pbdrv_bluetooth_btstack_platform_data_t *pdata = &pbdrv_bluetooth_btstack_platform_data; -static hci_con_handle_t le_con_handle = HCI_CON_HANDLE_INVALID; -static hci_con_handle_t pybricks_con_handle = HCI_CON_HANDLE_INVALID; -static hci_con_handle_t uart_con_handle = HCI_CON_HANDLE_INVALID; + +/** + * State of a connected host (Pybricks Code or similar). + */ +typedef struct { + /** Connection handle. */ + hci_con_handle_t con_handle; + /** Pybricks service is configured. */ + bool pybricks_configured; + /** UART service is configured. */ + bool uart_configured; + /** Notification to send when ready. */ + btstack_context_callback_registration_t send_request; + /** Notification to send. */ + const uint8_t *notification_data; + /** Notification size to send. */ + uint16_t notification_size; + /** Notification has been sent. */ + bool notification_done; +} pbdrv_bluetooth_btstack_host_connection_t; + +#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS +static pbdrv_bluetooth_btstack_host_connection_t host_connections[PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS]; +#endif + +static pbdrv_bluetooth_btstack_host_connection_t *pbdrv_bluetooth_btstack_get_host_connection(hci_con_handle_t con_handle) { + #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS + for (size_t i = 0; i < PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS; i++) { + pbdrv_bluetooth_btstack_host_connection_t *host = &host_connections[i]; + if (host->con_handle == con_handle) { + return host; + } + } + #endif + return NULL; +} /** * Converts BTStack error to most appropriate PBIO error. @@ -132,6 +165,7 @@ static pbio_error_t att_error_to_pbio_error(uint8_t status) { } static pbio_pybricks_error_t pybricks_data_received(hci_con_handle_t tx_con_handle, const uint8_t *data, uint16_t size) { + // Treating all incoming host data the same. if (pbdrv_bluetooth_receive_handler) { return pbdrv_bluetooth_receive_handler(data, size); } @@ -140,7 +174,11 @@ static pbio_pybricks_error_t pybricks_data_received(hci_con_handle_t tx_con_hand } static void pybricks_configured(hci_con_handle_t tx_con_handle, uint16_t value) { - pybricks_con_handle = value ? tx_con_handle : HCI_CON_HANDLE_INVALID; + pbdrv_bluetooth_btstack_host_connection_t *host = pbdrv_bluetooth_btstack_get_host_connection(tx_con_handle); + if (host == NULL) { + return; + } + host->pybricks_configured = !!value; pbdrv_bluetooth_host_connection_changed(); } @@ -213,7 +251,15 @@ bool pbdrv_bluetooth_peripheral_is_connected(pbdrv_bluetooth_peripheral_t *peri) } bool pbdrv_bluetooth_host_is_connected(void) { - return pybricks_con_handle != HCI_CON_HANDLE_INVALID; + #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS + for (size_t i = 0; i < PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS; i++) { + pbdrv_bluetooth_btstack_host_connection_t *host = &host_connections[i]; + if (host->con_handle != HCI_CON_HANDLE_INVALID) { + return true; + } + } + #endif + return false; } bool pbdrv_bluetooth_hci_is_enabled(void) { @@ -228,12 +274,22 @@ static void nordic_spp_packet_handler(uint8_t packet_type, uint16_t channel, uin } switch (hci_event_gattservice_meta_get_subevent_code(packet)) { - case GATTSERVICE_SUBEVENT_SPP_SERVICE_CONNECTED: - uart_con_handle = gattservice_subevent_spp_service_connected_get_con_handle(packet); + case GATTSERVICE_SUBEVENT_SPP_SERVICE_CONNECTED: { + uint16_t handle = gattservice_subevent_spp_service_connected_get_con_handle(packet); + pbdrv_bluetooth_btstack_host_connection_t *host = pbdrv_bluetooth_btstack_get_host_connection(handle); + if (host) { + host->uart_configured = true; + } break; - case GATTSERVICE_SUBEVENT_SPP_SERVICE_DISCONNECTED: - uart_con_handle = HCI_CON_HANDLE_INVALID; + } + case GATTSERVICE_SUBEVENT_SPP_SERVICE_DISCONNECTED: { + uint16_t handle = gattservice_subevent_spp_service_disconnected_get_con_handle(packet); + pbdrv_bluetooth_btstack_host_connection_t *host = pbdrv_bluetooth_btstack_get_host_connection(handle); + if (host) { + host->uart_configured = false; + } break; + } default: break; } @@ -338,32 +394,42 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe // HCI_ROLE_SLAVE means the connecting device is the central and the hub is the peripheral // HCI_ROLE_MASTER means the connecting device is the peripheral and the hub is the central. if (hci_subevent_le_connection_complete_get_role(packet) == HCI_ROLE_SLAVE) { - le_con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + uint16_t handle = hci_subevent_le_connection_complete_get_connection_handle(packet); + pbdrv_bluetooth_btstack_host_connection_t *host = pbdrv_bluetooth_btstack_get_host_connection(HCI_CON_HANDLE_INVALID); + if (host == NULL) { + DEBUG_PRINT("Warning: more than %d LE connections established.\n", PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS); + break; + } + host->con_handle = handle; // don't start advertising again on disconnect gap_advertisements_enable(false); pbdrv_bluetooth_advertising_state = PBDRV_BLUETOOTH_ADVERTISING_STATE_NONE; } break; - case HCI_EVENT_DISCONNECTION_COMPLETE: + case HCI_EVENT_DISCONNECTION_COMPLETE: { DEBUG_PRINT("HCI_EVENT_DISCONNECTION_COMPLETE\n"); - if (hci_event_disconnection_complete_get_connection_handle(packet) == le_con_handle) { - le_con_handle = HCI_CON_HANDLE_INVALID; - pybricks_con_handle = HCI_CON_HANDLE_INVALID; - uart_con_handle = HCI_CON_HANDLE_INVALID; + uint16_t handle = hci_event_disconnection_complete_get_connection_handle(packet); + pbdrv_bluetooth_btstack_host_connection_t *host = pbdrv_bluetooth_btstack_get_host_connection(handle); + if (host) { + host->con_handle = HCI_CON_HANDLE_INVALID; + host->pybricks_configured = false; + host->uart_configured = false; pbdrv_bluetooth_host_connection_changed(); + DEBUG_PRINT("Host with handle %u disconnected\n", handle); } else { 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 && hci_event_disconnection_complete_get_connection_handle(packet) == peri->con_handle) { + if (peri && handle == peri->con_handle) { DEBUG_PRINT("Peripheral %u with handle %u disconnected\n", i, peri->con_handle); gatt_client_stop_listening_for_characteristic_value_updates(&peri->platform_state->notification); peri->con_handle = HCI_CON_HANDLE_INVALID; + break; } } } break; - + } case GAP_EVENT_ADVERTISING_REPORT: { uint8_t event_type = gap_event_advertising_report_get_advertising_event_type(packet); uint8_t data_length = gap_event_advertising_report_get_data_length(packet); @@ -508,7 +574,7 @@ static void init_advertising_data(void) { } pbio_error_t pbdrv_bluetooth_start_advertising_func(pbio_os_state_t *state, void *context) { - #if !PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER + #if !PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS return PBIO_ERROR_NOT_SUPPORTED; #endif @@ -518,10 +584,10 @@ pbio_error_t pbdrv_bluetooth_start_advertising_func(pbio_os_state_t *state, void PBIO_OS_ASYNC_BEGIN(state); - // Don't advertise if already connected. BTstack also protects against this, - // but it means we never get the HCI event complete command because it is - // never given. - if (le_con_handle != HCI_CON_HANDLE_INVALID) { + pbdrv_bluetooth_btstack_host_connection_t *host = pbdrv_bluetooth_btstack_get_host_connection(HCI_CON_HANDLE_INVALID); + if (host == NULL) { + // There should be at least one available host connection. Otherwise + // we will never receive the advertise completion event below. return PBIO_ERROR_INVALID_OP; } @@ -551,43 +617,50 @@ pbio_error_t pbdrv_bluetooth_stop_advertising_func(pbio_os_state_t *state, void PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -typedef struct { - const uint8_t *data; - uint16_t size; - bool done; -} send_data_t; - +#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS static void pybricks_on_ready_to_send(void *context) { - send_data_t *send = context; - pybricks_service_server_send(pybricks_con_handle, send->data, send->size); - send->done = true; + pbdrv_bluetooth_btstack_host_connection_t *host = context; + pybricks_service_server_send(host->con_handle, host->notification_data, host->notification_size); + host->notification_done = true; pbio_os_request_poll(); } +#endif pbio_error_t pbdrv_bluetooth_send_pybricks_value_notification(pbio_os_state_t *state, const uint8_t *data, uint16_t size) { - #if !PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER - return PBIO_ERROR_NOT_SUPPORTED; - #endif - if (!pbdrv_bluetooth_btstack_ble_supported()) { return PBIO_ERROR_NOT_SUPPORTED; } - static send_data_t send_data; + #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS + static size_t i; + static pbdrv_bluetooth_btstack_host_connection_t *host; - static btstack_context_callback_registration_t send_request = { - .callback = pybricks_on_ready_to_send, - .context = &send_data, - }; PBIO_OS_ASYNC_BEGIN(state); - send_data.data = data; - send_data.size = size; - send_data.done = false; - pybricks_service_server_request_can_send_now(&send_request, pybricks_con_handle); - PBIO_OS_AWAIT_UNTIL(state, send_data.done); + for (i = 0; i < PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS; i++) { + host = &host_connections[i]; + if (host->con_handle != HCI_CON_HANDLE_INVALID && host->pybricks_configured) { + host->notification_data = data; + host->notification_size = size; + host->notification_done = false; + pybricks_service_server_request_can_send_now(&host->send_request, host->con_handle); + } else { + // No connection or not configured, don't hold up wait loop below. + host->notification_done = true; + } + } + + // Wait for all notifications to be sent. BTstack handles sending + // asynchronously. This loop completes when the slowest one is done. + for (i = 0; i < PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS; i++) { + host = &host_connections[i]; + PBIO_OS_AWAIT_UNTIL(state, host->notification_done); + } PBIO_OS_ASYNC_END(PBIO_SUCCESS); + #else + return PBIO_ERROR_NOT_SUPPORTED; + #endif } pbio_error_t pbdrv_bluetooth_peripheral_scan_and_connect_func(pbio_os_state_t *state, void *context) { @@ -1192,10 +1265,18 @@ pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_ti PBIO_OS_ASYNC_BEGIN(state); // Disconnect gracefully if connected to host. - if (le_con_handle != HCI_CON_HANDLE_INVALID) { - gap_disconnect(le_con_handle); - PBIO_OS_AWAIT_UNTIL(state, le_con_handle == HCI_CON_HANDLE_INVALID); + #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS + static size_t i; + static pbdrv_bluetooth_btstack_host_connection_t *host; + for (i = 0; i < PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS; i++) { + host = &host_connections[i]; + if (host->con_handle == HCI_CON_HANDLE_INVALID) { + continue; + } + gap_disconnect(host->con_handle); + PBIO_OS_AWAIT_UNTIL(state, host->con_handle == HCI_CON_HANDLE_INVALID); } + #endif // Wait for power off. PBIO_OS_AWAIT(state, &sub, bluetooth_btstack_handle_power_control(&sub, HCI_POWER_OFF, HCI_STATE_OFF)); @@ -1341,7 +1422,7 @@ void pbdrv_bluetooth_init_hci(void) { // GATT Client setup gatt_client_init(); - #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER + #if PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS // setup ATT server att_server_init(profile_data, att_read_callback, NULL); @@ -1352,6 +1433,13 @@ void pbdrv_bluetooth_init_hci(void) { pybricks_service_server_init(pybricks_data_received, pybricks_configured); nordic_spp_service_server_init(nordic_spp_packet_handler); + + for (size_t i = 0; i < PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS; i++) { + pbdrv_bluetooth_btstack_host_connection_t *host = &host_connections[i]; + host->con_handle = HCI_CON_HANDLE_INVALID; + host->send_request.callback = pybricks_on_ready_to_send; + host->send_request.context = host; + } #else (void)pybricks_data_received; (void)att_read_callback; diff --git a/lib/pbio/drv/bluetooth/pybricks_service_server.c b/lib/pbio/drv/bluetooth/pybricks_service_server.c index 7fbb91ba9..c8ed9764d 100644 --- a/lib/pbio/drv/bluetooth/pybricks_service_server.c +++ b/lib/pbio/drv/bluetooth/pybricks_service_server.c @@ -42,7 +42,7 @@ #include -#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER +#if PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS #define BTSTACK_FILE__ "pybricks_service_server.c" @@ -168,4 +168,4 @@ int pybricks_service_server_send(hci_con_handle_t con_handle, const uint8_t *dat return att_server_notify(con_handle, pybricks_command_event_value_handle, data, size); } -#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER +#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS diff --git a/lib/pbio/include/pbdrv/bluetooth.h b/lib/pbio/include/pbdrv/bluetooth.h index b9823fdbd..2bcfb7909 100644 --- a/lib/pbio/include/pbdrv/bluetooth.h +++ b/lib/pbio/include/pbdrv/bluetooth.h @@ -285,8 +285,8 @@ const char *pbdrv_bluetooth_get_hub_name(void); const char *pbdrv_bluetooth_get_fw_version(void); /** - * Tests if a central is connected to the Bluetooth chip and subscribed to - * Pybricks events. + * Tests if at least one central is connected to the Bluetooth chip and + * subscribed to Pybricks events. * * @param [in] connection The type of connection of interest. * @return True if Pybricks host connected, otherwise false. diff --git a/lib/pbio/platform/essential_hub/pbdrvconfig.h b/lib/pbio/platform/essential_hub/pbdrvconfig.h index e24c1977d..8844e2eec 100644 --- a/lib/pbio/platform/essential_hub/pbdrvconfig.h +++ b/lib/pbio/platform/essential_hub/pbdrvconfig.h @@ -30,7 +30,7 @@ #define PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS (2) #define PBDRV_CONFIG_BLUETOOTH_MAX_MTU_SIZE 515 #define PBDRV_CONFIG_BLUETOOTH_BTSTACK (1) -#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_STM32 (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_CC2564C (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND LWP3_HUB_KIND_TECHNIC_SMALL diff --git a/lib/pbio/platform/ev3/pbdrvconfig.h b/lib/pbio/platform/ev3/pbdrvconfig.h index a0ccda8ce..9e994deae 100644 --- a/lib/pbio/platform/ev3/pbdrvconfig.h +++ b/lib/pbio/platform/ev3/pbdrvconfig.h @@ -50,7 +50,7 @@ #define PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS (2) #define PBDRV_CONFIG_BLUETOOTH_MAX_MTU_SIZE 515 #define PBDRV_CONFIG_BLUETOOTH_BTSTACK (1) -#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER (0) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS (0) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_CLASSIC (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_EV3 (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_CC2560X (1) diff --git a/lib/pbio/platform/prime_hub/pbdrvconfig.h b/lib/pbio/platform/prime_hub/pbdrvconfig.h index a2d3748fe..5bb32df8e 100644 --- a/lib/pbio/platform/prime_hub/pbdrvconfig.h +++ b/lib/pbio/platform/prime_hub/pbdrvconfig.h @@ -30,7 +30,7 @@ #define PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS (2) #define PBDRV_CONFIG_BLUETOOTH_MAX_MTU_SIZE 515 #define PBDRV_CONFIG_BLUETOOTH_BTSTACK (1) -#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS (2) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_STM32 (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_CC2564C (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND LWP3_HUB_KIND_TECHNIC_LARGE @@ -119,7 +119,7 @@ #define PBDRV_CONFIG_USB (1) #define PBDRV_CONFIG_USB_MAX_PACKET_SIZE (64) -#define PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS (2) +#define PBDRV_CONFIG_USB_NUM_BUFFERED_PACKETS (20) #define PBDRV_CONFIG_USB_VID LEGO_USB_VID #define PBDRV_CONFIG_USB_PID 0xFFFF #define PBDRV_CONFIG_USB_PID_0 LEGO_USB_PID_SPIKE_PRIME @@ -128,7 +128,7 @@ #define PBDRV_CONFIG_USB_PROD_STR LEGO_USB_PROD_STR_TECHNIC_LARGE_HUB " + Pybricks" #define PBDRV_CONFIG_USB_STM32F4 (1) #define PBDRV_CONFIG_USB_STM32F4_HUB_VARIANT_ADDR 0x08007d80 -#define PBDRV_CONFIG_USB_CHARGE_ONLY (1) +#define PBDRV_CONFIG_USB_CHARGE_ONLY (0) #define PBDRV_CONFIG_STACK (1) #define PBDRV_CONFIG_STACK_EMBEDDED (1) diff --git a/lib/pbio/platform/test/pbdrvconfig.h b/lib/pbio/platform/test/pbdrvconfig.h index 584c5e473..eb0a6c558 100644 --- a/lib/pbio/platform/test/pbdrvconfig.h +++ b/lib/pbio/platform/test/pbdrvconfig.h @@ -10,7 +10,7 @@ #define PBDRV_CONFIG_BLUETOOTH (1) #define PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK (1) -#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_CC2564C (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND 0xff diff --git a/lib/pbio/platform/virtual_hub/pbdrvconfig.h b/lib/pbio/platform/virtual_hub/pbdrvconfig.h index 7c879d8c2..da5411c92 100644 --- a/lib/pbio/platform/virtual_hub/pbdrvconfig.h +++ b/lib/pbio/platform/virtual_hub/pbdrvconfig.h @@ -14,7 +14,7 @@ #define PBDRV_CONFIG_BLUETOOTH_NUM_CLASSIC_CONNECTIONS (2) #define PBDRV_CONFIG_BLUETOOTH_NUM_PERIPHERALS (2) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK (1) -#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_LE_SERVER (1) +#define PBDRV_CONFIG_BLUETOOTH_BTSTACK_NUM_LE_HOSTS (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_POSIX (1) #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_KIND (LWP3_HUB_KIND_TECHNIC_LARGE) #endif // PBDRV_CONFIG_RUN_ON_CI From e1850c1b287b2bcdf8b56398c290a20cca769dbe Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Wed, 21 Jan 2026 10:46:04 +0100 Subject: [PATCH 7/7] pbio/drv/bluetooth_btstack: Fix Bluetooth not turning off on shutdown. This was accidentally negated in a17c1d2c369baac351e0ce00376177a80232dbd3. --- lib/pbio/drv/bluetooth/bluetooth_btstack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index 83bf39400..1e80da0b7 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -1256,7 +1256,7 @@ static pbio_error_t bluetooth_btstack_handle_power_control(pbio_os_state_t *stat pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer) { - if (pbdrv_bluetooth_hci_is_enabled()) { + if (!pbdrv_bluetooth_hci_is_enabled()) { return PBIO_ERROR_INVALID_OP; }