Compare commits

...
85 Commits
Author SHA1 Message Date
MightyPork fe699c7918 Temporarily fixed #71 by fixed cell width, rel 7.0 preparations 2017-09-03 23:06:55 +02:00
MightyPork 4edaa0687d Implemented heartbeat 2017-09-03 22:37:54 +02:00
MightyPork f8ebae044a Fixed #63 2017-09-03 22:25:02 +02:00
MightyPork 9e9539472c renamed the project in the cmakelists clion file 2017-09-03 22:05:48 +02:00
MightyPork b57e0ee75b change GitHub URL 2017-09-03 22:02:02 +02:00
MightyPork 65e34a1634 Updated help page 2017-09-03 21:54:37 +02:00
MightyPork 6f32a6ef1a fix messed up insert/delete with region 2017-09-02 01:04:30 +02:00
MightyPork d3468a109c fix bug with hanging flag left set after GoTo col 80 2017-09-02 00:40:08 +02:00
MightyPorkandGitHub 8f82b81434 Update README.md 2017-08-25 15:19:26 +02:00
MightyPorkandGitHub 653dc55d85 Update README.md 2017-08-25 15:18:55 +02:00
MightyPork dda469ff21 Updated the xterm comparison page 2017-08-25 15:07:16 +02:00
MightyPork 3515e178cb Updated readme 2017-08-25 15:06:54 +02:00
MightyPork 5fd5c92390 bumped patch number 2017-08-25 12:47:41 +02:00
MightyPork e3ae34a932 fix broken cursor save / restore 2017-08-25 12:47:21 +02:00
MightyPork 423022b8bc Fixed the DECCOLM bug (not clearing screen etc) and a scrolling up not erasing freed lines bug 2017-08-25 12:26:18 +02:00
MightyPork fce3995800 changed mkfile attrs 2017-08-25 01:40:15 +02:00
MightyPork 67625a9317 bumped version for a new pre release for testing 2017-08-25 01:12:30 +02:00
MightyPork 35e1a788f0 Merge branch 'scrolling-region' 2017-08-25 01:08:43 +02:00
MightyPork dd68c112b0 implemented scrolling region and origin mode 2017-08-25 01:08:36 +02:00
MightyPork 2c7c496862 Merge branch 'throttle-redraw' 2017-08-24 23:08:17 +02:00
MightyPork 73c943bb97 throttle screen redraw via cooldown, setting 10/30 largely eliminates dropped bytes 2017-08-24 23:08:09 +02:00
MightyPork beda5e67c0 Merge branch 'async_uart' 2017-08-24 22:17:54 +02:00
MightyPork 357c81ba07 added async buffers for uart0, maybe improved byte drop ratio 2017-08-24 22:12:59 +02:00
MightyPork acd8801d33 W.I.P. new async Rx Tx buffer, probably not working 2017-08-24 00:47:14 +02:00
MightyPork 5c57ea3b28 Logging is now toggleable and updates are postponed if socket is busy 2017-08-22 23:05:21 +02:00
MightyPork 0e73f57da1 missing paren 2017-08-21 22:54:20 +02:00
MightyPork 1f18c7d1f6 moved the styles to the top 2017-08-21 22:47:54 +02:00
MightyPork 2a76eaee8e removed css files for doc and inlined them 2017-08-21 22:45:26 +02:00
MightyPork 3b8cfd41e3 small fixes & added doc directory 2017-08-21 22:39:59 +02:00
MightyPork 09cc66c75c fixed broken character sets & added character set 1 - augmented DOS codepage 437 2017-08-21 01:35:28 +02:00
MightyPork 7947b3a88c tab bug fixed 2017-08-20 22:44:56 +02:00
MightyPork 3355295aa3 CSI split to subroutines 2017-08-20 22:25:42 +02:00
MightyPork 87e0030be8 lowered fifo full threshold, seems the byte drop bug has improved 2017-08-20 20:51:42 +02:00
MightyPork 6d9d068268 split parser handlers file to multiple 2017-08-20 20:41:56 +02:00
MightyPork 276af04945 implemented CSI ! p as an alias to ESC c 2017-08-20 14:57:47 +02:00
MightyPork 8d9c5bb246 DECREPTPARM is now ID'd as Response to Query explicitly 2017-08-20 14:50:43 +02:00
MightyPork 59f26adf68 Stubs for scrolling region and other functions, implemented inverse video, improved parse (hardeing against xterm travesty), optimize tabs, DECREPTPARM 2017-08-20 14:42:38 +02:00
MightyPork 5995988a9b BEEEEEEEEEEEEEEEEEP 2017-08-20 00:28:31 +02:00
MightyPork fe714bf473 Parser cleanup; implemented #67, support for DCS queries, added doc comments, SGR enum 2017-08-19 23:15:15 +02:00
MightyPork 68747d493c Tab Stops™ 2017-08-19 18:58:55 +02:00
MightyPork 184b186b85 some missing sequences 2017-08-19 03:06:55 +02:00
MightyPork af47249492 NLM 2017-08-19 03:02:23 +02:00
MightyPork e22ae1e66a Buffered Logging + fix bel osc bug 2017-08-19 02:55:01 +02:00
MightyPork bf1507b624 ignore cursor chars inside escape seqs + can, sub terminate, esc start new 2017-08-19 01:35:30 +02:00
MightyPork b5378a6626 config toggle for xterm fn keys and display timeout added 2017-08-18 14:52:51 +02:00
MightyPork 9cac029e58 implemented numpad alt mode, fn keys alt mode and cursor alt mode 2017-08-18 14:19:54 +02:00
MightyPork 8117229926 moving some stuff around, removed "soft reset" seq, added server-side support for control key remaps 2017-08-18 00:38:46 +02:00
MightyPork 04b75b8218 xenl 2017-08-17 01:38:25 +02:00
MightyPork 89fca5c40a insert mode 2017-08-17 00:51:28 +02:00
MightyPork ca410736f7 removed some logging and version bump 2017-08-15 03:14:07 +02:00
MightyPork 2ca8bf1483 workaround for buggy ets_timer_disarm causing bootloop in some devices with perser tiemout > 0 2017-08-15 02:29:16 +02:00
MightyPork 35393827a9 implemented ctrl-, f- and other keys. enter now sends CR, use ctrl-enter for LF. 2017-08-15 01:04:58 +02:00
MightyPork ea84d1889d 0.6.6 2017-08-14 01:44:02 +02:00
MightyPork bc41a21f23 some more missing commands, added code pages, fixed the xterm size command and some other stuff. also FRAKTUR 2017-08-14 01:40:40 +02:00
MightyPork e48180ac10 more commands and glitches fixed, better debug messages, discard bad unicode 2017-08-13 17:30:54 +02:00
MightyPork e47a5477f9 fixed swapped 0 and 1 in clearing commands 2017-08-12 23:01:51 +02:00
MightyPork e944a994a7 version bump 2017-08-11 12:16:41 +02:00
MightyPork 6535b49551 implemented Inser and Delete lines/characters (e[ Pn L,M,@,P) 2017-08-11 11:59:50 +02:00
MightyPork 2cb8586c94 preparation for insert and delete commands 2017-08-10 01:58:10 +02:00
MightyPork ec1cebdd4c bugfixes 2017-08-10 01:52:48 +02:00
MightyPork af5a3a64ed fix bug and undo regression with cursor restore 2017-08-09 20:45:22 +02:00
MightyPork 7b94eef448 implemented a couple new ESC codes and stubs for 4 others 2017-08-09 20:41:47 +02:00
MightyPork c8067122d5 parser cleanup for adding new hash and short ESC sequences (issue #41) - EDM tbd 2017-08-07 00:48:15 +02:00
MightyPork 12385b7a5f dont clear terminal screen when not needed. also allow setting term title or buttons to empty string 2017-08-06 19:50:18 +02:00
MightyPork 5564147f67 version bump 2017-08-06 19:08:12 +02:00
MightyPork 32ba7dc0d7 fixed a lot of bugs with wifi connecting 2017-08-06 19:07:43 +02:00
MightyPork 80721c1715 allow submitting empty get args - may solve some bugs with wifi config 2017-08-06 17:56:27 +02:00
MightyPork e22d3f26d9 Added Wifi conf link to the bottom navbar, auto-expand Station config box 2017-08-06 17:53:21 +02:00
MightyPork 24a6a0dfd3 added git hash to version string 2017-08-06 14:58:46 +02:00
MightyPork 8a182f072d removed mentions of " ' > < from help, as those characters are now allowed and properly escaped everywhere 2017-08-06 14:26:54 +02:00
MightyPork 76b447d03e throttle reload nad use textarea for mobile input bc it shows better symbol for the enter button 2017-08-06 14:20:34 +02:00
MightyPork 319427ba2e the onscreen android keyboard now works 2017-08-06 14:17:05 +02:00
MightyPork f8c60b0668 parser timeout, better checksum checking and struct stuffing, shaved some bytes off RAM 2017-08-06 11:22:45 +02:00
MightyPork 4eaee9389d use html and js escapes for tpl strings 2017-08-06 02:15:31 +02:00
MightyPork 0a731bd35e fix some bugs 2017-08-06 00:24:16 +02:00
MightyPork 0b3cfe0e74 use 0x01 for seps in labels string so users can use pipe if they want to 2017-08-05 23:56:35 +02:00
MightyPork 4551ad6dbe added info about the new commands to help. also e]FRa is removed bc it is no longer useful for anything 2017-08-05 23:46:11 +02:00
MightyPork 3f4b8a94f1 commands to set button or title 2017-08-05 21:40:25 +02:00
MightyPork 373affb582 parser formatting to look lkike a huge comment to C editors 2017-08-05 18:57:57 +02:00
MightyPork b48bb12067 help page update 2017-08-05 18:20:13 +02:00
MightyPork 110f461cf5 bold works !! 2017-07-31 02:59:13 +02:00
MightyPork 3ae1451821 working unicode!!! WTF!! and ajax initial load to avoid having to escape shit 2017-07-31 02:41:35 +02:00
MightyPork d711909812 w.i.p. Terminal Themes added, improved terminal color picker, new compression in front-end 2017-07-31 00:39:46 +02:00
MightyPork 76167b8014 WiFi sections hiding, STA auto enable on scan click, Terminal colors preview, better explanations, CONFIGURABLE UART 2017-07-29 20:55:16 +02:00
MightyPork e42407ad62 fontello unpack script & improved checkbox style 2017-07-29 12:33:55 +02:00
111 changed files with 11992 additions and 2939 deletions
+29 -9
View File
@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.7)
# Do not attempt to build it with cmake, use make instead. # # Do not attempt to build it with cmake, use make instead. #
################################################################### ###################################################################
project(esp_vt100_firmware) project(ESPTerm)
set(CMAKE_CXX_STANDARD GNU99) set(CMAKE_CXX_STANDARD GNU99)
@@ -86,7 +86,6 @@ set(SOURCE_FILES
esp_iot_sdk_v1.5.2/include/json/jsonparse.h esp_iot_sdk_v1.5.2/include/json/jsonparse.h
esp_iot_sdk_v1.5.2/include/json/json.h esp_iot_sdk_v1.5.2/include/json/json.h
include/uart_hw.h
include/user_config.h include/user_config.h
include/ets_sys_extra.h include/ets_sys_extra.h
user/io.c user/io.c
@@ -97,10 +96,9 @@ set(SOURCE_FILES
user/cgi_persist.h user/cgi_persist.h
user/cgi_network.c user/cgi_network.c
user/cgi_network.h user/cgi_network.h
user/cgi_appcfg.c user/cgi_term_cfg.c
user/cgi_appcfg.h user/cgi_term_cfg.h
user/cgi_ping.c user/cgi_system.c
user/cgi_reset.c
user/uart_driver.c user/uart_driver.c
user/uart_handler.c user/uart_handler.c
user/ansi_parser.c user/ansi_parser.c
@@ -112,6 +110,8 @@ set(SOURCE_FILES
user/serial.h user/serial.h
user/routes.c user/routes.c
user/routes.h user/routes.h
user/uart_asyncdrv.c-
user/uart_asyncdrv.h-
user/cgi_main.c user/cgi_main.c
user/cgi_main.h user/cgi_main.h
user/cgi_sockets.c user/cgi_sockets.c
@@ -119,11 +119,29 @@ set(SOURCE_FILES
user/cgi_sockets.h user/cgi_sockets.h
user/ansi_parser_callbacks.c user/ansi_parser_callbacks.c
user/ansi_parser_callbacks.h user/ansi_parser_callbacks.h
user/user_main.h
user/wifimgr.c user/wifimgr.c
user/wifimgr.h user/wifimgr.h
user/persist.c user/persist.c
user/persist.h include/helpers.h) user/persist.h
include/helpers.h
user/syscfg.c
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)
include_directories(include) include_directories(include)
include_directories(user) include_directories(user)
@@ -143,6 +161,8 @@ add_definitions(
-DICACHE_RODATA_ATTR= -DICACHE_RODATA_ATTR=
-DFLAG_GZIP=2 -DFLAG_GZIP=2
-DADMIN_PASSWORD="asdf" -DADMIN_PASSWORD="asdf"
-DGIT_HASH="blabla"
-DDEBUG_HEAP=1
-DESPFS_HEATSHRINK) -DESPFS_HEATSHRINK)
add_executable(esp_vt100_firmware ${SOURCE_FILES}) add_executable(ESPTerm ${SOURCE_FILES})
+9 -2
View File
@@ -65,7 +65,14 @@ LIBS += esphttpd
# compiler flags using during compilation of source files # compiler flags using during compilation of source files
CFLAGS = -Os -ggdb -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ CFLAGS = -Os -ggdb -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
-nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \ -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \
-Wno-address -Wno-unused -DHTTPD_MAX_BACKLOG_SIZE=8192 -DADMIN_PASSWORD=$(ADMIN_PASSWORD) -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 # linker flags used to generate the main object file
LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static
@@ -196,7 +203,7 @@ libesphttpd/Makefile:
$(Q) [[ -e "libesphttpd/Makefile" ]] || echo -e "\e[31mlibesphttpd submodule missing.\nIf build fails, run \"git submodule init\" and \"git submodule update\".\e[0m" $(Q) [[ -e "libesphttpd/Makefile" ]] || echo -e "\e[31mlibesphttpd submodule missing.\nIf build fails, run \"git submodule init\" and \"git submodule update\".\e[0m"
libesphttpd: libesphttpd/Makefile libesphttpd: libesphttpd/Makefile
$(Q) make -C libesphttpd USE_OPENSDK=$(USE_OPENSDK) $(Q) make -C libesphttpd USE_OPENSDK=$(USE_OPENSDK) SERVERNAME_PREFIX="ESPTerm "
$(APP_AR): libesphttpd $(OBJ) $(APP_AR): libesphttpd $(OBJ)
$(vecho) "AR $@" $(vecho) "AR $@"
+64 -38
View File
@@ -1,76 +1,100 @@
# ESPTerm # 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 ESPTerm can add remote access via WiFi to any embeded project, all you need is a serial port.
[MightyPork/esphttpd](https://github.com/MightyPork/esphttpd) and
[MightyPork/libesphttpd](https://github.com/MightyPork/libesphttpd) respectively.
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 ## ESPTerm features
Arduino, AVR, PIC, STM8, STM32, mbed etc, anything with UART, even your USB-serial dongle will work.
Connect it to the master device via UART and use the terminal on the built-in web page for debug logging, - **Robust WiFi configuration interface**
remote control etc. It works like a simple LCD screen, in a way. - 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 \*) Linux console applications run via agetty at ttyUSB0 were used for testing, obviously you'll be better off using SSH for remote shell
the webpage (and keyboard on PC). There is some rudimentary touch input as well.
The screen size is adjustable up to 25x80 (via a special control sequence) and uses 16 standard colors ESPTerm firmware is written in C and is based on SpriteTM's `libesphttpd` http server library, forked by MightyPork to
(8 dark and 8 bright). Both default size and colors can be configured in the settings. [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. ### Pins
This means that what you type in the browser is sent to UART0 and what's received on UART0 is processed by the - Pin GPIO2 is used for debug messages at 115200 baud, 8 bit, no parity.
ANSI parser and applied to the internal screen buffer. You'll also immediately see the changes in your browser. - Pins Rx and Tx are used for the main communication UART. Connect your USB-serial dongle or application microcontroller here.
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. ### Console commands - escape sequences
*NOTE: Use the bare module, not something like LoLin or NodeMCU with a FTDI, it'll interfere*.
You should then directly see what you type & can even try some ANSI sequences, right from the browser.
- All ANSI sequences that make sense, as well as control codes like Backspace and CR / LF are implemented. - A list of most supported commands can be found here: http://bits.ondrovo.com/espterm-xterm.html
Set colors with your usual `\e[31;1m` etc (see Wikipedia). `\e` is the ASCII code 27 (ESC). - 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
Arrow keys generate ANSI sequences, ESC sends literal ASCII code 27 etc. Almost everything can be input ### Setup
straight from the browser.
- Buttons pressed in the browser UI send ASCII codes 1..5. Mouse clicks are sent as `\e[<row>;<col>M`. - 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.
- There's a built-in WiFi config page and a Help page with a list of all supported ANSI sequences and other details. ### 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 ## Development
### Installation for development ### Installation for development
- Clone this project with `--recursive`, or afterwards run `git submodule init` and `git submodule update`. - Clone this project with `--recursive`, or afterwards run `git submodule init` and `git submodule update`.
- Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/) and build it with - Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/) and build it with
`make toolchain esptool libhal STANDALONE=n`. `make toolchain esptool libhal STANDALONE=n`.
Make sure the `xtensa-lx106-elf/bin` folder is on $PATH. 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) - Install [esptool](https://github.com/espressif/esptool) (it's in the Arch community repo and on AUR, too)
- Set up udev rules so you have access to ttyUSB0 without root, eg: - Set up udev rules so you have access to ttyUSB0 without root, eg:
``` ```
KERNEL=="tty[A-Z]*[0-9]*", GROUP="uucp", MODE="0666" KERNEL=="tty[A-Z]*[0-9]*", GROUP="uucp", MODE="0666"
``` ```
- Install Ragel if you wish to make modifications to the ANSI sequence parser. - 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. 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 - Install Ruby and then the `sass` package with `gem install sass` (or try some other implementation, such as
`sassc`) `sassc`)
- Make sure your `esphttpdconfig.mk` is set up properly - link to the SDK etc. - 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. 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 ### Web resources
The web resources are in `html_orig`. To prepare for a build, run `build_web.sh`, which packs them and 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 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. 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. build scripts manually, too.
To flash, just run `make flash`. 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
+12
View File
@@ -40,3 +40,15 @@ ESP_SPI_FLASH_SIZE_K = 1024
# Admin password, used to store settings to flash as defaults # Admin password, used to store settings to flash as defaults
ADMIN_PASSWORD = "19738426" 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 \
-DDEBUG_INPUT=0 \
-DDEBUG_HEAP=1
+54 -46
View File
@@ -3,60 +3,68 @@
/** /**
* Those replacements are done by the development server to test it locally * Those replacements are done by the development server to test it locally
* without esphttpd. This is needed mainly for places where the replacements * without esphttpd. This is needed mainly for places where the replacements
* are given to JavaScript, to avoid syntax errors with %% * are given to JavaScript, to avoid syntax errors with
*/ */
return [ return [
'%term_title%' => 'ESP8266 Wireless Terminal', 'term_title' => 'ESPTerm local debug',
'%btn1%' => '1', 'btn1' => '1',
'%btn2%' => '2', 'btn2' => '2',
'%btn3%' => '3', 'btn3' => '3',
'%btn4%' => '4', 'btn4' => '',
'%btn5%' => '5', 'btn5' => '5',
'labels_seq' => 'TESPTerm local debug1235',
'%screenData%' => '{ 'screenData' => '  HELLOx NRE3',//'\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": 0, "y": 0,
"cv": 1,
"screen": "70 t259"
}',
'%ap_enable%' => '1', 'parser_tout_ms' => 10,
'%tpw%' => '60', 'display_tout_ms' => 20,
'%ap_channel%' => '7', 'fn_alt_mode' => '1',
'%ap_ssid%' => 'ESP-123456',
'%ap_password%' => 'Passw0rd!',
'%ap_hidden%' => '0',
'%sta_ssid%' => 'Chlivek',
'%sta_password%' => 'windows XP is The Best',
'%sta_active_ip%' => '1.2.3.4',
'%sta_active_ssid%' => 'Chlivek',
'%sta_enable%' => '1', 'opmode' => '2',
'%opmode%' => '3', 'sta_enable' => '0',
'%vers_fw%' => '1.2.3', 'ap_enable' => '1',
'%date%' => date('Y-m-d'),
'%time%' => date('G:i'),
'%vers_httpd%' => '4.5.6',
'%vers_sdk%' => '1.52',
'%githubrepo%' => 'https://github.com/MightyPork/esp-vt100-firmware',
'%ap_dhcp_time%' => '120', 'tpw' => '60',
'%ap_dhcp_start%' => '192.168.4.100', 'ap_channel' => '7',
'%ap_dhcp_end%' => '192.168.4.200', 'ap_ssid' => 'ESP-123456',
'%ap_addr_ip%' => '192.168.4.1', 'ap_password' => 'Passw0rd!',
'%ap_addr_mask%' => '255.255.255.0', 'ap_hidden' => '0',
'%sta_dhcp_enable%' => '1', 'sta_ssid' => 'Chlivek',
'%sta_addr_ip%' => '192.168.0.33', 'sta_password' => 'windows XP is The Best',
'%sta_addr_mask%' => '255.255.255.0', 'sta_active_ip' => '1.2.3.4',
'%sta_addr_gw%' => '192.168.0.1', 'sta_active_ssid' => 'Chlivek',
'%sta_mac%' => 'ab:cd:ef:01:23:45', 'vers_fw' => '1.2.3',
'%ap_mac%' => '01:23:45:ab:cd:ef', 'date' => date('Y-m-d'),
'time' => date('G:i'),
'vers_httpd' => '4.5.6',
'vers_sdk' => '1.52',
'githubrepo' => 'https://github.com/MightyPork/esp-vt100-firmware',
'%term_width%' => '26', 'ap_dhcp_time' => '120',
'%term_height%' => '10', 'ap_dhcp_start' => '192.168.4.100',
'%default_bg%' => '0', 'ap_dhcp_end' => '192.168.4.200',
'%default_fg%' => '7', 'ap_addr_ip' => '192.168.4.1',
'ap_addr_mask' => '255.255.255.0',
'sta_dhcp_enable' => '1',
'sta_addr_ip' => '192.168.0.33',
'sta_addr_mask' => '255.255.255.0',
'sta_addr_gw' => '192.168.0.1',
'sta_mac' => 'ab:cd:ef:01:23:45',
'ap_mac' => '01:23:45:ab:cd:ef',
'term_width' => '26',
'term_height' => '10',
'default_bg' => '0',
'default_fg' => '7',
'uart_baud' => 115200,
'uart_stopbits' => 1,
'uart_parity' => 2,
'theme' => 5,
]; ];
+8 -6
View File
@@ -18,6 +18,9 @@ if (! function_exists('pg')) {
} }
} }
pg('cfg_term', 'cfg', 'terminal', '/cfg/term');
pg('term_set', 'api', '', '/cfg/term/set');
pg('cfg_wifi', 'cfg', 'wifi', '/cfg/wifi'); pg('cfg_wifi', 'cfg', 'wifi', '/cfg/wifi');
pg('cfg_wifi_conn', '', '', '/cfg/wifi/connecting'); pg('cfg_wifi_conn', '', '', '/cfg/wifi/connecting');
pg('wifi_connstatus', 'api', '', '/cfg/wifi/connstatus'); pg('wifi_connstatus', 'api', '', '/cfg/wifi/connstatus');
@@ -27,13 +30,12 @@ pg('wifi_scan', 'api', '', '/cfg/wifi/scan');
pg('cfg_network', 'cfg', 'network', '/cfg/network'); pg('cfg_network', 'cfg', 'network', '/cfg/network');
pg('network_set', 'api', '', '/cfg/network/set'); pg('network_set', 'api', '', '/cfg/network/set');
pg('cfg_app', 'cfg', 'terminal', '/cfg/app'); pg('cfg_system', 'cfg', 'configure', '/cfg/system');
pg('app_set', 'api', '', '/cfg/app/set'); pg('system_set', 'api', '', '/cfg/system/set');
pg('cfg_admin', 'cfg', 'persist', '/cfg/admin'); pg('write_defaults', 'api', '', '/cfg/system/write_defaults');
pg('write_defaults', 'api', '', '/cfg/admin/write_defaults'); pg('restore_defaults', 'api', '', '/cfg/system/restore_defaults');
pg('restore_defaults', 'api', '', '/cfg/admin/restore_defaults'); pg('restore_hard', 'api', '', '/cfg/system/restore_hard');
pg('restore_hard', 'api', '', '/cfg/admin/restore_hard');
pg('help', 'cfg page-help', 'help', '/help'); pg('help', 'cfg page-help', 'help', '/help');
pg('about', 'cfg page-about', 'about', '/about'); pg('about', 'cfg page-about', 'about', '/about');
+576 -187
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -7,6 +7,7 @@ require_once __DIR__ . '/base.php';
$selected = [ $selected = [
'wifi.connected_ip_is', 'wifi.connected_ip_is',
'wifi.not_conn', 'wifi.not_conn',
'wifi.enter_passwd',
]; ];
$out = []; $out = [];
+1
View File
@@ -0,0 +1 @@
/out/
Binary file not shown.
Binary file not shown.
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
# resolve current file's directory
DIR=$(dirname $(realpath $0))
OUTPUT_DIR="$DIR/out"
SASS_DIR="$DIR/../sass"
ICON_PREFIX='icn'
# list with full paths, sort from newest
NEWEST=$(ls -dt1 "$DIR"/*.zip | head -1)
if [[ -z "$NEWEST" ]]; then
echo "Fontello zip not found."
exit 1
fi
# Clean the output folder
rm -rf "$OUTPUT_DIR"
mkdir -p "$OUTPUT_DIR"
echo "Unpacking fontello..."
unzip -ju "$NEWEST" -d "$OUTPUT_DIR"
echo "Patching paths in the fontello CSS..."
# Fix bad relative paths in the CSS
sed -i "s|\.\./font/|/fonts/|g" "$OUTPUT_DIR/"*.css
echo "Generating SASS file with icon codes..."
SASSFILE="$SASS_DIR/_fontello.scss"
echo -e "@charset \"UTF-8\";\n\n/* Fontello data, processed by the unpack script. */\n" > "$SASSFILE"
# Extract the base font-face style
#grep -Pazo "(?s)@font-face.*?normal;\n\}" "$OUTPUT_DIR/fontello.css" \
# | sed 's/\x0//g' >> "$SASSFILE"
grep -Pazo "(?s)@font-face \{\n\s*font-family: 'fontello';\n\s*src: url\('data.*?truetype'\);\n\}" "$OUTPUT_DIR/fontello-embedded.css" \
| sed 's/\x0//g' >> "$SASSFILE"
grep -Pazo "(?s)$ICON_PREFIX-\"\]:before .*?\}" "$OUTPUT_DIR/fontello.css" \
| sed 's/\x0//g' \
| sed "s/$ICON_PREFIX-\"\]:before/\n\n%fontello-icon-base \{\n\&::before /g" \
>> "$SASSFILE"
echo -e "\n}" >> "$SASSFILE"
echo -e "\n\n/* Fontello icon codes */" >> "$SASSFILE"
echo -n "\$icon-codes: (" >> "$SASSFILE"
sed -r "s|\.$ICON_PREFIX-([a-z0-9-]+):before \{ content: ('.*?');.*?$|\t\1: \2,|g" "$OUTPUT_DIR/fontello-codes.css" \
| sed -r "s|@.*||g" >> "$SASSFILE"
echo -ne "\n);\n" >> "$SASSFILE"
echo -ne "\n/* Fontello classes */" >> "$SASSFILE"
cat "$OUTPUT_DIR/fontello-codes.css" \
| sed -r 's/\/\*.+\*\///g' \
| sed -r "s|@.*||g" \
| sed 's/:before/::before/g' >> "$SASSFILE"
TAIL=$(cat <<ASDF
[class^="$ICON_PREFIX-"], [class*=" $ICON_PREFIX-"] {
@extend %fontello-icon-base;
}
@mixin icon-base() {
@extend %fontello-icon-base;
}
@mixin icon-content(\$icon-name) {
&::before {
content: map-get(\$icon-codes, \$icon-name);
}
}
@mixin icon(\$icon-name) {
@include icon-base();
@include icon-content(\icon-name);
}
ASDF
)
echo "$TAIL" >> "$SASSFILE"
echo -e "\e[32mFontello ready\e[0m"
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+34 -2
View File
@@ -7,12 +7,44 @@ if (!isset($_GET['page'])) $_GET['page'] = 'term';
$_GET['PAGE_TITLE'] = $_pages[$_GET['page']]->title . ' :: ' . APP_NAME; $_GET['PAGE_TITLE'] = $_pages[$_GET['page']]->title . ' :: ' . APP_NAME;
$_GET['BODYCLASS'] = $_pages[$_GET['page']]->bodyclass; $_GET['BODYCLASS'] = $_pages[$_GET['page']]->bodyclass;
if (!function_exists('tplSubs')) {
function tplSubs($str, $reps)
{
return preg_replace_callback('/%(j:|js:|h:|html:)?([a-z0-9-_.]+)%/i', function ($m) use ($reps) {
$key = $m[2];
if (array_key_exists($key, $reps)) {
$val = $reps[$key];
} else {
$val = '';
}
switch ($m[1]) {
case 'j:':
case 'js:':
$v = json_encode($val);
return substr($v, 1, strlen($v) - 2);
case 'h:':
case 'html:':
return htmlspecialchars($val);
default:
return $val;
}
}, $str);
}
}
require __DIR__ . '/pages/_head.php'; require __DIR__ . '/pages/_head.php';
$_pf = __DIR__ . '/pages/'.$_GET['page'].'.php'; $_pf = __DIR__ . '/pages/'.$_GET['page'].'.php';
if (file_exists($_pf)) { if (file_exists($_pf)) {
$f = file_get_contents($_pf); $f = file_get_contents($_pf);
$reps = DEBUG ? require(__DIR__ . '/_debug_replacements.php') : [];
$str = str_replace(array_keys($reps), array_values($reps), $f); if (DEBUG)
$str = tplSubs($f, require(__DIR__ . '/_debug_replacements.php'));
else $str = $f;
// special symbols
$str = str_replace('\,', '&#8239;', $str);
$str = preg_replace('/(?<=\w)~(?=\w)/', '&nbsp;', $str);
include_str($str); include_str($str);
} else { } else {
echo "404"; echo "404";
+971 -309
View File
File diff suppressed because it is too large Load Diff
+81 -2
View File
@@ -16,7 +16,7 @@ $.ready(function () {
}); });
// Expanding boxes on mobile // Expanding boxes on mobile
$('.Box.mobcol').forEach(function(x) { $('.Box.mobcol,.Box.fold').forEach(function(x) {
var h = x.querySelector('h2'); var h = x.querySelector('h2');
var hdl = function() { var hdl = function() {
@@ -100,11 +100,90 @@ $.ready(function () {
x.removeAttribute('tabindex'); x.removeAttribute('tabindex');
}); });
// brand works as a link back to term in widescreen mode
var br = qs('#brand'); var br = qs('#brand');
br && br.removeAttribute('tabindex'); br && br.addEventListener('click', function() {
location.href='/'; // go to terminal
});
} }
}); });
$._loader = function(vis) { $._loader = function(vis) {
$('#loader').toggleClass('show', vis); $('#loader').toggleClass('show', vis);
}; };
function showPage() {
$('#content').addClass('load');
}
$.ready(function() {
if (window.noAutoShow !== true) {
setTimeout(function () {
showPage();
}, 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;
}
}());
}
+1 -1
View File
@@ -488,7 +488,7 @@
// Multiple select // Multiple select
for (j = 0; j < value.length; j += 1) { for (j = 0; j < value.length; j += 1) {
elm[i].selected = ''; elm[i].selected = '';
if (elm[i].value === value[j]) { if (elm[i].value === ""+value[j]) {
elm[i].selected = 'selected'; elm[i].selected = 'selected';
break; break;
} }
+301
View File
@@ -0,0 +1,301 @@
// 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]];
};
// 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.getPressedKeyCodes = getPressedKeyCodes;
global.key.noConflict = noConflict;
global.key.unbind = unbindKey;
if(typeof module !== 'undefined') module.exports = assignKey;
})(this);
+2 -1
View File
@@ -1,7 +1,8 @@
// Generated from PHP locale file // Generated from PHP locale file
var _tr = { var _tr = {
"wifi.connected_ip_is": "Connected, IP is ", "wifi.connected_ip_is": "Connected, IP is ",
"wifi.not_conn": "Not connected." "wifi.not_conn": "Not connected.",
"wifi.enter_passwd": "Enter password for \":ssid:\""
}; };
function tr(key) { return _tr[key] || '?'+key+'?'; } function tr(key) { return _tr[key] || '?'+key+'?'; }
+576 -273
View File
@@ -1,303 +1,606 @@
(function() { var Screen = (function () {
/** var W = 0, H = 0; // dimensions
* Terminal module var inited = false;
*/
var Term = (function () {
var W, H;
var cursor = {a: false, x: 0, y: 0, suppress: false, hidden: false};
var screen = [];
var blinkIval;
/* var cursor = {
/!** Clear screen *!/ a: false, // active (blink state)
function cls() { x: 0, // 0-based coordinates
screen.forEach(function(cell, i) { y: 0,
cell.t = ' '; fg: 7, // colors 0-15
cell.fg = 7; bg: 0,
cell.bg = 0; attrs: 0,
blit(cell); suppress: false, // do not turn on in blink interval (for safe moving)
}); forceOn: false,
} hidden: false, // do not show
*/ hanging: false, // xenl
};
/** Set text and color at XY */ var screen = [];
function cellAt(y, x) { var blinkIval;
return screen[y*W+x]; var cursorFlashStartIval;
var frakturExceptions = {
'C': '\u212d',
'H': '\u210c',
'I': '\u2111',
'R': '\u211c',
'Z': '\u2128',
};
// 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 */
function _curCell() {
return screen[cursor.y*W + cursor.x];
}
/** Safely move cursor */
function cursorSet(y, x) {
// Hide and prevent from showing up during the move
cursor.suppress = true;
_draw(_curCell(), false);
cursor.x = x;
cursor.y = y;
// Show again
cursor.suppress = false;
_draw(_curCell());
}
/** 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;
} }
/** Get cell under cursor */ var elem = cell.e, fg, bg, cn, t;
function cursorCell() { // Colors
return cellAt(cursor.y, cursor.x); fg = inv ? cell.bg : cell.fg;
bg = inv ? cell.fg : cell.bg;
// Update
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;
/!** Enable or disable cursor visibility *!/ }
function cursorEnable(enable) {
cursor.hidden = !enable; /** Show entire screen */
cursor.a &= enable; function _drawAll() {
blit(cursorCell(), cursor.a); for (var i = W*H-1; i>=0; i--) {
_draw(screen[i]);
} }
}
/!** Safely move cursor *!/ function _rebuild(rows, cols) {
function cursorSet(y, x) { W = cols;
// Hide and prevent from showing up during the move H = rows;
cursor.suppress = true;
blit(cursorCell(), false);
cursor.x = x; /* Build screen & show */
cursor.y = y; var e, cell, scr = qs('#screen');
// Show again // Empty the screen node
cursor.suppress = false; while (scr.firstChild) scr.removeChild(scr.firstChild);
blit(cursorCell(), cursor.a);
screen = [];
for(var i = 0; i < W*H; i++) {
e = mk('span');
(function() {
var x = i % W;
var y = Math.floor(i / W);
e.addEventListener('click', function () {
Input.onTap(y, x);
});
})();
/* End of line */
if ((i > 0) && (i % W == 0)) {
scr.appendChild(mk('br'));
}
/* The cell */
scr.appendChild(e);
cell = {
t: ' ',
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),
};
screen.push(cell);
_draw(cell);
} }
*/ }
/** Update cell on display. inv = invert (for cursor) */ /** Init the terminal */
function blit(cell, inv) { function _init() {
var e = cell.e, fg, bg; /* Cursor blinking */
// Colors clearInterval(blinkIval);
fg = inv ? cell.bg : cell.fg; blinkIval = setInterval(function () {
bg = inv ? cell.fg : cell.bg; cursor.a = !cursor.a;
// Update // TODO try to invent a new way to indicate "hanging" - this is copied from gtkterm
e.innerText = (cell.t+' ')[0]; if (cursor.hidden || cursor.hanging) {
e.className = 'fg'+fg+' bg'+bg; cursor.a = false;
}
/** Show entire screen */
function blitAll() {
screen.forEach(function(cell, i) {
/* Invert if under cursor & cursor active */
var inv = cursor.a && (i == cursor.y*W+cursor.x);
blit(cell, inv);
});
}
/** Load screen content from a 'binary' sequence */
function load(obj) {
cursor.x = obj.x;
cursor.y = obj.y;
cursor.hidden = !obj.cv;
// full re-init if size changed
if (obj.w != W || obj.h != H) {
Term.init(obj);
return;
} }
// Simple compression - hexFG hexBG 'ASCII' (r/s/t/u NUM{1,2,3,4})? if (!cursor.suppress) {
// comma instead of both colors = same as before _draw(_curCell(), cursor.forceOn || cursor.a);
}
}, 500);
var i = 0, ci = 0, str = obj.screen; // blink attribute
var fg = 7, bg = 0; setInterval(function () {
while(i < str.length && ci<W*H) { $('#screen').removeClass('blink-hide');
var cell = screen[ci++]; setTimeout(function () {
$('#screen').addClass('blink-hide');
}, 800); // 200 ms ON
}, 1000);
var j = str[i]; inited = true;
if (j != ',') { // comma = repeat last colors }
fg = cell.fg = parseInt(str[i++], 16);
bg = cell.bg = parseInt(str[i++], 16); /** Decode two-byte number */
} else { function parse2B(s, i) {
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;
}
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, attrs, cell;
if (!inited) _init();
var cursorMoved;
// Set size
num = parse2B(str, i); i += 2; // height
num2 = parse2B(str, i); i += 2; // width
if (num != H || num2 != W) {
_rebuild(num, num2);
}
// console.log("Size ",num, num2);
// 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.hidden = !(num & 0x0001);
cursor.hanging = !!(num & 0x0002);
// console.log("Attributes word ",num.toString(16)+'h');
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_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;
}
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;
// console.log("Repeat x ",num);
for (; num>0 && ci<W*H; num--) {
cell = screen[ci++];
cell.fg = fg; cell.fg = fg;
cell.bg = bg; cell.bg = bg;
} cell.t = t;
cell.attrs = attrs;
var t = cell.t = str[i++];
var repchars = 0;
switch(str[i]) {
case 'r': repchars = 1; break;
case 's': repchars = 2; break;
case 't': repchars = 3; break;
case 'u': repchars = 4; break;
default: repchars = 0;
}
if (repchars > 0) {
var rep = parseInt(str.substr(i+1,repchars));
i = i + repchars + 1;
for (; rep>0 && ci<W*H; rep--) {
cell = screen[ci++];
cell.fg = fg;
cell.bg = bg;
cell.t = t;
}
} }
} }
else {
blitAll(); cell = screen[ci++];
} // Unique cell character
t = cell.t = j;
/** Init the terminal */ cell.fg = fg;
function init(obj) { cell.bg = bg;
W = obj.w; cell.attrs = attrs;
H = obj.h; // console.log("Symbol ", j);
/* Build screen & show */
var e, cell, scr = qs('#screen');
// Empty the screen node
while (scr.firstChild) scr.removeChild(scr.firstChild);
screen = [];
for(var i = 0; i < W*H; i++) {
e = mk('span');
(function() {
var x = i % W;
var y = Math.floor(i / W);
e.addEventListener('click', function () {
Input.onTap(y, x);
});
})();
/* End of line */
if ((i > 0) && (i % W == 0)) {
scr.appendChild(mk('br'));
}
/* The cell */
scr.appendChild(e);
cell = {t: ' ', fg: 7, bg: 0, e: e};
screen.push(cell);
blit(cell);
}
/* Cursor blinking */
clearInterval(blinkIval);
blinkIval = setInterval(function() {
cursor.a = !cursor.a;
if (cursor.hidden) {
cursor.a = false;
}
if (!cursor.suppress) {
blit(cursorCell(), cursor.a);
}
}, 500);
load(obj);
}
// publish
return {
init: init,
load: load
};
})();
/** Handle connections */
var Conn = (function() {
var ws;
function onOpen(evt) {
console.log("CONNECTED");
}
function onClose(evt) {
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
setTimeout(function() {
init();
}, 1000);
}
function onMessage(evt) {
try {
console.log("RX: ", evt.data);
// Assume all our messages are screen updates
Term.load(JSON.parse(evt.data));
} catch(e) {
console.error(e);
} }
} }
function doSend(message) { _drawAll();
console.log("TX: ", message);
if (ws.readyState != 1) { // if (!cursor.hidden || cursor.hanging || !cursor.suppress) {
console.error("Socket not ready"); // // hide cursor asap
return; // _draw(_curCell(), false);
} // }
if (typeof message != "string") {
message = JSON.stringify(message); if (cursorMoved) {
} cursor.forceOn = true;
ws.send(message); cursorFlashStartIval = setTimeout(function() {
cursor.forceOn = false;
}, 1200);
_draw(_curCell(), true);
} }
}
function init() { function _load_labels(str) {
ws = new WebSocket("ws://"+_root+"/ws/update.cgi"); var pieces = str.split('\x01');
ws.onopen = onOpen; qs('h1').textContent = pieces[0];
ws.onclose = onClose; qsa('#buttons button').forEach(function(x, i) {
ws.onmessage = onMessage; 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) : "&nbsp;";
x.style.opacity = s.length > 0 ? 1 : 0.2;
});
}
console.log("Opening socket."); 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);
switch(str.charAt(0)) {
case 'S':
_load_content(content);
break;
case 'T':
_load_labels(content);
break;
case 'B':
_beep();
break;
default:
console.warn("Bad data message type, ignoring.");
} }
}
return { return {
ws: null, load: load, // full load (string)
init: init,
send: doSend
};
})();
//
// Keyboard (& mouse) input
//
var Input = (function() {
function sendStrMsg(str) {
Conn.send("STR:"+str);
}
function sendPosMsg(y, x) {
Conn.send("TAP:"+y+','+x);
}
function sendBtnMsg(n) {
Conn.send("BTN:"+n);
}
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 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;
}
});
qsa('#buttons button').forEach(function(s) {
s.addEventListener('click', function() {
sendBtnMsg(+this.dataset['n']);
});
});
}
return {
init: init,
onTap: sendPosMsg
};
})();
window.termInit = function (obj) {
Term.init(obj);
Conn.init();
Input.init();
}; };
})(); })();
/** Handle connections */
var Conn = (function() {
var ws;
var heartbeatTout;
function onOpen(evt) {
console.log("CONNECTED");
}
function onClose(evt) {
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
setTimeout(function() {
init();
}, 200);
// this happens when the buffer gets fucked up via invalid unicode.
// we basically use polling instead of socket then
}
function onMessage(evt) {
try {
// . = 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);
}
}
function doSend(message) {
console.log("TX: ", message);
if (!ws) return; // for dry testing
if (ws.readyState != 1) {
console.error("Socket not ready");
return;
}
if (typeof message != "string") {
message = JSON.stringify(message);
}
ws.send(message);
}
function init() {
heartbeat();
ws = new WebSocket("ws://"+_root+"/term/update.ws");
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
console.log("Opening socket.");
// Ask for initial data
$.get('http://'+_root+'/term/init', function(resp, status) {
if (status !== 200) location.reload(true);
console.log("Data received!");
Screen.load(resp);
heartbeat();
showPage();
});
}
function heartbeat() {
clearTimeout(heartbeatTout);
heartbeatTout = setTimeout(heartbeatFail, 3000);
}
function heartbeatFail() {
console.error("Heartbeat lost, reloading...");
location.reload();
}
return {
ws: null,
init: init,
send: doSend
};
})();
/** User input */
var Input = (function() {
var opts = {
np_alt: false,
cu_alt: false,
fn_alt: false,
};
function sendStrMsg(str) {
Conn.send("STR:"+str);
}
function sendPosMsg(y, x) {
Conn.send("TAP:"+y+','+x);
}
function sendBtnMsg(n) {
Conn.send("BTN:"+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();
}
function init() {
_initKeys();
// Button presses
qsa('#buttons button').forEach(function(s) {
s.addEventListener('click', function() {
sendBtnMsg(+this.dataset['n']);
});
});
}
return {
init: init,
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();
}
},
};
})();
window.termInit = function () {
Conn.init();
Input.init();
};
+10 -32
View File
@@ -23,21 +23,6 @@
$('#sta-nw .ip').html(ip.length>0 ? tr('wifi.connected_ip_is')+ip : tr('wifi.not_conn')); $('#sta-nw .ip').html(ip.length>0 ? tr('wifi.connected_ip_is')+ip : tr('wifi.not_conn'));
} }
function submitPskModal(e, open) {
var passwd = $('#conn-passwd').val();
var ssid = $('#conn-ssid').val();
if (open || passwd.length) {
$('#sta_password').val(passwd);
$('#sta_ssid').val(ssid);
selectSta(ssid, passwd, '');
}
if (e) e.preventDefault();
Modal.hide('#psk-modal');
return false;
}
/** Update display for received response */ /** Update display for received response */
function onScan(resp, status) { function onScan(resp, status) {
//var ap_json = { //var ap_json = {
@@ -83,7 +68,6 @@
ap.enc = parseInt(ap.enc); ap.enc = parseInt(ap.enc);
if (ap.enc > 4) return; // hide unsupported auths if (ap.enc > 4) return; // hide unsupported auths
WiFi.scan_url = '/cfg/wifi/scan';
var item = mk('div'); var item = mk('div');
@@ -107,19 +91,18 @@
$item.on('click', function () { $item.on('click', function () {
var $th = $(this); var $th = $(this);
var ssid = $th.data('ssid'); var conn_ssid = $th.data('ssid');
var conn_pass = '';
$('#conn-ssid').val(ssid);
$('#conn-passwd').val('');
if (+$th.data('pwd')) { if (+$th.data('pwd')) {
// this AP needs a password // this AP needs a password
Modal.show('#psk-modal'); conn_pass = prompt(tr("wifi.enter_passwd").replace(":ssid:", conn_ssid));
$('#conn-passwd')[0].focus(); if (!conn_pass) return;
} else {
//Modal.show('#reset-modal');
submitPskModal(null, true);
} }
$('#sta_password').val(conn_pass);
$('#sta_ssid').val(conn_ssid);
selectSta(conn_ssid, conn_pass, '');
}); });
@@ -132,12 +115,13 @@
$('#ap-loader').removeClass('hidden'); $('#ap-loader').removeClass('hidden');
$('#ap-scan').addClass('hidden'); $('#ap-scan').addClass('hidden');
$('#ap-loader .anim-dots').html('.'); $('#ap-loader .anim-dots').html('.');
scanAPs(); scanAPs();
} }
/** Ask the CGI what APs are visible (async) */ /** Ask the CGI what APs are visible (async) */
function scanAPs() { function scanAPs() {
$.get('http://'+_root+w.scan_url, onScan); $.get('http://'+_root+'/cfg/wifi/scan', onScan);
} }
function rescan(time) { function rescan(time) {
@@ -146,12 +130,6 @@
/** Set up the WiFi page */ /** Set up the WiFi page */
function wifiInit(cfg) { function wifiInit(cfg) {
// Hide what should be hidden in this mode
cfg.mode = +cfg.mode;
$('#ap-noscan').toggleClass('hidden', cfg.mode != 2);
$('#ap-scan').toggleClass('hidden', cfg.mode == 2);
// Update slider value displays // Update slider value displays
$('.Row.range').forEach(function(x) { $('.Row.range').forEach(function(x) {
var inp = x.querySelector('input'); var inp = x.querySelector('input');
+76 -50
View File
@@ -4,70 +4,71 @@ return [
'appname' => 'ESPTerm', 'appname' => 'ESPTerm',
'menu.cfg_wifi' => 'WiFi Settings', 'menu.cfg_wifi' => 'WiFi Settings',
'menu.cfg_network' => 'Network Configuration', 'menu.cfg_network' => 'Network Settings',
'menu.cfg_app' => 'Terminal Settings', 'menu.cfg_term' => 'Terminal Settings',
'menu.about' => 'About ESPTerm', 'menu.about' => 'About ESPTerm',
'menu.help' => 'Quick Reference', 'menu.help' => 'Quick Reference',
'menu.term' => 'Back to Terminal', 'menu.term' => 'Back to Terminal',
'menu.cfg_admin' => 'Reset & Restore', 'menu.cfg_system' => 'System Settings',
'menu.cfg_wifi_conn' => 'Connecting to External Network', 'menu.cfg_wifi_conn' => 'Connecting to Network',
'menu.settings' => 'Settings', 'menu.settings' => 'Settings',
'title.term' => 'Terminal', '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.ap' => 'DHCP Server (AP)',
'net.sta' => 'DHCP Client (Station)', 'net.sta' => 'DHCP Client (Station)',
'net.sta_mac' => 'Station MAC', 'net.sta_mac' => 'Station MAC',
'net.ap_mac' => 'AP MAC', 'net.ap_mac' => 'AP MAC',
'net.details' => 'MAC addresses', 'net.details' => 'MAC addresses',
'app.defaults' => 'Initial settings', 'term.defaults' => 'Initial Settings',
'app.explain_initials' => ' 'term.explain_initials' => '
Those are the initial settings used after ESPTerm restarts, and they Those are the initial settings used after ESPTerm powers on.
will also be applied immediately after you submit this form. The selected colors will also be used after receiving a
They can be subsequently changed by ESC commands, but those changes screen- or attributes-reset command. Those settings will be
aren\'t persistent and will be lost when the device powers off.', applied immediately after saving to preview the changes live.',
'app.term_title' => 'Header text', 'term.example' => 'Default colors preview',
'app.term_width' => 'Screen width',
'app.term_height' => 'Screen height',
'app.default_fg' => 'Base text color',
'app.default_bg' => 'Base background',
'app.btn1' => 'Button 1 text',
'app.btn2' => 'Button 2 text',
'app.btn3' => 'Button 3 text',
'app.btn4' => 'Button 4 text',
'app.btn5' => 'Button 5 text',
'term.term_title' => 'Header text',
'term.term_width' => 'Width / height',
'term.default_fg_bg' => 'Text / background',
'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', 'color.0' => 'Black',
'color.1' => 'Dark Red', 'color.1' => 'Red',
'color.2' => 'Dark Green', 'color.2' => 'Green',
'color.3' => 'Dim Yellow', 'color.3' => 'Yellow',
'color.4' => 'Deep Blue', 'color.4' => 'Blue',
'color.5' => 'Dark Violet', 'color.5' => 'Purple',
'color.6' => 'Dark Cyan', 'color.6' => 'Cyan',
'color.7' => 'Silver', 'color.7' => 'Silver',
'color.8' => 'Gray', 'color.8' => 'Gray',
'color.9' => 'Light Red', 'color.9' => 'Light Red',
'color.10' => 'Light Green', 'color.10' => 'Light Green',
'color.11' => 'Light Yellow', 'color.11' => 'Light Yellow',
'color.12' => 'Light Blue', 'color.12' => 'Light Blue',
'color.13' => 'Light Violet', 'color.13' => 'Light Purple',
'color.14' => 'Light Cyan', 'color.14' => 'Light Cyan',
'color.15' => 'White', 'color.15' => 'White',
'net.explain_sta' => ' 'net.explain_sta' => '
Those settings affect the built-in DHCP client used for Switch off Dynamic IP to configure the static IP address.',
connecting to an external network. Switching DHCP (dynamic IP) off
makes ESPTerm use the configured static IP. Please double-check
those settings before submitting, setting them incorrectly may
make it hard to access ESPTerm via the external network.',
'net.explain_ap' => ' 'net.explain_ap' => '
Those settings affect the built-in DHCP server in AP mode. Those settings affect the built-in DHCP server in AP mode.',
Please double-check those settings before submitting, setting them
incorrectly may render ESPTerm inaccessible via the AP.',
'net.ap_dhcp_time' => 'Lease time', 'net.ap_dhcp_time' => 'Lease time',
'net.ap_dhcp_start' => 'Pool start IP', 'net.ap_dhcp_start' => 'Pool start IP',
@@ -81,7 +82,7 @@ return [
'net.sta_addr_gw' => 'Gateway IP', 'net.sta_addr_gw' => 'Gateway IP',
'wifi.ap' => 'Built-in Access Point', 'wifi.ap' => 'Built-in Access Point',
'wifi.sta' => 'Connect to External Network', 'wifi.sta' => 'Join Existing Network',
'wifi.enable' => 'Enabled', 'wifi.enable' => 'Enabled',
'wifi.tpw' => 'Transmit power', 'wifi.tpw' => 'Transmit power',
@@ -99,36 +100,61 @@ return [
'wifi.sta_password' => 'Password:', 'wifi.sta_password' => 'Password:',
'wifi.scanning' => 'Scanning', 'wifi.scanning' => 'Scanning',
'wifi.scan_now' => 'Start scanning!', 'wifi.scan_now' => 'Click here to start scanning!',
'wifi.cant_scan_no_sta' => 'Can\'t scan with Client mode disabled.', 'wifi.cant_scan_no_sta' => 'Click here to enable client mode and start scanning!',
'wifi.select_ssid' => 'Available networks:', 'wifi.select_ssid' => 'Available networks:',
'wifi.enter_passwd' => 'Enter password for ":ssid:"',
'wifi.sta_explain' => 'After selecting a network, press Apply to connect.',
'wifi.conn.status' => 'Status:', 'wifi.conn.status' => 'Status:',
'wifi.conn.back_to_config' => 'Back to WiFi config', '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.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.success' => "Connected! Received IP ",
'wifi.conn.working' => "Connecting to selected AP", 'wifi.conn.working' => "Connecting to selected AP",
'wifi.conn.fail' => "Connection failed, check settings & try again. Cause: ", 'wifi.conn.fail' => "Connection failed, check settings & try again. Cause: ",
'admin.confirm_restore' => 'Restore all settings to their default values?', 'system.save_restore' => 'Save & Restore',
'admin.confirm_restore_hard' => 'system.confirm_restore' => 'Restore all settings to their default values?',
'system.confirm_restore_hard' =>
'Restore to firmware default settings? This will reset ' . 'Restore to firmware default settings? This will reset ' .
'all active settings and switch to AP mode with the default SSID.', 'all active settings and switch to AP mode with the default SSID.',
'admin.confirm_store_defaults' => 'system.confirm_store_defaults' =>
'Enter admin password to confirm you want to store the current settings as defaults.', 'Enter admin password to confirm you want to store the current settings as defaults.',
'admin.password' => 'Admin password:', 'system.password' => 'Admin password:',
'admin.restore_defaults' => 'Reset to default settings', 'system.restore_defaults' => 'Reset active settings to defaults',
'admin.write_defaults' => 'Save current settings as default', 'system.write_defaults' => 'Save active settings as defaults',
'admin.restore_hard' => 'Reset to firmware default settings', 'system.restore_hard' => 'Reset active settings to firmware defaults',
'admin.explain' => ' 'system.explain_persist' => '
ESPTerm contains two persistent memory banks, one for default and ESPTerm contains two persistent memory banks, one for default and
one for active settings. Active settings can be stored as defaults one for active settings. Active settings can be stored as defaults
by the administrator. Use the following button to revert all by the administrator (password required).
active settings to their stored default values.
', ',
'system.uart' => 'Serial Port',
'system.explain_uart' => '
This form controls the primary, communication UART. The debug UART is fixed at 115.200 baud, one stop-bit and no parity.
',
'uart.baud' => 'Baud rate',
'uart.parity' => 'Parity',
'uart.parity.none' => 'None',
'uart.parity.odd' => 'Odd',
'uart.parity.even' => 'Even',
'uart.stop_bits' => 'Stop-bits',
'uart.stop_bits.one' => 'One',
'uart.stop_bits.one_and_half' => 'One and half',
'uart.stop_bits.two' => 'Two',
'apply' => 'Apply!', 'apply' => 'Apply!',
'enabled' => 'Enabled', 'enabled' => 'Enabled',
+1
View File
@@ -3,6 +3,7 @@
echo "Packing js..." echo "Packing js..."
cat jssrc/chibi.js \ cat jssrc/chibi.js \
jssrc/keymaster.js \
jssrc/utils.js \ jssrc/utils.js \
jssrc/modal.js \ jssrc/modal.js \
jssrc/notif.js \ jssrc/notif.js \
+1 -1
View File
@@ -20,7 +20,7 @@
<table> <table>
<tr> <tr>
<th>ESPTerm</th> <th>ESPTerm</th>
<td>v%vers_fw%, build %date% at %time%</td> <td>v%vers_fw%, built %date% at %time%</td>
</tr> </tr>
<tr> <tr>
<th>libesphttpd</th> <th>libesphttpd</th>
-34
View File
@@ -1,34 +0,0 @@
<div class="Box">
<div class="Row explain">
<?= tr('admin.explain') ?>
</div>
<div class="Row buttons">
<a class="button icn-restore"
onclick="return confirm('<?= tr('admin.confirm_restore') ?>');"
href="<?= e(url('restore_defaults')) ?>"
>
<?= tr('admin.restore_defaults') ?>
</a>
</div>
<div class="Row buttons">
<a onclick="writeDefaults(); return false;" href="#"><?= tr('admin.write_defaults') ?></a>
</div>
<div class="Row buttons">
<a onclick="return confirm('<?= tr('admin.confirm_restore_hard') ?>');"
href="<?= e(url('restore_hard')) ?>"
>
<?= tr('admin.restore_hard') ?>
</a>
</div>
</div>
<script>
function writeDefaults() {
var pw = prompt('<?= tr('admin.confirm_store_defaults') ?>');
if (!pw) return;
location.href = <?=json_encode(url('write_defaults')) ?> + '?pw=' + pw;
}
</script>
-71
View File
@@ -1,71 +0,0 @@
<form class="Box mobopen str" action="<?= e(url('app_set')) ?>" method="GET" id='form-1'>
<h2><?= tr('app.defaults') ?></h2>
<div class="Row explain">
<?= tr('app.explain_initials') ?>
</div>
<div class="Row">
<label for="term_width"><?= tr('app.term_width') ?></label>
<input type="number" step=1 min=1 max=255 name="term_width" id="term_width" value="%term_width%" required>
</div>
<div class="Row">
<label for="term_height"><?= tr('app.term_height') ?></label>
<input type="number" step=1 min=1 max=255 name="term_height" id="term_height" value="%term_height%" required>
</div>
<div class="Row">
<label for="default_fg"><?= tr("app.default_fg") ?></label>
<select name="default_fg" id="default_fg">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>
<script>qs('#default_fg').selectedIndex = %default_fg%;</script>
</div>
<div class="Row">
<label for="default_bg"><?= tr("app.default_bg") ?></label>
<select name="default_bg" id="default_bg">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>
<script>qs('#default_bg').selectedIndex = %default_bg%;</script>
</div>
<div class="Row">
<label for="term_title"><?= tr('app.term_title') ?></label>
<input type="text" name="term_title" id="term_title" value="%term_title%" required>
</div>
<div class="Row">
<label for="btn1"><?= tr("app.btn1") ?></label>
<input class="short" type="text" name="btn1" id="btn1" value="%btn1%">
</div>
<div class="Row">
<label for="btn2"><?= tr("app.btn2") ?></label>
<input class="short" type="text" name="btn2" id="btn2" value="%btn2%">
</div>
<div class="Row">
<label for="btn3"><?= tr("app.btn3") ?></label>
<input class="short" type="text" name="btn3" id="btn3" value="%btn3%">
</div>
<div class="Row">
<label for="btn4"><?= tr("app.btn4") ?></label>
<input class="short" type="text" name="btn4" id="btn4" value="%btn4%">
</div>
<div class="Row">
<label for="btn5"><?= tr("app.btn5") ?></label>
<input class="short" type="text" name="btn5" id="btn5" value="%btn5%">
</div>
<div class="Row buttons">
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
</div>
</form>
+25 -22
View File
@@ -12,22 +12,22 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
<div class="Row checkbox x-static-toggle" > <div class="Row checkbox x-static-toggle" >
<label><?= tr('net.sta_dhcp_enable') ?></label><!-- <label><?= tr('net.sta_dhcp_enable') ?></label><!--
--><span class="box" tabindex=0 role=checkbox></span> --><span class="box" tabindex=0 role=checkbox></span>
<input type="hidden" name="sta_dhcp_enable" value="%sta_dhcp_enable%"> <input type="hidden" id="sta_dhcp_enable" name="sta_dhcp_enable" value="%sta_dhcp_enable%">
</div> </div>
<div class="Row x-static"> <div class="Row x-static">
<label for="sta_addr_ip"><?= tr('net.sta_addr_ip') ?></label> <label for="sta_addr_ip"><?= tr('net.sta_addr_ip') ?></label>
<input type="text" name="sta_addr_ip" id="sta_addr_ip" value="%sta_addr_ip%" <?=$ipmask?> required> <input type="text" name="sta_addr_ip" id="sta_addr_ip" value="%h:sta_addr_ip%" <?=$ipmask?> required>
</div> </div>
<div class="Row x-static"> <div class="Row x-static">
<label for="sta_addr_mask"><?= tr('net.sta_addr_mask') ?></label> <label for="sta_addr_mask"><?= tr('net.sta_addr_mask') ?></label>
<input type="text" name="sta_addr_mask" id="sta_addr_mask" value="%sta_addr_mask%" <?=$ipmask?> required> <input type="text" name="sta_addr_mask" id="sta_addr_mask" value="%h:sta_addr_mask%" <?=$ipmask?> required>
</div> </div>
<div class="Row x-static"> <div class="Row x-static">
<label for="sta_addr_gw"><?= tr('net.sta_addr_gw') ?></label> <label for="sta_addr_gw"><?= tr('net.sta_addr_gw') ?></label>
<input type="text" name="sta_addr_gw" id="sta_addr_gw" value="%sta_addr_gw%" <?=$ipmask?> required> <input type="text" name="sta_addr_gw" id="sta_addr_gw" value="%h:sta_addr_gw%" <?=$ipmask?> required>
</div> </div>
<div class="Row buttons"> <div class="Row buttons">
@@ -43,29 +43,29 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
</div> </div>
<div class="Row"> <div class="Row">
<label for="ap_dhcp_time"><?= tr('net.ap_dhcp_time') ?><span class="mq-phone">&nbsp;(min)</span></label> <label for="ap_addr_mask"><?= tr('net.ap_addr_mask') ?></label>
<input type="number" step=1 min=1 max=2880 name="ap_dhcp_time" id="ap_dhcp_time" value="%ap_dhcp_time%" required> <input type="text" name="ap_addr_mask" id="ap_addr_mask" value="%h:ap_addr_mask%" <?=$ipmask?> required>
<span class="mq-no-phone">&nbsp;(min)</span>
</div>
<div class="Row">
<label for="ap_dhcp_start"><?= tr('net.ap_dhcp_start') ?></label>
<input type="text" name="ap_dhcp_start" id="ap_dhcp_start" value="%ap_dhcp_start%" <?=$ipmask?> required>
</div>
<div class="Row">
<label for="ap_dhcp_end"><?= tr('net.ap_dhcp_end') ?></label>
<input type="text" name="ap_dhcp_end" id="ap_dhcp_end" value="%ap_dhcp_end%" <?=$ipmask?> required>
</div> </div>
<div class="Row"> <div class="Row">
<label for="ap_addr_ip"><?= tr('net.ap_addr_ip') ?></label> <label for="ap_addr_ip"><?= tr('net.ap_addr_ip') ?></label>
<input type="text" name="ap_addr_ip" id="ap_addr_ip" value="%ap_addr_ip%" <?=$ipmask?> required> <input type="text" name="ap_addr_ip" id="ap_addr_ip" value="%h:ap_addr_ip%" <?=$ipmask?> required>
</div> </div>
<div class="Row"> <div class="Row">
<label for="ap_addr_mask"><?= tr('net.ap_addr_mask') ?></label> <label for="ap_dhcp_start"><?= tr('net.ap_dhcp_start') ?></label>
<input type="text" name="ap_addr_mask" id="ap_addr_mask" value="%ap_addr_mask%" <?=$ipmask?> required> <input type="text" name="ap_dhcp_start" id="ap_dhcp_start" value="%h:ap_dhcp_start%" <?=$ipmask?> required>
</div>
<div class="Row">
<label for="ap_dhcp_end"><?= tr('net.ap_dhcp_end') ?></label>
<input type="text" name="ap_dhcp_end" id="ap_dhcp_end" value="%h:ap_dhcp_end%" <?=$ipmask?> required>
</div>
<div class="Row">
<label for="ap_dhcp_time"><?= tr('net.ap_dhcp_time') ?><span class="mq-phone">&nbsp;(min)</span></label>
<input type="number" step=1 min=1 max=2880 name="ap_dhcp_time" id="ap_dhcp_time" value="%ap_dhcp_time%" required>
<span class="mq-no-phone">&nbsp;min</span>
</div> </div>
<div class="Row buttons"> <div class="Row buttons">
@@ -86,10 +86,13 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
<script> <script>
function updateStaticDisp() { function updateStaticDisp() {
$('.x-static').toggleClass('hidden', $('#sta_dhcp_enable').val()); var sttc = !parseInt($('#sta_dhcp_enable').val());
$('.x-static').toggleClass('hidden', !sttc);
} }
$('.x-static-toggle').on('click', function() { $('.x-static-toggle').on('click', function() {
updateStaticDisp(); setTimeout(function() {
updateStaticDisp();
}, 0)
}); });
updateStaticDisp(); updateStaticDisp();
</script> </script>
+90
View File
@@ -0,0 +1,90 @@
<div class="Box str mobcol">
<h2 tabindex=0><?= tr('system.save_restore') ?></h2>
<div class="Row explain nomargintop">
<?= tr('system.explain_persist') ?>
</div>
<div class="Row buttons2">
<a class="button icn-restore"
onclick="return confirm('<?= tr('system.confirm_restore') ?>');"
href="<?= e(url('restore_defaults')) ?>">
<?= tr('system.restore_defaults') ?>
</a>
</div>
<div class="Row buttons2">
<a onclick="writeDefaults(); return false;" href="#"><?= tr('system.write_defaults') ?></a>
</div>
<div class="Row buttons2">
<a onclick="return confirm('<?= tr('system.confirm_restore_hard') ?>');"
href="<?= e(url('restore_hard')) ?>">
<?= tr('system.restore_hard') ?>
</a>
</div>
</div>
<form class="Box str mobcol" action="<?= e(url('system_set')) ?>" method="GET" id="form-1">
<h2 tabindex=0><?= tr('system.uart') ?></h2>
<div class="Row explain">
<?= tr('system.explain_uart') ?>
</div>
<div class="Row">
<label for="uart_baud"><?= tr('uart.baud') ?><span class="mq-phone">&nbsp;(bps)</span></label>
<select name="uart_baud" id="uart_baud" class="short">
<?php foreach([
300, 600, 1200, 2400, 4800, 9600, 19200, 38400,
57600, 74880, 115200, 230400, 460800, 921600, 1843200, 3686400,
] as $b):
?><option value="<?=$b?>"><?= number_format($b, 0, ',', '.') ?></option>
<?php endforeach; ?>
</select>
<span class="mq-no-phone">&nbsp;bps</span>
</div>
<div class="Row">
<label for="uart_parity"><?= tr('uart.parity') ?></label>
<select name="uart_parity" id="uart_parity" class="short">
<?php foreach([
2 => tr('uart.parity.none'),
1 => tr('uart.parity.odd'),
0 => tr('uart.parity.even'),
] as $k => $label):
?><option value="<?=$k?>"><?=$label?></option>
<?php endforeach; ?>
</select>
</div>
<div class="Row">
<label for="uart_stopbits"><?= tr('uart.stop_bits') ?></label>
<select name="uart_stopbits" id="uart_stopbits" class="short">
<?php foreach([
1 => tr('uart.stop_bits.one'),
2 => tr('uart.stop_bits.one_and_half'),
3 => tr('uart.stop_bits.two'),
] as $k => $label):
?><option value="<?=$k?>"><?=$label?></option>
<?php endforeach; ?>
</select>
</div>
<div class="Row buttons">
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
</div>
</form>
<script>
function writeDefaults() {
var pw = prompt('<?= tr('system.confirm_store_defaults') ?>');
if (!pw) return;
location.href = <?=json_encode(url('write_defaults')) ?> + '?pw=' + pw;
}
$('#uart_baud').val(%uart_baud%);
$('#uart_parity').val(%uart_parity%);
$('#uart_stopbits').val(%uart_stopbits%);
</script>
+160
View File
@@ -0,0 +1,160 @@
<form class="Box mobopen str" action="<?= e(url('term_set')) ?>" method="GET" id='form-1'>
<h2><?= tr('term.defaults') ?></h2>
<div class="Row explain">
<?= tr('term.explain_initials') ?>
</div>
<div class="Row">
<label for="theme"><?= tr("term.theme") ?></label>
<select name="theme" id="theme" class="short" onchange="showColor()">
<option value="0">Tango</option>
<option value="1">Linux</option>
<option value="2">XTerm</option>
<option value="3">Rxvt</option>
<option value="4">Ambience</option>
<option value="5">Solarized</option>
</select>
</div>
<div class="Row color-preview">
<div class="colorprev">
<span data-fg=0 class="bg0 fg0">30</span><!--
--><span data-fg=1 class="bg0 fg1">31</span><!--
--><span data-fg=2 class="bg0 fg2">32</span><!--
--><span data-fg=3 class="bg0 fg3">33</span><!--
--><span data-fg=4 class="bg0 fg4">34</span><!--
--><span data-fg=5 class="bg0 fg5">35</span><!--
--><span data-fg=6 class="bg0 fg6">36</span><!--
--><span data-fg=7 class="bg0 fg7">37</span>
</div>
<div class="colorprev">
<span data-fg=8 class="bg0 fg8">90</span><!--
--><span data-fg=9 class="bg0 fg9">91</span><!--
--><span data-fg=10 class="bg0 fg10">92</span><!--
--><span data-fg=11 class="bg0 fg11">93</span><!--
--><span data-fg=12 class="bg0 fg12">94</span><!--
--><span data-fg=13 class="bg0 fg13">95</span><!--
--><span data-fg=14 class="bg0 fg14">96</span><!--
--><span data-fg=15 class="bg0 fg15">97</span>
</div>
<div class="colorprev">
<span data-bg=0 class="bg0 fg15">40</span><!--
--><span data-bg=1 class="bg1 fg15">41</span><!--
--><span data-bg=2 class="bg2 fg15">42</span><!--
--><span data-bg=3 class="bg3 fg0">43</span><!--
--><span data-bg=4 class="bg4 fg15">44</span><!--
--><span data-bg=5 class="bg5 fg15">45</span><!--
--><span data-bg=6 class="bg6 fg15">46</span><!--
--><span data-bg=7 class="bg7 fg0">47</span>
</div>
<div class="colorprev">
<span data-bg=8 class="bg8 fg15">100</span><!--
--><span data-bg=9 class="bg9 fg0">101</span><!--
--><span data-bg=10 class="bg10 fg0">102</span><!--
--><span data-bg=11 class="bg11 fg0">103</span><!--
--><span data-bg=12 class="bg12 fg0">104</span><!--
--><span data-bg=13 class="bg13 fg0">105</span><!--
--><span data-bg=14 class="bg14 fg0">106</span><!--
--><span data-bg=15 class="bg15 fg0">107</span>
</div>
</div>
<div class="Row color-preview">
<div style="
" id="color-example">
<?= tr("term.example") ?>
</div>
</div>
<div class="Row">
<label><?= tr("term.default_fg_bg") ?></label>
<select name="default_fg" id="default_fg" class="short" onchange="showColor()">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>&nbsp;<!--
--><select name="default_bg" id="default_bg" class="short" onchange="showColor()">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>
</div>
<div class="Row">
<label for="term_width"><?= tr('term.term_width') ?></label>
<input type="number" step=1 min=1 max=255 name="term_width" id="term_width" value="%term_width%" required>&nbsp;<!--
--><input type="number" step=1 min=1 max=255 name="term_height" id="term_height" value="%term_height%" required>
</div>
<div class="Row">
<label for="term_title"><?= tr('term.term_title') ?></label>
<input type="text" name="term_title" id="term_title" value="%h:term_title%" required>
</div>
<div class="Row">
<label><?= tr("term.buttons") ?></label>
<input class="short" type="text" name="btn1" id="btn1" value="%h:btn1%">&nbsp;
<input class="short" type="text" name="btn2" id="btn2" value="%h:btn2%">&nbsp;
<input class="short" type="text" name="btn3" id="btn3" value="%h:btn3%">&nbsp;
<input class="short" type="text" name="btn4" id="btn4" value="%h:btn4%">&nbsp;
<input class="short" type="text" name="btn5" id="btn5" value="%h:btn5%">
</div>
<div class="Row">
<label for="parser_tout_ms"><?= tr('term.parser_tout_ms') ?><span class="mq-phone">&nbsp;(ms)</span></label>
<input type="number" step=1 min=0 name="parser_tout_ms" id="parser_tout_ms" value="%parser_tout_ms%" required>
<span class="mq-no-phone">&nbsp;ms</span>
</div>
<div class="Row">
<label for="display_tout_ms"><?= tr('term.display_tout_ms') ?><span class="mq-phone">&nbsp;(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">&nbsp;ms</span>
</div>
<div class="Row">
<label for="display_cooldown_ms"><?= tr('term.display_cooldown_ms') ?><span class="mq-phone">&nbsp;(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">&nbsp;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>
</form>
<script>
$('#default_fg').val(%default_fg%);
$('#default_bg').val(%default_bg%);
$('#theme').val(%theme%);
function showColor() {
var ex = qs('#color-example');
ex.className = '';
ex.classList.add('fg'+$('#default_fg').val());
ex.classList.add('bg'+$('#default_bg').val());
var th = $('#theme').val();
$('.color-preview').forEach(function(e) {
e.className = 'Row color-preview theme-'+th;
});
}
showColor();
$('.colorprev span').on('click', function() {
var fg = this.dataset.fg;
var bg = this.dataset.bg;
if (typeof fg != 'undefined') $('#default_fg').val(fg);
if (typeof bg != 'undefined') $('#default_bg').val(bg);
showColor()
});
</script>
+46 -31
View File
@@ -1,28 +1,28 @@
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-1"> <form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-1">
<h2 tabindex=0><?= tr('wifi.ap') ?></h2> <h2 tabindex=0><?= tr('wifi.ap') ?></h2>
<div class="Row checkbox"> <div class="Row checkbox x-ap-toggle">
<label><?= tr('wifi.enable') ?></label><!-- <label><?= tr('wifi.enable') ?></label><!--
--><span class="box" tabindex=0></span> --><span class="box" tabindex=0></span>
<input type="hidden" name="ap_enable" value="%ap_enable%"> <input type="hidden" id="ap_enabled" name="ap_enable" value="%ap_enable%">
</div> </div>
<div class="Row"> <div class="Row x-ap-on">
<label for="ap_ssid"><?= tr('wifi.ap_ssid') ?></label> <label for="ap_ssid"><?= tr('wifi.ap_ssid') ?></label>
<input type="text" name="ap_ssid" id="ap_ssid" value="%ap_ssid%" required> <input type="text" name="ap_ssid" id="ap_ssid" value="%h:ap_ssid%" required>
</div> </div>
<div class="Row"> <div class="Row x-ap-on">
<label for="ap_password"><?= tr('wifi.ap_password') ?></label> <label for="ap_password"><?= tr('wifi.ap_password') ?></label>
<input type="text" name="ap_password" id="ap_password" value="%ap_password%"> <input type="text" name="ap_password" id="ap_password" value="%h:ap_password%">
</div> </div>
<div class="Row"> <div class="Row x-ap-on">
<label for="ap_channel"><?= tr('wifi.ap_channel') ?></label> <label for="ap_channel"><?= tr('wifi.ap_channel') ?></label>
<input type="number" name="ap_channel" id="ap_channel" min=1 max=14 value="%ap_channel%" required> <input type="number" name="ap_channel" id="ap_channel" min=1 max=14 value="%ap_channel%" required>
</div> </div>
<div class="Row range"> <div class="Row range x-ap-on">
<label for="tpw"> <label for="tpw">
<?= tr('wifi.tpw') ?> <?= tr('wifi.tpw') ?>
<span class="display x-disp1 mq-phone"></span> <span class="display x-disp1 mq-phone"></span>
@@ -31,7 +31,7 @@
<span class="display x-disp2 mq-no-phone"></span> <span class="display x-disp2 mq-no-phone"></span>
</div> </div>
<div class="Row checkbox"> <div class="Row checkbox x-ap-on">
<label><?= tr('wifi.ap_hidden') ?></label><!-- <label><?= tr('wifi.ap_hidden') ?></label><!--
--><span class="box" tabindex=0></span> --><span class="box" tabindex=0></span>
<input type="hidden" name="ap_hidden" value="%ap_hidden%"> <input type="hidden" name="ap_hidden" value="%ap_hidden%">
@@ -42,19 +42,24 @@
</div> </div>
</form> </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> <h2 tabindex=0><?= tr('wifi.sta') ?></h2>
<div class="Row checkbox"> <div class="Row checkbox x-sta-toggle">
<label><?= tr('wifi.enable') ?></label><!-- <label><?= tr('wifi.enable') ?></label><!--
--><span class="box" tabindex=0></span> --><span class="box" tabindex=0></span>
<input type="hidden" name="sta_enable" value="%sta_enable%"> <input type="hidden" id="sta_enabled" name="sta_enable" value="%sta_enable%">
</div>
<div class="Row explain nomargintop x-sta-on">
<span class="spacer"></span>
<?= tr("wifi.sta_explain") ?>
</div> </div>
<input type="hidden" name="sta_ssid" id="sta_ssid" value=""> <input type="hidden" name="sta_ssid" id="sta_ssid" value="">
<input type="hidden" name="sta_password" id="sta_password" value=""> <input type="hidden" name="sta_password" id="sta_password" value="">
<div class="Row sta-info"> <div class="Row sta-info x-sta-on">
<label><?= tr('wifi.sta_info') ?></label> <label><?= tr('wifi.sta_info') ?></label>
<div class="AP-preview hidden" id="sta-nw"> <div class="AP-preview hidden" id="sta-nw">
<div class="wrap"> <div class="wrap">
@@ -72,11 +77,10 @@
</div> </div>
</div> </div>
<div id="ap-box"> <div id="ap-box" class="x-sta-on">
<label><?= tr('wifi.select_ssid') ?></label> <label><?= tr('wifi.select_ssid') ?></label>
<div id="ap-scan"><a href="#" onclick="WiFi.startScanning(); return false"><?= tr('wifi.scan_now') ?></a></div> <div id="ap-scan"><a href="#" onclick="WiFi.startScanning(); return false"><?= tr('wifi.scan_now') ?></a></div>
<div id="ap-loader" class="hidden"><?= tr('wifi.scanning') ?><span class="anim-dots">.</span></div> <div id="ap-loader" class="hidden"><?= tr('wifi.scanning') ?><span class="anim-dots">.</span></div>
<div id="ap-noscan" class="hidden"><?= tr('wifi.cant_scan_no_sta') ?></div>
<div id="ap-list" class="hidden"></div> <div id="ap-list" class="hidden"></div>
</div> </div>
@@ -85,24 +89,35 @@
</div> </div>
</form> </form>
<div class="Modal hidden" id="psk-modal">
<div class="Dialog">
<form id="conn-form" onsubmit="submitPskModal(); return false;">
<input type="hidden" id="conn-ssid"><!--
--><label for="conn-passwd"><?= tr('wifi.sta_password') ?></label><!--
--><input type="text" id="conn-passwd"><!--
--><input type="submit" value="<?= tr('confirm') ?>">
</form>
</div>
</div>
<script> <script>
WiFi.scan_url = '<?= url('wifi_scan', true) ?>'; WiFi.scan_url = '<?= url('wifi_scan', true) ?>';
WiFi.init({ WiFi.init({
mode: '%opmode%', sta_ssid: '%j:sta_ssid%',
sta_ssid: '%sta_ssid%', sta_password: '%j:sta_password%',
sta_password: '%sta_password%', sta_active_ip: '%j:sta_active_ip%',
sta_active_ip: '%sta_active_ip%', sta_active_ssid: '%j:sta_active_ssid%',
sta_active_ssid: '%sta_active_ssid%',
}); });
function updateApDisp() {
var a = !!parseInt($('#ap_enabled').val());
$('.x-ap-on').toggleClass('hidden', !a);
}
$('.x-ap-toggle').on('click', function() {
setTimeout(function() {
updateApDisp();
}, 0)
});
function updateStaDisp() {
var a = !!parseInt($('#sta_enabled').val());
$('.x-sta-on').toggleClass('hidden', !a);
}
$('.x-sta-toggle').on('click', function() {
setTimeout(function() {
updateStaDisp();
}, 0)
});
updateApDisp();
updateStaDisp();
</script> </script>
+51 -15
View File
@@ -5,9 +5,15 @@
<a href="<?= e(url('cfg_wifi')) ?>" id="backbtn" class="button"><?= tr('wifi.conn.back_to_config') ?></a> <a href="<?= e(url('cfg_wifi')) ?>" id="backbtn" class="button"><?= tr('wifi.conn.back_to_config') ?></a>
</div> </div>
<div class="Box">
<p><?= tr('wifi.conn.explain_android_sucks') ?></p>
<p><?= tr('wifi.conn.explain_reset') ?></p>
</div>
<script> <script>
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var abortTmeo; var abortTmeo;
var failCounter = 0;
var messages = <?= json_encode([ var messages = <?= json_encode([
'disabled' => tr('wifi.conn.disabled'), 'disabled' => tr('wifi.conn.disabled'),
@@ -17,33 +23,63 @@
'fail' => tr('wifi.conn.fail'), 'fail' => tr('wifi.conn.fail'),
]) ?>; ]) ?>;
function onFail() {
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>);
$('.anim-dots').addClass('hidden');
}
function getStatus() { function getStatus() {
xhr.open("GET", 'http://'+_root+'<?= url('wifi_connstatus', true) ?>'); xhr.open("GET", 'http://'+_root+'<?= url('wifi_connstatus', true) ?>');
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) { if (xhr.readyState == 4) {
clearTimeout(abortTmeo); if (xhr.status == 200) {
var data = JSON.parse(xhr.responseText); clearTimeout(abortTmeo);
var done = false;
var msg = messages[data.status] || '...';
if (data.status == 'success') msg += data.ip;
if (data.status == 'fail') msg += data.cause;
$("#status").html(msg); try {
var data = JSON.parse(xhr.responseText);
var done = false;
var msg = messages[data.status] || '...';
if (done) { if (data.status == 'success') {
// $('#backbtn').removeClass('hidden'); msg += data.ip;
$('.anim-dots').addClass('hidden'); 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 { } else {
window.setTimeout(getStatus, 1000); onFail();
} }
} }
}; };
// XHR timeout
abortTmeo = setTimeout(function () { abortTmeo = setTimeout(function () {
xhr.abort(); xhr.abort();
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>); onFail();
// $('#backbtn').removeClass('hidden');
$('.anim-dots').addClass('hidden');
}, 4000); }, 4000);
xhr.send(); xhr.send();
+568 -286
View File
@@ -1,316 +1,598 @@
<div class="Box"> <div class="Box fold">
<h2>Wiring</h2> <h2>Tips & Troubleshooting</h2>
<ul> <div class="Row v">
<li>Communication UART is on pins <b>Rx, Tx</b> at 115200-8-1-N. The baud rate can be changed in Terminal Settings. <img src="/img/adapter.jpg" class="aside" alt="ESPTerm v2">
<li>Debug log is on pin <b>GPIO2</b> (P2) at 115200-8-1-N. This baud rate is fixed. <ul>
<li>Compatible with 3.3&nbsp;V and 5&nbsp;V logic. For 5&nbsp;V, 470&nbsp;R protection resistors are recommended. <li><b>Communication UART (Rx, Tx)</b> can be configured in the <a href="<?= url('cfg_system') ?>">System Settings</a>.
<li>If the "LVD" LED on the ESPTerm module lights up, it doesn't get enough power to run correctly. Check your connections.
<li>Connect Rx and Tx with a piece of wire to test the terminal alone, you should see what you type in the browser. <li><b>Boot log and debug messages</b> are available on pin <b>GPIO2</b> (P2) at 115200\,baud, 1 stop bit, no parity.
<i>NOTE: This won't work if your ESP8266 board has a built-in USB-serial converter (like NodeMCU).</i> Those messages may be disabled through compile flags.
<li>For best performance, use the module in Client mode (connected to external network).
<li>In AP mode, check that the channel used is clear; interference may cause a flaky connection. <li><b>Loopback test</b>: Connect the Rx and Tx pins with a piece of wire. Anything you type in the browser should
</ul> 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><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>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>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>
<div class="Box"> <div class="Box fold">
<h2>Screen</h2> <h2>Escape Sequences Intro</h2>
<ul> <div class="Row v">
<li>Most ANSI escape sequences are supported.</li> <img src="/img/vt100.jpg" class="aside" alt="VT102">
<li>The max screen size is 2000 characters (eg. <b>25x80</b>), default is <b>10x26</b>. Set using <code>\e]W&lt;rows&gt;;&lt;cols&gt;\a</code>.</li>
<li>The screen will automatically scroll, can be used for log output.</li> <p>ESPTerm emulates VT102 (pictured) with some additions from later VT models and <code>xterm</code>.
<li>Display update is sent <b>after 20 ms of inactivity</b>.</li> All commonly used attributes and commands are supported.
<li>At most 4 clients can be connected at the same time.</li> ESPTerm is capable of displaying ncurses applications such as <i>Midnight Commander</i> using <i>agetty</i>.</p>
<li>The browser display needs WebSockets for the real-time updating. It may not work on really old phones / browsers.</li>
</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>
<div class="Box"> <div class="Box fold">
<h2>Color Reference</h2> <h2>Screen Behavior</h2>
<p>Color is set using <code>\e[&lt;c&gt;m</code> or <code>\e[&lt;c&gt;;&lt;c&gt;m</code> where "&lt;c&gt;" are numbers from the following tables:</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> <p>
Screen updates are sent to the browser through a WebSocket after some time of inactivity on the communication UART
<div class="colorprev"> (called "Redraw Delay"). After an update is sent, at least a time of "Redraw Cooldown" must elapse before the next
<span class="bg7 fg0">30</span> update can be sent. Those delays are used is to avoid burdening the server with tiny updates during a large screen
<span class="bg0 fg1">31</span> repaint. If you experience issues (broken image due to dropped bytes), try adjusting those config options. It may also
<span class="bg0 fg2">32</span> be useful to try different baud rates.
<span class="bg0 fg3">33</span> </p>
<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>
<div class="colorprev">
<span class="bg7 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 nb">40</span>
<span class="bg1 fg15 nb">41</span>
<span class="bg2 fg15 nb">42</span>
<span class="bg3 fg0">43</span>
<span class="bg4 fg15 nb">44</span>
<span class="bg5 fg15 nb">45</span>
<span class="bg6 fg15 nb">46</span>
<span class="bg7 fg0">47</span>
</div>
<div class="colorprev">
<span class="bg8 fg15 nb">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>Bright foreground can also be set using the "bold" attribute 1 (eg. <code>\e[31;1m</code>). For more details, see the ANSI code reference below.</p>
</div> </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> <h2>User Input</h2>
<p> <div class="Row v">
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. <h3>Keyboard</h3>
The input is limited to ASCII codes 32-126, backspace 8 and tab 9.
</p>
<p>The buttons under the screen send ASCII codes 1, 2, 3, 4, 5.</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. Here are some examples:
</p>
<p> <table>
<b>Mouse input</b> (click/tap) is sent as <code>\e[&lt;y&gt;;&lt;x&gt;M</code>. You can use this for on-screen buttons, menu navigation etc. <thead><tr><th>Key</th><th>Code</th><th>Key</th><th>Code</th></tr></thead>
</p> <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>
<div class="Box"> <div class="Box fold">
<h2>Supported ANSI Escape Sequences</h2> <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> <h3>Movement</h3>
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>
<table class="ansiref"> <table class="ansiref w100">
<thead> <thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<tr> <tbody>
<th>Attribute</th> <tr>
<th>Meaning</th> <td>
</tr> \e[<i>n</i>A<br>
</thead> \e[<i>n</i>B<br>
<tbody> \e[<i>n</i>C<br>
<tr> \e[<i>n</i>D
<td>0</td> </td>
<td>Reset text attributes to default</td> <td>Move cursor up (A), down (B), right (C), left (D)</td>
</tr> </tr>
<tr> <tr>
<td>7</td> <td>
<td>Inverse (fg/bg swap when printing)</td> \e[<i>n</i>F<br>
</tr> \e[<i>n</i>E
<tr> </td>
<td>27</td> <td>Go <i>n</i> lines up (F) or down (E), start of line</td>
<td>Inverse OFF</td> </tr>
</tr> <tr>
<tr> <td>
<td>30-37, 90-97</td> \e[<i>r</i>d<br>
<td>Foreground color, normal and bright</td> \e[<i>c</i>G<br>
</tr> \e[<i>r</i>;<i>c</i>H
<tr> </td>
<td>40-47, 100-107</td> <td>
<td>Background color, normal and bright</td> Go to absolute position - row (d), column (G), or both (H). Use <code>\e[H</code> to go to 1,1.
</tr> </td>
</tbody> </tr>
</table> <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"> <h3>Scrolling Region</h3>
<thead>
<tr>
<th>Code</th>
<th>Params</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>\e[&lt;n&gt;A</td>
<td>[count]</td>
<td>Move cursor up</td>
</tr>
<tr>
<td>\e[&lt;n&gt;B</td>
<td>[count]</td>
<td>Move cursor down</td>
</tr>
<tr>
<td>\e[&lt;n&gt;C</td>
<td>[count]</td>
<td>Move cursor forward (right)</td>
</tr>
<tr>
<td>\e[&lt;n&gt;D</td>
<td>[count]</td>
<td>Move cursor backward (left)</td>
</tr>
<tr>
<td>\e[&lt;n&gt;E</td>
<td>[count]</td>
<td>Go N line down, start of line</td>
</tr>
<tr>
<td>\e[&lt;n&gt;F</td>
<td>[count]</td>
<td>Go N lines up, start of line</td>
</tr>
<tr>
<td>\e[&lt;n&gt;G</td>
<td>column</td>
<td>Go to column</td>
</tr>
<tr>
<td>\e[&lt;y&gt;;&lt;x&gt;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>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"> <h3>Tab stops</h3>
<thead>
<tr>
<th>Code</th>
<th>Params</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td>\e[&lt;m&gt;J</td>
<td>[mode=0]</td>
<td>Clear screen. Mode: 0 - from cursor, 1 - to cursor, 2 - all</td>
</tr>
<tr>
<td>\e[&lt;m&gt;K</td>
<td>[mode=0]</td>
<td>Erase line. Mode: 0 - from cursor, 1 - to cursor, 2 - all</td>
</tr>
<tr>
<td>\e[&lt;n&gt;S</td>
<td>[lines]</td>
<td>Scroll screen content up, add empty line at the bottom</td>
</tr>
<tr>
<td>\e[&lt;n&gt;T</td>
<td>[lines]</td>
<td>Scroll screen content down, add empty line at the top</td>
</tr>
<tr>
<td>\e]W&lt;r&gt;;&lt;c&gt;\a</td>
<td>rows;cols</td>
<td>Set screen size, maximum 25x80 (resp. total 2000 characters). This also clears the screen.</td>
</tr>
</tbody>
</table>
<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"> <h3>Other options</h3>
<thead>
<tr> <table class="ansiref w100">
<th>Code</th> <thead><tr><th>Code</th><th>Meaning</th></tr></thead>
<th>Params</th> <tbody>
<th>Meaning</th> <tr>
</tr> <td>\e[?7h<br>\e[?7l</td>
</thead> <td>Enable (h) or disable (l) cursor auto-wrap and screen auto-scroll</td>
<tbody> </tr>
<tr> <tr>
<td>\ec</td> <td>\e[?25h<br>\e[?25l</td>
<td>--</td> <td>Show (h) or hide (l) the cursor</td>
<td> </tr>
"Device Reset" - clear screen, reset attributes, show cursor & move it to 1,1. </tbody>
The screen size and WiFi settings stay unchanged. </table>
</td> </div>
</tr> </div>
<tr>
<td>\e]FR\a</td> <div class="Box fold">
<td>--</td> <h2>Screen Content Manipulation</h2>
<td>"Factory Reset", emergency code when you mess up the WiFi, restores SSID to unique default, clears stored credentials & enters Client+AP mode.</td>
</tr> <div class="Row v">
<tr> <p>
<td>\e[5n</td> <b>Legend:</b>
<td>--</td> Italic letters such as <i>n</i> are ASCII numbers that serve as arguments, separated with a semicolon.
<td>Query device status, replies with <code>\e[0n</code> "device is OK". Can be used to check if the UART works.</td> If an argument is left out, it's treated as 0 or 1, depending on what makes sense for the command.
</tr> </p>
</tbody>
</table> <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>Title and Button Labels</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>\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>
</tbody>
</table>
</div>
</div>
<div class="Box fold">
<h2>System Commands</h2>
<div class="Row v">
<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>
</tbody>
</table>
</div>
</div> </div>
+38 -13
View File
@@ -2,36 +2,61 @@
// Workaround for badly loaded page // Workaround for badly loaded page
setTimeout(function() { setTimeout(function() {
if (typeof termInit == 'undefined' || typeof $ == 'undefined') { if (typeof termInit == 'undefined' || typeof $ == 'undefined') {
console.error("Page load failed, refreshing…");
location.reload(true); location.reload(true);
} }
}, 2000); }, 2000);
</script> </script>
<h1>%term_title%</h1> <h1></h1>
<div id="termwrap"> <div id="termwrap">
<div id="screen"></div> <div id="screen" class="theme-%theme%"></div>
<div id="buttons"> <div id="buttons">
<button data-n="1" class="btn-blue">%btn1%</button><!-- <button data-n="1" class="btn-blue"></button><!--
--><button data-n="2" class="btn-blue">%btn2%</button><!-- --><button data-n="2" class="btn-blue"></button><!--
--><button data-n="3" class="btn-blue">%btn3%</button><!-- --><button data-n="3" class="btn-blue"></button><!--
--><button data-n="4" class="btn-blue">%btn4%</button><!-- --><button data-n="4" class="btn-blue"></button><!--
--><button data-n="5" class="btn-blue">%btn5%</button> --><button data-n="5" class="btn-blue"></button>
</div> </div>
</div> </div>
<textarea id="softkb-input" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
<nav id="botnav"> <nav id="botnav">
<a href="<?= url('cfg_wifi') ?>"><?= tr('menu.settings') ?></a><!-- <a href="#" onclick="toggleSoftKb(true); return false" class="icn-keyboard mq-tablet-max"></a><!--
--><a href="<?= url('help') ?>">Help</a><!-- --><a href="<?= url('cfg_term') ?>"><?= tr('term_nav.config') ?></a><!--
--><a href="<?= url('about') ?>">About</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> </nav>
<script> <script>
try { try {
termInit(%screenData%); window.noAutoShow = true;
termInit(); // the screen will be loaded via ajax
Screen.load('%j:labels_seq%');
// auto-clear the input box
$('#softkb-input').on('input', function(e) {
setTimeout(function(){
var str = $('#softkb-input').val();
$('#softkb-input').val('');
Input.sendString(str);
}, 1);
});
} catch(e) { } catch(e) {
console.error("Fail, reloading..."); console.error(e);
location.reload(true); console.error("Fail, reloading in 1s…");
setTimeout(function() {
location.reload(true);
}, 1000);
}
function toggleSoftKb(yes) {
var i = qs('#softkb-input');
if (yes) i.focus();
else i.blur();
} }
</script> </script>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-24
View File
@@ -1,24 +0,0 @@
$term-colors:
// 0 black, 1 red, 2 green, 3 yellow
// 4 blue, 5 mag, 6 cyan, 7 white
#111213, #CC0000, #4E9A06, #C4A000,
#3465A4, #75507B, #06989A, #D3D7CF,
// BRIGHT
// 8 black, 9 red, 10 green, 11 yellow
// 12 blue, 13 mag, 14 cyan, 15 white
#555753, #EF2929, #8AE234, #FCE94F,
#729FCF, #AD7FA8, #34E2E2, #EEEEEC;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
.fg8, .fg9, .fg10, .fg11, .fg12, .fg13, .fg14, .fg15 {
font-weight: bold;
}
.nb {
font-weight: normal !important;
}
+1 -2
View File
@@ -1,5 +1,5 @@
@import "normalize"; @import "normalize";
@import "fontello-embedded"; @import "fontello";
@import "lib/bourbon/bourbon"; @import "lib/bourbon/bourbon";
@import "grid-settings"; @import "grid-settings";
@@ -33,7 +33,6 @@ $c-form-highlight-a: #2ea1f9;
@import "pages/wifi"; @import "pages/wifi";
@import "pages/term"; @import "pages/term";
@import "pages/about"; @import "pages/about";
@import "term-colors";
// media queries // media queries
+15 -4
View File
@@ -1,5 +1,13 @@
@import "buttons"; @import "buttons";
#{$all-text-inputs}, select, label.select-wrap {
width: $form-field-w;
}
input[type="number"], input.short, select.short {
width: $form-field-w/2;
}
#{$all-text-inputs}, select { #{$all-text-inputs}, select {
border: 0 none; border: 0 none;
border-bottom: 2px solid $c-form-highlight; border-bottom: 2px solid $c-form-highlight;
@@ -33,17 +41,20 @@
color: $c-form-highlight-a; color: $c-form-highlight-a;
&::before { &::before {
font-weight: bold; font-family: "fontello";
position: absolute; position: absolute;
content: '×'; //content: '×';
//content: '✓';
left: 0; top: 0; right: 0; bottom: 0; left: 0; top: 0; right: 0; bottom: 0;
line-height: $h - 1px; line-height: 25px;
text-align: center; text-align: center;
font-size: $h; font-size: 20px;
vertical-align: middle; vertical-align: middle;
display: none; display: none;
} }
@include icon-content('ok');
&.checked::before { &.checked::before {
display: block; display: block;
} }
+16 -9
View File
@@ -1,14 +1,6 @@
// Unified Form wrapper // Unified Form wrapper
form { @include naked(); } form { @include naked(); }
#{$all-text-inputs}, select, label.select-wrap {
width: $form-field-w;
}
input[type="number"], input.short {
width: $form-field-w/2;
}
.Box.errors { .Box.errors {
.list { .list {
color: crimson; color: crimson;
@@ -37,6 +29,20 @@ input[type="number"], input.short {
margin-bottom: 0; margin-bottom: 0;
} }
&.v {
display: block;
}
.aside {
float: right;
margin-left: 5px;
margin-bottom: 5px;
@include media($phone) {
margin: 0; float: none;
}
}
.spacer { .spacer {
width: $form-label-w; width: $form-label-w;
@@ -45,7 +51,8 @@ input[type="number"], input.short {
} }
} }
&.buttons { // buttons2 is the same style, but different selector for use in the admin page
&.buttons, &.buttons2 {
margin: 16px auto; margin: 16px auto;
input, .button { input, .button {
margin-right: dist(-1); margin-right: dist(-1);
+7 -2
View File
@@ -31,6 +31,11 @@ a:hover {
} }
ul > * { ul > * {
padding-top: .2em; padding-top: .3em;
padding-bottom: .2em; padding-bottom: .3em;
}
ul {
margin-top: 0;
margin-bottom: 0;
} }
+59 -22
View File
@@ -78,10 +78,21 @@
} }
.Row.explain { .Row.explain {
display: block; // this stops flex messing up text with inline tags
max-width: 600px; margin-left: 0; max-width: 600px; margin-left: 0;
line-height: 1.2;
@include media($phone) { @include media($phone) {
margin-top: 60px; margin-top: 60px;
} }
&.nomargintop {
margin-top: 12px !important;
}
&.padleft {
}
} }
&.mobopen .Row.explain { &.mobopen .Row.explain {
margin-top: 12px; // default from .Row margin-top: 12px; // default from .Row
@@ -92,43 +103,69 @@
} }
} }
@include media($phone) { @mixin mobcol-base {
.Box.mobcol { h2 {
h2 { position: relative;
position: relative; cursor: pointer;
cursor: pointer; padding: 2px 1.3rem 2px 5px;
padding-right: 1.3rem; margin: 0 -5px 0 -5px;
&::after { &::after {
position: absolute; position: absolute;
right: 0; right: 4px;
content: ''; content: '';
top:50%; top:50%;
font-size: 120%; font-size: 120%;
font-weight: bold; font-weight: bold;
transform: translate(0,-50%) rotate(90deg); 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.fold, .Box.mobcol {
h2 {
padding: 2px 1.3rem 2px 5px;
margin: 0 -5px 0 -5px;
}
&.expanded h2::after { &.expanded h2::after {
transform: translate(-25%,-50%) rotate(-90deg);
margin-bottom: dist(0); margin-bottom: dist(0);
} }
}
.Row { .Box.mobcol {
display: none; @include mobcol-base;
}
#ap-box { #ap-box {
display: none; display: none;
} }
&.expanded { &.expanded {
.Row {
display: flex;
}
#ap-box { #ap-box {
display: block; display: block;
} }
+10
View File
@@ -48,3 +48,13 @@
color: $c-form-label-fg; color: $c-form-label-fg;
} }
} }
#content {
// fade in effect
opacity: 0;
transition: opacity 0.15s ease-in;
}
#content.load {
opacity: 1;
}
+1
View File
@@ -16,6 +16,7 @@
} }
#brand { #brand {
cursor: pointer;
color: $menu-fg; color: $menu-fg;
background: darken($menu-bg, 10%); background: darken($menu-bg, 10%);
font-size: 120%; font-size: 120%;
+4 -6
View File
@@ -33,7 +33,7 @@
} }
} }
.ansiref { .Row table {
&,td,th{ &,td,th{
border: 1px solid #666; border: 1px solid #666;
} }
@@ -45,7 +45,9 @@
th { th {
background-color: rgba(255,255,255,.1); background-color: rgba(255,255,255,.1);
} }
}
.ansiref {
td:nth-child(1) { td:nth-child(1) {
font-family: monospace; font-family: monospace;
} }
@@ -54,11 +56,7 @@
width: 100%; width: 100%;
td:nth-child(1) { td:nth-child(1) {
width: 9em; width: 6em;
}
td:nth-child(2) {
width: 8em;
} }
} }
} }
+158 -13
View File
@@ -1,15 +1,23 @@
body.term { body.term {
h1 { #content {
font-size: fsize(5); padding-left: 0;
@include media($phone) { padding-right: 0;
font-size: fsize(3);
h1 {
font-size: fsize(5);
@include media($phone) {
font-size: fsize(3);
}
} }
// longer duration to load everything in background nicely
transition: opacity 0.25s ease-in;
} }
#screen { #screen {
@include noselect(); @include noselect();
font-family: monospace; font-family: monospace;
font-size: 16pt; font-size: 20px;//16pt; - 16pt causes vertical breaks in line drawing characters??
white-space: nowrap; white-space: nowrap;
background: #111213; background: #111213;
padding: 6px; padding: 6px;
@@ -19,12 +27,11 @@ body.term {
span { span {
white-space: pre; white-space: pre;
cursor: pointer; cursor: pointer;
&:hover {
outline: 1px solid rgba(#ffffff, 0.4); display: inline-block;
@include media($phone) { line-height: 1.15em; // this adjusts the spacing
outline: 0 none; width: 0.6em; // horizontal spacing, estimated to be about right
} overflow: visible;
}
} }
} }
@@ -36,7 +43,7 @@ body.term {
margin: 0 3px; margin: 0 3px;
padding: 8px 5px; padding: 8px 5px;
//width: 18%; //width: 18%;
min-width: 65px; min-width: 62px;
//max-width: 65px; //max-width: 65px;
//min-width: initial; //min-width: initial;
cursor: pointer; cursor: pointer;
@@ -53,16 +60,154 @@ body.term {
text-decoration: underline; text-decoration: underline;
&, &:visited, &:link { &, &:visited, &:link {
color: #2e4d6e; color: #336085;
} }
&:hover { &:hover {
color: #5abfff; color: #5abfff;
} }
} }
.icn-keyboard {
text-decoration: none;
font-size: 150%;
vertical-align: middle;
}
} }
} }
#termwrap { #termwrap {
text-align: center; text-align: center;
} }
#softkb-input {
position: absolute;
top: -9999px;
}
// Tango
.theme-0 {
$term-colors:
#111213, #CC0000, #4E9A06, #C4A000, #3465A4, #75507B, #06989A, #D3D7CF,
#555753, #EF2929, #8AE234, #FCE94F, #729FCF, #AD7FA8, #34E2E2, #EEEEEC;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Linux
.theme-1 {
$term-colors:
#000000, #aa0000, #00aa00, #aa5500, #0000aa, #aa00aa, #00aaaa, #aaaaaa,
#555555, #ff5555, #55ff55, #ffff55, #5555ff, #ff55ff, #55ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// xterm
.theme-2 {
$term-colors:
#000000, #cd0000, #00cd00, #cdcd00, #0000ee, #cd00cd, #00cdcd, #e5e5e5,
#7f7f7f, #ff0000, #00ff00, #ffff00, #5c5cff, #ff00ff, #00ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// rxvt
.theme-3 {
$term-colors:
#000000, #cd0000, #00cd00, #cdcd00, #0000cd, #cd00cd, #00cdcd, #faebd7,
#404040, #ff0000, #00ff00, #ffff00, #0000ff, #ff00ff, #00ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Ambience
.theme-4 {
$term-colors:
#2e3436, #cc0000, #4e9a06, #c4a000, #3465a4, #75507b, #06989a, #d3d7cf,
#555753, #ef2929, #8ae234, #fce94f, #729fcf, #ad7fa8, #34e2e2, #eeeeec;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Solarized
.theme-5 {
$term-colors:
#073642, #dc322f, #859900, #b58900, #268bd2, #d33682, #2aa198, #eee8d5,
#002b36, #cb4b16, #586e75, #657b83, #839496, #6c71c4, #93a1a1, #fdf6e3;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// 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;
display: block;
margin-bottom: 0;
padding-left: $form-label-w;
@include media($phone) {
padding-left: 0;
font-size: 14pt;
}
.colorprev {
display:block;
margin: 0;
cursor: pointer;
}
}
#color-example {
display: inline-block;
padding: 5px;
}
+1
View File
@@ -19,6 +19,7 @@
padding: dist(-2); padding: dist(-2);
margin-bottom: dist(-2); margin-bottom: dist(-2);
margin-top: dist(-2); margin-top: dist(-2);
font-size: 110%;
} }
#ap-noscan { #ap-noscan {
+1 -1
View File
@@ -17,7 +17,7 @@
/** /**
* Helper that retrieves an arg from `connData->getArgs` and stores it in `buff`. Returns 1 on success * 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_HELPER(x) #x
#define STR(x) STR_HELPER(x) #define STR(x) STR_HELPER(x)
+376 -205
View File
@@ -2,35 +2,87 @@
/* #line 1 "user/ansi_parser.rl" */ /* #line 1 "user/ansi_parser.rl" */
#include <esp8266.h> #include <esp8266.h>
#include "ansi_parser.h" #include "ansi_parser.h"
#include "ansi_parser_callbacks.h"
#include "ascii.h"
#include "apars_logging.h"
/* Ragel constants block */ /* Ragel constants block */
/* #line 9 "user/ansi_parser.c" */ /* #line 12 "user/ansi_parser.c" */
static const char _ansi_actions[] = { static const char _ansi_actions[] = {
0, 1, 0, 1, 1, 1, 2, 1, 0, 1, 0, 1, 1, 1, 2, 1,
3, 1, 4, 1, 5, 1, 6, 1, 3, 1, 4, 1, 5, 1, 6, 1,
7, 1, 8, 1, 9, 1, 10, 1, 7, 1, 8, 1, 9, 1, 10, 1,
11, 1, 12 11, 1, 12, 1, 13, 1, 14, 2,
3, 6
}; };
static const char _ansi_eof_actions[] = { static const char _ansi_eof_actions[] = {
0, 13, 13, 13, 13, 13, 13, 13, 0, 1, 1, 1, 1, 1, 1, 1,
13, 13, 13, 13, 13, 13, 0, 0, 1, 1, 0, 0, 0, 0, 0
0
}; };
static const int ansi_start = 1; static const int ansi_start = 1;
static const int ansi_first_final = 14; static const int ansi_first_final = 10;
static const int ansi_error = 0; static const int ansi_error = 0;
static const int ansi_en_CSI_body = 3; static const int ansi_en_CSI_body = 5;
static const int ansi_en_OSC_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; static const int ansi_en_main = 1;
/* #line 8 "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;
// public
volatile u32 ansi_parser_char_cnt = 0;
void ICACHE_FLASH_ATTR
ansi_parser_reset(void) {
if (cs != ansi_start) {
cs = ansi_start;
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 * \brief Linear ANSI chars stream parser
* *
@@ -45,37 +97,115 @@ static const int ansi_en_main = 1;
* \param len - length of the newdata buffer * \param len - length of the newdata buffer
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
ansi_parser(const char *newdata, size_t len) ansi_parser(char newchar)
{ {
static int cs = -1;
// The CSI code is built here // The CSI code is built here
static char csi_leading; //!< Leading char, 0 if none static char leadchar;
static int csi_ni; //!< Number of the active digit static int arg_ni;
static int csi_n[CSI_N_MAX]; //!< Param digits static int arg_cnt;
static char csi_char; //!< CSI action char (end) static int arg[CSI_N_MAX];
static char string_buffer[STR_CHAR_MAX];
static int str_ni;
if (len == 0) len = strlen(newdata); // This is used to detect timeout delay (time since last rx char)
ansi_parser_char_cnt++;
// Load new data to Ragel vars
const char *p = newdata;
const char *eof = NULL;
const char *pe = newdata + len;
// Init Ragel on the first run // Init Ragel on the first run
if (cs == -1) { if (cs == -1) {
/* #line 69 "user/ansi_parser.c" */ /* #line 117 "user/ansi_parser.c" */
{ {
cs = ansi_start; cs = ansi_start;
} }
/* #line 44 "user/ansi_parser.rl" */ /* #line 91 "user/ansi_parser.rl" */
#if DEBUG_ANSI
memset(history, 0, sizeof(history));
#endif
} }
#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 // The parser
/* #line 79 "user/ansi_parser.c" */ /* #line 209 "user/ansi_parser.c" */
{ {
const char *_acts; const char *_acts;
unsigned int _nacts; unsigned int _nacts;
@@ -87,257 +217,296 @@ ansi_parser(const char *newdata, size_t len)
_resume: _resume:
switch ( cs ) { switch ( cs ) {
case 1: case 1:
if ( (*p) == 27 )
goto tr1;
goto tr0;
case 2:
switch( (*p) ) { switch( (*p) ) {
case 55: goto tr3; case 7: goto tr1;
case 56: goto tr4; case 27: goto tr2;
case 91: goto tr5;
case 93: goto tr6;
case 99: goto tr7;
} }
goto tr2; goto tr0;
case 0: case 0:
goto _out; goto _out;
case 14: case 2:
if ( (*p) == 27 ) switch( (*p) ) {
goto tr1; case 32: goto tr3;
goto tr0; 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: 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 tr10;
goto tr1;
case 5:
switch( (*p) ) {
case 59: goto tr13;
case 64: goto tr14;
}
if ( (*p) < 60 ) { if ( (*p) < 60 ) {
if ( (*p) > 47 ) { if ( (*p) > 47 ) {
if ( 48 <= (*p) && (*p) <= 57 ) if ( 48 <= (*p) && (*p) <= 57 )
goto tr9; goto tr12;
} else if ( (*p) >= 32 ) } else if ( (*p) >= 32 )
goto tr8; goto tr11;
} else if ( (*p) > 64 ) { } else if ( (*p) > 63 ) {
if ( (*p) > 90 ) { if ( (*p) > 90 ) {
if ( 97 <= (*p) && (*p) <= 122 ) if ( 96 <= (*p) && (*p) <= 122 )
goto tr11; goto tr15;
} else if ( (*p) >= 65 ) } else if ( (*p) >= 65 )
goto tr11; goto tr15;
} 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;
} else } else
goto tr11; goto tr11;
goto tr2; goto tr1;
case 15:
goto tr2;
case 5:
switch( (*p) ) {
case 70: goto tr12;
case 87: goto tr13;
}
goto tr2;
case 6: case 6:
if ( (*p) == 82 ) if ( (*p) == 59 )
goto tr14; goto tr13;
goto tr2; 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: case 7:
switch( (*p) ) { switch( (*p) ) {
case 7: goto tr15; case 7: goto tr17;
case 27: goto tr16; case 27: goto tr18;
} }
goto tr2; goto tr16;
case 16: case 13:
goto tr2; goto tr1;
case 8: case 8:
if ( (*p) == 92 ) if ( (*p) == 92 )
goto tr15; goto tr17;
goto tr2; goto tr1;
case 9: case 9:
if ( 48 <= (*p) && (*p) <= 57 )
goto tr17;
goto tr2;
case 10:
if ( (*p) == 59 )
goto tr18;
if ( 48 <= (*p) && (*p) <= 57 )
goto tr17;
goto tr2;
case 11:
if ( 48 <= (*p) && (*p) <= 57 )
goto tr19;
goto tr2;
case 12:
switch( (*p) ) { switch( (*p) ) {
case 7: goto tr20; case 7: goto tr1;
case 27: goto tr21; case 27: goto tr1;
} }
if ( 48 <= (*p) && (*p) <= 57 ) goto tr19;
goto tr19; case 14:
goto tr2; goto tr1;
case 13:
if ( (*p) == 92 )
goto tr20;
goto tr2;
} }
tr2: cs = 0; goto f0; tr1: cs = 0; goto f0;
tr0: cs = 1; goto f1; tr0: cs = 1; goto f1;
tr1: cs = 2; goto _again; tr2: cs = 2; goto _again;
tr8: cs = 4; goto f7; tr3: cs = 3; goto _again;
tr9: cs = 4; goto f8; tr4: cs = 4; goto _again;
tr10: cs = 4; goto f9; tr11: cs = 6; goto f8;
tr12: cs = 6; goto _again; tr12: cs = 6; goto f9;
tr14: cs = 7; goto _again; tr13: cs = 6; goto f10;
tr16: cs = 8; goto _again; tr16: cs = 7; goto f13;
tr13: cs = 9; goto _again; tr18: cs = 8; goto _again;
tr17: cs = 10; goto f8; tr5: cs = 10; goto f2;
tr18: cs = 11; goto f9; tr6: cs = 10; goto f3;
tr19: cs = 12; goto f8; tr7: cs = 10; goto f4;
tr21: cs = 13; goto _again; tr8: cs = 10; goto f5;
tr3: cs = 14; goto f2; tr9: cs = 10; goto f6;
tr4: cs = 14; goto f3; tr10: cs = 10; goto f7;
tr5: cs = 14; goto f4; tr15: cs = 11; goto f12;
tr6: cs = 14; goto f5; tr14: cs = 12; goto f11;
tr7: cs = 14; goto f6; tr17: cs = 13; goto f14;
tr11: cs = 15; goto f10; tr19: cs = 14; goto f15;
tr15: cs = 16; goto f11;
tr20: cs = 16; goto f12;
f1: _acts = _ansi_actions + 1; goto execFuncs; f0: _acts = _ansi_actions + 1; goto execFuncs;
f4: _acts = _ansi_actions + 3; goto execFuncs; f1: _acts = _ansi_actions + 3; goto execFuncs;
f7: _acts = _ansi_actions + 5; goto execFuncs; f5: _acts = _ansi_actions + 5; goto execFuncs;
f8: _acts = _ansi_actions + 7; goto execFuncs; f8: _acts = _ansi_actions + 7; goto execFuncs;
f9: _acts = _ansi_actions + 9; goto execFuncs; f9: _acts = _ansi_actions + 9; goto execFuncs;
f10: _acts = _ansi_actions + 11; goto execFuncs; f10: _acts = _ansi_actions + 11; goto execFuncs;
f0: _acts = _ansi_actions + 13; goto execFuncs; f12: _acts = _ansi_actions + 13; goto execFuncs;
f5: _acts = _ansi_actions + 15; goto execFuncs; f4: _acts = _ansi_actions + 15; goto execFuncs;
f11: _acts = _ansi_actions + 17; goto execFuncs; f13: _acts = _ansi_actions + 17; goto execFuncs;
f12: _acts = _ansi_actions + 19; goto execFuncs; f14: _acts = _ansi_actions + 19; goto execFuncs;
f6: _acts = _ansi_actions + 21; goto execFuncs; f7: _acts = _ansi_actions + 21; goto execFuncs;
f2: _acts = _ansi_actions + 23; goto execFuncs; f3: _acts = _ansi_actions + 23; goto execFuncs;
f3: _acts = _ansi_actions + 25; goto execFuncs; f6: _acts = _ansi_actions + 25; goto execFuncs;
f2: _acts = _ansi_actions + 27; goto execFuncs;
f15: _acts = _ansi_actions + 29; goto execFuncs;
f11: _acts = _ansi_actions + 31; goto execFuncs;
execFuncs: execFuncs:
_nacts = *_acts++; _nacts = *_acts++;
while ( _nacts-- > 0 ) { while ( _nacts-- > 0 ) {
switch ( *_acts++ ) { switch ( *_acts++ ) {
case 0: case 0:
/* #line 54 "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; break;
case 1: case 1:
/* #line 61 "user/ansi_parser.rl" */ /* #line 194 "user/ansi_parser.rl" */
{ {
/* Reset the CSI builder */ if ((*p) != 0) {
csi_leading = csi_char = 0; apars_handle_plainchar((*p));
csi_ni = 0;
/* Zero out digits */
for(int i = 0; i < CSI_N_MAX; i++) {
csi_n[i] = 0;
} }
{cs = 3;goto _again;}
} }
break; break;
case 2: case 2:
/* #line 74 "user/ansi_parser.rl" */ /* #line 202 "user/ansi_parser.rl" */
{ {
csi_leading = (*p); // Reset the CSI builder
} leadchar = NUL;
break; arg_ni = 0;
case 3: arg_cnt = 0;
/* #line 78 "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 85 "user/ansi_parser.rl" */
{
csi_ni++;
}
break;
case 5:
/* #line 89 "user/ansi_parser.rl" */
{
csi_char = (*p);
apars_handle_CSI(csi_leading, csi_n, csi_char); // Zero out digits
{cs = 1;goto _again;}
}
break;
case 6:
/* #line 97 "user/ansi_parser.rl" */
{
apars_handle_badseq();
{cs = 1;goto _again;}
}
break;
case 7:
/* #line 114 "user/ansi_parser.rl" */
{
csi_ni = 0;
// we reuse the CSI numeric buffer
for(int i = 0; i < CSI_N_MAX; i++) { for(int i = 0; i < CSI_N_MAX; i++) {
csi_n[i] = 0; arg[i] = 0;
} }
{cs = 5;goto _again;} {cs = 5;goto _again;}
} }
break; break;
case 8: case 3:
/* #line 125 "user/ansi_parser.rl" */ /* #line 216 "user/ansi_parser.rl" */
{ {
apars_handle_OSC_FactoryReset(); 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;} {cs = 1;goto _again;}
} }
break; break;
case 9: case 7:
/* #line 130 "user/ansi_parser.rl" */ /* #line 245 "user/ansi_parser.rl" */
{ {
apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]); 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;} {cs = 1;goto _again;}
} }
break; break;
case 10: case 10:
/* #line 141 "user/ansi_parser.rl" */ /* #line 270 "user/ansi_parser.rl" */
{ {
// Reset screen apars_handle_hash_cmd((*p));
apars_handle_RESET_cmd();
{cs = 1;goto _again;} {cs = 1;goto _again;}
} }
break; break;
case 11: case 11:
/* #line 147 "user/ansi_parser.rl" */ /* #line 275 "user/ansi_parser.rl" */
{ {
apars_handle_saveCursorAttrs(); apars_handle_short_cmd((*p));
{cs = 1;goto _again;} {cs = 1;goto _again;}
} }
break; break;
case 12: case 12:
/* #line 152 "user/ansi_parser.rl" */ /* #line 280 "user/ansi_parser.rl" */
{ {
apars_handle_restoreCursorAttrs(); apars_handle_space_cmd((*p));
{cs = 1;goto _again;} {cs = 1;goto _again;}
} }
break; break;
/* #line 341 "user/ansi_parser.c" */ case 13:
/* #line 287 "user/ansi_parser.rl" */
{
leadchar = (*p);
{cs = 9;goto _again;}
}
break;
case 14:
/* #line 292 "user/ansi_parser.rl" */
{
apars_handle_chs_designate(leadchar, (*p));
{cs = 1;goto _again;}
}
break;
/* #line 510 "user/ansi_parser.c" */
} }
} }
goto _again; goto _again;
@@ -354,16 +523,18 @@ _again:
unsigned int __nacts = (unsigned int) *__acts++; unsigned int __nacts = (unsigned int) *__acts++;
while ( __nacts-- > 0 ) { while ( __nacts-- > 0 ) {
switch ( *__acts++ ) { switch ( *__acts++ ) {
case 6: case 0:
/* #line 97 "user/ansi_parser.rl" */ /* #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 ) {cs = 1; if ( p == pe )
goto _test_eof; goto _test_eof;
goto _again;} goto _again;}
} }
break; break;
/* #line 367 "user/ansi_parser.c" */ /* #line 538 "user/ansi_parser.c" */
} }
} }
} }
@@ -371,6 +542,6 @@ goto _again;}
_out: {} _out: {}
} }
/* #line 171 "user/ansi_parser.rl" */ /* #line 315 "user/ansi_parser.rl" */
} }
+7 -13
View File
@@ -3,17 +3,9 @@
#include <stdlib.h> #include <stdlib.h>
// Max nr of CSI parameters void ansi_parser_reset(void);
#define CSI_N_MAX 3
extern void apars_handle_badseq(void); extern volatile u32 ansi_parser_char_cnt;
extern void apars_handle_CSI(char leadchar, int *params, char keychar);
extern void apars_handle_RESET_cmd(void);
extern void apars_handle_plainchar(char c);
extern void apars_handle_OSC_FactoryReset(void);
extern void apars_handle_OSC_SetScreenSize(int rows, int cols);
extern void apars_handle_saveCursorAttrs(void);
extern void apars_handle_restoreCursorAttrs(void);
/** /**
* \brief Linear ANSI chars stream parser * \brief Linear ANSI chars stream parser
@@ -25,9 +17,11 @@ extern void apars_handle_restoreCursorAttrs(void);
* \attention -> but always check the Ragel output for 'p--' * \attention -> but always check the Ragel output for 'p--'
* or 'p -=', that means trouble. * or 'p -=', that means trouble.
* *
* \param newdata - array of new chars to process * \param newchar - received char
* \param len - length of the newdata buffer
*/ */
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 #endif // ANSI_PARSER_H
+218 -74
View File
@@ -1,5 +1,8 @@
#include <esp8266.h> #include <esp8266.h>
#include "ansi_parser.h" #include "ansi_parser.h"
#include "ansi_parser_callbacks.h"
#include "ascii.h"
#include "apars_logging.h"
/* Ragel constants block */ /* Ragel constants block */
%%{ %%{
@@ -7,6 +10,54 @@
write data; 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;
// public
volatile u32 ansi_parser_char_cnt = 0;
void ICACHE_FLASH_ATTR
ansi_parser_reset(void) {
if (cs != ansi_start) {
cs = ansi_start;
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 * \brief Linear ANSI chars stream parser
* *
@@ -21,152 +72,245 @@
* \param len - length of the newdata buffer * \param len - length of the newdata buffer
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
ansi_parser(const char *newdata, size_t len) ansi_parser(char newchar)
{ {
static int cs = -1;
// The CSI code is built here // The CSI code is built here
static char csi_leading; //!< Leading char, 0 if none static char leadchar;
static int csi_ni; //!< Number of the active digit static int arg_ni;
static int csi_n[CSI_N_MAX]; //!< Param digits static int arg_cnt;
static char csi_char; //!< CSI action char (end) static int arg[CSI_N_MAX];
static char string_buffer[STR_CHAR_MAX];
static int str_ni;
if (len == 0) len = strlen(newdata); // This is used to detect timeout delay (time since last rx char)
ansi_parser_char_cnt++;
// Load new data to Ragel vars
const char *p = newdata;
const char *eof = NULL;
const char *pe = newdata + len;
// Init Ragel on the first run // Init Ragel on the first run
if (cs == -1) { if (cs == -1) {
%% write init; %% write init;
#if DEBUG_ANSI
memset(history, 0, sizeof(history));
#endif
} }
#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 // The parser
%%{ %%{
#/*
ESC = 27; ESC = 27;
NOESC = (any - ESC); NOESC = (any - ESC - 7);
TOK_ST = ESC '\\'; # String terminator - used for OSC commands TOK_ST = ESC '\\'; # String terminator - used for OSC commands
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 --- # --- Regular characters to be printed ---
action plain_char { action plain_char {
apars_handle_plainchar(fc); if (fc != 0) {
apars_handle_plainchar(fc);
}
} }
# --- CSI CSI commands (Select Graphic Rendition) --- # --- CSI commands ---
# Text color & style
action CSI_start { action CSI_start {
/* Reset the CSI builder */ // Reset the CSI builder
csi_leading = csi_char = 0; leadchar = NUL;
csi_ni = 0; arg_ni = 0;
arg_cnt = 0;
/* Zero out digits */ // Zero out digits
for(int i = 0; i < CSI_N_MAX; i++) { for(int i = 0; i < CSI_N_MAX; i++) {
csi_n[i] = 0; arg[i] = 0;
} }
fgoto CSI_body; fgoto CSI_body;
} }
action CSI_leading { action CSI_leading {
csi_leading = fc; leadchar = fc;
} }
action CSI_digit { action CSI_digit {
/* x10 + digit */ if (arg_cnt == 0) arg_cnt = 1;
if (csi_ni < CSI_N_MAX) { // x10 + digit
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 { 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 { action CSI_end {
csi_char = fc; apars_handle_csi(leadchar, arg, arg_cnt, fc);
apars_handle_CSI(csi_leading, csi_n, csi_char);
fgoto main;
}
action errBadSeq {
apars_handle_badseq();
fgoto main;
}
action back2main {
fgoto main; fgoto main;
} }
CSI_body := ((32..47|60..64) @CSI_leading)? CSI_body := ((32..47|60..64) @CSI_leading)?
((digit @CSI_digit)* ';' @CSI_semi)* ((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) --- action StrCmd_start {
# Module parametrisation leadchar = fc;
str_ni = 0;
action OSC_start { string_buffer[0] = '\0';
csi_ni = 0; inside_string = true;
fgoto STRCMD_body;
// we reuse the CSI numeric buffer
for(int i = 0; i < CSI_N_MAX; i++) {
csi_n[i] = 0;
}
fgoto OSC_body;
} }
action OSC_fr { action StrCmd_char {
apars_handle_OSC_FactoryReset(); 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; fgoto main;
} }
action OSC_resize { # According to the spec, ESC should be allowed inside the string sequence.
apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]); # We disallow ESC for simplicity, as it's hardly ever used.
STRCMD_body := ((NOESC @StrCmd_char)* STR_END @StrCmd_end) $!errBadSeq;
# --- Single character ESC ---
action HASH_code {
apars_handle_hash_cmd(fc);
fgoto main; fgoto main;
} }
OSC_body := ( action SHORT_code {
("FR" ('\a' | ESC '\\') @OSC_fr) | apars_handle_short_cmd(fc);
('W' (digit @CSI_digit)+ ';' @CSI_semi (digit @CSI_digit)+ ('\a' | ESC '\\') @OSC_resize)
) $!errBadSeq;
action RESET_cmd {
// Reset screen
apars_handle_RESET_cmd();
fgoto main; fgoto main;
} }
action CSI_SaveCursorAttrs { action SPACE_cmd {
apars_handle_saveCursorAttrs(); apars_handle_space_cmd(fc);
fgoto main; fgoto main;
} }
action CSI_RestoreCursorAttrs { # --- Charset selection ---
apars_handle_restoreCursorAttrs();
action CharsetCmd_start {
leadchar = fc;
fgoto charsetcmd_body;
}
action CharsetCmd_end {
apars_handle_chs_designate(leadchar, fc);
fgoto main; fgoto main;
} }
charsetcmd_body := (NOESC @CharsetCmd_end) $!errBadSeq;
# --- Main parser loop --- # --- Main parser loop ---
main := main :=
( (
(NOESC @plain_char)* ESC ( (NOESC @plain_char)* ESC (
'[' @CSI_start | ('[' @CSI_start) |
']' @OSC_start | ([_\]Pk\^X] @StrCmd_start) |
'c' @RESET_cmd | ('#' digit @HASH_code) |
'7' @CSI_SaveCursorAttrs | (([a-zA-Z0-9=<>~}|@\\] - [PXk]) @SHORT_code) |
'8' @CSI_RestoreCursorAttrs ([()*+-./%] @CharsetCmd_start) |
(' ' [FGLMN] @SPACE_cmd)
) )
)+ $!errBadSeq; )+ $!errBadSeq;
write exec; write exec;
#*/
}%% }%%
} }
+18 -270
View File
@@ -5,297 +5,45 @@
*/ */
#include <esp8266.h> #include <esp8266.h>
#include <helpers.h>
#include "ansi_parser_callbacks.h" #include "ansi_parser_callbacks.h"
#include "screen.h"
#include "ansi_parser.h"
#include "uart_driver.h" #include "uart_driver.h"
#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 void ICACHE_FLASH_ATTR
apars_handle_plainchar(char c) apars_respond(const char *str)
{ {
screen_putchar(c); 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)
{
warn("Invalid escape sequence discarded.");
}
/**
* \brief Handle fully received CSI ANSI sequence
* \param leadchar - private range leading character, 0 if none
* \param params - array of CSI_N_MAX ints holding the numeric arguments
* \param keychar - the char terminating the sequence
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
apars_handle_CSI(char leadchar, int *params, char keychar) apars_handle_bel(void)
{ {
/* send_beep();
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_set_fg(7);
screen_set_bg(0);
break; // cannot combine reset with others
}
else if (n >= 30 && n <= 37) screen_set_fg(n-30); // ANSI normal fg
else if (n >= 40 && n <= 47) screen_set_bg(n-40); // ANSI normal bg
else if (n == 39) screen_set_fg(7); // default fg
else if (n == 49) screen_set_bg(0); // default bg
else if (n == 7) screen_inverse(1); // inverse
else if (n == 27) screen_inverse(0); // positive
else if (n == 1) screen_set_bright_fg(); // ANSI bold = bright fg
else if (n >= 90 && n <= 97) screen_set_fg(n-90+8); // AIX bright fg
else if (n >= 100 && n <= 107) screen_set_bg(n-100+8); // AIX bright bg
}
break;
}
}
void ICACHE_FLASH_ATTR apars_handle_saveCursorAttrs(void)
{
screen_cursor_save(1);
}
void ICACHE_FLASH_ATTR apars_handle_restoreCursorAttrs(void)
{
screen_cursor_restore(1);
} }
/** /**
* \brief Handle a request to reset the display device * Send to uart the answerback message
*/ */
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
apars_handle_RESET_cmd(void) apars_handle_enq(void)
{ {
// XXX maybe user wanted to reset the module instead? // version encased in SOS and ST
screen_reset(); apars_respond("\x1bXESPTerm " FIRMWARE_VERSION "\x1b\\");
} }
/**
* Handle a factory reset request
*/
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
apars_handle_OSC_FactoryReset(void) apars_handle_tab(void)
{ {
warn("-------- Factory reset --------"); screen_tab_forward(1);
dbg("Switching to Client+AP mode");
// Send acknowledgement message to UART0
// User is performing this manually, so we can just print it as string
UART_WriteString(UART0, "\r\nFACTORY RESET\r\n", UART_TIMEOUT_US);
// Disconnect from AP if connected
int opmode = wifi_get_opmode();
if (opmode != SOFTAP_MODE) {
wifi_station_disconnect();
}
// Both must be enabled so we can manipulate their settings
wifi_set_opmode(STATIONAP_MODE);
// --- AP config ---
dbg("AP WiFi channel: 6");
struct softap_config apconf;
wifi_softap_get_config(&apconf);
apconf.authmode=AUTH_OPEN; // Disable access protection
apconf.channel=6; // Reset channel; user may have set bad channel in the UI
// generate unique AP name
u8 mac[6];
wifi_get_macaddr(SOFTAP_IF, mac);
sprintf((char*)apconf.ssid, "TERM-%02X%02X%02X", mac[3], mac[4], mac[5]);
apconf.ssid_len = (u8)strlen((char*)apconf.ssid);
info("New AP name: %s", (char*)apconf.ssid);
// --- Station ---
dbg("Erasing stored WiFi credentials...");
struct station_config staconf;
wifi_station_get_config(&staconf);
// clear info about SSID
staconf.ssid[0]=0;
staconf.bssid_set=0;
staconf.password[0]=0;
dbg("Commiting changes...");
wifi_softap_set_config(&apconf);
wifi_station_set_config(&staconf);
UART_WriteString(UART0, "Factory Reset complete, device reset.\r\n\r\n", UART_TIMEOUT_US);
info("*** FACTORY RESET COMPLETE ***");
dbg("Device reset...");
// Reboot to clean STA+AP mode with Channel X & reset AP SSID.
system_restart();
}
/**
* Handle a screen resize request
*/
void ICACHE_FLASH_ATTR
apars_handle_OSC_SetScreenSize(int rows, int cols)
{
info("OSC: Set screen size to %d x %d", rows, cols);
screen_resize(rows, cols);
} }
+14 -8
View File
@@ -1,13 +1,19 @@
#ifndef ANSI_PARSER_CALLBACKS_H #ifndef ANSI_PARSER_CALLBACKS_H
#define ANSI_PARSER_CALLBACKS_H #define ANSI_PARSER_CALLBACKS_H
void apars_handle_badseq(void); #include "apars_csi.h"
void apars_handle_CSI(char leadchar, int *params, char keychar); #include "apars_dcs.h"
void apars_handle_RESET_cmd(void); #include "apars_osc.h"
void apars_handle_plainchar(char c); #include "apars_string.h"
void apars_handle_OSC_FactoryReset(void); #include "apars_short.h"
void apars_handle_OSC_SetScreenSize(int rows, int cols); #include "apars_utf8.h"
void apars_handle_saveCursorAttrs(void);
void apars_handle_restoreCursorAttrs(void); 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 #endif //ESP_VT100_FIRMWARE_ANSI_PARSER_CALLBACKS_H
+673
View File
@@ -0,0 +1,673 @@
//
// 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)) {
// TODO mouse
// 1000 - C11 mouse - Send Mouse X & Y on button press and release.
// 1001 - Hilite mouse tracking
// 1002 - Cell Motion Mouse Tracking
// 1003 - All Motion Mouse Tracking
// 1004 - Send FocusIn/FocusOut events
// 1005 - Enable UTF-8 Mouse Mode
// 1006 - SGR mouse mode
ansi_noimpl("Mouse tracking");
}
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 ??
}
}
+10
View File
@@ -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
+89
View File
@@ -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();
}
}
+10
View File
@@ -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
+37
View File
@@ -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
+65
View File
@@ -0,0 +1,65 @@
//
// 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 set terminal button label
* @param num - button number 1-5
* @param str - button text
*/
static void ICACHE_FLASH_ATTR
set_button_text(int num, const char *str)
{
strncpy(termconf_scratch.btn[num-1], str, TERM_BTN_LEN);
screen_notifyChange(CHANGE_LABELS);
}
/**
* 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
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();
}
}
+10
View File
@@ -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
+179
View File
@@ -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);
}
}
+14
View File
@@ -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
+52
View File
@@ -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();
}
}
+10
View File
@@ -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
+84
View File
@@ -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();
}
+11
View File
@@ -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
+49
View File
@@ -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
-178
View File
@@ -1,178 +0,0 @@
/*
Cgi/template routines for configuring non-wifi settings
*/
#include <esp8266.h>
#include "cgi_appcfg.h"
#include "persist.h"
#include "screen.h"
#include "helpers.h"
#define SET_REDIR_SUC "/cfg/app"
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
/**
* Universal CGI endpoint to set Terminal params.
*/
httpd_cgi_state ICACHE_FLASH_ATTR
cgiAppCfgSetParams(HttpdConnData *connData)
{
char buff[50];
char redir_url_buf[300];
char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
// width and height must always go together so we can do max size validation
if (GET_ARG("term_width")) {
dbg("Default screen width: %s", buff);
int w = atoi(buff);
if (w > 1) {
if (GET_ARG("term_height")) {
dbg("Default screen height: %s", buff);
int h = atoi(buff);
if (h > 1) {
if (w * h <= MAX_SCREEN_SIZE) {
termconf->width = w;
termconf->height = h;
} else {
warn("Bad dimensions: %d x %d (total %d)", w, h, w*h);
redir_url += sprintf(redir_url, "term_width,term_height,");
}
} else {
warn("Bad height: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_width,");
}
} else {
warn("Missing height arg!");
// this wont happen normally when the form is used
redir_url += sprintf(redir_url, "term_width,term_height,");
}
} else {
warn("Bad width: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_width,");
}
}
if (GET_ARG("default_bg")) {
dbg("Screen default BG: %s", buff);
int color = atoi(buff);
if (color >= 0 && color < 16) {
termconf->default_bg = (u8) color;
} else {
warn("Bad color %s", buff);
redir_url += sprintf(redir_url, "default_bg,");
}
}
if (GET_ARG("default_fg")) {
dbg("Screen default FG: %s", buff);
int color = atoi(buff);
if (color >= 0 && color < 16) {
termconf->default_fg = (u8) color;
} else {
warn("Bad color %s", buff);
redir_url += sprintf(redir_url, "default_fg,");
}
}
if (GET_ARG("term_title")) {
dbg("Terminal title default text: \"%s\"", buff);
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in
}
if (GET_ARG("btn1")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn1, buff, 10);
}
if (GET_ARG("btn2")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn2, buff, 10);
}
if (GET_ARG("btn3")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn3, buff, 10);
}
if (GET_ARG("btn4")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn4, buff, 10);
}
if (GET_ARG("btn5")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn5, buff, 10);
}
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
info("Set app params - success, saving...");
terminal_apply_settings();
persist_store();
httpdRedirect(connData, SET_REDIR_SUC);
} else {
warn("Some settings did not validate, asking for correction");
// Some errors, appended to the URL as ?err=
httpdRedirect(connData, redir_url_buf);
}
return HTTPD_CGI_DONE;
}
httpd_cgi_state ICACHE_FLASH_ATTR
tplAppCfg(HttpdConnData *connData, char *token, void **arg)
{
#define BUFLEN 100
char buff[BUFLEN];
if (token == NULL) {
// We're done
return HTTPD_CGI_DONE;
}
strcpy(buff, ""); // fallback
if (streq(token, "term_width")) {
sprintf(buff, "%d", termconf->width);
}
else if (streq(token, "term_height")) {
sprintf(buff, "%d", termconf->height);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
}
else if (streq(token, "default_fg")) {
sprintf(buff, "%d", termconf->default_fg);
}
else if (streq(token, "term_title")) {
strncpy_safe(buff, termconf->title, BUFLEN);
}
else if (streq(token, "btn1")) {
strncpy_safe(buff, termconf->btn1, BUFLEN);
}
else if (streq(token, "btn2")) {
strncpy_safe(buff, termconf->btn2, BUFLEN);
}
else if (streq(token, "btn3")) {
strncpy_safe(buff, termconf->btn3, BUFLEN);
}
else if (streq(token, "btn4")) {
strncpy_safe(buff, termconf->btn4, BUFLEN);
}
else if (streq(token, "btn5")) {
strncpy_safe(buff, termconf->btn5, BUFLEN);
}
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
-9
View File
@@ -1,9 +0,0 @@
#ifndef CGIAPPCFG_H
#define CGIAPPCFG_H
#include "httpd.h"
httpd_cgi_state cgiAppCfgSetParams(HttpdConnData *connData);
httpd_cgi_state tplAppCfg(HttpdConnData *connData, char *token, void **arg);
#endif
+41 -40
View File
@@ -1,10 +1,11 @@
#include <esp8266.h> #include <esp8266.h>
#include <httpd.h> #include <httpd.h>
#include <esp_sdk_ver.h> #include <esp_sdk_ver.h>
#include <httpdespfs.h>
#include "cgi_main.h" #include "cgi_main.h"
#include "screen.h" #include "screen.h"
#include "user_main.h" #include "version.h"
#include "helpers.h" #include "helpers.h"
/** /**
@@ -19,70 +20,70 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token
{ {
if (token == NULL) { if (token == NULL) {
// Release data object // Release data object
screenSerializeToBuffer(NULL, 0, arg);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
const int bufsiz = 512; char buff[150];
char buff[bufsiz];
if (streq(token, "term_title")) { if (streq(token, "labels_seq")) {
httpdSend(connData, termconf->title, -1); screenSerializeLabelsToBuffer(buff, 150);
tplSend(connData, buff, -1);
} }
else if (streq(token, "btn1")) { else if (streq(token, "theme")) {
httpdSend(connData, termconf->btn1, -1); sprintf(buff, "%d", termconf->theme);
} tplSend(connData, buff, -1);
else if (streq(token, "btn2")) {
httpdSend(connData, termconf->btn2, -1);
}
else if (streq(token, "btn3")) {
httpdSend(connData, termconf->btn3, -1);
}
else if (streq(token, "btn4")) {
httpdSend(connData, termconf->btn4, -1);
}
else if (streq(token, "btn5")) {
httpdSend(connData, termconf->btn5, -1);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
httpdSend(connData, buff, -1);
}
else if (streq(token, "default_fg")) {
sprintf(buff, "%d", termconf->default_fg);
httpdSend(connData, buff, -1);
}
else if (streq(token, "screenData")) {
httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, arg);
httpdSend(connData, buff, -1);
return cont;
} }
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
httpd_cgi_state ICACHE_FLASH_ATTR
cgiTermInitialImage(HttpdConnData *connData)
{
const int bufsiz = 512;
char buff[bufsiz];
if (connData->conn == NULL) {
//Connection aborted. Clean up.
// Release data object
screenSerializeToBuffer(NULL, 0, &connData->cgiData);
return HTTPD_CGI_DONE;
}
if (connData->cgiData == NULL) {
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/octet-stream");
httpdEndHeaders(connData);
}
httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, &connData->cgiData);
httpdSend(connData, buff, -1); // no encode
return cont;
}
/** "About" page */ /** "About" page */
httpd_cgi_state ICACHE_FLASH_ATTR tplAbout(HttpdConnData *connData, char *token, void **arg) httpd_cgi_state ICACHE_FLASH_ATTR
tplAbout(HttpdConnData *connData, char *token, void **arg)
{ {
if (token == NULL) return HTTPD_CGI_DONE; if (token == NULL) return HTTPD_CGI_DONE;
if (streq(token, "vers_fw")) { if (streq(token, "vers_fw")) {
httpdSend(connData, FIRMWARE_VERSION, -1); tplSend(connData, FIRMWARE_VERSION, -1);
} }
else if (streq(token, "date")) { else if (streq(token, "date")) {
httpdSend(connData, __DATE__, -1); tplSend(connData, __DATE__, -1);
} }
else if (streq(token, "time")) { else if (streq(token, "time")) {
httpdSend(connData, __TIME__, -1); tplSend(connData, __TIME__, -1);
} }
else if (streq(token, "vers_httpd")) { else if (streq(token, "vers_httpd")) {
httpdSend(connData, HTTPDVER, -1); tplSend(connData, httpdGetVersion(), -1);
} }
else if (streq(token, "vers_sdk")) { else if (streq(token, "vers_sdk")) {
httpdSend(connData, STR(ESP_SDK_VERSION), -1); tplSend(connData, STR(ESP_SDK_VERSION), -1);
} }
else if (streq(token, "githubrepo")) { else if (streq(token, "githubrepo")) {
httpdSend(connData, TERMINAL_GITHUB_REPO, -1); tplSend(connData, TERMINAL_GITHUB_REPO, -1);
} }
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
+1
View File
@@ -3,5 +3,6 @@
httpd_cgi_state tplScreen(HttpdConnData *connData, char *token, void **arg); httpd_cgi_state tplScreen(HttpdConnData *connData, char *token, void **arg);
httpd_cgi_state tplAbout(HttpdConnData *connData, char *token, void **arg); httpd_cgi_state tplAbout(HttpdConnData *connData, char *token, void **arg);
httpd_cgi_state cgiTermInitialImage(HttpdConnData *connData);
#endif // CGI_MAIN_H #endif // CGI_MAIN_H
+5 -4
View File
@@ -3,6 +3,7 @@ configuring the network settings
*/ */
#include <esp8266.h> #include <esp8266.h>
#include <httpdespfs.h>
#include "cgi_network.h" #include "cgi_network.h"
#include "wifimgr.h" #include "wifimgr.h"
#include "persist.h" #include "persist.h"
@@ -27,9 +28,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
{ {
static ETSTimer timer; static ETSTimer timer;
char buff[50]; char buff[20];
char redir_url_buf[100];
char redir_url_buf[300];
char *redir_url = redir_url_buf; char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR); redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf // we'll test if anything was printed by looking for \0 in failed_keys_buf
@@ -200,7 +201,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
//Template code for the WLAN page. //Template code for the WLAN page.
httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *token, void **arg) httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *token, void **arg)
{ {
char buff[100]; char buff[20];
u8 mac[6]; u8 mac[6];
if (token == NULL) { if (token == NULL) {
@@ -246,6 +247,6 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *toke
sprintf(buff, MACSTR, MAC2STR(mac)); sprintf(buff, MACSTR, MAC2STR(mac));
} }
httpdSend(connData, buff, -1); tplSend(connData, buff, -1);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
+1 -1
View File
@@ -20,7 +20,7 @@ verify_admin_pw(const char *pw)
httpd_cgi_state ICACHE_FLASH_ATTR httpd_cgi_state ICACHE_FLASH_ATTR
cgiPersistWriteDefaults(HttpdConnData *connData) cgiPersistWriteDefaults(HttpdConnData *connData)
{ {
char buff[50]; char buff[PASSWORD_LEN];
if (connData->conn == NULL) { if (connData->conn == NULL) {
//Connection aborted. Clean up. //Connection aborted. Clean up.
-20
View File
@@ -1,20 +0,0 @@
#include <esp8266.h>
#include <httpd.h>
#include "cgi_ping.h"
httpd_cgi_state ICACHE_FLASH_ATTR cgiPing(HttpdConnData *connData)
{
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
httpdSend(connData, "pong\n", -1);
return HTTPD_CGI_DONE;
}
-11
View File
@@ -1,11 +0,0 @@
#ifndef CGI_PING_H
#define CGI_PING_H
#include <esp8266.h>
#include <httpd.h>
// this is used by the UI to check if server is already restarted and working again.
httpd_cgi_state cgiPing(HttpdConnData *connData);
#endif // CGI_PING_H
-31
View File
@@ -1,31 +0,0 @@
#include <esp8266.h>
#include <httpd.h>
#include "cgi_reset.h"
static ETSTimer tmr;
static void ICACHE_FLASH_ATTR tmrCb(void *arg)
{
system_restart();
}
httpd_cgi_state ICACHE_FLASH_ATTR cgiResetDevice(HttpdConnData *connData)
{
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
os_timer_disarm(&tmr);
os_timer_setfn(&tmr, tmrCb, NULL);
os_timer_arm(&tmr, 100, false);
httpdSend(connData, "system reset\n", -1);
return HTTPD_CGI_DONE;
}
-9
View File
@@ -1,9 +0,0 @@
#ifndef CGI_RESET_H
#define CGI_RESET_H
#include <esp8266.h>
#include <httpd.h>
httpd_cgi_state cgiResetDevice(HttpdConnData *connData);
#endif // CGI_RESET_H
+113 -16
View File
@@ -5,12 +5,49 @@
#include "cgi_sockets.h" #include "cgi_sockets.h"
#include "uart_driver.h" #include "uart_driver.h"
#include "screen.h" #include "screen.h"
#include "uart_buffer.h"
#include "ansi_parser.h"
#define SOCK_BUF_LEN 2048 #define LOOPBACK 0
#define SOCK_BUF_LEN 1024
static char sock_buff[SOCK_BUF_LEN]; static char sock_buff[SOCK_BUF_LEN];
static void notifyTimCb(void *arg) { volatile bool notify_available = true;
volatile bool notify_cooldown = false;
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
notifyCooldownTimCb(void *arg)
{
notify_cooldown = false;
}
static void ICACHE_FLASH_ATTR
notifyContentTimCb(void *arg)
{
void *data = NULL; void *data = NULL;
int max_bl, total_bl;
cgiWebsockMeasureBacklog(URL_WS_UPDATE, &max_bl, &total_bl);
if (!notify_available || notify_cooldown || (max_bl > 2048)) { // do not send if we have anything significant backlogged
// postpone a little
TIMER_START(&notifyContentTim, notifyContentTimCb, 5, 0);
return;
}
notify_available = false;
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
httpd_cgi_state cont = screenSerializeToBuffer(sock_buff, SOCK_BUF_LEN, &data); httpd_cgi_state cont = screenSerializeToBuffer(sock_buff, SOCK_BUF_LEN, &data);
@@ -21,21 +58,64 @@ static void notifyTimCb(void *arg) {
if (cont == HTTPD_CGI_DONE) break; if (cont == HTTPD_CGI_DONE) break;
} }
// cleanup
screenSerializeToBuffer(NULL, SOCK_BUF_LEN, &data); screenSerializeToBuffer(NULL, SOCK_BUF_LEN, &data);
notify_cooldown = true;
notify_available = true;
TIMER_START(&notifyCooldownTim, notifyCooldownTimCb, termconf->display_cooldown_ms, 0);
}
static void ICACHE_FLASH_ATTR
notifyLabelsTimCb(void *arg)
{
if (!notify_available || notify_cooldown) {
// postpone a little
TIMER_START(&notifyLabelsTim, notifyLabelsTimCb, 1, 0);
return;
}
notify_available = false;
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(&notifyCooldownTim, 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);
} }
static ETSTimer notifyTim;
/** /**
* Broadcast screen state to sockets. * Broadcast screen state to sockets.
* This is a callback for the Screen module, * This is a callback for the Screen module,
* called after each visible screen modification. * called after each visible screen modification.
*/ */
void ICACHE_FLASH_ATTR screen_notifyChange(void) void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic)
{ {
os_timer_disarm(&notifyTim); // this is not the most ideal/cleanest implementation
os_timer_setfn(&notifyTim, notifyTimCb, NULL); // PRs are welcome for a nicer update "queue" solution
os_timer_arm(&notifyTim, 20, 0); 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
TIMER_START(&notifyLabelsTim, notifyLabelsTimCb, termconf->display_tout_ms, 0);
}
else if (topic == CHANGE_CONTENT) {
// throttle delay
TIMER_START(&notifyContentTim, notifyContentTimCb, termconf->display_tout_ms, 0);
}
} }
/** Socket received a message */ /** Socket received a message */
@@ -45,17 +125,25 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
// Add terminator if missing (seems to randomly happen) // Add terminator if missing (seems to randomly happen)
data[len] = 0; data[len] = 0;
dbg("Sock RX str: %s, len %d", data, len); // TODO re-implement those, use single byte markers and B2 encoding
ws_dbg("Sock RX str: %s, len %d", data, len);
if (strstarts(data, "STR:")) { if (strstarts(data, "STR:")) {
// pass string verbatim // pass string verbatim
UART_WriteString(UART0, data+4, UART_TIMEOUT_US); #if LOOPBACK
for(int i=4;i<strlen(data); i++) {
ansi_parser(data[i]);
}
#else
UART_SendAsync(data+4, -1);
#endif
} }
else if (strstarts(data, "BTN:")) { else if (strstarts(data, "BTN:")) {
// send button as low ASCII value 1-9 // send button as low ASCII value 1-9
int btnNum = data[4] - '0'; u8 btnNum = (u8) (data[4] - '0');
if (btnNum > 0 && btnNum < 10) { if (btnNum > 0 && btnNum < 10) {
UART_WriteChar(UART0, (unsigned char)btnNum, UART_TIMEOUT_US); UART_SendAsync((const char *) &btnNum, 1);
} }
} }
else if (strstarts(data, "TAP:")) { else if (strstarts(data, "TAP:")) {
@@ -81,24 +169,33 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
} }
if (!screen_isCoordValid(y, x)) { if (!screen_isCoordValid(y, x)) {
warn("Mouse input at invalid coordinates"); ws_warn("Mouse input at invalid coordinates");
return; return;
} }
dbg("Screen clicked at row %d, col %d", y+1, x+1); ws_dbg("Screen clicked at row %d, col %d", y+1, x+1);
// Send as 1-based to user // Send as 1-based to user
sprintf(buf, "\033[%d;%dM", y+1, x+1); sprintf(buf, "\033[%d;%dM", y+1, x+1);
UART_WriteString(UART0, buf, UART_TIMEOUT_US); UART_SendAsync(buf, -1);
} }
else { else {
warn("Bad command."); ws_warn("Bad command.");
}
}
void ICACHE_FLASH_ATTR heartbeatTimCb(void *unused)
{
if (notify_available) {
cgiWebsockBroadcast(URL_WS_UPDATE, ".", 1, 0);
} }
} }
/** Socket connected for updates */ /** Socket connected for updates */
void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws) 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; ws->recvCb = updateSockRx;
TIMER_START(&heartbeatTim, heartbeatTimCb, 1000, 1);
} }
+18 -1
View File
@@ -1,9 +1,26 @@
#ifndef CGI_SOCKETS_H #ifndef CGI_SOCKETS_H
#define CGI_SOCKETS_H #define CGI_SOCKETS_H
#define URL_WS_UPDATE "/ws/update.cgi" #define URL_WS_UPDATE "/term/update.ws"
#include <cgiwebsocket.h>
#include "screen.h"
/** Update websocket connect callback */ /** Update websocket connect callback */
void updateSockConnect(Websock *ws); 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 #endif //CGI_SOCKETS_H
+165
View File
@@ -0,0 +1,165 @@
#include <esp8266.h>
#include <httpd.h>
#include <helpers.h>
#include <httpdespfs.h>
#include "cgi_system.h"
#include "persist.h"
#include "syscfg.h"
#include "uart_driver.h"
#define SET_REDIR_SUC "/cfg/system"
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
static ETSTimer tmr;
static void ICACHE_FLASH_ATTR tmrCb(void *arg)
{
system_restart();
}
httpd_cgi_state ICACHE_FLASH_ATTR cgiResetDevice(HttpdConnData *connData)
{
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
os_timer_disarm(&tmr);
os_timer_setfn(&tmr, tmrCb, NULL);
os_timer_arm(&tmr, 100, false);
httpdSend(connData, "system reset\n", -1);
return HTTPD_CGI_DONE;
}
httpd_cgi_state ICACHE_FLASH_ATTR cgiPing(HttpdConnData *connData)
{
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
httpdSend(connData, "pong\n", -1);
return HTTPD_CGI_DONE;
}
/**
* Universal CGI endpoint to set Terminal params.
*/
httpd_cgi_state ICACHE_FLASH_ATTR
cgiSystemCfgSetParams(HttpdConnData *connData)
{
char buff[50];
char redir_url_buf[100];
char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (GET_ARG("uart_baud")) {
dbg("Baud rate: %s", buff);
int baud = atoi(buff);
if (baud == BIT_RATE_300 ||
baud == BIT_RATE_600 ||
baud == BIT_RATE_1200 ||
baud == BIT_RATE_2400 ||
baud == BIT_RATE_4800 ||
baud == BIT_RATE_9600 ||
baud == BIT_RATE_19200 ||
baud == BIT_RATE_38400 ||
baud == BIT_RATE_57600 ||
baud == BIT_RATE_74880 ||
baud == BIT_RATE_115200 ||
baud == BIT_RATE_230400 ||
baud == BIT_RATE_460800 ||
baud == BIT_RATE_921600 ||
baud == BIT_RATE_1843200 ||
baud == BIT_RATE_3686400) {
sysconf->uart_baudrate = (u32) baud;
} else {
warn("Bad baud rate %s", buff);
redir_url += sprintf(redir_url, "uart_baud,");
}
}
if (GET_ARG("uart_parity")) {
dbg("Parity: %s", buff);
int parity = atoi(buff);
if (parity >= 0 && parity <= 2) {
sysconf->uart_parity = (UartParityMode) parity;
} else {
warn("Bad parity %s", buff);
redir_url += sprintf(redir_url, "uart_parity,");
}
}
if (GET_ARG("uart_stopbits")) {
dbg("Stop bits: %s", buff);
int stopbits = atoi(buff);
if (stopbits >= 1 && stopbits <= 3) {
sysconf->uart_stopbits = (UartStopBitsNum) stopbits;
} else {
warn("Bad stopbits %s", buff);
redir_url += sprintf(redir_url, "uart_stopbits,");
}
}
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
info("Set system params - success, saving...");
sysconf_apply_settings();
persist_store();
httpdRedirect(connData, SET_REDIR_SUC);
} else {
warn("Some settings did not validate, asking for correction");
// Some errors, appended to the URL as ?err=
httpdRedirect(connData, redir_url_buf);
}
return HTTPD_CGI_DONE;
}
httpd_cgi_state ICACHE_FLASH_ATTR
tplSystemCfg(HttpdConnData *connData, char *token, void **arg)
{
#define BUFLEN 100
char buff[BUFLEN];
if (token == NULL) {
// We're done
return HTTPD_CGI_DONE;
}
strcpy(buff, ""); // fallback
if (streq(token, "uart_baud")) {
sprintf(buff, "%d", sysconf->uart_baudrate);
}
else if (streq(token, "uart_parity")) {
sprintf(buff, "%d", sysconf->uart_parity);
}
else if (streq(token, "uart_stopbits")) {
sprintf(buff, "%d", sysconf->uart_stopbits);
}
tplSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef CGI_PING_H
#define CGI_PING_H
#include <esp8266.h>
#include <httpd.h>
httpd_cgi_state cgiPing(HttpdConnData *connData);
httpd_cgi_state cgiResetDevice(HttpdConnData *connData);
httpd_cgi_state cgiSystemCfgSetParams(HttpdConnData *connData);
httpd_cgi_state tplSystemCfg(HttpdConnData *connData, char *token, void **arg);
#endif // CGI_PING_H
+239
View File
@@ -0,0 +1,239 @@
/*
Cgi/template routines for configuring non-wifi settings
*/
#include <esp8266.h>
#include <httpdespfs.h>
#include "cgi_term_cfg.h"
#include "persist.h"
#include "screen.h"
#include "helpers.h"
#define SET_REDIR_SUC "/cfg/term"
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
/**
* Universal CGI endpoint to set Terminal params.
*/
httpd_cgi_state ICACHE_FLASH_ATTR
cgiTermCfgSetParams(HttpdConnData *connData)
{
char buff[50];
char redir_url_buf[100];
int32 n, w, h;
bool shall_clear_screen = false;
char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
// width and height must always go together so we can do max size validation
if (GET_ARG("term_width")) {
dbg("Default screen width: %s", buff);
w = atoi(buff);
if (w > 1) {
if (GET_ARG("term_height")) {
dbg("Default screen height: %s", buff);
h = atoi(buff);
if (h > 1) {
if (w * h <= MAX_SCREEN_SIZE) {
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,");
}
} else {
warn("Bad height: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_width,");
}
} else {
warn("Missing height arg!");
// this wont happen normally when the form is used
redir_url += sprintf(redir_url, "term_width,term_height,");
}
} else {
warn("Bad width: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_width,");
}
}
if (GET_ARG("default_bg")) {
dbg("Screen default BG: %s", buff);
n = atoi(buff);
if (n >= 0 && n < 16) {
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,");
}
}
if (GET_ARG("parser_tout_ms")) {
dbg("Parser timeout: %s ms", buff);
n = atoi(buff);
if (n >= 0) {
termconf->parser_tout_ms = n;
} else {
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) {
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,");
}
}
if (GET_ARG("theme")) {
dbg("Screen color theme: %s", buff);
n = atoi(buff);
if (n >= 0 && n <= 5) { // ALWAYS ADJUST WHEN ADDING NEW THEME!
termconf->theme = (u8) n;
} else {
warn("Bad theme num: %s", buff);
redir_url += sprintf(redir_url, "theme,");
}
}
if (GET_ARG("term_title")) {
dbg("Terminal title default text: \"%s\"", buff);
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in
}
for (int i = 1; i <= 5; i++) {
sprintf(buff, "btn%d", i);
if (GET_ARG(buff)) {
dbg("Button%d default text: \"%s\"", i, buff);
strncpy_safe(termconf->btn[i-1], buff, TERM_BTN_LEN);
}
}
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
info("Set term params - success, saving...");
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");
// Some errors, appended to the URL as ?err=
httpdRedirect(connData, redir_url_buf);
}
return HTTPD_CGI_DONE;
}
httpd_cgi_state ICACHE_FLASH_ATTR
tplTermCfg(HttpdConnData *connData, char *token, void **arg)
{
#define BUFLEN TERM_TITLE_LEN
char buff[BUFLEN];
char buff2[10];
if (token == NULL) {
// We're done
return HTTPD_CGI_DONE;
}
strcpy(buff, ""); // fallback
if (streq(token, "term_width")) {
sprintf(buff, "%d", termconf->width);
}
else if (streq(token, "term_height")) {
sprintf(buff, "%d", termconf->height);
}
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);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
}
else if (streq(token, "default_fg")) {
sprintf(buff, "%d", termconf->default_fg);
}
else if (streq(token, "term_title")) {
strncpy_safe(buff, termconf->title, BUFLEN);
}
else {
for (int i = 1; i <= 5; i++) {
sprintf(buff2, "btn%d", i);
if (streq(token, buff2)) {
strncpy_safe(buff, termconf->btn[i-1], BUFLEN);
break;
}
}
}
tplSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef CGIAPPCFG_H
#define CGIAPPCFG_H
#include "httpd.h"
httpd_cgi_state cgiTermCfgSetParams(HttpdConnData *connData);
httpd_cgi_state tplTermCfg(HttpdConnData *connData, char *token, void **arg);
#endif
+51 -11
View File
@@ -14,6 +14,7 @@ Cgi/template routines for the /wifi url.
*/ */
#include <esp8266.h> #include <esp8266.h>
#include <httpdespfs.h>
#include "cgi_wifi.h" #include "cgi_wifi.h"
#include "wifimgr.h" #include "wifimgr.h"
#include "persist.h" #include "persist.h"
@@ -183,9 +184,8 @@ static void ICACHE_FLASH_ATTR wifiStartScan(void)
} }
/** /**
* This CGI is called from the bit of AJAX-code in wifi.tpl. It will initiate a * Start a scan and return a result of an earlier scan, if available.
* scan for access points and if available will return the result of an earlier scan. * The STA is switched ON if disabled.
* The result is embedded in a bit of JSON parsed by the javascript in wifi.tpl.
*/ */
httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData)
{ {
@@ -193,6 +193,17 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData)
int len; int len;
char buff[256]; 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;
wifimgr_apply_settings();
}
// 2nd and following runs of the function via MORE: // 2nd and following runs of the function via MORE:
if (!cgiWifiAps.scanInProgress && pos != 0) { if (!cgiWifiAps.scanInProgress && pos != 0) {
// Fill in json code for an access point // Fill in json code for an access point
@@ -259,6 +270,13 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
char buff[100]; char buff[100];
struct ip_info info; 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); httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/json"); httpdHeader(connData, "Content-Type", "application/json");
httpdEndHeaders(connData); httpdEndHeaders(connData);
@@ -270,6 +288,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
} }
STATION_STATUS st = wifi_station_get_connect_status(); STATION_STATUS st = wifi_station_get_connect_status();
dbg("CONN STATE = %d", st);
switch(st) { switch(st) {
case STATION_IDLE: case STATION_IDLE:
sprintf(buff, "{\"status\": \"idle\"}"); // unclear when this is used sprintf(buff, "{\"status\": \"idle\"}"); // unclear when this is used
@@ -295,6 +314,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
wifi_get_ip_info(STATION_IF, &info); wifi_get_ip_info(STATION_IF, &info);
sprintf(buff, "{\"status\": \"success\", \"ip\": \""IPSTR"\"}", GOOD_IP2STR(info.ip.addr)); sprintf(buff, "{\"status\": \"success\", \"ip\": \""IPSTR"\"}", GOOD_IP2STR(info.ip.addr));
break; break;
default:
sprintf(buff, "{\"status\": \"working\", \"wtf\": \"state = %d\"}", st);
break;
} }
httpdSend(connData, buff, -1); httpdSend(connData, buff, -1);
@@ -319,8 +343,8 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
static ETSTimer timer; static ETSTimer timer;
char buff[50]; char buff[50];
char redir_url_buf[100]; // this is just barely enough - but it's split into two forms, so we never have error in all fields
char redir_url_buf[300];
char *redir_url = redir_url_buf; char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR); redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf // we'll test if anything was printed by looking for \0 in failed_keys_buf
@@ -330,6 +354,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
bool sta_turned_on = false;
bool sta_ssid_pw_changed = false;
// ---- WiFi opmode ---- // ---- WiFi opmode ----
if (GET_ARG("opmode")) { if (GET_ARG("opmode")) {
@@ -360,6 +387,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
if (enable) { if (enable) {
wificonf->opmode |= STATION_MODE; wificonf->opmode |= STATION_MODE;
sta_turned_on = true;
} else { } else {
wificonf->opmode &= ~STATION_MODE; wificonf->opmode &= ~STATION_MODE;
} }
@@ -457,6 +485,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
info("Setting station SSID to: \"%s\"", buff); info("Setting station SSID to: \"%s\"", buff);
strncpy_safe(wificonf->sta_ssid, buff, SSID_LEN); strncpy_safe(wificonf->sta_ssid, buff, SSID_LEN);
wifi_change_flags.sta = true; wifi_change_flags.sta = true;
sta_ssid_pw_changed = true;
} }
} }
@@ -468,12 +497,13 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
info("Setting station password to: \"%s\"", buff); info("Setting station password to: \"%s\"", buff);
strncpy_safe(wificonf->sta_password, buff, PASSWORD_LEN); strncpy_safe(wificonf->sta_password, buff, PASSWORD_LEN);
wifi_change_flags.sta = true; wifi_change_flags.sta = true;
sta_ssid_pw_changed = true;
} }
} }
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) { if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK // 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 // Settings are applied only if all was OK
// //
@@ -486,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. // If user connects via the Station IF, they may not even notice the connection reset.
os_timer_disarm(&timer); os_timer_disarm(&timer);
os_timer_setfn(&timer, applyWifiSettingsLaterCb, NULL); 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 { } else {
warn("Some WiFi settings did not validate, asking for correction"); warn("Some WiFi settings did not validate, asking for correction");
// Some errors, appended to the URL as ?err= // Some errors, appended to the URL as ?err=
@@ -501,7 +541,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
//Template code for the WLAN page. //Template code for the WLAN page.
httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg)
{ {
char buff[100]; char buff[PASSWORD_LEN];
int x; int x;
int connectStatus; int connectStatus;
@@ -552,7 +592,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
// For display of our current SSID // For display of our current SSID
connectStatus = wifi_station_get_connect_status(); connectStatus = wifi_station_get_connect_status();
x = wifi_get_opmode(); 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, ""); strcpy(buff, "");
} }
else { else {
@@ -565,7 +605,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
x = wifi_get_opmode(); x = wifi_get_opmode();
connectStatus = wifi_station_get_connect_status(); 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, ""); strcpy(buff, "");
} }
else { else {
@@ -580,6 +620,6 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
} }
} }
httpdSend(connData, buff, -1); tplSend(connData, buff, -1);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
+28
View File
@@ -13,6 +13,8 @@
#include "ansi_parser_callbacks.h" #include "ansi_parser_callbacks.h"
#include "wifimgr.h" #include "wifimgr.h"
#include "persist.h" #include "persist.h"
#include "screen.h"
#include "ansi_parser.h"
#define BTNGPIO 0 #define BTNGPIO 0
@@ -21,6 +23,28 @@ static bool enable_ap_button = false;
static ETSTimer resetBtntimer; static ETSTimer resetBtntimer;
static ETSTimer blinkyTimer; 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. // Holding BOOT pin triggers AP reset, then Factory Reset.
// Indicate that by blinking the on-board LED. // 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_setfn(&resetBtntimer, resetBtnTimerCb, NULL);
os_timer_arm(&resetBtntimer, 500, 1); 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. // One way to enter AP mode - hold GPIO0 low.
if (GPIO_INPUT_GET(BTNGPIO) == 0) { if (GPIO_INPUT_GET(BTNGPIO) == 0) {
// starting "in BOOT mode" - do not install the AP reset timer // starting "in BOOT mode" - do not install the AP reset timer
+44 -10
View File
@@ -17,21 +17,45 @@ static void ICACHE_FLASH_ATTR
apply_live_settings(void) apply_live_settings(void)
{ {
dbg("[Persist] Applying live settings..."); dbg("[Persist] Applying live settings...");
terminal_apply_settings();
dbg("[Persist] > system");
sysconf_apply_settings();
dbg("[Persist] > wifi");
wifimgr_apply_settings(); wifimgr_apply_settings();
dbg("[Persist] > terminal");
terminal_apply_settings();
dbg("[Persist] Live settings applied.");
// ... // ...
} }
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
restore_live_settings_to_hard_defaults(void) restore_live_settings_to_hard_defaults(void)
{ {
dbg("[Persist] Restore to hard defaults...");
dbg("[Persist] > system");
sysconf_restore_defaults();
dbg("[Persist] > wifi");
wifimgr_restore_defaults(); wifimgr_restore_defaults();
dbg("[Persist] > terminal");
terminal_restore_defaults(); terminal_restore_defaults();
dbg("[Persist] Restored to hard defaults.");
// ... // ...
} }
//endregion //endregion
const u32 wconf_at = (u32)&persist.defaults.wificonf - (u32)&persist.defaults;
const u32 tconf_at = (u32)&persist.defaults.termconf - (u32)&persist.defaults;
const u32 sconf_at = (u32)&persist.defaults.sysconf - (u32)&persist.defaults;
const u32 cksum_at = (u32)&persist.defaults.checksum - (u32)&persist.defaults;
/** /**
* Compute CRC32. Adapted from https://github.com/esp8266/Arduino * Compute CRC32. Adapted from https://github.com/esp8266/Arduino
* @param data * @param data
@@ -41,7 +65,9 @@ restore_live_settings_to_hard_defaults(void)
static uint32_t ICACHE_FLASH_ATTR static uint32_t ICACHE_FLASH_ATTR
calculateCRC32(const uint8_t *data, size_t length) calculateCRC32(const uint8_t *data, size_t length)
{ {
uint32_t crc = 0xffffffff; // the salt here should ensure settings are wiped when the structure changes
// CHECKSUM_SALT can be adjusted manually to force a reset.
uint32_t crc = 0xffffffff + CHECKSUM_SALT + ((wconf_at << 16) ^ (tconf_at << 10) ^ (sconf_at << 5));
while (length--) { while (length--) {
uint8_t c = *data++; uint8_t c = *data++;
for (uint32_t i = 0x80; i > 0; i >>= 1) { for (uint32_t i = 0x80; i > 0; i >>= 1) {
@@ -67,7 +93,8 @@ calculateCRC32(const uint8_t *data, size_t length)
static uint32_t ICACHE_FLASH_ATTR static uint32_t ICACHE_FLASH_ATTR
compute_checksum(AppConfigBundle *bundle) compute_checksum(AppConfigBundle *bundle)
{ {
return calculateCRC32((uint8_t *) bundle, sizeof(AppConfigBundle) - 4) ^ CHECKSUM_SALT; // this should be the bundle without the checksum
return calculateCRC32((uint8_t *) bundle, sizeof(AppConfigBundle) - 4);
} }
/** /**
@@ -78,10 +105,12 @@ persist_load(void)
{ {
info("[Persist] Loading stored settings from FLASH..."); info("[Persist] Loading stored settings from FLASH...");
dbg("sizeof(AppConfigBundle) = %d bytes", sizeof(AppConfigBundle)); dbg("AppConfigBundle memory map:");
dbg("sizeof(PersistBlock) = %d bytes", sizeof(PersistBlock)); dbg("> WiFiConfigBundle at %4d (error %2d)", wconf_at, wconf_at - 0);
dbg("sizeof(WiFiConfigBundle) = %d bytes", sizeof(WiFiConfigBundle)); dbg("> SystemConfigBundle at %4d (error %2d)", sconf_at, sconf_at - WIFICONF_SIZE);
dbg("sizeof(TerminalConfigBundle) = %d bytes", sizeof(TerminalConfigBundle)); dbg("> TerminalConfigBundle at %4d (error %2d)", tconf_at, tconf_at - WIFICONF_SIZE - SYSCONF_SIZE);
dbg("> Checksum at %4d (error %2d)", cksum_at, cksum_at - (APPCONF_SIZE - 4));
dbg("> Total size = %d bytes (error %d)", sizeof(AppConfigBundle), APPCONF_SIZE - sizeof(AppConfigBundle));
bool hard_reset = false; bool hard_reset = false;
@@ -99,7 +128,15 @@ persist_load(void)
} }
if (hard_reset) { if (hard_reset) {
// Zero all out
memset(&persist, 0, sizeof(PersistBlock));
persist_load_hard_default(); persist_load_hard_default();
// write them also as defaults
memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
persist_store();
// this also stores them to flash and applies to modules // this also stores them to flash and applies to modules
} else { } else {
apply_live_settings(); apply_live_settings();
@@ -135,9 +172,6 @@ persist_load_hard_default(void)
restore_live_settings_to_hard_defaults(); restore_live_settings_to_hard_defaults();
persist_store(); persist_store();
// // Store current -> default
// memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
info("[Persist] Settings restored to hard defaults."); info("[Persist] Settings restored to hard defaults.");
apply_live_settings(); // apply apply_live_settings(); // apply
+18 -5
View File
@@ -12,19 +12,26 @@
#include "wifimgr.h" #include "wifimgr.h"
#include "screen.h" #include "screen.h"
#include "syscfg.h"
// Changing this could be used to force-erase the config area // Changing this could be used to force-erase the config area
// after a firmware upgrade // after a firmware upgrade
#define CHECKSUM_SALT 0x5F5F5F5F #define CHECKSUM_SALT 3
#define APPCONF_SIZE 2048
/** Struct for current or default settings */ /** Struct for current or default settings */
typedef struct { typedef struct { // the entire block should be 1024 bytes long (for compatibility across upgrades)
WiFiConfigBundle wificonf; WiFiConfigBundle wificonf;
uint8_t _filler1[WIFICONF_SIZE - sizeof(WiFiConfigBundle)];
SystemConfigBundle sysconf;
uint8_t _filler3[SYSCONF_SIZE - sizeof(SystemConfigBundle)];
TerminalConfigBundle termconf; TerminalConfigBundle termconf;
uint8_t _filler2[TERMCONF_SIZE - sizeof(TerminalConfigBundle)];
// --- Space for future settings --- // --- Space for future settings ---
// Original size: 1024
//
// The size must be appropriately reduced each time something is added, // The size must be appropriately reduced each time something is added,
// and boolean flags defaulting to 0 should be used to detect unpopulated // and boolean flags defaulting to 0 should be used to detect unpopulated
// sections that must be restored to defaults on load. // sections that must be restored to defaults on load.
@@ -32,7 +39,13 @@ typedef struct {
// This ensures user settings are not lost each time they upgrade the firmware, // This ensures user settings are not lost each time they upgrade the firmware,
// which would lead to a checksum mismatch if the structure was changed and // which would lead to a checksum mismatch if the structure was changed and
// it grew to a different memory area. // it grew to a different memory area.
uint8_t filler[1024]; uint8_t _filler_end[
APPCONF_SIZE
- sizeof(uint32_t) // checksum
- WIFICONF_SIZE
- SYSCONF_SIZE
- TERMCONF_SIZE
];
uint32_t checksum; // computed before write and tested on load. If it doesn't match, values are reset to hard defaults. uint32_t checksum; // computed before write and tested on load. If it doesn't match, values are reset to hard defaults.
} AppConfigBundle; } AppConfigBundle;
+10 -9
View File
@@ -6,12 +6,11 @@
#include "routes.h" #include "routes.h"
#include "cgi_wifi.h" #include "cgi_wifi.h"
#include "cgi_reset.h" #include "cgi_system.h"
#include "cgi_ping.h"
#include "cgi_main.h" #include "cgi_main.h"
#include "cgi_sockets.h" #include "cgi_sockets.h"
#include "cgi_network.h" #include "cgi_network.h"
#include "cgi_appcfg.h" #include "cgi_term_cfg.h"
#include "cgi_persist.h" #include "cgi_persist.h"
#define WIFI_PROTECT 0 #define WIFI_PROTECT 0
@@ -33,6 +32,7 @@ HttpdBuiltInUrl routes[] = {
ROUTE_FILE("/help/?", "/help.tpl"), ROUTE_FILE("/help/?", "/help.tpl"),
// --- Sockets --- // --- Sockets ---
ROUTE_CGI("/term/init", cgiTermInitialImage),
ROUTE_WS(URL_WS_UPDATE, updateSockConnect), ROUTE_WS(URL_WS_UPDATE, updateSockConnect),
// --- System control --- // --- System control ---
@@ -54,13 +54,14 @@ HttpdBuiltInUrl routes[] = {
ROUTE_TPL_FILE("/cfg/network/?", tplNetwork, "/cfg_network.tpl"), ROUTE_TPL_FILE("/cfg/network/?", tplNetwork, "/cfg_network.tpl"),
ROUTE_CGI("/cfg/network/set", cgiNetworkSetParams), ROUTE_CGI("/cfg/network/set", cgiNetworkSetParams),
ROUTE_TPL_FILE("/cfg/app/?", tplAppCfg, "/cfg_app.tpl"), ROUTE_TPL_FILE("/cfg/term/?", tplTermCfg, "/cfg_term.tpl"),
ROUTE_CGI("/cfg/app/set", cgiAppCfgSetParams), ROUTE_CGI("/cfg/term/set", cgiTermCfgSetParams),
ROUTE_FILE("/cfg/admin/?", "/cfg_admin.tpl"), ROUTE_TPL_FILE("/cfg/system/?", tplSystemCfg, "/cfg_system.tpl"),
ROUTE_CGI("/cfg/admin/write_defaults", cgiPersistWriteDefaults), ROUTE_CGI("/cfg/system/set", cgiSystemCfgSetParams),
ROUTE_CGI("/cfg/admin/restore_defaults", cgiPersistRestoreDefaults), ROUTE_CGI("/cfg/system/write_defaults", cgiPersistWriteDefaults),
ROUTE_CGI("/cfg/admin/restore_hard", cgiPersistRestoreHard), ROUTE_CGI("/cfg/system/restore_defaults", cgiPersistRestoreDefaults),
ROUTE_CGI("/cfg/system/restore_hard", cgiPersistRestoreHard),
ROUTE_FILESYSTEM(), ROUTE_FILESYSTEM(),
ROUTE_END(), ROUTE_END(),
+1014 -275
View File
File diff suppressed because it is too large Load Diff
+171 -54
View File
@@ -34,17 +34,41 @@
* *
*/ */
// 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
#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"
/** Maximum screen size (determines size of the static data array) */
#define MAX_SCREEN_SIZE (80*26)
#define TERMCONF_VERSION 1
// --- Persistent Settings ---
typedef struct { typedef struct {
u32 width; u32 width;
u32 height; u32 height;
u8 default_bg; u8 default_bg; // should be the Color typedef, but this way the size is more explicit
u8 default_fg; u8 default_fg;
char title[64]; char title[TERM_TITLE_LEN];
char btn1[10]; char btn[5][TERM_BTN_LEN];
char btn2[10]; u8 theme;
char btn3[10]; u32 parser_tout_ms;
char btn4[10]; u32 display_tout_ms;
char btn5[10]; bool fn_alt_mode; // xterm compatibility mode (alternate codes for some FN keys)
u8 config_version;
u32 display_cooldown_ms;
} TerminalConfigBundle; } TerminalConfigBundle;
// Live config // Live config
@@ -56,48 +80,79 @@ extern TerminalConfigBundle * const termconf;
*/ */
extern TerminalConfigBundle termconf_scratch; extern TerminalConfigBundle termconf_scratch;
/** Restore default settings to termconf. Does not apply or copy to scratch. */
void terminal_restore_defaults(void); void terminal_restore_defaults(void);
/** Apply settings, redraw (clears the screen) */
void terminal_apply_settings(void); 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 ---
* Maximum screen size (determines size of the static data array)
* typedef struct {
* TODO May need adjusting if there are size problems when flashing the ESP. u8 lsb;
* We could also try to pack the Cell struct to a single 32bit word. u8 msb;
*/ } WordB2;
#define MAX_SCREEN_SIZE (80*25)
typedef struct {
u8 lsb;
u8 msb;
u8 xsb;
} WordB3;
typedef enum { typedef enum {
CLEAR_TO_CURSOR=0, CLEAR_FROM_CURSOR=1, CLEAR_ALL=2 CS_USASCII = 'B',
} ClearMode; CS_UKASCII = 'A',
CS_DEC_SUPPLEMENTAL = '0',
CS_DOS_437 = '1',
} CHARSET;
typedef uint8_t Color; /** Encode number to two nice ASCII bytes */
void encode2B(u16 number, WordB2 *stru);
httpd_cgi_state screenSerializeToBuffer(char *buffer, size_t buf_len, void **data); httpd_cgi_state screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
/** Init the screen */ void screenSerializeLabelsToBuffer(char *buffer, size_t buf_len);
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 --- // --- Clearing ---
typedef enum {
CLEAR_TO_CURSOR = 0,
CLEAR_FROM_CURSOR = 1,
CLEAR_ALL = 2
} ClearMode;
/** Screen reset to default state */ /** Screen reset to default state */
void screen_reset(void); void screen_reset(void);
/** Clear entire screen */
/** Clear entire screen, set all to 7 on 0 */
void screen_clear(ClearMode mode); void screen_clear(ClearMode mode);
/** Clear line */
/** Line reset to gray-on-white, empty */
void screen_clear_line(ClearMode mode); 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 */ /** Shift screen upwards */
void screen_scroll_up(unsigned int lines); void screen_scroll_up(unsigned int lines);
/** Shift screen downwards */ /** Shift screen downwards */
void screen_scroll_down(unsigned int lines); void screen_scroll_down(unsigned int lines);
@@ -105,57 +160,119 @@ void screen_scroll_down(unsigned int lines);
/** Set cursor position */ /** Set cursor position */
void screen_cursor_set(int y, int x); void screen_cursor_set(int y, int x);
/** Read cursor pos to given vars */ /** Read cursor pos to given vars */
void screen_cursor_get(int *y, int *x); void screen_cursor_get(int *y, int *x);
/** Set cursor X position */ /** Set cursor X position */
void screen_cursor_set_x(int x); void screen_cursor_set_x(int x);
/** Set cursor Y position */ /** Set cursor Y position */
void screen_cursor_set_y(int y); void screen_cursor_set_y(int y);
/** Relative cursor move */ /** 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 */ /** Save the cursor pos */
void screen_cursor_save(bool withAttrs); void screen_cursor_save(bool withAttrs);
/** Restore the cursor pos */ /** Restore the cursor pos */
void screen_cursor_restore(bool withAttrs); void screen_cursor_restore(bool withAttrs);
/** Enable cursor display */ // --- Cursor behavior setting ---
void screen_cursor_enable(bool enable);
/** Toggle INSERT / REPLACE */
void screen_set_insert_mode(bool insert);
/** Enable CR auto */
void screen_set_newline_mode(bool nlm);
/** Enable auto wrap */ /** Enable auto wrap */
void screen_wrap_enable(bool enable); 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 */ /** Set cursor foreground color */
void screen_set_fg(Color color); void screen_set_fg(Color color);
/** Set cursor background coloor */ /** Set cursor background coloor */
void screen_set_bg(Color color); 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 */ // --- Global modes and attributes ---
void screen_set_bright_fg(void);
/** Set cursor foreground and background color */ /** Enable cursor display */
void screen_set_colors(Color fg, Color bg); 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 */ // --- Charset ---
void screen_inverse(bool inverse);
/** Switch G0 <-> G1 */
void screen_set_charset_n(int Gx);
/** Assign G0 or G1 */
void screen_set_charset(int Gx, char charset);
/** Set a character in the cursor color, move to right with wrap. */ // --- Tab stops ---
void screen_putchar(char c);
#if 0 /** Remove all tabs on the screen */
/** Debug dump */ void screen_clear_all_tabs(void);
void screen_dd(void); /** Set tab at the current column */
#endif 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);
extern void screen_notifyChange(void); // --- Printing characters ---
/**
* Set a character in the cursor color, move to right with wrap.
* The character may be ASCII (then only one char is used), or
* 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);
// --- 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 #endif // SCREEN_H
+73 -12
View File
@@ -2,19 +2,85 @@
#include "uart_driver.h" #include "uart_driver.h"
#include "uart_handler.h" #include "uart_handler.h"
#include "ansi_parser.h" #include "ansi_parser.h"
#include "syscfg.h"
// Here the bitrates are defined #define LOGBUF_SIZE 1500
#define UART0_BAUD BIT_RATE_115200 static char logbuf[LOGBUF_SIZE];
#define UART1_BAUD BIT_RATE_115200 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
*/
void ICACHE_FLASH_ATTR serialInitBase(void)
{
UART_Init();
// debug port
UART_SetParity(UART1, PARITY_NONE);
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
}
/** /**
* Init the serial ports * Init the serial ports
*/ */
void ICACHE_FLASH_ATTR serialInit(void) void ICACHE_FLASH_ATTR serialInit(void)
{ {
UART_Init(UART0_BAUD, UART1_BAUD); UART_SetParity(UART0, (UartParityMode) sysconf->uart_parity);
UART_SetPrintPort(UART1); UART_SetStopBits(UART0, (UartStopBitsNum) sysconf->uart_stopbits);
UART_SetupAsyncReceiver(); UART_SetBaudrate(UART0, sysconf->uart_baudrate);
info("COM SERIAL: %d baud, %s parity, %s stopbit(s)",
sysconf->uart_baudrate,
(sysconf->uart_parity == PARITY_NONE ? "NONE" : (sysconf->uart_parity == PARITY_ODD ? "ODD" : "EVEN")),
(sysconf->uart_stopbits == ONE_STOP_BIT ? "1" : (sysconf->uart_stopbits == ONE_HALF_STOP_BIT ? "1.5" : "2"))
);
} }
/** /**
@@ -25,10 +91,5 @@ void ICACHE_FLASH_ATTR serialInit(void)
*/ */
void ICACHE_FLASH_ATTR UART_HandleRxByte(char c) void ICACHE_FLASH_ATTR UART_HandleRxByte(char c)
{ {
if (c > 0 && c < 127) { ansi_parser(c);
// TODO buffering, do not run parser after just 1 char
ansi_parser(&c, 1);
} else {
warn("Bad char %d ('%c')", (unsigned char)c, c);
}
} }
+3
View File
@@ -1,6 +1,9 @@
#ifndef SERIAL_H #ifndef SERIAL_H
#define SERIAL_H #define SERIAL_H
/** Initial uart init (before sysconf is loaded) */
void serialInitBase(void);
/** Init the uarts */ /** Init the uarts */
void serialInit(void); void serialInit(void);
+37
View File
@@ -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
+26
View File
@@ -0,0 +1,26 @@
//
// Created by MightyPork on 2017/07/29.
//
#include "syscfg.h"
#include "persist.h"
#include "uart_driver.h"
#include "serial.h"
SystemConfigBundle * const sysconf = &persist.current.sysconf;
void ICACHE_FLASH_ATTR
sysconf_apply_settings(void)
{
// !!! Update to current version !!!
serialInit();
}
void ICACHE_FLASH_ATTR
sysconf_restore_defaults(void)
{
sysconf->uart_parity = PARITY_NONE;
sysconf->uart_baudrate = BIT_RATE_115200;
sysconf->uart_stopbits = ONE_STOP_BIT;
}
+28
View File
@@ -0,0 +1,28 @@
//
// Created by MightyPork on 2017/07/29.
//
#ifndef ESP_VT100_FIRMWARE_SYSCFG_H
#define ESP_VT100_FIRMWARE_SYSCFG_H
#include <esp8266.h>
// 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;
void sysconf_apply_settings(void);
void sysconf_restore_defaults(void);
#endif //ESP_VT100_FIRMWARE_SYSCFG_H
+830
View File
@@ -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
//}
//
//
+178
View File
@@ -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

Some files were not shown because too many files have changed in this diff Show More