From 8b576b375e251cc145cf980d8278e09700f9c80b Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Tue, 30 Sep 2025 10:32:30 +0200 Subject: [PATCH] pbio/drv/bluetooth: Add way to forcefully exit. Such as mechanism had not yet been implemented after overhauling the Bluetooth drivers. Before, we used to try to disconnect and stop observing/scanning in the MicroPython deinit, and stop waiting in case of a shutdown request. No we do it as part of the pbio cleanup, and enforce shutdown if it fails since this is not a recoverable state. --- lib/pbio/drv/bluetooth/bluetooth.c | 45 +++++++++++++++++++ lib/pbio/drv/bluetooth/bluetooth.h | 1 + lib/pbio/drv/bluetooth/bluetooth_btstack.c | 6 ++- .../drv/bluetooth/bluetooth_stm32_bluenrg.c | 12 +++-- .../drv/bluetooth/bluetooth_stm32_cc2640.c | 13 ++++-- lib/pbio/include/pbdrv/bluetooth.h | 18 ++++++++ lib/pbio/include/pbio/main.h | 4 +- lib/pbio/src/main.c | 39 +++++++--------- lib/pbio/sys/main.c | 7 ++- 9 files changed, 112 insertions(+), 33 deletions(-) diff --git a/lib/pbio/drv/bluetooth/bluetooth.c b/lib/pbio/drv/bluetooth/bluetooth.c index 639d0490d..11275007b 100644 --- a/lib/pbio/drv/bluetooth/bluetooth.c +++ b/lib/pbio/drv/bluetooth/bluetooth.c @@ -559,7 +559,52 @@ pbio_error_t pbdrv_bluetooth_process_thread(pbio_os_state_t *state, void *contex PBIO_OS_ASYNC_END(PBIO_SUCCESS); } +pbio_error_t pbdrv_bluetooth_close_user_tasks(pbio_os_state_t *state, pbio_os_timer_t *timer) { + + static pbio_os_state_t sub; + + if (pbio_os_timer_is_expired(timer)) { + return PBIO_ERROR_TIMEDOUT; + } + + PBIO_OS_ASYNC_BEGIN(state); + + pbdrv_bluetooth_cancel_operation_request(); + + // Let ongoing user tasks finish first. + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_peripheral_command(&sub, NULL)); + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); + + // Disconnect peripheral. + pbdrv_bluetooth_peripheral_disconnect(); + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_peripheral_command(&sub, NULL)); + + // Stop scanning. + pbdrv_bluetooth_start_observing(NULL); + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); + + // Stop advertising. + pbdrv_bluetooth_start_broadcasting(NULL, 0); + PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_await_advertise_or_scan_command(&sub, NULL)); + + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + void pbdrv_bluetooth_deinit(void) { + + // 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. + pbio_os_state_t unused; + if (pbdrv_bluetooth_await_advertise_or_scan_command(&unused, NULL) != PBIO_SUCCESS || + pbdrv_bluetooth_await_peripheral_command(&unused, NULL) != PBIO_SUCCESS) { + + // Hard reset without waitng on completion of any process. + pbdrv_bluetooth_controller_reset_hard(); + return; + } + + // Gracefully disconnect from host and power down. pbio_busy_count_up(); pbdrv_bluetooth_cancel_operation_request(); shutting_down = true; diff --git a/lib/pbio/drv/bluetooth/bluetooth.h b/lib/pbio/drv/bluetooth/bluetooth.h index 3d5c09060..b0856ff00 100644 --- a/lib/pbio/drv/bluetooth/bluetooth.h +++ b/lib/pbio/drv/bluetooth/bluetooth.h @@ -24,6 +24,7 @@ #define PBDRV_BLUETOOTH_MAX_ADV_SIZE 31 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); diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index f35242c0a..69930ab58 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -848,6 +848,10 @@ const char *pbdrv_bluetooth_get_fw_version(void) { return "v1.4"; } +void pbdrv_bluetooth_controller_reset_hard(void) { + hci_power_control(HCI_POWER_OFF); +} + pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer) { // The event handler also pushes the bluetooth process along, but shouldn't @@ -864,7 +868,7 @@ pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_ti PBIO_OS_AWAIT_UNTIL(state, le_con_handle == HCI_CON_HANDLE_INVALID); } - hci_power_control(HCI_POWER_OFF); + pbdrv_bluetooth_controller_reset_hard(); PBIO_OS_AWAIT_UNTIL(state, hci_get_state() == HCI_STATE_OFF); PBIO_OS_ASYNC_END(PBIO_SUCCESS); diff --git a/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c index dc2853cae..f426168c1 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c +++ b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c @@ -1237,6 +1237,13 @@ static pbio_error_t init_uart_service(pbio_os_state_t *state, void *context) { PBIO_OS_ASYNC_END(PBIO_SUCCESS); } +void pbdrv_bluetooth_controller_reset_hard(void) { + pybricks_notify_en = uart_tx_notify_en = false; + conn_handle = peripheral_singleton.con_handle = 0; + spi_disable_cs(); + bluetooth_reset(true); +} + pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer) { PBIO_OS_ASYNC_BEGIN(state); @@ -1249,11 +1256,8 @@ pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_ti PBIO_OS_AWAIT_UNTIL(state, conn_handle == 0); } - pybricks_notify_en = uart_tx_notify_en = false; - conn_handle = peripheral_singleton.con_handle = 0; + pbdrv_bluetooth_controller_reset_hard(); - spi_disable_cs(); - bluetooth_reset(true); PBIO_OS_AWAIT_MS(state, timer, 50); PBIO_OS_ASYNC_END(PBIO_SUCCESS); diff --git a/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c index 2159cf8f7..d59ad08cb 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c +++ b/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c @@ -1837,6 +1837,14 @@ static pbio_error_t init_uart_service(pbio_os_state_t *state, void *context) { PBIO_OS_ASYNC_END(PBIO_SUCCESS); } +void pbdrv_bluetooth_controller_reset_hard(void) { + pybricks_notify_en = uart_tx_notify_en = false; + conn_handle = peripheral_singleton.con_handle = NO_CONNECTION; + + spi_set_mrdy(false); + bluetooth_reset(RESET_STATE_OUT_LOW); +} + pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_timer_t *timer) { PBIO_OS_ASYNC_BEGIN(state); @@ -1847,11 +1855,8 @@ pbio_error_t pbdrv_bluetooth_controller_reset(pbio_os_state_t *state, pbio_os_ti PBIO_OS_AWAIT_UNTIL(state, conn_handle == NO_CONNECTION); } - pybricks_notify_en = uart_tx_notify_en = false; - conn_handle = peripheral_singleton.con_handle = NO_CONNECTION; + pbdrv_bluetooth_controller_reset_hard(); - spi_set_mrdy(false); - bluetooth_reset(RESET_STATE_OUT_LOW); PBIO_OS_AWAIT_MS(state, timer, 150); PBIO_OS_ASYNC_END(PBIO_SUCCESS); diff --git a/lib/pbio/include/pbdrv/bluetooth.h b/lib/pbio/include/pbdrv/bluetooth.h index f55994c9d..7cba0b6cf 100644 --- a/lib/pbio/include/pbdrv/bluetooth.h +++ b/lib/pbio/include/pbdrv/bluetooth.h @@ -503,6 +503,19 @@ void pbdrv_bluetooth_restart_observing_request(void); */ pbio_error_t pbdrv_bluetooth_await_advertise_or_scan_command(pbio_os_state_t *state, void *context); +/** + * Awaits user activity to complete, usually called during cleanup after running + * a user program. This will disconnect from the peripheral and stop scanning + * and advertising. + * + * @param [in] state Protothread state. + * @param [in] timer Timer used to give up if this takes too long. + * @return ::PBIO_SUCCESS on completion. + * ::PBIO_ERROR_AGAIN while awaiting. + * ::PBIO_ERROR_TIMEDOUT if the timer expired. + */ +pbio_error_t pbdrv_bluetooth_close_user_tasks(pbio_os_state_t *state, pbio_os_timer_t *timer); + #else // PBDRV_CONFIG_BLUETOOTH static inline void pbdrv_bluetooth_init(void) { @@ -602,6 +615,11 @@ static inline pbio_error_t pbdrv_bluetooth_await_advertise_or_scan_command(pbio_ return PBIO_ERROR_NOT_SUPPORTED; } +static inline pbio_error_t pbdrv_bluetooth_close_user_tasks(pbio_os_state_t *state, pbio_os_timer_t *timer) { + return PBIO_ERROR_NOT_SUPPORTED; +} + + #endif // PBDRV_CONFIG_BLUETOOTH #endif // _PBDRV_BLUETOOTH_H_ diff --git a/lib/pbio/include/pbio/main.h b/lib/pbio/include/pbio/main.h index c1e7509ac..290d3590e 100644 --- a/lib/pbio/include/pbio/main.h +++ b/lib/pbio/include/pbio/main.h @@ -8,9 +8,11 @@ #include "pbio/config.h" +#include + void pbio_init(bool start_processes); void pbio_deinit(void); -void pbio_main_stop_application_resources(); +pbio_error_t pbio_main_stop_application_resources(void); void pbio_main_soft_stop(void); #endif // _PBIO_MAIN_H_ diff --git a/lib/pbio/src/main.c b/lib/pbio/src/main.c index 6df9c7378..b71ec7e85 100644 --- a/lib/pbio/src/main.c +++ b/lib/pbio/src/main.c @@ -69,37 +69,28 @@ void pbio_main_soft_stop(void) { pbdrv_bluetooth_cancel_operation_request(); } -static void wait_for_bluetooth(void) { - pbio_os_state_t unused; - while (pbdrv_bluetooth_await_advertise_or_scan_command(&unused, NULL) == PBIO_ERROR_AGAIN || - pbdrv_bluetooth_await_peripheral_command(&unused, NULL) == PBIO_ERROR_AGAIN) { - - // Run event loop until Bluetooth is idle. - pbio_os_run_processes_and_wait_for_event(); - } -} - /** * Stops all application-level background processes. Called when the user * application completes to get these modules back into their default state. * Drivers and OS-level processes continue running. + * + * @return ::PBIO_SUCCESS when completed + * ::PBIO_ERROR_TIMEDOUT if it could not stop processes in a reasonable + * amount of time. */ -void pbio_main_stop_application_resources() { +pbio_error_t pbio_main_stop_application_resources(void) { pbio_main_soft_stop(); - // Let ongoing task finish first. - wait_for_bluetooth(); + pbio_error_t err; + pbio_os_state_t state = 0; + pbio_os_timer_t timer; + pbio_os_timer_set(&timer, 5000); - // Stop broadcasting, observing and disconnect peripheral. - pbdrv_bluetooth_start_broadcasting(NULL, 0); - wait_for_bluetooth(); - - pbdrv_bluetooth_start_observing(NULL); - wait_for_bluetooth(); - - pbdrv_bluetooth_peripheral_disconnect(); - wait_for_bluetooth(); + // Run event loop until Bluetooth is idle or times out. + while ((err = pbdrv_bluetooth_close_user_tasks(&state, &timer)) == PBIO_ERROR_AGAIN) { + pbio_os_run_processes_and_wait_for_event(); + } #if PBIO_CONFIG_LIGHT pbio_light_animation_stop_all(); @@ -109,6 +100,10 @@ void pbio_main_stop_application_resources() { pbio_image_fill(pbdrv_display_get_image(), 0); pbdrv_display_update(); #endif + + pbio_os_run_processes_and_wait_for_event(); + + return err; } /** @} */ diff --git a/lib/pbio/sys/main.c b/lib/pbio/sys/main.c index fb7fe1701..ea439427a 100644 --- a/lib/pbio/sys/main.c +++ b/lib/pbio/sys/main.c @@ -114,7 +114,12 @@ int main(int argc, char **argv) { pbsys_main_run_program(&program); // Stop motors, user animations, user bluetooth activity, etc. - pbio_main_stop_application_resources(); + err = pbio_main_stop_application_resources(); + if (err != PBIO_SUCCESS) { + // If we couldn't get the system back in a normal state, proceed + // towards shutdown. + break; + } // Get system back in idle state. pbsys_status_clear(PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING);