diff --git a/Makefile b/Makefile index 6470145..c503bcf 100644 --- a/Makefile +++ b/Makefile @@ -216,7 +216,7 @@ libesphttpd/Makefile: $(Q) [[ -e "libesphttpd/Makefile" ]] || echo -e "\e[31mlibesphttpd submodule missing.\nIf build fails, run \"git submodule init\" and \"git submodule update\".\e[0m" libesphttpd: libesphttpd/Makefile - $(Q) make -C libesphttpd USE_OPENSDK=$(USE_OPENSDK) SERVERNAME_PREFIX="ESPTerm " -j4 + $(Q) make -C libesphttpd USE_OPENSDK=$(USE_OPENSDK) -j4 $(APP_AR): libesphttpd $(OBJ) $(vecho) "AR $@" diff --git a/esphttpdconfig.mk.example b/esphttpdconfig.mk.example index 714dce8..d95d005 100644 --- a/esphttpdconfig.mk.example +++ b/esphttpdconfig.mk.example @@ -39,6 +39,8 @@ OUTPUT_TYPE = combined ESP_SPI_FLASH_SIZE_K = 1024 GLOBAL_CFLAGS = \ + -DASYNC_LOG=1 \ + -DDEBUG_D2D=0 \ -DDEBUG_ROUTER=0 \ -DDEBUG_CAPTDNS=0 \ -DDEBUG_HTTP=0 \ diff --git a/front-end b/front-end index 29b8134..6f165da 160000 --- a/front-end +++ b/front-end @@ -1 +1 @@ -Subproject commit 29b813457c7a185c41e050d358b001fadd2498e4 +Subproject commit 6f165da9b6237fd88efc836a663a429ed8aadb6e diff --git a/libesphttpd b/libesphttpd index 7fce947..3484209 160000 --- a/libesphttpd +++ b/libesphttpd @@ -1 +1 @@ -Subproject commit 7fce9474395e208c83325ff6150fdd21ba16c9a4 +Subproject commit 348420959b919d95412daeafe5014f4255854f80 diff --git a/user/ansi_parser.c b/user/ansi_parser.c index 37d30cf..a6bbe79 100644 --- a/user/ansi_parser.c +++ b/user/ansi_parser.c @@ -36,10 +36,6 @@ static const int ansi_en_main = 1; /* #line 12 "user/ansi_parser.rl" */ -// Max nr of CSI parameters -#define CSI_N_MAX 10 -#define ANSI_STR_LEN 64 - static volatile int cs = -1; static volatile bool inside_string = false; @@ -123,12 +119,12 @@ ansi_parser(char newchar) // Init Ragel on the first run if (cs == -1) { -/* #line 127 "user/ansi_parser.c" */ +/* #line 123 "user/ansi_parser.c" */ { cs = ansi_start; } -/* #line 101 "user/ansi_parser.rl" */ +/* #line 97 "user/ansi_parser.rl" */ #if DEBUG_ANSI memset(history, 0, sizeof(history)); @@ -142,6 +138,13 @@ ansi_parser(char newchar) history[HISTORY_LEN-1] = newchar; #endif + // THose should work always, even inside a string + if (newchar == CAN || newchar == SUB) { + // Cancel the active sequence + cs = ansi_start; + return; + } + // Handle simple characters immediately (bypass parser) if (newchar < ' ' && !inside_string) { switch (newchar) { @@ -183,12 +186,6 @@ ansi_parser(char newchar) apars_handle_enq(); return; - // Cancel the active sequence - case CAN: - case SUB: - cs = ansi_start; - return; - default: // Discard all other control codes return; @@ -208,7 +205,7 @@ ansi_parser(char newchar) // The parser -/* #line 212 "user/ansi_parser.c" */ +/* #line 209 "user/ansi_parser.c" */ { const char *_acts; unsigned int _nacts; @@ -398,7 +395,7 @@ execFuncs: while ( _nacts-- > 0 ) { switch ( *_acts++ ) { case 0: -/* #line 188 "user/ansi_parser.rl" */ +/* #line 185 "user/ansi_parser.rl" */ { ansi_warn("Parser error."); apars_show_context(); @@ -407,7 +404,7 @@ execFuncs: } break; case 1: -/* #line 197 "user/ansi_parser.rl" */ +/* #line 194 "user/ansi_parser.rl" */ { if ((*p) != 0) { apars_handle_plainchar((*p)); @@ -415,7 +412,7 @@ execFuncs: } break; case 2: -/* #line 205 "user/ansi_parser.rl" */ +/* #line 202 "user/ansi_parser.rl" */ { // Reset the CSI builder leadchar = NUL; @@ -432,13 +429,13 @@ execFuncs: } break; case 3: -/* #line 220 "user/ansi_parser.rl" */ +/* #line 217 "user/ansi_parser.rl" */ { leadchar = (*p); } break; case 4: -/* #line 224 "user/ansi_parser.rl" */ +/* #line 221 "user/ansi_parser.rl" */ { if (arg_cnt == 0) arg_cnt = 1; // x10 + digit @@ -448,7 +445,7 @@ execFuncs: } break; case 5: -/* #line 232 "user/ansi_parser.rl" */ +/* #line 229 "user/ansi_parser.rl" */ { if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty arg_cnt++; @@ -456,20 +453,20 @@ execFuncs: } break; case 6: -/* #line 238 "user/ansi_parser.rl" */ +/* #line 235 "user/ansi_parser.rl" */ { interchar = (*p); } break; case 7: -/* #line 242 "user/ansi_parser.rl" */ +/* #line 239 "user/ansi_parser.rl" */ { apars_handle_csi(leadchar, arg, arg_cnt, interchar, (*p)); {cs = 1;goto _again;} } break; case 8: -/* #line 254 "user/ansi_parser.rl" */ +/* #line 251 "user/ansi_parser.rl" */ { leadchar = (*p); str_ni = 0; @@ -479,13 +476,13 @@ execFuncs: } break; case 9: -/* #line 262 "user/ansi_parser.rl" */ +/* #line 259 "user/ansi_parser.rl" */ { string_buffer[str_ni++] = (*p); } break; case 10: -/* #line 266 "user/ansi_parser.rl" */ +/* #line 263 "user/ansi_parser.rl" */ { inside_string = false; string_buffer[str_ni++] = '\0'; @@ -494,41 +491,41 @@ execFuncs: } break; case 11: -/* #line 279 "user/ansi_parser.rl" */ +/* #line 276 "user/ansi_parser.rl" */ { apars_handle_hash_cmd((*p)); {cs = 1;goto _again;} } break; case 12: -/* #line 284 "user/ansi_parser.rl" */ +/* #line 281 "user/ansi_parser.rl" */ { apars_handle_short_cmd((*p)); {cs = 1;goto _again;} } break; case 13: -/* #line 289 "user/ansi_parser.rl" */ +/* #line 286 "user/ansi_parser.rl" */ { apars_handle_space_cmd((*p)); {cs = 1;goto _again;} } break; case 14: -/* #line 296 "user/ansi_parser.rl" */ +/* #line 293 "user/ansi_parser.rl" */ { leadchar = (*p); {cs = 10;goto _again;} } break; case 15: -/* #line 301 "user/ansi_parser.rl" */ +/* #line 298 "user/ansi_parser.rl" */ { apars_handle_chs_designate(leadchar, (*p)); {cs = 1;goto _again;} } break; -/* #line 532 "user/ansi_parser.c" */ +/* #line 529 "user/ansi_parser.c" */ } } goto _again; @@ -546,7 +543,7 @@ _again: while ( __nacts-- > 0 ) { switch ( *__acts++ ) { case 0: -/* #line 188 "user/ansi_parser.rl" */ +/* #line 185 "user/ansi_parser.rl" */ { ansi_warn("Parser error."); apars_show_context(); @@ -556,7 +553,7 @@ _again: goto _again;} } break; -/* #line 560 "user/ansi_parser.c" */ +/* #line 557 "user/ansi_parser.c" */ } } } @@ -564,6 +561,6 @@ goto _again;} _out: {} } -/* #line 324 "user/ansi_parser.rl" */ +/* #line 321 "user/ansi_parser.rl" */ } diff --git a/user/ansi_parser.h b/user/ansi_parser.h index 212aaa4..1aa9528 100644 --- a/user/ansi_parser.h +++ b/user/ansi_parser.h @@ -3,6 +3,9 @@ #include +#define CSI_N_MAX 12 +#define ANSI_STR_LEN 256 + extern volatile bool ansi_parser_inhibit; // discard all characters void ansi_parser_reset(void); diff --git a/user/ansi_parser.rl b/user/ansi_parser.rl index c2042c7..67b3ef0 100644 --- a/user/ansi_parser.rl +++ b/user/ansi_parser.rl @@ -11,10 +11,6 @@ write data; }%% -// Max nr of CSI parameters -#define CSI_N_MAX 10 -#define ANSI_STR_LEN 64 - static volatile int cs = -1; static volatile bool inside_string = false; @@ -111,6 +107,13 @@ ansi_parser(char newchar) history[HISTORY_LEN-1] = newchar; #endif + // THose should work always, even inside a string + if (newchar == CAN || newchar == SUB) { + // Cancel the active sequence + cs = ansi_start; + return; + } + // Handle simple characters immediately (bypass parser) if (newchar < ' ' && !inside_string) { switch (newchar) { @@ -152,12 +155,6 @@ ansi_parser(char newchar) apars_handle_enq(); return; - // Cancel the active sequence - case CAN: - case SUB: - cs = ansi_start; - return; - default: // Discard all other control codes return; diff --git a/user/ansi_parser_callbacks.c b/user/ansi_parser_callbacks.c index fa6173f..014c968 100644 --- a/user/ansi_parser_callbacks.c +++ b/user/ansi_parser_callbacks.c @@ -11,6 +11,14 @@ #include "version.h" #include "uart_buffer.h" #include "screen.h" +#include "uart_driver.h" + +volatile bool enquiry_suppressed = false; +ETSTimer enqTimer; +void ICACHE_FLASH_ATTR enqTimerCb(void *unused) +{ + enquiry_suppressed = false; +} /** * Send a response to UART0 @@ -19,7 +27,8 @@ void ICACHE_FLASH_ATTR apars_respond(const char *str) { - UART_SendAsync(str, -1); + UART_WriteString(UART0, str, UART_TIMEOUT_US); + //UART_SendAsync(str, -1); } /** @@ -37,8 +46,16 @@ apars_handle_bel(void) void ICACHE_FLASH_ATTR apars_handle_enq(void) { + if (enquiry_suppressed) return; + // version encased in SOS and ST apars_respond("\x1bXESPTerm " FIRMWARE_VERSION "\x1b\\"); + + // Throttle enquiry - this is a single-character-invoked response, + // so it tends to happen randomly when throwing garbage at the ESP. + // We don't want to fill the output buffer with dozens of enquiry responses + enquiry_suppressed = true; + TIMER_START(&enqTimer, enqTimerCb, 500, 0); } void ICACHE_FLASH_ATTR diff --git a/user/apars_dcs.c b/user/apars_dcs.c index 7462d26..0bf5129 100644 --- a/user/apars_dcs.c +++ b/user/apars_dcs.c @@ -30,7 +30,7 @@ * @param buffer - the DCS body (after DCS and before ST) */ void ICACHE_FLASH_ATTR -apars_handle_dcs(const char *buffer) +apars_handle_dcs(char *buffer) { char buf[64]; // just about big enough for full-house SGR size_t len = strlen(buffer); diff --git a/user/apars_dcs.h b/user/apars_dcs.h index f9d4ad7..012c6fc 100644 --- a/user/apars_dcs.h +++ b/user/apars_dcs.h @@ -5,6 +5,6 @@ #ifndef ESP_VT100_FIRMWARE_APARS_DCS_H #define ESP_VT100_FIRMWARE_APARS_DCS_H -void apars_handle_dcs(const char *buffer); +void apars_handle_dcs(char *buffer); #endif //ESP_VT100_FIRMWARE_APARS_DCS_H diff --git a/user/apars_pm.c b/user/apars_pm.c new file mode 100644 index 0000000..c53ef13 --- /dev/null +++ b/user/apars_pm.c @@ -0,0 +1,34 @@ +// +// Created by MightyPork on 2017/08/20. +// +// Handle privacy messages +// PM Pt ST +// (PM = ESC ^) +// +// Those are used for device-to-device communication. +// They were not used for anything in the original VT100 and are not +// used by Xterm or any other common emulator, but they should be safely discarded. +// + +#include +#include +#include "apars_pm.h" +#include "version.h" +#include "ansi_parser_callbacks.h" +#include "screen.h" +#include "apars_logging.h" +#include "cgi_d2d.h" + +/** + * Helper function to parse incoming DCS (Device Control String) + * @param msg - the DCS body (after DCS and before ST) + */ +void ICACHE_FLASH_ATTR +apars_handle_pm(char *msg) +{ + if (d2d_parse_command(msg)) return; + + return; +fail: + ansi_warn("D2D message error: %s", msg); +} diff --git a/user/apars_pm.h b/user/apars_pm.h new file mode 100644 index 0000000..f1b241b --- /dev/null +++ b/user/apars_pm.h @@ -0,0 +1,10 @@ +// +// Created by MightyPork on 2017/08/20. +// + +#ifndef ESP_VT100_FIRMWARE_APARS_PM_H +#define ESP_VT100_FIRMWARE_APARS_PM_H + +void apars_handle_pm(char *msg); + +#endif //ESP_VT100_FIRMWARE_APARS_PM_H diff --git a/user/apars_short.c b/user/apars_short.c index e00b2e2..7fb35c5 100644 --- a/user/apars_short.c +++ b/user/apars_short.c @@ -46,6 +46,7 @@ #include "apars_short.h" #include "apars_logging.h" #include "screen.h" +#include "ansi_parser_callbacks.h" // ----- Character Set --- diff --git a/user/apars_string.c b/user/apars_string.c index dbd2382..16bc94f 100644 --- a/user/apars_string.c +++ b/user/apars_string.c @@ -17,6 +17,7 @@ #include "apars_logging.h" #include "ansi_parser_callbacks.h" #include "screen.h" +#include "apars_pm.h" // ----- Generic String cmd - disambiguation ----- @@ -24,7 +25,7 @@ void ICACHE_FLASH_ATTR apars_handle_string_cmd(char leadchar, char *buffer) { switch (leadchar) { - case 'k': // ESC k TITLE ST (defined in GNU screen manpage) + case 'k': // ESC k TITLE ST (defined in GNU screen manpage, probably not standard) screen_set_title(buffer); break; @@ -37,6 +38,7 @@ apars_handle_string_cmd(char leadchar, char *buffer) break; case '^': // PM - Privacy Message + apars_handle_pm(buffer); break; case '_': // APC - Application Program Command diff --git a/user/api.h b/user/api.h new file mode 100644 index 0000000..7f16a8b --- /dev/null +++ b/user/api.h @@ -0,0 +1,15 @@ +// +// Created by MightyPork on 2017/10/01. +// + +#ifndef ESPTERM_API_H +#define ESPTERM_API_H + +// TODO use X-MACRO for access restrictions etc + +#define API_D2D_MSG "/api/v1/msg" +#define API_REBOOT "/api/v1/reboot" +#define API_PING "/api/v1/ping" +#define API_CLEAR "/api/v1/clear" + +#endif //ESPTERM_API_H diff --git a/user/cgi_d2d.c b/user/cgi_d2d.c new file mode 100644 index 0000000..931920d --- /dev/null +++ b/user/cgi_d2d.c @@ -0,0 +1,316 @@ +// +// Created by MightyPork on 2017/10/01. +// + +#include +#include "cgi_d2d.h" +#include "version.h" +#include "ansi_parser_callbacks.h" +#include "api.h" +#include +#include + +#define D2D_TIMEOUT_MS 2000 + +#define D2D_HEADERS \ + "User-Agent: ESPTerm "FIRMWARE_VERSION" like curl wget HTTPie\r\n" \ + "Content-Type: text/plain; charset=utf-8\r\n" \ + "Accept-Encoding: identity\r\n" \ + "Accept-Charset: utf-8\r\n" \ + "Accept: text/*, application/json\r\n" \ + "Cache-Control: no-cache,private,max-age=0\r\n" + +struct d2d_request_opts { + bool want_body; + bool want_head; + size_t max_result_len; + char *nonce; +}; + +volatile bool request_pending = false; + +static void ICACHE_FLASH_ATTR +requestNoopCb(int http_status, + char *response_headers, + char *response_body, + size_t body_size, + void *userArg) +{ + request_pending = false; + if (userArg != NULL) free(userArg); +} + +static void ICACHE_FLASH_ATTR +requestCb(int http_status, + char *response_headers, + char *response_body, + size_t body_size, + void *userArg) +{ + if (userArg == NULL) { + request_pending = false; + return; + } + + struct d2d_request_opts *opts = userArg; + + d2d_dbg("Rx url response, code %d, nonce \"%s\"", http_status, opts->nonce?opts->nonce:""); + + // ensure positive - would be hard to parse + if (http_status < 0) http_status = -http_status; + + char buff100[100]; + int len = 0; + size_t headers_size = strlen(response_headers); + + if (opts->want_head) len += headers_size; + if (opts->want_body) len += body_size + (opts->want_head*2); + if (opts->max_result_len > 0 && len > opts->max_result_len) + len = (int) opts->max_result_len; + + char *bb = buff100; + bb += sprintf(bb, "\x1b^h;%d;", http_status); + const char *comma = ""; + + if (opts->want_head) { + bb += sprintf(bb, "%sH", comma); + comma = ","; + } + + if (opts->want_body) { + bb += sprintf(bb, "%sB", comma); + comma = ","; + } + + if (opts->nonce) { + bb += sprintf(bb, "%sN=%s", comma, opts->nonce); + comma = ","; + } + + if (opts->want_head || opts->want_body) { + bb += sprintf(bb, "%sL=%d", comma, len); + //comma = ","; + } + + // semicolon only if more data is to be sent + if (opts->want_head || opts->want_body) sprintf(bb, ";"); + + apars_respond(buff100); + + //d2d_dbg("Headers (part) %100s", response_headers); + //d2d_dbg("Body (part) %100s", response_body); + + // head and payload separated by \r\n\r\n (one \r\n is at the end of head - maybe) + if (opts->want_head) { + // truncate + if (headers_size > len) { + response_headers[len] = 0; + opts->want_body = false; // soz, it wouldn't fit + } + apars_respond(response_headers); + if(opts->want_body) apars_respond("\r\n"); + } + + if(opts->want_body) { + // truncate + if (opts->want_head*(headers_size+2)+body_size > len) { + response_body[len - (opts->want_head*(headers_size+2))] = 0; + } + + apars_respond(response_body); + } + + apars_respond("\a"); + + free(opts->nonce); + free(opts); + + request_pending = false; +} + +bool ICACHE_FLASH_ATTR +d2d_parse_command(char *msg) +{ + char buff40[40]; + char *p; + +#define FIND_NEXT(target, delim) do { \ + p = strchr(msg, (delim)); \ + if (p == NULL) return false; \ + *p = '\0'; \ + (target) = msg; \ + msg = p + 1; \ +} while(0) \ + + if (strstarts(msg, "M;")) { + if (request_pending) return false; + + // Send a esp-esp message + msg += 2; + const char *ip; + FIND_NEXT(ip, ';'); + const char *payload = msg; + + d2d_dbg("D2D Tx,dest=%s,msg=%s", ip, payload); + sprintf(buff40, "http://%s" API_D2D_MSG, ip); + + httpclient_args args; + httpclient_args_init(&args); + args.method = HTTPD_METHOD_POST; + args.body = payload; + args.headers = D2D_HEADERS; + args.timeout = D2D_TIMEOUT_MS; + args.url = buff40; // "escapes scope" warning - can ignore, strdup is used + + request_pending = true; + http_request(&args, requestNoopCb); + return true; + } + else if (strstarts(msg, "H;")) { + if (request_pending) return false; + + // Send a esp-esp message + msg += 2; + const char *method = NULL; + const char *params = NULL; + const char *nonce = NULL; + const char *url = NULL; + const char *payload = NULL; + httpd_method methodNum; + + FIND_NEXT(method, ';'); + + if (streq(method, "GET")) methodNum = HTTPD_METHOD_GET; + else if (streq(method, "POST")) methodNum = HTTPD_METHOD_POST; + else if (streq(method, "OPTIONS")) methodNum = HTTPD_METHOD_OPTIONS; + else if (streq(method, "PUT")) methodNum = HTTPD_METHOD_PUT; + else if (streq(method, "DELETE")) methodNum = HTTPD_METHOD_DELETE; + else if (streq(method, "PATCH")) methodNum = HTTPD_METHOD_PATCH; + else if (streq(method, "HEAD")) methodNum = HTTPD_METHOD_HEAD; + else { + d2d_warn("BAD METHOD: %s", method); + return false; + } + + FIND_NEXT(params, ';'); + + d2d_dbg("Method %s", method); + d2d_dbg("Params %s", params); + + size_t max_buf_len = HTTPCLIENT_DEF_MAX_LEN; + size_t max_result_len = 0; // 0 = no truncate + uint timeout = HTTPCLIENT_DEF_TIMEOUT_MS; + bool want_body = 0; + bool want_head = 0; + bool no_resp = 0; + + do { + p = strchr(params, ','); + if (p != NULL) *p = '\0'; + const char *param = params; + if (params[0] == 0) break; // no params + + if(streq(param, "H")) want_head = 1; // Return head + else if(streq(param, "B")) want_body = 1; // Return body + else if(streq(param, "X")) no_resp = 1; // X - no response, no callback + else if(strstarts(param, "l=")) { // max buffer length + max_buf_len = (size_t) atoi(param + 2); + } else if(strstarts(param, "L=")) { // max length + max_result_len = (size_t) atoi(param + 2); + } else if(strstarts(param, "T=")) { // timeout + timeout = (uint) atoi(param + 2); + } else if(strstarts(param, "N=")) { // Nonce + nonce = param+2; + } else { + d2d_warn("BAD PARAM: %s", param); + return false; + } + + d2d_dbg("- param %s", params); + + if (p == NULL) break; + params = p + 1; + } while(1); + + p = strchr(msg, '\n'); + if (p != NULL) *p = '\0'; + url = msg; + d2d_dbg("URL: %s", url); + + if (p != NULL) { + payload = p + 1; + d2d_dbg("Payload: %s", payload); + } else { + payload = NULL; + } + + httpclient_args args; + httpclient_args_init(&args); + args.method = methodNum; + args.body = payload; + args.headers = D2D_HEADERS; + args.timeout = timeout; + args.max_response_len = max_buf_len; + args.url = url; + + if (!no_resp) { + struct d2d_request_opts *opts = malloc(sizeof(struct d2d_request_opts)); + opts->want_body = want_body; + opts->want_head = want_head; + opts->max_result_len = max_result_len; + opts->nonce = esp_strdup(nonce); + args.userData = opts; + } + + request_pending = true; + http_request(&args, no_resp ? requestNoopCb : requestCb); + + d2d_dbg("Done"); + return true; + } + + return false; +} + +httpd_cgi_state ICACHE_FLASH_ATTR cgiD2DMessage(HttpdConnData *connData) +{ + if (connData->conn==NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + size_t len = 0; + if (connData->post && connData->post->buff) + len = strlen(connData->post->buff); + else if (connData->getArgs) + len = strlen(connData->getArgs); + else + len = 0; + + u8 *ip = connData->remote_ip; + char buf[20]; + sprintf(buf, "\x1b^m;"IPSTR";L=%d;", ip[0], ip[1], ip[2], ip[3], (int)len); + apars_respond(buf); + + if (connData->post && connData->post->buff) + apars_respond(connData->post->buff); + else if (connData->getArgs) + apars_respond(connData->getArgs); + + apars_respond("\a"); + + d2d_dbg("D2D Rx src="IPSTR",len=%d", ip[0], ip[1], ip[2], ip[3],len); + + // Received a msg + + httdResponseOptions(connData, 0); + httdSetTransferMode(connData, HTTPD_TRANSFER_CLOSE); + + httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Type", "text/plain"); + httpdEndHeaders(connData); + + httpdSend(connData, "message received\r\n", -1); + + return HTTPD_CGI_DONE; +} diff --git a/user/cgi_d2d.h b/user/cgi_d2d.h new file mode 100644 index 0000000..4eb3a74 --- /dev/null +++ b/user/cgi_d2d.h @@ -0,0 +1,25 @@ +// +// Created by MightyPork on 2017/10/01. +// + +#ifndef ESPTERM_CGI_D2D_H +#define ESPTERM_CGI_D2D_H + +#include +#include + +#if DEBUG_D2D +#define d2d_warn warn +#define d2d_dbg dbg +#define d2d_info info +#else +#define d2d_warn(fmt, ...) +#define d2d_dbg(fmt, ...) +#define d2d_info(fmt, ...) +#endif + +bool d2d_parse_command(char *msg); + +httpd_cgi_state cgiD2DMessage(HttpdConnData *connData); + +#endif //ESPTERM_CGI_D2D_H diff --git a/user/cgi_sockets.c b/user/cgi_sockets.c index 9d6a087..565afa5 100644 --- a/user/cgi_sockets.c +++ b/user/cgi_sockets.c @@ -69,7 +69,7 @@ updateNotify_do(Websock *ws, ScreenNotifyTopics topics) } httpd_cgi_state cont = screenSerializeToBuffer(sock_buff, SOCK_BUF_LEN, topics, &data); - int flg = WEBSOCK_FLAG_BIN; + int flg = 0; //WEBSOCK_FLAG_BIN if (cont == HTTPD_CGI_MORE) flg |= WEBSOCK_FLAG_MORE; if (i > 0) flg |= WEBSOCK_FLAG_CONT; if (ws) { @@ -260,7 +260,7 @@ static void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int // TODO base this on the actual buffer empty space, not rx chunk size if ((UART_AsyncTxGetEmptySpace() < 256) && !browser_wants_xon) { - UART_WriteChar(UART1, '-', 100); + //UART_WriteChar(UART1, '-', 100); cgiWebsockBroadcast(URL_WS_UPDATE, "-", 1, 0); browser_wants_xon = true; @@ -304,13 +304,17 @@ static void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int /** Send a heartbeat msg */ static void ICACHE_FLASH_ATTR heartbeatTimCb(void *unused) { + static u32 hbcnt=0; + if (term_active_clients > 0) { if (notify_available) { inp_dbg("."); // Heartbeat packet - indicate we're still connected // JS reloads the page if heartbeat is lost for a couple seconds - cgiWebsockBroadcast(URL_WS_UPDATE, ".", 1, 0); + char buf[10]; + sprintf(buf, ".%d", hbcnt++); + cgiWebsockBroadcast(URL_WS_UPDATE, buf, (int) strlen(buf), 0); // schedule next tick TIMER_START(&heartbeatTim, heartbeatTimCb, HB_TIME, 0); @@ -331,6 +335,7 @@ static void ICACHE_FLASH_ATTR resetHeartbeatTimer(void) static void ICACHE_FLASH_ATTR closeSockCb(Websock *ws) { term_active_clients--; + inp_dbg("Close socket CB, remain %d clients", term_active_clients); if (term_active_clients <= 0) { term_active_clients = 0; @@ -340,6 +345,7 @@ static void ICACHE_FLASH_ATTR closeSockCb(Websock *ws) // stop the timer os_timer_disarm(&heartbeatTim); + inp_dbg("Stop HB timer"); } } @@ -365,7 +371,7 @@ ETSTimer xonTim; static void ICACHE_FLASH_ATTR notify_empty_txbuf_cb(void *unused) { - UART_WriteChar(UART1, '+', 100); + //UART_WriteChar(UART1, '+', 100); cgiWebsockBroadcast(URL_WS_UPDATE, "+", 1, 0); resetHeartbeatTimer(); browser_wants_xon = false; diff --git a/user/cgi_system.c b/user/cgi_system.c index 8418c5d..d23d20b 100755 --- a/user/cgi_system.c +++ b/user/cgi_system.c @@ -50,7 +50,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiResetDevice(HttpdConnData *connData) os_timer_setfn(&tmr, tmrCb, NULL); os_timer_arm(&tmr, 100, false); - httpdSend(connData, "system reset\n", -1); + httpdSend(connData, "system reset\r\n", -1); return HTTPD_CGI_DONE; } @@ -66,7 +66,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiPing(HttpdConnData *connData) httpdHeader(connData, "Content-Type", "text/plain"); httpdEndHeaders(connData); - httpdSend(connData, "pong\n", -1); + httpdSend(connData, "pong\r\n", -1); return HTTPD_CGI_DONE; } diff --git a/user/routes.c b/user/routes.c index 9dfd8a5..4b6b502 100644 --- a/user/routes.c +++ b/user/routes.c @@ -14,6 +14,8 @@ #include "cgi_persist.h" #include "syscfg.h" #include "persist.h" +#include "api.h" +#include "cgi_d2d.h" /** * Password for WiFi config @@ -45,7 +47,8 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiOptionalPwLock(HttpdConnData *connData) break; case PWLOCK_SETTINGS_NOTERM: - protect = strstarts(connData->url, "/cfg") && !strstarts(connData->url, "/cfg/term"); + protect = strstarts(connData->url, "/cfg") && + !strstarts(connData->url, "/cfg/term"); break; case PWLOCK_SETTINGS_ALL: @@ -53,7 +56,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiOptionalPwLock(HttpdConnData *connData) break; case PWLOCK_MENUS: - protect = strstarts(connData->url, "/cfg") || strstarts(connData->url, "/about") || strstarts(connData->url, "/help"); + protect = strstarts(connData->url, "/cfg") || + strstarts(connData->url, "/about") || + strstarts(connData->url, "/help"); break; default: @@ -64,11 +69,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiOptionalPwLock(HttpdConnData *connData) // pages outside the normal scope if (sysconf->pwlock > PWLOCK_NONE) { - if (strstarts(connData->url, "/system/reset")) protect = true; + if (strstarts(connData->url, "/api/v1/reboot")) protect = true; } if (sysconf->pwlock > PWLOCK_SETTINGS_NOTERM) { - if (strstarts(connData->url, "/system/cls")) protect = true; + if (strstarts(connData->url, "/api/v1/clear")) protect = true; } if (sysconf->access_pw[0] == 0) { @@ -103,9 +108,12 @@ const HttpdBuiltInUrl routes[] ESP_CONST_DATA = { ROUTE_WS(URL_WS_UPDATE, updateSockConnect), // --- System control --- - ROUTE_CGI("/system/reset/?", cgiResetDevice), - ROUTE_CGI("/system/ping/?", cgiPing), - ROUTE_CGI("/system/cls/?", cgiResetScreen), + + // API endpoints + ROUTE_CGI(API_REBOOT"/?", cgiResetDevice), + ROUTE_CGI(API_PING"/?", cgiPing), + ROUTE_CGI(API_CLEAR"/?", cgiResetScreen), + ROUTE_CGI(API_D2D_MSG"/?", cgiD2DMessage), ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"), diff --git a/user/serial.c b/user/serial.c index 0a960cc..9c9edce 100644 --- a/user/serial.c +++ b/user/serial.c @@ -40,10 +40,10 @@ buf_pop(void *unused) } } -//LOCAL void my_putc(char c) -//{ -// UART_WriteCharCRLF(UART1, (u8) c, 10); -//} +LOCAL void my_putc(char c) +{ + UART_WriteCharCRLF(UART1, (u8) c, 10); +} /** * Init the serial ports @@ -56,8 +56,11 @@ void ICACHE_FLASH_ATTR serialInitBase(void) UART_SetStopBits(UART1, ONE_STOP_BIT); UART_SetBaudrate(UART1, BIT_RATE_115200); UART_SetPrintPort(UART1); +#if ASYNC_LOG os_install_putc1(buf_putc); - //os_install_putc1(my_putc); +#else + os_install_putc1(my_putc); +#endif UART_SetupAsyncReceiver(); // 1 ms timer diff --git a/user/uart_buffer.c b/user/uart_buffer.c index 20bc3c6..7c07d8f 100644 --- a/user/uart_buffer.c +++ b/user/uart_buffer.c @@ -8,8 +8,8 @@ #include #include -#define UART_TX_BUFFER_SIZE 512 //Ring buffer length of tx buffer -#define UART_RX_BUFFER_SIZE 600 //Ring buffer length of rx buffer +//#define buf_dbg(format, ...) printf(format "\r\n", ##__VA_ARGS__) +#define buf_dbg(format, ...) (void)format struct UartBuffer { uint32 UartBuffSize; @@ -22,12 +22,15 @@ struct UartBuffer { static struct UartBuffer *pTxBuffer = NULL; static struct UartBuffer *pRxBuffer = NULL; -static struct UartBuffer *UART_AsyncBufferInit(uint32 buf_size); +static u8 rxArray[UART_RX_BUFFER_SIZE]; +static u8 txArray[UART_TX_BUFFER_SIZE]; + +static struct UartBuffer *UART_AsyncBufferInit(uint32 buf_size, u8 *buffer); void ICACHE_FLASH_ATTR UART_AllocBuffers(void) { - pTxBuffer = UART_AsyncBufferInit(UART_TX_BUFFER_SIZE); - pRxBuffer = UART_AsyncBufferInit(UART_RX_BUFFER_SIZE); + pTxBuffer = UART_AsyncBufferInit(UART_TX_BUFFER_SIZE, txArray); + pRxBuffer = UART_AsyncBufferInit(UART_RX_BUFFER_SIZE, rxArray); } /****************************************************************************** @@ -37,7 +40,7 @@ void ICACHE_FLASH_ATTR UART_AllocBuffers(void) * Returns : NONE *******************************************************************************/ static struct UartBuffer *ICACHE_FLASH_ATTR -UART_AsyncBufferInit(uint32 buf_size) +UART_AsyncBufferInit(uint32 buf_size, u8 *buffer) { uint32 heap_size = system_get_free_heap_size(); if (heap_size <= buf_size) { @@ -47,7 +50,7 @@ UART_AsyncBufferInit(uint32 buf_size) else { struct UartBuffer *pBuff = (struct UartBuffer *) malloc(sizeof(struct UartBuffer)); pBuff->UartBuffSize = buf_size; - pBuff->pUartBuff = (uint8 *) malloc(pBuff->UartBuffSize); + pBuff->pUartBuff = buffer != NULL ? buffer : (uint8 *) malloc(pBuff->UartBuffSize); pBuff->pInPos = pBuff->pUartBuff; pBuff->pOutPos = pBuff->pUartBuff; pBuff->Space = (uint16) pBuff->UartBuffSize; @@ -55,6 +58,13 @@ UART_AsyncBufferInit(uint32 buf_size) } } +static void ICACHE_FLASH_ATTR +UART_AsyncBufferReset(struct UartBuffer *pBuff) +{ + pBuff->pInPos = pBuff->pUartBuff; + pBuff->pOutPos = pBuff->pUartBuff; + pBuff->Space = (uint16) pBuff->UartBuffSize; +} /** * Copy data onto Buffer @@ -67,23 +77,30 @@ UART_WriteToAsyncBuffer(struct UartBuffer *pCur, const char *pdata, uint16 data_ { if (data_len == 0) return; + buf_dbg("WTAB %d, space %d", data_len, pCur->Space); + uint16 tail_len = (uint16) (pCur->pUartBuff + pCur->UartBuffSize - pCur->pInPos); if (tail_len >= data_len) { //do not need to loop back the queue + buf_dbg("tail %d, no fold", tail_len); memcpy(pCur->pInPos, pdata, data_len); pCur->pInPos += (data_len); pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize); pCur->Space -= data_len; } else { + buf_dbg("tail only %d, folding", tail_len); memcpy(pCur->pInPos, pdata, tail_len); + buf_dbg("chunk 1, %d", tail_len); pCur->pInPos += (tail_len); pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize); pCur->Space -= tail_len; + buf_dbg("chunk 2, %d", data_len - tail_len); memcpy(pCur->pInPos, pdata + tail_len, data_len - tail_len); pCur->pInPos += (data_len - tail_len); pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize); pCur->Space -= (data_len - tail_len); } + buf_dbg("new space %d", pCur->Space); } /****************************************************************************** @@ -158,9 +175,8 @@ void UART_RxFifoCollect(void) uint8 fifo_data; fifo_len = (uint8) ((READ_PERI_REG(UART_STATUS(UART0)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT); if (fifo_len >= pRxBuffer->Space) { - // try to read at least the bit we can fifo_len = (uint8) (pRxBuffer->Space - 1); - UART_WriteChar(UART1, '%', 1); + UART_WriteChar(UART1, '#', 10); // discard contents of the FIFO - would loop forever buf_idx = 0; while (buf_idx < fifo_len) { @@ -193,35 +209,30 @@ u16 ICACHE_FLASH_ATTR UART_AsyncTxGetEmptySpace(void) return pTxBuffer->Space; } +u16 ICACHE_FLASH_ATTR UART_AsyncTxCount(void) +{ + return (u16) (pTxBuffer->UartBuffSize - pTxBuffer->Space); +} + /** * Schedule data to be sent * @param pdata * @param data_len - can be -1 for strlen */ void ICACHE_FLASH_ATTR -UART_SendAsync(const char *pdata, int16_t data_len) +UART_SendAsync(const char *pdata, int data_len) { - u16 real_len = (u16) data_len; - if (data_len <= 0) real_len = (u16) strlen(pdata); + size_t real_len = (data_len) <= 0 ? strlen(pdata) : (size_t) data_len; -// if (pTxBuffer == NULL) { -// printf("init tx buf\n\r"); -// pTxBuffer = UART_AsyncBufferInit(UART_TX_BUFFER_SIZE); -// if (pTxBuffer != NULL) { -// UART_WriteToAsyncBuffer(pTxBuffer, pdata, real_len); -// } -// else { -// printf("tx alloc fail\r\n"); -// } -// } -// else { - if (real_len <= pTxBuffer->Space) { - UART_WriteToAsyncBuffer(pTxBuffer, pdata, real_len); - } - else { - UART_WriteChar(UART1, '^', 1); - } -// } + buf_dbg("Send Async %d", real_len); + if (real_len <= pTxBuffer->Space) { + buf_dbg("accepted, space %d", pTxBuffer->Space); + UART_WriteToAsyncBuffer(pTxBuffer, pdata, (uint16) real_len); + } + else { + buf_dbg("FULL!"); + UART_WriteChar(UART1, '=', 10); + } // Here we enable TX empty interrupt that will take care of sending the content SET_PERI_REG_MASK(UART_CONF1(UART0), (UART_TX_EMPTY_THRESH_VAL & UART_TXFIFO_EMPTY_THRHD) << UART_TXFIFO_EMPTY_THRHD_S); @@ -257,8 +268,8 @@ void UART_DispatchFromTxBuffer(uint8 uart_no) uint8 len_tmp; uint16 data_len; -// if (pTxBuffer) { - data_len = (uint8) (pTxBuffer->UartBuffSize - pTxBuffer->Space); + data_len = (uint16) (pTxBuffer->UartBuffSize - pTxBuffer->Space); + buf_dbg("rem %d",data_len); if (data_len > fifo_remain) { len_tmp = fifo_remain; UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no); @@ -268,8 +279,9 @@ void UART_DispatchFromTxBuffer(uint8 uart_no) len_tmp = (uint8) data_len; UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no); - // we get one more IT after fifo ends even if we have 0 more bytes - // for notify + // We get one more IT after fifo ends even if we have 0 more bytes, + // for notify. Otherwise we would say we have space while the FIFO + // was still running if (next_empty_it_only_for_notify) { notify_empty_txbuf(); next_empty_it_only_for_notify = 0; @@ -279,9 +291,4 @@ void UART_DispatchFromTxBuffer(uint8 uart_no) SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA); } } - -// } -// else { -// error("pTxBuff null \n\r"); -// } } diff --git a/user/uart_buffer.h b/user/uart_buffer.h index 6b9b362..3f91d39 100644 --- a/user/uart_buffer.h +++ b/user/uart_buffer.h @@ -7,6 +7,9 @@ #include +#define UART_TX_BUFFER_SIZE 1000 //Ring buffer length of tx buffer +#define UART_RX_BUFFER_SIZE 600 //Ring buffer length of rx buffer + // the init func void UART_AllocBuffers(void); @@ -14,7 +17,7 @@ void UART_AllocBuffers(void); uint16 UART_ReadAsync(char *pdata, uint16 data_len); // write to tx buffer -void UART_SendAsync(const char *pdata, int16_t data_len); +void UART_SendAsync(const char *pdata, int data_len); //move data from uart fifo to rx buffer void UART_RxFifoCollect(void); @@ -22,6 +25,7 @@ void UART_RxFifoCollect(void); void UART_DispatchFromTxBuffer(uint8 uart_no); u16 UART_AsyncRxCount(void); +u16 UART_AsyncTxCount(void); u16 UART_AsyncTxGetEmptySpace(void); diff --git a/user/uart_handler.c b/user/uart_handler.c index 1f96d2f..66a2f4a 100755 --- a/user/uart_handler.c +++ b/user/uart_handler.c @@ -24,8 +24,8 @@ static void uart_recvTask(os_event_t *events); static void uart_processTask(os_event_t *events); // Those heavily affect the byte loss ratio -#define PROCESS_CHUNK_LEN 1 -#define FIFO_FULL_THRES 32 +#define PROCESS_CHUNK_LEN 10 +#define RX_FIFO_FULL_THRES 40 #define uart_recvTaskPrio 1 #define uart_recvTaskQueueLen 25 @@ -78,8 +78,8 @@ void ICACHE_FLASH_ATTR UART_SetupAsyncReceiver(void) ETS_UART_INTR_ATTACH((void *)uart0_rx_intr_handler, &(UartDev.rcv_buff)); // the buf will be used as an arg // fifo threshold config (max: UART_RXFIFO_FULL_THRHD = 127) - uint32_t conf = ((FIFO_FULL_THRES & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S); - conf |= ((0x10 & UART_TXFIFO_EMPTY_THRHD) << UART_TXFIFO_EMPTY_THRHD_S); + uint32_t conf = ((RX_FIFO_FULL_THRES & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S); + conf |= ((0x05 & UART_TXFIFO_EMPTY_THRHD) << UART_TXFIFO_EMPTY_THRHD_S); // timeout config conf |= ((0x06 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S); // timeout threshold conf |= UART_RX_TOUT_EN; // enable timeout diff --git a/user/user_main.c b/user/user_main.c index fd735b5..b8b38f5 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -30,6 +30,7 @@ #include "persist.h" #include "ansi_parser.h" #include "ascii.h" +#include "uart_buffer.h" #ifdef ESPFS_POS CgiUploadFlashDef uploadParams={ @@ -51,6 +52,7 @@ CgiUploadFlashDef uploadParams={ #define INCLUDE_FLASH_FNS #endif +#define HEAP_TIMER_MS 1000 /** Periodically show heap usage */ static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) { @@ -60,18 +62,24 @@ static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) int heap = system_get_free_heap_size(); int diff = (heap-last); + int rxc = UART_AsyncRxCount(); + int txc = UART_AsyncTxCount(); + + int rxp = ((rxc*10000) / UART_RX_BUFFER_SIZE)/100; + int txp = ((txc*10000) / UART_TX_BUFFER_SIZE)/100; + const char *cc = "+"; if (diff<0) cc = ""; if (diff == 0) { if (cnt == 5) { // only every 5 secs if no change - dbg("FH: %d", heap); + dbg("Rx: %2d%c, Tx: %2d%c, Hp: %d", rxp, '%', txp, '%', heap); cnt = 0; } } else { // report change - dbg("FH: %d (%s%d)", heap, cc, diff); + dbg("Rx: %2d%c, Tx: %2d%c, Hp: %d (%s%d)", rxp, '%', txp, '%', heap, cc, diff); cnt = 0; } @@ -101,7 +109,8 @@ void ICACHE_FLASH_ATTR user_init(void) banner_info("Firmware (c) Ondrej Hruska, 2017"); banner_info(TERMINAL_GITHUB_REPO); banner_info(""); - banner_info("Version "FIRMWARE_VERSION", built " __DATE__ " at " __TIME__ " " __TIMEZONE__); + banner_info("Version "FIRMWARE_VERSION","); + banner_info("built " __DATE__ " at " __TIME__ " " __TIMEZONE__); printf("\r\n"); ioInit(); @@ -116,7 +125,7 @@ void ICACHE_FLASH_ATTR user_init(void) #if DEBUG_HEAP // Heap use timer & blink - TIMER_START(&prHeapTimer, prHeapTimerCb, 1000, 1); + TIMER_START(&prHeapTimer, prHeapTimerCb, HEAP_TIMER_MS, 1); #endif // do later (some functions do not work if called from user_init) @@ -130,6 +139,7 @@ static void ICACHE_FLASH_ATTR user_start(void *unused) captdnsInit(); httpdInit(routes, 80); + httpdSetName("ESPTerm " FIRMWARE_VERSION); ansi_parser_inhibit = false; diff --git a/user/version.h b/user/version.h index 4d20ae7..ac47858 100644 --- a/user/version.h +++ b/user/version.h @@ -5,11 +5,14 @@ #ifndef ESP_VT100_FIRMWARE_VERSION_H #define ESP_VT100_FIRMWARE_VERSION_H +#include "helpers.h" + #define FW_V_MAJOR 2 #define FW_V_MINOR 1 #define FW_V_PATCH 0 +#define FW_CODENAME "Anthill" // 2.1.0 -#define FIRMWARE_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) +#define FIRMWARE_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) " \"" FW_CODENAME "\"" #define FIRMWARE_VERSION_NUM (FW_V_MAJOR*1000 + FW_V_MINOR*10 + FW_V_PATCH) // this is used in ID queries #define TERMINAL_GITHUB_REPO "https://github.com/espterm/espterm-firmware" #define TERMINAL_GITHUB_REPO_FRONT "https://github.com/espterm/espterm-front-end"