diff --git a/.gitmodules b/.gitmodules index 17f8003..6790fd5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,9 @@ [submodule "extras/crc_generic/crc_lib"] path = extras/crc_generic/crc_lib url = https://github.com/Zaltora/crc_generic_lib.git +[submodule "extras/libesphttpd/libesphttpd"] + path = extras/libesphttpd/libesphttpd + url = https://github.com/nochkin/libesphttpd +[submodule "extras/libesphttpd/libesphttpd/lib/heatshrink"] + path = extras/libesphttpd/libesphttpd/lib/heatshrink + url = https://github.com/atomicobject/heatshrink diff --git a/.travis.yml b/.travis.yml index 39674fe..4d2597d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,7 @@ addons: - git - help2man - vim-common + - zlib1g-dev before_install: - pip install --user pyserial diff --git a/examples/esphttpd/FreeRTOSConfig.h b/examples/esphttpd/FreeRTOSConfig.h new file mode 100644 index 0000000..d6d9c5e --- /dev/null +++ b/examples/esphttpd/FreeRTOSConfig.h @@ -0,0 +1,11 @@ +/* FreeRTOSConfig overrides. + + This is intended as an example of overriding some of the default FreeRTOSConfig settings, + which are otherwise found in FreeRTOS/Source/include/FreeRTOSConfig.h +*/ + +#define configUSE_RECURSIVE_MUTEXES 1 + +/* Use the defaults for everything else */ +#include_next + diff --git a/examples/esphttpd/Makefile b/examples/esphttpd/Makefile new file mode 100644 index 0000000..2362443 --- /dev/null +++ b/examples/esphttpd/Makefile @@ -0,0 +1,16 @@ +PROGRAM = esphttpd +EXTRA_COMPONENTS = extras/dhcpserver extras/rboot-ota extras/libesphttpd + +ESP_IP ?= 192.168.4.1 + +#Tag for OTA images. 0-27 characters. Change to eg your projects title. +LIBESPHTTPD_OTA_TAGNAME ?= generic + +LIBESPHTTPD_MAX_CONNECTIONS ?= 8 +LIBESPHTTPD_STACKSIZE ?= 2048 + +PROGRAM_CFLAGS += -DFREERTOS -DLIBESPHTTPD_OTA_TAGNAME="\"$(LIBESPHTTPD_OTA_TAGNAME)\"" -DFLASH_SIZE=$(FLASH_SIZE) +EXTRA_CFLAGS += -DMEMP_NUM_NETCONN=$(LIBESPHTTPD_MAX_CONNECTIONS) + +include ../../common.mk + diff --git a/examples/esphttpd/cgi-test.c b/examples/esphttpd/cgi-test.c new file mode 100644 index 0000000..7a16aed --- /dev/null +++ b/examples/esphttpd/cgi-test.c @@ -0,0 +1,90 @@ +/* +Cgi routines as used by the tests in the html/test subdirectory. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + + +#include +#include +#include + +#include + +#include "cgi-test.h" + +typedef struct { + int len; + int sendPos; +} TestbedState; + + +int ICACHE_FLASH_ATTR cgiTestbed(HttpdConnData *connData) { + char buff[1024]; + int first=0; + int l, x; + TestbedState *state=(TestbedState*)connData->cgiData; + + if (connData->conn==NULL) { + //Connection aborted. Clean up. + if (state) free(state); + return HTTPD_CGI_DONE; + } + + if (state==NULL) { + //First call + state=malloc(sizeof(TestbedState)); + memset(state, 0, sizeof(state)); + connData->cgiData=state; + first=1; + } + + if (connData->requestType==HTTPD_METHOD_GET) { + if (first) { + httpdStartResponse(connData, 200); + httpdHeader(connData, "content-type", "application/data"); + httpdEndHeaders(connData); + l=httpdFindArg(connData->getArgs, "len", buff, sizeof(buff)); + state->len=1024; + if (l!=-1) state->len=atoi(buff); + state->sendPos=0; + return HTTPD_CGI_MORE; + } else { + l=sizeof(buff); + if (l>(state->len-state->sendPos)) l=(state->len-state->sendPos); + //Fill with semi-random data + for (x=0; xsendPos>>10))&0x1F)+'0'; + httpdSend(connData, buff, l); + state->sendPos+=l; + printf("Test: Uploaded %d/%d bytes\n", state->sendPos, state->len); + if (state->len<=state->sendPos) { + if (state) free(state); + return HTTPD_CGI_DONE; + } else { + return HTTPD_CGI_MORE; + } + } + } + if (connData->requestType==HTTPD_METHOD_POST) { + if (connData->post->len!=connData->post->received) { + //Still receiving data. Ignore this. + printf("Test: got %d/%d bytes\n", connData->post->received, connData->post->len); + return HTTPD_CGI_MORE; + } else { + httpdStartResponse(connData, 200); + httpdHeader(connData, "content-type", "text/plain"); + httpdEndHeaders(connData); + l=sprintf(buff, "%d", connData->post->received); + httpdSend(connData, buff, l); + return HTTPD_CGI_DONE; + } + } + return HTTPD_CGI_DONE; +} diff --git a/examples/esphttpd/cgi-test.h b/examples/esphttpd/cgi-test.h new file mode 100644 index 0000000..a2c752e --- /dev/null +++ b/examples/esphttpd/cgi-test.h @@ -0,0 +1,8 @@ +#ifndef CGI_TEST_H +#define CGI_TEST_H + +#include + +int cgiTestbed(HttpdConnData *connData); + +#endif diff --git a/examples/esphttpd/cgi.c b/examples/esphttpd/cgi.c new file mode 100644 index 0000000..5b83ca2 --- /dev/null +++ b/examples/esphttpd/cgi.c @@ -0,0 +1,81 @@ +/* +Some random cgi routines. Used in the LED example and the page that returns the entire +flash as a binary. Also handles the hit counter on the main page. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + + +#include +#include +#include + +#include + +#include "cgi.h" +#include "io.h" + + +//cause I can't be bothered to write an ioGetLed() +static char currLedState=0; + +//Cgi that turns the LED on or off according to the 'led' param in the POST data +int ICACHE_FLASH_ATTR cgiLed(HttpdConnData *connData) { + int len; + char buff[1024]; + + if (connData->conn==NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + len=httpdFindArg(connData->post->buff, "led", buff, sizeof(buff)); + if (len!=0) { + currLedState=atoi(buff); + ioLed(currLedState); + } + + httpdRedirect(connData, "led.tpl"); + return HTTPD_CGI_DONE; +} + + + +//Template code for the led page. +int ICACHE_FLASH_ATTR tplLed(HttpdConnData *connData, char *token, void **arg) { + char buff[128]; + if (token==NULL) return HTTPD_CGI_DONE; + + strcpy(buff, "Unknown"); + if (strcmp(token, "ledstate")==0) { + if (currLedState) { + strcpy(buff, "on"); + } else { + strcpy(buff, "off"); + } + } + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} + +static int hitCounter=0; + +//Template code for the counter on the index page. +int ICACHE_FLASH_ATTR tplCounter(HttpdConnData *connData, char *token, void **arg) { + char buff[128]; + if (token==NULL) return HTTPD_CGI_DONE; + + if (strcmp(token, "counter")==0) { + hitCounter++; + sprintf(buff, "%d", hitCounter); + } + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} diff --git a/examples/esphttpd/cgi.h b/examples/esphttpd/cgi.h new file mode 100644 index 0000000..5cb7296 --- /dev/null +++ b/examples/esphttpd/cgi.h @@ -0,0 +1,10 @@ +#ifndef CGI_H +#define CGI_H + +#include + +int cgiLed(HttpdConnData *connData); +int tplLed(HttpdConnData *connData, char *token, void **arg); +int tplCounter(HttpdConnData *connData, char *token, void **arg); + +#endif diff --git a/examples/esphttpd/html/cats/cross-eyed-cat.jpg b/examples/esphttpd/html/cats/cross-eyed-cat.jpg new file mode 100644 index 0000000..e166e87 Binary files /dev/null and b/examples/esphttpd/html/cats/cross-eyed-cat.jpg differ diff --git a/examples/esphttpd/html/cats/junge-katze-iv.jpg b/examples/esphttpd/html/cats/junge-katze-iv.jpg new file mode 100644 index 0000000..c3cf70b Binary files /dev/null and b/examples/esphttpd/html/cats/junge-katze-iv.jpg differ diff --git a/examples/esphttpd/html/cats/kitten-loves-toy.jpg b/examples/esphttpd/html/cats/kitten-loves-toy.jpg new file mode 100644 index 0000000..569ff56 Binary files /dev/null and b/examples/esphttpd/html/cats/kitten-loves-toy.jpg differ diff --git a/examples/esphttpd/html/flash/140medley.min.js b/examples/esphttpd/html/flash/140medley.min.js new file mode 100644 index 0000000..d1495d1 --- /dev/null +++ b/examples/esphttpd/html/flash/140medley.min.js @@ -0,0 +1,2 @@ +var t=function(a,b){return function(c,d){return a.replace(/#{([^}]*)}/g,function(a,f){return Function("x","with(x)return "+f).call(c,d||b||{})})}},s=function(a,b){return b?{get:function(c){return a[c]&&b.parse(a[c])},set:function(c,d){a[c]=b.stringify(d)}}:{}}(this.localStorage||{},JSON),p=function(a,b,c,d){c=c||document;d=c[b="on"+b];a=c[b]=function(e){d=d&&d(e=e||c.event);return(a=a&&b(e))?b:d};c=this},m=function(a,b,c){b=document;c=b.createElement("p");c.innerHTML=a;for(a=b.createDocumentFragment();b= +c.firstChild;)a.appendChild(b);return a},$=function(a,b){a=a.match(/^(\W)?(.*)/);return(b||document)["getElement"+(a[1]?a[1]=="#"?"ById":"sByClassName":"sByTagName")](a[2])},j=function(a){for(a=0;a<4;a++)try{return a?new ActiveXObject([,"Msxml2","Msxml3","Microsoft"][a]+".XMLHTTP"):new XMLHttpRequest}catch(b){}}; diff --git a/examples/esphttpd/html/flash/index.html b/examples/esphttpd/html/flash/index.html new file mode 100644 index 0000000..d336a6c --- /dev/null +++ b/examples/esphttpd/html/flash/index.html @@ -0,0 +1,75 @@ + +Upgrade firmware + + + + + +
+

Update firmware

+
Loading...
+ + +
+ \ No newline at end of file diff --git a/examples/esphttpd/html/flash/style.css b/examples/esphttpd/html/flash/style.css new file mode 100644 index 0000000..7678ec8 --- /dev/null +++ b/examples/esphttpd/html/flash/style.css @@ -0,0 +1,34 @@ + +body { + background-color: #404040; + font-family: sans-serif; +} + +#main { + background-color: #d0d0FF; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + border: 2px solid #000000; + width: 800px; + margin: 0 auto; + padding: 20px +} + +#progressbar { + margin: 10px; + padding: 0; + border: 1px solid #000000; + height: 20px; + width: 200px; + background-color: #808080; +} + +#progressbarinner { + width: 10px; + height: 20px; + border: none; + background-color: #00ff00; +} + + diff --git a/examples/esphttpd/html/index.tpl b/examples/esphttpd/html/index.tpl new file mode 100644 index 0000000..737f301 --- /dev/null +++ b/examples/esphttpd/html/index.tpl @@ -0,0 +1,26 @@ + +Esp8266 web server + + + +
+

It Works

+

+If you see this, it means the tiny li'l website in your ESP8266 does actually work. Fyi, this page has +been loaded %counter% times. +

    +
  • If you haven't connected this device to your WLAN network now, you can do so.
  • +
  • You can also control the LED.
  • +
  • Esphttpd now also supports websockets.
  • +
  • Test esphttpd using the built-in test suite
  • +
  • And because I can, here's a link to my website
+ +

+ +

And because we're on the Internets now, here are the required pictures of cats:
+
+
+
+

+
+ diff --git a/examples/esphttpd/html/led.tpl b/examples/esphttpd/html/led.tpl new file mode 100644 index 0000000..7a9cf89 --- /dev/null +++ b/examples/esphttpd/html/led.tpl @@ -0,0 +1,15 @@ +Test + + + +
+

The LED

+

+If there's a LED connected to GPIO2, it's now %ledstate%. You can change that using the buttons below. +

+
+ + +
+
+ diff --git a/examples/esphttpd/html/style.css b/examples/esphttpd/html/style.css new file mode 100644 index 0000000..2a2f758 --- /dev/null +++ b/examples/esphttpd/html/style.css @@ -0,0 +1,17 @@ + +body { + background-color: #404040; + font-family: sans-serif; +} + +#main { + background-color: #d0d0FF; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + border: 2px solid #000000; + width: 800px; + margin: 0 auto; + padding: 20px +} + diff --git a/examples/esphttpd/html/test/index.html b/examples/esphttpd/html/test/index.html new file mode 100644 index 0000000..01f4121 --- /dev/null +++ b/examples/esphttpd/html/test/index.html @@ -0,0 +1,9 @@ +Webserver test + + + + +
+
Initializing test...
+
+ \ No newline at end of file diff --git a/examples/esphttpd/html/test/test.js b/examples/esphttpd/html/test/test.js new file mode 100644 index 0000000..03c0cd4 --- /dev/null +++ b/examples/esphttpd/html/test/test.js @@ -0,0 +1,205 @@ + +/* +Code to test the webserver. This depends on: +- The cat images being available, for concurrent espfs testing +- the test.cgi script available, for generic data mangling tests + + +This test does a max of 4 requests in parallel. The nonos SDK supports a max of +5 connections; the default libesphttpd setting is 4 sockets at a time. Unfortunately, +the nonos sdk just closes all sockets opened after the available sockets are opened, +instead of queueing them until a socket frees up. +*/ + + +function log(line) { + $("#log").insertAdjacentHTML('beforeend', line+'
'); +} + + +//Load an image multiple times in parallel +function testParLdImg(url, ct, doneFn) { + var im=[]; + var state={"loaded":0, "count":ct, "doneFn": doneFn, "error":false}; + for (var x=0; xthis.ts+2000) { + log("..."+Math.floor(e.loaded*100/this.len).toString()+"%"); + this.ts=Date.now(); + } + }.bind(state); + } + xhr.send(); +} + + +function testUploadCgi(len, doneFn) { + var xhr=j(); + var state={"len":len, "doneFn":doneFn, "ts": Date.now()}; + var data=""; + for (var x=0; x=200 && xhr.status<300) { + var ulen=parseInt(xhr.responseText); + if (ulen==this.len) { + log("Uploaded "+this.len+" bytes successfully."); + this.doneFn(true); + } else { + log("Webserver received "+ulen+" bytes successfully, but sent "+this.len+"!"); + this.doneFn(false); + } + } else if (xhr.readyState==4) { + log("Failed! Error "+xhr.status); + this.doneFn(false); + } + }.bind(state); + //If the webbrowser enables it, show progress. + if (typeof xhr.upload.onprogress != 'undefined') { + xhr.upload.onprogress=function(e) { + if (Date.now()>this.ts+2000) { + log("..."+Math.floor(e.loaded*100/e.total).toString()+"%"); + this.ts=Date.now(); + } + }.bind(state); + } + //Upload the file + xhr.send(data); +} + +function hammerNext(state, xhr) { + if (state.done==state.count) { + state.doneFn(!state.error); + } + if (state.started==state.count) return; + xhr.open("GET", "test.cgi?len="+state.len+"&nocache="+Math.floor(Math.random()*100000).toString()); + xhr.onreadystatechange=function(xhr) { + if (xhr.readyState==4 && xhr.status>=200 && xhr.status<300) { + if (xhr.response.length==this.len) { + state.done++; + hammerNext(this, xhr); + } else { + log("Downloaded "+xhr.response.length+" bytes successfully, but needed "+this.len+"!"); + state.done++; + hammerNext(this, xhr); + } + } else if (xhr.readyState==4) { + log("Failed! Error "+xhr.status); + state.done++; + hammerNext(this, xhr); + } + }.bind(state, xhr); + //If the webbrowser enables it, show progress. + if (typeof xhr.onprogress != 'undefined') { + xhr.onprogress=function(e) { + if (Date.now()>this.ts+2000) { + log("..."+state.done+"/"+state.count); + this.ts=Date.now(); + } + }.bind(state); + } + state.started++; + xhr.send(); +} + +function testHammer(count, par, len, doneFn) { + var state={"count":count, "started":0, "done":0, "par":par, "len":len, "doneFn":doneFn, "ts": Date.now(), "error":false}; + var xhr=[]; + for (var i=0; iSuccess!"); + successCnt++; + } else { + log("Test failed!"); + } + } + tstState++; + if (tstState==1) { + log("Testing parallel load of espfs files..."); + testParLdImg("../cats/kitten-loves-toy.jpg", 3, nextTest); + } else if (tstState==2) { + log("Testing GET request of 32K..."); + testDownloadCgi(32*1024, nextTest); + } else if (tstState==3) { + log("Testing GET request of 128K..."); + testDownloadCgi(128*1024, nextTest); + } else if (tstState==4) { + log("Testing GET request of 512K..."); + testDownloadCgi(512*1024, nextTest); + } else if (tstState==5) { + log("Testing POST request of 512 bytes..."); + testUploadCgi(512, nextTest); + } else if (tstState==6) { + log("Testing POST request of 16K bytes..."); + testUploadCgi(16*1024, nextTest); + } else if (tstState==7) { + log("Testing POST request of 512K bytes..."); + testUploadCgi(512*1024, nextTest); + } else if (tstState==8) { + log("Hammering webserver with 500 requests of size 512..."); + testHammer(500, 3, 512, nextTest); + } else if (tstState==9) { + log("Hammering webserver with 500 requests of size 2048..."); + testHammer(500, 3, 2048, nextTest); + } else { + log("Tests done! "+successCnt+" out of "+(tstState-1)+" tests were successful."); + } +} + + + +window.onload=function(e) { + log("Starting tests."); + nextTest(false); +} + + + diff --git a/examples/esphttpd/html/websocket/index.html b/examples/esphttpd/html/websocket/index.html new file mode 100644 index 0000000..d7423ec --- /dev/null +++ b/examples/esphttpd/html/websocket/index.html @@ -0,0 +1,69 @@ + + + + +WebSocket Test + + + +

WebSocket Test

+ +
diff --git a/examples/esphttpd/html/wifi/140medley.min.js b/examples/esphttpd/html/wifi/140medley.min.js new file mode 100644 index 0000000..d1495d1 --- /dev/null +++ b/examples/esphttpd/html/wifi/140medley.min.js @@ -0,0 +1,2 @@ +var t=function(a,b){return function(c,d){return a.replace(/#{([^}]*)}/g,function(a,f){return Function("x","with(x)return "+f).call(c,d||b||{})})}},s=function(a,b){return b?{get:function(c){return a[c]&&b.parse(a[c])},set:function(c,d){a[c]=b.stringify(d)}}:{}}(this.localStorage||{},JSON),p=function(a,b,c,d){c=c||document;d=c[b="on"+b];a=c[b]=function(e){d=d&&d(e=e||c.event);return(a=a&&b(e))?b:d};c=this},m=function(a,b,c){b=document;c=b.createElement("p");c.innerHTML=a;for(a=b.createDocumentFragment();b= +c.firstChild;)a.appendChild(b);return a},$=function(a,b){a=a.match(/^(\W)?(.*)/);return(b||document)["getElement"+(a[1]?a[1]=="#"?"ById":"sByClassName":"sByTagName")](a[2])},j=function(a){for(a=0;a<4;a++)try{return a?new ActiveXObject([,"Msxml2","Msxml3","Microsoft"][a]+".XMLHTTP"):new XMLHttpRequest}catch(b){}}; diff --git a/examples/esphttpd/html/wifi/connecting.html b/examples/esphttpd/html/wifi/connecting.html new file mode 100644 index 0000000..12e3a83 --- /dev/null +++ b/examples/esphttpd/html/wifi/connecting.html @@ -0,0 +1,43 @@ +Connecting... + + + + + +
+

Connecting to AP...

+

Status:
+

...
+

+
+ + diff --git a/examples/esphttpd/html/wifi/icons.png b/examples/esphttpd/html/wifi/icons.png new file mode 100644 index 0000000..03109e1 Binary files /dev/null and b/examples/esphttpd/html/wifi/icons.png differ diff --git a/examples/esphttpd/html/wifi/style.css b/examples/esphttpd/html/wifi/style.css new file mode 100644 index 0000000..abcfb10 --- /dev/null +++ b/examples/esphttpd/html/wifi/style.css @@ -0,0 +1,24 @@ + +body { + background-color: #404040; + font-family: sans-serif; +} + +#main { + background-color: #d0d0FF; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + border: 2px solid #000000; + width: 800px; + margin: 0 auto; + padding: 20px +} + +.icon { + background-image: url("icons.png"); + background-color: transparent; + width: 32px; + height: 32px; + display: inline-block; +} \ No newline at end of file diff --git a/examples/esphttpd/html/wifi/wifi.tpl b/examples/esphttpd/html/wifi/wifi.tpl new file mode 100644 index 0000000..e513025 --- /dev/null +++ b/examples/esphttpd/html/wifi/wifi.tpl @@ -0,0 +1,94 @@ +WiFi connection + + + + + +
+

+Current WiFi mode: %WiFiMode% +

+

+Note: %WiFiapwarn% +

+
+

+To connect to a WiFi network, please select one of the detected networks...
+

Scanning...
+
+WiFi password, if applicable:
+
+ +

+
+ + diff --git a/examples/esphttpd/io.c b/examples/esphttpd/io.c new file mode 100644 index 0000000..c104b9c --- /dev/null +++ b/examples/esphttpd/io.c @@ -0,0 +1,72 @@ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + + +#include +//#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define LEDGPIO 2 +#define BTNGPIO 0 + +#ifndef ESP32 +static ETSTimer resetBtntimer; +#endif + +void ioLed(int ena) { +#ifndef ESP32 + //gpio_output_set is overkill. ToDo: use better mactos + if (ena) { + sdk_gpio_output_set((1<=6) { //3 sec pressed + sdk_wifi_station_disconnect(); + sdk_wifi_set_opmode(STATIONAP_MODE); //reset to AP+STA mode + printf("Reset to AP mode. Restarting system...\n"); + sdk_system_restart(); + } + resetCnt=0; + } +} +#endif + + +void ioInit() { +#ifndef ESP32 + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0); + sdk_gpio_output_set(0, 0, (1< wrote this file. As long as you retain + * this notice you can do whatever you want with this stuff. If we meet some day, + * and you think this stuff is worth it, you can buy me a beer in return. + * ---------------------------------------------------------------------------- + */ + +/* +This is example code for the esphttpd library. It's a small-ish demo showing off +the server, including WiFi connection management capabilities, some IO and +some pictures of cats. +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "io.h" +#include "cgi.h" +#include "cgi-test.h" + +#define AP_SSID "esp-open-rtos AP" +#define AP_PSK "esp-open-rtos" + +//Function that tells the authentication system what users/passwords live on the system. +//This is disabled in the default build; if you want to try it, enable the authBasic line in +//the builtInUrls below. +int myPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen) { + if (no==0) { + strcpy(user, "admin"); + strcpy(pass, "s3cr3t"); + return 1; +//Add more users this way. Check against incrementing no for each user added. +// } else if (no==1) { +// strcpy(user, "user1"); +// strcpy(pass, "something"); +// return 1; + } + return 0; +} + + +static ETSTimer websockTimer; + +//Broadcast the uptime in seconds every second over connected websockets +static void websocketBcast(void *arg) { + static int ctr=0; + char buff[128]; + while(1) { + ctr++; + sprintf(buff, "Up for %d minutes %d seconds!\n", ctr/60, ctr%60); + cgiWebsockBroadcast("/websocket/ws.cgi", buff, strlen(buff), WEBSOCK_FLAG_NONE); + vTaskDelay(1000/portTICK_PERIOD_MS); + } +} + +//On reception of a message, send "You sent: " plus whatever the other side sent +static void myWebsocketRecv(Websock *ws, char *data, int len, int flags) { + int i; + char buff[128]; + sprintf(buff, "You sent: "); + for (i=0; irecvCb=myWebsocketRecv; + cgiWebsocketSend(ws, "Hi, Websocket!", 14, WEBSOCK_FLAG_NONE); +} + +//On reception of a message, echo it back verbatim +void myEchoWebsocketRecv(Websock *ws, char *data, int len, int flags) { + printf("EchoWs: echo, len=%d\n", len); + cgiWebsocketSend(ws, data, len, flags); +} + +//Echo websocket connected. Install reception handler. +void myEchoWebsocketConnect(Websock *ws) { + printf("EchoWs: connect\n"); + ws->recvCb=myEchoWebsocketRecv; +} + +CgiUploadFlashDef uploadParams={ + .type=CGIFLASH_TYPE_FW, + .fw1Pos=0x2000, + .fw2Pos=((FLASH_SIZE*1024*1024)/2)+0x2000, + .fwSize=((FLASH_SIZE*1024*1024)/2)-0x2000, + .tagName=LIBESPHTTPD_OTA_TAGNAME +}; + + +/* +This is the main url->function dispatching data struct. +In short, it's a struct with various URLs plus their handlers. The handlers can +be 'standard' CGI functions you wrote, or 'special' CGIs requiring an argument. +They can also be auth-functions. An asterisk will match any url starting with +everything before the asterisks; "*" matches everything. The list will be +handled top-down, so make sure to put more specific rules above the more +general ones. Authorization things (like authBasic) act as a 'barrier' and +should be placed above the URLs they protect. +*/ +HttpdBuiltInUrl builtInUrls[]={ + {"*", cgiRedirectApClientToHostname, "esp8266.nonet"}, + {"/", cgiRedirect, "/index.tpl"}, + {"/led.tpl", cgiEspFsTemplate, tplLed}, + {"/index.tpl", cgiEspFsTemplate, tplCounter}, + {"/led.cgi", cgiLed, NULL}, +#ifndef ESP32 + {"/flash/", cgiRedirect, "/flash/index.html"}, + {"/flash/next", cgiGetFirmwareNext, &uploadParams}, + {"/flash/upload", cgiUploadFirmware, &uploadParams}, + {"/flash/reboot", cgiRebootFirmware, NULL}, +#endif + //Routines to make the /wifi URL and everything beneath it work. + +//Enable the line below to protect the WiFi configuration with an username/password combo. +// {"/wifi/*", authBasic, myPassFn}, + + {"/wifi", cgiRedirect, "/wifi/wifi.tpl"}, + {"/wifi/", cgiRedirect, "/wifi/wifi.tpl"}, + {"/wifi/wifiscan.cgi", cgiWiFiScan, NULL}, + {"/wifi/wifi.tpl", cgiEspFsTemplate, tplWlan}, + {"/wifi/connect.cgi", cgiWiFiConnect, NULL}, + {"/wifi/connstatus.cgi", cgiWiFiConnStatus, NULL}, + {"/wifi/setmode.cgi", cgiWiFiSetMode, NULL}, + + {"/websocket/ws.cgi", cgiWebsocket, myWebsocketConnect}, + {"/websocket/echo.cgi", cgiWebsocket, myEchoWebsocketConnect}, + + {"/test", cgiRedirect, "/test/index.html"}, + {"/test/", cgiRedirect, "/test/index.html"}, + {"/test/test.cgi", cgiTestbed, NULL}, + + {"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem + {NULL, NULL, NULL} +}; + +void wifiInit() { + struct ip_info ap_ip; + uint8_t sdk_wifi_get_opmode(); + switch(sdk_wifi_get_opmode()) { + case STATIONAP_MODE: + case SOFTAP_MODE: + IP4_ADDR(&ap_ip.ip, 172, 16, 0, 1); + IP4_ADDR(&ap_ip.gw, 0, 0, 0, 0); + IP4_ADDR(&ap_ip.netmask, 255, 255, 0, 0); + sdk_wifi_set_ip_info(1, &ap_ip); + + struct sdk_softap_config ap_config = { + .ssid = AP_SSID, + .ssid_hidden = 0, + .channel = 3, + .ssid_len = strlen(AP_SSID), + .authmode = AUTH_WPA_WPA2_PSK, + .password = AP_PSK, + .max_connection = 3, + .beacon_interval = 100, + }; + sdk_wifi_softap_set_config(&ap_config); + + ip_addr_t first_client_ip; + IP4_ADDR(&first_client_ip, 172, 16, 0, 2); + dhcpserver_start(&first_client_ip, 4); + dhcpserver_set_dns(&ap_ip.ip); + dhcpserver_set_router(&ap_ip.ip); + break; + case STATION_MODE: + break; + default: + break; + } +} + +//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done. +void user_init(void) { + uart_set_baud(0, 115200); + + wifiInit(); + ioInit(); + captdnsInit(); + + espFsInit((void*)(_binary_build_web_espfs_bin_start)); + httpdInit(builtInUrls, 80); + + xTaskCreate(websocketBcast, "wsbcast", 300, NULL, 3, NULL); + + printf("\nReady\n"); +} diff --git a/extras/libesphttpd/component.mk b/extras/libesphttpd/component.mk new file mode 100644 index 0000000..22e433e --- /dev/null +++ b/extras/libesphttpd/component.mk @@ -0,0 +1,64 @@ +# Component makefile for extras/libesphttpd + +INC_DIRS += $(libesphttpd_ROOT)/libesphttpd/include $(libesphttpd_ROOT)/libesphttpd/espfs $(libesphttpd_ROOT)/libesphttpd/lib/heatshrink + +LIBESPHTTPD_MAX_CONNECTIONS ?= 4 +LIBESPHTTPD_STACKSIZE ?= 2048 +LIBESPHTTPD_OTA_TAGNAME ?= generic + +RBOOT_OTA ?= 1 +ESP_IP ?= 192.168.4.1 + +# args for passing into compile rule generation +libesphttpd_SRC_DIR = $(libesphttpd_ROOT)/libesphttpd/core $(libesphttpd_ROOT)/libesphttpd/espfs $(libesphttpd_ROOT)/libesphttpd/util +libesphttpd_CFLAGS += -DFREERTOS -DUSE_OPEN_SDK -DHTTPD_MAX_CONNECTIONS=$(LIBESPHTTPD_MAX_CONNECTIONS) -DHTTPD_STACKSIZE=$(LIBESPHTTPD_STACKSIZE) -DESPFS_HEATSHRINK -D__ets__ -DRBOOT_OTA=1 -std=gnu99 + +$(eval $(call component_compile_rules,libesphttpd)) + +rwildcard = $(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2)) + +LIBESPHTTPD_MKESPFSIMAGE_DIR = $(BUILD_DIR)mkespfsimage + +LIBESPHTTPD_MKESPFS = $(LIBESPHTTPD_MKESPFSIMAGE_DIR)/mkespfsimage +LIBESPHTTPD_HTML_DIR = html +LIBESPHTTPD_HTML_FILES = $(call rwildcard,$(LIBESPHTTPD_HTML_DIR),*) +LIBESPHTTPD_HTML_TINY_DIR = $(BUILD_DIR)html +LIBESPHTTPD_HTML_ESPFS = $(BUILD_DIR)web.espfs.bin +LIBESPHTTPD_HTML_ESPFS_PATH = $(PROGRAM_REAL_ROOT)/$(LIBESPHTTPD_HTML_ESPFS) +LIBESPHTTPD_HTML_ESPFS_OBJ = $(BUILD_DIR)web.espfs.o + +CROSS_BINARY_ARCH ?= xtensa +CROSS_OUTPUT_TARGET ?= elf32-xtensa-le + +LIBESPHTTPD_HTML_TINY ?= no + +CURL = curl +LIBESPHTTPD_CURL_OPTS = --connect-timeout 3 --max-time 60 -s + +$(LIBESPHTTPD_MKESPFSIMAGE_DIR): + $(Q)mkdir -p $(LIBESPHTTPD_MKESPFSIMAGE_DIR) + +$(PROGRAM_REAL_ROOT)/$(LIBESPHTTPD_MKESPFS): $(LIBESPHTTPD_MKESPFSIMAGE_DIR) + make -C $(libesphttpd_ROOT)/libesphttpd/espfs/mkespfsimage CC=gcc GZIP_COMPRESSION=yes USE_HEATSHRINK=yes BUILD_DIR=$(PROGRAM_REAL_ROOT)/$(BUILD_DIR) + +$(LIBESPHTTPD_HTML_ESPFS): $(PROGRAM_REAL_ROOT)/$(LIBESPHTTPD_MKESPFS) $(LIBESPHTTPD_HTML_FILES) + cd $(LIBESPHTTPD_HTML_DIR) && find . | $< > $(LIBESPHTTPD_HTML_ESPFS_PATH) || rm -f $(LIBESPHTTPD_HTML_ESPFS_PATH) + +$(LIBESPHTTPD_HTML_ESPFS_OBJ): $(LIBESPHTTPD_HTML_ESPFS) + $(Q)$(OBJCOPY) -I binary -O $(CROSS_OUTPUT_TARGET) -B $(CROSS_BINARY_ARCH) --rename-section .data=.irom.espfs $^ $@ + +$(BUILD_DIR)libesphttpd.a: $(LIBESPHTTPD_HTML_ESPFS_OBJ) + +htmlfs: $(LIBESPHTTPD_HTML_ESPFS_OBJ) + +$(libesphttpd_ROOT)/libesphttpd/mkupgimg/mkupgimg: + make -C $(libesphttpd_ROOT)/libesphttpd/mkupgimg CC=gcc RBOOT_OTA=$(RBOOT_OTA) + +$(FIRMWARE_DIR)ota_$(PROGRAM).bin: $(FW_FILE) $(libesphttpd_ROOT)/libesphttpd/mkupgimg/mkupgimg + $(libesphttpd_ROOT)/libesphttpd/mkupgimg/mkupgimg $(FW_FILE) "$(LIBESPHTTPD_OTA_TAGNAME)" $(FIRMWARE_DIR)ota_$(PROGRAM).bin + +ota: $(FIRMWARE_DIR)ota_$(PROGRAM).bin $(libesphttpd_ROOT)/libesphttpd/mkupgimg/mkupgimg + +webflash: $(FIRMWARE_DIR)ota_$(PROGRAM).bin + $(CURL) $(LIBESPHTTPD_CURL_OPTS) --data-binary "@$(FIRMWARE_DIR)ota_$(PROGRAM).bin" http://$(ESP_IP)/flash/upload && $(CURL) $(LIBESPHTTPD_CURL_OPTS) http://$(ESP_IP)/flash/reboot + diff --git a/extras/libesphttpd/libesphttpd b/extras/libesphttpd/libesphttpd new file mode 160000 index 0000000..c329aee --- /dev/null +++ b/extras/libesphttpd/libesphttpd @@ -0,0 +1 @@ +Subproject commit c329aeef0469a40152e1630dbd0f0b34bcebd4d5