262 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!DOCTYPE html>
 | |
| <html lang="en">
 | |
| <head>
 | |
|   <meta charset="utf-8">
 | |
| 	<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
 | |
| 	<title>WiFi Settings</title>
 | |
| 	<script>
 | |
| 		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");}
 | |
| 		function H(){window.open("https://kno.wled.ge/features/settings/#wifi-settings");}
 | |
| 		function B(){window.open(getURL("/settings"),"_self");}
 | |
| 		function N() {
 | |
| 			const button = gId("scan");
 | |
| 			button.disabled = true;
 | |
| 			button.textContent = "Scanning...";
 | |
| 
 | |
| 			fetch(getURL("/json/net")).then((response) => {
 | |
| 				return response.json();
 | |
| 			}).then((json) => {
 | |
| 				// Get the list of networks only, defaulting to an empty array.
 | |
| 				return Object.assign(
 | |
| 					{},
 | |
| 					{"networks": []},
 | |
| 					json,
 | |
| 				).networks.sort(
 | |
| 					// Sort by signal strength, descending.
 | |
| 					(a, b) => b.rssi - a.rssi
 | |
| 				).reduce(
 | |
| 					// Filter out duplicate SSIDs. Since it is sorted by signal
 | |
| 					// strength, the strongest signal will be kept in the
 | |
| 					// order it as originally appeared in the array.
 | |
| 					(unique, other) => {
 | |
| 						if(!unique.some(obj => obj.ssid === other.ssid)) {
 | |
| 							unique.push(other);
 | |
| 						}
 | |
| 						return unique;
 | |
| 					},
 | |
| 					[],
 | |
| 				);
 | |
| 			}).then((networks) => {
 | |
| 				// If there are no networks, fetch it again in a second.
 | |
| 				// but only do this a few times.
 | |
| 				if (networks.length === 0 && scanLoops < 10) {
 | |
| 					scanLoops++;
 | |
| 					setTimeout(N, 1000);
 | |
| 					return;
 | |
| 				}
 | |
| 				scanLoops = 0;
 | |
| 
 | |
| 				let cs = d.querySelectorAll("#wifi_entries input[type=text]");
 | |
| 				for (let input of (cs||[])) {
 | |
| 					let found = false;
 | |
| 					let select = cE("select");
 | |
| 					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]);
 | |
| 					}
 | |
| 
 | |
| 					for (let i = 0; i < networks.length; i++) {
 | |
| 						const option = cE("option");
 | |
| 
 | |
| 						option.setAttribute("value", networks[i].ssid);
 | |
| 						option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`;
 | |
| 
 | |
| 						if (networks[i].ssid === input.value) {
 | |
| 							option.setAttribute("selected", "selected");
 | |
| 							found = true;
 | |
| 						}
 | |
| 
 | |
| 						select.appendChild(option);
 | |
| 					}
 | |
| 					const option = cE("option");
 | |
| 
 | |
| 					option.setAttribute("value", "!Cs");
 | |
| 					option.textContent = "Other network...";
 | |
| 					select.appendChild(option);
 | |
| 
 | |
| 					if (input.value === "" || found) input.replaceWith(select);
 | |
| 					else select.remove(); 
 | |
| 				}
 | |
| 
 | |
| 				button.disabled = false;
 | |
| 				button.textContent = "Scan";
 | |
| 			});
 | |
| 		}
 | |
| 		// replace WiFi select with custom SSID input field again
 | |
| 		function T(cs) {
 | |
| 			if (!cs || cs.value != "!Cs") return;
 | |
| 			let input = cE("input");
 | |
| 			input.type = "text";
 | |
| 			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 = (i<maxNetworks) ? "inline":"none";
 | |
| 			gId("wifi_rem").style.display = (i>1) ? "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 = `<div id="net${i}"><hr class="sml">
 | |
| Network name (SSID${i==0?", empty to not connect":""}):<br><input type="text" id="CS${i}" name="CS${i}" maxlength="32" value="${ssid}" ${i>0?"required":""}><br>
 | |
| Network password:<br><input type="password" name="PW${i}" maxlength="64" value="${pass}"><br>
 | |
| Static IP (leave at 0.0.0.0 for DHCP)${i==0?"<br>Also used by Ethernet":""}:<br>
 | |
| <input name="IP${i}0" type="number" class="s" min="0" max="255" value="${ip&0xFF}" required>.<input name="IP${i}1" type="number" class="s" min="0" max="255" value="${(ip>>8)&0xFF}" required>.<input name="IP${i}2" type="number" class="s" min="0" max="255" value="${(ip>>16)&0xFF}" required>.<input name="IP${i}3" type="number" class="s" min="0" max="255" value="${(ip>>24)&0xFF}" required><br>
 | |
| Static gateway:<br>
 | |
| <input name="GW${i}0" type="number" class="s" min="0" max="255" value="${gw&0xFF}" required>.<input name="GW${i}1" type="number" class="s" min="0" max="255" value="${(gw>>8)&0xFF}" required>.<input name="GW${i}2" type="number" class="s" min="0" max="255" value="${(gw>>16)&0xFF}" required>.<input name="GW${i}3" type="number" class="s" min="0" max="255" value="${(gw>>24)&0xFF}" required><br>
 | |
| Static subnet mask:<br>
 | |
| <input name="SN${i}0" type="number" class="s" min="0" max="255" value="${sn&0xFF}" required>.<input name="SN${i}1" type="number" class="s" min="0" max="255" value="${(sn>>8)&0xFF}" required>.<input name="SN${i}2" type="number" class="s" min="0" max="255" value="${(sn>>16)&0xFF}" required>.<input name="SN${i}3" type="number" class="s" min="0" max="255" value="${(sn>>24)&0xFF}" required></div>`;
 | |
| 			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");
 | |
| 			scE.setAttribute("src", FILE_URL);
 | |
| 			scE.setAttribute("type", "text/javascript");
 | |
| 			scE.setAttribute("async", async);
 | |
| 			d.body.appendChild(scE);
 | |
| 			// success event
 | |
| 			scE.addEventListener("load", () => {
 | |
| 				//console.log("File loaded");
 | |
| 				GetV();
 | |
| 			});
 | |
| 			// error event
 | |
| 			scE.addEventListener("error", (ev) => {
 | |
| 				console.log("Error on loading file", ev);
 | |
| 				alert("Loading of configuration script failed.\nIncomplete page data!");
 | |
| 			});
 | |
| 		}
 | |
| 		function S() {
 | |
| 			let l = window.location;
 | |
| 			if (l.protocol == "file:") {
 | |
| 				loc = true;
 | |
| 				locip = localStorage.getItem('locIp');
 | |
| 				if (!locip) {
 | |
| 					locip = prompt("File Mode. Please enter WLED IP!");
 | |
| 					localStorage.setItem('locIp', locip);
 | |
| 				}
 | |
| 			} else {
 | |
| 				// detect reverse proxy
 | |
| 				let path = l.pathname;
 | |
| 				let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
 | |
| 				if (paths.length > 2) {
 | |
| 					paths.pop(); // remove "wifi"
 | |
| 					paths.pop(); // remove "settings"
 | |
| 					locproto = l.protocol;
 | |
| 					loc = true;
 | |
| 					locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths.join('/');
 | |
| 				}
 | |
| 			}
 | |
| 			loadJS(getURL('/settings/s.js?p=1'), false);	// If we set async false, file is loaded and executed, then next statement is processed
 | |
| 			if (loc) d.Sf.action = getURL('/settings/wifi');
 | |
| 		}
 | |
| 		function getURL(path) {
 | |
| 			return (loc ? locproto + "//" + locip : "") + path;
 | |
| 		}
 | |
| 	</script>
 | |
| 	<style>@import url("style.css");</style>
 | |
| </head>
 | |
| <body onload="S()">
 | |
| 	<form id="form_s" name="Sf" method="post">
 | |
| 		<div class="toprow">
 | |
| 		<div class="helpB"><button type="button" onclick="H()">?</button></div>
 | |
| 		<button type="button" onclick="B()">Back</button><button type="submit">Save & Connect</button><hr>
 | |
| 		</div>
 | |
| 		<h2>WiFi setup</h2>
 | |
| 		<h3>Connect to existing network</h3>
 | |
| 		<button type="button" id="scan" onclick="N()">Scan</button><br>
 | |
| 		<div id="wifi">
 | |
| 			Wireless networks
 | |
| 			<div id="wifi_entries"></div>
 | |
| 			<hr class="sml">
 | |
| 			<button type="button" id="wifi_add" onclick="addWiFi()">+</button>
 | |
| 			<button type="button" id="wifi_rem" onclick="remWiFi()">-</button><br>
 | |
| 		</div>
 | |
| 		DNS server address:<br>
 | |
| 		<input name="D0" type="number" class="s" min="0" max="255" required>.<input name="D1" type="number" class="s" min="0" max="255" required>.<input name="D2" type="number" class="s" min="0" max="255" required>.<input name="D3" type="number" class="s" min="0" max="255" required><br>
 | |
| 		<br>
 | |
| 		mDNS address (leave empty for no mDNS):<br>
 | |
| 		http:// <input type="text" name="CM" maxlength="32"> .local<br>
 | |
| 		Client IP: <span class="sip"> Not connected </span> <br>
 | |
| 		<h3>Configure Access Point</h3>
 | |
| 		AP SSID (leave empty for no AP):<br> <input type="text" name="AS" maxlength="32"><br>
 | |
| 		Hide AP name: <input type="checkbox" name="AH"><br>
 | |
| 		AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63" pattern="(.{8,63})|()" title="Empty or min. 8 characters"><br>
 | |
| 		Access Point WiFi channel: <input name="AC" type="number" class="xs" min="1" max="13" required><br>
 | |
| 		AP opens:
 | |
| 		<select name="AB">
 | |
| 			<option value="0">No connection after boot</option>
 | |
| 			<option value="1">Disconnected</option>
 | |
| 			<option value="2">Always</option>
 | |
| 			<option value="3">Never (not recommended)</option>
 | |
| 			<option value="4">Temporary (no connection after boot)</option>
 | |
| 		</select><br>
 | |
| 		AP IP: <span class="sip"> Not active </span><br>
 | |
| 		<h3>Experimental</h3>
 | |
| 		Force 802.11g mode (ESP8266 only): <input type="checkbox" name="FG"><br>
 | |
| 		Disable WiFi sleep: <input type="checkbox" name="WS"><br>
 | |
| 		<i>Can help with connectivity issues.<br>
 | |
| 		Do not enable if WiFi is working correctly, increases power consumption.</i>
 | |
| 
 | |
| 		<h3>ESP-NOW Wireless</h3>
 | |
| 		<div id="NoESPNOW" class="hide">
 | |
| 			<i class="warn">This firmware build does not include ESP-NOW support.<br></i>
 | |
| 		</div>
 | |
| 		<div id="ESPNOW">
 | |
| 			Enable ESP-NOW: <input type="checkbox" name="RE"><br>
 | |
| 			<i>Listen for events over ESP-NOW<br>
 | |
| 			Keep disabled if not using a remote or wireless sync, increases power consumption.<br></i>
 | |
| 			Paired Remote MAC: <input type="text" name="RMAC" minlength="12" maxlength="12"><br>
 | |
| 			Last device seen: <span class="rlid" onclick="d.Sf.RMAC.value=this.textContent;" style="cursor:pointer;">None</span> <br>
 | |
| 		</div>
 | |
| 
 | |
| 		<div id="ethd">
 | |
| 			<h3>Ethernet Type</h3>
 | |
| 			<select name="ETH">
 | |
| 				<option value="0">None</option>
 | |
| 				<option value="9">ABC! WLED V43 & compatible</option>
 | |
| 				<option value="2">ESP32-POE</option>
 | |
| 				<option value="11">ESP32-POE-WROVER</option>
 | |
| 				<option value="6">ESP32Deux/RGB2Go Tetra</option>
 | |
| 				<option value="7">KIT-VE</option>
 | |
| 				<option value="8">QuinLED-Dig-Octa & T-ETH-POE</option>
 | |
| 				<option value="4">QuinLED-ESP32</option>
 | |
| 				<option value="10">Serg74-ETH32</option>
 | |
| 				<option value="5">TwilightLord-ESP32</option>
 | |
| 				<option value="3">WESP32</option>
 | |
| 				<option value="1">WT32-ETH01</option>
 | |
| 			</select><br><br>
 | |
| 		</div>
 | |
| 		<hr>
 | |
| 		<button type="button" onclick="B()">Back</button><button type="submit">Save & Connect</button>
 | |
| 	</form>
 | |
| </body>
 | |
| </html>
 | 
