/* 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 */ #include #include #include "cgi_wifi.h" #include "wifimgr.h" #include "persist.h" #include "helpers.h" #include "config_xmacros.h" #include "cgi_logging.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; } /** * 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; cgi_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; cgi_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); } /** * Start a scan and return a result of an earlier scan, if available. * The STA is switched ON if disabled. */ httpd_cgi_state ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { int pos = (int) connData->cgiData; int len; char buff[256]; if (connData->conn == NULL) { //Connection aborted. Clean up. return HTTPD_CGI_DONE; } // auto-turn on STA if ((wificonf->opmode & STATION_MODE) == 0) { wificonf->opmode |= STATION_MODE; wifimgr_apply_settings(); } // 2nd and following runs of the function via MORE: if (!cgiWifiAps.scanInProgress && pos != 0) { // Fill in json code for an access point 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; buff[0] = 0; // avoid unitialized read if (connData->conn == NULL) { //Connection aborted. Clean up. return HTTPD_CGI_DONE; } httpdStartResponse(connData, 200); httpdHeader(connData, "Content-Type", "application/json"); httpdEndHeaders(connData); // 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(); cgi_dbg("CONN STATE = %d", st); 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; default: sprintf(buff, "{\"status\": \"working\", \"wtf\": \"state = %d\"}", st); 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[100]; // this is just barely enough - but it's split into two forms, so we never have error in all fields 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; } WiFiConfigBundle *wificonf_backup = malloc(sizeof(WiFiConfigBundle)); WiFiConfChangeFlags *wcf_backup = malloc(sizeof(WiFiConfChangeFlags)); memcpy(wificonf_backup, wificonf, sizeof(WiFiConfigBundle)); memcpy(wcf_backup, &wifi_change_flags, sizeof(WiFiConfChangeFlags)); bool sta_turned_on = false; bool sta_ssid_pw_changed = false; #define XSTRUCT wificonf #define X XSET_CGI_FUNC XTABLE_WIFICONF #undef X // ---- WiFi opmode ---- // those are helpers, not a real prop if (GET_ARG("ap_enable")) { cgi_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")) { cgi_dbg("Enable STA: %s", buff); int enable = atoi(buff); if (enable) { wificonf->opmode |= STATION_MODE; sta_turned_on = true; } else { wificonf->opmode &= ~STATION_MODE; } } (void)redir_url; if (redir_url_buf[strlen(SET_REDIR_ERR)] == 0) { // All was OK cgi_info("Set WiFi params - success, applying in 2000 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, 2000, false); if ((sta_ssid_pw_changed || sta_turned_on) && wificonf->opmode != SOFTAP_MODE && wificonf->sta_ssid[0] != 0) { // User wants to connect cgi_info("User wants to connect to SSID, redirecting to ConnStatus page."); httpdRedirect(connData, "/cfg/wifi/connecting"); } else { httpdRedirect(connData, SET_REDIR_SUC "?msg=Settings%20saved%20and%20applied."); } } else { cgi_warn("Some WiFi settings did not validate, asking for correction"); memcpy(wificonf, wificonf_backup, sizeof(WiFiConfigBundle)); memcpy(&wifi_change_flags, wcf_backup, sizeof(WiFiConfChangeFlags)); // Some errors, appended to the URL as ?err= httpdRedirect(connData, redir_url_buf); } free(wificonf_backup); free(wcf_backup); 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[PASSWORD_LEN]; int x; int connectStatus; if (token == NULL) { // We're done return HTTPD_CGI_DONE; } strcpy(buff, ""); // fallback #define XSTRUCT wificonf #define X XGET_CGI_FUNC XTABLE_WIFICONF #undef X // non-config 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, "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 || wificonf->opmode == SOFTAP_MODE) { strcpy(buff, ""); } else { struct station_config staconf; wifi_station_get_config(&staconf); strcpy(buff, (char *) staconf.ssid); } } else if (streq(token, "sta_active_ip")) { getStaIpAsString(buff); } tplSend(connData, buff, -1); return HTTPD_CGI_DONE; }