diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 2801babc1..53c0d5290 100755 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -40,21 +40,39 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { linked_remote[12] = '\0'; #endif - JsonObject nw_ins_0 = nw["ins"][0]; - getStringFromJson(clientSSID, nw_ins_0[F("ssid")], 33); - //int nw_ins_0_pskl = nw_ins_0[F("pskl")]; - //The WiFi PSK is normally not contained in the regular file for security reasons. - //If it is present however, we will use it - getStringFromJson(clientPass, nw_ins_0["psk"], 65); + size_t n = 0; + JsonArray nw_ins = nw["ins"]; + if (!nw_ins.isNull()) { + // as password are stored separately in wsec.json when reading configuration vector resize happens there, but for dynamic config we need to resize if necessary + if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing + for (JsonObject wifi : nw_ins) { + JsonArray ip = wifi["ip"]; + JsonArray gw = wifi["gw"]; + JsonArray sn = wifi["sn"]; + char ssid[33] = ""; + char pass[65] = ""; + IPAddress nIP = (uint32_t)0U, nGW = (uint32_t)0U, nSN = (uint32_t)0x00FFFFFF; // little endian + getStringFromJson(ssid, wifi[F("ssid")], 33); + getStringFromJson(pass, wifi["psk"], 65); // password is not normally present but if it is, use it + for (size_t i = 0; i < 4; i++) { + CJSON(nIP[i], ip[i]); + CJSON(nGW[i], gw[i]); + CJSON(nSN[i], sn[i]); + } + if (strlen(ssid) > 0) strlcpy(multiWiFi[n].clientSSID, ssid, 33); // this will keep old SSID intact if not present in JSON + if (strlen(pass) > 0) strlcpy(multiWiFi[n].clientPass, pass, 65); // this will keep old password intact if not present in JSON + multiWiFi[n].staticIP = nIP; + multiWiFi[n].staticGW = nGW; + multiWiFi[n].staticSN = nSN; + if (++n >= WLED_MAX_WIFI_COUNT) break; + } + } - JsonArray nw_ins_0_ip = nw_ins_0["ip"]; - JsonArray nw_ins_0_gw = nw_ins_0["gw"]; - JsonArray nw_ins_0_sn = nw_ins_0["sn"]; - - for (byte i = 0; i < 4; i++) { - CJSON(staticIP[i], nw_ins_0_ip[i]); - CJSON(staticGateway[i], nw_ins_0_gw[i]); - CJSON(staticSubnet[i], nw_ins_0_sn[i]); + JsonArray dns = nw[F("dns")]; + if (!dns.isNull()) { + for (size_t i = 0; i < 4; i++) { + CJSON(dnsAddress[i], dns[i]); + } } JsonObject ap = doc["ap"]; @@ -212,7 +230,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject btn_obj = hw["btn"]; bool pull = btn_obj[F("pull")] | (!disablePullUp); // if true, pullup is enabled disablePullUp = !pull; - JsonArray hw_btn_ins = btn_obj[F("ins")]; + JsonArray hw_btn_ins = btn_obj["ins"]; if (!hw_btn_ins.isNull()) { for (uint8_t b = 0; b < WLED_MAX_BUTTONS; b++) { // deallocate existing button pins pinManager.deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button @@ -433,7 +451,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (e131Port == DDP_DEFAULT_PORT) e131Port = E131_DEFAULT_PORT; // prevent double DDP port allocation CJSON(e131Multicast, if_live[F("mc")]); - JsonObject if_live_dmx = if_live[F("dmx")]; + JsonObject if_live_dmx = if_live["dmx"]; CJSON(e131Universe, if_live_dmx[F("uni")]); CJSON(e131SkipOutOfSequence, if_live_dmx[F("seqskip")]); CJSON(DMXAddress, if_live_dmx[F("addr")]); @@ -666,19 +684,23 @@ void serializeConfig() { #endif JsonArray nw_ins = nw.createNestedArray("ins"); + for (size_t n = 0; n < multiWiFi.size(); n++) { + JsonObject wifi = nw_ins.createNestedObject(); + wifi[F("ssid")] = multiWiFi[n].clientSSID; + wifi[F("pskl")] = strlen(multiWiFi[n].clientPass); + JsonArray wifi_ip = wifi.createNestedArray("ip"); + JsonArray wifi_gw = wifi.createNestedArray("gw"); + JsonArray wifi_sn = wifi.createNestedArray("sn"); + for (size_t i = 0; i < 4; i++) { + wifi_ip.add(multiWiFi[n].staticIP[i]); + wifi_gw.add(multiWiFi[n].staticGW[i]); + wifi_sn.add(multiWiFi[n].staticSN[i]); + } + } - JsonObject nw_ins_0 = nw_ins.createNestedObject(); - nw_ins_0[F("ssid")] = clientSSID; - nw_ins_0[F("pskl")] = strlen(clientPass); - - JsonArray nw_ins_0_ip = nw_ins_0.createNestedArray("ip"); - JsonArray nw_ins_0_gw = nw_ins_0.createNestedArray("gw"); - JsonArray nw_ins_0_sn = nw_ins_0.createNestedArray("sn"); - - for (byte i = 0; i < 4; i++) { - nw_ins_0_ip.add(staticIP[i]); - nw_ins_0_gw.add(staticGateway[i]); - nw_ins_0_sn.add(staticSubnet[i]); + JsonArray dns = nw.createNestedArray(F("dns")); + for (size_t i = 0; i < 4; i++) { + dns.add(dnsAddress[i]); } JsonObject ap = root.createNestedObject("ap"); @@ -694,7 +716,7 @@ void serializeConfig() { ap_ip.add(2); ap_ip.add(1); - JsonObject wifi = root.createNestedObject("wifi"); + JsonObject wifi = root.createNestedObject(F("wifi")); wifi[F("sleep")] = !noWifiSleep; wifi[F("phy")] = force802_3g; @@ -722,7 +744,7 @@ void serializeConfig() { } #endif - JsonObject hw = root.createNestedObject("hw"); + JsonObject hw = root.createNestedObject(F("hw")); JsonObject hw_led = hw.createNestedObject("led"); hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL @@ -1050,8 +1072,17 @@ bool deserializeConfigSec() { JsonObject root = pDoc->as(); - JsonObject nw_ins_0 = root["nw"]["ins"][0]; - getStringFromJson(clientPass, nw_ins_0["psk"], 65); + size_t n = 0; + JsonArray nw_ins = root["nw"]["ins"]; + if (!nw_ins.isNull()) { + if (nw_ins.size() > 1 && nw_ins.size() > multiWiFi.size()) multiWiFi.resize(nw_ins.size()); // resize constructs objects while resizing + for (JsonObject wifi : nw_ins) { + char pw[65] = ""; + getStringFromJson(pw, wifi["psk"], 65); + strlcpy(multiWiFi[n].clientPass, pw, 65); + if (++n >= WLED_MAX_WIFI_COUNT) break; + } + } JsonObject ap = root["ap"]; getStringFromJson(apPass, ap["psk"] , 65); @@ -1090,9 +1121,10 @@ void serializeConfigSec() { JsonObject nw = root.createNestedObject("nw"); JsonArray nw_ins = nw.createNestedArray("ins"); - - JsonObject nw_ins_0 = nw_ins.createNestedObject(); - nw_ins_0["psk"] = clientPass; + for (size_t i = 0; i < multiWiFi.size(); i++) { + JsonObject wifi = nw_ins.createNestedObject(); + wifi[F("psk")] = multiWiFi[i].clientPass; + } JsonObject ap = root.createNestedObject("ap"); ap["psk"] = apPass; diff --git a/wled00/const.h b/wled00/const.h index b8b71cafa..2ddfb858d 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -15,6 +15,10 @@ #define DEFAULT_MDNS_NAME "x" //increase if you need more +#ifndef WLED_MAX_WIFI_COUNT + #define WLED_MAX_WIFI_COUNT 3 +#endif + #ifndef WLED_MAX_USERMODS #ifdef ESP8266 #define WLED_MAX_USERMODS 4 diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 45a119bc9..63e17a597 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -8,7 +8,7 @@ var d = document; var loc = false, locip, locproto = "http:"; var scanLoops = 0, preScanSSID = ""; - + var maxNetworks = 3; function gId(e) { return d.getElementById(e); } function cE(e) { return d.createElement(e); } function toggle(el){gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide");} @@ -52,13 +52,14 @@ } scanLoops = 0; - let cs = gId("CS"); - if (cs) { + let cs = d.querySelectorAll("#wifi_entries input[type=text]"); + for (let input of (cs||[])) { + let found = false; let select = cE("select"); - select.setAttribute("id", "CS"); - select.setAttribute("name", "CS"); - select.setAttribute("onchange", "T()"); - preScanSSID = cs.value; + select.id = input.id; + select.name = input.name; + select.setAttribute("onchange", "T(this)"); + preScanSSID = input.value; for (let i = 0; i < select.children.length; i++) { select.removeChild(select.children[i]); @@ -70,8 +71,9 @@ option.setAttribute("value", networks[i].ssid); option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`; - if (networks[i].ssid === cs.value) { + if (networks[i].ssid === input.value) { option.setAttribute("selected", "selected"); + found = true; } select.appendChild(option); @@ -79,10 +81,11 @@ const option = cE("option"); option.setAttribute("value", "!Cs"); - option.textContent = `Other network...`; + option.textContent = "Other network..."; select.appendChild(option); - cs.replaceWith(select); + if (input.value === "" || found) input.replaceWith(select); + else select.remove(); } button.disabled = false; @@ -90,17 +93,48 @@ }); } // replace WiFi select with custom SSID input field again - function T() { - let cs = gId("CS"); + function T(cs) { if (!cs || cs.value != "!Cs") return; let input = cE("input"); input.type = "text"; - input.id = "CS"; - input.name ="CS"; + input.id = cs.id; + input.name = cs.name; input.setAttribute("maxlength",32); input.value = preScanSSID; cs.replaceWith(input); } + function resetWiFi(maxN = undefined) { + if (maxN) maxNetworks = maxN; + let entries = gId("wifi_entries").children + for (let i = entries.length; i > 0; i--) entries[i-1].remove(); + btnWiFi(0); + } + function btnWiFi(i) { + gId("wifi_add").style.display = (i1) ? "inline":"none"; + } + function addWiFi(ssid="",pass="",ip=0,gw=0,sn=0x00ffffff) { // little endian + var i = gId("wifi_entries").childNodes.length; + if (i >= maxNetworks) return; + var b = `

+Network name (SSID${i==0?", empty to not connect":""}):
0?"required":""}>
+Network password:

+Static IP (leave at 0.0.0.0 for DHCP)${i==0?"
Also used by Ethernet":""}:
+...
+Static gateway:
+...
+Static subnet mask:
+...
`; + gId("wifi_entries").insertAdjacentHTML("beforeend", b); + btnWiFi(i+1); + } + function remWiFi() { + const entries = gId("wifi_entries").children; + const i = entries.length; + if (i < 2) return; + entries[i-1].remove(); + btnWiFi(i-1); + } // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript function loadJS(FILE_URL, async = true) { let scE = cE("script"); @@ -158,24 +192,16 @@

WiFi setup

Connect to existing network


- Network name (SSID, empty to not connect):
-
- Network password:

- Static IP (leave at 0.0.0.0 for DHCP):
- . - . - . -
- Static gateway:
- . - . - . -
- Static subnet mask:
- . - . - . -
+
+ Wireless networks +
+
+ +
+
+ DNS server address:
+ ...
+
mDNS address (leave empty for no mDNS):
http:// .local
Client IP: Not connected
diff --git a/wled00/data/style.css b/wled00/data/style.css index 52b26a825..b6cb0f9e6 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -81,6 +81,7 @@ input:disabled { } input[type="text"], input[type="number"], +input[type="password"], select { font-size: medium; margin: 2px; diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 68c7ca5a5..c9fe350be 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -490,7 +490,7 @@ void prepareArtnetPollReply(ArtPollReply *reply) { // Node is DHCP capable // Node supports 15 bit Port-Address (Art-Net 3 or 4) // Node is able to switch between ArtNet and sACN - reply->reply_status_2 = (staticIP[0] == 0) ? 0x1F : 0x1D; + reply->reply_status_2 = (multiWiFi[0].staticIP[0] == 0) ? 0x1F : 0x1D; // RDM is disabled // Output style is continuous diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index e256ceb5f..bb24725d9 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -48,6 +48,21 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau return true; } +typedef struct WiFiConfig { + char clientSSID[33]; + char clientPass[65]; + IPAddress staticIP; + IPAddress staticGW; + IPAddress staticSN; + WiFiConfig(const char *ssid="", const char *pass="", uint32_t ip=0, uint32_t gw=0, uint32_t subnet=0x00FFFFFF) // little endian + : staticIP(ip) + , staticGW(gw) + , staticSN(subnet) + { + strncpy(clientSSID, ssid, 32); clientSSID[32] = 0; + strncpy(clientPass, pass, 64); clientPass[64] = 0; + } +} wifi_config; //colors.cpp // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) diff --git a/wled00/improv.cpp b/wled00/improv.cpp index 695d07ff7..f7867117f 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -259,14 +259,14 @@ void parseWiFiCommand(char* rpcData) { uint8_t ssidLen = rpcData[1]; if (ssidLen > len -1 || ssidLen > 32) return; - memset(clientSSID, 0, 32); - memcpy(clientSSID, rpcData+2, ssidLen); + memset(multiWiFi[0].clientSSID, 0, 32); + memcpy(multiWiFi[0].clientSSID, rpcData+2, ssidLen); - memset(clientPass, 0, 64); + memset(multiWiFi[0].clientPass, 0, 64); if (len > ssidLen +1) { uint8_t passLen = rpcData[2+ssidLen]; - memset(clientPass, 0, 64); - memcpy(clientPass, rpcData+3+ssidLen, passLen); + memset(multiWiFi[0].clientPass, 0, 64); + memcpy(multiWiFi[0].clientPass, rpcData+3+ssidLen, passLen); } sendImprovStateResponse(0x03); //provisioning diff --git a/wled00/network.cpp b/wled00/network.cpp index 3c7450a2a..2ae38f799 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -173,8 +173,8 @@ void WiFiEvent(WiFiEvent_t event) if (!apActive) { WiFi.disconnect(true); } - if (staticIP != (uint32_t)0x00000000 && staticGateway != (uint32_t)0x00000000) { - ETH.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); + if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) { + ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress); } else { ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); } diff --git a/wled00/set.cpp b/wled00/set.cpp index d3bb97443..49a54ab25 100755 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -19,21 +19,52 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) //WIFI SETTINGS if (subPage == SUBPAGE_WIFI) { - char oldSSID[sizeof(clientSSID)]; + unsigned cnt = 0; + for (size_t n = 0; n < WLED_MAX_WIFI_COUNT; n++) { + char cs[4] = "CS"; cs[2] = 48+n; cs[3] = 0; //client SSID + char pw[4] = "PW"; pw[2] = 48+n; pw[3] = 0; //client password + char ip[5] = "IP"; ip[2] = 48+n; ip[4] = 0; //IP address + char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address + char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask + if (request->hasArg(cs)) { + if (n >= multiWiFi.size()) multiWiFi.push_back(WiFiConfig()); // expand vector by one + char oldSSID[33]; strcpy(oldSSID, multiWiFi[n].clientSSID); + char oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass); - strcpy(oldSSID, clientSSID); - strlcpy(clientSSID,request->arg(F("CS")).c_str(), 33); - if (!strcmp(oldSSID, clientSSID)) forceReconnect = true; + strlcpy(multiWiFi[n].clientSSID, request->arg(cs).c_str(), 33); + if (strlen(oldSSID) == 0 || !strncmp(multiWiFi[n].clientSSID, oldSSID, 32)) { + forceReconnect = true; + } + if (!isAsterisksOnly(request->arg(pw).c_str(), 65)) { + strlcpy(multiWiFi[n].clientPass, request->arg(pw).c_str(), 65); + forceReconnect = true; + } + for (size_t i = 0; i < 4; i++) { + ip[3] = 48+i; + gw[3] = 48+i; + sn[3] = 48+i; + multiWiFi[n].staticIP[i] = request->arg(ip).toInt(); + multiWiFi[n].staticGW[i] = request->arg(gw).toInt(); + multiWiFi[n].staticSN[i] = request->arg(sn).toInt(); + } + cnt++; + } + } + // remove unused + if (cnt < multiWiFi.size()) { + cnt = multiWiFi.size() - cnt; + while (cnt--) multiWiFi.pop_back(); + multiWiFi.shrink_to_fit(); // release memory + } - if (!isAsterisksOnly(request->arg(F("CP")).c_str(), 65)) { - strlcpy(clientPass, request->arg(F("CP")).c_str(), 65); - forceReconnect = true; + if (request->hasArg(F("D0"))) { + dnsAddress = IPAddress(request->arg(F("D0")).toInt(),request->arg(F("D1")).toInt(),request->arg(F("D2")).toInt(),request->arg(F("D3")).toInt()); } strlcpy(cmDNS, request->arg(F("CM")).c_str(), 33); apBehavior = request->arg(F("AB")).toInt(); - strcpy(oldSSID, apSSID); + char oldSSID[33]; strcpy(oldSSID, apSSID); strlcpy(apSSID, request->arg(F("AS")).c_str(), 33); if (!strcmp(oldSSID, apSSID) && apActive) forceReconnect = true; apHide = request->hasArg(F("AH")); @@ -61,21 +92,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) ethernetType = request->arg(F("ETH")).toInt(); WLED::instance().initEthernet(); #endif - - char k[3]; k[2] = 0; - for (int i = 0; i<4; i++) - { - k[1] = i+48;//ascii 0,1,2,3 - - k[0] = 'I'; //static IP - staticIP[i] = request->arg(k).toInt(); - - k[0] = 'G'; //gateway - staticGateway[i] = request->arg(k).toInt(); - - k[0] = 'S'; //subnet - staticSubnet[i] = request->arg(k).toInt(); - } } //LED SETTINGS diff --git a/wled00/wled.cpp b/wled00/wled.cpp index f5fe63ad3..cc42e9dd5 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -441,6 +441,7 @@ void WLED::setup() escapedMac.toLowerCase(); WLED_SET_AP_SSID(); // otherwise it is empty on first boot until config is saved + multiWiFi.push_back(WiFiConfig(CLIENT_SSID,CLIENT_PASS)); // initialise vector with default WiFi DEBUG_PRINTLN(F("Reading config")); deserializeConfigFromFS(); @@ -462,13 +463,16 @@ void WLED::setup() usermods.setup(); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); - if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) + if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0) showWelcomePage = true; WiFi.persistent(false); #ifdef WLED_USE_ETHERNET WiFi.onEvent(WiFiEvent); #endif + WiFi.mode(WIFI_STA); // enable scanning + findWiFi(true); // start scanning for available WiFi-s + #ifdef WLED_ENABLE_ADALIGHT //Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused //Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused @@ -714,11 +718,52 @@ bool WLED::initEthernet() #else return false; // Ethernet not enabled for build #endif +} +// performs asynchronous scan for available networks (which may take couple of seconds to finish) +// returns configured WiFi ID with the strongest signal (or default if no configured networks available) +int8_t WLED::findWiFi(bool doScan) { + if (multiWiFi.size() <= 1) { + DEBUG_PRINTLN(F("Defaulf WiFi used.")); + return 0; + } + + if (doScan) WiFi.scanDelete(); // restart scan + + int status = WiFi.scanComplete(); // complete scan may take as much as several seconds (usually <3s with not very crowded air) + + if (status == WIFI_SCAN_FAILED) { + DEBUG_PRINTLN(F("WiFi scan started.")); + WiFi.scanNetworks(true); // start scanning in asynchronous mode + } else if (status >= 0) { // status contains number of found networks + DEBUG_PRINT(F("WiFi scan completed: ")); DEBUG_PRINTLN(status); + int rssi = -9999; + int selected = selectedWiFi; + for (int o = 0; o < status; o++) { + DEBUG_PRINT(F(" WiFi available: ")); DEBUG_PRINT(WiFi.SSID(o)); + DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(WiFi.RSSI(o)); DEBUG_PRINTLN(F("dB")); + for (unsigned n = 0; n < multiWiFi.size(); n++) + if (!strcmp(WiFi.SSID(o).c_str(), multiWiFi[n].clientSSID)) { + // find the WiFi with the strongest signal (but keep priority of entry if signal difference is not big) + if ((n < selected && WiFi.RSSI(o) > rssi-10) || WiFi.RSSI(o) > rssi) { + rssi = WiFi.RSSI(o); + selected = n; + } + break; + } + } + DEBUG_PRINT(F("Selected: ")); DEBUG_PRINT(multiWiFi[selected].clientSSID); + DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(rssi); DEBUG_PRINTLN(F("dB")); + return selected; + } + //DEBUG_PRINT(F("WiFi scan running.")); + return status; // scan is still running or there was an error } void WLED::initConnection() { + DEBUG_PRINTLN(F("initConnection() called.")); + #ifdef WLED_ENABLE_WEBSOCKETS ws.onEvent(wsEvent); #endif @@ -731,13 +776,13 @@ void WLED::initConnection() } #endif - WiFi.disconnect(true); // close old connections + WiFi.disconnect(true); // close old connections #ifdef ESP8266 WiFi.setPhyMode(force802_3g ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N); #endif - if (staticIP[0] != 0 && staticGateway[0] != 0) { - WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(1, 1, 1, 1)); + if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) { + WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress); } else { WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0)); } @@ -762,13 +807,14 @@ void WLED::initConnection() showWelcomePage = false; DEBUG_PRINT(F("Connecting to ")); - DEBUG_PRINT(clientSSID); + DEBUG_PRINT(multiWiFi[selectedWiFi].clientSSID); DEBUG_PRINTLN("..."); // convert the "serverDescription" into a valid DNS hostname (alphanumeric) char hostname[25]; prepareHostname(hostname); - WiFi.begin(clientSSID, clientPass); + WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass); // no harm if called multiple times + #ifdef ARDUINO_ARCH_ESP32 #if defined(LOLIN_WIFI_FIX) && (defined(ARDUINO_ARCH_ESP32C3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32S3)) WiFi.setTxPower(WIFI_POWER_8_5dBm); @@ -860,14 +906,19 @@ void WLED::initInterfaces() void WLED::handleConnection() { + static bool scanDone = true; static byte stacO = 0; unsigned long now = millis(); + const bool wifiConfigured = WLED_WIFI_CONFIGURED; - if (now < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) + // ignore connection handling if WiFi is configured and scan still running + // or within first 2s if WiFi is not configured or AP is always active + if ((wifiConfigured && multiWiFi.size() > 1 && WiFi.scanComplete() < 0) || (now < 2000 && (!wifiConfigured || apBehavior == AP_BEHAVIOR_ALWAYS))) return; if (lastReconnectAttempt == 0 || forceReconnect) { DEBUG_PRINTLN(F("Initial connect or forced reconnect.")); + selectedWiFi = findWiFi(); // find strongest WiFi initConnection(); interfacesInited = false; forceReconnect = false; @@ -888,7 +939,7 @@ void WLED::handleConnection() stacO = stac; DEBUG_PRINT(F("Connected AP clients: ")); DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { // trying to connect, but not connected + if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected if (stac) WiFi.disconnect(); // disable search so that AP can work else @@ -896,20 +947,30 @@ void WLED::handleConnection() } } } + if (!Network.isConnected()) { if (interfacesInited) { + if (scanDone && multiWiFi.size() > 1) { + DEBUG_PRINTLN(F("WiFi scan initiated on disconnect.")); + findWiFi(true); // reinit scan + scanDone = false; + return; // try to connect in next iteration + } DEBUG_PRINTLN(F("Disconnected!")); + selectedWiFi = findWiFi(); initConnection(); interfacesInited = false; + scanDone = true; } //send improv failed 6 seconds after second init attempt (24 sec. after provisioning) if (improvActive > 2 && now - lastReconnectAttempt > 6000) { sendImprovStateResponse(0x03, true); improvActive = 2; } - if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && WLED_WIFI_CONFIGURED) { + if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && wifiConfigured) { if (improvActive == 2) improvActive = 3; DEBUG_PRINTLN(F("Last reconnect too old.")); + if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list initConnection(); } if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { @@ -928,7 +989,7 @@ void WLED::handleConnection() } } } else if (!interfacesInited) { //newly connected - DEBUG_PRINTLN(""); + DEBUG_PRINTLN(); DEBUG_PRINT(F("Connected! IP address: ")); DEBUG_PRINTLN(Network.localIP()); if (improvActive) { diff --git a/wled00/wled.h b/wled00/wled.h index 507c664d2..f9027c4f9 100755 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -304,22 +304,21 @@ WLED_GLOBAL int8_t irPin _INIT(IRPIN); WLED_GLOBAL char ntpServerName[33] _INIT("0.wled.pool.ntp.org"); // NTP server to use // WiFi CONFIG (all these can be changed via web UI, no need to set them here) -WLED_GLOBAL char clientSSID[33] _INIT(CLIENT_SSID); -WLED_GLOBAL char clientPass[65] _INIT(CLIENT_PASS); -WLED_GLOBAL char cmDNS[33] _INIT(MDNS_NAME); // mDNS address (*.local, replaced by wledXXXXXX if default is used) -WLED_GLOBAL char apSSID[33] _INIT(""); // AP off by default (unless setup) -WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13) -WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID -WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default -WLED_GLOBAL IPAddress staticIP _INIT_N((( 0, 0, 0, 0))); // static IP of ESP -WLED_GLOBAL IPAddress staticGateway _INIT_N((( 0, 0, 0, 0))); // gateway (router) IP -WLED_GLOBAL IPAddress staticSubnet _INIT_N(((255, 255, 255, 0))); // most common subnet in home networks +WLED_GLOBAL uint8_t selectedWiFi _INIT(0); +WLED_GLOBAL std::vector multiWiFi; +WLED_GLOBAL IPAddress dnsAddress _INIT_N((( 8, 8, 8, 8))); // Google's DNS +WLED_GLOBAL char cmDNS[33] _INIT(MDNS_NAME); // mDNS address (*.local, replaced by wledXXXXXX if default is used) +WLED_GLOBAL char apSSID[33] _INIT(""); // AP off by default (unless setup) +WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13) +WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID +WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default #ifdef ARDUINO_ARCH_ESP32 -WLED_GLOBAL bool noWifiSleep _INIT(true); // disabling modem sleep modes will increase heat output and power usage, but may help with connection issues +WLED_GLOBAL bool noWifiSleep _INIT(true); // disabling modem sleep modes will increase heat output and power usage, but may help with connection issues #else WLED_GLOBAL bool noWifiSleep _INIT(false); #endif WLED_GLOBAL bool force802_3g _INIT(false); +#define WLED_WIFI_CONFIGURED (strlen(multiWiFi[0].clientSSID) >= 1 && strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) != 0) #ifdef WLED_USE_ETHERNET #ifdef WLED_ETH_DEFAULT // default ethernet board type if specified @@ -834,7 +833,6 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); #else #define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) #endif -#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) #ifndef WLED_AP_SSID_UNIQUE #define WLED_SET_AP_SSID() do { \ @@ -884,6 +882,7 @@ public: void initAP(bool resetAP = false); void initConnection(); void initInterfaces(); + int8_t findWiFi(bool doScan = false); #if defined(STATUSLED) void handleStatusLED(); #endif diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 9ea838797..89e5d65c4 100755 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -74,11 +74,11 @@ void loadSettingsFromEEPROM() int lastEEPROMversion = EEPROM.read(377); //last EEPROM version before update - readStringFromEEPROM( 0, clientSSID, 32); - readStringFromEEPROM( 32, clientPass, 64); - readStringFromEEPROM( 96, cmDNS, 32); - readStringFromEEPROM(128, apSSID, 32); - readStringFromEEPROM(160, apPass, 64); + readStringFromEEPROM( 0, multiWiFi[0].clientSSID, 32); + readStringFromEEPROM( 32, multiWiFi[0].clientPass, 64); + readStringFromEEPROM( 96, cmDNS, 32); + readStringFromEEPROM(128, apSSID, 32); + readStringFromEEPROM(160, apPass, 64); nightlightDelayMinsDefault = EEPROM.read(224); nightlightDelayMins = nightlightDelayMinsDefault; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index b583659f6..3c7ebd2c3 100755 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -248,23 +248,34 @@ void getSettingsJS(byte subPage, char* dest) if (subPage == SUBPAGE_WIFI) { - sappends('s',SET_F("CS"),clientSSID); - - byte l = strlen(clientPass); - char fpass[l+1]; //fill password field with *** - fpass[l] = 0; - memset(fpass,'*',l); - sappends('s',SET_F("CP"),fpass); - - char k[3]; k[2] = 0; //IP addresses - for (int i = 0; i<4; i++) - { - k[1] = 48+i; //ascii 0,1,2,3 - k[0] = 'I'; sappend('v',k,staticIP[i]); - k[0] = 'G'; sappend('v',k,staticGateway[i]); - k[0] = 'S'; sappend('v',k,staticSubnet[i]); + char nS[10]; + size_t l; + oappend(SET_F("resetWiFi(")); + oappend(itoa(WLED_MAX_WIFI_COUNT,nS,10)); + oappend(SET_F(");")); + for (size_t n = 0; n < multiWiFi.size(); n++) { + l = strlen(multiWiFi[n].clientPass); + char fpass[l+1]; //fill password field with *** + fpass[l] = 0; + memset(fpass,'*',l); + oappend(SET_F("addWiFi(\"")); + oappend(multiWiFi[n].clientSSID); + oappend(SET_F("\",\"")); + oappend(fpass); + oappend(SET_F("\",0x")); + oappend(itoa(multiWiFi[n].staticIP,nS,16)); + oappend(SET_F(",0x")); + oappend(itoa(multiWiFi[n].staticGW,nS,16)); + oappend(SET_F(",0x")); + oappend(itoa(multiWiFi[n].staticSN,nS,16)); + oappend(SET_F(");")); } + sappend('v',SET_F("D0"),dnsAddress[0]); + sappend('v',SET_F("D1"),dnsAddress[1]); + sappend('v',SET_F("D2"),dnsAddress[2]); + sappend('v',SET_F("D3"),dnsAddress[3]); + sappends('s',SET_F("CM"),cmDNS); sappend('i',SET_F("AB"),apBehavior); sappends('s',SET_F("AS"),apSSID);