From e617b4f2832f8366f9dbd966c3c1b6165673e0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 9 Jul 2017 00:41:08 +0200 Subject: [PATCH] implemented "wifi manager" and many advanced wifi config options. todo unification and persistence --- CMakeLists.txt | 6 +- html_orig/_start.php | 144 +++++++++ html_orig/jssrc/wifi.js | 6 +- html_orig/wifi.html | 8 +- libesphttpd | 2 +- user/cgi_wifi.c | 654 ++++++++++++++++++++++++++++++++++++++++ user/cgi_wifi.h | 33 ++ user/routes.c | 7 +- user/user_main.c | 67 ++-- user/wifi_manager.c | 194 ++++++++++++ user/wifi_manager.h | 47 +++ 11 files changed, 1129 insertions(+), 39 deletions(-) create mode 100644 html_orig/_start.php create mode 100644 user/cgi_wifi.c create mode 100644 user/cgi_wifi.h create mode 100644 user/wifi_manager.c create mode 100644 user/wifi_manager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ee1d441..3d9277e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,6 @@ set(SOURCE_FILES libesphttpd/include/espmissingincludes.h libesphttpd/include/espfs.h libesphttpd/include/esp8266.h - libesphttpd/include/cgiwifi.h libesphttpd/include/cgiwebsocket.h libesphttpd/include/cgiflash.h libesphttpd/include/captdns.h @@ -54,7 +53,6 @@ set(SOURCE_FILES libesphttpd/lib/heatshrink/heatshrink_decoder.c libesphttpd/lib/heatshrink/heatshrink.c libesphttpd/mkupgimg/mkupgimg.c - libesphttpd/util/cgiwifi.c libesphttpd/util/cgiwebsocket.c libesphttpd/util/cgiflash.c libesphttpd/util/captdns.c @@ -93,6 +91,8 @@ set(SOURCE_FILES include/ets_sys_extra.h user/io.c user/io.h + user/cgi_wifi.c + user/cgi_wifi.h user/cgi_ping.c user/cgi_reset.c user/uart_driver.c @@ -113,7 +113,7 @@ set(SOURCE_FILES user/cgi_sockets.h user/ansi_parser_callbacks.c user/ansi_parser_callbacks.h - user/user_main.h) + user/user_main.h user/wifi_manager.c user/wifi_manager.h) include_directories(include) include_directories(user) diff --git a/html_orig/_start.php b/html_orig/_start.php new file mode 100644 index 0000000..31bab8a --- /dev/null +++ b/html_orig/_start.php @@ -0,0 +1,144 @@ + [ $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); +} + +?> + + + + + + + <?= e($menu[$page][1]) ?> - <?= e($appname) ?> + + + + + + +
+ +
+ Loading… + + + + + + + + + + WiFi Settings - ESP8266 Remote Terminal + + + + + + +Loading… + +

WiFi settings

+ +
+ + + + + + + + + + + + + + + + + + + + + + +
WiFi mode%WiFiMode%
IP%StaIP%
Switch to
+
+ +
+
+
+ +
+
+

Some changes require a reboot, dropping connection. It can take a while to re-connect.

+

+ If you lose access, 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. +

+

+
+ +
+

Select AP to join

+
Scanning.
+
Can't scan in AP-only mode.
+ +
+ + + + + + + + diff --git a/html_orig/jssrc/wifi.js b/html_orig/jssrc/wifi.js index dd7f370..9fcf4c0 100644 --- a/html_orig/jssrc/wifi.js +++ b/html_orig/jssrc/wifi.js @@ -118,9 +118,9 @@ } $('#modeswitch').html([ - 'Client+AP AP only', - 'Client+AP', - 'Client only AP only' + 'Client+AP AP only', + 'Client+AP', + 'Client only AP only' ][obj.mode-1]); }; diff --git a/html_orig/wifi.html b/html_orig/wifi.html index 32ee1a8..e59f965 100644 --- a/html_orig/wifi.html +++ b/html_orig/wifi.html @@ -32,8 +32,8 @@ -
-
@@ -41,8 +41,8 @@ -
-
diff --git a/libesphttpd b/libesphttpd index 03003ea..38c6c91 160000 --- a/libesphttpd +++ b/libesphttpd @@ -1 +1 @@ -Subproject commit 03003ea591a272df50159ba52f84ca84c5cad78e +Subproject commit 38c6c91f50e5a5cfba8df8309a95e814695accba diff --git a/user/cgi_wifi.c b/user/cgi_wifi.c new file mode 100644 index 0000000..11a9291 --- /dev/null +++ b/user/cgi_wifi.c @@ -0,0 +1,654 @@ +/* +Cgi/template routines for the /wifi url. +*/ + +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Jeroen Domburg 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 + */ + +// 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 +#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, "Enable client for scanning."); + } + else if (x == STATIONAP_MODE) { // 3 + strcpy(buff, + "Switch: Client only, AP only"); + } + else { // 1 + strcpy(buff, + "Switch: Client+AP, AP only"); + } + } + httpdSend(connData, buff, -1); + return HTTPD_CGI_DONE; +} diff --git a/user/cgi_wifi.h b/user/cgi_wifi.h new file mode 100644 index 0000000..a300d2a --- /dev/null +++ b/user/cgi_wifi.h @@ -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 diff --git a/user/routes.c b/user/routes.c index 7c77313..2f3dc68 100644 --- a/user/routes.c +++ b/user/routes.c @@ -2,11 +2,10 @@ #include #include #include -#include #include #include "routes.h" - +#include "cgi_wifi.h" #include "cgi_reset.h" #include "cgi_ping.h" #include "cgi_main.h" @@ -48,9 +47,7 @@ HttpdBuiltInUrl routes[] = { ROUTE_CGI("/wifi/connect", cgiWiFiConnect), ROUTE_CGI("/wifi/connstatus", cgiWiFiConnStatus), ROUTE_FILE("/wifi/connecting", "/wifi_conn.tpl"), - ROUTE_CGI("/wifi/setmode", cgiWiFiSetMode), - ROUTE_CGI("/wifi/setchannel", cgiWiFiSetChannel), - ROUTE_CGI("/wifi/setname", cgiWiFiSetSSID), + ROUTE_CGI("/wifi/set", cgiWiFiSetParams), ROUTE_FILESYSTEM(), ROUTE_END(), diff --git a/user/user_main.c b/user/user_main.c index 95364d4..0990b41 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -26,6 +26,7 @@ #include "user_main.h" #include "uart_driver.h" #include "ansi_parser_callbacks.h" +#include "wifi_manager.h" #ifdef ESPFS_POS CgiUploadFlashDef uploadParams={ @@ -48,6 +49,7 @@ CgiUploadFlashDef uploadParams={ #endif static ETSTimer prHeapTimer; +static ETSTimer userStartTimer; /** Periodically show heap usage */ static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) @@ -77,11 +79,49 @@ static void ICACHE_FLASH_ATTR prHeapTimerCb(void *arg) 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. void ICACHE_FLASH_ATTR user_init(void) { 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"); banner("====== ESP8266 Remote Terminal ======"); banner_info("Firmware (c) Ondrej Hruska, 2017"); @@ -94,15 +134,6 @@ void ICACHE_FLASH_ATTR user_init(void) ioInit(); - // Change AP name if AI-THINKER found (means un-initialized device) - struct softap_config apconf; - wifi_softap_get_config(&apconf); - if (strstarts((char*)apconf.ssid, "AI-THINKER")) { - warn("Un-initialized device, performing factory reset."); - apars_handle_OSC_FactoryReset(); - return; - } - // 0x40200000 is the base address for spi flash memory mapping, ESPFS_POS is the position // where image is written in flash that is defined in Makefile. #ifdef ESPFS_POS @@ -111,25 +142,15 @@ void ICACHE_FLASH_ATTR user_init(void) espFsInit((void *) (webpages_espfs_start)); #endif - // Captive portal - captdnsInit(); - - // Server - httpdInit(routes, 80); - // Heap use timer & blink os_timer_disarm(&prHeapTimer); os_timer_setfn(&prHeapTimer, prHeapTimerCb, NULL); os_timer_arm(&prHeapTimer, 1000, 1); - // 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!"); + // do later (some functions do not yet work if called from user_init) + os_timer_disarm(&userStartTimer); + os_timer_setfn(&userStartTimer, user_start, NULL); + os_timer_arm(&userStartTimer, 10, 0); } // ---- unused funcs removed from sdk to save space --- diff --git a/user/wifi_manager.c b/user/wifi_manager.c new file mode 100644 index 0000000..d19a6dc --- /dev/null +++ b/user/wifi_manager.c @@ -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(); + } +} diff --git a/user/wifi_manager.h b/user/wifi_manager.h new file mode 100644 index 0000000..daf09e3 --- /dev/null +++ b/user/wifi_manager.h @@ -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 +#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