From 9ff44886103e43ea6b03787751286bd668b6082d Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Thu, 28 Apr 2016 06:46:34 +0200 Subject: [PATCH 01/66] Refactored espfs to handle Flash source --- esp-link/main.c | 2 +- espfs/espfs.c | 64 ++++++++++++++++++++++++++++++++++++---------- espfs/espfs.h | 13 ++++++++-- httpd/httpdespfs.c | 2 +- 4 files changed, 63 insertions(+), 18 deletions(-) diff --git a/esp-link/main.c b/esp-link/main.c index 49a228a..afaa946 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -147,7 +147,7 @@ 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 diff --git a/espfs/espfs.c b/espfs/espfs.c index f9942f3..6389b08 100644 --- a/espfs/espfs.c +++ b/espfs/espfs.c @@ -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 userCtxDef; + +EspFsContext * espLinkCtx = &espLinkCtxDef; +EspFsContext * userCtx = &userCtxDef; + +struct EspFsContext +{ + char* data; + EspFsSource source; + uint8_t valid; +}; struct EspFsFile { + EspFsContext *ctx; EspFsHeader *header; char decompressor; int32_t posDecomp; @@ -67,7 +80,20 @@ 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) { +void espfs_memcpy( EspFsContext * ctx, void * dest, const void * src, int count ) +{ + if( ctx->source == ESPFS_MEMORY ) + os_memcpy( dest, src, count ); + else + { + if( spi_flash_read( (int)src, dest, count ) != SPI_FLASH_RESULT_OK ) + os_memset( dest, 0, count ); // if read was not successful, reply with zeroes + } +} + +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; @@ -75,12 +101,13 @@ EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) { // check if there is valid header at address EspFsHeader testHeader; - os_memcpy(&testHeader, flashAddress, sizeof(EspFsHeader)); + espfs_memcpy(ctx, &testHeader, flashAddress, sizeof(EspFsHeader)); if (testHeader.magic != ESPFS_MAGIC) { return ESPFS_INIT_RESULT_NO_IMAGE; } - espFsData = (char *)flashAddress; + ctx->data = (char *)flashAddress; + ctx->valid = 1; return ESPFS_INIT_RESULT_OK; } @@ -89,7 +116,7 @@ EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) { //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; xsource == ESPFS_MEMORY ) + memcpyAligned(dest, src, count); + else + espfs_memcpy(ctx, dest, src, count); +} + // Returns flags of opened file. int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { if (fh == NULL) { @@ -116,19 +151,19 @@ 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; } //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) { + if (ctx->data == NULL) { #ifdef ESPFS_DBG os_printf("Call espFsInit first!\n"); #endif return NULL; } - char *p=espFsData; + char *p=ctx->data; char *hpos; char namebuf[256]; EspFsHeader h; @@ -139,7 +174,7 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { while(1) { hpos=p; //Grab the next file header. - os_memcpy(&h, p, sizeof(EspFsHeader)); + espfs_memcpy(ctx, &h, p, sizeof(EspFsHeader)); if (h.magic!=ESPFS_MAGIC) { #ifdef ESPFS_DBG os_printf("Magic mismatch. EspFS image broken.\n"); @@ -152,7 +187,7 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { } //Grab the name of the file. p+=sizeof(EspFsHeader); - os_memcpy(namebuf, p, sizeof(namebuf)); + espfs_memcpy(ctx, 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) { @@ -161,6 +196,7 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { 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->ctx = ctx; r->header=(EspFsHeader *)hpos; r->decompressor=h.compression; r->posComp=p; @@ -187,15 +223,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); diff --git a/espfs/espfs.h b/espfs/espfs.h index c8e13e7..8e4b79f 100644 --- a/espfs/espfs.h +++ b/espfs/espfs.h @@ -7,10 +7,19 @@ typedef enum { ESPFS_INIT_RESULT_BAD_ALIGN, } EspFsInitResult; +typedef enum { + ESPFS_MEMORY, + ESPFS_FLASH, +} EspFsSource; + typedef struct EspFsFile EspFsFile; +typedef struct EspFsContext EspFsContext; + +extern EspFsContext * espLinkCtx; +extern EspFsContext * userCtx; -EspFsInitResult espFsInit(void *flashAddress); -EspFsFile *espFsOpen(char *fileName); +EspFsInitResult espFsInit(EspFsContext *ctx, void *flashAddress, EspFsSource source); +EspFsFile *espFsOpen(EspFsContext *ctx, char *fileName); int espFsFlags(EspFsFile *fh); int espFsRead(EspFsFile *fh, char *buff, int len); void espFsClose(EspFsFile *fh); diff --git a/httpd/httpdespfs.c b/httpd/httpdespfs.c index c6f2c0c..9ab2865 100644 --- a/httpd/httpdespfs.c +++ b/httpd/httpdespfs.c @@ -40,7 +40,7 @@ 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; } From c5e2587e12c502b06deda5b851848599eccbee83 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Thu, 28 Apr 2016 20:46:55 +0200 Subject: [PATCH 02/66] Added: user page sections to config folder --- esp-link/config.c | 40 ++++++++++++++++++++++++++++++++++++++++ esp-link/config.h | 3 +++ 2 files changed, 43 insertions(+) diff --git a/esp-link/config.c b/esp-link/config.c index 1af160a..cbab480 100644 --- a/esp-link/config.c +++ b/esp-link/config.c @@ -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 0x0FC000; + 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; + } +} + diff --git a/esp-link/config.h b/esp-link/config.h index 341d7e1..bf08df4 100644 --- a/esp-link/config.h +++ b/esp-link/config.h @@ -46,4 +46,7 @@ bool configRestore(void); void configWipe(void); const size_t getFlashSize(); +const uint32_t getUserPageSectionStart(); +const uint32_t getUserPageSectionEnd(); + #endif From 97540a34b0256fadac2d6a7d8213a5587e441545 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Thu, 28 Apr 2016 23:18:00 +0200 Subject: [PATCH 03/66] Skeleton for the web-server page --- esp-link/cgi.c | 3 ++- esp-link/cgiservices.c | 2 ++ html/home.html | 6 ++++++ html/web-server.html | 12 ++++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 html/web-server.html diff --git a/esp-link/cgi.c b/esp-link/cgi.c index 96c03e9..6300317 100644 --- a/esp-link/cgi.c +++ b/esp-link/cgi.c @@ -213,7 +213,8 @@ 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\"" " ], " "\"version\": \"%s\", " "\"name\": \"%s\"" diff --git a/esp-link/cgiservices.c b/esp-link/cgiservices.c index 200039d..ef76287 100644 --- a/esp-link/cgiservices.c +++ b/esp-link/cgiservices.c @@ -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", diff --git a/html/home.html b/html/home.html index 138222e..de9862f 100644 --- a/html/home.html +++ b/html/home.html @@ -123,6 +123,12 @@ + Webpage size +
+ + +
+ Current partition Description:
diff --git a/html/web-server.html b/html/web-server.html new file mode 100644 index 0000000..f509230 --- /dev/null +++ b/html/web-server.html @@ -0,0 +1,12 @@ +
+
+

Web Server

+
+ +
+

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.

+
+ +
+ \ No newline at end of file From a1b4554dfb2a22f6ee5d430847a437c3e1dd54cb Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 30 Apr 2016 19:45:36 +0200 Subject: [PATCH 04/66] Multipart upload implementation --- esp-link/cgiwebserver.c | 31 +++++++ esp-link/cgiwebserver.h | 8 ++ esp-link/main.c | 2 + html/web-server.html | 12 ++- httpd/httpd.c | 2 + httpd/multipart.c | 190 ++++++++++++++++++++++++++++++++++++++++ httpd/multipart.h | 33 +++++++ 7 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 esp-link/cgiwebserver.c create mode 100644 esp-link/cgiwebserver.h create mode 100644 httpd/multipart.c create mode 100644 httpd/multipart.h diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c new file mode 100644 index 0000000..194b585 --- /dev/null +++ b/esp-link/cgiwebserver.c @@ -0,0 +1,31 @@ +// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo + +#include +#include +#include "cgi.h" +#include "cgioptiboot.h" +#include "multipart.h" + +void webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) +{ + switch(cmd) + { + case FILE_START: + os_printf("CB: File start: %s\n", data); + break; + case FILE_DATA: + os_printf("CB: Data (%d): %s\n", position, data); + break; + case FILE_DONE: + os_printf("CB: Done\n"); + break; + } +} + +MultipartCtx webServerContext = {.callBack = webServerMultipartCallback, .position = 0, .recvPosition = 0, .startTime = 0, .boundaryBuffer = NULL}; + +int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData) +{ + os_printf("WebServer upload\n"); + return multipartProcess(&webServerContext, connData); +} diff --git a/esp-link/cgiwebserver.h b/esp-link/cgiwebserver.h new file mode 100644 index 0000000..e3365be --- /dev/null +++ b/esp-link/cgiwebserver.h @@ -0,0 +1,8 @@ +#ifndef CGIWEBSERVER_H +#define CGIWEBSERVER_H + +#include + +int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData); + +#endif /* CGIWEBSERVER_H */ diff --git a/esp-link/main.c b/esp-link/main.c index afaa946..a5f78aa 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -19,6 +19,7 @@ #include "cgimqtt.h" #include "cgiflash.h" #include "cgioptiboot.h" +#include "cgiwebserver.h" #include "auth.h" #include "espfs.h" #include "uart.h" @@ -96,6 +97,7 @@ HttpdBuiltInUrl builtInUrls[] = { #ifdef MQTT { "/mqtt", cgiMqtt, NULL }, #endif + { "/web-server/upload", cgiWebServerUpload, NULL }, { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem { NULL, NULL, NULL } }; diff --git a/html/web-server.html b/html/web-server.html index f509230..c73e59c 100644 --- a/html/web-server.html +++ b/html/web-server.html @@ -4,9 +4,15 @@
-

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.

-
+

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.

+
+ The custom web page to upload: + +
+ + + \ No newline at end of file diff --git a/httpd/httpd.c b/httpd/httpd.c index f3c5594..8602ce3 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -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. @@ -509,6 +510,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. diff --git a/httpd/multipart.c b/httpd/multipart.c new file mode 100644 index 0000000..e4a99a4 --- /dev/null +++ b/httpd/multipart.c @@ -0,0 +1,190 @@ +#include +#include + +#include "multipart.h" +#include "cgi.h" + +#define BOUNDARY_SIZE 100 + +void multipartAllocBoundaryBuffer(MultipartCtx * context) +{ + if( context->boundaryBuffer == NULL ) + context->boundaryBuffer = (char *)os_malloc(3*BOUNDARY_SIZE + 1); + context->boundaryBufferPtr = 0; +} + +void multipartFreeBoundaryBuffer(MultipartCtx * context) +{ + if( context->boundaryBuffer != NULL ) + { + os_free(context->boundaryBuffer); + context->boundaryBuffer = NULL; + } +} + +void multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, char * buff, int len, int last) +{ + if( len != 0 ) + { + os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, buff, len); + + context->boundaryBufferPtr += len; + context->boundaryBuffer[context->boundaryBufferPtr] = 0; + } + + while( context->boundaryBufferPtr > 0 ) + { + if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) + return; + + int dataSize = BOUNDARY_SIZE; + + char * loc = os_strstr( context->boundaryBuffer, boundary ); + if( loc != NULL ) + { + int pos = loc - context->boundaryBuffer; + if( pos > BOUNDARY_SIZE ) + loc = NULL; + else + dataSize = pos; + } + + if( dataSize != 0 ) + { + switch( context->state ) + { + case STATE_SEARCH_HEADER: + case STATE_SEARCH_HEADER_END: + { + char * chr = os_strchr( context->boundaryBuffer, '\n' ); + if( chr != NULL ) + { + int pos = chr - context->boundaryBuffer + 1; + if( pos < dataSize ) + { + dataSize = pos; + loc = NULL; // this is not yet the boundary + } + 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++; + if( context->boundaryBuffer[pos] == '"' ) + { + pos++; + int start = pos; + while( pos < context->boundaryBufferPtr ) + { + if( context->boundaryBuffer[pos] == '"' ) + break; + pos++; + } + if( pos < context->boundaryBufferPtr ) + { + context->boundaryBuffer[pos] = 0; + os_printf("Uploading file: %s\n", context->boundaryBuffer + start); + context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ); + context->boundaryBuffer[pos] = '"'; + 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) + context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ); + context->boundaryBuffer[dataSize] = c; + context->position += dataSize; + } + break; + default: + break; + } + } + + if( loc != NULL ) + { + dataSize += os_strlen(boundary); + if( context->state == STATE_UPLOAD_FILE ) + { + context->callBack( FILE_DONE, NULL, 0, context->position ); + os_printf("File upload done\n"); + } + + context->state = STATE_SEARCH_HEADER; + } + + context->boundaryBufferPtr -= dataSize; + os_memcpy(context->boundaryBuffer, context->boundaryBuffer + dataSize, context->boundaryBufferPtr); + } +} + +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 different request + context->position = 0; + context->recvPosition = 0; + context->startTime = connData->startTime; + context->state = STATE_SEARCH_BOUNDARY; + + multipartAllocBoundaryBuffer(context); + } + + int feed = 0; + while( feed < post->buffLen ) + { + int len = post->buffLen - feed; + if( len > BOUNDARY_SIZE ) + len = BOUNDARY_SIZE; + multipartProcessBoundaryBuffer(context, post->multipartBoundary, post->buff + feed, len, 0); + feed += len; + } + + context->recvPosition += post->buffLen; + if( context->recvPosition < post->len ) + return HTTPD_CGI_MORE; + + multipartProcessBoundaryBuffer(context, post->multipartBoundary, NULL, 0, 1); + multipartFreeBoundaryBuffer( context ); + + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + return HTTPD_CGI_DONE; + } + else { + errorResponse(connData, 404, "Only multipart POST is supported"); + return HTTPD_CGI_DONE; + } +} diff --git a/httpd/multipart.h b/httpd/multipart.h new file mode 100644 index 0000000..76e15c4 --- /dev/null +++ b/httpd/multipart.h @@ -0,0 +1,33 @@ +#ifndef MULTIPART_H +#define MULTIPART_H + +#include + +typedef enum { + FILE_START, + FILE_DATA, + FILE_DONE, +} MultipartCmd; + +typedef enum { + STATE_SEARCH_BOUNDARY = 0, + STATE_SEARCH_HEADER, + STATE_SEARCH_HEADER_END, + STATE_UPLOAD_FILE +} MultipartState; + +typedef void (* MultipartCallback)(MultipartCmd cmd, char *data, int dataLen, int position); + +typedef struct { + MultipartCallback callBack; + int position; + int startTime; + int recvPosition; + char * boundaryBuffer; + int boundaryBufferPtr; + MultipartState state; +} MultipartCtx; + +int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * post ); + +#endif /* MULTIPART_H */ From 9bc8e297c0f6fe1be1d2ae34e41afb94ad7dc38d Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 30 Apr 2016 20:24:10 +0200 Subject: [PATCH 05/66] handle invalid upload --- esp-link/cgiwebserver.c | 23 ++++++++++++++---- httpd/multipart.c | 53 ++++++++++++++++++++++++++++------------- httpd/multipart.h | 5 ++-- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c index 194b585..8dbe042 100644 --- a/esp-link/cgiwebserver.c +++ b/esp-link/cgiwebserver.c @@ -5,27 +5,40 @@ #include "cgi.h" #include "cgioptiboot.h" #include "multipart.h" +#include "espfsformat.h" -void webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) +int webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) { switch(cmd) { case FILE_START: - os_printf("CB: File start: %s\n", data); + // do nothing break; case FILE_DATA: - os_printf("CB: Data (%d): %s\n", position, data); + if( position < 4 ) + { + 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; // clean espfs magic to mark as invalid + } + } + // TODO: flash write break; case FILE_DONE: - os_printf("CB: Done\n"); + // TODO: finalize changes, set back espfs magic break; } + return 0; } MultipartCtx webServerContext = {.callBack = webServerMultipartCallback, .position = 0, .recvPosition = 0, .startTime = 0, .boundaryBuffer = NULL}; int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData) { - os_printf("WebServer upload\n"); return multipartProcess(&webServerContext, connData); } diff --git a/httpd/multipart.c b/httpd/multipart.c index e4a99a4..813ac82 100644 --- a/httpd/multipart.c +++ b/httpd/multipart.c @@ -22,7 +22,7 @@ void multipartFreeBoundaryBuffer(MultipartCtx * context) } } -void multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, char * buff, int len, int last) +int multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, char * buff, int len, int last) { if( len != 0 ) { @@ -35,7 +35,7 @@ void multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, cha while( context->boundaryBufferPtr > 0 ) { if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) - return; + return 0; int dataSize = BOUNDARY_SIZE; @@ -96,7 +96,8 @@ void multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, cha { context->boundaryBuffer[pos] = 0; os_printf("Uploading file: %s\n", context->boundaryBuffer + start); - context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ); + if( context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ) ) + return 1; context->boundaryBuffer[pos] = '"'; context->state = STATE_SEARCH_HEADER_END; } @@ -111,7 +112,8 @@ void multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, cha { char c = context->boundaryBuffer[dataSize]; context->boundaryBuffer[dataSize] = 0; // add terminating zero (for easier handling) - context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ); + if( context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ) ) + return 1; context->boundaryBuffer[dataSize] = c; context->position += dataSize; } @@ -126,7 +128,8 @@ void multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, cha dataSize += os_strlen(boundary); if( context->state == STATE_UPLOAD_FILE ) { - context->callBack( FILE_DONE, NULL, 0, context->position ); + if( context->callBack( FILE_DONE, NULL, 0, context->position ) ) + return 1; os_printf("File upload done\n"); } @@ -136,6 +139,7 @@ void multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, cha context->boundaryBufferPtr -= dataSize; os_memcpy(context->boundaryBuffer, context->boundaryBuffer + dataSize, context->boundaryBufferPtr); } + return 0; } int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * connData ) @@ -162,25 +166,42 @@ int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * c multipartAllocBoundaryBuffer(context); } - int feed = 0; - while( feed < post->buffLen ) + if( context->state != STATE_ERROR ) { - int len = post->buffLen - feed; - if( len > BOUNDARY_SIZE ) - len = BOUNDARY_SIZE; - multipartProcessBoundaryBuffer(context, post->multipartBoundary, post->buff + feed, len, 0); - feed += len; + int feed = 0; + while( feed < post->buffLen ) + { + int len = post->buffLen - feed; + if( len > BOUNDARY_SIZE ) + len = BOUNDARY_SIZE; + if( multipartProcessBoundaryBuffer(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; - multipartProcessBoundaryBuffer(context, post->multipartBoundary, NULL, 0, 1); + if( context->state != STATE_ERROR ) + { + if( multipartProcessBoundaryBuffer(context, post->multipartBoundary, NULL, 0, 1) ) + context->state = STATE_ERROR; + } + multipartFreeBoundaryBuffer( context ); - - httpdStartResponse(connData, 204); - httpdEndHeaders(connData); + + if( context->state == STATE_ERROR ) + errorResponse(connData, 400, "Invalid file upload!"); + else + { + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + } return HTTPD_CGI_DONE; } else { diff --git a/httpd/multipart.h b/httpd/multipart.h index 76e15c4..d22815d 100644 --- a/httpd/multipart.h +++ b/httpd/multipart.h @@ -13,10 +13,11 @@ typedef enum { STATE_SEARCH_BOUNDARY = 0, STATE_SEARCH_HEADER, STATE_SEARCH_HEADER_END, - STATE_UPLOAD_FILE + STATE_UPLOAD_FILE, + STATE_ERROR, } MultipartState; -typedef void (* MultipartCallback)(MultipartCmd cmd, char *data, int dataLen, int position); +typedef int (* MultipartCallback)(MultipartCmd cmd, char *data, int dataLen, int position); typedef struct { MultipartCallback callBack; From eedf1959ca457e1c95fdd105155b0127170f81e5 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 30 Apr 2016 21:49:35 +0200 Subject: [PATCH 06/66] Flash file upload --- esp-link/cgiwebserver.c | 35 ++++++++++++++++++++++++++++++++--- httpd/multipart.c | 6 +++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c index 8dbe042..de4f3f5 100644 --- a/esp-link/cgiwebserver.c +++ b/esp-link/cgiwebserver.c @@ -6,8 +6,9 @@ #include "cgioptiboot.h" #include "multipart.h" #include "espfsformat.h" +#include "config.h" -int webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) +int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) { switch(cmd) { @@ -27,10 +28,38 @@ int webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int po data[p - position] = 0xFF; // clean espfs magic to mark as invalid } } - // TODO: flash write + + int spi_flash_addr = getUserPageSectionStart() + 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: - // TODO: finalize changes, set back espfs magic + { + uint32_t magic = ESPFS_MAGIC; + spi_flash_write( (int)getUserPageSectionStart(), (uint32_t *)&magic, sizeof(uint32_t) ); + } break; } return 0; diff --git a/httpd/multipart.c b/httpd/multipart.c index 813ac82..1198579 100644 --- a/httpd/multipart.c +++ b/httpd/multipart.c @@ -6,14 +6,14 @@ #define BOUNDARY_SIZE 100 -void multipartAllocBoundaryBuffer(MultipartCtx * context) +void ICACHE_FLASH_ATTR multipartAllocBoundaryBuffer(MultipartCtx * context) { if( context->boundaryBuffer == NULL ) context->boundaryBuffer = (char *)os_malloc(3*BOUNDARY_SIZE + 1); context->boundaryBufferPtr = 0; } -void multipartFreeBoundaryBuffer(MultipartCtx * context) +void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context) { if( context->boundaryBuffer != NULL ) { @@ -22,7 +22,7 @@ void multipartFreeBoundaryBuffer(MultipartCtx * context) } } -int multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, char * buff, int len, int last) +int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, char * buff, int len, int last) { if( len != 0 ) { From 730c0bdb5c62e5e6e9f01c2e4fc790a2b3be47cd Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 30 Apr 2016 22:08:12 +0200 Subject: [PATCH 07/66] Web server shows user pages --- esp-link/main.c | 3 +++ espfs/espfs.c | 8 +++++--- espfs/espfs.h | 3 ++- httpd/httpdespfs.c | 18 +++++++++++++++++- httpd/httpdespfs.h | 2 ++ 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/esp-link/main.c b/esp-link/main.c index a5f78aa..e2a9e0b 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -150,10 +150,13 @@ void user_init(void) { wifiInit(); // init the flash filesystem with the html stuff 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); + httpdespfsInit(); + // init the wifi-serial transparent bridge (port 23) serbridgeInit(23, 2323); uart_add_recv_cb(&serbridgeUartCb); diff --git a/espfs/espfs.c b/espfs/espfs.c index 6389b08..8657fd4 100644 --- a/espfs/espfs.c +++ b/espfs/espfs.c @@ -42,10 +42,10 @@ It's written for use with httpd, but doesn't need to be used as such. #include "espfs.h" EspFsContext espLinkCtxDef; -EspFsContext userCtxDef; +EspFsContext userPageCtxDef; EspFsContext * espLinkCtx = &espLinkCtxDef; -EspFsContext * userCtx = &userCtxDef; +EspFsContext * userPageCtx = &userPageCtxDef; struct EspFsContext { @@ -247,5 +247,7 @@ void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { os_free(fh); } - +int ICACHE_FLASH_ATTR espFsIsValid(EspFsContext *ctx) { + return ctx->valid; +} diff --git a/espfs/espfs.h b/espfs/espfs.h index 8e4b79f..cab1896 100644 --- a/espfs/espfs.h +++ b/espfs/espfs.h @@ -16,10 +16,11 @@ typedef struct EspFsFile EspFsFile; typedef struct EspFsContext EspFsContext; extern EspFsContext * espLinkCtx; -extern EspFsContext * userCtx; +extern EspFsContext * userPageCtx; 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); diff --git a/httpd/httpdespfs.c b/httpd/httpdespfs.c index 9ab2865..8beadca 100644 --- a/httpd/httpdespfs.c +++ b/httpd/httpdespfs.c @@ -13,11 +13,20 @@ Connector to let httpd use the espfs filesystem to serve the files in it. * ---------------------------------------------------------------------------- */ #include "httpdespfs.h" +#include "config.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.) 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"; +void ICACHE_FLASH_ATTR httpdespfsInit() +{ + 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"); +} //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 @@ -42,7 +51,14 @@ cgiEspFsHook(HttpdConnData *connData) { //First call to this cgi. Open the file so we can read it. file=espFsOpen(espLinkCtx, connData->url); if (file==NULL) { - return HTTPD_CGI_NOTFOUND; + if( espFsIsValid(userPageCtx) ) + { + file = espFsOpen(userPageCtx, connData->url ); + if( file == NULL ) + return HTTPD_CGI_NOTFOUND; + } + else + return HTTPD_CGI_NOTFOUND; } // The gzip checking code is intentionally without #ifdefs because checking diff --git a/httpd/httpdespfs.h b/httpd/httpdespfs.h index 847a8b6..95c5f50 100644 --- a/httpd/httpdespfs.h +++ b/httpd/httpdespfs.h @@ -7,6 +7,8 @@ #include "cgi.h" #include "httpd.h" +void httpdespfsInit(); + int cgiEspFsHook(HttpdConnData *connData); //int cgiEspFsTemplate(HttpdConnData *connData); //int ICACHE_FLASH_ATTR cgiEspFsHtml(HttpdConnData *connData); From 73a4fb4beffe3c52dfd28acd6c092da53a586a90 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 1 May 2016 01:03:00 +0200 Subject: [PATCH 08/66] Use iterators in espfs --- Makefile | 6 ++- esp-link/main.c | 3 +- espfs/espfs.c | 96 +++++++++++++++++++++++++---------------- espfs/espfs.h | 12 ++++++ httpd/httpdespfs.c | 10 ----- httpd/httpdespfs.h | 2 - web-server/web-server.c | 34 +++++++++++++++ web-server/web-server.h | 9 ++++ 8 files changed, 121 insertions(+), 51 deletions(-) create mode 100644 web-server/web-server.c create mode 100644 web-server/web-server.h diff --git a/Makefile b/Makefile index 4138886..ccf379f 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/esp-link/main.c b/esp-link/main.c index e2a9e0b..73bfac0 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -31,6 +31,7 @@ #include "log.h" #include "gpio.h" #include "cgiservices.h" +#include "web-server.h" #ifdef SYSLOG #include "syslog.h" @@ -155,7 +156,7 @@ void user_init(void) { //os_printf("espFsInit %s\n", res?"ERR":"ok"); // mount the http handlers httpdInit(builtInUrls, 80); - httpdespfsInit(); + webServerInit(); // init the wifi-serial transparent bridge (port 23) serbridgeInit(23, 2323); diff --git a/espfs/espfs.c b/espfs/espfs.c index 8657fd4..39f2e12 100644 --- a/espfs/espfs.c +++ b/espfs/espfs.c @@ -155,54 +155,78 @@ int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { return (int)flags; } +void ICACHE_FLASH_ATTR espFsIteratorInit(EspFsContext *ctx, EspFsIterator *iterator) +{ + if( ctx->data == NULL ) + { + iterator->ctx = NULL; + return; + } + iterator->ctx = ctx; + iterator->p = ctx->data; +} + +int ICACHE_FLASH_ATTR espFsIteratorNext(EspFsIterator *iterator) +{ + if( iterator->ctx == NULL ) + return 0; + + char * p = iterator->p; + + iterator->node = p; + EspFsHeader * hdr = &iterator->header; + espfs_memcpy(iterator->ctx, hdr, p, 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"); + return 0; + } + + p += sizeof(EspFsHeader); + + //Grab the name of the file. + espfs_memcpy(iterator->ctx, iterator->name, p, sizeof(iterator->name)); + + p+=hdr->nameLen+hdr->fileLenComp; + if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val + iterator->p = p; + return 1; +} + //Open a file and return a pointer to the file desc struct. EspFsFile ICACHE_FLASH_ATTR *espFsOpen(EspFsContext *ctx, char *fileName) { - if (ctx->data == NULL) { + EspFsIterator it; + espFsIteratorInit(ctx, &it); + if (it.ctx == NULL) { #ifdef ESPFS_DBG os_printf("Call espFsInit first!\n"); #endif return NULL; } - char *p=ctx->data; - 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. - espfs_memcpy(ctx, &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); - espfs_memcpy(ctx, 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->ctx = ctx; - r->header=(EspFsHeader *)hpos; - r->decompressor=h.compression; - r->posComp=p; - r->posStart=p; + r->header=(EspFsHeader *)it.node; + r->decompressor=it.header.compression; + r->posComp=it.node + it.header.nameLen + sizeof(EspFsHeader); + r->posStart=it.node + 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 @@ -212,10 +236,8 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(EspFsContext *ctx, 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. diff --git a/espfs/espfs.h b/espfs/espfs.h index cab1896..609df36 100644 --- a/espfs/espfs.h +++ b/espfs/espfs.h @@ -1,6 +1,8 @@ #ifndef ESPFS_H #define ESPFS_H +#include "espfsformat.h" + typedef enum { ESPFS_INIT_RESULT_OK, ESPFS_INIT_RESULT_NO_IMAGE, @@ -15,6 +17,14 @@ typedef enum { typedef struct EspFsFile EspFsFile; typedef struct EspFsContext EspFsContext; +typedef struct { + EspFsHeader header; + EspFsContext *ctx; + char name[256]; + char *node; + char * p; +} EspFsIterator; + extern EspFsContext * espLinkCtx; extern EspFsContext * userPageCtx; @@ -25,5 +35,7 @@ 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 \ No newline at end of file diff --git a/httpd/httpdespfs.c b/httpd/httpdespfs.c index 8beadca..9321aad 100644 --- a/httpd/httpdespfs.c +++ b/httpd/httpdespfs.c @@ -13,21 +13,11 @@ Connector to let httpd use the espfs filesystem to serve the files in it. * ---------------------------------------------------------------------------- */ #include "httpdespfs.h" -#include "config.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.) 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"; -void ICACHE_FLASH_ATTR httpdespfsInit() -{ - 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"); -} - //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. diff --git a/httpd/httpdespfs.h b/httpd/httpdespfs.h index 95c5f50..847a8b6 100644 --- a/httpd/httpdespfs.h +++ b/httpd/httpdespfs.h @@ -7,8 +7,6 @@ #include "cgi.h" #include "httpd.h" -void httpdespfsInit(); - int cgiEspFsHook(HttpdConnData *connData); //int cgiEspFsTemplate(HttpdConnData *connData); //int ICACHE_FLASH_ATTR cgiEspFsHtml(HttpdConnData *connData); diff --git a/web-server/web-server.c b/web-server/web-server.c new file mode 100644 index 0000000..ddfd0ec --- /dev/null +++ b/web-server/web-server.c @@ -0,0 +1,34 @@ +#include "web-server.h" + +#include "espfs.h" +#include "config.h" + +void ICACHE_FLASH_ATTR webServerBrowseFiles() +{ + EspFsIterator it; + espFsIteratorInit(userPageCtx, &it); + { + while( espFsIteratorNext(&it) ) + { + if( strlen(it.name) >= 6 ) + { + if( os_strcmp( it.name + strlen(it.name)-5, ".html" ) == 0 ) + { + os_printf("%s\n", it.name); // TODO + } + } + } + } +} + +void ICACHE_FLASH_ATTR webServerInit() +{ + espFsInit(userPageCtx, (void *)getUserPageSectionStart(), ESPFS_FLASH); + if( espFsIsValid( userPageCtx ) ) { + os_printf("Valid user file system found!\n"); + webServerBrowseFiles(); + } + else + os_printf("No user file system found!\n"); +} + diff --git a/web-server/web-server.h b/web-server/web-server.h new file mode 100644 index 0000000..5917605 --- /dev/null +++ b/web-server/web-server.h @@ -0,0 +1,9 @@ +#ifndef WEB_SERVER_H +#define WEB_SERVER_H + +#include + +void webServerInit(); + +#endif /* WEB_SERVER_H */ + From 4f159c1e47e4aaace75e9293c0e8623d833b80a5 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 1 May 2016 01:28:37 +0200 Subject: [PATCH 09/66] Bug when storing header in memory above 1MB --- espfs/espfs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/espfs/espfs.c b/espfs/espfs.c index 39f2e12..340e394 100644 --- a/espfs/espfs.c +++ b/espfs/espfs.c @@ -221,7 +221,8 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(EspFsContext *ctx, char *fileName) { //os_printf("Alloc %p[%d]\n", r, sizeof(EspFsFile)); if (r==NULL) return NULL; r->ctx = ctx; - r->header=(EspFsHeader *)it.node; + r->header=(EspFsHeader *)os_malloc(sizeof(EspFsHeader)); + os_memcpy(r->header, &it.header, sizeof(EspFsHeader)); r->decompressor=it.header.compression; r->posComp=it.node + it.header.nameLen + sizeof(EspFsHeader); r->posStart=it.node + it.header.nameLen + sizeof(EspFsHeader); @@ -266,6 +267,9 @@ int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { if (fh==NULL) return; //os_printf("Freed %p\n", fh); + if( fh->header != NULL ) + os_free( fh->header ); + fh->header = NULL; os_free(fh); } From aad4bc0631199585973bacc073e297523b436f9d Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 1 May 2016 06:34:57 +0200 Subject: [PATCH 10/66] names at the left frame --- esp-link/cgi.c | 4 +++- espfs/espfs.c | 6 +----- web-server/web-server.c | 47 ++++++++++++++++++++++++++++++++++++++--- web-server/web-server.h | 4 +++- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/esp-link/cgi.c b/esp-link/cgi.c index 6300317..5f7ce04 100644 --- a/esp-link/cgi.c +++ b/esp-link/cgi.c @@ -16,6 +16,7 @@ Some random cgi routines. #include #include "cgi.h" #include "config.h" +#include "web-server.h" #ifdef CGI_DBG #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) @@ -215,11 +216,12 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) { #endif "\"Debug log\", \"/log.html\", " "\"Web Server\", \"/web-server.html\"" + "%s" " ], " "\"version\": \"%s\", " "\"name\": \"%s\"" " }", - esp_link_version, name); + webServerUserPages(), esp_link_version, name); httpdSend(connData, buff, -1); return HTTPD_CGI_DONE; diff --git a/espfs/espfs.c b/espfs/espfs.c index 340e394..39f2e12 100644 --- a/espfs/espfs.c +++ b/espfs/espfs.c @@ -221,8 +221,7 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(EspFsContext *ctx, char *fileName) { //os_printf("Alloc %p[%d]\n", r, sizeof(EspFsFile)); if (r==NULL) return NULL; r->ctx = ctx; - r->header=(EspFsHeader *)os_malloc(sizeof(EspFsHeader)); - os_memcpy(r->header, &it.header, sizeof(EspFsHeader)); + r->header=(EspFsHeader *)it.node; r->decompressor=it.header.compression; r->posComp=it.node + it.header.nameLen + sizeof(EspFsHeader); r->posStart=it.node + it.header.nameLen + sizeof(EspFsHeader); @@ -267,9 +266,6 @@ int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { if (fh==NULL) return; //os_printf("Freed %p\n", fh); - if( fh->header != NULL ) - os_free( fh->header ); - fh->header = NULL; os_free(fh); } diff --git a/web-server/web-server.c b/web-server/web-server.c index ddfd0ec..b7df35e 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -3,22 +3,63 @@ #include "espfs.h" #include "config.h" +char * webServerPages = NULL; + +char * ICACHE_FLASH_ATTR webServerUserPages() +{ + return webServerPages; +} + void ICACHE_FLASH_ATTR webServerBrowseFiles() { + char buffer[1024]; + buffer[0] = 0; + EspFsIterator it; espFsIteratorInit(userPageCtx, &it); { while( espFsIteratorNext(&it) ) { - if( strlen(it.name) >= 6 ) + int nlen = strlen(it.name); + if( nlen >= 6 ) { - if( os_strcmp( it.name + strlen(it.name)-5, ".html" ) == 0 ) + if( os_strcmp( it.name + nlen-5, ".html" ) == 0 ) { - os_printf("%s\n", it.name); // TODO + char sh_name[17]; + + int spos = nlen-5; + + while( spos > 0 ) + { + if( it.name[spos+1] == '/' ) + break; + spos--; + } + + int ps = nlen-5-spos; + if( ps > 16 ) + ps = 16; + os_memcpy(sh_name, it.name + spos, ps); + sh_name[ps] = 0; + + os_strcat(buffer, ", \""); + os_strcat(buffer, sh_name); + os_strcat(buffer, "\", \"/"); + os_strcat(buffer, it.name); + os_strcat(buffer, "\""); } } + if( strlen(buffer) > 600 ) + break; } } + + if( webServerPages != NULL ) + os_free( webServerPages ); + + int len = strlen(buffer) + 1; + webServerPages = (char *)os_malloc( len ); + os_memcpy( webServerPages, buffer, len ); } void ICACHE_FLASH_ATTR webServerInit() diff --git a/web-server/web-server.h b/web-server/web-server.h index 5917605..7716b33 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -3,7 +3,9 @@ #include -void webServerInit(); +void webServerInit(); + +char * webServerUserPages(); #endif /* WEB_SERVER_H */ From 59a76f60eada0d671ccd1dd126b09d54421aefa8 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 1 May 2016 07:48:32 +0200 Subject: [PATCH 11/66] Added: script for creating file system --- createEspFs.pl | 114 ++++++++++++++++++++++++++++ examples/web-server/GZipped.html.gz | Bin 0 -> 70 bytes examples/web-server/LED.html | 3 + html/web-server.html | 2 +- 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100755 createEspFs.pl create mode 100644 examples/web-server/GZipped.html.gz create mode 100644 examples/web-server/LED.html diff --git a/createEspFs.pl b/createEspFs.pl new file mode 100755 index 0000000..721b3a3 --- /dev/null +++ b/createEspFs.pl @@ -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 = 'esp-link
'; + + open IF, "<", "$dir/$file" or die "Can't read file: $!"; + my @fc = ; + 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; +} diff --git a/examples/web-server/GZipped.html.gz b/examples/web-server/GZipped.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..e9c012cb904e9c9107c4e843e72fcd11459dc711 GIT binary patch literal 70 zcmV-M0J;AkiwFohktJ6E14mkEaByX0E@*UZYyeYGut~`*v*S_#0-Fp&yO4~`Vg(>d cugWYaNKLWP2Z{kDZS=va0Q%qKmo5MR09@P}TmS$7 literal 0 HcmV?d00001 diff --git a/examples/web-server/LED.html b/examples/web-server/LED.html new file mode 100644 index 0000000..4c88681 --- /dev/null +++ b/examples/web-server/LED.html @@ -0,0 +1,3 @@ +
+

Hello

+
diff --git a/html/web-server.html b/html/web-server.html index c73e59c..4fa5bd4 100644 --- a/html/web-server.html +++ b/html/web-server.html @@ -15,4 +15,4 @@
- \ No newline at end of file + From e36013946e1dcb12c1f790674d404596b4d519ff Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 1 May 2016 08:24:47 +0200 Subject: [PATCH 12/66] Fixed: reinitialize user page after upload --- esp-link/cgiwebserver.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c index de4f3f5..a34737f 100644 --- a/esp-link/cgiwebserver.c +++ b/esp-link/cgiwebserver.c @@ -7,6 +7,7 @@ #include "multipart.h" #include "espfsformat.h" #include "config.h" +#include "web-server.h" int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) { @@ -59,6 +60,7 @@ int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, i { uint32_t magic = ESPFS_MAGIC; spi_flash_write( (int)getUserPageSectionStart(), (uint32_t *)&magic, sizeof(uint32_t) ); + webServerInit(); } break; } From cf1115887b9868fe8871f9441156d24df4d76dc3 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 1 May 2016 12:19:11 +0200 Subject: [PATCH 13/66] Fix issue with invalid user file system --- web-server/web-server.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web-server/web-server.c b/web-server/web-server.c index b7df35e..0d83eb5 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -15,9 +15,10 @@ void ICACHE_FLASH_ATTR webServerBrowseFiles() char buffer[1024]; buffer[0] = 0; - EspFsIterator it; - espFsIteratorInit(userPageCtx, &it); + if( espFsIsValid( userPageCtx ) ) { + EspFsIterator it; + espFsIteratorInit(userPageCtx, &it); while( espFsIteratorNext(&it) ) { int nlen = strlen(it.name); @@ -65,11 +66,10 @@ void ICACHE_FLASH_ATTR webServerBrowseFiles() void ICACHE_FLASH_ATTR webServerInit() { espFsInit(userPageCtx, (void *)getUserPageSectionStart(), ESPFS_FLASH); - if( espFsIsValid( userPageCtx ) ) { + if( espFsIsValid( userPageCtx ) ) os_printf("Valid user file system found!\n"); - webServerBrowseFiles(); - } else os_printf("No user file system found!\n"); + webServerBrowseFiles(); } From 65df1cedc657988e22bcb7748f5213fb070bf79f Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 1 May 2016 17:27:28 +0200 Subject: [PATCH 14/66] Autorefresh page after uploading --- html/web-server.html | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/html/web-server.html b/html/web-server.html index 4fa5bd4..a5a1d41 100644 --- a/html/web-server.html +++ b/html/web-server.html @@ -7,7 +7,7 @@

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.

-
+ The custom web page to upload:
@@ -15,4 +15,15 @@ + + From 53e0e75ecb88942d4ac82a729fbdbd915cf27466 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Thu, 5 May 2016 07:32:46 +0200 Subject: [PATCH 15/66] Dummy web server skeleton --- examples/dummy-web-server.pl | 202 +++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100755 examples/dummy-web-server.pl diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl new file mode 100755 index 0000000..8b72dde --- /dev/null +++ b/examples/dummy-web-server.pl @@ -0,0 +1,202 @@ +#!/usr/bin/perl +use strict; + +use IO::Socket::INET; +use Data::Dumper; +use File::Basename; + +# auto-flush on socket +$| = 1; + +# creating a listening socket +my $server = new IO::Socket::INET ( + LocalHost => '0.0.0.0', + LocalPort => '7777', + Proto => 'tcp', + Listen => 5, + Reuse => 1 +); +die "cannot create socket $!\n" unless $server; +print "server waiting for client connection on port 7777\n"; + + +my $client; + +while ($client = $server->accept()) +{ + my $pid ; + while (not defined ($pid = fork())) + { + sleep 5; + } + if ($pid) + { + close $client; # Only meaningful in the client + } + else + { + $client->autoflush(1); # Always a good idea + close $server; + + my $httpReq = parse_http( $client ); + print Dumper($httpReq); + my $httpResp = process_http( $httpReq ); + print Dumper($httpResp); + + my $data = "HTTP/1.1 " . $httpResp->{code} . " " . $httpResp->{text} . "\r\n"; + + if( exists $httpResp->{fields} ) + { + for my $key( keys %{$httpResp->{fields}} ) + { + $data .= "$key: " . $httpResp->{fields}{$key} . "\r\n"; + } + } + $data .= "\r\n"; + if( exists $httpResp->{body} ) + { + $data .= $httpResp->{body}; + } + + print "$data\n\n"; + $client->send($data); + + if( $httpResp->{done} ) + { + # notify client that response has been sent + #shutdown($client, 1); + } + } +} + +exit(0); + +sub parse_http +{ + my ($client) = @_; + # read up to 1024 characters from the connected client + my $data = ""; + + do{ + my $buf = ""; + $client->recv($buf, 1024); + $data .= $buf; + }while( $data !~ /\r\n\r\n/s ); + #print "Query: $data\n"; + + my %resp; + + my @lines = split /\r\n/, $data; + my $head = shift @lines; + + if( $head =~ /(GET|POST) / ) + { + $resp{method} = $1; + $head =~ s/(GET|POST) //; + if( $head =~ /^([^ ]+) HTTP\/\d\.\d/ ) + { + $resp{url} = $1; + + my %fields; + while( my $arg = shift @lines ) + { + if( $arg =~ /^([\w-]+): (.*)$/ ) + { + $fields{$1} = $2; + } + } + $resp{fields} = \%fields; + } + else + { + $resp{method} = 'ERROR'; + $resp{error} = 'Invalid HTTP request'; + } + } + else + { + $resp{method} = 'ERROR'; + $resp{error} = 'Invalid HTTP request'; + } + + return \%resp; +} + +sub error_response +{ + my ($code, $msg) = @_; + + my %resp; + $resp{code} = $code; + $resp{text} = $msg; + $resp{fields} = {}; + $resp{done} = 1; + + return \%resp; +} + +sub slurp +{ + my ($file) = @_; + + open IF, "<", $file or die "Can't read file: $!"; + my @fc = ; + close(IF); + my $cnt = join("", @fc); + return $cnt; +} + +sub process_http +{ + my ($httpReq) = @_; + if( $httpReq->{method} eq 'ERROR' ) + { + return error_response(400, $httpReq->{error}); + } + + if( $httpReq->{method} eq 'GET' ) + { + my $url = $httpReq->{url}; + $url =~ s/^\///; + + $url = "home.html" if ! $url; + + my $pth = dirname $0; + + if( -f "$pth/../html/$url" ) + { + my $cnt = slurp( "$pth/../html/$url" ); + + if( $url =~ /\.html$/ ) + { + my $prep = slurp( "$pth/../html/head-" ); + $cnt = "$prep$cnt"; + } + + my %resp; + $resp{code} = 200; + $resp{text} = "OK"; + $resp{done} = 1; + $resp{body} = $cnt; + + $resp{fields} = {}; + $resp{fields}{'Content-Length'} = length($cnt); + + $resp{fields}{'Content-Type'} = "text/html; charset=UTF-8" if( $url =~ /\.html$/ ); + $resp{fields}{'Content-Type'} = "text/css" if( $url =~ /\.css$/ ); + $resp{fields}{'Content-Type'} = "text/javascript" if( $url =~ /\.js$/ ); + $resp{fields}{'Content-Type'} = "image/gif" if( $url =~ /\.ico$/ ); + $resp{fields}{'Connection'} = 'close'; + + return \%resp; + } + else + { + return error_response(404, "File not found"); + } + } + + # TODO + + return error_response(400, "Invalid HTTP request"); +} From adab4acb22b55cb60dbdf47f2eeb6b0b79ec6e95 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Fri, 6 May 2016 07:55:21 +0200 Subject: [PATCH 16/66] A bit more implementation in dummy web-server --- examples/dummy-web-server.pl | 98 +++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index 8b72dde..43983f1 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -20,6 +20,13 @@ die "cannot create socket $!\n" unless $server; print "server waiting for client connection on port 7777\n"; +my @webmethods = ( + [ "menu", \&getMenu ], + [ "pins", \&getPins ], + [ "system/info", \&getSystemInfo ], + [ "wifi/info", \&getWifiInfo ], +); + my $client; while ($client = $server->accept()) @@ -39,9 +46,10 @@ while ($client = $server->accept()) close $server; my $httpReq = parse_http( $client ); - print Dumper($httpReq); + #print Dumper($httpReq); + print Dumper($httpReq->{url}); my $httpResp = process_http( $httpReq ); - print Dumper($httpResp); + #print Dumper($httpResp); my $data = "HTTP/1.1 " . $httpResp->{code} . " " . $httpResp->{text} . "\r\n"; @@ -58,7 +66,6 @@ while ($client = $server->accept()) $data .= $httpResp->{body}; } - print "$data\n\n"; $client->send($data); if( $httpResp->{done} ) @@ -146,6 +153,29 @@ sub slurp return $cnt; } +sub content_response +{ + my ($content, $url) = @_; + + my %resp; + $resp{code} = 200; + $resp{text} = "OK"; + $resp{done} = 1; + $resp{body} = $content; + + $resp{fields} = {}; + $resp{fields}{'Content-Length'} = length($content); + + $resp{fields}{'Content-Type'} = "text/json"; + $resp{fields}{'Content-Type'} = "text/html; charset=UTF-8" if( $url =~ /\.html$/ ); + $resp{fields}{'Content-Type'} = "text/css" if( $url =~ /\.css$/ ); + $resp{fields}{'Content-Type'} = "text/javascript" if( $url =~ /\.js$/ ); + $resp{fields}{'Content-Type'} = "image/gif" if( $url =~ /\.ico$/ ); + $resp{fields}{'Connection'} = 'close'; + + return \%resp; +} + sub process_http { my ($httpReq) = @_; @@ -172,23 +202,14 @@ sub process_http my $prep = slurp( "$pth/../html/head-" ); $cnt = "$prep$cnt"; } + return content_response($cnt, $url); + } + elsif( grep { $_->[0] eq $url } @webmethods ) + { + my @mth = grep { $_->[0] eq $url } @webmethods; + my $webm = $mth[0]; - my %resp; - $resp{code} = 200; - $resp{text} = "OK"; - $resp{done} = 1; - $resp{body} = $cnt; - - $resp{fields} = {}; - $resp{fields}{'Content-Length'} = length($cnt); - - $resp{fields}{'Content-Type'} = "text/html; charset=UTF-8" if( $url =~ /\.html$/ ); - $resp{fields}{'Content-Type'} = "text/css" if( $url =~ /\.css$/ ); - $resp{fields}{'Content-Type'} = "text/javascript" if( $url =~ /\.js$/ ); - $resp{fields}{'Content-Type'} = "image/gif" if( $url =~ /\.ico$/ ); - $resp{fields}{'Connection'} = 'close'; - - return \%resp; + return content_response( $webm->[1]->(), $url ); } else { @@ -200,3 +221,42 @@ sub process_http return error_response(400, "Invalid HTTP request"); } + +sub getMenu +{ + my $out = sprintf( + "{ " . + "\"menu\": [ " . + "\"Home\", \"/home.html\", " . + "\"WiFi Station\", \"/wifi/wifiSta.html\", " . + "\"WiFi Soft-AP\", \"/wifi/wifiAp.html\", " . + "\"µC Console\", \"/console.html\", " . + "\"Services\", \"/services.html\", " . +#ifdef MQTT + "\"REST/MQTT\", \"/mqtt.html\", " . +#endif + "\"Debug log\", \"/log.html\", " . + "\"Web Server\", \"/web-server.html\"" . + "%s" . + " ], " . + "\"version\": \"%s\", " . + "\"name\": \"%s\"" . + " }", "", "dummy", "dummy-esp-link"); + + return $out; +} + +sub getPins +{ + return '{ "reset":12, "isp":-1, "conn":-1, "ser":2, "swap":0, "rxpup":1 }'; +} + +sub getSystemInfo +{ + return '{ "name": "esp-link-dummy", "reset cause": "6=external", "size": "4MB:512/512", "upload-size": "3145728", "id": "0xE0 0x4016", "partition": "user2.bin", "slip": "disabled", "mqtt": "disabled/disconnected", "baud": "57600", "description": "" }'; +} + +sub getWifiInfo +{ + return '{"mode": "STA", "modechange": "yes", "ssid": "DummySSID", "status": "got IP address", "phy": "11n", "rssi": "-45dB", "warn": "Switch to STA+AP mode", "apwarn": "Switch to STA+AP mode", "mac":"12:34:56:78:9a:bc", "chan":"11", "apssid": "ESP_012345", "appass": "", "apchan": "11", "apmaxc": "4", "aphidd": "disabled", "apbeac": "100", "apauth": "OPEN","apmac":"12:34:56:78:9a:bc", "ip": "192.168.1.2", "netmask": "255.255.255.0", "gateway": "192.168.1.1", "hostname": "esp-link", "staticip": "0.0.0.0", "dhcp": "on"}'; +} From c6d9f92212f3d6b42d920c6bb647307d82a7e097 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Fri, 6 May 2016 19:39:57 +0200 Subject: [PATCH 17/66] User pages on the dummy-web-server --- examples/dummy-web-server.pl | 64 ++++++++++++++++++++++++++++++++++-- examples/head-user- | 10 ++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 examples/head-user- diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index 43983f1..18b088f 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -47,7 +47,6 @@ while ($client = $server->accept()) my $httpReq = parse_http( $client ); #print Dumper($httpReq); - print Dumper($httpReq->{url}); my $httpResp = process_http( $httpReq ); #print Dumper($httpResp); @@ -204,6 +203,17 @@ sub process_http } return content_response($cnt, $url); } + if( -f "$pth/web-server/$url" ) + { + my $cnt = slurp( "$pth/web-server/$url" ); + + if( $url =~ /\.html$/ ) + { + my $prep = slurp( "$pth/head-user-" ); + $cnt = "$prep$cnt"; + } + return content_response($cnt, $url); + } elsif( grep { $_->[0] eq $url } @webmethods ) { my @mth = grep { $_->[0] eq $url } @webmethods; @@ -241,7 +251,7 @@ sub getMenu " ], " . "\"version\": \"%s\", " . "\"name\": \"%s\"" . - " }", "", "dummy", "dummy-esp-link"); + " }", readUserPages(), "dummy", "dummy-esp-link"); return $out; } @@ -260,3 +270,53 @@ sub getWifiInfo { return '{"mode": "STA", "modechange": "yes", "ssid": "DummySSID", "status": "got IP address", "phy": "11n", "rssi": "-45dB", "warn": "Switch to STA+AP mode", "apwarn": "Switch to STA+AP mode", "mac":"12:34:56:78:9a:bc", "chan":"11", "apssid": "ESP_012345", "appass": "", "apchan": "11", "apmaxc": "4", "aphidd": "disabled", "apbeac": "100", "apauth": "OPEN","apmac":"12:34:56:78:9a:bc", "ip": "192.168.1.2", "netmask": "255.255.255.0", "gateway": "192.168.1.1", "hostname": "esp-link", "staticip": "0.0.0.0", "dhcp": "on"}'; } + +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; +} + +sub readUserPages +{ + my $pth = dirname $0; + my @files = read_dir_structure( "$pth/web-server", "/" ); + + @files = grep { $_ =~ /\.html$/ } @files; + + my $add = ''; + for my $f ( @files ) + { + my $nam = $f; + $nam =~ s/\.html$//; + $nam =~ s/[^\/]*\///g; + $add .= ", \"$nam\", \"$f\""; + } + + return $add; +} diff --git a/examples/head-user- b/examples/head-user- new file mode 100644 index 0000000..514ff53 --- /dev/null +++ b/examples/head-user- @@ -0,0 +1,10 @@ + + + esp-link + + + + + + +
From 1f01668330993b417f2944cd38c44c82ed4f1155 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 07:37:34 +0200 Subject: [PATCH 18/66] Button + text field handling --- examples/dummy-web-server.pl | 77 ++++++++++++++++++++++++++++++------ examples/head-user- | 1 + examples/web-server/LED.html | 14 ++++++- html/userpage.js | 49 +++++++++++++++++++++++ 4 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 html/userpage.js diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index 18b088f..a8556d7 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -1,10 +1,16 @@ #!/usr/bin/perl use strict; +use threads; +use threads::shared; + use IO::Socket::INET; use Data::Dumper; use File::Basename; +my $ledLabel : shared = "LED is turned off"; + + # auto-flush on socket $| = 1; @@ -31,17 +37,7 @@ my $client; while ($client = $server->accept()) { - my $pid ; - while (not defined ($pid = fork())) - { - sleep 5; - } - if ($pid) - { - close $client; # Only meaningful in the client - } - else - { + threads->create( sub { $client->autoflush(1); # Always a good idea close $server; @@ -72,7 +68,8 @@ while ($client = $server->accept()) # notify client that response has been sent #shutdown($client, 1); } - } + } ); + close $client; # Only meaningful in the client } exit(0); @@ -101,7 +98,13 @@ sub parse_http $head =~ s/(GET|POST) //; if( $head =~ /^([^ ]+) HTTP\/\d\.\d/ ) { - $resp{url} = $1; + my $args = $1; + my $u = $args; + $u =~ s/\?.*$//g; + $args =~ s/^.*\?//g; + my %arg = split /[=\&]/, $args; + $resp{urlArgs} = \%arg; + $resp{url} = $u; my %fields; while( my $arg = shift @lines ) @@ -183,6 +186,18 @@ sub process_http return error_response(400, $httpReq->{error}); } + if( $httpReq->{url} =~ /\.json$/ ) + { + my $url = $httpReq->{url}; + $url =~ s/\.json$//; + my $pth = dirname $0; + + if( -f "$pth/web-server/$url" ) + { + return process_user_comm($httpReq); + } + } + if( $httpReq->{method} eq 'GET' ) { my $url = $httpReq->{url}; @@ -320,3 +335,39 @@ sub readUserPages return $add; } + +sub process_user_comm_led +{ + my ($http) = @_; + + if( $http->{urlArgs}{reason} eq "button" ) + { + my $btn = $http->{urlArgs}{id}; + + if($btn eq "btn_on" ) + { + $ledLabel = "LED is turned on"; + } + elsif($btn eq "btn_blink" ) + { + $ledLabel = "LED is blinking"; + } + elsif($btn eq "btn_off" ) + { + $ledLabel = "LED is turned off"; + } + } + + my $r = '{"text": "' . $ledLabel . '"}'; + return content_response($r, $http->{url}); +} + +sub process_user_comm() +{ + my ($http) = @_; + + if( $http->{url} eq '/LED.html.json' ) + { + return process_user_comm_led($http); + } +} diff --git a/examples/head-user- b/examples/head-user- index 514ff53..ec6715f 100644 --- a/examples/head-user- +++ b/examples/head-user- @@ -5,6 +5,7 @@ +
diff --git a/examples/web-server/LED.html b/examples/web-server/LED.html index 4c88681..159fa94 100644 --- a/examples/web-server/LED.html +++ b/examples/web-server/LED.html @@ -1,3 +1,13 @@ -
-

Hello

+
+

LED configuration

+ +
+

+ + + +

+

+

+ diff --git a/html/userpage.js b/html/userpage.js new file mode 100644 index 0000000..3cf3e59 --- /dev/null +++ b/html/userpage.js @@ -0,0 +1,49 @@ +//===== Java script for user pages + + +function notifyResponse( data ) +{ + Object.keys(data).forEach(function(v) { + var elem = document.getElementById(v); + if( elem != null ) + { + if(elem.tagName == "P" || elem.tagName == "DIV") + { + elem.innerHTML = data[v]; + } + } + }); +} + +function notifyButtonPressed( btnId ) +{ + ajaxJson("POST", window.location.pathname + ".json?reason=button\&id=" + btnId, notifyResponse); +} + +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; + } + } + + // load variables at first time + var loadVariables = function() { + ajaxJson("GET", window.location.pathname + ".json?reason=load", notifyResponse, + function () { setTimeout(loadVariables, 1000); } + ); + }; + loadVariables(); +}); From 7a3cea15cc54c39e071b59ac22d2d384f8fea944 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 08:32:52 +0200 Subject: [PATCH 19/66] Input range control --- examples/dummy-web-server.pl | 46 +++++++++++++++++++++++++++++------- examples/web-server/LED.html | 7 ++++++ html/userpage.js | 25 +++++++++++++++++++- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index a8556d7..5b2a6a0 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -9,6 +9,7 @@ use Data::Dumper; use File::Basename; my $ledLabel : shared = "LED is turned off"; +my $ledFreq : shared = 10; # auto-flush on socket @@ -85,7 +86,6 @@ sub parse_http $client->recv($buf, 1024); $data .= $buf; }while( $data !~ /\r\n\r\n/s ); - #print "Query: $data\n"; my %resp; @@ -121,6 +121,23 @@ sub parse_http $resp{method} = 'ERROR'; $resp{error} = 'Invalid HTTP request'; } + + if( $resp{method} eq 'POST' ) + { + my $remaining = join("\r\n", @lines); + my $cnt_len = $resp{fields}{'Content-Length'}; + + while( length($remaining) < $cnt_len ) + { + my $buf = ""; + $client->recv($buf, 1024); + $remaining .= $buf; + } + + $resp{postData} = $remaining; + my %pargs = split /[=\&]/, $remaining; + $resp{postArgs} = \%pargs; + } } else { @@ -131,7 +148,7 @@ sub parse_http return \%resp; } -sub error_response +sub simple_response { my ($code, $msg) = @_; @@ -183,7 +200,7 @@ sub process_http my ($httpReq) = @_; if( $httpReq->{method} eq 'ERROR' ) { - return error_response(400, $httpReq->{error}); + return simple_response(400, $httpReq->{error}); } if( $httpReq->{url} =~ /\.json$/ ) @@ -238,13 +255,11 @@ sub process_http } else { - return error_response(404, "File not found"); + return simple_response(404, "File not found"); } } - # TODO - - return error_response(400, "Invalid HTTP request"); + return simple_response(400, "Invalid HTTP request"); } sub getMenu @@ -339,6 +354,7 @@ sub readUserPages sub process_user_comm_led { my ($http) = @_; + my $loadData = ''; if( $http->{urlArgs}{reason} eq "button" ) { @@ -357,8 +373,20 @@ sub process_user_comm_led $ledLabel = "LED is turned off"; } } - - my $r = '{"text": "' . $ledLabel . '"}'; + elsif( $http->{urlArgs}{reason} eq "submit" ) + { + if( exists $http->{postArgs}{frequency} ) + { + $ledFreq = $http->{postArgs}{frequency}; + } + return simple_response(204, "OK"); + } + elsif( $http->{urlArgs}{reason} eq "load" ) + { + $loadData = ', "frequency": ' . $ledFreq; + } + + my $r = '{"text": "' . $ledLabel . '"' . $loadData . '}'; return content_response($r, $http->{url}); } diff --git a/examples/web-server/LED.html b/examples/web-server/LED.html index 159fa94..7a98b1b 100644 --- a/examples/web-server/LED.html +++ b/examples/web-server/LED.html @@ -9,5 +9,12 @@

+

+

+ Frequency: + + +
+

diff --git a/html/userpage.js b/html/userpage.js index 3cf3e59..1ef001e 100644 --- a/html/userpage.js +++ b/html/userpage.js @@ -4,6 +4,16 @@ 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") + { + el.value = data[v]; + } + } var elem = document.getElementById(v); if( elem != null ) { @@ -38,7 +48,20 @@ document.addEventListener("DOMContentLoaded", function(){ 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"; + } + // load variables at first time var loadVariables = function() { ajaxJson("GET", window.location.pathname + ".json?reason=load", notifyResponse, From a8fad5b26503a763e41e3ba2cfdaadb21675f65e Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 10:29:44 +0200 Subject: [PATCH 20/66] Refresh form data --- examples/dummy-web-server.pl | 37 ++++++++++++++++++++++++++++++------ examples/web-server/LED.html | 3 +++ html/userpage.js | 32 +++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index 5b2a6a0..530c199 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -8,8 +8,10 @@ use IO::Socket::INET; use Data::Dumper; use File::Basename; -my $ledLabel : shared = "LED is turned off"; -my $ledFreq : shared = 10; +my $ledLabel : shared = "LED is turned off"; +my $ledFreq : shared = 10; +my @ledHistory : shared; +my $startTime : shared = time; # auto-flush on socket @@ -20,7 +22,7 @@ my $server = new IO::Socket::INET ( LocalHost => '0.0.0.0', LocalPort => '7777', Proto => 'tcp', - Listen => 5, + Listen => 25, Reuse => 1 ); die "cannot create socket $!\n" unless $server; @@ -67,7 +69,7 @@ while ($client = $server->accept()) if( $httpResp->{done} ) { # notify client that response has been sent - #shutdown($client, 1); + shutdown($client, 1); } } ); close $client; # Only meaningful in the client @@ -351,6 +353,24 @@ sub readUserPages return $add; } +sub led_add_history +{ + my ($msg) = @_; + pop @ledHistory if @ledHistory >= 10; + + my $elapsed = time - $startTime; + my $secs = $elapsed % 60; + my $mins = int($elapsed / 60) % 60; + my $hours = int($elapsed / 3600) % 24; + + $secs = "0$secs" if length($secs) == 1; + $mins = "0$mins" if length($mins) == 1; + $hours = "0$hours" if length($hours) == 1; + + $msg = "$hours:$mins:$secs $msg"; + unshift @ledHistory, $msg; +} + sub process_user_comm_led { my ($http) = @_; @@ -363,14 +383,17 @@ sub process_user_comm_led if($btn eq "btn_on" ) { $ledLabel = "LED is turned on"; + led_add_history("Set LED on"); } elsif($btn eq "btn_blink" ) { $ledLabel = "LED is blinking"; + led_add_history("Set LED blinking"); } elsif($btn eq "btn_off" ) { $ledLabel = "LED is turned off"; + led_add_history("Set LED off"); } } elsif( $http->{urlArgs}{reason} eq "submit" ) @@ -378,6 +401,7 @@ sub process_user_comm_led if( exists $http->{postArgs}{frequency} ) { $ledFreq = $http->{postArgs}{frequency}; + led_add_history("Set LED frequency to $ledFreq Hz"); } return simple_response(204, "OK"); } @@ -385,8 +409,9 @@ sub process_user_comm_led { $loadData = ', "frequency": ' . $ledFreq; } - - my $r = '{"text": "' . $ledLabel . '"' . $loadData . '}'; + + my $list = ", \"led_history\": [" . join(", ", map { "\"$_\"" } @ledHistory ) . "]"; + my $r = '{"text": "' . $ledLabel . '"' . $list . $loadData . '}'; return content_response($r, $http->{url}); } diff --git a/examples/web-server/LED.html b/examples/web-server/LED.html index 7a98b1b..0ff8f44 100644 --- a/examples/web-server/LED.html +++ b/examples/web-server/LED.html @@ -16,5 +16,8 @@

+

+

    +

diff --git a/html/userpage.js b/html/userpage.js index 1ef001e..c8fc735 100644 --- a/html/userpage.js +++ b/html/userpage.js @@ -1,5 +1,6 @@ //===== Java script for user pages +var loadCounter = 0; function notifyResponse( data ) { @@ -21,6 +22,17 @@ function notifyResponse( data ) { elem.innerHTML = data[v]; } + if(elem.tagName == "UL" || elem.tagName == "OL") + { + var list = data[v]; + var html = ""; + + for (var i=0; i" + list[i] + ""); + } + + elem.innerHTML = html; + } } }); } @@ -30,6 +42,20 @@ 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); +} + document.addEventListener("DOMContentLoaded", function(){ // collect buttons var btns = document.getElementsByTagName("button"); @@ -60,6 +86,12 @@ document.addEventListener("DOMContentLoaded", function(){ frm.method = "POST"; frm.action = window.location.pathname + ".json?reason=submit"; + loadCounter = 4; + + frm.onsubmit = function () { + refreshFormData(); + return true; + }; } // load variables at first time From 8a44d16ae093f3babe4174a7e2fa99184b10c06f Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 11:40:52 +0200 Subject: [PATCH 21/66] Added: radio button implementation --- examples/dummy-web-server.pl | 14 +++++++++--- examples/web-server/LED.html | 43 ++++++++++++++++++++++-------------- html/userpage.js | 23 ++++++++++++------- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index 530c199..6743c43 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -12,7 +12,7 @@ my $ledLabel : shared = "LED is turned off"; my $ledFreq : shared = 10; my @ledHistory : shared; my $startTime : shared = time; - +my $pattern : shared = "50_50"; # auto-flush on socket $| = 1; @@ -401,13 +401,21 @@ sub process_user_comm_led if( exists $http->{postArgs}{frequency} ) { $ledFreq = $http->{postArgs}{frequency}; - led_add_history("Set LED frequency to $ledFreq Hz"); + led_add_history("Set frequency to $ledFreq Hz"); + } + if( exists $http->{postArgs}{pattern} ) + { + $pattern = $http->{postArgs}{pattern}; + my $out = $pattern; + $out =~ s/_/\% - /; + $out .= "%"; + led_add_history("Set pattern to $out"); } return simple_response(204, "OK"); } elsif( $http->{urlArgs}{reason} eq "load" ) { - $loadData = ', "frequency": ' . $ledFreq; + $loadData = ', "frequency": ' . $ledFreq . ', "pattern": "' . $pattern . '"'; } my $list = ", \"led_history\": [" . join(", ", map { "\"$_\"" } @ledHistory ) . "]"; diff --git a/examples/web-server/LED.html b/examples/web-server/LED.html index 0ff8f44..d471d76 100644 --- a/examples/web-server/LED.html +++ b/examples/web-server/LED.html @@ -3,21 +3,32 @@
-

- - - -

-

-

-

- Frequency: - - -
-

-

-

    -

    +
    +
    +

    Control

    + + + +

    +

    +
    +

    Frequency and pattern

    +
    + Pattern:
    + 25% on 75% off
    + 50% on 50% off
    + 75% on 25% off
    + + Frequency:
    +
    + +
    +
    +
    +
    +

    Logs

    +
      +
    +
diff --git a/html/userpage.js b/html/userpage.js index c8fc735..9f85a90 100644 --- a/html/userpage.js +++ b/html/userpage.js @@ -12,7 +12,14 @@ function notifyResponse( data ) var el = elems[ndx]; if(el.tagName == "INPUT") { - el.value = data[v]; + if( el.type == "radio" ) + { + el.checked = data[v] == el.value; + } + else + { + el.value = data[v]; + } } } var elem = document.getElementById(v); @@ -20,18 +27,18 @@ function notifyResponse( data ) { if(elem.tagName == "P" || elem.tagName == "DIV") { - elem.innerHTML = data[v]; + elem.innerHTML = data[v]; } if(elem.tagName == "UL" || elem.tagName == "OL") { - var list = data[v]; - var html = ""; + var list = data[v]; + var html = ""; - for (var i=0; i" + list[i] + ""); } - elem.innerHTML = html; + elem.innerHTML = html; } } }); @@ -49,8 +56,8 @@ function refreshFormData() notifyResponse(resp); if( loadCounter > 0 ) { - loadCounter--; - refreshFormData(); + loadCounter--; + refreshFormData(); } } ); } , 250); From ec6dc0c4c36062cea198b54e88affdcea9d1823a Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 12:41:02 +0200 Subject: [PATCH 22/66] Refresh rate implementation --- examples/dummy-web-server.pl | 16 ++++++++++++++++ examples/web-server/GZipped.html.gz | Bin 70 -> 0 bytes examples/web-server/Voltage.html | 10 ++++++++++ html/userpage.js | 21 +++++++++++++++++++++ 4 files changed, 47 insertions(+) delete mode 100644 examples/web-server/GZipped.html.gz create mode 100644 examples/web-server/Voltage.html diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index 6743c43..56015ef 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -423,6 +423,17 @@ sub process_user_comm_led return content_response($r, $http->{url}); } +sub process_user_comm_voltage +{ + my ($http) = @_; + + my $voltage = (((time - $startTime) % 60) - 30) / 30.0 + 4.0; + $voltage = sprintf("%.2f V", $voltage); + + my $r = '{"voltage": "' . $voltage . '"}'; + return content_response($r, $http->{url}); +} + sub process_user_comm() { my ($http) = @_; @@ -431,4 +442,9 @@ sub process_user_comm() { return process_user_comm_led($http); } + + if( $http->{url} eq '/Voltage.html.json' ) + { + return process_user_comm_voltage($http); + } } diff --git a/examples/web-server/GZipped.html.gz b/examples/web-server/GZipped.html.gz deleted file mode 100644 index e9c012cb904e9c9107c4e843e72fcd11459dc711..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70 zcmV-M0J;AkiwFohktJ6E14mkEaByX0E@*UZYyeYGut~`*v*S_#0-Fp&yO4~`Vg(>d cugWYaNKLWP2Z{kDZS=va0Q%qKmo5MR09@P}TmS$7 diff --git a/examples/web-server/Voltage.html b/examples/web-server/Voltage.html new file mode 100644 index 0000000..08e1017 --- /dev/null +++ b/examples/web-server/Voltage.html @@ -0,0 +1,10 @@ + + +
+

Voltage measurement

+
+ +
+

+

+ diff --git a/html/userpage.js b/html/userpage.js index 9f85a90..3a76530 100644 --- a/html/userpage.js +++ b/html/userpage.js @@ -1,6 +1,8 @@ //===== Java script for user pages var loadCounter = 0; +var refreshRate = 0; +var refreshTimer; function notifyResponse( data ) { @@ -42,6 +44,14 @@ function notifyResponse( data ) } } }); + + if( refreshRate != 0 ) + { + clearTimeout(refreshTimer); + refreshTimer = setTimeout( function () { + ajaxJson("GET", window.location.pathname + ".json?reason=refresh", notifyResponse ); + }, refreshRate ); + } } function notifyButtonPressed( btnId ) @@ -101,6 +111,17 @@ document.addEventListener("DOMContentLoaded", function(){ }; } + // 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"); + } + } + // load variables at first time var loadVariables = function() { ajaxJson("GET", window.location.pathname + ".json?reason=load", notifyResponse, From 722f21e88e0add905fdb5becbfea3abaa83477e5 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 14:21:04 +0200 Subject: [PATCH 23/66] Support for tables --- examples/dummy-web-server.pl | 3 ++- examples/web-server/Voltage.html | 3 +++ html/userpage.js | 33 +++++++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index 56015ef..e492933 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -430,7 +430,8 @@ sub process_user_comm_voltage my $voltage = (((time - $startTime) % 60) - 30) / 30.0 + 4.0; $voltage = sprintf("%.2f V", $voltage); - my $r = '{"voltage": "' . $voltage . '"}'; + my $table = ', "table": [["Time", "Min", "AVG", "Max"], ["0s-10s", "1 V", "3 V", "5 V"], ["10s-20s", "1 V", "2 V", "3 V"]]'; + my $r = '{"voltage": "' . $voltage . '"' . $table . '}'; return content_response($r, $http->{url}); } diff --git a/examples/web-server/Voltage.html b/examples/web-server/Voltage.html index 08e1017..96e4820 100644 --- a/examples/web-server/Voltage.html +++ b/examples/web-server/Voltage.html @@ -6,5 +6,8 @@

+ + + diff --git a/html/userpage.js b/html/userpage.js index 3a76530..ca1cbfc 100644 --- a/html/userpage.js +++ b/html/userpage.js @@ -27,7 +27,8 @@ function notifyResponse( data ) var elem = document.getElementById(v); if( elem != null ) { - if(elem.tagName == "P" || elem.tagName == "DIV") + 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]; } @@ -40,6 +41,36 @@ function notifyResponse( data ) html = html.concat("
  • " + list[i] + "
  • "); } + elem.innerHTML = html; + } + if(elem.tagName == "TABLE") + { + var list = data[v]; + var html = ""; + + if( list.length > 0 ) + { + var ths = list[0]; + html = html.concat(""); + + for (var i=0; i" + ths[i] + ""); + } + + html = html.concat(""); + } + + for (var i=1; i"); + + for (var j=0; j" + tds[j] + ""); + } + + html = html.concat(""); + } + elem.innerHTML = html; } } From 200fd0d22e089167c2ec99dbaba314e1d5d89a71 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 15:45:53 +0200 Subject: [PATCH 24/66] Added: user pages without checkbox --- examples/dummy-web-server.pl | 62 +++++++++++++++++++++++++++++++++++ examples/web-server/User.html | 22 +++++++++++++ html/userpage.js | 4 +++ 3 files changed, 88 insertions(+) create mode 100644 examples/web-server/User.html diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index e492933..9d0189d 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -13,6 +13,11 @@ my $ledFreq : shared = 10; my @ledHistory : shared; my $startTime : shared = time; my $pattern : shared = "50_50"; +my $userFname : shared; +my $userLname : shared; +my $userAge : shared; +my $userGender : shared; + # auto-flush on socket $| = 1; @@ -353,6 +358,20 @@ sub readUserPages return $add; } +sub jsonString +{ + my ($text) = @_; + return 'null' if ! defined $text; + return "\"$text\""; +} + +sub jsonNumber +{ + my ($num) = @_; + return 'null' if ! defined $num; + return $num + 0; +} + sub led_add_history { my ($msg) = @_; @@ -435,6 +454,44 @@ sub process_user_comm_voltage return content_response($r, $http->{url}); } +sub process_user_comm_user +{ + my ($http) = @_; + + + if( $http->{urlArgs}{reason} eq "submit" ) + { + if( exists $http->{postArgs}{last_name} ) + { + $userLname = $http->{postArgs}{last_name}; + } + if( exists $http->{postArgs}{first_name} ) + { + $userFname = $http->{postArgs}{first_name}; + } + if( exists $http->{postArgs}{age} ) + { + $userAge = $http->{postArgs}{age}; + } + if( exists $http->{postArgs}{gender} ) + { + $userGender = $http->{postArgs}{gender}; + } + return simple_response(204, "OK"); + } + elsif( $http->{urlArgs}{reason} eq "load" ) + { + my $r = '{"last_name": ' . jsonString($userLname) . + ', "first_name": ' . jsonString($userFname) . + ', "age": ' . jsonNumber($userAge) . + ', "gender": ' . jsonString($userGender) . '}'; + + return content_response($r, $http->{url}); + } + + return content_response("{}", $http->{url}); +} + sub process_user_comm() { my ($http) = @_; @@ -448,4 +505,9 @@ sub process_user_comm() { return process_user_comm_voltage($http); } + + if( $http->{url} eq '/User.html.json' ) + { + return process_user_comm_user($http); + } } diff --git a/examples/web-server/User.html b/examples/web-server/User.html new file mode 100644 index 0000000..d4676b9 --- /dev/null +++ b/examples/web-server/User.html @@ -0,0 +1,22 @@ +
    +

    User setup

    +
    + +
    +
    + First name:
    + Last name:
    + Age: + + Gender: + +
    + Notifications +
    + + +
    + diff --git a/html/userpage.js b/html/userpage.js index ca1cbfc..74fc829 100644 --- a/html/userpage.js +++ b/html/userpage.js @@ -23,6 +23,10 @@ function notifyResponse( data ) el.value = data[v]; } } + if(el.tagName == "SELECT") + { + el.value = data[v]; + } } var elem = document.getElementById(v); if( elem != null ) From e7c06a7586728a6676d3671e1826b87cda324044 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 16:53:30 +0200 Subject: [PATCH 25/66] Checkbox implementation --- examples/dummy-web-server.pl | 14 +++++--- html/userpage.js | 68 ++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/examples/dummy-web-server.pl b/examples/dummy-web-server.pl index 9d0189d..94ba51e 100755 --- a/examples/dummy-web-server.pl +++ b/examples/dummy-web-server.pl @@ -17,6 +17,7 @@ my $userFname : shared; my $userLname : shared; my $userAge : shared; my $userGender : shared; +my $userNotifs : shared; # auto-flush on socket @@ -477,14 +478,19 @@ sub process_user_comm_user { $userGender = $http->{postArgs}{gender}; } + if( exists $http->{postArgs}{notifications} ) + { + $userNotifs = $http->{postArgs}{notifications}; + } return simple_response(204, "OK"); } elsif( $http->{urlArgs}{reason} eq "load" ) { - my $r = '{"last_name": ' . jsonString($userLname) . - ', "first_name": ' . jsonString($userFname) . - ', "age": ' . jsonNumber($userAge) . - ', "gender": ' . jsonString($userGender) . '}'; + my $r = '{"last_name": ' . jsonString($userLname) . + ', "first_name": ' . jsonString($userFname) . + ', "age": ' . jsonNumber($userAge) . + ', "gender": ' . jsonString($userGender) . + ', "notifications":' . jsonString($userNotifs) . '}'; return content_response($r, $http->{url}); } diff --git a/html/userpage.js b/html/userpage.js index 74fc829..74c7a7b 100644 --- a/html/userpage.js +++ b/html/userpage.js @@ -3,6 +3,7 @@ var loadCounter = 0; var refreshRate = 0; var refreshTimer; +var hiddenInputs = []; function notifyResponse( data ) { @@ -18,6 +19,10 @@ function notifyResponse( data ) { el.checked = data[v] == el.value; } + else if( el.type == "checkbox" ) + { + el.checked = data[v] == "on"; + } else { el.value = data[v]; @@ -108,6 +113,34 @@ function 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"); @@ -141,6 +174,7 @@ document.addEventListener("DOMContentLoaded", function(){ loadCounter = 4; frm.onsubmit = function () { + recalculateHiddenInputs(); refreshFormData(); return true; }; @@ -157,6 +191,40 @@ document.addEventListener("DOMContentLoaded", function(){ } } + // 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, From a0ff98b073c3ab3f73874ea8aba433df44aa4881 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 17:20:10 +0200 Subject: [PATCH 26/66] Fixed: user page headers --- createEspFs.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/createEspFs.pl b/createEspFs.pl index 721b3a3..1612098 100755 --- a/createEspFs.pl +++ b/createEspFs.pl @@ -22,7 +22,7 @@ for my $file (@structured) $name =~ s/\.gz$//; } - my $head = 'esp-link
    '; + my $head = 'esp-link
    '; open IF, "<", "$dir/$file" or die "Can't read file: $!"; my @fc = ; From cbd07d1908ebe1cc32aeece1144f26167cbae240 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 7 May 2016 17:41:25 +0200 Subject: [PATCH 27/66] Skeleton for JSON queries --- httpd/httpdespfs.c | 9 +++++++++ web-server/web-server.c | 13 ++++++++++--- web-server/web-server.h | 4 ++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/httpd/httpdespfs.c b/httpd/httpdespfs.c index 9321aad..ea443d1 100644 --- a/httpd/httpdespfs.c +++ b/httpd/httpdespfs.c @@ -13,6 +13,7 @@ Connector to let httpd use the espfs filesystem to serve the files in it. * ---------------------------------------------------------------------------- */ #include "httpdespfs.h" +#include "web-server.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.) @@ -29,6 +30,14 @@ cgiEspFsHook(HttpdConnData *connData) { char acceptEncodingBuffer[64]; int isGzip; + int urlLen = os_strlen(connData->url); + if( urlLen > 5 ) + { + if( os_strcmp( connData->url+urlLen-5, ".json" ) == 0 ) + { + return webServerProcessJsonQuery(connData); + } + } //os_printf("cgiEspFsHook conn=%p conn->conn=%p file=%p\n", connData, connData->conn, file); if (connData->conn==NULL) { diff --git a/web-server/web-server.c b/web-server/web-server.c index 0d83eb5..862436e 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -2,6 +2,7 @@ #include "espfs.h" #include "config.h" +#include "cgi.h" char * webServerPages = NULL; @@ -21,7 +22,7 @@ void ICACHE_FLASH_ATTR webServerBrowseFiles() espFsIteratorInit(userPageCtx, &it); while( espFsIteratorNext(&it) ) { - int nlen = strlen(it.name); + int nlen = os_strlen(it.name); if( nlen >= 6 ) { if( os_strcmp( it.name + nlen-5, ".html" ) == 0 ) @@ -50,7 +51,7 @@ void ICACHE_FLASH_ATTR webServerBrowseFiles() os_strcat(buffer, "\""); } } - if( strlen(buffer) > 600 ) + if( os_strlen(buffer) > 600 ) break; } } @@ -58,7 +59,7 @@ void ICACHE_FLASH_ATTR webServerBrowseFiles() if( webServerPages != NULL ) os_free( webServerPages ); - int len = strlen(buffer) + 1; + int len = os_strlen(buffer) + 1; webServerPages = (char *)os_malloc( len ); os_memcpy( webServerPages, buffer, len ); } @@ -73,3 +74,9 @@ void ICACHE_FLASH_ATTR webServerInit() webServerBrowseFiles(); } +int ICACHE_FLASH_ATTR webServerProcessJsonQuery(HttpdConnData *connData) +{ + os_printf("URL: %s\n", connData->url); + errorResponse(connData, 400, "Slip protocol is not enabled!"); + return HTTPD_CGI_DONE; +} diff --git a/web-server/web-server.h b/web-server/web-server.h index 7716b33..2fdd236 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -3,9 +3,13 @@ #include +#include "httpd.h" + void webServerInit(); char * webServerUserPages(); +int webServerProcessJsonQuery(HttpdConnData *connData); + #endif /* WEB_SERVER_H */ From f989fde3bcc55251f7745a6eefbb883855d002ac Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Tue, 10 May 2016 07:02:41 +0200 Subject: [PATCH 28/66] Button feature is working --- cmd/cmd.h | 3 + examples/arduino/EspLinkSample/EspLink.cpp | 207 ++++++++++++++++++ examples/arduino/EspLinkSample/EspLink.h | 91 ++++++++ .../arduino/EspLinkSample/EspLinkSample.ino | 70 ++++++ examples/arduino/EspLinkSample/WebServer.h | 11 + serial/serbridge.c | 5 + serial/serbridge.h | 2 + web-server/web-server.c | 95 +++++++- web-server/web-server.h | 10 + 9 files changed, 492 insertions(+), 2 deletions(-) create mode 100644 examples/arduino/EspLinkSample/EspLink.cpp create mode 100644 examples/arduino/EspLinkSample/EspLink.h create mode 100644 examples/arduino/EspLinkSample/EspLinkSample.ino create mode 100644 examples/arduino/EspLinkSample/WebServer.h diff --git a/cmd/cmd.h b/cmd/cmd.h index 6b4b018..769326e 100644 --- a/cmd/cmd.h +++ b/cmd/cmd.h @@ -48,6 +48,9 @@ typedef enum { CMD_REST_SETUP = 20, CMD_REST_REQUEST, CMD_REST_SETHEADER, + + CMD_WEB_DATA = 30, + CMD_WEB_REQ_CB, } CmdName; typedef void (*cmdfunc_t)(CmdPacket *cmd); diff --git a/examples/arduino/EspLinkSample/EspLink.cpp b/examples/arduino/EspLinkSample/EspLink.cpp new file mode 100644 index 0000000..7b1a21d --- /dev/null +++ b/examples/arduino/EspLinkSample/EspLink.cpp @@ -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 = (4-((len+2)&3))&3; // 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; +} + diff --git a/examples/arduino/EspLinkSample/EspLink.h b/examples/arduino/EspLinkSample/EspLink.h new file mode 100644 index 0000000..aff43f9 --- /dev/null +++ b/examples/arduino/EspLinkSample/EspLink.h @@ -0,0 +1,91 @@ +#ifndef ESP_LINK_H +#define ESP_LINK_H + +#include +#include + +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 */ + diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino new file mode 100644 index 0000000..b1b199f --- /dev/null +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -0,0 +1,70 @@ + +#include "WebServer.h" +#include "EspLink.h" + +void packetReceived(CmdRequest *req); + +EspLink espLink(Serial, packetReceived); + +void packetReceived(CmdRequest *req) +{ + Serial.println("\nReceived\n"); + uint16_t shrt, port; + espLink.cmdPopArg(req, &shrt, 2); + RequestReason reason = (RequestReason)shrt; + Serial.print("Reason: "); + Serial.println(reason); + + uint8_t ip[4]; + espLink.cmdPopArg(req, &ip, 4); + Serial.print("IP: "); + for(int i=0; i < 4; i++) + { + Serial.print(ip[i], DEC); + if( i != 3 ) + Serial.print("."); + } + Serial.println(); + + espLink.cmdPopArg(req, &port, 2); + Serial.print("Port: "); + Serial.println(port); + + { + uint16_t len = espLink.cmdArgLen(req); + char bf[len+1]; + bf[len] = 0; + espLink.cmdPopArg(req, bf, len); + Serial.print("Url: "); + Serial.println(bf); + } + + switch( reason ) + { + case BUTTON: + { + uint16_t len = espLink.cmdArgLen(req); + char bf[len+1]; + bf[len] = 0; + espLink.cmdPopArg(req, bf, len); + Serial.print("Button: "); + Serial.println(bf); + } + break; + } + +} + +void setup() { + Serial.begin(57600); + + delay(10); + espLink.sendPacketStart(CMD_CB_ADD, 100, 1); + espLink.sendPacketArg(5, (uint8_t *)"webCb"); + espLink.sendPacketEnd(); +} + +void loop() { + espLink.readLoop(); +} + diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/EspLinkSample/WebServer.h new file mode 100644 index 0000000..d0869c1 --- /dev/null +++ b/examples/arduino/EspLinkSample/WebServer.h @@ -0,0 +1,11 @@ +#ifndef WEB_SERVER_H +#define WEB_SERVER_H + +typedef enum { + LOAD=0, + REFRESH, + BUTTON, + SUBMIT, +} RequestReason; + +#endif /* WEB_SERVER_H */ diff --git a/serial/serbridge.c b/serial/serbridge.c index 66b180c..e1f3fdc 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -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 serbridgeInProgramming() +{ + return slip_disabled; +} diff --git a/serial/serbridge.h b/serial/serbridge.h index aad593e..c24953c 100644 --- a/serial/serbridge.h +++ b/serial/serbridge.h @@ -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 serbridgeInProgramming(); + // callback when receiving UART chars when in programming mode extern void (*programmingCB)(char *buffer, short length); diff --git a/web-server/web-server.c b/web-server/web-server.c index 862436e..d8c55b4 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -1,8 +1,18 @@ #include "web-server.h" +#include + #include "espfs.h" #include "config.h" #include "cgi.h" +#include "cmd.h" +#include "serbridge.h" + +#define WEB_CB "webCb" + +static char* web_server_reasons[] = { + "load", "refresh", "button", "submit" +}; char * webServerPages = NULL; @@ -76,7 +86,88 @@ void ICACHE_FLASH_ATTR webServerInit() int ICACHE_FLASH_ATTR webServerProcessJsonQuery(HttpdConnData *connData) { - os_printf("URL: %s\n", connData->url); - errorResponse(connData, 400, "Slip protocol is not enabled!"); + 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( serbridgeInProgramming() ) + { + errorResponse(connData, 500, "Slip disabled at programming mode!"); + 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; + } + + char body[1024]; + int bodyLen = -1; + + switch(reason) + { + case BUTTON: + bodyLen = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); + if( bodyLen <= 0 ) + { + errorResponse(connData, 400, "No button ID specified!"); + return HTTPD_CGI_DONE; + } + break; + case SUBMIT: + { + // TODO + } + break; + case LOAD: + case REFRESH: + default: + break; + } + + os_printf("Web callback to MCU: %s\n", reasonBuf); + + cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, bodyLen >= 0 ? 5 : 4); + uint16_t r = (uint16_t)reason; + cmdResponseBody(&r, sizeof(uint16_t)); + cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); + cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); + cmdResponseBody(connData->url, os_strlen(connData->url)); + if( bodyLen >= 0 ) + cmdResponseBody(body, bodyLen); + cmdResponseEnd(); + + if( reason == SUBMIT ) + { + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + return HTTPD_CGI_DONE; + } + + // TODO return HTTPD_CGI_DONE; } diff --git a/web-server/web-server.h b/web-server/web-server.h index 2fdd236..5c4c594 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -5,6 +5,16 @@ #include "httpd.h" +typedef enum +{ + LOAD=0, + REFRESH, + BUTTON, + SUBMIT, + + INVALID=-1, +} RequestReason; + void webServerInit(); char * webServerUserPages(); From d7ab9ee7bc53832a6a3773424ff64834139b3f04 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Wed, 11 May 2016 07:49:22 +0200 Subject: [PATCH 29/66] Refactored: use different cgi for JSON --- esp-link/main.c | 1 + httpd/httpd.c | 7 +++++-- httpd/httpdespfs.c | 9 --------- web-server/web-server.c | 4 +++- web-server/web-server.h | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/esp-link/main.c b/esp-link/main.c index 73bfac0..7813792 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -99,6 +99,7 @@ HttpdBuiltInUrl builtInUrls[] = { { "/mqtt", cgiMqtt, NULL }, #endif { "/web-server/upload", cgiWebServerUpload, NULL }, + { "*.json", cgiJsonHook, NULL }, //Catch-all cgi JSON queries { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem { NULL, NULL, NULL } }; diff --git a/httpd/httpd.c b/httpd/httpd.c index 8602ce3..2dfacfa 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -355,11 +355,14 @@ 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; diff --git a/httpd/httpdespfs.c b/httpd/httpdespfs.c index ea443d1..9321aad 100644 --- a/httpd/httpdespfs.c +++ b/httpd/httpdespfs.c @@ -13,7 +13,6 @@ Connector to let httpd use the espfs filesystem to serve the files in it. * ---------------------------------------------------------------------------- */ #include "httpdespfs.h" -#include "web-server.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.) @@ -30,14 +29,6 @@ cgiEspFsHook(HttpdConnData *connData) { char acceptEncodingBuffer[64]; int isGzip; - int urlLen = os_strlen(connData->url); - if( urlLen > 5 ) - { - if( os_strcmp( connData->url+urlLen-5, ".json" ) == 0 ) - { - return webServerProcessJsonQuery(connData); - } - } //os_printf("cgiEspFsHook conn=%p conn->conn=%p file=%p\n", connData, connData->conn, file); if (connData->conn==NULL) { diff --git a/web-server/web-server.c b/web-server/web-server.c index d8c55b4..4a4de3a 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -84,8 +84,10 @@ void ICACHE_FLASH_ATTR webServerInit() webServerBrowseFiles(); } -int ICACHE_FLASH_ATTR webServerProcessJsonQuery(HttpdConnData *connData) +int ICACHE_FLASH_ATTR cgiJsonHook(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + if( !flashConfig.slip_enable ) { errorResponse(connData, 400, "Slip processing is disabled!"); diff --git a/web-server/web-server.h b/web-server/web-server.h index 5c4c594..b17373f 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -19,7 +19,7 @@ void webServerInit(); char * webServerUserPages(); -int webServerProcessJsonQuery(HttpdConnData *connData); +int ICACHE_FLASH_ATTR cgiJsonHook(HttpdConnData *connData); #endif /* WEB_SERVER_H */ From c14c214f49e7f3cf2c15b69596de653c981d8011 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Wed, 11 May 2016 08:00:27 +0200 Subject: [PATCH 30/66] JSON hook, keep connection alive --- web-server/web-server.c | 163 +++++++++++++++++++++------------------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/web-server/web-server.c b/web-server/web-server.c index 4a4de3a..654937f 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -88,88 +88,95 @@ int ICACHE_FLASH_ATTR cgiJsonHook(HttpdConnData *connData) { if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. - 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( serbridgeInProgramming() ) - { - errorResponse(connData, 500, "Slip disabled at programming mode!"); - 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; - } + void * cgiData = connData->cgiData; - if( reason == INVALID ) + if( cgiData == NULL ) { - errorResponse(connData, 400, "Invalid reason!"); - return HTTPD_CGI_DONE; - } - - char body[1024]; - int bodyLen = -1; - - switch(reason) - { - case BUTTON: - bodyLen = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); - if( bodyLen <= 0 ) - { - errorResponse(connData, 400, "No button ID specified!"); - return HTTPD_CGI_DONE; - } - break; - case SUBMIT: - { - // TODO - } - break; - case LOAD: - case REFRESH: - default: - break; - } - - os_printf("Web callback to MCU: %s\n", reasonBuf); - - cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, bodyLen >= 0 ? 5 : 4); - uint16_t r = (uint16_t)reason; - cmdResponseBody(&r, sizeof(uint16_t)); - cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); - cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); - cmdResponseBody(connData->url, os_strlen(connData->url)); - if( bodyLen >= 0 ) - cmdResponseBody(body, bodyLen); - cmdResponseEnd(); + 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( serbridgeInProgramming() ) + { + errorResponse(connData, 500, "Slip disabled at programming mode!"); + 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; + } + + char body[1024]; + int bodyLen = -1; + + switch(reason) + { + case BUTTON: + bodyLen = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); + if( bodyLen <= 0 ) + { + errorResponse(connData, 400, "No button ID specified!"); + return HTTPD_CGI_DONE; + } + break; + case SUBMIT: + { + // TODO + } + break; + case LOAD: + case REFRESH: + default: + break; + } + + os_printf("Web callback to MCU: %s\n", reasonBuf); + + cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, bodyLen >= 0 ? 5 : 4); + uint16_t r = (uint16_t)reason; + cmdResponseBody(&r, sizeof(uint16_t)); + cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); + cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); + cmdResponseBody(connData->url, os_strlen(connData->url)); + if( bodyLen >= 0 ) + cmdResponseBody(body, bodyLen); + cmdResponseEnd(); - if( reason == SUBMIT ) - { - httpdStartResponse(connData, 204); - httpdEndHeaders(connData); - return HTTPD_CGI_DONE; + if( reason == SUBMIT ) + { + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + return HTTPD_CGI_DONE; + } + + connData->cgiData = (void *)1; } // TODO - return HTTPD_CGI_DONE; + return HTTPD_CGI_MORE; } From 86c5614446a98b6ca27904526cb0cee1f55471a9 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Wed, 11 May 2016 19:30:08 +0200 Subject: [PATCH 31/66] Refactored web-server --- cmd/cmd.h | 2 +- cmd/handlers.c | 2 ++ esp-link/cgi.c | 2 +- esp-link/cgiwebserver.c | 2 +- esp-link/main.c | 4 ++-- examples/arduino/EspLinkSample/EspLink.h | 2 +- web-server/web-server.c | 14 +++++++++----- web-server/web-server.h | 8 +++++--- 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/cmd/cmd.h b/cmd/cmd.h index 769326e..a73119a 100644 --- a/cmd/cmd.h +++ b/cmd/cmd.h @@ -49,7 +49,7 @@ typedef enum { CMD_REST_REQUEST, CMD_REST_SETHEADER, - CMD_WEB_DATA = 30, + CMD_WEB_JSON_DATA = 30, CMD_WEB_REQ_CB, } CmdName; diff --git a/cmd/handlers.c b/cmd/handlers.c index 9e719a7..834b757 100644 --- a/cmd/handlers.c +++ b/cmd/handlers.c @@ -12,6 +12,7 @@ #ifdef REST #include #endif +#include #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_JSON_DATA, "WEB_JSON_DATA", WEB_JsonData}, }; //===== List of registered callbacks (to uC) diff --git a/esp-link/cgi.c b/esp-link/cgi.c index 5f7ce04..0052d0e 100644 --- a/esp-link/cgi.c +++ b/esp-link/cgi.c @@ -221,7 +221,7 @@ int ICACHE_FLASH_ATTR cgiMenu(HttpdConnData *connData) { "\"version\": \"%s\", " "\"name\": \"%s\"" " }", - webServerUserPages(), esp_link_version, name); + WEB_UserPages(), esp_link_version, name); httpdSend(connData, buff, -1); return HTTPD_CGI_DONE; diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c index a34737f..58dc544 100644 --- a/esp-link/cgiwebserver.c +++ b/esp-link/cgiwebserver.c @@ -60,7 +60,7 @@ int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, i { uint32_t magic = ESPFS_MAGIC; spi_flash_write( (int)getUserPageSectionStart(), (uint32_t *)&magic, sizeof(uint32_t) ); - webServerInit(); + WEB_Init(); } break; } diff --git a/esp-link/main.c b/esp-link/main.c index 7813792..488a6f6 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -99,7 +99,7 @@ HttpdBuiltInUrl builtInUrls[] = { { "/mqtt", cgiMqtt, NULL }, #endif { "/web-server/upload", cgiWebServerUpload, NULL }, - { "*.json", cgiJsonHook, NULL }, //Catch-all cgi JSON queries + { "*.json", WEB_CgiJsonHook, NULL }, //Catch-all cgi JSON queries { "*", cgiEspFsHook, NULL }, //Catch-all cgi function for the filesystem { NULL, NULL, NULL } }; @@ -157,7 +157,7 @@ void user_init(void) { //os_printf("espFsInit %s\n", res?"ERR":"ok"); // mount the http handlers httpdInit(builtInUrls, 80); - webServerInit(); + WEB_Init(); // init the wifi-serial transparent bridge (port 23) serbridgeInit(23, 2323); diff --git a/examples/arduino/EspLinkSample/EspLink.h b/examples/arduino/EspLinkSample/EspLink.h index aff43f9..c13a390 100644 --- a/examples/arduino/EspLinkSample/EspLink.h +++ b/examples/arduino/EspLinkSample/EspLink.h @@ -43,7 +43,7 @@ typedef enum { CMD_REST_REQUEST, CMD_REST_SETHEADER, - CMD_WEB_DATA = 30, + CMD_WEB_JSON_DATA = 30, CMD_WEB_REQ_CB, } CmdName; diff --git a/web-server/web-server.c b/web-server/web-server.c index 654937f..67b1fd8 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -16,12 +16,12 @@ static char* web_server_reasons[] = { char * webServerPages = NULL; -char * ICACHE_FLASH_ATTR webServerUserPages() +char * ICACHE_FLASH_ATTR WEB_UserPages() { return webServerPages; } -void ICACHE_FLASH_ATTR webServerBrowseFiles() +void ICACHE_FLASH_ATTR WEB_BrowseFiles() { char buffer[1024]; buffer[0] = 0; @@ -74,17 +74,17 @@ void ICACHE_FLASH_ATTR webServerBrowseFiles() os_memcpy( webServerPages, buffer, len ); } -void ICACHE_FLASH_ATTR webServerInit() +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"); - webServerBrowseFiles(); + WEB_BrowseFiles(); } -int ICACHE_FLASH_ATTR cgiJsonHook(HttpdConnData *connData) +int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) { if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. @@ -180,3 +180,7 @@ int ICACHE_FLASH_ATTR cgiJsonHook(HttpdConnData *connData) // TODO return HTTPD_CGI_MORE; } + +void ICACHE_FLASH_ATTR WEB_JsonData(CmdPacket *cmd) +{ +} \ No newline at end of file diff --git a/web-server/web-server.h b/web-server/web-server.h index b17373f..34be3db 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -4,6 +4,7 @@ #include #include "httpd.h" +#include "cmd.h" typedef enum { @@ -15,11 +16,12 @@ typedef enum INVALID=-1, } RequestReason; -void webServerInit(); +void WEB_Init(); -char * webServerUserPages(); +char * WEB_UserPages(); -int ICACHE_FLASH_ATTR cgiJsonHook(HttpdConnData *connData); +int WEB_CgiJsonHook(HttpdConnData *connData); +void WEB_JsonData(CmdPacket *cmd); #endif /* WEB_SERVER_H */ From 70a002c04aa1d6d4234ce1018052b1e9af4cb767 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Wed, 11 May 2016 20:40:22 +0200 Subject: [PATCH 32/66] Fixes in command sending --- examples/arduino/EspLinkSample/EspLink.cpp | 2 +- .../arduino/EspLinkSample/EspLinkSample.ino | 8 ++++++ web-server/web-server.c | 27 +++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/examples/arduino/EspLinkSample/EspLink.cpp b/examples/arduino/EspLinkSample/EspLink.cpp index 7b1a21d..0e2972a 100644 --- a/examples/arduino/EspLinkSample/EspLink.cpp +++ b/examples/arduino/EspLinkSample/EspLink.cpp @@ -71,7 +71,7 @@ void EspLink::sendPacketArg(uint16_t len, uint8_t * data) writeBuf((uint8_t*)&len, 2); writeBuf(data, len); - uint16_t pad = (4-((len+2)&3))&3; // get to multiple of 4 + uint16_t pad = ((len+3)&~3) - len; // get to multiple of 4 if (pad > 0) { uint32_t temp = 0; writeBuf((uint8_t*)&temp, pad); diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index b1b199f..81d721c 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -52,6 +52,14 @@ void packetReceived(CmdRequest *req) } break; } + + char * json = "{'last_name': 'helloka'}"; + + espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 3); + espLink.sendPacketArg(4, ip); + espLink.sendPacketArg(2, (uint8_t *)&port); + espLink.sendPacketArg(strlen(json), (uint8_t *)json); + espLink.sendPacketEnd(); } diff --git a/web-server/web-server.c b/web-server/web-server.c index 67b1fd8..5ce1de8 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -177,10 +177,33 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) connData->cgiData = (void *)1; } - // TODO return HTTPD_CGI_MORE; } void ICACHE_FLASH_ATTR WEB_JsonData(CmdPacket *cmd) { -} \ No newline at end of file + CmdRequest req; + cmdRequest(&req, cmd); + + os_printf("JSON data\n"); // TODO + + if (cmdGetArgc(&req) != 3) return; + + uint8_t ip[4]; + cmdPopArg(&req, ip, 4); + os_printf("IP:%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); // TODO + + uint16_t port; + cmdPopArg(&req, &port, 2); + os_printf("Port:%d\n", port); // TODO + + int16_t len = cmdArgLen(&req); + os_printf("Len:%d\n", len); // TODO + uint8_t json[len+1]; + json[len] = 0; + cmdPopArg(&req, json, len); + + os_printf("%s\n", json); // TODO + + // TODO +} From 87718ead287d46a2b9b29c4141422f3a8c77e4f6 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Thu, 12 May 2016 18:41:08 +0200 Subject: [PATCH 33/66] Implemented: JSON response from MCU --- httpd/httpd.c | 29 +++++++++++++++++++++++++++++ httpd/httpd.h | 1 + web-server/web-server.c | 21 +++++++++++++-------- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 2dfacfa..73c187c 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -626,3 +626,32 @@ void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) { espconn_accept(&httpdConn); espconn_tcp_set_max_con_allow(&httpdConn, MAX_CONN); } + +int ICACHE_FLASH_ATTR httpdNotify(uint8_t * ip, int port, const void * cgiArg) { + int i; + + for (i = 0; iconn == 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; + + char sendBuff[MAX_SENDBUFF_LEN]; + conn->priv->sendBuff = sendBuff; + conn->priv->sendBuffLen = 0; + + conn->cgiArg = cgiArg; + httpdProcessRequest(conn); + conn->cgiArg = NULL; + + return HTTPD_CGI_DONE; + } + return HTTPD_CGI_NOTFOUND; +} diff --git a/httpd/httpd.h b/httpd/httpd.h index 33a0700..1b0ed38 100644 --- a/httpd/httpd.h +++ b/httpd/httpd.h @@ -66,5 +66,6 @@ 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); +int ICACHE_FLASH_ATTR httpdNotify(uint8_t * ip, int port, const void * cgiArg); #endif diff --git a/web-server/web-server.c b/web-server/web-server.c index 5ce1de8..8597dcb 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -177,6 +177,18 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) connData->cgiData = (void *)1; } + if( connData->cgiArg != NULL ) // arrived data from MCU + { + noCacheHeaders(connData, 200); + httpdHeader(connData, "Content-Type", "application/json"); + char cl[16]; + os_sprintf(cl, "%d", os_strlen(connData->cgiArg)); + httpdHeader(connData, "Content-Length", cl); + httpdEndHeaders(connData); + httpdSend(connData, connData->cgiArg, os_strlen(connData->cgiArg)); + return HTTPD_CGI_DONE; + } + return HTTPD_CGI_MORE; } @@ -185,25 +197,18 @@ void ICACHE_FLASH_ATTR WEB_JsonData(CmdPacket *cmd) CmdRequest req; cmdRequest(&req, cmd); - os_printf("JSON data\n"); // TODO - if (cmdGetArgc(&req) != 3) return; uint8_t ip[4]; cmdPopArg(&req, ip, 4); - os_printf("IP:%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); // TODO uint16_t port; cmdPopArg(&req, &port, 2); - os_printf("Port:%d\n", port); // TODO int16_t len = cmdArgLen(&req); - os_printf("Len:%d\n", len); // TODO uint8_t json[len+1]; json[len] = 0; cmdPopArg(&req, json, len); - os_printf("%s\n", json); // TODO - - // TODO + httpdNotify(ip, port, json); } From c7785babf40517e3bce078ce973d42779ce34f89 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Thu, 12 May 2016 20:15:58 +0200 Subject: [PATCH 34/66] Implemented submit --- .../arduino/EspLinkSample/EspLinkSample.ino | 8 +- web-server/web-server.c | 91 ++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index 81d721c..94a50f6 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -42,18 +42,22 @@ void packetReceived(CmdRequest *req) switch( reason ) { case BUTTON: + case SUBMIT: { uint16_t len = espLink.cmdArgLen(req); char bf[len+1]; bf[len] = 0; espLink.cmdPopArg(req, bf, len); - Serial.print("Button: "); + Serial.print("Arg: "); Serial.println(bf); + + if( reason == SUBMIT ) + return; } break; } - char * json = "{'last_name': 'helloka'}"; + char * json = "{\"last_name\": \"helloka\"}"; espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 3); espLink.sendPacketArg(4, ip); diff --git a/web-server/web-server.c b/web-server/web-server.c index 8597dcb..4c5437c 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -84,6 +84,36 @@ void ICACHE_FLASH_ATTR WEB_Init() WEB_BrowseFiles(); } +int ICACHE_FLASH_ATTR WEB_addJsonString(char * str, char * buf, int maxLen) +{ + char * start = buf; + if( maxLen < 10 ) + return -1; + char * endp = start + maxLen - 10; + + *buf++ = '"'; + + int len = os_strlen(str); + char avalbuf[len*2+1]; + char * valbuf = avalbuf; + valbuf[len*2] = 0; + + httpdUrlDecode(str, len, valbuf, len * 2); + + while(*valbuf) + { + if( *valbuf == '"' || *valbuf == '\\' ) + *buf++ = '\\'; + *buf++ = *(valbuf++); + + if( buf > endp ) + return -1; + } + + *buf++ = '"'; + return buf - start; +} + int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) { if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. @@ -146,7 +176,66 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) break; case SUBMIT: { - // TODO + if( connData->post->received < connData->post->len ) + { + errorResponse(connData, 400, "Post too large!"); + return HTTPD_CGI_DONE; + } + + bodyLen = 0; + + int bptr = 0; + + body[bodyLen++] = '{'; + + 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; + char * value = val+1; + + int alen = WEB_addJsonString(name, body + bodyLen, sizeof(body) - bodyLen); + if( alen == -1 ) + { + errorResponse(connData, 400, "Post too large!"); + return HTTPD_CGI_DONE; + } + bodyLen += alen; + body[bodyLen++] = ':'; + alen = WEB_addJsonString(value, body + bodyLen, sizeof(body) - bodyLen); + if( alen == -1 ) + { + errorResponse(connData, 400, "Post too large!"); + return HTTPD_CGI_DONE; + } + bodyLen += alen; + body[bodyLen++] = ','; + } + } + + body[bodyLen++] = '}'; } break; case LOAD: From 9a0a78d85e73182a1277ea23fed5a14b803add6b Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Fri, 13 May 2016 21:22:04 +0200 Subject: [PATCH 35/66] Refactored: don't use json for MCU, it's too resouce consuming --- .../arduino/EspLinkSample/EspLinkSample.ino | 30 ++- web-server/web-server.c | 202 ++++++++++++------ web-server/web-server.h | 10 + 3 files changed, 172 insertions(+), 70 deletions(-) diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index 94a50f6..5fe59bd 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -42,7 +42,6 @@ void packetReceived(CmdRequest *req) switch( reason ) { case BUTTON: - case SUBMIT: { uint16_t len = espLink.cmdArgLen(req); char bf[len+1]; @@ -50,19 +49,38 @@ void packetReceived(CmdRequest *req) espLink.cmdPopArg(req, bf, len); Serial.print("Arg: "); Serial.println(bf); + } + break; + case SUBMIT: + { + int arg = 4; + while( espLink.cmdGetArgc(req) > arg ) + { + arg++; - if( reason == SUBMIT ) + uint16_t len = espLink.cmdArgLen(req); + char bf[len+1]; + bf[len] = 0; + espLink.cmdPopArg(req, bf, len); + + Serial.print(bf + 1); + Serial.print(" -> "); + Serial.println(bf + strlen(bf+1) + 2); + } return; } - break; } - char * json = "{\"last_name\": \"helloka\"}"; - espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 3); espLink.sendPacketArg(4, ip); espLink.sendPacketArg(2, (uint8_t *)&port); - espLink.sendPacketArg(strlen(json), (uint8_t *)json); + + char outBuf[30]; + outBuf[0] = 0; + strcpy(outBuf+1, "last_name"); + strcpy(outBuf+11,"helloka"); + espLink.sendPacketArg(19, (uint8_t *)outBuf); + espLink.sendPacketEnd(); } diff --git a/web-server/web-server.c b/web-server/web-server.c index 4c5437c..877ff2e 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -10,6 +10,8 @@ #define WEB_CB "webCb" +#define MAX_VARS 20 + static char* web_server_reasons[] = { "load", "refresh", "button", "submit" }; @@ -84,36 +86,6 @@ void ICACHE_FLASH_ATTR WEB_Init() WEB_BrowseFiles(); } -int ICACHE_FLASH_ATTR WEB_addJsonString(char * str, char * buf, int maxLen) -{ - char * start = buf; - if( maxLen < 10 ) - return -1; - char * endp = start + maxLen - 10; - - *buf++ = '"'; - - int len = os_strlen(str); - char avalbuf[len*2+1]; - char * valbuf = avalbuf; - valbuf[len*2] = 0; - - httpdUrlDecode(str, len, valbuf, len * 2); - - while(*valbuf) - { - if( *valbuf == '"' || *valbuf == '\\' ) - *buf++ = '\\'; - *buf++ = *(valbuf++); - - if( buf > endp ) - return -1; - } - - *buf++ = '"'; - return buf - start; -} - int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) { if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. @@ -161,18 +133,23 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) return HTTPD_CGI_DONE; } - char body[1024]; - int bodyLen = -1; + char body[1024]; + int bodyPtr = 0; + int argNum = 0; + char *argPos[MAX_VARS]; + int argLen[MAX_VARS]; switch(reason) { case BUTTON: - bodyLen = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); - if( bodyLen <= 0 ) + argLen[0] = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); + if( argLen[0] <= 0 ) { errorResponse(connData, 400, "No button ID specified!"); return HTTPD_CGI_DONE; } + argPos[0] = body; + argNum++; break; case SUBMIT: { @@ -182,14 +159,16 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) return HTTPD_CGI_DONE; } - bodyLen = 0; - int bptr = 0; - body[bodyLen++] = '{'; - while( bptr < connData->post->len ) { + if( argNum >= MAX_VARS ) + { + errorResponse(connData, 400, "Too many variables!"); + return HTTPD_CGI_DONE; + } + char * line = connData->post->buff + bptr; char * eo = os_strchr(line, '&' ); @@ -216,26 +195,29 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) char * name = line; char * value = val+1; - int alen = WEB_addJsonString(name, body + bodyLen, sizeof(body) - bodyLen); - if( alen == -1 ) - { - errorResponse(connData, 400, "Post too large!"); - return HTTPD_CGI_DONE; - } - bodyLen += alen; - body[bodyLen++] = ':'; - alen = WEB_addJsonString(value, body + bodyLen, sizeof(body) - bodyLen); - if( alen == -1 ) + int namLen = os_strlen(name); + int valLen = os_strlen(value); + + int totallen = namLen + valLen + 2; + if( bodyPtr + totallen > sizeof(body) - 10 ) { errorResponse(connData, 400, "Post too large!"); return HTTPD_CGI_DONE; } - bodyLen += alen; - body[bodyLen++] = ','; + + argPos[argNum] = body + bodyPtr; + + body[bodyPtr++] = (char)WEB_STRING; + os_strcpy( body + bodyPtr, name ); + bodyPtr += namLen; + body[bodyPtr++] = 0; + + os_strcpy( body + bodyPtr, value ); + bodyPtr += valLen; + + argLen[argNum++] = totallen; } } - - body[bodyLen++] = '}'; } break; case LOAD: @@ -246,14 +228,17 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) os_printf("Web callback to MCU: %s\n", reasonBuf); - cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, bodyLen >= 0 ? 5 : 4); + cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, 4 + argNum); uint16_t r = (uint16_t)reason; cmdResponseBody(&r, sizeof(uint16_t)); cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); cmdResponseBody(connData->url, os_strlen(connData->url)); - if( bodyLen >= 0 ) - cmdResponseBody(body, bodyLen); + + int j; + for( j=0; j < argNum; j++ ) + cmdResponseBody(argPos[j], argLen[j]); + cmdResponseEnd(); if( reason == SUBMIT ) @@ -268,13 +253,107 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) if( connData->cgiArg != NULL ) // arrived data from MCU { + char jsonBuf[1500]; + int jsonPtr = 0; + + + jsonBuf[jsonPtr++] = '{'; + CmdRequest * req = (CmdRequest *)(connData->cgiArg); + + int c = 2; + while( c++ < cmdGetArgc(req) ) + { + if( c < 3 ) // skip the first argument + jsonBuf[jsonPtr++] = ','; + + int len = cmdArgLen(req); + char buf[len+1]; + buf[len] = 0; + + cmdPopArg(req, buf, len); + + 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", os_strlen(connData->cgiArg)); + os_sprintf(cl, "%d", jsonPtr); httpdHeader(connData, "Content-Length", cl); httpdEndHeaders(connData); - httpdSend(connData, connData->cgiArg, os_strlen(connData->cgiArg)); + + httpdSend(connData, jsonBuf, jsonPtr); return HTTPD_CGI_DONE; } @@ -286,7 +365,7 @@ void ICACHE_FLASH_ATTR WEB_JsonData(CmdPacket *cmd) CmdRequest req; cmdRequest(&req, cmd); - if (cmdGetArgc(&req) != 3) return; + if (cmdGetArgc(&req) < 3) return; uint8_t ip[4]; cmdPopArg(&req, ip, 4); @@ -294,10 +373,5 @@ void ICACHE_FLASH_ATTR WEB_JsonData(CmdPacket *cmd) uint16_t port; cmdPopArg(&req, &port, 2); - int16_t len = cmdArgLen(&req); - uint8_t json[len+1]; - json[len] = 0; - cmdPopArg(&req, json, len); - - httpdNotify(ip, port, json); + httpdNotify(ip, port, &req); } diff --git a/web-server/web-server.h b/web-server/web-server.h index 34be3db..e063799 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -16,6 +16,16 @@ typedef enum INVALID=-1, } RequestReason; +typedef enum +{ + WEB_STRING=0, + WEB_NULL, + WEB_INTEGER, + WEB_BOOLEAN, + WEB_FLOAT, + WEB_JSON +} WebValueType; + void WEB_Init(); char * WEB_UserPages(); From cc8a5dd55ba9e7dfe5fb632f094be786662dc350 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 14 May 2016 19:37:09 +0200 Subject: [PATCH 36/66] LED on / off and blinking --- .../arduino/EspLinkSample/EspLinkSample.ino | 83 +++++++++- examples/arduino/EspLinkSample/WebServer.cpp | 150 ++++++++++++++++++ examples/arduino/EspLinkSample/WebServer.h | 75 ++++++++- web-server/web-server.c | 5 +- 4 files changed, 307 insertions(+), 6 deletions(-) create mode 100644 examples/arduino/EspLinkSample/WebServer.cpp diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index 5fe59bd..937cc07 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -1,7 +1,88 @@ #include "WebServer.h" + +#define LED_PIN 13 + +int8_t blinking = 0; +int8_t frequency = 10; +uint16_t elapse = 100; +uint32_t next_ts = 0; + +void ledHtmlCallback(WebServerCommand command, char * data, int dataLen); + +const char ledURL[] PROGMEM = "/LED.html.json"; + +const WebMethod PROGMEM methods[] = { + { ledURL, ledHtmlCallback }, + { NULL, NULL }, +}; + +WebServer webServer(Serial, methods); + +void setup() +{ + Serial.begin(57600); + webServer.init(); + + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, false); +} + +void loop() +{ + webServer.loop(); + + if( blinking ) + { + if( next_ts <= millis() ) + { + digitalWrite(LED_PIN, !digitalRead(LED_PIN)); + next_ts += elapse; + } + } +} + + +void ledHtmlCallback(WebServerCommand command, char * data, int dataLen) +{ + switch(command) + { + case BUTTON_PRESS: + if( strcmp_P(data, PSTR("btn_on") ) == 0 ) + { + blinking = 0; + digitalWrite(LED_PIN, true); + } else if( strcmp_P(data, PSTR("btn_off") ) == 0 ) + { + blinking = 0; + digitalWrite(LED_PIN, false); + } else if( strcmp_P(data, PSTR("btn_blink") ) == 0 ) + { + blinking = 1; + next_ts = millis() + elapse; + } + break; + case LOAD: + webServer.setArgNum(1); + case REFRESH: + if( command == REFRESH ) + webServer.setArgNum(1); + + 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")); + break; + default: + break; + } +} + + +/* #include "EspLink.h" + void packetReceived(CmdRequest *req); EspLink espLink(Serial, packetReceived); @@ -97,4 +178,4 @@ void setup() { void loop() { espLink.readLoop(); } - +*/ diff --git a/examples/arduino/EspLinkSample/WebServer.cpp b/examples/arduino/EspLinkSample/WebServer.cpp new file mode 100644 index 0000000..df7e50f --- /dev/null +++ b/examples/arduino/EspLinkSample/WebServer.cpp @@ -0,0 +1,150 @@ +#include "WebServer.h" + +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) +{ + instance = this; +} + +void WebServer::init() +{ + registerCallback(); +} + +void WebServer::loop() +{ + // TODO: resubscribe periodically + espLink.readLoop(); +} + +void WebServer::registerCallback() +{ + espLink.sendPacketStart(CMD_CB_ADD, 100, 1); + espLink.sendPacketArg(5, (uint8_t *)"webCb"); + espLink.sendPacketEnd(); +} + +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: + { + /* TODO: iterate through fields */ + } + return; + default: + break; + } + + args_to_send = -1; + method->callback( reason == WS_LOAD ? LOAD : REFRESH, NULL, 0); + + if( args_to_send == -1 ) + { + espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 2); + espLink.sendPacketArg(4, remote_ip); + espLink.sendPacketArg(2, (uint8_t *)&remote_port); + } + while( args_to_send-- > 0 ) + espLink.sendPacketArg(0, NULL); + espLink.sendPacketEnd(); +} + +void WebServer::handleRequest(CmdRequest *req) +{ + 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_JSON_DATA, 100, 2); + espLink.sendPacketArg(4, remote_ip); + espLink.sendPacketArg(2, (uint8_t *)&remote_port); + espLink.sendPacketEnd(); +} + +void WebServer::setArgNum(uint8_t num) +{ + espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 2 + (args_to_send = num)); + espLink.sendPacketArg(4, remote_ip); + espLink.sendPacketArg(2, (uint8_t *)&remote_port); +} + +void WebServer::setArgString(const char * name, const char * value) +{ + if( args_to_send <= 0 ) + return; + + 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); + + args_to_send--; +} + +void WebServer::setArgStringP(const char * name, const char * value) +{ + if( args_to_send <= 0 ) + return; + + 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); + + args_to_send--; +} + diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/EspLinkSample/WebServer.h index d0869c1..0914b50 100644 --- a/examples/arduino/EspLinkSample/WebServer.h +++ b/examples/arduino/EspLinkSample/WebServer.h @@ -1,11 +1,78 @@ #ifndef WEB_SERVER_H #define WEB_SERVER_H -typedef enum { - LOAD=0, +#include "EspLink.h" + +typedef enum +{ + BUTTON_PRESS, + SET_FIELD, REFRESH, - BUTTON, - SUBMIT, + 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; + + int16_t args_to_send; + + protected: + EspLink espLink; + + public: + WebServer(Stream &stream, const WebMethod * PROGMEM methods); + + void init(); + void loop(); + + void registerCallback(); + + static WebServer * getInstance() { return instance; } + uint8_t * getRemoteIp() { return remote_ip; } + uint16_t getRemotePort() { return remote_port; } + + void setArgNum(uint8_t num); + void setArgString(const char * name, const char * value); + void setArgStringP(const char * name, const char * value); +}; + #endif /* WEB_SERVER_H */ + diff --git a/web-server/web-server.c b/web-server/web-server.c index 877ff2e..f6a8861 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -272,6 +272,9 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) cmdPopArg(req, buf, len); + if(len == 0) + continue; + if( jsonPtr + 20 + len > sizeof(jsonBuf) ) { errorResponse(connData, 500, "Response too large!"); @@ -365,7 +368,7 @@ void ICACHE_FLASH_ATTR WEB_JsonData(CmdPacket *cmd) CmdRequest req; cmdRequest(&req, cmd); - if (cmdGetArgc(&req) < 3) return; + if (cmdGetArgc(&req) < 2) return; uint8_t ip[4]; cmdPopArg(&req, ip, 4); From 645cd1fa72ae85f6fa179cba3ba92c706c20c8b9 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 14 May 2016 20:04:29 +0200 Subject: [PATCH 37/66] Implemented: blink frequency --- .../arduino/EspLinkSample/EspLinkSample.ino | 110 ++---------------- examples/arduino/EspLinkSample/WebServer.cpp | 36 +++++- examples/arduino/EspLinkSample/WebServer.h | 5 + web-server/web-server.c | 2 +- 4 files changed, 50 insertions(+), 103 deletions(-) diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index 937cc07..ab672b0 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -62,8 +62,16 @@ void ledHtmlCallback(WebServerCommand command, char * data, int dataLen) next_ts = millis() + elapse; } break; + case SET_FIELD: + if( strcmp_P(data, PSTR("frequency") ) == 0 ) + { + frequency = webServer.getArgInt(); + elapse = 1000 / frequency; + } + break; case LOAD: - webServer.setArgNum(1); + webServer.setArgNum(2); + webServer.setArgInt("frequency", frequency); case REFRESH: if( command == REFRESH ) webServer.setArgNum(1); @@ -79,103 +87,3 @@ void ledHtmlCallback(WebServerCommand command, char * data, int dataLen) } -/* -#include "EspLink.h" - - -void packetReceived(CmdRequest *req); - -EspLink espLink(Serial, packetReceived); - -void packetReceived(CmdRequest *req) -{ - Serial.println("\nReceived\n"); - uint16_t shrt, port; - espLink.cmdPopArg(req, &shrt, 2); - RequestReason reason = (RequestReason)shrt; - Serial.print("Reason: "); - Serial.println(reason); - - uint8_t ip[4]; - espLink.cmdPopArg(req, &ip, 4); - Serial.print("IP: "); - for(int i=0; i < 4; i++) - { - Serial.print(ip[i], DEC); - if( i != 3 ) - Serial.print("."); - } - Serial.println(); - - espLink.cmdPopArg(req, &port, 2); - Serial.print("Port: "); - Serial.println(port); - - { - uint16_t len = espLink.cmdArgLen(req); - char bf[len+1]; - bf[len] = 0; - espLink.cmdPopArg(req, bf, len); - Serial.print("Url: "); - Serial.println(bf); - } - - switch( reason ) - { - case BUTTON: - { - uint16_t len = espLink.cmdArgLen(req); - char bf[len+1]; - bf[len] = 0; - espLink.cmdPopArg(req, bf, len); - Serial.print("Arg: "); - Serial.println(bf); - } - break; - case SUBMIT: - { - int arg = 4; - while( espLink.cmdGetArgc(req) > arg ) - { - arg++; - - uint16_t len = espLink.cmdArgLen(req); - char bf[len+1]; - bf[len] = 0; - espLink.cmdPopArg(req, bf, len); - - Serial.print(bf + 1); - Serial.print(" -> "); - Serial.println(bf + strlen(bf+1) + 2); - } - return; - } - } - - espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 3); - espLink.sendPacketArg(4, ip); - espLink.sendPacketArg(2, (uint8_t *)&port); - - char outBuf[30]; - outBuf[0] = 0; - strcpy(outBuf+1, "last_name"); - strcpy(outBuf+11,"helloka"); - espLink.sendPacketArg(19, (uint8_t *)outBuf); - - espLink.sendPacketEnd(); - -} - -void setup() { - Serial.begin(57600); - - delay(10); - espLink.sendPacketStart(CMD_CB_ADD, 100, 1); - espLink.sendPacketArg(5, (uint8_t *)"webCb"); - espLink.sendPacketEnd(); -} - -void loop() { - espLink.readLoop(); -} -*/ diff --git a/examples/arduino/EspLinkSample/WebServer.cpp b/examples/arduino/EspLinkSample/WebServer.cpp index df7e50f..5e100d5 100644 --- a/examples/arduino/EspLinkSample/WebServer.cpp +++ b/examples/arduino/EspLinkSample/WebServer.cpp @@ -46,7 +46,21 @@ void WebServer::invokeMethod(RequestReason reason, WebMethod * method, CmdReques break; case WS_SUBMIT: { - /* TODO: iterate through fields */ + 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; default: @@ -148,3 +162,23 @@ void WebServer::setArgStringP(const char * name, const char * value) args_to_send--; } +void WebServer::setArgInt(const char * name, int32_t value) +{ + if( args_to_send <= 0 ) + return; + + 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); + + args_to_send--; +} + +int32_t WebServer::getArgInt() +{ + return (int32_t)atol(value_ptr); +} + diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/EspLinkSample/WebServer.h index 0914b50..6d70817 100644 --- a/examples/arduino/EspLinkSample/WebServer.h +++ b/examples/arduino/EspLinkSample/WebServer.h @@ -53,6 +53,8 @@ class WebServer uint16_t remote_port; int16_t args_to_send; + + char * value_ptr; protected: EspLink espLink; @@ -70,8 +72,11 @@ class WebServer uint16_t getRemotePort() { return remote_port; } void setArgNum(uint8_t num); + void setArgInt(const char * name, int32_t value); void setArgString(const char * name, const char * value); void setArgStringP(const char * name, const char * value); + + int32_t getArgInt(); }; #endif /* WEB_SERVER_H */ diff --git a/web-server/web-server.c b/web-server/web-server.c index f6a8861..330c436 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -263,7 +263,7 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) int c = 2; while( c++ < cmdGetArgc(req) ) { - if( c < 3 ) // skip the first argument + if( c > 3 ) // skip the first argument jsonBuf[jsonPtr++] = ','; int len = cmdArgLen(req); From 61b3f9d224ffb3a3d070b7cd4ee50ae64c42ce62 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 14 May 2016 20:24:59 +0200 Subject: [PATCH 38/66] Added: radio button --- .../arduino/EspLinkSample/EspLinkSample.ino | 40 +++++++++++++++++-- examples/arduino/EspLinkSample/WebServer.cpp | 5 +++ examples/arduino/EspLinkSample/WebServer.h | 1 + 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index ab672b0..c776994 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -3,9 +3,11 @@ #define LED_PIN 13 -int8_t blinking = 0; -int8_t frequency = 10; +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; void ledHtmlCallback(WebServerCommand command, char * data, int dataLen); @@ -38,6 +40,7 @@ void loop() { digitalWrite(LED_PIN, !digitalRead(LED_PIN)); next_ts += elapse; + elapse = elapse_delta - elapse; } } } @@ -66,12 +69,41 @@ void ledHtmlCallback(WebServerCommand command, char * data, int dataLen) if( strcmp_P(data, PSTR("frequency") ) == 0 ) { frequency = webServer.getArgInt(); - elapse = 1000 / frequency; + digitalWrite(LED_PIN, false); + elapse_delta = 2000 / frequency; + elapse = pattern * elapse_delta / 4; + } + else if( strcmp_P(data, PSTR("pattern") ) == 0 ) + { + 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; } break; case LOAD: - webServer.setArgNum(2); + webServer.setArgNum(3); 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( command == REFRESH ) webServer.setArgNum(1); diff --git a/examples/arduino/EspLinkSample/WebServer.cpp b/examples/arduino/EspLinkSample/WebServer.cpp index 5e100d5..7aab354 100644 --- a/examples/arduino/EspLinkSample/WebServer.cpp +++ b/examples/arduino/EspLinkSample/WebServer.cpp @@ -182,3 +182,8 @@ int32_t WebServer::getArgInt() return (int32_t)atol(value_ptr); } +char * WebServer::getArgString() +{ + return value_ptr; +} + diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/EspLinkSample/WebServer.h index 6d70817..28365be 100644 --- a/examples/arduino/EspLinkSample/WebServer.h +++ b/examples/arduino/EspLinkSample/WebServer.h @@ -77,6 +77,7 @@ class WebServer void setArgStringP(const char * name, const char * value); int32_t getArgInt(); + char * getArgString(); }; #endif /* WEB_SERVER_H */ From 67fc8e582c380c5145cf571136ead4f605ee753c Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 14 May 2016 21:15:23 +0200 Subject: [PATCH 39/66] Implemented: led pages --- .../arduino/EspLinkSample/EspLinkSample.ino | 99 +-------- examples/arduino/EspLinkSample/LedPage.ino | 192 ++++++++++++++++++ examples/arduino/EspLinkSample/Pages.h | 10 + examples/arduino/EspLinkSample/WebServer.cpp | 16 ++ examples/arduino/EspLinkSample/WebServer.h | 1 + 5 files changed, 222 insertions(+), 96 deletions(-) create mode 100644 examples/arduino/EspLinkSample/LedPage.ino create mode 100644 examples/arduino/EspLinkSample/Pages.h diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index c776994..9f443bf 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -1,16 +1,6 @@ #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; - -void ledHtmlCallback(WebServerCommand command, char * data, int dataLen); +#include "Pages.h" const char ledURL[] PROGMEM = "/LED.html.json"; @@ -26,96 +16,13 @@ void setup() Serial.begin(57600); webServer.init(); - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, false); + ledInit(); } void loop() { webServer.loop(); - - if( blinking ) - { - if( next_ts <= millis() ) - { - digitalWrite(LED_PIN, !digitalRead(LED_PIN)); - next_ts += elapse; - elapse = elapse_delta - elapse; - } - } -} - -void ledHtmlCallback(WebServerCommand command, char * data, int dataLen) -{ - switch(command) - { - case BUTTON_PRESS: - if( strcmp_P(data, PSTR("btn_on") ) == 0 ) - { - blinking = 0; - digitalWrite(LED_PIN, true); - } else if( strcmp_P(data, PSTR("btn_off") ) == 0 ) - { - blinking = 0; - digitalWrite(LED_PIN, false); - } else if( strcmp_P(data, PSTR("btn_blink") ) == 0 ) - { - blinking = 1; - next_ts = millis() + elapse; - } - break; - case SET_FIELD: - if( strcmp_P(data, PSTR("frequency") ) == 0 ) - { - frequency = webServer.getArgInt(); - digitalWrite(LED_PIN, false); - elapse_delta = 2000 / frequency; - elapse = pattern * elapse_delta / 4; - } - else if( strcmp_P(data, PSTR("pattern") ) == 0 ) - { - 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; - } - break; - case LOAD: - webServer.setArgNum(3); - 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( command == REFRESH ) - webServer.setArgNum(1); - - 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")); - break; - default: - break; - } + ledLoop(); } - diff --git a/examples/arduino/EspLinkSample/LedPage.ino b/examples/arduino/EspLinkSample/LedPage.ino new file mode 100644 index 0000000..768520b --- /dev/null +++ b/examples/arduino/EspLinkSample/LedPage.ino @@ -0,0 +1,192 @@ +#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.setArgNum(4); + 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( command == REFRESH ) + webServer.setArgNum(2); + + 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; + } +} + + diff --git a/examples/arduino/EspLinkSample/Pages.h b/examples/arduino/EspLinkSample/Pages.h new file mode 100644 index 0000000..efa277c --- /dev/null +++ b/examples/arduino/EspLinkSample/Pages.h @@ -0,0 +1,10 @@ +#ifndef PAGES_H +#define PAGES_H + +void ledHtmlCallback(WebServerCommand command, char * data, int dataLen); +void ledLoop(); +void ledInit(); + +#endif /* PAGES_H */ + + diff --git a/examples/arduino/EspLinkSample/WebServer.cpp b/examples/arduino/EspLinkSample/WebServer.cpp index 7aab354..a72faa0 100644 --- a/examples/arduino/EspLinkSample/WebServer.cpp +++ b/examples/arduino/EspLinkSample/WebServer.cpp @@ -162,6 +162,22 @@ void WebServer::setArgStringP(const char * name, const char * value) args_to_send--; } +void WebServer::setArgJson(const char * name, const char * value) +{ + if( args_to_send <= 0 ) + return; + + 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); + + args_to_send--; +} + void WebServer::setArgInt(const char * name, int32_t value) { if( args_to_send <= 0 ) diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/EspLinkSample/WebServer.h index 28365be..e1742b6 100644 --- a/examples/arduino/EspLinkSample/WebServer.h +++ b/examples/arduino/EspLinkSample/WebServer.h @@ -73,6 +73,7 @@ class WebServer void setArgNum(uint8_t num); 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); From 002968830401dce9dae8e020232a7e09fa36699e Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 15 May 2016 10:17:12 +0200 Subject: [PATCH 40/66] User page draft --- .../arduino/EspLinkSample/EspLinkSample.ino | 4 +- examples/arduino/EspLinkSample/Pages.h | 3 + examples/arduino/EspLinkSample/UserPage.ino | 70 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 examples/arduino/EspLinkSample/UserPage.ino diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index 9f443bf..57d7e14 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -1,11 +1,12 @@ - #include "WebServer.h" #include "Pages.h" const char ledURL[] PROGMEM = "/LED.html.json"; +const char userURL[] PROGMEM = "/User.html.json"; const WebMethod PROGMEM methods[] = { { ledURL, ledHtmlCallback }, + { userURL, userHtmlCallback }, { NULL, NULL }, }; @@ -17,6 +18,7 @@ void setup() webServer.init(); ledInit(); + userInit(); } void loop() diff --git a/examples/arduino/EspLinkSample/Pages.h b/examples/arduino/EspLinkSample/Pages.h index efa277c..6142f36 100644 --- a/examples/arduino/EspLinkSample/Pages.h +++ b/examples/arduino/EspLinkSample/Pages.h @@ -5,6 +5,9 @@ void ledHtmlCallback(WebServerCommand command, char * data, int dataLen); void ledLoop(); void ledInit(); +void userHtmlCallback(WebServerCommand command, char * data, int dataLen); +void userInit(); + #endif /* PAGES_H */ diff --git a/examples/arduino/EspLinkSample/UserPage.ino b/examples/arduino/EspLinkSample/UserPage.ino new file mode 100644 index 0000000..7b7c187 --- /dev/null +++ b/examples/arduino/EspLinkSample/UserPage.ino @@ -0,0 +1,70 @@ +#include +#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) + +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); + } +} + +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); + break; + case LOAD: + { + char buf[MAX_STR_LEN]; + webServer.setArgNum(2); + userReadStr( buf, POS_FIRST_NAME ); + webServer.setArgString("first_name", buf); + userReadStr( buf, POS_LAST_NAME ); + webServer.setArgString("last_name", buf); + } + break; + case REFRESH: + // do nothing + break; + } +} + From b41dbcbe811aff7754d26faae5985ed38c607a0c Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sun, 15 May 2016 20:09:12 +0200 Subject: [PATCH 41/66] Implemented user pages --- examples/arduino/EspLinkSample/UserPage.ino | 17 +++++++++++- examples/arduino/EspLinkSample/WebServer.cpp | 28 ++++++++++++++++++++ examples/arduino/EspLinkSample/WebServer.h | 2 ++ html/userpage.js | 9 ++++++- web-server/web-server.c | 2 +- 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/examples/arduino/EspLinkSample/UserPage.ino b/examples/arduino/EspLinkSample/UserPage.ino index 7b7c187..0d7dfa4 100644 --- a/examples/arduino/EspLinkSample/UserPage.ino +++ b/examples/arduino/EspLinkSample/UserPage.ino @@ -8,6 +8,9 @@ #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() { @@ -20,6 +23,9 @@ void userInit() 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); } } @@ -51,15 +57,24 @@ void userHtmlCallback(WebServerCommand command, char * data, int dataLen) 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]; - webServer.setArgNum(2); + webServer.setArgNum(5); 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: diff --git a/examples/arduino/EspLinkSample/WebServer.cpp b/examples/arduino/EspLinkSample/WebServer.cpp index a72faa0..5b0e775 100644 --- a/examples/arduino/EspLinkSample/WebServer.cpp +++ b/examples/arduino/EspLinkSample/WebServer.cpp @@ -162,6 +162,21 @@ void WebServer::setArgStringP(const char * name, const char * value) args_to_send--; } +void WebServer::setArgBoolean(const char * name, uint8_t value) +{ + if( args_to_send <= 0 ) + return; + + 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); + + args_to_send--; +} + void WebServer::setArgJson(const char * name, const char * value) { if( args_to_send <= 0 ) @@ -203,3 +218,16 @@ 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; +} + diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/EspLinkSample/WebServer.h index e1742b6..3b9a444 100644 --- a/examples/arduino/EspLinkSample/WebServer.h +++ b/examples/arduino/EspLinkSample/WebServer.h @@ -76,9 +76,11 @@ class WebServer 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(); }; #endif /* WEB_SERVER_H */ diff --git a/html/userpage.js b/html/userpage.js index 74c7a7b..e9e7555 100644 --- a/html/userpage.js +++ b/html/userpage.js @@ -21,7 +21,14 @@ function notifyResponse( data ) } else if( el.type == "checkbox" ) { - el.checked = data[v] == "on"; + 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 { diff --git a/web-server/web-server.c b/web-server/web-server.c index 330c436..85bef1f 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -310,7 +310,7 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) } break; case WEB_BOOLEAN: - if( value ) { + if( *value ) { os_memcpy(jsonBuf + jsonPtr, "true", 4); jsonPtr += 4; } else { From 03d17bc072dd7138248d24666ee0d4d7daf197c4 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Fri, 20 May 2016 07:38:07 +0200 Subject: [PATCH 42/66] VoltagePage implementation --- .../arduino/EspLinkSample/EspLinkSample.ino | 4 + examples/arduino/EspLinkSample/Pages.h | 4 + .../arduino/EspLinkSample/VoltagePage.ino | 127 ++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 examples/arduino/EspLinkSample/VoltagePage.ino diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino index 57d7e14..ee47a9d 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -3,10 +3,12 @@ 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 }, }; @@ -19,6 +21,7 @@ void setup() ledInit(); userInit(); + voltageInit(); } void loop() @@ -26,5 +29,6 @@ void loop() webServer.loop(); ledLoop(); + voltageLoop(); } diff --git a/examples/arduino/EspLinkSample/Pages.h b/examples/arduino/EspLinkSample/Pages.h index 6142f36..7043b9d 100644 --- a/examples/arduino/EspLinkSample/Pages.h +++ b/examples/arduino/EspLinkSample/Pages.h @@ -8,6 +8,10 @@ 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 */ diff --git a/examples/arduino/EspLinkSample/VoltagePage.ino b/examples/arduino/EspLinkSample/VoltagePage.ino new file mode 100644 index 0000000..68f9aa3 --- /dev/null +++ b/examples/arduino/EspLinkSample/VoltagePage.ino @@ -0,0 +1,127 @@ +#include "WebServer.h" + +#include + +#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: + { + webServer.setArgNum(2); + + 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; + } +} + From 2e63586c161d80dc459e43799d803f3c6b3c54c6 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Fri, 20 May 2016 07:43:59 +0200 Subject: [PATCH 43/66] Reconnect periodically --- examples/arduino/EspLinkSample/WebServer.cpp | 9 ++++++++- examples/arduino/EspLinkSample/WebServer.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/arduino/EspLinkSample/WebServer.cpp b/examples/arduino/EspLinkSample/WebServer.cpp index 5b0e775..6faa822 100644 --- a/examples/arduino/EspLinkSample/WebServer.cpp +++ b/examples/arduino/EspLinkSample/WebServer.cpp @@ -1,4 +1,7 @@ #include "WebServer.h" +#include "Arduino.h" + +#define RESUBSCRIBE_LIMIT 1000 WebServer * WebServer::instance = NULL; @@ -19,7 +22,10 @@ void WebServer::init() void WebServer::loop() { - // TODO: resubscribe periodically + // resubscribe periodically + uint32_t elapsed = millis() - last_connect_ts; + if( elapsed > RESUBSCRIBE_LIMIT ) + registerCallback(); espLink.readLoop(); } @@ -28,6 +34,7 @@ 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) diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/EspLinkSample/WebServer.h index 3b9a444..0a3606d 100644 --- a/examples/arduino/EspLinkSample/WebServer.h +++ b/examples/arduino/EspLinkSample/WebServer.h @@ -55,6 +55,8 @@ class WebServer int16_t args_to_send; char * value_ptr; + + uint32_t last_connect_ts; protected: EspLink espLink; From d814a1f00a7a764fb73726697559f482d5da55db Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Fri, 20 May 2016 07:54:15 +0200 Subject: [PATCH 44/66] EspLink arduino library --- .../arduino/{EspLinkSample => libraries/EspLink}/EspLink.cpp | 0 examples/arduino/{EspLinkSample => libraries/EspLink}/EspLink.h | 0 .../arduino/{EspLinkSample => libraries/EspLink}/WebServer.cpp | 0 .../arduino/{EspLinkSample => libraries/EspLink}/WebServer.h | 0 .../EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino} | 1 + .../EspLink/examples/EspLinkWebApp}/LedPage.ino | 0 .../EspLink/examples/EspLinkWebApp}/Pages.h | 0 .../EspLink/examples/EspLinkWebApp}/UserPage.ino | 0 .../EspLink/examples/EspLinkWebApp}/VoltagePage.ino | 0 examples/arduino/libraries/readme.txt | 1 + 10 files changed, 2 insertions(+) rename examples/arduino/{EspLinkSample => libraries/EspLink}/EspLink.cpp (100%) rename examples/arduino/{EspLinkSample => libraries/EspLink}/EspLink.h (100%) rename examples/arduino/{EspLinkSample => libraries/EspLink}/WebServer.cpp (100%) rename examples/arduino/{EspLinkSample => libraries/EspLink}/WebServer.h (100%) rename examples/arduino/{EspLinkSample/EspLinkSample.ino => libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino} (96%) rename examples/arduino/{EspLinkSample => libraries/EspLink/examples/EspLinkWebApp}/LedPage.ino (100%) rename examples/arduino/{EspLinkSample => libraries/EspLink/examples/EspLinkWebApp}/Pages.h (100%) rename examples/arduino/{EspLinkSample => libraries/EspLink/examples/EspLinkWebApp}/UserPage.ino (100%) rename examples/arduino/{EspLinkSample => libraries/EspLink/examples/EspLinkWebApp}/VoltagePage.ino (100%) create mode 100644 examples/arduino/libraries/readme.txt diff --git a/examples/arduino/EspLinkSample/EspLink.cpp b/examples/arduino/libraries/EspLink/EspLink.cpp similarity index 100% rename from examples/arduino/EspLinkSample/EspLink.cpp rename to examples/arduino/libraries/EspLink/EspLink.cpp diff --git a/examples/arduino/EspLinkSample/EspLink.h b/examples/arduino/libraries/EspLink/EspLink.h similarity index 100% rename from examples/arduino/EspLinkSample/EspLink.h rename to examples/arduino/libraries/EspLink/EspLink.h diff --git a/examples/arduino/EspLinkSample/WebServer.cpp b/examples/arduino/libraries/EspLink/WebServer.cpp similarity index 100% rename from examples/arduino/EspLinkSample/WebServer.cpp rename to examples/arduino/libraries/EspLink/WebServer.cpp diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/libraries/EspLink/WebServer.h similarity index 100% rename from examples/arduino/EspLinkSample/WebServer.h rename to examples/arduino/libraries/EspLink/WebServer.h diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino similarity index 96% rename from examples/arduino/EspLinkSample/EspLinkSample.ino rename to examples/arduino/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino index ee47a9d..3f6dbaf 100644 --- a/examples/arduino/EspLinkSample/EspLinkSample.ino +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino @@ -1,3 +1,4 @@ +#include "EspLink.h" #include "WebServer.h" #include "Pages.h" diff --git a/examples/arduino/EspLinkSample/LedPage.ino b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino similarity index 100% rename from examples/arduino/EspLinkSample/LedPage.ino rename to examples/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino diff --git a/examples/arduino/EspLinkSample/Pages.h b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/Pages.h similarity index 100% rename from examples/arduino/EspLinkSample/Pages.h rename to examples/arduino/libraries/EspLink/examples/EspLinkWebApp/Pages.h diff --git a/examples/arduino/EspLinkSample/UserPage.ino b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino similarity index 100% rename from examples/arduino/EspLinkSample/UserPage.ino rename to examples/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino diff --git a/examples/arduino/EspLinkSample/VoltagePage.ino b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino similarity index 100% rename from examples/arduino/EspLinkSample/VoltagePage.ino rename to examples/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino diff --git a/examples/arduino/libraries/readme.txt b/examples/arduino/libraries/readme.txt new file mode 100644 index 0000000..96ce674 --- /dev/null +++ b/examples/arduino/libraries/readme.txt @@ -0,0 +1 @@ +For information on installing libraries, see: http://www.arduino.cc/en/Guide/Libraries From 466443f415ccb0aaaceb1a3275e94e70c5ba27de Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Fri, 20 May 2016 08:06:32 +0200 Subject: [PATCH 45/66] Added missing HTML files --- .../examples/EspLinkWebApp/web-page.espfs.img | Bin 0 -> 3016 bytes .../examples/EspLinkWebApp/web-page/LED.html | 34 ++++++++++++++++++ .../examples/EspLinkWebApp/web-page/User.html | 22 ++++++++++++ .../EspLinkWebApp/web-page/Voltage.html | 13 +++++++ 4 files changed, 69 insertions(+) create mode 100644 examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img create mode 100644 examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/LED.html create mode 100644 examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html create mode 100644 examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img new file mode 100644 index 0000000000000000000000000000000000000000..7c37710e427e18323a7e52c2f3be23c596437a43 GIT binary patch literal 3016 zcmeHJ&5qTb)1GkH0E^Rrby6Mn}=t(W}oRcl{xI!&p%f6{)WsV~dJ5prx z9avpthMOJ=&|}4Cxd!IZeh%!DXd2EuPbIcC*?5h7rLA z$PXOej#3#?Nnt>koWA9?>GB#5yn@($d`r0ChO<7_73(42;6%Dod^AL>3IR;~@=rT^ z;z!p~4QjN}pX(%@3%8W@vB+wEAiU0dc9fGT2+1B0QVddIz_DTm=mXAeno^P!7lh#7 zIjDN3bxy5cP}E9L&~1u2`(yy7F;xSGnI$-G?Gl{1xLA3Za}ivO78*VHHKc^~ycIdE z&<##QTwZh9VE$sw|Arm;K22XxMCJh)R`x(eEr0LW)3!+W=S+WSVdp4_#92;>r5NTC z(@-LZ9l7jmFF_y?PgWXwr~=Z~$>y4{%9$Jumx3HvKOyc025b(rxOLAg@zRilN9tbr zXEa%>VjFSu22)I0c>P5h-tdaqdG%RPVkrsXRUK4sN_qbf;KJ8z?vt*ka^NNud=i## zZV#(!i=t3(|9lxm_PV?IDU{o%-fljo-Y}E4#W%d2u;1e{e^A*iD%rKy7B{8!*c5Mw z6Hf6^$D8=-MCqk!#Z-L~AbD}S_z^q2l6?SVO8^DsWDFL8?bM|adnp3>ynq(JO-c|O zhaU)3er#IWEq-nN+A!RlorsI}zn#dYS-bE6n&utGerG%HI5X$x@D|y7M@nuL{5657 zj)aMtjLhJUcv`X9L#QSHX~QmFdBq(fDqKsP+mux~D@(;{E~!7M(Ahl=4d-gRzuwZ~ zYWo7yORnw12I*SeN4VXl`)x{#ZR*^*$N%B>db__uv<{{q4)jxQm)R(bQ;y#!UBdQu zcd(;)X!2`A$-wcvvcV_2W6Y|_tqp<9wuKlES72`q7Y58KOvC39)Lca4Sf6Jm@z z;B9Ai +

    LED configuration

    +
    + +
    +
    +
    +

    Control

    + + + +

    +

    +
    +

    Frequency and pattern

    +
    + Pattern:
    + 25% on 75% off
    + 50% on 50% off
    + 75% on 25% off
    + + Frequency:
    +
    + + +
    +
    +
    +

    Logs

    +
      +
    +
    +
    + diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html new file mode 100644 index 0000000..d4676b9 --- /dev/null +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html @@ -0,0 +1,22 @@ +
    +

    User setup

    +
    + +
    +
    + First name:
    + Last name:
    + Age: + + Gender: + +
    + Notifications +
    + + +
    + diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html new file mode 100644 index 0000000..96e4820 --- /dev/null +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html @@ -0,0 +1,13 @@ + + +
    +

    Voltage measurement

    +
    + +
    +

    + +

    + + + From adb61e3e28dec7af0e96d95bac3502dac3ed3fb0 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Fri, 20 May 2016 08:23:51 +0200 Subject: [PATCH 46/66] Missing HTML comments --- .../libraries/EspLink/examples/EspLinkWebApp/web-page/LED.html | 2 ++ .../libraries/EspLink/examples/EspLinkWebApp/web-page/User.html | 2 ++ .../EspLink/examples/EspLinkWebApp/web-page/Voltage.html | 2 ++ examples/web-server/LED.html | 2 ++ examples/web-server/User.html | 2 ++ examples/web-server/Voltage.html | 2 ++ 6 files changed, 12 insertions(+) diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/LED.html b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/LED.html index d471d76..a87f348 100644 --- a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/LED.html +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/LED.html @@ -1,3 +1,5 @@ + +

    LED configuration

    diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html index d4676b9..8dc4286 100644 --- a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html @@ -1,3 +1,5 @@ + +

    User setup

    diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html index 96e4820..b52098e 100644 --- a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html @@ -1,3 +1,5 @@ + +
    diff --git a/examples/web-server/LED.html b/examples/web-server/LED.html index d471d76..a87f348 100644 --- a/examples/web-server/LED.html +++ b/examples/web-server/LED.html @@ -1,3 +1,5 @@ + +

    LED configuration

    diff --git a/examples/web-server/User.html b/examples/web-server/User.html index d4676b9..8dc4286 100644 --- a/examples/web-server/User.html +++ b/examples/web-server/User.html @@ -1,3 +1,5 @@ + +

    User setup

    diff --git a/examples/web-server/Voltage.html b/examples/web-server/Voltage.html index 96e4820..b52098e 100644 --- a/examples/web-server/Voltage.html +++ b/examples/web-server/Voltage.html @@ -1,3 +1,5 @@ + +
    From 31a21a99054ea89938f1b21f859bd356acaf8d17 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 21 May 2016 07:08:28 +0200 Subject: [PATCH 47/66] Nicer argument handling --- .../arduino/libraries/EspLink/WebServer.cpp | 51 ++++--------------- .../arduino/libraries/EspLink/WebServer.h | 4 -- .../examples/EspLinkWebApp/LedPage.ino | 4 -- .../examples/EspLinkWebApp/UserPage.ino | 1 - .../examples/EspLinkWebApp/VoltagePage.ino | 2 - web-server/web-server.c | 8 +-- 6 files changed, 13 insertions(+), 57 deletions(-) diff --git a/examples/arduino/libraries/EspLink/WebServer.cpp b/examples/arduino/libraries/EspLink/WebServer.cpp index 6faa822..eea3ec6 100644 --- a/examples/arduino/libraries/EspLink/WebServer.cpp +++ b/examples/arduino/libraries/EspLink/WebServer.cpp @@ -70,21 +70,20 @@ void WebServer::invokeMethod(RequestReason reason, WebMethod * method, CmdReques } } return; - default: + case WS_LOAD: + case WS_REFRESH: break; + default: + return; } - args_to_send = -1; + espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 255); + espLink.sendPacketArg(4, remote_ip); + espLink.sendPacketArg(2, (uint8_t *)&remote_port); + method->callback( reason == WS_LOAD ? LOAD : REFRESH, NULL, 0); - if( args_to_send == -1 ) - { - espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 2); - espLink.sendPacketArg(4, remote_ip); - espLink.sendPacketArg(2, (uint8_t *)&remote_port); - } - while( args_to_send-- > 0 ) - espLink.sendPacketArg(0, NULL); + espLink.sendPacketArg(0, NULL); espLink.sendPacketEnd(); } @@ -130,18 +129,8 @@ void WebServer::handleRequest(CmdRequest *req) espLink.sendPacketEnd(); } -void WebServer::setArgNum(uint8_t num) -{ - espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 2 + (args_to_send = num)); - espLink.sendPacketArg(4, remote_ip); - espLink.sendPacketArg(2, (uint8_t *)&remote_port); -} - void WebServer::setArgString(const char * name, const char * value) { - if( args_to_send <= 0 ) - return; - uint8_t nlen = strlen(name); uint8_t vlen = strlen(value); char buf[nlen + vlen + 3]; @@ -149,15 +138,10 @@ void WebServer::setArgString(const char * name, const char * value) strcpy(buf+1, name); strcpy(buf+2+nlen, value); espLink.sendPacketArg(nlen+vlen+2, (uint8_t *)buf); - - args_to_send--; } void WebServer::setArgStringP(const char * name, const char * value) { - if( args_to_send <= 0 ) - return; - uint8_t nlen = strlen(name); uint8_t vlen = strlen_P(value); char buf[nlen + vlen + 3]; @@ -165,30 +149,20 @@ void WebServer::setArgStringP(const char * name, const char * value) strcpy(buf+1, name); strcpy_P(buf+2+nlen, value); espLink.sendPacketArg(nlen+vlen+2, (uint8_t *)buf); - - args_to_send--; } void WebServer::setArgBoolean(const char * name, uint8_t value) { - if( args_to_send <= 0 ) - return; - 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); - - args_to_send--; } void WebServer::setArgJson(const char * name, const char * value) { - if( args_to_send <= 0 ) - return; - uint8_t nlen = strlen(name); uint8_t vlen = strlen(value); char buf[nlen + vlen + 3]; @@ -196,23 +170,16 @@ void WebServer::setArgJson(const char * name, const char * value) strcpy(buf+1, name); strcpy(buf+2+nlen, value); espLink.sendPacketArg(nlen+vlen+2, (uint8_t *)buf); - - args_to_send--; } void WebServer::setArgInt(const char * name, int32_t value) { - if( args_to_send <= 0 ) - return; - 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); - - args_to_send--; } int32_t WebServer::getArgInt() diff --git a/examples/arduino/libraries/EspLink/WebServer.h b/examples/arduino/libraries/EspLink/WebServer.h index 0a3606d..a589f3d 100644 --- a/examples/arduino/libraries/EspLink/WebServer.h +++ b/examples/arduino/libraries/EspLink/WebServer.h @@ -52,8 +52,6 @@ class WebServer uint8_t remote_ip[4]; uint16_t remote_port; - int16_t args_to_send; - char * value_ptr; uint32_t last_connect_ts; @@ -73,7 +71,6 @@ class WebServer uint8_t * getRemoteIp() { return remote_ip; } uint16_t getRemotePort() { return remote_port; } - void setArgNum(uint8_t num); void setArgInt(const char * name, int32_t value); void setArgJson(const char * name, const char * value); void setArgString(const char * name, const char * value); @@ -86,4 +83,3 @@ class WebServer }; #endif /* WEB_SERVER_H */ - diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino index 768520b..494df83 100644 --- a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino @@ -154,7 +154,6 @@ void ledHtmlCallback(WebServerCommand command, char * data, int dataLen) } break; case LOAD: - webServer.setArgNum(4); webServer.setArgInt("frequency", frequency); switch(pattern) @@ -171,9 +170,6 @@ void ledHtmlCallback(WebServerCommand command, char * data, int dataLen) } case REFRESH: { - if( command == REFRESH ) - webServer.setArgNum(2); - if( blinking ) webServer.setArgStringP("text", PSTR("LED is blinking")); else diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino index 0d7dfa4..e6581a5 100644 --- a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino @@ -67,7 +67,6 @@ void userHtmlCallback(WebServerCommand command, char * data, int dataLen) case LOAD: { char buf[MAX_STR_LEN]; - webServer.setArgNum(5); userReadStr( buf, POS_FIRST_NAME ); webServer.setArgString("first_name", buf); userReadStr( buf, POS_LAST_NAME ); diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino index 68f9aa3..4080f75 100644 --- a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino @@ -87,8 +87,6 @@ void voltageHtmlCallback(WebServerCommand command, char * data, int dataLen) case LOAD: case REFRESH: { - webServer.setArgNum(2); - char buf[20]; uint8_t int_part = measured_voltage / 256; uint8_t float_part = ((measured_voltage & 255) * 100) / 256; diff --git a/web-server/web-server.c b/web-server/web-server.c index 85bef1f..2d536b4 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -263,9 +263,6 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) int c = 2; while( c++ < cmdGetArgc(req) ) { - if( c > 3 ) // skip the first argument - jsonBuf[jsonPtr++] = ','; - int len = cmdArgLen(req); char buf[len+1]; buf[len] = 0; @@ -273,7 +270,10 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) cmdPopArg(req, buf, len); if(len == 0) - continue; + break; // last argument + + if( c > 3 ) // skip the first argument + jsonBuf[jsonPtr++] = ','; if( jsonPtr + 20 + len > sizeof(jsonBuf) ) { From ceea29280221cf7b51371015dde0bc57a998fe26 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 21 May 2016 07:22:54 +0200 Subject: [PATCH 48/66] Simple LED control sample --- .../EspLinkWebSimpleLedControl.ino | 49 +++++++++++++++++++ .../EspLinkWebSimpleLedControl/SimpleLED.html | 7 +++ 2 files changed, 56 insertions(+) create mode 100644 examples/arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/EspLinkWebSimpleLedControl.ino create mode 100644 examples/arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/EspLinkWebSimpleLedControl.ino b/examples/arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/EspLinkWebSimpleLedControl.ino new file mode 100644 index 0000000..516a2ae --- /dev/null +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/EspLinkWebSimpleLedControl.ino @@ -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(); +} + diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html b/examples/arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html new file mode 100644 index 0000000..27dc359 --- /dev/null +++ b/examples/arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html @@ -0,0 +1,7 @@ + + +

    Simple LED control

    +

    + + + From e1d007914d8a59efa6f88d5b0a88de2345c3b0f8 Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 21 May 2016 11:14:04 +0200 Subject: [PATCH 49/66] Prepend header to HTML --- esp-link/cgiwebserver.c | 84 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c index 58dc544..cd3a61d 100644 --- a/esp-link/cgiwebserver.c +++ b/esp-link/cgiwebserver.c @@ -9,15 +9,57 @@ #include "config.h" #include "web-server.h" +int html_offset = 0; +int html_header_len = 0; +const char * HTML_HEADER = "esp-link" + "" + "
    "; + int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) { switch(cmd) { case FILE_START: - // do nothing + html_offset = 0; + html_header_len = 0; + // simple HTML file + if( ( dataLen > 5 ) && ( os_strcmp(data + dataLen - 5, ".html") == 0 ) ) + { + // 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; + 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 + html_offset, (uint32_t *)(&hdr), sizeof(EspFsHeader) ); + html_offset += sizeof(EspFsHeader); + + char nameBuf[len]; + os_memset(nameBuf, 0, len); + os_memcpy(nameBuf, data, dataLen); + + spi_flash_write( spi_flash_addr + html_offset, (uint32_t *)(nameBuf), len ); + html_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 + html_offset, (uint32_t *)(buf), html_header_len ); + html_offset += html_header_len; + } break; case FILE_DATA: - if( position < 4 ) + if(( position < 4 ) && (html_offset == 0)) { for(int p = position; p < 4; p++ ) { @@ -30,7 +72,7 @@ int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, i } } - int spi_flash_addr = getUserPageSectionStart() + position; + int spi_flash_addr = getUserPageSectionStart() + html_offset + position; int spi_flash_end_addr = spi_flash_addr + dataLen; if( spi_flash_end_addr + dataLen >= getUserPageSectionEnd() ) { @@ -58,9 +100,39 @@ int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, i break; case FILE_DONE: { - uint32_t magic = ESPFS_MAGIC; - spi_flash_write( (int)getUserPageSectionStart(), (uint32_t *)&magic, sizeof(uint32_t) ); - WEB_Init(); + if( html_offset != 0 ) + { + // write the terminating block on esp-fs + int spi_flash_addr = getUserPageSectionStart() + html_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; + + 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; + + spi_flash_write( (int)getUserPageSectionStart(), (uint32_t *)&hdr.magic, sizeof(uint32_t) ); + spi_flash_write( (int)getUserPageSectionStart() + 8, &totallen, sizeof(uint32_t) ); + spi_flash_write( (int)getUserPageSectionStart() + 12, &totallen, sizeof(uint32_t) ); + } + else + { + uint32_t magic = ESPFS_MAGIC; + spi_flash_write( (int)getUserPageSectionStart(), (uint32_t *)&magic, sizeof(uint32_t) ); + } + WEB_Init(); } break; } From 75769413ee167b1a09b2dd505db57794df40d34b Mon Sep 17 00:00:00 2001 From: Karai Csaba Date: Sat, 21 May 2016 11:29:25 +0200 Subject: [PATCH 50/66] Added: missing url decode --- web-server/web-server.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web-server/web-server.c b/web-server/web-server.c index 2d536b4..f9651ce 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -193,7 +193,9 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) { *val = 0; char * name = line; - char * value = val+1; + 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); From c56a7e5364b36fb1e920aa44a07f49ee7b7faf79 Mon Sep 17 00:00:00 2001 From: cskarai Date: Tue, 6 Sep 2016 07:42:13 +0200 Subject: [PATCH 51/66] Refactored arduino library --- .../libraries/EspLink/EspLink.cpp | 0 .../libraries/EspLink/EspLink.h | 0 .../libraries/EspLink/WebServer.cpp | 0 .../libraries/EspLink/WebServer.h | 0 .../examples/EspLinkWebApp/EspLinkWebApp.ino | 0 .../examples/EspLinkWebApp/LedPage.ino | 0 .../examples/EspLinkWebApp/Makefile.webpage | 8 + .../EspLink/examples/EspLinkWebApp/Pages.h | 0 .../examples/EspLinkWebApp/UserPage.ino | 0 .../examples/EspLinkWebApp/VoltagePage.ino | 0 .../examples/EspLinkWebApp/web-page.espfs.img | Bin 3016 -> 3148 bytes .../examples/EspLinkWebApp/web-page/LED.html | 0 .../examples/EspLinkWebApp/web-page/User.html | 0 .../EspLinkWebApp/web-page/Voltage.html | 0 .../EspLinkWebSimpleLedControl.ino | 0 .../EspLinkWebSimpleLedControl/SimpleLED.html | 0 .../arduino => arduino}/libraries/readme.txt | 0 examples/dummy-web-server.pl | 519 ------------------ examples/head-user- | 11 - examples/web-server/LED.html | 36 -- examples/web-server/User.html | 24 - examples/web-server/Voltage.html | 15 - 22 files changed, 8 insertions(+), 605 deletions(-) rename {examples/arduino => arduino}/libraries/EspLink/EspLink.cpp (100%) rename {examples/arduino => arduino}/libraries/EspLink/EspLink.h (100%) rename {examples/arduino => arduino}/libraries/EspLink/WebServer.cpp (100%) rename {examples/arduino => arduino}/libraries/EspLink/WebServer.h (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino (100%) create mode 100644 arduino/libraries/EspLink/examples/EspLinkWebApp/Makefile.webpage rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/Pages.h (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img (68%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/web-page/LED.html (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/web-page/User.html (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebApp/web-page/Voltage.html (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebSimpleLedControl/EspLinkWebSimpleLedControl.ino (100%) rename {examples/arduino => arduino}/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html (100%) rename {examples/arduino => arduino}/libraries/readme.txt (100%) delete mode 100755 examples/dummy-web-server.pl delete mode 100644 examples/head-user- delete mode 100644 examples/web-server/LED.html delete mode 100644 examples/web-server/User.html delete mode 100644 examples/web-server/Voltage.html diff --git a/examples/arduino/libraries/EspLink/EspLink.cpp b/arduino/libraries/EspLink/EspLink.cpp similarity index 100% rename from examples/arduino/libraries/EspLink/EspLink.cpp rename to arduino/libraries/EspLink/EspLink.cpp diff --git a/examples/arduino/libraries/EspLink/EspLink.h b/arduino/libraries/EspLink/EspLink.h similarity index 100% rename from examples/arduino/libraries/EspLink/EspLink.h rename to arduino/libraries/EspLink/EspLink.h diff --git a/examples/arduino/libraries/EspLink/WebServer.cpp b/arduino/libraries/EspLink/WebServer.cpp similarity index 100% rename from examples/arduino/libraries/EspLink/WebServer.cpp rename to arduino/libraries/EspLink/WebServer.cpp diff --git a/examples/arduino/libraries/EspLink/WebServer.h b/arduino/libraries/EspLink/WebServer.h similarity index 100% rename from examples/arduino/libraries/EspLink/WebServer.h rename to arduino/libraries/EspLink/WebServer.h diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino b/arduino/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino similarity index 100% rename from examples/arduino/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino rename to arduino/libraries/EspLink/examples/EspLinkWebApp/EspLinkWebApp.ino diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino b/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino similarity index 100% rename from examples/arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino rename to arduino/libraries/EspLink/examples/EspLinkWebApp/LedPage.ino diff --git a/arduino/libraries/EspLink/examples/EspLinkWebApp/Makefile.webpage b/arduino/libraries/EspLink/examples/EspLinkWebApp/Makefile.webpage new file mode 100644 index 0000000..d0183b0 --- /dev/null +++ b/arduino/libraries/EspLink/examples/EspLinkWebApp/Makefile.webpage @@ -0,0 +1,8 @@ +all: user_img + +clean: + rm -rf web-page.espfs.img + +user_img: + ../../../../../createEspFs.pl web-page web-page.espfs.img + diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/Pages.h b/arduino/libraries/EspLink/examples/EspLinkWebApp/Pages.h similarity index 100% rename from examples/arduino/libraries/EspLink/examples/EspLinkWebApp/Pages.h rename to arduino/libraries/EspLink/examples/EspLinkWebApp/Pages.h diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino b/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino similarity index 100% rename from examples/arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino rename to arduino/libraries/EspLink/examples/EspLinkWebApp/UserPage.ino diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino b/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino similarity index 100% rename from examples/arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino rename to arduino/libraries/EspLink/examples/EspLinkWebApp/VoltagePage.ino diff --git a/examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img b/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img similarity index 68% rename from examples/arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img rename to arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img index 7c37710e427e18323a7e52c2f3be23c596437a43..e7a760f7668a36d2d232a3ce1a12952ed6766bfd 100644 GIT binary patch delta 216 zcmX>henx`VH8`!9fq{pimYIQ}c5^M0A)~UQuC9V>ae+@}UbaGcW=@VmVoHiaMrvY8 zY7vksN>$L+ot(v}viTIV7!w}@%z)>t3=GdVzhx!C04w%77MKA74Ao2w4AmQXO&K+a XaDW|`f&$lOdqzV>L9h!L84v&f*flu> delta 100 zcmX>jaYCHeH8`!9fkA*FfQf-2VDnl|Q^w7_+ '0.0.0.0', - LocalPort => '7777', - Proto => 'tcp', - Listen => 25, - Reuse => 1 -); -die "cannot create socket $!\n" unless $server; -print "server waiting for client connection on port 7777\n"; - - -my @webmethods = ( - [ "menu", \&getMenu ], - [ "pins", \&getPins ], - [ "system/info", \&getSystemInfo ], - [ "wifi/info", \&getWifiInfo ], -); - -my $client; - -while ($client = $server->accept()) -{ - threads->create( sub { - $client->autoflush(1); # Always a good idea - close $server; - - my $httpReq = parse_http( $client ); - #print Dumper($httpReq); - my $httpResp = process_http( $httpReq ); - #print Dumper($httpResp); - - my $data = "HTTP/1.1 " . $httpResp->{code} . " " . $httpResp->{text} . "\r\n"; - - if( exists $httpResp->{fields} ) - { - for my $key( keys %{$httpResp->{fields}} ) - { - $data .= "$key: " . $httpResp->{fields}{$key} . "\r\n"; - } - } - $data .= "\r\n"; - if( exists $httpResp->{body} ) - { - $data .= $httpResp->{body}; - } - - $client->send($data); - - if( $httpResp->{done} ) - { - # notify client that response has been sent - shutdown($client, 1); - } - } ); - close $client; # Only meaningful in the client -} - -exit(0); - -sub parse_http -{ - my ($client) = @_; - # read up to 1024 characters from the connected client - my $data = ""; - - do{ - my $buf = ""; - $client->recv($buf, 1024); - $data .= $buf; - }while( $data !~ /\r\n\r\n/s ); - - my %resp; - - my @lines = split /\r\n/, $data; - my $head = shift @lines; - - if( $head =~ /(GET|POST) / ) - { - $resp{method} = $1; - $head =~ s/(GET|POST) //; - if( $head =~ /^([^ ]+) HTTP\/\d\.\d/ ) - { - my $args = $1; - my $u = $args; - $u =~ s/\?.*$//g; - $args =~ s/^.*\?//g; - my %arg = split /[=\&]/, $args; - $resp{urlArgs} = \%arg; - $resp{url} = $u; - - my %fields; - while( my $arg = shift @lines ) - { - if( $arg =~ /^([\w-]+): (.*)$/ ) - { - $fields{$1} = $2; - } - } - $resp{fields} = \%fields; - } - else - { - $resp{method} = 'ERROR'; - $resp{error} = 'Invalid HTTP request'; - } - - if( $resp{method} eq 'POST' ) - { - my $remaining = join("\r\n", @lines); - my $cnt_len = $resp{fields}{'Content-Length'}; - - while( length($remaining) < $cnt_len ) - { - my $buf = ""; - $client->recv($buf, 1024); - $remaining .= $buf; - } - - $resp{postData} = $remaining; - my %pargs = split /[=\&]/, $remaining; - $resp{postArgs} = \%pargs; - } - } - else - { - $resp{method} = 'ERROR'; - $resp{error} = 'Invalid HTTP request'; - } - - return \%resp; -} - -sub simple_response -{ - my ($code, $msg) = @_; - - my %resp; - $resp{code} = $code; - $resp{text} = $msg; - $resp{fields} = {}; - $resp{done} = 1; - - return \%resp; -} - -sub slurp -{ - my ($file) = @_; - - open IF, "<", $file or die "Can't read file: $!"; - my @fc = ; - close(IF); - my $cnt = join("", @fc); - return $cnt; -} - -sub content_response -{ - my ($content, $url) = @_; - - my %resp; - $resp{code} = 200; - $resp{text} = "OK"; - $resp{done} = 1; - $resp{body} = $content; - - $resp{fields} = {}; - $resp{fields}{'Content-Length'} = length($content); - - $resp{fields}{'Content-Type'} = "text/json"; - $resp{fields}{'Content-Type'} = "text/html; charset=UTF-8" if( $url =~ /\.html$/ ); - $resp{fields}{'Content-Type'} = "text/css" if( $url =~ /\.css$/ ); - $resp{fields}{'Content-Type'} = "text/javascript" if( $url =~ /\.js$/ ); - $resp{fields}{'Content-Type'} = "image/gif" if( $url =~ /\.ico$/ ); - $resp{fields}{'Connection'} = 'close'; - - return \%resp; -} - -sub process_http -{ - my ($httpReq) = @_; - if( $httpReq->{method} eq 'ERROR' ) - { - return simple_response(400, $httpReq->{error}); - } - - if( $httpReq->{url} =~ /\.json$/ ) - { - my $url = $httpReq->{url}; - $url =~ s/\.json$//; - my $pth = dirname $0; - - if( -f "$pth/web-server/$url" ) - { - return process_user_comm($httpReq); - } - } - - if( $httpReq->{method} eq 'GET' ) - { - my $url = $httpReq->{url}; - $url =~ s/^\///; - - $url = "home.html" if ! $url; - - my $pth = dirname $0; - - if( -f "$pth/../html/$url" ) - { - my $cnt = slurp( "$pth/../html/$url" ); - - if( $url =~ /\.html$/ ) - { - my $prep = slurp( "$pth/../html/head-" ); - $cnt = "$prep$cnt"; - } - return content_response($cnt, $url); - } - if( -f "$pth/web-server/$url" ) - { - my $cnt = slurp( "$pth/web-server/$url" ); - - if( $url =~ /\.html$/ ) - { - my $prep = slurp( "$pth/head-user-" ); - $cnt = "$prep$cnt"; - } - return content_response($cnt, $url); - } - elsif( grep { $_->[0] eq $url } @webmethods ) - { - my @mth = grep { $_->[0] eq $url } @webmethods; - my $webm = $mth[0]; - - return content_response( $webm->[1]->(), $url ); - } - else - { - return simple_response(404, "File not found"); - } - } - - return simple_response(400, "Invalid HTTP request"); -} - -sub getMenu -{ - my $out = sprintf( - "{ " . - "\"menu\": [ " . - "\"Home\", \"/home.html\", " . - "\"WiFi Station\", \"/wifi/wifiSta.html\", " . - "\"WiFi Soft-AP\", \"/wifi/wifiAp.html\", " . - "\"µC Console\", \"/console.html\", " . - "\"Services\", \"/services.html\", " . -#ifdef MQTT - "\"REST/MQTT\", \"/mqtt.html\", " . -#endif - "\"Debug log\", \"/log.html\", " . - "\"Web Server\", \"/web-server.html\"" . - "%s" . - " ], " . - "\"version\": \"%s\", " . - "\"name\": \"%s\"" . - " }", readUserPages(), "dummy", "dummy-esp-link"); - - return $out; -} - -sub getPins -{ - return '{ "reset":12, "isp":-1, "conn":-1, "ser":2, "swap":0, "rxpup":1 }'; -} - -sub getSystemInfo -{ - return '{ "name": "esp-link-dummy", "reset cause": "6=external", "size": "4MB:512/512", "upload-size": "3145728", "id": "0xE0 0x4016", "partition": "user2.bin", "slip": "disabled", "mqtt": "disabled/disconnected", "baud": "57600", "description": "" }'; -} - -sub getWifiInfo -{ - return '{"mode": "STA", "modechange": "yes", "ssid": "DummySSID", "status": "got IP address", "phy": "11n", "rssi": "-45dB", "warn": "Switch to STA+AP mode", "apwarn": "Switch to STA+AP mode", "mac":"12:34:56:78:9a:bc", "chan":"11", "apssid": "ESP_012345", "appass": "", "apchan": "11", "apmaxc": "4", "aphidd": "disabled", "apbeac": "100", "apauth": "OPEN","apmac":"12:34:56:78:9a:bc", "ip": "192.168.1.2", "netmask": "255.255.255.0", "gateway": "192.168.1.1", "hostname": "esp-link", "staticip": "0.0.0.0", "dhcp": "on"}'; -} - -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; -} - -sub readUserPages -{ - my $pth = dirname $0; - my @files = read_dir_structure( "$pth/web-server", "/" ); - - @files = grep { $_ =~ /\.html$/ } @files; - - my $add = ''; - for my $f ( @files ) - { - my $nam = $f; - $nam =~ s/\.html$//; - $nam =~ s/[^\/]*\///g; - $add .= ", \"$nam\", \"$f\""; - } - - return $add; -} - -sub jsonString -{ - my ($text) = @_; - return 'null' if ! defined $text; - return "\"$text\""; -} - -sub jsonNumber -{ - my ($num) = @_; - return 'null' if ! defined $num; - return $num + 0; -} - -sub led_add_history -{ - my ($msg) = @_; - pop @ledHistory if @ledHistory >= 10; - - my $elapsed = time - $startTime; - my $secs = $elapsed % 60; - my $mins = int($elapsed / 60) % 60; - my $hours = int($elapsed / 3600) % 24; - - $secs = "0$secs" if length($secs) == 1; - $mins = "0$mins" if length($mins) == 1; - $hours = "0$hours" if length($hours) == 1; - - $msg = "$hours:$mins:$secs $msg"; - unshift @ledHistory, $msg; -} - -sub process_user_comm_led -{ - my ($http) = @_; - my $loadData = ''; - - if( $http->{urlArgs}{reason} eq "button" ) - { - my $btn = $http->{urlArgs}{id}; - - if($btn eq "btn_on" ) - { - $ledLabel = "LED is turned on"; - led_add_history("Set LED on"); - } - elsif($btn eq "btn_blink" ) - { - $ledLabel = "LED is blinking"; - led_add_history("Set LED blinking"); - } - elsif($btn eq "btn_off" ) - { - $ledLabel = "LED is turned off"; - led_add_history("Set LED off"); - } - } - elsif( $http->{urlArgs}{reason} eq "submit" ) - { - if( exists $http->{postArgs}{frequency} ) - { - $ledFreq = $http->{postArgs}{frequency}; - led_add_history("Set frequency to $ledFreq Hz"); - } - if( exists $http->{postArgs}{pattern} ) - { - $pattern = $http->{postArgs}{pattern}; - my $out = $pattern; - $out =~ s/_/\% - /; - $out .= "%"; - led_add_history("Set pattern to $out"); - } - return simple_response(204, "OK"); - } - elsif( $http->{urlArgs}{reason} eq "load" ) - { - $loadData = ', "frequency": ' . $ledFreq . ', "pattern": "' . $pattern . '"'; - } - - my $list = ", \"led_history\": [" . join(", ", map { "\"$_\"" } @ledHistory ) . "]"; - my $r = '{"text": "' . $ledLabel . '"' . $list . $loadData . '}'; - return content_response($r, $http->{url}); -} - -sub process_user_comm_voltage -{ - my ($http) = @_; - - my $voltage = (((time - $startTime) % 60) - 30) / 30.0 + 4.0; - $voltage = sprintf("%.2f V", $voltage); - - my $table = ', "table": [["Time", "Min", "AVG", "Max"], ["0s-10s", "1 V", "3 V", "5 V"], ["10s-20s", "1 V", "2 V", "3 V"]]'; - my $r = '{"voltage": "' . $voltage . '"' . $table . '}'; - return content_response($r, $http->{url}); -} - -sub process_user_comm_user -{ - my ($http) = @_; - - - if( $http->{urlArgs}{reason} eq "submit" ) - { - if( exists $http->{postArgs}{last_name} ) - { - $userLname = $http->{postArgs}{last_name}; - } - if( exists $http->{postArgs}{first_name} ) - { - $userFname = $http->{postArgs}{first_name}; - } - if( exists $http->{postArgs}{age} ) - { - $userAge = $http->{postArgs}{age}; - } - if( exists $http->{postArgs}{gender} ) - { - $userGender = $http->{postArgs}{gender}; - } - if( exists $http->{postArgs}{notifications} ) - { - $userNotifs = $http->{postArgs}{notifications}; - } - return simple_response(204, "OK"); - } - elsif( $http->{urlArgs}{reason} eq "load" ) - { - my $r = '{"last_name": ' . jsonString($userLname) . - ', "first_name": ' . jsonString($userFname) . - ', "age": ' . jsonNumber($userAge) . - ', "gender": ' . jsonString($userGender) . - ', "notifications":' . jsonString($userNotifs) . '}'; - - return content_response($r, $http->{url}); - } - - return content_response("{}", $http->{url}); -} - -sub process_user_comm() -{ - my ($http) = @_; - - if( $http->{url} eq '/LED.html.json' ) - { - return process_user_comm_led($http); - } - - if( $http->{url} eq '/Voltage.html.json' ) - { - return process_user_comm_voltage($http); - } - - if( $http->{url} eq '/User.html.json' ) - { - return process_user_comm_user($http); - } -} diff --git a/examples/head-user- b/examples/head-user- deleted file mode 100644 index ec6715f..0000000 --- a/examples/head-user- +++ /dev/null @@ -1,11 +0,0 @@ - - - esp-link - - - - - - - -
    diff --git a/examples/web-server/LED.html b/examples/web-server/LED.html deleted file mode 100644 index a87f348..0000000 --- a/examples/web-server/LED.html +++ /dev/null @@ -1,36 +0,0 @@ - - -
    -

    LED configuration

    -
    - -
    -
    -
    -

    Control

    - - - -

    -

    -
    -

    Frequency and pattern

    -
    - Pattern:
    - 25% on 75% off
    - 50% on 50% off
    - 75% on 25% off
    - - Frequency:
    -
    - - -
    -
    -
    -

    Logs

    -
      -
    -
    -
    - diff --git a/examples/web-server/User.html b/examples/web-server/User.html deleted file mode 100644 index 8dc4286..0000000 --- a/examples/web-server/User.html +++ /dev/null @@ -1,24 +0,0 @@ - - -
    -

    User setup

    -
    - -
    -
    - First name:
    - Last name:
    - Age: - - Gender: - -
    - Notifications -
    - - -
    - diff --git a/examples/web-server/Voltage.html b/examples/web-server/Voltage.html deleted file mode 100644 index b52098e..0000000 --- a/examples/web-server/Voltage.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - -
    -

    Voltage measurement

    -
    - -
    -

    - -

    - - - From 62f8f70b963ea5a8bbfbe34ec06899fdaef12997 Mon Sep 17 00:00:00 2001 From: cskarai Date: Tue, 6 Sep 2016 20:24:30 +0200 Subject: [PATCH 52/66] Let WebServer handle other EspLink calls --- arduino/libraries/EspLink/WebServer.cpp | 11 +++++++++-- arduino/libraries/EspLink/WebServer.h | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/arduino/libraries/EspLink/WebServer.cpp b/arduino/libraries/EspLink/WebServer.cpp index eea3ec6..cd0af2c 100644 --- a/arduino/libraries/EspLink/WebServer.cpp +++ b/arduino/libraries/EspLink/WebServer.cpp @@ -3,14 +3,14 @@ #define RESUBSCRIBE_LIMIT 1000 -WebServer * WebServer::instance = NULL; +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) +WebServer::WebServer(Stream &streamIn, const WebMethod * PROGMEM methodsIn):espLink(streamIn, webServerCallback),methods(methodsIn),stream(streamIn),esplink_cb(NULL) { instance = this; } @@ -89,6 +89,13 @@ void WebServer::invokeMethod(RequestReason reason, WebMethod * method, CmdReques 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; diff --git a/arduino/libraries/EspLink/WebServer.h b/arduino/libraries/EspLink/WebServer.h index a589f3d..59f1cb2 100644 --- a/arduino/libraries/EspLink/WebServer.h +++ b/arduino/libraries/EspLink/WebServer.h @@ -56,6 +56,8 @@ class WebServer uint32_t last_connect_ts; + CmdRequestCB esplink_cb; + protected: EspLink espLink; @@ -66,6 +68,8 @@ class WebServer void loop(); void registerCallback(); + + void setEspLinkCallback(CmdRequestCB cb) { esplink_cb = cb; } static WebServer * getInstance() { return instance; } uint8_t * getRemoteIp() { return remote_ip; } @@ -80,6 +84,8 @@ class WebServer int32_t getArgInt(); char * getArgString(); uint8_t getArgBoolean(); + + EspLink * getEspLink() { return &espLink; } }; #endif /* WEB_SERVER_H */ From fae07a86a80a22c029a6dfcace1b22a4851989a1 Mon Sep 17 00:00:00 2001 From: cskarai Date: Sat, 17 Sep 2016 11:04:24 +0200 Subject: [PATCH 53/66] Refactored espfs file system --- espfs/espfs.c | 106 +++++++++++++++++++++++++++++--------------------- espfs/espfs.h | 15 +++---- 2 files changed, 70 insertions(+), 51 deletions(-) diff --git a/espfs/espfs.c b/espfs/espfs.c index 39f2e12..111bfd7 100644 --- a/espfs/espfs.c +++ b/espfs/espfs.c @@ -80,37 +80,6 @@ Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All a memory exception, crashing the program. */ -void espfs_memcpy( EspFsContext * ctx, void * dest, const void * src, int count ) -{ - if( ctx->source == ESPFS_MEMORY ) - os_memcpy( dest, src, count ); - else - { - if( spi_flash_read( (int)src, dest, count ) != SPI_FLASH_RESULT_OK ) - os_memset( dest, 0, count ); // if read was not successful, reply with zeroes - } -} - -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; -} - //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. @@ -133,12 +102,49 @@ void ICACHE_FLASH_ATTR memcpyAligned(char *dst, const 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 - espfs_memcpy(ctx, dest, src, count); + 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. @@ -155,6 +161,7 @@ int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { 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 ) @@ -163,19 +170,31 @@ void ICACHE_FLASH_ATTR espFsIteratorInit(EspFsContext *ctx, EspFsIterator *itera return; } iterator->ctx = ctx; - iterator->p = ctx->data; + 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 * p = iterator->p; + 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->node = p; + iterator->position = position; EspFsHeader * hdr = &iterator->header; - espfs_memcpy(iterator->ctx, hdr, p, sizeof(EspFsHeader)); + espfs_memcpy(iterator->ctx, hdr, position, sizeof(EspFsHeader)); if (hdr->magic!=ESPFS_MAGIC) { #ifdef ESPFS_DBG @@ -185,17 +204,15 @@ int ICACHE_FLASH_ATTR espFsIteratorNext(EspFsIterator *iterator) } if (hdr->flags&FLAG_LASTFILE) { //os_printf("End of image.\n"); + iterator->ctx = NULL; // invalidate the iterator return 0; } - p += sizeof(EspFsHeader); + position += sizeof(EspFsHeader); //Grab the name of the file. - espfs_memcpy(iterator->ctx, iterator->name, p, sizeof(iterator->name)); + espfs_memcpy(iterator->ctx, iterator->name, position, sizeof(iterator->name)); - p+=hdr->nameLen+hdr->fileLenComp; - if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val - iterator->p = p; return 1; } @@ -221,10 +238,10 @@ EspFsFile ICACHE_FLASH_ATTR *espFsOpen(EspFsContext *ctx, char *fileName) { //os_printf("Alloc %p[%d]\n", r, sizeof(EspFsFile)); if (r==NULL) return NULL; r->ctx = ctx; - r->header=(EspFsHeader *)it.node; + r->header=(EspFsHeader *)it.position; r->decompressor=it.header.compression; - r->posComp=it.node + it.header.nameLen + sizeof(EspFsHeader); - r->posStart=it.node + it.header.nameLen + sizeof(EspFsHeader); + r->posComp=it.position + it.header.nameLen + sizeof(EspFsHeader); + r->posStart=it.position + it.header.nameLen + sizeof(EspFsHeader); r->posDecomp=0; if (it.header.compression==COMPRESS_NONE) { r->decompData=NULL; @@ -269,6 +286,7 @@ 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; } diff --git a/espfs/espfs.h b/espfs/espfs.h index 609df36..654bc27 100644 --- a/espfs/espfs.h +++ b/espfs/espfs.h @@ -9,20 +9,21 @@ typedef enum { 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, - ESPFS_FLASH, + 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; - EspFsContext *ctx; - char name[256]; - char *node; - char * p; + 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; From f819030236b7a4f4709297ba71f8489d05f19e0c Mon Sep 17 00:00:00 2001 From: cskarai Date: Sat, 17 Sep 2016 12:39:40 +0200 Subject: [PATCH 54/66] Fixed: start web page from 0x100000 address --- esp-link/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-link/config.c b/esp-link/config.c index cbab480..10adc55 100644 --- a/esp-link/config.c +++ b/esp-link/config.c @@ -209,7 +209,7 @@ const uint32_t getUserPageSectionStart() case FLASH_SIZE_16M_MAP_1024_1024: case FLASH_SIZE_32M_MAP_512_512: case FLASH_SIZE_32M_MAP_1024_1024: - return 0x0FC000; + return 0x100000; default: return 0xFFFFFFFF; } From f7e8ab4734629c7cd92619ef273b7da844afe6c8 Mon Sep 17 00:00:00 2001 From: cskarai Date: Sat, 17 Sep 2016 13:24:04 +0200 Subject: [PATCH 55/66] Fixed: decode URL before searching file --- httpd/httpdespfs.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/httpd/httpdespfs.c b/httpd/httpdespfs.c index 9321aad..e080bdc 100644 --- a/httpd/httpdespfs.c +++ b/httpd/httpdespfs.c @@ -14,6 +14,8 @@ 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"; @@ -43,7 +45,12 @@ cgiEspFsHook(HttpdConnData *connData) { if (file==NULL) { if( espFsIsValid(userPageCtx) ) { - file = espFsOpen(userPageCtx, connData->url ); + 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; } From 4e5cdb25d287b360b1837dc7c35974d8ab4451ef Mon Sep 17 00:00:00 2001 From: cskarai Date: Sat, 17 Sep 2016 13:49:40 +0200 Subject: [PATCH 56/66] Fixed: WEB_JSON_DATA WEB_DATA change (uses SLIP protocol) --- arduino/libraries/EspLink/EspLink.h | 2 +- arduino/libraries/EspLink/WebServer.cpp | 4 ++-- cmd/cmd.h | 4 ++-- cmd/handlers.c | 2 +- web-server/web-server.c | 2 +- web-server/web-server.h | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/arduino/libraries/EspLink/EspLink.h b/arduino/libraries/EspLink/EspLink.h index c13a390..aff43f9 100644 --- a/arduino/libraries/EspLink/EspLink.h +++ b/arduino/libraries/EspLink/EspLink.h @@ -43,7 +43,7 @@ typedef enum { CMD_REST_REQUEST, CMD_REST_SETHEADER, - CMD_WEB_JSON_DATA = 30, + CMD_WEB_DATA = 30, CMD_WEB_REQ_CB, } CmdName; diff --git a/arduino/libraries/EspLink/WebServer.cpp b/arduino/libraries/EspLink/WebServer.cpp index cd0af2c..6a5c270 100644 --- a/arduino/libraries/EspLink/WebServer.cpp +++ b/arduino/libraries/EspLink/WebServer.cpp @@ -77,7 +77,7 @@ void WebServer::invokeMethod(RequestReason reason, WebMethod * method, CmdReques return; } - espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 255); + espLink.sendPacketStart(CMD_WEB_DATA, 100, 255); espLink.sendPacketArg(4, remote_ip); espLink.sendPacketArg(2, (uint8_t *)&remote_port); @@ -130,7 +130,7 @@ void WebServer::handleRequest(CmdRequest *req) return; // empty response - espLink.sendPacketStart(CMD_WEB_JSON_DATA, 100, 2); + espLink.sendPacketStart(CMD_WEB_DATA, 100, 2); espLink.sendPacketArg(4, remote_ip); espLink.sendPacketArg(2, (uint8_t *)&remote_port); espLink.sendPacketEnd(); diff --git a/cmd/cmd.h b/cmd/cmd.h index a73119a..be1e684 100644 --- a/cmd/cmd.h +++ b/cmd/cmd.h @@ -49,8 +49,8 @@ typedef enum { CMD_REST_REQUEST, CMD_REST_SETHEADER, - CMD_WEB_JSON_DATA = 30, - CMD_WEB_REQ_CB, + 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); diff --git a/cmd/handlers.c b/cmd/handlers.c index 834b757..af328e7 100644 --- a/cmd/handlers.c +++ b/cmd/handlers.c @@ -48,7 +48,7 @@ const CmdList commands[] = { {CMD_REST_REQUEST, "REST_REQ", REST_Request}, {CMD_REST_SETHEADER, "REST_SETHDR", REST_SetHeader}, #endif - {CMD_WEB_JSON_DATA, "WEB_JSON_DATA", WEB_JsonData}, + {CMD_WEB_DATA, "WEB_DATA", WEB_Data}, }; //===== List of registered callbacks (to uC) diff --git a/web-server/web-server.c b/web-server/web-server.c index f9651ce..72ee39c 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -365,7 +365,7 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) return HTTPD_CGI_MORE; } -void ICACHE_FLASH_ATTR WEB_JsonData(CmdPacket *cmd) +void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd) { CmdRequest req; cmdRequest(&req, cmd); diff --git a/web-server/web-server.h b/web-server/web-server.h index e063799..2e3413a 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -31,7 +31,7 @@ void WEB_Init(); char * WEB_UserPages(); int WEB_CgiJsonHook(HttpdConnData *connData); -void WEB_JsonData(CmdPacket *cmd); +void WEB_Data(CmdPacket *cmd); #endif /* WEB_SERVER_H */ From e14a5ca732ade96c27fdfa81fc873ccc98b59525 Mon Sep 17 00:00:00 2001 From: cskarai Date: Sat, 17 Sep 2016 16:45:52 +0200 Subject: [PATCH 57/66] Refactored: httpd handling --- httpd/httpd.c | 32 ++++++++++++++++++++++---------- httpd/httpd.h | 4 +++- web-server/web-server.c | 10 +++++++--- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 73c187c..bc16649 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -366,6 +366,7 @@ static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) { 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; @@ -627,7 +628,8 @@ void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) { espconn_tcp_set_max_con_allow(&httpdConn, MAX_CONN); } -int ICACHE_FLASH_ATTR httpdNotify(uint8_t * ip, int port, const void * cgiArg) { +// looks up connection handle based on ip / port +HttpdConnData * ICACHE_FLASH_ATTR httpdLookUpConn(uint8_t * ip, int port) { int i; for (i = 0; iconn->proto.tcp->remote_ip, ip, 4) != 0) continue; - char sendBuff[MAX_SENDBUFF_LEN]; - conn->priv->sendBuff = sendBuff; - conn->priv->sendBuffLen = 0; + 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->cgiArg = cgiArg; - httpdProcessRequest(conn); - conn->cgiArg = NULL; + conn->cgiResponse = response; + httpdProcessRequest(conn); + conn->cgiResponse = NULL; - return HTTPD_CGI_DONE; - } - return HTTPD_CGI_NOTFOUND; + return HTTPD_CGI_DONE; } diff --git a/httpd/httpd.h b/httpd/httpd.h index 1b0ed38..32d9394 100644 --- a/httpd/httpd.h +++ b/httpd/httpd.h @@ -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,6 +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); -int ICACHE_FLASH_ATTR httpdNotify(uint8_t * ip, int port, const void * cgiArg); +HttpdConnData * ICACHE_FLASH_ATTR httpdLookUpConn(uint8_t * ip, int port); +int ICACHE_FLASH_ATTR httpdSetCGIResponse(HttpdConnData * conn, void *response); #endif diff --git a/web-server/web-server.c b/web-server/web-server.c index 72ee39c..c1bfb6f 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -253,14 +253,14 @@ int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) connData->cgiData = (void *)1; } - if( connData->cgiArg != NULL ) // arrived data from MCU + if( connData->cgiResponse != NULL ) // data from MCU { char jsonBuf[1500]; int jsonPtr = 0; jsonBuf[jsonPtr++] = '{'; - CmdRequest * req = (CmdRequest *)(connData->cgiArg); + CmdRequest * req = (CmdRequest *)(connData->cgiResponse); int c = 2; while( c++ < cmdGetArgc(req) ) @@ -378,5 +378,9 @@ void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd) uint16_t port; cmdPopArg(&req, &port, 2); - httpdNotify(ip, port, &req); + HttpdConnData * conn = httpdLookUpConn(ip, port); + if( conn != NULL && conn->cgi == WEB_CgiJsonHook ) // make sure that the right CGI handler will be called + httpdSetCGIResponse( conn, &req ); + else + os_printf("WEB response ignored as no valid http connection found for the request!\n"); } From 31ff5b279f5935b7382bda22fc30e9157c002ca6 Mon Sep 17 00:00:00 2001 From: cskarai Date: Sun, 18 Sep 2016 08:58:53 +0200 Subject: [PATCH 58/66] Refactored web-server --- web-server/web-server.c | 598 ++++++++++++++++++++++------------------ web-server/web-server.h | 20 +- 2 files changed, 343 insertions(+), 275 deletions(-) diff --git a/web-server/web-server.c b/web-server/web-server.c index c1bfb6f..4689014 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -8,14 +8,31 @@ #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 -#define MAX_VARS 20 +struct ArgumentBuffer +{ + char argBuffer[MAX_ARGUMENT_BUFFER_SIZE]; + int argBufferPtr; + int numberOfArgs; +}; static char* web_server_reasons[] = { - "load", "refresh", "button", "submit" + "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() @@ -23,6 +40,7 @@ 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]; @@ -34,37 +52,35 @@ void ICACHE_FLASH_ATTR WEB_BrowseFiles() espFsIteratorInit(userPageCtx, &it); while( espFsIteratorNext(&it) ) { - int nlen = os_strlen(it.name); - if( nlen >= 6 ) + int nameLen = os_strlen(it.name); + if( nameLen >= 6 ) { - if( os_strcmp( it.name + nlen-5, ".html" ) == 0 ) + // fetch HTML files + if( os_strcmp( it.name + nameLen-5, ".html" ) == 0 ) { - char sh_name[17]; + int slashPos = nameLen - 5; - int spos = nlen-5; - - while( spos > 0 ) - { - if( it.name[spos+1] == '/' ) - break; - spos--; - } + // chop path and .html from the name + while( slashPos > 0 && it.name[slashPos-1] != '/' ) + slashPos--; - int ps = nlen-5-spos; - if( ps > 16 ) - ps = 16; - os_memcpy(sh_name, it.name + spos, ps); - sh_name[ps] = 0; + // here we check buffer overrun + int maxLen = 10 + os_strlen( it.name ) + (nameLen - slashPos -5); + if( maxLen >= sizeof(buffer) ) + break; os_strcat(buffer, ", \""); - os_strcat(buffer, sh_name); + + 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( os_strlen(buffer) > 600 ) - break; } } @@ -76,6 +92,7 @@ void ICACHE_FLASH_ATTR WEB_BrowseFiles() os_memcpy( webServerPages, buffer, len ); } +// initializer void ICACHE_FLASH_ATTR WEB_Init() { espFsInit(userPageCtx, (void *)getUserPageSectionStart(), ESPFS_FLASH); @@ -83,288 +100,339 @@ void ICACHE_FLASH_ATTR WEB_Init() os_printf("Valid user file system found!\n"); else os_printf("No user file system found!\n"); - WEB_BrowseFiles(); + WEB_BrowseFiles(); // collect user defined HTML files } -int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) +// initializes the argument buffer +static void WEB_argInit(struct ArgumentBuffer * argBuffer) { - if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + 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 - void * cgiData = connData->cgiData; + os_memcpy(argBuffer->argBuffer + argBuffer->argBufferPtr, &argLen, sizeof(int)); - if( cgiData == NULL ) + if( argLen != 0 ) { - 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( serbridgeInProgramming() ) - { - errorResponse(connData, 500, "Slip disabled at programming mode!"); - 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; - } + 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) ); - if( reason == INVALID ) - { - errorResponse(connData, 400, "Invalid reason!"); + 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( serbridgeInProgramming() ) + { + 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; - } - - char body[1024]; - int bodyPtr = 0; - int argNum = 0; - char *argPos[MAX_VARS]; - int argLen[MAX_VARS]; - - switch(reason) - { - case BUTTON: - argLen[0] = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); - if( argLen[0] <= 0 ) + } + + 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; } - argPos[0] = body; - argNum++; - break; - case SUBMIT: + if( WEB_addArg(&argBuffer, id_buf, id_len) ) { - if( connData->post->received < connData->post->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 ) { - errorResponse(connData, 400, "Post too large!"); - return HTTPD_CGI_DONE; + *eo = 0; + bptr = eo - connData->post->buff + 1; + } + else + { + eo = line + os_strlen( line ); + bptr = connData->post->len; } - int bptr = 0; + int len = os_strlen(line); + while( len >= 1 && ( line[len-1] == '\r' || line[len-1] == '\n' )) + len--; + line[len] = 0; - while( bptr < connData->post->len ) + char * val = os_strchr(line, '='); + if( val != NULL ) { - if( argNum >= MAX_VARS ) - { - errorResponse(connData, 400, "Too many variables!"); - return HTTPD_CGI_DONE; - } + *val = 0; + char * name = line; + int vblen = os_strlen(val+1) * 2; + char value[vblen]; + httpdUrlDecode(val+1, strlen(val+1), value, vblen); - char * line = connData->post->buff + bptr; + int namLen = os_strlen(name); + int valLen = os_strlen(value); - 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 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; - char * val = os_strchr(line, '='); - if( val != NULL ) + if( WEB_addArg(&argBuffer, arg, argPtr) ) { - *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); - - int totallen = namLen + valLen + 2; - if( bodyPtr + totallen > sizeof(body) - 10 ) - { - errorResponse(connData, 400, "Post too large!"); - return HTTPD_CGI_DONE; - } - - argPos[argNum] = body + bodyPtr; - - body[bodyPtr++] = (char)WEB_STRING; - os_strcpy( body + bodyPtr, name ); - bodyPtr += namLen; - body[bodyPtr++] = 0; - - os_strcpy( body + bodyPtr, value ); - bodyPtr += valLen; - - argLen[argNum++] = totallen; + errorResponse(connData, 400, "Post too large!"); + return HTTPD_CGI_DONE; } } } - break; - case LOAD: - case REFRESH: - default: - break; - } + } + 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; - os_printf("Web callback to MCU: %s\n", reasonBuf); + cmdPopArg(response, buf, len); - cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, 4 + argNum); - uint16_t r = (uint16_t)reason; - cmdResponseBody(&r, sizeof(uint16_t)); - cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); - cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); - cmdResponseBody(connData->url, os_strlen(connData->url)); + if(len == 0) + break; // last argument - int j; - for( j=0; j < argNum; j++ ) - cmdResponseBody(argPos[j], argLen[j]); + if( c > 3 ) // skip the first argument + jsonBuf[jsonPtr++] = ','; - cmdResponseEnd(); - - if( reason == SUBMIT ) + if( jsonPtr + 20 + len > sizeof(jsonBuf) ) { - httpdStartResponse(connData, 204); - httpdEndHeaders(connData); + errorResponse(connData, 500, "Response too large!"); return HTTPD_CGI_DONE; } - connData->cgiData = (void *)1; - } - - if( connData->cgiResponse != NULL ) // data from MCU - { - char jsonBuf[1500]; - int jsonPtr = 0; + 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++] = ':'; - jsonBuf[jsonPtr++] = '{'; - CmdRequest * req = (CmdRequest *)(connData->cgiResponse); + char * value = buf + 2 + nameLen; - int c = 2; - while( c++ < cmdGetArgc(req) ) + switch(type) { - int len = cmdArgLen(req); - char buf[len+1]; - buf[len] = 0; - - cmdPopArg(req, 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); + 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; - 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; - } + } 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; } + 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; @@ -373,14 +441,14 @@ void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd) if (cmdGetArgc(&req) < 2) return; uint8_t ip[4]; - cmdPopArg(&req, ip, 4); + cmdPopArg(&req, ip, 4); // pop the IP address uint16_t port; - cmdPopArg(&req, &port, 2); + cmdPopArg(&req, &port, 2); // pop the HTTP port - HttpdConnData * conn = httpdLookUpConn(ip, port); - if( conn != NULL && conn->cgi == WEB_CgiJsonHook ) // make sure that the right CGI handler will be called + 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 response ignored as no valid http connection found for the request!\n"); + os_printf("WEB_DATA ignored as no valid HTTP connection found!\n"); } diff --git a/web-server/web-server.h b/web-server/web-server.h index 2e3413a..0827229 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -8,22 +8,22 @@ typedef enum { - LOAD=0, - REFRESH, - BUTTON, - SUBMIT, + 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, - WEB_NULL, - WEB_INTEGER, - WEB_BOOLEAN, - WEB_FLOAT, - WEB_JSON + 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(); From 5ac138c65e9d9dc85dade54aab40b4f9af3388d1 Mon Sep 17 00:00:00 2001 From: cskarai Date: Sun, 18 Sep 2016 12:34:30 +0200 Subject: [PATCH 59/66] Refactored multipart class --- esp-link/cgiwebserver.c | 7 +- httpd/multipart.c | 146 ++++++++++++++++++++++++++++++++-------- httpd/multipart.h | 36 +++++----- 3 files changed, 140 insertions(+), 49 deletions(-) diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserver.c index cd3a61d..2c8b6b4 100644 --- a/esp-link/cgiwebserver.c +++ b/esp-link/cgiwebserver.c @@ -139,9 +139,12 @@ int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, i return 0; } -MultipartCtx webServerContext = {.callBack = webServerMultipartCallback, .position = 0, .recvPosition = 0, .startTime = 0, .boundaryBuffer = NULL}; +MultipartCtx * webServerContext = NULL; int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData) { - return multipartProcess(&webServerContext, connData); + if( webServerContext == NULL ) + webServerContext = multipartCreateContext( webServerMultipartCallback ); + + return multipartProcess(webServerContext, connData); } diff --git a/httpd/multipart.c b/httpd/multipart.c index 1198579..d709798 100644 --- a/httpd/multipart.c +++ b/httpd/multipart.c @@ -6,6 +6,36 @@ #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 ) @@ -13,6 +43,7 @@ void ICACHE_FLASH_ATTR multipartAllocBoundaryBuffer(MultipartCtx * context) context->boundaryBufferPtr = 0; } +// for freeing multipart buffer void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context) { if( context->boundaryBuffer != NULL ) @@ -22,11 +53,66 @@ void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context) } } -int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, char * boundary, char * buff, int len, int last) +// 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 ) + if( len != 0 ) // add data to the boundary buffer { - os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, buff, len); + os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, data, len); context->boundaryBufferPtr += len; context->boundaryBuffer[context->boundaryBufferPtr] = 0; @@ -34,22 +120,22 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha while( context->boundaryBufferPtr > 0 ) { - if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) + 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 * loc = os_strstr( context->boundaryBuffer, boundary ); - if( loc != NULL ) + char * boundaryLoc = mp_memmem( context->boundaryBuffer, context->boundaryBufferPtr, boundary, os_strlen(boundary) ); + if( boundaryLoc != NULL ) { - int pos = loc - context->boundaryBuffer; - if( pos > BOUNDARY_SIZE ) - loc = NULL; + int pos = boundaryLoc - context->boundaryBuffer; + if( pos > BOUNDARY_SIZE ) // process in the next call + boundaryLoc = NULL; else dataSize = pos; } - if( dataSize != 0 ) + if( dataSize != 0 ) // data to process { switch( context->state ) { @@ -59,11 +145,12 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha 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( pos < dataSize ) // if chop smaller than the dataSize, delete the boundary { dataSize = pos; - loc = NULL; // this is not yet the boundary + boundaryLoc = NULL; // process boundary next time } if( context->state == STATE_SEARCH_HEADER_END ) { @@ -81,24 +168,24 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha int pos = fnam - context->boundaryBuffer + 9; if( pos < dataSize ) { - while(context->boundaryBuffer[pos] == ' ') pos++; - if( context->boundaryBuffer[pos] == '"' ) + 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] == '"' ) + if( context->boundaryBuffer[pos] == '"' ) // quote end break; pos++; } if( pos < context->boundaryBufferPtr ) { - context->boundaryBuffer[pos] = 0; + 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 ) ) - return 1; - context->boundaryBuffer[pos] = '"'; + 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; } } @@ -112,7 +199,7 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha { 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 ) ) + if( context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ) ) // FILE_DATA callback return 1; context->boundaryBuffer[dataSize] = c; context->position += dataSize; @@ -123,25 +210,27 @@ int ICACHE_FLASH_ATTR multipartProcessBoundaryBuffer(MultipartCtx * context, cha } } - if( loc != NULL ) + if( boundaryLoc != NULL ) // boundary found? { - dataSize += os_strlen(boundary); + dataSize += os_strlen(boundary); // jump over the boundary if( context->state == STATE_UPLOAD_FILE ) { - if( context->callBack( FILE_DONE, NULL, 0, context->position ) ) - return 1; + 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; + 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. @@ -157,7 +246,7 @@ int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * c if( connData->startTime != context->startTime ) { - // reinitialize, as this is a different request + // reinitialize, as this is a new request context->position = 0; context->recvPosition = 0; context->startTime = connData->startTime; @@ -174,7 +263,7 @@ int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * c int len = post->buffLen - feed; if( len > BOUNDARY_SIZE ) len = BOUNDARY_SIZE; - if( multipartProcessBoundaryBuffer(context, post->multipartBoundary, post->buff + feed, len, 0) ) + if( multipartProcessData(context, post->multipartBoundary, post->buff + feed, len, 0) ) { context->state = STATE_ERROR; break; @@ -189,7 +278,8 @@ int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * c if( context->state != STATE_ERROR ) { - if( multipartProcessBoundaryBuffer(context, post->multipartBoundary, NULL, 0, 1) ) + // this is the last package, process the remaining data + if( multipartProcessData(context, post->multipartBoundary, NULL, 0, 1) ) context->state = STATE_ERROR; } diff --git a/httpd/multipart.h b/httpd/multipart.h index d22815d..87a4bb2 100644 --- a/httpd/multipart.h +++ b/httpd/multipart.h @@ -4,31 +4,29 @@ #include typedef enum { - FILE_START, - FILE_DATA, - FILE_DONE, + FILE_START, // multipart: the start of a new file + FILE_DATA, // multipart: file data + FILE_DONE, // multipart: file end } MultipartCmd; -typedef enum { - STATE_SEARCH_BOUNDARY = 0, - STATE_SEARCH_HEADER, - STATE_SEARCH_HEADER_END, - STATE_UPLOAD_FILE, - STATE_ERROR, -} MultipartState; +// 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); -typedef struct { - MultipartCallback callBack; - int position; - int startTime; - int recvPosition; - char * boundaryBuffer; - int boundaryBufferPtr; - MultipartState state; -} MultipartCtx; +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 */ From cd04d7f0e4bc3d099696f60ec9161eff1c95106b Mon Sep 17 00:00:00 2001 From: cskarai Date: Sun, 18 Sep 2016 12:39:28 +0200 Subject: [PATCH 60/66] For cleaner understanding rename to WebServerSetup --- esp-link/{cgiwebserver.c => cgiwebserversetup.c} | 6 +++--- esp-link/{cgiwebserver.h => cgiwebserversetup.h} | 2 +- esp-link/main.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename esp-link/{cgiwebserver.c => cgiwebserversetup.c} (94%) rename esp-link/{cgiwebserver.h => cgiwebserversetup.h} (57%) diff --git a/esp-link/cgiwebserver.c b/esp-link/cgiwebserversetup.c similarity index 94% rename from esp-link/cgiwebserver.c rename to esp-link/cgiwebserversetup.c index 2c8b6b4..19bc1c5 100644 --- a/esp-link/cgiwebserver.c +++ b/esp-link/cgiwebserversetup.c @@ -16,7 +16,7 @@ const char * HTML_HEADER = "esp-link "
    "; -int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) +int ICACHE_FLASH_ATTR webServerSetupMultipartCallback(MultipartCmd cmd, char *data, int dataLen, int position) { switch(cmd) { @@ -141,10 +141,10 @@ int ICACHE_FLASH_ATTR webServerMultipartCallback(MultipartCmd cmd, char *data, i MultipartCtx * webServerContext = NULL; -int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData) +int ICACHE_FLASH_ATTR cgiWebServerSetupUpload(HttpdConnData *connData) { if( webServerContext == NULL ) - webServerContext = multipartCreateContext( webServerMultipartCallback ); + webServerContext = multipartCreateContext( webServerSetupMultipartCallback ); return multipartProcess(webServerContext, connData); } diff --git a/esp-link/cgiwebserver.h b/esp-link/cgiwebserversetup.h similarity index 57% rename from esp-link/cgiwebserver.h rename to esp-link/cgiwebserversetup.h index e3365be..ffecb82 100644 --- a/esp-link/cgiwebserver.h +++ b/esp-link/cgiwebserversetup.h @@ -3,6 +3,6 @@ #include -int ICACHE_FLASH_ATTR cgiWebServerUpload(HttpdConnData *connData); +int ICACHE_FLASH_ATTR cgiWebServerSetupUpload(HttpdConnData *connData); #endif /* CGIWEBSERVER_H */ diff --git a/esp-link/main.c b/esp-link/main.c index 488a6f6..596cfbd 100644 --- a/esp-link/main.c +++ b/esp-link/main.c @@ -19,7 +19,7 @@ #include "cgimqtt.h" #include "cgiflash.h" #include "cgioptiboot.h" -#include "cgiwebserver.h" +#include "cgiwebserversetup.h" #include "auth.h" #include "espfs.h" #include "uart.h" @@ -98,7 +98,7 @@ HttpdBuiltInUrl builtInUrls[] = { #ifdef MQTT { "/mqtt", cgiMqtt, NULL }, #endif - { "/web-server/upload", cgiWebServerUpload, NULL }, + { "/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 } From 79e609ee781017f1d4b019fc2deae48cf070d164 Mon Sep 17 00:00:00 2001 From: cskarai Date: Tue, 20 Sep 2016 19:52:19 +0200 Subject: [PATCH 61/66] Added comments to cgiwebserversetup --- esp-link/cgiwebserversetup.c | 46 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/esp-link/cgiwebserversetup.c b/esp-link/cgiwebserversetup.c index 19bc1c5..2a25950 100644 --- a/esp-link/cgiwebserversetup.c +++ b/esp-link/cgiwebserversetup.c @@ -9,28 +9,31 @@ #include "config.h" #include "web-server.h" -int html_offset = 0; -int html_header_len = 0; +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 = "esp-link" "" "
    "; +// 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: - html_offset = 0; + upload_offset = 0; html_header_len = 0; // simple HTML file - if( ( dataLen > 5 ) && ( os_strcmp(data + dataLen - 5, ".html") == 0 ) ) + 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; + hdr.magic = 0xFFFFFFFF; // espfs magic is invalid during upload hdr.flags = 0; hdr.compression = 0; @@ -41,25 +44,25 @@ int ICACHE_FLASH_ATTR webServerSetupMultipartCallback(MultipartCmd cmd, char *da hdr.nameLen = len; hdr.fileLenComp = hdr.fileLenDecomp = 0xFFFFFFFF; - spi_flash_write( spi_flash_addr + html_offset, (uint32_t *)(&hdr), sizeof(EspFsHeader) ); - html_offset += sizeof(EspFsHeader); + 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 + html_offset, (uint32_t *)(nameBuf), len ); - html_offset += len; + 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 + html_offset, (uint32_t *)(buf), html_header_len ); - html_offset += 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 ) && (html_offset == 0)) + if(( position < 4 ) && (upload_offset == 0)) // for espfs images check the magic number { for(int p = position; p < 4; p++ ) { @@ -68,11 +71,11 @@ int ICACHE_FLASH_ATTR webServerSetupMultipartCallback(MultipartCmd cmd, char *da os_printf("Not an espfs image!\n"); return 1; } - data[p - position] = 0xFF; // clean espfs magic to mark as invalid + data[p - position] = 0xFF; // espfs magic is invalid during upload } } - int spi_flash_addr = getUserPageSectionStart() + html_offset + position; + int spi_flash_addr = getUserPageSectionStart() + upload_offset + position; int spi_flash_end_addr = spi_flash_addr + dataLen; if( spi_flash_end_addr + dataLen >= getUserPageSectionEnd() ) { @@ -100,10 +103,10 @@ int ICACHE_FLASH_ATTR webServerSetupMultipartCallback(MultipartCmd cmd, char *da break; case FILE_DONE: { - if( html_offset != 0 ) + if( html_header_len != 0 ) { // write the terminating block on esp-fs - int spi_flash_addr = getUserPageSectionStart() + html_offset + position; + int spi_flash_addr = getUserPageSectionStart() + upload_offset + position; uint32_t pad = 0; uint8_t pad_cnt = (4 - position) & 3; @@ -112,8 +115,9 @@ int ICACHE_FLASH_ATTR webServerSetupMultipartCallback(MultipartCmd cmd, char *da spi_flash_addr += pad_cnt; + // create ESPFS image EspFsHeader hdr; - hdr.magic = ESPFS_MAGIC; + hdr.magic = ESPFS_MAGIC; hdr.flags = 1; hdr.compression = 0; hdr.nameLen = 0; @@ -123,24 +127,28 @@ int ICACHE_FLASH_ATTR webServerSetupMultipartCallback(MultipartCmd cmd, char *da 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(); + WEB_Init(); // reload the content } break; } return 0; } -MultipartCtx * webServerContext = NULL; +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 ) From 78acf74522cfb1a99a56cf2b8def2f832d8057a0 Mon Sep 17 00:00:00 2001 From: cskarai Date: Tue, 20 Sep 2016 20:00:53 +0200 Subject: [PATCH 62/66] Refactored MCU flashing protection --- serial/serbridge.c | 14 +++++++------- serial/serbridge.h | 2 +- serial/slip.c | 2 -- web-server/web-server.c | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/serial/serbridge.c b/serial/serbridge.c index e1f3fdc..9f782bb 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -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 { @@ -505,7 +505,7 @@ serbridgeInit(int port1, int port2) espconn_regist_time(&serbridgeConn2, SER_BRIDGE_TIMEOUT, 0); } -int ICACHE_FLASH_ATTR serbridgeInProgramming() +int ICACHE_FLASH_ATTR serbridgeInMCUFlashing() { - return slip_disabled; + return in_mcu_flashing; } diff --git a/serial/serbridge.h b/serial/serbridge.h index c24953c..ed661e1 100644 --- a/serial/serbridge.h +++ b/serial/serbridge.h @@ -36,7 +36,7 @@ void ICACHE_FLASH_ATTR serbridgeInitPins(void); void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len); void ICACHE_FLASH_ATTR serbridgeReset(); -int ICACHE_FLASH_ATTR serbridgeInProgramming(); +int ICACHE_FLASH_ATTR serbridgeInMCUFlashing(); // callback when receiving UART chars when in programming mode extern void (*programmingCB)(char *buffer, short length); diff --git a/serial/slip.c b/serial/slip.c index 76d8f97..2f15186 100644 --- a/serial/slip.c +++ b/serial/slip.c @@ -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. diff --git a/web-server/web-server.c b/web-server/web-server.c index 4689014..0d323a2 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -167,7 +167,7 @@ static int ICACHE_FLASH_ATTR WEB_handleJSONRequest(HttpdConnData *connData) errorResponse(connData, 500, "No MCU callback is registered!"); return HTTPD_CGI_DONE; } - if( serbridgeInProgramming() ) + if( serbridgeInMCUFlashing() ) { errorResponse(connData, 500, "Slip disabled at uploading program onto the MCU!"); return HTTPD_CGI_DONE; From 1d96f6db853e31466301a183536fae19a5a011f3 Mon Sep 17 00:00:00 2001 From: cskarai Date: Tue, 20 Sep 2016 20:23:13 +0200 Subject: [PATCH 63/66] Tutorial for web-server --- WEB-SERVER.MD | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 WEB-SERVER.MD diff --git a/WEB-SERVER.MD b/WEB-SERVER.MD new file mode 100644 index 0000000..1d9700d --- /dev/null +++ b/WEB-SERVER.MD @@ -0,0 +1,43 @@ +ESP-LINK web-server tutorial +============================ + +LED flashing sample +-------------------- + +Circuit: + +. connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO: + (RX - levelshifter - TX, TX - levelshifter - RX) +. optionally connect RESET-s with a level shifter + + +Installation steps: + +. install the latest Arduino on the PC +. install EspLink library from arduino/libraries path +. open EspLinkWebSimpleLedControl sample from Arduino +. upload the code onto an Arduino Nano/Uno +. install esp-link +. jump to the Web Server page on esp-link UI +. upload SimpleLED.html ( arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html ) +. jump to SimpleLED page on esp-link UI +. turn on/off the LED + +Complex application sample +-------------------------- + +Circuit: + +. connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO: + (RX - levelshifter - TX, TX - levelshifter - RX) +. optionally connect RESET-s with a level shifter +. add a trimmer to A0 for Voltage measurement + +Installation steps: + +. open EspLinkWebApp sample from Arduino +. upload the code onto an Arduino Nano/Uno +. jump to the Web Server page on esp-link UI +. upload web-page.espfs.img ( arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img ) +. jump to LED/User/Voltage pages +. try out different settings From 5e721578bd58e4209ae7b102a5420f79dba5dd21 Mon Sep 17 00:00:00 2001 From: cskarai Date: Tue, 20 Sep 2016 20:40:59 +0200 Subject: [PATCH 64/66] Renamed file --- WEB-SERVER.MD => WEB-SERVER.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename WEB-SERVER.MD => WEB-SERVER.md (100%) diff --git a/WEB-SERVER.MD b/WEB-SERVER.md similarity index 100% rename from WEB-SERVER.MD rename to WEB-SERVER.md From 0898f185ffd7a54e37be61c3b837de57f6b1b415 Mon Sep 17 00:00:00 2001 From: cskarai Date: Tue, 20 Sep 2016 20:43:51 +0200 Subject: [PATCH 65/66] Fixed: file numbering --- WEB-SERVER.md | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/WEB-SERVER.md b/WEB-SERVER.md index 1d9700d..6bd2a3c 100644 --- a/WEB-SERVER.md +++ b/WEB-SERVER.md @@ -6,38 +6,39 @@ LED flashing sample Circuit: -. connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO: + - 1: connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO: (RX - levelshifter - TX, TX - levelshifter - RX) -. optionally connect RESET-s with a level shifter + - 2: optionally connect RESET-s with a level shifter Installation steps: -. install the latest Arduino on the PC -. install EspLink library from arduino/libraries path -. open EspLinkWebSimpleLedControl sample from Arduino -. upload the code onto an Arduino Nano/Uno -. install esp-link -. jump to the Web Server page on esp-link UI -. upload SimpleLED.html ( arduino/libraries/EspLink/examples/EspLinkWebSimpleLedControl/SimpleLED.html ) -. jump to SimpleLED page on esp-link UI -. turn on/off the LED + - 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: -. connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO: + - 1: connect a Nodemcu (ESP8266) board and an Arduino Nano / UNO: (RX - levelshifter - TX, TX - levelshifter - RX) -. optionally connect RESET-s with a level shifter -. add a trimmer to A0 for Voltage measurement + - 2: optionally connect RESET-s with a level shifter + - 3: add a trimmer to A0 for Voltage measurement Installation steps: -. open EspLinkWebApp sample from Arduino -. upload the code onto an Arduino Nano/Uno -. jump to the Web Server page on esp-link UI -. upload web-page.espfs.img ( arduino/libraries/EspLink/examples/EspLinkWebApp/web-page.espfs.img ) -. jump to LED/User/Voltage pages -. try out different settings + - 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 + From 687898e8c19f6cefc7c014cfd38cc151d35c1bb8 Mon Sep 17 00:00:00 2001 From: cskarai Date: Thu, 22 Sep 2016 20:34:39 +0200 Subject: [PATCH 66/66] Fixed: getMenu cache control --- esp-link/cgi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esp-link/cgi.c b/esp-link/cgi.c index 0052d0e..76b1539 100644 --- a/esp-link/cgi.c +++ b/esp-link/cgi.c @@ -194,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