Compare commits

..

46 Commits

Author SHA1 Message Date
Ondřej Hruška e125aec459
Merge pull request #262 from schneemaier/patch-1 6 years ago
schneemaier ee61b38026
Update flash.sh 6 years ago
Ondřej Hruška 7e4d25272a
Support for the new GPIO control web interface 6 years ago
Ondřej Hruška d3130c43da
API for writing, toggling, pulsing and reading GPIO pins 6 years ago
Ondřej Hruška 70020581fd
bump frontend revision 6 years ago
Ondřej Hruška 8cc9ae8df4
Merge branch 'patch-clickbuttons' 7 years ago
Ondřej Hruška 6971fe2036
v bump 7 years ago
Ondřej Hruška 9f97d5c9d1
bugfix for the clickbuttons not working 7 years ago
Ondřej Hruška 5343d44ea8
bump the frontend v for the hu patch 7 years ago
Ondřej Hruška 76fbf5d65f
Merge branch 'work' 7 years ago
Ondřej Hruška 6b4013f779
v bump 7 years ago
Ondřej Hruška 0438f88c83
update frontend ref 7 years ago
Ondřej Hruška d0d7200e57
Merge branch 'double-lines' into work 7 years ago
Ondřej Hruška 78aaa949a3
support double lines 7 years ago
Ondřej Hruška 5dbb592946
added OSC for color buttons 7 years ago
Ondřej Hruška d9aa0418fa
updated protocol, added button themeing and font conf 7 years ago
Ondřej Hruška 9008f2e1af
ref upd 7 years ago
Ondřej Hruška bc240e676b
added hu to build script 7 years ago
Ondřej Hruška 3af928b39d
version bump for pre-release build 7 years ago
Ondřej Hruška ead9120ae8
ref update 7 years ago
Ondřej Hruška dca44963e8
add new OSC and configurable button count 7 years ago
Ondřej Hruška 3c12efe28f
updated submodule ref 7 years ago
Ondřej Hruška e98588b317
Fix broken validation for bm* 7 years ago
Ondřej Hruška 0e6c297fe4
be smarter about detecting changes in loaded ini 7 years ago
Ondřej Hruška 3e4181f18e
Merge branch 'xmacro-config' into work 7 years ago
Ondřej Hruška 2f4cd08c60
import now fully working 7 years ago
Ondřej Hruška ba2b73a287
ini upload and parse (no loading to conf yet) 7 years ago
Ondřej Hruška 4e285694d1
ini export! 7 years ago
Ondřej Hruška 9700465927
fixed many bugs 7 years ago
Ondřej Hruška 1ab4101ac0
use xmacros for terminal config 7 years ago
Ondřej Hruška f0bc70553a
use xmacros for system 7 years ago
Ondřej Hruška 3eac0ee794
fmt again 7 years ago
Ondřej Hruška 2c80b59d71
formatting 7 years ago
Ondřej Hruška 9146c9880a
slightly cleaner XTABLE_WIFI 7 years ago
Ondřej Hruška cabd42f2b4
base of xmarco-based config, used for wifi and network 7 years ago
Ondřej Hruška fbd4693035
Merge branch 'master' into work 7 years ago
Ondřej Hruška c5670c09bb
update frontend ref 7 years ago
Ondřej Hruška 4e74d1cd79
updated front-end ref 7 years ago
Ondřej Hruška 4c8360dbb5
Merge remote-tracking branch 'origin/work' 7 years ago
Ondřej Hruška c0adc253b4
add cgi for setting background image 7 years ago
Ondřej Hruška be3e06aac1
version bump 7 years ago
Ondřej Hruška 992c927490
Merge remote-tracking branch 'origin/work' 7 years ago
Ondřej Hruška ef3b02e68c
added new chars to cp 1 7 years ago
Ondřej Hruška 1c556457c7
Merge branch 'new-codepages' into work 7 years ago
Ondřej Hruška c90de79a34 Update README.md 7 years ago
Ondřej Hruška df404f5f63
2.1.1 - patched cslang 7 years ago
  1. 5
      .gitignore
  2. 5
      CMakeLists.txt
  3. 53
      README.md
  4. 4
      build_parser.sh
  5. 1
      esphttpdconfig.mk.example
  6. 2
      front-end
  7. 2
      libesphttpd
  8. 3
      prepro.sh
  9. 4
      rel-tpl/flash.sh
  10. 1
      ship.sh
  11. 72
      user/apars_osc.c
  12. 24
      user/apars_short.c
  13. 1
      user/api.h
  14. 2
      user/cgi_d2d.c
  15. 174
      user/cgi_network.c
  16. 423
      user/cgi_persist.c
  17. 2
      user/cgi_persist.h
  18. 4
      user/cgi_sockets.c
  19. 311
      user/cgi_system.c
  20. 2
      user/cgi_system.h
  21. 486
      user/cgi_term_cfg.c
  22. 201
      user/cgi_wifi.c
  23. 8
      user/character_sets.h
  24. 144
      user/config_xmacros.c
  25. 100
      user/config_xmacros.h
  26. 526
      user/ini_parser.c
  27. 65
      user/ini_parser.h
  28. 284
      user/ini_parser.rl
  29. 63
      user/io.c
  30. 1
      user/io.h
  31. 8
      user/routes.c
  32. 583
      user/screen.c
  33. 108
      user/screen.h
  34. 22
      user/serial.c
  35. 119
      user/syscfg.c
  36. 56
      user/syscfg.h
  37. 1
      user/uart_handler.c
  38. 6
      user/user_main.c
  39. 6
      user/version.h
  40. 193
      user/wifimgr.c
  41. 69
      user/wifimgr.h

5
.gitignore vendored

@ -16,8 +16,9 @@ esphttpdconfig.mk
# Garbage added by CLion # Garbage added by CLion
.idea/ .idea/
cmake-build-debug/ cmake-build-debug*/
.sass-cache .sass-cache
*.map *.map
.gitignore !.gitignore

@ -146,9 +146,12 @@ set(SOURCE_FILES
user/jstring.c user/jstring.c
user/jstring.h user/jstring.h
user/character_sets.h user/character_sets.h
user/ini_parser.h
user/ini_parser.c
user/ini_parser.rl
user/utf8.h user/utf8.h
user/utf8.c user/utf8.c
user/cgi_logging.h) user/cgi_logging.h user/config_xmacros.h user/config_xmacros.c)
include_directories(include) include_directories(include)
include_directories(libesphttpd/esphttpclient) include_directories(libesphttpd/esphttpclient)

@ -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

@ -3,6 +3,10 @@
echo "-- Building parser from Ragel source --" echo "-- Building parser from Ragel source --"
ragel -L -G0 user/ansi_parser.rl -o user/ansi_parser.c ragel -L -G0 user/ansi_parser.rl -o user/ansi_parser.c
ragel -L -G0 user/ini_parser.rl -o user/ini_parser.c
sed -i "s/static const char _ansi_actions\[\]/static const char _ansi_actions\[\] ESP_CONST_DATA/" user/ansi_parser.c sed -i "s/static const char _ansi_actions\[\]/static const char _ansi_actions\[\] ESP_CONST_DATA/" user/ansi_parser.c
sed -i "s/static const char _ansi_eof_actions\[\]/static const char _ansi_eof_actions\[\] ESP_CONST_DATA/" user/ansi_parser.c sed -i "s/static const char _ansi_eof_actions\[\]/static const char _ansi_eof_actions\[\] ESP_CONST_DATA/" user/ansi_parser.c
sed -i "s/static const char _ini_actions\[\]/static const char _ini_actions\[\] ESP_CONST_DATA/" user/ini_parser.c
sed -i "s/static const char _ini_eof_actions\[\]/static const char _ini_eof_actions\[\] ESP_CONST_DATA/" user/ini_parser.c

@ -40,6 +40,7 @@ ESP_SPI_FLASH_SIZE_K = 1024
GLOBAL_CFLAGS = \ GLOBAL_CFLAGS = \
-DASYNC_LOG=1 \ -DASYNC_LOG=1 \
-DDEBUG_INI=1 \
-DDEBUG_D2D=0 \ -DDEBUG_D2D=0 \
-DDEBUG_ROUTER=0 \ -DDEBUG_ROUTER=0 \
-DDEBUG_CAPTDNS=0 \ -DDEBUG_CAPTDNS=0 \

@ -1 +1 @@
Subproject commit 8b610aa95bf8f7185c28cbe96c7485ec1af2383c Subproject commit 9aae724df2ce60a144eb9f170bb09bdbd02297df

@ -1 +1 @@
Subproject commit e4ecf0724e36c828be5222eddce58a6a5cd2386f Subproject commit d6651ca0d11b7950078247b8305f5b23a6be6c6c

@ -0,0 +1,3 @@
#!/usr/bin/env bash
xtensa-lx106-elf-gcc -E -Iuser -Ilibesphttpd/include -Iesp_iot_sdk_v1.5.2/include -Iinclude $@

@ -18,8 +18,8 @@ if [ -z ${ESPTOOL} ]; then
fi fi
fi fi
[ -z ESPPORT ] && ESPPORT=/dev/ttyUSB0 [ -z ${ESPPORT} ] && ESPPORT=/dev/ttyUSB0
[ -z ESPBAUD ] && ESPBAUD=460800 [ -z ${ESPBAUD} ] && ESPBAUD=460800
set -x set -x
${ESPTOOL} --port ${ESPPORT} --baud ${ESPBAUD} \ ${ESPTOOL} --port ${ESPPORT} --baud ${ESPBAUD} \

@ -64,6 +64,7 @@ if [ -z "$ESP_LANG" ]; then
buildlang cs buildlang cs
buildlang en buildlang en
buildlang de buildlang de
buildlang hu
else else
buildlang ${ESP_LANG} buildlang ${ESP_LANG}
fi fi

@ -16,6 +16,45 @@
#include "ansi_parser.h" #include "ansi_parser.h"
#include "cgi_sockets.h" #include "cgi_sockets.h"
/**
* Handle ESPTerm-specific command
*
* @param n0 - top-level, 20-39
* @param n1 - sub-command
* @param buffer - buffer past the second command and its semicolon
*/
void ICACHE_FLASH_ATTR
handle_espterm_osc(int n0, int n1, char *buffer)
{
if (n0 == 27) {
// Miscellaneous
if (n1 == 1) {
screen_set_backdrop(buffer);
}
else if (n1 == 2) {
screen_set_button_count(atoi(buffer));
}
else goto bad;
}
else if (n0 == 28) {
// Button label
screen_set_button_text(n1, buffer);
}
else if (n0 == 29) {
// Button message
screen_set_button_message(n1, buffer);
}
else if (n0 == 30) {
// Button color
screen_set_button_color(n1, buffer);
}
else goto bad;
return;
bad:
ansi_warn("No ESPTerm option at %d.%d", n0, n1);
}
/** /**
* Helper function to parse incoming OSC (Operating System Control) * Helper function to parse incoming OSC (Operating System Control)
* @param buffer - the OSC body (after OSC and before ST) * @param buffer - the OSC body (after OSC and before ST)
@ -23,8 +62,9 @@
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
apars_handle_osc(char *buffer) apars_handle_osc(char *buffer)
{ {
const char *origbuf = buffer;
int n = 0; int n = 0;
char c = 0; char c;
while ((c = *buffer++) != 0) { while ((c = *buffer++) != 0) {
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
n = (n * 10 + (c - '0')); n = (n * 10 + (c - '0'));
@ -49,20 +89,46 @@ apars_handle_osc(char *buffer)
buffer[0] = 'G'; buffer[0] = 'G';
notify_growl(buffer); notify_growl(buffer);
} }
else if (n >= 20 && n < 40) {
// New-style ESPTerm OSC
int n0 = n;
// Find sub-command
n = 0;
while ((c = *buffer++) != 0) {
if (c >= '0' && c <= '9') {
n = (n * 10 + (c - '0'));
} else {
break;
}
}
if (c == ';') {
handle_espterm_osc(n0, n, buffer);
} else {
ansi_warn("BAD OSC: %s", origbuf);
}
}
else if (n == 70) {
// ESPTerm: backdrop
ansi_warn("OSC 70 is deprecated, use 27;1");
screen_set_backdrop(buffer);
}
else if (n >= 81 && n <= 85) { else if (n >= 81 && n <= 85) {
// ESPTerm: action button label // ESPTerm: action button label
ansi_warn("OSC 8x is deprecated, use 28;x");
screen_set_button_text(n - 80, buffer); screen_set_button_text(n - 80, buffer);
} }
else if (n >= 91 && n <= 95) { else if (n >= 91 && n <= 95) {
// ESPTerm: action button message // ESPTerm: action button message
strncpy(termconf_live.btn_msg[n - 91], buffer, TERM_BTN_MSG_LEN); ansi_warn("OSC 9x is deprecated, use 29;x");
screen_set_button_message(n - 90, buffer);
} }
else { else {
ansi_noimpl("OSC %d ; %s ST", n, buffer); ansi_noimpl("OSC %d ; %s ST", n, buffer);
} }
} }
else { else {
ansi_warn("BAD OSC: %s", buffer); ansi_warn("BAD OSC: %s", origbuf);
apars_show_context(); apars_show_context();
} }
} }

@ -94,21 +94,33 @@ apars_handle_space_cmd(char c)
void ICACHE_FLASH_ATTR apars_handle_hash_cmd(char c) void ICACHE_FLASH_ATTR apars_handle_hash_cmd(char c)
{ {
switch(c) { switch(c) {
case '1': // Double height, single width, top half (CUSTOM!)
screen_set_line_attr(1, 2, 1);
break;
case '2': // Double height, single width, bottom half (CUSTOM!)
screen_set_line_attr(1, 1, 2);
break;
case '3': // Double size, top half case '3': // Double size, top half
case '4': // Single size, bottom half screen_set_line_attr(2, 2, 1);
break;
case '4': // Double size, bottom half
screen_set_line_attr(2, 1, 2);
break;
case '5': // Single width, single height case '5': // Single width, single height
screen_set_line_attr(1, 1, 1);
break;
case '6': // Double width case '6': // Double width
ansi_noimpl("Double Size Line"); screen_set_line_attr(2, 1, 1);
break; break;
case '8': case '8':
screen_fill_with_E(); screen_fill_with_E();
break; break;
// development codes - do not use! // // development codes - do not use!
case '7': // case '7':
http_get("http://wtfismyip.com/text", NULL, http_callback_example); // http_get("http://wtfismyip.com/text", NULL, http_callback_example);
break; // break;
default: default:
ansi_noimpl("ESC # %c", c); ansi_noimpl("ESC # %c", c);

@ -11,5 +11,6 @@
#define API_REBOOT "/api/v1/reboot" #define API_REBOOT "/api/v1/reboot"
#define API_PING "/api/v1/ping" #define API_PING "/api/v1/ping"
#define API_CLEAR "/api/v1/clear" #define API_CLEAR "/api/v1/clear"
#define API_GPIO "/api/v1/gpio"
#endif //ESPTERM_API_H #endif //ESPTERM_API_H

@ -178,7 +178,7 @@ d2d_parse_command(char *msg)
else if (strstarts(msg, "H;")) { else if (strstarts(msg, "H;")) {
if (request_pending) return false; if (request_pending) return false;
// Send a esp-esp message // Send a HTTP request
msg += 2; msg += 2;
const char *method = NULL; const char *method = NULL;
const char *params = NULL; const char *params = NULL;

@ -9,6 +9,7 @@ configuring the network settings
#include "persist.h" #include "persist.h"
#include "helpers.h" #include "helpers.h"
#include "cgi_logging.h" #include "cgi_logging.h"
#include "config_xmacros.h"
#define SET_REDIR_SUC "/cfg/network" #define SET_REDIR_SUC "/cfg/network"
#define SET_REDIR_ERR SET_REDIR_SUC"?err=" #define SET_REDIR_ERR SET_REDIR_SUC"?err="
@ -48,138 +49,10 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
// ---- AP DHCP server lease time ---- // ---- AP DHCP server lease time ----
if (GET_ARG("ap_dhcp_time")) { #define XSTRUCT wificonf
cgi_dbg("Setting DHCP lease time to: %s min.", buff); #define X XSET_CGI_FUNC
int min = atoi(buff); XTABLE_WIFICONF
if (min >= 1 && min <= 2880) { #undef X
if (wificonf->ap_dhcp_time != min) {
wificonf->ap_dhcp_time = (u16) min;
wifi_change_flags.ap = true;
}
} else {
cgi_warn("Lease time %s out of allowed range 1-2880.", buff);
redir_url += sprintf(redir_url, "ap_dhcp_time,");
}
}
// ---- AP DHCP start and end IP ----
if (GET_ARG("ap_dhcp_start")) {
cgi_dbg("Setting DHCP range start IP to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
if (wificonf->ap_dhcp_range.start_ip.addr != ip) {
wificonf->ap_dhcp_range.start_ip.addr = ip;
wifi_change_flags.ap = true;
}
} else {
cgi_warn("Bad IP: %s", buff);
redir_url += sprintf(redir_url, "ap_dhcp_start,");
}
}
if (GET_ARG("ap_dhcp_end")) {
cgi_dbg("Setting DHCP range end IP to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
if (wificonf->ap_dhcp_range.end_ip.addr != ip) {
wificonf->ap_dhcp_range.end_ip.addr = ip;
wifi_change_flags.ap = true;
}
} else {
cgi_warn("Bad IP: %s", buff);
redir_url += sprintf(redir_url, "ap_dhcp_end,");
}
}
// ---- AP local address & config ----
if (GET_ARG("ap_addr_ip")) {
cgi_dbg("Setting AP local IP to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
if (wificonf->ap_addr.ip.addr != ip) {
wificonf->ap_addr.ip.addr = ip;
wificonf->ap_addr.gw.addr = ip; // always the same, we're the router here
wifi_change_flags.ap = true;
}
} else {
cgi_warn("Bad IP: %s", buff);
redir_url += sprintf(redir_url, "ap_addr_ip,");
}
}
if (GET_ARG("ap_addr_mask")) {
cgi_dbg("Setting AP local IP netmask to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
if (wificonf->ap_addr.netmask.addr != ip) {
// ideally this should be checked to match the IP.
// Let's hope users know what they're doing
wificonf->ap_addr.netmask.addr = ip;
wifi_change_flags.ap = true;
}
} else {
cgi_warn("Bad IP mask: %s", buff);
redir_url += sprintf(redir_url, "ap_addr_mask,");
}
}
// ---- Station enable/disable DHCP ----
// DHCP enable / disable (disable means static IP is enabled)
if (GET_ARG("sta_dhcp_enable")) {
cgi_dbg("DHCP enable = %s", buff);
int enable = atoi(buff);
if (wificonf->sta_dhcp_enable != enable) {
wificonf->sta_dhcp_enable = (bool)enable;
wifi_change_flags.sta = true;
}
}
// ---- Station IP config (Static IP) ----
if (GET_ARG("sta_addr_ip")) {
cgi_dbg("Setting Station mode static IP to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
if (wificonf->sta_addr.ip.addr != ip) {
wificonf->sta_addr.ip.addr = ip;
wifi_change_flags.sta = true;
}
} else {
cgi_warn("Bad IP: %s", buff);
redir_url += sprintf(redir_url, "sta_addr_ip,");
}
}
if (GET_ARG("sta_addr_mask")) {
cgi_dbg("Setting Station mode static IP netmask to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0 && ip != 0xFFFFFFFFUL) {
if (wificonf->sta_addr.netmask.addr != ip) {
wificonf->sta_addr.netmask.addr = ip;
wifi_change_flags.sta = true;
}
} else {
cgi_warn("Bad IP mask: %s", buff);
redir_url += sprintf(redir_url, "sta_addr_mask,");
}
}
if (GET_ARG("sta_addr_gw")) {
cgi_dbg("Setting Station mode static IP default gateway to: \"%s\"", buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0) {
if (wificonf->sta_addr.gw.addr != ip) {
wificonf->sta_addr.gw.addr = ip;
wifi_change_flags.sta = true;
}
} else {
cgi_warn("Bad gw IP: %s", buff);
redir_url += sprintf(redir_url, "sta_addr_gw,");
}
}
(void) redir_url; (void) redir_url;
@ -216,7 +89,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[20]; char buff[64];
u8 mac[6]; u8 mac[6];
if (token == NULL) { if (token == NULL) {
@ -226,34 +99,13 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *toke
strcpy(buff, ""); // fallback strcpy(buff, ""); // fallback
if (streq(token, "ap_dhcp_time")) { #define XSTRUCT wificonf
sprintf(buff, "%d", wificonf->ap_dhcp_time); #define X XGET_CGI_FUNC
} XTABLE_WIFICONF
else if (streq(token, "ap_dhcp_start")) { #undef X
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.start_ip.addr));
} // non-config
else if (streq(token, "ap_dhcp_end")) { if (streq(token, "sta_mac")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.end_ip.addr));
}
else if (streq(token, "ap_addr_ip")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_addr.ip.addr));
}
else if (streq(token, "ap_addr_mask")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_addr.netmask.addr));
}
else if (streq(token, "sta_dhcp_enable")) {
sprintf(buff, "%d", wificonf->sta_dhcp_enable);
}
else if (streq(token, "sta_addr_ip")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.ip.addr));
}
else if (streq(token, "sta_addr_mask")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.netmask.addr));
}
else if (streq(token, "sta_addr_gw")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.gw.addr));
}
else if (streq(token, "sta_mac")) {
wifi_get_macaddr(STATION_IF, mac); wifi_get_macaddr(STATION_IF, mac);
sprintf(buff, MACSTR, MAC2STR(mac)); sprintf(buff, MACSTR, MAC2STR(mac));
} }

@ -7,8 +7,13 @@ Cgi/template routines for configuring non-wifi settings
#include "persist.h" #include "persist.h"
#include "helpers.h" #include "helpers.h"
#include "cgi_logging.h" #include "cgi_logging.h"
#include "version.h"
#include "screen.h"
#include "config_xmacros.h"
#include "ini_parser.h"
#define SET_REDIR_SUC "/cfg/system" #define SET_REDIR_SUC "/cfg/system"
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
static bool ICACHE_FLASH_ATTR static bool ICACHE_FLASH_ATTR
verify_admin_pw(const char *pw) verify_admin_pw(const char *pw)
@ -74,3 +79,421 @@ cgiPersistRestoreHard(HttpdConnData *connData)
httpdRedirect(connData, SET_REDIR_SUC "?msg=All%20settings%20restored%20to%20factory%20defaults."); httpdRedirect(connData, SET_REDIR_SUC "?msg=All%20settings%20restored%20to%20factory%20defaults.");
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
// -------------- Export --------------
#define httpdSend_orDie(conn, data, len) do { if (!httpdSend((conn), (data), (len))) return false; } while (0)
/* encode for double-quoted string */
int ICACHE_FLASH_ATTR httpdSend_dblquot(HttpdConnData *conn, const char *data, int len)
{
int start = 0, end = 0;
char c;
if (conn->conn==NULL) return 0;
if (len < 0) len = (int) strlen(data);
if (len==0) return 0;
for (end = 0; end < len; end++) {
c = data[end];
if (c == 0) {
// we found EOS
break;
}
if (c == '"' || c == '\'' || c == '\\' || c == '\n' || c == '\r' || c == '\x1b') {
if (start < end) httpdSend_orDie(conn, data + start, end - start);
start = end + 1;
}
if (c == '"') httpdSend_orDie(conn, "\\\"", 2);
else if (c == '\'') httpdSend_orDie(conn, "\\'", 2);
else if (c == '\\') httpdSend_orDie(conn, "\\\\", 2);
else if (c == '\n') httpdSend_orDie(conn, "\\n", 2);
else if (c == '\r') httpdSend_orDie(conn, "\\r", 2);
else if (c == '\x1b') httpdSend_orDie(conn, "\\e", 2);
}
if (start < end) httpdSend_orDie(conn, data + start, end - start);
return 1;
}
/**
* Export settings to INI
*
* @param connData
* @return status
*/
httpd_cgi_state ICACHE_FLASH_ATTR
cgiPersistExport(HttpdConnData *connData)
{
char buff[256];
u8 mac[6];
int step = (int) connData->cgiData;
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (step == 0) {
wifi_get_macaddr(SOFTAP_IF, mac);
sprintf(buff, "attachment; filename=ESPTerm_%02X%02X%02X_%s.ini",
mac[3], mac[4], mac[5], FW_VERSION);
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Disposition", buff);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
sprintf(buff, "# == ESPTerm config export ==\r\n"
"# Device: %02X%02X%02X - %s\r\n"
"# Version: %s\r\n",
mac[3], mac[4], mac[5],
termconf->title,
FW_VERSION);
httpdSend(connData, buff, -1);
}
// do not export SSID if unchanged - embeds unique ID that should
// not be overwritten in target.
char defSSID[20];
sprintf(defSSID, "TERM-%02X%02X%02X", mac[3], mac[4], mac[5]);
bool quoted;
#define X(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \
do { if (allow) { \
xget(buff, deref XSTRUCT->name); \
if (streq(#name, "ap_ssid") && streq(buff, defSSID)) break; \
\
quoted = false; \
quoted |= streq(#type, "char"); \
quoted |= streq(#type, "uchar"); \
if (strstarts(#name, "bm")) quoted=false; \
\
httpdSend(connData, "\r\n"#name " = ", -1); \
if (quoted) { \
httpdSend(connData, "\"", 1); \
httpdSend_dblquot(connData, buff, -1); \
httpdSend(connData, "\"", 1); \
} else { \
httpdSend(connData, buff, -1); \
} \
} } while(0);
#define admin 1
#define tpl 1
if (step == 1) {
httpdSend(connData, "\r\n[system]", -1);
#define XSTRUCT sysconf
XTABLE_SYSCONF
#undef XSTRUCT
httpdSend(connData, "\r\n", -1);
}
if (step == 2) {
httpdSend(connData, "\r\n[wifi]", -1);
#define XSTRUCT wificonf
XTABLE_WIFICONF
#undef XSTRUCT
httpdSend(connData, "\r\n", -1);
}
if (step == 3) {
httpdSend(connData, "\r\n[terminal]", -1);
#define XSTRUCT termconf
XTABLE_TERMCONF
#undef XSTRUCT
httpdSend(connData, "\r\n", -1);
return HTTPD_CGI_DONE;
}
#undef X
connData->cgiData = (void *) (step + 1);
return HTTPD_CGI_MORE;
}
// -------------- IMPORT --------------
struct IniUpload {
TerminalConfigBundle *term_backup;
WiFiConfigBundle *wifi_backup;
SystemConfigBundle *sys_backup;
bool term_ok;
bool wifi_ok;
bool sys_ok;
bool term_any;
bool wifi_any;
bool sys_any;
};
static void ICACHE_FLASH_ATTR iniCb(const char *section, const char *key, const char *value, void *userData)
{
HttpdConnData *connData = (HttpdConnData *)userData;
struct IniUpload *state;
if (!connData || !connData->cgiData) {
error("userData or state is NULL!");
return;
}
state = connData->cgiData;
// cgi_dbg("%s.%s = %s", section, key, value);
/** used for INI */
#define X(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \
if (streq(#name, key)) { \
found = true; \
type *_p = (type *) &XSTRUCT->name; \
enum xset_result res = xset(#name, _p, value, (const void*) (xsarg)); \
if (res == XSET_SET) { changed = true; xnotify; } \
else if (res == XSET_FAIL) { ok = false; } \
break; \
}
// notify flag, unused here
bool uart_changed = false;
bool found = false;
bool ok = true;
bool changed = false;
if (streq(section, "terminal")) {
do {
#define XSTRUCT termconf
XTABLE_TERMCONF
#undef XSTRUCT
} while (0);
if (found) {
if (!ok) state->term_ok = false;
state->term_any |= changed;
}
}
else if (streq(section, "wifi")) {
do {
#define XSTRUCT wificonf
XTABLE_WIFICONF
#undef XSTRUCT
} while (0);
if (found) {
if (!ok) state->wifi_ok = false;
state->wifi_any |= changed;
}
}
else if (streq(section, "system")) {
do {
#define XSTRUCT sysconf
XTABLE_SYSCONF
#undef XSTRUCT
} while (0);
if (found) {
if (!ok) state->sys_ok = false;
state->sys_any |= changed;
}
}
if (!found) cgi_warn("Unknown key %s.%s!", section, key);
#undef X
}
static void ICACHE_FLASH_ATTR
freeIniUploadStruct(HttpdConnData *connData)
{
cgi_dbg("Free struct...");
struct IniUpload *state;
if (connData && connData->cgiData) {
state = connData->cgiData;
if (state->sys_backup != NULL) free(state->sys_backup);
if (state->wifi_backup != NULL) free(state->wifi_backup);
if (state->term_backup != NULL) free(state->term_backup);
free(state);
connData->cgiData = NULL;
}
}
static httpd_cgi_state ICACHE_FLASH_ATTR
postRecvHdl(HttpdConnData *connData, char *data, int len)
{
struct IniUpload *state = connData->cgiData;
if (!state) return HTTPD_CGI_DONE;
cgi_dbg("INI parse - postRecvHdl");
if (len>0) {
// Discard the boundary marker (there is only one)
char *bdr = connData->post->multipartBoundary;
char *foundat = strstr(data, bdr);
if (foundat != NULL) {
*foundat = '#'; // make it a comment
}
ini_parse(data, (size_t) len);
}
cgi_dbg("Closing parser");
ini_parse_end();
cgi_dbg("INI parse - end.");
// abort if bad screen size
bool tooLarge = (termconf->width*termconf->height > MAX_SCREEN_SIZE);
state->term_ok &= !tooLarge;
if (tooLarge) cgi_warn("Bad term screen size!");
bool suc = state->term_ok && state->wifi_ok && state->sys_ok;
bool any = state->term_any || state->wifi_any || state->sys_any;
cgi_dbg("Evaluating results...");
if (!state->term_ok) cgi_warn("Terminal settings rejected.");
if (!state->wifi_ok) cgi_warn("WiFi settings rejected.");
if (!state->sys_ok) cgi_warn("System settings rejected.");
if (!suc) {
cgi_warn("Some validation failed, reverting all!");
memcpy(termconf, state->term_backup, sizeof(TerminalConfigBundle));
memcpy(wificonf, state->wifi_backup, sizeof(WiFiConfigBundle));
memcpy(sysconf, state->sys_backup, sizeof(SystemConfigBundle));
}
else {
if (state->term_any) {
cgi_dbg("Applying terminal settings");
terminal_apply_settings();
}
if (state->sys_any) {
cgi_dbg("Applying system settings");
sysconf_apply_settings();
}
if (state->wifi_any) {
cgi_dbg("Applying WiFi settings (scheduling...)");
wifimgr_apply_settings_later(2000);
}
if (any) {
cgi_dbg("Persisting results");
persist_store();
} else {
cgi_warn("Nothing written.");
}
}
cgi_dbg("Redirect");
char buff[100];
char *b = buff;
if (suc) {
if (any) {
httpdRedirect(connData, SET_REDIR_SUC"?msg=Settings%20loaded%20and%20applied.");
} else {
httpdRedirect(connData, SET_REDIR_SUC"?msg=No%20settings%20changed.");
}
} else {
b += sprintf(b, SET_REDIR_SUC"?errmsg=Errors%%20in:%%20");
bool comma = false;
if (!state->sys_ok) {
b += sprintf(b, "System%%20config");
comma = true;
}
if (!state->wifi_ok) {
if (comma) b += sprintf(b, ",%%20");
b += sprintf(b, "WiFi%%20config");
}
if (!state->term_ok) {
if (comma) b += sprintf(b, ",%%20");
b += sprintf(b, "Terminal%%20config");
}
httpdRedirect(connData, buff);
}
// Clean up.
freeIniUploadStruct(connData);
return HTTPD_CGI_DONE;
}
/**
* Import settings from INI
*
* @param connData
* @return status
*/
httpd_cgi_state ICACHE_FLASH_ATTR
cgiPersistImport(HttpdConnData *connData)
{
struct IniUpload *state = NULL;
if (connData->conn == NULL) {
//Connection aborted. Clean up.
freeIniUploadStruct(connData);
return HTTPD_CGI_DONE;
}
char *start = strstr(connData->post->buff, "\r\n\r\n");
if (start == NULL) {
error("Malformed attachment POST!");
httpdStartResponse(connData, 400);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
httpdSend(connData, "Bad format.", -1);
return HTTPD_CGI_DONE;
}
cgi_info("Starting INI parser for uploaded file...");
state = malloc(sizeof(struct IniUpload));
if (!state) {
error("state struct alloc fail");
return HTTPD_CGI_DONE;
}
state->sys_backup = NULL;
state->wifi_backup = NULL;
state->term_backup = NULL;
connData->cgiData = state;
cgi_dbg("Allocating backup buffers");
state->sys_backup = malloc(sizeof(SystemConfigBundle));
state->wifi_backup = malloc(sizeof(WiFiConfigBundle));
state->term_backup = malloc(sizeof(TerminalConfigBundle));
cgi_dbg("Copying orig data");
memcpy(state->sys_backup, sysconf, sizeof(SystemConfigBundle));
memcpy(state->wifi_backup, wificonf, sizeof(WiFiConfigBundle));
memcpy(state->term_backup, termconf, sizeof(TerminalConfigBundle));
state->sys_ok = true;
state->wifi_ok = true;
state->term_ok = true;
state->sys_any = false;
state->wifi_any = false;
state->term_any = false;
cgi_dbg("Parser starts!");
ini_parse_begin(iniCb, connData);
size_t datalen = (size_t) connData->post->buffLen - (start - connData->post->buff);
ini_parse(start, datalen);
connData->recvHdl = postRecvHdl;
// special case for too short ini
int bytes_remain = connData->post->len;
bytes_remain -= connData->post->buffLen;
if (bytes_remain <= 0) {
return connData->recvHdl(connData, NULL, 0);
}
// continues in recvHdl
return HTTPD_CGI_MORE;
}

@ -6,5 +6,7 @@
httpd_cgi_state cgiPersistWriteDefaults(HttpdConnData *connData); httpd_cgi_state cgiPersistWriteDefaults(HttpdConnData *connData);
httpd_cgi_state cgiPersistRestoreDefaults(HttpdConnData *connData); httpd_cgi_state cgiPersistRestoreDefaults(HttpdConnData *connData);
httpd_cgi_state cgiPersistRestoreHard(HttpdConnData *connData); httpd_cgi_state cgiPersistRestoreHard(HttpdConnData *connData);
httpd_cgi_state cgiPersistExport(HttpdConnData *connData);
httpd_cgi_state cgiPersistImport(HttpdConnData *connData);
#endif #endif

@ -271,8 +271,8 @@ static void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int
case 'b': case 'b':
// action button press // action button press
btnNum = (u8) (data[1]); btnNum = (u8) (data[1]);
if (btnNum > 0 && btnNum < 10) { if (btnNum > 0 && btnNum <= TERM_BTN_COUNT) {
UART_SendAsync(termconf_live.btn_msg[btnNum-1], -1); UART_SendAsync(TERM_BM_N(&termconf_live, btnNum-1), -1);
} }
break; break;

@ -10,7 +10,8 @@
#include "cgi_logging.h" #include "cgi_logging.h"
#define SET_REDIR_SUC "/cfg/system" #define SET_REDIR_SUC "/cfg/system"
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
void buildInputsJson(char *buff);
static ETSTimer tmr; static ETSTimer tmr;
@ -82,7 +83,18 @@ cgiSystemCfgSetParams(HttpdConnData *connData)
char redir_url_buf[100]; char redir_url_buf[100];
char *redir_url = redir_url_buf; char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
if (GET_ARG("redir")) {
strncpy(redir_url, buff, 40);
u32 len = strlen(buff);
if (len > 40) len = 40;
redir_url += len;
} else {
redir_url += sprintf(redir_url, SET_REDIR_SUC);
}
char *end_of_redir_url = redir_url;
redir_url += sprintf(redir_url, "?err=");
char *end_of_failed_redir_url = redir_url;
// 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
if (connData->conn == NULL) { if (connData->conn == NULL) {
@ -95,117 +107,95 @@ cgiSystemCfgSetParams(HttpdConnData *connData)
memcpy(admin_backup, &persist.admin, sizeof(AdminConfigBlock)); memcpy(admin_backup, &persist.admin, sizeof(AdminConfigBlock));
memcpy(sysconf_backup, sysconf, sizeof(SystemConfigBundle)); memcpy(sysconf_backup, sysconf, sizeof(SystemConfigBundle));
// flags for the template builder
bool uart_changed = false; //!< this is set in uart notify, for use in terminal settings (dummy here)
bool admin = false;
const bool tpl = false; // this optionally disables some fields
do { do {
if (!GET_ARG("pw")) { // Check admin PW
break; // if no PW in GET, not trying to configure anything protected if (GET_ARG("pw")) {
} if (!streq(buff, persist.admin.pw)) {
warn("Bad admin pw!");
if (!streq(buff, persist.admin.pw)) { redir_url += sprintf(redir_url, "pw,");
warn("Bad admin pw!"); break; // Abort
redir_url += sprintf(redir_url, "pw,"); } else {
break; admin = true;
}
// authenticated OK
if (GET_ARG("pwlock")) {
cgi_dbg("pwlock: %s", buff);
int pwlock = atoi(buff);
if (pwlock < 0 || pwlock >= PWLOCK_MAX) {
cgi_warn("Bad pwlock %s", buff);
redir_url += sprintf(redir_url, "pwlock,");
break;
} }
sysconf->pwlock = (enum pwlock) pwlock;
} }
if (GET_ARG("access_pw")) { // Changing admin PW
cgi_dbg("access_pw: %s", buff); if (admin && GET_ARG("admin_pw")) {
if (strlen(buff)) { if (strlen(buff)) {
strcpy(buff2, buff); cgi_dbg("admin_pw: %s", buff);
if (!GET_ARG("access_pw2")) {
cgi_warn("Missing repeated access_pw %s", buff);
redir_url += sprintf(redir_url, "access_pw2,");
break;
}
if (!streq(buff, buff2)) {
cgi_warn("Bad repeated access_pw %s", buff);
redir_url += sprintf(redir_url, "access_pw2,");
break;
}
if (strlen(buff) >= 64) {
cgi_warn("Too long access_pw %s", buff);
redir_url += sprintf(redir_url, "access_pw,");
break;
}
cgi_dbg("Changing access PW!");
strncpy(sysconf->access_pw, buff, 64);
}
}
if (GET_ARG("access_name")) {
cgi_dbg("access_name: %s", buff);
if (!strlen(buff) || strlen(buff) >= 32) {
cgi_warn("Too long access_name %s", buff);
redir_url += sprintf(redir_url, "access_name,");
break;
}
strncpy(sysconf->access_name, buff, 32);
}
if (GET_ARG("admin_pw")) {
cgi_dbg("admin_pw: %s", buff);
if (strlen(buff)) {
strcpy(buff2, buff); strcpy(buff2, buff);
if (!GET_ARG("admin_pw2")) { if (!GET_ARG("admin_pw2")) {
cgi_warn("Missing repeated admin_pw %s", buff); cgi_warn("Missing repeated admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw2,"); redir_url += sprintf(redir_url, "admin_pw2,");
break; break; // Abort
} }
if (!streq(buff, buff2)) { if (!streq(buff, buff2)) {
cgi_warn("Bad repeated admin_pw %s", buff); cgi_warn("Bad repeated admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw2,"); redir_url += sprintf(redir_url, "admin_pw2,");
break; break; // Abort
} }
if (strlen(buff) >= 64) { if (strlen(buff) >= 64) {
cgi_warn("Too long admin_pw %s", buff); cgi_warn("Too long admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw,"); redir_url += sprintf(redir_url, "admin_pw,");
break; break; // Abort
} }
cgi_dbg("Changing admin PW!"); cgi_dbg("Changing admin PW!");
strncpy(persist.admin.pw, buff, 64); strncpy(persist.admin.pw, buff, 64);
break; // this is the only field in this form
} }
} }
} while (0);
if (GET_ARG("overclock")) { // Reject filled but unconfirmed access PW
cgi_dbg("overclock = %s", buff); if (admin && GET_ARG("access_pw")) {
int enable = atoi(buff); if (strlen(buff)) {
if (sysconf->overclock != enable) { cgi_dbg("access_pw: %s", buff);
sysconf->overclock = (bool)enable;
strcpy(buff2, buff);
if (!GET_ARG("access_pw2")) {
cgi_warn("Missing repeated access_pw %s", buff);
redir_url += sprintf(redir_url, "access_pw2,");
break; // Abort
}
if (!streq(buff, buff2)) {
cgi_warn("Bad repeated access_pw %s", buff);
redir_url += sprintf(redir_url, "access_pw2,");
break; // Abort
}
}
} }
}
// Settings in the system config block
#define XSTRUCT sysconf
#define X XSET_CGI_FUNC
XTABLE_SYSCONF
#undef X
#undef XSTRUCT
} while (0);
(void)redir_url; (void)redir_url;
(void)uart_changed; // unused
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) { if (*end_of_failed_redir_url == '\0') {
// All was OK // All was OK
cgi_info("Set system params - success, saving..."); cgi_info("Set system params - success, saving...");
sysconf_apply_settings(); sysconf_apply_settings();
persist_store(); persist_store();
httpdRedirect(connData, SET_REDIR_SUC "?msg=Settings%20saved%20and%20applied."); strcpy(end_of_redir_url, "?msg=Settings%20saved%20and%20applied.");
httpdRedirect(connData, redir_url_buf);
} else { } else {
cgi_warn("Some settings did not validate, asking for correction"); cgi_warn("Some settings did not validate, asking for correction");
@ -222,6 +212,40 @@ cgiSystemCfgSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
static void tplSystemCfgFill(char *token, char *buff)
{
buff[0] = '\0';
const bool admin = false;
const bool tpl=true;
#define XSTRUCT sysconf
#define X XGET_CGI_FUNC_RETURN
XTABLE_SYSCONF
#undef X
#undef XSTRUCT
if (streq(token, "def_access_name")) {
sprintf(buff, "%s", DEF_ACCESS_NAME);
return;
}
if (streq(token, "def_access_pw")) {
sprintf(buff, "%s", DEF_ACCESS_PW);
return;
}
if (streq(token, "def_admin_pw")) {
sprintf(buff, "%s", DEFAULT_ADMIN_PW);
return;
}
if (streq(token, "gpio_initial")) {
buildInputsJson(buff);
return;
}
}
httpd_cgi_state ICACHE_FLASH_ATTR httpd_cgi_state ICACHE_FLASH_ATTR
tplSystemCfg(HttpdConnData *connData, char *token, void **arg) tplSystemCfg(HttpdConnData *connData, char *token, void **arg)
@ -234,27 +258,132 @@ tplSystemCfg(HttpdConnData *connData, char *token, void **arg)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
strcpy(buff, ""); // fallback tplSystemCfgFill(token, buff);
if (streq(token, "pwlock")) { tplSend(connData, buff, -1);
sprintf(buff, "%d", sysconf->pwlock); return HTTPD_CGI_DONE;
} }
else if (streq(token, "access_name")) {
sprintf(buff, "%s", sysconf->access_name);
static ETSTimer tmrPulse;
static void ICACHE_FLASH_ATTR tmrPulseCb(void *arg)
{
u32 mask = sysconf->gpio2_conf==GPIOCONF_OFF ? 0x30 : 0x34;
u32 argu = (u32) arg;
u32 set_on = argu & mask;
u32 set_off = (argu >> 16) & mask;
gpio_output_set(set_off, set_on, 0, 0); // the args are swapped here to achieve the opposite
}
void ICACHE_FLASH_ATTR
buildInputsJson(char *buff)
{
u32 inputs = gpio_input_get();
sprintf(buff, "{\"io2\":%d,\"io4\":%d,\"io5\":%d}",
((inputs&(1<<2)) != 0),
((inputs&(1<<4)) != 0),
((inputs&(1<<5)) != 0)
);
}
/**
* API to set GPIOs
*
* @param connData
* @return
*/
httpd_cgi_state ICACHE_FLASH_ATTR cgiGPIO(HttpdConnData *connData)
{
char buff[32];
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
} }
else if (streq(token, "def_access_name")) {
sprintf(buff, "%s", DEF_ACCESS_NAME); bool set = 0;
u32 inputs = gpio_input_get();
// args are do2, do4, do5, pulse. Values: 0 - off, 1 - on, t - toggle. pulse is in ms
s8 set_d2 = -1, set_d4 = -1, set_d5 = -1;
if (sysconf->gpio2_conf == GPIOCONF_OUT_START_0 || sysconf->gpio2_conf == GPIOCONF_OUT_START_1) {
if (GET_ARG("do2")) {
if (buff[0] == 't') {
set = ((inputs & (1<<2)) == 0);
} else {
set = buff[0] == '1';
}
set_d2 = set;
gpio_output_set((uint32) (set << 2), (uint32) ((!set) << 2), 0, 0);
}
} }
else if (streq(token, "def_access_pw")) {
sprintf(buff, "%s", DEF_ACCESS_PW); if (sysconf->gpio4_conf == GPIOCONF_OUT_START_0 || sysconf->gpio4_conf == GPIOCONF_OUT_START_1) {
if (GET_ARG("do4")) {
if (buff[0] == 't') {
set = ((inputs & (1<<4)) == 0);
} else {
set = buff[0] == '1';
}
set_d4 = set;
gpio_output_set((uint32) (set << 4), (uint32) ((!set) << 4), 0, 0);
}
} }
else if (streq(token, "def_admin_pw")) {
sprintf(buff, "%s", DEFAULT_ADMIN_PW); if (sysconf->gpio5_conf == GPIOCONF_OUT_START_0 || sysconf->gpio5_conf == GPIOCONF_OUT_START_1) {
if (GET_ARG("do5")) {
if (buff[0] == 't') {
set = ((inputs & (1<<5)) == 0);
} else {
set = buff[0] == '1';
}
set_d5 = set;
gpio_output_set((uint32) (set << 5), (uint32) ((!set) << 5), 0, 0);
}
} }
else if (streq(token, "overclock")) {
sprintf(buff, "%d", sysconf->overclock); if (GET_ARG("pulse")) {
int duration = atoi(buff);
if (duration > 0) {
u32 cmd = 0;
if (set_d2 != -1) {
if (set_d2) { cmd |= 1<<2; } else { cmd |= 1<<(16+2); }
}
if (set_d4 != -1) {
if (set_d4) { cmd |= 1<<4; } else { cmd |= 1<<(16+4); }
}
if (set_d5 != -1) {
if (set_d5) { cmd |= 1<<5; } else { cmd |= 1<<(16+5); }
}
os_timer_disarm(&tmrPulse);
os_timer_setfn(&tmrPulse, tmrPulseCb, (void*) cmd);
os_timer_arm(&tmrPulse, duration, false);
}
} }
tplSend(connData, buff, -1); httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/json");
httpdEndHeaders(connData);
// refresh inputs
buildInputsJson(buff);
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
/** "GPIO" page */
httpd_cgi_state ICACHE_FLASH_ATTR
tplGpio(HttpdConnData *connData, char *token, void **arg)
{
return tplSystemCfg(connData, token, arg);
}

@ -9,5 +9,7 @@ httpd_cgi_state cgiResetDevice(HttpdConnData *connData);
httpd_cgi_state cgiSystemCfgSetParams(HttpdConnData *connData); httpd_cgi_state cgiSystemCfgSetParams(HttpdConnData *connData);
httpd_cgi_state tplSystemCfg(HttpdConnData *connData, char *token, void **arg); httpd_cgi_state tplSystemCfg(HttpdConnData *connData, char *token, void **arg);
httpd_cgi_state cgiResetScreen(HttpdConnData *connData); httpd_cgi_state cgiResetScreen(HttpdConnData *connData);
httpd_cgi_state cgiGPIO(HttpdConnData *connData);
httpd_cgi_state tplGpio(HttpdConnData *connData, char *token, void **arg);
#endif // CGI_PING_H #endif // CGI_PING_H

@ -15,22 +15,6 @@ Cgi/template routines for configuring non-wifi settings
#define SET_REDIR_SUC "/cfg/term" #define SET_REDIR_SUC "/cfg/term"
#define SET_REDIR_ERR SET_REDIR_SUC"?err=" #define SET_REDIR_ERR SET_REDIR_SUC"?err="
/** convert hex number to int */
static ICACHE_FLASH_ATTR u32
decodehex(const char *buf) {
u32 n = 0;
char c;
while ((c = *buf++) != 0) {
if (c >= '0' && c <= '9') c -= '0';
else if (c >= 'a' && c <= 'f') c -= 'a'-10;
else if (c >= 'A' && c <= 'F') c -= 'A'-10;
else c = 0;
n *= 16;
n += c;
}
return n;
}
/** /**
* Universal CGI endpoint to set Terminal params. * Universal CGI endpoint to set Terminal params.
*/ */
@ -39,15 +23,11 @@ cgiTermCfgSetParams(HttpdConnData *connData)
{ {
char buff[50]; char buff[50];
char redir_url_buf[100]; char redir_url_buf[100];
int32 n, w, h;
ScreenNotifyTopics topics = 0; ScreenNotifyTopics topics = 0;
bool shall_clear_screen = false;
bool shall_init_uart = false;
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 redir_url
SystemConfigBundle *sysconf_backup = malloc(sizeof(SystemConfigBundle)); SystemConfigBundle *sysconf_backup = malloc(sizeof(SystemConfigBundle));
TerminalConfigBundle *termconf_backup = malloc(sizeof(TerminalConfigBundle)); TerminalConfigBundle *termconf_backup = malloc(sizeof(TerminalConfigBundle));
@ -59,322 +39,29 @@ cgiTermCfgSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
// width and height must always go together so we can do max size validation #define XSTRUCT termconf
if (GET_ARG("term_width")) { #define X XSET_CGI_FUNC
do { XTABLE_TERMCONF
cgi_dbg("Default screen width: %s", buff); #undef X
w = atoi(buff); #undef XSTRUCT
if (w < 1) {
cgi_warn("Bad width: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_width,");
break;
}
if (!GET_ARG("term_height")) {
cgi_warn("Missing height arg!");
// this wont happen normally when the form is used
redir_url += sprintf(redir_url, "term_height,");
break;
}
cgi_dbg("Default screen height: %s", buff);
h = atoi(buff);
if (h < 1) {
cgi_warn("Bad height: \"%s\"", buff);
redir_url += sprintf(redir_url, "term_height,");
break;
}
if (w * h > MAX_SCREEN_SIZE) {
cgi_warn("Bad dimensions: %d x %d (total %d)", w, h, w * h);
redir_url += sprintf(redir_url, "term_width,term_height,");
break;
}
if (termconf->width != w || termconf->height != h) {
termconf->width = w;
termconf->height = h;
shall_clear_screen = true;
topics |= TOPIC_CHANGE_SCREEN_OPTS | TOPIC_CHANGE_CONTENT_ALL;
}
} while (0);
}
if (GET_ARG("default_bg")) {
cgi_dbg("Screen default BG: %s", buff);
if (buff[0] == '#') {
// decode hex
n = decodehex(buff+1);
n += 256;
} else {
n = atoi(buff);
}
if (termconf->default_bg != n) {
termconf->default_bg = n; // this is current not sent through socket, no use to notify
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
}
if (GET_ARG("default_fg")) {
cgi_dbg("Screen default FG: %s", buff);
if (buff[0] == '#') {
// decode hex
n = decodehex(buff+1);
n += 256;
} else {
n = atoi(buff);
}
if (termconf->default_fg != n) { // width and height must always go together so we can do max size validation
termconf->default_fg = n; // this is current not sent through socket, no use to notify u32 siz = termconf->width*termconf->height;
topics |= TOPIC_CHANGE_SCREEN_OPTS; if (siz == 0 || siz > MAX_SCREEN_SIZE) {
} cgi_warn("Bad dimensions: %d x %d (total %d)", termconf->width, termconf->height, termconf->width*termconf->height);
} redir_url += sprintf(redir_url, "term_width,term_height,");
}
if (GET_ARG("parser_tout_ms")) {
cgi_dbg("Parser timeout: %s ms", buff); // Settings from SysConf - UART
n = atoi(buff); bool uart_changed = false;
if (n >= 0) { // restrict what keys are allowed
termconf->parser_tout_ms = n; #define admin 0
} else { #define tpl 0
cgi_warn("Bad parser timeout %s", buff); #define XSTRUCT sysconf
redir_url += sprintf(redir_url, "parser_tout_ms,"); #define X XSET_CGI_FUNC
} XTABLE_SYSCONF
} #undef X
#undef XSTRUCT
if (GET_ARG("display_tout_ms")) {
cgi_dbg("Display update idle timeout: %s ms", buff);
n = atoi(buff);
if (n >= 0) {
termconf->display_tout_ms = n;
} else {
cgi_warn("Bad update timeout %s", buff);
redir_url += sprintf(redir_url, "display_tout_ms,");
}
}
if (GET_ARG("display_cooldown_ms")) {
cgi_dbg("Display update cooldown: %s ms", buff);
n = atoi(buff);
if (n > 0) {
termconf->display_cooldown_ms = n;
} else {
cgi_warn("Bad cooldown %s", buff);
redir_url += sprintf(redir_url, "display_cooldown_ms,");
}
}
if (GET_ARG("fn_alt_mode")) {
cgi_dbg("FN alt mode: %s", buff);
n = atoi(buff);
termconf->fn_alt_mode = (bool)n;
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
if (GET_ARG("want_all_fn")) {
cgi_dbg("AllFN mode: %s", buff);
n = atoi(buff);
termconf->want_all_fn = (bool)n;
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
if (GET_ARG("crlf_mode")) {
cgi_dbg("CRLF mode: %s", buff);
n = atoi(buff);
termconf->crlf_mode = (bool)n;
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
if (GET_ARG("show_buttons")) {
cgi_dbg("Show buttons: %s", buff);
n = atoi(buff);
termconf->show_buttons = (bool)n;
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
if (GET_ARG("show_config_links")) {
cgi_dbg("Show config links: %s", buff);
n = atoi(buff);
termconf->show_config_links = (bool)n;
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
if (GET_ARG("loopback")) {
cgi_dbg("Loopback: %s", buff);
n = atoi(buff);
termconf->loopback = (bool)n;
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
if (GET_ARG("debugbar")) {
cgi_dbg("Debugbar: %s", buff);
n = atoi(buff);
termconf->debugbar = (bool)n;
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
if (GET_ARG("allow_decopt_12")) {
cgi_dbg("DECOPT 12: %s", buff);
n = atoi(buff);
termconf->allow_decopt_12 = (bool)n;
}
if (GET_ARG("ascii_debug")) {
cgi_dbg("ascii_debug: %s", buff);
n = atoi(buff);
termconf->ascii_debug = (bool)n;
shall_clear_screen = true;
}
if (GET_ARG("theme")) {
cgi_dbg("Screen color theme: %s", buff);
n = atoi(buff);
if (n >= 0) {
termconf->theme = (u8) n;
// this can't be notified, page must reload.
} else {
cgi_warn("Bad theme num: %s", buff);
redir_url += sprintf(redir_url, "theme,");
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
}
if (GET_ARG("cursor_shape")) {
cgi_dbg("Cursor shape: %s", buff);
n = atoi(buff);
if (n >= 0 && n <= 6 && n != 1) {
termconf->cursor_shape = (enum CursorShape) n;
topics |= TOPIC_CHANGE_SCREEN_OPTS;
} else {
cgi_warn("Bad cursor_shape num: %s", buff);
redir_url += sprintf(redir_url, "cursor_shape,");
}
}
if (GET_ARG("term_title")) {
cgi_dbg("Terminal title default text: \"%s\"", buff);
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in
topics |= TOPIC_CHANGE_TITLE;
}
for (int btn_i = 1; btn_i <= TERM_BTN_COUNT; btn_i++) {
sprintf(buff, "btn%d", btn_i);
if (GET_ARG(buff)) {
cgi_dbg("Button%d default text: \"%s\"", btn_i, buff);
strncpy_safe(termconf->btn[btn_i-1], buff, TERM_BTN_LEN);
topics |= TOPIC_CHANGE_BUTTONS;
}
sprintf(buff, "bm%d", btn_i);
if (GET_ARG(buff)) {
cgi_dbg("Button%d message (ASCII): \"%s\"", btn_i, buff);
// parse: comma,space or semicolon separated decimal values of ASCII codes
char c;
char *cp = buff;
int char_i = 0;
int acu = 0;
bool lastsp = 1;
char buff_bm[TERM_BTN_MSG_LEN];
while ((c = *cp++) != 0) {
if (c == ' ' || c == ',' || c == ';') {
if(lastsp) continue;
if (acu==0 || acu>255) {
cgi_warn("Bad value! %d", acu);
redir_url += sprintf(redir_url, "bm%d,", btn_i);
break;
}
if (char_i >= TERM_BTN_MSG_LEN-1) {
cgi_warn("Too long! %d", acu);
redir_url += sprintf(redir_url, "bm%d,", btn_i);
break;
}
cgi_dbg("acu %d", acu);
buff_bm[char_i++] = (char)acu;
// prepare for next char
acu = 0;
lastsp = 1;
} else if (c>='0'&&c<='9') {
lastsp = 0;
acu *= 10;
acu += c - '0';
} else {
cgi_warn("Bad syntax!");
redir_url += sprintf(redir_url, "bm%d,", btn_i);
break;
}
}
if (lastsp && char_i == 0) {
cgi_warn("Required!");
redir_url += sprintf(redir_url, "bm%d,", btn_i);
}
if (!lastsp) {
buff_bm[char_i++] = (char)acu;
}
buff_bm[char_i] = 0;
cgi_dbg("%s, chari = %d", buff_bm, char_i);
strncpy(termconf->btn_msg[btn_i-1], buff_bm, TERM_BTN_MSG_LEN);
}
}
if (GET_ARG("uart_baud")) {
cgi_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;
shall_init_uart = true;
} else {
cgi_warn("Bad baud rate %s", buff);
redir_url += sprintf(redir_url, "uart_baud,");
}
}
if (GET_ARG("uart_parity")) {
cgi_dbg("Parity: %s", buff);
int parity = atoi(buff);
if (parity >= 0 && parity <= 2) {
sysconf->uart_parity = (UartParityMode) parity;
shall_init_uart = true;
} else {
cgi_warn("Bad parity %s", buff);
redir_url += sprintf(redir_url, "uart_parity,");
}
}
if (GET_ARG("uart_stopbits")) {
cgi_dbg("Stop bits: %s", buff);
int stopbits = atoi(buff);
if (stopbits >= 1 && stopbits <= 3) {
sysconf->uart_stopbits = (UartStopBitsNum) stopbits;
shall_init_uart = true;
} else {
cgi_warn("Bad stopbits %s", buff);
redir_url += sprintf(redir_url, "uart_stopbits,");
}
}
(void)redir_url; (void)redir_url;
@ -384,17 +71,23 @@ cgiTermCfgSetParams(HttpdConnData *connData)
persist_store(); persist_store();
bool shall_clear_screen = false;
shall_clear_screen |= (termconf_backup->width != termconf->width);
shall_clear_screen |= (termconf_backup->height != termconf->height);
shall_clear_screen |= (termconf_backup->ascii_debug != termconf->ascii_debug);
if (shall_clear_screen) { if (shall_clear_screen) {
terminal_apply_settings(); terminal_apply_settings();
} else { } else {
terminal_apply_settings_noclear(); terminal_apply_settings_noclear();
} }
if (shall_init_uart) { if (uart_changed) {
serialInit(); sysconf_apply_settings();
} }
if (topics) screen_notifyChange(topics); // Trigger a full reload
screen_notifyChange(TOPIC_INITIAL);
httpdRedirect(connData, SET_REDIR_SUC "?msg=Settings%20saved%20and%20applied."); httpdRedirect(connData, SET_REDIR_SUC "?msg=Settings%20saved%20and%20applied.");
} else { } else {
@ -416,9 +109,8 @@ 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];
if (token == NULL) { if (token == NULL) {
// We're done // We're done
@ -427,106 +119,18 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
strcpy(buff, ""); // fallback strcpy(buff, ""); // fallback
if (streq(token, "term_width")) { #define XSTRUCT termconf
sprintf(buff, "%d", termconf->width); #define X XGET_CGI_FUNC
} XTABLE_TERMCONF
else if (streq(token, "term_height")) { #undef X
sprintf(buff, "%d", termconf->height); #undef XSTRUCT
}
else if (streq(token, "parser_tout_ms")) { // for uart
sprintf(buff, "%d", termconf->parser_tout_ms); #define XSTRUCT sysconf
} #define X XGET_CGI_FUNC
else if (streq(token, "display_tout_ms")) { XTABLE_SYSCONF
sprintf(buff, "%d", termconf->display_tout_ms); #undef X
} #undef XSTRUCT
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, "want_all_fn")) {
sprintf(buff, "%d", (int)termconf->want_all_fn);
}
else if (streq(token, "crlf_mode")) {
sprintf(buff, "%d", (int)termconf->crlf_mode);
}
else if (streq(token, "show_buttons")) {
sprintf(buff, "%d", (int)termconf->show_buttons);
}
else if (streq(token, "show_config_links")) {
sprintf(buff, "%d", (int)termconf->show_config_links);
}
else if (streq(token, "allow_decopt_12")) {
sprintf(buff, "%d", (int)termconf->allow_decopt_12);
}
else if (streq(token, "ascii_debug")) {
sprintf(buff, "%d", (int)termconf->ascii_debug);
}
else if (streq(token, "loopback")) {
sprintf(buff, "%d", (int)termconf->loopback);
}
else if (streq(token, "debugbar")) {
sprintf(buff, "%d", (int)termconf->debugbar);
}
else if (streq(token, "theme")) {
sprintf(buff, "%d", termconf->theme);
}
else if (streq(token, "default_bg")) {
if (termconf->default_bg < 256) {
sprintf(buff, "%d", termconf->default_bg);
} else {
sprintf(buff, "#%06X", termconf->default_bg - 256);
}
}
else if (streq(token, "default_fg")) {
if (termconf->default_fg < 256) {
sprintf(buff, "%d", termconf->default_fg);
} else {
sprintf(buff, "#%06X", termconf->default_fg - 256);
}
}
else if (streq(token, "cursor_shape")) {
sprintf(buff, "%d", termconf->cursor_shape);
}
else if (streq(token, "term_title")) {
strncpy_safe(buff, termconf->title, BUFLEN);
}
else 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);
}
else {
for (int btn_i = 1; btn_i <= TERM_BTN_COUNT; btn_i++) {
sprintf(buff2, "btn%d", btn_i);
if (streq(token, buff2)) {
strncpy_safe(buff, termconf->btn[btn_i-1], BUFLEN);
break;
}
sprintf(buff2, "bm%d", btn_i);
if (streq(token, buff2)) {
char c;
char *bp = buff;
char *cp = termconf->btn_msg[btn_i-1];
int n = 0;
while((c = *cp++) != 0) {
if(n>0) {
*bp = ',';
bp++;
}
bp += sprintf(bp, "%d", (int)c);
n++;
}
break;
}
}
}
tplSend(connData, buff, -1); tplSend(connData, buff, -1);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;

@ -19,6 +19,7 @@ Cgi/template routines for the /wifi url.
#include "wifimgr.h" #include "wifimgr.h"
#include "persist.h" #include "persist.h"
#include "helpers.h" #include "helpers.h"
#include "config_xmacros.h"
#include "cgi_logging.h" #include "cgi_logging.h"
#define SET_REDIR_SUC "/cfg/wifi" #define SET_REDIR_SUC "/cfg/wifi"
@ -66,46 +67,6 @@ int ICACHE_FLASH_ATTR rssi2perc(int rssi)
return r; return r;
} }
/**
* Convert Auth type to string
*/
const ICACHE_FLASH_ATTR char *auth2str(AUTH_MODE auth)
{
switch (auth) {
case AUTH_OPEN:
return "Open";
case AUTH_WEP:
return "WEP";
case AUTH_WPA_PSK:
return "WPA";
case AUTH_WPA2_PSK:
return "WPA2";
case AUTH_WPA_WPA2_PSK:
return "WPA/WPA2";
default:
return "Unknown";
}
}
/**
* Convert WiFi opmode to string
*/
const ICACHE_FLASH_ATTR char *opmode2str(WIFI_MODE opmode)
{
switch (opmode) {
case NULL_MODE:
return "Disabled";
case STATION_MODE:
return "Client";
case SOFTAP_MODE:
return "AP only";
case STATIONAP_MODE:
return "Client+AP";
default:
return "Unknown";
}
}
/** /**
* Callback the code calls when a wlan ap scan is done. Basically stores the result in * Callback the code calls when a wlan ap scan is done. Basically stores the result in
* the static cgiWifiAps struct. * the static cgiWifiAps struct.
@ -363,18 +324,14 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
bool sta_turned_on = false; bool sta_turned_on = false;
bool sta_ssid_pw_changed = false; bool sta_ssid_pw_changed = false;
#define XSTRUCT wificonf
#define X XSET_CGI_FUNC
XTABLE_WIFICONF
#undef X
// ---- WiFi opmode ---- // ---- WiFi opmode ----
if (GET_ARG("opmode")) { // those are helpers, not a real prop
cgi_dbg("Setting WiFi opmode to: %s", buff);
int mode = atoi(buff);
if (mode > NULL_MODE && mode < MAX_MODE) {
wificonf->opmode = (WIFI_MODE) mode;
} else {
cgi_warn("Bad opmode value \"%s\"", buff);
redir_url += sprintf(redir_url, "opmode,");
}
}
if (GET_ARG("ap_enable")) { if (GET_ARG("ap_enable")) {
cgi_dbg("Enable AP: %s", buff); cgi_dbg("Enable AP: %s", buff);
@ -399,114 +356,6 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
} }
} }
// ---- AP transmit power ----
if (GET_ARG("tpw")) {
cgi_dbg("Setting AP power to: %s", buff);
int tpw = atoi(buff);
if (tpw >= 0 && tpw <= 82) { // 0 actually isn't 0 but quite low. 82 is very strong
if (wificonf->tpw != tpw) {
wificonf->tpw = (u8) tpw;
wifi_change_flags.ap = true;
}
} else {
cgi_warn("tpw %s out of allowed range 0-82.", buff);
redir_url += sprintf(redir_url, "tpw,");
}
}
// ---- AP channel (applies in AP-only mode) ----
if (GET_ARG("ap_channel")) {
cgi_info("ap_channel = %s", buff);
int channel = atoi(buff);
if (channel > 0 && channel < 15) {
if (wificonf->ap_channel != channel) {
wificonf->ap_channel = (u8) channel;
wifi_change_flags.ap = true;
}
} else {
cgi_warn("Bad channel value \"%s\", allowed 1-14", buff);
redir_url += sprintf(redir_url, "ap_channel,");
}
}
// ---- SSID name in AP mode ----
if (GET_ARG("ap_ssid")) {
// Replace all invalid ASCII with underscores
int i;
for (i = 0; i < 32; i++) {
char c = buff[i];
if (c == 0) break;
if (c < 32 || c >= 127) buff[i] = '_';
}
buff[i] = 0;
if (strlen(buff) > 0) {
if (!streq(wificonf->ap_ssid, buff)) {
cgi_info("Setting SSID to \"%s\"", buff);
strncpy_safe(wificonf->ap_ssid, buff, SSID_LEN);
wifi_change_flags.ap = true;
}
} else {
cgi_warn("Bad SSID len.");
redir_url += sprintf(redir_url, "ap_ssid,");
}
}
// ---- AP password ----
if (GET_ARG("ap_password")) {
// Users are free to use any stupid shit in ther password,
// but it may lock them out.
if (strlen(buff) == 0 || (strlen(buff) >= 8 && strlen(buff) < PASSWORD_LEN-1)) {
if (!streq(wificonf->ap_password, buff)) {
cgi_info("Setting AP password to \"%s\"", buff);
strncpy_safe(wificonf->ap_password, buff, PASSWORD_LEN);
wifi_change_flags.ap = true;
}
} else {
cgi_warn("Bad password len.");
redir_url += sprintf(redir_url, "ap_password,");
}
}
// ---- Hide AP network (do not announce) ----
if (GET_ARG("ap_hidden")) {
cgi_dbg("AP hidden = %s", buff);
int hidden = atoi(buff);
if (hidden != wificonf->ap_hidden) {
wificonf->ap_hidden = (hidden != 0);
wifi_change_flags.ap = true;
}
}
// ---- Station SSID (to connect to) ----
if (GET_ARG("sta_ssid")) {
if (!streq(wificonf->sta_ssid, buff)) {
// No verification needed, at worst it fails to connect
cgi_info("Setting station SSID to: \"%s\"", buff);
strncpy_safe(wificonf->sta_ssid, buff, SSID_LEN);
wifi_change_flags.sta = true;
sta_ssid_pw_changed = true;
}
}
// ---- Station password (empty for none is allowed) ----
if (GET_ARG("sta_password")) {
if (!streq(wificonf->sta_password, buff)) {
// No verification needed, at worst it fails to connect
cgi_info("Setting station password to: \"%s\"", buff);
strncpy_safe(wificonf->sta_password, buff, PASSWORD_LEN);
wifi_change_flags.sta = true;
sta_ssid_pw_changed = true;
}
}
(void)redir_url; (void)redir_url;
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) { if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
@ -552,7 +401,6 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
//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)
{ {
@ -567,39 +415,18 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
strcpy(buff, ""); // fallback strcpy(buff, ""); // fallback
if (streq(token, "opmode_name")) { #define XSTRUCT wificonf
strcpy(buff, opmode2str(wificonf->opmode)); #define X XGET_CGI_FUNC
} XTABLE_WIFICONF
else if (streq(token, "opmode")) { #undef X
sprintf(buff, "%d", wificonf->opmode);
} // non-config
else if (streq(token, "sta_enable")) { if (streq(token, "sta_enable")) {
sprintf(buff, "%d", (wificonf->opmode & STATION_MODE) != 0); sprintf(buff, "%d", (wificonf->opmode & STATION_MODE) != 0);
} }
else if (streq(token, "ap_enable")) { else if (streq(token, "ap_enable")) {
sprintf(buff, "%d", (wificonf->opmode & SOFTAP_MODE) != 0); sprintf(buff, "%d", (wificonf->opmode & SOFTAP_MODE) != 0);
} }
else if (streq(token, "tpw")) {
sprintf(buff, "%d", wificonf->tpw);
}
else if (streq(token, "ap_channel")) {
sprintf(buff, "%d", wificonf->ap_channel);
}
else if (streq(token, "ap_ssid")) {
sprintf(buff, "%s", wificonf->ap_ssid);
}
else if (streq(token, "ap_password")) {
sprintf(buff, "%s", wificonf->ap_password);
}
else if (streq(token, "ap_hidden")) {
sprintf(buff, "%d", wificonf->ap_hidden);
}
else if (streq(token, "sta_ssid")) {
sprintf(buff, "%s", wificonf->sta_ssid);
}
else if (streq(token, "sta_password")) {
sprintf(buff, "%s", wificonf->sta_password);
}
else if (streq(token, "sta_rssi")) { else if (streq(token, "sta_rssi")) {
sprintf(buff, "%d", wifi_station_get_rssi()); sprintf(buff, "%d", wifi_station_get_rssi());
} }

@ -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 {

@ -0,0 +1,144 @@
//
// Created by MightyPork on 2017/10/22.
//
#include "config_xmacros.h"
#include "cgi_logging.h"
void ICACHE_FLASH_ATTR xget_dec(char *buff, u32 value)
{
sprintf(buff, "%d", value);
}
void ICACHE_FLASH_ATTR xget_bool(char *buff, bool value)
{
sprintf(buff, "%d", value?1:0);
}
void ICACHE_FLASH_ATTR xget_ustring(char *buff, const u8 *value)
{
sprintf(buff, "%s", (const char *) value);
}
void ICACHE_FLASH_ATTR xget_string(char *buff, const char *value)
{
sprintf(buff, "%s", value);
}
void ICACHE_FLASH_ATTR xget_ip(char *buff, const struct ip_addr *value)
{
sprintf(buff, IPSTR, GOOD_IP2STR(value->addr));
}
// ------------- XSET -------------
enum xset_result ICACHE_FLASH_ATTR
xset_ip(const char *name, struct ip_addr *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
u32 ip = ipaddr_addr(buff);
if (ip != 0 && ip != 0xFFFFFFFFUL) {
if (field->addr != ip) {
field->addr = ip;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad IP: %s", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_bool(const char *name, bool *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
bool enable = (atoi(buff) != 0);
if (*field != enable) {
*field = enable;
return XSET_SET;
}
return XSET_UNCHANGED;
}
enum xset_result ICACHE_FLASH_ATTR
xset_u8(const char *name, u8 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
u32 val = (u32) atoi(buff);
if (val <= 255) {
if (*field != val) {
*field = (u8) val;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad value, max 255: %s", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_u32(const char *name, u32 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
u32 val = (u32) atoi(buff);
if (*field != val) {
*field = (u32) val;
return XSET_SET;
}
return XSET_UNCHANGED;
}
enum xset_result ICACHE_FLASH_ATTR
xset_u16(const char *name, u16 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
u16 val = (u16) atoi(buff);
if (*field != val) {
*field = (u16) val;
return XSET_SET;
}
return XSET_UNCHANGED;
}
enum xset_result ICACHE_FLASH_ATTR
xset_string(const char *name, char *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
u32 maxlen = (u32) arg;
if (arg > 0 && (u32)strlen(buff) > maxlen) {
cgi_warn("String too long, max %d", maxlen);
return XSET_FAIL;
}
if (!streq(field, buff)) {
strncpy_safe(field, buff, (u32)arg);
return XSET_SET;
}
return XSET_UNCHANGED;
}
enum xset_result ICACHE_FLASH_ATTR
xset_ustring(const char *name, uchar *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
u32 maxlen = (u32) arg;
if (arg > 0 && (u32)strlen(buff) > maxlen) {
cgi_warn("String too long, max %d", maxlen);
return XSET_FAIL;
}
if (!streq(field, buff)) {
strncpy_safe(field, buff, (u32)arg);
return XSET_SET;
}
return XSET_UNCHANGED;
}

@ -0,0 +1,100 @@
//
// Created by MightyPork on 2017/10/22.
//
#ifndef ESPTERM_CONFIG_XMACROS_H
#define ESPTERM_CONFIG_XMACROS_H
#include <esp8266.h>
#include <helpers.h>
typedef unsigned char uchar;
#define XJOIN(a, b) a##b
/**Do nothing xnotify */
#define xnoop()
/**
* XGET interface
*
* @param buff - buffer where the value should be printed
* @param value - value to render to the buffer
*/
static inline bool xget_dummy(char *buff, u32 value)
{
sprintf(buff, "unused %d", value);
return false;
}
void xget_dec(char *buff, u32 value);
void xget_bool(char *buff, bool value);
void xget_ustring(char *buff, const u8 *value);
void xget_string(char *buff, const char *value);
void xget_ip(char *buff, const struct ip_addr *value);
void xget_dhcp(char *buff, const struct dhcps_lease *value);
/**
* XSET interface
*
* @param name - field name (for debug)
* @param field - pointer to the target field
* @param buff - field with the value to be set
* @param arg - arbitrary argument, used to modify behavior
*
* @return xset_result
*/
enum xset_result {
XSET_FAIL = 0,
XSET_SET = 1,
XSET_UNCHANGED = 2,
XSET_NONE = 3,
};
// Dummy for unimplemented setters
static inline enum xset_result xset_dummy(const char *name, void *field, const char *buff, const void *arg)
{
return XSET_UNCHANGED;
}
enum xset_result xset_ip(const char *name, struct ip_addr *field, const char *buff, const void *arg);
enum xset_result xset_bool(const char *name, bool *field, const char *buff, const void *arg);
enum xset_result xset_u8(const char *name, u8 *field, const char *buff, const void *arg);
enum xset_result xset_u32(const char *name, u32 *field, const char *buff, const void *arg);
enum xset_result xset_u16(const char *name, u16 *field, const char *buff, const void *arg);
// static string arrays are not &'d, so we don't get **
/** @param arg - max string length */
enum xset_result xset_string(const char *name, char *field, const char *buff, const void *arg);
enum xset_result xset_ustring(const char *name, u8 *field, const char *buff, const void *arg);
/**
* Helper template macro for CGI functions that load GET args to structs using XTABLE
*
* If 'name' is found in connData->getArgs, xset() is called.
* If the result is SET, xnotify() is fired. Else, 'name,' is appended to the redir_url buffer.
*/
#define XSET_CGI_FUNC(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \
if ((allow) && GET_ARG(#name)) { \
type *_p = (type *) &XSTRUCT->name; \
enum xset_result res = xset(#name, _p, buff, (const void*) (xsarg)); \
if (res == XSET_SET) { xnotify; } \
else if (res == XSET_FAIL) { redir_url += sprintf(redir_url, #name","); } \
}
#define XGET_CGI_FUNC(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \
if ((allow) && streq(token, #name)) xget(buff, deref XSTRUCT->name);
#define XGET_CGI_FUNC_RETURN(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \
if ((allow) && streq(token, #name)) { xget(buff, deref XSTRUCT->name); return; }
#define XSTRUCT_FIELD(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \
type name suffix;
#define XDUMP_FIELD(type, name, suffix, deref, xget, allow, xset, xsarg, xnotify) \
{ xget(buff, deref XSTRUCT->name); dbg(#name " = %s", buff); }
#endif //ESPTERM_CONFIG_XMACROS_H

@ -0,0 +1,526 @@
/* #line 1 "user/ini_parser.rl" */
/* Ragel constants block */
#include "ini_parser.h"
// Ragel setup
/* #line 10 "user/ini_parser.c" */
static const char _ini_actions[] ESP_CONST_DATA = {
0, 1, 1, 1, 2, 1, 3, 1,
4, 1, 5, 1, 6, 1, 7, 1,
8, 1, 9, 1, 10, 1, 11, 1,
13, 2, 0, 4, 2, 12, 4
};
static const char _ini_eof_actions[] ESP_CONST_DATA = {
0, 23, 5, 5, 15, 15, 15, 15,
19, 19, 0, 0, 0, 0, 0, 0,
0
};
static const int ini_start = 1;
static const int ini_first_final = 12;
static const int ini_error = 0;
static const int ini_en_section = 2;
static const int ini_en_keyvalue = 4;
static const int ini_en_comment = 8;
static const int ini_en_discard2eol = 10;
static const int ini_en_main = 1;
/* #line 10 "user/ini_parser.rl" */
// Persistent state
static int8_t cs = -1; //!< Ragel's Current State variable
static uint32_t buff_i = 0; //!< Write pointer for the buffers
static char value_quote = 0; //!< Quote character of the currently collected value
static bool value_nextesc = false; //!< Next character is escaped, trated specially, and if quote, as literal quote character
static IniParserCallback keyCallback = NULL; //!< Currently assigned callback
static void *userdata = NULL; //!< Currently assigned user data for the callback
// Buffers
static char keybuf[INI_KEY_MAX];
static char secbuf[INI_KEY_MAX];
static char valbuf[INI_VALUE_MAX];
// See header for doxygen!
void ICACHE_FLASH_ATTR
ini_parse_reset_partial(void)
{
buff_i = 0;
value_quote = 0;
value_nextesc = false;
}
void ICACHE_FLASH_ATTR
ini_parse_reset(void)
{
ini_parse_reset_partial();
keybuf[0] = secbuf[0] = valbuf[0] = 0;
/* #line 67 "user/ini_parser.c" */
{
cs = ini_start;
}
/* #line 41 "user/ini_parser.rl" */
}
void ICACHE_FLASH_ATTR
ini_parser_error(const char* msg)
{
ini_error("Parser error: %s", msg);
ini_parse_reset_partial();
}
void ICACHE_FLASH_ATTR
ini_parse_begin(IniParserCallback callback, void *userData)
{
keyCallback = callback;
userdata = userData;
ini_parse_reset();
}
void ICACHE_FLASH_ATTR
*ini_parse_end(void)
{
ini_parse("\n", 1);
if (keyCallback) {
keyCallback = NULL;
}
void *ud = userdata;
userdata = NULL;
return ud;
}
void ICACHE_FLASH_ATTR
ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData)
{
ini_parse_begin(callback, userData);
ini_parse(text, len);
ini_parse_end();
}
static void ICACHE_FLASH_ATTR
rtrim_buf(char *buf, int32_t end)
{
if (end > 0) {
while ((uint8_t)buf[--end] < 33);
end++; // go past the last character
}
buf[end] = 0;
}
void ICACHE_FLASH_ATTR
ini_parse(const char *newstr, size_t len)
{
int32_t i;
char c;
bool isnl;
bool isquot;
// Load new data to Ragel vars
const uint8_t *p;
const uint8_t *eof;
const uint8_t *pe;
if (len == 0) while(newstr[++len] != 0); // alternative to strlen
p = (const uint8_t *) newstr;
eof = NULL;
pe = (const uint8_t *) (newstr + len);
// Init Ragel on the first run
if (cs == -1) {
ini_parse_reset();
}
// The parser
/* #line 152 "user/ini_parser.c" */
{
const char *_acts;
unsigned int _nacts;
if ( p == pe )
goto _test_eof;
if ( cs == 0 )
goto _out;
_resume:
switch ( cs ) {
case 1:
switch( (*p) ) {
case 32u: goto tr1;
case 35u: goto tr3;
case 58u: goto tr0;
case 59u: goto tr3;
case 61u: goto tr0;
case 91u: goto tr4;
}
if ( (*p) < 9u ) {
if ( (*p) <= 8u )
goto tr0;
} else if ( (*p) > 13u ) {
if ( 14u <= (*p) && (*p) <= 31u )
goto tr0;
} else
goto tr1;
goto tr2;
case 0:
goto _out;
case 12:
goto tr0;
case 2:
switch( (*p) ) {
case 9u: goto tr6;
case 32u: goto tr6;
case 93u: goto tr5;
}
if ( (*p) <= 31u )
goto tr5;
goto tr7;
case 3:
if ( (*p) == 93u )
goto tr8;
if ( (*p) > 8u ) {
if ( 10u <= (*p) && (*p) <= 31u )
goto tr5;
} else
goto tr5;
goto tr7;
case 13:
goto tr5;
case 4:
switch( (*p) ) {
case 10u: goto tr10;
case 58u: goto tr11;
case 61u: goto tr11;
}
goto tr9;
case 5:
switch( (*p) ) {
case 9u: goto tr13;
case 10u: goto tr14;
case 13u: goto tr15;
case 32u: goto tr13;
}
goto tr12;
case 6:
switch( (*p) ) {
case 10u: goto tr14;
case 13u: goto tr15;
}
goto tr12;
case 14:
goto tr10;
case 7:
if ( (*p) == 10u )
goto tr14;
goto tr10;
case 8:
switch( (*p) ) {
case 10u: goto tr17;
case 13u: goto tr18;
}
goto tr16;
case 15:
goto tr19;
case 9:
if ( (*p) == 10u )
goto tr17;
goto tr19;
case 10:
switch( (*p) ) {
case 10u: goto tr21;
case 13u: goto tr22;
}
goto tr20;
case 16:
goto tr23;
case 11:
if ( (*p) == 10u )
goto tr21;
goto tr23;
}
tr23: cs = 0; goto _again;
tr0: cs = 0; goto f0;
tr5: cs = 0; goto f4;
tr10: cs = 0; goto f7;
tr19: cs = 0; goto f11;
tr1: cs = 1; goto _again;
tr6: cs = 2; goto _again;
tr7: cs = 3; goto f5;
tr9: cs = 4; goto f8;
tr13: cs = 5; goto _again;
tr11: cs = 5; goto f9;
tr12: cs = 6; goto f10;
tr15: cs = 7; goto _again;
tr16: cs = 8; goto _again;
tr18: cs = 9; goto _again;
tr20: cs = 10; goto _again;
tr22: cs = 11; goto _again;
tr2: cs = 12; goto f1;
tr3: cs = 12; goto f2;
tr4: cs = 12; goto f3;
tr8: cs = 13; goto f6;
tr14: cs = 14; goto f10;
tr17: cs = 15; goto f12;
tr21: cs = 16; goto f13;
f5: _acts = _ini_actions + 1; goto execFuncs;
f6: _acts = _ini_actions + 3; goto execFuncs;
f4: _acts = _ini_actions + 5; goto execFuncs;
f1: _acts = _ini_actions + 7; goto execFuncs;
f8: _acts = _ini_actions + 9; goto execFuncs;
f9: _acts = _ini_actions + 11; goto execFuncs;
f10: _acts = _ini_actions + 13; goto execFuncs;
f7: _acts = _ini_actions + 15; goto execFuncs;
f12: _acts = _ini_actions + 17; goto execFuncs;
f11: _acts = _ini_actions + 19; goto execFuncs;
f13: _acts = _ini_actions + 21; goto execFuncs;
f0: _acts = _ini_actions + 23; goto execFuncs;
f3: _acts = _ini_actions + 25; goto execFuncs;
f2: _acts = _ini_actions + 28; goto execFuncs;
execFuncs:
_nacts = *_acts++;
while ( _nacts-- > 0 ) {
switch ( *_acts++ ) {
case 0:
/* #line 130 "user/ini_parser.rl" */
{
buff_i = 0;
{cs = 2;goto _again;}
}
break;
case 1:
/* #line 135 "user/ini_parser.rl" */
{
if (buff_i >= INI_KEY_MAX) {
ini_parser_error("Section name too long");
{cs = 10;goto _again;}
}
keybuf[buff_i++] = (*p);
}
break;
case 2:
/* #line 143 "user/ini_parser.rl" */
{
// we need a separate buffer for the result, otherwise a failed
// partial parse would corrupt the section string
rtrim_buf(keybuf, buff_i);
for (i = 0; (c = keybuf[i]) != 0; i++) secbuf[i] = c;
secbuf[i] = 0;
{cs = 1;goto _again;}
}
break;
case 3:
/* #line 155 "user/ini_parser.rl" */
{
ini_parser_error("Syntax error in [section]");
if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;}
}
break;
case 4:
/* #line 162 "user/ini_parser.rl" */
{
buff_i = 0;
keybuf[buff_i++] = (*p); // add the first char
{cs = 4;goto _again;}
}
break;
case 5:
/* #line 168 "user/ini_parser.rl" */
{
if (buff_i >= INI_KEY_MAX) {
ini_parser_error("Key too long");
{cs = 10;goto _again;}
}
keybuf[buff_i++] = (*p);
}
break;
case 6:
/* #line 176 "user/ini_parser.rl" */
{
rtrim_buf(keybuf, buff_i);
// --- Value begin ---
buff_i = 0;
value_quote = 0;
value_nextesc = false;
}
break;
case 7:
/* #line 185 "user/ini_parser.rl" */
{
isnl = ((*p) == '\r' || (*p) == '\n');
isquot = ((*p) == '\'' || (*p) == '"');
// detect our starting quote
if (isquot && !value_nextesc && buff_i == 0 && value_quote == 0) {
value_quote = (*p);
goto valueCharDone;
}
if (buff_i >= INI_VALUE_MAX) {
ini_parser_error("Value too long");
{cs = 10;goto _again;}
}
// end of string - clean up and report
if ((!value_nextesc && (*p) == value_quote) || isnl) {
if (isnl && value_quote) {
ini_parser_error("Unterminated string");
{cs = 1;goto _again;}
}
// unquoted: trim from the end
if (!value_quote) {
rtrim_buf(valbuf, buff_i);
} else {
valbuf[buff_i] = 0;
}
if (keyCallback) {
keyCallback(secbuf, keybuf, valbuf, userdata);
}
// we don't want to discard to eol if the string was terminated by eol
// - would delete the next line
if (isnl) {cs = 1;goto _again;} else {cs = 10;goto _again;}
}
c = (*p);
// escape...
if (value_nextesc) {
if ((*p) == 'n') c = '\n';
else if ((*p) == 'r') c = '\r';
else if ((*p) == 't') c = '\t';
else if ((*p) == 'e') c = '\033';
}
// collecting characters...
if (value_nextesc || (*p) != '\\') { // is quoted, or is not a quoting backslash - literal character
valbuf[buff_i++] = c;
}
value_nextesc = (!value_nextesc && (*p) == '\\');
valueCharDone:;
}
break;
case 8:
/* #line 247 "user/ini_parser.rl" */
{
ini_parser_error("Syntax error in key=value");
if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;}
}
break;
case 9:
/* #line 257 "user/ini_parser.rl" */
{ {cs = 1;goto _again;} }
break;
case 10:
/* #line 258 "user/ini_parser.rl" */
{
ini_parser_error("Syntax error in comment");
if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;}
}
break;
case 11:
/* #line 265 "user/ini_parser.rl" */
{ {cs = 1;goto _again;} }
break;
case 12:
/* #line 273 "user/ini_parser.rl" */
{ {cs = 8;goto _again;} }
break;
case 13:
/* #line 276 "user/ini_parser.rl" */
{
ini_parser_error("Syntax error in root");
{cs = 10;goto _again;}
}
break;
/* #line 458 "user/ini_parser.c" */
}
}
goto _again;
_again:
if ( cs == 0 )
goto _out;
if ( ++p != pe )
goto _resume;
_test_eof: {}
if ( p == eof )
{
const char *__acts = _ini_actions + _ini_eof_actions[cs];
unsigned int __nacts = (unsigned int) *__acts++;
while ( __nacts-- > 0 ) {
switch ( *__acts++ ) {
case 3:
/* #line 155 "user/ini_parser.rl" */
{
ini_parser_error("Syntax error in [section]");
if((*p) == '\n') {cs = 1; if ( p == pe )
goto _test_eof;
goto _again;} else {cs = 10; if ( p == pe )
goto _test_eof;
goto _again;}
}
break;
case 8:
/* #line 247 "user/ini_parser.rl" */
{
ini_parser_error("Syntax error in key=value");
if((*p) == '\n') {cs = 1; if ( p == pe )
goto _test_eof;
goto _again;} else {cs = 10; if ( p == pe )
goto _test_eof;
goto _again;}
}
break;
case 10:
/* #line 258 "user/ini_parser.rl" */
{
ini_parser_error("Syntax error in comment");
if((*p) == '\n') {cs = 1; if ( p == pe )
goto _test_eof;
goto _again;} else {cs = 10; if ( p == pe )
goto _test_eof;
goto _again;}
}
break;
case 13:
/* #line 276 "user/ini_parser.rl" */
{
ini_parser_error("Syntax error in root");
{cs = 10; if ( p == pe )
goto _test_eof;
goto _again;}
}
break;
/* #line 517 "user/ini_parser.c" */
}
}
}
_out: {}
}
/* #line 283 "user/ini_parser.rl" */
}

@ -0,0 +1,65 @@
#ifndef INIPARSE_STREAM_H
#define INIPARSE_STREAM_H
#include <esp8266.h>
#ifdef DEBUG_INI
#define ini_error(fmt, ...) error("[INI] "#fmt, ##__VA_ARGS__)
#else
#define ini_error(fmt, ...)
#endif
// buffer sizes
#define INI_KEY_MAX 64
#define INI_VALUE_MAX 256
/**
* INI parser callback, called for each found key-value pair.
*
* @param section - current section, empty string for global keys
* @param key - found key (trimmed of whitespace)
* @param value - value, trimmed of quotes or whitespace
* @param userData - opaque user data pointer, general purpose
*/
typedef void (*IniParserCallback)(const char *section, const char *key, const char *value, void *userData);
/**
* Begin parsing a stream
*
* @param callback - key callback to assign
* @param userData - optional user data that willb e passed to the callback
*/
void ini_parse_begin(IniParserCallback callback, void *userData);
/**
* End parse stream.
* Flushes what remains in the buffer and removes callback.
*
* @returns userData or NULL if none
*/
void* ini_parse_end(void);
/**
* Parse a string (needn't be complete line or file)
*
* @param data - string to parse
* @param len - string length (0 = use strlen)
*/
void ini_parse(const char *data, size_t len);
/**
* Parse a complete file loaded to string
*
* @param text - entire file as string
* @param len - file length (0 = use strlen)
* @param callback - key callback
* @param userData - optional user data for key callback
*/
void ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData);
/**
* Explicitly reset the parser
*/
void ini_parse_reset(void);
#endif // INIPARSE_STREAM_H

@ -0,0 +1,284 @@
/* Ragel constants block */
#include "ini_parser.h"
// Ragel setup
%%{
machine ini;
write data;
alphtype unsigned char;
}%%
// Persistent state
static int8_t cs = -1; //!< Ragel's Current State variable
static uint32_t buff_i = 0; //!< Write pointer for the buffers
static char value_quote = 0; //!< Quote character of the currently collected value
static bool value_nextesc = false; //!< Next character is escaped, trated specially, and if quote, as literal quote character
static IniParserCallback keyCallback = NULL; //!< Currently assigned callback
static void *userdata = NULL; //!< Currently assigned user data for the callback
// Buffers
static char keybuf[INI_KEY_MAX];
static char secbuf[INI_KEY_MAX];
static char valbuf[INI_VALUE_MAX];
// See header for doxygen!
void ICACHE_FLASH_ATTR
ini_parse_reset_partial(void)
{
buff_i = 0;
value_quote = 0;
value_nextesc = false;
}
void ICACHE_FLASH_ATTR
ini_parse_reset(void)
{
ini_parse_reset_partial();
keybuf[0] = secbuf[0] = valbuf[0] = 0;
%% write init;
}
void ICACHE_FLASH_ATTR
ini_parser_error(const char* msg)
{
ini_error("Parser error: %s", msg);
ini_parse_reset_partial();
}
void ICACHE_FLASH_ATTR
ini_parse_begin(IniParserCallback callback, void *userData)
{
keyCallback = callback;
userdata = userData;
ini_parse_reset();
}
void ICACHE_FLASH_ATTR
*ini_parse_end(void)
{
ini_parse("\n", 1);
if (keyCallback) {
keyCallback = NULL;
}
void *ud = userdata;
userdata = NULL;
return ud;
}
void ICACHE_FLASH_ATTR
ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData)
{
ini_parse_begin(callback, userData);
ini_parse(text, len);
ini_parse_end();
}
static void ICACHE_FLASH_ATTR
rtrim_buf(char *buf, int32_t end)
{
if (end > 0) {
while ((uint8_t)buf[--end] < 33);
end++; // go past the last character
}
buf[end] = 0;
}
void ICACHE_FLASH_ATTR
ini_parse(const char *newstr, size_t len)
{
int32_t i;
char c;
bool isnl;
bool isquot;
// Load new data to Ragel vars
const uint8_t *p;
const uint8_t *eof;
const uint8_t *pe;
if (len == 0) while(newstr[++len] != 0); // alternative to strlen
p = (const uint8_t *) newstr;
eof = NULL;
pe = (const uint8_t *) (newstr + len);
// Init Ragel on the first run
if (cs == -1) {
ini_parse_reset();
}
// The parser
%%{
#/ *
ispace = [ \t]; # inline space
wchar = any - 0..8 - 10..31;
#apos = '\'';
#quot = '\"';
nonl = [^\r\n];
nl = '\r'? '\n';
# ---- [SECTION] ----
action sectionStart {
buff_i = 0;
fgoto section;
}
action sectionChar {
if (buff_i >= INI_KEY_MAX) {
ini_parser_error("Section name too long");
fgoto discard2eol;
}
keybuf[buff_i++] = fc;
}
action sectionEnd {
// we need a separate buffer for the result, otherwise a failed
// partial parse would corrupt the section string
rtrim_buf(keybuf, buff_i);
for (i = 0; (c = keybuf[i]) != 0; i++) secbuf[i] = c;
secbuf[i] = 0;
fgoto main;
}
section :=
(
ispace* <: ((wchar - ']')+ @sectionChar) ']' @sectionEnd
) $!{
ini_parser_error("Syntax error in [section]");
if(fc == '\n') fgoto main; else fgoto discard2eol;
};
# ---- KEY=VALUE ----
action keyStart {
buff_i = 0;
keybuf[buff_i++] = fc; // add the first char
fgoto keyvalue;
}
action keyChar {
if (buff_i >= INI_KEY_MAX) {
ini_parser_error("Key too long");
fgoto discard2eol;
}
keybuf[buff_i++] = fc;
}
action keyEnd {
rtrim_buf(keybuf, buff_i);
// --- Value begin ---
buff_i = 0;
value_quote = 0;
value_nextesc = false;
}
action valueChar {
isnl = (fc == '\r' || fc == '\n');
isquot = (fc == '\'' || fc == '"');
// detect our starting quote
if (isquot && !value_nextesc && buff_i == 0 && value_quote == 0) {
value_quote = fc;
goto valueCharDone;
}
if (buff_i >= INI_VALUE_MAX) {
ini_parser_error("Value too long");
fgoto discard2eol;
}
// end of string - clean up and report
if ((!value_nextesc && fc == value_quote) || isnl) {
if (isnl && value_quote) {
ini_parser_error("Unterminated string");
fgoto main;
}
// unquoted: trim from the end
if (!value_quote) {
rtrim_buf(valbuf, buff_i);
} else {
valbuf[buff_i] = 0;
}
if (keyCallback) {
keyCallback(secbuf, keybuf, valbuf, userdata);
}
// we don't want to discard to eol if the string was terminated by eol
// - would delete the next line
if (isnl) fgoto main; else fgoto discard2eol;
}
c = fc;
// escape...
if (value_nextesc) {
if (fc == 'n') c = '\n';
else if (fc == 'r') c = '\r';
else if (fc == 't') c = '\t';
else if (fc == 'e') c = '\033';
}
// collecting characters...
if (value_nextesc || fc != '\\') { // is quoted, or is not a quoting backslash - literal character
valbuf[buff_i++] = c;
}
value_nextesc = (!value_nextesc && fc == '\\');
valueCharDone:;
}
# use * for key, first char is already consumed.
keyvalue :=
(
([^\n=:]* @keyChar %keyEnd)
[=:] ispace* <: nonl* @valueChar nl @valueChar
) $!{
ini_parser_error("Syntax error in key=value");
if(fc == '\n') fgoto main; else fgoto discard2eol;
};
# ---- COMMENT ----
comment :=
(
nonl* nl
@{ fgoto main; }
) $!{
ini_parser_error("Syntax error in comment");
if(fc == '\n') fgoto main; else fgoto discard2eol;
};
# ---- CLEANUP ----
discard2eol := nonl* nl @{ fgoto main; };
# ---- ROOT ----
main :=
(space*
(
'[' @sectionStart |
[#;] @{ fgoto comment; } |
(wchar - [\t =:]) @keyStart
)
) $!{
ini_parser_error("Syntax error in root");
fgoto discard2eol;
};
write exec;
#*/
}%%
}

@ -98,7 +98,10 @@ static void ICACHE_FLASH_ATTR resetBtnTimerCb(void *arg) {
} else { } else {
// Switch LED pins back to UART mode // Switch LED pins back to UART mode
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD); PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK); if (sysconf->gpio2_conf == GPIOCONF_OFF) {
// only if uart is enabled
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
}
if (resetCnt>=12) { //6 secs pressed - FR (timer is at 500 ms) if (resetCnt>=12) { //6 secs pressed - FR (timer is at 500 ms)
info("Restoring to default settings via BOOT button!"); info("Restoring to default settings via BOOT button!");
@ -140,3 +143,61 @@ void ICACHE_FLASH_ATTR ioInit() {
} }
} }
struct pinmapping {
bool set;
bool reset;
bool enable;
bool disable;
bool input;
bool pullup;
};
void ICACHE_FLASH_ATTR userGpioInit(void)
{
const struct pinmapping pin_mappings[5] = {
// S R E D I P
{0, 1, 0, 1, 0, 0}, // OFF
{0, 1, 1, 0, 0, 0}, // OUT 0
{1, 0, 1, 0, 0, 0}, // OUT 1
{0, 0, 0, 1, 1, 1}, // IN PULL
{0, 0, 0, 1, 1, 0}, // IN NOPULL
};
u8 num;
const struct pinmapping *pm;
// GPIO2
num = 2;
pm = &pin_mappings[sysconf->gpio2_conf];
gpio_output_set((uint32) (pm->set << num), (uint32) (pm->reset << num), (uint32) (pm->enable << num), (uint32) (pm->disable << num));
if (pm->pullup) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_GPIO2_U);
} else {
PIN_PULLUP_DIS(PERIPHS_IO_MUX_GPIO2_U);
}
if (sysconf->gpio2_conf == GPIOCONF_OFF) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
} else {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2);
}
// GPIO4
num = 4;
pm = &pin_mappings[sysconf->gpio4_conf];
gpio_output_set((uint32) (pm->set << num), (uint32) (pm->reset << num), (uint32) (pm->enable << num), (uint32) (pm->disable << num));
if (pm->pullup) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_GPIO4_U);
} else {
PIN_PULLUP_DIS(PERIPHS_IO_MUX_GPIO4_U);
}
// GPIO5
num = 5;
pm = &pin_mappings[sysconf->gpio5_conf];
gpio_output_set((uint32) (pm->set << num), (uint32) (pm->reset << num), (uint32) (pm->enable << num), (uint32) (pm->disable << num));
if (pm->pullup) {
PIN_PULLUP_EN(PERIPHS_IO_MUX_GPIO5_U);
} else {
PIN_PULLUP_DIS(PERIPHS_IO_MUX_GPIO5_U);
}
}

@ -2,5 +2,6 @@
#define IO_H #define IO_H
void ioInit(void); void ioInit(void);
void userGpioInit(void);
#endif #endif

@ -23,8 +23,8 @@
static int ICACHE_FLASH_ATTR wifiPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen) static int ICACHE_FLASH_ATTR wifiPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen)
{ {
if (no == 0) { if (no == 0) {
os_strcpy(user, sysconf->access_name); os_strcpy(user, (const char *) sysconf->access_name);
os_strcpy(pass, sysconf->access_pw); os_strcpy(pass, (const char *) sysconf->access_pw);
return 1; return 1;
} }
if (no == 1) { if (no == 1) {
@ -103,6 +103,7 @@ const HttpdBuiltInUrl routes[] ESP_CONST_DATA = {
ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"), ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"),
ROUTE_TPL_FILE("/about/?", tplAbout, "/about.tpl"), ROUTE_TPL_FILE("/about/?", tplAbout, "/about.tpl"),
ROUTE_FILE("/help/?", "/help.html"), ROUTE_FILE("/help/?", "/help.html"),
ROUTE_TPL_FILE("/cfg/gpio/?", tplGpio, "/cfg_gpio.tpl"),
// --- Sockets --- // --- Sockets ---
ROUTE_WS(URL_WS_UPDATE, updateSockConnect), ROUTE_WS(URL_WS_UPDATE, updateSockConnect),
@ -114,6 +115,7 @@ const HttpdBuiltInUrl routes[] ESP_CONST_DATA = {
ROUTE_CGI(API_PING"/?", cgiPing), ROUTE_CGI(API_PING"/?", cgiPing),
ROUTE_CGI(API_CLEAR"/?", cgiResetScreen), ROUTE_CGI(API_CLEAR"/?", cgiResetScreen),
ROUTE_CGI(API_D2D_MSG"/?", cgiD2DMessage), ROUTE_CGI(API_D2D_MSG"/?", cgiD2DMessage),
ROUTE_CGI(API_GPIO"/?", cgiGPIO),
ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"), ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"),
@ -131,6 +133,8 @@ const HttpdBuiltInUrl routes[] ESP_CONST_DATA = {
ROUTE_TPL_FILE("/cfg/system/?", tplSystemCfg, "/cfg_system.tpl"), ROUTE_TPL_FILE("/cfg/system/?", tplSystemCfg, "/cfg_system.tpl"),
ROUTE_CGI("/cfg/system/set", cgiSystemCfgSetParams), ROUTE_CGI("/cfg/system/set", cgiSystemCfgSetParams),
ROUTE_CGI("/cfg/system/export", cgiPersistExport),
ROUTE_CGI("/cfg/system/import", cgiPersistImport),
ROUTE_CGI("/cfg/system/write_defaults", cgiPersistWriteDefaults), ROUTE_CGI("/cfg/system/write_defaults", cgiPersistWriteDefaults),
ROUTE_CGI("/cfg/system/restore_defaults", cgiPersistRestoreDefaults), ROUTE_CGI("/cfg/system/restore_defaults", cgiPersistRestoreDefaults),
ROUTE_CGI("/cfg/system/restore_hard", cgiPersistRestoreHard), ROUTE_CGI("/cfg/system/restore_hard", cgiPersistRestoreHard),

@ -8,6 +8,7 @@
#include "character_sets.h" #include "character_sets.h"
#include "utf8.h" #include "utf8.h"
#include "cgi_sockets.h" #include "cgi_sockets.h"
#include "cgi_logging.h"
TerminalConfigBundle * const termconf = &persist.current.termconf; TerminalConfigBundle * const termconf = &persist.current.termconf;
TerminalConfigBundle termconf_live; TerminalConfigBundle termconf_live;
@ -38,6 +39,7 @@ static Cell screen[MAX_SCREEN_SIZE];
#define TABSTOP_WORDS 5 #define TABSTOP_WORDS 5
#define LINE_ATTRS_COUNT 64
/** /**
* Screen state structure * Screen state structure
*/ */
@ -56,9 +58,12 @@ static struct {
int vm1; int vm1;
u32 tab_stops[TABSTOP_WORDS]; // tab stops bitmap u32 tab_stops[TABSTOP_WORDS]; // tab stops bitmap
u8 line_attribs[LINE_ATTRS_COUNT]; // assume that's quite enough...
char last_char[4]; char last_char[4];
} scr; } scr;
#define IS_DOUBLE_WIDTH() (scr.line_attribs[cursor.y]&0b001)
#define TOP scr.vm0 #define TOP scr.vm0
#define BTM scr.vm1 #define BTM scr.vm1
#define RH (scr.vm1 - scr.vm0 + 1) #define RH (scr.vm1 - scr.vm0 + 1)
@ -106,8 +111,19 @@ bool cursor_saved = false;
static struct { static struct {
bool alternate_active; bool alternate_active;
char title[TERM_TITLE_LEN]; char title[TERM_TITLE_LEN];
char btn[TERM_BTN_COUNT][TERM_BTN_LEN];
char btn_msg[TERM_BTN_COUNT][TERM_BTN_MSG_LEN]; char btn1[TERM_BTN_LEN];
char btn2[TERM_BTN_LEN];
char btn3[TERM_BTN_LEN];
char btn4[TERM_BTN_LEN];
char btn5[TERM_BTN_LEN];
char btn1_msg[TERM_BTN_MSG_LEN];
char btn2_msg[TERM_BTN_MSG_LEN];
char btn3_msg[TERM_BTN_MSG_LEN];
char btn4_msg[TERM_BTN_MSG_LEN];
char btn5_msg[TERM_BTN_MSG_LEN];
u32 width; u32 width;
u32 height; u32 height;
int vm0; int vm0;
@ -143,46 +159,200 @@ static struct {
int x_min, y_min, x_max, y_max; int x_min, y_min, x_max, y_max;
} scr_dirty; } scr_dirty;
#define reset_screen_dirty() do { \ static void ICACHE_FLASH_ATTR reset_screen_dirty(void)
scr_dirty.x_min = W; \ {
scr_dirty.x_max = -1; \ scr_dirty.x_min = W;
scr_dirty.y_min = H; \ scr_dirty.x_max = -1;
scr_dirty.y_max = -1; \ scr_dirty.y_min = H;
} while(0) scr_dirty.y_max = -1;
}
#define expand_dirty(y0, y1, x0, x1) do { \ static void ICACHE_FLASH_ATTR expand_dirty(int y0, int y1, int x0, int x1)
seri_dbg("Expand: X: (%d..%d) -> %d..%d, Y: (%d..%d) -> %d..%d", scr_dirty.x_min, scr_dirty.x_max, x0, x1, scr_dirty.y_min, scr_dirty.y_max, y0, y1); \ {
if ((int)(y0) < scr_dirty.y_min) scr_dirty.y_min = (y0); \ seri_dbg("Expand: X: (%d..%d) -> %d..%d, Y: (%d..%d) -> %d..%d",
if ((int)(x0) < scr_dirty.x_min) scr_dirty.x_min = (x0); \ scr_dirty.x_min, scr_dirty.x_max, x0, x1, scr_dirty.y_min, scr_dirty.y_max, y0, y1);
if ((int)(y1) > scr_dirty.y_max) scr_dirty.y_max = (y1); \
if ((int)(x1) > scr_dirty.x_max) scr_dirty.x_max = (x1); \ if ((y0) < scr_dirty.y_min) scr_dirty.y_min = (y0);
} while(0) if ((x0) < scr_dirty.x_min) scr_dirty.x_min = (x0);
if ((y1) > scr_dirty.y_max) scr_dirty.y_max = (y1);
if ((x1) > scr_dirty.x_max) scr_dirty.x_max = (x1);
}
#define NOTIFY_LOCK() do { \ #define NOTIFY_LOCK() { notifyLock++; }
notifyLock++; \
} while(0) static void ICACHE_FLASH_ATTR NOTIFY_DONE(u32 updateTopics)
{
#define NOTIFY_DONE(updateTopics) do { \ lockTopics |= (updateTopics);
lockTopics |= (updateTopics); \ if (notifyLock > 0) notifyLock--;
if (notifyLock > 0) notifyLock--; \ if (notifyLock == 0) {
if (notifyLock == 0) { \ screen_notifyChange(lockTopics);
screen_notifyChange(lockTopics); \ lockTopics = 0;
lockTopics = 0;\ }
} \ }
} while(0)
/** Clear the hanging attribute if the cursor is no longer >= W */ /** Clear the hanging attribute if the cursor is no longer >= W */
#define clear_invalid_hanging() do { \ static void ICACHE_FLASH_ATTR clear_invalid_hanging(void)
if (cursor.hanging && cursor.x != W-1) { \ {
cursor.hanging = false; \ if (cursor.hanging && cursor.x != W-1) {
screen_notifyChange(TOPIC_CHANGE_CURSOR); \ cursor.hanging = false;
} \ screen_notifyChange(TOPIC_CHANGE_CURSOR);
} while(false) }
}
#define cursor_inside_region() (cursor.y >= TOP && cursor.y <= BTM) #define cursor_inside_region() (cursor.y >= TOP && cursor.y <= BTM)
//region --- Settings --- //region --- Settings ---
/** Export color for config */
void ICACHE_FLASH_ATTR
xget_term_color(char *buff, u32 value)
{
if (value < 256) {
sprintf(buff, "%d", value);
} else {
sprintf(buff, "#%06X", value - 256);
}
}
/** Export button message as stirng for config */
void ICACHE_FLASH_ATTR
xget_term_bm(char *buff, char *value)
{
char c;
char *bp = buff;
char *cp = value;
buff[0] = 0;
int n = 0;
while((c = *cp++) != 0) {
if(n>0) {
*bp = ',';
bp++;
}
bp += sprintf(bp, "%d", (u8)c);
n++;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_term_bm(const char *name, char *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
// parse: comma,space or semicolon separated decimal values of ASCII codes
char c;
const char *cp = buff;
int char_i = 0;
int acu = 0;
bool lastsp = 1;
char buff_bm[TERM_BTN_MSG_LEN];
while ((c = *cp++) != 0) {
if (c == ' ' || c == ',' || c == ';') {
if(lastsp) continue;
if (acu==0 || acu>255) {
cgi_warn("Bad value! %d", acu);
return XSET_FAIL;
}
if (char_i >= TERM_BTN_MSG_LEN-1) {
cgi_warn("Too long! %d", char_i);
return XSET_FAIL;
}
dbg("+ %c", acu);
buff_bm[char_i++] = (char)acu;
// prepare for next char
acu = 0;
lastsp = 1;
} else if (c>='0'&&c<='9') {
lastsp = 0;
acu *= 10;
acu += c - '0';
} else {
cgi_warn("Bad syntax!");
return XSET_FAIL;
}
}
if (lastsp && char_i == 0) {
cgi_warn("Required!");
return XSET_FAIL;
}
if (!lastsp&&acu>0) {
dbg("+ %c", acu);
buff_bm[char_i++] = (char)acu;
}
buff_bm[char_i] = 0;
if (char_i == 0) {
cgi_warn("Required!");
return XSET_FAIL;
}
if (!streq(field, buff_bm)) {
strncpy(field, buff_bm, TERM_BTN_MSG_LEN);
return XSET_SET;
}
return XSET_UNCHANGED;
}
/** convert hex number to int */
static ICACHE_FLASH_ATTR u32
decodehex(const char *buf) {
u32 n = 0;
char c;
while ((c = *buf++) != 0) {
if (c >= '0' && c <= '9') c -= '0';
else if (c >= 'a' && c <= 'f') c -= 'a'-10;
else if (c >= 'A' && c <= 'F') c -= 'A'-10;
else c = 0;
n *= 16;
n += c;
}
return n;
}
enum xset_result ICACHE_FLASH_ATTR
xset_term_color(const char *name, u32 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
u32 n;
if (buff[0] == '#') {
// decode hex
n = decodehex(buff+1);
n += 256;
} else {
n = atoi(buff);
}
if (*field != n) {
*field = n;
return XSET_SET;
}
return XSET_UNCHANGED;
}
enum xset_result ICACHE_FLASH_ATTR
xset_term_cursorshape(const char *name, u32 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
u32 n = atoi(buff);
if (n >= 0 && n <= 6 && n != 1) {
if (*field != n) {
*field = n;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad cursor_shape num: %s", buff);
return XSET_FAIL;
}
}
/** /**
* Restore hard defaults * Restore hard defaults
*/ */
@ -194,10 +364,21 @@ terminal_restore_defaults(void)
termconf->default_bg = 0; termconf->default_bg = 0;
termconf->default_fg = 7; termconf->default_fg = 7;
sprintf(termconf->title, SCR_DEF_TITLE); sprintf(termconf->title, SCR_DEF_TITLE);
for(int i=1; i <= TERM_BTN_COUNT; i++) {
sprintf(termconf->btn[i-1], "%d", i); strcpy(termconf->btn1, "1");
sprintf(termconf->btn_msg[i-1], "%c", i); strcpy(termconf->btn2, "2");
} strcpy(termconf->btn3, "3");
strcpy(termconf->btn4, "4");
strcpy(termconf->btn5, "5");
strcpy((char*)termconf->bm1, "\x01");
strcpy((char*)termconf->bm2, "\x02");
strcpy((char*)termconf->bm3, "\x03");
strcpy((char*)termconf->bm4, "\x04");
strcpy((char*)termconf->bm5, "\x05");
termconf->bc1 = termconf->bc2 = termconf->bc3 = termconf->bc4 = termconf->bc5 = 0;
termconf->theme = 0; termconf->theme = 0;
termconf->parser_tout_ms = SCR_DEF_PARSER_TOUT_MS; termconf->parser_tout_ms = SCR_DEF_PARSER_TOUT_MS;
termconf->display_tout_ms = SCR_DEF_DISPLAY_TOUT_MS; termconf->display_tout_ms = SCR_DEF_DISPLAY_TOUT_MS;
@ -213,6 +394,9 @@ 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;
termconf->font_stack[0] = 0;
termconf->font_size = 20;
} }
/** /**
@ -231,22 +415,47 @@ terminal_apply_settings_noclear(void)
{ {
bool changed = false; bool changed = false;
// char buff[64];
//#define XSTRUCT termconf
//#define X XDUMP_FIELD
// XTABLE_TERMCONF
//#undef X
// return;
// Migrate // Migrate
if (termconf->config_version < 1) { if (termconf->config_version < 1) {
persist_dbg("termconf: Updating to version %d", 1); persist_dbg("termconf: Updating to version 1");
termconf->debugbar = SCR_DEF_DEBUGBAR; termconf->debugbar = SCR_DEF_DEBUGBAR;
changed = 1; changed = 1;
} }
if (termconf->config_version < 2) { if (termconf->config_version < 2) {
persist_dbg("termconf: Updating to version %d", 1); persist_dbg("termconf: Updating to version 2");
termconf->allow_decopt_12 = SCR_DEF_DECOPT12; termconf->allow_decopt_12 = SCR_DEF_DECOPT12;
changed = 1; changed = 1;
} }
if (termconf->config_version < 3) { if (termconf->config_version < 3) {
persist_dbg("termconf: Updating to version %d", 1); persist_dbg("termconf: Updating to version 3");
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 4");
termconf->backdrop[0] = 0;
changed = 1;
}
if (termconf->config_version < 5) {
persist_dbg("termconf: Updating to version 5");
termconf->button_count = SCR_DEF_BUTTON_COUNT;
changed = 1;
}
if (termconf->config_version < 6) {
persist_dbg("termconf: Updating to version 6");
termconf->backdrop[0] = 0;
termconf->font_stack[0] = 0;
termconf->font_size = 20;
termconf->bc1 = termconf->bc2 = termconf->bc3 = termconf->bc4 = termconf->bc5 = 0;
changed = 1;
}
termconf->config_version = TERMCONF_VERSION; termconf->config_version = TERMCONF_VERSION;
@ -346,7 +555,12 @@ screen_reset_sgr(void)
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
screen_reset_do(bool size, bool labels) screen_reset_do(bool size, bool labels)
{ {
ScreenNotifyTopics topics = TOPIC_CHANGE_SCREEN_OPTS | TOPIC_CHANGE_CURSOR | TOPIC_CHANGE_CONTENT_ALL; ScreenNotifyTopics topics =
TOPIC_CHANGE_SCREEN_OPTS
| TOPIC_CHANGE_CURSOR
| TOPIC_CHANGE_CONTENT_ALL
| TOPIC_DOUBLE_LINES;
NOTIFY_LOCK(); NOTIFY_LOCK();
// DECopts // DECopts
@ -383,18 +597,23 @@ screen_reset_do(bool size, bool labels)
scr.tab_stops[i] = 0x80808080; scr.tab_stops[i] = 0x80808080;
} }
// clear line attribs
memset(scr.line_attribs, 0, LINE_ATTRS_COUNT);
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++) { strcpy(termconf_live.btn1, termconf->btn1);
strcpy(termconf_live.btn[i], termconf->btn[i]); strcpy(termconf_live.btn2, termconf->btn2);
strcpy(termconf_live.btn_msg[i], termconf->btn_msg[i]); strcpy(termconf_live.btn3, termconf->btn3);
} strcpy(termconf_live.btn4, termconf->btn4);
strcpy(termconf_live.btn5, termconf->btn5);
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
@ -445,8 +664,11 @@ screen_swap_state(bool alternate)
ansi_dbg("Swap to alternate"); ansi_dbg("Swap to alternate");
// store old state // store old state
memcpy(state_backup.title, termconf_live.title, TERM_TITLE_LEN); memcpy(state_backup.title, termconf_live.title, TERM_TITLE_LEN);
memcpy(state_backup.btn, termconf_live.btn, sizeof(termconf_live.btn));
memcpy(state_backup.btn_msg, termconf_live.btn_msg, sizeof(termconf_live.btn_msg)); // copy multiple fields at once
memcpy(state_backup.btn1, termconf_live.btn1, sizeof(termconf_live.btn1)*TERM_BTN_COUNT);
memcpy(state_backup.btn1_msg, termconf_live.bm1, sizeof(termconf_live.bm1)*TERM_BTN_COUNT);
memcpy(state_backup.tab_stops, scr.tab_stops, sizeof(scr.tab_stops)); memcpy(state_backup.tab_stops, scr.tab_stops, sizeof(scr.tab_stops));
state_backup.vm0 = scr.vm0; state_backup.vm0 = scr.vm0;
state_backup.vm1 = scr.vm1; state_backup.vm1 = scr.vm1;
@ -458,8 +680,11 @@ screen_swap_state(bool alternate)
else { else {
ansi_dbg("Unswap from alternate"); ansi_dbg("Unswap from alternate");
memcpy(termconf_live.title, state_backup.title, TERM_TITLE_LEN); memcpy(termconf_live.title, state_backup.title, TERM_TITLE_LEN);
memcpy(termconf_live.btn, state_backup.btn, sizeof(termconf_live.btn));
memcpy(termconf_live.btn_msg, state_backup.btn_msg, sizeof(termconf_live.btn_msg)); // copy multiple fields at once
memcpy(termconf_live.btn1, state_backup.btn1, sizeof(termconf_live.btn1)*TERM_BTN_COUNT);
memcpy(termconf_live.bm1, state_backup.btn1_msg, sizeof(termconf_live.bm1)*TERM_BTN_COUNT);
memcpy(scr.tab_stops, state_backup.tab_stops, sizeof(scr.tab_stops)); memcpy(scr.tab_stops, state_backup.tab_stops, sizeof(scr.tab_stops));
scr.vm0 = state_backup.vm0; scr.vm0 = state_backup.vm0;
scr.vm1 = state_backup.vm1; scr.vm1 = state_backup.vm1;
@ -474,6 +699,32 @@ screen_swap_state(bool alternate)
//endregion //endregion
//region --- Double lines ---
void ICACHE_FLASH_ATTR
screen_set_line_attr(uint8_t double_w, uint8_t double_h_top, uint8_t double_h_bot)
{
NOTIFY_LOCK();
u8 attr = scr.line_attribs[cursor.y];
if (double_w==2) attr |= 0b001;
else if (double_w==1) attr &= ~0b001;
if (double_h_top==2) attr |= 0b010;
else if (double_h_top==1) attr &= ~0b010;
if (double_h_bot==2) attr |= 0b100;
else if (double_h_bot==1) attr &= ~0b100;
scr.line_attribs[cursor.y] = attr;
if (attr & 0b001) {
// if we're using double width - clamp cursor X position
// TODO this should happen in all cursor ops - now it can sometimes go offscreen
if (cursor.x >= W/2) cursor.x = W/2;
}
NOTIFY_DONE(TOPIC_DOUBLE_LINES);
}
//endregion
//region --- Tab stops --- //region --- Tab stops ---
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
@ -509,6 +760,7 @@ next_tab_stop(void)
{ {
// cursor must never go past EOL // cursor must never go past EOL
if (cursor.x >= W-1) return -1; if (cursor.x >= W-1) return -1;
if (IS_DOUBLE_WIDTH() && cursor.x >= W/2-1) return -1;
// find first word to inspect // find first word to inspect
int idx = (cursor.x+1)/32; int idx = (cursor.x+1)/32;
@ -520,6 +772,7 @@ next_tab_stop(void)
for(;offs<32;offs++) { for(;offs<32;offs++) {
cp++; cp++;
if (cp >= W) return -1; if (cp >= W) return -1;
if (IS_DOUBLE_WIDTH() && cp >= W/2) return -1;
if (w & 1) return cp; if (w & 1) return cp;
w >>= 1; w >>= 1;
} }
@ -577,6 +830,7 @@ screen_tab_forward(int count)
} }
else { else {
cursor.x = W - 1; cursor.x = W - 1;
if (IS_DOUBLE_WIDTH()) cursor.x = W/2 - 1;
} }
} }
NOTIFY_DONE(TOPIC_CHANGE_CURSOR); NOTIFY_DONE(TOPIC_CHANGE_CURSOR);
@ -778,19 +1032,23 @@ screen_clear(ClearMode mode)
unicode_cache_clear(); unicode_cache_clear();
clear_range_noutf(0, W * H - 1); clear_range_noutf(0, W * H - 1);
scr.last_char[0] = 0; scr.last_char[0] = 0;
for (int i = 0; i < LINE_ATTRS_COUNT; i++) scr.line_attribs[i] = 0;
break; break;
case CLEAR_FROM_CURSOR: case CLEAR_FROM_CURSOR:
clear_range_utf((cursor.y * W) + cursor.x, W * H - 1); clear_range_utf((cursor.y * W) + cursor.x, W * H - 1);
expand_dirty(cursor.y, H-1, 0, W-1); expand_dirty(cursor.y, H-1, 0, W-1);
for (int i = cursor.y; i < LINE_ATTRS_COUNT; i++) scr.line_attribs[i] = 0;
break; break;
case CLEAR_TO_CURSOR: case CLEAR_TO_CURSOR:
clear_range_utf(0, (cursor.y * W) + cursor.x); clear_range_utf(0, (cursor.y * W) + cursor.x);
expand_dirty(0, cursor.y, 0, W-1); expand_dirty(0, cursor.y, 0, W-1);
for (int i = 0; i <= cursor.y; i++) scr.line_attribs[i] = 0;
break; break;
} }
NOTIFY_DONE(mode == CLEAR_ALL ? TOPIC_CHANGE_CONTENT_ALL : TOPIC_CHANGE_CONTENT_PART); NOTIFY_DONE((mode == CLEAR_ALL ? TOPIC_CHANGE_CONTENT_ALL : TOPIC_CHANGE_CONTENT_PART)
| TOPIC_DOUBLE_LINES);
} }
/** /**
@ -842,11 +1100,15 @@ screen_insert_lines(unsigned int lines)
int targetStart = cursor.y + lines; int targetStart = cursor.y + lines;
if (targetStart > BTM) { if (targetStart > BTM) {
clear_range_utf(cursor.y*W, (BTM+1)*W-1); clear_range_utf(cursor.y*W, (BTM+1)*W-1);
for (int i = cursor.y; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
} else { } else {
// do the moving // do the moving
for (int i = BTM; i >= targetStart; i--) { for (int i = BTM; i >= targetStart; i--) {
utf_free_row(i); // release old characters utf_free_row(i); // release old characters
copy_row(i, i - lines); copy_row(i, i - lines);
scr.line_attribs[i] = scr.line_attribs[i-lines];
if (i != targetStart) utf_backup_row(i); if (i != targetStart) utf_backup_row(i);
} }
@ -858,7 +1120,7 @@ screen_insert_lines(unsigned int lines)
} }
} }
expand_dirty(cursor.y, BTM, 0, W - 1); expand_dirty(cursor.y, BTM, 0, W - 1);
NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART|TOPIC_DOUBLE_LINES);
} }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
@ -873,18 +1135,26 @@ screen_delete_lines(unsigned int lines)
// clear the entire rest of the screen // clear the entire rest of the screen
movedBlockEnd = cursor.y; movedBlockEnd = cursor.y;
clear_range_utf(movedBlockEnd*W, (BTM+1)*W-1); clear_range_utf(movedBlockEnd*W, (BTM+1)*W-1);
for (int i = movedBlockEnd; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
} else { } else {
// move some lines up, clear the rest // move some lines up, clear the rest
for (int i = cursor.y; i <= movedBlockEnd; i++) { for (int i = cursor.y; i <= movedBlockEnd; i++) {
utf_free_row(i); utf_free_row(i);
copy_row(i, i+lines); copy_row(i, i+lines);
scr.line_attribs[i] = scr.line_attribs[i+lines];
if (i != movedBlockEnd) utf_backup_row(i); if (i != movedBlockEnd) utf_backup_row(i);
} }
clear_range_noutf((movedBlockEnd+1)*W, (BTM+1)*W-1); clear_range_noutf((movedBlockEnd+1)*W, (BTM+1)*W-1);
for (int i = movedBlockEnd+1; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
} }
expand_dirty(cursor.y, BTM, 0, W - 1); expand_dirty(cursor.y, BTM, 0, W - 1);
NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART|TOPIC_DOUBLE_LINES);
} }
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
@ -982,8 +1252,8 @@ screen_resize(int rows, int cols)
if (W == cols && H == rows) return; // Do nothing if (W == cols && H == rows) return; // Do nothing
NOTIFY_LOCK(); NOTIFY_LOCK();
W = cols; W = (u32) cols;
H = rows; H = (u32) rows;
screen_reset_on_resize(); screen_reset_on_resize();
NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS|TOPIC_CHANGE_CONTENT_ALL|TOPIC_CHANGE_CURSOR); NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS|TOPIC_CHANGE_CONTENT_ALL|TOPIC_CHANGE_CURSOR);
} }
@ -1005,10 +1275,82 @@ void ICACHE_FLASH_ATTR
screen_set_button_text(int num, const char *text) screen_set_button_text(int num, const char *text)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
strncpy(termconf_live.btn[num-1], text, TERM_BTN_LEN);
if (num >= 1 && num <= TERM_BTN_COUNT) {
char *buf = TERM_BTN_N(&termconf_live, num - 1);
strncpy(buf, text, TERM_BTN_MSG_LEN);
}
else ansi_warn("Bad button num: %d", num);
NOTIFY_DONE(TOPIC_CHANGE_BUTTONS);
}
/**
* Helper function to set terminal button label
* @param num - button number 1-5
* @param str - button text
*/
void ICACHE_FLASH_ATTR
screen_set_button_message(int num, const char *msg)
{
NOTIFY_LOCK();
if (num >= 1 && num <= TERM_BTN_COUNT) {
char *buf = TERM_BM_N(&termconf_live, num - 1);
strncpy(buf, msg, TERM_BTN_MSG_LEN);
}
else ansi_warn("Bad button num: %d", num);
NOTIFY_DONE(TOPIC_CHANGE_BUTTONS); NOTIFY_DONE(TOPIC_CHANGE_BUTTONS);
} }
/**
* Helper function to set terminal button label
* @param num - button number 1-5
* @param str - button text
*/
void ICACHE_FLASH_ATTR
screen_set_button_color(int num, const char *buf)
{
NOTIFY_LOCK();
if (num >= 1 && num <= TERM_BTN_COUNT) {
u32 *fieldptr = &termconf_live.bc1 + (num-1);
xset_term_color("", fieldptr, buf, NULL);
}
else ansi_warn("Bad button num: %d", num);
NOTIFY_DONE(TOPIC_CHANGE_BUTTONS);
}
/**
* Set button count
* @param count - count 1-5
*/
void ICACHE_FLASH_ATTR
screen_set_button_count(int count)
{
NOTIFY_LOCK();
if (count >= 0 && count <= 5) {
dbg("%d ", count);
termconf_live.button_count = (u8) count;
}
else ansi_warn("Bad button count: %d", count);
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
*/ */
@ -1019,6 +1361,9 @@ screen_scroll_up(unsigned int lines)
if (lines >= RH) { if (lines >= RH) {
// clear entire region // clear entire region
clear_range_utf(TOP * W, (BTM + 1) * W - 1); clear_range_utf(TOP * W, (BTM + 1) * W - 1);
for (int i = TOP; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
goto done; goto done;
} }
@ -1031,14 +1376,18 @@ screen_scroll_up(unsigned int lines)
for (y = TOP; y <= BTM - lines; y++) { for (y = TOP; y <= BTM - lines; y++) {
utf_free_row(y); utf_free_row(y);
copy_row(y, y+lines); copy_row(y, y+lines);
scr.line_attribs[y] = scr.line_attribs[y+lines];
if (y < BTM - lines) utf_backup_row(y); if (y < BTM - lines) utf_backup_row(y);
} }
clear_range_noutf(y * W, (BTM + 1) * W - 1); clear_range_noutf(y * W, (BTM + 1) * W - 1);
for (int i = y; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
done: done:
expand_dirty(TOP, BTM, 0, W - 1); expand_dirty(TOP, BTM, 0, W - 1);
NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART|TOPIC_DOUBLE_LINES);
} }
/** /**
@ -1051,6 +1400,9 @@ screen_scroll_down(unsigned int lines)
if (lines >= RH) { if (lines >= RH) {
// clear entire region // clear entire region
clear_range_utf(TOP * W, (BTM + 1) * W - 1); clear_range_utf(TOP * W, (BTM + 1) * W - 1);
for (int i = TOP; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
goto done; goto done;
} }
@ -1063,13 +1415,17 @@ screen_scroll_down(unsigned int lines)
for (y = BTM; y >= TOP+lines; y--) { for (y = BTM; y >= TOP+lines; y--) {
utf_free_row(y); utf_free_row(y);
copy_row(y, y-lines); copy_row(y, y-lines);
scr.line_attribs[y] = scr.line_attribs[y-lines];
if (y > TOP + lines) utf_backup_row(y); if (y > TOP + lines) utf_backup_row(y);
} }
clear_range_noutf(TOP * W, TOP * W + lines * W - 1); clear_range_noutf(TOP * W, (TOP + lines) * W - 1);
for (int i = TOP; i < TOP + lines; i++) {
scr.line_attribs[i] = 0;
}
done: done:
expand_dirty(TOP, BTM, 0, W - 1); expand_dirty(TOP, BTM, 0, W - 1);
NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART); NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART|TOPIC_DOUBLE_LINES);
} }
/** Set scrolling region */ /** Set scrolling region */
@ -1102,7 +1458,7 @@ void ICACHE_FLASH_ATTR
screen_cursor_shape(enum CursorShape shape) screen_cursor_shape(enum CursorShape shape)
{ {
NOTIFY_LOCK(); NOTIFY_LOCK();
if (shape == CURSOR_DEFAULT) shape = termconf->cursor_shape; if (shape == CURSOR_DEFAULT) shape = (enum CursorShape) termconf->cursor_shape;
termconf_live.cursor_shape = shape; termconf_live.cursor_shape = shape;
NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS); NOTIFY_DONE(TOPIC_CHANGE_SCREEN_OPTS);
} }
@ -1174,6 +1530,9 @@ screen_cursor_set_x(int x)
// hanging happens when the cursor is virtually at col=81, which // hanging happens when the cursor is virtually at col=81, which
// cannot be set using the cursor-set commands. // cannot be set using the cursor-set commands.
cursor.hanging = false; cursor.hanging = false;
if (IS_DOUBLE_WIDTH() && cursor.x >= W/2) cursor.x = W/2-1;
NOTIFY_DONE(TOPIC_CHANGE_CURSOR); NOTIFY_DONE(TOPIC_CHANGE_CURSOR);
} }
@ -1218,6 +1577,7 @@ screen_cursor_move(int dy, int dx, bool scroll)
cursor.x += dx; cursor.x += dx;
cursor.y += dy; cursor.y += dy;
if (cursor.x >= (int)W) cursor.x = W - 1; if (cursor.x >= (int)W) cursor.x = W - 1;
if (IS_DOUBLE_WIDTH() && cursor.x >= W/2) cursor.x = W/2-1;
if (cursor.x < (int)0) { if (cursor.x < (int)0) {
if (cursor.auto_wrap && cursor.reverse_wrap) { if (cursor.auto_wrap && cursor.reverse_wrap) {
// this is mimicking a behavior from xterm that allows any number of steps backwards with reverse wraparound enabled // this is mimicking a behavior from xterm that allows any number of steps backwards with reverse wraparound enabled
@ -1324,6 +1684,8 @@ screen_cursor_restore(bool withAttrs)
} }
} }
if (IS_DOUBLE_WIDTH() && cursor.x >= W/2) cursor.x = W/2-1;
NOTIFY_DONE(TOPIC_CHANGE_CURSOR); NOTIFY_DONE(TOPIC_CHANGE_CURSOR);
} }
@ -1337,7 +1699,7 @@ screen_back_index(int count)
cursor.x = new_x; cursor.x = new_x;
} else { } else {
cursor.x = 0; cursor.x = 0;
screen_insert_characters(-new_x); screen_insert_characters((unsigned int) -new_x);
topics |= TOPIC_CHANGE_CONTENT_PART; topics |= TOPIC_CHANGE_CONTENT_PART;
} }
NOTIFY_DONE(topics); NOTIFY_DONE(topics);
@ -1529,7 +1891,7 @@ do_save_private_opt(int n, bool save)
{ {
ScreenNotifyTopics topics = TOPIC_INTERNAL; ScreenNotifyTopics topics = TOPIC_INTERNAL;
if (!save) NOTIFY_LOCK(); if (!save) NOTIFY_LOCK();
#define SAVE_RESTORE(sf, of) do { if (save) sf=(of); else of=(sf); } while(0) #define SAVE_RESTORE(sf, of) do { if (save) (sf)=(of); else (of)=(sf); } while(0)
switch (n) { switch (n) {
case 1: case 1:
SAVE_RESTORE(opt_backup.cursors_alt_mode, scr.cursors_alt_mode); SAVE_RESTORE(opt_backup.cursors_alt_mode, scr.cursors_alt_mode);
@ -1692,6 +2054,10 @@ putchar_graphic(const char *ch)
cursor.hanging = true; // hanging - next typed char wraps around, but backspace and arrows still stay on the same line. cursor.hanging = true; // hanging - next typed char wraps around, but backspace and arrows still stay on the same line.
cursor.x = W - 1; cursor.x = W - 1;
} }
if (IS_DOUBLE_WIDTH() && cursor.x >= W/2) {
cursor.hanging = true; // hanging
cursor.x = W/2 - 1;
}
NOTIFY_DONE(topics); NOTIFY_DONE(topics);
return ch; return ch;
@ -1891,6 +2257,15 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
bufput_utf8((num)); \ bufput_utf8((num)); \
} while(0) } while(0)
#define bufput_color_utf8(c) do { \
if ((c) < 256) { \
bufput_utf8(c); \
} else { \
bufput_utf8((((c)-256)&0xFFF) | 0x10000); \
bufput_utf8((((c)-256)>>12)&0xFFF); \
} \
} while(0)
// tags for screen serialization // tags for screen serialization
#define SEQ_TAG_SKIP '\x01' #define SEQ_TAG_SKIP '\x01'
#define SEQ_TAG_REPEAT '\x02' #define SEQ_TAG_REPEAT '\x02'
@ -1901,12 +2276,15 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
#define SEQ_TAG_ATTRS_0 '\x07' #define SEQ_TAG_ATTRS_0 '\x07'
#define TOPICMARK_SCREEN_OPTS 'O' #define TOPICMARK_SCREEN_OPTS 'O'
#define TOPICMARK_STATIC_OPTS 'P'
#define TOPICMARK_TITLE 'T' #define TOPICMARK_TITLE 'T'
#define TOPICMARK_BUTTONS 'B' #define TOPICMARK_BUTTONS 'B'
#define TOPICMARK_DEBUG 'D' #define TOPICMARK_DEBUG 'D'
#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'
#define TOPICMARK_DBL_LINE 'H'
if (ss == NULL) { if (ss == NULL) {
// START! // START!
@ -1997,12 +2375,8 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
bufput_utf8(H); bufput_utf8(H);
bufput_utf8(W); bufput_utf8(W);
bufput_utf8(termconf_live.theme); bufput_utf8(termconf_live.theme);
bufput_color_utf8(termconf_live.default_fg);
bufput_utf8(termconf_live.default_fg & 0xFFFF); bufput_color_utf8(termconf_live.default_bg);
bufput_utf8((termconf_live.default_fg >> 16) & 0xFFFF);
bufput_utf8(termconf_live.default_bg & 0xFFFF);
bufput_utf8((termconf_live.default_bg >> 16) & 0xFFFF);
bufput_utf8( bufput_utf8(
(scr.cursor_visible << 0) | (scr.cursor_visible << 0) |
@ -2021,28 +2395,79 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
); );
END_TOPIC END_TOPIC
BEGIN_TOPIC(TOPIC_CHANGE_STATIC_OPTS, 110)
bufput_c(TOPICMARK_STATIC_OPTS);
size_t len = strlen(termconf_live.font_stack);
if (len > 0) {
memcpy(bb, termconf_live.font_stack, len);
bb += len;
remain -= len;
}
bufput_c('\x01');
bufput_utf8(termconf_live.font_size);
END_TOPIC
BEGIN_TOPIC(TOPIC_DOUBLE_LINES, 70)
bufput_c(TOPICMARK_DBL_LINE);
int cnt = 0;
for (int i = 0; i < LINE_ATTRS_COUNT; i++) {
if (scr.line_attribs[i] != 0) cnt++;
}
bufput_utf8(cnt);
for (int i = 0; i < LINE_ATTRS_COUNT; i++) {
if (scr.line_attribs[i] != 0) {
bufput_utf8((i << 3) | (scr.line_attribs[i]&0b111));
}
}
END_TOPIC
BEGIN_TOPIC(TOPIC_CHANGE_TITLE, TERM_TITLE_LEN+4+1) BEGIN_TOPIC(TOPIC_CHANGE_TITLE, TERM_TITLE_LEN+4+1)
bufput_c(TOPICMARK_TITLE); bufput_c(TOPICMARK_TITLE);
int len = (int) strlen(termconf_live.title); size_t len = 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
BEGIN_TOPIC(TOPIC_CHANGE_BUTTONS, (TERM_BTN_LEN+4)*TERM_BTN_COUNT+1+4) BEGIN_TOPIC(TOPIC_CHANGE_BUTTONS, (TERM_BTN_LEN+4)*termconf_live.button_count+1+4)
bufput_c(TOPICMARK_BUTTONS); bufput_c(TOPICMARK_BUTTONS);
bufput_utf8(TERM_BTN_COUNT); bufput_utf8(termconf_live.button_count);
u32 *cp = &termconf_live.bc1;
for (int i = 0; i < termconf_live.button_count; i++) {
uint32_t c = *cp++;
bufput_color_utf8(c);
size_t len = strlen(TERM_BTN_N(&termconf_live, i));
if (len > 0) {
memcpy(bb, TERM_BTN_N(&termconf_live, i), len);
bb += len;
remain -= len;
}
bufput_c('\x01');
}
END_TOPIC
BEGIN_TOPIC(TOPIC_CHANGE_BACKDROP, TERM_BACKDROP_LEN+1+1)
bufput_c(TOPICMARK_BACKDROP);
for (int i = 0; i < TERM_BTN_COUNT; i++) { size_t len = strlen(termconf_live.backdrop);
int len = (int) strlen(termconf_live.btn[i]); if (len > 0) {
memcpy(bb, termconf_live.btn[i], len); memcpy(bb, termconf_live.backdrop, len);
bb += len; bb += len;
remain -= len; remain -= len;
bufput_c('\x01');
} }
bufput_c('\x01');
END_TOPIC END_TOPIC
BEGIN_TOPIC(TOPIC_INTERNAL, 45) BEGIN_TOPIC(TOPIC_INTERNAL, 45)
@ -2063,6 +2488,8 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
bufput_utf8(cursor.charsetN); bufput_utf8(cursor.charsetN);
bufput_c(cursor.charset0); bufput_c(cursor.charset0);
bufput_c(cursor.charset1); bufput_c(cursor.charset1);
bufput_color_utf8(cursor.fg);
bufput_color_utf8(cursor.bg);
bufput_utf8(system_get_free_heap_size()); bufput_utf8(system_get_free_heap_size());
bufput_utf8(term_active_clients); bufput_utf8(term_active_clients);
END_TOPIC END_TOPIC

@ -5,6 +5,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <esp8266.h> #include <esp8266.h>
#include <httpd.h> #include <httpd.h>
#include "config_xmacros.h"
/** /**
* This module handles the virtual screen and operations on it. * This module handles the virtual screen and operations on it.
@ -38,6 +39,8 @@
#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 TERM_FONTSTACK_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
@ -68,38 +71,79 @@ enum CursorShape {
#define SCR_DEF_DEBUGBAR 0 #define SCR_DEF_DEBUGBAR 0
#define SCR_DEF_DECOPT12 0 #define SCR_DEF_DECOPT12 0
#define SCR_DEF_ASCIIDEBUG 0 #define SCR_DEF_ASCIIDEBUG 0
#define SCR_DEF_BUTTON_COUNT 5
// --- Persistent Settings --- // --- Persistent Settings ---
#define CURSOR_BLINKS(shape) ((shape)==CURSOR_BLOCK_BL||(shape)==CURSOR_UNDERLINE_BL||(shape)==CURSOR_BAR_BL) #define CURSOR_BLINKS(shape) ((shape)==CURSOR_BLOCK_BL||(shape)==CURSOR_UNDERLINE_BL||(shape)==CURSOR_BAR_BL)
// 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 500
#define TERMCONF_VERSION 3 #define TERMCONF_VERSION 6
//....Type................Name..Suffix...............Deref..XGET.........Cast..XSET...........NOTIFY..Allow
// Deref is used to pass the field to xget. Cast is used to convert the &'d field to what xset wants (needed for static arrays)
#define XTABLE_TERMCONF \
X(u32, width, /**/, /**/, xget_dec, xset_u32, NULL, /**/, 1) \
X(u32, height, /**/, /**/, xget_dec, xset_u32, NULL, /**/, 1) \
X(u32, default_bg, /**/, /**/, xget_term_color, xset_term_color, NULL, /**/, 1) \
X(u32, default_fg, /**/, /**/, xget_term_color, xset_term_color, NULL, /**/, 1) \
X(char, title, [TERM_TITLE_LEN], /**/, xget_string, xset_string, TERM_TITLE_LEN, /**/, 1) \
X(char, btn1, [TERM_BTN_LEN], /**/, xget_string, xset_string, TERM_BTN_LEN, /**/, 1) \
X(char, btn2, [TERM_BTN_LEN], /**/, xget_string, xset_string, TERM_BTN_LEN, /**/, 1) \
X(char, btn3, [TERM_BTN_LEN], /**/, xget_string, xset_string, TERM_BTN_LEN, /**/, 1) \
X(char, btn4, [TERM_BTN_LEN], /**/, xget_string, xset_string, TERM_BTN_LEN, /**/, 1) \
X(char, btn5, [TERM_BTN_LEN], /**/, xget_string, xset_string, TERM_BTN_LEN, /**/, 1) \
X(u8, theme, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1) \
X(u32, parser_tout_ms, /**/, /**/, xget_dec, xset_u32, NULL, /**/, 1) \
X(u32, display_tout_ms, /**/, /**/, xget_dec, xset_u32, NULL, /**/, 1) \
X(bool, fn_alt_mode, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(u8, config_version, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1) \
X(u32, display_cooldown_ms, /**/, /**/, xget_dec, xset_u32, NULL, /**/, 1) \
X(bool, loopback, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(bool, show_buttons, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(bool, show_config_links, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(char, bm1, [TERM_BTN_MSG_LEN], /**/, xget_term_bm, xset_term_bm, NULL, /**/, 1) \
X(char, bm2, [TERM_BTN_MSG_LEN], /**/, xget_term_bm, xset_term_bm, NULL, /**/, 1) \
X(char, bm3, [TERM_BTN_MSG_LEN], /**/, xget_term_bm, xset_term_bm, NULL, /**/, 1) \
X(char, bm4, [TERM_BTN_MSG_LEN], /**/, xget_term_bm, xset_term_bm, NULL, /**/, 1) \
X(char, bm5, [TERM_BTN_MSG_LEN], /**/, xget_term_bm, xset_term_bm, NULL, /**/, 1) \
X(u32, cursor_shape, /**/, /**/, xget_dec, xset_term_cursorshape, NULL, /**/, 1) \
X(bool, crlf_mode, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(bool, want_all_fn, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(bool, debugbar, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(bool, allow_decopt_12, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(bool, ascii_debug, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(char, backdrop, [TERM_BACKDROP_LEN], /**/, xget_string, xset_string, TERM_BACKDROP_LEN, /**/, 1) \
X(u8, button_count, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1) \
X(u32, bc1, /**/, /**/, xget_term_color, xset_term_color, NULL, /**/, 1) \
X(u32, bc2, /**/, /**/, xget_term_color, xset_term_color, NULL, /**/, 1) \
X(u32, bc3, /**/, /**/, xget_term_color, xset_term_color, NULL, /**/, 1) \
X(u32, bc4, /**/, /**/, xget_term_color, xset_term_color, NULL, /**/, 1) \
X(u32, bc5, /**/, /**/, xget_term_color, xset_term_color, NULL, /**/, 1) \
X(char, font_stack, [TERM_FONTSTACK_LEN], /**/, xget_string, xset_string, TERM_FONTSTACK_LEN, /**/, 1) \
X(u8, font_size, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1)
/** Get button N message from the passed config structure pointer */
#define TERM_BM_N(tc, n) ((tc)->bm1+(TERM_BTN_MSG_LEN*(n)))
/** Get button N text from the passed config structure pointer */
#define TERM_BTN_N(tc, n) ((tc)->btn1+(TERM_BTN_LEN*(n)))
/** Export color for config */
void xget_term_color(char *buff, u32 value);
/** Export button message as stirng for config */
void xget_term_bm(char *buff, char *value);
/** Set button message */
enum xset_result xset_term_bm(const char *name, char *field, const char *buff, const void *arg);
/** Set color */
enum xset_result xset_term_color(const char *name, u32 *field, const char *buff, const void *arg);
/** Set cursor shape */
enum xset_result xset_term_cursorshape(const char *name, u32 *field, const char *buff, const void *arg);
typedef struct { typedef struct {
u32 width; #define X XSTRUCT_FIELD
u32 height; XTABLE_TERMCONF
u32 default_bg; // 00-FFh - ANSI colors, (00:00:00-FF:FF:FF)+256 - True Color #undef X
u32 default_fg;
char title[TERM_TITLE_LEN];
char btn[TERM_BTN_COUNT][TERM_BTN_LEN];
u8 theme;
u32 parser_tout_ms;
u32 display_tout_ms;
bool fn_alt_mode; // xterm compatibility mode (alternate codes for some FN keys)
u8 config_version;
u32 display_cooldown_ms;
bool loopback;
bool show_buttons;
bool show_config_links;
char btn_msg[TERM_BTN_COUNT][TERM_BTN_MSG_LEN];
enum CursorShape cursor_shape;
bool crlf_mode;
bool want_all_fn;
bool debugbar;
bool allow_decopt_12;
bool ascii_debug;
} TerminalConfigBundle; } TerminalConfigBundle;
// Live config // Live config
@ -148,6 +192,11 @@ 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);
void screen_set_button_message(int num, const char *msg);
void screen_set_button_color(int num, const char *buf);
void screen_set_button_count(int count);
/** Change backdrop */
void screen_set_backdrop(const char *url);
// --- Encoding --- // --- Encoding ---
@ -169,15 +218,21 @@ 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_CHANGE_STATIC_OPTS = (1<<9),
TOPIC_DOUBLE_LINES = (1<<10),
TOPIC_FLAG_NOCLEAN = (1<<15), // do not clean dirty extents TOPIC_FLAG_NOCLEAN = (1<<15), // do not clean dirty extents
// combos // combos
TOPIC_INITIAL = TOPIC_INITIAL =
TOPIC_CHANGE_SCREEN_OPTS | TOPIC_CHANGE_SCREEN_OPTS |
TOPIC_CHANGE_STATIC_OPTS |
TOPIC_CHANGE_CONTENT_ALL | TOPIC_CHANGE_CONTENT_ALL |
TOPIC_CHANGE_CURSOR | TOPIC_CHANGE_CURSOR |
TOPIC_CHANGE_TITLE | TOPIC_CHANGE_TITLE |
TOPIC_CHANGE_BUTTONS, TOPIC_CHANGE_BACKDROP |
TOPIC_CHANGE_BUTTONS |
TOPIC_DOUBLE_LINES,
}; };
typedef u16 ScreenNotifyTopics; typedef u16 ScreenNotifyTopics;
@ -329,6 +384,9 @@ void screen_tab_reverse(int count);
/** Move left, shift right if at the boundary */ /** Move left, shift right if at the boundary */
void screen_back_index(int count); void screen_back_index(int count);
/** Set line attribs; 0-no change, 1,2 - single,double */
void screen_set_line_attr(uint8_t double_w, uint8_t double_h_top, uint8_t double_h_bot);
// --- Printing characters --- // --- Printing characters ---
/** /**

@ -11,10 +11,12 @@ static ETSTimer flushLogTimer;
static void buf_putc(char c) static void buf_putc(char c)
{ {
if (lb_ls != lb_nw) { if (sysconf->gpio2_conf == GPIOCONF_OFF) {
logbuf[lb_nw++] = c; if (lb_ls != lb_nw) {
if (lb_nw >= DEBUG_LOGBUF_SIZE) lb_nw = 0; logbuf[lb_nw++] = c;
} if (lb_nw >= DEBUG_LOGBUF_SIZE) lb_nw = 0;
}
}
} }
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
@ -42,7 +44,9 @@ buf_pop(void *unused)
LOCAL void my_putc(char c) LOCAL void my_putc(char c)
{ {
UART_WriteCharCRLF(UART1, (u8) c, 10); if (sysconf->gpio2_conf == GPIOCONF_OFF) {
UART_WriteCharCRLF(UART1, (u8) c, 200);
}
} }
/** /**
@ -79,6 +83,14 @@ void ICACHE_FLASH_ATTR serialInit(void)
UART_SetStopBits(UART0, (UartStopBitsNum) sysconf->uart_stopbits); UART_SetStopBits(UART0, (UartStopBitsNum) sysconf->uart_stopbits);
UART_SetBaudrate(UART0, sysconf->uart_baudrate); UART_SetBaudrate(UART0, sysconf->uart_baudrate);
// GPIO2 may be used as a remotely controlled GPIO
if (sysconf->gpio2_conf == GPIOCONF_OFF) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
} else {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2);
// further config of the GPIO will be done in persist.c after calling this func
}
info("COM SERIAL: %d baud, %s parity, %s stopbit(s)", info("COM SERIAL: %d baud, %s parity, %s stopbit(s)",
sysconf->uart_baudrate, sysconf->uart_baudrate,
(sysconf->uart_parity == PARITY_NONE ? "NONE" : (sysconf->uart_parity == PARITY_ODD ? "ODD" : "EVEN")), (sysconf->uart_parity == PARITY_NONE ? "NONE" : (sysconf->uart_parity == PARITY_ODD ? "ODD" : "EVEN")),

@ -6,12 +6,113 @@
#include "persist.h" #include "persist.h"
#include "uart_driver.h" #include "uart_driver.h"
#include "serial.h" #include "serial.h"
#include "cgi_logging.h"
#include "io.h"
SystemConfigBundle * const sysconf = &persist.current.sysconf; SystemConfigBundle * const sysconf = &persist.current.sysconf;
enum xset_result ICACHE_FLASH_ATTR
xset_sys_baudrate(const char *name, u32 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, 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) {
if (*field != baud) {
*field = (u32) baud;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad baud rate %s", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_sys_parity(const char *name, u8 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
int parity = atoi(buff);
if (parity >= 0 && parity <= 2) {
if (*field != parity) {
*field = (UartParityMode) parity;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad parity %s", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_sys_stopbits(const char *name, u8 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
int stopbits = atoi(buff);
if (stopbits >= 1 && stopbits <= 3) {
if (*field != stopbits) {
*field = (UartParityMode) stopbits;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad stopbits %s", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_sys_pwlock(const char *name, u8 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
int pwlock = atoi(buff);
if (pwlock >= 0 && pwlock < PWLOCK_MAX) {
if (*field != pwlock) {
*field = (enum pwlock) pwlock;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad pwlock %s", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_sys_accesspw(const char *name, uchar *field, const char *buff, const void *arg)
{
// Do not overwrite pw if empty
if (strlen(buff) == 0) return XSET_UNCHANGED;
return xset_ustring(name, field, buff, arg);
}
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
sysconf_apply_settings(void) sysconf_apply_settings(void)
{ {
// char buff[64];
//#define XSTRUCT sysconf
//#define X XDUMP_FIELD
// XTABLE_SYSCONF
//#undef X
bool changed = false; bool changed = false;
if (sysconf->config_version < 1) { if (sysconf->config_version < 1) {
dbg("Upgrading syscfg to v 1"); dbg("Upgrading syscfg to v 1");
@ -19,6 +120,14 @@ sysconf_apply_settings(void)
changed = true; changed = true;
} }
if (sysconf->config_version < 2) {
dbg("Upgrading syscfg to v 2");
sysconf->gpio2_conf = GPIOCONF_OFF;
sysconf->gpio4_conf = GPIOCONF_OUT_START_0;
sysconf->gpio5_conf = GPIOCONF_OUT_START_0;
changed = true;
}
sysconf->config_version = SYSCONF_VERSION; sysconf->config_version = SYSCONF_VERSION;
if (changed) { if (changed) {
@ -28,6 +137,9 @@ sysconf_apply_settings(void)
// uart settings live here, but the CGI handler + form has been moved to the Terminal config page // uart settings live here, but the CGI handler + form has been moved to the Terminal config page
serialInit(); serialInit();
// initialize user GPIOs
userGpioInit();
system_update_cpu_freq((uint8) (sysconf->overclock ? 160 : 80)); system_update_cpu_freq((uint8) (sysconf->overclock ? 160 : 80));
} }
@ -40,7 +152,10 @@ sysconf_restore_defaults(void)
sysconf->config_version = SYSCONF_VERSION; sysconf->config_version = SYSCONF_VERSION;
sysconf->access_pw[0] = 0; sysconf->access_pw[0] = 0;
sysconf->pwlock = PWLOCK_NONE; sysconf->pwlock = PWLOCK_NONE;
strcpy(sysconf->access_pw, DEF_ACCESS_PW); strcpy((char *)sysconf->access_pw, DEF_ACCESS_PW);
strcpy(sysconf->access_name, DEF_ACCESS_NAME); strcpy((char *)sysconf->access_name, DEF_ACCESS_NAME);
sysconf->overclock = false; sysconf->overclock = false;
sysconf->gpio2_conf = GPIOCONF_OFF; // means 'use debug uart'
sysconf->gpio4_conf = GPIOCONF_OUT_START_0;
sysconf->gpio5_conf = GPIOCONF_OUT_START_0;
} }

@ -6,11 +6,12 @@
#define ESP_VT100_FIRMWARE_SYSCFG_H #define ESP_VT100_FIRMWARE_SYSCFG_H
#include <esp8266.h> #include <esp8266.h>
#include "config_xmacros.h"
// Size designed for the wifi config structure // Size designed for the wifi config structure
// Must be constant to avoid corrupting user config after upgrade // Must be constant to avoid corrupting user config after upgrade
#define SYSCONF_SIZE 300 #define SYSCONF_SIZE 300
#define SYSCONF_VERSION 1 #define SYSCONF_VERSION 2
#define DEF_ACCESS_PW "1234" #define DEF_ACCESS_PW "1234"
#define DEF_ACCESS_NAME "espterm" #define DEF_ACCESS_NAME "espterm"
@ -24,15 +25,46 @@ enum pwlock {
PWLOCK_MAX = 5, PWLOCK_MAX = 5,
}; };
enum gpioconf {
GPIOCONF_OFF = 0,
GPIOCONF_OUT_START_0 = 1,
GPIOCONF_OUT_START_1 = 2,
GPIOCONF_IN_PULL = 3,
GPIOCONF_IN_NOPULL = 4,
};
//....Type................Name..Suffix...............Deref..XGET............XSET.........................NOTIFY....Allow
// Deref is used to pass the field to xget. Cast is used to convert the &'d field to what xset wants (needed for static arrays)
#define XTABLE_SYSCONF \
X(u32, uart_baudrate, /**/, /**/, xget_dec, xset_sys_baudrate, NULL, uart_changed=true, 1) \
X(u8, uart_parity, /**/, /**/, xget_dec, xset_sys_parity, NULL, uart_changed=true, 1) \
X(u8, uart_stopbits, /**/, /**/, xget_dec, xset_sys_stopbits, NULL, uart_changed=true, 1) \
\
X(u8, config_version, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1) \
\
X(u8, pwlock, /**/, /**/, xget_dec, xset_sys_pwlock, NULL, /**/, admin|tpl) \
X(uchar, access_pw, [64], /**/, xget_ustring, xset_sys_accesspw, NULL, /**/, admin) \
X(uchar, access_name, [32], /**/, xget_ustring, xset_ustring, NULL, /**/, admin|tpl) \
\
X(bool, overclock, /**/, /**/, xget_bool, xset_bool, NULL, /**/, 1) \
X(u8, gpio2_conf, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1) \
X(u8, gpio4_conf, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1) \
X(u8, gpio5_conf, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1)
typedef struct { typedef struct {
u32 uart_baudrate; #define X XSTRUCT_FIELD
u8 uart_parity; XTABLE_SYSCONF
u8 uart_stopbits; #undef X
u8 config_version;
enum pwlock pwlock : 8; // page access lock // u32 uart_baudrate;
char access_pw[64]; // access password // u8 uart_parity;
char access_name[32]; // access name // u8 uart_stopbits;
bool overclock; // u8 config_version;
// enum pwlock pwlock : 8; // page access lock
// char access_pw[64]; // access password
// char access_name[32]; // access name
// bool overclock;
// bool debug_uart;
} SystemConfigBundle; } SystemConfigBundle;
extern SystemConfigBundle * const sysconf; extern SystemConfigBundle * const sysconf;
@ -41,4 +73,10 @@ void sysconf_apply_settings(void);
void sysconf_restore_defaults(void); void sysconf_restore_defaults(void);
enum xset_result xset_sys_baudrate(const char *name, u32 *field, const char *buff, const void *arg);
enum xset_result xset_sys_parity(const char *name, u8 *field, const char *buff, const void *arg);
enum xset_result xset_sys_stopbits(const char *name, u8 *field, const char *buff, const void *arg);
enum xset_result xset_sys_pwlock(const char *name, u8 *field, const char *buff, const void *arg);
enum xset_result xset_sys_accesspw(const char *name, uchar *field, const char *buff, const void *arg);
#endif //ESP_VT100_FIRMWARE_SYSCFG_H #endif //ESP_VT100_FIRMWARE_SYSCFG_H

@ -56,6 +56,7 @@ void ICACHE_FLASH_ATTR UART_Init(void)
// U1TXD (GPIO2) // U1TXD (GPIO2)
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK); PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
PIN_PULLUP_DIS(PERIPHS_IO_MUX_GPIO2_U);
// Configure the UART peripherals // Configure the UART peripherals
UART_SetWordLength(UART0, EIGHT_BITS); // main UART_SetWordLength(UART0, EIGHT_BITS); // main

@ -96,6 +96,8 @@ static ETSTimer prHeapTimer;
//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done. //Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done.
void ICACHE_FLASH_ATTR user_init(void) void ICACHE_FLASH_ATTR user_init(void)
{ {
sysconf->gpio2_conf = GPIOCONF_OFF; // enable debug UART for the start-up (sysconf is not loaded yet)
ansi_parser_inhibit = true; ansi_parser_inhibit = true;
serialInitBase(); serialInitBase();
@ -110,7 +112,7 @@ void ICACHE_FLASH_ATTR user_init(void)
banner_gap(); banner_gap();
banner("================ ESPTerm ================"); banner("================ ESPTerm ================");
banner_info(); banner_info();
banner_info("Project by Ondrej Hruska, 2017"); banner_info("Project by Ondrej Hruska, 2017-2018");
banner_info(); banner_info();
banner_info(TERMINAL_GITHUB_REPO_NOPROTO); banner_info(TERMINAL_GITHUB_REPO_NOPROTO);
banner_info(); banner_info();
@ -150,6 +152,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);

@ -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 4
#define FW_V_PATCH 0 #define FW_V_PATCH 0
#define FW_V_SUFFIX "" #define FW_V_SUFFIX "-pre2"
#define FW_CODENAME "Anthill" // 2.1.0 #define FW_CODENAME "Damselfly"
#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

@ -4,10 +4,127 @@
#include "wifimgr.h" #include "wifimgr.h"
#include "persist.h" #include "persist.h"
#include "cgi_logging.h"
#include "config_xmacros.h"
WiFiConfigBundle * const wificonf = &persist.current.wificonf; WiFiConfigBundle * const wificonf = &persist.current.wificonf;
WiFiConfChangeFlags wifi_change_flags; WiFiConfChangeFlags wifi_change_flags;
enum xset_result ICACHE_FLASH_ATTR
xset_wifi_lease_time(const char *name, u16 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s min", name, buff);
int min = atoi(buff);
if (min >= 1 && min <= 2880) {
if (*field != min) {
*field = (u16) min;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Lease time %s out of allowed range 1-2880.", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_wifi_opmode(const char *name, u8 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
int mode = atoi(buff);
if (mode > NULL_MODE && mode < MAX_MODE) {
if (*field != mode) {
*field = (WIFI_MODE) mode;
return XSET_SET;
}
return XSET_UNCHANGED; // opmode does not use flags
} else {
cgi_warn("Bad opmode value \"%s\"", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_wifi_tpw(const char *name, u8 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
int tpw = atoi(buff);
if (tpw >= 0 && tpw <= 82) { // 0 actually isn't 0 but quite low. 82 is very strong
if (*field != tpw) {
*field = (u8) tpw;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("tpw %s out of allowed range 0-82.", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_wifi_ap_channel(const char *name, u8 *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
int channel = atoi(buff);
if (channel > 0 && channel < 15) {
if (*field != channel) {
*field = (u8) channel;
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad channel value \"%s\", allowed 1-14", buff);
return XSET_FAIL;
}
}
enum xset_result ICACHE_FLASH_ATTR
xset_wifi_ssid(const char *name, uchar *field, const char *buff, const void *arg)
{
u8 buff2[SSID_LEN];
bool want_subs = arg!=0;
int i;
for (i = 0; i < SSID_LEN; i++) {
char c = buff[i];
if (c == 0) break;
if (want_subs && (c < 32 || c >= 127)) c = '_';
buff2[i] = (u8) c;
}
buff2[i] = 0;
cgi_dbg("Setting %s = %s", name, buff);
if (strlen((char *)buff2) > 0) {
if (!streq(field, buff2)) {
strncpy_safe(field, buff2, SSID_LEN);
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad SSID len.");
return XSET_FAIL;
}
}
/** Set PW - allow len 0 or 8-64 */
enum xset_result ICACHE_FLASH_ATTR
xset_wifi_pwd(const char *name, uchar *field, const char *buff, const void *arg)
{
cgi_dbg("Setting %s = %s", name, buff);
if (strlen(buff) == 0 || (strlen(buff) >= 8 && strlen(buff) < PASSWORD_LEN-1)) {
if (!streq(field, buff)) {
strncpy_safe(field, buff, PASSWORD_LEN);
return XSET_SET;
}
return XSET_UNCHANGED;
} else {
cgi_warn("Bad password len.");
return XSET_FAIL;
}
}
int ICACHE_FLASH_ATTR getStaIpAsString(char *buffer) int ICACHE_FLASH_ATTR getStaIpAsString(char *buffer)
{ {
WIFI_MODE x = wifi_get_opmode(); WIFI_MODE x = wifi_get_opmode();
@ -42,13 +159,11 @@ wifimgr_restore_defaults(void)
wificonf->ap_password[0] = 0; // PSK2 always if password is not null. wificonf->ap_password[0] = 0; // PSK2 always if password is not null.
wificonf->ap_hidden = false; wificonf->ap_hidden = false;
IP4_ADDR(&wificonf->ap_addr.ip, 192, 168, 4, 1); IP4_ADDR(&wificonf->ap_addr_ip, 192, 168, 4, 1);
IP4_ADDR(&wificonf->ap_addr.netmask, 255, 255, 255, 0); IP4_ADDR(&wificonf->ap_addr_mask, 255, 255, 255, 0);
wificonf->ap_addr.gw.addr = wificonf->ap_addr.gw.addr;
IP4_ADDR(&wificonf->ap_dhcp_range.start_ip, 192, 168, 4, 100); IP4_ADDR(&wificonf->ap_dhcp_start, 192, 168, 4, 100);
IP4_ADDR(&wificonf->ap_dhcp_range.end_ip, 192, 168, 4, 200); IP4_ADDR(&wificonf->ap_dhcp_end, 192, 168, 4, 200);
wificonf->ap_dhcp_range.enable = 1; // this will never get changed, idk why it's even there
wificonf->ap_dhcp_time = 120; wificonf->ap_dhcp_time = 120;
// --- Client config --- // --- Client config ---
@ -56,9 +171,9 @@ wifimgr_restore_defaults(void)
wificonf->sta_password[0] = 0; wificonf->sta_password[0] = 0;
wificonf->sta_dhcp_enable = true; wificonf->sta_dhcp_enable = true;
IP4_ADDR(&wificonf->sta_addr.ip, 192, 168, 0, (mac[5] == 1 ? 2 : mac[5])); // avoid being the same as "default gw" IP4_ADDR(&wificonf->sta_addr_ip, 192, 168, 0, (mac[5] == 1 ? 2 : mac[5])); // avoid being the same as "default gw"
IP4_ADDR(&wificonf->sta_addr.netmask, 255, 255, 255, 0); IP4_ADDR(&wificonf->sta_addr_mask, 255, 255, 255, 0);
IP4_ADDR(&wificonf->sta_addr.gw, 192, 168, 0, 1); IP4_ADDR(&wificonf->sta_addr_gw, 192, 168, 0, 1); // a common default...
} }
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
@ -83,13 +198,18 @@ configure_station(void)
} }
else { else {
wifi_info("[WiFi] Setting up static IP..."); wifi_info("[WiFi] Setting up static IP...");
wifi_dbg("[WiFi] Client.ip = "IPSTR, GOOD_IP2STR(wificonf->sta_addr.ip.addr)); wifi_dbg("[WiFi] Client.ip = "IPSTR, GOOD_IP2STR(wificonf->sta_addr_ip.addr));
wifi_dbg("[WiFi] Client.mask = "IPSTR, GOOD_IP2STR(wificonf->sta_addr.netmask.addr)); wifi_dbg("[WiFi] Client.mask = "IPSTR, GOOD_IP2STR(wificonf->sta_addr_mask.addr));
wifi_dbg("[WiFi] Client.gw = "IPSTR, GOOD_IP2STR(wificonf->sta_addr.gw.addr)); wifi_dbg("[WiFi] Client.gw = "IPSTR, GOOD_IP2STR(wificonf->sta_addr_gw.addr));
wifi_station_dhcpc_stop(); wifi_station_dhcpc_stop();
// Load static IP config // Load static IP config
if (!wifi_set_ip_info(STATION_IF, &wificonf->sta_addr)) { struct ip_info ipstruct;
ipstruct.ip.addr = wificonf->sta_addr_ip.addr;
ipstruct.netmask.addr = wificonf->sta_addr_mask.addr;
ipstruct.gw.addr = wificonf->sta_addr_gw.addr;
if (!wifi_set_ip_info(STATION_IF, &ipstruct)) {
error("[WiFi] Error setting static IP!"); error("[WiFi] Error setting static IP!");
return; return;
} }
@ -112,7 +232,7 @@ configure_ap(void)
strcpy((char *) conf.password, (char *) wificonf->ap_password); strcpy((char *) conf.password, (char *) wificonf->ap_password);
conf.authmode = (wificonf->ap_password[0] == 0 ? AUTH_OPEN : AUTH_WPA2_PSK); conf.authmode = (wificonf->ap_password[0] == 0 ? AUTH_OPEN : AUTH_WPA2_PSK);
conf.ssid_len = (uint8_t) strlen((char *) conf.ssid); conf.ssid_len = (uint8_t) strlen((char *) conf.ssid);
conf.ssid_hidden = wificonf->ap_hidden; conf.ssid_hidden = (uint8) wificonf->ap_hidden;
conf.max_connection = 4; // default 4 (max possible) conf.max_connection = 4; // default 4 (max possible)
conf.beacon_interval = 100; // default 100 ms conf.beacon_interval = 100; // default 100 ms
@ -127,24 +247,32 @@ configure_ap(void)
// Set IP // Set IP
wifi_info("[WiFi] Configuring SoftAP local IP..."); wifi_info("[WiFi] Configuring SoftAP local IP...");
wifi_dbg("[WiFi] SoftAP.ip = "IPSTR, GOOD_IP2STR(wificonf->ap_addr.ip.addr)); wifi_dbg("[WiFi] SoftAP.ip = "IPSTR, GOOD_IP2STR(wificonf->ap_addr_ip.addr));
wifi_dbg("[WiFi] SoftAP.mask = "IPSTR, GOOD_IP2STR(wificonf->ap_addr.netmask.addr)); wifi_dbg("[WiFi] SoftAP.mask = "IPSTR, GOOD_IP2STR(wificonf->ap_addr_mask.addr));
wifi_dbg("[WiFi] SoftAP.gw = "IPSTR, GOOD_IP2STR(wificonf->ap_addr.gw.addr));
wifi_softap_dhcps_stop(); wifi_softap_dhcps_stop();
// Configure DHCP // Configure DHCP
if (!wifi_set_ip_info(SOFTAP_IF, &wificonf->ap_addr)) { struct ip_info ipstruct;
ipstruct.ip.addr = wificonf->ap_addr_ip.addr;
ipstruct.netmask.addr = wificonf->ap_addr_mask.addr;
ipstruct.gw.addr = wificonf->ap_addr_ip.addr;
if (!wifi_set_ip_info(SOFTAP_IF, &ipstruct)) {
error("[WiFi] IP set fail!"); error("[WiFi] IP set fail!");
return; return;
} }
wifi_info("[WiFi] Configuring SoftAP DHCP server..."); wifi_info("[WiFi] Configuring SoftAP DHCP server...");
wifi_dbg("[WiFi] DHCP.start = "IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.start_ip.addr)); wifi_dbg("[WiFi] DHCP.start = "IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_start.addr));
wifi_dbg("[WiFi] DHCP.end = "IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.end_ip.addr)); wifi_dbg("[WiFi] DHCP.end = "IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_end.addr));
wifi_dbg("[WiFi] DHCP.lease = %d minutes", wificonf->ap_dhcp_time); wifi_dbg("[WiFi] DHCP.lease = %d minutes", wificonf->ap_dhcp_time);
if (!wifi_softap_set_dhcps_lease(&wificonf->ap_dhcp_range)) { struct dhcps_lease dhcpstruct;
dhcpstruct.start_ip = wificonf->ap_dhcp_start;
dhcpstruct.end_ip = wificonf->ap_dhcp_end;
dhcpstruct.enable = 1; // ???
if (!wifi_softap_set_dhcps_lease(&dhcpstruct)) {
error("[WiFi] DHCP address range set fail!"); error("[WiFi] DHCP address range set fail!");
return; return;
} }
@ -164,6 +292,21 @@ configure_ap(void)
} }
} }
static ETSTimer tim;
static void ICACHE_FLASH_ATTR
wifimgr_apply_settings_later_Cb(void *unused)
{
wifimgr_apply_settings();
}
void ICACHE_FLASH_ATTR
wifimgr_apply_settings_later(uint32_t delay_ms)
{
wifi_info("[WiFi] Scheduling settings apply in %d ms", delay_ms);
TIMER_START(&tim, wifimgr_apply_settings_later_Cb, delay_ms, 0);
}
/** /**
* Register the WiFi event listener, cycle WiFi, apply settings * Register the WiFi event listener, cycle WiFi, apply settings
*/ */
@ -172,6 +315,12 @@ wifimgr_apply_settings(void)
{ {
wifi_info("[WiFi] Initializing..."); wifi_info("[WiFi] Initializing...");
// char buff[64];
//#define XSTRUCT wificonf
//#define X XDUMP_FIELD
// XTABLE_WIFICONF
//#undef X
// !!! Update to current version !!! // !!! Update to current version !!!
// Force wifi cycle // Force wifi cycle
@ -190,7 +339,7 @@ wifimgr_apply_settings(void)
} }
if (opmode != wificonf->opmode) { if (opmode != wificonf->opmode) {
wifi_set_opmode_current(wificonf->opmode); wifi_set_opmode_current((WIFI_MODE) wificonf->opmode);
} }
// Configure the client // Configure the client

@ -8,6 +8,7 @@
#define ESP_VT100_FIRMWARE_WIFI_MANAGER_H #define ESP_VT100_FIRMWARE_WIFI_MANAGER_H
#include <esp8266.h> #include <esp8266.h>
#include "config_xmacros.h"
#include "cgi_wifi.h" #include "cgi_wifi.h"
#define SSID_LEN 32 #define SSID_LEN 32
@ -19,6 +20,44 @@
#define WIFICONF_VERSION 0 #define WIFICONF_VERSION 0
#define wifimgr_notify_ap() { wifi_change_flags.ap = true; }
#define wifimgr_notify_sta() { wifi_change_flags.ap = true; }
//....Type................Name..Suffix...............Deref..XGET...........XSET.........................NOTIFY................Allow
// Deref is used to pass the field to xget. Cast is used to convert the &'d field to what xset wants (needed for static arrays)
#define XTABLE_WIFICONF \
X(u8, opmode, /**/, /**/, xget_dec, xset_wifi_opmode, NULL, /**/, 1) \
\
X(u8, tpw, /**/, /**/, xget_dec, xset_wifi_tpw, NULL, wifimgr_notify_ap(), 1) \
X(u8, ap_channel, /**/, /**/, xget_dec, xset_wifi_ap_channel, NULL, wifimgr_notify_ap(), 1) \
X(uchar, ap_ssid, [SSID_LEN], /**/, xget_ustring, xset_wifi_ssid, 1, wifimgr_notify_ap(), 1) \
X(uchar, ap_password, [PASSWORD_LEN], /**/, xget_ustring, xset_wifi_pwd, NULL, wifimgr_notify_ap(), 1) \
X(bool, ap_hidden, /**/, /**/, xget_bool, xset_bool, NULL, wifimgr_notify_ap(), 1) \
\
X(u16, ap_dhcp_time, /**/, /**/, xget_dec, xset_wifi_lease_time, NULL, wifimgr_notify_ap(), 1) \
X(u32, unused1, /**/, /**/, xget_dummy, xset_dummy, NULL, /**/, 0) \
X(struct ip_addr, ap_dhcp_start, /**/, &, xget_ip, xset_ip, NULL, wifimgr_notify_ap(), 1) \
X(struct ip_addr, ap_dhcp_end, /**/, &, xget_ip, xset_ip, NULL, wifimgr_notify_ap(), 1) \
\
X(struct ip_addr, ap_addr_ip, /**/, &, xget_ip, xset_ip, NULL, wifimgr_notify_ap(), 1) \
X(struct ip_addr, ap_addr_mask, /**/, &, xget_ip, xset_ip, NULL, wifimgr_notify_ap(), 1) \
\
\
X(u32, unused2, /**/, /**/, xget_dummy, xset_dummy, NULL, /**/, 0) \
X(uchar, sta_ssid, [SSID_LEN], /**/, xget_ustring, xset_wifi_ssid, 0, wifimgr_notify_sta(), 1) \
X(uchar, sta_password, [PASSWORD_LEN], /**/, xget_ustring, xset_wifi_pwd, NULL, wifimgr_notify_sta(), 1) \
X(bool, sta_dhcp_enable, /**/, /**/, xget_bool, xset_bool, NULL, wifimgr_notify_sta(), 1) \
\
X(struct ip_addr, sta_addr_ip, /**/, &, xget_ip, xset_ip, NULL, wifimgr_notify_sta(), 1) \
X(struct ip_addr, sta_addr_mask, /**/, &, xget_ip, xset_ip, NULL, wifimgr_notify_sta(), 1) \
X(struct ip_addr, sta_addr_gw, /**/, &, xget_ip, xset_ip, NULL, wifimgr_notify_sta(), 1) \
\
\
X(u8, config_version, /**/, /**/, xget_dec, xset_u8, NULL, /**/, 1)
// unused1 - replaces 'enabled' bit from old dhcps_lease struct
// unused2 - gap after 'ap_gw' which isn't used and doesn't make sense
/** /**
* A structure holding all configured WiFi parameters * A structure holding all configured WiFi parameters
* and the active state. * and the active state.
@ -26,25 +65,9 @@
* This block can be used eg. for WiFi config backup. * This block can be used eg. for WiFi config backup.
*/ */
typedef struct { typedef struct {
WIFI_MODE opmode : 8; #define X XSTRUCT_FIELD
u8 tpw; XTABLE_WIFICONF
#undef X
// AP config
u8 ap_channel;
u8 ap_ssid[SSID_LEN];
u8 ap_password[PASSWORD_LEN];
bool ap_hidden;
//
u16 ap_dhcp_time; // in minutes
struct dhcps_lease ap_dhcp_range;
struct ip_info ap_addr;
// Client config
u8 sta_ssid[SSID_LEN];
u8 sta_password[PASSWORD_LEN];
bool sta_dhcp_enable;
struct ip_info sta_addr;
u8 config_version;
} WiFiConfigBundle; } WiFiConfigBundle;
typedef struct { typedef struct {
@ -59,9 +82,17 @@ extern WiFiConfigBundle * const wificonf;
void wifimgr_restore_defaults(void); void wifimgr_restore_defaults(void);
void wifimgr_apply_settings(void); void wifimgr_apply_settings(void);
void wifimgr_apply_settings_later(uint32_t delay_ms);
int getStaIpAsString(char *buffer); int getStaIpAsString(char *buffer);
enum xset_result xset_wifi_lease_time(const char *name, u16 *field, const char *buff, const void *arg);
enum xset_result xset_wifi_opmode(const char *name, u8 *field, const char *buff, const void *arg);
enum xset_result xset_wifi_tpw(const char *name, u8 *field, const char *buff, const void *arg);
enum xset_result xset_wifi_ap_channel(const char *name, u8 *field, const char *buff, const void *arg);
enum xset_result xset_wifi_ssid(const char *name, uchar *field, const char *buff, const void *arg);
enum xset_result xset_wifi_pwd(const char *name, uchar *field, const char *buff, const void *arg);
#if DEBUG_WIFI #if DEBUG_WIFI
#define wifi_warn warn #define wifi_warn warn
#define wifi_dbg dbg #define wifi_dbg dbg

Loading…
Cancel
Save