Compare commits

...
8 Commits
16 changed files with 670 additions and 576 deletions
+1
View File
@@ -14,5 +14,6 @@ eagle.app.sym
.idea/ .idea/
CMakeLists.txt CMakeLists.txt
cmake-build-debug/ cmake-build-debug/
.sass-cache
*.map *.map
+68 -37
View File
@@ -13,73 +13,104 @@ Those forks include improvements not yet available upstream.
This project aims to be a wireless terminal emulator that'll work with the likes of This project aims to be a wireless terminal emulator that'll work with the likes of
Arduino, AVR, PIC, STM8, STM32, mbed etc, anything with UART, even your USB-serial dongle will work. Arduino, AVR, PIC, STM8, STM32, mbed etc, anything with UART, even your USB-serial dongle will work.
Connect it to the master device via UART and use the terminal (on your PC or phone) for debug logging, Connect it to the master device via UART and use the terminal on the built-in web page for debug logging,
remote control etc. It works like a simple LCD screen, in a way. remote control etc. It works like a simple LCD screen, in a way.
It lets you make simple UI (manipulating the screen with ANSI sequences) and receive input from buttons on It lets you make simple UI (manipulating the screen with ANSI sequences) and receive input from buttons on
the webpage (and keyboard on PC). Touch input is a possibility but not currently implemented. the webpage (and keyboard on PC). Touch input is planned as well.
The screen size should be adjustable up to 25x80 (via a special control sequence) and uses 16 standard colors. The screen size is adjustable up to 25x80 (via a special control sequence) and uses 16 standard colors
(8 dark and 8 bright).
## Project status ## Project status
*Still far from finished and also there's like zero documentation, but you can give it a spin if you wish.* *Almost finished, still possibly buggy, but it looks promising. Most of the features are there now.*
- We have a working **2-way terminal** (UART->ESP->Browser and vice versa) with real-time update via websocket. - We have a working **2-way terminal** (UART->ESP->Browser and vice versa) with real-time update via websocket.
This means that what you type in the browser is sent to UART0 and what's received on UART0 is processed by the This means that what you type in the browser is sent to UART0 and what's received on UART0 is processed by the
ANSI parser and applied to the internal screen buffer. You'll also immediately see the changes in your browser. ANSI parser and applied to the internal screen buffer. You'll also immediately see the changes in your browser.
There's a filter in the way that discards garbage characters (like unicode and most ASCII outside 32-126).
For a quick test, try connecting the UART0 Rx and Tx to make a loopback interface. For a quick test, try connecting the UART0 Rx and Tx with a piece of wire to make a loopback interface.
You should then see what you type & can even try out the ANSI sequences. *NOTE: Use the bare module, not something like LoLin or NodeMCU with a FTDI, it'll interfere*.
You should then directly see what you type & can even try some ANSI sequences, right from the browser.
Arrow keys generate ANSI sequences, ESC sends literal ASCII code 27 - should be all you need to get started.
- All ANSI sequences that make sense, as well as control codes like Backspace and CR / LF are implemented. - All ANSI sequences that make sense, as well as control codes like Backspace and CR / LF are implemented.
Set colors with your usual `\e[31;1m` etc (see Wikipedia). `\e` is the ASCII code 27 (ESC). Set colors with your usual `\e[31;1m` etc (see Wikipedia). `\e` is the ASCII code 27 (ESC).
Arrow keys generate ANSI sequences, ESC sends literal ASCII code 27 etc. Almost everything can be input
straight from the browser.
- To resize the screen, send `\e]W<rows>;<cols>\a` (it's an OSC code, terminated by ST).
- Buttons pressed in the browser UI send ASCII codes 1..5. Mouse clicks also send events to the server, - Buttons pressed in the browser UI send ASCII codes 1..5. Mouse clicks also send events to the server,
but currently don't generate any output in the terminal. but currently don't generate any output in the terminal, I still hadn't decided on the best encoding.
- There is also currently no way to set up the WiFi, so it'll use whatever you configured the ESP to - By tapping the header on the terminal page, you'll open the WiFi config page. It's in essence the
in your previous project. This'll be addressed later. esphttpd's wifi config page, but re-styled and much improved. You can set AP SSID, channel, see the IP
address etc right there.
## Setting it all up ## Development
### Installation for development
- Clone this project with `--recursive`, or afterwards run `git submodule init` and `git submodule update`.
- Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/) and build it with - Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/) and build it with
`make toolchain esptool libhal STANDALONE=n`. Make sure the `xtensa-lx106-elf/bin` folder is on $PATH. `make toolchain esptool libhal STANDALONE=n`.
- Install [esptool](https://github.com/espressif/esptool) (it's in Arch community repo and on AUR, too)
Make sure the `xtensa-lx106-elf/bin` folder is on $PATH.
- Install [esptool](https://github.com/espressif/esptool) (it's in the Arch community repo and on AUR, too)
- Set up udev rules so you have access to ttyUSB0 without root, eg: - Set up udev rules so you have access to ttyUSB0 without root, eg:
``` ```
KERNEL=="tty[A-Z]*[0-9]*", GROUP="uucp", MODE="0666" KERNEL=="tty[A-Z]*[0-9]*", GROUP="uucp", MODE="0666"
``` ```
- Clone this project with `--recursive`, or afterwards run `git submodule init` and `git submodule update`.
## Development - Install Ragel if you wish to make modifications to the parser.
If not, comment out it's call in `build_parser.sh`. The `.rl` file is the actual source, the `.c` is generated.
The web resources are in `html_orig`. To prepare for a build, run `compress_html.sh`. - Install Ruby and then the `sass` package with `gem install sass` (or try some other implementation, such as
This should pack them and put in `html`. `sassc`)
If you're missing the compression tools, you can simply copy them there manually uncompressed and hope for the best. - Get the IoT SDK from one of:
serial comm is handled in `serial.c`, sockets etc in `user_main.c`. ```
ESP8266_NONOS_SDK_V2.0.0_16_08_10.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1690"
ESP8266_NONOS_SDK_V2.0.0_16_07_19.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1613"
ESP8266_NONOS_SDK_V1.5.4_16_05_20.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1469"
ESP8266_NONOS_SDK_V1.5.3_16_04_18.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1361"
ESP8266_NONOS_SDK_V1.5.2_16_01_29.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1079"
```
Get the IoT SDK from one of: It's tested with 1.5.2, 2.0.0 won't work without adjusting the build scripts. Any 1.5.x could be fine.
``` The `esp-open-sdk` Makefile could also download this for you with the right flags.
ESP8266_NONOS_SDK_V2.0.0_16_08_10.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1690"
ESP8266_NONOS_SDK_V2.0.0_16_07_19.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1613"
ESP8266_NONOS_SDK_V1.5.4_16_05_20.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1469"
ESP8266_NONOS_SDK_V1.5.3_16_04_18.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1361"
ESP8266_NONOS_SDK_V1.5.2_16_01_29.zip:
wget --content-disposition "http://bbs.espressif.com/download/file.php?id=1079"
```
It's tested with 1.5.2, 2.0.0 won't work without adjusting the build scripts. Any 1.5.x could be fine. - Make sure your `esphttpdconfig.mk` is set up properly - link to the SDK etc.
To flash, just run `make flash`. First make sure your `esphttpdconfig.mk` is set up properly - link to sdk etc. ### Web resources
The web resources are in `html_orig`. To prepare for a build, run `build_web.sh`, which packs them and
copies over to `html`. The compression and minification is handled by scripts in libesphttpd, specifically
it runs yuicompressor on js and css and gzip or heatshrink on the other files. The `html` folder is
then embedded in the firmware image.
It's kind of tricky to develop the web resources locally; you might want to try the "split image"
Makefile option, then you can flash just the html portion with `make htmlflash`. I haven't tried this.
### Flashing
The Makefile should automatically build the parser and web resources for you when you run `make`.
Sometimes it does not, particularly with `make -B`. Try just plain `make`. You can always run those
build scripts manually, too.
To flash, just run `make flash`.
+2
View File
@@ -11,6 +11,8 @@ cat $DD/chibi.js \
$DD/term.js \ $DD/term.js \
$DD/wifi.js > html/js/app.js $DD/wifi.js > html/js/app.js
sass --sourcemap=none html_orig/sass/app.scss html_orig/css/app.css
# No need to compress CSS and JS now, we run YUI on it later # No need to compress CSS and JS now, we run YUI on it later
cp html_orig/css/app.css html/css/app.css cp html_orig/css/app.css html/css/app.css
cp html_orig/term.html html/term.tpl cp html_orig/term.html html/term.tpl
+5 -3
View File
@@ -668,6 +668,10 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
.page-term h1 { .page-term h1 {
font-size: 1.42383em; } } font-size: 1.42383em; } }
.page-term #screen { .page-term #screen {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-family: monospace; font-family: monospace;
font-size: 16pt; font-size: 16pt;
white-space: nowrap; white-space: nowrap;
@@ -680,7 +684,7 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
cursor: pointer; } cursor: pointer; }
.page-term #screen span:hover { .page-term #screen span:hover {
outline: 1px solid rgba(255, 255, 255, 0.4); } outline: 1px solid rgba(255, 255, 255, 0.4); }
@media screen and (max-width: 1000px) { @media screen and (max-width: 544px) {
.page-term #screen span:hover { .page-term #screen span:hover {
outline: 0 none; } } outline: 0 none; } }
.page-term #buttons { .page-term #buttons {
@@ -697,5 +701,3 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
#termwrap { #termwrap {
text-align: center; } text-align: center; }
/*# sourceMappingURL=app.css.map */
+5 -3
View File
@@ -668,6 +668,10 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
.page-term h1 { .page-term h1 {
font-size: 1.42383em; } } font-size: 1.42383em; } }
.page-term #screen { .page-term #screen {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-family: monospace; font-family: monospace;
font-size: 16pt; font-size: 16pt;
white-space: nowrap; white-space: nowrap;
@@ -680,7 +684,7 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
cursor: pointer; } cursor: pointer; }
.page-term #screen span:hover { .page-term #screen span:hover {
outline: 1px solid rgba(255, 255, 255, 0.4); } outline: 1px solid rgba(255, 255, 255, 0.4); }
@media screen and (max-width: 1000px) { @media screen and (max-width: 544px) {
.page-term #screen span:hover { .page-term #screen span:hover {
outline: 0 none; } } outline: 0 none; } }
.page-term #buttons { .page-term #buttons {
@@ -697,5 +701,3 @@ input[type="number"], input[type="password"], input[type="text"], textarea, sele
#termwrap { #termwrap {
text-align: center; } text-align: center; }
/*# sourceMappingURL=app.css.map */
+3 -1
View File
@@ -7,6 +7,8 @@
} }
#screen { #screen {
@include noselect();
font-family: monospace; font-family: monospace;
font-size: 16pt; font-size: 16pt;
white-space: nowrap; white-space: nowrap;
@@ -21,7 +23,7 @@
cursor: pointer; cursor: pointer;
&:hover { &:hover {
outline: 1px solid rgba(#ffffff,0.4); outline: 1px solid rgba(#ffffff,0.4);
@include media($tablet-max) { @include media($phone) {
outline: 0 none; outline: 0 none;
} }
} }
+98 -274
View File
@@ -1,212 +1,16 @@
/* #line 1 "user/ansi_parser.rl" */ /* #line 1 "user/ansi_parser.rl" */
#include <esp8266.h> #include <esp8266.h>
#include "screen.h"
#include "ansi_parser.h" #include "ansi_parser.h"
// Max nr of CSI parameters
#define CSI_N_MAX 3
/**
* \brief Handle fully received CSI ANSI sequence
* \param leadchar - private range leading character, 0 if none
* \param params - array of CSI_N_MAX ints holding the numeric arguments
* \param keychar - the char terminating the sequence
*/
void ICACHE_FLASH_ATTR
handle_CSI(char leadchar, int *params, char keychar)
{
/*
Implemented codes (from Wikipedia)
CSI n A CUU Cursor Up
CSI n B CUD Cursor Down
CSI n C CUF Cursor Forward
CSI n D CUB Cursor Back
CSI n E CNL Cursor Next Line
CSI n F CPL Cursor Previous Line
CSI n G CHA Cursor Horizontal Absolute
CSI n ; m H CUP Cursor Position
CSI n J ED Erase Display
CSI n K EL Erase in Line
CSI n S SU Scroll Up
CSI n T SD Scroll Down
CSI n ; m f HVP Horizontal and Vertical Position
CSI n m SGR Select Graphic Rendition (Implemented only some)
CSI 6n DSR Device Status Report NOT IMPL
CSI s SCP Save Cursor Position
CSI u RCP Restore Cursor Position
CSI ?25l DECTCEM Hides the cursor
CSI ?25h DECTCEM Shows the cursor
*/
int n1 = params[0];
int n2 = params[1];
// int n3 = params[2];
// defaults
switch (keychar) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'S':
case 'T':
if (n1 == 0) n1 = 1;
break;
case 'H':
case 'f':
if (n1 == 0) n1 = 1;
if (n2 == 0) n2 = 1;
break;
case 'J':
case 'K':
if (n1 > 2) n1 = 0;
break;
}
switch (keychar) {
// CUU CUD CUF CUB
case 'A': screen_cursor_move(0, -n1); break;
case 'B': screen_cursor_move(0, n1); break;
case 'C': screen_cursor_move(n1, 0); break;
case 'D': screen_cursor_move(-n1, 0); break;
case 'E': // CNL
screen_cursor_move(0, n1);
screen_cursor_set_x(0);
break;
case 'F': // CPL
screen_cursor_move(0, -n1);
screen_cursor_set_x(0);
break;
// CHA
case 'G':
screen_cursor_set_x(n1 - 1); break; // 1-based
// SU, SD
case 'S': screen_scroll_up(n1); break;
case 'T': screen_scroll_down(n1); break;
// CUP,HVP
case 'H':
case 'f':
screen_cursor_set(n2-1, n1-1); break; // 1-based
case 'J': // ED
if (n1 == 0) {
screen_clear(CLEAR_TO_CURSOR);
} else if (n1 == 1) {
screen_clear(CLEAR_FROM_CURSOR);
} else {
screen_clear(CLEAR_ALL);
screen_cursor_set(0, 0);
}
break;
case 'K': // EL
if (n1 == 0) {
screen_clear_line(CLEAR_TO_CURSOR);
} else if (n1 == 1) {
screen_clear_line(CLEAR_FROM_CURSOR);
} else {
screen_clear_line(CLEAR_ALL);
screen_cursor_set_x(0);
}
break;
// SCP, RCP
case 's': screen_cursor_save(); break;
case 'u': screen_cursor_restore(); break;
// DECTCEM cursor show hide
case 'l':
if (leadchar == '?' && n1 == 25) {
screen_cursor_enable(1);
}
break;
case 'h':
if (leadchar == '?' && n1 == 25) {
screen_cursor_enable(0);
}
break;
case 'm': // SGR
// iterate arguments
for (int i = 0; i < CSI_N_MAX; i++) {
int n = params[i];
if (i == 0 && n == 0) { // reset SGR
screen_set_fg(7);
screen_set_bg(0);
break; // cannot combine reset with others
}
else if (n >= 30 && n <= 37) screen_set_fg(n-30); // ANSI normal fg
else if (n >= 40 && n <= 47) screen_set_bg(n-40); // ANSI normal bg
else if (n == 39) screen_set_fg(7); // default fg
else if (n == 49) screen_set_bg(0); // default bg
else if (n == 7) screen_inverse(1); // inverse
else if (n == 27) screen_inverse(0); // positive
else if (n == 1) screen_set_bright_fg(); // ANSI bold = bright fg
else if (n >= 90 && n <= 97) screen_set_fg(n-90+8); // AIX bright fg
else if (n >= 100 && n <= 107) screen_set_bg(n-100+8); // AIX bright bg
}
break;
}
}
/**
* \brief Handle a request to reset the display device
*/
void ICACHE_FLASH_ATTR
handle_RESET_cmd(void)
{
screen_reset();
}
/**
* \brief Handle a received plain character
* \param c - the character
*/
void ICACHE_FLASH_ATTR
handle_plainchar(char c)
{
screen_putchar(c);
}
void ICACHE_FLASH_ATTR
handle_OSC_FactoryReset(void)
{
info("OSC: Factory reset");
warn("NOT IMPLEMENTED");
// TODO
}
void ICACHE_FLASH_ATTR
handle_OSC_SetScreenSize(int rows, int cols)
{
info("OSC: Set screen size to %d x %d", rows, cols);
screen_resize(rows, cols);
}
/* Ragel constants block */ /* Ragel constants block */
/* #line 206 "user/ansi_parser.c" */ /* #line 9 "user/ansi_parser.c" */
static const char _ansi_actions[] = { static const char _ansi_actions[] = {
0, 1, 0, 1, 1, 1, 2, 1, 0, 1, 0, 1, 1, 1, 2, 1,
3, 1, 4, 1, 5, 1, 6, 1, 3, 1, 4, 1, 5, 1, 6, 1,
7, 1, 8, 1, 9, 1, 10 7, 1, 8, 1, 9, 1, 10, 1,
11, 1, 12
}; };
static const char _ansi_eof_actions[] = { static const char _ansi_eof_actions[] = {
@@ -224,7 +28,7 @@ static const int ansi_en_OSC_body = 5;
static const int ansi_en_main = 1; static const int ansi_en_main = 1;
/* #line 205 "user/ansi_parser.rl" */ /* #line 8 "user/ansi_parser.rl" */
/** /**
@@ -261,17 +65,17 @@ ansi_parser(const char *newdata, size_t len)
// Init Ragel on the first run // Init Ragel on the first run
if (cs == -1) { if (cs == -1) {
/* #line 265 "user/ansi_parser.c" */ /* #line 69 "user/ansi_parser.c" */
{ {
cs = ansi_start; cs = ansi_start;
} }
/* #line 241 "user/ansi_parser.rl" */ /* #line 44 "user/ansi_parser.rl" */
} }
// The parser // The parser
/* #line 275 "user/ansi_parser.c" */ /* #line 79 "user/ansi_parser.c" */
{ {
const char *_acts; const char *_acts;
unsigned int _nacts; unsigned int _nacts;
@@ -288,9 +92,11 @@ case 1:
goto tr0; goto tr0;
case 2: case 2:
switch( (*p) ) { switch( (*p) ) {
case 91: goto tr3; case 55: goto tr3;
case 93: goto tr4; case 56: goto tr4;
case 99: goto tr5; case 91: goto tr5;
case 93: goto tr6;
case 99: goto tr7;
} }
goto tr2; goto tr2;
case 0: case 0:
@@ -301,131 +107,135 @@ case 14:
goto tr0; goto tr0;
case 3: case 3:
if ( (*p) == 59 ) if ( (*p) == 59 )
goto tr8; goto tr10;
if ( (*p) < 60 ) { if ( (*p) < 60 ) {
if ( (*p) > 47 ) { if ( (*p) > 47 ) {
if ( 48 <= (*p) && (*p) <= 57 ) if ( 48 <= (*p) && (*p) <= 57 )
goto tr7; goto tr9;
} else if ( (*p) >= 32 ) } else if ( (*p) >= 32 )
goto tr6; goto tr8;
} else if ( (*p) > 64 ) { } else if ( (*p) > 64 ) {
if ( (*p) > 90 ) { if ( (*p) > 90 ) {
if ( 97 <= (*p) && (*p) <= 122 ) if ( 97 <= (*p) && (*p) <= 122 )
goto tr9; goto tr11;
} else if ( (*p) >= 65 ) } else if ( (*p) >= 65 )
goto tr9; goto tr11;
} else } else
goto tr6; goto tr8;
goto tr2; goto tr2;
case 4: case 4:
if ( (*p) == 59 ) if ( (*p) == 59 )
goto tr8; goto tr10;
if ( (*p) < 65 ) { if ( (*p) < 65 ) {
if ( 48 <= (*p) && (*p) <= 57 ) if ( 48 <= (*p) && (*p) <= 57 )
goto tr7; goto tr9;
} else if ( (*p) > 90 ) { } else if ( (*p) > 90 ) {
if ( 97 <= (*p) && (*p) <= 122 ) if ( 97 <= (*p) && (*p) <= 122 )
goto tr9; goto tr11;
} else } else
goto tr9; goto tr11;
goto tr2; goto tr2;
case 15: case 15:
goto tr2; goto tr2;
case 5: case 5:
switch( (*p) ) { switch( (*p) ) {
case 70: goto tr10; case 70: goto tr12;
case 87: goto tr11; case 87: goto tr13;
} }
goto tr2; goto tr2;
case 6: case 6:
if ( (*p) == 82 ) if ( (*p) == 82 )
goto tr12; goto tr14;
goto tr2; goto tr2;
case 7: case 7:
switch( (*p) ) { switch( (*p) ) {
case 7: goto tr13; case 7: goto tr15;
case 27: goto tr14; case 27: goto tr16;
} }
goto tr2; goto tr2;
case 16: case 16:
goto tr2; goto tr2;
case 8: case 8:
if ( (*p) == 92 ) if ( (*p) == 92 )
goto tr13; goto tr15;
goto tr2; goto tr2;
case 9: case 9:
if ( 48 <= (*p) && (*p) <= 57 ) if ( 48 <= (*p) && (*p) <= 57 )
goto tr15; goto tr17;
goto tr2; goto tr2;
case 10: case 10:
if ( (*p) == 59 ) if ( (*p) == 59 )
goto tr16; goto tr18;
if ( 48 <= (*p) && (*p) <= 57 ) if ( 48 <= (*p) && (*p) <= 57 )
goto tr15; goto tr17;
goto tr2; goto tr2;
case 11: case 11:
if ( 48 <= (*p) && (*p) <= 57 ) if ( 48 <= (*p) && (*p) <= 57 )
goto tr17; goto tr19;
goto tr2; goto tr2;
case 12: case 12:
switch( (*p) ) { switch( (*p) ) {
case 7: goto tr18; case 7: goto tr20;
case 27: goto tr19; case 27: goto tr21;
} }
if ( 48 <= (*p) && (*p) <= 57 ) if ( 48 <= (*p) && (*p) <= 57 )
goto tr17; goto tr19;
goto tr2; goto tr2;
case 13: case 13:
if ( (*p) == 92 ) if ( (*p) == 92 )
goto tr18; goto tr20;
goto tr2; goto tr2;
} }
tr2: cs = 0; goto f0; tr2: cs = 0; goto f0;
tr0: cs = 1; goto f1; tr0: cs = 1; goto f1;
tr1: cs = 2; goto _again; tr1: cs = 2; goto _again;
tr6: cs = 4; goto f5;
tr7: cs = 4; goto f6;
tr8: cs = 4; goto f7; tr8: cs = 4; goto f7;
tr10: cs = 6; goto _again; tr9: cs = 4; goto f8;
tr12: cs = 7; goto _again; tr10: cs = 4; goto f9;
tr14: cs = 8; goto _again; tr12: cs = 6; goto _again;
tr11: cs = 9; goto _again; tr14: cs = 7; goto _again;
tr15: cs = 10; goto f6; tr16: cs = 8; goto _again;
tr16: cs = 11; goto f7; tr13: cs = 9; goto _again;
tr17: cs = 12; goto f6; tr17: cs = 10; goto f8;
tr19: cs = 13; goto _again; tr18: cs = 11; goto f9;
tr19: cs = 12; goto f8;
tr21: cs = 13; goto _again;
tr3: cs = 14; goto f2; tr3: cs = 14; goto f2;
tr4: cs = 14; goto f3; tr4: cs = 14; goto f3;
tr5: cs = 14; goto f4; tr5: cs = 14; goto f4;
tr9: cs = 15; goto f8; tr6: cs = 14; goto f5;
tr13: cs = 16; goto f9; tr7: cs = 14; goto f6;
tr18: cs = 16; goto f10; tr11: cs = 15; goto f10;
tr15: cs = 16; goto f11;
tr20: cs = 16; goto f12;
f1: _acts = _ansi_actions + 1; goto execFuncs; f1: _acts = _ansi_actions + 1; goto execFuncs;
f2: _acts = _ansi_actions + 3; goto execFuncs; f4: _acts = _ansi_actions + 3; goto execFuncs;
f5: _acts = _ansi_actions + 5; goto execFuncs; f7: _acts = _ansi_actions + 5; goto execFuncs;
f6: _acts = _ansi_actions + 7; goto execFuncs; f8: _acts = _ansi_actions + 7; goto execFuncs;
f7: _acts = _ansi_actions + 9; goto execFuncs; f9: _acts = _ansi_actions + 9; goto execFuncs;
f8: _acts = _ansi_actions + 11; goto execFuncs; f10: _acts = _ansi_actions + 11; goto execFuncs;
f0: _acts = _ansi_actions + 13; goto execFuncs; f0: _acts = _ansi_actions + 13; goto execFuncs;
f3: _acts = _ansi_actions + 15; goto execFuncs; f5: _acts = _ansi_actions + 15; goto execFuncs;
f9: _acts = _ansi_actions + 17; goto execFuncs; f11: _acts = _ansi_actions + 17; goto execFuncs;
f10: _acts = _ansi_actions + 19; goto execFuncs; f12: _acts = _ansi_actions + 19; goto execFuncs;
f4: _acts = _ansi_actions + 21; goto execFuncs; f6: _acts = _ansi_actions + 21; goto execFuncs;
f2: _acts = _ansi_actions + 23; goto execFuncs;
f3: _acts = _ansi_actions + 25; goto execFuncs;
execFuncs: execFuncs:
_nacts = *_acts++; _nacts = *_acts++;
while ( _nacts-- > 0 ) { while ( _nacts-- > 0 ) {
switch ( *_acts++ ) { switch ( *_acts++ ) {
case 0: case 0:
/* #line 251 "user/ansi_parser.rl" */ /* #line 54 "user/ansi_parser.rl" */
{ {
handle_plainchar((*p)); apars_handle_plainchar((*p));
} }
break; break;
case 1: case 1:
/* #line 258 "user/ansi_parser.rl" */ /* #line 61 "user/ansi_parser.rl" */
{ {
/* Reset the CSI builder */ /* Reset the CSI builder */
csi_leading = csi_char = 0; csi_leading = csi_char = 0;
@@ -440,13 +250,13 @@ execFuncs:
} }
break; break;
case 2: case 2:
/* #line 271 "user/ansi_parser.rl" */ /* #line 74 "user/ansi_parser.rl" */
{ {
csi_leading = (*p); csi_leading = (*p);
} }
break; break;
case 3: case 3:
/* #line 275 "user/ansi_parser.rl" */ /* #line 78 "user/ansi_parser.rl" */
{ {
/* x10 + digit */ /* x10 + digit */
if (csi_ni < CSI_N_MAX) { if (csi_ni < CSI_N_MAX) {
@@ -455,30 +265,30 @@ execFuncs:
} }
break; break;
case 4: case 4:
/* #line 282 "user/ansi_parser.rl" */ /* #line 85 "user/ansi_parser.rl" */
{ {
csi_ni++; csi_ni++;
} }
break; break;
case 5: case 5:
/* #line 286 "user/ansi_parser.rl" */ /* #line 89 "user/ansi_parser.rl" */
{ {
csi_char = (*p); csi_char = (*p);
handle_CSI(csi_leading, csi_n, csi_char); apars_handle_CSI(csi_leading, csi_n, csi_char);
{cs = 1; goto _again;} {cs = 1; goto _again;}
} }
break; break;
case 6: case 6:
/* #line 294 "user/ansi_parser.rl" */ /* #line 97 "user/ansi_parser.rl" */
{ {
warn("Invalid escape sequence discarded."); apars_handle_badseq();
{cs = 1; goto _again;} {cs = 1; goto _again;}
} }
break; break;
case 7: case 7:
/* #line 311 "user/ansi_parser.rl" */ /* #line 114 "user/ansi_parser.rl" */
{ {
csi_ni = 0; csi_ni = 0;
@@ -491,29 +301,43 @@ execFuncs:
} }
break; break;
case 8: case 8:
/* #line 322 "user/ansi_parser.rl" */ /* #line 125 "user/ansi_parser.rl" */
{ {
handle_OSC_FactoryReset(); apars_handle_OSC_FactoryReset();
{cs = 1; goto _again;} {cs = 1; goto _again;}
} }
break; break;
case 9: case 9:
/* #line 327 "user/ansi_parser.rl" */ /* #line 130 "user/ansi_parser.rl" */
{ {
handle_OSC_SetScreenSize(csi_n[0], csi_n[1]); apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]);
{cs = 1; goto _again;} {cs = 1; goto _again;}
} }
break; break;
case 10: case 10:
/* #line 338 "user/ansi_parser.rl" */ /* #line 141 "user/ansi_parser.rl" */
{ {
// Reset screen // Reset screen
handle_RESET_cmd(); apars_handle_RESET_cmd();
{cs = 1; goto _again;} {cs = 1; goto _again;}
} }
break; break;
/* #line 517 "user/ansi_parser.c" */ case 11:
/* #line 147 "user/ansi_parser.rl" */
{
apars_handle_saveCursorAttrs();
{cs = 1; goto _again;}
}
break;
case 12:
/* #line 152 "user/ansi_parser.rl" */
{
apars_handle_restoreCursorAttrs();
{cs = 1; goto _again;}
}
break;
/* #line 341 "user/ansi_parser.c" */
} }
} }
goto _again; goto _again;
@@ -531,13 +355,13 @@ _again:
while ( __nacts-- > 0 ) { while ( __nacts-- > 0 ) {
switch ( *__acts++ ) { switch ( *__acts++ ) {
case 6: case 6:
/* #line 294 "user/ansi_parser.rl" */ /* #line 97 "user/ansi_parser.rl" */
{ {
warn("Invalid escape sequence discarded."); apars_handle_badseq();
{cs = 1; goto _again;} {cs = 1; goto _again;}
} }
break; break;
/* #line 541 "user/ansi_parser.c" */ /* #line 365 "user/ansi_parser.c" */
} }
} }
} }
@@ -545,6 +369,6 @@ _again:
_out: {} _out: {}
} }
/* #line 356 "user/ansi_parser.rl" */ /* #line 171 "user/ansi_parser.rl" */
} }
+9
View File
@@ -6,6 +6,15 @@
// Max nr of CSI parameters // Max nr of CSI parameters
#define CSI_N_MAX 3 #define CSI_N_MAX 3
extern void apars_handle_badseq(void);
extern void apars_handle_CSI(char leadchar, int *params, char keychar);
extern void apars_handle_RESET_cmd(void);
extern void apars_handle_plainchar(char c);
extern void apars_handle_OSC_FactoryReset(void);
extern void apars_handle_OSC_SetScreenSize(int rows, int cols);
extern void apars_handle_saveCursorAttrs(void);
extern void apars_handle_restoreCursorAttrs(void);
/** /**
* \brief Linear ANSI chars stream parser * \brief Linear ANSI chars stream parser
* *
+19 -204
View File
@@ -1,203 +1,6 @@
#include <esp8266.h> #include <esp8266.h>
#include "screen.h"
#include "ansi_parser.h" #include "ansi_parser.h"
// Max nr of CSI parameters
#define CSI_N_MAX 3
/**
* \brief Handle fully received CSI ANSI sequence
* \param leadchar - private range leading character, 0 if none
* \param params - array of CSI_N_MAX ints holding the numeric arguments
* \param keychar - the char terminating the sequence
*/
void ICACHE_FLASH_ATTR
handle_CSI(char leadchar, int *params, char keychar)
{
/*
Implemented codes (from Wikipedia)
CSI n A CUU Cursor Up
CSI n B CUD Cursor Down
CSI n C CUF Cursor Forward
CSI n D CUB Cursor Back
CSI n E CNL Cursor Next Line
CSI n F CPL Cursor Previous Line
CSI n G CHA Cursor Horizontal Absolute
CSI n ; m H CUP Cursor Position
CSI n J ED Erase Display
CSI n K EL Erase in Line
CSI n S SU Scroll Up
CSI n T SD Scroll Down
CSI n ; m f HVP Horizontal and Vertical Position
CSI n m SGR Select Graphic Rendition (Implemented only some)
CSI 6n DSR Device Status Report NOT IMPL
CSI s SCP Save Cursor Position
CSI u RCP Restore Cursor Position
CSI ?25l DECTCEM Hides the cursor
CSI ?25h DECTCEM Shows the cursor
*/
int n1 = params[0];
int n2 = params[1];
// int n3 = params[2];
// defaults
switch (keychar) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'S':
case 'T':
if (n1 == 0) n1 = 1;
break;
case 'H':
case 'f':
if (n1 == 0) n1 = 1;
if (n2 == 0) n2 = 1;
break;
case 'J':
case 'K':
if (n1 > 2) n1 = 0;
break;
}
switch (keychar) {
// CUU CUD CUF CUB
case 'A': screen_cursor_move(0, -n1); break;
case 'B': screen_cursor_move(0, n1); break;
case 'C': screen_cursor_move(n1, 0); break;
case 'D': screen_cursor_move(-n1, 0); break;
case 'E': // CNL
screen_cursor_move(0, n1);
screen_cursor_set_x(0);
break;
case 'F': // CPL
screen_cursor_move(0, -n1);
screen_cursor_set_x(0);
break;
// CHA
case 'G':
screen_cursor_set_x(n1 - 1); break; // 1-based
// SU, SD
case 'S': screen_scroll_up(n1); break;
case 'T': screen_scroll_down(n1); break;
// CUP,HVP
case 'H':
case 'f':
screen_cursor_set(n2-1, n1-1); break; // 1-based
case 'J': // ED
if (n1 == 0) {
screen_clear(CLEAR_TO_CURSOR);
} else if (n1 == 1) {
screen_clear(CLEAR_FROM_CURSOR);
} else {
screen_clear(CLEAR_ALL);
screen_cursor_set(0, 0);
}
break;
case 'K': // EL
if (n1 == 0) {
screen_clear_line(CLEAR_TO_CURSOR);
} else if (n1 == 1) {
screen_clear_line(CLEAR_FROM_CURSOR);
} else {
screen_clear_line(CLEAR_ALL);
screen_cursor_set_x(0);
}
break;
// SCP, RCP
case 's': screen_cursor_save(); break;
case 'u': screen_cursor_restore(); break;
// DECTCEM cursor show hide
case 'l':
if (leadchar == '?' && n1 == 25) {
screen_cursor_enable(1);
}
break;
case 'h':
if (leadchar == '?' && n1 == 25) {
screen_cursor_enable(0);
}
break;
case 'm': // SGR
// iterate arguments
for (int i = 0; i < CSI_N_MAX; i++) {
int n = params[i];
if (i == 0 && n == 0) { // reset SGR
screen_set_fg(7);
screen_set_bg(0);
break; // cannot combine reset with others
}
else if (n >= 30 && n <= 37) screen_set_fg(n-30); // ANSI normal fg
else if (n >= 40 && n <= 47) screen_set_bg(n-40); // ANSI normal bg
else if (n == 39) screen_set_fg(7); // default fg
else if (n == 49) screen_set_bg(0); // default bg
else if (n == 7) screen_inverse(1); // inverse
else if (n == 27) screen_inverse(0); // positive
else if (n == 1) screen_set_bright_fg(); // ANSI bold = bright fg
else if (n >= 90 && n <= 97) screen_set_fg(n-90+8); // AIX bright fg
else if (n >= 100 && n <= 107) screen_set_bg(n-100+8); // AIX bright bg
}
break;
}
}
/**
* \brief Handle a request to reset the display device
*/
void ICACHE_FLASH_ATTR
handle_RESET_cmd(void)
{
screen_reset();
}
/**
* \brief Handle a received plain character
* \param c - the character
*/
void ICACHE_FLASH_ATTR
handle_plainchar(char c)
{
screen_putchar(c);
}
void ICACHE_FLASH_ATTR
handle_OSC_FactoryReset(void)
{
info("OSC: Factory reset");
warn("NOT IMPLEMENTED");
// TODO
}
void ICACHE_FLASH_ATTR
handle_OSC_SetScreenSize(int rows, int cols)
{
info("OSC: Set screen size to %d x %d", rows, cols);
screen_resize(rows, cols);
}
/* Ragel constants block */ /* Ragel constants block */
%%{ %%{
machine ansi; machine ansi;
@@ -249,7 +52,7 @@ ansi_parser(const char *newdata, size_t len)
# --- Regular characters to be printed --- # --- Regular characters to be printed ---
action plain_char { action plain_char {
handle_plainchar(fc); apars_handle_plainchar(fc);
} }
# --- CSI CSI commands (Select Graphic Rendition) --- # --- CSI CSI commands (Select Graphic Rendition) ---
@@ -286,13 +89,13 @@ ansi_parser(const char *newdata, size_t len)
action CSI_end { action CSI_end {
csi_char = fc; csi_char = fc;
handle_CSI(csi_leading, csi_n, csi_char); apars_handle_CSI(csi_leading, csi_n, csi_char);
fgoto main; fgoto main;
} }
action errBadSeq { action errBadSeq {
warn("Invalid escape sequence discarded."); apars_handle_badseq();
fgoto main; fgoto main;
} }
@@ -320,12 +123,12 @@ ansi_parser(const char *newdata, size_t len)
} }
action OSC_fr { action OSC_fr {
handle_OSC_FactoryReset(); apars_handle_OSC_FactoryReset();
fgoto main; fgoto main;
} }
action OSC_resize { action OSC_resize {
handle_OSC_SetScreenSize(csi_n[0], csi_n[1]); apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]);
fgoto main; fgoto main;
} }
@@ -337,7 +140,17 @@ ansi_parser(const char *newdata, size_t len)
action RESET_cmd { action RESET_cmd {
// Reset screen // Reset screen
handle_RESET_cmd(); apars_handle_RESET_cmd();
fgoto main;
}
action CSI_SaveCursorAttrs {
apars_handle_saveCursorAttrs();
fgoto main;
}
action CSI_RestoreCursorAttrs {
apars_handle_restoreCursorAttrs();
fgoto main; fgoto main;
} }
@@ -348,7 +161,9 @@ ansi_parser(const char *newdata, size_t len)
(NOESC @plain_char)* ESC ( (NOESC @plain_char)* ESC (
'[' @CSI_start | '[' @CSI_start |
']' @OSC_start | ']' @OSC_start |
'c' @RESET_cmd 'c' @RESET_cmd |
'7' @CSI_SaveCursorAttrs |
'8' @CSI_RestoreCursorAttrs
) )
)+ $!errBadSeq; )+ $!errBadSeq;
+281
View File
@@ -0,0 +1,281 @@
/**
* ANSI code parser callbacks that do the actual work.
* Split from the parser itself for easier editing without
* having to deal with Ragel.
*/
#include <esp8266.h>
#include "ansi_parser_callbacks.h"
#include "screen.h"
#include "ansi_parser.h"
#include "uart_driver.h"
/**
* Handle a received plain character
*/
void ICACHE_FLASH_ATTR
apars_handle_plainchar(char c)
{
screen_putchar(c);
}
/**
* Bad sequence received, show warning
*/
void apars_handle_badseq(void)
{
warn("Invalid escape sequence discarded.");
}
/**
* \brief Handle fully received CSI ANSI sequence
* \param leadchar - private range leading character, 0 if none
* \param params - array of CSI_N_MAX ints holding the numeric arguments
* \param keychar - the char terminating the sequence
*/
void ICACHE_FLASH_ATTR
apars_handle_CSI(char leadchar, int *params, char keychar)
{
/*
Implemented codes (from Wikipedia)
CSI n A CUU Cursor Up
CSI n B CUD Cursor Down
CSI n C CUF Cursor Forward
CSI n D CUB Cursor Back
CSI n E CNL Cursor Next Line
CSI n F CPL Cursor Previous Line
CSI n G CHA Cursor Horizontal Absolute
CSI n ; m H CUP Cursor Position
CSI n J ED Erase Display
CSI n K EL Erase in Line
CSI n S SU Scroll Up
CSI n T SD Scroll Down
CSI n ; m f HVP Horizontal and Vertical Position
CSI n m SGR Select Graphic Rendition (Implemented only some)
CSI 6n DSR Device Status Report
CSI s SCP Save Cursor Position
CSI u RCP Restore Cursor Position
CSI ?25l DECTCEM Hides the cursor
CSI ?25h DECTCEM Shows the cursor
*/
int n1 = params[0];
int n2 = params[1];
// int n3 = params[2];
// defaults
switch (keychar) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'S':
case 'T':
if (n1 == 0) n1 = 1;
break;
case 'H':
case 'f':
if (n1 == 0) n1 = 1;
if (n2 == 0) n2 = 1;
break;
case 'J':
case 'K':
if (n1 > 2) n1 = 0;
break;
}
switch (keychar) {
// CUU CUD CUF CUB
case 'A': screen_cursor_move(-n1, 0); break;
case 'B': screen_cursor_move(n1, 0); break;
case 'C': screen_cursor_move(0, n1); break;
case 'D': screen_cursor_move(0, -n1); break;
case 'E': // CNL
screen_cursor_move(n1, 0);
screen_cursor_set_x(0);
break;
case 'F': // CPL
screen_cursor_move(-n1, 0);
screen_cursor_set_x(0);
break;
// CHA
case 'G':
screen_cursor_set_x(n1 - 1); break; // 1-based
// SU, SD
case 'S': screen_scroll_up(n1); break;
case 'T': screen_scroll_down(n1); break;
// CUP,HVP
case 'H':
case 'f':
screen_cursor_set(n1-1, n2-1); break; // 1-based
case 'J': // ED
if (n1 == 0) {
screen_clear(CLEAR_TO_CURSOR);
} else if (n1 == 1) {
screen_clear(CLEAR_FROM_CURSOR);
} else {
screen_clear(CLEAR_ALL);
screen_cursor_set(0, 0);
}
break;
case 'K': // EL
if (n1 == 0) {
screen_clear_line(CLEAR_TO_CURSOR);
} else if (n1 == 1) {
screen_clear_line(CLEAR_FROM_CURSOR);
} else {
screen_clear_line(CLEAR_ALL);
screen_cursor_set_x(0);
}
break;
// SCP, RCP
case 's': screen_cursor_save(0); break;
case 'u': screen_cursor_restore(0); break;
case 'n':
if (n1 == 6) {
// Query cursor position
char buf[20];
int x, y;
screen_cursor_get(&y, &x);
sprintf(buf, "\033[%d;%dR", y+1, x+1);
UART_WriteString(UART0, buf, UART_TIMEOUT_US);
}
else if (n1 == 5) {
// Query device status - reply "Device is OK"
UART_WriteString(UART0, "\0330n", UART_TIMEOUT_US);
}
break;
// DECTCEM cursor show hide
case 'l':
if (leadchar == '?' && n1 == 25) {
screen_cursor_enable(1);
}
break;
case 'h':
if (leadchar == '?' && n1 == 25) {
screen_cursor_enable(0);
}
break;
case 'm': // SGR
// iterate arguments
for (int i = 0; i < CSI_N_MAX; i++) {
int n = params[i];
if (i == 0 && n == 0) { // reset SGR
screen_set_fg(7);
screen_set_bg(0);
break; // cannot combine reset with others
}
else if (n >= 30 && n <= 37) screen_set_fg(n-30); // ANSI normal fg
else if (n >= 40 && n <= 47) screen_set_bg(n-40); // ANSI normal bg
else if (n == 39) screen_set_fg(7); // default fg
else if (n == 49) screen_set_bg(0); // default bg
else if (n == 7) screen_inverse(1); // inverse
else if (n == 27) screen_inverse(0); // positive
else if (n == 1) screen_set_bright_fg(); // ANSI bold = bright fg
else if (n >= 90 && n <= 97) screen_set_fg(n-90+8); // AIX bright fg
else if (n >= 100 && n <= 107) screen_set_bg(n-100+8); // AIX bright bg
}
break;
}
}
void ICACHE_FLASH_ATTR apars_handle_saveCursorAttrs(void)
{
screen_cursor_save(1);
}
void ICACHE_FLASH_ATTR apars_handle_restoreCursorAttrs(void)
{
screen_cursor_restore(1);
}
/**
* \brief Handle a request to reset the display device
*/
void ICACHE_FLASH_ATTR
apars_handle_RESET_cmd(void)
{
// XXX maybe user wanted to reset the module instead?
screen_reset();
}
/**
* Handle a factory reset request
*/
void ICACHE_FLASH_ATTR
apars_handle_OSC_FactoryReset(void)
{
info("OSC: Factory reset");
// Send acknowledgement message to UART0
// User is performing this manually, so we can just print it as string
UART_WriteString(UART0, "\r\nFACTORY RESET\r\n", UART_TIMEOUT_US);
// Disconnect from AP if connected
int opmode = wifi_get_opmode();
if (opmode != SOFTAP_MODE) {
wifi_station_disconnect();
}
// Both must be enabled so we can manipulate their settings
wifi_set_opmode(STATIONAP_MODE);
// --- AP config ---
struct softap_config apconf;
wifi_softap_get_config(&apconf);
apconf.authmode=AUTH_OPEN; // Disable access protection
apconf.channel=1; // Reset channel; user may have set bad channel in the UI
// generate unique AP name
u8 mac[6];
wifi_get_macaddr(SOFTAP_IF, mac);
sprintf((char*)apconf.ssid, "TERM-%02X%02X%02X", mac[3], mac[4], mac[5]);
apconf.ssid_len = (u8)strlen((char*)apconf.ssid);
// --- Station ---
struct station_config staconf;
wifi_station_get_config(&staconf);
// clear info about SSID
staconf.ssid[0]=0;
staconf.bssid_set=0;
staconf.password[0]=0;
wifi_softap_set_config(&apconf);
wifi_station_set_config(&staconf);
UART_WriteString(UART0, "Factory Reset complete, device reset.\r\n\r\n", UART_TIMEOUT_US);
// Reboot to clean STA+AP mode with Channel 1 & reset AP SSID.
system_restart();
}
/**
* Handle a screen resize request
*/
void ICACHE_FLASH_ATTR
apars_handle_OSC_SetScreenSize(int rows, int cols)
{
info("OSC: Set screen size to %d x %d", rows, cols);
screen_resize(rows, cols);
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef ANSI_PARSER_CALLBACKS_H
#define ANSI_PARSER_CALLBACKS_H
void apars_handle_badseq(void);
void apars_handle_CSI(char leadchar, int *params, char keychar);
void apars_handle_RESET_cmd(void);
void apars_handle_plainchar(char c);
void apars_handle_OSC_FactoryReset(void);
void apars_handle_OSC_SetScreenSize(int rows, int cols);
void apars_handle_saveCursorAttrs(void);
void apars_handle_restoreCursorAttrs(void);
#endif //ESP_VT100_FIRMWARE_ANSI_PARSER_CALLBACKS_H
+55 -12
View File
@@ -6,18 +6,15 @@
#include "uart_driver.h" #include "uart_driver.h"
#include "screen.h" #include "screen.h"
/** static volatile bool timer_running = false;
* Broadcast screen state to sockets. static ETSTimer notifyTim;
* This is a callback for the Screen module,
* called after each visible screen modification. static void notifyTimCb(void *arg) {
*/ timer_running = false;
void ICACHE_FLASH_ATTR screen_notifyChange()
{
// TODO cooldown / buffering to reduce nr of such events
void *data = NULL; void *data = NULL;
const int bufsiz = 512; const int bufsiz = 1024;
char buff[bufsiz]; char buff[bufsiz];
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, &data); httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, &data);
@@ -27,17 +24,34 @@ void ICACHE_FLASH_ATTR screen_notifyChange()
cgiWebsockBroadcast(URL_WS_UPDATE, buff, (int) strlen(buff), flg); cgiWebsockBroadcast(URL_WS_UPDATE, buff, (int) strlen(buff), flg);
if (cont == HTTPD_CGI_DONE) break; if (cont == HTTPD_CGI_DONE) break;
} }
screenSerializeToBuffer(NULL, bufsiz, &data);
}
/**
* Broadcast screen state to sockets.
* This is a callback for the Screen module,
* called after each visible screen modification.
*/
void ICACHE_FLASH_ATTR screen_notifyChange(void)
{
if (timer_running) return;
timer_running = true;
os_timer_disarm(&notifyTim);
os_timer_setfn(&notifyTim, notifyTimCb, NULL);
os_timer_arm(&notifyTim, 20, 0);
} }
/** Socket received a message */ /** Socket received a message */
void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags) void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
{ {
char buf[20];
// Add terminator if missing (seems to randomly happen) // Add terminator if missing (seems to randomly happen)
data[len] = 0; data[len] = 0;
dbg("Sock RX str: %s, len %d", data, len); dbg("Sock RX str: %s, len %d", data, len);
if (strstarts(data, "STR:")) { if (strstarts(data, "STR:")) {
// pass string verbatim // pass string verbatim
UART_WriteString(UART0, data+4, UART_TIMEOUT_US); UART_WriteString(UART0, data+4, UART_TIMEOUT_US);
@@ -50,8 +64,37 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
} }
} }
else if (strstarts(data, "TAP:")) { else if (strstarts(data, "TAP:")) {
// TODO // this comes in as 0-based
warn("TODO mouse input handling not implemented!");
int y=0, x=0;
char *pc=data+4;
char c;
int phase=0;
while((c=*pc++) != '\0') {
if (c==','||c==';') {
phase++;
}
else if (c>='0' && c<='9') {
if (phase==0) {
y=y*10+(c-'0');
} else {
x=x*10+(c-'0');
}
}
}
if (!screen_isCoordValid(y, x)) {
warn("Mouse input at invalid coordinates");
return;
}
dbg("Screen clicked at row %d, col %d", y+1, x+1);
// Send as 1-based to user
sprintf(buf, "\033[%d;%dM", y+1, x+1);
UART_WriteString(UART0, buf, UART_TIMEOUT_US);
} }
else { else {
warn("Bad command."); warn("Bad command.");
+81 -26
View File
@@ -10,12 +10,12 @@
#define COLOR_MAX 15 #define COLOR_MAX 15
/** /**
* Screen cell data type * Screen cell data type (16 bits)
*/ */
typedef struct { typedef struct __attribute__((packed)){
char c; char c : 8;
Color fg; Color fg : 4;
Color bg; Color bg : 4;
} Cell; } Cell;
/** /**
@@ -27,8 +27,8 @@ static Cell screen[MAX_SCREEN_SIZE];
* Cursor position and attributes * Cursor position and attributes
*/ */
static struct { static struct {
Coordinate x; //!< X coordinate int x; //!< X coordinate
Coordinate y; //!< Y coordinate int y; //!< Y coordinate
bool visible; //!< Visible bool visible; //!< Visible
bool inverse; //!< Inverse colors bool inverse; //!< Inverse colors
Color fg; //!< Foreground color for writing Color fg; //!< Foreground color for writing
@@ -39,19 +39,25 @@ static struct {
* Saved cursor position, used with the SCP RCP commands * Saved cursor position, used with the SCP RCP commands
*/ */
static struct { static struct {
Coordinate x; int x;
Coordinate y; int y;
// optionally saved attrs
bool withAttrs;
bool inverse;
Color fg;
Color bg;
} cursor_sav; } cursor_sav;
/** /**
* Active screen width * Active screen width
*/ */
static Coordinate W = SCREEN_DEF_W; static int W = SCREEN_DEF_W;
/** /**
* Active screen height * Active screen height
*/ */
static Coordinate H = SCREEN_DEF_H; static int H = SCREEN_DEF_H;
// XXX volatile is probably not needed // XXX volatile is probably not needed
static volatile int notifyLock = 0; static volatile int notifyLock = 0;
@@ -193,7 +199,7 @@ screen_clear_line(ClearMode mode)
* @param rows - new height * @param rows - new height
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
screen_resize(Coordinate rows, Coordinate cols) screen_resize(int rows, int cols)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
// sanitize // sanitize
@@ -277,7 +283,7 @@ done:
* Set cursor position * Set cursor position
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
screen_cursor_set(Coordinate x, Coordinate y) screen_cursor_set(int y, int x)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
if (x >= W) x = W - 1; if (x >= W) x = W - 1;
@@ -287,11 +293,21 @@ screen_cursor_set(Coordinate x, Coordinate y)
NOTIFY_DONE(); NOTIFY_DONE();
} }
/**
* Set cursor position
*/
void ICACHE_FLASH_ATTR
screen_cursor_get(int *y, int *x)
{
*x = cursor.x;
*y = cursor.y;
}
/** /**
* Set cursor X position * Set cursor X position
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
screen_cursor_set_x(Coordinate x) screen_cursor_set_x(int x)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
if (x >= W) x = W - 1; if (x >= W) x = W - 1;
@@ -303,7 +319,7 @@ screen_cursor_set_x(Coordinate x)
* Set cursor Y position * Set cursor Y position
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
screen_cursor_set_y(Coordinate y) screen_cursor_set_y(int y)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
if (y >= H) y = H - 1; if (y >= H) y = H - 1;
@@ -315,7 +331,7 @@ screen_cursor_set_y(Coordinate y)
* Relative cursor move * Relative cursor move
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
screen_cursor_move(int dx, int dy) screen_cursor_move(int dy, int dx)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
int move; int move;
@@ -344,21 +360,40 @@ screen_cursor_move(int dx, int dy)
* Save the cursor pos * Save the cursor pos
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
screen_cursor_save(void) screen_cursor_save(bool withAttrs)
{ {
cursor_sav.x = cursor.x; cursor_sav.x = cursor.x;
cursor_sav.y = cursor.y; cursor_sav.y = cursor.y;
cursor_sav.withAttrs = withAttrs;
if (withAttrs) {
cursor_sav.fg = cursor.fg;
cursor_sav.bg = cursor.bg;
cursor_sav.inverse = cursor.inverse;
} else {
cursor_sav.fg = SCREEN_DEF_FG;
cursor_sav.bg = SCREEN_DEF_BG;
cursor_sav.inverse = 0;
}
} }
/** /**
* Restore the cursor pos * Restore the cursor pos
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
screen_cursor_restore(void) screen_cursor_restore(bool withAttrs)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
cursor.x = cursor_sav.x; cursor.x = cursor_sav.x;
cursor.y = cursor_sav.y; cursor.y = cursor_sav.y;
if (withAttrs) {
cursor.fg = cursor_sav.fg;
cursor.bg = cursor_sav.bg;
cursor.inverse = cursor_sav.inverse;
}
NOTIFY_DONE(); NOTIFY_DONE();
} }
@@ -431,6 +466,18 @@ screen_set_bright_fg(void)
//endregion //endregion
/**
* Check if coords are in range
*
* @param y
* @param x
* @return OK
*/
bool ICACHE_FLASH_ATTR screen_isCoordValid(int y, int x)
{
return x >= 0 && y >= 0 && x < W && y < H;
}
/** /**
* Set a character in the cursor color, move to right with wrap. * Set a character in the cursor color, move to right with wrap.
*/ */
@@ -448,23 +495,31 @@ screen_putchar(char ch)
goto done; goto done;
case '\n': case '\n':
screen_cursor_move(0, 1); screen_cursor_move(1, 0);
goto done; goto done;
case 8: // BS case 8: // BS
if (cursor.x > 0) cursor.x--; if (cursor.x > 0) {
cursor.x--;
} else {
// wrap around start of line
if (cursor.y>0) {
cursor.x=W-1;
cursor.y--;
}
}
// erase target cell // erase target cell
c = &screen[cursor.x + cursor.y * W]; c = &screen[cursor.x + cursor.y * W];
c->c = ' '; c->c = ' ';
goto done; goto done;
case 9: // TAB case 9: // TAB
c->c = ' '; if (cursor.x<((W-1)-(W-1)%4)) {
// nested recurs >:( but it's ok c->c = ' ';
screen_putchar(' '); do {
screen_putchar(' '); screen_putchar(' ');
screen_putchar(' '); } while(cursor.x%4!=0);
screen_putchar(' '); }
goto done; goto done;
default: default:
+14 -9
View File
@@ -24,7 +24,7 @@
* 8 black, 9 red, 10 green, 11 yellow * 8 black, 9 red, 10 green, 11 yellow
* 12 blue, 13 mag, 14 cyan, 15 white * 12 blue, 13 mag, 14 cyan, 15 white
* *
* Coordinates are 0-based, left-top is the origin. * ints are 0-based, left-top is the origin.
* X grows to the right, Y to the bottom. * X grows to the right, Y to the bottom.
* *
* +----> * +---->
@@ -53,7 +53,6 @@ typedef enum {
} ClearMode; } ClearMode;
typedef uint8_t Color; typedef uint8_t Color;
typedef int Coordinate;
httpd_cgi_state ICACHE_FLASH_ATTR httpd_cgi_state ICACHE_FLASH_ATTR
screenSerializeToBuffer(char *buffer, size_t buf_len, void **data); screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
@@ -62,7 +61,10 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
void screen_init(void); void screen_init(void);
/** Change the screen size */ /** Change the screen size */
void screen_resize(Coordinate rows, Coordinate cols); void screen_resize(int rows, int cols);
/** Check if coord is valid */
bool screen_isCoordValid(int y, int x);
// --- Clearing --- // --- Clearing ---
@@ -84,22 +86,25 @@ void screen_scroll_down(unsigned int lines);
// --- Cursor control --- // --- Cursor control ---
/** Set cursor position */ /** Set cursor position */
void screen_cursor_set(Coordinate x, Coordinate y); void screen_cursor_set(int y, int x);
/** Read cursor pos to given vars */
void screen_cursor_get(int *y, int *x);
/** Set cursor X position */ /** Set cursor X position */
void screen_cursor_set_x(Coordinate x); void screen_cursor_set_x(int x);
/** Set cursor Y position */ /** Set cursor Y position */
void screen_cursor_set_y(Coordinate y); void screen_cursor_set_y(int y);
/** Relative cursor move */ /** Relative cursor move */
void screen_cursor_move(int dx, int dy); void screen_cursor_move(int dy, int dx);
/** Save the cursor pos */ /** Save the cursor pos */
void screen_cursor_save(void); void screen_cursor_save(bool withAttrs);
/** Restore the cursor pos */ /** Restore the cursor pos */
void screen_cursor_restore(void); void screen_cursor_restore(bool withAttrs);
/** Enable cursor display */ /** Enable cursor display */
void screen_cursor_enable(bool enable); void screen_cursor_enable(bool enable);
+15 -6
View File
@@ -23,7 +23,7 @@
#include "screen.h" #include "screen.h"
#include "routes.h" #include "routes.h"
#define FIRMWARE_VERSION "0.2" #define FIRMWARE_VERSION "0.3"
#ifdef ESPFS_POS #ifdef ESPFS_POS
CgiUploadFlashDef uploadParams={ CgiUploadFlashDef uploadParams={
@@ -50,20 +50,29 @@ static ETSTimer prHeapTimer;
/** Periodically show heap usage */ /** Periodically show heap usage */
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg)
{ {
static uint32_t last = 0; static int last = 0;
static int cnt = 0;
int heap = system_get_free_heap_size();
int diff = (heap-last);
uint32_t heap = system_get_free_heap_size();
int32_t diff = (heap-last);
const char *cc = "+"; const char *cc = "+";
if (diff<0) cc = ""; if (diff<0) cc = "";
if (diff == 0) { if (diff == 0) {
dbg("Free heap: %d bytes", heap); if (cnt == 5) {
// only every 5 secs if no change
dbg("Free heap: %d bytes", heap);
cnt = 0;
}
} else { } else {
// report change
dbg("Free heap: %d bytes (%s%d)", heap, cc, diff); dbg("Free heap: %d bytes (%s%d)", heap, cc, diff);
cnt = 0;
} }
last = heap; last = heap;
cnt++;
} }
//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done. //Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done.
@@ -100,7 +109,7 @@ void ICACHE_FLASH_ATTR user_init(void)
// Heap use timer & blink // Heap use timer & blink
os_timer_disarm(&prHeapTimer); os_timer_disarm(&prHeapTimer);
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL); os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL);
os_timer_arm(&prHeapTimer, 5000, 1); os_timer_arm(&prHeapTimer, 1000, 1);
// The terminal screen // The terminal screen
screen_init(); screen_init();