Issue with the auto connect feature

Hi,

I'm developing a BLE Central using a nRF52840 DK. My project goal is to analyze the quality and the robustness of a peripheral's BLE connection.

For some parts of my testing procedure, I need the central to automatically reconnect to the peripheral. I achieve that by calling bt_le_set_auto_conn right after bt_conn_le_create.

I will add a reproducible sample at the end of this question. Nevertheless the central works this way:

  1. Scan the nearby until it found a device with a particular name (actually hardcoded in the central)
  2. Connect to that peripheral
  3. Activate bt_le_set_auto_conn
  4. Set the BLE security level to 3 (Encryption and authentication)
    1. Automatically confirm the passkey
  5. Start some threads (one to read the RSSI and one to retrieve the packet error rate)
  6. Wait
    1. When the connection is lost
      1. Stop the threads
      2. Release some memory
      3. Do not start scanning
    2. Reconnect to the peripheral when it comes back in range

What I observed

When the auto connect feature is enable, the disconnected callback is not called on a disconnection. However the connected callback is called on every reconnection.

The kernel was panicking on reconnection because since the disconnection callback is not called, the thread created before isn't close indeed that on reconnection, the central try to recreate the thread that already exist making him panicking.
I highly suspect that because then I tried to correct this by passing the bt_conn object to the thread on creation and in the while (1) loop in the thread's entrypoint, adding a switch to check the connection state and returning (to close the thread) if the connection was in a other state than BT_CONN_STATE_CONNECTED and now the thread is closed on disconnection, the kernel isn't panicking on reconnection.

After the first disconnection, the connection with the peripheral will closed every 30 seconds (I suspect that because the link isn't encrypted (neither the pairing complete callback or the security changed callback are call on a reconnection) and my peripheral need at least a security level 3)

The errors

[00:00:01.973,541] <inf> main: Connected: 7F:D6:F3:3C:D3:8A
[00:00:01.974,060] <dbg> main: bla bla bla
[00:00:02.075,500] <inf> main: Data length updated. Length 69/69 bytes, time 2120/2120 us
[00:00:02.327,972] <inf> main: Passkey confirmed
[00:00:02.775,115] <inf> main: Security changed to level 4
[00:00:02.876,007] <inf> main: pairing_complete: Pairing complete
[00:00:02.974,182] <dbg> main: bla bla bla
[00:00:03.569,091] <inf> main: Phy updated to 2M
[00:00:03.974,334] <dbg> main: bla bla bla
[00:00:04.974,517] <inf> main: peer disconnected
<---- I disconnected the peripheral by turning off the bluetooth ---->
<---- Then I turned on the bluetooth on the peripheral ---->
[00:00:09.155,853] <err> bt_l2cap: No available L2CAP context for conn 0x20002040
[00:00:09.155,883] <err> bt_smp: No available SMP context for conn 0x20002040
[00:00:09.155,914] <err> bt_att: No available ATT context for conn 0x20002040
[00:00:09.155,914] <inf> main: Connected: CA:D5:C8:E6:52:E9
[00:00:09.156,036] <dbg> main: bla bla bla
[00:00:09.157,531] <wrn> bt_l2cap: Ignoring data for unknown channel ID 0x0004
[00:00:09.257,415] <inf> main: Data length updated. Length 69/69 bytes, time 2120/2120 us
[00:00:10.156,097] <dbg> main: bla bla bla
[00:00:10.657,501] <wrn> bt_l2cap: Ignoring data for unknown channel ID 0x0006
[00:00:11.156,219] <dbg> main: bla bla bla
...

My questions

I presume that the auto connect feature "disable" the disconnected callback, why this behavior ?

What are this L2CAP, SMP and ATT errors ?? I know for sure that they are not raised by the connected callback in itself (my function) because I added a print at its beginning and the errors printed before mine.

What are this L2CAP warning that come after the connection callback saying that it's ignoring data for "unknown channel" ??

Limitations

I don't have access to the peripheral code. However, it must work with any peripheral. I actually use 4 different peripherals and the result are the same.

Setup

  • nRF52840 DK
  • nRF SDK v2.7.0
  • WSL2 (Ubuntu 22.04) on a Windows 10 host
  • If it's matter, I do not use the VS Code extension but the CLI version

Minimum reproducible example (main.c & prj.conf)

I reduce a lot the code by removing threads, discovering phases, buttons, ect... However, I added a dummy thread just to see if the device is connected or not

#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#include <zephyr/drivers/gpio.h>

#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/byteorder.h>

#define PERIPHERAL_TO_CONNECT_TO "HereIsThePeripheralName"

LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);

static struct bt_conn *default_conn;

#define THREAD_STACK_SIZE_EXAMPLE 1000
static k_tid_t th_id_example;
static struct k_thread th_data_example;
static K_THREAD_STACK_DEFINE(th_stack_example, THREAD_STACK_SIZE_EXAMPLE);

static void start_scan(void);

static bool parse_data_cb(struct bt_data *data, void *user_data)
{
  bt_addr_le_t *addr = user_data;

  switch (data->type)
  {
    case BT_DATA_NAME_SHORTENED:
    case BT_DATA_NAME_COMPLETE:
      ((uint8_t *)data->data)[data->data_len] = '\0';
      LOG_INF("[DEVICE]: %02X:%02X:%02X:%02X:%02X:%02X\tDevice name: %s", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0], data->data);
      if (strncmp(data->data, PERIPHERAL_TO_CONNECT_TO, data->data_len) == 0)
      {
        int err = bt_le_scan_stop();
        if (err)
        {
          LOG_ERR("Stop LE scan failed (err %d)", err);
          return false;
        }

        struct bt_conn_le_create_param *create_param = BT_CONN_LE_CREATE_CONN;
        struct bt_le_conn_param *param = BT_LE_CONN_PARAM_DEFAULT;
      
        err = bt_conn_le_create(addr, create_param, param, &default_conn);
        if (err)
        {
          LOG_WRN("Connection failed (err %d)", err);
          start_scan();
        }
        err = bt_le_set_auto_conn(addr, param);
        if (err)
        {
          LOG_ERR("Error: bt_le_set_auto_conn() return %d", err);
          start_scan();
        }
        return false;
      }
      break;
    default:
      break;
    }
  return true;
}

static void on_device_found_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad)
{
  if (type == BT_GAP_ADV_TYPE_ADV_IND || type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND || type == BT_GAP_ADV_TYPE_EXT_ADV || type == BT_GAP_ADV_TYPE_SCAN_RSP)
  {
    if (rssi < -30)
      bt_data_parse(ad, parse_data_cb, (void *)addr);
  }
}

static void start_scan(void)
{
  int err;

  struct bt_le_scan_param scan_param = {
    .type = BT_LE_SCAN_TYPE_ACTIVE,
    .options = BT_LE_SCAN_OPT_NONE,
    .interval = BT_GAP_SCAN_FAST_INTERVAL,
    .window = BT_GAP_SCAN_FAST_WINDOW,
  };

  err = bt_le_scan_start(&scan_param, on_device_found_cb);
  if (err)
  {
    LOG_ERR("Scanning failed to start (err %d)", err);
    return;
  }

  LOG_INF("Scanning successfully started");
}

static enum bt_conn_state get_state(struct bt_conn *conn)
{
  struct bt_conn_info info;

  int err = bt_conn_get_info(conn, &info);
  if (err)
  {
    LOG_ERR("Error: bt_conn_get_info() return %d", err);
    return BT_CONN_STATE_DISCONNECTED;
  }
  return info.state;
}

static void thread_entrypoint_example(void *p1, void *p2, void *p3)
{
  struct bt_conn *conn = p1;

  while (1)
  {
    switch (get_state(conn))
    {
      case BT_CONN_STATE_CONNECTED:
        LOG_DBG("bla bla bla");
        break;
      default:
        LOG_INF("peer disconnected");
        return;
    }
    k_sleep(K_SECONDS(1));
  }
}

static void connected_cb(struct bt_conn *conn, uint8_t conn_err)
{
  const bt_addr_le_t * addr = bt_conn_get_dst(conn);

  if (conn_err)
  {
    LOG_ERR("Failed to connect to %02X:%02X:%02X:%02X:%02X:%02X (err %u)", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0], conn_err);
    bt_conn_unref(default_conn);
    default_conn = NULL;
    start_scan();
  }

  LOG_INF("Connected: %02X:%02X:%02X:%02X:%02X:%02X", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0]);

  if (conn == default_conn)
  {
    int err = bt_conn_set_security(conn, BT_SECURITY_L3);
    if (err)
      LOG_ERR("Error while pairing %d", err);

    th_id_example = k_thread_create(&th_data_example,
      th_stack_example,
      K_THREAD_STACK_SIZEOF(th_stack_example),
      thread_entrypoint_example,
      conn,
      NULL,
      NULL,
      K_PRIO_PREEMPT(1),
      0,
      K_NO_WAIT);
  }
}

static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
  LOG_INF("Beginning disconnected_cb");

  k_thread_abort(th_id_example);

  const bt_addr_le_t * addr = bt_conn_get_dst(conn);
  if (default_conn != conn)
    return;

  LOG_INF("Disconnected: %02X:%02X:%02X:%02X:%02X:%02X (reason 0x%02x)", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0], reason);
}

static void auth_passkey_display_cb(struct bt_conn *conn, unsigned int passkey)
{
  LOG_INF("Passkey is: %06u", passkey);
}

static void auth_passkey_confirm_cb(struct bt_conn *conn, unsigned int passkey)
{
  LOG_INF("Passkey confirmed");
  bt_conn_auth_passkey_confirm(conn);
}

static void le_param_updated_cb(struct bt_conn *conn, uint16_t interval, uint16_t latency, uint16_t timeout)
{
  LOG_INF("CONN PARAMS UPDATED interval %d, latency %d, timeout %d", interval, latency, timeout);
}

static bool le_param_req_cb(struct bt_conn *conn, struct bt_le_conn_param *param)
{
  LOG_INF("CONN PARAMS UPDATE REQUESTED interval_min %d, interval_max %d, latency %d, timeout %d", param->interval_min, param->interval_max, param->latency, param->timeout);

  return true;
}

static void auth_cancel_cb(struct bt_conn *conn)
{
  LOG_INF("Pairing cancelled");
}

static void le_phy_update_cb(struct bt_conn *conn, struct bt_conn_le_phy_info *param)
{
  switch (param->tx_phy)
  {
    case BT_CONN_LE_TX_POWER_PHY_1M:
    case BT_CONN_LE_TX_POWER_PHY_2M:
      LOG_INF("Phy updated to %dM", param->tx_phy);
      break;
    default:
      LOG_INF("Phy updated to CODED");
      break;
  }
}

static void le_data_len_updated(struct bt_conn *conn, struct bt_conn_le_data_len_info *info)
{
  uint16_t tx_len = info->tx_max_len;
  uint16_t tx_time = info->tx_max_time;
  uint16_t rx_len = info->rx_max_len;
  uint16_t rx_time = info->rx_max_time;
  LOG_INF("Data length updated. Length %d/%d bytes, time %d/%d us", tx_len, rx_len, tx_time, rx_time);
}

static void security_changed_cb(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
  if (err)
    LOG_ERR("Error on security changed. err %d", err);
  else
  {
    switch (level)
    {
      case BT_SECURITY_L0:
        LOG_INF("Security changed to level 0");
        break;
      case BT_SECURITY_L1:
        LOG_INF("Security changed to level 1");
        break;
      case BT_SECURITY_L2:
        LOG_INF("Security changed to level 2");
        break; 
      case BT_SECURITY_L3:
        LOG_INF("Security changed to level 3");
        break;
      case BT_SECURITY_L4:
        LOG_INF("Security changed to level 4");
        break;
      default:
        LOG_INF("Security changed to FORCE_PAIR");
        break;
    }
  }
}

static void pairing_complete(struct bt_conn *conn, bool bonded)
{
  if (!bonded)
    LOG_INF("Pairing failed");
  else
    LOG_INF("Pairing complete");
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
  .connected = connected_cb,
  .disconnected = disconnected_cb,
  .le_param_req = le_param_req_cb,
  .le_param_updated = le_param_updated_cb,
  .le_data_len_updated = le_data_len_updated,
  .le_phy_updated = le_phy_update_cb,
  .security_changed = security_changed_cb,
};

static struct bt_conn_auth_cb conn_auth_callbacks = {
  .passkey_display = auth_passkey_display_cb,
  .passkey_confirm = auth_passkey_confirm_cb,
  .cancel = auth_cancel_cb,
};

static struct bt_conn_auth_info_cb auth_info_cb = {
  .pairing_complete = pairing_complete
};

int main(void)
{
  LOG_INIT();

  int err = bt_conn_auth_cb_register(&conn_auth_callbacks);
  bt_conn_auth_info_cb_register(&auth_info_cb);

  err = bt_enable(NULL);
  if (err)
  {
    LOG_ERR("Bluetooth init failed (err %d)", err);
    return 1;
  }

  bt_set_bondable(true);
  LOG_INF("Bluetooth initialized");

  start_scan();

  return 0;
}

CONFIG_BT=y
CONFIG_BT_DEVICE_NAME="test"
CONFIG_BT_CENTRAL=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_CONSOLE=y
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_EVENTS=y
CONFIG_BT_SMP=y
CONFIG_BT_BONDABLE=y
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_MODE_DEFERRED=y
CONFIG_LOG_PRINTK=y
CONFIG_DEBUG=y
CONFIG_BT_USER_PHY_UPDATE=y
CONFIG_BT_AUTO_PHY_UPDATE=n
CONFIG_BT_USER_DATA_LEN_UPDATE=y
CONFIG_BT_AUTO_DATA_LEN_UPDATE=n
Parents
  • Hello,

    I must admit that I was not aware of this auto_conn feature, and thus, I don't know if it is intentional that the disconnected callback should disappear. However, it sounds odd that it should, since it is useful for the application to know whether you are currently connected or not. 

    Let me ask internally, and I will get back to you, hopefully this week, no later than the beginning of next week.

    Best regards,

    Edvin

Reply
  • Hello,

    I must admit that I was not aware of this auto_conn feature, and thus, I don't know if it is intentional that the disconnected callback should disappear. However, it sounds odd that it should, since it is useful for the application to know whether you are currently connected or not. 

    Let me ask internally, and I will get back to you, hopefully this week, no later than the beginning of next week.

    Best regards,

    Edvin

Children
  • Hi,

    I hope you are well.

    Have you made any progress in the understanding of my problem ?

    Kind regards,

    Victor

  • Hello Victor,

    I was working from home when I originally got your ticket, and I didn't have an DK at hand, so I asked someone in our softdevice team to check it, and I didn't hear from them. Sorry for not picking this up sooner (my fault, not theirs). Thank you for pinging back!

    So now, after testing your central application with the peripheral_uart sample as the peripheral, I saw some weird behavior, although not exactly the same as you. I saw the same error messages, but I didn't see the disconnects. However, when I removed the bt_le_set_auto_conn() I still saw some weird behavior, that it couldn't connect, because "a connection already existed in the disconnected state":

    [00:01:20.563,415] <inf> main: [DEVICE]: C6:10:0A:A5:6A:85      Device name: Nordic_UART_Service
    
    [00:01:20.564,300] <wrn> bt_conn: Found valid connection (0x20002040) with address C6:10:0A:A5:6A:85 (random) in disconnected state
    
    [00:01:20.564,300] <wrn> main: Connection failed (err -22)
    
    [00:01:20.565,185] <inf> main: Scanning successfully started
    

    So something similar to what you said about a missing disconnect event. 

    So in your disconnected callback, you should unreference the connection pointer:

    static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
    {
      LOG_INF("Beginning disconnected_cb");
    
      k_thread_abort(th_id_example);
    
      const bt_addr_le_t * addr = bt_conn_get_dst(conn);
      if (default_conn != conn)
        return;
    
      bt_conn_unref(conn);
    
      LOG_INF("Disconnected: %02X:%02X:%02X:%02X:%02X:%02X (reason 0x%02x)", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0], reason);
    }

    This made it work without the bt_le_set_auto_conn(). However, turning it back on, I still see the same error messages after the connected event (except for the first time). However, no disconnects. 

    Another thing I didn't understand is that I didn't see where you restarted your scanning after the disconnect. Apparently, setting the auto conn feature makes the device scan at all times, also when you are in a connection. This is something you need to be aware of, at least if your device is battery powered. 

    However, let me ping back on our internal softdevice team to see if they have any insight of why these error messages are present. As of the disconnect events every 30 seconds, I can't reproduce that, so if you want me to look into it, please provide a peripheral sample that can reproduce this, but I suspect this is the peripheral's decision, since it is not present during my tests.

    Best regards,

    Edvin

  • Hello Edvin,

    Thanks for this answer. Sorry for not writing back sooner.

    Sadly, I can't provide you a sample for that peripheral, it is a third-party peripheral and I don't have the source code myself.
    But I'm pretty sure that the disconnection every 30 seconds comes from the peripheral too. I suspect it because I do not have issue while using a phone as a peripheral (Asus Zenfone 9, Android 13, using nRF Connect for Mobile) but I do have issues with at least 3 third-party peripherals (mainly connected watches).

    I did know that without the bt_le_set_auto_conn, I need to unref the connection in the disconnected callback. In fact I did not restart the scanning phase because it's disabling the auto connect feature.

    Now I was trying to get rig of that bt_le_set_auto_conn feature and to implement it by hands:

    When receiving an advertisement, compare the address with the bonded addresses that my central has. If they are equal, the central will connect to it.

    I works perfectly with my phone, however when trying with the peripheral, I get an error on security changed (err 9) and the disconnection with 0x13 as a reason being BT_HCI_ERR_REMOTE_USER_TERM_CON, that means the disconnection is the peripheral's decision. The error 9 on security changed is BT_SECURITY_ERR_UNSPECIFIED.

    To achieve that, I added a second scan, device found and parse data (+ one little function to  be used by bt_foreach_bond):

    static struct tmp_bond {
      const bt_addr_le_t *addr;
      struct net_buf_simple *ad;
    };
    
    static void auto_start_rescan(void);
    
    static bool reparse_data_cb(struct bt_data *data, void *user_data)
    {
      bt_addr_le_t *addr = user_data;
      char addr_peer[BT_ADDR_LE_STR_LEN];
    
      switch (data->type)
      {
        case BT_DATA_NAME_SHORTENED:
        case BT_DATA_NAME_COMPLETE:
          bt_addr_le_to_str(addr, addr_peer, sizeof(addr_peer));
          ((uint8_t *)data->data)[data->data_len] = '\0';
          LOG_ERR("connect to %s", data->data);
    
          if (true)
          {
            int err = bt_le_scan_stop();
            if (err)
            {
              LOG_ERR("Stop LE scan failed (err %d)", err);
              return false;
            }
    
            struct bt_conn_le_create_param *create_param = BT_CONN_LE_CREATE_CONN;
            struct bt_le_conn_param *param = BT_LE_CONN_PARAM_DEFAULT;
    
            err = bt_conn_le_create(addr, create_param, param, &default_conn);
            if (err)
            {
              LOG_WRN("Connection failed (err %d)", err);
              auto_start_rescan();
            }
            return false;
          }
          break;
        default:
          return true;
      }
      return true;
    }
    
    static void parse_bond_cb(const struct bt_bond_info *info, void *user_data)
    {
      struct tmp_bond *temp = (struct tmp_bond *)user_data;
    
      if (bt_addr_le_eq(&(info->addr), temp->addr))
        bt_data_parse(temp->ad, reparse_data_cb, (void *)temp->addr);
    }
    
    static void on_device_refound_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad)
    {
      if (type == BT_GAP_ADV_TYPE_ADV_IND || type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND || type == BT_GAP_ADV_TYPE_EXT_ADV || type == BT_GAP_ADV_TYPE_SCAN_RSP)
      {
        if (rssi < 30)
        {
          struct tmp_bond temp;
          temp.addr = addr;
          temp.ad = ad;
          bt_foreach_bond(BT_ID_DEFAULT, parse_bond_cb, &temp);
        }
      }
    }
    
    static void auto_start_rescan(void)
    {
      int err;
    
      struct bt_le_scan_param scan_param = {
        .type       = BT_LE_SCAN_TYPE_ACTIVE,
        .options    = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
        .interval   = BT_GAP_SCAN_FAST_INTERVAL,
        .window     = BT_GAP_SCAN_FAST_WINDOW,
      };
    
      err = bt_le_scan_start(&scan_param, on_device_refound_cb);
      if (err) {
        LOG_ERR("Scanning failed to start (err %d)", err);
        return;
      }
    
      LOG_INF("Scanning successfully started");
    }

    I modified the disconnect callback to start this second scanning method:

    static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
    {
      LOG_DBG("Beginning disconnected_cb");
    
      k_thread_abort(th_id_example);
    
      const bt_addr_le_t * addr = bt_conn_get_dst(conn);
      if (default_conn != conn)
        return;
    
      bt_conn_unref(default_conn);
      default_conn = NULL;
    
      LOG_INF("Disconnected: %02X:%02X:%02X:%02X:%02X:%02X (reason 0x%02x)", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0], reason);
    
      auto_start_rescan();
    }

    The sample in total:

    #include <zephyr/types.h>
    #include <stddef.h>
    #include <errno.h>
    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/util.h>
    #include <zephyr/drivers/gpio.h>
    
    #include <zephyr/logging/log.h>
    #include <zephyr/logging/log_ctrl.h>
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/uuid.h>
    #include <zephyr/bluetooth/gatt.h>
    #include <zephyr/sys/byteorder.h>
    
    #define PERIPHERAL_TO_CONNECT_TO "MY_PERIPHERAL"
    
    LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
    
    static struct bt_conn *default_conn;
    
    #define THREAD_STACK_SIZE_EXAMPLE 1000
    static k_tid_t th_id_example;
    static struct k_thread th_data_example;
    static K_THREAD_STACK_DEFINE(th_stack_example, THREAD_STACK_SIZE_EXAMPLE);
    
    static void start_scan(void);
    
    static struct tmp_bond {
      const bt_addr_le_t *addr;
      struct net_buf_simple *ad;
    };
    
    static void auto_start_rescan(void);
    
    static bool reparse_data_cb(struct bt_data *data, void *user_data)
    {
      bt_addr_le_t *addr = user_data;
      char addr_peer[BT_ADDR_LE_STR_LEN];
    
      switch (data->type)
      {
        case BT_DATA_NAME_SHORTENED:
        case BT_DATA_NAME_COMPLETE:
          bt_addr_le_to_str(addr, addr_peer, sizeof(addr_peer));
          ((uint8_t *)data->data)[data->data_len] = '\0';
          LOG_ERR("connect to %s", data->data);
    
          if (true)
          {
            int err = bt_le_scan_stop();
            if (err)
            {
              LOG_ERR("Stop LE scan failed (err %d)", err);
              return false;
            }
    
            struct bt_conn_le_create_param *create_param = BT_CONN_LE_CREATE_CONN;
            struct bt_le_conn_param *param = BT_LE_CONN_PARAM_DEFAULT;
    
            err = bt_conn_le_create(addr, create_param, param, &default_conn);
            if (err)
            {
              LOG_WRN("Connection failed (err %d)", err);
              auto_start_rescan();
            }
            return false;
          }
          break;
        default:
          return true;
      }
      return true;
    }
    
    static void parse_bond_cb(const struct bt_bond_info *info, void *user_data)
    {
      struct tmp_bond *temp = (struct tmp_bond *)user_data;
    
      if (bt_addr_le_eq(&(info->addr), temp->addr))
        bt_data_parse(temp->ad, reparse_data_cb, (void *)temp->addr);
    }
    
    static void on_device_refound_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad)
    {
      if (type == BT_GAP_ADV_TYPE_ADV_IND || type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND || type == BT_GAP_ADV_TYPE_EXT_ADV || type == BT_GAP_ADV_TYPE_SCAN_RSP)
      {
        if (rssi < 30)
        {
          struct tmp_bond temp;
          temp.addr = addr;
          temp.ad = ad;
          bt_foreach_bond(BT_ID_DEFAULT, parse_bond_cb, &temp);
        }
      }
    }
    
    static void auto_start_rescan(void)
    {
      int err;
    
      struct bt_le_scan_param scan_param = {
        .type       = BT_LE_SCAN_TYPE_ACTIVE,
        .options    = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
        .interval   = BT_GAP_SCAN_FAST_INTERVAL,
        .window     = BT_GAP_SCAN_FAST_WINDOW,
      };
    
      err = bt_le_scan_start(&scan_param, on_device_refound_cb);
      if (err) {
        LOG_ERR("Scanning failed to start (err %d)", err);
        return;
      }
    
      LOG_INF("Scanning successfully started");
    }
    
    static bool parse_data_cb(struct bt_data *data, void *user_data)
    {
      bt_addr_le_t *addr = user_data;
    
      switch (data->type)
      {
        case BT_DATA_NAME_SHORTENED:
        case BT_DATA_NAME_COMPLETE:
          ((uint8_t *)data->data)[data->data_len] = '\0';
          LOG_INF("[DEVICE]: %02X:%02X:%02X:%02X:%02X:%02X\tDevice name: %s", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0], data->data);
          if (strncmp(data->data, PERIPHERAL_TO_CONNECT_TO, data->data_len) == 0)
          {
            int err = bt_le_scan_stop();
            if (err)
            {
              LOG_ERR("Stop LE scan failed (err %d)", err);
              return false;
            }
    
            struct bt_conn_le_create_param *create_param = BT_CONN_LE_CREATE_CONN;
            struct bt_le_conn_param *param = BT_LE_CONN_PARAM_DEFAULT;
    
            err = bt_conn_le_create(addr, create_param, param, &default_conn);
            if (err)
            {
              LOG_WRN("Connection failed (err %d)", err);
              start_scan();
            }
            return false;
          }
          break;
        default:
          break;
      }
      return true;
    }
    
    static void on_device_found_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad)
    {
      if (type == BT_GAP_ADV_TYPE_ADV_IND || type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND || type == BT_GAP_ADV_TYPE_EXT_ADV || type == BT_GAP_ADV_TYPE_SCAN_RSP)
      {
        if (rssi < -30)
        {
          bt_data_parse(ad, parse_data_cb, (void *)addr);
        }
      }
    }
    
    static void start_scan(void)
    {
      int err;
    
      struct bt_le_scan_param scan_param = {
        .type       = BT_LE_SCAN_TYPE_ACTIVE,
        .options    = BT_LE_SCAN_OPT_NONE,
        .interval   = BT_GAP_SCAN_FAST_INTERVAL,
        .window     = BT_GAP_SCAN_FAST_WINDOW,
      };
    
      err = bt_le_scan_start(&scan_param, on_device_found_cb);
      if (err) {
        LOG_ERR("Scanning failed to start (err %d)", err);
        return;
      }
    
      LOG_INF("Scanning successfully started");
    }
    
    static enum bt_conn_state get_state(struct bt_conn *conn)
    {
      struct bt_conn_info info;
    
      int err = bt_conn_get_info(conn, &info);
      if (err)
      {
        LOG_ERR("Error: bt_conn_get_info() return %d", err);
        return BT_CONN_STATE_DISCONNECTED;
      }
      return info.state;
    }
    
    static void thread_entrypoint_example(void *p1, void *p2, void *p3)
    {
      struct bt_conn *conn = p1;
    
      while (1)
      {
        switch (get_state(conn))
        {
          case BT_CONN_STATE_CONNECTED:
            LOG_INF("bla bla bla");
            break;
          default:
            LOG_INF("peer disconnected");
            return;
        }
        k_sleep(K_SECONDS(1));
      }
    }
    
    static void connected_cb(struct bt_conn *conn, uint8_t conn_err)
    {
      LOG_WRN("CONNECTED CALLBACK !!!");
      const bt_addr_le_t * addr = bt_conn_get_dst(conn);
    
      if (conn_err) {
        LOG_ERR("Failed to connect to %02X:%02X:%02X:%02X:%02X:%02X (err %u)", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0], conn_err);
    
        bt_conn_unref(default_conn);
        default_conn = NULL;
        start_scan();
      }
      LOG_INF("Connected: %02X:%02X:%02X:%02X:%02X:%02X", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0]);
    
      if (conn == default_conn)
      {
        switch (bt_conn_get_security(conn))
        {
          case BT_SECURITY_L0:
            LOG_WRN("Security level 0");
            break;
          case BT_SECURITY_L1:
            LOG_WRN("Security level 1");
            break;
          case BT_SECURITY_L2:
            LOG_WRN("Security level 2");
            break;
          case BT_SECURITY_L3:
            LOG_WRN("Security level 3");
            break;
          case BT_SECURITY_L4:
            LOG_WRN("Security level 4");
            break;
          default:
            LOG_WRN("Security FORCE_PAIR");
            break;
        }
    
        int err = bt_conn_set_security(conn, BT_SECURITY_L3);
        if (err)
          LOG_ERR("Error while pairing %d", err);
    
        th_id_example = k_thread_create(&th_data_example,
          th_stack_example,
          K_THREAD_STACK_SIZEOF(th_stack_example),
          thread_entrypoint_example,
          conn,
          NULL,
          NULL,
          K_PRIO_PREEMPT(1),
          0,
          K_NO_WAIT);
      }
    }
    
    static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
    {
      LOG_DBG("Beginning disconnected_cb");
    
      k_thread_abort(th_id_example);
    
      const bt_addr_le_t * addr = bt_conn_get_dst(conn);
      if (default_conn != conn)
        return;
    
      bt_conn_unref(default_conn);
      default_conn = NULL;
    
      LOG_INF("Disconnected: %02X:%02X:%02X:%02X:%02X:%02X (reason 0x%02x)", addr->a.val[5], addr->a.val[4], addr->a.val[3], addr->a.val[2], addr->a.val[1], addr->a.val[0], reason);
    
      auto_start_rescan();
    }
    
    static void auth_passkey_display_cb(struct bt_conn *conn, unsigned int passkey)
    {
      LOG_WRN("Passkey is: %06u", passkey);
    }
    
    static void auth_passkey_confirm_cb(struct bt_conn *conn, unsigned int passkey)
    {
      LOG_WRN("Passkey confirmed");
      bt_conn_auth_passkey_confirm(conn);
    }
    
    static void le_param_updated_cb(struct bt_conn *conn, uint16_t interval, uint16_t latency, uint16_t timeout)
    {
      LOG_WRN("CONN PARAMS UPDATED  interval %d, latency %d, timeout %d", interval, latency, timeout);
    }
    
    static bool le_param_req_cb(struct bt_conn *conn, struct bt_le_conn_param *param)
    {
      LOG_WRN("CONN PARAMS UPDATE REQUESTED  interval_min %d, interval_max %d, latency %d, timeout %d", param->interval_min, param->interval_max, param->latency, param->timeout);
    
      return true;
    }
    
    static void auth_cancel_cb(struct bt_conn *conn)
    {
      LOG_WRN("Pairing cancelled");
    }
    
    static void le_phy_update_cb(struct bt_conn *conn, struct bt_conn_le_phy_info *param)
    {
      switch (param->tx_phy)
      {
        case BT_CONN_LE_TX_POWER_PHY_1M:
        case BT_CONN_LE_TX_POWER_PHY_2M:
          LOG_WRN("Phy updated to %dM", param->tx_phy);
          break;
        default:
          LOG_WRN("Phy updated to CODED");
          break;
      }
    }
    
    static void le_data_len_updated(struct bt_conn *conn, struct bt_conn_le_data_len_info *info)
    {
      uint16_t tx_len     = info->tx_max_len;
      uint16_t rx_len     = info->rx_max_len;
      uint16_t tx_time    = info->tx_max_time;
      uint16_t rx_time    = info->rx_max_time;
      LOG_INF("Data length updated. Length %d/%d bytes, time %d/%d us", tx_len, rx_len, tx_time, rx_time);
    }
    
    static void security_changed_cb(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
    {
      if (err)
        LOG_ERR("Error on security changed. err %d", err);
      else
      {
        switch (level)
        {
          case BT_SECURITY_L0:
            LOG_INF("Security changed to level 0");
            break;
          case BT_SECURITY_L1:
            LOG_INF("Security changed to level 1");
            break;
          case BT_SECURITY_L2:
            LOG_INF("Security changed to level 2");
            break;
          case BT_SECURITY_L3:
            LOG_INF("Security changed to level 3");
            break;
          case BT_SECURITY_L4:
            LOG_INF("Security changed to level 4");
            break;
          default:
            LOG_INF("Security changed to FORCE_PAIR");
            break;
        }
      }
    }
    
    static void pairing_complete(struct bt_conn *conn, bool bonded)
    {
      if (!bonded)
        LOG_DBG("Pairing failed");
      else
        LOG_DBG("Pairing complete");
    }
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
        .connected = connected_cb,
        .disconnected = disconnected_cb,
        .le_param_req = le_param_req_cb,
        .le_param_updated = le_param_updated_cb,
        .le_data_len_updated = le_data_len_updated,
        .le_phy_updated = le_phy_update_cb,
        .security_changed = security_changed_cb,
    };
    
    static struct bt_conn_auth_cb conn_auth_callbacks = {
      .passkey_display = auth_passkey_display_cb,
      .passkey_confirm = auth_passkey_confirm_cb,
      .cancel = auth_cancel_cb,
    };
    
    static struct bt_conn_auth_info_cb auth_info_cb = {
      .pairing_complete = pairing_complete
    };
    
    int main(void)
    {
      LOG_INIT();
    
      int err = bt_conn_auth_cb_register(&conn_auth_callbacks);
      bt_conn_auth_info_cb_register(&auth_info_cb);
    
      // Init Bluetooth subsystem
      err = bt_enable(NULL);
      if (err)
      {
        LOG_ERR("Bluetooth init failed (err %d)", err);
        return  1;
      }
    
      bt_set_bondable(true);
      LOG_INF("Bluetooth initialized");
    
      start_scan();
    
      return 0;
    }
    

    The log

    [00:00:01.402,038] <inf> main: [DEVICE]: 64:72:49:26:EC:91      Device name: MY_PERIPHERAL
    [00:00:01.426,177] <wrn> main: CONNECTED CALLBACK !!!
    [00:00:01.426,177] <inf> main: Connected: 64:72:49:26:EC:91
    [00:00:01.426,208] <wrn> main: Security level 1
    [00:00:01.426,727] <inf> main: bla bla bla
    [00:00:01.528,137] <inf> main: Data length updated. Length 69/69 bytes, time 2120/2120 us
    [00:00:01.830,413] <wrn> main: Passkey confirmed
    [00:00:02.277,770] <inf> main: Security changed to level 4
    [00:00:02.378,631] <dbg> main: pairing_complete: Pairing complete
    [00:00:02.426,818] <inf> main: bla bla bla
    [00:00:03.427,001] <inf> main: bla bla bla
    [00:00:03.827,178] <wrn> main: Phy updated to 2M
    [00:00:04.427,215] <inf> main: bla bla bla
    [00:00:05.427,276] <inf> main: bla bla bla
    [00:00:05.627,593] <dbg> main: disconnected_cb: Beginning disconnected_cb
    [00:00:05.627,624] <inf> main: Disconnected: CA:D5:C8:E6:52:E9 (reason 0x13)
    [00:00:05.628,448] <inf> main: Scanning successfully started
    [00:00:08.415,985] <err> main: connect to MY_PERIPHERAL
    [00:00:08.464,172] <wrn> main: CONNECTED CALLBACK !!!
    [00:00:08.464,202] <inf> main: Connected: CA:D5:C8:E6:52:E9
    [00:00:08.464,202] <wrn> main: Security level 1
    [00:00:08.464,813] <inf> main: bla bla bla
    [00:00:08.665,557] <err> main: Error on security changed. err 9
    [00:00:08.665,679] <dbg> main: disconnected_cb: Beginning disconnected_cb
    [00:00:08.665,710] <inf> main: Disconnected: CA:D5:C8:E6:52:E9 (reason 0x13)
    [00:00:08.666,534] <inf> main: Scanning successfully started

  • It is difficult to say why 3rd party devices decide to disconnect without access to their application. Do you have any way to "forget device" on the 3rd party device, which would mean to delete the bonding information? Perhaps it has old bonding data that is no longer present on the nRF, which can cause it to disconnect. 

    I don't know, but they may also decide to use the error BT_SECURITY_ERR_UNSPECIFIED, for all I know.

    Best regards,

    Edvin

  • I deleted the bonding information after 3-4 tests in a row, it didn't change the result.

    Kind regards,

    Victor

Related