Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8028f14387 | ||
|
|
daba5340f7 | ||
|
|
a82e14961c | ||
|
|
6c9ae7e126 | ||
|
|
a3c84f9a8f | ||
|
|
7aa3cd7f75 | ||
|
|
58ed6098c4 | ||
|
|
16a36a3d26 | ||
|
|
9d1cefeaab | ||
|
|
357600de8f | ||
|
|
fe699c7918 | ||
|
|
4edaa0687d | ||
|
|
f8ebae044a | ||
|
|
9e9539472c | ||
|
|
b57e0ee75b | ||
|
|
65e34a1634 | ||
|
|
6f32a6ef1a | ||
|
|
d3468a109c | ||
|
|
8f82b81434 | ||
|
|
653dc55d85 | ||
|
|
dda469ff21 | ||
|
|
3515e178cb | ||
|
|
5fd5c92390 | ||
|
|
e3ae34a932 | ||
|
|
423022b8bc | ||
|
|
fce3995800 | ||
|
|
67625a9317 | ||
|
|
35e1a788f0 | ||
|
|
dd68c112b0 | ||
|
|
2c7c496862 | ||
|
|
73c943bb97 | ||
|
|
beda5e67c0 | ||
|
|
357c81ba07 | ||
|
|
acd8801d33 | ||
|
|
5c57ea3b28 | ||
|
|
0e73f57da1 | ||
|
|
1f18c7d1f6 | ||
|
|
2a76eaee8e | ||
|
|
3b8cfd41e3 | ||
|
|
09cc66c75c | ||
|
|
7947b3a88c | ||
|
|
3355295aa3 | ||
|
|
87e0030be8 | ||
|
|
6d9d068268 | ||
|
|
276af04945 | ||
|
|
8d9c5bb246 | ||
|
|
59f26adf68 | ||
|
|
5995988a9b | ||
|
|
fe714bf473 | ||
|
|
68747d493c | ||
|
|
184b186b85 | ||
|
|
af47249492 | ||
|
|
e22ae1e66a | ||
|
|
bf1507b624 | ||
|
|
b5378a6626 | ||
|
|
9cac029e58 | ||
|
|
8117229926 | ||
|
|
04b75b8218 | ||
|
|
89fca5c40a | ||
|
|
ca410736f7 | ||
|
|
2ca8bf1483 | ||
|
|
35393827a9 | ||
|
|
ea84d1889d | ||
|
|
bc41a21f23 | ||
|
|
e48180ac10 | ||
|
|
e47a5477f9 | ||
|
|
e944a994a7 | ||
|
|
6535b49551 | ||
|
|
2cb8586c94 | ||
|
|
ec1cebdd4c | ||
|
|
af5a3a64ed | ||
|
|
7b94eef448 | ||
|
|
c8067122d5 | ||
|
|
12385b7a5f | ||
|
|
5564147f67 | ||
|
|
32ba7dc0d7 | ||
|
|
80721c1715 | ||
|
|
e22d3f26d9 |
+23
-4
@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.7)
|
||||
# Do not attempt to build it with cmake, use make instead. #
|
||||
###################################################################
|
||||
|
||||
project(esp_vt100_firmware)
|
||||
project(ESPTerm)
|
||||
|
||||
set(CMAKE_CXX_STANDARD GNU99)
|
||||
|
||||
@@ -110,6 +110,8 @@ set(SOURCE_FILES
|
||||
user/serial.h
|
||||
user/routes.c
|
||||
user/routes.h
|
||||
user/uart_asyncdrv.c-
|
||||
user/uart_asyncdrv.h-
|
||||
user/cgi_main.c
|
||||
user/cgi_main.h
|
||||
user/cgi_sockets.c
|
||||
@@ -117,14 +119,29 @@ set(SOURCE_FILES
|
||||
user/cgi_sockets.h
|
||||
user/ansi_parser_callbacks.c
|
||||
user/ansi_parser_callbacks.h
|
||||
user/user_main.h
|
||||
user/wifimgr.c
|
||||
user/wifimgr.h
|
||||
user/persist.c
|
||||
user/persist.h
|
||||
include/helpers.h
|
||||
user/syscfg.c
|
||||
user/syscfg.h)
|
||||
user/syscfg.h
|
||||
user/ascii.h
|
||||
user/sgr.h
|
||||
user/apars_utf8.c
|
||||
user/apars_utf8.h
|
||||
user/apars_logging.h
|
||||
user/version.h
|
||||
user/apars_csi.c
|
||||
user/apars_csi.h
|
||||
user/apars_short.c
|
||||
user/apars_short.h
|
||||
user/apars_string.c
|
||||
user/apars_string.h
|
||||
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)
|
||||
|
||||
include_directories(include)
|
||||
include_directories(user)
|
||||
@@ -145,6 +162,8 @@ add_definitions(
|
||||
-DFLAG_GZIP=2
|
||||
-DADMIN_PASSWORD="asdf"
|
||||
-DGIT_HASH="blabla"
|
||||
-DDEBUG_HEAP=1
|
||||
-DDEBUG_MALLOC=1
|
||||
-DESPFS_HEATSHRINK)
|
||||
|
||||
add_executable(esp_vt100_firmware ${SOURCE_FILES})
|
||||
add_executable(ESPTerm ${SOURCE_FILES})
|
||||
|
||||
@@ -65,8 +65,14 @@ LIBS += esphttpd
|
||||
# compiler flags using during compilation of source files
|
||||
CFLAGS = -Os -ggdb -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
|
||||
-nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \
|
||||
-Wno-address -Wno-unused -DHTTPD_MAX_BACKLOG_SIZE=4096 -DADMIN_PASSWORD=$(ADMIN_PASSWORD) \
|
||||
-DGIT_HASH='"$(shell git rev-parse --short HEAD)"'
|
||||
-Wno-address -Wno-unused
|
||||
|
||||
CFLAGS += -DGIT_HASH='"$(shell git rev-parse --short HEAD)"'
|
||||
CFLAGS += -DADMIN_PASSWORD=$(ADMIN_PASSWORD)
|
||||
|
||||
ifdef GLOBAL_CFLAGS
|
||||
CFLAGS += $(GLOBAL_CFLAGS)
|
||||
endif
|
||||
|
||||
# linker flags used to generate the main object file
|
||||
LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static
|
||||
|
||||
@@ -1,76 +1,100 @@
|
||||
# ESPTerm
|
||||
|
||||
ESP8266 Wireless Terminal Emulator project
|
||||
ESPTerm is a terminal emulator running on the ESP8266 WiFi chip.
|
||||
The firmware emulates VT102 with some additional features based on `xterm` and later VT models, and the terminal screen can be accessed using any web browser, even on a phone or tablet. It works with ESP-01, ESP-01S, ESP-12 and likely many other modules (I use ESP-12 on a NodeMCU board for development).
|
||||
|
||||
This project is based on SpriteTM's esphttpd and libesphttpd, forked by MightyPork to
|
||||
[MightyPork/esphttpd](https://github.com/MightyPork/esphttpd) and
|
||||
[MightyPork/libesphttpd](https://github.com/MightyPork/libesphttpd) respectively.
|
||||
ESPTerm can add remote access via WiFi to any embeded project, all you need is a serial port.
|
||||
|
||||
Those forks include improvements not available upstream.
|
||||
## Screenshots, photos
|
||||
|
||||
## Goals
|
||||
- Look at the [GitHub releases page][releases], there are some pics.
|
||||
|
||||
The 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.
|
||||
## ESPTerm features
|
||||
|
||||
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.
|
||||
- **Robust WiFi configuration interface**
|
||||
- static IP
|
||||
- DHCP
|
||||
- AP channel and strength setting
|
||||
- AP password
|
||||
- SSID search for finding your existing network
|
||||
- **Almost complete VT102 emulation** with some extras
|
||||
- *Sufficient to run most Linux console applications, including ncurses-based ones\**
|
||||
- All standard SGR (text style attributes) supported
|
||||
- Full UTF-8 support
|
||||
- Alternate character sets support
|
||||
- 16 colors
|
||||
- Scrolling Region, Origin Mode
|
||||
- Tab Stops
|
||||
- Cursor save/restore
|
||||
- Character/Line insert, delete, clear operations
|
||||
- Audible BEL (ASCII 7 beep)
|
||||
- Most standard queries (get cursor position, get SGR, get screen size...)
|
||||
- All DEC private options either implemented or safely consumed by the parser
|
||||
- **Other features:**
|
||||
- Screen size up to 80x25
|
||||
- Real-time screen update via WebSocket
|
||||
- Button to open Android software keyboard
|
||||
- 5 buttons under the screen for quick commands
|
||||
- Button labels can be changed (by OSC commands or via settings)
|
||||
- Screen title can be changed (by OSC commands or via settings)
|
||||
- Built-in help page with basic troubleshooting and command reference
|
||||
|
||||
It lets you make simple UI (manipulating the screen with ANSI sequences) and receive input from buttons on
|
||||
the webpage (and keyboard on PC). There is some rudimentary touch input as well.
|
||||
\*) Linux console applications run via agetty at ttyUSB0 were used for testing, obviously you'll be better off using SSH for remote shell
|
||||
|
||||
The screen size is adjustable up to 25x80 (via a special control sequence) and uses 16 standard colors
|
||||
(8 dark and 8 bright). Both default size and colors can be configured in the settings.
|
||||
ESPTerm firmware is written in C and is based on SpriteTM's `libesphttpd` http server library, forked by MightyPork to
|
||||
[MightyPork/libesphttpd](https://github.com/MightyPork/libesphttpd) respectively. This fork includes various improvements and changes required by the project.
|
||||
|
||||
## Project status
|
||||
## Running ESPTerm
|
||||
|
||||
*A little buggy, but mostly okay! There are many features and fixes planned, but it should be fairly usable already.*
|
||||
To run ESPTerm on your ESP8266, either build it yourself from source using `xtensa-lx106-elf-gcc` (and the included Makefile), or download pre-built binaries from the [GitHub releases section][releases]. Flash the binaries using [esptool](https://github.com/espressif/esptool).
|
||||
|
||||
- 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
|
||||
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 with a piece of wire to make a loopback interface.
|
||||
*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.
|
||||
|
||||
- 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).
|
||||
|
||||
Arrow keys generate ANSI sequences, ESC sends literal ASCII code 27 etc. Almost everything can be input
|
||||
straight from the browser.
|
||||
### Pins
|
||||
|
||||
- Buttons pressed in the browser UI send ASCII codes 1..5. Mouse clicks are sent as `\e[<row>;<col>M`.
|
||||
- Pin GPIO2 is used for debug messages at 115200 baud, 8 bit, no parity.
|
||||
- Pins Rx and Tx are used for the main communication UART. Connect your USB-serial dongle or application microcontroller here.
|
||||
|
||||
- There's a built-in WiFi config page and a Help page with a list of all supported ANSI sequences and other details.
|
||||
### Console commands - escape sequences
|
||||
|
||||
- A list of most supported commands can be found here: http://bits.ondrovo.com/espterm-xterm.html
|
||||
- To set the screen title, use `OSC 0 ; title ST` (`OSC` = `ESC ]`, `ST` = `ESC \` or ASCII 7)
|
||||
- To set buttons text, use `OSC 8 1 ; text ST` with 81 through 85.
|
||||
- Mouse clicks (as of v0.6.9) generate `CSI row ; col M` at the Tx pin (`CSI` = `ESC [`)
|
||||
- For more info, look eg. on Wikipedia or here: http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||
|
||||
### Setup
|
||||
|
||||
- When flashed for the first time, ESPTerm wipes any possible previous WiFi configuration, because it implements its own WiFi config manager with many additional features.
|
||||
- It should start in AP mode, the default SSID being `TERM-MACADR` with `MACADR` being three unique bytes from the MAC address / Device ID.
|
||||
- Connect to it via a smartphone or laptop and configure WiFi as desired.
|
||||
- To re-enable the built-in AP, hold the BOOT (GPIO0) button for about 1 s, until the blue LED starts slowly flashing. Then release the button!
|
||||
- To reset all settings to defaults, hold the BOOT (GPIO0) button until the blue LED flashes rapidly, then release the button.
|
||||
- When you accidentally make the blue LED flash, you can cancel the operation by pressing Reset or disconnecting its power supply. The wipe is done on the button's release.
|
||||
|
||||
### Config files
|
||||
|
||||
ESPTerm has two config "files", one for defaults and one for the currently used settings. In the case of the terminal config, there is also a third, temporary file for changes done via ESC commands.
|
||||
|
||||
When you get your settings *just right*, you can store them as defaults, which can then be at any time restored by holding the BOOT (GPIO0) button. You can do this on the System Settings page. This asks for an "admin password", which you can define when building the firmware in the `esphttpdconfig.mk` file. The default password is `19738426`. This password can't presently be changed without re-flashing the firmware.
|
||||
|
||||
You can also restore everything (except the saved defaults) to "factory defaults", there is a button for this on the System Settings page. Those are the initial values in the config files.
|
||||
|
||||
## 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
|
||||
`make toolchain esptool libhal STANDALONE=n`.
|
||||
|
||||
`make toolchain esptool libhal STANDALONE=n`.
|
||||
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:
|
||||
|
||||
```
|
||||
KERNEL=="tty[A-Z]*[0-9]*", GROUP="uucp", MODE="0666"
|
||||
```
|
||||
|
||||
- Install Ragel if you wish to make modifications to the ANSI sequence parser.
|
||||
If not, comment out its call in `build_parser.sh`. The `.rl` file is the actual source, the `.c` is generated.
|
||||
|
||||
- Install Ruby and then the `sass` package with `gem install sass` (or try some other implementation, such as
|
||||
`sassc`)
|
||||
|
||||
- Make sure your `esphttpdconfig.mk` is set up properly - link to the SDK etc.
|
||||
|
||||
The IoT SDK is now included in the project due to problems with obtaining the correct version and patching it.
|
||||
@@ -80,7 +104,7 @@ than welcome!
|
||||
### 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
|
||||
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.
|
||||
|
||||
@@ -97,3 +121,5 @@ Sometimes it does not, particularly with `make -B`. Try just plain `make`. You c
|
||||
build scripts manually, too.
|
||||
|
||||
To flash, just run `make flash`.
|
||||
|
||||
[releases]: https://github.com/MightyPork/esp-vt100-firmware/releases
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,3 +40,18 @@ ESP_SPI_FLASH_SIZE_K = 1024
|
||||
|
||||
# Admin password, used to store settings to flash as defaults
|
||||
ADMIN_PASSWORD = "19738426"
|
||||
|
||||
GLOBAL_CFLAGS = \
|
||||
-DDEBUG_ROUTER=0 \
|
||||
-DDEBUG_CAPTDNS=0 \
|
||||
-DDEBUG_HTTP=1 \
|
||||
-DDEBUG_ESPFS=0 \
|
||||
-DDEBUG_WS=0 \
|
||||
-DDEBUG_ANSI=1 \
|
||||
-DDEBUG_ANSI_NOIMPL=1 \
|
||||
-DHTTPD_MAX_BACKLOG_SIZE=8192 \
|
||||
-DHTTPD_MAX_HEAD_LEN=1024 \
|
||||
-DHTTPD_MAX_POST_LEN=512 \
|
||||
-DDEBUG_INPUT=0 \
|
||||
-DDEBUG_HEAP=1 \
|
||||
-DDEBUG_MALLOC=0
|
||||
|
||||
@@ -16,14 +16,10 @@ return [
|
||||
'labels_seq' => 'TESPTerm local debug1235',
|
||||
|
||||
'screenData' => 'HELLOxNRE3',//'\u000b\u0001\u001b\u0001\u0001\u0001\u0001\u0001\f\u0005\u0001\u0010\u0003HELLOx\u0002\u000b\u0001\u0001N\u0001RE\u00023\u0001', //,
|
||||
/*'{
|
||||
"w": 26, "h": 10,
|
||||
"x": 10, "y": 5,
|
||||
"cv": 1, "fg": 10, "bg": 2,
|
||||
"screen": ""
|
||||
}',//"70 t259"*/
|
||||
|
||||
'parser_tout_ms' => 10,
|
||||
'display_tout_ms' => 20,
|
||||
'fn_alt_mode' => '1',
|
||||
|
||||
'opmode' => '2',
|
||||
'sta_enable' => '0',
|
||||
|
||||
@@ -14,6 +14,8 @@ function process_html($s) {
|
||||
return $s;
|
||||
}
|
||||
|
||||
$no_tpl_files = ['help', 'cfg_wifi_conn'];
|
||||
|
||||
ob_start();
|
||||
foreach($_pages as $_k => $p) {
|
||||
if ($p->bodyclass == 'api') continue;
|
||||
@@ -30,7 +32,7 @@ foreach($_pages as $_k => $p) {
|
||||
// $s = process_html($s);
|
||||
|
||||
ob_clean(); // clean up
|
||||
$of = __DIR__ . '/../html/' . $_k . '.tpl';
|
||||
$of = __DIR__ . '/../html/' . $_k . (in_array($_k, $no_tpl_files) ? '.html' : '.tpl');
|
||||
file_put_contents($of, $s); // write to a file
|
||||
}
|
||||
|
||||
|
||||
+78
-20
@@ -416,8 +416,12 @@ a:hover {
|
||||
cursor: pointer; }
|
||||
|
||||
ul > * {
|
||||
padding-top: .2em;
|
||||
padding-bottom: .2em; }
|
||||
padding-top: .3em;
|
||||
padding-bottom: .3em; }
|
||||
|
||||
ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0; }
|
||||
|
||||
/* Main outer container */
|
||||
#outer {
|
||||
@@ -629,7 +633,36 @@ ul > * {
|
||||
.Box.mobopen .Row.explain {
|
||||
margin-top: 18px; } }
|
||||
|
||||
.Box.fold h2 {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 2px 1.3rem 2px 5px;
|
||||
margin: 0 -5px 0 -5px; }
|
||||
.Box.fold h2::after {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
content: '▸';
|
||||
top: 50%;
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
transform: translate(0, -50%) rotate(90deg); }
|
||||
.Box.fold.expanded h2::after {
|
||||
transform: translate(-25%, -50%) rotate(-90deg);
|
||||
margin-bottom: 1rem; }
|
||||
.Box.fold .Row {
|
||||
display: none; }
|
||||
.Box.fold.expanded .Row {
|
||||
display: flex; }
|
||||
.Box.fold.expanded .Row.v {
|
||||
display: block; }
|
||||
|
||||
@media screen and (max-width: 544px) {
|
||||
.Box.fold h2, .Box.mobcol h2 {
|
||||
padding: 2px 1.3rem 2px 5px;
|
||||
margin: 0 -5px 0 -5px; }
|
||||
.Box.fold.expanded h2::after, .Box.mobcol.expanded h2::after {
|
||||
margin-bottom: 1rem; }
|
||||
|
||||
.Box.mobcol h2 {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
@@ -648,10 +681,12 @@ ul > * {
|
||||
margin-bottom: 1rem; }
|
||||
.Box.mobcol .Row {
|
||||
display: none; }
|
||||
.Box.mobcol #ap-box {
|
||||
display: none; }
|
||||
.Box.mobcol.expanded .Row {
|
||||
display: flex; }
|
||||
.Box.mobcol.expanded .Row.v {
|
||||
display: block; }
|
||||
.Box.mobcol #ap-box {
|
||||
display: none; }
|
||||
.Box.mobcol.expanded #ap-box {
|
||||
display: block; } }
|
||||
.Modal {
|
||||
@@ -875,6 +910,16 @@ form {
|
||||
margin-top: 0; }
|
||||
.Row:last-child {
|
||||
margin-bottom: 0; }
|
||||
.Row.v {
|
||||
display: block; }
|
||||
.Row .aside {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
margin-bottom: 5px; }
|
||||
@media screen and (max-width: 544px) {
|
||||
.Row .aside {
|
||||
margin: 0;
|
||||
float: none; } }
|
||||
.Row .spacer {
|
||||
width: 160px; }
|
||||
@media screen and (max-width: 544px) {
|
||||
@@ -1107,7 +1152,7 @@ body.term #screen {
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
font-family: monospace;
|
||||
font-size: 16pt;
|
||||
font-size: 20px;
|
||||
white-space: nowrap;
|
||||
background: #111213;
|
||||
padding: 6px;
|
||||
@@ -1115,12 +1160,11 @@ body.term #screen {
|
||||
border: 2px solid #3983CD; }
|
||||
body.term #screen span {
|
||||
white-space: pre;
|
||||
cursor: pointer; }
|
||||
body.term #screen span:hover {
|
||||
outline: 1px solid rgba(255, 255, 255, 0.4); }
|
||||
@media screen and (max-width: 544px) {
|
||||
body.term #screen span:hover {
|
||||
outline: 0 none; } }
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 1.15em;
|
||||
width: 0.6em;
|
||||
overflow: visible; }
|
||||
body.term #buttons {
|
||||
margin-top: 10px;
|
||||
white-space: nowrap; }
|
||||
@@ -1152,9 +1196,6 @@ body.term #botnav {
|
||||
position: absolute;
|
||||
top: -9999px; }
|
||||
|
||||
.nb {
|
||||
font-weight: normal !important; }
|
||||
|
||||
.theme-0 .fg0 {
|
||||
color: #111213; }
|
||||
.theme-0 .bg0 {
|
||||
@@ -1548,6 +1589,24 @@ body.term #botnav {
|
||||
.bold {
|
||||
font-weight: bold !important; }
|
||||
|
||||
.faint span {
|
||||
opacity: 0.6; }
|
||||
|
||||
.italic {
|
||||
font-style: italic; }
|
||||
|
||||
.under {
|
||||
text-decoration: underline; }
|
||||
|
||||
.strike {
|
||||
text-decoration: line-through; }
|
||||
|
||||
.underline.strike {
|
||||
text-decoration: underline line-through; }
|
||||
|
||||
.blink-hide .blink {
|
||||
color: transparent; }
|
||||
|
||||
.Row.color-preview {
|
||||
font-family: monospace;
|
||||
font-size: 16pt;
|
||||
@@ -1590,20 +1649,19 @@ body.term #botnav {
|
||||
padding: 0.38198rem 0;
|
||||
text-align: center; }
|
||||
|
||||
.ansiref, .ansiref td, .ansiref th {
|
||||
.Row table, .Row table td, .Row table th {
|
||||
border: 1px solid #666; }
|
||||
.ansiref th, .ansiref td {
|
||||
.Row table th, .Row table td {
|
||||
white-space: normal; }
|
||||
.ansiref th {
|
||||
.Row table th {
|
||||
background-color: rgba(255, 255, 255, 0.1); }
|
||||
|
||||
.ansiref td:nth-child(1) {
|
||||
font-family: monospace; }
|
||||
.ansiref.w100 {
|
||||
width: 100%; }
|
||||
.ansiref.w100 td:nth-child(1) {
|
||||
width: 9em; }
|
||||
.ansiref.w100 td:nth-child(2) {
|
||||
width: 8em; }
|
||||
width: 6em; }
|
||||
|
||||
@media screen and (min-width: 545px) {
|
||||
.mq-phone {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
+810
-89
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ $.ready(function () {
|
||||
});
|
||||
|
||||
// Expanding boxes on mobile
|
||||
$('.Box.mobcol').forEach(function(x) {
|
||||
$('.Box.mobcol,.Box.fold').forEach(function(x) {
|
||||
var h = x.querySelector('h2');
|
||||
|
||||
var hdl = function() {
|
||||
@@ -123,3 +123,67 @@ $.ready(function() {
|
||||
}, 1);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
|
||||
if (!String.fromCodePoint) {
|
||||
(function() {
|
||||
var defineProperty = (function() {
|
||||
// IE 8 only supports `Object.defineProperty` on DOM elements
|
||||
try {
|
||||
var object = {};
|
||||
var $defineProperty = Object.defineProperty;
|
||||
var result = $defineProperty(object, object, object) && $defineProperty;
|
||||
} catch(error) {}
|
||||
return result;
|
||||
}());
|
||||
var stringFromCharCode = String.fromCharCode;
|
||||
var floor = Math.floor;
|
||||
var fromCodePoint = function() {
|
||||
var MAX_SIZE = 0x4000;
|
||||
var codeUnits = [];
|
||||
var highSurrogate;
|
||||
var lowSurrogate;
|
||||
var index = -1;
|
||||
var length = arguments.length;
|
||||
if (!length) {
|
||||
return '';
|
||||
}
|
||||
var result = '';
|
||||
while (++index < length) {
|
||||
var codePoint = Number(arguments[index]);
|
||||
if (
|
||||
!isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
|
||||
codePoint < 0 || // not a valid Unicode code point
|
||||
codePoint > 0x10FFFF || // not a valid Unicode code point
|
||||
floor(codePoint) != codePoint // not an integer
|
||||
) {
|
||||
throw RangeError('Invalid code point: ' + codePoint);
|
||||
}
|
||||
if (codePoint <= 0xFFFF) { // BMP code point
|
||||
codeUnits.push(codePoint);
|
||||
} else { // Astral code point; split in surrogate halves
|
||||
// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
|
||||
codePoint -= 0x10000;
|
||||
highSurrogate = (codePoint >> 10) + 0xD800;
|
||||
lowSurrogate = (codePoint % 0x400) + 0xDC00;
|
||||
codeUnits.push(highSurrogate, lowSurrogate);
|
||||
}
|
||||
if (index + 1 == length || codeUnits.length > MAX_SIZE) {
|
||||
result += stringFromCharCode.apply(null, codeUnits);
|
||||
codeUnits.length = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
if (defineProperty) {
|
||||
defineProperty(String, 'fromCodePoint', {
|
||||
'value': fromCodePoint,
|
||||
'configurable': true,
|
||||
'writable': true
|
||||
});
|
||||
} else {
|
||||
String.fromCodePoint = fromCodePoint;
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
// keymaster.js
|
||||
// (c) 2011-2013 Thomas Fuchs
|
||||
// keymaster.js may be freely distributed under the MIT license.
|
||||
|
||||
;(function(global){
|
||||
var k,
|
||||
_handlers = {},
|
||||
_mods = { 16: false, 18: false, 17: false, 91: false },
|
||||
_scope = 'all',
|
||||
// modifier keys
|
||||
_MODIFIERS = {
|
||||
'⇧': 16, shift: 16,
|
||||
'⌥': 18, alt: 18, option: 18,
|
||||
'⌃': 17, ctrl: 17, control: 17,
|
||||
'⌘': 91, command: 91
|
||||
},
|
||||
// special keys
|
||||
_MAP = {
|
||||
backspace: 8, tab: 9, clear: 12,
|
||||
enter: 13, 'return': 13,
|
||||
esc: 27, escape: 27, space: 32,
|
||||
left: 37, up: 38,
|
||||
right: 39, down: 40,
|
||||
del: 46, 'delete': 46,
|
||||
home: 36, end: 35,
|
||||
pageup: 33, pagedown: 34,
|
||||
',': 188, '.': 190, '/': 191,
|
||||
'`': 192, '-': 189, '=': 187,
|
||||
';': 186, '\'': 222,
|
||||
'[': 219, ']': 221, '\\': 220,
|
||||
// added:
|
||||
insert: 45,
|
||||
np_0: 96, np_1: 97, np_2: 98, np_3: 99, np_4: 100, np_5: 101,
|
||||
np_6: 102, np_7: 103, np_8: 104, np_9: 105, np_mul: 106,
|
||||
np_add: 107, np_sub: 109, np_point: 110, np_div: 111, numlock: 144,
|
||||
},
|
||||
code = function(x){
|
||||
return _MAP[x] || x.toUpperCase().charCodeAt(0);
|
||||
},
|
||||
_downKeys = [];
|
||||
|
||||
for(k=1;k<20;k++) _MAP['f'+k] = 111+k;
|
||||
|
||||
// IE doesn't support Array#indexOf, so have a simple replacement
|
||||
function index(array, item){
|
||||
var i = array.length;
|
||||
while(i--) if(array[i]===item) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// for comparing mods before unassignment
|
||||
function compareArray(a1, a2) {
|
||||
if (a1.length != a2.length) return false;
|
||||
for (var i = 0; i < a1.length; i++) {
|
||||
if (a1[i] !== a2[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var modifierMap = {
|
||||
16:'shiftKey',
|
||||
18:'altKey',
|
||||
17:'ctrlKey',
|
||||
91:'metaKey'
|
||||
};
|
||||
function updateModifierKey(event) {
|
||||
for(k in _mods) _mods[k] = event[modifierMap[k]];
|
||||
};
|
||||
|
||||
function isModifierPressed(mod) {
|
||||
if (mod=='control'||mod=='ctrl') return _mods[17];
|
||||
if (mod=='shift') return _mods[16];
|
||||
if (mod=='meta') return _mods[91];
|
||||
if (mod=='alt') return _mods[18];
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle keydown event
|
||||
function dispatch(event) {
|
||||
var key, handler, k, i, modifiersMatch, scope;
|
||||
key = event.keyCode;
|
||||
|
||||
if (index(_downKeys, key) == -1) {
|
||||
_downKeys.push(key);
|
||||
}
|
||||
|
||||
// if a modifier key, set the key.<modifierkeyname> property to true and return
|
||||
if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
|
||||
if(key in _mods) {
|
||||
_mods[key] = true;
|
||||
// 'assignKey' from inside this closure is exported to window.key
|
||||
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
|
||||
return;
|
||||
}
|
||||
updateModifierKey(event);
|
||||
|
||||
// see if we need to ignore the keypress (filter() can can be overridden)
|
||||
// by default ignore key presses if a select, textarea, or input is focused
|
||||
if(!assignKey.filter.call(this, event)) return;
|
||||
|
||||
// abort if no potentially matching shortcuts found
|
||||
if (!(key in _handlers)) return;
|
||||
|
||||
scope = getScope();
|
||||
|
||||
// for each potential shortcut
|
||||
for (i = 0; i < _handlers[key].length; i++) {
|
||||
handler = _handlers[key][i];
|
||||
|
||||
// see if it's in the current scope
|
||||
if(handler.scope == scope || handler.scope == 'all'){
|
||||
// check if modifiers match if any
|
||||
modifiersMatch = handler.mods.length > 0;
|
||||
for(k in _mods)
|
||||
if((!_mods[k] && index(handler.mods, +k) > -1) ||
|
||||
(_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
|
||||
// call the handler and stop the event if neccessary
|
||||
if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
|
||||
if(handler.method(event, handler)===false){
|
||||
if(event.preventDefault) event.preventDefault();
|
||||
else event.returnValue = false;
|
||||
if(event.stopPropagation) event.stopPropagation();
|
||||
if(event.cancelBubble) event.cancelBubble = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// unset modifier keys on keyup
|
||||
function clearModifier(event){
|
||||
var key = event.keyCode, k,
|
||||
i = index(_downKeys, key);
|
||||
|
||||
// remove key from _downKeys
|
||||
if (i >= 0) {
|
||||
_downKeys.splice(i, 1);
|
||||
}
|
||||
|
||||
if(key == 93 || key == 224) key = 91;
|
||||
if(key in _mods) {
|
||||
_mods[key] = false;
|
||||
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
|
||||
}
|
||||
};
|
||||
|
||||
function resetModifiers() {
|
||||
for(k in _mods) _mods[k] = false;
|
||||
for(k in _MODIFIERS) assignKey[k] = false;
|
||||
};
|
||||
|
||||
// parse and assign shortcut
|
||||
function assignKey(key, scope, method){
|
||||
var keys, mods;
|
||||
keys = getKeys(key);
|
||||
if (method === undefined) {
|
||||
method = scope;
|
||||
scope = 'all';
|
||||
}
|
||||
|
||||
// for each shortcut
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
// set modifier keys if any
|
||||
mods = [];
|
||||
key = keys[i].split('+');
|
||||
if (key.length > 1){
|
||||
mods = getMods(key);
|
||||
key = [key[key.length-1]];
|
||||
}
|
||||
// convert to keycode and...
|
||||
key = key[0]
|
||||
key = code(key);
|
||||
// ...store handler
|
||||
if (!(key in _handlers)) _handlers[key] = [];
|
||||
_handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
|
||||
}
|
||||
};
|
||||
|
||||
// unbind all handlers for given key in current scope
|
||||
function unbindKey(key, scope) {
|
||||
var multipleKeys, keys,
|
||||
mods = [],
|
||||
i, j, obj;
|
||||
|
||||
multipleKeys = getKeys(key);
|
||||
|
||||
for (j = 0; j < multipleKeys.length; j++) {
|
||||
keys = multipleKeys[j].split('+');
|
||||
|
||||
if (keys.length > 1) {
|
||||
mods = getMods(keys);
|
||||
}
|
||||
|
||||
key = keys[keys.length - 1];
|
||||
key = code(key);
|
||||
|
||||
if (scope === undefined) {
|
||||
scope = getScope();
|
||||
}
|
||||
if (!_handlers[key]) {
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < _handlers[key].length; i++) {
|
||||
obj = _handlers[key][i];
|
||||
// only clear handlers if correct scope and mods match
|
||||
if (obj.scope === scope && compareArray(obj.mods, mods)) {
|
||||
_handlers[key][i] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true if the key with code 'keyCode' is currently down
|
||||
// Converts strings into key codes.
|
||||
function isPressed(keyCode) {
|
||||
if (typeof(keyCode)=='string') {
|
||||
keyCode = code(keyCode);
|
||||
}
|
||||
return index(_downKeys, keyCode) != -1;
|
||||
}
|
||||
|
||||
function getPressedKeyCodes() {
|
||||
return _downKeys.slice(0);
|
||||
}
|
||||
|
||||
function filter(event){
|
||||
var tagName = (event.target || event.srcElement).tagName;
|
||||
// ignore keypressed in any elements that support keyboard data input
|
||||
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
|
||||
}
|
||||
|
||||
// initialize key.<modifier> to false
|
||||
for(k in _MODIFIERS) assignKey[k] = false;
|
||||
|
||||
// set current scope (default 'all')
|
||||
function setScope(scope){ _scope = scope || 'all' };
|
||||
function getScope(){ return _scope || 'all' };
|
||||
|
||||
// delete all handlers for a given scope
|
||||
function deleteScope(scope){
|
||||
var key, handlers, i;
|
||||
|
||||
for (key in _handlers) {
|
||||
handlers = _handlers[key];
|
||||
for (i = 0; i < handlers.length; ) {
|
||||
if (handlers[i].scope === scope) handlers.splice(i, 1);
|
||||
else i++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// abstract key logic for assign and unassign
|
||||
function getKeys(key) {
|
||||
var keys;
|
||||
key = key.replace(/\s/g, '');
|
||||
keys = key.split(',');
|
||||
if ((keys[keys.length - 1]) == '') {
|
||||
keys[keys.length - 2] += ',';
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// abstract mods logic for assign and unassign
|
||||
function getMods(key) {
|
||||
var mods = key.slice(0, key.length - 1);
|
||||
for (var mi = 0; mi < mods.length; mi++)
|
||||
mods[mi] = _MODIFIERS[mods[mi]];
|
||||
return mods;
|
||||
}
|
||||
|
||||
// cross-browser events
|
||||
function addEvent(object, event, method) {
|
||||
if (object.addEventListener)
|
||||
object.addEventListener(event, method, false);
|
||||
else if(object.attachEvent)
|
||||
object.attachEvent('on'+event, function(){ method(window.event) });
|
||||
};
|
||||
|
||||
// set the handlers globally on document
|
||||
addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
|
||||
addEvent(document, 'keyup', clearModifier);
|
||||
|
||||
// reset modifiers to false whenever the window is (re)focused.
|
||||
addEvent(window, 'focus', resetModifiers);
|
||||
|
||||
// store previously defined key
|
||||
var previousKey = global.key;
|
||||
|
||||
// restore previously defined key and return reference to our key object
|
||||
function noConflict() {
|
||||
var k = global.key;
|
||||
global.key = previousKey;
|
||||
return k;
|
||||
}
|
||||
|
||||
// set window.key and window.key.set/get/deleteScope, and the default filter
|
||||
global.key = assignKey;
|
||||
global.key.setScope = setScope;
|
||||
global.key.getScope = getScope;
|
||||
global.key.deleteScope = deleteScope;
|
||||
global.key.filter = filter;
|
||||
global.key.isPressed = isPressed;
|
||||
global.key.isModifier = isModifierPressed;
|
||||
global.key.getPressedKeyCodes = getPressedKeyCodes;
|
||||
global.key.noConflict = noConflict;
|
||||
global.key.unbind = unbindKey;
|
||||
|
||||
if(typeof module !== 'undefined') module.exports = assignKey;
|
||||
|
||||
})(this);
|
||||
+435
-88
@@ -1,3 +1,38 @@
|
||||
/** Decode two-byte number */
|
||||
function parse2B(s, i) {
|
||||
return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127;
|
||||
}
|
||||
|
||||
/** Decode three-byte number */
|
||||
function parse3B(s, i) {
|
||||
return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127;
|
||||
}
|
||||
|
||||
function Chr(n) {
|
||||
return String.fromCharCode(n);
|
||||
}
|
||||
|
||||
function encode2B(n) {
|
||||
var lsb, msb;
|
||||
lsb = (n % 127);
|
||||
n = ((n - lsb) / 127);
|
||||
lsb += 1;
|
||||
msb = (n + 1);
|
||||
return Chr(lsb) + Chr(msb);
|
||||
}
|
||||
|
||||
function encode3B(n) {
|
||||
var lsb, msb, xsb;
|
||||
lsb = (n % 127);
|
||||
n = (n - lsb) / 127;
|
||||
lsb += 1;
|
||||
msb = (n % 127);
|
||||
n = (n - msb) / 127;
|
||||
msb += 1;
|
||||
xsb = (n + 1);
|
||||
return Chr(lsb) + Chr(msb) + Chr(xsb);
|
||||
}
|
||||
|
||||
var Screen = (function () {
|
||||
var W = 0, H = 0; // dimensions
|
||||
var inited = false;
|
||||
@@ -8,29 +43,31 @@ var Screen = (function () {
|
||||
y: 0,
|
||||
fg: 7, // colors 0-15
|
||||
bg: 0,
|
||||
bold: false,
|
||||
attrs: 0,
|
||||
suppress: false, // do not turn on in blink interval (for safe moving)
|
||||
hidden: false // do not show
|
||||
forceOn: false,
|
||||
hidden: false, // do not show
|
||||
hanging: false, // xenl
|
||||
};
|
||||
|
||||
var screen = [];
|
||||
var blinkIval;
|
||||
var cursorFlashStartIval;
|
||||
|
||||
/** Clear screen */
|
||||
function _clear() {
|
||||
for (var i = W*H-1; i>=0; i--) {
|
||||
var cell = screen[i];
|
||||
cell.t = ' ';
|
||||
cell.bg = cursor.bg;
|
||||
cell.fg = cursor.fg;
|
||||
cell.bold = false;
|
||||
_draw(cell);
|
||||
}
|
||||
}
|
||||
var frakturExceptions = {
|
||||
'C': '\u212d',
|
||||
'H': '\u210c',
|
||||
'I': '\u2111',
|
||||
'R': '\u211c',
|
||||
'Z': '\u2128',
|
||||
};
|
||||
|
||||
/** Set text and color at XY */
|
||||
function _cellAt(y, x) {
|
||||
return screen[y*W+x];
|
||||
// for BEL
|
||||
var audioCtx = null;
|
||||
try {
|
||||
audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)();
|
||||
} catch (er) {
|
||||
console.error("Browser does not support AudioContext, can't beep.", er);
|
||||
}
|
||||
|
||||
/** Get cell under cursor */
|
||||
@@ -38,13 +75,6 @@ var Screen = (function () {
|
||||
return screen[cursor.y*W + cursor.x];
|
||||
}
|
||||
|
||||
/** Enable or disable cursor visibility */
|
||||
function _cursorEnable(enable) {
|
||||
cursor.hidden = !enable;
|
||||
cursor.a &= enable;
|
||||
_draw(_curCell());
|
||||
}
|
||||
|
||||
/** Safely move cursor */
|
||||
function cursorSet(y, x) {
|
||||
// Hide and prevent from showing up during the move
|
||||
@@ -59,17 +89,49 @@ var Screen = (function () {
|
||||
|
||||
/** Update cell on display. inv = invert (for cursor) */
|
||||
function _draw(cell, inv) {
|
||||
if (!cell) return;
|
||||
if (typeof inv == 'undefined') {
|
||||
inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
|
||||
}
|
||||
|
||||
var e = cell.e, fg, bg;
|
||||
var elem = cell.e, fg, bg, cn, t;
|
||||
// Colors
|
||||
fg = inv ? cell.bg : cell.fg;
|
||||
bg = inv ? cell.fg : cell.bg;
|
||||
// Update
|
||||
e.innerText = (cell.t + ' ')[0];
|
||||
e.className = 'fg' + fg + ' bg' + bg + (cell.bold ? ' bold' : '');
|
||||
elem.textContent = t = (cell.t + ' ')[0];
|
||||
|
||||
cn = 'fg' + fg + ' bg' + bg;
|
||||
if (cell.attrs & (1<<0)) cn += ' bold';
|
||||
if (cell.attrs & (1<<2)) cn += ' italic';
|
||||
if (cell.attrs & (1<<3)) cn += ' under';
|
||||
if (cell.attrs & (1<<4)) cn += ' blink';
|
||||
if (cell.attrs & (1<<5)) {
|
||||
cn += ' fraktur';
|
||||
// perform substitution
|
||||
if (t >= 'a' && t <= 'z') {
|
||||
t = String.fromCodePoint(0x1d51e - 97 + t.charCodeAt(0));
|
||||
}
|
||||
else if (t >= 'A' && t <= 'Z') {
|
||||
// this set is incomplete, some exceptions are needed
|
||||
if (frakturExceptions.hasOwnProperty(t)) {
|
||||
t = frakturExceptions[t];
|
||||
} else {
|
||||
t = String.fromCodePoint(0x1d504 - 65 + t.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
elem.textContent = t;
|
||||
}
|
||||
if (cell.attrs & (1<<6)) cn += ' strike';
|
||||
|
||||
if (cell.attrs & (1<<1)) {
|
||||
cn += ' faint';
|
||||
// faint requires special html - otherwise it would also dim the background.
|
||||
// we use opacity on the text...
|
||||
elem.innerHTML = '<span>' + e(elem.textContent) + '</span>';
|
||||
}
|
||||
|
||||
elem.className = cn;
|
||||
}
|
||||
|
||||
/** Show entire screen */
|
||||
@@ -97,8 +159,21 @@ var Screen = (function () {
|
||||
(function() {
|
||||
var x = i % W;
|
||||
var y = Math.floor(i / W);
|
||||
e.addEventListener('click', function () {
|
||||
Input.onTap(y, x);
|
||||
e.addEventListener('mouseenter', function (evt) {
|
||||
Input.onMouseMove(x, y);
|
||||
});
|
||||
e.addEventListener('mousedown', function (evt) {
|
||||
Input.onMouseDown(x, y, evt.button+1);
|
||||
});
|
||||
e.addEventListener('mouseup', function (evt) {
|
||||
Input.onMouseUp(x, y, evt.button+1);
|
||||
});
|
||||
e.addEventListener('contextmenu', function (evt) {
|
||||
evt.preventDefault();
|
||||
});
|
||||
e.addEventListener('mousewheel', function (evt) {
|
||||
Input.onMouseWheel(x, y, evt.deltaY>0?1:-1);
|
||||
return false;
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -111,8 +186,9 @@ var Screen = (function () {
|
||||
|
||||
cell = {
|
||||
t: ' ',
|
||||
fg: cursor.fg,
|
||||
bg: cursor.bg,
|
||||
fg: 7,
|
||||
bg: 0, // the colors will be replaced immediately as we receive data (user won't see this)
|
||||
attrs: 0,
|
||||
e: e,
|
||||
x: i % W,
|
||||
y: Math.floor(i / W),
|
||||
@@ -128,30 +204,39 @@ var Screen = (function () {
|
||||
clearInterval(blinkIval);
|
||||
blinkIval = setInterval(function () {
|
||||
cursor.a = !cursor.a;
|
||||
if (cursor.hidden) {
|
||||
// TODO try to invent a new way to indicate "hanging" - this is copied from gtkterm
|
||||
if (cursor.hidden || cursor.hanging) {
|
||||
cursor.a = false;
|
||||
}
|
||||
|
||||
if (!cursor.suppress) {
|
||||
_draw(_curCell(), cursor.a);
|
||||
_draw(_curCell(), cursor.forceOn || cursor.a);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// blink attribute
|
||||
setInterval(function () {
|
||||
$('#screen').removeClass('blink-hide');
|
||||
setTimeout(function () {
|
||||
$('#screen').addClass('blink-hide');
|
||||
}, 800); // 200 ms ON
|
||||
}, 1000);
|
||||
|
||||
inited = true;
|
||||
}
|
||||
|
||||
/** Decode two-byte number */
|
||||
function parse2B(s, i) {
|
||||
return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127;
|
||||
}
|
||||
|
||||
var SEQ_SET_COLOR = 1;
|
||||
var SEQ_SET_COLOR_ATTR = 1;
|
||||
var SEQ_REPEAT = 2;
|
||||
var SEQ_SET_COLOR = 3;
|
||||
var SEQ_SET_ATTR = 4;
|
||||
|
||||
function _load_content(str) {
|
||||
var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, bold, cell;
|
||||
var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell;
|
||||
|
||||
if (!inited) _init();
|
||||
|
||||
var cursorMoved;
|
||||
|
||||
// Set size
|
||||
num = parse2B(str, i); i += 2; // height
|
||||
num2 = parse2B(str, i); i += 2; // width
|
||||
@@ -163,32 +248,45 @@ var Screen = (function () {
|
||||
// Cursor position
|
||||
num = parse2B(str, i); i += 2; // row
|
||||
num2 = parse2B(str, i); i += 2; // col
|
||||
cursorMoved = (cursor.x != num2 || cursor.y != num);
|
||||
cursorSet(num, num2);
|
||||
// console.log("Cursor at ",num, num2);
|
||||
|
||||
// Attributes
|
||||
num = parse2B(str, i); i += 2; // fg bg bold hidden
|
||||
cursor.fg = num & 0x0F;
|
||||
cursor.bg = (num & 0xF0) >> 4;
|
||||
cursor.bold = !!(num & 0x100);
|
||||
cursor.hidden = !(num & 0x200);
|
||||
// console.log("FG ",cursor.fg, ", BG ", cursor.bg,", BOLD ", cursor.bold, ", HIDE ", cursor.hidden);
|
||||
cursor.hidden = !(num & 0x0001);
|
||||
cursor.hanging = !!(num & 0x0002);
|
||||
// console.log("Attributes word ",num.toString(16)+'h');
|
||||
|
||||
fg = cursor.fg;
|
||||
bg = cursor.bg;
|
||||
bold = cursor.bold;
|
||||
Input.setAlts(
|
||||
!!(num & 0x0004), // cu
|
||||
!!(num & 0x0008), // np
|
||||
!!(num & 0x0010) // fn
|
||||
);
|
||||
|
||||
fg = 7;
|
||||
bg = 0;
|
||||
attrs = 0;
|
||||
|
||||
// Here come the content
|
||||
while(i < str.length && ci<W*H) {
|
||||
|
||||
j = str[i++];
|
||||
jc = j.charCodeAt(0);
|
||||
if (jc == SEQ_SET_COLOR) {
|
||||
if (jc == SEQ_SET_COLOR_ATTR) {
|
||||
num = parse3B(str, i); i += 3;
|
||||
fg = num & 0x0F;
|
||||
bg = (num & 0xF0) >> 4;
|
||||
attrs = (num & 0xFF00)>>8;
|
||||
}
|
||||
else if (jc == SEQ_SET_COLOR) {
|
||||
num = parse2B(str, i); i += 2;
|
||||
fg = num & 0x0F;
|
||||
bg = (num & 0xF0) >> 4;
|
||||
bold = !!(num & 0x100);
|
||||
// console.log("Switch to ",fg,bg,bold);
|
||||
}
|
||||
else if (jc == SEQ_SET_ATTR) {
|
||||
num = parse2B(str, i); i += 2;
|
||||
attrs = num & 0xFF;
|
||||
}
|
||||
else if (jc == SEQ_REPEAT) {
|
||||
num = parse2B(str, i); i += 2;
|
||||
@@ -198,7 +296,7 @@ var Screen = (function () {
|
||||
cell.fg = fg;
|
||||
cell.bg = bg;
|
||||
cell.t = t;
|
||||
cell.bold = bold;
|
||||
cell.attrs = attrs;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -207,12 +305,25 @@ var Screen = (function () {
|
||||
t = cell.t = j;
|
||||
cell.fg = fg;
|
||||
cell.bg = bg;
|
||||
cell.bold = bold;
|
||||
cell.attrs = attrs;
|
||||
// console.log("Symbol ", j);
|
||||
}
|
||||
}
|
||||
|
||||
_drawAll();
|
||||
|
||||
// if (!cursor.hidden || cursor.hanging || !cursor.suppress) {
|
||||
// // hide cursor asap
|
||||
// _draw(_curCell(), false);
|
||||
// }
|
||||
|
||||
if (cursorMoved) {
|
||||
cursor.forceOn = true;
|
||||
cursorFlashStartIval = setTimeout(function() {
|
||||
cursor.forceOn = false;
|
||||
}, 1200);
|
||||
_draw(_curCell(), true);
|
||||
}
|
||||
}
|
||||
|
||||
function _load_labels(str) {
|
||||
@@ -221,11 +332,39 @@ var Screen = (function () {
|
||||
qsa('#buttons button').forEach(function(x, i) {
|
||||
var s = pieces[i+1].trim();
|
||||
// if empty string, use the "dim" effect and put nbsp instead to stretch the btn vertically
|
||||
x.innerHTML = s.length >0 ? e(s) : " ";
|
||||
x.innerHTML = s.length > 0 ? e(s) : " ";
|
||||
x.style.opacity = s.length > 0 ? 1 : 0.2;
|
||||
});
|
||||
}
|
||||
|
||||
function _beep()
|
||||
{
|
||||
var osc, gain;
|
||||
if (!audioCtx) return;
|
||||
|
||||
// Main beep
|
||||
osc = audioCtx.createOscillator();
|
||||
gain = audioCtx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
gain.gain.value = 0.5;
|
||||
osc.frequency.value = 750;
|
||||
osc.type = 'sine';
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime+0.05);
|
||||
|
||||
// Surrogate beep (making it sound like 'oops')
|
||||
osc = audioCtx.createOscillator();
|
||||
gain = audioCtx.createGain();
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
gain.gain.value = 0.2;
|
||||
osc.frequency.value = 400;
|
||||
osc.type = 'sine';
|
||||
osc.start(audioCtx.currentTime+0.05);
|
||||
osc.stop(audioCtx.currentTime+0.08);
|
||||
}
|
||||
|
||||
/** Load screen content from a binary sequence (new) */
|
||||
function load(str) {
|
||||
var content = str.substr(1);
|
||||
@@ -236,6 +375,9 @@ var Screen = (function () {
|
||||
case 'T':
|
||||
_load_labels(content);
|
||||
break;
|
||||
case 'B':
|
||||
_beep();
|
||||
break;
|
||||
default:
|
||||
console.warn("Bad data message type, ignoring.");
|
||||
}
|
||||
@@ -249,6 +391,8 @@ var Screen = (function () {
|
||||
/** Handle connections */
|
||||
var Conn = (function() {
|
||||
var ws;
|
||||
var heartbeatTout;
|
||||
var pingIv;
|
||||
|
||||
function onOpen(evt) {
|
||||
console.log("CONNECTED");
|
||||
@@ -258,14 +402,20 @@ var Conn = (function() {
|
||||
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
|
||||
setTimeout(function() {
|
||||
init();
|
||||
}, 1000);
|
||||
}, 200);
|
||||
// this happens when the buffer gets fucked up via invalid unicode.
|
||||
// we basically use polling instead of socket then
|
||||
}
|
||||
|
||||
function onMessage(evt) {
|
||||
try {
|
||||
console.log("RX: ", evt.data);
|
||||
// Assume all our messages are screen updates
|
||||
Screen.load(evt.data);
|
||||
// . = heartbeat
|
||||
if (evt.data != '.') {
|
||||
//console.log("RX: ", evt.data);
|
||||
// Assume all our messages are screen updates
|
||||
Screen.load(evt.data);
|
||||
}
|
||||
heartbeat();
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -273,6 +423,7 @@ var Conn = (function() {
|
||||
|
||||
function doSend(message) {
|
||||
console.log("TX: ", message);
|
||||
|
||||
if (!ws) return; // for dry testing
|
||||
if (ws.readyState != 1) {
|
||||
console.error("Socket not ready");
|
||||
@@ -285,6 +436,8 @@ var Conn = (function() {
|
||||
}
|
||||
|
||||
function init() {
|
||||
heartbeat();
|
||||
|
||||
ws = new WebSocket("ws://"+_root+"/term/update.ws");
|
||||
ws.onopen = onOpen;
|
||||
ws.onclose = onClose;
|
||||
@@ -297,11 +450,33 @@ var Conn = (function() {
|
||||
if (status !== 200) location.reload(true);
|
||||
console.log("Data received!");
|
||||
Screen.load(resp);
|
||||
heartbeat();
|
||||
|
||||
showPage();
|
||||
});
|
||||
}
|
||||
|
||||
function heartbeat() {
|
||||
clearTimeout(heartbeatTout);
|
||||
heartbeatTout = setTimeout(heartbeatFail, 2000);
|
||||
}
|
||||
|
||||
function heartbeatFail() {
|
||||
console.error("Heartbeat lost, probing server...");
|
||||
pingIv = setInterval(function() {
|
||||
console.log("> ping");
|
||||
$.get('http://'+_root+'/system/ping', function(resp, status) {
|
||||
if (status == 200) {
|
||||
clearInterval(pingIv);
|
||||
console.info("Server ready, reloading page...");
|
||||
location.reload();
|
||||
}
|
||||
}, {
|
||||
timeout: 100,
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return {
|
||||
ws: null,
|
||||
init: init,
|
||||
@@ -309,56 +484,228 @@ var Conn = (function() {
|
||||
};
|
||||
})();
|
||||
|
||||
/** User input */
|
||||
/**
|
||||
* User input
|
||||
*
|
||||
* --- Rx messages: ---
|
||||
* S - screen content (binary encoding of the entire screen with simple compression)
|
||||
* T - text labels - Title and buttons, \0x01-separated
|
||||
* B - beep
|
||||
* . - heartbeat
|
||||
*
|
||||
* --- Tx messages ---
|
||||
* s - string
|
||||
* b - action button
|
||||
* p - mb press
|
||||
* r - mb release
|
||||
* m - mouse move
|
||||
*/
|
||||
var Input = (function() {
|
||||
function sendStrMsg(str) {
|
||||
Conn.send("STR:"+str);
|
||||
}
|
||||
var opts = {
|
||||
np_alt: false,
|
||||
cu_alt: false,
|
||||
fn_alt: false,
|
||||
};
|
||||
|
||||
function sendPosMsg(y, x) {
|
||||
Conn.send("TAP:"+y+','+x);
|
||||
function sendStrMsg(str) {
|
||||
Conn.send("s"+str);
|
||||
}
|
||||
|
||||
function sendBtnMsg(n) {
|
||||
Conn.send("BTN:"+n);
|
||||
Conn.send("b"+Chr(n));
|
||||
}
|
||||
|
||||
function fa(alt, normal) {
|
||||
return opts.fn_alt ? alt : normal;
|
||||
}
|
||||
|
||||
function ca(alt, normal) {
|
||||
return opts.cu_alt ? alt : normal;
|
||||
}
|
||||
|
||||
function na(alt, normal) {
|
||||
return opts.np_alt ? alt : normal;
|
||||
}
|
||||
|
||||
function _bindFnKeys() {
|
||||
var keymap = {
|
||||
'tab': '\x09',
|
||||
'backspace': '\x08',
|
||||
'enter': '\x0d',
|
||||
'ctrl+enter': '\x0a',
|
||||
'esc': '\x1b',
|
||||
'up': ca('\x1bOA', '\x1b[A'),
|
||||
'down': ca('\x1bOB', '\x1b[B'),
|
||||
'right': ca('\x1bOC', '\x1b[C'),
|
||||
'left': ca('\x1bOD', '\x1b[D'),
|
||||
'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')),
|
||||
'insert': '\x1b[2~',
|
||||
'delete': '\x1b[3~',
|
||||
'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')),
|
||||
'pageup': '\x1b[5~',
|
||||
'pagedown': '\x1b[6~',
|
||||
'f1': fa('\x1bOP', '\x1b[11~'),
|
||||
'f2': fa('\x1bOQ', '\x1b[12~'),
|
||||
'f3': fa('\x1bOR', '\x1b[13~'),
|
||||
'f4': fa('\x1bOS', '\x1b[14~'),
|
||||
'f5': '\x1b[15~', // note the disconnect
|
||||
'f6': '\x1b[17~',
|
||||
'f7': '\x1b[18~',
|
||||
'f8': '\x1b[19~',
|
||||
'f9': '\x1b[20~',
|
||||
'f10': '\x1b[21~', // note the disconnect
|
||||
'f11': '\x1b[23~',
|
||||
'f12': '\x1b[24~',
|
||||
'shift+f1': fa('\x1bO1;2P', '\x1b[25~'),
|
||||
'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect
|
||||
'shift+f3': fa('\x1bO1;2R', '\x1b[28~'),
|
||||
'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect
|
||||
'shift+f5': fa('\x1b[15;2~', '\x1b[31~'),
|
||||
'shift+f6': fa('\x1b[17;2~', '\x1b[32~'),
|
||||
'shift+f7': fa('\x1b[18;2~', '\x1b[33~'),
|
||||
'shift+f8': fa('\x1b[19;2~', '\x1b[34~'),
|
||||
'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is?
|
||||
'shift+f10': fa('\x1b[21;2~', '\x1b[36~'),
|
||||
'shift+f11': fa('\x1b[22;2~', '\x1b[37~'),
|
||||
'shift+f12': fa('\x1b[23;2~', '\x1b[38~'),
|
||||
'np_0': na('\x1bOp', '0'),
|
||||
'np_1': na('\x1bOq', '1'),
|
||||
'np_2': na('\x1bOr', '2'),
|
||||
'np_3': na('\x1bOs', '3'),
|
||||
'np_4': na('\x1bOt', '4'),
|
||||
'np_5': na('\x1bOu', '5'),
|
||||
'np_6': na('\x1bOv', '6'),
|
||||
'np_7': na('\x1bOw', '7'),
|
||||
'np_8': na('\x1bOx', '8'),
|
||||
'np_9': na('\x1bOy', '9'),
|
||||
'np_mul': na('\x1bOR', '*'),
|
||||
'np_add': na('\x1bOl', '+'),
|
||||
'np_sub': na('\x1bOS', '-'),
|
||||
'np_point': na('\x1bOn', '.'),
|
||||
'np_div': na('\x1bOQ', '/'),
|
||||
// we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest)
|
||||
};
|
||||
|
||||
for (var k in keymap) {
|
||||
if (keymap.hasOwnProperty(k)) {
|
||||
bind(k, keymap[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bind(combo, str) {
|
||||
// mac fix - allow also cmd
|
||||
if (combo.indexOf('ctrl+') !== -1) {
|
||||
combo += ',' + combo.replace('ctrl', 'command');
|
||||
}
|
||||
|
||||
// unbind possible old binding
|
||||
key.unbind(combo);
|
||||
|
||||
key(combo, function (e) {
|
||||
e.preventDefault();
|
||||
sendStrMsg(str)
|
||||
});
|
||||
}
|
||||
|
||||
function _initKeys() {
|
||||
// This takes care of text characters typed
|
||||
window.addEventListener('keypress', function(evt) {
|
||||
var str = '';
|
||||
if (evt.key) str = evt.key;
|
||||
else if (evt.which) str = String.fromCodePoint(evt.which);
|
||||
if (str.length>0 && str.charCodeAt(0) >= 32) {
|
||||
// console.log("Typed ", str);
|
||||
sendStrMsg(str);
|
||||
}
|
||||
});
|
||||
|
||||
// ctrl-letter codes are sent as simple low ASCII codes
|
||||
for (var i = 1; i<=26;i++) {
|
||||
bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i));
|
||||
}
|
||||
bind('ctrl+]', '\x1b'); // alternate way to enter ESC
|
||||
bind('ctrl+\\', '\x1c');
|
||||
bind('ctrl+[', '\x1d');
|
||||
bind('ctrl+^', '\x1e');
|
||||
bind('ctrl+_', '\x1f');
|
||||
|
||||
_bindFnKeys();
|
||||
}
|
||||
|
||||
var mb1 = 0;
|
||||
var mb2 = 0;
|
||||
var mb3 = 0;
|
||||
|
||||
function init() {
|
||||
window.addEventListener('keypress', function(e) {
|
||||
var code = +e.which;
|
||||
if (code >= 32 && code < 127) {
|
||||
var ch = String.fromCharCode(code);
|
||||
//console.log("Typed ", ch, "code", code, e);
|
||||
sendStrMsg(ch);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', function(e) {
|
||||
var code = e.keyCode;
|
||||
//console.log("Down ", code, e);
|
||||
switch(code) {
|
||||
case 8: sendStrMsg('\x08'); break;
|
||||
case 10:
|
||||
case 13: sendStrMsg('\x0d\x0a'); break;
|
||||
case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences
|
||||
case 37: sendStrMsg('\x1b[D'); break;
|
||||
case 38: sendStrMsg('\x1b[A'); break;
|
||||
case 39: sendStrMsg('\x1b[C'); break;
|
||||
case 40: sendStrMsg('\x1b[B'); break;
|
||||
}
|
||||
});
|
||||
_initKeys();
|
||||
|
||||
// Button presses
|
||||
qsa('#buttons button').forEach(function(s) {
|
||||
s.addEventListener('click', function() {
|
||||
sendBtnMsg(+this.dataset['n']);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('mousedown', function(evt) {
|
||||
if (evt.button == 0) mb1 = 1;
|
||||
if (evt.button == 1) mb2 = 1;
|
||||
if (evt.button == 2) mb3 = 1;
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', function(evt) {
|
||||
if (evt.button == 0) mb1 = 0;
|
||||
if (evt.button == 1) mb2 = 0;
|
||||
if (evt.button == 2) mb3 = 0;
|
||||
});
|
||||
}
|
||||
|
||||
function packModifiersForMouse() {
|
||||
return (key.isModifier('ctrl')?1:0) |
|
||||
(key.isModifier('shift')?2:0) |
|
||||
(key.isModifier('alt')?4:0) |
|
||||
(key.isModifier('meta')?8:0);
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
onTap: sendPosMsg,
|
||||
// onTap: sendPosMsg,
|
||||
sendString: sendStrMsg,
|
||||
setAlts: function(cu, np, fn) {
|
||||
if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn) {
|
||||
opts.cu_alt = cu;
|
||||
opts.np_alt = np;
|
||||
opts.fn_alt = fn;
|
||||
|
||||
// rebind keys - codes have changed
|
||||
_bindFnKeys();
|
||||
}
|
||||
},
|
||||
onMouseMove: function (x, y) {
|
||||
var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0;
|
||||
var m = packModifiersForMouse();
|
||||
Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
|
||||
},
|
||||
onMouseDown: function (x, y, b) {
|
||||
if (b > 3 || b < 1) return;
|
||||
var m = packModifiersForMouse();
|
||||
Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
|
||||
console.log("B ",b," M ",m);
|
||||
},
|
||||
onMouseUp: function (x, y, b) {
|
||||
if (b > 3 || b < 1) return;
|
||||
var m = packModifiersForMouse();
|
||||
Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
|
||||
console.log("B ",b," M ",m);
|
||||
},
|
||||
onMouseWheel: function (x, y, dir) {
|
||||
// -1 ... btn 4 (away from user)
|
||||
// +1 ... btn 5 (towards user)
|
||||
var m = packModifiersForMouse();
|
||||
var b = (dir < 0 ? 4 : 5);
|
||||
Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m));
|
||||
console.log("B ",b," M ",m);
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
+20
-2
@@ -16,6 +16,11 @@ return [
|
||||
|
||||
'title.term' => 'Terminal',
|
||||
|
||||
'term_nav.config' => 'Config',
|
||||
'term_nav.wifi' => 'WiFi',
|
||||
'term_nav.help' => 'Help',
|
||||
'term_nav.about' => 'About',
|
||||
|
||||
'net.ap' => 'DHCP Server (AP)',
|
||||
'net.sta' => 'DHCP Client (Station)',
|
||||
'net.sta_mac' => 'Station MAC',
|
||||
@@ -37,6 +42,9 @@ return [
|
||||
'term.buttons' => 'Button labels',
|
||||
'term.theme' => 'Color scheme',
|
||||
'term.parser_tout_ms' => 'Parser timeout',
|
||||
'term.display_tout_ms' => 'Redraw delay',
|
||||
'term.display_cooldown_ms' => 'Redraw cooldown',
|
||||
'term.fn_alt_mode' => 'SS3 Fn keys',
|
||||
|
||||
// terminal color labels
|
||||
'color.0' => 'Black',
|
||||
@@ -100,10 +108,20 @@ return [
|
||||
|
||||
'wifi.conn.status' => 'Status:',
|
||||
'wifi.conn.back_to_config' => 'Back to WiFi config',
|
||||
'wifi.conn.telemetry_lost' => 'Telemetry lost, something went wrong. Try again...',
|
||||
'wifi.conn.telemetry_lost' => 'Telemetry lost; something went wrong, or your device disconnected.',
|
||||
'wifi.conn.explain_android_sucks' => '
|
||||
If you\'re configuring ESPTerm via a smartphone, or were connected
|
||||
from another external network, your device may lose connection and this
|
||||
progress indicator won\'t work. Please wait a while (~ 15 seconds),
|
||||
then check if the connection succeeded.',
|
||||
|
||||
'wifi.conn.explain_reset' => '
|
||||
To force enable the built-in AP, hold the BOOT
|
||||
button until the blue LED starts flashing. Hold the button longer (until the LED
|
||||
flashes rapidly) for a "factory reset".',
|
||||
|
||||
'wifi.conn.disabled' =>"Station mode is disabled.",
|
||||
'wifi.conn.idle' =>"Idle, not connected and with no IP.",
|
||||
'wifi.conn.idle' =>"Idle, not connected and has no IP.",
|
||||
'wifi.conn.success' => "Connected! Received IP ",
|
||||
'wifi.conn.working' => "Connecting to selected AP",
|
||||
'wifi.conn.fail' => "Connection failed, check settings & try again. Cause: ",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
echo "Packing js..."
|
||||
|
||||
cat jssrc/chibi.js \
|
||||
jssrc/keymaster.js \
|
||||
jssrc/utils.js \
|
||||
jssrc/modal.js \
|
||||
jssrc/notif.js \
|
||||
|
||||
@@ -110,6 +110,24 @@
|
||||
<span class="mq-no-phone"> ms</span>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="display_tout_ms"><?= tr('term.display_tout_ms') ?><span class="mq-phone"> (ms)</span></label>
|
||||
<input type="number" step=1 min=0 name="display_tout_ms" id="display_tout_ms" value="%display_tout_ms%" required>
|
||||
<span class="mq-no-phone"> ms</span>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="display_cooldown_ms"><?= tr('term.display_cooldown_ms') ?><span class="mq-phone"> (ms)</span></label>
|
||||
<input type="number" step=1 min=0 name="display_cooldown_ms" id="display_cooldown_ms" value="%display_cooldown_ms%" required>
|
||||
<span class="mq-no-phone"> ms</span>
|
||||
</div>
|
||||
|
||||
<div class="Row checkbox" >
|
||||
<label><?= tr('term.fn_alt_mode') ?></label><!--
|
||||
--><span class="box" tabindex=0 role=checkbox></span>
|
||||
<input type="hidden" id="fn_alt_mode" name="fn_alt_mode" value="%fn_alt_mode%">
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-2">
|
||||
<form class="Box str mobcol expanded" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-2">
|
||||
<h2 tabindex=0><?= tr('wifi.sta') ?></h2>
|
||||
|
||||
<div class="Row checkbox x-sta-toggle">
|
||||
|
||||
@@ -5,9 +5,15 @@
|
||||
<a href="<?= e(url('cfg_wifi')) ?>" id="backbtn" class="button"><?= tr('wifi.conn.back_to_config') ?></a>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<p><?= tr('wifi.conn.explain_android_sucks') ?></p>
|
||||
<p><?= tr('wifi.conn.explain_reset') ?></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var xhr = new XMLHttpRequest();
|
||||
var abortTmeo;
|
||||
var failCounter = 0;
|
||||
|
||||
var messages = <?= json_encode([
|
||||
'disabled' => tr('wifi.conn.disabled'),
|
||||
@@ -17,33 +23,63 @@
|
||||
'fail' => tr('wifi.conn.fail'),
|
||||
]) ?>;
|
||||
|
||||
function onFail() {
|
||||
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>);
|
||||
$('.anim-dots').addClass('hidden');
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
xhr.open("GET", 'http://'+_root+'<?= url('wifi_connstatus', true) ?>');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) {
|
||||
clearTimeout(abortTmeo);
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
var done = false;
|
||||
var msg = messages[data.status] || '...';
|
||||
if (data.status == 'success') msg += data.ip;
|
||||
if (data.status == 'fail') msg += data.cause;
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200) {
|
||||
clearTimeout(abortTmeo);
|
||||
|
||||
$("#status").html(msg);
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
var done = false;
|
||||
var msg = messages[data.status] || '...';
|
||||
|
||||
if (done) {
|
||||
// $('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
if (data.status == 'success') {
|
||||
msg += data.ip;
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (data.status == 'fail') {
|
||||
msg += data.cause;
|
||||
done = true;
|
||||
}
|
||||
|
||||
$("#status").html(msg);
|
||||
|
||||
if (done) {
|
||||
// $('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
} else {
|
||||
// ask again after a short delay
|
||||
window.setTimeout(getStatus, 1000);
|
||||
}
|
||||
} catch(e) {
|
||||
failCounter++;
|
||||
console.log(e);
|
||||
// repeat
|
||||
if (failCounter > 5) {
|
||||
onFail();
|
||||
}
|
||||
else {
|
||||
window.setTimeout(getStatus, 1000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.setTimeout(getStatus, 1000);
|
||||
onFail();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// XHR timeout
|
||||
abortTmeo = setTimeout(function () {
|
||||
xhr.abort();
|
||||
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>);
|
||||
// $('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
onFail();
|
||||
}, 4000);
|
||||
|
||||
xhr.send();
|
||||
|
||||
+563
-324
@@ -1,361 +1,600 @@
|
||||
<div class="Box">
|
||||
<h2>Troubleshooting</h2>
|
||||
<div class="Box fold">
|
||||
<h2>Tips & Troubleshooting</h2>
|
||||
|
||||
<ul>
|
||||
<li>ESPTerm communicates with external µC via UART on pins <b>Rx</b> and <b>Tx</b>.
|
||||
It can be configured in <a href="<?= url('cfg_system') ?>">System Settings</a>.
|
||||
Make sure you're using the correct baud rate and other settings on the other side.
|
||||
<div class="Row v">
|
||||
<img src="/img/adapter.jpg" class="aside" alt="ESPTerm v2">
|
||||
<ul>
|
||||
<li><b>Communication UART (Rx, Tx)</b> can be configured in the <a href="<?= url('cfg_system') ?>">System Settings</a>.
|
||||
|
||||
<li>Boot log and debug messages are available on pin <b>GPIO2</b> (P2) at 115200\,bps, 1 stop bit, no parity.
|
||||
<li><b>Boot log and debug messages</b> are available on pin <b>GPIO2</b> (P2) at 115200\,baud, 1 stop bit, no parity.
|
||||
Those messages may be disabled through compile flags.
|
||||
|
||||
<li>ESPTerm uses 3.3\,V logic. For communication with 5\,V logic (eg. Aurdino), 470\,R
|
||||
protection resistors are recommended. The ESPTerm breakout board already includes those resistors.
|
||||
<li><b>Loopback test</b>: Connect the Rx and Tx pins with a piece of wire. Anything you type in the browser should
|
||||
appear on the screen. Set Parser Timeout = 0 in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>
|
||||
to be able to manually enter escape sequences.
|
||||
|
||||
<li>If the Low Voltage Detector ("LVD") LED on the ESPTerm breakout lights up, your power connections have too high
|
||||
resistance or the power supply is insufficient. ESP8266 can consume in short spikes up to 500\,A and
|
||||
the supply voltage on the module must not drop below about 2.7\,V. Avoid needless breadboard jumpers and too thin wires.
|
||||
<li><b>For best performance</b>, use the module in Client mode (connected to external network) and minimize the number
|
||||
of simultaneous connections. Enabling AP consumes extra RAM and strains the processor.
|
||||
|
||||
<li>Connect Rx and Tx with a piece of wire to verify that the terminal works correctly; you should then immediately
|
||||
see what you type in the browser. <i>This won't work if your ESP8266 board has a built-in USB-serial
|
||||
converter connected to those pins.</i>
|
||||
<li>In AP mode, <b>check that the WiFi channel used is clear</b>; interference may cause flaky connection.
|
||||
A good mobile app to use for this is
|
||||
<a href="https://play.google.com/store/apps/details?id=com.farproc.wifi.analyzer">WiFi Analyzer (Google Play)</a>.
|
||||
Adjust the hotspot strength and range using the Tx Power setting.
|
||||
|
||||
<li>For best performance, use the module in Client mode (connected to external network). Enabling AP mode consumes
|
||||
extra RAM and cycles, since the captive portal DNS server and DHCP server are be loaded.
|
||||
|
||||
<li>In AP mode, check that the channel used by ESPTerm is clear; interference may cause flaky connection. A good mobile app to use for this is <a href="https://play.google.com/store/apps/details?id=com.farproc.wifi.analyzer">WiFi Analyzer (Google Play)</a>
|
||||
|
||||
<li>The terminal browser display needs WebSockets for the real-time updating to work.
|
||||
It will not work with some old browsers (Android < 4.4, IE < 10, Opera Mini)
|
||||
- see the <a href="https://caniuse.com/websockets/embed/">compatibility chart</a>.
|
||||
|
||||
<li>At most 4 clients can be connected to the AP at the same time. This is a limitation of the ESP8266 SDK.
|
||||
</ul>
|
||||
<li>Hold the BOOT button (GPIO0 to GND) for ~1 second to force enable AP. Hold it for ~6 seconds to restore default settings.
|
||||
(This is indicated by the blue LED rapidly flashing). Default settings can be overwritten in the
|
||||
<a href="<?= url('cfg_system') ?>">System Settings</a>.
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Screen</h2>
|
||||
<div class="Box fold">
|
||||
<h2>Escape Sequences Intro</h2>
|
||||
|
||||
<ul>
|
||||
<li>Most ANSI escape sequences are supported.
|
||||
<div class="Row v">
|
||||
<img src="/img/vt100.jpg" class="aside" alt="VT102">
|
||||
|
||||
<li>The initial screen size and other terminal options can be configured in
|
||||
<a href="<?= url('cfg_term') ?>">Terminal Settings</a>
|
||||
<p>ESPTerm emulates VT102 (pictured) with some additions from later VT models and <code>xterm</code>.
|
||||
All commonly used attributes and commands are supported.
|
||||
ESPTerm is capable of displaying ncurses applications such as <i>Midnight Commander</i> using <i>agetty</i>.</p>
|
||||
|
||||
<li>Display updates are sent to the browser after 20 ms of inactivity on the communication UART.
|
||||
This is to avoid hogging the server with tiny updates during large screen repaints.
|
||||
</ul>
|
||||
<p>
|
||||
ESPTerm accepts UTF-8 characters received on the communication UART and displays them on the screen,
|
||||
interpreting some codes as Control Characters. Those are e.g. Carriage Return (13), Line Feed (10), Tab (9), Backspace (8) and Bell (7).
|
||||
Escape sequences start with the control character ESC (27), followed by any number of ASCII characters
|
||||
forming the body of the command. On this page, we'll use <code>\e</code> to indicate ESC.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Escape sequences can be divided based on their first character and structure. Most common types are:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>CSI commands: <code>\e[</code> followed by 1 optional leading character, multiple numbers divided by
|
||||
semicolons, and one or two trailing characters. Those control the cursor, set attributes and manipulate screen content. E.g. <code>\e[?7;10h</code>, <code>\e[2J</code></li>
|
||||
<li>SGR commands: CSI terminated by <code>m</code>. Those set text attributes and colors. E.g. <code>\e[</code></li>
|
||||
<li>OSC commands: <code>\e]</code> followed by any number of UTF-8 characters (ESPTerm supports up to 64)
|
||||
terminated by <code>\e\\</code> (ESC and backslash) or Bell (7). Those are used to exchange text strings.</li>
|
||||
<li>There are a few other commands that don't follow this pattern, such as <code>\e7</code> or
|
||||
<code>\e#8</code>, mostly for historical reasons.</li>
|
||||
</ul>
|
||||
|
||||
<p>A list of the most important escape sequences is presented in the following sections.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box theme-0">
|
||||
<h2>Color Reference</h2>
|
||||
<div class="Box fold">
|
||||
<h2>Screen Behavior</h2>
|
||||
|
||||
<p>Color is set using <code>\e[<c>m</code> where <code><c></code> are numbers separated by semicolons
|
||||
(and <code>\e</code> is ESC, code 27). The actual color representation depends on a color theme which
|
||||
can be selected in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.</p>
|
||||
<div class="Row v">
|
||||
<p>The initial screen size, title text and button labels can be configured in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.</p>
|
||||
|
||||
<b>Foreground</b>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg7 fg0">30</span>
|
||||
<span class="bg0 fg1">31</span>
|
||||
<span class="bg0 fg2">32</span>
|
||||
<span class="bg0 fg3">33</span>
|
||||
<span class="bg0 fg4">34</span>
|
||||
<span class="bg0 fg5">35</span>
|
||||
<span class="bg0 fg6">36</span>
|
||||
<span class="bg0 fg7">37</span>
|
||||
<p>
|
||||
Screen updates are sent to the browser through a WebSocket after some time of inactivity on the communication UART
|
||||
(called "Redraw Delay"). After an update is sent, at least a time of "Redraw Cooldown" must elapse before the next
|
||||
update can be sent. Those delays are used is to avoid burdening the server with tiny updates during a large screen
|
||||
repaint. If you experience issues (broken image due to dropped bytes), try adjusting those config options. It may also
|
||||
be useful to try different baud rates.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg0 fg8">90</span>
|
||||
<span class="bg0 fg9">91</span>
|
||||
<span class="bg0 fg10">92</span>
|
||||
<span class="bg0 fg11">93</span>
|
||||
<span class="bg0 fg12">94</span>
|
||||
<span class="bg0 fg13">95</span>
|
||||
<span class="bg0 fg14">96</span>
|
||||
<span class="bg0 fg15">97</span>
|
||||
</div>
|
||||
|
||||
<b>Background</b>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg0 fg15">40</span>
|
||||
<span class="bg1 fg15">41</span>
|
||||
<span class="bg2 fg15">42</span>
|
||||
<span class="bg3 fg0">43</span>
|
||||
<span class="bg4 fg15">44</span>
|
||||
<span class="bg5 fg15">45</span>
|
||||
<span class="bg6 fg15">46</span>
|
||||
<span class="bg7 fg0">47</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg8 fg15">100</span>
|
||||
<span class="bg9 fg0">101</span>
|
||||
<span class="bg10 fg0">102</span>
|
||||
<span class="bg11 fg0">103</span>
|
||||
<span class="bg12 fg0">104</span>
|
||||
<span class="bg13 fg0">105</span>
|
||||
<span class="bg14 fg0">106</span>
|
||||
<span class="bg15 fg0">107</span>
|
||||
</div>
|
||||
|
||||
<p>To make the text bold, use the "bold" attribute (eg. <code>\e[31;40;1m</code> for
|
||||
<span style="padding: 2px;" class="bg0 fg1 bold">bold red on black</span>).</p>
|
||||
|
||||
<p>The colors are reset to default using <code>\e[0m</code>. For more info, see the tables below.</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<div class="Box fold">
|
||||
<h2>Text Attributes</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
All text attributes are set using SGR commands like <code>\e[10;20;30m</code>, with up to 10 numbers separated by semicolons.
|
||||
To restore all attributes to their default states, use SGR 0: <code>\e[0m</code> or <code>\e[m</code>.
|
||||
</p>
|
||||
|
||||
<p>Those are the supported text attributes SGR codes:</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Style</th><th>Enable</th><th>Disable</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><b>Bold</b></td><td>1</td><td>21, 22</td></tr>
|
||||
<tr><td style="opacity:.6">Faint</td><td>2</td><td>22</td></tr>
|
||||
<tr><td><i>Italic</i></td><td>3</td><td>23</td></tr>
|
||||
<tr><td><u>Underlined</u></td><td>4</td><td>24</td></tr>
|
||||
<tr><td>Blink</td><td>5</td><td>25</td></tr>
|
||||
<tr><td><span style="color:black;background:#ccc;">Inverse</span></td><td>7</td><td>27</td></tr>
|
||||
<tr><td><s>Striked</s></td><td>9</td><td>29</td></tr>
|
||||
<tr><td>𝔉𝔯𝔞𝔨𝔱𝔲𝔯</td><td>20</td><td>23</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold theme-0">
|
||||
<h2>Colors</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>Colors are set using SGR commands (like <code>\e[10;20;30m</code>). The following tables list the SGR codes to use.
|
||||
Selected colors are used for any new text entered, as well as for empty space when using line and screen clearing commands.
|
||||
The configured default colors can be restored using SGR 39 for foreground and SGR 49 for background.</p>
|
||||
|
||||
<p>The actual color representation depends on a color theme which
|
||||
can be selected in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.</p>
|
||||
|
||||
<h3>Foreground colors</h3>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg7 fg0">30</span>
|
||||
<span class="bg0 fg1">31</span>
|
||||
<span class="bg0 fg2">32</span>
|
||||
<span class="bg0 fg3">33</span>
|
||||
<span class="bg0 fg4">34</span>
|
||||
<span class="bg0 fg5">35</span>
|
||||
<span class="bg0 fg6">36</span>
|
||||
<span class="bg0 fg7">37</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg0 fg8">90</span>
|
||||
<span class="bg0 fg9">91</span>
|
||||
<span class="bg0 fg10">92</span>
|
||||
<span class="bg0 fg11">93</span>
|
||||
<span class="bg0 fg12">94</span>
|
||||
<span class="bg0 fg13">95</span>
|
||||
<span class="bg0 fg14">96</span>
|
||||
<span class="bg0 fg15">97</span>
|
||||
</div>
|
||||
|
||||
<h3>Background colors</h3>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg0 fg15">40</span>
|
||||
<span class="bg1 fg15">41</span>
|
||||
<span class="bg2 fg15">42</span>
|
||||
<span class="bg3 fg0">43</span>
|
||||
<span class="bg4 fg15">44</span>
|
||||
<span class="bg5 fg15">45</span>
|
||||
<span class="bg6 fg15">46</span>
|
||||
<span class="bg7 fg0">47</span>
|
||||
</div>
|
||||
|
||||
<div class="colorprev">
|
||||
<span class="bg8 fg15">100</span>
|
||||
<span class="bg9 fg0">101</span>
|
||||
<span class="bg10 fg0">102</span>
|
||||
<span class="bg11 fg0">103</span>
|
||||
<span class="bg12 fg0">104</span>
|
||||
<span class="bg13 fg0">105</span>
|
||||
<span class="bg14 fg0">106</span>
|
||||
<span class="bg15 fg0">107</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>User Input</h2>
|
||||
|
||||
<p>
|
||||
All the user types on their keyboard is sent as-is to the UART, including ESC, ANSI sequences for arrow keys and CR-LF for Enter.
|
||||
</p>
|
||||
<div class="Row v">
|
||||
<h3>Keyboard</h3>
|
||||
|
||||
<p>The buttons under the screen send ASCII codes 1, 2, 3, 4, 5. This choice was made to make their parsing as simple as possible.</p>
|
||||
<p>
|
||||
The user can input text using their keyboard, or on Android, using the on-screen keyboard which is open using
|
||||
a button beneath the screen. Supported are all printable characters, as well as many control keys, such as arrows, Ctrl+letters
|
||||
and function keys. Sequences sent by function keys are based on VT102 and xterm.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Mouse input (click/tap) is sent as <code>\e[<y>;<x>M</code>. You can use this for virtual on-screen buttons.
|
||||
</p>
|
||||
<p>
|
||||
The codes sent by Home, End, F1-F4 and cursor keys are affected by various keyboard modes (Application Cursor Keys,
|
||||
Application Numpad Mode, SS3 Fn Keys Mode).
|
||||
Some can be set in the <a href="<?= url('cfg_term') ?>">Terminal Settings</a>, others via commands.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Here are some examples of control key codes:
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Key</th><th>Code</th><th>Key</th><th>Code</th></tr></thead>
|
||||
<tr>
|
||||
<td>🡑</td>
|
||||
<td><code>\e[A</code></td>
|
||||
<td>F1</td>
|
||||
<td><code>\eOP</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🡓</td>
|
||||
<td><code>\e[B</code></td>
|
||||
<td>F2</td>
|
||||
<td><code>\eOQ</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🡒</td>
|
||||
<td><code>\e[C</code></td>
|
||||
<td>F3</td>
|
||||
<td><code>\eOR</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🡐</td>
|
||||
<td><code>\e[D</code></td>
|
||||
<td>F4</td>
|
||||
<td><code>\eOS</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Home</td>
|
||||
<td><code>\eOH</code></td>
|
||||
<td>F5</td>
|
||||
<td><code>\e[15~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>End</td>
|
||||
<td><code>\eOF</code></td>
|
||||
<td>F6</td>
|
||||
<td><code>\e[17~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Insert</td>
|
||||
<td><code>\e[2~</code></td>
|
||||
<td>F7</td>
|
||||
<td><code>\e[18~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Delete</td>
|
||||
<td><code>\e[3~</code></td>
|
||||
<td>F8</td>
|
||||
<td><code>\e[19~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Page Up</td>
|
||||
<td><code>\e[5~</code></td>
|
||||
<td>F9</td>
|
||||
<td><code>\e[20~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Page Down</td>
|
||||
<td><code>\e[6~</code></td>
|
||||
<td>F10</td>
|
||||
<td><code>\e[21~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Enter</td>
|
||||
<td><code>CR (13)</code></td>
|
||||
<td>F11</td>
|
||||
<td><code>\e[23~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ctrl+Enter</td>
|
||||
<td><code>LF (10)</code></td>
|
||||
<td>F12</td>
|
||||
<td><code>\e[24~</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tab</td>
|
||||
<td><code>TAB (9)</code></td>
|
||||
<td>ESC</td>
|
||||
<td><code>ESC (27)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Backspace</td>
|
||||
<td><code>BS (8)</code></td>
|
||||
<td>Ctrl+A..Z</td>
|
||||
<td><code>ASCII 1-26</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Action buttons</h3>
|
||||
|
||||
<p>
|
||||
The blue buttons under the screen send ASCII codes 1, 2, 3, 4, 5, which incidentally correspond to Ctrl+A,B,C,D,E.
|
||||
This choice was made to make button press parsing as simple as possible.
|
||||
</p>
|
||||
|
||||
<h3>Mouse</h3>
|
||||
|
||||
<p>
|
||||
ESPTerm in the current version does not implement standard mouse input. Instead, clicks/taps produce CSI sequences
|
||||
<code>\e[<i>r</i>;<i>c</i>M</code> (row, column). You can use this for virtual on-screen buttons.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Supported ANSI Escape Sequences</h2>
|
||||
<div class="Box fold">
|
||||
<h2>Cursor Commands</h2>
|
||||
|
||||
<p>Sequences are started by ASCII code 27 (ESC, <code>\e</code>, <code>\x1b</code>, <code>\033</code>)</p>
|
||||
<div class="Row v">
|
||||
<p>
|
||||
The coordinates are 1-based, origin is top left. The cursor can move within the entire screen,
|
||||
or in the active Scrolling Region if Origin Mode is enabled.
|
||||
</p>
|
||||
|
||||
<p>Instead of <code>\a</code> (BELL, ASCII 7) in the commands, you can use `\e\` (ESC + backslash). It's the Text Separator code.</p>
|
||||
<p>After writing a character, the cursor advances to the right. If it has reached the end of the row,
|
||||
it stays on the same line, but writing the next character makes it jump to the start of the next
|
||||
line first, scrolling up if needed. If Auto-wrap mode is disabled, the cursor never wraps or scrolls
|
||||
the screen.
|
||||
</p>
|
||||
|
||||
<h3>Text Attributes</h3>
|
||||
<p>
|
||||
<b>Legend:</b>
|
||||
Italic letters such as <i>n</i> are ASCII numbers that serve as arguments, separated with a semicolon.
|
||||
If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
All text attributes are set using <code>\e[...m</code> where the dots are numbers separated by semicolons.
|
||||
You can combine up to 3 attributes in a single command.
|
||||
</p>
|
||||
<h3>Movement</h3>
|
||||
|
||||
<table class="ansiref">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>Reset text attributes to default</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Bold</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>21 or 22</td>
|
||||
<td>Bold off</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Inverse (fg/bg swap when printing)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>27</td>
|
||||
<td>Inverse OFF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>30-37, 90-97</td>
|
||||
<td>Foreground color, normal and bright</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>40-47, 100-107</td>
|
||||
<td>Background color, normal and bright</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>A<br>
|
||||
\e[<i>n</i>B<br>
|
||||
\e[<i>n</i>C<br>
|
||||
\e[<i>n</i>D
|
||||
</td>
|
||||
<td>Move cursor up (A), down (B), right (C), left (D)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>F<br>
|
||||
\e[<i>n</i>E
|
||||
</td>
|
||||
<td>Go <i>n</i> lines up (F) or down (E), start of line</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>r</i>d<br>
|
||||
\e[<i>c</i>G<br>
|
||||
\e[<i>r</i>;<i>c</i>H
|
||||
</td>
|
||||
<td>
|
||||
Go to absolute position - row (d), column (G), or both (H). Use <code>\e[H</code> to go to 1,1.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[6n</td>
|
||||
<td>Query cursor position. Sent back as <code>\e[<i>r</i>;<i>c</i>R</code>.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Cursor</h3>
|
||||
<h3>Save / restore</h3>
|
||||
|
||||
<p>Movement commands scroll the screen if needed. The coordinates are 1-based, origin top left.</p>
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[s<br>\e[u</td>
|
||||
<td>Save (s) or restore (u) cursor position</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e7<br>\e8</td>
|
||||
<td>Save (7) or restore (8) cursor position and attributes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Params</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[<n>A</td>
|
||||
<td>[count]</td>
|
||||
<td>Move cursor up</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>B</td>
|
||||
<td>[count]</td>
|
||||
<td>Move cursor down</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>C</td>
|
||||
<td>[count]</td>
|
||||
<td>Move cursor forward (right)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>D</td>
|
||||
<td>[count]</td>
|
||||
<td>Move cursor backward (left)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>E</td>
|
||||
<td>[count]</td>
|
||||
<td>Go N lines down, start of line</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>F</td>
|
||||
<td>[count]</td>
|
||||
<td>Go N lines up, start of line</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>G</td>
|
||||
<td>column</td>
|
||||
<td>Go to column</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<y>;<x>G</td>
|
||||
<td>[row=1];[col=1]</td>
|
||||
<td>Go to row and column</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[6n</td>
|
||||
<td>--</td>
|
||||
<td>Query cursor position. Position is sent back as <code>\e[?;?R</code> with row;column.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[s</td>
|
||||
<td>--</td>
|
||||
<td>Store position</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[u</td>
|
||||
<td>--</td>
|
||||
<td>Restore position</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e7</td>
|
||||
<td>--</td>
|
||||
<td>Store position & attributes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e8</td>
|
||||
<td>--</td>
|
||||
<td>Restore position & attributes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[?25l</td>
|
||||
<td>--</td>
|
||||
<td>Hide cursor</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[?25h</td>
|
||||
<td>--</td>
|
||||
<td>Show cursor</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[?7l</td>
|
||||
<td>--</td>
|
||||
<td>Disable cursor auto-wrap & auto-scroll at end of screen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[?7h</td>
|
||||
<td>--</td>
|
||||
<td>Enable cursor auto-wrap & auto-scroll at end of screen</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Scrolling Region</h3>
|
||||
|
||||
<h3>Screen</h3>
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[<i>a</i>;<i>b</i>r</td>
|
||||
<td>Set scrolling region to rows <i>a</i> through <i>b</i> and go to 1,1. By default, the
|
||||
scrolling region spans the entire screen height. The cursor can leave the region using
|
||||
absolute position commands, unless Origin Mode (see below) is active.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[?6h<br>\e[?6l</td>
|
||||
<td>Enable (h) or disable (l) Origin Mode and go to 1,1. In Origin Mode, all coordinates
|
||||
are relative to the Scrolling Region and the cursor can't leave the region.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>S<br>
|
||||
\e[<i>n</i>T
|
||||
</td>
|
||||
<td>Move contents of the Scrolling Region up (S) or down (T), pad with empty lines of the current background color.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Params</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[<m>J</td>
|
||||
<td>[mode=0]</td>
|
||||
<td>Clear screen. Mode: 0 - from cursor, 1 - to cursor, 2 - all</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<m>K</td>
|
||||
<td>[mode=0]</td>
|
||||
<td>Erase line. Mode: 0 - from cursor, 1 - to cursor, 2 - all</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>S</td>
|
||||
<td>[lines]</td>
|
||||
<td>Scroll screen content up, add empty line at the bottom</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<n>T</td>
|
||||
<td>[lines]</td>
|
||||
<td>Scroll screen content down, add empty line at the top</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e]W<r>;<c>\a</td>
|
||||
<td>rows;cols</td>
|
||||
<td>Set screen size (max ~ 80x30). This also clears the screen. This is a custom ESPTerm OSC command.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Tab stops</h3>
|
||||
|
||||
<h3>System Commands</h3>
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\eH</td>
|
||||
<td>Set tab stop at the current column. There are, by default, tabs every 8 columns.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>I<br>
|
||||
\e[<i>n</i>Z
|
||||
</td>
|
||||
<td>Advance (I) or go back (Z) <i>n</i> tab stops or end/start of line. ASCII TAB (9) is equivalent to <code>\e[1I</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[0g<br>
|
||||
\e[3g<br>
|
||||
</td>
|
||||
<td>Clear tab stop at the current column (0), or all columns (3).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Params</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\ec</td>
|
||||
<td>--</td>
|
||||
<td>
|
||||
"Device Reset" - clear screen, reset attributes, show cursor & move it to 1,1.
|
||||
The screen size and WiFi settings stay unchanged.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[5n</td>
|
||||
<td>--</td>
|
||||
<td>Query device status, replies with <code>\e[0n</code> "device is OK". Can be used to check if the UART works.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e]TITLE=…\a</td>
|
||||
<td>Title text</td>
|
||||
<td>Set terminal title. This is a custom ESPTerm OSC command.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e]BTNn=…\a</td>
|
||||
<td>1-5, label</td>
|
||||
<td>Set button label. This is a custom ESPTerm OSC command.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ASCII 24 (18h)</td>
|
||||
<td></td>
|
||||
<td>This symbol is sent by ESPTerm when it becomes ready to receive commands. It can be used to wait before it becomes
|
||||
ready on power-up, or to detect a spontaneous ESPTerm restart - that could happen due to RAM exhaustion due
|
||||
to a memory leak, or perhaps a power outage.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Other options</h3>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[?7h<br>\e[?7l</td>
|
||||
<td>Enable (h) or disable (l) cursor auto-wrap and screen auto-scroll</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[?25h<br>\e[?25l</td>
|
||||
<td>Show (h) or hide (l) the cursor</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>Screen Content Manipulation</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
<b>Legend:</b>
|
||||
Italic letters such as <i>n</i> are ASCII numbers that serve as arguments, separated with a semicolon.
|
||||
If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command.
|
||||
</p>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e[<i>m</i>J</td>
|
||||
<td>Clear part of screen. <i>m</i>: 0 - from cursor, 1 - to cursor, 2 - all</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[<i>m</i>K</td>
|
||||
<td>Erase part of line. <i>m</i>: 0 - from cursor, 1 - to cursor, 2 - all</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>X
|
||||
</td>
|
||||
<td>Erase <i>n</i> characters in line.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>L<br>
|
||||
\e[<i>n</i>M
|
||||
</td>
|
||||
<td>Insert (L) or delete (M) <i>n</i> lines. Following lines are pulled up or pushed down.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e[<i>n</i>@<br>
|
||||
\e[<i>nP
|
||||
</td>
|
||||
<td>Insert (@) or delete (P) <i>n</i> characters. The rest of the line is pulled left or pushed right.
|
||||
Characters going past the end of line are lost.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>Alternate Character Sets</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
ESPTerm implements Alternate Character Sets as a way to print box drawing characters
|
||||
and special symbols. A character set can change what each received ASCII character
|
||||
is printed as on the screen (eg. "{" is "π" in codepage 0). The implementation is based
|
||||
on the original VT devices.
|
||||
Since ESPTerm also fully supports UTF-8, you can probably ignore this feature and use
|
||||
Unicode directly. It's added for compatibility with some programs that use this.
|
||||
</p>
|
||||
|
||||
<p>The following codepages are implemented:</p>
|
||||
|
||||
<ul>
|
||||
<li>B - US ASCII (default)</li>
|
||||
<li>A - UK ASCII: # replaced with £</li>
|
||||
<li>0 - Symbols and basic line drawing (standard DEC alternate character set)</li>
|
||||
<li>1 - Symbols and advanced line drawing (based on DOS codepage 437)</li>
|
||||
</ul>
|
||||
|
||||
<p>To see what character maps to which symbol, look in the source code or try it. All codepages use codes 32-127, 32 being space.</p>
|
||||
|
||||
<p>
|
||||
There are two character set slots, G0 and G1.
|
||||
Those slots are selected as active using ASCII codes Shift In and Shift Out (those originally served for shifting
|
||||
a red-black typewriter tape). Each slot (G0 and G1) can have a different codepage assigned. G0 and G1 and the active slot number are
|
||||
saved and restored with the cursor and cleared with a screen reset (<code>\ec</code>).
|
||||
</p>
|
||||
|
||||
<p>The following commands are used:</p>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\e(<i>x</i></td>
|
||||
<td>Set G0 = codepage <i>x</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e)<i>x</i></td>
|
||||
<td>Set G1 = codepage <i>x</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SO (14)</td>
|
||||
<td>Activate G0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SI (15)</td>
|
||||
<td>Activate G1</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Box fold">
|
||||
<h2>System Commands</h2>
|
||||
|
||||
<div class="Row v">
|
||||
<p>
|
||||
It's possible to dynamically change the screen title text and action button labels.
|
||||
Setting an empty label to a button makes it look disabled. The buttons send ASCII 1-5 when clicked.
|
||||
Those changes are not retained after restart.
|
||||
</p>
|
||||
|
||||
<table class="ansiref w100">
|
||||
<thead><tr><th>Code</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>\ec</td>
|
||||
<td>
|
||||
Clear screen, reset attributes and cursor.
|
||||
The screen size, title and button labels remain unchanged.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[5n</td>
|
||||
<td>
|
||||
Query device status, ESPTerm replies with <code>\e[0n</code> "device is OK".
|
||||
Can be used to check if the terminal has booted up and is ready to receive commands.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CAN (24)</td>
|
||||
<td>
|
||||
This ASCII code is not a command, but is sent by ESPTerm when it becomes ready to receive commands.
|
||||
When this code is received on the UART, it means ESPTerm has restarted and is ready. Use this to detect
|
||||
spontaneous restarts which require a full screen repaint.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e]0;<i>title</i>\a</td>
|
||||
<td>Set screen title (this is a standard OSC command)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
\e]<i>81</i>;<i>btn1</i>\a<br>
|
||||
\e]<i>82</i>;<i>btn2</i>\a<br>
|
||||
\e]<i>83</i>;<i>btn3</i>\a<br>
|
||||
\e]<i>84</i>;<i>btn4</i>\a<br>
|
||||
\e]<i>85</i>;<i>btn5</i>\a<br>
|
||||
</td>
|
||||
<td>Set button 1-5 label - eg. <code>\e]81;Yes\a</code>
|
||||
sets the first button text to "Yes".</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>\e[8;<i>r</i>;<i>c</i>t</td>
|
||||
<td>Set screen size (this is a command borrowed from xterm)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
|
||||
<nav id="botnav">
|
||||
<a href="#" onclick="toggleSoftKb(true); return false" class="icn-keyboard mq-tablet-max"></a><!--
|
||||
--><a href="<?= url('cfg_term') ?>"><?= tr('menu.settings') ?></a><!--
|
||||
--><a href="<?= url('help') ?>">Help</a><!--
|
||||
--><a href="<?= url('about') ?>">About</a>
|
||||
--><a href="<?= url('cfg_term') ?>"><?= tr('term_nav.config') ?></a><!--
|
||||
--><a href="<?= url('cfg_wifi') ?>"><?= tr('term_nav.wifi') ?></a><!--
|
||||
--><a href="<?= url('help') ?>"><?= tr('term_nav.help') ?></a><!--
|
||||
--><a href="<?= url('about') ?>"><?= tr('term_nav.about') ?></a>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -29,6 +29,20 @@ form { @include naked(); }
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.v {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aside {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
@include media($phone) {
|
||||
margin: 0; float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: $form-label-w;
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ a:hover {
|
||||
}
|
||||
|
||||
ul > * {
|
||||
padding-top: .2em;
|
||||
padding-bottom: .2em;
|
||||
padding-top: .3em;
|
||||
padding-bottom: .3em;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -103,44 +103,69 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mobcol-base {
|
||||
h2 {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 2px 1.3rem 2px 5px;
|
||||
margin: 0 -5px 0 -5px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
content: '▸';
|
||||
|
||||
top:50%;
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
transform: translate(0,-50%) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded h2::after {
|
||||
transform: translate(-25%,-50%) rotate(-90deg);
|
||||
margin-bottom: dist(0);
|
||||
}
|
||||
|
||||
.Row {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.Row {
|
||||
display: flex;
|
||||
|
||||
&.v {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Box.fold {
|
||||
@include mobcol-base;
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
.Box.mobcol {
|
||||
.Box.fold, .Box.mobcol {
|
||||
h2 {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 2px 1.3rem 2px 5px;
|
||||
margin: 0 -5px 0 -5px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
content: '▸';
|
||||
|
||||
top:50%;
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
transform: translate(0,-50%) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded h2::after {
|
||||
transform: translate(-25%,-50%) rotate(-90deg);
|
||||
margin-bottom: dist(0);
|
||||
}
|
||||
}
|
||||
|
||||
.Row {
|
||||
display: none;
|
||||
}
|
||||
.Box.mobcol {
|
||||
@include mobcol-base;
|
||||
|
||||
#ap-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.Row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#ap-box {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ansiref {
|
||||
.Row table {
|
||||
&,td,th{
|
||||
border: 1px solid #666;
|
||||
}
|
||||
@@ -45,7 +45,9 @@
|
||||
th {
|
||||
background-color: rgba(255,255,255,.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ansiref {
|
||||
td:nth-child(1) {
|
||||
font-family: monospace;
|
||||
}
|
||||
@@ -54,11 +56,7 @@
|
||||
width: 100%;
|
||||
|
||||
td:nth-child(1) {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
td:nth-child(2) {
|
||||
width: 8em;
|
||||
width: 6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ body.term {
|
||||
#screen {
|
||||
@include noselect();
|
||||
font-family: monospace;
|
||||
font-size: 16pt;
|
||||
font-size: 20px;//16pt; - 16pt causes vertical breaks in line drawing characters??
|
||||
white-space: nowrap;
|
||||
background: #111213;
|
||||
padding: 6px;
|
||||
@@ -27,12 +27,11 @@ body.term {
|
||||
span {
|
||||
white-space: pre;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
outline: 1px solid rgba(#ffffff, 0.4);
|
||||
@include media($phone) {
|
||||
outline: 0 none;
|
||||
}
|
||||
}
|
||||
|
||||
display: inline-block;
|
||||
line-height: 1.15em; // this adjusts the spacing
|
||||
width: 0.6em; // horizontal spacing, estimated to be about right
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,11 +85,6 @@ body.term {
|
||||
top: -9999px;
|
||||
}
|
||||
|
||||
// "non-bold"
|
||||
.nb {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
// Tango
|
||||
.theme-0 {
|
||||
$term-colors:
|
||||
@@ -163,10 +157,36 @@ body.term {
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes
|
||||
.bold {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.faint span { // content of faint is wrapped in span
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.under {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.strike {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.underline.strike {
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
|
||||
.blink-hide .blink {
|
||||
color: transparent;
|
||||
}
|
||||
//
|
||||
|
||||
.Row.color-preview {
|
||||
font-family: monospace;
|
||||
font-size: 16pt;
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
/**
|
||||
* Helper that retrieves an arg from `connData->getArgs` and stores it in `buff`. Returns 1 on success
|
||||
*/
|
||||
#define GET_ARG(key) (httpdFindArg(connData->getArgs, key, buff, sizeof(buff)) > 0)
|
||||
#define GET_ARG(key) (httpdFindArg(connData->getArgs, key, buff, sizeof(buff)) >= 0)
|
||||
|
||||
#define STR_HELPER(x) #x
|
||||
#define STR(x) STR_HELPER(x)
|
||||
|
||||
+1
-1
Submodule libesphttpd updated: b804b196fc...13fa224963
+359
-309
@@ -2,50 +2,87 @@
|
||||
/* #line 1 "user/ansi_parser.rl" */
|
||||
#include <esp8266.h>
|
||||
#include "ansi_parser.h"
|
||||
#include "screen.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "ascii.h"
|
||||
#include "apars_logging.h"
|
||||
|
||||
/* Ragel constants block */
|
||||
|
||||
/* #line 10 "user/ansi_parser.c" */
|
||||
/* #line 12 "user/ansi_parser.c" */
|
||||
static const char _ansi_actions[] = {
|
||||
0, 1, 0, 1, 1, 1, 2, 1,
|
||||
3, 1, 4, 1, 5, 1, 6, 1,
|
||||
7, 1, 8, 1, 9, 1, 10, 1,
|
||||
11, 1, 12, 1, 13, 1, 14, 2,
|
||||
9, 10, 2, 9, 11
|
||||
3, 6
|
||||
};
|
||||
|
||||
static const char _ansi_eof_actions[] = {
|
||||
0, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 0, 0, 0, 0, 0
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
static const int ansi_start = 1;
|
||||
static const int ansi_first_final = 26;
|
||||
static const int ansi_first_final = 10;
|
||||
static const int ansi_error = 0;
|
||||
|
||||
static const int ansi_en_CSI_body = 3;
|
||||
static const int ansi_en_OSC_body = 5;
|
||||
static const int ansi_en_CSI_body = 5;
|
||||
static const int ansi_en_STRCMD_body = 7;
|
||||
static const int ansi_en_charsetcmd_body = 9;
|
||||
static const int ansi_en_main = 1;
|
||||
|
||||
|
||||
/* #line 9 "user/ansi_parser.rl" */
|
||||
/* #line 11 "user/ansi_parser.rl" */
|
||||
|
||||
|
||||
// Max nr of CSI parameters
|
||||
#define CSI_N_MAX 10
|
||||
#define STR_CHAR_MAX 64
|
||||
|
||||
static volatile int cs = -1;
|
||||
static volatile bool inside_string = false;
|
||||
|
||||
static ETSTimer resetTim;
|
||||
// public
|
||||
volatile u32 ansi_parser_char_cnt = 0;
|
||||
|
||||
static void ICACHE_FLASH_ATTR
|
||||
resetParserCb(void *arg) {
|
||||
void ICACHE_FLASH_ATTR
|
||||
ansi_parser_reset(void) {
|
||||
if (cs != ansi_start) {
|
||||
cs = ansi_start;
|
||||
warn("Parser timeout, state reset");
|
||||
inside_string = false;
|
||||
apars_reset_utf8buffer();
|
||||
ansi_warn("Parser timeout, state reset");
|
||||
}
|
||||
}
|
||||
|
||||
#define HISTORY_LEN 10
|
||||
|
||||
#if DEBUG_ANSI
|
||||
static char history[HISTORY_LEN + 1];
|
||||
#endif
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_show_context(void)
|
||||
{
|
||||
#if DEBUG_ANSI
|
||||
char buf1[HISTORY_LEN*3+2];
|
||||
char buf2[HISTORY_LEN*3+2];
|
||||
char *b1 = buf1;
|
||||
char *b2 = buf2;
|
||||
char c;
|
||||
|
||||
for(int i=0;i<HISTORY_LEN;i++) {
|
||||
c = history[i];
|
||||
b1 += sprintf(b1, "%2X ", c);
|
||||
if (c < 32 || c > 127) c = '.';
|
||||
b2 += sprintf(b2, "%c ", c);
|
||||
}
|
||||
|
||||
ansi_dbg("Context: %s", buf2);
|
||||
ansi_dbg(" %s", buf1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Linear ANSI chars stream parser
|
||||
*
|
||||
@@ -60,44 +97,115 @@ resetParserCb(void *arg) {
|
||||
* \param len - length of the newdata buffer
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
ansi_parser(const char *newdata, size_t len)
|
||||
ansi_parser(char newchar)
|
||||
{
|
||||
// The CSI code is built here
|
||||
static char csi_leading; //!< Leading char, 0 if none
|
||||
static int csi_ni; //!< Number of the active digit
|
||||
static int csi_n[CSI_N_MAX]; //!< Param digits
|
||||
static char csi_char; //!< CSI action char (end)
|
||||
static char osc_buffer[OSC_CHAR_MAX];
|
||||
static int osc_bi;
|
||||
static char leadchar;
|
||||
static int arg_ni;
|
||||
static int arg_cnt;
|
||||
static int arg[CSI_N_MAX];
|
||||
static char string_buffer[STR_CHAR_MAX];
|
||||
static int str_ni;
|
||||
|
||||
if (len == 0) len = strlen(newdata);
|
||||
|
||||
// Load new data to Ragel vars
|
||||
const char *p = newdata;
|
||||
const char *eof = NULL;
|
||||
const char *pe = newdata + len;
|
||||
// This is used to detect timeout delay (time since last rx char)
|
||||
ansi_parser_char_cnt++;
|
||||
|
||||
// Init Ragel on the first run
|
||||
if (cs == -1) {
|
||||
|
||||
/* #line 84 "user/ansi_parser.c" */
|
||||
/* #line 117 "user/ansi_parser.c" */
|
||||
{
|
||||
cs = ansi_start;
|
||||
}
|
||||
|
||||
/* #line 57 "user/ansi_parser.rl" */
|
||||
/* #line 91 "user/ansi_parser.rl" */
|
||||
|
||||
#if DEBUG_ANSI
|
||||
memset(history, 0, sizeof(history));
|
||||
#endif
|
||||
}
|
||||
|
||||
// schedule state reset
|
||||
if (termconf->parser_tout_ms > 0) {
|
||||
os_timer_disarm(&resetTim);
|
||||
os_timer_setfn(&resetTim, resetParserCb, NULL);
|
||||
os_timer_arm(&resetTim, termconf->parser_tout_ms, 0);
|
||||
#if DEBUG_ANSI
|
||||
for(int i=1; i<HISTORY_LEN; i++) {
|
||||
history[i-1] = history[i];
|
||||
}
|
||||
history[HISTORY_LEN-1] = newchar;
|
||||
#endif
|
||||
|
||||
// Handle simple characters immediately (bypass parser)
|
||||
if (newchar < ' ') {
|
||||
switch (newchar) {
|
||||
case ESC:
|
||||
if (!inside_string) {
|
||||
// Reset state
|
||||
cs = ansi_start;
|
||||
// now the ESC will be processed by the parser
|
||||
}
|
||||
break; // proceed to parser
|
||||
|
||||
// Literally passed
|
||||
case FF:
|
||||
case VT:
|
||||
newchar = LF; // translate to LF, like VT100 / xterm do
|
||||
case CR:
|
||||
case LF:
|
||||
case BS:
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
|
||||
case TAB:
|
||||
apars_handle_tab();
|
||||
return;
|
||||
|
||||
// Select G0 or G1
|
||||
case SI:
|
||||
apars_handle_chs_switch(0);
|
||||
return;
|
||||
|
||||
case SO:
|
||||
apars_handle_chs_switch(1);
|
||||
return;
|
||||
|
||||
case BEL:
|
||||
// bel is also used to terminate OSC
|
||||
if (!inside_string) {
|
||||
apars_handle_bel();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENQ:
|
||||
apars_handle_enq();
|
||||
return;
|
||||
|
||||
// Cancel the active sequence
|
||||
case CAN:
|
||||
case SUB:
|
||||
cs = ansi_start;
|
||||
return;
|
||||
|
||||
default:
|
||||
// Discard all others
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// >= ' '
|
||||
|
||||
// bypass the parser for simple characters (speed-up)
|
||||
if (cs == ansi_start) {
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Load new data to Ragel vars
|
||||
const char *p = &newchar;
|
||||
const char *eof = NULL;
|
||||
const char *pe = &newchar + 1;
|
||||
|
||||
// The parser
|
||||
|
||||
/* #line 101 "user/ansi_parser.c" */
|
||||
/* #line 209 "user/ansi_parser.c" */
|
||||
{
|
||||
const char *_acts;
|
||||
unsigned int _nacts;
|
||||
@@ -109,356 +217,296 @@ ansi_parser(const char *newdata, size_t len)
|
||||
_resume:
|
||||
switch ( cs ) {
|
||||
case 1:
|
||||
if ( (*p) == 27 )
|
||||
goto tr1;
|
||||
goto tr0;
|
||||
case 2:
|
||||
switch( (*p) ) {
|
||||
case 55: goto tr3;
|
||||
case 56: goto tr4;
|
||||
case 91: goto tr5;
|
||||
case 93: goto tr6;
|
||||
case 99: goto tr7;
|
||||
case 7: goto tr1;
|
||||
case 27: goto tr2;
|
||||
}
|
||||
goto tr2;
|
||||
goto tr0;
|
||||
case 0:
|
||||
goto _out;
|
||||
case 26:
|
||||
if ( (*p) == 27 )
|
||||
goto tr1;
|
||||
goto tr0;
|
||||
case 2:
|
||||
switch( (*p) ) {
|
||||
case 32: goto tr3;
|
||||
case 35: goto tr4;
|
||||
case 37: goto tr5;
|
||||
case 80: goto tr7;
|
||||
case 88: goto tr7;
|
||||
case 91: goto tr8;
|
||||
case 107: goto tr7;
|
||||
}
|
||||
if ( (*p) < 64 ) {
|
||||
if ( (*p) < 48 ) {
|
||||
if ( 40 <= (*p) && (*p) <= 47 )
|
||||
goto tr5;
|
||||
} else if ( (*p) > 57 ) {
|
||||
if ( 60 <= (*p) && (*p) <= 62 )
|
||||
goto tr6;
|
||||
} else
|
||||
goto tr6;
|
||||
} else if ( (*p) > 92 ) {
|
||||
if ( (*p) < 97 ) {
|
||||
if ( 93 <= (*p) && (*p) <= 95 )
|
||||
goto tr7;
|
||||
} else if ( (*p) > 122 ) {
|
||||
if ( 124 <= (*p) && (*p) <= 126 )
|
||||
goto tr6;
|
||||
} else
|
||||
goto tr6;
|
||||
} else
|
||||
goto tr6;
|
||||
goto tr1;
|
||||
case 3:
|
||||
if ( (*p) == 59 )
|
||||
if ( (*p) > 71 ) {
|
||||
if ( 76 <= (*p) && (*p) <= 78 )
|
||||
goto tr9;
|
||||
} else if ( (*p) >= 70 )
|
||||
goto tr9;
|
||||
goto tr1;
|
||||
case 10:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr1;
|
||||
case 27: goto tr2;
|
||||
}
|
||||
goto tr0;
|
||||
case 4:
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr10;
|
||||
goto tr1;
|
||||
case 5:
|
||||
switch( (*p) ) {
|
||||
case 59: goto tr13;
|
||||
case 64: goto tr14;
|
||||
}
|
||||
if ( (*p) < 60 ) {
|
||||
if ( (*p) > 47 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr9;
|
||||
goto tr12;
|
||||
} else if ( (*p) >= 32 )
|
||||
goto tr8;
|
||||
} else if ( (*p) > 64 ) {
|
||||
goto tr11;
|
||||
} else if ( (*p) > 63 ) {
|
||||
if ( (*p) > 90 ) {
|
||||
if ( 97 <= (*p) && (*p) <= 122 )
|
||||
goto tr11;
|
||||
if ( 96 <= (*p) && (*p) <= 122 )
|
||||
goto tr15;
|
||||
} else if ( (*p) >= 65 )
|
||||
goto tr11;
|
||||
} else
|
||||
goto tr8;
|
||||
goto tr2;
|
||||
case 4:
|
||||
if ( (*p) == 59 )
|
||||
goto tr10;
|
||||
if ( (*p) < 65 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr9;
|
||||
} else if ( (*p) > 90 ) {
|
||||
if ( 97 <= (*p) && (*p) <= 122 )
|
||||
goto tr11;
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr11;
|
||||
goto tr2;
|
||||
case 27:
|
||||
goto tr2;
|
||||
case 5:
|
||||
switch( (*p) ) {
|
||||
case 66: goto tr12;
|
||||
case 84: goto tr13;
|
||||
case 87: goto tr14;
|
||||
}
|
||||
goto tr2;
|
||||
goto tr1;
|
||||
case 6:
|
||||
if ( (*p) == 84 )
|
||||
goto tr15;
|
||||
goto tr2;
|
||||
case 7:
|
||||
if ( (*p) == 78 )
|
||||
goto tr16;
|
||||
goto tr2;
|
||||
case 8:
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr17;
|
||||
goto tr2;
|
||||
case 9:
|
||||
if ( (*p) == 61 )
|
||||
goto tr18;
|
||||
goto tr2;
|
||||
case 10:
|
||||
if ( (*p) == 27 )
|
||||
goto tr2;
|
||||
goto tr19;
|
||||
case 11:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr20;
|
||||
case 27: goto tr21;
|
||||
}
|
||||
goto tr19;
|
||||
case 28:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr20;
|
||||
case 27: goto tr21;
|
||||
}
|
||||
goto tr19;
|
||||
case 12:
|
||||
if ( (*p) == 92 )
|
||||
goto tr22;
|
||||
goto tr2;
|
||||
case 29:
|
||||
goto tr2;
|
||||
case 13:
|
||||
if ( (*p) == 73 )
|
||||
goto tr23;
|
||||
goto tr2;
|
||||
case 14:
|
||||
if ( (*p) == 84 )
|
||||
goto tr24;
|
||||
goto tr2;
|
||||
case 15:
|
||||
if ( (*p) == 76 )
|
||||
goto tr25;
|
||||
goto tr2;
|
||||
case 16:
|
||||
if ( (*p) == 69 )
|
||||
goto tr26;
|
||||
goto tr2;
|
||||
case 17:
|
||||
if ( (*p) == 61 )
|
||||
goto tr27;
|
||||
goto tr2;
|
||||
case 18:
|
||||
if ( (*p) == 27 )
|
||||
goto tr2;
|
||||
goto tr28;
|
||||
case 19:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr29;
|
||||
case 27: goto tr30;
|
||||
}
|
||||
goto tr28;
|
||||
case 30:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr29;
|
||||
case 27: goto tr30;
|
||||
}
|
||||
goto tr28;
|
||||
case 20:
|
||||
if ( (*p) == 92 )
|
||||
goto tr31;
|
||||
goto tr2;
|
||||
case 21:
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr32;
|
||||
goto tr2;
|
||||
case 22:
|
||||
if ( (*p) == 59 )
|
||||
goto tr33;
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr32;
|
||||
goto tr2;
|
||||
case 23:
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr34;
|
||||
goto tr2;
|
||||
case 24:
|
||||
goto tr13;
|
||||
if ( (*p) < 64 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr12;
|
||||
} else if ( (*p) > 90 ) {
|
||||
if ( 96 <= (*p) && (*p) <= 122 )
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr15;
|
||||
goto tr1;
|
||||
case 11:
|
||||
goto tr1;
|
||||
case 12:
|
||||
if ( (*p) == 59 )
|
||||
goto tr13;
|
||||
if ( (*p) < 64 ) {
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr12;
|
||||
} else if ( (*p) > 90 ) {
|
||||
if ( 96 <= (*p) && (*p) <= 122 )
|
||||
goto tr15;
|
||||
} else
|
||||
goto tr15;
|
||||
goto tr1;
|
||||
case 7:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr35;
|
||||
case 27: goto tr36;
|
||||
case 7: goto tr17;
|
||||
case 27: goto tr18;
|
||||
}
|
||||
if ( 48 <= (*p) && (*p) <= 57 )
|
||||
goto tr34;
|
||||
goto tr2;
|
||||
case 25:
|
||||
goto tr16;
|
||||
case 13:
|
||||
goto tr1;
|
||||
case 8:
|
||||
if ( (*p) == 92 )
|
||||
goto tr35;
|
||||
goto tr2;
|
||||
goto tr17;
|
||||
goto tr1;
|
||||
case 9:
|
||||
switch( (*p) ) {
|
||||
case 7: goto tr1;
|
||||
case 27: goto tr1;
|
||||
}
|
||||
goto tr19;
|
||||
case 14:
|
||||
goto tr1;
|
||||
}
|
||||
|
||||
tr2: cs = 0; goto f0;
|
||||
tr1: cs = 0; goto f0;
|
||||
tr0: cs = 1; goto f1;
|
||||
tr1: cs = 2; goto _again;
|
||||
tr8: cs = 4; goto f7;
|
||||
tr9: cs = 4; goto f8;
|
||||
tr10: cs = 4; goto f9;
|
||||
tr12: cs = 6; goto _again;
|
||||
tr15: cs = 7; goto _again;
|
||||
tr16: cs = 8; goto _again;
|
||||
tr17: cs = 9; goto f8;
|
||||
tr18: cs = 10; goto _again;
|
||||
tr19: cs = 11; goto f11;
|
||||
tr21: cs = 12; goto _again;
|
||||
tr13: cs = 13; goto _again;
|
||||
tr23: cs = 14; goto _again;
|
||||
tr24: cs = 15; goto _again;
|
||||
tr25: cs = 16; goto _again;
|
||||
tr26: cs = 17; goto _again;
|
||||
tr27: cs = 18; goto _again;
|
||||
tr28: cs = 19; goto f11;
|
||||
tr30: cs = 20; goto _again;
|
||||
tr14: cs = 21; goto _again;
|
||||
tr32: cs = 22; goto f8;
|
||||
tr33: cs = 23; goto f9;
|
||||
tr34: cs = 24; goto f8;
|
||||
tr36: cs = 25; goto _again;
|
||||
tr3: cs = 26; goto f2;
|
||||
tr4: cs = 26; goto f3;
|
||||
tr5: cs = 26; goto f4;
|
||||
tr6: cs = 26; goto f5;
|
||||
tr7: cs = 26; goto f6;
|
||||
tr11: cs = 27; goto f10;
|
||||
tr20: cs = 28; goto f12;
|
||||
tr22: cs = 29; goto f13;
|
||||
tr31: cs = 29; goto f15;
|
||||
tr35: cs = 29; goto f16;
|
||||
tr29: cs = 30; goto f14;
|
||||
tr2: cs = 2; goto _again;
|
||||
tr3: cs = 3; goto _again;
|
||||
tr4: cs = 4; goto _again;
|
||||
tr11: cs = 6; goto f8;
|
||||
tr12: cs = 6; goto f9;
|
||||
tr13: cs = 6; goto f10;
|
||||
tr16: cs = 7; goto f13;
|
||||
tr18: cs = 8; goto _again;
|
||||
tr5: cs = 10; goto f2;
|
||||
tr6: cs = 10; goto f3;
|
||||
tr7: cs = 10; goto f4;
|
||||
tr8: cs = 10; goto f5;
|
||||
tr9: cs = 10; goto f6;
|
||||
tr10: cs = 10; goto f7;
|
||||
tr15: cs = 11; goto f12;
|
||||
tr14: cs = 12; goto f11;
|
||||
tr17: cs = 13; goto f14;
|
||||
tr19: cs = 14; goto f15;
|
||||
|
||||
f1: _acts = _ansi_actions + 1; goto execFuncs;
|
||||
f4: _acts = _ansi_actions + 3; goto execFuncs;
|
||||
f7: _acts = _ansi_actions + 5; goto execFuncs;
|
||||
f0: _acts = _ansi_actions + 1; goto execFuncs;
|
||||
f1: _acts = _ansi_actions + 3; goto execFuncs;
|
||||
f5: _acts = _ansi_actions + 5; goto execFuncs;
|
||||
f8: _acts = _ansi_actions + 7; goto execFuncs;
|
||||
f9: _acts = _ansi_actions + 9; goto execFuncs;
|
||||
f10: _acts = _ansi_actions + 11; goto execFuncs;
|
||||
f0: _acts = _ansi_actions + 13; goto execFuncs;
|
||||
f5: _acts = _ansi_actions + 15; goto execFuncs;
|
||||
f16: _acts = _ansi_actions + 17; goto execFuncs;
|
||||
f11: _acts = _ansi_actions + 19; goto execFuncs;
|
||||
f15: _acts = _ansi_actions + 21; goto execFuncs;
|
||||
f13: _acts = _ansi_actions + 23; goto execFuncs;
|
||||
f12: _acts = _ansi_actions + 13; goto execFuncs;
|
||||
f4: _acts = _ansi_actions + 15; goto execFuncs;
|
||||
f13: _acts = _ansi_actions + 17; goto execFuncs;
|
||||
f14: _acts = _ansi_actions + 19; goto execFuncs;
|
||||
f7: _acts = _ansi_actions + 21; goto execFuncs;
|
||||
f3: _acts = _ansi_actions + 23; goto execFuncs;
|
||||
f6: _acts = _ansi_actions + 25; goto execFuncs;
|
||||
f2: _acts = _ansi_actions + 27; goto execFuncs;
|
||||
f3: _acts = _ansi_actions + 29; goto execFuncs;
|
||||
f14: _acts = _ansi_actions + 31; goto execFuncs;
|
||||
f12: _acts = _ansi_actions + 34; goto execFuncs;
|
||||
f15: _acts = _ansi_actions + 29; goto execFuncs;
|
||||
f11: _acts = _ansi_actions + 31; goto execFuncs;
|
||||
|
||||
execFuncs:
|
||||
_nacts = *_acts++;
|
||||
while ( _nacts-- > 0 ) {
|
||||
switch ( *_acts++ ) {
|
||||
case 0:
|
||||
/* #line 76 "user/ansi_parser.rl" */
|
||||
/* #line 185 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_plainchar((*p));
|
||||
ansi_warn("Parser error.");
|
||||
apars_show_context();
|
||||
inside_string = false; // no longer in string, for sure
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
/* #line 83 "user/ansi_parser.rl" */
|
||||
/* #line 194 "user/ansi_parser.rl" */
|
||||
{
|
||||
// Reset the CSI builder
|
||||
csi_leading = csi_char = 0;
|
||||
csi_ni = 0;
|
||||
|
||||
// Zero out digits
|
||||
for(int i = 0; i < CSI_N_MAX; i++) {
|
||||
csi_n[i] = 0;
|
||||
if ((*p) != 0) {
|
||||
apars_handle_plainchar((*p));
|
||||
}
|
||||
|
||||
{cs = 3;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
/* #line 96 "user/ansi_parser.rl" */
|
||||
/* #line 202 "user/ansi_parser.rl" */
|
||||
{
|
||||
csi_leading = (*p);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
/* #line 100 "user/ansi_parser.rl" */
|
||||
{
|
||||
// x10 + digit
|
||||
if (csi_ni < CSI_N_MAX) {
|
||||
csi_n[csi_ni] = csi_n[csi_ni]*10 + ((*p) - '0');
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
/* #line 107 "user/ansi_parser.rl" */
|
||||
{
|
||||
csi_ni++;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
/* #line 111 "user/ansi_parser.rl" */
|
||||
{
|
||||
csi_char = (*p);
|
||||
// Reset the CSI builder
|
||||
leadchar = NUL;
|
||||
arg_ni = 0;
|
||||
arg_cnt = 0;
|
||||
|
||||
apars_handle_CSI(csi_leading, csi_n, csi_char);
|
||||
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
/* #line 119 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_badseq();
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
/* #line 136 "user/ansi_parser.rl" */
|
||||
{
|
||||
csi_ni = 0;
|
||||
|
||||
// we reuse the CSI numeric buffer
|
||||
// Zero out digits
|
||||
for(int i = 0; i < CSI_N_MAX; i++) {
|
||||
csi_n[i] = 0;
|
||||
arg[i] = 0;
|
||||
}
|
||||
|
||||
osc_bi = 0;
|
||||
osc_buffer[0] = '\0';
|
||||
|
||||
{cs = 5;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
/* #line 150 "user/ansi_parser.rl" */
|
||||
case 3:
|
||||
/* #line 216 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]);
|
||||
leadchar = (*p);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
/* #line 220 "user/ansi_parser.rl" */
|
||||
{
|
||||
if (arg_cnt == 0) arg_cnt = 1;
|
||||
// x10 + digit
|
||||
if (arg_ni < CSI_N_MAX) {
|
||||
arg[arg_ni] = arg[arg_ni]*10 + ((*p) - '0');
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
/* #line 228 "user/ansi_parser.rl" */
|
||||
{
|
||||
if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty
|
||||
arg_cnt++;
|
||||
arg_ni++;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
/* #line 234 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_csi(leadchar, arg, arg_cnt, (*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
/* #line 155 "user/ansi_parser.rl" */
|
||||
case 7:
|
||||
/* #line 245 "user/ansi_parser.rl" */
|
||||
{
|
||||
osc_buffer[osc_bi++] = (*p);
|
||||
leadchar = (*p);
|
||||
str_ni = 0;
|
||||
string_buffer[0] = '\0';
|
||||
inside_string = true;
|
||||
{cs = 7;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
/* #line 253 "user/ansi_parser.rl" */
|
||||
{
|
||||
string_buffer[str_ni++] = (*p);
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
/* #line 257 "user/ansi_parser.rl" */
|
||||
{
|
||||
inside_string = false;
|
||||
string_buffer[str_ni++] = '\0';
|
||||
apars_handle_string_cmd(leadchar, string_buffer);
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
/* #line 159 "user/ansi_parser.rl" */
|
||||
/* #line 270 "user/ansi_parser.rl" */
|
||||
{
|
||||
osc_buffer[osc_bi++] = '\0';
|
||||
apars_handle_OSC_SetTitle(osc_buffer);
|
||||
apars_handle_hash_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 11:
|
||||
/* #line 165 "user/ansi_parser.rl" */
|
||||
/* #line 275 "user/ansi_parser.rl" */
|
||||
{
|
||||
osc_buffer[osc_bi++] = '\0';
|
||||
apars_handle_OSC_SetButton(csi_n[0], osc_buffer);
|
||||
apars_handle_short_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
/* #line 177 "user/ansi_parser.rl" */
|
||||
/* #line 280 "user/ansi_parser.rl" */
|
||||
{
|
||||
// Reset screen
|
||||
apars_handle_RESET_cmd();
|
||||
apars_handle_space_cmd((*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 13:
|
||||
/* #line 183 "user/ansi_parser.rl" */
|
||||
/* #line 287 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_saveCursorAttrs();
|
||||
{cs = 1;goto _again;}
|
||||
leadchar = (*p);
|
||||
{cs = 9;goto _again;}
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
/* #line 188 "user/ansi_parser.rl" */
|
||||
/* #line 292 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_restoreCursorAttrs();
|
||||
apars_handle_chs_designate(leadchar, (*p));
|
||||
{cs = 1;goto _again;}
|
||||
}
|
||||
break;
|
||||
/* #line 462 "user/ansi_parser.c" */
|
||||
/* #line 510 "user/ansi_parser.c" */
|
||||
}
|
||||
}
|
||||
goto _again;
|
||||
@@ -475,16 +523,18 @@ _again:
|
||||
unsigned int __nacts = (unsigned int) *__acts++;
|
||||
while ( __nacts-- > 0 ) {
|
||||
switch ( *__acts++ ) {
|
||||
case 6:
|
||||
/* #line 119 "user/ansi_parser.rl" */
|
||||
case 0:
|
||||
/* #line 185 "user/ansi_parser.rl" */
|
||||
{
|
||||
apars_handle_badseq();
|
||||
ansi_warn("Parser error.");
|
||||
apars_show_context();
|
||||
inside_string = false; // no longer in string, for sure
|
||||
{cs = 1; if ( p == pe )
|
||||
goto _test_eof;
|
||||
goto _again;}
|
||||
}
|
||||
break;
|
||||
/* #line 488 "user/ansi_parser.c" */
|
||||
/* #line 538 "user/ansi_parser.c" */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,6 +542,6 @@ goto _again;}
|
||||
_out: {}
|
||||
}
|
||||
|
||||
/* #line 208 "user/ansi_parser.rl" */
|
||||
/* #line 315 "user/ansi_parser.rl" */
|
||||
|
||||
}
|
||||
|
||||
+7
-16
@@ -2,21 +2,10 @@
|
||||
#define ANSI_PARSER_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <screen.h>
|
||||
|
||||
// Max nr of CSI parameters
|
||||
#define CSI_N_MAX 3
|
||||
#define OSC_CHAR_MAX TERM_TITLE_LEN
|
||||
void ansi_parser_reset(void);
|
||||
|
||||
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_SetScreenSize(int rows, int cols);
|
||||
extern void apars_handle_saveCursorAttrs(void);
|
||||
extern void apars_handle_restoreCursorAttrs(void);
|
||||
extern void apars_handle_OSC_SetButton(int num, const char *buffer);
|
||||
extern void apars_handle_OSC_SetTitle(const char *buffer);
|
||||
extern volatile u32 ansi_parser_char_cnt;
|
||||
|
||||
/**
|
||||
* \brief Linear ANSI chars stream parser
|
||||
@@ -28,9 +17,11 @@ extern void apars_handle_OSC_SetTitle(const char *buffer);
|
||||
* \attention -> but always check the Ragel output for 'p--'
|
||||
* or 'p -=', that means trouble.
|
||||
*
|
||||
* \param newdata - array of new chars to process
|
||||
* \param len - length of the newdata buffer
|
||||
* \param newchar - received char
|
||||
*/
|
||||
void ansi_parser(const char *newdata, size_t len);
|
||||
void ansi_parser(char newchar);
|
||||
|
||||
/** This shows a short error message and prints the history (if any) */
|
||||
void apars_show_context(void);
|
||||
|
||||
#endif // ANSI_PARSER_H
|
||||
|
||||
+203
-96
@@ -1,6 +1,8 @@
|
||||
#include <esp8266.h>
|
||||
#include "ansi_parser.h"
|
||||
#include "screen.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "ascii.h"
|
||||
#include "apars_logging.h"
|
||||
|
||||
/* Ragel constants block */
|
||||
%%{
|
||||
@@ -8,18 +10,54 @@
|
||||
write data;
|
||||
}%%
|
||||
|
||||
// Max nr of CSI parameters
|
||||
#define CSI_N_MAX 10
|
||||
#define STR_CHAR_MAX 64
|
||||
|
||||
static volatile int cs = -1;
|
||||
static volatile bool inside_string = false;
|
||||
|
||||
static ETSTimer resetTim;
|
||||
// public
|
||||
volatile u32 ansi_parser_char_cnt = 0;
|
||||
|
||||
static void ICACHE_FLASH_ATTR
|
||||
resetParserCb(void *arg) {
|
||||
void ICACHE_FLASH_ATTR
|
||||
ansi_parser_reset(void) {
|
||||
if (cs != ansi_start) {
|
||||
cs = ansi_start;
|
||||
warn("Parser timeout, state reset");
|
||||
inside_string = false;
|
||||
apars_reset_utf8buffer();
|
||||
ansi_warn("Parser timeout, state reset");
|
||||
}
|
||||
}
|
||||
|
||||
#define HISTORY_LEN 10
|
||||
|
||||
#if DEBUG_ANSI
|
||||
static char history[HISTORY_LEN + 1];
|
||||
#endif
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_show_context(void)
|
||||
{
|
||||
#if DEBUG_ANSI
|
||||
char buf1[HISTORY_LEN*3+2];
|
||||
char buf2[HISTORY_LEN*3+2];
|
||||
char *b1 = buf1;
|
||||
char *b2 = buf2;
|
||||
char c;
|
||||
|
||||
for(int i=0;i<HISTORY_LEN;i++) {
|
||||
c = history[i];
|
||||
b1 += sprintf(b1, "%2X ", c);
|
||||
if (c < 32 || c > 127) c = '.';
|
||||
b2 += sprintf(b2, "%c ", c);
|
||||
}
|
||||
|
||||
ansi_dbg("Context: %s", buf2);
|
||||
ansi_dbg(" %s", buf1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Linear ANSI chars stream parser
|
||||
*
|
||||
@@ -34,172 +72,241 @@ resetParserCb(void *arg) {
|
||||
* \param len - length of the newdata buffer
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
ansi_parser(const char *newdata, size_t len)
|
||||
ansi_parser(char newchar)
|
||||
{
|
||||
// The CSI code is built here
|
||||
static char csi_leading; //!< Leading char, 0 if none
|
||||
static int csi_ni; //!< Number of the active digit
|
||||
static int csi_n[CSI_N_MAX]; //!< Param digits
|
||||
static char csi_char; //!< CSI action char (end)
|
||||
static char osc_buffer[OSC_CHAR_MAX];
|
||||
static int osc_bi;
|
||||
static char leadchar;
|
||||
static int arg_ni;
|
||||
static int arg_cnt;
|
||||
static int arg[CSI_N_MAX];
|
||||
static char string_buffer[STR_CHAR_MAX];
|
||||
static int str_ni;
|
||||
|
||||
if (len == 0) len = strlen(newdata);
|
||||
|
||||
// Load new data to Ragel vars
|
||||
const char *p = newdata;
|
||||
const char *eof = NULL;
|
||||
const char *pe = newdata + len;
|
||||
// This is used to detect timeout delay (time since last rx char)
|
||||
ansi_parser_char_cnt++;
|
||||
|
||||
// Init Ragel on the first run
|
||||
if (cs == -1) {
|
||||
%% write init;
|
||||
|
||||
#if DEBUG_ANSI
|
||||
memset(history, 0, sizeof(history));
|
||||
#endif
|
||||
}
|
||||
|
||||
// schedule state reset
|
||||
if (termconf->parser_tout_ms > 0) {
|
||||
os_timer_disarm(&resetTim);
|
||||
os_timer_setfn(&resetTim, resetParserCb, NULL);
|
||||
os_timer_arm(&resetTim, termconf->parser_tout_ms, 0);
|
||||
#if DEBUG_ANSI
|
||||
for(int i=1; i<HISTORY_LEN; i++) {
|
||||
history[i-1] = history[i];
|
||||
}
|
||||
history[HISTORY_LEN-1] = newchar;
|
||||
#endif
|
||||
|
||||
// Handle simple characters immediately (bypass parser)
|
||||
if (newchar < ' ') {
|
||||
switch (newchar) {
|
||||
case ESC:
|
||||
if (!inside_string) {
|
||||
// Reset state
|
||||
cs = ansi_start;
|
||||
// now the ESC will be processed by the parser
|
||||
}
|
||||
break; // proceed to parser
|
||||
|
||||
// Literally passed
|
||||
case FF:
|
||||
case VT:
|
||||
newchar = LF; // translate to LF, like VT100 / xterm do
|
||||
case CR:
|
||||
case LF:
|
||||
case BS:
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
|
||||
case TAB:
|
||||
apars_handle_tab();
|
||||
return;
|
||||
|
||||
// Select G0 or G1
|
||||
case SI:
|
||||
apars_handle_chs_switch(0);
|
||||
return;
|
||||
|
||||
case SO:
|
||||
apars_handle_chs_switch(1);
|
||||
return;
|
||||
|
||||
case BEL:
|
||||
// bel is also used to terminate OSC
|
||||
if (!inside_string) {
|
||||
apars_handle_bel();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENQ:
|
||||
apars_handle_enq();
|
||||
return;
|
||||
|
||||
// Cancel the active sequence
|
||||
case CAN:
|
||||
case SUB:
|
||||
cs = ansi_start;
|
||||
return;
|
||||
|
||||
default:
|
||||
// Discard all others
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// >= ' '
|
||||
|
||||
// bypass the parser for simple characters (speed-up)
|
||||
if (cs == ansi_start) {
|
||||
apars_handle_plainchar(newchar);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Load new data to Ragel vars
|
||||
const char *p = &newchar;
|
||||
const char *eof = NULL;
|
||||
const char *pe = &newchar + 1;
|
||||
|
||||
// The parser
|
||||
%%{
|
||||
#/*
|
||||
ESC = 27;
|
||||
NOESC = (any - ESC);
|
||||
NOESC = (any - ESC - 7);
|
||||
TOK_ST = ESC '\\'; # String terminator - used for OSC commands
|
||||
OSC_END = ('\a' | ESC '\\');
|
||||
STR_END = (7 | TOK_ST);
|
||||
|
||||
# --- Error handler ---
|
||||
|
||||
action errBadSeq {
|
||||
ansi_warn("Parser error.");
|
||||
apars_show_context();
|
||||
inside_string = false; // no longer in string, for sure
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
# --- Regular characters to be printed ---
|
||||
|
||||
action plain_char {
|
||||
apars_handle_plainchar(fc);
|
||||
if (fc != 0) {
|
||||
apars_handle_plainchar(fc);
|
||||
}
|
||||
}
|
||||
|
||||
# --- CSI CSI commands (Select Graphic Rendition) ---
|
||||
# Text color & style
|
||||
# --- CSI commands ---
|
||||
|
||||
action CSI_start {
|
||||
// Reset the CSI builder
|
||||
csi_leading = csi_char = 0;
|
||||
csi_ni = 0;
|
||||
leadchar = NUL;
|
||||
arg_ni = 0;
|
||||
arg_cnt = 0;
|
||||
|
||||
// Zero out digits
|
||||
for(int i = 0; i < CSI_N_MAX; i++) {
|
||||
csi_n[i] = 0;
|
||||
arg[i] = 0;
|
||||
}
|
||||
|
||||
fgoto CSI_body;
|
||||
}
|
||||
|
||||
action CSI_leading {
|
||||
csi_leading = fc;
|
||||
leadchar = fc;
|
||||
}
|
||||
|
||||
action CSI_digit {
|
||||
if (arg_cnt == 0) arg_cnt = 1;
|
||||
// x10 + digit
|
||||
if (csi_ni < CSI_N_MAX) {
|
||||
csi_n[csi_ni] = csi_n[csi_ni]*10 + (fc - '0');
|
||||
if (arg_ni < CSI_N_MAX) {
|
||||
arg[arg_ni] = arg[arg_ni]*10 + (fc - '0');
|
||||
}
|
||||
}
|
||||
|
||||
action CSI_semi {
|
||||
csi_ni++;
|
||||
if (arg_cnt == 0) arg_cnt = 1; // handle case when first arg is empty
|
||||
arg_cnt++;
|
||||
arg_ni++;
|
||||
}
|
||||
|
||||
action CSI_end {
|
||||
csi_char = fc;
|
||||
|
||||
apars_handle_CSI(csi_leading, csi_n, csi_char);
|
||||
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action errBadSeq {
|
||||
apars_handle_badseq();
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action back2main {
|
||||
apars_handle_csi(leadchar, arg, arg_cnt, fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
CSI_body := ((32..47|60..64) @CSI_leading)?
|
||||
((digit @CSI_digit)* ';' @CSI_semi)*
|
||||
(digit @CSI_digit)* alpha @CSI_end $!errBadSeq;
|
||||
(digit @CSI_digit)* (alpha|'`'|'@') @CSI_end $!errBadSeq;
|
||||
|
||||
# --- String commands ---
|
||||
|
||||
# --- OSC commands (Operating System Commands) ---
|
||||
# Module parametrisation
|
||||
|
||||
action OSC_start {
|
||||
csi_ni = 0;
|
||||
|
||||
// we reuse the CSI numeric buffer
|
||||
for(int i = 0; i < CSI_N_MAX; i++) {
|
||||
csi_n[i] = 0;
|
||||
}
|
||||
|
||||
osc_bi = 0;
|
||||
osc_buffer[0] = '\0';
|
||||
|
||||
fgoto OSC_body;
|
||||
action StrCmd_start {
|
||||
leadchar = fc;
|
||||
str_ni = 0;
|
||||
string_buffer[0] = '\0';
|
||||
inside_string = true;
|
||||
fgoto STRCMD_body;
|
||||
}
|
||||
|
||||
action OSC_resize {
|
||||
apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]);
|
||||
action StrCmd_char {
|
||||
string_buffer[str_ni++] = fc;
|
||||
}
|
||||
|
||||
action StrCmd_end {
|
||||
inside_string = false;
|
||||
string_buffer[str_ni++] = '\0';
|
||||
apars_handle_string_cmd(leadchar, string_buffer);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action OSC_text_char {
|
||||
osc_buffer[osc_bi++] = fc;
|
||||
}
|
||||
# According to the spec, ESC should be allowed inside the string sequence.
|
||||
# We disallow ESC for simplicity, as it's hardly ever used.
|
||||
STRCMD_body := ((NOESC @StrCmd_char)* STR_END @StrCmd_end) $!errBadSeq;
|
||||
|
||||
action OSC_title {
|
||||
osc_buffer[osc_bi++] = '\0';
|
||||
apars_handle_OSC_SetTitle(osc_buffer);
|
||||
# --- Single character ESC ---
|
||||
|
||||
action HASH_code {
|
||||
apars_handle_hash_cmd(fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action OSC_button {
|
||||
osc_buffer[osc_bi++] = '\0';
|
||||
apars_handle_OSC_SetButton(csi_n[0], osc_buffer);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
OSC_body := (
|
||||
("BTN" digit @CSI_digit '=' (NOESC @OSC_text_char)+ OSC_END @OSC_button) |
|
||||
("TITLE=" (NOESC @OSC_text_char)+ OSC_END @OSC_title) |
|
||||
('W' (digit @CSI_digit)+ ';' @CSI_semi (digit @CSI_digit)+ OSC_END @OSC_resize)
|
||||
) $!errBadSeq;
|
||||
|
||||
action RESET_cmd {
|
||||
// Reset screen
|
||||
apars_handle_RESET_cmd();
|
||||
action SHORT_code {
|
||||
apars_handle_short_cmd(fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action CSI_SaveCursorAttrs {
|
||||
apars_handle_saveCursorAttrs();
|
||||
action SPACE_cmd {
|
||||
apars_handle_space_cmd(fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action CSI_RestoreCursorAttrs {
|
||||
apars_handle_restoreCursorAttrs();
|
||||
# --- Charset selection ---
|
||||
|
||||
action CharsetCmd_start {
|
||||
leadchar = fc;
|
||||
fgoto charsetcmd_body;
|
||||
}
|
||||
|
||||
action CharsetCmd_end {
|
||||
apars_handle_chs_designate(leadchar, fc);
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
charsetcmd_body := (NOESC @CharsetCmd_end) $!errBadSeq;
|
||||
|
||||
# --- Main parser loop ---
|
||||
|
||||
main :=
|
||||
(
|
||||
(NOESC @plain_char)* ESC (
|
||||
'[' @CSI_start |
|
||||
']' @OSC_start |
|
||||
'c' @RESET_cmd |
|
||||
'7' @CSI_SaveCursorAttrs |
|
||||
'8' @CSI_RestoreCursorAttrs
|
||||
('[' @CSI_start) |
|
||||
([_\]Pk\^X] @StrCmd_start) |
|
||||
('#' digit @HASH_code) |
|
||||
(([a-zA-Z0-9=<>~}|@\\] - [PXk]) @SHORT_code) |
|
||||
([()*+-./%] @CharsetCmd_start) |
|
||||
(' ' [FGLMN] @SPACE_cmd)
|
||||
)
|
||||
)+ $!errBadSeq;
|
||||
|
||||
|
||||
+19
-259
@@ -5,285 +5,45 @@
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#include <helpers.h>
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "screen.h"
|
||||
#include "ansi_parser.h"
|
||||
#include "uart_driver.h"
|
||||
#include "persist.h"
|
||||
|
||||
static char utf_collect[4];
|
||||
static int utf_i = 0;
|
||||
static int utf_j = 0;
|
||||
#include "cgi_sockets.h"
|
||||
#include "version.h"
|
||||
#include "uart_buffer.h"
|
||||
|
||||
/**
|
||||
* Handle a received plain character
|
||||
* Send a response to UART0
|
||||
* @param str
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_plainchar(char c)
|
||||
apars_respond(const char *str)
|
||||
{
|
||||
// collecting unicode glyphs...
|
||||
if (c & 0x80) {
|
||||
if (utf_i == 0) {
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
utf_i = 2;
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0) {
|
||||
utf_i = 3;
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0) {
|
||||
utf_i = 4;
|
||||
}
|
||||
|
||||
utf_collect[0] = c;
|
||||
utf_j = 1;
|
||||
}
|
||||
else {
|
||||
utf_collect[utf_j++] = c;
|
||||
if (utf_j >= utf_i) {
|
||||
screen_putchar(utf_collect);
|
||||
utf_i = 0;
|
||||
utf_j = 0;
|
||||
memset(utf_collect, 0, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
utf_collect[0] = c;
|
||||
utf_collect[1] = 0; // just to make sure it's closed...
|
||||
screen_putchar(utf_collect);
|
||||
}
|
||||
UART_SendAsync(str, -1);
|
||||
//UART_WriteString(UART0, str, UART_TIMEOUT_US);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bad sequence received, show warning
|
||||
* Beep at the user.
|
||||
*/
|
||||
void apars_handle_badseq(void)
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_bel(void)
|
||||
{
|
||||
warn("Invalid escape sequence discarded.");
|
||||
send_beep();
|
||||
}
|
||||
|
||||
/**
|
||||
* \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
|
||||
* Send to uart the answerback message
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_CSI(char leadchar, int *params, char keychar)
|
||||
apars_handle_enq(void)
|
||||
{
|
||||
/*
|
||||
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);
|
||||
}
|
||||
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, "\033[0n", UART_TIMEOUT_US);
|
||||
}
|
||||
break;
|
||||
|
||||
// DECTCEM feature enable / disable
|
||||
|
||||
case 'h':
|
||||
if (leadchar == '?') {
|
||||
if (n1 == 25) {
|
||||
screen_cursor_enable(1);
|
||||
} else if (n1 == 7) {
|
||||
screen_wrap_enable(1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (leadchar == '?') {
|
||||
if (n1 == 25) {
|
||||
screen_cursor_enable(0);
|
||||
} else if (n1 == 7) {
|
||||
screen_wrap_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_reset_cursor(); // resets colors, inverse and bold.
|
||||
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(false); // default bg
|
||||
else if (n == 7) screen_inverse(true); // inverse
|
||||
else if (n == 27) screen_inverse(false); // positive
|
||||
else if (n == 1) screen_set_bold(true); // bold
|
||||
else if (n == 21 || n == 22) screen_set_bold(false); // bold off
|
||||
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)
|
||||
{
|
||||
screen_reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_OSC_SetButton(int num, const char *buffer)
|
||||
{
|
||||
strncpy(termconf_scratch.btn[num-1], buffer, TERM_BTN_LEN);
|
||||
info("OSC: Set BTN%d = %s", num, buffer);
|
||||
screen_notifyChange(CHANGE_LABELS);
|
||||
// version encased in SOS and ST
|
||||
apars_respond("\x1bXESPTerm " FIRMWARE_VERSION "\x1b\\");
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_OSC_SetTitle(const char *buffer)
|
||||
apars_handle_tab(void)
|
||||
{
|
||||
strncpy(termconf_scratch.title, buffer, TERM_TITLE_LEN);
|
||||
info("OSC: Set TITLE = %s", buffer);
|
||||
screen_notifyChange(CHANGE_LABELS);
|
||||
screen_tab_forward(1);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#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_SetScreenSize(int rows, int cols);
|
||||
void apars_handle_saveCursorAttrs(void);
|
||||
void apars_handle_restoreCursorAttrs(void);
|
||||
void apars_handle_OSC_SetButton(int num, const char *buffer);
|
||||
void apars_handle_OSC_SetTitle(const char *buffer);
|
||||
#include "apars_csi.h"
|
||||
#include "apars_dcs.h"
|
||||
#include "apars_osc.h"
|
||||
#include "apars_string.h"
|
||||
#include "apars_short.h"
|
||||
#include "apars_utf8.h"
|
||||
|
||||
void apars_respond(const char *str);
|
||||
|
||||
void apars_handle_bel(void);
|
||||
void apars_handle_enq(void);
|
||||
void apars_handle_tab(void);
|
||||
|
||||
extern void apars_show_context(void);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_ANSI_PARSER_CALLBACKS_H
|
||||
|
||||
@@ -0,0 +1,681 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Handle CSI sequences
|
||||
// CSI <symbol?> Pm <symbol?> <char>
|
||||
// (CSI = ESC [)
|
||||
//
|
||||
// Example of those are cursor manipulation sequences and SGR.
|
||||
//
|
||||
// For details, see:
|
||||
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Functions-using-CSI-_-ordered-by-the-final-character_s_
|
||||
//
|
||||
// Note:
|
||||
// not all sequences listed in the xterm manual are implemented, notably sequences with the trailing symbol,
|
||||
// graphic mode sequences, mouse reporting and complex multi-argument sequences that operate on regions.
|
||||
//
|
||||
// The screen size can be set using the xterm sequence: CSI Py ; Px t
|
||||
//
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_csi.h"
|
||||
#include "screen.h"
|
||||
#include "apars_logging.h"
|
||||
#include "ansi_parser.h"
|
||||
#include "ascii.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "uart_driver.h"
|
||||
#include "sgr.h"
|
||||
#include "version.h"
|
||||
#include "syscfg.h"
|
||||
|
||||
// TODO simplify file - split to subroutines
|
||||
|
||||
static void warn_bad_csi()
|
||||
{
|
||||
ansi_noimpl_r("Unknown CSI");
|
||||
apars_show_context();
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char lead;
|
||||
const int *n;
|
||||
int count;
|
||||
char aug; // augmenting
|
||||
char key;
|
||||
} CSI_Data;
|
||||
|
||||
static void do_csi_privattr(CSI_Data *opts);
|
||||
static void do_csi_sgr(CSI_Data *opts);
|
||||
static void do_csi_decreqtparm(CSI_Data *opts);
|
||||
|
||||
/**
|
||||
* 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, const int *params, int count, char keychar)
|
||||
{
|
||||
CSI_Data opts = {leadchar, params, count, NUL, keychar};
|
||||
char buf[32];
|
||||
bool yn = false; // for ? l h
|
||||
|
||||
int n1 = params[0];
|
||||
int n2 = params[1];
|
||||
int n3 = params[2];
|
||||
|
||||
// defaults - FIXME this may inadvertently affect some variants that should be left unchanged
|
||||
switch (keychar) {
|
||||
case 'A': // move
|
||||
case 'a':
|
||||
case 'e':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'b':
|
||||
case 'E':
|
||||
case 'F':
|
||||
case 'G': // set X
|
||||
case '`':
|
||||
case 'S': // scrolling
|
||||
case 'T':
|
||||
case 'X': // clear in line
|
||||
case 'd': // set Y
|
||||
case 'L':
|
||||
case 'M':
|
||||
case '@':
|
||||
case 'P':
|
||||
case 'I':
|
||||
case 'Z':
|
||||
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;
|
||||
|
||||
default:
|
||||
// leave as is
|
||||
break;
|
||||
}
|
||||
|
||||
switch (keychar) {
|
||||
// CUU CUD CUF CUB
|
||||
case 'A': // Up
|
||||
screen_cursor_move(-n1, 0, false);
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
case 'B': // Down
|
||||
screen_cursor_move(n1, 0, false);
|
||||
break;
|
||||
|
||||
case 'a': // some archaic form of "Go Right"
|
||||
case 'C': // Right (forward)
|
||||
screen_cursor_move(0, n1, false);
|
||||
break;
|
||||
|
||||
case 'D': // Left (backward)
|
||||
screen_cursor_move(0, -n1, false);
|
||||
break;
|
||||
|
||||
case 'E': // CNL - Cursor Next Line
|
||||
screen_cursor_move(n1, 0, false);
|
||||
screen_cursor_set_x(0);
|
||||
break;
|
||||
|
||||
case 'F': // CPL - Cursor Prev Line
|
||||
screen_cursor_move(-n1, 0, false);
|
||||
screen_cursor_set_x(0);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
// TODO repeat preceding graphic character n1 times
|
||||
ansi_noimpl("Repeat char");
|
||||
return;
|
||||
|
||||
// Set X
|
||||
case 'G':
|
||||
case '`': // alternate code
|
||||
screen_cursor_set_x(n1 - 1);
|
||||
break; // 1-based
|
||||
|
||||
// Set Y
|
||||
case 'd':
|
||||
screen_cursor_set_y(n1 - 1);
|
||||
break; // 1-based
|
||||
|
||||
// Clear in line
|
||||
case 'X':
|
||||
screen_clear_in_line(n1);
|
||||
break; // 1-based
|
||||
|
||||
// SU, SD - scroll up/down
|
||||
case 'S':
|
||||
if (leadchar == NUL && count <= 1) {
|
||||
screen_scroll_up(n1);
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI ? Pi; Pa; Pv S (sixel)
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
if (leadchar == NUL && count <= 1) {
|
||||
// CSI Ps T
|
||||
screen_scroll_down(n1);
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI Ps ; Ps ; Ps ; Ps ; Ps T
|
||||
// CSI > Ps; Ps T
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 't': // xterm window commands
|
||||
if (leadchar == NUL && count <= 2) {
|
||||
// CSI Ps ; Ps ; Ps t
|
||||
switch (n1) {
|
||||
case 8: // set size
|
||||
screen_resize(n2, n3);
|
||||
break;
|
||||
|
||||
case 18: // report size
|
||||
printf(buf, "\033[8;%d;%dt", termconf_scratch.height, termconf_scratch.width);
|
||||
apars_respond(buf);
|
||||
break;
|
||||
|
||||
case 21: // Report title
|
||||
apars_respond("\033]L");
|
||||
apars_respond(termconf_scratch.title);
|
||||
apars_respond("\033\\");
|
||||
break;
|
||||
|
||||
case 24: // Set Height only
|
||||
screen_resize(n2, termconf_scratch.width);
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_noimpl("CSI %d t", n1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI > Ps; Ps t
|
||||
// CSI Ps SP t,
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
// CUP,HVP - set position
|
||||
case 'H':
|
||||
case 'f':
|
||||
screen_cursor_set(n1-1, n2-1);
|
||||
break; // 1-based
|
||||
|
||||
case 'J': // Erase screen
|
||||
if (leadchar == '?') {
|
||||
// TODO selective erase
|
||||
ansi_noimpl("Selective erase");
|
||||
}
|
||||
|
||||
if (n1 == 0) {
|
||||
screen_clear(CLEAR_FROM_CURSOR);
|
||||
} else if (n1 == 1) {
|
||||
screen_clear(CLEAR_TO_CURSOR);
|
||||
} else {
|
||||
screen_clear(CLEAR_ALL);
|
||||
screen_cursor_set(0, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'K': // Erase lines
|
||||
if (leadchar == '?') {
|
||||
// TODO selective erase
|
||||
ansi_noimpl("Selective erase");
|
||||
}
|
||||
|
||||
if (n1 == 0) {
|
||||
screen_clear_line(CLEAR_FROM_CURSOR);
|
||||
} else if (n1 == 1) {
|
||||
screen_clear_line(CLEAR_TO_CURSOR);
|
||||
} else {
|
||||
screen_clear_line(CLEAR_ALL);
|
||||
}
|
||||
break;
|
||||
|
||||
// SCP, RCP - save/restore position
|
||||
case 's':
|
||||
if (leadchar == NUL && count == 0) {
|
||||
screen_cursor_save(0);
|
||||
}
|
||||
else if (leadchar == '?') {
|
||||
// Save private attributes (CSI ? Pm h/l)
|
||||
ansi_noimpl("Save private attrs");
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI ? Pm s
|
||||
// CSI Pl; Pr s
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
if (leadchar == NUL && (count == 2 || count == 0)) {
|
||||
screen_set_scrolling_region(n1-1, n2-1);
|
||||
}
|
||||
else if (leadchar == '?') {
|
||||
// Restore private attributes (CSI ? Pm h/l)
|
||||
ansi_noimpl("Restore private attrs");
|
||||
}
|
||||
else {
|
||||
// other:
|
||||
// CSI ? Pm r
|
||||
// CSI Pt; Pl; Pb; Pr; Ps$ r
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
if (leadchar == NUL && count == 0) {
|
||||
screen_cursor_restore(0);
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'h': // DEC feature enable
|
||||
case 'l': // DEC feature disable
|
||||
do_csi_privattr(&opts);
|
||||
break;
|
||||
|
||||
case 'm': // SGR - set graphics rendition
|
||||
do_csi_sgr(&opts);
|
||||
break;
|
||||
|
||||
case 'L': // Insert lines (shove down)
|
||||
screen_insert_lines(n1);
|
||||
break;
|
||||
|
||||
case 'M': // Delete lines (pull up)
|
||||
screen_delete_lines(n1);
|
||||
break;
|
||||
|
||||
case '@': // Insert in line (shove right)
|
||||
screen_insert_characters(n1);
|
||||
break;
|
||||
|
||||
case 'P': // Delete in line (pull left)
|
||||
screen_delete_characters(n1);
|
||||
break;
|
||||
|
||||
case 'g': // Clear tabs
|
||||
if (n1 == 3) {
|
||||
screen_clear_all_tabs();
|
||||
}
|
||||
else if (n1 == 0) {
|
||||
screen_clear_tab();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Z': // Tab backward
|
||||
screen_tab_reverse(n1);
|
||||
break;
|
||||
|
||||
case 'I': // Tab forward
|
||||
screen_tab_forward(n1);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (leadchar == '!') { // RIS
|
||||
/* On real VT there are differences between soft and hard reset, we treat both equally */
|
||||
screen_reset();
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'n': // Queries
|
||||
if (leadchar == '>') {
|
||||
// some xterm garbage - discard
|
||||
// CSI > Ps n
|
||||
ansi_noimpl("CSI > %d n", n1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (leadchar == NUL) {
|
||||
if (n1 == 6) {
|
||||
// Query cursor position
|
||||
int x, y;
|
||||
screen_cursor_get(&y, &x);
|
||||
sprintf(buf, "\033[%d;%dR", y + 1, x + 1);
|
||||
apars_respond(buf);
|
||||
}
|
||||
else if (n1 == 5) {
|
||||
// Query device status - reply "Device is OK"
|
||||
apars_respond("\033[0n");
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c': // CSI-c - report capabilities
|
||||
if (leadchar == NUL) {
|
||||
apars_respond("\033[?64;9c"); // pretend we're vt400 with national character sets
|
||||
}
|
||||
else if (leadchar == '>') {
|
||||
// 41 - we're "VT400", 0 - ROM cartridge number
|
||||
sprintf(buf, "\033[>41;%d;0c", FIRMWARE_VERSION_NUM);
|
||||
apars_respond(buf);
|
||||
}
|
||||
else {
|
||||
warn_bad_csi();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'x': // DECREQTPARM -> DECREPTPARM
|
||||
do_csi_decreqtparm(&opts);
|
||||
break;
|
||||
|
||||
default:
|
||||
warn_bad_csi();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI [?] Pm {h|l}
|
||||
* @param opts
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR do_csi_privattr(CSI_Data *opts)
|
||||
{
|
||||
bool yn = (opts->key == 'h');
|
||||
|
||||
if (opts->lead == '?') {
|
||||
// --- DEC private attributes ---
|
||||
for (int i = 0; i < opts->count; i++) {
|
||||
int n = opts->n[i];
|
||||
if (n == 1) {
|
||||
screen_set_cursors_alt_mode(yn);
|
||||
}
|
||||
else if (n == 2) {
|
||||
// should reset all Gx to USASCII and reset to VT100 (which we use always)
|
||||
screen_set_charset(0, 'B');
|
||||
screen_set_charset(1, 'B');
|
||||
}
|
||||
else if (n == 3) {
|
||||
// DECCOLM 132 column mode - not implemented due to RAM demands
|
||||
ansi_noimpl("132col");
|
||||
|
||||
// DECCOLM side effects as per
|
||||
// https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/deccolm-cls.html
|
||||
screen_clear(CLEAR_ALL);
|
||||
screen_set_scrolling_region(0, 0);
|
||||
screen_cursor_set(0, 0);
|
||||
}
|
||||
else if (n == 4) {
|
||||
// Smooth scroll - not implemented
|
||||
}
|
||||
else if (n == 5) {
|
||||
screen_set_reverse_video(yn);
|
||||
}
|
||||
else if (n == 6) {
|
||||
screen_set_origin_mode(yn);
|
||||
}
|
||||
else if (n == 7) {
|
||||
screen_wrap_enable(yn);
|
||||
}
|
||||
else if (n == 8) {
|
||||
// Key auto-repeat
|
||||
// We don't implement this currently, but it could be added
|
||||
// - discard repeated keypress events between keydown and keyup.
|
||||
ansi_noimpl("Auto-repeat toggle");
|
||||
}
|
||||
else if (n == 9 || (n >= 1000 && n <= 1006) || n == 1015) {
|
||||
// 9 - X10 tracking
|
||||
// 1000 - X11 mouse - Send Mouse X & Y on button press and release.
|
||||
// 1001 - Hilite mouse tracking - not impl
|
||||
// 1002 - Cell Motion Mouse Tracking
|
||||
// 1003 - All Motion Mouse Tracking
|
||||
// 1004 - Send FocusIn/FocusOut events
|
||||
// 1005 - Enable UTF-8 Mouse Mode - we implement this as an alias to X10 mode
|
||||
// 1006 - SGR mouse mode
|
||||
|
||||
if (n == 9) mouse_tracking.mode = yn ? MTM_X10 : MTM_NONE;
|
||||
else if (n == 1000) mouse_tracking.mode = yn ? MTM_NORMAL : MTM_NONE;
|
||||
else if (n == 1002) mouse_tracking.mode = yn ? MTM_BUTTON_MOTION : MTM_NONE;
|
||||
else if (n == 1003) mouse_tracking.mode = yn ? MTM_ANY_MOTION : MTM_NONE;
|
||||
else if (n == 1004) mouse_tracking.focus_tracking = yn;
|
||||
else if (n == 1005) mouse_tracking.encoding = yn ? MTE_UTF8 : MTE_SIMPLE;
|
||||
else if (n == 1006) mouse_tracking.encoding = yn ? MTE_SGR : MTE_SIMPLE;
|
||||
else if (n == 1015) mouse_tracking.encoding = yn ? MTE_URXVT : MTE_SIMPLE;
|
||||
}
|
||||
else if (n == 12) {
|
||||
// TODO Cursor blink on/off
|
||||
ansi_noimpl("Cursor blink toggle");
|
||||
}
|
||||
else if (n == 40) {
|
||||
// allow/disallow 80->132 mode
|
||||
// not implemented because of RAM demands
|
||||
ansi_noimpl("132col enable");
|
||||
}
|
||||
else if (n == 45) {
|
||||
// reverse wrap-around
|
||||
ansi_noimpl("Reverse Wraparound");
|
||||
}
|
||||
else if (n == 69) {
|
||||
// horizontal margins
|
||||
ansi_noimpl("Left/right margin");
|
||||
}
|
||||
else if (n == 47 || n == 1047) {
|
||||
// Switch to/from alternate screen
|
||||
// - not implemented fully due to RAM demands
|
||||
screen_swap_state(yn);
|
||||
}
|
||||
else if (n == 1048) {
|
||||
// same as DECSC - save/restore cursor with attributes
|
||||
if (yn) {
|
||||
screen_cursor_save(true);
|
||||
}
|
||||
else {
|
||||
screen_cursor_restore(true);
|
||||
}
|
||||
}
|
||||
else if (n == 1049) {
|
||||
// save/restore cursor and screen and clear it
|
||||
if (yn) {
|
||||
screen_cursor_save(true);
|
||||
screen_swap_state(true); // this should save the screen - can't because of RAM size
|
||||
screen_clear(CLEAR_ALL);
|
||||
}
|
||||
else {
|
||||
screen_clear(CLEAR_ALL);
|
||||
screen_swap_state(false); // this should restore the screen - can't because of RAM size
|
||||
screen_cursor_restore(true);
|
||||
}
|
||||
}
|
||||
else if (n >= 1050 && n <= 1053) {
|
||||
// TODO Different kinds of function key emulation ?
|
||||
// (In practice this seems hardly ever used)
|
||||
|
||||
// Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.
|
||||
// Ps = 1 0 5 1 -> Set Sun function-key mode.
|
||||
// Ps = 1 0 5 2 -> Set HP function-key mode.
|
||||
// Ps = 1 0 5 3 -> Set SCO function-key mode.
|
||||
ansi_noimpl("FN key emul type");
|
||||
}
|
||||
else if (n == 2004) {
|
||||
// Bracketed paste mode
|
||||
// Discard, we don't implement this
|
||||
}
|
||||
else if (n == 25) {
|
||||
screen_set_cursor_visible(yn);
|
||||
}
|
||||
else {
|
||||
ansi_noimpl("CSI ? %d %c", n, opts->key);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// --- DEC standard attributes ---
|
||||
for (int i = 0; i < opts->count; i++) {
|
||||
int n = opts->n[i];
|
||||
|
||||
if (n == 4) {
|
||||
screen_set_insert_mode(yn);
|
||||
}
|
||||
else if (n == 20) {
|
||||
screen_set_newline_mode(yn);
|
||||
}
|
||||
else {
|
||||
ansi_noimpl("CSI %d %c", n, opts->key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSI [ Pm m
|
||||
* @param opts
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR do_csi_sgr(CSI_Data *opts)
|
||||
{
|
||||
int count = opts->count;
|
||||
|
||||
if (count == 0) {
|
||||
count = 1; // this makes it work as 0 (reset)
|
||||
}
|
||||
|
||||
if (opts->lead != NUL) {
|
||||
// some xterm garbage - discard
|
||||
// CSI > Ps; Ps m
|
||||
return;
|
||||
}
|
||||
|
||||
// iterate arguments
|
||||
for (int i = 0; i < count; i++) {
|
||||
int n = opts->n[i];
|
||||
|
||||
if (n == SGR_RESET) screen_reset_sgr();
|
||||
// -- set color --
|
||||
else if (n >= SGR_FG_START && n <= SGR_FG_END) screen_set_fg((Color) (n - SGR_FG_START)); // ANSI normal fg
|
||||
else if (n >= SGR_BG_START && n <= SGR_BG_END) screen_set_bg((Color) (n - SGR_BG_START)); // ANSI normal bg
|
||||
else if (n == SGR_FG_DEFAULT) screen_set_fg(termconf_scratch.default_fg); // default fg
|
||||
else if (n == SGR_BG_DEFAULT) screen_set_bg(termconf_scratch.default_bg); // default bg
|
||||
// -- set attr --
|
||||
else if (n == SGR_BOLD) screen_set_sgr(ATTR_BOLD, 1);
|
||||
else if (n == SGR_FAINT) screen_set_sgr(ATTR_FAINT, 1);
|
||||
else if (n == SGR_ITALIC) screen_set_sgr(ATTR_ITALIC, 1);
|
||||
else if (n == SGR_UNDERLINE) screen_set_sgr(ATTR_UNDERLINE, 1);
|
||||
else if (n == SGR_BLINK || n == SGR_BLINK_FAST) screen_set_sgr(ATTR_BLINK, 1); // 6 - rapid blink, not supported
|
||||
else if (n == SGR_STRIKE) screen_set_sgr(ATTR_STRIKE, 1);
|
||||
else if (n == SGR_FRAKTUR) screen_set_sgr(ATTR_FRAKTUR, 1);
|
||||
else if (n == SGR_INVERSE) screen_set_sgr_inverse(1);
|
||||
// -- clear attr --
|
||||
else if (n == SGR_OFF(SGR_BOLD)) screen_set_sgr(ATTR_BOLD, 0); // can also mean "Double Underline"
|
||||
else if (n == SGR_OFF(SGR_FAINT)) screen_set_sgr(ATTR_FAINT | ATTR_BOLD, 0); // "normal"
|
||||
else if (n == SGR_OFF(SGR_ITALIC)) screen_set_sgr(ATTR_ITALIC | ATTR_FRAKTUR, 0); // there is no dedicated OFF code for Fraktur
|
||||
else if (n == SGR_OFF(SGR_UNDERLINE)) screen_set_sgr(ATTR_UNDERLINE, 0);
|
||||
else if (n == SGR_OFF(SGR_BLINK)) screen_set_sgr(ATTR_BLINK, 0);
|
||||
else if (n == SGR_OFF(SGR_STRIKE)) screen_set_sgr(ATTR_STRIKE, 0);
|
||||
else if (n == SGR_OFF(SGR_INVERSE)) screen_set_sgr_inverse(0);
|
||||
// -- AIX bright colors --
|
||||
else if (n >= SGR_FG_BRT_START && n <= SGR_FG_BRT_END) screen_set_fg((Color) ((n - SGR_FG_BRT_START) + 8)); // AIX bright fg
|
||||
else if (n >= SGR_BG_BRT_START && n <= SGR_BG_BRT_END) screen_set_bg((Color) ((n - SGR_BG_BRT_START) + 8)); // AIX bright bg
|
||||
else {
|
||||
ansi_noimpl("SGR %d", n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// data tables for the DECREPTPARM command response
|
||||
|
||||
struct DECREPTPARM_parity { int parity; const char * msg; };
|
||||
static const struct DECREPTPARM_parity DECREPTPARM_parity_arr[] = {
|
||||
{PARITY_NONE, "1"},
|
||||
{PARITY_ODD, "4"},
|
||||
{PARITY_EVEN, "5"},
|
||||
{-1, 0}
|
||||
};
|
||||
|
||||
struct DECREPTPARM_baud { int baud; const char * msg; };
|
||||
static const struct DECREPTPARM_baud DECREPTPARM_baud_arr[] = {
|
||||
{BIT_RATE_300, "48"},
|
||||
{BIT_RATE_600, "56"},
|
||||
{BIT_RATE_1200, "64"},
|
||||
{BIT_RATE_2400, "88"},
|
||||
{BIT_RATE_4800, "96"},
|
||||
{BIT_RATE_9600 , "104"},
|
||||
{BIT_RATE_19200 , "112"},
|
||||
{BIT_RATE_38400 , "120"},
|
||||
{BIT_RATE_57600 , "128"}, // this is the last in the spec, follow +8
|
||||
{BIT_RATE_74880 , "136"},
|
||||
{BIT_RATE_115200, "144"},
|
||||
{BIT_RATE_230400, "152"},
|
||||
{BIT_RATE_460800, "160"},
|
||||
{BIT_RATE_921600, "168"},
|
||||
{BIT_RATE_1843200, "176"},
|
||||
{BIT_RATE_3686400, "184"},
|
||||
{-1, 0}
|
||||
};
|
||||
|
||||
/**
|
||||
* CSI [ Ps x
|
||||
* @param opts
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR do_csi_decreqtparm(CSI_Data *opts)
|
||||
{
|
||||
const int n1 = opts->n[0];
|
||||
|
||||
// reference http://vt100.net/docs/vt100-ug/chapter3.html - search DECREPTPARM
|
||||
if (n1 <= 1) {
|
||||
apars_respond("\033["); // this is a response on request (2 would be gratuitous)
|
||||
|
||||
apars_respond(n1 == 0 ? "2;" : "3;");
|
||||
|
||||
// Parity
|
||||
for(const struct DECREPTPARM_parity *p = DECREPTPARM_parity_arr; p->parity != -1; p++) {
|
||||
if (p->parity == sysconf->uart_parity) {
|
||||
apars_respond(p->msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// bits per character (uart byte) - 1 = 8, 2 = 7
|
||||
apars_respond(";1;");
|
||||
|
||||
// Baud rate
|
||||
for(const struct DECREPTPARM_baud *p = DECREPTPARM_baud_arr; p->baud != -1; p++) {
|
||||
if (p->baud == sysconf->uart_baudrate) {
|
||||
apars_respond(p->msg);
|
||||
apars_respond(";");
|
||||
apars_respond(p->msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// multiplier 1, flags 0
|
||||
apars_respond(";1;0x"); // ROM cartridge number ??
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_CSI_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_CSI_H
|
||||
|
||||
void apars_handle_csi(char leadchar, const int *params, int count, char keychar);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_CSI_H
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Handle DCS sequences (Device Control String)
|
||||
// DCS Pt ST
|
||||
// (DCS = ESC P)
|
||||
//
|
||||
// Those are used for terminal status queries.
|
||||
//
|
||||
// Pt =
|
||||
// $ q " p .... read conformance level
|
||||
// $ q " q .... read character protection attribute
|
||||
// $ q r ...... read scrolling region extents
|
||||
// $ q s ...... read horizontal margins extents
|
||||
// $ q m ...... read SGR in a format that would restore them exactly when run as a CSI Pm m
|
||||
// $ q SP q ... read cursor style
|
||||
//
|
||||
// For details, see
|
||||
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions
|
||||
//
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_dcs.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "screen.h"
|
||||
#include "apars_logging.h"
|
||||
|
||||
/**
|
||||
* Helper function to parse incoming DCS (Device Control String)
|
||||
* @param buffer - the DCS body (after DCS and before ST)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_dcs(const char *buffer)
|
||||
{
|
||||
char buf[64]; // just about big enough for full-house SGR
|
||||
size_t len = strlen(buffer);
|
||||
if ((len == 3 || len == 4) && strneq(buffer, "$q", 2)) {
|
||||
// DECRQSS - Request Status String
|
||||
if (strneq(buffer+2, "\"p", 2)) {
|
||||
// DECSCL - Select Conformance Level
|
||||
apars_respond("\033P1$r64;1\"p\033\\"); // 64;1 - Pretend we are VT400 with 7-bit characters
|
||||
}
|
||||
else if (strneq(buffer+2, "\"q", 2)) {
|
||||
// DECSCA - Select character protection attribute
|
||||
sprintf(buf, "\033P1$r%d\"q\033\\", 0); // 0 - Can erase - TODO real protection status if implemented
|
||||
apars_respond(buf);
|
||||
}
|
||||
else if (buffer[2] == 'r') {
|
||||
// DECSTBM - Query scrolling region
|
||||
sprintf(buf, "\033P1$r%d;%dr\033\\", 1, termconf_scratch.height); // 1-80 TODO real extent of scrolling region
|
||||
apars_respond(buf);
|
||||
}
|
||||
else if (buffer[2] == 's') {
|
||||
// DECSLRM - Query horizontal margins
|
||||
sprintf(buf, "\033P1$r%d;%ds\033\\", 1, termconf_scratch.width); // Can erase - TODO real extent of horiz margins if implemented
|
||||
apars_respond(buf);
|
||||
}
|
||||
else if (buffer[2] == 'm') {
|
||||
// SGR - query SGR
|
||||
apars_respond("\033P1$r");
|
||||
screen_report_sgr(buf);
|
||||
apars_respond(buf);
|
||||
apars_respond("m\033\\");
|
||||
}
|
||||
else if (strneq(buffer+2, " q", 2)) {
|
||||
// DECSCUSR - Query cursor style
|
||||
sprintf(buf, "\033P1$r%d q\033\\", 1);
|
||||
/*
|
||||
Ps = 0 -> blinking block.
|
||||
Ps = 1 -> blinking block (default).
|
||||
Ps = 2 -> steady block.
|
||||
Ps = 3 -> blinking underline.
|
||||
Ps = 4 -> steady underline.
|
||||
Ps = 5 -> blinking bar (xterm).
|
||||
Ps = 6 -> steady bar (xterm).
|
||||
*/
|
||||
apars_respond(buf);
|
||||
}
|
||||
else {
|
||||
// invalid query
|
||||
ansi_noimpl("DCS %s ST", buffer);
|
||||
sprintf(buf, "\033P0$r%s\033\\", buffer+2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ansi_warn("Bad DCS: %s", buffer);
|
||||
apars_show_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_DCS_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_DCS_H
|
||||
|
||||
void apars_handle_dcs(const char *buffer);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_DCS_H
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Logging functions for the parser, can be switched off with a compile flag
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_LOGGING_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_LOGGING_H
|
||||
|
||||
#include <esp8266.h>
|
||||
|
||||
#ifndef DEBUG_ANSI
|
||||
#define DEBUG_ANSI 0
|
||||
#endif
|
||||
|
||||
#ifndef DEBUG_ANSI_NOIMPL
|
||||
#define DEBUG_ANSI_NOIMPL 0
|
||||
#endif
|
||||
|
||||
// defined in the makefile
|
||||
#if DEBUG_ANSI
|
||||
#define ansi_warn warn
|
||||
#define ansi_dbg dbg
|
||||
#else
|
||||
#define ansi_warn(fmt, ...)
|
||||
#define ansi_dbg(fmt, ...)
|
||||
#endif
|
||||
|
||||
#if DEBUG_ANSI_NOIMPL
|
||||
#define ansi_noimpl(fmt, ...) warn("NOIMPL: " fmt, ##__VA_ARGS__)
|
||||
#define ansi_noimpl_r(fmt, ...) warn(fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define ansi_noimpl(fmt, ...)
|
||||
#define ansi_noimpl_r(fmt, ...)
|
||||
#endif
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_LOGGING_H
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Handle OSC commands (Operating System Command)
|
||||
// ESC ] Ps ; Pt ST
|
||||
//
|
||||
// Those are used to pass various text variables to the terminal:
|
||||
//
|
||||
// Ps = 0 or 2 ... set screen title to Pt
|
||||
// Ps = 81-85 ... set button label to Pt
|
||||
//
|
||||
|
||||
#include "apars_osc.h"
|
||||
#include "apars_logging.h"
|
||||
#include "screen.h"
|
||||
#include "ansi_parser.h"
|
||||
|
||||
/**
|
||||
* Helper function to parse incoming OSC (Operating System Control)
|
||||
* @param buffer - the OSC body (after OSC and before ST)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_osc(const char *buffer)
|
||||
{
|
||||
const char *orig_buff = buffer;
|
||||
int n = 0;
|
||||
char c = 0;
|
||||
while ((c = *buffer++) != 0) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
n = (n * 10 + (c - '0'));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == ';') {
|
||||
// Do something with the data string and number
|
||||
// (based on xterm manpage)
|
||||
if (n == 0 || n == 2) {
|
||||
screen_set_title(buffer);
|
||||
}
|
||||
else if (n >= 81 && n <= 85) { // numbers chosen to not collide with any xterm supported codes
|
||||
screen_set_button_text(n - 80, buffer);
|
||||
}
|
||||
else {
|
||||
ansi_noimpl("OSC %d ; %s ST", n, buffer);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ansi_warn("BAD OSC: %s", orig_buff);
|
||||
apars_show_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_OSC_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_OSC_H
|
||||
|
||||
void apars_handle_osc(const char *buffer);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_OSC_H
|
||||
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// Short sequences: (* = not implemented)
|
||||
//
|
||||
// ESC char
|
||||
// ESC 6 Back Index (DECBI), VT420 and up.
|
||||
// ESC 7 Save Cursor (DECSC).
|
||||
// ESC 8 Restore Cursor (DECRC).
|
||||
// ESC 9 Forward Index (DECFI), VT420 and up.
|
||||
// ESC = Application Keypad (DECKPAM).
|
||||
// ESC > Normal Keypad (DECKPNM).
|
||||
// ESC F Cursor to lower left corner of screen.
|
||||
// ESC c Full Reset (RIS).
|
||||
// *ESC l Memory Lock (per HP terminals). Locks memory above the cursor.
|
||||
// *ESC m Memory Unlock (per HP terminals).
|
||||
// ESC n Invoke the G2 Character Set as GL (LS2).
|
||||
// ESC o Invoke the G3 Character Set as GL (LS3).
|
||||
// *ESC | Invoke the G3 Character Set as GR (LS3R).
|
||||
// *ESC } Invoke the G2 Character Set as GR (LS2R).
|
||||
// *ESC ~ Invoke the G1 Character Set as GR (LS1R).
|
||||
//
|
||||
// ESC # Ps
|
||||
// *ESC # 3 DEC double-height line, top half (DECDHL).
|
||||
// *ESC # 4 DEC double-height line, bottom half (DECDHL).
|
||||
// *ESC # 5 DEC single-width line (DECSWL).
|
||||
// *ESC # 6 DEC double-width line (DECDWL).
|
||||
// ESC # 8 DEC Screen Alignment Test (DECALN).
|
||||
//
|
||||
// ESC SP char
|
||||
// *ESC SP F 7-bit controls (S7C1T).
|
||||
// *ESC SP G 8-bit controls (S8C1T).
|
||||
// *ESC SP L Set ANSI conformance level 1 (dpANS X3.134.1).
|
||||
// *ESC SP M Set ANSI conformance level 2 (dpANS X3.134.1).
|
||||
// *ESC SP N Set ANSI conformance level 3 (dpANS X3.134.1).
|
||||
//
|
||||
// Charset commands
|
||||
// ESC ( char Designate G0 character set ('0' = symbols, 'B' = ASCII-US, 'A' = ASCII-UK)
|
||||
// ESC ) char Designate G1 character set
|
||||
// SO Switch to G0 character set
|
||||
// SI Switch to G1 character set
|
||||
//
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_short.h"
|
||||
#include "apars_logging.h"
|
||||
#include "screen.h"
|
||||
|
||||
// ----- Character Set ---
|
||||
|
||||
/**
|
||||
* Command to assign G0 or G1
|
||||
* @param slot - ( or ) for G0 or G1
|
||||
* @param c - character table ID (0, B etc)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_chs_designate(char slot, char c)
|
||||
{
|
||||
if (slot == '(') screen_set_charset(0, c); // G0
|
||||
else if (slot == ')') screen_set_charset(1, c); // G1
|
||||
else {
|
||||
ansi_noimpl("ESC %c %c", slot, c);
|
||||
}
|
||||
// other alternatives * + . - / not implemented
|
||||
}
|
||||
|
||||
/** Select charset slot */
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_chs_switch(int Gx)
|
||||
{
|
||||
screen_set_charset_n(Gx);
|
||||
}
|
||||
|
||||
// ----- ESC SP char -----
|
||||
|
||||
/**
|
||||
* ESC SP <c> (this sets 8/7-bit mode and some other archaic options)
|
||||
* @param c - key character
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_space_cmd(char c)
|
||||
{
|
||||
ansi_noimpl("ESC SP %c", c);
|
||||
}
|
||||
|
||||
// ----- ESC # num -----
|
||||
|
||||
/**
|
||||
* Codes in the format ESC # n
|
||||
* @param c - the trailing symbol (numeric ASCII)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR apars_handle_hash_cmd(char c)
|
||||
{
|
||||
switch(c) {
|
||||
case '3': // Double size, top half
|
||||
case '4': // Single size, bottom half
|
||||
case '5': // Single width, single height
|
||||
case '6': // Double width
|
||||
ansi_noimpl("Double Size Line");
|
||||
break;
|
||||
|
||||
case '8':
|
||||
screen_fill_with_E();
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_noimpl("ESC # %c", c);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- ESC char -----
|
||||
|
||||
/**
|
||||
* Single-character escape codes (ESC x)
|
||||
* @param c - the trailing symbol (ASCII)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR apars_handle_short_cmd(char c)
|
||||
{
|
||||
switch(c) {
|
||||
case 'c': // screen reset
|
||||
screen_reset();
|
||||
break;
|
||||
|
||||
case '7': // save cursor + attributes
|
||||
screen_cursor_save(true);
|
||||
break;
|
||||
|
||||
case '8': // restore cursor + attributes
|
||||
screen_cursor_restore(true);
|
||||
break;
|
||||
|
||||
case 'E': // same as CR LF
|
||||
screen_cursor_move(1, 0, false);
|
||||
screen_cursor_set_x(0);
|
||||
break;
|
||||
|
||||
case 'F': // bottom left
|
||||
screen_cursor_set(termconf_scratch.height-1, 0);
|
||||
break;
|
||||
|
||||
case 'D': // move cursor down, scroll screen up if needed
|
||||
screen_cursor_move(1, 0, true);
|
||||
break;
|
||||
|
||||
case 'M': // move cursor up, scroll screen down if needed
|
||||
screen_cursor_move(-1, 0, true);
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
screen_set_tab();
|
||||
break;
|
||||
|
||||
case '>':
|
||||
screen_set_numpad_alt_mode(false);
|
||||
break;
|
||||
|
||||
case '<': // "Enter ANSI / VT100 mode" - no-op (we don't support VT52 mode)
|
||||
break;
|
||||
|
||||
case '=':
|
||||
screen_set_numpad_alt_mode(true);
|
||||
break;
|
||||
|
||||
case '|': // Invoke the G3 Character Set as GR (LS3R).
|
||||
case '}': // Invoke the G2 Character Set as GR (LS2R).
|
||||
case '~': // Invoke the G1 Character Set as GR (LS1R).
|
||||
// Those do not seem to do anything TODO investigate
|
||||
break;
|
||||
|
||||
case '@': // no-op padding char (?)
|
||||
break;
|
||||
|
||||
case '\\': // spurious string terminator
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_noimpl("ESC %c", c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_SIMPLE_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_SIMPLE_H
|
||||
|
||||
void apars_handle_short_cmd(char c);
|
||||
void apars_handle_hash_cmd(char c);
|
||||
void apars_handle_space_cmd(char c);
|
||||
void apars_handle_chs_designate(char slot, char c);
|
||||
void apars_handle_chs_switch(int Gx);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_SIMPLE_H
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// String based commands.
|
||||
// Those command start with a introducer sequence, followed by arbitrary string,
|
||||
// and end with a String Terminator (`ESC \`, or `BEL`)
|
||||
//
|
||||
// ESC k Pt ST ... set screen title (same as `OSC 0 ; Pt ST`, is shorter)
|
||||
// ESC ] Pt ST ... OSC - Operating System Command (split to its own file)
|
||||
// ESC P Pt ST ... DCS - Device Control String (split to its own file)
|
||||
// ESC ^ Pt ST ... PM - Privacy message (unused)
|
||||
// ESC _ Pt ST ... APC - Application Program Command (unused)
|
||||
// ESC X Pt ST ... SOS - Start Of String (unused; sent back in response to ENQ)
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "apars_string.h"
|
||||
#include "apars_logging.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "screen.h"
|
||||
|
||||
// ----- Generic String cmd - disambiguation -----
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_string_cmd(char leadchar, const char *buffer)
|
||||
{
|
||||
switch (leadchar) {
|
||||
case 'k': // ESC k TITLE ST (defined in GNU screen manpage)
|
||||
screen_set_title(buffer);
|
||||
break;
|
||||
|
||||
case ']': // OSC - Operating System Command
|
||||
apars_handle_osc(buffer);
|
||||
break;
|
||||
|
||||
case 'P': // DCS - Device Control String
|
||||
apars_handle_dcs(buffer);
|
||||
break;
|
||||
|
||||
case '^': // PM - Privacy Message
|
||||
break;
|
||||
|
||||
case '_': // APC - Application Program Command
|
||||
break;
|
||||
|
||||
case 'X': // SOS - Start of String (purpose unclear)
|
||||
break;
|
||||
|
||||
default:
|
||||
ansi_warn("Bad str cmd");
|
||||
apars_show_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_STRING_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_STRING_H
|
||||
|
||||
void apars_handle_string_cmd(char leadchar, const char *buffer);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_STRING_H
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
// UTF-8 parser - collects bytes of a code point before writing them
|
||||
// into a screen cell.
|
||||
//
|
||||
|
||||
#include "apars_utf8.h"
|
||||
#include "apars_logging.h"
|
||||
#include "screen.h"
|
||||
|
||||
static char utf_collect[4];
|
||||
static int utf_i = 0;
|
||||
static int utf_j = 0;
|
||||
|
||||
/**
|
||||
* Clear the buffer where we collect pieces of a code point.
|
||||
* This is used for parser reset.
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_reset_utf8buffer(void)
|
||||
{
|
||||
utf_i = 0;
|
||||
utf_j = 0;
|
||||
memset(utf_collect, 0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a received plain character
|
||||
* @param c - received character
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
apars_handle_plainchar(char c)
|
||||
{
|
||||
// collecting unicode glyphs...
|
||||
if (c & 0x80) {
|
||||
if (utf_i == 0) {
|
||||
// start
|
||||
if (c == 192 || c == 193 || c >= 245) {
|
||||
// forbidden codes
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
utf_i = 2;
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0) {
|
||||
utf_i = 3;
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0) {
|
||||
utf_i = 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) {
|
||||
goto fail;
|
||||
}
|
||||
else {
|
||||
utf_collect[utf_j++] = c;
|
||||
if (utf_j >= utf_i) {
|
||||
screen_putchar(utf_collect);
|
||||
apars_reset_utf8buffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
utf_collect[0] = c;
|
||||
utf_collect[1] = 0; // just to make sure it's closed...
|
||||
screen_putchar(utf_collect);
|
||||
}
|
||||
|
||||
return;
|
||||
fail:
|
||||
ansi_warn("Bad UTF-8: %0Xh", c);
|
||||
apars_reset_utf8buffer();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_APARS_UTF8_H
|
||||
#define ESP_VT100_FIRMWARE_APARS_UTF8_H
|
||||
|
||||
void apars_handle_plainchar(char c);
|
||||
void apars_reset_utf8buffer(void);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_APARS_UTF8_H
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/19.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_ASCII_H
|
||||
#define ESP_VT100_FIRMWARE_ASCII_H
|
||||
|
||||
enum ASCII_CODES {
|
||||
NUL = 0,
|
||||
SOH = 1,
|
||||
STX = 2,
|
||||
ETX = 3,
|
||||
EOT = 4,
|
||||
ENQ = 5,
|
||||
ACK = 6,
|
||||
BEL = 7, /* \a */
|
||||
BS = 8, /* \b */
|
||||
TAB = 9, /* \t */
|
||||
LF = 10, /* \n */
|
||||
VT = 11, /* \v */
|
||||
FF = 12, /* \f */
|
||||
CR = 13, /* \r */
|
||||
SO = 14,
|
||||
SI = 15,
|
||||
DLE = 16,
|
||||
DC1 = 17,
|
||||
DC2 = 18,
|
||||
DC3 = 19,
|
||||
DC4 = 20,
|
||||
NAK = 21,
|
||||
SYN = 22,
|
||||
ETB = 23,
|
||||
CAN = 24,
|
||||
EM = 25,
|
||||
SUB = 26,
|
||||
ESC = 27, /* \033, \x1b, \e */
|
||||
FS = 28,
|
||||
GS = 29,
|
||||
RS = 30,
|
||||
US = 31,
|
||||
SP = 32,
|
||||
DEL = 127,
|
||||
|
||||
// aliases
|
||||
XON = DC1,
|
||||
XOFF = DC3,
|
||||
};
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_ASCII_H
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "cgi_main.h"
|
||||
#include "screen.h"
|
||||
#include "user_main.h"
|
||||
#include "version.h"
|
||||
#include "helpers.h"
|
||||
|
||||
/**
|
||||
|
||||
+166
-64
@@ -5,28 +5,50 @@
|
||||
#include "cgi_sockets.h"
|
||||
#include "uart_driver.h"
|
||||
#include "screen.h"
|
||||
#include "uart_buffer.h"
|
||||
#include "ansi_parser.h"
|
||||
#include "jstring.h"
|
||||
|
||||
#define SOCK_BUF_LEN 1024
|
||||
static char sock_buff[SOCK_BUF_LEN];
|
||||
#define LOOPBACK 0
|
||||
|
||||
#define HB_TIME 1000
|
||||
|
||||
#define SOCK_BUF_LEN 2000
|
||||
|
||||
volatile bool notify_available = true;
|
||||
volatile bool notify_cooldown = false;
|
||||
|
||||
static ETSTimer notifyTim;
|
||||
static ETSTimer notifyTim2;
|
||||
static ETSTimer notifyContentTim;
|
||||
static ETSTimer notifyLabelsTim;
|
||||
static ETSTimer notifyCooldownTim;
|
||||
static ETSTimer heartbeatTim;
|
||||
|
||||
// we're trying to do a kind of mutex here, without the actual primitives
|
||||
// this might glitch, very rarely.
|
||||
// it's recommended to put some delay between setting labels and updating the screen.
|
||||
|
||||
/**
|
||||
* Cooldown delay is over
|
||||
* @param arg
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR
|
||||
notifyTimCb(void *arg) {
|
||||
void *data = NULL;
|
||||
notifyCooldownTimCb(void *arg)
|
||||
{
|
||||
notify_cooldown = false;
|
||||
}
|
||||
|
||||
if (!notify_available) {
|
||||
static void ICACHE_FLASH_ATTR
|
||||
notifyContentTimCb(void *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
|
||||
os_timer_disarm(¬ifyTim);
|
||||
os_timer_setfn(¬ifyTim, notifyTimCb, NULL);
|
||||
os_timer_arm(¬ifyTim, 1, 0);
|
||||
TIMER_START(¬ifyContentTim, notifyContentTimCb, 5, 0);
|
||||
return;
|
||||
}
|
||||
notify_available = false;
|
||||
@@ -43,17 +65,20 @@ notifyTimCb(void *arg) {
|
||||
// cleanup
|
||||
screenSerializeToBuffer(NULL, SOCK_BUF_LEN, &data);
|
||||
|
||||
notify_cooldown = true;
|
||||
notify_available = true;
|
||||
|
||||
TIMER_START(¬ifyCooldownTim, notifyCooldownTimCb, termconf->display_cooldown_ms, 0);
|
||||
}
|
||||
|
||||
static void ICACHE_FLASH_ATTR
|
||||
notifyLabelsTimCb(void *arg)
|
||||
{
|
||||
if (!notify_available) {
|
||||
char sock_buff[SOCK_BUF_LEN];
|
||||
|
||||
if (!notify_available || notify_cooldown) {
|
||||
// postpone a little
|
||||
os_timer_disarm(¬ifyTim2);
|
||||
os_timer_setfn(¬ifyTim2, notifyLabelsTimCb, NULL);
|
||||
os_timer_arm(¬ifyTim2, 1, 0);
|
||||
TIMER_START(¬ifyLabelsTim, notifyLabelsTimCb, 1, 0);
|
||||
return;
|
||||
}
|
||||
notify_available = false;
|
||||
@@ -61,7 +86,18 @@ notifyLabelsTimCb(void *arg)
|
||||
screenSerializeLabelsToBuffer(sock_buff, SOCK_BUF_LEN);
|
||||
cgiWebsockBroadcast(URL_WS_UPDATE, sock_buff, (int) strlen(sock_buff), 0);
|
||||
|
||||
notify_cooldown = true;
|
||||
notify_available = true;
|
||||
|
||||
TIMER_START(¬ifyCooldownTim, notifyCooldownTimCb, termconf->display_cooldown_ms, 0);
|
||||
}
|
||||
|
||||
/** Beep */
|
||||
void ICACHE_FLASH_ATTR
|
||||
send_beep(void)
|
||||
{
|
||||
// here's some potential for a race error with the other broadcast functions :C
|
||||
cgiWebsockBroadcast(URL_WS_UPDATE, "B", 1, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,84 +110,150 @@ void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic)
|
||||
{
|
||||
// this is not the most ideal/cleanest implementation
|
||||
// PRs are welcome for a nicer update "queue" solution
|
||||
if (termconf->display_tout_ms == 0) termconf->display_tout_ms = SCR_DEF_DISPLAY_TOUT_MS;
|
||||
|
||||
// NOTE: the timers are restarted if already running
|
||||
|
||||
if (topic == CHANGE_LABELS) {
|
||||
// separate timer from content change timer, to avoid losing that update
|
||||
os_timer_disarm(¬ifyTim2);
|
||||
os_timer_setfn(¬ifyTim2, notifyLabelsTimCb, NULL);
|
||||
os_timer_arm(¬ifyTim2, SCREEN_NOTIFY_DELAY_MS, 0);
|
||||
TIMER_START(¬ifyLabelsTim, notifyLabelsTimCb, termconf->display_tout_ms, 0);
|
||||
}
|
||||
else if (topic == CHANGE_CONTENT) {
|
||||
// throttle delay
|
||||
os_timer_disarm(¬ifyTim);
|
||||
os_timer_setfn(¬ifyTim, notifyTimCb, NULL);
|
||||
os_timer_arm(¬ifyTim, SCREEN_NOTIFY_DELAY_MS, 0);
|
||||
TIMER_START(¬ifyContentTim, notifyContentTimCb, termconf->display_tout_ms, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR sendMouseAction(char evt, int y, int x, int button, u8 mods)
|
||||
{
|
||||
// one-based
|
||||
x++;
|
||||
y++;
|
||||
|
||||
bool ctrl = (mods & 1) > 0;
|
||||
bool shift = (mods & 2) > 0;
|
||||
bool alt = (mods & 4) > 0;
|
||||
bool meta = (mods & 8) > 0;
|
||||
enum MTM mtm = mouse_tracking.mode;
|
||||
enum MTE mte = mouse_tracking.encoding;
|
||||
|
||||
// No message on release in X10 mode
|
||||
if (mtm == MTM_X10 && (button == 0 || evt == 'r')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt == 'm' && mtm != MTM_BUTTON_MOTION && mtm != MTM_ANY_MOTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt == 'm' && mtm == MTM_BUTTON_MOTION && button == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int eventcode = 0;
|
||||
|
||||
if (mtm == MTM_X10) {
|
||||
eventcode = button-1;
|
||||
}
|
||||
else {
|
||||
if (button == 0 || (evt == 'r' && mte != MTE_SGR)) eventcode = 3; // release
|
||||
else if (button == 1) eventcode = 0;
|
||||
else if (button == 2) eventcode = 1;
|
||||
else if (button == 3) eventcode = 2;
|
||||
else if (button == 4) eventcode = 64;
|
||||
else if (button == 5) eventcode = 65;
|
||||
|
||||
if (shift) eventcode |= 4;
|
||||
if (alt || meta) eventcode |= 8;
|
||||
if (ctrl) eventcode |= 16;
|
||||
|
||||
if (mtm == MTM_BUTTON_MOTION || mtm == MTM_ANY_MOTION) {
|
||||
if (evt == 'm') {
|
||||
eventcode |= 32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encode
|
||||
char buf[20];
|
||||
buf[0] = 0;
|
||||
if (mte == MTE_SIMPLE || mte == MTE_UTF8) {
|
||||
// strictly, for UTF8 this will break if any coord is over 127,
|
||||
// but that is unlikely due to screen size limitations in ESPTerm
|
||||
sprintf(buf, "\x1b[M%c%c%c", (u8)(32+eventcode), (u8)(32+x), (u8)(32+y));
|
||||
}
|
||||
else if (mte == MTE_SGR) {
|
||||
sprintf(buf, "\x1b[<%d;%d;%d%c", eventcode, x, y, evt == 'p' ? 'M' : 'm');
|
||||
}
|
||||
else if (mte == MTE_URXVT) {
|
||||
sprintf(buf, "\x1b[%d;%d;%dM", (u8)(32+eventcode), (u8)(32+x), (u8)(32+y));
|
||||
}
|
||||
|
||||
UART_SendAsync(buf, -1);
|
||||
}
|
||||
|
||||
/** Socket received a message */
|
||||
void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
|
||||
{
|
||||
char buf[20];
|
||||
// Add terminator if missing (seems to randomly happen)
|
||||
data[len] = 0;
|
||||
|
||||
// TODO re-implement those, use single byte markers and B2 encoding
|
||||
ws_dbg("Sock RX str: %s, len %d", data, len);
|
||||
|
||||
dbg("Sock RX str: %s, len %d", data, len);
|
||||
int y, x, m, b;
|
||||
u8 btnNum;
|
||||
|
||||
if (strstarts(data, "STR:")) {
|
||||
char c = data[0];
|
||||
switch (c) {
|
||||
case 's':
|
||||
// pass string verbatim
|
||||
UART_WriteString(UART0, data+4, UART_TIMEOUT_US);
|
||||
}
|
||||
else if (strstarts(data, "BTN:")) {
|
||||
// send button as low ASCII value 1-9
|
||||
int btnNum = data[4] - '0';
|
||||
if (btnNum > 0 && btnNum < 10) {
|
||||
UART_WriteChar(UART0, (unsigned char)btnNum, UART_TIMEOUT_US);
|
||||
#if LOOPBACK
|
||||
for(int i=4;i<strlen(data); i++) {
|
||||
ansi_parser(data[i]);
|
||||
}
|
||||
}
|
||||
else if (strstarts(data, "TAP:")) {
|
||||
// this comes in as 0-based
|
||||
|
||||
int y=0, x=0;
|
||||
|
||||
char *pc=data+4;
|
||||
char c;
|
||||
int phase=0;
|
||||
|
||||
while((c=*pc++) != '\0') {
|
||||
if (c==','||c==';') {
|
||||
phase++;
|
||||
#else
|
||||
UART_SendAsync(data+1, -1);
|
||||
#endif
|
||||
break;
|
||||
case 'b':
|
||||
// action button press
|
||||
btnNum = (u8) (data[1]);
|
||||
if (btnNum > 0 && btnNum < 10) {
|
||||
UART_SendAsync((const char *) &btnNum, 1); // TODO this is where we use user-configured codes
|
||||
}
|
||||
else if (c>='0' && c<='9') {
|
||||
if (phase==0) {
|
||||
y=y*10+(c-'0');
|
||||
} else {
|
||||
x=x*10+(c-'0');
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
case 'p':
|
||||
case 'r':
|
||||
if (mouse_tracking.mode == MTM_NONE) break; // no need to parse, not enabled
|
||||
|
||||
if (!screen_isCoordValid(y, x)) {
|
||||
warn("Mouse input at invalid coordinates");
|
||||
return;
|
||||
}
|
||||
// mouse move
|
||||
y = parse2B(data+1); // row, 0-based
|
||||
x = parse2B(data+3); // column, 0-based
|
||||
b = parse2B(data+5); // mouse button, 0 = none, 1-5 = button number
|
||||
m = parse2B(data+7); // modifier keys held
|
||||
|
||||
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);
|
||||
sendMouseAction(c,y,x,b,m);
|
||||
break;
|
||||
default:
|
||||
ws_warn("Bad command.");
|
||||
}
|
||||
else {
|
||||
warn("Bad command.");
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR heartbeatTimCb(void *unused)
|
||||
{
|
||||
if (notify_available) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/** Socket connected for updates */
|
||||
void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws)
|
||||
{
|
||||
info("Socket connected to "URL_WS_UPDATE);
|
||||
ws_info("Socket connected to "URL_WS_UPDATE);
|
||||
ws->recvCb = updateSockRx;
|
||||
|
||||
TIMER_START(&heartbeatTim, heartbeatTimCb, HB_TIME, 1);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,24 @@
|
||||
|
||||
#define URL_WS_UPDATE "/term/update.ws"
|
||||
|
||||
#include <cgiwebsocket.h>
|
||||
#include "screen.h"
|
||||
|
||||
/** Update websocket connect callback */
|
||||
void updateSockConnect(Websock *ws);
|
||||
void screen_notifyChange(ScreenNotifyChangeTopic topic);
|
||||
|
||||
void send_beep(void);
|
||||
|
||||
// defined in the makefile
|
||||
#if DEBUG_INPUT
|
||||
#define ws_warn warn
|
||||
#define ws_dbg dbg
|
||||
#define ws_info info
|
||||
#else
|
||||
#define ws_warn(...)
|
||||
#define ws_dbg(...)
|
||||
#define ws_info(...)
|
||||
#endif
|
||||
|
||||
#endif //CGI_SOCKETS_H
|
||||
|
||||
+61
-8
@@ -20,7 +20,9 @@ cgiTermCfgSetParams(HttpdConnData *connData)
|
||||
{
|
||||
char buff[50];
|
||||
char redir_url_buf[100];
|
||||
int n, w, h;
|
||||
int32 n, w, h;
|
||||
|
||||
bool shall_clear_screen = false;
|
||||
|
||||
char *redir_url = redir_url_buf;
|
||||
redir_url += sprintf(redir_url, SET_REDIR_ERR);
|
||||
@@ -41,8 +43,11 @@ cgiTermCfgSetParams(HttpdConnData *connData)
|
||||
h = atoi(buff);
|
||||
if (h > 1) {
|
||||
if (w * h <= MAX_SCREEN_SIZE) {
|
||||
termconf->width = w;
|
||||
termconf->height = h;
|
||||
if (termconf->width != w || termconf->height != h) {
|
||||
termconf->width = w;
|
||||
termconf->height = h;
|
||||
shall_clear_screen = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad dimensions: %d x %d (total %d)", w, h, w*h);
|
||||
redir_url += sprintf(redir_url, "term_width,term_height,");
|
||||
@@ -66,7 +71,10 @@ cgiTermCfgSetParams(HttpdConnData *connData)
|
||||
dbg("Screen default BG: %s", buff);
|
||||
n = atoi(buff);
|
||||
if (n >= 0 && n < 16) {
|
||||
termconf->default_bg = (u8) n;
|
||||
if (termconf->default_bg != n) {
|
||||
termconf->default_bg = (u8) n;
|
||||
shall_clear_screen = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad color %s", buff);
|
||||
redir_url += sprintf(redir_url, "default_bg,");
|
||||
@@ -77,18 +85,49 @@ cgiTermCfgSetParams(HttpdConnData *connData)
|
||||
dbg("Parser timeout: %s ms", buff);
|
||||
n = atoi(buff);
|
||||
if (n >= 0) {
|
||||
termconf->parser_tout_ms = (u8) n;
|
||||
termconf->parser_tout_ms = n;
|
||||
} else {
|
||||
warn("Bad timeout %s", buff);
|
||||
warn("Bad parser timeout %s", buff);
|
||||
redir_url += sprintf(redir_url, "parser_tout_ms,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("display_tout_ms")) {
|
||||
dbg("Display update idle timeout: %s ms", buff);
|
||||
n = atoi(buff);
|
||||
if (n > 0) {
|
||||
termconf->display_tout_ms = n;
|
||||
} else {
|
||||
warn("Bad update timeout %s", buff);
|
||||
redir_url += sprintf(redir_url, "display_tout_ms,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("display_cooldown_ms")) {
|
||||
dbg("Display update cooldown: %s ms", buff);
|
||||
n = atoi(buff);
|
||||
if (n > 0) {
|
||||
termconf->display_cooldown_ms = n;
|
||||
} else {
|
||||
warn("Bad cooldown %s", buff);
|
||||
redir_url += sprintf(redir_url, "display_cooldown_ms,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("fn_alt_mode")) {
|
||||
dbg("FN alt mode: %s", buff);
|
||||
n = atoi(buff);
|
||||
termconf->fn_alt_mode = (bool)n;
|
||||
}
|
||||
|
||||
if (GET_ARG("default_fg")) {
|
||||
dbg("Screen default FG: %s", buff);
|
||||
n = atoi(buff);
|
||||
if (n >= 0 && n < 16) {
|
||||
termconf->default_fg = (u8) n;
|
||||
if (termconf->default_fg != n) {
|
||||
termconf->default_fg = (u8) n;
|
||||
shall_clear_screen = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad color %s", buff);
|
||||
redir_url += sprintf(redir_url, "default_fg,");
|
||||
@@ -123,9 +162,14 @@ cgiTermCfgSetParams(HttpdConnData *connData)
|
||||
// All was OK
|
||||
info("Set term params - success, saving...");
|
||||
|
||||
terminal_apply_settings();
|
||||
persist_store();
|
||||
|
||||
if (shall_clear_screen) {
|
||||
terminal_apply_settings();
|
||||
} else {
|
||||
terminal_apply_settings_noclear();
|
||||
}
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
} else {
|
||||
warn("Some settings did not validate, asking for correction");
|
||||
@@ -159,6 +203,15 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
|
||||
else if (streq(token, "parser_tout_ms")) {
|
||||
sprintf(buff, "%d", termconf->parser_tout_ms);
|
||||
}
|
||||
else if (streq(token, "display_tout_ms")) {
|
||||
sprintf(buff, "%d", termconf->display_tout_ms);
|
||||
}
|
||||
else if (streq(token, "display_cooldown_ms")) {
|
||||
sprintf(buff, "%d", termconf->display_cooldown_ms);
|
||||
}
|
||||
else if (streq(token, "fn_alt_mode")) {
|
||||
sprintf(buff, "%d", (int)termconf->fn_alt_mode);
|
||||
}
|
||||
else if (streq(token, "theme")) {
|
||||
sprintf(buff, "%d", termconf->theme);
|
||||
}
|
||||
|
||||
+40
-6
@@ -193,6 +193,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData)
|
||||
int len;
|
||||
char buff[256];
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
// auto-turn on STA
|
||||
if ((wificonf->opmode & STATION_MODE) == 0) {
|
||||
wificonf->opmode |= STATION_MODE;
|
||||
@@ -265,6 +270,13 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
|
||||
char buff[100];
|
||||
struct ip_info info;
|
||||
|
||||
buff[0] = 0; // avoid unitialized read
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "application/json");
|
||||
httpdEndHeaders(connData);
|
||||
@@ -276,6 +288,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
|
||||
}
|
||||
|
||||
STATION_STATUS st = wifi_station_get_connect_status();
|
||||
dbg("CONN STATE = %d", st);
|
||||
switch(st) {
|
||||
case STATION_IDLE:
|
||||
sprintf(buff, "{\"status\": \"idle\"}"); // unclear when this is used
|
||||
@@ -301,9 +314,14 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
|
||||
wifi_get_ip_info(STATION_IF, &info);
|
||||
sprintf(buff, "{\"status\": \"success\", \"ip\": \""IPSTR"\"}", GOOD_IP2STR(info.ip.addr));
|
||||
break;
|
||||
|
||||
default:
|
||||
sprintf(buff, "{\"status\": \"working\", \"wtf\": \"state = %d\"}", st);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
tplSend(connData, buff, -1);
|
||||
httpdSend(connData, buff, -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
@@ -336,6 +354,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
bool sta_turned_on = false;
|
||||
bool sta_ssid_pw_changed = false;
|
||||
|
||||
// ---- WiFi opmode ----
|
||||
|
||||
if (GET_ARG("opmode")) {
|
||||
@@ -366,6 +387,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
|
||||
|
||||
if (enable) {
|
||||
wificonf->opmode |= STATION_MODE;
|
||||
sta_turned_on = true;
|
||||
} else {
|
||||
wificonf->opmode &= ~STATION_MODE;
|
||||
}
|
||||
@@ -463,6 +485,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
|
||||
info("Setting station SSID to: \"%s\"", buff);
|
||||
strncpy_safe(wificonf->sta_ssid, buff, SSID_LEN);
|
||||
wifi_change_flags.sta = true;
|
||||
sta_ssid_pw_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,12 +497,13 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
|
||||
info("Setting station password to: \"%s\"", buff);
|
||||
strncpy_safe(wificonf->sta_password, buff, PASSWORD_LEN);
|
||||
wifi_change_flags.sta = true;
|
||||
sta_ssid_pw_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
|
||||
// All was OK
|
||||
info("Set WiFi params - success, applying in 1000 ms");
|
||||
info("Set WiFi params - success, applying in 2000 ms");
|
||||
|
||||
// Settings are applied only if all was OK
|
||||
//
|
||||
@@ -492,9 +516,19 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
|
||||
// If user connects via the Station IF, they may not even notice the connection reset.
|
||||
os_timer_disarm(&timer);
|
||||
os_timer_setfn(&timer, applyWifiSettingsLaterCb, NULL);
|
||||
os_timer_arm(&timer, 1000, false);
|
||||
os_timer_arm(&timer, 2000, false);
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
if ((sta_ssid_pw_changed || sta_turned_on)
|
||||
&& wificonf->opmode != SOFTAP_MODE
|
||||
&& wificonf->sta_ssid[0] != 0) {
|
||||
// User wants to connect
|
||||
|
||||
info("User wants to connect to SSID, redirecting to ConnStatus page.");
|
||||
httpdRedirect(connData, "/cfg/wifi/connecting");
|
||||
}
|
||||
else {
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
}
|
||||
} else {
|
||||
warn("Some WiFi settings did not validate, asking for correction");
|
||||
// Some errors, appended to the URL as ?err=
|
||||
@@ -558,7 +592,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
|
||||
// For display of our current SSID
|
||||
connectStatus = wifi_station_get_connect_status();
|
||||
x = wifi_get_opmode();
|
||||
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP) {
|
||||
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP || wificonf->opmode == SOFTAP_MODE) {
|
||||
strcpy(buff, "");
|
||||
}
|
||||
else {
|
||||
@@ -571,7 +605,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
|
||||
x = wifi_get_opmode();
|
||||
connectStatus = wifi_station_get_connect_status();
|
||||
|
||||
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP) {
|
||||
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP || wificonf->opmode == SOFTAP_MODE) {
|
||||
strcpy(buff, "");
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "wifimgr.h"
|
||||
#include "persist.h"
|
||||
#include "screen.h"
|
||||
#include "ansi_parser.h"
|
||||
|
||||
#define BTNGPIO 0
|
||||
|
||||
@@ -21,6 +23,28 @@ static bool enable_ap_button = false;
|
||||
|
||||
static ETSTimer resetBtntimer;
|
||||
static ETSTimer blinkyTimer;
|
||||
static ETSTimer ansiParserResetTimer;
|
||||
|
||||
static void ICACHE_FLASH_ATTR ansiParserResetTimerCb(void *arg) {
|
||||
static u32 last_charcnt = 0;
|
||||
static int same_charcnt = -1;
|
||||
if (termconf->parser_tout_ms == 0) return;
|
||||
|
||||
if (last_charcnt != ansi_parser_char_cnt) {
|
||||
last_charcnt = ansi_parser_char_cnt;
|
||||
same_charcnt = 0;
|
||||
}
|
||||
else {
|
||||
if (same_charcnt != -1) {
|
||||
same_charcnt++;
|
||||
if (same_charcnt > termconf->parser_tout_ms) {
|
||||
ansi_parser_reset();
|
||||
same_charcnt = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Holding BOOT pin triggers AP reset, then Factory Reset.
|
||||
// Indicate that by blinking the on-board LED.
|
||||
@@ -102,6 +126,10 @@ void ICACHE_FLASH_ATTR ioInit() {
|
||||
os_timer_setfn(&resetBtntimer, resetBtnTimerCb, NULL);
|
||||
os_timer_arm(&resetBtntimer, 500, 1);
|
||||
|
||||
os_timer_disarm(&ansiParserResetTimer);
|
||||
os_timer_setfn(&ansiParserResetTimer, ansiParserResetTimerCb, NULL);
|
||||
os_timer_arm(&ansiParserResetTimer, 1, 1);
|
||||
|
||||
// One way to enter AP mode - hold GPIO0 low.
|
||||
if (GPIO_INPUT_GET(BTNGPIO) == 0) {
|
||||
// starting "in BOOT mode" - do not install the AP reset timer
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/09/04.
|
||||
//
|
||||
|
||||
#include "jstring.h"
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
encode2B(u16 number, WordB2 *stru)
|
||||
{
|
||||
stru->lsb = (u8) (number % 127);
|
||||
number = (u16) ((number - stru->lsb) / 127);
|
||||
stru->lsb += 1;
|
||||
|
||||
stru->msb = (u8) (number + 1);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
encode3B(u32 number, WordB3 *stru)
|
||||
{
|
||||
stru->lsb = (u8) (number % 127);
|
||||
number = (number - stru->lsb) / 127;
|
||||
stru->lsb += 1;
|
||||
|
||||
stru->msb = (u8) (number % 127);
|
||||
number = (number - stru->msb) / 127;
|
||||
stru->msb += 1;
|
||||
|
||||
stru->xsb = (u8) (number + 1);
|
||||
}
|
||||
|
||||
u16 ICACHE_FLASH_ATTR
|
||||
parse2B(const char *str)
|
||||
{
|
||||
return (u16) ((str[0] - 1) + (str[1] - 1) * 127);
|
||||
}
|
||||
|
||||
u32 ICACHE_FLASH_ATTR
|
||||
parse3B(const char *str)
|
||||
{
|
||||
return (u32) ((str[0] - 1) + (str[1] - 1) * 127 + (str[2] - 1) * 127 * 127);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/09/04.
|
||||
//
|
||||
|
||||
#ifndef ESPTERM_JSTRING_H
|
||||
#define ESPTERM_JSTRING_H
|
||||
|
||||
#include <esp8266.h>
|
||||
|
||||
typedef struct {
|
||||
u8 lsb;
|
||||
u8 msb;
|
||||
} WordB2;
|
||||
|
||||
typedef struct {
|
||||
u8 lsb;
|
||||
u8 msb;
|
||||
u8 xsb;
|
||||
} WordB3;
|
||||
|
||||
void encode2B(u16 number, WordB2 *stru);
|
||||
|
||||
void encode3B(u32 number, WordB3 *stru);
|
||||
|
||||
u16 parse2B(const char *str);
|
||||
|
||||
u32 parse3B(const char *str);
|
||||
|
||||
#endif //ESPTERM_JSTRING_H
|
||||
+2
-2
@@ -29,7 +29,7 @@ HttpdBuiltInUrl routes[] = {
|
||||
// --- Web pages ---
|
||||
ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"),
|
||||
ROUTE_TPL_FILE("/about/?", tplAbout, "/about.tpl"),
|
||||
ROUTE_FILE("/help/?", "/help.tpl"),
|
||||
ROUTE_FILE("/help/?", "/help.html"),
|
||||
|
||||
// --- Sockets ---
|
||||
ROUTE_CGI("/term/init", cgiTermInitialImage),
|
||||
@@ -46,7 +46,7 @@ HttpdBuiltInUrl routes[] = {
|
||||
ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"),
|
||||
|
||||
ROUTE_TPL_FILE("/cfg/wifi/?", tplWlan, "/cfg_wifi.tpl"),
|
||||
ROUTE_FILE("/cfg/wifi/connecting/?", "/cfg_wifi_conn.tpl"),
|
||||
ROUTE_FILE("/cfg/wifi/connecting/?", "/cfg_wifi_conn.html"),
|
||||
ROUTE_CGI("/cfg/wifi/scan", cgiWiFiScan),
|
||||
ROUTE_CGI("/cfg/wifi/connstatus", cgiWiFiConnStatus),
|
||||
ROUTE_CGI("/cfg/wifi/set", cgiWiFiSetParams),
|
||||
|
||||
+957
-290
File diff suppressed because it is too large
Load Diff
+163
-67
@@ -34,29 +34,41 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// Size designed for the wifi config structure
|
||||
// Size designed for the terminal config structure
|
||||
// Must be constant to avoid corrupting user config after upgrade
|
||||
#define TERMCONF_SIZE 200
|
||||
|
||||
#define TERM_BTN_LEN 10
|
||||
#define TERM_TITLE_LEN 64
|
||||
|
||||
typedef enum {
|
||||
CHANGE_CONTENT,
|
||||
CHANGE_LABELS,
|
||||
} ScreenNotifyChangeTopic;
|
||||
#define SCR_DEF_DISPLAY_TOUT_MS 10
|
||||
#define SCR_DEF_DISPLAY_COOLDOWN_MS 30
|
||||
#define SCR_DEF_PARSER_TOUT_MS 10
|
||||
#define SCR_DEF_FN_ALT_MODE false
|
||||
#define SCR_DEF_WIDTH 26
|
||||
#define SCR_DEF_HEIGHT 10
|
||||
#define SCR_DEF_TITLE "ESPTerm"
|
||||
|
||||
#define SCREEN_NOTIFY_DELAY_MS 20
|
||||
/** Maximum screen size (determines size of the static data array) */
|
||||
#define MAX_SCREEN_SIZE (80*25)
|
||||
|
||||
#define TERMCONF_VERSION 1
|
||||
|
||||
// --- Persistent Settings ---
|
||||
|
||||
typedef struct {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u8 default_bg;
|
||||
u8 default_bg; // should be the Color typedef, but this way the size is more explicit
|
||||
u8 default_fg;
|
||||
char title[TERM_TITLE_LEN];
|
||||
char btn[5][TERM_BTN_LEN];
|
||||
u8 theme;
|
||||
u32 parser_tout_ms;
|
||||
u32 display_tout_ms;
|
||||
bool fn_alt_mode; // xterm compatibility mode (alternate codes for some FN keys)
|
||||
u8 config_version;
|
||||
u32 display_cooldown_ms;
|
||||
} TerminalConfigBundle;
|
||||
|
||||
// Live config
|
||||
@@ -68,59 +80,88 @@ extern TerminalConfigBundle * const termconf;
|
||||
*/
|
||||
extern TerminalConfigBundle termconf_scratch;
|
||||
|
||||
void terminal_restore_defaults(void);
|
||||
void terminal_apply_settings(void);
|
||||
enum MTM {
|
||||
MTM_NONE = 0,
|
||||
MTM_X10 = 1,
|
||||
MTM_NORMAL = 2,
|
||||
MTM_BUTTON_MOTION = 3,
|
||||
MTM_ANY_MOTION = 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* Maximum screen size (determines size of the static data array)
|
||||
*
|
||||
* TODO May need adjusting if there are size problems when flashing the ESP.
|
||||
* We could also try to pack the Cell struct to a single 32bit word.
|
||||
*/
|
||||
#define MAX_SCREEN_SIZE (80*30)
|
||||
enum MTE {
|
||||
MTE_SIMPLE = 0,
|
||||
MTE_UTF8 = 1,
|
||||
MTE_SGR = 2,
|
||||
MTE_URXVT = 3,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
enum MTM mode;
|
||||
bool focus_tracking;
|
||||
enum MTE encoding;
|
||||
} MouseTrackingConfig;
|
||||
|
||||
extern MouseTrackingConfig mouse_tracking;
|
||||
|
||||
/** Restore default settings to termconf. Does not apply or copy to scratch. */
|
||||
void terminal_restore_defaults(void);
|
||||
/** Apply settings, redraw (clears the screen) */
|
||||
void terminal_apply_settings(void);
|
||||
/** Apply settings, redraw (no clear - not resized) */
|
||||
void terminal_apply_settings_noclear(void);
|
||||
/** Init the screen */
|
||||
void screen_init(void);
|
||||
/** Change the screen size */
|
||||
void screen_resize(int rows, int cols);
|
||||
/** Set screen title */
|
||||
void screen_set_title(const char *title);
|
||||
/** Set a button text */
|
||||
void screen_set_button_text(int num, const char *text);
|
||||
|
||||
// --- Encoding ---
|
||||
|
||||
typedef enum {
|
||||
CLEAR_TO_CURSOR=0, CLEAR_FROM_CURSOR=1, CLEAR_ALL=2
|
||||
} ClearMode;
|
||||
|
||||
typedef uint8_t Color;
|
||||
CS_USASCII = 'B',
|
||||
CS_UKASCII = 'A',
|
||||
CS_DEC_SUPPLEMENTAL = '0',
|
||||
CS_DOS_437 = '1',
|
||||
} CHARSET;
|
||||
|
||||
httpd_cgi_state screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
|
||||
|
||||
void screenSerializeLabelsToBuffer(char *buffer, size_t buf_len);
|
||||
|
||||
|
||||
typedef struct {
|
||||
u8 lsb;
|
||||
u8 msb;
|
||||
} WordB2;
|
||||
|
||||
/** Encode number to two nice ASCII bytes */
|
||||
void encode2B(u16 number, WordB2 *stru);
|
||||
|
||||
/** Init the screen */
|
||||
void screen_init(void);
|
||||
|
||||
/** Change the screen size */
|
||||
void screen_resize(int rows, int cols);
|
||||
|
||||
/** Check if coord is valid */
|
||||
bool screen_isCoordValid(int y, int x);
|
||||
|
||||
// --- Clearing ---
|
||||
|
||||
typedef enum {
|
||||
CLEAR_TO_CURSOR = 0,
|
||||
CLEAR_FROM_CURSOR = 1,
|
||||
CLEAR_ALL = 2
|
||||
} ClearMode;
|
||||
|
||||
/** Screen reset to default state */
|
||||
void screen_reset(void);
|
||||
|
||||
/** Clear entire screen, set all to 7 on 0 */
|
||||
/** Clear entire screen */
|
||||
void screen_clear(ClearMode mode);
|
||||
|
||||
/** Line reset to gray-on-white, empty */
|
||||
/** Clear line */
|
||||
void screen_clear_line(ClearMode mode);
|
||||
/** Clear part of line */
|
||||
void screen_clear_in_line(unsigned int count);
|
||||
/** Swap to alternate buffer (really what this does is backup terminal title, size and other global attributes) */
|
||||
void screen_swap_state(bool alternate);
|
||||
|
||||
// --- insert / delete ---
|
||||
|
||||
/** Insert lines at cursor, shove down */
|
||||
void screen_insert_lines(unsigned int lines);
|
||||
/** Delete lines at cursor, pull up */
|
||||
void screen_delete_lines(unsigned int lines);
|
||||
/** Insert characters at cursor, shove right */
|
||||
void screen_insert_characters(unsigned int count);
|
||||
/** Delete characters at cursor, pull left */
|
||||
void screen_delete_characters(unsigned int count);
|
||||
/** Shift screen upwards */
|
||||
void screen_scroll_up(unsigned int lines);
|
||||
|
||||
/** Shift screen downwards */
|
||||
void screen_scroll_down(unsigned int lines);
|
||||
|
||||
@@ -128,51 +169,87 @@ void screen_scroll_down(unsigned int lines);
|
||||
|
||||
/** Set cursor position */
|
||||
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 */
|
||||
void screen_cursor_set_x(int x);
|
||||
|
||||
/** Set cursor Y position */
|
||||
void screen_cursor_set_y(int y);
|
||||
|
||||
/** Reset cursor attribs */
|
||||
void screen_reset_cursor(void);
|
||||
|
||||
/** Relative cursor move */
|
||||
void screen_cursor_move(int dy, int dx);
|
||||
|
||||
void screen_cursor_move(int dy, int dx, bool scroll);
|
||||
/** Save the cursor pos */
|
||||
void screen_cursor_save(bool withAttrs);
|
||||
|
||||
/** Restore the cursor pos */
|
||||
void screen_cursor_restore(bool withAttrs);
|
||||
|
||||
/** Enable cursor display */
|
||||
void screen_cursor_enable(bool enable);
|
||||
// --- Cursor behavior setting ---
|
||||
|
||||
/** Toggle INSERT / REPLACE */
|
||||
void screen_set_insert_mode(bool insert);
|
||||
/** Enable CR auto */
|
||||
void screen_set_newline_mode(bool nlm);
|
||||
/** Enable auto wrap */
|
||||
void screen_wrap_enable(bool enable);
|
||||
/** Set scrolling region */
|
||||
void screen_set_scrolling_region(int from, int to);
|
||||
/** Enable or disable origin remap to top left of scrolling region */
|
||||
void screen_set_origin_mode(bool region_origin);
|
||||
|
||||
// --- Colors ---
|
||||
// --- Graphic rendition setting ---
|
||||
|
||||
typedef uint8_t Color; // 0-16
|
||||
|
||||
#define ATTR_BOLD (1<<0)
|
||||
#define ATTR_FAINT (1<<1)
|
||||
#define ATTR_ITALIC (1<<2)
|
||||
#define ATTR_UNDERLINE (1<<3)
|
||||
#define ATTR_BLINK (1<<4)
|
||||
#define ATTR_FRAKTUR (1<<5)
|
||||
#define ATTR_STRIKE (1<<6)
|
||||
|
||||
/** Set cursor foreground color */
|
||||
void screen_set_fg(Color color);
|
||||
|
||||
/** Set cursor background coloor */
|
||||
void screen_set_bg(Color color);
|
||||
/** Enable/disable attrs by bitmask */
|
||||
void screen_set_sgr(u8 attrs, bool ena);
|
||||
/** Set the inverse attribute */
|
||||
void screen_set_sgr_inverse(bool ena);
|
||||
/** Reset cursor attribs */
|
||||
void screen_reset_sgr(void);
|
||||
|
||||
/** make foreground bright */
|
||||
void screen_set_bold(bool bold);
|
||||
// --- Global modes and attributes ---
|
||||
|
||||
/** Set cursor foreground and background color */
|
||||
void screen_set_colors(Color fg, Color bg);
|
||||
/** Enable cursor display */
|
||||
void screen_set_cursor_visible(bool visible);
|
||||
/** Toggle application keypad mode */
|
||||
void screen_set_numpad_alt_mode(bool app_mode);
|
||||
/** Toggle application cursor mode */
|
||||
void screen_set_cursors_alt_mode(bool app_mode);
|
||||
/** Set reverse video mode */
|
||||
void screen_set_reverse_video(bool reverse);
|
||||
|
||||
/** Invert colors */
|
||||
void screen_inverse(bool inverse);
|
||||
// --- Charset ---
|
||||
|
||||
/** Switch G0 <-> G1 */
|
||||
void screen_set_charset_n(int Gx);
|
||||
/** Assign G0 or G1 */
|
||||
void screen_set_charset(int Gx, char charset);
|
||||
|
||||
// --- Tab stops ---
|
||||
|
||||
/** Remove all tabs on the screen */
|
||||
void screen_clear_all_tabs(void);
|
||||
/** Set tab at the current column */
|
||||
void screen_set_tab(void);
|
||||
/** Remove tab at the current column (if any) */
|
||||
void screen_clear_tab(void);
|
||||
/** Move forward one tab */
|
||||
void screen_tab_forward(int count);
|
||||
/** Move backward one tab */
|
||||
void screen_tab_reverse(int count);
|
||||
|
||||
// --- Printing characters ---
|
||||
|
||||
/**
|
||||
* Set a character in the cursor color, move to right with wrap.
|
||||
@@ -180,12 +257,31 @@ void screen_inverse(bool inverse);
|
||||
* unicode (then it can be 4 chars, or terminated by a zero)
|
||||
*/
|
||||
void screen_putchar(const char *ch);
|
||||
/**
|
||||
* esc # 8 - fill entire screen with E of default colors
|
||||
* (DEC alignment test mode)
|
||||
*/
|
||||
void screen_fill_with_E(void);
|
||||
|
||||
#if 0
|
||||
/** Debug dump */
|
||||
void screen_dd(void);
|
||||
#endif
|
||||
// --- Queries ---
|
||||
|
||||
/** Check if coord is valid */
|
||||
bool screen_isCoordValid(int y, int x);
|
||||
/** Report current SGR as num;num;... for DAC query */
|
||||
void screen_report_sgr(char *buffer);
|
||||
|
||||
// --- Notify ---
|
||||
|
||||
typedef enum {
|
||||
CHANGE_CONTENT = 0,
|
||||
CHANGE_LABELS = 1,
|
||||
} ScreenNotifyChangeTopic;
|
||||
|
||||
/**
|
||||
* Called when the screen content or settings change
|
||||
* and the front-end should redraw / update.
|
||||
* @param topic - what kind of change this is (chooses what message to send)
|
||||
*/
|
||||
extern void screen_notifyChange(ScreenNotifyChangeTopic topic);
|
||||
|
||||
#endif // SCREEN_H
|
||||
|
||||
+50
-1
@@ -4,6 +4,47 @@
|
||||
#include "ansi_parser.h"
|
||||
#include "syscfg.h"
|
||||
|
||||
#define LOGBUF_SIZE 2048
|
||||
static char logbuf[LOGBUF_SIZE];
|
||||
static u32 lb_nw = 1;
|
||||
static u32 lb_ls = 0;
|
||||
static ETSTimer flushLogTimer;
|
||||
|
||||
static void buf_putc(char c)
|
||||
{
|
||||
if (lb_ls != lb_nw) {
|
||||
logbuf[lb_nw++] = c;
|
||||
if (lb_nw >= LOGBUF_SIZE) lb_nw = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void buf_pop(void *unused)
|
||||
{
|
||||
u32 quantity = 32;
|
||||
u32 old_ls;
|
||||
while (quantity > 0) {
|
||||
// stop when done
|
||||
if ((lb_ls == lb_nw-1) || (lb_ls == LOGBUF_SIZE-1 && lb_nw == 0)) break;
|
||||
|
||||
old_ls = lb_ls;
|
||||
lb_ls++;
|
||||
if (lb_ls >= LOGBUF_SIZE) lb_ls = 0;
|
||||
|
||||
if (OK == UART_WriteCharCRLF(UART1, logbuf[lb_ls], 1000)) {
|
||||
quantity--;
|
||||
} else {
|
||||
// try another time
|
||||
lb_ls = old_ls;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOCAL void my_putc(char c)
|
||||
{
|
||||
UART_WriteCharCRLF(UART1, (u8) c, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the serial ports
|
||||
*/
|
||||
@@ -15,7 +56,15 @@ void ICACHE_FLASH_ATTR serialInitBase(void)
|
||||
UART_SetStopBits(UART1, ONE_STOP_BIT);
|
||||
UART_SetBaudrate(UART1, BIT_RATE_115200);
|
||||
UART_SetPrintPort(UART1);
|
||||
os_install_putc1(buf_putc);
|
||||
//os_install_putc1(my_putc);
|
||||
UART_SetupAsyncReceiver();
|
||||
|
||||
// 1 ms timer
|
||||
os_timer_disarm(&flushLogTimer);
|
||||
os_timer_setfn(&flushLogTimer, buf_pop, NULL);
|
||||
//os_timer_arm(&flushLogTimer, 1, 1);
|
||||
ets_timer_arm_new(&flushLogTimer, 1, 500, 0); // us timer
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,5 +91,5 @@ void ICACHE_FLASH_ATTR serialInit(void)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR UART_HandleRxByte(char c)
|
||||
{
|
||||
ansi_parser(&c, 1);
|
||||
ansi_parser(c);
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/19.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_SGR_H
|
||||
#define ESP_VT100_FIRMWARE_SGR_H
|
||||
|
||||
enum SGR_CODES {
|
||||
SGR_RESET = 0,
|
||||
SGR_BOLD = 1,
|
||||
SGR_FAINT = 2,
|
||||
SGR_ITALIC = 3,
|
||||
SGR_UNDERLINE = 4,
|
||||
SGR_BLINK = 5,
|
||||
SGR_BLINK_FAST = 6,
|
||||
SGR_INVERSE = 7,
|
||||
SGR_STRIKE = 9,
|
||||
SGR_FRAKTUR = 20,
|
||||
|
||||
SGR_FG_START = 30,
|
||||
SGR_FG_END = 37,
|
||||
SGR_FG_DEFAULT = 39,
|
||||
|
||||
SGR_BG_START = 40,
|
||||
SGR_BG_END = 47,
|
||||
SGR_BG_DEFAULT = 49,
|
||||
|
||||
SGR_FG_BRT_START = 90,
|
||||
SGR_FG_BRT_END = 97,
|
||||
|
||||
SGR_BG_BRT_START = 100,
|
||||
SGR_BG_BRT_END = 107,
|
||||
};
|
||||
|
||||
#define SGR_OFF(n) (20+(n))
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_SGR_H
|
||||
@@ -12,6 +12,8 @@ SystemConfigBundle * const sysconf = &persist.current.sysconf;
|
||||
void ICACHE_FLASH_ATTR
|
||||
sysconf_apply_settings(void)
|
||||
{
|
||||
// !!! Update to current version !!!
|
||||
|
||||
serialInit();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
// Size designed for the wifi config structure
|
||||
// Must be constant to avoid corrupting user config after upgrade
|
||||
#define SYSCONF_SIZE 200
|
||||
#define SYSCONF_VERSION 0
|
||||
|
||||
typedef struct {
|
||||
u32 uart_baudrate;
|
||||
u8 uart_parity;
|
||||
u8 uart_stopbits;
|
||||
u8 config_version;
|
||||
} SystemConfigBundle;
|
||||
|
||||
extern SystemConfigBundle * const sysconf;
|
||||
|
||||
@@ -0,0 +1,830 @@
|
||||
/*
|
||||
* File : uart.c
|
||||
* Copyright (C) 2013 - 2016, Espressif Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of version 3 of the GNU General Public License as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <esp8266.h>
|
||||
#include <uart_register.h>
|
||||
#include "uart_asyncdrv.h-"
|
||||
|
||||
// UartDev is defined and initialized in rom code.
|
||||
extern UartDevice UartDev;
|
||||
|
||||
struct UartBuffer {
|
||||
uint32 UartBuffSize;
|
||||
uint8 *pUartBuff;
|
||||
uint8 *pInPos;
|
||||
uint8 *pOutPos;
|
||||
STATUS BuffState;
|
||||
uint16 Space; //remanent space of the buffer
|
||||
uint8 TcpControl;
|
||||
struct UartBuffer *nextBuff;
|
||||
};
|
||||
|
||||
struct UartRxBuff {
|
||||
uint32 UartRxBuffSize;
|
||||
uint8 *pUartRxBuff;
|
||||
uint8 *pWritePos;
|
||||
uint8 *pReadPos;
|
||||
STATUS RxBuffState;
|
||||
uint32 Space; //remanent space of the buffer
|
||||
};
|
||||
|
||||
LOCAL struct UartBuffer* pTxBuffer = NULL;
|
||||
LOCAL struct UartBuffer* pRxBuffer = NULL;
|
||||
|
||||
// Forward declare
|
||||
#if UART_BUFF_EN
|
||||
LOCAL void Uart_Buf_Cpy(struct UartBuffer* pCur, char* pdata , uint16 data_len);
|
||||
void uart_buf_free(struct UartBuffer* pBuff);
|
||||
void tx_buff_enq(char* pdata, uint16 data_len );
|
||||
LOCAL void tx_fifo_insert(struct UartBuffer* pTxBuff, uint8 data_len, uint8 uart_no);
|
||||
void tx_start_uart_buffer(uint8 uart_no);
|
||||
uint16 rx_buff_deq(char* pdata, uint16 data_len );
|
||||
void Uart_rx_buff_enq();
|
||||
#endif
|
||||
|
||||
//void ICACHE_FLASH_ATTR uart_test_rx();
|
||||
STATUS uart_tx_one_char(uint8 uart, uint8 TxChar);
|
||||
STATUS uart_tx_one_char_no_wait(uint8 uart, uint8 TxChar);
|
||||
void uart1_sendStr_no_wait(const char *str);
|
||||
struct UartBuffer* UART_AsyncBufferInit(u32 size);
|
||||
|
||||
void uart_rx_intr_enable(uint8 uart_no);
|
||||
void uart_rx_intr_disable(uint8 uart_no);
|
||||
void uart0_tx_buffer(uint8 *buf, uint16 len);
|
||||
|
||||
void uart0_sendStr(const char *str);
|
||||
|
||||
|
||||
/*uart demo with a system task, to output what uart receives*/
|
||||
/*this is a example to process uart data from task,please change the priority to fit your application task if exists*/
|
||||
/*it might conflict with your task, if so,please arrange the priority of different task, or combine it to a different event in the same task. */
|
||||
#define uart_recvTaskPrio 0
|
||||
#define uart_recvTaskQueueLen 10
|
||||
os_event_t uart_recvTaskQueue[uart_recvTaskQueueLen];
|
||||
|
||||
#define DBG
|
||||
#define DBG1 uart1_sendStr_no_wait
|
||||
#define DBG2 os_printf
|
||||
|
||||
|
||||
LOCAL void uart0_rx_intr_handler(void *para);
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_config
|
||||
* Description : Internal used function
|
||||
* UART0 used for data TX/RX, RX buffer size is 0x100, interrupt enabled
|
||||
* UART1 just used for debug output
|
||||
* Parameters : uart_no, use UART0 or UART1 defined ahead
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
LOCAL void ICACHE_FLASH_ATTR
|
||||
uart_config(uint8 uart_no)
|
||||
{
|
||||
if (uart_no == UART1){
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
|
||||
}else{
|
||||
/* rcv_buff size if 0x100 */
|
||||
ETS_UART_INTR_ATTACH(uart0_rx_intr_handler, &(UartDev.rcv_buff));
|
||||
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
|
||||
#if UART_HW_RTS
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_U0RTS); //HW FLOW CONTROL RTS PIN
|
||||
#endif
|
||||
#if UART_HW_CTS
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_U0CTS); //HW FLOW CONTROL CTS PIN
|
||||
#endif
|
||||
}
|
||||
uart_div_modify(uart_no, UART_CLK_FREQ / (UartDev.baut_rate));//SET BAUDRATE
|
||||
|
||||
WRITE_PERI_REG(UART_CONF0(uart_no), ((UartDev.exist_parity & UART_PARITY_EN_M) << UART_PARITY_EN_S) //SET BIT AND PARITY MODE
|
||||
| ((UartDev.parity & UART_PARITY_M) <<UART_PARITY_S )
|
||||
| ((UartDev.stop_bits & UART_STOP_BIT_NUM) << UART_STOP_BIT_NUM_S)
|
||||
| ((UartDev.data_bits & UART_BIT_NUM) << UART_BIT_NUM_S));
|
||||
|
||||
//clear rx and tx fifo,not ready
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST); //RESET FIFO
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||
|
||||
if (uart_no == UART0){
|
||||
//set rx fifo trigger
|
||||
WRITE_PERI_REG(UART_CONF1(uart_no),
|
||||
((100 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S) |
|
||||
#if UART_HW_RTS
|
||||
((110 & UART_RX_FLOW_THRHD) << UART_RX_FLOW_THRHD_S) |
|
||||
UART_RX_FLOW_EN | //enbale rx flow control
|
||||
#endif
|
||||
(0x02 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S |
|
||||
UART_RX_TOUT_EN|
|
||||
((0x10 & UART_TXFIFO_EMPTY_THRHD)<<UART_TXFIFO_EMPTY_THRHD_S));//wjl
|
||||
#if UART_HW_CTS
|
||||
SET_PERI_REG_MASK( UART_CONF0(uart_no),UART_TX_FLOW_EN); //add this sentense to add a tx flow control via MTCK( CTS )
|
||||
#endif
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_TOUT_INT_ENA |UART_FRM_ERR_INT_ENA);
|
||||
}else{
|
||||
WRITE_PERI_REG(UART_CONF1(uart_no),((UartDev.rcv_buff.TrigLvl & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S));//TrigLvl default val == 1
|
||||
}
|
||||
//clear all interrupt
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), 0xffff);
|
||||
//enable rx_interrupt
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_OVF_INT_ENA);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart1_tx_one_char
|
||||
* Description : Internal used function
|
||||
* Use uart1 interface to transfer one char
|
||||
* Parameters : uint8 TxChar - character to tx
|
||||
* Returns : OK
|
||||
*******************************************************************************/
|
||||
STATUS uart_tx_one_char(uint8 uart, uint8 TxChar)
|
||||
{
|
||||
while (true){
|
||||
uint32 fifo_cnt = READ_PERI_REG(UART_STATUS(uart)) & (UART_TXFIFO_CNT<<UART_TXFIFO_CNT_S);
|
||||
if ((fifo_cnt >> UART_TXFIFO_CNT_S & UART_TXFIFO_CNT) < 126) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
WRITE_PERI_REG(UART_FIFO(uart) , TxChar);
|
||||
return OK;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart1_write_char
|
||||
* Description : Internal used function
|
||||
* Do some special deal while tx char is '\r' or '\n'
|
||||
* Parameters : char c - character to tx
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
LOCAL void ICACHE_FLASH_ATTR
|
||||
uart1_write_char(char c)
|
||||
{
|
||||
if (c == '\n'){
|
||||
uart_tx_one_char(UART1, '\r');
|
||||
uart_tx_one_char(UART1, '\n');
|
||||
}else if (c == '\r'){
|
||||
|
||||
}else{
|
||||
uart_tx_one_char(UART1, c);
|
||||
}
|
||||
}
|
||||
|
||||
//os_printf output to fifo or to the tx buffer
|
||||
LOCAL void ICACHE_FLASH_ATTR
|
||||
uart0_write_char_no_wait(char c)
|
||||
{
|
||||
#if UART_BUFF_EN //send to uart0 fifo but do not wait
|
||||
uint8 chr;
|
||||
// if (c == '\n'){
|
||||
// chr = '\r';
|
||||
// tx_buff_enq(&chr, 1);
|
||||
// chr = '\n';
|
||||
// tx_buff_enq(&chr, 1);
|
||||
// }else if (c == '\r'){
|
||||
//
|
||||
// }else{
|
||||
tx_buff_enq(&c,1);
|
||||
// }
|
||||
#else //send to uart tx buffer
|
||||
if (c == '\n'){
|
||||
uart_tx_one_char_no_wait(UART0, '\r');
|
||||
uart_tx_one_char_no_wait(UART0, '\n');
|
||||
}else if (c == '\r'){
|
||||
|
||||
}
|
||||
else{
|
||||
uart_tx_one_char_no_wait(UART0, c);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart0_tx_buffer
|
||||
* Description : use uart0 to transfer buffer
|
||||
* Parameters : uint8 *buf - point to send buffer
|
||||
* uint16 len - buffer len
|
||||
* Returns :
|
||||
*******************************************************************************/
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart0_tx_buffer(uint8 *buf, uint16 len)
|
||||
{
|
||||
uint16 i;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
uart_tx_one_char(UART0, buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart0_sendStr
|
||||
* Description : use uart0 to transfer buffer
|
||||
* Parameters : uint8 *buf - point to send buffer
|
||||
* uint16 len - buffer len
|
||||
* Returns :
|
||||
*******************************************************************************/
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart0_sendStr(const char *str)
|
||||
{
|
||||
while(*str){
|
||||
uart_tx_one_char(UART0, *str++);
|
||||
}
|
||||
}
|
||||
void at_port_print(const char *str) __attribute__((alias("uart0_sendStr")));
|
||||
/******************************************************************************
|
||||
* FunctionName : uart0_rx_intr_handler
|
||||
* Description : Internal used function
|
||||
* UART0 interrupt handler, add self handle code inside
|
||||
* Parameters : void *para - point to ETS_UART_INTR_ATTACH's arg
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
LOCAL void
|
||||
uart0_rx_intr_handler(void *para)
|
||||
{
|
||||
/* uart0 and uart1 intr combine togther, when interrupt occur, see reg 0x3ff20020, bit2, bit0 represents
|
||||
* uart1 and uart0 respectively
|
||||
*/
|
||||
uint8 RcvChar;
|
||||
uint8 uart_no = UART0;//UartDev.buff_uart_no;
|
||||
uint8 fifo_len = 0;
|
||||
uint8 buf_idx = 0;
|
||||
uint8 temp,cnt;
|
||||
//RcvMsgBuff *pRxBuff = (RcvMsgBuff *)para;
|
||||
|
||||
/*ATTENTION:*/
|
||||
/*IN NON-OS VERSION SDK, DO NOT USE "ICACHE_FLASH_ATTR" FUNCTIONS IN THE WHOLE HANDLER PROCESS*/
|
||||
/*ALL THE FUNCTIONS CALLED IN INTERRUPT HANDLER MUST BE DECLARED IN RAM */
|
||||
/*IF NOT , POST AN EVENT AND PROCESS IN SYSTEM TASK */
|
||||
if(UART_FRM_ERR_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_FRM_ERR_INT_ST)){
|
||||
DBG1("FRM_ERR\r\n");
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_FRM_ERR_INT_CLR);
|
||||
}else if(UART_RXFIFO_FULL_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_FULL_INT_ST)){
|
||||
// Full interrupt - FIFO threshold reached
|
||||
DBG("f");
|
||||
uart_rx_intr_disable(UART0);
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR);
|
||||
system_os_post(uart_recvTaskPrio, 0, 0); // tell the handler
|
||||
}else if(UART_RXFIFO_TOUT_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_TOUT_INT_ST)){
|
||||
// Timeout interrupt - Line idle for a while
|
||||
DBG("t");
|
||||
uart_rx_intr_disable(UART0);
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_TOUT_INT_CLR);
|
||||
system_os_post(uart_recvTaskPrio, 0, 0); // tell the handler
|
||||
}else if(UART_TXFIFO_EMPTY_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_TXFIFO_EMPTY_INT_ST)){
|
||||
DBG("e");
|
||||
// TX fifo is empty, can send more.
|
||||
|
||||
/* to output uart data from uart buffer directly in empty interrupt handler*/
|
||||
/*instead of processing in system event, in order not to wait for current task/function to quit */
|
||||
/*ATTENTION:*/
|
||||
/*IN NON-OS VERSION SDK, DO NOT USE "ICACHE_FLASH_ATTR" FUNCTIONS IN THE WHOLE HANDLER PROCESS*/
|
||||
/*ALL THE FUNCTIONS CALLED IN INTERRUPT HANDLER MUST BE DECLARED IN RAM */
|
||||
CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
#if UART_BUFF_EN
|
||||
tx_start_uart_buffer(UART0);
|
||||
#endif
|
||||
//system_os_post(uart_recvTaskPrio, 1, 0);
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_TXFIFO_EMPTY_INT_CLR);
|
||||
|
||||
}else if(UART_RXFIFO_OVF_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_OVF_INT_ST)){
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_RXFIFO_OVF_INT_CLR);
|
||||
DBG1("RX OVF!!\r\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_init
|
||||
* Description : user interface for init uart
|
||||
* Parameters : UartBautRate uart0_br - uart0 bautrate
|
||||
* UartBautRate uart1_br - uart1 bautrate
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
#if UART_SELFTEST&UART_BUFF_EN
|
||||
os_timer_t buff_timer_t;
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart_test_rx()
|
||||
{
|
||||
uint8 uart_buf[128]={0};
|
||||
uint16 len = 0;
|
||||
len = rx_buff_deq(uart_buf, 128 );
|
||||
tx_buff_enq(uart_buf,len);
|
||||
}
|
||||
#endif
|
||||
|
||||
LOCAL void ICACHE_FLASH_ATTR ///////
|
||||
uart_recvTask(os_event_t *events)
|
||||
{
|
||||
if(events->sig == 0){
|
||||
#if UART_BUFF_EN
|
||||
Uart_rx_buff_enq();
|
||||
#else
|
||||
uint8 fifo_len = (READ_PERI_REG(UART_STATUS(UART0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
|
||||
uint8 d_tmp = 0;
|
||||
uint8 idx=0;
|
||||
for(idx=0;idx<fifo_len;idx++) {
|
||||
d_tmp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF;
|
||||
uart_tx_one_char(UART0, d_tmp);
|
||||
}
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR|UART_RXFIFO_TOUT_INT_CLR);
|
||||
uart_rx_intr_enable(UART0);
|
||||
#endif
|
||||
}else if(events->sig == 1){
|
||||
#if UART_BUFF_EN
|
||||
//already move uart buffer output to uart empty interrupt
|
||||
//tx_start_uart_buffer(UART0);
|
||||
#else
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart_init(UartBautRate uart0_br, UartBautRate uart1_br)
|
||||
{
|
||||
/*this is a example to process uart data from task,please change the priority to fit your application task if exists*/
|
||||
system_os_task(uart_recvTask, uart_recvTaskPrio, uart_recvTaskQueue, uart_recvTaskQueueLen); //demo with a task to process the uart data
|
||||
|
||||
UartDev.baut_rate = uart0_br;
|
||||
uart_config(UART0);
|
||||
UartDev.baut_rate = uart1_br;
|
||||
uart_config(UART1);
|
||||
ETS_UART_INTR_ENABLE();
|
||||
|
||||
#if UART_BUFF_EN
|
||||
pTxBuffer = UART_AsyncBufferInit(UART_TX_BUFFER_SIZE);
|
||||
pRxBuffer = UART_AsyncBufferInit(UART_RX_BUFFER_SIZE);
|
||||
#endif
|
||||
|
||||
|
||||
/*option 1: use default print, output from uart0 , will wait some time if fifo is full */
|
||||
//do nothing...
|
||||
|
||||
/*option 2: output from uart1,uart1 output will not wait , just for output debug info */
|
||||
/*os_printf output uart data via uart1(GPIO2)*/
|
||||
os_install_putc1((void *)uart1_write_char); //use this one to output debug information via uart1 //
|
||||
|
||||
/*option 3: output from uart0 will skip current byte if fifo is full now... */
|
||||
/*see uart0_write_char_no_wait:you can output via a buffer or output directly */
|
||||
/*os_printf output uart data via uart0 or uart buffer*/
|
||||
//os_install_putc1((void *)uart0_write_char_no_wait); //use this to print via uart0
|
||||
|
||||
#if UART_SELFTEST&UART_BUFF_EN
|
||||
os_timer_disarm(&buff_timer_t);
|
||||
os_timer_setfn(&buff_timer_t, uart_test_rx , NULL); //a demo to process the data in uart rx buffer
|
||||
os_timer_arm(&buff_timer_t,10,1);
|
||||
#endif
|
||||
}
|
||||
|
||||
//void ICACHE_FLASH_ATTR
|
||||
//uart_reattach()
|
||||
//{
|
||||
// uart_init(BIT_RATE_115200, BIT_RATE_115200);
|
||||
//}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_tx_one_char_no_wait
|
||||
* Description : uart tx a single char without waiting for fifo
|
||||
* Parameters : uint8 uart - uart port
|
||||
* uint8 TxChar - char to tx
|
||||
* Returns : STATUS
|
||||
*******************************************************************************/
|
||||
STATUS uart_tx_one_char_no_wait(uint8 uart, uint8 TxChar)
|
||||
{
|
||||
uint8 fifo_cnt = (( READ_PERI_REG(UART_STATUS(uart))>>UART_TXFIFO_CNT_S)& UART_TXFIFO_CNT);
|
||||
if (fifo_cnt < 126) {
|
||||
WRITE_PERI_REG(UART_FIFO(uart) , TxChar);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
//STATUS uart0_tx_one_char_no_wait(uint8 TxChar)
|
||||
//{
|
||||
// uint8 fifo_cnt = (( READ_PERI_REG(UART_STATUS(UART0))>>UART_TXFIFO_CNT_S)& UART_TXFIFO_CNT);
|
||||
// if (fifo_cnt < 126) {
|
||||
// WRITE_PERI_REG(UART_FIFO(UART0) , TxChar);
|
||||
// }
|
||||
// return OK;
|
||||
//}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart1_sendStr_no_wait
|
||||
* Description : uart tx a string without waiting for every char, used for print debug info which can be lost
|
||||
* Parameters : const char *str - string to be sent
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
void uart1_sendStr_no_wait(const char *str)
|
||||
{
|
||||
while(*str){
|
||||
uart_tx_one_char_no_wait(UART1, *str++);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if UART_BUFF_EN
|
||||
/******************************************************************************
|
||||
* FunctionName : Uart_Buf_Init
|
||||
* Description : tx buffer enqueue: fill a first linked buffer
|
||||
* Parameters : char *pdata - data point to be enqueue
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
struct UartBuffer* ICACHE_FLASH_ATTR
|
||||
UART_AsyncBufferInit(uint32 buf_size)
|
||||
{
|
||||
uint32 heap_size = system_get_free_heap_size();
|
||||
if(heap_size <=buf_size){
|
||||
DBG1("no buf for uart\n\r");
|
||||
return NULL;
|
||||
}else{
|
||||
DBG("test heap size: %d\n\r",heap_size);
|
||||
struct UartBuffer* pBuff = (struct UartBuffer* )os_malloc(sizeof(struct UartBuffer));
|
||||
pBuff->UartBuffSize = buf_size;
|
||||
pBuff->pUartBuff = (uint8*)os_malloc(pBuff->UartBuffSize);
|
||||
pBuff->pInPos = pBuff->pUartBuff;
|
||||
pBuff->pOutPos = pBuff->pUartBuff;
|
||||
pBuff->Space = (uint16) pBuff->UartBuffSize;
|
||||
pBuff->BuffState = OK;
|
||||
pBuff->nextBuff = NULL;
|
||||
pBuff->TcpControl = RUN;
|
||||
return pBuff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//copy uart buffer
|
||||
LOCAL void Uart_Buf_Cpy(struct UartBuffer* pCur, char* pdata , uint16 data_len)
|
||||
{
|
||||
if(data_len == 0) return;
|
||||
|
||||
uint16 tail_len = (uint16) (pCur->pUartBuff + pCur->UartBuffSize - pCur->pInPos);
|
||||
if(tail_len >= data_len){ //do not need to loop back the queue
|
||||
os_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{
|
||||
os_memcpy(pCur->pInPos, pdata, tail_len);
|
||||
pCur->pInPos += ( tail_len );
|
||||
pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize );
|
||||
pCur->Space -=tail_len;
|
||||
os_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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_buf_free
|
||||
* Description : deinit of the tx buffer
|
||||
* Parameters : struct UartBuffer* pTxBuff - tx buffer struct pointer
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
void ICACHE_FLASH_ATTR
|
||||
uart_buf_free(struct UartBuffer* pBuff)
|
||||
{
|
||||
os_free(pBuff->pUartBuff);
|
||||
os_free(pBuff);
|
||||
}
|
||||
|
||||
|
||||
//rx buffer dequeue
|
||||
uint16 ICACHE_FLASH_ATTR
|
||||
rx_buff_deq(char* pdata, uint16 data_len)
|
||||
{
|
||||
uint16 buf_len = (pRxBuffer->UartBuffSize- pRxBuffer->Space);
|
||||
uint16 tail_len = pRxBuffer->pUartBuff + pRxBuffer->UartBuffSize - pRxBuffer->pOutPos ;
|
||||
uint16 len_tmp = 0;
|
||||
len_tmp = ((data_len > buf_len)?buf_len:data_len);
|
||||
if(pRxBuffer->pOutPos <= pRxBuffer->pInPos){
|
||||
os_memcpy(pdata, pRxBuffer->pOutPos,len_tmp);
|
||||
pRxBuffer->pOutPos+= len_tmp;
|
||||
pRxBuffer->Space += len_tmp;
|
||||
}else{
|
||||
if(len_tmp>tail_len){
|
||||
os_memcpy(pdata, pRxBuffer->pOutPos, tail_len);
|
||||
pRxBuffer->pOutPos += tail_len;
|
||||
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff + (pRxBuffer->pOutPos- pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize );
|
||||
pRxBuffer->Space += tail_len;
|
||||
|
||||
os_memcpy(pdata+tail_len , pRxBuffer->pOutPos, len_tmp-tail_len);
|
||||
pRxBuffer->pOutPos+= ( len_tmp-tail_len );
|
||||
pRxBuffer->pOutPos= (pRxBuffer->pUartBuff + (pRxBuffer->pOutPos- pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize );
|
||||
pRxBuffer->Space +=( len_tmp-tail_len);
|
||||
}else{
|
||||
//os_printf("case 3 in rx deq\n\r");
|
||||
os_memcpy(pdata, pRxBuffer->pOutPos, len_tmp);
|
||||
pRxBuffer->pOutPos += len_tmp;
|
||||
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff + (pRxBuffer->pOutPos- pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize );
|
||||
pRxBuffer->Space += len_tmp;
|
||||
}
|
||||
}
|
||||
if(pRxBuffer->Space >= UART_FIFO_LEN){
|
||||
uart_rx_intr_enable(UART0);
|
||||
}
|
||||
return len_tmp;
|
||||
}
|
||||
|
||||
|
||||
//move data from uart fifo to rx buffer
|
||||
void Uart_rx_buff_enq()
|
||||
{
|
||||
uint8 fifo_len,buf_idx;
|
||||
uint8 fifo_data;
|
||||
#if 1
|
||||
fifo_len = (READ_PERI_REG(UART_STATUS(UART0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
|
||||
if(fifo_len >= pRxBuffer->Space){
|
||||
os_printf("buf full!!!\n\r");
|
||||
}else{
|
||||
buf_idx=0;
|
||||
while(buf_idx < fifo_len){
|
||||
buf_idx++;
|
||||
fifo_data = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF;
|
||||
*(pRxBuffer->pInPos++) = fifo_data;
|
||||
if(pRxBuffer->pInPos == (pRxBuffer->pUartBuff + pRxBuffer->UartBuffSize)){
|
||||
pRxBuffer->pInPos = pRxBuffer->pUartBuff;
|
||||
}
|
||||
}
|
||||
pRxBuffer->Space -= fifo_len ;
|
||||
if(pRxBuffer->Space >= UART_FIFO_LEN){
|
||||
//os_printf("after rx enq buf enough\n\r");
|
||||
uart_rx_intr_enable(UART0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//fill the uart tx buffer
|
||||
void ICACHE_FLASH_ATTR
|
||||
tx_buff_enq(char* pdata, uint16 data_len )
|
||||
{
|
||||
if(pTxBuffer == NULL){
|
||||
DBG1("\n\rnull, create buffer struct\n\r");
|
||||
pTxBuffer = UART_AsyncBufferInit(UART_TX_BUFFER_SIZE);
|
||||
if(pTxBuffer!= NULL){
|
||||
Uart_Buf_Cpy(pTxBuffer , pdata, data_len );
|
||||
}else{
|
||||
DBG1("uart tx MALLOC no buf \n\r");
|
||||
}
|
||||
}else{
|
||||
if(data_len <= pTxBuffer->Space){
|
||||
Uart_Buf_Cpy(pTxBuffer , pdata, data_len);
|
||||
}else{
|
||||
DBG1("UART TX BUF FULL!!!!\n\r");
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
if(pTxBuffer->Space <= URAT_TX_LOWER_SIZE){
|
||||
set_tcp_block();
|
||||
}
|
||||
#endif
|
||||
SET_PERI_REG_MASK(UART_CONF1(UART0), (UART_TX_EMPTY_THRESH_VAL & UART_TXFIFO_EMPTY_THRHD)<<UART_TXFIFO_EMPTY_THRHD_S);
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//--------------------------------
|
||||
LOCAL void tx_fifo_insert(struct UartBuffer* pTxBuff, uint8 data_len, uint8 uart_no)
|
||||
{
|
||||
uint8 i;
|
||||
for(i = 0; i<data_len;i++){
|
||||
WRITE_PERI_REG(UART_FIFO(uart_no) , *(pTxBuff->pOutPos++));
|
||||
if(pTxBuff->pOutPos == (pTxBuff->pUartBuff + pTxBuff->UartBuffSize)){
|
||||
pTxBuff->pOutPos = pTxBuff->pUartBuff;
|
||||
}
|
||||
}
|
||||
pTxBuff->pOutPos = (pTxBuff->pUartBuff + (pTxBuff->pOutPos - pTxBuff->pUartBuff) % pTxBuff->UartBuffSize );
|
||||
pTxBuff->Space += data_len;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : tx_start_uart_buffer
|
||||
* Description : get data from the tx buffer and fill the uart tx fifo, co-work with the uart fifo empty interrupt
|
||||
* Parameters : uint8 uart_no - uart port num
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
void tx_start_uart_buffer(uint8 uart_no)
|
||||
{
|
||||
uint8 tx_fifo_len = (READ_PERI_REG(UART_STATUS(uart_no))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT;
|
||||
uint8 fifo_remain = UART_FIFO_LEN - tx_fifo_len ;
|
||||
uint8 len_tmp;
|
||||
uint16 tail_ptx_len,head_ptx_len,data_len;
|
||||
//struct UartBuffer* pTxBuff = *get_buff_prt();
|
||||
|
||||
if(pTxBuffer){
|
||||
data_len = (pTxBuffer->UartBuffSize - pTxBuffer->Space);
|
||||
if(data_len > fifo_remain){
|
||||
len_tmp = fifo_remain;
|
||||
tx_fifo_enq(pTxBuffer, len_tmp, uart_no);
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
}else{
|
||||
len_tmp = data_len;
|
||||
tx_fifo_enq(pTxBuffer, len_tmp, uart_no);
|
||||
}
|
||||
}else{
|
||||
DBG1("pTxBuff null \n\r");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
void uart_rx_intr_disable(uint8 uart_no)
|
||||
{
|
||||
#if 1
|
||||
CLEAR_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_TOUT_INT_ENA);
|
||||
#else
|
||||
ETS_UART_INTR_DISABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
void uart_rx_intr_enable(uint8 uart_no)
|
||||
{
|
||||
#if 1
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA|UART_RXFIFO_TOUT_INT_ENA);
|
||||
#else
|
||||
ETS_UART_INTR_ENABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//========================================================
|
||||
LOCAL void
|
||||
uart0_write_char(char c)
|
||||
{
|
||||
if (c == '\n') {
|
||||
uart_tx_one_char(UART0, '\r');
|
||||
uart_tx_one_char(UART0, '\n');
|
||||
} else if (c == '\r') {
|
||||
} else {
|
||||
uart_tx_one_char(UART0, c);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetWordLength(uint8 uart_no, UartBitsNum4Char len)
|
||||
{
|
||||
SET_PERI_REG_BITS(UART_CONF0(uart_no),UART_BIT_NUM,len,UART_BIT_NUM_S);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetStopBits(uint8 uart_no, UartStopBitsNum bit_num)
|
||||
{
|
||||
SET_PERI_REG_BITS(UART_CONF0(uart_no),UART_STOP_BIT_NUM,bit_num,UART_STOP_BIT_NUM_S);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetLineInverse(uint8 uart_no, UART_LineLevelInverse inverse_mask)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_LINE_INV_MASK);
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), inverse_mask);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetParity(uint8 uart_no, UartParityMode Parity_mode)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_PARITY |UART_PARITY_EN);
|
||||
if(Parity_mode==NONE_BITS){
|
||||
}else{
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), Parity_mode|UART_PARITY_EN);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetBaudrate(uint8 uart_no,uint32 baud_rate)
|
||||
{
|
||||
uart_div_modify(uart_no, UART_CLK_FREQ /baud_rate);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetFlowCtrl(uint8 uart_no,UART_HwFlowCtrl flow_ctrl,uint8 rx_thresh)
|
||||
{
|
||||
if(flow_ctrl&USART_HardwareFlowControl_RTS){
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_U0RTS);
|
||||
SET_PERI_REG_BITS(UART_CONF1(uart_no),UART_RX_FLOW_THRHD,rx_thresh,UART_RX_FLOW_THRHD_S);
|
||||
SET_PERI_REG_MASK(UART_CONF1(uart_no), UART_RX_FLOW_EN);
|
||||
}else{
|
||||
CLEAR_PERI_REG_MASK(UART_CONF1(uart_no), UART_RX_FLOW_EN);
|
||||
}
|
||||
if(flow_ctrl&USART_HardwareFlowControl_CTS){
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_UART0_CTS);
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_TX_FLOW_EN);
|
||||
}else{
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_TX_FLOW_EN);
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_WaitTxFifoEmpty(uint8 uart_no , uint32 time_out_us) //do not use if tx flow control enabled
|
||||
{
|
||||
uint32 t_s = system_get_time();
|
||||
while (READ_PERI_REG(UART_STATUS(uart_no)) & (UART_TXFIFO_CNT << UART_TXFIFO_CNT_S)){
|
||||
|
||||
if(( system_get_time() - t_s )> time_out_us){
|
||||
break;
|
||||
}
|
||||
WRITE_PERI_REG(0X60000914, 0X73);//WTD
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool ICACHE_FLASH_ATTR
|
||||
UART_CheckOutputFinished(uint8 uart_no, uint32 time_out_us)
|
||||
{
|
||||
uint32 t_start = system_get_time();
|
||||
uint8 tx_fifo_len;
|
||||
uint32 tx_buff_len;
|
||||
while(1){
|
||||
tx_fifo_len =( (READ_PERI_REG(UART_STATUS(uart_no))>>UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT);
|
||||
if(pTxBuffer){
|
||||
tx_buff_len = ((pTxBuffer->UartBuffSize)-(pTxBuffer->Space));
|
||||
}else{
|
||||
tx_buff_len = 0;
|
||||
}
|
||||
|
||||
if( tx_fifo_len==0 && tx_buff_len==0){
|
||||
return TRUE;
|
||||
}
|
||||
if( system_get_time() - t_start > time_out_us){
|
||||
return FALSE;
|
||||
}
|
||||
WRITE_PERI_REG(0X60000914, 0X73);//WTD
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_ResetFifo(uint8 uart_no)
|
||||
{
|
||||
SET_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||
CLEAR_PERI_REG_MASK(UART_CONF0(uart_no), UART_RXFIFO_RST | UART_TXFIFO_RST);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_ClearIntrStatus(uint8 uart_no,uint32 clr_mask)
|
||||
{
|
||||
WRITE_PERI_REG(UART_INT_CLR(uart_no), clr_mask);
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetIntrEna(uint8 uart_no,uint32 ena_mask)
|
||||
{
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), ena_mask);
|
||||
}
|
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
UART_SetPrintPort(uint8 uart_no)
|
||||
{
|
||||
if(uart_no==1){
|
||||
os_install_putc1(uart1_write_char);
|
||||
}else{
|
||||
/*option 1: do not wait if uart fifo is full,drop current character*/
|
||||
os_install_putc1(uart0_write_char_no_wait);
|
||||
/*option 2: wait for a while if uart fifo is full*/
|
||||
os_install_putc1(uart0_write_char);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//========================================================
|
||||
|
||||
//
|
||||
///*test code*/
|
||||
//void ICACHE_FLASH_ATTR
|
||||
//uart_init_2(UartBautRate uart0_br, UartBautRate uart1_br)
|
||||
//{
|
||||
// // rom use 74880 baut_rate, here reinitialize
|
||||
// UartDev.baut_rate = uart0_br;
|
||||
// UartDev.exist_parity = STICK_PARITY_EN;
|
||||
// UartDev.parity = EVEN_BITS;
|
||||
// UartDev.stop_bits = ONE_STOP_BIT;
|
||||
// UartDev.data_bits = EIGHT_BITS;
|
||||
//
|
||||
// uart_config(UART0);
|
||||
// UartDev.baut_rate = uart1_br;
|
||||
// uart_config(UART1);
|
||||
// ETS_UART_INTR_ENABLE();
|
||||
//
|
||||
// // install uart1 putc callback
|
||||
// os_install_putc1((void *)uart1_write_char);//print output at UART1
|
||||
//}
|
||||
//
|
||||
//
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* File : uart.h
|
||||
* Copyright (C) 2013 - 2016, Espressif Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of version 3 of the GNU General Public License as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef UART_APP_H
|
||||
#define UART_APP_H
|
||||
|
||||
#include <esp8266.h>
|
||||
|
||||
#define UART_TX_BUFFER_SIZE 512 //Ring buffer length of tx buffer
|
||||
#define UART_RX_BUFFER_SIZE 512 //Ring buffer length of rx buffer
|
||||
|
||||
#define UART_BUFF_EN 1 //use uart buffer , FOR UART0
|
||||
#define UART_SELFTEST 0 //set 1:enable the loop test demo for uart buffer, FOR UART0
|
||||
|
||||
#define UART_HW_RTS 0 //set 1: enable uart hw flow control RTS, PIN MTDO, FOR UART0
|
||||
#define UART_HW_CTS 0 //set1: enable uart hw flow contrl CTS , PIN MTCK, FOR UART0
|
||||
|
||||
#define UART0 0
|
||||
#define UART1 1
|
||||
|
||||
typedef enum {
|
||||
FIVE_BITS = 0x0,
|
||||
SIX_BITS = 0x1,
|
||||
SEVEN_BITS = 0x2,
|
||||
EIGHT_BITS = 0x3
|
||||
} UartBitsNum4Char;
|
||||
|
||||
typedef enum {
|
||||
ONE_STOP_BIT = 0x1,
|
||||
ONE_HALF_STOP_BIT = 0x2,
|
||||
TWO_STOP_BIT = 0x3
|
||||
} UartStopBitsNum;
|
||||
|
||||
typedef enum {
|
||||
NONE_BITS = 0x2,
|
||||
ODD_BITS = 1,
|
||||
EVEN_BITS = 0
|
||||
} UartParityMode;
|
||||
|
||||
typedef enum {
|
||||
STICK_PARITY_DIS = 0,
|
||||
STICK_PARITY_EN = 1
|
||||
} UartExistParity;
|
||||
|
||||
typedef enum {
|
||||
UART_None_Inverse = 0x0,
|
||||
UART_Rxd_Inverse = UART_RXD_INV,
|
||||
UART_CTS_Inverse = UART_CTS_INV,
|
||||
UART_Txd_Inverse = UART_TXD_INV,
|
||||
UART_RTS_Inverse = UART_RTS_INV,
|
||||
} UART_LineLevelInverse;
|
||||
|
||||
|
||||
typedef enum {
|
||||
BIT_RATE_300 = 300,
|
||||
BIT_RATE_600 = 600,
|
||||
BIT_RATE_1200 = 1200,
|
||||
BIT_RATE_2400 = 2400,
|
||||
BIT_RATE_4800 = 4800,
|
||||
BIT_RATE_9600 = 9600,
|
||||
BIT_RATE_19200 = 19200,
|
||||
BIT_RATE_38400 = 38400,
|
||||
BIT_RATE_57600 = 57600,
|
||||
BIT_RATE_74880 = 74880,
|
||||
BIT_RATE_115200 = 115200,
|
||||
BIT_RATE_230400 = 230400,
|
||||
BIT_RATE_460800 = 460800,
|
||||
BIT_RATE_921600 = 921600,
|
||||
BIT_RATE_1843200 = 1843200,
|
||||
BIT_RATE_3686400 = 3686400,
|
||||
} UartBautRate;
|
||||
|
||||
typedef enum {
|
||||
NONE_CTRL,
|
||||
HARDWARE_CTRL,
|
||||
XON_XOFF_CTRL
|
||||
} UartFlowCtrl;
|
||||
|
||||
typedef enum {
|
||||
USART_HardwareFlowControl_None = 0x0,
|
||||
USART_HardwareFlowControl_RTS = 0x1,
|
||||
USART_HardwareFlowControl_CTS = 0x2,
|
||||
USART_HardwareFlowControl_CTS_RTS = 0x3
|
||||
} UART_HwFlowCtrl;
|
||||
|
||||
typedef enum {
|
||||
EMPTY,
|
||||
UNDER_WRITE,
|
||||
WRITE_OVER
|
||||
} RcvMsgBuffState;
|
||||
|
||||
typedef struct {
|
||||
uint32 RcvBuffSize;
|
||||
uint8 *pRcvMsgBuff;
|
||||
uint8 *pWritePos;
|
||||
uint8 *pReadPos;
|
||||
uint8 TrigLvl; //JLU: may need to pad
|
||||
RcvMsgBuffState BuffState;
|
||||
} RcvMsgBuff;
|
||||
|
||||
typedef struct {
|
||||
uint32 TrxBuffSize;
|
||||
uint8 *pTrxBuff;
|
||||
} TrxMsgBuff;
|
||||
|
||||
typedef enum {
|
||||
BAUD_RATE_DET,
|
||||
WAIT_SYNC_FRM,
|
||||
SRCH_MSG_HEAD,
|
||||
RCV_MSG_BODY,
|
||||
RCV_ESC_CHAR,
|
||||
} RcvMsgState;
|
||||
|
||||
typedef struct {
|
||||
UartBautRate baut_rate;
|
||||
UartBitsNum4Char data_bits;
|
||||
UartExistParity exist_parity;
|
||||
UartParityMode parity;
|
||||
UartStopBitsNum stop_bits;
|
||||
UartFlowCtrl flow_ctrl;
|
||||
RcvMsgBuff rcv_buff;
|
||||
TrxMsgBuff trx_buff;
|
||||
RcvMsgState rcv_state;
|
||||
int received;
|
||||
int buff_uart_no; //indicate which uart use tx/rx buffer
|
||||
} UartDevice;
|
||||
|
||||
void uart_init(UartBautRate uart0_br, UartBautRate uart1_br);
|
||||
|
||||
///////////////////////////////////////
|
||||
#define UART_FIFO_LEN 128 //define the tx fifo length
|
||||
#define UART_TX_EMPTY_THRESH_VAL 0x10
|
||||
|
||||
|
||||
typedef enum {
|
||||
RUN = 0,
|
||||
BLOCK = 1,
|
||||
} TCPState;
|
||||
|
||||
|
||||
//==============================================
|
||||
#define FUNC_UART0_CTS 4
|
||||
#define FUNC_U0CTS 4
|
||||
#define FUNC_U1TXD_BK 2
|
||||
#define UART_LINE_INV_MASK (0x3f<<19)
|
||||
void UART_SetWordLength(uint8 uart_no, UartBitsNum4Char len);
|
||||
void UART_SetStopBits(uint8 uart_no, UartStopBitsNum bit_num);
|
||||
void UART_SetLineInverse(uint8 uart_no, UART_LineLevelInverse inverse_mask);
|
||||
void UART_SetParity(uint8 uart_no, UartParityMode Parity_mode);
|
||||
void UART_SetBaudrate(uint8 uart_no, uint32 baud_rate);
|
||||
void UART_SetFlowCtrl(uint8 uart_no, UART_HwFlowCtrl flow_ctrl, uint8 rx_thresh);
|
||||
void UART_WaitTxFifoEmpty(uint8 uart_no, uint32 time_out_us); //do not use if tx flow control enabled
|
||||
void UART_ResetFifo(uint8 uart_no);
|
||||
void UART_ClearIntrStatus(uint8 uart_no, uint32 clr_mask);
|
||||
void UART_SetIntrEna(uint8 uart_no, uint32 ena_mask);
|
||||
void UART_SetPrintPort(uint8 uart_no);
|
||||
bool UART_CheckOutputFinished(uint8 uart_no, uint32 time_out_us);
|
||||
//==============================================
|
||||
|
||||
// Buffer functions
|
||||
uint16 rx_buff_deq(char* pdata, uint16 data_len);
|
||||
void tx_buff_enq(char* pdata, uint16 data_len);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/24.
|
||||
//
|
||||
|
||||
#include "uart_buffer.h"
|
||||
#include "uart_driver.h"
|
||||
#include "uart_handler.h"
|
||||
#include <esp8266.h>
|
||||
#include <uart_register.h>
|
||||
|
||||
#define UART_TX_BUFFER_SIZE 256 //Ring buffer length of tx buffer
|
||||
#define UART_RX_BUFFER_SIZE 512 //Ring buffer length of rx buffer
|
||||
|
||||
struct UartBuffer {
|
||||
uint32 UartBuffSize;
|
||||
uint8 *pUartBuff;
|
||||
uint8 *pInPos;
|
||||
uint8 *pOutPos;
|
||||
uint16 Space;
|
||||
};
|
||||
|
||||
static struct UartBuffer *pTxBuffer = NULL;
|
||||
static struct UartBuffer *pRxBuffer = NULL;
|
||||
|
||||
static struct UartBuffer *UART_AsyncBufferInit(uint32 buf_size);
|
||||
|
||||
void ICACHE_FLASH_ATTR UART_AllocBuffers(void)
|
||||
{
|
||||
pTxBuffer = UART_AsyncBufferInit(UART_TX_BUFFER_SIZE);
|
||||
pRxBuffer = UART_AsyncBufferInit(UART_RX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : Uart_Buf_Init
|
||||
* Description : tx buffer enqueue: fill a first linked buffer
|
||||
* Parameters : char *pdata - data point to be enqueue
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
static struct UartBuffer *ICACHE_FLASH_ATTR
|
||||
UART_AsyncBufferInit(uint32 buf_size)
|
||||
{
|
||||
uint32 heap_size = system_get_free_heap_size();
|
||||
if (heap_size <= buf_size) {
|
||||
error("uart buf malloc fail, out of memory");
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
struct UartBuffer *pBuff = (struct UartBuffer *) malloc(sizeof(struct UartBuffer));
|
||||
pBuff->UartBuffSize = buf_size;
|
||||
pBuff->pUartBuff = (uint8 *) malloc(pBuff->UartBuffSize);
|
||||
pBuff->pInPos = pBuff->pUartBuff;
|
||||
pBuff->pOutPos = pBuff->pUartBuff;
|
||||
pBuff->Space = (uint16) pBuff->UartBuffSize;
|
||||
return pBuff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy data onto Buffer
|
||||
* @param pCur - buffer
|
||||
* @param pdata - data src
|
||||
* @param data_len - data len
|
||||
*/
|
||||
static void UART_WriteToAsyncBuffer(struct UartBuffer *pCur, const char *pdata, uint16 data_len)
|
||||
{
|
||||
if (data_len == 0) return;
|
||||
|
||||
uint16 tail_len = (uint16) (pCur->pUartBuff + pCur->UartBuffSize - pCur->pInPos);
|
||||
if (tail_len >= data_len) { //do not need to loop back the queue
|
||||
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 {
|
||||
memcpy(pCur->pInPos, pdata, tail_len);
|
||||
pCur->pInPos += (tail_len);
|
||||
pCur->pInPos = (pCur->pUartBuff + (pCur->pInPos - pCur->pUartBuff) % pCur->UartBuffSize);
|
||||
pCur->Space -= 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);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : uart_buf_free
|
||||
* Description : deinit of the tx buffer
|
||||
* Parameters : struct UartBuffer* pTxBuff - tx buffer struct pointer
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
void ICACHE_FLASH_ATTR UART_FreeAsyncBuffer(struct UartBuffer *pBuff)
|
||||
{
|
||||
free(pBuff->pUartBuff);
|
||||
free(pBuff);
|
||||
}
|
||||
|
||||
u16 ICACHE_FLASH_ATTR UART_AsyncRxCount(void)
|
||||
{
|
||||
return (u16) (pRxBuffer->UartBuffSize - pRxBuffer->Space);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve some data from the RX buffer
|
||||
* @param pdata - target
|
||||
* @param data_len - max data to retrieve
|
||||
* @return real nr of bytes retrieved
|
||||
*/
|
||||
uint16 ICACHE_FLASH_ATTR
|
||||
UART_ReadAsync(char *pdata, uint16 data_len)
|
||||
{
|
||||
uint16 buf_len = (uint16) (pRxBuffer->UartBuffSize - pRxBuffer->Space);
|
||||
uint16 tail_len = (uint16) (pRxBuffer->pUartBuff + pRxBuffer->UartBuffSize - pRxBuffer->pOutPos);
|
||||
uint16 len_tmp = 0;
|
||||
len_tmp = ((data_len > buf_len) ? buf_len : data_len);
|
||||
if (pRxBuffer->pOutPos <= pRxBuffer->pInPos) {
|
||||
memcpy(pdata, pRxBuffer->pOutPos, len_tmp);
|
||||
pRxBuffer->pOutPos += len_tmp;
|
||||
pRxBuffer->Space += len_tmp;
|
||||
}
|
||||
else {
|
||||
if (len_tmp > tail_len) {
|
||||
memcpy(pdata, pRxBuffer->pOutPos, tail_len);
|
||||
pRxBuffer->pOutPos += tail_len;
|
||||
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff +
|
||||
(pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize);
|
||||
pRxBuffer->Space += tail_len;
|
||||
|
||||
memcpy(pdata + tail_len, pRxBuffer->pOutPos, len_tmp - tail_len);
|
||||
pRxBuffer->pOutPos += (len_tmp - tail_len);
|
||||
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff +
|
||||
(pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize);
|
||||
pRxBuffer->Space += (len_tmp - tail_len);
|
||||
}
|
||||
else {
|
||||
//os_printf("case 3 in rx deq\n\r");
|
||||
memcpy(pdata, pRxBuffer->pOutPos, len_tmp);
|
||||
pRxBuffer->pOutPos += len_tmp;
|
||||
pRxBuffer->pOutPos = (pRxBuffer->pUartBuff +
|
||||
(pRxBuffer->pOutPos - pRxBuffer->pUartBuff) % pRxBuffer->UartBuffSize);
|
||||
pRxBuffer->Space += len_tmp;
|
||||
}
|
||||
}
|
||||
// this maybe shouldnt be here??
|
||||
if (pRxBuffer->Space >= UART_FIFO_LEN) {
|
||||
uart_rx_intr_enable(UART0);
|
||||
}
|
||||
return len_tmp;
|
||||
}
|
||||
|
||||
//move data from uart fifo to rx buffer
|
||||
void UART_RxFifoCollect(void)
|
||||
{
|
||||
uint8 fifo_len, buf_idx;
|
||||
uint8 fifo_data;
|
||||
fifo_len = (uint8) ((READ_PERI_REG(UART_STATUS(UART0)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT);
|
||||
if (fifo_len >= pRxBuffer->Space) {
|
||||
UART_WriteChar(UART1, '%', 100);
|
||||
}
|
||||
else {
|
||||
buf_idx = 0;
|
||||
while (buf_idx < fifo_len) {
|
||||
buf_idx++;
|
||||
fifo_data = (uint8) (READ_PERI_REG(UART_FIFO(UART0)) & 0xFF);
|
||||
*(pRxBuffer->pInPos++) = fifo_data;
|
||||
if (pRxBuffer->pInPos == (pRxBuffer->pUartBuff + pRxBuffer->UartBuffSize)) {
|
||||
pRxBuffer->pInPos = pRxBuffer->pUartBuff;
|
||||
}
|
||||
}
|
||||
pRxBuffer->Space -= fifo_len;
|
||||
|
||||
if (pRxBuffer->Space >= UART_FIFO_LEN) {
|
||||
uart_rx_intr_enable(UART0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
u16 real_len = (u16) data_len;
|
||||
if (data_len <= 0) real_len = (u16) strlen(pdata);
|
||||
|
||||
// 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, '^', 100);
|
||||
}
|
||||
// }
|
||||
|
||||
// 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);
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------
|
||||
static void UART_TxFifoEnq(struct UartBuffer *pTxBuff, uint8 data_len, uint8 uart_no)
|
||||
{
|
||||
uint8 i;
|
||||
for (i = 0; i < data_len; i++) {
|
||||
WRITE_PERI_REG(UART_FIFO(uart_no), *(pTxBuff->pOutPos++));
|
||||
if (pTxBuff->pOutPos == (pTxBuff->pUartBuff + pTxBuff->UartBuffSize)) {
|
||||
pTxBuff->pOutPos = pTxBuff->pUartBuff;
|
||||
}
|
||||
}
|
||||
pTxBuff->pOutPos = (pTxBuff->pUartBuff + (pTxBuff->pOutPos - pTxBuff->pUartBuff) % pTxBuff->UartBuffSize);
|
||||
pTxBuff->Space += data_len;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* FunctionName : TxFromBuffer
|
||||
* Description : get data from the tx buffer and fill the uart tx fifo, co-work with the uart fifo empty interrupt
|
||||
* Parameters : uint8 uart_no - uart port num
|
||||
* Returns : NONE
|
||||
*******************************************************************************/
|
||||
void UART_DispatchFromTxBuffer(uint8 uart_no)
|
||||
{
|
||||
const uint8 tx_fifo_len = (uint8) ((READ_PERI_REG(UART_STATUS(uart_no)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT);
|
||||
const uint8 fifo_remain = (uint8) (UART_FIFO_LEN - tx_fifo_len);
|
||||
uint8 len_tmp;
|
||||
uint16 data_len;
|
||||
|
||||
// if (pTxBuffer) {
|
||||
data_len = (uint8) (pTxBuffer->UartBuffSize - pTxBuffer->Space);
|
||||
if (data_len > fifo_remain) {
|
||||
len_tmp = fifo_remain;
|
||||
UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no);
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
}
|
||||
else {
|
||||
len_tmp = (uint8) data_len;
|
||||
UART_TxFifoEnq(pTxBuffer, len_tmp, uart_no);
|
||||
}
|
||||
// }
|
||||
// else {
|
||||
// error("pTxBuff null \n\r");
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/24.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_UART_BUFFER_H
|
||||
#define ESP_VT100_FIRMWARE_UART_BUFFER_H
|
||||
|
||||
#include <esp8266.h>
|
||||
|
||||
// the init func
|
||||
void UART_AllocBuffers(void);
|
||||
|
||||
// read from rx buffer
|
||||
uint16 UART_ReadAsync(char *pdata, uint16 data_len);
|
||||
|
||||
// write to tx buffer
|
||||
void UART_SendAsync(const char *pdata, int16_t data_len);
|
||||
|
||||
//move data from uart fifo to rx buffer
|
||||
void UART_RxFifoCollect(void);
|
||||
//move data from uart tx buffer to fifo
|
||||
void UART_DispatchFromTxBuffer(uint8 uart_no);
|
||||
|
||||
u16 UART_AsyncRxCount(void);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_UART_BUFFER_H
|
||||
+2
-2
@@ -84,7 +84,7 @@ bool ICACHE_FLASH_ATTR UART_CheckOutputFinished(UARTn uart_no, uint32 time_out_u
|
||||
uint8 tx_fifo_len;
|
||||
|
||||
while (1) {
|
||||
tx_fifo_len = UART_TxQueLen(uart_no);
|
||||
tx_fifo_len = UART_TxFifoCount(uart_no);
|
||||
|
||||
// TODO If using output circbuf, check if empty
|
||||
|
||||
@@ -162,7 +162,7 @@ STATUS UART_WriteChar(UARTn uart_no, uint8 c, uint32 timeout_us)
|
||||
uint32 t_s = system_get_time();
|
||||
|
||||
while ((system_get_time() - t_s) < timeout_us) {
|
||||
uint8 fifo_cnt = UART_TxQueLen(uart_no);
|
||||
uint8 fifo_cnt = UART_TxFifoCount(uart_no);
|
||||
|
||||
if (fifo_cnt < UART_TX_FULL_THRESH_VAL) {
|
||||
WRITE_PERI_REG(UART_FIFO(uart_no), c);
|
||||
|
||||
+2
-2
@@ -172,8 +172,8 @@ extern UartDevice UartDev;
|
||||
//==============================================
|
||||
|
||||
// FIFO used count
|
||||
#define UART_TxQueLen(uart_no) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT)
|
||||
#define UART_RxQueLen(uart_no) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT)
|
||||
#define UART_TxFifoCount(uart_no) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT)
|
||||
#define UART_RxFifoCount(uart_no) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT)
|
||||
|
||||
STATUS UART_WriteCharCRLF(UARTn uart_no, uint8 c, uint32 timeout_us);
|
||||
STATUS UART_WriteChar(UARTn uart_no, uint8 c, uint32 timeout_us);
|
||||
|
||||
+87
-33
@@ -14,15 +14,26 @@
|
||||
#include <esp8266.h>
|
||||
#include "uart_driver.h"
|
||||
#include "uart_handler.h"
|
||||
#include "uart_buffer.h"
|
||||
|
||||
// messy irq/task based UART handling below
|
||||
|
||||
static void uart0_rx_intr_handler(void *para);
|
||||
|
||||
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 4
|
||||
|
||||
#define uart_recvTaskPrio 1
|
||||
#define uart_recvTaskQueueLen 10
|
||||
#define uart_recvTaskQueueLen 25
|
||||
static os_event_t uart_recvTaskQueue[uart_recvTaskQueueLen];
|
||||
//
|
||||
#define uart_processTaskPrio 0
|
||||
#define uart_processTaskQueueLen 25
|
||||
static os_event_t uart_processTaskQueue[uart_processTaskQueueLen];
|
||||
|
||||
/** Clear the fifos */
|
||||
void ICACHE_FLASH_ATTR clear_rxtx(int uart_no)
|
||||
@@ -50,21 +61,27 @@ void ICACHE_FLASH_ATTR UART_Init(void)
|
||||
UART_ResetFifo(UART0);
|
||||
UART_SetWordLength(UART1, EIGHT_BITS); // debug (output only)
|
||||
UART_ResetFifo(UART1);
|
||||
|
||||
// remainder is configured based on user settings in serial.c
|
||||
}
|
||||
|
||||
/** Configure Rx on UART0 */
|
||||
void ICACHE_FLASH_ATTR UART_SetupAsyncReceiver(void)
|
||||
{
|
||||
UART_AllocBuffers();
|
||||
|
||||
// Start the Rx reading task
|
||||
system_os_task(uart_recvTask, uart_recvTaskPrio, uart_recvTaskQueue, uart_recvTaskQueueLen);
|
||||
system_os_task(uart_processTask, uart_processTaskPrio, uart_processTaskQueue, uart_processTaskQueueLen);
|
||||
|
||||
// set handler
|
||||
ETS_UART_INTR_ATTACH((void *)uart0_rx_intr_handler, &(UartDev.rcv_buff)); // the buf will be used as an arg
|
||||
|
||||
// fifo threshold config
|
||||
uint32_t conf = ((100 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S);
|
||||
// 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);
|
||||
// timeout config
|
||||
conf |= ((0x02 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S); // timeout threshold
|
||||
conf |= ((0x06 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S); // timeout threshold
|
||||
conf |= UART_RX_TOUT_EN; // enable timeout
|
||||
WRITE_PERI_REG(UART_CONF1(UART0), conf);
|
||||
|
||||
@@ -83,48 +100,80 @@ void ICACHE_FLASH_ATTR UART_SetupAsyncReceiver(void)
|
||||
// ---- async receive stuff ----
|
||||
|
||||
|
||||
void uart_rx_intr_disable(uint8 uart_no)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
|
||||
}
|
||||
|
||||
|
||||
void uart_rx_intr_enable(uint8 uart_no)
|
||||
{
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
|
||||
}
|
||||
|
||||
//
|
||||
//void ICACHE_FLASH_ATTR UART_PollRx(void)
|
||||
//{
|
||||
// uint8 fifo_len = (uint8) UART_GetRxFifoCount(UART0);
|
||||
//
|
||||
// for (uint8 idx = 0; idx < fifo_len; idx++) {
|
||||
// uint8 d_tmp = (uint8) (READ_PERI_REG(UART_FIFO(UART0)) & 0xFF);
|
||||
// UART_HandleRxByte(d_tmp);
|
||||
// }
|
||||
//}
|
||||
|
||||
/**
|
||||
* @brief get number of bytes in UART tx fifo
|
||||
* @param UART number
|
||||
* This is the system task handling UART Rx bytes.
|
||||
* The function must be re-entrant.
|
||||
*
|
||||
* @param events
|
||||
*/
|
||||
#define UART_GetRxFifoCount(uart_no) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT)
|
||||
|
||||
|
||||
void ICACHE_FLASH_ATTR UART_PollRx(void)
|
||||
static void uart_recvTask(os_event_t *events)
|
||||
{
|
||||
uint8 fifo_len = UART_GetRxFifoCount(UART0);
|
||||
//#define PROCESS_CHUNK_LEN 64
|
||||
// static char buf[PROCESS_CHUNK_LEN];
|
||||
|
||||
for (uint8 idx = 0; idx < fifo_len; idx++) {
|
||||
uint8 d_tmp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF;
|
||||
UART_HandleRxByte(d_tmp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void ICACHE_FLASH_ATTR uart_recvTask(os_event_t *events)
|
||||
{
|
||||
if (events->sig == 0) {
|
||||
UART_PollRx();
|
||||
UART_RxFifoCollect();
|
||||
|
||||
// clear irq flags
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR | UART_RXFIFO_TOUT_INT_CLR);
|
||||
// enable rx irq again
|
||||
uart_rx_intr_enable(UART0);
|
||||
} else if (events->sig == 1) {
|
||||
|
||||
// Trigger the Reading task
|
||||
system_os_post(uart_processTaskPrio, 5, 0);
|
||||
}
|
||||
else if (events->sig == 1) {
|
||||
// ???
|
||||
}
|
||||
// else if (events->sig == 5) {
|
||||
// // Send a few to the parser...
|
||||
// int bytes = UART_ReadAsync(buf, PROCESS_CHUNK_LEN);
|
||||
// for (uint8 idx = 0; idx < bytes; idx++) {
|
||||
// UART_HandleRxByte(buf[idx]);
|
||||
// }
|
||||
//
|
||||
// // ask for another run
|
||||
// if (UART_AsyncRxCount() > 0) {
|
||||
// system_os_post(uart_recvTaskPrio, 5, 0);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the system task handling UART Rx bytes.
|
||||
* The function must be re-entrant.
|
||||
*
|
||||
* @param events
|
||||
*/
|
||||
static void uart_processTask(os_event_t *events)
|
||||
{
|
||||
static char buf[PROCESS_CHUNK_LEN];
|
||||
|
||||
if (events->sig == 5) {
|
||||
// Send a few to the parser...
|
||||
int bytes = UART_ReadAsync(buf, PROCESS_CHUNK_LEN);
|
||||
for (uint8 idx = 0; idx < bytes; idx++) {
|
||||
UART_HandleRxByte(buf[idx]);
|
||||
}
|
||||
|
||||
// ask for another run
|
||||
if (UART_AsyncRxCount() > 0) {
|
||||
system_os_post(uart_processTaskPrio, 5, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +202,7 @@ uart0_rx_intr_handler(void *para)
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR);
|
||||
|
||||
// run handler
|
||||
system_os_post(uart_recvTaskPrio, 0, 0); /* -> notify the polling thread */
|
||||
system_os_post(uart_recvTaskPrio, 0, 1); /* -> notify the polling thread */
|
||||
}
|
||||
|
||||
if (status_reg & UART_RXFIFO_TOUT_INT_ST) {
|
||||
@@ -167,9 +216,14 @@ uart0_rx_intr_handler(void *para)
|
||||
|
||||
if (status_reg & UART_TXFIFO_EMPTY_INT_ST) {
|
||||
CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
|
||||
UART_DispatchFromTxBuffer(UART0);
|
||||
// WRITE_PERI_REG(UART_INT_CLR(UART0), UART_TXFIFO_EMPTY_INT_CLR); //- is called by the dispatch func if more data is to be sent.
|
||||
}
|
||||
|
||||
if (status_reg & UART_RXFIFO_OVF_INT_ST) {
|
||||
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_OVF_INT_CLR);
|
||||
|
||||
// overflow error
|
||||
UART_WriteChar(UART1, '!', 100);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-2
@@ -21,7 +21,22 @@ void UART_SetupAsyncReceiver(void);
|
||||
/** User must provide this func for handling received bytes */
|
||||
extern void UART_HandleRxByte(char c);
|
||||
|
||||
/** Poll uart manually while waiting for something */
|
||||
void UART_PollRx(void);
|
||||
static inline void uart_rx_intr_disable(uint8 uart_no)
|
||||
{
|
||||
CLEAR_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
|
||||
}
|
||||
|
||||
static inline void uart_rx_intr_enable(uint8 uart_no)
|
||||
{
|
||||
SET_PERI_REG_MASK(UART_INT_ENA(uart_no), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief get number of bytes in UART tx fifo
|
||||
* @param UART number
|
||||
*/
|
||||
static inline u8 UART_GetRxFifoCount(u8 uart_no) {
|
||||
return (u8) ((READ_PERI_REG(UART_STATUS((uart_no))) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+3
-1
@@ -23,7 +23,7 @@
|
||||
#include "io.h"
|
||||
#include "screen.h"
|
||||
#include "routes.h"
|
||||
#include "user_main.h"
|
||||
#include "version.h"
|
||||
#include "uart_driver.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "wifimgr.h"
|
||||
@@ -113,10 +113,12 @@ void ICACHE_FLASH_ATTR user_init(void)
|
||||
espFsInit((void *) (webpages_espfs_start));
|
||||
#endif
|
||||
|
||||
#if DEBUG_HEAP
|
||||
// Heap use timer & blink
|
||||
os_timer_disarm(&prHeapTimer);
|
||||
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL);
|
||||
os_timer_arm(&prHeapTimer, 1000, 1);
|
||||
#endif
|
||||
|
||||
// do later (some functions do not work if called from user_init)
|
||||
os_timer_disarm(&userStartTimer);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#ifndef USER_MAIN_H_H
|
||||
#define USER_MAIN_H_H
|
||||
|
||||
#define FIRMWARE_VERSION "0.6.1+" GIT_HASH
|
||||
#define TERMINAL_GITHUB_REPO "https://github.com/MightyPork/esp-vt100-firmware"
|
||||
|
||||
#endif //USER_MAIN_H_H
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/08/20.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_VERSION_H
|
||||
#define ESP_VT100_FIRMWARE_VERSION_H
|
||||
|
||||
#define FW_V_MAJOR 0
|
||||
#define FW_V_MINOR 7
|
||||
#define FW_V_PATCH 1
|
||||
|
||||
#define FIRMWARE_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) "+" GIT_HASH
|
||||
#define FIRMWARE_VERSION_NUM (FW_V_MAJOR*10000 + FW_V_MINOR*100 + FW_V_PATCH) // this is used in ID queries
|
||||
#define TERMINAL_GITHUB_REPO "https://github.com/MightyPork/ESPTerm"
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_VERSION_H
|
||||
+2
-5
@@ -43,11 +43,6 @@ wifimgr_restore_defaults(void)
|
||||
IP4_ADDR(&wificonf->sta_addr.ip, 192, 168, 0, (mac[5] == 1 ? 2 : mac[5])); // avoid being the same as "default gw"
|
||||
IP4_ADDR(&wificonf->sta_addr.netmask, 255, 255, 255, 0);
|
||||
IP4_ADDR(&wificonf->sta_addr.gw, 192, 168, 0, 1);
|
||||
|
||||
// DEBUG ONLY - TODO remove for release
|
||||
// wificonf->opmode = STATION_MODE;
|
||||
// sprintf((char*)wificonf->sta_ssid, "Chlivek");
|
||||
// sprintf((char*)wificonf->sta_password, "prase chrochta");
|
||||
}
|
||||
|
||||
static void ICACHE_FLASH_ATTR
|
||||
@@ -161,6 +156,8 @@ wifimgr_apply_settings(void)
|
||||
{
|
||||
info("[WiFi] Initializing...");
|
||||
|
||||
// !!! Update to current version !!!
|
||||
|
||||
// Force wifi cycle
|
||||
// Disconnect - may not be needed?
|
||||
WIFI_MODE opmode = wifi_get_opmode();
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
// Must be constant to avoid corrupting user config after upgrade
|
||||
#define WIFICONF_SIZE 340
|
||||
|
||||
#define WIFICONF_VERSION 0
|
||||
|
||||
/**
|
||||
* A structure holding all configured WiFi parameters
|
||||
* and the active state.
|
||||
@@ -42,6 +44,7 @@ typedef struct {
|
||||
u8 sta_password[PASSWORD_LEN];
|
||||
bool sta_dhcp_enable;
|
||||
struct ip_info sta_addr;
|
||||
u8 config_version;
|
||||
} WiFiConfigBundle;
|
||||
|
||||
typedef struct {
|
||||
|
||||
Reference in New Issue
Block a user