Restarting High Duty Cycle Direct Advertising

I would like for the high duty cycle direct advertising to restart immediately after the ~1.2s timeout.

[I'm using Zephyr. I'm also really new to programming these devices]

I've tried to modify the direct_adv example (to use high duty cycle & restart after timeout).

Below are the parts of my code that differ from the original example:

static void connected(struct bt_conn *conn, uint8_t err)
{
	if (err) {
		printk("Connection failed (err 0x%02x)\n", err);
		printk("Stopping advertising...?\n");
		int err_stop;
		err_stop = bt_le_adv_stop();
		printk("Error code: %d\n",err_stop);
		int err_dis;
		err_dis = bt_conn_disconnect(conn,BT_HCI_ERR_REMOTE_USER_TERM_CONN);
		printk("Attempted to disconnect/end connection (%d)\n", err_dis);
		printk("Attempting to unreference connection...\n");
		bt_conn_unref(conn); //try to fix 'bt_conn_exists_le: Found valid connection in disconnected state' error		
	} else {
		printk("Connected\n");
	}
}

static void bt_ready(void)
{
	int err;
	char addr[BT_ADDR_LE_STR_LEN];

	printk("Bluetooth initialized\n");
	printk("Wait two seconds...\n");
	k_sleep(K_SECONDS(2));
	
	printk("Load settings\n");
	if (IS_ENABLED(CONFIG_SETTINGS)) {
		settings_load();
	}
	bt_addr_le_copy(&bond_addr, BT_ADDR_LE_NONE);
	bt_foreach_bond(BT_ID_DEFAULT, copy_last_bonded_addr, NULL);

	/* Address is equal to BT_ADDR_LE_NONE if compare returns 0.
	 * This means there is no bond yet.
	 */
	if (bt_addr_le_cmp(&bond_addr, BT_ADDR_LE_NONE) != 0) {
		bt_addr_le_to_str(&bond_addr, addr, sizeof(addr));
		printk("Direct advertising to %s\n", addr);
		//Previously used low-duty:
		//adv_param = *BT_LE_ADV_CONN_DIR_LOW_DUTY(&bond_addr);
		//now using: (this will be high duty)
		adv_param = *BT_LE_ADV_CONN_DIR(&bond_addr);
		adv_param.options |= BT_LE_ADV_OPT_DIR_ADDR_RPA;
		err = bt_le_adv_start(&adv_param, NULL, 0, NULL, 0);
	}
	//[removed unmodified code for brevity]
}

The bt_ready method is called every five seconds from main():

    bt_ready();
	bt_conn_auth_info_cb_register(&bt_conn_auth_info);
	while (1) {
		
		k_sleep(K_SECONDS(5));
		bt_ready();
	}

The log of the error:

*** Booting Zephyr OS build v3.2.99-ncs1 ***
Bluetooth initialized
Wait two seconds...
[00:00:00.010,498] <inf> fs_nvs: nvs_mount: 6 Sectors of 4096 bytes
[00:00:00.010,528] <inf> fs_nvs: nvs_mount: alloc wra: 0, fb0
[00:00:00.010,528] <inf> fs_nvs: nvs_mount: data wra: 0, c4
[00:00:00.010,650] <inf> sdc_hci_driver: hci_driver_open: SoftDevice Controller build revision:
                                         6d 90 41 2a 38 e8 ad 17  29 a5 03 38 39 27 d7 85 |m.A*8... )..89'..
                                         1f 85 d8 e1                                      |....             
[00:00:00.013,336] <inf> bt_hci_core: hci_vs_init: HW Platform: Nordic Semiconductor (0x0002)
[00:00:00.013,366] <inf> bt_hci_core: hci_vs_init: HW Variant: nRF52x (0x0002)
[00:00:00.013,397] <inf> bt_hci_core: hci_vs_init: Firmware: Standard Bluetooth controller (0x00) Version 109.16784 Build 2917677098
[00:00:00.013,793] <inf> bt_hci_core: bt_init: No ID address. App must call settings_load()
Load settings
Direct advertising to C0:DC:DA:1F:C4:81 (public)
Advertising successfully started
[00:00:02.019,561] <inf> bt_hci_core: bt_dev_show_info: Identity: F8:9D:09:B3:40:09 (random)
[00:00:02.019,592] <inf> bt_hci_core: bt_dev_show_info: HCI: version 5.3 (0x0c) revision 0x11fa, manufacturer 0x0059
[00:00:02.019,622] <inf> bt_hci_core: bt_dev_show_info: LMP: version 5.3 (0x0c) subver 0x11fa
Connection failed (err 0x3c)
Stopping advertising...?
Error code: 0
Attempted to disconnect/end connection (-128)
Attempting to unreference connection...
Bluetooth initialized
Wait two seconds...
Load settings
Direct advertising to C0:DC:DA:1F:C4:81 (public)
Advertising failed to start (err -22)
[00:00:09.033,905] <wrn> bt_hci_core: bt_hci_cmd_send_sync: opcode 0x2027 status 0x12
[00:00:09.033,935] <err> bt_id: bt_id_add: Failed to add IRK to controller
[00:00:09.038,665] <wrn> bt_conn: bt_conn_exists_le: Found valid connection in disconnected state

The 'Connection failed (err 0x3c)' error is fine (this is BT_HCI_ERR_ADV_TIMEOUT: High duty cycle directed connectable advertiser started by bt_le_adv_start failed to be connected within the timeout). We expect that to happen almost straight away.

I want to then start the advertising again. I originally tried calling bt_ready from inside the connected() callback where the above error is reported. I now try from a loop inside main().

The problem is:

Advertising failed to start (err -22)
[00:00:09.033,905] <wrn> bt_hci_core: bt_hci_cmd_send_sync: opcode 0x2027 status 0x12
[00:00:09.033,935] <err> bt_id: bt_id_add: Failed to add IRK to controller
[00:00:09.038,665] <wrn> bt_conn: bt_conn_exists_le: Found valid connection in disconnected state

I clearly need to somehow either (a) remove the valid connection so it can restart, or (b) I need to figure out how to restart advertising on a valid connection?

I imagine this is quite straightforward!

[edit: here's a link to my full version of main.c]

Note on the application

I'm using BLE for a rather non-typical application: I'm doing insect tracking, and have a series of high-gain transmitters (at a remote field-site), each rotating, sending their angle, id and time in advertising packets. A BLE receiver on the bee detects these (and their signal strength) and can use this information to triangulate its location (the signal strength peaks when the antennas are pointing at it, for example). This info is stored and read off later, once the bee returns to the hive. To save energy, we need to allow the receiver (on the bee) to be disabled most of the time, so I want to use a much higher duty-cycle of packets from the transmitters (e.g. if the receiver is only on for 6ms, then I need the transmitters to send e.g. every 3ms). I've over simplified the approach, but I think that gives the idea.

  • Hello,

    The program needs to exit the connection callback before attempting to re-start the advertisement. I think the easiest way to do this is to create a workqueue item to start advertising, then offload this task to the workqueue thread from the connected callback. 

    Define workqueue item

    static void advertising_start(struct k_work *work);
    static K_WORK_DEFINE(start_advertising_worker, advertising_start);

    advertising_start() implementation

    static void advertising_start(struct k_work *work)
    {
    	int err;
    	char addr[BT_ADDR_LE_STR_LEN];
    	
    	bt_addr_le_copy(&bond_addr, BT_ADDR_LE_NONE);
    	bt_foreach_bond(BT_ID_DEFAULT, copy_last_bonded_addr, NULL);
    
    	/* Address is equal to BT_ADDR_LE_NONE if compare returns 0.
    	 * This means there is no bond yet.
    	 */
    	if (bt_addr_le_cmp(&bond_addr, BT_ADDR_LE_NONE) != 0) {
    		bt_addr_le_to_str(&bond_addr, addr, sizeof(addr));
    		printk("Direct advertising to %s\n", addr);
    
    		//adv_param = *BT_LE_ADV_CONN_DIR_LOW_DUTY(&bond_addr);
    		adv_param = *BT_LE_ADV_CONN_DIR(&bond_addr);
    		adv_param.options |= BT_LE_ADV_OPT_DIR_ADDR_RPA;
    		err = bt_le_adv_start(&adv_param, NULL, 0, NULL, 0);
    	} else {
    		err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
    	}
    
    	if (err) {
    		printk("Advertising failed to start (err %d)\n", err);
    	} else {
    		printk("Advertising successfully started\n");
    	}	
    }

    Submit "advertising start task" to the workqueue from the connected callback

    static void connected(struct bt_conn *conn, uint8_t err)
    {
    	if (err == BT_HCI_ERR_ADV_TIMEOUT) {
    		printk("Trying to re-start directed adv.\n");
    		k_work_submit(&start_advertising_worker);
    	}
    	else if (err) {
    		printk("Connection failed (err 0x%02x)\n", err);
    	} else {
    		printk("Connected\n");
    	}
    }

    I tried this with the direct_adv example, and it seemed to work as expected. Here's the full main.c with at the changes I made:

    /*
     * Copyright (c) 2022 Michal Morsisko
     * Copyright (c) 2015-2016 Intel Corporation
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr/types.h>
    #include <stddef.h>
    #include <string.h>
    #include <errno.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/byteorder.h>
    #include <zephyr/sys/reboot.h>
    #include <zephyr/settings/settings.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>
    
    
    
    /* Custom Service Variables */
    #define BT_UUID_CUSTOM_SERVICE_VAL \
    	BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)
    
    static struct bt_uuid_128 primary_service_uuid = BT_UUID_INIT_128(
    	BT_UUID_CUSTOM_SERVICE_VAL);
    
    static struct bt_uuid_128 read_characteristic_uuid = BT_UUID_INIT_128(
    	BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1));
    
    static struct bt_uuid_128 write_characteristic_uuid = BT_UUID_INIT_128(
    	BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef2));
    
    static int signed_value;
    static struct bt_le_adv_param adv_param;
    static bt_addr_le_t bond_addr;
    
    
    static void advertising_start(struct k_work *work);
    
    static K_WORK_DEFINE(start_advertising_worker, advertising_start);
    
    static ssize_t read_signed(struct bt_conn *conn, const struct bt_gatt_attr *attr,
    			   void *buf, uint16_t len, uint16_t offset)
    {
    	int *value = &signed_value;
    
    	return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
    				 sizeof(signed_value));
    }
    
    static ssize_t write_signed(struct bt_conn *conn, const struct bt_gatt_attr *attr,
    			    const void *buf, uint16_t len, uint16_t offset,
    			    uint8_t flags)
    {
    	int *value = &signed_value;
    
    	if (offset + len > sizeof(signed_value)) {
    		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
    	}
    
    	memcpy(value + offset, buf, len);
    
    	return len;
    }
    
    /* Vendor Primary Service Declaration */
    BT_GATT_SERVICE_DEFINE(primary_service,
    	BT_GATT_PRIMARY_SERVICE(&primary_service_uuid),
    	BT_GATT_CHARACTERISTIC(&read_characteristic_uuid.uuid,
    			       BT_GATT_CHRC_READ,
    			       BT_GATT_PERM_READ,
    			       read_signed, NULL, NULL),
    	BT_GATT_CHARACTERISTIC(&write_characteristic_uuid.uuid,
    			       BT_GATT_CHRC_WRITE,
    			       BT_GATT_PERM_WRITE_ENCRYPT,
    			       NULL, write_signed, NULL),
    );
    
    static const struct bt_data ad[] = {
    	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    	BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_CUSTOM_SERVICE_VAL),
    };
    
    static void connected(struct bt_conn *conn, uint8_t err)
    {
    	if (err == BT_HCI_ERR_ADV_TIMEOUT) {
    		printk("Trying to re-start directed adv.\n");
    		k_work_submit(&start_advertising_worker);
    	}
    	else if (err) {
    		printk("Connection failed (err 0x%02x)\n", err);
    	} else {
    		printk("Connected\n");
    	}
    }
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	printk("Disconnected (reason 0x%02x)\n", reason);
    }
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected = connected,
    	.disconnected = disconnected
    };
    
    static void copy_last_bonded_addr(const struct bt_bond_info *info, void *data)
    {
    	bt_addr_le_copy(&bond_addr, &info->addr);
    }
    
    static void advertising_start(struct k_work *work)
    {
    	int err;
    	char addr[BT_ADDR_LE_STR_LEN];
    	
    	bt_addr_le_copy(&bond_addr, BT_ADDR_LE_NONE);
    	bt_foreach_bond(BT_ID_DEFAULT, copy_last_bonded_addr, NULL);
    
    	/* Address is equal to BT_ADDR_LE_NONE if compare returns 0.
    	 * This means there is no bond yet.
    	 */
    	if (bt_addr_le_cmp(&bond_addr, BT_ADDR_LE_NONE) != 0) {
    		bt_addr_le_to_str(&bond_addr, addr, sizeof(addr));
    		printk("Direct advertising to %s\n", addr);
    
    		//adv_param = *BT_LE_ADV_CONN_DIR_LOW_DUTY(&bond_addr);
    		adv_param = *BT_LE_ADV_CONN_DIR(&bond_addr);
    		adv_param.options |= BT_LE_ADV_OPT_DIR_ADDR_RPA;
    		err = bt_le_adv_start(&adv_param, NULL, 0, NULL, 0);
    	} else {
    		err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
    	}
    
    	if (err) {
    		printk("Advertising failed to start (err %d)\n", err);
    	} else {
    		printk("Advertising successfully started\n");
    	}	
    }
    
    static void bt_ready(void)
    {
    
    	printk("Bluetooth initialized\n");
    
    	if (IS_ENABLED(CONFIG_SETTINGS)) {
    		settings_load();
    	}
    }
    
    void pairing_complete(struct bt_conn *conn, bool bonded)
    {
    	printk("Pairing completed. Rebooting in 5 seconds...\n");
    
    	k_sleep(K_SECONDS(5));
    	sys_reboot(SYS_REBOOT_WARM);
    }
    
    static struct bt_conn_auth_info_cb bt_conn_auth_info = {
    	.pairing_complete = pairing_complete
    };
    
    int main(void)
    {
    	int err;
    
    	err = bt_enable(NULL);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return 0;
    	}
    
    	bt_ready();
    	bt_conn_auth_info_cb_register(&bt_conn_auth_info);
    
    	k_work_submit(&start_advertising_worker);
    
    	while (1) {
    		k_sleep(K_FOREVER);
    	}
    	return 0;
    }
    

    In my code, I didn't implement this, but you would probably want to limit the number of times you restart the advertisement to prevent battery drainage. Additionally, as you might be aware, the high-duty directed advertisement should only run for 1.28 seconds according to the spec.

    Best regards,

    Vidar

  • Thank you for writing all that code for me. It's really appreciated. That appears to be working I guess... but I can't actually tell. I've tried bonding with it via the Nordic nRF connect app, but never see high-duty packets? I also tried looking for them via a separate system running the DA14531 SoC, but I couldn't see them there either (I also tried manually setting the target address equal to my phone and the DA14531's address).

    Is there a way of detecting these packets? I assume I can't see them as I've some how messed up the address or something, and direct adv packets that aren't addressed to a device get ignored?

    Thanks again for your help.

    E.g. Modifying the target address...

    err = bt_addr_le_from_str("C0:56:78:9A:BC:DE", "random", &bond_addr);
    if (err) {
    printk("Setting address failed (err %d)\n", err);
    }
  • I'm Happy to help! I verified that it worked on my end by using the nRF connect app on Android with 'autoconnect' enabled (Note: it won't work on iOS as it doesn't support directed advertisement). With 'autoconnect' enabled, the phone should try to re-connect when it receives directed advertisement packets from the device after the connection has been lost. 

    Here are the steps I used to test this:

    1. Connect & bond with the device from the nRF connect

    2. Enable autoconnect by pressing the 3 dotted button in the top right corner and select "autoconnect"

    3. Reset the peripheral to force a disconnect

    4. Observe from the app that the connection state transitions from 'connected' to 'disconnected' and then back to 'connected'.

    Debug log showing reconnect on the first advertisment after reset

    *** Booting Zephyr OS build 28a3fca7da5c ***
    [00:00:00.006,561] <inf> fs_nvs: nvs_mount: 6 Sectors of 4096 bytes
    [00:00:00.006,561] <inf> fs_nvs: nvs_mount: alloc wra: 0, fb8
    [00:00:00.006,591] <inf> fs_nvs: nvs_mount: data wra: 0, b4
    [00:00:00.006,713] <inf> bt_sdc_hci_driver: hci_driver_open: SoftDevice Controller build revision: 
                                                e0 7e 2e c1 5e 05 85 23  46 15 dc fa 8e 29 7d 70 |.~..^..# F....)}p
                                                10 93 a5 fc                                      |....             
    [00:00:00.009,521] <inf> bt_hci_core: hci_vs_init: HW Platform: Nordic Semiconductor (0x0002)
    [00:00:00.009,552] <inf> bt_hci_core: hci_vs_init: HW Variant: nRF52x (0x0002)
    [00:00:00.009,582] <inf> bt_hci_core: hci_vs_init: Firmware: Standard Bluetooth controller (0x00) Version 224.11902 Build 2231721665
    [00:00:00.009,979] <inf> bt_hci_core: bt_init: No ID address. App must call settings_load()
    Bluetooth initialized
    [00:00:00.010,803] <inf> bt_hci_core: bt_dev_show_info: Identity: D8:2A:C8:17:11:5C (random)
    [00:00:00.010,833] <inf> bt_hci_core: bt_dev_show_info: HCI: version 5.4 (0x0d) revision 0x1077, manufacturer 0x0059
    [00:00:00.010,894] <inf> bt_hci_core: bt_dev_show_info: LMP: version 5.4 (0x0d) subver 0x1077
    Direct advertising to 6C:DD:BC:29:D8:8E (public)
    Advertising successfully started
    Connected

    I should also add that directed advertisements will not be "Scanner" view when scanning for new devices. 

  •  I run into the same problem and your work queue solution works great! Thanks!

    I have a question though. Are we sure that the advertising work will never run concurrently with the connection finalization? Wouldn't this be a problem?

  • Are you attempting to start your directed advertiser when there is a connection error? In that case, the workqueue item should be processed after the connection object has been released.

Related