Intermittent Stuck in "Connecting..." with iOS 18 and nRF Connect Mobile

Hello Nordic Community,

I’m encountering an intermittent issue with my BLE peripherals, which are based on nRF5340 + nRF7002 SoC, while testing with nRF Connect Mobile on devices running iOS 18.

The issue manifests as follows:

  • The peripheral devices are discoverable and show up in the scan list.
  • When attempting to connect, the connection gets stuck in "Connecting..." status.
  • Sometimes, restarting or disabling/enabling Bluetooth on the iOS device resolves the issue temporarily. However, the problem reappears after some time, especially if I try to reconnect later.
  • The issue is random and doesn’t occur with all peripherals.

This setup has been tested extensively with Android and earlier iOS versions without any such problems.

Details:

  1. Setup:

    • Sensor hardware: nRF5340 + nRF7002 SoC
    • nRF Connect SDK v2.6.0
    • Testing app: nRF Connect Mobile (iOS)
  2. Problem Symptoms:

    • Devices are discoverable via scanning.
    • Connection gets stuck at "Connecting..." in nRF Connect Mobile.
    • Sometimes restarting the sensor or toggling Bluetooth on the iOS device helps temporarily.
    • Randomly occurs, affecting some peripherals but not all.
    • Checking FW logs, seems like my peripheral is not even getting a connection request.
  3. What works:

    • Android devices and iOS versions prior to iOS 18 connect reliably.
    • Once connected, functionality appears normal, provided the connection is established.

Questions:

  1. Has anyone else experienced BLE connection issues specifically with nRF Connect Mobile on iOS 18?
  2. Are there any known compatibility updates or changes in iOS 18 that might cause this behavior?
  3. Could this be related to BLE resource allocation or the number of concurrent connections on the iOS device?
  4. What additional debugging steps or firmware configurations would you recommend to improve stability?

I’d appreciate any insights or suggestions on resolving this issue. Let me know if further details or logs are needed.

Thank you!

Parents Reply Children
  • Hi Susheel,

    Thank you for your response! The issue does not seem specific to a sample but appears across various custom implementations, including mine, which I’ve shared above. My setup uses nRF Connect SDK v2.6.0, and the advertisement and connection logic are implemented as shown in the code.

    This is my BLE related code:

    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(ble_control, CONFIG_LOG_DEFAULT_LEVEL);
    
    #include "ble_control.h"
    
    #include <zephyr/kernel.h>
    #include <zephyr/types.h>
    #include <stddef.h>
    #include <string.h>
    #include <stdio.h>
    #include <zephyr/logging/log_backend_ble.h>
    #include <zephyr/mgmt/mcumgr/transport/smp_bt.h>
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/uuid.h>
    #include <zephyr/bluetooth/gatt.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/settings/settings.h>
    #include <zephyr/bluetooth/services/dis.h>
    #include <zephyr/random/random.h>
    
    #include "version.h"
    #include "status_control.h"
    #include "sensor_control.h"
    #include "led_control.h"
    #include "../power_management.h"
    #include "../services/data_service.h"
    #include "../services/start_service.h"
    #include "../services/production_service.h"
    
    #define BLE_AUTH_DELAY_MS       1000 // ms
    #define MAX_AUTH_RETRIES        3
    #define RANDOM_ID_PREFIX        "EVR-"
    #define RANDOM_ID_LEN           32
    #define MIN_CONN_INTERVAL 0x0010 // 20ms
    #define MAX_CONN_INTERVAL 0x0020 // 40ms
    #define SLAVE_LATENCY     0      // No latency
    #define CONN_SUP_TIMEOUT  400    // 4 seconds
    #define MAX_AUTH_RETRIES  3
    #define RETRY_DELAY_MS    300
    
    static struct bt_le_conn_param *conn_params = BT_LE_CONN_PARAM(
        MIN_CONN_INTERVAL,
        MAX_CONN_INTERVAL,
        SLAVE_LATENCY,
        CONN_SUP_TIMEOUT
    );
    
    static struct k_work_delayable auth_work;
    static struct bt_conn *pending_conn = NULL;
    
    static bool bl_central_connected = false;
    static char random_id[RANDOM_ID_LEN + 1]; // +1 for null terminator
    
    static int auth_retries = 0;
    
    static struct bt_data ad[3] = {
        BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
        BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_DIS_VAL)),
        BT_DATA_BYTES(BT_DATA_UUID128_ALL, LOGGER_BACKEND_BLE_ADV_UUID_DATA),
        BT_DATA(BT_DATA_NAME_COMPLETE, NULL, 0), // Placeholder for name
    };
    
    /* Function prototypes */
    static int bt_ready(void);
    static void construct_advertisement_data(void);
    static void auth_work_handler(struct k_work *work);
    
    static struct bt_conn_le_phy_param *phy_params = BT_CONN_LE_PHY_PARAM_1M;
    
    /**
     * Construct advertisement data using the serial number and side from production_service.
     */
    static void construct_advertisement_data(void)
    {
        const char *serial = get_serialnumber();
    
        if (!serial || strlen(serial) == 0) {
            LOG_ERR("Serial number not available for advertisement.");
            return;
        }
    
        snprintf(random_id, RANDOM_ID_LEN, "%s%s", RANDOM_ID_PREFIX, serial);
    
        ad[2] = (struct bt_data)BT_DATA(BT_DATA_NAME_COMPLETE, random_id, strlen(random_id));
    
        LOG_INF("Advertisement data set: %s", random_id);
    }
    
    static int bt_ready(void)
    {
        int ret;
    
        LOG_INF("Bluetooth initialized");
    
        construct_advertisement_data();
    
        struct bt_le_adv_param adv_param = {
            .id = BT_ID_DEFAULT,
            .interval_min = 0x0030, // 30ms
            .interval_max = 0x0060, // 60ms
            .options = BT_LE_ADV_OPT_CONNECTABLE,
        };
    
        // Start advertising
        ret = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), NULL, 0);
        if (ret) {
            LOG_ERR("Advertising failed to start (err %d)", ret);
            return ret;
        }
    
        LOG_INF("Advertising successfully started");
        return 0;
    }
    
    static void auth_work_handler(struct k_work *work)
    {
        if (!pending_conn) {
            LOG_ERR("No connection available for security setup");
            return;
        }
    
        int security_err = bt_conn_set_security(pending_conn, BT_SECURITY_L2);
        if (security_err) {
            LOG_ERR("Failed to set security (err %d), retrying...", security_err);
            auth_retries++;
    
            if (auth_retries < MAX_AUTH_RETRIES) {
                k_work_reschedule(&auth_work, K_MSEC(RETRY_DELAY_MS));
            } else {
                LOG_ERR("Authentication failed after %d retries", MAX_AUTH_RETRIES);
                bt_conn_disconnect(pending_conn, BT_HCI_ERR_AUTH_FAIL);
                auth_retries = 0;
                bt_conn_unref(pending_conn);
                pending_conn = NULL;
            }
        } else {
            LOG_INF("Security successfully set");
            auth_retries = 0;
    
            // Update connection parameters
            bt_conn_le_param_update(pending_conn, conn_params);
    
            // Request PHY update
            int ret = bt_conn_le_phy_update(pending_conn, phy_params);
            if (ret) {
                LOG_ERR("Failed to update PHY (err %d)", ret);
            } else {
                LOG_INF("PHY update requested to 1M");
            }
    
            bt_conn_unref(pending_conn);
            pending_conn = NULL;
    
            led_switch(LED_GREEN);
            led_active_on();
            bl_central_connected = true;
    
            LOG_INF("Connected");
        }
    }
    
    int init_ble(void)
    {
        k_work_init_delayable(&auth_work, auth_work_handler);
    
        int ret = bt_enable(NULL);
        if (ret != 0) {
            LOG_ERR("Bluetooth enable failed: %d", ret);
            return ret;
        }
    
        ret = settings_load();
        if (ret != 0) {
            LOG_ERR("Load settings failed: %d", ret);
            return ret;
        }
    
        ret = production_service_init();
        if (ret != 0) {
            LOG_ERR("Init production service failed: %d", ret);
            return ret;
        }
    
        ret = bt_ready();
        if (ret != 0) {
            LOG_ERR("Bluetooth initialization failed: %d", ret);
            return ret;
        }
    
        LOG_INF("Bluetooth initialized!");
        return ret;
    }
    
    static void connected(struct bt_conn *conn, uint8_t err)
    {
        if (err) {
            LOG_ERR("Connection failed (err 0x%02x)", err);
            return;
        }
    
        auth_retries = 0;
    
        pending_conn = bt_conn_ref(conn);
        k_work_schedule(&auth_work, K_MSEC(BLE_AUTH_DELAY_MS));
    
        stop_power_off_work();
    }
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
        LOG_INF("Disconnected (reason 0x%02x)", reason);
    
        bl_central_connected = false;
        led_switch(LED_RED);
        start_power_off_work();
    }
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
        .connected = connected,
        .disconnected = disconnected,
    };
    
    bool central_connected(void)
    {
        return bl_central_connected;
    }

    The problem isn't specific to the nRF Connect app, as it also occurs in other BLE applications on iOS 18. However, Android and iOS versions prior to iOS 18 do not exhibit this behavior.

    If you can reproduce the issue on your end, it would be great to compare notes. Let me know if there are any specific tests or configurations you’d recommend to narrow down the problem further.

    Thank you!

Related