Merge pull request #30 from MightyPork/wifi-redesign
Settings overhaul & exposed many new wifi settingspull/111/merge
commit
9a9ed1445a
@ -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; |
||||
} |
@ -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(); |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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'; |
@ -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 = {}); |
@ -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:', |
||||
]; |
@ -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> |
@ -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> |
@ -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; |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
@ -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; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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); } |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
@ -0,0 +1,3 @@ |
||||
@import "background-tiling"; |
||||
@import "pointer"; |
||||
@import "misc"; |
@ -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; |
||||
} |
@ -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,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 @@ |
||||
Subproject commit 03003ea591a272df50159ba52f84ca84c5cad78e |
||||
Subproject commit f3dd1a25993775bec062a1906ced7b07a7fc9db1 |
@ -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 |
@ -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 |
@ -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 |
@ -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
|
@ -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
|
Loading…
Reference in new issue