How read sensor using WebServer or WebSocket ?

I am actually learning how to create a sketch to read sensors (any sensor) using the WebServer method.

Is there anyone in this forum who can provide me with a link for an application using Arduino IDE 2.x to read sensors (any sensor) with a WebServer that separates the HTML, CSS, and JavaScript files into a 'data' folder, like the file organization below

I am Sorry, I have been trying many examples of applications to read sensors with a WebServer by separating HTML, CSS, and JavaScript into a 'data' folder for 2 weeks, but none of them are working. Please help me

Are you using the ESPAsyncWebserver library?

Do you need the sensor’s value to automatically update on the browser page ? If so, Do you need this to be push or pull ?

You should write a description that gives an overview about your project.
There are many different approaches to create a website.
Which approach fits best highly depends on your real application

What exactly did you mean with your diaram? Esp32_Web_Server.ino is a file on your PC. As for the data folder I guess this should be a folder on your Esp32 that would be your web server's root folder?

That's the typical file structure when you want to use the IDE to upload some files into a partition on your ESP32

➜ Sketch dir, main sketch file with same name as the directory and a .ino extension, a data directory and inside that data directory you have all the files that you want transferred to the ESP32.

(for the 1.8.x version of the IDE you would install GitHub - me-no-dev/arduino-esp32fs-plugin: Arduino plugin for uploading files to ESP32 file system)

Yes, give me a project example for push and pull to update browser. Thank you very much

Thank you. I knew there must be a way of doing this but I didn't know how.

I think I understand Yudilubis's question now. I'd go with implementing REST API Web server functions that would return (reply with) current sensor values in JSON format and having index.html calling them with Javascipt when needed. Javascript can easily parse JSON so this would probably be the most appropriate format. Say your HTTP request would be GET /sensorValue then the Web server would reply with JSON something like {"sensor":"voltage","value":"2.3"}.

In Javascript you would need a httpClient class, something like:

			var httpClient = function () {
				this.request = function (url, method, callback) {
					var httpRequest = new XMLHttpRequest ();
					var httpRequestTimeout = null;
					
					httpRequest.onreadystatechange = function () {
						// console.log (httpRequest.readyState);
						if (httpRequest.readyState == 1) { // 1 = OPENED, start timing
							clearTimeout (httpRequestTimeout);
							httpRequestTimeout = setTimeout (function () { 
								alert ('Server did not reply (in time).'); 
							}, 5000);
						}
						if (httpRequest.readyState == 4) { // 4 = DONE, call callback function with responseText
							clearTimeout (httpRequestTimeout);
							switch (httpRequest.status) {
								case 200: 	callback (httpRequest.responseText); // 200 = OK
											break;
								case 0:		break;
								default: 	alert ('Server reported error ' + httpRequest.status + ' ' + httpRequest.responseText); // some other reply status, like 404, 503, ...
											break;
							}
						}
					}
					httpRequest.open (method, url, true);
					httpRequest.send (null);
				}
			}

Then you can call Web server's API functions using Javascript like:

			function refresSensorValue () { 
				var client = new httpClient ();
				
				client.request ('/sensorValue', 'GET', function (json){
					document.getElementById ('sensorValue').innerText = (JSON.parse (json).value);
				});
			}

I hope this helps. Let me know if you need more detailed information.

I guess it has to be a completed and working example

push and pull at the same time. Sounds like user @yudilubis does not know what that push / pull mean different roles = different codes in this case

Still you should give an overview about your project. What is the final purpose of having the sensor-values on a website

what kind of sensor values?
at what update-rate?

I think OP is looking for just an example of AJAX driven update (client is requesting = PULL) and WEBSOCKET driven update (Server is forcing the update = PUSH).

it's not very difficult to write, probably 10 minutes worth for a simple example...

OK let me try - typed here so to be tested:

the code will assume the setup described by OP

with slight modification to file names : home.htm, scripts.js and styles.css because I like it better like that. :slight_smile:

**AJAX BASED DEMO - CLIENT PULL **

Arduino code

/* ============================================
  code is placed under the MIT license
  Copyright (c) 2025 J-M-L
  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/


#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <SPIFFS.h>

const char* ssid = "xxx";
const char* password = "xxx$xxx$";

AsyncWebServer server(80);

// the variable that is displayed on the web page
long value = 0;

void setup() {
  Serial.begin(115200);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.write(".");
  }
  Serial.println("\nConnected to WiFi");

  // Mount SPIFFS
  if (!SPIFFS.begin(true)) {
    Serial.println("SPIFFS Mount Failed");
    while (true) yield();
    return;
  }
  // Serve home.htm
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/home.htm", "text/html");
  });

  // Serve scripts.js
  server.on("/scripts.js", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/scripts.js", "application/javascript");
  });

  // Serve style.css
  server.on("/styles.css", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/styles.css", "text/css");
  });

  // Sending the value
  server.on("/request", HTTP_GET, [](AsyncWebServerRequest * request) {
    Serial.printf("Got a request for value = %ld\n", value);
    
    char tmpBuf[20];                                // enough for a long
    snprintf(tmpBuf, sizeof tmpBuf, "%ld", value);  // build the text for the value
    request->send(200, "text/plain", tmpBuf);       // send as plain text (could be a JSON as well if we wanted better content management)

    if (++value > 10) value = 0; // cycle value for next request
  });

  // Start the server
  server.begin();

  Serial.print("navigate to http://"); Serial.println(WiFi.localIP());
}

void loop() {}

home.htm

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>AJAX Demo</title>
    <link rel="stylesheet" href="styles.css">
    <script src="scripts.js" defer></script>
  </head>
  <body>
    <h1>Current Server Data</h1>
    <label for='value'>Value:</label>
    <span id='value' style="min-width: 100px; display: inline-block; text-align: center;">?</span>
  </body>
</html>

scripts.js

// Function to load the initial value or periodically refresh it
function loadValue() {
  var request = new XMLHttpRequest();
  request.open('GET', '/request', true);
  request.onload = function() {
    if (request.status == 200) {
      var value = parseFloat(request.responseText);  // Convert to a number

      // Update the span content
      var valueSpan = document.getElementById('value');
      valueSpan.innerText = request.responseText;

      // Change the background color and text color based on value
      if (value <= 3) {
        valueSpan.style.backgroundColor = 'blue';
        valueSpan.style.color = 'white';
      } else if (value <= 7) {
        valueSpan.style.backgroundColor = 'green';
        valueSpan.style.color = 'black';
      } else if (value > 7) {
        valueSpan.style.backgroundColor = 'red';
        valueSpan.style.color = 'black';
      } else {
        valueSpan.style.backgroundColor = '';
        valueSpan.style.color = 'black';
      }
    }
  };
  request.send();
}

// Set interval to refresh data every second
setInterval(loadValue, 1000);

// Call loadValue when the page loads
window.onload = loadValue;

and styles.css

body {
  font-family: Arial, sans-serif;
  margin: 20px;
  padding: 0;
  background-color: #f9f9f9;
  color: #333;
}

h1 {
  font-size: 24px;
  margin-bottom: 20px;
}

label {
  font-size: 16px;
  margin-right: 10px;
}

#value {
  padding: 5px 10px;
  border-radius: 4px;
  display: inline-block;
  min-width: 100px;
  text-align: center;
}


Now if you want to do the same thing with websockets and a server push over the web socket

**WEBSOCKET BASED DEMO - SEVER PUSH **

Arduino sketch

/* ============================================
  code is placed under the MIT license
  Copyright (c) 2025 J-M-L
  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <SPIFFS.h>
#include <AsyncWebSocket.h>

const char* ssid = "xxx";
const char* password = "xx$xxx$";
const unsigned long updateInterval = 1000; // ms

AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
unsigned long lastUpdateTime = 0;
long value = 0;

void sendValueOverWebSocketIfNeeded() {
  if (ws.count() > 0) {  // Only send if there's at least one client connected
    char tmpBuf[20];
    snprintf(tmpBuf, sizeof(tmpBuf), "%ld", value);
    ws.textAll(tmpBuf);  // Send the value as plain text (C-string) to all connected clients
    Serial.printf("I've sent %s\n", tmpBuf);
    if (++value > 10) value = 0;  // Cycle value
  }
}

void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  if (type == WS_EVT_CONNECT) {
    Serial.println("New client connected");
  } else if (type == WS_EVT_ERROR) {
    Serial.println("Error occurred on WebSocket");
  }
}

void setup() {
  Serial.begin(115200);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.write(".");
  }
  Serial.println("\nConnected to WiFi");

  // Mount SPIFFS
  if (!SPIFFS.begin(true)) {
    Serial.println("SPIFFS Mount Failed");
    while (true) yield();
    return;
  }

  // Serve home.htm, scripts.js, and styles.css
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/home.htm", "text/html");
  });

  server.on("/scripts.js", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/scripts.js", "application/javascript");
  });

  server.on("/styles.css", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/styles.css", "text/css");
  });

  // WebSocket event handling
  ws.onEvent(onWsEvent);
  server.addHandler(&ws);

  // Start the server
  server.begin();

  Serial.print("navigate to http://");
  Serial.println(WiFi.localIP());
}

void loop() {
  unsigned long currentMillis = millis();  // Get the current time

  // Check if 1 second has passed
  if (currentMillis - lastUpdateTime >= updateInterval) {
    lastUpdateTime = currentMillis;
    sendValueOverWebSocketIfNeeded();
  }
}

home.htm

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>WebSocket Demo</title>
    <link rel="stylesheet" href="styles.css">
    <script src="scripts.js" defer></script>
  </head>
  <body>
    <h1>Current Server Data</h1>
    <label for='value'>Value:</label>
    <span id='value' style="min-width: 100px; display: inline-block; text-align: center;">?</span>
  </body>
</html>

scripts.js

var ws = new WebSocket('ws://' + window.location.hostname + '/ws');

ws.onmessage = function(event) {
  var value = parseFloat(event.data);  // Parse the number sent from the server
  var valueSpan = document.getElementById('value');
  valueSpan.innerText = value;

  // Change background color based on value
  if (value <= 3) {
    valueSpan.style.backgroundColor = 'blue';
    valueSpan.style.color = 'white';
  } else if (value <= 7) {
    valueSpan.style.backgroundColor = 'green';
    valueSpan.style.color = 'black';
  } else if (value > 7) {
    valueSpan.style.backgroundColor = 'red';
    valueSpan.style.color = 'black';
  } else {
    valueSpan.style.backgroundColor = '';
    valueSpan.style.color = 'black';
  }
};

and styles.css

body {
  font-family: Arial, sans-serif;
  margin: 20px;
  padding: 0;
  background-color: #f9f9f9;
  color: #333;
}

h1 {
  font-size: 24px;
  margin-bottom: 20px;
}

label {
  font-size: 16px;
  margin-right: 10px;
}

#value {
  padding: 5px 10px;
  border-radius: 4px;
  display: inline-block;
  min-width: 100px;
  text-align: center;
}


What it does:

in both cases you have a simple web page showing a label and a textfield for the value and this value is updated once per second to show the current server value.

In the first example, there I'm using AJAX. The client browser sends a request every second to ask for the updated value and it uses the the DOM to modify the content of the text field.

I added a bit of business logic in the script to make it interesting, if the value is less than 3 then you get a blue background, green under 7 and red above.

At each request the server increments the value and rollover when reaching 10.

In the second example, I'm using a websocket. The client connects and establishes a socket with the server. This websocket is a live link between the ESP32 and the browser and you can discuss over that channel. I'm using it with a simple milis() based update to send over that channel the updated value if there is at least one client on the websocket. (you can open multiple browser window and you'll see them update their value at the same time).


THIS IS TOTALLY UNTESTED - LET ME KNOW IF THAT WORKS

(of course update the SSID and PWD to match your settings)

Actually the web sockets example shows the client pulling the 3 files for the web home page and then the server pushing the updated data.

So pull & push in the same example :wink:

I am not familiar with the whole process.

@J-M-L you posted a *.inosketch, *.htm, *.js and *.css-file.
Now where can I looked up how to put these four files into exact place that will make it work.

I assume it is not done by hitting the arduino-IDE-upload button.

I have put the link for the data uploader in a previous post (for IDE 1.8.x)

The directory structure is what OP asked about at first

Here is a video about how to do what you are asking. He has a couple more about using websockets. Very good way to get started in using the esp32 over a network.

Mo thunder link

Ok thank you for your help Jackson.
Maybe I don’t yet know how to test what you’ve described above. Just like the two links below, where I’ve already created an HTML file, script, and CSS to read data from the BME280 or DS18B20 sensors:

  1. Display Sensor Readings in Gauges with ESP32 Web Server
  2. Analog Gauge Temperature Reading using ESP32 Server | ESP32

but I haven’t figured out how to test it and display the web page using the IP address obtained from the Serial Monitor.

Please help me. I am still a beginner, currently learning ESP32 programming for IoT.
thank you.

Here are the two links that are actually similar:

That’s worrying that you don’t know what to do. It probably means you didn’t practice enough nor researched how all this should work. There are simple tutorials for that online…

Once you understand :

  • Create a demo directory
  • Create a data directory inside the demo
  • Using the codes above and the exact file names, create the 3 web files in the data directory. File names have to be exactly as I wrote
  • Make sure the upload tool I mentioned before is configured for your IDE version. The link I provided earlier was for version 1.8.19, there is probably the equivalent for version 2.x somewhere
  • Launch the IDE 1.8.19
  • Create a new sketch with the above code adjusted with your WiFi credentials and save in in the demo directory under the name demo.ino (the file name has to match the name of the directory )
  • Make sure the right esp32 board and port are selected and that you have a configuration with a SPIFF partition
  • Compile and upload the sketch
  • Use the upload esp32 data tool menu to transfer the 3 web files
  • Open the serial monitor at 115200 bauds
  • reboot your esp32 if you don’t see it booting.
  • After the boot the esp32 shows it’s connecting to your network and in the monitor the url you have to copy paste in a web browser.
  • Launch your browser and paste the URL
  • A simple web page should appear and the value should automatically increase up to 10 and cycle. The serial monitor should also display the same information.

And most importantly Study in details the whole thing.

This is a foggy question.
What exactly do you mean by " it ??

You will have to describe this "it" very precisely

But I haven’t figured out how to display the web page using the IP address obtained from the Serial Monitor.

Depending on

  • does your ESP32 connect to an existing WiFi-network
  • does your ESP32 create its very own WiFi

You have to connect the device that shall display the web page to the same WiFi-network.

If this is something you did not yet know. Starting with such a project is pretty ambitious.

The analog gauge looks fancy. Though like almost any fancy thing. Making it fancy requires knowledge.

If you want to know which IP address has been assigned to your ESP32 you can display it with:

void setup () {
    Serial.begin (115200);
    
   WiFi.begin (SSID, PASSWORD);

    while (WiFi.localIP ().toString () == "0.0.0.0") { // wait until we get IP from the router
        delay (1000); 
        Serial.printf ("   .\n"); 
    } 

    Serial.printf ("Got IP: %s\n", (char *) WiFi.localIP ().toString ().c_str ());
}

But checkin Serial output all the time is rather impractical, you have several better options:

  • configure your ESP32 to use static IP address of your choice

  • configure your router's DHCP to always assign the same IP address to your ESP32

  • use mDNS on your ESP32 so it will be accessible by its name

The code I provided just does that...

it should not be rocket science for the OP to copy that URL and paste it in her/his preferred browser...

OP is MIA, which is annoying - I'd love to know if what I typed works :slight_smile:

Indeed. I'm sorry I missed it somehow.