Merge branch 'work'

master 2.3.0
Ondřej Hruška 7 years ago
commit 76fbf5d65f
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 3
      .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. 1
      ship.sh
  10. 72
      user/apars_osc.c
  11. 24
      user/apars_short.c
  12. 174
      user/cgi_network.c
  13. 423
      user/cgi_persist.c
  14. 2
      user/cgi_persist.h
  15. 4
      user/cgi_sockets.c
  16. 140
      user/cgi_system.c
  17. 486
      user/cgi_term_cfg.c
  18. 201
      user/cgi_wifi.c
  19. 463
      user/character_sets.h
  20. 144
      user/config_xmacros.c
  21. 97
      user/config_xmacros.h
  22. 526
      user/ini_parser.c
  23. 65
      user/ini_parser.h
  24. 284
      user/ini_parser.rl
  25. 6
      user/routes.c
  26. 597
      user/screen.c
  27. 108
      user/screen.h
  28. 2
      user/serial.c
  29. 104
      user/syscfg.c
  30. 43
      user/syscfg.h
  31. 2
      user/user_main.c
  32. 4
      user/version.h
  33. 193
      user/wifimgr.c
  34. 69
      user/wifimgr.h

3
.gitignore vendored

@ -20,4 +20,5 @@ cmake-build-debug/
.sass-cache
*.map
.gitignore
!.gitignore

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

@ -5,21 +5,24 @@
![Photo][photo-hw]<br>
*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
functionally comparable to eg. gnome-terminal, terminator, konsole, GtkTerm or PuTTY.
ESPTerm **passes most of VTTEST test cases**, making it functionally comparable to eg.
gnome-terminal, terminator, konsole, GtkTerm or PuTTY.
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
implemented to work with ncurses.
mouse support**, provided agetty is made to believe it's Xterm, which shows ESPTerm
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]
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.
It works with ESP-01, ESP-01S, ESP-12 and likely many other modules (I use an ESP-12
on a LoLin NodeMCU board from eBay for development).
It works with ESP-01, ESP-01S, ESP-12 and likely many other modules.
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
@ -48,6 +51,8 @@ It **does not work with**:
- Internet Explorer (any version) - crashes, missing JS features
- Opera Mini - crashes, missing JS and CSS features
- 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
## Main features
@ -58,6 +63,10 @@ It **does not work with**:
- Full UTF-8 support, alternate character sets
- Standard mouse tracking modes
- 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**
- Real-time screen update via WebSocket
- 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)
- Static IP, DHCP, channel selection, power
- 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?
@ -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.
- 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
config, there is also a third, temporary file for changes done via ESC commands.
When you get your settings *just right*, you can store them as defaults, which can then be at any time restored
by holding the BOOT (GPIO0) button. You can do this on the System Settings page. This asks for an "admin password",
which you can define when building the firmware in the `esphttpdconfig.mk` file.
The default password is `19738426`. This password can't presently be changed without re-flashing the firmware.
which can (and should!) be changed. This password can't be easily recovered when forgotten.
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
Developing ESPTerm wasn't an easy task, because the information is scattered across many places and the existing
terminal emulators I originally used for reference (terminator, Konsole) are not implemented correctly in some details.
Developing the terminal emulator was complicated by the information being scattered across many places and the existing
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
implementation that is probably the most complete emulator available, although it's cumbersome to use and its age
really shows in the looks.
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.
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
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
and changes required by the project.
[MightyPork/libesphttpd][httpdlib]. This fork includes various improvements and changes required by the project.
### Installation for development

@ -3,6 +3,10 @@
echo "-- Building parser from Ragel source --"
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_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 = \
-DASYNC_LOG=1 \
-DDEBUG_INI=1 \
-DDEBUG_D2D=0 \
-DDEBUG_ROUTER=0 \
-DDEBUG_CAPTDNS=0 \

@ -1 +1 @@
Subproject commit 4a032ee3b52920bf59ae21b4effc31fdf3cece33
Subproject commit 8b43b1d17182bf27c62b5d38467c69ee98257239

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

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

@ -16,6 +16,45 @@
#include "ansi_parser.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)
* @param buffer - the OSC body (after OSC and before ST)
@ -23,8 +62,9 @@
void ICACHE_FLASH_ATTR
apars_handle_osc(char *buffer)
{
const char *origbuf = buffer;
int n = 0;
char c = 0;
char c;
while ((c = *buffer++) != 0) {
if (c >= '0' && c <= '9') {
n = (n * 10 + (c - '0'));
@ -49,20 +89,46 @@ apars_handle_osc(char *buffer)
buffer[0] = 'G';
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) {
// ESPTerm: action button label
ansi_warn("OSC 8x is deprecated, use 28;x");
screen_set_button_text(n - 80, buffer);
}
else if (n >= 91 && n <= 95) {
// 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 {
ansi_noimpl("OSC %d ; %s ST", n, buffer);
}
}
else {
ansi_warn("BAD OSC: %s", buffer);
ansi_warn("BAD OSC: %s", origbuf);
apars_show_context();
}
}

@ -94,21 +94,33 @@ apars_handle_space_cmd(char c)
void ICACHE_FLASH_ATTR apars_handle_hash_cmd(char 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 '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
screen_set_line_attr(1, 1, 1);
break;
case '6': // Double width
ansi_noimpl("Double Size Line");
screen_set_line_attr(2, 1, 1);
break;
case '8':
screen_fill_with_E();
break;
// development codes - do not use!
case '7':
http_get("http://wtfismyip.com/text", NULL, http_callback_example);
break;
// // development codes - do not use!
// case '7':
// http_get("http://wtfismyip.com/text", NULL, http_callback_example);
// break;
default:
ansi_noimpl("ESC # %c", c);

@ -9,6 +9,7 @@ configuring the network settings
#include "persist.h"
#include "helpers.h"
#include "cgi_logging.h"
#include "config_xmacros.h"
#define SET_REDIR_SUC "/cfg/network"
#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 ----
if (GET_ARG("ap_dhcp_time")) {
cgi_dbg("Setting DHCP lease time to: %s min.", buff);
int min = atoi(buff);
if (min >= 1 && min <= 2880) {
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,");
}
}
#define XSTRUCT wificonf
#define X XSET_CGI_FUNC
XTABLE_WIFICONF
#undef X
(void) redir_url;
@ -216,7 +89,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
//Template code for the WLAN page.
httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *token, void **arg)
{
char buff[20];
char buff[64];
u8 mac[6];
if (token == NULL) {
@ -226,34 +99,13 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *toke
strcpy(buff, ""); // fallback
if (streq(token, "ap_dhcp_time")) {
sprintf(buff, "%d", wificonf->ap_dhcp_time);
}
else if (streq(token, "ap_dhcp_start")) {
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.start_ip.addr));
}
else if (streq(token, "ap_dhcp_end")) {
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")) {
#define XSTRUCT wificonf
#define X XGET_CGI_FUNC
XTABLE_WIFICONF
#undef X
// non-config
if (streq(token, "sta_mac")) {
wifi_get_macaddr(STATION_IF, mac);
sprintf(buff, MACSTR, MAC2STR(mac));
}

@ -7,8 +7,13 @@ Cgi/template routines for configuring non-wifi settings
#include "persist.h"
#include "helpers.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_ERR SET_REDIR_SUC"?err="
static bool ICACHE_FLASH_ATTR
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.");
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 cgiPersistRestoreDefaults(HttpdConnData *connData);
httpd_cgi_state cgiPersistRestoreHard(HttpdConnData *connData);
httpd_cgi_state cgiPersistExport(HttpdConnData *connData);
httpd_cgi_state cgiPersistImport(HttpdConnData *connData);
#endif

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

@ -95,108 +95,84 @@ cgiSystemCfgSetParams(HttpdConnData *connData)
memcpy(admin_backup, &persist.admin, sizeof(AdminConfigBlock));
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 {
if (!GET_ARG("pw")) {
break; // if no PW in GET, not trying to configure anything protected
}
if (!streq(buff, persist.admin.pw)) {
warn("Bad admin pw!");
redir_url += sprintf(redir_url, "pw,");
break;
}
// 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;
// Check admin PW
if (GET_ARG("pw")) {
if (!streq(buff, persist.admin.pw)) {
warn("Bad admin pw!");
redir_url += sprintf(redir_url, "pw,");
break; // Abort
} else {
admin = true;
}
sysconf->pwlock = (enum pwlock) pwlock;
}
if (GET_ARG("access_pw")) {
cgi_dbg("access_pw: %s", buff);
// Changing admin PW
if (admin && GET_ARG("admin_pw")) {
if (strlen(buff)) {
strcpy(buff2, 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);
}
cgi_dbg("admin_pw: %s", buff);
if (GET_ARG("admin_pw")) {
cgi_dbg("admin_pw: %s", buff);
if (strlen(buff)) {
strcpy(buff2, buff);
if (!GET_ARG("admin_pw2")) {
cgi_warn("Missing repeated admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw2,");
break;
break; // Abort
}
if (!streq(buff, buff2)) {
cgi_warn("Bad repeated admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw2,");
break;
break; // Abort
}
if (strlen(buff) >= 64) {
cgi_warn("Too long admin_pw %s", buff);
redir_url += sprintf(redir_url, "admin_pw,");
break;
break; // Abort
}
cgi_dbg("Changing admin PW!");
strncpy(persist.admin.pw, buff, 64);
break; // this is the only field in this form
}
}
} while (0);
if (GET_ARG("overclock")) {
cgi_dbg("overclock = %s", buff);
int enable = atoi(buff);
if (sysconf->overclock != enable) {
sysconf->overclock = (bool)enable;
// Reject filled but unconfirmed access PW
if (admin && GET_ARG("access_pw")) {
if (strlen(buff)) {
cgi_dbg("access_pw: %s", buff);
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)uart_changed; // unused
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
@ -236,13 +212,16 @@ tplSystemCfg(HttpdConnData *connData, char *token, void **arg)
strcpy(buff, ""); // fallback
if (streq(token, "pwlock")) {
sprintf(buff, "%d", sysconf->pwlock);
}
else if (streq(token, "access_name")) {
sprintf(buff, "%s", sysconf->access_name);
}
else if (streq(token, "def_access_name")) {
const bool admin = false;
const bool tpl=true;
#define XSTRUCT sysconf
#define X XGET_CGI_FUNC
XTABLE_SYSCONF
#undef X
#undef XSTRUCT
if (streq(token, "def_access_name")) {
sprintf(buff, "%s", DEF_ACCESS_NAME);
}
else if (streq(token, "def_access_pw")) {
@ -251,9 +230,6 @@ tplSystemCfg(HttpdConnData *connData, char *token, void **arg)
else if (streq(token, "def_admin_pw")) {
sprintf(buff, "%s", DEFAULT_ADMIN_PW);
}
else if (streq(token, "overclock")) {
sprintf(buff, "%d", sysconf->overclock);
}
tplSend(connData, buff, -1);
return HTTPD_CGI_DONE;

@ -15,22 +15,6 @@ Cgi/template routines for configuring non-wifi settings
#define SET_REDIR_SUC "/cfg/term"
#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.
*/
@ -39,15 +23,11 @@ cgiTermCfgSetParams(HttpdConnData *connData)
{
char buff[50];
char redir_url_buf[100];
int32 n, w, h;
ScreenNotifyTopics topics = 0;
bool shall_clear_screen = false;
bool shall_init_uart = false;
char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
// we'll test if anything was printed by looking for \0 in redir_url
SystemConfigBundle *sysconf_backup = malloc(sizeof(SystemConfigBundle));
TerminalConfigBundle *termconf_backup = malloc(sizeof(TerminalConfigBundle));
@ -59,322 +39,29 @@ cgiTermCfgSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE;
}
// width and height must always go together so we can do max size validation
if (GET_ARG("term_width")) {
do {
cgi_dbg("Default screen width: %s", buff);
w = atoi(buff);
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);
}
#define XSTRUCT termconf
#define X XSET_CGI_FUNC
XTABLE_TERMCONF
#undef X
#undef XSTRUCT
if (termconf->default_fg != n) {
termconf->default_fg = n; // this is current not sent through socket, no use to notify
topics |= TOPIC_CHANGE_SCREEN_OPTS;
}
}
if (GET_ARG("parser_tout_ms")) {
cgi_dbg("Parser timeout: %s ms", buff);
n = atoi(buff);
if (n >= 0) {
termconf->parser_tout_ms = n;
} else {
cgi_warn("Bad parser timeout %s", buff);
redir_url += sprintf(redir_url, "parser_tout_ms,");
}
}
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,");
}
}
// width and height must always go together so we can do max size validation
u32 siz = termconf->width*termconf->height;
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,");
}
// Settings from SysConf - UART
bool uart_changed = false;
// restrict what keys are allowed
#define admin 0
#define tpl 0
#define XSTRUCT sysconf
#define X XSET_CGI_FUNC
XTABLE_SYSCONF
#undef X
#undef XSTRUCT
(void)redir_url;
@ -384,17 +71,23 @@ cgiTermCfgSetParams(HttpdConnData *connData)
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) {
terminal_apply_settings();
} else {
terminal_apply_settings_noclear();
}
if (shall_init_uart) {
serialInit();
if (uart_changed) {
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.");
} else {
@ -416,9 +109,8 @@ cgiTermCfgSetParams(HttpdConnData *connData)
httpd_cgi_state ICACHE_FLASH_ATTR
tplTermCfg(HttpdConnData *connData, char *token, void **arg)
{
#define BUFLEN TERM_TITLE_LEN
#define BUFLEN 100 // large enough for backdrop
char buff[BUFLEN];
char buff2[10];
if (token == NULL) {
// We're done
@ -427,106 +119,18 @@ tplTermCfg(HttpdConnData *connData, char *token, void **arg)
strcpy(buff, ""); // fallback
if (streq(token, "term_width")) {
sprintf(buff, "%d", termconf->width);
}
else if (streq(token, "term_height")) {
sprintf(buff, "%d", termconf->height);
}
else if (streq(token, "parser_tout_ms")) {
sprintf(buff, "%d", termconf->parser_tout_ms);
}
else if (streq(token, "display_tout_ms")) {
sprintf(buff, "%d", termconf->display_tout_ms);
}
else if (streq(token, "display_cooldown_ms")) {
sprintf(buff, "%d", termconf->display_cooldown_ms);
}
else if (streq(token, "fn_alt_mode")) {
sprintf(buff, "%d", (int)termconf->fn_alt_mode);
}
else if (streq(token, "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;
}
}
}
#define XSTRUCT termconf
#define X XGET_CGI_FUNC
XTABLE_TERMCONF
#undef X
#undef XSTRUCT
// for uart
#define XSTRUCT sysconf
#define X XGET_CGI_FUNC
XTABLE_SYSCONF
#undef X
#undef XSTRUCT
tplSend(connData, buff, -1);
return HTTPD_CGI_DONE;

@ -19,6 +19,7 @@ Cgi/template routines for the /wifi url.
#include "wifimgr.h"
#include "persist.h"
#include "helpers.h"
#include "config_xmacros.h"
#include "cgi_logging.h"
#define SET_REDIR_SUC "/cfg/wifi"
@ -66,46 +67,6 @@ int ICACHE_FLASH_ATTR rssi2perc(int rssi)
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
* the static cgiWifiAps struct.
@ -363,18 +324,14 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
bool sta_turned_on = false;
bool sta_ssid_pw_changed = false;
#define XSTRUCT wificonf
#define X XSET_CGI_FUNC
XTABLE_WIFICONF
#undef X
// ---- WiFi opmode ----
if (GET_ARG("opmode")) {
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,");
}
}
// those are helpers, not a real prop
if (GET_ARG("ap_enable")) {
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;
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;
}
//Template code for the WLAN page.
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
if (streq(token, "opmode_name")) {
strcpy(buff, opmode2str(wificonf->opmode));
}
else if (streq(token, "opmode")) {
sprintf(buff, "%d", wificonf->opmode);
}
else if (streq(token, "sta_enable")) {
#define XSTRUCT wificonf
#define X XGET_CGI_FUNC
XTABLE_WIFICONF
#undef X
// non-config
if (streq(token, "sta_enable")) {
sprintf(buff, "%d", (wificonf->opmode & STATION_MODE) != 0);
}
else if (streq(token, "ap_enable")) {
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")) {
sprintf(buff, "%d", wifi_station_get_rssi());
}

@ -19,143 +19,352 @@
* Based on rxvt-unicode screen.C table.
*/
static const u16 codepage_0[] ESP_CONST_DATA =
{// Unicode ASCII SYM
{ // Unicode ASCII SYM
// %%BEGIN:0%%
0x2666, // 96 ` ♦
0x2592, // 97 a ▒
0x2409, // 98 b HT
0x240c, // 99 c FF
0x240d, // 100 d CR
0x240a, // 101 e LF
0x00b0, // 102 f °
0x00b1, // 103 g ±
0x2424, // 104 h NL
0x240b, // 105 i VT
0x2518, // 106 j ┘
0x2510, // 107 k ┐
0x250c, // 108 l ┌
0x2514, // 109 m └
0x253c, // 110 n ┼
0x23ba, // 111 o ⎺
0x23bb, // 112 p ⎻
0x2500, // 113 q ─
0x23bc, // 114 r ⎼
0x23bd, // 115 s ⎽
0x251c, // 116 t ├
0x2524, // 117 u ┤
0x2534, // 118 v ┴
0x252c, // 119 w ┬
0x2502, // 120 x │
0x2264, // 121 y ≤
0x2265, // 122 z ≥
0x03c0, // 123 { π
0x2260, // 124 | ≠
0x20a4, // 125 } £
0x00b7, // 126 ~ ·
u'', // 0x2666 96 `
u'', // 0x2592 97 a
u'', // 0x2409 98 b
u'', // 0x240c 99 c FF
u'', // 0x240d 100 d CR
u'', // 0x240a 101 e LF
u'°', // 0x00b0 102 f
u'±', // 0x00b1 103 g
u'', // 0x2424 104 h NL
u'', // 0x240b 105 i VT
u'', // 0x2518 106 j
u'', // 0x2510 107 k
u'', // 0x250c 108 l
u'', // 0x2514 109 m
u'', // 0x253c 110 n
u'', // 0x23ba 111 o
u'', // 0x23bb 112 p
u'', // 0x2500 113 q
u'', // 0x23bc 114 r
u'', // 0x23bd 115 s
u'', // 0x251c 116 t
u'', // 0x2524 117 u
u'', // 0x2534 118 v
u'', // 0x252c 119 w
u'', // 0x2502 120 x
u'', // 0x2264 121 y
u'', // 0x2265 122 z
u'π', // 0x03c0 123 {
u'', // 0x2260 124 |
u'£', // 0x20a4 125 }
u'·', // 0x00b7 126 ~
// %%END:0%%
};
#define CODEPAGE_1_BEGIN 33
#define CODEPAGE_1_END 126
// DOS, thin and double lines, arrows, angles, diagonals, thick border
static const u16 codepage_1[] ESP_CONST_DATA =
{// Unicode ASCII SYM DOS
{// Unicode ASCII DOS
// %%BEGIN:1%%
0x263A, // 33 ! (1) - low ASCII symbols from DOS, moved to +32
0x263B, // 34 " (2)
0x2665, // 35 # (3)
0x2666, // 36 $ (4)
0x2663, // 37 % (5)
0x2660, // 38 & (6)
0x2022, // 39 ' (7) - inverse dot and circle left out, can be done with SGR
0x231B, // 40 ( - hourglass (timer icon)
0x25CB, // 41 ) (9)
0x21AF, // 42 * - electricity (lightning monitor...)
0x266A, // 43 + (13)
0x266B, // 44 , (14)
0x263C, // 45 - (15)
0x2302, // 46 . (127)
0x2622, // 47 / - radioactivity (geiger counter...)
0x2591, // 48 0 (176) - this block is kept aligned and ordered from DOS, moved -128
0x2592, // 49 1 (177)
0x2593, // 50 2 (178)
0x2502, // 51 3 (179)
0x2524, // 52 4 ┤ (180)
0x2561, // 53 5 ╡ (181)
0x2562, // 54 6 (182)
0x2556, // 55 7 (183)
0x2555, // 56 8 (184)
0x2563, // 57 9 (185)
0x2551, // 58 : (186)
0x2557, // 59 ; (187)
0x255D, // 60 < (188)
0x255C, // 61 = (189)
0x255B, // 62 > (190)
0x2510, // 63 ? (191)
0x2514, // 64 @ (192)
0x2534, // 65 A (193)
0x252C, // 66 B (194)
0x251C, // 67 C (195)
0x2500, // 68 D (196)
0x253C, // 69 E (197)
0x255E, // 70 F (198)
0x255F, // 71 G (199)
0x255A, // 72 H (200)
0x2554, // 73 I (201)
0x2569, // 74 J (202)
0x2566, // 75 K (203)
0x2560, // 76 L (204)
0x2550, // 77 M (205)
0x256C, // 78 N (206)
0x2567, // 79 O (207)
0x2568, // 80 P (208)
0x2564, // 81 Q (209)
0x2565, // 82 R (210)
0x2559, // 83 S (211)
0x2558, // 84 T (212)
0x2552, // 85 U (213)
0x2553, // 86 V (214)
0x256B, // 87 W (215)
0x256A, // 88 X (216)
0x2518, // 89 Y (217)
0x250C, // 90 Z (218)
0x2588, // 91 [ (219)
0x2584, // 92 \ (220)
0x258C, // 93 ] (221)
0x2590, // 94 ^ (222)
0x2580, // 95 _ (223)
0x2195, // 96 ` (18) - moved from low DOS ASCII
0x2191, // 97 a (24)
0x2193, // 98 b (25)
0x2192, // 99 c (26)
0x2190, // 100 d (27)
0x2194, // 101 e (29)
0x25B2, // 102 f (30)
0x25BC, // 103 g (31)
0x25BA, // 104 h (16)
0x25C4, // 105 i (17)
0x25E2, // 106 j - added for slanted corners
0x25E3, // 107 k
0x25E4, // 108 l
0x25E5, // 109 m
0x256D, // 110 n - rounded corners
0x256E, // 111 o
0x256F, // 112 p
0x2570, // 113 q
0x0, // 114 r - free positions for future expansion
0x0, // 115 s
0x0, // 116 t
0x0, // 117 u
0x0, // 118 v
0x0, // 119 w
0x0, // 120 x
0x0, // 121 y
0x0, // 122 z
0x0, // 123 {
0x0, // 124 |
0x2714, // 125 } - checkboxes or checklist items
0x2718, // 126 ~
u'', // 0x263A, 33 ! (1) - low ASCII symbols from DOS, moved to +32
u'', // 0x263B, 34 " (2)
u'', // 0x2665, 35 # (3)
u'', // 0x2666, 36 $ (4)
u'', // 0x2663, 37 % (5)
u'', // 0x2660, 38 & (6)
u'', // 0x2022, 39 ' (7) - inverse dot and circle left out, can be done with SGR
u'', // 0x231B, 40 ( - hourglass (timer icon)
u'', // 0x25CB, 41 ) (9)
u'', // 0x21AF, 42 * - electricity (lightning monitor...)
u'', // 0x266A, 43 + (13)
u'', // 0x266B, 44 , (14)
u'', // 0x263C, 45 - (15)
u'', // 0x2302, 46 . (127)
u'', // 0x2622, 47 / - radioactivity (geiger counter...)
u'', // 0x2591, 48 0 (176) - this block is kept aligned and ordered from DOS, moved -128
u'', // 0x2592, 49 1 (177)
u'', // 0x2593, 50 2 (178)
u'', // 0x2502, 51 3 (179)
u'', // 0x2524, 52 4 (180)
u'', // 0x2561, 53 5 (181)
u'', // 0x2562, 54 6 (182)
u'', // 0x2556, 55 7 (183)
u'', // 0x2555, 56 8 (184)
u'', // 0x2563, 57 9 (185)
u'', // 0x2551, 58 : (186)
u'', // 0x2557, 59 ; (187)
u'', // 0x255D, 60 < (188)
u'', // 0x255C, 61 = (189)
u'', // 0x255B, 62 > (190)
u'', // 0x2510, 63 ? (191)
u'', // 0x2514, 64 @ (192)
u'', // 0x2534, 65 A (193)
u'', // 0x252C, 66 B (194)
u'', // 0x251C, 67 C (195)
u'', // 0x2500, 68 D (196)
u'', // 0x253C, 69 E (197)
u'', // 0x255E, 70 F (198)
u'', // 0x255F, 71 G (199)
u'', // 0x255A, 72 H (200)
u'', // 0x2554, 73 I (201)
u'', // 0x2569, 74 J (202)
u'', // 0x2566, 75 K (203)
u'', // 0x2560, 76 L (204)
u'', // 0x2550, 77 M (205)
u'', // 0x256C, 78 N (206)
u'', // 0x2567, 79 O (207)
u'', // 0x2568, 80 P (208)
u'', // 0x2564, 81 Q (209)
u'', // 0x2565, 82 R (210)
u'', // 0x2559, 83 S (211)
u'', // 0x2558, 84 T (212)
u'', // 0x2552, 85 U (213)
u'', // 0x2553, 86 V (214)
u'', // 0x256B, 87 W (215)
u'', // 0x256A, 88 X (216)
u'', // 0x2518, 89 Y (217)
u'', // 0x250C, 90 Z (218)
u'', // 0x2588, 91 [ (219)
u'', // 0x2584, 92 \ (220)
u'', // 0x258C, 93 ] (221)
u'', // 0x2590, 94 ^ (222)
u'', // 0x2580, 95 _ (223)
u'', // 0x2195, 96 ` (18) - moved from low DOS ASCII
u'', // 0x2191, 97 a (24)
u'', // 0x2193, 98 b (25)
u'', // 0x2192, 99 c (26)
u'', // 0x2190, 100 d (27)
u'', // 0x2194, 101 e (29)
u'', // 0x25B2, 102 f (30)
u'', // 0x25BC, 103 g (31)
u'', // 0x25BA, 104 h (16)
u'', // 0x25C4, 105 i (17)
u'', // 0x25E2, 106 j - added for slanted corners
u'', // 0x25E3, 107 k
u'', // 0x25E4, 108 l
u'', // 0x25E5, 109 m
u'', // 0x256D, 110 n - rounded corners
u'', // 0x256E, 111 o
u'', // 0x256F, 112 p
u'', // 0x2570, 113 q
u'', // 0x0, 114 r - right up diagonal
u'', // 0x0, 115 s - right down diagonal
u'', // 0x0, 116 t
u'', // 0x0, 117 u
u'', // 0x0, 118 v
u'', // 0x0, 119 w
u'', // 0x0, 120 x
0xE0B0, // powerline right triangle (filled), 121 y
0xE0B1, // powerline right triangle (hollow), 122 z
0xE0B2, // powerline left triangle (filled), 123 {
0xE0B3, // powerline left triangle (hollow), 124 | - reserved
u'', // 0x2714, 125 } - checkboxes or checklist items
u'', // 0x2718, 126 ~
// %%END:1%%
};
#define CODEPAGE_2_BEGIN 33
#define CODEPAGE_2_END 126
// blocks, thick and split lines, line butts
static const u16 codepage_2[] ESP_CONST_DATA =
{// Unicode ASCII DOS
// %%BEGIN:2%%
u'', // 0x2581, 33 ! - those are ordered this way to allow easy calculating of the right code (for graphs)
u'', // 0x2582, 34 "
u'', // 0x2583, 35 #
u'', // 0x2584, 36 $
u'', // 0x2585, 37 %
u'', // 0x2586, 38 &
u'', // 0x2587, 39 ' - 7-eighths
u'', // 0x2588, 40 ( - full block, shared by both sequences
u'', // 0x2589, 41 ) - those grow thinner, to re-use the full block
u'', // 0x258A, 42 *
u'', // 0x258B, 43 +
u'', // 0x258C, 44 ,
u'', // 0x258D, 45 -
u'', // 0x258E, 46 .
u'', // 0x258F, 47 /
u'', // 0x2594, 48 0 - complementary symbols
u'', // 0x2595, 49 1
u'', // 0x2590, 50 2
u'', // 0x2580, 51 3
u'', // 0x2598, 52 4 - top-left, top-right, bottom-right, bottom-left
u'', // 0x259D, 53 5
u'', // 0x2597, 54 6
u'', // 0x2596, 55 7
u'', // 0x259F, 56 8
u'', // 0x2599, 57 9
u'', // 0x259B, 58 :
u'', // 0x259C, 59 ;
u'', // 0x259E, 60 < - complementary diagonals
u'', // 0x259A, 61 =
u'', // 0x, 62 > - here are thick and thin lines and their joins. it's really quite arbitrary, based on the unicode order, excluding single lines
u'', // 0x, 63 ?
u'', // 0x, 64 @
u'', // 0x, 65 A
u'', // 0x, 66 B
u'', // 0x, 67 C
u'', // 0x, 68 D
u'', // 0x, 69 E
u'', // 0x, 70 F
u'', // 0x, 71 G
u'', // 0x, 72 H
u'', // 0x, 73 I
u'', // 0x, 74 J
u'', // 0x, 75 K
u'', // 0x, 76 L
u'', // 0x, 77 M
u'', // 0x, 78 N
u'', // 0x, 79 O
u'', // 0x, 80 P
u'', // 0x, 81 Q
u'', // 0x, 82 R
u'', // 0x, 83 S
u'', // 0x, 84 T
u'', // 0x, 85 U
u'', // 0x, 86 V
u'', // 0x, 87 W
u'', // 0x, 88 X
u'', // 0x, 89 Y
u'', // 0x, 90 Z
u'', // 0x, 91 [
u'', // 0x, 92 \ .
u'', // 0x, 93 ]
u'', // 0x, 94 ^
u'', // 0x, 95 _
u'', // 0x, 96 `
u'', // 0x, 97 a
u'', // 0x, 98 b
u'', // 0x, 99 c
u'', // 0x, 100 d
u'', // 0x, 101 e
u'', // 0x, 102 f
u'', // 0x, 103 g
u'', // 0x, 104 h
u'', // 0x, 105 i
u'', // 0x, 106 j
u'', // 0x, 107 k
u'', // 0x, 108 l
u'', // 0x, 109 m
u'', // 0x, 110 n
u'', // 0x, 111 o
u'', // 0x, 112 p
u'', // 0x, 113 q
u'', // 0x, 114 r
u'', // 0x, 115 s
u'', // 0x, 116 t
u'', // 0x, 117 u
u'', // 0x, 118 v
u'', // 0x, 119 w - butts
u'', // 0x, 120 x
u'', // 0x, 121 y
u'', // 0x, 122 z
u'', // 0x, 123 {
u'', // 0x, 124 |
u'', // 0x, 125 }
u'', // 0x, 126 ~
// %%END:2%%
};
#define CODEPAGE_3_BEGIN 33
#define CODEPAGE_3_END 48
// dashed lines, split straight lines
static const u16 codepage_3[] ESP_CONST_DATA =
{// Unicode ASCII DOS
// %%BEGIN:3%%
u'', // 0x, 33 !
u'', // 0x, 34 "
u'', // 0x, 35 #
u'', // 0x, 36 $
u'', // 0x, 37 %
u'', // 0x, 38 &
u'', // 0x, 39 '
u'', // 0x, 40 (
u'', // 0x, 41 )
u'', // 0x, 42 *
u'', // 0x, 43 +
u'', // 0x, 44 ,
u'', // 0x, 45 -
u'', // 0x, 46 .
u'', // 0x, 47 /
u'', // 0x, 48 0
// %%END:3%%
// u'\0', // 0x, 49 1
// u'\0', // 0x, 50 2
// u'\0', // 0x, 51 3
// u'\0', // 0x, 52 4
// u'\0', // 0x, 53 5
// u'\0', // 0x, 54 6
// u'\0', // 0x, 55 7
// u'\0', // 0x, 56 8
// u'\0', // 0x, 57 9
// u'\0', // 0x, 58 :
// u'\0', // 0x, 59 ;
// u'\0', // 0x, 60 <
// u'\0', // 0x, 61 =
// u'\0', // 0x, 62 >
// u'\0', // 0x, 63 ?
// u'\0', // 0x, 64 @
// u'\0', // 0x, 65 A
// u'\0', // 0x, 66 B
// u'\0', // 0x, 67 C
// u'\0', // 0x, 68 D
// u'\0', // 0x, 69 E
// u'\0', // 0x, 70 F
// u'\0', // 0x, 71 G
// u'\0', // 0x, 72 H
// u'\0', // 0x, 73 I
// u'\0', // 0x, 74 J
// u'\0', // 0x, 75 K
// u'\0', // 0x, 76 L
// u'\0', // 0x, 77 M
// u'\0', // 0x, 78 N
// u'\0', // 0x, 79 O
// u'\0', // 0x, 80 P
// u'\0', // 0x, 81 Q
// u'\0', // 0x, 82 R
// u'\0', // 0x, 83 S
// u'\0', // 0x, 84 T
// u'\0', // 0x, 85 U
// u'\0', // 0x, 86 V
// u'\0', // 0x, 87 W
// u'\0', // 0x, 88 X
// u'\0', // 0x, 89 Y
// u'\0', // 0x, 90 Z
// u'\0', // 0x, 91 [
// u'\0', // 0x, 92 \ .
// u'\0', // 0x, 93 ]
// u'\0', // 0x, 94 ^
// u'\0', // 0x, 95 _
// u'\0', // 0x, 96 `
// u'\0', // 0x, 97 a
// u'\0', // 0x, 98 b
// u'\0', // 0x, 99 c
// u'\0', // 0x, 100 d
// u'\0', // 0x, 101 e
// u'\0', // 0x, 102 f
// u'\0', // 0x, 103 g
// u'\0', // 0x, 104 h
// u'\0', // 0x, 105 i
// u'\0', // 0x, 106 j
// u'\0', // 0x, 107 k
// u'\0', // 0x, 108 l
// u'\0', // 0x, 109 m
// u'\0', // 0x, 110 n
// u'\0', // 0x, 111 o
// u'\0', // 0x, 112 p
// u'\0', // 0x, 113 q
// u'\0', // 0x, 114 r
// u'\0', // 0x, 115 s
// u'\0', // 0x, 116 t
// u'\0', // 0x, 117 u
// u'\0', // 0x, 118 v
// u'\0', // 0x, 119 w
// u'\0', // 0x, 120 x
// u'\0', // 0x, 121 y
// u'\0', // 0x, 122 z
// u'\0', // 0x, 123 {
// u'\0', // 0x, 124 |
// u'\0', // 0x, 125 }
// u'\0', // 0x, 126 ~
};
#endif //ESPTERM_CHARACTER_SETS_H_H

@ -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,97 @@
//
// 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 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;
#*/
}%%
}

@ -23,8 +23,8 @@
static int ICACHE_FLASH_ATTR wifiPassFn(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen)
{
if (no == 0) {
os_strcpy(user, sysconf->access_name);
os_strcpy(pass, sysconf->access_pw);
os_strcpy(user, (const char *) sysconf->access_name);
os_strcpy(pass, (const char *) sysconf->access_pw);
return 1;
}
if (no == 1) {
@ -131,6 +131,8 @@ const HttpdBuiltInUrl routes[] ESP_CONST_DATA = {
ROUTE_TPL_FILE("/cfg/system/?", tplSystemCfg, "/cfg_system.tpl"),
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/restore_defaults", cgiPersistRestoreDefaults),
ROUTE_CGI("/cfg/system/restore_hard", cgiPersistRestoreHard),

@ -8,6 +8,7 @@
#include "character_sets.h"
#include "utf8.h"
#include "cgi_sockets.h"
#include "cgi_logging.h"
TerminalConfigBundle * const termconf = &persist.current.termconf;
TerminalConfigBundle termconf_live;
@ -38,6 +39,7 @@ static Cell screen[MAX_SCREEN_SIZE];
#define TABSTOP_WORDS 5
#define LINE_ATTRS_COUNT 64
/**
* Screen state structure
*/
@ -56,9 +58,12 @@ static struct {
int vm1;
u32 tab_stops[TABSTOP_WORDS]; // tab stops bitmap
u8 line_attribs[LINE_ATTRS_COUNT]; // assume that's quite enough...
char last_char[4];
} scr;
#define IS_DOUBLE_WIDTH() (scr.line_attribs[cursor.y]&0b001)
#define TOP scr.vm0
#define BTM scr.vm1
#define RH (scr.vm1 - scr.vm0 + 1)
@ -106,8 +111,19 @@ bool cursor_saved = false;
static struct {
bool alternate_active;
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 height;
int vm0;
@ -143,46 +159,200 @@ static struct {
int x_min, y_min, x_max, y_max;
} scr_dirty;
#define reset_screen_dirty() do { \
scr_dirty.x_min = W; \
scr_dirty.x_max = -1; \
scr_dirty.y_min = H; \
scr_dirty.y_max = -1; \
} while(0)
static void ICACHE_FLASH_ATTR reset_screen_dirty(void)
{
scr_dirty.x_min = W;
scr_dirty.x_max = -1;
scr_dirty.y_min = H;
scr_dirty.y_max = -1;
}
#define expand_dirty(y0, y1, x0, x1) do { \
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); \
if ((int)(x0) < scr_dirty.x_min) scr_dirty.x_min = (x0); \
if ((int)(y1) > scr_dirty.y_max) scr_dirty.y_max = (y1); \
if ((int)(x1) > scr_dirty.x_max) scr_dirty.x_max = (x1); \
} while(0)
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 ((y0) < scr_dirty.y_min) scr_dirty.y_min = (y0);
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() { notifyLock++; }
#define NOTIFY_LOCK() do { \
notifyLock++; \
} while(0)
#define NOTIFY_DONE(updateTopics) do { \
lockTopics |= (updateTopics); \
if (notifyLock > 0) notifyLock--; \
if (notifyLock == 0) { \
screen_notifyChange(lockTopics); \
lockTopics = 0;\
} \
} while(0)
static void ICACHE_FLASH_ATTR NOTIFY_DONE(u32 updateTopics)
{
lockTopics |= (updateTopics);
if (notifyLock > 0) notifyLock--;
if (notifyLock == 0) {
screen_notifyChange(lockTopics);
lockTopics = 0;
}
}
/** Clear the hanging attribute if the cursor is no longer >= W */
#define clear_invalid_hanging() do { \
if (cursor.hanging && cursor.x != W-1) { \
cursor.hanging = false; \
screen_notifyChange(TOPIC_CHANGE_CURSOR); \
} \
} while(false)
static void ICACHE_FLASH_ATTR clear_invalid_hanging(void)
{
if (cursor.hanging && cursor.x != W-1) {
cursor.hanging = false;
screen_notifyChange(TOPIC_CHANGE_CURSOR);
}
}
#define cursor_inside_region() (cursor.y >= TOP && cursor.y <= BTM)
//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
*/
@ -194,10 +364,21 @@ terminal_restore_defaults(void)
termconf->default_bg = 0;
termconf->default_fg = 7;
sprintf(termconf->title, SCR_DEF_TITLE);
for(int i=1; i <= TERM_BTN_COUNT; i++) {
sprintf(termconf->btn[i-1], "%d", i);
sprintf(termconf->btn_msg[i-1], "%c", i);
}
strcpy(termconf->btn1, "1");
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->parser_tout_ms = SCR_DEF_PARSER_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->allow_decopt_12 = SCR_DEF_DECOPT12;
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;
// char buff[64];
//#define XSTRUCT termconf
//#define X XDUMP_FIELD
// XTABLE_TERMCONF
//#undef X
// return;
// Migrate
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;
changed = 1;
}
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;
changed = 1;
}
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;
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;
@ -346,7 +555,12 @@ screen_reset_sgr(void)
static void ICACHE_FLASH_ATTR
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();
// DECopts
@ -383,18 +597,23 @@ screen_reset_do(bool size, bool labels)
scr.tab_stops[i] = 0x80808080;
}
// clear line attribs
memset(scr.line_attribs, 0, LINE_ATTRS_COUNT);
if (labels) {
strcpy(termconf_live.title, termconf->title);
strcpy(termconf_live.backdrop, termconf->backdrop);
for (int i = 1; i <= TERM_BTN_COUNT; i++) {
strcpy(termconf_live.btn[i], termconf->btn[i]);
strcpy(termconf_live.btn_msg[i], termconf->btn_msg[i]);
}
strcpy(termconf_live.btn1, termconf->btn1);
strcpy(termconf_live.btn2, termconf->btn2);
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_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
@ -445,8 +664,11 @@ screen_swap_state(bool alternate)
ansi_dbg("Swap to alternate");
// store old state
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));
state_backup.vm0 = scr.vm0;
state_backup.vm1 = scr.vm1;
@ -458,8 +680,11 @@ screen_swap_state(bool alternate)
else {
ansi_dbg("Unswap from alternate");
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));
scr.vm0 = state_backup.vm0;
scr.vm1 = state_backup.vm1;
@ -474,6 +699,32 @@ screen_swap_state(bool alternate)
//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 ---
void ICACHE_FLASH_ATTR
@ -509,6 +760,7 @@ next_tab_stop(void)
{
// cursor must never go past EOL
if (cursor.x >= W-1) return -1;
if (IS_DOUBLE_WIDTH() && cursor.x >= W/2-1) return -1;
// find first word to inspect
int idx = (cursor.x+1)/32;
@ -520,6 +772,7 @@ next_tab_stop(void)
for(;offs<32;offs++) {
cp++;
if (cp >= W) return -1;
if (IS_DOUBLE_WIDTH() && cp >= W/2) return -1;
if (w & 1) return cp;
w >>= 1;
}
@ -577,6 +830,7 @@ screen_tab_forward(int count)
}
else {
cursor.x = W - 1;
if (IS_DOUBLE_WIDTH()) cursor.x = W/2 - 1;
}
}
NOTIFY_DONE(TOPIC_CHANGE_CURSOR);
@ -778,19 +1032,23 @@ screen_clear(ClearMode mode)
unicode_cache_clear();
clear_range_noutf(0, W * H - 1);
scr.last_char[0] = 0;
for (int i = 0; i < LINE_ATTRS_COUNT; i++) scr.line_attribs[i] = 0;
break;
case CLEAR_FROM_CURSOR:
clear_range_utf((cursor.y * W) + cursor.x, W * H - 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;
case CLEAR_TO_CURSOR:
clear_range_utf(0, (cursor.y * W) + cursor.x);
expand_dirty(0, cursor.y, 0, W-1);
for (int i = 0; i <= cursor.y; i++) scr.line_attribs[i] = 0;
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;
if (targetStart > BTM) {
clear_range_utf(cursor.y*W, (BTM+1)*W-1);
for (int i = cursor.y; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
} else {
// do the moving
for (int i = BTM; i >= targetStart; i--) {
utf_free_row(i); // release old characters
copy_row(i, i - lines);
scr.line_attribs[i] = scr.line_attribs[i-lines];
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);
NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART);
NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART|TOPIC_DOUBLE_LINES);
}
void ICACHE_FLASH_ATTR
@ -873,18 +1135,26 @@ screen_delete_lines(unsigned int lines)
// clear the entire rest of the screen
movedBlockEnd = cursor.y;
clear_range_utf(movedBlockEnd*W, (BTM+1)*W-1);
for (int i = movedBlockEnd; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
} else {
// move some lines up, clear the rest
for (int i = cursor.y; i <= movedBlockEnd; i++) {
utf_free_row(i);
copy_row(i, i+lines);
scr.line_attribs[i] = scr.line_attribs[i+lines];
if (i != movedBlockEnd) utf_backup_row(i);
}
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);
NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART);
NOTIFY_DONE(TOPIC_CHANGE_CONTENT_PART|TOPIC_DOUBLE_LINES);
}
void ICACHE_FLASH_ATTR
@ -982,8 +1252,8 @@ screen_resize(int rows, int cols)
if (W == cols && H == rows) return; // Do nothing
NOTIFY_LOCK();
W = cols;
H = rows;
W = (u32) cols;
H = (u32) rows;
screen_reset_on_resize();
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)
{
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);
}
/**
* 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
*/
@ -1019,6 +1361,9 @@ screen_scroll_up(unsigned int lines)
if (lines >= RH) {
// clear entire region
clear_range_utf(TOP * W, (BTM + 1) * W - 1);
for (int i = TOP; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
goto done;
}
@ -1031,14 +1376,18 @@ screen_scroll_up(unsigned int lines)
for (y = TOP; y <= BTM - lines; y++) {
utf_free_row(y);
copy_row(y, y+lines);
scr.line_attribs[y] = scr.line_attribs[y+lines];
if (y < BTM - lines) utf_backup_row(y);
}
clear_range_noutf(y * W, (BTM + 1) * W - 1);
for (int i = y; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
done:
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) {
// clear entire region
clear_range_utf(TOP * W, (BTM + 1) * W - 1);
for (int i = TOP; i <= BTM; i++) {
scr.line_attribs[i] = 0;
}
goto done;
}
@ -1063,13 +1415,17 @@ screen_scroll_down(unsigned int lines)
for (y = BTM; y >= TOP+lines; y--) {
utf_free_row(y);
copy_row(y, y-lines);
scr.line_attribs[y] = scr.line_attribs[y-lines];
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:
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 */
@ -1102,7 +1458,7 @@ void ICACHE_FLASH_ATTR
screen_cursor_shape(enum CursorShape shape)
{
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;
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
// cannot be set using the cursor-set commands.
cursor.hanging = false;
if (IS_DOUBLE_WIDTH() && cursor.x >= W/2) cursor.x = W/2-1;
NOTIFY_DONE(TOPIC_CHANGE_CURSOR);
}
@ -1218,6 +1577,7 @@ screen_cursor_move(int dy, int dx, bool scroll)
cursor.x += dx;
cursor.y += dy;
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.auto_wrap && cursor.reverse_wrap) {
// 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);
}
@ -1337,7 +1699,7 @@ screen_back_index(int count)
cursor.x = new_x;
} else {
cursor.x = 0;
screen_insert_characters(-new_x);
screen_insert_characters((unsigned int) -new_x);
topics |= TOPIC_CHANGE_CONTENT_PART;
}
NOTIFY_DONE(topics);
@ -1529,7 +1891,7 @@ do_save_private_opt(int n, bool save)
{
ScreenNotifyTopics topics = TOPIC_INTERNAL;
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) {
case 1:
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.x = W - 1;
}
if (IS_DOUBLE_WIDTH() && cursor.x >= W/2) {
cursor.hanging = true; // hanging
cursor.x = W/2 - 1;
}
NOTIFY_DONE(topics);
return ch;
@ -1794,6 +2160,20 @@ utf8_remap(char *out, char g, char charset)
}
break;
case CS_2_BLOCKS_LINES: /* ESPTerm Character Rom 2 */
if ((g >= CODEPAGE_2_BEGIN) && (g <= CODEPAGE_2_END)) {
n = codepage_2[g - CODEPAGE_2_BEGIN];
if (n) utf = n;
}
break;
case CS_3_LINES_EXTRA: /* ESPTerm Character Rom 3 */
if ((g >= CODEPAGE_3_BEGIN) && (g <= CODEPAGE_3_END)) {
n = codepage_3[g - CODEPAGE_3_BEGIN];
if (n) utf = n;
}
break;
case CS_A_UKASCII: /* UK, replaces # with GBP */
if (g == '#') utf = 0x20a4; // £
break;
@ -1877,6 +2257,15 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
bufput_utf8((num)); \
} 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
#define SEQ_TAG_SKIP '\x01'
#define SEQ_TAG_REPEAT '\x02'
@ -1887,12 +2276,15 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
#define SEQ_TAG_ATTRS_0 '\x07'
#define TOPICMARK_SCREEN_OPTS 'O'
#define TOPICMARK_STATIC_OPTS 'P'
#define TOPICMARK_TITLE 'T'
#define TOPICMARK_BUTTONS 'B'
#define TOPICMARK_DEBUG 'D'
#define TOPICMARK_BELL '!'
#define TOPICMARK_CURSOR 'C'
#define TOPICMARK_SCREEN 'S'
#define TOPICMARK_BACKDROP 'W'
#define TOPICMARK_DBL_LINE 'H'
if (ss == NULL) {
// START!
@ -1983,12 +2375,8 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
bufput_utf8(H);
bufput_utf8(W);
bufput_utf8(termconf_live.theme);
bufput_utf8(termconf_live.default_fg & 0xFFFF);
bufput_utf8((termconf_live.default_fg >> 16) & 0xFFFF);
bufput_utf8(termconf_live.default_bg & 0xFFFF);
bufput_utf8((termconf_live.default_bg >> 16) & 0xFFFF);
bufput_color_utf8(termconf_live.default_fg);
bufput_color_utf8(termconf_live.default_bg);
bufput_utf8(
(scr.cursor_visible << 0) |
@ -2007,28 +2395,79 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
);
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)
bufput_c(TOPICMARK_TITLE);
int len = (int) strlen(termconf_live.title);
memcpy(bb, termconf_live.title, len);
bb += len;
remain -= len;
size_t len = strlen(termconf_live.title);
if (len > 0) {
memcpy(bb, termconf_live.title, len);
bb += len;
remain -= len;
}
bufput_c('\x01');
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_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++) {
int len = (int) strlen(termconf_live.btn[i]);
memcpy(bb, termconf_live.btn[i], len);
size_t len = strlen(termconf_live.backdrop);
if (len > 0) {
memcpy(bb, termconf_live.backdrop, len);
bb += len;
remain -= len;
bufput_c('\x01');
}
bufput_c('\x01');
END_TOPIC
BEGIN_TOPIC(TOPIC_INTERNAL, 45)
@ -2049,6 +2488,8 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, ScreenNotifyTopics topics,
bufput_utf8(cursor.charsetN);
bufput_c(cursor.charset0);
bufput_c(cursor.charset1);
bufput_color_utf8(cursor.fg);
bufput_color_utf8(cursor.bg);
bufput_utf8(system_get_free_heap_size());
bufput_utf8(term_active_clients);
END_TOPIC

@ -5,6 +5,7 @@
#include <stdbool.h>
#include <esp8266.h>
#include <httpd.h>
#include "config_xmacros.h"
/**
* This module handles the virtual screen and operations on it.
@ -38,6 +39,8 @@
#define TERM_BTN_MSG_LEN 10
#define TERM_TITLE_LEN 64
#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_COOLDOWN_MS 35
@ -68,38 +71,77 @@ enum CursorShape {
#define SCR_DEF_DEBUGBAR 0
#define SCR_DEF_DECOPT12 0
#define SCR_DEF_ASCIIDEBUG 0
#define SCR_DEF_BUTTON_COUNT 5
// --- Persistent Settings ---
#define CURSOR_BLINKS(shape) ((shape)==CURSOR_BLOCK_BL||(shape)==CURSOR_UNDERLINE_BL||(shape)==CURSOR_BAR_BL)
// Size designed for the terminal config structure
// Must be constant to avoid corrupting user config after upgrade
#define TERMCONF_SIZE 300
#define TERMCONF_VERSION 3
#define TERMCONF_SIZE 500
#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)
#define TERM_BM_N(tc, n) ((tc)->bm1+(TERM_BTN_MSG_LEN*n))
#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 {
u32 width;
u32 height;
u32 default_bg; // 00-FFh - ANSI colors, (00:00:00-FF:FF:FF)+256 - True Color
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;
#define X XSTRUCT_FIELD
XTABLE_TERMCONF
#undef X
} TerminalConfigBundle;
// Live config
@ -148,6 +190,11 @@ void screen_resize(int rows, int cols);
void screen_set_title(const char *title);
/** Set a button 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 ---
@ -156,6 +203,8 @@ typedef enum {
CS_A_UKASCII = 'A',
CS_0_DEC_SUPPLEMENTAL = '0',
CS_1_DOS_437 = '1',
CS_2_BLOCKS_LINES = '2',
CS_3_LINES_EXTRA = '3',
} CHARSET;
enum ScreenSerializeTopic {
@ -167,15 +216,21 @@ enum ScreenSerializeTopic {
TOPIC_CHANGE_CURSOR = (1<<5),
TOPIC_INTERNAL = (1<<6), // debugging internal state
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
// combos
TOPIC_INITIAL =
TOPIC_CHANGE_SCREEN_OPTS |
TOPIC_CHANGE_STATIC_OPTS |
TOPIC_CHANGE_CONTENT_ALL |
TOPIC_CHANGE_CURSOR |
TOPIC_CHANGE_TITLE |
TOPIC_CHANGE_BUTTONS,
TOPIC_CHANGE_BACKDROP |
TOPIC_CHANGE_BUTTONS |
TOPIC_DOUBLE_LINES,
};
typedef u16 ScreenNotifyTopics;
@ -327,6 +382,9 @@ void screen_tab_reverse(int count);
/** Move left, shift right if at the boundary */
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 ---
/**

@ -42,7 +42,7 @@ buf_pop(void *unused)
LOCAL void my_putc(char c)
{
UART_WriteCharCRLF(UART1, (u8) c, 10);
UART_WriteCharCRLF(UART1, (u8) c, 200);
}
/**

@ -6,12 +6,112 @@
#include "persist.h"
#include "uart_driver.h"
#include "serial.h"
#include "cgi_logging.h"
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
sysconf_apply_settings(void)
{
// char buff[64];
//#define XSTRUCT sysconf
//#define X XDUMP_FIELD
// XTABLE_SYSCONF
//#undef X
bool changed = false;
if (sysconf->config_version < 1) {
dbg("Upgrading syscfg to v 1");
@ -40,7 +140,7 @@ sysconf_restore_defaults(void)
sysconf->config_version = SYSCONF_VERSION;
sysconf->access_pw[0] = 0;
sysconf->pwlock = PWLOCK_NONE;
strcpy(sysconf->access_pw, DEF_ACCESS_PW);
strcpy(sysconf->access_name, DEF_ACCESS_NAME);
strcpy((char *)sysconf->access_pw, DEF_ACCESS_PW);
strcpy((char *)sysconf->access_name, DEF_ACCESS_NAME);
sysconf->overclock = false;
}

@ -6,6 +6,7 @@
#define ESP_VT100_FIRMWARE_SYSCFG_H
#include <esp8266.h>
#include "config_xmacros.h"
// Size designed for the wifi config structure
// Must be constant to avoid corrupting user config after upgrade
@ -24,15 +25,35 @@ enum pwlock {
PWLOCK_MAX = 5,
};
//....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) \
typedef struct {
u32 uart_baudrate;
u8 uart_parity;
u8 uart_stopbits;
u8 config_version;
enum pwlock pwlock : 8; // page access lock
char access_pw[64]; // access password
char access_name[32]; // access name
bool overclock;
#define X XSTRUCT_FIELD
XTABLE_SYSCONF
#undef X
// u32 uart_baudrate;
// u8 uart_parity;
// u8 uart_stopbits;
// u8 config_version;
// enum pwlock pwlock : 8; // page access lock
// char access_pw[64]; // access password
// char access_name[32]; // access name
// bool overclock;
} SystemConfigBundle;
extern SystemConfigBundle * const sysconf;
@ -41,4 +62,10 @@ void sysconf_apply_settings(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

@ -150,6 +150,8 @@ static void ICACHE_FLASH_ATTR user_start(void *unused)
// Critically important for client application if any kind of screen persistence / content re-use is needed
UART_WriteChar(UART0, CAN, UART_TIMEOUT_US); // 0x18 - 24 - CAN
dbg("tsize=%d", sizeof(TerminalConfigBundle));
#if DEBUG_HEAP
// Heap use timer & blink
TIMER_START(&prHeapTimer, prHeapTimerCb, HEAP_TIMER_MS, 1);

@ -9,10 +9,10 @@
#define STR(x) STR_HELPER(x)
#define FW_V_MAJOR 2
#define FW_V_MINOR 1
#define FW_V_MINOR 3
#define FW_V_PATCH 0
#define FW_V_SUFFIX ""
#define FW_CODENAME "Anthill" // 2.1.0
#define FW_CODENAME "Cricket" // 2.3
#define FW_CODENAME_QUOTED "\""FW_CODENAME"\""
#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 "persist.h"
#include "cgi_logging.h"
#include "config_xmacros.h"
WiFiConfigBundle * const wificonf = &persist.current.wificonf;
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)
{
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_hidden = false;
IP4_ADDR(&wificonf->ap_addr.ip, 192, 168, 4, 1);
IP4_ADDR(&wificonf->ap_addr.netmask, 255, 255, 255, 0);
wificonf->ap_addr.gw.addr = wificonf->ap_addr.gw.addr;
IP4_ADDR(&wificonf->ap_addr_ip, 192, 168, 4, 1);
IP4_ADDR(&wificonf->ap_addr_mask, 255, 255, 255, 0);
IP4_ADDR(&wificonf->ap_dhcp_range.start_ip, 192, 168, 4, 100);
IP4_ADDR(&wificonf->ap_dhcp_range.end_ip, 192, 168, 4, 200);
wificonf->ap_dhcp_range.enable = 1; // this will never get changed, idk why it's even there
IP4_ADDR(&wificonf->ap_dhcp_start, 192, 168, 4, 100);
IP4_ADDR(&wificonf->ap_dhcp_end, 192, 168, 4, 200);
wificonf->ap_dhcp_time = 120;
// --- Client config ---
@ -56,9 +171,9 @@ wifimgr_restore_defaults(void)
wificonf->sta_password[0] = 0;
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.netmask, 255, 255, 255, 0);
IP4_ADDR(&wificonf->sta_addr.gw, 192, 168, 0, 1);
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_mask, 255, 255, 255, 0);
IP4_ADDR(&wificonf->sta_addr_gw, 192, 168, 0, 1); // a common default...
}
static void ICACHE_FLASH_ATTR
@ -83,13 +198,18 @@ configure_station(void)
}
else {
wifi_info("[WiFi] Setting up static IP...");
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.gw = "IPSTR, GOOD_IP2STR(wificonf->sta_addr.gw.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_mask.addr));
wifi_dbg("[WiFi] Client.gw = "IPSTR, GOOD_IP2STR(wificonf->sta_addr_gw.addr));
wifi_station_dhcpc_stop();
// 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!");
return;
}
@ -112,7 +232,7 @@ configure_ap(void)
strcpy((char *) conf.password, (char *) wificonf->ap_password);
conf.authmode = (wificonf->ap_password[0] == 0 ? AUTH_OPEN : AUTH_WPA2_PSK);
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.beacon_interval = 100; // default 100 ms
@ -127,24 +247,32 @@ configure_ap(void)
// Set 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.mask = "IPSTR, GOOD_IP2STR(wificonf->ap_addr.netmask.addr));
wifi_dbg("[WiFi] SoftAP.gw = "IPSTR, GOOD_IP2STR(wificonf->ap_addr.gw.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_mask.addr));
wifi_softap_dhcps_stop();
// 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!");
return;
}
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.end = "IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.end_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_end.addr));
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!");
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
*/
@ -172,6 +315,12 @@ wifimgr_apply_settings(void)
{
wifi_info("[WiFi] Initializing...");
// char buff[64];
//#define XSTRUCT wificonf
//#define X XDUMP_FIELD
// XTABLE_WIFICONF
//#undef X
// !!! Update to current version !!!
// Force wifi cycle
@ -190,7 +339,7 @@ wifimgr_apply_settings(void)
}
if (opmode != wificonf->opmode) {
wifi_set_opmode_current(wificonf->opmode);
wifi_set_opmode_current((WIFI_MODE) wificonf->opmode);
}
// Configure the client

@ -8,6 +8,7 @@
#define ESP_VT100_FIRMWARE_WIFI_MANAGER_H
#include <esp8266.h>
#include "config_xmacros.h"
#include "cgi_wifi.h"
#define SSID_LEN 32
@ -19,6 +20,44 @@
#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
* and the active state.
@ -26,25 +65,9 @@
* This block can be used eg. for WiFi config backup.
*/
typedef struct {
WIFI_MODE opmode : 8;
u8 tpw;
// 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;
#define X XSTRUCT_FIELD
XTABLE_WIFICONF
#undef X
} WiFiConfigBundle;
typedef struct {
@ -59,9 +82,17 @@ extern WiFiConfigBundle * const wificonf;
void wifimgr_restore_defaults(void);
void wifimgr_apply_settings(void);
void wifimgr_apply_settings_later(uint32_t delay_ms);
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
#define wifi_warn warn
#define wifi_dbg dbg

Loading…
Cancel
Save