Skip to content

Commit 21a754f

Browse files
committed
extmod/zephyr_ble: Add BLE reactivation support without bt_disable.
Allow BLE to be reactivated after ble.active(False) by handling the -EALREADY return from bt_enable() when the stack is already running. Like the vanilla Zephyr port, we don't call bt_disable() during deinit because it requires complex HCI coordination with the CYW43 controller. Instead, we leave the stack enabled and skip the init loop on reactivation, just restarting the HCI RX task and work thread. This enables basic reactivation scenarios (e.g., running multiple BLE tests sequentially). Connection cycle reactivation still has issues that need further investigation. Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
1 parent d3de945 commit 21a754f

File tree

1 file changed

+34
-33
lines changed

1 file changed

+34
-33
lines changed

extmod/zephyr_ble/modbluetooth_zephyr.c

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -404,16 +404,8 @@ int mp_bluetooth_init(void) {
404404
// Only initialize the BLE stack if not already ACTIVE
405405
int current_state = mp_bluetooth_zephyr_ble_state;
406406
if (current_state != MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE) {
407-
// Check if BLE was previously activated and then deactivated
408-
// Zephyr's bt_enable() can only be called once per process lifetime
409-
if (current_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED) {
410-
mp_raise_msg(&mp_type_RuntimeError,
411-
MP_ERROR_TEXT("BLE reactivation not supported. "
412-
"After ble.active(False), BLE cannot be reactivated. "
413-
"Use ble.active(True) once per session and leave active."));
414-
}
415-
416407
// First-time initialization: port resources and controller
408+
// Only do this when coming from OFF state, not when reinitializing from SUSPENDED
417409
if (current_state == MP_BLUETOOTH_ZEPHYR_BLE_STATE_OFF) {
418410
// Initialize port-specific resources (soft timers, sched nodes, etc.)
419411
#if MICROPY_PY_NETWORK_CYW43 || MICROPY_PY_BLUETOOTH_USE_ZEPHYR_HCI
@@ -433,7 +425,13 @@ int mp_bluetooth_init(void) {
433425
#endif
434426

435427
// Register connection callbacks (only on first init, not when reinitializing from SUSPENDED)
428+
// Zephyr docs: callbacks persist across bt_disable()/bt_enable() cycles
436429
bt_conn_cb_register(&mp_bt_zephyr_conn_callbacks);
430+
431+
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
432+
// Register scan callback (only on first init)
433+
bt_le_scan_cb_register(&mp_bluetooth_zephyr_gap_scan_cb_struct);
434+
#endif
437435
}
438436

439437
// Initialize Zephyr BLE host stack
@@ -457,6 +455,18 @@ int mp_bluetooth_init(void) {
457455
// Call bt_enable() with ready callback
458456
int ret = bt_enable(mp_bluetooth_zephyr_bt_ready_cb);
459457
DEBUG_printf("bt_enable returned %d\n", ret);
458+
459+
// Handle -EALREADY: stack is already enabled (reactivation from SUSPENDED)
460+
// In this case, skip the init loop and just restart our tasks
461+
if (ret == -EALREADY) {
462+
DEBUG_printf("BLE stack already enabled (reactivation)\n");
463+
mp_bluetooth_zephyr_bt_enable_result = 0; // Mark as success
464+
// Exit init phase since we're not going through the loop
465+
extern void mp_bluetooth_zephyr_init_phase_exit(void);
466+
mp_bluetooth_zephyr_init_phase_exit();
467+
goto init_complete;
468+
}
469+
460470
if (ret) {
461471
return bt_err_to_errno(ret);
462472
}
@@ -533,6 +543,7 @@ int mp_bluetooth_init(void) {
533543
}
534544
}
535545

546+
init_complete:
536547
DEBUG_printf("BLE initialization successful!\n");
537548

538549
// Start HCI RX task for continuous polling of incoming HCI data
@@ -546,11 +557,6 @@ int mp_bluetooth_init(void) {
546557
DEBUG_printf("BLE already ACTIVE (state=%d)\n", mp_bluetooth_zephyr_ble_state);
547558
}
548559

549-
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
550-
// Register scan callback every time we activate (it was unregistered during deinit)
551-
bt_le_scan_cb_register(&mp_bluetooth_zephyr_gap_scan_cb_struct);
552-
#endif
553-
554560
mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_ACTIVE;
555561

556562
DEBUG_printf("mp_bluetooth_init: ready\n");
@@ -565,36 +571,31 @@ int mp_bluetooth_deinit(void) {
565571
return 0;
566572
}
567573

568-
// Stop the HCI RX task first (stop receiving new HCI packets)
569-
mp_bluetooth_zephyr_hci_rx_task_stop();
570-
571-
// Stop the dedicated BLE work queue thread (FreeRTOS builds only)
572-
// Must be done before other cleanup to prevent work from being processed during teardown
573-
mp_bluetooth_zephyr_work_thread_stop();
574-
575-
// Set state to SUSPENDED first to prevent disconnect callbacks from unreffing connections
576-
// that will be unreffed by Zephyr's deferred_work (triggered by advertise_stop/scan_stop).
577-
//
578-
// LIMITATION: Reinit from SUSPENDED is not supported. Zephyr's bt_enable() can only be
579-
// called once (uses atomic CAS on bt_dev.enabled). Since we don't call bt_disable() here
580-
// (matching vanilla's approach - see ports/zephyr/modbluetooth_zephyr.c:331), the stack
581-
// remains enabled and bt_enable() will return -EALREADY on subsequent calls.
582-
// BLE can only be initialized once per process lifetime.
574+
// Set state to SUSPENDED to prevent callbacks during shutdown
583575
mp_bluetooth_zephyr_ble_state = MP_BLUETOOTH_ZEPHYR_BLE_STATE_SUSPENDED;
584576

577+
// Stop advertising/scanning before bt_disable()
585578
mp_bluetooth_gap_advertise_stop();
586579

580+
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
581+
mp_bluetooth_gap_scan_stop();
582+
#endif
583+
587584
#if CONFIG_BT_GATT_DYNAMIC_DB
588585
for (size_t i = 0; i < MP_STATE_PORT(bluetooth_zephyr_root_pointers)->n_services; ++i) {
589586
bt_gatt_service_unregister(MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i]);
590587
MP_STATE_PORT(bluetooth_zephyr_root_pointers)->services[i] = NULL;
591588
}
592589
#endif
593590

594-
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
595-
mp_bluetooth_gap_scan_stop();
596-
bt_le_scan_cb_unregister(&mp_bluetooth_zephyr_gap_scan_cb_struct);
597-
#endif
591+
// Note: We do NOT call bt_disable() here. Like the vanilla Zephyr port,
592+
// we leave the BLE stack enabled. On reactivation, bt_enable() returns
593+
// -EALREADY which we handle as success (stack is already running).
594+
// This avoids complex HCI shutdown/restart issues with the CYW43 controller.
595+
596+
// Stop HCI RX task and work thread
597+
mp_bluetooth_zephyr_hci_rx_task_stop();
598+
mp_bluetooth_zephyr_work_thread_stop();
598599

599600
MP_STATE_PORT(bluetooth_zephyr_root_pointers) = NULL;
600601
mp_bt_zephyr_next_conn = NULL;

0 commit comments

Comments
 (0)