Compare commits

..
36 Commits
Author SHA1 Message Date
MightyPork ca410736f7 removed some logging and version bump 2017-08-15 03:14:07 +02:00
MightyPork 2ca8bf1483 workaround for buggy ets_timer_disarm causing bootloop in some devices with perser tiemout > 0 2017-08-15 02:29:16 +02:00
MightyPork 35393827a9 implemented ctrl-, f- and other keys. enter now sends CR, use ctrl-enter for LF. 2017-08-15 01:04:58 +02:00
MightyPork ea84d1889d 0.6.6 2017-08-14 01:44:02 +02:00
MightyPork bc41a21f23 some more missing commands, added code pages, fixed the xterm size command and some other stuff. also FRAKTUR 2017-08-14 01:40:40 +02:00
MightyPork e48180ac10 more commands and glitches fixed, better debug messages, discard bad unicode 2017-08-13 17:30:54 +02:00
MightyPork e47a5477f9 fixed swapped 0 and 1 in clearing commands 2017-08-12 23:01:51 +02:00
MightyPork e944a994a7 version bump 2017-08-11 12:16:41 +02:00
MightyPork 6535b49551 implemented Inser and Delete lines/characters (e[ Pn L,M,@,P) 2017-08-11 11:59:50 +02:00
MightyPork 2cb8586c94 preparation for insert and delete commands 2017-08-10 01:58:10 +02:00
MightyPork ec1cebdd4c bugfixes 2017-08-10 01:52:48 +02:00
MightyPork af5a3a64ed fix bug and undo regression with cursor restore 2017-08-09 20:45:22 +02:00
MightyPork 7b94eef448 implemented a couple new ESC codes and stubs for 4 others 2017-08-09 20:41:47 +02:00
MightyPork c8067122d5 parser cleanup for adding new hash and short ESC sequences (issue #41) - EDM tbd 2017-08-07 00:48:15 +02:00
MightyPork 12385b7a5f dont clear terminal screen when not needed. also allow setting term title or buttons to empty string 2017-08-06 19:50:18 +02:00
MightyPork 5564147f67 version bump 2017-08-06 19:08:12 +02:00
MightyPork 32ba7dc0d7 fixed a lot of bugs with wifi connecting 2017-08-06 19:07:43 +02:00
MightyPork 80721c1715 allow submitting empty get args - may solve some bugs with wifi config 2017-08-06 17:56:27 +02:00
MightyPork e22d3f26d9 Added Wifi conf link to the bottom navbar, auto-expand Station config box 2017-08-06 17:53:21 +02:00
MightyPork 24a6a0dfd3 added git hash to version string 2017-08-06 14:58:46 +02:00
MightyPork 8a182f072d removed mentions of " ' > < from help, as those characters are now allowed and properly escaped everywhere 2017-08-06 14:26:54 +02:00
MightyPork 76b447d03e throttle reload nad use textarea for mobile input bc it shows better symbol for the enter button 2017-08-06 14:20:34 +02:00
MightyPork 319427ba2e the onscreen android keyboard now works 2017-08-06 14:17:05 +02:00
MightyPork f8c60b0668 parser timeout, better checksum checking and struct stuffing, shaved some bytes off RAM 2017-08-06 11:22:45 +02:00
MightyPork 4eaee9389d use html and js escapes for tpl strings 2017-08-06 02:15:31 +02:00
MightyPork 0a731bd35e fix some bugs 2017-08-06 00:24:16 +02:00
MightyPork 0b3cfe0e74 use 0x01 for seps in labels string so users can use pipe if they want to 2017-08-05 23:56:35 +02:00
MightyPork 4551ad6dbe added info about the new commands to help. also e]FRa is removed bc it is no longer useful for anything 2017-08-05 23:46:11 +02:00
MightyPork 3f4b8a94f1 commands to set button or title 2017-08-05 21:40:25 +02:00
MightyPork 373affb582 parser formatting to look lkike a huge comment to C editors 2017-08-05 18:57:57 +02:00
MightyPork b48bb12067 help page update 2017-08-05 18:20:13 +02:00
MightyPork 110f461cf5 bold works !! 2017-07-31 02:59:13 +02:00
MightyPork 3ae1451821 working unicode!!! WTF!! and ajax initial load to avoid having to escape shit 2017-07-31 02:41:35 +02:00
MightyPork d711909812 w.i.p. Terminal Themes added, improved terminal color picker, new compression in front-end 2017-07-31 00:39:46 +02:00
MightyPork 76167b8014 WiFi sections hiding, STA auto enable on scan click, Terminal colors preview, better explanations, CONFIGURABLE UART 2017-07-29 20:55:16 +02:00
MightyPork e42407ad62 fontello unpack script & improved checkbox style 2017-07-29 12:33:55 +02:00
81 changed files with 5259 additions and 1969 deletions
+9 -6
View File
@@ -86,7 +86,6 @@ set(SOURCE_FILES
esp_iot_sdk_v1.5.2/include/json/jsonparse.h
esp_iot_sdk_v1.5.2/include/json/json.h
include/uart_hw.h
include/user_config.h
include/ets_sys_extra.h
user/io.c
@@ -97,10 +96,9 @@ set(SOURCE_FILES
user/cgi_persist.h
user/cgi_network.c
user/cgi_network.h
user/cgi_appcfg.c
user/cgi_appcfg.h
user/cgi_ping.c
user/cgi_reset.c
user/cgi_term_cfg.c
user/cgi_term_cfg.h
user/cgi_system.c
user/uart_driver.c
user/uart_handler.c
user/ansi_parser.c
@@ -123,7 +121,10 @@ set(SOURCE_FILES
user/wifimgr.c
user/wifimgr.h
user/persist.c
user/persist.h include/helpers.h)
user/persist.h
include/helpers.h
user/syscfg.c
user/syscfg.h)
include_directories(include)
include_directories(user)
@@ -143,6 +144,8 @@ add_definitions(
-DICACHE_RODATA_ATTR=
-DFLAG_GZIP=2
-DADMIN_PASSWORD="asdf"
-DGIT_HASH="blabla"
-DDEBUG_ANSI=1
-DESPFS_HEATSHRINK)
add_executable(esp_vt100_firmware ${SOURCE_FILES})
+10 -2
View File
@@ -65,7 +65,15 @@ LIBS += esphttpd
# compiler flags using during compilation of source files
CFLAGS = -Os -ggdb -std=gnu99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
-nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \
-Wno-address -Wno-unused -DHTTPD_MAX_BACKLOG_SIZE=8192 -DADMIN_PASSWORD=$(ADMIN_PASSWORD)
-Wno-address -Wno-unused
CFLAGS += -DHTTPD_MAX_BACKLOG_SIZE=10240
CFLAGS += -DGIT_HASH='"$(shell git rev-parse --short HEAD)"'
CFLAGS += -DADMIN_PASSWORD=$(ADMIN_PASSWORD)
# Debug logging
CFLAGS += -DDEBUG_ANSI=1
CFLAGS += -DDEBUG_INPUT=0
# linker flags used to generate the main object file
LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static
@@ -196,7 +204,7 @@ libesphttpd/Makefile:
$(Q) [[ -e "libesphttpd/Makefile" ]] || echo -e "\e[31mlibesphttpd submodule missing.\nIf build fails, run \"git submodule init\" and \"git submodule update\".\e[0m"
libesphttpd: libesphttpd/Makefile
$(Q) make -C libesphttpd USE_OPENSDK=$(USE_OPENSDK)
$(Q) make -C libesphttpd USE_OPENSDK=$(USE_OPENSDK) SERVERNAME_PREFIX="ESPTerm "
$(APP_AR): libesphttpd $(OBJ)
$(vecho) "AR $@"
+57 -45
View File
@@ -3,60 +3,72 @@
/**
* Those replacements are done by the development server to test it locally
* without esphttpd. This is needed mainly for places where the replacements
* are given to JavaScript, to avoid syntax errors with %%
* are given to JavaScript, to avoid syntax errors with
*/
return [
'%term_title%' => 'ESP8266 Wireless Terminal',
'term_title' => 'ESPTerm local debug',
'%btn1%' => '1',
'%btn2%' => '2',
'%btn3%' => '3',
'%btn4%' => '4',
'%btn5%' => '5',
'btn1' => '1',
'btn2' => '2',
'btn3' => '3',
'btn4' => '',
'btn5' => '5',
'labels_seq' => 'TESPTerm local debug1235',
'%screenData%' => '{
'screenData' => '  HELLOx NRE3',//'\u000b\u0001\u001b\u0001\u0001\u0001\u0001\u0001\f\u0005\u0001\u0010\u0003HELLOx\u0002\u000b\u0001\u0001N\u0001RE\u00023\u0001', //,
/*'{
"w": 26, "h": 10,
"x": 0, "y": 0,
"cv": 1,
"screen": "70 t259"
}',
"x": 10, "y": 5,
"cv": 1, "fg": 10, "bg": 2,
"screen": ""
}',//"70 t259"*/
'%ap_enable%' => '1',
'%tpw%' => '60',
'%ap_channel%' => '7',
'%ap_ssid%' => 'ESP-123456',
'%ap_password%' => 'Passw0rd!',
'%ap_hidden%' => '0',
'%sta_ssid%' => 'Chlivek',
'%sta_password%' => 'windows XP is The Best',
'%sta_active_ip%' => '1.2.3.4',
'%sta_active_ssid%' => 'Chlivek',
'parser_tout_ms' => 10,
'%sta_enable%' => '1',
'%opmode%' => '3',
'%vers_fw%' => '1.2.3',
'%date%' => date('Y-m-d'),
'%time%' => date('G:i'),
'%vers_httpd%' => '4.5.6',
'%vers_sdk%' => '1.52',
'%githubrepo%' => 'https://github.com/MightyPork/esp-vt100-firmware',
'opmode' => '2',
'sta_enable' => '0',
'ap_enable' => '1',
'%ap_dhcp_time%' => '120',
'%ap_dhcp_start%' => '192.168.4.100',
'%ap_dhcp_end%' => '192.168.4.200',
'%ap_addr_ip%' => '192.168.4.1',
'%ap_addr_mask%' => '255.255.255.0',
'tpw' => '60',
'ap_channel' => '7',
'ap_ssid' => 'ESP-123456',
'ap_password' => 'Passw0rd!',
'ap_hidden' => '0',
'%sta_dhcp_enable%' => '1',
'%sta_addr_ip%' => '192.168.0.33',
'%sta_addr_mask%' => '255.255.255.0',
'%sta_addr_gw%' => '192.168.0.1',
'sta_ssid' => 'Chlivek',
'sta_password' => 'windows XP is The Best',
'sta_active_ip' => '1.2.3.4',
'sta_active_ssid' => 'Chlivek',
'%sta_mac%' => 'ab:cd:ef:01:23:45',
'%ap_mac%' => '01:23:45:ab:cd:ef',
'vers_fw' => '1.2.3',
'date' => date('Y-m-d'),
'time' => date('G:i'),
'vers_httpd' => '4.5.6',
'vers_sdk' => '1.52',
'githubrepo' => 'https://github.com/MightyPork/esp-vt100-firmware',
'%term_width%' => '26',
'%term_height%' => '10',
'%default_bg%' => '0',
'%default_fg%' => '7',
'ap_dhcp_time' => '120',
'ap_dhcp_start' => '192.168.4.100',
'ap_dhcp_end' => '192.168.4.200',
'ap_addr_ip' => '192.168.4.1',
'ap_addr_mask' => '255.255.255.0',
'sta_dhcp_enable' => '1',
'sta_addr_ip' => '192.168.0.33',
'sta_addr_mask' => '255.255.255.0',
'sta_addr_gw' => '192.168.0.1',
'sta_mac' => 'ab:cd:ef:01:23:45',
'ap_mac' => '01:23:45:ab:cd:ef',
'term_width' => '26',
'term_height' => '10',
'default_bg' => '0',
'default_fg' => '7',
'uart_baud' => 115200,
'uart_stopbits' => 1,
'uart_parity' => 2,
'theme' => 5,
];
+8 -6
View File
@@ -18,6 +18,9 @@ if (! function_exists('pg')) {
}
}
pg('cfg_term', 'cfg', 'terminal', '/cfg/term');
pg('term_set', 'api', '', '/cfg/term/set');
pg('cfg_wifi', 'cfg', 'wifi', '/cfg/wifi');
pg('cfg_wifi_conn', '', '', '/cfg/wifi/connecting');
pg('wifi_connstatus', 'api', '', '/cfg/wifi/connstatus');
@@ -27,13 +30,12 @@ pg('wifi_scan', 'api', '', '/cfg/wifi/scan');
pg('cfg_network', 'cfg', 'network', '/cfg/network');
pg('network_set', 'api', '', '/cfg/network/set');
pg('cfg_app', 'cfg', 'terminal', '/cfg/app');
pg('app_set', 'api', '', '/cfg/app/set');
pg('cfg_system', 'cfg', 'configure', '/cfg/system');
pg('system_set', 'api', '', '/cfg/system/set');
pg('cfg_admin', 'cfg', 'persist', '/cfg/admin');
pg('write_defaults', 'api', '', '/cfg/admin/write_defaults');
pg('restore_defaults', 'api', '', '/cfg/admin/restore_defaults');
pg('restore_hard', 'api', '', '/cfg/admin/restore_hard');
pg('write_defaults', 'api', '', '/cfg/system/write_defaults');
pg('restore_defaults', 'api', '', '/cfg/system/restore_defaults');
pg('restore_hard', 'api', '', '/cfg/system/restore_hard');
pg('help', 'cfg page-help', 'help', '/help');
pg('about', 'cfg page-about', 'about', '/about');
+513 -167
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -7,6 +7,7 @@ require_once __DIR__ . '/base.php';
$selected = [
'wifi.connected_ip_is',
'wifi.not_conn',
'wifi.enter_passwd',
];
$out = [];
+1
View File
@@ -0,0 +1 @@
/out/
Binary file not shown.
Binary file not shown.
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
# resolve current file's directory
DIR=$(dirname $(realpath $0))
OUTPUT_DIR="$DIR/out"
SASS_DIR="$DIR/../sass"
ICON_PREFIX='icn'
# list with full paths, sort from newest
NEWEST=$(ls -dt1 "$DIR"/*.zip | head -1)
if [[ -z "$NEWEST" ]]; then
echo "Fontello zip not found."
exit 1
fi
# Clean the output folder
rm -rf "$OUTPUT_DIR"
mkdir -p "$OUTPUT_DIR"
echo "Unpacking fontello..."
unzip -ju "$NEWEST" -d "$OUTPUT_DIR"
echo "Patching paths in the fontello CSS..."
# Fix bad relative paths in the CSS
sed -i "s|\.\./font/|/fonts/|g" "$OUTPUT_DIR/"*.css
echo "Generating SASS file with icon codes..."
SASSFILE="$SASS_DIR/_fontello.scss"
echo -e "@charset \"UTF-8\";\n\n/* Fontello data, processed by the unpack script. */\n" > "$SASSFILE"
# Extract the base font-face style
#grep -Pazo "(?s)@font-face.*?normal;\n\}" "$OUTPUT_DIR/fontello.css" \
# | sed 's/\x0//g' >> "$SASSFILE"
grep -Pazo "(?s)@font-face \{\n\s*font-family: 'fontello';\n\s*src: url\('data.*?truetype'\);\n\}" "$OUTPUT_DIR/fontello-embedded.css" \
| sed 's/\x0//g' >> "$SASSFILE"
grep -Pazo "(?s)$ICON_PREFIX-\"\]:before .*?\}" "$OUTPUT_DIR/fontello.css" \
| sed 's/\x0//g' \
| sed "s/$ICON_PREFIX-\"\]:before/\n\n%fontello-icon-base \{\n\&::before /g" \
>> "$SASSFILE"
echo -e "\n}" >> "$SASSFILE"
echo -e "\n\n/* Fontello icon codes */" >> "$SASSFILE"
echo -n "\$icon-codes: (" >> "$SASSFILE"
sed -r "s|\.$ICON_PREFIX-([a-z0-9-]+):before \{ content: ('.*?');.*?$|\t\1: \2,|g" "$OUTPUT_DIR/fontello-codes.css" \
| sed -r "s|@.*||g" >> "$SASSFILE"
echo -ne "\n);\n" >> "$SASSFILE"
echo -ne "\n/* Fontello classes */" >> "$SASSFILE"
cat "$OUTPUT_DIR/fontello-codes.css" \
| sed -r 's/\/\*.+\*\///g' \
| sed -r "s|@.*||g" \
| sed 's/:before/::before/g' >> "$SASSFILE"
TAIL=$(cat <<ASDF
[class^="$ICON_PREFIX-"], [class*=" $ICON_PREFIX-"] {
@extend %fontello-icon-base;
}
@mixin icon-base() {
@extend %fontello-icon-base;
}
@mixin icon-content(\$icon-name) {
&::before {
content: map-get(\$icon-codes, \$icon-name);
}
}
@mixin icon(\$icon-name) {
@include icon-base();
@include icon-content(\icon-name);
}
ASDF
)
echo "$TAIL" >> "$SASSFILE"
echo -e "\e[32mFontello ready\e[0m"
+34 -2
View File
@@ -7,12 +7,44 @@ if (!isset($_GET['page'])) $_GET['page'] = 'term';
$_GET['PAGE_TITLE'] = $_pages[$_GET['page']]->title . ' :: ' . APP_NAME;
$_GET['BODYCLASS'] = $_pages[$_GET['page']]->bodyclass;
if (!function_exists('tplSubs')) {
function tplSubs($str, $reps)
{
return preg_replace_callback('/%(j:|js:|h:|html:)?([a-z0-9-_.]+)%/i', function ($m) use ($reps) {
$key = $m[2];
if (array_key_exists($key, $reps)) {
$val = $reps[$key];
} else {
$val = '';
}
switch ($m[1]) {
case 'j:':
case 'js:':
$v = json_encode($val);
return substr($v, 1, strlen($v) - 2);
case 'h:':
case 'html:':
return htmlspecialchars($val);
default:
return $val;
}
}, $str);
}
}
require __DIR__ . '/pages/_head.php';
$_pf = __DIR__ . '/pages/'.$_GET['page'].'.php';
if (file_exists($_pf)) {
$f = file_get_contents($_pf);
$reps = DEBUG ? require(__DIR__ . '/_debug_replacements.php') : [];
$str = str_replace(array_keys($reps), array_values($reps), $f);
if (DEBUG)
$str = tplSubs($f, require(__DIR__ . '/_debug_replacements.php'));
else $str = $f;
// special symbols
$str = str_replace('\,', '&#8239;', $str);
$str = preg_replace('/(?<=\w)~(?=\w)/', '&nbsp;', $str);
include_str($str);
} else {
echo "404";
+701 -184
View File
File diff suppressed because it is too large Load Diff
+80 -1
View File
@@ -100,11 +100,90 @@ $.ready(function () {
x.removeAttribute('tabindex');
});
// brand works as a link back to term in widescreen mode
var br = qs('#brand');
br && br.removeAttribute('tabindex');
br && br.addEventListener('click', function() {
location.href='/'; // go to terminal
});
}
});
$._loader = function(vis) {
$('#loader').toggleClass('show', vis);
};
function showPage() {
$('#content').addClass('load');
}
$.ready(function() {
if (window.noAutoShow !== true) {
setTimeout(function () {
showPage();
}, 1);
}
});
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
if (!String.fromCodePoint) {
(function() {
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch(error) {}
return result;
}());
var stringFromCharCode = String.fromCharCode;
var floor = Math.floor;
var fromCodePoint = function() {
var MAX_SIZE = 0x4000;
var codeUnits = [];
var highSurrogate;
var lowSurrogate;
var index = -1;
var length = arguments.length;
if (!length) {
return '';
}
var result = '';
while (++index < length) {
var codePoint = Number(arguments[index]);
if (
!isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
codePoint < 0 || // not a valid Unicode code point
codePoint > 0x10FFFF || // not a valid Unicode code point
floor(codePoint) != codePoint // not an integer
) {
throw RangeError('Invalid code point: ' + codePoint);
}
if (codePoint <= 0xFFFF) { // BMP code point
codeUnits.push(codePoint);
} else { // Astral code point; split in surrogate halves
// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
codePoint -= 0x10000;
highSurrogate = (codePoint >> 10) + 0xD800;
lowSurrogate = (codePoint % 0x400) + 0xDC00;
codeUnits.push(highSurrogate, lowSurrogate);
}
if (index + 1 == length || codeUnits.length > MAX_SIZE) {
result += stringFromCharCode.apply(null, codeUnits);
codeUnits.length = 0;
}
}
return result;
};
if (defineProperty) {
defineProperty(String, 'fromCodePoint', {
'value': fromCodePoint,
'configurable': true,
'writable': true
});
} else {
String.fromCodePoint = fromCodePoint;
}
}());
}
+1 -1
View File
@@ -488,7 +488,7 @@
// Multiple select
for (j = 0; j < value.length; j += 1) {
elm[i].selected = '';
if (elm[i].value === value[j]) {
if (elm[i].value === ""+value[j]) {
elm[i].selected = 'selected';
break;
}
+296
View File
@@ -0,0 +1,296 @@
// keymaster.js
// (c) 2011-2013 Thomas Fuchs
// keymaster.js may be freely distributed under the MIT license.
;(function(global){
var k,
_handlers = {},
_mods = { 16: false, 18: false, 17: false, 91: false },
_scope = 'all',
// modifier keys
_MODIFIERS = {
'⇧': 16, shift: 16,
'⌥': 18, alt: 18, option: 18,
'⌃': 17, ctrl: 17, control: 17,
'⌘': 91, command: 91
},
// special keys
_MAP = {
backspace: 8, tab: 9, clear: 12,
enter: 13, 'return': 13,
esc: 27, escape: 27, space: 32,
left: 37, up: 38,
right: 39, down: 40,
del: 46, 'delete': 46,
home: 36, end: 35,
pageup: 33, pagedown: 34, insert: 45, // added insert
',': 188, '.': 190, '/': 191,
'`': 192, '-': 189, '=': 187,
';': 186, '\'': 222,
'[': 219, ']': 221, '\\': 220
},
code = function(x){
return _MAP[x] || x.toUpperCase().charCodeAt(0);
},
_downKeys = [];
for(k=1;k<20;k++) _MAP['f'+k] = 111+k;
// IE doesn't support Array#indexOf, so have a simple replacement
function index(array, item){
var i = array.length;
while(i--) if(array[i]===item) return i;
return -1;
}
// for comparing mods before unassignment
function compareArray(a1, a2) {
if (a1.length != a2.length) return false;
for (var i = 0; i < a1.length; i++) {
if (a1[i] !== a2[i]) return false;
}
return true;
}
var modifierMap = {
16:'shiftKey',
18:'altKey',
17:'ctrlKey',
91:'metaKey'
};
function updateModifierKey(event) {
for(k in _mods) _mods[k] = event[modifierMap[k]];
};
// handle keydown event
function dispatch(event) {
var key, handler, k, i, modifiersMatch, scope;
key = event.keyCode;
if (index(_downKeys, key) == -1) {
_downKeys.push(key);
}
// if a modifier key, set the key.<modifierkeyname> property to true and return
if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
if(key in _mods) {
_mods[key] = true;
// 'assignKey' from inside this closure is exported to window.key
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
return;
}
updateModifierKey(event);
// see if we need to ignore the keypress (filter() can can be overridden)
// by default ignore key presses if a select, textarea, or input is focused
if(!assignKey.filter.call(this, event)) return;
// abort if no potentially matching shortcuts found
if (!(key in _handlers)) return;
scope = getScope();
// for each potential shortcut
for (i = 0; i < _handlers[key].length; i++) {
handler = _handlers[key][i];
// see if it's in the current scope
if(handler.scope == scope || handler.scope == 'all'){
// check if modifiers match if any
modifiersMatch = handler.mods.length > 0;
for(k in _mods)
if((!_mods[k] && index(handler.mods, +k) > -1) ||
(_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
// call the handler and stop the event if neccessary
if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
if(handler.method(event, handler)===false){
if(event.preventDefault) event.preventDefault();
else event.returnValue = false;
if(event.stopPropagation) event.stopPropagation();
if(event.cancelBubble) event.cancelBubble = true;
}
}
}
}
};
// unset modifier keys on keyup
function clearModifier(event){
var key = event.keyCode, k,
i = index(_downKeys, key);
// remove key from _downKeys
if (i >= 0) {
_downKeys.splice(i, 1);
}
if(key == 93 || key == 224) key = 91;
if(key in _mods) {
_mods[key] = false;
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
}
};
function resetModifiers() {
for(k in _mods) _mods[k] = false;
for(k in _MODIFIERS) assignKey[k] = false;
};
// parse and assign shortcut
function assignKey(key, scope, method){
var keys, mods;
keys = getKeys(key);
if (method === undefined) {
method = scope;
scope = 'all';
}
// for each shortcut
for (var i = 0; i < keys.length; i++) {
// set modifier keys if any
mods = [];
key = keys[i].split('+');
if (key.length > 1){
mods = getMods(key);
key = [key[key.length-1]];
}
// convert to keycode and...
key = key[0]
key = code(key);
// ...store handler
if (!(key in _handlers)) _handlers[key] = [];
_handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
}
};
// unbind all handlers for given key in current scope
function unbindKey(key, scope) {
var multipleKeys, keys,
mods = [],
i, j, obj;
multipleKeys = getKeys(key);
for (j = 0; j < multipleKeys.length; j++) {
keys = multipleKeys[j].split('+');
if (keys.length > 1) {
mods = getMods(keys);
}
key = keys[keys.length - 1];
key = code(key);
if (scope === undefined) {
scope = getScope();
}
if (!_handlers[key]) {
return;
}
for (i = 0; i < _handlers[key].length; i++) {
obj = _handlers[key][i];
// only clear handlers if correct scope and mods match
if (obj.scope === scope && compareArray(obj.mods, mods)) {
_handlers[key][i] = {};
}
}
}
};
// Returns true if the key with code 'keyCode' is currently down
// Converts strings into key codes.
function isPressed(keyCode) {
if (typeof(keyCode)=='string') {
keyCode = code(keyCode);
}
return index(_downKeys, keyCode) != -1;
}
function getPressedKeyCodes() {
return _downKeys.slice(0);
}
function filter(event){
var tagName = (event.target || event.srcElement).tagName;
// ignore keypressed in any elements that support keyboard data input
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
}
// initialize key.<modifier> to false
for(k in _MODIFIERS) assignKey[k] = false;
// set current scope (default 'all')
function setScope(scope){ _scope = scope || 'all' };
function getScope(){ return _scope || 'all' };
// delete all handlers for a given scope
function deleteScope(scope){
var key, handlers, i;
for (key in _handlers) {
handlers = _handlers[key];
for (i = 0; i < handlers.length; ) {
if (handlers[i].scope === scope) handlers.splice(i, 1);
else i++;
}
}
};
// abstract key logic for assign and unassign
function getKeys(key) {
var keys;
key = key.replace(/\s/g, '');
keys = key.split(',');
if ((keys[keys.length - 1]) == '') {
keys[keys.length - 2] += ',';
}
return keys;
}
// abstract mods logic for assign and unassign
function getMods(key) {
var mods = key.slice(0, key.length - 1);
for (var mi = 0; mi < mods.length; mi++)
mods[mi] = _MODIFIERS[mods[mi]];
return mods;
}
// cross-browser events
function addEvent(object, event, method) {
if (object.addEventListener)
object.addEventListener(event, method, false);
else if(object.attachEvent)
object.attachEvent('on'+event, function(){ method(window.event) });
};
// set the handlers globally on document
addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
addEvent(document, 'keyup', clearModifier);
// reset modifiers to false whenever the window is (re)focused.
addEvent(window, 'focus', resetModifiers);
// store previously defined key
var previousKey = global.key;
// restore previously defined key and return reference to our key object
function noConflict() {
var k = global.key;
global.key = previousKey;
return k;
}
// set window.key and window.key.set/get/deleteScope, and the default filter
global.key = assignKey;
global.key.setScope = setScope;
global.key.getScope = getScope;
global.key.deleteScope = deleteScope;
global.key.filter = filter;
global.key.isPressed = isPressed;
global.key.getPressedKeyCodes = getPressedKeyCodes;
global.key.noConflict = noConflict;
global.key.unbind = unbindKey;
if(typeof module !== 'undefined') module.exports = assignKey;
})(this);
+2 -1
View File
@@ -1,7 +1,8 @@
// Generated from PHP locale file
var _tr = {
"wifi.connected_ip_is": "Connected, IP is ",
"wifi.not_conn": "Not connected."
"wifi.not_conn": "Not connected.",
"wifi.enter_passwd": "Enter password for \":ssid:\""
};
function tr(key) { return _tr[key] || '?'+key+'?'; }
+312 -149
View File
@@ -1,138 +1,102 @@
(function() {
/**
* Terminal module
*/
var Term = (function () {
var W, H;
var cursor = {a: false, x: 0, y: 0, suppress: false, hidden: false};
var Screen = (function () {
var W = 0, H = 0; // dimensions
var inited = false;
var cursor = {
a: false, // active (blink state)
x: 0, // 0-based coordinates
y: 0,
fg: 7, // colors 0-15
bg: 0,
attrs: 0,
suppress: false, // do not turn on in blink interval (for safe moving)
hidden: false // do not show
};
var screen = [];
var blinkIval;
/*
/!** Clear screen *!/
function cls() {
screen.forEach(function(cell, i) {
cell.t = ' ';
cell.fg = 7;
cell.bg = 0;
blit(cell);
});
}
*/
/** Set text and color at XY */
function cellAt(y, x) {
return screen[y*W+x];
}
var frakturExceptions = {
'C': '\u212d',
'H': '\u210c',
'I': '\u2111',
'R': '\u211c',
'Z': '\u2128',
};
/** Get cell under cursor */
function cursorCell() {
return cellAt(cursor.y, cursor.x);
function _curCell() {
return screen[cursor.y*W + cursor.x];
}
/*
/!** Enable or disable cursor visibility *!/
function cursorEnable(enable) {
cursor.hidden = !enable;
cursor.a &= enable;
blit(cursorCell(), cursor.a);
}
/!** Safely move cursor *!/
/** Safely move cursor */
function cursorSet(y, x) {
// Hide and prevent from showing up during the move
cursor.suppress = true;
blit(cursorCell(), false);
_draw(_curCell(), false);
cursor.x = x;
cursor.y = y;
// Show again
cursor.suppress = false;
blit(cursorCell(), cursor.a);
_draw(_curCell());
}
*/
/** Update cell on display. inv = invert (for cursor) */
function blit(cell, inv) {
var e = cell.e, fg, bg;
function _draw(cell, inv) {
if (typeof inv == 'undefined') {
inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y;
}
var elem = cell.e, fg, bg, cn, t;
// Colors
fg = inv ? cell.bg : cell.fg;
bg = inv ? cell.fg : cell.bg;
// Update
e.innerText = (cell.t+' ')[0];
e.className = 'fg'+fg+' bg'+bg;
elem.textContent = t = (cell.t + ' ')[0];
cn = 'fg' + fg + ' bg' + bg;
if (cell.attrs & (1<<0)) cn += ' bold';
if (cell.attrs & (1<<2)) cn += ' italic';
if (cell.attrs & (1<<3)) cn += ' under';
if (cell.attrs & (1<<4)) cn += ' blink';
if (cell.attrs & (1<<5)) {
cn += ' fraktur';
// perform substitution
if (t >= 'a' && t <= 'z') {
t = String.fromCodePoint(0x1d51e - 97 + t.charCodeAt(0));
}
else if (t >= 'A' && t <= 'Z') {
// this set is incomplete, some exceptions are needed
if (frakturExceptions.hasOwnProperty(t)) {
t = frakturExceptions[t];
} else {
t = String.fromCodePoint(0x1d504 - 65 + t.charCodeAt(0));
}
}
elem.textContent = t;
}
if (cell.attrs & (1<<6)) cn += ' strike';
if (cell.attrs & (1<<1)) {
cn += ' faint';
// faint requires special html - otherwise it would also dim the background.
// we use opacity on the text...
elem.innerHTML = '<span>' + e(elem.textContent) + '</span>';
}
elem.className = cn;
}
/** Show entire screen */
function blitAll() {
screen.forEach(function(cell, i) {
/* Invert if under cursor & cursor active */
var inv = cursor.a && (i == cursor.y*W+cursor.x);
blit(cell, inv);
});
}
/** Load screen content from a 'binary' sequence */
function load(obj) {
cursor.x = obj.x;
cursor.y = obj.y;
cursor.hidden = !obj.cv;
// full re-init if size changed
if (obj.w != W || obj.h != H) {
Term.init(obj);
return;
}
// Simple compression - hexFG hexBG 'ASCII' (r/s/t/u NUM{1,2,3,4})?
// comma instead of both colors = same as before
var i = 0, ci = 0, str = obj.screen;
var fg = 7, bg = 0;
while(i < str.length && ci<W*H) {
var cell = screen[ci++];
var j = str[i];
if (j != ',') { // comma = repeat last colors
fg = cell.fg = parseInt(str[i++], 16);
bg = cell.bg = parseInt(str[i++], 16);
} else {
i++;
cell.fg = fg;
cell.bg = bg;
}
var t = cell.t = str[i++];
var repchars = 0;
switch(str[i]) {
case 'r': repchars = 1; break;
case 's': repchars = 2; break;
case 't': repchars = 3; break;
case 'u': repchars = 4; break;
default: repchars = 0;
}
if (repchars > 0) {
var rep = parseInt(str.substr(i+1,repchars));
i = i + repchars + 1;
for (; rep>0 && ci<W*H; rep--) {
cell = screen[ci++];
cell.fg = fg;
cell.bg = bg;
cell.t = t;
}
function _drawAll() {
for (var i = W*H-1; i>=0; i--) {
_draw(screen[i]);
}
}
blitAll();
}
/** Init the terminal */
function init(obj) {
W = obj.w;
H = obj.h;
function _rebuild(rows, cols) {
W = cols;
H = rows;
/* Build screen & show */
var e, cell, scr = qs('#screen');
@@ -160,11 +124,22 @@
/* The cell */
scr.appendChild(e);
cell = {t: ' ', fg: 7, bg: 0, e: e};
cell = {
t: ' ',
fg: cursor.fg,
bg: cursor.bg,
attrs: 0,
e: e,
x: i % W,
y: Math.floor(i / W),
};
screen.push(cell);
blit(cell);
_draw(cell);
}
}
/** Init the terminal */
function _init() {
/* Cursor blinking */
clearInterval(blinkIval);
blinkIval = setInterval(function () {
@@ -174,17 +149,138 @@
}
if (!cursor.suppress) {
blit(cursorCell(), cursor.a);
_draw(_curCell(), cursor.a);
}
}, 500);
load(obj);
// blink attribute
setInterval(function () {
$('#screen').removeClass('blink-hide');
setTimeout(function() {
$('#screen').addClass('blink-hide');
}, 800); // 200 ms ON
}, 1000);
inited = true;
}
/** Decode two-byte number */
function parse2B(s, i) {
return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127;
}
/** Decode three-byte number */
function parse3B(s, i) {
return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127;
}
var SEQ_SET_COLOR_ATTR = 1;
var SEQ_REPEAT = 2;
var SEQ_SET_COLOR = 3;
var SEQ_SET_ATTR = 4;
function _load_content(str) {
var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell;
if (!inited) _init();
// Set size
num = parse2B(str, i); i += 2; // height
num2 = parse2B(str, i); i += 2; // width
if (num != H || num2 != W) {
_rebuild(num, num2);
}
// console.log("Size ",num, num2);
// Cursor position
num = parse2B(str, i); i += 2; // row
num2 = parse2B(str, i); i += 2; // col
cursorSet(num, num2);
// console.log("Cursor at ",num, num2);
// Attributes
num = parse2B(str, i); i += 2; // fg bg bold hidden
cursor.fg = num & 0x0F;
cursor.bg = (num & 0xF0) >> 4;
cursor.hidden = !(num & 0x100);
fg = cursor.fg;
bg = cursor.bg;
attrs = 0;
// Here come the content
while(i < str.length && ci<W*H) {
j = str[i++];
jc = j.charCodeAt(0);
if (jc == SEQ_SET_COLOR_ATTR) {
num = parse3B(str, i); i += 3;
fg = num & 0x0F;
bg = (num & 0xF0) >> 4;
attrs = (num & 0xFF00)>>8;
}
else if (jc == SEQ_SET_COLOR) {
num = parse2B(str, i); i += 2;
fg = num & 0x0F;
bg = (num & 0xF0) >> 4;
}
else if (jc == SEQ_SET_ATTR) {
num = parse2B(str, i); i += 2;
attrs = num & 0xFF;
}
else if (jc == SEQ_REPEAT) {
num = parse2B(str, i); i += 2;
// console.log("Repeat x ",num);
for (; num>0 && ci<W*H; num--) {
cell = screen[ci++];
cell.fg = fg;
cell.bg = bg;
cell.t = t;
cell.attrs = attrs;
}
}
else {
cell = screen[ci++];
// Unique cell character
t = cell.t = j;
cell.fg = fg;
cell.bg = bg;
cell.attrs = attrs;
// console.log("Symbol ", j);
}
}
_drawAll();
}
function _load_labels(str) {
var pieces = str.split('\x01');
qs('h1').textContent = pieces[0];
qsa('#buttons button').forEach(function(x, i) {
var s = pieces[i+1].trim();
// if empty string, use the "dim" effect and put nbsp instead to stretch the btn vertically
x.innerHTML = s.length >0 ? e(s) : "&nbsp;";
x.style.opacity = s.length > 0 ? 1 : 0.2;
});
}
/** Load screen content from a binary sequence (new) */
function load(str) {
var content = str.substr(1);
switch(str.charAt(0)) {
case 'S':
_load_content(content);
break;
case 'T':
_load_labels(content);
break;
default:
console.warn("Bad data message type, ignoring.");
}
}
// publish
return {
init: init,
load: load
load: load, // full load (string)
};
})();
@@ -200,14 +296,16 @@
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
setTimeout(function() {
init();
}, 1000);
}, 200);
// this happens when the buffer gets fucked up via invalid unicode.
// we basically use polling instead of socket then
}
function onMessage(evt) {
try {
console.log("RX: ", evt.data);
//console.log("RX: ", evt.data);
// Assume all our messages are screen updates
Term.load(JSON.parse(evt.data));
Screen.load(evt.data);
} catch(e) {
console.error(e);
}
@@ -215,6 +313,7 @@
function doSend(message) {
console.log("TX: ", message);
if (!ws) return; // for dry testing
if (ws.readyState != 1) {
console.error("Socket not ready");
return;
@@ -226,12 +325,21 @@
}
function init() {
ws = new WebSocket("ws://"+_root+"/ws/update.cgi");
ws = new WebSocket("ws://"+_root+"/term/update.ws");
ws.onopen = onOpen;
ws.onclose = onClose;
ws.onmessage = onMessage;
console.log("Opening socket.");
// Ask for initial data
$.get('http://'+_root+'/term/init', function(resp, status) {
if (status !== 200) location.reload(true);
console.log("Data received!");
Screen.load(resp);
showPage();
});
}
return {
@@ -241,9 +349,7 @@
};
})();
//
// Keyboard (& mouse) input
//
/** User input */
var Input = (function() {
function sendStrMsg(str) {
Conn.send("STR:"+str);
@@ -257,30 +363,89 @@
Conn.send("BTN:"+n);
}
function _initKeys() {
// This takes care of text characters typed
window.addEventListener('keypress', function(evt) {
var str = '';
if (evt.key) str = evt.key;
else if (evt.which) str = String.fromCodePoint(evt.which);
if (str.length>0 && str.charCodeAt(0) >= 32) {
// console.log("Typed ", str);
sendStrMsg(str);
}
});
var keymap = {
'tab': '\x09',
'backspace': '\x08',
'enter': '\x0d',
'ctrl+enter': '\x0a',
'esc': '\x1b',
'up': '\x1b[A',
'down': '\x1b[B',
'right': '\x1b[C',
'left': '\x1b[D',
'home': '\x1b[1~',
'insert': '\x1b[2~',
'delete': '\x1b[3~',
'end': '\x1b[4~',
'pageup': '\x1b[5~',
'pagedown': '\x1b[6~',
'f1': '\x1b[11~',
'f2': '\x1b[12~',
'f3': '\x1b[13~',
'f4': '\x1b[14~',
'f5': '\x1b[15~', // disconnect
'f6': '\x1b[17~',
'f7': '\x1b[18~',
'f8': '\x1b[19~',
'f9': '\x1b[20~',
'f10': '\x1b[21~', // disconnect
'f11': '\x1b[23~',
'f12': '\x1b[24~',
'shift+f1': '\x1b[25~',
'shift+f2': '\x1b[26~', // disconnect
'shift+f3': '\x1b[28~',
'shift+f4': '\x1b[29~', // disconnect
'shift+f5': '\x1b[31~',
'shift+f6': '\x1b[32~',
'shift+f7': '\x1b[33~',
'shift+f8': '\x1b[34~',
};
function bind(combo, str) {
// mac fix - allow also cmd
if (combo.indexOf('ctrl+') !== -1) {
combo += ',' + combo.replace('ctrl', 'command');
}
key(combo, function (e) {
e.preventDefault();
// console.log(combo);
sendStrMsg(str)
});
}
for (var k in keymap) {
if (keymap.hasOwnProperty(k)) {
bind(k, keymap[k]);
}
}
// ctrl-letter codes are sent as simple low ASCII codes
for (var i = 1; i<=26;i++) {
bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i));
}
bind('ctrl+]', '\x1b'); // alternate way to enter ESC
bind('ctrl+\\', '\x1c');
bind('ctrl+[', '\x1d');
bind('ctrl+^', '\x1e');
bind('ctrl+_', '\x1f');
}
function init() {
window.addEventListener('keypress', function(e) {
var code = +e.which;
if (code >= 32 && code < 127) {
var ch = String.fromCharCode(code);
//console.log("Typed ", ch, "code", code, e);
sendStrMsg(ch);
}
});
window.addEventListener('keydown', function(e) {
var code = e.keyCode;
//console.log("Down ", code, e);
switch(code) {
case 8: sendStrMsg('\x08'); break;
case 13: sendStrMsg('\x0d\x0a'); break;
case 27: sendStrMsg('\x1b'); break; // this allows to directly enter control sequences
case 37: sendStrMsg('\x1b[D'); break;
case 38: sendStrMsg('\x1b[A'); break;
case 39: sendStrMsg('\x1b[C'); break;
case 40: sendStrMsg('\x1b[B'); break;
}
});
_initKeys();
// Button presses
qsa('#buttons button').forEach(function(s) {
s.addEventListener('click', function() {
sendBtnMsg(+this.dataset['n']);
@@ -290,14 +455,12 @@
return {
init: init,
onTap: sendPosMsg
onTap: sendPosMsg,
sendString: sendStrMsg,
};
})();
window.termInit = function (obj) {
Term.init(obj);
window.termInit = function () {
Conn.init();
Input.init();
};
})();
+10 -32
View File
@@ -23,21 +23,6 @@
$('#sta-nw .ip').html(ip.length>0 ? tr('wifi.connected_ip_is')+ip : tr('wifi.not_conn'));
}
function submitPskModal(e, open) {
var passwd = $('#conn-passwd').val();
var ssid = $('#conn-ssid').val();
if (open || passwd.length) {
$('#sta_password').val(passwd);
$('#sta_ssid').val(ssid);
selectSta(ssid, passwd, '');
}
if (e) e.preventDefault();
Modal.hide('#psk-modal');
return false;
}
/** Update display for received response */
function onScan(resp, status) {
//var ap_json = {
@@ -83,7 +68,6 @@
ap.enc = parseInt(ap.enc);
if (ap.enc > 4) return; // hide unsupported auths
WiFi.scan_url = '/cfg/wifi/scan';
var item = mk('div');
@@ -107,19 +91,18 @@
$item.on('click', function () {
var $th = $(this);
var ssid = $th.data('ssid');
$('#conn-ssid').val(ssid);
$('#conn-passwd').val('');
var conn_ssid = $th.data('ssid');
var conn_pass = '';
if (+$th.data('pwd')) {
// this AP needs a password
Modal.show('#psk-modal');
$('#conn-passwd')[0].focus();
} else {
//Modal.show('#reset-modal');
submitPskModal(null, true);
conn_pass = prompt(tr("wifi.enter_passwd").replace(":ssid:", conn_ssid));
if (!conn_pass) return;
}
$('#sta_password').val(conn_pass);
$('#sta_ssid').val(conn_ssid);
selectSta(conn_ssid, conn_pass, '');
});
@@ -132,12 +115,13 @@
$('#ap-loader').removeClass('hidden');
$('#ap-scan').addClass('hidden');
$('#ap-loader .anim-dots').html('.');
scanAPs();
}
/** Ask the CGI what APs are visible (async) */
function scanAPs() {
$.get('http://'+_root+w.scan_url, onScan);
$.get('http://'+_root+'/cfg/wifi/scan', onScan);
}
function rescan(time) {
@@ -146,12 +130,6 @@
/** Set up the WiFi page */
function wifiInit(cfg) {
// Hide what should be hidden in this mode
cfg.mode = +cfg.mode;
$('#ap-noscan').toggleClass('hidden', cfg.mode != 2);
$('#ap-scan').toggleClass('hidden', cfg.mode == 2);
// Update slider value displays
$('.Row.range').forEach(function(x) {
var inp = x.querySelector('input');
+73 -50
View File
@@ -4,70 +4,68 @@ return [
'appname' => 'ESPTerm',
'menu.cfg_wifi' => 'WiFi Settings',
'menu.cfg_network' => 'Network Configuration',
'menu.cfg_app' => 'Terminal Settings',
'menu.cfg_network' => 'Network Settings',
'menu.cfg_term' => 'Terminal Settings',
'menu.about' => 'About ESPTerm',
'menu.help' => 'Quick Reference',
'menu.term' => 'Back to Terminal',
'menu.cfg_admin' => 'Reset & Restore',
'menu.cfg_wifi_conn' => 'Connecting to External Network',
'menu.cfg_system' => 'System Settings',
'menu.cfg_wifi_conn' => 'Connecting to Network',
'menu.settings' => 'Settings',
'title.term' => 'Terminal',
'term_nav.config' => 'Config',
'term_nav.wifi' => 'WiFi',
'term_nav.help' => 'Help',
'term_nav.about' => 'About',
'net.ap' => 'DHCP Server (AP)',
'net.sta' => 'DHCP Client (Station)',
'net.sta_mac' => 'Station MAC',
'net.ap_mac' => 'AP MAC',
'net.details' => 'MAC addresses',
'app.defaults' => 'Initial settings',
'app.explain_initials' => '
Those are the initial settings used after ESPTerm restarts, and they
will also be applied immediately after you submit this form.
They can be subsequently changed by ESC commands, but those changes
aren\'t persistent and will be lost when the device powers off.',
'term.defaults' => 'Initial Settings',
'term.explain_initials' => '
Those are the initial settings used after ESPTerm powers on.
The selected colors will also be used after receiving a
screen- or attributes-reset command. Those settings will be
applied immediately after saving to preview the changes live.',
'app.term_title' => 'Header text',
'app.term_width' => 'Screen width',
'app.term_height' => 'Screen height',
'app.default_fg' => 'Base text color',
'app.default_bg' => 'Base background',
'app.btn1' => 'Button 1 text',
'app.btn2' => 'Button 2 text',
'app.btn3' => 'Button 3 text',
'app.btn4' => 'Button 4 text',
'app.btn5' => 'Button 5 text',
'term.example' => 'Default colors preview',
'term.term_title' => 'Header text',
'term.term_width' => 'Width / height',
'term.default_fg_bg' => 'Text / background',
'term.buttons' => 'Button labels',
'term.theme' => 'Color scheme',
'term.parser_tout_ms' => 'Parser timeout',
// terminal color labels
'color.0' => 'Black',
'color.1' => 'Dark Red',
'color.2' => 'Dark Green',
'color.3' => 'Dim Yellow',
'color.4' => 'Deep Blue',
'color.5' => 'Dark Violet',
'color.6' => 'Dark Cyan',
'color.1' => 'Red',
'color.2' => 'Green',
'color.3' => 'Yellow',
'color.4' => 'Blue',
'color.5' => 'Purple',
'color.6' => 'Cyan',
'color.7' => 'Silver',
'color.8' => 'Gray',
'color.9' => 'Light Red',
'color.10' => 'Light Green',
'color.11' => 'Light Yellow',
'color.12' => 'Light Blue',
'color.13' => 'Light Violet',
'color.13' => 'Light Purple',
'color.14' => 'Light Cyan',
'color.15' => 'White',
'net.explain_sta' => '
Those settings affect the built-in DHCP client used for
connecting to an external network. Switching DHCP (dynamic IP) off
makes ESPTerm use the configured static IP. Please double-check
those settings before submitting, setting them incorrectly may
make it hard to access ESPTerm via the external network.',
Switch off Dynamic IP to configure the static IP address.',
'net.explain_ap' => '
Those settings affect the built-in DHCP server in AP mode.
Please double-check those settings before submitting, setting them
incorrectly may render ESPTerm inaccessible via the AP.',
Those settings affect the built-in DHCP server in AP mode.',
'net.ap_dhcp_time' => 'Lease time',
'net.ap_dhcp_start' => 'Pool start IP',
@@ -81,7 +79,7 @@ return [
'net.sta_addr_gw' => 'Gateway IP',
'wifi.ap' => 'Built-in Access Point',
'wifi.sta' => 'Connect to External Network',
'wifi.sta' => 'Join Existing Network',
'wifi.enable' => 'Enabled',
'wifi.tpw' => 'Transmit power',
@@ -99,36 +97,61 @@ return [
'wifi.sta_password' => 'Password:',
'wifi.scanning' => 'Scanning',
'wifi.scan_now' => 'Start scanning!',
'wifi.cant_scan_no_sta' => 'Can\'t scan with Client mode disabled.',
'wifi.scan_now' => 'Click here to start scanning!',
'wifi.cant_scan_no_sta' => 'Click here to enable client mode and start scanning!',
'wifi.select_ssid' => 'Available networks:',
'wifi.enter_passwd' => 'Enter password for ":ssid:"',
'wifi.sta_explain' => 'After selecting a network, press Apply to connect.',
'wifi.conn.status' => 'Status:',
'wifi.conn.back_to_config' => 'Back to WiFi config',
'wifi.conn.telemetry_lost' => 'Telemetry lost, something went wrong. Try again...',
'wifi.conn.telemetry_lost' => 'Telemetry lost; something went wrong, or your device disconnected.',
'wifi.conn.explain_android_sucks' => '
If you\'re configuring ESPTerm via a smartphone, or were connected
from another external network, your device may lose connection and this
progress indicator won\'t work. Please wait a while (~ 15 seconds),
then check if the connection succeeded.',
'wifi.conn.explain_reset' => '
To force enable the built-in AP, hold the BOOT
button until the blue LED starts flashing. Hold the button longer (until the LED
flashes rapidly) for a "factory reset".',
'wifi.conn.disabled' =>"Station mode is disabled.",
'wifi.conn.idle' =>"Idle, not connected and with no IP.",
'wifi.conn.idle' =>"Idle, not connected and has no IP.",
'wifi.conn.success' => "Connected! Received IP ",
'wifi.conn.working' => "Connecting to selected AP",
'wifi.conn.fail' => "Connection failed, check settings & try again. Cause: ",
'admin.confirm_restore' => 'Restore all settings to their default values?',
'admin.confirm_restore_hard' =>
'system.save_restore' => 'Save & Restore',
'system.confirm_restore' => 'Restore all settings to their default values?',
'system.confirm_restore_hard' =>
'Restore to firmware default settings? This will reset ' .
'all active settings and switch to AP mode with the default SSID.',
'admin.confirm_store_defaults' =>
'system.confirm_store_defaults' =>
'Enter admin password to confirm you want to store the current settings as defaults.',
'admin.password' => 'Admin password:',
'admin.restore_defaults' => 'Reset to default settings',
'admin.write_defaults' => 'Save current settings as default',
'admin.restore_hard' => 'Reset to firmware default settings',
'admin.explain' => '
'system.password' => 'Admin password:',
'system.restore_defaults' => 'Reset active settings to defaults',
'system.write_defaults' => 'Save active settings as defaults',
'system.restore_hard' => 'Reset active settings to firmware defaults',
'system.explain_persist' => '
ESPTerm contains two persistent memory banks, one for default and
one for active settings. Active settings can be stored as defaults
by the administrator. Use the following button to revert all
active settings to their stored default values.
by the administrator (password required).
',
'system.uart' => 'Serial Port',
'system.explain_uart' => '
This form controls the primary, communication UART. The debug UART is fixed at 115.200 baud, one stop-bit and no parity.
',
'uart.baud' => 'Baud rate',
'uart.parity' => 'Parity',
'uart.parity.none' => 'None',
'uart.parity.odd' => 'Odd',
'uart.parity.even' => 'Even',
'uart.stop_bits' => 'Stop-bits',
'uart.stop_bits.one' => 'One',
'uart.stop_bits.one_and_half' => 'One and half',
'uart.stop_bits.two' => 'Two',
'apply' => 'Apply!',
'enabled' => 'Enabled',
+1
View File
@@ -3,6 +3,7 @@
echo "Packing js..."
cat jssrc/chibi.js \
jssrc/keymaster.js \
jssrc/utils.js \
jssrc/modal.js \
jssrc/notif.js \
+1 -1
View File
@@ -20,7 +20,7 @@
<table>
<tr>
<th>ESPTerm</th>
<td>v%vers_fw%, build %date% at %time%</td>
<td>v%vers_fw%, built %date% at %time%</td>
</tr>
<tr>
<th>libesphttpd</th>
-34
View File
@@ -1,34 +0,0 @@
<div class="Box">
<div class="Row explain">
<?= tr('admin.explain') ?>
</div>
<div class="Row buttons">
<a class="button icn-restore"
onclick="return confirm('<?= tr('admin.confirm_restore') ?>');"
href="<?= e(url('restore_defaults')) ?>"
>
<?= tr('admin.restore_defaults') ?>
</a>
</div>
<div class="Row buttons">
<a onclick="writeDefaults(); return false;" href="#"><?= tr('admin.write_defaults') ?></a>
</div>
<div class="Row buttons">
<a onclick="return confirm('<?= tr('admin.confirm_restore_hard') ?>');"
href="<?= e(url('restore_hard')) ?>"
>
<?= tr('admin.restore_hard') ?>
</a>
</div>
</div>
<script>
function writeDefaults() {
var pw = prompt('<?= tr('admin.confirm_store_defaults') ?>');
if (!pw) return;
location.href = <?=json_encode(url('write_defaults')) ?> + '?pw=' + pw;
}
</script>
-71
View File
@@ -1,71 +0,0 @@
<form class="Box mobopen str" action="<?= e(url('app_set')) ?>" method="GET" id='form-1'>
<h2><?= tr('app.defaults') ?></h2>
<div class="Row explain">
<?= tr('app.explain_initials') ?>
</div>
<div class="Row">
<label for="term_width"><?= tr('app.term_width') ?></label>
<input type="number" step=1 min=1 max=255 name="term_width" id="term_width" value="%term_width%" required>
</div>
<div class="Row">
<label for="term_height"><?= tr('app.term_height') ?></label>
<input type="number" step=1 min=1 max=255 name="term_height" id="term_height" value="%term_height%" required>
</div>
<div class="Row">
<label for="default_fg"><?= tr("app.default_fg") ?></label>
<select name="default_fg" id="default_fg">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>
<script>qs('#default_fg').selectedIndex = %default_fg%;</script>
</div>
<div class="Row">
<label for="default_bg"><?= tr("app.default_bg") ?></label>
<select name="default_bg" id="default_bg">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>
<script>qs('#default_bg').selectedIndex = %default_bg%;</script>
</div>
<div class="Row">
<label for="term_title"><?= tr('app.term_title') ?></label>
<input type="text" name="term_title" id="term_title" value="%term_title%" required>
</div>
<div class="Row">
<label for="btn1"><?= tr("app.btn1") ?></label>
<input class="short" type="text" name="btn1" id="btn1" value="%btn1%">
</div>
<div class="Row">
<label for="btn2"><?= tr("app.btn2") ?></label>
<input class="short" type="text" name="btn2" id="btn2" value="%btn2%">
</div>
<div class="Row">
<label for="btn3"><?= tr("app.btn3") ?></label>
<input class="short" type="text" name="btn3" id="btn3" value="%btn3%">
</div>
<div class="Row">
<label for="btn4"><?= tr("app.btn4") ?></label>
<input class="short" type="text" name="btn4" id="btn4" value="%btn4%">
</div>
<div class="Row">
<label for="btn5"><?= tr("app.btn5") ?></label>
<input class="short" type="text" name="btn5" id="btn5" value="%btn5%">
</div>
<div class="Row buttons">
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
</div>
</form>
+24 -21
View File
@@ -12,22 +12,22 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
<div class="Row checkbox x-static-toggle" >
<label><?= tr('net.sta_dhcp_enable') ?></label><!--
--><span class="box" tabindex=0 role=checkbox></span>
<input type="hidden" name="sta_dhcp_enable" value="%sta_dhcp_enable%">
<input type="hidden" id="sta_dhcp_enable" name="sta_dhcp_enable" value="%sta_dhcp_enable%">
</div>
<div class="Row x-static">
<label for="sta_addr_ip"><?= tr('net.sta_addr_ip') ?></label>
<input type="text" name="sta_addr_ip" id="sta_addr_ip" value="%sta_addr_ip%" <?=$ipmask?> required>
<input type="text" name="sta_addr_ip" id="sta_addr_ip" value="%h:sta_addr_ip%" <?=$ipmask?> required>
</div>
<div class="Row x-static">
<label for="sta_addr_mask"><?= tr('net.sta_addr_mask') ?></label>
<input type="text" name="sta_addr_mask" id="sta_addr_mask" value="%sta_addr_mask%" <?=$ipmask?> required>
<input type="text" name="sta_addr_mask" id="sta_addr_mask" value="%h:sta_addr_mask%" <?=$ipmask?> required>
</div>
<div class="Row x-static">
<label for="sta_addr_gw"><?= tr('net.sta_addr_gw') ?></label>
<input type="text" name="sta_addr_gw" id="sta_addr_gw" value="%sta_addr_gw%" <?=$ipmask?> required>
<input type="text" name="sta_addr_gw" id="sta_addr_gw" value="%h:sta_addr_gw%" <?=$ipmask?> required>
</div>
<div class="Row buttons">
@@ -43,29 +43,29 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
</div>
<div class="Row">
<label for="ap_dhcp_time"><?= tr('net.ap_dhcp_time') ?><span class="mq-phone">&nbsp;(min)</span></label>
<input type="number" step=1 min=1 max=2880 name="ap_dhcp_time" id="ap_dhcp_time" value="%ap_dhcp_time%" required>
<span class="mq-no-phone">&nbsp;(min)</span>
</div>
<div class="Row">
<label for="ap_dhcp_start"><?= tr('net.ap_dhcp_start') ?></label>
<input type="text" name="ap_dhcp_start" id="ap_dhcp_start" value="%ap_dhcp_start%" <?=$ipmask?> required>
</div>
<div class="Row">
<label for="ap_dhcp_end"><?= tr('net.ap_dhcp_end') ?></label>
<input type="text" name="ap_dhcp_end" id="ap_dhcp_end" value="%ap_dhcp_end%" <?=$ipmask?> required>
<label for="ap_addr_mask"><?= tr('net.ap_addr_mask') ?></label>
<input type="text" name="ap_addr_mask" id="ap_addr_mask" value="%h:ap_addr_mask%" <?=$ipmask?> required>
</div>
<div class="Row">
<label for="ap_addr_ip"><?= tr('net.ap_addr_ip') ?></label>
<input type="text" name="ap_addr_ip" id="ap_addr_ip" value="%ap_addr_ip%" <?=$ipmask?> required>
<input type="text" name="ap_addr_ip" id="ap_addr_ip" value="%h:ap_addr_ip%" <?=$ipmask?> required>
</div>
<div class="Row">
<label for="ap_addr_mask"><?= tr('net.ap_addr_mask') ?></label>
<input type="text" name="ap_addr_mask" id="ap_addr_mask" value="%ap_addr_mask%" <?=$ipmask?> required>
<label for="ap_dhcp_start"><?= tr('net.ap_dhcp_start') ?></label>
<input type="text" name="ap_dhcp_start" id="ap_dhcp_start" value="%h:ap_dhcp_start%" <?=$ipmask?> required>
</div>
<div class="Row">
<label for="ap_dhcp_end"><?= tr('net.ap_dhcp_end') ?></label>
<input type="text" name="ap_dhcp_end" id="ap_dhcp_end" value="%h:ap_dhcp_end%" <?=$ipmask?> required>
</div>
<div class="Row">
<label for="ap_dhcp_time"><?= tr('net.ap_dhcp_time') ?><span class="mq-phone">&nbsp;(min)</span></label>
<input type="number" step=1 min=1 max=2880 name="ap_dhcp_time" id="ap_dhcp_time" value="%ap_dhcp_time%" required>
<span class="mq-no-phone">&nbsp;min</span>
</div>
<div class="Row buttons">
@@ -86,10 +86,13 @@ $ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
<script>
function updateStaticDisp() {
$('.x-static').toggleClass('hidden', $('#sta_dhcp_enable').val());
var sttc = !parseInt($('#sta_dhcp_enable').val());
$('.x-static').toggleClass('hidden', !sttc);
}
$('.x-static-toggle').on('click', function() {
setTimeout(function() {
updateStaticDisp();
}, 0)
});
updateStaticDisp();
</script>
+90
View File
@@ -0,0 +1,90 @@
<div class="Box str mobcol">
<h2 tabindex=0><?= tr('system.save_restore') ?></h2>
<div class="Row explain nomargintop">
<?= tr('system.explain_persist') ?>
</div>
<div class="Row buttons2">
<a class="button icn-restore"
onclick="return confirm('<?= tr('system.confirm_restore') ?>');"
href="<?= e(url('restore_defaults')) ?>">
<?= tr('system.restore_defaults') ?>
</a>
</div>
<div class="Row buttons2">
<a onclick="writeDefaults(); return false;" href="#"><?= tr('system.write_defaults') ?></a>
</div>
<div class="Row buttons2">
<a onclick="return confirm('<?= tr('system.confirm_restore_hard') ?>');"
href="<?= e(url('restore_hard')) ?>">
<?= tr('system.restore_hard') ?>
</a>
</div>
</div>
<form class="Box str mobcol" action="<?= e(url('system_set')) ?>" method="GET" id="form-1">
<h2 tabindex=0><?= tr('system.uart') ?></h2>
<div class="Row explain">
<?= tr('system.explain_uart') ?>
</div>
<div class="Row">
<label for="uart_baud"><?= tr('uart.baud') ?><span class="mq-phone">&nbsp;(bps)</span></label>
<select name="uart_baud" id="uart_baud" class="short">
<?php foreach([
300, 600, 1200, 2400, 4800, 9600, 19200, 38400,
57600, 74880, 115200, 230400, 460800, 921600, 1843200, 3686400,
] as $b):
?><option value="<?=$b?>"><?= number_format($b, 0, ',', '.') ?></option>
<?php endforeach; ?>
</select>
<span class="mq-no-phone">&nbsp;bps</span>
</div>
<div class="Row">
<label for="uart_parity"><?= tr('uart.parity') ?></label>
<select name="uart_parity" id="uart_parity" class="short">
<?php foreach([
2 => tr('uart.parity.none'),
1 => tr('uart.parity.odd'),
0 => tr('uart.parity.even'),
] as $k => $label):
?><option value="<?=$k?>"><?=$label?></option>
<?php endforeach; ?>
</select>
</div>
<div class="Row">
<label for="uart_stopbits"><?= tr('uart.stop_bits') ?></label>
<select name="uart_stopbits" id="uart_stopbits" class="short">
<?php foreach([
1 => tr('uart.stop_bits.one'),
2 => tr('uart.stop_bits.one_and_half'),
3 => tr('uart.stop_bits.two'),
] as $k => $label):
?><option value="<?=$k?>"><?=$label?></option>
<?php endforeach; ?>
</select>
</div>
<div class="Row buttons">
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
</div>
</form>
<script>
function writeDefaults() {
var pw = prompt('<?= tr('system.confirm_store_defaults') ?>');
if (!pw) return;
location.href = <?=json_encode(url('write_defaults')) ?> + '?pw=' + pw;
}
$('#uart_baud').val(%uart_baud%);
$('#uart_parity').val(%uart_parity%);
$('#uart_stopbits').val(%uart_stopbits%);
</script>
+142
View File
@@ -0,0 +1,142 @@
<form class="Box mobopen str" action="<?= e(url('term_set')) ?>" method="GET" id='form-1'>
<h2><?= tr('term.defaults') ?></h2>
<div class="Row explain">
<?= tr('term.explain_initials') ?>
</div>
<div class="Row">
<label for="theme"><?= tr("term.theme") ?></label>
<select name="theme" id="theme" class="short" onchange="showColor()">
<option value="0">Tango</option>
<option value="1">Linux</option>
<option value="2">XTerm</option>
<option value="3">Rxvt</option>
<option value="4">Ambience</option>
<option value="5">Solarized</option>
</select>
</div>
<div class="Row color-preview">
<div class="colorprev">
<span data-fg=0 class="bg0 fg0">30</span><!--
--><span data-fg=1 class="bg0 fg1">31</span><!--
--><span data-fg=2 class="bg0 fg2">32</span><!--
--><span data-fg=3 class="bg0 fg3">33</span><!--
--><span data-fg=4 class="bg0 fg4">34</span><!--
--><span data-fg=5 class="bg0 fg5">35</span><!--
--><span data-fg=6 class="bg0 fg6">36</span><!--
--><span data-fg=7 class="bg0 fg7">37</span>
</div>
<div class="colorprev">
<span data-fg=8 class="bg0 fg8">90</span><!--
--><span data-fg=9 class="bg0 fg9">91</span><!--
--><span data-fg=10 class="bg0 fg10">92</span><!--
--><span data-fg=11 class="bg0 fg11">93</span><!--
--><span data-fg=12 class="bg0 fg12">94</span><!--
--><span data-fg=13 class="bg0 fg13">95</span><!--
--><span data-fg=14 class="bg0 fg14">96</span><!--
--><span data-fg=15 class="bg0 fg15">97</span>
</div>
<div class="colorprev">
<span data-bg=0 class="bg0 fg15">40</span><!--
--><span data-bg=1 class="bg1 fg15">41</span><!--
--><span data-bg=2 class="bg2 fg15">42</span><!--
--><span data-bg=3 class="bg3 fg0">43</span><!--
--><span data-bg=4 class="bg4 fg15">44</span><!--
--><span data-bg=5 class="bg5 fg15">45</span><!--
--><span data-bg=6 class="bg6 fg15">46</span><!--
--><span data-bg=7 class="bg7 fg0">47</span>
</div>
<div class="colorprev">
<span data-bg=8 class="bg8 fg15">100</span><!--
--><span data-bg=9 class="bg9 fg0">101</span><!--
--><span data-bg=10 class="bg10 fg0">102</span><!--
--><span data-bg=11 class="bg11 fg0">103</span><!--
--><span data-bg=12 class="bg12 fg0">104</span><!--
--><span data-bg=13 class="bg13 fg0">105</span><!--
--><span data-bg=14 class="bg14 fg0">106</span><!--
--><span data-bg=15 class="bg15 fg0">107</span>
</div>
</div>
<div class="Row color-preview">
<div style="
" id="color-example">
<?= tr("term.example") ?>
</div>
</div>
<div class="Row">
<label><?= tr("term.default_fg_bg") ?></label>
<select name="default_fg" id="default_fg" class="short" onchange="showColor()">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>&nbsp;<!--
--><select name="default_bg" id="default_bg" class="short" onchange="showColor()">
<?php for($i=0; $i<16; $i++): ?>
<option value="<?=$i?>"><?= tr("color.$i") ?></option>
<?php endfor; ?>
</select>
</div>
<div class="Row">
<label for="term_width"><?= tr('term.term_width') ?></label>
<input type="number" step=1 min=1 max=255 name="term_width" id="term_width" value="%term_width%" required>&nbsp;<!--
--><input type="number" step=1 min=1 max=255 name="term_height" id="term_height" value="%term_height%" required>
</div>
<div class="Row">
<label for="term_title"><?= tr('term.term_title') ?></label>
<input type="text" name="term_title" id="term_title" value="%h:term_title%" required>
</div>
<div class="Row">
<label><?= tr("term.buttons") ?></label>
<input class="short" type="text" name="btn1" id="btn1" value="%h:btn1%">&nbsp;
<input class="short" type="text" name="btn2" id="btn2" value="%h:btn2%">&nbsp;
<input class="short" type="text" name="btn3" id="btn3" value="%h:btn3%">&nbsp;
<input class="short" type="text" name="btn4" id="btn4" value="%h:btn4%">&nbsp;
<input class="short" type="text" name="btn5" id="btn5" value="%h:btn5%">
</div>
<div class="Row">
<label for="parser_tout_ms"><?= tr('term.parser_tout_ms') ?><span class="mq-phone">&nbsp;(ms)</span></label>
<input type="number" step=1 min=0 name="parser_tout_ms" id="parser_tout_ms" value="%parser_tout_ms%" required>
<span class="mq-no-phone">&nbsp;ms</span>
</div>
<div class="Row buttons">
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
</div>
</form>
<script>
$('#default_fg').val(%default_fg%);
$('#default_bg').val(%default_bg%);
$('#theme').val(%theme%);
function showColor() {
var ex = qs('#color-example');
ex.className = '';
ex.classList.add('fg'+$('#default_fg').val());
ex.classList.add('bg'+$('#default_bg').val());
var th = $('#theme').val();
$('.color-preview').forEach(function(e) {
e.className = 'Row color-preview theme-'+th;
});
}
showColor();
$('.colorprev span').on('click', function() {
var fg = this.dataset.fg;
var bg = this.dataset.bg;
if (typeof fg != 'undefined') $('#default_fg').val(fg);
if (typeof bg != 'undefined') $('#default_bg').val(bg);
showColor()
});
</script>
+46 -31
View File
@@ -1,28 +1,28 @@
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-1">
<h2 tabindex=0><?= tr('wifi.ap') ?></h2>
<div class="Row checkbox">
<div class="Row checkbox x-ap-toggle">
<label><?= tr('wifi.enable') ?></label><!--
--><span class="box" tabindex=0></span>
<input type="hidden" name="ap_enable" value="%ap_enable%">
<input type="hidden" id="ap_enabled" name="ap_enable" value="%ap_enable%">
</div>
<div class="Row">
<div class="Row x-ap-on">
<label for="ap_ssid"><?= tr('wifi.ap_ssid') ?></label>
<input type="text" name="ap_ssid" id="ap_ssid" value="%ap_ssid%" required>
<input type="text" name="ap_ssid" id="ap_ssid" value="%h:ap_ssid%" required>
</div>
<div class="Row">
<div class="Row x-ap-on">
<label for="ap_password"><?= tr('wifi.ap_password') ?></label>
<input type="text" name="ap_password" id="ap_password" value="%ap_password%">
<input type="text" name="ap_password" id="ap_password" value="%h:ap_password%">
</div>
<div class="Row">
<div class="Row x-ap-on">
<label for="ap_channel"><?= tr('wifi.ap_channel') ?></label>
<input type="number" name="ap_channel" id="ap_channel" min=1 max=14 value="%ap_channel%" required>
</div>
<div class="Row range">
<div class="Row range x-ap-on">
<label for="tpw">
<?= tr('wifi.tpw') ?>
<span class="display x-disp1 mq-phone"></span>
@@ -31,7 +31,7 @@
<span class="display x-disp2 mq-no-phone"></span>
</div>
<div class="Row checkbox">
<div class="Row checkbox x-ap-on">
<label><?= tr('wifi.ap_hidden') ?></label><!--
--><span class="box" tabindex=0></span>
<input type="hidden" name="ap_hidden" value="%ap_hidden%">
@@ -42,19 +42,24 @@
</div>
</form>
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-2">
<form class="Box str mobcol expanded" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-2">
<h2 tabindex=0><?= tr('wifi.sta') ?></h2>
<div class="Row checkbox">
<div class="Row checkbox x-sta-toggle">
<label><?= tr('wifi.enable') ?></label><!--
--><span class="box" tabindex=0></span>
<input type="hidden" name="sta_enable" value="%sta_enable%">
<input type="hidden" id="sta_enabled" name="sta_enable" value="%sta_enable%">
</div>
<div class="Row explain nomargintop x-sta-on">
<span class="spacer"></span>
<?= tr("wifi.sta_explain") ?>
</div>
<input type="hidden" name="sta_ssid" id="sta_ssid" value="">
<input type="hidden" name="sta_password" id="sta_password" value="">
<div class="Row sta-info">
<div class="Row sta-info x-sta-on">
<label><?= tr('wifi.sta_info') ?></label>
<div class="AP-preview hidden" id="sta-nw">
<div class="wrap">
@@ -72,11 +77,10 @@
</div>
</div>
<div id="ap-box">
<div id="ap-box" class="x-sta-on">
<label><?= tr('wifi.select_ssid') ?></label>
<div id="ap-scan"><a href="#" onclick="WiFi.startScanning(); return false"><?= tr('wifi.scan_now') ?></a></div>
<div id="ap-loader" class="hidden"><?= tr('wifi.scanning') ?><span class="anim-dots">.</span></div>
<div id="ap-noscan" class="hidden"><?= tr('wifi.cant_scan_no_sta') ?></div>
<div id="ap-list" class="hidden"></div>
</div>
@@ -85,24 +89,35 @@
</div>
</form>
<div class="Modal hidden" id="psk-modal">
<div class="Dialog">
<form id="conn-form" onsubmit="submitPskModal(); return false;">
<input type="hidden" id="conn-ssid"><!--
--><label for="conn-passwd"><?= tr('wifi.sta_password') ?></label><!--
--><input type="text" id="conn-passwd"><!--
--><input type="submit" value="<?= tr('confirm') ?>">
</form>
</div>
</div>
<script>
WiFi.scan_url = '<?= url('wifi_scan', true) ?>';
WiFi.init({
mode: '%opmode%',
sta_ssid: '%sta_ssid%',
sta_password: '%sta_password%',
sta_active_ip: '%sta_active_ip%',
sta_active_ssid: '%sta_active_ssid%',
sta_ssid: '%j:sta_ssid%',
sta_password: '%j:sta_password%',
sta_active_ip: '%j:sta_active_ip%',
sta_active_ssid: '%j:sta_active_ssid%',
});
function updateApDisp() {
var a = !!parseInt($('#ap_enabled').val());
$('.x-ap-on').toggleClass('hidden', !a);
}
$('.x-ap-toggle').on('click', function() {
setTimeout(function() {
updateApDisp();
}, 0)
});
function updateStaDisp() {
var a = !!parseInt($('#sta_enabled').val());
$('.x-sta-on').toggleClass('hidden', !a);
}
$('.x-sta-toggle').on('click', function() {
setTimeout(function() {
updateStaDisp();
}, 0)
});
updateApDisp();
updateStaDisp();
</script>
+42 -6
View File
@@ -5,9 +5,15 @@
<a href="<?= e(url('cfg_wifi')) ?>" id="backbtn" class="button"><?= tr('wifi.conn.back_to_config') ?></a>
</div>
<div class="Box">
<p><?= tr('wifi.conn.explain_android_sucks') ?></p>
<p><?= tr('wifi.conn.explain_reset') ?></p>
</div>
<script>
var xhr = new XMLHttpRequest();
var abortTmeo;
var failCounter = 0;
var messages = <?= json_encode([
'disabled' => tr('wifi.conn.disabled'),
@@ -17,16 +23,32 @@
'fail' => tr('wifi.conn.fail'),
]) ?>;
function onFail() {
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>);
$('.anim-dots').addClass('hidden');
}
function getStatus() {
xhr.open("GET", 'http://'+_root+'<?= url('wifi_connstatus', true) ?>');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
clearTimeout(abortTmeo);
try {
var data = JSON.parse(xhr.responseText);
var done = false;
var msg = messages[data.status] || '...';
if (data.status == 'success') msg += data.ip;
if (data.status == 'fail') msg += data.cause;
if (data.status == 'success') {
msg += data.ip;
done = true;
}
if (data.status == 'fail') {
msg += data.cause;
done = true;
}
$("#status").html(msg);
@@ -34,16 +56,30 @@
// $('#backbtn').removeClass('hidden');
$('.anim-dots').addClass('hidden');
} else {
// ask again after a short delay
window.setTimeout(getStatus, 1000);
}
} catch(e) {
failCounter++;
console.log(e);
// repeat
if (failCounter > 5) {
onFail();
}
else {
window.setTimeout(getStatus, 1000);
}
}
} else {
onFail();
}
}
};
// XHR timeout
abortTmeo = setTimeout(function () {
xhr.abort();
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>);
// $('#backbtn').removeClass('hidden');
$('.anim-dots').addClass('hidden');
onFail();
}, 4000);
xhr.send();
+81 -36
View File
@@ -1,15 +1,34 @@
<div class="Box">
<h2>Wiring</h2>
<h2>Troubleshooting</h2>
<ul>
<li>Communication UART is on pins <b>Rx, Tx</b> at 115200-8-1-N. The baud rate can be changed in Terminal Settings.
<li>Debug log is on pin <b>GPIO2</b> (P2) at 115200-8-1-N. This baud rate is fixed.
<li>Compatible with 3.3&nbsp;V and 5&nbsp;V logic. For 5&nbsp;V, 470&nbsp;R protection resistors are recommended.
<li>If the "LVD" LED on the ESPTerm module lights up, it doesn't get enough power to run correctly. Check your connections.
<li>Connect Rx and Tx with a piece of wire to test the terminal alone, you should see what you type in the browser.
<i>NOTE: This won't work if your ESP8266 board has a built-in USB-serial converter (like NodeMCU).</i>
<li>For best performance, use the module in Client mode (connected to external network).
<li>In AP mode, check that the channel used is clear; interference may cause a flaky connection.
<li>ESPTerm communicates with external µC via UART on pins <b>Rx</b> and <b>Tx</b>.
It can be configured in <a href="<?= url('cfg_system') ?>">System Settings</a>.
Make sure you're using the correct baud rate and other settings on the other side.
<li>Boot log and debug messages are available on pin <b>GPIO2</b> (P2) at 115200\,bps, 1 stop bit, no parity.
<li>ESPTerm uses 3.3\,V logic. For communication with 5\,V logic (eg. Aurdino), 470\,R
protection resistors are recommended. The ESPTerm breakout board already includes those resistors.
<li>If the Low Voltage Detector ("LVD") LED on the ESPTerm breakout lights up, your power connections have too high
resistance or the power supply is insufficient. ESP8266 can consume in short spikes up to 500\,A and
the supply voltage on the module must not drop below about 2.7\,V. Avoid needless breadboard jumpers and too thin wires.
<li>Connect Rx and Tx with a piece of wire to verify that the terminal works correctly; you should then immediately
see what you type in the browser. <i>This won't work if your ESP8266 board has a built-in USB-serial
converter connected to those pins.</i>
<li>For best performance, use the module in Client mode (connected to external network). Enabling AP mode consumes
extra RAM and cycles, since the captive portal DNS server and DHCP server are be loaded.
<li>In AP mode, check that the channel used by ESPTerm is clear; interference may cause flaky connection. A good mobile app to use for this is <a href="https://play.google.com/store/apps/details?id=com.farproc.wifi.analyzer">WiFi Analyzer (Google Play)</a>
<li>The terminal browser display needs WebSockets for the real-time updating to work.
It will not work with some old browsers (Android < 4.4, IE < 10, Opera Mini)
- see the <a href="https://caniuse.com/websockets/embed/">compatibility chart</a>.
<li>At most 4 clients can be connected to the AP at the same time. This is a limitation of the ESP8266 SDK.
</ul>
</div>
@@ -17,19 +36,22 @@
<h2>Screen</h2>
<ul>
<li>Most ANSI escape sequences are supported.</li>
<li>The max screen size is 2000 characters (eg. <b>25x80</b>), default is <b>10x26</b>. Set using <code>\e]W&lt;rows&gt;;&lt;cols&gt;\a</code>.</li>
<li>The screen will automatically scroll, can be used for log output.</li>
<li>Display update is sent <b>after 20 ms of inactivity</b>.</li>
<li>At most 4 clients can be connected at the same time.</li>
<li>The browser display needs WebSockets for the real-time updating. It may not work on really old phones / browsers.</li>
<li>Most ANSI escape sequences are supported.
<li>The initial screen size and other terminal options can be configured in
<a href="<?= url('cfg_term') ?>">Terminal Settings</a>
<li>Display updates are sent to the browser after 20 ms of inactivity on the communication UART.
This is to avoid hogging the server with tiny updates during large screen repaints.
</ul>
</div>
<div class="Box">
<div class="Box theme-0">
<h2>Color Reference</h2>
<p>Color is set using <code>\e[&lt;c&gt;m</code> or <code>\e[&lt;c&gt;;&lt;c&gt;m</code> where "&lt;c&gt;" are numbers from the following tables:</p>
<p>Color is set using <code>\e[&lt;c&gt;m</code> where <code>&lt;c&gt;</code> are numbers separated by semicolons
(and <code>\e</code> is ESC, code 27). The actual color representation depends on a color theme which
can be selected in <a href="<?= url('cfg_term') ?>">Terminal Settings</a>.</p>
<b>Foreground</b>
@@ -45,7 +67,7 @@
</div>
<div class="colorprev">
<span class="bg7 fg8">90</span>
<span class="bg0 fg8">90</span>
<span class="bg0 fg9">91</span>
<span class="bg0 fg10">92</span>
<span class="bg0 fg11">93</span>
@@ -58,18 +80,18 @@
<b>Background</b>
<div class="colorprev">
<span class="bg0 fg15 nb">40</span>
<span class="bg1 fg15 nb">41</span>
<span class="bg2 fg15 nb">42</span>
<span class="bg0 fg15">40</span>
<span class="bg1 fg15">41</span>
<span class="bg2 fg15">42</span>
<span class="bg3 fg0">43</span>
<span class="bg4 fg15 nb">44</span>
<span class="bg5 fg15 nb">45</span>
<span class="bg6 fg15 nb">46</span>
<span class="bg4 fg15">44</span>
<span class="bg5 fg15">45</span>
<span class="bg6 fg15">46</span>
<span class="bg7 fg0">47</span>
</div>
<div class="colorprev">
<span class="bg8 fg15 nb">100</span>
<span class="bg8 fg15">100</span>
<span class="bg9 fg0">101</span>
<span class="bg10 fg0">102</span>
<span class="bg11 fg0">103</span>
@@ -79,7 +101,10 @@
<span class="bg15 fg0">107</span>
</div>
<p>Bright foreground can also be set using the "bold" attribute 1 (eg. <code>\e[31;1m</code>). For more details, see the ANSI code reference below.</p>
<p>To make the text bold, use the "bold" attribute (eg. <code>\e[31;40;1m</code> for
<span style="padding: 2px;" class="bg0 fg1 bold">bold red on black</span>).</p>
<p>The colors are reset to default using <code>\e[0m</code>. For more info, see the tables below.</p>
</div>
<div class="Box">
@@ -87,13 +112,12 @@
<p>
All the user types on their keyboard is sent as-is to the UART, including ESC, ANSI sequences for arrow keys and CR-LF for Enter.
The input is limited to ASCII codes 32-126, backspace 8 and tab 9.
</p>
<p>The buttons under the screen send ASCII codes 1, 2, 3, 4, 5.</p>
<p>The buttons under the screen send ASCII codes 1, 2, 3, 4, 5. This choice was made to make their parsing as simple as possible.</p>
<p>
<b>Mouse input</b> (click/tap) is sent as <code>\e[&lt;y&gt;;&lt;x&gt;M</code>. You can use this for on-screen buttons, menu navigation etc.
Mouse input (click/tap) is sent as <code>\e[&lt;y&gt;;&lt;x&gt;M</code>. You can use this for virtual on-screen buttons.
</p>
</div>
@@ -123,6 +147,14 @@
<td>0</td>
<td>Reset text attributes to default</td>
</tr>
<tr>
<td>1</td>
<td>Bold</td>
</tr>
<tr>
<td>21 or 22</td>
<td>Bold off</td>
</tr>
<tr>
<td>7</td>
<td>Inverse (fg/bg swap when printing)</td>
@@ -178,7 +210,7 @@
<tr>
<td>\e[&lt;n&gt;E</td>
<td>[count]</td>
<td>Go N line down, start of line</td>
<td>Go N lines down, start of line</td>
</tr>
<tr>
<td>\e[&lt;n&gt;F</td>
@@ -277,7 +309,7 @@
<tr>
<td>\e]W&lt;r&gt;;&lt;c&gt;\a</td>
<td>rows;cols</td>
<td>Set screen size, maximum 25x80 (resp. total 2000 characters). This also clears the screen.</td>
<td>Set screen size (max ~ 80x30). This also clears the screen. This is a custom ESPTerm OSC command.</td>
</tr>
</tbody>
</table>
@@ -301,16 +333,29 @@
The screen size and WiFi settings stay unchanged.
</td>
</tr>
<tr>
<td>\e]FR\a</td>
<td>--</td>
<td>"Factory Reset", emergency code when you mess up the WiFi, restores SSID to unique default, clears stored credentials & enters Client+AP mode.</td>
</tr>
<tr>
<td>\e[5n</td>
<td>--</td>
<td>Query device status, replies with <code>\e[0n</code> "device is OK". Can be used to check if the UART works.</td>
</tr>
<tr>
<td>\e]TITLE=…\a</td>
<td>Title text</td>
<td>Set terminal title. This is a custom ESPTerm OSC command.</td>
</tr>
<tr>
<td>\e]BTNn=…\a</td>
<td>1-5, label</td>
<td>Set button label. This is a custom ESPTerm OSC command.</td>
</tr>
<tr>
<td>ASCII 24 (18h)</td>
<td></td>
<td>This symbol is sent by ESPTerm when it becomes ready to receive commands. It can be used to wait before it becomes
ready on power-up, or to detect a spontaneous ESPTerm restart - that could happen due to RAM exhaustion due
to a memory leak, or perhaps a power outage.
</td>
</tr>
</tbody>
</table>
</div>
+37 -12
View File
@@ -2,36 +2,61 @@
// Workaround for badly loaded page
setTimeout(function() {
if (typeof termInit == 'undefined' || typeof $ == 'undefined') {
console.error("Page load failed, refreshing…");
location.reload(true);
}
}, 2000);
</script>
<h1>%term_title%</h1>
<h1></h1>
<div id="termwrap">
<div id="screen"></div>
<div id="screen" class="theme-%theme%"></div>
<div id="buttons">
<button data-n="1" class="btn-blue">%btn1%</button><!--
--><button data-n="2" class="btn-blue">%btn2%</button><!--
--><button data-n="3" class="btn-blue">%btn3%</button><!--
--><button data-n="4" class="btn-blue">%btn4%</button><!--
--><button data-n="5" class="btn-blue">%btn5%</button>
<button data-n="1" class="btn-blue"></button><!--
--><button data-n="2" class="btn-blue"></button><!--
--><button data-n="3" class="btn-blue"></button><!--
--><button data-n="4" class="btn-blue"></button><!--
--><button data-n="5" class="btn-blue"></button>
</div>
</div>
<textarea id="softkb-input" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
<nav id="botnav">
<a href="<?= url('cfg_wifi') ?>"><?= tr('menu.settings') ?></a><!--
--><a href="<?= url('help') ?>">Help</a><!--
--><a href="<?= url('about') ?>">About</a>
<a href="#" onclick="toggleSoftKb(true); return false" class="icn-keyboard mq-tablet-max"></a><!--
--><a href="<?= url('cfg_term') ?>"><?= tr('term_nav.config') ?></a><!--
--><a href="<?= url('cfg_wifi') ?>"><?= tr('term_nav.wifi') ?></a><!--
--><a href="<?= url('help') ?>"><?= tr('term_nav.help') ?></a><!--
--><a href="<?= url('about') ?>"><?= tr('term_nav.about') ?></a>
</nav>
<script>
try {
termInit(%screenData%);
window.noAutoShow = true;
termInit(); // the screen will be loaded via ajax
Screen.load('%j:labels_seq%');
// auto-clear the input box
$('#softkb-input').on('input', function(e) {
setTimeout(function(){
var str = $('#softkb-input').val();
$('#softkb-input').val('');
Input.sendString(str);
}, 1);
});
} catch(e) {
console.error("Fail, reloading...");
console.error(e);
console.error("Fail, reloading in 1s…");
setTimeout(function() {
location.reload(true);
}, 1000);
}
function toggleSoftKb(yes) {
var i = qs('#softkb-input');
if (yes) i.focus();
else i.blur();
}
</script>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-24
View File
@@ -1,24 +0,0 @@
$term-colors:
// 0 black, 1 red, 2 green, 3 yellow
// 4 blue, 5 mag, 6 cyan, 7 white
#111213, #CC0000, #4E9A06, #C4A000,
#3465A4, #75507B, #06989A, #D3D7CF,
// BRIGHT
// 8 black, 9 red, 10 green, 11 yellow
// 12 blue, 13 mag, 14 cyan, 15 white
#555753, #EF2929, #8AE234, #FCE94F,
#729FCF, #AD7FA8, #34E2E2, #EEEEEC;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
.fg8, .fg9, .fg10, .fg11, .fg12, .fg13, .fg14, .fg15 {
font-weight: bold;
}
.nb {
font-weight: normal !important;
}
+1 -2
View File
@@ -1,5 +1,5 @@
@import "normalize";
@import "fontello-embedded";
@import "fontello";
@import "lib/bourbon/bourbon";
@import "grid-settings";
@@ -33,7 +33,6 @@ $c-form-highlight-a: #2ea1f9;
@import "pages/wifi";
@import "pages/term";
@import "pages/about";
@import "term-colors";
// media queries
+15 -4
View File
@@ -1,5 +1,13 @@
@import "buttons";
#{$all-text-inputs}, select, label.select-wrap {
width: $form-field-w;
}
input[type="number"], input.short, select.short {
width: $form-field-w/2;
}
#{$all-text-inputs}, select {
border: 0 none;
border-bottom: 2px solid $c-form-highlight;
@@ -33,17 +41,20 @@
color: $c-form-highlight-a;
&::before {
font-weight: bold;
font-family: "fontello";
position: absolute;
content: '×';
//content: '×';
//content: '✓';
left: 0; top: 0; right: 0; bottom: 0;
line-height: $h - 1px;
line-height: 25px;
text-align: center;
font-size: $h;
font-size: 20px;
vertical-align: middle;
display: none;
}
@include icon-content('ok');
&.checked::before {
display: block;
}
+2 -9
View File
@@ -1,14 +1,6 @@
// Unified Form wrapper
form { @include naked(); }
#{$all-text-inputs}, select, label.select-wrap {
width: $form-field-w;
}
input[type="number"], input.short {
width: $form-field-w/2;
}
.Box.errors {
.list {
color: crimson;
@@ -45,7 +37,8 @@ input[type="number"], input.short {
}
}
&.buttons {
// buttons2 is the same style, but different selector for use in the admin page
&.buttons, &.buttons2 {
margin: 16px auto;
input, .button {
margin-right: dist(-1);
+14 -2
View File
@@ -78,10 +78,21 @@
}
.Row.explain {
display: block; // this stops flex messing up text with inline tags
max-width: 600px; margin-left: 0;
line-height: 1.2;
@include media($phone) {
margin-top: 60px;
}
&.nomargintop {
margin-top: 12px !important;
}
&.padleft {
}
}
&.mobopen .Row.explain {
margin-top: 12px; // default from .Row
@@ -97,11 +108,12 @@
h2 {
position: relative;
cursor: pointer;
padding-right: 1.3rem;
padding: 2px 1.3rem 2px 5px;
margin: 0 -5px 0 -5px;
&::after {
position: absolute;
right: 0;
right: 4px;
content: '';
top:50%;
+10
View File
@@ -48,3 +48,13 @@
color: $c-form-label-fg;
}
}
#content {
// fade in effect
opacity: 0;
transition: opacity 0.15s ease-in;
}
#content.load {
opacity: 1;
}
+1
View File
@@ -16,6 +16,7 @@
}
#brand {
cursor: pointer;
color: $menu-fg;
background: darken($menu-bg, 10%);
font-size: 120%;
+148 -2
View File
@@ -1,4 +1,8 @@
body.term {
#content {
padding-left: 0;
padding-right: 0;
h1 {
font-size: fsize(5);
@include media($phone) {
@@ -6,6 +10,10 @@ body.term {
}
}
// longer duration to load everything in background nicely
transition: opacity 0.25s ease-in;
}
#screen {
@include noselect();
font-family: monospace;
@@ -36,7 +44,7 @@ body.term {
margin: 0 3px;
padding: 8px 5px;
//width: 18%;
min-width: 65px;
min-width: 62px;
//max-width: 65px;
//min-width: initial;
cursor: pointer;
@@ -53,16 +61,154 @@ body.term {
text-decoration: underline;
&, &:visited, &:link {
color: #2e4d6e;
color: #336085;
}
&:hover {
color: #5abfff;
}
}
.icn-keyboard {
text-decoration: none;
font-size: 150%;
vertical-align: middle;
}
}
}
#termwrap {
text-align: center;
}
#softkb-input {
position: absolute;
top: -9999px;
}
// Tango
.theme-0 {
$term-colors:
#111213, #CC0000, #4E9A06, #C4A000, #3465A4, #75507B, #06989A, #D3D7CF,
#555753, #EF2929, #8AE234, #FCE94F, #729FCF, #AD7FA8, #34E2E2, #EEEEEC;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Linux
.theme-1 {
$term-colors:
#000000, #aa0000, #00aa00, #aa5500, #0000aa, #aa00aa, #00aaaa, #aaaaaa,
#555555, #ff5555, #55ff55, #ffff55, #5555ff, #ff55ff, #55ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// xterm
.theme-2 {
$term-colors:
#000000, #cd0000, #00cd00, #cdcd00, #0000ee, #cd00cd, #00cdcd, #e5e5e5,
#7f7f7f, #ff0000, #00ff00, #ffff00, #5c5cff, #ff00ff, #00ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// rxvt
.theme-3 {
$term-colors:
#000000, #cd0000, #00cd00, #cdcd00, #0000cd, #cd00cd, #00cdcd, #faebd7,
#404040, #ff0000, #00ff00, #ffff00, #0000ff, #ff00ff, #00ffff, #ffffff;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Ambience
.theme-4 {
$term-colors:
#2e3436, #cc0000, #4e9a06, #c4a000, #3465a4, #75507b, #06989a, #d3d7cf,
#555753, #ef2929, #8ae234, #fce94f, #729fcf, #ad7fa8, #34e2e2, #eeeeec;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Solarized
.theme-5 {
$term-colors:
#073642, #dc322f, #859900, #b58900, #268bd2, #d33682, #2aa198, #eee8d5,
#002b36, #cb4b16, #586e75, #657b83, #839496, #6c71c4, #93a1a1, #fdf6e3;
@for $i from 1 through length($term-colors) {
$c: nth($term-colors, $i);
.fg#{$i - 1} { color: $c; }
.bg#{$i - 1} { background-color: $c; }
}
}
// Attributes
.bold {
font-weight: bold !important;
}
.faint span { // content of faint is wrapped in span
opacity: 0.6;
}
.italic {
font-style: italic;
}
.under {
text-decoration: underline;
}
.strike {
text-decoration: line-through;
}
.underline.strike {
text-decoration: underline line-through;
}
.blink-hide .blink {
color: transparent;
}
//
.Row.color-preview {
font-family: monospace;
font-size: 16pt;
display: block;
margin-bottom: 0;
padding-left: $form-label-w;
@include media($phone) {
padding-left: 0;
font-size: 14pt;
}
.colorprev {
display:block;
margin: 0;
cursor: pointer;
}
}
#color-example {
display: inline-block;
padding: 5px;
}
+1
View File
@@ -19,6 +19,7 @@
padding: dist(-2);
margin-bottom: dist(-2);
margin-top: dist(-2);
font-size: 110%;
}
#ap-noscan {
+1 -1
View File
@@ -17,7 +17,7 @@
/**
* Helper that retrieves an arg from `connData->getArgs` and stores it in `buff`. Returns 1 on success
*/
#define GET_ARG(key) (httpdFindArg(connData->getArgs, key, buff, sizeof(buff)) > 0)
#define GET_ARG(key) (httpdFindArg(connData->getArgs, key, buff, sizeof(buff)) >= 0)
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
+393 -131
View File
@@ -2,35 +2,83 @@
/* #line 1 "user/ansi_parser.rl" */
#include <esp8266.h>
#include "ansi_parser.h"
#include "screen.h"
/* Ragel constants block */
/* #line 9 "user/ansi_parser.c" */
/* #line 10 "user/ansi_parser.c" */
static const char _ansi_actions[] = {
0, 1, 0, 1, 1, 1, 2, 1,
3, 1, 4, 1, 5, 1, 6, 1,
7, 1, 8, 1, 9, 1, 10, 1,
11, 1, 12
11, 1, 12, 1, 13, 1, 14, 1,
15, 1, 16, 1, 17, 2, 2, 5,
2, 10, 11, 2, 10, 12
};
static const char _ansi_eof_actions[] = {
0, 13, 13, 13, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 0, 0,
0
0, 0, 0, 0, 0, 0, 0
};
static const int ansi_start = 1;
static const int ansi_first_final = 14;
static const int ansi_first_final = 30;
static const int ansi_error = 0;
static const int ansi_en_CSI_body = 3;
static const int ansi_en_OSC_body = 5;
static const int ansi_en_CSI_body = 5;
static const int ansi_en_OSC_body = 7;
static const int ansi_en_TITLE_body = 27;
static const int ansi_en_charsetcmd_body = 29;
static const int ansi_en_main = 1;
/* #line 8 "user/ansi_parser.rl" */
/* #line 9 "user/ansi_parser.rl" */
static volatile int cs = -1;
volatile u32 ansi_parser_char_cnt = 0;
void ICACHE_FLASH_ATTR
ansi_parser_reset(void) {
if (cs != ansi_start) {
cs = ansi_start;
apars_reset_utf8buffer();
ansi_warn("Parser timeout, state reset");
}
}
#define HISTORY_LEN 16
#if DEBUG_ANSI
static char history[HISTORY_LEN + 1];
#endif
void ICACHE_FLASH_ATTR
apars_handle_badseq(void)
{
#if DEBUG_ANSI
char buf1[HISTORY_LEN*3+2];
char buf2[HISTORY_LEN*3+2];
char *b1 = buf1;
char *b2 = buf2;
char c;
for(int i=0;i<HISTORY_LEN;i++) {
c = history[i];
b1 += sprintf(b1, "%2X ", c);
if (c < 32 || c > 127) c = '.';
b2 += sprintf(b2, "%c ", c);
}
ansi_dbg("Context: %s", buf2);
ansi_dbg(" %s", buf1);
#endif
}
/**
* \brief Linear ANSI chars stream parser
*
@@ -47,13 +95,16 @@ static const int ansi_en_main = 1;
void ICACHE_FLASH_ATTR
ansi_parser(const char *newdata, size_t len)
{
static int cs = -1;
// The CSI code is built here
static char csi_leading; //!< Leading char, 0 if none
static int csi_ni; //!< Number of the active digit
static int csi_cnt; //!< Digit count
static int csi_n[CSI_N_MAX]; //!< Param digits
static char csi_char; //!< CSI action char (end)
static char osc_buffer[OSC_CHAR_MAX];
static int osc_bi;
ansi_parser_char_cnt++;
if (len == 0) len = strlen(newdata);
@@ -65,17 +116,28 @@ ansi_parser(const char *newdata, size_t len)
// Init Ragel on the first run
if (cs == -1) {
/* #line 69 "user/ansi_parser.c" */
/* #line 120 "user/ansi_parser.c" */
{
cs = ansi_start;
}
/* #line 44 "user/ansi_parser.rl" */
/* #line 89 "user/ansi_parser.rl" */
#if DEBUG_ANSI
memset(history, 0, sizeof(history));
#endif
}
#if DEBUG_ANSI
for(int i=len; i<HISTORY_LEN; i++) {
history[i-len] = history[i];
}
strcpy(&history[HISTORY_LEN-len], newdata);
#endif
// The parser
/* #line 79 "user/ansi_parser.c" */
/* #line 141 "user/ansi_parser.c" */
{
const char *_acts;
unsigned int _nacts;
@@ -92,203 +154,361 @@ case 1:
goto tr0;
case 2:
switch( (*p) ) {
case 55: goto tr3;
case 56: goto tr4;
case 91: goto tr5;
case 93: goto tr6;
case 99: goto tr7;
case 32: goto tr3;
case 35: goto tr4;
case 91: goto tr7;
case 93: goto tr8;
case 107: goto tr9;
}
goto tr2;
case 0:
goto _out;
case 14:
if ( (*p) == 27 )
goto tr1;
goto tr0;
case 3:
if ( (*p) == 59 )
goto tr10;
if ( (*p) < 60 ) {
if ( (*p) > 47 ) {
if ( 48 <= (*p) && (*p) <= 57 )
goto tr9;
} else if ( (*p) >= 32 )
goto tr8;
} else if ( (*p) > 64 ) {
goto tr6;
} else if ( (*p) >= 40 )
goto tr5;
} else if ( (*p) > 62 ) {
if ( (*p) > 90 ) {
if ( 97 <= (*p) && (*p) <= 122 )
goto tr11;
goto tr6;
} else if ( (*p) >= 65 )
goto tr11;
goto tr6;
} else
goto tr8;
goto tr6;
goto tr2;
case 4:
if ( (*p) == 59 )
case 0:
goto _out;
case 3:
if ( 70 <= (*p) && (*p) <= 71 )
goto tr10;
if ( (*p) < 65 ) {
if ( 48 <= (*p) && (*p) <= 57 )
goto tr9;
} else if ( (*p) > 90 ) {
if ( 97 <= (*p) && (*p) <= 122 )
goto tr11;
} else
goto tr11;
goto tr2;
case 15:
case 30:
if ( (*p) == 27 )
goto tr1;
goto tr0;
case 4:
if ( 48 <= (*p) && (*p) <= 57 )
goto tr11;
goto tr2;
case 5:
switch( (*p) ) {
case 70: goto tr12;
case 87: goto tr13;
case 59: goto tr14;
case 64: goto tr15;
}
if ( (*p) < 60 ) {
if ( (*p) > 47 ) {
if ( 48 <= (*p) && (*p) <= 57 )
goto tr13;
} else if ( (*p) >= 32 )
goto tr12;
} else if ( (*p) > 63 ) {
if ( (*p) > 90 ) {
if ( 96 <= (*p) && (*p) <= 122 )
goto tr16;
} else if ( (*p) >= 65 )
goto tr16;
} else
goto tr12;
goto tr2;
case 6:
if ( (*p) == 82 )
if ( (*p) == 59 )
goto tr14;
if ( (*p) < 64 ) {
if ( 48 <= (*p) && (*p) <= 57 )
goto tr13;
} else if ( (*p) > 90 ) {
if ( 96 <= (*p) && (*p) <= 122 )
goto tr16;
} else
goto tr16;
goto tr2;
case 31:
goto tr2;
case 32:
if ( (*p) == 59 )
goto tr14;
if ( (*p) < 64 ) {
if ( 48 <= (*p) && (*p) <= 57 )
goto tr13;
} else if ( (*p) > 90 ) {
if ( 96 <= (*p) && (*p) <= 122 )
goto tr16;
} else
goto tr16;
goto tr2;
case 7:
switch( (*p) ) {
case 7: goto tr15;
case 27: goto tr16;
case 48: goto tr17;
case 66: goto tr18;
case 84: goto tr19;
case 87: goto tr20;
}
goto tr2;
case 16:
goto tr2;
case 8:
if ( (*p) == 92 )
goto tr15;
if ( (*p) == 59 )
goto tr21;
goto tr2;
case 9:
if ( 48 <= (*p) && (*p) <= 57 )
goto tr17;
goto tr2;
switch( (*p) ) {
case 7: goto tr23;
case 27: goto tr24;
}
goto tr22;
case 33:
switch( (*p) ) {
case 7: goto tr23;
case 27: goto tr24;
}
goto tr22;
case 10:
if ( (*p) == 59 )
goto tr18;
if ( 48 <= (*p) && (*p) <= 57 )
goto tr17;
if ( (*p) == 92 )
goto tr25;
goto tr2;
case 34:
goto tr2;
case 11:
if ( 48 <= (*p) && (*p) <= 57 )
goto tr19;
if ( (*p) == 84 )
goto tr26;
goto tr2;
case 12:
switch( (*p) ) {
case 7: goto tr20;
case 27: goto tr21;
}
if ( 48 <= (*p) && (*p) <= 57 )
goto tr19;
if ( (*p) == 78 )
goto tr27;
goto tr2;
case 13:
if ( 48 <= (*p) && (*p) <= 57 )
goto tr28;
goto tr2;
case 14:
if ( (*p) == 61 )
goto tr29;
goto tr2;
case 15:
switch( (*p) ) {
case 7: goto tr31;
case 27: goto tr32;
}
goto tr30;
case 35:
switch( (*p) ) {
case 7: goto tr31;
case 27: goto tr32;
}
goto tr30;
case 16:
if ( (*p) == 92 )
goto tr20;
goto tr33;
goto tr2;
case 17:
if ( (*p) == 73 )
goto tr34;
goto tr2;
case 18:
if ( (*p) == 84 )
goto tr35;
goto tr2;
case 19:
if ( (*p) == 76 )
goto tr36;
goto tr2;
case 20:
if ( (*p) == 69 )
goto tr37;
goto tr2;
case 21:
if ( (*p) == 61 )
goto tr38;
goto tr2;
case 22:
if ( 48 <= (*p) && (*p) <= 57 )
goto tr39;
goto tr2;
case 23:
if ( (*p) == 59 )
goto tr40;
if ( 48 <= (*p) && (*p) <= 57 )
goto tr39;
goto tr2;
case 24:
if ( 48 <= (*p) && (*p) <= 57 )
goto tr41;
goto tr2;
case 25:
switch( (*p) ) {
case 7: goto tr42;
case 27: goto tr43;
}
if ( 48 <= (*p) && (*p) <= 57 )
goto tr41;
goto tr2;
case 26:
if ( (*p) == 92 )
goto tr42;
goto tr2;
case 27:
switch( (*p) ) {
case 7: goto tr45;
case 27: goto tr46;
}
goto tr44;
case 36:
switch( (*p) ) {
case 7: goto tr45;
case 27: goto tr46;
}
goto tr44;
case 28:
if ( (*p) == 92 )
goto tr47;
goto tr2;
case 37:
goto tr2;
case 29:
goto tr48;
case 38:
goto tr2;
}
tr2: cs = 0; goto f0;
tr0: cs = 1; goto f1;
tr1: cs = 2; goto _again;
tr8: cs = 4; goto f7;
tr9: cs = 4; goto f8;
tr10: cs = 4; goto f9;
tr12: cs = 6; goto _again;
tr14: cs = 7; goto _again;
tr16: cs = 8; goto _again;
tr13: cs = 9; goto _again;
tr17: cs = 10; goto f8;
tr18: cs = 11; goto f9;
tr19: cs = 12; goto f8;
tr21: cs = 13; goto _again;
tr3: cs = 14; goto f2;
tr4: cs = 14; goto f3;
tr5: cs = 14; goto f4;
tr6: cs = 14; goto f5;
tr7: cs = 14; goto f6;
tr11: cs = 15; goto f10;
tr15: cs = 16; goto f11;
tr20: cs = 16; goto f12;
tr3: cs = 3; goto _again;
tr4: cs = 4; goto _again;
tr12: cs = 6; goto f9;
tr13: cs = 6; goto f10;
tr14: cs = 6; goto f11;
tr17: cs = 8; goto _again;
tr21: cs = 9; goto _again;
tr22: cs = 9; goto f14;
tr24: cs = 10; goto _again;
tr18: cs = 11; goto _again;
tr26: cs = 12; goto _again;
tr27: cs = 13; goto _again;
tr28: cs = 14; goto f10;
tr29: cs = 15; goto _again;
tr30: cs = 15; goto f14;
tr32: cs = 16; goto _again;
tr19: cs = 17; goto _again;
tr34: cs = 18; goto _again;
tr35: cs = 19; goto _again;
tr36: cs = 20; goto _again;
tr37: cs = 21; goto _again;
tr20: cs = 22; goto _again;
tr39: cs = 23; goto f10;
tr40: cs = 24; goto f11;
tr41: cs = 25; goto f10;
tr43: cs = 26; goto _again;
tr44: cs = 27; goto f14;
tr46: cs = 28; goto _again;
tr5: cs = 30; goto f2;
tr6: cs = 30; goto f3;
tr7: cs = 30; goto f4;
tr8: cs = 30; goto f5;
tr9: cs = 30; goto f6;
tr10: cs = 30; goto f7;
tr11: cs = 30; goto f8;
tr16: cs = 31; goto f13;
tr15: cs = 32; goto f12;
tr23: cs = 33; goto f15;
tr38: cs = 34; goto f6;
tr25: cs = 34; goto f16;
tr33: cs = 34; goto f18;
tr42: cs = 34; goto f19;
tr31: cs = 35; goto f17;
tr45: cs = 36; goto f15;
tr47: cs = 37; goto f16;
tr48: cs = 38; goto f20;
f1: _acts = _ansi_actions + 1; goto execFuncs;
f4: _acts = _ansi_actions + 3; goto execFuncs;
f7: _acts = _ansi_actions + 5; goto execFuncs;
f8: _acts = _ansi_actions + 7; goto execFuncs;
f9: _acts = _ansi_actions + 9; goto execFuncs;
f10: _acts = _ansi_actions + 11; goto execFuncs;
f9: _acts = _ansi_actions + 5; goto execFuncs;
f10: _acts = _ansi_actions + 7; goto execFuncs;
f11: _acts = _ansi_actions + 9; goto execFuncs;
f13: _acts = _ansi_actions + 11; goto execFuncs;
f0: _acts = _ansi_actions + 13; goto execFuncs;
f5: _acts = _ansi_actions + 15; goto execFuncs;
f11: _acts = _ansi_actions + 17; goto execFuncs;
f12: _acts = _ansi_actions + 19; goto execFuncs;
f6: _acts = _ansi_actions + 21; goto execFuncs;
f2: _acts = _ansi_actions + 23; goto execFuncs;
f3: _acts = _ansi_actions + 25; goto execFuncs;
f6: _acts = _ansi_actions + 17; goto execFuncs;
f19: _acts = _ansi_actions + 19; goto execFuncs;
f14: _acts = _ansi_actions + 21; goto execFuncs;
f16: _acts = _ansi_actions + 23; goto execFuncs;
f18: _acts = _ansi_actions + 25; goto execFuncs;
f8: _acts = _ansi_actions + 27; goto execFuncs;
f3: _acts = _ansi_actions + 29; goto execFuncs;
f7: _acts = _ansi_actions + 31; goto execFuncs;
f2: _acts = _ansi_actions + 33; goto execFuncs;
f20: _acts = _ansi_actions + 35; goto execFuncs;
f12: _acts = _ansi_actions + 37; goto execFuncs;
f15: _acts = _ansi_actions + 40; goto execFuncs;
f17: _acts = _ansi_actions + 43; goto execFuncs;
execFuncs:
_nacts = *_acts++;
while ( _nacts-- > 0 ) {
switch ( *_acts++ ) {
case 0:
/* #line 54 "user/ansi_parser.rl" */
/* #line 112 "user/ansi_parser.rl" */
{
if ((*p) != 0) {
apars_handle_plainchar((*p));
}
}
break;
case 1:
/* #line 61 "user/ansi_parser.rl" */
/* #line 121 "user/ansi_parser.rl" */
{
/* Reset the CSI builder */
// Reset the CSI builder
csi_leading = csi_char = 0;
csi_ni = 0;
csi_cnt = 0;
/* Zero out digits */
// Zero out digits
for(int i = 0; i < CSI_N_MAX; i++) {
csi_n[i] = 0;
}
{cs = 3;goto _again;}
{cs = 5;goto _again;}
}
break;
case 2:
/* #line 74 "user/ansi_parser.rl" */
/* #line 135 "user/ansi_parser.rl" */
{
csi_leading = (*p);
}
break;
case 3:
/* #line 78 "user/ansi_parser.rl" */
/* #line 139 "user/ansi_parser.rl" */
{
/* x10 + digit */
if (csi_cnt == 0) csi_cnt = 1;
// x10 + digit
if (csi_ni < CSI_N_MAX) {
csi_n[csi_ni] = csi_n[csi_ni]*10 + ((*p) - '0');
}
}
break;
case 4:
/* #line 85 "user/ansi_parser.rl" */
/* #line 147 "user/ansi_parser.rl" */
{
if (csi_cnt == 0) csi_cnt = 1; // handle case when first arg is empty
csi_cnt++;
csi_ni++;
}
break;
case 5:
/* #line 89 "user/ansi_parser.rl" */
/* #line 153 "user/ansi_parser.rl" */
{
csi_char = (*p);
apars_handle_CSI(csi_leading, csi_n, csi_char);
apars_handle_CSI(csi_leading, csi_n, csi_cnt, csi_char);
{cs = 1;goto _again;}
}
break;
case 6:
/* #line 97 "user/ansi_parser.rl" */
/* #line 159 "user/ansi_parser.rl" */
{
ansi_warn("Invalid escape sequence discarded.");
apars_handle_badseq();
{cs = 1;goto _again;}
}
break;
case 7:
/* #line 114 "user/ansi_parser.rl" */
/* #line 177 "user/ansi_parser.rl" */
{
csi_ni = 0;
@@ -297,47 +517,86 @@ execFuncs:
csi_n[i] = 0;
}
{cs = 5;goto _again;}
osc_bi = 0;
osc_buffer[0] = '\0';
{cs = 7;goto _again;}
}
break;
case 8:
/* #line 125 "user/ansi_parser.rl" */
/* #line 192 "user/ansi_parser.rl" */
{
apars_handle_OSC_FactoryReset();
{cs = 1;goto _again;}
osc_bi = 0;
osc_buffer[0] = '\0';
{cs = 27;goto _again;}
}
break;
case 9:
/* #line 130 "user/ansi_parser.rl" */
/* #line 198 "user/ansi_parser.rl" */
{
apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]);
{cs = 1;goto _again;}
}
break;
case 10:
/* #line 141 "user/ansi_parser.rl" */
/* #line 203 "user/ansi_parser.rl" */
{
// Reset screen
apars_handle_RESET_cmd();
{cs = 1;goto _again;}
osc_buffer[osc_bi++] = (*p);
}
break;
case 11:
/* #line 147 "user/ansi_parser.rl" */
/* #line 207 "user/ansi_parser.rl" */
{
apars_handle_saveCursorAttrs();
osc_buffer[osc_bi++] = '\0';
apars_handle_OSC_SetTitle(osc_buffer);
{cs = 1;goto _again;}
}
break;
case 12:
/* #line 152 "user/ansi_parser.rl" */
/* #line 213 "user/ansi_parser.rl" */
{
apars_handle_restoreCursorAttrs();
osc_buffer[osc_bi++] = '\0';
apars_handle_OSC_SetButton(csi_n[0], osc_buffer);
{cs = 1;goto _again;}
}
break;
/* #line 341 "user/ansi_parser.c" */
case 13:
/* #line 245 "user/ansi_parser.rl" */
{
apars_handle_hashCode((*p));
{cs = 1;goto _again;}
}
break;
case 14:
/* #line 250 "user/ansi_parser.rl" */
{
apars_handle_shortCode((*p));
{cs = 1;goto _again;}
}
break;
case 15:
/* #line 255 "user/ansi_parser.rl" */
{
apars_handle_setXCtrls((*p));
{cs = 1;goto _again;}
}
break;
case 16:
/* #line 260 "user/ansi_parser.rl" */
{
// abuse the buffer for storing the leading char
osc_buffer[0] = (*p);
{cs = 29;goto _again;}
}
break;
case 17:
/* #line 266 "user/ansi_parser.rl" */
{
apars_handle_characterSet(osc_buffer[0], (*p));
{cs = 1;goto _again;}
}
break;
/* #line 600 "user/ansi_parser.c" */
}
}
goto _again;
@@ -355,15 +614,16 @@ _again:
while ( __nacts-- > 0 ) {
switch ( *__acts++ ) {
case 6:
/* #line 97 "user/ansi_parser.rl" */
/* #line 159 "user/ansi_parser.rl" */
{
ansi_warn("Invalid escape sequence discarded.");
apars_handle_badseq();
{cs = 1; if ( p == pe )
goto _test_eof;
goto _again;}
}
break;
/* #line 367 "user/ansi_parser.c" */
/* #line 627 "user/ansi_parser.c" */
}
}
}
@@ -371,6 +631,8 @@ goto _again;}
_out: {}
}
/* #line 171 "user/ansi_parser.rl" */
/* #line 290 "user/ansi_parser.rl" */
}
// 'ESC k blah OSC_end' is a shortcut for setting title (k is defined in GNU screen as Title Definition String)
+28 -7
View File
@@ -2,18 +2,36 @@
#define ANSI_PARSER_H
#include <stdlib.h>
#include <screen.h>
// Max nr of CSI parameters
#define CSI_N_MAX 3
#define CSI_N_MAX 10
#define OSC_CHAR_MAX TERM_TITLE_LEN
extern void apars_handle_badseq(void);
extern void apars_handle_CSI(char leadchar, int *params, char keychar);
extern void apars_handle_RESET_cmd(void);
extern void apars_handle_plainchar(char c);
extern void apars_handle_OSC_FactoryReset(void);
extern void apars_handle_CSI(char leadchar, int *params, int count, char keychar);
extern void apars_handle_OSC_SetScreenSize(int rows, int cols);
extern void apars_handle_saveCursorAttrs(void);
extern void apars_handle_restoreCursorAttrs(void);
extern void apars_handle_OSC_SetButton(int num, const char *buffer);
extern void apars_handle_OSC_SetTitle(const char *buffer);
extern void apars_handle_shortCode(char c);
extern void apars_handle_hashCode(char c);
extern void apars_handle_characterSet(char leadchar, char c);
extern void apars_handle_setXCtrls(char c);
extern void apars_reset_utf8buffer(void);
void ansi_parser_reset(void);
extern volatile u32 ansi_parser_char_cnt;
// defined in the makefile
#if DEBUG_ANSI
#define ansi_warn warn
#define ansi_dbg dbg
#else
#define ansi_warn(...)
#define ansi_dbg(...)
#endif
/**
* \brief Linear ANSI chars stream parser
@@ -30,4 +48,7 @@ extern void apars_handle_restoreCursorAttrs(void);
*/
void ansi_parser(const char *newdata, size_t len);
/** This shows a short error message and prints the history (if any) */
void apars_handle_badseq(void);
#endif // ANSI_PARSER_H
+141 -20
View File
@@ -1,5 +1,6 @@
#include <esp8266.h>
#include "ansi_parser.h"
#include "screen.h"
/* Ragel constants block */
%%{
@@ -7,6 +8,47 @@
write data;
}%%
static volatile int cs = -1;
volatile u32 ansi_parser_char_cnt = 0;
void ICACHE_FLASH_ATTR
ansi_parser_reset(void) {
if (cs != ansi_start) {
cs = ansi_start;
apars_reset_utf8buffer();
ansi_warn("Parser timeout, state reset");
}
}
#define HISTORY_LEN 16
#if DEBUG_ANSI
static char history[HISTORY_LEN + 1];
#endif
void ICACHE_FLASH_ATTR
apars_handle_badseq(void)
{
#if DEBUG_ANSI
char buf1[HISTORY_LEN*3+2];
char buf2[HISTORY_LEN*3+2];
char *b1 = buf1;
char *b2 = buf2;
char c;
for(int i=0;i<HISTORY_LEN;i++) {
c = history[i];
b1 += sprintf(b1, "%2X ", c);
if (c < 32 || c > 127) c = '.';
b2 += sprintf(b2, "%c ", c);
}
ansi_dbg("Context: %s", buf2);
ansi_dbg(" %s", buf1);
#endif
}
/**
* \brief Linear ANSI chars stream parser
*
@@ -23,13 +65,16 @@
void ICACHE_FLASH_ATTR
ansi_parser(const char *newdata, size_t len)
{
static int cs = -1;
// The CSI code is built here
static char csi_leading; //!< Leading char, 0 if none
static int csi_ni; //!< Number of the active digit
static int csi_cnt; //!< Digit count
static int csi_n[CSI_N_MAX]; //!< Param digits
static char csi_char; //!< CSI action char (end)
static char osc_buffer[OSC_CHAR_MAX];
static int osc_bi;
ansi_parser_char_cnt++;
if (len == 0) len = strlen(newdata);
@@ -41,29 +86,45 @@ ansi_parser(const char *newdata, size_t len)
// Init Ragel on the first run
if (cs == -1) {
%% write init;
#if DEBUG_ANSI
memset(history, 0, sizeof(history));
#endif
}
#if DEBUG_ANSI
for(int i=len; i<HISTORY_LEN; i++) {
history[i-len] = history[i];
}
strcpy(&history[HISTORY_LEN-len], newdata);
#endif
// The parser
%%{
#/*
ESC = 27;
NOESC = (any - ESC);
TOK_ST = ESC '\\'; # String terminator - used for OSC commands
OSC_END = ('\a' | ESC '\\');
# --- Regular characters to be printed ---
action plain_char {
if (fc != 0) {
apars_handle_plainchar(fc);
}
}
# --- CSI CSI commands (Select Graphic Rendition) ---
# Text color & style
action CSI_start {
/* Reset the CSI builder */
// Reset the CSI builder
csi_leading = csi_char = 0;
csi_ni = 0;
csi_cnt = 0;
/* Zero out digits */
// Zero out digits
for(int i = 0; i < CSI_N_MAX; i++) {
csi_n[i] = 0;
}
@@ -76,25 +137,27 @@ ansi_parser(const char *newdata, size_t len)
}
action CSI_digit {
/* x10 + digit */
if (csi_cnt == 0) csi_cnt = 1;
// x10 + digit
if (csi_ni < CSI_N_MAX) {
csi_n[csi_ni] = csi_n[csi_ni]*10 + (fc - '0');
}
}
action CSI_semi {
if (csi_cnt == 0) csi_cnt = 1; // handle case when first arg is empty
csi_cnt++;
csi_ni++;
}
action CSI_end {
csi_char = fc;
apars_handle_CSI(csi_leading, csi_n, csi_char);
apars_handle_CSI(csi_leading, csi_n, csi_cnt, csi_char);
fgoto main;
}
action errBadSeq {
ansi_warn("Invalid escape sequence discarded.");
apars_handle_badseq();
fgoto main;
}
@@ -105,7 +168,7 @@ ansi_parser(const char *newdata, size_t len)
CSI_body := ((32..47|60..64) @CSI_leading)?
((digit @CSI_digit)* ';' @CSI_semi)*
(digit @CSI_digit)* alpha @CSI_end $!errBadSeq;
(digit @CSI_digit)* (alpha|'`'|'@') @CSI_end $!errBadSeq;
# --- OSC commands (Operating System Commands) ---
@@ -119,25 +182,50 @@ ansi_parser(const char *newdata, size_t len)
csi_n[i] = 0;
}
osc_bi = 0;
osc_buffer[0] = '\0';
fgoto OSC_body;
}
action OSC_fr {
apars_handle_OSC_FactoryReset();
fgoto main;
# collecting title string; this can also be entered by ESC k
action SetTitle_start {
osc_bi = 0;
osc_buffer[0] = '\0';
fgoto TITLE_body;
}
action OSC_resize {
apars_handle_OSC_SetScreenSize(csi_n[0], csi_n[1]);
fgoto main;
}
action OSC_text_char {
osc_buffer[osc_bi++] = fc;
}
action OSC_title {
osc_buffer[osc_bi++] = '\0';
apars_handle_OSC_SetTitle(osc_buffer);
fgoto main;
}
action OSC_button {
osc_buffer[osc_bi++] = '\0';
apars_handle_OSC_SetButton(csi_n[0], osc_buffer);
fgoto main;
}
# 0; is xterm title hack
OSC_body := (
("FR" ('\a' | ESC '\\') @OSC_fr) |
('W' (digit @CSI_digit)+ ';' @CSI_semi (digit @CSI_digit)+ ('\a' | ESC '\\') @OSC_resize)
("BTN" digit @CSI_digit '=' (NOESC @OSC_text_char)* OSC_END @OSC_button) |
("TITLE=" @SetTitle_start) |
("0;" (NOESC @OSC_text_char)* OSC_END @OSC_title) |
('W' (digit @CSI_digit)+ ';' @CSI_semi (digit @CSI_digit)+ OSC_END @OSC_resize)
) $!errBadSeq;
TITLE_body := (NOESC @OSC_text_char)* OSC_END @OSC_title $!errBadSeq;
action RESET_cmd {
// Reset screen
apars_handle_RESET_cmd();
@@ -154,19 +242,52 @@ ansi_parser(const char *newdata, size_t len)
fgoto main;
}
action HASH_code {
apars_handle_hashCode(fc);
fgoto main;
}
action SHORT_code {
apars_handle_shortCode(fc);
fgoto main;
}
action SetXCtrls {
apars_handle_setXCtrls(fc);
fgoto main;
}
action CharsetCmd_start {
// abuse the buffer for storing the leading char
osc_buffer[0] = fc;
fgoto charsetcmd_body;
}
action CharsetCmd_end {
apars_handle_characterSet(osc_buffer[0], fc);
fgoto main;
}
charsetcmd_body := (any @CharsetCmd_end) $!errBadSeq;
# --- Main parser loop ---
main :=
(
(NOESC @plain_char)* ESC (
'[' @CSI_start |
']' @OSC_start |
'c' @RESET_cmd |
'7' @CSI_SaveCursorAttrs |
'8' @CSI_RestoreCursorAttrs
('[' @CSI_start) |
(']' @OSC_start) |
('#' digit @HASH_code) |
('k' @SetTitle_start) |
([a-jl-zA-Z0-9=<>] @SHORT_code) |
([()*+-./] @CharsetCmd_start) |
(' ' [FG] @SetXCtrls)
)
)+ $!errBadSeq;
write exec;
#*/
}%%
}
// 'ESC k blah OSC_end' is a shortcut for setting title (k is defined in GNU screen as Title Definition String)
+349 -154
View File
@@ -10,21 +10,100 @@
#include "ansi_parser.h"
#include "uart_driver.h"
// screen manpage - https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html
static char utf_collect[4];
static int utf_i = 0;
static int utf_j = 0;
/**
* Handle a received plain character
*/
void ICACHE_FLASH_ATTR
apars_handle_plainchar(char c)
{
screen_putchar(c);
if (c == 7) return; // BELL - beep (TODO play beep in browser)
// collecting unicode glyphs...
if (c & 0x80) {
if (utf_i == 0) {
// start
if (c == 192 || c == 193 || c >= 245) {
// forbidden codes
goto fail;
}
/**
* Bad sequence received, show warning
*/
void apars_handle_badseq(void)
if ((c & 0xE0) == 0xC0) {
utf_i = 2;
}
else if ((c & 0xF0) == 0xE0) {
utf_i = 3;
}
else if ((c & 0xF8) == 0xF0) {
utf_i = 4;
}
else {
// chars over 127 that don't start unicode sequences
goto fail;
}
utf_collect[0] = c;
utf_j = 1;
}
else {
if ((c & 0xC0) != 0x80) {
goto fail;
}
else {
utf_collect[utf_j++] = c;
if (utf_j >= utf_i) {
screen_putchar(utf_collect);
apars_reset_utf8buffer();
}
}
}
}
else {
if (c == 14) {
screen_set_charset_n(1);
return;
}
if (c == 15) {
screen_set_charset_n(0);
return;
}
utf_collect[0] = c;
utf_collect[1] = 0; // just to make sure it's closed...
screen_putchar(utf_collect);
}
return;
fail:
ansi_warn("Bad UTF-8");
apars_reset_utf8buffer();
}
void ICACHE_FLASH_ATTR
apars_reset_utf8buffer(void)
{
warn("Invalid escape sequence discarded.");
utf_i = 0;
utf_j = 0;
memset(utf_collect, 0, 4);
}
void ICACHE_FLASH_ATTR
apars_handle_characterSet(char leadchar, char c)
{
if (leadchar == '(') screen_set_charset(0, c);
else if (leadchar == ')') screen_set_charset(1, c);
}
void ICACHE_FLASH_ATTR
apars_handle_setXCtrls(char c)
{
// this does not seem to do anything, sent by some unix programs
// ansi_warn("NOIMPL Select %cbit ctrls", c=='F'? '7':'8');
}
/**
@@ -34,47 +113,31 @@ void apars_handle_badseq(void)
* \param keychar - the char terminating the sequence
*/
void ICACHE_FLASH_ATTR
apars_handle_CSI(char leadchar, int *params, char keychar)
apars_handle_CSI(char leadchar, int *params, int count, char keychar)
{
/*
Implemented codes (from Wikipedia)
CSI n A CUU Cursor Up
CSI n B CUD Cursor Down
CSI n C CUF Cursor Forward
CSI n D CUB Cursor Back
CSI n E CNL Cursor Next Line
CSI n F CPL Cursor Previous Line
CSI n G CHA Cursor Horizontal Absolute
CSI n ; m H CUP Cursor Position
CSI n J ED Erase Display
CSI n K EL Erase in Line
CSI n S SU Scroll Up
CSI n T SD Scroll Down
CSI n ; m f HVP Horizontal and Vertical Position
CSI n m SGR Select Graphic Rendition (Implemented only some)
CSI 6n DSR Device Status Report
CSI s SCP Save Cursor Position
CSI u RCP Restore Cursor Position
CSI ?25l DECTCEM Hides the cursor
CSI ?25h DECTCEM Shows the cursor
*/
int n1 = params[0];
int n2 = params[1];
// int n3 = params[2];
int n3 = params[2];
static char buf[20];
// defaults
switch (keychar) {
case 'A':
case 'A': // move
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'S':
case 'G': // set X
case '`':
case 'S': // scrolling
case 'T':
case 'X': // clear in line
case 'd': // set Y
case 'L':
case 'M':
case '@':
case 'P':
if (n1 == 0) n1 = 1;
break;
@@ -92,60 +155,73 @@ apars_handle_CSI(char leadchar, int *params, char keychar)
switch (keychar) {
// CUU CUD CUF CUB
case 'A': screen_cursor_move(-n1, 0); break;
case 'B': screen_cursor_move(n1, 0); break;
case 'C': screen_cursor_move(0, n1); break;
case 'D': screen_cursor_move(0, -n1); break;
case 'A': screen_cursor_move(-n1, 0, false); break;
case 'B': screen_cursor_move(n1, 0, false); break;
case 'C': screen_cursor_move(0, n1, false); break;
case 'D': screen_cursor_move(0, -n1, false); break;
case 'E': // CNL
screen_cursor_move(n1, 0);
case 'E': // CNL - Cursor Next Line
screen_cursor_move(n1, 0, false);
screen_cursor_set_x(0);
break;
case 'F': // CPL
screen_cursor_move(-n1, 0);
case 'F': // CPL - Cursor Prev Line
screen_cursor_move(-n1, 0, false);
screen_cursor_set_x(0);
break;
// CHA
// Set X
case 'G':
screen_cursor_set_x(n1 - 1); break; // 1-based
case '`': // alternate code
screen_cursor_set_x(n1 - 1);
break; // 1-based
// SU, SD
// Set Y
case 'd':
screen_cursor_set_y(n1 - 1);
break; // 1-based
// clear in line
case 'X':
screen_clear_in_line(n1);
break; // 1-based
// SU, SD - scroll up/down
case 'S': screen_scroll_up(n1); break;
case 'T': screen_scroll_down(n1); break;
// CUP,HVP
// CUP,HVP - set position
case 'H':
case 'f':
screen_cursor_set(n1-1, n2-1); break; // 1-based
screen_cursor_set(n1-1, n2-1);
break; // 1-based
case 'J': // ED
case 'J': // ED - clear screen
if (n1 == 0) {
screen_clear(CLEAR_TO_CURSOR);
} else if (n1 == 1) {
screen_clear(CLEAR_FROM_CURSOR);
} else if (n1 == 1) {
screen_clear(CLEAR_TO_CURSOR);
} else {
screen_clear(CLEAR_ALL);
screen_cursor_set(0, 0);
}
break;
case 'K': // EL
case 'K': // EL - clear line
if (n1 == 0) {
screen_clear_line(CLEAR_TO_CURSOR);
} else if (n1 == 1) {
screen_clear_line(CLEAR_FROM_CURSOR);
} else if (n1 == 1) {
screen_clear_line(CLEAR_TO_CURSOR);
} else {
screen_clear_line(CLEAR_ALL);
}
break;
// SCP, RCP
// SCP, RCP - save/restore position
case 's': screen_cursor_save(0); break;
case 'u': screen_cursor_restore(0); break;
case 'n':
case 'n': // queries
if (n1 == 6) {
// Query cursor position
char buf[20];
@@ -158,136 +234,238 @@ apars_handle_CSI(char leadchar, int *params, char keychar)
// Query device status - reply "Device is OK"
UART_WriteString(UART0, "\033[0n", UART_TIMEOUT_US);
}
else {
ansi_warn("NOIMPL query %d", n1);
}
break;
// DECTCEM feature enable / disable
case 'h':
case 'h': // feature enable
if (leadchar == '?') {
if (n1 == 25) {
screen_cursor_enable(1);
} else if (n1 == 7) {
screen_wrap_enable(1);
if (n1 == 25) screen_cursor_enable(1);
else if (n1 == 7) screen_wrap_enable(1);
else if (n1 == 1) {
// TODO something with arrow keys??
}
else {
ansi_warn("NOIMPL DEC opt %d", n1);
}
}
else {
if (n1 == 4) {
// TODO insert mode, i think this is to suppress user input
}
else {
ansi_warn("NOIMPL flag %d", n1);
}
}
break;
case 'l':
case 'l': // feature disable
if (leadchar == '?') {
if (n1 == 25) {
screen_cursor_enable(0);
} else if (n1 == 7) {
screen_wrap_enable(0);
if (n1 == 25) screen_cursor_enable(0);
else if (n1 == 7) screen_wrap_enable(0);
else if (n1 == 1) {
// TODO see above
}
else {
ansi_warn("NOIMPL DEC opt %d", n1);
}
}
else {
if (n1 == 4) {
// TODO see above
}
else {
ansi_warn("NOIMPL flag %d", n1);
}
}
break;
case 'm': // SGR
case 'm': // SGR - graphics rendition aka attributes
if (count == 0) {
count = 1; // this makes it work as 0 (reset)
}
// iterate arguments
for (int i = 0; i < CSI_N_MAX; i++) {
for (int i = 0; i < count; i++) {
int n = params[i];
if (i == 0 && n == 0) { // reset SGR
screen_set_fg(7);
screen_set_bg(0);
break; // cannot combine reset with others
if (n == 0) { // reset SGR
screen_reset_cursor(); // resets colors, inverse and bold.
}
else if (n >= 30 && n <= 37) screen_set_fg((Color) (n - 30)); // ANSI normal fg
else if (n >= 40 && n <= 47) screen_set_bg((Color) (n - 40)); // ANSI normal bg
else if (n == 39) screen_set_fg(termconf_scratch.default_fg); // default fg
else if (n == 49) screen_set_bg(termconf_scratch.default_bg); // default bg
else if (n == 1) screen_attr_enable(ATTR_BOLD);
else if (n == 2) screen_attr_enable(ATTR_FAINT);
else if (n == 3) screen_attr_enable(ATTR_ITALIC);
else if (n == 4) screen_attr_enable(ATTR_UNDERLINE);
else if (n == 5 || n == 6) screen_attr_enable(ATTR_BLINK); // 6 - rapid blink, not supported
else if (n == 7) screen_inverse_enable(true);
else if (n == 9) screen_attr_enable(ATTR_STRIKE);
else if (n == 20) screen_attr_enable(ATTR_FRAKTUR);
else if (n == 21) screen_attr_disable(ATTR_BOLD);
else if (n == 22) screen_attr_disable(ATTR_FAINT);
else if (n == 23) screen_attr_disable(ATTR_ITALIC|ATTR_FRAKTUR);
else if (n == 24) screen_attr_disable(ATTR_UNDERLINE);
else if (n == 25) screen_attr_disable(ATTR_BLINK);
else if (n == 27) screen_inverse_enable(false);
else if (n == 29) screen_attr_disable(ATTR_STRIKE);
else if (n >= 90 && n <= 97) screen_set_fg((Color) (n - 90 + 8)); // AIX bright fg
else if (n >= 100 && n <= 107) screen_set_bg((Color) (n - 100 + 8)); // AIX bright bg
else {
ansi_warn("NOIMPL SGR attr %d", n);
}
else if (n >= 30 && n <= 37) screen_set_fg(n-30); // ANSI normal fg
else if (n >= 40 && n <= 47) screen_set_bg(n-40); // ANSI normal bg
else if (n == 39) screen_set_fg(7); // default fg
else if (n == 49) screen_set_bg(0); // default bg
else if (n == 7) screen_inverse(1); // inverse
else if (n == 27) screen_inverse(0); // positive
else if (n == 1) screen_set_bright_fg(); // ANSI bold = bright fg
else if (n >= 90 && n <= 97) screen_set_fg(n-90+8); // AIX bright fg
else if (n >= 100 && n <= 107) screen_set_bg(n-100+8); // AIX bright bg
}
break;
case 't': // xterm hacks
switch(n1) {
case 8: // set size
screen_resize(n2, n3);
break;
case 18: // report size
printf(buf, "\033[8;%d;%dt", termconf_scratch.height, termconf_scratch.width);
UART_WriteString(UART0, buf, UART_TIMEOUT_US);
break;
case 11: // Report iconified -> is not iconified
UART_WriteString(UART0, "\033[1t", UART_TIMEOUT_US);
break;
case 21: // Report title
UART_WriteString(UART0, "\033]L", UART_TIMEOUT_US);
UART_WriteString(UART0, termconf_scratch.title, UART_TIMEOUT_US);
UART_WriteString(UART0, "\033\\", UART_TIMEOUT_US);
break;
case 24: // Set Height only
screen_resize(n2, termconf_scratch.width);
break;
}
}
break;
void ICACHE_FLASH_ATTR apars_handle_saveCursorAttrs(void)
{
screen_cursor_save(1);
}
case 'L':
screen_insert_lines(n1);
break;
void ICACHE_FLASH_ATTR apars_handle_restoreCursorAttrs(void)
{
screen_cursor_restore(1);
}
case 'M':
screen_delete_lines(n1);
break;
/**
* \brief Handle a request to reset the display device
*/
void ICACHE_FLASH_ATTR
apars_handle_RESET_cmd(void)
{
// XXX maybe user wanted to reset the module instead?
screen_reset();
}
case '@':
screen_insert_characters(n1);
break;
/**
* Handle a factory reset request
*/
void ICACHE_FLASH_ATTR
apars_handle_OSC_FactoryReset(void)
{
warn("-------- Factory reset --------");
case 'P':
screen_delete_characters(n1);
break;
dbg("Switching to Client+AP mode");
case 'r':
// TODO scrolling region
// ansi_warn("NOIMPL scrolling region");
break;
// Send acknowledgement message to UART0
// User is performing this manually, so we can just print it as string
UART_WriteString(UART0, "\r\nFACTORY RESET\r\n", UART_TIMEOUT_US);
case 'g':
// TODO clear tab
ansi_warn("NOIMPL clear tab");
break;
// Disconnect from AP if connected
int opmode = wifi_get_opmode();
if (opmode != SOFTAP_MODE) {
wifi_station_disconnect();
}
case 'Z':
// TODO back tab
ansi_warn("NOIMPL cursor back tab");
break;
// Both must be enabled so we can manipulate their settings
wifi_set_opmode(STATIONAP_MODE);
case 'q':
// TODO setmode (??)
ansi_warn("NOIMPL CSI setmode %d", n1);
break;
// --- AP config ---
dbg("AP WiFi channel: 6");
struct softap_config apconf;
wifi_softap_get_config(&apconf);
apconf.authmode=AUTH_OPEN; // Disable access protection
apconf.channel=6; // Reset channel; user may have set bad channel in the UI
// generate unique AP name
u8 mac[6];
wifi_get_macaddr(SOFTAP_IF, mac);
sprintf((char*)apconf.ssid, "TERM-%02X%02X%02X", mac[3], mac[4], mac[5]);
apconf.ssid_len = (u8)strlen((char*)apconf.ssid);
info("New AP name: %s", (char*)apconf.ssid);
// --- Station ---
dbg("Erasing stored WiFi credentials...");
struct station_config staconf;
wifi_station_get_config(&staconf);
// clear info about SSID
staconf.ssid[0]=0;
staconf.bssid_set=0;
staconf.password[0]=0;
dbg("Commiting changes...");
wifi_softap_set_config(&apconf);
wifi_station_set_config(&staconf);
UART_WriteString(UART0, "Factory Reset complete, device reset.\r\n\r\n", UART_TIMEOUT_US);
info("*** FACTORY RESET COMPLETE ***");
dbg("Device reset...");
// Reboot to clean STA+AP mode with Channel X & reset AP SSID.
case 'p':
if (leadchar == '!') {
info("SOFT RESET!");
system_restart();
}
break;
case 'c':
// report capabilities (pretend we're vt4xx)
UART_WriteString(UART0, "\033[?64;22;c", UART_TIMEOUT_US);
break;
default:
ansi_warn("Unknown CSI: %c", keychar);
apars_handle_badseq();
}
}
/** codes in the format ESC # n */
void ICACHE_FLASH_ATTR apars_handle_hashCode(char c)
{
switch(c) {
case '8':
screen_fill_with_E();
break;
default:
ansi_warn("Unknown # sequence: %c", c);
}
}
/** those are single-character escape codes (ESC x) */
void ICACHE_FLASH_ATTR apars_handle_shortCode(char c)
{
switch(c) {
case 'c': // screen reset
screen_reset();
break;
case '7': // save cursor + attrs
screen_cursor_save(true);
break;
case '8': // restore cursor + attrs
screen_cursor_restore(true);
break;
case 'E': // same as CR LF
screen_cursor_move(1, 0, false);
screen_cursor_set_x(0);
break;
case 'D': // move cursor down, scroll screen up if needed
screen_cursor_move(1, 0, true);
break;
case 'M': // move cursor up, scroll screen down if needed
screen_cursor_move(-1, 0, true);
break;
case 'H':
// TODO set tab
// ansi_warn("NOIMPL set tab");
break;
// TODO those don't seem to do anything
case '>':
// ansi_warn("NOIMPL NUMKP");
break;
case '<':
// ansi_warn("NOIMPL SETANSI");
break;
case '=':
// ansi_warn("NOIMPL ALTKP");
break;
default:
ansi_warn("Unknown 1-char seq %c", c);
}
}
/**
* Handle a screen resize request
@@ -299,3 +477,20 @@ apars_handle_OSC_SetScreenSize(int rows, int cols)
screen_resize(rows, cols);
}
void ICACHE_FLASH_ATTR
apars_handle_OSC_SetButton(int num, const char *buffer)
{
strncpy(termconf_scratch.btn[num-1], buffer, TERM_BTN_LEN);
info("OSC: Set BTN%d = %s", num, buffer);
screen_notifyChange(CHANGE_LABELS);
}
void ICACHE_FLASH_ATTR
apars_handle_OSC_SetTitle(const char *buffer)
{
strncpy(termconf_scratch.title, buffer, TERM_TITLE_LEN);
info("OSC: Set TITLE = %s", buffer);
screen_notifyChange(CHANGE_LABELS);
}
+8 -6
View File
@@ -1,13 +1,15 @@
#ifndef ANSI_PARSER_CALLBACKS_H
#define ANSI_PARSER_CALLBACKS_H
void apars_handle_badseq(void);
void apars_handle_CSI(char leadchar, int *params, char keychar);
void apars_handle_RESET_cmd(void);
void apars_handle_plainchar(char c);
void apars_handle_OSC_FactoryReset(void);
void apars_handle_CSI(char leadchar, int *params, int count, char keychar);
void apars_handle_OSC_SetScreenSize(int rows, int cols);
void apars_handle_saveCursorAttrs(void);
void apars_handle_restoreCursorAttrs(void);
void apars_handle_OSC_SetButton(int num, const char *buffer);
void apars_handle_OSC_SetTitle(const char *buffer);
void apars_handle_shortCode(char c);
void apars_handle_hashCode(char c);
void apars_handle_characterSet(char leadchar, char c);
void apars_handle_setXCtrls(char c);
void apars_reset_utf8buffer(void);
#endif //ESP_VT100_FIRMWARE_ANSI_PARSER_CALLBACKS_H
-9
View File
@@ -1,9 +0,0 @@
#ifndef CGIAPPCFG_H
#define CGIAPPCFG_H
#include "httpd.h"
httpd_cgi_state cgiAppCfgSetParams(HttpdConnData *connData);
httpd_cgi_state tplAppCfg(HttpdConnData *connData, char *token, void **arg);
#endif
+41 -40
View File
@@ -1,6 +1,7 @@
#include <esp8266.h>
#include <httpd.h>
#include <esp_sdk_ver.h>
#include <httpdespfs.h>
#include "cgi_main.h"
#include "screen.h"
@@ -19,70 +20,70 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token
{
if (token == NULL) {
// Release data object
screenSerializeToBuffer(NULL, 0, arg);
return HTTPD_CGI_DONE;
}
char buff[150];
if (streq(token, "labels_seq")) {
screenSerializeLabelsToBuffer(buff, 150);
tplSend(connData, buff, -1);
}
else if (streq(token, "theme")) {
sprintf(buff, "%d", termconf->theme);
tplSend(connData, buff, -1);
}
return HTTPD_CGI_DONE;
}
httpd_cgi_state ICACHE_FLASH_ATTR
cgiTermInitialImage(HttpdConnData *connData)
{
const int bufsiz = 512;
char buff[bufsiz];
if (streq(token, "term_title")) {
httpdSend(connData, termconf->title, -1);
}
else if (streq(token, "btn1")) {
httpdSend(connData, termconf->btn1, -1);
}
else if (streq(token, "btn2")) {
httpdSend(connData, termconf->btn2, -1);
}
else if (streq(token, "btn3")) {
httpdSend(connData, termconf->btn3, -1);
}
else if (streq(token, "btn4")) {
httpdSend(connData, termconf->btn4, -1);
}
else if (streq(token, "btn5")) {
httpdSend(connData, termconf->btn5, -1);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
httpdSend(connData, buff, -1);
}
else if (streq(token, "default_fg")) {
sprintf(buff, "%d", termconf->default_fg);
httpdSend(connData, buff, -1);
}
else if (streq(token, "screenData")) {
httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, arg);
httpdSend(connData, buff, -1);
return cont;
}
if (connData->conn == NULL) {
//Connection aborted. Clean up.
// Release data object
screenSerializeToBuffer(NULL, 0, &connData->cgiData);
return HTTPD_CGI_DONE;
}
if (connData->cgiData == NULL) {
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/octet-stream");
httpdEndHeaders(connData);
}
httpd_cgi_state cont = screenSerializeToBuffer(buff, bufsiz, &connData->cgiData);
httpdSend(connData, buff, -1); // no encode
return cont;
}
/** "About" page */
httpd_cgi_state ICACHE_FLASH_ATTR tplAbout(HttpdConnData *connData, char *token, void **arg)
httpd_cgi_state ICACHE_FLASH_ATTR
tplAbout(HttpdConnData *connData, char *token, void **arg)
{
if (token == NULL) return HTTPD_CGI_DONE;
if (streq(token, "vers_fw")) {
httpdSend(connData, FIRMWARE_VERSION, -1);
tplSend(connData, FIRMWARE_VERSION, -1);
}
else if (streq(token, "date")) {
httpdSend(connData, __DATE__, -1);
tplSend(connData, __DATE__, -1);
}
else if (streq(token, "time")) {
httpdSend(connData, __TIME__, -1);
tplSend(connData, __TIME__, -1);
}
else if (streq(token, "vers_httpd")) {
httpdSend(connData, HTTPDVER, -1);
tplSend(connData, httpdGetVersion(), -1);
}
else if (streq(token, "vers_sdk")) {
httpdSend(connData, STR(ESP_SDK_VERSION), -1);
tplSend(connData, STR(ESP_SDK_VERSION), -1);
}
else if (streq(token, "githubrepo")) {
httpdSend(connData, TERMINAL_GITHUB_REPO, -1);
tplSend(connData, TERMINAL_GITHUB_REPO, -1);
}
return HTTPD_CGI_DONE;
+1
View File
@@ -3,5 +3,6 @@
httpd_cgi_state tplScreen(HttpdConnData *connData, char *token, void **arg);
httpd_cgi_state tplAbout(HttpdConnData *connData, char *token, void **arg);
httpd_cgi_state cgiTermInitialImage(HttpdConnData *connData);
#endif // CGI_MAIN_H
+5 -4
View File
@@ -3,6 +3,7 @@ configuring the network settings
*/
#include <esp8266.h>
#include <httpdespfs.h>
#include "cgi_network.h"
#include "wifimgr.h"
#include "persist.h"
@@ -27,9 +28,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
{
static ETSTimer timer;
char buff[50];
char buff[20];
char redir_url_buf[100];
char redir_url_buf[300];
char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
@@ -200,7 +201,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
//Template code for the WLAN page.
httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *token, void **arg)
{
char buff[100];
char buff[20];
u8 mac[6];
if (token == NULL) {
@@ -246,6 +247,6 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *toke
sprintf(buff, MACSTR, MAC2STR(mac));
}
httpdSend(connData, buff, -1);
tplSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
+1 -1
View File
@@ -20,7 +20,7 @@ verify_admin_pw(const char *pw)
httpd_cgi_state ICACHE_FLASH_ATTR
cgiPersistWriteDefaults(HttpdConnData *connData)
{
char buff[50];
char buff[PASSWORD_LEN];
if (connData->conn == NULL) {
//Connection aborted. Clean up.
-20
View File
@@ -1,20 +0,0 @@
#include <esp8266.h>
#include <httpd.h>
#include "cgi_ping.h"
httpd_cgi_state ICACHE_FLASH_ATTR cgiPing(HttpdConnData *connData)
{
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
httpdSend(connData, "pong\n", -1);
return HTTPD_CGI_DONE;
}
-11
View File
@@ -1,11 +0,0 @@
#ifndef CGI_PING_H
#define CGI_PING_H
#include <esp8266.h>
#include <httpd.h>
// this is used by the UI to check if server is already restarted and working again.
httpd_cgi_state cgiPing(HttpdConnData *connData);
#endif // CGI_PING_H
-31
View File
@@ -1,31 +0,0 @@
#include <esp8266.h>
#include <httpd.h>
#include "cgi_reset.h"
static ETSTimer tmr;
static void ICACHE_FLASH_ATTR tmrCb(void *arg)
{
system_restart();
}
httpd_cgi_state ICACHE_FLASH_ATTR cgiResetDevice(HttpdConnData *connData)
{
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
os_timer_disarm(&tmr);
os_timer_setfn(&tmr, tmrCb, NULL);
os_timer_arm(&tmr, 100, false);
httpdSend(connData, "system reset\n", -1);
return HTTPD_CGI_DONE;
}
-9
View File
@@ -1,9 +0,0 @@
#ifndef CGI_RESET_H
#define CGI_RESET_H
#include <esp8266.h>
#include <httpd.h>
httpd_cgi_state cgiResetDevice(HttpdConnData *connData);
#endif // CGI_RESET_H
+63 -10
View File
@@ -6,12 +6,31 @@
#include "uart_driver.h"
#include "screen.h"
#define SOCK_BUF_LEN 2048
#define SOCK_BUF_LEN 1024
static char sock_buff[SOCK_BUF_LEN];
static void notifyTimCb(void *arg) {
volatile bool notify_available = true;
static ETSTimer notifyTim;
static ETSTimer notifyTim2;
// we're trying to do a kind of mutex here, without the actual primitives
// this might glitch, very rarely.
// it's recommended to put some delay between setting labels and updating the screen.
static void ICACHE_FLASH_ATTR
notifyTimCb(void *arg) {
void *data = NULL;
if (!notify_available) {
// postpone a little
os_timer_disarm(&notifyTim);
os_timer_setfn(&notifyTim, notifyTimCb, NULL);
os_timer_arm(&notifyTim, 1, 0);
return;
}
notify_available = false;
for (int i = 0; i < 20; i++) {
httpd_cgi_state cont = screenSerializeToBuffer(sock_buff, SOCK_BUF_LEN, &data);
int flg = 0;
@@ -21,21 +40,53 @@ static void notifyTimCb(void *arg) {
if (cont == HTTPD_CGI_DONE) break;
}
// cleanup
screenSerializeToBuffer(NULL, SOCK_BUF_LEN, &data);
notify_available = true;
}
static void ICACHE_FLASH_ATTR
notifyLabelsTimCb(void *arg)
{
if (!notify_available) {
// postpone a little
os_timer_disarm(&notifyTim2);
os_timer_setfn(&notifyTim2, notifyLabelsTimCb, NULL);
os_timer_arm(&notifyTim2, 1, 0);
return;
}
notify_available = false;
screenSerializeLabelsToBuffer(sock_buff, SOCK_BUF_LEN);
cgiWebsockBroadcast(URL_WS_UPDATE, sock_buff, (int) strlen(sock_buff), 0);
notify_available = true;
}
static ETSTimer notifyTim;
/**
* Broadcast screen state to sockets.
* This is a callback for the Screen module,
* called after each visible screen modification.
*/
void ICACHE_FLASH_ATTR screen_notifyChange(void)
void ICACHE_FLASH_ATTR screen_notifyChange(ScreenNotifyChangeTopic topic)
{
// this is not the most ideal/cleanest implementation
// PRs are welcome for a nicer update "queue" solution
if (topic == CHANGE_LABELS) {
// separate timer from content change timer, to avoid losing that update
os_timer_disarm(&notifyTim2);
os_timer_setfn(&notifyTim2, notifyLabelsTimCb, NULL);
os_timer_arm(&notifyTim2, SCREEN_NOTIFY_DELAY_MS, 0);
}
else if (topic == CHANGE_CONTENT) {
// throttle delay
os_timer_disarm(&notifyTim);
os_timer_setfn(&notifyTim, notifyTimCb, NULL);
os_timer_arm(&notifyTim, 20, 0);
os_timer_arm(&notifyTim, SCREEN_NOTIFY_DELAY_MS, 0);
}
}
/** Socket received a message */
@@ -45,7 +96,9 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
// Add terminator if missing (seems to randomly happen)
data[len] = 0;
dbg("Sock RX str: %s, len %d", data, len);
// TODO re-implement those, use single byte markers and B2 encoding
ws_dbg("Sock RX str: %s, len %d", data, len);
if (strstarts(data, "STR:")) {
// pass string verbatim
@@ -81,24 +134,24 @@ void ICACHE_FLASH_ATTR updateSockRx(Websock *ws, char *data, int len, int flags)
}
if (!screen_isCoordValid(y, x)) {
warn("Mouse input at invalid coordinates");
ws_warn("Mouse input at invalid coordinates");
return;
}
dbg("Screen clicked at row %d, col %d", y+1, x+1);
ws_dbg("Screen clicked at row %d, col %d", y+1, x+1);
// Send as 1-based to user
sprintf(buf, "\033[%d;%dM", y+1, x+1);
UART_WriteString(UART0, buf, UART_TIMEOUT_US);
}
else {
warn("Bad command.");
ws_warn("Bad command.");
}
}
/** Socket connected for updates */
void ICACHE_FLASH_ATTR updateSockConnect(Websock *ws)
{
info("Socket connected to "URL_WS_UPDATE);
ws_info("Socket connected to "URL_WS_UPDATE);
ws->recvCb = updateSockRx;
}
+15 -1
View File
@@ -1,9 +1,23 @@
#ifndef CGI_SOCKETS_H
#define CGI_SOCKETS_H
#define URL_WS_UPDATE "/ws/update.cgi"
#define URL_WS_UPDATE "/term/update.ws"
#include "screen.h"
/** Update websocket connect callback */
void updateSockConnect(Websock *ws);
void screen_notifyChange(ScreenNotifyChangeTopic topic);
// defined in the makefile
#if DEBUG_INPUT
#define ws_warn warn
#define ws_dbg dbg
#define ws_info info
#else
#define ws_warn(...)
#define ws_dbg(...)
#define ws_info(...)
#endif
#endif //CGI_SOCKETS_H
+165
View File
@@ -0,0 +1,165 @@
#include <esp8266.h>
#include <httpd.h>
#include <helpers.h>
#include <httpdespfs.h>
#include "cgi_system.h"
#include "persist.h"
#include "syscfg.h"
#include "uart_driver.h"
#define SET_REDIR_SUC "/cfg/system"
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
static ETSTimer tmr;
static void ICACHE_FLASH_ATTR tmrCb(void *arg)
{
system_restart();
}
httpd_cgi_state ICACHE_FLASH_ATTR cgiResetDevice(HttpdConnData *connData)
{
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
os_timer_disarm(&tmr);
os_timer_setfn(&tmr, tmrCb, NULL);
os_timer_arm(&tmr, 100, false);
httpdSend(connData, "system reset\n", -1);
return HTTPD_CGI_DONE;
}
httpd_cgi_state ICACHE_FLASH_ATTR cgiPing(HttpdConnData *connData)
{
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "text/plain");
httpdEndHeaders(connData);
httpdSend(connData, "pong\n", -1);
return HTTPD_CGI_DONE;
}
/**
* Universal CGI endpoint to set Terminal params.
*/
httpd_cgi_state ICACHE_FLASH_ATTR
cgiSystemCfgSetParams(HttpdConnData *connData)
{
char buff[50];
char redir_url_buf[100];
char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (GET_ARG("uart_baud")) {
dbg("Baud rate: %s", buff);
int baud = atoi(buff);
if (baud == BIT_RATE_300 ||
baud == BIT_RATE_600 ||
baud == BIT_RATE_1200 ||
baud == BIT_RATE_2400 ||
baud == BIT_RATE_4800 ||
baud == BIT_RATE_9600 ||
baud == BIT_RATE_19200 ||
baud == BIT_RATE_38400 ||
baud == BIT_RATE_57600 ||
baud == BIT_RATE_74880 ||
baud == BIT_RATE_115200 ||
baud == BIT_RATE_230400 ||
baud == BIT_RATE_460800 ||
baud == BIT_RATE_921600 ||
baud == BIT_RATE_1843200 ||
baud == BIT_RATE_3686400) {
sysconf->uart_baudrate = (u32) baud;
} else {
warn("Bad baud rate %s", buff);
redir_url += sprintf(redir_url, "uart_baud,");
}
}
if (GET_ARG("uart_parity")) {
dbg("Parity: %s", buff);
int parity = atoi(buff);
if (parity >= 0 && parity <= 2) {
sysconf->uart_parity = (UartParityMode) parity;
} else {
warn("Bad parity %s", buff);
redir_url += sprintf(redir_url, "uart_parity,");
}
}
if (GET_ARG("uart_stopbits")) {
dbg("Stop bits: %s", buff);
int stopbits = atoi(buff);
if (stopbits >= 1 && stopbits <= 3) {
sysconf->uart_stopbits = (UartStopBitsNum) stopbits;
} else {
warn("Bad stopbits %s", buff);
redir_url += sprintf(redir_url, "uart_stopbits,");
}
}
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
info("Set system params - success, saving...");
sysconf_apply_settings();
persist_store();
httpdRedirect(connData, SET_REDIR_SUC);
} else {
warn("Some settings did not validate, asking for correction");
// Some errors, appended to the URL as ?err=
httpdRedirect(connData, redir_url_buf);
}
return HTTPD_CGI_DONE;
}
httpd_cgi_state ICACHE_FLASH_ATTR
tplSystemCfg(HttpdConnData *connData, char *token, void **arg)
{
#define BUFLEN 100
char buff[BUFLEN];
if (token == NULL) {
// We're done
return HTTPD_CGI_DONE;
}
strcpy(buff, ""); // fallback
if (streq(token, "uart_baud")) {
sprintf(buff, "%d", sysconf->uart_baudrate);
}
else if (streq(token, "uart_parity")) {
sprintf(buff, "%d", sysconf->uart_parity);
}
else if (streq(token, "uart_stopbits")) {
sprintf(buff, "%d", sysconf->uart_stopbits);
}
tplSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef CGI_PING_H
#define CGI_PING_H
#include <esp8266.h>
#include <httpd.h>
httpd_cgi_state cgiPing(HttpdConnData *connData);
httpd_cgi_state cgiResetDevice(HttpdConnData *connData);
httpd_cgi_state cgiSystemCfgSetParams(HttpdConnData *connData);
httpd_cgi_state tplSystemCfg(HttpdConnData *connData, char *token, void **arg);
#endif // CGI_PING_H
+75 -51
View File
@@ -3,23 +3,27 @@ Cgi/template routines for configuring non-wifi settings
*/
#include <esp8266.h>
#include "cgi_appcfg.h"
#include <httpdespfs.h>
#include "cgi_term_cfg.h"
#include "persist.h"
#include "screen.h"
#include "helpers.h"
#define SET_REDIR_SUC "/cfg/app"
#define SET_REDIR_SUC "/cfg/term"
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
/**
* Universal CGI endpoint to set Terminal params.
*/
httpd_cgi_state ICACHE_FLASH_ATTR
cgiAppCfgSetParams(HttpdConnData *connData)
cgiTermCfgSetParams(HttpdConnData *connData)
{
char buff[50];
char redir_url_buf[100];
int n, w, h;
bool shall_clear_screen = false;
char redir_url_buf[300];
char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
@@ -32,15 +36,18 @@ cgiAppCfgSetParams(HttpdConnData *connData)
// width and height must always go together so we can do max size validation
if (GET_ARG("term_width")) {
dbg("Default screen width: %s", buff);
int w = atoi(buff);
w = atoi(buff);
if (w > 1) {
if (GET_ARG("term_height")) {
dbg("Default screen height: %s", buff);
int h = atoi(buff);
h = atoi(buff);
if (h > 1) {
if (w * h <= MAX_SCREEN_SIZE) {
if (termconf->width != w || termconf->height != h) {
termconf->width = w;
termconf->height = h;
shall_clear_screen = true;
}
} else {
warn("Bad dimensions: %d x %d (total %d)", w, h, w*h);
redir_url += sprintf(redir_url, "term_width,term_height,");
@@ -62,63 +69,79 @@ cgiAppCfgSetParams(HttpdConnData *connData)
if (GET_ARG("default_bg")) {
dbg("Screen default BG: %s", buff);
int color = atoi(buff);
if (color >= 0 && color < 16) {
termconf->default_bg = (u8) color;
n = atoi(buff);
if (n >= 0 && n < 16) {
if (termconf->default_bg != n) {
termconf->default_bg = (u8) n;
shall_clear_screen = true;
}
} else {
warn("Bad color %s", buff);
redir_url += sprintf(redir_url, "default_bg,");
}
}
if (GET_ARG("parser_tout_ms")) {
dbg("Parser timeout: %s ms", buff);
n = atoi(buff);
if (n >= 0) {
termconf->parser_tout_ms = (u8) n;
} else {
warn("Bad timeout %s", buff);
redir_url += sprintf(redir_url, "parser_tout_ms,");
}
}
if (GET_ARG("default_fg")) {
dbg("Screen default FG: %s", buff);
int color = atoi(buff);
if (color >= 0 && color < 16) {
termconf->default_fg = (u8) color;
n = atoi(buff);
if (n >= 0 && n < 16) {
if (termconf->default_fg != n) {
termconf->default_fg = (u8) n;
shall_clear_screen = true;
}
} else {
warn("Bad color %s", buff);
redir_url += sprintf(redir_url, "default_fg,");
}
}
if (GET_ARG("theme")) {
dbg("Screen color theme: %s", buff);
n = atoi(buff);
if (n >= 0 && n <= 5) { // ALWAYS ADJUST WHEN ADDING NEW THEME!
termconf->theme = (u8) n;
} else {
warn("Bad theme num: %s", buff);
redir_url += sprintf(redir_url, "theme,");
}
}
if (GET_ARG("term_title")) {
dbg("Terminal title default text: \"%s\"", buff);
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in
}
if (GET_ARG("btn1")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn1, buff, 10);
for (int i = 1; i <= 5; i++) {
sprintf(buff, "btn%d", i);
if (GET_ARG(buff)) {
dbg("Button%d default text: \"%s\"", i, buff);
strncpy_safe(termconf->btn[i-1], buff, TERM_BTN_LEN);
}
if (GET_ARG("btn2")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn2, buff, 10);
}
if (GET_ARG("btn3")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn3, buff, 10);
}
if (GET_ARG("btn4")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn4, buff, 10);
}
if (GET_ARG("btn5")) {
dbg("Button1 default text: \"%s\"", buff);
strncpy_safe(termconf->btn5, buff, 10);
}
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
info("Set app params - success, saving...");
info("Set term params - success, saving...");
terminal_apply_settings();
persist_store();
if (shall_clear_screen) {
terminal_apply_settings();
} else {
terminal_apply_settings_noclear();
}
httpdRedirect(connData, SET_REDIR_SUC);
} else {
warn("Some settings did not validate, asking for correction");
@@ -130,10 +153,11 @@ cgiAppCfgSetParams(HttpdConnData *connData)
httpd_cgi_state ICACHE_FLASH_ATTR
tplAppCfg(HttpdConnData *connData, char *token, void **arg)
tplTermCfg(HttpdConnData *connData, char *token, void **arg)
{
#define BUFLEN 100
#define BUFLEN TERM_TITLE_LEN
char buff[BUFLEN];
char buff2[10];
if (token == NULL) {
// We're done
@@ -148,6 +172,12 @@ tplAppCfg(HttpdConnData *connData, char *token, void **arg)
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, "theme")) {
sprintf(buff, "%d", termconf->theme);
}
else if (streq(token, "default_bg")) {
sprintf(buff, "%d", termconf->default_bg);
}
@@ -157,22 +187,16 @@ tplAppCfg(HttpdConnData *connData, char *token, void **arg)
else if (streq(token, "term_title")) {
strncpy_safe(buff, termconf->title, BUFLEN);
}
else if (streq(token, "btn1")) {
strncpy_safe(buff, termconf->btn1, BUFLEN);
else {
for (int i = 1; i <= 5; i++) {
sprintf(buff2, "btn%d", i);
if (streq(token, buff2)) {
strncpy_safe(buff, termconf->btn[i-1], BUFLEN);
break;
}
else if (streq(token, "btn2")) {
strncpy_safe(buff, termconf->btn2, BUFLEN);
}
else if (streq(token, "btn3")) {
strncpy_safe(buff, termconf->btn3, BUFLEN);
}
else if (streq(token, "btn4")) {
strncpy_safe(buff, termconf->btn4, BUFLEN);
}
else if (streq(token, "btn5")) {
strncpy_safe(buff, termconf->btn5, BUFLEN);
}
httpdSend(connData, buff, -1);
tplSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef CGIAPPCFG_H
#define CGIAPPCFG_H
#include "httpd.h"
httpd_cgi_state cgiTermCfgSetParams(HttpdConnData *connData);
httpd_cgi_state tplTermCfg(HttpdConnData *connData, char *token, void **arg);
#endif
+50 -10
View File
@@ -14,6 +14,7 @@ Cgi/template routines for the /wifi url.
*/
#include <esp8266.h>
#include <httpdespfs.h>
#include "cgi_wifi.h"
#include "wifimgr.h"
#include "persist.h"
@@ -183,9 +184,8 @@ static void ICACHE_FLASH_ATTR wifiStartScan(void)
}
/**
* This CGI is called from the bit of AJAX-code in wifi.tpl. It will initiate a
* scan for access points and if available will return the result of an earlier scan.
* The result is embedded in a bit of JSON parsed by the javascript in wifi.tpl.
* Start a scan and return a result of an earlier scan, if available.
* The STA is switched ON if disabled.
*/
httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData)
{
@@ -193,6 +193,17 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData)
int len;
char buff[256];
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
// auto-turn on STA
if ((wificonf->opmode & STATION_MODE) == 0) {
wificonf->opmode |= STATION_MODE;
wifimgr_apply_settings();
}
// 2nd and following runs of the function via MORE:
if (!cgiWifiAps.scanInProgress && pos != 0) {
// Fill in json code for an access point
@@ -259,6 +270,13 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
char buff[100];
struct ip_info info;
buff[0] = 0; // avoid unitialized read
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/json");
httpdEndHeaders(connData);
@@ -270,6 +288,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
}
STATION_STATUS st = wifi_station_get_connect_status();
dbg("CONN STATE = %d", st);
switch(st) {
case STATION_IDLE:
sprintf(buff, "{\"status\": \"idle\"}"); // unclear when this is used
@@ -295,6 +314,11 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
wifi_get_ip_info(STATION_IF, &info);
sprintf(buff, "{\"status\": \"success\", \"ip\": \""IPSTR"\"}", GOOD_IP2STR(info.ip.addr));
break;
default:
sprintf(buff, "{\"status\": \"working\", \"wtf\": \"state = %d\"}", st);
break;
}
httpdSend(connData, buff, -1);
@@ -319,8 +343,8 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
static ETSTimer timer;
char buff[50];
char redir_url_buf[100]; // this is just barely enough - but it's split into two forms, so we never have error in all fields
char redir_url_buf[300];
char *redir_url = redir_url_buf;
redir_url += sprintf(redir_url, SET_REDIR_ERR);
// we'll test if anything was printed by looking for \0 in failed_keys_buf
@@ -330,6 +354,9 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
return HTTPD_CGI_DONE;
}
bool sta_turned_on = false;
bool sta_ssid_pw_changed = false;
// ---- WiFi opmode ----
if (GET_ARG("opmode")) {
@@ -360,6 +387,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
if (enable) {
wificonf->opmode |= STATION_MODE;
sta_turned_on = true;
} else {
wificonf->opmode &= ~STATION_MODE;
}
@@ -457,6 +485,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
info("Setting station SSID to: \"%s\"", buff);
strncpy_safe(wificonf->sta_ssid, buff, SSID_LEN);
wifi_change_flags.sta = true;
sta_ssid_pw_changed = true;
}
}
@@ -468,12 +497,13 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
info("Setting station password to: \"%s\"", buff);
strncpy_safe(wificonf->sta_password, buff, PASSWORD_LEN);
wifi_change_flags.sta = true;
sta_ssid_pw_changed = true;
}
}
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
// All was OK
info("Set WiFi params - success, applying in 1000 ms");
info("Set WiFi params - success, applying in 2000 ms");
// Settings are applied only if all was OK
//
@@ -486,9 +516,19 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
// If user connects via the Station IF, they may not even notice the connection reset.
os_timer_disarm(&timer);
os_timer_setfn(&timer, applyWifiSettingsLaterCb, NULL);
os_timer_arm(&timer, 1000, false);
os_timer_arm(&timer, 2000, false);
if ((sta_ssid_pw_changed || sta_turned_on)
&& wificonf->opmode != SOFTAP_MODE
&& wificonf->sta_ssid[0] != 0) {
// User wants to connect
info("User wants to connect to SSID, redirecting to ConnStatus page.");
httpdRedirect(connData, "/cfg/wifi/connecting");
}
else {
httpdRedirect(connData, SET_REDIR_SUC);
}
} else {
warn("Some WiFi settings did not validate, asking for correction");
// Some errors, appended to the URL as ?err=
@@ -501,7 +541,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
//Template code for the WLAN page.
httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg)
{
char buff[100];
char buff[PASSWORD_LEN];
int x;
int connectStatus;
@@ -552,7 +592,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
// For display of our current SSID
connectStatus = wifi_station_get_connect_status();
x = wifi_get_opmode();
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP) {
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP || wificonf->opmode == SOFTAP_MODE) {
strcpy(buff, "");
}
else {
@@ -565,7 +605,7 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
x = wifi_get_opmode();
connectStatus = wifi_station_get_connect_status();
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP) {
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP || wificonf->opmode == SOFTAP_MODE) {
strcpy(buff, "");
}
else {
@@ -580,6 +620,6 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token,
}
}
httpdSend(connData, buff, -1);
tplSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
+28
View File
@@ -13,6 +13,8 @@
#include "ansi_parser_callbacks.h"
#include "wifimgr.h"
#include "persist.h"
#include "screen.h"
#include "ansi_parser.h"
#define BTNGPIO 0
@@ -21,6 +23,28 @@ static bool enable_ap_button = false;
static ETSTimer resetBtntimer;
static ETSTimer blinkyTimer;
static ETSTimer ansiParserResetTimer;
static void ICACHE_FLASH_ATTR ansiParserResetTimerCb(void *arg) {
static u32 last_charcnt = 0;
static int same_charcnt = -1;
if (termconf->parser_tout_ms == 0) return;
if (last_charcnt != ansi_parser_char_cnt) {
last_charcnt = ansi_parser_char_cnt;
same_charcnt = 0;
}
else {
if (same_charcnt != -1) {
same_charcnt++;
if (same_charcnt > termconf->parser_tout_ms) {
ansi_parser_reset();
same_charcnt = -1;
}
}
}
}
// Holding BOOT pin triggers AP reset, then Factory Reset.
// Indicate that by blinking the on-board LED.
@@ -102,6 +126,10 @@ void ICACHE_FLASH_ATTR ioInit() {
os_timer_setfn(&resetBtntimer, resetBtnTimerCb, NULL);
os_timer_arm(&resetBtntimer, 500, 1);
os_timer_disarm(&ansiParserResetTimer);
os_timer_setfn(&ansiParserResetTimer, ansiParserResetTimerCb, NULL);
os_timer_arm(&ansiParserResetTimer, 1, 1);
// One way to enter AP mode - hold GPIO0 low.
if (GPIO_INPUT_GET(BTNGPIO) == 0) {
// starting "in BOOT mode" - do not install the AP reset timer
+44 -10
View File
@@ -17,21 +17,45 @@ static void ICACHE_FLASH_ATTR
apply_live_settings(void)
{
dbg("[Persist] Applying live settings...");
terminal_apply_settings();
dbg("[Persist] > system");
sysconf_apply_settings();
dbg("[Persist] > wifi");
wifimgr_apply_settings();
dbg("[Persist] > terminal");
terminal_apply_settings();
dbg("[Persist] Live settings applied.");
// ...
}
static void ICACHE_FLASH_ATTR
restore_live_settings_to_hard_defaults(void)
{
dbg("[Persist] Restore to hard defaults...");
dbg("[Persist] > system");
sysconf_restore_defaults();
dbg("[Persist] > wifi");
wifimgr_restore_defaults();
dbg("[Persist] > terminal");
terminal_restore_defaults();
dbg("[Persist] Restored to hard defaults.");
// ...
}
//endregion
const u32 wconf_at = (u32)&persist.defaults.wificonf - (u32)&persist.defaults;
const u32 tconf_at = (u32)&persist.defaults.termconf - (u32)&persist.defaults;
const u32 sconf_at = (u32)&persist.defaults.sysconf - (u32)&persist.defaults;
const u32 cksum_at = (u32)&persist.defaults.checksum - (u32)&persist.defaults;
/**
* Compute CRC32. Adapted from https://github.com/esp8266/Arduino
* @param data
@@ -41,7 +65,9 @@ restore_live_settings_to_hard_defaults(void)
static uint32_t ICACHE_FLASH_ATTR
calculateCRC32(const uint8_t *data, size_t length)
{
uint32_t crc = 0xffffffff;
// the salt here should ensure settings are wiped when the structure changes
// CHECKSUM_SALT can be adjusted manually to force a reset.
uint32_t crc = 0xffffffff + CHECKSUM_SALT + ((wconf_at << 16) ^ (tconf_at << 10) ^ (sconf_at << 5));
while (length--) {
uint8_t c = *data++;
for (uint32_t i = 0x80; i > 0; i >>= 1) {
@@ -67,7 +93,8 @@ calculateCRC32(const uint8_t *data, size_t length)
static uint32_t ICACHE_FLASH_ATTR
compute_checksum(AppConfigBundle *bundle)
{
return calculateCRC32((uint8_t *) bundle, sizeof(AppConfigBundle) - 4) ^ CHECKSUM_SALT;
// this should be the bundle without the checksum
return calculateCRC32((uint8_t *) bundle, sizeof(AppConfigBundle) - 4);
}
/**
@@ -78,10 +105,12 @@ persist_load(void)
{
info("[Persist] Loading stored settings from FLASH...");
dbg("sizeof(AppConfigBundle) = %d bytes", sizeof(AppConfigBundle));
dbg("sizeof(PersistBlock) = %d bytes", sizeof(PersistBlock));
dbg("sizeof(WiFiConfigBundle) = %d bytes", sizeof(WiFiConfigBundle));
dbg("sizeof(TerminalConfigBundle) = %d bytes", sizeof(TerminalConfigBundle));
dbg("AppConfigBundle memory map:");
dbg("> WiFiConfigBundle at %4d (error %2d)", wconf_at, wconf_at - 0);
dbg("> SystemConfigBundle at %4d (error %2d)", sconf_at, sconf_at - WIFICONF_SIZE);
dbg("> TerminalConfigBundle at %4d (error %2d)", tconf_at, tconf_at - WIFICONF_SIZE - SYSCONF_SIZE);
dbg("> Checksum at %4d (error %2d)", cksum_at, cksum_at - (APPCONF_SIZE - 4));
dbg("> Total size = %d bytes (error %d)", sizeof(AppConfigBundle), APPCONF_SIZE - sizeof(AppConfigBundle));
bool hard_reset = false;
@@ -99,7 +128,15 @@ persist_load(void)
}
if (hard_reset) {
// Zero all out
memset(&persist, 0, sizeof(PersistBlock));
persist_load_hard_default();
// write them also as defaults
memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
persist_store();
// this also stores them to flash and applies to modules
} else {
apply_live_settings();
@@ -135,9 +172,6 @@ persist_load_hard_default(void)
restore_live_settings_to_hard_defaults();
persist_store();
// // Store current -> default
// memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
info("[Persist] Settings restored to hard defaults.");
apply_live_settings(); // apply
+18 -5
View File
@@ -12,19 +12,26 @@
#include "wifimgr.h"
#include "screen.h"
#include "syscfg.h"
// Changing this could be used to force-erase the config area
// after a firmware upgrade
#define CHECKSUM_SALT 0x5F5F5F5F
#define CHECKSUM_SALT 3
#define APPCONF_SIZE 2048
/** Struct for current or default settings */
typedef struct {
typedef struct { // the entire block should be 1024 bytes long (for compatibility across upgrades)
WiFiConfigBundle wificonf;
uint8_t _filler1[WIFICONF_SIZE - sizeof(WiFiConfigBundle)];
SystemConfigBundle sysconf;
uint8_t _filler3[SYSCONF_SIZE - sizeof(SystemConfigBundle)];
TerminalConfigBundle termconf;
uint8_t _filler2[TERMCONF_SIZE - sizeof(TerminalConfigBundle)];
// --- Space for future settings ---
// Original size: 1024
//
// The size must be appropriately reduced each time something is added,
// and boolean flags defaulting to 0 should be used to detect unpopulated
// sections that must be restored to defaults on load.
@@ -32,7 +39,13 @@ typedef struct {
// This ensures user settings are not lost each time they upgrade the firmware,
// which would lead to a checksum mismatch if the structure was changed and
// it grew to a different memory area.
uint8_t filler[1024];
uint8_t _filler_end[
APPCONF_SIZE
- sizeof(uint32_t) // checksum
- WIFICONF_SIZE
- SYSCONF_SIZE
- TERMCONF_SIZE
];
uint32_t checksum; // computed before write and tested on load. If it doesn't match, values are reset to hard defaults.
} AppConfigBundle;
+10 -9
View File
@@ -6,12 +6,11 @@
#include "routes.h"
#include "cgi_wifi.h"
#include "cgi_reset.h"
#include "cgi_ping.h"
#include "cgi_system.h"
#include "cgi_main.h"
#include "cgi_sockets.h"
#include "cgi_network.h"
#include "cgi_appcfg.h"
#include "cgi_term_cfg.h"
#include "cgi_persist.h"
#define WIFI_PROTECT 0
@@ -33,6 +32,7 @@ HttpdBuiltInUrl routes[] = {
ROUTE_FILE("/help/?", "/help.tpl"),
// --- Sockets ---
ROUTE_CGI("/term/init", cgiTermInitialImage),
ROUTE_WS(URL_WS_UPDATE, updateSockConnect),
// --- System control ---
@@ -54,13 +54,14 @@ HttpdBuiltInUrl routes[] = {
ROUTE_TPL_FILE("/cfg/network/?", tplNetwork, "/cfg_network.tpl"),
ROUTE_CGI("/cfg/network/set", cgiNetworkSetParams),
ROUTE_TPL_FILE("/cfg/app/?", tplAppCfg, "/cfg_app.tpl"),
ROUTE_CGI("/cfg/app/set", cgiAppCfgSetParams),
ROUTE_TPL_FILE("/cfg/term/?", tplTermCfg, "/cfg_term.tpl"),
ROUTE_CGI("/cfg/term/set", cgiTermCfgSetParams),
ROUTE_FILE("/cfg/admin/?", "/cfg_admin.tpl"),
ROUTE_CGI("/cfg/admin/write_defaults", cgiPersistWriteDefaults),
ROUTE_CGI("/cfg/admin/restore_defaults", cgiPersistRestoreDefaults),
ROUTE_CGI("/cfg/admin/restore_hard", cgiPersistRestoreHard),
ROUTE_TPL_FILE("/cfg/system/?", tplSystemCfg, "/cfg_system.tpl"),
ROUTE_CGI("/cfg/system/set", cgiSystemCfgSetParams),
ROUTE_CGI("/cfg/system/write_defaults", cgiPersistWriteDefaults),
ROUTE_CGI("/cfg/system/restore_defaults", cgiPersistRestoreDefaults),
ROUTE_CGI("/cfg/system/restore_hard", cgiPersistRestoreHard),
ROUTE_FILESYSTEM(),
ROUTE_END(),
+398 -101
View File
@@ -8,6 +8,12 @@
TerminalConfigBundle * const termconf = &persist.current.termconf;
TerminalConfigBundle termconf_scratch;
// forward declare
static void utf8_remap(char* out, char g, char table);
#define W termconf_scratch.width
#define H termconf_scratch.height
/**
* Restore hard defaults
*/
@@ -17,12 +23,11 @@ void terminal_restore_defaults(void)
termconf->default_fg = 7;
termconf->width = 26;
termconf->height = 10;
termconf->parser_tout_ms = 10;
sprintf(termconf->title, "ESPTerm");
sprintf(termconf->btn1, "1");
sprintf(termconf->btn2, "2");
sprintf(termconf->btn3, "3");
sprintf(termconf->btn4, "4");
sprintf(termconf->btn5, "5");
for(int i=1; i <= 5; i++) {
sprintf(termconf->btn[i-1], "%d", i);
}
}
/**
@@ -30,12 +35,22 @@ void terminal_restore_defaults(void)
*/
void terminal_apply_settings(void)
{
memcpy(&termconf_scratch, termconf, sizeof(TerminalConfigBundle));
terminal_apply_settings_noclear();
screen_init();
}
#define W termconf_scratch.width
#define H termconf_scratch.height
void terminal_apply_settings_noclear(void)
{
memcpy(&termconf_scratch, termconf, sizeof(TerminalConfigBundle));
if (W*H >= MAX_SCREEN_SIZE) {
error("BAD SCREEN SIZE: %d rows x %d cols", H, W);
error("reverting terminal settings to default");
terminal_restore_defaults();
persist_store();
memcpy(&termconf_scratch, termconf, sizeof(TerminalConfigBundle));
screen_init();
}
}
/**
* Highest permissible value of the color attribute
@@ -46,9 +61,10 @@ void terminal_apply_settings(void)
* Screen cell data type (16 bits)
*/
typedef struct __attribute__((packed)){
char c : 8;
char c[4]; // space for a full unicode character
Color fg : 4;
Color bg : 4;
u8 attrs;
} Cell;
/**
@@ -62,9 +78,15 @@ static Cell screen[MAX_SCREEN_SIZE];
static struct {
int x; //!< X coordinate
int y; //!< Y coordinate
bool visible; //!< Visible
bool inverse; //!< Inverse colors
bool autowrap; //!< Wrapping when EOL
bool visible; //!< Visible (not attribute, DEC special)
bool inverse;
u8 attrs;
char charset0;
char charset1;
int charsetN;
Color fg; //!< Foreground color for writing
Color bg; //!< Background color for writing
} cursor;
@@ -75,10 +97,10 @@ static struct {
static struct {
int x;
int y;
// optionally saved attrs
// mark that attrs are saved
bool withAttrs;
bool inverse;
u8 attrs;
bool inverse; // attribute that's not in the bitfield
Color fg;
Color bg;
} cursor_sav;
@@ -96,7 +118,7 @@ static volatile int notifyLock = 0;
#define NOTIFY_DONE() do { \
if (notifyLock > 0) notifyLock--; \
if (notifyLock == 0) screen_notifyChange(); \
if (notifyLock == 0) screen_notifyChange(CHANGE_CONTENT); \
} while(0)
/**
@@ -105,12 +127,21 @@ static volatile int notifyLock = 0;
static inline void
clear_range(unsigned int from, unsigned int to)
{
Color fg = cursor.inverse ? cursor.bg : cursor.fg;
Color bg = cursor.inverse ? cursor.fg : cursor.bg;
if (to >= W*H) to = W*H-1;
Color fg = (cursor.inverse) ? cursor.bg : cursor.fg;
Color bg = (cursor.inverse) ? cursor.fg : cursor.bg;
Cell sample;
sample.c[0] = ' ';
sample.c[1] = 0;
sample.c[2] = 0;
sample.c[3] = 0;
sample.fg = fg;
sample.bg = bg;
sample.attrs = 0;
for (unsigned int i = from; i <= to; i++) {
screen[i].c = ' ';
screen[i].fg = fg;
screen[i].bg = bg;
memcpy(&screen[i], &sample, sizeof(Cell));
}
}
@@ -125,8 +156,12 @@ cursor_reset(void)
cursor.fg = termconf_scratch.default_fg;
cursor.bg = termconf_scratch.default_bg;
cursor.visible = 1;
cursor.inverse = 0;
cursor.autowrap = 1;
cursor.attrs = 0;
cursor.charset0 = 'B';
cursor.charset1 = '0';
cursor.charsetN = 0;
}
//endregion
@@ -156,6 +191,18 @@ screen_reset(void)
NOTIFY_DONE();
}
/**
* Reset the cursor
*/
void ICACHE_FLASH_ATTR
screen_reset_cursor(void)
{
cursor.fg = termconf_scratch.default_fg;
cursor.bg = termconf_scratch.default_bg;
cursor.attrs = 0;
cursor.inverse = false;
}
/**
* Clear screen area
*/
@@ -202,10 +249,121 @@ screen_clear_line(ClearMode mode)
NOTIFY_DONE();
}
void ICACHE_FLASH_ATTR
screen_clear_in_line(unsigned int count)
{
if (cursor.x + count >= W) {
screen_clear_line(CLEAR_FROM_CURSOR);
}
else {
NOTIFY_LOCK();
clear_range(cursor.y * W + cursor.x, cursor.y * W + cursor.x + count - 1);
NOTIFY_DONE();
}
}
void ICACHE_FLASH_ATTR
screen_fill_with_E(void)
{
NOTIFY_LOCK();
Cell sample;
sample.c[0] = 'E';
sample.c[1] = 0;
sample.c[2] = 0;
sample.c[3] = 0;
sample.fg = termconf_scratch.default_fg;
sample.bg = termconf_scratch.default_bg;
sample.attrs = 0;
for (unsigned int i = 0; i <= W*H-1; i++) {
memcpy(&screen[i], &sample, sizeof(Cell));
}
NOTIFY_DONE();
}
//endregion
//region Screen manipulation
void screen_insert_lines(unsigned int lines)
{
NOTIFY_LOCK();
// shift the following lines
int targetStart = cursor.y + lines;
if (targetStart >= H) {
targetStart = H;
} else {
// do the moving
for (int i = H-1; i >= targetStart; i--) {
memcpy(screen+i*W, screen+(i-lines)*W, sizeof(Cell)*W);
}
}
// clear the first line
screen_clear_line(CLEAR_ALL);
// copy it to the rest of the cleared region
for (int i = cursor.y+1; i < targetStart; i++) {
memcpy(screen+i*W, screen+cursor.y*W, sizeof(Cell)*W);
}
NOTIFY_DONE();
}
void screen_delete_lines(unsigned int lines)
{
NOTIFY_LOCK();
// shift lines up
int targetEnd = H - 1 - lines;
if (targetEnd <= cursor.y) {
targetEnd = cursor.y;
} else {
// do the moving
for (int i = cursor.y; i <= targetEnd; i++) {
memcpy(screen+i*W, screen+(i+lines)*W, sizeof(Cell)*W);
}
}
// clear the rest
clear_range(targetEnd*W, W*H-1);
NOTIFY_DONE();
}
void screen_insert_characters(unsigned int count)
{
NOTIFY_LOCK();
int targetStart = cursor.x + count;
if (targetStart >= W) {
targetStart = W-1;
} else {
// do the moving
for (int i = W-1; i >= targetStart; i--) {
memcpy(screen+cursor.y*W+i, screen+cursor.y*W+(i-count), sizeof(Cell));
}
}
clear_range(cursor.y*W+cursor.x, cursor.y*W+targetStart-1);
NOTIFY_DONE();
}
void screen_delete_characters(unsigned int count)
{
NOTIFY_LOCK();
int targetEnd = W - count;
if (targetEnd > cursor.x) {
// do the moving
for (int i = cursor.x; i <= targetEnd; i++) {
memcpy(screen+cursor.y*W+i, screen+cursor.y*W+(i+count), sizeof(Cell));
}
}
if (targetEnd <= cursor.x) {
screen_clear_line(CLEAR_FROM_CURSOR);
}
else {
clear_range(cursor.y * W + (W - count), (cursor.y + 1) * W - 1);
}
NOTIFY_DONE();
}
/**
* Change the screen size
*
@@ -345,26 +503,26 @@ screen_cursor_set_y(int y)
* Relative cursor move
*/
void ICACHE_FLASH_ATTR
screen_cursor_move(int dy, int dx)
screen_cursor_move(int dy, int dx, bool scroll)
{
NOTIFY_LOCK();
int move;
cursor.x += dx;
cursor.y += dy;
if (cursor.x >= W) cursor.x = W - 1;
if (cursor.x < 0) cursor.x = 0;
if (cursor.x >= (int)W) cursor.x = W - 1;
if (cursor.x < (int)0) cursor.x = 0;
if (cursor.y < 0) {
move = -cursor.y;
cursor.y = 0;
screen_scroll_down((unsigned int)move);
if (scroll) screen_scroll_down((unsigned int)move);
}
if (cursor.y >= H) {
move = cursor.y - (H - 1);
cursor.y = H - 1;
screen_scroll_up((unsigned int)move);
if (scroll) screen_scroll_up((unsigned int)move);
}
NOTIFY_DONE();
@@ -384,11 +542,11 @@ screen_cursor_save(bool withAttrs)
if (withAttrs) {
cursor_sav.fg = cursor.fg;
cursor_sav.bg = cursor.bg;
cursor_sav.inverse = cursor.inverse;
cursor_sav.attrs = cursor.attrs;
} else {
cursor_sav.fg = termconf_scratch.default_fg;
cursor_sav.bg = termconf_scratch.default_bg;
cursor_sav.inverse = 0;
cursor_sav.attrs = 0; // avoid leftovers if the wrong restore is used
}
}
@@ -405,7 +563,7 @@ screen_cursor_restore(bool withAttrs)
if (withAttrs) {
cursor.fg = cursor_sav.fg;
cursor.bg = cursor_sav.bg;
cursor.inverse = cursor_sav.inverse;
cursor.attrs = cursor_sav.attrs;
}
NOTIFY_DONE();
@@ -457,36 +615,19 @@ screen_set_bg(Color color)
cursor.bg = color;
}
/**
* Set cursor foreground and background color
*/
void ICACHE_FLASH_ATTR
screen_set_colors(Color fg, Color bg)
void screen_attr_enable(u8 attrs)
{
screen_set_fg(fg);
screen_set_bg(bg);
cursor.attrs |= attrs;
}
/**
* Invert colors
*/
void ICACHE_FLASH_ATTR
screen_inverse(bool inverse)
void screen_attr_disable(u8 attrs)
{
cursor.inverse = inverse;
cursor.attrs &= ~attrs;
}
/**
* Make foreground bright.
*
* This relates to the '1' SGR command which originally means
* "bold font". We interpret that as "Bright", similar to other
* terminal emulators.
*/
void ICACHE_FLASH_ATTR
screen_set_bright_fg(void)
void screen_inverse_enable(bool ena)
{
cursor.fg = (cursor.fg % 8) + 8;
cursor.inverse = ena;
}
//endregion
@@ -503,24 +644,38 @@ bool ICACHE_FLASH_ATTR screen_isCoordValid(int y, int x)
return x >= 0 && y >= 0 && x < W && y < H;
}
void ICACHE_FLASH_ATTR screen_set_charset_n(int Gx)
{
if (Gx < 0 || Gx > 1) return; // bad n
cursor.charsetN = Gx;
}
void ICACHE_FLASH_ATTR screen_set_charset(int Gx, char charset)
{
if (Gx == 0) cursor.charset0 = charset;
if (Gx == 1) cursor.charset1 = charset;
}
/**
* Set a character in the cursor color, move to right with wrap.
*/
void ICACHE_FLASH_ATTR
screen_putchar(char ch)
screen_putchar(const char *ch)
{
char buf[4];
NOTIFY_LOCK();
Cell *c = &screen[cursor.x + cursor.y * W];
// Special treatment for CRLF
switch (ch) {
switch (ch[0]) {
case '\r':
screen_cursor_set_x(0);
goto done;
case '\n':
screen_cursor_move(1, 0);
screen_cursor_move(1, 0, true); // can scroll
goto done;
case 8: // BS
@@ -533,29 +688,38 @@ screen_putchar(char ch)
cursor.y--;
}
}
// erase target cell
c = &screen[cursor.x + cursor.y * W];
c->c = ' ';
// apparently backspace should not clear the cell
goto done;
case 9: // TAB
// TODO change if tab setting is ever implemented
if (cursor.x<((W-1)-(W-1)%4)) {
c->c = ' ';
c->c[0] = ' ';
c->c[1] = 0;
c->c[2] = 0;
c->c[3] = 0;
do {
screen_putchar(' ');
screen_putchar(" ");
} while(cursor.x%4!=0);
}
goto done;
default:
if (ch < ' ') {
if (ch[0] < ' ') {
// Discard
warn("Ignoring control char %d", (int)ch);
warn("Ignoring control char %d", (int)ch[0]);
goto done;
}
}
c->c = ch;
if (ch[1] == 0 && ch[0] <= 0x7f) {
// we have len=1 and ASCII
utf8_remap(c->c, ch[0], (cursor.charsetN == 0) ? cursor.charset0 : cursor.charset1);
}
else {
// copy unicode char
strncpy(c->c, ch, 4);
}
if (cursor.inverse) {
c->fg = cursor.bg;
@@ -564,6 +728,7 @@ screen_putchar(char ch)
c->fg = cursor.fg;
c->bg = cursor.bg;
}
c->attrs = cursor.attrs;
cursor.x++;
// X wrap
@@ -586,6 +751,84 @@ done:
NOTIFY_DONE();
}
/**
* translates VT100 ACS escape codes to Unicode values.
* Based on rxvt-unicode screen.C table.
*/
static const u16 vt100_to_unicode[62] =
{
// ? ? ? ? ? ? ?
// A=UPARR B=DNARR C=RTARR D=LFARR E=FLBLK F=3/4BL G=SNOMN
0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603,
// H= I= J= K= L= M= N=
0, 0, 0, 0, 0, 0, 0,
// O= P= Q= R= S= T= U=
0, 0, 0, 0, 0, 0, 0,
// V= W= X= Y= Z= [= \=
0, 0, 0, 0, 0, 0, 0,
// ? ? v->0 v->1 v->2 v->3 v->4
// ]= ^= _=SPC `=DIAMN a=HSMED b=HT c=FF
0, 0, 0x0020, 0x25c6, 0x2592, 0x2409, 0x240c,
// v->5 v->6 v->7 v->8 v->9 v->a v->b
// d=CR e=LF f=DEGRE g=PLSMN h=NL i=VT j=SL-BR
0x240d, 0x240a, 0x00b0, 0x00b1, 0x2424, 0x240b, 0x2518,
// v->c v->d v->e v->f v->10 v->11 v->12
// k=SL-TR l=SL-TL m=SL-BL n=SL-+ o=SL-T1 p=SL-T2 q=SL-HZ
0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, 0x23bb, 0x2500,
// v->13 v->14 v->15 v->16 v->17 v->18 v->19
// r=SL-T4 s=SL-T5 t=SL-VR u=SL-VL v=SL-HU w=Sl-HD x=SL-VT
0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, 0x2502,
// v->1a v->1b b->1c v->1d v->1e/a3 v->1f
// y=LT-EQ z=GT-EQ {=PI |=NOTEQ }=POUND ~=DOT
0x2264, 0x2265, 0x03c0, 0x2260, 0x20a4, 0x00b7
};
/**
* UTF remap
* @param out - output char[4]
* @param g - ASCII char
* @param table - table name (0, A, B)
*/
static void ICACHE_FLASH_ATTR
utf8_remap(char *out, char g, char table)
{
u16 utf = 0;
switch (table)
{
case '0': /* DEC Special Character & Line Drawing Set */
if ((g >= 0x41) && (g <= 0x7e) && (vt100_to_unicode[g - 0x41])) {
utf = vt100_to_unicode[g - 0x41];
}
break;
case 'A': /* UK, replaces # with GBP */
if (g == '#') utf = 0x20a4;
break;
}
if (utf > 0x7F) {
// formulas taken from: https://gist.github.com/yamamushi/5823402
if ((utf >= 0x80) && (utf <= 0x07FF)) {
out[0] = (char) ((utf >> 0x06) ^ 0xC0);
out[1] = (char) (((utf ^ 0xFFC0) | 0x80) & ~0x40);
out[2]=0;
}
else if ((utf >= 0x0800) && (utf <= 0xFFFF)) {
out[0] = (char) (((utf ^ 0xFC0FFF) >> 0x0C) | 0xE0);
out[1] = (char) ((((utf ^ 0xFFF03F) >> 0x06) | 0x80) & ~0x40);
out[2] = (char) (((utf ^ 0xFFFC0) | 0x80) & ~0x40);
out[3]=0;
} else {
out[0] = g;
out[1] = 0;
}
} else {
out[0] = g;
out[1] = 0;
}
}
//region Serialization
@@ -627,10 +870,54 @@ void screen_dd(void)
struct ScreenSerializeState {
Color lastFg;
Color lastBg;
char lastChar;
bool lastAttrs;
char lastChar[4];
int index;
};
void ICACHE_FLASH_ATTR
encode2B(u16 number, WordB2 *stru)
{
stru->lsb = (u8) (number % 127);
number = (u16) ((number - stru->lsb) / 127);
stru->lsb += 1;
stru->msb = (u8) (number + 1);
}
void ICACHE_FLASH_ATTR
encode3B(u32 number, WordB3 *stru)
{
stru->lsb = (u8) (number % 127);
number = (number - stru->lsb) / 127;
stru->lsb += 1;
stru->msb = (u8) (number % 127);
number = (number - stru->msb) / 127;
stru->msb += 1;
stru->xsb = (u8) (number + 1);
}
/**
* buffer should be at least 64+5*10+6 long (title + buttons + 6), ie. 120
* @param buffer
* @param buf_len
*/
void ICACHE_FLASH_ATTR
screenSerializeLabelsToBuffer(char *buffer, size_t buf_len)
{
// let's just assume it's long enough - called with the huge websocket buffer
sprintf(buffer, "T%s\x01%s\x01%s\x01%s\x01%s\x01%s", // use 0x01 as separator
termconf_scratch.title,
termconf_scratch.btn[0],
termconf_scratch.btn[1],
termconf_scratch.btn[2],
termconf_scratch.btn[3],
termconf_scratch.btn[4]
);
}
/**
* Serialize the screen to a data buffer. May need multiple calls if the buffer is insufficient in size.
*
@@ -654,6 +941,8 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
}
Cell *cell, *cell0;
WordB2 w1, w2, w3, w4, w5;
WordB3 lw1;
size_t remain = buf_len; int used = 0;
char *bb = buffer;
@@ -669,9 +958,21 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
ss->index = 0;
ss->lastBg = 0;
ss->lastFg = 0;
ss->lastChar = '\0';
ss->lastAttrs = 0;
memset(ss->lastChar, 0, 4); // this ensures the first char is never "repeat"
bufprint("{\n \"w\": %d, \"h\": %d,\n \"x\": %d, \"y\": %d,\n \"cv\": %d,\n \"screen\": \"", W, H, cursor.x, cursor.y, cursor.visible);
encode2B((u16) H, &w1);
encode2B((u16) W, &w2);
encode2B((u16) cursor.y, &w3);
encode2B((u16) cursor.x, &w4);
encode2B((u16) (
cursor.fg |
(cursor.bg<<4) |
(cursor.visible ? 1<<8 : 0))
, &w5);
// H W X Y Attribs
bufprint("S%c%c%c%c%c%c%c%c%c%c", w1.lsb, w1.msb, w2.lsb, w2.msb, w3.lsb, w3.msb, w4.lsb, w4.msb, w5.lsb, w5.msb);
}
int i = ss->index;
@@ -683,52 +984,53 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
while (i < W*H
&& cell->fg == ss->lastFg
&& cell->bg == ss->lastBg
&& cell->c == ss->lastChar) {
&& cell->attrs == ss->lastAttrs
&& strneq(cell->c, ss->lastChar, 4)) {
// Repeat
repCnt++;
cell = &screen[++i];
}
if (repCnt == 0) {
// All this crap is needed because it's JSON and also
// embedded in HTML (hence the angle brackets)
if (cell0->fg == ss->lastFg && cell0->bg == ss->lastBg) {
// same colors as previous
bufprint(",");
} else {
bufprint("%X%X", cell0->fg, cell0->bg);
// No repeat
bool changeAttrs = cell0->attrs != ss->lastAttrs;
bool changeColors = (cell0->fg != ss->lastFg || cell0->bg != ss->lastBg);
if (!changeAttrs && changeColors) {
encode2B(cell0->fg | (cell0->bg<<4), &w1);
bufprint("\x03%c%c", w1.lsb, w1.msb);
}
else if (changeAttrs && !changeColors) {
// attrs only
encode2B(cell0->attrs, &w1);
bufprint("\x04%c%c", w1.lsb, w1.msb);
}
else if (changeAttrs && changeColors) {
// colors and attrs
encode3B((u32) (
cell0->fg |
(cell0->bg<<4) |
(cell0->attrs<<8))
, &lw1);
bufprint("\x01%c%c%c", lw1.lsb, lw1.msb, lw1.xsb);
}
char c = cell0->c;
if (c == '"' || c == '\\') {
bufprint("\\%c", c);
}
else if (c == '<' || c == '>' || c == '\'' || c == '/' || c == '&') {
bufprint("\\u00%02X", (int)c);
}
else {
// copy the symbol, until first 0 or reached 4 bytes
char c;
int j = 0;
while ((c = cell->c[j++]) != 0 && j < 4) {
bufprint("%c", c);
}
ss->lastFg = cell0->fg;
ss->lastBg = cell0->bg;
ss->lastChar = cell0->c;
ss->lastAttrs = cell0->attrs;
memcpy(ss->lastChar, cell0->c, 4);
i++;
} else {
char code;
if(repCnt<10) {
code = 'r';
} else if(repCnt<100) {
code = 's';
} else if(repCnt<1000) {
code = 't';
} else {
code = 'u';
}
bufprint("%c%d", code, repCnt);
// Repeat count
encode2B((u16) repCnt, &w1);
bufprint("\x02%c%c", w1.lsb, w1.msb);
}
}
@@ -738,12 +1040,7 @@ screenSerializeToBuffer(char *buffer, size_t buf_len, void **data)
return HTTPD_CGI_MORE;
}
if (remain >= 3) {
bufprint("\"\n}");
return HTTPD_CGI_DONE;
} else {
return HTTPD_CGI_MORE;
}
}
//endregion
+72 -36
View File
@@ -34,17 +34,37 @@
*
*/
// Size designed for the wifi config structure
// Must be constant to avoid corrupting user config after upgrade
#define TERMCONF_SIZE 200
#define TERM_BTN_LEN 10
#define TERM_TITLE_LEN 64
typedef enum {
CHANGE_CONTENT,
CHANGE_LABELS,
} ScreenNotifyChangeTopic;
#define ATTR_BOLD (1<<0)
#define ATTR_FAINT (1<<1)
#define ATTR_ITALIC (1<<2)
#define ATTR_UNDERLINE (1<<3)
#define ATTR_BLINK (1<<4)
#define ATTR_FRAKTUR (1<<5)
#define ATTR_STRIKE (1<<6)
#define SCREEN_NOTIFY_DELAY_MS 20
typedef struct {
u32 width;
u32 height;
u8 default_bg;
u8 default_fg;
char title[64];
char btn1[10];
char btn2[10];
char btn3[10];
char btn4[10];
char btn5[10];
char title[TERM_TITLE_LEN];
char btn[5][TERM_BTN_LEN];
u8 theme;
u32 parser_tout_ms;
} TerminalConfigBundle;
// Live config
@@ -58,6 +78,7 @@ extern TerminalConfigBundle termconf_scratch;
void terminal_restore_defaults(void);
void terminal_apply_settings(void);
void terminal_apply_settings_noclear(void); // the same, but with no screen reset / init
/**
* Maximum screen size (determines size of the static data array)
@@ -65,7 +86,7 @@ void terminal_apply_settings(void);
* TODO May need adjusting if there are size problems when flashing the ESP.
* We could also try to pack the Cell struct to a single 32bit word.
*/
#define MAX_SCREEN_SIZE (80*25)
#define MAX_SCREEN_SIZE (80*30)
typedef enum {
CLEAR_TO_CURSOR=0, CLEAR_FROM_CURSOR=1, CLEAR_ALL=2
@@ -75,12 +96,26 @@ typedef uint8_t Color;
httpd_cgi_state screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
void screenSerializeLabelsToBuffer(char *buffer, size_t buf_len);
typedef struct {
u8 lsb;
u8 msb;
} WordB2;
typedef struct {
u8 lsb;
u8 msb;
u8 xsb;
} WordB3;
/** Encode number to two nice ASCII bytes */
void encode2B(u16 number, WordB2 *stru);
/** Init the screen */
void screen_init(void);
/** Change the screen size */
void screen_resize(int rows, int cols);
/** Check if coord is valid */
bool screen_isCoordValid(int y, int x);
@@ -88,45 +123,45 @@ bool screen_isCoordValid(int y, int x);
/** Screen reset to default state */
void screen_reset(void);
/** Clear entire screen, set all to 7 on 0 */
/** Clear entire screen */
void screen_clear(ClearMode mode);
/** Line reset to gray-on-white, empty */
/** Clear line */
void screen_clear_line(ClearMode mode);
/** Clear part of line */
void screen_clear_in_line(unsigned int count);
/** Shift screen upwards */
void screen_scroll_up(unsigned int lines);
/** Shift screen downwards */
void screen_scroll_down(unsigned int lines);
/** esc # 8 - fill entire screen with E of default colors (DEC alignment display) */
void screen_fill_with_E(void);
// --- insert / delete ---
void screen_insert_lines(unsigned int lines);
void screen_delete_lines(unsigned int lines);
void screen_insert_characters(unsigned int count);
void screen_delete_characters(unsigned int count);
// --- Cursor control ---
/** Set cursor position */
void screen_cursor_set(int y, int x);
/** Read cursor pos to given vars */
void screen_cursor_get(int *y, int *x);
/** Set cursor X position */
void screen_cursor_set_x(int x);
/** Set cursor Y position */
void screen_cursor_set_y(int y);
/** Reset cursor attribs */
void screen_reset_cursor(void);
/** Relative cursor move */
void screen_cursor_move(int dy, int dx);
void screen_cursor_move(int dy, int dx, bool scroll);
/** Save the cursor pos */
void screen_cursor_save(bool withAttrs);
/** Restore the cursor pos */
void screen_cursor_restore(bool withAttrs);
/** Enable cursor display */
void screen_cursor_enable(bool enable);
/** Enable auto wrap */
void screen_wrap_enable(bool enable);
@@ -134,28 +169,29 @@ void screen_wrap_enable(bool enable);
/** Set cursor foreground color */
void screen_set_fg(Color color);
/** Set cursor background coloor */
void screen_set_bg(Color color);
/** make foreground bright */
void screen_set_bright_fg(void);
// enable or disable attrs by bitmask
void screen_attr_enable(u8 attrs);
void screen_attr_disable(u8 attrs);
void screen_inverse_enable(bool ena);
/** Set cursor foreground and background color */
void screen_set_colors(Color fg, Color bg);
void screen_set_charset_n(int Gx);
void screen_set_charset(int Gx, char charset);
/** Invert colors */
void screen_inverse(bool inverse);
/** Set a character in the cursor color, move to right with wrap. */
void screen_putchar(char c);
/**
* Set a character in the cursor color, move to right with wrap.
* The character may be ASCII (then only one char is used), or
* unicode (then it can be 4 chars, or terminated by a zero)
*/
void screen_putchar(const char *ch);
#if 0
/** Debug dump */
void screen_dd(void);
#endif
extern void screen_notifyChange(void);
extern void screen_notifyChange(ScreenNotifyChangeTopic topic);
#endif // SCREEN_H
+23 -11
View File
@@ -2,19 +2,36 @@
#include "uart_driver.h"
#include "uart_handler.h"
#include "ansi_parser.h"
#include "syscfg.h"
// Here the bitrates are defined
#define UART0_BAUD BIT_RATE_115200
#define UART1_BAUD BIT_RATE_115200
/**
* Init the serial ports
*/
void ICACHE_FLASH_ATTR serialInitBase(void)
{
UART_Init();
// debug port
UART_SetParity(UART1, PARITY_NONE);
UART_SetStopBits(UART1, ONE_STOP_BIT);
UART_SetBaudrate(UART1, BIT_RATE_115200);
UART_SetPrintPort(UART1);
UART_SetupAsyncReceiver();
}
/**
* Init the serial ports
*/
void ICACHE_FLASH_ATTR serialInit(void)
{
UART_Init(UART0_BAUD, UART1_BAUD);
UART_SetPrintPort(UART1);
UART_SetupAsyncReceiver();
UART_SetParity(UART0, (UartParityMode) sysconf->uart_parity);
UART_SetStopBits(UART0, (UartStopBitsNum) sysconf->uart_stopbits);
UART_SetBaudrate(UART0, sysconf->uart_baudrate);
info("COM SERIAL: %d baud, %s parity, %s stopbit(s)",
sysconf->uart_baudrate,
(sysconf->uart_parity == PARITY_NONE ? "NONE" : (sysconf->uart_parity == PARITY_ODD ? "ODD" : "EVEN")),
(sysconf->uart_stopbits == ONE_STOP_BIT ? "1" : (sysconf->uart_stopbits == ONE_HALF_STOP_BIT ? "1.5" : "2"))
);
}
/**
@@ -25,10 +42,5 @@ void ICACHE_FLASH_ATTR serialInit(void)
*/
void ICACHE_FLASH_ATTR UART_HandleRxByte(char c)
{
if (c > 0 && c < 127) {
// TODO buffering, do not run parser after just 1 char
ansi_parser(&c, 1);
} else {
warn("Bad char %d ('%c')", (unsigned char)c, c);
}
}
+3
View File
@@ -1,6 +1,9 @@
#ifndef SERIAL_H
#define SERIAL_H
/** Initial uart init (before sysconf is loaded) */
void serialInitBase(void);
/** Init the uarts */
void serialInit(void);
+24
View File
@@ -0,0 +1,24 @@
//
// Created by MightyPork on 2017/07/29.
//
#include "syscfg.h"
#include "persist.h"
#include "uart_driver.h"
#include "serial.h"
SystemConfigBundle * const sysconf = &persist.current.sysconf;
void ICACHE_FLASH_ATTR
sysconf_apply_settings(void)
{
serialInit();
}
void ICACHE_FLASH_ATTR
sysconf_restore_defaults(void)
{
sysconf->uart_parity = PARITY_NONE;
sysconf->uart_baudrate = BIT_RATE_115200;
sysconf->uart_stopbits = ONE_STOP_BIT;
}
+26
View File
@@ -0,0 +1,26 @@
//
// Created by MightyPork on 2017/07/29.
//
#ifndef ESP_VT100_FIRMWARE_SYSCFG_H
#define ESP_VT100_FIRMWARE_SYSCFG_H
#include <esp8266.h>
// Size designed for the wifi config structure
// Must be constant to avoid corrupting user config after upgrade
#define SYSCONF_SIZE 200
typedef struct {
u32 uart_baudrate;
u8 uart_parity;
u8 uart_stopbits;
} SystemConfigBundle;
extern SystemConfigBundle * const sysconf;
void sysconf_apply_settings(void);
void sysconf_restore_defaults(void);
#endif //ESP_VT100_FIRMWARE_SYSCFG_H
+5 -17
View File
@@ -32,22 +32,8 @@ void ICACHE_FLASH_ATTR clear_rxtx(int uart_no)
}
/**
* @brief Configure UART 115200-8-N-1
* @param uart_no
*/
static void ICACHE_FLASH_ATTR my_uart_init(UARTn uart_no, uint32 baud)
{
UART_SetParity(uart_no, PARITY_NONE);
UART_SetStopBits(uart_no, ONE_STOP_BIT);
UART_SetWordLength(uart_no, EIGHT_BITS);
UART_SetBaudrate(uart_no, baud);
UART_ResetFifo(uart_no);
}
/** Configure basic UART func and pins */
void ICACHE_FLASH_ATTR UART_Init(uint32_t baud0, uint32_t baud1)
void ICACHE_FLASH_ATTR UART_Init(void)
{
// U0TXD
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
@@ -60,8 +46,10 @@ void ICACHE_FLASH_ATTR UART_Init(uint32_t baud0, uint32_t baud1)
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
// Configure the UART peripherals
my_uart_init(UART0, baud0); // main
my_uart_init(UART1, baud1); // debug (output only)
UART_SetWordLength(UART0, EIGHT_BITS); // main
UART_ResetFifo(UART0);
UART_SetWordLength(UART1, EIGHT_BITS); // debug (output only)
UART_ResetFifo(UART1);
}
/** Configure Rx on UART0 */
+2 -2
View File
@@ -12,8 +12,8 @@
#include <esp8266.h>
/** Configure UART periphs and enable pins */
void UART_Init(uint32_t baud0, uint32_t baud1);
/** Configure UART periphs and enable pins - does not set baud rate, parity and stopbits */
void UART_Init(void);
/** Configure async Rx on UART0 */
void UART_SetupAsyncReceiver(void);
+4 -18
View File
@@ -86,12 +86,12 @@ void ICACHE_FLASH_ATTR user_init(void)
static ETSTimer userStartTimer;
static ETSTimer prHeapTimer;
serialInit();
serialInitBase();
// Prevent WiFi starting and connecting by default
// let wifi manager handle it
wifi_station_set_auto_connect(false);
wifi_set_opmode(NULL_MODE); // save to flash if changed - this might avoid the current spike on startup?
wifi_set_opmode(NULL_MODE); // saves to flash if changed - this might avoid the current spike on startup?
printf("\r\n");
banner("====== ESP8266 Remote Terminal ======");
@@ -126,30 +126,16 @@ void ICACHE_FLASH_ATTR user_init(void)
static void user_start(void *unused)
{
// Change AP name if AI-THINKER found (means un-initialized device)
// struct softap_config apconf;
// wifi_softap_get_config(&apconf);
// if (strstarts((char *) apconf.ssid, "AI-THINKER")) {
// warn("Un-initialized device, performing factory reset.");
// apars_handle_OSC_FactoryReset();
// return;
// }
// Load and apply stored settings, or defaults if stored settings are invalid
persist_load();
// Captive portal (DNS redirector)
captdnsInit();
// Server
httpdInit(routes, 80);
// The terminal screen
captdnsInit();
httpdInit(routes, 80);
screen_init();
// Print the CANCEL character to indicate the module has restarted
// Critically important for client application if any kind of screen persistence / content re-use is needed
UART_WriteChar(UART0, 24, UART_TIMEOUT_US); // 0x18 - 24 - CAN
info("Listening on UART0, 115200-8-N-1!");
}
// ---- unused funcs removed from sdk to save space ---
+1 -1
View File
@@ -1,7 +1,7 @@
#ifndef USER_MAIN_H_H
#define USER_MAIN_H_H
#define FIRMWARE_VERSION "0.6.0-beta"
#define FIRMWARE_VERSION "0.6.7+" GIT_HASH
#define TERMINAL_GITHUB_REPO "https://github.com/MightyPork/esp-vt100-firmware"
#endif //USER_MAIN_H_H
+1 -1
View File
@@ -19,7 +19,7 @@ wifimgr_restore_defaults(void)
u8 mac[6];
wifi_get_macaddr(SOFTAP_IF, mac);
wificonf->opmode = SOFTAP_MODE;
wificonf->opmode = STATIONAP_MODE; // Client+AP, so we can scan without having to enable Station
wificonf->tpw = 20;
wificonf->ap_channel = 1;
sprintf((char *) wificonf->ap_ssid, "TERM-%02X%02X%02X", mac[3], mac[4], mac[5]);
+7 -4
View File
@@ -13,6 +13,10 @@
#define SSID_LEN 32
#define PASSWORD_LEN 64
// Size designed for the wifi config structure
// Must be constant to avoid corrupting user config after upgrade
#define WIFICONF_SIZE 340
/**
* A structure holding all configured WiFi parameters
* and the active state.
@@ -23,21 +27,20 @@ typedef struct {
WIFI_MODE opmode : 8;
u8 tpw;
// --- AP config ---
// 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 ---
// Client config
u8 sta_ssid[SSID_LEN];
u8 sta_password[PASSWORD_LEN];
bool sta_dhcp_enable;
struct ip_info sta_addr;
} WiFiConfigBundle;