autoconnect issue after power reset in RPA

Dear Nordic,

using NRF54L15-DK I am advertising BLE with RPA, hence it changes mac on every reset and and at specified interval. Now when Phone connects first time, it asks passkey to enter and gets bonded/paired successfully.
Issue is when I reset Board, Its mac changes in advertisement causes phone autoconnect  and asks passkey. i Need once it is bonded, then it must not ask for passkey even after  Power reset or reset until bond is deleted(Right now not implemented and not required). 

SDK & Toolchain version:-3.3.1

please help
Reagrds

#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/sys/byteorder.h>
#include <hal/nrf_ficr.h>
#include <platform/CHIPDeviceLayer.h>
#include "lock_manager.h"
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/random/random.h>
#include <zephyr/settings/settings.h>

#define CUSTOM_LOCK_SVC_UUID_VAL \
    BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)

#define CUSTOM_LOCK_STATE_CHAR_VAL \
    BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1)

static struct bt_uuid_128 lock_service_uuid = BT_UUID_INIT_128(CUSTOM_LOCK_SVC_UUID_VAL);
static struct bt_uuid_128 lock_state_uuid = BT_UUID_INIT_128(CUSTOM_LOCK_STATE_CHAR_VAL);

// Variable to track BLE command state
static uint8_t ble_lock_state = 0;
unsigned int static_passkey = 123456; 


// Statically define the Custom Lock Service and its writeable characteristic
// Statically define the Custom Lock Service and its writeable characteristic
BT_GATT_SERVICE_DEFINE(custom_lock_svc,
    BT_GATT_PRIMARY_SERVICE(&lock_service_uuid),
    BT_GATT_CHARACTERISTIC(&lock_state_uuid.uuid,
                           BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP,
                           BT_GATT_PERM_WRITE_AUTHEN, // <-- Changed from BT_GATT_PERM_WRITE
                           NULL, write_lock_state, &ble_lock_state)
);
static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
    // Advertise our custom 128-bit Lock Service UUID so it's easy to find!
    BT_DATA_BYTES(BT_DATA_UUID128_ALL, CUSTOM_LOCK_SVC_UUID_VAL),
};
// 1. Callback matching the exact signature required by bt_foreach_bond
static void accept_list_bond_cb(const struct bt_bond_info *info, void *user_data)
{
    int err = bt_le_filter_accept_list_add(&info->addr);
    if (err) {
        printk("Failed to add bond to accept list (err %d)\n", err);
    } else {
        printk("Added bonded device from Flash to Accept List\n");
    }
}
// 2. Function to load ALL stored bonds into the Filter Accept List
void populate_accept_list_from_stored_bonds(void)
{
    // Clear any stale accept list entries first
    bt_le_filter_accept_list_clear();

    // Iterate through all saved bonds and invoke the callback
    bt_foreach_bond(BT_ID_DEFAULT, accept_list_bond_cb, NULL);
}


// 3. For NEW device pairing completion during runtime
static void pairing_complete(struct bt_conn *conn, bool bonded)
{
    printk("Pairing completed. Bonded: %d\n", bonded);

    if (bonded) {
        // Extract connection peer address and add directly
        const bt_addr_le_t *peer_addr = bt_conn_get_dst(conn);
        if (peer_addr) {
            int err = bt_le_filter_accept_list_add(peer_addr);
            if (err) {
                printk("Failed to add new pair to accept list (err %d)\n", err);
            } else {
                printk("Added new device to Accept List:%d\n",peer_addr->a.val[0]);
            }
        }
    }
}
static void pairing_failed(struct bt_conn *conn,enum bt_security_err reason)
{
    printk("Pairing failed (%d)\n", reason);
}

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

static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
    char addr[BT_ADDR_LE_STR_LEN];
    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

    if (!err) {
        printk("Security changed for %s: level %u (Encryption Successful!)\n", addr, level);
    } else {
        printk("Security failed for %s: level %u err %d\n", addr, level, err);
        
        /* IF security fails on a re-connection, the bond keys on mobile or lock 
           are out of sync. Unbond to allow a clean repair. */
        if (err == BT_SECURITY_ERR_PIN_OR_KEY_MISSING || err == 9) {
            printk("Key mismatch! Clearing stale bond...\n");
            bt_unpair(BT_ID_DEFAULT, bt_conn_get_dst(conn));
        }
    }
}


// Struct to pass context into the bond iterator
struct bond_check_ctx {
    const bt_addr_le_t *target_addr;
    bool is_bonded;
};

static void check_bond_cb(const struct bt_bond_info *info, void *user_data)
{
    struct bond_check_ctx *ctx = (struct bond_check_ctx *)user_data;

    // Compare destination address with stored bond identities
    if (bt_addr_le_cmp(&info->addr, ctx->target_addr) == 0) {
        ctx->is_bonded = true;
    }
}

// Check if the connecting address matches a known bond identity in RAM
static bool is_device_bonded(const bt_addr_le_t *addr)
{
    struct bond_check_ctx {
        const bt_addr_le_t *target;
        bool found;
    } ctx = { .target = addr, .found = false };

    auto check_cb = [](const struct bt_bond_info *info, void *user_data) {
        auto *c = (struct bond_check_ctx *)user_data;
        if (bt_addr_le_cmp(&info->addr, c->target) == 0) {
            c->found = true;
        }
    };

    bt_foreach_bond(BT_ID_DEFAULT, check_cb, &ctx);
    return ctx.found;
}
static void connected(struct bt_conn *conn, uint8_t err)
{
    if (err) {
        printk("Connection failed (err %u)\n", err);
        return;
    }

    const bt_addr_le_t *peer_addr = bt_conn_get_dst(conn);
    printk("New peer address =%d",peer_addr);
    if (is_device_bonded(peer_addr)) 
    {
        printk("Central is ALREADY BONDED! Elevating security to encrypt link...");
        
        // Elevate security so stack encrypts using existing LTK
        int sec_err = bt_conn_set_security(conn, BT_SECURITY_L1);
        if (sec_err) {
            printk("Failed to set security level (err %d)", sec_err);
        }
    } else {
        printk("Central is NEW / UNBONDED. Waiting for phone to initiate pairing...");
        /* DO NOT call bt_conn_set_security here! Let the mobile app initiate pairing */
    }
}
static struct bt_conn_cb conn_callbacks = {
    .connected = connected,
    //.disconnected = disconnected,
    .security_changed = security_changed,
};
// Helper callback to populate accept list with existing bonds

// Helper function to dynamically start the 500 ms custom advertising
static void start_custom_advertising(void)
{

    // Check if any bonded device exists
  bt_foreach_bond(BT_ID_DEFAULT, accept_list_bond_cb, NULL);

    // 2. Configure advertising set options to filter requests
    struct bt_le_adv_param adv_param = *BT_LE_ADV_PARAM(
        BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_FILTER_CONN,
        BT_GAP_ADV_FAST_INT_MIN_2,
        BT_GAP_ADV_FAST_INT_MAX_2,
        NULL
    );
    // // Define advertising parameters using our custom dynamic identity
    // struct bt_le_adv_param adv_param = {
    //     .id = BT_ID_DEFAULT,       // Use identity created from factory FICR MAC
    //     .sid = 0,
    //     .options = (BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_USE_IDENTITY ),
    //     .interval_min = 800,         // 500 ms (800 * 0.625 ms)
    //     .interval_max = 800,         // 500 ms (800 * 0.625 ms)
    //     .peer = NULL,
    // };
// Register this in your setup code:
    bt_conn_cb_register(&conn_callbacks);
    bt_conn_auth_info_cb_register(&auth_info_cb);
    if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
        settings_load(); // Restores keys from flash to RAM
    }
    populate_accept_list_from_stored_bonds();
    int err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), NULL, 0);
    if (err) {
        printk("Custom BLE advertising failed to start (err %d)\n", err);
    } else {
        printk("Custom BLE advertising started successfully at 500ms intervals!\n");
    }
}

// Helper function to extract factory MAC and set up Custom Identity
static void init_custom_ble_advertising(void)
{

    
    // bt_addr_le_t addr;

 	// uint8_t mac_addr[6];    
    // // Fetch the 6-byte factory MAC address
    // ssize_t ret = hwinfo_get_device_id(mac_addr, sizeof(mac_addr));
   
    // if (ret > 0) 
    // {
    //     printk("nRF54L15 BLE MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\n",mac_addr[5], mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]);
    // } 
    // else 
    // {
    //     printk("Failed to read MAC address from FICR.\n");
    // }

    // // Format as a valid Static Random address
	// memcpy(addr.a.val, mac_addr, 6);
	// addr.a.val[0] ^= 0xFF; // Flip the first byte so it doesn't match Matter's default identity
    // addr.type = BT_ADDR_LE_RANDOM;
    // addr.a.val[5] |= 0xC0;
    bt_addr_le_t addr;

// 1. Set address type to LE Random
addr.type = BT_ADDR_LE_RANDOM;

// 2. Fill address bytes with payload/hash
sys_rand_get(addr.a.val, sizeof(addr.a.val));

// 3. Force byte 5 bits to 0b01xxxxxx (0x40 flag)
BT_ADDR_SET_RPA(&addr.a);

// 4. Verify formatting
if (bt_addr_le_is_rpa(&addr)) {
    printk("Address formatted as a valid RPA structure.\n");
}
    // // Create identity in Bluetooth stack (BT stack must already be enabled)
    // int my_identity = bt_id_create(&addr, NULL);
    // if (my_identity < 0) {
    //     printk("Failed to create identity from FICR MAC (err %d). Reverting to default ID.\n", my_identity);
    //     my_adv_identity = BT_ADDR_LE_PUBLIC_ID;
    // } else {
    //     printk("Custom identity %d created using Chip FICR MAC.\n", my_identity);
    //     my_adv_identity = (uint8_t)my_identity;
    // }

    bt_passkey_set(static_passkey);
    printk("Static Passkey set to: %06u\n", static_passkey);
    // Start advertising our custom lock service
    start_custom_advertising();
}
Khodidas Domadiya

Parents
  • Hi Khodidas, 

    Could you check if you have configured CONFIG_BT_BONDABLE=y ? 
    Without that the device will only pair and not bond. Meaning each time you connect it will require a new bond. 

    When you use advertising with RPA, and you bonded to the peer device,  the peer device (phone) should be able to recognize the advertiser using the keys they exchanged when bond even the address changes. 

    I would suggest to go through our BLE course: https://academy.nordicsemi.com/courses/bluetooth-low-energy-fundamentals/lessons/lesson-5-bluetooth-le-security-fundamentals/

  • Dear Hung Bui
    yes i have used, it still facing issue. attached is prj.conf

    #
    # Copyright (c) 2022 Nordic Semiconductor ASA
    #
    # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
    #
    
    # This sample uses Kconfig.defaults to set options common for all
    # samples. This file should contain only options specific for this sample
    # or overrides of default values.
    
    # Enable CHIP
    CONFIG_CHIP=y
    CONFIG_CHIP_PROJECT_CONFIG="src/chip_project_config.h"
    CONFIG_CHIP_DEVICE_PRODUCT_NAME="Matter Door Lock"
    
    # Configure ZAP file name
    CONFIG_NCS_SAMPLE_MATTER_ZAP_FILE_PATH="${APPLICATION_CONFIG_DIR}/src/default_zap/lock.zap"
    
    # 32774 == 0x8006 (example lock-app)
    CONFIG_CHIP_DEVICE_PRODUCT_ID=32774
    CONFIG_STD_CPP17=y
    
    # Enable Matter extended announcement and increase duration to 1 hour.
    CONFIG_CHIP_BLE_EXT_ADVERTISING=y
    CONFIG_CHIP_BLE_ADVERTISING_DURATION=60
    
    # Add support for LEDs and buttons on Nordic development kits
    CONFIG_DK_LIBRARY=y
    
    # Bluetooth Low Energy configuration
    CONFIG_BT_DEVICE_NAME="MatterLock"
    
    # Suspend devices when the CPU goes into sleep
    CONFIG_PM_DEVICE=y
    
    # Other settings
    CONFIG_THREAD_NAME=y
    CONFIG_MPU_STACK_GUARD=y
    CONFIG_RESET_ON_FATAL_ERROR=n
    CONFIG_CHIP_LIB_SHELL=y
    CONFIG_NCS_SAMPLE_MATTER_TEST_SHELL=y
    
    # Reduce application size
    CONFIG_USE_SEGGER_RTT=n
    CONFIG_MATTER_LOG_LEVEL_INF=y
    CONFIG_CHIP_APP_LOG_LEVEL=3
    
    # Enable Factory Data feature
    CONFIG_CHIP_FACTORY_DATA=y
    CONFIG_CHIP_FACTORY_DATA_BUILD=y
    
    
    #=============================Mantra======================#
    # Enable multi-advertising / multiple identities if using concurrently with Matter
    CONFIG_BT_ID_MAX=2
    
    # Enable standard BLE peripheral and GATT features
    CONFIG_BT_PERIPHERAL=y
    #CONFIG_BT_DEVICE_NAME="Smart_lock1.1"
    CONFIG_BT_DEVICE_NAME="MySecureLock"
    
    # Optional: If you want to use the Nordic UART Service (NUS) for custom phone-to-lock features
    CONFIG_CHIP_NUS=y
    
    
    #========for Fix Passkey======#
    CONFIG_BT_SMP=y
    CONFIG_BT_SIGNING=y
    CONFIG_BT_BONDABLE=y
    CONFIG_BT_FIXED_PASSKEY=y
    CONFIG_BT_SETTINGS=y
    CONFIG_BT_CTLR_PRIVACY=y
    CONFIG_BT_CTLR_TX_PWR_MINUS_40=y
    CONFIG_BT_FILTER_ACCEPT_LIST=y
    # Allow overwriting/updating existing bond keys
    CONFIG_BT_KEYS_OVERWRITE_OLDEST=y
    CONFIG_BT_PRIVACY=y
    
    # Enable multi-connection support
    CONFIG_BT_MAX_CONN=3
    CONFIG_BT_MAX_PAIRED=3
    #CONFIG_BT_ID_MAX=3
    CONFIG_TEST_RANDOM_GENERATOR=y
    
    CONFIG_FLASH=y
    CONFIG_FLASH_PAGE_LAYOUT=y
    CONFIG_FLASH_MAP=y
    CONFIG_NVS=y
    CONFIG_SETTINGS=y
    
    CONFIG_PM=y
    #For Hw information like mac, uniq serialno. etc
    CONFIG_HWINFO=y
    
    CONFIG_BT_PRIVACY=n
    

  • Hi Khodidas,

    If it's Matter device then the behavior is expected. You are not supposed to have BLE bond retained with a central if you do Matter. It's because in Matter you only use the phone for provisioning and you shouldn't retain bond after that. 
    You can take a look at this ticket: BLE Bonding after power cycle 

    A solution was suggested in the ticket. 

Reply Children
No Data
Related