Browse Source

Added software wol sender

Toby Chui 1 year ago
parent
commit
2aad4c8794
7 changed files with 408 additions and 7 deletions
  1. 1 0
      espwol/data/conf.txt
  2. 2 2
      espwol/data/index.html
  3. 328 0
      espwol/data/wol.html
  4. 10 0
      espwol/espwol.ino
  5. BIN
      espwol/lib/WakeOnLan.zip
  6. 37 0
      espwol/macsave.ino
  7. 30 5
      espwol/webserver.ino

+ 1 - 0
espwol/data/conf.txt

@@ -0,0 +1 @@
+[]

+ 2 - 2
espwol/data/index.html

@@ -232,8 +232,8 @@
 					</tbody>
 				</table>
 			</div>
-			<h3>🤖 RESTFUL API </h3>
-			<p>You can also access the power status and controls via RESTFUL API requests.</p>
+			<h3>🤖 RESTFUL API / Magic Packet</h3>
+			<p>You can also access the power status and controls via RESTFUL API requests or <a href="./wol">send WoL magic packet</a></p>
 			<div class="codeblock"><code>
 				GET /api/status <br>
 				POST /api/power <br>

+ 328 - 0
espwol/data/wol.html

@@ -0,0 +1,328 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<meta http-equiv="X-UA-Compatible" content="ie=edge">
+	<title>Magic Packet | ESPWoL</title>
+	<style>
+		body{
+			font-family: Arial, sans-serif;
+			color: #262626;
+		}
+		.box {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+		}
+
+		.wallpaper{
+			position: absolute;
+			width: 100%;
+			height: 100%;
+			left: 0;
+			top: 0;
+			background-color: #ffffff;
+		}
+
+		
+
+		.main{
+			width: 480px;
+			height: calc(90vh - 2em);
+			padding: 1em;
+			-webkit-box-shadow: 10px 10px 18px 0px rgba(0,0,0,0.14);
+			-moz-box-shadow: 10px 10px 18px 0px rgba(0,0,0,0.14);
+			box-shadow: 10px 10px 18px 0px rgba(0,0,0,0.14);
+			border-radius: 0.6em;
+			border: 1px solid rgb(172, 172, 172);
+			position: relative;
+			background-color: white;
+		}
+
+		.divider{
+			width: calc(100% - 4em);
+			padding-top: 0.4em;
+			margin-bottom: 0.4em;
+			border-bottom: 1px solid rgb(226, 226, 226);
+			padding-right: 2em;
+			padding-left: 2em;
+		}
+
+		small{
+			color: rgb(59, 59, 59);
+		}
+
+		.codeblock{
+			padding: 0.6em;
+			background-color: #242424;
+			color: white;
+		}
+
+		/* Properties Table */
+		.table_component {
+			overflow: auto;
+			width: 100%;
+		}
+
+		.table_component table {
+			border: 1px solid #dededf;
+			height: 100%;
+			width: 100%;
+			table-layout: fixed;
+			border-collapse: collapse;
+			border-spacing: 1px;
+			text-align: left;
+		}
+
+		.table_component caption {
+			caption-side: top;
+			text-align: left;
+		}
+
+		.table_component th {
+			border: 1px solid #dededf;
+			background-color: #eceff1;
+			color: #000000;
+			padding: 5px;
+		}
+
+		.table_component td {
+			border: 1px solid #dededf;
+			background-color: #ffffff;
+			color: #000000;
+			padding: 5px;
+		}
+
+
+		/* status blinker */
+		#statusBlinker{
+			position: absolute;
+			left: 1em;
+			bottom: 1em;
+			width: 1em;
+			height: 2px;
+			background-color: rgb(95, 95, 95);
+		}
+
+		#statusBlinker.interval{
+			background-color: rgb(235, 235, 235);
+		}
+
+		input[type=text] {
+			width: 100%;
+			padding: 6px 10px;
+			margin: 8px 0;
+			box-sizing: border-box;
+		}
+
+		.themebtn{
+			padding: 0.6em;
+			padding-left: 1.2em;
+			padding-right: 1em;
+			background-color: #242424;
+			border-radius: 0.4em;
+			color: white;
+			border: none;
+			cursor: pointer;
+		}
+
+		.themebtn.blue{
+			background-color: #466bb1;
+		}
+
+		.themebtn.red{
+			background-color: #b14646;
+		}
+
+		.themebtn:hover{
+			opacity: 0.6;
+		}
+	</style>
+</head>
+<body>
+	<div class="wallpaper"></div>
+	<br>
+	<div class="box">
+		<div class="main">
+			<h2>✨ WoL Magic Packet</h2>
+			<p>Select a host below to send Wake-on-LAN magic packet for remote startup</p>
+			<div class="table_component" role="region" tabindex="0">
+				<table>
+					<thead>
+						<tr>
+							<th>Name</th>
+							<th>MAC Address</th>
+							<th>Action</th>
+						</tr>
+					</thead>
+					<tbody id="storedMacList">
+						<tr>
+							<td>
+								
+							</td>
+							<td>
+
+							</td>
+							<td>
+
+							</td>
+						</tr>
+						
+					</tbody>
+				</table>
+			</div>
+			<h3>🖥️ Add Computer </h3>
+			<p>You can add more computer MAC address to the list</p>
+			<p  style="margin-bottom: 0;">Target name: </p>
+			<input id="nameInput" type="text">
+			<p style="margin-bottom: 0;">MAC Address: </p>
+			<input id="macInput" type="text">
+			<button class="themebtn" onclick="handleAddMacAddress();">Add MAC Address</button>
+			<br>
+			<div class="divider"></div>
+			<small>ESPWoL | Deisnged by tobychui 2023 - 2024</small>
+			<br><br>
+			<div><a href="/">Back</a></div>
+		</div>
+	</div>
+	<script>
+		let currentListOfMacs = [];
+
+		function initMacAddressList(){
+			fetch("/api/mem/read", {
+				method: "GET",
+			})
+			.then(response => {
+				if (!response.ok) {
+					throw new Error("Failed to read MAC address list");
+				}
+				return response.json();
+			}).then(data => {
+				let macTable = document.getElementById("storedMacList");
+				macTable.innerHTML = "";
+				currentListOfMacs = data;
+				if (data.length == 0){
+					//No devices
+					macTable.innerHTML = `<tr><td colspan="3">✔️ No stored MAC address</td></tr>`;
+				}else{
+					//Create a table
+					let newTable = "";
+					for(var i = 0; i < data.length; i++){
+						newTable += `<tr>
+							<td>
+								${data[i].name}
+							</td>
+							<td style="overflow-wrap: break-word;  word-break: break-word;">
+								${data[i].mac}
+							</td>
+							<td>
+								<button class="themebtn blue" onclick="sendWoL('${data[i].mac}');">Wake</button>
+								<button class="themebtn red" onclick="delRecord('${data[i].mac}');">Delete</button>
+							</td>
+						</tr>`;
+					}
+					macTable.innerHTML = newTable;
+				}
+				console.log(data);
+			}).catch(error => {
+				let macTable = document.getElementById("storedMacList");
+				macTable.innerHTML = `<tr><td colspan="3">✔️ No stored MAC address</td></tr>`;
+				console.error("Error:", error);
+			});
+		}
+		initMacAddressList();
+
+
+		function sendWoL(macaddr){
+			fetch("/api/wol?mac=" + macaddr, {
+				method: "GET",
+			})
+			.then(response => {
+				if (!response.ok) {
+					throw new Error("Failed to send WoL packet");
+				}
+
+				//ok! Reset the input fileds
+				alert("Wake on LAN packet sent!")
+				return;
+			})
+		}
+
+		//Remove a record from the list
+		function delRecord(macaddr){
+			let newListOfMac = [];
+			currentListOfMacs.forEach(record => {
+				if (record.mac != macaddr){
+					newListOfMac.push(record);
+				}
+			});
+
+			//save the new list to server
+			fetch("/api/mem/write?mac=" + JSON.stringify(newListOfMac), {
+				method: "POST",
+				headers: {
+					"Content-Type": "application/json"
+				},
+			})
+			.then(response => {
+				if (!response.ok) {
+					throw new Error("Failed to write MAC address list");
+				}
+				return;
+			})
+			.then(data => {
+				initMacAddressList();
+			});
+		}
+
+		function isValidMacAddress(macAddress) {
+			// MAC address pattern: XX:XX:XX:XX:XX:XX
+			// Where X is a hexadecimal digit (0-9, A-F)
+			var macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
+			return macRegex.test(macAddress);
+		}
+
+		function handleAddMacAddress(){
+			let newName = document.getElementById("nameInput").value;
+			if (newName.trim() == ""){
+				alert("Target name is missing!");
+				return;
+			}
+			let newMacAddress = document.getElementById("macInput").value;
+			newMacAddress = newMacAddress.trim();
+			if (!isValidMacAddress(newMacAddress)){
+				alert("Mac address is invalid");
+				return;
+			}
+
+			//Append the new name and mac to the list
+			currentListOfMacs.push({
+				"name":newName,
+				"mac":newMacAddress
+			});
+			
+
+			fetch("/api/mem/write?mac=" + JSON.stringify(currentListOfMacs), {
+				method: "POST",
+				headers: {
+					"Content-Type": "application/json"
+				},
+			})
+			.then(response => {
+				if (!response.ok) {
+					throw new Error("Failed to write MAC address list");
+				}
+
+				//ok! Reset the input fileds
+				document.getElementById("nameInput").value = "";
+				document.getElementById("macInput").value = "";
+				return;
+			})
+			.then(data => {
+				initMacAddressList();
+			});
+		}
+	</script>
+</body>
+</html>

+ 10 - 0
espwol/espwol.ino

@@ -18,6 +18,9 @@
 #include <ESP8266mDNS.h>
 #include <ArduinoJson.h>
 #include <LittleFS.h>
+#include <WiFiUdp.h>
+#include <WakeOnLan.h>
+
 
 /* Pin Definations */
 //On-board Programmable Button
@@ -32,11 +35,14 @@
 /* WiFi & Web Server Related */
 WiFiManager wifiManager;
 ESP8266WebServer server(80);
+WiFiUDP UDP;
+WakeOnLan WOL(UDP);
 
 /* Discovery */
 #include <ESP8266mDNS.h>
 //To prevent collision, the device MAC will be appended to this name
 String MDNS_NAME = "espwol";
+const char* DB_PATH = "/conf.txt"; // File path for storing the string data
 
 /* Global Variables */
 bool hddLedState = 0;
@@ -84,6 +90,10 @@ void setup() {
       Serial.println("[INFO] mDNS started. Connect to your device using http://" + MDNS_NAME + ".local");
       MDNS.addService("http", "tcp", 80);
   }
+
+  /* Wake On Lan Settings */
+  WOL.setRepeat(3, 100);
+  WOL.calculateBroadcastAddress(WiFi.localIP(), WiFi.subnetMask());
   
   /* Bind web listener */
   registerServeEndpoints();

BIN
espwol/lib/WakeOnLan.zip


+ 37 - 0
espwol/macsave.ino

@@ -0,0 +1,37 @@
+void handleSaveMac() {
+  if (server.hasArg("mac")) {
+    // Extract the string from the POST request
+    String data = server.arg("mac");
+
+    // Open the file in write mode
+    File file = LittleFS.open(DB_PATH, "w");
+    if (!file) {
+      server.send(500, "text/plain", "Failed to open file for writing");
+      return;
+    }
+
+    // Write the string data to the file
+    file.print(data);
+    file.close();
+
+    sendOK();
+  } else {
+    server.send(400, "text/plain", "No string provided in the request");
+  }
+}
+
+void handleGetMac() {
+  // Open the file in read mode
+  File file = LittleFS.open(DB_PATH, "r");
+  if (!file) {
+    server.send(500, "text/plain", "Failed to open file for reading");
+    return;
+  }
+
+  // Read the string data from the file
+  String data = file.readString();
+  file.close();
+
+  // Send the string data as the json resp
+  server.send(200, "application/json", data);
+}

+ 30 - 5
espwol/webserver.ino

@@ -7,6 +7,7 @@
 
 void registerServeEndpoints() {
   server.serveStatic("/", LittleFS, "/index.html");
+  server.serveStatic("/wol", LittleFS, "/wol.html");
 
   /* Button Actions */
   server.on("/power/press", handlePowerBtnPress);
@@ -15,19 +16,43 @@ void registerServeEndpoints() {
   server.on("/reset/release", handleResetBtnRelease);
   server.on("/status", handleStatus);
   server.on("/ipaddr", handleGetIPAddr);
-  
+
   /* RESTFUL API */
   server.on("/api/status", handleStatus);
   server.on("/api/power", handlePowerSequence);
   server.on("/api/reset", handleResetSequence);
+  server.on("/api/wol", handleSendWoLPacket);
+  server.on("/api/mem/write", HTTP_POST, handleSaveMac);
+  server.on("/api/mem/read", HTTP_GET, handleGetMac);
 
   server.onNotFound(handleNotFound);
 }
 
-void sendOK(){
+//Reply ok to request
+void sendOK() {
   server.send(200, "application/json", "\"ok\"");
 }
 
+/* Send Wake-On-Lan packet to another MAC address */
+void handleSendWoLPacket() {
+  // Check if there is a "mac" parameter in the URL
+  if (server.hasArg("mac")) {
+    // Get the value of the "mac" parameter
+    String macAddress = server.arg("mac");
+    Serial.print("MAC Address: ");
+    Serial.println(macAddress);
+
+    //Send MAC address to both port 9 and 7
+    WOL.sendMagicPacket(macAddress);
+    WOL.sendMagicPacket(macAddress, 7);
+    
+    server.send(200, "text/plain", "Received MAC Address: " + macAddress);
+  } else {
+    server.send(400, "text/plain", "Missing 'mac' parameter");
+  }
+}
+
+
 /* Emulate the power button press events in hardware */
 void handlePowerBtnPress() {
   digitalWrite(PWR_BTN, HIGH);
@@ -59,8 +84,8 @@ void handleResetBtnRelease() {
 }
 
 
-//Serving the LED status 
-void handleStatus(){
+//Serving the LED status
+void handleStatus() {
   //Create a JSON object, 64 should be more than enough bytes to work with
   StaticJsonDocument<64> jsonDoc;
   jsonDoc["hdd"] = hddLedState;
@@ -71,7 +96,7 @@ void handleStatus(){
 }
 
 //Return the ip address and mDNS name of the device
-void handleGetIPAddr(){
+void handleGetIPAddr() {
   String ipAddrStr = WiFi.localIP().toString();
   StaticJsonDocument<128> jsonDoc;
   jsonDoc["ipaddr"] = ipAddrStr;