diff --git a/components/wifi_provisioning/CMakeLists.txt b/components/wifi_provisioning/CMakeLists.txt index 0147d685..b261f1e8 100644 --- a/components/wifi_provisioning/CMakeLists.txt +++ b/components/wifi_provisioning/CMakeLists.txt @@ -1,10 +1,28 @@ -set(COMPONENT_ADD_INCLUDEDIRS include) -set(COMPONENT_PRIV_INCLUDEDIRS proto-c ../protocomm/proto-c) -set(COMPONENT_SRCS "src/wifi_config.c" - "proto-c/wifi_config.pb-c.c" - "proto-c/wifi_constants.pb-c.c") +set(srcs "src/wifi_config.c" + "src/wifi_scan.c" + "src/manager.c" + "src/handlers.c" + "src/scheme_softap.c" + "src/scheme_console.c" + "proto-c/wifi_config.pb-c.c" + "proto-c/wifi_scan.pb-c.c" + "proto-c/wifi_constants.pb-c.c") -set(COMPONENT_REQUIRES lwip) -set(COMPONENT_PRIV_REQUIRES protobuf-c protocomm) +if(CONFIG_BT_ENABLED) + if(CONFIG_BT_BLUEDROID_ENABLED OR CONFIG_BT_NIMBLE_ENABLED) + list(APPEND srcs + "src/scheme_ble.c") + endif() +endif() -register_component() +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS include + PRIV_INCLUDE_DIRS src proto-c ../protocomm/proto-c + REQUIRES lwip protocomm + PRIV_REQUIRES protobuf-c bt mdns json esp_timer) + +# To avoid warning for strncpy +set_source_files_properties(src/handlers.c src/scheme_softap.c + PROPERTIES COMPILE_FLAGS + -Wno-stringop-truncation +) diff --git a/components/wifi_provisioning/Kconfig b/components/wifi_provisioning/Kconfig index 13a58aa1..cfadd5a9 100644 --- a/components/wifi_provisioning/Kconfig +++ b/components/wifi_provisioning/Kconfig @@ -1,10 +1,18 @@ -menu "Unified Provisioning" +menu "Wi-Fi Provisioning Manager" + + config WIFI_PROV_SCAN_MAX_ENTRIES + int "Max Wi-Fi Scan Result Entries" + default 16 + range 1 255 + help + This sets the maximum number of entries of Wi-Fi scan results that will be kept by the provisioning manager + + config WIFI_PROV_AUTOSTOP_TIMEOUT + int "Provisioning auto-stop timeout" + default 30 + range 5 600 + help + Time (in seconds) after which the Wi-Fi provisioning manager will auto-stop after connecting to + a Wi-Fi network successfully. -config ENABLE_UNIFIED_PROVISIONING - bool "Enable Unified Provisioning" - default n - select MBEDTLS_CIPHER_MODE_CTR - select MBEDTLS_ECP_C - help - This enables Unified Provisioning feature along with required components like Wifi-Provisioning, Protocomm and their mbedtls dependencies. endmenu diff --git a/components/wifi_provisioning/component.mk b/components/wifi_provisioning/component.mk index 5b5f985e..1d3fc747 100644 --- a/components/wifi_provisioning/component.mk +++ b/components/wifi_provisioning/component.mk @@ -1,8 +1,13 @@ -COMPONENT_SRCDIRS := -COMPONENT_ADD_INCLUDEDIRS := - -ifdef CONFIG_ENABLE_UNIFIED_PROVISIONING COMPONENT_SRCDIRS := src proto-c COMPONENT_ADD_INCLUDEDIRS := include -COMPONENT_PRIV_INCLUDEDIRS := proto-c ../protocomm/proto-c/ +COMPONENT_PRIV_INCLUDEDIRS := src proto-c ../protocomm/proto-c/ + +# To avoid warning for strncpy in "handlers.c" and "scheme_softap.c" +CPPFLAGS += -Wno-stringop-truncation + +ifndef CONFIG_BT_BLUEDROID_ENABLED + ifndef CONFIG_BT_NIMBLE_ENABLED + COMPONENT_OBJEXCLUDE := src/scheme_ble.o + endif endif + diff --git a/components/wifi_provisioning/include/wifi_provisioning/manager.h b/components/wifi_provisioning/include/wifi_provisioning/manager.h new file mode 100644 index 00000000..2edd43fe --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/manager.h @@ -0,0 +1,558 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "esp_event.h" +#include "wifi_provisioning/wifi_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +ESP_EVENT_DECLARE_BASE(WIFI_PROV_EVENT); + +/** + * @brief Events generated by manager + * + * These events are generated in order of declaration and, for the + * stretch of time between initialization and de-initialization of + * the manager, each event is signaled only once + */ +typedef enum { + /** + * Emitted when the manager is initialized + */ + WIFI_PROV_INIT, + + /** + * Indicates that provisioning has started + */ + WIFI_PROV_START, + + /** + * Emitted when Wi-Fi AP credentials are received via `protocomm` + * endpoint `wifi_config`. The event data in this case is a pointer + * to the corresponding `wifi_sta_config_t` structure + */ + WIFI_PROV_CRED_RECV, + + /** + * Emitted when device fails to connect to the AP of which the + * credentials were received earlier on event `WIFI_PROV_CRED_RECV`. + * The event data in this case is a pointer to the disconnection + * reason code with type `wifi_prov_sta_fail_reason_t` + */ + WIFI_PROV_CRED_FAIL, + + /** + * Emitted when device successfully connects to the AP of which the + * credentials were received earlier on event `WIFI_PROV_CRED_RECV` + */ + WIFI_PROV_CRED_SUCCESS, + + /** + * Signals that provisioning service has stopped + */ + WIFI_PROV_END, + + /** + * Signals that manager has been de-initialized + */ + WIFI_PROV_DEINIT, +} wifi_prov_cb_event_t; + +typedef void (*wifi_prov_cb_func_t)(void *user_data, wifi_prov_cb_event_t event, void *event_data); + +/** + * @brief Event handler that is used by the manager while + * provisioning service is active + */ +typedef struct { + /** + * Callback function to be executed on provisioning events + */ + wifi_prov_cb_func_t event_cb; + + /** + * User context data to pass as parameter to callback function + */ + void *user_data; +} wifi_prov_event_handler_t; + +/** + * @brief Event handler can be set to none if not used + */ +#define WIFI_PROV_EVENT_HANDLER_NONE { \ + .event_cb = NULL, \ + .user_data = NULL \ +} + +/** + * @brief Structure for specifying the provisioning scheme to be + * followed by the manager + * + * @note Ready to use schemes are available: + * - wifi_prov_scheme_ble : for provisioning over BLE transport + GATT server + * - wifi_prov_scheme_softap : for provisioning over SoftAP transport + HTTP server + * - wifi_prov_scheme_console : for provisioning over Serial UART transport + Console (for debugging) + */ +typedef struct wifi_prov_scheme { + /** + * Function which is to be called by the manager when it is to + * start the provisioning service associated with a protocomm instance + * and a scheme specific configuration + */ + esp_err_t (*prov_start) (protocomm_t *pc, void *config); + + /** + * Function which is to be called by the manager to stop the + * provisioning service previously associated with a protocomm instance + */ + esp_err_t (*prov_stop) (protocomm_t *pc); + + /** + * Function which is to be called by the manager to generate + * a new configuration for the provisioning service, that is + * to be passed to prov_start() + */ + void *(*new_config) (void); + + /** + * Function which is to be called by the manager to delete a + * configuration generated using new_config() + */ + void (*delete_config) (void *config); + + /** + * Function which is to be called by the manager to set the + * service name and key values in the configuration structure + */ + esp_err_t (*set_config_service) (void *config, const char *service_name, const char *service_key); + + /** + * Function which is to be called by the manager to set a protocomm endpoint + * with an identifying name and UUID in the configuration structure + */ + esp_err_t (*set_config_endpoint) (void *config, const char *endpoint_name, uint16_t uuid); + + /** + * Sets mode of operation of Wi-Fi during provisioning + * This is set to : + * - WIFI_MODE_APSTA for SoftAP transport + * - WIFI_MODE_STA for BLE transport + */ + wifi_mode_t wifi_mode; +} wifi_prov_scheme_t; + +/** + * @brief Structure for specifying the manager configuration + */ +typedef struct { + /** + * Provisioning scheme to use. Following schemes are already available: + * - wifi_prov_scheme_ble : for provisioning over BLE transport + GATT server + * - wifi_prov_scheme_softap : for provisioning over SoftAP transport + HTTP server + mDNS (optional) + * - wifi_prov_scheme_console : for provisioning over Serial UART transport + Console (for debugging) + */ + wifi_prov_scheme_t scheme; + + /** + * Event handler required by the scheme for incorporating scheme specific + * behavior while provisioning manager is running. Various options may be + * provided by the scheme for setting this field. Use WIFI_PROV_EVENT_HANDLER_NONE + * when not used. When using scheme wifi_prov_scheme_ble, the following + * options are available: + * - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM + * - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE + * - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT + */ + wifi_prov_event_handler_t scheme_event_handler; + + /** + * Event handler that can be set for the purpose of incorporating application + * specific behavior. Use WIFI_PROV_EVENT_HANDLER_NONE when not used. + */ + wifi_prov_event_handler_t app_event_handler; +} wifi_prov_mgr_config_t; + +/** + * @brief Security modes supported by the Provisioning Manager. + * + * These are same as the security modes provided by protocomm + */ +typedef enum wifi_prov_security { + /** + * No security (plain-text communication) + */ + WIFI_PROV_SECURITY_0 = 0, + + /** + * This secure communication mode consists of + * X25519 key exchange + * + proof of possession (pop) based authentication + * + AES-CTR encryption + */ + WIFI_PROV_SECURITY_1 +} wifi_prov_security_t; + +/** + * @brief Initialize provisioning manager instance + * + * Configures the manager and allocates internal resources + * + * Configuration specifies the provisioning scheme (transport) + * and event handlers + * + * Event WIFI_PROV_INIT is emitted right after initialization + * is complete + * + * @param[in] config Configuration structure + * + * @return + * - ESP_OK : Success + * - ESP_FAIL : Fail + */ +esp_err_t wifi_prov_mgr_init(wifi_prov_mgr_config_t config); + +/** + * @brief Stop provisioning (if running) and release + * resource used by the manager + * + * Event WIFI_PROV_DEINIT is emitted right after de-initialization + * is finished + * + * If provisioning service is still active when this API is called, + * it first stops the service, hence emitting WIFI_PROV_END, and + * then performs the de-initialization + */ +void wifi_prov_mgr_deinit(void); + +/** + * @brief Checks if device is provisioned + * + * This checks if Wi-Fi credentials are present on the NVS + * + * The Wi-Fi credentials are assumed to be kept in the same + * NVS namespace as used by esp_wifi component + * + * If one were to call esp_wifi_set_config() directly instead + * of going through the provisioning process, this function will + * still yield true (i.e. device will be found to be provisioned) + * + * @note Calling wifi_prov_mgr_start_provisioning() automatically + * resets the provision state, irrespective of what the + * state was prior to making the call. + * + * @param[out] provisioned True if provisioned, else false + * + * @return + * - ESP_OK : Retrieved provision state successfully + * - ESP_FAIL : Wi-Fi not initialized + * - ESP_ERR_INVALID_ARG : Null argument supplied + * - ESP_ERR_INVALID_STATE : Manager not initialized + */ +esp_err_t wifi_prov_mgr_is_provisioned(bool *provisioned); + +/** + * @brief Start provisioning service + * + * This starts the provisioning service according to the scheme + * configured at the time of initialization. For scheme : + * - wifi_prov_scheme_ble : This starts protocomm_ble, which internally initializes + * BLE transport and starts GATT server for handling + * provisioning requests + * - wifi_prov_scheme_softap : This activates SoftAP mode of Wi-Fi and starts + * protocomm_httpd, which internally starts an HTTP + * server for handling provisioning requests (If mDNS is + * active it also starts advertising service with type + * _esp_wifi_prov._tcp) + * + * Event WIFI_PROV_START is emitted right after provisioning starts without failure + * + * @note This API will start provisioning service even if device is found to be + * already provisioned, i.e. wifi_prov_mgr_is_provisioned() yields true + * + * @param[in] security Specify which protocomm security scheme to use : + * - WIFI_PROV_SECURITY_0 : For no security + * - WIFI_PROV_SECURITY_1 : x25519 secure handshake for session + * establishment followed by AES-CTR encryption of provisioning messages + * @param[in] pop Pointer to proof of possession string (NULL if not needed). This + * is relevant only for protocomm security 1, in which case it is used + * for authenticating secure session + * @param[in] service_name Unique name of the service. This translates to: + * - Wi-Fi SSID when provisioning mode is softAP + * - Device name when provisioning mode is BLE + * @param[in] service_key Key required by client to access the service (NULL if not needed). + * This translates to: + * - Wi-Fi password when provisioning mode is softAP + * - ignored when provisioning mode is BLE + * + * @return + * - ESP_OK : Provisioning started successfully + * - ESP_FAIL : Failed to start provisioning service + * - ESP_ERR_INVALID_STATE : Provisioning manager not initialized or already started + */ +esp_err_t wifi_prov_mgr_start_provisioning(wifi_prov_security_t security, const char *pop, + const char *service_name, const char *service_key); + +/** + * @brief Stop provisioning service + * + * If provisioning service is active, this API will initiate a process to stop + * the service and return. Once the service actually stops, the event WIFI_PROV_END + * will be emitted. + * + * If wifi_prov_mgr_deinit() is called without calling this API first, it will + * automatically stop the provisioning service and emit the WIFI_PROV_END, followed + * by WIFI_PROV_DEINIT, before returning. + * + * This API will generally be used along with wifi_prov_mgr_disable_auto_stop() + * in the scenario when the main application has registered its own endpoints, + * and wishes that the provisioning service is stopped only when some protocomm + * command from the client side application is received. + * + * Calling this API inside an endpoint handler, with sufficient cleanup_delay, + * will allow the response / acknowledgment to be sent successfully before the + * underlying protocomm service is stopped. + * + * Cleaup_delay is set when calling wifi_prov_mgr_disable_auto_stop(). + * If not specified, it defaults to 1000ms. + * + * For straightforward cases, using this API is usually not necessary as + * provisioning is stopped automatically once WIFI_PROV_CRED_SUCCESS is emitted. + * Stopping is delayed (maximum 30 seconds) thus allowing the client side + * application to query for Wi-Fi state, i.e. after receiving the first query + * and sending `Wi-Fi state connected` response the service is stopped immediately. + */ +void wifi_prov_mgr_stop_provisioning(void); + +/** + * @brief Wait for provisioning service to finish + * + * Calling this API will block until provisioning service is stopped + * i.e. till event WIFI_PROV_END is emitted. + * + * This will not block if provisioning is not started or not initialized. + */ +void wifi_prov_mgr_wait(void); + +/** + * @brief Disable auto stopping of provisioning service upon completion + * + * By default, once provisioning is complete, the provisioning service is automatically + * stopped, and all endpoints (along with those registered by main application) are + * deactivated. + * + * This API is useful in the case when main application wishes to close provisioning service + * only after it receives some protocomm command from the client side app. For example, after + * connecting to Wi-Fi, the device may want to connect to the cloud, and only once that is + * successfully, the device is said to be fully configured. But, then it is upto the main + * application to explicitly call wifi_prov_mgr_stop_provisioning() later when the device is + * fully configured and the provisioning service is no longer required. + * + * @note This must be called before executing wifi_prov_mgr_start_provisioning() + * + * @param[in] cleanup_delay Sets the delay after which the actual cleanup of transport related + * resources is done after a call to wifi_prov_mgr_stop_provisioning() + * returns. Minimum allowed value is 100ms. If not specified, this will + * default to 1000ms. + * + * @return + * - ESP_OK : Success + * - ESP_ERR_INVALID_STATE : Manager not initialized or + * provisioning service already started + */ +esp_err_t wifi_prov_mgr_disable_auto_stop(uint32_t cleanup_delay); + +/** + * @brief Set application version and capabilities in the JSON data returned by + * proto-ver endpoint + * + * This function can be called multiple times, to specify information about the various + * application specific services running on the device, identified by unique labels. + * + * The provisioning service itself registers an entry in the JSON data, by the label "prov", + * containing only provisioning service version and capabilities. Application services should + * use a label other than "prov" so as not to overwrite this. + * + * @note This must be called before executing wifi_prov_mgr_start_provisioning() + * + * @param[in] label String indicating the application name. + * + * @param[in] version String indicating the application version. + * There is no constraint on format. + * + * @param[in] capabilities Array of strings with capabilities. + * These could be used by the client side app to know + * the application registered endpoint capabilities + * + * @param[in] total_capabilities Size of capabilities array + * + * @return + * - ESP_OK : Success + * - ESP_ERR_INVALID_STATE : Manager not initialized or + * provisioning service already started + * - ESP_ERR_NO_MEM : Failed to allocate memory for version string + * - ESP_ERR_INVALID_ARG : Null argument + */ +esp_err_t wifi_prov_mgr_set_app_info(const char *label, const char *version, + const char**capabilities, size_t total_capabilities); + +/** + * @brief Create an additional endpoint and allocate internal resources for it + * + * This API is to be called by the application if it wants to create an additional + * endpoint. All additional endpoints will be assigned UUIDs starting from 0xFF54 + * and so on in the order of execution. + * + * protocomm handler for the created endpoint is to be registered later using + * wifi_prov_mgr_endpoint_register() after provisioning has started. + * + * @note This API can only be called BEFORE provisioning is started + * + * @note Additional endpoints can be used for configuring client provided + * parameters other than Wi-Fi credentials, that are necessary for the + * main application and hence must be set prior to starting the application + * + * @note After session establishment, the additional endpoints must be targeted + * first by the client side application before sending Wi-Fi configuration, + * because once Wi-Fi configuration finishes the provisioning service is + * stopped and hence all endpoints are unregistered + * + * @param[in] ep_name unique name of the endpoint + * + * @return + * - ESP_OK : Success + * - ESP_FAIL : Failure + */ +esp_err_t wifi_prov_mgr_endpoint_create(const char *ep_name); + +/** + * @brief Register a handler for the previously created endpoint + * + * This API can be called by the application to register a protocomm handler + * to any endpoint that was created using wifi_prov_mgr_endpoint_create(). + * + * @note This API can only be called AFTER provisioning has started + * + * @note Additional endpoints can be used for configuring client provided + * parameters other than Wi-Fi credentials, that are necessary for the + * main application and hence must be set prior to starting the application + * + * @note After session establishment, the additional endpoints must be targeted + * first by the client side application before sending Wi-Fi configuration, + * because once Wi-Fi configuration finishes the provisioning service is + * stopped and hence all endpoints are unregistered + * + * @param[in] ep_name Name of the endpoint + * @param[in] handler Endpoint handler function + * @param[in] user_ctx User data + * + * @return + * - ESP_OK : Success + * - ESP_FAIL : Failure + */ +esp_err_t wifi_prov_mgr_endpoint_register(const char *ep_name, + protocomm_req_handler_t handler, + void *user_ctx); + +/** + * @brief Unregister the handler for an endpoint + * + * This API can be called if the application wants to selectively + * unregister the handler of an endpoint while the provisioning + * is still in progress. + * + * All the endpoint handlers are unregistered automatically when + * the provisioning stops. + * + * @param[in] ep_name Name of the endpoint + */ +void wifi_prov_mgr_endpoint_unregister(const char *ep_name); + +/** + * @brief Event handler for provisioning manager + * + * This is called from the main event handler and controls the + * provisioning manager's internal state machine depending on + * incoming Wi-Fi events + * + * @note : This function is DEPRECATED, because events are now + * handled internally using the event loop library, esp_event. + * Calling this will do nothing and simply return ESP_OK. + * + * @param[in] ctx Event context data + * @param[in] event Event info + * + * @return + * - ESP_OK : Event handled successfully + */ +esp_err_t wifi_prov_mgr_event_handler(void *ctx, system_event_t *event) __attribute__ ((deprecated)); + +/** + * @brief Get state of Wi-Fi Station during provisioning + * + * @param[out] state Pointer to wifi_prov_sta_state_t + * variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved Wi-Fi state + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t wifi_prov_mgr_get_wifi_state(wifi_prov_sta_state_t *state); + +/** + * @brief Get reason code in case of Wi-Fi station + * disconnection during provisioning + * +* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t +* variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved Wi-Fi disconnect reason + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t wifi_prov_mgr_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t *reason); + +/** + * @brief Runs Wi-Fi as Station with the supplied configuration + * + * Configures the Wi-Fi station mode to connect to the AP with + * SSID and password specified in config structure and sets + * Wi-Fi to run as station. + * + * This is automatically called by provisioning service upon + * receiving new credentials. + * + * If credentials are to be supplied to the manager via a + * different mode other than through protocomm, then this + * API needs to be called. + * + * Event WIFI_PROV_CRED_RECV is emitted after credentials have + * been applied and Wi-Fi station started + * + * @param[in] wifi_cfg Pointer to Wi-Fi configuration structure + * + * @return + * - ESP_OK : Wi-Fi configured and started successfully + * - ESP_FAIL : Failed to set configuration + */ +esp_err_t wifi_prov_mgr_configure_sta(wifi_config_t *wifi_cfg); + +#ifdef __cplusplus +} +#endif diff --git a/components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h b/components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h new file mode 100644 index 00000000..ed30d818 --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/scheme_ble.h @@ -0,0 +1,82 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "wifi_provisioning/manager.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Scheme that can be used by manager for provisioning + * over BLE transport with GATT server + */ +extern const wifi_prov_scheme_t wifi_prov_scheme_ble; + +/* This scheme specific event handler is to be used when application + * doesn't require BT and BLE after provisioning has finished */ +#define WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM { \ + .event_cb = wifi_prov_scheme_ble_event_cb_free_btdm, \ + .user_data = NULL \ +} + +/* This scheme specific event handler is to be used when application + * doesn't require BLE to be active after provisioning has finished */ +#define WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE { \ + .event_cb = wifi_prov_scheme_ble_event_cb_free_ble, \ + .user_data = NULL \ +} + +/* This scheme specific event handler is to be used when application + * doesn't require BT to be active after provisioning has finished */ +#define WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT { \ + .event_cb = wifi_prov_scheme_ble_event_cb_free_bt, \ + .user_data = NULL \ +} + +void wifi_prov_scheme_ble_event_cb_free_btdm(void *user_data, wifi_prov_cb_event_t event, void *event_data); +void wifi_prov_scheme_ble_event_cb_free_ble (void *user_data, wifi_prov_cb_event_t event, void *event_data); +void wifi_prov_scheme_ble_event_cb_free_bt (void *user_data, wifi_prov_cb_event_t event, void *event_data); + +/** + * @brief Set the 128 bit GATT service UUID used for provisioning + * + * This API is used to override the default 128 bit provisioning + * service UUID, which is 0000ffff-0000-1000-8000-00805f9b34fb. + * + * This must be called before starting provisioning, i.e. before + * making a call to wifi_prov_mgr_start_provisioning(), otherwise + * the default UUID will be used. + * + * @note The data being pointed to by the argument must be valid + * atleast till provisioning is started. Upon start, the + * manager will store an internal copy of this UUID, and + * this data can be freed or invalidated afterwords. + * + * @param[in] uuid128 A custom 128 bit UUID + * + * @return + * - ESP_OK : Success + * - ESP_ERR_INVALID_ARG : Null argument + */ +esp_err_t wifi_prov_scheme_ble_set_service_uuid(uint8_t *uuid128); + +#ifdef __cplusplus +} +#endif diff --git a/components/wifi_provisioning/include/wifi_provisioning/scheme_console.h b/components/wifi_provisioning/include/wifi_provisioning/scheme_console.h new file mode 100644 index 00000000..760fe324 --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/scheme_console.h @@ -0,0 +1,34 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "wifi_provisioning/manager.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Scheme that can be used by manager for provisioning + * over console (Serial UART) + */ +extern const wifi_prov_scheme_t wifi_prov_scheme_console; + +#ifdef __cplusplus +} +#endif diff --git a/components/wifi_provisioning/include/wifi_provisioning/scheme_softap.h b/components/wifi_provisioning/include/wifi_provisioning/scheme_softap.h new file mode 100644 index 00000000..eda48be0 --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/scheme_softap.h @@ -0,0 +1,47 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "wifi_provisioning/manager.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Scheme that can be used by manager for provisioning + * over SoftAP transport with HTTP server + */ +extern const wifi_prov_scheme_t wifi_prov_scheme_softap; + +/** + * + * @brief Provide HTTPD Server handle externally. + * + * Useful in cases wherein applications need the webserver for some + * different operations, and do not want the wifi provisioning component + * to start/stop a new instance. + * + * @note This API should be called before wifi_prov_mgr_start_provisioning() + * + * @param[in] handle Handle to HTTPD server instance + */ +void wifi_prov_scheme_softap_set_httpd_handle(void *handle); +#ifdef __cplusplus +} +#endif diff --git a/components/wifi_provisioning/include/wifi_provisioning/wifi_config.h b/components/wifi_provisioning/include/wifi_provisioning/wifi_config.h index 115bde6e..2fa64448 100644 --- a/components/wifi_provisioning/include/wifi_provisioning/wifi_config.h +++ b/components/wifi_provisioning/include/wifi_provisioning/wifi_config.h @@ -16,7 +16,7 @@ #define _WIFI_PROV_CONFIG_H_ #include -#include + #ifdef __cplusplus extern "C" { #endif @@ -76,7 +76,7 @@ typedef struct { */ typedef struct { char ssid[33]; /*!< SSID of the AP to which the slave is to be connected */ - char password[65]; /*!< Password of the AP */ + char password[64]; /*!< Password of the AP */ char bssid[6]; /*!< BSSID of the AP */ uint8_t channel; /*!< Channel of the AP */ } wifi_prov_config_set_data_t; diff --git a/components/wifi_provisioning/include/wifi_provisioning/wifi_scan.h b/components/wifi_provisioning/include/wifi_provisioning/wifi_scan.h new file mode 100644 index 00000000..a4ebb7e5 --- /dev/null +++ b/components/wifi_provisioning/include/wifi_provisioning/wifi_scan.h @@ -0,0 +1,166 @@ +// 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. + +#ifndef _PROV_WIFI_SCAN_H_ +#define _PROV_WIFI_SCAN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define WIFI_SSID_LEN sizeof(((wifi_ap_record_t *)0)->ssid) +#define WIFI_BSSID_LEN sizeof(((wifi_ap_record_t *)0)->bssid) + +/** + * @brief Type of context data passed to each get/set/apply handler + * function set in `wifi_prov_scan_handlers` structure. + * + * This is passed as an opaque pointer, thereby allowing it be defined + * later in application code as per requirements. + */ +typedef struct wifi_prov_scan_ctx wifi_prov_scan_ctx_t; + +/** + * @brief Structure of entries in the scan results list + */ +typedef struct { + /** + * SSID of Wi-Fi AP + */ + char ssid[WIFI_SSID_LEN]; + + /** + * BSSID of Wi-Fi AP + */ + char bssid[WIFI_BSSID_LEN]; + + /** + * Wi-Fi channel number + */ + uint8_t channel; + + /** + * Signal strength + */ + int rssi; + + /** + * Wi-Fi security mode + */ + uint8_t auth; +} wifi_prov_scan_result_t; + +/** + * @brief Internal handlers for receiving and responding to protocomm + * requests from client + * + * This is to be passed as priv_data for protocomm request handler + * (refer to `wifi_prov_scan_handler()`) when calling `protocomm_add_endpoint()`. + */ +typedef struct wifi_prov_scan_handlers { + /** + * Handler function called when scan start command is received + * with various scan parameters : + * + * blocking (input) - If true, the function should return only + * when the scanning is finished + * + * passive (input) - If true, scan is to be started in passive + * mode (this may be slower) instead of active mode + * + * group_channels (input) - This specifies whether to scan + * all channels in one go (when zero) or perform scanning of + * channels in groups, with 120ms delay between scanning of + * consecutive groups, and the value of this parameter sets the + * number of channels in each group. This is useful when transport + * mode is SoftAP, where scanning all channels in one go may not + * give the Wi-Fi driver enough time to send out beacons, and + * hence may cause disconnection with any connected stations. + * When scanning in groups, the manager will wait for atleast + * 120ms after completing scan on a group of channels, and thus + * allow the driver to send out the beacons. For example, given + * that the total number of Wi-Fi channels is 14, then setting + * group_channels to 4, will create 5 groups, with each group + * having 3 channels, except the last one which will have + * 14 % 3 = 2 channels. So, when scan is started, the first 3 + * channels will be scanned, followed by a 120ms delay, and then + * the next 3 channels, and so on, until all the 14 channels have + * been scanned. One may need to adjust this parameter as having + * only few channels in a group may slow down the overall scan + * time, while having too many may again cause disconnection. + * Usually a value of 4 should work for most cases. Note that + * for any other mode of transport, e.g. BLE, this can be safely + * set to 0, and hence achieve the fastest overall scanning time. + * + * period_ms (input) - Scan parameter specifying how long to + * wait on each channel (in milli-seconds) + */ + esp_err_t (*scan_start)(bool blocking, bool passive, + uint8_t group_channels, uint32_t period_ms, + wifi_prov_scan_ctx_t **ctx); + + /** + * Handler function called when scan status is requested. Status + * is given the parameters : + * + * scan_finished (output) - When scan has finished this returns true + * + * result_count (output) - This gives the total number of results + * obtained till now. If scan is yet happening this number will + * keep on updating + */ + esp_err_t (*scan_status)(bool *scan_finished, + uint16_t *result_count, + wifi_prov_scan_ctx_t **ctx); + + /** + * Handler function called when scan result is requested. Parameters : + * + * scan_result - For fetching scan results. This can be called even + * if scan is still on going + * + * start_index (input) - Starting index from where to fetch the + * entries from the results list + * + * count (input) - Number of entries to fetch from the starting index + * + * entries (output) - List of entries returned. Each entry consists + * of ssid, channel and rssi information + */ + esp_err_t (*scan_result)(uint16_t result_index, + wifi_prov_scan_result_t *result, + wifi_prov_scan_ctx_t **ctx); + + /** + * Context pointer to be passed to above handler functions upon invocation + */ + wifi_prov_scan_ctx_t *ctx; +} wifi_prov_scan_handlers_t; + +/** + * @brief Handler for sending on demand Wi-Fi scan results + * + * This is to be registered as the `prov-scan` endpoint handler + * (protocomm `protocomm_req_handler_t`) using `protocomm_add_endpoint()` + */ +esp_err_t wifi_prov_scan_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, void *priv_data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/wifi_provisioning/proto-c/wifi_scan.pb-c.c b/components/wifi_provisioning/proto-c/wifi_scan.pb-c.c new file mode 100644 index 00000000..4cf5b5dc --- /dev/null +++ b/components/wifi_provisioning/proto-c/wifi_scan.pb-c.c @@ -0,0 +1,878 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: wifi_scan.proto */ + +/* Do not generate deprecated warnings for self */ +#ifndef PROTOBUF_C__NO_DEPRECATED +#define PROTOBUF_C__NO_DEPRECATED +#endif + +#include "wifi_scan.pb-c.h" +void cmd_scan_start__init + (CmdScanStart *message) +{ + static const CmdScanStart init_value = CMD_SCAN_START__INIT; + *message = init_value; +} +size_t cmd_scan_start__get_packed_size + (const CmdScanStart *message) +{ + assert(message->base.descriptor == &cmd_scan_start__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t cmd_scan_start__pack + (const CmdScanStart *message, + uint8_t *out) +{ + assert(message->base.descriptor == &cmd_scan_start__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t cmd_scan_start__pack_to_buffer + (const CmdScanStart *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &cmd_scan_start__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CmdScanStart * + cmd_scan_start__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CmdScanStart *) + protobuf_c_message_unpack (&cmd_scan_start__descriptor, + allocator, len, data); +} +void cmd_scan_start__free_unpacked + (CmdScanStart *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &cmd_scan_start__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void resp_scan_start__init + (RespScanStart *message) +{ + static const RespScanStart init_value = RESP_SCAN_START__INIT; + *message = init_value; +} +size_t resp_scan_start__get_packed_size + (const RespScanStart *message) +{ + assert(message->base.descriptor == &resp_scan_start__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t resp_scan_start__pack + (const RespScanStart *message, + uint8_t *out) +{ + assert(message->base.descriptor == &resp_scan_start__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t resp_scan_start__pack_to_buffer + (const RespScanStart *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &resp_scan_start__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +RespScanStart * + resp_scan_start__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (RespScanStart *) + protobuf_c_message_unpack (&resp_scan_start__descriptor, + allocator, len, data); +} +void resp_scan_start__free_unpacked + (RespScanStart *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &resp_scan_start__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void cmd_scan_status__init + (CmdScanStatus *message) +{ + static const CmdScanStatus init_value = CMD_SCAN_STATUS__INIT; + *message = init_value; +} +size_t cmd_scan_status__get_packed_size + (const CmdScanStatus *message) +{ + assert(message->base.descriptor == &cmd_scan_status__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t cmd_scan_status__pack + (const CmdScanStatus *message, + uint8_t *out) +{ + assert(message->base.descriptor == &cmd_scan_status__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t cmd_scan_status__pack_to_buffer + (const CmdScanStatus *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &cmd_scan_status__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CmdScanStatus * + cmd_scan_status__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CmdScanStatus *) + protobuf_c_message_unpack (&cmd_scan_status__descriptor, + allocator, len, data); +} +void cmd_scan_status__free_unpacked + (CmdScanStatus *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &cmd_scan_status__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void resp_scan_status__init + (RespScanStatus *message) +{ + static const RespScanStatus init_value = RESP_SCAN_STATUS__INIT; + *message = init_value; +} +size_t resp_scan_status__get_packed_size + (const RespScanStatus *message) +{ + assert(message->base.descriptor == &resp_scan_status__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t resp_scan_status__pack + (const RespScanStatus *message, + uint8_t *out) +{ + assert(message->base.descriptor == &resp_scan_status__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t resp_scan_status__pack_to_buffer + (const RespScanStatus *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &resp_scan_status__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +RespScanStatus * + resp_scan_status__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (RespScanStatus *) + protobuf_c_message_unpack (&resp_scan_status__descriptor, + allocator, len, data); +} +void resp_scan_status__free_unpacked + (RespScanStatus *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &resp_scan_status__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void cmd_scan_result__init + (CmdScanResult *message) +{ + static const CmdScanResult init_value = CMD_SCAN_RESULT__INIT; + *message = init_value; +} +size_t cmd_scan_result__get_packed_size + (const CmdScanResult *message) +{ + assert(message->base.descriptor == &cmd_scan_result__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t cmd_scan_result__pack + (const CmdScanResult *message, + uint8_t *out) +{ + assert(message->base.descriptor == &cmd_scan_result__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t cmd_scan_result__pack_to_buffer + (const CmdScanResult *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &cmd_scan_result__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +CmdScanResult * + cmd_scan_result__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (CmdScanResult *) + protobuf_c_message_unpack (&cmd_scan_result__descriptor, + allocator, len, data); +} +void cmd_scan_result__free_unpacked + (CmdScanResult *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &cmd_scan_result__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void wi_fi_scan_result__init + (WiFiScanResult *message) +{ + static const WiFiScanResult init_value = WI_FI_SCAN_RESULT__INIT; + *message = init_value; +} +size_t wi_fi_scan_result__get_packed_size + (const WiFiScanResult *message) +{ + assert(message->base.descriptor == &wi_fi_scan_result__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t wi_fi_scan_result__pack + (const WiFiScanResult *message, + uint8_t *out) +{ + assert(message->base.descriptor == &wi_fi_scan_result__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t wi_fi_scan_result__pack_to_buffer + (const WiFiScanResult *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &wi_fi_scan_result__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +WiFiScanResult * + wi_fi_scan_result__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (WiFiScanResult *) + protobuf_c_message_unpack (&wi_fi_scan_result__descriptor, + allocator, len, data); +} +void wi_fi_scan_result__free_unpacked + (WiFiScanResult *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &wi_fi_scan_result__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void resp_scan_result__init + (RespScanResult *message) +{ + static const RespScanResult init_value = RESP_SCAN_RESULT__INIT; + *message = init_value; +} +size_t resp_scan_result__get_packed_size + (const RespScanResult *message) +{ + assert(message->base.descriptor == &resp_scan_result__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t resp_scan_result__pack + (const RespScanResult *message, + uint8_t *out) +{ + assert(message->base.descriptor == &resp_scan_result__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t resp_scan_result__pack_to_buffer + (const RespScanResult *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &resp_scan_result__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +RespScanResult * + resp_scan_result__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (RespScanResult *) + protobuf_c_message_unpack (&resp_scan_result__descriptor, + allocator, len, data); +} +void resp_scan_result__free_unpacked + (RespScanResult *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &resp_scan_result__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +void wi_fi_scan_payload__init + (WiFiScanPayload *message) +{ + static const WiFiScanPayload init_value = WI_FI_SCAN_PAYLOAD__INIT; + *message = init_value; +} +size_t wi_fi_scan_payload__get_packed_size + (const WiFiScanPayload *message) +{ + assert(message->base.descriptor == &wi_fi_scan_payload__descriptor); + return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message)); +} +size_t wi_fi_scan_payload__pack + (const WiFiScanPayload *message, + uint8_t *out) +{ + assert(message->base.descriptor == &wi_fi_scan_payload__descriptor); + return protobuf_c_message_pack ((const ProtobufCMessage*)message, out); +} +size_t wi_fi_scan_payload__pack_to_buffer + (const WiFiScanPayload *message, + ProtobufCBuffer *buffer) +{ + assert(message->base.descriptor == &wi_fi_scan_payload__descriptor); + return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer); +} +WiFiScanPayload * + wi_fi_scan_payload__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data) +{ + return (WiFiScanPayload *) + protobuf_c_message_unpack (&wi_fi_scan_payload__descriptor, + allocator, len, data); +} +void wi_fi_scan_payload__free_unpacked + (WiFiScanPayload *message, + ProtobufCAllocator *allocator) +{ + if(!message) + return; + assert(message->base.descriptor == &wi_fi_scan_payload__descriptor); + protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); +} +static const ProtobufCFieldDescriptor cmd_scan_start__field_descriptors[4] = +{ + { + "blocking", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BOOL, + 0, /* quantifier_offset */ + offsetof(CmdScanStart, blocking), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "passive", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BOOL, + 0, /* quantifier_offset */ + offsetof(CmdScanStart, passive), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "group_channels", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_UINT32, + 0, /* quantifier_offset */ + offsetof(CmdScanStart, group_channels), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "period_ms", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_UINT32, + 0, /* quantifier_offset */ + offsetof(CmdScanStart, period_ms), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned cmd_scan_start__field_indices_by_name[] = { + 0, /* field[0] = blocking */ + 2, /* field[2] = group_channels */ + 1, /* field[1] = passive */ + 3, /* field[3] = period_ms */ +}; +static const ProtobufCIntRange cmd_scan_start__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 4 } +}; +const ProtobufCMessageDescriptor cmd_scan_start__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CmdScanStart", + "CmdScanStart", + "CmdScanStart", + "", + sizeof(CmdScanStart), + 4, + cmd_scan_start__field_descriptors, + cmd_scan_start__field_indices_by_name, + 1, cmd_scan_start__number_ranges, + (ProtobufCMessageInit) cmd_scan_start__init, + NULL,NULL,NULL /* reserved[123] */ +}; +#define resp_scan_start__field_descriptors NULL +#define resp_scan_start__field_indices_by_name NULL +#define resp_scan_start__number_ranges NULL +const ProtobufCMessageDescriptor resp_scan_start__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "RespScanStart", + "RespScanStart", + "RespScanStart", + "", + sizeof(RespScanStart), + 0, + resp_scan_start__field_descriptors, + resp_scan_start__field_indices_by_name, + 0, resp_scan_start__number_ranges, + (ProtobufCMessageInit) resp_scan_start__init, + NULL,NULL,NULL /* reserved[123] */ +}; +#define cmd_scan_status__field_descriptors NULL +#define cmd_scan_status__field_indices_by_name NULL +#define cmd_scan_status__number_ranges NULL +const ProtobufCMessageDescriptor cmd_scan_status__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CmdScanStatus", + "CmdScanStatus", + "CmdScanStatus", + "", + sizeof(CmdScanStatus), + 0, + cmd_scan_status__field_descriptors, + cmd_scan_status__field_indices_by_name, + 0, cmd_scan_status__number_ranges, + (ProtobufCMessageInit) cmd_scan_status__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor resp_scan_status__field_descriptors[2] = +{ + { + "scan_finished", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BOOL, + 0, /* quantifier_offset */ + offsetof(RespScanStatus, scan_finished), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "result_count", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_UINT32, + 0, /* quantifier_offset */ + offsetof(RespScanStatus, result_count), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned resp_scan_status__field_indices_by_name[] = { + 1, /* field[1] = result_count */ + 0, /* field[0] = scan_finished */ +}; +static const ProtobufCIntRange resp_scan_status__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor resp_scan_status__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "RespScanStatus", + "RespScanStatus", + "RespScanStatus", + "", + sizeof(RespScanStatus), + 2, + resp_scan_status__field_descriptors, + resp_scan_status__field_indices_by_name, + 1, resp_scan_status__number_ranges, + (ProtobufCMessageInit) resp_scan_status__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor cmd_scan_result__field_descriptors[2] = +{ + { + "start_index", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_UINT32, + 0, /* quantifier_offset */ + offsetof(CmdScanResult, start_index), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "count", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_UINT32, + 0, /* quantifier_offset */ + offsetof(CmdScanResult, count), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned cmd_scan_result__field_indices_by_name[] = { + 1, /* field[1] = count */ + 0, /* field[0] = start_index */ +}; +static const ProtobufCIntRange cmd_scan_result__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 2 } +}; +const ProtobufCMessageDescriptor cmd_scan_result__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "CmdScanResult", + "CmdScanResult", + "CmdScanResult", + "", + sizeof(CmdScanResult), + 2, + cmd_scan_result__field_descriptors, + cmd_scan_result__field_indices_by_name, + 1, cmd_scan_result__number_ranges, + (ProtobufCMessageInit) cmd_scan_result__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor wi_fi_scan_result__field_descriptors[5] = +{ + { + "ssid", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(WiFiScanResult, ssid), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "channel", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_UINT32, + 0, /* quantifier_offset */ + offsetof(WiFiScanResult, channel), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "rssi", + 3, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_INT32, + 0, /* quantifier_offset */ + offsetof(WiFiScanResult, rssi), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "bssid", + 4, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_BYTES, + 0, /* quantifier_offset */ + offsetof(WiFiScanResult, bssid), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "auth", + 5, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(WiFiScanResult, auth), + &wifi_auth_mode__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned wi_fi_scan_result__field_indices_by_name[] = { + 4, /* field[4] = auth */ + 3, /* field[3] = bssid */ + 1, /* field[1] = channel */ + 2, /* field[2] = rssi */ + 0, /* field[0] = ssid */ +}; +static const ProtobufCIntRange wi_fi_scan_result__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 5 } +}; +const ProtobufCMessageDescriptor wi_fi_scan_result__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "WiFiScanResult", + "WiFiScanResult", + "WiFiScanResult", + "", + sizeof(WiFiScanResult), + 5, + wi_fi_scan_result__field_descriptors, + wi_fi_scan_result__field_indices_by_name, + 1, wi_fi_scan_result__number_ranges, + (ProtobufCMessageInit) wi_fi_scan_result__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor resp_scan_result__field_descriptors[1] = +{ + { + "entries", + 1, + PROTOBUF_C_LABEL_REPEATED, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(RespScanResult, n_entries), + offsetof(RespScanResult, entries), + &wi_fi_scan_result__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned resp_scan_result__field_indices_by_name[] = { + 0, /* field[0] = entries */ +}; +static const ProtobufCIntRange resp_scan_result__number_ranges[1 + 1] = +{ + { 1, 0 }, + { 0, 1 } +}; +const ProtobufCMessageDescriptor resp_scan_result__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "RespScanResult", + "RespScanResult", + "RespScanResult", + "", + sizeof(RespScanResult), + 1, + resp_scan_result__field_descriptors, + resp_scan_result__field_indices_by_name, + 1, resp_scan_result__number_ranges, + (ProtobufCMessageInit) resp_scan_result__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCFieldDescriptor wi_fi_scan_payload__field_descriptors[8] = +{ + { + "msg", + 1, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(WiFiScanPayload, msg), + &wi_fi_scan_msg_type__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "status", + 2, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_ENUM, + 0, /* quantifier_offset */ + offsetof(WiFiScanPayload, status), + &status__descriptor, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "cmd_scan_start", + 10, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiScanPayload, payload_case), + offsetof(WiFiScanPayload, cmd_scan_start), + &cmd_scan_start__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "resp_scan_start", + 11, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiScanPayload, payload_case), + offsetof(WiFiScanPayload, resp_scan_start), + &resp_scan_start__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "cmd_scan_status", + 12, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiScanPayload, payload_case), + offsetof(WiFiScanPayload, cmd_scan_status), + &cmd_scan_status__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "resp_scan_status", + 13, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiScanPayload, payload_case), + offsetof(WiFiScanPayload, resp_scan_status), + &resp_scan_status__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "cmd_scan_result", + 14, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiScanPayload, payload_case), + offsetof(WiFiScanPayload, cmd_scan_result), + &cmd_scan_result__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "resp_scan_result", + 15, + PROTOBUF_C_LABEL_NONE, + PROTOBUF_C_TYPE_MESSAGE, + offsetof(WiFiScanPayload, payload_case), + offsetof(WiFiScanPayload, resp_scan_result), + &resp_scan_result__descriptor, + NULL, + 0 | PROTOBUF_C_FIELD_FLAG_ONEOF, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, +}; +static const unsigned wi_fi_scan_payload__field_indices_by_name[] = { + 6, /* field[6] = cmd_scan_result */ + 2, /* field[2] = cmd_scan_start */ + 4, /* field[4] = cmd_scan_status */ + 0, /* field[0] = msg */ + 7, /* field[7] = resp_scan_result */ + 3, /* field[3] = resp_scan_start */ + 5, /* field[5] = resp_scan_status */ + 1, /* field[1] = status */ +}; +static const ProtobufCIntRange wi_fi_scan_payload__number_ranges[2 + 1] = +{ + { 1, 0 }, + { 10, 2 }, + { 0, 8 } +}; +const ProtobufCMessageDescriptor wi_fi_scan_payload__descriptor = +{ + PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC, + "WiFiScanPayload", + "WiFiScanPayload", + "WiFiScanPayload", + "", + sizeof(WiFiScanPayload), + 8, + wi_fi_scan_payload__field_descriptors, + wi_fi_scan_payload__field_indices_by_name, + 2, wi_fi_scan_payload__number_ranges, + (ProtobufCMessageInit) wi_fi_scan_payload__init, + NULL,NULL,NULL /* reserved[123] */ +}; +static const ProtobufCEnumValue wi_fi_scan_msg_type__enum_values_by_number[6] = +{ + { "TypeCmdScanStart", "WI_FI_SCAN_MSG_TYPE__TypeCmdScanStart", 0 }, + { "TypeRespScanStart", "WI_FI_SCAN_MSG_TYPE__TypeRespScanStart", 1 }, + { "TypeCmdScanStatus", "WI_FI_SCAN_MSG_TYPE__TypeCmdScanStatus", 2 }, + { "TypeRespScanStatus", "WI_FI_SCAN_MSG_TYPE__TypeRespScanStatus", 3 }, + { "TypeCmdScanResult", "WI_FI_SCAN_MSG_TYPE__TypeCmdScanResult", 4 }, + { "TypeRespScanResult", "WI_FI_SCAN_MSG_TYPE__TypeRespScanResult", 5 }, +}; +static const ProtobufCIntRange wi_fi_scan_msg_type__value_ranges[] = { +{0, 0},{0, 6} +}; +static const ProtobufCEnumValueIndex wi_fi_scan_msg_type__enum_values_by_name[6] = +{ + { "TypeCmdScanResult", 4 }, + { "TypeCmdScanStart", 0 }, + { "TypeCmdScanStatus", 2 }, + { "TypeRespScanResult", 5 }, + { "TypeRespScanStart", 1 }, + { "TypeRespScanStatus", 3 }, +}; +const ProtobufCEnumDescriptor wi_fi_scan_msg_type__descriptor = +{ + PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC, + "WiFiScanMsgType", + "WiFiScanMsgType", + "WiFiScanMsgType", + "", + 6, + wi_fi_scan_msg_type__enum_values_by_number, + 6, + wi_fi_scan_msg_type__enum_values_by_name, + 1, + wi_fi_scan_msg_type__value_ranges, + NULL,NULL,NULL,NULL /* reserved[1234] */ +}; diff --git a/components/wifi_provisioning/proto-c/wifi_scan.pb-c.h b/components/wifi_provisioning/proto-c/wifi_scan.pb-c.h new file mode 100644 index 00000000..9caea2c7 --- /dev/null +++ b/components/wifi_provisioning/proto-c/wifi_scan.pb-c.h @@ -0,0 +1,350 @@ +/* Generated by the protocol buffer compiler. DO NOT EDIT! */ +/* Generated from: wifi_scan.proto */ + +#ifndef PROTOBUF_C_wifi_5fscan_2eproto__INCLUDED +#define PROTOBUF_C_wifi_5fscan_2eproto__INCLUDED + +#include + +PROTOBUF_C__BEGIN_DECLS + +#if PROTOBUF_C_VERSION_NUMBER < 1003000 +# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers. +#elif 1003001 < PROTOBUF_C_MIN_COMPILER_VERSION +# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c. +#endif + +#include "constants.pb-c.h" +#include "wifi_constants.pb-c.h" + +typedef struct _CmdScanStart CmdScanStart; +typedef struct _RespScanStart RespScanStart; +typedef struct _CmdScanStatus CmdScanStatus; +typedef struct _RespScanStatus RespScanStatus; +typedef struct _CmdScanResult CmdScanResult; +typedef struct _WiFiScanResult WiFiScanResult; +typedef struct _RespScanResult RespScanResult; +typedef struct _WiFiScanPayload WiFiScanPayload; + + +/* --- enums --- */ + +typedef enum _WiFiScanMsgType { + WI_FI_SCAN_MSG_TYPE__TypeCmdScanStart = 0, + WI_FI_SCAN_MSG_TYPE__TypeRespScanStart = 1, + WI_FI_SCAN_MSG_TYPE__TypeCmdScanStatus = 2, + WI_FI_SCAN_MSG_TYPE__TypeRespScanStatus = 3, + WI_FI_SCAN_MSG_TYPE__TypeCmdScanResult = 4, + WI_FI_SCAN_MSG_TYPE__TypeRespScanResult = 5 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WI_FI_SCAN_MSG_TYPE) +} WiFiScanMsgType; + +/* --- messages --- */ + +struct _CmdScanStart +{ + ProtobufCMessage base; + protobuf_c_boolean blocking; + protobuf_c_boolean passive; + uint32_t group_channels; + uint32_t period_ms; +}; +#define CMD_SCAN_START__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_start__descriptor) \ + , 0, 0, 0, 0 } + + +struct _RespScanStart +{ + ProtobufCMessage base; +}; +#define RESP_SCAN_START__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&resp_scan_start__descriptor) \ + } + + +struct _CmdScanStatus +{ + ProtobufCMessage base; +}; +#define CMD_SCAN_STATUS__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_status__descriptor) \ + } + + +struct _RespScanStatus +{ + ProtobufCMessage base; + protobuf_c_boolean scan_finished; + uint32_t result_count; +}; +#define RESP_SCAN_STATUS__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&resp_scan_status__descriptor) \ + , 0, 0 } + + +struct _CmdScanResult +{ + ProtobufCMessage base; + uint32_t start_index; + uint32_t count; +}; +#define CMD_SCAN_RESULT__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&cmd_scan_result__descriptor) \ + , 0, 0 } + + +struct _WiFiScanResult +{ + ProtobufCMessage base; + ProtobufCBinaryData ssid; + uint32_t channel; + int32_t rssi; + ProtobufCBinaryData bssid; + WifiAuthMode auth; +}; +#define WI_FI_SCAN_RESULT__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&wi_fi_scan_result__descriptor) \ + , {0,NULL}, 0, 0, {0,NULL}, WIFI_AUTH_MODE__Open } + + +struct _RespScanResult +{ + ProtobufCMessage base; + size_t n_entries; + WiFiScanResult **entries; +}; +#define RESP_SCAN_RESULT__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&resp_scan_result__descriptor) \ + , 0,NULL } + + +typedef enum { + WI_FI_SCAN_PAYLOAD__PAYLOAD__NOT_SET = 0, + WI_FI_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_START = 10, + WI_FI_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_START = 11, + WI_FI_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_STATUS = 12, + WI_FI_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_STATUS = 13, + WI_FI_SCAN_PAYLOAD__PAYLOAD_CMD_SCAN_RESULT = 14, + WI_FI_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_RESULT = 15 + PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(WI_FI_SCAN_PAYLOAD__PAYLOAD) +} WiFiScanPayload__PayloadCase; + +struct _WiFiScanPayload +{ + ProtobufCMessage base; + WiFiScanMsgType msg; + Status status; + WiFiScanPayload__PayloadCase payload_case; + union { + CmdScanStart *cmd_scan_start; + RespScanStart *resp_scan_start; + CmdScanStatus *cmd_scan_status; + RespScanStatus *resp_scan_status; + CmdScanResult *cmd_scan_result; + RespScanResult *resp_scan_result; + }; +}; +#define WI_FI_SCAN_PAYLOAD__INIT \ + { PROTOBUF_C_MESSAGE_INIT (&wi_fi_scan_payload__descriptor) \ + , WI_FI_SCAN_MSG_TYPE__TypeCmdScanStart, STATUS__Success, WI_FI_SCAN_PAYLOAD__PAYLOAD__NOT_SET, {0} } + + +/* CmdScanStart methods */ +void cmd_scan_start__init + (CmdScanStart *message); +size_t cmd_scan_start__get_packed_size + (const CmdScanStart *message); +size_t cmd_scan_start__pack + (const CmdScanStart *message, + uint8_t *out); +size_t cmd_scan_start__pack_to_buffer + (const CmdScanStart *message, + ProtobufCBuffer *buffer); +CmdScanStart * + cmd_scan_start__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void cmd_scan_start__free_unpacked + (CmdScanStart *message, + ProtobufCAllocator *allocator); +/* RespScanStart methods */ +void resp_scan_start__init + (RespScanStart *message); +size_t resp_scan_start__get_packed_size + (const RespScanStart *message); +size_t resp_scan_start__pack + (const RespScanStart *message, + uint8_t *out); +size_t resp_scan_start__pack_to_buffer + (const RespScanStart *message, + ProtobufCBuffer *buffer); +RespScanStart * + resp_scan_start__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void resp_scan_start__free_unpacked + (RespScanStart *message, + ProtobufCAllocator *allocator); +/* CmdScanStatus methods */ +void cmd_scan_status__init + (CmdScanStatus *message); +size_t cmd_scan_status__get_packed_size + (const CmdScanStatus *message); +size_t cmd_scan_status__pack + (const CmdScanStatus *message, + uint8_t *out); +size_t cmd_scan_status__pack_to_buffer + (const CmdScanStatus *message, + ProtobufCBuffer *buffer); +CmdScanStatus * + cmd_scan_status__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void cmd_scan_status__free_unpacked + (CmdScanStatus *message, + ProtobufCAllocator *allocator); +/* RespScanStatus methods */ +void resp_scan_status__init + (RespScanStatus *message); +size_t resp_scan_status__get_packed_size + (const RespScanStatus *message); +size_t resp_scan_status__pack + (const RespScanStatus *message, + uint8_t *out); +size_t resp_scan_status__pack_to_buffer + (const RespScanStatus *message, + ProtobufCBuffer *buffer); +RespScanStatus * + resp_scan_status__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void resp_scan_status__free_unpacked + (RespScanStatus *message, + ProtobufCAllocator *allocator); +/* CmdScanResult methods */ +void cmd_scan_result__init + (CmdScanResult *message); +size_t cmd_scan_result__get_packed_size + (const CmdScanResult *message); +size_t cmd_scan_result__pack + (const CmdScanResult *message, + uint8_t *out); +size_t cmd_scan_result__pack_to_buffer + (const CmdScanResult *message, + ProtobufCBuffer *buffer); +CmdScanResult * + cmd_scan_result__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void cmd_scan_result__free_unpacked + (CmdScanResult *message, + ProtobufCAllocator *allocator); +/* WiFiScanResult methods */ +void wi_fi_scan_result__init + (WiFiScanResult *message); +size_t wi_fi_scan_result__get_packed_size + (const WiFiScanResult *message); +size_t wi_fi_scan_result__pack + (const WiFiScanResult *message, + uint8_t *out); +size_t wi_fi_scan_result__pack_to_buffer + (const WiFiScanResult *message, + ProtobufCBuffer *buffer); +WiFiScanResult * + wi_fi_scan_result__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void wi_fi_scan_result__free_unpacked + (WiFiScanResult *message, + ProtobufCAllocator *allocator); +/* RespScanResult methods */ +void resp_scan_result__init + (RespScanResult *message); +size_t resp_scan_result__get_packed_size + (const RespScanResult *message); +size_t resp_scan_result__pack + (const RespScanResult *message, + uint8_t *out); +size_t resp_scan_result__pack_to_buffer + (const RespScanResult *message, + ProtobufCBuffer *buffer); +RespScanResult * + resp_scan_result__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void resp_scan_result__free_unpacked + (RespScanResult *message, + ProtobufCAllocator *allocator); +/* WiFiScanPayload methods */ +void wi_fi_scan_payload__init + (WiFiScanPayload *message); +size_t wi_fi_scan_payload__get_packed_size + (const WiFiScanPayload *message); +size_t wi_fi_scan_payload__pack + (const WiFiScanPayload *message, + uint8_t *out); +size_t wi_fi_scan_payload__pack_to_buffer + (const WiFiScanPayload *message, + ProtobufCBuffer *buffer); +WiFiScanPayload * + wi_fi_scan_payload__unpack + (ProtobufCAllocator *allocator, + size_t len, + const uint8_t *data); +void wi_fi_scan_payload__free_unpacked + (WiFiScanPayload *message, + ProtobufCAllocator *allocator); +/* --- per-message closures --- */ + +typedef void (*CmdScanStart_Closure) + (const CmdScanStart *message, + void *closure_data); +typedef void (*RespScanStart_Closure) + (const RespScanStart *message, + void *closure_data); +typedef void (*CmdScanStatus_Closure) + (const CmdScanStatus *message, + void *closure_data); +typedef void (*RespScanStatus_Closure) + (const RespScanStatus *message, + void *closure_data); +typedef void (*CmdScanResult_Closure) + (const CmdScanResult *message, + void *closure_data); +typedef void (*WiFiScanResult_Closure) + (const WiFiScanResult *message, + void *closure_data); +typedef void (*RespScanResult_Closure) + (const RespScanResult *message, + void *closure_data); +typedef void (*WiFiScanPayload_Closure) + (const WiFiScanPayload *message, + void *closure_data); + +/* --- services --- */ + + +/* --- descriptors --- */ + +extern const ProtobufCEnumDescriptor wi_fi_scan_msg_type__descriptor; +extern const ProtobufCMessageDescriptor cmd_scan_start__descriptor; +extern const ProtobufCMessageDescriptor resp_scan_start__descriptor; +extern const ProtobufCMessageDescriptor cmd_scan_status__descriptor; +extern const ProtobufCMessageDescriptor resp_scan_status__descriptor; +extern const ProtobufCMessageDescriptor cmd_scan_result__descriptor; +extern const ProtobufCMessageDescriptor wi_fi_scan_result__descriptor; +extern const ProtobufCMessageDescriptor resp_scan_result__descriptor; +extern const ProtobufCMessageDescriptor wi_fi_scan_payload__descriptor; + +PROTOBUF_C__END_DECLS + + +#endif /* PROTOBUF_C_wifi_5fscan_2eproto__INCLUDED */ diff --git a/components/wifi_provisioning/proto/CMakeLists.txt b/components/wifi_provisioning/proto/CMakeLists.txt new file mode 100644 index 00000000..316486eb --- /dev/null +++ b/components/wifi_provisioning/proto/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.5) + +set(PROTO_COMPILER "protoc") +set(PROTO_C_COMPILER "protoc-c") +set(C_OUT_PATH "${CMAKE_CURRENT_LIST_DIR}/../proto-c") +set(PY_OUT_PATH "${CMAKE_CURRENT_LIST_DIR}/../python") +set(PROTOCOMM_INCL_PATH "${CMAKE_CURRENT_LIST_DIR}/../../protocomm/proto") + +set(PROTO_SRCS "wifi_constants.proto" + "wifi_config.proto" + "wifi_scan.proto") + +add_custom_target(c_proto + COMMAND ${PROTO_C_COMPILER} --c_out=${C_OUT_PATH} -I . -I ${PROTOCOMM_INCL_PATH} ${PROTO_SRCS} + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) + +add_custom_target(python_proto + COMMAND ${PROTO_COMPILER} --python_out=${PY_OUT_PATH} -I . -I ${PROTOCOMM_INCL_PATH} ${PROTO_SRCS} + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) + +add_custom_target(proto ALL + DEPENDS c_proto python_proto + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) diff --git a/components/wifi_provisioning/proto/README.md b/components/wifi_provisioning/proto/README.md index 10c428be..68f9be20 100644 --- a/components/wifi_provisioning/proto/README.md +++ b/components/wifi_provisioning/proto/README.md @@ -1,7 +1,28 @@ -# Protobuf files for defining Wi-Fi config-data packet structures +# Protobuf files for defining Wi-Fi provisioning packet structures + +`wifi_provisioning` uses Google Protobuf for language, transport and architecture agnostic protocol communication. These proto files define the protocomm packet structure, separated across multiple files: +* wifi_contants.proto - Defines the various enums for indicating state of Wi-Fi (connected / disconnect / connecting), diconnect reasons, auth modes, etc. +* wifi_config.proto - Defines Wi-Fi configuration structures and commands for setting credentials (SSID, passphrase, BSSID), applying credentials and getting connection state. +* wifi_scan.proto - Defines Wi-Fi scan commands and result structures Note : These proto files are not automatically compiled during the build process. -Run "make" (Optional) to generate the respective C and Python files. The generated C files are used by protocomm itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to protocomm layer. +# Compilation -Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under "protocomm/proto-c" and "protocomm/python" directories, and thus running make (and installing the Protobuf compilers) is optional. +Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under `components/wifi_provisioning/proto-c` and `components/wifi_provisioning/python` directories, and thus running cmake / make (and installing the Protobuf compilers) is optional. + +If using `cmake` follow the below steps. If using `make`, jump to Step 2 directly. + +## Step 1 (Only for cmake) + +When using cmake, first create a build directory and call cmake from inside: + +``` +mkdir build +cd build +cmake .. +``` + +## Step 2 + +Simply run `make` to generate the respective C and Python files. The newly created files will overwrite those under `components/wifi_provisioning/proto-c` and `components/wifi_provisioning/python` diff --git a/components/wifi_provisioning/proto/wifi_scan.proto b/components/wifi_provisioning/proto/wifi_scan.proto new file mode 100644 index 00000000..ea240f20 --- /dev/null +++ b/components/wifi_provisioning/proto/wifi_scan.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; + +import "constants.proto"; +import "wifi_constants.proto"; + +message CmdScanStart { + bool blocking = 1; + bool passive = 2; + uint32 group_channels = 3; + uint32 period_ms = 4; +} + +message RespScanStart { + +} + +message CmdScanStatus { + +} + +message RespScanStatus { + bool scan_finished = 1; + uint32 result_count = 2; +} + +message CmdScanResult { + uint32 start_index = 1; + uint32 count = 2; +} + +message WiFiScanResult { + bytes ssid = 1; + uint32 channel = 2; + int32 rssi = 3; + bytes bssid = 4; + WifiAuthMode auth = 5; +} + +message RespScanResult { + repeated WiFiScanResult entries = 1; +} + +enum WiFiScanMsgType { + TypeCmdScanStart = 0; + TypeRespScanStart = 1; + TypeCmdScanStatus = 2; + TypeRespScanStatus = 3; + TypeCmdScanResult = 4; + TypeRespScanResult = 5; +} + +message WiFiScanPayload { + WiFiScanMsgType msg = 1; + Status status = 2; + oneof payload { + CmdScanStart cmd_scan_start = 10; + RespScanStart resp_scan_start = 11; + CmdScanStatus cmd_scan_status = 12; + RespScanStatus resp_scan_status = 13; + CmdScanResult cmd_scan_result = 14; + RespScanResult resp_scan_result = 15; + } +} diff --git a/components/wifi_provisioning/python/wifi_scan_pb2.py b/components/wifi_provisioning/python/wifi_scan_pb2.py new file mode 100644 index 00000000..2e95d8f5 --- /dev/null +++ b/components/wifi_provisioning/python/wifi_scan_pb2.py @@ -0,0 +1,522 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: wifi_scan.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +import constants_pb2 as constants__pb2 +import wifi_constants_pb2 as wifi__constants__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='wifi_scan.proto', + package='', + syntax='proto3', + serialized_options=None, + serialized_pb=_b('\n\x0fwifi_scan.proto\x1a\x0f\x63onstants.proto\x1a\x14wifi_constants.proto\"\\\n\x0c\x43mdScanStart\x12\x10\n\x08\x62locking\x18\x01 \x01(\x08\x12\x0f\n\x07passive\x18\x02 \x01(\x08\x12\x16\n\x0egroup_channels\x18\x03 \x01(\r\x12\x11\n\tperiod_ms\x18\x04 \x01(\r\"\x0f\n\rRespScanStart\"\x0f\n\rCmdScanStatus\"=\n\x0eRespScanStatus\x12\x15\n\rscan_finished\x18\x01 \x01(\x08\x12\x14\n\x0cresult_count\x18\x02 \x01(\r\"3\n\rCmdScanResult\x12\x13\n\x0bstart_index\x18\x01 \x01(\r\x12\r\n\x05\x63ount\x18\x02 \x01(\r\"i\n\x0eWiFiScanResult\x12\x0c\n\x04ssid\x18\x01 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x02 \x01(\r\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\x12\r\n\x05\x62ssid\x18\x04 \x01(\x0c\x12\x1b\n\x04\x61uth\x18\x05 \x01(\x0e\x32\r.WifiAuthMode\"2\n\x0eRespScanResult\x12 \n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x0f.WiFiScanResult\"\xd8\x02\n\x0fWiFiScanPayload\x12\x1d\n\x03msg\x18\x01 \x01(\x0e\x32\x10.WiFiScanMsgType\x12\x17\n\x06status\x18\x02 \x01(\x0e\x32\x07.Status\x12\'\n\x0e\x63md_scan_start\x18\n \x01(\x0b\x32\r.CmdScanStartH\x00\x12)\n\x0fresp_scan_start\x18\x0b \x01(\x0b\x32\x0e.RespScanStartH\x00\x12)\n\x0f\x63md_scan_status\x18\x0c \x01(\x0b\x32\x0e.CmdScanStatusH\x00\x12+\n\x10resp_scan_status\x18\r \x01(\x0b\x32\x0f.RespScanStatusH\x00\x12)\n\x0f\x63md_scan_result\x18\x0e \x01(\x0b\x32\x0e.CmdScanResultH\x00\x12+\n\x10resp_scan_result\x18\x0f \x01(\x0b\x32\x0f.RespScanResultH\x00\x42\t\n\x07payload*\x9c\x01\n\x0fWiFiScanMsgType\x12\x14\n\x10TypeCmdScanStart\x10\x00\x12\x15\n\x11TypeRespScanStart\x10\x01\x12\x15\n\x11TypeCmdScanStatus\x10\x02\x12\x16\n\x12TypeRespScanStatus\x10\x03\x12\x15\n\x11TypeCmdScanResult\x10\x04\x12\x16\n\x12TypeRespScanResult\x10\x05\x62\x06proto3') + , + dependencies=[constants__pb2.DESCRIPTOR,wifi__constants__pb2.DESCRIPTOR,]) + +_WIFISCANMSGTYPE = _descriptor.EnumDescriptor( + name='WiFiScanMsgType', + full_name='WiFiScanMsgType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='TypeCmdScanStart', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeRespScanStart', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeCmdScanStatus', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeRespScanStatus', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeCmdScanResult', index=4, number=4, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TypeRespScanResult', index=5, number=5, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=809, + serialized_end=965, +) +_sym_db.RegisterEnumDescriptor(_WIFISCANMSGTYPE) + +WiFiScanMsgType = enum_type_wrapper.EnumTypeWrapper(_WIFISCANMSGTYPE) +TypeCmdScanStart = 0 +TypeRespScanStart = 1 +TypeCmdScanStatus = 2 +TypeRespScanStatus = 3 +TypeCmdScanResult = 4 +TypeRespScanResult = 5 + + + +_CMDSCANSTART = _descriptor.Descriptor( + name='CmdScanStart', + full_name='CmdScanStart', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='blocking', full_name='CmdScanStart.blocking', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='passive', full_name='CmdScanStart.passive', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='group_channels', full_name='CmdScanStart.group_channels', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='period_ms', full_name='CmdScanStart.period_ms', index=3, + number=4, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=58, + serialized_end=150, +) + + +_RESPSCANSTART = _descriptor.Descriptor( + name='RespScanStart', + full_name='RespScanStart', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=152, + serialized_end=167, +) + + +_CMDSCANSTATUS = _descriptor.Descriptor( + name='CmdScanStatus', + full_name='CmdScanStatus', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=169, + serialized_end=184, +) + + +_RESPSCANSTATUS = _descriptor.Descriptor( + name='RespScanStatus', + full_name='RespScanStatus', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='scan_finished', full_name='RespScanStatus.scan_finished', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='result_count', full_name='RespScanStatus.result_count', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=186, + serialized_end=247, +) + + +_CMDSCANRESULT = _descriptor.Descriptor( + name='CmdScanResult', + full_name='CmdScanResult', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='start_index', full_name='CmdScanResult.start_index', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='count', full_name='CmdScanResult.count', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=249, + serialized_end=300, +) + + +_WIFISCANRESULT = _descriptor.Descriptor( + name='WiFiScanResult', + full_name='WiFiScanResult', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='ssid', full_name='WiFiScanResult.ssid', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='channel', full_name='WiFiScanResult.channel', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='rssi', full_name='WiFiScanResult.rssi', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bssid', full_name='WiFiScanResult.bssid', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='auth', full_name='WiFiScanResult.auth', index=4, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=302, + serialized_end=407, +) + + +_RESPSCANRESULT = _descriptor.Descriptor( + name='RespScanResult', + full_name='RespScanResult', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='entries', full_name='RespScanResult.entries', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=409, + serialized_end=459, +) + + +_WIFISCANPAYLOAD = _descriptor.Descriptor( + name='WiFiScanPayload', + full_name='WiFiScanPayload', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='msg', full_name='WiFiScanPayload.msg', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='status', full_name='WiFiScanPayload.status', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='cmd_scan_start', full_name='WiFiScanPayload.cmd_scan_start', index=2, + number=10, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='resp_scan_start', full_name='WiFiScanPayload.resp_scan_start', index=3, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='cmd_scan_status', full_name='WiFiScanPayload.cmd_scan_status', index=4, + number=12, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='resp_scan_status', full_name='WiFiScanPayload.resp_scan_status', index=5, + number=13, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='cmd_scan_result', full_name='WiFiScanPayload.cmd_scan_result', index=6, + number=14, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='resp_scan_result', full_name='WiFiScanPayload.resp_scan_result', index=7, + number=15, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='payload', full_name='WiFiScanPayload.payload', + index=0, containing_type=None, fields=[]), + ], + serialized_start=462, + serialized_end=806, +) + +_WIFISCANRESULT.fields_by_name['auth'].enum_type = wifi__constants__pb2._WIFIAUTHMODE +_RESPSCANRESULT.fields_by_name['entries'].message_type = _WIFISCANRESULT +_WIFISCANPAYLOAD.fields_by_name['msg'].enum_type = _WIFISCANMSGTYPE +_WIFISCANPAYLOAD.fields_by_name['status'].enum_type = constants__pb2._STATUS +_WIFISCANPAYLOAD.fields_by_name['cmd_scan_start'].message_type = _CMDSCANSTART +_WIFISCANPAYLOAD.fields_by_name['resp_scan_start'].message_type = _RESPSCANSTART +_WIFISCANPAYLOAD.fields_by_name['cmd_scan_status'].message_type = _CMDSCANSTATUS +_WIFISCANPAYLOAD.fields_by_name['resp_scan_status'].message_type = _RESPSCANSTATUS +_WIFISCANPAYLOAD.fields_by_name['cmd_scan_result'].message_type = _CMDSCANRESULT +_WIFISCANPAYLOAD.fields_by_name['resp_scan_result'].message_type = _RESPSCANRESULT +_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFISCANPAYLOAD.fields_by_name['cmd_scan_start']) +_WIFISCANPAYLOAD.fields_by_name['cmd_scan_start'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload'] +_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFISCANPAYLOAD.fields_by_name['resp_scan_start']) +_WIFISCANPAYLOAD.fields_by_name['resp_scan_start'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload'] +_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFISCANPAYLOAD.fields_by_name['cmd_scan_status']) +_WIFISCANPAYLOAD.fields_by_name['cmd_scan_status'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload'] +_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFISCANPAYLOAD.fields_by_name['resp_scan_status']) +_WIFISCANPAYLOAD.fields_by_name['resp_scan_status'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload'] +_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFISCANPAYLOAD.fields_by_name['cmd_scan_result']) +_WIFISCANPAYLOAD.fields_by_name['cmd_scan_result'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload'] +_WIFISCANPAYLOAD.oneofs_by_name['payload'].fields.append( + _WIFISCANPAYLOAD.fields_by_name['resp_scan_result']) +_WIFISCANPAYLOAD.fields_by_name['resp_scan_result'].containing_oneof = _WIFISCANPAYLOAD.oneofs_by_name['payload'] +DESCRIPTOR.message_types_by_name['CmdScanStart'] = _CMDSCANSTART +DESCRIPTOR.message_types_by_name['RespScanStart'] = _RESPSCANSTART +DESCRIPTOR.message_types_by_name['CmdScanStatus'] = _CMDSCANSTATUS +DESCRIPTOR.message_types_by_name['RespScanStatus'] = _RESPSCANSTATUS +DESCRIPTOR.message_types_by_name['CmdScanResult'] = _CMDSCANRESULT +DESCRIPTOR.message_types_by_name['WiFiScanResult'] = _WIFISCANRESULT +DESCRIPTOR.message_types_by_name['RespScanResult'] = _RESPSCANRESULT +DESCRIPTOR.message_types_by_name['WiFiScanPayload'] = _WIFISCANPAYLOAD +DESCRIPTOR.enum_types_by_name['WiFiScanMsgType'] = _WIFISCANMSGTYPE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +CmdScanStart = _reflection.GeneratedProtocolMessageType('CmdScanStart', (_message.Message,), dict( + DESCRIPTOR = _CMDSCANSTART, + __module__ = 'wifi_scan_pb2' + # @@protoc_insertion_point(class_scope:CmdScanStart) + )) +_sym_db.RegisterMessage(CmdScanStart) + +RespScanStart = _reflection.GeneratedProtocolMessageType('RespScanStart', (_message.Message,), dict( + DESCRIPTOR = _RESPSCANSTART, + __module__ = 'wifi_scan_pb2' + # @@protoc_insertion_point(class_scope:RespScanStart) + )) +_sym_db.RegisterMessage(RespScanStart) + +CmdScanStatus = _reflection.GeneratedProtocolMessageType('CmdScanStatus', (_message.Message,), dict( + DESCRIPTOR = _CMDSCANSTATUS, + __module__ = 'wifi_scan_pb2' + # @@protoc_insertion_point(class_scope:CmdScanStatus) + )) +_sym_db.RegisterMessage(CmdScanStatus) + +RespScanStatus = _reflection.GeneratedProtocolMessageType('RespScanStatus', (_message.Message,), dict( + DESCRIPTOR = _RESPSCANSTATUS, + __module__ = 'wifi_scan_pb2' + # @@protoc_insertion_point(class_scope:RespScanStatus) + )) +_sym_db.RegisterMessage(RespScanStatus) + +CmdScanResult = _reflection.GeneratedProtocolMessageType('CmdScanResult', (_message.Message,), dict( + DESCRIPTOR = _CMDSCANRESULT, + __module__ = 'wifi_scan_pb2' + # @@protoc_insertion_point(class_scope:CmdScanResult) + )) +_sym_db.RegisterMessage(CmdScanResult) + +WiFiScanResult = _reflection.GeneratedProtocolMessageType('WiFiScanResult', (_message.Message,), dict( + DESCRIPTOR = _WIFISCANRESULT, + __module__ = 'wifi_scan_pb2' + # @@protoc_insertion_point(class_scope:WiFiScanResult) + )) +_sym_db.RegisterMessage(WiFiScanResult) + +RespScanResult = _reflection.GeneratedProtocolMessageType('RespScanResult', (_message.Message,), dict( + DESCRIPTOR = _RESPSCANRESULT, + __module__ = 'wifi_scan_pb2' + # @@protoc_insertion_point(class_scope:RespScanResult) + )) +_sym_db.RegisterMessage(RespScanResult) + +WiFiScanPayload = _reflection.GeneratedProtocolMessageType('WiFiScanPayload', (_message.Message,), dict( + DESCRIPTOR = _WIFISCANPAYLOAD, + __module__ = 'wifi_scan_pb2' + # @@protoc_insertion_point(class_scope:WiFiScanPayload) + )) +_sym_db.RegisterMessage(WiFiScanPayload) + + +# @@protoc_insertion_point(module_scope) diff --git a/components/wifi_provisioning/src/handlers.c b/components/wifi_provisioning/src/handlers.c new file mode 100644 index 00000000..66f138a2 --- /dev/null +++ b/components/wifi_provisioning/src/handlers.c @@ -0,0 +1,199 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include + +#include "wifi_provisioning/wifi_config.h" +#include "wifi_provisioning/wifi_scan.h" +#include "wifi_provisioning/manager.h" +#include "wifi_provisioning_priv.h" + +static const char *TAG = "wifi_prov_handlers"; + +/* Provide definition of wifi_prov_ctx_t */ +struct wifi_prov_ctx { + wifi_config_t wifi_cfg; +}; + +static wifi_config_t *get_config(wifi_prov_ctx_t **ctx) +{ + return (*ctx ? & (*ctx)->wifi_cfg : NULL); +} + +static wifi_config_t *new_config(wifi_prov_ctx_t **ctx) +{ + free(*ctx); + (*ctx) = (wifi_prov_ctx_t *) calloc(1, sizeof(wifi_prov_ctx_t)); + return get_config(ctx); +} + +static void free_config(wifi_prov_ctx_t **ctx) +{ + free(*ctx); + *ctx = NULL; +} + +static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data, wifi_prov_ctx_t **ctx) +{ + /* Initialize to zero */ + memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t)); + + if (wifi_prov_mgr_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { + ESP_LOGW(TAG, "Wi-Fi provisioning manager not running"); + return ESP_ERR_INVALID_STATE; + } + + if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) { + ESP_LOGD(TAG, "Got state : connected"); + + /* IP Addr assigned to STA */ + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info); + esp_ip4addr_ntoa(&ip_info.ip, resp_data->conn_info.ip_addr, sizeof(resp_data->conn_info.ip_addr)); + + + /* AP information to which STA is connected */ + wifi_ap_record_t ap_info; + esp_wifi_sta_get_ap_info(&ap_info); + memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); + memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); + resp_data->conn_info.channel = ap_info.primary; + resp_data->conn_info.auth_mode = ap_info.authmode; + + /* Tell manager to stop provisioning service */ + wifi_prov_mgr_done(); + } else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + ESP_LOGD(TAG, "Got state : disconnected"); + + /* If disconnected, convey reason */ + wifi_prov_mgr_get_wifi_disconnect_reason(&resp_data->fail_reason); + } else { + ESP_LOGD(TAG, "Got state : connecting"); + } + return ESP_OK; +} + +static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data, wifi_prov_ctx_t **ctx) +{ + wifi_config_t *wifi_cfg = get_config(ctx); + if (wifi_cfg) { + free_config(ctx); + } + + wifi_cfg = new_config(ctx); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Unable to allocate Wi-Fi config"); + return ESP_ERR_NO_MEM; + } + + ESP_LOGD(TAG, "Wi-Fi Credentials Received"); + + /* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard). + * But this doesn't guarantee that the saved SSID will be null terminated, because + * wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character) */ + strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid)); + + /* Using strlcpy allows both max passphrase length (63 bytes) and ensures null termination + * because size of wifi_cfg->sta.password is 64 bytes (1 extra byte for null character) */ + strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password)); + return ESP_OK; +} + +static esp_err_t apply_config_handler(wifi_prov_ctx_t **ctx) +{ + wifi_config_t *wifi_cfg = get_config(ctx); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Wi-Fi config not set"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = wifi_prov_mgr_configure_sta(wifi_cfg); + if (ret == ESP_OK) { + ESP_LOGD(TAG, "Wi-Fi Credentials Applied"); + } else { + ESP_LOGE(TAG, "Failed to apply Wi-Fi Credentials"); + } + + free_config(ctx); + return ret; +} + +esp_err_t get_wifi_prov_handlers(wifi_prov_config_handlers_t *ptr) +{ + if (!ptr) { + return ESP_ERR_INVALID_ARG; + } + ptr->get_status_handler = get_status_handler; + ptr->set_config_handler = set_config_handler; + ptr->apply_config_handler = apply_config_handler; + ptr->ctx = NULL; + return ESP_OK; +} + +/*************************************************************************/ + +static esp_err_t scan_start(bool blocking, bool passive, + uint8_t group_channels, uint32_t period_ms, + wifi_prov_scan_ctx_t **ctx) +{ + return wifi_prov_mgr_wifi_scan_start(blocking, passive, group_channels, period_ms); +} + +static esp_err_t scan_status(bool *scan_finished, + uint16_t *result_count, + wifi_prov_scan_ctx_t **ctx) +{ + *scan_finished = wifi_prov_mgr_wifi_scan_finished(); + *result_count = wifi_prov_mgr_wifi_scan_result_count(); + return ESP_OK; +} + +static esp_err_t scan_result(uint16_t result_index, + wifi_prov_scan_result_t *result, + wifi_prov_scan_ctx_t **ctx) +{ + const wifi_ap_record_t *record = wifi_prov_mgr_wifi_scan_result(result_index); + if (!record) { + return ESP_FAIL; + } + + /* Compile time check ensures memory safety in case SSID length in + * record / result structure definition changes in future */ + _Static_assert(sizeof(result->ssid) == sizeof(record->ssid), + "source and destination should be of same size"); + memcpy(result->ssid, record->ssid, sizeof(record->ssid)); + memcpy(result->bssid, record->bssid, sizeof(record->bssid)); + result->channel = record->primary; + result->rssi = record->rssi; + result->auth = record->authmode; + return ESP_OK; +} + +esp_err_t get_wifi_scan_handlers(wifi_prov_scan_handlers_t *ptr) +{ + if (!ptr) { + return ESP_ERR_INVALID_ARG; + } + ptr->scan_start = scan_start; + ptr->scan_status = scan_status; + ptr->scan_result = scan_result; + ptr->ctx = NULL; + return ESP_OK; +} diff --git a/components/wifi_provisioning/src/manager.c b/components/wifi_provisioning/src/manager.c new file mode 100644 index 00000000..c36b3a8a --- /dev/null +++ b/components/wifi_provisioning/src/manager.c @@ -0,0 +1,1554 @@ +// 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); +} diff --git a/components/wifi_provisioning/src/scheme_ble.c b/components/wifi_provisioning/src/scheme_ble.c new file mode 100644 index 00000000..6faf49bd --- /dev/null +++ b/components/wifi_provisioning/src/scheme_ble.c @@ -0,0 +1,232 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include + +#include "wifi_provisioning/scheme_ble.h" +#include "wifi_provisioning_priv.h" + +static const char *TAG = "wifi_prov_scheme_ble"; + +extern const wifi_prov_scheme_t wifi_prov_scheme_ble; + +static uint8_t *custom_service_uuid; + +static esp_err_t prov_start(protocomm_t *pc, void *config) +{ + if (!pc) { + ESP_LOGE(TAG, "Protocomm handle cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + if (!config) { + ESP_LOGE(TAG, "Cannot start with null configuration"); + return ESP_ERR_INVALID_ARG; + } + + protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; + + /* Start protocomm as BLE service */ + if (protocomm_ble_start(pc, ble_config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm BLE service"); + return ESP_FAIL; + } + return ESP_OK; +} + +esp_err_t wifi_prov_scheme_ble_set_service_uuid(uint8_t *uuid128) +{ + if (!uuid128) { + return ESP_ERR_INVALID_ARG; + } + custom_service_uuid = uuid128; + return ESP_OK; +} + +static void *new_config(void) +{ + protocomm_ble_config_t *ble_config = calloc(1, sizeof(protocomm_ble_config_t)); + if (!ble_config) { + ESP_LOGE(TAG, "Error allocating memory for new configuration"); + return NULL; + } + + /* The default provisioning service UUID */ + const uint8_t service_uuid[16] = { + /* LSB <--------------------------------------- + * ---------------------------------------> MSB */ + 0x07, 0xed, 0x9b, 0x2d, 0x0f, 0x06, 0x7c, 0x87, + 0x9b, 0x43, 0x43, 0x6b, 0x4d, 0x24, 0x75, 0x17, + }; + + memcpy(ble_config->service_uuid, service_uuid, sizeof(ble_config->service_uuid)); + return ble_config; +} + +static void delete_config(void *config) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot delete null configuration"); + return; + } + + protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; + for (unsigned int i = 0; i < ble_config->nu_lookup_count; i++) { + free((void *)ble_config->nu_lookup[i].name); + } + free(ble_config->nu_lookup); + free(ble_config); +} + +static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot set null configuration"); + return ESP_ERR_INVALID_ARG; + } + + if (!service_name) { + ESP_LOGE(TAG, "Service name cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + + protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; + strlcpy(ble_config->device_name, service_name, sizeof(ble_config->device_name)); + + /* If a custom service UUID has been provided, override the default one */ + if (custom_service_uuid) { + memcpy(ble_config->service_uuid, custom_service_uuid, sizeof(ble_config->service_uuid)); + } + return ESP_OK; +} + +static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot set null configuration"); + return ESP_ERR_INVALID_ARG; + } + + if (!endpoint_name) { + ESP_LOGE(TAG, "EP name cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + protocomm_ble_config_t *ble_config = (protocomm_ble_config_t *) config; + + char *copy_ep_name = strdup(endpoint_name); + if (!copy_ep_name) { + ESP_LOGE(TAG, "Error allocating memory for EP name"); + return ESP_ERR_NO_MEM; + } + + protocomm_ble_name_uuid_t *lookup_table = ( + realloc(ble_config->nu_lookup, (ble_config->nu_lookup_count + 1) * sizeof(protocomm_ble_name_uuid_t))); + if (!lookup_table) { + ESP_LOGE(TAG, "Error allocating memory for EP-UUID lookup table"); + return ESP_ERR_NO_MEM; + } + + lookup_table[ble_config->nu_lookup_count].name = copy_ep_name; + lookup_table[ble_config->nu_lookup_count].uuid = uuid; + ble_config->nu_lookup = lookup_table; + ble_config->nu_lookup_count += 1; + return ESP_OK; +} + +/* Used when both BT and BLE are not needed by application */ +void wifi_prov_scheme_ble_event_cb_free_btdm(void *user_data, wifi_prov_cb_event_t event, void *event_data) +{ + esp_err_t err; + switch (event) { + case WIFI_PROV_INIT: + /* Release BT memory, as we need only BLE */ + err = esp_bt_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bt_mem_release of classic BT failed %d", err); + } else { + ESP_LOGI(TAG, "BT memory released"); + } + break; + + case WIFI_PROV_DEINIT: + /* Release memory used by BLE and Bluedroid host stack */ + err = esp_bt_mem_release(ESP_BT_MODE_BTDM); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bt_mem_release of BTDM failed %d", err); + } else { + ESP_LOGI(TAG, "BTDM memory released"); + } + break; + + default: + break; + } +} + +/* Used when BT is not needed by application */ +void wifi_prov_scheme_ble_event_cb_free_bt(void *user_data, wifi_prov_cb_event_t event, void *event_data) +{ + esp_err_t err; + switch (event) { + case WIFI_PROV_INIT: + /* Release BT memory, as we need only BLE */ + err = esp_bt_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bt_mem_release of classic BT failed %d", err); + } else { + ESP_LOGI(TAG, "BT memory released"); + } + break; + + default: + break; + } +} + +/* Used when BLE is not needed by application */ +void wifi_prov_scheme_ble_event_cb_free_ble(void *user_data, wifi_prov_cb_event_t event, void *event_data) +{ + esp_err_t err; + switch (event) { + case WIFI_PROV_DEINIT: + /* Release memory used by BLE stack */ + err = esp_bt_mem_release(ESP_BT_MODE_BLE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "bt_mem_release of BLE failed %d", err); + } else { + ESP_LOGI(TAG, "BLE memory released"); + } + break; + + default: + break; + } +} + +const wifi_prov_scheme_t wifi_prov_scheme_ble = { + .prov_start = prov_start, + .prov_stop = protocomm_ble_stop, + .new_config = new_config, + .delete_config = delete_config, + .set_config_service = set_config_service, + .set_config_endpoint = set_config_endpoint, + .wifi_mode = WIFI_MODE_STA +}; diff --git a/components/wifi_provisioning/src/scheme_console.c b/components/wifi_provisioning/src/scheme_console.c new file mode 100644 index 00000000..28732847 --- /dev/null +++ b/components/wifi_provisioning/src/scheme_console.c @@ -0,0 +1,92 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include + +#include "wifi_provisioning/scheme_console.h" +#include "wifi_provisioning_priv.h" + +static const char *TAG = "wifi_prov_scheme_console"; + +extern const wifi_prov_scheme_t wifi_prov_scheme_console; + +static esp_err_t prov_start(protocomm_t *pc, void *config) +{ + if (!pc) { + ESP_LOGE(TAG, "Protocomm handle cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + if (!config) { + ESP_LOGE(TAG, "Cannot start with null configuration"); + return ESP_ERR_INVALID_ARG; + } + + protocomm_console_config_t *console_config = (protocomm_console_config_t *) config; + + /* Start protocomm console */ + esp_err_t err = protocomm_console_start(pc, console_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); + return ESP_FAIL; + } + return ESP_OK; +} + +static void *new_config(void) +{ + protocomm_console_config_t *console_config = malloc(sizeof(protocomm_console_config_t)); + if (!console_config) { + ESP_LOGE(TAG, "Error allocating memory for new configuration"); + return NULL; + } + protocomm_console_config_t default_config = PROTOCOMM_CONSOLE_DEFAULT_CONFIG(); + memcpy(console_config, &default_config, sizeof(default_config)); + return console_config; +} + +static void delete_config(void *config) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot delete null configuration"); + return; + } + free(config); +} + +static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) +{ + return ESP_OK; +} + +static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) +{ + return ESP_OK; +} + +const wifi_prov_scheme_t wifi_prov_scheme_console = { + .prov_start = prov_start, + .prov_stop = protocomm_console_stop, + .new_config = new_config, + .delete_config = delete_config, + .set_config_service = set_config_service, + .set_config_endpoint = set_config_endpoint, + .wifi_mode = WIFI_MODE_STA +}; diff --git a/components/wifi_provisioning/src/scheme_softap.c b/components/wifi_provisioning/src/scheme_softap.c new file mode 100644 index 00000000..2b0c3c24 --- /dev/null +++ b/components/wifi_provisioning/src/scheme_softap.c @@ -0,0 +1,207 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include +#include + +#include "wifi_provisioning/scheme_softap.h" +#include "wifi_provisioning_priv.h" + +typedef struct softap_config { + protocomm_httpd_config_t httpd_config; + char ssid[33]; + char password[65]; +} wifi_prov_softap_config_t; + +static const char *TAG = "wifi_prov_scheme_softap"; + +extern const wifi_prov_scheme_t wifi_prov_scheme_softap; +static void *scheme_softap_prov_httpd_handle; +static esp_err_t start_wifi_ap(const char *ssid, const char *pass) +{ + /* Build Wi-Fi configuration for AP mode */ + wifi_config_t wifi_config = { + .ap = { + .max_connection = 5, + }, + }; + + strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); + wifi_config.ap.ssid_len = strnlen(ssid, sizeof(wifi_config.ap.ssid)); + + if (strlen(pass) == 0) { + memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password)); + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } else { + strlcpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; + } + + /* Run Wi-Fi in AP + STA mode with configuration built above */ + esp_err_t err = esp_wifi_set_mode(WIFI_MODE_APSTA); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi mode : %d", err); + return err; + } + err = esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set Wi-Fi config : %d", err); + return err; + } + + return ESP_OK; +} + +static esp_err_t prov_start(protocomm_t *pc, void *config) +{ + if (!pc) { + ESP_LOGE(TAG, "Protocomm handle cannot be null"); + return ESP_ERR_INVALID_ARG; + } + + if (!config) { + ESP_LOGE(TAG, "Cannot start with null configuration"); + return ESP_ERR_INVALID_ARG; + } + + wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config; + + protocomm_httpd_config_t *httpd_config = &softap_config->httpd_config; + + if (scheme_softap_prov_httpd_handle) { + httpd_config->ext_handle_provided = true; + httpd_config->data.handle = scheme_softap_prov_httpd_handle; + } + + /* Start protocomm server on top of HTTP */ + esp_err_t err = protocomm_httpd_start(pc, httpd_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start protocomm HTTP server"); + return err; + } + + /* Start Wi-Fi softAP with specified ssid and password */ + err = start_wifi_ap(softap_config->ssid, softap_config->password); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start Wi-Fi AP"); + protocomm_httpd_stop(pc); + return err; + } + + /* Add mDNS service for allowing discovery of provisioning + * service on the SoftAP network (Optional). Even though + * this is an http service we identify it by _esp_wifi_prov so + * that application is free to use _http without conflict */ + err = mdns_service_add("Wi-Fi Provisioning Service", "_esp_wifi_prov", "_tcp", + softap_config->httpd_config.data.config.port, NULL, 0); + if (err != ESP_OK) { + /* mDNS is not mandatory for provisioning to work, + * so print warning and return without failure */ + ESP_LOGW(TAG, "Error adding mDNS service! Check if mDNS is running"); + } else { + /* Information to identify the roles of the various + * protocomm endpoint URIs provided by the service */ + err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "version_endpoint", "/proto-ver"); + err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "session_endpoint", "/prov-session"); + err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "config_endpoint", "/prov-config"); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error adding mDNS service text item"); + } + } + return ESP_OK; +} + +static esp_err_t prov_stop(protocomm_t *pc) +{ + esp_err_t err = protocomm_httpd_stop(pc); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Error occurred while stopping protocomm_httpd"); + } + + mdns_service_remove("_esp_wifi_prov", "_tcp"); + return err; +} + +static void *new_config(void) +{ + wifi_prov_softap_config_t *softap_config = calloc(1, sizeof(wifi_prov_softap_config_t)); + if (!softap_config) { + ESP_LOGE(TAG, "Error allocating memory for new configuration"); + return NULL; + } + protocomm_httpd_config_t default_config = { + .data = { + .config = PROTOCOMM_HTTPD_DEFAULT_CONFIG() + } + }; + softap_config->httpd_config = default_config; + return softap_config; +} + +static void delete_config(void *config) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot delete null configuration"); + return; + } + + wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config; + free(softap_config); +} + +static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key) +{ + if (!config) { + ESP_LOGE(TAG, "Cannot set null configuration"); + return ESP_ERR_INVALID_ARG; + } + + if (!service_name) { + ESP_LOGE(TAG, "Service name cannot be NULL"); + return ESP_ERR_INVALID_ARG; + } + + wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config; + strlcpy(softap_config->ssid, service_name, sizeof(softap_config->ssid)); + if (service_key) { + strlcpy(softap_config->password, service_key, sizeof(softap_config->password)); + } + return ESP_OK; +} + +static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid) +{ + return ESP_OK; +} + +void wifi_prov_scheme_softap_set_httpd_handle(void *handle) +{ + scheme_softap_prov_httpd_handle = handle; +} + +const wifi_prov_scheme_t wifi_prov_scheme_softap = { + .prov_start = prov_start, + .prov_stop = prov_stop, + .new_config = new_config, + .delete_config = delete_config, + .set_config_service = set_config_service, + .set_config_endpoint = set_config_endpoint, + .wifi_mode = WIFI_MODE_APSTA +}; diff --git a/components/wifi_provisioning/src/wifi_config.c b/components/wifi_provisioning/src/wifi_config.c index 09ccc37d..93e3e856 100644 --- a/components/wifi_provisioning/src/wifi_config.c +++ b/components/wifi_provisioning/src/wifi_config.c @@ -151,15 +151,36 @@ static esp_err_t cmd_set_config_handler(WiFiConfigPayload *req, wifi_prov_config_set_data_t req_data; memset(&req_data, 0, sizeof(req_data)); - memcpy(req_data.ssid, req->cmd_set_config->ssid.data, - req->cmd_set_config->ssid.len); - memcpy(req_data.password, req->cmd_set_config->passphrase.data, - req->cmd_set_config->passphrase.len); - memcpy(req_data.bssid, req->cmd_set_config->bssid.data, - req->cmd_set_config->bssid.len); - req_data.channel = req->cmd_set_config->channel; - if (h->set_config_handler(&req_data, &h->ctx) == ESP_OK) { - resp_payload->status = STATUS__Success; + + /* Check arguments provided in protobuf packet: + * - SSID / Passphrase string length must be within the standard limits + * - BSSID must either be NULL or have length equal to that imposed by the standard + * If any of these conditions are not satisfied, don't invoke the handler and + * send error status without closing connection */ + resp_payload->status = STATUS__InvalidArgument; + if (req->cmd_set_config->bssid.len != 0 && + req->cmd_set_config->bssid.len != sizeof(req_data.bssid)) { + ESP_LOGD(TAG, "Received invalid BSSID"); + } else if (req->cmd_set_config->ssid.len >= sizeof(req_data.ssid)) { + ESP_LOGD(TAG, "Received invalid SSID"); + } else if (req->cmd_set_config->passphrase.len >= sizeof(req_data.password)) { + ESP_LOGD(TAG, "Received invalid Passphrase"); + } else { + /* The received SSID and Passphrase are not NULL terminated so + * we memcpy over zeroed out arrays. Above length checks ensure + * that there is atleast 1 extra byte for null termination */ + memcpy(req_data.ssid, req->cmd_set_config->ssid.data, + req->cmd_set_config->ssid.len); + memcpy(req_data.password, req->cmd_set_config->passphrase.data, + req->cmd_set_config->passphrase.len); + memcpy(req_data.bssid, req->cmd_set_config->bssid.data, + req->cmd_set_config->bssid.len); + req_data.channel = req->cmd_set_config->channel; + if (h->set_config_handler(&req_data, &h->ctx) == ESP_OK) { + resp_payload->status = STATUS__Success; + } else { + resp_payload->status = STATUS__InternalError; + } } resp->payload_case = WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_CONFIG; @@ -188,7 +209,7 @@ static esp_err_t cmd_apply_config_handler(WiFiConfigPayload *req, if (h->apply_config_handler(&h->ctx) == ESP_OK) { resp_payload->status = STATUS__Success; } else { - resp_payload->status = STATUS__InvalidArgument; + resp_payload->status = STATUS__InternalError; } resp->payload_case = WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_APPLY_CONFIG; diff --git a/components/wifi_provisioning/src/wifi_provisioning_priv.h b/components/wifi_provisioning/src/wifi_provisioning_priv.h new file mode 100644 index 00000000..a607b49d --- /dev/null +++ b/components/wifi_provisioning/src/wifi_provisioning_priv.h @@ -0,0 +1,102 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "wifi_provisioning/manager.h" +#include "wifi_provisioning/wifi_config.h" +#include "wifi_provisioning/wifi_scan.h" + +/** + * @brief Notify manager that provisioning is done + * + * Stops the provisioning. This is called by the get_status_handler() + * when the status is connected. This has no effect if main application + * has disabled auto stop on completion by calling + * wifi_prov_mgr_disable_auto_stop() + * + * @return + * - ESP_OK : Provisioning will be stopped + * - ESP_FAIL : Failed to stop provisioning + */ +esp_err_t wifi_prov_mgr_done(void); + +/** + * @brief Start Wi-Fi AP Scan + * + * @param[in] blocking Set true to return only after scanning is complete + * @param[in] passive Set true to perform passive scan instead of default active scan + * @param[in] group_channels Number of channels to scan in one go + * (set to 0 for scanning all channels in one go) + * @param[in] period_ms Scan time (in milli-seconds) on each channel + * + * @return + * - ESP_OK : Successfully started Wi-Fi scanning + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t wifi_prov_mgr_wifi_scan_start(bool blocking, bool passive, + uint8_t group_channels, + uint32_t period_ms); + +/** + * @brief Use to query the state of Wi-Fi scan + * + * @return + * - true : Scan finished + * - false : Scan running + */ +bool wifi_prov_mgr_wifi_scan_finished(void); + +/** + * @brief Get the count of results in the scan list + * + * @return + * - count : Number of Wi-Fi Access Points detected while scanning + */ +uint16_t wifi_prov_mgr_wifi_scan_result_count(void); + +/** + * @brief Get AP record for a particular index in the scan list result + * + * @param[out] index Index of the result to fetch + * + * @return + * - result : Pointer to Access Point record + */ +const wifi_ap_record_t *wifi_prov_mgr_wifi_scan_result(uint16_t index); + +/** + * @brief Get protocomm handlers for wifi_config provisioning endpoint + * + * @param[out] ptr pointer to structure to be set + * + * @return + * - ESP_OK : success + * - ESP_ERR_INVALID_ARG : null argument + */ +esp_err_t get_wifi_prov_handlers(wifi_prov_config_handlers_t *ptr); + +/** + * @brief Get protocomm handlers for wifi_scan provisioning endpoint + * + * @param[out] ptr pointer to structure to be set + * + * @return + * - ESP_OK : success + * - ESP_ERR_INVALID_ARG : null argument + */ +esp_err_t get_wifi_scan_handlers(wifi_prov_scan_handlers_t *ptr); diff --git a/components/wifi_provisioning/src/wifi_scan.c b/components/wifi_provisioning/src/wifi_scan.c new file mode 100644 index 00000000..8ab3980d --- /dev/null +++ b/components/wifi_provisioning/src/wifi_scan.c @@ -0,0 +1,297 @@ +// 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 "wifi_scan.pb-c.h" + +#include + +static const char *TAG = "proto_wifi_scan"; + +typedef struct wifi_prov_scan_cmd { + int cmd_num; + esp_err_t (*command_handler)(WiFiScanPayload *req, + WiFiScanPayload *resp, void *priv_data); +} wifi_prov_scan_cmd_t; + +static esp_err_t cmd_scan_start_handler(WiFiScanPayload *req, + WiFiScanPayload *resp, + void *priv_data); + +static esp_err_t cmd_scan_status_handler(WiFiScanPayload *req, + WiFiScanPayload *resp, + void *priv_data); + +static esp_err_t cmd_scan_result_handler(WiFiScanPayload *req, + WiFiScanPayload *resp, + void *priv_data); + +static wifi_prov_scan_cmd_t cmd_table[] = { + { + .cmd_num = WI_FI_SCAN_MSG_TYPE__TypeCmdScanStart, + .command_handler = cmd_scan_start_handler + }, + { + .cmd_num = WI_FI_SCAN_MSG_TYPE__TypeCmdScanStatus, + .command_handler = cmd_scan_status_handler + }, + { + .cmd_num = WI_FI_SCAN_MSG_TYPE__TypeCmdScanResult, + .command_handler = cmd_scan_result_handler + } +}; + +static esp_err_t cmd_scan_start_handler(WiFiScanPayload *req, + WiFiScanPayload *resp, void *priv_data) +{ + wifi_prov_scan_handlers_t *h = (wifi_prov_scan_handlers_t *) priv_data; + if (!h) { + ESP_LOGE(TAG, "Command invoked without handlers"); + return ESP_ERR_INVALID_STATE; + } + + RespScanStart *resp_payload = (RespScanStart *) malloc(sizeof(RespScanStart)); + if (!resp_payload) { + ESP_LOGE(TAG, "Error allocating memory"); + return ESP_ERR_NO_MEM; + } + + resp_scan_start__init(resp_payload); + resp->status = (h->scan_start(req->cmd_scan_start->blocking, + req->cmd_scan_start->passive, + req->cmd_scan_start->group_channels, + req->cmd_scan_start->period_ms, + &h->ctx) == ESP_OK ? + STATUS__Success : STATUS__InternalError); + resp->payload_case = WI_FI_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_START; + resp->resp_scan_start = resp_payload; + return ESP_OK; +} + +static esp_err_t cmd_scan_status_handler(WiFiScanPayload *req, + WiFiScanPayload *resp, void *priv_data) +{ + bool scan_finished = false; + uint16_t result_count = 0; + + wifi_prov_scan_handlers_t *h = (wifi_prov_scan_handlers_t *) priv_data; + if (!h) { + ESP_LOGE(TAG, "Command invoked without handlers"); + return ESP_ERR_INVALID_STATE; + } + + RespScanStatus *resp_payload = (RespScanStatus *) malloc(sizeof(RespScanStatus)); + if (!resp_payload) { + ESP_LOGE(TAG, "Error allocating memory"); + return ESP_ERR_NO_MEM; + } + + resp_scan_status__init(resp_payload); + resp->status = (h->scan_status(&scan_finished, &result_count, &h->ctx) == ESP_OK ? + STATUS__Success : STATUS__InternalError); + resp_payload->scan_finished = scan_finished; + resp_payload->result_count = result_count; + resp->payload_case = WI_FI_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_STATUS; + resp->resp_scan_status = resp_payload; + return ESP_OK; +} + +static esp_err_t cmd_scan_result_handler(WiFiScanPayload *req, + WiFiScanPayload *resp, void *priv_data) +{ + esp_err_t err; + wifi_prov_scan_result_t scan_result = {{0}, {0}, 0, 0, 0}; + WiFiScanResult **results = NULL; + wifi_prov_scan_handlers_t *h = (wifi_prov_scan_handlers_t *) priv_data; + if (!h) { + ESP_LOGE(TAG, "Command invoked without handlers"); + return ESP_ERR_INVALID_STATE; + } + + RespScanResult *resp_payload = (RespScanResult *) malloc(sizeof(RespScanResult)); + if (!resp_payload) { + ESP_LOGE(TAG, "Error allocating memory"); + return ESP_ERR_NO_MEM; + } + resp_scan_result__init(resp_payload); + + resp->status = STATUS__Success; + resp->payload_case = WI_FI_SCAN_PAYLOAD__PAYLOAD_RESP_SCAN_RESULT; + resp->resp_scan_result = resp_payload; + + results = (WiFiScanResult **) calloc(req->cmd_scan_result->count, + sizeof(WiFiScanResult *)); + if (!results) { + ESP_LOGE(TAG, "Failed to allocate memory for results array"); + return ESP_ERR_NO_MEM; + } + resp_payload->entries = results; + resp_payload->n_entries = req->cmd_scan_result->count; + + for (uint16_t i = 0; i < req->cmd_scan_result->count; i++) { + err = h->scan_result(i + req->cmd_scan_result->start_index, + &scan_result, &h->ctx); + if (err != ESP_OK) { + resp->status = STATUS__InternalError; + break; + } + + results[i] = (WiFiScanResult *) malloc(sizeof(WiFiScanResult)); + if (!results[i]) { + ESP_LOGE(TAG, "Failed to allocate memory for result entry"); + return ESP_ERR_NO_MEM; + } + wi_fi_scan_result__init(results[i]); + + results[i]->ssid.len = strnlen(scan_result.ssid, 32); + results[i]->ssid.data = (uint8_t *) strndup(scan_result.ssid, 32); + if (!results[i]->ssid.data) { + ESP_LOGE(TAG, "Failed to allocate memory for scan result entry SSID"); + return ESP_ERR_NO_MEM; + } + + results[i]->channel = scan_result.channel; + results[i]->rssi = scan_result.rssi; + results[i]->auth = scan_result.auth; + + results[i]->bssid.len = sizeof(scan_result.bssid); + results[i]->bssid.data = malloc(results[i]->bssid.len); + if (!results[i]->bssid.data) { + ESP_LOGE(TAG, "Failed to allocate memory for scan result entry BSSID"); + return ESP_ERR_NO_MEM; + } + memcpy(results[i]->bssid.data, scan_result.bssid, results[i]->bssid.len); + } + return ESP_OK; +} + + +static int lookup_cmd_handler(int cmd_id) +{ + int i; + + for (i = 0; i < sizeof(cmd_table)/sizeof(wifi_prov_scan_cmd_t); i++) { + if (cmd_table[i].cmd_num == cmd_id) { + return i; + } + } + + return -1; +} + +static void wifi_prov_scan_cmd_cleanup(WiFiScanPayload *resp, void *priv_data) +{ + switch (resp->msg) { + case WI_FI_SCAN_MSG_TYPE__TypeRespScanStart: + { + free(resp->resp_scan_start); + } + break; + case WI_FI_SCAN_MSG_TYPE__TypeRespScanStatus: + { + free(resp->resp_scan_status); + } + break; + case WI_FI_SCAN_MSG_TYPE__TypeRespScanResult: + { + if (!resp->resp_scan_result) return; + if (resp->resp_scan_result->entries) { + for (uint16_t i = 0; i < resp->resp_scan_result->n_entries; i++) { + if (!resp->resp_scan_result->entries[i]) continue; + free(resp->resp_scan_result->entries[i]->ssid.data); + free(resp->resp_scan_result->entries[i]->bssid.data); + free(resp->resp_scan_result->entries[i]); + } + free(resp->resp_scan_result->entries); + } + free(resp->resp_scan_result); + } + break; + default: + ESP_LOGE(TAG, "Unsupported response type in cleanup_handler"); + break; + } + return; +} + +static esp_err_t wifi_prov_scan_cmd_dispatcher(WiFiScanPayload *req, + WiFiScanPayload *resp, void *priv_data) +{ + esp_err_t ret; + + ESP_LOGD(TAG, "In wifi_prov_scan_cmd_dispatcher Cmd=%d", req->msg); + + int cmd_index = lookup_cmd_handler(req->msg); + if (cmd_index < 0) { + ESP_LOGE(TAG, "Invalid command handler lookup"); + return ESP_FAIL; + } + + ret = cmd_table[cmd_index].command_handler(req, resp, priv_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error executing command handler"); + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t wifi_prov_scan_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen, + uint8_t **outbuf, ssize_t *outlen, void *priv_data) +{ + WiFiScanPayload *req; + WiFiScanPayload resp; + esp_err_t ret = ESP_OK; + + req = wi_fi_scan_payload__unpack(NULL, inlen, inbuf); + if (!req) { + ESP_LOGE(TAG, "Unable to unpack scan message"); + return ESP_ERR_INVALID_ARG; + } + + wi_fi_scan_payload__init(&resp); + ret = wifi_prov_scan_cmd_dispatcher(req, &resp, priv_data); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Command dispatcher error %d", ret); + ret = ESP_FAIL; + goto exit; + } + + resp.msg = req->msg + 1; /* Response is request + 1 */ + *outlen = wi_fi_scan_payload__get_packed_size(&resp); + if (*outlen <= 0) { + ESP_LOGE(TAG, "Invalid encoding for response"); + ret = ESP_FAIL; + goto exit; + } + + *outbuf = (uint8_t *) malloc(*outlen); + if (!*outbuf) { + ESP_LOGE(TAG, "System out of memory"); + ret = ESP_ERR_NO_MEM; + goto exit; + } + wi_fi_scan_payload__pack(&resp, *outbuf); + ESP_LOGD(TAG, "Response packet size : %d", *outlen); + exit: + + wi_fi_scan_payload__free_unpacked(req, NULL); + wifi_prov_scan_cmd_cleanup(&resp, priv_data); + return ret; +} diff --git a/examples/provisioning/README.md b/examples/provisioning/README.md index 96e0b93b..4a755e3f 100644 --- a/examples/provisioning/README.md +++ b/examples/provisioning/README.md @@ -1,6 +1,25 @@ # Provisioning Application Examples -These consist of the following examples : +This primarily consists of a single unified example wifi_prov_mgr + +* wifi_prov_mgr + Abstracts out most of the complexity of Wi-Fi provisioning and allows easy switching between the SoftAP (using HTTP) and BLE transports. It also demonstrates how applications can register and use additional custom data endpoints. + +Provisioning applications are available for various platforms: + +* Android: + - [BLE Provisioning app on Play Store](https://play.google.com/store/apps/details?id=com.espressif.provble). + - [SoftAP Provisioning app on Play Store](https://play.google.com/store/apps/details?id=com.espressif.provsoftap). + - Source code on GitHub: [esp-idf-provisioning-android](https://github.com/espressif/esp-idf-provisioning-android). +* iOS: + - [BLE Provisioning app on app store](https://apps.apple.com/in/app/esp-ble-provisioning/id1473590141) + - [SoftAP Provisioning app on app Store](https://apps.apple.com/in/app/esp-softap-provisioning/id1474040630) + - Source code on GitHub: [esp-idf-provisioning-ios](https://github.com/espressif/esp-idf-provisioning-ios) +* For all other platforms a python based command line tool is provided under "$IDF_PATH/tools/esp_prov" + +## Legacy Examples + +The legacy examples require own implementation of provisioning functions and handlers. The Wi-Fi provisioning component abstracts out most of this complexity and provides a simpler interface and so, that is recommended for use. However, if you want to use lower level provisioning and protocomm APIs, you can check the these examples under legacy/ folder: * softap_prov Provisioning involves Wi-Fi station configuration via an HTTP server running on the device, which is initially configured to be in SoftAP mode. After provisioning, device runs in Wi-Fi station mode only and connects to the AP whose credentials were provided during provisioning. @@ -14,10 +33,4 @@ These consist of the following examples : * custom_config Similar to softap_prov examples, but allows for configuration of custom (device-local) information during provisioning. This is intended as an example for implementing custom provisioning schemes. -Provisioning applications are available for various platforms: - -* For Android, a provisioning application along with source code is available on GitHub : [esp-idf-provisioning-android](https://github.com/espressif/esp-idf-provisioning-android) -* For iOS, a provisioning application along with source code is available on GitHub : [esp-idf-provisioning-ios](https://github.com/espressif/esp-idf-provisioning-ios) -* For all other platforms a python based command line tool is provided under "$IDF_PATH/tools/esp_prov" - Refer to the README.md files in each example directory for more information. diff --git a/examples/provisioning/custom_config/components/custom_provisioning/CMakeLists.txt b/examples/provisioning/custom_config/components/custom_provisioning/CMakeLists.txt deleted file mode 100644 index 4e0f61c1..00000000 --- a/examples/provisioning/custom_config/components/custom_provisioning/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(COMPONENT_ADD_INCLUDEDIRS include) -set(COMPONENT_PRIV_INCLUDEDIRS proto-c) -set(COMPONENT_SRCS "src/custom_config.c" - "proto-c/custom_config.pb-c.c") - -set(COMPONENT_PRIV_REQUIRES protobuf-c) - -register_component() diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto/README.md b/examples/provisioning/custom_config/components/custom_provisioning/proto/README.md deleted file mode 100644 index 16d41538..00000000 --- a/examples/provisioning/custom_config/components/custom_provisioning/proto/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Protobuf files for defining custom config-data packet structures - -This is an example proto file defining custom configuration related data packet structures, namely - -1. CustomConfigRequest - for sending configuration data consisting of various fields (Info and Version) -2. CustomConfigResponse - for receiving configuration status (fail/success) - -Note : These proto files are not automatically compiled during the build process. - -Run "make" (Optional) to generate the respective C and Python files. The generated C files are used by protocomm itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to protocomm layer. - -Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under "protocomm/proto-c" and "protocomm/python" directories, and thus running make (and installing the Protobuf compilers) is optional. diff --git a/examples/provisioning/custom_config/main/CMakeLists.txt b/examples/provisioning/custom_config/main/CMakeLists.txt deleted file mode 100644 index 3c44a1a3..00000000 --- a/examples/provisioning/custom_config/main/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -set(COMPONENT_SRCS "app_main.c" - "app_prov.c" - "app_prov_handlers.c") -set(COMPONENT_ADD_INCLUDEDIRS ".") - -register_component() diff --git a/examples/provisioning/custom_config/main/Kconfig.projbuild b/examples/provisioning/custom_config/main/Kconfig.projbuild deleted file mode 100644 index 33f635e0..00000000 --- a/examples/provisioning/custom_config/main/Kconfig.projbuild +++ /dev/null @@ -1,58 +0,0 @@ -menu "Example Configuration" - -config SOFTAP_SSID - string "WiFi SSID" - default "myssid" - help - SSID (network name) for the example to connect to. - -config SOFTAP_PASS - string "WiFi Password" - default "mypassword" - help - WiFi password (WPA or WPA2) for the example to use. - -config USE_SEC_1 - bool - default n - prompt "Use Security Version 1" - help - Security version 1 used Curve25519 key exchange for establishing - secure session between device and client during provisioning - -config USE_POP - bool - depends on USE_SEC_1 - default n - prompt "Use proof-of-possession" - help - Proof-of-possession can be optionally used to prove that the device is indeed - in possession of the user who is provisioning the device. This proof-of-possession - is internally used to generate the shared secret through key exchange. - -config POP - string "Proof-of-possession" - default "abcd1234" - depends on USE_POP - -config PROTOCOMM_HTTPD_PORT - int "Protocomm HTTP Port" - default 80 - help - Port on which to run Protocomm HTTP based provisioning service - -config RESET_PROVISIONED - bool - default n - prompt "Reset provisioned status of the device" - help - This erases the NVS to reset provisioned status of the device on every reboot. - Provisioned status is determined by the WiFi STA configuration, saved on the NVS. - -config EXAMPLE_AP_RECONN_ATTEMPTS - int "Maximum AP connection attempts" - default 5 - help - Set the maximum connection attempts to perform when connecting to a Wi-Fi AP. - -endmenu diff --git a/examples/provisioning/custom_config/sdkconfig.defaults b/examples/provisioning/custom_config/sdkconfig.defaults deleted file mode 100644 index 992010c3..00000000 --- a/examples/provisioning/custom_config/sdkconfig.defaults +++ /dev/null @@ -1,3 +0,0 @@ -CONFIG_ENABLE_UNIFIED_PROVISIONING=y -CONFIG_LWIP_NETIF_LOOPBACK=y -CONFIG_LWIP_LOOPBACK_MAX_PBUFS=1 diff --git a/examples/provisioning/legacy/ble_prov/CMakeLists.txt b/examples/provisioning/legacy/ble_prov/CMakeLists.txt new file mode 100644 index 00000000..30012d3e --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_prov) diff --git a/examples/provisioning/legacy/ble_prov/Makefile b/examples/provisioning/legacy/ble_prov/Makefile new file mode 100644 index 00000000..ccd2a96b --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_prov + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/provisioning/legacy/ble_prov/README.md b/examples/provisioning/legacy/ble_prov/README.md new file mode 100644 index 00000000..d0639441 --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/README.md @@ -0,0 +1,208 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +# BLE based Provisioning Example (Legacy) + +> Note: It is recommended to use the new `wifi_prov_mgr` example which is based on the simpler `wifi_provisioning` APIs. Check this example only if you wish to use lower level provisioning and protocomm APIs and want more control over the handlers. + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +`ble_prov` example demonstrates the implementation and integration of various IDF components for building a provisioning application. + +For this example BLE is chosen as the mode of transport, over which the provisioning related communication is to take place, between the device (to be provisioned) and the client (owner of the device). + +In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed. + +Right after provisioning is complete, BLE is turned off and disabled to free the memory used by the BLE stack. Though, that is specific to this example, and the user can choose to keep BLE on in their own application. + +`ble_prov` uses the following components : +* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration +* `protocomm` : for protocol based communication and secure session establishment +* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures +* `bt` : ESP32 BLE stack for transport of protobuf packets + +This example can be used, as it is, for adding a provisioning service to any application intended for IoT. + +## How to use example + +### Hardware Required + +Example should be able to run on any commonly available ESP32 development board. + +### Application Required + +Provisioning applications are available for various platforms. See below + +#### Platform : Android + +For Android, a provisioning application along with source code is available on GitHub : [esp-idf-provisioning-android](https://github.com/espressif/esp-idf-provisioning-android) + +#### Platform : iOS + +For iOS, a provisioning application along with source code is available on GitHub : [esp-idf-provisioning-ios](https://github.com/espressif/esp-idf-provisioning-ios) + +#### Platform : Linux / Windows / macOS + +To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). Make sure to satisfy all the dependencies prior to running the script. + +Presently, `esp_prov` supports BLE transport only for Linux platform. For Windows/macOS it falls back to console mode and requires another application (for BLE) through which the communication can take place. + +There are various applications, specific to Windows and macOS platform which can be used. The `esp_prov` console will guide you through the provisioning process of locating the correct BLE GATT services and characteristics, the values to write, and input read values. + +### Configure the project + +``` +idf.py menuconfig +``` + +* Under Example Configuration set the following : + * Security Version (default 1) + * Proof of Possession (default "abcd1234") + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (550) app: Starting BLE provisioning +I (1130) app_prov: Provisioning started with BLE devname : PROV_261FCC +``` + +Make sure to note down the BLE device name (starting with PROV_) displayed in the serial monitor log (eg. PROV_261FCC). This will depend on the MAC ID and will be unique for every device. + +In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration : + +``` +python esp_prov.py --transport ble --service_name PROV_261FCC --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword +``` + +Above command will perform the provisioning steps, and the monitor log should display something like this : + +``` +I (682950) app_prov_handler: WiFi Credentials Received : + ssid : myssid + password : mypassword +. +. +. +I (683130) app_prov: STA Start +I (683130) app_prov_handler: WiFi Credentials Applied +. +. +. +I (688270) app_prov_handler: Connecting state +. +. +. +I (688390) app_prov: STA Got IP +I (688390) app: got ip:192.168.43.220 +I (693410) app_prov_handler: Connected state +``` + +After sometime the provisioning app will exit and BLE will be turned off + +``` +I (718390) app_prov: Stopping provisioning +I (718670) app_prov: Provisioning stopped +``` + +## Troubleshooting + +### Provisioning failed + +It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason : + +``` +E (39291) app_prov: STA Disconnected +E (39291) app_prov: Disconnect reason : 201 +I (39291) app_prov: STA AP Not found +I (42021) app_prov_handler: Disconnected state +``` + +### Provisioning does not start + +If the serial monitor log is different, as shown below : + +``` +I (539) app_prov: Found ssid myssid +I (539) app_prov: Found password mypassword +I (549) app: Starting WiFi station +``` + +It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example + +``` +make erase_flash +idf.py -p PORT flash monitor +``` + +Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution. + +### Unsupported platform + +If the platform requirement, for running `esp_prov` is not satisfied, then the script execution will fallback to console mode, in which case the full process (involving user inputs) will look like this : + +``` +BLE client is running in console mode + This could be due to your platform not being supported or dependencies not being met + Please ensure all pre-requisites are met to run the full fledged client +BLECLI >> Please connect to BLE device `PROV_261FCC` manually using your tool of choice +BLECLI >> Was the device connected successfully? [y/n] y +BLECLI >> List available attributes of the connected device +BLECLI >> Is the service UUID '0000ffff-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff53-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff51-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff52-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y + +==== Verifying protocol version ==== +BLECLI >> Write following data to characteristic with UUID '0000ff53-0000-1000-8000-00805f9b34fb' : + >> 56302e31 +BLECLI >> Enter data read from characteristic (in hex) : + << 53554343455353 +==== Verified protocol version successfully ==== + +==== Starting Session ==== +BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' : + >> 10015a25a201220a20ae6d9d5d1029f8c366892252d2d5a0ffa7ce1ee5829312545dd5f2aba057294d +BLECLI >> Enter data read from characteristic (in hex) : + << 10015a390801aa0134122048008bfc365fad4753dc75912e0c764d60749cb26dd609595b6fbc72e12614031a1089733af233c7448e7d7fb7963682c6d8 +BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' : + >> 10015a270802b2012212204051088dc294fe4621fac934a8ea22e948fcc3e8ac458aac088ce705c65dbfb9 +BLECLI >> Enter data read from characteristic (in hex) : + << 10015a270803ba01221a20c8d38059d5206a3d92642973ac6ba8ac2f6ecf2b7a3632964eb35a0f20133adb +==== Session Established ==== + +==== Sending Wifi credential to esp32 ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 98471ac4019a46765c28d87df8c8ae71c1ae6cfe0bc9c615bc6d2c +BLECLI >> Enter data read from characteristic (in hex) : + << 3271f39a +==== Wifi Credentials sent successfully ==== + +==== Applying config to esp32 ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 5355 +BLECLI >> Enter data read from characteristic (in hex) : + << 1664db24 +==== Apply config sent successfully ==== + +==== Wifi connection state ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 290d +BLECLI >> Enter data read from characteristic (in hex) : + << 505f72a9f8521025c1964d7789c4d7edc56aedebd144e1b667bc7c0975757b80cc091aa9f3e95b06eaefbc30290fa1 +++++ WiFi state: connected ++++ +==== Provisioning was successful ==== +``` + +The write data is to be copied from the console output ```>>``` to the platform specific application and the data read from the application is to be pasted at the user input prompt ```<<``` of the console, in the format (hex) indicated in above sample log. diff --git a/examples/provisioning/legacy/ble_prov/ble_prov_test.py b/examples/provisioning/legacy/ble_prov/ble_prov_test.py new file mode 100644 index 00000000..eef5e51f --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/ble_prov_test.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# +# Copyright 2018 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. + +from __future__ import print_function +import re +import os +import time + +import ttfw_idf +import esp_prov + +# Have esp_prov throw exception +esp_prov.config_throw_except = True + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI_BT") +def test_examples_provisioning_ble(env, extra_data): + # Acquire DUT + dut1 = env.get_dut("ble_prov", "examples/provisioning/legacy/ble_prov", dut_class=ttfw_idf.ESP32DUT) + + # Get binary file + binary_file = os.path.join(dut1.app.binary_path, "ble_prov.bin") + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("ble_prov_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("ble_prov_bin_size", bin_size // 1024, dut1.TARGET) + + # Upload binary and start testing + dut1.start_app() + + # Parse BLE devname + devname = dut1.expect(re.compile(r"Provisioning started with BLE devname : '(PROV_\S\S\S\S\S\S)'"), timeout=60)[0] + print("BLE Device Alias for DUT :", devname) + + # Match additional headers sent in the request + dut1.expect("BLE Provisioning started", timeout=30) + + print("Starting Provisioning") + verbose = False + protover = "V0.1" + secver = 1 + pop = "abcd1234" + provmode = "ble" + ap_ssid = "myssid" + ap_password = "mypassword" + + print("Getting security") + security = esp_prov.get_security(secver, pop, verbose) + if security is None: + raise RuntimeError("Failed to get security") + + print("Getting transport") + transport = esp_prov.get_transport(provmode, devname) + if transport is None: + raise RuntimeError("Failed to get transport") + + print("Verifying protocol version") + if not esp_prov.version_match(transport, protover): + raise RuntimeError("Mismatch in protocol version") + + print("Starting Session") + if not esp_prov.establish_session(transport, security): + raise RuntimeError("Failed to start session") + + print("Sending Wifi credential to DUT") + if not esp_prov.send_wifi_config(transport, security, ap_ssid, ap_password): + raise RuntimeError("Failed to send Wi-Fi config") + + print("Applying config") + if not esp_prov.apply_wifi_config(transport, security): + raise RuntimeError("Failed to send apply config") + + success = False + while True: + time.sleep(5) + print("Wi-Fi connection state") + ret = esp_prov.get_wifi_config(transport, security) + if (ret == 1): + continue + elif (ret == 0): + print("Provisioning was successful") + success = True + break + + if not success: + raise RuntimeError("Provisioning failed") + + +if __name__ == '__main__': + test_examples_provisioning_ble() diff --git a/examples/provisioning/legacy/ble_prov/main/CMakeLists.txt b/examples/provisioning/legacy/ble_prov/main/CMakeLists.txt new file mode 100644 index 00000000..a483b387 --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "app_main.c" + "app_prov.c" + "app_prov_handlers.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/examples/provisioning/legacy/ble_prov/main/Kconfig.projbuild b/examples/provisioning/legacy/ble_prov/main/Kconfig.projbuild new file mode 100644 index 00000000..b1578f84 --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/main/Kconfig.projbuild @@ -0,0 +1,40 @@ +menu "Example Configuration" + + config EXAMPLE_USE_SEC_1 + bool + default y + prompt "Use Security Version 1" + help + Security version 1 used Curve25519 key exchange for establishing + secure session between device and client during provisioning + + config EXAMPLE_USE_POP + bool + depends on EXAMPLE_USE_SEC_1 + default y + prompt "Use proof-of-possession" + help + Proof-of-possession can be optionally used to prove that the device is indeed + in possession of the user who is provisioning the device. This proof-of-possession + is internally used to generate the shared secret through key exchange. + + config EXAMPLE_POP + string "Proof-of-possession" + default "abcd1234" + depends on EXAMPLE_USE_POP + + config EXAMPLE_RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status of the device" + help + This erases the NVS to reset provisioned status of the device on every reboot. + Provisioned status is determined by the Wi-Fi STA configuration, saved on the NVS. + + config EXAMPLE_AP_RECONN_ATTEMPTS + int "Maximum AP connection attempts" + default 5 + help + Set the maximum connection attempts to perform when connecting to a Wi-Fi AP. + +endmenu diff --git a/examples/provisioning/legacy/ble_prov/main/app_main.c b/examples/provisioning/legacy/ble_prov/main/app_main.c new file mode 100644 index 00000000..e4ad35de --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/main/app_main.c @@ -0,0 +1,145 @@ +/* BLE based Provisioning Example + + 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 "app_prov.h" + +#define EXAMPLE_AP_RECONN_ATTEMPTS CONFIG_EXAMPLE_AP_RECONN_ATTEMPTS + +static const char *TAG = "app"; + +static void start_ble_provisioning(void); + +static void event_handler(void* arg, esp_event_base_t event_base, + int event_id, void* event_data) +{ + static int s_retry_num_ap_not_found = 0; + static int s_retry_num_ap_auth_fail = 0; + + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + esp_wifi_connect(); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + wifi_event_sta_disconnected_t* disconnected = (wifi_event_sta_disconnected_t*) event_data; + switch (disconnected->reason) { + case WIFI_REASON_AUTH_EXPIRE: + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + case WIFI_REASON_BEACON_TIMEOUT: + case WIFI_REASON_AUTH_FAIL: + case WIFI_REASON_ASSOC_FAIL: + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGW(TAG, "connect to the AP fail : auth Error"); + if (s_retry_num_ap_auth_fail < EXAMPLE_AP_RECONN_ATTEMPTS) { + s_retry_num_ap_auth_fail++; + esp_wifi_connect(); + ESP_LOGI(TAG, "retry connecting to the AP..."); + } else { + /* Restart provisioning if authentication fails */ + start_ble_provisioning(); + } + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGW(TAG, "connect to the AP fail : not found"); + if (s_retry_num_ap_not_found < EXAMPLE_AP_RECONN_ATTEMPTS) { + s_retry_num_ap_not_found++; + esp_wifi_connect(); + ESP_LOGI(TAG, "retry to connecting to the AP..."); + } + break; + default: + /* None of the expected reasons */ + esp_wifi_connect(); + break; + } + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; + ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + s_retry_num_ap_not_found = 0; + s_retry_num_ap_auth_fail = 0; + } +} + +static void wifi_init_sta(void) +{ + /* Set our event handling */ + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handler, NULL)); + + /* Start Wi-Fi in station mode with credentials set during provisioning */ + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); +} + +static void start_ble_provisioning(void) +{ + /* Security version */ + int security = 0; + /* Proof of possession */ + const protocomm_security_pop_t *pop = NULL; + +#ifdef CONFIG_EXAMPLE_USE_SEC_1 + security = 1; +#endif + + /* Having proof of possession is optional */ +#ifdef CONFIG_EXAMPLE_USE_POP + const static protocomm_security_pop_t app_pop = { + .data = (uint8_t *) CONFIG_EXAMPLE_POP, + .len = (sizeof(CONFIG_EXAMPLE_POP)-1) + }; + pop = &app_pop; +#endif + + ESP_ERROR_CHECK(app_prov_start_ble_provisioning(security, pop)); +} + +void app_main(void) +{ + /* Initialize networking stack */ + ESP_ERROR_CHECK(esp_netif_init()); + + /* Create default event loop needed by the + * main app and the provisioning service */ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* Initialize NVS needed by Wi-Fi */ + ESP_ERROR_CHECK(nvs_flash_init()); + + /* Initialize Wi-Fi including netif with default config */ + esp_netif_create_default_wifi_sta(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + /* Check if device is provisioned */ + bool provisioned; + if (app_prov_is_provisioned(&provisioned) != ESP_OK) { + ESP_LOGE(TAG, "Error getting device provisioning state"); + return; + } + + if (provisioned == false) { + /* If not provisioned, start provisioning via BLE */ + ESP_LOGI(TAG, "Starting BLE provisioning"); + start_ble_provisioning(); + } else { + /* Else start as station with credentials set during provisioning */ + ESP_LOGI(TAG, "Starting WiFi station"); + wifi_init_sta(); + } +} diff --git a/examples/provisioning/legacy/ble_prov/main/app_prov.c b/examples/provisioning/legacy/ble_prov/main/app_prov.c new file mode 100644 index 00000000..19ea1b31 --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/main/app_prov.c @@ -0,0 +1,387 @@ +/* BLE based Provisioning Example + + 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 + +#ifdef CONFIG_BT_NIMBLE_ENABLED +#include "esp_nimble_hci.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#endif + +#include +#include +#include +#include +#include + +#include "app_prov.h" + +static const char *TAG = "app_prov"; +static const char *ssid_prefix = "PROV_"; + +/* Handler for catching WiFi events */ +static void app_prov_event_handler(void* handler_arg, esp_event_base_t base, int id, void* data); + +/* Handlers for wifi_config provisioning endpoint */ +extern wifi_prov_config_handlers_t wifi_prov_handlers; + +/** + * @brief Data relevant to provisioning application + */ +struct app_prov_data { + protocomm_t *pc; /*!< Protocomm handler */ + int security; /*!< Type of security to use with protocomm */ + const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */ + esp_timer_handle_t timer; /*!< Handle to timer */ + + /* State of WiFi Station */ + wifi_prov_sta_state_t wifi_state; + + /* Code for WiFi station disconnection (if disconnected) */ + wifi_prov_sta_fail_reason_t wifi_disconnect_reason; +}; + +/* Pointer to provisioning application data */ +static struct app_prov_data *g_prov; + +static esp_err_t app_prov_start_service(void) +{ + /* Create new protocomm instance */ + g_prov->pc = protocomm_new(); + if (g_prov->pc == NULL) { + ESP_LOGE(TAG, "Failed to create new protocomm instance"); + return ESP_FAIL; + } + + /* Endpoint UUIDs */ + protocomm_ble_name_uuid_t nu_lookup_table[] = { + {"prov-session", 0x0001}, + {"prov-config", 0x0002}, + {"proto-ver", 0x0003}, + }; + + /* Config for protocomm_ble_start() */ + protocomm_ble_config_t config = { + .service_uuid = { + /* LSB <--------------------------------------- + * ---------------------------------------> MSB */ + 0xb4, 0xdf, 0x5a, 0x1c, 0x3f, 0x6b, 0xf4, 0xbf, + 0xea, 0x4a, 0x82, 0x03, 0x04, 0x90, 0x1a, 0x02, + }, + .nu_lookup_count = sizeof(nu_lookup_table)/sizeof(nu_lookup_table[0]), + .nu_lookup = nu_lookup_table + }; + + /* With the above selection of 128bit primary service UUID and + * 16bit endpoint UUIDs, the 128bit characteristic UUIDs will be + * formed by replacing the 12th and 13th bytes of the service UUID + * with the 16bit endpoint UUID, i.e. : + * service UUID : 021a9004-0382-4aea-bff4-6b3f1c5adfb4 + * masked base : 021a____-0382-4aea-bff4-6b3f1c5adfb4 + * ------------------------------------------------------ + * resulting characteristic UUIDs : + * 1) prov-session : 021a0001-0382-4aea-bff4-6b3f1c5adfb4 + * 2) prov-config : 021a0002-0382-4aea-bff4-6b3f1c5adfb4 + * 3) proto-ver : 021a0003-0382-4aea-bff4-6b3f1c5adfb4 + * + * Also, note that each endpoint (characteristic) will have + * an associated "Characteristic User Description" descriptor + * with 16bit UUID 0x2901, each containing the corresponding + * endpoint name. These descriptors can be used by a client + * side application to figure out which characteristic is + * mapped to which endpoint, without having to hardcode the + * UUIDs */ + + uint8_t eth_mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, eth_mac); + snprintf(config.device_name, sizeof(config.device_name), "%s%02X%02X%02X", + ssid_prefix, eth_mac[3], eth_mac[4], eth_mac[5]); + + /* Release BT memory, as we need only BLE */ + esp_err_t err = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (err) { + ESP_LOGE(TAG, "bt_controller_mem_release failed %d", err); + if (err != ESP_ERR_INVALID_STATE) { + return err; + } + } + + /* Start protocomm layer on top of BLE */ + if (protocomm_ble_start(g_prov->pc, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start BLE provisioning"); + return ESP_FAIL; + } + + /* Set protocomm version verification endpoint for protocol */ + protocomm_set_version(g_prov->pc, "proto-ver", "V0.1"); + + /* Set protocomm security type for endpoint */ + if (g_prov->security == 0) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL); + } else if (g_prov->security == 1) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop); + } + + /* Add endpoint for provisioning to set wifi station config */ + if (protocomm_add_endpoint(g_prov->pc, "prov-config", + wifi_prov_config_data_handler, + (void *) &wifi_prov_handlers) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set provisioning endpoint"); + protocomm_ble_stop(g_prov->pc); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Provisioning started with BLE devname : '%s'", config.device_name); + return ESP_OK; +} + +static void app_prov_stop_service(void) +{ + /* Remove provisioning endpoint */ + protocomm_remove_endpoint(g_prov->pc, "prov-config"); + /* Unset provisioning security */ + protocomm_unset_security(g_prov->pc, "prov-session"); + /* Unset provisioning version endpoint */ + protocomm_unset_version(g_prov->pc, "proto-ver"); + /* Stop protocomm ble service */ + protocomm_ble_stop(g_prov->pc); + /* Delete protocomm instance */ + protocomm_delete(g_prov->pc); + + /* Remove event handler */ + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler); + + /* Release memory used by BT stack */ + esp_bt_mem_release(ESP_BT_MODE_BTDM); +} + +/* Task spawned by timer callback */ +static void stop_prov_task(void * arg) +{ + ESP_LOGI(TAG, "Stopping provisioning"); + app_prov_stop_service(); + + /* Timer not needed anymore */ + esp_timer_handle_t timer = g_prov->timer; + esp_timer_delete(timer); + g_prov->timer = NULL; + + /* Free provisioning process data */ + free(g_prov); + g_prov = NULL; + ESP_LOGI(TAG, "Provisioning stopped"); + + vTaskDelete(NULL); +} + +/* Callback to be invoked by timer */ +static void _stop_prov_cb(void * arg) +{ + xTaskCreate(&stop_prov_task, "stop_prov", 2048, NULL, tskIDLE_PRIORITY, NULL); +} + +/* Event handler for starting/stopping provisioning */ +static void app_prov_event_handler(void* handler_arg, esp_event_base_t event_base, + int event_id, void* event_data) +{ + /* If pointer to provisioning application data is NULL + * then provisioning is not running */ + if (!g_prov) { + 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 */ + g_prov->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. + * Schedule timer to stop provisioning app after 30 seconds. */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTED; + if (g_prov && g_prov->timer) { + esp_timer_start_once(g_prov->timer, 30000*1000U); + } + } 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 */ + g_prov->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_BEACON_TIMEOUT: + case WIFI_REASON_AUTH_FAIL: + case WIFI_REASON_ASSOC_FAIL: + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGI(TAG, "STA Auth Error"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR; + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGI(TAG, "STA AP Not found"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND; + break; + default: + /* If none of the expected reasons, + * retry connecting to host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + esp_wifi_connect(); + } + } +} + +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state) +{ + if (g_prov == NULL || state == NULL) { + return ESP_FAIL; + } + + *state = g_prov->wifi_state; + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason) +{ + if (g_prov == NULL || reason == NULL) { + return ESP_FAIL; + } + + if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) { + return ESP_FAIL; + } + + *reason = g_prov->wifi_disconnect_reason; + return ESP_OK; +} + +esp_err_t app_prov_is_provisioned(bool *provisioned) +{ + *provisioned = false; + +#ifdef CONFIG_EXAMPLE_RESET_PROVISIONED + nvs_flash_erase(); +#endif + + /* Get WiFi 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; + ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid); + ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password); + } + return ESP_OK; +} + +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg) +{ + /* Configure WiFi as station */ + if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode"); + return ESP_FAIL; + } + /* Configure WiFi 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 WiFi configuration"); + return ESP_FAIL; + } + /* Start WiFi */ + if (esp_wifi_start() != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi configuration"); + return ESP_FAIL; + } + /* Connect to AP */ + if (esp_wifi_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect WiFi"); + return ESP_FAIL; + } + + if (g_prov) { + /* Reset wifi station state for provisioning app */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + } + return ESP_OK; +} + +esp_err_t app_prov_start_ble_provisioning(int security, const protocomm_security_pop_t *pop) +{ + /* If provisioning app data present, + * means provisioning app is already running */ + if (g_prov) { + ESP_LOGI(TAG, "Invalid provisioning state"); + return ESP_FAIL; + } + + /* Allocate memory for provisioning app data */ + g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data)); + if (!g_prov) { + ESP_LOGI(TAG, "Unable to allocate prov data"); + return ESP_ERR_NO_MEM; + } + + /* Initialize app data */ + g_prov->pop = pop; + g_prov->security = security; + + /* Create timer object as a member of app data */ + esp_timer_create_args_t timer_conf = { + .callback = _stop_prov_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "stop_ble_tm" + }; + esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to create timer"); + return err; + } + + err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register WiFi event handler"); + return err; + } + + err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register IP event handler"); + return err; + } + + /* Start provisioning service through BLE */ + err = app_prov_start_service(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Provisioning failed to start"); + return err; + } + + ESP_LOGI(TAG, "BLE Provisioning started"); + return ESP_OK; +} diff --git a/examples/provisioning/legacy/ble_prov/main/app_prov.h b/examples/provisioning/legacy/ble_prov/main/app_prov.h new file mode 100644 index 00000000..64e4dbe3 --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/main/app_prov.h @@ -0,0 +1,79 @@ +/* BLE based Provisioning Example + + 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. +*/ + +#pragma once + +#include +#include + +/** + * @brief Get state of WiFi Station during provisioning + * + * @note WiFi is initially configured as AP, when + * provisioning starts. After provisioning data + * is provided by user, the WiFi is reconfigured + * to run as both AP and Station. + * + * @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi state + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state); + +/** + * @brief Get reason code in case of WiFi station + * disconnection during provisioning + * +* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi disconnect reason + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason); + +/** + * @brief Checks if device is provisioned + * * + * @param[out] provisioned True if provisioned, else false + * + * @return + * - ESP_OK : Retrieved provision state successfully + * - ESP_FAIL : Failed to retrieve provision state + */ +esp_err_t app_prov_is_provisioned(bool *provisioned); + +/** + * @brief Runs WiFi as Station + * + * Configures the WiFi station mode to connect to the + * SSID and password specified in config structure, + * and starts WiFi to run as station + * + * @param[in] wifi_cfg Pointer to WiFi cofiguration structure + * + * @return + * - ESP_OK : WiFi configured and started successfully + * - ESP_FAIL : Failed to set configuration + */ +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg); + +/** + * @brief Start provisioning via Bluetooth + * + * @param[in] security Security mode + * @param[in] pop Pointer to proof of possession (NULL if not present) + * + * @return + * - ESP_OK : Provisioning started successfully + * - ESP_FAIL : Failed to start + */ +esp_err_t app_prov_start_ble_provisioning(int security, const protocomm_security_pop_t *pop); diff --git a/examples/provisioning/softap_prov/main/app_prov_handlers.c b/examples/provisioning/legacy/ble_prov/main/app_prov_handlers.c similarity index 79% rename from examples/provisioning/softap_prov/main/app_prov_handlers.c rename to examples/provisioning/legacy/ble_prov/main/app_prov_handlers.c index 4a0c0d99..87cc64f0 100644 --- a/examples/provisioning/softap_prov/main/app_prov_handlers.c +++ b/examples/provisioning/legacy/ble_prov/main/app_prov_handlers.c @@ -15,7 +15,7 @@ #include #include -#include +#include #include @@ -60,10 +60,9 @@ static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data, wifi ESP_LOGI(TAG, "Connected state"); /* IP Addr assigned to STA */ - tcpip_adapter_ip_info_t ip_info; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); - char *ip_addr = ip4addr_ntoa(&ip_info.ip); - strcpy(resp_data->conn_info.ip_addr, ip_addr); + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info); + esp_ip4addr_ntoa(&ip_info.ip, resp_data->conn_info.ip_addr, sizeof(resp_data->conn_info.ip_addr)); /* AP information to which STA is connected */ wifi_ap_record_t ap_info; @@ -98,10 +97,14 @@ static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data, ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s", req_data->ssid, req_data->password); - memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid, - strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid))); - memcpy((char *) wifi_cfg->sta.password, req_data->password, - strnlen(req_data->password, sizeof(wifi_cfg->sta.password))); + + /* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard). + * But this doesn't guarantee that the saved SSID will be null terminated, because + * wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character). + * Although, this is not a matter for concern because esp_wifi library reads the SSID + * upto 32 bytes in absence of null termination */ + strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid)); + strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password)); return ESP_OK; } diff --git a/examples/provisioning/custom_config/main/component.mk b/examples/provisioning/legacy/ble_prov/main/component.mk similarity index 100% rename from examples/provisioning/custom_config/main/component.mk rename to examples/provisioning/legacy/ble_prov/main/component.mk diff --git a/examples/provisioning/legacy/ble_prov/partitions.csv b/examples/provisioning/legacy/ble_prov/partitions.csv new file mode 100644 index 00000000..d01414b8 --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/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, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1200000, diff --git a/examples/provisioning/legacy/ble_prov/sdkconfig.defaults b/examples/provisioning/legacy/ble_prov/sdkconfig.defaults new file mode 100644 index 00000000..37eeb5f8 --- /dev/null +++ b/examples/provisioning/legacy/ble_prov/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Override some defaults so BT stack is enabled and +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n + +# 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/examples/provisioning/legacy/console_prov/CMakeLists.txt b/examples/provisioning/legacy/console_prov/CMakeLists.txt new file mode 100644 index 00000000..65420058 --- /dev/null +++ b/examples/provisioning/legacy/console_prov/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(console_prov) diff --git a/examples/provisioning/legacy/console_prov/Makefile b/examples/provisioning/legacy/console_prov/Makefile new file mode 100644 index 00000000..c84ccd93 --- /dev/null +++ b/examples/provisioning/legacy/console_prov/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := console_prov + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/provisioning/legacy/console_prov/README.md b/examples/provisioning/legacy/console_prov/README.md new file mode 100644 index 00000000..01b50cb1 --- /dev/null +++ b/examples/provisioning/legacy/console_prov/README.md @@ -0,0 +1,208 @@ +# Console based Provisioning Example (Legacy) + +> Check this example only if you wish to use console based provisioning. For any real applications, it is recommended to use the new `wifi_prov_mgr` example which is based on the simpler `wifi_provisioning` APIs. + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +`console_prov` example demonstrates the implementation and integration of various IDF components for building a console based provisioning application. + +For this example UART console is chosen as the mode of transport, over which the provisioning related communication is to take place, between the device (to be provisioned) and the client (owner of the device). + +In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed. + +Right after provisioning is complete, the UART console is deactivated. + +`console_prov` uses the following components : +* `wifi_provisioning` : provides data structures and protocomm endpoint handlers for Wi-Fi configuration +* `protocomm` : for protocol based communication and secure session establishment +* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures + +This example can be used, as it is, for adding a provisioning service to any application intended for IoT. But it is more suitable for debugging protocomm and provisioning related components and feature additions. + +## How to use example + +### Hardware Required + +Example should be able to run on any commonly available ESP32 development board. + +### Application Required + +To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). This feature of `esp_prov` should work on all platforms, given the dependencies are satisfied. + +### Configure the project + +``` +idf.py menuconfig +``` + +* Under Example Configuration set the following : + * Security Version (default 1) + * Proof of Possession (default "abcd1234") + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (388) app: Starting console provisioning +I (398) app_prov: Console provisioning started +. +. +. +>> +``` + +In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration, the script should be run as follows : + +``` +python esp_prov.py --transport console --proto_ver "V0.1" --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword +``` + +A console will open up and the `Client->Device` commands have to be copied manually to the serial monitor console prompt : + +``` +==== Verifying protocol version ==== +Client->Device msg : proto-ver 0 56302e31 +Enter device->client msg : +``` + +On pasting the command on the serial monitor console, a `Device->Client` message will appear for each command : + +``` +>> proto-ver 0 56302e31 +53554343455353 +``` + +Copy this message back to the `esp_prov` console for proceeding to the next command : + +``` +==== Verifying protocol version ==== +Client->Device msg : proto-ver 0 56302e31 +Enter device->client msg : 53554343455353 +==== Verified protocol version successfully ==== + +==== Starting Session ==== +Client->Device msg : prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970 +Enter device->client msg : +``` + +This process keeps on till the device gets provisioned. + +Note that the commands are in the following format : + +``` + +``` + +This is helpful in understanding the provisioning process and the order in which the endpoints are communicated with. + +The full execution sequence of `esp_prov`, as seen on the console, is shown here : + +``` +==== Verifying protocol version ==== +Client->Device msg : proto-ver 0 56302e31 +Enter device->client msg : 53554343455353 +==== Verified protocol version successfully ==== + +==== Starting Session ==== +Client->Device msg : prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970 +Enter device->client msg : 10015a390801aa013412207566f4de191f600ea42de5c2b1df73f1f16685c2edb43d7c3ffc83d6b81ff61b1a103db6476536a88db10b7e0a172d4adef8 +Client->Device msg : prov-session 0 10015a270802b20122122084ca311e51c904a94f8a249c049f7aed33b39671cc11f0b92b15b299ef5653b7 +Enter device->client msg : 10015a270803ba01221a203246230190d5c1f5d94c01b56ac8cace1086cfb2d937a4a46cb6c79db7a35a8b +==== Session Established ==== + +==== Sending Wifi credential to esp32 ==== +Client->Device msg : prov-config 0 8f0c8cb6f2d53c4cc53b29be8ba1aac3edbb1dead39117c34687d6 +Enter device->client msg : 2e1f0eb0 +==== Wifi Credentials sent successfully ==== + +==== Applying config to esp32 ==== +Client->Device msg : prov-config 0 e8df +Enter device->client msg : 245c83f0 +==== Apply config sent successfully ==== + +==== Wifi connection state ==== +Client->Device msg : prov-config 0 2d36 +Enter device->client msg : 1b38a7411b6e2608aae50a6571807e04a6e90520b3b1e3c1e5b38cea4b9022e56485b92ff84289df218311972a42eb +++++ WiFi state: connected ++++ +==== Provisioning was successful ==== +``` + +The serial monitor console, for above sequence of commands, would look like : + +``` +>> proto-ver 0 56302e31 +53554343455353 +>> prov-session 0 10015a25a201220a20677106cc2f5b2acb5d8da26f0ad443df006daa1cd5bb3d75a8324d81ec5ef970 +10015a390801aa013412207566f4de191f600ea42de5c2b1df73f1f16685c2edb43d7c3ffc83d6b81ff61b1a103db6476536a88db10b7e0a172d4adef8 +>> prov-session 0 10015a270802b20122122084ca311e51c904a94f8a249c049f7aed33b39671cc11f0b92b15b299ef5653b7 +10015a270803ba01221a203246230190d5c1f5d94c01b56ac8cace1086cfb2d937a4a46cb6c79db7a35a8b +>> prov-config 0 8f0c8cb6f2d53c4cc53b29be8ba1aac3edbb1dead39117c34687d6 +I (1073738) app_prov_handler: WiFi Credentials Received : + ssid : myssid + password : mypassword + +2e1f0eb0 +>> prov-config 0 e8df +I (1084218) app_prov_handler: WiFi Credentials Applied + +245c83f0 +>> prov-config 0 2d36 +I (1089728) app_prov: STA Got IP +I (1089728) app: got ip:192.168.43.220 +I (1099698) app_prov_handler: Connected state + +1b38a7411b6e2608aae50a6571807e04a6e90520b3b1e3c1e5b38cea4b9022e56485b92ff84289df218311972a42eb +>> +``` + +After sometime the provisioning app will exit and UART console will be stopped + +``` +I (1119728) app_prov: Stopping provisioning +I (1119728) protocomm_console: Stopping console... +I (1119728) app_prov: Provisioning stopped +I (1119748) protocomm_console: Console stopped +``` + +## Troubleshooting + +### Provisioning failed + +It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason) and the provisioning app will continue running, allowing the user to retry the process. Serial monitor log will display the failure along with disconnect reason : + +``` +E (39291) app_prov: STA Disconnected +E (39291) app_prov: Disconnect reason : 201 +I (39291) app_prov: STA AP Not found +I (42021) app_prov_handler: Disconnected state +``` + +### Provisioning does not start + +If the serial monitor log is different, as shown below : + +``` +I (539) app_prov: Found ssid myssid +I (539) app_prov: Found password mypassword +I (549) app: Starting WiFi station +``` + +It means the Wi-Fi credentials were already set by some other application flashed previously to your device. To erase these credentials either do full erase and then flash the example + +``` +make erase_flash +idf.py -p PORT flash monitor +``` + +Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution. diff --git a/examples/provisioning/legacy/console_prov/main/CMakeLists.txt b/examples/provisioning/legacy/console_prov/main/CMakeLists.txt new file mode 100644 index 00000000..a483b387 --- /dev/null +++ b/examples/provisioning/legacy/console_prov/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "app_main.c" + "app_prov.c" + "app_prov_handlers.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/examples/provisioning/legacy/console_prov/main/Kconfig.projbuild b/examples/provisioning/legacy/console_prov/main/Kconfig.projbuild new file mode 100644 index 00000000..b1578f84 --- /dev/null +++ b/examples/provisioning/legacy/console_prov/main/Kconfig.projbuild @@ -0,0 +1,40 @@ +menu "Example Configuration" + + config EXAMPLE_USE_SEC_1 + bool + default y + prompt "Use Security Version 1" + help + Security version 1 used Curve25519 key exchange for establishing + secure session between device and client during provisioning + + config EXAMPLE_USE_POP + bool + depends on EXAMPLE_USE_SEC_1 + default y + prompt "Use proof-of-possession" + help + Proof-of-possession can be optionally used to prove that the device is indeed + in possession of the user who is provisioning the device. This proof-of-possession + is internally used to generate the shared secret through key exchange. + + config EXAMPLE_POP + string "Proof-of-possession" + default "abcd1234" + depends on EXAMPLE_USE_POP + + config EXAMPLE_RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status of the device" + help + This erases the NVS to reset provisioned status of the device on every reboot. + Provisioned status is determined by the Wi-Fi STA configuration, saved on the NVS. + + config EXAMPLE_AP_RECONN_ATTEMPTS + int "Maximum AP connection attempts" + default 5 + help + Set the maximum connection attempts to perform when connecting to a Wi-Fi AP. + +endmenu diff --git a/examples/provisioning/legacy/console_prov/main/app_main.c b/examples/provisioning/legacy/console_prov/main/app_main.c new file mode 100644 index 00000000..25806de8 --- /dev/null +++ b/examples/provisioning/legacy/console_prov/main/app_main.c @@ -0,0 +1,116 @@ +/* Console based Provisioning Example + + 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 "app_prov.h" + +#define EXAMPLE_AP_RECONN_ATTEMPTS CONFIG_EXAMPLE_AP_RECONN_ATTEMPTS + +static const char *TAG = "app"; + +static void event_handler(void* arg, esp_event_base_t event_base, + int event_id, void* event_data) +{ + static int s_retry_num = 0; + + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + esp_wifi_connect(); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + if (s_retry_num < EXAMPLE_AP_RECONN_ATTEMPTS) { + esp_wifi_connect(); + s_retry_num++; + ESP_LOGI(TAG, "retry to connect to the AP"); + } + ESP_LOGI(TAG,"connect to the AP fail"); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; + ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + s_retry_num = 0; + } +} + +static void wifi_init_sta(void) +{ + /* Set our event handling */ + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handler, NULL)); + + /* Start Wi-Fi in station mode with credentials set during provisioning */ + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); +} + +static void start_console_provisioning(void) +{ + /* Security version */ + int security = 0; + /* Proof of possession */ + const protocomm_security_pop_t *pop = NULL; + +#ifdef CONFIG_EXAMPLE_USE_SEC_1 + security = 1; +#endif + + /* Having proof of possession is optional */ +#ifdef CONFIG_EXAMPLE_USE_POP + const static protocomm_security_pop_t app_pop = { + .data = (uint8_t *) CONFIG_EXAMPLE_POP, + .len = (sizeof(CONFIG_EXAMPLE_POP)-1) + }; + pop = &app_pop; +#endif + + ESP_ERROR_CHECK(app_prov_start_console_provisioning(security, pop)); +} + +void app_main(void) +{ + /* Initialize networking stack */ + ESP_ERROR_CHECK(esp_netif_init()); + + /* Create default event loop needed by the + * main app and the provisioning service */ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* Initialize NVS needed by Wi-Fi */ + ESP_ERROR_CHECK(nvs_flash_init()); + + /* Initialize Wi-Fi including netif with default config */ + esp_netif_create_default_wifi_sta(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + /* Check if device is provisioned */ + bool provisioned; + if (app_prov_is_provisioned(&provisioned) != ESP_OK) { + ESP_LOGE(TAG, "Error getting device provisioning state"); + return; + } + + if (provisioned == false) { + /* If not provisioned, start provisioning via console */ + ESP_LOGI(TAG, "Starting console provisioning"); + start_console_provisioning(); + } else { + /* Else start as station with credentials set during provisioning */ + ESP_LOGI(TAG, "Starting WiFi station"); + wifi_init_sta(); + } +} diff --git a/examples/provisioning/legacy/console_prov/main/app_prov.c b/examples/provisioning/legacy/console_prov/main/app_prov.c new file mode 100644 index 00000000..b260db5f --- /dev/null +++ b/examples/provisioning/legacy/console_prov/main/app_prov.c @@ -0,0 +1,325 @@ +/* Console based Provisioning Example + + 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 + +#include "app_prov.h" + +static const char *TAG = "app_prov"; + +/* Handler for catching WiFi events */ +static void app_prov_event_handler(void* handler_arg, esp_event_base_t base, int id, void* data); + +/* Handlers for wifi_config provisioning endpoint */ +extern wifi_prov_config_handlers_t wifi_prov_handlers; + +/** + * @brief Data relevant to provisioning application + */ +struct app_prov_data { + protocomm_t *pc; /*!< Protocomm handler */ + int security; /*!< Type of security to use with protocomm */ + const protocomm_security_pop_t *pop; /*!< Pointer to proof of possession */ + esp_timer_handle_t timer; /*!< Handle to timer */ + + /* State of WiFi Station */ + wifi_prov_sta_state_t wifi_state; + + /* Code for WiFi station disconnection (if disconnected) */ + wifi_prov_sta_fail_reason_t wifi_disconnect_reason; +}; + +/* Pointer to provisioning application data */ +static struct app_prov_data *g_prov; + +static esp_err_t app_prov_start_service(void) +{ + /* Create new protocomm instance */ + g_prov->pc = protocomm_new(); + if (g_prov->pc == NULL) { + ESP_LOGE(TAG, "Failed to create new protocomm instance"); + return ESP_FAIL; + } + + /* Config for protocomm_console_start() */ + protocomm_console_config_t config = PROTOCOMM_CONSOLE_DEFAULT_CONFIG(); + + /* Start protocomm using console */ + if (protocomm_console_start(g_prov->pc, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start console provisioning"); + return ESP_FAIL; + } + + /* Set protocomm version verification endpoint for protocol */ + protocomm_set_version(g_prov->pc, "proto-ver", "V0.1"); + + /* Set protocomm security type for endpoint */ + if (g_prov->security == 0) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security0, NULL); + } else if (g_prov->security == 1) { + protocomm_set_security(g_prov->pc, "prov-session", &protocomm_security1, g_prov->pop); + } + + /* Add endpoint for provisioning to set wifi station config */ + if (protocomm_add_endpoint(g_prov->pc, "prov-config", + wifi_prov_config_data_handler, + (void *) &wifi_prov_handlers) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set provisioning endpoint"); + protocomm_console_stop(g_prov->pc); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Provisioning started"); + return ESP_OK; +} + +static void app_prov_stop_service(void) +{ + /* Remove provisioning endpoint */ + protocomm_remove_endpoint(g_prov->pc, "prov-config"); + /* Unset provisioning security */ + protocomm_unset_security(g_prov->pc, "prov-session"); + /* Unset provisioning version endpoint */ + protocomm_unset_version(g_prov->pc, "proto-ver"); + /* Stop protocomm console service */ + protocomm_console_stop(g_prov->pc); + /* Delete protocomm instance */ + protocomm_delete(g_prov->pc); + + /* Remove event handler */ + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler); +} + +/* Task spawned by timer callback */ +static void stop_prov_task(void * arg) +{ + ESP_LOGI(TAG, "Stopping provisioning"); + app_prov_stop_service(); + + /* Timer not needed anymore */ + esp_timer_handle_t timer = g_prov->timer; + esp_timer_delete(timer); + g_prov->timer = NULL; + + /* Free provisioning process data */ + free(g_prov); + g_prov = NULL; + ESP_LOGI(TAG, "Provisioning stopped"); + + vTaskDelete(NULL); +} + +/* Callback to be invoked by timer */ +static void _stop_prov_cb(void * arg) +{ + xTaskCreate(&stop_prov_task, "stop_prov", 2048, NULL, tskIDLE_PRIORITY, NULL); +} + +/* Event handler for starting/stopping provisioning */ +static void app_prov_event_handler(void* handler_arg, esp_event_base_t event_base, + int event_id, void* event_data) +{ + /* If pointer to provisioning application data is NULL + * then provisioning is not running */ + if (!g_prov) { + 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 */ + g_prov->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. + * Schedule timer to stop provisioning app after 30 seconds. */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTED; + if (g_prov && g_prov->timer) { + esp_timer_start_once(g_prov->timer, 30000*1000U); + } + } 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 */ + g_prov->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_BEACON_TIMEOUT: + case WIFI_REASON_AUTH_FAIL: + case WIFI_REASON_ASSOC_FAIL: + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGI(TAG, "STA Auth Error"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR; + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGI(TAG, "STA AP Not found"); + g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND; + break; + default: + /* If none of the expected reasons, + * retry connecting to host SSID */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + esp_wifi_connect(); + } + } +} + +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state) +{ + if (g_prov == NULL || state == NULL) { + return ESP_FAIL; + } + + *state = g_prov->wifi_state; + return ESP_OK; +} + +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason) +{ + if (g_prov == NULL || reason == NULL) { + return ESP_FAIL; + } + + if (g_prov->wifi_state != WIFI_PROV_STA_DISCONNECTED) { + return ESP_FAIL; + } + + *reason = g_prov->wifi_disconnect_reason; + return ESP_OK; +} + +esp_err_t app_prov_is_provisioned(bool *provisioned) +{ + *provisioned = false; + +#ifdef CONFIG_EXAMPLE_RESET_PROVISIONED + nvs_flash_erase(); +#endif + + /* Get WiFi 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; + ESP_LOGI(TAG, "Found ssid %s", (const char*) wifi_cfg.sta.ssid); + ESP_LOGI(TAG, "Found password %s", (const char*) wifi_cfg.sta.password); + } + return ESP_OK; +} + +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg) +{ + /* Configure WiFi as station */ + if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode"); + return ESP_FAIL; + } + /* Configure WiFi 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 WiFi configuration"); + return ESP_FAIL; + } + /* Start WiFi */ + if (esp_wifi_start() != ESP_OK) { + ESP_LOGE(TAG, "Failed to set WiFi configuration"); + return ESP_FAIL; + } + /* Connect to AP */ + if (esp_wifi_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect WiFi"); + return ESP_FAIL; + } + + if (g_prov) { + /* Reset wifi station state for provisioning app */ + g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; + } + return ESP_OK; +} + +esp_err_t app_prov_start_console_provisioning(int security, const protocomm_security_pop_t *pop) +{ + /* If provisioning app data present, + * means provisioning app is already running */ + if (g_prov) { + ESP_LOGI(TAG, "Invalid provisioning state"); + return ESP_FAIL; + } + + /* Allocate memory for provisioning app data */ + g_prov = (struct app_prov_data *) calloc(1, sizeof(struct app_prov_data)); + if (!g_prov) { + ESP_LOGI(TAG, "Unable to allocate prov data"); + return ESP_ERR_NO_MEM; + } + + /* Initialize app data */ + g_prov->pop = pop; + g_prov->security = security; + + /* Create timer object as a member of app data */ + esp_timer_create_args_t timer_conf = { + .callback = _stop_prov_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "stop_console_tm" + }; + esp_err_t err = esp_timer_create(&timer_conf, &g_prov->timer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to create timer"); + return err; + } + + err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register WiFi event handler"); + return err; + } + + err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register IP event handler"); + return err; + } + + /* Start provisioning service through console */ + err = app_prov_start_service(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Provisioning failed to start"); + return err; + } + + ESP_LOGI(TAG, "Console provisioning started"); + return ESP_OK; +} diff --git a/examples/provisioning/legacy/console_prov/main/app_prov.h b/examples/provisioning/legacy/console_prov/main/app_prov.h new file mode 100644 index 00000000..5e6d8847 --- /dev/null +++ b/examples/provisioning/legacy/console_prov/main/app_prov.h @@ -0,0 +1,79 @@ +/* Console based Provisioning Example + + 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. +*/ + +#pragma once + +#include +#include + +/** + * @brief Get state of WiFi Station during provisioning + * + * @note WiFi is initially configured as AP, when + * provisioning starts. After provisioning data + * is provided by user, the WiFi is reconfigured + * to run as both AP and Station. + * + * @param[out] state Pointer to wifi_prov_sta_state_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi state + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state); + +/** + * @brief Get reason code in case of WiFi station + * disconnection during provisioning + * +* @param[out] reason Pointer to wifi_prov_sta_fail_reason_t variable to be filled + * + * @return + * - ESP_OK : Successfully retrieved wifi disconnect reason + * - ESP_FAIL : Provisioning app not running + */ +esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason); + +/** + * @brief Checks if device is provisioned + * * + * @param[out] provisioned True if provisioned, else false + * + * @return + * - ESP_OK : Retrieved provision state successfully + * - ESP_FAIL : Failed to retrieve provision state + */ +esp_err_t app_prov_is_provisioned(bool *provisioned); + +/** + * @brief Runs WiFi as Station + * + * Configures the WiFi station mode to connect to the + * SSID and password specified in config structure, + * and starts WiFi to run as station + * + * @param[in] wifi_cfg Pointer to WiFi cofiguration structure + * + * @return + * - ESP_OK : WiFi configured and started successfully + * - ESP_FAIL : Failed to set configuration + */ +esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg); + +/** + * @brief Start provisioning via Console + * + * @param[in] security Security mode + * @param[in] pop Pointer to proof of possession (NULL if not present) + * + * @return + * - ESP_OK : Provisioning started successfully + * - ESP_FAIL : Failed to start + */ +esp_err_t app_prov_start_console_provisioning(int security, const protocomm_security_pop_t *pop); diff --git a/examples/provisioning/legacy/console_prov/main/app_prov_handlers.c b/examples/provisioning/legacy/console_prov/main/app_prov_handlers.c new file mode 100644 index 00000000..850be09c --- /dev/null +++ b/examples/provisioning/legacy/console_prov/main/app_prov_handlers.c @@ -0,0 +1,131 @@ +/* Console based Provisioning Example + + 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. +*/ + +/* This file is mostly a boiler-plate code that applications can use without much change */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "app_prov.h" + +static const char* TAG = "app_prov_handler"; + +/* Provide definition of wifi_prov_ctx_t */ +struct wifi_prov_ctx { + wifi_config_t wifi_cfg; +}; + +static wifi_config_t *get_config(wifi_prov_ctx_t **ctx) +{ + return (*ctx ? &(*ctx)->wifi_cfg : NULL); +} + +static wifi_config_t *new_config(wifi_prov_ctx_t **ctx) +{ + free(*ctx); + (*ctx) = (wifi_prov_ctx_t *) calloc(1, sizeof(wifi_prov_ctx_t)); + return get_config(ctx); +} + +static void free_config(wifi_prov_ctx_t **ctx) +{ + free(*ctx); + *ctx = NULL; +} + +static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data, wifi_prov_ctx_t **ctx) +{ + /* Initialize to zero */ + memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t)); + + if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { + ESP_LOGW(TAG, "Prov app not running"); + return ESP_FAIL; + } + + if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) { + ESP_LOGI(TAG, "Connected state"); + + /* IP Addr assigned to STA */ + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info); + esp_ip4addr_ntoa(&ip_info.ip, resp_data->conn_info.ip_addr, sizeof(resp_data->conn_info.ip_addr)); + + /* AP information to which STA is connected */ + wifi_ap_record_t ap_info; + esp_wifi_sta_get_ap_info(&ap_info); + memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); + memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); + resp_data->conn_info.channel = ap_info.primary; + resp_data->conn_info.auth_mode = ap_info.authmode; + } else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Disconnected state"); + + /* If disconnected, convey reason */ + app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason); + } else { + ESP_LOGI(TAG, "Connecting state"); + } + return ESP_OK; +} + +static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data, wifi_prov_ctx_t **ctx) +{ + wifi_config_t *wifi_cfg = get_config(ctx); + if (wifi_cfg) { + free_config(ctx); + } + + wifi_cfg = new_config(ctx); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Unable to alloc wifi config"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s", + req_data->ssid, req_data->password); + + /* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard). + * But this doesn't guarantee that the saved SSID will be null terminated, because + * wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character). + * Although, this is not a matter for concern because esp_wifi library reads the SSID + * upto 32 bytes in absence of null termination */ + strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid)); + strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password)); + return ESP_OK; +} + +static esp_err_t apply_config_handler(wifi_prov_ctx_t **ctx) +{ + wifi_config_t *wifi_cfg = get_config(ctx); + if (!wifi_cfg) { + ESP_LOGE(TAG, "WiFi config not set"); + return ESP_FAIL; + } + + app_prov_configure_sta(wifi_cfg); + ESP_LOGI(TAG, "WiFi Credentials Applied"); + + free_config(ctx); + return ESP_OK; +} + +wifi_prov_config_handlers_t wifi_prov_handlers = { + .get_status_handler = get_status_handler, + .set_config_handler = set_config_handler, + .apply_config_handler = apply_config_handler, + .ctx = NULL +}; diff --git a/examples/provisioning/softap_prov/main/component.mk b/examples/provisioning/legacy/console_prov/main/component.mk similarity index 100% rename from examples/provisioning/softap_prov/main/component.mk rename to examples/provisioning/legacy/console_prov/main/component.mk diff --git a/examples/provisioning/custom_config/CMakeLists.txt b/examples/provisioning/legacy/custom_config/CMakeLists.txt similarity index 51% rename from examples/provisioning/custom_config/CMakeLists.txt rename to examples/provisioning/legacy/custom_config/CMakeLists.txt index 52f2c76f..ec9b0ce6 100644 --- a/examples/provisioning/custom_config/CMakeLists.txt +++ b/examples/provisioning/legacy/custom_config/CMakeLists.txt @@ -2,9 +2,5 @@ # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) -# (Not part of the boilerplate) -# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. -set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) - include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(custom_config) diff --git a/examples/provisioning/custom_config/Makefile b/examples/provisioning/legacy/custom_config/Makefile similarity index 67% rename from examples/provisioning/custom_config/Makefile rename to examples/provisioning/legacy/custom_config/Makefile index 2a74b778..8ded51d7 100644 --- a/examples/provisioning/custom_config/Makefile +++ b/examples/provisioning/legacy/custom_config/Makefile @@ -5,7 +5,5 @@ PROJECT_NAME := custom_config -EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common - include $(IDF_PATH)/make/project.mk diff --git a/examples/provisioning/custom_config/README.md b/examples/provisioning/legacy/custom_config/README.md similarity index 90% rename from examples/provisioning/custom_config/README.md rename to examples/provisioning/legacy/custom_config/README.md index 6a07e69f..0af5006c 100644 --- a/examples/provisioning/custom_config/README.md +++ b/examples/provisioning/legacy/custom_config/README.md @@ -1,4 +1,6 @@ -# SoftAP + HTTPD based Provisioning Example featuring Custom configuration +# SoftAP + HTTPD based Provisioning Example featuring Custom configuration (Legacy) + +> Note: It is recommended to use the new `wifi_prov_mgr` example which is based on the simpler `wifi_provisioning` APIs. Check this example only if you wish to use lower level provisioning and protocomm APIs and want more control over the handlers. (See the README.md file in the upper level 'examples' directory for more information about examples.) @@ -29,11 +31,9 @@ To provision the device running this example, the `esp_prov.py` script needs to ### Configure the project ``` -make menuconfig +idf.py menuconfig ``` -* Set serial port under Serial Flasher Options. - * Under Example Configuration set the following : * SoftAP SSID (Defaults to PROV_) * SoftAP Password (Defaults to PROV_PASS) @@ -45,7 +45,7 @@ make menuconfig Build the project and flash it to the board, then run monitor tool to view serial output: ``` -make -j4 flash monitor +idf.py -p PORT flash monitor ``` (To exit the serial monitor, type ``Ctrl-]``.) @@ -68,7 +68,7 @@ I (519482) tcpip_adapter: softAP assign IP to station,IP is: 192.168.4.2 In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace the values corresponding to the parameters `--custom_info` and `--custom_ver` with your desired values for the custom configuration). Assuming default example configuration, the script should be run as follows : ``` -python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 0 --transport softap --softap_endpoint 192.168.4.1:80 --custom_config --custom_info "some string" --custom_ver 4321 +python esp_prov.py --transport softap --service_name "192.168.4.1:80" --sec_ver 0 --ssid myssid --passphrase mypassword --custom_config --custom_info "some string" --custom_ver 4321 ``` Above command will perform the provisioning steps, and the monitor log should display something like this : @@ -138,7 +138,7 @@ It means the Wi-Fi credentials were already set by some other application flashe ``` make erase_flash -make -j4 flash monitor +idf.py -p PORT flash monitor ``` Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution. diff --git a/examples/provisioning/legacy/custom_config/components/custom_provisioning/CMakeLists.txt b/examples/provisioning/legacy/custom_config/components/custom_provisioning/CMakeLists.txt new file mode 100644 index 00000000..bcffb88b --- /dev/null +++ b/examples/provisioning/legacy/custom_config/components/custom_provisioning/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "src/custom_config.c" + "proto-c/custom_config.pb-c.c" + INCLUDE_DIRS include + PRIV_INCLUDE_DIRS proto-c + PRIV_REQUIRES protobuf-c) \ No newline at end of file diff --git a/examples/provisioning/custom_config/components/custom_provisioning/component.mk b/examples/provisioning/legacy/custom_config/components/custom_provisioning/component.mk similarity index 100% rename from examples/provisioning/custom_config/components/custom_provisioning/component.mk rename to examples/provisioning/legacy/custom_config/components/custom_provisioning/component.mk diff --git a/examples/provisioning/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h b/examples/provisioning/legacy/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h similarity index 98% rename from examples/provisioning/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h rename to examples/provisioning/legacy/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h index e76c808e..a9b6214a 100644 --- a/examples/provisioning/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h +++ b/examples/provisioning/legacy/custom_config/components/custom_provisioning/include/custom_provisioning/custom_config.h @@ -14,7 +14,6 @@ #ifndef _CUSTOM_PROV_CONFIG_H_ #define _CUSTOM_PROV_CONFIG_H_ -#include /** * @brief Custom config data received by device diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.c b/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.c similarity index 100% rename from examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.c rename to examples/provisioning/legacy/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.c diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.h b/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.h similarity index 100% rename from examples/provisioning/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.h rename to examples/provisioning/legacy/custom_config/components/custom_provisioning/proto-c/custom_config.pb-c.h diff --git a/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/CMakeLists.txt b/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/CMakeLists.txt new file mode 100644 index 00000000..153cfdce --- /dev/null +++ b/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.5) + +set(PROTO_COMPILER "protoc") +set(PROTO_C_COMPILER "protoc-c") +set(C_OUT_PATH "${CMAKE_CURRENT_LIST_DIR}/../proto-c") +set(PY_OUT_PATH "${CMAKE_CURRENT_LIST_DIR}/../python") + +set(PROTO_SRCS "custom_config.proto") + +add_custom_target(c_proto + COMMAND ${PROTO_C_COMPILER} --c_out=${C_OUT_PATH} -I . ${PROTO_SRCS} + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) + +add_custom_target(python_proto + COMMAND ${PROTO_COMPILER} --python_out=${PY_OUT_PATH} -I . ${PROTO_SRCS} + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) + +add_custom_target(proto ALL + DEPENDS c_proto python_proto + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + ) diff --git a/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/README.md b/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/README.md new file mode 100644 index 00000000..933e82cb --- /dev/null +++ b/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/README.md @@ -0,0 +1,27 @@ +# Protobuf files for defining custom config-data packet structures + +This is an example proto file defining custom configuration related data packet structures, namely - +1. CustomConfigRequest - for sending configuration data consisting of various fields (Info and Version) +2. CustomConfigResponse - for receiving configuration status (fail/success) + +Note : These proto files are not automatically compiled during the build process. + +# Compilation + +Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under `examples/provisioning/custom_config/components/custom_provisioning/proto-c` and `examples/provisioning/custom_config/components/custom_provisioning/python` directories, and thus running cmake / make (and installing the Protobuf compilers) is optional. + +If using `cmake` follow the below steps. If using `make`, jump to Step 2 directly. + +## Step 1 (Only for cmake) + +When using cmake, first create a build directory and call cmake from inside: + +``` +mkdir build +cd build +cmake .. +``` + +## Step 2 + +Simply run `make` to generate the respective C and Python files. The newly created files will overwrite those under `examples/provisioning/custom_config/components/custom_provisioning/proto-c` and `examples/provisioning/custom_config/components/custom_provisioning/python` diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto/custom_config.proto b/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/custom_config.proto similarity index 100% rename from examples/provisioning/custom_config/components/custom_provisioning/proto/custom_config.proto rename to examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/custom_config.proto diff --git a/examples/provisioning/custom_config/components/custom_provisioning/proto/makefile b/examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/makefile similarity index 100% rename from examples/provisioning/custom_config/components/custom_provisioning/proto/makefile rename to examples/provisioning/legacy/custom_config/components/custom_provisioning/proto/makefile diff --git a/examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py b/examples/provisioning/legacy/custom_config/components/custom_provisioning/python/custom_config_pb2.py similarity index 100% rename from examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py rename to examples/provisioning/legacy/custom_config/components/custom_provisioning/python/custom_config_pb2.py diff --git a/examples/provisioning/custom_config/components/custom_provisioning/src/custom_config.c b/examples/provisioning/legacy/custom_config/components/custom_provisioning/src/custom_config.c similarity index 100% rename from examples/provisioning/custom_config/components/custom_provisioning/src/custom_config.c rename to examples/provisioning/legacy/custom_config/components/custom_provisioning/src/custom_config.c diff --git a/examples/provisioning/legacy/custom_config/main/CMakeLists.txt b/examples/provisioning/legacy/custom_config/main/CMakeLists.txt new file mode 100644 index 00000000..a483b387 --- /dev/null +++ b/examples/provisioning/legacy/custom_config/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "app_main.c" + "app_prov.c" + "app_prov_handlers.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/examples/provisioning/legacy/custom_config/main/Kconfig.projbuild b/examples/provisioning/legacy/custom_config/main/Kconfig.projbuild new file mode 100644 index 00000000..0d353fdb --- /dev/null +++ b/examples/provisioning/legacy/custom_config/main/Kconfig.projbuild @@ -0,0 +1,58 @@ +menu "Example Configuration" + + config EXAMPLE_SSID + string "Wi-Fi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + + config EXAMPLE_PASS + string "Wi-Fi Password" + default "mypassword" + help + Wi-Fi password (WPA or WPA2) for the example to use. + + config EXAMPLE_USE_SEC_1 + bool + default n + prompt "Use Security Version 1" + help + Security version 1 used Curve25519 key exchange for establishing + secure session between device and client during provisioning + + config EXAMPLE_USE_POP + bool + depends on EXAMPLE_USE_SEC_1 + default n + prompt "Use proof-of-possession" + help + Proof-of-possession can be optionally used to prove that the device is indeed + in possession of the user who is provisioning the device. This proof-of-possession + is internally used to generate the shared secret through key exchange. + + config EXAMPLE_POP + string "Proof-of-possession" + default "abcd1234" + depends on EXAMPLE_USE_POP + + config EXAMPLE_PROTOCOMM_HTTPD_PORT + int "Protocomm HTTP Port" + default 80 + help + Port on which to run Protocomm HTTP based provisioning service + + config EXAMPLE_RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status of the device" + help + This erases the NVS to reset provisioned status of the device on every reboot. + Provisioned status is determined by the Wi-Fi STA configuration, saved on the NVS. + + config EXAMPLE_AP_RECONN_ATTEMPTS + int "Maximum AP connection attempts" + default 5 + help + Set the maximum connection attempts to perform when connecting to a Wi-Fi AP. + +endmenu diff --git a/examples/provisioning/custom_config/main/app_main.c b/examples/provisioning/legacy/custom_config/main/app_main.c similarity index 69% rename from examples/provisioning/custom_config/main/app_main.c rename to examples/provisioning/legacy/custom_config/main/app_main.c index 63a8cd44..773789a3 100644 --- a/examples/provisioning/custom_config/main/app_main.c +++ b/examples/provisioning/legacy/custom_config/main/app_main.c @@ -8,27 +8,23 @@ */ #include - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_system.h" -#include "esp_wifi.h" -#include "esp_log.h" -#include "esp_netif.h" -#include "esp_event.h" -#include "protocol_examples_common.h" -#include "nvs.h" -#include "nvs_flash.h" +#include +#include +#include +#include +#include +#include +#include #include #include #include "app_prov.h" -static const char *TAG = "app"; - #define EXAMPLE_AP_RECONN_ATTEMPTS CONFIG_EXAMPLE_AP_RECONN_ATTEMPTS +static const char *TAG = "app"; + static void event_handler(void* arg, esp_event_base_t event_base, int event_id, void* event_data) { @@ -45,44 +41,50 @@ static void event_handler(void* arg, esp_event_base_t event_base, ESP_LOGI(TAG,"connect to the AP fail"); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; - ESP_LOGI(TAG, "got ip:%s", - ip4addr_ntoa(&event->ip_info.ip)); + ESP_LOGI(TAG, "got ip: " IPSTR, IP2STR(&event->ip_info.ip)); s_retry_num = 0; } } -static void wifi_init_sta() +static void wifi_init_sta(void) { /* Set our event handling */ ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handler, NULL)); - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); - ESP_ERROR_CHECK(esp_wifi_start() ); + /* Start Wi-Fi in station mode with credentials set during provisioning */ + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); } -void app_main() +static void start_softap_provisioning(void) { /* Security version */ int security = 0; /* Proof of possession */ const protocomm_security_pop_t *pop = NULL; -#ifdef CONFIG_USE_SEC_1 +#ifdef CONFIG_EXAMPLE_USE_SEC_1 security = 1; #endif /* Having proof of possession is optional */ -#ifdef CONFIG_USE_POP +#ifdef CONFIG_EXAMPLE_USE_POP const static protocomm_security_pop_t app_pop = { - .data = (uint8_t *) CONFIG_POP, - .len = (sizeof(CONFIG_POP)-1) + .data = (uint8_t *) CONFIG_EXAMPLE_POP, + .len = (sizeof(CONFIG_EXAMPLE_POP)-1) }; pop = &app_pop; #endif + ESP_ERROR_CHECK(app_prov_start_softap_provisioning( + CONFIG_EXAMPLE_SSID, CONFIG_EXAMPLE_PASS, security, pop)); +} + +void app_main(void) +{ /* Initialize networking stack */ - tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_netif_init()); /* Create default event loop needed by the * main app and the provisioning service */ @@ -91,6 +93,12 @@ void app_main() /* Initialize NVS needed by Wi-Fi */ ESP_ERROR_CHECK(nvs_flash_init()); + /* Initialize Wi-Fi including netif with default config */ + esp_netif_create_default_wifi_sta(); + esp_netif_create_default_wifi_ap(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + /* Check if device is provisioned */ bool provisioned; if (app_prov_is_provisioned(&provisioned) != ESP_OK) { @@ -101,11 +109,10 @@ void app_main() if (provisioned == false) { /* If not provisioned, start provisioning via soft AP */ ESP_LOGI(TAG, "Starting WiFi SoftAP provisioning"); - app_prov_start_softap_provisioning(CONFIG_SOFTAP_SSID, CONFIG_SOFTAP_PASS, - security, pop); + start_softap_provisioning(); } else { /* Start WiFi station with credentials set during provisioning */ ESP_LOGI(TAG, "Starting WiFi station"); - wifi_init_sta(NULL); + wifi_init_sta(); } } diff --git a/examples/provisioning/custom_config/main/app_prov.c b/examples/provisioning/legacy/custom_config/main/app_prov.c similarity index 84% rename from examples/provisioning/custom_config/main/app_prov.c rename to examples/provisioning/legacy/custom_config/main/app_prov.c index 64403ce5..d9859cdb 100644 --- a/examples/provisioning/custom_config/main/app_prov.c +++ b/examples/provisioning/legacy/custom_config/main/app_prov.c @@ -13,10 +13,8 @@ #include #include #include +#include -#include -#include -#include #include #include #include @@ -28,6 +26,9 @@ static const char *TAG = "app_prov"; +/* Handler for catching WiFi events */ +static void app_prov_event_handler(void* handler_arg, esp_event_base_t base, int id, void* data); + /* Handlers for provisioning endpoints */ extern wifi_prov_config_handlers_t wifi_prov_handlers; extern custom_prov_config_handler_t custom_prov_handler; @@ -117,6 +118,10 @@ static void app_prov_stop_service(void) protocomm_httpd_stop(g_prov->pc); /* Delete protocomm instance */ protocomm_delete(g_prov->pc); + + /* Remove event handler */ + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler); } /* Task spawned by timer callback */ @@ -145,33 +150,24 @@ static void _stop_prov_cb(void * arg) xTaskCreate(&stop_prov_task, "stop_prov", 2048, NULL, tskIDLE_PRIORITY, NULL); } -/* Event handler for starting/stopping provisioning. - * To be called from within the context of the main - * event handler. - */ -esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) +/* Event handler for starting/stopping provisioning */ +static void app_prov_event_handler(void* handler_arg, esp_event_base_t event_base, + int event_id, void* event_data) { - /* For accessing reason codes in case of disconnection */ - system_event_info_t *info = &event->event_info; - /* If pointer to provisioning application data is NULL - * then provisioning is not running, therefore return without - * error */ + * then provisioning is not running */ if (!g_prov) { - return ESP_OK; + return; } - switch(event->event_id) { - case SYSTEM_EVENT_STA_START: + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { ESP_LOGI(TAG, "STA Start"); - /* Once configuration is received by protocomm server, - * device is restarted as both AP and Station. - * Once station starts, wait for connection to - * establish with configured host SSID and password */ + /* 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 */ g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; - break; - - case SYSTEM_EVENT_STA_GOT_IP: + } 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. * Schedule timer to stop provisioning app after 30 seconds. */ @@ -188,16 +184,16 @@ esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) * signaling a failure in provisioning. */ esp_timer_start_once(g_prov->timer, 30000*1000U); } - break; - - case SYSTEM_EVENT_STA_DISCONNECTED: + } 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 */ g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED; - ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason); + + 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 (info->disconnected.reason) { + switch (disconnected->reason) { case WIFI_REASON_AUTH_EXPIRE: case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: case WIFI_REASON_BEACON_TIMEOUT: @@ -215,18 +211,9 @@ esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) /* If none of the expected reasons, * retry connecting to host SSID */ g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; - if (info->disconnected.reason == WIFI_REASON_BASIC_RATE_NOT_SUPPORT) { - /*Switch to 802.11 bgn mode */ - esp_wifi_set_protocol(ESP_IF_WIFI_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N); - } esp_wifi_connect(); } - break; - - default: - break; } - return ESP_OK; } esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state) @@ -255,25 +242,15 @@ esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reaso esp_err_t app_prov_is_provisioned(bool *provisioned) { -#ifdef CONFIG_RESET_PROVISIONED + *provisioned = false; + +#ifdef CONFIG_EXAMPLE_RESET_PROVISIONED nvs_flash_erase(); #endif - if (nvs_flash_init() != ESP_OK) { - ESP_LOGE(TAG, "Failed to init NVS"); - return ESP_FAIL; - } - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - if (esp_wifi_init(&cfg) != ESP_OK) { - ESP_LOGE(TAG, "Failed to init wifi"); - return ESP_FAIL; - } - /* Get WiFi Station configuration */ wifi_config_t wifi_cfg; if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) { - *provisioned = false; return ESP_FAIL; } @@ -318,14 +295,6 @@ esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg) static esp_err_t start_wifi_ap(const char *ssid, const char *pass) { - /* Initialize WiFi with default configuration */ - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - esp_err_t err = esp_wifi_init(&cfg); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to init WiFi : %d", err); - return err; - } - /* Build WiFi configuration for AP mode */ wifi_config_t wifi_config = { .ap = { @@ -333,19 +302,18 @@ static esp_err_t start_wifi_ap(const char *ssid, const char *pass) }, }; - strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); - wifi_config.ap.ssid_len = strlen(ssid); + strlcpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); if (strlen(pass) == 0) { memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password)); wifi_config.ap.authmode = WIFI_AUTH_OPEN; } else { - strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + strlcpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; } /* Start WiFi in AP mode with configuration built above */ - err = esp_wifi_set_mode(WIFI_MODE_AP); + esp_err_t err = esp_wifi_set_mode(WIFI_MODE_AP); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set WiFi mode : %d", err); return err; @@ -381,7 +349,7 @@ esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass, return ESP_ERR_NO_MEM; } - /* Initialise app data */ + /* Initialize app data */ g_prov->pop = pop; g_prov->security = security; @@ -398,6 +366,18 @@ esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass, return err; } + err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register WiFi event handler"); + return err; + } + + err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register IP event handler"); + return err; + } + /* Start WiFi softAP with specified ssid and password */ err = start_wifi_ap(ssid, pass); if (err != ESP_OK) { diff --git a/examples/provisioning/custom_config/main/app_prov.h b/examples/provisioning/legacy/custom_config/main/app_prov.h similarity index 85% rename from examples/provisioning/custom_config/main/app_prov.h rename to examples/provisioning/legacy/custom_config/main/app_prov.h index a36c3aff..be46b294 100644 --- a/examples/provisioning/custom_config/main/app_prov.h +++ b/examples/provisioning/legacy/custom_config/main/app_prov.h @@ -9,7 +9,7 @@ #pragma once -#include +#include #include #include @@ -43,21 +43,6 @@ esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state); */ esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason); -/** - * @brief Event handler for provisioning app - * - * This is called from the main event handler and controls the - * provisioning application, depeding on WiFi events - * - * @param[in] ctx Event context data - * @param[in] event Event info - * - * @return - * - ESP_OK : Event handled successfully - * - ESP_FAIL : Failed to start server on event AP start - */ -esp_err_t app_prov_event_handler(void *ctx, system_event_t *event); - /** * @brief Checks if device is provisioned * * diff --git a/examples/provisioning/custom_config/main/app_prov_handlers.c b/examples/provisioning/legacy/custom_config/main/app_prov_handlers.c similarity index 81% rename from examples/provisioning/custom_config/main/app_prov_handlers.c rename to examples/provisioning/legacy/custom_config/main/app_prov_handlers.c index c67eeebc..490be081 100644 --- a/examples/provisioning/custom_config/main/app_prov_handlers.c +++ b/examples/provisioning/legacy/custom_config/main/app_prov_handlers.c @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -72,10 +72,9 @@ static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data, wifi ESP_LOGI(TAG, "Connected state"); /* IP Addr assigned to STA */ - tcpip_adapter_ip_info_t ip_info; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); - char *ip_addr = ip4addr_ntoa(&ip_info.ip); - strcpy(resp_data->conn_info.ip_addr, ip_addr); + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info); + esp_ip4addr_ntoa(&ip_info.ip, resp_data->conn_info.ip_addr, sizeof(resp_data->conn_info.ip_addr)); /* AP information to which STA is connected */ wifi_ap_record_t ap_info; @@ -110,10 +109,14 @@ static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data, ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s", req_data->ssid, req_data->password); - memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid, - strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid))); - memcpy((char *) wifi_cfg->sta.password, req_data->password, - strnlen(req_data->password, sizeof(wifi_cfg->sta.password))); + + /* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard). + * But this doesn't guarantee that the saved SSID will be null terminated, because + * wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character). + * Although, this is not a matter for concern because esp_wifi library reads the SSID + * upto 32 bytes in absence of null termination */ + strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid)); + strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password)); return ESP_OK; } diff --git a/examples/provisioning/legacy/custom_config/main/component.mk b/examples/provisioning/legacy/custom_config/main/component.mk new file mode 100644 index 00000000..61f8990c --- /dev/null +++ b/examples/provisioning/legacy/custom_config/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/provisioning/softap_prov/CMakeLists.txt b/examples/provisioning/legacy/softap_prov/CMakeLists.txt similarity index 51% rename from examples/provisioning/softap_prov/CMakeLists.txt rename to examples/provisioning/legacy/softap_prov/CMakeLists.txt index 95d0b77b..fa6ac143 100644 --- a/examples/provisioning/softap_prov/CMakeLists.txt +++ b/examples/provisioning/legacy/softap_prov/CMakeLists.txt @@ -2,9 +2,5 @@ # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) -# (Not part of the boilerplate) -# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. -set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) - include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(softap_prov) diff --git a/examples/provisioning/softap_prov/Makefile b/examples/provisioning/legacy/softap_prov/Makefile similarity index 67% rename from examples/provisioning/softap_prov/Makefile rename to examples/provisioning/legacy/softap_prov/Makefile index cb6d6fb2..ab167ef2 100644 --- a/examples/provisioning/softap_prov/Makefile +++ b/examples/provisioning/legacy/softap_prov/Makefile @@ -5,7 +5,5 @@ PROJECT_NAME := softap_prov -EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common - include $(IDF_PATH)/make/project.mk diff --git a/examples/provisioning/softap_prov/README.md b/examples/provisioning/legacy/softap_prov/README.md similarity index 91% rename from examples/provisioning/softap_prov/README.md rename to examples/provisioning/legacy/softap_prov/README.md index e5445b80..c59bc0bd 100644 --- a/examples/provisioning/softap_prov/README.md +++ b/examples/provisioning/legacy/softap_prov/README.md @@ -1,4 +1,6 @@ -# SoftAP + HTTPD based Provisioning Example +# SoftAP + HTTPD based Provisioning Example (Legacy) + +> Note: It is recommended to use the new `wifi_prov_mgr` example which is based on the simpler `wifi_provisioning` APIs. Check this example only if you wish to use lower level provisioning and protocomm APIs and want more control over the handlers. (See the README.md file in the upper level 'examples' directory for more information about examples.) @@ -42,11 +44,9 @@ To provision the device running this example, the `esp_prov.py` script needs to ### Configure the project ``` -make menuconfig +idf.py menuconfig ``` -* Set serial port under Serial Flasher Options. - * Under Example Configuration set the following : * SoftAP SSID (Defaults to PROV_) * SoftAP Password (Defaults to PROV_PASS) @@ -58,7 +58,7 @@ make menuconfig Build the project and flash it to the board, then run monitor tool to view serial output: ``` -make -j4 flash monitor +idf.py -p PORT flash monitor ``` (To exit the serial monitor, type ``Ctrl-]``.) @@ -81,7 +81,7 @@ I (519482) tcpip_adapter: softAP assign IP to station,IP is: 192.168.4.2 In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). The SoftAP endpoint corresponds to the IP and port of the device on the SoftAP network, but this is usually same as the default value and may be left out. Assuming default example configuration, the script should be run as follows : ``` -python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport softap --softap_endpoint 192.168.4.1:80 +python esp_prov.py --transport softap --service_name "192.168.4.1:80" --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword ``` Above command will perform the provisioning steps, and the monitor log should display something like this : @@ -145,7 +145,7 @@ It means the Wi-Fi credentials were already set by some other application flashe ``` make erase_flash -make -j4 flash monitor +idf.py -p PORT flash monitor ``` Or, enable `Reset Provisioning` option under `Example Configuration` under menuconfig. But this will erase the saved Wi-Fi credentials every time the device boots, so this is not the preferred solution. diff --git a/examples/provisioning/legacy/softap_prov/main/CMakeLists.txt b/examples/provisioning/legacy/softap_prov/main/CMakeLists.txt new file mode 100644 index 00000000..a483b387 --- /dev/null +++ b/examples/provisioning/legacy/softap_prov/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "app_main.c" + "app_prov.c" + "app_prov_handlers.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/examples/provisioning/legacy/softap_prov/main/Kconfig.projbuild b/examples/provisioning/legacy/softap_prov/main/Kconfig.projbuild new file mode 100644 index 00000000..d0fa3096 --- /dev/null +++ b/examples/provisioning/legacy/softap_prov/main/Kconfig.projbuild @@ -0,0 +1,59 @@ +menu "Example Configuration" + + config EXAMPLE_SSID_SET_MAC + bool "Use MAC as SSID" + default y + help + Set SoftAP SSID as PROV_. + + config EXAMPLE_SSID + string "Wi-Fi SSID" + default "PROV_SSID" + depends on !EXAMPLE_SSID_SET_MAC + help + SSID (network name) for the example to connect to. + + config EXAMPLE_PASS + string "Wi-Fi Password" + default "PROV_PASS" + help + Wi-Fi password (WPA or WPA2) for the example to use. + + config EXAMPLE_USE_SEC_1 + bool + default y + prompt "Use Security Version 1" + help + Security version 1 used Curve25519 key exchange for establishing + secure session between device and client during provisioning + + config EXAMPLE_USE_POP + bool + depends on EXAMPLE_USE_SEC_1 + default y + prompt "Use proof-of-possession" + help + Proof-of-possession can be optionally used to prove that the device is indeed + in possession of the user who is provisioning the device. This proof-of-possession + is internally used to generate the shared secret through key exchange. + + config EXAMPLE_POP + string "Proof-of-possession" + default "abcd1234" + depends on EXAMPLE_USE_POP + + config EXAMPLE_RESET_PROVISIONED + bool + default n + prompt "Reset provisioned status of the device" + help + This erases the NVS to reset provisioned status of the device on every reboot. + Provisioned status is determined by the Wi-Fi STA configuration, saved on the NVS. + + config EXAMPLE_AP_RECONN_ATTEMPTS + int "Maximum AP connection attempts" + default 5 + help + Set the maximum connection attempts to perform when connecting to a Wi-Fi AP. + +endmenu diff --git a/examples/provisioning/softap_prov/main/app_main.c b/examples/provisioning/legacy/softap_prov/main/app_main.c similarity index 73% rename from examples/provisioning/softap_prov/main/app_main.c rename to examples/provisioning/legacy/softap_prov/main/app_main.c index c2eec9bd..6f673008 100644 --- a/examples/provisioning/softap_prov/main/app_main.c +++ b/examples/provisioning/legacy/softap_prov/main/app_main.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include @@ -21,10 +21,10 @@ #include "app_prov.h" -static const char *TAG = "app"; - #define EXAMPLE_AP_RECONN_ATTEMPTS CONFIG_EXAMPLE_AP_RECONN_ATTEMPTS +static const char *TAG = "app"; + static void event_handler(void* arg, esp_event_base_t event_base, int event_id, void* event_data) { @@ -41,66 +41,46 @@ static void event_handler(void* arg, esp_event_base_t event_base, ESP_LOGI(TAG,"connect to the AP fail"); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; - ESP_LOGI(TAG, "got ip:%s", - ip4addr_ntoa(&event->ip_info.ip)); + ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); s_retry_num = 0; } } -static void wifi_init_sta() +static void wifi_init_sta(void) { + /* Set our event handling */ ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handler, NULL)); - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); - ESP_ERROR_CHECK(esp_wifi_start() ); + /* Start Wi-Fi in station mode with credentials set during provisioning */ + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); } -void app_main() +static void start_softap_provisioning(void) { /* Security version */ int security = 0; /* Proof of possession */ const protocomm_security_pop_t *pop = NULL; -#ifdef CONFIG_USE_SEC_1 +#ifdef CONFIG_EXAMPLE_USE_SEC_1 security = 1; #endif /* Having proof of possession is optional */ -#ifdef CONFIG_USE_POP +#ifdef CONFIG_EXAMPLE_USE_POP const static protocomm_security_pop_t app_pop = { - .data = (uint8_t *) CONFIG_POP, - .len = (sizeof(CONFIG_POP)-1) + .data = (uint8_t *) CONFIG_EXAMPLE_POP, + .len = (sizeof(CONFIG_EXAMPLE_POP)-1) }; pop = &app_pop; #endif - /* Initialize networking stack */ - tcpip_adapter_init(); - - /* Create default event loop needed by the - * main app and the provisioning service */ - ESP_ERROR_CHECK(esp_event_loop_create_default()); - - /* Initialize NVS needed by Wi-Fi */ - ESP_ERROR_CHECK(nvs_flash_init()); - - /* Check if device is provisioned */ - bool provisioned; - if (app_prov_is_provisioned(&provisioned) != ESP_OK) { - ESP_LOGE(TAG, "Error getting device provisioning state"); - return; - } - - if (provisioned == false) { - /* If not provisioned, start provisioning via soft AP */ - ESP_LOGI(TAG, "Starting WiFi SoftAP provisioning"); - const char *ssid = NULL; -#ifdef CONFIG_SOFTAP_SSID - ssid = CONFIG_SOFTAP_SSID; +#ifdef CONFIG_EXAMPLE_SSID + ssid = CONFIG_EXAMPLE_SSID; #else uint8_t eth_mac[6]; esp_wifi_get_mac(WIFI_IF_STA, eth_mac); @@ -112,11 +92,42 @@ void app_main() ssid = ssid_with_mac; #endif - app_prov_start_softap_provisioning(ssid, CONFIG_SOFTAP_PASS, - security, pop); + ESP_ERROR_CHECK(app_prov_start_softap_provisioning( + ssid, CONFIG_EXAMPLE_PASS, security, pop)); +} + +void app_main(void) +{ + /* Initialize networking stack */ + ESP_ERROR_CHECK(esp_netif_init()); + + /* Create default event loop needed by the + * main app and the provisioning service */ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* Initialize NVS needed by Wi-Fi */ + ESP_ERROR_CHECK(nvs_flash_init()); + + /* Initialize Wi-Fi including netif with default config */ + esp_netif_create_default_wifi_sta(); + esp_netif_create_default_wifi_ap(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + /* Check if device is provisioned */ + bool provisioned; + if (app_prov_is_provisioned(&provisioned) != ESP_OK) { + ESP_LOGE(TAG, "Error getting device provisioning state"); + return; + } + + if (provisioned == false) { + /* If not provisioned, start provisioning via soft AP */ + ESP_LOGI(TAG, "Starting WiFi SoftAP provisioning"); + start_softap_provisioning(); } else { /* Start WiFi station with credentials set during provisioning */ ESP_LOGI(TAG, "Starting WiFi station"); - wifi_init_sta(NULL); + wifi_init_sta(); } } diff --git a/examples/provisioning/softap_prov/main/app_prov.c b/examples/provisioning/legacy/softap_prov/main/app_prov.c similarity index 84% rename from examples/provisioning/softap_prov/main/app_prov.c rename to examples/provisioning/legacy/softap_prov/main/app_prov.c index 495c0899..73c8bdfc 100644 --- a/examples/provisioning/softap_prov/main/app_prov.c +++ b/examples/provisioning/legacy/softap_prov/main/app_prov.c @@ -13,10 +13,8 @@ #include #include #include +#include -#include -#include -#include #include #include #include @@ -27,6 +25,9 @@ static const char *TAG = "app_prov"; +/* Handler for catching WiFi events */ +static void app_prov_event_handler(void* handler_arg, esp_event_base_t base, int id, void* data); + /* Handlers for wifi_config provisioning endpoint */ extern wifi_prov_config_handlers_t wifi_prov_handlers; @@ -103,6 +104,10 @@ static void app_prov_stop_service(void) protocomm_httpd_stop(g_prov->pc); /* Delete protocomm instance */ protocomm_delete(g_prov->pc); + + /* Remove event handler */ + esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler); } /* Task spawned by timer callback */ @@ -131,33 +136,24 @@ static void _stop_prov_cb(void * arg) xTaskCreate(&stop_prov_task, "stop_prov", 2048, NULL, tskIDLE_PRIORITY, NULL); } -/* Event handler for starting/stopping provisioning. - * To be called from within the context of the main - * event handler. - */ -esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) +/* Event handler for starting/stopping provisioning */ +static void app_prov_event_handler(void* handler_arg, esp_event_base_t event_base, + int event_id, void* event_data) { - /* For accessing reason codes in case of disconnection */ - system_event_info_t *info = &event->event_info; - /* If pointer to provisioning application data is NULL - * then provisioning is not running, therefore return without - * error */ + * then provisioning is not running */ if (!g_prov) { - return ESP_OK; + return; } - switch(event->event_id) { - case SYSTEM_EVENT_STA_START: + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { ESP_LOGI(TAG, "STA Start"); - /* Once configuration is received by protocomm server, - * device is restarted as both AP and Station. - * Once station starts, wait for connection to - * establish with configured host SSID and password */ + /* 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 */ g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; - break; - - case SYSTEM_EVENT_STA_GOT_IP: + } 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. * Schedule timer to stop provisioning app after 30 seconds. */ @@ -174,16 +170,16 @@ esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) * signaling a failure in provisioning. */ esp_timer_start_once(g_prov->timer, 30000*1000U); } - break; - - case SYSTEM_EVENT_STA_DISCONNECTED: + } 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 */ g_prov->wifi_state = WIFI_PROV_STA_DISCONNECTED; - ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason); + + 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 (info->disconnected.reason) { + switch (disconnected->reason) { case WIFI_REASON_AUTH_EXPIRE: case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: case WIFI_REASON_BEACON_TIMEOUT: @@ -198,21 +194,12 @@ esp_err_t app_prov_event_handler(void *ctx, system_event_t *event) g_prov->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND; break; default: - if (info->disconnected.reason == WIFI_REASON_BASIC_RATE_NOT_SUPPORT) { - /*Switch to 802.11 bgn mode */ - esp_wifi_set_protocol(ESP_IF_WIFI_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N); - } /* If none of the expected reasons, * retry connecting to host SSID */ g_prov->wifi_state = WIFI_PROV_STA_CONNECTING; esp_wifi_connect(); } - break; - - default: - break; } - return ESP_OK; } esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state) @@ -243,21 +230,10 @@ esp_err_t app_prov_is_provisioned(bool *provisioned) { *provisioned = false; -#ifdef CONFIG_RESET_PROVISIONED +#ifdef CONFIG_EXAMPLE_RESET_PROVISIONED nvs_flash_erase(); #endif - if (nvs_flash_init() != ESP_OK) { - ESP_LOGE(TAG, "Failed to init NVS"); - return ESP_FAIL; - } - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - if (esp_wifi_init(&cfg) != ESP_OK) { - ESP_LOGE(TAG, "Failed to init wifi"); - return ESP_FAIL; - } - /* Get WiFi Station configuration */ wifi_config_t wifi_cfg; if (esp_wifi_get_config(ESP_IF_WIFI_STA, &wifi_cfg) != ESP_OK) { @@ -305,14 +281,6 @@ esp_err_t app_prov_configure_sta(wifi_config_t *wifi_cfg) static esp_err_t start_wifi_ap(const char *ssid, const char *pass) { - /* Initialize WiFi with default configuration */ - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - esp_err_t err = esp_wifi_init(&cfg); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to init WiFi : %d", err); - return err; - } - /* Build WiFi configuration for AP mode */ wifi_config_t wifi_config = { .ap = { @@ -320,19 +288,18 @@ static esp_err_t start_wifi_ap(const char *ssid, const char *pass) }, }; - strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); - wifi_config.ap.ssid_len = strlen(ssid); + strlcpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); if (strlen(pass) == 0) { memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password)); wifi_config.ap.authmode = WIFI_AUTH_OPEN; } else { - strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + strlcpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; } /* Start WiFi in AP mode with configuration built above */ - err = esp_wifi_set_mode(WIFI_MODE_AP); + esp_err_t err = esp_wifi_set_mode(WIFI_MODE_AP); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set WiFi mode : %d", err); return err; @@ -385,6 +352,18 @@ esp_err_t app_prov_start_softap_provisioning(const char *ssid, const char *pass, return err; } + err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, app_prov_event_handler, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register WiFi event handler"); + return err; + } + + err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, app_prov_event_handler, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register IP event handler"); + return err; + } + /* Start WiFi softAP with specified ssid and password */ err = start_wifi_ap(ssid, pass); if (err != ESP_OK) { diff --git a/examples/provisioning/softap_prov/main/app_prov.h b/examples/provisioning/legacy/softap_prov/main/app_prov.h similarity index 85% rename from examples/provisioning/softap_prov/main/app_prov.h rename to examples/provisioning/legacy/softap_prov/main/app_prov.h index ec196585..c87ff4f0 100644 --- a/examples/provisioning/softap_prov/main/app_prov.h +++ b/examples/provisioning/legacy/softap_prov/main/app_prov.h @@ -9,7 +9,7 @@ #pragma once -#include +#include #include #include @@ -42,21 +42,6 @@ esp_err_t app_prov_get_wifi_state(wifi_prov_sta_state_t* state); */ esp_err_t app_prov_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t* reason); -/** - * @brief Event handler for provisioning app - * - * This is called from the main event handler and controls the - * provisioning application, depeding on WiFi events - * - * @param[in] ctx Event context data - * @param[in] event Event info - * - * @return - * - ESP_OK : Event handled successfully - * - ESP_FAIL : Failed to start server on event AP start - */ -esp_err_t app_prov_event_handler(void *ctx, system_event_t *event); - /** * @brief Checks if device is provisioned * * diff --git a/examples/provisioning/legacy/softap_prov/main/app_prov_handlers.c b/examples/provisioning/legacy/softap_prov/main/app_prov_handlers.c new file mode 100644 index 00000000..87cc64f0 --- /dev/null +++ b/examples/provisioning/legacy/softap_prov/main/app_prov_handlers.c @@ -0,0 +1,131 @@ +/* SoftAP based Provisioning Example + + 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. +*/ + +/* This file is mostly a boiler-plate code that applications can use without much change */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "app_prov.h" + +static const char* TAG = "app_prov_handler"; + +/* Provide definition of wifi_prov_ctx_t */ +struct wifi_prov_ctx { + wifi_config_t wifi_cfg; +}; + +static wifi_config_t *get_config(wifi_prov_ctx_t **ctx) +{ + return (*ctx ? &(*ctx)->wifi_cfg : NULL); +} + +static wifi_config_t *new_config(wifi_prov_ctx_t **ctx) +{ + free(*ctx); + (*ctx) = (wifi_prov_ctx_t *) calloc(1, sizeof(wifi_prov_ctx_t)); + return get_config(ctx); +} + +static void free_config(wifi_prov_ctx_t **ctx) +{ + free(*ctx); + *ctx = NULL; +} + +static esp_err_t get_status_handler(wifi_prov_config_get_data_t *resp_data, wifi_prov_ctx_t **ctx) +{ + /* Initialize to zero */ + memset(resp_data, 0, sizeof(wifi_prov_config_get_data_t)); + + if (app_prov_get_wifi_state(&resp_data->wifi_state) != ESP_OK) { + ESP_LOGW(TAG, "Prov app not running"); + return ESP_FAIL; + } + + if (resp_data->wifi_state == WIFI_PROV_STA_CONNECTED) { + ESP_LOGI(TAG, "Connected state"); + + /* IP Addr assigned to STA */ + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info); + esp_ip4addr_ntoa(&ip_info.ip, resp_data->conn_info.ip_addr, sizeof(resp_data->conn_info.ip_addr)); + + /* AP information to which STA is connected */ + wifi_ap_record_t ap_info; + esp_wifi_sta_get_ap_info(&ap_info); + memcpy(resp_data->conn_info.bssid, (char *)ap_info.bssid, sizeof(ap_info.bssid)); + memcpy(resp_data->conn_info.ssid, (char *)ap_info.ssid, sizeof(ap_info.ssid)); + resp_data->conn_info.channel = ap_info.primary; + resp_data->conn_info.auth_mode = ap_info.authmode; + } else if (resp_data->wifi_state == WIFI_PROV_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Disconnected state"); + + /* If disconnected, convey reason */ + app_prov_get_wifi_disconnect_reason(&resp_data->fail_reason); + } else { + ESP_LOGI(TAG, "Connecting state"); + } + return ESP_OK; +} + +static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data, wifi_prov_ctx_t **ctx) +{ + wifi_config_t *wifi_cfg = get_config(ctx); + if (wifi_cfg) { + free_config(ctx); + } + + wifi_cfg = new_config(ctx); + if (!wifi_cfg) { + ESP_LOGE(TAG, "Unable to alloc wifi config"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s", + req_data->ssid, req_data->password); + + /* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard). + * But this doesn't guarantee that the saved SSID will be null terminated, because + * wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character). + * Although, this is not a matter for concern because esp_wifi library reads the SSID + * upto 32 bytes in absence of null termination */ + strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid)); + strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password)); + return ESP_OK; +} + +static esp_err_t apply_config_handler(wifi_prov_ctx_t **ctx) +{ + wifi_config_t *wifi_cfg = get_config(ctx); + if (!wifi_cfg) { + ESP_LOGE(TAG, "WiFi config not set"); + return ESP_FAIL; + } + + app_prov_configure_sta(wifi_cfg); + ESP_LOGI(TAG, "WiFi Credentials Applied"); + + free_config(ctx); + return ESP_OK; +} + +wifi_prov_config_handlers_t wifi_prov_handlers = { + .get_status_handler = get_status_handler, + .set_config_handler = set_config_handler, + .apply_config_handler = apply_config_handler, + .ctx = NULL +}; diff --git a/examples/provisioning/legacy/softap_prov/main/component.mk b/examples/provisioning/legacy/softap_prov/main/component.mk new file mode 100644 index 00000000..61f8990c --- /dev/null +++ b/examples/provisioning/legacy/softap_prov/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/provisioning/legacy/softap_prov/softap_prov_test.py b/examples/provisioning/legacy/softap_prov/softap_prov_test.py new file mode 100644 index 00000000..238234c1 --- /dev/null +++ b/examples/provisioning/legacy/softap_prov/softap_prov_test.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# +# Copyright 2018 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. + +from __future__ import print_function +import re +import os +import time + +import ttfw_idf +import esp_prov +import wifi_tools + +# Have esp_prov throw exception +esp_prov.config_throw_except = True + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI_BT") +def test_examples_provisioning_softap(env, extra_data): + # Acquire DUT + dut1 = env.get_dut("softap_prov", "examples/provisioning/legacy/softap_prov", dut_class=ttfw_idf.ESP32DUT) + + # Get binary file + binary_file = os.path.join(dut1.app.binary_path, "softap_prov.bin") + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("softap_prov_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("softap_prov_bin_size", bin_size // 1024, dut1.TARGET) + + # Upload binary and start testing + dut1.start_app() + + # Parse IP address of STA + dut1.expect("Starting WiFi SoftAP provisioning", timeout=60) + [ssid, password] = dut1.expect(re.compile(r"SoftAP Provisioning started with SSID '(\S+)', Password '(\S+)'"), timeout=30) + + iface = wifi_tools.get_wiface_name() + if iface is None: + raise RuntimeError("Failed to get Wi-Fi interface on host") + print("Interface name : " + iface) + print("SoftAP SSID : " + ssid) + print("SoftAP Password : " + password) + + ctrl = wifi_tools.wpa_cli(iface, reset_on_exit=True) + print("Connecting to DUT SoftAP...") + ip = ctrl.connect(ssid, password) + got_ip = dut1.expect(re.compile(r"DHCP server assigned IP to a station, IP is: (\d+.\d+.\d+.\d+)"), timeout=30)[0] + if ip != got_ip: + raise RuntimeError("SoftAP connected to another host! " + ip + "!=" + got_ip) + print("Connected to DUT SoftAP") + + print("Starting Provisioning") + verbose = False + protover = "V0.1" + secver = 1 + pop = "abcd1234" + provmode = "softap" + ap_ssid = "myssid" + ap_password = "mypassword" + softap_endpoint = ip.split('.')[0] + "." + ip.split('.')[1] + "." + ip.split('.')[2] + ".1:80" + + print("Getting security") + security = esp_prov.get_security(secver, pop, verbose) + if security is None: + raise RuntimeError("Failed to get security") + + print("Getting transport") + transport = esp_prov.get_transport(provmode, softap_endpoint) + if transport is None: + raise RuntimeError("Failed to get transport") + + print("Verifying protocol version") + if not esp_prov.version_match(transport, protover): + raise RuntimeError("Mismatch in protocol version") + + print("Starting Session") + if not esp_prov.establish_session(transport, security): + raise RuntimeError("Failed to start session") + + print("Sending Wifi credential to DUT") + if not esp_prov.send_wifi_config(transport, security, ap_ssid, ap_password): + raise RuntimeError("Failed to send Wi-Fi config") + + print("Applying config") + if not esp_prov.apply_wifi_config(transport, security): + raise RuntimeError("Failed to send apply config") + + success = False + while True: + time.sleep(5) + print("Wi-Fi connection state") + ret = esp_prov.get_wifi_config(transport, security) + if (ret == 1): + continue + elif (ret == 0): + print("Provisioning was successful") + success = True + break + + if not success: + raise RuntimeError("Provisioning failed") + + +if __name__ == '__main__': + test_examples_provisioning_softap() diff --git a/examples/provisioning/softap_prov/main/CMakeLists.txt b/examples/provisioning/softap_prov/main/CMakeLists.txt deleted file mode 100644 index 3c44a1a3..00000000 --- a/examples/provisioning/softap_prov/main/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -set(COMPONENT_SRCS "app_main.c" - "app_prov.c" - "app_prov_handlers.c") -set(COMPONENT_ADD_INCLUDEDIRS ".") - -register_component() diff --git a/examples/provisioning/softap_prov/main/Kconfig.projbuild b/examples/provisioning/softap_prov/main/Kconfig.projbuild deleted file mode 100644 index 4ec635a9..00000000 --- a/examples/provisioning/softap_prov/main/Kconfig.projbuild +++ /dev/null @@ -1,59 +0,0 @@ -menu "Example Configuration" - -config SOFTAP_SSID_SET_MAC - bool "Use MAC as SSID" - default y - help - Set SoftAP SSID as PROV_. - -config SOFTAP_SSID - string "WiFi SSID" - default "PROV_SSID" - depends on !SOFTAP_SSID_SET_MAC - help - SSID (network name) for the example to connect to. - -config SOFTAP_PASS - string "WiFi Password" - default "PROV_PASS" - help - WiFi password (WPA or WPA2) for the example to use. - -config USE_SEC_1 - bool - default y - prompt "Use Security Version 1" - help - Security version 1 used Curve25519 key exchange for establishing - secure session between device and client during provisioning - -config USE_POP - bool - depends on USE_SEC_1 - default y - prompt "Use proof-of-possession" - help - Proof-of-possession can be optionally used to prove that the device is indeed - in possession of the user who is provisioning the device. This proof-of-possession - is internally used to generate the shared secret through key exchange. - -config POP - string "Proof-of-possession" - default "abcd1234" - depends on USE_POP - -config RESET_PROVISIONED - bool - default n - prompt "Reset provisioned status of the device" - help - This erases the NVS to reset provisioned status of the device on every reboot. - Provisioned status is determined by the WiFi STA configuration, saved on the NVS. - -config EXAMPLE_AP_RECONN_ATTEMPTS - int "Maximum AP connection attempts" - default 5 - help - Set the maximum connection attempts to perform when connecting to a Wi-Fi AP. - -endmenu diff --git a/examples/provisioning/softap_prov/sdkconfig.defaults b/examples/provisioning/softap_prov/sdkconfig.defaults deleted file mode 100644 index 992010c3..00000000 --- a/examples/provisioning/softap_prov/sdkconfig.defaults +++ /dev/null @@ -1,3 +0,0 @@ -CONFIG_ENABLE_UNIFIED_PROVISIONING=y -CONFIG_LWIP_NETIF_LOOPBACK=y -CONFIG_LWIP_LOOPBACK_MAX_PBUFS=1 diff --git a/examples/provisioning/softap_prov/utils/wifi_tools.py b/examples/provisioning/softap_prov/utils/wifi_tools.py deleted file mode 100644 index 1d40111d..00000000 --- a/examples/provisioning/softap_prov/utils/wifi_tools.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2018 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. -# - -import dbus -import dbus.mainloop.glib -import netifaces -import time - -def get_wiface_name(): - for iface in netifaces.interfaces(): - if iface.startswith('w'): - return iface - return None - -def get_wiface_IPv4(iface): - try: - [info] = netifaces.ifaddresses(iface)[netifaces.AF_INET] - return info['addr'] - except KeyError: - return None - -class wpa_cli: - def __init__(self, iface, reset_on_exit = False): - self.iface_name = iface - self.iface_obj = None - self.iface_ifc = None - self.old_network = None - self.new_network = None - self.connected = False - self.reset_on_exit = reset_on_exit - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - bus = dbus.SystemBus() - - service = dbus.Interface(bus.get_object("fi.w1.wpa_supplicant1", "/fi/w1/wpa_supplicant1"), "fi.w1.wpa_supplicant1") - paths = service.Get("fi.w1.wpa_supplicant1", "Interfaces", dbus_interface='org.freedesktop.DBus.Properties') - iface_path = service.GetInterface(self.iface_name) - self.iface_obj = bus.get_object("fi.w1.wpa_supplicant1", iface_path) - self.iface_ifc = dbus.Interface(self.iface_obj, "fi.w1.wpa_supplicant1.Interface") - if self.iface_ifc == None: - raise RuntimeError('supplicant : Failed to fetch interface') - - self.old_network = self.iface_obj.Get("fi.w1.wpa_supplicant1.Interface", "CurrentNetwork", dbus_interface='org.freedesktop.DBus.Properties') - if self.old_network == '/': - self.old_network = None - else: - self.connected = True - - def connect(self, ssid, password): - if self.connected == True: - self.iface_ifc.Disconnect() - self.connected = False - - if self.new_network != None: - self.iface_ifc.RemoveNetwork(self.new_network) - - self.new_network = self.iface_ifc.AddNetwork({"ssid": ssid, "psk": password}) - self.iface_ifc.SelectNetwork(self.new_network) - - ip = None - retry = 10 - while retry > 0: - time.sleep(5) - ip = get_wiface_IPv4(self.iface_name) - if ip != None: - self.connected = True - return ip - retry -= 1 - - self.reset() - raise RuntimeError('wpa_cli : Connection failed') - - def reset(self): - if self.iface_ifc != None: - if self.connected == True: - self.iface_ifc.Disconnect() - self.connected = False - if self.new_network != None: - self.iface_ifc.RemoveNetwork(self.new_network) - self.new_network = None - if self.old_network != None: - self.iface_ifc.SelectNetwork(self.old_network) - self.old_network = None - - def __del__(self): - if self.reset_on_exit == True: - self.reset() diff --git a/examples/provisioning/wifi_prov_mgr/CMakeLists.txt b/examples/provisioning/wifi_prov_mgr/CMakeLists.txt new file mode 100644 index 00000000..b036d9ce --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(wifi_prov_mgr) diff --git a/examples/provisioning/wifi_prov_mgr/Makefile b/examples/provisioning/wifi_prov_mgr/Makefile new file mode 100644 index 00000000..151c25b7 --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := wifi_prov_mgr + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/provisioning/wifi_prov_mgr/README.md b/examples/provisioning/wifi_prov_mgr/README.md new file mode 100644 index 00000000..2233ce23 --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/README.md @@ -0,0 +1,286 @@ +# Wi-Fi Provisioning Manager Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +`wifi_prov_mgr` example demonstrates the usage of `wifi_provisioning` manager component for building a provisioning application. + +For this example, BLE is chosen as the default mode of transport, over which the provisioning related communication is to take place. NimBLE has been configured as the default host, but you can also switch to Bluedroid using menuconfig -> Components -> Bluetooth -> Bluetooth Host. + +> Note: Since ESP32-S2 does not support BLE, the SoftAP will be the default mode of transport in that case. Even for ESP32, you can change to SoftAP transport from menuconfig. + +In the provisioning process the device is configured as a Wi-Fi station with specified credentials. Once configured, the device will retain the Wi-Fi configuration, until a flash erase is performed. + +Right after provisioning is complete, BLE is turned off and disabled to free the memory used by the BLE stack. Though, that is specific to this example, and the user can choose to keep BLE stack intact in their own application. + +`wifi_prov_mgr` uses the following components : +* `wifi_provisioning` : provides manager, data structures and protocomm endpoint handlers for Wi-Fi configuration +* `protocomm` : for protocol based communication and secure session establishment +* `protobuf` : Google's protocol buffer library for serialization of protocomm data structures +* `bt` : ESP32 BLE stack for transport of protobuf packets + +This example can be used, as it is, for adding a provisioning service to any application intended for IoT. + +## How to use example + +### Hardware Required + +Example should be able to run on any commonly available ESP32/ESP32-S2 development board. + +### Application Required + +Provisioning applications are available for various platforms. See below + +#### Platform : Android + +For Android, a provisioning application along with source code is available on GitHub : [esp-idf-provisioning-android](https://github.com/espressif/esp-idf-provisioning-android) + +#### Platform : iOS + +For iOS, a provisioning application along with source code is available on GitHub : [esp-idf-provisioning-ios](https://github.com/espressif/esp-idf-provisioning-ios) + +#### Platform : Linux / Windows / macOS + +To provision the device running this example, the `esp_prov.py` script needs to be run (found under `$IDF_PATH/tools/esp_prov`). Make sure to satisfy all the dependencies prior to running the script. + +Presently, `esp_prov` supports BLE transport only for Linux platform. For Windows/macOS it falls back to console mode and requires another application (for BLE) through which the communication can take place. + +There are various applications, specific to Windows and macOS platform which can be used. The `esp_prov` console will guide you through the provisioning process of locating the correct BLE GATT services and characteristics, the values to write, and input read values. + +### Configure the project + +``` +idf.py menuconfig +``` +* Set the BLE/Soft AP transport under "Example Configuration" options. ESP32-S2 will have only SoftAP option. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (445) app: Starting provisioning +I (1035) app: Provisioning started +I (1045) wifi_prov_mgr: Provisioning started with service name : PROV_261FCC +``` + +Make sure to note down the BLE device name (starting with `PROV_`) displayed in the serial monitor log (eg. PROV_261FCC). This will depend on the MAC ID and will be unique for every device. + +In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (make sure to replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration, which uses protocomm security scheme 1 and proof of possession PoP based authentication : + +``` +python esp_prov.py --transport ble --service_name PROV_261FCC --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword +``` + +Above command will perform the provisioning steps, and the monitor log should display something like this : + +``` +I (39725) app: Received Wi-Fi credentials + SSID : myssid + Password : mypassword +. +. +. +I (45335) tcpip_adapter: sta ip: 192.168.43.243, mask: 255.255.255.0, gw: 192.168.43.1 +I (45345) app: Provisioning successful +I (45345) app: Connected with IP Address:192.168.43.243 +I (46355) app: Hello World! +I (47355) app: Hello World! +I (48355) app: Hello World! +I (49355) app: Hello World! +. +. +. +I (52315) wifi_prov_mgr: Provisioning stopped +. +. +. +I (52355) app: Hello World! +I (53355) app: Hello World! +I (54355) app: Hello World! +I (55355) app: Hello World! +``` + +### Wi-Fi Scanning + +Provisioning manager also supports providing real-time Wi-Fi scan results (performed on the device) during provisioning. This allows the client side applications to choose the AP for which the device Wi-Fi station is to be configured. Various information about the visible APs is available, like signal strength (RSSI) and security type, etc. Also, the manager now provides capabilities information which can be used by client applications to determine the security type and availability of specific features (like `wifi_scan`). + +When using the scan based provisioning, we don't need to specify the `--ssid` and `--passphrase` fields explicitly: + +``` +python esp_prov.py --transport ble --service_name PROV_261FCC --pop abcd1234 +``` + +See below the sample output from `esp_prov` tool on running above command: + +``` +Connecting... +Connected +Getting Services... +Security scheme determined to be : 1 + +==== Starting Session ==== +==== Session Established ==== + +==== Scanning Wi-Fi APs ==== +++++ Scan process executed in 1.9967520237 sec +++++ Scan results : 5 + +++++ Scan finished in 2.7374596596 sec +==== Wi-Fi Scan results ==== +S.N. SSID BSSID CHN RSSI AUTH +[ 1] MyHomeWiFiAP 788a20841996 1 -45 WPA2_PSK +[ 2] MobileHotspot 7a8a20841996 11 -46 WPA2_PSK +[ 3] MyHomeWiFiAP 788a208daa26 11 -54 WPA2_PSK +[ 4] NeighborsWiFiAP 8a8a20841996 6 -61 WPA2_PSK +[ 5] InsecureWiFiAP dca4caf1227c 7 -74 Open + +Select AP by number (0 to rescan) : 1 +Enter passphrase for MyHomeWiFiAP : + +==== Sending Wi-Fi credential to esp32 ==== +==== Wi-Fi Credentials sent successfully ==== + +==== Applying config to esp32 ==== +==== Apply config sent successfully ==== + +==== Wi-Fi connection state ==== +++++ WiFi state: connected ++++ +==== Provisioning was successful ==== +``` + +### Sending Custom Data + +The provisioning manager allows applications to send some custom data during provisioning, which may be +required for some other operations like connecting to some cloud service. This is achieved by creating +and registering additional endpoints using the below APIs + +``` +wifi_prov_mgr_endpoint_create(); +wifi_prov_mgr_endpoint_register(); +``` + +In this particular example, we have added an endpoint named "custom-data" which can be tested +by passing the `--custom_data ` option to the esp\_prov tool. Following output is +expected on success: + +``` +==== Sending Custom data to esp32 ==== +CustomData response: SUCCESS +``` + +## Troubleshooting + +### Provisioning failed + +It is possible that the Wi-Fi credentials provided were incorrect, or the device was not able to establish connection to the network, in which the the `esp_prov` script will notify failure (with reason). Serial monitor log will display the failure along with disconnect reason : + +``` +E (367015) app: Provisioning failed! + Reason : Wi-Fi AP password incorrect + Please reset to factory and retry provisioning +``` + +Once credentials have been applied, even though wrong credentials were provided, the device will no longer go into provisioning mode on subsequent reboots until NVS is erased (see following section). + +### Provisioning does not start + +If the serial monitor log shows the following : + +``` +I (465) app: Already provisioned, starting Wi-Fi STA +``` + +it means either the device has been provisioned earlier with or without success (e.g. scenario covered in above section), or that the Wi-Fi credentials were already set by some other application flashed previously onto your device. On setting the log level to DEBUG this is clearly evident : + +``` +D (455) wifi_prov_mgr: Found Wi-Fi SSID : myssid +D (465) wifi_prov_mgr: Found Wi-Fi Password : m********d +I (465) app: Already provisioned, starting Wi-Fi STA +``` + +To fix this we simple need to erase the NVS partition from flash. First we need to find out its address and size. This can be seen from the monitor log on the top right after reboot. + +``` +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (58) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (73) boot: 2 factory factory app 00 00 00010000 00124f80 +I (80) boot: End of partition table +``` + +Now erase NVS partition by running the following commands : + +``` +$IDF_PATH/components/esptool_py/esptool/esptool.py erase_region 0x9000 0x6000 +``` + +### Unsupported platform + +If the platform requirement, for running `esp_prov` is not satisfied, then the script execution will fallback to console mode, in which case the full process (involving user inputs) will look like this : + +``` +==== Esp_Prov Version: v1.0 ==== +BLE client is running in console mode + This could be due to your platform not being supported or dependencies not being met + Please ensure all pre-requisites are met to run the full fledged client +BLECLI >> Please connect to BLE device `PROV_261FCC` manually using your tool of choice +BLECLI >> Was the device connected successfully? [y/n] y +BLECLI >> List available attributes of the connected device +BLECLI >> Is the service UUID '0000ffff-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff53-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff51-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y +BLECLI >> Is the characteristic UUID '0000ff52-0000-1000-8000-00805f9b34fb' listed among available attributes? [y/n] y + +==== Verifying protocol version ==== +BLECLI >> Write following data to characteristic with UUID '0000ff53-0000-1000-8000-00805f9b34fb' : + >> 56302e31 +BLECLI >> Enter data read from characteristic (in hex) : + << 53554343455353 +==== Verified protocol version successfully ==== + +==== Starting Session ==== +BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' : + >> 10015a25a201220a20ae6d9d5d1029f8c366892252d2d5a0ffa7ce1ee5829312545dd5f2aba057294d +BLECLI >> Enter data read from characteristic (in hex) : + << 10015a390801aa0134122048008bfc365fad4753dc75912e0c764d60749cb26dd609595b6fbc72e12614031a1089733af233c7448e7d7fb7963682c6d8 +BLECLI >> Write following data to characteristic with UUID '0000ff51-0000-1000-8000-00805f9b34fb' : + >> 10015a270802b2012212204051088dc294fe4621fac934a8ea22e948fcc3e8ac458aac088ce705c65dbfb9 +BLECLI >> Enter data read from characteristic (in hex) : + << 10015a270803ba01221a20c8d38059d5206a3d92642973ac6ba8ac2f6ecf2b7a3632964eb35a0f20133adb +==== Session Established ==== + +==== Sending Wifi credential to esp32 ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 98471ac4019a46765c28d87df8c8ae71c1ae6cfe0bc9c615bc6d2c +BLECLI >> Enter data read from characteristic (in hex) : + << 3271f39a +==== Wifi Credentials sent successfully ==== + +==== Applying config to esp32 ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 5355 +BLECLI >> Enter data read from characteristic (in hex) : + << 1664db24 +==== Apply config sent successfully ==== + +==== Wifi connection state ==== +BLECLI >> Write following data to characteristic with UUID '0000ff52-0000-1000-8000-00805f9b34fb' : + >> 290d +BLECLI >> Enter data read from characteristic (in hex) : + << 505f72a9f8521025c1964d7789c4d7edc56aedebd144e1b667bc7c0975757b80cc091aa9f3e95b06eaefbc30290fa1 +++++ WiFi state: connected ++++ +==== Provisioning was successful ==== +``` + +The write data is to be copied from the console output ```>>``` to the platform specific application and the data read from the application is to be pasted at the user input prompt ```<<``` of the console, in the format (hex) indicated in above sample log. diff --git a/examples/provisioning/wifi_prov_mgr/main/CMakeLists.txt b/examples/provisioning/wifi_prov_mgr/main/CMakeLists.txt new file mode 100644 index 00000000..61fac40e --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "app_main.c" + INCLUDE_DIRS ".") diff --git a/examples/provisioning/wifi_prov_mgr/main/Kconfig.projbuild b/examples/provisioning/wifi_prov_mgr/main/Kconfig.projbuild new file mode 100644 index 00000000..3cd03d66 --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/main/Kconfig.projbuild @@ -0,0 +1,22 @@ +menu "Example Configuration" + + choice EXAMPLE_PROV_TRANSPORT + bool "Provisioning Transport" + default EXAMPLE_PROV_TRANSPORT_BLE + help + Wi-Fi provisioning component offers both, SoftAP and BLE transports. Choose any one. + + config EXAMPLE_PROV_TRANSPORT_BLE + bool "BLE" + select BT_ENABLED + depends on IDF_TARGET_ESP32 + config EXAMPLE_PROV_TRANSPORT_SOFTAP + bool "Soft AP" + endchoice + + config EXAMPLE_PROV_TRANSPORT + int + default 1 if EXAMPLE_PROV_TRANSPORT_BLE + default 2 if EXAMPLE_PROV_TRANSPORT_SOFTAP + +endmenu diff --git a/examples/provisioning/wifi_prov_mgr/main/app_main.c b/examples/provisioning/wifi_prov_mgr/main/app_main.c new file mode 100644 index 00000000..e446a5d6 --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/main/app_main.c @@ -0,0 +1,282 @@ +/* Wi-Fi Provisioning Manager Example + + 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 + +#ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE +#include +#endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ + +#ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP +#include +#endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ + +static const char *TAG = "app"; + +/* Signal Wi-Fi events on this event-group */ +const int WIFI_CONNECTED_EVENT = BIT0; +static EventGroupHandle_t wifi_event_group; + +/* Event handler for catching system events */ +static void event_handler(void* arg, esp_event_base_t event_base, + int event_id, void* event_data) +{ + if (event_base == WIFI_PROV_EVENT) { + switch (event_id) { + case WIFI_PROV_START: + ESP_LOGI(TAG, "Provisioning started"); + break; + case WIFI_PROV_CRED_RECV: { + wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data; + ESP_LOGI(TAG, "Received Wi-Fi credentials" + "\n\tSSID : %s\n\tPassword : %s", + (const char *) wifi_sta_cfg->ssid, + (const char *) wifi_sta_cfg->password); + break; + } + case WIFI_PROV_CRED_FAIL: { + wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data; + ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s" + "\n\tPlease reset to factory and retry provisioning", + (*reason == WIFI_PROV_STA_AUTH_ERROR) ? + "Wi-Fi station authentication failed" : "Wi-Fi access-point not found"); + break; + } + case WIFI_PROV_CRED_SUCCESS: + ESP_LOGI(TAG, "Provisioning successful"); + break; + case WIFI_PROV_END: + /* De-initialize manager once provisioning is finished */ + wifi_prov_mgr_deinit(); + break; + default: + break; + } + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + esp_wifi_connect(); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; + ESP_LOGI(TAG, "Connected with IP Address:" IPSTR, IP2STR(&event->ip_info.ip)); + /* Signal main application to continue execution */ + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_EVENT); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Disconnected. Connecting to the AP again..."); + esp_wifi_connect(); + } +} + +static void wifi_init_sta(void) +{ + /* Start Wi-Fi in station mode */ + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); +} + +static void get_device_service_name(char *service_name, size_t max) +{ + uint8_t eth_mac[6]; + const char *ssid_prefix = "PROV_"; + esp_wifi_get_mac(WIFI_IF_STA, eth_mac); + snprintf(service_name, max, "%s%02X%02X%02X", + ssid_prefix, eth_mac[3], eth_mac[4], eth_mac[5]); +} + +/* Handler for the optional provisioning endpoint registered by the application. + * The data format can be chosen by applications. Here, we are using plain ascii text. + * Applications can choose to use other formats like protobuf, JSON, XML, etc. + */ +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", 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; /* +1 for NULL terminating byte */ + + return ESP_OK; +} + +void app_main(void) +{ + /* Initialize NVS partition */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + /* NVS partition was truncated + * and needs to be erased */ + ESP_ERROR_CHECK(nvs_flash_erase()); + + /* Retry nvs_flash_init */ + ESP_ERROR_CHECK(nvs_flash_init()); + } + + /* Initialize TCP/IP */ + ESP_ERROR_CHECK(esp_netif_init()); + + /* Initialize the event loop */ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + wifi_event_group = xEventGroupCreate(); + + /* Register our event handler for Wi-Fi, IP and Provisioning related events */ + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL)); + + /* Initialize Wi-Fi including netif with default config */ + esp_netif_create_default_wifi_sta(); +#ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP + esp_netif_create_default_wifi_ap(); +#endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + /* Configuration for the provisioning manager */ + wifi_prov_mgr_config_t config = { + /* What is the Provisioning Scheme that we want ? + * wifi_prov_scheme_softap or wifi_prov_scheme_ble */ +#ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE + .scheme = wifi_prov_scheme_ble, +#endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ +#ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP + .scheme = wifi_prov_scheme_softap, +#endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ + + /* Any default scheme specific event handler that you would + * like to choose. Since our example application requires + * neither BT nor BLE, we can choose to release the associated + * memory once provisioning is complete, or not needed + * (in case when device is already provisioned). Choosing + * appropriate scheme specific event handler allows the manager + * to take care of this automatically. This can be set to + * WIFI_PROV_EVENT_HANDLER_NONE when using wifi_prov_scheme_softap*/ +#ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE + .scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM +#endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ +#ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP + .scheme_event_handler = WIFI_PROV_EVENT_HANDLER_NONE +#endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_SOFTAP */ + }; + + /* Initialize provisioning manager with the + * configuration parameters set above */ + ESP_ERROR_CHECK(wifi_prov_mgr_init(config)); + + bool provisioned = false; + /* Let's find out if the device is provisioned */ + ESP_ERROR_CHECK(wifi_prov_mgr_is_provisioned(&provisioned)); + + /* If device is not yet provisioned start provisioning service */ + if (!provisioned) { + ESP_LOGI(TAG, "Starting provisioning"); + + /* What is the Device Service Name that we want + * This translates to : + * - Wi-Fi SSID when scheme is wifi_prov_scheme_softap + * - device name when scheme is wifi_prov_scheme_ble + */ + char service_name[12]; + get_device_service_name(service_name, sizeof(service_name)); + + /* What is the security level that we want (0 or 1): + * - WIFI_PROV_SECURITY_0 is simply plain text communication. + * - WIFI_PROV_SECURITY_1 is secure communication which consists of secure handshake + * using X25519 key exchange and proof of possession (pop) and AES-CTR + * for encryption/decryption of messages. + */ + wifi_prov_security_t security = WIFI_PROV_SECURITY_1; + + /* Do we want a proof-of-possession (ignored if Security 0 is selected): + * - this should be a string with length > 0 + * - NULL if not used + */ + const char *pop = "abcd1234"; + + /* What is the service key (could be NULL) + * This translates to : + * - Wi-Fi password when scheme is wifi_prov_scheme_softap + * - simply ignored when scheme is wifi_prov_scheme_ble + */ + const char *service_key = NULL; + +#ifdef CONFIG_EXAMPLE_PROV_TRANSPORT_BLE + /* This step is only useful when scheme is wifi_prov_scheme_ble. This will + * set a custom 128 bit UUID which will be included in the BLE advertisement + * and will correspond to the primary GATT service that provides provisioning + * endpoints as GATT characteristics. Each GATT characteristic will be + * formed using the primary service UUID as base, with different auto assigned + * 12th and 13th bytes (assume counting starts from 0th byte). The client side + * applications must identify the endpoints by reading the User Characteristic + * Description descriptor (0x2901) for each characteristic, which contains the + * endpoint name of the characteristic */ + uint8_t custom_service_uuid[] = { + /* LSB <--------------------------------------- + * ---------------------------------------> MSB */ + 0xb4, 0xdf, 0x5a, 0x1c, 0x3f, 0x6b, 0xf4, 0xbf, + 0xea, 0x4a, 0x82, 0x03, 0x04, 0x90, 0x1a, 0x02, + }; + wifi_prov_scheme_ble_set_service_uuid(custom_service_uuid); +#endif /* CONFIG_EXAMPLE_PROV_TRANSPORT_BLE */ + + /* An optional endpoint that applications can create if they expect to + * get some additional custom data during provisioning workflow. + * The endpoint name can be anything of your choice. + * This call must be made before starting the provisioning. + */ + wifi_prov_mgr_endpoint_create("custom-data"); + /* Start provisioning service */ + ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(security, pop, service_name, service_key)); + + /* The handler for the optional endpoint created above. + * This call must be made after starting the provisioning, and only if the endpoint + * has already been created above. + */ + wifi_prov_mgr_endpoint_register("custom-data", custom_prov_data_handler, NULL); + + /* Uncomment the following to wait for the provisioning to finish and then release + * the resources of the manager. Since in this case de-initialization is triggered + * by the default event loop handler, we don't need to call the following */ + // wifi_prov_mgr_wait(); + // wifi_prov_mgr_deinit(); + } else { + ESP_LOGI(TAG, "Already provisioned, starting Wi-Fi STA"); + + /* We don't need the manager as device is already provisioned, + * so let's release it's resources */ + wifi_prov_mgr_deinit(); + + /* Start Wi-Fi station */ + wifi_init_sta(); + } + + /* Wait for Wi-Fi connection */ + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_EVENT, false, true, portMAX_DELAY); + + /* Start main application now */ + while (1) { + ESP_LOGI(TAG, "Hello World!"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} diff --git a/examples/provisioning/wifi_prov_mgr/main/component.mk b/examples/provisioning/wifi_prov_mgr/main/component.mk new file mode 100644 index 00000000..61f8990c --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/provisioning/wifi_prov_mgr/partitions.csv b/examples/provisioning/wifi_prov_mgr/partitions.csv new file mode 100644 index 00000000..d01414b8 --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/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, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1200000, diff --git a/examples/provisioning/wifi_prov_mgr/sdkconfig.defaults b/examples/provisioning/wifi_prov_mgr/sdkconfig.defaults new file mode 100644 index 00000000..9125e679 --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/sdkconfig.defaults @@ -0,0 +1,11 @@ +# Override some defaults so BT stack is enabled and +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +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/examples/provisioning/wifi_prov_mgr/wifi_prov_mgr_test.py b/examples/provisioning/wifi_prov_mgr/wifi_prov_mgr_test.py new file mode 100644 index 00000000..aa00d75c --- /dev/null +++ b/examples/provisioning/wifi_prov_mgr/wifi_prov_mgr_test.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# +# Copyright 2018 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. + +from __future__ import print_function +import re +import os +import time + +import ttfw_idf +import esp_prov + +# Have esp_prov throw exception +esp_prov.config_throw_except = True + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI_BT") +def test_examples_wifi_prov_mgr(env, extra_data): + # Acquire DUT + dut1 = env.get_dut("wifi_prov_mgr", "examples/provisioning/wifi_prov_mgr", dut_class=ttfw_idf.ESP32DUT) + + # Get binary file + binary_file = os.path.join(dut1.app.binary_path, "wifi_prov_mgr.bin") + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("wifi_prov_mgr_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("wifi_prov_mgr_bin_size", bin_size // 1024, dut1.TARGET) + + # Upload binary and start testing + dut1.start_app() + + # Check if BT memory is released before provisioning starts + dut1.expect("wifi_prov_scheme_ble: BT memory released", timeout=60) + + # Parse BLE devname + devname = dut1.expect(re.compile(r"Provisioning started with service name : (PROV_\S\S\S\S\S\S)"), timeout=30)[0] + print("BLE Device Alias for DUT :", devname) + + print("Starting Provisioning") + verbose = False + protover = "v1.1" + secver = 1 + pop = "abcd1234" + provmode = "ble" + ap_ssid = "myssid" + ap_password = "mypassword" + + print("Getting security") + security = esp_prov.get_security(secver, pop, verbose) + if security is None: + raise RuntimeError("Failed to get security") + + print("Getting transport") + transport = esp_prov.get_transport(provmode, devname) + if transport is None: + raise RuntimeError("Failed to get transport") + + print("Verifying protocol version") + if not esp_prov.version_match(transport, protover): + raise RuntimeError("Mismatch in protocol version") + + print("Verifying scan list capability") + if not esp_prov.has_capability(transport, 'wifi_scan'): + raise RuntimeError("Capability not present") + + print("Starting Session") + if not esp_prov.establish_session(transport, security): + raise RuntimeError("Failed to start session") + + print("Sending Custom Data") + if not esp_prov.custom_data(transport, security, "My Custom Data"): + raise RuntimeError("Failed to send custom data") + + print("Sending Wifi credential to DUT") + if not esp_prov.send_wifi_config(transport, security, ap_ssid, ap_password): + raise RuntimeError("Failed to send Wi-Fi config") + + print("Applying config") + if not esp_prov.apply_wifi_config(transport, security): + raise RuntimeError("Failed to send apply config") + + success = False + retry = 0 + while True: + time.sleep(5) + print("Wi-Fi connection state") + ret = esp_prov.get_wifi_config(transport, security) + if (ret == 1): + continue + elif (ret == 0): + print("Provisioning was successful") + success = True + elif (ret == 3 and retry < 3): + retry = retry + 1 + print("Connection failed.. retry again...: ", ret) + continue + break + + if not success: + raise RuntimeError("Provisioning failed") + + # Check if BTDM memory is released after provisioning finishes + dut1.expect("wifi_prov_scheme_ble: BTDM memory released", timeout=30) + + +if __name__ == '__main__': + test_examples_wifi_prov_mgr()