diff --git a/network_provisioning/examples/wifi_prov/main/app_main.c b/network_provisioning/examples/wifi_prov/main/app_main.c index bf5d911587..37de715d71 100644 --- a/network_provisioning/examples/wifi_prov/main/app_main.c +++ b/network_provisioning/examples/wifi_prov/main/app_main.c @@ -283,14 +283,14 @@ static void wifi_prov_print_qr(const char *name, const char *username, const cha } #ifdef CONFIG_EXAMPLE_PROV_ENABLE_APP_CALLBACK -void wifi_prov_app_callback(void *user_data, wifi_prov_cb_event_t event, void *event_data) +void wifi_prov_app_callback(void *user_data, network_prov_cb_event_t event, void *event_data) { /** * This is blocking callback, any configurations that needs to be set when a particular * provisioning event is triggered can be set here. */ switch (event) { - case WIFI_PROV_SET_STA_CONFIG: { + case NETWORK_PROV_SET_WIFI_STA_CONFIG: { /** * Wi-Fi configurations can be set here before the Wi-Fi is enabled in * STA mode. @@ -304,11 +304,11 @@ void wifi_prov_app_callback(void *user_data, wifi_prov_cb_event_t event, void *e } } -const wifi_prov_event_handler_t wifi_prov_event_handler = { +static const network_prov_event_handler_t wifi_prov_event_handler = { .event_cb = wifi_prov_app_callback, .user_data = NULL, }; -#endif /* EXAMPLE_PROV_ENABLE_APP_CALLBACK */ +#endif /* CONFIG_EXAMPLE_PROV_ENABLE_APP_CALLBACK */ void app_main(void) { diff --git a/network_provisioning/examples/wifi_prov_ext_protocomm/CMakeLists.txt b/network_provisioning/examples/wifi_prov_ext_protocomm/CMakeLists.txt new file mode 100644 index 0000000000..d00ed56ed2 --- /dev/null +++ b/network_provisioning/examples/wifi_prov_ext_protocomm/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +project(wifi_prov_ext_protocomm) diff --git a/network_provisioning/examples/wifi_prov_ext_protocomm/main/CMakeLists.txt b/network_provisioning/examples/wifi_prov_ext_protocomm/main/CMakeLists.txt new file mode 100644 index 0000000000..61fac40e63 --- /dev/null +++ b/network_provisioning/examples/wifi_prov_ext_protocomm/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "app_main.c" + INCLUDE_DIRS ".") diff --git a/network_provisioning/examples/wifi_prov_ext_protocomm/main/Kconfig.projbuild b/network_provisioning/examples/wifi_prov_ext_protocomm/main/Kconfig.projbuild new file mode 100644 index 0000000000..fba4e41705 --- /dev/null +++ b/network_provisioning/examples/wifi_prov_ext_protocomm/main/Kconfig.projbuild @@ -0,0 +1,33 @@ +menu "Example Configuration" + + config EXAMPLE_PROV_TRANSPORT_BLE + bool + default y + select BT_ENABLED + depends on !IDF_TARGET_ESP32S2 + help + This example uses BLE transport exclusively. + + config EXAMPLE_PROV_USING_BLUEDROID + bool + depends on (BT_BLUEDROID_ENABLED && (IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S3)) + select BT_BLE_42_FEATURES_SUPPORTED + default y + help + This enables BLE 4.2 features for Bluedroid. + + config EXAMPLE_PROV_SHOW_QR + bool "Show provisioning QR code on terminal" + default y + help + Display the provisioning QR code as ASCII art on the serial terminal + in addition to printing the URL. + + config EXAMPLE_RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status on each boot" + help + Erases NVS so provisioning runs on every boot. Useful for testing. + +endmenu diff --git a/network_provisioning/examples/wifi_prov_ext_protocomm/main/app_main.c b/network_provisioning/examples/wifi_prov_ext_protocomm/main/app_main.c new file mode 100644 index 0000000000..452ee024f2 --- /dev/null +++ b/network_provisioning/examples/wifi_prov_ext_protocomm/main/app_main.c @@ -0,0 +1,234 @@ +/* Wi-Fi Provisioning in SESSION_ONLY Mode Example + + This example demonstrates NETWORK_PROV_MODE_SESSION_ONLY, which starts + the BLE transport with only prov-session and proto-ver endpoints active. + No provisioning state machine or provisioning endpoints (prov-config, + prov-scan, prov-ctrl) are registered by default. + + If the device is not yet provisioned, network_prov_mgr_enable_provisioning() + is called between init and start_provisioning() to opt-in to the full + provisioning flow. On subsequent boots the device is already provisioned, + so enable_provisioning() is NOT called — BLE starts for local control only + and the provisioning endpoints are absent. + + Application endpoints (e.g. a local-control endpoint) are registered the + same way as in any other provisioning example: call + network_prov_mgr_endpoint_create() on NETWORK_PROV_INIT and + network_prov_mgr_endpoint_register() on NETWORK_PROV_START. + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this software is + distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. +*/ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include "qrcode.h" + +static const char *TAG = "session_only_prov"; + +#define EXAMPLE_POP "abcd1234" +#define PROV_QR_VERSION "v1" +#define PROV_TRANSPORT_BLE "ble" +#define QRCODE_BASE_URL "https://espressif.github.io/esp-jumpstart/qrcode.html" + +#define WIFI_CONNECTED_BIT BIT0 +static EventGroupHandle_t s_wifi_event_group; + +static void wifi_prov_print_qr(const char *name, const char *pop) +{ + if (!name) { + ESP_LOGW(TAG, "Cannot generate QR code payload. Data missing."); + return; + } + char payload[150] = {0}; + if (pop) { + snprintf(payload, sizeof(payload), + "{\"ver\":\"%s\",\"name\":\"%s\",\"pop\":\"%s\",\"transport\":\"%s\"}", + PROV_QR_VERSION, name, pop, PROV_TRANSPORT_BLE); + } else { + snprintf(payload, sizeof(payload), + "{\"ver\":\"%s\",\"name\":\"%s\",\"transport\":\"%s\"}", + PROV_QR_VERSION, name, PROV_TRANSPORT_BLE); + } +#ifdef CONFIG_EXAMPLE_PROV_SHOW_QR + ESP_LOGI(TAG, "Scan this QR code from the provisioning application for Provisioning."); + esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT(); + esp_qrcode_generate(&cfg, payload); +#endif + ESP_LOGI(TAG, "If QR code is not visible, copy paste the below URL in a browser.\n%s?data=%s", + QRCODE_BASE_URL, payload); +} + +/* Handler for the optional custom endpoint, same as in the wifi_prov example. */ +#define CUSTOM_DATA_ENDPOINT "custom-data" + +static esp_err_t custom_prov_data_handler(uint32_t session_id, const uint8_t *inbuf, + ssize_t inlen, uint8_t **outbuf, + ssize_t *outlen, void *priv_data) +{ + if (inbuf) { + ESP_LOGI(TAG, "Received data: %.*s", (int)inlen, (char *)inbuf); + } + char response[] = "SUCCESS"; + *outbuf = (uint8_t *)strdup(response); + if (*outbuf == NULL) { + ESP_LOGE(TAG, "System out of memory"); + return ESP_ERR_NO_MEM; + } + *outlen = strlen(response) + 1; + return ESP_OK; +} + +static void prov_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_base == NETWORK_PROV_EVENT) { + switch (event_id) { + case NETWORK_PROV_INIT: + network_prov_mgr_endpoint_create(CUSTOM_DATA_ENDPOINT); + break; + + case NETWORK_PROV_START: + ESP_LOGI(TAG, "Provisioning started"); + network_prov_mgr_endpoint_register(CUSTOM_DATA_ENDPOINT, + custom_prov_data_handler, NULL); + break; + + case NETWORK_PROV_WIFI_CRED_RECV: { + wifi_sta_config_t *cfg = (wifi_sta_config_t *)event_data; + ESP_LOGI(TAG, "Received Wi-Fi credentials: SSID \"%s\"", + (const char *)cfg->ssid); + break; + } + case NETWORK_PROV_WIFI_CRED_FAIL: { + network_prov_wifi_sta_fail_reason_t *reason = + (network_prov_wifi_sta_fail_reason_t *)event_data; + ESP_LOGE(TAG, "Provisioning failed: %s", + (*reason == NETWORK_PROV_WIFI_STA_AUTH_ERROR) ? + "Wi-Fi authentication failed" : "AP not found"); + break; + } + case NETWORK_PROV_WIFI_CRED_SUCCESS: + ESP_LOGI(TAG, "Provisioning successful"); + break; + + case NETWORK_PROV_END: + ESP_LOGI(TAG, "Provisioning ended"); + break; + + default: + break; + } + } else if (event_base == WIFI_EVENT && + event_id == WIFI_EVENT_STA_DISCONNECTED) { + esp_wifi_connect(); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t *evt = (ip_event_got_ip_t *)event_data; + ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&evt->ip_info.ip)); + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } +} + +void app_main(void) +{ + esp_err_t ret; + + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + s_wifi_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, + &prov_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, + &prov_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_PROV_EVENT, ESP_EVENT_ANY_ID, + &prov_event_handler, NULL)); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + + bool provisioned = false; + ESP_ERROR_CHECK(network_prov_mgr_is_wifi_provisioned(&provisioned)); + + uint8_t eth_mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, eth_mac); + char service_name[32]; + snprintf(service_name, sizeof(service_name), "PROV_%02X%02X%02X", + eth_mac[3], eth_mac[4], eth_mac[5]); + + /* ----------------------------------------------------------------------- + * Initialize the provisioning manager in SESSION_ONLY mode. + * In this mode, only prov-session and proto-ver are registered on the + * BLE transport. No provisioning state machine is active by default. + * + * When using SCHEME_BLE_EVENT_HANDLER_FREE_BLE the BT classic memory is + * freed after provisioning; use NONE to keep full BT available. + * --------------------------------------------------------------------- */ + network_prov_mgr_config_t config = { + .scheme = network_prov_scheme_ble, + .mode = NETWORK_PROV_MODE_SESSION_ONLY, + .scheme_event_handler = NETWORK_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE, + .app_event_handler = NETWORK_PROV_EVENT_HANDLER_NONE, + }; + ESP_ERROR_CHECK(network_prov_mgr_init(config)); + + if (!provisioned) { + ESP_LOGI(TAG, "Starting provisioning"); + + /* Opt-in to full provisioning on this boot. This registers the + * provisioning GATT characteristics (prov-config, prov-scan, + * prov-ctrl) so they are included in the BLE service table and adds + * wifi_scan / wifi_prov to the proto-ver capabilities JSON. */ + ESP_ERROR_CHECK(network_prov_mgr_enable_provisioning()); + + ESP_ERROR_CHECK(network_prov_mgr_start_provisioning( + NETWORK_PROV_SECURITY_1, EXAMPLE_POP, service_name, NULL)); + + wifi_prov_print_qr(service_name, EXAMPLE_POP); + + network_prov_mgr_wait(); + } else { + ESP_LOGI(TAG, "Already provisioned — starting BLE for local control only"); + + /* No enable_provisioning() call here: BLE starts with only + * prov-session + proto-ver + local-ctrl (registered via endpoint_create + * in the NETWORK_PROV_INIT handler above). */ + ESP_ERROR_CHECK(network_prov_mgr_start_provisioning( + NETWORK_PROV_SECURITY_1, EXAMPLE_POP, service_name, NULL)); + } + + network_prov_mgr_deinit(); + + esp_wifi_connect(); + xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, + false, true, portMAX_DELAY); + + ESP_LOGI(TAG, "Connected to Wi-Fi"); +} diff --git a/network_provisioning/examples/wifi_prov_ext_protocomm/main/idf_component.yml b/network_provisioning/examples/wifi_prov_ext_protocomm/main/idf_component.yml new file mode 100644 index 0000000000..aaa70de6dd --- /dev/null +++ b/network_provisioning/examples/wifi_prov_ext_protocomm/main/idf_component.yml @@ -0,0 +1,7 @@ +version: "1.0.0" +dependencies: + espressif/qrcode: + version: "^0.1.0" + espressif/network_provisioning: + version: "*" + override_path: '../../../' diff --git a/network_provisioning/examples/wifi_prov_ext_protocomm/partitions.csv b/network_provisioning/examples/wifi_prov_ext_protocomm/partitions.csv new file mode 100644 index 0000000000..d4af98dfbd --- /dev/null +++ b/network_provisioning/examples/wifi_prov_ext_protocomm/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x6000, +phy_init, data, phy, , 0x1000, +factory, app, factory, , 0x180000, diff --git a/network_provisioning/examples/wifi_prov_ext_protocomm/sdkconfig.defaults b/network_provisioning/examples/wifi_prov_ext_protocomm/sdkconfig.defaults new file mode 100644 index 0000000000..18c5f6f311 --- /dev/null +++ b/network_provisioning/examples/wifi_prov_ext_protocomm/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Enable Bluetooth and NimBLE stack (default for RISC-V targets) +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y + + +## For Bluedroid as binary is larger than default size +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" diff --git a/network_provisioning/idf_component.yml b/network_provisioning/idf_component.yml index ab78cfedd7..f80cf87577 100644 --- a/network_provisioning/idf_component.yml +++ b/network_provisioning/idf_component.yml @@ -1,4 +1,4 @@ -version: "1.2.2" +version: "1.2.3" description: Network provisioning component for Wi-Fi or Thread devices url: https://github.com/espressif/idf-extra-components/tree/master/network_provisioning dependencies: diff --git a/network_provisioning/include/network_provisioning/manager.h b/network_provisioning/include/network_provisioning/manager.h index 1aae0e0058..30a486230a 100644 --- a/network_provisioning/include/network_provisioning/manager.h +++ b/network_provisioning/include/network_provisioning/manager.h @@ -207,6 +207,39 @@ typedef struct network_prov_scheme { /** * @brief Structure for specifying the manager configuration */ +/** + * @brief Operating mode of the provisioning manager + */ +typedef enum { + /** + * Full provisioning mode (default, backward compatible). + * The manager registers all provisioning endpoints (prov-session, proto-ver, + * prov-config, prov-scan, prov-ctrl) and runs the full provisioning state + * machine. + */ + NETWORK_PROV_MODE_FULL = 0, + + /** + * Session-only mode: the provisioning transport is kept alive after + * provisioning completes (and on already-provisioned boots) so that + * the application can reuse the protocomm session for its own purposes. + * Only prov-session and proto-ver are registered by default; no + * provisioning endpoints or state machine are active. + * + * Call network_prov_mgr_enable_provisioning() between init() and + * start_provisioning() to optionally layer provisioning on top. + * + * network_prov_mgr_set_app_info() works identically in this mode. + * Capabilities such as no_pop/no_sec are still set automatically based + * on the security parameters passed to start_provisioning(). Provisioning + * capabilities (wifi_scan, wifi_prov, etc.) are added only if + * enable_provisioning() has been called. + * + * Applicable to all transport schemes (BLE, SoftAP, etc.). + */ + NETWORK_PROV_MODE_SESSION_ONLY, +} network_prov_mode_t; + typedef struct { /** * Provisioning scheme to use. Following schemes are already available: @@ -216,6 +249,13 @@ typedef struct { */ network_prov_scheme_t scheme; + /** + * Operating mode. Set to NETWORK_PROV_MODE_SESSION_ONLY to keep the + * transport alive after provisioning for application reuse of the + * protocomm session. Default (0) = NETWORK_PROV_MODE_FULL. + */ + network_prov_mode_t mode; + /** * Event handler required by the scheme for incorporating scheme specific * behavior while provisioning manager is running. Various options may be @@ -239,6 +279,7 @@ typedef struct { */ network_prov_wifi_conn_cfg_t network_prov_wifi_conn_cfg; #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI + } network_prov_mgr_config_t; /** @@ -529,6 +570,42 @@ esp_err_t network_prov_mgr_disable_auto_stop(uint32_t cleanup_delay); esp_err_t network_prov_mgr_set_app_info(const char *label, const char *version, const char **capabilities, size_t total_capabilities); +/** + * @brief Enable provisioning endpoints in SESSION_ONLY mode + * + * In NETWORK_PROV_MODE_SESSION_ONLY, the manager does not register the + * provisioning endpoints (prov-config, prov-scan, prov-ctrl) or run the + * provisioning state machine by default. Call this API between + * network_prov_mgr_init() and network_prov_mgr_start_provisioning() to + * opt-in to full provisioning on this boot. + * + * After this call, start_provisioning() behaves identically to + * NETWORK_PROV_MODE_FULL: it registers all provisioning handlers, adds + * wifi_scan/wifi_prov capabilities to proto-ver, and runs the state machine. + * + * Has no effect (returns ESP_OK) in NETWORK_PROV_MODE_FULL, since + * provisioning is always enabled in that mode. + * + * Must be called while the manager is in the idle state (after init, before + * start_provisioning). + * + * @return + * - ESP_OK : Success + * - ESP_ERR_INVALID_STATE : Manager not initialized or already started + */ +esp_err_t network_prov_mgr_enable_provisioning(void); + +/** + * @brief Get the provisioning manager mode configured at initialization + * + * May be called from any provisioning event handler, including NETWORK_PROV_DEINIT + * (where the manager context has already been freed). In that case the cached + * mode from the last initialization is returned. + * + * @return NETWORK_PROV_MODE_FULL or NETWORK_PROV_MODE_SESSION_ONLY + */ +network_prov_mode_t network_prov_mgr_get_mode(void); + /** * @brief Create an additional endpoint and allocate internal resources for it * diff --git a/network_provisioning/src/manager.c b/network_provisioning/src/manager.c index 2788072257..b3486a2a35 100644 --- a/network_provisioning/src/manager.c +++ b/network_provisioning/src/manager.c @@ -104,9 +104,19 @@ struct network_prov_mgr_ctx { /* Provisioning scheme configuration */ void *prov_scheme_config; - /* Protocomm handle */ + /* Protocomm handle (active while the BLE transport is running) */ protocomm_t *pc; + /* In SESSION_ONLY mode, when provisioning auto-stops, the transport is + * kept alive for application reuse of the protocomm session. The pc handle + * is moved here so deinit() can stop it. */ + protocomm_t *session_pc; + + /* True when provisioning endpoints (prov-config, prov-scan, prov-ctrl) + * and the state machine are active. Always true in FULL mode; set by + * network_prov_mgr_enable_provisioning() in SESSION_ONLY mode. */ + bool prov_endpoints_enabled; + /* Type of security to use with protocomm */ int security; @@ -185,6 +195,11 @@ struct network_prov_mgr_ctx { * and never deleted as network_prov_mgr is a singleton */ static SemaphoreHandle_t prov_ctx_lock = NULL; +/* Mode from the last network_prov_mgr_init() call. Kept alive across deinit so + * that event handlers (e.g. NETWORK_PROV_DEINIT) can still query the mode after + * prov_ctx has been freed. */ +static network_prov_mode_t s_mgr_mode = NETWORK_PROV_MODE_FULL; + /* Pointer to provisioning context data */ static struct network_prov_mgr_ctx *prov_ctx; @@ -278,7 +293,7 @@ esp_err_t network_prov_mgr_set_app_info(const char *label, const char *version, return ret; } -static cJSON *network_prov_get_info_json(void) +static cJSON *network_prov_get_info_json_locked(void) { cJSON *full_info_json = prov_ctx->app_info_json ? cJSON_Duplicate(prov_ctx->app_info_json, 1) : cJSON_CreateObject(); @@ -311,18 +326,18 @@ static cJSON *network_prov_get_info_json(void) cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("no_pop")); } + /* Provisioning capabilities — only when provisioning endpoints are active. + * In SESSION_ONLY mode without enable_provisioning(), these are absent. */ + if (prov_ctx->prov_endpoints_enabled) { #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - /* Indicate capability for performing Wi-Fi provision */ - cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("wifi_prov")); - /* Indicate capability for performing Wi-Fi scan */ - cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("wifi_scan")); + cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("wifi_prov")); + cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("wifi_scan")); #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - /* Indicate capability for performing Thread provision */ - cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("thread_prov")); - /* Indicate capability for performing Thread scan */ - cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("thread_scan")); + cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("thread_prov")); + cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("thread_scan")); #endif + } return full_info_json; } @@ -335,7 +350,6 @@ static esp_err_t network_prov_mgr_start_service(const char *service_name, const const network_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"); @@ -346,14 +360,15 @@ static esp_err_t network_prov_mgr_start_service(const char *service_name, const if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to configure service"); protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; 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); + prov_ctx->pc = NULL; return ret; } @@ -364,7 +379,7 @@ static esp_err_t network_prov_mgr_start_service(const char *service_name, const &protocomm_security0, NULL); #else // Enable SECURITY_VERSION_0 in Protocomm configuration menu - return ESP_ERR_NOT_SUPPORTED; + ret = ESP_ERR_NOT_SUPPORTED; #endif } else if (prov_ctx->security == 1) { #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1 @@ -372,7 +387,7 @@ static esp_err_t network_prov_mgr_start_service(const char *service_name, const &protocomm_security1, prov_ctx->protocomm_sec_params); #else // Enable SECURITY_VERSION_1 in Protocomm configuration menu - return ESP_ERR_NOT_SUPPORTED; + ret = ESP_ERR_NOT_SUPPORTED; #endif } else if (prov_ctx->security == 2) { #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2 @@ -380,7 +395,7 @@ static esp_err_t network_prov_mgr_start_service(const char *service_name, const &protocomm_security2, prov_ctx->protocomm_sec_params); #else // Enable SECURITY_VERSION_2 in Protocomm configuration menu - return ESP_ERR_NOT_SUPPORTED; + ret = ESP_ERR_NOT_SUPPORTED; #endif } else { ESP_LOGE(TAG, "Unsupported protocomm security version %d", prov_ctx->security); @@ -390,152 +405,166 @@ static esp_err_t network_prov_mgr_start_service(const char *service_name, const ESP_LOGE(TAG, "Failed to set security endpoint"); scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; return ret; } - /* Set version information / capabilities of provisioning service and application */ - cJSON *version_json = network_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 version / capabilities endpoint */ + { + cJSON *version_json = network_prov_get_info_json_locked(); + 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); + prov_ctx->pc = NULL; + return ret; + } } - prov_ctx->network_prov_handlers = malloc(sizeof(network_prov_config_handlers_t)); - ret = get_network_prov_handlers(prov_ctx->network_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; - } + if (prov_ctx->prov_endpoints_enabled) { + /* Register the three provisioning endpoint handlers */ + prov_ctx->network_prov_handlers = malloc(sizeof(network_prov_config_handlers_t)); + ret = get_network_prov_handlers(prov_ctx->network_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); + prov_ctx->pc = NULL; + return ESP_ERR_NO_MEM; + } - /* Add protocomm endpoint for network configuration */ - ret = protocomm_add_endpoint(prov_ctx->pc, "prov-config", - network_prov_config_data_handler, - prov_ctx->network_prov_handlers); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to set provisioning endpoint"); - free(prov_ctx->network_prov_handlers); - scheme->prov_stop(prov_ctx->pc); - protocomm_delete(prov_ctx->pc); - return ret; - } + ret = protocomm_add_endpoint(prov_ctx->pc, "prov-config", + network_prov_config_data_handler, + prov_ctx->network_prov_handlers); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set provisioning endpoint"); + free(prov_ctx->network_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + return ret; + } - prov_ctx->network_scan_handlers = malloc(sizeof(network_prov_scan_handlers_t)); - ret = get_network_scan_handlers(prov_ctx->network_scan_handlers); - if (ret != ESP_OK) { - ESP_LOGD(TAG, "Failed to allocate memory for network scan handlers"); - free(prov_ctx->network_prov_handlers); - scheme->prov_stop(prov_ctx->pc); - protocomm_delete(prov_ctx->pc); - return ESP_ERR_NO_MEM; - } + prov_ctx->network_scan_handlers = malloc(sizeof(network_prov_scan_handlers_t)); + ret = get_network_scan_handlers(prov_ctx->network_scan_handlers); + if (ret != ESP_OK) { + ESP_LOGD(TAG, "Failed to allocate memory for network scan handlers"); + free(prov_ctx->network_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + return ESP_ERR_NO_MEM; + } - /* Add endpoint for scanning networks and sending scan list */ - ret = protocomm_add_endpoint(prov_ctx->pc, "prov-scan", - network_prov_scan_handler, - prov_ctx->network_scan_handlers); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to set network scan endpoint"); - free(prov_ctx->network_scan_handlers); - free(prov_ctx->network_prov_handlers); - scheme->prov_stop(prov_ctx->pc); - protocomm_delete(prov_ctx->pc); - return ret; - } + ret = protocomm_add_endpoint(prov_ctx->pc, "prov-scan", + network_prov_scan_handler, + prov_ctx->network_scan_handlers); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set network scan endpoint"); + free(prov_ctx->network_scan_handlers); + free(prov_ctx->network_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + return ret; + } - prov_ctx->network_ctrl_handlers = malloc(sizeof(network_ctrl_handlers_t)); - ret = get_network_ctrl_handlers(prov_ctx->network_ctrl_handlers); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to allocate memory for network ctrl handlers"); - free(prov_ctx->network_prov_handlers); - scheme->prov_stop(prov_ctx->pc); - protocomm_delete(prov_ctx->pc); - return ESP_ERR_NO_MEM; - } + prov_ctx->network_ctrl_handlers = malloc(sizeof(network_ctrl_handlers_t)); + ret = get_network_ctrl_handlers(prov_ctx->network_ctrl_handlers); + if (ret != ESP_OK) { + ESP_LOGD(TAG, "Failed to allocate memory for network ctrl handlers"); + free(prov_ctx->network_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + return ESP_ERR_NO_MEM; + } - /* Add endpoint for controlling state of network provisioning */ - ret = protocomm_add_endpoint(prov_ctx->pc, "prov-ctrl", - network_ctrl_handler, - prov_ctx->network_ctrl_handlers); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to set network ctrl endpoint"); - free(prov_ctx->network_ctrl_handlers); - free(prov_ctx->network_prov_handlers); - scheme->prov_stop(prov_ctx->pc); - protocomm_delete(prov_ctx->pc); - return ret; - } + ret = protocomm_add_endpoint(prov_ctx->pc, "prov-ctrl", + network_ctrl_handler, + prov_ctx->network_ctrl_handlers); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set network ctrl endpoint"); + free(prov_ctx->network_ctrl_handlers); + free(prov_ctx->network_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + return ret; + } - /* Register global event handler */ #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - ret = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, - network_prov_mgr_event_handler_internal, NULL); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to register WiFi event handler"); - free(prov_ctx->network_scan_handlers); - free(prov_ctx->network_ctrl_handlers); - free(prov_ctx->network_prov_handlers); - scheme->prov_stop(prov_ctx->pc); - protocomm_delete(prov_ctx->pc); - return ret; - } + ret = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, + network_prov_mgr_event_handler_internal, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to register WiFi event handler"); + free(prov_ctx->network_scan_handlers); + free(prov_ctx->network_ctrl_handlers); + free(prov_ctx->network_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + return ret; + } - ret = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, - network_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, - network_prov_mgr_event_handler_internal); - free(prov_ctx->network_scan_handlers); - free(prov_ctx->network_ctrl_handlers); - free(prov_ctx->network_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, + network_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, + network_prov_mgr_event_handler_internal); + free(prov_ctx->network_scan_handlers); + free(prov_ctx->network_ctrl_handlers); + free(prov_ctx->network_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + return ret; + } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - ret = esp_event_handler_register(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, - network_prov_mgr_event_handler_internal, NULL); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to register OpenThread event handler"); - free(prov_ctx->network_scan_handlers); - free(prov_ctx->network_ctrl_handlers); - free(prov_ctx->network_prov_handlers); - scheme->prov_stop(prov_ctx->pc); - protocomm_delete(prov_ctx->pc); - return ret; - } + ret = esp_event_handler_register(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, + network_prov_mgr_event_handler_internal, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to register OpenThread event handler"); + free(prov_ctx->network_scan_handlers); + free(prov_ctx->network_ctrl_handlers); + free(prov_ctx->network_prov_handlers); + scheme->prov_stop(prov_ctx->pc); + protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; + return ret; + } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD + } /* prov_endpoints_enabled */ ret = esp_event_handler_register(NETWORK_PROV_MGR_PVT_EVENT, NETWORK_PROV_MGR_STOP, network_prov_mgr_event_handler_internal, NULL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register provisioning event handler"); + if (prov_ctx->prov_endpoints_enabled) { #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, - network_prov_mgr_event_handler_internal); - esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, - network_prov_mgr_event_handler_internal); -#endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, + network_prov_mgr_event_handler_internal); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, + network_prov_mgr_event_handler_internal); +#endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - esp_event_handler_unregister(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, - network_prov_mgr_event_handler_internal); -#endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - free(prov_ctx->network_scan_handlers); - free(prov_ctx->network_ctrl_handlers); - free(prov_ctx->network_prov_handlers); + esp_event_handler_unregister(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, + network_prov_mgr_event_handler_internal); +#endif + free(prov_ctx->network_scan_handlers); + free(prov_ctx->network_ctrl_handlers); + free(prov_ctx->network_prov_handlers); + } scheme->prov_stop(prov_ctx->pc); protocomm_delete(prov_ctx->pc); + prov_ctx->pc = NULL; return ret; } @@ -633,39 +662,47 @@ static void prov_stop_and_notify(bool is_async) vTaskDelay(cleanup_delay / portTICK_PERIOD_MS); } - protocomm_remove_endpoint(prov_ctx->pc, "prov-ctrl"); - - protocomm_remove_endpoint(prov_ctx->pc, "prov-scan"); - - protocomm_remove_endpoint(prov_ctx->pc, "prov-config"); - - protocomm_unset_security(prov_ctx->pc, "prov-session"); - - protocomm_unset_version(prov_ctx->pc, "proto-ver"); + if (prov_ctx->prov_endpoints_enabled) { + protocomm_remove_endpoint(prov_ctx->pc, "prov-ctrl"); + protocomm_remove_endpoint(prov_ctx->pc, "prov-scan"); + protocomm_remove_endpoint(prov_ctx->pc, "prov-config"); + } - /* 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); + if (prov_ctx->mgr_config.mode == NETWORK_PROV_MODE_SESSION_ONLY && is_async) { + /* Auto-stop after provisioning in SESSION_ONLY mode: keep the transport + * alive so the application can reuse the protocomm session. Stash the + * handle so deinit() can stop it. prov-session and proto-ver remain + * registered on the transport. */ + prov_ctx->session_pc = prov_ctx->pc; + } else { + protocomm_unset_security(prov_ctx->pc, "prov-session"); + protocomm_unset_version(prov_ctx->pc, "proto-ver"); - /* Delete protocomm instance */ - protocomm_delete(prov_ctx->pc); + /* 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); + protocomm_delete(prov_ctx->pc); + } prov_ctx->pc = NULL; - /* Free provisioning handlers */ - free(prov_ctx->network_prov_handlers->ctx); - free(prov_ctx->network_prov_handlers); - prov_ctx->network_prov_handlers = NULL; + if (prov_ctx->prov_endpoints_enabled) { + free(prov_ctx->network_prov_handlers->ctx); + free(prov_ctx->network_prov_handlers); + prov_ctx->network_prov_handlers = NULL; - free(prov_ctx->network_scan_handlers->ctx); - free(prov_ctx->network_scan_handlers); - prov_ctx->network_scan_handlers = NULL; + free(prov_ctx->network_scan_handlers->ctx); + free(prov_ctx->network_scan_handlers); + prov_ctx->network_scan_handlers = NULL; - free(prov_ctx->network_ctrl_handlers); - prov_ctx->network_ctrl_handlers = NULL; + free(prov_ctx->network_ctrl_handlers); + prov_ctx->network_ctrl_handlers = NULL; + } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - /* Switch device to Wi-Fi STA mode irrespective of - * whether provisioning was completed or not */ - esp_wifi_set_mode(WIFI_MODE_STA); + if (prov_ctx->prov_endpoints_enabled) { + /* Switch device to Wi-Fi STA mode irrespective of + * whether provisioning was completed or not */ + esp_wifi_set_mode(WIFI_MODE_STA); + } #endif ESP_LOGI(TAG, "Provisioning stopped"); @@ -759,8 +796,13 @@ static bool network_prov_mgr_stop_service(bool blocking) ESP_LOGD(TAG, "Stopping provisioning"); prov_ctx->prov_state = NETWORK_PROV_STATE_STOPPING; - /* Free proof of possession */ - if (prov_ctx->protocomm_sec_params) { + /* Free proof of possession. + * In SESSION_ONLY mode the security handler stays active after provisioning so + * that local-control clients can establish fresh sessions. Freeing the PoP here + * would leave a dangling pointer inside the security1 context and cause + * "Key mismatch" on every reconnect. Defer the free to deinit(). */ + if (prov_ctx->protocomm_sec_params && + prov_ctx->mgr_config.mode != NETWORK_PROV_MODE_SESSION_ONLY) { if (prov_ctx->security == 1) { // In case of security 1 we keep an internal copy of "pop". // Hence free it at this point @@ -770,39 +812,41 @@ static bool network_prov_mgr_stop_service(bool blocking) prov_ctx->protocomm_sec_params = NULL; } + if (prov_ctx->prov_endpoints_enabled) { #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - /* 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; - } + /* 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, - network_prov_mgr_event_handler_internal); - esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, - network_prov_mgr_event_handler_internal); + /* Remove event handler */ + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, + network_prov_mgr_event_handler_internal); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, + network_prov_mgr_event_handler_internal); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - /* Delete all scan results */ - prov_ctx->scanning = false; - for (uint8_t i = 0; i < MAX_SCAN_RESULTS; ++i) { - if (prov_ctx->scan_result[i]) { - free(prov_ctx->scan_result[i]); + /* Delete all scan results */ + prov_ctx->scanning = false; + for (uint8_t i = 0; i < MAX_SCAN_RESULTS; ++i) { + if (prov_ctx->scan_result[i]) { + free(prov_ctx->scan_result[i]); + } + prov_ctx->scan_result[i] = NULL; } - prov_ctx->scan_result[i] = NULL; - } - prov_ctx->scan_result_count = 0; + prov_ctx->scan_result_count = 0; - /* Remove event handler */ - esp_event_handler_unregister(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, - network_prov_mgr_event_handler_internal); + /* Remove event handler */ + esp_event_handler_unregister(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, + network_prov_mgr_event_handler_internal); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD + } /* prov_endpoints_enabled */ if (blocking) { /* Run the cleanup without launching a separate task. Also the @@ -861,6 +905,43 @@ esp_err_t network_prov_mgr_disable_auto_stop(uint32_t cleanup_delay) return ret; } +esp_err_t network_prov_mgr_enable_provisioning(void) +{ + 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 != NETWORK_PROV_STATE_IDLE) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + + /* Already enabled (FULL mode or called twice) */ + if (prov_ctx->prov_endpoints_enabled) { + ret = ESP_OK; + goto exit; + } + + /* The prov-ctrl/prov-scan/prov-config GATT characteristics are always + * registered in init() so the service table is stable across boots. + * Here we just enable the provisioning state machine and handlers. */ + prov_ctx->prov_endpoints_enabled = true; + ret = ESP_OK; + +exit: + RELEASE_LOCK(prov_ctx_lock); + return ret; +} + +network_prov_mode_t network_prov_mgr_get_mode(void) +{ + return s_mgr_mode; +} + /* Call this if provisioning is completed before the timeout occurs */ esp_err_t network_prov_mgr_done(void) { @@ -1895,8 +1976,6 @@ esp_err_t network_prov_mgr_init(network_prov_mgr_config_t config) config.scheme.set_config_service, config.scheme.set_config_endpoint }; - - /* All function pointers in the scheme structure must be non-null */ for (size_t i = 0; i < sizeof(fn_ptrs) / sizeof(fn_ptrs[0]); i++) { if (!fn_ptrs[i]) { return ESP_ERR_INVALID_ARG; @@ -1919,12 +1998,15 @@ esp_err_t network_prov_mgr_init(network_prov_mgr_config_t config) } prov_ctx->mgr_config = config; + s_mgr_mode = config.mode; + prov_ctx->prov_endpoints_enabled = (config.mode == NETWORK_PROV_MODE_FULL); prov_ctx->prov_state = NETWORK_PROV_STATE_IDLE; prov_ctx->mgr_info.version = NETWORK_PROV_MGR_VERSION; + esp_err_t ret = ESP_OK; + /* Allocate memory for provisioning scheme configuration */ const network_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"); @@ -1932,6 +2014,12 @@ esp_err_t network_prov_mgr_init(network_prov_mgr_config_t config) goto exit; } + /* Always register all provisioning endpoints in the GATT table regardless of mode. + * This keeps the GATT service table identical across boots so iOS/macOS handle + * caches remain valid. On already-provisioned SESSION_ONLY boots, prov-ctrl/ + * prov-scan/prov-config appear in GATT but have no protocomm handlers — writes + * to them return ATT errors, which is harmless since local-control clients only + * use get_params/set_params/get_config. */ ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-ctrl", 0xFF4F); if (ret != ESP_OK) { ESP_LOGE(TAG, "failed to configure Network state control endpoint"); @@ -2032,6 +2120,23 @@ esp_err_t network_prov_mgr_deinit(void) return ESP_OK; } + if (prov_ctx->session_pc) { + /* SESSION_ONLY mode: BLE was kept alive after provisioning completed. + * Tear down the transport now that the manager is being destroyed. */ + prov_ctx->mgr_config.scheme.prov_stop(prov_ctx->session_pc); + protocomm_delete(prov_ctx->session_pc); + prov_ctx->session_pc = NULL; + /* Free the PoP that was kept alive so the security handler could serve + * local-control sessions. Safe to free now that BLE is stopped. */ + if (prov_ctx->protocomm_sec_params) { + if (prov_ctx->security == 1) { + uint8_t *pop = (uint8_t *)((protocomm_security1_params_t *)prov_ctx->protocomm_sec_params)->data; + free(pop); + } + prov_ctx->protocomm_sec_params = NULL; + } + } + if (prov_ctx->app_info_json) { cJSON_Delete(prov_ctx->app_info_json); } @@ -2115,50 +2220,51 @@ esp_err_t network_prov_mgr_start_provisioning(network_prov_security_t security, prov_ctx->prov_state = NETWORK_PROV_STATE_STARTING; #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI uint8_t restore_wifi_flag = 0; - /* Start Wi-Fi in Station Mode. - * This is necessary for scanning to work */ - ret = esp_wifi_set_mode(WIFI_MODE_STA); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to set Wi-Fi mode to STA"); - goto err; - } - ret = esp_wifi_start(); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to start Wi-Fi"); - goto err; - } + wifi_config_t wifi_cfg_empty = {0}, wifi_cfg_old = {0}; + if (prov_ctx->prov_endpoints_enabled) { + /* Start Wi-Fi in Station Mode. + * This is necessary for scanning to work */ + ret = esp_wifi_set_mode(WIFI_MODE_STA); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi mode to STA"); + goto err; + } + ret = esp_wifi_start(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to start Wi-Fi"); + goto err; + } - /* Change Wi-Fi storage to RAM temporarily and erase any old - * credentials in RAM(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(WIFI_IF_STA, &wifi_cfg_old); - ret = esp_wifi_set_storage(WIFI_STORAGE_RAM); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to set Wi-Fi storage to RAM"); - goto err; - } + /* Change Wi-Fi storage to RAM temporarily and erase any old + * credentials in RAM(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 */ + esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg_old); + ret = esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi storage to RAM"); + goto err; + } - /* WiFi storage needs to be restored before exiting this API */ - restore_wifi_flag |= WIFI_PROV_STORAGE_BIT; - /* Erase Wi-Fi credentials in RAM, when call disconnect and user code - * receive WIFI_EVENT_STA_DISCONNECTED and maybe call esp_wifi_connect, at - * this time Wi-Fi will have no configuration to connect */ - ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg_empty); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to set empty Wi-Fi credentials"); - goto err; - } - /* WiFi settings needs to be restored if provisioning error before exiting this API */ - restore_wifi_flag |= WIFI_PROV_SETTING_BIT; + /* WiFi storage needs to be restored before exiting this API */ + restore_wifi_flag |= WIFI_PROV_STORAGE_BIT; + /* Erase Wi-Fi credentials in RAM, when call disconnect and user code + * receive WIFI_EVENT_STA_DISCONNECTED and maybe call esp_wifi_connect, at + * this time Wi-Fi will have no configuration to connect */ + ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg_empty); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set empty Wi-Fi credentials"); + goto err; + } + /* WiFi settings needs to be restored if provisioning error before exiting this API */ + restore_wifi_flag |= WIFI_PROV_SETTING_BIT; - ret = esp_wifi_disconnect(); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to disconnect"); - goto err; - } + ret = esp_wifi_disconnect(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to disconnect"); + goto err; + } + } /* prov_endpoints_enabled */ #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0 @@ -2195,54 +2301,56 @@ esp_err_t network_prov_mgr_start_provisioning(network_prov_security_t security, #endif prov_ctx->security = security; + if (prov_ctx->prov_endpoints_enabled) { #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - esp_timer_create_args_t wifi_connect_timer_conf = { - .callback = wifi_connect_timer_cb, - .arg = NULL, - .dispatch_method = ESP_TIMER_TASK, - .name = "network_prov_wifi_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"); - goto err; - } + esp_timer_create_args_t wifi_connect_timer_conf = { + .callback = wifi_connect_timer_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "network_prov_wifi_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"); + goto err; + } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - esp_timer_create_args_t thread_timeout_timer_conf = { - .callback = thread_timeout_timer_cb, - .arg = NULL, - .dispatch_method = ESP_TIMER_TASK, - .name = "thread_prov_timeout_tm" - }; - ret = esp_timer_create(&thread_timeout_timer_conf, &prov_ctx->thread_timeout_timer); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to create Thread attaching timeout timer"); - goto err; - } -#endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - - /* 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, + esp_timer_create_args_t thread_timeout_timer_conf = { + .callback = thread_timeout_timer_cb, .arg = NULL, .dispatch_method = ESP_TIMER_TASK, - .name = "network_prov_autostop_tm" + .name = "thread_prov_timeout_tm" }; - ret = esp_timer_create(&autostop_timer_conf, &prov_ctx->autostop_timer); + ret = esp_timer_create(&thread_timeout_timer_conf, &prov_ctx->thread_timeout_timer); if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to create auto-stop timer"); + ESP_LOGE(TAG, "Failed to create Thread attaching timeout timer"); + goto err; + } +#endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD + + /* 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 = "network_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"); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - esp_timer_delete(prov_ctx->wifi_connect_timer); + esp_timer_delete(prov_ctx->wifi_connect_timer); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - esp_timer_delete(prov_ctx->thread_timeout_timer); + esp_timer_delete(prov_ctx->thread_timeout_timer); #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - goto err; + goto err; + } } - } + } /* prov_endpoints_enabled */ esp_timer_create_args_t cleanup_delay_timer = { .callback = cleanup_delay_timer_cb, @@ -2254,12 +2362,18 @@ esp_err_t network_prov_mgr_start_provisioning(network_prov_security_t security, if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to create cleanup delay timer"); #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - esp_timer_delete(prov_ctx->wifi_connect_timer); + if (prov_ctx->wifi_connect_timer) { + esp_timer_delete(prov_ctx->wifi_connect_timer); + } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - esp_timer_delete(prov_ctx->thread_timeout_timer); + if (prov_ctx->thread_timeout_timer) { + esp_timer_delete(prov_ctx->thread_timeout_timer); + } #endif // CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - esp_timer_delete(prov_ctx->autostop_timer); + if (prov_ctx->autostop_timer) { + esp_timer_delete(prov_ctx->autostop_timer); + } goto err; } @@ -2273,12 +2387,18 @@ esp_err_t network_prov_mgr_start_provisioning(network_prov_security_t security, /* Start provisioning service */ ret = network_prov_mgr_start_service(service_name, service_key); if (ret != ESP_OK) { - esp_timer_delete(prov_ctx->autostop_timer); + if (prov_ctx->autostop_timer) { + esp_timer_delete(prov_ctx->autostop_timer); + } #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI - esp_timer_delete(prov_ctx->wifi_connect_timer); + if (prov_ctx->wifi_connect_timer) { + esp_timer_delete(prov_ctx->wifi_connect_timer); + } #endif #ifdef CONFIG_NETWORK_PROV_NETWORK_TYPE_THREAD - esp_timer_delete(prov_ctx->thread_timeout_timer); + if (prov_ctx->thread_timeout_timer) { + esp_timer_delete(prov_ctx->thread_timeout_timer); + } #endif esp_timer_delete(prov_ctx->cleanup_delay_timer); } diff --git a/network_provisioning/src/scheme_ble.c b/network_provisioning/src/scheme_ble.c index 565284b69f..ada1682346 100644 --- a/network_provisioning/src/scheme_ble.c +++ b/network_provisioning/src/scheme_ble.c @@ -311,11 +311,11 @@ void network_prov_scheme_ble_event_cb_free_bt(void *user_data, network_prov_cb_e void network_prov_scheme_ble_event_cb_free_ble(void *user_data, network_prov_cb_event_t event, void *event_data) { #ifdef CONFIG_BT_CONTROLLER_ENABLED - esp_err_t err; switch (event) { case NETWORK_PROV_DEINIT: #ifndef CONFIG_NETWORK_PROV_KEEP_BLE_ON_AFTER_PROV /* Release memory used by BLE stack */ + esp_err_t err; err = esp_bt_mem_release(ESP_BT_MODE_BLE); if (err != ESP_OK) { ESP_LOGE(TAG, "bt_mem_release of BLE failed %d", err);