Guide no. 56
ESP32 and BME280: the board serves a web page with temperature, humidity, pressure and a chart that updates by itself.
By Ada Intelisano · informaticait.it technical team
- BME280 on SDA (21) and SCL (22), powered at 3.3 V.
- The board serves the page and answers /data with JSON.
- The browser draws the chart, with no external libraries.
- You open it with the IP address or with meteo.local.
The temperature, humidity and pressure in your home on your phone, with a chart that grows by itself. The ESP32 does everything: it reads the sensor, holds a small web page and answers the browser. No external services, no subscriptions: it works inside your own Wi-Fi network.
1The parts
- a FireBeetle 2 ESP32-E board with the pins already soldered
- a BME280 sensor (here the SparkFun one with soldered pins), breadboard and four jumper wires
- your home Wi-Fi network (2.4 GHz) with its name and password
2Connect the sensor
Four wires: 3V3, GND, SDA (GPIO 21), SCL (GPIO 22). The SparkFun BME280 runs at 3.3 V only: do not connect it to the VCC pin, which carries 5 V. The I²C address is 0x77, the one the sketch expects.
3How it works
{"t":21.4,"u":48,"p":1013}. The board sends just these few characters every 5 seconds; the browser does the drawing.4The sketch
Write your network name and password in the first lines. The page is inside the sketch, between
R"rawliteral( and )rawliteral": it is HTML with a little JavaScript that asks for /data and draws the chart on a
canvas. No external libraries: the page works even without Internet access.
// Networked weather station: the board reads the BME280 and serves a web page with the values and a chart that updates by itself. #include <WiFi.h> #include <WebServer.h> #include <ESPmDNS.h> #include <Adafruit_BME280.h> const char* NETWORK = "your-network-name"; const char* PASSWORD = "your-network-password"; WebServer server(80); Adafruit_BME280 bme; // The page: HTML and JavaScript. Every 5 seconds it asks for /data and adds a point to the chart (the last 60). const char PAGE[] PROGMEM = R"rawliteral( <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"><title>Weather station</title> <style>body{font-family:sans-serif;margin:16px;background:#f4f7f5}h1{font-size:20px} .v{display:inline-block;background:#fff;border-radius:8px;padding:10px 14px;margin:4px;min-width:90px} .v b{display:block;font-size:24px;color:#1f7a3a}canvas{background:#fff;border-radius:8px;width:100%;max-width:600px}</style> </head><body><h1>Weather station</h1> <div class="v">Temperature<b id="t">--</b></div><div class="v">Humidity<b id="u">--</b></div><div class="v">Pressure<b id="p">--</b></div> <canvas id="g" width="600" height="260"></canvas> <script> const points = []; function draw() { const c = document.getElementById('g').getContext('2d'); c.clearRect(0, 0, 600, 260); if (points.length < 2) return; const min = Math.min(...points) - 0.5, max = Math.max(...points) + 0.5; c.fillStyle = '#555'; c.fillText(max.toFixed(1) + ' °C', 4, 12); c.fillText(min.toFixed(1) + ' °C', 4, 254); c.strokeStyle = '#1f7a3a'; c.lineWidth = 2; c.beginPath(); points.forEach((v, i) => { const x = 50 + i * 540 / 59, y = 250 - (v - min) / (max - min) * 240; i ? c.lineTo(x, y) : c.moveTo(x, y); }); c.stroke(); } async function update() { const d = await (await fetch('/data')).json(); document.getElementById('t').textContent = d.t.toFixed(1) + ' °C'; document.getElementById('u').textContent = d.u.toFixed(0) + ' %'; document.getElementById('p').textContent = d.p.toFixed(0) + ' hPa'; points.push(d.t); if (points.length > 60) points.shift(); draw(); } update(); setInterval(update, 5000); </script></body></html> )rawliteral"; void mainPage() { server.send_P(200, "text/html", PAGE); } void data() { // the readings in JSON format, for example {"t":21.4,"u":48,"p":1013} String j = "{\"t\":" + String(bme.readTemperature(), 1) + ",\"u\":" + String(bme.readHumidity(), 0) + ",\"p\":" + String(bme.readPressure() / 100.0, 0) + "}"; // from pascals to hectopascals server.send(200, "application/json", j); } void setup() { Serial.begin(115200); if (!bme.begin(0x77)) Serial.println("BME280 not found: check the wires"); WiFi.mode(WIFI_STA); WiFi.begin(NETWORK, PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.print("\nOpen http://"); Serial.println(WiFi.localIP()); MDNS.begin("meteo"); // or http://meteo.local server.on("/", mainPage); server.on("/data", data); server.begin(); } void loop() { server.handleClient(); }
5Try it
- “BME280 not found”: SDA and SCL are swapped, or the sensor is not getting 3.3 V.
- The page does not open: is the phone on another network (guest network or mobile data)? Try the numeric address.
6Challenge yourself
- Add a second line to the chart for humidity, in another color.
- Have the board keep the last 60 readings in an array, so the chart is already full when you open the page.
- the ESP32 can act as a web server for your network;
- the page loads once, then the browser only asks for the data in JSON;
- a
canvaswith a few lines of JavaScript is enough for a chart.
Useful products
Other guides
- Resistor colour code and tolerances
- N-channel and P-channel MOSFETs
- Bipolar transistor (BJT) or MOSFET?
- Relays: how to drive them and how much current they draw
- THT and SMD/SMT components
- XIAO board pinouts
- LEDs: forward voltage and series resistor
- The multimeter: measuring voltage and current
- Wemos D1 mini: first steps
- MOSFET module with Arduino and ESP32
- Relay module with Arduino and ESP32
- 3D printing filaments: PLA, PETG and fibre-filled versions
- Analog and digital: the differences
- ADS1115: 16-bit converter with Arduino and ESP32
- From bits to real values: converting sensor readings
- DS3231 RTC clock with Arduino and ESP32
- Linear or switching regulator: which one to choose
- Step-down, step-up and buck-boost: choosing the right module
- Voltage divider: reading voltages higher than 5 V or 3.3 V
- Pull-up, pull-down and bounce: wiring a button properly
- 3.3 V and 5 V logic levels: getting an ESP32 and an Arduino to talk
- The I²C bus: addresses, pull-ups and the scanner
- Lithium batteries: voltages, charging and protection
- ESP32 power consumption and deep sleep: how long the battery lasts
- Temperature sensors compared
- ESP32: which pins to use and which to avoid
- Getting started with the ESP32 in Arduino IDE 2 and PlatformIO
- SSD1306 OLED display and 16×2 I²C LCD with Arduino and ESP32
- PIR motion sensor and HC-SR04 ultrasonic sensor
- Date and time from the Internet with the ESP32: NTP, Italian time zone and daylight saving time
- MicroSD data logger: saving measurements to CSV
- Solar-powered ESP32: panel, charger and the numbers for Italy
- Web server on the ESP32: control a relay from your phone
- Your first LED: wire it up and make it blink
- The button: let Arduino decide
- The potentiometer: turn a knob and read its value
- Soft light: adjusting the brightness of an LED
- The buzzer: playing notes and melodies
- The traffic light: three LEDs in sequence
- The thermometer: reading the temperature with the TMP36
- The servo motor: moving an arm with Arduino
- The night light: the photoresistor and your first decision
- The relay: switching on a light bulb with Arduino
- The LCD display: making Arduino write
- The electronic dice: your first complete project
- The RGB LED: every color from three lights
- The joystick: two-axis control with a shield
- The LED ring: twelve colors from a single pin
- The DC motor: driving it with a MOSFET
- Soil moisture: the plant that asks for water
- The stepper motor: turn exactly as far as you want
- The parking sensor: faster and faster beeps
- The 7-segment display: big, bright numbers
- Automatic watering: the plant that waters itself
- The keypad: a lock with a secret code
- A Telegram bot on the ESP32: your home from your phone
- MQTT with the ESP32: the language of connected devices
- Updating the ESP32 over Wi-Fi (OTA)
- ESP-NOW: two ESP32 boards talking without a router
- WebSocket with the ESP32: the LED that follows the slider
- Readings on a Google sheet, with deep sleep
- WiFiManager: choose the Wi-Fi from your phone
- Bluetooth LE with the ESP32: the phone without a network
- The camera in your browser with the ESP32-S3
- A portable Bluetooth speaker you build yourself
- Bluetooth desktop speakers, 2 × 20 W
- Stereo MP3 player with two buttons
- The shelf that plays music: a Visaton exciter
- How to solder well
- How much does it draw? Current and power with the INA219
- Load-cell scale with the HX711
- CO2 traffic light: when to open the windows
- RFID card door opener
- GPS: track logger on microSD
- LoRa: how far does it go? A range test with two boards
- Battery weather sign with an e-paper display
- Web radio with an ESP32 and an I2S amplifier
- Getting started with the Raspberry Pi Pico 2 in MicroPython
- Your first amplifier: the LM386 and a speaker
- Talking Arduino: the DFR0760 speech synthesis module
- The spectrum of your music on an LED matrix
- Drawing with 64 LEDs: the 8×8 matrix with the MAX7219
- A real voltage: the 12-bit MCP4725 DAC
- The heartbeat on Arduino: an ECG with the AD8232