OVMS3-idf/docs/en/api-reference/provisioning/wifi_provisioning.rst

299 lines
19 KiB
ReStructuredText

Wi-Fi Provisioning
==================
Overview
--------
This component provides APIs that control Wi-Fi provisioning service for receiving and configuring Wi-Fi credentials over SoftAP or BLE transport via secure :doc:`Protocol Communication (protocomm)<protocomm>` sessions. The set of ``wifi_prov_mgr_`` APIs help in quickly implementing a provisioning service having 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 this must be called prior to invoking any other ``wifi_prov_mgr_`` APIs. Note that the manager relies on other components of IDF, namely NVS, TCP/IP, Event Loop and Wi-Fi (and optionally mDNS), hence these must be initialized beforehand. The manager can be de-initialized at any moment by making a call to :cpp:func:`wifi_prov_mgr_deinit()`.
.. highlight:: c
::
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_ble,
.scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM,
.app_event_handler = {
.event_cb = prov_event_handler,
.user_data = NULL
}
};
ESP_ERR_CHECK( wifi_prov_mgr_init(config) );
The configuration structure ``wifi_prov_mgr_config_t`` has a few fields to specify the behavior desired of the manager :
* `scheme` : This is used to specify the provisioning scheme. Each scheme corresponds to one of the modes of transport supported by protocomm. Hence, we have three options :
* ``wifi_prov_scheme_ble`` : BLE transport and GATT Server for handling provisioning commands
* ``wifi_prov_scheme_softap`` : Wi-Fi SoftAP transport and HTTP Server for handling provisioning commands
* ``wifi_prov_scheme_console`` : Serial transport and console for handling provisioning commands
* `scheme_event_handler` : An event handler defined along with scheme. Choosing appropriate scheme specific event handler allows the manager to take care of certain matters automatically. Presently this is not used for either SoftAP or Console based provisioning, but is very convenient for BLE. To understand how, we must recall that Bluetooth requires quite some amount of memory to function and once provisioning is finished, the main application may want to reclaim back this memory (or part of it, if it needs to use either BLE or classic BT). 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 BLE / classic BT / BTDM memory will be freed automatically when the provisioning manager is de-initialized. The available options are:
* ``WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM`` - Free both classic BT and BLE (BTDM) memory. Used when main application doesn't require Bluetooth at all.
* ``WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE`` - Free only BLE memory. Used when main application requires classic BT.
* ``WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT`` - Free only classic BT. Used when main application requires BLE. In this case freeing happens right when the manager is initialized.
* ``WIFI_PROV_EVENT_HANDLER_NONE`` - Don't use any scheme specific handler. Used when provisioning scheme is not BLE (i.e. SoftAP or Console), or when main application wants to handle the memory reclaiming on its own, or needs both BLE and classic BT to function.
* `app_event_handler` : Application specific event handler which can be used to execute specific calls depending on the state of the provisioning service. This is to be set to a function of the form ``void app_event_handler(void *user_data, wifi_prov_cb_event_t event, void *event_data)`` along with any user data to be made available at the time of handling. This can also be set to ``WIFI_PROV_EVENT_HANDLER_NONE`` if not used. See definition of ``wifi_prov_cb_event_t`` for the list of events that are generated by the provisioning service. Following is a snippet showing a typical application specific provisioning event handler along with usage of the ``event_data`` parameter :
.. highlight:: c
::
void prov_event_handler(void *user_data,
wifi_prov_cb_event_t event,
void *event_data)
{
switch (event) {
case WIFI_PROV_INIT:
ESP_LOGI(TAG, "Manager initialized");
break;
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 : %s",
(*reason == WIFI_PROV_STA_AUTH_ERROR) ?
"Wi-Fi AP password incorrect" :
"Wi-Fi AP not found");
break;
}
case WIFI_PROV_CRED_SUCCESS:
ESP_LOGI(TAG, "Provisioning successful");
break;
case WIFI_PROV_END:
ESP_LOGI(TAG, "Provisioning stopped");
break;
case WIFI_PROV_DEINIT:
ESP_LOGI(TAG, "Manager de-initialized");
break;
default:
break;
}
}
.. _wifi-prov-check-state:
Check Provisioning State
^^^^^^^^^^^^^^^^^^^^^^^^
Whether 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 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 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
* main application must implement some logic to call ``esp_wifi_`` APIs for erasing the credentials at runtime
* main application must implement some logic to force start the provisioning irrespective of the provisioning state
.. highlight:: c
::
bool provisioned = false;
ESP_ERR_CHECK( wifi_prov_mgr_is_provisioned(&provisioned) );
Event Loop Handling
^^^^^^^^^^^^^^^^^^^
Presently Wi-Fi provisioning manager cannot directly catch external system events, hence it is necessary to explicitly call :cpp:func:`wifi_prov_mgr_event_handler()` from inside the global event loop handler. See the following snippet :
.. highlight:: c
::
static esp_err_t global_event_loop_handler(void *ctx, system_event_t *event)
{
/* Pass event information to provisioning manager so that it can
* maintain its internal state depending upon the system event */
wifi_prov_mgr_event_handler(ctx, event);
/* Event handling logic for main application */
switch (event->event_id) {
.....
.....
.....
}
return ESP_OK;
}
Start Provisioning Service
^^^^^^^^^^^^^^^^^^^^^^^^^^
At the time of starting provisioning we need to specify a service name and the corresponding key. These translate to :
* Wi-Fi SoftAP SSID and passphrase, respectively, when scheme is ``wifi_prov_scheme_softap``
* BLE Device name (service key is ignored) when 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/decryption of subsequent messages
* Security 0 is simply plain text communication. In this case the `pop` is simply ignored
See :doc:`Provisioning<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_ERR_CHECK( wifi_prov_mgr_start_provisioning(security, pop, service_name, service_key) );
The provisioning service will automatically finish only if it receives valid Wi-Fi AP credentials followed by successfully connection of device to the AP (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 won't accept new credentials anymore, but the provisioning service will keep on running (only to convey failure to the client), until the device is restarted. Upon restart the provisioning state will turn out to be true this time (as credentials will be found in NVS), but device will again fail 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 will wait for the provisioning to finish, then de-initialize the manager to free up resources and finally start 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()`.
.. highlight:: c
::
// Start provisioning service
ESP_ERR_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 application specific event handler which is to be configured during initialization, as explained above in :ref:`wifi-prov-mgr-init`.
.. highlight:: c
::
void prov_event_handler(void *user_data, wifi_prov_cb_event_t event, void *event_data)
{
switch (event) {
case WIFI_PROV_END:
// De-initialize manager once provisioning is finished
wifi_prov_mgr_deinit();
break;
default:
break;
}
}
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 BLE 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 hostname set by the main application is used, and the service type is internally set to `_esp_wifi_prov`.
When using BLE transport, a custom 128 bit UUID should be set using :cpp:func:`wifi_prov_scheme_ble_set_service_uuid()`. This UUID will be included in the BLE advertisement and will correspond to the primary GATT service that provides provisioning endpoints as GATT characteristics. Each GATT characteristic will be formed using the primary service UUID as base, with different auto assigned 12th and 13th bytes (assume counting starts from 0th byte). Since, an endpoint characteristic UUID is auto assigned, it shouldn't 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 will be 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 :
.. list-table:: Endpoints provided by Provisioning Service
:widths: 10 25 50
:header-rows: 1
* - Endpoint Name (BLE + GATT Server)
- URI (SoftAP + HTTP Server + mDNS)
- Description
* - prov-session
- http://<mdns-hostname>.local/prov-session
- Security endpoint used for session establishment
* - prov-config
- http://<mdns-hostname>.local/prov-config
- Endpoint used for configuring Wi-Fi credentials on device
* - proto-ver
- http://<mdns-hostname>.local/proto-ver
- 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 un-encrypted, hence necessary information (that 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 `no_pop` capability is supported, which indicates that the service doesn't require proof of possession for authentication. Any application related version / capabilities will be given by other labels (like `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 (this is not needed when manager is configured to use protocomm security 0).
See Unified Provisioning for more details about the secure handshake and encryption used. Applications must use the `.proto` files found under `components/protocomm/proto <https://github.com/espressif/esp-idf/components/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 commands, serialized as Protobuf messages (the corresponding `.proto` files can be found under `components/wifi_provisioning/proto <https://github.com/espressif/esp-idf/components/wifi_provisioning/proto>`_) :
* `get_status` - For querying the Wi-Fi connection status. The device will respond with a status which will be one of connecting / connected / disconnected. If status is disconnected, a disconnection reason will also 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 start the Wi-Fi station
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<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.
.. highlight:: 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 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 will be stopped (and BLE / SoftAP turned off) automatically after responding to the next `get_status` command. If `get_status` command is not received by the device, the service will be stopped after a 30s timeout.
On the other hand, if device was not able to connect using the provided Wi-Fi credentials, due to incorrect SSID / passphrase, the service will keep running, and `get_status` will keep responding with disconnected status and reason for disconnection. Any further attempts to provide another set of Wi-Fi credentials, will be rejected. These credentials will be 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 will only be stopped 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 this credentials over a custom protocomm endpoint, then after sucessfully 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/manager`
API Reference
-------------
.. include:: /_build/inc/manager.inc
.. include:: /_build/inc/scheme_ble.inc
.. include:: /_build/inc/scheme_softap.inc
.. include:: /_build/inc/scheme_console.inc
.. include:: /_build/inc/wifi_config.inc