Compare commits

...
10 Commits
9 changed files with 108 additions and 38 deletions
+33 -20
View File
@@ -5,21 +5,24 @@
![Photo][photo-hw]<br> ![Photo][photo-hw]<br>
*Fig 1: Breadboard adapter developed for ESPTerm* *Fig 1: Breadboard adapter developed for ESPTerm*
As of release 1.0, ESPTerm **passes most of VTTEST test cases** (from the main menu and some Xterm specific), making it ESPTerm **passes most of VTTEST test cases**, making it functionally comparable to eg.
functionally comparable to eg. gnome-terminal, terminator, konsole, GtkTerm or PuTTY. gnome-terminal, terminator, konsole, GtkTerm or PuTTY.
ESPTerm is **capable of running Midnight Commander** through agetty, **including full ESPTerm is **capable of running Midnight Commander** through agetty, **including full
mouse support**, provided agetty is made to believe it's Xterm, which shows ESPTerm is sufficiently well mouse support**, provided agetty is made to believe it's Xterm, which shows ESPTerm
implemented to work with ncurses. is compatible with ncurses.
In addition to control sequences that manipulate the terminal or user input, there
is a set of simple **networking commands** for device-to-device message exchange and
interacting with remote servers.
To see what escape sequences are supported, check out this [annotated Xterm manual page][xterm-compare] To see what escape sequences are supported, check out this [annotated Xterm manual page][xterm-compare]
which was used for reference, or the built-in help page ([online demo][demo-help]) which was used for reference, or the built-in help page ([online demo][demo-help])
The terminal screen can be accessed using any web browser, even on a phone or tablet. 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 an ESP-12 It works with ESP-01, ESP-01S, ESP-12 and likely many other modules.
on a LoLin NodeMCU board from eBay for development).
With ESPTerm, you can add remote access via WiFi to any embeded project, all you need is With ESPTerm, you can add remote access via WiFi to any embeded project, all you need is
a serial port and some imagination! UART and some imagination!
## Try it online ## Try it online
@@ -48,6 +51,8 @@ It **does not work with**:
- Internet Explorer (any version) - crashes, missing JS features - Internet Explorer (any version) - crashes, missing JS features
- Opera Mini - crashes, missing JS and CSS features - Opera Mini - crashes, missing JS and CSS features
- Blackberry browser - not tested, but unlikely - Blackberry browser - not tested, but unlikely
- Safari on old iOS versions - missing features
- WebOS browser
- Old Android Browser (before 4.4?) - not tested, likely missing JS features - Old Android Browser (before 4.4?) - not tested, likely missing JS features
## Main features ## Main features
@@ -58,6 +63,10 @@ It **does not work with**:
- Full UTF-8 support, alternate character sets - Full UTF-8 support, alternate character sets
- Standard mouse tracking modes - Standard mouse tracking modes
- You can dynamically set screen title, button labels... - You can dynamically set screen title, button labels...
- **Networking commands**
- Command for sending a message to another ESPTerm
- API endpoint (`/api/v1/msg`) for receiving messages sent e.g. from a script on PC, web browser or CURL
- Command for requesting remote servers and getting back response headers and/or body
- **Web Terminal Interface** - **Web Terminal Interface**
- Real-time screen update via WebSocket - Real-time screen update via WebSocket
- Mouse and keyboard input, works also on mobile - Mouse and keyboard input, works also on mobile
@@ -67,6 +76,15 @@ It **does not work with**:
- **User-friendly comprehensive WiFi configuration** (Demo: [WiFi][demo-wifi], [network][demo-network] config) - **User-friendly comprehensive WiFi configuration** (Demo: [WiFi][demo-wifi], [network][demo-network] config)
- Static IP, DHCP, channel selection, power - Static IP, DHCP, channel selection, power
- SSID search utility for finding your existing network - SSID search utility for finding your existing network
- **Basic security features**
- Possibility to password-lock parts of the web interface
- Admin password for some sensitive operations (can be changed!)
- Configurable AP password & Hidden attribute
- **Advanced settings storage**
- Stored in Flash
- Seamlesly updated and usually backwards compatible in minor releases
- Settings can be saved as defaults and later easily restored (ideal e.g. for classroom use, saving
good tested settings before giving the module to students)
## Bugs? Ideas? ## Bugs? Ideas?
@@ -108,36 +126,31 @@ It can happen that some changes to the WiFi or network config make the module in
- To reset all settings to defaults, hold the button a couple seconds until the LED flashes rapidly, then release it. - To reset all settings to defaults, hold the button a couple seconds until the LED flashes rapidly, then release it.
- You can cancel this wipe/reset operation (when triggered by accident) by pressing Reset or disconnecting the power supply. - You can cancel this wipe/reset operation (when triggered by accident) by pressing Reset or disconnecting the power supply.
### Config files ### Config banks
ESPTerm has two config "files", one for defaults and one for the currently used settings. In the case of the terminal 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. 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 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", 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. which can (and should!) be changed. This password can't be easily recovered when forgotten.
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 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. on the System Settings page. Those are the initial values you would get after a clean install.
## Research resources ## Research resources
Developing ESPTerm wasn't an easy task, because the information is scattered across many places and the existing Developing the terminal emulator was complicated by the information being scattered across many places and the existing
terminal emulators I originally used for reference (terminator, Konsole) are not implemented correctly in some details. implementations I used for reference often got some details wrong or didn't implement certain features at all. Xterm proved to be by far the most complete implementation.
A great tool for checking my implementation has proven to be [VTTTEST][vttest] and Xterm as a reference A great tool for checking my code has proven to be [VTTTEST][vttest]. ESPTerm passes most of the tests on the main page and some additional Xterm specific ones, like Mouse Tracking.
implementation that is probably the most complete emulator available, although it's cumbersome to use and its age
really shows in the looks.
I've comnpiled a list of those I found most helpful here: [VT100 emulation resources][resources] Here is a list of useful [VT100 emulation resources][resources] I've collected for reference.
## Development ## Development
ESPTerm's firmware is written in C and is based on SpriteTM's `libesphttpd` http server library forked to ESPTerm's firmware is written in C and is based on SpriteTM's `libesphttpd` http server library forked to
[MightyPork/libesphttpd][httpdlib]. This fork includes various improvements [MightyPork/libesphttpd][httpdlib]. This fork includes various improvements and changes required by the project.
and changes required by the project.
### Installation for development ### Installation for development
+4
View File
@@ -49,6 +49,10 @@ apars_handle_osc(char *buffer)
buffer[0] = 'G'; buffer[0] = 'G';
notify_growl(buffer); notify_growl(buffer);
} }
else if (n == 70) {
// ESPTerm: backdrop
screen_set_backdrop(buffer);
}
else if (n >= 81 && n <= 85) { else if (n >= 81 && n <= 85) {
// ESPTerm: action button label // ESPTerm: action button label
screen_set_button_text(n - 80, buffer); screen_set_button_text(n - 80, buffer);
+11 -2
View File
@@ -256,10 +256,16 @@ cgiTermCfgSetParams(HttpdConnData *connData)
if (GET_ARG("term_title")) { if (GET_ARG("term_title")) {
cgi_dbg("Terminal title default text: \"%s\"", buff); cgi_dbg("Terminal title default text: \"%s\"", buff);
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in strncpy_safe(termconf->title, buff, TERM_TITLE_LEN); // ATTN those must match the values in
topics |= TOPIC_CHANGE_TITLE; topics |= TOPIC_CHANGE_TITLE;
} }
if (GET_ARG("backdrop")) {
cgi_dbg("Terminal backdrop url: \"%s\"", buff);
strncpy_safe(termconf->backdrop, buff, TERM_BACKDROP_LEN); // ATTN those must match the values in
topics |= TOPIC_CHANGE_BACKDROP;
}
for (int btn_i = 1; btn_i <= TERM_BTN_COUNT; btn_i++) { for (int btn_i = 1; btn_i <= TERM_BTN_COUNT; btn_i++) {
sprintf(buff, "btn%d", btn_i); sprintf(buff, "btn%d", btn_i);
if (GET_ARG(buff)) { if (GET_ARG(buff)) {
@@ -416,7 +422,7 @@ cgiTermCfgSetParams(HttpdConnData *connData)
httpd_cgi_state ICACHE_FLASH_ATTR httpd_cgi_state ICACHE_FLASH_ATTR
tplTermCfg(HttpdConnData *connData, char *token, void **arg) tplTermCfg(HttpdConnData *connData, char *token, void **arg)
{ {
#define BUFLEN TERM_TITLE_LEN #define BUFLEN 100 // large enough for backdrop
char buff[BUFLEN]; char buff[BUFLEN];
char buff2[10]; char buff2[10];
@@ -492,6 +498,9 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
else if (streq(token, "term_title")) { else if (streq(token, "term_title")) {
strncpy_safe(buff, termconf->title, BUFLEN); strncpy_safe(buff, termconf->title, BUFLEN);
} }
else if (streq(token, "backdrop")) {
strncpy_safe(buff, termconf->backdrop, BUFLEN);
}
else if (streq(token, "uart_baud")) { else if (streq(token, "uart_baud")) {
sprintf(buff, "%d", sysconf->uart_baudrate); sprintf(buff, "%d", sysconf->uart_baudrate);
} }
+4 -4
View File
@@ -146,10 +146,10 @@ static const u16 codepage_1[] ESP_CONST_DATA =
u'', // 0x0, 114 r - right up diagonal u'', // 0x0, 114 r - right up diagonal
u'', // 0x0, 115 s - right down diagonal u'', // 0x0, 115 s - right down diagonal
u'', // 0x0, 116 t u'', // 0x0, 116 t
0, // 0x0, 117 u u'', // 0x0, 117 u
0, // 0x0, 118 v u'', // 0x0, 118 v
0, // 0x0, 119 w u'', // 0x0, 119 w
0, // 0x0, 120 x u'', // 0x0, 120 x
0xE0B0, // powerline right triangle (filled), 121 y 0xE0B0, // powerline right triangle (filled), 121 y
0xE0B1, // powerline right triangle (hollow), 122 z 0xE0B1, // powerline right triangle (hollow), 122 z
0xE0B2, // powerline left triangle (filled), 123 { 0xE0B2, // powerline left triangle (filled), 123 {
+43 -7
View File
@@ -213,6 +213,7 @@ terminal_restore_defaults(void)
termconf->debugbar = SCR_DEF_DEBUGBAR; termconf->debugbar = SCR_DEF_DEBUGBAR;
termconf->allow_decopt_12 = SCR_DEF_DECOPT12; termconf->allow_decopt_12 = SCR_DEF_DECOPT12;
termconf->ascii_debug = SCR_DEF_ASCIIDEBUG; termconf->ascii_debug = SCR_DEF_ASCIIDEBUG;
termconf->backdrop[0] = 0;
} }
/** /**
@@ -247,6 +248,11 @@ terminal_apply_settings_noclear(void)
termconf->ascii_debug = SCR_DEF_ASCIIDEBUG; termconf->ascii_debug = SCR_DEF_ASCIIDEBUG;
changed = 1; changed = 1;
} }
if (termconf->config_version < 4) {
persist_dbg("termconf: Updating to version %d", 1);
termconf->backdrop[0] = 0;
changed = 1;
}
termconf->config_version = TERMCONF_VERSION; termconf->config_version = TERMCONF_VERSION;
@@ -385,6 +391,7 @@ screen_reset_do(bool size, bool labels)
if (labels) { if (labels) {
strcpy(termconf_live.title, termconf->title); strcpy(termconf_live.title, termconf->title);
strcpy(termconf_live.backdrop, termconf->backdrop);
for (int i = 1; i <= TERM_BTN_COUNT; i++) { for (int i = 1; i <= TERM_BTN_COUNT; i++) {
strcpy(termconf_live.btn[i], termconf->btn[i]); strcpy(termconf_live.btn[i], termconf->btn[i]);
@@ -394,7 +401,7 @@ screen_reset_do(bool size, bool labels)
termconf_live.show_buttons = termconf->show_buttons; termconf_live.show_buttons = termconf->show_buttons;
termconf_live.show_config_links = termconf->show_config_links; termconf_live.show_config_links = termconf->show_config_links;
topics |= TOPIC_CHANGE_TITLE | TOPIC_CHANGE_BUTTONS; topics |= TOPIC_CHANGE_TITLE | TOPIC_CHANGE_BUTTONS | TOPIC_CHANGE_BACKDROP;
} }
// initial values in the save buffer in case of receiving restore without storing first // initial values in the save buffer in case of receiving restore without storing first
@@ -1009,6 +1016,18 @@ screen_set_button_text(int num, const char *text)
NOTIFY_DONE(TOPIC_CHANGE_BUTTONS); NOTIFY_DONE(TOPIC_CHANGE_BUTTONS);
} }
/**
* Helper function to set terminalbackdrop
* @param url - url
*/
void ICACHE_FLASH_ATTR
screen_set_backdrop(const char *url)
{
NOTIFY_LOCK();
strncpy(termconf_live.backdrop, url, TERM_BACKDROP_LEN);
NOTIFY_DONE(TOPIC_CHANGE_BACKDROP);
}
/** /**
* Shift screen upwards * Shift screen upwards
*/ */
@@ -1907,6 +1926,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
#define TOPICMARK_BELL '!' #define TOPICMARK_BELL '!'
#define TOPICMARK_CURSOR 'C' #define TOPICMARK_CURSOR 'C'
#define TOPICMARK_SCREEN 'S' #define TOPICMARK_SCREEN 'S'
#define TOPICMARK_BACKDROP 'W'
if (ss == NULL) { if (ss == NULL) {
// START! // START!
@@ -2025,9 +2045,11 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
bufput_c(TOPICMARK_TITLE); bufput_c(TOPICMARK_TITLE);
int len = (int) strlen(termconf_live.title); int len = (int) strlen(termconf_live.title);
memcpy(bb, termconf_live.title, len); if (len > 0) {
bb += len; memcpy(bb, termconf_live.title, len);
remain -= len; bb += len;
remain -= len;
}
bufput_c('\x01'); bufput_c('\x01');
END_TOPIC END_TOPIC
@@ -2038,13 +2060,27 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
for (int i = 0; i < TERM_BTN_COUNT; i++) { for (int i = 0; i < TERM_BTN_COUNT; i++) {
int len = (int) strlen(termconf_live.btn[i]); int len = (int) strlen(termconf_live.btn[i]);
memcpy(bb, termconf_live.btn[i], len); if (len > 0) {
bb += len; memcpy(bb, termconf_live.btn[i], len);
remain -= len; bb += len;
remain -= len;
}
bufput_c('\x01'); bufput_c('\x01');
} }
END_TOPIC END_TOPIC
BEGIN_TOPIC(TOPIC_CHANGE_BACKDROP, TERM_BACKDROP_LEN+1+1)
bufput_c(TOPICMARK_BACKDROP);
int len = (int) strlen(termconf_live.backdrop);
if (len > 0) {
memcpy(bb, termconf_live.backdrop, len);
bb += len;
remain -= len;
}
bufput_c('\x01');
END_TOPIC
BEGIN_TOPIC(TOPIC_INTERNAL, 45) BEGIN_TOPIC(TOPIC_INTERNAL, 45)
bufput_c(TOPICMARK_DEBUG); bufput_c(TOPICMARK_DEBUG);
// General flags // General flags
+8 -2
View File
@@ -38,6 +38,7 @@
#define TERM_BTN_MSG_LEN 10 #define TERM_BTN_MSG_LEN 10
#define TERM_TITLE_LEN 64 #define TERM_TITLE_LEN 64
#define TERM_BTN_COUNT 5 #define TERM_BTN_COUNT 5
#define TERM_BACKDROP_LEN 100
#define SCR_DEF_DISPLAY_TOUT_MS 12 #define SCR_DEF_DISPLAY_TOUT_MS 12
#define SCR_DEF_DISPLAY_COOLDOWN_MS 35 #define SCR_DEF_DISPLAY_COOLDOWN_MS 35
@@ -74,8 +75,8 @@ enum CursorShape {
// Size designed for the terminal config structure // Size designed for the terminal config structure
// Must be constant to avoid corrupting user config after upgrade // Must be constant to avoid corrupting user config after upgrade
#define TERMCONF_SIZE 300 #define TERMCONF_SIZE 400
#define TERMCONF_VERSION 3 #define TERMCONF_VERSION 4
typedef struct { typedef struct {
u32 width; u32 width;
@@ -100,6 +101,7 @@ typedef struct {
bool debugbar; bool debugbar;
bool allow_decopt_12; bool allow_decopt_12;
bool ascii_debug; bool ascii_debug;
char backdrop[TERM_BACKDROP_LEN];
} TerminalConfigBundle; } TerminalConfigBundle;
// Live config // Live config
@@ -148,6 +150,8 @@ void screen_resize(int rows, int cols);
void screen_set_title(const char *title); void screen_set_title(const char *title);
/** Set a button text */ /** Set a button text */
void screen_set_button_text(int num, const char *text); void screen_set_button_text(int num, const char *text);
/** Change backdrop */
void screen_set_backdrop(const char *url);
// --- Encoding --- // --- Encoding ---
@@ -169,6 +173,7 @@ enum ScreenSerializeTopic {
TOPIC_CHANGE_CURSOR = (1<<5), TOPIC_CHANGE_CURSOR = (1<<5),
TOPIC_INTERNAL = (1<<6), // debugging internal state TOPIC_INTERNAL = (1<<6), // debugging internal state
TOPIC_BELL = (1<<7), // beep TOPIC_BELL = (1<<7), // beep
TOPIC_CHANGE_BACKDROP = (1<<8),
TOPIC_FLAG_NOCLEAN = (1<<15), // do not clean dirty extents TOPIC_FLAG_NOCLEAN = (1<<15), // do not clean dirty extents
// combos // combos
@@ -177,6 +182,7 @@ enum ScreenSerializeTopic {
TOPIC_CHANGE_CONTENT_ALL | TOPIC_CHANGE_CONTENT_ALL |
TOPIC_CHANGE_CURSOR | TOPIC_CHANGE_CURSOR |
TOPIC_CHANGE_TITLE | TOPIC_CHANGE_TITLE |
TOPIC_CHANGE_BACKDROP |
TOPIC_CHANGE_BUTTONS, TOPIC_CHANGE_BUTTONS,
}; };
+2
View File
@@ -150,6 +150,8 @@ static void ICACHE_FLASH_ATTR user_start(void *unused)
// Critically important for client application if any kind of screen persistence / content re-use is needed // Critically important for client application if any kind of screen persistence / content re-use is needed
UART_WriteChar(UART0, CAN, UART_TIMEOUT_US); // 0x18 - 24 - CAN UART_WriteChar(UART0, CAN, UART_TIMEOUT_US); // 0x18 - 24 - CAN
dbg("tsize=%d", sizeof(TerminalConfigBundle));
#if DEBUG_HEAP #if DEBUG_HEAP
// Heap use timer & blink // Heap use timer & blink
TIMER_START(&prHeapTimer, prHeapTimerCb, HEAP_TIMER_MS, 1); TIMER_START(&prHeapTimer, prHeapTimerCb, HEAP_TIMER_MS, 1);
+2 -2
View File
@@ -9,10 +9,10 @@
#define STR(x) STR_HELPER(x) #define STR(x) STR_HELPER(x)
#define FW_V_MAJOR 2 #define FW_V_MAJOR 2
#define FW_V_MINOR 1 #define FW_V_MINOR 2
#define FW_V_PATCH 0 #define FW_V_PATCH 0
#define FW_V_SUFFIX "" #define FW_V_SUFFIX ""
#define FW_CODENAME "Anthill" // 2.1.0 #define FW_CODENAME "Bluet" // 2.2
#define FW_CODENAME_QUOTED "\""FW_CODENAME"\"" #define FW_CODENAME_QUOTED "\""FW_CODENAME"\""
#define FW_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) FW_V_SUFFIX #define FW_VERSION STR(FW_V_MAJOR) "." STR(FW_V_MINOR) "." STR(FW_V_PATCH) FW_V_SUFFIX