diff --git a/Makefile b/Makefile index 921721c..f1e2060 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ MCU_ISP_PIN ?= 13 # GPIO pin used for "connectivity" LED, active low LED_CONN_PIN ?= 0 # GPIO pin used for "serial activity" LED, active low -LED_SERIAL_PIN ?= 2 +LED_SERIAL_PIN ?= 14 # --------------- esp-link version --------------- diff --git a/html/wifi/140medley.min.js b/html/140medley.min.js similarity index 92% rename from html/wifi/140medley.min.js rename to html/140medley.min.js index d1495d1..ff21073 100644 --- a/html/wifi/140medley.min.js +++ b/html/140medley.min.js @@ -1,2 +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= +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),b=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/html/console.js b/html/console.js new file mode 100644 index 0000000..4205738 --- /dev/null +++ b/html/console.js @@ -0,0 +1,30 @@ +function fetchText(delay) { + el = $("#console"); + if (el.textEnd == undefined) { + el.textEnd = 0; + el.innerHTML = ""; + } + window.setTimeout(function() { + ajaxJson('GET', console_url + "?start=" + el.textEnd, updateText, retryLoad); + }, delay); +} + +function updateText(resp) { + el = $("#console"); + + delay = 3000; + if (resp != null && resp.len > 0) { + console.log("updateText got", resp.len, "chars at", resp.start); + if (resp.start > el.textEnd) { + el.innerHTML = el.innerHTML.concat("\r\n

esp link - Microcontroller Console

@@ -22,88 +20,29 @@
+ +
+ +
+ + +
+ diff --git a/html/help.tpl b/html/help.tpl index 783fd46..e022025 100644 --- a/html/help.tpl +++ b/html/help.tpl @@ -1,5 +1,3 @@ -%head% -

esp link - Help

@@ -7,70 +5,9 @@
-

This text is somewhat out of date, please refer to +

Please refer to the online README -for the time being.

-

The ESP Link functions in two wifi modes: Station+AccessPoint (STA+AP) and Station (STA). -In the STA+AP mode it presents a network called esp8266 that you can connect to using the -password jeelabs8266. This mode is intended for initial configuration, but it is -fully functional. Typically the easiest way to connect to the esp8266 network is using a phone, -tablet, or laptop.

-

The recommended next step is to configure the ESP Link to connect to your "normal" -Wifi network so you can access it from any of your machines. Once you have connected the ESP Link -to your network and pointed your browser at it successfully, you should -switch the ESP Link to STA mode, which is more secure (no canned password).

-

In STA mode the ESP Link connects to the configured network (the info is saved in flash). -If, after a reset, it cannot connect for one minute, it automatically reverts to STA+AP mode -allowing you to reconnect to the esp8266 network to change configuration.

-

In STA mode the most tricky part usually is the IP address. On most networks, the ESP Link -will get a dynamic IP address assigned via DHCP and you now need to enter that IP address into -your browser to connect. The good news is that after you reset your ESP Link it will continue to -have the same IP address. However, if you leave it off for the week-end it will most likely get a -fresh IP the next time it starts up. On many Wifi routers you can enter a fixed mapping from -the ESP Link's hardware MAC address to a static IP address so it always gets the same IP -address. This is the recommended method of operation.

- -

Using your esp-link

-

-The esp-link can used in several distinct ways: -

    -
  • as a transparent bridge between TCP port 23 and the serial port
  • -
  • as a web console to see input from the serial port
  • -
  • as an Arduino, AVR, or ARM processor programmer using serial-over-TCP
  • -
  • as an Arduino, AVR, or ARM processor programmer by uploading HEX files (not yet functional)
  • -
-

- -

Transparent bridge

-

The ESP accepts TCP connections to port 23 and "connects" through to the serial port. -Up to 5 simultaneous TCP connections are supported and characters coming in on the serial -port get passed through to all connections. Characters coming in on a connection get copied -through to the serial port.

-

When using Linux a simple way to use this is nc esp8266 23

- -

Programmer using serial-over-TCP

-

By hooking up the ESP's GPIO lines to the reset line of an Arduino (or AVR in general) that is -preloaded with the Optiboot bootloader/flasher it is possible to reprogram these processors over -Wifi. The way is works is that the ESP toggles the reset line each time a connection is established -and the first characters are the flash programming synchronization sequence.

-

When using Linux avrdude can be instructed to program an AVR over TCP by using a special syntax -for the serial port: -Pnet:esp8266:23, where esp8266 is the hostname of the ESP -Serial Programmer (an IP address could have been used instead).

-

NXP's LPC800-serial ARM processors can be programmed similarly by hooking up GPIO pins to the -ARM's reset and ISP lines. The ESP Serial Programmer issues the correct reset/isp pulses to put -the ARM chip into firmware programming mode.

- -

Web Console

-

The output of an attached Arduino/AVR/ARM can also be monitored via the console web page. -When connecting, it shows the most recent 10KB of characters received on the serial port and -then continues to print everything that comes in on the serial port. Eventually the page refreshes -when it gets very long. (Yes, this could be improved with some javascript...)

- -

Programmer using HEX upload

-

(Not yet functional) Instead of using the wifi-to-serial bridge to program -microcontrollers it is often faster to upload the HEX file to the ESP Serial Programmer and -have it perform the actual programming protocol.

- +for up-to-date help.

diff --git a/html/index.tpl b/html/home.tpl similarity index 76% rename from html/index.tpl rename to html/home.tpl index ddb4f7d..7d33306 100644 --- a/html/index.tpl +++ b/html/home.tpl @@ -1,13 +1,11 @@ -%head% -

esp link

-

%version%

+

-

The ESP Link connects the ESP's serial port to Wifi and it can +

The ESP Link bridges the ESP8266 serial port to Wifi and it can program microcontrollers over the serial port, in particular Arduinos, AVRs, and NXP's LPC800-series ARM processors.

diff --git a/html/led.tpl b/html/led.tpl deleted file mode 100644 index 78ea6c0..0000000 --- a/html/led.tpl +++ /dev/null @@ -1,16 +0,0 @@ -LED test - ESP Link - - - -
-
%topnav%
-

esp link - LED test

-

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

-
- - -
-
- diff --git a/html/log.tpl b/html/log.tpl index 92ca403..d72d368 100644 --- a/html/log.tpl +++ b/html/log.tpl @@ -1,5 +1,3 @@ -%head% -

esp link - Debug Log

@@ -8,12 +6,17 @@

The debug log shows the 1024 last characters printed by the esp-link software itself to its own debug log.

-
-%log%
-
+

     
+ + + diff --git a/html/style.css b/html/style.css index 3e738cf..ca798cc 100644 --- a/html/style.css +++ b/html/style.css @@ -7,6 +7,26 @@ body { color: #777; } +.card { + background-color: #eee; + padding: 1em; + margin: 0.5em; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + border-radius: 0.5em; + border: 0px solid #000000; +} + +/* wifi AP selection form */ +#aps label div { + display: inline-block; + margin: 0em 0.2em; +} + +.pure-table td, .pure-table th { + padding: 0.5em 0.5em; +} + /* make images size-up */ .xx-pure-img-responsive { max-width: 100%; @@ -237,3 +257,75 @@ pre.console a { } } +#messages { + position: absolute; + left: 25%; + width: 50%; + top: 10; + z-index: 200; + font-size: 110%; + text-align: center; +} +#warning { + background-color: #933; + color: #fcc; + padding: 0.1em 0.4em; +} +#notification { + background-color: #693; + color: #cfc; + padding: 0.1em 0.4em; +} + +#spinner { + position: absolute; + right: 10%; + top: 20; + z-index: 1000; +} +.spinner { + height: 50px; + width: 50px; + -webkit-animation: rotation 1s infinite linear; + -moz-animation: rotation 1s infinite linear; + -o-animation: rotation 1s infinite linear; + animation: rotation 1s infinite linear; + border-left: 10px solid rgba(204, 51, 0, 0.15); + border-right: 10px solid rgba(204, 51, 0, 0.15); + border-bottom: 10px solid rgba(204, 51, 0, 0.15); + border-top: 10px solid rgba(204, 51, 0, 0.8); + border-radius: 100%; +} + +@-webkit-keyframes rotation { + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(359deg); + } +} +@-moz-keyframes rotation { + from { + -moz-transform: rotate(0deg); + } + to { + -moz-transform: rotate(359deg); + } +} +@-o-keyframes rotation { + from { + -o-transform: rotate(0deg); + } + to { + -o-transform: rotate(359deg); + } +} +@keyframes rotation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +} diff --git a/html/ui.js b/html/ui.js index acc38a0..9593fd9 100644 --- a/html/ui.js +++ b/html/ui.js @@ -1,3 +1,16 @@ +// fill out menu items +(function() { + console.log("Filling out menu with", menu.length, "items"); + html = ""; + for (var i=0; i" + menu[i] + ""); + } + document.getElementById("menu-list").innerHTML = html; + v = document.getElementById("version"); + if (v != null) { v.innerHTML = version; } +}()); + (function (window, document) { var layout = document.getElementById('layout'), @@ -33,3 +46,60 @@ }; }(this, this.document)); + +function showWarning(text) { + var el = $("#warning"); + el.innerHTML = text; + el.removeAttribute('hidden'); +} +function hideWarning(text) { + el = $("#warning").setAttribute('hidden', ''); +} +function showNotification(text) { + var el = $("#notification"); + el.innerHTML = text; + el.removeAttribute('hidden'); + setTimeout(function() { el.setAttribute('hidden', ''); }, 4000); +} + +function ajaxReq(method, url, ok_cb, err_cb) { + var xhr = j(); + xhr.open(method, url); + var timeout = setTimeout(function() { + xhr.abort(); + err_cb(599, "Request timeout"); + }, 20000); + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) { return; } + clearTimeout(timeout); + if (xhr.status >= 200 && xhr.status < 300) { + ok_cb(xhr.responseText); + } else { + err_cb(xhr.status, xhr.statusText); + } + } + xhr.send(); +} + +function ajaxJson(method, url, ok_cb, err_cb) { + ajaxReq(method, url, function(resp) { ok_cb(JSON.parse(resp)); }, err_cb); +} + +function ajaxSpin(method, url, ok_cb, err_cb) { + $("#spinner").removeAttribute('hidden'); + console.log("starting spinner"); + ajaxReq(method, url, function(resp) { + $("#spinner").setAttribute('hidden', ''); + ok_cb(resp); + }, function(status, statusText) { + $("#spinner").setAttribute('hidden', ''); + showWarning("Request error: " + statusText); + err_cb(status, statusText); + }); +} + +function ajaxJsonSpin(method, url, ok_cb, err_cb) { + ajaxSpin(method, url, function(resp) { ok_cb(JSON.parse(resp)); }, err_cb); +} + + diff --git a/html/wifi/connecting.html b/html/wifi/connecting.html deleted file mode 100644 index 4499691..0000000 --- a/html/wifi/connecting.html +++ /dev/null @@ -1,44 +0,0 @@ -Connecting... - ESP Link - - - - - -
-

ESP Link - Connecting

-

Connecting to AP...

-

Status: -... -

-
- - diff --git a/html/wifi/wifi.tpl b/html/wifi/wifi.tpl index 03fcb8b..f3d4f57 100644 --- a/html/wifi/wifi.tpl +++ b/html/wifi/wifi.tpl @@ -1,103 +1,177 @@ -%head% -

esp link - Wifi Configuration

-
-

Current Wifi State

-

- WiFi mode: %WiFiMode%
- Configured network: %currSsid% Status: %currStatus% Phy:%currPhy% -

-

%WiFiapwarn%

- -

Change Wifi association

-
-

To connect to a WiFi network, please select one of the detected networks, - enter the password, and hit the connect button...

-

Scanning...
-

WiFi password, if applicable:
- - -

-
- +
+
+

Wifi State

+ + + + + + + +
WiFi mode
Configured network
Wifi status
Wifi rssi
Wifi phy
+
+
+

Wifi Association

+
+ + To connect to a WiFi network, please select one of the detected networks, + enter the password, and hit the connect button... + +
Scanning...
+ + + +
+
- - + diff --git a/httpd/httpd.c b/httpd/httpd.c index 1f2d945..b63923b 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -19,7 +19,7 @@ Esp8266 http server - core routines //Max length of request head #define MAX_HEAD_LEN 1024 //Max amount of connections -#define MAX_CONN 4 +#define MAX_CONN 6 //Max post buffer len #define MAX_POST 1024 //Max send buffer len @@ -56,7 +56,7 @@ typedef struct { //add it here. static const MimeMap mimeTypes[]={ {"htm", "text/htm"}, - {"html", "text/html"}, + {"html", "text/html; charset=UTF-8"}, {"css", "text/css"}, {"js", "text/javascript"}, {"txt", "text/plain"}, diff --git a/httpd/httpdespfs.c b/httpd/httpdespfs.c index 6f5cf86..e24252d 100644 --- a/httpd/httpdespfs.c +++ b/httpd/httpdespfs.c @@ -15,6 +15,7 @@ Connector to let httpd use the espfs filesystem to serve the files in it. #include "httpdespfs.h" #include "espfs.h" #include "espfsformat.h" +#include "cgi.h" // The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression. // If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.) @@ -87,6 +88,72 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { } +//cgiEspFsHtml is a simple HTML file that gets prefixed by head.tpl +int ICACHE_FLASH_ATTR cgiEspFsHtml(HttpdConnData *connData) { + EspFsFile *file = connData->cgiData; + char buff[2048]; + + if (connData->conn==NULL) { + // Connection aborted. Clean up. + if (file != NULL) espFsClose(file); + return HTTPD_CGI_DONE; + } + + // The first time around we send the head template in one go and we open the file + if (file == NULL) { + int status = 200; + // open file, return error on failure + file = espFsOpen("/head.tpl"); + if (file == NULL) { + os_strcpy(buff, "Header file 'head.tpl' not found\n"); + os_printf(buff); + status = 500; + } else { + // read file and return it + int len = espFsRead(file, buff, sizeof(buff)); + if (len == sizeof(buff)) { + os_strcpy(buff, "Header file 'head.tpl' too large!\n"); + os_printf(buff); + status = 500; + } + // open the real file for next time around + file = espFsOpen(connData->url); + if (file == NULL) { + os_strcpy(buff, connData->url); + os_strcat(buff, " not found\n"); + os_printf(buff); + status = 404; + } else { + connData->cgiData = file; + httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Type", "text/html; charset=UTF-8"); + httpdEndHeaders(connData); + httpdSend(connData, buff, len); + printGlobalJSON(connData); + return HTTPD_CGI_MORE; + } + } + // error response + httpdStartResponse(connData, status); + httpdHeader(connData, "text/plain", "text/html; charset=UTF-8"); + httpdEndHeaders(connData); + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; + } + + // The second time around send actual file + int len = espFsRead(file, buff, sizeof(buff)); + httpdSend(connData, buff, len); + if (len == sizeof(buff)) { + return HTTPD_CGI_MORE; + } else { + connData->cgiData = NULL; + espFsClose(file); + return HTTPD_CGI_DONE; + } +} + +#if 0 //cgiEspFsTemplate can be used as a template. typedef struct { @@ -186,4 +253,5 @@ int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) { return HTTPD_CGI_MORE; } } +#endif diff --git a/httpd/httpdespfs.h b/httpd/httpdespfs.h index 5eda335..39b5bfc 100644 --- a/httpd/httpdespfs.h +++ b/httpd/httpdespfs.h @@ -5,5 +5,6 @@ int cgiEspFsHook(HttpdConnData *connData); int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData); +int ICACHE_FLASH_ATTR cgiEspFsHtml(HttpdConnData *connData); -#endif \ No newline at end of file +#endif diff --git a/serial/console.c b/serial/console.c index 647e0fb..f78fd8b 100644 --- a/serial/console.c +++ b/serial/console.c @@ -44,13 +44,6 @@ console_write_char(char c) { console_write(c); } -void ICACHE_FLASH_ATTR -jsonHeader(HttpdConnData *connData, int code) { - httpdStartResponse(connData, code); - httpdHeader(connData, "Content-Type", "application/json"); - httpdEndHeaders(connData); -} - int ICACHE_FLASH_ATTR ajaxConsoleReset(HttpdConnData *connData) { jsonHeader(connData, 200); @@ -119,31 +112,6 @@ ajaxConsole(HttpdConnData *connData) { return HTTPD_CGI_DONE; } -//===== Display a web page with the console -int ICACHE_FLASH_ATTR -tplConsole(HttpdConnData *connData, char *token, void **arg) { - if (token==NULL) return HTTPD_CGI_DONE; - -/* - if (os_strcmp(token, "console") == 0) { - if (console_wr > console_rd) { - httpdSend(connData, console_buf+console_rd, console_wr-console_rd); - } else if (console_rd != console_wr) { - httpdSend(connData, console_buf+console_rd, BUF_MAX-console_rd); - httpdSend(connData, console_buf, console_wr); - } else { - httpdSend(connData, "", -1); - } - } else if (os_strcmp(token, "head")==0) { -*/ - if (os_strcmp(token, "head")==0) { - printHead(connData); - } else { - httpdSend(connData, "Unknown\n", -1); - } - return HTTPD_CGI_DONE; -} - void ICACHE_FLASH_ATTR consoleInit() { console_wr = 0; console_rd = 0; diff --git a/user/cgi.c b/user/cgi.c index 8f6ca67..b52edf5 100644 --- a/user/cgi.c +++ b/user/cgi.c @@ -1,6 +1,5 @@ /* -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. +Some random cgi routines. */ /* @@ -10,6 +9,8 @@ flash as a binary. Also handles the hit counter on the main page. * 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. * ---------------------------------------------------------------------------- + * Heavily modified and enhanced by Thorsten von Eicken in 2015 + * ---------------------------------------------------------------------------- */ @@ -17,134 +18,19 @@ flash as a binary. Also handles the hit counter on the main page. #include "cgi.h" #include "espfs.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[512]; - if (token==NULL) return HTTPD_CGI_DONE; - - os_strcpy(buff, "Unknown"); - if (os_strcmp(token, "ledstate")==0) { - if (currLedState) { - os_strcpy(buff, "on"); - } else { - os_strcpy(buff, "off"); - } - } else if (os_strcmp(token, "topnav")==0) { - printNav(buff); - } - httpdSend(connData, buff, -1); - return HTTPD_CGI_DONE; -} - -static long hitCounter=0; - -//Template code for the counter on the index page. -int ICACHE_FLASH_ATTR tplCounter(HttpdConnData *connData, char *token, void **arg) { - char buff[64]; - if (token==NULL) return HTTPD_CGI_DONE; - - if (printSysInfo(buff, token) > 0) { - // awesome... - } else if (os_strcmp(token, "head")==0) { - printHead(connData); - buff[0] = 0; - } else if (os_strcmp(token, "version")==0) { -# define VERS_STR_STR(V) #V -# define VERS_STR(V) VERS_STR_STR(V) - os_sprintf(buff, "%s", VERS_STR(VERSION)); - } else if (os_strcmp(token, "counter")==0) { - hitCounter++; - os_sprintf(buff, "%ld", hitCounter); - } - httpdSend(connData, buff, -1); - return HTTPD_CGI_DONE; -} - -static char *navLinks[][2] = { - { "Home", "/index.tpl" }, { "Wifi", "/wifi/wifi.tpl" }, { "\xC2\xB5""C Console", "/console.tpl" }, - { "Debug log", "/log.tpl" }, { "Help", "/help.tpl" }, - { 0, 0 }, -}; - -// Print the navigation links into the buffer and return the length of what got added -int ICACHE_FLASH_ATTR printNav(char *buff) { - int len = 0; - for (uint8_t i=0; navLinks[i][0] != NULL; i++) { - //os_printf("nav %d: %s -> %s\n", i, navLinks[i][0], navLinks[i][1]); - len += os_sprintf(buff+len, - "
  • %s
  • ", - navLinks[i][1], navLinks[i][0]); - } - len += os_sprintf(buff+len, "
  • %dKB
  • ", - system_get_free_heap_size()/1024); - //os_printf("nav(%d): %s\n", len, buff); - return len; -} - -void ICACHE_FLASH_ATTR printHead(HttpdConnData *connData) { - char buff[1024]; - - struct EspFsFile *file = espFsOpen("/head.tpl"); - if (file == NULL) { - os_printf("Header file 'head.tpl' not found\n"); - return; - } - - int len = espFsRead(file, buff, 1024); - if (len == 1024) { - os_printf("Header file 'head.tpl' too large!\n"); - buff[1023] = 0; - } else { - buff[len] = 0; // ensure null termination - } - - if (len > 0) { - char *p = os_strstr(buff, "%topnav%"); - if (p != NULL) { - char navBuf[512]; - int n = p - buff; - httpdSend(connData, buff, n); - printNav(navBuf); - httpdSend(connData, navBuf, -1); - httpdSend(connData, buff+n+8, len-n-8); - } else { - httpdSend(connData, buff, len); - } - } - espFsClose(file); +void ICACHE_FLASH_ATTR +jsonHeader(HttpdConnData *connData, int code) { + httpdStartResponse(connData, code); + httpdHeader(connData, "Content-Type", "application/json"); + httpdEndHeaders(connData); } #define TOKEN(x) (os_strcmp(token, x) == 0) +#if 0 // Handle system information variables and print their value, returns the number of // characters appended to buff -int ICACHE_FLASH_ATTR printSysInfo(char *buff, char *token) { +int ICACHE_FLASH_ATTR printGlobalInfo(char *buff, int buflen, char *token) { if (TOKEN("si_chip_id")) { return os_sprintf(buff, "0x%x", system_get_chip_id()); } else if (TOKEN("si_freeheap")) { @@ -162,4 +48,16 @@ int ICACHE_FLASH_ATTR printSysInfo(char *buff, char *token) { return 0; } } - +#endif + +void ICACHE_FLASH_ATTR printGlobalJSON(HttpdConnData *connData) { + httpdSend(connData, + "\n", -1); +} diff --git a/user/cgi.h b/user/cgi.h index 2e1638c..52af3c6 100644 --- a/user/cgi.h +++ b/user/cgi.h @@ -3,11 +3,7 @@ #include "httpd.h" -int cgiLed(HttpdConnData *connData); -int tplLed(HttpdConnData *connData, char *token, void **arg); -int tplCounter(HttpdConnData *connData, char *token, void **arg); -int printNav(char *buff); -void ICACHE_FLASH_ATTR printHead(HttpdConnData *connData); -int ICACHE_FLASH_ATTR printSysInfo(char *buff, char *token); +void ICACHE_FLASH_ATTR jsonHeader(HttpdConnData *connData, int code); +void ICACHE_FLASH_ATTR printGlobalJSON(HttpdConnData *connData); #endif diff --git a/user/cgiwifi.c b/user/cgiwifi.c index eab2a91..d4b6109 100644 --- a/user/cgiwifi.c +++ b/user/cgiwifi.c @@ -21,6 +21,8 @@ Cgi/template routines for the /wifi url. //Enable this to disallow any changes in AP settings //#define DEMO_MODE +#define SLEEP_MODE LIGHT_SLEEP_T + // ===== wifi status change callback uint8_t wifiState = wifiIsDisconnected; @@ -35,6 +37,7 @@ static char *wifiReasons[] = { "beacon_timeout", "no_ap_found" }; static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; +static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; static char* ICACHE_FLASH_ATTR wifiGetReason(void) { if (wifiReason <= 24) return wifiReasons[wifiReason]; @@ -89,7 +92,7 @@ static void ICACHE_FLASH_ATTR wifiHandleEventCb(System_Event_t *evt) { //WiFi access point data typedef struct { char ssid[32]; - char rssi; + sint8 rssi; char enc; } ApData; @@ -175,7 +178,7 @@ int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { if (!cgiWifiAps.scanInProgress && pos!=0) { //Fill in json code for an access point if (pos-1ssid, cgiWifiAps.apData[pos-1]->rssi, cgiWifiAps.apData[pos-1]->enc, (pos-1==cgiWifiAps.noAps-1)?"":","); httpdSend(connData, buff, len); @@ -214,6 +217,14 @@ int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { // ===== timers to change state and rescue from failed associations +#if 0 +static ETSTimer deepTimer; +static void ICACHE_FLASH_ATTR deepSleepCb(void *arg) { + system_deep_sleep_set_option(2); + system_deep_sleep(100*1000); +} +#endif + //#define CONNTRY_IDLE 0 //#define CONNTRY_WORKING 1 //#define CONNTRY_SUCCESS 2 @@ -237,7 +248,11 @@ static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { // We're happily connected, go to STA mode os_printf("Wifi got IP. Going into STA mode..\n"); wifi_set_opmode(1); + wifi_set_sleep_type(SLEEP_MODE); os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); + //os_timer_disarm(&deepTimer); + //os_timer_setfn(&deepTimer, deepSleepCb, NULL); + //os_timer_arm(&deepTimer, 1000, 1); } log_uart(false); // no more resetTimer at this point, gotta use physical reset to recover if in trouble @@ -269,8 +284,8 @@ static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); } -//This cgi uses the routines above to connect to a specific access point with the -//given ESSID using the given password. +// This cgi uses the routines above to connect to a specific access point with the +// given ESSID using the given password. int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { char essid[128]; char passwd[128]; @@ -280,23 +295,25 @@ int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { return HTTPD_CGI_DONE; } - httpdFindArg(connData->post->buff, "essid", essid, sizeof(essid)); - httpdFindArg(connData->post->buff, "passwd", passwd, sizeof(passwd)); - - os_strncpy((char*)stconf.ssid, essid, 32); - os_strncpy((char*)stconf.password, passwd, 64); - os_printf("Wifi try to connect to AP %s pw %s\n", essid, passwd); - - //Schedule disconnect/connect - os_timer_disarm(&reassTimer); - os_timer_setfn(&reassTimer, reassTimerCb, NULL); -//Set to 0 if you want to disable the actual reconnecting bit -#ifdef DEMO_MODE - httpdRedirect(connData, "/wifi"); -#else - os_timer_arm(&reassTimer, 1000, 0); - httpdRedirect(connData, "connecting.html"); + int el = httpdFindArg(connData->post->buff, "essid", essid, sizeof(essid)); + int pl = httpdFindArg(connData->post->buff, "passwd", passwd, sizeof(passwd)); + + if (el > 0 && pl > 0) { + //Set to 0 if you want to disable the actual reconnecting bit +#ifndef DEMO_MODE + os_strncpy((char*)stconf.ssid, essid, 32); + os_strncpy((char*)stconf.password, passwd, 64); + os_printf("Wifi try to connect to AP %s pw %s\n", essid, passwd); + + //Schedule disconnect/connect + os_timer_disarm(&reassTimer); + os_timer_setfn(&reassTimer, reassTimerCb, NULL); + os_timer_arm(&reassTimer, 1000, 0); #endif + jsonHeader(connData, 200); + } else { + jsonHeader(connData, 400); + } return HTTPD_CGI_DONE; } @@ -315,16 +332,19 @@ int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { int m = atoi(buff); os_printf("Wifi switching to mode %d\n", m); #ifndef DEMO_MODE - wifi_set_opmode(m); + wifi_set_opmode(m&3); if (m == 1) { + wifi_set_sleep_type(SLEEP_MODE); // STA-only mode, reset into STA+AP after a timeout os_timer_disarm(&resetTimer); os_timer_setfn(&resetTimer, resetTimerCb, NULL); os_timer_arm(&resetTimer, RESET_TIMEOUT, 0); } + jsonHeader(connData, 200); #endif + } else { + jsonHeader(connData, 400); } - httpdRedirect(connData, "/wifi"); return HTTPD_CGI_DONE; } @@ -336,9 +356,8 @@ int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { int len; struct ip_info info; int st=wifi_station_get_connect_status(); - httpdStartResponse(connData, 200); - httpdHeader(connData, "Content-Type", "text/json"); - httpdEndHeaders(connData); + + jsonHeader(connData, 200); len = os_sprintf(buff, "{\"status\": \"%s\"", st > 0 && st < sizeof(connStatuses) ? connStatuses[st] : "unknown"); @@ -368,49 +387,42 @@ int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { return HTTPD_CGI_DONE; } -//Template code for the WLAN page. -int ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) { +static char *wifiWarn[] = { 0, + "Switch to STA+AP mode", + "Can't scan in this mode! Switch to STA+AP mode", + "Switch to STA mode", +}; + +// Cgi to return various Wifi information +int ICACHE_FLASH_ATTR cgiWifiInfo(HttpdConnData *connData) { char buff[1024]; - int x; - static struct station_config stconf; - if (token==NULL) return HTTPD_CGI_DONE; - wifi_station_get_config(&stconf); + int len; - os_strcpy(buff, "Unknown"); - if (os_strcmp(token, "WiFiMode")==0) { - x = wifi_get_opmode() & 0x3; - os_strcpy(buff, wifiMode[x]); - } else if (os_strcmp(token, "currSsid")==0) { - os_strcpy(buff, (char*)stconf.ssid); - } else if (os_strcmp(token, "currStatus")==0) { - int st=wifi_station_get_connect_status(); - if (st > 0 && st < sizeof(connStatuses)) - os_strcpy(buff, connStatuses[st]); - else - os_strcpy(buff, "unknown"); - } else if (os_strcmp(token, "currPhy")==0) { - int m = wifi_get_phy_mode(); - os_sprintf(buff, "%d", m); - } else if (os_strcmp(token, "WiFiPasswd")==0) { - os_strcpy(buff, (char*)stconf.password); - } else if (os_strcmp(token, "WiFiapwarn")==0) { - x=wifi_get_opmode(); - switch (x) { - case 1: - os_strcpy(buff, "Click here to go to STA+AP mode."); - break; - case 2: - os_strcpy(buff, "Can't scan in this mode. Click here to go to STA+AP mode."); - break; - case 3: - os_strcpy(buff, "Click here to go to STA mode."); - break; - } - } else if (os_strcmp(token, "head")==0) { - printHead(connData); - buff[0] = 0; + if (connData->conn==NULL) { + return HTTPD_CGI_DONE; // Connection aborted } - httpdSend(connData, buff, -1); + + struct station_config stconf; + wifi_station_get_config(&stconf); + + uint8_t op = wifi_get_opmode() & 0x3; + char *mode = wifiMode[op]; + char *status = "unknown"; + int st = wifi_station_get_connect_status(); + if (st > 0 && st < sizeof(connStatuses)) status = connStatuses[st]; + int p = wifi_get_phy_mode(); + char *phy = wifiPhy[p&3]; + char *warn = wifiWarn[op]; + sint8 rssi = wifi_station_get_rssi(); + if (rssi > 0) rssi = 0; + + len = os_sprintf(buff, + "{\"mode\": \"%s\", \"ssid\": \"%s\", \"status\": \"%s\", \"phy\": \"%s\", " + "\"rssi\": \"%ddB\", \"warn\": \"%s\", \"passwd\": \"%s\"}", + mode, (char*)stconf.ssid, status, phy, rssi, warn, (char*)stconf.password); + + jsonHeader(connData, 200); + httpdSend(connData, buff, len); return HTTPD_CGI_DONE; } diff --git a/user/cgiwifi.h b/user/cgiwifi.h index 9746b94..847e605 100644 --- a/user/cgiwifi.h +++ b/user/cgiwifi.h @@ -6,7 +6,7 @@ enum { wifiIsDisconnected, wifiIsConnected, wifiGotIP }; int cgiWiFiScan(HttpdConnData *connData); -int tplWlan(HttpdConnData *connData, char *token, void **arg); +int cgiWifiInfo(HttpdConnData *connData); int cgiWiFi(HttpdConnData *connData); int cgiWiFiConnect(HttpdConnData *connData); int cgiWiFiSetMode(HttpdConnData *connData); diff --git a/user/log.c b/user/log.c index 9b4dd98..edddafb 100644 --- a/user/log.c +++ b/user/log.c @@ -11,6 +11,7 @@ #define BUF_MAX (1024) static char log_buf[BUF_MAX]; static int log_wr, log_rd; +static int log_pos; static bool log_no_uart; // start out printing to uart static bool log_newline; // at start of a new line @@ -30,8 +31,10 @@ static void ICACHE_FLASH_ATTR log_write(char c) { log_buf[log_wr] = c; log_wr = (log_wr+1) % BUF_MAX; - if (log_wr == log_rd) + if (log_wr == log_rd) { log_rd = (log_rd+1) % BUF_MAX; // full, eat first char + log_pos++; + } } #if 0 @@ -63,25 +66,47 @@ log_write_char(char c) { log_write(c); } -//===== Display a web page with the log int ICACHE_FLASH_ATTR -tplLog(HttpdConnData *connData, char *token, void **arg) { - if (token==NULL) return HTTPD_CGI_DONE; +ajaxLog(HttpdConnData *connData) { + char buff[2048]; + int len; // length of text in buff + int log_len = (log_wr+BUF_MAX-log_rd) % BUF_MAX; // num chars in log_buf + int start = 0; // offset onto log_wr to start sending out chars + + jsonHeader(connData, 200); + + // figure out where to start in buffer based on URI param + len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); + if (len > 0) { + start = atoi(buff); + if (start < log_pos) { + start = 0; + } else if (start >= log_pos+log_len) { + start = log_len; + } else { + start = start - log_pos; + } + } + + // start outputting + len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", + log_len-start, log_pos+start); - if (os_strcmp(token, "log") == 0) { - if (log_wr > log_rd) { - httpdSend(connData, log_buf+log_rd, log_wr-log_rd); - } else if (log_rd != log_wr) { - httpdSend(connData, log_buf+log_rd, BUF_MAX-log_rd); - httpdSend(connData, log_buf, log_wr); + int rd = (log_rd+start) % BUF_MAX; + while (len < 2040 && rd != log_wr) { + uint8_t c = log_buf[rd]; + if (c == '\\' || c == '"') { + buff[len++] = '\\'; + buff[len++] = c; + } else if (c < ' ') { + len += os_sprintf(buff+len, "\\u%04x", c); } else { - httpdSend(connData, "", -1); + buff[len++] = c; } - } else if (os_strcmp(token, "head")==0) { - printHead(connData); - } else { - httpdSend(connData, "Unknown\n", -1); + rd = (rd + 1) % BUF_MAX; } + os_strcpy(buff+len, "\"}"); len+=2; + httpdSend(connData, buff, len); return HTTPD_CGI_DONE; } diff --git a/user/log.h b/user/log.h index fbeb18e..08330f8 100644 --- a/user/log.h +++ b/user/log.h @@ -5,6 +5,6 @@ void logInit(void); void ICACHE_FLASH_ATTR log_uart(bool enable); -int tplLog(HttpdConnData *connData, char *token, void **arg); +int ICACHE_FLASH_ATTR ajaxLog(HttpdConnData *connData); #endif diff --git a/user/user_main.c b/user/user_main.c index 21fc306..663176f 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -59,18 +59,19 @@ general ones. Authorization things (like authBasic) act as a 'barrier' and should be placed above the URLs they protect. */ HttpdBuiltInUrl builtInUrls[]={ - {"/", cgiRedirect, "/index.tpl"}, - //{"/flash/read", cgiReadFlash, NULL}, + {"/", cgiRedirect, "/home.tpl"}, {"/flash/next", cgiGetFirmwareNext, NULL}, {"/flash/upload", cgiUploadFirmware, NULL}, {"/flash/reboot", cgiRebootFirmware, NULL}, - {"/index.tpl", cgiEspFsTemplate, tplCounter}, - {"/help.tpl", cgiEspFsTemplate, tplCounter}, - {"/log.tpl", cgiEspFsTemplate, tplLog}, - {"/console.tpl", cgiEspFsTemplate, tplConsole}, + {"/home.tpl", cgiEspFsHtml, NULL}, + //{"/help.tpl", cgiEspFsTemplate, tplCounter}, + {"/log.tpl", cgiEspFsHtml, NULL}, + {"/log/text", ajaxLog, NULL}, + {"/console.tpl", cgiEspFsHtml, NULL}, {"/console/reset", ajaxConsoleReset, NULL}, {"/console/baud", ajaxConsoleBaud, NULL}, {"/console/text", ajaxConsole, NULL}, + {"/help.tpl", cgiEspFsHtml, NULL}, //Routines to make the /wifi URL and everything beneath it work. @@ -79,11 +80,12 @@ HttpdBuiltInUrl builtInUrls[]={ {"/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}, + {"/wifi/wifi.tpl", cgiEspFsHtml, NULL}, + {"/wifi/info", cgiWifiInfo, NULL}, + {"/wifi/wifiscan", cgiWiFiScan, NULL}, + {"/wifi/connect", cgiWiFiConnect, NULL}, + {"/wifi/connstatus", cgiWiFiConnStatus, NULL}, + {"/wifi/setmode", cgiWiFiSetMode, NULL}, {"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem {NULL, NULL, NULL}