// Copyright 2019 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wifi_provisioning_priv.h" #define WIFI_PROV_MGR_VERSION "v1.1" #define MAX_SCAN_RESULTS CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES #define ACQUIRE_LOCK(mux) assert(xSemaphoreTake(mux, portMAX_DELAY) == pdTRUE) #define RELEASE_LOCK(mux) assert(xSemaphoreGive(mux) == pdTRUE) static const char *TAG = "wifi_prov_mgr"; ESP_EVENT_DEFINE_BASE(WIFI_PROV_EVENT); 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 { /* Security 0 is used */ bool no_sec; /* 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 for Provisioning Auto Stop timer */ esp_timer_handle_t autostop_timer; /* Handle for delayed Wi-Fi connection timer */ esp_timer_handle_t wifi_connect_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; /* Protocomm handlers for Wi-Fi scan endpoint */ wifi_prov_scan_handlers_t *wifi_scan_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; /* Wi-Fi scan parameters and state variables */ bool scanning; uint8_t channels_per_group; uint16_t curr_channel; uint16_t ap_list_len[14]; // 14 entries corresponding to each channel wifi_ap_record_t *ap_list[14]; wifi_ap_record_t *ap_list_sorted[MAX_SCAN_RESULTS]; wifi_scan_config_t scan_cfg; }; /* 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, size_t event_data_size) { 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 */ RELEASE_LOCK(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); } if (esp_event_post(WIFI_PROV_EVENT, event_id, event_data, event_data_size, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post event %d to default event loop", event_id); } ACQUIRE_LOCK(prov_ctx_lock); } } 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; ACQUIRE_LOCK(prov_ctx_lock); 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])); } } ret = ESP_OK; } else { ret = ESP_ERR_INVALID_STATE; } RELEASE_LOCK(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 Security / Proof of Possession is not used, indicate in capabilities */ if (prov_ctx->mgr_info.capabilities.no_sec) { cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("no_sec")); } else if (prov_ctx->mgr_info.capabilities.no_pop) { cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("no_pop")); } /* Indicate capability for performing Wi-Fi scan */ cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("wifi_scan")); return full_info_json; } /* Declare the internal event handler */ static void wifi_prov_mgr_event_handler_internal(void* arg, esp_event_base_t event_base, int event_id, void* event_data); 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(); char *version_str = cJSON_Print(version_json); ret = protocomm_set_version(prov_ctx->pc, "proto-ver", version_str); free(version_str); 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)); ret = get_wifi_prov_handlers(prov_ctx->wifi_prov_handlers); if (ret != ESP_OK) { ESP_LOGD(TAG, "Failed to allocate memory for provisioning handlers"); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ESP_ERR_NO_MEM; } /* 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; } prov_ctx->wifi_scan_handlers = malloc(sizeof(wifi_prov_scan_handlers_t)); ret = get_wifi_scan_handlers(prov_ctx->wifi_scan_handlers); if (ret != ESP_OK) { ESP_LOGD(TAG, "Failed to allocate memory for Wi-Fi scan handlers"); free(prov_ctx->wifi_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ESP_ERR_NO_MEM; } /* Add endpoint for scanning Wi-Fi APs and sending scan list */ ret = protocomm_add_endpoint(prov_ctx->pc, "prov-scan", wifi_prov_scan_handler, prov_ctx->wifi_scan_handlers); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi scan endpoint"); free(prov_ctx->wifi_scan_handlers); free(prov_ctx->wifi_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } /* Register global event handler */ ret = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_prov_mgr_event_handler_internal, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register WiFi event handler"); free(prov_ctx->wifi_scan_handlers); free(prov_ctx->wifi_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } ret = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_prov_mgr_event_handler_internal, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register IP event handler"); esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_prov_mgr_event_handler_internal); free(prov_ctx->wifi_scan_handlers); free(prov_ctx->wifi_prov_handlers); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); return ret; } ESP_LOGI(TAG, "Provisioning started with service name : %s ", service_name ? service_name : ""); return ESP_OK; } esp_err_t wifi_prov_mgr_endpoint_create(const char *ep_name) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } esp_err_t err = ESP_FAIL; ACQUIRE_LOCK(prov_ctx_lock); 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++; } RELEASE_LOCK(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; ACQUIRE_LOCK(prov_ctx_lock); 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); } RELEASE_LOCK(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; } ACQUIRE_LOCK(prov_ctx_lock); 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); } RELEASE_LOCK(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; free(prov_ctx->wifi_scan_handlers->ctx); free(prov_ctx->wifi_scan_handlers); prov_ctx->wifi_scan_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) { ACQUIRE_LOCK(prov_ctx_lock); prov_ctx->prov_state = WIFI_PROV_STATE_IDLE; RELEASE_LOCK(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); } if (esp_event_post(WIFI_PROV_EVENT, WIFI_PROV_END, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post event WIFI_PROV_END"); } 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)) { RELEASE_LOCK(prov_ctx_lock); vTaskDelay(100 / portTICK_PERIOD_MS); ACQUIRE_LOCK(prov_ctx_lock); } } 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) { RELEASE_LOCK(prov_ctx_lock); vTaskDelay(100 / portTICK_PERIOD_MS); ACQUIRE_LOCK(prov_ctx_lock); } 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; } /* Timers not needed anymore */ if (prov_ctx->autostop_timer) { esp_timer_stop(prov_ctx->autostop_timer); esp_timer_delete(prov_ctx->autostop_timer); prov_ctx->autostop_timer = NULL; } if (prov_ctx->wifi_connect_timer) { esp_timer_stop(prov_ctx->wifi_connect_timer); esp_timer_delete(prov_ctx->wifi_connect_timer); prov_ctx->wifi_connect_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; } /* Delete all scan results */ for (uint16_t channel = 0; channel < 14; channel++) { free(prov_ctx->ap_list[channel]); prov_ctx->ap_list[channel] = NULL; } prov_ctx->scanning = false; for (uint8_t i = 0; i < MAX_SCAN_RESULTS; i++) { prov_ctx->ap_list_sorted[i] = NULL; } /* Remove event handler */ esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_prov_mgr_event_handler_internal); esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_prov_mgr_event_handler_internal); if (blocking) { /* Run the cleanup without launching a separate task. Also the * WIFI_PROV_END event is not emitted in this case */ RELEASE_LOCK(prov_ctx_lock); prov_stop_task((void *)0); ACQUIRE_LOCK(prov_ctx_lock); 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; ACQUIRE_LOCK(prov_ctx_lock); 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; ret = ESP_OK; } else { ret = ESP_ERR_INVALID_STATE; } RELEASE_LOCK(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; } bool auto_stop_enabled = false; ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && !prov_ctx->mgr_info.capabilities.no_auto_stop) { auto_stop_enabled = true; } RELEASE_LOCK(prov_ctx_lock); /* Stop provisioning if auto stop is enabled */ if (auto_stop_enabled) { wifi_prov_mgr_stop_provisioning(); } return ESP_OK; } static esp_err_t update_wifi_scan_results(void) { if (!prov_ctx->scanning) { return ESP_ERR_INVALID_STATE; } ESP_LOGD(TAG, "Scan finished"); esp_err_t ret = ESP_FAIL; uint16_t count = 0; uint16_t curr_channel = prov_ctx->curr_channel; if (prov_ctx->ap_list[curr_channel]) { free(prov_ctx->ap_list[curr_channel]); prov_ctx->ap_list[curr_channel] = NULL; prov_ctx->ap_list_len[curr_channel] = 0; } if (esp_wifi_scan_get_ap_num(&count) != ESP_OK) { ESP_LOGE(TAG, "Failed to get count of scanned APs"); goto exit; } if (!count) { ESP_LOGD(TAG, "Scan result empty"); ret = ESP_OK; goto exit; } prov_ctx->ap_list[curr_channel] = (wifi_ap_record_t *) calloc(count, sizeof(wifi_ap_record_t)); if (!prov_ctx->ap_list[curr_channel]) { ESP_LOGE(TAG, "Failed to allocate memory for AP list"); goto exit; } if (esp_wifi_scan_get_ap_records(&count, prov_ctx->ap_list[curr_channel]) != ESP_OK) { ESP_LOGE(TAG, "Failed to get scanned AP records"); goto exit; } prov_ctx->ap_list_len[curr_channel] = count; if (prov_ctx->channels_per_group) { ESP_LOGD(TAG, "Scan results for channel %d :", curr_channel); } else { ESP_LOGD(TAG, "Scan results :"); } ESP_LOGD(TAG, "\tS.N. %-32s %-12s %s %s", "SSID", "BSSID", "RSSI", "AUTH"); for (uint8_t i = 0; i < prov_ctx->ap_list_len[curr_channel]; i++) { ESP_LOGD(TAG, "\t[%2d] %-32s %02x%02x%02x%02x%02x%02x %4d %4d", i, prov_ctx->ap_list[curr_channel][i].ssid, prov_ctx->ap_list[curr_channel][i].bssid[0], prov_ctx->ap_list[curr_channel][i].bssid[1], prov_ctx->ap_list[curr_channel][i].bssid[2], prov_ctx->ap_list[curr_channel][i].bssid[3], prov_ctx->ap_list[curr_channel][i].bssid[4], prov_ctx->ap_list[curr_channel][i].bssid[5], prov_ctx->ap_list[curr_channel][i].rssi, prov_ctx->ap_list[curr_channel][i].authmode); } /* Store results in sorted list */ { int rc = MIN(count, MAX_SCAN_RESULTS); int is = MAX_SCAN_RESULTS - rc - 1; while (rc > 0 && is >= 0) { if (prov_ctx->ap_list_sorted[is]) { if (prov_ctx->ap_list_sorted[is]->rssi > prov_ctx->ap_list[curr_channel][rc - 1].rssi) { prov_ctx->ap_list_sorted[is + rc] = &prov_ctx->ap_list[curr_channel][rc - 1]; rc--; continue; } prov_ctx->ap_list_sorted[is + rc] = prov_ctx->ap_list_sorted[is]; } is--; } while (rc > 0) { prov_ctx->ap_list_sorted[rc - 1] = &prov_ctx->ap_list[curr_channel][rc - 1]; rc--; } } ret = ESP_OK; exit: if (!prov_ctx->channels_per_group) { /* All channel scan was performed * so nothing more to do */ prov_ctx->scanning = false; goto final; } curr_channel = prov_ctx->curr_channel = (prov_ctx->curr_channel + 1) % 14; if (ret != ESP_OK || curr_channel == 0) { prov_ctx->scanning = false; goto final; } if ((curr_channel % prov_ctx->channels_per_group) == 0) { vTaskDelay(120 / portTICK_PERIOD_MS); } ESP_LOGD(TAG, "Scan starting on channel %u...", curr_channel); prov_ctx->scan_cfg.channel = curr_channel; ret = esp_wifi_scan_start(&prov_ctx->scan_cfg, false); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start scan"); prov_ctx->scanning = false; goto final; } ESP_LOGD(TAG, "Scan started"); final: return ret; } /* DEPRECATED : 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) { return ESP_OK; } static void wifi_prov_mgr_event_handler_internal( void* arg, esp_event_base_t event_base, int event_id, void* event_data) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } ACQUIRE_LOCK(prov_ctx_lock); /* 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) { RELEASE_LOCK(prov_ctx_lock); return; } /* If scan completed then update scan result */ if (prov_ctx->prov_state == WIFI_PROV_STATE_STARTED && event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { update_wifi_scan_results(); } /* 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) { RELEASE_LOCK(prov_ctx_lock); return; } if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { ESP_LOGI(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; } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ESP_LOGI(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 after configured timeout. */ if (!prov_ctx->mgr_info.capabilities.no_auto_stop) { ESP_LOGD(TAG, "Starting %d sec timer for stop_prov_timer_cb()", CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT); esp_timer_start_once(prov_ctx->autostop_timer, CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT * 1000000U); } /* Execute user registered callback handler */ execute_event_cb(WIFI_PROV_CRED_SUCCESS, NULL, 0); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { ESP_LOGE(TAG, "STA Disconnected"); /* Station couldn't connect to configured host SSID */ prov_ctx->wifi_state = WIFI_PROV_STA_DISCONNECTED; wifi_event_sta_disconnected_t* disconnected = (wifi_event_sta_disconnected_t*) event_data; ESP_LOGE(TAG, "Disconnect reason : %d", disconnected->reason); /* Set code corresponding to the reason for disconnection */ switch (disconnected->reason) { case WIFI_REASON_AUTH_EXPIRE: case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: case WIFI_REASON_AUTH_FAIL: case WIFI_REASON_ASSOC_EXPIRE: case WIFI_REASON_HANDSHAKE_TIMEOUT: ESP_LOGE(TAG, "STA Auth Error"); prov_ctx->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR; break; case WIFI_REASON_NO_AP_FOUND: ESP_LOGE(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, sizeof(reason)); } } RELEASE_LOCK(prov_ctx_lock); } esp_err_t wifi_prov_mgr_wifi_scan_start(bool blocking, bool passive, uint8_t group_channels, uint32_t period_ms) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return ESP_ERR_INVALID_STATE; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return ESP_ERR_INVALID_STATE; } if (prov_ctx->scanning) { ESP_LOGD(TAG, "Scan already running"); RELEASE_LOCK(prov_ctx_lock); return ESP_OK; } /* Clear sorted list for new entries */ for (uint8_t i = 0; i < MAX_SCAN_RESULTS; i++) { prov_ctx->ap_list_sorted[i] = NULL; } if (passive) { prov_ctx->scan_cfg.scan_type = WIFI_SCAN_TYPE_PASSIVE; prov_ctx->scan_cfg.scan_time.passive = period_ms; } else { prov_ctx->scan_cfg.scan_type = WIFI_SCAN_TYPE_ACTIVE; prov_ctx->scan_cfg.scan_time.active.min = period_ms; prov_ctx->scan_cfg.scan_time.active.max = period_ms; } prov_ctx->channels_per_group = group_channels; if (prov_ctx->channels_per_group) { ESP_LOGD(TAG, "Scan starting on channel 1..."); prov_ctx->scan_cfg.channel = 1; } else { ESP_LOGD(TAG, "Scan starting..."); prov_ctx->scan_cfg.channel = 0; } if (esp_wifi_scan_start(&prov_ctx->scan_cfg, false) != ESP_OK) { ESP_LOGE(TAG, "Failed to start scan"); return ESP_FAIL; } ESP_LOGD(TAG, "Scan started"); prov_ctx->scanning = true; prov_ctx->curr_channel = prov_ctx->scan_cfg.channel; RELEASE_LOCK(prov_ctx_lock); /* If scan is to be non-blocking, return immediately */ if (!blocking) { return ESP_OK; } /* Loop till scan is complete */ bool scanning = true; while (scanning) { ACQUIRE_LOCK(prov_ctx_lock); scanning = (prov_ctx && prov_ctx->scanning); RELEASE_LOCK(prov_ctx_lock); /* 120ms delay is sufficient for Wi-Fi beacons to be sent */ vTaskDelay(120 / portTICK_PERIOD_MS); } return ESP_OK; } bool wifi_prov_mgr_wifi_scan_finished(void) { bool scan_finished = true; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return scan_finished; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return scan_finished; } scan_finished = !prov_ctx->scanning; RELEASE_LOCK(prov_ctx_lock); return scan_finished; } uint16_t wifi_prov_mgr_wifi_scan_result_count(void) { uint16_t rval = 0; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return rval; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return rval; } while (rval < MAX_SCAN_RESULTS) { if (!prov_ctx->ap_list_sorted[rval]) { break; } rval++; } RELEASE_LOCK(prov_ctx_lock); return rval; } const wifi_ap_record_t *wifi_prov_mgr_wifi_scan_result(uint16_t index) { const wifi_ap_record_t *rval = NULL; if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return rval; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return rval; } if (index < MAX_SCAN_RESULTS) { rval = prov_ctx->ap_list_sorted[index]; } RELEASE_LOCK(prov_ctx_lock); return rval; } 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; } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx == NULL || state == NULL) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } *state = prov_ctx->wifi_state; RELEASE_LOCK(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; } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx == NULL || reason == NULL) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } if (prov_ctx->wifi_state != WIFI_PROV_STA_DISCONNECTED) { RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } *reason = prov_ctx->wifi_disconnect_reason; RELEASE_LOCK(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; } static void wifi_connect_timer_cb(void *arg) { if (esp_wifi_connect() != ESP_OK) { ESP_LOGE(TAG, "Failed to connect Wi-Fi"); } } 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; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Invalid state of Provisioning app"); RELEASE_LOCK(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"); RELEASE_LOCK(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"); RELEASE_LOCK(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"); RELEASE_LOCK(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"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } /* Connect to AP after one second so that the response can * be sent to the client successfully, before a channel change happens*/ if (esp_timer_start_once(prov_ctx->wifi_connect_timer, 1000 * 1000U) != ESP_OK) { ESP_LOGE(TAG, "Failed to start Wi-Fi connect timer"); RELEASE_LOCK(prov_ctx_lock); return ESP_FAIL; } /* 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, sizeof(wifi_cfg->sta)); RELEASE_LOCK(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 = xSemaphoreCreateMutex(); 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; } } ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx) { ESP_LOGE(TAG, "Provisioning manager already initialized"); RELEASE_LOCK(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"); RELEASE_LOCK(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-scan", 0xFF50); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to configure Wi-Fi scanning endpoint"); 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, 0); } RELEASE_LOCK(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) { ACQUIRE_LOCK(prov_ctx_lock); if (prov_ctx && prov_ctx->prov_state != WIFI_PROV_STATE_IDLE) { RELEASE_LOCK(prov_ctx_lock); vTaskDelay(1000 / portTICK_PERIOD_MS); continue; } break; } RELEASE_LOCK(prov_ctx_lock); } void wifi_prov_mgr_deinit(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } ACQUIRE_LOCK(prov_ctx_lock); /* 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"); RELEASE_LOCK(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; RELEASE_LOCK(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); } if (esp_event_post(WIFI_PROV_EVENT, WIFI_PROV_END, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post event WIFI_PROV_END"); } } 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); } if (esp_event_post(WIFI_PROV_EVENT, WIFI_PROV_DEINIT, NULL, 0, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post event WIFI_PROV_DEINIT"); } } 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; } ACQUIRE_LOCK(prov_ctx_lock); if (!prov_ctx) { ESP_LOGE(TAG, "Provisioning manager not initialized"); RELEASE_LOCK(prov_ctx_lock); return ESP_ERR_INVALID_STATE; } if (prov_ctx->prov_state != WIFI_PROV_STATE_IDLE) { ESP_LOGE(TAG, "Provisioning service already started"); RELEASE_LOCK(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; /* Start Wi-Fi in Station Mode. * This is necessary for scanning to work */ esp_err_t err = esp_wifi_set_mode(WIFI_MODE_STA); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi mode to STA"); RELEASE_LOCK(prov_ctx_lock); return err; } err = esp_wifi_start(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to start Wi-Fi"); RELEASE_LOCK(prov_ctx_lock); return err; } /* 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); err = esp_wifi_set_storage(WIFI_STORAGE_RAM); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set Wi-Fi storage to RAM"); RELEASE_LOCK(prov_ctx_lock); return err; } esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_cfg_empty); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set empty Wi-Fi credentials"); RELEASE_LOCK(prov_ctx_lock); return err; } err = esp_wifi_disconnect(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to disconnect"); RELEASE_LOCK(prov_ctx_lock); return err; } /* Initialize app data */ if (security == WIFI_PROV_SECURITY_0) { prov_ctx->mgr_info.capabilities.no_sec = true; } else 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; esp_timer_create_args_t wifi_connect_timer_conf = { .callback = wifi_connect_timer_cb, .arg = NULL, .dispatch_method = ESP_TIMER_TASK, .name = "wifi_prov_connect_tm" }; ret = esp_timer_create(&wifi_connect_timer_conf, &prov_ctx->wifi_connect_timer); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create Wi-Fi connect timer"); free((void *)prov_ctx->pop.data); goto err; } /* 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 autostop_timer_conf = { .callback = stop_prov_timer_cb, .arg = NULL, .dispatch_method = ESP_TIMER_TASK, .name = "wifi_prov_autostop_tm" }; ret = esp_timer_create(&autostop_timer_conf, &prov_ctx->autostop_timer); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create auto-stop timer"); esp_timer_delete(prov_ctx->wifi_connect_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 */ RELEASE_LOCK(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->autostop_timer); esp_timer_delete(prov_ctx->wifi_connect_timer); free((void *)prov_ctx->pop.data); } ACQUIRE_LOCK(prov_ctx_lock); if (ret == ESP_OK) { prov_ctx->prov_state = WIFI_PROV_STATE_STARTED; /* Execute user registered callback handler */ execute_event_cb(WIFI_PROV_START, NULL, 0); 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: RELEASE_LOCK(prov_ctx_lock); return ret; } void wifi_prov_mgr_stop_provisioning(void) { if (!prov_ctx_lock) { ESP_LOGE(TAG, "Provisioning manager not initialized"); return; } ACQUIRE_LOCK(prov_ctx_lock); /* 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); RELEASE_LOCK(prov_ctx_lock); }