'.format(data["autoexec"]===1?"Active":"Inactive");
+ }
+ if(data.hasOwnProperty('list')) {
+ data["list"].forEach(function(e, idx, array) {
+ for (const [key, value] of Object.entries(e)) {
+ h+= ' '.format(key,value);
+ }
+ }
+
+ );
+ h += "\n";
+ $( "#command-list" ).html(h);
+ }
+
+ })
+ .fail(function() {
+ //don't do anything, the server might be down while esp32 recalibrates radio
+ });
+
+ RepeatCheckConfigInterval();
+
+}
diff --git a/components/wifi-manager/component.mk b/components/wifi-manager/component.mk
new file mode 100644
index 00000000..ce01ad6e
--- /dev/null
+++ b/components/wifi-manager/component.mk
@@ -0,0 +1,11 @@
+#
+# Component Makefile
+#
+# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
+# this will take the sources in the src/ directory, compile them and link them into
+# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
+# please read the SDK documents if you need to do this.
+#
+COMPONENT_EMBED_FILES := style.css jquery.gz code.js index.html
+CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG
+COMPONENT_ADD_INCLUDEDIRS := .
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..c336290d
--- /dev/null
+++ b/components/wifi-manager/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 "dns_server.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "wifi_manager.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..48075699
--- /dev/null
+++ b/components/wifi-manager/dns_server.h
@@ -0,0 +1,140 @@
+/*
+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_
+#include
+#include
+
+
+#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..38fc5147
--- /dev/null
+++ b/components/wifi-manager/http_server.c
@@ -0,0 +1,367 @@
+/*
+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 "http_server.h"
+#include "cmd_system.h"
+
+
+
+/* @brief tag used for ESP serial console messages */
+static const char TAG[] = "http_server";
+static const char json_start[] = "{ \"autoexec\" : %u, \"list\" : [";
+static const char json_end[] = "]}";
+static const char template[] = "{ '%s' : '%s' }";
+static const char array_separator[]=",";
+
+/* @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", 1024*3, 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);
+ }
+ else
+ {
+ ESP_LOGE(TAG,"Error accepting new connection. Terminating HTTP server");
+ }
+ 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);
+ ESP_LOGD(TAG,"Processing line %s",line);
+
+ 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) {
+ ESP_LOGI(TAG,"Redirecting to default AP IP Address : %s", DEFAULT_AP_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 */
+ ESP_LOGI(TAG,"Processing ap.json request");
+ 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_LOGE(TAG, "http_server_netconn_serve: GET /ap.json failed to obtain mutex");
+ }
+ /* request a wifi scan */
+ ESP_LOGI(TAG,"Starting 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 ")){
+ ESP_LOGI(TAG,"Serving 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_LOGE(TAG, "http_server_netconn_serve: GET /status failed to obtain mutex");
+ }
+ }
+ else if(strstr(line, "GET /config.json ")){
+ ESP_LOGI(TAG,"Serving config.json");
+ char autoexec_name[21]={0};
+ char * autoexec_value=NULL;
+ char * autoexec_flag_s=NULL;
+ uint8_t autoexec_flag=0;
+ int buflen=MAX_COMMAND_LINE_SIZE+strlen(template)+1;
+ char * buff = malloc(buflen);
+ if(!buff)
+ {
+ ESP_LOGE(TAG,"Unable to allocate buffer for config.json!");
+ netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+ }
+ else
+ {
+ int i=1;
+ size_t l = 0;
+ netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
+
+ autoexec_flag = wifi_manager_get_flag();
+ snprintf(buff,buflen-1, json_start, autoexec_flag);
+ netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+ do {
+ snprintf(autoexec_name,sizeof(autoexec_name)-1,"autoexec%u",i);
+ ESP_LOGD(TAG,"Getting command name %s", autoexec_name);
+ autoexec_value= wifi_manager_alloc_get_config(autoexec_name, &l);
+ if(autoexec_value!=NULL ){
+ if(i>1)
+ {
+ netconn_write(conn, array_separator, strlen(array_separator), NETCONN_NOCOPY);
+ ESP_LOGD(TAG,"%s", array_separator);
+ }
+ ESP_LOGI(TAG,"found command %s = %s", autoexec_name, autoexec_value);
+ snprintf(buff,buflen-1,template, autoexec_name,autoexec_value);
+ netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+ ESP_LOGD(TAG,"%s", buff);
+ ESP_LOGD(TAG,"Freeing memory for command %s name", autoexec_name);
+ free(autoexec_value);
+ }
+ else {
+ ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
+ break;
+ }
+ i++;
+ } while(1);
+ free(buff);
+ netconn_write(conn, json_end, strlen(json_end), NETCONN_NOCOPY);
+ ESP_LOGD(TAG,"%s", json_end);
+ }
+ }
+ else if(strstr(line, "POST /factory.json ")){
+ guided_factory();
+ }
+ else if(strstr(line, "POST /config.json ")){
+ ESP_LOGI(TAG,"Serving POST config.json");
+
+ if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
+ int i=1;
+ int lenS = 0, lenA=0;
+ char autoexec_name[21]={0};
+ char * autoexec_value=NULL;
+ char * autoexec_flag_s=NULL;
+ uint8_t autoexec_flag=0;
+ autoexec_flag_s = http_server_get_header(save_ptr, "X-Custom-autoexec: ", &lenA);
+ if(autoexec_flag_s!=NULL && lenA > 0)
+ {
+ autoexec_flag = atoi(autoexec_flag_s);
+ wifi_manager_save_autoexec_flag(autoexec_flag);
+ }
+
+ do {
+ snprintf(autoexec_name,sizeof(autoexec_name)-1,"X-Custom-autoexec%u:",i++);
+ ESP_LOGD(TAG,"Looking for command name %s", autoexec_name);
+ autoexec_value = http_server_get_header(save_ptr, autoexec_name, &lenS);
+
+ if(autoexec_value ){
+ if(lenS < MAX_COMMAND_LINE_SIZE ){
+ ESP_LOGD(TAG, "http_server_netconn_serve: config.json/ call, with %s: %s", autoexec_name, autoexec_value);
+ wifi_manager_save_autoexec_config(autoexec_value,autoexec_name,lenS);
+ }
+ else
+ {
+ ESP_LOGE(TAG,"command line length is too long : %s = %s", autoexec_name, autoexec_value);
+ }
+ }
+ else {
+ ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
+ break;
+ }
+ } while(1);
+
+ netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
+
+ }
+ else{
+ netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+ ESP_LOGE(TAG, "http_server_netconn_serve: GET /status failed to obtain mutex");
+ }
+ }
+
+ else if(strstr(line, "DELETE /connect.json ")) {
+ ESP_LOGI(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_LOGI(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, with ssid: %s, password: %s", ssid, password);
+ 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);
+ ESP_LOGE(TAG, "bad request the authentification header is not complete/not the correct format");
+ }
+
+ }
+ else{
+ netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
+ ESP_LOGE(TAG, "bad request");
+ }
+ }
+ }
+ else{
+ ESP_LOGE(TAG, "URL Not found. Sending 404.");
+ 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..3b4c2419
--- /dev/null
+++ b/components/wifi-manager/http_server.h
@@ -0,0 +1,95 @@
+/*
+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
+#include
+#include
+#include
+#include
+#include "wifi_manager.h"
+#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"
+
+#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/index.html b/components/wifi-manager/index.html
new file mode 100644
index 00000000..7c976d6f
--- /dev/null
+++ b/components/wifi-manager/index.html
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+
+
+
+ esp32-wifi-manager
+
+
+
+