Merge pull request #30 from MightyPork/wifi-redesign
Settings overhaul & exposed many new wifi settings
This commit is contained in:
+14
-3
@@ -36,7 +36,6 @@ set(SOURCE_FILES
|
||||
libesphttpd/include/espmissingincludes.h
|
||||
libesphttpd/include/espfs.h
|
||||
libesphttpd/include/esp8266.h
|
||||
libesphttpd/include/cgiwifi.h
|
||||
libesphttpd/include/cgiwebsocket.h
|
||||
libesphttpd/include/cgiflash.h
|
||||
libesphttpd/include/captdns.h
|
||||
@@ -54,7 +53,6 @@ set(SOURCE_FILES
|
||||
libesphttpd/lib/heatshrink/heatshrink_decoder.c
|
||||
libesphttpd/lib/heatshrink/heatshrink.c
|
||||
libesphttpd/mkupgimg/mkupgimg.c
|
||||
libesphttpd/util/cgiwifi.c
|
||||
libesphttpd/util/cgiwebsocket.c
|
||||
libesphttpd/util/cgiflash.c
|
||||
libesphttpd/util/captdns.c
|
||||
@@ -93,6 +91,14 @@ set(SOURCE_FILES
|
||||
include/ets_sys_extra.h
|
||||
user/io.c
|
||||
user/io.h
|
||||
user/cgi_wifi.c
|
||||
user/cgi_wifi.h
|
||||
user/cgi_persist.c
|
||||
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/uart_driver.c
|
||||
@@ -113,7 +119,11 @@ set(SOURCE_FILES
|
||||
user/cgi_sockets.h
|
||||
user/ansi_parser_callbacks.c
|
||||
user/ansi_parser_callbacks.h
|
||||
user/user_main.h)
|
||||
user/user_main.h
|
||||
user/wifimgr.c
|
||||
user/wifimgr.h
|
||||
user/persist.c
|
||||
user/persist.h include/helpers.h)
|
||||
|
||||
include_directories(include)
|
||||
include_directories(user)
|
||||
@@ -132,6 +142,7 @@ add_definitions(
|
||||
-DICACHE_FLASH_ATTR=
|
||||
-DICACHE_RODATA_ATTR=
|
||||
-DFLAG_GZIP=2
|
||||
-DADMIN_PASSWORD="asdf"
|
||||
-DESPFS_HEATSHRINK)
|
||||
|
||||
add_executable(esp_vt100_firmware ${SOURCE_FILES})
|
||||
|
||||
@@ -65,7 +65,7 @@ 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
|
||||
-Wno-address -Wno-unused -DHTTPD_MAX_BACKLOG_SIZE=8192 -DADMIN_PASSWORD=$(ADMIN_PASSWORD)
|
||||
|
||||
# linker flags used to generate the main object file
|
||||
LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static
|
||||
|
||||
+10
-19
@@ -7,24 +7,15 @@ mkdir -p html/img
|
||||
mkdir -p html/js
|
||||
mkdir -p html/css
|
||||
|
||||
# Join scripts
|
||||
DD=html_orig/jssrc
|
||||
cat $DD/chibi.js \
|
||||
$DD/utils.js \
|
||||
$DD/modal.js \
|
||||
$DD/appcommon.js \
|
||||
$DD/term.js \
|
||||
$DD/wifi.js > html/js/app.js
|
||||
cd html_orig
|
||||
sh ./packjs.sh
|
||||
php ./build_html.php
|
||||
cd ..
|
||||
|
||||
sass --sourcemap=none html_orig/sass/app.scss html_orig/css/app.css
|
||||
cp html_orig/js/app.js html/js/
|
||||
|
||||
# No need to compress CSS and JS now, we run YUI on it later
|
||||
cp html_orig/css/app.css html/css/app.css
|
||||
cp html_orig/term.html html/term.tpl
|
||||
cp html_orig/wifi.html html/wifi.tpl
|
||||
cp html_orig/about.html html/about.tpl
|
||||
cp html_orig/help.html html/help.tpl
|
||||
cp html_orig/wifi_conn.html html/wifi_conn.tpl
|
||||
cp html_orig/img/loader.gif html/img/loader.gif
|
||||
cp html_orig/img/cvut.svg html/img/cvut.svg
|
||||
cp html_orig/favicon.ico html/favicon.ico
|
||||
sass html_orig/sass/app.scss html/css/app.css
|
||||
rm html/css/app.css.map
|
||||
|
||||
cp html_orig/img/* html/img/
|
||||
cp html_orig/favicon.ico html/favicon.ico
|
||||
|
||||
@@ -37,3 +37,6 @@ OUTPUT_TYPE = combined
|
||||
|
||||
# SPI flash size, in K
|
||||
ESP_SPI_FLASH_SIZE_K = 1024
|
||||
|
||||
# Admin password, used to store settings to flash as defaults
|
||||
ADMIN_PASSWORD = "19738426"
|
||||
|
||||
@@ -1 +1 @@
|
||||
_test_env.php
|
||||
pages/_test_env.php
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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 %%
|
||||
*/
|
||||
return [
|
||||
'%term_title%' => 'ESP8266 Wireless Terminal',
|
||||
|
||||
'%btn1%' => '1',
|
||||
'%btn2%' => '2',
|
||||
'%btn3%' => '3',
|
||||
'%btn4%' => '4',
|
||||
'%btn5%' => '5',
|
||||
|
||||
'%screenData%' => '{
|
||||
"w": 26, "h": 10,
|
||||
"x": 0, "y": 0,
|
||||
"cv": 1,
|
||||
"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',
|
||||
|
||||
'%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',
|
||||
|
||||
'%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',
|
||||
];
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
define("ESP_IP", "192.168.0.19");
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
$pages = [];
|
||||
|
||||
if (! function_exists('pg')) {
|
||||
/** Add a page */
|
||||
function pg($key, $bc, $icon, $path, $titleKey = null)
|
||||
{
|
||||
global $pages;
|
||||
$pages[$key] = (object) [
|
||||
'key' => $key,
|
||||
'bodyclass' => $bc,
|
||||
'path' => $path,
|
||||
'icon' => $icon ? "icn-$icon" : '',
|
||||
'label' => tr("menu.$key"),
|
||||
'title' => $titleKey ? tr($titleKey) : tr("menu.$key"),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pg('cfg_wifi', 'cfg', 'wifi', '/cfg/wifi');
|
||||
pg('cfg_wifi_conn', '', '', '/cfg/wifi/connecting');
|
||||
pg('wifi_connstatus', 'api', '', '/cfg/wifi/connstatus');
|
||||
pg('wifi_set', 'api', '', '/cfg/wifi/set');
|
||||
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_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('help', 'cfg page-help', 'help', '/help');
|
||||
pg('about', 'cfg page-about', 'about', '/about');
|
||||
pg('term', 'term', '', '/', 'title.term');
|
||||
|
||||
// ajax API
|
||||
|
||||
return $pages;
|
||||
@@ -1,80 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>About - ESP8266 Remote Terminal</title>
|
||||
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<script src="/js/app.js"></script>
|
||||
</head>
|
||||
<body class="page-about">
|
||||
|
||||
<h1 onclick="location.href='/'">About</h1>
|
||||
|
||||
<div class="Box">
|
||||
<img src="/img/cvut.svg" id="logo" class="mq-tablet-min">
|
||||
<h2>ESP8266 Remote Terminal</h2>
|
||||
|
||||
<img src="/img/cvut.svg" id="logo2" class="mq-phone">
|
||||
|
||||
<p>© Ondřej Hruška, 2017 <<a href="mailto:ondra@ondrovo.com">ondra@ondrovo.com</a>></p>
|
||||
|
||||
<p><a href="http://measure.feld.cvut.cz/" target="blank">Katedra měření, FEL ČVUT</a><br>Department of Measurement, FEE CTU</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Firmware</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Firmware</th>
|
||||
<td>v%vers_fw%, build <i>%date%</i> at <i>%time%</i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>libesphttpd</th>
|
||||
<td>v%vers_httpd%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ESP IoT SDK</th>
|
||||
<td>v%vers_sdk%</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Issues</h2>
|
||||
<p>
|
||||
Please report any issues to the <a href="%githubrepo%/issues">bugtracker</a> or send them by e-mail.
|
||||
</p>
|
||||
<p>
|
||||
Firmware updates can be downloaded from the <a href="%githubrepo%/releases">releases page</a>.
|
||||
Flash the images using <a href="https://github.com/espressif/esptool">esptool</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Contributing</h2>
|
||||
<p>
|
||||
Submit your improvements and ideas to the project on <a href="%githubrepo%">GitHub</a>.<br>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Acknowledgements</h2>
|
||||
<p>
|
||||
The webserver is based on a <a href="https://github.com/MightyPork/libesphttpd">fork</a> of the
|
||||
<a href="https://github.com/Spritetm/esphttpd">esphttpd</a> library by Jeroen Domburg (Sprite_tm).
|
||||
</p>
|
||||
<p>
|
||||
Using (modified) JS library <a href="https://github.com/kylebarrow/chibi">chibi.js</a> by Kyle Barrow as a lightweight jQuery alternative.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<nav id="botnav">
|
||||
<a href="/">Terminal</a><!--
|
||||
--><a href="/help">Help</a><!--
|
||||
--><a href="/wifi">WiFi config</a>
|
||||
</nav>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The common stuff required by both index.php and build_html.php
|
||||
* this must be required_once
|
||||
*/
|
||||
|
||||
if (defined('BASE_INITED')) return;
|
||||
define('BASE_INITED', true);
|
||||
|
||||
if (!empty($argv[1])) {
|
||||
parse_str($argv[1], $_GET);
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__ . '/_env.php')) {
|
||||
die("Copy <b>_env.php.example</b> to <b>_env.php</b> and check the settings inside!");
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/_env.php';
|
||||
|
||||
$prod = defined('STDIN');
|
||||
define('DEBUG', !$prod);
|
||||
$root = DEBUG ? json_encode(ESP_IP) : 'location.host';
|
||||
define('JS_WEB_ROOT', $root);
|
||||
|
||||
define('LOCALE', isset($_GET['locale']) ? $_GET['locale'] : 'en');
|
||||
|
||||
$_messages = require(__DIR__ . '/lang/' . LOCALE . '.php');
|
||||
$_pages = require(__DIR__ . '/_pages.php');
|
||||
|
||||
define('APP_NAME', 'ESPTerm');
|
||||
|
||||
/** URL (dev or production) */
|
||||
function url($name, $relative = false)
|
||||
{
|
||||
global $_pages;
|
||||
if ($relative) return $_pages[$name]->path;
|
||||
|
||||
if (DEBUG) return "/index.php?page=$name";
|
||||
else return $_pages[$name]->path;
|
||||
}
|
||||
|
||||
/** URL label for a button */
|
||||
function label($name)
|
||||
{
|
||||
global $_pages;
|
||||
return $_pages[$name]->label;
|
||||
}
|
||||
|
||||
function e($s)
|
||||
{
|
||||
return htmlspecialchars($s, ENT_HTML5 | ENT_QUOTES);
|
||||
}
|
||||
|
||||
function tr($key)
|
||||
{
|
||||
global $_messages;
|
||||
return isset($_messages[$key]) ? $_messages[$key] : ('??' . $key . '??');
|
||||
}
|
||||
|
||||
/** Like eval, but allows <?php and ?> */
|
||||
function include_str($code)
|
||||
{
|
||||
$tmp = tmpfile();
|
||||
$tmpf = stream_get_meta_data($tmp);
|
||||
$tmpf = $tmpf ['uri'];
|
||||
fwrite($tmp, $code);
|
||||
$ret = include($tmpf);
|
||||
fclose($tmp);
|
||||
return $ret;
|
||||
}
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
function process_html($s) {
|
||||
$pattern = '/<!--(.*)-->/Uis';
|
||||
$s = preg_replace($pattern, '', $s);
|
||||
|
||||
$pattern = '/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(?<!\:|\\\|\'|\")\/\/.*))/';
|
||||
$s = preg_replace($pattern, '', $s);
|
||||
|
||||
$pattern = '/\s+/s';
|
||||
$s = preg_replace($pattern, ' ', $s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
foreach($_pages as $_k => $p) {
|
||||
if ($p->bodyclass == 'api') continue;
|
||||
echo "Generating: $_k ($p->title)\n";
|
||||
$_GET['page'] = $_k;
|
||||
ob_flush(); // print the message
|
||||
ob_clean(); // clean up
|
||||
include(__DIR__ . '/index.php');
|
||||
$s = ob_get_contents(); // grab the output
|
||||
|
||||
// remove newlines and comments
|
||||
// as tests have shown, it saves just a couple kilobytes,
|
||||
// making it not a very big improvement at the expense of ugly html.
|
||||
// $s = process_html($s);
|
||||
|
||||
ob_clean(); // clean up
|
||||
$of = __DIR__ . '/../html/' . $_k . '.tpl';
|
||||
file_put_contents($of, $s); // write to a file
|
||||
}
|
||||
|
||||
ob_flush();
|
||||
+637
-215
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Executable
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/* This script is run on demand to generate JS version of tr() */
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
$selected = [
|
||||
'wifi.connected_ip_is',
|
||||
'wifi.not_conn',
|
||||
];
|
||||
|
||||
$out = [];
|
||||
foreach ($selected as $key) {
|
||||
$out[$key] = $_messages[$key];
|
||||
}
|
||||
|
||||
file_put_contents(__DIR__. '/jssrc/lang.js',
|
||||
"// Generated from PHP locale file\n" .
|
||||
'var _tr = ' . json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE) . ";\n\n" .
|
||||
"function tr(key) { return _tr[key] || '?'+key+'?'; }\n"
|
||||
);
|
||||
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/base.php';
|
||||
|
||||
if (!isset($_GET['page'])) $_GET['page'] = 'term';
|
||||
|
||||
$_GET['PAGE_TITLE'] = $_pages[$_GET['page']]->title . ' :: ' . APP_NAME;
|
||||
$_GET['BODYCLASS'] = $_pages[$_GET['page']]->bodyclass;
|
||||
|
||||
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);
|
||||
include_str($str);
|
||||
} else {
|
||||
echo "404";
|
||||
}
|
||||
|
||||
require __DIR__ . '/pages/_tail.php';
|
||||
+334
-202
@@ -393,8 +393,9 @@
|
||||
return cb;
|
||||
};
|
||||
// Toggle class
|
||||
cb.toggleClass = function (classes) {
|
||||
classHelper(classes, 'toggle', nodes);
|
||||
cb.toggleClass = function (classes, set) {
|
||||
var method = ((typeof set === 'undefined') ? 'toggle' : (+set ? 'add' : 'remove'));
|
||||
classHelper(classes, method, nodes);
|
||||
return cb;
|
||||
};
|
||||
// Has class
|
||||
@@ -704,18 +705,27 @@
|
||||
function mk(e) {return document.createElement(e)}
|
||||
|
||||
/** Find one by query */
|
||||
function qq(s) {return document.querySelector(s)}
|
||||
function qs(s) {return document.querySelector(s)}
|
||||
|
||||
/** Find all by query */
|
||||
function qa(s) {return document.querySelectorAll(s)}
|
||||
function qsa(s) {return document.querySelectorAll(s)}
|
||||
|
||||
/** Convert any to bool safely */
|
||||
function bool(x) {
|
||||
return (x === 1 || x === '1' || x === true || x === 'true');
|
||||
}
|
||||
|
||||
function intval(x) {
|
||||
return parseInt(x);
|
||||
/**
|
||||
* Filter 'spacebar' and 'return' from keypress handler,
|
||||
* and when they're pressed, fire the callback.
|
||||
* use $(...).on('keypress', cr(handler))
|
||||
*/
|
||||
function cr(hdl) {
|
||||
return function(e) {
|
||||
if (e.which == 10 || e.which == 13 || e.which == 32) {
|
||||
hdl();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Extend an objects with options */
|
||||
@@ -738,23 +748,23 @@ function rgxe(str) {
|
||||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
||||
}
|
||||
|
||||
/** Format number to N decimal places, output as string */
|
||||
function numfmt(x, places) {
|
||||
var pow = Math.pow(10, places);
|
||||
return Math.round(x*pow) / pow;
|
||||
}
|
||||
|
||||
function estimateLoadTime(fs, n) {
|
||||
return (1000/fs)*n+1500;
|
||||
}
|
||||
|
||||
/** Get millisecond timestamp */
|
||||
function msNow() {
|
||||
return +(new Date);
|
||||
}
|
||||
|
||||
/** Get ms elapsed since msNow() */
|
||||
function msElapsed(start) {
|
||||
return msNow() - start;
|
||||
}
|
||||
|
||||
/** Shim for log base 10 */
|
||||
Math.log10 = Math.log10 || function(x) {
|
||||
return Math.log(x) / Math.LN10;
|
||||
};
|
||||
@@ -795,6 +805,25 @@ String.prototype.format = function () {
|
||||
return out;
|
||||
};
|
||||
|
||||
/** HTML escape */
|
||||
function e(str) {
|
||||
return $.htmlEscape(str);
|
||||
}
|
||||
|
||||
/** Check for undefined */
|
||||
function undef(x) {
|
||||
return typeof x == 'undefined';
|
||||
}
|
||||
|
||||
/** Safe json parse */
|
||||
function jsp() {
|
||||
try {
|
||||
return JSON.parse(e);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/** Module for toggling a modal overlay */
|
||||
(function () {
|
||||
var modal = {};
|
||||
@@ -836,8 +865,73 @@ String.prototype.format = function () {
|
||||
|
||||
window.Modal = modal;
|
||||
})();
|
||||
(function (nt) {
|
||||
var sel = '#notif';
|
||||
|
||||
var hideTmeo1; // timeout to start hiding (transition)
|
||||
var hideTmeo2; // timeout to add the hidden class
|
||||
|
||||
nt.show = function (message, timeout) {
|
||||
$(sel).html(message);
|
||||
Modal.show(sel);
|
||||
|
||||
clearTimeout(hideTmeo1);
|
||||
clearTimeout(hideTmeo2);
|
||||
|
||||
if (undef(timeout)) timeout = 2500;
|
||||
|
||||
hideTmeo1 = setTimeout(nt.hide, timeout);
|
||||
};
|
||||
|
||||
nt.hide = function () {
|
||||
var $m = $(sel);
|
||||
$m.removeClass('visible');
|
||||
hideTmeo2 = setTimeout(function () {
|
||||
$m.addClass('hidden');
|
||||
}, 250); // transition time
|
||||
};
|
||||
|
||||
nt.init = function() {
|
||||
$(sel).on('click', function() {
|
||||
nt.hide(this);
|
||||
});
|
||||
};
|
||||
})(window.Notify = {});
|
||||
/** Global generic init */
|
||||
$.ready(function () {
|
||||
// Checkbox UI (checkbox CSS and hidden input with int value)
|
||||
$('.Row.checkbox').forEach(function(x) {
|
||||
var inp = x.querySelector('input');
|
||||
var box = x.querySelector('.box');
|
||||
|
||||
$(box).toggleClass('checked', inp.value);
|
||||
|
||||
var hdl = function() {
|
||||
inp.value = 1 - inp.value;
|
||||
$(box).toggleClass('checked', inp.value)
|
||||
};
|
||||
|
||||
$(x).on('click', hdl).on('keypress', cr(hdl));
|
||||
});
|
||||
|
||||
// Expanding boxes on mobile
|
||||
$('.Box.mobcol').forEach(function(x) {
|
||||
var h = x.querySelector('h2');
|
||||
|
||||
var hdl = function() {
|
||||
$(x).toggleClass('expanded');
|
||||
};
|
||||
$(h).on('click', hdl).on('keypress', cr(hdl));
|
||||
});
|
||||
|
||||
qsa('form').forEach(function(x) {
|
||||
$(x).on('keypress', function(e) {
|
||||
if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) {
|
||||
x.submit();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// loader dots...
|
||||
setInterval(function () {
|
||||
$('.anim-dots').each(function (x) {
|
||||
@@ -850,12 +944,13 @@ $.ready(function () {
|
||||
|
||||
// flipping number boxes with the mouse wheel
|
||||
$('input[type=number]').on('mousewheel', function(e) {
|
||||
var val = +$(this).val();
|
||||
var $this = $(this);
|
||||
var val = +$this.val();
|
||||
if (isNaN(val)) val = 1;
|
||||
|
||||
var step = +($(this).attr('step') || 1);
|
||||
var min = +$(this).attr('min');
|
||||
var max = +$(this).attr('max');
|
||||
var step = +($this.attr('step') || 1);
|
||||
var min = +$this.attr('min');
|
||||
var max = +$this.attr('max');
|
||||
if(e.wheelDelta > 0) {
|
||||
val += step;
|
||||
} else {
|
||||
@@ -864,28 +959,243 @@ $.ready(function () {
|
||||
|
||||
if (typeof min != 'undefined') val = Math.max(val, +min);
|
||||
if (typeof max != 'undefined') val = Math.min(val, +max);
|
||||
$(this).val(val);
|
||||
$this.val(val);
|
||||
|
||||
if ("createEvent" in document) {
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
evt.initEvent("change", false, true);
|
||||
$(this)[0].dispatchEvent(evt);
|
||||
$this[0].dispatchEvent(evt);
|
||||
} else {
|
||||
$(this)[0].fireEvent("onchange");
|
||||
$this[0].fireEvent("onchange");
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
var errAt = location.search.indexOf('err=');
|
||||
if (errAt !== -1 && qs('.Box.errors')) {
|
||||
var errs = location.search.substr(errAt+4).split(',');
|
||||
var hres = [];
|
||||
errs.forEach(function(er) {
|
||||
var lbl = qs('label[for="'+er+'"]');
|
||||
if (lbl) {
|
||||
lbl.classList.add('error');
|
||||
hres.push(lbl.childNodes[0].textContent.trim().replace(/: ?$/, ''));
|
||||
} else {
|
||||
hres.push(er);
|
||||
}
|
||||
});
|
||||
|
||||
qs('.Box.errors .list').innerHTML = hres.join(', ');
|
||||
qs('.Box.errors').classList.remove('hidden');
|
||||
}
|
||||
|
||||
Modal.init();
|
||||
Notify.init();
|
||||
|
||||
// remove tabindixes from h2 if wide
|
||||
if (window.innerWidth > 550) {
|
||||
qsa('.Box h2').forEach(function (x) {
|
||||
x.removeAttribute('tabindex');
|
||||
});
|
||||
|
||||
var br = qs('#brand');
|
||||
br && br.removeAttribute('tabindex');
|
||||
}
|
||||
});
|
||||
|
||||
$._loader = function(vis) {
|
||||
if(vis)
|
||||
$('#loader').addClass('show');
|
||||
else
|
||||
$('#loader').removeClass('show');
|
||||
$('#loader').toggleClass('show', vis);
|
||||
};
|
||||
// Generated from PHP locale file
|
||||
var _tr = {
|
||||
"wifi.connected_ip_is": "Connected, IP is ",
|
||||
"wifi.not_conn": "Not connected."
|
||||
};
|
||||
|
||||
function tr(key) { return _tr[key] || '?'+key+'?'; }
|
||||
(function(w) {
|
||||
var authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2'];
|
||||
var curSSID;
|
||||
|
||||
// Get XX % for a slider input
|
||||
function rangePt(inp) {
|
||||
return Math.round(((inp.value / inp.max)*100)) + '%';
|
||||
}
|
||||
|
||||
// Display selected STA SSID etc
|
||||
function selectSta(name, password, ip) {
|
||||
$('#sta_ssid').val(name);
|
||||
$('#sta_password').val(password);
|
||||
|
||||
$('#sta-nw').toggleClass('hidden', name.length == 0);
|
||||
$('#sta-nw-nil').toggleClass('hidden', name.length > 0);
|
||||
|
||||
$('#sta-nw .essid').html(e(name));
|
||||
var nopw = undef(password) || password.length == 0;
|
||||
$('#sta-nw .x-passwd').html(e(password));
|
||||
$('#sta-nw .passwd').toggleClass('hidden', nopw);
|
||||
$('#sta-nw .nopasswd').toggleClass('hidden', !nopw);
|
||||
$('#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 = {
|
||||
// "result": {
|
||||
// "inProgress": "0",
|
||||
// "APs": [
|
||||
// {"essid": "Chlivek", "bssid": "88:f7:c7:52:b3:99", "rssi": "204", "enc": "4", "channel": "1"},
|
||||
// {"essid": "TyNikdy", "bssid": "5c:f4:ab:0d:f1:1b", "rssi": "164", "enc": "3", "channel": "1"},
|
||||
// ]
|
||||
// }
|
||||
//};
|
||||
|
||||
if (status != 200) {
|
||||
// bad response
|
||||
rescan(5000); // wait 5sm then retry
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
resp = JSON.parse(resp);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
rescan(5000);
|
||||
return;
|
||||
}
|
||||
|
||||
var done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0);
|
||||
rescan(done ? 15000 : 1000);
|
||||
if (!done) return; // no redraw yet
|
||||
|
||||
// clear the AP list
|
||||
var $list = $('#ap-list');
|
||||
// remove old APs
|
||||
$('#ap-list .AP').remove();
|
||||
|
||||
$list.toggleClass('hidden', !done);
|
||||
$('#ap-loader').toggleClass('hidden', done);
|
||||
|
||||
// scan done
|
||||
resp.result.APs.sort(function (a, b) {
|
||||
return b.rssi - a.rssi;
|
||||
}).forEach(function (ap) {
|
||||
ap.enc = parseInt(ap.enc);
|
||||
|
||||
if (ap.enc > 4) return; // hide unsupported auths
|
||||
WiFi.scan_url = '/cfg/wifi/scan';
|
||||
|
||||
var item = mk('div');
|
||||
|
||||
var $item = $(item)
|
||||
.data('ssid', ap.essid)
|
||||
.data('pwd', ap.enc)
|
||||
.attr('tabindex', 0)
|
||||
.addClass('AP');
|
||||
|
||||
// mark current SSID
|
||||
if (ap.essid == curSSID) {
|
||||
$item.addClass('selected');
|
||||
}
|
||||
|
||||
var inner = mk('div');
|
||||
$(inner).addClass('inner')
|
||||
.htmlAppend('<div class="rssi">{0}</div>'.format(ap.rssi_perc))
|
||||
.htmlAppend('<div class="essid" title="{0}">{0}</div>'.format($.htmlEscape(ap.essid)))
|
||||
.htmlAppend('<div class="auth">{0}</div>'.format(authStr[ap.enc]));
|
||||
|
||||
$item.on('click', function () {
|
||||
var $th = $(this);
|
||||
|
||||
var ssid = $th.data('ssid');
|
||||
|
||||
$('#conn-ssid').val(ssid);
|
||||
$('#conn-passwd').val('');
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
item.appendChild(inner);
|
||||
$list[0].appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function startScanning() {
|
||||
$('#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);
|
||||
}
|
||||
|
||||
function rescan(time) {
|
||||
setTimeout(scanAPs, time);
|
||||
}
|
||||
|
||||
/** 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');
|
||||
var disp1 = x.querySelector('.x-disp1');
|
||||
var disp2 = x.querySelector('.x-disp2');
|
||||
var t = rangePt(inp);
|
||||
$(disp1).html(t);
|
||||
$(disp2).html(t);
|
||||
$(inp).on('input', function() {
|
||||
t = rangePt(inp);
|
||||
$(disp1).html(t);
|
||||
$(disp2).html(t);
|
||||
});
|
||||
});
|
||||
|
||||
// Forget STA credentials
|
||||
$('#forget-sta').on('click', function() {
|
||||
selectSta('', '', '');
|
||||
return false;
|
||||
});
|
||||
|
||||
selectSta(cfg.sta_ssid, cfg.sta_password, cfg.sta_active_ip);
|
||||
curSSID = cfg.sta_active_ssid;
|
||||
}
|
||||
|
||||
w.init = wifiInit;
|
||||
w.startScanning = startScanning;
|
||||
})(window.WiFi = {});
|
||||
(function() {
|
||||
/**
|
||||
* Terminal module
|
||||
@@ -1023,7 +1333,7 @@ $._loader = function(vis) {
|
||||
H = obj.h;
|
||||
|
||||
/* Build screen & show */
|
||||
var e, cell, scr = qq('#screen');
|
||||
var e, cell, scr = qs('#screen');
|
||||
|
||||
// Empty the screen node
|
||||
while (scr.firstChild) scr.removeChild(scr.firstChild);
|
||||
@@ -1088,7 +1398,7 @@ $._loader = function(vis) {
|
||||
console.warn("SOCKET CLOSED, code "+evt.code+". Reconnecting...");
|
||||
setTimeout(function() {
|
||||
init();
|
||||
}, 2000);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function onMessage(evt) {
|
||||
@@ -1169,7 +1479,7 @@ $._loader = function(vis) {
|
||||
}
|
||||
});
|
||||
|
||||
qa('#buttons button').forEach(function(s) {
|
||||
qsa('#buttons button').forEach(function(s) {
|
||||
s.addEventListener('click', function() {
|
||||
sendBtnMsg(+this.dataset['n']);
|
||||
});
|
||||
@@ -1187,183 +1497,5 @@ $._loader = function(vis) {
|
||||
Term.init(obj);
|
||||
Conn.init();
|
||||
Input.init();
|
||||
}
|
||||
})();
|
||||
/** Wifi page */
|
||||
(function () {
|
||||
var authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2'];
|
||||
var curSSID;
|
||||
|
||||
/** Update display for received response */
|
||||
function onScan(resp, status) {
|
||||
if (status != 200) {
|
||||
// bad response
|
||||
rescan(5000); // wait 5sm then retry
|
||||
return;
|
||||
}
|
||||
|
||||
resp = JSON.parse(resp);
|
||||
|
||||
var done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0);
|
||||
rescan(done ? 15000 : 1000);
|
||||
if (!done) return; // no redraw yet
|
||||
|
||||
// clear the AP list
|
||||
var $list = $('#ap-list');
|
||||
// remove old APs
|
||||
$('.AP').remove();
|
||||
|
||||
$list.toggle(done);
|
||||
$('#ap-loader').toggle(!done);
|
||||
|
||||
// scan done
|
||||
resp.result.APs.sort(function (a, b) {
|
||||
return b.rssi - a.rssi;
|
||||
}).forEach(function (ap) {
|
||||
ap.enc = intval(ap.enc);
|
||||
|
||||
if (ap.enc > 4) return; // hide unsupported auths
|
||||
|
||||
var item = document.createElement('div');
|
||||
|
||||
var $item = $(item)
|
||||
.data('ssid', ap.essid)
|
||||
.data('pwd', ap.enc != 0)
|
||||
.addClass('AP');
|
||||
|
||||
// mark current SSID
|
||||
if (ap.essid == curSSID) {
|
||||
$item.addClass('selected');
|
||||
}
|
||||
|
||||
var inner = document.createElement('div');
|
||||
$(inner).addClass('inner')
|
||||
.htmlAppend('<div class="rssi">{0}</div>'.format(ap.rssi_perc))
|
||||
.htmlAppend('<div class="essid" title="{0}">{0}</div>'.format($.htmlEscape(ap.essid)))
|
||||
.htmlAppend('<div class="auth">{0}</div>'.format(authStr[ap.enc]));
|
||||
|
||||
$item.on('click', function () {
|
||||
var $th = $(this);
|
||||
|
||||
// populate the form
|
||||
$('#conn-essid').val($th.data('ssid'));
|
||||
$('#conn-passwd').val(''); // clear
|
||||
|
||||
if ($th.data('pwd')) {
|
||||
// this AP needs a password
|
||||
Modal.show('#psk-modal');
|
||||
} else {
|
||||
Modal.show('#reset-modal');
|
||||
$('#conn-form').submit();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
item.appendChild(inner);
|
||||
$list[0].appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
/** Ask the CGI what APs are visible (async) */
|
||||
function scanAPs() {
|
||||
$.get('http://'+_root+'/wifi/scan', onScan);
|
||||
}
|
||||
|
||||
function rescan(time) {
|
||||
setTimeout(scanAPs, time);
|
||||
}
|
||||
|
||||
/** Set up the WiFi page */
|
||||
window.wifiInit = function (obj) {
|
||||
//var ap_json = {
|
||||
// "result": {
|
||||
// "inProgress": "0",
|
||||
// "APs": [
|
||||
// {"essid": "Chlivek", "bssid": "88:f7:c7:52:b3:99", "rssi": "204", "enc": "4", "channel": "1"},
|
||||
// {"essid": "TyNikdy", "bssid": "5c:f4:ab:0d:f1:1b", "rssi": "164", "enc": "3", "channel": "1"},
|
||||
// ]
|
||||
// }
|
||||
//};
|
||||
|
||||
// Hide what should be hidden in this mode
|
||||
$('.x-hide-'+obj.mode).addClass('hidden');
|
||||
obj.mode = +obj.mode;
|
||||
|
||||
// Channel writable only in AP mode
|
||||
if (obj.mode != 2) $('#channel').attr('readonly', 1);
|
||||
|
||||
curSSID = obj.staSSID;
|
||||
|
||||
// add SSID to the opmode field
|
||||
if (curSSID) {
|
||||
var box = $('#opmodebox');
|
||||
box.html(box.html() + ' (' + curSSID + ')');
|
||||
}
|
||||
|
||||
// hide IP if IP not received
|
||||
if (!obj.staIP) $('.x-hide-noip').addClass('hidden');
|
||||
|
||||
// scan if not AP
|
||||
if (obj.mode != 2) {
|
||||
scanAPs();
|
||||
}
|
||||
|
||||
$('#modeswitch').html([
|
||||
'<a class="button" href="/wifi/setmode?mode=3">Client+AP</a> <a class="button" href="/wifi/setmode?mode=2">AP only</a>',
|
||||
'<a class="button" href="/wifi/setmode?mode=3">Client+AP</a>',
|
||||
'<a class="button" href="/wifi/setmode?mode=1">Client only</a> <a class="button" href="/wifi/setmode?mode=2">AP only</a>'
|
||||
][obj.mode-1]);
|
||||
};
|
||||
|
||||
window.wifiConn = function () {
|
||||
var xhr = new XMLHttpRequest();
|
||||
var abortTmeo;
|
||||
|
||||
function getStatus() {
|
||||
xhr.open("GET", 'http://'+_root+"/wifi/connstatus");
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) {
|
||||
clearTimeout(abortTmeo);
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
var done = false;
|
||||
var msg = '...';
|
||||
|
||||
if (data.status == "idle") {
|
||||
msg = "Preparing to connect";
|
||||
}
|
||||
else if (data.status == "success") {
|
||||
msg = "Connected! Received IP " + data.ip + ".";
|
||||
done = true;
|
||||
}
|
||||
else if (data.status == "working") {
|
||||
msg = "Connecting to selected AP";
|
||||
}
|
||||
else if (data.status == "fail") {
|
||||
msg = "Connection failed, check your password and try again.";
|
||||
done = true;
|
||||
}
|
||||
|
||||
$("#status").html(msg);
|
||||
|
||||
if (done) {
|
||||
$('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
} else {
|
||||
window.setTimeout(getStatus, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
abortTmeo = setTimeout(function () {
|
||||
xhr.abort();
|
||||
$("#status").html("Telemetry lost, try reconnecting to the AP.");
|
||||
$('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
}, 4000);
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
getStatus();
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,38 @@
|
||||
/** Global generic init */
|
||||
$.ready(function () {
|
||||
// Checkbox UI (checkbox CSS and hidden input with int value)
|
||||
$('.Row.checkbox').forEach(function(x) {
|
||||
var inp = x.querySelector('input');
|
||||
var box = x.querySelector('.box');
|
||||
|
||||
$(box).toggleClass('checked', inp.value);
|
||||
|
||||
var hdl = function() {
|
||||
inp.value = 1 - inp.value;
|
||||
$(box).toggleClass('checked', inp.value)
|
||||
};
|
||||
|
||||
$(x).on('click', hdl).on('keypress', cr(hdl));
|
||||
});
|
||||
|
||||
// Expanding boxes on mobile
|
||||
$('.Box.mobcol').forEach(function(x) {
|
||||
var h = x.querySelector('h2');
|
||||
|
||||
var hdl = function() {
|
||||
$(x).toggleClass('expanded');
|
||||
};
|
||||
$(h).on('click', hdl).on('keypress', cr(hdl));
|
||||
});
|
||||
|
||||
qsa('form').forEach(function(x) {
|
||||
$(x).on('keypress', function(e) {
|
||||
if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) {
|
||||
x.submit();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// loader dots...
|
||||
setInterval(function () {
|
||||
$('.anim-dots').each(function (x) {
|
||||
@@ -12,12 +45,13 @@ $.ready(function () {
|
||||
|
||||
// flipping number boxes with the mouse wheel
|
||||
$('input[type=number]').on('mousewheel', function(e) {
|
||||
var val = +$(this).val();
|
||||
var $this = $(this);
|
||||
var val = +$this.val();
|
||||
if (isNaN(val)) val = 1;
|
||||
|
||||
var step = +($(this).attr('step') || 1);
|
||||
var min = +$(this).attr('min');
|
||||
var max = +$(this).attr('max');
|
||||
var step = +($this.attr('step') || 1);
|
||||
var min = +$this.attr('min');
|
||||
var max = +$this.attr('max');
|
||||
if(e.wheelDelta > 0) {
|
||||
val += step;
|
||||
} else {
|
||||
@@ -26,25 +60,51 @@ $.ready(function () {
|
||||
|
||||
if (typeof min != 'undefined') val = Math.max(val, +min);
|
||||
if (typeof max != 'undefined') val = Math.min(val, +max);
|
||||
$(this).val(val);
|
||||
$this.val(val);
|
||||
|
||||
if ("createEvent" in document) {
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
evt.initEvent("change", false, true);
|
||||
$(this)[0].dispatchEvent(evt);
|
||||
$this[0].dispatchEvent(evt);
|
||||
} else {
|
||||
$(this)[0].fireEvent("onchange");
|
||||
$this[0].fireEvent("onchange");
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
var errAt = location.search.indexOf('err=');
|
||||
if (errAt !== -1 && qs('.Box.errors')) {
|
||||
var errs = location.search.substr(errAt+4).split(',');
|
||||
var hres = [];
|
||||
errs.forEach(function(er) {
|
||||
var lbl = qs('label[for="'+er+'"]');
|
||||
if (lbl) {
|
||||
lbl.classList.add('error');
|
||||
hres.push(lbl.childNodes[0].textContent.trim().replace(/: ?$/, ''));
|
||||
} else {
|
||||
hres.push(er);
|
||||
}
|
||||
});
|
||||
|
||||
qs('.Box.errors .list').innerHTML = hres.join(', ');
|
||||
qs('.Box.errors').classList.remove('hidden');
|
||||
}
|
||||
|
||||
Modal.init();
|
||||
Notify.init();
|
||||
|
||||
// remove tabindixes from h2 if wide
|
||||
if (window.innerWidth > 550) {
|
||||
qsa('.Box h2').forEach(function (x) {
|
||||
x.removeAttribute('tabindex');
|
||||
});
|
||||
|
||||
var br = qs('#brand');
|
||||
br && br.removeAttribute('tabindex');
|
||||
}
|
||||
});
|
||||
|
||||
$._loader = function(vis) {
|
||||
if(vis)
|
||||
$('#loader').addClass('show');
|
||||
else
|
||||
$('#loader').removeClass('show');
|
||||
$('#loader').toggleClass('show', vis);
|
||||
};
|
||||
|
||||
@@ -393,8 +393,9 @@
|
||||
return cb;
|
||||
};
|
||||
// Toggle class
|
||||
cb.toggleClass = function (classes) {
|
||||
classHelper(classes, 'toggle', nodes);
|
||||
cb.toggleClass = function (classes, set) {
|
||||
var method = ((typeof set === 'undefined') ? 'toggle' : (+set ? 'add' : 'remove'));
|
||||
classHelper(classes, method, nodes);
|
||||
return cb;
|
||||
};
|
||||
// Has class
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Generated from PHP locale file
|
||||
var _tr = {
|
||||
"wifi.connected_ip_is": "Connected, IP is ",
|
||||
"wifi.not_conn": "Not connected."
|
||||
};
|
||||
|
||||
function tr(key) { return _tr[key] || '?'+key+'?'; }
|
||||
@@ -0,0 +1,32 @@
|
||||
(function (nt) {
|
||||
var sel = '#notif';
|
||||
|
||||
var hideTmeo1; // timeout to start hiding (transition)
|
||||
var hideTmeo2; // timeout to add the hidden class
|
||||
|
||||
nt.show = function (message, timeout) {
|
||||
$(sel).html(message);
|
||||
Modal.show(sel);
|
||||
|
||||
clearTimeout(hideTmeo1);
|
||||
clearTimeout(hideTmeo2);
|
||||
|
||||
if (undef(timeout)) timeout = 2500;
|
||||
|
||||
hideTmeo1 = setTimeout(nt.hide, timeout);
|
||||
};
|
||||
|
||||
nt.hide = function () {
|
||||
var $m = $(sel);
|
||||
$m.removeClass('visible');
|
||||
hideTmeo2 = setTimeout(function () {
|
||||
$m.addClass('hidden');
|
||||
}, 250); // transition time
|
||||
};
|
||||
|
||||
nt.init = function() {
|
||||
$(sel).on('click', function() {
|
||||
nt.hide(this);
|
||||
});
|
||||
};
|
||||
})(window.Notify = {});
|
||||
@@ -135,7 +135,7 @@
|
||||
H = obj.h;
|
||||
|
||||
/* Build screen & show */
|
||||
var e, cell, scr = qq('#screen');
|
||||
var e, cell, scr = qs('#screen');
|
||||
|
||||
// Empty the screen node
|
||||
while (scr.firstChild) scr.removeChild(scr.firstChild);
|
||||
@@ -281,7 +281,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
qa('#buttons button').forEach(function(s) {
|
||||
qsa('#buttons button').forEach(function(s) {
|
||||
s.addEventListener('click', function() {
|
||||
sendBtnMsg(+this.dataset['n']);
|
||||
});
|
||||
@@ -299,5 +299,5 @@
|
||||
Term.init(obj);
|
||||
Conn.init();
|
||||
Input.init();
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
function mk(e) {return document.createElement(e)}
|
||||
|
||||
/** Find one by query */
|
||||
function qq(s) {return document.querySelector(s)}
|
||||
function qs(s) {return document.querySelector(s)}
|
||||
|
||||
/** Find all by query */
|
||||
function qa(s) {return document.querySelectorAll(s)}
|
||||
function qsa(s) {return document.querySelectorAll(s)}
|
||||
|
||||
/** Convert any to bool safely */
|
||||
function bool(x) {
|
||||
return (x === 1 || x === '1' || x === true || x === 'true');
|
||||
}
|
||||
|
||||
function intval(x) {
|
||||
return parseInt(x);
|
||||
/**
|
||||
* Filter 'spacebar' and 'return' from keypress handler,
|
||||
* and when they're pressed, fire the callback.
|
||||
* use $(...).on('keypress', cr(handler))
|
||||
*/
|
||||
function cr(hdl) {
|
||||
return function(e) {
|
||||
if (e.which == 10 || e.which == 13 || e.which == 32) {
|
||||
hdl();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Extend an objects with options */
|
||||
@@ -36,23 +45,23 @@ function rgxe(str) {
|
||||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
||||
}
|
||||
|
||||
/** Format number to N decimal places, output as string */
|
||||
function numfmt(x, places) {
|
||||
var pow = Math.pow(10, places);
|
||||
return Math.round(x*pow) / pow;
|
||||
}
|
||||
|
||||
function estimateLoadTime(fs, n) {
|
||||
return (1000/fs)*n+1500;
|
||||
}
|
||||
|
||||
/** Get millisecond timestamp */
|
||||
function msNow() {
|
||||
return +(new Date);
|
||||
}
|
||||
|
||||
/** Get ms elapsed since msNow() */
|
||||
function msElapsed(start) {
|
||||
return msNow() - start;
|
||||
}
|
||||
|
||||
/** Shim for log base 10 */
|
||||
Math.log10 = Math.log10 || function(x) {
|
||||
return Math.log(x) / Math.LN10;
|
||||
};
|
||||
@@ -93,3 +102,22 @@ String.prototype.format = function () {
|
||||
return out;
|
||||
};
|
||||
|
||||
/** HTML escape */
|
||||
function e(str) {
|
||||
return $.htmlEscape(str);
|
||||
}
|
||||
|
||||
/** Check for undefined */
|
||||
function undef(x) {
|
||||
return typeof x == 'undefined';
|
||||
}
|
||||
|
||||
/** Safe json parse */
|
||||
function jsp() {
|
||||
try {
|
||||
return JSON.parse(e);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+152
-148
@@ -1,89 +1,45 @@
|
||||
/** Wifi page */
|
||||
(function () {
|
||||
(function(w) {
|
||||
var authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2'];
|
||||
var curSSID;
|
||||
|
||||
/** Update display for received response */
|
||||
function onScan(resp, status) {
|
||||
if (status != 200) {
|
||||
// bad response
|
||||
rescan(5000); // wait 5sm then retry
|
||||
return;
|
||||
// Get XX % for a slider input
|
||||
function rangePt(inp) {
|
||||
return Math.round(((inp.value / inp.max)*100)) + '%';
|
||||
}
|
||||
|
||||
// Display selected STA SSID etc
|
||||
function selectSta(name, password, ip) {
|
||||
$('#sta_ssid').val(name);
|
||||
$('#sta_password').val(password);
|
||||
|
||||
$('#sta-nw').toggleClass('hidden', name.length == 0);
|
||||
$('#sta-nw-nil').toggleClass('hidden', name.length > 0);
|
||||
|
||||
$('#sta-nw .essid').html(e(name));
|
||||
var nopw = undef(password) || password.length == 0;
|
||||
$('#sta-nw .x-passwd').html(e(password));
|
||||
$('#sta-nw .passwd').toggleClass('hidden', nopw);
|
||||
$('#sta-nw .nopasswd').toggleClass('hidden', !nopw);
|
||||
$('#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, '');
|
||||
}
|
||||
|
||||
resp = JSON.parse(resp);
|
||||
|
||||
var done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0);
|
||||
rescan(done ? 15000 : 1000);
|
||||
if (!done) return; // no redraw yet
|
||||
|
||||
// clear the AP list
|
||||
var $list = $('#ap-list');
|
||||
// remove old APs
|
||||
$('.AP').remove();
|
||||
|
||||
$list.toggle(done);
|
||||
$('#ap-loader').toggle(!done);
|
||||
|
||||
// scan done
|
||||
resp.result.APs.sort(function (a, b) {
|
||||
return b.rssi - a.rssi;
|
||||
}).forEach(function (ap) {
|
||||
ap.enc = intval(ap.enc);
|
||||
|
||||
if (ap.enc > 4) return; // hide unsupported auths
|
||||
|
||||
var item = document.createElement('div');
|
||||
|
||||
var $item = $(item)
|
||||
.data('ssid', ap.essid)
|
||||
.data('pwd', ap.enc != 0)
|
||||
.addClass('AP');
|
||||
|
||||
// mark current SSID
|
||||
if (ap.essid == curSSID) {
|
||||
$item.addClass('selected');
|
||||
}
|
||||
|
||||
var inner = document.createElement('div');
|
||||
$(inner).addClass('inner')
|
||||
.htmlAppend('<div class="rssi">{0}</div>'.format(ap.rssi_perc))
|
||||
.htmlAppend('<div class="essid" title="{0}">{0}</div>'.format($.htmlEscape(ap.essid)))
|
||||
.htmlAppend('<div class="auth">{0}</div>'.format(authStr[ap.enc]));
|
||||
|
||||
$item.on('click', function () {
|
||||
var $th = $(this);
|
||||
|
||||
// populate the form
|
||||
$('#conn-essid').val($th.data('ssid'));
|
||||
$('#conn-passwd').val(''); // clear
|
||||
|
||||
if ($th.data('pwd')) {
|
||||
// this AP needs a password
|
||||
Modal.show('#psk-modal');
|
||||
} else {
|
||||
Modal.show('#reset-modal');
|
||||
$('#conn-form').submit();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
item.appendChild(inner);
|
||||
$list[0].appendChild(item);
|
||||
});
|
||||
if (e) e.preventDefault();
|
||||
Modal.hide('#psk-modal');
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Ask the CGI what APs are visible (async) */
|
||||
function scanAPs() {
|
||||
$.get('http://'+_root+'/wifi/scan', onScan);
|
||||
}
|
||||
|
||||
function rescan(time) {
|
||||
setTimeout(scanAPs, time);
|
||||
}
|
||||
|
||||
/** Set up the WiFi page */
|
||||
window.wifiInit = function (obj) {
|
||||
/** Update display for received response */
|
||||
function onScan(resp, status) {
|
||||
//var ap_json = {
|
||||
// "result": {
|
||||
// "inProgress": "0",
|
||||
@@ -94,85 +50,133 @@
|
||||
// }
|
||||
//};
|
||||
|
||||
// Hide what should be hidden in this mode
|
||||
$('.x-hide-'+obj.mode).addClass('hidden');
|
||||
obj.mode = +obj.mode;
|
||||
|
||||
// Channel writable only in AP mode
|
||||
if (obj.mode != 2) $('#channel').attr('readonly', 1);
|
||||
|
||||
curSSID = obj.staSSID;
|
||||
|
||||
// add SSID to the opmode field
|
||||
if (curSSID) {
|
||||
var box = $('#opmodebox');
|
||||
box.html(box.html() + ' (' + curSSID + ')');
|
||||
if (status != 200) {
|
||||
// bad response
|
||||
rescan(5000); // wait 5sm then retry
|
||||
return;
|
||||
}
|
||||
|
||||
// hide IP if IP not received
|
||||
if (!obj.staIP) $('.x-hide-noip').addClass('hidden');
|
||||
|
||||
// scan if not AP
|
||||
if (obj.mode != 2) {
|
||||
scanAPs();
|
||||
try {
|
||||
resp = JSON.parse(resp);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
rescan(5000);
|
||||
return;
|
||||
}
|
||||
|
||||
$('#modeswitch').html([
|
||||
'<a class="button" href="/wifi/setmode?mode=3">Client+AP</a> <a class="button" href="/wifi/setmode?mode=2">AP only</a>',
|
||||
'<a class="button" href="/wifi/setmode?mode=3">Client+AP</a>',
|
||||
'<a class="button" href="/wifi/setmode?mode=1">Client only</a> <a class="button" href="/wifi/setmode?mode=2">AP only</a>'
|
||||
][obj.mode-1]);
|
||||
};
|
||||
var done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0);
|
||||
rescan(done ? 15000 : 1000);
|
||||
if (!done) return; // no redraw yet
|
||||
|
||||
window.wifiConn = function () {
|
||||
var xhr = new XMLHttpRequest();
|
||||
var abortTmeo;
|
||||
// clear the AP list
|
||||
var $list = $('#ap-list');
|
||||
// remove old APs
|
||||
$('#ap-list .AP').remove();
|
||||
|
||||
function getStatus() {
|
||||
xhr.open("GET", 'http://'+_root+"/wifi/connstatus");
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) {
|
||||
clearTimeout(abortTmeo);
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
var done = false;
|
||||
var msg = '...';
|
||||
$list.toggleClass('hidden', !done);
|
||||
$('#ap-loader').toggleClass('hidden', done);
|
||||
|
||||
if (data.status == "idle") {
|
||||
msg = "Preparing to connect";
|
||||
}
|
||||
else if (data.status == "success") {
|
||||
msg = "Connected! Received IP " + data.ip + ".";
|
||||
done = true;
|
||||
}
|
||||
else if (data.status == "working") {
|
||||
msg = "Connecting to selected AP";
|
||||
}
|
||||
else if (data.status == "fail") {
|
||||
msg = "Connection failed, check your password and try again.";
|
||||
done = true;
|
||||
}
|
||||
// scan done
|
||||
resp.result.APs.sort(function (a, b) {
|
||||
return b.rssi - a.rssi;
|
||||
}).forEach(function (ap) {
|
||||
ap.enc = parseInt(ap.enc);
|
||||
|
||||
$("#status").html(msg);
|
||||
if (ap.enc > 4) return; // hide unsupported auths
|
||||
WiFi.scan_url = '/cfg/wifi/scan';
|
||||
|
||||
if (done) {
|
||||
$('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
} else {
|
||||
window.setTimeout(getStatus, 1000);
|
||||
}
|
||||
var item = mk('div');
|
||||
|
||||
var $item = $(item)
|
||||
.data('ssid', ap.essid)
|
||||
.data('pwd', ap.enc)
|
||||
.attr('tabindex', 0)
|
||||
.addClass('AP');
|
||||
|
||||
// mark current SSID
|
||||
if (ap.essid == curSSID) {
|
||||
$item.addClass('selected');
|
||||
}
|
||||
|
||||
var inner = mk('div');
|
||||
$(inner).addClass('inner')
|
||||
.htmlAppend('<div class="rssi">{0}</div>'.format(ap.rssi_perc))
|
||||
.htmlAppend('<div class="essid" title="{0}">{0}</div>'.format($.htmlEscape(ap.essid)))
|
||||
.htmlAppend('<div class="auth">{0}</div>'.format(authStr[ap.enc]));
|
||||
|
||||
$item.on('click', function () {
|
||||
var $th = $(this);
|
||||
|
||||
var ssid = $th.data('ssid');
|
||||
|
||||
$('#conn-ssid').val(ssid);
|
||||
$('#conn-passwd').val('');
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
abortTmeo = setTimeout(function () {
|
||||
xhr.abort();
|
||||
$("#status").html("Telemetry lost, try reconnecting to the AP.");
|
||||
$('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
}, 4000);
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
item.appendChild(inner);
|
||||
$list[0].appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
getStatus();
|
||||
};
|
||||
})();
|
||||
function startScanning() {
|
||||
$('#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);
|
||||
}
|
||||
|
||||
function rescan(time) {
|
||||
setTimeout(scanAPs, time);
|
||||
}
|
||||
|
||||
/** 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');
|
||||
var disp1 = x.querySelector('.x-disp1');
|
||||
var disp2 = x.querySelector('.x-disp2');
|
||||
var t = rangePt(inp);
|
||||
$(disp1).html(t);
|
||||
$(disp2).html(t);
|
||||
$(inp).on('input', function() {
|
||||
t = rangePt(inp);
|
||||
$(disp1).html(t);
|
||||
$(disp2).html(t);
|
||||
});
|
||||
});
|
||||
|
||||
// Forget STA credentials
|
||||
$('#forget-sta').on('click', function() {
|
||||
selectSta('', '', '');
|
||||
return false;
|
||||
});
|
||||
|
||||
selectSta(cfg.sta_ssid, cfg.sta_password, cfg.sta_active_ip);
|
||||
curSSID = cfg.sta_active_ssid;
|
||||
}
|
||||
|
||||
w.init = wifiInit;
|
||||
w.startScanning = startScanning;
|
||||
})(window.WiFi = {});
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'appname' => 'ESPTerm',
|
||||
|
||||
'menu.cfg_wifi' => 'WiFi Settings',
|
||||
'menu.cfg_network' => 'Network Configuration',
|
||||
'menu.cfg_app' => '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.settings' => 'Settings',
|
||||
|
||||
'title.term' => 'Terminal',
|
||||
|
||||
'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.',
|
||||
|
||||
'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',
|
||||
|
||||
'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.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.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.',
|
||||
|
||||
'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.',
|
||||
|
||||
'net.ap_dhcp_time' => 'Lease time',
|
||||
'net.ap_dhcp_start' => 'Pool start IP',
|
||||
'net.ap_dhcp_end' => 'Pool end IP',
|
||||
'net.ap_addr_ip' => 'Own IP address',
|
||||
'net.ap_addr_mask' => 'Subnet mask',
|
||||
|
||||
'net.sta_dhcp_enable' => 'Use dynamic IP',
|
||||
'net.sta_addr_ip' => 'ESPTerm static IP',
|
||||
'net.sta_addr_mask' => 'Subnet mask',
|
||||
'net.sta_addr_gw' => 'Gateway IP',
|
||||
|
||||
'wifi.ap' => 'Built-in Access Point',
|
||||
'wifi.sta' => 'Connect to External Network',
|
||||
|
||||
'wifi.enable' => 'Enabled',
|
||||
'wifi.tpw' => 'Transmit power',
|
||||
'wifi.ap_channel' => 'Channel',
|
||||
'wifi.ap_ssid' => 'AP SSID',
|
||||
'wifi.ap_password' => 'Password',
|
||||
'wifi.ap_hidden' => 'Hide SSID',
|
||||
'wifi.sta_info' => 'Selected',
|
||||
|
||||
'wifi.not_conn' => 'Not connected.',
|
||||
'wifi.sta_none' => 'None',
|
||||
'wifi.sta_active_pw' => '🔒',
|
||||
'wifi.sta_active_nopw' => '🔓 Open access',
|
||||
'wifi.connected_ip_is' => 'Connected, IP is ',
|
||||
'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.select_ssid' => 'Available networks:',
|
||||
|
||||
'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.disabled' =>"Station mode is disabled.",
|
||||
'wifi.conn.idle' =>"Idle, not connected and with 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' =>
|
||||
'Restore to firmware default settings? This will reset ' .
|
||||
'all active settings and switch to AP mode with the default SSID.',
|
||||
'admin.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' => '
|
||||
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.
|
||||
',
|
||||
|
||||
'apply' => 'Apply!',
|
||||
'enabled' => 'Enabled',
|
||||
'disabled' => 'Disabled',
|
||||
'yes' => 'Yes',
|
||||
'no' => 'No',
|
||||
'confirm' => 'OK',
|
||||
'form_errors' => 'Validation errors for:',
|
||||
];
|
||||
+4
-2
@@ -5,6 +5,8 @@ echo "Packing js..."
|
||||
cat jssrc/chibi.js \
|
||||
jssrc/utils.js \
|
||||
jssrc/modal.js \
|
||||
jssrc/notif.js \
|
||||
jssrc/appcommon.js \
|
||||
jssrc/term.js \
|
||||
jssrc/wifi.js > js/app.js
|
||||
jssrc/lang.js \
|
||||
jssrc/wifi.js \
|
||||
jssrc/term.js > js/app.js
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<nav id="menu">
|
||||
<div id="brand" tabindex=0><?= tr('appname') ?></div>
|
||||
<a href="<?= e(url('term')) ?>" class="icn-back"><?= tr('menu.term') ?></a>
|
||||
<?php
|
||||
// generate the menu
|
||||
foreach ($_pages as $k => $page) {
|
||||
if (strpos($page->bodyclass, 'cfg') === false) continue;
|
||||
|
||||
$sel = ($_GET['page'] == $k) ? 'selected' : '';
|
||||
$text = $page->label;
|
||||
$url = e(url($k));
|
||||
echo "<a href=\"$url\" class=\"$page->icon $sel\">$text</a>";
|
||||
}
|
||||
?>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
function menuOpen() { $('#menu').toggleClass('expanded') }
|
||||
$('#brand').on('click', menuOpen).on('keypress', cr(menuOpen));
|
||||
</script>
|
||||
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title><?= $_GET['PAGE_TITLE'] ?></title>
|
||||
<link href="/css/app.css" rel="stylesheet">
|
||||
<script src="/js/app.js"></script>
|
||||
<script>var _root = <?= JS_WEB_ROOT ?>;</script>
|
||||
</head>
|
||||
<body class="<?= $_GET['BODYCLASS'] ?>">
|
||||
<div id="outer">
|
||||
<?php
|
||||
$cfg = false;
|
||||
if (strpos($_GET['BODYCLASS'], 'cfg') !== false) {
|
||||
$cfg = true;
|
||||
require __DIR__ . '/_cfg_menu.php';
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="content">
|
||||
<img src="/img/loader.gif" alt="Loading…" id="loader">
|
||||
<?php if ($cfg): ?>
|
||||
<h1><?= tr('menu.' . $_GET['page']) ?></h1>
|
||||
|
||||
<div class="Box errors hidden">
|
||||
<span class="lead"><?= tr('form_errors') ?></span> <span class="list"></span>
|
||||
</div>
|
||||
|
||||
<?php endif; ?>
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
<div class="NotifyMsg hidden" id="notif"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,69 @@
|
||||
<div class="Box">
|
||||
<img src="/img/cvut.svg" id="logo" class="mq-tablet-min">
|
||||
<h2>ESP8266 Remote Terminal</h2>
|
||||
|
||||
<img src="/img/cvut.svg" id="logo2" class="mq-phone">
|
||||
|
||||
<p>
|
||||
© Ondřej Hruška, 2016-2017
|
||||
<<a href="mailto:ondra@ondrovo.com">ondra@ondrovo.com</a>>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="http://measure.feld.cvut.cz/" target="blank">Katedra měření, FEL ČVUT</a><br>
|
||||
Department of Measurement, FEE CTU
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Version</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>ESPTerm</th>
|
||||
<td>v%vers_fw%, build %date% at %time%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>libesphttpd</th>
|
||||
<td>v%vers_httpd%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>ESP IoT SDK</th>
|
||||
<td>v%vers_sdk%</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Issues</h2>
|
||||
<p>
|
||||
Please report any issues to the <a href="%githubrepo%/issues">bugtracker</a> or send them by e-mail (see above).
|
||||
</p>
|
||||
<p>
|
||||
Firmware updates can be downloaded from the <a href="%githubrepo%/releases">releases page</a> and flashed
|
||||
with <a href="https://github.com/espressif/esptool">esptool.py</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Contributing</h2>
|
||||
<p>
|
||||
<i class="icn-github"></i> You're welcome to submit your improvements and ideas to our <a href="%githubrepo%">GitHub repository</a>!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<i class="icn-donate"></i> If you'd like to donate, please try <a href="https://paypal.me/mightypork">PayPal</a> or
|
||||
<a href="https://liberapay.com/MightyPork/">LiberaPay</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Thanks</h2>
|
||||
<p>
|
||||
The webserver is based on a <a href="https://github.com/MightyPork/libesphttpd">fork</a> of the
|
||||
<a href="https://github.com/Spritetm/esphttpd">esphttpd</a> library by Jeroen Domburg (Sprite_tm).
|
||||
</p>
|
||||
<p>
|
||||
Using (modified) JS library <a href="https://github.com/kylebarrow/chibi">chibi.js</a> by
|
||||
Kyle Barrow as a lightweight jQuery alternative.
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1,71 @@
|
||||
<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>
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
$ipmask='pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$"';
|
||||
?>
|
||||
|
||||
<form class="Box str mobcol" action="<?= e(url('network_set')) ?>" method="GET" id="form-2">
|
||||
<h2 tabindex=0><?= tr('net.sta') ?></h2>
|
||||
|
||||
<div class="Row explain">
|
||||
<?= tr('net.explain_sta') ?>
|
||||
</div>
|
||||
|
||||
<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%">
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-2').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="Box str mobcol" action="<?= e(url('network_set')) ?>" method="GET" id="form-1">
|
||||
<h2 tabindex=0><?= tr('net.ap') ?></h2>
|
||||
|
||||
<div class="Row explain">
|
||||
<?= tr('net.explain_ap') ?>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_dhcp_time"><?= tr('net.ap_dhcp_time') ?><span class="mq-phone"> (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"> (min)</span>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_dhcp_start"><?= tr('net.ap_dhcp_start') ?></label>
|
||||
<input type="text" name="ap_dhcp_start" id="ap_dhcp_start" value="%ap_dhcp_start%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_dhcp_end"><?= tr('net.ap_dhcp_end') ?></label>
|
||||
<input type="text" name="ap_dhcp_end" id="ap_dhcp_end" value="%ap_dhcp_end%" <?=$ipmask?> required>
|
||||
</div>
|
||||
|
||||
<div 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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="Box mobcol">
|
||||
<h2><?= tr('net.details') ?></h2>
|
||||
|
||||
<div class="Row">
|
||||
<label><?= tr('net.sta_mac') ?></label><input type="text" readonly value="%sta_mac%">
|
||||
</div>
|
||||
<div class="Row">
|
||||
<label><?= tr('net.ap_mac') ?></label><input type="text" readonly value="%ap_mac%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateStaticDisp() {
|
||||
$('.x-static').toggleClass('hidden', $('#sta_dhcp_enable').val());
|
||||
}
|
||||
$('.x-static-toggle').on('click', function() {
|
||||
updateStaticDisp();
|
||||
});
|
||||
updateStaticDisp();
|
||||
</script>
|
||||
@@ -0,0 +1,108 @@
|
||||
<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">
|
||||
<label><?= tr('wifi.enable') ?></label><!--
|
||||
--><span class="box" tabindex=0></span>
|
||||
<input type="hidden" name="ap_enable" value="%ap_enable%">
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_ssid"><?= tr('wifi.ap_ssid') ?></label>
|
||||
<input type="text" name="ap_ssid" id="ap_ssid" value="%ap_ssid%" required>
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<label for="ap_password"><?= tr('wifi.ap_password') ?></label>
|
||||
<input type="text" name="ap_password" id="ap_password" value="%ap_password%">
|
||||
</div>
|
||||
|
||||
<div class="Row">
|
||||
<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">
|
||||
<label for="tpw">
|
||||
<?= tr('wifi.tpw') ?>
|
||||
<span class="display x-disp1 mq-phone"></span>
|
||||
</label>
|
||||
<input type="range" name="tpw" id="tpw" step=1 min=0 max=82 value="%tpw%">
|
||||
<span class="display x-disp2 mq-no-phone"></span>
|
||||
</div>
|
||||
|
||||
<div class="Row checkbox">
|
||||
<label><?= tr('wifi.ap_hidden') ?></label><!--
|
||||
--><span class="box" tabindex=0></span>
|
||||
<input type="hidden" name="ap_hidden" value="%ap_hidden%">
|
||||
</div>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-1').submit()"><?= tr('apply') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="GET" id="form-2">
|
||||
<h2 tabindex=0><?= tr('wifi.sta') ?></h2>
|
||||
|
||||
<div class="Row checkbox">
|
||||
<label><?= tr('wifi.enable') ?></label><!--
|
||||
--><span class="box" tabindex=0></span>
|
||||
<input type="hidden" name="sta_enable" value="%sta_enable%">
|
||||
</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">
|
||||
<label><?= tr('wifi.sta_info') ?></label>
|
||||
<div class="AP-preview hidden" id="sta-nw">
|
||||
<div class="wrap">
|
||||
<div class="inner">
|
||||
<div class="essid"></div>
|
||||
<div class="passwd"><?= tr('wifi.sta_active_pw') ?> <span class="x-passwd"></span></div>
|
||||
<div class="nopasswd"><?= tr('wifi.sta_active_nopw') ?></div>
|
||||
<div class="ip"></div>
|
||||
</div>
|
||||
<a class="forget" href="#" id="forget-sta">×</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="AP-preview-nil" id="sta-nw-nil">
|
||||
<?= tr('wifi.sta_none') ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ap-box">
|
||||
<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>
|
||||
|
||||
<div class="Row buttons">
|
||||
<a class="button icn-ok" href="#" onclick="qs('#form-2').submit()"><?= tr('apply') ?></a>
|
||||
</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%',
|
||||
});
|
||||
</script>
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
<h1><?= tr('menu.cfg_wifi_conn') ?></h1>
|
||||
|
||||
<div class="Box">
|
||||
<p><b><?= tr('wifi.conn.status') ?></b> <span id="status"></span><span class="anim-dots">.</span></p>
|
||||
<a href="<?= e(url('cfg_wifi')) ?>" id="backbtn" class="button"><?= tr('wifi.conn.back_to_config') ?></a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var xhr = new XMLHttpRequest();
|
||||
var abortTmeo;
|
||||
|
||||
var messages = <?= json_encode([
|
||||
'disabled' => tr('wifi.conn.disabled'),
|
||||
'idle' => tr('wifi.conn.idle'),
|
||||
'success' => tr('wifi.conn.success'),
|
||||
'working' => tr('wifi.conn.working'),
|
||||
'fail' => tr('wifi.conn.fail'),
|
||||
]) ?>;
|
||||
|
||||
function getStatus() {
|
||||
xhr.open("GET", 'http://'+_root+'<?= url('wifi_connstatus', true) ?>');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) {
|
||||
clearTimeout(abortTmeo);
|
||||
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;
|
||||
|
||||
$("#status").html(msg);
|
||||
|
||||
if (done) {
|
||||
// $('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
} else {
|
||||
window.setTimeout(getStatus, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
abortTmeo = setTimeout(function () {
|
||||
xhr.abort();
|
||||
$("#status").html(<?= json_encode(tr('wifi.conn.telemetry_lost')) ?>);
|
||||
// $('#backbtn').removeClass('hidden');
|
||||
$('.anim-dots').addClass('hidden');
|
||||
}, 4000);
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
getStatus();
|
||||
</script>
|
||||
@@ -1,29 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Help - ESP8266 Remote Terminal</title>
|
||||
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<script src="/js/app.js"></script>
|
||||
</head>
|
||||
<body class="page-help">
|
||||
|
||||
<h1 onclick="location.href='/'">Quick Reference</h1>
|
||||
|
||||
<div class="Box">
|
||||
<h2>Wiring</h2>
|
||||
|
||||
<ul>
|
||||
<li>Communication UART on pins <b>Rx, Tx</b> at 115200-8-1-N</li>
|
||||
<li>Debug log on pin <b>GPIO2</b> at 115200-8-1-N</li>
|
||||
<li>Use 3.3V logic, or 5V with protection resistors (470R or more)</li>
|
||||
<li>If the "LVD" LED on the ESP Term board lights up, the module doesn't get enough power. Check your connections.</li>
|
||||
<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 V and 5 V logic. For 5 V, 470 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.
|
||||
NOTE: This won't work if your ESP8266 board has a built-in USB-serial (like NodeMCU).</li>
|
||||
<li>For best performance, use the module in the Client mode. In AP mode, check that the channel used is clear;
|
||||
interference may cause lag in the terminal.</li>
|
||||
<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.
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -328,12 +314,3 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<nav id="botnav">
|
||||
<a href="/">Terminal</a><!--
|
||||
--><a href="/wifi">WiFi config</a><!--
|
||||
--><a href="/about">About</a>
|
||||
</nav>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script>
|
||||
// Workaround for badly loaded page
|
||||
setTimeout(function() {
|
||||
if (typeof termInit == 'undefined' || typeof $ == 'undefined') {
|
||||
location.reload(true);
|
||||
}
|
||||
}, 2000);
|
||||
</script>
|
||||
|
||||
<h1>%term_title%</h1>
|
||||
|
||||
<div id="termwrap">
|
||||
<div id="screen"></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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav id="botnav">
|
||||
<a href="<?= url('cfg_wifi') ?>"><?= tr('menu.settings') ?></a><!--
|
||||
--><a href="<?= url('help') ?>">Help</a><!--
|
||||
--><a href="<?= url('about') ?>">About</a>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
try {
|
||||
termInit(%screenData%);
|
||||
} catch(e) {
|
||||
console.error("Fail, reloading...");
|
||||
location.reload(true);
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,178 +0,0 @@
|
||||
html {
|
||||
font-family: Arial, sans-serif;
|
||||
color: #D0D0D0;
|
||||
background: #131315;
|
||||
}
|
||||
|
||||
html, body {
|
||||
@include naked();
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a, a:visited, a:link {
|
||||
cursor: pointer;
|
||||
color: #5abfff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #5abfff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.Box {
|
||||
display: block;
|
||||
max-width: 900px;
|
||||
|
||||
margin-top: dist(0);
|
||||
padding: dist(-1) dist(0);
|
||||
|
||||
@include media($phone) {
|
||||
margin-top: dist(-1);
|
||||
padding: dist(-3) dist(-2);
|
||||
}
|
||||
//
|
||||
//h1 + & {
|
||||
// margin-top: 0;
|
||||
//}
|
||||
//
|
||||
//h2 {
|
||||
// margin-top: 0;
|
||||
//}
|
||||
|
||||
p:first-child {
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
border-radius: 3px;
|
||||
background-color: rgba(white, .07);
|
||||
|
||||
//&.wide {
|
||||
// width: initial;
|
||||
// max-width: initial;
|
||||
//}
|
||||
//
|
||||
//&.medium {
|
||||
// max-width: 1200px;
|
||||
//}
|
||||
//
|
||||
//.Valfield {
|
||||
// display: inline-block;
|
||||
// min-width: 10em;
|
||||
//}
|
||||
}
|
||||
|
||||
body {
|
||||
position: relative;
|
||||
|
||||
padding: dist(0);
|
||||
@include media($phone) {
|
||||
padding: dist(-1);
|
||||
}
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
& > * {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
h1,h2 {
|
||||
@include noselect();
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: fsize(6);
|
||||
margin-top: 0;
|
||||
margin-bottom: dist(0);
|
||||
|
||||
@include media($phone) {
|
||||
font-size: fsize(3);
|
||||
margin-bottom: dist(-1);
|
||||
}
|
||||
|
||||
@include media($tablet) {
|
||||
font-size: fsize(5);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: fsize(2);
|
||||
margin-bottom: dist(-1);
|
||||
//&:first-child{margin-top:0}
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: dist(-2);
|
||||
white-space: nowrap;
|
||||
|
||||
@include media($phone) {
|
||||
padding: dist(-3);
|
||||
}
|
||||
}
|
||||
|
||||
tbody th {
|
||||
text-align: right;
|
||||
width: $form-label-w;
|
||||
color: $c-form-label-fg;
|
||||
|
||||
@include media($phone) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
tbody td {
|
||||
input[type="text"], input[type="number"] {
|
||||
width: 10em;
|
||||
|
||||
@include media($phone) {
|
||||
width: 8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loader wheel in top right corner
|
||||
#loader {
|
||||
position: absolute;
|
||||
right: dist(1);
|
||||
top: dist(1);
|
||||
|
||||
transition: opacity .2s;
|
||||
opacity: 0;
|
||||
|
||||
@include media($phone) {
|
||||
top: dist(0);
|
||||
right: dist(0);
|
||||
}
|
||||
|
||||
&.show {
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
|
||||
ul > * {
|
||||
padding-top: .1em;
|
||||
padding-bottom: .1em;
|
||||
}
|
||||
|
||||
#botnav {
|
||||
padding-top: 1.5em;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
padding: 0 dist(-2);
|
||||
text-decoration: underline;
|
||||
|
||||
&, &:visited, &:link {
|
||||
color: #2e4d6e;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #5abfff;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-18
@@ -1,4 +1,5 @@
|
||||
@import "normalize";
|
||||
@import "fontello-embedded";
|
||||
@import "lib/bourbon/bourbon";
|
||||
|
||||
@import "grid-settings";
|
||||
@@ -6,15 +7,16 @@
|
||||
|
||||
@import "utils";
|
||||
|
||||
$form-label-w: 130px;
|
||||
$form-label-w: 160px;
|
||||
$form-label-gap: 8px;
|
||||
$form-field-w: 250px;
|
||||
|
||||
$c-red-outline: #ff0099;
|
||||
$c-form-label-fg: white;
|
||||
$c-form-field-bg: #303030;
|
||||
$c-form-field-bg: #3c3c3c;
|
||||
$c-form-field-fg: white;
|
||||
$c-form-highlight: #214e7a;
|
||||
$c-form-highlight-a: #2972ba;
|
||||
$c-form-highlight: #2972ba;
|
||||
$c-form-highlight-a: #2ea1f9;
|
||||
|
||||
@function dist($x) {
|
||||
@return modular-scale($x, 1rem, $golden);
|
||||
@@ -24,16 +26,7 @@ $c-form-highlight-a: #2972ba;
|
||||
@return modular-scale($x, 1em, $major-second);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[onclick] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@import "modal";
|
||||
@import "layout";
|
||||
@import "layout/index";
|
||||
@import "form/index";
|
||||
|
||||
// import all our pages
|
||||
@@ -45,17 +38,17 @@ $c-form-highlight-a: #2972ba;
|
||||
// media queries
|
||||
|
||||
@include media($tablet-min) {
|
||||
.mq-phone { display: none; }
|
||||
.mq-phone { display: none!important; }
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
.mq-tablet-min { display: none; }
|
||||
.mq-tablet-min, .mq-no-phone { display: none !important; }
|
||||
}
|
||||
|
||||
@include media($normal-min) {
|
||||
.mq-tablet-max { display: none; }
|
||||
.mq-tablet-max { display: none !important; }
|
||||
}
|
||||
|
||||
@include media($tablet-max) {
|
||||
.mq-normal-min { display: none; }
|
||||
.mq-normal-min { display: none !important; }
|
||||
}
|
||||
|
||||
@@ -31,9 +31,18 @@ button, input[type=submit], .button {
|
||||
min-width: initial;
|
||||
}
|
||||
|
||||
text-shadow: 1.5px 1.5px 2px rgba(black, 0.6);
|
||||
&::before {
|
||||
vertical-align: -1px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
text-shadow: 1.5px 1.5px 2px rgba(black, 0.4);
|
||||
|
||||
@include fancy-btn-colors($btn-blue-f, $btn-blue-b, $btn-blue-fa, $btn-blue-ba);
|
||||
|
||||
&:focus {
|
||||
outline-color: $c-red-outline;
|
||||
}
|
||||
}
|
||||
|
||||
//input[type="submit"], .btn-green {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
border-radius: 2px;
|
||||
padding: 0 0.6em;
|
||||
border: 0 none;
|
||||
outline: 0 none !important;
|
||||
//outline: 0 none !important;
|
||||
line-height: 1.8em;
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 3px;
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
color: $c-form-field-fg;
|
||||
padding: 6px;
|
||||
line-height: 1em;
|
||||
outline: 0 none !important;
|
||||
-moz-outline: 0 none !important;
|
||||
//outline: 0 none !important;
|
||||
//-moz-outline: 0 none !important;
|
||||
font-weight: normal;
|
||||
|
||||
&:focus, &:hover {
|
||||
@@ -16,6 +16,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
.Row.checkbox {
|
||||
$h: 27px;
|
||||
line-height: $h;
|
||||
|
||||
.box {
|
||||
overflow: hidden;
|
||||
width: $h;
|
||||
height: $h;
|
||||
border: 1px solid #808080;
|
||||
border-radius: 3px;
|
||||
background: $c-form-field-bg;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: $c-form-highlight-a;
|
||||
|
||||
&::before {
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
content: '×';
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
line-height: $h - 1px;
|
||||
text-align: center;
|
||||
font-size: $h;
|
||||
vertical-align: middle;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.checked::before {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Row.range {
|
||||
.display {
|
||||
margin-left: 1ex;
|
||||
}
|
||||
|
||||
label .display { font-weight: normal; }
|
||||
}
|
||||
|
||||
//#{$all-text-inputs} {
|
||||
// @include can-select();
|
||||
//}
|
||||
|
||||
@@ -5,13 +5,29 @@ form { @include naked(); }
|
||||
width: $form-field-w;
|
||||
}
|
||||
|
||||
form .Row {
|
||||
input[type="number"], input.short {
|
||||
width: $form-field-w/2;
|
||||
}
|
||||
|
||||
.Box.errors {
|
||||
.list {
|
||||
color: crimson;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.lead {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.Row {
|
||||
vertical-align: middle;
|
||||
margin: 14px auto;
|
||||
margin: 12px auto;
|
||||
text-align: left;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
@@ -30,6 +46,7 @@ form .Row {
|
||||
}
|
||||
|
||||
&.buttons {
|
||||
margin: 16px auto;
|
||||
input, .button {
|
||||
margin-right: dist(-1);
|
||||
}
|
||||
@@ -80,34 +97,44 @@ form .Row {
|
||||
align-self: flex-start;
|
||||
|
||||
@include noselect;
|
||||
@include nowrap;
|
||||
}
|
||||
|
||||
.checkbox-wrap {
|
||||
display: inline-block;
|
||||
width: $form-label-w;
|
||||
padding: $form-label-gap;
|
||||
text-align: right;
|
||||
align-self: flex-start;
|
||||
label.error {
|
||||
color: crimson;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
margin: auto;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
//.checkbox-wrap {
|
||||
// display: inline-block;
|
||||
// width: $form-label-w;
|
||||
// padding: $form-label-gap;
|
||||
// text-align: right;
|
||||
// align-self: flex-start;
|
||||
//
|
||||
// input[type=checkbox] {
|
||||
// margin: auto;
|
||||
// width: auto;
|
||||
// height: auto;
|
||||
// }
|
||||
//
|
||||
// & + label {
|
||||
// width: $form-field-w;
|
||||
// padding-left: 0;
|
||||
// text-align: left;
|
||||
// cursor: pointer;
|
||||
// }
|
||||
//}
|
||||
|
||||
& + label {
|
||||
width: $form-field-w;
|
||||
padding-left: 0;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="range"] {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
// special phone style
|
||||
@include media($phone) {
|
||||
flex-direction: column;
|
||||
margin: 6px auto;
|
||||
|
||||
&.buttons, &.centered {
|
||||
&.buttons, &.centered, &.checkbox {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@@ -139,7 +166,7 @@ form .Row {
|
||||
}
|
||||
}
|
||||
|
||||
#{$all-text-inputs}, textarea {
|
||||
#{$all-text-inputs}, input[type="range"], textarea, select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
//@import 'form_layout';
|
||||
@import 'form_layout';
|
||||
//@import 'select';
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
html {
|
||||
font-family: Arial, sans-serif;
|
||||
color: #D0D0D0;
|
||||
background: #131315;
|
||||
}
|
||||
|
||||
html, body {
|
||||
@include naked();
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a, a:visited, a:link {
|
||||
cursor: pointer;
|
||||
color: #5abfff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #5abfff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
[onclick] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul > * {
|
||||
padding-top: .2em;
|
||||
padding-bottom: .2em;
|
||||
}
|
||||
Executable
+137
@@ -0,0 +1,137 @@
|
||||
.Box {
|
||||
display: block;
|
||||
max-width: 900px;
|
||||
|
||||
margin-top: dist(0);
|
||||
padding: dist(-1) dist(0);
|
||||
|
||||
// clear floats
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
clear: both
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
margin-top: dist(-1);
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 + & {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
border-radius: 3px;
|
||||
background-color: rgba(white, .07);
|
||||
box-shadow: 0 0 4px black;
|
||||
border: 1px solid #4f4f4f;
|
||||
|
||||
&.wide {
|
||||
width: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
&.medium {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
//.Valfield {
|
||||
// display: inline-block;
|
||||
// min-width: 10em;
|
||||
//}
|
||||
|
||||
// Submit Top Right
|
||||
&.str {
|
||||
position: relative;
|
||||
.Row.buttons {
|
||||
position: absolute;
|
||||
|
||||
@include media($phone) {
|
||||
right: dist(0);
|
||||
top: 1.8em;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
@include media($tablet-min) {
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin-top: dist(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.str.mobopen .Row.buttons {
|
||||
top: 0;
|
||||
margin-top: dist(-1);
|
||||
}
|
||||
|
||||
.Row.explain {
|
||||
max-width: 600px; margin-left: 0;
|
||||
@include media($phone) {
|
||||
margin-top: 60px;
|
||||
}
|
||||
}
|
||||
&.mobopen .Row.explain {
|
||||
margin-top: 12px; // default from .Row
|
||||
|
||||
@include media($phone) {
|
||||
margin-top: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
.Box.mobcol {
|
||||
h2 {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding-right: 1.3rem;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
content: '▸';
|
||||
|
||||
top:50%;
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
transform: translate(0,-50%) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded h2::after {
|
||||
transform: translate(-25%,-50%) rotate(-90deg);
|
||||
margin-bottom: dist(0);
|
||||
}
|
||||
|
||||
.Row {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#ap-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.Row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#ap-box {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#content {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
|
||||
padding: dist(0);
|
||||
@include media($phone) {
|
||||
padding: dist(-1);
|
||||
}
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
& > * {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: fsize(7);
|
||||
margin-top: 0;
|
||||
margin-bottom: dist(0);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: fsize(3);
|
||||
margin-bottom: dist(-1);
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
h1 {
|
||||
font-size: fsize(5);
|
||||
margin-bottom: dist(-1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: fsize(2);
|
||||
margin-bottom: dist(-1);
|
||||
}
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: dist(-2);
|
||||
}
|
||||
|
||||
tbody th {
|
||||
text-align: right;
|
||||
width: $form-label-w;
|
||||
color: $c-form-label-fg;
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
@import "base";
|
||||
|
||||
@import "outer-wrap";
|
||||
@import "menu";
|
||||
@import "content";
|
||||
@import "loader";
|
||||
|
||||
@import "box";
|
||||
@import "modal";
|
||||
@@ -0,0 +1,18 @@
|
||||
// Loader wheel in top right corner
|
||||
#loader {
|
||||
position: absolute;
|
||||
right: dist(1);
|
||||
top: dist(1);
|
||||
|
||||
transition: opacity .2s;
|
||||
opacity: 0;
|
||||
|
||||
@include media($phone) {
|
||||
top: dist(0);
|
||||
right: dist(0);
|
||||
}
|
||||
|
||||
&.show {
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#menu {
|
||||
$menu-bg: #3983CD;
|
||||
$menu-hl: #5badff; //#1bd886;
|
||||
$menu-fg: white;
|
||||
|
||||
flex: 0 0 15rem;
|
||||
background: $menu-bg;
|
||||
|
||||
& > * {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: dist(-1) dist(0);
|
||||
|
||||
@include nowrap;
|
||||
@include noselect;
|
||||
}
|
||||
|
||||
#brand {
|
||||
color: $menu-fg;
|
||||
background: darken($menu-bg, 10%);
|
||||
font-size: 120%;
|
||||
text-align: center;
|
||||
position:relative;
|
||||
transition: none;
|
||||
font-weight: bold;
|
||||
|
||||
margin-bottom: dist(0);
|
||||
|
||||
@include media($phone) {
|
||||
background: $menu-bg;
|
||||
cursor: pointer;
|
||||
margin-bottom: dist(-2);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
color: rgba(black, .4);
|
||||
right: dist(0);
|
||||
content: '▸';
|
||||
|
||||
top:50%;
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
transform: translate(0,-50%) rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.expanded #brand {
|
||||
background: darken($menu-bg, 10%);
|
||||
|
||||
@include media($phone) {
|
||||
&:after { transform: translate(-25%,-50%) rotate(-90deg) }
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 130%;
|
||||
color: $menu-fg;
|
||||
|
||||
transition: background-color 0.2s;
|
||||
text-shadow: 0 0 5px rgba(black, .4);
|
||||
|
||||
&:hover, &.selected {
|
||||
background: $menu-hl;
|
||||
text-shadow: 0 0 5px rgba(black, .6);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
position: relative;
|
||||
box-shadow: 0 0 5px rgba(black, .5);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-color: $c-red-outline;
|
||||
}
|
||||
|
||||
//&::before {
|
||||
// content: "▸";
|
||||
// padding-right: .5rem;
|
||||
// position: relative;
|
||||
// top: -0.1rem;
|
||||
//}
|
||||
|
||||
// Fontello
|
||||
&::before {
|
||||
vertical-align: -2px;
|
||||
margin-left: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
display: none;
|
||||
&::before {margin-left: 10px;}
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded a { display:block }
|
||||
|
||||
@include media($tablet) {
|
||||
#brand {
|
||||
font-size: 95%;
|
||||
margin-bottom: dist(-1);
|
||||
}
|
||||
|
||||
a { font-size: 105%; }
|
||||
|
||||
flex-basis: 10rem;
|
||||
|
||||
& > * { padding: dist(-2) dist(-1); }
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,11 @@
|
||||
//min-height: 15rem;
|
||||
|
||||
background: #1c1c1e;
|
||||
border-left: 6px solid $c-form-highlight-a;
|
||||
border-right: 6px solid $c-form-highlight-a;
|
||||
box-shadow: 0 0 2px 0 #434349, 0 0 6px 0 black;
|
||||
border-left: 6px solid $c-form-highlight;
|
||||
border-right: 6px solid $c-form-highlight;
|
||||
border-top: 1px solid $c-form-highlight;
|
||||
border-bottom: 1px solid $c-form-highlight;
|
||||
box-shadow: 0 0 6px 0 black;
|
||||
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -40,21 +42,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// "toast"
|
||||
.NotifyMsg {
|
||||
position: fixed;
|
||||
bottom: dist(2);
|
||||
top: dist(1);
|
||||
right: dist(2);
|
||||
padding: dist(-1) dist(0);
|
||||
|
||||
// center horizontally
|
||||
left: 50%;
|
||||
@include translate(-50%,0);
|
||||
//left: 50%;
|
||||
//@include translate(-50%,0);
|
||||
// hack to remove blur in chrome
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
-webkit-transform: translateZ(0) scale(1.0, 1.0);
|
||||
|
||||
background: #37a349;
|
||||
background: #3887d0;
|
||||
&.error {
|
||||
background: #d03e42;
|
||||
}
|
||||
@@ -75,4 +77,3 @@
|
||||
&.visible { opacity: 1 }
|
||||
&.hidden { display: none }
|
||||
}
|
||||
*/
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
/* Main outer container */
|
||||
#outer {
|
||||
display: flex;
|
||||
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@include media($phone) {
|
||||
#outer {
|
||||
display: block;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
// mobile friendly
|
||||
#logo2 {
|
||||
max-width: 100%;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
td {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.page-term {
|
||||
body.term {
|
||||
h1 {
|
||||
font-size: fsize(5);
|
||||
@include media($phone) {
|
||||
@@ -34,14 +34,33 @@
|
||||
|
||||
button {
|
||||
margin: 0 3px;
|
||||
padding: 10px 0;
|
||||
width: 18%;
|
||||
max-width: 65px;
|
||||
min-width: initial;
|
||||
padding: 8px 5px;
|
||||
//width: 18%;
|
||||
min-width: 65px;
|
||||
//max-width: 65px;
|
||||
//min-width: initial;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
#botnav {
|
||||
padding-top: 1.5em;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
padding: 0 dist(-2);
|
||||
text-decoration: underline;
|
||||
|
||||
&, &:visited, &:link {
|
||||
color: #2e4d6e;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #5abfff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#termwrap {
|
||||
|
||||
+113
-36
@@ -13,11 +13,12 @@
|
||||
margin: 0 (- dist(-3));
|
||||
}
|
||||
|
||||
#ap-loader, #ap-noscan {
|
||||
#ap-loader, #ap-noscan, #ap-scan {
|
||||
background: rgba(white, .1);
|
||||
border-radius: 5px;
|
||||
padding: dist(-2);
|
||||
margin-bottom: dist(-2);
|
||||
margin-top: dist(-2);
|
||||
}
|
||||
|
||||
#ap-noscan {
|
||||
@@ -25,7 +26,14 @@
|
||||
}
|
||||
|
||||
#ap-box {
|
||||
padding-bottom: dist(-2);
|
||||
padding-top: dist(-2);
|
||||
|
||||
label {
|
||||
display: block;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-bottom: dist(-3);
|
||||
}
|
||||
}
|
||||
|
||||
#psk-modal form {
|
||||
@@ -43,6 +51,50 @@
|
||||
@extend %form-row-spacing;
|
||||
}
|
||||
|
||||
%ap-inner {
|
||||
cursor: pointer;
|
||||
@include noselect;
|
||||
|
||||
position: relative;
|
||||
&:active {
|
||||
left: 0;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
|
||||
background: #afafaf;
|
||||
transition: background-color 0.5s;
|
||||
&:hover { background: white }
|
||||
|
||||
display: flex;
|
||||
|
||||
.rssi {
|
||||
min-width: 2.5rem;
|
||||
flex: 0 0 15%;
|
||||
text-align: right;
|
||||
|
||||
&:after {
|
||||
padding-left: dist(-5);
|
||||
content: '%';
|
||||
font-size: fsize(-1);
|
||||
}
|
||||
}
|
||||
|
||||
.essid {
|
||||
flex: 1 1 70%;
|
||||
min-width: 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.auth {
|
||||
flex: 0 0 15%;
|
||||
}
|
||||
}
|
||||
|
||||
.AP {
|
||||
// can't use margins inside a column
|
||||
|
||||
@@ -56,53 +108,78 @@
|
||||
top: 0 !important; // no click effect
|
||||
}
|
||||
|
||||
// the actual silver box
|
||||
.inner {
|
||||
cursor: pointer;
|
||||
@include noselect;
|
||||
|
||||
position: relative;
|
||||
&:active {
|
||||
left: 0;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
|
||||
background: #afafaf;
|
||||
transition: background-color 0.5s;
|
||||
&:hover { background: white }
|
||||
|
||||
display: flex;
|
||||
@extend %ap-inner;
|
||||
|
||||
& > * {
|
||||
padding: dist(-1);
|
||||
@include nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rssi {
|
||||
min-width: 2.5rem;
|
||||
flex: 0 0 15%;
|
||||
text-align: right;
|
||||
.AP-preview-nil {
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px dashed #ddd;
|
||||
width: 250px;
|
||||
height: 94px;
|
||||
}
|
||||
|
||||
&:after {
|
||||
padding-left: dist(-5);
|
||||
content: '%';
|
||||
font-size: fsize(-1);
|
||||
.AP-preview {
|
||||
.wrap {
|
||||
@extend %ap-inner;
|
||||
|
||||
flex-direction: row;
|
||||
background: #ddd !important; // override the hover effect #43de81
|
||||
cursor: default;
|
||||
top: 0 !important; // no click effect
|
||||
overflow: hidden;
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > * {
|
||||
padding: dist(-1);
|
||||
@include nowrap;
|
||||
}
|
||||
}
|
||||
.forget {
|
||||
align-self: stretch;
|
||||
line-height: 100%;
|
||||
padding: dist(-1);
|
||||
border-left: 1px solid #bbb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&, &:hover {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
font-size: 28px;
|
||||
|
||||
&:hover {
|
||||
background: #dc4a6a;
|
||||
color: white;
|
||||
border-left: 1px solid #666;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
position: relative;
|
||||
padding-top: calc(#{dist(-1)} + 1px);
|
||||
}
|
||||
}
|
||||
|
||||
.essid {
|
||||
flex: 1 1 70%;
|
||||
min-width: 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-weight: bold;
|
||||
.essid, .passwd, .nopasswd {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.auth {
|
||||
flex: 0 0 15%;
|
||||
.x-passwd {
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
|
||||
// Utilities for background tiling
|
||||
|
||||
// Use a tile as background (w, h - size of time)
|
||||
@mixin tile_xy($w, $h, $x, $y) {
|
||||
background-position: (-$x*$w) (-$y*$h);
|
||||
}
|
||||
|
||||
|
||||
// Use a square tile as background (size - w & h of time)
|
||||
@mixin tile($size, $x, $y) {
|
||||
@include tile_xy($size, $size, $x, $y);
|
||||
}
|
||||
|
||||
|
||||
// Button with sprite-sheet
|
||||
// A B
|
||||
// B:hover B:hover
|
||||
@mixin tile_btn_h($w, $h, $x) {
|
||||
@include tile_xy($w, $h, $x, 0);
|
||||
&:hover {
|
||||
@include tile_xy($w, $h, $x, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// active the same as hover
|
||||
@mixin tile_btn_h_act($w, $h, $x) {
|
||||
@include tile_xy($w, $h, $x, 0);
|
||||
&:hover, &.active {
|
||||
@include tile_xy($w, $h, $x, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Button with sprite-sheet
|
||||
// A A:hover
|
||||
// B B:hover
|
||||
@mixin tile_btn_v($w, $h, $y) {
|
||||
@include tile_xy($w, $h, 0, $y);
|
||||
&:hover {
|
||||
@include tile_xy($w, $h, 1, $y);
|
||||
}
|
||||
}
|
||||
|
||||
// active the same as hover
|
||||
@mixin tile_btn_v_act($w, $h, $y) {
|
||||
@include tile_xy($w, $h, 0, $y);
|
||||
&:hover, &.active {
|
||||
@include tile_xy($w, $h, 1, $y);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin inset-shadow-top($w, $c) {
|
||||
box-shadow: inset 0 $w ($w*2) (-$w) $c;
|
||||
}
|
||||
|
||||
@mixin inset-shadow-bottom($w, $c) {
|
||||
box-shadow: inset 0 (-$w) ($w*2) (-$w) $c;
|
||||
}
|
||||
|
||||
@mixin inset-shadow-left($w, $c) {
|
||||
box-shadow: inset $w 0 ($w*2) (-$w) $c;
|
||||
}
|
||||
|
||||
@mixin inset-shadow-right($w, $c) {
|
||||
box-shadow: inset (-$w) 0 ($w*2) (-$w) $c;
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
@import "background-tiling";
|
||||
@import "pointer";
|
||||
@import "misc";
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
// Add a highlight for debugging
|
||||
@mixin highlight($color) {
|
||||
outline: 1px solid $color;
|
||||
background: rgba($color, .05);
|
||||
box-shadow: 0 0 2px 2px rgba($color, .2), inset 0 0 2px 2px rgba($color, .2);
|
||||
}
|
||||
|
||||
// Ellipsis, but for block elements
|
||||
@mixin block-ellipsis($width: 100%) {
|
||||
display: block;
|
||||
max-width: $width;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
// No margins, padding, borders
|
||||
@mixin naked() {
|
||||
border: 0 none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@mixin translate($x, $y) {
|
||||
@include transform(translate($x, $y));
|
||||
}
|
||||
|
||||
// Disallow wrapping
|
||||
@mixin nowrap() {
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
|
||||
@mixin click-through() {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
// Disallow text selection
|
||||
@mixin noselect() {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
// Allow text selection
|
||||
@mixin can-select() {
|
||||
-webkit-user-select: text;
|
||||
-khtml-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
|
||||
cursor: text;
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
xterm -e "php -S localhost:2000"
|
||||
xterm -e "php -S 0.0.0.0:2000"
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ESP8266 Remote Terminal</title>
|
||||
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||
|
||||
<script>
|
||||
// Workaround for badly loaded page
|
||||
setTimeout(function() {
|
||||
if (typeof termInit == 'undefined' || typeof $ == 'undefined') {
|
||||
location.reload(true);
|
||||
}
|
||||
}, 2000);
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<script src="/js/app.js"></script>
|
||||
</head>
|
||||
<body class="page-term">
|
||||
|
||||
<h1 onclick="location.href='/wifi'">ESP8266 Remote Terminal</h1>
|
||||
|
||||
<div id="termwrap">
|
||||
<div id="screen"></div>
|
||||
|
||||
<div id="buttons">
|
||||
<button data-n="1" class="btn-blue">1</button><!--
|
||||
--><button data-n="2" class="btn-blue">2</button><!--
|
||||
--><button data-n="3" class="btn-blue">3</button><!--
|
||||
--><button data-n="4" class="btn-blue">4</button><!--
|
||||
--><button data-n="5" class="btn-blue">5</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav id="botnav">
|
||||
<a href="/wifi">WiFi config</a><!--
|
||||
--><a href="/help">Help</a><!--
|
||||
--><a href="/about">About</a>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
try {
|
||||
_root = window.location.host;
|
||||
termInit(%screenData%);
|
||||
} catch(e) {
|
||||
console.error("Fail, reloading...");
|
||||
location.reload(true);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
require '_test_env.php';
|
||||
|
||||
$f = file_get_contents('term.html');
|
||||
|
||||
$f = str_replace('%screenData%',
|
||||
'{
|
||||
"w": 26, "h": 10,
|
||||
"x": 0, "y": 0,
|
||||
"cv": 1,
|
||||
"screen": "70 t259"
|
||||
}', $f);
|
||||
|
||||
$f = str_replace('window.location.host', json_encode(ESP_IP), $f);
|
||||
|
||||
echo $f;
|
||||
@@ -1,89 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||
|
||||
<title>WiFi Settings - ESP8266 Remote Terminal</title>
|
||||
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<script src="/js/app.js"></script>
|
||||
</head>
|
||||
<body class="page-wifi">
|
||||
|
||||
<img src="/img/loader.gif" alt="Loading…" id="loader">
|
||||
|
||||
<h1 onclick="location.href='/'">WiFi settings</h1>
|
||||
|
||||
<div class="Box" id="wificonfbox">
|
||||
<table>
|
||||
<tr>
|
||||
<th>WiFi mode</th>
|
||||
<td id="opmodebox">%WiFiMode%</td>
|
||||
</tr>
|
||||
<tr class="x-hide-noip x-hide-2">
|
||||
<th>IP</th>
|
||||
<td>%StaIP%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Switch to</th>
|
||||
<td id="modeswitch"></td>
|
||||
</tr>
|
||||
<tr class="x-hide-1">
|
||||
<th><label for="channel">AP channel</label></th>
|
||||
<td>
|
||||
<form action="/wifi/setchannel" method="GET">
|
||||
<input name="ch" id="channel" type="number" step=1 min=1 max=14 value="%WiFiChannel%"><!--
|
||||
--><input type="submit" value="Set" class="narrow btn-green x-hide-3">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="x-hide-1">
|
||||
<th><label for="channel">AP name</label></th>
|
||||
<td>
|
||||
<form action="/wifi/setname" method="GET">
|
||||
<input name="name" type="text" value="%APName%"><!--
|
||||
--><input type="submit" value="Set" class="narrow btn-green">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colspan=2 style="white-space: normal;">
|
||||
<p>Some changes require a reboot, dropping connection. It can take a while to re-connect.</p>
|
||||
<p>
|
||||
<b>If you lose access</b>, hold the BOOT button for 2 seconds (the Tx LED starts blinking) to re-enable AP mode.
|
||||
If that fails, hold the BOOT button for over 5 seconds (rapid Tx LED flashing) to perform a factory reset.
|
||||
<p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="Box" id="ap-box">
|
||||
<h2>Select AP to join</h2>
|
||||
<div id="ap-loader" class="x-hide-2">Scanning<span class="anim-dots">.</span></div>
|
||||
<div id="ap-noscan" class="x-hide-1 x-hide-3">Can't scan in AP-only mode.</div>
|
||||
<div id="ap-list" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<nav id="botnav">
|
||||
<a href="/">Terminal</a><!--
|
||||
--><a href="/help">Help</a><!--
|
||||
--><a href="/about">About</a>
|
||||
</nav>
|
||||
|
||||
<div class="Modal hidden" id="psk-modal">
|
||||
<div class="Dialog">
|
||||
<form action="/wifi/connect" method="post" id="conn-form">
|
||||
<input type="hidden" id="conn-essid" name="essid"><!--
|
||||
--><label for="conn-passwd">Password:</label><!--
|
||||
--><input type="password" id="conn-passwd" name="passwd"><!--
|
||||
--><input type="submit" value="Connect!">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
_root = window.location.host;
|
||||
wifiInit({staSSID: '%StaSSID%', staIP: '%StaIP%', mode: '%WiFiModeNum%'});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||
|
||||
<title>Connecting…</title>
|
||||
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<script src="js/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Connecting to network</h1>
|
||||
|
||||
<div class="Box">
|
||||
<p><b>Status:</b><br><span id="status"></span><span class="anim-dots">.</span></p>
|
||||
<a href="/wifi" id="backbtn" class="hidden button">Back to WiFi config</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
_root = window.location.host;
|
||||
wifiConn();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
require '_test_env.php';
|
||||
|
||||
$f = file_get_contents('wifi.html');
|
||||
|
||||
$f = str_replace('%StaSSID%', 'Chlivek', $f);
|
||||
$f = str_replace('%StaIP%', json_encode(ESP_IP), $f);
|
||||
$f = str_replace('%WiFiModeNum%', '1', $f);
|
||||
$f = str_replace('%WiFiMode%', 'Client', $f);
|
||||
$f = str_replace('%WiFiChannel%', '1', $f);
|
||||
|
||||
$f = str_replace('window.location.host', json_encode(ESP_IP), $f);
|
||||
|
||||
echo $f;
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/07/23.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_HELPERS_H
|
||||
#define ESP_VT100_FIRMWARE_HELPERS_H
|
||||
|
||||
// strcpy that adds 0 at the end of the buffer. Returns void.
|
||||
#define strncpy_safe(dst, src, n) do { strncpy((char *)(dst), (char *)(src), (n)); (dst)[(n)-1]=0; } while (0)
|
||||
|
||||
/**
|
||||
* Convert IP hex to arguments for printf.
|
||||
* Library IP2STR(ip) does not work correctly due to unaligned memory access.
|
||||
*/
|
||||
#define GOOD_IP2STR(ip) ((ip)>>0)&0xff, ((ip)>>8)&0xff, ((ip)>>16)&0xff, ((ip)>>24)&0xff
|
||||
|
||||
/**
|
||||
* 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 STR_HELPER(x) #x
|
||||
#define STR(x) STR_HELPER(x)
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_HELPERS_H
|
||||
+1
-1
Submodule libesphttpd updated: 03003ea591...f3dd1a2599
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
Cgi/template routines for configuring non-wifi settings
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "cgi_appcfg.h"
|
||||
#include "persist.h"
|
||||
#include "screen.h"
|
||||
#include "helpers.h"
|
||||
|
||||
#define SET_REDIR_SUC "/cfg/app"
|
||||
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
|
||||
|
||||
/**
|
||||
* Universal CGI endpoint to set Terminal params.
|
||||
*/
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
cgiAppCfgSetParams(HttpdConnData *connData)
|
||||
{
|
||||
char buff[50];
|
||||
|
||||
char redir_url_buf[300];
|
||||
char *redir_url = redir_url_buf;
|
||||
redir_url += sprintf(redir_url, SET_REDIR_ERR);
|
||||
// we'll test if anything was printed by looking for \0 in failed_keys_buf
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
// width and height must always go together so we can do max size validation
|
||||
if (GET_ARG("term_width")) {
|
||||
dbg("Default screen width: %s", buff);
|
||||
int w = atoi(buff);
|
||||
if (w > 1) {
|
||||
if (GET_ARG("term_height")) {
|
||||
dbg("Default screen height: %s", buff);
|
||||
int h = atoi(buff);
|
||||
if (h > 1) {
|
||||
if (w * h <= MAX_SCREEN_SIZE) {
|
||||
termconf->width = w;
|
||||
termconf->height = h;
|
||||
} else {
|
||||
warn("Bad dimensions: %d x %d (total %d)", w, h, w*h);
|
||||
redir_url += sprintf(redir_url, "term_width,term_height,");
|
||||
}
|
||||
} else {
|
||||
warn("Bad height: \"%s\"", buff);
|
||||
redir_url += sprintf(redir_url, "term_width,");
|
||||
}
|
||||
} else {
|
||||
warn("Missing height arg!");
|
||||
// this wont happen normally when the form is used
|
||||
redir_url += sprintf(redir_url, "term_width,term_height,");
|
||||
}
|
||||
} else {
|
||||
warn("Bad width: \"%s\"", buff);
|
||||
redir_url += sprintf(redir_url, "term_width,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("default_bg")) {
|
||||
dbg("Screen default BG: %s", buff);
|
||||
int color = atoi(buff);
|
||||
if (color >= 0 && color < 16) {
|
||||
termconf->default_bg = (u8) color;
|
||||
} else {
|
||||
warn("Bad color %s", buff);
|
||||
redir_url += sprintf(redir_url, "default_bg,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("default_fg")) {
|
||||
dbg("Screen default FG: %s", buff);
|
||||
int color = atoi(buff);
|
||||
if (color >= 0 && color < 16) {
|
||||
termconf->default_fg = (u8) color;
|
||||
} else {
|
||||
warn("Bad color %s", buff);
|
||||
redir_url += sprintf(redir_url, "default_fg,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("term_title")) {
|
||||
dbg("Terminal title default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->title, buff, 64); // ATTN those must match the values in
|
||||
}
|
||||
|
||||
if (GET_ARG("btn1")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn1, buff, 10);
|
||||
}
|
||||
|
||||
if (GET_ARG("btn2")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn2, buff, 10);
|
||||
}
|
||||
|
||||
if (GET_ARG("btn3")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn3, buff, 10);
|
||||
}
|
||||
|
||||
if (GET_ARG("btn4")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn4, buff, 10);
|
||||
}
|
||||
|
||||
if (GET_ARG("btn5")) {
|
||||
dbg("Button1 default text: \"%s\"", buff);
|
||||
strncpy_safe(termconf->btn5, buff, 10);
|
||||
}
|
||||
|
||||
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
|
||||
// All was OK
|
||||
info("Set app params - success, saving...");
|
||||
|
||||
terminal_apply_settings();
|
||||
persist_store();
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
} else {
|
||||
warn("Some settings did not validate, asking for correction");
|
||||
// Some errors, appended to the URL as ?err=
|
||||
httpdRedirect(connData, redir_url_buf);
|
||||
}
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
tplAppCfg(HttpdConnData *connData, char *token, void **arg)
|
||||
{
|
||||
#define BUFLEN 100
|
||||
char buff[BUFLEN];
|
||||
|
||||
if (token == NULL) {
|
||||
// We're done
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
strcpy(buff, ""); // fallback
|
||||
|
||||
if (streq(token, "term_width")) {
|
||||
sprintf(buff, "%d", termconf->width);
|
||||
}
|
||||
else if (streq(token, "term_height")) {
|
||||
sprintf(buff, "%d", termconf->height);
|
||||
}
|
||||
else if (streq(token, "default_bg")) {
|
||||
sprintf(buff, "%d", termconf->default_bg);
|
||||
}
|
||||
else if (streq(token, "default_fg")) {
|
||||
sprintf(buff, "%d", termconf->default_fg);
|
||||
}
|
||||
else if (streq(token, "term_title")) {
|
||||
strncpy_safe(buff, termconf->title, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn1")) {
|
||||
strncpy_safe(buff, termconf->btn1, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn2")) {
|
||||
strncpy_safe(buff, termconf->btn2, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn3")) {
|
||||
strncpy_safe(buff, termconf->btn3, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn4")) {
|
||||
strncpy_safe(buff, termconf->btn4, BUFLEN);
|
||||
}
|
||||
else if (streq(token, "btn5")) {
|
||||
strncpy_safe(buff, termconf->btn5, BUFLEN);
|
||||
}
|
||||
|
||||
httpdSend(connData, buff, -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#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
|
||||
+28
-4
@@ -5,9 +5,7 @@
|
||||
#include "cgi_main.h"
|
||||
#include "screen.h"
|
||||
#include "user_main.h"
|
||||
|
||||
#define STR_HELPER(x) #x
|
||||
#define STR(x) STR_HELPER(x)
|
||||
#include "helpers.h"
|
||||
|
||||
/**
|
||||
* Main page template substitution
|
||||
@@ -28,7 +26,33 @@ httpd_cgi_state ICACHE_FLASH_ATTR tplScreen(HttpdConnData *connData, char *token
|
||||
const int bufsiz = 512;
|
||||
char buff[bufsiz];
|
||||
|
||||
if (streq(token, "screenData")) {
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
configuring the network settings
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "cgi_network.h"
|
||||
#include "wifimgr.h"
|
||||
#include "persist.h"
|
||||
#include "helpers.h"
|
||||
|
||||
#define SET_REDIR_SUC "/cfg/network"
|
||||
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
|
||||
|
||||
/**
|
||||
* Callback for async timer
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR applyNetSettingsLaterCb(void *arg)
|
||||
{
|
||||
wifimgr_apply_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal CGI endpoint to set network params.
|
||||
* Those affect DHCP etc, may cause a disconnection.
|
||||
*/
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiNetworkSetParams(HttpdConnData *connData)
|
||||
{
|
||||
static ETSTimer timer;
|
||||
|
||||
char buff[50];
|
||||
|
||||
char redir_url_buf[300];
|
||||
char *redir_url = redir_url_buf;
|
||||
redir_url += sprintf(redir_url, SET_REDIR_ERR);
|
||||
// we'll test if anything was printed by looking for \0 in failed_keys_buf
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
// ---- AP DHCP server lease time ----
|
||||
|
||||
if (GET_ARG("ap_dhcp_time")) {
|
||||
dbg("Setting DHCP lease time to: %s min.", buff);
|
||||
int min = atoi(buff);
|
||||
if (min >= 1 && min <= 2880) {
|
||||
if (wificonf->ap_dhcp_time != min) {
|
||||
wificonf->ap_dhcp_time = (u16) min;
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("Lease time %s out of allowed range 1-2880.", buff);
|
||||
redir_url += sprintf(redir_url, "ap_dhcp_time,");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- AP DHCP start and end IP ----
|
||||
|
||||
if (GET_ARG("ap_dhcp_start")) {
|
||||
dbg("Setting DHCP range start IP to: \"%s\"", buff);
|
||||
u32 ip = ipaddr_addr(buff);
|
||||
if (ip != 0) {
|
||||
if (wificonf->ap_dhcp_range.start_ip.addr != ip) {
|
||||
wificonf->ap_dhcp_range.start_ip.addr = ip;
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad IP: %s", buff);
|
||||
redir_url += sprintf(redir_url, "ap_dhcp_start,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("ap_dhcp_end")) {
|
||||
dbg("Setting DHCP range end IP to: \"%s\"", buff);
|
||||
u32 ip = ipaddr_addr(buff);
|
||||
if (ip != 0) {
|
||||
if (wificonf->ap_dhcp_range.end_ip.addr != ip) {
|
||||
wificonf->ap_dhcp_range.end_ip.addr = ip;
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad IP: %s", buff);
|
||||
redir_url += sprintf(redir_url, "ap_dhcp_end,");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- AP local address & config ----
|
||||
|
||||
if (GET_ARG("ap_addr_ip")) {
|
||||
dbg("Setting AP local IP to: \"%s\"", buff);
|
||||
u32 ip = ipaddr_addr(buff);
|
||||
if (ip != 0) {
|
||||
if (wificonf->ap_addr.ip.addr != ip) {
|
||||
wificonf->ap_addr.ip.addr = ip;
|
||||
wificonf->ap_addr.gw.addr = ip; // always the same, we're the router here
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad IP: %s", buff);
|
||||
redir_url += sprintf(redir_url, "ap_addr_ip,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("ap_addr_mask")) {
|
||||
dbg("Setting AP local IP netmask to: \"%s\"", buff);
|
||||
u32 ip = ipaddr_addr(buff);
|
||||
if (ip != 0) {
|
||||
if (wificonf->ap_addr.netmask.addr != ip) {
|
||||
// ideally this should be checked to match the IP.
|
||||
// Let's hope users know what they're doing
|
||||
wificonf->ap_addr.netmask.addr = ip;
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad IP mask: %s", buff);
|
||||
redir_url += sprintf(redir_url, "ap_addr_mask,");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Station enable/disable DHCP ----
|
||||
|
||||
// DHCP enable / disable (disable means static IP is enabled)
|
||||
if (GET_ARG("sta_dhcp_enable")) {
|
||||
dbg("DHCP enable = %s", buff);
|
||||
int enable = atoi(buff);
|
||||
if (wificonf->sta_dhcp_enable != enable) {
|
||||
wificonf->sta_dhcp_enable = (bool)enable;
|
||||
wifi_change_flags.sta = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Station IP config (Static IP) ----
|
||||
|
||||
if (GET_ARG("sta_addr_ip")) {
|
||||
dbg("Setting Station mode static IP to: \"%s\"", buff);
|
||||
u32 ip = ipaddr_addr(buff);
|
||||
if (ip != 0) {
|
||||
if (wificonf->sta_addr.ip.addr != ip) {
|
||||
wificonf->sta_addr.ip.addr = ip;
|
||||
wifi_change_flags.sta = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad IP: %s", buff);
|
||||
redir_url += sprintf(redir_url, "sta_addr_ip,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("sta_addr_mask")) {
|
||||
dbg("Setting Station mode static IP netmask to: \"%s\"", buff);
|
||||
u32 ip = ipaddr_addr(buff);
|
||||
if (ip != 0 && ip != 0xFFFFFFFFUL) {
|
||||
if (wificonf->sta_addr.netmask.addr != ip) {
|
||||
wificonf->sta_addr.netmask.addr = ip;
|
||||
wifi_change_flags.sta = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad IP mask: %s", buff);
|
||||
redir_url += sprintf(redir_url, "sta_addr_mask,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("sta_addr_gw")) {
|
||||
dbg("Setting Station mode static IP default gateway to: \"%s\"", buff);
|
||||
u32 ip = ipaddr_addr(buff);
|
||||
if (ip != 0) {
|
||||
if (wificonf->sta_addr.gw.addr != ip) {
|
||||
wificonf->sta_addr.gw.addr = ip;
|
||||
wifi_change_flags.sta = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad gw IP: %s", buff);
|
||||
redir_url += sprintf(redir_url, "sta_addr_gw,");
|
||||
}
|
||||
}
|
||||
|
||||
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
|
||||
// All was OK
|
||||
info("Set network params - success, applying in 1000 ms");
|
||||
|
||||
// Settings are applied only if all was OK
|
||||
persist_store();
|
||||
|
||||
// Delayed settings apply, so the response page has a chance to load.
|
||||
// If user connects via the Station IF, they may not even notice the connection reset.
|
||||
os_timer_disarm(&timer);
|
||||
os_timer_setfn(&timer, applyNetSettingsLaterCb, NULL);
|
||||
os_timer_arm(&timer, 1000, false);
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
} else {
|
||||
warn("Some WiFi settings did not validate, asking for correction");
|
||||
// Some errors, appended to the URL as ?err=
|
||||
httpdRedirect(connData, redir_url_buf);
|
||||
}
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
|
||||
//Template code for the WLAN page.
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR tplNetwork(HttpdConnData *connData, char *token, void **arg)
|
||||
{
|
||||
char buff[100];
|
||||
u8 mac[6];
|
||||
|
||||
if (token == NULL) {
|
||||
// We're done
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
strcpy(buff, ""); // fallback
|
||||
|
||||
if (streq(token, "ap_dhcp_time")) {
|
||||
sprintf(buff, "%d", wificonf->ap_dhcp_time);
|
||||
}
|
||||
else if (streq(token, "ap_dhcp_start")) {
|
||||
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.start_ip.addr));
|
||||
}
|
||||
else if (streq(token, "ap_dhcp_end")) {
|
||||
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.end_ip.addr));
|
||||
}
|
||||
else if (streq(token, "ap_addr_ip")) {
|
||||
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_addr.ip.addr));
|
||||
}
|
||||
else if (streq(token, "ap_addr_mask")) {
|
||||
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->ap_addr.netmask.addr));
|
||||
}
|
||||
else if (streq(token, "sta_dhcp_enable")) {
|
||||
sprintf(buff, "%d", wificonf->sta_dhcp_enable);
|
||||
}
|
||||
else if (streq(token, "sta_addr_ip")) {
|
||||
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.ip.addr));
|
||||
}
|
||||
else if (streq(token, "sta_addr_mask")) {
|
||||
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.netmask.addr));
|
||||
}
|
||||
else if (streq(token, "sta_addr_gw")) {
|
||||
sprintf(buff, IPSTR, GOOD_IP2STR(wificonf->sta_addr.gw.addr));
|
||||
}
|
||||
else if (streq(token, "sta_mac")) {
|
||||
wifi_get_macaddr(STATION_IF, mac);
|
||||
sprintf(buff, MACSTR, MAC2STR(mac));
|
||||
}
|
||||
else if (streq(token, "ap_mac")) {
|
||||
wifi_get_macaddr(SOFTAP_IF, mac);
|
||||
sprintf(buff, MACSTR, MAC2STR(mac));
|
||||
}
|
||||
|
||||
httpdSend(connData, buff, -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef CGINET_H
|
||||
#define CGINET_H
|
||||
|
||||
#include "httpd.h"
|
||||
|
||||
httpd_cgi_state cgiNetworkSetParams(HttpdConnData *connData);
|
||||
httpd_cgi_state tplNetwork(HttpdConnData *connData, char *token, void **arg);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Cgi/template routines for configuring non-wifi settings
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "cgi_persist.h"
|
||||
#include "persist.h"
|
||||
#include "helpers.h"
|
||||
|
||||
#define SET_REDIR_SUC "/cfg/admin"
|
||||
|
||||
static bool ICACHE_FLASH_ATTR
|
||||
verify_admin_pw(const char *pw)
|
||||
{
|
||||
// This is not really for security, but to prevent someone who
|
||||
// shouldn't touch those settings from fucking it up.
|
||||
return streq(pw, STR(ADMIN_PASSWORD)); // the PW comes from the makefile
|
||||
}
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
cgiPersistWriteDefaults(HttpdConnData *connData)
|
||||
{
|
||||
char buff[50];
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
// width and height must always go together so we can do max size validation
|
||||
if (GET_ARG("pw")) {
|
||||
dbg("Entered password for admin: %s", buff);
|
||||
if (verify_admin_pw(buff)) {
|
||||
dbg("pw is OK");
|
||||
|
||||
persist_set_as_default();
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
// if pw failed, show the same error as if it's wrong
|
||||
}
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC "?err=Password"); // this will show in the "validation errors" box
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
cgiPersistRestoreDefaults(HttpdConnData *connData)
|
||||
{
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
persist_restore_default();
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
cgiPersistRestoreHard(HttpdConnData *connData)
|
||||
{
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
persist_restore_hard_default();
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef CGIPERSIST_H
|
||||
#define CGIPERSIST_H
|
||||
|
||||
#include "httpd.h"
|
||||
|
||||
httpd_cgi_state cgiPersistWriteDefaults(HttpdConnData *connData);
|
||||
httpd_cgi_state cgiPersistRestoreDefaults(HttpdConnData *connData);
|
||||
httpd_cgi_state cgiPersistRestoreHard(HttpdConnData *connData);
|
||||
|
||||
#endif
|
||||
+585
@@ -0,0 +1,585 @@
|
||||
/*
|
||||
Cgi/template routines for the /wifi url.
|
||||
*/
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* File adapted and improved by Ondřej Hruška <ondra@ondrovo.com>
|
||||
*/
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "cgi_wifi.h"
|
||||
#include "wifimgr.h"
|
||||
#include "persist.h"
|
||||
#include "helpers.h"
|
||||
|
||||
#define SET_REDIR_SUC "/cfg/wifi"
|
||||
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
|
||||
|
||||
/** WiFi access point data */
|
||||
typedef struct {
|
||||
char ssid[32];
|
||||
char bssid[8];
|
||||
int channel;
|
||||
char rssi;
|
||||
char enc;
|
||||
} ApData;
|
||||
|
||||
/** Scan result type */
|
||||
typedef struct {
|
||||
char scanInProgress; //if 1, don't access the underlying stuff from the webpage.
|
||||
ApData **apData;
|
||||
int noAps;
|
||||
} ScanResultData;
|
||||
|
||||
/** Static scan status storage. */
|
||||
static ScanResultData cgiWifiAps;
|
||||
|
||||
/** Connection to AP periodic check timer */
|
||||
static os_timer_t staCheckTimer;
|
||||
|
||||
/**
|
||||
* Calculate approximate signal strength % from RSSI
|
||||
*/
|
||||
int ICACHE_FLASH_ATTR rssi2perc(int rssi)
|
||||
{
|
||||
int r;
|
||||
|
||||
if (rssi > 200)
|
||||
r = 100;
|
||||
else if (rssi < 100)
|
||||
r = 0;
|
||||
else
|
||||
r = 100 - 2 * (200 - rssi); // approx.
|
||||
|
||||
if (r > 100) r = 100;
|
||||
if (r < 0) r = 0;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Auth type to string
|
||||
*/
|
||||
const ICACHE_FLASH_ATTR char *auth2str(AUTH_MODE auth)
|
||||
{
|
||||
switch (auth) {
|
||||
case AUTH_OPEN:
|
||||
return "Open";
|
||||
case AUTH_WEP:
|
||||
return "WEP";
|
||||
case AUTH_WPA_PSK:
|
||||
return "WPA";
|
||||
case AUTH_WPA2_PSK:
|
||||
return "WPA2";
|
||||
case AUTH_WPA_WPA2_PSK:
|
||||
return "WPA/WPA2";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert WiFi opmode to string
|
||||
*/
|
||||
const ICACHE_FLASH_ATTR char *opmode2str(WIFI_MODE opmode)
|
||||
{
|
||||
switch (opmode) {
|
||||
case NULL_MODE:
|
||||
return "Disabled";
|
||||
case STATION_MODE:
|
||||
return "Client";
|
||||
case SOFTAP_MODE:
|
||||
return "AP only";
|
||||
case STATIONAP_MODE:
|
||||
return "Client+AP";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback the code calls when a wlan ap scan is done. Basically stores the result in
|
||||
* the static cgiWifiAps struct.
|
||||
*
|
||||
* @param arg - a pointer to {struct bss_info}, which is a linked list of the found APs
|
||||
* @param status - OK if the scan succeeded
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status)
|
||||
{
|
||||
int n;
|
||||
struct bss_info *bss_link = (struct bss_info *) arg;
|
||||
dbg("wifiScanDoneCb %d", status);
|
||||
if (status != OK) {
|
||||
cgiWifiAps.scanInProgress = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear prev ap data if needed.
|
||||
if (cgiWifiAps.apData != NULL) {
|
||||
for (n = 0; n < cgiWifiAps.noAps; n++) free(cgiWifiAps.apData[n]);
|
||||
free(cgiWifiAps.apData);
|
||||
}
|
||||
|
||||
// Count amount of access points found.
|
||||
n = 0;
|
||||
while (bss_link != NULL) {
|
||||
bss_link = bss_link->next.stqe_next;
|
||||
n++;
|
||||
}
|
||||
// Allocate memory for access point data
|
||||
cgiWifiAps.apData = (ApData **) malloc(sizeof(ApData *) * n);
|
||||
if (cgiWifiAps.apData == NULL) {
|
||||
error("Out of memory allocating apData");
|
||||
return;
|
||||
}
|
||||
cgiWifiAps.noAps = n;
|
||||
info("Scan done: found %d APs", n);
|
||||
|
||||
// Copy access point data to the static struct
|
||||
n = 0;
|
||||
bss_link = (struct bss_info *) arg;
|
||||
while (bss_link != NULL) {
|
||||
if (n >= cgiWifiAps.noAps) {
|
||||
// This means the bss_link changed under our nose. Shouldn't happen!
|
||||
// Break because otherwise we will write in unallocated memory.
|
||||
error("Huh? I have more than the allocated %d aps!", cgiWifiAps.noAps);
|
||||
break;
|
||||
}
|
||||
// Save the ap data.
|
||||
cgiWifiAps.apData[n] = (ApData *) malloc(sizeof(ApData));
|
||||
if (cgiWifiAps.apData[n] == NULL) {
|
||||
error("Can't allocate mem for ap buff.");
|
||||
cgiWifiAps.scanInProgress = 0;
|
||||
return;
|
||||
}
|
||||
cgiWifiAps.apData[n]->rssi = bss_link->rssi;
|
||||
cgiWifiAps.apData[n]->channel = bss_link->channel;
|
||||
cgiWifiAps.apData[n]->enc = bss_link->authmode;
|
||||
strncpy(cgiWifiAps.apData[n]->ssid, (char *) bss_link->ssid, 32);
|
||||
strncpy(cgiWifiAps.apData[n]->bssid, (char *) bss_link->bssid, 6);
|
||||
|
||||
bss_link = bss_link->next.stqe_next;
|
||||
n++;
|
||||
}
|
||||
// We're done.
|
||||
cgiWifiAps.scanInProgress = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Routine to start a WiFi access point scan.
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR wifiStartScan(void)
|
||||
{
|
||||
if (cgiWifiAps.scanInProgress) return;
|
||||
cgiWifiAps.scanInProgress = 1;
|
||||
wifi_station_scan(NULL, wifiScanDoneCb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData)
|
||||
{
|
||||
int pos = (int) connData->cgiData;
|
||||
int len;
|
||||
char buff[256];
|
||||
|
||||
// 2nd and following runs of the function via MORE:
|
||||
if (!cgiWifiAps.scanInProgress && pos != 0) {
|
||||
// Fill in json code for an access point
|
||||
if (pos - 1 < cgiWifiAps.noAps) {
|
||||
int rssi = cgiWifiAps.apData[pos - 1]->rssi;
|
||||
|
||||
len = sprintf(buff, "{\"essid\": \"%s\", \"bssid\": \""
|
||||
MACSTR
|
||||
"\", \"rssi\": %d, \"rssi_perc\": %d, \"enc\": %d, \"channel\": %d}%s",
|
||||
cgiWifiAps.apData[pos - 1]->ssid,
|
||||
MAC2STR(cgiWifiAps.apData[pos - 1]->bssid),
|
||||
rssi,
|
||||
rssi2perc(rssi),
|
||||
cgiWifiAps.apData[pos - 1]->enc,
|
||||
cgiWifiAps.apData[pos - 1]->channel,
|
||||
(pos - 1 == cgiWifiAps.noAps - 1) ? "\n " : ",\n "); //<-terminator
|
||||
|
||||
httpdSend(connData, buff, len);
|
||||
}
|
||||
pos++;
|
||||
if ((pos - 1) >= cgiWifiAps.noAps) {
|
||||
len = sprintf(buff, " ]\n }\n}"); // terminate the whole object
|
||||
httpdSend(connData, buff, len);
|
||||
// Also start a new scan.
|
||||
wifiStartScan();
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
else {
|
||||
connData->cgiData = (void *) pos;
|
||||
return HTTPD_CGI_MORE;
|
||||
}
|
||||
}
|
||||
|
||||
// First run of the function
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "application/json");
|
||||
httpdEndHeaders(connData);
|
||||
|
||||
if (cgiWifiAps.scanInProgress == 1) {
|
||||
// We're still scanning. Tell Javascript code that.
|
||||
len = sprintf(buff, "{\n \"result\": {\n \"inProgress\": 1\n }\n}");
|
||||
httpdSend(connData, buff, len);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
else {
|
||||
// We have a scan result. Pass it on.
|
||||
len = sprintf(buff, "{\n \"result\": {\n \"inProgress\": 0,\n \"APs\": [\n ");
|
||||
httpdSend(connData, buff, len);
|
||||
if (cgiWifiAps.apData == NULL) cgiWifiAps.noAps = 0;
|
||||
connData->cgiData = (void *) 1;
|
||||
return HTTPD_CGI_MORE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cgi to get connection status.
|
||||
*
|
||||
* This endpoint returns JSON with keys:
|
||||
* - status = 'idle', 'working' or 'fail',
|
||||
* - ip = IP address, after connection succeeds
|
||||
*/
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData)
|
||||
{
|
||||
char buff[100];
|
||||
struct ip_info info;
|
||||
|
||||
httpdStartResponse(connData, 200);
|
||||
httpdHeader(connData, "Content-Type", "application/json");
|
||||
httpdEndHeaders(connData);
|
||||
|
||||
// if bad opmode or no SSID configured, skip any checks
|
||||
if (!(wificonf->opmode & STATION_MODE) || wificonf->sta_ssid[0] == 0) {
|
||||
httpdSend(connData, "{\"status\": \"disabled\"}", -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
STATION_STATUS st = wifi_station_get_connect_status();
|
||||
switch(st) {
|
||||
case STATION_IDLE:
|
||||
sprintf(buff, "{\"status\": \"idle\"}"); // unclear when this is used
|
||||
break;
|
||||
|
||||
case STATION_CONNECTING:
|
||||
sprintf(buff, "{\"status\": \"working\"}");
|
||||
break;
|
||||
|
||||
case STATION_WRONG_PASSWORD:
|
||||
sprintf(buff, "{\"status\": \"fail\", \"cause\": \"WRONG_PASSWORD\"}");
|
||||
break;
|
||||
|
||||
case STATION_NO_AP_FOUND:
|
||||
sprintf(buff, "{\"status\": \"fail\", \"cause\": \"AP_NOT_FOUND\"}");
|
||||
break;
|
||||
|
||||
case STATION_CONNECT_FAIL:
|
||||
sprintf(buff, "{\"status\": \"fail\", \"cause\": \"CONNECTION_FAILED\"}");
|
||||
break;
|
||||
|
||||
case STATION_GOT_IP:
|
||||
wifi_get_ip_info(STATION_IF, &info);
|
||||
sprintf(buff, "{\"status\": \"success\", \"ip\": \""IPSTR"\"}", GOOD_IP2STR(info.ip.addr));
|
||||
break;
|
||||
}
|
||||
|
||||
httpdSend(connData, buff, -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for async timer
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR applyWifiSettingsLaterCb(void *arg)
|
||||
{
|
||||
(void*)arg;
|
||||
wifimgr_apply_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal CGI endpoint to set WiFi params.
|
||||
* Note that some may cause a (delayed) restart.
|
||||
*/
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
|
||||
{
|
||||
static ETSTimer timer;
|
||||
|
||||
char buff[50];
|
||||
|
||||
char redir_url_buf[300];
|
||||
char *redir_url = redir_url_buf;
|
||||
redir_url += sprintf(redir_url, SET_REDIR_ERR);
|
||||
// we'll test if anything was printed by looking for \0 in failed_keys_buf
|
||||
|
||||
if (connData->conn == NULL) {
|
||||
//Connection aborted. Clean up.
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
// ---- WiFi opmode ----
|
||||
|
||||
if (GET_ARG("opmode")) {
|
||||
dbg("Setting WiFi opmode to: %s", buff);
|
||||
int mode = atoi(buff);
|
||||
if (mode > NULL_MODE && mode < MAX_MODE) {
|
||||
wificonf->opmode = (WIFI_MODE) mode;
|
||||
} else {
|
||||
warn("Bad opmode value \"%s\"", buff);
|
||||
redir_url += sprintf(redir_url, "opmode,");
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("ap_enable")) {
|
||||
dbg("Enable AP: %s", buff);
|
||||
int enable = atoi(buff);
|
||||
|
||||
if (enable) {
|
||||
wificonf->opmode |= SOFTAP_MODE;
|
||||
} else {
|
||||
wificonf->opmode &= ~SOFTAP_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
if (GET_ARG("sta_enable")) {
|
||||
dbg("Enable STA: %s", buff);
|
||||
int enable = atoi(buff);
|
||||
|
||||
if (enable) {
|
||||
wificonf->opmode |= STATION_MODE;
|
||||
} else {
|
||||
wificonf->opmode &= ~STATION_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- AP transmit power ----
|
||||
|
||||
if (GET_ARG("tpw")) {
|
||||
dbg("Setting AP power to: %s", buff);
|
||||
int tpw = atoi(buff);
|
||||
if (tpw >= 0 && tpw <= 82) { // 0 actually isn't 0 but quite low. 82 is very strong
|
||||
if (wificonf->tpw != tpw) {
|
||||
wificonf->tpw = (u8) tpw;
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("tpw %s out of allowed range 0-82.", buff);
|
||||
redir_url += sprintf(redir_url, "tpw,");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- AP channel (applies in AP-only mode) ----
|
||||
|
||||
if (GET_ARG("ap_channel")) {
|
||||
info("ap_channel = %s", buff);
|
||||
int channel = atoi(buff);
|
||||
if (channel > 0 && channel < 15) {
|
||||
if (wificonf->ap_channel != channel) {
|
||||
wificonf->ap_channel = (u8) channel;
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad channel value \"%s\", allowed 1-14", buff);
|
||||
redir_url += sprintf(redir_url, "ap_channel,");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- SSID name in AP mode ----
|
||||
|
||||
if (GET_ARG("ap_ssid")) {
|
||||
// Replace all invalid ASCII with underscores
|
||||
int i;
|
||||
for (i = 0; i < 32; i++) {
|
||||
char c = buff[i];
|
||||
if (c == 0) break;
|
||||
if (c < 32 || c >= 127) buff[i] = '_';
|
||||
}
|
||||
buff[i] = 0;
|
||||
|
||||
if (strlen(buff) > 0) {
|
||||
if (!streq(wificonf->ap_ssid, buff)) {
|
||||
info("Setting SSID to \"%s\"", buff);
|
||||
strncpy_safe(wificonf->ap_ssid, buff, SSID_LEN);
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad SSID len.");
|
||||
redir_url += sprintf(redir_url, "ap_ssid,");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- AP password ----
|
||||
|
||||
if (GET_ARG("ap_password")) {
|
||||
// Users are free to use any stupid shit in ther password,
|
||||
// but it may lock them out.
|
||||
if (strlen(buff) == 0 || (strlen(buff) >= 8 && strlen(buff) < PASSWORD_LEN-1)) {
|
||||
if (!streq(wificonf->ap_password, buff)) {
|
||||
info("Setting AP password to \"%s\"", buff);
|
||||
strncpy_safe(wificonf->ap_password, buff, PASSWORD_LEN);
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
} else {
|
||||
warn("Bad password len.");
|
||||
redir_url += sprintf(redir_url, "ap_password,");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Hide AP network (do not announce) ----
|
||||
|
||||
if (GET_ARG("ap_hidden")) {
|
||||
dbg("AP hidden = %s", buff);
|
||||
int hidden = atoi(buff);
|
||||
if (hidden != wificonf->ap_hidden) {
|
||||
wificonf->ap_hidden = (hidden != 0);
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Station SSID (to connect to) ----
|
||||
|
||||
if (GET_ARG("sta_ssid")) {
|
||||
if (!streq(wificonf->sta_ssid, buff)) {
|
||||
// No verification needed, at worst it fails to connect
|
||||
info("Setting station SSID to: \"%s\"", buff);
|
||||
strncpy_safe(wificonf->sta_ssid, buff, SSID_LEN);
|
||||
wifi_change_flags.sta = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Station password (empty for none is allowed) ----
|
||||
|
||||
if (GET_ARG("sta_password")) {
|
||||
if (!streq(wificonf->sta_password, buff)) {
|
||||
// No verification needed, at worst it fails to connect
|
||||
info("Setting station password to: \"%s\"", buff);
|
||||
strncpy_safe(wificonf->sta_password, buff, PASSWORD_LEN);
|
||||
wifi_change_flags.sta = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) {
|
||||
// All was OK
|
||||
info("Set WiFi params - success, applying in 1000 ms");
|
||||
|
||||
// Settings are applied only if all was OK
|
||||
//
|
||||
// This is so that options that consist of multiple keys sent together are not applied
|
||||
// only partially if set wrong, which could lead to eg. user losing access and having
|
||||
// to reset to defaults.
|
||||
persist_store();
|
||||
|
||||
// Delayed settings apply, so the response page has a chance to load.
|
||||
// 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);
|
||||
|
||||
httpdRedirect(connData, SET_REDIR_SUC);
|
||||
} else {
|
||||
warn("Some WiFi settings did not validate, asking for correction");
|
||||
// Some errors, appended to the URL as ?err=
|
||||
httpdRedirect(connData, redir_url_buf);
|
||||
}
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
|
||||
//Template code for the WLAN page.
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg)
|
||||
{
|
||||
char buff[100];
|
||||
int x;
|
||||
int connectStatus;
|
||||
|
||||
if (token == NULL) {
|
||||
// We're done
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
|
||||
strcpy(buff, ""); // fallback
|
||||
|
||||
if (streq(token, "opmode_name")) {
|
||||
strcpy(buff, opmode2str(wificonf->opmode));
|
||||
}
|
||||
else if (streq(token, "opmode")) {
|
||||
sprintf(buff, "%d", wificonf->opmode);
|
||||
}
|
||||
else if (streq(token, "sta_enable")) {
|
||||
sprintf(buff, "%d", (wificonf->opmode & STATION_MODE) != 0);
|
||||
}
|
||||
else if (streq(token, "ap_enable")) {
|
||||
sprintf(buff, "%d", (wificonf->opmode & SOFTAP_MODE) != 0);
|
||||
}
|
||||
else if (streq(token, "tpw")) {
|
||||
sprintf(buff, "%d", wificonf->tpw);
|
||||
}
|
||||
else if (streq(token, "ap_channel")) {
|
||||
sprintf(buff, "%d", wificonf->ap_channel);
|
||||
}
|
||||
else if (streq(token, "ap_ssid")) {
|
||||
sprintf(buff, "%s", wificonf->ap_ssid);
|
||||
}
|
||||
else if (streq(token, "ap_password")) {
|
||||
sprintf(buff, "%s", wificonf->ap_password);
|
||||
}
|
||||
else if (streq(token, "ap_hidden")) {
|
||||
sprintf(buff, "%d", wificonf->ap_hidden);
|
||||
}
|
||||
else if (streq(token, "sta_ssid")) {
|
||||
sprintf(buff, "%s", wificonf->sta_ssid);
|
||||
}
|
||||
else if (streq(token, "sta_password")) {
|
||||
sprintf(buff, "%s", wificonf->sta_password);
|
||||
}
|
||||
else if (streq(token, "sta_rssi")) {
|
||||
sprintf(buff, "%d", wifi_station_get_rssi());
|
||||
}
|
||||
else if (streq(token, "sta_active_ssid")) {
|
||||
// For display of our current SSID
|
||||
connectStatus = wifi_station_get_connect_status();
|
||||
x = wifi_get_opmode();
|
||||
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP) {
|
||||
strcpy(buff, "");
|
||||
}
|
||||
else {
|
||||
struct station_config staconf;
|
||||
wifi_station_get_config(&staconf);
|
||||
strcpy(buff, (char *) staconf.ssid);
|
||||
}
|
||||
}
|
||||
else if (streq(token, "sta_active_ip")) {
|
||||
x = wifi_get_opmode();
|
||||
connectStatus = wifi_station_get_connect_status();
|
||||
|
||||
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP) {
|
||||
strcpy(buff, "");
|
||||
}
|
||||
else {
|
||||
struct ip_info info;
|
||||
wifi_get_ip_info(STATION_IF, &info);
|
||||
sprintf(buff, IPSTR, GOOD_IP2STR(info.ip.addr));
|
||||
|
||||
// sprintf(buff, "ip: "IPSTR", mask: "IPSTR", gw: "IPSTR,
|
||||
// GOOD_IP2STR(info.ip.addr),
|
||||
// GOOD_IP2STR(info.netmask.addr),
|
||||
// GOOD_IP2STR(info.gw.addr));
|
||||
}
|
||||
}
|
||||
|
||||
httpdSend(connData, buff, -1);
|
||||
return HTTPD_CGI_DONE;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef CGIWIFI_H
|
||||
#define CGIWIFI_H
|
||||
|
||||
#include "httpd.h"
|
||||
#include "helpers.h"
|
||||
|
||||
httpd_cgi_state cgiWiFiScan(HttpdConnData *connData);
|
||||
|
||||
httpd_cgi_state cgiWiFiSetParams(HttpdConnData *connData);
|
||||
httpd_cgi_state tplWlan(HttpdConnData *connData, char *token, void **arg);
|
||||
httpd_cgi_state cgiWiFiConnStatus(HttpdConnData *connData);
|
||||
|
||||
#endif
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "wifimgr.h"
|
||||
#include "persist.h"
|
||||
|
||||
#define BTNGPIO 0
|
||||
|
||||
@@ -74,16 +76,16 @@ static void ICACHE_FLASH_ATTR resetBtnTimerCb(void *arg) {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
|
||||
|
||||
if (resetCnt>=10) { //5 secs pressed - FR
|
||||
if (resetCnt>=10) { //5 secs pressed - FR (timer is at 500 ms)
|
||||
info("BOOT-button triggered FACTORY RESET!");
|
||||
apars_handle_OSC_FactoryReset();
|
||||
persist_restore_default();
|
||||
}
|
||||
else if (resetCnt>=2) { //1 sec pressed
|
||||
wifi_station_disconnect();
|
||||
wifi_set_opmode(STATIONAP_MODE); //reset to AP+STA mode
|
||||
info("BOOT-button triggered reset to AP mode, restarting...");
|
||||
info("BOOT-button triggered reset to AP mode...");
|
||||
|
||||
system_restart();
|
||||
wificonf->opmode = STATIONAP_MODE;
|
||||
persist_store();
|
||||
wifimgr_apply_settings();
|
||||
}
|
||||
resetCnt=0;
|
||||
}
|
||||
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/07/09.
|
||||
//
|
||||
|
||||
#include "persist.h"
|
||||
#include <esp8266.h>
|
||||
#include "wifimgr.h"
|
||||
#include "screen.h"
|
||||
|
||||
PersistBlock persist;
|
||||
|
||||
#define PERSIST_SECTOR_ID 0x3D
|
||||
|
||||
//region Persist and restore individual modules
|
||||
|
||||
static void ICACHE_FLASH_ATTR
|
||||
apply_live_settings(void)
|
||||
{
|
||||
dbg("[Persist] Applying live settings...");
|
||||
terminal_apply_settings();
|
||||
wifimgr_apply_settings();
|
||||
// ...
|
||||
}
|
||||
|
||||
static void ICACHE_FLASH_ATTR
|
||||
restore_live_settings_to_hard_defaults(void)
|
||||
{
|
||||
wifimgr_restore_defaults();
|
||||
terminal_restore_defaults();
|
||||
// ...
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
/**
|
||||
* Compute CRC32. Adapted from https://github.com/esp8266/Arduino
|
||||
* @param data
|
||||
* @param length
|
||||
* @return crc32
|
||||
*/
|
||||
static uint32_t ICACHE_FLASH_ATTR
|
||||
calculateCRC32(const uint8_t *data, size_t length)
|
||||
{
|
||||
uint32_t crc = 0xffffffff;
|
||||
while (length--) {
|
||||
uint8_t c = *data++;
|
||||
for (uint32_t i = 0x80; i > 0; i >>= 1) {
|
||||
bool bit = (bool) (crc & 0x80000000UL);
|
||||
if (c & i) {
|
||||
bit = !bit;
|
||||
}
|
||||
crc <<= 1;
|
||||
if (bit) {
|
||||
crc ^= 0x04c11db7UL;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a persist bundle checksum
|
||||
*
|
||||
* @param bundle
|
||||
* @return
|
||||
*/
|
||||
static uint32_t ICACHE_FLASH_ATTR
|
||||
compute_checksum(AppConfigBundle *bundle)
|
||||
{
|
||||
return calculateCRC32((uint8_t *) bundle, sizeof(AppConfigBundle) - 4) ^ CHECKSUM_SALT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load, verify and apply persistent config
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
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));
|
||||
|
||||
bool hard_reset = false;
|
||||
|
||||
// Try to load
|
||||
hard_reset |= !system_param_load(PERSIST_SECTOR_ID, 0, &persist, sizeof(PersistBlock));
|
||||
|
||||
// Verify checksums
|
||||
if (hard_reset ||
|
||||
(compute_checksum(&persist.defaults) != persist.defaults.checksum) ||
|
||||
(compute_checksum(&persist.current) != persist.current.checksum)) {
|
||||
error("[Persist] Checksum verification: FAILED");
|
||||
hard_reset = true;
|
||||
} else {
|
||||
info("[Persist] Checksum verification: PASSED");
|
||||
}
|
||||
|
||||
if (hard_reset) {
|
||||
persist_restore_hard_default();
|
||||
// this also stores them to flash and applies to modules
|
||||
} else {
|
||||
apply_live_settings();
|
||||
}
|
||||
|
||||
info("[Persist] All settings loaded and applied.");
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR
|
||||
persist_store(void)
|
||||
{
|
||||
info("[Persist] Storing all settings to FLASH...");
|
||||
|
||||
// Update checksums before write
|
||||
persist.current.checksum = compute_checksum(&persist.current);
|
||||
persist.defaults.checksum = compute_checksum(&persist.defaults);
|
||||
|
||||
if (!system_param_save_with_protect(PERSIST_SECTOR_ID, &persist, sizeof(PersistBlock))) {
|
||||
error("[Persist] Store to flash failed!");
|
||||
}
|
||||
info("[Persist] All settings persisted.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore to built-in defaults
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
persist_restore_hard_default(void)
|
||||
{
|
||||
info("[Persist] Restoring all settings to hard defaults...");
|
||||
|
||||
// Set live config to default values
|
||||
restore_live_settings_to_hard_defaults();
|
||||
|
||||
// Store current -> default
|
||||
memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
|
||||
persist_store();
|
||||
|
||||
info("[Persist] All settings restored to hard defaults.");
|
||||
|
||||
apply_live_settings(); // apply
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore default settings & apply
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
persist_restore_default(void)
|
||||
{
|
||||
info("[Persist] Restoring live settings to stored defaults...");
|
||||
memcpy(&persist.current, &persist.defaults, sizeof(AppConfigBundle));
|
||||
apply_live_settings();
|
||||
info("[Persist] Settings restored to stored defaults.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Store current settings as defaults & write to flash
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
persist_set_as_default(void)
|
||||
{
|
||||
info("[Persist] Storing live settings as defaults..");
|
||||
|
||||
// current -> defaults
|
||||
memcpy(&persist.defaults, &persist.current, sizeof(AppConfigBundle));
|
||||
persist_store();
|
||||
|
||||
info("[Persist] Default settings updated.");
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/07/09.
|
||||
//
|
||||
// There are 4 sets of settings.
|
||||
// - hard defaults - hardcoded in firmware, used for init defaults after flash or if stored data are corrupt
|
||||
// - defaults - persisted by privileged user
|
||||
// - current - persistent current config state, can be restored to defaults any time
|
||||
// - live - non-persistent settings valid only for the current runtime
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_PERSIST_H
|
||||
#define ESP_VT100_FIRMWARE_PERSIST_H
|
||||
|
||||
#include "wifimgr.h"
|
||||
#include "screen.h"
|
||||
|
||||
// Changing this could be used to force-erase the config area
|
||||
// after a firmware upgrade
|
||||
#define CHECKSUM_SALT 0x5F5F5F5F
|
||||
|
||||
/** Struct for current or default settings */
|
||||
typedef struct {
|
||||
WiFiConfigBundle wificonf;
|
||||
TerminalConfigBundle termconf;
|
||||
|
||||
// --- 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.
|
||||
//
|
||||
// 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];
|
||||
|
||||
uint32_t checksum; // computed before write and tested on load. If it doesn't match, values are reset to hard defaults.
|
||||
} AppConfigBundle;
|
||||
|
||||
/** This is the entire data block stored in FLASH */
|
||||
typedef struct {
|
||||
AppConfigBundle defaults; // defaults are stored here
|
||||
AppConfigBundle current; // active settings adjusted by the user
|
||||
} PersistBlock;
|
||||
|
||||
// Persist holds the data currently loaded from the flash
|
||||
extern PersistBlock persist;
|
||||
|
||||
void persist_load(void);
|
||||
void persist_restore_hard_default(void);
|
||||
void persist_restore_default(void);
|
||||
void persist_set_as_default(void);
|
||||
void persist_store(void);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_PERSIST_H
|
||||
+26
-16
@@ -2,15 +2,17 @@
|
||||
#include <httpd.h>
|
||||
#include <cgiwebsocket.h>
|
||||
#include <httpdespfs.h>
|
||||
#include <cgiwifi.h>
|
||||
#include <auth.h>
|
||||
|
||||
#include "routes.h"
|
||||
|
||||
#include "cgi_wifi.h"
|
||||
#include "cgi_reset.h"
|
||||
#include "cgi_ping.h"
|
||||
#include "cgi_main.h"
|
||||
#include "cgi_sockets.h"
|
||||
#include "cgi_network.h"
|
||||
#include "cgi_appcfg.h"
|
||||
#include "cgi_persist.h"
|
||||
|
||||
#define WIFI_PROTECT 0
|
||||
#define WIFI_AUTH_NAME "wifi"
|
||||
@@ -27,30 +29,38 @@ HttpdBuiltInUrl routes[] = {
|
||||
|
||||
// --- Web pages ---
|
||||
ROUTE_TPL_FILE("/", tplScreen, "/term.tpl"),
|
||||
ROUTE_TPL_FILE("/about", tplAbout, "/about.tpl"),
|
||||
ROUTE_FILE("/help", "/help.tpl"),
|
||||
ROUTE_TPL_FILE("/about/?", tplAbout, "/about.tpl"),
|
||||
ROUTE_FILE("/help/?", "/help.tpl"),
|
||||
|
||||
// --- Sockets ---
|
||||
ROUTE_WS(URL_WS_UPDATE, updateSockConnect),
|
||||
|
||||
// --- System control ---
|
||||
ROUTE_CGI("/system/reset", cgiResetDevice),
|
||||
ROUTE_CGI("/system/ping", cgiPing),
|
||||
ROUTE_CGI("/system/reset/?", cgiResetDevice),
|
||||
ROUTE_CGI("/system/ping/?", cgiPing),
|
||||
|
||||
// --- WiFi config ---
|
||||
// --- WiFi config --- (TODO make this conditional and configurable)
|
||||
#if WIFI_PROTECT
|
||||
ROUTE_AUTH("/wifi*", wifiPassFn),
|
||||
#endif
|
||||
ROUTE_REDIRECT("/wifi/", "/wifi"),
|
||||
ROUTE_TPL_FILE("/wifi", tplWlan, "/wifi.tpl"),
|
||||
ROUTE_REDIRECT("/cfg/?", "/cfg/wifi"),
|
||||
|
||||
ROUTE_CGI("/wifi/scan", cgiWiFiScan),
|
||||
ROUTE_CGI("/wifi/connect", cgiWiFiConnect),
|
||||
ROUTE_CGI("/wifi/connstatus", cgiWiFiConnStatus),
|
||||
ROUTE_FILE("/wifi/connecting", "/wifi_conn.tpl"),
|
||||
ROUTE_CGI("/wifi/setmode", cgiWiFiSetMode),
|
||||
ROUTE_CGI("/wifi/setchannel", cgiWiFiSetChannel),
|
||||
ROUTE_CGI("/wifi/setname", cgiWiFiSetSSID),
|
||||
ROUTE_TPL_FILE("/cfg/wifi/?", tplWlan, "/cfg_wifi.tpl"),
|
||||
ROUTE_FILE("/cfg/wifi/connecting/?", "/cfg_wifi_conn.tpl"),
|
||||
ROUTE_CGI("/cfg/wifi/scan", cgiWiFiScan),
|
||||
ROUTE_CGI("/cfg/wifi/connstatus", cgiWiFiConnStatus),
|
||||
ROUTE_CGI("/cfg/wifi/set", cgiWiFiSetParams),
|
||||
|
||||
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_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_FILESYSTEM(),
|
||||
ROUTE_END(),
|
||||
|
||||
+37
-14
@@ -1,9 +1,42 @@
|
||||
#include <esp8266.h>
|
||||
#include <httpd.h>
|
||||
#include "screen.h"
|
||||
#include "persist.h"
|
||||
|
||||
//region Data structures
|
||||
|
||||
TerminalConfigBundle * const termconf = &persist.current.termconf;
|
||||
TerminalConfigBundle termconf_scratch;
|
||||
|
||||
/**
|
||||
* Restore hard defaults
|
||||
*/
|
||||
void terminal_restore_defaults(void)
|
||||
{
|
||||
termconf->default_bg = 0;
|
||||
termconf->default_fg = 7;
|
||||
termconf->width = 26;
|
||||
termconf->height = 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply settings after eg. restore from defaults
|
||||
*/
|
||||
void terminal_apply_settings(void)
|
||||
{
|
||||
memcpy(&termconf_scratch, termconf, sizeof(TerminalConfigBundle));
|
||||
screen_init();
|
||||
}
|
||||
|
||||
#define W termconf_scratch.width
|
||||
#define H termconf_scratch.height
|
||||
|
||||
/**
|
||||
* Highest permissible value of the color attribute
|
||||
*/
|
||||
@@ -50,16 +83,6 @@ static struct {
|
||||
Color bg;
|
||||
} cursor_sav;
|
||||
|
||||
/**
|
||||
* Active screen width
|
||||
*/
|
||||
static int W = SCREEN_DEF_W;
|
||||
|
||||
/**
|
||||
* Active screen height
|
||||
*/
|
||||
static int H = SCREEN_DEF_H;
|
||||
|
||||
// XXX volatile is probably not needed
|
||||
static volatile int notifyLock = 0;
|
||||
|
||||
@@ -99,8 +122,8 @@ cursor_reset(void)
|
||||
{
|
||||
cursor.x = 0;
|
||||
cursor.y = 0;
|
||||
cursor.fg = SCREEN_DEF_FG;
|
||||
cursor.bg = SCREEN_DEF_BG;
|
||||
cursor.fg = termconf_scratch.default_fg;
|
||||
cursor.bg = termconf_scratch.default_bg;
|
||||
cursor.visible = 1;
|
||||
cursor.inverse = 0;
|
||||
cursor.autowrap = 1;
|
||||
@@ -363,8 +386,8 @@ screen_cursor_save(bool withAttrs)
|
||||
cursor_sav.bg = cursor.bg;
|
||||
cursor_sav.inverse = cursor.inverse;
|
||||
} else {
|
||||
cursor_sav.fg = SCREEN_DEF_FG;
|
||||
cursor_sav.bg = SCREEN_DEF_BG;
|
||||
cursor_sav.fg = termconf_scratch.default_fg;
|
||||
cursor_sav.bg = termconf_scratch.default_bg;
|
||||
cursor_sav.inverse = 0;
|
||||
}
|
||||
}
|
||||
|
||||
+26
-8
@@ -34,6 +34,31 @@
|
||||
*
|
||||
*/
|
||||
|
||||
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];
|
||||
} TerminalConfigBundle;
|
||||
|
||||
// Live config
|
||||
extern TerminalConfigBundle * const termconf;
|
||||
|
||||
/**
|
||||
* Transient live config with no persist, can be modified via esc sequences.
|
||||
* terminal_apply_settings() copies termconf to this struct, erasing old scratch changes
|
||||
*/
|
||||
extern TerminalConfigBundle termconf_scratch;
|
||||
|
||||
void terminal_restore_defaults(void);
|
||||
void terminal_apply_settings(void);
|
||||
|
||||
/**
|
||||
* Maximum screen size (determines size of the static data array)
|
||||
*
|
||||
@@ -42,20 +67,13 @@
|
||||
*/
|
||||
#define MAX_SCREEN_SIZE (80*25)
|
||||
|
||||
#define SCREEN_DEF_W 26 //!< Default screen width
|
||||
#define SCREEN_DEF_H 10 //!< Default screen height
|
||||
|
||||
#define SCREEN_DEF_BG 0 //!< Default screen background
|
||||
#define SCREEN_DEF_FG 7 //!< Default screen foreground
|
||||
|
||||
typedef enum {
|
||||
CLEAR_TO_CURSOR=0, CLEAR_FROM_CURSOR=1, CLEAR_ALL=2
|
||||
} ClearMode;
|
||||
|
||||
typedef uint8_t Color;
|
||||
|
||||
httpd_cgi_state ICACHE_FLASH_ATTR
|
||||
screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
|
||||
httpd_cgi_state screenSerializeToBuffer(char *buffer, size_t buf_len, void **data);
|
||||
|
||||
/** Init the screen */
|
||||
void screen_init(void);
|
||||
|
||||
+37
-17
@@ -26,6 +26,8 @@
|
||||
#include "user_main.h"
|
||||
#include "uart_driver.h"
|
||||
#include "ansi_parser_callbacks.h"
|
||||
#include "wifimgr.h"
|
||||
#include "persist.h"
|
||||
|
||||
#ifdef ESPFS_POS
|
||||
CgiUploadFlashDef uploadParams={
|
||||
@@ -47,8 +49,6 @@ CgiUploadFlashDef uploadParams={
|
||||
#define INCLUDE_FLASH_FNS
|
||||
#endif
|
||||
|
||||
static ETSTimer prHeapTimer;
|
||||
|
||||
/** Periodically show heap usage */
|
||||
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg)
|
||||
{
|
||||
@@ -77,11 +77,22 @@ static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg)
|
||||
cnt++;
|
||||
}
|
||||
|
||||
// Deferred init
|
||||
static void user_start(void *unused);
|
||||
|
||||
//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done.
|
||||
void ICACHE_FLASH_ATTR user_init(void)
|
||||
{
|
||||
static ETSTimer userStartTimer;
|
||||
static ETSTimer prHeapTimer;
|
||||
|
||||
serialInit();
|
||||
|
||||
// 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?
|
||||
|
||||
printf("\r\n");
|
||||
banner("====== ESP8266 Remote Terminal ======");
|
||||
banner_info("Firmware (c) Ondrej Hruska, 2017");
|
||||
@@ -94,15 +105,6 @@ void ICACHE_FLASH_ATTR user_init(void)
|
||||
|
||||
ioInit();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 0x40200000 is the base address for spi flash memory mapping, ESPFS_POS is the position
|
||||
// where image is written in flash that is defined in Makefile.
|
||||
#ifdef ESPFS_POS
|
||||
@@ -111,17 +113,35 @@ void ICACHE_FLASH_ATTR user_init(void)
|
||||
espFsInit((void *) (webpages_espfs_start));
|
||||
#endif
|
||||
|
||||
// Captive portal
|
||||
captdnsInit();
|
||||
|
||||
// Server
|
||||
httpdInit(routes, 80);
|
||||
|
||||
// Heap use timer & blink
|
||||
os_timer_disarm(&prHeapTimer);
|
||||
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL);
|
||||
os_timer_arm(&prHeapTimer, 1000, 1);
|
||||
|
||||
// do later (some functions do not work if called from user_init)
|
||||
os_timer_disarm(&userStartTimer);
|
||||
os_timer_setfn(&userStartTimer, user_start, NULL);
|
||||
os_timer_arm(&userStartTimer, 10, 0);
|
||||
}
|
||||
|
||||
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
|
||||
screen_init();
|
||||
|
||||
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/07/08.
|
||||
//
|
||||
|
||||
#include "wifimgr.h"
|
||||
#include "persist.h"
|
||||
|
||||
WiFiConfigBundle * const wificonf = &persist.current.wificonf;
|
||||
WiFiConfChangeFlags wifi_change_flags;
|
||||
|
||||
/**
|
||||
* Restore defaults in the WiFi config block.
|
||||
* This is to be called if the WiFi config is corrupted on startup,
|
||||
* before applying the config.
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
wifimgr_restore_defaults(void)
|
||||
{
|
||||
u8 mac[6];
|
||||
wifi_get_macaddr(SOFTAP_IF, mac);
|
||||
|
||||
wificonf->opmode = SOFTAP_MODE;
|
||||
wificonf->tpw = 20;
|
||||
wificonf->ap_channel = 1;
|
||||
sprintf((char *) wificonf->ap_ssid, "TERM-%02X%02X%02X", mac[3], mac[4], mac[5]);
|
||||
wificonf->ap_password[0] = 0; // PSK2 always if password is not null.
|
||||
wificonf->ap_hidden = false;
|
||||
|
||||
IP4_ADDR(&wificonf->ap_addr.ip, 192, 168, 4, 1);
|
||||
IP4_ADDR(&wificonf->ap_addr.netmask, 255, 255, 255, 0);
|
||||
wificonf->ap_addr.gw.addr = wificonf->ap_addr.gw.addr;
|
||||
|
||||
IP4_ADDR(&wificonf->ap_dhcp_range.start_ip, 192, 168, 4, 100);
|
||||
IP4_ADDR(&wificonf->ap_dhcp_range.end_ip, 192, 168, 4, 200);
|
||||
wificonf->ap_dhcp_range.enable = 1; // this will never get changed, idk why it's even there
|
||||
wificonf->ap_dhcp_time = 120;
|
||||
|
||||
// --- Client config ---
|
||||
wificonf->sta_ssid[0] = 0;
|
||||
wificonf->sta_password[0] = 0;
|
||||
wificonf->sta_dhcp_enable = true;
|
||||
|
||||
IP4_ADDR(&wificonf->sta_addr.ip, 192, 168, 0, (mac[5] == 1 ? 2 : mac[5])); // avoid being the same as "default gw"
|
||||
IP4_ADDR(&wificonf->sta_addr.netmask, 255, 255, 255, 0);
|
||||
IP4_ADDR(&wificonf->sta_addr.gw, 192, 168, 0, 1);
|
||||
|
||||
// DEBUG ONLY - TODO remove for release
|
||||
wificonf->opmode = STATION_MODE;
|
||||
sprintf((char*)wificonf->sta_ssid, "Chlivek");
|
||||
sprintf((char*)wificonf->sta_password, "prase chrochta");
|
||||
}
|
||||
|
||||
static void ICACHE_FLASH_ATTR
|
||||
configure_station(void)
|
||||
{
|
||||
info("[WiFi] Configuring Station mode...");
|
||||
struct station_config conf;
|
||||
strcpy((char *) conf.ssid, (char *) wificonf->sta_ssid);
|
||||
strcpy((char *) conf.password, (char *) wificonf->sta_password);
|
||||
dbg("[WiFi] Connecting to \"%s\", password \"%s\"", conf.ssid, conf.password);
|
||||
conf.bssid_set = 0;
|
||||
conf.bssid[0] = 0;
|
||||
wifi_station_disconnect();
|
||||
wifi_station_set_config_current(&conf);
|
||||
|
||||
if (wificonf->sta_dhcp_enable) {
|
||||
dbg("[WiFi] Starting DHCP...");
|
||||
if (!wifi_station_dhcpc_start()) {
|
||||
error("[WiFi] DHCP failed to start!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
info("[WiFi] Setting up static IP...");
|
||||
dbg("[WiFi] Client.ip = "IPSTR, GOOD_IP2STR(wificonf->sta_addr.ip.addr));
|
||||
dbg("[WiFi] Client.mask = "IPSTR, GOOD_IP2STR(wificonf->sta_addr.netmask.addr));
|
||||
dbg("[WiFi] Client.gw = "IPSTR, GOOD_IP2STR(wificonf->sta_addr.gw.addr));
|
||||
|
||||
wifi_station_dhcpc_stop();
|
||||
// Load static IP config
|
||||
if (!wifi_set_ip_info(STATION_IF, &wificonf->sta_addr)) {
|
||||
error("[WiFi] Error setting static IP!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
info("[WiFi] Trying to connect to AP...");
|
||||
wifi_station_connect();
|
||||
}
|
||||
|
||||
static void ICACHE_FLASH_ATTR
|
||||
configure_ap(void)
|
||||
{
|
||||
bool suc;
|
||||
|
||||
info("[WiFi] Configuring SoftAP mode...");
|
||||
// AP is enabled
|
||||
struct softap_config conf;
|
||||
conf.channel = wificonf->ap_channel;
|
||||
strcpy((char *) conf.ssid, (char *) wificonf->ap_ssid);
|
||||
strcpy((char *) conf.password, (char *) wificonf->ap_password);
|
||||
conf.authmode = (wificonf->ap_password[0] == 0 ? AUTH_OPEN : AUTH_WPA2_PSK);
|
||||
conf.ssid_len = (uint8_t) strlen((char *) conf.ssid);
|
||||
conf.ssid_hidden = wificonf->ap_hidden;
|
||||
conf.max_connection = 4; // default 4 (max possible)
|
||||
conf.beacon_interval = 100; // default 100 ms
|
||||
|
||||
// Set config
|
||||
//ETS_UART_INTR_DISABLE();
|
||||
suc = wifi_softap_set_config_current(&conf);
|
||||
//ETS_UART_INTR_ENABLE();
|
||||
if (!suc) {
|
||||
error("[WiFi] AP config set fail!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set IP
|
||||
info("[WiFi] Configuring SoftAP local IP...");
|
||||
dbg("[WiFi] SoftAP.ip = "IPSTR, GOOD_IP2STR(wificonf->ap_addr.ip.addr));
|
||||
dbg("[WiFi] SoftAP.mask = "IPSTR, GOOD_IP2STR(wificonf->ap_addr.netmask.addr));
|
||||
dbg("[WiFi] SoftAP.gw = "IPSTR, GOOD_IP2STR(wificonf->ap_addr.gw.addr));
|
||||
|
||||
wifi_softap_dhcps_stop();
|
||||
|
||||
// Configure DHCP
|
||||
if (!wifi_set_ip_info(SOFTAP_IF, &wificonf->ap_addr)) {
|
||||
error("[WiFi] IP set fail!");
|
||||
return;
|
||||
}
|
||||
|
||||
info("[WiFi] Configuring SoftAP DHCP server...");
|
||||
dbg("[WiFi] DHCP.start = "IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.start_ip.addr));
|
||||
dbg("[WiFi] DHCP.end = "IPSTR, GOOD_IP2STR(wificonf->ap_dhcp_range.end_ip.addr));
|
||||
dbg("[WiFi] DHCP.lease = %d minutes", wificonf->ap_dhcp_time);
|
||||
|
||||
if (!wifi_softap_set_dhcps_lease(&wificonf->ap_dhcp_range)) {
|
||||
error("[WiFi] DHCP address range set fail!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wifi_softap_set_dhcps_lease_time(wificonf->ap_dhcp_time)) {
|
||||
error("[WiFi] DHCP lease time set fail!");
|
||||
return;
|
||||
}
|
||||
|
||||
// some weird magic shit about router
|
||||
uint8 mode = 1;
|
||||
wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode);
|
||||
|
||||
if (!wifi_softap_dhcps_start()) {
|
||||
error("[WiFi] Failed to start DHCP server!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the WiFi event listener, cycle WiFi, apply settings
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR
|
||||
wifimgr_apply_settings(void)
|
||||
{
|
||||
info("[WiFi] Initializing...");
|
||||
|
||||
// Force wifi cycle
|
||||
// Disconnect - may not be needed?
|
||||
WIFI_MODE opmode = wifi_get_opmode();
|
||||
|
||||
bool is_sta = wificonf->opmode & STATION_MODE;
|
||||
bool is_ap = wificonf->opmode & SOFTAP_MODE;
|
||||
|
||||
if ((wificonf->opmode & STATION_MODE) && !(opmode & STATION_MODE)) {
|
||||
wifi_change_flags.sta = true;
|
||||
}
|
||||
|
||||
if ((wificonf->opmode & SOFTAP_MODE) && !(opmode & SOFTAP_MODE)) {
|
||||
wifi_change_flags.ap = true;
|
||||
}
|
||||
|
||||
if (opmode != wificonf->opmode) {
|
||||
wifi_set_opmode_current(wificonf->opmode);
|
||||
}
|
||||
|
||||
// Configure the client
|
||||
if (is_sta && wifi_change_flags.sta) {
|
||||
configure_station();
|
||||
}
|
||||
|
||||
// Configure the AP
|
||||
if (is_ap && wifi_change_flags.ap) {
|
||||
configure_ap();
|
||||
}
|
||||
|
||||
wifi_change_flags.ap = false;
|
||||
wifi_change_flags.sta = false;
|
||||
|
||||
info("[WiFi] WiFi settings applied.");
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Created by MightyPork on 2017/07/08.
|
||||
// This module handles all WiFi configuration and is interfaced
|
||||
// by the cgi_wifi functions.
|
||||
//
|
||||
|
||||
#ifndef ESP_VT100_FIRMWARE_WIFI_MANAGER_H
|
||||
#define ESP_VT100_FIRMWARE_WIFI_MANAGER_H
|
||||
|
||||
#include <esp8266.h>
|
||||
#include "cgi_wifi.h"
|
||||
|
||||
#define SSID_LEN 32
|
||||
#define PASSWORD_LEN 64
|
||||
|
||||
/**
|
||||
* A structure holding all configured WiFi parameters
|
||||
* and the active state.
|
||||
*
|
||||
* This block can be used eg. for WiFi config backup.
|
||||
*/
|
||||
typedef struct {
|
||||
WIFI_MODE opmode : 8;
|
||||
u8 tpw;
|
||||
|
||||
// --- AP config ---
|
||||
u8 ap_channel;
|
||||
u8 ap_ssid[SSID_LEN];
|
||||
u8 ap_password[PASSWORD_LEN];
|
||||
bool ap_hidden;
|
||||
u16 ap_dhcp_time; // in minutes
|
||||
struct dhcps_lease ap_dhcp_range;
|
||||
|
||||
struct ip_info ap_addr;
|
||||
|
||||
// --- Client config ---
|
||||
u8 sta_ssid[SSID_LEN];
|
||||
u8 sta_password[PASSWORD_LEN];
|
||||
bool sta_dhcp_enable;
|
||||
|
||||
struct ip_info sta_addr;
|
||||
} WiFiConfigBundle;
|
||||
|
||||
typedef struct {
|
||||
bool sta;
|
||||
bool ap;
|
||||
} WiFiConfChangeFlags;
|
||||
|
||||
extern WiFiConfChangeFlags wifi_change_flags;
|
||||
|
||||
extern WiFiConfigBundle * const wificonf;
|
||||
|
||||
void wifimgr_restore_defaults(void);
|
||||
|
||||
void wifimgr_apply_settings(void);
|
||||
|
||||
#endif //ESP_VT100_FIRMWARE_WIFI_MANAGER_H
|
||||
Reference in New Issue
Block a user