@@ -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