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.
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.
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=75
CONFIG_BT_WIFI_PROV=y
CONFIG_BT_BONDABLE=n
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_SMP=y
CONFIG_BT_BUF_ACL_RX_SIZE=151
CONFIG_BT_L2CAP_TX_MTU=147
CONFIG_BT_BUF_ACL_TX_SIZE=151
CONFIG_BT_RX_STACK_SIZE=5120
CONFIG_BT_DEVICE_NAME_DYNAMIC=y
Kconfig1.2 Enable the necessary NVS and flash configurations.
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_NVS=y
Kconfig1.3 Enable the necessary Wi-Fi credential configurations.
CONFIG_WIFI_CREDENTIALS=y
CONFIG_WIFI_CREDENTIALS_MAX_ENTRIES=1
Kconfig2. 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>
C3. 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};
C3.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)
C4. 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'};
C4.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)),
};
C4.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)),
};
C5. 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;
C5.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;
}
C5.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;
}
C6. 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;
C7. 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);
}
C7.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));
C8. 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);
C8.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);
C9. 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;
}
C10. 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);
C10.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");
C11. 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));
C12. Apply stored Wi-Fi credentials.
net_mgmt(NET_REQUEST_WIFI_CONNECT_STORED, iface, NULL, 0);
C13. 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.
Board | Build with TF-M | Extra CMake arguments |
---|---|---|
nRF7002 DK | nrf7002dk_nrf5340_cpuapp_ns | N/A |
nRF5340 DK + nRF7002 EK | nrf5340dk_nrf5340_cpuapp_ns | -DSHIELD=nrf7002ek |
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
*** Booting nRF Connect SDK 2.6.1-3758bcbfa5cd ***
[00:00:01.507,080] <inf> bt_hci_core: hci_vs_init: HW Platform: Nordic Semiconductor (0x0002)
[00:00:01.507,110] <inf> bt_hci_core: hci_vs_init: HW Variant: nRF53x (0x0003)
[00:00:01.507,141] <inf> bt_hci_core: hci_vs_init: Firmware: Standard Bluetooth controller (0x00) Version 197.47763 Build 2370639017
[00:00:01.509,063] <inf> bt_hci_core: bt_dev_show_info: Identity: E7:57:B0:79:D1:83 (random)
[00:00:01.509,094] <inf> bt_hci_core: bt_dev_show_info: HCI: version 5.4 (0x0d) revision 0x2102, manufacturer 0x0059
[00:00:01.509,124] <inf> bt_hci_core: bt_dev_show_info: LMP: version 5.4 (0x0d) subver 0x2102
[00:00:01.509,155] <inf> Lesson2_Exercise3: main: Bluetooth initialized.
[00:00:01.509,155] <inf> Lesson2_Exercise3: main: Wi-Fi provisioning service starts successfully.
[00:00:01.511,169] <inf> Lesson2_Exercise3: main: BT Advertising successfully started.
TerminalTesting
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
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.
[00:00:39.612,854] <inf> wifi_prov: prov_request_handler: Start parsing...
[00:00:39.612,854] <inf> wifi_prov: prov_set_config_handler: Set_config received...
[00:00:46.372,375] <inf> Lesson2_Exercise3: wifi_connect_handler: Connected to Wi-Fi Network
TerminalThe full log of the process should look like this
*** Booting nRF Connect SDK 2.6.1-3758bcbfa5cd ***
[00:00:01.507,080] <inf> bt_hci_core: hci_vs_init: HW Platform: Nordic Semiconductor (0x0002)
[00:00:01.507,110] <inf> bt_hci_core: hci_vs_init: HW Variant: nRF53x (0x0003)
[00:00:01.507,141] <inf> bt_hci_core: hci_vs_init: Firmware: Standard Bluetooth controller (0x00) Version 197.47763 Build 2370639017
[00:00:01.509,063] <inf> bt_hci_core: bt_dev_show_info: Identity: E7:57:B0:79:D1:83 (random)
[00:00:01.509,094] <inf> bt_hci_core: bt_dev_show_info: HCI: version 5.4 (0x0d) revision 0x2102, manufacturer 0x0059
[00:00:01.509,124] <inf> bt_hci_core: bt_dev_show_info: LMP: version 5.4 (0x0d) subver 0x2102
[00:00:01.509,155] <inf> Lesson2_Exercise3: main: Bluetooth initialized.
[00:00:01.509,155] <inf> Lesson2_Exercise3: main: Wi-Fi provisioning service starts successfully.
[00:00:01.511,169] <inf> Lesson2_Exercise3: main: BT Advertising successfully started.
[00:00:01.511,199] <inf> Lesson2_Exercise3: wifi_register_cb: Registering Wi-Fi events
[00:00:12.469,573] <inf> Lesson2_Exercise3: connected: BT Connected: 4F:22:C9:76:A6:18 (random)
[00:00:12.668,487] <wrn> bt_att: bt_att_recv: Unhandled ATT code 0x1d
[00:00:12.961,273] <inf> Lesson2_Exercise3: disconnected: BT Disconnected: 4F:22:C9:76:A6:18 (random) (reason 0x13).
[00:00:12.961,456] <inf> Lesson2_Exercise3: update_adv_data_task: Cannot update advertisement data, err = -11
[00:00:19.002,990] <inf> Lesson2_Exercise3: connected: BT Connected: 6E:B0:A2:28:DD:56 (random)
[00:00:19.214,813] <wrn> bt_att: bt_att_recv: Unhandled ATT code 0x1d
[00:00:21.993,743] <inf> Lesson2_Exercise3: pairing_complete: BT pairing completed: 6E:B0:A2:28:DD:56 (random), bonded: 0
[00:00:21.993,927] <inf> Lesson2_Exercise3: security_changed: BT Security changed: 6E:B0:A2:28:DD:56 (random) level 2.
[00:00:22.627,532] <inf> wifi_prov: control_point_ccc_cfg_changed: Wi-Fi Provisioning service - control point: indications enabled
[00:00:22.968,780] <inf> wifi_prov: data_out_ccc_cfg_changed: Wi-Fi Provisioning service - data out: notifications enabled
[00:00:23.456,329] <inf> wifi_prov: prov_request_handler: Start parsing...
[00:00:23.456,359] <inf> wifi_prov: prov_get_status_handler: GET_STATUS received...
OK
[00:00:24.644,958] <inf> wifi_prov: prov_request_handler: Start parsing...
[00:00:24.644,989] <inf> wifi_prov: prov_start_scan_handler: Start_Scan received...
[00:00:30.592,590] <inf> wifi_prov: prov_request_handler: Start parsing...
[00:00:30.592,620] <inf> wifi_prov: prov_stop_scan_handler: Stop_Scan received...
OK
OK
OK
OK
[00:00:39.612,854] <inf> wifi_prov: prov_request_handler: Start parsing...
[00:00:39.612,854] <inf> wifi_prov: prov_set_config_handler: Set_config received...
[00:00:46.372,375] <inf> Lesson2_Exercise3: wifi_connect_handler: Connected to Wi-Fi Network
[00:02:13.993,743] <inf> wifi_prov: control_point_ccc_cfg_changed: Wi-Fi Provisioning service - control point: indications disabled
[00:02:13.993,804] <inf> wifi_prov: data_out_ccc_cfg_changed: Wi-Fi Provisioning service - data out: notifications disabled
[00:02:13.993,957] <inf> Lesson2_Exercise3: disconnected: BT Disconnected: 6E:B0:A2:28:DD:56 (random) (reason 0x13).
Terminal