implemented "wifi manager" and many advanced wifi config options. todo unification and persistence

pull/30/head
Ondřej Hruška 8 years ago
parent c39391f4a0
commit e617b4f283
  1. 6
      CMakeLists.txt
  2. 144
      html_orig/_start.php
  3. 6
      html_orig/jssrc/wifi.js
  4. 8
      html_orig/wifi.html
  5. 2
      libesphttpd
  6. 654
      user/cgi_wifi.c
  7. 33
      user/cgi_wifi.h
  8. 7
      user/routes.c
  9. 67
      user/user_main.c
  10. 194
      user/wifi_manager.c
  11. 47
      user/wifi_manager.h

@ -36,7 +36,6 @@ set(SOURCE_FILES
libesphttpd/include/espmissingincludes.h libesphttpd/include/espmissingincludes.h
libesphttpd/include/espfs.h libesphttpd/include/espfs.h
libesphttpd/include/esp8266.h libesphttpd/include/esp8266.h
libesphttpd/include/cgiwifi.h
libesphttpd/include/cgiwebsocket.h libesphttpd/include/cgiwebsocket.h
libesphttpd/include/cgiflash.h libesphttpd/include/cgiflash.h
libesphttpd/include/captdns.h libesphttpd/include/captdns.h
@ -54,7 +53,6 @@ set(SOURCE_FILES
libesphttpd/lib/heatshrink/heatshrink_decoder.c libesphttpd/lib/heatshrink/heatshrink_decoder.c
libesphttpd/lib/heatshrink/heatshrink.c libesphttpd/lib/heatshrink/heatshrink.c
libesphttpd/mkupgimg/mkupgimg.c libesphttpd/mkupgimg/mkupgimg.c
libesphttpd/util/cgiwifi.c
libesphttpd/util/cgiwebsocket.c libesphttpd/util/cgiwebsocket.c
libesphttpd/util/cgiflash.c libesphttpd/util/cgiflash.c
libesphttpd/util/captdns.c libesphttpd/util/captdns.c
@ -93,6 +91,8 @@ set(SOURCE_FILES
include/ets_sys_extra.h include/ets_sys_extra.h
user/io.c user/io.c
user/io.h user/io.h
user/cgi_wifi.c
user/cgi_wifi.h
user/cgi_ping.c user/cgi_ping.c
user/cgi_reset.c user/cgi_reset.c
user/uart_driver.c user/uart_driver.c
@ -113,7 +113,7 @@ set(SOURCE_FILES
user/cgi_sockets.h user/cgi_sockets.h
user/ansi_parser_callbacks.c user/ansi_parser_callbacks.c
user/ansi_parser_callbacks.h user/ansi_parser_callbacks.h
user/user_main.h) user/user_main.h user/wifi_manager.c user/wifi_manager.h)
include_directories(include) include_directories(include)
include_directories(user) include_directories(user)

@ -0,0 +1,144 @@
<?php
require '_test_env.php';
$prod = defined('STDIN');
$root = $prod ? '' : ('http://' . ESP_IP);
$menu = [
'home' => [ $prod ? '/status' : '/page_status.php', 'Home' ],
'wifi' => [ $prod ? '/wifi' : '/page_wifi.php', 'WiFi config' ],
'about' => [ $prod ? '/about' : '/page_about.php', 'About' ],
];
$appname = 'Current Analyser';
function e($s) {
return htmlspecialchars($s, ENT_HTML5|ENT_QUOTES);
}
?><!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><?= e($menu[$page][1]) ?> - <?= e($appname) ?></title>
<link href="/css/app.css" rel="stylesheet">
<script src="/js/all.js"></script>
<script>
// server root (or URL) - used for local development with remote AJAX calls
// (this needs CORS working on the target - which I added to esp-httpd)
var _root = <?= json_encode($root) ?>;
</script>
</head>
<body class="page-<?=$page?>">
<div id="outer">
<nav id="menu">
<div id="brand" onclick="$('#menu').toggleClass('expanded')"><?= e($appname) ?></div>
<?php
// generate the menu
foreach($menu as $k => $m) {
$sel = ($page == $k) ? ' class="selected"' : '';
$text = e($m[1]);
$url = e($m[0]);
echo "<a href=\"$url\"$sel>$text</a>";
}
?>
</nav>
<div id="content">
<img src="/img/loader.gif" alt="Loading…" id="loader">
<!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>

@ -118,9 +118,9 @@
} }
$('#modeswitch').html([ $('#modeswitch').html([
'<a class="button" href="/wifi/setmode?mode=3">Client+AP</a>&nbsp;<a class="button" href="/wifi/setmode?mode=2">AP only</a>', '<a class="button" href="/wifi/set?opmode=3">Client+AP</a>&nbsp;<a class="button" href="/wifi/set?opmode=2">AP only</a>',
'<a class="button" href="/wifi/setmode?mode=3">Client+AP</a>', '<a class="button" href="/wifi/set?opmode=3">Client+AP</a>',
'<a class="button" href="/wifi/setmode?mode=1">Client only</a>&nbsp;<a class="button" href="/wifi/setmode?mode=2">AP only</a>' '<a class="button" href="/wifi/set?opmode=1">Client only</a>&nbsp;<a class="button" href="/wifi/set?opmode=2">AP only</a>'
][obj.mode-1]); ][obj.mode-1]);
}; };

@ -32,8 +32,8 @@
<tr class="x-hide-1"> <tr class="x-hide-1">
<th><label for="channel">AP channel</label></th> <th><label for="channel">AP channel</label></th>
<td> <td>
<form action="/wifi/setchannel" method="GET"> <form action="/wifi/set" method="GET">
<input name="ch" id="channel" type="number" step=1 min=1 max=14 value="%WiFiChannel%"><!-- <input name="ap_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"> --><input type="submit" value="Set" class="narrow btn-green x-hide-3">
</form> </form>
</td> </td>
@ -41,8 +41,8 @@
<tr class="x-hide-1"> <tr class="x-hide-1">
<th><label for="channel">AP name</label></th> <th><label for="channel">AP name</label></th>
<td> <td>
<form action="/wifi/setname" method="GET"> <form action="/wifi/set" method="GET">
<input name="name" type="text" value="%APName%"><!-- <input name="ap_ssid" type="text" value="%APName%"><!--
--><input type="submit" value="Set" class="narrow btn-green"> --><input type="submit" value="Set" class="narrow btn-green">
</form> </form>
</td> </td>

@ -1 +1 @@
Subproject commit 03003ea591a272df50159ba52f84ca84c5cad78e Subproject commit 38c6c91f50e5a5cfba8df8309a95e814695accba

@ -0,0 +1,654 @@
/*
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>
*/
// TODO convert to work with WiFi Manager
// TODO make changes write to wificonf and apply when a different CGI is run (/wifi/apply or something)
// TODO (connection will trigger this immediately, with some delayto show the connecting page. Then polling cna proceed as usual)
#include <esp8266.h>
#include "cgi_wifi.h"
/** 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;
/** Progress of connection to AP enum */
typedef enum {
CONNTRY_IDLE = 0,
CONNTRY_WORKING = 1,
CONNTRY_SUCCESS = 2,
CONNTRY_FAIL = 3,
} ConnTry;
/** Connection result var */
static ConnTry connTryStatus = CONNTRY_IDLE;
/** Connection to AP periodic check timer */
static os_timer_t staCheckTimer;
/** reset_later() timer */
static ETSTimer resetTmr;
/**
* Callback for reset_later()
*/
static void ICACHE_FLASH_ATTR resetTmrCb(void *arg)
{
system_restart();
}
/**
* Schedule a reset
* @param ms reset delay (milliseconds)
*/
static void ICACHE_FLASH_ATTR reset_later(int ms)
{
os_timer_disarm(&resetTmr);
os_timer_setfn(&resetTmr, resetTmrCb, NULL);
os_timer_arm(&resetTmr, ms, false);
}
/**
* 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;
}
}
/** Temp store for new ap info. */
static struct station_config stconf;
/**
* This routine is ran some time after a connection attempt to an access point. If
* the connect succeeds, this gets the module in STA-only mode.
*/
static void ICACHE_FLASH_ATTR staCheckConnStatus(void *arg)
{
int x = wifi_station_get_connect_status();
if (x == STATION_GOT_IP) {
info("Connected to AP.");
connTryStatus = CONNTRY_SUCCESS;
// This would enter STA only mode, but that kills the browser page if using STA+AP.
// Instead we stay in the current mode and let the user switch manually.
//wifi_set_opmode(STATION_MODE);
//system_restart();
}
else {
connTryStatus = CONNTRY_FAIL;
error("Connection failed.");
}
}
/**
* Actually connect to a station. This routine is timed because I had problems
* with immediate connections earlier. It probably was something else that caused it,
* but I can't be arsed to put the code back :P
*/
static void ICACHE_FLASH_ATTR cgiWiFiConnect_do(void *arg)
{
int x;
dbg("Try to connect to AP...");
wifi_station_disconnect();
wifi_station_set_config(&stconf);
wifi_station_connect();
x = wifi_get_opmode();
connTryStatus = CONNTRY_WORKING;
if (x != STATION_MODE) {
//Schedule check
os_timer_disarm(&staCheckTimer);
os_timer_setfn(&staCheckTimer, staCheckConnStatus, NULL);
os_timer_arm(&staCheckTimer, 15000, 0); //time out after 15 secs of trying to connect
}
}
/**
* This cgi uses the routines above to connect to a specific access point with the
* given ESSID using the given password.
*
* Args:
* - essid = SSID to connect to
* - passwd = password to connect with
*/
httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData)
{
char essid[128];
char passwd[128];
static os_timer_t reassTimer;
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
int ssilen = httpdFindArg(connData->post->buff, "essid", essid, sizeof(essid));
int passlen = httpdFindArg(connData->post->buff, "passwd", passwd, sizeof(passwd));
if (ssilen == -1 || passlen == -1) {
error("Not rx needed args!");
httpdRedirect(connData, "/wifi");
}
else {
strncpy((char *) stconf.ssid, essid, 32);
strncpy((char *) stconf.password, passwd, 64);
info("Try to connect to AP %s pw %s", essid, passwd);
//Schedule disconnect/connect
os_timer_disarm(&reassTimer);
os_timer_setfn(&reassTimer, cgiWiFiConnect_do, NULL);
// redirect & start connecting a little bit later
os_timer_arm(&reassTimer, 2000, 0); // was 500, increased so the connecting page has time to load
connTryStatus = CONNTRY_IDLE;
httpdRedirect(connData, "/wifi/connecting");
}
return HTTPD_CGI_DONE;
}
/**
* 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];
int len;
struct ip_info info;
int st = wifi_station_get_connect_status();
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/json");
httpdEndHeaders(connData);
if (connTryStatus == CONNTRY_IDLE) {
len = sprintf(buff, "{\"status\": \"idle\"}");
}
else if (connTryStatus == CONNTRY_WORKING || connTryStatus == CONNTRY_SUCCESS) {
if (st == STATION_GOT_IP) {
wifi_get_ip_info(STATION_IF, &info);
len = sprintf(buff, "{\"status\": \"success\", \"ip\": \""
IPSTR
"\"}", GOOD_IP2STR(info.ip.addr));
os_timer_disarm(&staCheckTimer);
os_timer_setfn(&staCheckTimer, staCheckConnStatus, NULL);
os_timer_arm(&staCheckTimer, 1000, 0);
} else {
len = sprintf(buff, "{\"status\": \"working\"}");
}
}
else {
len = sprintf(buff, "{\"status\": \"fail\"}");
}
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
}
/**
* Universal CGI endpoint to set WiFi params.
* Note that some may cause a (delayed) restart.
*
* Args:
* - ap_ch = channel 1-14
* - ap_ssid = SSID name for AP mode
* - opmode = WiFi mode (resets device)
* - hostname = set client hostname
* - tpw = set transmit power
* - sta_dhcp_lt = DHCP server lease time
* - sta_ip = station mode static IP
* - sta_mask = station mode static IP mask (apply only if 'ip' is also sent)
* - sta_gw = station mode default gateway (apply only if 'ip' is also sent)
* (can be left out, then 0.0.0.0 is used and outbound connections won't work -
* but we're normally not making any)
* - dhcp = enable or disable DHCP on the station interface
*/
httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiSetParams(HttpdConnData *connData)
{
int len, len2, len3;
char buff[50];
char buff2[50];
char buff3[50];
// TODO change so that settings are not applied immediately, but persisted first
// TODO apply temporary changes like static IP in wifi event CBs
if (connData->conn == NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
// AP channel (applies in AP-only mode)
len = httpdFindArg(connData->getArgs, "ap_ch", buff, sizeof(buff));
if (len > 0) {
info("Setting WiFi channel for AP-only mode to: %s", buff);
int channel = atoi(buff);
if (channel > 0 && channel < 15) {
dbg("Setting channel=%d", channel);
struct softap_config wificfg;
wifi_softap_get_config(&wificfg);
wificfg.channel = (uint8) channel;
wifi_softap_set_config(&wificfg);
} else {
warn("Bad channel value %s, allowed 1-14", buff);
}
}
// SSID name in AP mode
len = httpdFindArg(connData->getArgs, "ap_ssid", buff, sizeof(buff));
if (len > 0) {
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;
info("Setting SSID to %s", buff);
struct softap_config wificfg;
wifi_softap_get_config(&wificfg);
sprintf((char *) wificfg.ssid, buff);
wificfg.ssid_len = strlen((char *) wificfg.ssid);
wifi_softap_set_config(&wificfg);
}
// WiFi mode
len = httpdFindArg(connData->getArgs, "opmode", buff, sizeof(buff));
if (len > 0) {
dbg("Setting WiFi opmode to: %s", buff);
int mode = atoi(buff);
if (mode > NULL_MODE && mode < MAX_MODE) {
wifi_set_opmode(mode);
reset_later(200);
} else {
warn("Bad opmode value %s", buff);
}
}
// Hostname in station mode (for DHCP)
len = httpdFindArg(connData->getArgs, "hostname", buff, sizeof(buff));
if (len > 0) {
dbg("Setting station sta_hostname to: %s", buff);
wifi_station_set_hostname(buff);
// TODO persistency, re-apply on boot
}
// Hostname in station mode (for DHCP)
len = httpdFindArg(connData->getArgs, "tpw", buff, sizeof(buff));
if (len > 0) {
dbg("Setting AP power to: %s", buff);
int tpw = atoi(buff);
// min tpw to avoid user locking themselves out TODO verify
if (tpw >= 0 && tpw <= 82) {
// TODO persistency, re-apply on boot
system_phy_set_max_tpw(tpw);
} else {
warn("tpw %s out of allowed range 0-82.", buff);
}
}
// DHCP server lease time
len = httpdFindArg(connData->getArgs, "ap_dhcp_lt", buff, sizeof(buff));
if (len > 0) {
dbg("Setting DHCP lease time to: %s min.", buff);
int min = atoi(buff);
if (min >= 1 && min <= 2880) {
// TODO persistency, re-apply on boot
// TODO set only if we're in the right opmode
wifi_softap_set_dhcps_lease_time(min);
} else {
warn("Lease time %s out of allowed range 1-2880.", buff);
}
}
// DHCP enable / disable (disable means static IP is enabled)
len = httpdFindArg(connData->getArgs, "sta_dhcp", buff, sizeof(buff));
if (len > 0) {
dbg("DHCP enable = %s", buff);
int enable = atoi(buff);
if (enable != 0) {
wifi_station_dhcpc_stop();
} else {
wifi_station_dhcpc_start();
}
// TODO persistency
}
// Static IP
len = httpdFindArg(connData->getArgs, "sta_ip", buff, sizeof(buff));
len2 = httpdFindArg(connData->getArgs, "sta_mask", buff2, sizeof(buff2));
len3 = httpdFindArg(connData->getArgs, "sta_gw", buff3, sizeof(buff3));
if (len > 0) {
// TODO set only if we're in the right opmode
// TODO persistency
dbg("Setting static IP = %s", buff);
struct ip_info ipinfo;
ipinfo.ip.addr = ipaddr_addr(buff);
ipinfo.netmask.addr = IPADDR_NONE;
ipinfo.gw.addr = IPADDR_NONE;
if (len2 > 0) {
dbg("Netmask = %s", buff2);
ipinfo.netmask.addr = ipaddr_addr(buff2);
}
if (len3 > 0) {
dbg("Gateway = %s", buff3);
ipinfo.gw.addr = ipaddr_addr(buff3);
}
// TODO ...
wifi_station_dhcpc_stop();
wifi_set_ip_info(STATION_IF, &ipinfo);
}
httpdRedirect(connData, "/wifi");
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[500];
int x;
int connectStatus;
static struct station_config stconf;
static struct softap_config apconf;
if (token == NULL) {
// We're done
return HTTPD_CGI_DONE;
}
wifi_station_get_config(&stconf);
wifi_softap_get_config(&apconf);
strcpy(buff, "Unknown");
if (streq(token, "WiFiMode")) {
x = wifi_get_opmode();
strcpy(buff, opmode2str(x));
}
else if (streq(token, "WiFiModeNum")) {
x = wifi_get_opmode();
sprintf(buff, "%d", x);
}
else if (streq(token, "WiFiChannel")) {
sprintf(buff, "%d", apconf.channel);
}
else if (streq(token, "APName")) {
sprintf(buff, "%s", apconf.ssid);
}
else if (streq(token, "StaIP")) {
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));
}
}
else if (streq(token, "StaSSID")) {
connectStatus = wifi_station_get_connect_status();
x = wifi_get_opmode();
if (x == SOFTAP_MODE || connectStatus != STATION_GOT_IP) {
strcpy(buff, "");
}
else {
strcpy(buff, (char *) stconf.ssid);
}
}
else if (streq(token, "WiFiPasswd")) {
strcpy(buff, (char *) stconf.password);
}
else if (streq(token, "WiFiapwarn")) {
// TODO get rid of this
x = wifi_get_opmode();
if (x == SOFTAP_MODE) { // 2
strcpy(buff, "<a href=\"/wifi/setmode?mode=3\">Enable client</a> for scanning.");
}
else if (x == STATIONAP_MODE) { // 3
strcpy(buff,
"Switch: <a href=\"/wifi/setmode?mode=1\">Client only</a>, <a href=\"/wifi/setmode?mode=2\">AP only</a>");
}
else { // 1
strcpy(buff,
"Switch: <a href=\"/wifi/setmode?mode=3\">Client+AP</a>, <a href=\"/wifi/setmode?mode=2\">AP only</a>");
}
}
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}

@ -0,0 +1,33 @@
#ifndef CGIWIFI_H
#define CGIWIFI_H
#include "httpd.h"
/**
* 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
httpd_cgi_state cgiWiFiScan(HttpdConnData *connData);
httpd_cgi_state cgiWiFiConnect(HttpdConnData *connData);
httpd_cgi_state cgiWiFiConnStatus(HttpdConnData *connData);
httpd_cgi_state cgiWiFiSetParams(HttpdConnData *connData);
httpd_cgi_state tplWlan(HttpdConnData *connData, char *token, void **arg);
// WiFi config options:
// - Persistent
// - channel
// - AP ssid
// - opmode
// - AP to connect to
// - Temporary
// - sta_hostname (sta)
// - tpw (ap, sta+ap?)
// - dhcp_lt (ap, sta+ap)
// - static IP
// - static mask
// - static gw
// - dhcp enable or disable
#endif

@ -2,11 +2,10 @@
#include <httpd.h> #include <httpd.h>
#include <cgiwebsocket.h> #include <cgiwebsocket.h>
#include <httpdespfs.h> #include <httpdespfs.h>
#include <cgiwifi.h>
#include <auth.h> #include <auth.h>
#include "routes.h" #include "routes.h"
#include "cgi_wifi.h"
#include "cgi_reset.h" #include "cgi_reset.h"
#include "cgi_ping.h" #include "cgi_ping.h"
#include "cgi_main.h" #include "cgi_main.h"
@ -48,9 +47,7 @@ HttpdBuiltInUrl routes[] = {
ROUTE_CGI("/wifi/connect", cgiWiFiConnect), ROUTE_CGI("/wifi/connect", cgiWiFiConnect),
ROUTE_CGI("/wifi/connstatus", cgiWiFiConnStatus), ROUTE_CGI("/wifi/connstatus", cgiWiFiConnStatus),
ROUTE_FILE("/wifi/connecting", "/wifi_conn.tpl"), ROUTE_FILE("/wifi/connecting", "/wifi_conn.tpl"),
ROUTE_CGI("/wifi/setmode", cgiWiFiSetMode), ROUTE_CGI("/wifi/set", cgiWiFiSetParams),
ROUTE_CGI("/wifi/setchannel", cgiWiFiSetChannel),
ROUTE_CGI("/wifi/setname", cgiWiFiSetSSID),
ROUTE_FILESYSTEM(), ROUTE_FILESYSTEM(),
ROUTE_END(), ROUTE_END(),

@ -26,6 +26,7 @@
#include "user_main.h" #include "user_main.h"
#include "uart_driver.h" #include "uart_driver.h"
#include "ansi_parser_callbacks.h" #include "ansi_parser_callbacks.h"
#include "wifi_manager.h"
#ifdef ESPFS_POS #ifdef ESPFS_POS
CgiUploadFlashDef uploadParams={ CgiUploadFlashDef uploadParams={
@ -48,6 +49,7 @@ CgiUploadFlashDef uploadParams={
#endif #endif
static ETSTimer prHeapTimer; static ETSTimer prHeapTimer;
static ETSTimer userStartTimer;
/** Periodically show heap usage */ /** Periodically show heap usage */
static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg)
@ -77,11 +79,49 @@ static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg)
cnt++; cnt++;
} }
static void user_start(void *unused)
{
// TODO load persistent data, init wificonf
// Change AP name if AI-THINKER found (means un-initialized device)
struct softap_config apconf;
wifi_softap_get_config(&apconf);
if (strstarts((char*)apconf.ssid, "AI-THINKER")) {
warn("Un-initialized device, performing factory reset.");
apars_handle_OSC_FactoryReset();
return;
}
// Set up WiFi & connect
wifimgr_restore_defaults();
wifimgr_apply_settings();
// Captive portal
captdnsInit();
// Server
httpdInit(routes, 80);
// The terminal screen
screen_init();
// Print the CANCEL character to indicate the module has restarted
// Critically important for client application if any kind of screen persistence / content re-use is needed
UART_WriteChar(UART0, 24, UART_TIMEOUT_US); // 0x18 - 24 - CAN
info("Listening on UART0, 115200-8-N-1!");
}
//Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done. //Main routine. Initialize stdout, the I/O, filesystem and the webserver and we're done.
void ICACHE_FLASH_ATTR user_init(void) void ICACHE_FLASH_ATTR user_init(void)
{ {
serialInit(); serialInit();
// Prevent WiFi starting and connecting by default
// let wifi manager handle it
wifi_station_set_auto_connect(false);
wifi_set_opmode(NULL_MODE);
printf("\r\n"); printf("\r\n");
banner("====== ESP8266 Remote Terminal ======"); banner("====== ESP8266 Remote Terminal ======");
banner_info("Firmware (c) Ondrej Hruska, 2017"); banner_info("Firmware (c) Ondrej Hruska, 2017");
@ -94,15 +134,6 @@ void ICACHE_FLASH_ATTR user_init(void)
ioInit(); ioInit();
// Change AP name if AI-THINKER found (means un-initialized device)
struct softap_config apconf;
wifi_softap_get_config(&apconf);
if (strstarts((char*)apconf.ssid, "AI-THINKER")) {
warn("Un-initialized device, performing factory reset.");
apars_handle_OSC_FactoryReset();
return;
}
// 0x40200000 is the base address for spi flash memory mapping, ESPFS_POS is the position // 0x40200000 is the base address for spi flash memory mapping, ESPFS_POS is the position
// where image is written in flash that is defined in Makefile. // where image is written in flash that is defined in Makefile.
#ifdef ESPFS_POS #ifdef ESPFS_POS
@ -111,25 +142,15 @@ void ICACHE_FLASH_ATTR user_init(void)
espFsInit((void *) (webpages_espfs_start)); espFsInit((void *) (webpages_espfs_start));
#endif #endif
// Captive portal
captdnsInit();
// Server
httpdInit(routes, 80);
// Heap use timer & blink // Heap use timer & blink
os_timer_disarm(&prHeapTimer); os_timer_disarm(&prHeapTimer);
os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL); os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL);
os_timer_arm(&prHeapTimer, 1000, 1); os_timer_arm(&prHeapTimer, 1000, 1);
// The terminal screen // do later (some functions do not yet work if called from user_init)
screen_init(); os_timer_disarm(&userStartTimer);
os_timer_setfn(&userStartTimer, user_start, NULL);
// Print the CANCEL character to indicate the module has restarted os_timer_arm(&userStartTimer, 10, 0);
// Critically important for client application if any kind of screen persistence / content re-use is needed
UART_WriteChar(UART0, 24, UART_TIMEOUT_US); // 0x18 - 24 - CAN
info("Listening on UART0, 115200-8-N-1!");
} }
// ---- unused funcs removed from sdk to save space --- // ---- unused funcs removed from sdk to save space ---

@ -0,0 +1,194 @@
//
// Created by MightyPork on 2017/07/08.
//
#include "wifi_manager.h"
WiFiSettingsBlock wificonf;
/**
* 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 wifimgr_restore_defaults(void)
{
u8 mac[6];
wifi_get_macaddr(SOFTAP_IF, mac);
wificonf.opmode = STATIONAP_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_dhcp_lease_time = 120;
wificonf.ap_hidden = false;
IP4_ADDR(&wificonf.ap_ip.ip, 192, 168, mac[5], 1);
IP4_ADDR(&wificonf.ap_ip.netmask, 255, 255, 255, 0);
IP4_ADDR(&wificonf.ap_ip.gw, 192, 168, mac[5], 1);
// --- Client config ---
wificonf.sta_ssid[0] = 0;
wificonf.sta_password[0] = 0;
//sprintf((char *) wificonf.sta_ssid, "Chlivek");
//sprintf((char *) wificonf.sta_password, "prase chrochta");
strcpy((char *) wificonf.sta_hostname, (char *) wificonf.ap_ssid); // use the same value for sta_hostname as AP name
wificonf.sta_dhcp_enable = true;
IP4_ADDR(&wificonf.sta_ip.ip, 192, 168, 0, (mac[5]==1?2:mac[5]));// avoid being the same as "default gw"
IP4_ADDR(&wificonf.sta_ip.netmask, 255, 255, 255, 0);
IP4_ADDR(&wificonf.sta_ip.gw, 192, 168, 0, 1);
}
/**
* Event handler
*/
void wifimgr_event_cb(System_Event_t *event)
{
switch (event->event) {
// case EVENT_STAMODE_CONNECTED:
// EVENT_STAMODE_DISCONNECTED,
// EVENT_STAMODE_AUTHMODE_CHANGE,
// EVENT_STAMODE_GOT_IP,
// EVENT_STAMODE_DHCP_TIMEOUT,
// EVENT_SOFTAPMODE_STACONNECTED,
// EVENT_SOFTAPMODE_STADISCONNECTED,
// EVENT_SOFTAPMODE_PROBEREQRECVED,
}
}
static void 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);
dbg("[WiFi] Hostname = %s", wificonf.sta_hostname);
wifi_station_set_hostname((char*)wificonf.sta_hostname);
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_ip.ip.addr));
dbg("[WiFi] Client.mask = "IPSTR, GOOD_IP2STR(wificonf.sta_ip.netmask.addr));
dbg("[WiFi] Client.gw = "IPSTR, GOOD_IP2STR(wificonf.sta_ip.gw.addr));
wifi_station_dhcpc_stop();
// Load static IP config
if (!wifi_set_ip_info(STATION_IF, &wificonf.sta_ip)) {
error("[WiFi] Error setting static IP!");
return;
}
}
info("[WiFi] Trying to connect to AP...");
wifi_station_connect();
}
static void 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 = 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_ip.ip.addr));
dbg("[WiFi] SoftAP.mask = "IPSTR, GOOD_IP2STR(wificonf.ap_ip.netmask.addr));
dbg("[WiFi] SoftAP.gw = "IPSTR, GOOD_IP2STR(wificonf.ap_ip.gw.addr));
wifi_softap_dhcps_stop();
// Configure DHCP
if (!wifi_set_ip_info(SOFTAP_IF, &wificonf.ap_ip)) {
error("[WiFi] IP set fail!");
return;
}
info("[WiFi] Configuring SoftAP DHCP server...");
struct dhcps_lease dhcp_lease;
struct ip_addr ip;
ip.addr = wificonf.ap_ip.ip.addr;
ip.addr = (ip.addr & 0x00FFFFFFUL) | ((((ip.addr >> 24) & 0xFF) + 99UL) << 24);
dhcp_lease.start_ip.addr = ip.addr;
ip.addr = (ip.addr & 0x00FFFFFFUL) | ((((ip.addr >> 24) & 0xFF) + 100UL) << 24);
dhcp_lease.end_ip.addr = ip.addr;
dbg("[WiFi] DHCP.start = "IPSTR, GOOD_IP2STR(dhcp_lease.start_ip.addr));
dbg("[WiFi] DHCP.end = "IPSTR, GOOD_IP2STR(dhcp_lease.end_ip.addr));
dbg("[WiFi] DHCP.lease = %d minutes", wificonf.ap_dhcp_lease_time);
if (!wifi_softap_set_dhcps_lease(&dhcp_lease)) {
error("[WiFi] DHCP address range set fail!");
return;
}
if (!wifi_softap_set_dhcps_lease_time(wificonf.ap_dhcp_lease_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 wifimgr_apply_settings(void)
{
info("[WiFi] Initializing WiFi manager...");
// wifi_set_event_handler_cb(wifimgr_event_cb);
// Force wifi cycle
dbg("[WiFi] WiFi reset to apply new settings");
wifi_set_opmode(NULL_MODE);
wifi_set_opmode(wificonf.opmode);
// Configure the client
if (wificonf.opmode == STATIONAP_MODE || wificonf.opmode == STATION_MODE) {
configure_station();
}
// Configure the AP
if (wificonf.opmode == STATIONAP_MODE || wificonf.opmode == SOFTAP_MODE) {
configure_ap();
}
}

@ -0,0 +1,47 @@
//
// 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"
/**
* 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 : 32;
u8 sta_hostname[32];
u32 tpw;
// --- AP config ---
u32 ap_channel; // 32 for alignment, needs 8
u8 ap_ssid[32];
u8 ap_password[32];
u32 ap_hidden;
u32 ap_dhcp_lease_time; // in minutes
struct ip_info ap_ip;
// --- Client config ---
u8 sta_ssid[32];
u8 sta_password[64];
u32 sta_dhcp_enable;
struct ip_info sta_ip;
} WiFiSettingsBlock;
extern WiFiSettingsBlock wificonf;
void wifimgr_restore_defaults(void);
void wifimgr_apply_settings(void);
#endif //ESP_VT100_FIRMWARE_WIFI_MANAGER_H
Loading…
Cancel
Save