Chapter 288: Device Provisioning Methods
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the concept of device provisioning and its importance in the product lifecycle.
- Differentiate between various provisioning strategies, including factory flashing, SoftAP, and BLE-based methods.
- Implement a secure, user-friendly Wi-Fi provisioning flow using ESP-IDF’s unified provisioning manager.
- Use a companion mobile application to securely send credentials to an ESP32 device.
- Handle provisioning events within your application to know when a device has been successfully configured.
- Design a basic provisioning strategy for a real-world IoT product.
Introduction
In the last chapter, we prepared a single, production-ready firmware binary. This is a critical step, but it raises a new question: how does each individual device get the unique information it needs to operate in its final environment? Your product might be deployed in thousands of different homes or offices, each with its own Wi-Fi network name and password. You also need a way to link a specific physical device to a specific user account in your cloud backend.
This process of configuring a generic device with its unique identity, credentials, and operational parameters is called provisioning. It is the bridge between manufacturing and operation. A poor provisioning experience can render a product unusable and lead to customer frustration and costly support calls. A well-designed provisioning strategy, on the other hand, is secure, scalable, and so seamless that the user barely notices it. This chapter explores the most common and effective methods for provisioning ESP32-based devices.
Theory
What is Provisioning?
Provisioning is the set of actions required to prepare and configure a device to operate within a specific environment. It goes beyond simply flashing firmware. It involves loading the device with unique, instance-specific data that it needs to connect, authenticate, and function correctly.
Think of it like setting up a new computer. The operating system is the “firmware,” but you still need to connect it to your Wi-Fi, log in with your user account, and install specific applications. This setup process is analogous to provisioning.
Data typically handled during provisioning includes:
- Network Credentials: Wi-Fi SSID and password.
- Cloud Identity: Device certificates, private keys, and endpoint URLs needed to connect to an IoT cloud platform like AWS IoT or Azure IoT Hub.
- Unique Identifiers: A unique device ID or serial number.
- User Association: Linking the device to a specific user account.
- Manufacturing Data: Calibration values, hardware revision, or manufacturing date.
Provisioning Strategies
There are several strategies for provisioning a device, each with its own trade-offs between security, user experience, and manufacturing complexity.
1. Pre-provisioning (Factory Flashing)
In this model, all necessary data is embedded into the firmware or flashed onto a data partition in the factory.
- Pros: Simple for the end-user (the device works out of the box).
- Cons: Highly inflexible and insecure for credentials like Wi-Fi passwords. It’s impossible to know the user’s Wi-Fi details at the factory. For cloud credentials, it requires generating and flashing a unique identity for every single device on the assembly line, which is complex to manage at scale.
This method is only suitable for products that operate on a known, fixed network (e.g., devices in a factory that connect to the factory’s own Wi-Fi) or that don’t require network connectivity.
2. Interactive Provisioning (User-driven)
This is the most common model for consumer IoT devices. The device starts in a special “provisioning mode” and the user provides the necessary credentials, typically via a smartphone app. ESP-IDF provides excellent support for this model through its Unified Provisioning Manager.
The two primary communication channels (transports) for this are:
- SoftAP (Software-enabled Access Point): The ESP32 creates its own Wi-Fi network (e.g.,
PROV_Device_123
). The user connects their phone to this network, and the companion app sends the home Wi-Fi credentials over this temporary, direct link. - BLE (Bluetooth Low Energy): The ESP32 advertises itself as a BLE peripheral. The user’s phone connects to it via BLE, and the app sends the credentials over the secure Bluetooth link. This is generally considered a better user experience as the user doesn’t have to disconnect from their home Wi-Fi.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% sequenceDiagram actor User as User participant Phone as Smartphone App participant ESP32 as ESP32 Device participant Router as Home Wi-Fi Router rect rgb(173, 216, 230) note over User, ESP32: BLE Provisioning (Recommended) User->>Phone: Open Provisioning App Phone->>ESP32: Scan for BLE devices ESP32-->>Phone: Advertise ("my-prov-device") Phone->>ESP32: Connect via BLE User->>Phone: Enter Proof of Possession Phone->>ESP32: Send PoP for secure channel User->>Phone: Select Wi-Fi & enter password Phone->>ESP32: Send Wi-Fi credentials over BLE end rect rgb(255, 255, 224) note over User, ESP32: SoftAP Provisioning (Fallback) User->>Phone: Go to Wi-Fi Settings Phone->>ESP32: Connect to ESP32's SoftAP ("my-prov-device-xxxx") User->>Phone: Return to Provisioning App User->>Phone: Enter Proof of Possession Phone->>ESP32: Send PoP for secure channel User->>Phone: Select Wi-Fi & enter password Phone->>ESP32: Send Wi-Fi credentials over SoftAP end ESP32->>ESP32: Receive credentials, stop provisioning mode ESP32->>Router: Connect using new credentials Router-->>ESP32: Assign IP Address ESP32->>Phone: Confirm success Phone->>User: Display "Provisioning Successful!"
In both cases, the connection is secured using a Proof-of-Possession (PoP). This is a shared secret (like a password or QR code) that the user must provide to the app to prove they have physical access to the device, preventing unauthorized provisioning.
3. Claiming-based Provisioning
This is a highly scalable and secure cloud-based method.
- All devices are flashed in the factory with generic, non-sensitive firmware. They have no knowledge of their final destination.
- When a device boots for the first time, it enters a “claiming” mode.
- The user, in their mobile app, scans a QR code on the device.
- The app sends this unique identifier to the cloud backend, “claiming” the device for that user’s account.
- The device then securely communicates with the cloud, which provides it with its operational credentials (like Wi-Fi details and a unique device certificate).
ESP RainMaker, Espressif’s end-to-end IoT platform, uses this model effectively. It simplifies development by abstracting away the complexities of cloud integration and provisioning.
Feature | Pre-provisioning (Factory) | Interactive (BLE/SoftAP) | Claiming-based (Cloud) |
---|---|---|---|
User Experience | Excellent (works out of the box) | Good (requires user interaction via app) | Excellent (simple scan-and-go) |
Security | Poor (if Wi-Fi credentials are included) | Good (secured by Proof of Possession) | Excellent (leverages cloud security) |
Flexibility | Very Low (tied to a specific network/setup) | High (works with any user network) | High (works with any user network) |
Scalability | Low (complex factory process per device) | Medium (scales well for consumer products) | Very High (ideal for mass production) |
Best For | Fixed-network industrial devices | Most consumer IoT products (lights, plugs) | Large-scale deployments, platforms like ESP RainMaker |
ESP-IDF’s Unified Provisioning Manager
Espressif provides a component named wifi_provisioning
that simplifies the implementation of interactive provisioning. It handles the low-level details of the BLE and SoftAP transports, security handshakes, and credential storage.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph TD A(Start: app_main) --> B[Initialize NVS, Wi-Fi, Events]; B --> C[wifi_prov_mgr_init]; C --> D{Is Device Already Provisioned?}; D -- "Yes" --> E[Start Wi-Fi Station Mode]; E --> F((WiFi Connected)); D -- "No" --> G[Start Provisioning Manager<br><b>wifi_prov_mgr_start_provisioning</b>]; G --> H(Device enters Provisioning Mode<br>Advertises via BLE / Creates SoftAP); H --> I{Wait for Provisioning Event}; I -- "WIFI_PROV_CRED_RECV" --> J[Credentials Received]; J --> K{Attempt to Connect to AP}; K -- "Success (IP_EVENT_STA_GOT_IP)" --> L[WIFI_PROV_CRED_SUCCESS Event]; L --> M[Stop Provisioning<br><b>wifi_prov_mgr_deinit</b>]; M --> E; K -- "Fail (WIFI_EVENT_STA_DISCONNECTED)" --> N[WIFI_PROV_CRED_FAIL Event]; N --> H; I -- "Other Events" --> H; classDef start fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef check fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; classDef endo fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; class A start; class B,C,E,G,H,J,L,M process; class D,I,K decision; class N check; class F endo;
Key features:
- Unified API: You write your code once, and can enable BLE, SoftAP, or both via
menuconfig
. - Security: Implements the
protocomm
security layer (sec1) which encrypts the data exchange. Requires a Proof-of-Possession. - Event-driven: It notifies your application of key events, such as when provisioning starts or successfully completes.
- Companion Apps: Espressif provides open-source companion mobile apps for both Android and iOS that work with this component out of the box.
Practical Examples
Let’s implement a secure Wi-Fi provisioning service using the wifi_provisioning
manager component with both BLE and SoftAP transports enabled.
1. Project Setup and Configuration
- Start with a new, standard ESP-IDF project in VS Code.
- Open the configuration menu:
idf.py menuconfig
. - Enable Wi-Fi Provisioning:
- Navigate to
Component config
—>Wi-Fi Provisioning
—>. - Select
Enable Wi-Fi Provisioning Manager
. - Under
Transport
, enable bothEnable BLE transport
andEnable SoftAP transport
. - Set a
Service Name
for your device, e.g.,my-prov-device
. This will be used for both the BLE advertisement name and the SoftAP SSID prefix. - Set a
Proof of Possession (PoP)
string. For this example, useesp32-pop
. In a real product, this should be unique per device.
- Navigate to
- Enable Bluetooth: Since we are using the BLE transport, Bluetooth must be enabled.
- Navigate to
Component config
—>Bluetooth
—> and select[*] Enable
. - Then select
[*] Bluetooth Low Energy
.
- Navigate to
- Save the configuration and exit.
2. Add Component Dependencies
Modify your main/CMakeLists.txt
to include the necessary components.
main/CMakeLists.txt
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES nvs_flash wifi_provisioning)
3. Implement the Provisioning Logic
Replace the content of main/main.c
with the following code. This code initializes the network stack, the provisioning manager, and handles provisioning events.
main/main.c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "esp_event.h"
// Includes for Wi-Fi Provisioning
#include "wifi_provisioning/manager.h"
#include "wifi_provisioning/scheme_ble.h"
#include "wifi_provisioning/scheme_softap.h"
static const char *TAG = "PROV_EXAMPLE";
/* Signal bits for Wi-Fi events */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;
#define WIFI_MAX_RETRY 5
// Forward declaration
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data);
/* Event handler for provisioning manager */
static void prov_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_PROV_EVENT) {
switch (event_id) {
case WIFI_PROV_START:
ESP_LOGI(TAG, "Provisioning started");
break;
case WIFI_PROV_CRED_RECV: {
wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data;
ESP_LOGI(TAG, "Received Wi-Fi credentials for SSID: %s", (const char *) wifi_sta_cfg->ssid);
break;
}
case WIFI_PROV_CRED_FAIL: {
wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
ESP_LOGE(TAG, "Provisioning failed! Reason: %s",
(*reason == WIFI_PROV_STA_AUTH_ERROR) ?
"Wi-Fi station authentication failed" : "Wi-Fi station not found");
break;
}
case WIFI_PROV_CRED_SUCCESS:
ESP_LOGI(TAG, "Provisioning successful");
break;
case WIFI_PROV_END:
/* De-initialize the provisioning manager */
wifi_prov_mgr_deinit();
break;
default:
break;
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP address:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < WIFI_MAX_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "Retrying to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"Failed to connect to the AP");
}
}
static void start_provisioning(void)
{
/* Initialize the provisioning manager */
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_ble, // Use BLE for provisioning
.scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM
};
ESP_ERROR_CHECK(wifi_prov_mgr_init(config));
bool provisioned = false;
/* Check if the device has been provisioned once */
ESP_ERROR_CHECK(wifi_prov_mgr_is_provisioned(&provisioned));
if (!provisioned) {
ESP_LOGI(TAG, "Starting provisioning");
/* Get the service name and proof of possession from Kconfig */
const char *service_name = NULL;
ESP_ERROR_CHECK(wifi_prov_mgr_get_service_name(&service_name));
const char *pop = NULL;
ESP_ERROR_CHECK(wifi_prov_mgr_get_pop(&pop));
/* Start provisioning. The service name will be used for BLE advertisement and SoftAP SSID. */
ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(WIFI_PROV_SECURITY_1, pop, service_name, NULL));
} else {
ESP_LOGI(TAG, "Device already provisioned. Starting Wi-Fi station.");
wifi_prov_mgr_deinit();
// Start Wi-Fi station mode
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_start();
}
}
void app_main(void)
{
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize networking stack
ESP_ERROR_CHECK(esp_netif_init());
// Create default event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
s_wifi_event_group = xEventGroupCreate();
// Register event handlers
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &prov_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &prov_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &prov_event_handler, NULL));
// Initialize Wi-Fi
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
start_provisioning();
/* Wait for Wi-Fi connection */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to AP successfully!");
// Your main application logic starts here
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGE(TAG, "Failed to connect to AP after multiple retries.");
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}
4. Build, Flash, and Provision
- Build and Flash: Run
idf.py build flash monitor
. - Observe Logs: In the monitor, you will see the log “Starting provisioning”.
- Get the App: Install the “ESP SoftAP Provisioning” or “ESP BLE Provisioning” app from the App Store (iOS) or Play Store (Android).
- Provision via BLE:
- Open the “ESP BLE Provisioning” app.
- The app will scan for devices. You should see your device named
my-prov-device
. - Tap on it. It will ask for the Proof of Possession. Enter
esp32-pop
. - The app will then scan for Wi-Fi networks. Select your home Wi-Fi and enter its password.
- Tap “Provision”.
- Observe Success: The app will show a success message. In your device’s monitor logs, you will see it receive the credentials, connect to your Wi-Fi, and get an IP address. The device will then store these credentials in NVS flash.
- Reboot: Reboot the device. It will now bypass provisioning, read the credentials from NVS, and connect directly to your Wi-Fi.
Variant Notes
The Unified Provisioning feature is designed for cross-variant compatibility.
- BLE Provisioning: This is the preferred method for the best user experience. It is supported by all modern ESP32 variants that include a Bluetooth radio: ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, and ESP32-H2.
- SoftAP Provisioning: This is a universal fallback. Since every ESP32 variant has Wi-Fi capabilities, SoftAP provisioning will work on all of them. If you need to support a variant without Bluetooth, or want to provide a non-Bluetooth option, this is the way to go.
- Code Portability: The C code you write using the
wifi_provisioning
manager is identical regardless of the transport chosen. The selection between BLE and SoftAP is handled entirely by the configuration inmenuconfig
, making it trivial to switch or enable both.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Proof of Possession (PoP) Mismatch | App fails to connect or shows a security error. Provisioning does not start. | Ensure the PoP string in the app exactly matches the one set in menuconfig. Case-sensitive! |
NVS Not Initialized | Provisioning seems to succeed, but the device doesn’t save credentials and enters provisioning mode again after reboot. | Call nvs_flash_init() at the beginning of app_main. Ensure error handling is present. |
Bluetooth Not Enabled | BLE provisioning is selected, but the device doesn’t appear in the app’s scan list. Errors during wifi_prov_mgr_init. | In menuconfig, go to Component config -> Bluetooth and enable both Bluetooth and Bluetooth Low Energy. |
Provisioning Manager Not De-initialized | Device works, but has unusually high RAM usage. Potential instability over time. | After successful provisioning (in the WIFI_PROV_END event), call wifi_prov_mgr_deinit() to free resources. |
No Wi-Fi Reconnect Logic | Device connects after provisioning but goes offline permanently if the Wi-Fi router reboots or the signal is temporarily lost. | Implement an event handler for WIFI_EVENT_STA_DISCONNECTED that attempts to reconnect multiple times, like in the example code. |
Exercises
- Add Custom Provisioning Data: Modify the example to allow the user to set a custom “Device Location” string (e.g., “Living Room”) during provisioning. You will need to register a custom endpoint handler using
wifi_prov_mgr_endpoint_create
andwifi_prov_mgr_endpoint_register
. The mobile app also supports sending custom data. - Visual Feedback: Use the
blinker
library from Chapter 286 to provide visual feedback.- Blink slowly (1 Hz) when the device is waiting for provisioning.
- Blink rapidly (5 Hz) when the app is connected and provisioning is in progress.
- Turn the LED on solid when provisioning is successful and the device is connected to Wi-Fi.
- Implement a Factory Reset: Add a button handler (using
gpio_install_isr_service
andgpio_isr_handler_add
). If the button is held down for more than 5 seconds, it should erase the Wi-Fi credentials from NVS and restart the device, forcing it back into provisioning mode. You will need to find the specific NVS key where the credentials are stored. - Design a Provisioning Strategy: You are designing a smart irrigation system for commercial farms. The system consists of a central gateway and dozens of battery-powered sensor nodes. The farm may not have reliable BLE/Wi-Fi access in the fields. Propose a provisioning strategy for the sensor nodes. Consider how they will be identified and how they will securely connect to the gateway. Justify your choices.
Summary
- Provisioning is the essential process of configuring a device with unique credentials and parameters.
- Common provisioning strategies include pre-provisioning (factory flashing) and user-driven interactive provisioning (BLE/SoftAP).
- ESP-IDF’s Unified Provisioning Manager (
wifi_provisioning
component) provides a powerful and secure way to implement interactive Wi-Fi provisioning. - BLE is the preferred transport for a modern user experience, while SoftAP is a universal fallback. Both can be enabled simultaneously.
- A Proof of Possession (PoP) is critical for securing the provisioning process against unauthorized access.
- Proper event handling is crucial for knowing when provisioning is complete and for de-initializing the manager to conserve resources.
- A successful provisioning flow is the first step toward a positive user experience with any connected product.
Further Reading
- Wi-Fi Provisioning Documentation: https://docs.espressif.com/projects/esp-idf/en/v5.2.1/esp32/api-reference/provisioning/wifi_provisioning.html
- ESP RainMaker for Cloud-based Provisioning: https://rainmaker.espressif.com/
- Example Code on GitHub (BLE Provisioning): https://github.com/espressif/esp-idf/tree/v5.2.1/examples/provisioning/wifi_prov_mgr