]> granicus.if.org Git - esp-idf/commitdiff
wifi_provisioning : Wi-Fi Provisioning Manager added
authorAnurag Kar <anurag.kar@espressif.com>
Tue, 16 Apr 2019 11:44:10 +0000 (17:14 +0530)
committerbot <bot@espressif.com>
Fri, 28 Jun 2019 07:49:03 +0000 (07:49 +0000)
12 files changed:
components/wifi_provisioning/CMakeLists.txt
components/wifi_provisioning/component.mk
components/wifi_provisioning/include/wifi_provisioning/manager.h [new file with mode: 0644]
components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h [new file with mode: 0644]
components/wifi_provisioning/include/wifi_provisioning/scheme_console.h [new file with mode: 0644]
components/wifi_provisioning/include/wifi_provisioning/scheme_softap.h [new file with mode: 0644]
components/wifi_provisioning/src/handlers.c [new file with mode: 0644]
components/wifi_provisioning/src/manager.c [new file with mode: 0644]
components/wifi_provisioning/src/scheme_ble.c [new file with mode: 0644]
components/wifi_provisioning/src/scheme_console.c [new file with mode: 0644]
components/wifi_provisioning/src/scheme_softap.c [new file with mode: 0644]
components/wifi_provisioning/src/wifi_provisioning_priv.h [new file with mode: 0644]

index 0147d685c77e15e72cc83fa4bffbf74e0480c39e..9266acce003769f8e5dd6b76bd326f0e3a9d034b 100644 (file)
@@ -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_BLUEDROID_ENABLED)
+        list(APPEND COMPONENT_SRCS
+            "src/scheme_ble.c")
+    endif()
+endif()
 
 register_component()
index efeb597c374bada519bd60052d33d7d8b657e388..a2a2265f10948df80b25b01713a667e43a8066c5 100644 (file)
@@ -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_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 (file)
index 0000000..6b53560
--- /dev/null
@@ -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 <esp_event_loop.h>
+#include <protocomm.h>
+
+#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 (file)
index 0000000..ed30d81
--- /dev/null
@@ -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 <protocomm.h>
+#include <protocomm_ble.h>
+
+#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 (file)
index 0000000..760fe32
--- /dev/null
@@ -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 <protocomm.h>
+#include <protocomm_console.h>
+
+#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 (file)
index 0000000..043b14e
--- /dev/null
@@ -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 <protocomm.h>
+#include <protocomm_httpd.h>
+
+#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 (file)
index 0000000..0a57ef5
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#include <esp_err.h>
+#include <esp_log.h>
+
+#include <esp_wifi.h>
+#include <tcpip_adapter.h>
+
+#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 (file)
index 0000000..5f772a7
--- /dev/null
@@ -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 <string.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <freertos/task.h>
+
+#include <cJSON.h>
+
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_wifi.h>
+#include <esp_timer.h>
+
+#include <protocomm.h>
+#include <protocomm_security0.h>
+#include <protocomm_security1.h>
+
+#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 : "<NULL>");
+    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 (file)
index 0000000..add9084
--- /dev/null
@@ -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 <string.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_bt.h>
+
+#include <protocomm.h>
+#include <protocomm_ble.h>
+
+#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 (file)
index 0000000..2873284
--- /dev/null
@@ -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 <string.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_wifi.h>
+
+#include <protocomm.h>
+#include <protocomm_console.h>
+
+#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 (file)
index 0000000..270ef1f
--- /dev/null
@@ -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 <string.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_wifi.h>
+
+#include <mdns.h>
+#include <protocomm.h>
+#include <protocomm_httpd.h>
+
+#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 (file)
index 0000000..e93c085
--- /dev/null
@@ -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 <esp_event_loop.h>
+
+#include <protocomm.h>
+#include <protocomm_security.h>
+
+#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);