pull/183/merge
cskarai 9 years ago committed by GitHub
commit 06c384badd
  1. 6
      Makefile
  2. 44
      WEB-SERVER.md
  3. 207
      arduino/libraries/EspLink/EspLink.cpp
  4. 91
      arduino/libraries/EspLink/EspLink.h
  5. 214
      arduino/libraries/EspLink/WebServer.cpp
  6. 91
      arduino/libraries/EspLink/WebServer.h
  7. 35
      arduino/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino
  8. 188
      arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino
  9. 8
      arduino/libraries/EspLink/examples/EspLinkWebApp/Makefile.webpage
  10. 17
      arduino/libraries/EspLink/examples/EspLinkWebApp/Pages.h
  11. 84
      arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino
  12. 125
      arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino
  13. BIN
      arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img
  14. 36
      arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/LED.html
  15. 24
      arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html
  16. 15
      arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html
  17. 49
      arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/EspLinkWebSimpleLedControl.ino
  18. 7
      arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html
  19. 1
      arduino/libraries/readme.txt
  20. 3
      cmd/cmd.h
  21. 2
      cmd/handlers.c
  22. 114
      createEspFs.pl
  23. 10
      esp-link/cgi.c
  24. 2
      esp-link/cgiservices.c
  25. 158
      esp-link/cgiwebserversetup.c
  26. 8
      esp-link/cgiwebserversetup.h
  27. 40
      esp-link/config.c
  28. 3
      esp-link/config.h
  29. 9
      esp-link/main.c
  30. 202
      espfs/espfs.c
  31. 27
      espfs/espfs.h
  32. 6
      html/home.html
  33. 242
      html/userpage.js
  34. 29
      html/web-server.html
  35. 50
      httpd/httpd.c
  36. 3
      httpd/httpd.h
  37. 19
      httpd/httpdespfs.c
  38. 301
      httpd/multipart.c
  39. 32
      httpd/multipart.h
  40. 15
      serial/serbridge.c
  41. 2
      serial/serbridge.h
  42. 2
      serial/slip.c
  43. 454
      web-server/web-server.c
  44. 37
      web-server/web-server.h

@ -101,7 +101,7 @@ LED_SERIAL_PIN ?= 14
# --------------- esp-link modules config options ---------------
# Optional Modules mqtt
MODULES ?= mqtt rest syslog
MODULES ?= mqtt rest syslog web-server
# --------------- esphttpd config options ---------------
@ -220,6 +220,10 @@ ifneq (,$(findstring syslog,$(MODULES)))
CFLAGS += -DSYSLOG
endif
ifneq (,$(findstring web-server,$(MODULES)))
CFLAGS += -DWEBSERVER
endif
# which modules (subdirectories) of the project to include in compiling
LIBRARIES_DIR = libraries
MODULES += espfs httpd user serial cmd esp-link

@ -0,0 +1,44 @@
ESP-LINK web-server tutorial
============================
LED flashing sample
--------------------
Circuit:
- 1: connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO:
(RX - levelshifter - TX, TX - levelshifter - RX)
- 2: optionally connect RESET-s with a level shifter
Installation steps:
- 1: install the latest Arduino on the PC
- 2: install EspLink library from arduino/libraries path
- 3: open EspLinkWebSimpleLedControl sample from Arduino
- 4: upload the code onto an Arduino Nano/Uno
- 5: install esp-link
- 6: jump to the Web Server page on esp-link UI
- 7: upload SimpleLED.html ( arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html )
- 8: jump to SimpleLED page on esp-link UI
- 9: turn on/off the LED
Complex application sample
--------------------------
Circuit:
- 1: connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO:
(RX - levelshifter - TX, TX - levelshifter - RX)
- 2: optionally connect RESET-s with a level shifter
- 3: add a trimmer to A0 for Voltage measurement
Installation steps:
- 1: open EspLinkWebApp sample from Arduino
- 2: upload the code onto an Arduino Nano/Uno
- 3: jump to the Web Server page on esp-link UI
- 4: upload web-page.espfs.img ( arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img )
- 5: jump to LED/User/Voltage pages
- 6: try out different settings

@ -0,0 +1,207 @@
#include "EspLink.h"
#define READ_BUF_DFLT_SIZE 64
// Standard SLIP escape chars from RFC
#define SLIP_END 0300 // indicates end of packet
#define SLIP_ESC 0333 // indicates byte stuffing
#define SLIP_ESC_END 0334 // ESC ESC_END means END data byte
#define SLIP_ESC_ESC 0335 // ESC ESC_ESC means ESC data byte
EspLink::EspLink(Stream &streamIn, CmdRequestCB callback):stream(streamIn),requestCb(callback)
{
readBuf = NULL;
readLastChar = 0;
}
EspLink::~EspLink()
{
if( readBuf != NULL )
free( readBuf );
readBuf = NULL;
}
void EspLink::writeChar(uint8_t data)
{
switch(data)
{
case SLIP_END:
stream.write(SLIP_ESC);
stream.write(SLIP_ESC_END);
break;
case SLIP_ESC:
stream.write(SLIP_ESC);
stream.write(SLIP_ESC_ESC);
break;
default:
stream.write(data);
}
crc16_add(data, &crc16_out);
}
/* CITT CRC16 polynomial ^16 + ^12 + ^5 + 1 */
/*---------------------------------------------------------------------------*/
void EspLink::crc16_add(uint8_t b, uint16_t *crc)
{
*crc ^= b;
*crc = (*crc >> 8) | (*crc << 8);
*crc ^= (*crc & 0xff00) << 4;
*crc ^= (*crc >> 8) >> 4;
*crc ^= (*crc & 0xff00) >> 5;
}
void EspLink::writeBuf(uint8_t * buf, uint16_t len)
{
while(len-- > 0)
writeChar(*buf++);
}
void EspLink::sendPacketStart(uint16_t cmd, uint32_t value, uint16_t argc)
{
crc16_out = 0;
stream.write( SLIP_END );
writeBuf((uint8_t*)&cmd, 2);
writeBuf((uint8_t*)&argc, 2);
writeBuf((uint8_t*)&value, 4);
}
void EspLink::sendPacketArg(uint16_t len, uint8_t * data)
{
writeBuf((uint8_t*)&len, 2);
writeBuf(data, len);
uint16_t pad = ((len+3)&~3) - len; // get to multiple of 4
if (pad > 0) {
uint32_t temp = 0;
writeBuf((uint8_t*)&temp, pad);
}
}
void EspLink::sendPacketEnd() {
uint16_t crc = crc16_out;
writeBuf((uint8_t*)&crc, 2);
stream.write(SLIP_END);
}
void EspLink::parseSlipPacket()
{
CmdRequest req;
req.cmd = (CmdPacket *)readBuf;
req.arg_num = 0;
req.arg_ptr = readBuf + sizeof(CmdPacket);
requestCb(&req);
free(readBuf);
readBuf = NULL;
}
void EspLink::checkPacket()
{
if( readBufPtr <= 3 )
return;
uint16_t crc = 0;
for(uint16_t i=0; i < readBufPtr - 2; i++)
crc16_add(readBuf[i], &crc);
uint16_t crcpacket = *(uint16_t*)(readBuf + readBufPtr - 2);
if( crc == crcpacket )
{
readBufPtr -= 2;
parseSlipPacket();
}
}
void EspLink::readLoop()
{
if( stream.available() > 0 )
{
int byt = stream.read();
switch(readState)
{
case WAIT_FOR_SLIP_START:
if( byt == SLIP_END )
{
if(readBuf != NULL)
free(readBuf);
readBufPtr = 0;
readBufMax = READ_BUF_DFLT_SIZE;
readBuf = (uint8_t *)malloc(readBufMax);
readState = READ_SLIP_PACKAGE;
}
break;
case READ_SLIP_PACKAGE:
if( byt == SLIP_END )
{
readState = WAIT_FOR_SLIP_START;
checkPacket();
break;
}
if( byt == SLIP_ESC )
break;
if( readLastChar == SLIP_ESC && byt == SLIP_ESC_END )
byt = SLIP_END;
else if( readLastChar == SLIP_ESC && byt == SLIP_ESC_ESC )
byt = SLIP_ESC;
if( readBufPtr >= readBufMax )
{
readBufMax = readBufMax + READ_BUF_DFLT_SIZE;
readBuf = (uint8_t *)realloc(readBuf, readBufMax);
if( readBuf == NULL )
{
readState = WAIT_FOR_SLIP_START; // TODO
break;
}
}
readBuf[readBufPtr++] = byt;
break;
}
readLastChar = byt;
}
}
// Return the number of arguments given a command struct
uint32_t EspLink::cmdGetArgc(CmdRequest *req) {
return req->cmd->argc;
}
// Copy the next argument from a command structure into the data pointer, returns 0 on success
// -1 on error
int32_t EspLink::cmdPopArg(CmdRequest *req, void *data, uint16_t len) {
uint16_t length;
if (req->arg_num >= req->cmd->argc)
return -1;
length = *(uint16_t*)req->arg_ptr;
if (length != len) return -1; // safety check
memcpy(data, req->arg_ptr + 2, length);
req->arg_ptr += (length+5)&~3; // round up to multiple of 4
req->arg_num ++;
return 0;
}
// Skip the next argument
void EspLink::cmdSkipArg(CmdRequest *req) {
uint16_t length;
if (req->arg_num >= req->cmd->argc) return;
length = *(uint16_t*)req->arg_ptr;
req->arg_ptr += (length+5)&~3;
req->arg_num ++;
}
// Return the length of the next argument
uint16_t EspLink::cmdArgLen(CmdRequest *req) {
return *(uint16_t*)req->arg_ptr;
}

@ -0,0 +1,91 @@
#ifndef ESP_LINK_H
#define ESP_LINK_H
#include <inttypes.h>
#include <Stream.h>
typedef struct __attribute__((__packed__)) {
uint16_t len; // length of data
uint8_t data[0]; // really data[len]
} CmdArg;
typedef struct __attribute__((__packed__)) {
uint16_t cmd; // command to perform, from CmdName enum
uint16_t argc; // number of arguments to command
uint32_t value; // callback pointer for response or first argument
CmdArg args[0]; // really args[argc]
} CmdPacket;
typedef struct {
CmdPacket *cmd; // command packet header
uint32_t arg_num; // number of args parsed
uint8_t *arg_ptr; // pointer to ??
} CmdRequest;
typedef void (* CmdRequestCB)(CmdRequest *);
typedef enum {
CMD_NULL = 0,
CMD_SYNC, // synchronize and clear
CMD_RESP_V, // response with a value
CMD_RESP_CB, // response with a callback
CMD_WIFI_STATUS, // get the current wifi status
CMD_CB_ADD,
CMD_CB_EVENTS,
CMD_GET_TIME, // get current time in seconds since the unix epoch
CMD_MQTT_SETUP = 10, // set-up callbacks
CMD_MQTT_PUBLISH, // publish a message
CMD_MQTT_SUBSCRIBE, // subscribe to a topic
CMD_MQTT_LWT, // set the last-will-topic and messge
CMD_REST_SETUP = 20,
CMD_REST_REQUEST,
CMD_REST_SETHEADER,
CMD_WEB_DATA = 30,
CMD_WEB_REQ_CB,
} CmdName;
typedef enum
{
WAIT_FOR_SLIP_START,
READ_SLIP_PACKAGE,
} ReadState;
class EspLink
{
private:
uint16_t crc16_out;
Stream &stream;
ReadState readState;
uint8_t * readBuf;
uint16_t readBufPtr;
uint16_t readBufMax;
int readLastChar;
CmdRequestCB requestCb;
void crc16_add(uint8_t b, uint16_t *crc);
void writeChar(uint8_t chr);
void writeBuf(uint8_t * buf, uint16_t len);
void checkPacket();
void parseSlipPacket();
public:
EspLink(Stream &stream, CmdRequestCB callback);
~EspLink();
void sendPacketStart(uint16_t cmd, uint32_t value, uint16_t argc);
void sendPacketArg(uint16_t len, uint8_t * data);
void sendPacketEnd();
void readLoop();
uint32_t cmdGetArgc(CmdRequest *req);
int32_t cmdPopArg(CmdRequest *req, void *data, uint16_t len);
void cmdSkipArg(CmdRequest *req);
uint16_t cmdArgLen(CmdRequest *req);
};
#endif /* ESP_LINK_H */

@ -0,0 +1,214 @@
#include "WebServer.h"
#include "Arduino.h"
#define RESUBSCRIBE_LIMIT 1000
WebServer * WebServer::instance = NULL;
void webServerCallback(CmdRequest *req)
{
WebServer::getInstance()->handleRequest(req);
}
WebServer::WebServer(Stream &streamIn, const WebMethod * PROGMEM methodsIn):espLink(streamIn, webServerCallback),methods(methodsIn),stream(streamIn),esplink_cb(NULL)
{
instance = this;
}
void WebServer::init()
{
registerCallback();
}
void WebServer::loop()
{
// resubscribe periodically
uint32_t elapsed = millis() - last_connect_ts;
if( elapsed > RESUBSCRIBE_LIMIT )
registerCallback();
espLink.readLoop();
}
void WebServer::registerCallback()
{
espLink.sendPacketStart(CMD_CB_ADD, 100, 1);
espLink.sendPacketArg(5, (uint8_t *)"webCb");
espLink.sendPacketEnd();
last_connect_ts = millis();
}
void WebServer::invokeMethod(RequestReason reason, WebMethod * method, CmdRequest *req)
{
switch(reason)
{
case WS_BUTTON:
{
uint16_t len = espLink.cmdArgLen(req);
char bf[len+1];
bf[len] = 0;
espLink.cmdPopArg(req, bf, len);
method->callback(BUTTON_PRESS, bf, len);
}
break;
case WS_SUBMIT:
{
int arg_len = espLink.cmdGetArgc( req );
int cnt = 4;
while( cnt < arg_len )
{
uint16_t len = espLink.cmdArgLen(req);
char bf[len+1];
bf[len] = 0;
espLink.cmdPopArg(req, bf, len);
value_ptr = bf + 2 + strlen(bf+1);
method->callback(SET_FIELD, bf+1, strlen(bf+1));
cnt++;
}
}
return;
case WS_LOAD:
case WS_REFRESH:
break;
default:
return;
}
espLink.sendPacketStart(CMD_WEB_DATA, 100, 255);
espLink.sendPacketArg(4, remote_ip);
espLink.sendPacketArg(2, (uint8_t *)&remote_port);
method->callback( reason == WS_LOAD ? LOAD : REFRESH, NULL, 0);
espLink.sendPacketArg(0, NULL);
espLink.sendPacketEnd();
}
void WebServer::handleRequest(CmdRequest *req)
{
if( req->cmd->cmd != CMD_WEB_REQ_CB )
{
if( esplink_cb != NULL )
esplink_cb(req);
return;
}
uint16_t shrt;
espLink.cmdPopArg(req, &shrt, 2);
RequestReason reason = (RequestReason)shrt;
espLink.cmdPopArg(req, &remote_ip, 4);
espLink.cmdPopArg(req, &remote_port, 2);
{
uint16_t len = espLink.cmdArgLen(req);
char bf[len+1];
bf[len] = 0;
espLink.cmdPopArg(req, bf, len);
const WebMethod * meth = methods;
do
{
WebMethod m;
memcpy_P(&m, meth, sizeof(WebMethod));
if( m.url == NULL || m.callback == NULL )
break;
if( strcmp_P(bf, m.url) == 0 )
{
invokeMethod(reason, &m, req);
return;
}
meth++;
}while(1);
}
if( reason == WS_SUBMIT )
return;
// empty response
espLink.sendPacketStart(CMD_WEB_DATA, 100, 2);
espLink.sendPacketArg(4, remote_ip);
espLink.sendPacketArg(2, (uint8_t *)&remote_port);
espLink.sendPacketEnd();
}
void WebServer::setArgString(const char * name, const char * value)
{
uint8_t nlen = strlen(name);
uint8_t vlen = strlen(value);
char buf[nlen + vlen + 3];
buf[0] = WEB_STRING;
strcpy(buf+1, name);
strcpy(buf+2+nlen, value);
espLink.sendPacketArg(nlen+vlen+2, (uint8_t *)buf);
}
void WebServer::setArgStringP(const char * name, const char * value)
{
uint8_t nlen = strlen(name);
uint8_t vlen = strlen_P(value);
char buf[nlen + vlen + 3];
buf[0] = WEB_STRING;
strcpy(buf+1, name);
strcpy_P(buf+2+nlen, value);
espLink.sendPacketArg(nlen+vlen+2, (uint8_t *)buf);
}
void WebServer::setArgBoolean(const char * name, uint8_t value)
{
uint8_t nlen = strlen(name);
char buf[nlen + 4];
buf[0] = WEB_BOOLEAN;
strcpy(buf+1, name);
buf[2 + nlen] = value;
espLink.sendPacketArg(nlen+3, (uint8_t *)buf);
}
void WebServer::setArgJson(const char * name, const char * value)
{
uint8_t nlen = strlen(name);
uint8_t vlen = strlen(value);
char buf[nlen + vlen + 3];
buf[0] = WEB_JSON;
strcpy(buf+1, name);
strcpy(buf+2+nlen, value);
espLink.sendPacketArg(nlen+vlen+2, (uint8_t *)buf);
}
void WebServer::setArgInt(const char * name, int32_t value)
{
uint8_t nlen = strlen(name);
char buf[nlen + 7];
buf[0] = WEB_INTEGER;
strcpy(buf+1, name);
memcpy(buf+2+nlen, &value, 4);
espLink.sendPacketArg(nlen+6, (uint8_t *)buf);
}
int32_t WebServer::getArgInt()
{
return (int32_t)atol(value_ptr);
}
char * WebServer::getArgString()
{
return value_ptr;
}
uint8_t WebServer::getArgBoolean()
{
if( strcmp_P(value_ptr, PSTR("on")) == 0 )
return 1;
if( strcmp_P(value_ptr, PSTR("true")) == 0 )
return 1;
if( strcmp_P(value_ptr, PSTR("yes")) == 0 )
return 1;
if( strcmp_P(value_ptr, PSTR("1")) == 0 )
return 1;
return 0;
}

@ -0,0 +1,91 @@
#ifndef WEB_SERVER_H
#define WEB_SERVER_H
#include "EspLink.h"
typedef enum
{
BUTTON_PRESS,
SET_FIELD,
REFRESH,
LOAD,
} WebServerCommand;
typedef void (*WebServerCallback)(WebServerCommand command, char * data, int dataLen);
typedef struct
{
const char * PROGMEM url;
WebServerCallback callback;
} WebMethod;
typedef enum {
WS_LOAD=0,
WS_REFRESH,
WS_BUTTON,
WS_SUBMIT,
} RequestReason;
typedef enum
{
WEB_STRING=0,
WEB_NULL,
WEB_INTEGER,
WEB_BOOLEAN,
WEB_FLOAT,
WEB_JSON
} WebValueType;
class WebServer
{
friend void webServerCallback(CmdRequest *req);
private:
const WebMethod * PROGMEM methods;
Stream &stream;
static WebServer * instance;
void invokeMethod(RequestReason reason, WebMethod * method, CmdRequest *req);
void handleRequest(CmdRequest *req);
uint8_t remote_ip[4];
uint16_t remote_port;
char * value_ptr;
uint32_t last_connect_ts;
CmdRequestCB esplink_cb;
protected:
EspLink espLink;
public:
WebServer(Stream &stream, const WebMethod * PROGMEM methods);
void init();
void loop();
void registerCallback();
void setEspLinkCallback(CmdRequestCB cb) { esplink_cb = cb; }
static WebServer * getInstance() { return instance; }
uint8_t * getRemoteIp() { return remote_ip; }
uint16_t getRemotePort() { return remote_port; }
void setArgInt(const char * name, int32_t value);
void setArgJson(const char * name, const char * value);
void setArgString(const char * name, const char * value);
void setArgStringP(const char * name, const char * value);
void setArgBoolean(const char * name, uint8_t value);
int32_t getArgInt();
char * getArgString();
uint8_t getArgBoolean();
EspLink * getEspLink() { return &espLink; }
};
#endif /* WEB_SERVER_H */

@ -0,0 +1,35 @@
#include "EspLink.h"
#include "WebServer.h"
#include "Pages.h"
const char ledURL[] PROGMEM = "/LED.html.json";
const char userURL[] PROGMEM = "/User.html.json";
const char voltageURL[] PROGMEM = "/Voltage.html.json";
const WebMethod PROGMEM methods[] = {
{ ledURL, ledHtmlCallback },
{ userURL, userHtmlCallback },
{ voltageURL, voltageHtmlCallback },
{ NULL, NULL },
};
WebServer webServer(Serial, methods);
void setup()
{
Serial.begin(57600);
webServer.init();
ledInit();
userInit();
voltageInit();
}
void loop()
{
webServer.loop();
ledLoop();
voltageLoop();
}

@ -0,0 +1,188 @@
#include "WebServer.h"
#define LED_PIN 13
int8_t blinking = 0;
int8_t frequency = 10;
uint8_t pattern = 2;
uint16_t elapse = 100;
uint16_t elapse_delta = 200;
uint32_t next_ts = 0;
#define MAX_LOGS 5
uint32_t log_ts[MAX_LOGS];
uint8_t log_msg[MAX_LOGS];
uint8_t log_ptr = 0;
void ledInit()
{
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, false);
}
void ledLoop()
{
if( blinking )
{
if( next_ts <= millis() )
{
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
next_ts += elapse;
elapse = elapse_delta - elapse;
}
}
}
void ledAddLog(uint8_t msg)
{
if( log_ptr >= MAX_LOGS )
log_ptr = MAX_LOGS - 1;
for(int8_t i=log_ptr-1; i >= 0; i--)
{
log_ts[i+1] = log_ts[i];
log_msg[i+1] = log_msg[i];
}
log_msg[0] = msg;
log_ts[0] = millis();
log_ptr++;
}
void ledHistoryToLog(char * buf)
{
buf[0] = 0;
strcat(buf, "[");
for(uint8_t i=0; i < log_ptr; i++)
{
if( i != 0 )
strcat(buf, ",");
char bf[20];
sprintf(bf, "\"%lds: ", log_ts[i] / 1000);
strcat(buf, bf);
uint8_t msg = log_msg[i];
if( msg == 0xE1 )
{
strcat_P(buf, PSTR("set pattern to 25%-75%"));
}
else if( msg == 0xE2 )
{
strcat_P(buf, PSTR("set pattern to 50%-50%"));
}
else if( msg == 0xE3 )
{
strcat_P(buf, PSTR("set pattern to 75%-25%"));
}
else if( msg == 0xF0 )
{
strcat_P(buf, PSTR("set led on"));
}
else if( msg == 0xF1 )
{
strcat_P(buf, PSTR("set led blinking"));
}
else if( msg == 0xF2 )
{
strcat_P(buf, PSTR("set led off"));
}
else
{
strcat_P(buf, PSTR("set frequency to "));
sprintf(bf, "%d Hz", msg);
strcat(buf, bf);
}
strcat(buf, "\"");
}
strcat(buf, "]");
}
void ledHtmlCallback(WebServerCommand command, char * data, int dataLen)
{
switch(command)
{
case BUTTON_PRESS:
if( strcmp_P(data, PSTR("btn_on") ) == 0 )
{
if( blinking || digitalRead(LED_PIN) == false )
ledAddLog(0xF0);
blinking = 0;
digitalWrite(LED_PIN, true);
} else if( strcmp_P(data, PSTR("btn_off") ) == 0 )
{
if( blinking || digitalRead(LED_PIN) == true )
ledAddLog(0xF2);
blinking = 0;
digitalWrite(LED_PIN, false);
} else if( strcmp_P(data, PSTR("btn_blink") ) == 0 )
{
if( !blinking )
ledAddLog(0xF1);
blinking = 1;
next_ts = millis() + elapse;
}
break;
case SET_FIELD:
if( strcmp_P(data, PSTR("frequency") ) == 0 )
{
int8_t oldf = frequency;
frequency = webServer.getArgInt();
digitalWrite(LED_PIN, false);
elapse_delta = 2000 / frequency;
elapse = pattern * elapse_delta / 4;
if( oldf != frequency )
ledAddLog(frequency);
}
else if( strcmp_P(data, PSTR("pattern") ) == 0 )
{
int8_t oldp = pattern;
char * arg = webServer.getArgString();
if( strcmp_P(arg, PSTR("25_75")) == 0 )
pattern = 1;
else if( strcmp_P(arg, PSTR("50_50")) == 0 )
pattern = 2;
else if( strcmp_P(arg, PSTR("75_25")) == 0 )
pattern = 3;
digitalWrite(LED_PIN, false);
elapse = pattern * elapse_delta / 4;
if( oldp != pattern )
ledAddLog(0xE0 + pattern);
}
break;
case LOAD:
webServer.setArgInt("frequency", frequency);
switch(pattern)
{
case 1:
webServer.setArgStringP("pattern", PSTR("25_75"));
break;
case 2:
webServer.setArgStringP("pattern", PSTR("50_50"));
break;
case 3:
webServer.setArgStringP("pattern", PSTR("75_25"));
break;
}
case REFRESH:
{
if( blinking )
webServer.setArgStringP("text", PSTR("LED is blinking"));
else
webServer.setArgStringP("text", digitalRead(LED_PIN) ? PSTR("LED is turned on") : PSTR("LED is turned off"));
char buf[255];
ledHistoryToLog(buf);
webServer.setArgJson("led_history", buf);
}
break;
default:
break;
}
}

@ -0,0 +1,8 @@
all: user_img
clean:
rm -rf web-page.espfs.img
user_img:
../../../../../createEspFs.pl web-page web-page.espfs.img

@ -0,0 +1,17 @@
#ifndef PAGES_H
#define PAGES_H
void ledHtmlCallback(WebServerCommand command, char * data, int dataLen);
void ledLoop();
void ledInit();
void userHtmlCallback(WebServerCommand command, char * data, int dataLen);
void userInit();
void voltageHtmlCallback(WebServerCommand command, char * data, int dataLen);
void voltageLoop();
void voltageInit();
#endif /* PAGES_H */

@ -0,0 +1,84 @@
#include <EEPROM.h>
#include "WebServer.h"
#define MAGIC 0xABEF
#define MAX_STR_LEN 32
#define POS_MAGIC 0
#define POS_FIRST_NAME (POS_MAGIC + 2)
#define POS_LAST_NAME (POS_FIRST_NAME + MAX_STR_LEN)
#define POS_AGE (POS_LAST_NAME + MAX_STR_LEN)
#define POS_GENDER (POS_AGE+1)
#define POS_NOTIFICATIONS (POS_GENDER+1)
void userInit()
{
uint16_t magic;
EEPROM.get(POS_MAGIC, magic);
if( magic != MAGIC )
{
magic = MAGIC;
EEPROM.put(POS_MAGIC, magic);
EEPROM.update(POS_FIRST_NAME, 0);
EEPROM.update(POS_LAST_NAME, 0);
EEPROM.update(POS_AGE, 0);
EEPROM.update(POS_GENDER, 'f');
EEPROM.update(POS_NOTIFICATIONS, 0);
}
}
void userWriteStr(char * str, int ndx)
{
for(uint8_t i=0; i < MAX_STR_LEN-1; i++)
{
EEPROM.update(ndx + i, str[i]);
if( str[i] == 0 )
break;
}
EEPROM.update(ndx + MAX_STR_LEN - 1, 0);
}
void userReadStr(char * str, int ndx)
{
for(uint8_t i=0; i < MAX_STR_LEN; i++)
{
str[i] = EEPROM[ndx + i];
}
}
void userHtmlCallback(WebServerCommand command, char * data, int dataLen)
{
switch(command)
{
case SET_FIELD:
if( strcmp_P(data, PSTR("first_name")) == 0 )
userWriteStr(webServer.getArgString(), POS_FIRST_NAME);
if( strcmp_P(data, PSTR("last_name")) == 0 )
userWriteStr(webServer.getArgString(), POS_LAST_NAME);
if( strcmp_P(data, PSTR("age")) == 0 )
EEPROM.update(POS_AGE, (uint8_t)webServer.getArgInt());
if( strcmp_P(data, PSTR("gender")) == 0 )
EEPROM.update(POS_GENDER, (strcmp_P(webServer.getArgString(), PSTR("male")) == 0 ? 'm' : 'f'));
if( strcmp_P(data, PSTR("notifications")) == 0 )
EEPROM.update(POS_NOTIFICATIONS, (uint8_t)webServer.getArgBoolean());
break;
case LOAD:
{
char buf[MAX_STR_LEN];
userReadStr( buf, POS_FIRST_NAME );
webServer.setArgString("first_name", buf);
userReadStr( buf, POS_LAST_NAME );
webServer.setArgString("last_name", buf);
webServer.setArgInt("age", (uint8_t)EEPROM[POS_AGE]);
webServer.setArgStringP("gender", (EEPROM[POS_GENDER] == 'm') ? PSTR("male") : PSTR("female"));
webServer.setArgBoolean("notifications", EEPROM[POS_NOTIFICATIONS] != 0);
}
break;
case REFRESH:
// do nothing
break;
}
}

@ -0,0 +1,125 @@
#include "WebServer.h"
#include <avr/io.h>
#define SAMPLE_COUNT 100
#define PERIOD_COUNT (135 * SAMPLE_COUNT)
uint16_t smin = 0xFFFF;
uint16_t smax = 0;
uint32_t savg = 0;
uint16_t count;
uint32_t voltage = 0;
uint16_t measured_voltage = 0;
#define MAX_HISTORY 3
uint8_t history_cnt = 0;
uint32_t h_ts[MAX_HISTORY];
uint16_t h_min[MAX_HISTORY];
uint16_t h_max[MAX_HISTORY];
uint16_t h_avg[MAX_HISTORY];
uint16_t calibrate = 0x128; // calibrate this manually
void voltageInit()
{
analogReference(DEFAULT);
count = 0;
}
void voltageLoop()
{
uint16_t adc = analogRead(A0);
if( adc < smin )
smin = adc;
if( adc > smax )
smax = adc;
savg += adc;
voltage += adc;
count++;
if( (count % SAMPLE_COUNT) == 0 )
{
voltage /= SAMPLE_COUNT;
measured_voltage = voltage * calibrate / 256;
voltage = 0;
}
if( count == PERIOD_COUNT )
{
for(int8_t i=MAX_HISTORY-2; i >=0; i-- )
{
h_ts[i+1] = h_ts[i];
h_min[i+1] = h_min[i];
h_max[i+1] = h_max[i];
h_avg[i+1] = h_avg[i];
}
h_ts[0] = millis();
h_min[0] = (uint32_t)smin * calibrate / 256;
h_max[0] = (uint32_t)smax * calibrate / 256;
h_avg[0] = (savg / PERIOD_COUNT) * calibrate / 256;
smin = 0xFFFF;
smax = 0;
savg = 0;
if( history_cnt < MAX_HISTORY )
history_cnt++;
count = 0;
}
}
void voltageHtmlCallback(WebServerCommand command, char * data, int dataLen)
{
switch(command)
{
case BUTTON_PRESS:
// no buttons
break;
case SET_FIELD:
/* TODO */
break;
case LOAD:
case REFRESH:
{
char buf[20];
uint8_t int_part = measured_voltage / 256;
uint8_t float_part = ((measured_voltage & 255) * 100) / 256;
sprintf(buf, "%d.%02d V", int_part, float_part);
webServer.setArgString("voltage", buf);
char tab[256];
tab[0] = 0;
strcat_P(tab, PSTR("[[\"Time\",\"Min\",\"AVG\",\"Max\"]"));
for(uint8_t i=0; i < history_cnt; i++ )
{
uint8_t min_i = h_min[i] / 256;
uint8_t min_f = ((h_min[i] & 255) * 100) / 256;
uint8_t max_i = h_max[i] / 256;
uint8_t max_f = ((h_max[i] & 255) * 100) / 256;
uint8_t avg_i = h_avg[i] / 256;
uint8_t avg_f = ((h_avg[i] & 255) * 100) / 256;
sprintf(buf, ",[\"%d s\",", h_ts[i] / 1000);
strcat(tab, buf);
sprintf(buf, "\"%d.%02d V\",", min_i, min_f);
strcat(tab, buf);
sprintf(buf, "\"%d.%02d V\",", avg_i, avg_f);
strcat(tab, buf);
sprintf(buf, "\"%d.%02d V\"]", max_i, max_f);
strcat(tab, buf);
}
strcat_P(tab, PSTR("]"));
webServer.setArgJson("table", tab);
}
break;
}
}

@ -0,0 +1,36 @@
<!-- EspLink will add header here -->
<div class="header">
<h1>LED configuration</h1>
</div>
<div class="content">
<div class="pure-g"><div class="pure-u-1 pure-u-md-1-2">
<div class="card">
<h1>Control</h1>
<button id="btn_on" type="button">Turn on</button>
<button id="btn_blink" type="button">Start blinking</button>
<button id="btn_off" type="button">Turn off</button>
<p id="text"/>
</div>
<div class="card">
<h1>Frequency and pattern</h1>
<form>
<b>Pattern:</b><br/>
<input type="radio" name="pattern" value="25_75">25% on 75% off</input><br/>
<input type="radio" name="pattern" value="50_50">50% on 50% off</input><br/>
<input type="radio" name="pattern" value="75_25">75% on 25% off</input><br/>
<b>Frequency:</b><br/>
<input type="range" name="frequency" min="1" max="25"><br/>
<input type="submit">
</form>
</div></div>
<div class="pure-u-1 pure-u-md-1-2">
<div class="card" style="min-height: 400px">
<h1>Logs</h1>
<ul id="led_history"/>
</div>
</div></div>
</div>
</body></html>

@ -0,0 +1,24 @@
<!-- EspLink will add header here -->
<div class="header">
<h1>User setup</h1>
</div>
<div class="content">
<form>
First name:<br/><input name="first_name" type="text"/>
Last name:<br/><input name="last_name" type="text"/>
Age:
<input name="age" type="number"/>
Gender:
<select name="gender">
<option value="female">Female</option>
<option value="male">Male</option>
</select>
<br>
Notifications<input name="notifications" type="checkbox"/>
<br>
<input type="submit">
</form>
</div>
</body></html>

@ -0,0 +1,15 @@
<!-- EspLink will add header here -->
<meta name="refresh-rate" content="500" />
<div class="header">
<h1>Voltage measurement</h1>
</div>
<div class="content">
<p id="voltage" align="center" style="font-size: 800%"/>
<table class="pure-table pure-table-bordered" align="center" id="table"/>
</div>
</body></html>

@ -0,0 +1,49 @@
#include "EspLink.h"
#include "WebServer.h"
#define LED_PIN 13
void simpleLedHtmlCallback(WebServerCommand command, char * data, int dataLen);
const char simpleLedURL[] PROGMEM = "/SimpleLED.html.json";
const WebMethod PROGMEM methods[] = {
{ simpleLedURL, simpleLedHtmlCallback },
{ NULL, NULL },
};
WebServer webServer(Serial, methods);
void simpleLedHtmlCallback(WebServerCommand command, char * data, int dataLen)
{
switch(command)
{
case BUTTON_PRESS:
if( strcmp_P(data, PSTR("btn_on") ) == 0 )
digitalWrite(LED_PIN, true);
else if( strcmp_P(data, PSTR("btn_off") ) == 0 )
digitalWrite(LED_PIN, false);
break;
case SET_FIELD:
// no fields to set
break;
case LOAD:
case REFRESH:
if( digitalRead(LED_PIN) )
webServer.setArgString("text", "LED is on");
else
webServer.setArgString("text", "LED is off");
break;
}
}
void setup()
{
Serial.begin(57600);
webServer.init();
}
void loop()
{
webServer.loop();
}

@ -0,0 +1,7 @@
<!-- EspLink will add header here -->
<h1 class="header">Simple LED control</h1>
<h2><div id="text"/></h2>
<button id="btn_on" type="button">Turn on</button>
<button id="btn_off" type="button">Turn off</button>
</body></html>

@ -0,0 +1 @@
For information on installing libraries, see: http://www.arduino.cc/en/Guide/Libraries

@ -48,6 +48,9 @@ typedef enum {
CMD_REST_SETUP = 20,
CMD_REST_REQUEST,
CMD_REST_SETHEADER,
CMD_WEB_DATA = 30, // MCU pushes data using this command
CMD_WEB_REQ_CB, // esp-link WEB callback
} CmdName;
typedef void (*cmdfunc_t)(CmdPacket *cmd);

@ -12,6 +12,7 @@
#ifdef REST
#include <rest.h>
#endif
#include <web-server.h>
#ifdef CMD_DBG
#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
@ -47,6 +48,7 @@ const CmdList commands[] = {
{CMD_REST_REQUEST, "REST_REQ", REST_Request},
{CMD_REST_SETHEADER, "REST_SETHDR", REST_SetHeader},
#endif
{CMD_WEB_DATA, "WEB_DATA", WEB_Data},
};
//===== List of registered callbacks (to uC)

@ -0,0 +1,114 @@
#!/usr/bin/perl
use strict;
use Data::Dumper;
my $dir = shift @ARGV;
my $out = shift @ARGV;
my $espfs = '';
my @structured = read_dir_structure($dir, "");
for my $file (@structured)
{
my $flags = 0;
my $name = $file;
my $compression = 0;
if( $name =~ /\.gz$/ )
{
$flags |= 2;
$name =~ s/\.gz$//;
}
my $head = '<!doctype html><html><head><title>esp-link</title><link rel=stylesheet href="/pure.css"><link rel=stylesheet href="/style.css"><meta name=viewport content="width=device-width, initial-scale=1"><script src="/ui.js"></script><script src="/userpage.js"></script></head><body><div id=layout>';
open IF, "<", "$dir/$file" or die "Can't read file: $!";
my @fc = <IF>;
close(IF);
my $cnt = join("", @fc);
if( $name =~ /\.html$/ )
{
if( ! ( $flags & 2 ) )
{
$cnt = "$head$cnt";
}
else
{
printf("TODO: prepend headers to GZipped HTML content!\n");
}
}
$name .= chr(0);
$name .= chr(0) while( (length($name) & 3) != 0 );
my $size = length($cnt);
$espfs .= "ESfs";
$espfs .= chr($flags);
$espfs .= chr($compression);
$espfs .= chr( length($name) & 255 );
$espfs .= chr( length($name) / 256 );
$espfs .= chr( $size & 255 );
$espfs .= chr( ( $size / 0x100 ) & 255 );
$espfs .= chr( ( $size / 0x10000 ) & 255 );
$espfs .= chr( ( $size / 0x1000000 ) & 255 );
$espfs .= chr( $size & 255 );
$espfs .= chr( ( $size / 0x100 ) & 255 );
$espfs .= chr( ( $size / 0x10000 ) & 255 );
$espfs .= chr( ( $size / 0x1000000 ) & 255 );
$espfs .= $name;
$cnt .= chr(0) while( (length($cnt) & 3) != 0 );
$espfs .= $cnt;
}
$espfs .= "ESfs";
$espfs .= chr(1);
for(my $i=0; $i < 11; $i++)
{
$espfs .= chr(0);
}
open FH, ">", $out or die "Can't open file for write, $!";
print FH $espfs;
close(FH);
exit(0);
sub read_dir_structure
{
my ($dir, $base) = @_;
my @files;
opendir my $dh, $dir or die "Could not open '$dir' for reading: $!\n";
while (my $file = readdir $dh) {
if ($file eq '.' or $file eq '..') {
next;
}
my $path = "$dir/$file";
if( -d "$path" )
{
my @sd = read_dir_structure($path, "$base/$file");
push @files, @sd ;
}
else
{
push @files, "$base/$file";
}
}
close( $dh );
$_ =~ s/^\/// for(@files);
return @files;
}

@ -16,6 +16,7 @@ Some random cgi routines.
#include <esp8266.h>
#include "cgi.h"
#include "config.h"
#include "web-server.h"
#ifdef CGI_DBG
#define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
@ -193,8 +194,7 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[1024];
// don't use jsonHeader so the response does get cached
httpdStartResponse(connData, 200);
httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate");
noCacheHeaders(connData, 200);
httpdHeader(connData, "Content-Type", "application/json");
httpdEndHeaders(connData);
// limit hostname to 12 chars
@ -213,12 +213,14 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) {
#ifdef MQTT
"\"REST/MQTT\", \"/mqtt.html\", "
#endif
"\"Debug log\", \"/log.html\""
"\"Debug log\", \"/log.html\", "
"\"Web Server\", \"/web-server.html\""
"%s"
" ], "
"\"version\": \"%s\", "
"\"name\": \"%s\""
" }",
esp_link_version, name);
WEB_UserPages(), esp_link_version, name);
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;

@ -68,6 +68,7 @@ int ICACHE_FLASH_ATTR cgiSystemInfo(HttpdConnData *connData) {
"\"name\": \"%s\", "
"\"reset cause\": \"%d=%s\", "
"\"size\": \"%s\", "
"\"upload-size\": \"%d\", "
"\"id\": \"0x%02X 0x%04X\", "
"\"partition\": \"%s\", "
"\"slip\": \"%s\", "
@ -79,6 +80,7 @@ int ICACHE_FLASH_ATTR cgiSystemInfo(HttpdConnData *connData) {
rst_info->reason,
rst_codes[rst_info->reason],
flash_maps[system_get_flash_size_map()],
getUserPageSectionEnd()-getUserPageSectionStart(),
fid & 0xff, (fid & 0xff00) | ((fid >> 16) & 0xff),
part_id ? "user2.bin" : "user1.bin",
flashConfig.slip_enable ? "enabled" : "disabled",

@ -0,0 +1,158 @@
// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo
#include <esp8266.h>
#include <osapi.h>
#include "cgi.h"
#include "cgioptiboot.h"
#include "multipart.h"
#include "espfsformat.h"
#include "config.h"
#include "web-server.h"
int upload_offset = 0; // flash offset where to store page upload
int html_header_len = 0; // HTML header length (for uploading HTML files)
// this is the header to add if user uploads HTML file
const char * HTML_HEADER = "<!doctype html><html><head><title>esp-link</title>"
"<link rel=stylesheet href=\"/pure.css\"><link rel=stylesheet href=\"/style.css\">"
"<meta name=viewport content=\"width=device-width, initial-scale=1\"><script src=\"/ui.js\">"
"</script><script src=\"/userpage.js\"></script></head><body><div id=layout> ";
// multipart callback for uploading user defined pages
int ICACHE_FLASH_ATTR webServerSetupMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position)
{
switch(cmd)
{
case FILE_START:
upload_offset = 0;
html_header_len = 0;
// simple HTML file
if( ( dataLen > 5 ) && ( os_strcmp(data + dataLen - 5, ".html") == 0 ) ) // if the file ends with .html, wrap into an espfs image
{
// write the start block on esp-fs
int spi_flash_addr = getUserPageSectionStart();
spi_flash_erase_sector(spi_flash_addr/SPI_FLASH_SEC_SIZE);
EspFsHeader hdr;
hdr.magic = 0xFFFFFFFF; // espfs magic is invalid during upload
hdr.flags = 0;
hdr.compression = 0;
int len = dataLen + 1;
while(( len & 3 ) != 0 )
len++;
hdr.nameLen = len;
hdr.fileLenComp = hdr.fileLenDecomp = 0xFFFFFFFF;
spi_flash_write( spi_flash_addr + upload_offset, (uint32_t *)(&hdr), sizeof(EspFsHeader) );
upload_offset += sizeof(EspFsHeader);
char nameBuf[len];
os_memset(nameBuf, 0, len);
os_memcpy(nameBuf, data, dataLen);
spi_flash_write( spi_flash_addr + upload_offset, (uint32_t *)(nameBuf), len );
upload_offset += len;
html_header_len = os_strlen(HTML_HEADER) & ~3; // upload only 4 byte aligned part
char buf[html_header_len];
os_memcpy(buf, HTML_HEADER, html_header_len);
spi_flash_write( spi_flash_addr + upload_offset, (uint32_t *)(buf), html_header_len );
upload_offset += html_header_len;
}
break;
case FILE_DATA:
if(( position < 4 ) && (upload_offset == 0)) // for espfs images check the magic number
{
for(int p = position; p < 4; p++ )
{
if( data[p - position] != ((ESPFS_MAGIC >> (p * 8) ) & 255 ) )
{
os_printf("Not an espfs image!\n");
return 1;
}
data[p - position] = 0xFF; // espfs magic is invalid during upload
}
}
int spi_flash_addr = getUserPageSectionStart() + upload_offset + position;
int spi_flash_end_addr = spi_flash_addr + dataLen;
if( spi_flash_end_addr + dataLen >= getUserPageSectionEnd() )
{
os_printf("No more space in the flash!\n");
return 1;
}
int ptr = 0;
while( spi_flash_addr < spi_flash_end_addr )
{
if (spi_flash_addr % SPI_FLASH_SEC_SIZE == 0){
spi_flash_erase_sector(spi_flash_addr/SPI_FLASH_SEC_SIZE);
}
int max = (spi_flash_addr | (SPI_FLASH_SEC_SIZE - 1)) + 1;
int len = spi_flash_end_addr - spi_flash_addr;
if( spi_flash_end_addr > max )
len = max - spi_flash_addr;
spi_flash_write( spi_flash_addr, (uint32_t *)(data + ptr), len );
ptr += len;
spi_flash_addr += len;
}
break;
case FILE_DONE:
{
if( html_header_len != 0 )
{
// write the terminating block on esp-fs
int spi_flash_addr = getUserPageSectionStart() + upload_offset + position;
uint32_t pad = 0;
uint8_t pad_cnt = (4 - position) & 3;
if( pad_cnt )
spi_flash_write( spi_flash_addr, &pad, pad_cnt );
spi_flash_addr += pad_cnt;
// create ESPFS image
EspFsHeader hdr;
hdr.magic = ESPFS_MAGIC;
hdr.flags = 1;
hdr.compression = 0;
hdr.nameLen = 0;
hdr.fileLenComp = hdr.fileLenDecomp = 0;
spi_flash_write( spi_flash_addr, (uint32_t *)(&hdr), sizeof(EspFsHeader) );
uint32_t totallen = html_header_len + position;
// restore ESPFS magic
spi_flash_write( (int)getUserPageSectionStart(), (uint32_t *)&hdr.magic, sizeof(uint32_t) );
// set file size
spi_flash_write( (int)getUserPageSectionStart() + 8, &totallen, sizeof(uint32_t) );
spi_flash_write( (int)getUserPageSectionStart() + 12, &totallen, sizeof(uint32_t) );
}
else
{
// set espfs magic (set it valid)
uint32_t magic = ESPFS_MAGIC;
spi_flash_write( (int)getUserPageSectionStart(), (uint32_t *)&magic, sizeof(uint32_t) );
}
WEB_Init(); // reload the content
}
break;
}
return 0;
}
MultipartCtx * webServerContext = NULL; // multipart upload context for web server
// this callback is called when user uploads the web-page
int ICACHE_FLASH_ATTR cgiWebServerSetupUpload(HttpdConnData *connData)
{
if( webServerContext == NULL )
webServerContext = multipartCreateContext( webServerSetupMultipartCallback );
return multipartProcess(webServerContext, connData);
}

@ -0,0 +1,8 @@
#ifndef CGIWEBSERVER_H
#define CGIWEBSERVER_H
#include <httpd.h>
int ICACHE_FLASH_ATTR cgiWebServerSetupUpload(HttpdConnData *connData);
#endif /* CGIWEBSERVER_H */

@ -195,3 +195,43 @@ getFlashSize() {
return 0;
return 1 << size_id;
}
const uint32_t getUserPageSectionStart()
{
enum flash_size_map map = system_get_flash_size_map();
switch(map)
{
case FLASH_SIZE_4M_MAP_256_256:
return FLASH_SECT + FIRMWARE_SIZE - 3*FLASH_SECT;// bootloader + firmware - 12KB (highly risky...)
case FLASH_SIZE_8M_MAP_512_512:
return FLASH_SECT + FIRMWARE_SIZE;
case FLASH_SIZE_16M_MAP_512_512:
case FLASH_SIZE_16M_MAP_1024_1024:
case FLASH_SIZE_32M_MAP_512_512:
case FLASH_SIZE_32M_MAP_1024_1024:
return 0x100000;
default:
return 0xFFFFFFFF;
}
}
const uint32_t getUserPageSectionEnd()
{
enum flash_size_map map = system_get_flash_size_map();
switch(map)
{
case FLASH_SIZE_4M_MAP_256_256:
return FLASH_SECT + FIRMWARE_SIZE - 2*FLASH_SECT;
case FLASH_SIZE_8M_MAP_512_512:
return FLASH_SECT + FIRMWARE_SIZE + 2*FLASH_SECT;
case FLASH_SIZE_16M_MAP_512_512:
case FLASH_SIZE_16M_MAP_1024_1024:
return 0x1FC000;
case FLASH_SIZE_32M_MAP_512_512:
case FLASH_SIZE_32M_MAP_1024_1024:
return 0x3FC000;
default:
return 0xFFFFFFFF;
}
}

@ -46,4 +46,7 @@ bool configRestore(void);
void configWipe(void);
const size_t getFlashSize();
const uint32_t getUserPageSectionStart();
const uint32_t getUserPageSectionEnd();
#endif

@ -19,6 +19,7 @@
#include "cgimqtt.h"
#include "cgiflash.h"
#include "cgioptiboot.h"
#include "cgiwebserversetup.h"
#include "auth.h"
#include "espfs.h"
#include "uart.h"
@ -30,6 +31,7 @@
#include "log.h"
#include "gpio.h"
#include "cgiservices.h"
#include "web-server.h"
#ifdef SYSLOG
#include "syslog.h"
@ -96,6 +98,8 @@ HttpdBuiltInUrl builtInUrls[] = {
#ifdef MQTT
{ "/mqtt", cgiMqtt, NULL },
#endif
{ "/web-server/upload", cgiWebServerSetupUpload, NULL },
{ "*.json", WEB_CgiJsonHook, NULL }, //Catch-all cgi JSON queries
{ "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem
{ NULL, NULL, NULL }
};
@ -147,11 +151,14 @@ void user_init(void) {
// Wifi
wifiInit();
// init the flash filesystem with the html stuff
espFsInit(&_binary_espfs_img_start);
espFsInit(espLinkCtx, &_binary_espfs_img_start, ESPFS_MEMORY);
//EspFsInitResult res = espFsInit(&_binary_espfs_img_start);
//os_printf("espFsInit %s\n", res?"ERR":"ok");
// mount the http handlers
httpdInit(builtInUrls, 80);
WEB_Init();
// init the wifi-serial transparent bridge (port 23)
serbridgeInit(23, 2323);
uart_add_recv_cb(&serbridgeUartCb);

@ -30,6 +30,7 @@ It's written for use with httpd, but doesn't need to be used as such.
#define os_malloc malloc
#define os_free free
#define os_memcpy memcpy
#define os_memset memset
#define os_strncmp strncmp
#define os_strcmp strcmp
#define os_strcpy strcpy
@ -40,9 +41,21 @@ It's written for use with httpd, but doesn't need to be used as such.
#include "espfsformat.h"
#include "espfs.h"
static char* espFsData = NULL;
EspFsContext espLinkCtxDef;
EspFsContext userPageCtxDef;
EspFsContext * espLinkCtx = &espLinkCtxDef;
EspFsContext * userPageCtx = &userPageCtxDef;
struct EspFsContext
{
char* data;
EspFsSource source;
uint8_t valid;
};
struct EspFsFile {
EspFsContext *ctx;
EspFsHeader *header;
char decompressor;
int32_t posDecomp;
@ -67,29 +80,12 @@ Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All
a memory exception, crashing the program.
*/
EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) {
// base address must be aligned to 4 bytes
if (((int)flashAddress & 3) != 0) {
return ESPFS_INIT_RESULT_BAD_ALIGN;
}
// check if there is valid header at address
EspFsHeader testHeader;
os_memcpy(&testHeader, flashAddress, sizeof(EspFsHeader));
if (testHeader.magic != ESPFS_MAGIC) {
return ESPFS_INIT_RESULT_NO_IMAGE;
}
espFsData = (char *)flashAddress;
return ESPFS_INIT_RESULT_OK;
}
//Copies len bytes over from dst to src, but does it using *only*
//aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works.
//ToDo: perhaps os_memcpy also does unaligned accesses?
#ifdef __ets__
void ICACHE_FLASH_ATTR memcpyAligned(char *dst, char *src, int len) {
void ICACHE_FLASH_ATTR memcpyAligned(char *dst, const char *src, int len) {
int x;
int w, b;
for (x=0; x<len; x++) {
@ -106,6 +102,51 @@ void ICACHE_FLASH_ATTR memcpyAligned(char *dst, char *src, int len) {
#define memcpyAligned memcpy
#endif
void ICACHE_FLASH_ATTR memcpyFromFlash(char *dst, const char *src, int len)
{
if( spi_flash_read( (int)src, (void *)dst, len ) != SPI_FLASH_RESULT_OK )
os_memset( dst, 0, len ); // if read was not successful, reply with zeroes
}
// memcpy on MEMORY/FLASH file systems
void espfs_memcpy( EspFsContext * ctx, void * dest, const void * src, int count )
{
if( ctx->source == ESPFS_MEMORY )
os_memcpy( dest, src, count );
else
memcpyFromFlash(dest, src, count);
}
// aligned memcpy on MEMORY/FLASH file systems
void espfs_memcpyAligned( EspFsContext * ctx, void * dest, const void * src, int count )
{
if( ctx->source == ESPFS_MEMORY )
memcpyAligned(dest, src, count);
else
memcpyFromFlash(dest, src, count);
}
// initializes an EspFs context
EspFsInitResult ICACHE_FLASH_ATTR espFsInit(EspFsContext *ctx, void *flashAddress, EspFsSource source) {
ctx->valid = 0;
ctx->source = source;
// base address must be aligned to 4 bytes
if (((int)flashAddress & 3) != 0) {
return ESPFS_INIT_RESULT_BAD_ALIGN;
}
// check if there is valid header at address
EspFsHeader testHeader;
espfs_memcpy(ctx, &testHeader, flashAddress, sizeof(EspFsHeader));
if (testHeader.magic != ESPFS_MAGIC) {
return ESPFS_INIT_RESULT_NO_IMAGE;
}
ctx->data = (char *)flashAddress;
ctx->valid = 1;
return ESPFS_INIT_RESULT_OK;
}
// Returns flags of opened file.
int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) {
if (fh == NULL) {
@ -116,57 +157,93 @@ int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) {
}
int8_t flags;
memcpyAligned((char*)&flags, (char*)&fh->header->flags, 1);
espfs_memcpyAligned(fh->ctx, (char*)&flags, (char*)&fh->header->flags, 1);
return (int)flags;
}
// creates and initializes an iterator over the espfs file system
void ICACHE_FLASH_ATTR espFsIteratorInit(EspFsContext *ctx, EspFsIterator *iterator)
{
if( ctx->data == NULL )
{
iterator->ctx = NULL;
return;
}
iterator->ctx = ctx;
iterator->position = NULL;
}
// moves iterator to the next file on espfs
// returns 1 if iterator move was successful, otherwise 0 (last file)
// iterator->header and iterator->name will contain file information
int ICACHE_FLASH_ATTR espFsIteratorNext(EspFsIterator *iterator)
{
if( iterator->ctx == NULL )
return 0;
char * position = iterator->position;
if( position == NULL )
position = iterator->ctx->data; // first node
else
{
// jump the iterator to the next file
position+=sizeof(EspFsHeader) + iterator->header.nameLen+iterator->header.fileLenComp;
if ((int)position&3) position+=4-((int)position&3); //align to next 32bit val
}
iterator->position = position;
EspFsHeader * hdr = &iterator->header;
espfs_memcpy(iterator->ctx, hdr, position, sizeof(EspFsHeader));
if (hdr->magic!=ESPFS_MAGIC) {
#ifdef ESPFS_DBG
os_printf("Magic mismatch. EspFS image broken.\n");
#endif
return 0;
}
if (hdr->flags&FLAG_LASTFILE) {
//os_printf("End of image.\n");
iterator->ctx = NULL; // invalidate the iterator
return 0;
}
position += sizeof(EspFsHeader);
//Grab the name of the file.
espfs_memcpy(iterator->ctx, iterator->name, position, sizeof(iterator->name));
return 1;
}
//Open a file and return a pointer to the file desc struct.
EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) {
if (espFsData == NULL) {
EspFsFile ICACHE_FLASH_ATTR *espFsOpen(EspFsContext *ctx, char *fileName) {
EspFsIterator it;
espFsIteratorInit(ctx, &it);
if (it.ctx == NULL) {
#ifdef ESPFS_DBG
os_printf("Call espFsInit first!\n");
#endif
return NULL;
}
char *p=espFsData;
char *hpos;
char namebuf[256];
EspFsHeader h;
EspFsFile *r;
//Strip initial slashes
while(fileName[0]=='/') fileName++;
//Go find that file!
while(1) {
hpos=p;
//Grab the next file header.
os_memcpy(&h, p, sizeof(EspFsHeader));
if (h.magic!=ESPFS_MAGIC) {
#ifdef ESPFS_DBG
os_printf("Magic mismatch. EspFS image broken.\n");
#endif
return NULL;
}
if (h.flags&FLAG_LASTFILE) {
//os_printf("End of image.\n");
return NULL;
}
//Grab the name of the file.
p+=sizeof(EspFsHeader);
os_memcpy(namebuf, p, sizeof(namebuf));
// os_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n",
// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags);
if (os_strcmp(namebuf, fileName)==0) {
//Search the file
while( espFsIteratorNext(&it) )
{
if (os_strcmp(it.name, fileName)==0) {
//Yay, this is the file we need!
p+=h.nameLen; //Skip to content.
r=(EspFsFile *)os_malloc(sizeof(EspFsFile)); //Alloc file desc mem
EspFsFile * r=(EspFsFile *)os_malloc(sizeof(EspFsFile)); //Alloc file desc mem
//os_printf("Alloc %p[%d]\n", r, sizeof(EspFsFile));
if (r==NULL) return NULL;
r->header=(EspFsHeader *)hpos;
r->decompressor=h.compression;
r->posComp=p;
r->posStart=p;
r->ctx = ctx;
r->header=(EspFsHeader *)it.position;
r->decompressor=it.header.compression;
r->posComp=it.position + it.header.nameLen + sizeof(EspFsHeader);
r->posStart=it.position + it.header.nameLen + sizeof(EspFsHeader);
r->posDecomp=0;
if (h.compression==COMPRESS_NONE) {
if (it.header.compression==COMPRESS_NONE) {
r->decompData=NULL;
} else {
#ifdef ESPFS_DBG
@ -176,10 +253,8 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) {
}
return r;
}
//We don't need this file. Skip name and file
p+=h.nameLen+h.fileLenComp;
if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val
}
return NULL;
}
//Read len bytes from the given file into buff. Returns the actual amount of bytes read.
@ -187,15 +262,15 @@ int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) {
int flen, fdlen;
if (fh==NULL) return 0;
//Cache file length.
memcpyAligned((char*)&flen, (char*)&fh->header->fileLenComp, 4);
memcpyAligned((char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4);
espfs_memcpyAligned(fh->ctx, (char*)&flen, (char*)&fh->header->fileLenComp, 4);
espfs_memcpyAligned(fh->ctx, (char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4);
//Do stuff depending on the way the file is compressed.
if (fh->decompressor==COMPRESS_NONE) {
int toRead;
toRead=flen-(fh->posComp-fh->posStart);
if (len>toRead) len=toRead;
// os_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp);
memcpyAligned(buff, fh->posComp, len);
espfs_memcpyAligned(fh->ctx, buff, fh->posComp, len);
fh->posDecomp+=len;
fh->posComp+=len;
// os_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp);
@ -211,5 +286,8 @@ void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) {
os_free(fh);
}
// checks if the file system is valid (detect if the content is an espfs image or random data)
int ICACHE_FLASH_ATTR espFsIsValid(EspFsContext *ctx) {
return ctx->valid;
}

@ -1,19 +1,42 @@
#ifndef ESPFS_H
#define ESPFS_H
#include "espfsformat.h"
typedef enum {
ESPFS_INIT_RESULT_OK,
ESPFS_INIT_RESULT_NO_IMAGE,
ESPFS_INIT_RESULT_BAD_ALIGN,
} EspFsInitResult;
// Only 1 MByte of the flash can be directly accessed with ESP8266
// If flash size is >1 Mbyte, SDK API is required to retrieve flash content
typedef enum {
ESPFS_MEMORY, // read data directly from memory (fast, max 1 MByte)
ESPFS_FLASH, // read data from flash using SDK API (no limit for the size)
} EspFsSource;
typedef struct EspFsFile EspFsFile;
typedef struct EspFsContext EspFsContext;
typedef struct {
EspFsHeader header; // the header of the current file
EspFsContext *ctx; // pointer to espfs context
char name[256]; // the name of the current file
char *position; // position of the iterator (pointer on the file system)
} EspFsIterator;
extern EspFsContext * espLinkCtx;
extern EspFsContext * userPageCtx;
EspFsInitResult espFsInit(void *flashAddress);
EspFsFile *espFsOpen(char *fileName);
EspFsInitResult espFsInit(EspFsContext *ctx, void *flashAddress, EspFsSource source);
EspFsFile *espFsOpen(EspFsContext *ctx, char *fileName);
int espFsIsValid(EspFsContext *ctx);
int espFsFlags(EspFsFile *fh);
int espFsRead(EspFsFile *fh, char *buff, int len);
void espFsClose(EspFsFile *fh);
void espFsIteratorInit(EspFsContext *ctx, EspFsIterator *iterator);
int espFsIteratorNext(EspFsIterator *iterator);
#endif

@ -123,6 +123,12 @@
<div class="popup pop-left">Size configured into bootloader, must match chip size</div>
</div>
</td></tr>
<tr><td>Webpage size</td><td>
<div>
<span class="system-upload-size"></span>
<div class="popup pop-left">The maximal size of the custom web page a user can upload.</div>
</div>
</td></tr>
<tr><td>Current partition</td><td class="system-partition"></td></tr>
<tr><td colspan=2 class="popup-target">Description:<br>
<div class="click-to-edit system-description">

@ -0,0 +1,242 @@
//===== Java script for user pages
var loadCounter = 0;
var refreshRate = 0;
var refreshTimer;
var hiddenInputs = [];
function notifyResponse( data )
{
Object.keys(data).forEach(function(v) {
var elems = document.getElementsByName(v);
var ndx;
for(ndx = 0; ndx < elems.length; ndx++ )
{
var el = elems[ndx];
if(el.tagName == "INPUT")
{
if( el.type == "radio" )
{
el.checked = data[v] == el.value;
}
else if( el.type == "checkbox" )
{
if( data[v] == "on" )
el.checked = true;
else if( data[v] == "off" )
el.checked = false;
else if( data[v] == true )
el.checked = true;
else
el.checked = false;
}
else
{
el.value = data[v];
}
}
if(el.tagName == "SELECT")
{
el.value = data[v];
}
}
var elem = document.getElementById(v);
if( elem != null )
{
if(elem.tagName == "P" || elem.tagName == "DIV" || elem.tagName == "SPAN" || elem.tagName == "TR" || elem.tagName == "TH" || elem.tagName == "TD" ||
elem.tagName == "TEXTAREA" )
{
elem.innerHTML = data[v];
}
if(elem.tagName == "UL" || elem.tagName == "OL")
{
var list = data[v];
var html = "";
for (var i=0; i<list.length; i++) {
html = html.concat("<li>" + list[i] + "</li>");
}
elem.innerHTML = html;
}
if(elem.tagName == "TABLE")
{
var list = data[v];
var html = "";
if( list.length > 0 )
{
var ths = list[0];
html = html.concat("<tr>");
for (var i=0; i<ths.length; i++) {
html = html.concat("<th>" + ths[i] + "</th>");
}
html = html.concat("</tr>");
}
for (var i=1; i<list.length; i++) {
var tds = list[i];
html = html.concat("<tr>");
for (var j=0; j<tds.length; j++) {
html = html.concat("<td>" + tds[j] + "</td>");
}
html = html.concat("</tr>");
}
elem.innerHTML = html;
}
}
});
if( refreshRate != 0 )
{
clearTimeout(refreshTimer);
refreshTimer = setTimeout( function () {
ajaxJson("GET", window.location.pathname + ".json?reason=refresh", notifyResponse );
}, refreshRate );
}
}
function notifyButtonPressed( btnId )
{
ajaxJson("POST", window.location.pathname + ".json?reason=button\&id=" + btnId, notifyResponse);
}
function refreshFormData()
{
setTimeout( function () {
ajaxJson("GET", window.location.pathname + ".json?reason=refresh", function (resp) {
notifyResponse(resp);
if( loadCounter > 0 )
{
loadCounter--;
refreshFormData();
}
} );
} , 250);
}
function recalculateHiddenInputs()
{
for(var i=0; i < hiddenInputs.length; i++)
{
var hinput = hiddenInputs[i];
var name = hinput.name;
var elems = document.getElementsByName(name);
for(var j=0; j < elems.length; j++ )
{
var chk = elems[j];
var inptp = chk.type;
if( inptp == "checkbox" ) {
if( chk.checked )
{
hinput.disabled = true;
hinput.value = "on";
}
else
{
hinput.disabled = false;
hinput.value = "off";
}
}
}
}
}
document.addEventListener("DOMContentLoaded", function(){
// collect buttons
var btns = document.getElementsByTagName("button");
var ndx;
for (ndx = 0; ndx < btns.length; ndx++) {
var btn = btns[ndx];
var id = btn.getAttribute("id");
var onclk = btn.getAttribute("onclick");
var type = btn.getAttribute("type");
if( id != null && onclk == null && type == "button" )
{
var fn;
eval( "fn = function() { notifyButtonPressed(\"" + id + "\") }" );
btn.onclick = fn;
}
}
// collect forms
var frms = document.getElementsByTagName("form");
for (ndx = 0; ndx < frms.length; ndx++) {
var frm = frms[ndx];
var method = frm.method;
var action = frm.action;
frm.method = "POST";
frm.action = window.location.pathname + ".json?reason=submit";
loadCounter = 4;
frm.onsubmit = function () {
recalculateHiddenInputs();
refreshFormData();
return true;
};
}
// collect metas
var metas = document.getElementsByTagName("meta");
for (ndx = 0; ndx < metas.length; ndx++) {
var meta = metas[ndx];
if( meta.getAttribute("name") == "refresh-rate" )
{
refreshRate = meta.getAttribute("content");
}
}
// collect checkboxes
var inputs = document.getElementsByTagName("input");
for (ndx = 0; ndx < inputs.length; ndx++) {
var inp = inputs[ndx];
if( inp.getAttribute("type") == "checkbox" )
{
var name = inp.getAttribute("name");
var hasHidden = false;
if( name != null )
{
var inpelems = document.getElementsByName(name);
for(var i=0; i < inpelems.length; i++ )
{
var inptp = inpelems[i].type;
if( inptp == "hidden" )
hasHidden = true;
}
}
if( !hasHidden )
{
var parent = inp.parentElement;
var input = document.createElement("input");
input.type = "hidden";
input.name = inp.name;
parent.appendChild(input);
hiddenInputs.push(input);
}
}
}
// load variables at first time
var loadVariables = function() {
ajaxJson("GET", window.location.pathname + ".json?reason=load", notifyResponse,
function () { setTimeout(loadVariables, 1000); }
);
};
loadVariables();
});

@ -0,0 +1,29 @@
<div id="main">
<div class="header">
<h1>Web Server</h1>
</div>
<div class="content">
<p>User defined web pages can be uploaded to esp-link. This is useful if esp-link acts as a web server while MCU provides
the measurement data.</p>
<form method="post" action="web-server/upload" name="submit" enctype="multipart/form-data" onSubmit="return onSubmit()">
The custom web page to upload: <input type="file" name="webpage">
<input type="submit" name="submit" value="Submit">
</form>
</div>
</div>
<script>
var allowSubmit = true;
function onSubmit() {
setTimeout(function() {
window.location.reload();
}, 1000);
return true;
}
</script>
</body></html>

@ -132,6 +132,7 @@ static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) {
if (conn->post->buff != NULL) os_free(conn->post->buff);
conn->cgi = NULL;
conn->post->buff = NULL;
conn->post->multipartBoundary = NULL;
}
//Stupid li'l helper function that returns the value of a hex char.
@ -354,14 +355,18 @@ static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) {
if (conn->cgi == NULL) {
while (builtInUrls[i].url != NULL) {
int match = 0;
int urlLen = os_strlen(builtInUrls[i].url);
//See if there's a literal match
if (os_strcmp(builtInUrls[i].url, conn->url) == 0) match = 1;
//See if there's a wildcard match
if (builtInUrls[i].url[os_strlen(builtInUrls[i].url) - 1] == '*' &&
os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url) - 1) == 0) match = 1;
if (builtInUrls[i].url[urlLen - 1] == '*' &&
os_strncmp(builtInUrls[i].url, conn->url, urlLen - 1) == 0) match = 1;
else if (builtInUrls[i].url[0] == '*' && ( strlen(conn->url) >= urlLen -1 ) &&
os_strncmp(builtInUrls[i].url + 1, conn->url + strlen(conn->url) - urlLen + 1, urlLen - 1) == 0) match = 1;
if (match) {
//os_printf("Is url index %d\n", i);
conn->cgiData = NULL;
conn->cgiResponse = NULL;
conn->cgi = builtInUrls[i].cgiCb;
conn->cgiArg = builtInUrls[i].cgiArg;
break;
@ -509,6 +514,7 @@ static void ICACHE_FLASH_ATTR httpdRecvCb(void *arg, char *data, unsigned short
if (data[x] == '\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n") != NULL) {
//Indicate we're done with the headers.
conn->post->len = 0;
conn->post->multipartBoundary = NULL;
//Reset url data
conn->url = NULL;
//Iterate over all received headers and parse them.
@ -621,3 +627,43 @@ void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) {
espconn_accept(&httpdConn);
espconn_tcp_set_max_con_allow(&httpdConn, MAX_CONN);
}
// looks up connection handle based on ip / port
HttpdConnData * ICACHE_FLASH_ATTR httpdLookUpConn(uint8_t * ip, int port) {
int i;
for (i = 0; i<MAX_CONN; i++)
{
HttpdConnData *conn = connData+i;
if (conn->conn == NULL)
continue;
if (conn->cgi == NULL)
continue;
if (conn->conn->proto.tcp->remote_port != port )
continue;
if (os_memcmp(conn->conn->proto.tcp->remote_ip, ip, 4) != 0)
continue;
return conn;
}
return NULL;
}
// this method is used for setting the response of a CGI handler outside of the HTTP callback
// this method useful at the following scenario:
// Browser -> CGI handler -> MCU request
// MCU response -> CGI handler -> browser
// when MCU response arrives, the handler looks up connection based on ip/port and call httpdSetCGIResponse with the data to transmit
int ICACHE_FLASH_ATTR httpdSetCGIResponse(HttpdConnData * conn, void * response) {
char sendBuff[MAX_SENDBUFF_LEN];
conn->priv->sendBuff = sendBuff;
conn->priv->sendBuffLen = 0;
conn->cgiResponse = response;
httpdProcessRequest(conn);
conn->cgiResponse = NULL;
return HTTPD_CGI_DONE;
}

@ -30,6 +30,7 @@ struct HttpdConnData {
const void *cgiArg;
void *cgiData;
void *cgiPrivData; // Used for streaming handlers storing state between requests
void *cgiResponse; // used for forwarding response to the CGI handler
HttpdPriv *priv;
cgiSendCallback cgi;
HttpdPostData *post;
@ -66,5 +67,7 @@ void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn);
int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen);
int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len);
void ICACHE_FLASH_ATTR httpdFlush(HttpdConnData *conn);
HttpdConnData * ICACHE_FLASH_ATTR httpdLookUpConn(uint8_t * ip, int port);
int ICACHE_FLASH_ATTR httpdSetCGIResponse(HttpdConnData * conn, void *response);
#endif

@ -14,11 +14,12 @@ Connector to let httpd use the espfs filesystem to serve the files in it.
*/
#include "httpdespfs.h"
#define MAX_URL_LEN 255
// 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.)
static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n";
//This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding
//path in the filesystem and if it exists, passes the file through. This simulates what a normal
//webserver would do with static files.
@ -40,9 +41,21 @@ cgiEspFsHook(HttpdConnData *connData) {
if (file==NULL) {
//First call to this cgi. Open the file so we can read it.
file=espFsOpen(connData->url);
file=espFsOpen(espLinkCtx, connData->url);
if (file==NULL) {
return HTTPD_CGI_NOTFOUND;
if( espFsIsValid(userPageCtx) )
{
int maxLen = strlen(connData->url) * 2 + 1;
if( maxLen > MAX_URL_LEN )
maxLen = MAX_URL_LEN;
char decodedURL[maxLen];
httpdUrlDecode(connData->url, strlen(connData->url), decodedURL, maxLen);
file = espFsOpen(userPageCtx, decodedURL );
if( file == NULL )
return HTTPD_CGI_NOTFOUND;
}
else
return HTTPD_CGI_NOTFOUND;
}
// The gzip checking code is intentionally without #ifdefs because checking

@ -0,0 +1,301 @@
#include <esp8266.h>
#include <osapi.h>
#include "multipart.h"
#include "cgi.h"
#define BOUNDARY_SIZE 100
typedef enum {
STATE_SEARCH_BOUNDARY = 0, // state: searching multipart boundary
STATE_SEARCH_HEADER, // state: search multipart file header
STATE_SEARCH_HEADER_END, // state: search the end of the file header
STATE_UPLOAD_FILE, // state: read file content
STATE_ERROR, // state: error (stop processing)
} MultipartState;
struct _MultipartCtx {
MultipartCallback callBack; // callback for multipart events
int position; // current file position
int startTime; // timestamp when connection was initiated
int recvPosition; // receive position (how many bytes was processed from the HTTP post)
char * boundaryBuffer; // buffer used for boundary detection
int boundaryBufferPtr; // pointer in the boundary buffer
MultipartState state; // multipart processing state
};
// this method is responsible for creating the multipart context
MultipartCtx * ICACHE_FLASH_ATTR multipartCreateContext(MultipartCallback callback)
{
MultipartCtx * ctx = (MultipartCtx *)os_malloc(sizeof(MultipartCtx));
ctx->callBack = callback;
ctx->position = ctx->startTime = ctx->recvPosition = ctx->boundaryBufferPtr = 0;
ctx->boundaryBuffer = NULL;
ctx->state = STATE_SEARCH_BOUNDARY;
return ctx;
}
// for allocating buffer for multipart upload
void ICACHE_FLASH_ATTR multipartAllocBoundaryBuffer(MultipartCtx * context)
{
if( context->boundaryBuffer == NULL )
context->boundaryBuffer = (char *)os_malloc(3*BOUNDARY_SIZE + 1);
context->boundaryBufferPtr = 0;
}
// for freeing multipart buffer
void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context)
{
if( context->boundaryBuffer != NULL )
{
os_free(context->boundaryBuffer);
context->boundaryBuffer = NULL;
}
}
// for destroying the context
void ICACHE_FLASH_ATTR multipartDestroyContext(MultipartCtx * context)
{
multipartFreeBoundaryBuffer(context);
os_free(context);
}
// this is because of os_memmem is missing
void * mp_memmem(const void *l, size_t l_len, const void *s, size_t s_len)
{
register char *cur, *last;
const char *cl = (const char *)l;
const char *cs = (const char *)s;
/* we need something to compare */
if (l_len == 0 || s_len == 0)
return NULL;
/* "s" must be smaller or equal to "l" */
if (l_len < s_len)
return NULL;
/* special case where s_len == 1 */
if (s_len == 1)
return memchr(l, (int)*cs, l_len);
/* the last position where its possible to find "s" in "l" */
last = (char *)cl + l_len - s_len;
for (cur = (char *)cl; cur <= last; cur++)
if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
return cur;
return NULL;
}
// this method is for processing data coming from the HTTP post request
// context: the multipart context
// boundary: a string which indicates boundary
// data: the received data
// len: the received data length (can't be bigger than BOUNDARY_SIZE)
// last: last packet indicator
//
// Detecting a boundary is not easy. One has to take care of boundaries which are splitted in 2 packets
// [Packet 1, 5 bytes of the boundary][Packet 2, remaining 10 bytes of the boundary];
//
// Algorythm:
// - create a buffer which size is 3*BOUNDARY_SIZE
// - put data into the buffer as long as the buffer size is smaller than 2*BOUNDARY_SIZE
// - search boundary in the received buffer, if found: boundary reached -> process data before boundary -> process boundary
// - if not found -> process the first BOUNDARY_SIZE amount of bytes from the buffer
// - remove processed data from the buffer
// this algorythm guarantees that no boundary loss will happen
int ICACHE_FLASH_ATTR multipartProcessData(MultipartCtx * context, char * boundary, char * data, int len, int last)
{
if( len != 0 ) // add data to the boundary buffer
{
os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, data, len);
context->boundaryBufferPtr += len;
context->boundaryBuffer[context->boundaryBufferPtr] = 0;
}
while( context->boundaryBufferPtr > 0 )
{
if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) // return if buffer is too small and not the last packet is processed
return 0;
int dataSize = BOUNDARY_SIZE;
char * boundaryLoc = mp_memmem( context->boundaryBuffer, context->boundaryBufferPtr, boundary, os_strlen(boundary) );
if( boundaryLoc != NULL )
{
int pos = boundaryLoc - context->boundaryBuffer;
if( pos > BOUNDARY_SIZE ) // process in the next call
boundaryLoc = NULL;
else
dataSize = pos;
}
if( dataSize != 0 ) // data to process
{
switch( context->state )
{
case STATE_SEARCH_HEADER:
case STATE_SEARCH_HEADER_END:
{
char * chr = os_strchr( context->boundaryBuffer, '\n' );
if( chr != NULL )
{
// chop datasize to contain only one line
int pos = chr - context->boundaryBuffer + 1;
if( pos < dataSize ) // if chop smaller than the dataSize, delete the boundary
{
dataSize = pos;
boundaryLoc = NULL; // process boundary next time
}
if( context->state == STATE_SEARCH_HEADER_END )
{
if( pos == 1 || ( ( pos == 2 ) && ( context->boundaryBuffer[0] == '\r' ) ) ) // empty line?
{
context->state = STATE_UPLOAD_FILE;
context->position = 0;
}
}
else if( os_strncmp( context->boundaryBuffer, "Content-Disposition:", 20 ) == 0 )
{
char * fnam = os_strstr( context->boundaryBuffer, "filename=" );
if( fnam != NULL )
{
int pos = fnam - context->boundaryBuffer + 9;
if( pos < dataSize )
{
while(context->boundaryBuffer[pos] == ' ') pos++; // skip spaces
if( context->boundaryBuffer[pos] == '"' ) // quote start
{
pos++;
int start = pos;
while( pos < context->boundaryBufferPtr )
{
if( context->boundaryBuffer[pos] == '"' ) // quote end
break;
pos++;
}
if( pos < context->boundaryBufferPtr )
{
context->boundaryBuffer[pos] = 0; // terminating zero for the file name
os_printf("Uploading file: %s\n", context->boundaryBuffer + start);
if( context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ) ) // FILE_START callback
return 1; // if an error happened
context->boundaryBuffer[pos] = '"'; // restore the original quote
context->state = STATE_SEARCH_HEADER_END;
}
}
}
}
}
}
}
break;
case STATE_UPLOAD_FILE:
{
char c = context->boundaryBuffer[dataSize];
context->boundaryBuffer[dataSize] = 0; // add terminating zero (for easier handling)
if( context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ) ) // FILE_DATA callback
return 1;
context->boundaryBuffer[dataSize] = c;
context->position += dataSize;
}
break;
default:
break;
}
}
if( boundaryLoc != NULL ) // boundary found?
{
dataSize += os_strlen(boundary); // jump over the boundary
if( context->state == STATE_UPLOAD_FILE )
{
if( context->callBack( FILE_DONE, NULL, 0, context->position ) ) // file done callback
return 1; // if an error happened
os_printf("File upload done\n");
}
context->state = STATE_SEARCH_HEADER; // search the next header
}
// move the buffer back with dataSize
context->boundaryBufferPtr -= dataSize;
os_memcpy(context->boundaryBuffer, context->boundaryBuffer + dataSize, context->boundaryBufferPtr);
}
return 0;
}
// for processing multipart requests
int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * connData )
{
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
if (connData->requestType == HTTPD_METHOD_POST) {
HttpdPostData *post = connData->post;
if( post->multipartBoundary == NULL )
{
errorResponse(connData, 404, "Only multipart POST is supported");
return HTTPD_CGI_DONE;
}
if( connData->startTime != context->startTime )
{
// reinitialize, as this is a new request
context->position = 0;
context->recvPosition = 0;
context->startTime = connData->startTime;
context->state = STATE_SEARCH_BOUNDARY;
multipartAllocBoundaryBuffer(context);
}
if( context->state != STATE_ERROR )
{
int feed = 0;
while( feed < post->buffLen )
{
int len = post->buffLen - feed;
if( len > BOUNDARY_SIZE )
len = BOUNDARY_SIZE;
if( multipartProcessData(context, post->multipartBoundary, post->buff + feed, len, 0) )
{
context->state = STATE_ERROR;
break;
}
feed += len;
}
}
context->recvPosition += post->buffLen;
if( context->recvPosition < post->len )
return HTTPD_CGI_MORE;
if( context->state != STATE_ERROR )
{
// this is the last package, process the remaining data
if( multipartProcessData(context, post->multipartBoundary, NULL, 0, 1) )
context->state = STATE_ERROR;
}
multipartFreeBoundaryBuffer( context );
if( context->state == STATE_ERROR )
errorResponse(connData, 400, "Invalid file upload!");
else
{
httpdStartResponse(connData, 204);
httpdEndHeaders(connData);
}
return HTTPD_CGI_DONE;
}
else {
errorResponse(connData, 404, "Only multipart POST is supported");
return HTTPD_CGI_DONE;
}
}

@ -0,0 +1,32 @@
#ifndef MULTIPART_H
#define MULTIPART_H
#include <httpd.h>
typedef enum {
FILE_START, // multipart: the start of a new file
FILE_DATA, // multipart: file data
FILE_DONE, // multipart: file end
} MultipartCmd;
// multipart callback
// -> FILE_START : data+dataLen contains the filename, position is 0
// -> FILE_DATA : data+dataLen contains file data, position is the file position
// -> FILE_DONE : data+dataLen is 0, position is the complete file size
typedef int (* MultipartCallback)(MultipartCmd cmd, char *data, int dataLen, int position);
struct _MultipartCtx; // the context for multipart listening
typedef struct _MultipartCtx MultipartCtx;
// use this for creating a multipart context
MultipartCtx * ICACHE_FLASH_ATTR multipartCreateContext(MultipartCallback callback);
// for destroying multipart context
void ICACHE_FLASH_ATTR multipartDestroyContext(MultipartCtx * context);
// use this function for processing HTML multipart updates
int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * post );
#endif /* MULTIPART_H */

@ -23,7 +23,7 @@ static struct espconn serbridgeConn2; // programming port
static esp_tcp serbridgeTcp1, serbridgeTcp2;
static int8_t mcu_reset_pin, mcu_isp_pin;
extern uint8_t slip_disabled; // disable slip to allow flashing of attached MCU
uint8_t in_mcu_flashing; // for disabling slip during MCU flashing
void (*programmingCB)(char *buffer, short length) = NULL;
@ -124,14 +124,14 @@ telnetUnwrap(uint8_t *inBuf, int len, uint8_t state)
#ifdef SERBR_DBG
else { os_printf("MCU isp: no pin\n"); }
#endif
slip_disabled++;
in_mcu_flashing++;
break;
case RTS_OFF:
if (mcu_isp_pin >= 0) {
GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(100L);
}
if (slip_disabled > 0) slip_disabled--;
if (in_mcu_flashing > 0) in_mcu_flashing--;
break;
}
state = TN_end;
@ -222,7 +222,7 @@ serbridgeRecvCb(void *arg, char *data, unsigned short len)
//if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(1000L); // wait a millisecond before writing to the UART below
conn->conn_mode = cmPGM;
slip_disabled++; // disable SLIP so it doesn't interfere with flashing
in_mcu_flashing++; // disable SLIP so it doesn't interfere with flashing
#ifdef SKIP_AT_RESET
serledFlash(50); // short blink on serial LED
return;
@ -355,7 +355,7 @@ serbridgeUartCb(char *buf, short length)
{
if (programmingCB) {
programmingCB(buf, length);
} else if (!flashConfig.slip_enable || slip_disabled > 0) {
} else if (!flashConfig.slip_enable || in_mcu_flashing > 0) {
//os_printf("SLIP: disabled got %d\n", length);
console_process(buf, length);
} else {
@ -504,3 +504,8 @@ serbridgeInit(int port1, int port2)
espconn_tcp_set_max_con_allow(&serbridgeConn2, MAX_CONN);
espconn_regist_time(&serbridgeConn2, SER_BRIDGE_TIMEOUT, 0);
}
int ICACHE_FLASH_ATTR serbridgeInMCUFlashing()
{
return in_mcu_flashing;
}

@ -36,6 +36,8 @@ void ICACHE_FLASH_ATTR serbridgeInitPins(void);
void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len);
void ICACHE_FLASH_ATTR serbridgeReset();
int ICACHE_FLASH_ATTR serbridgeInMCUFlashing();
// callback when receiving UART chars when in programming mode
extern void (*programmingCB)(char *buffer, short length);

@ -13,8 +13,6 @@
#define DBG(format, ...) do { } while(0)
#endif
uint8_t slip_disabled; // temporarily disable slip to allow flashing of attached MCU
extern void ICACHE_FLASH_ATTR console_process(char *buf, short len);
// This SLIP parser tries to conform to RFC 1055 https://tools.ietf.org/html/rfc1055.

@ -0,0 +1,454 @@
#include "web-server.h"
#include <espconn.h>
#include "espfs.h"
#include "config.h"
#include "cgi.h"
#include "cmd.h"
#include "serbridge.h"
// the file is responsible for handling user defined web-pages
// - collects HTML files from user image, shows them on the left frame
// - handles JSON data coming from the browser
// - handles SLIP messages coming from MCU
#define WEB_CB "webCb"
#define MAX_ARGUMENT_BUFFER_SIZE 1024
struct ArgumentBuffer
{
char argBuffer[MAX_ARGUMENT_BUFFER_SIZE];
int argBufferPtr;
int numberOfArgs;
};
static char* web_server_reasons[] = {
"load", // readable name for RequestReason::LOAD
"refresh", // readable name for RequestReason::REFRESH
"button", // readable name for RequestReason::BUTTON
"submit" // readable name for RequestReason::SUBMIT
};
// this variable contains the names of the user defined pages
// this information appears at the left frame below of the built in URL-s
// format: ,"UserPage1", "/UserPage1.html", "UserPage2", "/UserPage2.html",
char * webServerPages = NULL;
char * ICACHE_FLASH_ATTR WEB_UserPages()
{
return webServerPages;
}
// generates the content of webServerPages variable (called at booting/web page uploading)
void ICACHE_FLASH_ATTR WEB_BrowseFiles()
{
char buffer[1024];
buffer[0] = 0;
if( espFsIsValid( userPageCtx ) )
{
EspFsIterator it;
espFsIteratorInit(userPageCtx, &it);
while( espFsIteratorNext(&it) )
{
int nameLen = os_strlen(it.name);
if( nameLen >= 6 )
{
// fetch HTML files
if( os_strcmp( it.name + nameLen-5, ".html" ) == 0 )
{
int slashPos = nameLen - 5;
// chop path and .html from the name
while( slashPos > 0 && it.name[slashPos-1] != '/' )
slashPos--;
// here we check buffer overrun
int maxLen = 10 + os_strlen( it.name ) + (nameLen - slashPos -5);
if( maxLen >= sizeof(buffer) )
break;
os_strcat(buffer, ", \"");
int writePos = os_strlen(buffer);
for( int i=slashPos; i < nameLen-5; i++ )
buffer[writePos++] = it.name[i];
buffer[writePos] = 0; // terminating zero
os_strcat(buffer, "\", \"/");
os_strcat(buffer, it.name);
os_strcat(buffer, "\"");
}
}
}
}
if( webServerPages != NULL )
os_free( webServerPages );
int len = os_strlen(buffer) + 1;
webServerPages = (char *)os_malloc( len );
os_memcpy( webServerPages, buffer, len );
}
// initializer
void ICACHE_FLASH_ATTR WEB_Init()
{
espFsInit(userPageCtx, (void *)getUserPageSectionStart(), ESPFS_FLASH);
if( espFsIsValid( userPageCtx ) )
os_printf("Valid user file system found!\n");
else
os_printf("No user file system found!\n");
WEB_BrowseFiles(); // collect user defined HTML files
}
// initializes the argument buffer
static void WEB_argInit(struct ArgumentBuffer * argBuffer)
{
argBuffer->numberOfArgs = 0;
argBuffer->argBufferPtr = 0;
}
// adds an argument to the argument buffer (returns 0 if successful)
static int WEB_addArg(struct ArgumentBuffer * argBuffer, char * arg, int argLen )
{
if( argBuffer->argBufferPtr + argLen + sizeof(int) >= MAX_ARGUMENT_BUFFER_SIZE )
return -1; // buffer overflow
os_memcpy(argBuffer->argBuffer + argBuffer->argBufferPtr, &argLen, sizeof(int));
if( argLen != 0 )
{
os_memcpy( argBuffer->argBuffer + argBuffer->argBufferPtr + sizeof(int), arg, argLen );
argBuffer->numberOfArgs++;
}
argBuffer->argBufferPtr += argLen + sizeof(int);
return 0;
}
// creates and sends a SLIP message from the argument buffer
static void WEB_sendArgBuffer(struct ArgumentBuffer * argBuffer, HttpdConnData *connData, int id, RequestReason reason)
{
cmdResponseStart(CMD_WEB_REQ_CB, id, 4 + argBuffer->numberOfArgs);
uint16_t r = (uint16_t)reason;
cmdResponseBody(&r, sizeof(uint16_t)); // 1st argument: reason
cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); // 2nd argument: IP
cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); // 3rd argument: port
cmdResponseBody(connData->url, os_strlen(connData->url)); // 4th argument: URL
int p = 0;
for( int j=0; j < argBuffer->numberOfArgs; j++ )
{
int argLen;
os_memcpy( &argLen, argBuffer->argBuffer + p, sizeof(int) );
char * arg = argBuffer->argBuffer + p + sizeof(int);
cmdResponseBody(arg, argLen);
p += argLen + sizeof(int);
}
cmdResponseEnd();
}
// this method processes SLIP data from MCU and converts to JSON
// this method receives JSON from the browser, sends SLIP data to MCU
static int ICACHE_FLASH_ATTR WEB_handleJSONRequest(HttpdConnData *connData)
{
if( !flashConfig.slip_enable )
{
errorResponse(connData, 400, "Slip processing is disabled!");
return HTTPD_CGI_DONE;
}
CmdCallback* cb = cmdGetCbByName( WEB_CB );
if( cb == NULL )
{
errorResponse(connData, 500, "No MCU callback is registered!");
return HTTPD_CGI_DONE;
}
if( serbridgeInMCUFlashing() )
{
errorResponse(connData, 500, "Slip disabled at uploading program onto the MCU!");
return HTTPD_CGI_DONE;
}
char reasonBuf[16];
int i;
int len = httpdFindArg(connData->getArgs, "reason", reasonBuf, sizeof(reasonBuf));
if( len < 0 )
{
errorResponse(connData, 400, "No reason specified!");
return HTTPD_CGI_DONE;
}
RequestReason reason = INVALID;
for(i=0; i < sizeof(web_server_reasons)/sizeof(char *); i++)
{
if( os_strcmp( web_server_reasons[i], reasonBuf ) == 0 )
reason = (RequestReason)i;
}
if( reason == INVALID )
{
errorResponse(connData, 400, "Invalid reason!");
return HTTPD_CGI_DONE;
}
struct ArgumentBuffer argBuffer;
WEB_argInit( &argBuffer );
switch(reason)
{
case BUTTON:
{
char id_buf[40];
int id_len = httpdFindArg(connData->getArgs, "id", id_buf, sizeof(id_buf));
if( id_len <= 0 )
{
errorResponse(connData, 400, "No button ID specified!");
return HTTPD_CGI_DONE;
}
if( WEB_addArg(&argBuffer, id_buf, id_len) )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
}
break;
case SUBMIT:
{
if( connData->post->received < connData->post->len )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
int bptr = 0;
while( bptr < connData->post->len )
{
char * line = connData->post->buff + bptr;
char * eo = os_strchr(line, '&' );
if( eo != NULL )
{
*eo = 0;
bptr = eo - connData->post->buff + 1;
}
else
{
eo = line + os_strlen( line );
bptr = connData->post->len;
}
int len = os_strlen(line);
while( len >= 1 && ( line[len-1] == '\r' || line[len-1] == '\n' ))
len--;
line[len] = 0;
char * val = os_strchr(line, '=');
if( val != NULL )
{
*val = 0;
char * name = line;
int vblen = os_strlen(val+1) * 2;
char value[vblen];
httpdUrlDecode(val+1, strlen(val+1), value, vblen);
int namLen = os_strlen(name);
int valLen = os_strlen(value);
char arg[namLen + valLen + 3];
int argPtr = 0;
arg[argPtr++] = (char)WEB_STRING;
os_strcpy( arg + argPtr, name );
argPtr += namLen;
arg[argPtr++] = 0;
os_strcpy( arg + argPtr, value );
argPtr += valLen;
if( WEB_addArg(&argBuffer, arg, argPtr) )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
}
}
}
break;
case LOAD:
case REFRESH:
default:
break;
}
if( WEB_addArg(&argBuffer, NULL, 0) )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
os_printf("Web callback to MCU: %s\n", reasonBuf);
WEB_sendArgBuffer(&argBuffer, connData, (uint32_t)cb->callback, reason );
if( reason == SUBMIT )
{
httpdStartResponse(connData, 204);
httpdEndHeaders(connData);
return HTTPD_CGI_DONE;
}
return HTTPD_CGI_MORE;
}
// this method receives SLIP data from MCU sends JSON to the browser
static int ICACHE_FLASH_ATTR WEB_handleMCUResponse(HttpdConnData *connData, CmdRequest * response)
{
char jsonBuf[1500];
int jsonPtr = 0;
jsonBuf[jsonPtr++] = '{';
int c = 2;
while( c++ < cmdGetArgc(response) )
{
int len = cmdArgLen(response);
char buf[len+1];
buf[len] = 0;
cmdPopArg(response, buf, len);
if(len == 0)
break; // last argument
if( c > 3 ) // skip the first argument
jsonBuf[jsonPtr++] = ',';
if( jsonPtr + 20 + len > sizeof(jsonBuf) )
{
errorResponse(connData, 500, "Response too large!");
return HTTPD_CGI_DONE;
}
WebValueType type = (WebValueType)buf[0];
int nameLen = os_strlen(buf+1);
jsonBuf[jsonPtr++] = '"';
os_memcpy(jsonBuf + jsonPtr, buf + 1, nameLen);
jsonPtr += nameLen;
jsonBuf[jsonPtr++] = '"';
jsonBuf[jsonPtr++] = ':';
char * value = buf + 2 + nameLen;
switch(type)
{
case WEB_NULL:
os_memcpy(jsonBuf + jsonPtr, "null", 4);
jsonPtr += 4;
break;
case WEB_INTEGER:
{
int v;
os_memcpy( &v, value, 4);
char intbuf[20];
os_sprintf(intbuf, "%d", v);
os_strcpy(jsonBuf + jsonPtr, intbuf);
jsonPtr += os_strlen(intbuf);
}
break;
case WEB_BOOLEAN:
if( *value ) {
os_memcpy(jsonBuf + jsonPtr, "true", 4);
jsonPtr += 4;
} else {
os_memcpy(jsonBuf + jsonPtr, "false", 5);
jsonPtr += 5;
}
break;
case WEB_FLOAT:
{
float f;
os_memcpy( &f, value, 4);
char intbuf[20];
os_sprintf(intbuf, "%f", f);
os_strcpy(jsonBuf + jsonPtr, intbuf);
jsonPtr += os_strlen(intbuf);
}
break;
case WEB_STRING:
jsonBuf[jsonPtr++] = '"';
while(*value)
{
if( *value == '\\' || *value == '"' )
jsonBuf[jsonPtr++] = '\\';
jsonBuf[jsonPtr++] = *(value++);
}
jsonBuf[jsonPtr++] = '"';
break;
case WEB_JSON:
os_memcpy(jsonBuf + jsonPtr, value, len - 2 - nameLen);
jsonPtr += len - 2 - nameLen;
break;
}
}
jsonBuf[jsonPtr++] = '}';
noCacheHeaders(connData, 200);
httpdHeader(connData, "Content-Type", "application/json");
char cl[16];
os_sprintf(cl, "%d", jsonPtr);
httpdHeader(connData, "Content-Length", cl);
httpdEndHeaders(connData);
httpdSend(connData, jsonBuf, jsonPtr);
return HTTPD_CGI_DONE;
}
// this method is responsible for the MCU <==JSON==> Browser communication
int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData)
{
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
void * cgiData = connData->cgiData;
if( cgiData == NULL )
{
connData->cgiData = (void *)1; // indicate, that request was processed
return WEB_handleJSONRequest(connData);
}
if( connData->cgiResponse != NULL ) // data from MCU
return WEB_handleMCUResponse(connData, (CmdRequest *)(connData->cgiResponse));
return HTTPD_CGI_MORE;
}
// this method is called when MCU transmits WEB_DATA command
void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd)
{
CmdRequest req;
cmdRequest(&req, cmd);
if (cmdGetArgc(&req) < 2) return;
uint8_t ip[4];
cmdPopArg(&req, ip, 4); // pop the IP address
uint16_t port;
cmdPopArg(&req, &port, 2); // pop the HTTP port
HttpdConnData * conn = httpdLookUpConn(ip, port); // look up connection based on IP/port
if( conn != NULL && conn->cgi == WEB_CgiJsonHook ) // make sure that the right CGI handler is configured
httpdSetCGIResponse( conn, &req );
else
os_printf("WEB_DATA ignored as no valid HTTP connection found!\n");
}

@ -0,0 +1,37 @@
#ifndef WEB_SERVER_H
#define WEB_SERVER_H
#include <esp8266.h>
#include "httpd.h"
#include "cmd.h"
typedef enum
{
LOAD=0, // loading web-page content at the first time
REFRESH, // loading web-page subsequently
BUTTON, // HTML button pressed
SUBMIT, // HTML form is submitted
INVALID=-1,
} RequestReason;
typedef enum
{
WEB_STRING=0, // the value is string
WEB_NULL, // the value is NULL
WEB_INTEGER, // the value is integer
WEB_BOOLEAN, // the value is boolean
WEB_FLOAT, // the value is float
WEB_JSON // the value is JSON data
} WebValueType;
void WEB_Init();
char * WEB_UserPages();
int WEB_CgiJsonHook(HttpdConnData *connData);
void WEB_Data(CmdPacket *cmd);
#endif /* WEB_SERVER_H */
Loading…
Cancel
Save