Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Click or drag files to this area to upload. You can upload up to 2 files.

Exercise 3

Provisioning the device over Bluetooth LE

In this exercise, we are going to add support for provisioning over Bluetooth LE to our application.

For this exercise, we very much recommend going though the Bluetooth Low Energy Fundamentals course, as we reuse a lot of the API’s that are covered in detail in that course.

Exercise steps

In the GitHub repository for this course, go to the base code for this exercise, found in lesson2/wififund_less2_exer3.

1. Enable and configure the necessary configurations.

1.1 Enable the necessary Bluetooth LE configurations.

1.2 Enable the necessary NVS and flash configurations.

1.3 Enable the necessary Wi-Fi credential configurations.

2. Include header files for Bluetooth LE.

Include the necessary Bluetooth LE header files in main.c, as well as the header file for the Wi-Fi Provisioning Service.

Add the following lines in main.c

#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 <bluetooth/services/wifi_provisioning.h>

3. Define a provisioning service variable prov_svc_data structure.

3.1 Define an array prov_svc_data for storing provisioning service data.

static uint8_t prov_svc_data[] = {BT_UUID_PROV_VAL, 0x00, 0x00, 0x00, 0x00};

3.2 Define indexes for the available bits to store information.

Define macros to easily be able to refer to the available bits of prov_svc_data.

#define ADV_DATA_VERSION_IDX          (BT_UUID_SIZE_128 + 0)
#define ADV_DATA_FLAG_IDX             (BT_UUID_SIZE_128 + 1)
#define ADV_DATA_FLAG_PROV_STATUS_BIT BIT(0)
#define ADV_DATA_FLAG_CONN_STATUS_BIT BIT(1)
#define ADV_DATA_RSSI_IDX             (BT_UUID_SIZE_128 + 3)

4. Define the structures for the advertisement data and scan response data.

4.1 Define a variable for the device name device_name.

static uint8_t device_name[] = {'P', 'V', '0', '0', '0', '0', '0', '0'};

4.2 Define the data structure bt_data ad[] for the advertisement packet.

Firstly, we set the advertisement flags to advertise in general discoverable mode (BT_LE_AD_GENERAL) and that classic Bluetooth (BR/EDR) is not supported (BT_LE_AD_NO_BRERD).

Secondly, we advertise the UUID of the Wi-Fi Provisioning Service, BT_UUID_PROV_VAL.

Lastly, we want to advertise the device name, which we defined earlier as the variable device_name.

Add the following lines to the main.c file

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_PROV_VAL),
	BT_DATA(BT_DATA_NAME_COMPLETE, device_name, sizeof(device_name)),
};

4.3 Define the structure bt_data sd[] for the scan response.

In the scan response, we want to include the provisioning service data stored in prov_svc_data.

static const struct bt_data sd[] = {
	BT_DATA(BT_DATA_SVC_DATA128, prov_svc_data, sizeof(prov_svc_data)),
};

5. Define the function update_wifi_status_in_adv() to update prov_svc_data.

5.1 Update prov_svc_data with the firmware version PROV_SVC_VER.

prov_svc_data[ADV_DATA_VERSION_IDX] = PROV_SVC_VER;

5.2 Use the function bt_wifi_prov_state_get() to get the provisioning state of the device, which returns true or false, depending on if the device is provisioned or not, and update prov_svc_data

if (!bt_wifi_prov_state_get()) {
	prov_svc_data[ADV_DATA_FLAG_IDX] &= ~ADV_DATA_FLAG_PROV_STATUS_BIT;
} else {
	prov_svc_data[ADV_DATA_FLAG_IDX] |= ADV_DATA_FLAG_PROV_STATUS_BIT;
}

5.3 Then request the status of the net interface. If Wi-Fi is not connected and/or an error occurs, mark the status as not connected. If not, mark it as connected.

struct net_if *iface = net_if_get_first_wifi();
struct wifi_iface_status status = { 0 };

int err = net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, iface, &status,
 sizeof(struct wifi_iface_status));
if ((err != 0) || (status.state < WIFI_STATE_ASSOCIATED)) {
	prov_svc_data[ADV_DATA_FLAG_IDX] &= ~ADV_DATA_FLAG_CONN_STATUS_BIT;
	prov_svc_data[ADV_DATA_RSSI_IDX] = INT8_MIN;
} else { /* WiFi is connected. */
	prov_svc_data[ADV_DATA_FLAG_IDX] |= ADV_DATA_FLAG_CONN_STATUS_BIT;
	prov_svc_data[ADV_DATA_RSSI_IDX] = status.rssi;
}

6. Define the work structures for updating advertisement parameters and data.

Declare the work structures k_work_delayable for updating the advertisement parameters (update_adv_param_work) and updating the advertisement data (update_adv_data_work).

static struct k_work_delayable update_adv_param_work;
static struct k_work_delayable update_adv_data_work;

7. Define the task functions for the work structures.

7.1 Define update_adv_param_task().

In the case where the advertisement data and/or scan response data changes, we need a function that will stop advertising, then starts advertising again with the updated parameters.

Add the following code snippet in main.c

int err;

err = bt_le_adv_stop();
if (err != 0) {
	LOG_ERR("Cannot stop advertisement: err = %d\n", err);
	return;
}

err = bt_le_adv_start(prov_svc_data[ADV_DATA_FLAG_IDX] & ADV_DATA_FLAG_PROV_STATUS_BIT ?
	PROV_BT_LE_ADV_PARAM_SLOW : PROV_BT_LE_ADV_PARAM_FAST,
	ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err != 0) {
	LOG_ERR("Cannot start advertisement: err = %d\n", err);
}

7.2 Define update_adv_data_task()

Call the function update_wifi_status_in_adv() to update the status through prov_svc_data. Then, update the advertisement and scan response data using bt_le_adv_update_data().

Then reschedule update_adv_data_work.

Add the following code snippet in main.c

int err;

update_wifi_status_in_adv();
err = bt_le_adv_update_data(ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err != 0) {
	LOG_INF("Cannot update advertisement data, err = %d\n", err);
}
k_work_reschedule_for_queue(&adv_daemon_work_q, &update_adv_data_work,
			K_SECONDS(ADV_DATA_UPDATE_INTERVAL));

8. Alter the callback functions for the connected and disconnected connection events.

This is done because when a Bluetooth connection is established, the device will stop advertising and we no longer need to update the advertisement data. However, after a disconnect event, we want the device to start advertising again with updated advertisement data showing the current Wi-Fi status.

8.1 Upon a connected event, we want to cancel update_adv_data_work.

Add the following code snippet to the connected() function in main.c

k_work_cancel_delayable(&update_adv_data_work);

8.2 Upon a disconnected event, reschedule both work items update_adv_param_work and update_adv_data_work.

Add the following code snippet to the disconnected() function in main.c

k_work_reschedule_for_queue(&adv_daemon_work_q, &update_adv_param_work,
			K_SECONDS(ADV_PARAM_UPDATE_DELAY));
k_work_reschedule_for_queue(&adv_daemon_work_q, &update_adv_data_work, K_NO_WAIT);

9. Enable the Bluetooth Wi-Fi Provisioning Service.

After enabling Bluetooth LE, we initialize the provisioning module using bt_wifi_prov_init().

Add the following code snippet in main()

err = bt_wifi_prov_init();
if (err == 0) {
	LOG_INF("Wi-Fi provisioning service starts successfully.\n");
} else {
	LOG_ERR("Error occurs when initializing Wi-Fi provisioning service.\n");
	return 0;
}

10. Start advertising.

10.1 Prepare the advertisement data.

struct net_if *iface = net_if_get_default();
struct net_linkaddr *mac_addr = net_if_get_link_addr(iface);
char device_name_str[sizeof(device_name) + 1];

if (mac_addr) {
	update_dev_name(mac_addr);
}
device_name_str[sizeof(device_name_str) - 1] = '\0';
memcpy(device_name_str, device_name, sizeof(device_name));
bt_set_name(device_name_str);

10.2 Start advertising using bt_le_adv_start().

update_wifi_status_in_adv();

err = bt_le_adv_start(prov_svc_data[ADV_DATA_FLAG_IDX] & ADV_DATA_FLAG_PROV_STATUS_BIT ?
	PROV_BT_LE_ADV_PARAM_SLOW : PROV_BT_LE_ADV_PARAM_FAST,
	ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err) {
	LOG_ERR("BT Advertising failed to start (err %d)\n", err);
	return 0;
}
LOG_INF("BT Advertising successfully started.\n");

11. Initialize all work items to their respective task functions

Initialize the work items update_adv_param_work and update_adv_data_work to their respective task functions and schedule one of them for queue.

k_work_init_delayable(&update_adv_param_work, update_adv_param_task);
k_work_init_delayable(&update_adv_data_work, update_adv_data_task);
k_work_schedule_for_queue(&adv_daemon_work_q, &update_adv_data_work,
		K_SECONDS(ADV_DATA_UPDATE_INTERVAL));

12. Apply stored Wi-Fi credentials.

net_mgmt(NET_REQUEST_WIFI_CONNECT_STORED, iface, NULL, 0);

13. Build and flash the application to your board.

This exercise uses the PSA backend for storing the Wi-Fi credentials. Therefore, you must build with TF-M.

BoardBuild with TF-M
nRF7002 DKnrf7002dk_nrf5340_cpuapp_ns
nRF5340 DK + nRF7002 EKnrf5340dk_nrf5340_cpuapp_ns

Turn on the device and wait for it to start up. You should see the following log output when the device is ready to be provisioned

Testing

Android

14.1 Open the nRF Wi-Fi Provisioner app and click Start.

14.2 Click on the device that appears and select Pair in the pop-up.

14.3 Click on Start provisioning.

14.4. Select the network to which you want to provision the device and enter the network password.

14.5. Click on Provision to provision the device.

Wait a few seconds. When the device is provisioned, you should see the following in the app

iOS

14.1 Open the nRF Wi-Fi Provisioner app and wait for your device to show up.

14.2 Click on the device that appears and select Pair in the pop-up.

14.3 Click on Access Point and the app will scan and display all Wi-Fi networks in your vicinity.

14.4 Select the Wi-Fi network you want your board to connect to, then select the channel you want to use (this generally does not matter).

14.5 Under Access Point, enter the password for your Wi-Fi network, then click Set Configuration.

You have successfully provisioned your Wi-Fi device to the network.

Upon successful provisioning, you should be able to see a similar view to the picture shown below.

You should see the following log output when the device has been provisioned.

The full log of the process should look like this

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.