Wi-Fi Provisioning
==================
:link_to_translation:`zh_CN:[中文]`
Overview
--------
This component provides APIs that control the Wi-Fi provisioning service for receiving and configuring Wi-Fi credentials over SoftAP or Bluetooth LE transport via secure :doc:`protocomm` sessions. The set of ``wifi_prov_mgr_`` APIs help quickly implement a provisioning service that has necessary features with minimal amount of code and sufficient flexibility.
.. _wifi-prov-mgr-init:
Initialization
^^^^^^^^^^^^^^
:cpp:func:`wifi_prov_mgr_init()` is called to configure and initialize the provisioning manager, and thus must be called prior to invoking any other ``wifi_prov_mgr_`` APIs. Note that the manager relies on other components of ESP-IDF, namely NVS, TCP/IP, Event Loop and Wi-Fi, and optionally mDNS, hence these components must be initialized beforehand. The manager can be de-initialized at any moment by making a call to :cpp:func:`wifi_prov_mgr_deinit()`.
.. code-block:: c
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_ble,
.scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM
};
ESP_ERROR_CHECK( wifi_prov_mgr_init(config) );
The configuration structure :cpp:type:`wifi_prov_mgr_config_t` has a few fields to specify the desired behavior of the manager:
* :cpp:member:`wifi_prov_mgr_config_t::scheme` - This is used to specify the provisioning scheme. Each scheme corresponds to one of the modes of transport supported by protocomm. Hence, support the following options:
* ``wifi_prov_scheme_ble`` - Bluetooth LE transport and GATT Server for handling the provisioning commands.
* ``wifi_prov_scheme_softap`` - Wi-Fi SoftAP transport and HTTP Server for handling the provisioning commands.
* ``wifi_prov_scheme_console`` - Serial transport and console for handling the provisioning commands.
* :cpp:member:`wifi_prov_mgr_config_t::scheme_event_handler`: An event handler defined along with the scheme. Choosing the appropriate scheme-specific event handler allows the manager to take care of certain matters automatically. Presently, this option is not used for either the SoftAP or Console-based provisioning, but is very convenient for Bluetooth LE. To understand how, we must recall that Bluetooth requires a substantial amount of memory to function, and once the provisioning is finished, the main application may want to reclaim back this memory (or part of it) if it needs to use either Bluetooth LE or classic Bluetooth. Also, upon every future reboot of a provisioned device, this reclamation of memory needs to be performed again. To reduce this complication in using ``wifi_prov_scheme_ble``, the scheme-specific handlers have been defined, and depending upon the chosen handler, the Bluetooth LE/classic Bluetooth/BTDM memory is freed automatically when the provisioning manager is de-initialized. The available options are:
* ``WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM`` - Free both classic Bluetooth and Bluetooth LE/BTDM memory. Used when the main application does not require Bluetooth at all.
* ``WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE`` - Free only Bluetooth LE memory. Used when main application requires classic Bluetooth.
* ``WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT`` - Free only classic Bluetooth. Used when main application requires Bluetooth LE. In this case freeing happens right when the manager is initialized.
* ``WIFI_PROV_EVENT_HANDLER_NONE`` - Do not use any scheme specific handler. Used when the provisioning scheme is not Bluetooth LE, i.e., using SoftAP or Console, or when main application wants to handle the memory reclaiming on its own, or needs both Bluetooth LE and classic Bluetooth to function.
* :cpp:member:`wifi_prov_mgr_config_t::app_event_handler` (Deprecated) - It is now recommended to catch ``WIFI_PROV_EVENT`` that is emitted to the default event loop handler. See definition of ``wifi_prov_cb_event_t`` for the list of events that are generated by the provisioning service. Here is an excerpt showing some of the provisioning events:
.. code-block:: c
static void event_handler(void* arg, esp_event_base_t event_base,
int 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"
"\n\tSSID : %s\n\tPassword : %s",
(const char *) wifi_sta_cfg->ssid,
(const char *) wifi_sta_cfg->password);
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!\n\tReason : %s"
"\n\tPlease reset to factory and retry provisioning",
(*reason == WIFI_PROV_STA_AUTH_ERROR) ?
"Wi-Fi station authentication failed" : "Wi-Fi access-point not found");
break;
}
case WIFI_PROV_CRED_SUCCESS:
ESP_LOGI(TAG, "Provisioning successful");
break;
case WIFI_PROV_END:
/* De-initialize manager once provisioning is finished */
wifi_prov_mgr_deinit();
break;
default:
break;
}
}
}
The manager can be de-initialized at any moment by making a call to :cpp:func:`wifi_prov_mgr_deinit()`.
.. _wifi-prov-check-state:
Check the Provisioning State
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Whether the device is provisioned or not can be checked at runtime by calling :cpp:func:`wifi_prov_mgr_is_provisioned()`. This internally checks if the Wi-Fi credentials are stored in NVS.
Note that presently the manager does not have its own NVS namespace for storage of Wi-Fi credentials, instead it relies on the ``esp_wifi_`` APIs to set and get the credentials stored in NVS from the default location.
If the provisioning state needs to be reset, any of the following approaches may be taken:
* The associated part of NVS partition has to be erased manually
* The main application must implement some logic to call ``esp_wifi_`` APIs for erasing the credentials at runtime
* The main application must implement some logic to force start the provisioning irrespective of the provisioning state
.. code-block:: c
bool provisioned = false;
ESP_ERROR_CHECK( wifi_prov_mgr_is_provisioned(&provisioned) );
Start the Provisioning Service
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
At the time of starting provisioning we need to specify a service name and the corresponding key, that is to say:
* A Wi-Fi SoftAP SSID and a passphrase, respectively, when the scheme is ``wifi_prov_scheme_softap``.
* Bluetooth LE device name with the service key ignored when the scheme is ``wifi_prov_scheme_ble``.
Also, since internally the manager uses ``protocomm``, we have the option of choosing one of the security features provided by it:
* Security 1 is secure communication which consists of a prior handshake involving X25519 key exchange along with authentication using a proof of possession ``pop``, followed by AES-CTR for encryption or decryption of subsequent messages.
* Security 0 is simply plain text communication. In this case the ``pop`` is simply ignored.
See :doc:`provisioning` for details about the security features.
.. highlight:: c
::
const char *service_name = "my_device";
const char *service_key = "password";
wifi_prov_security_t security = WIFI_PROV_SECURITY_1;
const char *pop = "abcd1234";
ESP_ERROR_CHECK( wifi_prov_mgr_start_provisioning(security, pop, service_name, service_key) );
The provisioning service automatically finishes only if it receives valid Wi-Fi AP credentials followed by successful connection of device to the AP with IP obtained. Regardless of that, the provisioning service can be stopped at any moment by making a call to :cpp:func:`wifi_prov_mgr_stop_provisioning()`.
.. note::
If the device fails to connect with the provided credentials, it does not accept new credentials anymore, but the provisioning service keeps on running, only to convey failure to the client, until the device is restarted. Upon restart, the provisioning state turns out to be true this time, as credentials are found in NVS, but the device does fail again to connect with those same credentials, unless an AP with the matching credentials somehow does become available. This situation can be fixed by resetting the credentials in NVS or force starting the provisioning service. This has been explained above in :ref:`wifi-prov-check-state`.
Waiting for Completion
^^^^^^^^^^^^^^^^^^^^^^
Typically, the main application waits for the provisioning to finish, then de-initializes the manager to free up resources, and finally starts executing its own logic.
There are two ways for making this possible. The simpler way is to use a blocking call to :cpp:func:`wifi_prov_mgr_wait()`.
.. code-block:: c
// Start provisioning service
ESP_ERROR_CHECK( wifi_prov_mgr_start_provisioning(security, pop, service_name, service_key) );
// Wait for service to complete
wifi_prov_mgr_wait();
// Finally de-initialize the manager
wifi_prov_mgr_deinit();
The other way is to use the default event loop handler to catch ``WIFI_PROV_EVENT`` and call :cpp:func:`wifi_prov_mgr_deinit()` when event ID is ``WIFI_PROV_END``:
.. code-block:: c
static void event_handler(void* arg, esp_event_base_t event_base,
int event_id, void* event_data)
{
if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_END) {
/* De-initialize the manager once the provisioning is finished */
wifi_prov_mgr_deinit();
}
}
User Side Implementation
^^^^^^^^^^^^^^^^^^^^^^^^
When the service is started, the device to be provisioned is identified by the advertised service name, which, depending upon the selected transport, is either the Bluetooth LE device name or the SoftAP SSID.
When using SoftAP transport, for allowing service discovery, mDNS must be initialized before starting provisioning. In this case, the host name set by the main application is used, and the service type is internally set to ``_esp_wifi_prov``.
When using Bluetooth LE transport, a custom 128-bit UUID should be set using :cpp:func:`wifi_prov_scheme_ble_set_service_uuid()`. This UUID is to be included in the Bluetooth LE advertisement and corresponds to the primary GATT service that provides provisioning endpoints as GATT characteristics. Each GATT characteristic is formed using the primary service UUID as the base, with different auto-assigned 12th and 13th bytes, presumably counting from the 0th byte. Since an endpoint characteristic UUID is auto-assigned, it should not be used to identify the endpoint. Instead, client-side applications should identify the endpoints by reading the User Characteristic Description (``0x2901``) descriptor for each characteristic, which contains the endpoint name of the characteristic. For example, if the service UUID is set to ``55cc035e-fb27-4f80-be02-3c60828b7451``, each endpoint characteristic is assigned a UUID like ``55cc____-fb27-4f80-be02-3c60828b7451``, with unique values at the 12th and 13th bytes.
Once connected to the device, the provisioning-related protocomm endpoints can be identified as follows:
.. |br| raw:: html
.. list-table:: Endpoints Provided by the Provisioning Service
:widths: 35 35 30
:header-rows: 1
* - Endpoint Name |br| i.e., Bluetooth LE + GATT Server
- URI, i.e., SoftAP |br| + HTTP Server + mDNS
- Description
* - prov-session
- http://.local/prov-session
- Security endpoint used for session establishment
* - prov-scan
- http://wifi-prov.local/prov-scan
- the endpoint used for starting Wi-Fi scan and receiving scan results
* - prov-ctrl
- http://wifi-prov.local/prov-ctrl
- the endpoint used for controlling Wi-Fi provisioning state
* - prov-config
- http://.local/prov-config
- the endpoint used for configuring Wi-Fi credentials on device
* - proto-ver
- http://.local/proto-ver
- the endpoint for retrieving version info
Immediately after connecting, the client application may fetch the version/capabilities information from the ``proto-ver`` endpoint. All communications to this endpoint are unencrypted, hence necessary information, which may be relevant for deciding compatibility, can be retrieved before establishing a secure session. The response is in JSON format and looks like : ``prov: { ver: v1.1, cap: [no_pop] }, my_app: { ver: 1.345, cap: [cloud, local_ctrl] },....``. Here label ``prov`` provides provisioning service version ``ver`` and capabilities ``cap``. For now, only the ``no_pop`` capability is supported, which indicates that the service does not require proof of possession for authentication. Any application-related version or capabilities are given by other labels, e.g., ``my_app`` in this example. These additional fields are set using :cpp:func:`wifi_prov_mgr_set_app_info()`.
User side applications need to implement the signature handshaking required for establishing and authenticating secure protocomm sessions as per the security scheme configured for use, which is not needed when the manager is configured to use protocomm security 0.
See :doc:`provisioning` for more details about the secure handshake and encryption used. Applications must use the ``.proto`` files found under :component:`protocomm/proto`, which define the Protobuf message structures supported by ``prov-session`` endpoint.
Once a session is established, Wi-Fi credentials are configured using the following set of ``wifi_config`` commands, serialized as Protobuf messages with the corresponding ``.proto`` files that can be found under :component:`wifi_provisioning/proto`:
* ``get_status`` - For querying the Wi-Fi connection status. The device responds with a status which is one of connecting, connected or disconnected. If the status is disconnected, a disconnection reason is also to be included in the status response.
* ``set_config`` - For setting the Wi-Fi connection credentials.
* ``apply_config`` - For applying the credentials saved during ``set_config`` and starting the Wi-Fi station.
After session establishment, the client can also request Wi-Fi scan results from the device. The results returned is a list of AP SSIDs, sorted in descending order of signal strength. This allows client applications to display APs nearby to the device at the time of provisioning, and users can select one of the SSIDs and provide the password which is then sent using the ``wifi_config`` commands described above. The ``wifi_scan`` endpoint supports the following protobuf commands :
* ``scan_start`` - For starting Wi-Fi scan with various options:
* ``blocking`` (input) - If true, the command returns only when the scanning is finished.
* ``passive`` (input) - If true, the scan is started in passive mode, which may be slower, instead of active mode.
* ``group_channels`` (input) - This specifies whether to scan all channels in one go when zero, or perform scanning of channels in groups, with 120 ms delay between scanning of consecutive groups, and the value of this parameter sets the number of channels in each group. This is useful when transport mode is SoftAP, where scanning all channels in one go may not give the Wi-Fi driver enough time to send out beacons, and hence may cause disconnection with any connected stations. When scanning in groups, the manager waits for at least 120 ms after completing the scan on a group of channels, and thus allows the driver to send out the beacons. For example, given that the total number of Wi-Fi channels is 14, then setting ``group_channels`` to 3 creates 5 groups, with each group having 3 channels, except the last one which has 14 % 3 = 2 channels. So, when the scan is started, the first 3 channels will be scanned, followed by a 120 ms delay, and then the next 3 channels, and so on, until all the 14 channels have been scanned.One may need to adjust this parameter as having only a few channels in a group may increase the overall scan time, while having too many may again cause disconnection. Usually, a value of 4 should work for most cases. Note that for any other mode of transport, e.g. Bluetooth LE, this can be safely set to 0, and hence achieve the shortest overall scanning time.
* ``period_ms`` (input) - The scan parameter specifying how long to wait on each channel.
* ``scan_status`` - It gives the status of scanning process:
* ``scan_finished`` (output) - When the scan has finished, this returns true.
* ``result_count`` (output) - This gives the total number of results obtained till now. If the scan is yet happening, this number keeps on updating.
* ``scan_result`` - For fetching the scan results. This can be called even if the scan is still on going.
* ``start_index`` (input) - Where the index starts from to fetch the entries from the results list.
* ``count`` (input) - The number of entries to fetch from the starting index.
* ``entries`` (output) - The list of entries returned. Each entry consists of ``ssid``, ``channel`` and ``rssi`` information.
The client can also control the provisioning state of the device using ``wifi_ctrl`` endpoint. The ``wifi_ctrl`` endpoint supports the following protobuf commands:
* ``ctrl_reset`` - Resets internal state machine of the device and clears provisioned credentials only in case of provisioning failures.
* ``ctrl_reprov`` - Resets internal state machine of the device and clears provisioned credentials only in case the device is to be provisioned again for new credentials after a previous successful provisioning.
Additional Endpoints
^^^^^^^^^^^^^^^^^^^^
In case users want to have some additional protocomm endpoints customized to their requirements, this is done in two steps. First is creation of an endpoint with a specific name, and the second step is the registration of a handler for this endpoint. See :doc:`protocomm` for the function signature of an endpoint handler. A custom endpoint must be created after initialization and before starting the provisioning service. Whereas, the protocomm handler is registered for this endpoint only after starting the provisioning service.
.. code-block:: c
wifi_prov_mgr_init(config);
wifi_prov_mgr_endpoint_create("custom-endpoint");
wifi_prov_mgr_start_provisioning(security, pop, service_name, service_key);
wifi_prov_mgr_endpoint_register("custom-endpoint", custom_ep_handler, custom_ep_data);
When the provisioning service stops, the endpoint is unregistered automatically.
One can also choose to call :cpp:func:`wifi_prov_mgr_endpoint_unregister()` to manually deactivate an endpoint at runtime. This can also be used to deactivate the internal endpoints used by the provisioning service.
When/How to Stop the Provisioning Service?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The default behavior is that once the device successfully connects using the Wi-Fi credentials set by the ``apply_config`` command, the provisioning service stops, and Bluetooth LE or SoftAP turns off, automatically after responding to the next ``get_status`` command. If ``get_status`` command is not received by the device, the service stops after a 30s timeout.
On the other hand, if device is not able to connect using the provided Wi-Fi credentials, due to incorrect SSID or passphrase, the service keeps running, and ``get_status`` keeps responding with disconnected status and reason for disconnection. Any further attempts to provide another set of Wi-Fi credentials, are to be rejected. These credentials are preserved, unless the provisioning service is force started, or NVS erased.
If this default behavior is not desired, it can be disabled by calling :cpp:func:`wifi_prov_mgr_disable_auto_stop()`. Now the provisioning service stops only after an explicit call to :cpp:func:`wifi_prov_mgr_stop_provisioning()`, which returns immediately after scheduling a task for stopping the service. The service stops after a certain delay and ``WIFI_PROV_END`` event gets emitted. This delay is specified by the argument to :cpp:func:`wifi_prov_mgr_disable_auto_stop()`.
The customized behavior is useful for applications which want the provisioning service to be stopped some time after the Wi-Fi connection is successfully established. For example, if the application requires the device to connect to some cloud service and obtain another set of credentials, and exchange these credentials over a custom protocomm endpoint, then after successfully doing so, stop the provisioning service by calling :cpp:func:`wifi_prov_mgr_stop_provisioning()` inside the protocomm handler itself. The right amount of delay ensures that the transport resources are freed only after the response from the protocomm handler reaches the client side application.
Application Examples
--------------------
For complete example implementation see :example:`provisioning/wifi_prov_mgr`.
Provisioning Tools
--------------------
Provisioning applications are available for various platforms, along with source code:
* Android:
* `Bluetooth LE Provisioning app on Play Store `_.
* `SoftAP Provisioning app on Play Store `_.
* Source code on GitHub: `esp-idf-provisioning-android `_.
* iOS:
* `Bluetooth LE Provisioning app on App Store `_.
* `SoftAP Provisioning app on App Store `_.
* Source code on GitHub: `esp-idf-provisioning-ios `_.
* Linux/MacOS/Windows: :idf:`tools/esp_prov`, a Python-based command-line tool for provisioning.
The phone applications offer simple UI and are thus more user centric, while the command-line application is useful as a debugging tool for developers.
API Reference
-------------
.. include-build-file:: inc/manager.inc
.. include-build-file:: inc/scheme_ble.inc
.. include-build-file:: inc/scheme_softap.inc
.. include-build-file:: inc/scheme_console.inc
.. include-build-file:: inc/wifi_config.inc