BME280 web dashboard
The board serves its own web page with live temperature, humidity and pressure and a chart that grows every two seconds.
Wiring
| From | To board | Note |
|---|---|---|
| BME280 VIN or VCC | 3V3 | |
| BME280 GND | GND | |
| BME280 SDA or SDI | GPIO 21 | |
| BME280 SCL or SCK | GPIO 22 |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32 Dev Module
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
const char* WIFI_NAME = "your-network";
const char* WIFI_PASS = "your-password";
Adafruit_BME280 bme;
WebServer server(80);
unsigned long lastLog = 0;
const char PAGE[] = R"html(<!doctype html>
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>BME280</title>
<style>body{font-family:sans-serif;margin:16px;background:#f2f5f1;color:#14231f}b{font-size:2em}
canvas{width:100%;height:160px;background:#fff;border:1px solid #c8d1cb}</style></head>
<body><h1>BME280</h1>
<p><b id="t">--</b> °C <b id="h">--</b> %RH <b id="p">--</b> hPa</p>
<canvas id="c" width="600" height="160"></canvas><p id="s">connecting</p>
<script>
var pts = [], c = document.getElementById('c'), g = c.getContext('2d');
function draw() {
g.clearRect(0, 0, 600, 160);
if (pts.length < 2) return;
var lo = Math.min.apply(null, pts) - 0.5, hi = Math.max.apply(null, pts) + 0.5;
g.beginPath();
pts.forEach(function (v, i) {
var x = i * 600 / (pts.length - 1), y = 160 - (v - lo) * 160 / (hi - lo);
if (i) g.lineTo(x, y); else g.moveTo(x, y);
});
g.strokeStyle = '#c0341a'; g.lineWidth = 2; g.stroke();
}
function tick() {
fetch('/data.json').then(function (r) { return r.json(); }).then(function (d) {
document.getElementById('t').textContent = d.t.toFixed(1);
document.getElementById('h').textContent = d.h.toFixed(0);
document.getElementById('p').textContent = d.p.toFixed(1);
pts.push(d.t); if (pts.length > 150) pts.shift(); draw();
document.getElementById('s').textContent = 'updated ' + new Date().toLocaleTimeString();
}).catch(function () { document.getElementById('s').textContent = 'board not answering'; });
}
tick(); setInterval(tick, 2000);
</script></body></html>)html";
void sendData() {
char json[96];
snprintf(json, sizeof json, "{\"t\":%.2f,\"h\":%.1f,\"p\":%.1f}",
bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0f);
server.send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
if (!bme.begin(0x76, &Wire) && !bme.begin(0x77, &Wire)) {
Serial.println("BME280 not found at 0x76 or 0x77: check wiring, or it may be a BMP280");
for (;;) delay(1000);
}
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_NAME, WIFI_PASS);
Serial.printf("wifi joining %s\n", WIFI_NAME);
while (WiFi.status() != WL_CONNECTED) delay(300);
server.on("/", []() { server.send(200, "text/html", PAGE); });
server.on("/data.json", sendData);
server.begin();
Serial.printf("dashboard at http://%s/\n", WiFi.localIP().toString().c_str());
}
void loop() {
server.handleClient();
if (millis() - lastLog > 10000) {
lastLog = millis();
Serial.printf("t %.2f C h %.1f %% p %.1f hPa\n", bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0f);
}
}Wiring
| From | To board | Note |
|---|---|---|
| BME280 VIN or VCC | 3V3 | |
| BME280 GND | GND | |
| BME280 SDA or SDI | GPIO 8 | |
| BME280 SCL or SCK | GPIO 9 |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32S3 Dev Module
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
const char* WIFI_NAME = "your-network";
const char* WIFI_PASS = "your-password";
Adafruit_BME280 bme;
WebServer server(80);
unsigned long lastLog = 0;
const char PAGE[] = R"html(<!doctype html>
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>BME280</title>
<style>body{font-family:sans-serif;margin:16px;background:#f2f5f1;color:#14231f}b{font-size:2em}
canvas{width:100%;height:160px;background:#fff;border:1px solid #c8d1cb}</style></head>
<body><h1>BME280</h1>
<p><b id="t">--</b> °C <b id="h">--</b> %RH <b id="p">--</b> hPa</p>
<canvas id="c" width="600" height="160"></canvas><p id="s">connecting</p>
<script>
var pts = [], c = document.getElementById('c'), g = c.getContext('2d');
function draw() {
g.clearRect(0, 0, 600, 160);
if (pts.length < 2) return;
var lo = Math.min.apply(null, pts) - 0.5, hi = Math.max.apply(null, pts) + 0.5;
g.beginPath();
pts.forEach(function (v, i) {
var x = i * 600 / (pts.length - 1), y = 160 - (v - lo) * 160 / (hi - lo);
if (i) g.lineTo(x, y); else g.moveTo(x, y);
});
g.strokeStyle = '#c0341a'; g.lineWidth = 2; g.stroke();
}
function tick() {
fetch('/data.json').then(function (r) { return r.json(); }).then(function (d) {
document.getElementById('t').textContent = d.t.toFixed(1);
document.getElementById('h').textContent = d.h.toFixed(0);
document.getElementById('p').textContent = d.p.toFixed(1);
pts.push(d.t); if (pts.length > 150) pts.shift(); draw();
document.getElementById('s').textContent = 'updated ' + new Date().toLocaleTimeString();
}).catch(function () { document.getElementById('s').textContent = 'board not answering'; });
}
tick(); setInterval(tick, 2000);
</script></body></html>)html";
void sendData() {
char json[96];
snprintf(json, sizeof json, "{\"t\":%.2f,\"h\":%.1f,\"p\":%.1f}",
bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0f);
server.send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
Wire.begin(8, 9);
if (!bme.begin(0x76, &Wire) && !bme.begin(0x77, &Wire)) {
Serial.println("BME280 not found at 0x76 or 0x77: check wiring, or it may be a BMP280");
for (;;) delay(1000);
}
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_NAME, WIFI_PASS);
Serial.printf("wifi joining %s\n", WIFI_NAME);
while (WiFi.status() != WL_CONNECTED) delay(300);
server.on("/", []() { server.send(200, "text/html", PAGE); });
server.on("/data.json", sendData);
server.begin();
Serial.printf("dashboard at http://%s/\n", WiFi.localIP().toString().c_str());
}
void loop() {
server.handleClient();
if (millis() - lastLog > 10000) {
lastLog = millis();
Serial.printf("t %.2f C h %.1f %% p %.1f hPa\n", bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0f);
}
}Wiring
| From | To board | Note |
|---|---|---|
| BME280 VIN or VCC | 3V3 | |
| BME280 GND | GND | |
| BME280 SDA or SDI | GPIO 6 | |
| BME280 SCL or SCK | GPIO 7 |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32C3 Dev Module
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
const char* WIFI_NAME = "your-network";
const char* WIFI_PASS = "your-password";
Adafruit_BME280 bme;
WebServer server(80);
unsigned long lastLog = 0;
const char PAGE[] = R"html(<!doctype html>
<html><head><meta name="viewport" content="width=device-width,initial-scale=1"><title>BME280</title>
<style>body{font-family:sans-serif;margin:16px;background:#f2f5f1;color:#14231f}b{font-size:2em}
canvas{width:100%;height:160px;background:#fff;border:1px solid #c8d1cb}</style></head>
<body><h1>BME280</h1>
<p><b id="t">--</b> °C <b id="h">--</b> %RH <b id="p">--</b> hPa</p>
<canvas id="c" width="600" height="160"></canvas><p id="s">connecting</p>
<script>
var pts = [], c = document.getElementById('c'), g = c.getContext('2d');
function draw() {
g.clearRect(0, 0, 600, 160);
if (pts.length < 2) return;
var lo = Math.min.apply(null, pts) - 0.5, hi = Math.max.apply(null, pts) + 0.5;
g.beginPath();
pts.forEach(function (v, i) {
var x = i * 600 / (pts.length - 1), y = 160 - (v - lo) * 160 / (hi - lo);
if (i) g.lineTo(x, y); else g.moveTo(x, y);
});
g.strokeStyle = '#c0341a'; g.lineWidth = 2; g.stroke();
}
function tick() {
fetch('/data.json').then(function (r) { return r.json(); }).then(function (d) {
document.getElementById('t').textContent = d.t.toFixed(1);
document.getElementById('h').textContent = d.h.toFixed(0);
document.getElementById('p').textContent = d.p.toFixed(1);
pts.push(d.t); if (pts.length > 150) pts.shift(); draw();
document.getElementById('s').textContent = 'updated ' + new Date().toLocaleTimeString();
}).catch(function () { document.getElementById('s').textContent = 'board not answering'; });
}
tick(); setInterval(tick, 2000);
</script></body></html>)html";
void sendData() {
char json[96];
snprintf(json, sizeof json, "{\"t\":%.2f,\"h\":%.1f,\"p\":%.1f}",
bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0f);
server.send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
Wire.begin(6, 7);
if (!bme.begin(0x76, &Wire) && !bme.begin(0x77, &Wire)) {
Serial.println("BME280 not found at 0x76 or 0x77: check wiring, or it may be a BMP280");
for (;;) delay(1000);
}
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_NAME, WIFI_PASS);
Serial.printf("wifi joining %s\n", WIFI_NAME);
while (WiFi.status() != WL_CONNECTED) delay(300);
server.on("/", []() { server.send(200, "text/html", PAGE); });
server.on("/data.json", sendData);
server.begin();
Serial.printf("dashboard at http://%s/\n", WiFi.localIP().toString().c_str());
}
void loop() {
server.handleClient();
if (millis() - lastLog > 10000) {
lastLog = millis();
Serial.printf("t %.2f C h %.1f %% p %.1f hPa\n", bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0f);
}
}Parts
| Quantity | Part | Where to buy |
|---|---|---|
| 1× | ESP32 dev board | Buy at Amazon |
| 1× | BME280 breakout, I2C | Buy at Adafruit |
| 4× | Jumper wires |
Amazon buy links may include an Associates tag, and this site may earn a commission from qualifying Amazon purchases at no extra cost to you. Adafruit links are direct.
Libraries
Adafruit BME280 Library, Adafruit Unified Sensor, WiFi (built in), WebServer (built in)
Load it
- In the Arduino IDE, install the esp32 board package and pick the board named above the sketch.
- Install the libraries listed above from the Library Manager.
- Paste the sketch, choose the board's port and upload. Hold BOOT if the upload stalls at Connecting.
- Close the IDE's serial monitor, then open the bench here to watch the output.
Field notes
- Open the address the bench prints, such as http://192.168.1.80/, from any phone or computer on the same network. The chart keeps the last five minutes in the browser.
- Many cheap purple breakouts sold as BME280 carry a BMP280, which has no humidity sensor. The library refuses it, and the sketch says so. The chip markings tell them apart.
- Address 0x76 or 0x77 depends on the board's SDO pin; the sketch tries both. Run the I2C scanner project if neither answers.
- The BME280 warms itself a little when read constantly. Readings every two seconds are fine; for accurate room temperature, keep the sensor away from the ESP32's own heat.
- /data.json is plain JSON, so Home Assistant's REST sensor or a script can read it too.
Not tested yet
- Not tested on hardware yet. Compiles for every board listed with Arduino-ESP32 core 3.3.12.
Built it? Send a build report with your board and what the serial port printed. Working reports become the "Tested on this board" entries above.