diff --git a/.gitignore b/.gitignore index c013837..3334351 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ cmake-build-debug/ .sass-cache *.map +.gitignore diff --git a/CMakeLists.txt b/CMakeLists.txt index 12dea35..8e65181 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,8 @@ set(SOURCE_FILES libesphttpd/util/cgiwebsocket.c libesphttpd/util/cgiflash.c libesphttpd/util/captdns.c + libesphttpd/esphttpclient/httpclient.c + libesphttpd/include/httpclient.h esp_iot_sdk_v1.5.2/include/user_interface.h esp_iot_sdk_v1.5.2/include/upgrade.h @@ -85,9 +87,9 @@ set(SOURCE_FILES esp_iot_sdk_v1.5.2/include/json/jsontree.h esp_iot_sdk_v1.5.2/include/json/jsonparse.h esp_iot_sdk_v1.5.2/include/json/json.h - include/user_config.h include/ets_sys_extra.h + include/helpers.h user/io.c user/io.h user/cgi_wifi.c @@ -121,7 +123,6 @@ set(SOURCE_FILES user/wifimgr.h user/persist.c user/persist.h - include/helpers.h user/syscfg.c user/syscfg.h user/ascii.h @@ -139,9 +140,18 @@ set(SOURCE_FILES user/apars_osc.c user/apars_osc.h user/apars_dcs.c - user/apars_dcs.h user/uart_buffer.c user/uart_buffer.h user/jstring.c user/jstring.h user/character_sets.h user/utf8.h user/utf8.c user/cgi_logging.h) + user/apars_dcs.h + user/uart_buffer.c + user/uart_buffer.h + user/jstring.c + user/jstring.h + user/character_sets.h + user/utf8.h + user/utf8.c + user/cgi_logging.h) include_directories(include) +include_directories(libesphttpd/esphttpclient) include_directories(user) include_directories(libesphttpd/include) include_directories(libesphttpd/espfs) @@ -160,13 +170,34 @@ add_definitions( -DICACHE_FLASH_ATTR= -DICACHE_RODATA_ATTR= -DFLAG_GZIP=2 - -DADMIN_PASSWORD="asdf" + -DESP_LANG="en" + -DGIT_HASH_BACKEND="asdf" -DGIT_HASH_FRONTEND="asdf" -DGIT_HASH="blabla" + + -D__TIMEZONE__="UTC" + -DESPFS_HEATSHRINK + + -DDEBUG_ANSI=1 + -DDEBUG_ANSI_NOIMPL=1 + -DDEBUG_CAPTDNS=1 + -DDEBUG_CGI=0 + -DDEBUG_ESPFS=1 -DDEBUG_HEAP=1 + -DDEBUG_HTTP=1 + -DDEBUG_HTTPC=1 + -DDEBUG_INPUT=1 -DDEBUG_MALLOC=1 - -D__TIMEZONE__="UTC" - -DESPFS_HEATSHRINK) + -DDEBUG_PERSIST=1 + -DDEBUG_ROUTER=1 + -DDEBUG_UTFCACHE=1 + -DDEBUG_WIFI=1 + -DDEBUG_WS=1 + -DDEBUG_ROUTER=1 +) +# all the debug keys should be listed here ^ so clion thinks they are used +# and doesn't mess up the formatting and inspections + add_executable(ESPTerm ${SOURCE_FILES}) diff --git a/Makefile b/Makefile index a815b7a..474bb3a 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,10 @@ LIBS = c gcc hal phy pp net80211 wpa main lwip crypto #Add in esphttpd lib LIBS += esphttpd +ifndef ESP_LANG +ESP_LANG = en +endif + # compiler flags using during compilation of source files -ggdb CFLAGS = -Os -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \ @@ -69,7 +73,7 @@ CFLAGS = -Os -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inli CFLAGS += -DGIT_HASH_BACKEND='"$(shell git rev-parse --short HEAD)"' CFLAGS += -DGIT_HASH_FRONTEND='"$(shell cd front-end && git rev-parse --short HEAD)"' -CFLAGS += -D__TIMEZONE__='"$(shell date +%Z)"' +CFLAGS += -D__TIMEZONE__='"$(shell date +%Z)"' -DESP_LANG='"$(ESP_LANG)"' ifdef GLOBAL_CFLAGS CFLAGS += $(GLOBAL_CFLAGS) @@ -194,6 +198,10 @@ endef web: $(Q) ./build_web.sh +updweb: + $(Q) cd front-end && git pull + $(Q) ./build_web.sh + parser: $(Q) ./build_parser.sh @@ -206,13 +214,16 @@ espmac: all: checkdirs $(Q) make actual_all -j4 -B +release: + $(Q) ./release.sh + actual_all: parser $(TARGET_OUT) $(FW_BASE) 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 b/esphttpdconfig.mk deleted file mode 100644 index 617b057..0000000 --- a/esphttpdconfig.mk +++ /dev/null @@ -1,61 +0,0 @@ -# --------------- esphttpd config options --------------- - -# If GZIP_COMPRESSION is set to "yes" then the static css, js, and html files will be compressed with gzip before added to the espfs image -# and will be served with gzip Content-Encoding header. -# This could speed up the downloading of these files, but might break compatibility with older web browsers not supporting gzip encoding -# because Accept-Encoding is simply ignored. Enable this option if you have large static files to serve (for e.g. JQuery, Twitter bootstrap) -# By default only js, css and html files are compressed. -# If you have text based static files with different extensions what you want to serve compressed then you will need to add the extension to the following places: -# - Add the extension to this Makefile at the webpages.espfs target to the find command -# - Add the extension to the gzippedFileTypes array in the user/httpd.c file -# -# Adding JPG or PNG files (and any other compressed formats) is not recommended, because GZIP compression does not works effectively on compressed files. - -#Static gzipping is disabled by default. -GZIP_COMPRESSION = yes - -# If COMPRESS_W_YUI is set to "yes" then the static css and js files will be compressed with yui-compressor -# This option works only when GZIP_COMPRESSION is set to "yes" -# http://yui.github.io/yuicompressor/ -#Disabled by default. -COMPRESS_W_YUI = no - -YUI-COMPRESSOR = /usr/bin/yui-compressor - -#If USE_HEATSHRINK is set to "yes" then the espfs files will be compressed with Heatshrink and decompressed -#on the fly while reading the file. Because the decompression is done in the esp8266, it does not require -#any support in the browser. -USE_HEATSHRINK = yes - -USE_OPENSDK = yes - -# this ugly trick is needed to allow a relative path -SDK_BASE=$(dir $(lastword $(MAKEFILE_LIST)))/esp_iot_sdk_v1.5.2/ - -# combined / separate / ota -OUTPUT_TYPE = combined - -# SPI flash size, in K -ESP_SPI_FLASH_SIZE_K = 1024 - -GLOBAL_CFLAGS = \ - -DDEBUG_ROUTER=0 \ - -DDEBUG_CAPTDNS=0 \ - -DDEBUG_HTTP=0 \ - -DDEBUG_ESPFS=0 \ - -DDEBUG_PERSIST=1 \ - -DDEBUG_UTFCACHE=0 \ - -DDEBUG_CGI=0 \ - -DDEBUG_WIFI=0 \ - -DDEBUG_WS=0 \ - -DDEBUG_ANSI=1 \ - -DDEBUG_ANSI_NOIMPL=1 \ - -DDEBUG_INPUT=0 \ - -DDEBUG_HEAP=1 \ - -DDEBUG_MALLOC=0 \ - -DHTTPD_MAX_BACKLOG_SIZE=8192 \ - -DHTTPD_MAX_HEAD_LEN=1024 \ - -DHTTPD_MAX_POST_LEN=512 \ - -DDEBUG_LOGBUF_SIZE=1024 \ - -mforce-l32 \ - -DUSE_OPTIMIZE_PRINTF=1 diff --git a/esphttpdconfig.mk.example b/esphttpdconfig.mk.example index 617b057..d95d005 100644 --- a/esphttpdconfig.mk.example +++ b/esphttpdconfig.mk.example @@ -39,9 +39,12 @@ 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 \ + -DDEBUG_HTTPC=0 \ -DDEBUG_ESPFS=0 \ -DDEBUG_PERSIST=1 \ -DDEBUG_UTFCACHE=0 \ diff --git a/front-end b/front-end index f5dd70a..0114846 160000 --- a/front-end +++ b/front-end @@ -1 +1 @@ -Subproject commit f5dd70a6f32ac36f0820badc170832f27242a09d +Subproject commit 01148465b71b640892a50ec825205ae581d6dca7 diff --git a/get_version.c b/get_version.c new file mode 100644 index 0000000..0809451 --- /dev/null +++ b/get_version.c @@ -0,0 +1,14 @@ +// +// Created by MightyPork on 2017/10/03. +// +// helper for building release packages +// +// Run with `tcc -run` +// + +#include "user/version.h" +#include + +void main() { + printf(FW_VERSION); +} diff --git a/include/user_config.h b/include/user_config.h index 8b13789..e69de29 100644 --- a/include/user_config.h +++ b/include/user_config.h @@ -1 +0,0 @@ - diff --git a/libesphttpd b/libesphttpd index 3479ab3..e4ecf07 160000 --- a/libesphttpd +++ b/libesphttpd @@ -1 +1 @@ -Subproject commit 3479ab3efcb4581669370cde6a607f936ff5515a +Subproject commit e4ecf0724e36c828be5222eddce58a6a5cd2386f diff --git a/rel-tpl/README.txt b/rel-tpl/README.txt new file mode 100644 index 0000000..dc039ea --- /dev/null +++ b/rel-tpl/README.txt @@ -0,0 +1,21 @@ +This is a release archive of ESPTerm, +the VT100 terminal emulator for ESP8266. + +-------------------------------------------- +Version: %VERS% +Locale : %LANG% +Built : %DATETIME% +-------------------------------------------- + +Source repository: + https://github.com/espterm/espterm-firmware + +Report any bugs to our bug-tracker at + https://github.com/espterm/espterm-firmware/issues +or send them to out mailing list + espterm-dev@googlegroups.com + +On-line demo is available at + https://espterm.github.io/term.html + +[EOF] diff --git a/rel-tpl/flash.sh b/rel-tpl/flash.sh new file mode 100644 index 0000000..4a92a28 --- /dev/null +++ b/rel-tpl/flash.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# The parameters ESPTOOL, ESPPORT and ESPBAUD can be customized +# - export your preferred values in .bashrc + +echo -e "\e[32;1mFlashing ESPTerm %VERS% (%LANG%)\e[0m" + +if [ -z ${ESPTOOL} ]; then + ESPTOOL='esptool' + which ${ESPTOOL} &>/dev/null + if [ $? -ne 0 ]; then + ESPTOOL='esptool.py' + which ${ESPTOOL} &>/dev/null + if [ $? -ne 0 ]; then + echo -e '\e[31;1mesptool not found!\e[0m' + exit 1 + fi + fi +fi + +[ -z ESPPORT ] && ESPPORT=/dev/ttyUSB0 +[ -z ESPBAUD ] && ESPBAUD=460800 + +set -x +${ESPTOOL} --port ${ESPPORT} --baud ${ESPBAUD} \ + write_flash 0x00000 '%FILE0%' 0x40000 '%FILE4%' diff --git a/release/.gitignore b/release/.gitignore new file mode 100644 index 0000000..3814986 --- /dev/null +++ b/release/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!flash-tpl.sh diff --git a/ship.sh b/ship.sh new file mode 100755 index 0000000..ae6e87f --- /dev/null +++ b/ship.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +if [ -z "$1" ]; then + vers=$(tcc -run get_version.c) +else + vers=$1 +fi + +git pull + +echo -n -e "\e[1;36mBuilding packages for version $vers\e[0m" + +cd front-end +git pull +cd .. + +function buildlang() { + lang=$1 + + echo -e "\n\e[33;1m------ Building \"${lang}\" package ------\e[0m\n" + + make clean + ESP_LANG=${lang} make web + ESP_LANG=${lang} make actual_all -B -j4 + + cd release + + destdir="$vers-$lang" + file0=${vers}-0x00000-${lang}.bin + file4=${vers}-0x40000-${lang}.bin + [ -e ${destdir} ] && rm -r ${destdir} + mkdir ${destdir} + cp ../firmware/0x00000.bin ${destdir}/${file0} + cp ../firmware/0x40000.bin ${destdir}/${file4} + + flashsh=${destdir}/flash.sh + cp ../rel-tpl/flash.sh ${flashsh} + sed -i s/%FILE0%/${file0}/ ${flashsh} + sed -i s/%FILE4%/${file4}/ ${flashsh} + sed -i s/%VERS%/${vers}/ ${flashsh} + sed -i s/%LANG%/${lang}/ ${flashsh} + chmod +x ${flashsh} + + readmefil=${destdir}/README.txt + cp ../rel-tpl/README.txt ${readmefil} + sed -i s/%VERS%/${vers}/ ${readmefil} + sed -i s/%LANG%/${lang}/ ${readmefil} + dt=$(LC_TIME=en_US.UTF-8 date '+%c') + sed -i "s#%DATETIME%#${dt}#" ${readmefil} + unix2dos ${readmefil} + + cd ${destdir} + sha256sum ${file0} ${file4} README.txt flash.sh > checksums.txt + cd .. + + targetfile=espterm-${vers}-${lang}.zip + [[ -e ${targetfile}.zip ]] && rm ${targetfile}.zip + pwd + zip -9 ${targetfile} ${destdir}/* + cd .. +} + +if [ -z "$ESP_LANG" ]; then + buildlang cs + buildlang en + buildlang de +else + buildlang ${ESP_LANG} +fi diff --git a/user/ansi_parser.c b/user/ansi_parser.c index 7b01ded..a6bbe79 100644 --- a/user/ansi_parser.c +++ b/user/ansi_parser.c @@ -5,10 +5,11 @@ #include "ansi_parser_callbacks.h" #include "ascii.h" #include "apars_logging.h" +#include "screen.h" /* Ragel constants block */ -/* #line 12 "user/ansi_parser.c" */ +/* #line 13 "user/ansi_parser.c" */ static const char _ansi_actions[] ESP_CONST_DATA = { 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, @@ -32,18 +33,15 @@ static const int ansi_en_charsetcmd_body = 10; static const int ansi_en_main = 1; -/* #line 11 "user/ansi_parser.rl" */ +/* #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; // public volatile u32 ansi_parser_char_cnt = 0; +volatile bool ansi_parser_inhibit = 0; void ICACHE_FLASH_ATTR ansi_parser_reset(void) { @@ -108,18 +106,25 @@ ansi_parser(char newchar) static char string_buffer[ANSI_STR_LEN]; static int str_ni; + if (ansi_parser_inhibit) return; + // This is used to detect timeout delay (time since last rx char) ansi_parser_char_cnt++; + if (termconf->ascii_debug) { + apars_handle_plainchar(newchar); + return; + } + // Init Ragel on the first run if (cs == -1) { -/* #line 118 "user/ansi_parser.c" */ +/* #line 123 "user/ansi_parser.c" */ { cs = ansi_start; } -/* #line 92 "user/ansi_parser.rl" */ +/* #line 97 "user/ansi_parser.rl" */ #if DEBUG_ANSI memset(history, 0, sizeof(history)); @@ -133,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) { @@ -174,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; @@ -199,7 +205,7 @@ ansi_parser(char newchar) // The parser -/* #line 203 "user/ansi_parser.c" */ +/* #line 209 "user/ansi_parser.c" */ { const char *_acts; unsigned int _nacts; @@ -389,7 +395,7 @@ execFuncs: while ( _nacts-- > 0 ) { switch ( *_acts++ ) { case 0: -/* #line 179 "user/ansi_parser.rl" */ +/* #line 185 "user/ansi_parser.rl" */ { ansi_warn("Parser error."); apars_show_context(); @@ -398,7 +404,7 @@ execFuncs: } break; case 1: -/* #line 188 "user/ansi_parser.rl" */ +/* #line 194 "user/ansi_parser.rl" */ { if ((*p) != 0) { apars_handle_plainchar((*p)); @@ -406,7 +412,7 @@ execFuncs: } break; case 2: -/* #line 196 "user/ansi_parser.rl" */ +/* #line 202 "user/ansi_parser.rl" */ { // Reset the CSI builder leadchar = NUL; @@ -423,13 +429,13 @@ execFuncs: } break; case 3: -/* #line 211 "user/ansi_parser.rl" */ +/* #line 217 "user/ansi_parser.rl" */ { leadchar = (*p); } break; case 4: -/* #line 215 "user/ansi_parser.rl" */ +/* #line 221 "user/ansi_parser.rl" */ { if (arg_cnt == 0) arg_cnt = 1; // x10 + digit @@ -439,7 +445,7 @@ execFuncs: } break; case 5: -/* #line 223 "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++; @@ -447,20 +453,20 @@ execFuncs: } break; case 6: -/* #line 229 "user/ansi_parser.rl" */ +/* #line 235 "user/ansi_parser.rl" */ { interchar = (*p); } break; case 7: -/* #line 233 "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 245 "user/ansi_parser.rl" */ +/* #line 251 "user/ansi_parser.rl" */ { leadchar = (*p); str_ni = 0; @@ -470,13 +476,13 @@ execFuncs: } break; case 9: -/* #line 253 "user/ansi_parser.rl" */ +/* #line 259 "user/ansi_parser.rl" */ { string_buffer[str_ni++] = (*p); } break; case 10: -/* #line 257 "user/ansi_parser.rl" */ +/* #line 263 "user/ansi_parser.rl" */ { inside_string = false; string_buffer[str_ni++] = '\0'; @@ -485,41 +491,41 @@ execFuncs: } break; case 11: -/* #line 270 "user/ansi_parser.rl" */ +/* #line 276 "user/ansi_parser.rl" */ { apars_handle_hash_cmd((*p)); {cs = 1;goto _again;} } break; case 12: -/* #line 275 "user/ansi_parser.rl" */ +/* #line 281 "user/ansi_parser.rl" */ { apars_handle_short_cmd((*p)); {cs = 1;goto _again;} } break; case 13: -/* #line 280 "user/ansi_parser.rl" */ +/* #line 286 "user/ansi_parser.rl" */ { apars_handle_space_cmd((*p)); {cs = 1;goto _again;} } break; case 14: -/* #line 287 "user/ansi_parser.rl" */ +/* #line 293 "user/ansi_parser.rl" */ { leadchar = (*p); {cs = 10;goto _again;} } break; case 15: -/* #line 292 "user/ansi_parser.rl" */ +/* #line 298 "user/ansi_parser.rl" */ { apars_handle_chs_designate(leadchar, (*p)); {cs = 1;goto _again;} } break; -/* #line 523 "user/ansi_parser.c" */ +/* #line 529 "user/ansi_parser.c" */ } } goto _again; @@ -537,7 +543,7 @@ _again: while ( __nacts-- > 0 ) { switch ( *__acts++ ) { case 0: -/* #line 179 "user/ansi_parser.rl" */ +/* #line 185 "user/ansi_parser.rl" */ { ansi_warn("Parser error."); apars_show_context(); @@ -547,7 +553,7 @@ _again: goto _again;} } break; -/* #line 551 "user/ansi_parser.c" */ +/* #line 557 "user/ansi_parser.c" */ } } } @@ -555,6 +561,6 @@ goto _again;} _out: {} } -/* #line 315 "user/ansi_parser.rl" */ +/* #line 321 "user/ansi_parser.rl" */ } diff --git a/user/ansi_parser.h b/user/ansi_parser.h index 8e813bd..1aa9528 100644 --- a/user/ansi_parser.h +++ b/user/ansi_parser.h @@ -3,6 +3,11 @@ #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); extern volatile u32 ansi_parser_char_cnt; diff --git a/user/ansi_parser.rl b/user/ansi_parser.rl index 2f74623..67b3ef0 100644 --- a/user/ansi_parser.rl +++ b/user/ansi_parser.rl @@ -3,6 +3,7 @@ #include "ansi_parser_callbacks.h" #include "ascii.h" #include "apars_logging.h" +#include "screen.h" /* Ragel constants block */ %%{ @@ -10,15 +11,12 @@ 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; // public volatile u32 ansi_parser_char_cnt = 0; +volatile bool ansi_parser_inhibit = 0; void ICACHE_FLASH_ATTR ansi_parser_reset(void) { @@ -83,9 +81,16 @@ ansi_parser(char newchar) static char string_buffer[ANSI_STR_LEN]; static int str_ni; + if (ansi_parser_inhibit) return; + // This is used to detect timeout delay (time since last rx char) ansi_parser_char_cnt++; + if (termconf->ascii_debug) { + apars_handle_plainchar(newchar); + return; + } + // Init Ragel on the first run if (cs == -1) { %% write init; @@ -102,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) { @@ -143,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..d17187f 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 "wifimgr.h" + +volatile bool enquiry_suppressed = false; +ETSTimer enqTimer; +void ICACHE_FLASH_ATTR enqTimerCb(void *unused) +{ + enquiry_suppressed = false; +} /** * Send a response to UART0 @@ -19,6 +27,7 @@ void ICACHE_FLASH_ATTR apars_respond(const char *str) { + // Using the Tx buffer causes issues with large data (eg. from http requests) UART_SendAsync(str, -1); } @@ -37,8 +46,32 @@ apars_handle_bel(void) void ICACHE_FLASH_ATTR apars_handle_enq(void) { + if (enquiry_suppressed) return; + + u8 mac[6]; + wifi_get_macaddr(SOFTAP_IF, mac); + + char buf100[100]; + char *buf = buf100; + buf += sprintf(buf, "\x1bX"); + buf += sprintf(buf, "ESPTerm "VERSION_STRING" "); + buf += sprintf(buf, "#"GIT_HASH_BACKEND"+"GIT_HASH_FRONTEND" "); + buf += sprintf(buf, "id=%02X%02X%02X ", mac[3], mac[4], mac[5]); + int x = getStaIpAsString(buf); + if (x) buf += x; + else buf--; // remove the trailing space + buf += sprintf(buf, "\x1b\\"); + + (void)buf; + // version encased in SOS and ST - apars_respond("\x1bXESPTerm " FIRMWARE_VERSION "\x1b\\"); + apars_respond(buf100); + + // 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_csi.c b/user/apars_csi.c index ed18ac9..ca1ff26 100644 --- a/user/apars_csi.c +++ b/user/apars_csi.c @@ -709,6 +709,8 @@ do_csi_set_private_option(CSI_Data *opts) mouse_tracking.mode, mouse_tracking.encoding, mouse_tracking.focus_tracking); + + screen_notifyChange(TOPIC_CHANGE_SCREEN_OPTS); } else if (n == 12) { screen_cursor_blink(yn); @@ -757,11 +759,11 @@ do_csi_set_private_option(CSI_Data *opts) } else if (n == 800) { // ESPTerm: Toggle display of buttons termconf_live.show_buttons = yn; - screen_notifyChange(CHANGE_CONTENT); // this info is included in the screen preamble + screen_notifyChange(TOPIC_CHANGE_SCREEN_OPTS); // this info is included in the screen preamble } else if (n == 801) { // ESPTerm: Toggle display of config links termconf_live.show_config_links = yn; - screen_notifyChange(CHANGE_CONTENT); // this info is included in the screen preamble + screen_notifyChange(TOPIC_CHANGE_SCREEN_OPTS); // this info is included in the screen preamble } else { ansi_noimpl("?OPTION %d", n); diff --git a/user/apars_dcs.c b/user/apars_dcs.c index 5238582..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); @@ -47,7 +47,9 @@ apars_handle_dcs(const char *buffer) } else if (buffer[2] == 'r') { // DECSTBM - Query scrolling region - sprintf(buf, "\033P1$r%d;%dr\033\\", 1, termconf_live.height); // 1-80 TODO real extent of scrolling region + int v0, v1; + screen_region_get(&v0, &v1); + sprintf(buf, "\033P1$r%d;%dr\033\\", v0+1, v1+1); apars_respond(buf); } else if (buffer[2] == 's') { @@ -64,7 +66,7 @@ apars_handle_dcs(const char *buffer) } else if (strneq(buffer+2, " q", 2)) { // DECSCUSR - Query cursor style - sprintf(buf, "\033P1$r%d q\033\\", 1); + sprintf(buf, "\033P1$r%d q\033\\", termconf_live.cursor_shape); /* Ps = 0 -> blinking block. Ps = 1 -> blinking block (default). 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 64ae3ab..7fb35c5 100644 --- a/user/apars_short.c +++ b/user/apars_short.c @@ -42,9 +42,11 @@ // #include +#include #include "apars_short.h" #include "apars_logging.h" #include "screen.h" +#include "ansi_parser_callbacks.h" // ----- Character Set --- @@ -103,6 +105,11 @@ void ICACHE_FLASH_ATTR apars_handle_hash_cmd(char c) screen_fill_with_E(); break; + // development codes - do not use! + case '7': + http_get("http://wtfismyip.com/text", NULL, http_callback_example); + break; + default: ansi_noimpl("ESC # %c", c); } 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/apars_utf8.c b/user/apars_utf8.c index b3f502c..a8db968 100644 --- a/user/apars_utf8.c +++ b/user/apars_utf8.c @@ -8,9 +8,13 @@ #include "apars_utf8.h" #include "apars_logging.h" #include "screen.h" +#include "uart_driver.h" +#include "ansi_parser_callbacks.h" +#include "ansi_parser.h" +#include "ascii.h" -static char utf_collect[4]; -static int utf_i = 0; +static u8 bytes[4]; +static int utf_len = 0; static int utf_j = 0; /** @@ -20,11 +24,121 @@ static int utf_j = 0; void ICACHE_FLASH_ATTR apars_reset_utf8buffer(void) { - utf_i = 0; + utf_len = 0; utf_j = 0; - memset(utf_collect, 0, 4); + memset(bytes, 0, 4); } +// Code Points First Byte Second Byte Third Byte Fourth Byte +// U+0000 - U+007F 00 - 7F +// U+0080 - U+07FF C2 - DF 80 - BF +// U+0800 - U+0FFF E0 *A0 - BF 80 - BF +// U+1000 - U+CFFF E1 - EC 80 - BF 80 - BF +// U+D000 - U+D7FF ED 80 - *9F 80 - BF +// U+E000 - U+FFFF EE - EF 80 - BF 80 - BF +// U+10000 - U+3FFFF F0 *90 - BF 80 - BF 80 - BF +// U+40000 - U+FFFFF F1 - F3 80 - BF 80 - BF 80 - BF +// U+100000 - U+10FFFF F4 80 - *8F 80 - BF 80 - BF + +static void ICACHE_FLASH_ATTR screen_print_ascii(const char *str) +{ + char gly[2]; + gly[1] = 0; + for(int j = 0;str[j]!=0;j++) { + gly[0] = str[j]; + screen_putchar(gly); + } +} + +static void ICACHE_FLASH_ATTR hdump_spaces_eol(int needed) +{ + if (needed == 0) needed = 5; + int x, y; + screen_cursor_get(&y, &x); + if (x > termconf_live.width - needed) { + screen_clear_in_line(CLEAR_FROM_CURSOR); + screen_putchar("\n"); + screen_putchar("\r"); + } +} + + +static void ICACHE_FLASH_ATTR hdump_good(const char *ch) +{ + char buf[10]; + hdump_spaces_eol(6); + + screen_set_fg(7); + screen_set_bg(0); + if(ch[0]<32) { + screen_set_fg(7); + screen_set_bg(2); + switch (ch[0]) { + case NUL: screen_print_ascii("NUL"); break; + case SOH: screen_print_ascii("SOH"); break; + case STX: screen_print_ascii("STX"); break; + case ETX: screen_print_ascii("ETX"); break; + case EOT: screen_print_ascii("EOT"); break; + case ENQ: screen_print_ascii("ENQ"); break; + case ACK: screen_print_ascii("ACK"); break; + case BEL: screen_print_ascii("BEL"); break; + case BS: screen_print_ascii("BS"); break; + case TAB: screen_print_ascii("TAB"); break; + case LF: screen_print_ascii("LF"); break; + case VT: screen_print_ascii("VT"); break; + case FF: screen_print_ascii("FF"); break; + case CR: screen_print_ascii("CR"); break; + case SO: screen_print_ascii("SO"); break; + case SI: screen_print_ascii("SI"); break; + case DLE: screen_print_ascii("DLE"); break; + case DC1: screen_print_ascii("DC1"); break; + case DC2: screen_print_ascii("DC2"); break; + case DC3: screen_print_ascii("DC3"); break; + case DC4: screen_print_ascii("DC4"); break; + case NAK: screen_print_ascii("NAK"); break; + case SYN: screen_print_ascii("SYN"); break; + case ETB: screen_print_ascii("ETB"); break; + case CAN: screen_print_ascii("CAN"); break; + case EM: screen_print_ascii("EM"); break; + case SUB: screen_print_ascii("SUB"); break; + case ESC: screen_print_ascii("ESC"); break; + case FS: screen_print_ascii("FS"); break; + case GS: screen_print_ascii("GS"); break; + case RS: screen_print_ascii("RS"); break; + case US: screen_print_ascii("US"); break; + case SP: screen_print_ascii("SP"); break; + case DEL: screen_print_ascii("DEL"); break; + default: + sprintf(buf, "%02Xh", ch[0]); + screen_print_ascii(buf); + } + } else { + screen_putchar(ch); + } + + screen_set_default_bg(); + screen_set_default_fg(); + screen_print_ascii(" "); +} + +static void ICACHE_FLASH_ATTR hdump_bad(const char *ch, int len) +{ + char buf[10]; + hdump_spaces_eol(len*5); + + screen_set_fg(7); + screen_set_bg(1); + for (int i=0;i= 245) { - // forbidden codes (would be an overlong sequence) + if (uc == 0xC0 || uc == 0xC1 || uc > 0xF4) { + // forbidden start codes goto fail; } - if ((c & 0xE0) == 0xC0) { - utf_i = 2; + if ((uc & 0xE0) == 0xC0) { + utf_len = 2; } - else if ((c & 0xF0) == 0xE0) { - utf_i = 3; + else if ((uc & 0xF0) == 0xE0) { + utf_len = 3; } - else if ((c & 0xF8) == 0xF0) { - utf_i = 4; + else if ((uc & 0xF8) == 0xF0) { + utf_len = 4; } else { // chars over 127 that don't start unicode sequences goto fail; } - - utf_collect[0] = c; - utf_j = 1; } else { - if ((c & 0xC0) != 0x80) { + if ((uc & 0xC0) != 0x80) { + bytes[utf_j++] = uc; goto fail; } else { - utf_collect[utf_j++] = c; - if (utf_j >= utf_i) { - screen_putchar(utf_collect); + bytes[utf_j++] = uc; + if (utf_j >= utf_len) { + // check for bad sequences - overlong or some other problem + if (bytes[0] == 0xF4 && bytes[1] > 0x8F) goto fail; + if (bytes[0] == 0xF0 && bytes[1] < 0x90) goto fail; + if (bytes[0] == 0xED && bytes[1] > 0x9F) goto fail; + if (bytes[0] == 0xE0 && bytes[1] < 0xA0) goto fail; + + // trap for surrogates - those break javascript + if (bytes[0] == 0xED && bytes[1] >= 0xA0 && bytes[1] <= 0xBF) goto fail; + + if (termconf_live.ascii_debug) { + hdump_good((const char *) bytes); + } else { + screen_putchar((const char *) bytes); + } apars_reset_utf8buffer(); } } } } else { - utf_collect[0] = c; - utf_collect[1] = 0; // just to make sure it's closed... - screen_putchar(utf_collect); + bytes[0] = uc; + bytes[1] = 0; // just to make sure it's closed... + if (termconf_live.ascii_debug) { + hdump_good((const char *) bytes); + } else { + screen_putchar((const char *) bytes); + } + apars_reset_utf8buffer(); } return; - fail: - ansi_warn("Bad UTF-8: %0Xh", c); +fail: + if (termconf_live.ascii_debug) { + hdump_bad((const char *) bytes, utf_j); + } else { + screen_putchar("\xEF\xBF\xBD"); + } + //ansi_warn("BAD UTF8!"); + //apars_show_context(); apars_reset_utf8buffer(); } 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..1c28235 --- /dev/null +++ b/user/cgi_d2d.c @@ -0,0 +1,324 @@ +// +// Created by MightyPork on 2017/10/01. +// + +#include +#include "cgi_d2d.h" +#include "version.h" +#include "ansi_parser_callbacks.h" +#include "api.h" +#include "uart_driver.h" +#include +#include + +#define D2D_TIMEOUT_MS 2000 + +#define D2D_HEADERS \ + "User-Agent: ESPTerm "VERSION_STRING" 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; + +// NOTE! We bypass the async buffer here - used for user input and +// responses to queries {apars_respond()}. In rare situations this could +// lead to a race condition and mixing two different messages +static inline void ICACHE_FLASH_ATTR sendResponseToUART(const char *str) +{ + UART_WriteString(UART0, str, UART_TIMEOUT_US); +} + +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; + + // 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; + + d2d_info("Rx HTTP response, code %d, len %d, nonce \"%s\"", http_status, len, opts->nonce?opts->nonce:""); + + 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, ";"); + + sendResponseToUART(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 + } + sendResponseToUART(response_headers); + if(opts->want_body) sendResponseToUART("\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; + } + + sendResponseToUART(response_body); + } + + sendResponseToUART("\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_info("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_info("HTTP request"); + d2d_dbg("Method %s", method); + 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("Request sent."); + 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); + sendResponseToUART(buf); + + if (connData->post && connData->post->buff) + sendResponseToUART(connData->post->buff); + else if (connData->getArgs) + sendResponseToUART(connData->getArgs); + + sendResponseToUART("\a"); + + d2d_info("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_main.c b/user/cgi_main.c index da13a41..ccde875 100644 --- a/user/cgi_main.c +++ b/user/cgi_main.c @@ -25,24 +25,12 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token char buff[150]; - if (streq(token, "labels_seq")) { - screenSerializeLabelsToBuffer(buff, 150); - tplSend(connData, buff, -1); - } - else if (streq(token, "theme")) { - sprintf(buff, "%d", termconf->theme); - tplSend(connData, buff, -1); - } - else if (streq(token, "want_all_fn")) { + if (streq(token, "want_all_fn")) { sprintf(buff, "%d", termconf->want_all_fn); tplSend(connData, buff, -1); } - else if (streq(token, "default_fg")) { - sprintf(buff, "%d", termconf->default_fg); - tplSend(connData, buff, -1); - } - else if (streq(token, "default_bg")) { - sprintf(buff, "%d", termconf->default_bg); + else if (streq(token, "debugbar")) { + sprintf(buff, "%d", termconf->debugbar); tplSend(connData, buff, -1); } @@ -56,7 +44,7 @@ tplAbout(HttpdConnData *connData, char *token, void **arg) if (token == NULL) return HTTPD_CGI_DONE; if (streq(token, "vers_fw")) { - tplSend(connData, FIRMWARE_VERSION, -1); + tplSend(connData, VERSION_STRING, -1); } else if (streq(token, "date")) { tplSend(connData, __DATE__, -1); @@ -68,7 +56,6 @@ tplAbout(HttpdConnData *connData, char *token, void **arg) tplSend(connData, httpdGetVersion(), -1); } else if (streq(token, "vers_sdk")) { - //tplSend(connData, STR(ESP_SDK_VERSION), -1); tplSend(connData, system_get_sdk_version(), -1); } else if (streq(token, "hash_backend")) { diff --git a/user/cgi_sockets.c b/user/cgi_sockets.c index 7f4fcfc..565afa5 100644 --- a/user/cgi_sockets.c +++ b/user/cgi_sockets.c @@ -16,20 +16,22 @@ // Must be less than httpd sendbuf #define SOCK_BUF_LEN 2000 +volatile ScreenNotifyTopics pendingBroadcastTopics = 0; + // flags for screen update timeouts volatile bool notify_available = true; volatile bool notify_cooldown = false; +volatile bool notify_scheduled = false; /** True if we sent XOFF to browser to stop uploading, * and we have to tell it we're ready again */ volatile bool browser_wants_xon = false; -static ETSTimer notifyContentTim; -static ETSTimer notifyLabelsTim; +static ETSTimer updateNotifyTim; static ETSTimer notifyCooldownTim; static ETSTimer heartbeatTim; -volatile int active_clients = 0; +volatile int term_active_clients = 0; // we're trying to do a kind of mutex here, without the actual primitives // this might glitch, very rarely. @@ -52,26 +54,22 @@ notifyCooldownTimCb(void *arg) * @param arg */ static void ICACHE_FLASH_ATTR -notifyContentTimCb(void *arg) +updateNotify_do(Websock *ws, ScreenNotifyTopics topics) { - Websock *ws = arg; void *data = NULL; - int max_bl, total_bl; char sock_buff[SOCK_BUF_LEN]; - cgiWebsockMeasureBacklog(URL_WS_UPDATE, &max_bl, &total_bl); - - if (!notify_available || notify_cooldown || (max_bl > 2048)) { // do not send if we have anything significant backlogged - // postpone a little - TIMER_START(¬ifyContentTim, notifyContentTimCb, 4, 0); - inp_dbg("postpone notify content"); - return; - } notify_available = false; for (int i = 0; i < 20; i++) { - httpd_cgi_state cont = screenSerializeToBuffer(sock_buff, SOCK_BUF_LEN, &data); - int flg = 0; + if (! ws) { + // broadcast + topics = pendingBroadcastTopics; + pendingBroadcastTopics = 0; + } + httpd_cgi_state cont = screenSerializeToBuffer(sock_buff, SOCK_BUF_LEN, topics, &data); + + int flg = 0; //WEBSOCK_FLAG_BIN if (cont == HTTPD_CGI_MORE) flg |= WEBSOCK_FLAG_MORE; if (i > 0) flg |= WEBSOCK_FLAG_CONT; if (ws) { @@ -86,56 +84,45 @@ notifyContentTimCb(void *arg) } // cleanup - screenSerializeToBuffer(NULL, SOCK_BUF_LEN, &data); + screenSerializeToBuffer(NULL, 0, 0, &data); - notify_cooldown = true; notify_available = true; - - TIMER_START(¬ifyCooldownTim, notifyCooldownTimCb, termconf->display_cooldown_ms, 0); } /** - * Tell browsers about the new text labels + * Tell browser we have new content * @param arg */ static void ICACHE_FLASH_ATTR -notifyLabelsTimCb(void *arg) +updateNotifyCb(void *arg) { - Websock *ws = arg; - char sock_buff[SOCK_BUF_LEN]; + int max_bl, total_bl; + cgiWebsockMeasureBacklog(URL_WS_UPDATE, &max_bl, &total_bl); + + inp_dbg("Notify broadcast +%02Xh?", pendingBroadcastTopics); - if (!notify_available || notify_cooldown) { + if (!notify_available || notify_cooldown || (max_bl > 2048)) { // do not send if we have anything significant backlogged // postpone a little - TIMER_START(¬ifyLabelsTim, notifyLabelsTimCb, 7, 0); - inp_dbg("postpone notify labels"); + TIMER_START(&updateNotifyTim, updateNotifyCb, 4, 0); + inp_dbg("postpone notify; avail? %d coold? %d maxbl? %d", notify_available, notify_cooldown, max_bl); return; } - notify_available = false; - screenSerializeLabelsToBuffer(sock_buff, SOCK_BUF_LEN); - - if (ws) { - cgiWebsocketSend(ws, sock_buff, (int) strlen(sock_buff), 0); - } else { - cgiWebsockBroadcast(URL_WS_UPDATE, sock_buff, (int) strlen(sock_buff), 0); - resetHeartbeatTimer(); - } + updateNotify_do(arg, 0); + notify_scheduled = false; notify_cooldown = true; - notify_available = true; TIMER_START(¬ifyCooldownTim, notifyCooldownTimCb, termconf->display_cooldown_ms, 0); } + /** Beep */ void ICACHE_FLASH_ATTR send_beep(void) { - if (active_clients == 0) return; - - // here's some potential for a race error with the other broadcast functions :C - cgiWebsockBroadcast(URL_WS_UPDATE, "B", 1, 0); - resetHeartbeatTimer(); + if (term_active_clients == 0) return; + screen_notifyChange(TOPIC_BELL); // XXX has latency, maybe better to send directly } @@ -143,11 +130,11 @@ send_beep(void) void ICACHE_FLASH_ATTR notify_growl(char *msg) { - if (active_clients == 0) return; + if (term_active_clients == 0) return; - // TODO via timer... - // here's some potential for a race error with the other broadcast functions :C - cgiWebsockBroadcast(URL_WS_UPDATE, msg, (int) strlen(msg), 0); + // here's some potential for a race error with the other broadcast functions + // - we assume app won't send notifications in the middle of updating content + cgiWebsockBroadcast(URL_WS_UPDATE, msg, (int) strlen(msg), WEBSOCK_FLAG_BIN); resetHeartbeatTimer(); } @@ -157,24 +144,20 @@ notify_growl(char *msg) * This is a callback for the Screen module, * called after each visible screen modification. */ -void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic) +void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyTopics topics) { - if (active_clients == 0) return; + if (term_active_clients == 0) return; - // this is probably not needed here - ensure timeout is not 0 - if (termconf->display_tout_ms == 0) - termconf->display_tout_ms = SCR_DEF_DISPLAY_TOUT_MS; + inp_dbg("Notify +%02Xh", topics); - // NOTE: the timers are restarted if already running + pendingBroadcastTopics |= topics; - if (topic == CHANGE_LABELS) { - // separate timer from content change timer, to avoid losing that update - TIMER_START(¬ifyLabelsTim, notifyLabelsTimCb, termconf->display_tout_ms+2, 0); // this delay is useful when both are fired at once on screen reset - } - else if (topic == CHANGE_CONTENT) { - // throttle delay - TIMER_START(¬ifyContentTim, notifyContentTimCb, termconf->display_tout_ms, 0); - } + int time = termconf->display_tout_ms; + if (time == 0 && notify_scheduled) return; // do not reset the timer if already scheduled + + notify_scheduled = true; + // NOTE: the timer is restarted if already running + TIMER_START(&updateNotifyTim, updateNotifyCb, time, 0); // note - this adds latency to beep } /** @@ -277,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; @@ -296,8 +279,7 @@ static void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int case 'i': // requests initial load inp_dbg("Client requests initial load"); - notifyContentTimCb(ws); // delay?? - notifyLabelsTimCb(ws); + updateNotify_do(ws, TOPIC_INITIAL|TOPIC_FLAG_NOCLEAN); break; case 'm': @@ -322,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) { - if (active_clients > 0) { + 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); @@ -348,9 +334,10 @@ static void ICACHE_FLASH_ATTR resetHeartbeatTimer(void) static void ICACHE_FLASH_ATTR closeSockCb(Websock *ws) { - active_clients--; - if (active_clients <= 0) { - active_clients = 0; + term_active_clients--; + inp_dbg("Close socket CB, remain %d clients", term_active_clients); + if (term_active_clients <= 0) { + term_active_clients = 0; if (mouse_tracking.focus_tracking) { UART_SendAsync("\x1b[O", 3); @@ -358,6 +345,7 @@ static void ICACHE_FLASH_ATTR closeSockCb(Websock *ws) // stop the timer os_timer_disarm(&heartbeatTim); + inp_dbg("Stop HB timer"); } } @@ -368,7 +356,7 @@ void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws) ws->recvCb = updateSockRx; ws->closeCb = closeSockCb; - if (active_clients == 0) { + if (term_active_clients == 0) { if (mouse_tracking.focus_tracking) { UART_SendAsync("\x1b[I", 3); } @@ -376,14 +364,14 @@ void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws) resetHeartbeatTimer(); } - active_clients++; + term_active_clients++; } 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_sockets.h b/user/cgi_sockets.h index 2010cfb..f608b3d 100644 --- a/user/cgi_sockets.h +++ b/user/cgi_sockets.h @@ -15,6 +15,8 @@ void send_beep(void); /** open pop-up notification */ void notify_growl(char *msg); +extern volatile int term_active_clients; + // defined in the makefile #if DEBUG_INPUT #define inp_warn warn 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/cgi_term_cfg.c b/user/cgi_term_cfg.c index 775db58..fe68015 100644 --- a/user/cgi_term_cfg.c +++ b/user/cgi_term_cfg.c @@ -40,7 +40,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) char buff[50]; char redir_url_buf[100]; int32 n, w, h; - bool notify_screen_content = 0, notify_screen_labels = 0; + ScreenNotifyTopics topics = 0; bool shall_clear_screen = false; bool shall_init_uart = false; @@ -94,7 +94,8 @@ cgiTermCfgSetParams(HttpdConnData *connData) if (termconf->width != w || termconf->height != h) { termconf->width = w; termconf->height = h; - shall_clear_screen = true; // this causes a notify + shall_clear_screen = true; + topics |= TOPIC_CHANGE_SCREEN_OPTS | TOPIC_CHANGE_CONTENT_ALL; } } while (0); } @@ -112,6 +113,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) if (termconf->default_bg != n) { termconf->default_bg = n; // this is current not sent through socket, no use to notify + topics |= TOPIC_CHANGE_SCREEN_OPTS; } } @@ -128,6 +130,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) if (termconf->default_fg != n) { termconf->default_fg = n; // this is current not sent through socket, no use to notify + topics |= TOPIC_CHANGE_SCREEN_OPTS; } } @@ -145,7 +148,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) if (GET_ARG("display_tout_ms")) { cgi_dbg("Display update idle timeout: %s ms", buff); n = atoi(buff); - if (n > 0) { + if (n >= 0) { termconf->display_tout_ms = n; } else { cgi_warn("Bad update timeout %s", buff); @@ -168,40 +171,62 @@ cgiTermCfgSetParams(HttpdConnData *connData) cgi_dbg("FN alt mode: %s", buff); n = atoi(buff); termconf->fn_alt_mode = (bool)n; - notify_screen_content = true; + topics |= TOPIC_CHANGE_SCREEN_OPTS; } if (GET_ARG("want_all_fn")) { cgi_dbg("AllFN mode: %s", buff); n = atoi(buff); termconf->want_all_fn = (bool)n; + topics |= TOPIC_CHANGE_SCREEN_OPTS; } if (GET_ARG("crlf_mode")) { cgi_dbg("CRLF mode: %s", buff); n = atoi(buff); termconf->crlf_mode = (bool)n; - notify_screen_content = true; + topics |= TOPIC_CHANGE_SCREEN_OPTS; } if (GET_ARG("show_buttons")) { cgi_dbg("Show buttons: %s", buff); n = atoi(buff); termconf->show_buttons = (bool)n; - notify_screen_content = true; + topics |= TOPIC_CHANGE_SCREEN_OPTS; } if (GET_ARG("show_config_links")) { cgi_dbg("Show config links: %s", buff); n = atoi(buff); termconf->show_config_links = (bool)n; - notify_screen_content = true; + topics |= TOPIC_CHANGE_SCREEN_OPTS; } if (GET_ARG("loopback")) { cgi_dbg("Loopback: %s", buff); n = atoi(buff); termconf->loopback = (bool)n; + topics |= TOPIC_CHANGE_SCREEN_OPTS; + } + + if (GET_ARG("debugbar")) { + cgi_dbg("Debugbar: %s", buff); + n = atoi(buff); + termconf->debugbar = (bool)n; + topics |= TOPIC_CHANGE_SCREEN_OPTS; + } + + if (GET_ARG("allow_decopt_12")) { + cgi_dbg("DECOPT 12: %s", buff); + n = atoi(buff); + termconf->allow_decopt_12 = (bool)n; + } + + if (GET_ARG("ascii_debug")) { + cgi_dbg("ascii_debug: %s", buff); + n = atoi(buff); + termconf->ascii_debug = (bool)n; + shall_clear_screen = true; } if (GET_ARG("theme")) { @@ -213,6 +238,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) } else { cgi_warn("Bad theme num: %s", buff); redir_url += sprintf(redir_url, "theme,"); + topics |= TOPIC_CHANGE_SCREEN_OPTS; } } @@ -221,7 +247,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) n = atoi(buff); if (n >= 0 && n <= 6 && n != 1) { termconf->cursor_shape = (enum CursorShape) n; - notify_screen_content = true; + topics |= TOPIC_CHANGE_SCREEN_OPTS; } else { cgi_warn("Bad cursor_shape num: %s", buff); redir_url += sprintf(redir_url, "cursor_shape,"); @@ -231,7 +257,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) if (GET_ARG("term_title")) { cgi_dbg("Terminal title default text: \"%s\"", buff); strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in - notify_screen_labels = true; + topics |= TOPIC_CHANGE_TITLE; } for (int btn_i = 1; btn_i <= TERM_BTN_COUNT; btn_i++) { @@ -239,7 +265,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) if (GET_ARG(buff)) { cgi_dbg("Button%d default text: \"%s\"", btn_i, buff); strncpy_safe(termconf->btn[btn_i-1], buff, TERM_BTN_LEN); - notify_screen_labels = true; + topics |= TOPIC_CHANGE_BUTTONS; } sprintf(buff, "bm%d", btn_i); @@ -368,13 +394,7 @@ cgiTermCfgSetParams(HttpdConnData *connData) serialInit(); } - if (notify_screen_content) { - screen_notifyChange(CHANGE_CONTENT); - } - - if (notify_screen_labels) { - screen_notifyChange(CHANGE_LABELS); - } + if (topics) screen_notifyChange(topics); httpdRedirect(connData, SET_REDIR_SUC "?msg=Settings%20saved%20and%20applied."); } else { @@ -437,9 +457,18 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg) else if (streq(token, "show_config_links")) { sprintf(buff, "%d", (int)termconf->show_config_links); } + else if (streq(token, "allow_decopt_12")) { + sprintf(buff, "%d", (int)termconf->allow_decopt_12); + } + else if (streq(token, "ascii_debug")) { + sprintf(buff, "%d", (int)termconf->ascii_debug); + } else if (streq(token, "loopback")) { sprintf(buff, "%d", (int)termconf->loopback); } + else if (streq(token, "debugbar")) { + sprintf(buff, "%d", (int)termconf->debugbar); + } else if (streq(token, "theme")) { sprintf(buff, "%d", termconf->theme); } diff --git a/user/cgi_wifi.c b/user/cgi_wifi.c index 8f0f22f..7378977 100644 --- a/user/cgi_wifi.c +++ b/user/cgi_wifi.c @@ -617,22 +617,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, } } else if (streq(token, "sta_active_ip")) { - x = wifi_get_opmode(); - connectStatus = wifi_station_get_connect_status(); - - if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP || wificonf->opmode == SOFTAP_MODE) { - strcpy(buff, ""); - } - else { - struct ip_info info; - wifi_get_ip_info(STATION_IF, &info); - sprintf(buff, IPSTR, GOOD_IP2STR(info.ip.addr)); - -// sprintf(buff, "ip: "IPSTR", mask: "IPSTR", gw: "IPSTR, -// GOOD_IP2STR(info.ip.addr), -// GOOD_IP2STR(info.netmask.addr), -// GOOD_IP2STR(info.gw.addr)); - } + getStaIpAsString(buff); } tplSend(connData, buff, -1); 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/routes.h b/user/routes.h index 2ba843f..5296b48 100644 --- a/user/routes.h +++ b/user/routes.h @@ -6,7 +6,4 @@ extern const HttpdBuiltInUrl routes[]; -/** Broadcast screen state to sockets */ -void screen_notifyChange(); - #endif //ROUTES_H diff --git a/user/screen.c b/user/screen.c index 3a7af28..755aa44 100644 --- a/user/screen.c +++ b/user/screen.c @@ -5,10 +5,9 @@ #include "sgr.h" #include "ascii.h" #include "apars_logging.h" -#include "jstring.h" #include "character_sets.h" #include "utf8.h" -#include "uart_buffer.h" +#include "cgi_sockets.h" TerminalConfigBundle * const termconf = &persist.current.termconf; TerminalConfigBundle termconf_live; @@ -74,7 +73,6 @@ typedef struct { bool hanging; //!< xenl state - cursor half-wrapped /* SGR */ - bool inverse; //!< not in attrs bc it's applied immediately when writing the cell bool conceal; //!< similar to inverse, causes all to be replaced by SP u16 attrs; Color fg; //!< Foreground color for writing @@ -139,19 +137,46 @@ static struct { * (from nested calls) */ static volatile int notifyLock = 0; +static volatile ScreenNotifyTopics lockTopics = 0; -#define NOTIFY_LOCK() do { \ +static struct { + int x_min, y_min, x_max, y_max; +} scr_dirty; + +#define reset_screen_dirty() do { \ + scr_dirty.x_min = W; \ + scr_dirty.x_max = -1; \ + scr_dirty.y_min = H; \ + scr_dirty.y_max = -1; \ + } while(0) + +#define expand_dirty(y0, y1, x0, x1) do { \ + seri_dbg("Expand: X: (%d..%d) -> %d..%d, Y: (%d..%d) -> %d..%d", scr_dirty.x_min, scr_dirty.x_max, x0, x1, scr_dirty.y_min, scr_dirty.y_max, y0, y1); \ + if ((int)(y0) < scr_dirty.y_min) scr_dirty.y_min = (y0); \ + if ((int)(x0) < scr_dirty.x_min) scr_dirty.x_min = (x0); \ + if ((int)(y1) > scr_dirty.y_max) scr_dirty.y_max = (y1); \ + if ((int)(x1) > scr_dirty.x_max) scr_dirty.x_max = (x1); \ + } while(0) + +#define NOTIFY_LOCK() do { \ notifyLock++; \ } while(0) - -#define NOTIFY_DONE() do { \ + +#define NOTIFY_DONE(updateTopics) do { \ + lockTopics |= (updateTopics); \ if (notifyLock > 0) notifyLock--; \ - if (notifyLock == 0) screen_notifyChange(CHANGE_CONTENT); \ + if (notifyLock == 0) { \ + screen_notifyChange(lockTopics); \ + lockTopics = 0;\ + } \ } while(0) /** Clear the hanging attribute if the cursor is no longer >= W */ #define clear_invalid_hanging() do { \ - if (cursor.hanging && cursor.x != W-1) cursor.hanging = false; \ + if (cursor.hanging && cursor.x != W-1) { \ + cursor.hanging = false; \ + screen_notifyChange(TOPIC_CHANGE_CURSOR); \ + } \ } while(false) #define cursor_inside_region() (cursor.y >= TOP && cursor.y <= BTM) @@ -185,6 +210,9 @@ terminal_restore_defaults(void) termconf->cursor_shape = SCR_DEF_CURSOR_SHAPE; termconf->crlf_mode = SCR_DEF_CRLF; termconf->want_all_fn = SCR_DEF_ALLFN; + termconf->debugbar = SCR_DEF_DEBUGBAR; + termconf->allow_decopt_12 = SCR_DEF_DECOPT12; + termconf->ascii_debug = SCR_DEF_ASCIIDEBUG; } /** @@ -203,22 +231,28 @@ terminal_apply_settings_noclear(void) { bool changed = false; -// // Migrate to v1 -// if (termconf->config_version < 1) { -// persist_dbg("termconf: Updating to version %d", 1); -// termconf->display_cooldown_ms = SCR_DEF_DISPLAY_COOLDOWN_MS; -// changed = 1; -// } + // Migrate + if (termconf->config_version < 1) { + persist_dbg("termconf: Updating to version %d", 1); + termconf->debugbar = SCR_DEF_DEBUGBAR; + changed = 1; + } + if (termconf->config_version < 2) { + persist_dbg("termconf: Updating to version %d", 1); + termconf->allow_decopt_12 = SCR_DEF_DECOPT12; + changed = 1; + } + if (termconf->config_version < 3) { + persist_dbg("termconf: Updating to version %d", 1); + termconf->ascii_debug = SCR_DEF_ASCIIDEBUG; + changed = 1; + } termconf->config_version = TERMCONF_VERSION; // Validation... - if (termconf->display_tout_ms == 0) { - termconf->display_tout_ms = SCR_DEF_DISPLAY_TOUT_MS; - changed = 1; - } if (termconf->display_cooldown_ms == 0) { - termconf->display_cooldown_ms = SCR_DEF_DISPLAY_COOLDOWN_MS; + termconf->display_cooldown_ms = 1; changed = 1; } @@ -248,6 +282,7 @@ screen_init(void) { if(DEBUG_HEAP) dbg("Screen buffer size = %d bytes", sizeof(screen)); + reset_screen_dirty(); screen_reset(); } @@ -257,6 +292,8 @@ screen_init(void) static void ICACHE_FLASH_ATTR cursor_reset(void) { + NOTIFY_LOCK(); + cursor.x = 0; cursor.y = 0; cursor.hanging = false; @@ -266,6 +303,8 @@ cursor_reset(void) cursor.charset1 = CS_0_DEC_SUPPLEMENTAL; screen_reset_sgr(); + + NOTIFY_DONE(TOPIC_CHANGE_CURSOR); } /** @@ -285,7 +324,7 @@ screen_reset_on_resize(void) // size is left unchanged screen_clear(CLEAR_ALL); // also clears utf cache - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_INTERNAL); } /** @@ -294,15 +333,20 @@ screen_reset_on_resize(void) void ICACHE_FLASH_ATTR screen_reset_sgr(void) { + NOTIFY_LOCK(); + cursor.fg = 0; cursor.bg = 0; cursor.attrs = 0; cursor.conceal = false; + + NOTIFY_DONE(TOPIC_INTERNAL); } static void ICACHE_FLASH_ATTR screen_reset_do(bool size, bool labels) { + ScreenNotifyTopics topics = TOPIC_CHANGE_SCREEN_OPTS | TOPIC_CHANGE_CURSOR | TOPIC_CHANGE_CONTENT_ALL; NOTIFY_LOCK(); // DECopts @@ -350,7 +394,7 @@ screen_reset_do(bool size, bool labels) termconf_live.show_buttons = termconf->show_buttons; termconf_live.show_config_links = termconf->show_config_links; - screen_notifyChange(CHANGE_LABELS); + topics |= TOPIC_CHANGE_TITLE | TOPIC_CHANGE_BUTTONS; } // initial values in the save buffer in case of receiving restore without storing first @@ -368,7 +412,7 @@ screen_reset_do(bool size, bool labels) opt_backup.show_buttons = termconf_live.show_buttons; opt_backup.show_config_links = termconf_live.show_config_links; - NOTIFY_DONE(); + NOTIFY_DONE(topics); } /** @@ -395,6 +439,8 @@ screen_swap_state(bool alternate) return; // nothing to do } + NOTIFY_LOCK(); + if (alternate) { ansi_dbg("Swap to alternate"); // store old state @@ -411,7 +457,6 @@ screen_swap_state(bool alternate) } else { ansi_dbg("Unswap from alternate"); - NOTIFY_LOCK(); memcpy(termconf_live.title, state_backup.title, TERM_TITLE_LEN); memcpy(termconf_live.btn, state_backup.btn, sizeof(termconf_live.btn)); memcpy(termconf_live.btn_msg, state_backup.btn_msg, sizeof(termconf_live.btn_msg)); @@ -421,11 +466,10 @@ screen_swap_state(bool alternate) // this may clear the screen as a side effect if size changed screen_resize(state_backup.height, state_backup.width); // TODO restore screen content (if this is ever possible) - NOTIFY_DONE(); - screen_notifyChange(CHANGE_LABELS); } state_backup.alternate_active = alternate; + NOTIFY_DONE(TOPIC_INITIAL); } //endregion @@ -435,19 +479,25 @@ screen_swap_state(bool alternate) void ICACHE_FLASH_ATTR screen_clear_all_tabs(void) { + NOTIFY_LOCK(); memset(scr.tab_stops, 0, sizeof(scr.tab_stops)); + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR screen_set_tab(void) { + NOTIFY_LOCK(); scr.tab_stops[cursor.x/32] |= (1<<(cursor.x%32)); + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR screen_clear_tab(void) { + NOTIFY_LOCK(); scr.tab_stops[cursor.x/32] &= ~(1<<(cursor.x%32)); + NOTIFY_DONE(TOPIC_INTERNAL); } /** @@ -529,7 +579,7 @@ screen_tab_forward(int count) cursor.x = W - 1; } } - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CURSOR); } void ICACHE_FLASH_ATTR @@ -544,7 +594,7 @@ screen_tab_reverse(int count) cursor.x = 0; } } - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CURSOR); } //endregion @@ -570,6 +620,11 @@ clear_range_do(unsigned int from, unsigned int to, bool clear_utf) // we discard all attributes except color-set flags sample.attrs = (CellAttrs) (cursor.attrs & (ATTR_FG | ATTR_BG)); + // if no colors, always use 0,0 + if (0 == sample.attrs) { + sample.fg = sample.bg = 0; + } + for (unsigned int i = from; i <= to; i++) { if (clear_utf) { UnicodeCacheRef symbol = screen[i].symbol; @@ -727,13 +782,15 @@ screen_clear(ClearMode mode) case CLEAR_FROM_CURSOR: clear_range_utf((cursor.y * W) + cursor.x, W * H - 1); + expand_dirty(cursor.y, H-1, 0, W-1); break; case CLEAR_TO_CURSOR: clear_range_utf(0, (cursor.y * W) + cursor.x); + expand_dirty(0, cursor.y, 0, W-1); break; } - NOTIFY_DONE(); + NOTIFY_DONE(mode == CLEAR_ALL ? TOPIC_CHANGE_CONTENT_ALL : TOPIC_CHANGE_CONTENT_PART); } /** @@ -746,17 +803,20 @@ screen_clear_line(ClearMode mode) switch (mode) { case CLEAR_ALL: clear_row_utf(cursor.y); + expand_dirty(cursor.y, cursor.y, 0, W-1); break; case CLEAR_FROM_CURSOR: clear_range_utf(cursor.y * W + cursor.x, (cursor.y + 1) * W - 1); + expand_dirty(cursor.y, cursor.y, cursor.x, W-1); break; case CLEAR_TO_CURSOR: clear_range_utf(cursor.y * W, cursor.y * W + cursor.x); + expand_dirty(cursor.y, cursor.y, 0, cursor.x); break; } - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } void ICACHE_FLASH_ATTR @@ -767,8 +827,9 @@ screen_clear_in_line(unsigned int count) screen_clear_line(CLEAR_FROM_CURSOR); } else { clear_range_utf(cursor.y * W + cursor.x, cursor.y * W + cursor.x + count - 1); + expand_dirty(cursor.y, cursor.y, cursor.x, cursor.x + count - 1); } - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } void ICACHE_FLASH_ATTR @@ -796,7 +857,8 @@ screen_insert_lines(unsigned int lines) copy_row(i, cursor.y); } } - NOTIFY_DONE(); + expand_dirty(cursor.y, BTM, 0, W - 1); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } void ICACHE_FLASH_ATTR @@ -821,7 +883,8 @@ screen_delete_lines(unsigned int lines) clear_range_noutf((movedBlockEnd+1)*W, (BTM+1)*W-1); } - NOTIFY_DONE(); + expand_dirty(cursor.y, BTM, 0, W - 1); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } void ICACHE_FLASH_ATTR @@ -844,7 +907,8 @@ screen_insert_characters(unsigned int count) } clear_range_utf(cursor.y * W + cursor.x, cursor.y * W + targetStart - 1); } - NOTIFY_DONE(); + expand_dirty(cursor.y, cursor.y, cursor.x, W - 1); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } void ICACHE_FLASH_ATTR @@ -870,7 +934,8 @@ screen_delete_characters(unsigned int count) screen_clear_line(CLEAR_FROM_CURSOR); } - NOTIFY_DONE(); + expand_dirty(cursor.y, cursor.y, cursor.x, W - 1); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } //endregion @@ -891,7 +956,7 @@ screen_fill_with_E(void) for (unsigned int i = 0; i <= W*H-1; i++) { memcpy(&screen[i], &sample, sizeof(Cell)); } - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_ALL); } /** @@ -920,14 +985,15 @@ screen_resize(int rows, int cols) W = cols; H = rows; screen_reset_on_resize(); - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS|TOPIC_CHANGE_CONTENT_ALL|TOPIC_CHANGE_CURSOR); } void ICACHE_FLASH_ATTR screen_set_title(const char *title) { + NOTIFY_LOCK(); strncpy(termconf_live.title, title, TERM_TITLE_LEN); - screen_notifyChange(CHANGE_LABELS); + NOTIFY_DONE(TOPIC_CHANGE_TITLE); } /** @@ -938,8 +1004,9 @@ screen_set_title(const char *title) void ICACHE_FLASH_ATTR screen_set_button_text(int num, const char *text) { + NOTIFY_LOCK(); strncpy(termconf_live.btn[num-1], text, TERM_BTN_LEN); - screen_notifyChange(CHANGE_LABELS); + NOTIFY_DONE(TOPIC_CHANGE_BUTTONS); } /** @@ -970,7 +1037,8 @@ screen_scroll_up(unsigned int lines) clear_range_noutf(y * W, (BTM + 1) * W - 1); done: - NOTIFY_DONE(); + expand_dirty(TOP, BTM, 0, W - 1); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } /** @@ -1000,13 +1068,15 @@ screen_scroll_down(unsigned int lines) clear_range_noutf(TOP * W, TOP * W + lines * W - 1); done: - NOTIFY_DONE(); + expand_dirty(TOP, BTM, 0, W - 1); + NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); } /** Set scrolling region */ void ICACHE_FLASH_ATTR screen_set_scrolling_region(int from, int to) { + NOTIFY_LOCK(); if (from <= 0 && to <= 0) { scr.vm0 = 0; scr.vm1 = H-1; @@ -1020,6 +1090,7 @@ screen_set_scrolling_region(int from, int to) // Always move cursor home (may be translated due to DECOM) screen_cursor_set(0, 0); + NOTIFY_DONE(TOPIC_INTERNAL); } //endregion @@ -1033,13 +1104,15 @@ screen_cursor_shape(enum CursorShape shape) NOTIFY_LOCK(); if (shape == CURSOR_DEFAULT) shape = termconf->cursor_shape; termconf_live.cursor_shape = shape; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); } /** set cursor blink option */ void ICACHE_FLASH_ATTR screen_cursor_blink(bool blink) { + if (!termconf->allow_decopt_12) return; + NOTIFY_LOCK(); if (blink) { if (termconf_live.cursor_shape == CURSOR_BLOCK) termconf_live.cursor_shape = CURSOR_BLOCK_BL; @@ -1050,7 +1123,7 @@ screen_cursor_blink(bool blink) if (termconf_live.cursor_shape == CURSOR_BAR_BL) termconf_live.cursor_shape = CURSOR_BAR; if (termconf_live.cursor_shape == CURSOR_UNDERLINE_BL) termconf_live.cursor_shape = CURSOR_UNDERLINE; } - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); } /** @@ -1062,7 +1135,7 @@ screen_cursor_set(int y, int x) NOTIFY_LOCK(); screen_cursor_set_x(x); screen_cursor_set_y(y); - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CURSOR); } /** @@ -1079,6 +1152,14 @@ screen_cursor_get(int *y, int *x) } } +/* Report scrolling region */ +void ICACHE_FLASH_ATTR +screen_region_get(int *pv0, int *pv1) +{ + *pv0 = TOP; + *pv1 = BTM; +} + /** * Set cursor X position */ @@ -1093,7 +1174,7 @@ screen_cursor_set_x(int x) // hanging happens when the cursor is virtually at col=81, which // cannot be set using the cursor-set commands. cursor.hanging = false; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CURSOR); } /** @@ -1112,7 +1193,7 @@ screen_cursor_set_y(int y) if (y < 0) y = 0; } cursor.y = y; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CURSOR); } /** @@ -1123,6 +1204,7 @@ screen_cursor_move(int dy, int dx, bool scroll) { NOTIFY_LOCK(); int move; + bool scrolled = 0; clear_invalid_hanging(); @@ -1169,7 +1251,10 @@ screen_cursor_move(int dy, int dx, bool scroll) if (was_inside) { move = -(cursor.y - TOP); cursor.y = TOP; - if (scroll) screen_scroll_down((unsigned int) move); + if (scroll) { + screen_scroll_down((unsigned int) move); + scrolled = true; + } } else { // outside the region, just validate that we're not going offscreen @@ -1184,7 +1269,10 @@ screen_cursor_move(int dy, int dx, bool scroll) if (was_inside) { move = cursor.y - BTM; cursor.y = BTM; - if (scroll) screen_scroll_up((unsigned int) move); + if (scroll) { + screen_scroll_up((unsigned int) move); + scrolled = true; + } } else { // outside the region, just validate that we're not going offscreen @@ -1195,7 +1283,11 @@ screen_cursor_move(int dy, int dx, bool scroll) } } - NOTIFY_DONE(); + if (scrolled) { + expand_dirty(TOP, BTM, 0, W-1); + } + + NOTIFY_DONE(TOPIC_CHANGE_CURSOR | (scrolled*TOPIC_CHANGE_CONTENT_PART)); } /** @@ -1232,21 +1324,23 @@ screen_cursor_restore(bool withAttrs) } } - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_CURSOR); } void ICACHE_FLASH_ATTR screen_back_index(int count) { NOTIFY_LOCK(); + ScreenNotifyTopics topics = TOPIC_CHANGE_CURSOR; int new_x = cursor.x - count; if (new_x >= 0) { cursor.x = new_x; } else { cursor.x = 0; screen_insert_characters(-new_x); + topics |= TOPIC_CHANGE_CONTENT_PART; } - NOTIFY_DONE(); + NOTIFY_DONE(topics); } //endregion @@ -1261,7 +1355,7 @@ screen_set_cursor_visible(bool visible) { NOTIFY_LOCK(); scr.cursor_visible = visible; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); } /** @@ -1270,7 +1364,9 @@ screen_set_cursor_visible(bool visible) void ICACHE_FLASH_ATTR screen_wrap_enable(bool enable) { + NOTIFY_LOCK(); cursor.auto_wrap = enable; + NOTIFY_DONE(TOPIC_INTERNAL); } /** @@ -1279,7 +1375,9 @@ screen_wrap_enable(bool enable) void ICACHE_FLASH_ATTR screen_reverse_wrap_enable(bool enable) { + NOTIFY_LOCK(); cursor.reverse_wrap = enable; + NOTIFY_DONE(TOPIC_INTERNAL); } /** @@ -1288,8 +1386,10 @@ screen_reverse_wrap_enable(bool enable) void ICACHE_FLASH_ATTR screen_set_fg(Color color) { + NOTIFY_LOCK(); cursor.fg = color; cursor.attrs |= ATTR_FG; + NOTIFY_DONE(TOPIC_INTERNAL); } /** @@ -1298,8 +1398,10 @@ screen_set_fg(Color color) void ICACHE_FLASH_ATTR screen_set_bg(Color color) { + NOTIFY_LOCK(); cursor.bg = color; cursor.attrs |= ATTR_BG; + NOTIFY_DONE(TOPIC_INTERNAL); } /** @@ -1308,8 +1410,10 @@ screen_set_bg(Color color) void ICACHE_FLASH_ATTR screen_set_default_fg(void) { + NOTIFY_LOCK(); cursor.fg = 0; cursor.attrs &= ~ATTR_FG; + NOTIFY_DONE(TOPIC_INTERNAL); } /** @@ -1318,45 +1422,57 @@ screen_set_default_fg(void) void ICACHE_FLASH_ATTR screen_set_default_bg(void) { + NOTIFY_LOCK(); cursor.bg = 0; cursor.attrs &= ~ATTR_BG; + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR screen_set_sgr(CellAttrs attrs, bool ena) { + NOTIFY_LOCK(); if (ena) { cursor.attrs |= attrs; } else { cursor.attrs &= ~attrs; } + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR screen_set_sgr_conceal(bool ena) { + NOTIFY_LOCK(); cursor.conceal = ena; + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR screen_set_charset_n(int Gx) { + NOTIFY_LOCK(); if (Gx < 0 || Gx > 1) return; // bad n cursor.charsetN = Gx; + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR screen_set_charset(int Gx, char charset) { + NOTIFY_LOCK(); if (Gx == 0) cursor.charset0 = charset; else if (Gx == 1) cursor.charset1 = charset; + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR screen_set_insert_mode(bool insert) { + NOTIFY_LOCK(); scr.insert_mode = insert; + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR @@ -1364,7 +1480,7 @@ screen_set_numpad_alt_mode(bool alt_mode) { NOTIFY_LOCK(); scr.numpad_alt_mode = alt_mode; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); } void ICACHE_FLASH_ATTR @@ -1372,7 +1488,7 @@ screen_set_cursors_alt_mode(bool alt_mode) { NOTIFY_LOCK(); scr.cursors_alt_mode = alt_mode; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); } void ICACHE_FLASH_ATTR @@ -1380,7 +1496,7 @@ screen_set_reverse_video(bool reverse) { NOTIFY_LOCK(); scr.reverse_video = reverse; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); } void ICACHE_FLASH_ATTR @@ -1388,7 +1504,7 @@ screen_set_bracketed_paste(bool ena) { NOTIFY_LOCK(); scr.bracketed_paste = ena; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); } void ICACHE_FLASH_ATTR @@ -1396,29 +1512,34 @@ screen_set_newline_mode(bool nlm) { NOTIFY_LOCK(); termconf_live.crlf_mode = nlm; - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); } void ICACHE_FLASH_ATTR screen_set_origin_mode(bool region_origin) { + NOTIFY_LOCK(); cursor.origin_mode = region_origin; screen_cursor_set(0, 0); + NOTIFY_DONE(TOPIC_INTERNAL); } static void ICACHE_FLASH_ATTR do_save_private_opt(int n, bool save) { + ScreenNotifyTopics topics = TOPIC_INTERNAL; + if (!save) NOTIFY_LOCK(); #define SAVE_RESTORE(sf, of) do { if (save) sf=(of); else of=(sf); } while(0) switch (n) { case 1: SAVE_RESTORE(opt_backup.cursors_alt_mode, scr.cursors_alt_mode); + topics |= TOPIC_CHANGE_SCREEN_OPTS; break; case 5: SAVE_RESTORE(opt_backup.reverse_video, scr.reverse_video); break; case 6: - SAVE_RESTORE(opt_backup.origin_mode, cursor.origin_mode); + SAVE_RESTORE(opt_backup.origin_mode, cursor.origin_mode); // XXX maybe we should move cursor to 1,1 if it's restored to True break; case 7: SAVE_RESTORE(opt_backup.auto_wrap, cursor.auto_wrap); @@ -1429,14 +1550,17 @@ do_save_private_opt(int n, bool save) case 1002: case 1003: SAVE_RESTORE(opt_backup.mouse_tracking, mouse_tracking.mode); + topics |= TOPIC_CHANGE_SCREEN_OPTS; break; case 1004: SAVE_RESTORE(opt_backup.focus_tracking, mouse_tracking.focus_tracking); + topics |= TOPIC_CHANGE_SCREEN_OPTS; break; case 1005: case 1006: case 1015: SAVE_RESTORE(opt_backup.mouse_encoding, mouse_tracking.encoding); + topics |= TOPIC_CHANGE_SCREEN_OPTS; break; case 12: // cursor blink if (save) { @@ -1444,9 +1568,11 @@ do_save_private_opt(int n, bool save) } else { screen_cursor_blink(opt_backup.cursor_blink); } + topics |= TOPIC_CHANGE_SCREEN_OPTS; break; case 25: SAVE_RESTORE(opt_backup.cursor_visible, scr.cursor_visible); + topics |= TOPIC_CHANGE_SCREEN_OPTS; break; case 45: SAVE_RESTORE(opt_backup.reverse_wrap, cursor.reverse_wrap); @@ -1456,19 +1582,24 @@ do_save_private_opt(int n, bool save) break; case 800: SAVE_RESTORE(opt_backup.show_buttons, termconf_live.show_buttons); + topics |= TOPIC_CHANGE_SCREEN_OPTS; break; case 801: SAVE_RESTORE(opt_backup.show_config_links, termconf_live.show_config_links); + topics |= TOPIC_CHANGE_SCREEN_OPTS; break; default: ansi_warn("Cannot store ?%d", n); } + if (!save) NOTIFY_DONE(topics); } void ICACHE_FLASH_ATTR screen_save_private_opt(int n) { + NOTIFY_LOCK(); do_save_private_opt(n, true); + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR @@ -1476,7 +1607,7 @@ screen_restore_private_opt(int n) { NOTIFY_LOCK(); do_save_private_opt(n, false); - NOTIFY_DONE(); + NOTIFY_DONE(TOPIC_INTERNAL); } void ICACHE_FLASH_ATTR @@ -1507,6 +1638,9 @@ putchar_graphic(const char *ch) { static char buf[4]; + NOTIFY_LOCK(); + ScreenNotifyTopics topics = TOPIC_CHANGE_CURSOR; + if (cursor.hanging) { // perform the scheduled wrap if hanging // if auto-wrap = off, it overwrites the last char @@ -1529,17 +1663,29 @@ putchar_graphic(const char *ch) // move the rest of the line if we're in Insert Mode if (cursor.x < W-1 && scr.insert_mode) screen_insert_characters(1); - if (ch[1] == 0 && ch[0] <= 0x7f) { + char chs = (cursor.charsetN == 0) ? cursor.charset0 : cursor.charset1; + if (chs != 'B' && ch[1] == 0 && ch[0] <= 0x7f) { // we have len=1 and ASCII, can be re-mapped using a table - utf8_remap(buf, ch[0], (cursor.charsetN == 0) ? cursor.charset0 : cursor.charset1); + utf8_remap(buf, ch[0], chs); ch = buf; } + + UnicodeCacheRef oldSymbol = c->symbol; + Color oldFg = c->fg; + Color oldBg = c->bg; + CellAttrs oldAttrs = c->attrs; + unicode_cache_remove(c->symbol); c->symbol = unicode_cache_add((const u8 *)ch); c->fg = cursor.fg; c->bg = cursor.bg; c->attrs = cursor.attrs; + if (c->symbol != oldSymbol || c->fg != oldFg || c->bg != oldBg || c->attrs != oldAttrs) { + expand_dirty(cursor.y, cursor.y, cursor.x, cursor.x); + topics |= TOPIC_CHANGE_CONTENT_PART; + } + cursor.x++; // X wrap if (cursor.x >= W) { @@ -1547,6 +1693,7 @@ putchar_graphic(const char *ch) cursor.x = W - 1; } + NOTIFY_DONE(topics); return ch; } @@ -1556,8 +1703,6 @@ putchar_graphic(const char *ch) void ICACHE_FLASH_ATTR screen_putchar(const char *ch) { - NOTIFY_LOCK(); - // clear "hanging" flag if not possible clear_invalid_hanging(); @@ -1598,8 +1743,8 @@ screen_putchar(const char *ch) // not have to call the remap function repeatedly. strncpy(scr.last_char, result, 4); - done: - NOTIFY_DONE(); +done: + return; } /** @@ -1609,7 +1754,6 @@ screen_putchar(const char *ch) void ICACHE_FLASH_ATTR screen_repeat_last_character(int count) { - NOTIFY_LOCK(); if (scr.last_char[0]==0) { scr.last_char[0] = ' '; scr.last_char[1] = 0; @@ -1621,7 +1765,6 @@ screen_repeat_last_character(int count) putchar_graphic(scr.last_char); count--; } - NOTIFY_DONE(); } /** @@ -1661,7 +1804,7 @@ utf8_remap(char *out, char g, char charset) break; } - utf8_encode(out, utf); + utf8_encode(out, utf, false); } //endregion @@ -1670,33 +1813,23 @@ utf8_remap(char *out, char g, char charset) struct ScreenSerializeState { Color lastFg; Color lastBg; + Color lastLiveFg; + Color lastLiveBg; CellAttrs lastAttrs; UnicodeCacheRef lastSymbol; char lastChar[4]; u8 lastCharLen; - int index; + int index; // index in the screen buffer + ScreenNotifyTopics topics; + ScreenNotifyTopics last_topic; + ScreenNotifyTopics current_topic; + bool partial; + int x_min, x_max, y_min, y_max; + int i_max; + int i_start; + bool first; }; -/** - * buffer should be at least 64+5*10+6 long (title + buttons + 6), ie. 120 - * @param buffer - * @param buf_len - */ -void ICACHE_FLASH_ATTR -screenSerializeLabelsToBuffer(char *buffer, size_t buf_len) -{ - (void)buf_len; - // let's just assume it's long enough - called with the huge websocket buffer - sprintf(buffer, "T%s\x01%s\x01%s\x01%s\x01%s\x01%s", // use 0x01 as separator - termconf_live.title, - termconf_live.btn[0], - termconf_live.btn[1], - termconf_live.btn[2], - termconf_live.btn[3], - termconf_live.btn[4] - ); -} - /** * Serialize the screen to a data buffer. May need multiple calls if the buffer is insufficient in size. * @@ -1705,12 +1838,14 @@ screenSerializeLabelsToBuffer(char *buffer, size_t buf_len) * * @param buffer - buffer array of limited size. If NULL, indicates this is the last call. * @param buf_len - buffer array size + * @param topics - what should be included in the message (ignored after the first call) * @param data - opaque pointer to internal data structure for storing state between repeated calls - * if NULL, indicates this is the first call. - * @return HTTPD_CGI_DONE or HTTPD_CGI_MORE. If more, repeat with the same DATA. + * if NULL, indicates this is the first call; the structure will be allocated. + * + * @return HTTPD_CGI_DONE or HTTPD_CGI_MORE. If more, repeat with the same `data` pointer. */ httpd_cgi_state ICACHE_FLASH_ATTR -screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) +screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics, void **data) { struct ScreenSerializeState *ss = *data; @@ -1719,11 +1854,11 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) return HTTPD_CGI_DONE; } - Cell *cell, *cell0; + Cell *cell, *cell0; // temporary cell pointers for finding repetitions - u8 nbytes; - size_t remain = buf_len; - char *bb = buffer; + u8 nbytes; // temporary variable for utf writing utilities + size_t remain = buf_len; // remaining space in the output buffer + char *bb = buffer; // write pointer #define bufput_c(c) do { \ *bb = (char)(c); \ @@ -1732,7 +1867,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) } while(0) #define bufput_utf8(num) do { \ - nbytes = utf8_encode(bb, (num)+1); \ + nbytes = utf8_encode(bb, (num)+1, true); \ bb += nbytes; \ remain -= nbytes; \ } while(0) @@ -1742,69 +1877,267 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) bufput_utf8((num)); \ } while(0) + // tags for screen serialization +#define SEQ_TAG_SKIP '\x01' +#define SEQ_TAG_REPEAT '\x02' +#define SEQ_TAG_COLORS '\x03' +#define SEQ_TAG_ATTRS '\x04' +#define SEQ_TAG_FG '\x05' +#define SEQ_TAG_BG '\x06' +#define SEQ_TAG_ATTRS_0 '\x07' + +#define TOPICMARK_SCREEN_OPTS 'O' +#define TOPICMARK_TITLE 'T' +#define TOPICMARK_BUTTONS 'B' +#define TOPICMARK_DEBUG 'D' +#define TOPICMARK_BELL '!' +#define TOPICMARK_CURSOR 'C' +#define TOPICMARK_SCREEN 'S' + if (ss == NULL) { + // START! + *data = ss = malloc(sizeof(struct ScreenSerializeState)); - ss->index = 0; - ss->lastBg = 0xFF; - ss->lastFg = 0xFF; - ss->lastAttrs = 0xFFFF; - ss->lastCharLen = 0; - ss->lastSymbol = 0; + + if (topics == 0 || termconf_live.debugbar) { + topics |= TOPIC_INTERNAL; + } + + if (topics & TOPIC_CHANGE_CONTENT_PART) { + // reset dirty extents + ss->partial = true; + + ss->x_min = scr_dirty.x_min; + ss->x_max = scr_dirty.x_max; + ss->y_min = scr_dirty.y_min; + ss->y_max = scr_dirty.y_max; + + if (ss->x_min > ss->x_max || ss->y_min > ss->y_max) { + seri_warn("Partial redraw, but bad bounds! X %d..%d, Y %d..%d", ss->x_min, ss->x_max, ss->y_min, ss->y_max); + // use full redraw + reset_screen_dirty(); + + topics ^= TOPIC_CHANGE_CONTENT_PART; + topics |= TOPIC_CHANGE_CONTENT_ALL; + } else { + // is OK + ss->i_max = ss->y_max * W + ss->x_max; + ss->index = W*ss->y_min + ss->x_min; + seri_dbg("Partial! X %d..%d, Y %d..%d, i_max %d", ss->x_min, ss->x_max, ss->y_min, ss->y_max, ss->i_max); + } + } + + if (topics & TOPIC_CHANGE_CONTENT_ALL) { + // this is a no-clean request, do not purge + // it's also always a full-screen repaint + ss->partial = false; + ss->index = 0; + ss->i_max = W*H-1; + ss->x_min = 0; + ss->x_max = W-1; + ss->y_min = 0; + ss->y_max = H-1; + seri_dbg("Full redraw!"); + } + + ss->i_start = ss->index; + + if ((topics & (TOPIC_CHANGE_CONTENT_ALL | TOPIC_CHANGE_CONTENT_PART)) && !(topics & TOPIC_FLAG_NOCLEAN)) { + reset_screen_dirty(); + } + + ss->topics = topics; + ss->last_topic = 0; // to be filled + ss->current_topic = 0; // to be filled strncpy(ss->lastChar, " ", 4); - bufput_c('S'); - // H W X Y Attribs - bufput_utf8(H); - bufput_utf8(W); - bufput_utf8(cursor.y); - bufput_utf8(cursor.x); - // 3B has 18 free bits - bufput_utf8( - (scr.cursor_visible << 0) | - (cursor.hanging << 1) | - (scr.cursors_alt_mode << 2) | - (scr.numpad_alt_mode << 3) | - (termconf_live.fn_alt_mode << 4) | - ((mouse_tracking.mode>MTM_NONE) << 5) | // disables context menu - ((mouse_tracking.mode>=MTM_NORMAL) << 6) | // disables selecting - (termconf_live.show_buttons << 7) | - (termconf_live.show_config_links << 8) | - ((termconf_live.cursor_shape&0x07) << 9) | // 9,10,11 - cursor shape based on DECSCUSR - (termconf_live.crlf_mode << 12) | - (scr.bracketed_paste << 13) | - (scr.reverse_video << 14) - ); + bufput_c('U'); // - stands for "update" + + bufput_utf8(topics); + + if (ss->partial) { + // advance to the first char we want to send + } } -#define SEQ_TAG_REPEAT '\x02' -#define SEQ_TAG_COLORS '\x03' -#define SEQ_TAG_ATTRS '\x04' -#define SEQ_TAG_FG '\x05' -#define SEQ_TAG_BG '\x06' + int begun_topic = 0; + int prev_topic = 0; + +#define BEGIN_TOPIC(topic, size) \ + if (ss->last_topic == prev_topic) { \ + begun_topic = (topic); \ + if (ss->topics & (topic)) { \ + if (remain < (size)) return HTTPD_CGI_MORE; + +#define END_TOPIC \ + } \ + ss->last_topic = begun_topic; \ + ss->current_topic = 0; \ + } \ + prev_topic = begun_topic; + + if (ss->current_topic == 0) { + BEGIN_TOPIC(TOPIC_CHANGE_SCREEN_OPTS, 32+1) + bufput_c(TOPICMARK_SCREEN_OPTS); + + bufput_utf8(H); + bufput_utf8(W); + bufput_utf8(termconf_live.theme); + + bufput_utf8(termconf_live.default_fg & 0xFFFF); + bufput_utf8((termconf_live.default_fg >> 16) & 0xFFFF); + + bufput_utf8(termconf_live.default_bg & 0xFFFF); + bufput_utf8((termconf_live.default_bg >> 16) & 0xFFFF); + + bufput_utf8( + (scr.cursor_visible << 0) | + (termconf_live.debugbar << 1) | // debugbar - this was previously "hanging" + (scr.cursors_alt_mode << 2) | + (scr.numpad_alt_mode << 3) | + (termconf_live.fn_alt_mode << 4) | + ((mouse_tracking.mode > MTM_NONE) << 5) | // disables context menu + ((mouse_tracking.mode >= MTM_NORMAL) << 6) | // disables selecting + (termconf_live.show_buttons << 7) | + (termconf_live.show_config_links << 8) | + ((termconf_live.cursor_shape & 0x07) << 9) | // 9,10,11 - cursor shape based on DECSCUSR + (termconf_live.crlf_mode << 12) | + (scr.bracketed_paste << 13) | + (scr.reverse_video << 14) + ); + END_TOPIC + + BEGIN_TOPIC(TOPIC_CHANGE_TITLE, TERM_TITLE_LEN+4+1) + bufput_c(TOPICMARK_TITLE); + + int len = (int) strlen(termconf_live.title); + memcpy(bb, termconf_live.title, len); + bb += len; + remain -= len; + bufput_c('\x01'); + END_TOPIC + + BEGIN_TOPIC(TOPIC_CHANGE_BUTTONS, (TERM_BTN_LEN+4)*TERM_BTN_COUNT+1+4) + bufput_c(TOPICMARK_BUTTONS); + + bufput_utf8(TERM_BTN_COUNT); + + for (int i = 0; i < TERM_BTN_COUNT; i++) { + int len = (int) strlen(termconf_live.btn[i]); + memcpy(bb, termconf_live.btn[i], len); + bb += len; + remain -= len; + bufput_c('\x01'); + } + END_TOPIC + + BEGIN_TOPIC(TOPIC_INTERNAL, 45) + bufput_c(TOPICMARK_DEBUG); + // General flags + bufput_utf8( + (scr.insert_mode << 0) | + (cursor.conceal << 1) | + (cursor.auto_wrap << 2) | + (cursor.reverse_wrap << 3) | + (cursor.origin_mode << 4) | + (cursor_saved << 5) | + (state_backup.alternate_active << 6) + ); + bufput_utf8(cursor.attrs); + bufput_utf8(scr.vm0); + bufput_utf8(scr.vm1); + bufput_utf8(cursor.charsetN); + bufput_c(cursor.charset0); + bufput_c(cursor.charset1); + bufput_utf8(system_get_free_heap_size()); + bufput_utf8(term_active_clients); + END_TOPIC + + BEGIN_TOPIC(TOPIC_BELL, 1) + bufput_c(TOPICMARK_BELL); + END_TOPIC + + BEGIN_TOPIC(TOPIC_CHANGE_CURSOR, 13) + bufput_c(TOPICMARK_CURSOR); + bufput_utf8(cursor.y); + bufput_utf8(cursor.x); + bufput_utf8( + (cursor.hanging << 0) + ); + END_TOPIC + + if (ss->last_topic == TOPIC_CHANGE_CURSOR) { + // now we can begin any of the two screen sequences + + if (ss->topics & TOPIC_CHANGE_CONTENT_ALL) { + ss->current_topic = TOPIC_CHANGE_CONTENT_ALL; + } + if (ss->topics & TOPIC_CHANGE_CONTENT_PART) { + ss->current_topic = TOPIC_CHANGE_CONTENT_PART; + } + + if (ss->current_topic == 0) { + // no screen mode - wrap it up + goto ser_done; + } + + // start the screen section + } + } +#define INC_I() do { \ + i++; \ + if (ss->partial) {\ + if (i%W == 0) i += (ss->x_min);\ + else if (i%W > ss->x_max) i += (W - ss->x_max + ss->x_min - 1);\ + } \ + } while (0) + + // screen contents int i = ss->index; - while(i < W*H && remain > 12) { + if (i == ss->i_start) { + bufput_c(TOPICMARK_SCREEN); // desired update mode is in `ss->current_topic` + bufput_utf8(ss->y_min); // Y0 + bufput_utf8(ss->x_min); // X0 + bufput_utf8(ss->y_max - ss->y_min + 1); // height + bufput_utf8(ss->x_max - ss->x_min + 1); // width + ss->index = 0; + ss->lastBg = 0; + ss->lastFg = 0; + ss->lastLiveBg = 0; + ss->lastLiveFg = 0; + ss->lastAttrs = 0; + ss->lastCharLen = 0; + ss->lastSymbol = 0; + ss->first = 1; + } + while(i <= ss->i_max && remain > 12) { cell = cell0 = &screen[i]; - // Count how many times same as previous int repCnt = 0; - while (i < W*H - && cell->fg == ss->lastFg - && cell->bg == ss->lastBg - && cell->attrs == ss->lastAttrs - && cell->symbol == ss->lastSymbol) { - // Repeat - repCnt++; - cell = &screen[++i]; + + if (!ss->first) { + // Count how many times same as previous + while (i <= ss->i_max + && cell->fg == ss->lastFg + && cell->bg == ss->lastBg + && cell->attrs == ss->lastAttrs + && cell->symbol == ss->lastSymbol) { + // Repeat + repCnt++; + INC_I(); + cell = &screen[i]; // it can go outside the allocated memory here if we went over the top + } } if (repCnt == 0) { // No repeat - first occurrence - bool changeAttrs = cell0->attrs != ss->lastAttrs; - bool changeFg = cell0->fg != ss->lastFg; - bool changeBg = cell0->bg != ss->lastBg; - bool changeColors = changeFg && changeBg; + bool changeAttrs = ss->first || (cell0->attrs != ss->lastAttrs); + bool changeFg = (cell0->fg != ss->lastLiveFg) && (cell0->attrs & ATTR_FG); + bool changeBg = (cell0->bg != ss->lastLiveBg) && (cell0->attrs & ATTR_BG); + bool changeColors = ss->first || (changeFg && changeBg); Color fg, bg; + ss->first = false; // Reverse fg and bg if we're in global reverse mode fg = cell0->fg; @@ -1821,7 +2154,11 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) } if (changeAttrs) { - bufput_t_utf8(SEQ_TAG_ATTRS, cell0->attrs); + if (cell0->attrs) { + bufput_t_utf8(SEQ_TAG_ATTRS, cell0->attrs); + } else { + bufput_c(SEQ_TAG_ATTRS_0); + } } // copy the symbol, until first 0 or reached 4 bytes @@ -1837,31 +2174,46 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data) ss->lastFg = cell0->fg; ss->lastBg = cell0->bg; + if (cell0->attrs & ATTR_FG) ss->lastLiveFg = cell0->fg; + if (cell0->attrs & ATTR_BG) ss->lastLiveBg = cell0->bg; ss->lastAttrs = cell0->attrs; ss->lastSymbol = cell0->symbol; - i++; + INC_I(); } else { // last character was repeated repCnt times - bufput_t_utf8(SEQ_TAG_REPEAT, repCnt); + int savings = ss->lastCharLen*repCnt; + if (savings > 2) { + // Repeat count + bufput_t_utf8(SEQ_TAG_REPEAT, repCnt); + } else { + // repeat it manually + for(int k = 0; k < repCnt; k++) { + for (int j = 0; j < ss->lastCharLen; j++) { + bufput_c(ss->lastChar[j]); + } + } + } } } ss->index = i; + if (i >= ss->i_max) goto ser_done; + + // MORE TO WRITE... + bufput_c('\0'); // terminate the string + return HTTPD_CGI_MORE; + +ser_done: bufput_c('\0'); // terminate the string + return HTTPD_CGI_DONE; +} +//endregion #if 0 - printf("MSG: "); +printf("MSG: "); for (int j=0;j #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..622565a 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 @@ -52,6 +52,7 @@ void ICACHE_FLASH_ATTR UART_Init(void) // U0RXD PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0RXD_U); // U1TXD (GPIO2) PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK); @@ -78,8 +79,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 7b9a98f..db52518 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -28,6 +28,9 @@ #include "ansi_parser_callbacks.h" #include "wifimgr.h" #include "persist.h" +#include "ansi_parser.h" +#include "ascii.h" +#include "uart_buffer.h" #ifdef ESPFS_POS CgiUploadFlashDef uploadParams={ @@ -49,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) { @@ -58,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/Tx: %d/%d%c, Hp: %d", rxp, txp, '%', heap); cnt = 0; } } else { // report change - dbg("FH: %d (%s%d)", heap, cc, diff); + dbg("Rx/Tx: %d/%d%c, Hp: %d (%s%d)", rxp, txp, '%', heap, cc, diff); cnt = 0; } @@ -86,6 +96,7 @@ static ETSTimer prHeapTimer; //Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done. void ICACHE_FLASH_ATTR user_init(void) { + ansi_parser_inhibit = true; serialInitBase(); // Prevent WiFi starting and connecting by default @@ -93,13 +104,22 @@ void ICACHE_FLASH_ATTR user_init(void) wifi_station_set_auto_connect(false); wifi_set_opmode(NULL_MODE); // saves to flash if changed - this might avoid the current spike on startup? - printf("\r\n"); - banner("====== ESPTerm ======"); - banner_info("Firmware (c) Ondrej Hruska, 2017"); - banner_info(TERMINAL_GITHUB_REPO); - banner_info(""); - banner_info("Version "FIRMWARE_VERSION", built " __DATE__ " at " __TIME__ " " __TIMEZONE__); - printf("\r\n"); + u8 mac[6]; + wifi_get_macaddr(SOFTAP_IF, mac); + + banner_gap(); + banner("================ ESPTerm ================"); + banner_info(); + banner_info("Project by Ondrej Hruska, 2017"); + banner_info(); + banner_info(TERMINAL_GITHUB_REPO_NOPROTO); + banner_info(); + banner_info("Version "FW_VERSION" ("ESP_LANG"), code name "FW_CODENAME_QUOTED); + banner_info(" back-end #"GIT_HASH_BACKEND" front-end #"GIT_HASH_FRONTEND); + banner_info(" built "__DATE__" at "__TIME__" "__TIMEZONE__); + banner_info(); + banner_info("Device ID: %02X%02X%02X", mac[3], mac[4], mac[5]); + banner_gap(); ioInit(); @@ -111,11 +131,6 @@ void ICACHE_FLASH_ATTR user_init(void) espFsInit((void *) (webpages_espfs_start)); #endif -#if DEBUG_HEAP - // Heap use timer & blink - TIMER_START(&prHeapTimer, prHeapTimerCb, 1000, 1); -#endif - // do later (some functions do not work if called from user_init) TIMER_START(&userStartTimer, user_start, 10, 0); } @@ -127,10 +142,18 @@ static void ICACHE_FLASH_ATTR user_start(void *unused) captdnsInit(); httpdInit(routes, 80); + httpdSetName("ESPTerm " VERSION_STRING); + + ansi_parser_inhibit = false; // Print the CANCEL character to indicate the module has restarted // Critically important for client application if any kind of screen persistence / content re-use is needed - UART_WriteChar(UART0, 24, UART_TIMEOUT_US); // 0x18 - 24 - CAN + UART_WriteChar(UART0, CAN, UART_TIMEOUT_US); // 0x18 - 24 - CAN + +#if DEBUG_HEAP + // Heap use timer & blink + TIMER_START(&prHeapTimer, prHeapTimerCb, HEAP_TIMER_MS, 1); +#endif } // ---- unused funcs removed from sdk to save space --- diff --git a/user/utf8.c b/user/utf8.c index 3d4c693..b615279 100644 --- a/user/utf8.c +++ b/user/utf8.c @@ -160,8 +160,13 @@ unicode_cache_remove(UnicodeCacheRef ref) * @return number of bytes on success, 0 on failure (also produces U+FFFD, which uses 3 bytes) */ int ICACHE_FLASH_ATTR -utf8_encode(char *out, uint32_t utf) +utf8_encode(char *out, uint32_t utf, bool surrogateFix) { + // Skip the surrogate block (wtf, unicode???) + if (surrogateFix && utf >= 0xD800) { + utf += 0x800; + } + if (utf <= 0x7F) { // Plain ASCII out[0] = (char) utf; diff --git a/user/utf8.h b/user/utf8.h index c24bf07..675545a 100644 --- a/user/utf8.h +++ b/user/utf8.h @@ -63,9 +63,10 @@ bool unicode_cache_remove(UnicodeCacheRef ref); * * @param out - output buffer (min 4 characters), will be 0-terminated if shorten than 4 * @param utf - code point 0-0x10FFFF + * @param surrogateFix - add 0x800 to 0xD800-0xDFFF to avoid invalid code points * @return number of bytes on success, 0 on failure (also produces U+FFFD, which uses 3 bytes) */ -int utf8_encode(char *out, uint32_t utf); +int utf8_encode(char *out, uint32_t utf, bool surrogateFix); #if DEBUG_UTFCACHE #define utfc_warn warn diff --git a/user/version.h b/user/version.h index 6019923..1bd6e06 100644 --- a/user/version.h +++ b/user/version.h @@ -5,13 +5,26 @@ #ifndef ESP_VT100_FIRMWARE_VERSION_H #define ESP_VT100_FIRMWARE_VERSION_H +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + #define FW_V_MAJOR 2 -#define FW_V_MINOR 0 +#define FW_V_MINOR 1 #define FW_V_PATCH 0 +#define FW_V_SUFFIX "-beta3" +//#define FW_V_SUFFIX "" +#define FW_CODENAME "Anthill" // 2.1.0 +#define FW_CODENAME_QUOTED "\""FW_CODENAME"\"" + +#define FW_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) FW_V_SUFFIX + +#define VERSION_STRING FW_VERSION " " FW_CODENAME_QUOTED -#define FIRMWARE_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) #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_NOPROTO "github.com/espterm/espterm-firmware" +#define TERMINAL_GITHUB_REPO "https://"TERMINAL_GITHUB_REPO_NOPROTO + #define TERMINAL_GITHUB_REPO_FRONT "https://github.com/espterm/espterm-front-end" #endif //ESP_VT100_FIRMWARE_VERSION_H diff --git a/user/wifimgr.c b/user/wifimgr.c index f1acc8e..2588365 100644 --- a/user/wifimgr.c +++ b/user/wifimgr.c @@ -8,6 +8,22 @@ WiFiConfigBundle * const wificonf = &persist.current.wificonf; WiFiConfChangeFlags wifi_change_flags; +int ICACHE_FLASH_ATTR getStaIpAsString(char *buffer) +{ + WIFI_MODE x = wifi_get_opmode(); + STATION_STATUS connectStatus = wifi_station_get_connect_status(); + + if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP || wificonf->opmode == SOFTAP_MODE) { + strcpy(buffer, ""); + return 0; + } + else { + struct ip_info info; + wifi_get_ip_info(STATION_IF, &info); + return sprintf(buffer, IPSTR, GOOD_IP2STR(info.ip.addr)); + } +} + /** * Restore defaults in the WiFi config block. * This is to be called if the WiFi config is corrupted on startup, diff --git a/user/wifimgr.h b/user/wifimgr.h index 33c49bb..146c3c8 100644 --- a/user/wifimgr.h +++ b/user/wifimgr.h @@ -60,6 +60,8 @@ void wifimgr_restore_defaults(void); void wifimgr_apply_settings(void); +int getStaIpAsString(char *buffer); + #if DEBUG_WIFI #define wifi_warn warn #define wifi_dbg dbg