ESP32/Web server/52. A reading on the page
Your chip
Your language
Web server · 52 of 81

A reading on the page

Building the number into the page means the reader has to press F5 to see a new one. Splitting the page from the number is four extra lines, and it is the split every dashboard on this board is built on.

/esp32/web-server-sensor-readings · arduino · S3

Watch what crosses the wire on each update, and what the reader sees while it does.

The reader presses F5
1× a second
Updates per second1/s
Per update
638 B
Per minute
37 kB
Page blanks
60 times a minute
Every new number costs a whole page. The reading is inside the HTML, so the only way to get a fresh one is to fetch the HTML again — and while that is in flight the reader is looking at a blank card. Scroll position goes too. At 1× a second that is 37 kB a minute to deliver about 240 bytes of actual information.

Two routes, not one

The first version everyone writes builds the number into the HTML:

"<h1>Light: " + String(analogRead(LDR)) + "</h1>"

It works, and it forces a full reload for every new value. Split it instead:

  • / returns the page. It is a constant, it never changes, and it can be served straight out of flash.
  • /light returns three or four characters of plain text. No tags, no headers you did not need, no layout.

The page then asks for /light on a timer and rewrites one element. About 130 bytes cross the wire instead of 500, and nothing blanks.

Content-Type is the instruction

text/html tells the browser to parse it; text/plain tells it not to. Send a bare number as text/html and it still works — until the day a reading contains a character the parser cares about. Say what a response is.

Polling has a ceiling

Once a second for a temperature is sensible. Ten times a second for a graph is ten new TCP connections a second, and each one costs a handshake to carry four bytes. That is the point where the connection should stay open instead: WebSocket dashboards is the comparison, and Async web server is the library that serves both.

On your S3
ChipXtensa LX7 · 2 × 240 MHz
Board settingESP32S3 Dev Module
Default I2CSDA 8 · SCL 9
Watch out forThe port vanishes after upload

The code

Two routes. `/` is the page and never changes; `/light` is the reading and nothing else. The script in the page asks for `/light` once a second and writes the answer into a span.

sensor_page.ino
#include <WiFi.h>
#include <WebServer.h>

const int LDR = 33;                // ADC1, so it survives Wi-Fi
WebServer server(80);

const char PAGE[] PROGMEM = R"html(
<!doctype html><meta charset=utf-8>
<h1>Light</h1><h2 id=v>--</h2>
<script>
setInterval(() => fetch('/light')
  .then(r => r.text())
  .then(t => v.textContent = t), 1000);
</script>
)html";

void setup() {
  Serial.begin(115200);
  pinMode(LDR, INPUT);
  WiFi.begin("your-network", "your-password");
  while (WiFi.status() != WL_CONNECTED) delay(250);
  Serial.println(WiFi.localIP());

  server.on("/", HTTP_GET, [] {
    server.send_P(200, "text/html", PAGE);
  });

  server.on("/light", HTTP_GET, [] {
    server.sendHeader("Cache-Control", "no-store");
    server.send(200, "text/plain", String(analogRead(LDR)));
  });

  server.begin();
}

void loop() {
  server.handleClient();
}

GPIO 33 is an ADC1 pin. On the classic ESP32 the ADC2 pins - 0, 2, 4, 12-15, 25-27 - stop returning anything once the radio is on, and this sketch turns the radio on.

When it does not work

The number appears once and then never changes

The browser is serving /light from its cache. Send a Cache-Control no-store header on that route, or append a changing query parameter to the fetch. It looks like a frozen sensor and it is a frozen response.

analogRead always returns 4095, or always 0

You are on an ADC2 pin with the radio running. On the classic ESP32 the second ADC block shares hardware with Wi-Fi and the radio wins. Move the sensor to GPIO 32-39.

The page shows two dashes and never fills in

The fetch is failing, not the sensor. Open the browser console - it will name the reason - and check /light on its own by visiting it directly. If that path returns the number, the page is the problem; if it does not, the route is.

It works alone and falls apart with a second tab open

Each tab polls once a second, and this server answers strictly one request at a time. Two tabs is two requests a second queued against each other, plus whatever else the browser asks for. That is the wall the async server exists to remove.

Where this goes next

The page can read the board now. The next one lets the board be told something.

Switching a pin from the page

Edit this page — content/esp32/web-server-sensor-readings.mdx

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse ESP32 on the forum