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.c b/lib/pbio/drv/bluetooth/bluetooth.c index 9f2cd1ffa..f1d03f46b 100644 --- a/lib/pbio/drv/bluetooth/bluetooth.c +++ b/lib/pbio/drv/bluetooth/bluetooth.c @@ -79,10 +79,22 @@ 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 - if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS)) { + if (!pbdrv_bluetooth_host_is_connected()) { return PBIO_ERROR_INVALID_OP; } @@ -100,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; } @@ -108,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; } @@ -118,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; } @@ -182,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; } @@ -320,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; } @@ -355,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; } @@ -405,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; } @@ -502,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; } @@ -570,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; @@ -712,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.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..1e80da0b7 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,12 @@ 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(); } static bool hci_event_is_type(uint8_t *packet, uint8_t event_type) { @@ -184,12 +223,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; @@ -213,32 +246,26 @@ 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) { + #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) { + 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) { switch (packet_type) { case HCI_EVENT_PACKET: @@ -247,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; } @@ -357,31 +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); @@ -526,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 @@ -536,6 +584,13 @@ pbio_error_t pbdrv_bluetooth_start_advertising_func(pbio_os_state_t *state, void PBIO_OS_ASYNC_BEGIN(state); + 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; + } + init_advertising_data(); gap_advertisements_enable(true); @@ -562,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) { @@ -1194,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_is_connected(PBDRV_BLUETOOTH_CONNECTION_HCI)) { + if (!pbdrv_bluetooth_hci_is_enabled()) { return PBIO_ERROR_INVALID_OP; } @@ -1203,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)); @@ -1352,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); @@ -1363,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/bluetooth_stm32_bluenrg.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c index bc3209546..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); @@ -889,6 +875,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 +933,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..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); @@ -1268,6 +1255,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 +1357,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/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/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..2bcfb7909 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,28 @@ 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 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 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. + * + * @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 @@ -631,10 +631,17 @@ 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; +} + +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. 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 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..4ef8d0cd9 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_host_is_connected()) { + 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 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/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 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);