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.

Parents
  • 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

  •  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.

  • Whenever there is a disconnection in the peripheral snd never prompted by me. I guess that online happens on connection errors?

  • I don't see any issue with adding the advertising start work from the disconnected callback, if that's what you're asking.

  • Actually, it's racy. As I suspected the advertising work sometimes happens before the connection is deallocated.

    Using the `recycled` callback (which I just found about) is much nicer.

Reply Children
No Data
Related