diff --git a/arduino/libraries/WiFi/src/WiFi.cpp b/arduino/libraries/WiFi/src/WiFi.cpp
index e1580705..eefbb286 100644
--- a/arduino/libraries/WiFi/src/WiFi.cpp
+++ b/arduino/libraries/WiFi/src/WiFi.cpp
@@ -615,11 +615,19 @@ esp_err_t WiFiClass::systemEventHandler(void* ctx, system_event_t* event)
return ESP_OK;
}
+#include "wifi_manager.h"
+
void WiFiClass::handleSystemEvent(system_event_t* event)
{
switch (event->event_id) {
case SYSTEM_EVENT_SCAN_DONE:
xEventGroupSetBits(_eventGroup, BIT2);
+
+ if (wifi_manager_event_group != NULL) {
+ xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT);
+ wifi_manager_send_message(EVENT_SCAN_DONE, NULL);
+ }
+
break;
case SYSTEM_EVENT_STA_START: {
@@ -664,9 +672,23 @@ void WiFiClass::handleSystemEvent(system_event_t* event)
case SYSTEM_EVENT_STA_GOT_IP:
memcpy(&_ipInfo, &event->event_info.got_ip.ip_info, sizeof(_ipInfo));
_status = WL_CONNECTED;
+
+ if (wifi_manager_event_group != NULL) {
+ xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT);
+ wifi_manager_send_message(EVENT_STA_GOT_IP, (void*)event->event_info.got_ip.ip_info.ip.addr );
+ }
+
break;
case SYSTEM_EVENT_STA_DISCONNECTED: {
+
+ if (wifi_manager_event_group != NULL) {
+ /* 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(EVENT_STA_DISCONNECTED, (void*)( (uint32_t)event->event_info.disconnected.reason) );
+ }
+
uint8_t reason = event->event_info.disconnected.reason;
_reasonCode = reason;
@@ -725,6 +747,10 @@ void WiFiClass::handleSystemEvent(system_event_t* event)
_status = WL_AP_LISTENING;
xEventGroupSetBits(_eventGroup, BIT1);
+ if (wifi_manager_event_group != NULL) {
+ xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STARTED_BIT);
+ }
+
break;
}
@@ -737,6 +763,10 @@ void WiFiClass::handleSystemEvent(system_event_t* event)
case SYSTEM_EVENT_AP_STACONNECTED:
_status = WL_AP_CONNECTED;
+ if (wifi_manager_event_group != NULL) {
+ xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STA_CONNECTED_BIT);
+ }
+
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
@@ -744,6 +774,10 @@ void WiFiClass::handleSystemEvent(system_event_t* event)
esp_wifi_ap_get_sta_list(&staList);
+ if (wifi_manager_event_group != NULL) {
+ xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_AP_STA_CONNECTED_BIT);
+ }
+
if (staList.num == 0) {
_status = WL_AP_LISTENING;
}
diff --git a/components/wifi_manager/CMakeLists.txt b/components/wifi_manager/CMakeLists.txt
new file mode 100644
index 00000000..d5cbf20c
--- /dev/null
+++ b/components/wifi_manager/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(COMPONENT_SRCS
+ "main.c"
+ "dns_server.c"
+ "http_server.c"
+ "json.c"
+ "wifi_manager.c"
+)
+set(COMPONENT_ADD_INCLUDEDIRS "")
+set(COMPONENT_EMBED_FILES "style.css jquery.gz code.js index.html")
+
+register_component()
diff --git a/components/wifi_manager/Kconfig.projbuild b/components/wifi_manager/Kconfig.projbuild
new file mode 100644
index 00000000..b1dbe5fe
--- /dev/null
+++ b/components/wifi_manager/Kconfig.projbuild
@@ -0,0 +1,63 @@
+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_MAX_RETRY
+ int "Max Retry on failed connection"
+ default 2
+ help
+ Defines when a connection is lost/attempt to connect is made, how many retries should be made before giving up.
+
+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/components/wifi_manager/ap.json b/components/wifi_manager/ap.json
new file mode 100644
index 00000000..de61f86a
--- /dev/null
+++ b/components/wifi_manager/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/components/wifi_manager/code.js b/components/wifi_manager/code.js
new file mode 100644
index 00000000..c4f19bb5
--- /dev/null
+++ b/components/wifi_manager/code.js
@@ -0,0 +1,331 @@
+// First, checks if it isn't implemented yet.
+if (!String.prototype.format) {
+ String.prototype.format = function() {
+ var args = arguments;
+ return this.replace(/{(\d+)}/g, function(match, number) {
+ return typeof args[number] != 'undefined'
+ ? args[number]
+ : match
+ ;
+ });
+ };
+}
+
+var apList = null;
+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, 2800);
+}
+
+$(document).ready(function(){
+
+
+ $("#wifi-status").on("click", ".ape", function() {
+ $( "#wifi" ).slideUp( "fast", function() {});
+ $( "#connect-details" ).slideDown( "fast", function() {});
+ });
+
+ $("#manual_add").on("click", ".ape", function() {
+ selectedSSID = $(this).text();
+ $( "#ssid-pwd" ).text(selectedSSID);
+ $( "#wifi" ).slideUp( "fast", function() {});
+ $( "#connect_manual" ).slideDown( "fast", function() {});
+ $( "#connect" ).slideUp( "fast", function() {});
+
+ //update wait screen
+ $( "#loading" ).show();
+ $( "#connect-success" ).hide();
+ $( "#connect-fail" ).hide();
+ });
+
+ $("#wifi-list").on("click", ".ape", function() {
+ selectedSSID = $(this).text();
+ $( "#ssid-pwd" ).text(selectedSSID);
+ $( "#wifi" ).slideUp( "fast", function() {});
+ $( "#connect_manual" ).slideUp( "fast", function() {});
+ $( "#connect" ).slideDown( "fast", function() {});
+
+ //update wait screen
+ $( "#loading" ).show();
+ $( "#connect-success" ).hide();
+ $( "#connect-fail" ).hide();
+ });
+
+ $("#cancel").on("click", function() {
+ selectedSSID = "";
+ $( "#connect" ).slideUp( "fast", function() {});
+ $( "#connect_manual" ).slideUp( "fast", function() {});
+ $( "#wifi" ).slideDown( "fast", function() {});
+ });
+
+ $("#manual_cancel").on("click", function() {
+ selectedSSID = "";
+ $( "#connect" ).slideUp( "fast", function() {});
+ $( "#connect_manual" ).slideUp( "fast", function() {});
+ $( "#wifi" ).slideDown( "fast", function() {});
+ });
+
+ $("#join").on("click", function() {
+ performConnect();
+ });
+
+ $("#manual_join").on("click", function() {
+ performConnect($(this).data('connect'));
+ });
+
+ $("#ok-details").on("click", function() {
+ $( "#connect-details" ).slideUp( "fast", function() {});
+ $( "#wifi" ).slideDown( "fast", function() {});
+
+ });
+
+ $("#ok-credits").on("click", function() {
+ $( "#credits" ).slideUp( "fast", function() {});
+ $( "#app" ).slideDown( "fast", function() {});
+
+ });
+
+ $("#acredits").on("click", function(event) {
+ event.preventDefault();
+ $( "#app" ).slideUp( "fast", function() {});
+ $( "#credits" ).slideDown( "fast", function() {});
+ });
+
+ $("#ok-connect").on("click", function() {
+ $( "#connect-wait" ).slideUp( "fast", function() {});
+ $( "#wifi" ).slideDown( "fast", function() {});
+ });
+
+ $("#disconnect").on("click", function() {
+ $( "#connect-details-wrap" ).addClass('blur');
+ $( "#diag-disconnect" ).slideDown( "fast", function() {});
+ });
+
+ $("#no-disconnect").on("click", function() {
+ $( "#diag-disconnect" ).slideUp( "fast", function() {});
+ $( "#connect-details-wrap" ).removeClass('blur');
+ });
+
+ $("#yes-disconnect").on("click", function() {
+
+ stopCheckStatusInterval();
+ selectedSSID = "";
+
+ $( "#diag-disconnect" ).slideUp( "fast", function() {});
+ $( "#connect-details-wrap" ).removeClass('blur');
+
+ $.ajax({
+ url: '/connect.json',
+ dataType: 'json',
+ method: 'DELETE',
+ cache: false,
+ data: { 'timestamp': Date.now()}
+ });
+
+ startCheckStatusInterval();
+
+ $( "#connect-details" ).slideUp( "fast", function() {});
+ $( "#wifi" ).slideDown( "fast", function() {})
+ });
+
+
+
+
+
+
+
+
+ //first time the page loads: attempt get the connection status and start the wifi scan
+ refreshAP();
+ startCheckStatusInterval();
+ startRefreshAPInterval();
+
+
+
+
+});
+
+
+
+
+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=$('#manual_ssid').val();
+ pwd = $("#manual_pwd").val();
+ }else{
+ pwd = $("#pwd").val();
+ }
+ //reset connection
+ $( "#loading" ).show();
+ $( "#connect-success" ).hide();
+ $( "#connect-fail" ).hide();
+
+ $( "#ok-connect" ).prop("disabled",true);
+ $( "#ssid-wait" ).text(selectedSSID);
+ $( "#connect" ).slideUp( "fast", function() {});
+ $( "#connect_manual" ).slideUp( "fast", function() {});
+ $( "#connect-wait" ).slideDown( "fast", function() {});
+
+
+ $.ajax({
+ url: '/connect.json',
+ dataType: 'json',
+ method: 'POST',
+ cache: false,
+ headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd },
+ data: { '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';
+ }
+}
+
+
+function refreshAP(){
+ $.getJSON( "/ap.json", function( data ) {
+ if(data.length > 0){
+ //sort by signal strength
+ data.sort(function (a, b) {
+ var x = a["rssi"]; var y = b["rssi"];
+ return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+ });
+ apList = data;
+ refreshAPHTML(apList);
+
+ }
+ });
+}
+
+function refreshAPHTML(data){
+ var h = "";
+ data.forEach(function(e, idx, array) {
+ h += '
{3}
'.format(idx === array.length - 1?'':' brdb', rssiToIcon(e.rssi), e.auth==0?'':'pw',e.ssid);
+ h += "\n";
+ });
+
+ $( "#wifi-list" ).html(h)
+}
+
+
+
+
+function checkStatus(){
+ $.getJSON( "/status.json", function( data ) {
+ if(data.hasOwnProperty('ssid') && data['ssid'] != ""){
+ if(data["ssid"] === selectedSSID){
+ //that's a connection attempt
+ if(data["urc"] === 0){
+ //got connection
+ $("#connected-to span").text(data["ssid"]);
+ $("#connect-details h1").text(data["ssid"]);
+ $("#ip").text(data["ip"]);
+ $("#netmask").text(data["netmask"]);
+ $("#gw").text(data["gw"]);
+ $("#wifi-status").slideDown( "fast", function() {});
+
+ //unlock the wait screen if needed
+ $( "#ok-connect" ).prop("disabled",false);
+
+ //update wait screen
+ $( "#loading" ).hide();
+ $( "#connect-success" ).show();
+ $( "#connect-fail" ).hide();
+ }
+ else if(data["urc"] === 1){
+ //failed attempt
+ $("#connected-to span").text('');
+ $("#connect-details h1").text('');
+ $("#ip").text('0.0.0.0');
+ $("#netmask").text('0.0.0.0');
+ $("#gw").text('0.0.0.0');
+
+ //don't show any connection
+ $("#wifi-status").slideUp( "fast", function() {});
+
+ //unlock the wait screen
+ $( "#ok-connect" ).prop("disabled",false);
+
+ //update wait screen
+ $( "#loading" ).hide();
+ $( "#connect-fail" ).show();
+ $( "#connect-success" ).hide();
+ }
+ }
+ else if(data.hasOwnProperty('urc') && data['urc'] === 0){
+ //ESP32 is already connected to a wifi without having the user do anything
+ if( !($("#wifi-status").is(":visible")) ){
+ $("#connected-to span").text(data["ssid"]);
+ $("#connect-details h1").text(data["ssid"]);
+ $("#ip").text(data["ip"]);
+ $("#netmask").text(data["netmask"]);
+ $("#gw").text(data["gw"]);
+ $("#wifi-status").slideDown( "fast", function() {});
+ }
+ }
+ }
+ else if(data.hasOwnProperty('urc') && data['urc'] === 2){
+ //that's a manual disconnect
+ if($("#wifi-status").is(":visible")){
+ $("#wifi-status").slideUp( "fast", function() {});
+ }
+ }
+ })
+ .fail(function() {
+ //don't do anything, the server might be down while esp32 recalibrates radio
+ });
+
+
+}
diff --git a/components/wifi_manager/component.mk b/components/wifi_manager/component.mk
new file mode 100644
index 00000000..f8699218
--- /dev/null
+++ b/components/wifi_manager/component.mk
@@ -0,0 +1,6 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
+COMPONENT_EMBED_FILES := style.css jquery.gz code.js index.html
diff --git a/components/wifi_manager/compress.bat b/components/wifi_manager/compress.bat
new file mode 100644
index 00000000..bff6a512
--- /dev/null
+++ b/components/wifi_manager/compress.bat
@@ -0,0 +1,2 @@
+gzip index.html style.css jquery.js --best --keep --force
+pause
\ No newline at end of file
diff --git a/components/wifi_manager/connect b/components/wifi_manager/connect
new file mode 100644
index 00000000..8c7fe211
--- /dev/null
+++ b/components/wifi_manager/connect
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/components/wifi_manager/dns_server.c b/components/wifi_manager/dns_server.c
new file mode 100644
index 00000000..cce537ab
--- /dev/null
+++ b/components/wifi_manager/dns_server.c
@@ -0,0 +1,183 @@
+/*
+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 "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() {
+ 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 sa, 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);
+ }
+ memset(&sa, 0, sizeof(struct sockaddr_in));
+
+ /* Bind to port 53 (typical DNS Server port) */
+ tcpip_adapter_ip_info_t ip;
+ tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_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/components/wifi_manager/dns_server.h b/components/wifi_manager/dns_server.h
new file mode 100644
index 00000000..d1da1f8c
--- /dev/null
+++ b/components/wifi_manager/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/components/wifi_manager/http_server.c b/components/wifi_manager/http_server.c
new file mode 100644
index 00000000..c58175eb
--- /dev/null
+++ b/components/wifi_manager/http_server.c
@@ -0,0 +1,273 @@
+/*
+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 http_server.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 "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_event_loop.h"
+#include "nvs_flash.h"
+#include "esp_log.h"
+#include "driver/gpio.h"
+#include "mdns.h"
+#include "lwip/api.h"
+#include "lwip/err.h"
+#include "lwip/netdb.h"
+#include "lwip/opt.h"
+#include "lwip/memp.h"
+#include "lwip/ip.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/priv/api_msg.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/priv/tcpip_priv.h"
+
+#include "http_server.h"
+#include "wifi_manager.h"
+
+
+/* @brief tag used for ESP serial console messages */
+static const char TAG[] = "http_server";
+
+/* @brief task handle for the http server */
+static TaskHandle_t task_http_server = 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 jquery_gz_start[] asm("_binary_jquery_gz_start");
+extern const uint8_t jquery_gz_end[] asm("_binary_jquery_gz_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 http headers stored in ROM */
+const static char http_html_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/html\n\n";
+const static char http_css_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/css\nCache-Control: public, max-age=31536000\n\n";
+const static char http_js_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\n\n";
+const static char http_jquery_gz_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\nAccept-Ranges: bytes\nContent-Length: 29995\nContent-Encoding: gzip\n\n";
+const static char http_400_hdr[] = "HTTP/1.1 400 Bad Request\nContent-Length: 0\n\n";
+const static char http_404_hdr[] = "HTTP/1.1 404 Not Found\nContent-Length: 0\n\n";
+const static char http_503_hdr[] = "HTTP/1.1 503 Service Unavailable\nContent-Length: 0\n\n";
+const static char http_ok_json_no_cache_hdr[] = "HTTP/1.1 200 OK\nContent-type: application/json\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\n\n";
+const static char http_redirect_hdr_start[] = "HTTP/1.1 302 Found\nLocation: http://";
+const static char http_redirect_hdr_end[] = "/\n\n";
+
+
+
+void http_server_start(){
+ if(task_http_server == NULL){
+ xTaskCreate(&http_server, "http_server", 2048, NULL, WIFI_MANAGER_TASK_PRIORITY-1, &task_http_server);
+ }
+}
+
+void http_server(void *pvParameters) {
+
+ struct netconn *conn, *newconn;
+ err_t err;
+ conn = netconn_new(NETCONN_TCP);
+ netconn_bind(conn, IP_ADDR_ANY, 80);
+ netconn_listen(conn);
+ ESP_LOGI(TAG, "HTTP Server listening on 80/tcp");
+ do {
+ err = netconn_accept(conn, &newconn);
+ if (err == ERR_OK) {
+ http_server_netconn_serve(newconn);
+ netconn_delete(newconn);
+ }
+ taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. */
+ } while(err == ERR_OK);
+ netconn_close(conn);
+ netconn_delete(conn);
+
+ vTaskDelete( NULL );
+}
+
+
+char* http_server_get_header(char *request, char *header_name, int *len) {
+ *len = 0;
+ char *ret = NULL;
+ char *ptr = NULL;
+
+ ptr = strstr(request, header_name);
+ if (ptr) {
+ ret = ptr + strlen(header_name);
+ ptr = ret;
+ while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r') {
+ (*len)++;
+ ptr++;
+ }
+ return ret;
+ }
+ return NULL;
+}
+
+
+void http_server_netconn_serve(struct netconn *conn) {
+
+ struct netbuf *inbuf;
+ char *buf = NULL;
+ u16_t buflen;
+ err_t err;
+ const char new_line[2] = "\n";
+
+ err = netconn_recv(conn, &inbuf);
+ if (err == ERR_OK) {
+
+ netbuf_data(inbuf, (void**)&buf, &buflen);
+
+ /* extract the first line of the request */
+ char *save_ptr = buf;
+ char *line = strtok_r(save_ptr, new_line, &save_ptr);
+
+ if(line) {
+
+ /* captive portal functionality: redirect to access point IP for HOST that are not the access point IP OR the STA IP */
+ int lenH = 0;
+ char *host = http_server_get_header(save_ptr, "Host: ", &lenH);
+ /* determine if Host is from the STA IP address */
+ wifi_manager_lock_sta_ip_string(portMAX_DELAY);
+ bool access_from_sta_ip = lenH > 0?strstr(host, wifi_manager_get_sta_ip_string()):false;
+ wifi_manager_unlock_sta_ip_string();
+
+ if (lenH > 0 && !strstr(host, DEFAULT_AP_IP) && !access_from_sta_ip) {
+ netconn_write(conn, http_redirect_hdr_start, sizeof(http_redirect_hdr_start) - 1, NETCONN_NOCOPY);
+ netconn_write(conn, DEFAULT_AP_IP, sizeof(DEFAULT_AP_IP) - 1, NETCONN_NOCOPY);
+ netconn_write(conn, http_redirect_hdr_end, sizeof(http_redirect_hdr_end) - 1, NETCONN_NOCOPY);
+ }
+ else{
+ /* default page */
+ if(strstr(line, "GET / ")) {
+ netconn_write(conn, http_html_hdr, sizeof(http_html_hdr) - 1, NETCONN_NOCOPY);
+ netconn_write(conn, index_html_start, index_html_end - index_html_start, NETCONN_NOCOPY);
+ }
+ else if(strstr(line, "GET /jquery.js ")) {
+ netconn_write(conn, http_jquery_gz_hdr, sizeof(http_jquery_gz_hdr) - 1, NETCONN_NOCOPY);
+ netconn_write(conn, jquery_gz_start, jquery_gz_end - jquery_gz_start, NETCONN_NOCOPY);
+ }
+ else if(strstr(line, "GET /code.js ")) {
+ netconn_write(conn, http_js_hdr, sizeof(http_js_hdr) - 1, NETCONN_NOCOPY);
+ netconn_write(conn, code_js_start, code_js_end - code_js_start, NETCONN_NOCOPY);
+ }
+ else if(strstr(line, "GET /ap.json ")) {
+ /* if we can get the mutex, write the last version of the AP list */
+ if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
+ netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
+ char *buff = wifi_manager_get_ap_list_json();
+ netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+ wifi_manager_unlock_json_buffer();
+ }
+ else{
+ netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+ ESP_LOGD(TAG, "http_server_netconn_serve: GET /ap.json failed to obtain mutex");
+ }
+ /* request a wifi scan */
+ wifi_manager_scan_async();
+ }
+ else if(strstr(line, "GET /style.css ")) {
+ netconn_write(conn, http_css_hdr, sizeof(http_css_hdr) - 1, NETCONN_NOCOPY);
+ netconn_write(conn, style_css_start, style_css_end - style_css_start, NETCONN_NOCOPY);
+ }
+ else if(strstr(line, "GET /status.json ")){
+ if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
+ char *buff = wifi_manager_get_ip_info_json();
+ if(buff){
+ netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
+ netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+ wifi_manager_unlock_json_buffer();
+ }
+ else{
+ netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+ }
+ }
+ else{
+ netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+ ESP_LOGD(TAG, "http_server_netconn_serve: GET /status failed to obtain mutex");
+ }
+ }
+ else if(strstr(line, "DELETE /connect.json ")) {
+ ESP_LOGD(TAG, "http_server_netconn_serve: DELETE /connect.json");
+ /* request a disconnection from wifi and forget about it */
+ wifi_manager_disconnect_async();
+ netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
+ }
+ else if(strstr(line, "POST /connect.json ")) {
+ ESP_LOGD(TAG, "http_server_netconn_serve: POST /connect.json");
+
+ bool found = false;
+ int lenS = 0, lenP = 0;
+ char *ssid = NULL, *password = NULL;
+ ssid = http_server_get_header(save_ptr, "X-Custom-ssid: ", &lenS);
+ password = http_server_get_header(save_ptr, "X-Custom-pwd: ", &lenP);
+
+ if(ssid && lenS <= MAX_SSID_SIZE && password && lenP <= MAX_PASSWORD_SIZE){
+ wifi_config_t* config = wifi_manager_get_wifi_sta_config();
+ memset(config, 0x00, sizeof(wifi_config_t));
+ memcpy(config->sta.ssid, ssid, lenS);
+ memcpy(config->sta.password, password, lenP);
+ ESP_LOGD(TAG, "http_server_netconn_serve: wifi_manager_connect_async() call");
+ wifi_manager_connect_async();
+ netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
+ found = true;
+ }
+
+ if(!found){
+ /* bad request the authentification header is not complete/not the correct format */
+ netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
+ }
+
+ }
+ else{
+ netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
+ }
+ }
+ }
+ else{
+ netconn_write(conn, http_404_hdr, sizeof(http_404_hdr) - 1, NETCONN_NOCOPY);
+ }
+ }
+
+ /* free the buffer */
+ netbuf_delete(inbuf);
+}
diff --git a/components/wifi_manager/http_server.h b/components/wifi_manager/http_server.h
new file mode 100644
index 00000000..184618bb
--- /dev/null
+++ b/components/wifi_manager/http_server.h
@@ -0,0 +1,70 @@
+/*
+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 http_server.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_SERVER_H_INCLUDED
+#define HTTP_SERVER_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief RTOS task for the HTTP server. Do not start manually.
+ * @see void http_server_start()
+ */
+void http_server(void *pvParameters);
+
+/* @brief helper function that processes one HTTP request at a time */
+void http_server_netconn_serve(struct netconn *conn);
+
+/* @brief create the task for the http server */
+void http_server_start();
+
+/**
+ * @brief gets a char* pointer to the first occurence of header_name withing the complete http request request.
+ *
+ * For optimization purposes, no local copy is made. memcpy can then be used in coordination with len to extract the
+ * data.
+ *
+ * @param request the full HTTP raw request.
+ * @param header_name the header that is being searched.
+ * @param len the size of the header value if found.
+ * @return pointer to the beginning of the header value.
+ */
+char* http_server_get_header(char *request, char *header_name, int *len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/components/wifi_manager/include/wifi_manager.h b/components/wifi_manager/include/wifi_manager.h
new file mode 100644
index 00000000..a5eb209e
--- /dev/null
+++ b/components/wifi_manager/include/wifi_manager.h
@@ -0,0 +1 @@
+#include "../wifi_manager.h"
diff --git a/components/wifi_manager/index.html b/components/wifi_manager/index.html
new file mode 100644
index 00000000..b4a218d8
--- /dev/null
+++ b/components/wifi_manager/index.html
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+ esp32-wifi-manager
+
+
+