From 4ef6c85f0cac63f2204e849e7852ac2c33d58f0b Mon Sep 17 00:00:00 2001 From: Anurag Kar Date: Tue, 16 Apr 2019 17:14:10 +0530 Subject: [PATCH] wifi_provisioning : Wi-Fi Provisioning Manager added --- components/wifi_provisioning/CMakeLists.txt | 17 +- components/wifi_provisioning/component.mk | 6 +- .../include/wifi_provisioning/manager.h | 553 ++++++++ .../include/wifi_provisioning/scheme_ble.h | 82 ++ .../wifi_provisioning/scheme_console.h | 34 + .../include/wifi_provisioning/scheme_softap.h | 34 + components/wifi_provisioning/src/handlers.c | 146 +++ components/wifi_provisioning/src/manager.c | 1117 +++++++++++++++++ components/wifi_provisioning/src/scheme_ble.c | 232 ++++ .../wifi_provisioning/src/scheme_console.c | 92 ++ .../wifi_provisioning/src/scheme_softap.c | 202 +++ .../src/wifi_provisioning_priv.h | 44 + 12 files changed, 2555 insertions(+), 4 deletions(-) create mode 100644 components/wifi_provisioning/include/wifi_provisioning/manager.h create mode 100644 components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h create mode 100644 components/wifi_provisioning/include/wifi_provisioning/scheme_console.h create mode 100644 components/wifi_provisioning/include/wifi_provisioning/scheme_softap.h create mode 100644 components/wifi_provisioning/src/handlers.c create mode 100644 components/wifi_provisioning/src/manager.c create mode 100644 components/wifi_provisioning/src/scheme_ble.c create mode 100644 components/wifi_provisioning/src/scheme_console.c create mode 100644 components/wifi_provisioning/src/scheme_softap.c create mode 100644 components/wifi_provisioning/src/wifi_provisioning_priv.h diff --git a/components/wifi_provisioning/CMakeLists.txt b/components/wifi_provisioning/CMakeLists.txt index 0147d685c7..ec129bce3b 100644 --- a/components/wifi_provisioning/CMakeLists.txt +++ b/components/wifi_provisioning/CMakeLists.txt @@ -1,10 +1,21 @@ set(COMPONENT_ADD_INCLUDEDIRS include) -set(COMPONENT_PRIV_INCLUDEDIRS proto-c ../protocomm/proto-c) +set(COMPONENT_PRIV_INCLUDEDIRS src proto-c ../protocomm/proto-c) set(COMPONENT_SRCS "src/wifi_config.c" + "src/manager.c" + "src/handlers.c" + "src/scheme_softap.c" + "src/scheme_console.c" "proto-c/wifi_config.pb-c.c" "proto-c/wifi_constants.pb-c.c") -set(COMPONENT_REQUIRES lwip) -set(COMPONENT_PRIV_REQUIRES protobuf-c protocomm) +set(COMPONENT_REQUIRES lwip protocomm) +set(COMPONENT_PRIV_REQUIRES protobuf-c bt mdns json) + +if(CONFIG_BT_ENABLED) + if(CONFIG_BT_BLUEDROID_ENABLED) + list(APPEND COMPONENT_SRCS + "src/scheme_ble.c") + endif() +endif() register_component() diff --git a/components/wifi_provisioning/component.mk b/components/wifi_provisioning/component.mk index efeb597c37..458d957c76 100644 --- a/components/wifi_provisioning/component.mk +++ b/components/wifi_provisioning/component.mk @@ -1,3 +1,7 @@ COMPONENT_SRCDIRS := src proto-c COMPONENT_ADD_INCLUDEDIRS := include -COMPONENT_PRIV_INCLUDEDIRS := proto-c ../protocomm/proto-c/ +COMPONENT_PRIV_INCLUDEDIRS := src proto-c ../protocomm/proto-c/ + +ifneq ($(filter y, $(CONFIG_BT_ENABLED) $(CONFIG_BT_BLUEDROID_ENABLED)),y y) + COMPONENT_OBJEXCLUDE := src/scheme_ble.o +endif diff --git a/components/wifi_provisioning/include/wifi_provisioning/manager.h b/components/wifi_provisioning/include/wifi_provisioning/manager.h new file mode 100644 index 0000000000..6b535604d4 --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/manager.h @@ -0,0 +1,553 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "wifi_provisioning/wifi_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Events generated by manager + * + * These events are generated in order of declaration and, for the + * stretch of time between initialization and de-initialization of + * the manager, each event is signaled only once + */ +typedef enum { + /** + * Emitted when the manager is initialized + */ + WIFI_PROV_INIT, + + /** + * Indicates that provisioning has started + */ + WIFI_PROV_START, + + /** + * Emitted when Wi-Fi AP credentials are received via `protocomm` + * endpoint `wifi_config`. The event data in this case is a pointer + * to the corresponding `wifi_sta_config_t` structure + */ + WIFI_PROV_CRED_RECV, + + /** + * Emitted when device fails to connect to the AP of which the + * credentials were received earlier on event `WIFI_PROV_CRED_RECV`. + * The event data in this case is a pointer to the disconnection + * reason code with type `wifi_prov_sta_fail_reason_t` + */ + WIFI_PROV_CRED_FAIL, + + /** + * Emitted when device successfully connects to the AP of which the + * credentials were received earlier on event `WIFI_PROV_CRED_RECV` + */ + WIFI_PROV_CRED_SUCCESS, + + /** + * Signals that provisioning service has stopped + */ + WIFI_PROV_END, + + /** + * Signals that manager has been de-initialized + */ + WIFI_PROV_DEINIT, +} wifi_prov_cb_event_t; + +typedef void (*wifi_prov_cb_func_t)(void *user_data, wifi_prov_cb_event_t event, void *event_data); + +/** + * @brief Event handler that is used by the manager while + * provisioning service is active + */ +typedef struct { + /** + * Callback function to be executed on provisioning events + */ + wifi_prov_cb_func_t event_cb; + + /** + * User context data to pass as parameter to callback function + */ + void *user_data; +} wifi_prov_event_handler_t; + +/** + * @brief Event handler can be set to none if not used + */ +#define WIFI_PROV_EVENT_HANDLER_NONE { \ + .event_cb = NULL, \ + .user_data = NULL \ +} + +/** + * @brief Structure for specifying the provisioning scheme to be + * followed by the manager + * + * @note Ready to use schemes are available: + * - wifi_prov_scheme_ble : for provisioning over BLE transport + GATT server + * - wifi_prov_scheme_softap : for provisioning over SoftAP transport + HTTP server + * - wifi_prov_scheme_console : for provisioning over Serial UART transport + Console (for debugging) + */ +typedef struct wifi_prov_scheme { + /** + * Function which is to be called by the manager when it is to + * start the provisioning service associated with a protocomm instance + * and a scheme specific configuration + */ + esp_err_t (*prov_start) (protocomm_t *pc, void *config); + + /** + * Function which is to be called by the manager to stop the + * provisioning service previously associated with a protocomm instance + */ + esp_err_t (*prov_stop) (protocomm_t *pc); + + /** + * Function which is to be called by the manager to generate + * a new configuration for the provisioning service, that is + * to be passed to prov_start() + */ + void *(*new_config) (void); + + /** + * Function which is to be called by the manager to delete a + * configuration generated using new_config() + */ + void (*delete_config) (void *config); + + /** + * Function which is to be called by the manager to set the + * service name and key values in the configuration structure + */ + esp_err_t (*set_config_service) (void *config, const char *service_name, const char *service_key); + + /** + * Function which is to be called by the manager to set a protocomm endpoint + * with an identifying name and UUID in the configuration structure + */ + esp_err_t (*set_config_endpoint) (void *config, const char *endpoint_name, uint16_t uuid); + + /** + * Sets mode of operation of Wi-Fi during provisioning + * This is set to : + * - WIFI_MODE_APSTA for SoftAP transport + * - WIFI_MODE_STA for BLE transport + */ + wifi_mode_t wifi_mode; +} wifi_prov_scheme_t; + +/** + * @brief Structure for specifying the manager configuration + */ +typedef struct { + /** + * Provisioning scheme to use. Following schemes are already available: + * - wifi_prov_scheme_ble : for provisioning over BLE transport + GATT server + * - wifi_prov_scheme_softap : for provisioning over SoftAP transport + HTTP server + mDNS (optional) + * - wifi_prov_scheme_console : for provisioning over Serial UART transport + Console (for debugging) + */ + wifi_prov_scheme_t scheme; + + /** + * Event handler required by the scheme for incorporating scheme specific + * behavior while provisioning manager is running. Various options may be + * provided by the scheme for setting this field. Use WIFI_PROV_EVENT_HANDLER_NONE + * when not used. When using scheme wifi_prov_scheme_ble, the following + * options are available: + * - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM + * - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE + * - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT + */ + wifi_prov_event_handler_t scheme_event_handler; + + /** + * Event handler that can be set for the purpose of incorporating application + * specific behavior. Use WIFI_PROV_EVENT_HANDLER_NONE when not used. + */ + wifi_prov_event_handler_t app_event_handler; +} wifi_prov_mgr_config_t; + +/** + * @brief Security modes supported by the Provisioning Manager. + * + * These are same as the security modes provided by protocomm + */ +typedef enum wifi_prov_security { + /** + * No security (plain-text communication) + */ + WIFI_PROV_SECURITY_0 = 0, + + /** + * This secure communication mode consists of + * X25519 key exchange + * + proof of possession (pop) based authentication + * + AES-CTR encryption + */ + WIFI_PROV_SECURITY_1 +} wifi_prov_security_t; + +/** + * @brief Initialize provisioning manager instance + * + * Configures the manager and allocates internal resources + * + * Configuration specifies the provisioning scheme (transport) + * and event handlers + * + * Event WIFI_PROV_INIT is emitted right after initialization + * is complete + * + * @param[in] config Configuration structure + * + * @return + * - ESP_OK : Success + * - ESP_FAIL : Fail + */ +esp_err_t wifi_prov_mgr_init(wifi_prov_mgr_config_t config); + +/** + * @brief Stop provisioning (if running) and release + * resource used by the manager + * + * Event WIFI_PROV_DEINIT is emitted right after de-initialization + * is finished + * + * If provisioning service is still active when this API is called, + * it first stops the service, hence emitting WIFI_PROV_END, and + * then performs the de-initialization + */ +void wifi_prov_mgr_deinit(void); + +/** + * @brief Checks if device is provisioned + * + * This checks if Wi-Fi credentials are present on the NVS + * + * The Wi-Fi credentials are assumed to be kept in the same + * NVS namespace as used by esp_wifi component + * + * If one were to call esp_wifi_set_config() directly instead + * of going through the provisioning process, this function will + * still yield true (i.e. device will be found to be provisioned) + * + * @note Calling wifi_prov_mgr_start_provisioning() automatically + * resets the provision state, irrespective of what the + * state was prior to making the call. + * + * @param[out] provisioned True if provisioned, else false + * + * @return + * - ESP_OK : Retrieved provision state successfully + * - ESP_FAIL : Wi-Fi not initialized + * - ESP_ERR_INVALID_ARG : Null argument supplied + * - ESP_ERR_INVALID_STATE : Manager not initialized + */ +esp_err_t wifi_prov_mgr_is_provisioned(bool *provisioned); + +/** + * @brief Start provisioning service + * + * This starts the provisioning service according to the scheme + * configured at the time of initialization. For scheme : + * - wifi_prov_scheme_ble : This starts protocomm_ble, which internally initializes + * BLE transport and starts GATT server for handling + * provisioning requests + * - wifi_prov_scheme_softap : This activates SoftAP mode of Wi-Fi and starts + * protocomm_httpd, which internally starts an HTTP + * server for handling provisioning requests (If mDNS is + * active it also starts advertising service with type + * _esp_wifi_prov._tcp) + * + * Event WIFI_PROV_START is emitted right after provisioning starts without failure + * + * @note This API will start provisioning service even if device is found to be + * already provisioned, i.e. wifi_prov_mgr_is_provisioned() yields true + * + * @param[in] security Specify which protocomm security scheme to use : + * - WIFI_PROV_SECURITY_0 : For no security + * - WIFI_PROV_SECURITY_1 : x25519 secure handshake for session + * establishment followed by AES-CTR encryption of provisioning messages + * @param[in] pop Pointer to proof of possession string (NULL if not needed). This + * is relevant only for protocomm security 1, in which case it is used + * for authenticating secure session + * @param[in] service_name Unique name of the service. This translates to: + * - Wi-Fi SSID when provisioning mode is softAP + * - Device name when provisioning mode is BLE + * @param[in] service_key Key required by client to access the service (NULL if not needed). + * This translates to: + * - Wi-Fi password when provisioning mode is softAP + * - ignored when provisioning mode is BLE + * + * @return + * - ESP_OK : Provisioning started successfully + * - ESP_FAIL : Failed to start provisioning service + * - ESP_ERR_INVALID_STATE : Provisioning manager not initialized or already started + */ +esp_err_t wifi_prov_mgr_start_provisioning(wifi_prov_security_t security, const char *pop, + const char *service_name, const char *service_key); + +/** + * @brief Stop provisioning service + * + * If provisioning service is active, this API will initiate a process to stop + * the service and return. Once the service actually stops, the event WIFI_PROV_END + * will be emitted. + * + * If wifi_prov_mgr_deinit() is called without calling this API first, it will + * automatically stop the provisioning service and emit the WIFI_PROV_END, followed + * by WIFI_PROV_DEINIT, before returning. + * + * This API will generally be used along with wifi_prov_mgr_disable_auto_stop() + * in the scenario when the main application has registered its own endpoints, + * and wishes that the provisioning service is stopped only when some protocomm + * command from the client side application is received. + * + * Calling this API inside an endpoint handler, with sufficient cleanup_delay, + * will allow the response / acknowledgment to be sent successfully before the + * underlying protocomm service is stopped. + * + * Cleaup_delay is set when calling wifi_prov_mgr_disable_auto_stop(). + * If not specified, it defaults to 1000ms. + * + * For straightforward cases, using this API is usually not necessary as + * provisioning is stopped automatically once WIFI_PROV_CRED_SUCCESS is emitted. + * Stopping is delayed (maximum 30 seconds) thus allowing the client side + * application to query for Wi-Fi state, i.e. after receiving the first query + * and sending `Wi-Fi state connected` response the service is stopped immediately. + */ +void wifi_prov_mgr_stop_provisioning(void); + +/** + * @brief Wait for provisioning service to finish + * + * Calling this API will block until provisioning service is stopped + * i.e. till event WIFI_PROV_END is emitted. + * + * This will not block if provisioning is not started or not initialized. + */ +void wifi_prov_mgr_wait(void); + +/** + * @brief Disable auto stopping of provisioning service upon completion + * + * By default, once provisioning is complete, the provisioning service is automatically + * stopped, and all endpoints (along with those registered by main application) are + * deactivated. + * + * This API is useful in the case when main application wishes to close provisioning service + * only after it receives some protocomm command from the client side app. For example, after + * connecting to Wi-Fi, the device may want to connect to the cloud, and only once that is + * successfully, the device is said to be fully configured. But, then it is upto the main + * application to explicitly call wifi_prov_mgr_stop_provisioning() later when the device is + * fully configured and the provisioning service is no longer required. + * + * @note This must be called before executing wifi_prov_mgr_start_provisioning() + * + * @param[in] cleanup_delay Sets the delay after which the actual cleanup of transport related + * resources is done after a call to wifi_prov_mgr_stop_provisioning() + * returns. Minimum allowed value is 100ms. If not specified, this will + * default to 1000ms. + * + * @return + * - ESP_OK : Success + * - ESP_ERR_INVALID_STATE : Manager not initialized or + * provisioning service already started + */ +esp_err_t wifi_prov_mgr_disable_auto_stop(uint32_t cleanup_delay); + +/** + * @brief Set application version and capabilities in the JSON data returned by + * proto-ver endpoint + * + * This function can be called multiple times, to specify information about the various + * application specific services running on the device, identified by unique labels. + * + * The provisioning service itself registers an entry in the JSON data, by the label "prov", + * containing only provisioning service version and capabilities. Application services should + * use a label other than "prov" so as not to overwrite this. + * + * @note This must be called before executing wifi_prov_mgr_start_provisioning() + * + * @param[in] label String indicating the application name. + * + * @param[in] version String indicating the application version. + * There is no constraint on format. + * + * @param[in] capabilities Array of strings with capabilities. + * These could be used by the client side app to know + * the application registered endpoint capabilities + * + * @param[in] total_capabilities Size of capabilities array + * + * @return + * - ESP_OK : Success + * - ESP_ERR_INVALID_STATE : Manager not initialized or + * provisioning service already started + * - ESP_ERR_NO_MEM : Failed to allocate memory for version string + * - ESP_ERR_INVALID_ARG : Null argument + */ +esp_err_t wifi_prov_mgr_set_app_info(const char *label, const char *version, + const char**capabilities, size_t total_capabilities); + +/** + * @brief Create an additional endpoint and allocate internal resources for it + * + * This API is to be called by the application if it wants to create an additional + * endpoint. All additional endpoints will be assigned UUIDs starting from 0xFF54 + * and so on in the order of execution. + * + * protocomm handler for the created endpoint is to be registered later using + * wifi_prov_mgr_endpoint_register() after provisioning has started. + * + * @note This API can only be called BEFORE provisioning is started + * + * @note Additional endpoints can be used for configuring client provided + * parameters other than Wi-Fi credentials, that are necessary for the + * main application and hence must be set prior to starting the application + * + * @note After session establishment, the additional endpoints must be targeted + * first by the client side application before sending Wi-Fi configuration, + * because once Wi-Fi configuration finishes the provisioning service is + * stopped and hence all endpoints are unregistered + * + * @param[in] ep_name unique name of the endpoint + * + * @return + * - ESP_OK : Success + * - ESP_FAIL : Failure + */ +esp_err_t wifi_prov_mgr_endpoint_create(const char *ep_name); + +/** + * @brief Register a handler for the previously created endpoint + * + * This API can be called by the application to register a protocomm handler + * to any endpoint that was created using wifi_prov_mgr_endpoint_create(). + * + * @note This API can only be called AFTER provisioning has started + * + * @note Additional endpoints can be used for configuring client provided + * parameters other than Wi-Fi credentials, that are necessary for the + * main application and hence must be set prior to starting the application + * + * @note After session establishment, the additional endpoints must be targeted + * first by the client side application before sending Wi-Fi configuration, + * because once Wi-Fi configuration finishes the provisioning service is + * stopped and hence all endpoints are unregistered + * + * @param[in] ep_name Name of the endpoint + * @param[in] handler Endpoint handler function + * @param[in] user_ctx User data + * + * @return + * - ESP_OK : Success + * - ESP_FAIL : Failure + */ +esp_err_t wifi_prov_mgr_endpoint_register(const char *ep_name, + protocomm_req_handler_t handler, + void *user_ctx); + +/** + * @brief Unregister the handler for an endpoint + * + * This API can be called if the application wants to selectively + * unregister the handler of an endpoint while the provisioning + * is still in progress. + * + * All the endpoint handlers are unregistered automatically when + * the provisioning stops. + * + * @param[in] ep_name Name of the endpoint + */ +void wifi_prov_mgr_endpoint_unregister(const char *ep_name); + +/** + * @brief Event handler for provisioning manager + * + * This is called from the main event handler and controls the + * provisioning manager's internal state machine depending on + * incoming Wi-Fi events + * + * @param[in] ctx Event context data + * @param[in] event Event info + * + * @return + * - ESP_OK : Event handled successfully + * - ESP_ERR_FAIL : This event cannot be handled + */ +esp_err_t wifi_prov_mgr_event_handler(void *ctx, system_event_t *event); + +/** + * @brief Get state of Wi-Fi Station during provisioning + * + * @param[out] state Pointer to wifi_prov_sta_state_t + * variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved Wi-Fi state + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t wifi_prov_mgr_get_wifi_state(wifi_prov_sta_state_t *state); + +/** + * @brief Get reason code in case of Wi-Fi station + * disconnection during provisioning + * +* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t +* variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved Wi-Fi disconnect reason + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t wifi_prov_mgr_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t *reason); + +/** + * @brief Runs Wi-Fi as Station with the supplied configuration + * + * Configures the Wi-Fi station mode to connect to the AP with + * SSID and password specified in config structure and sets + * Wi-Fi to run as station. + * + * This is automatically called by provisioning service upon + * receiving new credentials. + * + * If credentials are to be supplied to the manager via a + * different mode other than through protocomm, then this + * API needs to be called. + * + * Event WIFI_PROV_CRED_RECV is emitted after credentials have + * been applied and Wi-Fi station started + * + * @param[in] wifi_cfg Pointer to Wi-Fi configuration structure + * + * @return + * - ESP_OK : Wi-Fi configured and started successfully + * - ESP_FAIL : Failed to set configuration + */ +esp_err_t wifi_prov_mgr_configure_sta(wifi_config_t *wifi_cfg); + +#ifdef __cplusplus +} +#endif diff --git a/components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h b/components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h new file mode 100644 index 0000000000..ed30d81815 --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h @@ -0,0 +1,82 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "wifi_provisioning/manager.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Scheme that can be used by manager for provisioning + * over BLE transport with GATT server + */ +extern const wifi_prov_scheme_t wifi_prov_scheme_ble; + +/* This scheme specific event handler is to be used when application + * doesn't require BT and BLE after provisioning has finished */ +#define WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM { \ + .event_cb = wifi_prov_scheme_ble_event_cb_free_btdm, \ + .user_data = NULL \ +} + +/* This scheme specific event handler is to be used when application + * doesn't require BLE to be active after provisioning has finished */ +#define WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE { \ + .event_cb = wifi_prov_scheme_ble_event_cb_free_ble, \ + .user_data = NULL \ +} + +/* This scheme specific event handler is to be used when application + * doesn't require BT to be active after provisioning has finished */ +#define WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT { \ + .event_cb = wifi_prov_scheme_ble_event_cb_free_bt, \ + .user_data = NULL \ +} + +void wifi_prov_scheme_ble_event_cb_free_btdm(void *user_data, wifi_prov_cb_event_t event, void *event_data); +void wifi_prov_scheme_ble_event_cb_free_ble (void *user_data, wifi_prov_cb_event_t event, void *event_data); +void wifi_prov_scheme_ble_event_cb_free_bt (void *user_data, wifi_prov_cb_event_t event, void *event_data); + +/** + * @brief Set the 128 bit GATT service UUID used for provisioning + * + * This API is used to override the default 128 bit provisioning + * service UUID, which is 0000ffff-0000-1000-8000-00805f9b34fb. + * + * This must be called before starting provisioning, i.e. before + * making a call to wifi_prov_mgr_start_provisioning(), otherwise + * the default UUID will be used. + * + * @note The data being pointed to by the argument must be valid + * atleast till provisioning is started. Upon start, the + * manager will store an internal copy of this UUID, and + * this data can be freed or invalidated afterwords. + * + * @param[in] uuid128 A custom 128 bit UUID + * + * @return + * - ESP_OK : Success + * - ESP_ERR_INVALID_ARG : Null argument + */ +esp_err_t wifi_prov_scheme_ble_set_service_uuid(uint8_t *uuid128); + +#ifdef __cplusplus +} +#endif diff --git a/components/wifi_provisioning/include/wifi_provisioning/scheme_console.h b/components/wifi_provisioning/include/wifi_provisioning/scheme_console.h new file mode 100644 index 0000000000..760fe3243a --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/scheme_console.h @@ -0,0 +1,34 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "wifi_provisioning/manager.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Scheme that can be used by manager for provisioning + * over console (Serial UART) + */ +extern const wifi_prov_scheme_t wifi_prov_scheme_console; + +#ifdef __cplusplus +} +#endif diff --git a/components/wifi_provisioning/include/wifi_provisioning/scheme_softap.h b/components/wifi_provisioning/include/wifi_provisioning/scheme_softap.h new file mode 100644 index 0000000000..043b14e202 --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/scheme_softap.h @@ -0,0 +1,34 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "wifi_provisioning/manager.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Scheme that can be used by manager for provisioning + * over SoftAP transport with HTTP server + */ +extern const wifi_prov_scheme_t wifi_prov_scheme_softap; + +#ifdef __cplusplus +} +#endif diff --git a/components/wifi_provisioning/src/handlers.c b/components/wifi_provisioning/src/handlers.c new file mode 100644 index 0000000000..0a57ef56cc --- /dev/null +++ b/components/wifi_provisioning/src/handlers.c @@ -0,0 +1,146 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include + +#include "wifi_provisioning/wifi_config.h" +#include "wifi_provisioning/manager.h" +#include "wifi_provisioning_priv.h" + +static const char *TAG = "wifi_prov_handlers"; + +/* Provide definition of wifi_prov_ctx_t */ +struct wifi_prov_ctx { + wifi_config_t wifi_cfg; +}; + +static wifi_config_t *get_config(wifi_prov_ctx_t **ctx) +{ + return (*ctx ? & (*ctx)->wifi_cfg : NULL); +} + +static wifi_config_t *new_config(wifi_prov_ctx_t **ctx) +{ + free(*ctx); + (*ctx) = (wifi_prov_ctx_t *) calloc(1, sizeof(wifi_prov_ctx_t)); + return get_config(ctx); +} + +static void free_config(wifi_prov_ctx_t **ctx) +{ + free(*ctx); + *ctx = NULL; +} + +static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data, wifi_prov_ctx_t **ctx) +{ + /* Initialize to zero */ + memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t)); + + if (wifi_prov_mgr_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { + ESP_LOGW(TAG, "Wi-Fi provisioning manager not running"); + return ESP_ERR_INVALID_STATE; + } + + if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) { + ESP_LOGD(TAG, "Got state : connected"); + + /* IP Addr assigned to STA */ + tcpip_adapter_ip_info_t ip_info; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); + char *ip_addr = ip4addr_ntoa(&ip_info.ip); + strcpy(resp_data->conn_info.ip_addr, ip_addr); + + /* AP information to which STA is connected */ + wifi_ap_record_t ap_info; + esp_wifi_sta_get_ap_info(&ap_info); + memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); + memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); + resp_data->conn_info.channel = ap_info.primary; + resp_data->conn_info.auth_mode = ap_info.authmode; + + /* Tell manager to stop provisioning service */ + wifi_prov_mgr_done(); + } else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + ESP_LOGD(TAG, "Got state : disconnected"); + + /* If disconnected, convey reason */ + wifi_prov_mgr_get_wifi_disconnect_reason(&resp_data->fail_reason); + } else { + ESP_LOGD(TAG, "Got state : connecting"); + } + return ESP_OK; +} + +static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data, wifi_prov_ctx_t **ctx) +{ + wifi_config_t *wifi_cfg = get_config(ctx); + if (wifi_cfg) { + free_config(ctx); + } + + wifi_cfg = new_config(ctx); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Unable to allocate Wi-Fi config"); + return ESP_ERR_NO_MEM; + } + + ESP_LOGD(TAG, "Wi-Fi Credentials Received"); + + /* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard). + * But this doesn't guarantee that the saved SSID will be null terminated, because + * wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character) */ + strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid)); + + /* Using strlcpy allows both max passphrase length (63 bytes) and ensures null termination + * because size of wifi_cfg->sta.password is 64 bytes (1 extra byte for null character) */ + strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password)); + return ESP_OK; +} + +static esp_err_t apply_config_handler(wifi_prov_ctx_t **ctx) +{ + wifi_config_t *wifi_cfg = get_config(ctx); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Wi-Fi config not set"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = wifi_prov_mgr_configure_sta(wifi_cfg); + if (ret == ESP_OK) { + ESP_LOGD(TAG, "Wi-Fi Credentials Applied"); + } else { + ESP_LOGE(TAG, "Failed to apply Wi-Fi Credentials"); + } + + free_config(ctx); + return ret; +} + +wifi_prov_config_handlers_t get_wifi_prov_handlers(void) +{ + wifi_prov_config_handlers_t wifi_prov_handlers = { + .get_status_handler = get_status_handler, + .set_config_handler = set_config_handler, + .apply_config_handler = apply_config_handler, + .ctx = NULL + }; + return wifi_prov_handlers; +} diff --git a/components/wifi_provisioning/src/manager.c b/components/wifi_provisioning/src/manager.c new file mode 100644 index 0000000000..5f772a7dff --- /dev/null +++ b/components/wifi_provisioning/src/manager.c @@ -0,0 +1,1117 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "wifi_provisioning_priv.h" + +#define WIFI_PROV_MGR_VERSION "v1.0" + +static const char *TAG = "wifi_prov_mgr"; + +typedef enum { + WIFI_PROV_STATE_IDLE, + WIFI_PROV_STATE_STARTING, + WIFI_PROV_STATE_STARTED, + WIFI_PROV_STATE_CRED_RECV, + WIFI_PROV_STATE_FAIL, + WIFI_PROV_STATE_SUCCESS, + WIFI_PROV_STATE_STOPPING +} wifi_prov_mgr_state_t; + +/** + * @brief Structure for storing capabilities supported by + * the provisioning service + */ +struct wifi_prov_capabilities { + /* Proof of Possession is not required for establishing session */ + bool no_pop; + + /* Provisioning doesn't stop on it's own after receiving Wi-Fi credentials + * instead application has to explicitly call wifi_prov_mgr_stop_provisioning() */ + bool no_auto_stop; +}; + +/** + * @brief Structure for storing miscellaneous information about + * provisioning service that will be conveyed to clients + */ +struct wifi_prov_info { + const char *version; + struct wifi_prov_capabilities capabilities; +}; + +/** + * @brief Context data for provisioning manager + */ +struct wifi_prov_mgr_ctx { + /* Provisioning manager configuration */ + wifi_prov_mgr_config_t mgr_config; + + /* State of the provisioning service */ + wifi_prov_mgr_state_t prov_state; + + /* Provisioning scheme configuration */ + void *prov_scheme_config; + + /* Protocomm handle */ + protocomm_t *pc; + + /* Type of security to use with protocomm */ + int security; + + /* Pointer to proof of possession */ + protocomm_security_pop_t pop; + + /* Handle to timer */ + esp_timer_handle_t timer; + + /* State of Wi-Fi Station */ + wifi_prov_sta_state_t wifi_state; + + /* Code for Wi-Fi station disconnection (if disconnected) */ + wifi_prov_sta_fail_reason_t wifi_disconnect_reason; + + /* Protocomm handlers for Wi-Fi configuration endpoint */ + wifi_prov_config_handlers_t *wifi_prov_handlers; + + /* Count of used endpoint UUIDs */ + unsigned int endpoint_uuid_used; + + /* Provisioning service information */ + struct wifi_prov_info mgr_info; + + /* Application related information in JSON format */ + cJSON *app_info_json; + + /* Delay after which resources will be cleaned up asynchronously + * upon execution of wifi_prov_mgr_stop_provisioning() */ + uint32_t cleanup_delay; +}; + +/* Mutex to lock/unlock access to provisioning singleton + * context data. This is allocated only once on first init + * and never deleted as wifi_prov_mgr is a singleton */ +static SemaphoreHandle_t prov_ctx_lock = NULL; + +/* Pointer to provisioning context data */ +static struct wifi_prov_mgr_ctx *prov_ctx; + +/* This executes registered app_event_callback for a particular event + * + * NOTE : By the time this fucntion returns, it is possible that + * the manager got de-initialized due to a call to wifi_prov_mgr_deinit() + * either inside the event callbacks or from another thread. Therefore + * post execution of execute_event_cb(), the validity of prov_ctx must + * always be checked. A cleaner way, to avoid this pitfall safely, would + * be to limit the usage of this function to only public APIs, and that + * too at the very end, just before returning. + * + * NOTE: This function should be called only after ensuring that the + * context is valid and the control mutex is locked. */ +static void execute_event_cb(wifi_prov_cb_event_t event_id, void *event_data) +{ + ESP_LOGD(TAG, "execute_event_cb : %d", event_id); + + if (prov_ctx) { + wifi_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb; + void *app_data = prov_ctx->mgr_config.app_event_handler.user_data; + + wifi_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb; + void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data; + + /* Release the mutex before executing the callbacks. This is done so that + * wifi_prov_mgr_event_handler() doesn't stay blocked for the duration */ + xSemaphoreGiveRecursive(prov_ctx_lock); + + if (scheme_cb) { + /* Call scheme specific event handler */ + scheme_cb(scheme_data, event_id, event_data); + } + + if (app_cb) { + /* Call application specific event handler */ + app_cb(app_data, event_id, event_data); + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + } +} + +esp_err_t wifi_prov_mgr_set_app_info(const char *label, const char *version, + const char**capabilities, size_t total_capabilities) +{ + if (!label || !version || !capabilities) { + return ESP_ERR_INVALID_ARG; + } + + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = ESP_FAIL; + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + + if (prov_ctx && prov_ctx->prov_state == WIFI_PROV_STATE_IDLE) { + if (!prov_ctx->app_info_json) { + prov_ctx->app_info_json = cJSON_CreateObject(); + } + + cJSON *new_entry_json = cJSON_CreateObject(); + cJSON *capabilities_json = cJSON_CreateArray(); + cJSON_AddItemToObject(prov_ctx->app_info_json, label, new_entry_json); + + /* Version ("ver") */ + cJSON_AddStringToObject(new_entry_json, "ver", version); + + /* List of capabilities ("cap") */ + cJSON_AddItemToObject(new_entry_json, "cap", capabilities_json); + for (unsigned int i = 0; i < total_capabilities; i++) { + if (capabilities[i]) { + cJSON_AddItemToArray(capabilities_json, cJSON_CreateString(capabilities[i])); + } + } + + } else { + ret = ESP_ERR_INVALID_STATE; + } + + xSemaphoreGiveRecursive(prov_ctx_lock); + return ret; +} + +static cJSON* wifi_prov_get_info_json(void) +{ + cJSON *full_info_json = prov_ctx->app_info_json ? + cJSON_Duplicate(prov_ctx->app_info_json, 1) : cJSON_CreateObject(); + cJSON *prov_info_json = cJSON_CreateObject(); + cJSON *prov_capabilities = cJSON_CreateArray(); + + /* Use label "prov" to indicate provisioning related information */ + cJSON_AddItemToObject(full_info_json, "prov", prov_info_json); + + /* Version field */ + cJSON_AddStringToObject(prov_info_json, "ver", prov_ctx->mgr_info.version); + + /* Capabilities field */ + cJSON_AddItemToObject(prov_info_json, "cap", prov_capabilities); + + /* If Proof of Possession is not used, indicate in capabilities */ + if (prov_ctx->mgr_info.capabilities.no_pop) { + cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("no_pop")); + } + return full_info_json; +} + +static esp_err_t wifi_prov_mgr_start_service(const char *service_name, const char *service_key) +{ + const wifi_prov_scheme_t *scheme = &prov_ctx->mgr_config.scheme; + esp_err_t ret; + + /* Create new protocomm instance */ + prov_ctx->pc = protocomm_new(); + if (prov_ctx->pc == NULL) { + ESP_LOGE(TAG, "Failed to create new protocomm instance"); + return ESP_FAIL; + } + + ret = scheme->set_config_service(prov_ctx->prov_scheme_config, service_name, service_key); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure service"); + protocomm_delete(prov_ctx->pc); + return ret; + } + + /* Start provisioning */ + ret = scheme->prov_start(prov_ctx->pc, prov_ctx->prov_scheme_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to start service"); + protocomm_delete(prov_ctx->pc); + return ret; + } + + /* Set version information / capabilities of provisioning service and application */ + cJSON *version_json = wifi_prov_get_info_json(); + ret = protocomm_set_version(prov_ctx->pc, "proto-ver", cJSON_Print(version_json)); + cJSON_Delete(version_json); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set version endpoint"); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + return ret; + } + + /* Set protocomm security type for endpoint */ + if (prov_ctx->security == 0) { + ret = protocomm_set_security(prov_ctx->pc, "prov-session", + &protocomm_security0, NULL); + } else if (prov_ctx->security == 1) { + ret = protocomm_set_security(prov_ctx->pc, "prov-session", + &protocomm_security1, &prov_ctx->pop); + } else { + ESP_LOGE(TAG, "Unsupported protocomm security version %d", prov_ctx->security); + ret = ESP_ERR_INVALID_ARG; + } + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set security endpoint"); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + return ret; + } + + prov_ctx->wifi_prov_handlers = malloc(sizeof(wifi_prov_config_handlers_t)); + if (!prov_ctx->wifi_prov_handlers) { + ESP_LOGD(TAG, "Failed to allocate memory for provisioning handlers"); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + } + *prov_ctx->wifi_prov_handlers = get_wifi_prov_handlers(); + + /* Add protocomm endpoint for Wi-Fi station configuration */ + ret = protocomm_add_endpoint(prov_ctx->pc, "prov-config", + wifi_prov_config_data_handler, + prov_ctx->wifi_prov_handlers); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set provisioning endpoint"); + free(prov_ctx->wifi_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + return ret; + } + + ESP_LOGI(TAG, "Provisioning started with service name : %s ", + service_name ? service_name : ""); + return ESP_OK; +} + +esp_err_t wifi_prov_mgr_endpoint_create(const char *ep_name) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t err = ESP_FAIL; + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (prov_ctx && + prov_ctx->prov_state == WIFI_PROV_STATE_IDLE) { + err = prov_ctx->mgr_config.scheme.set_config_endpoint( + prov_ctx->prov_scheme_config, ep_name, + prov_ctx->endpoint_uuid_used + 1); + } + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to create additional endpoint"); + } else { + prov_ctx->endpoint_uuid_used++; + } + xSemaphoreGiveRecursive(prov_ctx_lock); + return err; +} + +esp_err_t wifi_prov_mgr_endpoint_register(const char *ep_name, protocomm_req_handler_t handler, void *user_ctx) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t err = ESP_FAIL; + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (prov_ctx && + prov_ctx->prov_state > WIFI_PROV_STATE_STARTING && + prov_ctx->prov_state < WIFI_PROV_STATE_STOPPING) { + err = protocomm_add_endpoint(prov_ctx->pc, ep_name, handler, user_ctx); + } + xSemaphoreGiveRecursive(prov_ctx_lock); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register handler for endpoint"); + } + return err; +} + +void wifi_prov_mgr_endpoint_unregister(const char *ep_name) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return; + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (prov_ctx && + prov_ctx->prov_state > WIFI_PROV_STATE_STARTING && + prov_ctx->prov_state < WIFI_PROV_STATE_STOPPING) { + protocomm_remove_endpoint(prov_ctx->pc, ep_name); + } + xSemaphoreGiveRecursive(prov_ctx_lock); +} + +static void prov_stop_task(void *arg) +{ + bool is_this_a_task = (bool) arg; + + wifi_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb; + void *app_data = prov_ctx->mgr_config.app_event_handler.user_data; + + wifi_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb; + void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data; + + /* This delay is so that the client side app is notified first + * and then the provisioning is stopped. Generally 1000ms is enough. */ + uint32_t cleanup_delay = prov_ctx->cleanup_delay > 100 ? prov_ctx->cleanup_delay : 100; + vTaskDelay(cleanup_delay / portTICK_PERIOD_MS); + + /* All the extra application added endpoints are also + * removed automatically when prov_stop is called */ + prov_ctx->mgr_config.scheme.prov_stop(prov_ctx->pc); + + /* Delete protocomm instance */ + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + + /* Free provisioning handlers */ + free(prov_ctx->wifi_prov_handlers->ctx); + free(prov_ctx->wifi_prov_handlers); + prov_ctx->wifi_prov_handlers = NULL; + + /* Switch device to Wi-Fi STA mode irrespective of + * whether provisioning was completed or not */ + esp_wifi_set_mode(WIFI_MODE_STA); + ESP_LOGI(TAG, "Provisioning stopped"); + + if (is_this_a_task) { + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + prov_ctx->prov_state = WIFI_PROV_STATE_IDLE; + xSemaphoreGiveRecursive(prov_ctx_lock); + + ESP_LOGD(TAG, "execute_event_cb : %d", WIFI_PROV_END); + if (scheme_cb) { + scheme_cb(scheme_data, WIFI_PROV_END, NULL); + } + if (app_cb) { + app_cb(app_data, WIFI_PROV_END, NULL); + } + vTaskDelete(NULL); + } +} + +/* This will do one of these: + * 1) if blocking is false, start a task for stopping the provisioning service (returns true) + * 2) if blocking is true, stop provisioning service immediately (returns true) + * 3) if service was already in the process of termination, in blocking mode this will + * wait till the service is stopped (returns false) + * 4) if service was not running, this will return immediately (returns false) + * + * NOTE: This function should be called only after ensuring that the context + * is valid and the control mutex is locked + * + * NOTE: When blocking mode is selected, the event callbacks are not executed. + * This help with de-initialization. + */ +static bool wifi_prov_mgr_stop_service(bool blocking) +{ + if (blocking) { + /* Wait for any ongoing calls to wifi_prov_mgr_start_service() or + * wifi_prov_mgr_stop_service() from another thread to finish */ + while (prov_ctx && ( + prov_ctx->prov_state == WIFI_PROV_STATE_STARTING || + prov_ctx->prov_state == WIFI_PROV_STATE_STOPPING)) { + xSemaphoreGiveRecursive(prov_ctx_lock); + vTaskDelay(100 / portTICK_PERIOD_MS); + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + } + } else { + /* Wait for any ongoing call to wifi_prov_mgr_start_service() + * from another thread to finish */ + while (prov_ctx && + prov_ctx->prov_state == WIFI_PROV_STATE_STARTING) { + xSemaphoreGiveRecursive(prov_ctx_lock); + vTaskDelay(100 / portTICK_PERIOD_MS); + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + } + + if (prov_ctx && prov_ctx->prov_state == WIFI_PROV_STATE_STOPPING) { + ESP_LOGD(TAG, "Provisioning is already stopping"); + return false; + } + } + + if (!prov_ctx || prov_ctx->prov_state == WIFI_PROV_STATE_IDLE) { + ESP_LOGD(TAG, "Provisioning not running"); + return false; + } + + /* Timer not needed anymore */ + if (prov_ctx->timer) { + esp_timer_stop(prov_ctx->timer); + esp_timer_delete(prov_ctx->timer); + prov_ctx->timer = NULL; + } + + ESP_LOGD(TAG, "Stopping provisioning"); + prov_ctx->prov_state = WIFI_PROV_STATE_STOPPING; + + /* Free proof of possession */ + if (prov_ctx->pop.data) { + free((void *)prov_ctx->pop.data); + prov_ctx->pop.data = NULL; + } + + if (blocking) { + /* Run the cleanup without launching a separate task. Also the + * WIFI_PROV_END event is not emitted in this case */ + xSemaphoreGiveRecursive(prov_ctx_lock); + prov_stop_task((void *)0); + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + prov_ctx->prov_state = WIFI_PROV_STATE_IDLE; + } else { + /* Launch cleanup task to perform the cleanup asynchronously. + * It is important to do this asynchronously because, there are + * situations in which the transport level resources have to be + * released - some duration after - returning from a call to + * wifi_prov_mgr_stop_provisioning(), like when it is called + * inside a protocomm handler */ + assert(xTaskCreate(prov_stop_task, "prov_stop_task", 4096, (void *)1, + tskIDLE_PRIORITY, NULL) == pdPASS); + ESP_LOGD(TAG, "Provisioning scheduled for stopping"); + } + return true; +} + +/* Task spawned by timer callback */ +static void stop_prov_timer_cb(void *arg) +{ + wifi_prov_mgr_stop_provisioning(); +} + +esp_err_t wifi_prov_mgr_disable_auto_stop(uint32_t cleanup_delay) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = ESP_FAIL; + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + + if (prov_ctx && prov_ctx->prov_state == WIFI_PROV_STATE_IDLE) { + prov_ctx->mgr_info.capabilities.no_auto_stop = true; + prov_ctx->cleanup_delay = cleanup_delay; + } else { + ret = ESP_ERR_INVALID_STATE; + } + + xSemaphoreGiveRecursive(prov_ctx_lock); + return ret; +} + +/* Call this if provisioning is completed before the timeout occurs */ +esp_err_t wifi_prov_mgr_done(void) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + /* Stop provisioning if auto stop is not disabled */ + if (prov_ctx && !prov_ctx->mgr_info.capabilities.no_auto_stop) { + wifi_prov_mgr_stop_provisioning(); + } + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_OK; +} + +/* Event handler for starting/stopping provisioning. + * To be called from within the context of the main + * event handler */ +esp_err_t wifi_prov_mgr_event_handler(void *ctx, system_event_t *event) +{ + /* For accessing reason codes in case of disconnection */ + system_event_info_t *info = &event->event_info; + + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + + /* If pointer to provisioning application data is NULL + * then provisioning manager is not running, therefore + * return with error to allow the global handler to act */ + if (!prov_ctx) { + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_ERR_INVALID_STATE; + } + + /* Only handle events when credential is received and + * Wi-Fi STA is yet to complete trying the connection */ + if (prov_ctx->prov_state != WIFI_PROV_STATE_CRED_RECV) { + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_OK; + } + + esp_err_t ret = ESP_OK; + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGD(TAG, "STA Start"); + /* Once configuration is received through protocomm, + * device is started as station. Once station starts, + * wait for connection to establish with configured + * host SSID and password */ + prov_ctx->wifi_state = WIFI_PROV_STA_CONNECTING; + break; + + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGD(TAG, "STA Got IP"); + /* Station got IP. That means configuration is successful. */ + prov_ctx->wifi_state = WIFI_PROV_STA_CONNECTED; + prov_ctx->prov_state = WIFI_PROV_STATE_SUCCESS; + + /* If auto stop is enabled (default), schedule timer to + * stop provisioning app after 30 seconds. */ + if (!prov_ctx->mgr_info.capabilities.no_auto_stop) { + ESP_LOGD(TAG, "Starting 30s timer for stop_prov_timer_cb()"); + esp_timer_start_once(prov_ctx->timer, 30000 * 1000U); + } + + /* Execute user registered callback handler */ + execute_event_cb(WIFI_PROV_CRED_SUCCESS, NULL); + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + ESP_LOGD(TAG, "STA Disconnected"); + /* Station couldn't connect to configured host SSID */ + prov_ctx->wifi_state = WIFI_PROV_STA_DISCONNECTED; + ESP_LOGD(TAG, "Disconnect reason : %d", info->disconnected.reason); + + /* Set code corresponding to the reason for disconnection */ + switch (info->disconnected.reason) { + case WIFI_REASON_AUTH_EXPIRE: + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + case WIFI_REASON_BEACON_TIMEOUT: + case WIFI_REASON_AUTH_FAIL: + case WIFI_REASON_ASSOC_FAIL: + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGD(TAG, "STA Auth Error"); + prov_ctx->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR; + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGD(TAG, "STA AP Not found"); + prov_ctx->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND; + break; + default: + /* If none of the expected reasons, + * retry connecting to host SSID */ + prov_ctx->wifi_state = WIFI_PROV_STA_CONNECTING; + esp_wifi_connect(); + } + + /* In case of disconnection, update state of service and + * run the event handler with disconnection reason as data */ + if (prov_ctx->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + prov_ctx->prov_state = WIFI_PROV_STATE_FAIL; + wifi_prov_sta_fail_reason_t reason = prov_ctx->wifi_disconnect_reason; + /* Execute user registered callback handler */ + execute_event_cb(WIFI_PROV_CRED_FAIL, (void *)&reason); + } + break; + + default: + /* This event is not intended to be handled by this handler. + * Return ESP_FAIL to signal global event handler to take + * control */ + ret = ESP_FAIL; + break; + } + + xSemaphoreGiveRecursive(prov_ctx_lock); + return ret; +} + +esp_err_t wifi_prov_mgr_get_wifi_state(wifi_prov_sta_state_t *state) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (prov_ctx == NULL || state == NULL) { + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + + *state = prov_ctx->wifi_state; + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_OK; +} + +esp_err_t wifi_prov_mgr_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t *reason) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (prov_ctx == NULL || reason == NULL) { + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + + if (prov_ctx->wifi_state != WIFI_PROV_STA_DISCONNECTED) { + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + + *reason = prov_ctx->wifi_disconnect_reason; + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_OK; +} + +static void debug_print_wifi_credentials(wifi_sta_config_t sta, const char* pretext) +{ + size_t passlen = strlen((const char*) sta.password); + ESP_LOGD(TAG, "%s Wi-Fi SSID : %.*s", pretext, + strnlen((const char *) sta.ssid, sizeof(sta.ssid)), (const char *) sta.ssid); + + if (passlen) { + /* Mask password partially if longer than 3, else mask it completely */ + memset(sta.password + (passlen > 3), '*', passlen - 2*(passlen > 3)); + ESP_LOGD(TAG, "%s Wi-Fi Password : %s", pretext, (const char *) sta.password); + } +} + +esp_err_t wifi_prov_mgr_is_provisioned(bool *provisioned) +{ + if (!provisioned) { + return ESP_ERR_INVALID_ARG; + } + + *provisioned = false; + + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + /* Get Wi-Fi Station configuration */ + wifi_config_t wifi_cfg; + if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) { + return ESP_FAIL; + } + + if (strlen((const char *) wifi_cfg.sta.ssid)) { + *provisioned = true; + debug_print_wifi_credentials(wifi_cfg.sta, "Found"); + } + return ESP_OK; +} + +esp_err_t wifi_prov_mgr_configure_sta(wifi_config_t *wifi_cfg) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (!prov_ctx) { + ESP_LOGE(TAG, "Invalid state of Provisioning app"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + if (prov_ctx->prov_state >= WIFI_PROV_STATE_CRED_RECV) { + ESP_LOGE(TAG, "Wi-Fi credentials already received by provisioning app"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + debug_print_wifi_credentials(wifi_cfg->sta, "Received"); + + /* Configure Wi-Fi as both AP and/or Station */ + if (esp_wifi_set_mode(prov_ctx->mgr_config.scheme.wifi_mode) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi mode"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + + /* Don't release mutex yet as it is possible that right after + * esp_wifi_connect() is called below, the related Wi-Fi event + * happens even before manager state is updated in the next + * few lines causing the internal event handler to miss */ + + /* Set Wi-Fi storage again to flash to keep the newly + * provided credentials on NVS */ + if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set storage Wi-Fi"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + /* Configure Wi-Fi station with host credentials + * provided during provisioning */ + if (esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_cfg) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi configuration"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + /* (Re)Start Wi-Fi */ + if (esp_wifi_start() != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi configuration"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + /* Connect to AP */ + if (esp_wifi_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect Wi-Fi"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_FAIL; + } + /* This delay allows channel change to complete */ + vTaskDelay(1000 / portTICK_PERIOD_MS); + + /* Reset Wi-Fi station state for provisioning app */ + prov_ctx->wifi_state = WIFI_PROV_STA_CONNECTING; + prov_ctx->prov_state = WIFI_PROV_STATE_CRED_RECV; + /* Execute user registered callback handler */ + execute_event_cb(WIFI_PROV_CRED_RECV, (void *)&wifi_cfg->sta); + xSemaphoreGiveRecursive(prov_ctx_lock); + + return ESP_OK; +} + +esp_err_t wifi_prov_mgr_init(wifi_prov_mgr_config_t config) +{ + if (!prov_ctx_lock) { + /* Create mutex if this is the first time init is being called. + * This is created only once and never deleted because if some + * other thread is trying to take this mutex while it is being + * deleted from another thread then the reference may become + * invalid and cause exception */ + prov_ctx_lock = xSemaphoreCreateRecursiveMutex(); + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Failed to create mutex"); + return ESP_ERR_NO_MEM; + } + } + + void *fn_ptrs[] = { + config.scheme.prov_stop, + config.scheme.prov_start, + config.scheme.new_config, + config.scheme.delete_config, + config.scheme.set_config_service, + config.scheme.set_config_endpoint + }; + + /* All function pointers in the scheme structure must be non-null */ + for (int i = 0; i < sizeof(fn_ptrs)/sizeof(fn_ptrs[0]); i++) { + if (!fn_ptrs[i]) { + return ESP_ERR_INVALID_ARG; + } + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (prov_ctx) { + ESP_LOGE(TAG, "Provisioning manager already initialized"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_ERR_INVALID_STATE; + } + + /* Allocate memory for provisioning app data */ + prov_ctx = (struct wifi_prov_mgr_ctx *) calloc(1, sizeof(struct wifi_prov_mgr_ctx)); + if (!prov_ctx) { + ESP_LOGE(TAG, "Error allocating memory for singleton instance"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_ERR_NO_MEM; + } + + prov_ctx->mgr_config = config; + prov_ctx->prov_state = WIFI_PROV_STATE_IDLE; + prov_ctx->mgr_info.version = WIFI_PROV_MGR_VERSION; + + /* Allocate memory for provisioning scheme configuration */ + const wifi_prov_scheme_t *scheme = &prov_ctx->mgr_config.scheme; + esp_err_t ret = ESP_OK; + prov_ctx->prov_scheme_config = scheme->new_config(); + if (!prov_ctx->prov_scheme_config) { + ESP_LOGE(TAG, "failed to allocate provisioning scheme configuration"); + ret = ESP_ERR_NO_MEM; + goto exit; + } + + ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-session", 0xFF51); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "failed to configure security endpoint"); + goto exit; + } + + ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-config", 0xFF52); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "failed to configure Wi-Fi configuration endpoint"); + goto exit; + } + + ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "proto-ver", 0xFF53); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "failed to configure version endpoint"); + goto exit; + } + + /* Application specific custom endpoints will be assigned + * incremental UUIDs starting after this value */ + prov_ctx->endpoint_uuid_used = 0xFF53; + + /* This delay is so that the client side app is notified first + * and then the provisioning is stopped. Default is 1000ms. */ + prov_ctx->cleanup_delay = 1000; + +exit: + if (ret != ESP_OK) { + if (prov_ctx->prov_scheme_config) { + config.scheme.delete_config(prov_ctx->prov_scheme_config); + } + free(prov_ctx); + } else { + execute_event_cb(WIFI_PROV_INIT, NULL); + } + xSemaphoreGiveRecursive(prov_ctx_lock); + return ret; +} + +void wifi_prov_mgr_wait(void) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return; + } + + while (1) { + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (prov_ctx && + prov_ctx->prov_state != WIFI_PROV_STATE_IDLE) { + xSemaphoreGiveRecursive(prov_ctx_lock); + vTaskDelay(1000 / portTICK_PERIOD_MS); + continue; + } + break; + } + xSemaphoreGiveRecursive(prov_ctx_lock); +} + +void wifi_prov_mgr_deinit(void) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return; + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + + /* This will do one of these: + * 1) if found running, stop the provisioning service (returns true) + * 2) if service was already in the process of termination, this will + * wait till the service is stopped (returns false) + * 3) if service was not running, this will return immediately (returns false) + */ + bool service_was_running = wifi_prov_mgr_stop_service(1); + + /* If service was not running, its also possible that manager + * was not even initialized */ + if (!service_was_running && !prov_ctx) { + ESP_LOGD(TAG, "Manager already de-initialized"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return; + } + + if (prov_ctx->app_info_json) { + cJSON_Delete(prov_ctx->app_info_json); + } + + if (prov_ctx->prov_scheme_config) { + prov_ctx->mgr_config.scheme.delete_config(prov_ctx->prov_scheme_config); + } + + /* Extract the callbacks to be called post deinit */ + wifi_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb; + void *app_data = prov_ctx->mgr_config.app_event_handler.user_data; + + wifi_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb; + void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data; + + /* Free manager context */ + free(prov_ctx); + prov_ctx = NULL; + xSemaphoreGiveRecursive(prov_ctx_lock); + + /* If a running service was also stopped during de-initialization + * then WIFI_PROV_END event also needs to be emitted before deinit */ + if (service_was_running) { + ESP_LOGD(TAG, "execute_event_cb : %d", WIFI_PROV_END); + if (scheme_cb) { + scheme_cb(scheme_data, WIFI_PROV_END, NULL); + } + if (app_cb) { + app_cb(app_data, WIFI_PROV_END, NULL); + } + } + + ESP_LOGD(TAG, "execute_event_cb : %d", WIFI_PROV_DEINIT); + + /* Execute deinit event callbacks */ + if (scheme_cb) { + scheme_cb(scheme_data, WIFI_PROV_DEINIT, NULL); + } + if (app_cb) { + app_cb(app_data, WIFI_PROV_DEINIT, NULL); + } +} + +esp_err_t wifi_prov_mgr_start_provisioning(wifi_prov_security_t security, const char *pop, + const char *service_name, const char *service_key) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (!prov_ctx) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_ERR_INVALID_STATE; + } + + if (prov_ctx->prov_state != WIFI_PROV_STATE_IDLE) { + ESP_LOGE(TAG, "Provisioning service already started"); + xSemaphoreGiveRecursive(prov_ctx_lock); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = ESP_OK; + /* Update state so that parallel call to wifi_prov_mgr_start_provisioning() + * or wifi_prov_mgr_stop_provisioning() or wifi_prov_mgr_deinit() from another + * thread doesn't interfere with this process */ + prov_ctx->prov_state = WIFI_PROV_STATE_STARTING; + + /* Change Wi-Fi storage to RAM temporarily and erase any old + * credentials (i.e. without erasing the copy on NVS). Also + * call disconnect to make sure device doesn't remain connected + * to the AP whose credentials were present earlier */ + wifi_config_t wifi_cfg_empty, wifi_cfg_old; + memset(&wifi_cfg_empty, 0, sizeof(wifi_config_t)); + esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg_old); + esp_wifi_set_storage(WIFI_STORAGE_RAM); + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_cfg_empty); + esp_wifi_disconnect(); + + /* Initialize app data */ + if (pop) { + prov_ctx->pop.len = strlen(pop); + prov_ctx->pop.data = malloc(prov_ctx->pop.len); + if (!prov_ctx->pop.data) { + ESP_LOGE(TAG, "Unable to allocate PoP data"); + ret = ESP_ERR_NO_MEM; + goto err; + } + memcpy((void *)prov_ctx->pop.data, pop, prov_ctx->pop.len); + } else { + prov_ctx->mgr_info.capabilities.no_pop = true; + } + prov_ctx->security = security; + + /* If auto stop on completion is enabled (default) create the stopping timer */ + if (!prov_ctx->mgr_info.capabilities.no_auto_stop) { + /* Create timer object as a member of app data */ + esp_timer_create_args_t timer_conf = { + .callback = stop_prov_timer_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "wifi_prov_mgr_tm" + }; + ret = esp_timer_create(&timer_conf, &prov_ctx->timer); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to create timer"); + free((void *)prov_ctx->pop.data); + goto err; + } + } + + /* System APIs for BLE / Wi-Fi will be called inside wifi_prov_mgr_start_service(), + * which may trigger system level events. Hence, releasing the context lock will + * ensure that wifi_prov_mgr_event_handler() doesn't block the global event_loop + * handler when system events need to be handled */ + xSemaphoreGiveRecursive(prov_ctx_lock); + + /* Start provisioning service */ + ret = wifi_prov_mgr_start_service(service_name, service_key); + if (ret != ESP_OK) { + esp_timer_delete(prov_ctx->timer); + free((void *)prov_ctx->pop.data); + } + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + if (ret == ESP_OK) { + prov_ctx->prov_state = WIFI_PROV_STATE_STARTED; + /* Execute user registered callback handler */ + execute_event_cb(WIFI_PROV_START, NULL); + goto exit; + } + +err: + prov_ctx->prov_state = WIFI_PROV_STATE_IDLE; + esp_wifi_set_storage(WIFI_STORAGE_FLASH); + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_cfg_old); + +exit: + xSemaphoreGiveRecursive(prov_ctx_lock); + return ret; +} + +void wifi_prov_mgr_stop_provisioning(void) +{ + if (!prov_ctx_lock) { + ESP_LOGE(TAG, "Provisioning manager not initialized"); + return; + } + + xSemaphoreTakeRecursive(prov_ctx_lock, portMAX_DELAY); + + /* Launches task for stopping the provisioning service. This will do one of these: + * 1) start a task for stopping the provisioning service (returns true) + * 2) if service was already in the process of termination, this will + * wait till the service is stopped (returns false) + * 3) if service was not running, this will return immediately (returns false) + */ + wifi_prov_mgr_stop_service(0); + + xSemaphoreGiveRecursive(prov_ctx_lock); +} diff --git a/components/wifi_provisioning/src/scheme_ble.c b/components/wifi_provisioning/src/scheme_ble.c new file mode 100644 index 0000000000..add9084016 --- /dev/null +++ b/components/wifi_provisioning/src/scheme_ble.c @@ -0,0 +1,232 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include + +#include "wifi_provisioning/scheme_ble.h" +#include "wifi_provisioning_priv.h" + +static const char *TAG = "wifi_prov_scheme_ble"; + +extern const wifi_prov_scheme_t wifi_prov_scheme_ble; + +static uint8_t *custom_service_uuid; + +static esp_err_t prov_start(protocomm_t *pc, void *config) +{ + if (!pc) { + ESP_LOGE(TAG, "Protocomm handle cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + if (!config) { + ESP_LOGE(TAG, "Cannot start with null configuration"); + return ESP_ERR_INVALID_ARG; + } + + protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; + + /* Start protocomm as BLE service */ + if (protocomm_ble_start(pc, ble_config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm BLE service"); + return ESP_FAIL; + } + return ESP_OK; +} + +esp_err_t wifi_prov_scheme_ble_set_service_uuid(uint8_t *uuid128) +{ + if (!uuid128) { + return ESP_ERR_INVALID_ARG; + } + custom_service_uuid = uuid128; + return ESP_OK; +} + +static void *new_config(void) +{ + protocomm_ble_config_t *ble_config = calloc(1, sizeof(protocomm_ble_config_t)); + if (!ble_config) { + ESP_LOGE(TAG, "Error allocating memory for new configuration"); + return NULL; + } + + /* The default provisioning service UUID */ + const uint8_t service_uuid[16] = { + /* LSB <--------------------------------------- + * ---------------------------------------> MSB */ + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + }; + + memcpy(ble_config->service_uuid, service_uuid, sizeof(ble_config->service_uuid)); + return ble_config; +} + +static void delete_config(void *config) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot delete null configuration"); + return; + } + + protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; + for (unsigned int i = 0; i < ble_config->nu_lookup_count; i++) { + free((void *)ble_config->nu_lookup[i].name); + } + free(ble_config->nu_lookup); + free(ble_config); +} + +static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot set null configuration"); + return ESP_ERR_INVALID_ARG; + } + + if (!service_name) { + ESP_LOGE(TAG, "Service name cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + + protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; + strlcpy(ble_config->device_name, service_name, sizeof(ble_config->device_name)); + + /* If a custom service UUID has been provided, override the default one */ + if (custom_service_uuid) { + memcpy(ble_config->service_uuid, custom_service_uuid, sizeof(ble_config->service_uuid)); + } + return ESP_OK; +} + +static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot set null configuration"); + return ESP_ERR_INVALID_ARG; + } + + if (!endpoint_name) { + ESP_LOGE(TAG, "EP name cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; + + char *copy_ep_name = strdup(endpoint_name); + if (!copy_ep_name) { + ESP_LOGE(TAG, "Error allocating memory for EP name"); + return ESP_ERR_NO_MEM; + } + + protocomm_ble_name_uuid_t *lookup_table = ( + realloc(ble_config->nu_lookup, (ble_config->nu_lookup_count + 1) * sizeof(protocomm_ble_name_uuid_t))); + if (!lookup_table) { + ESP_LOGE(TAG, "Error allocating memory for EP-UUID lookup table"); + return ESP_ERR_NO_MEM; + } + + lookup_table[ble_config->nu_lookup_count].name = copy_ep_name; + lookup_table[ble_config->nu_lookup_count].uuid = uuid; + ble_config->nu_lookup = lookup_table; + ble_config->nu_lookup_count += 1; + return ESP_OK; +} + +/* Used when both BT and BLE are not needed by application */ +void wifi_prov_scheme_ble_event_cb_free_btdm(void *user_data, wifi_prov_cb_event_t event, void *event_data) +{ + esp_err_t err; + switch (event) { + case WIFI_PROV_INIT: + /* Release BT memory, as we need only BLE */ + err = esp_bt_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bt_mem_release of classic BT failed %d", err); + } else { + ESP_LOGI(TAG, "BT memory released"); + } + break; + + case WIFI_PROV_DEINIT: + /* Release memory used by BLE and Bluedroid host stack */ + err = esp_bt_mem_release(ESP_BT_MODE_BTDM); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bt_mem_release of BTDM failed %d", err); + } else { + ESP_LOGI(TAG, "BTDM memory released"); + } + break; + + default: + break; + } +} + +/* Used when BT is not needed by application */ +void wifi_prov_scheme_ble_event_cb_free_bt(void *user_data, wifi_prov_cb_event_t event, void *event_data) +{ + esp_err_t err; + switch (event) { + case WIFI_PROV_INIT: + /* Release BT memory, as we need only BLE */ + err = esp_bt_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bt_mem_release of classic BT failed %d", err); + } else { + ESP_LOGI(TAG, "BT memory released"); + } + break; + + default: + break; + } +} + +/* Used when BLE is not needed by application */ +void wifi_prov_scheme_ble_event_cb_free_ble(void *user_data, wifi_prov_cb_event_t event, void *event_data) +{ + esp_err_t err; + switch (event) { + case WIFI_PROV_DEINIT: + /* Release memory used by BLE stack */ + err = esp_bt_mem_release(ESP_BT_MODE_BLE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bt_mem_release of BLE failed %d", err); + } else { + ESP_LOGI(TAG, "BLE memory released"); + } + break; + + default: + break; + } +} + +const wifi_prov_scheme_t wifi_prov_scheme_ble = { + .prov_start = prov_start, + .prov_stop = protocomm_ble_stop, + .new_config = new_config, + .delete_config = delete_config, + .set_config_service = set_config_service, + .set_config_endpoint = set_config_endpoint, + .wifi_mode = WIFI_MODE_STA +}; diff --git a/components/wifi_provisioning/src/scheme_console.c b/components/wifi_provisioning/src/scheme_console.c new file mode 100644 index 0000000000..28732847ac --- /dev/null +++ b/components/wifi_provisioning/src/scheme_console.c @@ -0,0 +1,92 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include + +#include "wifi_provisioning/scheme_console.h" +#include "wifi_provisioning_priv.h" + +static const char *TAG = "wifi_prov_scheme_console"; + +extern const wifi_prov_scheme_t wifi_prov_scheme_console; + +static esp_err_t prov_start(protocomm_t *pc, void *config) +{ + if (!pc) { + ESP_LOGE(TAG, "Protocomm handle cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + if (!config) { + ESP_LOGE(TAG, "Cannot start with null configuration"); + return ESP_ERR_INVALID_ARG; + } + + protocomm_console_config_t *console_config = (protocomm_console_config_t *) config; + + /* Start protocomm console */ + esp_err_t err = protocomm_console_start(pc, console_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); + return ESP_FAIL; + } + return ESP_OK; +} + +static void *new_config(void) +{ + protocomm_console_config_t *console_config = malloc(sizeof(protocomm_console_config_t)); + if (!console_config) { + ESP_LOGE(TAG, "Error allocating memory for new configuration"); + return NULL; + } + protocomm_console_config_t default_config = PROTOCOMM_CONSOLE_DEFAULT_CONFIG(); + memcpy(console_config, &default_config, sizeof(default_config)); + return console_config; +} + +static void delete_config(void *config) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot delete null configuration"); + return; + } + free(config); +} + +static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) +{ + return ESP_OK; +} + +static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) +{ + return ESP_OK; +} + +const wifi_prov_scheme_t wifi_prov_scheme_console = { + .prov_start = prov_start, + .prov_stop = protocomm_console_stop, + .new_config = new_config, + .delete_config = delete_config, + .set_config_service = set_config_service, + .set_config_endpoint = set_config_endpoint, + .wifi_mode = WIFI_MODE_STA +}; diff --git a/components/wifi_provisioning/src/scheme_softap.c b/components/wifi_provisioning/src/scheme_softap.c new file mode 100644 index 0000000000..270ef1f587 --- /dev/null +++ b/components/wifi_provisioning/src/scheme_softap.c @@ -0,0 +1,202 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include +#include + +#include "wifi_provisioning/scheme_softap.h" +#include "wifi_provisioning_priv.h" + +typedef struct softap_config { + protocomm_httpd_config_t httpd_config; + char ssid[33]; + char password[65]; +} wifi_prov_softap_config_t; + +static const char *TAG = "wifi_prov_scheme_softap"; + +extern const wifi_prov_scheme_t wifi_prov_scheme_softap; + +static esp_err_t start_wifi_ap(const char *ssid, const char *pass) +{ + /* Build Wi-Fi configuration for AP mode */ + wifi_config_t wifi_config = { + .ap = { + .max_connection = 5, + }, + }; + + strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); + wifi_config.ap.ssid_len = strnlen(ssid, sizeof(wifi_config.ap.ssid)); + + if (strlen(pass) == 0) { + memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password)); + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } else { + strlcpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; + } + + /* Start Wi-Fi in AP mode with configuration built above */ + esp_err_t err = esp_wifi_set_mode(WIFI_MODE_AP); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi mode : %d", err); + return err; + } + err = esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi config : %d", err); + return err; + } + err = esp_wifi_start(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start Wi-Fi : %d", err); + return err; + } + ESP_LOGD(TAG, "Wi-Fi SoftAP started"); + return ESP_OK; +} + +static esp_err_t prov_start(protocomm_t *pc, void *config) +{ + if (!pc) { + ESP_LOGE(TAG, "Protocomm handle cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + if (!config) { + ESP_LOGE(TAG, "Cannot start with null configuration"); + return ESP_ERR_INVALID_ARG; + } + + wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config; + + protocomm_httpd_config_t *httpd_config = &softap_config->httpd_config; + + /* Start protocomm server on top of HTTP */ + esp_err_t err = protocomm_httpd_start(pc, httpd_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); + return err; + } + + /* Start Wi-Fi softAP with specified ssid and password */ + err = start_wifi_ap(softap_config->ssid, softap_config->password); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start Wi-Fi AP"); + protocomm_httpd_stop(pc); + return err; + } + + /* Add mDNS service for allowing discovery of provisioning + * service on the SoftAP network (Optional). Even though + * this is an http service we identify it by _esp_wifi_prov so + * that application is free to use _http without conflict */ + err = mdns_service_add("Wi-Fi Provisioning Service", "_esp_wifi_prov", "_tcp", + softap_config->httpd_config.data.config.port, NULL, 0); + if (err != ESP_OK) { + /* mDNS is not mandatory for provisioning to work, + * so print warning and return without failure */ + ESP_LOGW(TAG, "Error adding mDNS service! Check if mDNS is running"); + } else { + /* Information to identify the roles of the various + * protocomm endpoint URIs provided by the service */ + err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "version_endpoint", "/proto-ver"); + err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "session_endpoint", "/prov-session"); + err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "config_endpoint", "/prov-config"); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error adding mDNS service text item"); + } + } + return ESP_OK; +} + +static esp_err_t prov_stop(protocomm_t *pc) +{ + esp_err_t err = protocomm_httpd_stop(pc); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error occurred while stopping protocomm_httpd"); + } + + mdns_service_remove("_esp_wifi_prov", "_tcp"); + return err; +} + +static void *new_config(void) +{ + wifi_prov_softap_config_t *softap_config = calloc(1, sizeof(wifi_prov_softap_config_t)); + if (!softap_config) { + ESP_LOGE(TAG, "Error allocating memory for new configuration"); + return NULL; + } + protocomm_httpd_config_t default_config = { + .data = { + .config = PROTOCOMM_HTTPD_DEFAULT_CONFIG() + } + }; + softap_config->httpd_config = default_config; + return softap_config; +} + +static void delete_config(void *config) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot delete null configuration"); + return; + } + + wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config; + free(softap_config); +} + +static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot set null configuration"); + return ESP_ERR_INVALID_ARG; + } + + if (!service_name) { + ESP_LOGE(TAG, "Service name cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + + wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config; + strlcpy(softap_config->ssid, service_name, sizeof(softap_config->ssid)); + if (service_key) { + strlcpy(softap_config->password, service_key, sizeof(softap_config->password)); + } + return ESP_OK; +} + +static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) +{ + return ESP_OK; +} + +const wifi_prov_scheme_t wifi_prov_scheme_softap = { + .prov_start = prov_start, + .prov_stop = prov_stop, + .new_config = new_config, + .delete_config = delete_config, + .set_config_service = set_config_service, + .set_config_endpoint = set_config_endpoint, + .wifi_mode = WIFI_MODE_APSTA +}; diff --git a/components/wifi_provisioning/src/wifi_provisioning_priv.h b/components/wifi_provisioning/src/wifi_provisioning_priv.h new file mode 100644 index 0000000000..e93c0859d9 --- /dev/null +++ b/components/wifi_provisioning/src/wifi_provisioning_priv.h @@ -0,0 +1,44 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include +#include + +#include "wifi_provisioning/manager.h" +#include "wifi_provisioning/wifi_config.h" + +/** + * @brief Notify manager that provisioning is done + * + * Stops the provisioning. This is called by the get_status_handler() + * when the status is connected. This has no effect if main application + * has disabled auto stop on completion by calling + * wifi_prov_mgr_disable_auto_stop() + * + * @return + * - ESP_OK : Provisioning will be stopped + * - ESP_FAIL : Failed to stop provisioning + */ +esp_err_t wifi_prov_mgr_done(void); + +/** + * @brief Get protocomm handlers for wifi_config provisioning endpoint + * + * @return wifi_prov_config_handlers_t structure + */ +wifi_prov_config_handlers_t get_wifi_prov_handlers(void);