ARTICLE AD BOX
I want to preface this by saying I am a complete amateur to web development of any kind and this is my first foray into web development of any kind, so please forgive any dumb questions or assumptions I may make.
I am attempting to create a simple, lightweight webpage that will take data fed into the host device through several clients (transmitted via UDP) and display it on a canvas drawing made to resemble a bar graph. This page will be hosted on an ESP32 and the clients that are connecting to the host will also be ESP32s. Ideally this master will have the slave modules reporting to it and then maybe one or two computers accessing the web page at a time, so the majority of the computational effort will be just streaming data in. I have an image that describes this but can't embed images yet so you'll have to click this link to see it.
My ideal workflow for this is
Slave takes data -> Organize/transmit data chunk -> Receive data -> Modify HTML variables -> Redraw canvas with new individual line values.
I've already managed to get the data flowing into the host and a website hosted, I am now at the part where I need to have this data organized into variables and then displayed/animated on the web page. Reloading the page on a setInterval function isn't a solution for me as I want this graph to be as close to live as possible.
To my understanding, GET and POST requests are not sufficient to transmit this data between the slaves and master as GET and POST can only be used to 'locally' change a webpage, or to submit a form to do some PHP stuff with which seems outside the scope of what I want. So one client sending a get/post request cannot modify the look of a web page for another user, only for itself.
I've looked into fetch() as a potential method, but this seems to be more of a method used to display a webpage inside of a webpage as a sort of embed type use case, not for modifying HTML live. I've looked at websocket, but again I'm not sure this solves my redrawing issue.
Thank you for any help you can offer.
<!DOCTYPE html> <html> <body> <center> <h2>Planter Air Balancer</h2> <script> setInterval(myTimer, 1000); function myTimer() { const date = new Date(); document.getElementById("time").innerHTML = date.toLocaleTimeString(); } </script> <canvas id="myCanvas" width="800" height="400" style="border:2px solid #000000;"> Sorry, your browser does not support canvas. </canvas> <p id="Graph"></p> </body> <script> setInterval(myCanvas, 100); function myCanvas(){ let numRows = 47; // Create a Canvas: const canvas = document.getElementById("myCanvas"); const ctx = canvas.getContext("2d"); // Set font size and style ctx.font = "10px Arial"; // Clear drawing field ctx.clearRect(0,0,canvas.width, canvas.height); for(let i = 1; i <= numRows; i++){ ctx.fillText (i, 16.5*i, canvas.height-10); // Define a new path ctx.beginPath(); // Set a start-point ctx.moveTo(2+16.5*i, canvas.height-20); // Set an end-point ctx.lineTo(2+16.5*i, canvas.height/2-(Math.random()*10)); //This Math.random() piece is just to give an idea of what I want to have the end product resemble. I'll set up some arrays or a structure to feed data into later on. // Stroke it (Do the Drawing) ctx.stroke(); } } </script> <p id = "time"></p> </center> </html>Master
// Load Wi-Fi library #include <WiFi.h> #include <esp_littlefs.h> #include <WiFiUdp.h> // Replace with your network credentials const char* ssid = "ESP32-Access-Point"; const char* password = "123456789"; // Set web server port number to 80 WiFiServer server(80); WiFiUDP udp; const int udpPort = 80; // Variable to store the HTTP request String header; void setup() { Serial.begin(115200); //udp.begin(WiFi.localIP(), udpPort); // Connect to Wi-Fi network with SSID and password Serial.print("Setting AP (Access Point)…"); // Remove the password parameter, if you want the AP (Access Point) to be open WiFi.softAP(ssid, password); IPAddress IP = WiFi.softAPIP(); Serial.print("AP IP address: "); Serial.println(IP); server.begin(); udp.begin(udpPort); Serial.println(udpPort); } void loop(){ static bool clientConnected = false; int randomNumber = random(100); int serialAvailability = Serial.available(); if(Serial.available()){ char text[100] = {0}; Serial.readBytesUntil('\n', text, 100); Serial.println(text); if(!clientConnected) return; } int packetSize = udp.parsePacket(); if(packetSize){ Serial.print("Received packet of size "); Serial.println(packetSize); Serial.print("From: "); Serial.print(udp.remoteIP()); //Serial.print(" , Port: "); //Serial.print(udp.remotePort()); Serial.print(". "); clientConnected = true; char packetBuffer[150] = {0}; udp.read(packetBuffer, packetSize); Serial.println(packetBuffer); } WiFiClient client = server.available(); // Listen for incoming clients if (client) { // If a new client connects, //Serial.println("New Client."); // print a message out in the serial port String currentLine = ""; // make a String to hold incoming data from the client while (client.connected()) { // loop while the client's connected if (client.available()) { // if there's bytes to read from the client, char c = client.read(); // read a byte, then Serial.write(c); // print it out the serial monitor header += c; if (c == '\n') { // if the byte is a newline character // if the current line is blank, you got two newline characters in a row. // that's the end of the client HTTP request, so send a response: if (currentLine.length() == 0) { // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) // and a content-type so the client knows what's coming, then a blank line: client.println("HTTP/1.1 200 OK"); client.println("Content-type:text/html"); //client.println("Connection: close"); client.println(); // Display the HTML web page client.println("<!DOCTYPE html><html>"); client.println("<body><center><h2>Planter Air Balancer</h2>"); client.println("<script>"); client.println("setInterval(myTimer,1000);"); client.println("function myTimer(){"); client.println("const date = new Date();"); client.println("document.getElementById(\"time\").innerHTML = date.toLocaleTimeString();}"); client.println("</script>"); client.println("<canvas id=\"myCanvas\" width = \"800\" height = \"400\" style = \"border:2px solid #000000;\">"); client.println("Sorry, your browser does not support canvas."); client.println("</canvas><p id=\"Graph\"></p></body>"); client.println("<script>"); client.println("setInterval(myCanvas, 1000);"); client.println("function myCanvas(){"); client.println("let numRows = 47;"); client.println("const canvas = document.getElementById(\"myCanvas\");"); client.println("const ctx = canvas.getContext(\"2d\");"); client.println("ctx.font = \"10px Arial\";"); client.println("ctx.clearRect(0,0, canvas.width, canvas.height)"); client.println("for(let i = 1; i <=numRows; i++){"); client.println("ctx.fillText (i, 16.5*i, canvas.height-10);"); client.println("ctx.beginPath();"); client.println("ctx.moveTo(2+16.5*i, canvas.height-20);"); client.println("ctx.lineTo(2+16.5*i, canvas.height/2-Math.random()*10);"); client.println("ctx.stroke();"); client.println("}"); client.println("}"); client.println("</script>"); client.println("<p id = \"time\"></p>"); client.println("</center>"); client.println("</html>"); // The HTTP response ends with another blank line client.println(); // Break out of the while loop break; } else { // if you got a newline, then clear currentLine currentLine = ""; } } else if (c != '\r') { // if you got anything else but a carriage return character, currentLine += c; // add it to the end of the currentLine } } } // Clear the header variable header = ""; // Close the connection client.stop(); //Serial.println("Client disconnected."); //Serial.println(""); } }Slave
#include <arduino.h> #include <Wire.h> #include <WiFi.h> #include <WiFiUdp.h> const int udpPort = 80; boolean connected = false; WiFiUDP udp; #define S1 36 #define S2 39 #define S3 34 #define S4 35 #define S5 32 #define S6 33 #define S7 32 #define S8 33 int randLOW = -1; int randHIGH = 1; unsigned long sensorOne = 0; unsigned long sensorTwo = 0; unsigned long sensorThree = 0; unsigned long sensor[48]; struct{ int Sensor1; int Sensor2; int Sensor3; int Sensor4; int Sensor5; int Sensor6; int Sensor7; int Sensor8; } pSensor; unsigned long timeSinceLast; unsigned long timeSetAvg; int16_t sensor1Init; int16_t sensor2Init; int16_t sensor3Init; int16_t sensor4Init; int16_t sensor5Init; int16_t sensor6Init; int16_t sensor7Init; int16_t sensor8Init; const char* ssid = "ESP32-Access-Point"; const char* password = "123456789"; const char* host = "192.168.4.1"; const char * networkName = "ESP32-Access-Point"; const char * networkPswd = "123456789"; const char * udpAddress = "192.168.4.1"; void setup() { pinMode(S1, INPUT); pinMode(S2, INPUT); pinMode(S3, INPUT); pinMode(S4, INPUT); pinMode(S5, INPUT); pinMode(S6, INPUT); pinMode(S7, INPUT); pinMode(S8, INPUT_PULLDOWN); Serial.begin(115200); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); connectToWiFi(networkName, networkPswd); udp.begin(udpPort); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } } void loop() { delay(100); pSensor.Sensor1 = analogRead(S1)-sensor1Init; pSensor.Sensor2 = analogRead(S2)-sensor2Init; pSensor.Sensor3 = analogRead(S3)-sensor3Init; pSensor.Sensor4 = analogRead(S4)-sensor4Init; pSensor.Sensor5 = analogRead(S5)-sensor5Init; pSensor.Sensor6 = analogRead(S6)-sensor6Init; pSensor.Sensor7 = analogRead(S7)-sensor7Init; pSensor.Sensor8 = analogRead(S8)-sensor8Init; /////////////////////////WIFI STUFF BEGINS/////////////////////// if(connected){ //Send a packet //int a = a++; udp.beginPacket(udpAddress,udpPort); //udp.printf("Seconds since boot: %lu", millis()/1000); udp.printf("%i", pSensor.Sensor1); udp.printf(", "); udp.printf("%i", pSensor.Sensor2); udp.printf(", "); udp.printf("%i", pSensor.Sensor3); udp.printf(", "); udp.printf("%i", pSensor.Sensor4); udp.printf(", "); udp.printf("%i", pSensor.Sensor5); udp.printf(", "); udp.printf("%i", pSensor.Sensor6); udp.printf(", "); udp.printf("%i", pSensor.Sensor7); udp.printf(", "); udp.printf("%i", pSensor.Sensor8); udp.endPacket(); Serial.print(pSensor.Sensor1); Serial.print(", "); Serial.print(pSensor.Sensor2); Serial.print(", "); Serial.print(pSensor.Sensor3); Serial.print(", "); Serial.print(pSensor.Sensor4); Serial.print(", "); Serial.print(pSensor.Sensor5); Serial.print(", "); Serial.print(pSensor.Sensor6); Serial.print(", "); Serial.print(pSensor.Sensor7); Serial.print(", "); Serial.println(pSensor.Sensor8); //Serial.println("Sent Data!"); //Serial.println(udp.remotePort()); //Serial.println(udp.remoteIP()); } } void connectToWiFi(const char * ssid, const char * pwd){ Serial.println("Connecting to WiFi network: " + String(ssid)); // delete old config WiFi.disconnect(true); //register event handler WiFi.onEvent(WiFiEvent); //Initiate connection WiFi.begin(ssid, pwd); Serial.println("Waiting for WIFI connection..."); } //wifi event handler void WiFiEvent(WiFiEvent_t event){ switch(event) { case ARDUINO_EVENT_WIFI_STA_GOT_IP: //When connected set Serial.print("WiFi connected! IP address: "); Serial.println(WiFi.localIP()); //initializes the UDP state //This initializes the transfer buffer udp.begin(WiFi.localIP(),udpPort); connected = true; break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: Serial.println("WiFi lost connection"); connected = false; break; default: break; } }