diff --git a/.gitignore b/.gitignore index c027e556..5448b48b 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,8 @@ wifi_manager/.project sdkconfig sdkconfig.old **/build/ +**/managed_components/ +dependencies.lock #doxygen Doxyfile wifi_manager/doc/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 74ea43d5..83e0077c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,12 @@ if(IDF_VERSION_MAJOR GREATER_EQUAL 4) idf_component_register(SRC_DIRS src - REQUIRES log nvs_flash mdns wpa_supplicant lwip esp_http_server + REQUIRES esp_wifi log nvs_flash mdns wpa_supplicant lwip esp_http_server INCLUDE_DIRS src EMBED_FILES src/style.css src/code.js src/index.html) else() set(COMPONENT_SRCDIRS src) set(COMPONENT_ADD_INCLUDEDIRS src) - set(COMPONENT_REQUIRES log nvs_flash mdns wpa_supplicant lwip esp_http_server) + set(COMPONENT_REQUIRES esp_wifi log nvs_flash mdns wpa_supplicant lwip esp_http_server) set(COMPONENT_EMBED_FILES src/style.css src/code.js src/index.html) register_component() endif() diff --git a/dist/esp32-wifi-manager_0.0.1.tgz b/dist/esp32-wifi-manager_0.0.1.tgz new file mode 100644 index 00000000..abafc5dd Binary files /dev/null and b/dist/esp32-wifi-manager_0.0.1.tgz differ diff --git a/dist/esp32-wifi-manager_0.0.1/.gitignore b/dist/esp32-wifi-manager_0.0.1/.gitignore new file mode 100644 index 00000000..5448b48b --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/.gitignore @@ -0,0 +1,75 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Eclipse +.metadata/ +RemoteSystemsTempFiles/.project +.settings/ +*.a +*.o +*.d +wifi_manager/.cproject +wifi_manager/.project +sdkconfig +sdkconfig.old +**/build/ +**/managed_components/ +dependencies.lock +#doxygen +Doxyfile +wifi_manager/doc/ +.project +.cproject + +# Visual Studio Code +.vscode/ diff --git a/dist/esp32-wifi-manager_0.0.1/.travis.yml b/dist/esp32-wifi-manager_0.0.1/.travis.yml new file mode 100644 index 00000000..37c55458 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/.travis.yml @@ -0,0 +1,122 @@ +language: bash + +# when you suspects issues in cache, use the following line to disable cache. +# cache: false +cache: + directories: + - ${HOME}/distfiles + - ${HOME}/.ccache + - ${HOME}/.cache/pip +os: + - linux + +matrix: + include: + - env: + - PROJECT_TARGET="esp32" + - PROJECT_SDK_BRANCH="master" +# - env: +# - PROJECT_TARGET="esp8266" +# - PROJECT_SDK_BRANCH="master" + +addons: + apt: + packages: + - gcc + - wget + - make + - libncurses-dev + - flex + - bison + - python + - python-pip + - gperf + - ccache + +before_install: + # Save path to the git respository + - PROJECT_PATH=$(pwd) + +install: + - export TOOLCHAIN_DIR="${HOME}/${PROJECT_TARGET}" + - | + if [ ${PROJECT_TARGET} == "esp8266" ]; then + export PROJECT_GCC_PREFIX="xtensa-lx106-elf" + export PROJECT_TOOLCHAIN_FILE=xtensa-lx106-elf-linux64-1.22.0-92-g8facf4c-5.2.0.tar.gz + export PROJECT_SDK_NAME="ESP8266_RTOS_SDK" + else + export PROJECT_GCC_PREFIX="xtensa-esp32-elf" + export PROJECT_TOOLCHAIN_FILE=xtensa-esp32-elf-gcc8_2_0-esp32-2019r1-linux-amd64.tar.gz + export PROJECT_SDK_NAME="esp-idf" + fi + - export PROJECT_GCC_FILE="${PROJECT_GCC_PREFIX}-gcc" + - export PROJECT_DISTFILE_DIR="${HOME}/distfiles" + - export IDF_PATH=${TOOLCHAIN_DIR}/${PROJECT_SDK_NAME} + - export PROJECT_LOG="${HOME}/build.log" + - export PROJECT_EXAMPLE_DIR="${PROJECT_PATH}/examples" + # Install ESP32 toochain following steps as desribed + # in http://esp-idf.readthedocs.io/en/latest/linux-setup.html + + # Prepare directory for the toolchain + - mkdir -p ${TOOLCHAIN_DIR} ${PROJECT_DISTFILE_DIR} + # Get SDK from github + - git clone --branch ${PROJECT_SDK_BRANCH} --recursive https://github.com/espressif/${PROJECT_SDK_NAME}.git ${IDF_PATH} + + # Setup ccache to build faster + # XXX when the entire build process exceeds 50 min, th job will be killed + # https://docs.travis-ci.com/user/customizing-the-build/#build-timeouts + - ccache --version + - mkdir ${HOME}/ccache_bin + - (cd ${HOME}/ccache_bin && ln -s /usr/bin/ccache ${PROJECT_GCC_FILE}) + - export CCACHE_BASEDIR=$PROJECT_PATH + - export CCACHE_CPP2=true + + # Get Python requirements + - python -m pip install --user --upgrade pyOpenSSL + - python -m pip install --user -r ${IDF_PATH}/requirements.txt + + # Download binary toolchain if it does not exist + - | + if [ ! -f ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} ]; then + wget -O ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} https://dl.espressif.com/dl/${PROJECT_TOOLCHAIN_FILE} + fi + - tar -xz -C ${TOOLCHAIN_DIR} -f ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} + + # Make toolchains available for all terminal sessions + - export PATH=$HOME/ccache_bin:$PATH:$HOME/${PROJECT_TARGET}/${PROJECT_GCC_PREFIX}/bin + +script: + - rm -f ${PROJECT_LOG} + # XXX surpress log output where possible. when the size exceeds 4 MB, the + # job will be killed. + - | + IGNORE_FILE="travis-ignore" + + case ${PROJECT_TARGET} in + esp32) + ;; + esp8266) + IGNORE_FILE="travis-ignore-esp8266" + # these drivers do not compile for ESP8266 yet + export EXCLUDE_COMPONENTS="encoder max7219 mcp23x17" + ;; + esac + + cd ${PROJECT_EXAMPLE_DIR} + for i in $(ls -d */); do + if [ ! -e ${PROJECT_EXAMPLE_DIR}/${i}/${IGNORE_FILE} ]; then + echo "Building ${i}..." + cd ${PROJECT_EXAMPLE_DIR}/${i} + make defconfig + make -j2 >> ${PROJECT_LOG} + if [ $? -ne 0 ]; then + # when failed, show last 100 lines for debugging, and exit with + # non-zero exit code + tail -n 100 ${PROJECT_LOG} + exit 1 + fi + make clean >/dev/null + # make sure the directory is clean + rm -rf ${i}/sdkconfig ${i}/build + fi + done diff --git a/dist/esp32-wifi-manager_0.0.1/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.1/CMakeLists.txt new file mode 100644 index 00000000..83e0077c --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/CMakeLists.txt @@ -0,0 +1,12 @@ +if(IDF_VERSION_MAJOR GREATER_EQUAL 4) + idf_component_register(SRC_DIRS src + REQUIRES esp_wifi log nvs_flash mdns wpa_supplicant lwip esp_http_server + INCLUDE_DIRS src + EMBED_FILES src/style.css src/code.js src/index.html) +else() + set(COMPONENT_SRCDIRS src) + set(COMPONENT_ADD_INCLUDEDIRS src) + set(COMPONENT_REQUIRES esp_wifi log nvs_flash mdns wpa_supplicant lwip esp_http_server) + set(COMPONENT_EMBED_FILES src/style.css src/code.js src/index.html) + register_component() +endif() diff --git a/dist/esp32-wifi-manager_0.0.1/Kconfig b/dist/esp32-wifi-manager_0.0.1/Kconfig new file mode 100644 index 00000000..59c5a9a8 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/Kconfig @@ -0,0 +1,81 @@ +menu "Wifi Manager Configuration" + +config WIFI_MANAGER_TASK_PRIORITY + int "RTOS Task Priority for the wifi_manager" + default 5 + help + Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1. For this particular reason, minimum recommended task priority is 2. + +config WIFI_MANAGER_RETRY_TIMER + int "Time (in ms) between each retry attempt" + default 5000 + help + Defines the time to wait before an attempt to re-connect to a saved wifi is made after connection is lost or another unsuccesful attempt is made. + +config WIFI_MANAGER_MAX_RETRY_START_AP + int "Max Retry before starting the AP" + default 3 + help + Defines the maximum number of failed retries allowed before the WiFi manager starts its own access point. + +config WIFI_MANAGER_SHUTDOWN_AP_TIMER + int "Time (in ms) to wait before shutting down the AP" + default 60000 + help + Defines the time (in ms) to wait after a succesful connection before shutting down the access point. + +config WEBAPP_LOCATION + string "Defines the URL where the wifi manager is located" + default "/" + help + This parameter helps you relocate the wifimanager to another URL, for instance /wifimanager/ The trailing slash is important and should be included + +config DEFAULT_AP_SSID + string "Access Point SSID" + default "esp32" + help + SSID (network name) the the esp32 will broadcast. + +config DEFAULT_AP_PASSWORD + string "Access Point Password" + default "esp32pwd" + help + Password used for the Access Point. Leave empty and set AUTH MODE to WIFI_AUTH_OPEN for no password. + +config DEFAULT_AP_CHANNEL + int "Access Point WiFi Channel" + default 1 + help + Be careful you might not see the access point if you use a channel not allowed in your country. + +config DEFAULT_AP_IP + string "Access Point IP Address" + default "10.10.0.1" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_GATEWAY + string "Access Point IP Gateway" + default "10.10.0.1" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_NETMASK + string "Access Point Netmask" + default "255.255.255.0" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_MAX_CONNECTIONS + int "Access Point Max Connections" + default 4 + help + Max is 4. + +config DEFAULT_AP_BEACON_INTERVAL + int "Access Point Beacon Interval (ms)" + default 100 + help + 100ms is the recommended default. + +endmenu diff --git a/dist/esp32-wifi-manager_0.0.1/LICENSE.md b/dist/esp32-wifi-manager_0.0.1/LICENSE.md new file mode 100644 index 00000000..5f2ac0bc --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2017-2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dist/esp32-wifi-manager_0.0.1/README.md b/dist/esp32-wifi-manager_0.0.1/README.md new file mode 100644 index 00000000..43248f3d --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/README.md @@ -0,0 +1,225 @@ +# What is esp32-wifi-manager? + +### Build status [![Build Status](https://travis-ci.com/tonyp7/esp32-wifi-manager.svg?branch=master)](https://travis-ci.com/tonyp7/esp32-wifi-manager) + +*esp32-wifi-manager* is a pure C esp-idf component for ESP32 that enables easy management of wifi networks through a web portal. + +*esp32-wifi-manager* is is an all in one wifi scanner, http server & dns daemon living in the least amount of RAM possible. + +*esp32-wifi-manager* will automatically attempt to re-connect to a previously saved network on boot, and if it cannot find a saved wifi it will start its own access point through which you can manage and connect to wifi networks. Upon a succesful connection, the software will shutdown the access point automatically after some time (1 minute by default). + +*esp32-wifi-manager* compiles with esp-idf 4.2 and above. See [Getting Started](#getting-started) to guide you through your first setup. + +# Content + - [Demo](#demo) + - [Look And Feel](#look-and-feel) + - [Getting Started](#getting-started) + - [Requirements](#requirements) + - [Hello World](#hello-world) + - [Configuring the Wifi Manager](#configuring-the-wifi-manager) + - [Adding esp32-wifi-manager to your code](#adding-esp32-wifi-manager-to-your-code) + - [Interacting with the manager](#interacting-with-the-manager) + - [Interacting with the http server](#interacting-with-the-http-server) + - [Thread safety and access to NVS](#thread-safety-and-access-to-nvs) + - [License](#license) + + +# Demo +[![esp32-wifi-manager demo](http://img.youtube.com/vi/hxlZi15bym4/0.jpg)](http://www.youtube.com/watch?v=hxlZi15bym4) + +# Look and Feel +![esp32-wifi-manager on an mobile device](https://idyl.io/wp-content/uploads/2017/11/esp32-wifi-manager-password.png "esp32-wifi-manager") ![esp32-wifi-manager on an mobile device](https://idyl.io/wp-content/uploads/2017/11/esp32-wifi-manager-connected-to.png "esp32-wifi-manager") + +# Getting Started + +## Requirements + +To get you started, esp32-wifi-manager needs: + +- esp-idf **4.2 and up** +- esp32 or esp32-s2 + +There are breaking changes and new features in esp-idf 4.1 and 4.2 which makes esp32-wifi-manager incompatible with anything lower than 4.2. This includes esp_netif (introduced in 4.1) and esp_event_handler_instance_t (introduced in 4.2). It is recommended to compile esp32-wifi-manager with the master tree to avoid any compatibility issue. + +## Hello World + +Clone the repository where you want it to be. If you are unfamiliar with Git, you can use Github Desktop on Windows: + +```bash +git clone https://github.com/tonyp7/esp32-wifi-manager.git +``` + +Navigate under the included example: + +```bash +cd esp32-wifi-manager/examples/default_demo +``` + +Compile the code and load it on your esp32: + +```bash +idf.py build flash monitor +``` + +_Note: while it is encouraged to use the newer build system with idf.py and cmake, esp32-wifi-manager still supports the legacy build system. If you are using make on Linux or make using MSYS2 on Windows, you can still use "make build flash monitor" if you prefer_ + +Now, using any wifi capable device, you will see a new wifi access point named *esp32*. Connect to it using the default password *esp32pwd*. If the captive portal does not pop up on your device, you can access the wifi manager at its default IP address: http://10.10.0.1. + +## Configuring the Wifi Manager + +esp32-wifi-manager can be configured without touching its code. At the project level use: + +```bash +idf.py menuconfig +``` + +Navigate in "Component config" then pick "Wifi Manager Configuration". You will be greeted by the following screen: + +![esp32-wifi-manager-menuconfig](https://idyl.io/wp-content/uploads/2020/08/wifi-manager-menuconfig-800px.png "menuconfig screen") + +You can change the ssid and password of the access point at your convenience, but it is highly recommended to keep default values. Your password should be between 8 and 63 characters long, to comply with the WPA2 standard. If the password is set to an empty value or is less than 8 characters long, esp32-wifi-manager will create its access point as an open wifi network. + +You can also change the values for various timers, for instance how long it takes for the access point to shutdown once a connection is established (default: 60000). While it could be tempting to set this timer to 0, just be warned that in that case the user will never get the feedback that a connection is succesful. Shutting down the AP will instantly kill the current navigating session on the captive portal. + +Finally, you can choose to relocate esp32-wifi-manager to a different URL by changing the default value of "/" to something else, for instance "/wifimanager/". Please note that the trailing slash does matter. This feature is particularly useful in case you want your own webapp to co-exist with esp32-wifi-manager's own web pages. + +# Adding esp32-wifi-manager to your code + +In order to use esp32-wifi-manager effectively in your esp-idf projects, copy the whole esp32-wifi-manager repository (or git clone) into a components subfolder. + +Your project should look like this: + + - project_folder + - build + - components + - esp32-wifi-manager + - main + - main.c + +Under eclipse, this is what a typical project looks like: + +![eclipse project with esp32-wifi-manager](https://idyl.io/wp-content/uploads/2020/07/eclipse-idf-project.png "eclipse project with esp32-wifi-manager") + +Once this is done, you need to edit the CMakeLists.txt file at the root of your project to register the components folder. This is done by adding the following line: + +```cmake +set(EXTRA_COMPONENTS_DIRS components/) +``` + +A typical CmakeLists.txt file should look like this: + +```cmake +cmake_minimum_required(VERSION 3.5) +set(EXTRA_COMPONENT_DIRS components/) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(name_of_your_project) +``` + +If you are using the old build system with make instead, you should edit the Makefile instead such as: + +```make +PROJECT_NAME := name_of_your_project +EXTRA_COMPONENT_DIRS := components/ +include $(IDF_PATH)/make/project.mk +``` + +Once this is done, you can now in your user code add the header: + +```c +#include "wifi_manager.h" +``` + +All you need to do now is to call wifi_manager_start(); in your code. See [examples/default_demo](examples/default_demo) if you are uncertain. + + +## Interacting with the manager + +Ther are effectively three different ways you can embed esp32-wifi-manager with your code: +* Just forget about it and poll in your code for wifi connectivity status +* Use event callbacks +* Modify esp32-wifi-manager code directly to fit your needs + +**Event callbacks** are the cleanest way to use the wifi manager and that's the recommended way to do it. A typical use-case would be to get notified when wifi manager finally gets a connection to an access point. In order to do this you can simply define a callback function: + +```c +void cb_connection_ok(void *pvParameter){ + ESP_LOGI(TAG, "I have a connection!"); +} +``` + +Then just register it by calling: + +```c +wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok); +``` + +That's it! Now everytime the event is triggered it will call this function. The [examples/default_demo](examples/default_demo) contains sample code using callbacks. + +### List of events + +The list of possible events you can add a callback to are defined by message_code_t in wifi_manager.h. They are as following: + +* WM_ORDER_START_HTTP_SERVER +* WM_ORDER_STOP_HTTP_SERVER +* WM_ORDER_START_DNS_SERVICE +* WM_ORDER_STOP_DNS_SERVICE +* WM_ORDER_START_WIFI_SCAN +* WM_ORDER_LOAD_AND_RESTORE_STA +* WM_ORDER_CONNECT_STA +* WM_ORDER_DISCONNECT_STA +* WM_ORDER_START_AP +* WM_EVENT_STA_DISCONNECTED +* WM_EVENT_SCAN_DONE +* WM_EVENT_STA_GOT_IP +* WM_ORDER_STOP_AP + +In practice, keeping track of WM_EVENT_STA_GOT_IP and WM_EVENT_STA_DISCONNECTED is key to know whether or not your esp32 has a connection. The other messages can mostly be ignored in a typical application using esp32-wifi-manager. + +### Events parameters + +Callback signature includes a void* pointer. For most events, this additional parameter is empty and sent as a NULL value. A few select events have additional data which can be leveraged by user code. They are listed below: + +* WM_EVENT_SCAN_DONE is sent with a wifi_event_sta_scan_done_t* object. +* WM_EVENT_STA_DISCONNECTED is sent with a wifi_event_sta_disconnected_t* object. +* WM_EVENT_STA_GOT_IP is sent with a ip_event_got_ip_t* object. + +These objects are standard esp-idf structures, and are documented as such in the [official pages](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html). + +The [examples/default_demo](examples/default_demo) demonstrates how you can read a ip_event_got_ip_t object to access the IP address assigned to the esp32. + +## Interacting with the http server + +Because esp32-wifi-manager spawns its own http server, you might want to extend this server to serve your own pages in your application. It is possible to do so by registering your own URL handler using the standard esp_http_server signature: + +```c +esp_err_t my_custom_handler(httpd_req_t *req){ +``` + +And then registering the handler by doing + +```c +http_app_set_handler_hook(HTTP_GET, &my_custom_handler); +``` + +The [examples/http_hook](examples/http_hook) contains an example where a web page is registered at /helloworld + +## Thread safety and access to NVS + +esp32-wifi-manager accesses the non-volatile storage to store and loads its configuration into a dedicated namespace "espwifimgr". If you want to make sure there will never be a conflict with concurrent access to the NVS, you can include nvs_sync.h and use calls to nvs_sync_lock and nvs_sync_unlock. + +```c +nvs_handle handle; + +if(nvs_sync_lock( portMAX_DELAY )){ + if(nvs_open(wifi_manager_nvs_namespace, NVS_READWRITE, &handle) == ESP_OK){ + /* do something with NVS */ + nvs_close(handle); + } + nvs_sync_unlock(); +} +``` +nvs_sync_lock waits for the number of ticks sent to it as a parameter to acquire a mutex. It is recommended to use portMAX_DELAY. In practice, nvs_sync_lock will almost never wait. + + +# License +*esp32-wifi-manager* is MIT licensed. As such, it can be included in any project, commercial or not, as long as you retain original copyright. Please make sure to read the license file. diff --git a/dist/esp32-wifi-manager_0.0.1/component.mk b/dist/esp32-wifi-manager_0.0.1/component.mk new file mode 100644 index 00000000..75e34089 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/component.mk @@ -0,0 +1,4 @@ +COMPONENT_ADD_INCLUDEDIRS = src +COMPONENT_SRCDIRS = src +COMPONENT_DEPENDS = log esp_http_server +COMPONENT_EMBED_FILES := src/style.css src/code.js src/index.html diff --git a/dist/esp32-wifi-manager_0.0.1/examples/default_demo/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/CMakeLists.txt new file mode 100644 index 00000000..6458baae --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/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) +set(EXTRA_COMPONENT_DIRS ../../) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(default_demo) diff --git a/dist/esp32-wifi-manager_0.0.1/examples/default_demo/Makefile b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/Makefile new file mode 100644 index 00000000..fb186183 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := wifi_manager + +EXTRA_COMPONENT_DIRS := ../../ + +include $(IDF_PATH)/make/project.mk + diff --git a/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/CMakeLists.txt new file mode 100644 index 00000000..31addfc4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "user_main.c") + +register_component() diff --git a/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/component.mk b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/idf_component.yml b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/idf_component.yml new file mode 100644 index 00000000..8826a619 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "^1.3.2" + ## Required IDF version + idf: + version: ">=4.1.0" + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true diff --git a/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/user_main.c b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/user_main.c new file mode 100644 index 00000000..19af535e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/main/user_main.c @@ -0,0 +1,80 @@ +/* +Copyright (c) 2017-2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file main.c +@author Tony Pottier +@brief Entry point for the ESP32 application. +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "wifi_manager.h" + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "main"; + +/** + * @brief RTOS task that periodically prints the heap memory available. + * @note Pure debug information, should not be ever started on production code! This is an example on how you can integrate your code with wifi-manager + */ +void monitoring_task(void *pvParameter) +{ + for(;;){ + ESP_LOGI(TAG, "free heap: %lu",esp_get_free_heap_size()); + vTaskDelay( pdMS_TO_TICKS(10000) ); + } +} + + +/** + * @brief this is an exemple of a callback that you can setup in your own app to get notified of wifi manager event. + */ +void cb_connection_ok(void *pvParameter){ + ip_event_got_ip_t* param = (ip_event_got_ip_t*)pvParameter; + + /* transform IP to human readable string */ + char str_ip[16]; + esp_ip4addr_ntoa(¶m->ip_info.ip, str_ip, IP4ADDR_STRLEN_MAX); + + ESP_LOGI(TAG, "I have a connection and my IP is %s!", str_ip); +} + +void app_main() +{ + /* start the wifi manager */ + wifi_manager_start(); + + /* register a callback as an example to how you can integrate your code with the wifi manager */ + wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok); +#if CONFIG_SOC_CPU_CORES_NUM > 1 + /* your code should go here. Here we simply create a task on core 2 that monitors free heap memory */ + xTaskCreatePinnedToCore(&monitoring_task, "monitoring_task", 2048, NULL, 1, NULL, 1); +#endif +} diff --git a/dist/esp32-wifi-manager_0.0.1/examples/default_demo/sdkconfig.defaults b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/sdkconfig.defaults new file mode 100644 index 00000000..f44c06f4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/default_demo/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_LWIP_IPV6=y diff --git a/dist/esp32-wifi-manager_0.0.1/examples/http_hook/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/CMakeLists.txt new file mode 100644 index 00000000..377f86fe --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/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) +set(EXTRA_COMPONENT_DIRS ../../) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(http_hook) diff --git a/dist/esp32-wifi-manager_0.0.1/examples/http_hook/Makefile b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/Makefile new file mode 100644 index 00000000..fb186183 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := wifi_manager + +EXTRA_COMPONENT_DIRS := ../../ + +include $(IDF_PATH)/make/project.mk + diff --git a/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/CMakeLists.txt new file mode 100644 index 00000000..31addfc4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "user_main.c") + +register_component() diff --git a/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/component.mk b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/idf_component.yml b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/idf_component.yml new file mode 100644 index 00000000..8826a619 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "^1.3.2" + ## Required IDF version + idf: + version: ">=4.1.0" + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true diff --git a/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/user_main.c b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/user_main.c new file mode 100644 index 00000000..2446f269 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/main/user_main.c @@ -0,0 +1,76 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file user_main.c +@author Tony Pottier +@brief Entry point for the ESP32 application. +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "wifi_manager.h" +#include "http_app.h" + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "main"; + + +static esp_err_t my_get_handler(httpd_req_t *req){ + + /* our custom page sits at /helloworld in this example */ + if(strcmp(req->uri, "/helloworld") == 0){ + + ESP_LOGI(TAG, "Serving page /helloworld"); + + const char* response = "

Hello World!

"; + + httpd_resp_set_status(req, "200 OK"); + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, response, strlen(response)); + } + else{ + /* send a 404 otherwise */ + httpd_resp_send_404(req); + } + + return ESP_OK; +} + + +void app_main() +{ + /* start the wifi manager */ + wifi_manager_start(); + + /* set custom handler for the http server + * Now navigate to /helloworld to see the custom page + * */ + http_app_set_handler_hook(HTTP_GET, &my_get_handler); + +} diff --git a/dist/esp32-wifi-manager_0.0.1/examples/http_hook/sdkconfig.defaults b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/sdkconfig.defaults new file mode 100644 index 00000000..f44c06f4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/examples/http_hook/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_LWIP_IPV6=y diff --git a/dist/esp32-wifi-manager_0.0.1/idf_component.yml b/dist/esp32-wifi-manager_0.0.1/idf_component.yml new file mode 100644 index 00000000..739f2805 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/idf_component.yml @@ -0,0 +1,5 @@ +branch: update_build_system +description: esp32-wifi-manager +license: MIT +url: https://github.com/ankayca/esp32-wifi-manager/tree/update_build_system +version: 0.0.1 diff --git a/dist/esp32-wifi-manager_0.0.1/src/ap.json b/dist/esp32-wifi-manager_0.0.1/src/ap.json new file mode 100644 index 00000000..de61f86a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/ap.json @@ -0,0 +1,12 @@ +[ +{"ssid":"Pantum-AP-A6D49F","chan":11,"rssi":-55,"auth":4}, +{"ssid":"a0308","chan":1,"rssi":-56,"auth":3}, +{"ssid":"dlink-D9D8","chan":11,"rssi":-82,"auth":4}, +{"ssid":"Linksys06730","chan":7,"rssi":-85,"auth":3}, +{"ssid":"SINGTEL-5171","chan":9,"rssi":-88,"auth":4}, +{"ssid":"1126-1","chan":11,"rssi":-89,"auth":4}, +{"ssid":"The Shah 5GHz-2","chan":1,"rssi":-90,"auth":3}, +{"ssid":"SINGTEL-1D28 (2G)","chan":11,"rssi":-91,"auth":3}, +{"ssid":"dlink-F864","chan":1,"rssi":-92,"auth":4}, +{"ssid":"dlink-74F0","chan":1,"rssi":-93,"auth":4} +] \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/code.js b/dist/esp32-wifi-manager_0.0.1/src/code.js new file mode 100644 index 00000000..934595a9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/code.js @@ -0,0 +1,352 @@ +// save some bytes +const gel = (e) => document.getElementById(e); + +const wifi_div = gel("wifi"); +const connect_div = gel("connect"); +const connect_manual_div = gel("connect_manual"); +const connect_wait_div = gel("connect-wait"); +const connect_details_div = gel("connect-details"); + +function docReady(fn) { + // see if DOM is already available + if ( + document.readyState === "complete" || + document.readyState === "interactive" + ) { + // call on next available tick + setTimeout(fn, 1); + } else { + document.addEventListener("DOMContentLoaded", fn); + } +} + +var selectedSSID = ""; +var refreshAPInterval = null; +var checkStatusInterval = null; + +function stopCheckStatusInterval() { + if (checkStatusInterval != null) { + clearInterval(checkStatusInterval); + checkStatusInterval = null; + } +} + +function stopRefreshAPInterval() { + if (refreshAPInterval != null) { + clearInterval(refreshAPInterval); + refreshAPInterval = null; + } +} + +function startCheckStatusInterval() { + checkStatusInterval = setInterval(checkStatus, 950); +} + +function startRefreshAPInterval() { + refreshAPInterval = setInterval(refreshAP, 3800); +} + +docReady(async function () { + gel("wifi-status").addEventListener( + "click", + () => { + wifi_div.style.display = "none"; + document.getElementById("connect-details").style.display = "block"; + }, + false + ); + + gel("manual_add").addEventListener( + "click", + (e) => { + selectedSSID = e.target.innerText; + + gel("ssid-pwd").textContent = selectedSSID; + wifi_div.style.display = "none"; + connect_manual_div.style.display = "block"; + connect_div.style.display = "none"; + + gel("connect-success").display = "none"; + gel("connect-fail").display = "none"; + }, + false + ); + + gel("wifi-list").addEventListener( + "click", + (e) => { + selectedSSID = e.target.innerText; + gel("ssid-pwd").textContent = selectedSSID; + connect_div.style.display = "block"; + wifi_div.style.display = "none"; + // init_cancel(); + }, + false + ); + + function cancel() { + selectedSSID = ""; + connect_div.style.display = "none"; + connect_manual_div.style.display = "none"; + wifi_div.style.display = "block"; + } + + gel("cancel").addEventListener("click", cancel, false); + + gel("manual_cancel").addEventListener("click", cancel, false); + + gel("join").addEventListener("click", performConnect, false); + + gel("manual_join").addEventListener( + "click", + (e) => { + performConnect("manual"); + }, + false + ); + + gel("ok-details").addEventListener( + "click", + () => { + connect_details_div.style.display = "none"; + wifi_div.style.display = "block"; + }, + false + ); + + gel("ok-credits").addEventListener( + "click", + () => { + gel("credits").style.display = "none"; + gel("app").style.display = "block"; + }, + false + ); + + gel("acredits").addEventListener( + "click", + () => { + event.preventDefault(); + gel("app").style.display = "none"; + gel("credits").style.display = "block"; + }, + false + ); + + gel("ok-connect").addEventListener( + "click", + () => { + connect_wait_div.style.display = "none"; + wifi_div.style.display = "block"; + }, + false + ); + + gel("disconnect").addEventListener( + "click", + () => { + gel("diag-disconnect").style.display = "block"; + gel("connect-details-wrap").classList.add("blur"); + }, + false + ); + + gel("no-disconnect").addEventListener( + "click", + () => { + gel("diag-disconnect").style.display = "none"; + gel("connect-details-wrap").classList.remove("blur"); + }, + false + ); + + gel("yes-disconnect").addEventListener("click", async () => { + stopCheckStatusInterval(); + selectedSSID = ""; + + document.getElementById("diag-disconnect").style.display = "none"; + gel("connect-details-wrap").classList.remove("blur"); + + await fetch("connect.json", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: { timestamp: Date.now() }, + }); + + startCheckStatusInterval(); + + connect_details_div.style.display = "none"; + wifi_div.style.display = "block"; + }); + + //first time the page loads: attempt get the connection status and start the wifi scan + await refreshAP(); + startCheckStatusInterval(); + startRefreshAPInterval(); +}); + +async function performConnect(conntype) { + //stop the status refresh. This prevents a race condition where a status + //request would be refreshed with wrong ip info from a previous connection + //and the request would automatically shows as succesful. + stopCheckStatusInterval(); + + //stop refreshing wifi list + stopRefreshAPInterval(); + + var pwd; + if (conntype == "manual") { + //Grab the manual SSID and PWD + selectedSSID = gel("manual_ssid").value; + pwd = gel("manual_pwd").value; + } else { + pwd = gel("pwd").value; + } + //reset connection + gel("loading").style.display = "block"; + gel("connect-success").style.display = "none"; + gel("connect-fail").style.display = "none"; + + gel("ok-connect").disabled = true; + gel("ssid-wait").textContent = selectedSSID; + connect_div.style.display = "none"; + connect_manual_div.style.display = "none"; + connect_wait_div.style.display = "block"; + + await fetch("connect.json", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Custom-ssid": selectedSSID, + "X-Custom-pwd": pwd, + }, + body: { timestamp: Date.now() }, + }); + + //now we can re-set the intervals regardless of result + startCheckStatusInterval(); + startRefreshAPInterval(); +} + +function rssiToIcon(rssi) { + if (rssi >= -60) { + return "w0"; + } else if (rssi >= -67) { + return "w1"; + } else if (rssi >= -75) { + return "w2"; + } else { + return "w3"; + } +} + +async function refreshAP(url = "ap.json") { + try { + var res = await fetch(url); + var access_points = await res.json(); + if (access_points.length > 0) { + //sort by signal strength + access_points.sort((a, b) => { + var x = a["rssi"]; + var y = b["rssi"]; + return x < y ? 1 : x > y ? -1 : 0; + }); + refreshAPHTML(access_points); + } + } catch (e) { + console.info("Access points returned empty from /ap.json!"); + } +} + +function refreshAPHTML(data) { + var h = ""; + data.forEach(function (e, idx, array) { + let ap_class = idx === array.length - 1 ? "" : " brdb"; + let rssicon = rssiToIcon(e.rssi); + let auth = e.auth == 0 ? "" : "pw"; + h += `
${e.ssid}
\n`; + }); + + gel("wifi-list").innerHTML = h; +} + +async function checkStatus(url = "status.json") { + try { + var response = await fetch(url); + var data = await response.json(); + if (data && data.hasOwnProperty("ssid") && data["ssid"] != "") { + if (data["ssid"] === selectedSSID) { + // Attempting connection + switch (data["urc"]) { + case 0: + console.info("Got connection!"); + document.querySelector( + "#connected-to div div div span" + ).textContent = data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = data["ip"]; + gel("netmask").textContent = data["netmask"]; + gel("gw").textContent = data["gw"]; + gel("wifi-status").style.display = "block"; + + //unlock the wait screen if needed + gel("ok-connect").disabled = false; + + //update wait screen + gel("loading").style.display = "none"; + gel("connect-success").style.display = "block"; + gel("connect-fail").style.display = "none"; + break; + case 1: + console.info("Connection attempt failed!"); + document.querySelector( + "#connected-to div div div span" + ).textContent = data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = "0.0.0.0"; + gel("netmask").textContent = "0.0.0.0"; + gel("gw").textContent = "0.0.0.0"; + + //don't show any connection + gel("wifi-status").display = "none"; + + //unlock the wait screen + gel("ok-connect").disabled = false; + + //update wait screen + gel("loading").display = "none"; + gel("connect-fail").style.display = "block"; + gel("connect-success").style.display = "none"; + break; + } + } else if (data.hasOwnProperty("urc") && data["urc"] === 0) { + console.info("Connection established"); + //ESP32 is already connected to a wifi without having the user do anything + if ( + gel("wifi-status").style.display == "" || + gel("wifi-status").style.display == "none" + ) { + document.querySelector("#connected-to div div div span").textContent = + data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = data["ip"]; + gel("netmask").textContent = data["netmask"]; + gel("gw").textContent = data["gw"]; + gel("wifi-status").style.display = "block"; + } + } + } else if (data.hasOwnProperty("urc") && data["urc"] === 2) { + console.log("Manual disconnect requested..."); + if (gel("wifi-status").style.display == "block") { + gel("wifi-status").style.display = "none"; + } + } + } catch (e) { + console.info("Was not able to fetch /status.json"); + } +} diff --git a/dist/esp32-wifi-manager_0.0.1/src/component.mk b/dist/esp32-wifi-manager_0.0.1/src/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/dist/esp32-wifi-manager_0.0.1/src/compress.bat b/dist/esp32-wifi-manager_0.0.1/src/compress.bat new file mode 100644 index 00000000..b9caad22 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/compress.bat @@ -0,0 +1,2 @@ +gzip index.html style.css --best --keep --force +pause \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/connect b/dist/esp32-wifi-manager_0.0.1/src/connect new file mode 100644 index 00000000..8c7fe211 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/connect @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/dns_server.c b/dist/esp32-wifi-manager_0.0.1/src/dns_server.c new file mode 100644 index 00000000..6693913a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/dns_server.c @@ -0,0 +1,184 @@ +/* +Copyright (c) 2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file dns_server.c +@author Tony Pottier +@brief Defines an extremely basic DNS server for captive portal functionality. +It's basically a DNS hijack that replies to the esp's address no matter which +request is sent to it. + +Contains the freeRTOS task for the DNS server that processes the requests. + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wifi_manager.h" +#include "dns_server.h" + +static const char TAG[] = "dns_server"; +static TaskHandle_t task_dns_server = NULL; +int socket_fd; + +void dns_server_start() { + if(task_dns_server == NULL){ + xTaskCreate(&dns_server, "dns_server", 3072, NULL, WIFI_MANAGER_TASK_PRIORITY-1, &task_dns_server); + } +} + +void dns_server_stop(){ + if(task_dns_server){ + vTaskDelete(task_dns_server); + close(socket_fd); + task_dns_server = NULL; + } + +} + + + +void dns_server(void *pvParameters) { + + + + struct sockaddr_in ra; + + /* Set redirection DNS hijack to the access point IP */ + ip4_addr_t ip_resolved; + inet_pton(AF_INET, DEFAULT_AP_IP, &ip_resolved); + + + /* Create UDP socket */ + socket_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (socket_fd < 0){ + ESP_LOGE(TAG, "Failed to create socket"); + exit(0); + } + + /* Bind to port 53 (typical DNS Server port) */ + esp_netif_ip_info_t ip; + esp_netif_t* netif_sta = wifi_manager_get_esp_netif_sta(); + ESP_ERROR_CHECK(esp_netif_get_ip_info(netif_sta, &ip)); + ra.sin_family = AF_INET; + ra.sin_addr.s_addr = ip.ip.addr; + ra.sin_port = htons(53); + if (bind(socket_fd, (struct sockaddr *)&ra, sizeof(struct sockaddr_in)) == -1) { + ESP_LOGE(TAG, "Failed to bind to 53/udp"); + close(socket_fd); + exit(1); + } + + struct sockaddr_in client; + socklen_t client_len; + client_len = sizeof(client); + int length; + uint8_t data[DNS_QUERY_MAX_SIZE]; /* dns query buffer */ + uint8_t response[DNS_ANSWER_MAX_SIZE]; /* dns response buffer */ + char ip_address[INET_ADDRSTRLEN]; /* buffer to store IPs as text. This is only used for debug and serves no other purpose */ + char *domain; /* This is only used for debug and serves no other purpose */ + int err; + + ESP_LOGI(TAG, "DNS Server listening on 53/udp"); + + /* Start loop to process DNS requests */ + for(;;) { + + memset(data, 0x00, sizeof(data)); /* reset buffer */ + length = recvfrom(socket_fd, data, sizeof(data), 0, (struct sockaddr *)&client, &client_len); /* read udp request */ + + /*if the query is bigger than the buffer size we simply ignore it. This case should only happen in case of multiple + * queries within the same DNS packet and is not supported by this simple DNS hijack. */ + if ( length > 0 && ((length + sizeof(dns_answer_t)-1) < DNS_ANSWER_MAX_SIZE) ) { + + data[length] = '\0'; /*in case there's a bogus domain name that isn't null terminated */ + + /* Generate header message */ + memcpy(response, data, sizeof(dns_header_t)); + dns_header_t *dns_header = (dns_header_t*)response; + dns_header->QR = 1; /*response bit */ + dns_header->OPCode = DNS_OPCODE_QUERY; /* no support for other type of response */ + dns_header->AA = 1; /*authoritative answer */ + dns_header->RCode = DNS_REPLY_CODE_NO_ERROR; /* no error */ + dns_header->TC = 0; /*no truncation */ + dns_header->RD = 0; /*no recursion */ + dns_header->ANCount = dns_header->QDCount; /* set answer count = question count -- duhh! */ + dns_header->NSCount = 0x0000; /* name server resource records = 0 */ + dns_header->ARCount = 0x0000; /* resource records = 0 */ + + + /* copy the rest of the query in the response */ + memcpy(response + sizeof(dns_header_t), data + sizeof(dns_header_t), length - sizeof(dns_header_t)); + + + /* extract domain name and request IP for debug */ + inet_ntop(AF_INET, &(client.sin_addr), ip_address, INET_ADDRSTRLEN); + domain = (char*) &data[sizeof(dns_header_t) + 1]; + for(char* c=domain; *c != '\0'; c++){ + if(*c < ' ' || *c > 'z') *c = '.'; /* technically we should test if the first two bits are 00 (e.g. if( (*c & 0xC0) == 0x00) *c = '.') but this makes the code a lot more readable */ + } + ESP_LOGI(TAG, "Replying to DNS request for %s from %s", domain, ip_address); + + + /* create DNS answer at the end of the query*/ + dns_answer_t *dns_answer = (dns_answer_t*)&response[length]; + dns_answer->NAME = __bswap_16(0xC00C); /* This is a pointer to the beginning of the question. As per DNS standard, first two bits must be set to 11 for some odd reason hence 0xC0 */ + dns_answer->TYPE = __bswap_16(DNS_ANSWER_TYPE_A); + dns_answer->CLASS = __bswap_16(DNS_ANSWER_CLASS_IN); + dns_answer->TTL = (uint32_t)0x00000000; /* no caching. Avoids DNS poisoning since this is a DNS hijack */ + dns_answer->RDLENGTH = __bswap_16(0x0004); /* 4 byte => size of an ipv4 address */ + dns_answer->RDATA = ip_resolved.addr; + + err = sendto(socket_fd, response, length+sizeof(dns_answer_t), 0, (struct sockaddr *)&client, client_len); + if (err < 0) { + ESP_LOGE(TAG, "UDP sendto failed: %d", err); + } + } + + taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. DNS daemon should not be taxing on the system */ + + } + close(socket_fd); + + vTaskDelete ( NULL ); +} + + + + diff --git a/dist/esp32-wifi-manager_0.0.1/src/dns_server.h b/dist/esp32-wifi-manager_0.0.1/src/dns_server.h new file mode 100644 index 00000000..d1da1f8c --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/dns_server.h @@ -0,0 +1,137 @@ +/* +Copyright (c) 2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file dns_server.h +@author Tony Pottier +@brief Defines an extremly basic DNS server for captive portal functionality. + +Contains the freeRTOS task for the DNS server that processes the requests. + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +@see http://www.zytrax.com/books/dns/ch15 +*/ + +#ifndef MAIN_DNS_SERVER_H_ +#define MAIN_DNS_SERVER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** 12 byte header, 64 byte domain name, 4 byte qtype/qclass. This NOT compliant with the RFC, but it's good enough for a captive portal + * if a DNS query is too big it just wont be processed. */ +#define DNS_QUERY_MAX_SIZE 80 + +/** Query + 2 byte ptr, 2 byte type, 2 byte class, 4 byte TTL, 2 byte len, 4 byte data */ +#define DNS_ANSWER_MAX_SIZE (DNS_QUERY_MAX_SIZE+16) + + +/** + * @brief RCODE values used in a DNS header message + */ +typedef enum dns_reply_code_t { + DNS_REPLY_CODE_NO_ERROR = 0, + DNS_REPLY_CODE_FORM_ERROR = 1, + DNS_REPLY_CODE_SERVER_FAILURE = 2, + DNS_REPLY_CODE_NON_EXISTANT_DOMAIN = 3, + DNS_REPLY_CODE_NOT_IMPLEMENTED = 4, + DNS_REPLY_CODE_REFUSED = 5, + DNS_REPLY_CODE_YXDOMAIN = 6, + DNS_REPLY_CODE_YXRRSET = 7, + DNS_REPLY_CODE_NXRRSET = 8 +}dns_reply_code_t; + + + +/** + * @brief OPCODE values used in a DNS header message + */ +typedef enum dns_opcode_code_t { + DNS_OPCODE_QUERY = 0, + DNS_OPCODE_IQUERY = 1, + DNS_OPCODE_STATUS = 2 +}dns_opcode_code_t; + + + +/** + * @brief Represents a 12 byte DNS header. + * __packed__ is needed to prevent potential unwanted memory alignments + */ +typedef struct __attribute__((__packed__)) dns_header_t{ + uint16_t ID; // identification number + uint8_t RD : 1; // recursion desired + uint8_t TC : 1; // truncated message + uint8_t AA : 1; // authoritive answer + uint8_t OPCode : 4; // message_type + uint8_t QR : 1; // query/response flag + uint8_t RCode : 4; // response code + uint8_t Z : 3; // its z! reserved + uint8_t RA : 1; // recursion available + uint16_t QDCount; // number of question entries + uint16_t ANCount; // number of answer entries + uint16_t NSCount; // number of authority entries + uint16_t ARCount; // number of resource entries +}dns_header_t; + + + +typedef enum dns_answer_type_t { + DNS_ANSWER_TYPE_A = 1, + DNS_ANSWER_TYPE_NS = 2, + DNS_ANSWER_TYPE_CNAME = 5, + DNS_ANSWER_TYPE_SOA = 6, + DNS_ANSWER_TYPE_WKS = 11, + DNS_ANSWER_TYPE_PTR = 12, + DNS_ANSWER_TYPE_MX = 15, + DNS_ANSWER_TYPE_SRV = 33, + DNS_ANSWER_TYPE_AAAA = 28 +}dns_answer_type_t; + +typedef enum dns_answer_class_t { + DNS_ANSWER_CLASS_IN = 1 +}dns_answer_class_t; + + + +typedef struct __attribute__((__packed__)) dns_answer_t{ + uint16_t NAME; /* for the sake of simplicity only 16 bit pointers are supported */ + uint16_t TYPE; /* Unsigned 16 bit value. The resource record types - determines the content of the RDATA field. */ + uint16_t CLASS; /* Class of response. */ + uint32_t TTL; /* The time in seconds that the record may be cached. A value of 0 indicates the record should not be cached. */ + uint16_t RDLENGTH; /* Unsigned 16-bit value that defines the length in bytes of the RDATA record. */ + uint32_t RDATA; /* For the sake of simplicity only ipv4 is supported, and as such it's a unsigned 32 bit */ +}dns_answer_t; + +void dns_server(void *pvParameters); +void dns_server_start(); +void dns_server_stop(); + + + +#ifdef __cplusplus +} +#endif + + +#endif /* MAIN_DNS_SERVER_H_ */ diff --git a/dist/esp32-wifi-manager_0.0.1/src/http_app.c b/dist/esp32-wifi-manager_0.0.1/src/http_app.c new file mode 100644 index 00000000..8dbc40ef --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/http_app.c @@ -0,0 +1,479 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file http_app.c +@author Tony Pottier +@brief Defines all functions necessary for the HTTP server to run. + +Contains the freeRTOS task for the HTTP listener and all necessary support +function to process requests, decode URLs, serve files, etc. etc. + +@note http_server task cannot run without the wifi_manager task! +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "esp_netif.h" +#include + +#include "wifi_manager.h" +#include "http_app.h" + + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "http_server"; + +/* @brief the HTTP server handle */ +static httpd_handle_t httpd_handle = NULL; + +/* function pointers to URI handlers that can be user made */ +esp_err_t (*custom_get_httpd_uri_handler)(httpd_req_t *r) = NULL; +esp_err_t (*custom_post_httpd_uri_handler)(httpd_req_t *r) = NULL; + +/* strings holding the URLs of the wifi manager */ +static char* http_root_url = NULL; +static char* http_redirect_url = NULL; +static char* http_js_url = NULL; +static char* http_css_url = NULL; +static char* http_connect_url = NULL; +static char* http_ap_url = NULL; +static char* http_status_url = NULL; + +/** + * @brief embedded binary data. + * @see file "component.mk" + * @see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#embedding-binary-data + */ +extern const uint8_t style_css_start[] asm("_binary_style_css_start"); +extern const uint8_t style_css_end[] asm("_binary_style_css_end"); +extern const uint8_t code_js_start[] asm("_binary_code_js_start"); +extern const uint8_t code_js_end[] asm("_binary_code_js_end"); +extern const uint8_t index_html_start[] asm("_binary_index_html_start"); +extern const uint8_t index_html_end[] asm("_binary_index_html_end"); + + +/* const httpd related values stored in ROM */ +const static char http_200_hdr[] = "200 OK"; +const static char http_302_hdr[] = "302 Found"; +const static char http_400_hdr[] = "400 Bad Request"; +const static char http_404_hdr[] = "404 Not Found"; +const static char http_503_hdr[] = "503 Service Unavailable"; +const static char http_location_hdr[] = "Location"; +const static char http_content_type_html[] = "text/html"; +const static char http_content_type_js[] = "text/javascript"; +const static char http_content_type_css[] = "text/css"; +const static char http_content_type_json[] = "application/json"; +const static char http_cache_control_hdr[] = "Cache-Control"; +const static char http_cache_control_no_cache[] = "no-store, no-cache, must-revalidate, max-age=0"; +const static char http_cache_control_cache[] = "public, max-age=31536000"; +const static char http_pragma_hdr[] = "Pragma"; +const static char http_pragma_no_cache[] = "no-cache"; + + + +esp_err_t http_app_set_handler_hook( httpd_method_t method, esp_err_t (*handler)(httpd_req_t *r) ){ + + if(method == HTTP_GET){ + custom_get_httpd_uri_handler = handler; + return ESP_OK; + } + else if(method == HTTP_POST){ + custom_post_httpd_uri_handler = handler; + return ESP_OK; + } + else{ + return ESP_ERR_INVALID_ARG; + } + +} + + +static esp_err_t http_server_delete_handler(httpd_req_t *req){ + + ESP_LOGI(TAG, "DELETE %s", req->uri); + + /* DELETE /connect.json */ + if(strcmp(req->uri, http_connect_url) == 0){ + wifi_manager_disconnect_async(); + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, NULL, 0); + } + else{ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + + return ESP_OK; +} + + +static esp_err_t http_server_post_handler(httpd_req_t *req){ + + + esp_err_t ret = ESP_OK; + + ESP_LOGI(TAG, "POST %s", req->uri); + + /* POST /connect.json */ + if(strcmp(req->uri, http_connect_url) == 0){ + + + /* buffers for the headers */ + size_t ssid_len = 0, password_len = 0; + char *ssid = NULL, *password = NULL; + + /* len of values provided */ + ssid_len = httpd_req_get_hdr_value_len(req, "X-Custom-ssid"); + password_len = httpd_req_get_hdr_value_len(req, "X-Custom-pwd"); + + + if(ssid_len && ssid_len <= MAX_SSID_SIZE && password_len && password_len <= MAX_PASSWORD_SIZE){ + + /* get the actual value of the headers */ + ssid = malloc(sizeof(char) * (ssid_len + 1)); + password = malloc(sizeof(char) * (password_len + 1)); + httpd_req_get_hdr_value_str(req, "X-Custom-ssid", ssid, ssid_len+1); + httpd_req_get_hdr_value_str(req, "X-Custom-pwd", password, password_len+1); + + wifi_config_t* config = wifi_manager_get_wifi_sta_config(); + memset(config, 0x00, sizeof(wifi_config_t)); + memcpy(config->sta.ssid, ssid, ssid_len); + memcpy(config->sta.password, password, password_len); + ESP_LOGI(TAG, "ssid: %s, password: %s", ssid, password); + ESP_LOGD(TAG, "http_server_post_handler: wifi_manager_connect_async() call"); + wifi_manager_connect_async(); + + /* free memory */ + free(ssid); + free(password); + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, NULL, 0); + + } + else{ + /* bad request the authentification header is not complete/not the correct format */ + httpd_resp_set_status(req, http_400_hdr); + httpd_resp_send(req, NULL, 0); + } + + } + else{ + + if(custom_post_httpd_uri_handler == NULL){ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + else{ + + /* if there's a hook, run it */ + ret = (*custom_post_httpd_uri_handler)(req); + } + } + + return ret; +} + + +static esp_err_t http_server_get_handler(httpd_req_t *req){ + + char* host = NULL; + size_t buf_len; + esp_err_t ret = ESP_OK; + + ESP_LOGD(TAG, "GET %s", req->uri); + + /* Get header value string length and allocate memory for length + 1, + * extra byte for null termination */ + buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1; + if (buf_len > 1) { + host = malloc(buf_len); + if(httpd_req_get_hdr_value_str(req, "Host", host, buf_len) != ESP_OK){ + /* if something is wrong we just 0 the whole memory */ + memset(host, 0x00, buf_len); + } + } + + /* determine if Host is from the STA IP address */ + wifi_manager_lock_sta_ip_string(portMAX_DELAY); + bool access_from_sta_ip = host != NULL?strstr(host, wifi_manager_get_sta_ip_string()):false; + wifi_manager_unlock_sta_ip_string(); + + + if (host != NULL && !strstr(host, DEFAULT_AP_IP) && !access_from_sta_ip) { + + /* Captive Portal functionality */ + /* 302 Redirect to IP of the access point */ + httpd_resp_set_status(req, http_302_hdr); + httpd_resp_set_hdr(req, http_location_hdr, http_redirect_url); + httpd_resp_send(req, NULL, 0); + + } + else{ + + /* GET / */ + if(strcmp(req->uri, http_root_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_html); + httpd_resp_send(req, (char*)index_html_start, index_html_end - index_html_start); + } + /* GET /code.js */ + else if(strcmp(req->uri, http_js_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_js); + httpd_resp_send(req, (char*)code_js_start, code_js_end - code_js_start); + } + /* GET /style.css */ + else if(strcmp(req->uri, http_css_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_css); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_cache); + httpd_resp_send(req, (char*)style_css_start, style_css_end - style_css_start); + } + /* GET /ap.json */ + else if(strcmp(req->uri, http_ap_url) == 0){ + + /* if we can get the mutex, write the last version of the AP list */ + if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){ + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + char* ap_buf = wifi_manager_get_ap_list_json(); + httpd_resp_send(req, ap_buf, strlen(ap_buf)); + wifi_manager_unlock_json_buffer(); + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + ESP_LOGE(TAG, "http_server_netconn_serve: GET /ap.json failed to obtain mutex"); + } + + /* request a wifi scan */ + wifi_manager_scan_async(); + } + /* GET /status.json */ + else if(strcmp(req->uri, http_status_url) == 0){ + + if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){ + char *buff = wifi_manager_get_ip_info_json(); + if(buff){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, buff, strlen(buff)); + wifi_manager_unlock_json_buffer(); + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + } + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + ESP_LOGE(TAG, "http_server_netconn_serve: GET /status.json failed to obtain mutex"); + } + } + else{ + + if(custom_get_httpd_uri_handler == NULL){ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + else{ + + /* if there's a hook, run it */ + ret = (*custom_get_httpd_uri_handler)(req); + } + } + + } + + /* memory clean up */ + if(host != NULL){ + free(host); + } + + return ret; + +} + +/* URI wild card for any GET request */ +static const httpd_uri_t http_server_get_request = { + .uri = "*", + .method = HTTP_GET, + .handler = http_server_get_handler +}; + +static const httpd_uri_t http_server_post_request = { + .uri = "*", + .method = HTTP_POST, + .handler = http_server_post_handler +}; + +static const httpd_uri_t http_server_delete_request = { + .uri = "*", + .method = HTTP_DELETE, + .handler = http_server_delete_handler +}; + + +void http_app_stop(){ + + if(httpd_handle != NULL){ + + + /* dealloc URLs */ + if(http_root_url) { + free(http_root_url); + http_root_url = NULL; + } + if(http_redirect_url){ + free(http_redirect_url); + http_redirect_url = NULL; + } + if(http_js_url){ + free(http_js_url); + http_js_url = NULL; + } + if(http_css_url){ + free(http_css_url); + http_css_url = NULL; + } + if(http_connect_url){ + free(http_connect_url); + http_connect_url = NULL; + } + if(http_ap_url){ + free(http_ap_url); + http_ap_url = NULL; + } + if(http_status_url){ + free(http_status_url); + http_status_url = NULL; + } + + /* stop server */ + httpd_stop(httpd_handle); + httpd_handle = NULL; + } +} + + +/** + * @brief helper to generate URLs of the wifi manager + */ +static char* http_app_generate_url(const char* page){ + + char* ret; + + int root_len = strlen(WEBAPP_LOCATION); + const size_t url_sz = sizeof(char) * ( (root_len+1) + ( strlen(page) + 1) ); + + ret = malloc(url_sz); + memset(ret, 0x00, url_sz); + strcpy(ret, WEBAPP_LOCATION); + ret = strcat(ret, page); + + return ret; +} + +void http_app_start(bool lru_purge_enable){ + + esp_err_t err; + + if(httpd_handle == NULL){ + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + /* this is an important option that isn't set up by default. + * We could register all URLs one by one, but this would not work while the fake DNS is active */ + config.uri_match_fn = httpd_uri_match_wildcard; + config.lru_purge_enable = lru_purge_enable; + + /* generate the URLs */ + if(http_root_url == NULL){ + int root_len = strlen(WEBAPP_LOCATION); + + /* all the pages */ + const char page_js[] = "code.js"; + const char page_css[] = "style.css"; + const char page_connect[] = "connect.json"; + const char page_ap[] = "ap.json"; + const char page_status[] = "status.json"; + + /* root url, eg "/" */ + const size_t http_root_url_sz = sizeof(char) * (root_len+1); + http_root_url = malloc(http_root_url_sz); + memset(http_root_url, 0x00, http_root_url_sz); + strcpy(http_root_url, WEBAPP_LOCATION); + + /* redirect url */ + size_t redirect_sz = 22 + root_len + 1; /* strlen(http://255.255.255.255) + strlen("/") + 1 for \0 */ + http_redirect_url = malloc(sizeof(char) * redirect_sz); + *http_redirect_url = '\0'; + + if(root_len == 1){ + snprintf(http_redirect_url, redirect_sz, "http://%s", DEFAULT_AP_IP); + } + else{ + snprintf(http_redirect_url, redirect_sz, "http://%s%s", DEFAULT_AP_IP, WEBAPP_LOCATION); + } + + /* generate the other pages URLs*/ + http_js_url = http_app_generate_url(page_js); + http_css_url = http_app_generate_url(page_css); + http_connect_url = http_app_generate_url(page_connect); + http_ap_url = http_app_generate_url(page_ap); + http_status_url = http_app_generate_url(page_status); + + } + + err = httpd_start(&httpd_handle, &config); + + if (err == ESP_OK) { + ESP_LOGI(TAG, "Registering URI handlers"); + httpd_register_uri_handler(httpd_handle, &http_server_get_request); + httpd_register_uri_handler(httpd_handle, &http_server_post_request); + httpd_register_uri_handler(httpd_handle, &http_server_delete_request); + } + } + +} diff --git a/dist/esp32-wifi-manager_0.0.1/src/http_app.h b/dist/esp32-wifi-manager_0.0.1/src/http_app.h new file mode 100644 index 00000000..53f9472a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/http_app.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file http_app.h +@author Tony Pottier +@brief Defines all functions necessary for the HTTP server to run. + +Contains the freeRTOS task for the HTTP listener and all necessary support +function to process requests, decode URLs, serve files, etc. etc. + +@note http_server task cannot run without the wifi_manager task! +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#ifndef HTTP_APP_H_INCLUDED +#define HTTP_APP_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** @brief Defines the URL where the wifi manager is located + * By default it is at the server root (ie "/"). If you wish to add your own webpages + * you may want to relocate the wifi manager to another URL, for instance /wifimanager + */ +#define WEBAPP_LOCATION CONFIG_WEBAPP_LOCATION + + +/** + * @brief spawns the http server + */ +void http_app_start(bool lru_purge_enable); + +/** + * @brief stops the http server + */ +void http_app_stop(); + +/** + * @brief sets a hook into the wifi manager URI handlers. Setting the handler to NULL disables the hook. + * @return ESP_OK in case of success, ESP_ERR_INVALID_ARG if the method is unsupported. + */ +esp_err_t http_app_set_handler_hook( httpd_method_t method, esp_err_t (*handler)(httpd_req_t *r) ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/esp32-wifi-manager_0.0.1/src/index.html b/dist/esp32-wifi-manager_0.0.1/src/index.html new file mode 100644 index 00000000..ce4bf146 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/index.html @@ -0,0 +1,136 @@ + + + + + + + + + esp32-wifi-manager + + +
+
+
+
+

Wi-Fi

+
+
+

Connected to:

+
+
+
+
+

Manual connect

+
+
ADD (HIDDEN) SSID
+
+

or choose a network...

+
+
+
Powered by esp32-wifi-manager.
+
+
+
+

Enter Details

+
+

Manual Connection

+
+ + +
+
+ + +
+
+
+
+

Enter Password

+
+

Password for

+
+ +
+
+ + +
+
+
+
+

Please wait...

+
+

Connecting to

+
+
+
+

You may lose wifi access while the esp32 recalibrates its radio. Please wait until your device automatically reconnects. This can take up to 30s.

+
+
+

Success!

+
+
+

Connection failed

+

Please double-check wifi password if any and make sure the access point has good signal.

+
+
+
+ +
+
+
+
+
+

+
+

+
+
+ +
+
+

IP Address

+
+
IP Address:
+
Subnet Mask:
+
Default Gateway:
+
+
+ +
+
+
+
+

Are you sure you would like to disconnect from this wifi?

+
+ + +
+
+
+
+
+
+
+
+

About this app...

+
+

+
+

esp32-wifi-manager, © 2017-2020, Tony Pottier
Licended under the MIT License.

+

+ This app would not be possible without the following libraries: +

+
    +
  • SpinKit, © 2015, Tobias Ahlin. Licensed under the MIT License.
  • +
  • jQuery, The jQuery Foundation. Licensed under the MIT License.
  • +
  • cJSON, © 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.
  • +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/json.c b/dist/esp32-wifi-manager_0.0.1/src/json.c new file mode 100644 index 00000000..d448711a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/json.c @@ -0,0 +1,143 @@ +/* +@file json.c +@brief handles very basic JSON with a minimal footprint on the system + +This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license: +Copyright (c) 2009 Dave Gamble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +@see https://github.com/DaveGamble/cJSON +*/ + +#include +#include +#include +#include +#include "json.h" + + +bool json_print_string(const unsigned char *input, unsigned char *output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + //output = ensure(output_buffer, sizeof("\"\""), hooks); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + if (strchr("\"\\\b\f\n\r\t", *input_pointer)) + { + /* one character escape sequence */ + escape_characters++; + } + else if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + /* in the original cJSON it is possible to realloc here in case output buffer is too small. + * This is overkill for an embedded system. */ + output = output_buffer; + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + diff --git a/dist/esp32-wifi-manager_0.0.1/src/json.h b/dist/esp32-wifi-manager_0.0.1/src/json.h new file mode 100644 index 00000000..4c1c8c75 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/json.h @@ -0,0 +1,47 @@ +/* +@file json.h +@brief handles very basic JSON with a minimal footprint on the system + +This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license: +Copyright (c) 2009 Dave Gamble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +@see https://github.com/DaveGamble/cJSON +*/ + +#ifndef JSON_H_INCLUDED +#define JSON_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Render the cstring provided to a JSON escaped version that can be printed. + * @param input the input buffer to be escaped. + * @param output_buffer the output buffer to write to. You must ensure it is big enough to contain the final string. + * @see cJSON equivlaent static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) + */ +bool json_print_string(const unsigned char *input, unsigned char *output_buffer); + +#ifdef __cplusplus +} +#endif + +#endif /* JSON_H_INCLUDED */ diff --git a/dist/esp32-wifi-manager_0.0.1/src/lock.svg b/dist/esp32-wifi-manager_0.0.1/src/lock.svg new file mode 100644 index 00000000..f7ebc7e9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/lock.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/nvs_sync.c b/dist/esp32-wifi-manager_0.0.1/src/nvs_sync.c new file mode 100644 index 00000000..4ceed8bd --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/nvs_sync.c @@ -0,0 +1,79 @@ +/** +Copyright (c) 2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file nvs_sync.c +@author Tony Pottier +@brief Exposes a simple API to synchronize NVS memory read and writes + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include "nvs_sync.h" + + +static SemaphoreHandle_t nvs_sync_mutex = NULL; + +esp_err_t nvs_sync_create(){ + if(nvs_sync_mutex == NULL){ + + nvs_sync_mutex = xSemaphoreCreateMutex(); + + if(nvs_sync_mutex){ + return ESP_OK; + } + else{ + return ESP_FAIL; + } + } + else{ + return ESP_OK; + } +} + +void nvs_sync_free(){ + if(nvs_sync_mutex != NULL){ + vSemaphoreDelete( nvs_sync_mutex ); + nvs_sync_mutex = NULL; + } +} + +bool nvs_sync_lock(TickType_t xTicksToWait){ + if(nvs_sync_mutex){ + if( xSemaphoreTake( nvs_sync_mutex, xTicksToWait ) == pdTRUE ) { + return true; + } + else{ + return false; + } + } + else{ + return false; + } +} + +void nvs_sync_unlock(){ + xSemaphoreGive( nvs_sync_mutex ); +} \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/nvs_sync.h b/dist/esp32-wifi-manager_0.0.1/src/nvs_sync.h new file mode 100644 index 00000000..e03396e5 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/nvs_sync.h @@ -0,0 +1,76 @@ +/** +Copyright (c) 2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file nvs_sync.h +@author Tony Pottier +@brief Exposes a simple API to synchronize NVS memory read and writes + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + + + +#ifndef WIFI_MANAGER_NVS_SYNC_H_INCLUDED +#define WIFI_MANAGER_NVS_SYNC_H_INCLUDED + +#include /* for type bool */ +#include /* for TickType_t */ +#include /* for esp_err_t */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Attempts to get hold of the NVS semaphore for a set amount of ticks. + * @note If you are uncertain about the number of ticks to wait use portMAX_DELAY. + * @return true on a succesful lock, false otherwise + */ +bool nvs_sync_lock(TickType_t xTicksToWait); + + +/** + * @brief Releases the NVS semaphore + */ +void nvs_sync_unlock(); + + +/** + * @brief Create the NVS semaphore + * @return ESP_OK: success or if the semaphore already exists + * ESP_FAIL: failure + */ +esp_err_t nvs_sync_create(); + +/** + * @brief Frees memory associated with the NVS semaphore + * @warning Do not delete a semaphore that has tasks blocked on it (tasks that are in the Blocked state waiting for the semaphore to become available). + */ +void nvs_sync_free(); + + +#ifdef __cplusplus +} +#endif + +#endif /* WIFI_MANAGER_NVS_SYNC_H_INCLUDED */ \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/settings.svg b/dist/esp32-wifi-manager_0.0.1/src/settings.svg new file mode 100644 index 00000000..19cacede --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/settings.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/status b/dist/esp32-wifi-manager_0.0.1/src/status new file mode 100644 index 00000000..3824a535 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/status @@ -0,0 +1 @@ +{"ssid":"zodmgbbq","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":0} \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/style.css b/dist/esp32-wifi-manager_0.0.1/src/style.css new file mode 100644 index 00000000..5c83cc2f --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/style.css @@ -0,0 +1,248 @@ +body { + background-color: #eee; + border: 0; + margin: 0; + font: 1.1em tahoma, arial, sans-serif; +} +a { + color: darkblue; + transition: color .2s ease-out; + text-decoration: none +} +a:hover { + color: red; +} +input { + display: none; + font: 1.1em tahoma, arial, sans-serif; +} +input:focus, +select:focus, +textarea:focus, +button:focus { + outline: none; +} +input[type="button"] { + width: 100px; + padding: 5px; + text-align: center; + display: block; +} +p { + padding: 10px; +} +#credits { + display: none; +} +#app {} #app-wrap {} #disconnect { + width: 150px; +} +.diag-box { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + height: 100%; + width: 100%; + display: none; +} +.diag-box-win { + position: absolute; + left: 10%; + width: 80%; + text-align: center; + border: 2px outset #888; + background-color: #fff; + border-radius: 10px; + top: 20%; +} +.blur { + -webkit-filter: blur(2px); + -moz-filter: blur(2px); + -ms-filter: blur(2px); + -o-filter: blur(2px); + filter: blur(2px); +} +.ape { + margin-left: 20px; + padding: 10px 0px 10px 10px; +} +.ape:hover { + cursor: pointer; +} +.brdb { + border-bottom: 1px solid #888; +} +header { + background-color: #fff; + border-bottom: 1px solid #888; +} +section { + background-color: #fff; + border-bottom: 1px solid #888; + border-top: 1px solid #888; +} +h1 { + display: block; + text-align: center; + margin: 0; + padding: 15px; + font-size: 1.4em +} +h2 { + margin: 0; + margin-top: 20px; + padding: 10px; + text-transform: uppercase; + color: #888; + font-size: 1.0em +} +h3 { + margin: 0; + text-align: center; + padding: 20px 0px 20px 0px; +} +.gr { + color: green; +} +.rd { + color: red; +} +#wifi-status { + display: none; +} +#connect { + display: none; +} +#connect_manual { + display: none; +} +#manual_ssid { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +#manual_pwd { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +#pwd { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +.buttons { + padding: 15px; +} +#join { + float: right; +} +#manual_join { + float: right; +} +#yes-disconnect { + display: inline-block; + margin-left: 20px; +} +#no-disconnect { + display: inline-block; +} +.ctr { + margin: 0 auto; +} +.tctr { + text-align: center; +} +#connect-wait { + display: none; +} +#connect-success { + display: none; +} +#connect-fail { + display: none; +} +#connect-details { + display: none; +} +.fr { + float: right; + margin-right: 20px; +} +.w0 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPHBhdGggZD0iTTUsMTNMNywxNUM5Ljc2LDEyLjI0IDE0LjI0LDEyLjI0IDE3LDE1TDE5LDEzQzE1LjE0LDkuMTQgOC44Nyw5LjE0IDUsMTNaIiBzdHlsZT0iZmlsbDogYmxhY2s7Ii8+CjxwYXRoIGQ9Ik05LDE3TDEyLDIwTDE1LDE3QzEzLjM1LDE1LjM0IDEwLjY2LDE1LjM0IDksMTdaIiBzdHlsZT0iZmlsbDogYmxhY2s7Ii8+Cjwvc3ZnPgo=') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w1 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPHBhdGggZD0iTTksMTdMMTIsMjBMMTUsMTdDMTMuMzUsMTUuMzQgMTAuNjYsMTUuMzQgOSwxN1oiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPC9zdmc+Cg==') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w2 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNOSwxN0wxMiwyMEwxNSwxN0MxMy4zNSwxNS4zNCAxMC42NiwxNS4zNCA5LDE3WiIgc3R5bGU9ImZpbGw6IGJsYWNrOyIvPgo8L3N2Zz4K') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w3 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNOSwxN0wxMiwyMEwxNSwxN0MxMy4zNSwxNS4zNCAxMC42NiwxNS4zNCA5LDE3WiIgc3R5bGU9ImZpbGw6IGdyYXk7Ii8+Cjwvc3ZnPgo=') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.pw { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIHN0eWxlPSJmaWxsOiBibGFjazsiIGQ9Ik0xOCA4aC0xVjZjMC0yLjc2LTIuMjQtNS01LTVTNyAzLjI0IDcgNnYySDZjLTEuMSAwLTIgLjktMiAydjEwYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWMTBjMC0xLjEtLjktMi0yLTJ6bS02IDljLTEuMSAwLTItLjktMi0ycy45LTIgMi0yIDIgLjkgMiAyLS45IDItMiAyem0zLjEtOUg4LjlWNmMwLTEuNzEgMS4zOS0zLjEgMy4xLTMuMSAxLjcxIDAgMy4xIDEuMzkgMy4xIDMuMXYyeiI+PC9wYXRoPgo8L3N2Zz4=') no-repeat right top; + height: 24px; + margin-right: 30px; +} +/* SpinKit is licensed under the MIT License. Copyright (c) 2015 Tobias Ahlin */ + +.spinner { + width: 40px; + height: 40px; + position: relative; + margin: 100px auto; +} +.double-bounce1, +.double-bounce2 { + width: 100%; + height: 100%; + border-radius: 50%; + background-color: #333; + opacity: 0.6; + position: absolute; + top: 0; + left: 0; + -webkit-animation: sk-bounce 2.0s infinite ease-in-out; + animation: sk-bounce 2.0s infinite ease-in-out; +} +.double-bounce2 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} +@-webkit-keyframes sk-bounce { + 0%, 100% { + -webkit-transform: scale(0.0) + } + 50% { + -webkit-transform: scale(1.0) + } +} +@keyframes sk-bounce { + 0%, 100% { + transform: scale(0.0); + -webkit-transform: scale(0.0); + } + 50% { + transform: scale(1.0); + -webkit-transform: scale(1.0); + } +} +/* end of SpinKit */ \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.1/src/wifi0.svg b/dist/esp32-wifi-manager_0.0.1/src/wifi0.svg new file mode 100644 index 00000000..394a6495 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/wifi0.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.1/src/wifi1.svg b/dist/esp32-wifi-manager_0.0.1/src/wifi1.svg new file mode 100644 index 00000000..c64211bf --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/wifi1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.1/src/wifi2.svg b/dist/esp32-wifi-manager_0.0.1/src/wifi2.svg new file mode 100644 index 00000000..033b9de9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/wifi2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.1/src/wifi3.svg b/dist/esp32-wifi-manager_0.0.1/src/wifi3.svg new file mode 100644 index 00000000..7cd5667e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/wifi3.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.1/src/wifi_manager.c b/dist/esp32-wifi-manager_0.0.1/src/wifi_manager.c new file mode 100644 index 00000000..3d11a940 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/wifi_manager.c @@ -0,0 +1,1336 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file wifi_manager.c +@author Tony Pottier +@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis + +Contains the freeRTOS task and all necessary support + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include +#include "esp_system.h" +#include +#include +#include +#include +#include +#include +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_wifi_types.h" +#include "esp_log.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "mdns.h" +#include "lwip/api.h" +#include "lwip/err.h" +#include "lwip/netdb.h" +#include "lwip/ip4_addr.h" + + +#include "json.h" +#include "dns_server.h" +#include "nvs_sync.h" +#include "wifi_manager.h" + + + +/* objects used to manipulate the main queue of events */ +QueueHandle_t wifi_manager_queue; + +/* @brief software timer to wait between each connection retry. + * There is no point hogging a hardware timer for a functionality like this which only needs to be 'accurate enough' */ +TimerHandle_t wifi_manager_retry_timer = NULL; + +/* @brief software timer that will trigger shutdown of the AP after a succesful STA connection + * There is no point hogging a hardware timer for a functionality like this which only needs to be 'accurate enough' */ +TimerHandle_t wifi_manager_shutdown_ap_timer = NULL; + +SemaphoreHandle_t wifi_manager_json_mutex = NULL; +SemaphoreHandle_t wifi_manager_sta_ip_mutex = NULL; +char *wifi_manager_sta_ip = NULL; +uint16_t ap_num = MAX_AP_NUM; +wifi_ap_record_t *accessp_records; +char *accessp_json = NULL; +char *ip_info_json = NULL; +wifi_config_t* wifi_manager_config_sta = NULL; + +/* @brief Array of callback function pointers */ +void (**cb_ptr_arr)(void*) = NULL; + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "wifi_manager"; + +/* @brief task handle for the main wifi_manager task */ +static TaskHandle_t task_wifi_manager = NULL; + +/* @brief netif object for the STATION */ +static esp_netif_t* esp_netif_sta = NULL; + +/* @brief netif object for the ACCESS POINT */ +static esp_netif_t* esp_netif_ap = NULL; + +/** + * The actual WiFi settings in use + */ +struct wifi_settings_t wifi_settings = { + .ap_ssid = DEFAULT_AP_SSID, + .ap_pwd = DEFAULT_AP_PASSWORD, + .ap_channel = DEFAULT_AP_CHANNEL, + .ap_ssid_hidden = DEFAULT_AP_SSID_HIDDEN, + .ap_bandwidth = DEFAULT_AP_BANDWIDTH, + .sta_only = DEFAULT_STA_ONLY, + .sta_power_save = DEFAULT_STA_POWER_SAVE, + .sta_static_ip = 0, +}; + +const char wifi_manager_nvs_namespace[] = "espwifimgr"; + +static EventGroupHandle_t wifi_manager_event_group; + +/* @brief indicate that the ESP32 is currently connected. */ +const int WIFI_MANAGER_WIFI_CONNECTED_BIT = BIT0; + +const int WIFI_MANAGER_AP_STA_CONNECTED_BIT = BIT1; + +/* @brief Set automatically once the SoftAP is started */ +const int WIFI_MANAGER_AP_STARTED_BIT = BIT2; + +/* @brief When set, means a client requested to connect to an access point.*/ +const int WIFI_MANAGER_REQUEST_STA_CONNECT_BIT = BIT3; + +/* @brief This bit is set automatically as soon as a connection was lost */ +const int WIFI_MANAGER_STA_DISCONNECT_BIT = BIT4; + +/* @brief When set, means the wifi manager attempts to restore a previously saved connection at startup. */ +const int WIFI_MANAGER_REQUEST_RESTORE_STA_BIT = BIT5; + +/* @brief When set, means a client requested to disconnect from currently connected AP. */ +const int WIFI_MANAGER_REQUEST_WIFI_DISCONNECT_BIT = BIT6; + +/* @brief When set, means a scan is in progress */ +const int WIFI_MANAGER_SCAN_BIT = BIT7; + +/* @brief When set, means user requested for a disconnect */ +const int WIFI_MANAGER_REQUEST_DISCONNECT_BIT = BIT8; + + + +void wifi_manager_timer_retry_cb( TimerHandle_t xTimer ){ + + ESP_LOGI(TAG, "Retry Timer Tick! Sending ORDER_CONNECT_STA with reason CONNECTION_REQUEST_AUTO_RECONNECT"); + + /* stop the timer */ + xTimerStop( xTimer, (TickType_t) 0 ); + + /* Attempt to reconnect */ + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_AUTO_RECONNECT); + +} + +void wifi_manager_timer_shutdown_ap_cb( TimerHandle_t xTimer){ + + /* stop the timer */ + xTimerStop( xTimer, (TickType_t) 0 ); + + /* Attempt to shutdown AP */ + wifi_manager_send_message(WM_ORDER_STOP_AP, NULL); +} + +void wifi_manager_scan_async(){ + wifi_manager_send_message(WM_ORDER_START_WIFI_SCAN, NULL); +} + +void wifi_manager_disconnect_async(){ + wifi_manager_send_message(WM_ORDER_DISCONNECT_STA, NULL); +} + + +void wifi_manager_start(){ + + /* disable the default wifi logging */ + esp_log_level_set("wifi", ESP_LOG_NONE); + + /* initialize flash memory */ + nvs_flash_init(); + ESP_ERROR_CHECK(nvs_sync_create()); /* semaphore for thread synchronization on NVS memory */ + + /* memory allocation */ + wifi_manager_queue = xQueueCreate( 3, sizeof( queue_message) ); + wifi_manager_json_mutex = xSemaphoreCreateMutex(); + accessp_records = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * MAX_AP_NUM); + accessp_json = (char*)malloc(MAX_AP_NUM * JSON_ONE_APP_SIZE + 4); /* 4 bytes for json encapsulation of "[\n" and "]\0" */ + wifi_manager_clear_access_points_json(); + ip_info_json = (char*)malloc(sizeof(char) * JSON_IP_INFO_SIZE); + wifi_manager_clear_ip_info_json(); + wifi_manager_config_sta = (wifi_config_t*)malloc(sizeof(wifi_config_t)); + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + memset(&wifi_settings.sta_static_ip_config, 0x00, sizeof(esp_netif_ip_info_t)); + cb_ptr_arr = malloc(sizeof(void (*)(void*)) * WM_MESSAGE_CODE_COUNT); + for(int i=0; ista.ssid) != 0){ + /* different ssid or ssid does not exist in flash: save new ssid */ + esp_err = nvs_set_blob(handle, "ssid", wifi_manager_config_sta->sta.ssid, 32); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + ESP_LOGI(TAG, "wifi_manager_wrote wifi_sta_config: ssid:%s",wifi_manager_config_sta->sta.ssid); + + } + + sz = sizeof(tmp_conf.sta.password); + esp_err = nvs_get_blob(handle, "password", tmp_conf.sta.password, &sz); + if( (esp_err == ESP_OK || esp_err == ESP_ERR_NVS_NOT_FOUND) && strcmp( (char*)tmp_conf.sta.password, (char*)wifi_manager_config_sta->sta.password) != 0){ + /* different password or password does not exist in flash: save new password */ + esp_err = nvs_set_blob(handle, "password", wifi_manager_config_sta->sta.password, 64); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + ESP_LOGI(TAG, "wifi_manager_wrote wifi_sta_config: password:%s",wifi_manager_config_sta->sta.password); + } + + sz = sizeof(tmp_settings); + esp_err = nvs_get_blob(handle, "settings", &tmp_settings, &sz); + if( (esp_err == ESP_OK || esp_err == ESP_ERR_NVS_NOT_FOUND) && + ( + strcmp( (char*)tmp_settings.ap_ssid, (char*)wifi_settings.ap_ssid) != 0 || + strcmp( (char*)tmp_settings.ap_pwd, (char*)wifi_settings.ap_pwd) != 0 || + tmp_settings.ap_ssid_hidden != wifi_settings.ap_ssid_hidden || + tmp_settings.ap_bandwidth != wifi_settings.ap_bandwidth || + tmp_settings.sta_only != wifi_settings.sta_only || + tmp_settings.sta_power_save != wifi_settings.sta_power_save || + tmp_settings.ap_channel != wifi_settings.ap_channel + ) + ){ + esp_err = nvs_set_blob(handle, "settings", &wifi_settings, sizeof(wifi_settings)); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_ssid: %s",wifi_settings.ap_ssid); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_pwd: %s",wifi_settings.ap_pwd); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_channel: %i",wifi_settings.ap_channel); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_hidden (1 = yes): %i",wifi_settings.ap_ssid_hidden); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_bandwidth (1 = 20MHz, 2 = 40MHz): %i",wifi_settings.ap_bandwidth); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_only (0 = APSTA, 1 = STA when connected): %i",wifi_settings.sta_only); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_power_save (1 = yes): %i",wifi_settings.sta_power_save); + } + + if(change){ + esp_err = nvs_commit(handle); + } + else{ + ESP_LOGI(TAG, "Wifi config was not saved to flash because no change has been detected."); + } + + if (esp_err != ESP_OK) return esp_err; + + nvs_close(handle); + nvs_sync_unlock(); + + } + else{ + ESP_LOGE(TAG, "wifi_manager_save_sta_config failed to acquire nvs_sync mutex"); + } + + return ESP_OK; +} + +bool wifi_manager_fetch_wifi_sta_config(){ + + nvs_handle handle; + esp_err_t esp_err; + if(nvs_sync_lock( portMAX_DELAY )){ + + esp_err = nvs_open(wifi_manager_nvs_namespace, NVS_READONLY, &handle); + + if(esp_err != ESP_OK){ + nvs_sync_unlock(); + return false; + } + + if(wifi_manager_config_sta == NULL){ + wifi_manager_config_sta = (wifi_config_t*)malloc(sizeof(wifi_config_t)); + } + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + + /* allocate buffer */ + size_t sz = sizeof(wifi_settings); + uint8_t *buff = (uint8_t*)malloc(sizeof(uint8_t) * sz); + memset(buff, 0x00, sizeof(sz)); + + /* ssid */ + sz = sizeof(wifi_manager_config_sta->sta.ssid); + esp_err = nvs_get_blob(handle, "ssid", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(wifi_manager_config_sta->sta.ssid, buff, sz); + + /* password */ + sz = sizeof(wifi_manager_config_sta->sta.password); + esp_err = nvs_get_blob(handle, "password", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(wifi_manager_config_sta->sta.password, buff, sz); + + /* settings */ + sz = sizeof(wifi_settings); + esp_err = nvs_get_blob(handle, "settings", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(&wifi_settings, buff, sz); + + free(buff); + nvs_close(handle); + nvs_sync_unlock(); + + + ESP_LOGI(TAG, "wifi_manager_fetch_wifi_sta_config: ssid:%s password:%s",wifi_manager_config_sta->sta.ssid,wifi_manager_config_sta->sta.password); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_ssid:%s",wifi_settings.ap_ssid); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_pwd:%s",wifi_settings.ap_pwd); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_channel:%i",wifi_settings.ap_channel); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_hidden (1 = yes):%i",wifi_settings.ap_ssid_hidden); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_bandwidth (1 = 20MHz, 2 = 40MHz)%i",wifi_settings.ap_bandwidth); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_only (0 = APSTA, 1 = STA when connected):%i",wifi_settings.sta_only); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_power_save (1 = yes):%i",wifi_settings.sta_power_save); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_static_ip (0 = dhcp client, 1 = static ip):%i",wifi_settings.sta_static_ip); + + return wifi_manager_config_sta->sta.ssid[0] != '\0'; + + + } + else{ + return false; + } + +} + + +void wifi_manager_clear_ip_info_json(){ + strcpy(ip_info_json, "{}\n"); +} + + +void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code){ + + wifi_config_t *config = wifi_manager_get_wifi_sta_config(); + if(config){ + + const char *ip_info_json_format = ",\"ip\":\"%s\",\"netmask\":\"%s\",\"gw\":\"%s\",\"urc\":%d}\n"; + + memset(ip_info_json, 0x00, JSON_IP_INFO_SIZE); + + /* to avoid declaring a new buffer we copy the data directly into the buffer at its correct address */ + strcpy(ip_info_json, "{\"ssid\":"); + json_print_string(config->sta.ssid, (unsigned char*)(ip_info_json+strlen(ip_info_json)) ); + + size_t ip_info_json_len = strlen(ip_info_json); + size_t remaining = JSON_IP_INFO_SIZE - ip_info_json_len; + if(update_reason_code == UPDATE_CONNECTION_OK){ + /* rest of the information is copied after the ssid */ + esp_netif_ip_info_t ip_info; + ESP_ERROR_CHECK(esp_netif_get_ip_info(esp_netif_sta, &ip_info)); + + char ip[IP4ADDR_STRLEN_MAX]; /* note: IP4ADDR_STRLEN_MAX is defined in lwip */ + char gw[IP4ADDR_STRLEN_MAX]; + char netmask[IP4ADDR_STRLEN_MAX]; + + esp_ip4addr_ntoa(&ip_info.ip, ip, IP4ADDR_STRLEN_MAX); + esp_ip4addr_ntoa(&ip_info.gw, gw, IP4ADDR_STRLEN_MAX); + esp_ip4addr_ntoa(&ip_info.netmask, netmask, IP4ADDR_STRLEN_MAX); + + + snprintf( (ip_info_json + ip_info_json_len), remaining, ip_info_json_format, + ip, + netmask, + gw, + (int)update_reason_code); + } + else{ + /* notify in the json output the reason code why this was updated without a connection */ + snprintf( (ip_info_json + ip_info_json_len), remaining, ip_info_json_format, + "0", + "0", + "0", + (int)update_reason_code); + } + } + else{ + wifi_manager_clear_ip_info_json(); + } + + +} + + +void wifi_manager_clear_access_points_json(){ + strcpy(accessp_json, "[]\n"); +} +void wifi_manager_generate_acess_points_json(){ + + strcpy(accessp_json, "["); + + + const char oneap_str[] = ",\"chan\":%d,\"rssi\":%d,\"auth\":%d}%c\n"; + + /* stack buffer to hold on to one AP until it's copied over to accessp_json */ + char one_ap[JSON_ONE_APP_SIZE]; + for(int i=0; i and + * . + * + * Another thing deserves our attention is that the default behavior of LwIP is to abort all TCP socket connections on + * receiving the disconnect. Most of time it is not a problem. However, for some special application, this may not be + * what they want, consider following scenarios: + * + * The application creates a TCP connection to maintain the application-level keep-alive data that is sent out + * every 60 seconds. + * + * Due to certain reasons, the Wi-Fi connection is cut off, and the is raised. + * According to the current implementation, all TCP connections will be removed and the keep-alive socket will be + * in a wrong status. However, since the application designer believes that the network layer should NOT care about + * this error at the Wi-Fi layer, the application does not close the socket. + * + * Five seconds later, the Wi-Fi connection is restored because esp_wifi_connect() is called in the application + * event callback function. Moreover, the station connects to the same AP and gets the same IPV4 address as before. + * + * Sixty seconds later, when the application sends out data with the keep-alive socket, the socket returns an error + * and the application closes the socket and re-creates it when necessary. + * + * In above scenario, ideally, the application sockets and the network layer should not be affected, since the Wi-Fi + * connection only fails temporarily and recovers very quickly. The application can enable “Keep TCP connections when + * IP changed” via LwIP menuconfig.*/ + case WIFI_EVENT_STA_DISCONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED"); + + wifi_event_sta_disconnected_t* wifi_event_sta_disconnected = (wifi_event_sta_disconnected_t*)malloc(sizeof(wifi_event_sta_disconnected_t)); + *wifi_event_sta_disconnected = *( (wifi_event_sta_disconnected_t*)event_data ); + + /* if a DISCONNECT message is posted while a scan is in progress this scan will NEVER end, causing scan to never work again. For this reason SCAN_BIT is cleared too */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT | WIFI_MANAGER_SCAN_BIT); + + /* post disconnect event with reason code */ + wifi_manager_send_message(WM_EVENT_STA_DISCONNECTED, (void*)wifi_event_sta_disconnected ); + break; + + /* This event arises when the AP to which the station is connected changes its authentication mode, e.g., from no auth + * to WPA. Upon receiving this event, the event task will do nothing. Generally, the application event callback does + * not need to handle this either. */ + case WIFI_EVENT_STA_AUTHMODE_CHANGE: + ESP_LOGI(TAG, "WIFI_EVENT_STA_AUTHMODE_CHANGE"); + break; + + case WIFI_EVENT_AP_START: + ESP_LOGI(TAG, "WIFI_EVENT_AP_START"); + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STARTED_BIT); + break; + + case WIFI_EVENT_AP_STOP: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STOP"); + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_AP_STARTED_BIT); + break; + + /* Every time a station is connected to ESP32 AP, the will arise. Upon receiving this + * event, the event task will do nothing, and the application callback can also ignore it. However, you may want + * to do something, for example, to get the info of the connected STA, etc. */ + case WIFI_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STACONNECTED"); + break; + + /* This event can happen in the following scenarios: + * The application calls esp_wifi_disconnect(), or esp_wifi_deauth_sta(), to manually disconnect the station. + * The Wi-Fi driver kicks off the station, e.g. because the AP has not received any packets in the past five minutes, etc. + * The station kicks off the AP. + * When this event happens, the event task will do nothing, but the application event callback needs to do + * something, e.g., close the socket which is related to this station, etc. */ + case WIFI_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STADISCONNECTED"); + break; + + /* This event is disabled by default. The application can enable it via API esp_wifi_set_event_mask(). + * When this event is enabled, it will be raised each time the AP receives a probe request. */ + case WIFI_EVENT_AP_PROBEREQRECVED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_PROBEREQRECVED"); + break; + + } /* end switch */ + } + else if(event_base == IP_EVENT){ + + switch(event_id){ + + /* This event arises when the DHCP client successfully gets the IPV4 address from the DHCP server, + * or when the IPV4 address is changed. The event means that everything is ready and the application can begin + * its tasks (e.g., creating sockets). + * The IPV4 may be changed because of the following reasons: + * The DHCP client fails to renew/rebind the IPV4 address, and the station’s IPV4 is reset to 0. + * The DHCP client rebinds to a different address. + * The static-configured IPV4 address is changed. + * Whether the IPV4 address is changed or NOT is indicated by field ip_change of ip_event_got_ip_t. + * The socket is based on the IPV4 address, which means that, if the IPV4 changes, all sockets relating to this + * IPV4 will become abnormal. Upon receiving this event, the application needs to close all sockets and recreate + * the application when the IPV4 changes to a valid one. */ + case IP_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "IP_EVENT_STA_GOT_IP"); + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT); + ip_event_got_ip_t* ip_event_got_ip = (ip_event_got_ip_t*)malloc(sizeof(ip_event_got_ip_t)); + *ip_event_got_ip = *( (ip_event_got_ip_t*)event_data ); + wifi_manager_send_message(WM_EVENT_STA_GOT_IP, (void*)(ip_event_got_ip) ); + break; + + /* This event arises when the IPV6 SLAAC support auto-configures an address for the ESP32, or when this address changes. + * The event means that everything is ready and the application can begin its tasks (e.g., creating sockets). */ + case IP_EVENT_GOT_IP6: + ESP_LOGI(TAG, "IP_EVENT_GOT_IP6"); + break; + + /* This event arises when the IPV4 address become invalid. + * IP_STA_LOST_IP doesn’t arise immediately after the WiFi disconnects, instead it starts an IPV4 address lost timer, + * if the IPV4 address is got before ip lost timer expires, IP_EVENT_STA_LOST_IP doesn’t happen. Otherwise, the event + * arises when IPV4 address lost timer expires. + * Generally the application don’t need to care about this event, it is just a debug event to let the application + * know that the IPV4 address is lost. */ + case IP_EVENT_STA_LOST_IP: + ESP_LOGI(TAG, "IP_EVENT_STA_LOST_IP"); + break; + + } + } + +} + + +wifi_config_t* wifi_manager_get_wifi_sta_config(){ + return wifi_manager_config_sta; +} + + +void wifi_manager_connect_async(){ + /* in order to avoid a false positive on the front end app we need to quickly flush the ip json + * There'se a risk the front end sees an IP or a password error when in fact + * it's a remnant from a previous connection + */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_clear_ip_info_json(); + wifi_manager_unlock_json_buffer(); + } + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_USER); +} + + +char* wifi_manager_get_ip_info_json(){ + return ip_info_json; +} + + +void wifi_manager_destroy(){ + + vTaskDelete(task_wifi_manager); + task_wifi_manager = NULL; + + /* heap buffers */ + free(accessp_records); + accessp_records = NULL; + free(accessp_json); + accessp_json = NULL; + free(ip_info_json); + ip_info_json = NULL; + free(wifi_manager_sta_ip); + wifi_manager_sta_ip = NULL; + if(wifi_manager_config_sta){ + free(wifi_manager_config_sta); + wifi_manager_config_sta = NULL; + } + + /* RTOS objects */ + vSemaphoreDelete(wifi_manager_json_mutex); + wifi_manager_json_mutex = NULL; + vSemaphoreDelete(wifi_manager_sta_ip_mutex); + wifi_manager_sta_ip_mutex = NULL; + vEventGroupDelete(wifi_manager_event_group); + wifi_manager_event_group = NULL; + vQueueDelete(wifi_manager_queue); + wifi_manager_queue = NULL; + + +} + + +void wifi_manager_filter_unique( wifi_ap_record_t * aplist, uint16_t * aps) { + int total_unique; + wifi_ap_record_t * first_free; + total_unique=*aps; + + first_free=NULL; + + for(int i=0; i<*aps-1;i++) { + wifi_ap_record_t * ap = &aplist[i]; + + /* skip the previously removed APs */ + if (ap->ssid[0] == 0) continue; + + /* remove the identical SSID+authmodes */ + for(int j=i+1; j<*aps;j++) { + wifi_ap_record_t * ap1 = &aplist[j]; + if ( (strcmp((const char *)ap->ssid, (const char *)ap1->ssid)==0) && + (ap->authmode == ap1->authmode) ) { /* same SSID, different auth mode is skipped */ + /* save the rssi for the display */ + if ((ap1->rssi) > (ap->rssi)) ap->rssi=ap1->rssi; + /* clearing the record */ + memset(ap1,0, sizeof(wifi_ap_record_t)); + } + } + } + /* reorder the list so APs follow each other in the list */ + for(int i=0; i<*aps;i++) { + wifi_ap_record_t * ap = &aplist[i]; + /* skipping all that has no name */ + if (ap->ssid[0] == 0) { + /* mark the first free slot */ + if (first_free==NULL) first_free=ap; + total_unique--; + continue; + } + if (first_free!=NULL) { + memcpy(first_free, ap, sizeof(wifi_ap_record_t)); + memset(ap,0, sizeof(wifi_ap_record_t)); + /* find the next free slot */ + for(int j=0; j<*aps;j++) { + if (aplist[j].ssid[0]==0) { + first_free=&aplist[j]; + break; + } + } + } + } + /* update the length of the list */ + *aps = total_unique; +} + + +BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param){ + queue_message msg; + msg.code = code; + msg.param = param; + return xQueueSendToFront( wifi_manager_queue, &msg, portMAX_DELAY); +} + +BaseType_t wifi_manager_send_message(message_code_t code, void *param){ + queue_message msg; + msg.code = code; + msg.param = param; + return xQueueSend( wifi_manager_queue, &msg, portMAX_DELAY); +} + + +void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) ){ + + if(cb_ptr_arr && message_code < WM_MESSAGE_CODE_COUNT){ + cb_ptr_arr[message_code] = func_ptr; + } +} + +esp_netif_t* wifi_manager_get_esp_netif_ap(){ + return esp_netif_ap; +} + +esp_netif_t* wifi_manager_get_esp_netif_sta(){ + return esp_netif_sta; +} + +void wifi_manager( void * pvParameters ){ + + + queue_message msg; + BaseType_t xStatus; + EventBits_t uxBits; + uint8_t retries = 0; + + + /* initialize the tcp stack */ + ESP_ERROR_CHECK(esp_netif_init()); + + /* event loop for the wifi driver */ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + esp_netif_sta = esp_netif_create_default_wifi_sta(); + esp_netif_ap = esp_netif_create_default_wifi_ap(); + + + /* default wifi config */ + wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + + /* event handler for the connection */ + esp_event_handler_instance_t instance_wifi_event; + esp_event_handler_instance_t instance_ip_event; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_manager_event_handler, NULL,&instance_wifi_event)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &wifi_manager_event_handler, NULL,&instance_ip_event)); + + + /* SoftAP - Wifi Access Point configuration setup */ + wifi_config_t ap_config = { + .ap = { + .ssid_len = 0, + .channel = wifi_settings.ap_channel, + .ssid_hidden = wifi_settings.ap_ssid_hidden, + .max_connection = DEFAULT_AP_MAX_CONNECTIONS, + .beacon_interval = DEFAULT_AP_BEACON_INTERVAL, + }, + }; + memcpy(ap_config.ap.ssid, wifi_settings.ap_ssid , sizeof(wifi_settings.ap_ssid)); + + /* if the password lenght is under 8 char which is the minium for WPA2, the access point starts as open */ + if(strlen( (char*)wifi_settings.ap_pwd) < WPA2_MINIMUM_PASSWORD_LENGTH){ + ap_config.ap.authmode = WIFI_AUTH_OPEN; + memset( ap_config.ap.password, 0x00, sizeof(ap_config.ap.password) ); + } + else{ + ap_config.ap.authmode = WIFI_AUTH_WPA2_PSK; + memcpy(ap_config.ap.password, wifi_settings.ap_pwd, sizeof(wifi_settings.ap_pwd)); + } + + + /* DHCP AP configuration */ + esp_netif_dhcps_stop(esp_netif_ap); /* DHCP client/server must be stopped before setting new IP information. */ + esp_netif_ip_info_t ap_ip_info; + memset(&ap_ip_info, 0x00, sizeof(ap_ip_info)); + inet_pton(AF_INET, DEFAULT_AP_IP, &ap_ip_info.ip); + inet_pton(AF_INET, DEFAULT_AP_GATEWAY, &ap_ip_info.gw); + inet_pton(AF_INET, DEFAULT_AP_NETMASK, &ap_ip_info.netmask); + ESP_ERROR_CHECK(esp_netif_set_ip_info(esp_netif_ap, &ap_ip_info)); + ESP_ERROR_CHECK(esp_netif_dhcps_start(esp_netif_ap)); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &ap_config)); + ESP_ERROR_CHECK(esp_wifi_set_bandwidth(WIFI_IF_AP, wifi_settings.ap_bandwidth)); + ESP_ERROR_CHECK(esp_wifi_set_ps(wifi_settings.sta_power_save)); + + + /* by default the mode is STA because wifi_manager will not start the access point unless it has to! */ + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + + /* start http server */ + http_app_start(false); + + /* wifi scanner config */ + wifi_scan_config_t scan_config = { + .ssid = 0, + .bssid = 0, + .channel = 0, + .show_hidden = true + }; + + /* enqueue first event: load previous config */ + wifi_manager_send_message(WM_ORDER_LOAD_AND_RESTORE_STA, NULL); + + + /* main processing loop */ + for(;;){ + xStatus = xQueueReceive( wifi_manager_queue, &msg, portMAX_DELAY ); + + if( xStatus == pdPASS ){ + switch(msg.code){ + + case WM_EVENT_SCAN_DONE:{ + wifi_event_sta_scan_done_t *evt_scan_done = (wifi_event_sta_scan_done_t*)msg.param; + /* only check for AP if the scan is succesful */ + if(evt_scan_done->status == 0){ + /* As input param, it stores max AP number ap_records can hold. As output param, it receives the actual AP number this API returns. + * As a consequence, ap_num MUST be reset to MAX_AP_NUM at every scan */ + ap_num = MAX_AP_NUM; + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_num, accessp_records)); + /* make sure the http server isn't trying to access the list while it gets refreshed */ + if(wifi_manager_lock_json_buffer( pdMS_TO_TICKS(1000) )){ + /* Will remove the duplicate SSIDs from the list and update ap_num */ + wifi_manager_filter_unique(accessp_records, &ap_num); + wifi_manager_generate_acess_points_json(); + wifi_manager_unlock_json_buffer(); + } + else{ + ESP_LOGE(TAG, "could not get access to json mutex in wifi_scan"); + } + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(evt_scan_done); + } + break; + + case WM_ORDER_START_WIFI_SCAN: + ESP_LOGD(TAG, "MESSAGE: ORDER_START_WIFI_SCAN"); + + /* if a scan is already in progress this message is simply ignored thanks to the WIFI_MANAGER_SCAN_BIT uxBit */ + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if(! (uxBits & WIFI_MANAGER_SCAN_BIT) ){ + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT); + ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, false)); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_LOAD_AND_RESTORE_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_LOAD_AND_RESTORE_STA"); + if(wifi_manager_fetch_wifi_sta_config()){ + ESP_LOGI(TAG, "Saved wifi found on startup. Will attempt to connect."); + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_RESTORE_CONNECTION); + } + else{ + /* no wifi saved: start soft AP! This is what should happen during a first run */ + ESP_LOGI(TAG, "No saved wifi found on startup. Starting access point."); + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_CONNECT_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_CONNECT_STA"); + + /* very important: precise that this connection attempt is specifically requested. + * Param in that case is a boolean indicating if the request was made automatically + * by the wifi_manager. + * */ + if((BaseType_t)msg.param == CONNECTION_REQUEST_USER) { + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + } + else if((BaseType_t)msg.param == CONNECTION_REQUEST_RESTORE_CONNECTION) { + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + } + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if( ! (uxBits & WIFI_MANAGER_WIFI_CONNECTED_BIT) ){ + /* update config to latest and attempt connection */ + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_manager_get_wifi_sta_config())); + + /* if there is a wifi scan in progress abort it first + Calling esp_wifi_scan_stop will trigger a SCAN_DONE event which will reset this bit */ + if(uxBits & WIFI_MANAGER_SCAN_BIT){ + esp_wifi_scan_stop(); + } + ESP_ERROR_CHECK(esp_wifi_connect()); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_EVENT_STA_DISCONNECTED: + ;wifi_event_sta_disconnected_t* wifi_event_sta_disconnected = (wifi_event_sta_disconnected_t*)msg.param; + ESP_LOGI(TAG, "MESSAGE: EVENT_STA_DISCONNECTED with Reason code: %d", wifi_event_sta_disconnected->reason); + + /* this even can be posted in numerous different conditions + * + * 1. SSID password is wrong + * 2. Manual disconnection ordered + * 3. Connection lost + * + * Having clear understand as to WHY the event was posted is key to having an efficient wifi manager + * + * With wifi_manager, we determine: + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT is set, We consider it's a client that requested the connection. + * When SYSTEM_EVENT_STA_DISCONNECTED is posted, it's probably a password/something went wrong with the handshake. + * + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT is set, it's a disconnection that was ASKED by the client (clicking disconnect in the app) + * When SYSTEM_EVENT_STA_DISCONNECTED is posted, saved wifi is erased from the NVS memory. + * + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT and WIFI_MANAGER_REQUEST_STA_CONNECT_BIT are NOT set, it's a lost connection + * + * In this version of the software, reason codes are not used. They are indicated here for potential future usage. + * + * REASON CODE: + * 1 UNSPECIFIED + * 2 AUTH_EXPIRE auth no longer valid, this smells like someone changed a password on the AP + * 3 AUTH_LEAVE + * 4 ASSOC_EXPIRE + * 5 ASSOC_TOOMANY too many devices already connected to the AP => AP fails to respond + * 6 NOT_AUTHED + * 7 NOT_ASSOCED + * 8 ASSOC_LEAVE tested as manual disconnect by user OR in the wireless MAC blacklist + * 9 ASSOC_NOT_AUTHED + * 10 DISASSOC_PWRCAP_BAD + * 11 DISASSOC_SUPCHAN_BAD + * 12 + * 13 IE_INVALID + * 14 MIC_FAILURE + * 15 4WAY_HANDSHAKE_TIMEOUT wrong password! This was personnaly tested on my home wifi with a wrong password. + * 16 GROUP_KEY_UPDATE_TIMEOUT + * 17 IE_IN_4WAY_DIFFERS + * 18 GROUP_CIPHER_INVALID + * 19 PAIRWISE_CIPHER_INVALID + * 20 AKMP_INVALID + * 21 UNSUPP_RSN_IE_VERSION + * 22 INVALID_RSN_IE_CAP + * 23 802_1X_AUTH_FAILED wrong password? + * 24 CIPHER_SUITE_REJECTED + * 200 BEACON_TIMEOUT + * 201 NO_AP_FOUND + * 202 AUTH_FAIL + * 203 ASSOC_FAIL + * 204 HANDSHAKE_TIMEOUT + * + * */ + + /* reset saved sta IP */ + wifi_manager_safe_update_sta_ip_string((uint32_t)0); + + /* if there was a timer on to stop the AP, well now it's time to cancel that since connection was lost! */ + if(xTimerIsTimerActive(wifi_manager_shutdown_ap_timer) == pdTRUE ){ + xTimerStop( wifi_manager_shutdown_ap_timer, (TickType_t)0 ); + } + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if( uxBits & WIFI_MANAGER_REQUEST_STA_CONNECT_BIT ){ + /* there are no retries when it's a user requested connection by design. This avoids a user hanging too much + * in case they typed a wrong password for instance. Here we simply clear the request bit and move on */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_FAILED_ATTEMPT ); + wifi_manager_unlock_json_buffer(); + } + + } + else if (uxBits & WIFI_MANAGER_REQUEST_DISCONNECT_BIT){ + /* user manually requested a disconnect so the lost connection is a normal event. Clear the flag and restart the AP */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_DISCONNECT_BIT); + + /* erase configuration */ + if(wifi_manager_config_sta){ + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + } + + /* regenerate json status */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_USER_DISCONNECT ); + wifi_manager_unlock_json_buffer(); + } + + /* save NVS memory */ + wifi_manager_save_sta_config(); + + /* start SoftAP */ + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + else{ + /* lost connection ? */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION ); + wifi_manager_unlock_json_buffer(); + } + + /* Start the timer that will try to restore the saved config */ + xTimerStart( wifi_manager_retry_timer, (TickType_t)0 ); + + /* if it was a restore attempt connection, we clear the bit */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + + /* if the AP is not started, we check if we have reached the threshold of failed attempt to start it */ + if(! (uxBits & WIFI_MANAGER_AP_STARTED_BIT) ){ + + /* if the nunber of retries is below the threshold to start the AP, a reconnection attempt is made + * This way we avoid restarting the AP directly in case the connection is mementarily lost */ + if(retries < WIFI_MANAGER_MAX_RETRY_START_AP){ + retries++; + } + else{ + /* In this scenario the connection was lost beyond repair: kick start the AP! */ + retries = 0; + + /* start SoftAP */ + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + } + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(wifi_event_sta_disconnected); + + break; + + case WM_ORDER_START_AP: + ESP_LOGI(TAG, "MESSAGE: ORDER_START_AP"); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); + + /* restart HTTP daemon */ + http_app_stop(); + http_app_start(true); + + /* start DNS */ + dns_server_start(); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_STOP_AP: + ESP_LOGI(TAG, "MESSAGE: ORDER_STOP_AP"); + + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + + /* before stopping the AP, we check that we are still connected. There's a chance that once the timer + * kicks in, for whatever reason the esp32 is already disconnected. + */ + if(uxBits & WIFI_MANAGER_WIFI_CONNECTED_BIT){ + + /* set to STA only */ + esp_wifi_set_mode(WIFI_MODE_STA); + + /* stop DNS */ + dns_server_stop(); + + /* restart HTTP daemon */ + http_app_stop(); + http_app_start(false); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + } + + break; + + case WM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "WM_EVENT_STA_GOT_IP"); + ip_event_got_ip_t* ip_event_got_ip = (ip_event_got_ip_t*)msg.param; + uxBits = xEventGroupGetBits(wifi_manager_event_group); + + /* reset connection requests bits -- doesn't matter if it was set or not */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + + /* save IP as a string for the HTTP server host */ + wifi_manager_safe_update_sta_ip_string(ip_event_got_ip->ip_info.ip.addr); + + /* save wifi config in NVS if it wasn't a restored of a connection */ + if(uxBits & WIFI_MANAGER_REQUEST_RESTORE_STA_BIT){ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + } + else{ + wifi_manager_save_sta_config(); + } + + /* reset number of retries */ + retries = 0; + + /* refresh JSON with the new IP */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + /* generate the connection info with success */ + wifi_manager_generate_ip_info_json( UPDATE_CONNECTION_OK ); + wifi_manager_unlock_json_buffer(); + } + else { abort(); } + + /* bring down DNS hijack */ + dns_server_stop(); + + /* start the timer that will eventually shutdown the access point + * We check first that it's actually running because in case of a boot and restore connection + * the AP is not even started to begin with. + */ + if(uxBits & WIFI_MANAGER_AP_STARTED_BIT){ + TickType_t t = pdMS_TO_TICKS( WIFI_MANAGER_SHUTDOWN_AP_TIMER ); + + /* if for whatever reason user configured the shutdown timer to be less than 1 tick, the AP is stopped straight away */ + if(t > 0){ + xTimerStart( wifi_manager_shutdown_ap_timer, (TickType_t)0 ); + } + else{ + wifi_manager_send_message(WM_ORDER_STOP_AP, (void*)NULL); + } + + } + + /* callback and free memory allocated for the void* param */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(ip_event_got_ip); + + break; + + case WM_ORDER_DISCONNECT_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_DISCONNECT_STA"); + + /* precise this is coming from a user request */ + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_DISCONNECT_BIT); + + /* order wifi discconect */ + ESP_ERROR_CHECK(esp_wifi_disconnect()); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + default: + break; + + } /* end of switch/case */ + } /* end of if status=pdPASS */ + } /* end of for loop */ + + vTaskDelete( NULL ); + +} + + diff --git a/dist/esp32-wifi-manager_0.0.1/src/wifi_manager.h b/dist/esp32-wifi-manager_0.0.1/src/wifi_manager.h new file mode 100644 index 00000000..cba3b989 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.1/src/wifi_manager.h @@ -0,0 +1,417 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file wifi_manager.h +@author Tony Pottier +@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis + +Contains the freeRTOS task and all necessary support + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#ifndef WIFI_MANAGER_H_INCLUDED +#define WIFI_MANAGER_H_INCLUDED + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Defines the maximum size of a SSID name. 32 is IEEE standard. + * @warning limit is also hard coded in wifi_config_t. Never extend this value. + */ +#define MAX_SSID_SIZE 32 + +/** + * @brief Defines the maximum size of a WPA2 passkey. 64 is IEEE standard. + * @warning limit is also hard coded in wifi_config_t. Never extend this value. + */ +#define MAX_PASSWORD_SIZE 64 + + +/** + * @brief Defines the maximum number of access points that can be scanned. + * + * To save memory and avoid nasty out of memory errors, + * we can limit the number of APs detected in a wifi scan. + */ +#define MAX_AP_NUM 15 + + +/** + * @brief Defines the maximum number of failed retries allowed before the WiFi manager starts its own access point. + * Setting it to 2 for instance means there will be 3 attempts in total (original request + 2 retries) + */ +#define WIFI_MANAGER_MAX_RETRY_START_AP CONFIG_WIFI_MANAGER_MAX_RETRY_START_AP + +/** + * @brief Time (in ms) between each retry attempt + * Defines the time to wait before an attempt to re-connect to a saved wifi is made after connection is lost or another unsuccesful attempt is made. + */ +#define WIFI_MANAGER_RETRY_TIMER CONFIG_WIFI_MANAGER_RETRY_TIMER + + +/** + * @brief Time (in ms) to wait before shutting down the AP + * Defines the time (in ms) to wait after a succesful connection before shutting down the access point. + */ +#define WIFI_MANAGER_SHUTDOWN_AP_TIMER CONFIG_WIFI_MANAGER_SHUTDOWN_AP_TIMER + + +/** @brief Defines the task priority of the wifi_manager. + * + * Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1. + * For this particular reason, minimum task priority is 1. It it highly not recommended to set + * it to 1 though as the sub-tasks will now have a priority of 0 which is the priority + * of freeRTOS' idle task. + */ +#define WIFI_MANAGER_TASK_PRIORITY CONFIG_WIFI_MANAGER_TASK_PRIORITY + +/** @brief Defines the auth mode as an access point + * Value must be of type wifi_auth_mode_t + * @see esp_wifi_types.h + * @warning if set to WIFI_AUTH_OPEN, passowrd me be empty. See DEFAULT_AP_PASSWORD. + */ +#define AP_AUTHMODE WIFI_AUTH_WPA2_PSK + +/** @brief Defines visibility of the access point. 0: visible AP. 1: hidden */ +#define DEFAULT_AP_SSID_HIDDEN 0 + +/** @brief Defines access point's name. Default value: esp32. Run 'make menuconfig' to setup your own value or replace here by a string */ +#define DEFAULT_AP_SSID CONFIG_DEFAULT_AP_SSID + +/** @brief Defines access point's password. + * @warning In the case of an open access point, the password must be a null string "" or "\0" if you want to be verbose but waste one byte. + * In addition, the AP_AUTHMODE must be WIFI_AUTH_OPEN + */ +#define DEFAULT_AP_PASSWORD CONFIG_DEFAULT_AP_PASSWORD + +/** @brief Defines the hostname broadcasted by mDNS */ +#define DEFAULT_HOSTNAME "esp32" + +/** @brief Defines access point's bandwidth. + * Value: WIFI_BW_HT20 for 20 MHz or WIFI_BW_HT40 for 40 MHz + * 20 MHz minimize channel interference but is not suitable for + * applications with high data speeds + */ +#define DEFAULT_AP_BANDWIDTH WIFI_BW_HT20 + +/** @brief Defines access point's channel. + * Channel selection is only effective when not connected to another AP. + * Good practice for minimal channel interference to use + * For 20 MHz: 1, 6 or 11 in USA and 1, 5, 9 or 13 in most parts of the world + * For 40 MHz: 3 in USA and 3 or 11 in most parts of the world + */ +#define DEFAULT_AP_CHANNEL CONFIG_DEFAULT_AP_CHANNEL + + + +/** @brief Defines the access point's default IP address. Default: "10.10.0.1 */ +#define DEFAULT_AP_IP CONFIG_DEFAULT_AP_IP + +/** @brief Defines the access point's gateway. This should be the same as your IP. Default: "10.10.0.1" */ +#define DEFAULT_AP_GATEWAY CONFIG_DEFAULT_AP_GATEWAY + +/** @brief Defines the access point's netmask. Default: "255.255.255.0" */ +#define DEFAULT_AP_NETMASK CONFIG_DEFAULT_AP_NETMASK + +/** @brief Defines access point's maximum number of clients. Default: 4 */ +#define DEFAULT_AP_MAX_CONNECTIONS CONFIG_DEFAULT_AP_MAX_CONNECTIONS + +/** @brief Defines access point's beacon interval. 100ms is the recommended default. */ +#define DEFAULT_AP_BEACON_INTERVAL CONFIG_DEFAULT_AP_BEACON_INTERVAL + +/** @brief Defines if esp32 shall run both AP + STA when connected to another AP. + * Value: 0 will have the own AP always on (APSTA mode) + * Value: 1 will turn off own AP when connected to another AP (STA only mode when connected) + * Turning off own AP when connected to another AP minimize channel interference and increase throughput + */ +#define DEFAULT_STA_ONLY 1 + +/** @brief Defines if wifi power save shall be enabled. + * Value: WIFI_PS_NONE for full power (wifi modem always on) + * Value: WIFI_PS_MODEM for power save (wifi modem sleep periodically) + * Note: Power save is only effective when in STA only mode + */ +#define DEFAULT_STA_POWER_SAVE WIFI_PS_NONE + +/** + * @brief Defines the maximum length in bytes of a JSON representation of an access point. + * + * maximum ap string length with full 32 char ssid: 75 + \\n + \0 = 77\n + * example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","chan":12,"rssi":-100,"auth":4},\n + * BUT: we need to escape JSON. Imagine a ssid full of \" ? so it's 32 more bytes hence 77 + 32 = 99.\n + * this is an edge case but I don't think we should crash in a catastrophic manner just because + * someone decided to have a funny wifi name. + */ +#define JSON_ONE_APP_SIZE 99 + +/** + * @brief Defines the maximum length in bytes of a JSON representation of the IP information + * assuming all ips are 4*3 digits, and all characters in the ssid require to be escaped. + * example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":99} + * Run this JS (browser console is easiest) to come to the conclusion that 159 is the worst case. + * ``` + * var a = {"ssid":"abcdefghijklmnopqrstuvwxyz012345","ip":"255.255.255.255","netmask":"255.255.255.255","gw":"255.255.255.255","urc":99}; + * // Replace all ssid characters with a double quote which will have to be escaped + * a.ssid = a.ssid.split('').map(() => '"').join(''); + * console.log(JSON.stringify(a).length); // => 158 +1 for null + * console.log(JSON.stringify(a)); // print it + * ``` + */ +#define JSON_IP_INFO_SIZE 159 + + +/** + * @brief defines the minimum length of an access point password running on WPA2 + */ +#define WPA2_MINIMUM_PASSWORD_LENGTH 8 + + +/** + * @brief Defines the complete list of all messages that the wifi_manager can process. + * + * Some of these message are events ("EVENT"), and some of them are action ("ORDER") + * Each of these messages can trigger a callback function and each callback function is stored + * in a function pointer array for convenience. Because of this behavior, it is extremely important + * to maintain a strict sequence and the top level special element 'MESSAGE_CODE_COUNT' + * + * @see wifi_manager_set_callback + */ +typedef enum message_code_t { + NONE = 0, + WM_ORDER_START_HTTP_SERVER = 1, + WM_ORDER_STOP_HTTP_SERVER = 2, + WM_ORDER_START_DNS_SERVICE = 3, + WM_ORDER_STOP_DNS_SERVICE = 4, + WM_ORDER_START_WIFI_SCAN = 5, + WM_ORDER_LOAD_AND_RESTORE_STA = 6, + WM_ORDER_CONNECT_STA = 7, + WM_ORDER_DISCONNECT_STA = 8, + WM_ORDER_START_AP = 9, + WM_EVENT_STA_DISCONNECTED = 10, + WM_EVENT_SCAN_DONE = 11, + WM_EVENT_STA_GOT_IP = 12, + WM_ORDER_STOP_AP = 13, + WM_MESSAGE_CODE_COUNT = 14 /* important for the callback array */ + +}message_code_t; + +/** + * @brief simplified reason codes for a lost connection. + * + * esp-idf maintains a big list of reason codes which in practice are useless for most typical application. + */ +typedef enum update_reason_code_t { + UPDATE_CONNECTION_OK = 0, + UPDATE_FAILED_ATTEMPT = 1, + UPDATE_USER_DISCONNECT = 2, + UPDATE_LOST_CONNECTION = 3 +}update_reason_code_t; + +typedef enum connection_request_made_by_code_t{ + CONNECTION_REQUEST_NONE = 0, + CONNECTION_REQUEST_USER = 1, + CONNECTION_REQUEST_AUTO_RECONNECT = 2, + CONNECTION_REQUEST_RESTORE_CONNECTION = 3, + CONNECTION_REQUEST_MAX = 0x7fffffff /*force the creation of this enum as a 32 bit int */ +}connection_request_made_by_code_t; + +/** + * The actual WiFi settings in use + */ +struct wifi_settings_t{ + uint8_t ap_ssid[MAX_SSID_SIZE]; + uint8_t ap_pwd[MAX_PASSWORD_SIZE]; + uint8_t ap_channel; + uint8_t ap_ssid_hidden; + wifi_bandwidth_t ap_bandwidth; + bool sta_only; + wifi_ps_type_t sta_power_save; + bool sta_static_ip; + esp_netif_ip_info_t sta_static_ip_config; +}; +extern struct wifi_settings_t wifi_settings; + + +/** + * @brief Structure used to store one message in the queue. + */ +typedef struct{ + message_code_t code; + void *param; +} queue_message; + + +/** + * @brief returns the current esp_netif object for the STAtion + */ +esp_netif_t* wifi_manager_get_esp_netif_sta(); + +/** + * @brief returns the current esp_netif object for the Access Point + */ +esp_netif_t* wifi_manager_get_esp_netif_ap(); + + +/** + * Allocate heap memory for the wifi manager and start the wifi_manager RTOS task + */ +void wifi_manager_start(); + +/** + * Frees up all memory allocated by the wifi_manager and kill the task. + */ +void wifi_manager_destroy(); + +/** + * Filters the AP scan list to unique SSIDs + */ +void filter_unique( wifi_ap_record_t * aplist, uint16_t * ap_num); + +/** + * Main task for the wifi_manager + */ +void wifi_manager( void * pvParameters ); + + +char* wifi_manager_get_ap_list_json(); +char* wifi_manager_get_ip_info_json(); + + +void wifi_manager_scan_async(); + + +/** + * @brief saves the current STA wifi config to flash ram storage. + */ +esp_err_t wifi_manager_save_sta_config(); + +/** + * @brief fetch a previously STA wifi config in the flash ram storage. + * @return true if a previously saved config was found, false otherwise. + */ +bool wifi_manager_fetch_wifi_sta_config(); + +wifi_config_t* wifi_manager_get_wifi_sta_config(); + + +/** + * @brief requests a connection to an access point that will be process in the main task thread. + */ +void wifi_manager_connect_async(); + +/** + * @brief requests a wifi scan + */ +void wifi_manager_scan_awifi_manager_send_messagesync(); + +/** + * @brief requests to disconnect and forget about the access point. + */ +void wifi_manager_disconnect_async(); + +/** + * @brief Tries to get access to json buffer mutex. + * + * The HTTP server can try to access the json to serve clients while the wifi manager thread can try + * to update it. These two tasks are synchronized through a mutex. + * + * The mutex is used by both the access point list json and the connection status json.\n + * These two resources should technically have their own mutex but we lose some flexibility to save + * on memory. + * + * This is a simple wrapper around freeRTOS function xSemaphoreTake. + * + * @param xTicksToWait The time in ticks to wait for the semaphore to become available. + * @return true in success, false otherwise. + */ +bool wifi_manager_lock_json_buffer(TickType_t xTicksToWait); + +/** + * @brief Releases the json buffer mutex. + */ +void wifi_manager_unlock_json_buffer(); + +/** + * @brief Generates the connection status json: ssid and IP addresses. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code); +/** + * @brief Clears the connection status json. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_clear_ip_info_json(); + +/** + * @brief Generates the list of access points after a wifi scan. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_generate_acess_points_json(); + +/** + * @brief Clear the list of access points. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_clear_access_points_json(); + + +/** + * @brief Start the mDNS service + */ +void wifi_manager_initialise_mdns(); + + +bool wifi_manager_lock_sta_ip_string(TickType_t xTicksToWait); +void wifi_manager_unlock_sta_ip_string(); + +/** + * @brief gets the string representation of the STA IP address, e.g.: "192.168.1.69" + */ +char* wifi_manager_get_sta_ip_string(); + +/** + * @brief thread safe char representation of the STA IP update + */ +void wifi_manager_safe_update_sta_ip_string(uint32_t ip); + + +/** + * @brief Register a callback to a custom function when specific event message_code happens. + */ +void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) ); + + +BaseType_t wifi_manager_send_message(message_code_t code, void *param); +BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param); + +#ifdef __cplusplus +} +#endif + +#endif /* WIFI_MANAGER_H_INCLUDED */ diff --git a/dist/esp32-wifi-manager_0.0.3.tgz b/dist/esp32-wifi-manager_0.0.3.tgz new file mode 100644 index 00000000..0c617381 Binary files /dev/null and b/dist/esp32-wifi-manager_0.0.3.tgz differ diff --git a/dist/esp32-wifi-manager_0.0.3/.gitignore b/dist/esp32-wifi-manager_0.0.3/.gitignore new file mode 100644 index 00000000..5448b48b --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/.gitignore @@ -0,0 +1,75 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Eclipse +.metadata/ +RemoteSystemsTempFiles/.project +.settings/ +*.a +*.o +*.d +wifi_manager/.cproject +wifi_manager/.project +sdkconfig +sdkconfig.old +**/build/ +**/managed_components/ +dependencies.lock +#doxygen +Doxyfile +wifi_manager/doc/ +.project +.cproject + +# Visual Studio Code +.vscode/ diff --git a/dist/esp32-wifi-manager_0.0.3/.travis.yml b/dist/esp32-wifi-manager_0.0.3/.travis.yml new file mode 100644 index 00000000..37c55458 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/.travis.yml @@ -0,0 +1,122 @@ +language: bash + +# when you suspects issues in cache, use the following line to disable cache. +# cache: false +cache: + directories: + - ${HOME}/distfiles + - ${HOME}/.ccache + - ${HOME}/.cache/pip +os: + - linux + +matrix: + include: + - env: + - PROJECT_TARGET="esp32" + - PROJECT_SDK_BRANCH="master" +# - env: +# - PROJECT_TARGET="esp8266" +# - PROJECT_SDK_BRANCH="master" + +addons: + apt: + packages: + - gcc + - wget + - make + - libncurses-dev + - flex + - bison + - python + - python-pip + - gperf + - ccache + +before_install: + # Save path to the git respository + - PROJECT_PATH=$(pwd) + +install: + - export TOOLCHAIN_DIR="${HOME}/${PROJECT_TARGET}" + - | + if [ ${PROJECT_TARGET} == "esp8266" ]; then + export PROJECT_GCC_PREFIX="xtensa-lx106-elf" + export PROJECT_TOOLCHAIN_FILE=xtensa-lx106-elf-linux64-1.22.0-92-g8facf4c-5.2.0.tar.gz + export PROJECT_SDK_NAME="ESP8266_RTOS_SDK" + else + export PROJECT_GCC_PREFIX="xtensa-esp32-elf" + export PROJECT_TOOLCHAIN_FILE=xtensa-esp32-elf-gcc8_2_0-esp32-2019r1-linux-amd64.tar.gz + export PROJECT_SDK_NAME="esp-idf" + fi + - export PROJECT_GCC_FILE="${PROJECT_GCC_PREFIX}-gcc" + - export PROJECT_DISTFILE_DIR="${HOME}/distfiles" + - export IDF_PATH=${TOOLCHAIN_DIR}/${PROJECT_SDK_NAME} + - export PROJECT_LOG="${HOME}/build.log" + - export PROJECT_EXAMPLE_DIR="${PROJECT_PATH}/examples" + # Install ESP32 toochain following steps as desribed + # in http://esp-idf.readthedocs.io/en/latest/linux-setup.html + + # Prepare directory for the toolchain + - mkdir -p ${TOOLCHAIN_DIR} ${PROJECT_DISTFILE_DIR} + # Get SDK from github + - git clone --branch ${PROJECT_SDK_BRANCH} --recursive https://github.com/espressif/${PROJECT_SDK_NAME}.git ${IDF_PATH} + + # Setup ccache to build faster + # XXX when the entire build process exceeds 50 min, th job will be killed + # https://docs.travis-ci.com/user/customizing-the-build/#build-timeouts + - ccache --version + - mkdir ${HOME}/ccache_bin + - (cd ${HOME}/ccache_bin && ln -s /usr/bin/ccache ${PROJECT_GCC_FILE}) + - export CCACHE_BASEDIR=$PROJECT_PATH + - export CCACHE_CPP2=true + + # Get Python requirements + - python -m pip install --user --upgrade pyOpenSSL + - python -m pip install --user -r ${IDF_PATH}/requirements.txt + + # Download binary toolchain if it does not exist + - | + if [ ! -f ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} ]; then + wget -O ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} https://dl.espressif.com/dl/${PROJECT_TOOLCHAIN_FILE} + fi + - tar -xz -C ${TOOLCHAIN_DIR} -f ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} + + # Make toolchains available for all terminal sessions + - export PATH=$HOME/ccache_bin:$PATH:$HOME/${PROJECT_TARGET}/${PROJECT_GCC_PREFIX}/bin + +script: + - rm -f ${PROJECT_LOG} + # XXX surpress log output where possible. when the size exceeds 4 MB, the + # job will be killed. + - | + IGNORE_FILE="travis-ignore" + + case ${PROJECT_TARGET} in + esp32) + ;; + esp8266) + IGNORE_FILE="travis-ignore-esp8266" + # these drivers do not compile for ESP8266 yet + export EXCLUDE_COMPONENTS="encoder max7219 mcp23x17" + ;; + esac + + cd ${PROJECT_EXAMPLE_DIR} + for i in $(ls -d */); do + if [ ! -e ${PROJECT_EXAMPLE_DIR}/${i}/${IGNORE_FILE} ]; then + echo "Building ${i}..." + cd ${PROJECT_EXAMPLE_DIR}/${i} + make defconfig + make -j2 >> ${PROJECT_LOG} + if [ $? -ne 0 ]; then + # when failed, show last 100 lines for debugging, and exit with + # non-zero exit code + tail -n 100 ${PROJECT_LOG} + exit 1 + fi + make clean >/dev/null + # make sure the directory is clean + rm -rf ${i}/sdkconfig ${i}/build + fi + done diff --git a/dist/esp32-wifi-manager_0.0.3/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.3/CMakeLists.txt new file mode 100644 index 00000000..83e0077c --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/CMakeLists.txt @@ -0,0 +1,12 @@ +if(IDF_VERSION_MAJOR GREATER_EQUAL 4) + idf_component_register(SRC_DIRS src + REQUIRES esp_wifi log nvs_flash mdns wpa_supplicant lwip esp_http_server + INCLUDE_DIRS src + EMBED_FILES src/style.css src/code.js src/index.html) +else() + set(COMPONENT_SRCDIRS src) + set(COMPONENT_ADD_INCLUDEDIRS src) + set(COMPONENT_REQUIRES esp_wifi log nvs_flash mdns wpa_supplicant lwip esp_http_server) + set(COMPONENT_EMBED_FILES src/style.css src/code.js src/index.html) + register_component() +endif() diff --git a/dist/esp32-wifi-manager_0.0.3/Kconfig b/dist/esp32-wifi-manager_0.0.3/Kconfig new file mode 100644 index 00000000..59c5a9a8 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/Kconfig @@ -0,0 +1,81 @@ +menu "Wifi Manager Configuration" + +config WIFI_MANAGER_TASK_PRIORITY + int "RTOS Task Priority for the wifi_manager" + default 5 + help + Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1. For this particular reason, minimum recommended task priority is 2. + +config WIFI_MANAGER_RETRY_TIMER + int "Time (in ms) between each retry attempt" + default 5000 + help + Defines the time to wait before an attempt to re-connect to a saved wifi is made after connection is lost or another unsuccesful attempt is made. + +config WIFI_MANAGER_MAX_RETRY_START_AP + int "Max Retry before starting the AP" + default 3 + help + Defines the maximum number of failed retries allowed before the WiFi manager starts its own access point. + +config WIFI_MANAGER_SHUTDOWN_AP_TIMER + int "Time (in ms) to wait before shutting down the AP" + default 60000 + help + Defines the time (in ms) to wait after a succesful connection before shutting down the access point. + +config WEBAPP_LOCATION + string "Defines the URL where the wifi manager is located" + default "/" + help + This parameter helps you relocate the wifimanager to another URL, for instance /wifimanager/ The trailing slash is important and should be included + +config DEFAULT_AP_SSID + string "Access Point SSID" + default "esp32" + help + SSID (network name) the the esp32 will broadcast. + +config DEFAULT_AP_PASSWORD + string "Access Point Password" + default "esp32pwd" + help + Password used for the Access Point. Leave empty and set AUTH MODE to WIFI_AUTH_OPEN for no password. + +config DEFAULT_AP_CHANNEL + int "Access Point WiFi Channel" + default 1 + help + Be careful you might not see the access point if you use a channel not allowed in your country. + +config DEFAULT_AP_IP + string "Access Point IP Address" + default "10.10.0.1" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_GATEWAY + string "Access Point IP Gateway" + default "10.10.0.1" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_NETMASK + string "Access Point Netmask" + default "255.255.255.0" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_MAX_CONNECTIONS + int "Access Point Max Connections" + default 4 + help + Max is 4. + +config DEFAULT_AP_BEACON_INTERVAL + int "Access Point Beacon Interval (ms)" + default 100 + help + 100ms is the recommended default. + +endmenu diff --git a/dist/esp32-wifi-manager_0.0.3/LICENSE.md b/dist/esp32-wifi-manager_0.0.3/LICENSE.md new file mode 100644 index 00000000..5f2ac0bc --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2017-2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dist/esp32-wifi-manager_0.0.3/README.md b/dist/esp32-wifi-manager_0.0.3/README.md new file mode 100644 index 00000000..43248f3d --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/README.md @@ -0,0 +1,225 @@ +# What is esp32-wifi-manager? + +### Build status [![Build Status](https://travis-ci.com/tonyp7/esp32-wifi-manager.svg?branch=master)](https://travis-ci.com/tonyp7/esp32-wifi-manager) + +*esp32-wifi-manager* is a pure C esp-idf component for ESP32 that enables easy management of wifi networks through a web portal. + +*esp32-wifi-manager* is is an all in one wifi scanner, http server & dns daemon living in the least amount of RAM possible. + +*esp32-wifi-manager* will automatically attempt to re-connect to a previously saved network on boot, and if it cannot find a saved wifi it will start its own access point through which you can manage and connect to wifi networks. Upon a succesful connection, the software will shutdown the access point automatically after some time (1 minute by default). + +*esp32-wifi-manager* compiles with esp-idf 4.2 and above. See [Getting Started](#getting-started) to guide you through your first setup. + +# Content + - [Demo](#demo) + - [Look And Feel](#look-and-feel) + - [Getting Started](#getting-started) + - [Requirements](#requirements) + - [Hello World](#hello-world) + - [Configuring the Wifi Manager](#configuring-the-wifi-manager) + - [Adding esp32-wifi-manager to your code](#adding-esp32-wifi-manager-to-your-code) + - [Interacting with the manager](#interacting-with-the-manager) + - [Interacting with the http server](#interacting-with-the-http-server) + - [Thread safety and access to NVS](#thread-safety-and-access-to-nvs) + - [License](#license) + + +# Demo +[![esp32-wifi-manager demo](http://img.youtube.com/vi/hxlZi15bym4/0.jpg)](http://www.youtube.com/watch?v=hxlZi15bym4) + +# Look and Feel +![esp32-wifi-manager on an mobile device](https://idyl.io/wp-content/uploads/2017/11/esp32-wifi-manager-password.png "esp32-wifi-manager") ![esp32-wifi-manager on an mobile device](https://idyl.io/wp-content/uploads/2017/11/esp32-wifi-manager-connected-to.png "esp32-wifi-manager") + +# Getting Started + +## Requirements + +To get you started, esp32-wifi-manager needs: + +- esp-idf **4.2 and up** +- esp32 or esp32-s2 + +There are breaking changes and new features in esp-idf 4.1 and 4.2 which makes esp32-wifi-manager incompatible with anything lower than 4.2. This includes esp_netif (introduced in 4.1) and esp_event_handler_instance_t (introduced in 4.2). It is recommended to compile esp32-wifi-manager with the master tree to avoid any compatibility issue. + +## Hello World + +Clone the repository where you want it to be. If you are unfamiliar with Git, you can use Github Desktop on Windows: + +```bash +git clone https://github.com/tonyp7/esp32-wifi-manager.git +``` + +Navigate under the included example: + +```bash +cd esp32-wifi-manager/examples/default_demo +``` + +Compile the code and load it on your esp32: + +```bash +idf.py build flash monitor +``` + +_Note: while it is encouraged to use the newer build system with idf.py and cmake, esp32-wifi-manager still supports the legacy build system. If you are using make on Linux or make using MSYS2 on Windows, you can still use "make build flash monitor" if you prefer_ + +Now, using any wifi capable device, you will see a new wifi access point named *esp32*. Connect to it using the default password *esp32pwd*. If the captive portal does not pop up on your device, you can access the wifi manager at its default IP address: http://10.10.0.1. + +## Configuring the Wifi Manager + +esp32-wifi-manager can be configured without touching its code. At the project level use: + +```bash +idf.py menuconfig +``` + +Navigate in "Component config" then pick "Wifi Manager Configuration". You will be greeted by the following screen: + +![esp32-wifi-manager-menuconfig](https://idyl.io/wp-content/uploads/2020/08/wifi-manager-menuconfig-800px.png "menuconfig screen") + +You can change the ssid and password of the access point at your convenience, but it is highly recommended to keep default values. Your password should be between 8 and 63 characters long, to comply with the WPA2 standard. If the password is set to an empty value or is less than 8 characters long, esp32-wifi-manager will create its access point as an open wifi network. + +You can also change the values for various timers, for instance how long it takes for the access point to shutdown once a connection is established (default: 60000). While it could be tempting to set this timer to 0, just be warned that in that case the user will never get the feedback that a connection is succesful. Shutting down the AP will instantly kill the current navigating session on the captive portal. + +Finally, you can choose to relocate esp32-wifi-manager to a different URL by changing the default value of "/" to something else, for instance "/wifimanager/". Please note that the trailing slash does matter. This feature is particularly useful in case you want your own webapp to co-exist with esp32-wifi-manager's own web pages. + +# Adding esp32-wifi-manager to your code + +In order to use esp32-wifi-manager effectively in your esp-idf projects, copy the whole esp32-wifi-manager repository (or git clone) into a components subfolder. + +Your project should look like this: + + - project_folder + - build + - components + - esp32-wifi-manager + - main + - main.c + +Under eclipse, this is what a typical project looks like: + +![eclipse project with esp32-wifi-manager](https://idyl.io/wp-content/uploads/2020/07/eclipse-idf-project.png "eclipse project with esp32-wifi-manager") + +Once this is done, you need to edit the CMakeLists.txt file at the root of your project to register the components folder. This is done by adding the following line: + +```cmake +set(EXTRA_COMPONENTS_DIRS components/) +``` + +A typical CmakeLists.txt file should look like this: + +```cmake +cmake_minimum_required(VERSION 3.5) +set(EXTRA_COMPONENT_DIRS components/) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(name_of_your_project) +``` + +If you are using the old build system with make instead, you should edit the Makefile instead such as: + +```make +PROJECT_NAME := name_of_your_project +EXTRA_COMPONENT_DIRS := components/ +include $(IDF_PATH)/make/project.mk +``` + +Once this is done, you can now in your user code add the header: + +```c +#include "wifi_manager.h" +``` + +All you need to do now is to call wifi_manager_start(); in your code. See [examples/default_demo](examples/default_demo) if you are uncertain. + + +## Interacting with the manager + +Ther are effectively three different ways you can embed esp32-wifi-manager with your code: +* Just forget about it and poll in your code for wifi connectivity status +* Use event callbacks +* Modify esp32-wifi-manager code directly to fit your needs + +**Event callbacks** are the cleanest way to use the wifi manager and that's the recommended way to do it. A typical use-case would be to get notified when wifi manager finally gets a connection to an access point. In order to do this you can simply define a callback function: + +```c +void cb_connection_ok(void *pvParameter){ + ESP_LOGI(TAG, "I have a connection!"); +} +``` + +Then just register it by calling: + +```c +wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok); +``` + +That's it! Now everytime the event is triggered it will call this function. The [examples/default_demo](examples/default_demo) contains sample code using callbacks. + +### List of events + +The list of possible events you can add a callback to are defined by message_code_t in wifi_manager.h. They are as following: + +* WM_ORDER_START_HTTP_SERVER +* WM_ORDER_STOP_HTTP_SERVER +* WM_ORDER_START_DNS_SERVICE +* WM_ORDER_STOP_DNS_SERVICE +* WM_ORDER_START_WIFI_SCAN +* WM_ORDER_LOAD_AND_RESTORE_STA +* WM_ORDER_CONNECT_STA +* WM_ORDER_DISCONNECT_STA +* WM_ORDER_START_AP +* WM_EVENT_STA_DISCONNECTED +* WM_EVENT_SCAN_DONE +* WM_EVENT_STA_GOT_IP +* WM_ORDER_STOP_AP + +In practice, keeping track of WM_EVENT_STA_GOT_IP and WM_EVENT_STA_DISCONNECTED is key to know whether or not your esp32 has a connection. The other messages can mostly be ignored in a typical application using esp32-wifi-manager. + +### Events parameters + +Callback signature includes a void* pointer. For most events, this additional parameter is empty and sent as a NULL value. A few select events have additional data which can be leveraged by user code. They are listed below: + +* WM_EVENT_SCAN_DONE is sent with a wifi_event_sta_scan_done_t* object. +* WM_EVENT_STA_DISCONNECTED is sent with a wifi_event_sta_disconnected_t* object. +* WM_EVENT_STA_GOT_IP is sent with a ip_event_got_ip_t* object. + +These objects are standard esp-idf structures, and are documented as such in the [official pages](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html). + +The [examples/default_demo](examples/default_demo) demonstrates how you can read a ip_event_got_ip_t object to access the IP address assigned to the esp32. + +## Interacting with the http server + +Because esp32-wifi-manager spawns its own http server, you might want to extend this server to serve your own pages in your application. It is possible to do so by registering your own URL handler using the standard esp_http_server signature: + +```c +esp_err_t my_custom_handler(httpd_req_t *req){ +``` + +And then registering the handler by doing + +```c +http_app_set_handler_hook(HTTP_GET, &my_custom_handler); +``` + +The [examples/http_hook](examples/http_hook) contains an example where a web page is registered at /helloworld + +## Thread safety and access to NVS + +esp32-wifi-manager accesses the non-volatile storage to store and loads its configuration into a dedicated namespace "espwifimgr". If you want to make sure there will never be a conflict with concurrent access to the NVS, you can include nvs_sync.h and use calls to nvs_sync_lock and nvs_sync_unlock. + +```c +nvs_handle handle; + +if(nvs_sync_lock( portMAX_DELAY )){ + if(nvs_open(wifi_manager_nvs_namespace, NVS_READWRITE, &handle) == ESP_OK){ + /* do something with NVS */ + nvs_close(handle); + } + nvs_sync_unlock(); +} +``` +nvs_sync_lock waits for the number of ticks sent to it as a parameter to acquire a mutex. It is recommended to use portMAX_DELAY. In practice, nvs_sync_lock will almost never wait. + + +# License +*esp32-wifi-manager* is MIT licensed. As such, it can be included in any project, commercial or not, as long as you retain original copyright. Please make sure to read the license file. diff --git a/dist/esp32-wifi-manager_0.0.3/component.mk b/dist/esp32-wifi-manager_0.0.3/component.mk new file mode 100644 index 00000000..75e34089 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/component.mk @@ -0,0 +1,4 @@ +COMPONENT_ADD_INCLUDEDIRS = src +COMPONENT_SRCDIRS = src +COMPONENT_DEPENDS = log esp_http_server +COMPONENT_EMBED_FILES := src/style.css src/code.js src/index.html diff --git a/dist/esp32-wifi-manager_0.0.3/examples/default_demo/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/CMakeLists.txt new file mode 100644 index 00000000..6458baae --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/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) +set(EXTRA_COMPONENT_DIRS ../../) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(default_demo) diff --git a/dist/esp32-wifi-manager_0.0.3/examples/default_demo/Makefile b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/Makefile new file mode 100644 index 00000000..fb186183 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := wifi_manager + +EXTRA_COMPONENT_DIRS := ../../ + +include $(IDF_PATH)/make/project.mk + diff --git a/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/CMakeLists.txt new file mode 100644 index 00000000..31addfc4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "user_main.c") + +register_component() diff --git a/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/component.mk b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/idf_component.yml b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/idf_component.yml new file mode 100644 index 00000000..8826a619 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "^1.3.2" + ## Required IDF version + idf: + version: ">=4.1.0" + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true diff --git a/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/user_main.c b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/user_main.c new file mode 100644 index 00000000..19af535e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/main/user_main.c @@ -0,0 +1,80 @@ +/* +Copyright (c) 2017-2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file main.c +@author Tony Pottier +@brief Entry point for the ESP32 application. +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "wifi_manager.h" + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "main"; + +/** + * @brief RTOS task that periodically prints the heap memory available. + * @note Pure debug information, should not be ever started on production code! This is an example on how you can integrate your code with wifi-manager + */ +void monitoring_task(void *pvParameter) +{ + for(;;){ + ESP_LOGI(TAG, "free heap: %lu",esp_get_free_heap_size()); + vTaskDelay( pdMS_TO_TICKS(10000) ); + } +} + + +/** + * @brief this is an exemple of a callback that you can setup in your own app to get notified of wifi manager event. + */ +void cb_connection_ok(void *pvParameter){ + ip_event_got_ip_t* param = (ip_event_got_ip_t*)pvParameter; + + /* transform IP to human readable string */ + char str_ip[16]; + esp_ip4addr_ntoa(¶m->ip_info.ip, str_ip, IP4ADDR_STRLEN_MAX); + + ESP_LOGI(TAG, "I have a connection and my IP is %s!", str_ip); +} + +void app_main() +{ + /* start the wifi manager */ + wifi_manager_start(); + + /* register a callback as an example to how you can integrate your code with the wifi manager */ + wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok); +#if CONFIG_SOC_CPU_CORES_NUM > 1 + /* your code should go here. Here we simply create a task on core 2 that monitors free heap memory */ + xTaskCreatePinnedToCore(&monitoring_task, "monitoring_task", 2048, NULL, 1, NULL, 1); +#endif +} diff --git a/dist/esp32-wifi-manager_0.0.3/examples/default_demo/sdkconfig.defaults b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/sdkconfig.defaults new file mode 100644 index 00000000..f44c06f4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/default_demo/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_LWIP_IPV6=y diff --git a/dist/esp32-wifi-manager_0.0.3/examples/http_hook/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/CMakeLists.txt new file mode 100644 index 00000000..377f86fe --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/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) +set(EXTRA_COMPONENT_DIRS ../../) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(http_hook) diff --git a/dist/esp32-wifi-manager_0.0.3/examples/http_hook/Makefile b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/Makefile new file mode 100644 index 00000000..fb186183 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := wifi_manager + +EXTRA_COMPONENT_DIRS := ../../ + +include $(IDF_PATH)/make/project.mk + diff --git a/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/CMakeLists.txt new file mode 100644 index 00000000..31addfc4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "user_main.c") + +register_component() diff --git a/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/component.mk b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/idf_component.yml b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/idf_component.yml new file mode 100644 index 00000000..8826a619 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "^1.3.2" + ## Required IDF version + idf: + version: ">=4.1.0" + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true diff --git a/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/user_main.c b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/user_main.c new file mode 100644 index 00000000..2446f269 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/main/user_main.c @@ -0,0 +1,76 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file user_main.c +@author Tony Pottier +@brief Entry point for the ESP32 application. +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "wifi_manager.h" +#include "http_app.h" + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "main"; + + +static esp_err_t my_get_handler(httpd_req_t *req){ + + /* our custom page sits at /helloworld in this example */ + if(strcmp(req->uri, "/helloworld") == 0){ + + ESP_LOGI(TAG, "Serving page /helloworld"); + + const char* response = "

Hello World!

"; + + httpd_resp_set_status(req, "200 OK"); + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, response, strlen(response)); + } + else{ + /* send a 404 otherwise */ + httpd_resp_send_404(req); + } + + return ESP_OK; +} + + +void app_main() +{ + /* start the wifi manager */ + wifi_manager_start(); + + /* set custom handler for the http server + * Now navigate to /helloworld to see the custom page + * */ + http_app_set_handler_hook(HTTP_GET, &my_get_handler); + +} diff --git a/dist/esp32-wifi-manager_0.0.3/examples/http_hook/sdkconfig.defaults b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/sdkconfig.defaults new file mode 100644 index 00000000..f44c06f4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/examples/http_hook/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_LWIP_IPV6=y diff --git a/dist/esp32-wifi-manager_0.0.3/idf_component.yml b/dist/esp32-wifi-manager_0.0.3/idf_component.yml new file mode 100644 index 00000000..60a458ed --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/idf_component.yml @@ -0,0 +1,9 @@ +dependencies: + idf: + version: '>=4.2' +description: esp32-wifi-manager is a pure C esp-idf component for ESP32 that enables + easy management of wifi networks through a web portal. +license: MIT +repository: https://github.com/ankayca/esp32-wifi-manager/tree/update_build_system +url: https://github.com/ankayca/esp32-wifi-manager/tree/update_build_system +version: 0.0.3 diff --git a/dist/esp32-wifi-manager_0.0.3/src/ap.json b/dist/esp32-wifi-manager_0.0.3/src/ap.json new file mode 100644 index 00000000..de61f86a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/ap.json @@ -0,0 +1,12 @@ +[ +{"ssid":"Pantum-AP-A6D49F","chan":11,"rssi":-55,"auth":4}, +{"ssid":"a0308","chan":1,"rssi":-56,"auth":3}, +{"ssid":"dlink-D9D8","chan":11,"rssi":-82,"auth":4}, +{"ssid":"Linksys06730","chan":7,"rssi":-85,"auth":3}, +{"ssid":"SINGTEL-5171","chan":9,"rssi":-88,"auth":4}, +{"ssid":"1126-1","chan":11,"rssi":-89,"auth":4}, +{"ssid":"The Shah 5GHz-2","chan":1,"rssi":-90,"auth":3}, +{"ssid":"SINGTEL-1D28 (2G)","chan":11,"rssi":-91,"auth":3}, +{"ssid":"dlink-F864","chan":1,"rssi":-92,"auth":4}, +{"ssid":"dlink-74F0","chan":1,"rssi":-93,"auth":4} +] \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/code.js b/dist/esp32-wifi-manager_0.0.3/src/code.js new file mode 100644 index 00000000..934595a9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/code.js @@ -0,0 +1,352 @@ +// save some bytes +const gel = (e) => document.getElementById(e); + +const wifi_div = gel("wifi"); +const connect_div = gel("connect"); +const connect_manual_div = gel("connect_manual"); +const connect_wait_div = gel("connect-wait"); +const connect_details_div = gel("connect-details"); + +function docReady(fn) { + // see if DOM is already available + if ( + document.readyState === "complete" || + document.readyState === "interactive" + ) { + // call on next available tick + setTimeout(fn, 1); + } else { + document.addEventListener("DOMContentLoaded", fn); + } +} + +var selectedSSID = ""; +var refreshAPInterval = null; +var checkStatusInterval = null; + +function stopCheckStatusInterval() { + if (checkStatusInterval != null) { + clearInterval(checkStatusInterval); + checkStatusInterval = null; + } +} + +function stopRefreshAPInterval() { + if (refreshAPInterval != null) { + clearInterval(refreshAPInterval); + refreshAPInterval = null; + } +} + +function startCheckStatusInterval() { + checkStatusInterval = setInterval(checkStatus, 950); +} + +function startRefreshAPInterval() { + refreshAPInterval = setInterval(refreshAP, 3800); +} + +docReady(async function () { + gel("wifi-status").addEventListener( + "click", + () => { + wifi_div.style.display = "none"; + document.getElementById("connect-details").style.display = "block"; + }, + false + ); + + gel("manual_add").addEventListener( + "click", + (e) => { + selectedSSID = e.target.innerText; + + gel("ssid-pwd").textContent = selectedSSID; + wifi_div.style.display = "none"; + connect_manual_div.style.display = "block"; + connect_div.style.display = "none"; + + gel("connect-success").display = "none"; + gel("connect-fail").display = "none"; + }, + false + ); + + gel("wifi-list").addEventListener( + "click", + (e) => { + selectedSSID = e.target.innerText; + gel("ssid-pwd").textContent = selectedSSID; + connect_div.style.display = "block"; + wifi_div.style.display = "none"; + // init_cancel(); + }, + false + ); + + function cancel() { + selectedSSID = ""; + connect_div.style.display = "none"; + connect_manual_div.style.display = "none"; + wifi_div.style.display = "block"; + } + + gel("cancel").addEventListener("click", cancel, false); + + gel("manual_cancel").addEventListener("click", cancel, false); + + gel("join").addEventListener("click", performConnect, false); + + gel("manual_join").addEventListener( + "click", + (e) => { + performConnect("manual"); + }, + false + ); + + gel("ok-details").addEventListener( + "click", + () => { + connect_details_div.style.display = "none"; + wifi_div.style.display = "block"; + }, + false + ); + + gel("ok-credits").addEventListener( + "click", + () => { + gel("credits").style.display = "none"; + gel("app").style.display = "block"; + }, + false + ); + + gel("acredits").addEventListener( + "click", + () => { + event.preventDefault(); + gel("app").style.display = "none"; + gel("credits").style.display = "block"; + }, + false + ); + + gel("ok-connect").addEventListener( + "click", + () => { + connect_wait_div.style.display = "none"; + wifi_div.style.display = "block"; + }, + false + ); + + gel("disconnect").addEventListener( + "click", + () => { + gel("diag-disconnect").style.display = "block"; + gel("connect-details-wrap").classList.add("blur"); + }, + false + ); + + gel("no-disconnect").addEventListener( + "click", + () => { + gel("diag-disconnect").style.display = "none"; + gel("connect-details-wrap").classList.remove("blur"); + }, + false + ); + + gel("yes-disconnect").addEventListener("click", async () => { + stopCheckStatusInterval(); + selectedSSID = ""; + + document.getElementById("diag-disconnect").style.display = "none"; + gel("connect-details-wrap").classList.remove("blur"); + + await fetch("connect.json", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: { timestamp: Date.now() }, + }); + + startCheckStatusInterval(); + + connect_details_div.style.display = "none"; + wifi_div.style.display = "block"; + }); + + //first time the page loads: attempt get the connection status and start the wifi scan + await refreshAP(); + startCheckStatusInterval(); + startRefreshAPInterval(); +}); + +async function performConnect(conntype) { + //stop the status refresh. This prevents a race condition where a status + //request would be refreshed with wrong ip info from a previous connection + //and the request would automatically shows as succesful. + stopCheckStatusInterval(); + + //stop refreshing wifi list + stopRefreshAPInterval(); + + var pwd; + if (conntype == "manual") { + //Grab the manual SSID and PWD + selectedSSID = gel("manual_ssid").value; + pwd = gel("manual_pwd").value; + } else { + pwd = gel("pwd").value; + } + //reset connection + gel("loading").style.display = "block"; + gel("connect-success").style.display = "none"; + gel("connect-fail").style.display = "none"; + + gel("ok-connect").disabled = true; + gel("ssid-wait").textContent = selectedSSID; + connect_div.style.display = "none"; + connect_manual_div.style.display = "none"; + connect_wait_div.style.display = "block"; + + await fetch("connect.json", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Custom-ssid": selectedSSID, + "X-Custom-pwd": pwd, + }, + body: { timestamp: Date.now() }, + }); + + //now we can re-set the intervals regardless of result + startCheckStatusInterval(); + startRefreshAPInterval(); +} + +function rssiToIcon(rssi) { + if (rssi >= -60) { + return "w0"; + } else if (rssi >= -67) { + return "w1"; + } else if (rssi >= -75) { + return "w2"; + } else { + return "w3"; + } +} + +async function refreshAP(url = "ap.json") { + try { + var res = await fetch(url); + var access_points = await res.json(); + if (access_points.length > 0) { + //sort by signal strength + access_points.sort((a, b) => { + var x = a["rssi"]; + var y = b["rssi"]; + return x < y ? 1 : x > y ? -1 : 0; + }); + refreshAPHTML(access_points); + } + } catch (e) { + console.info("Access points returned empty from /ap.json!"); + } +} + +function refreshAPHTML(data) { + var h = ""; + data.forEach(function (e, idx, array) { + let ap_class = idx === array.length - 1 ? "" : " brdb"; + let rssicon = rssiToIcon(e.rssi); + let auth = e.auth == 0 ? "" : "pw"; + h += `
${e.ssid}
\n`; + }); + + gel("wifi-list").innerHTML = h; +} + +async function checkStatus(url = "status.json") { + try { + var response = await fetch(url); + var data = await response.json(); + if (data && data.hasOwnProperty("ssid") && data["ssid"] != "") { + if (data["ssid"] === selectedSSID) { + // Attempting connection + switch (data["urc"]) { + case 0: + console.info("Got connection!"); + document.querySelector( + "#connected-to div div div span" + ).textContent = data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = data["ip"]; + gel("netmask").textContent = data["netmask"]; + gel("gw").textContent = data["gw"]; + gel("wifi-status").style.display = "block"; + + //unlock the wait screen if needed + gel("ok-connect").disabled = false; + + //update wait screen + gel("loading").style.display = "none"; + gel("connect-success").style.display = "block"; + gel("connect-fail").style.display = "none"; + break; + case 1: + console.info("Connection attempt failed!"); + document.querySelector( + "#connected-to div div div span" + ).textContent = data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = "0.0.0.0"; + gel("netmask").textContent = "0.0.0.0"; + gel("gw").textContent = "0.0.0.0"; + + //don't show any connection + gel("wifi-status").display = "none"; + + //unlock the wait screen + gel("ok-connect").disabled = false; + + //update wait screen + gel("loading").display = "none"; + gel("connect-fail").style.display = "block"; + gel("connect-success").style.display = "none"; + break; + } + } else if (data.hasOwnProperty("urc") && data["urc"] === 0) { + console.info("Connection established"); + //ESP32 is already connected to a wifi without having the user do anything + if ( + gel("wifi-status").style.display == "" || + gel("wifi-status").style.display == "none" + ) { + document.querySelector("#connected-to div div div span").textContent = + data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = data["ip"]; + gel("netmask").textContent = data["netmask"]; + gel("gw").textContent = data["gw"]; + gel("wifi-status").style.display = "block"; + } + } + } else if (data.hasOwnProperty("urc") && data["urc"] === 2) { + console.log("Manual disconnect requested..."); + if (gel("wifi-status").style.display == "block") { + gel("wifi-status").style.display = "none"; + } + } + } catch (e) { + console.info("Was not able to fetch /status.json"); + } +} diff --git a/dist/esp32-wifi-manager_0.0.3/src/component.mk b/dist/esp32-wifi-manager_0.0.3/src/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/dist/esp32-wifi-manager_0.0.3/src/compress.bat b/dist/esp32-wifi-manager_0.0.3/src/compress.bat new file mode 100644 index 00000000..b9caad22 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/compress.bat @@ -0,0 +1,2 @@ +gzip index.html style.css --best --keep --force +pause \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/connect b/dist/esp32-wifi-manager_0.0.3/src/connect new file mode 100644 index 00000000..8c7fe211 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/connect @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/dns_server.c b/dist/esp32-wifi-manager_0.0.3/src/dns_server.c new file mode 100644 index 00000000..6693913a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/dns_server.c @@ -0,0 +1,184 @@ +/* +Copyright (c) 2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file dns_server.c +@author Tony Pottier +@brief Defines an extremely basic DNS server for captive portal functionality. +It's basically a DNS hijack that replies to the esp's address no matter which +request is sent to it. + +Contains the freeRTOS task for the DNS server that processes the requests. + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wifi_manager.h" +#include "dns_server.h" + +static const char TAG[] = "dns_server"; +static TaskHandle_t task_dns_server = NULL; +int socket_fd; + +void dns_server_start() { + if(task_dns_server == NULL){ + xTaskCreate(&dns_server, "dns_server", 3072, NULL, WIFI_MANAGER_TASK_PRIORITY-1, &task_dns_server); + } +} + +void dns_server_stop(){ + if(task_dns_server){ + vTaskDelete(task_dns_server); + close(socket_fd); + task_dns_server = NULL; + } + +} + + + +void dns_server(void *pvParameters) { + + + + struct sockaddr_in ra; + + /* Set redirection DNS hijack to the access point IP */ + ip4_addr_t ip_resolved; + inet_pton(AF_INET, DEFAULT_AP_IP, &ip_resolved); + + + /* Create UDP socket */ + socket_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (socket_fd < 0){ + ESP_LOGE(TAG, "Failed to create socket"); + exit(0); + } + + /* Bind to port 53 (typical DNS Server port) */ + esp_netif_ip_info_t ip; + esp_netif_t* netif_sta = wifi_manager_get_esp_netif_sta(); + ESP_ERROR_CHECK(esp_netif_get_ip_info(netif_sta, &ip)); + ra.sin_family = AF_INET; + ra.sin_addr.s_addr = ip.ip.addr; + ra.sin_port = htons(53); + if (bind(socket_fd, (struct sockaddr *)&ra, sizeof(struct sockaddr_in)) == -1) { + ESP_LOGE(TAG, "Failed to bind to 53/udp"); + close(socket_fd); + exit(1); + } + + struct sockaddr_in client; + socklen_t client_len; + client_len = sizeof(client); + int length; + uint8_t data[DNS_QUERY_MAX_SIZE]; /* dns query buffer */ + uint8_t response[DNS_ANSWER_MAX_SIZE]; /* dns response buffer */ + char ip_address[INET_ADDRSTRLEN]; /* buffer to store IPs as text. This is only used for debug and serves no other purpose */ + char *domain; /* This is only used for debug and serves no other purpose */ + int err; + + ESP_LOGI(TAG, "DNS Server listening on 53/udp"); + + /* Start loop to process DNS requests */ + for(;;) { + + memset(data, 0x00, sizeof(data)); /* reset buffer */ + length = recvfrom(socket_fd, data, sizeof(data), 0, (struct sockaddr *)&client, &client_len); /* read udp request */ + + /*if the query is bigger than the buffer size we simply ignore it. This case should only happen in case of multiple + * queries within the same DNS packet and is not supported by this simple DNS hijack. */ + if ( length > 0 && ((length + sizeof(dns_answer_t)-1) < DNS_ANSWER_MAX_SIZE) ) { + + data[length] = '\0'; /*in case there's a bogus domain name that isn't null terminated */ + + /* Generate header message */ + memcpy(response, data, sizeof(dns_header_t)); + dns_header_t *dns_header = (dns_header_t*)response; + dns_header->QR = 1; /*response bit */ + dns_header->OPCode = DNS_OPCODE_QUERY; /* no support for other type of response */ + dns_header->AA = 1; /*authoritative answer */ + dns_header->RCode = DNS_REPLY_CODE_NO_ERROR; /* no error */ + dns_header->TC = 0; /*no truncation */ + dns_header->RD = 0; /*no recursion */ + dns_header->ANCount = dns_header->QDCount; /* set answer count = question count -- duhh! */ + dns_header->NSCount = 0x0000; /* name server resource records = 0 */ + dns_header->ARCount = 0x0000; /* resource records = 0 */ + + + /* copy the rest of the query in the response */ + memcpy(response + sizeof(dns_header_t), data + sizeof(dns_header_t), length - sizeof(dns_header_t)); + + + /* extract domain name and request IP for debug */ + inet_ntop(AF_INET, &(client.sin_addr), ip_address, INET_ADDRSTRLEN); + domain = (char*) &data[sizeof(dns_header_t) + 1]; + for(char* c=domain; *c != '\0'; c++){ + if(*c < ' ' || *c > 'z') *c = '.'; /* technically we should test if the first two bits are 00 (e.g. if( (*c & 0xC0) == 0x00) *c = '.') but this makes the code a lot more readable */ + } + ESP_LOGI(TAG, "Replying to DNS request for %s from %s", domain, ip_address); + + + /* create DNS answer at the end of the query*/ + dns_answer_t *dns_answer = (dns_answer_t*)&response[length]; + dns_answer->NAME = __bswap_16(0xC00C); /* This is a pointer to the beginning of the question. As per DNS standard, first two bits must be set to 11 for some odd reason hence 0xC0 */ + dns_answer->TYPE = __bswap_16(DNS_ANSWER_TYPE_A); + dns_answer->CLASS = __bswap_16(DNS_ANSWER_CLASS_IN); + dns_answer->TTL = (uint32_t)0x00000000; /* no caching. Avoids DNS poisoning since this is a DNS hijack */ + dns_answer->RDLENGTH = __bswap_16(0x0004); /* 4 byte => size of an ipv4 address */ + dns_answer->RDATA = ip_resolved.addr; + + err = sendto(socket_fd, response, length+sizeof(dns_answer_t), 0, (struct sockaddr *)&client, client_len); + if (err < 0) { + ESP_LOGE(TAG, "UDP sendto failed: %d", err); + } + } + + taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. DNS daemon should not be taxing on the system */ + + } + close(socket_fd); + + vTaskDelete ( NULL ); +} + + + + diff --git a/dist/esp32-wifi-manager_0.0.3/src/dns_server.h b/dist/esp32-wifi-manager_0.0.3/src/dns_server.h new file mode 100644 index 00000000..d1da1f8c --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/dns_server.h @@ -0,0 +1,137 @@ +/* +Copyright (c) 2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file dns_server.h +@author Tony Pottier +@brief Defines an extremly basic DNS server for captive portal functionality. + +Contains the freeRTOS task for the DNS server that processes the requests. + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +@see http://www.zytrax.com/books/dns/ch15 +*/ + +#ifndef MAIN_DNS_SERVER_H_ +#define MAIN_DNS_SERVER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** 12 byte header, 64 byte domain name, 4 byte qtype/qclass. This NOT compliant with the RFC, but it's good enough for a captive portal + * if a DNS query is too big it just wont be processed. */ +#define DNS_QUERY_MAX_SIZE 80 + +/** Query + 2 byte ptr, 2 byte type, 2 byte class, 4 byte TTL, 2 byte len, 4 byte data */ +#define DNS_ANSWER_MAX_SIZE (DNS_QUERY_MAX_SIZE+16) + + +/** + * @brief RCODE values used in a DNS header message + */ +typedef enum dns_reply_code_t { + DNS_REPLY_CODE_NO_ERROR = 0, + DNS_REPLY_CODE_FORM_ERROR = 1, + DNS_REPLY_CODE_SERVER_FAILURE = 2, + DNS_REPLY_CODE_NON_EXISTANT_DOMAIN = 3, + DNS_REPLY_CODE_NOT_IMPLEMENTED = 4, + DNS_REPLY_CODE_REFUSED = 5, + DNS_REPLY_CODE_YXDOMAIN = 6, + DNS_REPLY_CODE_YXRRSET = 7, + DNS_REPLY_CODE_NXRRSET = 8 +}dns_reply_code_t; + + + +/** + * @brief OPCODE values used in a DNS header message + */ +typedef enum dns_opcode_code_t { + DNS_OPCODE_QUERY = 0, + DNS_OPCODE_IQUERY = 1, + DNS_OPCODE_STATUS = 2 +}dns_opcode_code_t; + + + +/** + * @brief Represents a 12 byte DNS header. + * __packed__ is needed to prevent potential unwanted memory alignments + */ +typedef struct __attribute__((__packed__)) dns_header_t{ + uint16_t ID; // identification number + uint8_t RD : 1; // recursion desired + uint8_t TC : 1; // truncated message + uint8_t AA : 1; // authoritive answer + uint8_t OPCode : 4; // message_type + uint8_t QR : 1; // query/response flag + uint8_t RCode : 4; // response code + uint8_t Z : 3; // its z! reserved + uint8_t RA : 1; // recursion available + uint16_t QDCount; // number of question entries + uint16_t ANCount; // number of answer entries + uint16_t NSCount; // number of authority entries + uint16_t ARCount; // number of resource entries +}dns_header_t; + + + +typedef enum dns_answer_type_t { + DNS_ANSWER_TYPE_A = 1, + DNS_ANSWER_TYPE_NS = 2, + DNS_ANSWER_TYPE_CNAME = 5, + DNS_ANSWER_TYPE_SOA = 6, + DNS_ANSWER_TYPE_WKS = 11, + DNS_ANSWER_TYPE_PTR = 12, + DNS_ANSWER_TYPE_MX = 15, + DNS_ANSWER_TYPE_SRV = 33, + DNS_ANSWER_TYPE_AAAA = 28 +}dns_answer_type_t; + +typedef enum dns_answer_class_t { + DNS_ANSWER_CLASS_IN = 1 +}dns_answer_class_t; + + + +typedef struct __attribute__((__packed__)) dns_answer_t{ + uint16_t NAME; /* for the sake of simplicity only 16 bit pointers are supported */ + uint16_t TYPE; /* Unsigned 16 bit value. The resource record types - determines the content of the RDATA field. */ + uint16_t CLASS; /* Class of response. */ + uint32_t TTL; /* The time in seconds that the record may be cached. A value of 0 indicates the record should not be cached. */ + uint16_t RDLENGTH; /* Unsigned 16-bit value that defines the length in bytes of the RDATA record. */ + uint32_t RDATA; /* For the sake of simplicity only ipv4 is supported, and as such it's a unsigned 32 bit */ +}dns_answer_t; + +void dns_server(void *pvParameters); +void dns_server_start(); +void dns_server_stop(); + + + +#ifdef __cplusplus +} +#endif + + +#endif /* MAIN_DNS_SERVER_H_ */ diff --git a/dist/esp32-wifi-manager_0.0.3/src/http_app.c b/dist/esp32-wifi-manager_0.0.3/src/http_app.c new file mode 100644 index 00000000..8dbc40ef --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/http_app.c @@ -0,0 +1,479 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file http_app.c +@author Tony Pottier +@brief Defines all functions necessary for the HTTP server to run. + +Contains the freeRTOS task for the HTTP listener and all necessary support +function to process requests, decode URLs, serve files, etc. etc. + +@note http_server task cannot run without the wifi_manager task! +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "esp_netif.h" +#include + +#include "wifi_manager.h" +#include "http_app.h" + + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "http_server"; + +/* @brief the HTTP server handle */ +static httpd_handle_t httpd_handle = NULL; + +/* function pointers to URI handlers that can be user made */ +esp_err_t (*custom_get_httpd_uri_handler)(httpd_req_t *r) = NULL; +esp_err_t (*custom_post_httpd_uri_handler)(httpd_req_t *r) = NULL; + +/* strings holding the URLs of the wifi manager */ +static char* http_root_url = NULL; +static char* http_redirect_url = NULL; +static char* http_js_url = NULL; +static char* http_css_url = NULL; +static char* http_connect_url = NULL; +static char* http_ap_url = NULL; +static char* http_status_url = NULL; + +/** + * @brief embedded binary data. + * @see file "component.mk" + * @see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#embedding-binary-data + */ +extern const uint8_t style_css_start[] asm("_binary_style_css_start"); +extern const uint8_t style_css_end[] asm("_binary_style_css_end"); +extern const uint8_t code_js_start[] asm("_binary_code_js_start"); +extern const uint8_t code_js_end[] asm("_binary_code_js_end"); +extern const uint8_t index_html_start[] asm("_binary_index_html_start"); +extern const uint8_t index_html_end[] asm("_binary_index_html_end"); + + +/* const httpd related values stored in ROM */ +const static char http_200_hdr[] = "200 OK"; +const static char http_302_hdr[] = "302 Found"; +const static char http_400_hdr[] = "400 Bad Request"; +const static char http_404_hdr[] = "404 Not Found"; +const static char http_503_hdr[] = "503 Service Unavailable"; +const static char http_location_hdr[] = "Location"; +const static char http_content_type_html[] = "text/html"; +const static char http_content_type_js[] = "text/javascript"; +const static char http_content_type_css[] = "text/css"; +const static char http_content_type_json[] = "application/json"; +const static char http_cache_control_hdr[] = "Cache-Control"; +const static char http_cache_control_no_cache[] = "no-store, no-cache, must-revalidate, max-age=0"; +const static char http_cache_control_cache[] = "public, max-age=31536000"; +const static char http_pragma_hdr[] = "Pragma"; +const static char http_pragma_no_cache[] = "no-cache"; + + + +esp_err_t http_app_set_handler_hook( httpd_method_t method, esp_err_t (*handler)(httpd_req_t *r) ){ + + if(method == HTTP_GET){ + custom_get_httpd_uri_handler = handler; + return ESP_OK; + } + else if(method == HTTP_POST){ + custom_post_httpd_uri_handler = handler; + return ESP_OK; + } + else{ + return ESP_ERR_INVALID_ARG; + } + +} + + +static esp_err_t http_server_delete_handler(httpd_req_t *req){ + + ESP_LOGI(TAG, "DELETE %s", req->uri); + + /* DELETE /connect.json */ + if(strcmp(req->uri, http_connect_url) == 0){ + wifi_manager_disconnect_async(); + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, NULL, 0); + } + else{ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + + return ESP_OK; +} + + +static esp_err_t http_server_post_handler(httpd_req_t *req){ + + + esp_err_t ret = ESP_OK; + + ESP_LOGI(TAG, "POST %s", req->uri); + + /* POST /connect.json */ + if(strcmp(req->uri, http_connect_url) == 0){ + + + /* buffers for the headers */ + size_t ssid_len = 0, password_len = 0; + char *ssid = NULL, *password = NULL; + + /* len of values provided */ + ssid_len = httpd_req_get_hdr_value_len(req, "X-Custom-ssid"); + password_len = httpd_req_get_hdr_value_len(req, "X-Custom-pwd"); + + + if(ssid_len && ssid_len <= MAX_SSID_SIZE && password_len && password_len <= MAX_PASSWORD_SIZE){ + + /* get the actual value of the headers */ + ssid = malloc(sizeof(char) * (ssid_len + 1)); + password = malloc(sizeof(char) * (password_len + 1)); + httpd_req_get_hdr_value_str(req, "X-Custom-ssid", ssid, ssid_len+1); + httpd_req_get_hdr_value_str(req, "X-Custom-pwd", password, password_len+1); + + wifi_config_t* config = wifi_manager_get_wifi_sta_config(); + memset(config, 0x00, sizeof(wifi_config_t)); + memcpy(config->sta.ssid, ssid, ssid_len); + memcpy(config->sta.password, password, password_len); + ESP_LOGI(TAG, "ssid: %s, password: %s", ssid, password); + ESP_LOGD(TAG, "http_server_post_handler: wifi_manager_connect_async() call"); + wifi_manager_connect_async(); + + /* free memory */ + free(ssid); + free(password); + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, NULL, 0); + + } + else{ + /* bad request the authentification header is not complete/not the correct format */ + httpd_resp_set_status(req, http_400_hdr); + httpd_resp_send(req, NULL, 0); + } + + } + else{ + + if(custom_post_httpd_uri_handler == NULL){ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + else{ + + /* if there's a hook, run it */ + ret = (*custom_post_httpd_uri_handler)(req); + } + } + + return ret; +} + + +static esp_err_t http_server_get_handler(httpd_req_t *req){ + + char* host = NULL; + size_t buf_len; + esp_err_t ret = ESP_OK; + + ESP_LOGD(TAG, "GET %s", req->uri); + + /* Get header value string length and allocate memory for length + 1, + * extra byte for null termination */ + buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1; + if (buf_len > 1) { + host = malloc(buf_len); + if(httpd_req_get_hdr_value_str(req, "Host", host, buf_len) != ESP_OK){ + /* if something is wrong we just 0 the whole memory */ + memset(host, 0x00, buf_len); + } + } + + /* determine if Host is from the STA IP address */ + wifi_manager_lock_sta_ip_string(portMAX_DELAY); + bool access_from_sta_ip = host != NULL?strstr(host, wifi_manager_get_sta_ip_string()):false; + wifi_manager_unlock_sta_ip_string(); + + + if (host != NULL && !strstr(host, DEFAULT_AP_IP) && !access_from_sta_ip) { + + /* Captive Portal functionality */ + /* 302 Redirect to IP of the access point */ + httpd_resp_set_status(req, http_302_hdr); + httpd_resp_set_hdr(req, http_location_hdr, http_redirect_url); + httpd_resp_send(req, NULL, 0); + + } + else{ + + /* GET / */ + if(strcmp(req->uri, http_root_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_html); + httpd_resp_send(req, (char*)index_html_start, index_html_end - index_html_start); + } + /* GET /code.js */ + else if(strcmp(req->uri, http_js_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_js); + httpd_resp_send(req, (char*)code_js_start, code_js_end - code_js_start); + } + /* GET /style.css */ + else if(strcmp(req->uri, http_css_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_css); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_cache); + httpd_resp_send(req, (char*)style_css_start, style_css_end - style_css_start); + } + /* GET /ap.json */ + else if(strcmp(req->uri, http_ap_url) == 0){ + + /* if we can get the mutex, write the last version of the AP list */ + if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){ + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + char* ap_buf = wifi_manager_get_ap_list_json(); + httpd_resp_send(req, ap_buf, strlen(ap_buf)); + wifi_manager_unlock_json_buffer(); + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + ESP_LOGE(TAG, "http_server_netconn_serve: GET /ap.json failed to obtain mutex"); + } + + /* request a wifi scan */ + wifi_manager_scan_async(); + } + /* GET /status.json */ + else if(strcmp(req->uri, http_status_url) == 0){ + + if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){ + char *buff = wifi_manager_get_ip_info_json(); + if(buff){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, buff, strlen(buff)); + wifi_manager_unlock_json_buffer(); + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + } + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + ESP_LOGE(TAG, "http_server_netconn_serve: GET /status.json failed to obtain mutex"); + } + } + else{ + + if(custom_get_httpd_uri_handler == NULL){ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + else{ + + /* if there's a hook, run it */ + ret = (*custom_get_httpd_uri_handler)(req); + } + } + + } + + /* memory clean up */ + if(host != NULL){ + free(host); + } + + return ret; + +} + +/* URI wild card for any GET request */ +static const httpd_uri_t http_server_get_request = { + .uri = "*", + .method = HTTP_GET, + .handler = http_server_get_handler +}; + +static const httpd_uri_t http_server_post_request = { + .uri = "*", + .method = HTTP_POST, + .handler = http_server_post_handler +}; + +static const httpd_uri_t http_server_delete_request = { + .uri = "*", + .method = HTTP_DELETE, + .handler = http_server_delete_handler +}; + + +void http_app_stop(){ + + if(httpd_handle != NULL){ + + + /* dealloc URLs */ + if(http_root_url) { + free(http_root_url); + http_root_url = NULL; + } + if(http_redirect_url){ + free(http_redirect_url); + http_redirect_url = NULL; + } + if(http_js_url){ + free(http_js_url); + http_js_url = NULL; + } + if(http_css_url){ + free(http_css_url); + http_css_url = NULL; + } + if(http_connect_url){ + free(http_connect_url); + http_connect_url = NULL; + } + if(http_ap_url){ + free(http_ap_url); + http_ap_url = NULL; + } + if(http_status_url){ + free(http_status_url); + http_status_url = NULL; + } + + /* stop server */ + httpd_stop(httpd_handle); + httpd_handle = NULL; + } +} + + +/** + * @brief helper to generate URLs of the wifi manager + */ +static char* http_app_generate_url(const char* page){ + + char* ret; + + int root_len = strlen(WEBAPP_LOCATION); + const size_t url_sz = sizeof(char) * ( (root_len+1) + ( strlen(page) + 1) ); + + ret = malloc(url_sz); + memset(ret, 0x00, url_sz); + strcpy(ret, WEBAPP_LOCATION); + ret = strcat(ret, page); + + return ret; +} + +void http_app_start(bool lru_purge_enable){ + + esp_err_t err; + + if(httpd_handle == NULL){ + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + /* this is an important option that isn't set up by default. + * We could register all URLs one by one, but this would not work while the fake DNS is active */ + config.uri_match_fn = httpd_uri_match_wildcard; + config.lru_purge_enable = lru_purge_enable; + + /* generate the URLs */ + if(http_root_url == NULL){ + int root_len = strlen(WEBAPP_LOCATION); + + /* all the pages */ + const char page_js[] = "code.js"; + const char page_css[] = "style.css"; + const char page_connect[] = "connect.json"; + const char page_ap[] = "ap.json"; + const char page_status[] = "status.json"; + + /* root url, eg "/" */ + const size_t http_root_url_sz = sizeof(char) * (root_len+1); + http_root_url = malloc(http_root_url_sz); + memset(http_root_url, 0x00, http_root_url_sz); + strcpy(http_root_url, WEBAPP_LOCATION); + + /* redirect url */ + size_t redirect_sz = 22 + root_len + 1; /* strlen(http://255.255.255.255) + strlen("/") + 1 for \0 */ + http_redirect_url = malloc(sizeof(char) * redirect_sz); + *http_redirect_url = '\0'; + + if(root_len == 1){ + snprintf(http_redirect_url, redirect_sz, "http://%s", DEFAULT_AP_IP); + } + else{ + snprintf(http_redirect_url, redirect_sz, "http://%s%s", DEFAULT_AP_IP, WEBAPP_LOCATION); + } + + /* generate the other pages URLs*/ + http_js_url = http_app_generate_url(page_js); + http_css_url = http_app_generate_url(page_css); + http_connect_url = http_app_generate_url(page_connect); + http_ap_url = http_app_generate_url(page_ap); + http_status_url = http_app_generate_url(page_status); + + } + + err = httpd_start(&httpd_handle, &config); + + if (err == ESP_OK) { + ESP_LOGI(TAG, "Registering URI handlers"); + httpd_register_uri_handler(httpd_handle, &http_server_get_request); + httpd_register_uri_handler(httpd_handle, &http_server_post_request); + httpd_register_uri_handler(httpd_handle, &http_server_delete_request); + } + } + +} diff --git a/dist/esp32-wifi-manager_0.0.3/src/http_app.h b/dist/esp32-wifi-manager_0.0.3/src/http_app.h new file mode 100644 index 00000000..53f9472a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/http_app.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file http_app.h +@author Tony Pottier +@brief Defines all functions necessary for the HTTP server to run. + +Contains the freeRTOS task for the HTTP listener and all necessary support +function to process requests, decode URLs, serve files, etc. etc. + +@note http_server task cannot run without the wifi_manager task! +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#ifndef HTTP_APP_H_INCLUDED +#define HTTP_APP_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** @brief Defines the URL where the wifi manager is located + * By default it is at the server root (ie "/"). If you wish to add your own webpages + * you may want to relocate the wifi manager to another URL, for instance /wifimanager + */ +#define WEBAPP_LOCATION CONFIG_WEBAPP_LOCATION + + +/** + * @brief spawns the http server + */ +void http_app_start(bool lru_purge_enable); + +/** + * @brief stops the http server + */ +void http_app_stop(); + +/** + * @brief sets a hook into the wifi manager URI handlers. Setting the handler to NULL disables the hook. + * @return ESP_OK in case of success, ESP_ERR_INVALID_ARG if the method is unsupported. + */ +esp_err_t http_app_set_handler_hook( httpd_method_t method, esp_err_t (*handler)(httpd_req_t *r) ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/esp32-wifi-manager_0.0.3/src/index.html b/dist/esp32-wifi-manager_0.0.3/src/index.html new file mode 100644 index 00000000..ce4bf146 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/index.html @@ -0,0 +1,136 @@ + + + + + + + + + esp32-wifi-manager + + +
+
+
+
+

Wi-Fi

+
+
+

Connected to:

+
+
+
+
+

Manual connect

+
+
ADD (HIDDEN) SSID
+
+

or choose a network...

+
+
+
Powered by esp32-wifi-manager.
+
+
+
+

Enter Details

+
+

Manual Connection

+
+ + +
+
+ + +
+
+
+
+

Enter Password

+
+

Password for

+
+ +
+
+ + +
+
+
+
+

Please wait...

+
+

Connecting to

+
+
+
+

You may lose wifi access while the esp32 recalibrates its radio. Please wait until your device automatically reconnects. This can take up to 30s.

+
+
+

Success!

+
+
+

Connection failed

+

Please double-check wifi password if any and make sure the access point has good signal.

+
+
+
+ +
+
+
+
+
+

+
+

+
+
+ +
+
+

IP Address

+
+
IP Address:
+
Subnet Mask:
+
Default Gateway:
+
+
+ +
+
+
+
+

Are you sure you would like to disconnect from this wifi?

+
+ + +
+
+
+
+
+
+
+
+

About this app...

+
+

+
+

esp32-wifi-manager, © 2017-2020, Tony Pottier
Licended under the MIT License.

+

+ This app would not be possible without the following libraries: +

+
    +
  • SpinKit, © 2015, Tobias Ahlin. Licensed under the MIT License.
  • +
  • jQuery, The jQuery Foundation. Licensed under the MIT License.
  • +
  • cJSON, © 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.
  • +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/json.c b/dist/esp32-wifi-manager_0.0.3/src/json.c new file mode 100644 index 00000000..d448711a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/json.c @@ -0,0 +1,143 @@ +/* +@file json.c +@brief handles very basic JSON with a minimal footprint on the system + +This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license: +Copyright (c) 2009 Dave Gamble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +@see https://github.com/DaveGamble/cJSON +*/ + +#include +#include +#include +#include +#include "json.h" + + +bool json_print_string(const unsigned char *input, unsigned char *output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + //output = ensure(output_buffer, sizeof("\"\""), hooks); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + if (strchr("\"\\\b\f\n\r\t", *input_pointer)) + { + /* one character escape sequence */ + escape_characters++; + } + else if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + /* in the original cJSON it is possible to realloc here in case output buffer is too small. + * This is overkill for an embedded system. */ + output = output_buffer; + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + diff --git a/dist/esp32-wifi-manager_0.0.3/src/json.h b/dist/esp32-wifi-manager_0.0.3/src/json.h new file mode 100644 index 00000000..4c1c8c75 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/json.h @@ -0,0 +1,47 @@ +/* +@file json.h +@brief handles very basic JSON with a minimal footprint on the system + +This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license: +Copyright (c) 2009 Dave Gamble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +@see https://github.com/DaveGamble/cJSON +*/ + +#ifndef JSON_H_INCLUDED +#define JSON_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Render the cstring provided to a JSON escaped version that can be printed. + * @param input the input buffer to be escaped. + * @param output_buffer the output buffer to write to. You must ensure it is big enough to contain the final string. + * @see cJSON equivlaent static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) + */ +bool json_print_string(const unsigned char *input, unsigned char *output_buffer); + +#ifdef __cplusplus +} +#endif + +#endif /* JSON_H_INCLUDED */ diff --git a/dist/esp32-wifi-manager_0.0.3/src/lock.svg b/dist/esp32-wifi-manager_0.0.3/src/lock.svg new file mode 100644 index 00000000..f7ebc7e9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/lock.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/nvs_sync.c b/dist/esp32-wifi-manager_0.0.3/src/nvs_sync.c new file mode 100644 index 00000000..4ceed8bd --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/nvs_sync.c @@ -0,0 +1,79 @@ +/** +Copyright (c) 2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file nvs_sync.c +@author Tony Pottier +@brief Exposes a simple API to synchronize NVS memory read and writes + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include "nvs_sync.h" + + +static SemaphoreHandle_t nvs_sync_mutex = NULL; + +esp_err_t nvs_sync_create(){ + if(nvs_sync_mutex == NULL){ + + nvs_sync_mutex = xSemaphoreCreateMutex(); + + if(nvs_sync_mutex){ + return ESP_OK; + } + else{ + return ESP_FAIL; + } + } + else{ + return ESP_OK; + } +} + +void nvs_sync_free(){ + if(nvs_sync_mutex != NULL){ + vSemaphoreDelete( nvs_sync_mutex ); + nvs_sync_mutex = NULL; + } +} + +bool nvs_sync_lock(TickType_t xTicksToWait){ + if(nvs_sync_mutex){ + if( xSemaphoreTake( nvs_sync_mutex, xTicksToWait ) == pdTRUE ) { + return true; + } + else{ + return false; + } + } + else{ + return false; + } +} + +void nvs_sync_unlock(){ + xSemaphoreGive( nvs_sync_mutex ); +} \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/nvs_sync.h b/dist/esp32-wifi-manager_0.0.3/src/nvs_sync.h new file mode 100644 index 00000000..e03396e5 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/nvs_sync.h @@ -0,0 +1,76 @@ +/** +Copyright (c) 2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file nvs_sync.h +@author Tony Pottier +@brief Exposes a simple API to synchronize NVS memory read and writes + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + + + +#ifndef WIFI_MANAGER_NVS_SYNC_H_INCLUDED +#define WIFI_MANAGER_NVS_SYNC_H_INCLUDED + +#include /* for type bool */ +#include /* for TickType_t */ +#include /* for esp_err_t */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Attempts to get hold of the NVS semaphore for a set amount of ticks. + * @note If you are uncertain about the number of ticks to wait use portMAX_DELAY. + * @return true on a succesful lock, false otherwise + */ +bool nvs_sync_lock(TickType_t xTicksToWait); + + +/** + * @brief Releases the NVS semaphore + */ +void nvs_sync_unlock(); + + +/** + * @brief Create the NVS semaphore + * @return ESP_OK: success or if the semaphore already exists + * ESP_FAIL: failure + */ +esp_err_t nvs_sync_create(); + +/** + * @brief Frees memory associated with the NVS semaphore + * @warning Do not delete a semaphore that has tasks blocked on it (tasks that are in the Blocked state waiting for the semaphore to become available). + */ +void nvs_sync_free(); + + +#ifdef __cplusplus +} +#endif + +#endif /* WIFI_MANAGER_NVS_SYNC_H_INCLUDED */ \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/settings.svg b/dist/esp32-wifi-manager_0.0.3/src/settings.svg new file mode 100644 index 00000000..19cacede --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/settings.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/status b/dist/esp32-wifi-manager_0.0.3/src/status new file mode 100644 index 00000000..3824a535 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/status @@ -0,0 +1 @@ +{"ssid":"zodmgbbq","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":0} \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/style.css b/dist/esp32-wifi-manager_0.0.3/src/style.css new file mode 100644 index 00000000..5c83cc2f --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/style.css @@ -0,0 +1,248 @@ +body { + background-color: #eee; + border: 0; + margin: 0; + font: 1.1em tahoma, arial, sans-serif; +} +a { + color: darkblue; + transition: color .2s ease-out; + text-decoration: none +} +a:hover { + color: red; +} +input { + display: none; + font: 1.1em tahoma, arial, sans-serif; +} +input:focus, +select:focus, +textarea:focus, +button:focus { + outline: none; +} +input[type="button"] { + width: 100px; + padding: 5px; + text-align: center; + display: block; +} +p { + padding: 10px; +} +#credits { + display: none; +} +#app {} #app-wrap {} #disconnect { + width: 150px; +} +.diag-box { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + height: 100%; + width: 100%; + display: none; +} +.diag-box-win { + position: absolute; + left: 10%; + width: 80%; + text-align: center; + border: 2px outset #888; + background-color: #fff; + border-radius: 10px; + top: 20%; +} +.blur { + -webkit-filter: blur(2px); + -moz-filter: blur(2px); + -ms-filter: blur(2px); + -o-filter: blur(2px); + filter: blur(2px); +} +.ape { + margin-left: 20px; + padding: 10px 0px 10px 10px; +} +.ape:hover { + cursor: pointer; +} +.brdb { + border-bottom: 1px solid #888; +} +header { + background-color: #fff; + border-bottom: 1px solid #888; +} +section { + background-color: #fff; + border-bottom: 1px solid #888; + border-top: 1px solid #888; +} +h1 { + display: block; + text-align: center; + margin: 0; + padding: 15px; + font-size: 1.4em +} +h2 { + margin: 0; + margin-top: 20px; + padding: 10px; + text-transform: uppercase; + color: #888; + font-size: 1.0em +} +h3 { + margin: 0; + text-align: center; + padding: 20px 0px 20px 0px; +} +.gr { + color: green; +} +.rd { + color: red; +} +#wifi-status { + display: none; +} +#connect { + display: none; +} +#connect_manual { + display: none; +} +#manual_ssid { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +#manual_pwd { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +#pwd { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +.buttons { + padding: 15px; +} +#join { + float: right; +} +#manual_join { + float: right; +} +#yes-disconnect { + display: inline-block; + margin-left: 20px; +} +#no-disconnect { + display: inline-block; +} +.ctr { + margin: 0 auto; +} +.tctr { + text-align: center; +} +#connect-wait { + display: none; +} +#connect-success { + display: none; +} +#connect-fail { + display: none; +} +#connect-details { + display: none; +} +.fr { + float: right; + margin-right: 20px; +} +.w0 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPHBhdGggZD0iTTUsMTNMNywxNUM5Ljc2LDEyLjI0IDE0LjI0LDEyLjI0IDE3LDE1TDE5LDEzQzE1LjE0LDkuMTQgOC44Nyw5LjE0IDUsMTNaIiBzdHlsZT0iZmlsbDogYmxhY2s7Ii8+CjxwYXRoIGQ9Ik05LDE3TDEyLDIwTDE1LDE3QzEzLjM1LDE1LjM0IDEwLjY2LDE1LjM0IDksMTdaIiBzdHlsZT0iZmlsbDogYmxhY2s7Ii8+Cjwvc3ZnPgo=') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w1 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPHBhdGggZD0iTTksMTdMMTIsMjBMMTUsMTdDMTMuMzUsMTUuMzQgMTAuNjYsMTUuMzQgOSwxN1oiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPC9zdmc+Cg==') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w2 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNOSwxN0wxMiwyMEwxNSwxN0MxMy4zNSwxNS4zNCAxMC42NiwxNS4zNCA5LDE3WiIgc3R5bGU9ImZpbGw6IGJsYWNrOyIvPgo8L3N2Zz4K') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w3 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNOSwxN0wxMiwyMEwxNSwxN0MxMy4zNSwxNS4zNCAxMC42NiwxNS4zNCA5LDE3WiIgc3R5bGU9ImZpbGw6IGdyYXk7Ii8+Cjwvc3ZnPgo=') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.pw { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIHN0eWxlPSJmaWxsOiBibGFjazsiIGQ9Ik0xOCA4aC0xVjZjMC0yLjc2LTIuMjQtNS01LTVTNyAzLjI0IDcgNnYySDZjLTEuMSAwLTIgLjktMiAydjEwYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWMTBjMC0xLjEtLjktMi0yLTJ6bS02IDljLTEuMSAwLTItLjktMi0ycy45LTIgMi0yIDIgLjkgMiAyLS45IDItMiAyem0zLjEtOUg4LjlWNmMwLTEuNzEgMS4zOS0zLjEgMy4xLTMuMSAxLjcxIDAgMy4xIDEuMzkgMy4xIDMuMXYyeiI+PC9wYXRoPgo8L3N2Zz4=') no-repeat right top; + height: 24px; + margin-right: 30px; +} +/* SpinKit is licensed under the MIT License. Copyright (c) 2015 Tobias Ahlin */ + +.spinner { + width: 40px; + height: 40px; + position: relative; + margin: 100px auto; +} +.double-bounce1, +.double-bounce2 { + width: 100%; + height: 100%; + border-radius: 50%; + background-color: #333; + opacity: 0.6; + position: absolute; + top: 0; + left: 0; + -webkit-animation: sk-bounce 2.0s infinite ease-in-out; + animation: sk-bounce 2.0s infinite ease-in-out; +} +.double-bounce2 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} +@-webkit-keyframes sk-bounce { + 0%, 100% { + -webkit-transform: scale(0.0) + } + 50% { + -webkit-transform: scale(1.0) + } +} +@keyframes sk-bounce { + 0%, 100% { + transform: scale(0.0); + -webkit-transform: scale(0.0); + } + 50% { + transform: scale(1.0); + -webkit-transform: scale(1.0); + } +} +/* end of SpinKit */ \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.3/src/wifi0.svg b/dist/esp32-wifi-manager_0.0.3/src/wifi0.svg new file mode 100644 index 00000000..394a6495 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/wifi0.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.3/src/wifi1.svg b/dist/esp32-wifi-manager_0.0.3/src/wifi1.svg new file mode 100644 index 00000000..c64211bf --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/wifi1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.3/src/wifi2.svg b/dist/esp32-wifi-manager_0.0.3/src/wifi2.svg new file mode 100644 index 00000000..033b9de9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/wifi2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.3/src/wifi3.svg b/dist/esp32-wifi-manager_0.0.3/src/wifi3.svg new file mode 100644 index 00000000..7cd5667e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/wifi3.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.3/src/wifi_manager.c b/dist/esp32-wifi-manager_0.0.3/src/wifi_manager.c new file mode 100644 index 00000000..3d11a940 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/wifi_manager.c @@ -0,0 +1,1336 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file wifi_manager.c +@author Tony Pottier +@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis + +Contains the freeRTOS task and all necessary support + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include +#include "esp_system.h" +#include +#include +#include +#include +#include +#include +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_wifi_types.h" +#include "esp_log.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "mdns.h" +#include "lwip/api.h" +#include "lwip/err.h" +#include "lwip/netdb.h" +#include "lwip/ip4_addr.h" + + +#include "json.h" +#include "dns_server.h" +#include "nvs_sync.h" +#include "wifi_manager.h" + + + +/* objects used to manipulate the main queue of events */ +QueueHandle_t wifi_manager_queue; + +/* @brief software timer to wait between each connection retry. + * There is no point hogging a hardware timer for a functionality like this which only needs to be 'accurate enough' */ +TimerHandle_t wifi_manager_retry_timer = NULL; + +/* @brief software timer that will trigger shutdown of the AP after a succesful STA connection + * There is no point hogging a hardware timer for a functionality like this which only needs to be 'accurate enough' */ +TimerHandle_t wifi_manager_shutdown_ap_timer = NULL; + +SemaphoreHandle_t wifi_manager_json_mutex = NULL; +SemaphoreHandle_t wifi_manager_sta_ip_mutex = NULL; +char *wifi_manager_sta_ip = NULL; +uint16_t ap_num = MAX_AP_NUM; +wifi_ap_record_t *accessp_records; +char *accessp_json = NULL; +char *ip_info_json = NULL; +wifi_config_t* wifi_manager_config_sta = NULL; + +/* @brief Array of callback function pointers */ +void (**cb_ptr_arr)(void*) = NULL; + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "wifi_manager"; + +/* @brief task handle for the main wifi_manager task */ +static TaskHandle_t task_wifi_manager = NULL; + +/* @brief netif object for the STATION */ +static esp_netif_t* esp_netif_sta = NULL; + +/* @brief netif object for the ACCESS POINT */ +static esp_netif_t* esp_netif_ap = NULL; + +/** + * The actual WiFi settings in use + */ +struct wifi_settings_t wifi_settings = { + .ap_ssid = DEFAULT_AP_SSID, + .ap_pwd = DEFAULT_AP_PASSWORD, + .ap_channel = DEFAULT_AP_CHANNEL, + .ap_ssid_hidden = DEFAULT_AP_SSID_HIDDEN, + .ap_bandwidth = DEFAULT_AP_BANDWIDTH, + .sta_only = DEFAULT_STA_ONLY, + .sta_power_save = DEFAULT_STA_POWER_SAVE, + .sta_static_ip = 0, +}; + +const char wifi_manager_nvs_namespace[] = "espwifimgr"; + +static EventGroupHandle_t wifi_manager_event_group; + +/* @brief indicate that the ESP32 is currently connected. */ +const int WIFI_MANAGER_WIFI_CONNECTED_BIT = BIT0; + +const int WIFI_MANAGER_AP_STA_CONNECTED_BIT = BIT1; + +/* @brief Set automatically once the SoftAP is started */ +const int WIFI_MANAGER_AP_STARTED_BIT = BIT2; + +/* @brief When set, means a client requested to connect to an access point.*/ +const int WIFI_MANAGER_REQUEST_STA_CONNECT_BIT = BIT3; + +/* @brief This bit is set automatically as soon as a connection was lost */ +const int WIFI_MANAGER_STA_DISCONNECT_BIT = BIT4; + +/* @brief When set, means the wifi manager attempts to restore a previously saved connection at startup. */ +const int WIFI_MANAGER_REQUEST_RESTORE_STA_BIT = BIT5; + +/* @brief When set, means a client requested to disconnect from currently connected AP. */ +const int WIFI_MANAGER_REQUEST_WIFI_DISCONNECT_BIT = BIT6; + +/* @brief When set, means a scan is in progress */ +const int WIFI_MANAGER_SCAN_BIT = BIT7; + +/* @brief When set, means user requested for a disconnect */ +const int WIFI_MANAGER_REQUEST_DISCONNECT_BIT = BIT8; + + + +void wifi_manager_timer_retry_cb( TimerHandle_t xTimer ){ + + ESP_LOGI(TAG, "Retry Timer Tick! Sending ORDER_CONNECT_STA with reason CONNECTION_REQUEST_AUTO_RECONNECT"); + + /* stop the timer */ + xTimerStop( xTimer, (TickType_t) 0 ); + + /* Attempt to reconnect */ + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_AUTO_RECONNECT); + +} + +void wifi_manager_timer_shutdown_ap_cb( TimerHandle_t xTimer){ + + /* stop the timer */ + xTimerStop( xTimer, (TickType_t) 0 ); + + /* Attempt to shutdown AP */ + wifi_manager_send_message(WM_ORDER_STOP_AP, NULL); +} + +void wifi_manager_scan_async(){ + wifi_manager_send_message(WM_ORDER_START_WIFI_SCAN, NULL); +} + +void wifi_manager_disconnect_async(){ + wifi_manager_send_message(WM_ORDER_DISCONNECT_STA, NULL); +} + + +void wifi_manager_start(){ + + /* disable the default wifi logging */ + esp_log_level_set("wifi", ESP_LOG_NONE); + + /* initialize flash memory */ + nvs_flash_init(); + ESP_ERROR_CHECK(nvs_sync_create()); /* semaphore for thread synchronization on NVS memory */ + + /* memory allocation */ + wifi_manager_queue = xQueueCreate( 3, sizeof( queue_message) ); + wifi_manager_json_mutex = xSemaphoreCreateMutex(); + accessp_records = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * MAX_AP_NUM); + accessp_json = (char*)malloc(MAX_AP_NUM * JSON_ONE_APP_SIZE + 4); /* 4 bytes for json encapsulation of "[\n" and "]\0" */ + wifi_manager_clear_access_points_json(); + ip_info_json = (char*)malloc(sizeof(char) * JSON_IP_INFO_SIZE); + wifi_manager_clear_ip_info_json(); + wifi_manager_config_sta = (wifi_config_t*)malloc(sizeof(wifi_config_t)); + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + memset(&wifi_settings.sta_static_ip_config, 0x00, sizeof(esp_netif_ip_info_t)); + cb_ptr_arr = malloc(sizeof(void (*)(void*)) * WM_MESSAGE_CODE_COUNT); + for(int i=0; ista.ssid) != 0){ + /* different ssid or ssid does not exist in flash: save new ssid */ + esp_err = nvs_set_blob(handle, "ssid", wifi_manager_config_sta->sta.ssid, 32); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + ESP_LOGI(TAG, "wifi_manager_wrote wifi_sta_config: ssid:%s",wifi_manager_config_sta->sta.ssid); + + } + + sz = sizeof(tmp_conf.sta.password); + esp_err = nvs_get_blob(handle, "password", tmp_conf.sta.password, &sz); + if( (esp_err == ESP_OK || esp_err == ESP_ERR_NVS_NOT_FOUND) && strcmp( (char*)tmp_conf.sta.password, (char*)wifi_manager_config_sta->sta.password) != 0){ + /* different password or password does not exist in flash: save new password */ + esp_err = nvs_set_blob(handle, "password", wifi_manager_config_sta->sta.password, 64); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + ESP_LOGI(TAG, "wifi_manager_wrote wifi_sta_config: password:%s",wifi_manager_config_sta->sta.password); + } + + sz = sizeof(tmp_settings); + esp_err = nvs_get_blob(handle, "settings", &tmp_settings, &sz); + if( (esp_err == ESP_OK || esp_err == ESP_ERR_NVS_NOT_FOUND) && + ( + strcmp( (char*)tmp_settings.ap_ssid, (char*)wifi_settings.ap_ssid) != 0 || + strcmp( (char*)tmp_settings.ap_pwd, (char*)wifi_settings.ap_pwd) != 0 || + tmp_settings.ap_ssid_hidden != wifi_settings.ap_ssid_hidden || + tmp_settings.ap_bandwidth != wifi_settings.ap_bandwidth || + tmp_settings.sta_only != wifi_settings.sta_only || + tmp_settings.sta_power_save != wifi_settings.sta_power_save || + tmp_settings.ap_channel != wifi_settings.ap_channel + ) + ){ + esp_err = nvs_set_blob(handle, "settings", &wifi_settings, sizeof(wifi_settings)); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_ssid: %s",wifi_settings.ap_ssid); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_pwd: %s",wifi_settings.ap_pwd); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_channel: %i",wifi_settings.ap_channel); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_hidden (1 = yes): %i",wifi_settings.ap_ssid_hidden); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_bandwidth (1 = 20MHz, 2 = 40MHz): %i",wifi_settings.ap_bandwidth); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_only (0 = APSTA, 1 = STA when connected): %i",wifi_settings.sta_only); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_power_save (1 = yes): %i",wifi_settings.sta_power_save); + } + + if(change){ + esp_err = nvs_commit(handle); + } + else{ + ESP_LOGI(TAG, "Wifi config was not saved to flash because no change has been detected."); + } + + if (esp_err != ESP_OK) return esp_err; + + nvs_close(handle); + nvs_sync_unlock(); + + } + else{ + ESP_LOGE(TAG, "wifi_manager_save_sta_config failed to acquire nvs_sync mutex"); + } + + return ESP_OK; +} + +bool wifi_manager_fetch_wifi_sta_config(){ + + nvs_handle handle; + esp_err_t esp_err; + if(nvs_sync_lock( portMAX_DELAY )){ + + esp_err = nvs_open(wifi_manager_nvs_namespace, NVS_READONLY, &handle); + + if(esp_err != ESP_OK){ + nvs_sync_unlock(); + return false; + } + + if(wifi_manager_config_sta == NULL){ + wifi_manager_config_sta = (wifi_config_t*)malloc(sizeof(wifi_config_t)); + } + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + + /* allocate buffer */ + size_t sz = sizeof(wifi_settings); + uint8_t *buff = (uint8_t*)malloc(sizeof(uint8_t) * sz); + memset(buff, 0x00, sizeof(sz)); + + /* ssid */ + sz = sizeof(wifi_manager_config_sta->sta.ssid); + esp_err = nvs_get_blob(handle, "ssid", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(wifi_manager_config_sta->sta.ssid, buff, sz); + + /* password */ + sz = sizeof(wifi_manager_config_sta->sta.password); + esp_err = nvs_get_blob(handle, "password", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(wifi_manager_config_sta->sta.password, buff, sz); + + /* settings */ + sz = sizeof(wifi_settings); + esp_err = nvs_get_blob(handle, "settings", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(&wifi_settings, buff, sz); + + free(buff); + nvs_close(handle); + nvs_sync_unlock(); + + + ESP_LOGI(TAG, "wifi_manager_fetch_wifi_sta_config: ssid:%s password:%s",wifi_manager_config_sta->sta.ssid,wifi_manager_config_sta->sta.password); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_ssid:%s",wifi_settings.ap_ssid); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_pwd:%s",wifi_settings.ap_pwd); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_channel:%i",wifi_settings.ap_channel); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_hidden (1 = yes):%i",wifi_settings.ap_ssid_hidden); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_bandwidth (1 = 20MHz, 2 = 40MHz)%i",wifi_settings.ap_bandwidth); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_only (0 = APSTA, 1 = STA when connected):%i",wifi_settings.sta_only); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_power_save (1 = yes):%i",wifi_settings.sta_power_save); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_static_ip (0 = dhcp client, 1 = static ip):%i",wifi_settings.sta_static_ip); + + return wifi_manager_config_sta->sta.ssid[0] != '\0'; + + + } + else{ + return false; + } + +} + + +void wifi_manager_clear_ip_info_json(){ + strcpy(ip_info_json, "{}\n"); +} + + +void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code){ + + wifi_config_t *config = wifi_manager_get_wifi_sta_config(); + if(config){ + + const char *ip_info_json_format = ",\"ip\":\"%s\",\"netmask\":\"%s\",\"gw\":\"%s\",\"urc\":%d}\n"; + + memset(ip_info_json, 0x00, JSON_IP_INFO_SIZE); + + /* to avoid declaring a new buffer we copy the data directly into the buffer at its correct address */ + strcpy(ip_info_json, "{\"ssid\":"); + json_print_string(config->sta.ssid, (unsigned char*)(ip_info_json+strlen(ip_info_json)) ); + + size_t ip_info_json_len = strlen(ip_info_json); + size_t remaining = JSON_IP_INFO_SIZE - ip_info_json_len; + if(update_reason_code == UPDATE_CONNECTION_OK){ + /* rest of the information is copied after the ssid */ + esp_netif_ip_info_t ip_info; + ESP_ERROR_CHECK(esp_netif_get_ip_info(esp_netif_sta, &ip_info)); + + char ip[IP4ADDR_STRLEN_MAX]; /* note: IP4ADDR_STRLEN_MAX is defined in lwip */ + char gw[IP4ADDR_STRLEN_MAX]; + char netmask[IP4ADDR_STRLEN_MAX]; + + esp_ip4addr_ntoa(&ip_info.ip, ip, IP4ADDR_STRLEN_MAX); + esp_ip4addr_ntoa(&ip_info.gw, gw, IP4ADDR_STRLEN_MAX); + esp_ip4addr_ntoa(&ip_info.netmask, netmask, IP4ADDR_STRLEN_MAX); + + + snprintf( (ip_info_json + ip_info_json_len), remaining, ip_info_json_format, + ip, + netmask, + gw, + (int)update_reason_code); + } + else{ + /* notify in the json output the reason code why this was updated without a connection */ + snprintf( (ip_info_json + ip_info_json_len), remaining, ip_info_json_format, + "0", + "0", + "0", + (int)update_reason_code); + } + } + else{ + wifi_manager_clear_ip_info_json(); + } + + +} + + +void wifi_manager_clear_access_points_json(){ + strcpy(accessp_json, "[]\n"); +} +void wifi_manager_generate_acess_points_json(){ + + strcpy(accessp_json, "["); + + + const char oneap_str[] = ",\"chan\":%d,\"rssi\":%d,\"auth\":%d}%c\n"; + + /* stack buffer to hold on to one AP until it's copied over to accessp_json */ + char one_ap[JSON_ONE_APP_SIZE]; + for(int i=0; i and + * . + * + * Another thing deserves our attention is that the default behavior of LwIP is to abort all TCP socket connections on + * receiving the disconnect. Most of time it is not a problem. However, for some special application, this may not be + * what they want, consider following scenarios: + * + * The application creates a TCP connection to maintain the application-level keep-alive data that is sent out + * every 60 seconds. + * + * Due to certain reasons, the Wi-Fi connection is cut off, and the is raised. + * According to the current implementation, all TCP connections will be removed and the keep-alive socket will be + * in a wrong status. However, since the application designer believes that the network layer should NOT care about + * this error at the Wi-Fi layer, the application does not close the socket. + * + * Five seconds later, the Wi-Fi connection is restored because esp_wifi_connect() is called in the application + * event callback function. Moreover, the station connects to the same AP and gets the same IPV4 address as before. + * + * Sixty seconds later, when the application sends out data with the keep-alive socket, the socket returns an error + * and the application closes the socket and re-creates it when necessary. + * + * In above scenario, ideally, the application sockets and the network layer should not be affected, since the Wi-Fi + * connection only fails temporarily and recovers very quickly. The application can enable “Keep TCP connections when + * IP changed” via LwIP menuconfig.*/ + case WIFI_EVENT_STA_DISCONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED"); + + wifi_event_sta_disconnected_t* wifi_event_sta_disconnected = (wifi_event_sta_disconnected_t*)malloc(sizeof(wifi_event_sta_disconnected_t)); + *wifi_event_sta_disconnected = *( (wifi_event_sta_disconnected_t*)event_data ); + + /* if a DISCONNECT message is posted while a scan is in progress this scan will NEVER end, causing scan to never work again. For this reason SCAN_BIT is cleared too */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT | WIFI_MANAGER_SCAN_BIT); + + /* post disconnect event with reason code */ + wifi_manager_send_message(WM_EVENT_STA_DISCONNECTED, (void*)wifi_event_sta_disconnected ); + break; + + /* This event arises when the AP to which the station is connected changes its authentication mode, e.g., from no auth + * to WPA. Upon receiving this event, the event task will do nothing. Generally, the application event callback does + * not need to handle this either. */ + case WIFI_EVENT_STA_AUTHMODE_CHANGE: + ESP_LOGI(TAG, "WIFI_EVENT_STA_AUTHMODE_CHANGE"); + break; + + case WIFI_EVENT_AP_START: + ESP_LOGI(TAG, "WIFI_EVENT_AP_START"); + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STARTED_BIT); + break; + + case WIFI_EVENT_AP_STOP: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STOP"); + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_AP_STARTED_BIT); + break; + + /* Every time a station is connected to ESP32 AP, the will arise. Upon receiving this + * event, the event task will do nothing, and the application callback can also ignore it. However, you may want + * to do something, for example, to get the info of the connected STA, etc. */ + case WIFI_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STACONNECTED"); + break; + + /* This event can happen in the following scenarios: + * The application calls esp_wifi_disconnect(), or esp_wifi_deauth_sta(), to manually disconnect the station. + * The Wi-Fi driver kicks off the station, e.g. because the AP has not received any packets in the past five minutes, etc. + * The station kicks off the AP. + * When this event happens, the event task will do nothing, but the application event callback needs to do + * something, e.g., close the socket which is related to this station, etc. */ + case WIFI_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STADISCONNECTED"); + break; + + /* This event is disabled by default. The application can enable it via API esp_wifi_set_event_mask(). + * When this event is enabled, it will be raised each time the AP receives a probe request. */ + case WIFI_EVENT_AP_PROBEREQRECVED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_PROBEREQRECVED"); + break; + + } /* end switch */ + } + else if(event_base == IP_EVENT){ + + switch(event_id){ + + /* This event arises when the DHCP client successfully gets the IPV4 address from the DHCP server, + * or when the IPV4 address is changed. The event means that everything is ready and the application can begin + * its tasks (e.g., creating sockets). + * The IPV4 may be changed because of the following reasons: + * The DHCP client fails to renew/rebind the IPV4 address, and the station’s IPV4 is reset to 0. + * The DHCP client rebinds to a different address. + * The static-configured IPV4 address is changed. + * Whether the IPV4 address is changed or NOT is indicated by field ip_change of ip_event_got_ip_t. + * The socket is based on the IPV4 address, which means that, if the IPV4 changes, all sockets relating to this + * IPV4 will become abnormal. Upon receiving this event, the application needs to close all sockets and recreate + * the application when the IPV4 changes to a valid one. */ + case IP_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "IP_EVENT_STA_GOT_IP"); + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT); + ip_event_got_ip_t* ip_event_got_ip = (ip_event_got_ip_t*)malloc(sizeof(ip_event_got_ip_t)); + *ip_event_got_ip = *( (ip_event_got_ip_t*)event_data ); + wifi_manager_send_message(WM_EVENT_STA_GOT_IP, (void*)(ip_event_got_ip) ); + break; + + /* This event arises when the IPV6 SLAAC support auto-configures an address for the ESP32, or when this address changes. + * The event means that everything is ready and the application can begin its tasks (e.g., creating sockets). */ + case IP_EVENT_GOT_IP6: + ESP_LOGI(TAG, "IP_EVENT_GOT_IP6"); + break; + + /* This event arises when the IPV4 address become invalid. + * IP_STA_LOST_IP doesn’t arise immediately after the WiFi disconnects, instead it starts an IPV4 address lost timer, + * if the IPV4 address is got before ip lost timer expires, IP_EVENT_STA_LOST_IP doesn’t happen. Otherwise, the event + * arises when IPV4 address lost timer expires. + * Generally the application don’t need to care about this event, it is just a debug event to let the application + * know that the IPV4 address is lost. */ + case IP_EVENT_STA_LOST_IP: + ESP_LOGI(TAG, "IP_EVENT_STA_LOST_IP"); + break; + + } + } + +} + + +wifi_config_t* wifi_manager_get_wifi_sta_config(){ + return wifi_manager_config_sta; +} + + +void wifi_manager_connect_async(){ + /* in order to avoid a false positive on the front end app we need to quickly flush the ip json + * There'se a risk the front end sees an IP or a password error when in fact + * it's a remnant from a previous connection + */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_clear_ip_info_json(); + wifi_manager_unlock_json_buffer(); + } + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_USER); +} + + +char* wifi_manager_get_ip_info_json(){ + return ip_info_json; +} + + +void wifi_manager_destroy(){ + + vTaskDelete(task_wifi_manager); + task_wifi_manager = NULL; + + /* heap buffers */ + free(accessp_records); + accessp_records = NULL; + free(accessp_json); + accessp_json = NULL; + free(ip_info_json); + ip_info_json = NULL; + free(wifi_manager_sta_ip); + wifi_manager_sta_ip = NULL; + if(wifi_manager_config_sta){ + free(wifi_manager_config_sta); + wifi_manager_config_sta = NULL; + } + + /* RTOS objects */ + vSemaphoreDelete(wifi_manager_json_mutex); + wifi_manager_json_mutex = NULL; + vSemaphoreDelete(wifi_manager_sta_ip_mutex); + wifi_manager_sta_ip_mutex = NULL; + vEventGroupDelete(wifi_manager_event_group); + wifi_manager_event_group = NULL; + vQueueDelete(wifi_manager_queue); + wifi_manager_queue = NULL; + + +} + + +void wifi_manager_filter_unique( wifi_ap_record_t * aplist, uint16_t * aps) { + int total_unique; + wifi_ap_record_t * first_free; + total_unique=*aps; + + first_free=NULL; + + for(int i=0; i<*aps-1;i++) { + wifi_ap_record_t * ap = &aplist[i]; + + /* skip the previously removed APs */ + if (ap->ssid[0] == 0) continue; + + /* remove the identical SSID+authmodes */ + for(int j=i+1; j<*aps;j++) { + wifi_ap_record_t * ap1 = &aplist[j]; + if ( (strcmp((const char *)ap->ssid, (const char *)ap1->ssid)==0) && + (ap->authmode == ap1->authmode) ) { /* same SSID, different auth mode is skipped */ + /* save the rssi for the display */ + if ((ap1->rssi) > (ap->rssi)) ap->rssi=ap1->rssi; + /* clearing the record */ + memset(ap1,0, sizeof(wifi_ap_record_t)); + } + } + } + /* reorder the list so APs follow each other in the list */ + for(int i=0; i<*aps;i++) { + wifi_ap_record_t * ap = &aplist[i]; + /* skipping all that has no name */ + if (ap->ssid[0] == 0) { + /* mark the first free slot */ + if (first_free==NULL) first_free=ap; + total_unique--; + continue; + } + if (first_free!=NULL) { + memcpy(first_free, ap, sizeof(wifi_ap_record_t)); + memset(ap,0, sizeof(wifi_ap_record_t)); + /* find the next free slot */ + for(int j=0; j<*aps;j++) { + if (aplist[j].ssid[0]==0) { + first_free=&aplist[j]; + break; + } + } + } + } + /* update the length of the list */ + *aps = total_unique; +} + + +BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param){ + queue_message msg; + msg.code = code; + msg.param = param; + return xQueueSendToFront( wifi_manager_queue, &msg, portMAX_DELAY); +} + +BaseType_t wifi_manager_send_message(message_code_t code, void *param){ + queue_message msg; + msg.code = code; + msg.param = param; + return xQueueSend( wifi_manager_queue, &msg, portMAX_DELAY); +} + + +void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) ){ + + if(cb_ptr_arr && message_code < WM_MESSAGE_CODE_COUNT){ + cb_ptr_arr[message_code] = func_ptr; + } +} + +esp_netif_t* wifi_manager_get_esp_netif_ap(){ + return esp_netif_ap; +} + +esp_netif_t* wifi_manager_get_esp_netif_sta(){ + return esp_netif_sta; +} + +void wifi_manager( void * pvParameters ){ + + + queue_message msg; + BaseType_t xStatus; + EventBits_t uxBits; + uint8_t retries = 0; + + + /* initialize the tcp stack */ + ESP_ERROR_CHECK(esp_netif_init()); + + /* event loop for the wifi driver */ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + esp_netif_sta = esp_netif_create_default_wifi_sta(); + esp_netif_ap = esp_netif_create_default_wifi_ap(); + + + /* default wifi config */ + wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + + /* event handler for the connection */ + esp_event_handler_instance_t instance_wifi_event; + esp_event_handler_instance_t instance_ip_event; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_manager_event_handler, NULL,&instance_wifi_event)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &wifi_manager_event_handler, NULL,&instance_ip_event)); + + + /* SoftAP - Wifi Access Point configuration setup */ + wifi_config_t ap_config = { + .ap = { + .ssid_len = 0, + .channel = wifi_settings.ap_channel, + .ssid_hidden = wifi_settings.ap_ssid_hidden, + .max_connection = DEFAULT_AP_MAX_CONNECTIONS, + .beacon_interval = DEFAULT_AP_BEACON_INTERVAL, + }, + }; + memcpy(ap_config.ap.ssid, wifi_settings.ap_ssid , sizeof(wifi_settings.ap_ssid)); + + /* if the password lenght is under 8 char which is the minium for WPA2, the access point starts as open */ + if(strlen( (char*)wifi_settings.ap_pwd) < WPA2_MINIMUM_PASSWORD_LENGTH){ + ap_config.ap.authmode = WIFI_AUTH_OPEN; + memset( ap_config.ap.password, 0x00, sizeof(ap_config.ap.password) ); + } + else{ + ap_config.ap.authmode = WIFI_AUTH_WPA2_PSK; + memcpy(ap_config.ap.password, wifi_settings.ap_pwd, sizeof(wifi_settings.ap_pwd)); + } + + + /* DHCP AP configuration */ + esp_netif_dhcps_stop(esp_netif_ap); /* DHCP client/server must be stopped before setting new IP information. */ + esp_netif_ip_info_t ap_ip_info; + memset(&ap_ip_info, 0x00, sizeof(ap_ip_info)); + inet_pton(AF_INET, DEFAULT_AP_IP, &ap_ip_info.ip); + inet_pton(AF_INET, DEFAULT_AP_GATEWAY, &ap_ip_info.gw); + inet_pton(AF_INET, DEFAULT_AP_NETMASK, &ap_ip_info.netmask); + ESP_ERROR_CHECK(esp_netif_set_ip_info(esp_netif_ap, &ap_ip_info)); + ESP_ERROR_CHECK(esp_netif_dhcps_start(esp_netif_ap)); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &ap_config)); + ESP_ERROR_CHECK(esp_wifi_set_bandwidth(WIFI_IF_AP, wifi_settings.ap_bandwidth)); + ESP_ERROR_CHECK(esp_wifi_set_ps(wifi_settings.sta_power_save)); + + + /* by default the mode is STA because wifi_manager will not start the access point unless it has to! */ + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + + /* start http server */ + http_app_start(false); + + /* wifi scanner config */ + wifi_scan_config_t scan_config = { + .ssid = 0, + .bssid = 0, + .channel = 0, + .show_hidden = true + }; + + /* enqueue first event: load previous config */ + wifi_manager_send_message(WM_ORDER_LOAD_AND_RESTORE_STA, NULL); + + + /* main processing loop */ + for(;;){ + xStatus = xQueueReceive( wifi_manager_queue, &msg, portMAX_DELAY ); + + if( xStatus == pdPASS ){ + switch(msg.code){ + + case WM_EVENT_SCAN_DONE:{ + wifi_event_sta_scan_done_t *evt_scan_done = (wifi_event_sta_scan_done_t*)msg.param; + /* only check for AP if the scan is succesful */ + if(evt_scan_done->status == 0){ + /* As input param, it stores max AP number ap_records can hold. As output param, it receives the actual AP number this API returns. + * As a consequence, ap_num MUST be reset to MAX_AP_NUM at every scan */ + ap_num = MAX_AP_NUM; + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_num, accessp_records)); + /* make sure the http server isn't trying to access the list while it gets refreshed */ + if(wifi_manager_lock_json_buffer( pdMS_TO_TICKS(1000) )){ + /* Will remove the duplicate SSIDs from the list and update ap_num */ + wifi_manager_filter_unique(accessp_records, &ap_num); + wifi_manager_generate_acess_points_json(); + wifi_manager_unlock_json_buffer(); + } + else{ + ESP_LOGE(TAG, "could not get access to json mutex in wifi_scan"); + } + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(evt_scan_done); + } + break; + + case WM_ORDER_START_WIFI_SCAN: + ESP_LOGD(TAG, "MESSAGE: ORDER_START_WIFI_SCAN"); + + /* if a scan is already in progress this message is simply ignored thanks to the WIFI_MANAGER_SCAN_BIT uxBit */ + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if(! (uxBits & WIFI_MANAGER_SCAN_BIT) ){ + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT); + ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, false)); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_LOAD_AND_RESTORE_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_LOAD_AND_RESTORE_STA"); + if(wifi_manager_fetch_wifi_sta_config()){ + ESP_LOGI(TAG, "Saved wifi found on startup. Will attempt to connect."); + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_RESTORE_CONNECTION); + } + else{ + /* no wifi saved: start soft AP! This is what should happen during a first run */ + ESP_LOGI(TAG, "No saved wifi found on startup. Starting access point."); + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_CONNECT_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_CONNECT_STA"); + + /* very important: precise that this connection attempt is specifically requested. + * Param in that case is a boolean indicating if the request was made automatically + * by the wifi_manager. + * */ + if((BaseType_t)msg.param == CONNECTION_REQUEST_USER) { + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + } + else if((BaseType_t)msg.param == CONNECTION_REQUEST_RESTORE_CONNECTION) { + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + } + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if( ! (uxBits & WIFI_MANAGER_WIFI_CONNECTED_BIT) ){ + /* update config to latest and attempt connection */ + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_manager_get_wifi_sta_config())); + + /* if there is a wifi scan in progress abort it first + Calling esp_wifi_scan_stop will trigger a SCAN_DONE event which will reset this bit */ + if(uxBits & WIFI_MANAGER_SCAN_BIT){ + esp_wifi_scan_stop(); + } + ESP_ERROR_CHECK(esp_wifi_connect()); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_EVENT_STA_DISCONNECTED: + ;wifi_event_sta_disconnected_t* wifi_event_sta_disconnected = (wifi_event_sta_disconnected_t*)msg.param; + ESP_LOGI(TAG, "MESSAGE: EVENT_STA_DISCONNECTED with Reason code: %d", wifi_event_sta_disconnected->reason); + + /* this even can be posted in numerous different conditions + * + * 1. SSID password is wrong + * 2. Manual disconnection ordered + * 3. Connection lost + * + * Having clear understand as to WHY the event was posted is key to having an efficient wifi manager + * + * With wifi_manager, we determine: + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT is set, We consider it's a client that requested the connection. + * When SYSTEM_EVENT_STA_DISCONNECTED is posted, it's probably a password/something went wrong with the handshake. + * + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT is set, it's a disconnection that was ASKED by the client (clicking disconnect in the app) + * When SYSTEM_EVENT_STA_DISCONNECTED is posted, saved wifi is erased from the NVS memory. + * + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT and WIFI_MANAGER_REQUEST_STA_CONNECT_BIT are NOT set, it's a lost connection + * + * In this version of the software, reason codes are not used. They are indicated here for potential future usage. + * + * REASON CODE: + * 1 UNSPECIFIED + * 2 AUTH_EXPIRE auth no longer valid, this smells like someone changed a password on the AP + * 3 AUTH_LEAVE + * 4 ASSOC_EXPIRE + * 5 ASSOC_TOOMANY too many devices already connected to the AP => AP fails to respond + * 6 NOT_AUTHED + * 7 NOT_ASSOCED + * 8 ASSOC_LEAVE tested as manual disconnect by user OR in the wireless MAC blacklist + * 9 ASSOC_NOT_AUTHED + * 10 DISASSOC_PWRCAP_BAD + * 11 DISASSOC_SUPCHAN_BAD + * 12 + * 13 IE_INVALID + * 14 MIC_FAILURE + * 15 4WAY_HANDSHAKE_TIMEOUT wrong password! This was personnaly tested on my home wifi with a wrong password. + * 16 GROUP_KEY_UPDATE_TIMEOUT + * 17 IE_IN_4WAY_DIFFERS + * 18 GROUP_CIPHER_INVALID + * 19 PAIRWISE_CIPHER_INVALID + * 20 AKMP_INVALID + * 21 UNSUPP_RSN_IE_VERSION + * 22 INVALID_RSN_IE_CAP + * 23 802_1X_AUTH_FAILED wrong password? + * 24 CIPHER_SUITE_REJECTED + * 200 BEACON_TIMEOUT + * 201 NO_AP_FOUND + * 202 AUTH_FAIL + * 203 ASSOC_FAIL + * 204 HANDSHAKE_TIMEOUT + * + * */ + + /* reset saved sta IP */ + wifi_manager_safe_update_sta_ip_string((uint32_t)0); + + /* if there was a timer on to stop the AP, well now it's time to cancel that since connection was lost! */ + if(xTimerIsTimerActive(wifi_manager_shutdown_ap_timer) == pdTRUE ){ + xTimerStop( wifi_manager_shutdown_ap_timer, (TickType_t)0 ); + } + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if( uxBits & WIFI_MANAGER_REQUEST_STA_CONNECT_BIT ){ + /* there are no retries when it's a user requested connection by design. This avoids a user hanging too much + * in case they typed a wrong password for instance. Here we simply clear the request bit and move on */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_FAILED_ATTEMPT ); + wifi_manager_unlock_json_buffer(); + } + + } + else if (uxBits & WIFI_MANAGER_REQUEST_DISCONNECT_BIT){ + /* user manually requested a disconnect so the lost connection is a normal event. Clear the flag and restart the AP */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_DISCONNECT_BIT); + + /* erase configuration */ + if(wifi_manager_config_sta){ + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + } + + /* regenerate json status */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_USER_DISCONNECT ); + wifi_manager_unlock_json_buffer(); + } + + /* save NVS memory */ + wifi_manager_save_sta_config(); + + /* start SoftAP */ + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + else{ + /* lost connection ? */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION ); + wifi_manager_unlock_json_buffer(); + } + + /* Start the timer that will try to restore the saved config */ + xTimerStart( wifi_manager_retry_timer, (TickType_t)0 ); + + /* if it was a restore attempt connection, we clear the bit */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + + /* if the AP is not started, we check if we have reached the threshold of failed attempt to start it */ + if(! (uxBits & WIFI_MANAGER_AP_STARTED_BIT) ){ + + /* if the nunber of retries is below the threshold to start the AP, a reconnection attempt is made + * This way we avoid restarting the AP directly in case the connection is mementarily lost */ + if(retries < WIFI_MANAGER_MAX_RETRY_START_AP){ + retries++; + } + else{ + /* In this scenario the connection was lost beyond repair: kick start the AP! */ + retries = 0; + + /* start SoftAP */ + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + } + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(wifi_event_sta_disconnected); + + break; + + case WM_ORDER_START_AP: + ESP_LOGI(TAG, "MESSAGE: ORDER_START_AP"); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); + + /* restart HTTP daemon */ + http_app_stop(); + http_app_start(true); + + /* start DNS */ + dns_server_start(); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_STOP_AP: + ESP_LOGI(TAG, "MESSAGE: ORDER_STOP_AP"); + + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + + /* before stopping the AP, we check that we are still connected. There's a chance that once the timer + * kicks in, for whatever reason the esp32 is already disconnected. + */ + if(uxBits & WIFI_MANAGER_WIFI_CONNECTED_BIT){ + + /* set to STA only */ + esp_wifi_set_mode(WIFI_MODE_STA); + + /* stop DNS */ + dns_server_stop(); + + /* restart HTTP daemon */ + http_app_stop(); + http_app_start(false); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + } + + break; + + case WM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "WM_EVENT_STA_GOT_IP"); + ip_event_got_ip_t* ip_event_got_ip = (ip_event_got_ip_t*)msg.param; + uxBits = xEventGroupGetBits(wifi_manager_event_group); + + /* reset connection requests bits -- doesn't matter if it was set or not */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + + /* save IP as a string for the HTTP server host */ + wifi_manager_safe_update_sta_ip_string(ip_event_got_ip->ip_info.ip.addr); + + /* save wifi config in NVS if it wasn't a restored of a connection */ + if(uxBits & WIFI_MANAGER_REQUEST_RESTORE_STA_BIT){ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + } + else{ + wifi_manager_save_sta_config(); + } + + /* reset number of retries */ + retries = 0; + + /* refresh JSON with the new IP */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + /* generate the connection info with success */ + wifi_manager_generate_ip_info_json( UPDATE_CONNECTION_OK ); + wifi_manager_unlock_json_buffer(); + } + else { abort(); } + + /* bring down DNS hijack */ + dns_server_stop(); + + /* start the timer that will eventually shutdown the access point + * We check first that it's actually running because in case of a boot and restore connection + * the AP is not even started to begin with. + */ + if(uxBits & WIFI_MANAGER_AP_STARTED_BIT){ + TickType_t t = pdMS_TO_TICKS( WIFI_MANAGER_SHUTDOWN_AP_TIMER ); + + /* if for whatever reason user configured the shutdown timer to be less than 1 tick, the AP is stopped straight away */ + if(t > 0){ + xTimerStart( wifi_manager_shutdown_ap_timer, (TickType_t)0 ); + } + else{ + wifi_manager_send_message(WM_ORDER_STOP_AP, (void*)NULL); + } + + } + + /* callback and free memory allocated for the void* param */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(ip_event_got_ip); + + break; + + case WM_ORDER_DISCONNECT_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_DISCONNECT_STA"); + + /* precise this is coming from a user request */ + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_DISCONNECT_BIT); + + /* order wifi discconect */ + ESP_ERROR_CHECK(esp_wifi_disconnect()); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + default: + break; + + } /* end of switch/case */ + } /* end of if status=pdPASS */ + } /* end of for loop */ + + vTaskDelete( NULL ); + +} + + diff --git a/dist/esp32-wifi-manager_0.0.3/src/wifi_manager.h b/dist/esp32-wifi-manager_0.0.3/src/wifi_manager.h new file mode 100644 index 00000000..cba3b989 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.3/src/wifi_manager.h @@ -0,0 +1,417 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file wifi_manager.h +@author Tony Pottier +@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis + +Contains the freeRTOS task and all necessary support + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#ifndef WIFI_MANAGER_H_INCLUDED +#define WIFI_MANAGER_H_INCLUDED + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Defines the maximum size of a SSID name. 32 is IEEE standard. + * @warning limit is also hard coded in wifi_config_t. Never extend this value. + */ +#define MAX_SSID_SIZE 32 + +/** + * @brief Defines the maximum size of a WPA2 passkey. 64 is IEEE standard. + * @warning limit is also hard coded in wifi_config_t. Never extend this value. + */ +#define MAX_PASSWORD_SIZE 64 + + +/** + * @brief Defines the maximum number of access points that can be scanned. + * + * To save memory and avoid nasty out of memory errors, + * we can limit the number of APs detected in a wifi scan. + */ +#define MAX_AP_NUM 15 + + +/** + * @brief Defines the maximum number of failed retries allowed before the WiFi manager starts its own access point. + * Setting it to 2 for instance means there will be 3 attempts in total (original request + 2 retries) + */ +#define WIFI_MANAGER_MAX_RETRY_START_AP CONFIG_WIFI_MANAGER_MAX_RETRY_START_AP + +/** + * @brief Time (in ms) between each retry attempt + * Defines the time to wait before an attempt to re-connect to a saved wifi is made after connection is lost or another unsuccesful attempt is made. + */ +#define WIFI_MANAGER_RETRY_TIMER CONFIG_WIFI_MANAGER_RETRY_TIMER + + +/** + * @brief Time (in ms) to wait before shutting down the AP + * Defines the time (in ms) to wait after a succesful connection before shutting down the access point. + */ +#define WIFI_MANAGER_SHUTDOWN_AP_TIMER CONFIG_WIFI_MANAGER_SHUTDOWN_AP_TIMER + + +/** @brief Defines the task priority of the wifi_manager. + * + * Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1. + * For this particular reason, minimum task priority is 1. It it highly not recommended to set + * it to 1 though as the sub-tasks will now have a priority of 0 which is the priority + * of freeRTOS' idle task. + */ +#define WIFI_MANAGER_TASK_PRIORITY CONFIG_WIFI_MANAGER_TASK_PRIORITY + +/** @brief Defines the auth mode as an access point + * Value must be of type wifi_auth_mode_t + * @see esp_wifi_types.h + * @warning if set to WIFI_AUTH_OPEN, passowrd me be empty. See DEFAULT_AP_PASSWORD. + */ +#define AP_AUTHMODE WIFI_AUTH_WPA2_PSK + +/** @brief Defines visibility of the access point. 0: visible AP. 1: hidden */ +#define DEFAULT_AP_SSID_HIDDEN 0 + +/** @brief Defines access point's name. Default value: esp32. Run 'make menuconfig' to setup your own value or replace here by a string */ +#define DEFAULT_AP_SSID CONFIG_DEFAULT_AP_SSID + +/** @brief Defines access point's password. + * @warning In the case of an open access point, the password must be a null string "" or "\0" if you want to be verbose but waste one byte. + * In addition, the AP_AUTHMODE must be WIFI_AUTH_OPEN + */ +#define DEFAULT_AP_PASSWORD CONFIG_DEFAULT_AP_PASSWORD + +/** @brief Defines the hostname broadcasted by mDNS */ +#define DEFAULT_HOSTNAME "esp32" + +/** @brief Defines access point's bandwidth. + * Value: WIFI_BW_HT20 for 20 MHz or WIFI_BW_HT40 for 40 MHz + * 20 MHz minimize channel interference but is not suitable for + * applications with high data speeds + */ +#define DEFAULT_AP_BANDWIDTH WIFI_BW_HT20 + +/** @brief Defines access point's channel. + * Channel selection is only effective when not connected to another AP. + * Good practice for minimal channel interference to use + * For 20 MHz: 1, 6 or 11 in USA and 1, 5, 9 or 13 in most parts of the world + * For 40 MHz: 3 in USA and 3 or 11 in most parts of the world + */ +#define DEFAULT_AP_CHANNEL CONFIG_DEFAULT_AP_CHANNEL + + + +/** @brief Defines the access point's default IP address. Default: "10.10.0.1 */ +#define DEFAULT_AP_IP CONFIG_DEFAULT_AP_IP + +/** @brief Defines the access point's gateway. This should be the same as your IP. Default: "10.10.0.1" */ +#define DEFAULT_AP_GATEWAY CONFIG_DEFAULT_AP_GATEWAY + +/** @brief Defines the access point's netmask. Default: "255.255.255.0" */ +#define DEFAULT_AP_NETMASK CONFIG_DEFAULT_AP_NETMASK + +/** @brief Defines access point's maximum number of clients. Default: 4 */ +#define DEFAULT_AP_MAX_CONNECTIONS CONFIG_DEFAULT_AP_MAX_CONNECTIONS + +/** @brief Defines access point's beacon interval. 100ms is the recommended default. */ +#define DEFAULT_AP_BEACON_INTERVAL CONFIG_DEFAULT_AP_BEACON_INTERVAL + +/** @brief Defines if esp32 shall run both AP + STA when connected to another AP. + * Value: 0 will have the own AP always on (APSTA mode) + * Value: 1 will turn off own AP when connected to another AP (STA only mode when connected) + * Turning off own AP when connected to another AP minimize channel interference and increase throughput + */ +#define DEFAULT_STA_ONLY 1 + +/** @brief Defines if wifi power save shall be enabled. + * Value: WIFI_PS_NONE for full power (wifi modem always on) + * Value: WIFI_PS_MODEM for power save (wifi modem sleep periodically) + * Note: Power save is only effective when in STA only mode + */ +#define DEFAULT_STA_POWER_SAVE WIFI_PS_NONE + +/** + * @brief Defines the maximum length in bytes of a JSON representation of an access point. + * + * maximum ap string length with full 32 char ssid: 75 + \\n + \0 = 77\n + * example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","chan":12,"rssi":-100,"auth":4},\n + * BUT: we need to escape JSON. Imagine a ssid full of \" ? so it's 32 more bytes hence 77 + 32 = 99.\n + * this is an edge case but I don't think we should crash in a catastrophic manner just because + * someone decided to have a funny wifi name. + */ +#define JSON_ONE_APP_SIZE 99 + +/** + * @brief Defines the maximum length in bytes of a JSON representation of the IP information + * assuming all ips are 4*3 digits, and all characters in the ssid require to be escaped. + * example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":99} + * Run this JS (browser console is easiest) to come to the conclusion that 159 is the worst case. + * ``` + * var a = {"ssid":"abcdefghijklmnopqrstuvwxyz012345","ip":"255.255.255.255","netmask":"255.255.255.255","gw":"255.255.255.255","urc":99}; + * // Replace all ssid characters with a double quote which will have to be escaped + * a.ssid = a.ssid.split('').map(() => '"').join(''); + * console.log(JSON.stringify(a).length); // => 158 +1 for null + * console.log(JSON.stringify(a)); // print it + * ``` + */ +#define JSON_IP_INFO_SIZE 159 + + +/** + * @brief defines the minimum length of an access point password running on WPA2 + */ +#define WPA2_MINIMUM_PASSWORD_LENGTH 8 + + +/** + * @brief Defines the complete list of all messages that the wifi_manager can process. + * + * Some of these message are events ("EVENT"), and some of them are action ("ORDER") + * Each of these messages can trigger a callback function and each callback function is stored + * in a function pointer array for convenience. Because of this behavior, it is extremely important + * to maintain a strict sequence and the top level special element 'MESSAGE_CODE_COUNT' + * + * @see wifi_manager_set_callback + */ +typedef enum message_code_t { + NONE = 0, + WM_ORDER_START_HTTP_SERVER = 1, + WM_ORDER_STOP_HTTP_SERVER = 2, + WM_ORDER_START_DNS_SERVICE = 3, + WM_ORDER_STOP_DNS_SERVICE = 4, + WM_ORDER_START_WIFI_SCAN = 5, + WM_ORDER_LOAD_AND_RESTORE_STA = 6, + WM_ORDER_CONNECT_STA = 7, + WM_ORDER_DISCONNECT_STA = 8, + WM_ORDER_START_AP = 9, + WM_EVENT_STA_DISCONNECTED = 10, + WM_EVENT_SCAN_DONE = 11, + WM_EVENT_STA_GOT_IP = 12, + WM_ORDER_STOP_AP = 13, + WM_MESSAGE_CODE_COUNT = 14 /* important for the callback array */ + +}message_code_t; + +/** + * @brief simplified reason codes for a lost connection. + * + * esp-idf maintains a big list of reason codes which in practice are useless for most typical application. + */ +typedef enum update_reason_code_t { + UPDATE_CONNECTION_OK = 0, + UPDATE_FAILED_ATTEMPT = 1, + UPDATE_USER_DISCONNECT = 2, + UPDATE_LOST_CONNECTION = 3 +}update_reason_code_t; + +typedef enum connection_request_made_by_code_t{ + CONNECTION_REQUEST_NONE = 0, + CONNECTION_REQUEST_USER = 1, + CONNECTION_REQUEST_AUTO_RECONNECT = 2, + CONNECTION_REQUEST_RESTORE_CONNECTION = 3, + CONNECTION_REQUEST_MAX = 0x7fffffff /*force the creation of this enum as a 32 bit int */ +}connection_request_made_by_code_t; + +/** + * The actual WiFi settings in use + */ +struct wifi_settings_t{ + uint8_t ap_ssid[MAX_SSID_SIZE]; + uint8_t ap_pwd[MAX_PASSWORD_SIZE]; + uint8_t ap_channel; + uint8_t ap_ssid_hidden; + wifi_bandwidth_t ap_bandwidth; + bool sta_only; + wifi_ps_type_t sta_power_save; + bool sta_static_ip; + esp_netif_ip_info_t sta_static_ip_config; +}; +extern struct wifi_settings_t wifi_settings; + + +/** + * @brief Structure used to store one message in the queue. + */ +typedef struct{ + message_code_t code; + void *param; +} queue_message; + + +/** + * @brief returns the current esp_netif object for the STAtion + */ +esp_netif_t* wifi_manager_get_esp_netif_sta(); + +/** + * @brief returns the current esp_netif object for the Access Point + */ +esp_netif_t* wifi_manager_get_esp_netif_ap(); + + +/** + * Allocate heap memory for the wifi manager and start the wifi_manager RTOS task + */ +void wifi_manager_start(); + +/** + * Frees up all memory allocated by the wifi_manager and kill the task. + */ +void wifi_manager_destroy(); + +/** + * Filters the AP scan list to unique SSIDs + */ +void filter_unique( wifi_ap_record_t * aplist, uint16_t * ap_num); + +/** + * Main task for the wifi_manager + */ +void wifi_manager( void * pvParameters ); + + +char* wifi_manager_get_ap_list_json(); +char* wifi_manager_get_ip_info_json(); + + +void wifi_manager_scan_async(); + + +/** + * @brief saves the current STA wifi config to flash ram storage. + */ +esp_err_t wifi_manager_save_sta_config(); + +/** + * @brief fetch a previously STA wifi config in the flash ram storage. + * @return true if a previously saved config was found, false otherwise. + */ +bool wifi_manager_fetch_wifi_sta_config(); + +wifi_config_t* wifi_manager_get_wifi_sta_config(); + + +/** + * @brief requests a connection to an access point that will be process in the main task thread. + */ +void wifi_manager_connect_async(); + +/** + * @brief requests a wifi scan + */ +void wifi_manager_scan_awifi_manager_send_messagesync(); + +/** + * @brief requests to disconnect and forget about the access point. + */ +void wifi_manager_disconnect_async(); + +/** + * @brief Tries to get access to json buffer mutex. + * + * The HTTP server can try to access the json to serve clients while the wifi manager thread can try + * to update it. These two tasks are synchronized through a mutex. + * + * The mutex is used by both the access point list json and the connection status json.\n + * These two resources should technically have their own mutex but we lose some flexibility to save + * on memory. + * + * This is a simple wrapper around freeRTOS function xSemaphoreTake. + * + * @param xTicksToWait The time in ticks to wait for the semaphore to become available. + * @return true in success, false otherwise. + */ +bool wifi_manager_lock_json_buffer(TickType_t xTicksToWait); + +/** + * @brief Releases the json buffer mutex. + */ +void wifi_manager_unlock_json_buffer(); + +/** + * @brief Generates the connection status json: ssid and IP addresses. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code); +/** + * @brief Clears the connection status json. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_clear_ip_info_json(); + +/** + * @brief Generates the list of access points after a wifi scan. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_generate_acess_points_json(); + +/** + * @brief Clear the list of access points. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_clear_access_points_json(); + + +/** + * @brief Start the mDNS service + */ +void wifi_manager_initialise_mdns(); + + +bool wifi_manager_lock_sta_ip_string(TickType_t xTicksToWait); +void wifi_manager_unlock_sta_ip_string(); + +/** + * @brief gets the string representation of the STA IP address, e.g.: "192.168.1.69" + */ +char* wifi_manager_get_sta_ip_string(); + +/** + * @brief thread safe char representation of the STA IP update + */ +void wifi_manager_safe_update_sta_ip_string(uint32_t ip); + + +/** + * @brief Register a callback to a custom function when specific event message_code happens. + */ +void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) ); + + +BaseType_t wifi_manager_send_message(message_code_t code, void *param); +BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param); + +#ifdef __cplusplus +} +#endif + +#endif /* WIFI_MANAGER_H_INCLUDED */ diff --git a/dist/esp32-wifi-manager_0.0.4.tgz b/dist/esp32-wifi-manager_0.0.4.tgz new file mode 100644 index 00000000..223b62ae Binary files /dev/null and b/dist/esp32-wifi-manager_0.0.4.tgz differ diff --git a/dist/esp32-wifi-manager_0.0.4/.gitignore b/dist/esp32-wifi-manager_0.0.4/.gitignore new file mode 100644 index 00000000..5448b48b --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/.gitignore @@ -0,0 +1,75 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Eclipse +.metadata/ +RemoteSystemsTempFiles/.project +.settings/ +*.a +*.o +*.d +wifi_manager/.cproject +wifi_manager/.project +sdkconfig +sdkconfig.old +**/build/ +**/managed_components/ +dependencies.lock +#doxygen +Doxyfile +wifi_manager/doc/ +.project +.cproject + +# Visual Studio Code +.vscode/ diff --git a/dist/esp32-wifi-manager_0.0.4/.travis.yml b/dist/esp32-wifi-manager_0.0.4/.travis.yml new file mode 100644 index 00000000..37c55458 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/.travis.yml @@ -0,0 +1,122 @@ +language: bash + +# when you suspects issues in cache, use the following line to disable cache. +# cache: false +cache: + directories: + - ${HOME}/distfiles + - ${HOME}/.ccache + - ${HOME}/.cache/pip +os: + - linux + +matrix: + include: + - env: + - PROJECT_TARGET="esp32" + - PROJECT_SDK_BRANCH="master" +# - env: +# - PROJECT_TARGET="esp8266" +# - PROJECT_SDK_BRANCH="master" + +addons: + apt: + packages: + - gcc + - wget + - make + - libncurses-dev + - flex + - bison + - python + - python-pip + - gperf + - ccache + +before_install: + # Save path to the git respository + - PROJECT_PATH=$(pwd) + +install: + - export TOOLCHAIN_DIR="${HOME}/${PROJECT_TARGET}" + - | + if [ ${PROJECT_TARGET} == "esp8266" ]; then + export PROJECT_GCC_PREFIX="xtensa-lx106-elf" + export PROJECT_TOOLCHAIN_FILE=xtensa-lx106-elf-linux64-1.22.0-92-g8facf4c-5.2.0.tar.gz + export PROJECT_SDK_NAME="ESP8266_RTOS_SDK" + else + export PROJECT_GCC_PREFIX="xtensa-esp32-elf" + export PROJECT_TOOLCHAIN_FILE=xtensa-esp32-elf-gcc8_2_0-esp32-2019r1-linux-amd64.tar.gz + export PROJECT_SDK_NAME="esp-idf" + fi + - export PROJECT_GCC_FILE="${PROJECT_GCC_PREFIX}-gcc" + - export PROJECT_DISTFILE_DIR="${HOME}/distfiles" + - export IDF_PATH=${TOOLCHAIN_DIR}/${PROJECT_SDK_NAME} + - export PROJECT_LOG="${HOME}/build.log" + - export PROJECT_EXAMPLE_DIR="${PROJECT_PATH}/examples" + # Install ESP32 toochain following steps as desribed + # in http://esp-idf.readthedocs.io/en/latest/linux-setup.html + + # Prepare directory for the toolchain + - mkdir -p ${TOOLCHAIN_DIR} ${PROJECT_DISTFILE_DIR} + # Get SDK from github + - git clone --branch ${PROJECT_SDK_BRANCH} --recursive https://github.com/espressif/${PROJECT_SDK_NAME}.git ${IDF_PATH} + + # Setup ccache to build faster + # XXX when the entire build process exceeds 50 min, th job will be killed + # https://docs.travis-ci.com/user/customizing-the-build/#build-timeouts + - ccache --version + - mkdir ${HOME}/ccache_bin + - (cd ${HOME}/ccache_bin && ln -s /usr/bin/ccache ${PROJECT_GCC_FILE}) + - export CCACHE_BASEDIR=$PROJECT_PATH + - export CCACHE_CPP2=true + + # Get Python requirements + - python -m pip install --user --upgrade pyOpenSSL + - python -m pip install --user -r ${IDF_PATH}/requirements.txt + + # Download binary toolchain if it does not exist + - | + if [ ! -f ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} ]; then + wget -O ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} https://dl.espressif.com/dl/${PROJECT_TOOLCHAIN_FILE} + fi + - tar -xz -C ${TOOLCHAIN_DIR} -f ${PROJECT_DISTFILE_DIR}/${PROJECT_TOOLCHAIN_FILE} + + # Make toolchains available for all terminal sessions + - export PATH=$HOME/ccache_bin:$PATH:$HOME/${PROJECT_TARGET}/${PROJECT_GCC_PREFIX}/bin + +script: + - rm -f ${PROJECT_LOG} + # XXX surpress log output where possible. when the size exceeds 4 MB, the + # job will be killed. + - | + IGNORE_FILE="travis-ignore" + + case ${PROJECT_TARGET} in + esp32) + ;; + esp8266) + IGNORE_FILE="travis-ignore-esp8266" + # these drivers do not compile for ESP8266 yet + export EXCLUDE_COMPONENTS="encoder max7219 mcp23x17" + ;; + esac + + cd ${PROJECT_EXAMPLE_DIR} + for i in $(ls -d */); do + if [ ! -e ${PROJECT_EXAMPLE_DIR}/${i}/${IGNORE_FILE} ]; then + echo "Building ${i}..." + cd ${PROJECT_EXAMPLE_DIR}/${i} + make defconfig + make -j2 >> ${PROJECT_LOG} + if [ $? -ne 0 ]; then + # when failed, show last 100 lines for debugging, and exit with + # non-zero exit code + tail -n 100 ${PROJECT_LOG} + exit 1 + fi + make clean >/dev/null + # make sure the directory is clean + rm -rf ${i}/sdkconfig ${i}/build + fi + done diff --git a/dist/esp32-wifi-manager_0.0.4/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.4/CMakeLists.txt new file mode 100644 index 00000000..83e0077c --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/CMakeLists.txt @@ -0,0 +1,12 @@ +if(IDF_VERSION_MAJOR GREATER_EQUAL 4) + idf_component_register(SRC_DIRS src + REQUIRES esp_wifi log nvs_flash mdns wpa_supplicant lwip esp_http_server + INCLUDE_DIRS src + EMBED_FILES src/style.css src/code.js src/index.html) +else() + set(COMPONENT_SRCDIRS src) + set(COMPONENT_ADD_INCLUDEDIRS src) + set(COMPONENT_REQUIRES esp_wifi log nvs_flash mdns wpa_supplicant lwip esp_http_server) + set(COMPONENT_EMBED_FILES src/style.css src/code.js src/index.html) + register_component() +endif() diff --git a/dist/esp32-wifi-manager_0.0.4/Kconfig b/dist/esp32-wifi-manager_0.0.4/Kconfig new file mode 100644 index 00000000..59c5a9a8 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/Kconfig @@ -0,0 +1,81 @@ +menu "Wifi Manager Configuration" + +config WIFI_MANAGER_TASK_PRIORITY + int "RTOS Task Priority for the wifi_manager" + default 5 + help + Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1. For this particular reason, minimum recommended task priority is 2. + +config WIFI_MANAGER_RETRY_TIMER + int "Time (in ms) between each retry attempt" + default 5000 + help + Defines the time to wait before an attempt to re-connect to a saved wifi is made after connection is lost or another unsuccesful attempt is made. + +config WIFI_MANAGER_MAX_RETRY_START_AP + int "Max Retry before starting the AP" + default 3 + help + Defines the maximum number of failed retries allowed before the WiFi manager starts its own access point. + +config WIFI_MANAGER_SHUTDOWN_AP_TIMER + int "Time (in ms) to wait before shutting down the AP" + default 60000 + help + Defines the time (in ms) to wait after a succesful connection before shutting down the access point. + +config WEBAPP_LOCATION + string "Defines the URL where the wifi manager is located" + default "/" + help + This parameter helps you relocate the wifimanager to another URL, for instance /wifimanager/ The trailing slash is important and should be included + +config DEFAULT_AP_SSID + string "Access Point SSID" + default "esp32" + help + SSID (network name) the the esp32 will broadcast. + +config DEFAULT_AP_PASSWORD + string "Access Point Password" + default "esp32pwd" + help + Password used for the Access Point. Leave empty and set AUTH MODE to WIFI_AUTH_OPEN for no password. + +config DEFAULT_AP_CHANNEL + int "Access Point WiFi Channel" + default 1 + help + Be careful you might not see the access point if you use a channel not allowed in your country. + +config DEFAULT_AP_IP + string "Access Point IP Address" + default "10.10.0.1" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_GATEWAY + string "Access Point IP Gateway" + default "10.10.0.1" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_NETMASK + string "Access Point Netmask" + default "255.255.255.0" + help + This is used for the redirection to the captive portal. It is recommended to leave unchanged. + +config DEFAULT_AP_MAX_CONNECTIONS + int "Access Point Max Connections" + default 4 + help + Max is 4. + +config DEFAULT_AP_BEACON_INTERVAL + int "Access Point Beacon Interval (ms)" + default 100 + help + 100ms is the recommended default. + +endmenu diff --git a/dist/esp32-wifi-manager_0.0.4/LICENSE.md b/dist/esp32-wifi-manager_0.0.4/LICENSE.md new file mode 100644 index 00000000..5f2ac0bc --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2017-2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dist/esp32-wifi-manager_0.0.4/README.md b/dist/esp32-wifi-manager_0.0.4/README.md new file mode 100644 index 00000000..43248f3d --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/README.md @@ -0,0 +1,225 @@ +# What is esp32-wifi-manager? + +### Build status [![Build Status](https://travis-ci.com/tonyp7/esp32-wifi-manager.svg?branch=master)](https://travis-ci.com/tonyp7/esp32-wifi-manager) + +*esp32-wifi-manager* is a pure C esp-idf component for ESP32 that enables easy management of wifi networks through a web portal. + +*esp32-wifi-manager* is is an all in one wifi scanner, http server & dns daemon living in the least amount of RAM possible. + +*esp32-wifi-manager* will automatically attempt to re-connect to a previously saved network on boot, and if it cannot find a saved wifi it will start its own access point through which you can manage and connect to wifi networks. Upon a succesful connection, the software will shutdown the access point automatically after some time (1 minute by default). + +*esp32-wifi-manager* compiles with esp-idf 4.2 and above. See [Getting Started](#getting-started) to guide you through your first setup. + +# Content + - [Demo](#demo) + - [Look And Feel](#look-and-feel) + - [Getting Started](#getting-started) + - [Requirements](#requirements) + - [Hello World](#hello-world) + - [Configuring the Wifi Manager](#configuring-the-wifi-manager) + - [Adding esp32-wifi-manager to your code](#adding-esp32-wifi-manager-to-your-code) + - [Interacting with the manager](#interacting-with-the-manager) + - [Interacting with the http server](#interacting-with-the-http-server) + - [Thread safety and access to NVS](#thread-safety-and-access-to-nvs) + - [License](#license) + + +# Demo +[![esp32-wifi-manager demo](http://img.youtube.com/vi/hxlZi15bym4/0.jpg)](http://www.youtube.com/watch?v=hxlZi15bym4) + +# Look and Feel +![esp32-wifi-manager on an mobile device](https://idyl.io/wp-content/uploads/2017/11/esp32-wifi-manager-password.png "esp32-wifi-manager") ![esp32-wifi-manager on an mobile device](https://idyl.io/wp-content/uploads/2017/11/esp32-wifi-manager-connected-to.png "esp32-wifi-manager") + +# Getting Started + +## Requirements + +To get you started, esp32-wifi-manager needs: + +- esp-idf **4.2 and up** +- esp32 or esp32-s2 + +There are breaking changes and new features in esp-idf 4.1 and 4.2 which makes esp32-wifi-manager incompatible with anything lower than 4.2. This includes esp_netif (introduced in 4.1) and esp_event_handler_instance_t (introduced in 4.2). It is recommended to compile esp32-wifi-manager with the master tree to avoid any compatibility issue. + +## Hello World + +Clone the repository where you want it to be. If you are unfamiliar with Git, you can use Github Desktop on Windows: + +```bash +git clone https://github.com/tonyp7/esp32-wifi-manager.git +``` + +Navigate under the included example: + +```bash +cd esp32-wifi-manager/examples/default_demo +``` + +Compile the code and load it on your esp32: + +```bash +idf.py build flash monitor +``` + +_Note: while it is encouraged to use the newer build system with idf.py and cmake, esp32-wifi-manager still supports the legacy build system. If you are using make on Linux or make using MSYS2 on Windows, you can still use "make build flash monitor" if you prefer_ + +Now, using any wifi capable device, you will see a new wifi access point named *esp32*. Connect to it using the default password *esp32pwd*. If the captive portal does not pop up on your device, you can access the wifi manager at its default IP address: http://10.10.0.1. + +## Configuring the Wifi Manager + +esp32-wifi-manager can be configured without touching its code. At the project level use: + +```bash +idf.py menuconfig +``` + +Navigate in "Component config" then pick "Wifi Manager Configuration". You will be greeted by the following screen: + +![esp32-wifi-manager-menuconfig](https://idyl.io/wp-content/uploads/2020/08/wifi-manager-menuconfig-800px.png "menuconfig screen") + +You can change the ssid and password of the access point at your convenience, but it is highly recommended to keep default values. Your password should be between 8 and 63 characters long, to comply with the WPA2 standard. If the password is set to an empty value or is less than 8 characters long, esp32-wifi-manager will create its access point as an open wifi network. + +You can also change the values for various timers, for instance how long it takes for the access point to shutdown once a connection is established (default: 60000). While it could be tempting to set this timer to 0, just be warned that in that case the user will never get the feedback that a connection is succesful. Shutting down the AP will instantly kill the current navigating session on the captive portal. + +Finally, you can choose to relocate esp32-wifi-manager to a different URL by changing the default value of "/" to something else, for instance "/wifimanager/". Please note that the trailing slash does matter. This feature is particularly useful in case you want your own webapp to co-exist with esp32-wifi-manager's own web pages. + +# Adding esp32-wifi-manager to your code + +In order to use esp32-wifi-manager effectively in your esp-idf projects, copy the whole esp32-wifi-manager repository (or git clone) into a components subfolder. + +Your project should look like this: + + - project_folder + - build + - components + - esp32-wifi-manager + - main + - main.c + +Under eclipse, this is what a typical project looks like: + +![eclipse project with esp32-wifi-manager](https://idyl.io/wp-content/uploads/2020/07/eclipse-idf-project.png "eclipse project with esp32-wifi-manager") + +Once this is done, you need to edit the CMakeLists.txt file at the root of your project to register the components folder. This is done by adding the following line: + +```cmake +set(EXTRA_COMPONENTS_DIRS components/) +``` + +A typical CmakeLists.txt file should look like this: + +```cmake +cmake_minimum_required(VERSION 3.5) +set(EXTRA_COMPONENT_DIRS components/) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(name_of_your_project) +``` + +If you are using the old build system with make instead, you should edit the Makefile instead such as: + +```make +PROJECT_NAME := name_of_your_project +EXTRA_COMPONENT_DIRS := components/ +include $(IDF_PATH)/make/project.mk +``` + +Once this is done, you can now in your user code add the header: + +```c +#include "wifi_manager.h" +``` + +All you need to do now is to call wifi_manager_start(); in your code. See [examples/default_demo](examples/default_demo) if you are uncertain. + + +## Interacting with the manager + +Ther are effectively three different ways you can embed esp32-wifi-manager with your code: +* Just forget about it and poll in your code for wifi connectivity status +* Use event callbacks +* Modify esp32-wifi-manager code directly to fit your needs + +**Event callbacks** are the cleanest way to use the wifi manager and that's the recommended way to do it. A typical use-case would be to get notified when wifi manager finally gets a connection to an access point. In order to do this you can simply define a callback function: + +```c +void cb_connection_ok(void *pvParameter){ + ESP_LOGI(TAG, "I have a connection!"); +} +``` + +Then just register it by calling: + +```c +wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok); +``` + +That's it! Now everytime the event is triggered it will call this function. The [examples/default_demo](examples/default_demo) contains sample code using callbacks. + +### List of events + +The list of possible events you can add a callback to are defined by message_code_t in wifi_manager.h. They are as following: + +* WM_ORDER_START_HTTP_SERVER +* WM_ORDER_STOP_HTTP_SERVER +* WM_ORDER_START_DNS_SERVICE +* WM_ORDER_STOP_DNS_SERVICE +* WM_ORDER_START_WIFI_SCAN +* WM_ORDER_LOAD_AND_RESTORE_STA +* WM_ORDER_CONNECT_STA +* WM_ORDER_DISCONNECT_STA +* WM_ORDER_START_AP +* WM_EVENT_STA_DISCONNECTED +* WM_EVENT_SCAN_DONE +* WM_EVENT_STA_GOT_IP +* WM_ORDER_STOP_AP + +In practice, keeping track of WM_EVENT_STA_GOT_IP and WM_EVENT_STA_DISCONNECTED is key to know whether or not your esp32 has a connection. The other messages can mostly be ignored in a typical application using esp32-wifi-manager. + +### Events parameters + +Callback signature includes a void* pointer. For most events, this additional parameter is empty and sent as a NULL value. A few select events have additional data which can be leveraged by user code. They are listed below: + +* WM_EVENT_SCAN_DONE is sent with a wifi_event_sta_scan_done_t* object. +* WM_EVENT_STA_DISCONNECTED is sent with a wifi_event_sta_disconnected_t* object. +* WM_EVENT_STA_GOT_IP is sent with a ip_event_got_ip_t* object. + +These objects are standard esp-idf structures, and are documented as such in the [official pages](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html). + +The [examples/default_demo](examples/default_demo) demonstrates how you can read a ip_event_got_ip_t object to access the IP address assigned to the esp32. + +## Interacting with the http server + +Because esp32-wifi-manager spawns its own http server, you might want to extend this server to serve your own pages in your application. It is possible to do so by registering your own URL handler using the standard esp_http_server signature: + +```c +esp_err_t my_custom_handler(httpd_req_t *req){ +``` + +And then registering the handler by doing + +```c +http_app_set_handler_hook(HTTP_GET, &my_custom_handler); +``` + +The [examples/http_hook](examples/http_hook) contains an example where a web page is registered at /helloworld + +## Thread safety and access to NVS + +esp32-wifi-manager accesses the non-volatile storage to store and loads its configuration into a dedicated namespace "espwifimgr". If you want to make sure there will never be a conflict with concurrent access to the NVS, you can include nvs_sync.h and use calls to nvs_sync_lock and nvs_sync_unlock. + +```c +nvs_handle handle; + +if(nvs_sync_lock( portMAX_DELAY )){ + if(nvs_open(wifi_manager_nvs_namespace, NVS_READWRITE, &handle) == ESP_OK){ + /* do something with NVS */ + nvs_close(handle); + } + nvs_sync_unlock(); +} +``` +nvs_sync_lock waits for the number of ticks sent to it as a parameter to acquire a mutex. It is recommended to use portMAX_DELAY. In practice, nvs_sync_lock will almost never wait. + + +# License +*esp32-wifi-manager* is MIT licensed. As such, it can be included in any project, commercial or not, as long as you retain original copyright. Please make sure to read the license file. diff --git a/dist/esp32-wifi-manager_0.0.4/component.mk b/dist/esp32-wifi-manager_0.0.4/component.mk new file mode 100644 index 00000000..75e34089 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/component.mk @@ -0,0 +1,4 @@ +COMPONENT_ADD_INCLUDEDIRS = src +COMPONENT_SRCDIRS = src +COMPONENT_DEPENDS = log esp_http_server +COMPONENT_EMBED_FILES := src/style.css src/code.js src/index.html diff --git a/dist/esp32-wifi-manager_0.0.4/examples/default_demo/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/CMakeLists.txt new file mode 100644 index 00000000..6458baae --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/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) +set(EXTRA_COMPONENT_DIRS ../../) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(default_demo) diff --git a/dist/esp32-wifi-manager_0.0.4/examples/default_demo/Makefile b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/Makefile new file mode 100644 index 00000000..fb186183 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := wifi_manager + +EXTRA_COMPONENT_DIRS := ../../ + +include $(IDF_PATH)/make/project.mk + diff --git a/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/CMakeLists.txt new file mode 100644 index 00000000..31addfc4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "user_main.c") + +register_component() diff --git a/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/component.mk b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/idf_component.yml b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/idf_component.yml new file mode 100644 index 00000000..8826a619 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "^1.3.2" + ## Required IDF version + idf: + version: ">=4.1.0" + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true diff --git a/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/user_main.c b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/user_main.c new file mode 100644 index 00000000..19af535e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/main/user_main.c @@ -0,0 +1,80 @@ +/* +Copyright (c) 2017-2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file main.c +@author Tony Pottier +@brief Entry point for the ESP32 application. +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "wifi_manager.h" + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "main"; + +/** + * @brief RTOS task that periodically prints the heap memory available. + * @note Pure debug information, should not be ever started on production code! This is an example on how you can integrate your code with wifi-manager + */ +void monitoring_task(void *pvParameter) +{ + for(;;){ + ESP_LOGI(TAG, "free heap: %lu",esp_get_free_heap_size()); + vTaskDelay( pdMS_TO_TICKS(10000) ); + } +} + + +/** + * @brief this is an exemple of a callback that you can setup in your own app to get notified of wifi manager event. + */ +void cb_connection_ok(void *pvParameter){ + ip_event_got_ip_t* param = (ip_event_got_ip_t*)pvParameter; + + /* transform IP to human readable string */ + char str_ip[16]; + esp_ip4addr_ntoa(¶m->ip_info.ip, str_ip, IP4ADDR_STRLEN_MAX); + + ESP_LOGI(TAG, "I have a connection and my IP is %s!", str_ip); +} + +void app_main() +{ + /* start the wifi manager */ + wifi_manager_start(); + + /* register a callback as an example to how you can integrate your code with the wifi manager */ + wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok); +#if CONFIG_SOC_CPU_CORES_NUM > 1 + /* your code should go here. Here we simply create a task on core 2 that monitors free heap memory */ + xTaskCreatePinnedToCore(&monitoring_task, "monitoring_task", 2048, NULL, 1, NULL, 1); +#endif +} diff --git a/dist/esp32-wifi-manager_0.0.4/examples/default_demo/sdkconfig.defaults b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/sdkconfig.defaults new file mode 100644 index 00000000..f44c06f4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/default_demo/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_LWIP_IPV6=y diff --git a/dist/esp32-wifi-manager_0.0.4/examples/http_hook/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/CMakeLists.txt new file mode 100644 index 00000000..377f86fe --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/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) +set(EXTRA_COMPONENT_DIRS ../../) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(http_hook) diff --git a/dist/esp32-wifi-manager_0.0.4/examples/http_hook/Makefile b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/Makefile new file mode 100644 index 00000000..fb186183 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := wifi_manager + +EXTRA_COMPONENT_DIRS := ../../ + +include $(IDF_PATH)/make/project.mk + diff --git a/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/CMakeLists.txt b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/CMakeLists.txt new file mode 100644 index 00000000..31addfc4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "user_main.c") + +register_component() diff --git a/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/component.mk b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/idf_component.yml b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/idf_component.yml new file mode 100644 index 00000000..8826a619 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "^1.3.2" + ## Required IDF version + idf: + version: ">=4.1.0" + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true diff --git a/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/user_main.c b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/user_main.c new file mode 100644 index 00000000..2446f269 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/main/user_main.c @@ -0,0 +1,76 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file user_main.c +@author Tony Pottier +@brief Entry point for the ESP32 application. +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "wifi_manager.h" +#include "http_app.h" + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "main"; + + +static esp_err_t my_get_handler(httpd_req_t *req){ + + /* our custom page sits at /helloworld in this example */ + if(strcmp(req->uri, "/helloworld") == 0){ + + ESP_LOGI(TAG, "Serving page /helloworld"); + + const char* response = "

Hello World!

"; + + httpd_resp_set_status(req, "200 OK"); + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, response, strlen(response)); + } + else{ + /* send a 404 otherwise */ + httpd_resp_send_404(req); + } + + return ESP_OK; +} + + +void app_main() +{ + /* start the wifi manager */ + wifi_manager_start(); + + /* set custom handler for the http server + * Now navigate to /helloworld to see the custom page + * */ + http_app_set_handler_hook(HTTP_GET, &my_get_handler); + +} diff --git a/dist/esp32-wifi-manager_0.0.4/examples/http_hook/sdkconfig.defaults b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/sdkconfig.defaults new file mode 100644 index 00000000..f44c06f4 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/examples/http_hook/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_LWIP_IPV6=y diff --git a/dist/esp32-wifi-manager_0.0.4/idf_component.yml b/dist/esp32-wifi-manager_0.0.4/idf_component.yml new file mode 100644 index 00000000..7b9cc72d --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/idf_component.yml @@ -0,0 +1,9 @@ +dependencies: + idf: + version: '>=4.2' +description: esp32-wifi-manager is a pure C esp-idf component for ESP32 that enables + easy management of wifi networks through a web portal. +license: MIT +repository: https://github.com/ankayca/esp32-wifi-manager/tree/update_build_system +url: https://github.com/ankayca/esp32-wifi-manager/tree/update_build_system +version: 0.0.4 diff --git a/dist/esp32-wifi-manager_0.0.4/src/ap.json b/dist/esp32-wifi-manager_0.0.4/src/ap.json new file mode 100644 index 00000000..de61f86a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/ap.json @@ -0,0 +1,12 @@ +[ +{"ssid":"Pantum-AP-A6D49F","chan":11,"rssi":-55,"auth":4}, +{"ssid":"a0308","chan":1,"rssi":-56,"auth":3}, +{"ssid":"dlink-D9D8","chan":11,"rssi":-82,"auth":4}, +{"ssid":"Linksys06730","chan":7,"rssi":-85,"auth":3}, +{"ssid":"SINGTEL-5171","chan":9,"rssi":-88,"auth":4}, +{"ssid":"1126-1","chan":11,"rssi":-89,"auth":4}, +{"ssid":"The Shah 5GHz-2","chan":1,"rssi":-90,"auth":3}, +{"ssid":"SINGTEL-1D28 (2G)","chan":11,"rssi":-91,"auth":3}, +{"ssid":"dlink-F864","chan":1,"rssi":-92,"auth":4}, +{"ssid":"dlink-74F0","chan":1,"rssi":-93,"auth":4} +] \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/code.js b/dist/esp32-wifi-manager_0.0.4/src/code.js new file mode 100644 index 00000000..934595a9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/code.js @@ -0,0 +1,352 @@ +// save some bytes +const gel = (e) => document.getElementById(e); + +const wifi_div = gel("wifi"); +const connect_div = gel("connect"); +const connect_manual_div = gel("connect_manual"); +const connect_wait_div = gel("connect-wait"); +const connect_details_div = gel("connect-details"); + +function docReady(fn) { + // see if DOM is already available + if ( + document.readyState === "complete" || + document.readyState === "interactive" + ) { + // call on next available tick + setTimeout(fn, 1); + } else { + document.addEventListener("DOMContentLoaded", fn); + } +} + +var selectedSSID = ""; +var refreshAPInterval = null; +var checkStatusInterval = null; + +function stopCheckStatusInterval() { + if (checkStatusInterval != null) { + clearInterval(checkStatusInterval); + checkStatusInterval = null; + } +} + +function stopRefreshAPInterval() { + if (refreshAPInterval != null) { + clearInterval(refreshAPInterval); + refreshAPInterval = null; + } +} + +function startCheckStatusInterval() { + checkStatusInterval = setInterval(checkStatus, 950); +} + +function startRefreshAPInterval() { + refreshAPInterval = setInterval(refreshAP, 3800); +} + +docReady(async function () { + gel("wifi-status").addEventListener( + "click", + () => { + wifi_div.style.display = "none"; + document.getElementById("connect-details").style.display = "block"; + }, + false + ); + + gel("manual_add").addEventListener( + "click", + (e) => { + selectedSSID = e.target.innerText; + + gel("ssid-pwd").textContent = selectedSSID; + wifi_div.style.display = "none"; + connect_manual_div.style.display = "block"; + connect_div.style.display = "none"; + + gel("connect-success").display = "none"; + gel("connect-fail").display = "none"; + }, + false + ); + + gel("wifi-list").addEventListener( + "click", + (e) => { + selectedSSID = e.target.innerText; + gel("ssid-pwd").textContent = selectedSSID; + connect_div.style.display = "block"; + wifi_div.style.display = "none"; + // init_cancel(); + }, + false + ); + + function cancel() { + selectedSSID = ""; + connect_div.style.display = "none"; + connect_manual_div.style.display = "none"; + wifi_div.style.display = "block"; + } + + gel("cancel").addEventListener("click", cancel, false); + + gel("manual_cancel").addEventListener("click", cancel, false); + + gel("join").addEventListener("click", performConnect, false); + + gel("manual_join").addEventListener( + "click", + (e) => { + performConnect("manual"); + }, + false + ); + + gel("ok-details").addEventListener( + "click", + () => { + connect_details_div.style.display = "none"; + wifi_div.style.display = "block"; + }, + false + ); + + gel("ok-credits").addEventListener( + "click", + () => { + gel("credits").style.display = "none"; + gel("app").style.display = "block"; + }, + false + ); + + gel("acredits").addEventListener( + "click", + () => { + event.preventDefault(); + gel("app").style.display = "none"; + gel("credits").style.display = "block"; + }, + false + ); + + gel("ok-connect").addEventListener( + "click", + () => { + connect_wait_div.style.display = "none"; + wifi_div.style.display = "block"; + }, + false + ); + + gel("disconnect").addEventListener( + "click", + () => { + gel("diag-disconnect").style.display = "block"; + gel("connect-details-wrap").classList.add("blur"); + }, + false + ); + + gel("no-disconnect").addEventListener( + "click", + () => { + gel("diag-disconnect").style.display = "none"; + gel("connect-details-wrap").classList.remove("blur"); + }, + false + ); + + gel("yes-disconnect").addEventListener("click", async () => { + stopCheckStatusInterval(); + selectedSSID = ""; + + document.getElementById("diag-disconnect").style.display = "none"; + gel("connect-details-wrap").classList.remove("blur"); + + await fetch("connect.json", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: { timestamp: Date.now() }, + }); + + startCheckStatusInterval(); + + connect_details_div.style.display = "none"; + wifi_div.style.display = "block"; + }); + + //first time the page loads: attempt get the connection status and start the wifi scan + await refreshAP(); + startCheckStatusInterval(); + startRefreshAPInterval(); +}); + +async function performConnect(conntype) { + //stop the status refresh. This prevents a race condition where a status + //request would be refreshed with wrong ip info from a previous connection + //and the request would automatically shows as succesful. + stopCheckStatusInterval(); + + //stop refreshing wifi list + stopRefreshAPInterval(); + + var pwd; + if (conntype == "manual") { + //Grab the manual SSID and PWD + selectedSSID = gel("manual_ssid").value; + pwd = gel("manual_pwd").value; + } else { + pwd = gel("pwd").value; + } + //reset connection + gel("loading").style.display = "block"; + gel("connect-success").style.display = "none"; + gel("connect-fail").style.display = "none"; + + gel("ok-connect").disabled = true; + gel("ssid-wait").textContent = selectedSSID; + connect_div.style.display = "none"; + connect_manual_div.style.display = "none"; + connect_wait_div.style.display = "block"; + + await fetch("connect.json", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Custom-ssid": selectedSSID, + "X-Custom-pwd": pwd, + }, + body: { timestamp: Date.now() }, + }); + + //now we can re-set the intervals regardless of result + startCheckStatusInterval(); + startRefreshAPInterval(); +} + +function rssiToIcon(rssi) { + if (rssi >= -60) { + return "w0"; + } else if (rssi >= -67) { + return "w1"; + } else if (rssi >= -75) { + return "w2"; + } else { + return "w3"; + } +} + +async function refreshAP(url = "ap.json") { + try { + var res = await fetch(url); + var access_points = await res.json(); + if (access_points.length > 0) { + //sort by signal strength + access_points.sort((a, b) => { + var x = a["rssi"]; + var y = b["rssi"]; + return x < y ? 1 : x > y ? -1 : 0; + }); + refreshAPHTML(access_points); + } + } catch (e) { + console.info("Access points returned empty from /ap.json!"); + } +} + +function refreshAPHTML(data) { + var h = ""; + data.forEach(function (e, idx, array) { + let ap_class = idx === array.length - 1 ? "" : " brdb"; + let rssicon = rssiToIcon(e.rssi); + let auth = e.auth == 0 ? "" : "pw"; + h += `
${e.ssid}
\n`; + }); + + gel("wifi-list").innerHTML = h; +} + +async function checkStatus(url = "status.json") { + try { + var response = await fetch(url); + var data = await response.json(); + if (data && data.hasOwnProperty("ssid") && data["ssid"] != "") { + if (data["ssid"] === selectedSSID) { + // Attempting connection + switch (data["urc"]) { + case 0: + console.info("Got connection!"); + document.querySelector( + "#connected-to div div div span" + ).textContent = data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = data["ip"]; + gel("netmask").textContent = data["netmask"]; + gel("gw").textContent = data["gw"]; + gel("wifi-status").style.display = "block"; + + //unlock the wait screen if needed + gel("ok-connect").disabled = false; + + //update wait screen + gel("loading").style.display = "none"; + gel("connect-success").style.display = "block"; + gel("connect-fail").style.display = "none"; + break; + case 1: + console.info("Connection attempt failed!"); + document.querySelector( + "#connected-to div div div span" + ).textContent = data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = "0.0.0.0"; + gel("netmask").textContent = "0.0.0.0"; + gel("gw").textContent = "0.0.0.0"; + + //don't show any connection + gel("wifi-status").display = "none"; + + //unlock the wait screen + gel("ok-connect").disabled = false; + + //update wait screen + gel("loading").display = "none"; + gel("connect-fail").style.display = "block"; + gel("connect-success").style.display = "none"; + break; + } + } else if (data.hasOwnProperty("urc") && data["urc"] === 0) { + console.info("Connection established"); + //ESP32 is already connected to a wifi without having the user do anything + if ( + gel("wifi-status").style.display == "" || + gel("wifi-status").style.display == "none" + ) { + document.querySelector("#connected-to div div div span").textContent = + data["ssid"]; + document.querySelector("#connect-details h1").textContent = + data["ssid"]; + gel("ip").textContent = data["ip"]; + gel("netmask").textContent = data["netmask"]; + gel("gw").textContent = data["gw"]; + gel("wifi-status").style.display = "block"; + } + } + } else if (data.hasOwnProperty("urc") && data["urc"] === 2) { + console.log("Manual disconnect requested..."); + if (gel("wifi-status").style.display == "block") { + gel("wifi-status").style.display = "none"; + } + } + } catch (e) { + console.info("Was not able to fetch /status.json"); + } +} diff --git a/dist/esp32-wifi-manager_0.0.4/src/component.mk b/dist/esp32-wifi-manager_0.0.4/src/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/dist/esp32-wifi-manager_0.0.4/src/compress.bat b/dist/esp32-wifi-manager_0.0.4/src/compress.bat new file mode 100644 index 00000000..b9caad22 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/compress.bat @@ -0,0 +1,2 @@ +gzip index.html style.css --best --keep --force +pause \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/connect b/dist/esp32-wifi-manager_0.0.4/src/connect new file mode 100644 index 00000000..8c7fe211 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/connect @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/dns_server.c b/dist/esp32-wifi-manager_0.0.4/src/dns_server.c new file mode 100644 index 00000000..6693913a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/dns_server.c @@ -0,0 +1,184 @@ +/* +Copyright (c) 2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file dns_server.c +@author Tony Pottier +@brief Defines an extremely basic DNS server for captive portal functionality. +It's basically a DNS hijack that replies to the esp's address no matter which +request is sent to it. + +Contains the freeRTOS task for the DNS server that processes the requests. + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wifi_manager.h" +#include "dns_server.h" + +static const char TAG[] = "dns_server"; +static TaskHandle_t task_dns_server = NULL; +int socket_fd; + +void dns_server_start() { + if(task_dns_server == NULL){ + xTaskCreate(&dns_server, "dns_server", 3072, NULL, WIFI_MANAGER_TASK_PRIORITY-1, &task_dns_server); + } +} + +void dns_server_stop(){ + if(task_dns_server){ + vTaskDelete(task_dns_server); + close(socket_fd); + task_dns_server = NULL; + } + +} + + + +void dns_server(void *pvParameters) { + + + + struct sockaddr_in ra; + + /* Set redirection DNS hijack to the access point IP */ + ip4_addr_t ip_resolved; + inet_pton(AF_INET, DEFAULT_AP_IP, &ip_resolved); + + + /* Create UDP socket */ + socket_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (socket_fd < 0){ + ESP_LOGE(TAG, "Failed to create socket"); + exit(0); + } + + /* Bind to port 53 (typical DNS Server port) */ + esp_netif_ip_info_t ip; + esp_netif_t* netif_sta = wifi_manager_get_esp_netif_sta(); + ESP_ERROR_CHECK(esp_netif_get_ip_info(netif_sta, &ip)); + ra.sin_family = AF_INET; + ra.sin_addr.s_addr = ip.ip.addr; + ra.sin_port = htons(53); + if (bind(socket_fd, (struct sockaddr *)&ra, sizeof(struct sockaddr_in)) == -1) { + ESP_LOGE(TAG, "Failed to bind to 53/udp"); + close(socket_fd); + exit(1); + } + + struct sockaddr_in client; + socklen_t client_len; + client_len = sizeof(client); + int length; + uint8_t data[DNS_QUERY_MAX_SIZE]; /* dns query buffer */ + uint8_t response[DNS_ANSWER_MAX_SIZE]; /* dns response buffer */ + char ip_address[INET_ADDRSTRLEN]; /* buffer to store IPs as text. This is only used for debug and serves no other purpose */ + char *domain; /* This is only used for debug and serves no other purpose */ + int err; + + ESP_LOGI(TAG, "DNS Server listening on 53/udp"); + + /* Start loop to process DNS requests */ + for(;;) { + + memset(data, 0x00, sizeof(data)); /* reset buffer */ + length = recvfrom(socket_fd, data, sizeof(data), 0, (struct sockaddr *)&client, &client_len); /* read udp request */ + + /*if the query is bigger than the buffer size we simply ignore it. This case should only happen in case of multiple + * queries within the same DNS packet and is not supported by this simple DNS hijack. */ + if ( length > 0 && ((length + sizeof(dns_answer_t)-1) < DNS_ANSWER_MAX_SIZE) ) { + + data[length] = '\0'; /*in case there's a bogus domain name that isn't null terminated */ + + /* Generate header message */ + memcpy(response, data, sizeof(dns_header_t)); + dns_header_t *dns_header = (dns_header_t*)response; + dns_header->QR = 1; /*response bit */ + dns_header->OPCode = DNS_OPCODE_QUERY; /* no support for other type of response */ + dns_header->AA = 1; /*authoritative answer */ + dns_header->RCode = DNS_REPLY_CODE_NO_ERROR; /* no error */ + dns_header->TC = 0; /*no truncation */ + dns_header->RD = 0; /*no recursion */ + dns_header->ANCount = dns_header->QDCount; /* set answer count = question count -- duhh! */ + dns_header->NSCount = 0x0000; /* name server resource records = 0 */ + dns_header->ARCount = 0x0000; /* resource records = 0 */ + + + /* copy the rest of the query in the response */ + memcpy(response + sizeof(dns_header_t), data + sizeof(dns_header_t), length - sizeof(dns_header_t)); + + + /* extract domain name and request IP for debug */ + inet_ntop(AF_INET, &(client.sin_addr), ip_address, INET_ADDRSTRLEN); + domain = (char*) &data[sizeof(dns_header_t) + 1]; + for(char* c=domain; *c != '\0'; c++){ + if(*c < ' ' || *c > 'z') *c = '.'; /* technically we should test if the first two bits are 00 (e.g. if( (*c & 0xC0) == 0x00) *c = '.') but this makes the code a lot more readable */ + } + ESP_LOGI(TAG, "Replying to DNS request for %s from %s", domain, ip_address); + + + /* create DNS answer at the end of the query*/ + dns_answer_t *dns_answer = (dns_answer_t*)&response[length]; + dns_answer->NAME = __bswap_16(0xC00C); /* This is a pointer to the beginning of the question. As per DNS standard, first two bits must be set to 11 for some odd reason hence 0xC0 */ + dns_answer->TYPE = __bswap_16(DNS_ANSWER_TYPE_A); + dns_answer->CLASS = __bswap_16(DNS_ANSWER_CLASS_IN); + dns_answer->TTL = (uint32_t)0x00000000; /* no caching. Avoids DNS poisoning since this is a DNS hijack */ + dns_answer->RDLENGTH = __bswap_16(0x0004); /* 4 byte => size of an ipv4 address */ + dns_answer->RDATA = ip_resolved.addr; + + err = sendto(socket_fd, response, length+sizeof(dns_answer_t), 0, (struct sockaddr *)&client, client_len); + if (err < 0) { + ESP_LOGE(TAG, "UDP sendto failed: %d", err); + } + } + + taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. DNS daemon should not be taxing on the system */ + + } + close(socket_fd); + + vTaskDelete ( NULL ); +} + + + + diff --git a/dist/esp32-wifi-manager_0.0.4/src/dns_server.h b/dist/esp32-wifi-manager_0.0.4/src/dns_server.h new file mode 100644 index 00000000..d1da1f8c --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/dns_server.h @@ -0,0 +1,137 @@ +/* +Copyright (c) 2019 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file dns_server.h +@author Tony Pottier +@brief Defines an extremly basic DNS server for captive portal functionality. + +Contains the freeRTOS task for the DNS server that processes the requests. + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +@see http://www.zytrax.com/books/dns/ch15 +*/ + +#ifndef MAIN_DNS_SERVER_H_ +#define MAIN_DNS_SERVER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** 12 byte header, 64 byte domain name, 4 byte qtype/qclass. This NOT compliant with the RFC, but it's good enough for a captive portal + * if a DNS query is too big it just wont be processed. */ +#define DNS_QUERY_MAX_SIZE 80 + +/** Query + 2 byte ptr, 2 byte type, 2 byte class, 4 byte TTL, 2 byte len, 4 byte data */ +#define DNS_ANSWER_MAX_SIZE (DNS_QUERY_MAX_SIZE+16) + + +/** + * @brief RCODE values used in a DNS header message + */ +typedef enum dns_reply_code_t { + DNS_REPLY_CODE_NO_ERROR = 0, + DNS_REPLY_CODE_FORM_ERROR = 1, + DNS_REPLY_CODE_SERVER_FAILURE = 2, + DNS_REPLY_CODE_NON_EXISTANT_DOMAIN = 3, + DNS_REPLY_CODE_NOT_IMPLEMENTED = 4, + DNS_REPLY_CODE_REFUSED = 5, + DNS_REPLY_CODE_YXDOMAIN = 6, + DNS_REPLY_CODE_YXRRSET = 7, + DNS_REPLY_CODE_NXRRSET = 8 +}dns_reply_code_t; + + + +/** + * @brief OPCODE values used in a DNS header message + */ +typedef enum dns_opcode_code_t { + DNS_OPCODE_QUERY = 0, + DNS_OPCODE_IQUERY = 1, + DNS_OPCODE_STATUS = 2 +}dns_opcode_code_t; + + + +/** + * @brief Represents a 12 byte DNS header. + * __packed__ is needed to prevent potential unwanted memory alignments + */ +typedef struct __attribute__((__packed__)) dns_header_t{ + uint16_t ID; // identification number + uint8_t RD : 1; // recursion desired + uint8_t TC : 1; // truncated message + uint8_t AA : 1; // authoritive answer + uint8_t OPCode : 4; // message_type + uint8_t QR : 1; // query/response flag + uint8_t RCode : 4; // response code + uint8_t Z : 3; // its z! reserved + uint8_t RA : 1; // recursion available + uint16_t QDCount; // number of question entries + uint16_t ANCount; // number of answer entries + uint16_t NSCount; // number of authority entries + uint16_t ARCount; // number of resource entries +}dns_header_t; + + + +typedef enum dns_answer_type_t { + DNS_ANSWER_TYPE_A = 1, + DNS_ANSWER_TYPE_NS = 2, + DNS_ANSWER_TYPE_CNAME = 5, + DNS_ANSWER_TYPE_SOA = 6, + DNS_ANSWER_TYPE_WKS = 11, + DNS_ANSWER_TYPE_PTR = 12, + DNS_ANSWER_TYPE_MX = 15, + DNS_ANSWER_TYPE_SRV = 33, + DNS_ANSWER_TYPE_AAAA = 28 +}dns_answer_type_t; + +typedef enum dns_answer_class_t { + DNS_ANSWER_CLASS_IN = 1 +}dns_answer_class_t; + + + +typedef struct __attribute__((__packed__)) dns_answer_t{ + uint16_t NAME; /* for the sake of simplicity only 16 bit pointers are supported */ + uint16_t TYPE; /* Unsigned 16 bit value. The resource record types - determines the content of the RDATA field. */ + uint16_t CLASS; /* Class of response. */ + uint32_t TTL; /* The time in seconds that the record may be cached. A value of 0 indicates the record should not be cached. */ + uint16_t RDLENGTH; /* Unsigned 16-bit value that defines the length in bytes of the RDATA record. */ + uint32_t RDATA; /* For the sake of simplicity only ipv4 is supported, and as such it's a unsigned 32 bit */ +}dns_answer_t; + +void dns_server(void *pvParameters); +void dns_server_start(); +void dns_server_stop(); + + + +#ifdef __cplusplus +} +#endif + + +#endif /* MAIN_DNS_SERVER_H_ */ diff --git a/dist/esp32-wifi-manager_0.0.4/src/http_app.c b/dist/esp32-wifi-manager_0.0.4/src/http_app.c new file mode 100644 index 00000000..8dbc40ef --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/http_app.c @@ -0,0 +1,479 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file http_app.c +@author Tony Pottier +@brief Defines all functions necessary for the HTTP server to run. + +Contains the freeRTOS task for the HTTP listener and all necessary support +function to process requests, decode URLs, serve files, etc. etc. + +@note http_server task cannot run without the wifi_manager task! +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "esp_netif.h" +#include + +#include "wifi_manager.h" +#include "http_app.h" + + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "http_server"; + +/* @brief the HTTP server handle */ +static httpd_handle_t httpd_handle = NULL; + +/* function pointers to URI handlers that can be user made */ +esp_err_t (*custom_get_httpd_uri_handler)(httpd_req_t *r) = NULL; +esp_err_t (*custom_post_httpd_uri_handler)(httpd_req_t *r) = NULL; + +/* strings holding the URLs of the wifi manager */ +static char* http_root_url = NULL; +static char* http_redirect_url = NULL; +static char* http_js_url = NULL; +static char* http_css_url = NULL; +static char* http_connect_url = NULL; +static char* http_ap_url = NULL; +static char* http_status_url = NULL; + +/** + * @brief embedded binary data. + * @see file "component.mk" + * @see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#embedding-binary-data + */ +extern const uint8_t style_css_start[] asm("_binary_style_css_start"); +extern const uint8_t style_css_end[] asm("_binary_style_css_end"); +extern const uint8_t code_js_start[] asm("_binary_code_js_start"); +extern const uint8_t code_js_end[] asm("_binary_code_js_end"); +extern const uint8_t index_html_start[] asm("_binary_index_html_start"); +extern const uint8_t index_html_end[] asm("_binary_index_html_end"); + + +/* const httpd related values stored in ROM */ +const static char http_200_hdr[] = "200 OK"; +const static char http_302_hdr[] = "302 Found"; +const static char http_400_hdr[] = "400 Bad Request"; +const static char http_404_hdr[] = "404 Not Found"; +const static char http_503_hdr[] = "503 Service Unavailable"; +const static char http_location_hdr[] = "Location"; +const static char http_content_type_html[] = "text/html"; +const static char http_content_type_js[] = "text/javascript"; +const static char http_content_type_css[] = "text/css"; +const static char http_content_type_json[] = "application/json"; +const static char http_cache_control_hdr[] = "Cache-Control"; +const static char http_cache_control_no_cache[] = "no-store, no-cache, must-revalidate, max-age=0"; +const static char http_cache_control_cache[] = "public, max-age=31536000"; +const static char http_pragma_hdr[] = "Pragma"; +const static char http_pragma_no_cache[] = "no-cache"; + + + +esp_err_t http_app_set_handler_hook( httpd_method_t method, esp_err_t (*handler)(httpd_req_t *r) ){ + + if(method == HTTP_GET){ + custom_get_httpd_uri_handler = handler; + return ESP_OK; + } + else if(method == HTTP_POST){ + custom_post_httpd_uri_handler = handler; + return ESP_OK; + } + else{ + return ESP_ERR_INVALID_ARG; + } + +} + + +static esp_err_t http_server_delete_handler(httpd_req_t *req){ + + ESP_LOGI(TAG, "DELETE %s", req->uri); + + /* DELETE /connect.json */ + if(strcmp(req->uri, http_connect_url) == 0){ + wifi_manager_disconnect_async(); + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, NULL, 0); + } + else{ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + + return ESP_OK; +} + + +static esp_err_t http_server_post_handler(httpd_req_t *req){ + + + esp_err_t ret = ESP_OK; + + ESP_LOGI(TAG, "POST %s", req->uri); + + /* POST /connect.json */ + if(strcmp(req->uri, http_connect_url) == 0){ + + + /* buffers for the headers */ + size_t ssid_len = 0, password_len = 0; + char *ssid = NULL, *password = NULL; + + /* len of values provided */ + ssid_len = httpd_req_get_hdr_value_len(req, "X-Custom-ssid"); + password_len = httpd_req_get_hdr_value_len(req, "X-Custom-pwd"); + + + if(ssid_len && ssid_len <= MAX_SSID_SIZE && password_len && password_len <= MAX_PASSWORD_SIZE){ + + /* get the actual value of the headers */ + ssid = malloc(sizeof(char) * (ssid_len + 1)); + password = malloc(sizeof(char) * (password_len + 1)); + httpd_req_get_hdr_value_str(req, "X-Custom-ssid", ssid, ssid_len+1); + httpd_req_get_hdr_value_str(req, "X-Custom-pwd", password, password_len+1); + + wifi_config_t* config = wifi_manager_get_wifi_sta_config(); + memset(config, 0x00, sizeof(wifi_config_t)); + memcpy(config->sta.ssid, ssid, ssid_len); + memcpy(config->sta.password, password, password_len); + ESP_LOGI(TAG, "ssid: %s, password: %s", ssid, password); + ESP_LOGD(TAG, "http_server_post_handler: wifi_manager_connect_async() call"); + wifi_manager_connect_async(); + + /* free memory */ + free(ssid); + free(password); + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, NULL, 0); + + } + else{ + /* bad request the authentification header is not complete/not the correct format */ + httpd_resp_set_status(req, http_400_hdr); + httpd_resp_send(req, NULL, 0); + } + + } + else{ + + if(custom_post_httpd_uri_handler == NULL){ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + else{ + + /* if there's a hook, run it */ + ret = (*custom_post_httpd_uri_handler)(req); + } + } + + return ret; +} + + +static esp_err_t http_server_get_handler(httpd_req_t *req){ + + char* host = NULL; + size_t buf_len; + esp_err_t ret = ESP_OK; + + ESP_LOGD(TAG, "GET %s", req->uri); + + /* Get header value string length and allocate memory for length + 1, + * extra byte for null termination */ + buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1; + if (buf_len > 1) { + host = malloc(buf_len); + if(httpd_req_get_hdr_value_str(req, "Host", host, buf_len) != ESP_OK){ + /* if something is wrong we just 0 the whole memory */ + memset(host, 0x00, buf_len); + } + } + + /* determine if Host is from the STA IP address */ + wifi_manager_lock_sta_ip_string(portMAX_DELAY); + bool access_from_sta_ip = host != NULL?strstr(host, wifi_manager_get_sta_ip_string()):false; + wifi_manager_unlock_sta_ip_string(); + + + if (host != NULL && !strstr(host, DEFAULT_AP_IP) && !access_from_sta_ip) { + + /* Captive Portal functionality */ + /* 302 Redirect to IP of the access point */ + httpd_resp_set_status(req, http_302_hdr); + httpd_resp_set_hdr(req, http_location_hdr, http_redirect_url); + httpd_resp_send(req, NULL, 0); + + } + else{ + + /* GET / */ + if(strcmp(req->uri, http_root_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_html); + httpd_resp_send(req, (char*)index_html_start, index_html_end - index_html_start); + } + /* GET /code.js */ + else if(strcmp(req->uri, http_js_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_js); + httpd_resp_send(req, (char*)code_js_start, code_js_end - code_js_start); + } + /* GET /style.css */ + else if(strcmp(req->uri, http_css_url) == 0){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_css); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_cache); + httpd_resp_send(req, (char*)style_css_start, style_css_end - style_css_start); + } + /* GET /ap.json */ + else if(strcmp(req->uri, http_ap_url) == 0){ + + /* if we can get the mutex, write the last version of the AP list */ + if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){ + + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + char* ap_buf = wifi_manager_get_ap_list_json(); + httpd_resp_send(req, ap_buf, strlen(ap_buf)); + wifi_manager_unlock_json_buffer(); + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + ESP_LOGE(TAG, "http_server_netconn_serve: GET /ap.json failed to obtain mutex"); + } + + /* request a wifi scan */ + wifi_manager_scan_async(); + } + /* GET /status.json */ + else if(strcmp(req->uri, http_status_url) == 0){ + + if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){ + char *buff = wifi_manager_get_ip_info_json(); + if(buff){ + httpd_resp_set_status(req, http_200_hdr); + httpd_resp_set_type(req, http_content_type_json); + httpd_resp_set_hdr(req, http_cache_control_hdr, http_cache_control_no_cache); + httpd_resp_set_hdr(req, http_pragma_hdr, http_pragma_no_cache); + httpd_resp_send(req, buff, strlen(buff)); + wifi_manager_unlock_json_buffer(); + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + } + } + else{ + httpd_resp_set_status(req, http_503_hdr); + httpd_resp_send(req, NULL, 0); + ESP_LOGE(TAG, "http_server_netconn_serve: GET /status.json failed to obtain mutex"); + } + } + else{ + + if(custom_get_httpd_uri_handler == NULL){ + httpd_resp_set_status(req, http_404_hdr); + httpd_resp_send(req, NULL, 0); + } + else{ + + /* if there's a hook, run it */ + ret = (*custom_get_httpd_uri_handler)(req); + } + } + + } + + /* memory clean up */ + if(host != NULL){ + free(host); + } + + return ret; + +} + +/* URI wild card for any GET request */ +static const httpd_uri_t http_server_get_request = { + .uri = "*", + .method = HTTP_GET, + .handler = http_server_get_handler +}; + +static const httpd_uri_t http_server_post_request = { + .uri = "*", + .method = HTTP_POST, + .handler = http_server_post_handler +}; + +static const httpd_uri_t http_server_delete_request = { + .uri = "*", + .method = HTTP_DELETE, + .handler = http_server_delete_handler +}; + + +void http_app_stop(){ + + if(httpd_handle != NULL){ + + + /* dealloc URLs */ + if(http_root_url) { + free(http_root_url); + http_root_url = NULL; + } + if(http_redirect_url){ + free(http_redirect_url); + http_redirect_url = NULL; + } + if(http_js_url){ + free(http_js_url); + http_js_url = NULL; + } + if(http_css_url){ + free(http_css_url); + http_css_url = NULL; + } + if(http_connect_url){ + free(http_connect_url); + http_connect_url = NULL; + } + if(http_ap_url){ + free(http_ap_url); + http_ap_url = NULL; + } + if(http_status_url){ + free(http_status_url); + http_status_url = NULL; + } + + /* stop server */ + httpd_stop(httpd_handle); + httpd_handle = NULL; + } +} + + +/** + * @brief helper to generate URLs of the wifi manager + */ +static char* http_app_generate_url(const char* page){ + + char* ret; + + int root_len = strlen(WEBAPP_LOCATION); + const size_t url_sz = sizeof(char) * ( (root_len+1) + ( strlen(page) + 1) ); + + ret = malloc(url_sz); + memset(ret, 0x00, url_sz); + strcpy(ret, WEBAPP_LOCATION); + ret = strcat(ret, page); + + return ret; +} + +void http_app_start(bool lru_purge_enable){ + + esp_err_t err; + + if(httpd_handle == NULL){ + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + /* this is an important option that isn't set up by default. + * We could register all URLs one by one, but this would not work while the fake DNS is active */ + config.uri_match_fn = httpd_uri_match_wildcard; + config.lru_purge_enable = lru_purge_enable; + + /* generate the URLs */ + if(http_root_url == NULL){ + int root_len = strlen(WEBAPP_LOCATION); + + /* all the pages */ + const char page_js[] = "code.js"; + const char page_css[] = "style.css"; + const char page_connect[] = "connect.json"; + const char page_ap[] = "ap.json"; + const char page_status[] = "status.json"; + + /* root url, eg "/" */ + const size_t http_root_url_sz = sizeof(char) * (root_len+1); + http_root_url = malloc(http_root_url_sz); + memset(http_root_url, 0x00, http_root_url_sz); + strcpy(http_root_url, WEBAPP_LOCATION); + + /* redirect url */ + size_t redirect_sz = 22 + root_len + 1; /* strlen(http://255.255.255.255) + strlen("/") + 1 for \0 */ + http_redirect_url = malloc(sizeof(char) * redirect_sz); + *http_redirect_url = '\0'; + + if(root_len == 1){ + snprintf(http_redirect_url, redirect_sz, "http://%s", DEFAULT_AP_IP); + } + else{ + snprintf(http_redirect_url, redirect_sz, "http://%s%s", DEFAULT_AP_IP, WEBAPP_LOCATION); + } + + /* generate the other pages URLs*/ + http_js_url = http_app_generate_url(page_js); + http_css_url = http_app_generate_url(page_css); + http_connect_url = http_app_generate_url(page_connect); + http_ap_url = http_app_generate_url(page_ap); + http_status_url = http_app_generate_url(page_status); + + } + + err = httpd_start(&httpd_handle, &config); + + if (err == ESP_OK) { + ESP_LOGI(TAG, "Registering URI handlers"); + httpd_register_uri_handler(httpd_handle, &http_server_get_request); + httpd_register_uri_handler(httpd_handle, &http_server_post_request); + httpd_register_uri_handler(httpd_handle, &http_server_delete_request); + } + } + +} diff --git a/dist/esp32-wifi-manager_0.0.4/src/http_app.h b/dist/esp32-wifi-manager_0.0.4/src/http_app.h new file mode 100644 index 00000000..53f9472a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/http_app.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file http_app.h +@author Tony Pottier +@brief Defines all functions necessary for the HTTP server to run. + +Contains the freeRTOS task for the HTTP listener and all necessary support +function to process requests, decode URLs, serve files, etc. etc. + +@note http_server task cannot run without the wifi_manager task! +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#ifndef HTTP_APP_H_INCLUDED +#define HTTP_APP_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** @brief Defines the URL where the wifi manager is located + * By default it is at the server root (ie "/"). If you wish to add your own webpages + * you may want to relocate the wifi manager to another URL, for instance /wifimanager + */ +#define WEBAPP_LOCATION CONFIG_WEBAPP_LOCATION + + +/** + * @brief spawns the http server + */ +void http_app_start(bool lru_purge_enable); + +/** + * @brief stops the http server + */ +void http_app_stop(); + +/** + * @brief sets a hook into the wifi manager URI handlers. Setting the handler to NULL disables the hook. + * @return ESP_OK in case of success, ESP_ERR_INVALID_ARG if the method is unsupported. + */ +esp_err_t http_app_set_handler_hook( httpd_method_t method, esp_err_t (*handler)(httpd_req_t *r) ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/esp32-wifi-manager_0.0.4/src/index.html b/dist/esp32-wifi-manager_0.0.4/src/index.html new file mode 100644 index 00000000..ce4bf146 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/index.html @@ -0,0 +1,136 @@ + + + + + + + + + esp32-wifi-manager + + +
+
+
+
+

Wi-Fi

+
+
+

Connected to:

+
+
+
+
+

Manual connect

+
+
ADD (HIDDEN) SSID
+
+

or choose a network...

+
+
+
Powered by esp32-wifi-manager.
+
+
+
+

Enter Details

+
+

Manual Connection

+
+ + +
+
+ + +
+
+
+
+

Enter Password

+
+

Password for

+
+ +
+
+ + +
+
+
+
+

Please wait...

+
+

Connecting to

+
+
+
+

You may lose wifi access while the esp32 recalibrates its radio. Please wait until your device automatically reconnects. This can take up to 30s.

+
+
+

Success!

+
+
+

Connection failed

+

Please double-check wifi password if any and make sure the access point has good signal.

+
+
+
+ +
+
+
+
+
+

+
+

+
+
+ +
+
+

IP Address

+
+
IP Address:
+
Subnet Mask:
+
Default Gateway:
+
+
+ +
+
+
+
+

Are you sure you would like to disconnect from this wifi?

+
+ + +
+
+
+
+
+
+
+
+

About this app...

+
+

+
+

esp32-wifi-manager, © 2017-2020, Tony Pottier
Licended under the MIT License.

+

+ This app would not be possible without the following libraries: +

+
    +
  • SpinKit, © 2015, Tobias Ahlin. Licensed under the MIT License.
  • +
  • jQuery, The jQuery Foundation. Licensed under the MIT License.
  • +
  • cJSON, © 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.
  • +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/json.c b/dist/esp32-wifi-manager_0.0.4/src/json.c new file mode 100644 index 00000000..d448711a --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/json.c @@ -0,0 +1,143 @@ +/* +@file json.c +@brief handles very basic JSON with a minimal footprint on the system + +This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license: +Copyright (c) 2009 Dave Gamble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +@see https://github.com/DaveGamble/cJSON +*/ + +#include +#include +#include +#include +#include "json.h" + + +bool json_print_string(const unsigned char *input, unsigned char *output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + //output = ensure(output_buffer, sizeof("\"\""), hooks); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + if (strchr("\"\\\b\f\n\r\t", *input_pointer)) + { + /* one character escape sequence */ + escape_characters++; + } + else if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + /* in the original cJSON it is possible to realloc here in case output buffer is too small. + * This is overkill for an embedded system. */ + output = output_buffer; + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + diff --git a/dist/esp32-wifi-manager_0.0.4/src/json.h b/dist/esp32-wifi-manager_0.0.4/src/json.h new file mode 100644 index 00000000..4c1c8c75 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/json.h @@ -0,0 +1,47 @@ +/* +@file json.h +@brief handles very basic JSON with a minimal footprint on the system + +This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license: +Copyright (c) 2009 Dave Gamble + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +@see https://github.com/DaveGamble/cJSON +*/ + +#ifndef JSON_H_INCLUDED +#define JSON_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Render the cstring provided to a JSON escaped version that can be printed. + * @param input the input buffer to be escaped. + * @param output_buffer the output buffer to write to. You must ensure it is big enough to contain the final string. + * @see cJSON equivlaent static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) + */ +bool json_print_string(const unsigned char *input, unsigned char *output_buffer); + +#ifdef __cplusplus +} +#endif + +#endif /* JSON_H_INCLUDED */ diff --git a/dist/esp32-wifi-manager_0.0.4/src/lock.svg b/dist/esp32-wifi-manager_0.0.4/src/lock.svg new file mode 100644 index 00000000..f7ebc7e9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/lock.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/nvs_sync.c b/dist/esp32-wifi-manager_0.0.4/src/nvs_sync.c new file mode 100644 index 00000000..4ceed8bd --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/nvs_sync.c @@ -0,0 +1,79 @@ +/** +Copyright (c) 2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file nvs_sync.c +@author Tony Pottier +@brief Exposes a simple API to synchronize NVS memory read and writes + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include "nvs_sync.h" + + +static SemaphoreHandle_t nvs_sync_mutex = NULL; + +esp_err_t nvs_sync_create(){ + if(nvs_sync_mutex == NULL){ + + nvs_sync_mutex = xSemaphoreCreateMutex(); + + if(nvs_sync_mutex){ + return ESP_OK; + } + else{ + return ESP_FAIL; + } + } + else{ + return ESP_OK; + } +} + +void nvs_sync_free(){ + if(nvs_sync_mutex != NULL){ + vSemaphoreDelete( nvs_sync_mutex ); + nvs_sync_mutex = NULL; + } +} + +bool nvs_sync_lock(TickType_t xTicksToWait){ + if(nvs_sync_mutex){ + if( xSemaphoreTake( nvs_sync_mutex, xTicksToWait ) == pdTRUE ) { + return true; + } + else{ + return false; + } + } + else{ + return false; + } +} + +void nvs_sync_unlock(){ + xSemaphoreGive( nvs_sync_mutex ); +} \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/nvs_sync.h b/dist/esp32-wifi-manager_0.0.4/src/nvs_sync.h new file mode 100644 index 00000000..e03396e5 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/nvs_sync.h @@ -0,0 +1,76 @@ +/** +Copyright (c) 2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file nvs_sync.h +@author Tony Pottier +@brief Exposes a simple API to synchronize NVS memory read and writes + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + + + +#ifndef WIFI_MANAGER_NVS_SYNC_H_INCLUDED +#define WIFI_MANAGER_NVS_SYNC_H_INCLUDED + +#include /* for type bool */ +#include /* for TickType_t */ +#include /* for esp_err_t */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Attempts to get hold of the NVS semaphore for a set amount of ticks. + * @note If you are uncertain about the number of ticks to wait use portMAX_DELAY. + * @return true on a succesful lock, false otherwise + */ +bool nvs_sync_lock(TickType_t xTicksToWait); + + +/** + * @brief Releases the NVS semaphore + */ +void nvs_sync_unlock(); + + +/** + * @brief Create the NVS semaphore + * @return ESP_OK: success or if the semaphore already exists + * ESP_FAIL: failure + */ +esp_err_t nvs_sync_create(); + +/** + * @brief Frees memory associated with the NVS semaphore + * @warning Do not delete a semaphore that has tasks blocked on it (tasks that are in the Blocked state waiting for the semaphore to become available). + */ +void nvs_sync_free(); + + +#ifdef __cplusplus +} +#endif + +#endif /* WIFI_MANAGER_NVS_SYNC_H_INCLUDED */ \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/settings.svg b/dist/esp32-wifi-manager_0.0.4/src/settings.svg new file mode 100644 index 00000000..19cacede --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/settings.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/status b/dist/esp32-wifi-manager_0.0.4/src/status new file mode 100644 index 00000000..3824a535 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/status @@ -0,0 +1 @@ +{"ssid":"zodmgbbq","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":0} \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/style.css b/dist/esp32-wifi-manager_0.0.4/src/style.css new file mode 100644 index 00000000..5c83cc2f --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/style.css @@ -0,0 +1,248 @@ +body { + background-color: #eee; + border: 0; + margin: 0; + font: 1.1em tahoma, arial, sans-serif; +} +a { + color: darkblue; + transition: color .2s ease-out; + text-decoration: none +} +a:hover { + color: red; +} +input { + display: none; + font: 1.1em tahoma, arial, sans-serif; +} +input:focus, +select:focus, +textarea:focus, +button:focus { + outline: none; +} +input[type="button"] { + width: 100px; + padding: 5px; + text-align: center; + display: block; +} +p { + padding: 10px; +} +#credits { + display: none; +} +#app {} #app-wrap {} #disconnect { + width: 150px; +} +.diag-box { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + height: 100%; + width: 100%; + display: none; +} +.diag-box-win { + position: absolute; + left: 10%; + width: 80%; + text-align: center; + border: 2px outset #888; + background-color: #fff; + border-radius: 10px; + top: 20%; +} +.blur { + -webkit-filter: blur(2px); + -moz-filter: blur(2px); + -ms-filter: blur(2px); + -o-filter: blur(2px); + filter: blur(2px); +} +.ape { + margin-left: 20px; + padding: 10px 0px 10px 10px; +} +.ape:hover { + cursor: pointer; +} +.brdb { + border-bottom: 1px solid #888; +} +header { + background-color: #fff; + border-bottom: 1px solid #888; +} +section { + background-color: #fff; + border-bottom: 1px solid #888; + border-top: 1px solid #888; +} +h1 { + display: block; + text-align: center; + margin: 0; + padding: 15px; + font-size: 1.4em +} +h2 { + margin: 0; + margin-top: 20px; + padding: 10px; + text-transform: uppercase; + color: #888; + font-size: 1.0em +} +h3 { + margin: 0; + text-align: center; + padding: 20px 0px 20px 0px; +} +.gr { + color: green; +} +.rd { + color: red; +} +#wifi-status { + display: none; +} +#connect { + display: none; +} +#connect_manual { + display: none; +} +#manual_ssid { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +#manual_pwd { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +#pwd { + border: none; + width: 80%; + margin-left: 35px; + padding: 10px 0px 10px 10px; + display: block +} +.buttons { + padding: 15px; +} +#join { + float: right; +} +#manual_join { + float: right; +} +#yes-disconnect { + display: inline-block; + margin-left: 20px; +} +#no-disconnect { + display: inline-block; +} +.ctr { + margin: 0 auto; +} +.tctr { + text-align: center; +} +#connect-wait { + display: none; +} +#connect-success { + display: none; +} +#connect-fail { + display: none; +} +#connect-details { + display: none; +} +.fr { + float: right; + margin-right: 20px; +} +.w0 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPHBhdGggZD0iTTUsMTNMNywxNUM5Ljc2LDEyLjI0IDE0LjI0LDEyLjI0IDE3LDE1TDE5LDEzQzE1LjE0LDkuMTQgOC44Nyw5LjE0IDUsMTNaIiBzdHlsZT0iZmlsbDogYmxhY2s7Ii8+CjxwYXRoIGQ9Ik05LDE3TDEyLDIwTDE1LDE3QzEzLjM1LDE1LjM0IDEwLjY2LDE1LjM0IDksMTdaIiBzdHlsZT0iZmlsbDogYmxhY2s7Ii8+Cjwvc3ZnPgo=') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w1 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPHBhdGggZD0iTTksMTdMMTIsMjBMMTUsMTdDMTMuMzUsMTUuMzQgMTAuNjYsMTUuMzQgOSwxN1oiIHN0eWxlPSJmaWxsOiBibGFjazsiLz4KPC9zdmc+Cg==') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w2 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNOSwxN0wxMiwyMEwxNSwxN0MxMy4zNSwxNS4zNCAxMC42NiwxNS4zNCA5LDE3WiIgc3R5bGU9ImZpbGw6IGJsYWNrOyIvPgo8L3N2Zz4K') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.w3 { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIGQ9Ik0xLDlMMywxMUM3Ljk3LDYuMDMgMTYuMDMsNi4wMyAyMSwxMUwyMyw5QzE2LjkzLDIuOTMgNy4wOCwyLjkzIDEsOVoiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNNSwxM0w3LDE1QzkuNzYsMTIuMjQgMTQuMjQsMTIuMjQgMTcsMTVMMTksMTNDMTUuMTQsOS4xNCA4Ljg3LDkuMTQgNSwxM1oiIHN0eWxlPSJmaWxsOiBncmF5OyIvPgo8cGF0aCBkPSJNOSwxN0wxMiwyMEwxNSwxN0MxMy4zNSwxNS4zNCAxMC42NiwxNS4zNCA5LDE3WiIgc3R5bGU9ImZpbGw6IGdyYXk7Ii8+Cjwvc3ZnPgo=') no-repeat right top; + height: 24px; + margin-right: 20px; +} +.pw { + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxwYXRoIHN0eWxlPSJmaWxsOiBibGFjazsiIGQ9Ik0xOCA4aC0xVjZjMC0yLjc2LTIuMjQtNS01LTVTNyAzLjI0IDcgNnYySDZjLTEuMSAwLTIgLjktMiAydjEwYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWMTBjMC0xLjEtLjktMi0yLTJ6bS02IDljLTEuMSAwLTItLjktMi0ycy45LTIgMi0yIDIgLjkgMiAyLS45IDItMiAyem0zLjEtOUg4LjlWNmMwLTEuNzEgMS4zOS0zLjEgMy4xLTMuMSAxLjcxIDAgMy4xIDEuMzkgMy4xIDMuMXYyeiI+PC9wYXRoPgo8L3N2Zz4=') no-repeat right top; + height: 24px; + margin-right: 30px; +} +/* SpinKit is licensed under the MIT License. Copyright (c) 2015 Tobias Ahlin */ + +.spinner { + width: 40px; + height: 40px; + position: relative; + margin: 100px auto; +} +.double-bounce1, +.double-bounce2 { + width: 100%; + height: 100%; + border-radius: 50%; + background-color: #333; + opacity: 0.6; + position: absolute; + top: 0; + left: 0; + -webkit-animation: sk-bounce 2.0s infinite ease-in-out; + animation: sk-bounce 2.0s infinite ease-in-out; +} +.double-bounce2 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} +@-webkit-keyframes sk-bounce { + 0%, 100% { + -webkit-transform: scale(0.0) + } + 50% { + -webkit-transform: scale(1.0) + } +} +@keyframes sk-bounce { + 0%, 100% { + transform: scale(0.0); + -webkit-transform: scale(0.0); + } + 50% { + transform: scale(1.0); + -webkit-transform: scale(1.0); + } +} +/* end of SpinKit */ \ No newline at end of file diff --git a/dist/esp32-wifi-manager_0.0.4/src/wifi0.svg b/dist/esp32-wifi-manager_0.0.4/src/wifi0.svg new file mode 100644 index 00000000..394a6495 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/wifi0.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.4/src/wifi1.svg b/dist/esp32-wifi-manager_0.0.4/src/wifi1.svg new file mode 100644 index 00000000..c64211bf --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/wifi1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.4/src/wifi2.svg b/dist/esp32-wifi-manager_0.0.4/src/wifi2.svg new file mode 100644 index 00000000..033b9de9 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/wifi2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.4/src/wifi3.svg b/dist/esp32-wifi-manager_0.0.4/src/wifi3.svg new file mode 100644 index 00000000..7cd5667e --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/wifi3.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dist/esp32-wifi-manager_0.0.4/src/wifi_manager.c b/dist/esp32-wifi-manager_0.0.4/src/wifi_manager.c new file mode 100644 index 00000000..3d11a940 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/wifi_manager.c @@ -0,0 +1,1336 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file wifi_manager.c +@author Tony Pottier +@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis + +Contains the freeRTOS task and all necessary support + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#include +#include +#include +#include +#include +#include "esp_system.h" +#include +#include +#include +#include +#include +#include +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_wifi_types.h" +#include "esp_log.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "mdns.h" +#include "lwip/api.h" +#include "lwip/err.h" +#include "lwip/netdb.h" +#include "lwip/ip4_addr.h" + + +#include "json.h" +#include "dns_server.h" +#include "nvs_sync.h" +#include "wifi_manager.h" + + + +/* objects used to manipulate the main queue of events */ +QueueHandle_t wifi_manager_queue; + +/* @brief software timer to wait between each connection retry. + * There is no point hogging a hardware timer for a functionality like this which only needs to be 'accurate enough' */ +TimerHandle_t wifi_manager_retry_timer = NULL; + +/* @brief software timer that will trigger shutdown of the AP after a succesful STA connection + * There is no point hogging a hardware timer for a functionality like this which only needs to be 'accurate enough' */ +TimerHandle_t wifi_manager_shutdown_ap_timer = NULL; + +SemaphoreHandle_t wifi_manager_json_mutex = NULL; +SemaphoreHandle_t wifi_manager_sta_ip_mutex = NULL; +char *wifi_manager_sta_ip = NULL; +uint16_t ap_num = MAX_AP_NUM; +wifi_ap_record_t *accessp_records; +char *accessp_json = NULL; +char *ip_info_json = NULL; +wifi_config_t* wifi_manager_config_sta = NULL; + +/* @brief Array of callback function pointers */ +void (**cb_ptr_arr)(void*) = NULL; + +/* @brief tag used for ESP serial console messages */ +static const char TAG[] = "wifi_manager"; + +/* @brief task handle for the main wifi_manager task */ +static TaskHandle_t task_wifi_manager = NULL; + +/* @brief netif object for the STATION */ +static esp_netif_t* esp_netif_sta = NULL; + +/* @brief netif object for the ACCESS POINT */ +static esp_netif_t* esp_netif_ap = NULL; + +/** + * The actual WiFi settings in use + */ +struct wifi_settings_t wifi_settings = { + .ap_ssid = DEFAULT_AP_SSID, + .ap_pwd = DEFAULT_AP_PASSWORD, + .ap_channel = DEFAULT_AP_CHANNEL, + .ap_ssid_hidden = DEFAULT_AP_SSID_HIDDEN, + .ap_bandwidth = DEFAULT_AP_BANDWIDTH, + .sta_only = DEFAULT_STA_ONLY, + .sta_power_save = DEFAULT_STA_POWER_SAVE, + .sta_static_ip = 0, +}; + +const char wifi_manager_nvs_namespace[] = "espwifimgr"; + +static EventGroupHandle_t wifi_manager_event_group; + +/* @brief indicate that the ESP32 is currently connected. */ +const int WIFI_MANAGER_WIFI_CONNECTED_BIT = BIT0; + +const int WIFI_MANAGER_AP_STA_CONNECTED_BIT = BIT1; + +/* @brief Set automatically once the SoftAP is started */ +const int WIFI_MANAGER_AP_STARTED_BIT = BIT2; + +/* @brief When set, means a client requested to connect to an access point.*/ +const int WIFI_MANAGER_REQUEST_STA_CONNECT_BIT = BIT3; + +/* @brief This bit is set automatically as soon as a connection was lost */ +const int WIFI_MANAGER_STA_DISCONNECT_BIT = BIT4; + +/* @brief When set, means the wifi manager attempts to restore a previously saved connection at startup. */ +const int WIFI_MANAGER_REQUEST_RESTORE_STA_BIT = BIT5; + +/* @brief When set, means a client requested to disconnect from currently connected AP. */ +const int WIFI_MANAGER_REQUEST_WIFI_DISCONNECT_BIT = BIT6; + +/* @brief When set, means a scan is in progress */ +const int WIFI_MANAGER_SCAN_BIT = BIT7; + +/* @brief When set, means user requested for a disconnect */ +const int WIFI_MANAGER_REQUEST_DISCONNECT_BIT = BIT8; + + + +void wifi_manager_timer_retry_cb( TimerHandle_t xTimer ){ + + ESP_LOGI(TAG, "Retry Timer Tick! Sending ORDER_CONNECT_STA with reason CONNECTION_REQUEST_AUTO_RECONNECT"); + + /* stop the timer */ + xTimerStop( xTimer, (TickType_t) 0 ); + + /* Attempt to reconnect */ + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_AUTO_RECONNECT); + +} + +void wifi_manager_timer_shutdown_ap_cb( TimerHandle_t xTimer){ + + /* stop the timer */ + xTimerStop( xTimer, (TickType_t) 0 ); + + /* Attempt to shutdown AP */ + wifi_manager_send_message(WM_ORDER_STOP_AP, NULL); +} + +void wifi_manager_scan_async(){ + wifi_manager_send_message(WM_ORDER_START_WIFI_SCAN, NULL); +} + +void wifi_manager_disconnect_async(){ + wifi_manager_send_message(WM_ORDER_DISCONNECT_STA, NULL); +} + + +void wifi_manager_start(){ + + /* disable the default wifi logging */ + esp_log_level_set("wifi", ESP_LOG_NONE); + + /* initialize flash memory */ + nvs_flash_init(); + ESP_ERROR_CHECK(nvs_sync_create()); /* semaphore for thread synchronization on NVS memory */ + + /* memory allocation */ + wifi_manager_queue = xQueueCreate( 3, sizeof( queue_message) ); + wifi_manager_json_mutex = xSemaphoreCreateMutex(); + accessp_records = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * MAX_AP_NUM); + accessp_json = (char*)malloc(MAX_AP_NUM * JSON_ONE_APP_SIZE + 4); /* 4 bytes for json encapsulation of "[\n" and "]\0" */ + wifi_manager_clear_access_points_json(); + ip_info_json = (char*)malloc(sizeof(char) * JSON_IP_INFO_SIZE); + wifi_manager_clear_ip_info_json(); + wifi_manager_config_sta = (wifi_config_t*)malloc(sizeof(wifi_config_t)); + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + memset(&wifi_settings.sta_static_ip_config, 0x00, sizeof(esp_netif_ip_info_t)); + cb_ptr_arr = malloc(sizeof(void (*)(void*)) * WM_MESSAGE_CODE_COUNT); + for(int i=0; ista.ssid) != 0){ + /* different ssid or ssid does not exist in flash: save new ssid */ + esp_err = nvs_set_blob(handle, "ssid", wifi_manager_config_sta->sta.ssid, 32); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + ESP_LOGI(TAG, "wifi_manager_wrote wifi_sta_config: ssid:%s",wifi_manager_config_sta->sta.ssid); + + } + + sz = sizeof(tmp_conf.sta.password); + esp_err = nvs_get_blob(handle, "password", tmp_conf.sta.password, &sz); + if( (esp_err == ESP_OK || esp_err == ESP_ERR_NVS_NOT_FOUND) && strcmp( (char*)tmp_conf.sta.password, (char*)wifi_manager_config_sta->sta.password) != 0){ + /* different password or password does not exist in flash: save new password */ + esp_err = nvs_set_blob(handle, "password", wifi_manager_config_sta->sta.password, 64); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + ESP_LOGI(TAG, "wifi_manager_wrote wifi_sta_config: password:%s",wifi_manager_config_sta->sta.password); + } + + sz = sizeof(tmp_settings); + esp_err = nvs_get_blob(handle, "settings", &tmp_settings, &sz); + if( (esp_err == ESP_OK || esp_err == ESP_ERR_NVS_NOT_FOUND) && + ( + strcmp( (char*)tmp_settings.ap_ssid, (char*)wifi_settings.ap_ssid) != 0 || + strcmp( (char*)tmp_settings.ap_pwd, (char*)wifi_settings.ap_pwd) != 0 || + tmp_settings.ap_ssid_hidden != wifi_settings.ap_ssid_hidden || + tmp_settings.ap_bandwidth != wifi_settings.ap_bandwidth || + tmp_settings.sta_only != wifi_settings.sta_only || + tmp_settings.sta_power_save != wifi_settings.sta_power_save || + tmp_settings.ap_channel != wifi_settings.ap_channel + ) + ){ + esp_err = nvs_set_blob(handle, "settings", &wifi_settings, sizeof(wifi_settings)); + if (esp_err != ESP_OK){ + nvs_sync_unlock(); + return esp_err; + } + change = true; + + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_ssid: %s",wifi_settings.ap_ssid); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_pwd: %s",wifi_settings.ap_pwd); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_channel: %i",wifi_settings.ap_channel); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_hidden (1 = yes): %i",wifi_settings.ap_ssid_hidden); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_bandwidth (1 = 20MHz, 2 = 40MHz): %i",wifi_settings.ap_bandwidth); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_only (0 = APSTA, 1 = STA when connected): %i",wifi_settings.sta_only); + ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_power_save (1 = yes): %i",wifi_settings.sta_power_save); + } + + if(change){ + esp_err = nvs_commit(handle); + } + else{ + ESP_LOGI(TAG, "Wifi config was not saved to flash because no change has been detected."); + } + + if (esp_err != ESP_OK) return esp_err; + + nvs_close(handle); + nvs_sync_unlock(); + + } + else{ + ESP_LOGE(TAG, "wifi_manager_save_sta_config failed to acquire nvs_sync mutex"); + } + + return ESP_OK; +} + +bool wifi_manager_fetch_wifi_sta_config(){ + + nvs_handle handle; + esp_err_t esp_err; + if(nvs_sync_lock( portMAX_DELAY )){ + + esp_err = nvs_open(wifi_manager_nvs_namespace, NVS_READONLY, &handle); + + if(esp_err != ESP_OK){ + nvs_sync_unlock(); + return false; + } + + if(wifi_manager_config_sta == NULL){ + wifi_manager_config_sta = (wifi_config_t*)malloc(sizeof(wifi_config_t)); + } + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + + /* allocate buffer */ + size_t sz = sizeof(wifi_settings); + uint8_t *buff = (uint8_t*)malloc(sizeof(uint8_t) * sz); + memset(buff, 0x00, sizeof(sz)); + + /* ssid */ + sz = sizeof(wifi_manager_config_sta->sta.ssid); + esp_err = nvs_get_blob(handle, "ssid", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(wifi_manager_config_sta->sta.ssid, buff, sz); + + /* password */ + sz = sizeof(wifi_manager_config_sta->sta.password); + esp_err = nvs_get_blob(handle, "password", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(wifi_manager_config_sta->sta.password, buff, sz); + + /* settings */ + sz = sizeof(wifi_settings); + esp_err = nvs_get_blob(handle, "settings", buff, &sz); + if(esp_err != ESP_OK){ + free(buff); + nvs_sync_unlock(); + return false; + } + memcpy(&wifi_settings, buff, sz); + + free(buff); + nvs_close(handle); + nvs_sync_unlock(); + + + ESP_LOGI(TAG, "wifi_manager_fetch_wifi_sta_config: ssid:%s password:%s",wifi_manager_config_sta->sta.ssid,wifi_manager_config_sta->sta.password); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_ssid:%s",wifi_settings.ap_ssid); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_pwd:%s",wifi_settings.ap_pwd); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_channel:%i",wifi_settings.ap_channel); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_hidden (1 = yes):%i",wifi_settings.ap_ssid_hidden); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_bandwidth (1 = 20MHz, 2 = 40MHz)%i",wifi_settings.ap_bandwidth); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_only (0 = APSTA, 1 = STA when connected):%i",wifi_settings.sta_only); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_power_save (1 = yes):%i",wifi_settings.sta_power_save); + ESP_LOGD(TAG, "wifi_manager_fetch_wifi_settings: sta_static_ip (0 = dhcp client, 1 = static ip):%i",wifi_settings.sta_static_ip); + + return wifi_manager_config_sta->sta.ssid[0] != '\0'; + + + } + else{ + return false; + } + +} + + +void wifi_manager_clear_ip_info_json(){ + strcpy(ip_info_json, "{}\n"); +} + + +void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code){ + + wifi_config_t *config = wifi_manager_get_wifi_sta_config(); + if(config){ + + const char *ip_info_json_format = ",\"ip\":\"%s\",\"netmask\":\"%s\",\"gw\":\"%s\",\"urc\":%d}\n"; + + memset(ip_info_json, 0x00, JSON_IP_INFO_SIZE); + + /* to avoid declaring a new buffer we copy the data directly into the buffer at its correct address */ + strcpy(ip_info_json, "{\"ssid\":"); + json_print_string(config->sta.ssid, (unsigned char*)(ip_info_json+strlen(ip_info_json)) ); + + size_t ip_info_json_len = strlen(ip_info_json); + size_t remaining = JSON_IP_INFO_SIZE - ip_info_json_len; + if(update_reason_code == UPDATE_CONNECTION_OK){ + /* rest of the information is copied after the ssid */ + esp_netif_ip_info_t ip_info; + ESP_ERROR_CHECK(esp_netif_get_ip_info(esp_netif_sta, &ip_info)); + + char ip[IP4ADDR_STRLEN_MAX]; /* note: IP4ADDR_STRLEN_MAX is defined in lwip */ + char gw[IP4ADDR_STRLEN_MAX]; + char netmask[IP4ADDR_STRLEN_MAX]; + + esp_ip4addr_ntoa(&ip_info.ip, ip, IP4ADDR_STRLEN_MAX); + esp_ip4addr_ntoa(&ip_info.gw, gw, IP4ADDR_STRLEN_MAX); + esp_ip4addr_ntoa(&ip_info.netmask, netmask, IP4ADDR_STRLEN_MAX); + + + snprintf( (ip_info_json + ip_info_json_len), remaining, ip_info_json_format, + ip, + netmask, + gw, + (int)update_reason_code); + } + else{ + /* notify in the json output the reason code why this was updated without a connection */ + snprintf( (ip_info_json + ip_info_json_len), remaining, ip_info_json_format, + "0", + "0", + "0", + (int)update_reason_code); + } + } + else{ + wifi_manager_clear_ip_info_json(); + } + + +} + + +void wifi_manager_clear_access_points_json(){ + strcpy(accessp_json, "[]\n"); +} +void wifi_manager_generate_acess_points_json(){ + + strcpy(accessp_json, "["); + + + const char oneap_str[] = ",\"chan\":%d,\"rssi\":%d,\"auth\":%d}%c\n"; + + /* stack buffer to hold on to one AP until it's copied over to accessp_json */ + char one_ap[JSON_ONE_APP_SIZE]; + for(int i=0; i and + * . + * + * Another thing deserves our attention is that the default behavior of LwIP is to abort all TCP socket connections on + * receiving the disconnect. Most of time it is not a problem. However, for some special application, this may not be + * what they want, consider following scenarios: + * + * The application creates a TCP connection to maintain the application-level keep-alive data that is sent out + * every 60 seconds. + * + * Due to certain reasons, the Wi-Fi connection is cut off, and the is raised. + * According to the current implementation, all TCP connections will be removed and the keep-alive socket will be + * in a wrong status. However, since the application designer believes that the network layer should NOT care about + * this error at the Wi-Fi layer, the application does not close the socket. + * + * Five seconds later, the Wi-Fi connection is restored because esp_wifi_connect() is called in the application + * event callback function. Moreover, the station connects to the same AP and gets the same IPV4 address as before. + * + * Sixty seconds later, when the application sends out data with the keep-alive socket, the socket returns an error + * and the application closes the socket and re-creates it when necessary. + * + * In above scenario, ideally, the application sockets and the network layer should not be affected, since the Wi-Fi + * connection only fails temporarily and recovers very quickly. The application can enable “Keep TCP connections when + * IP changed” via LwIP menuconfig.*/ + case WIFI_EVENT_STA_DISCONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED"); + + wifi_event_sta_disconnected_t* wifi_event_sta_disconnected = (wifi_event_sta_disconnected_t*)malloc(sizeof(wifi_event_sta_disconnected_t)); + *wifi_event_sta_disconnected = *( (wifi_event_sta_disconnected_t*)event_data ); + + /* if a DISCONNECT message is posted while a scan is in progress this scan will NEVER end, causing scan to never work again. For this reason SCAN_BIT is cleared too */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT | WIFI_MANAGER_SCAN_BIT); + + /* post disconnect event with reason code */ + wifi_manager_send_message(WM_EVENT_STA_DISCONNECTED, (void*)wifi_event_sta_disconnected ); + break; + + /* This event arises when the AP to which the station is connected changes its authentication mode, e.g., from no auth + * to WPA. Upon receiving this event, the event task will do nothing. Generally, the application event callback does + * not need to handle this either. */ + case WIFI_EVENT_STA_AUTHMODE_CHANGE: + ESP_LOGI(TAG, "WIFI_EVENT_STA_AUTHMODE_CHANGE"); + break; + + case WIFI_EVENT_AP_START: + ESP_LOGI(TAG, "WIFI_EVENT_AP_START"); + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STARTED_BIT); + break; + + case WIFI_EVENT_AP_STOP: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STOP"); + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_AP_STARTED_BIT); + break; + + /* Every time a station is connected to ESP32 AP, the will arise. Upon receiving this + * event, the event task will do nothing, and the application callback can also ignore it. However, you may want + * to do something, for example, to get the info of the connected STA, etc. */ + case WIFI_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STACONNECTED"); + break; + + /* This event can happen in the following scenarios: + * The application calls esp_wifi_disconnect(), or esp_wifi_deauth_sta(), to manually disconnect the station. + * The Wi-Fi driver kicks off the station, e.g. because the AP has not received any packets in the past five minutes, etc. + * The station kicks off the AP. + * When this event happens, the event task will do nothing, but the application event callback needs to do + * something, e.g., close the socket which is related to this station, etc. */ + case WIFI_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_STADISCONNECTED"); + break; + + /* This event is disabled by default. The application can enable it via API esp_wifi_set_event_mask(). + * When this event is enabled, it will be raised each time the AP receives a probe request. */ + case WIFI_EVENT_AP_PROBEREQRECVED: + ESP_LOGI(TAG, "WIFI_EVENT_AP_PROBEREQRECVED"); + break; + + } /* end switch */ + } + else if(event_base == IP_EVENT){ + + switch(event_id){ + + /* This event arises when the DHCP client successfully gets the IPV4 address from the DHCP server, + * or when the IPV4 address is changed. The event means that everything is ready and the application can begin + * its tasks (e.g., creating sockets). + * The IPV4 may be changed because of the following reasons: + * The DHCP client fails to renew/rebind the IPV4 address, and the station’s IPV4 is reset to 0. + * The DHCP client rebinds to a different address. + * The static-configured IPV4 address is changed. + * Whether the IPV4 address is changed or NOT is indicated by field ip_change of ip_event_got_ip_t. + * The socket is based on the IPV4 address, which means that, if the IPV4 changes, all sockets relating to this + * IPV4 will become abnormal. Upon receiving this event, the application needs to close all sockets and recreate + * the application when the IPV4 changes to a valid one. */ + case IP_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "IP_EVENT_STA_GOT_IP"); + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT); + ip_event_got_ip_t* ip_event_got_ip = (ip_event_got_ip_t*)malloc(sizeof(ip_event_got_ip_t)); + *ip_event_got_ip = *( (ip_event_got_ip_t*)event_data ); + wifi_manager_send_message(WM_EVENT_STA_GOT_IP, (void*)(ip_event_got_ip) ); + break; + + /* This event arises when the IPV6 SLAAC support auto-configures an address for the ESP32, or when this address changes. + * The event means that everything is ready and the application can begin its tasks (e.g., creating sockets). */ + case IP_EVENT_GOT_IP6: + ESP_LOGI(TAG, "IP_EVENT_GOT_IP6"); + break; + + /* This event arises when the IPV4 address become invalid. + * IP_STA_LOST_IP doesn’t arise immediately after the WiFi disconnects, instead it starts an IPV4 address lost timer, + * if the IPV4 address is got before ip lost timer expires, IP_EVENT_STA_LOST_IP doesn’t happen. Otherwise, the event + * arises when IPV4 address lost timer expires. + * Generally the application don’t need to care about this event, it is just a debug event to let the application + * know that the IPV4 address is lost. */ + case IP_EVENT_STA_LOST_IP: + ESP_LOGI(TAG, "IP_EVENT_STA_LOST_IP"); + break; + + } + } + +} + + +wifi_config_t* wifi_manager_get_wifi_sta_config(){ + return wifi_manager_config_sta; +} + + +void wifi_manager_connect_async(){ + /* in order to avoid a false positive on the front end app we need to quickly flush the ip json + * There'se a risk the front end sees an IP or a password error when in fact + * it's a remnant from a previous connection + */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_clear_ip_info_json(); + wifi_manager_unlock_json_buffer(); + } + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_USER); +} + + +char* wifi_manager_get_ip_info_json(){ + return ip_info_json; +} + + +void wifi_manager_destroy(){ + + vTaskDelete(task_wifi_manager); + task_wifi_manager = NULL; + + /* heap buffers */ + free(accessp_records); + accessp_records = NULL; + free(accessp_json); + accessp_json = NULL; + free(ip_info_json); + ip_info_json = NULL; + free(wifi_manager_sta_ip); + wifi_manager_sta_ip = NULL; + if(wifi_manager_config_sta){ + free(wifi_manager_config_sta); + wifi_manager_config_sta = NULL; + } + + /* RTOS objects */ + vSemaphoreDelete(wifi_manager_json_mutex); + wifi_manager_json_mutex = NULL; + vSemaphoreDelete(wifi_manager_sta_ip_mutex); + wifi_manager_sta_ip_mutex = NULL; + vEventGroupDelete(wifi_manager_event_group); + wifi_manager_event_group = NULL; + vQueueDelete(wifi_manager_queue); + wifi_manager_queue = NULL; + + +} + + +void wifi_manager_filter_unique( wifi_ap_record_t * aplist, uint16_t * aps) { + int total_unique; + wifi_ap_record_t * first_free; + total_unique=*aps; + + first_free=NULL; + + for(int i=0; i<*aps-1;i++) { + wifi_ap_record_t * ap = &aplist[i]; + + /* skip the previously removed APs */ + if (ap->ssid[0] == 0) continue; + + /* remove the identical SSID+authmodes */ + for(int j=i+1; j<*aps;j++) { + wifi_ap_record_t * ap1 = &aplist[j]; + if ( (strcmp((const char *)ap->ssid, (const char *)ap1->ssid)==0) && + (ap->authmode == ap1->authmode) ) { /* same SSID, different auth mode is skipped */ + /* save the rssi for the display */ + if ((ap1->rssi) > (ap->rssi)) ap->rssi=ap1->rssi; + /* clearing the record */ + memset(ap1,0, sizeof(wifi_ap_record_t)); + } + } + } + /* reorder the list so APs follow each other in the list */ + for(int i=0; i<*aps;i++) { + wifi_ap_record_t * ap = &aplist[i]; + /* skipping all that has no name */ + if (ap->ssid[0] == 0) { + /* mark the first free slot */ + if (first_free==NULL) first_free=ap; + total_unique--; + continue; + } + if (first_free!=NULL) { + memcpy(first_free, ap, sizeof(wifi_ap_record_t)); + memset(ap,0, sizeof(wifi_ap_record_t)); + /* find the next free slot */ + for(int j=0; j<*aps;j++) { + if (aplist[j].ssid[0]==0) { + first_free=&aplist[j]; + break; + } + } + } + } + /* update the length of the list */ + *aps = total_unique; +} + + +BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param){ + queue_message msg; + msg.code = code; + msg.param = param; + return xQueueSendToFront( wifi_manager_queue, &msg, portMAX_DELAY); +} + +BaseType_t wifi_manager_send_message(message_code_t code, void *param){ + queue_message msg; + msg.code = code; + msg.param = param; + return xQueueSend( wifi_manager_queue, &msg, portMAX_DELAY); +} + + +void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) ){ + + if(cb_ptr_arr && message_code < WM_MESSAGE_CODE_COUNT){ + cb_ptr_arr[message_code] = func_ptr; + } +} + +esp_netif_t* wifi_manager_get_esp_netif_ap(){ + return esp_netif_ap; +} + +esp_netif_t* wifi_manager_get_esp_netif_sta(){ + return esp_netif_sta; +} + +void wifi_manager( void * pvParameters ){ + + + queue_message msg; + BaseType_t xStatus; + EventBits_t uxBits; + uint8_t retries = 0; + + + /* initialize the tcp stack */ + ESP_ERROR_CHECK(esp_netif_init()); + + /* event loop for the wifi driver */ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + esp_netif_sta = esp_netif_create_default_wifi_sta(); + esp_netif_ap = esp_netif_create_default_wifi_ap(); + + + /* default wifi config */ + wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + + /* event handler for the connection */ + esp_event_handler_instance_t instance_wifi_event; + esp_event_handler_instance_t instance_ip_event; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_manager_event_handler, NULL,&instance_wifi_event)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &wifi_manager_event_handler, NULL,&instance_ip_event)); + + + /* SoftAP - Wifi Access Point configuration setup */ + wifi_config_t ap_config = { + .ap = { + .ssid_len = 0, + .channel = wifi_settings.ap_channel, + .ssid_hidden = wifi_settings.ap_ssid_hidden, + .max_connection = DEFAULT_AP_MAX_CONNECTIONS, + .beacon_interval = DEFAULT_AP_BEACON_INTERVAL, + }, + }; + memcpy(ap_config.ap.ssid, wifi_settings.ap_ssid , sizeof(wifi_settings.ap_ssid)); + + /* if the password lenght is under 8 char which is the minium for WPA2, the access point starts as open */ + if(strlen( (char*)wifi_settings.ap_pwd) < WPA2_MINIMUM_PASSWORD_LENGTH){ + ap_config.ap.authmode = WIFI_AUTH_OPEN; + memset( ap_config.ap.password, 0x00, sizeof(ap_config.ap.password) ); + } + else{ + ap_config.ap.authmode = WIFI_AUTH_WPA2_PSK; + memcpy(ap_config.ap.password, wifi_settings.ap_pwd, sizeof(wifi_settings.ap_pwd)); + } + + + /* DHCP AP configuration */ + esp_netif_dhcps_stop(esp_netif_ap); /* DHCP client/server must be stopped before setting new IP information. */ + esp_netif_ip_info_t ap_ip_info; + memset(&ap_ip_info, 0x00, sizeof(ap_ip_info)); + inet_pton(AF_INET, DEFAULT_AP_IP, &ap_ip_info.ip); + inet_pton(AF_INET, DEFAULT_AP_GATEWAY, &ap_ip_info.gw); + inet_pton(AF_INET, DEFAULT_AP_NETMASK, &ap_ip_info.netmask); + ESP_ERROR_CHECK(esp_netif_set_ip_info(esp_netif_ap, &ap_ip_info)); + ESP_ERROR_CHECK(esp_netif_dhcps_start(esp_netif_ap)); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &ap_config)); + ESP_ERROR_CHECK(esp_wifi_set_bandwidth(WIFI_IF_AP, wifi_settings.ap_bandwidth)); + ESP_ERROR_CHECK(esp_wifi_set_ps(wifi_settings.sta_power_save)); + + + /* by default the mode is STA because wifi_manager will not start the access point unless it has to! */ + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + + /* start http server */ + http_app_start(false); + + /* wifi scanner config */ + wifi_scan_config_t scan_config = { + .ssid = 0, + .bssid = 0, + .channel = 0, + .show_hidden = true + }; + + /* enqueue first event: load previous config */ + wifi_manager_send_message(WM_ORDER_LOAD_AND_RESTORE_STA, NULL); + + + /* main processing loop */ + for(;;){ + xStatus = xQueueReceive( wifi_manager_queue, &msg, portMAX_DELAY ); + + if( xStatus == pdPASS ){ + switch(msg.code){ + + case WM_EVENT_SCAN_DONE:{ + wifi_event_sta_scan_done_t *evt_scan_done = (wifi_event_sta_scan_done_t*)msg.param; + /* only check for AP if the scan is succesful */ + if(evt_scan_done->status == 0){ + /* As input param, it stores max AP number ap_records can hold. As output param, it receives the actual AP number this API returns. + * As a consequence, ap_num MUST be reset to MAX_AP_NUM at every scan */ + ap_num = MAX_AP_NUM; + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_num, accessp_records)); + /* make sure the http server isn't trying to access the list while it gets refreshed */ + if(wifi_manager_lock_json_buffer( pdMS_TO_TICKS(1000) )){ + /* Will remove the duplicate SSIDs from the list and update ap_num */ + wifi_manager_filter_unique(accessp_records, &ap_num); + wifi_manager_generate_acess_points_json(); + wifi_manager_unlock_json_buffer(); + } + else{ + ESP_LOGE(TAG, "could not get access to json mutex in wifi_scan"); + } + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(evt_scan_done); + } + break; + + case WM_ORDER_START_WIFI_SCAN: + ESP_LOGD(TAG, "MESSAGE: ORDER_START_WIFI_SCAN"); + + /* if a scan is already in progress this message is simply ignored thanks to the WIFI_MANAGER_SCAN_BIT uxBit */ + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if(! (uxBits & WIFI_MANAGER_SCAN_BIT) ){ + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT); + ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, false)); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_LOAD_AND_RESTORE_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_LOAD_AND_RESTORE_STA"); + if(wifi_manager_fetch_wifi_sta_config()){ + ESP_LOGI(TAG, "Saved wifi found on startup. Will attempt to connect."); + wifi_manager_send_message(WM_ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_RESTORE_CONNECTION); + } + else{ + /* no wifi saved: start soft AP! This is what should happen during a first run */ + ESP_LOGI(TAG, "No saved wifi found on startup. Starting access point."); + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_CONNECT_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_CONNECT_STA"); + + /* very important: precise that this connection attempt is specifically requested. + * Param in that case is a boolean indicating if the request was made automatically + * by the wifi_manager. + * */ + if((BaseType_t)msg.param == CONNECTION_REQUEST_USER) { + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + } + else if((BaseType_t)msg.param == CONNECTION_REQUEST_RESTORE_CONNECTION) { + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + } + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if( ! (uxBits & WIFI_MANAGER_WIFI_CONNECTED_BIT) ){ + /* update config to latest and attempt connection */ + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_manager_get_wifi_sta_config())); + + /* if there is a wifi scan in progress abort it first + Calling esp_wifi_scan_stop will trigger a SCAN_DONE event which will reset this bit */ + if(uxBits & WIFI_MANAGER_SCAN_BIT){ + esp_wifi_scan_stop(); + } + ESP_ERROR_CHECK(esp_wifi_connect()); + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_EVENT_STA_DISCONNECTED: + ;wifi_event_sta_disconnected_t* wifi_event_sta_disconnected = (wifi_event_sta_disconnected_t*)msg.param; + ESP_LOGI(TAG, "MESSAGE: EVENT_STA_DISCONNECTED with Reason code: %d", wifi_event_sta_disconnected->reason); + + /* this even can be posted in numerous different conditions + * + * 1. SSID password is wrong + * 2. Manual disconnection ordered + * 3. Connection lost + * + * Having clear understand as to WHY the event was posted is key to having an efficient wifi manager + * + * With wifi_manager, we determine: + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT is set, We consider it's a client that requested the connection. + * When SYSTEM_EVENT_STA_DISCONNECTED is posted, it's probably a password/something went wrong with the handshake. + * + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT is set, it's a disconnection that was ASKED by the client (clicking disconnect in the app) + * When SYSTEM_EVENT_STA_DISCONNECTED is posted, saved wifi is erased from the NVS memory. + * + * If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT and WIFI_MANAGER_REQUEST_STA_CONNECT_BIT are NOT set, it's a lost connection + * + * In this version of the software, reason codes are not used. They are indicated here for potential future usage. + * + * REASON CODE: + * 1 UNSPECIFIED + * 2 AUTH_EXPIRE auth no longer valid, this smells like someone changed a password on the AP + * 3 AUTH_LEAVE + * 4 ASSOC_EXPIRE + * 5 ASSOC_TOOMANY too many devices already connected to the AP => AP fails to respond + * 6 NOT_AUTHED + * 7 NOT_ASSOCED + * 8 ASSOC_LEAVE tested as manual disconnect by user OR in the wireless MAC blacklist + * 9 ASSOC_NOT_AUTHED + * 10 DISASSOC_PWRCAP_BAD + * 11 DISASSOC_SUPCHAN_BAD + * 12 + * 13 IE_INVALID + * 14 MIC_FAILURE + * 15 4WAY_HANDSHAKE_TIMEOUT wrong password! This was personnaly tested on my home wifi with a wrong password. + * 16 GROUP_KEY_UPDATE_TIMEOUT + * 17 IE_IN_4WAY_DIFFERS + * 18 GROUP_CIPHER_INVALID + * 19 PAIRWISE_CIPHER_INVALID + * 20 AKMP_INVALID + * 21 UNSUPP_RSN_IE_VERSION + * 22 INVALID_RSN_IE_CAP + * 23 802_1X_AUTH_FAILED wrong password? + * 24 CIPHER_SUITE_REJECTED + * 200 BEACON_TIMEOUT + * 201 NO_AP_FOUND + * 202 AUTH_FAIL + * 203 ASSOC_FAIL + * 204 HANDSHAKE_TIMEOUT + * + * */ + + /* reset saved sta IP */ + wifi_manager_safe_update_sta_ip_string((uint32_t)0); + + /* if there was a timer on to stop the AP, well now it's time to cancel that since connection was lost! */ + if(xTimerIsTimerActive(wifi_manager_shutdown_ap_timer) == pdTRUE ){ + xTimerStop( wifi_manager_shutdown_ap_timer, (TickType_t)0 ); + } + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + if( uxBits & WIFI_MANAGER_REQUEST_STA_CONNECT_BIT ){ + /* there are no retries when it's a user requested connection by design. This avoids a user hanging too much + * in case they typed a wrong password for instance. Here we simply clear the request bit and move on */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_FAILED_ATTEMPT ); + wifi_manager_unlock_json_buffer(); + } + + } + else if (uxBits & WIFI_MANAGER_REQUEST_DISCONNECT_BIT){ + /* user manually requested a disconnect so the lost connection is a normal event. Clear the flag and restart the AP */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_DISCONNECT_BIT); + + /* erase configuration */ + if(wifi_manager_config_sta){ + memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t)); + } + + /* regenerate json status */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_USER_DISCONNECT ); + wifi_manager_unlock_json_buffer(); + } + + /* save NVS memory */ + wifi_manager_save_sta_config(); + + /* start SoftAP */ + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + else{ + /* lost connection ? */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION ); + wifi_manager_unlock_json_buffer(); + } + + /* Start the timer that will try to restore the saved config */ + xTimerStart( wifi_manager_retry_timer, (TickType_t)0 ); + + /* if it was a restore attempt connection, we clear the bit */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + + /* if the AP is not started, we check if we have reached the threshold of failed attempt to start it */ + if(! (uxBits & WIFI_MANAGER_AP_STARTED_BIT) ){ + + /* if the nunber of retries is below the threshold to start the AP, a reconnection attempt is made + * This way we avoid restarting the AP directly in case the connection is mementarily lost */ + if(retries < WIFI_MANAGER_MAX_RETRY_START_AP){ + retries++; + } + else{ + /* In this scenario the connection was lost beyond repair: kick start the AP! */ + retries = 0; + + /* start SoftAP */ + wifi_manager_send_message(WM_ORDER_START_AP, NULL); + } + } + } + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(wifi_event_sta_disconnected); + + break; + + case WM_ORDER_START_AP: + ESP_LOGI(TAG, "MESSAGE: ORDER_START_AP"); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA)); + + /* restart HTTP daemon */ + http_app_stop(); + http_app_start(true); + + /* start DNS */ + dns_server_start(); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + case WM_ORDER_STOP_AP: + ESP_LOGI(TAG, "MESSAGE: ORDER_STOP_AP"); + + + uxBits = xEventGroupGetBits(wifi_manager_event_group); + + /* before stopping the AP, we check that we are still connected. There's a chance that once the timer + * kicks in, for whatever reason the esp32 is already disconnected. + */ + if(uxBits & WIFI_MANAGER_WIFI_CONNECTED_BIT){ + + /* set to STA only */ + esp_wifi_set_mode(WIFI_MODE_STA); + + /* stop DNS */ + dns_server_stop(); + + /* restart HTTP daemon */ + http_app_stop(); + http_app_start(false); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + } + + break; + + case WM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "WM_EVENT_STA_GOT_IP"); + ip_event_got_ip_t* ip_event_got_ip = (ip_event_got_ip_t*)msg.param; + uxBits = xEventGroupGetBits(wifi_manager_event_group); + + /* reset connection requests bits -- doesn't matter if it was set or not */ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT); + + /* save IP as a string for the HTTP server host */ + wifi_manager_safe_update_sta_ip_string(ip_event_got_ip->ip_info.ip.addr); + + /* save wifi config in NVS if it wasn't a restored of a connection */ + if(uxBits & WIFI_MANAGER_REQUEST_RESTORE_STA_BIT){ + xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT); + } + else{ + wifi_manager_save_sta_config(); + } + + /* reset number of retries */ + retries = 0; + + /* refresh JSON with the new IP */ + if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ + /* generate the connection info with success */ + wifi_manager_generate_ip_info_json( UPDATE_CONNECTION_OK ); + wifi_manager_unlock_json_buffer(); + } + else { abort(); } + + /* bring down DNS hijack */ + dns_server_stop(); + + /* start the timer that will eventually shutdown the access point + * We check first that it's actually running because in case of a boot and restore connection + * the AP is not even started to begin with. + */ + if(uxBits & WIFI_MANAGER_AP_STARTED_BIT){ + TickType_t t = pdMS_TO_TICKS( WIFI_MANAGER_SHUTDOWN_AP_TIMER ); + + /* if for whatever reason user configured the shutdown timer to be less than 1 tick, the AP is stopped straight away */ + if(t > 0){ + xTimerStart( wifi_manager_shutdown_ap_timer, (TickType_t)0 ); + } + else{ + wifi_manager_send_message(WM_ORDER_STOP_AP, (void*)NULL); + } + + } + + /* callback and free memory allocated for the void* param */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])( msg.param ); + free(ip_event_got_ip); + + break; + + case WM_ORDER_DISCONNECT_STA: + ESP_LOGI(TAG, "MESSAGE: ORDER_DISCONNECT_STA"); + + /* precise this is coming from a user request */ + xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_DISCONNECT_BIT); + + /* order wifi discconect */ + ESP_ERROR_CHECK(esp_wifi_disconnect()); + + /* callback */ + if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); + + break; + + default: + break; + + } /* end of switch/case */ + } /* end of if status=pdPASS */ + } /* end of for loop */ + + vTaskDelete( NULL ); + +} + + diff --git a/dist/esp32-wifi-manager_0.0.4/src/wifi_manager.h b/dist/esp32-wifi-manager_0.0.4/src/wifi_manager.h new file mode 100644 index 00000000..cba3b989 --- /dev/null +++ b/dist/esp32-wifi-manager_0.0.4/src/wifi_manager.h @@ -0,0 +1,417 @@ +/* +Copyright (c) 2017-2020 Tony Pottier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +@file wifi_manager.h +@author Tony Pottier +@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis + +Contains the freeRTOS task and all necessary support + +@see https://idyl.io +@see https://github.com/tonyp7/esp32-wifi-manager +*/ + +#ifndef WIFI_MANAGER_H_INCLUDED +#define WIFI_MANAGER_H_INCLUDED + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Defines the maximum size of a SSID name. 32 is IEEE standard. + * @warning limit is also hard coded in wifi_config_t. Never extend this value. + */ +#define MAX_SSID_SIZE 32 + +/** + * @brief Defines the maximum size of a WPA2 passkey. 64 is IEEE standard. + * @warning limit is also hard coded in wifi_config_t. Never extend this value. + */ +#define MAX_PASSWORD_SIZE 64 + + +/** + * @brief Defines the maximum number of access points that can be scanned. + * + * To save memory and avoid nasty out of memory errors, + * we can limit the number of APs detected in a wifi scan. + */ +#define MAX_AP_NUM 15 + + +/** + * @brief Defines the maximum number of failed retries allowed before the WiFi manager starts its own access point. + * Setting it to 2 for instance means there will be 3 attempts in total (original request + 2 retries) + */ +#define WIFI_MANAGER_MAX_RETRY_START_AP CONFIG_WIFI_MANAGER_MAX_RETRY_START_AP + +/** + * @brief Time (in ms) between each retry attempt + * Defines the time to wait before an attempt to re-connect to a saved wifi is made after connection is lost or another unsuccesful attempt is made. + */ +#define WIFI_MANAGER_RETRY_TIMER CONFIG_WIFI_MANAGER_RETRY_TIMER + + +/** + * @brief Time (in ms) to wait before shutting down the AP + * Defines the time (in ms) to wait after a succesful connection before shutting down the access point. + */ +#define WIFI_MANAGER_SHUTDOWN_AP_TIMER CONFIG_WIFI_MANAGER_SHUTDOWN_AP_TIMER + + +/** @brief Defines the task priority of the wifi_manager. + * + * Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1. + * For this particular reason, minimum task priority is 1. It it highly not recommended to set + * it to 1 though as the sub-tasks will now have a priority of 0 which is the priority + * of freeRTOS' idle task. + */ +#define WIFI_MANAGER_TASK_PRIORITY CONFIG_WIFI_MANAGER_TASK_PRIORITY + +/** @brief Defines the auth mode as an access point + * Value must be of type wifi_auth_mode_t + * @see esp_wifi_types.h + * @warning if set to WIFI_AUTH_OPEN, passowrd me be empty. See DEFAULT_AP_PASSWORD. + */ +#define AP_AUTHMODE WIFI_AUTH_WPA2_PSK + +/** @brief Defines visibility of the access point. 0: visible AP. 1: hidden */ +#define DEFAULT_AP_SSID_HIDDEN 0 + +/** @brief Defines access point's name. Default value: esp32. Run 'make menuconfig' to setup your own value or replace here by a string */ +#define DEFAULT_AP_SSID CONFIG_DEFAULT_AP_SSID + +/** @brief Defines access point's password. + * @warning In the case of an open access point, the password must be a null string "" or "\0" if you want to be verbose but waste one byte. + * In addition, the AP_AUTHMODE must be WIFI_AUTH_OPEN + */ +#define DEFAULT_AP_PASSWORD CONFIG_DEFAULT_AP_PASSWORD + +/** @brief Defines the hostname broadcasted by mDNS */ +#define DEFAULT_HOSTNAME "esp32" + +/** @brief Defines access point's bandwidth. + * Value: WIFI_BW_HT20 for 20 MHz or WIFI_BW_HT40 for 40 MHz + * 20 MHz minimize channel interference but is not suitable for + * applications with high data speeds + */ +#define DEFAULT_AP_BANDWIDTH WIFI_BW_HT20 + +/** @brief Defines access point's channel. + * Channel selection is only effective when not connected to another AP. + * Good practice for minimal channel interference to use + * For 20 MHz: 1, 6 or 11 in USA and 1, 5, 9 or 13 in most parts of the world + * For 40 MHz: 3 in USA and 3 or 11 in most parts of the world + */ +#define DEFAULT_AP_CHANNEL CONFIG_DEFAULT_AP_CHANNEL + + + +/** @brief Defines the access point's default IP address. Default: "10.10.0.1 */ +#define DEFAULT_AP_IP CONFIG_DEFAULT_AP_IP + +/** @brief Defines the access point's gateway. This should be the same as your IP. Default: "10.10.0.1" */ +#define DEFAULT_AP_GATEWAY CONFIG_DEFAULT_AP_GATEWAY + +/** @brief Defines the access point's netmask. Default: "255.255.255.0" */ +#define DEFAULT_AP_NETMASK CONFIG_DEFAULT_AP_NETMASK + +/** @brief Defines access point's maximum number of clients. Default: 4 */ +#define DEFAULT_AP_MAX_CONNECTIONS CONFIG_DEFAULT_AP_MAX_CONNECTIONS + +/** @brief Defines access point's beacon interval. 100ms is the recommended default. */ +#define DEFAULT_AP_BEACON_INTERVAL CONFIG_DEFAULT_AP_BEACON_INTERVAL + +/** @brief Defines if esp32 shall run both AP + STA when connected to another AP. + * Value: 0 will have the own AP always on (APSTA mode) + * Value: 1 will turn off own AP when connected to another AP (STA only mode when connected) + * Turning off own AP when connected to another AP minimize channel interference and increase throughput + */ +#define DEFAULT_STA_ONLY 1 + +/** @brief Defines if wifi power save shall be enabled. + * Value: WIFI_PS_NONE for full power (wifi modem always on) + * Value: WIFI_PS_MODEM for power save (wifi modem sleep periodically) + * Note: Power save is only effective when in STA only mode + */ +#define DEFAULT_STA_POWER_SAVE WIFI_PS_NONE + +/** + * @brief Defines the maximum length in bytes of a JSON representation of an access point. + * + * maximum ap string length with full 32 char ssid: 75 + \\n + \0 = 77\n + * example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","chan":12,"rssi":-100,"auth":4},\n + * BUT: we need to escape JSON. Imagine a ssid full of \" ? so it's 32 more bytes hence 77 + 32 = 99.\n + * this is an edge case but I don't think we should crash in a catastrophic manner just because + * someone decided to have a funny wifi name. + */ +#define JSON_ONE_APP_SIZE 99 + +/** + * @brief Defines the maximum length in bytes of a JSON representation of the IP information + * assuming all ips are 4*3 digits, and all characters in the ssid require to be escaped. + * example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":99} + * Run this JS (browser console is easiest) to come to the conclusion that 159 is the worst case. + * ``` + * var a = {"ssid":"abcdefghijklmnopqrstuvwxyz012345","ip":"255.255.255.255","netmask":"255.255.255.255","gw":"255.255.255.255","urc":99}; + * // Replace all ssid characters with a double quote which will have to be escaped + * a.ssid = a.ssid.split('').map(() => '"').join(''); + * console.log(JSON.stringify(a).length); // => 158 +1 for null + * console.log(JSON.stringify(a)); // print it + * ``` + */ +#define JSON_IP_INFO_SIZE 159 + + +/** + * @brief defines the minimum length of an access point password running on WPA2 + */ +#define WPA2_MINIMUM_PASSWORD_LENGTH 8 + + +/** + * @brief Defines the complete list of all messages that the wifi_manager can process. + * + * Some of these message are events ("EVENT"), and some of them are action ("ORDER") + * Each of these messages can trigger a callback function and each callback function is stored + * in a function pointer array for convenience. Because of this behavior, it is extremely important + * to maintain a strict sequence and the top level special element 'MESSAGE_CODE_COUNT' + * + * @see wifi_manager_set_callback + */ +typedef enum message_code_t { + NONE = 0, + WM_ORDER_START_HTTP_SERVER = 1, + WM_ORDER_STOP_HTTP_SERVER = 2, + WM_ORDER_START_DNS_SERVICE = 3, + WM_ORDER_STOP_DNS_SERVICE = 4, + WM_ORDER_START_WIFI_SCAN = 5, + WM_ORDER_LOAD_AND_RESTORE_STA = 6, + WM_ORDER_CONNECT_STA = 7, + WM_ORDER_DISCONNECT_STA = 8, + WM_ORDER_START_AP = 9, + WM_EVENT_STA_DISCONNECTED = 10, + WM_EVENT_SCAN_DONE = 11, + WM_EVENT_STA_GOT_IP = 12, + WM_ORDER_STOP_AP = 13, + WM_MESSAGE_CODE_COUNT = 14 /* important for the callback array */ + +}message_code_t; + +/** + * @brief simplified reason codes for a lost connection. + * + * esp-idf maintains a big list of reason codes which in practice are useless for most typical application. + */ +typedef enum update_reason_code_t { + UPDATE_CONNECTION_OK = 0, + UPDATE_FAILED_ATTEMPT = 1, + UPDATE_USER_DISCONNECT = 2, + UPDATE_LOST_CONNECTION = 3 +}update_reason_code_t; + +typedef enum connection_request_made_by_code_t{ + CONNECTION_REQUEST_NONE = 0, + CONNECTION_REQUEST_USER = 1, + CONNECTION_REQUEST_AUTO_RECONNECT = 2, + CONNECTION_REQUEST_RESTORE_CONNECTION = 3, + CONNECTION_REQUEST_MAX = 0x7fffffff /*force the creation of this enum as a 32 bit int */ +}connection_request_made_by_code_t; + +/** + * The actual WiFi settings in use + */ +struct wifi_settings_t{ + uint8_t ap_ssid[MAX_SSID_SIZE]; + uint8_t ap_pwd[MAX_PASSWORD_SIZE]; + uint8_t ap_channel; + uint8_t ap_ssid_hidden; + wifi_bandwidth_t ap_bandwidth; + bool sta_only; + wifi_ps_type_t sta_power_save; + bool sta_static_ip; + esp_netif_ip_info_t sta_static_ip_config; +}; +extern struct wifi_settings_t wifi_settings; + + +/** + * @brief Structure used to store one message in the queue. + */ +typedef struct{ + message_code_t code; + void *param; +} queue_message; + + +/** + * @brief returns the current esp_netif object for the STAtion + */ +esp_netif_t* wifi_manager_get_esp_netif_sta(); + +/** + * @brief returns the current esp_netif object for the Access Point + */ +esp_netif_t* wifi_manager_get_esp_netif_ap(); + + +/** + * Allocate heap memory for the wifi manager and start the wifi_manager RTOS task + */ +void wifi_manager_start(); + +/** + * Frees up all memory allocated by the wifi_manager and kill the task. + */ +void wifi_manager_destroy(); + +/** + * Filters the AP scan list to unique SSIDs + */ +void filter_unique( wifi_ap_record_t * aplist, uint16_t * ap_num); + +/** + * Main task for the wifi_manager + */ +void wifi_manager( void * pvParameters ); + + +char* wifi_manager_get_ap_list_json(); +char* wifi_manager_get_ip_info_json(); + + +void wifi_manager_scan_async(); + + +/** + * @brief saves the current STA wifi config to flash ram storage. + */ +esp_err_t wifi_manager_save_sta_config(); + +/** + * @brief fetch a previously STA wifi config in the flash ram storage. + * @return true if a previously saved config was found, false otherwise. + */ +bool wifi_manager_fetch_wifi_sta_config(); + +wifi_config_t* wifi_manager_get_wifi_sta_config(); + + +/** + * @brief requests a connection to an access point that will be process in the main task thread. + */ +void wifi_manager_connect_async(); + +/** + * @brief requests a wifi scan + */ +void wifi_manager_scan_awifi_manager_send_messagesync(); + +/** + * @brief requests to disconnect and forget about the access point. + */ +void wifi_manager_disconnect_async(); + +/** + * @brief Tries to get access to json buffer mutex. + * + * The HTTP server can try to access the json to serve clients while the wifi manager thread can try + * to update it. These two tasks are synchronized through a mutex. + * + * The mutex is used by both the access point list json and the connection status json.\n + * These two resources should technically have their own mutex but we lose some flexibility to save + * on memory. + * + * This is a simple wrapper around freeRTOS function xSemaphoreTake. + * + * @param xTicksToWait The time in ticks to wait for the semaphore to become available. + * @return true in success, false otherwise. + */ +bool wifi_manager_lock_json_buffer(TickType_t xTicksToWait); + +/** + * @brief Releases the json buffer mutex. + */ +void wifi_manager_unlock_json_buffer(); + +/** + * @brief Generates the connection status json: ssid and IP addresses. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code); +/** + * @brief Clears the connection status json. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_clear_ip_info_json(); + +/** + * @brief Generates the list of access points after a wifi scan. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_generate_acess_points_json(); + +/** + * @brief Clear the list of access points. + * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful. + */ +void wifi_manager_clear_access_points_json(); + + +/** + * @brief Start the mDNS service + */ +void wifi_manager_initialise_mdns(); + + +bool wifi_manager_lock_sta_ip_string(TickType_t xTicksToWait); +void wifi_manager_unlock_sta_ip_string(); + +/** + * @brief gets the string representation of the STA IP address, e.g.: "192.168.1.69" + */ +char* wifi_manager_get_sta_ip_string(); + +/** + * @brief thread safe char representation of the STA IP update + */ +void wifi_manager_safe_update_sta_ip_string(uint32_t ip); + + +/** + * @brief Register a callback to a custom function when specific event message_code happens. + */ +void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) ); + + +BaseType_t wifi_manager_send_message(message_code_t code, void *param); +BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param); + +#ifdef __cplusplus +} +#endif + +#endif /* WIFI_MANAGER_H_INCLUDED */ diff --git a/examples/default_demo/CMakeLists.txt b/examples/default_demo/CMakeLists.txt index 39289112..6458baae 100644 --- a/examples/default_demo/CMakeLists.txt +++ b/examples/default_demo/CMakeLists.txt @@ -3,4 +3,4 @@ cmake_minimum_required(VERSION 3.5) set(EXTRA_COMPONENT_DIRS ../../) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(wifi_manager) +project(default_demo) diff --git a/examples/default_demo/main/idf_component.yml b/examples/default_demo/main/idf_component.yml new file mode 100644 index 00000000..8826a619 --- /dev/null +++ b/examples/default_demo/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "^1.3.2" + ## Required IDF version + idf: + version: ">=4.1.0" + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true diff --git a/examples/default_demo/main/user_main.c b/examples/default_demo/main/user_main.c index 4c11767f..19af535e 100644 --- a/examples/default_demo/main/user_main.c +++ b/examples/default_demo/main/user_main.c @@ -47,7 +47,7 @@ static const char TAG[] = "main"; void monitoring_task(void *pvParameter) { for(;;){ - ESP_LOGI(TAG, "free heap: %d",esp_get_free_heap_size()); + ESP_LOGI(TAG, "free heap: %lu",esp_get_free_heap_size()); vTaskDelay( pdMS_TO_TICKS(10000) ); } } @@ -73,7 +73,8 @@ void app_main() /* register a callback as an example to how you can integrate your code with the wifi manager */ wifi_manager_set_callback(WM_EVENT_STA_GOT_IP, &cb_connection_ok); - +#if CONFIG_SOC_CPU_CORES_NUM > 1 /* your code should go here. Here we simply create a task on core 2 that monitors free heap memory */ xTaskCreatePinnedToCore(&monitoring_task, "monitoring_task", 2048, NULL, 1, NULL, 1); +#endif } diff --git a/examples/http_hook/CMakeLists.txt b/examples/http_hook/CMakeLists.txt index 39289112..377f86fe 100644 --- a/examples/http_hook/CMakeLists.txt +++ b/examples/http_hook/CMakeLists.txt @@ -3,4 +3,4 @@ cmake_minimum_required(VERSION 3.5) set(EXTRA_COMPONENT_DIRS ../../) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(wifi_manager) +project(http_hook) diff --git a/examples/http_hook/main/idf_component.yml b/examples/http_hook/main/idf_component.yml new file mode 100644 index 00000000..8826a619 --- /dev/null +++ b/examples/http_hook/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "^1.3.2" + ## Required IDF version + idf: + version: ">=4.1.0" + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 00000000..563b4c5b --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,8 @@ +dependencies: + idf: + version: '>=4.2' +description: esp32-wifi-manager is a pure C esp-idf component for ESP32 that enables easy management of wifi networks through a web portal. +url: https://github.com/ankayca/esp32-wifi-manager/tree/update_build_system +repository: https://github.com/ankayca/esp32-wifi-manager/tree/update_build_system +version: 0.0.4 +license: "MIT" \ No newline at end of file diff --git a/src/wifi_manager.c b/src/wifi_manager.c index 3d11a940..d934563f 100644 --- a/src/wifi_manager.c +++ b/src/wifi_manager.c @@ -1018,6 +1018,8 @@ void wifi_manager( void * pvParameters ){ if(! (uxBits & WIFI_MANAGER_SCAN_BIT) ){ xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT); ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, false)); + ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, false)); + esp_wifi_scan_start(&scan_config, false); } /* callback */