|
|
@@ -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);
|
|
|
+}
|