Комнатный Wi-Fi-монитор на ESP32
В этом проекте ESP32 измеряет температуру, влажность и давление датчиком BME280. Плата подключается к домашней Wi-Fi-сети и показывает данные на локальной веб-странице. С той же страницы можно включить светодиод.

Фото реального результата: educ8s, ESP32 Web Server with BME280 Sensor.

Схема автора исходного проекта. Таблица ниже повторяет те же сигнальные выводы GPIO27, GPIO26 и GPIO32 и является основной проверкой монтажа.
Результат
После включения ESP32 печатает в монитор порта адрес, например:
Wi-Fi connected
Open: http://192.168.1.54/
Если телефон или компьютер подключён к тому же маршрутизатору, этот адрес открывает страницу с текущими измерениями и кнопками светодиода. Данные обновляются без перезагрузки страницы.
Устройство работает только внутри локальной сети. Настраивать переадресацию портов на маршрутизаторе не нужно.
Детали
| Деталь | Количество | Точная конфигурация |
|---|---|---|
| ESP32 DevKit V1 | 1 | модуль ESP-WROOM-32, USB-разъём и стабилизатор на плате |
| BME280 | 1 | четырёхвыводный I2C-модуль, рассчитанный на 3,3 В |
| Красный светодиод 5 мм | 1 | обычный индикаторный |
| Резистор 220 Ом | 1 | последовательно со светодиодом |
| Макетная плата и провода | 1 набор | соединения |
| USB-кабель с передачей данных | 1 | питание и прошивка |
| Wi-Fi 2,4 ГГц | 1 сеть | имя и пароль должны быть известны |
ESP32 не подключается к сети только 5 ГГц. Маршрутизатор может одновременно раздавать 2,4 и 5 ГГц — это нормально.
Безопасность
- Питайте датчик BME280 от 3V3, а не от 5V.
- Никогда не подключайте светодиод без последовательного резистора.
- Не публикуйте скетч с настоящим паролем Wi-Fi.
- Веб-сервер не имеет авторизации. Не выводите его в Интернет и не подключайте вместо светодиода опасную нагрузку.
- При любых изменениях монтажа отключайте USB.
Схема соединений
| Откуда | Куда |
|---|---|
| ESP32 3V3 | BME280 VCC или VIN |
| ESP32 GND | BME280 GND |
| ESP32 GPIO27 | BME280 SDA или SDI |
| ESP32 GPIO26 | BME280 SCL или SCK |
| ESP32 GPIO32 | резистор 220 Ом |
| второй вывод резистора | длинная ножка светодиода, анод |
| короткая ножка светодиода, катод | ESP32 GND |
У разных плат порядок выводов BME280 отличается. Читайте подписи на самой плате. Убедитесь, что это BME280: похожий BMP280 не измеряет влажность.
Сборка
- Установите ESP32 поперёк центрального разрыва макетной платы.
- Подключите BME280 четырьмя проводами.
- Установите светодиод так, чтобы его ножки находились в разных рядах.
- Соедините GPIO32 с анодом только через резистор 220 Ом.
- Проверьте отсутствие перемычки между 3V3 и GND.
- Подключите USB и не трогайте схему во время работы.
Подготовка Arduino IDE
- Откройте Файл → Настройки.
- Добавьте в поле адресов менеджера плат официальный адрес:
https://espressif.github.io/arduino-esp32/package_esp32_index.json
- В менеджере плат установите пакет esp32 by Espressif Systems.
- Выберите ESP32 Dev Module и правильный последовательный порт.
- В менеджере библиотек установите Adafruit BME280 Library и Adafruit Unified Sensor.
Программа
В первых двух строках с данными сети замените учебные значения на имя и пароль своей сети. Кавычки оставьте.
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
const char *WIFI_NAME = "YOUR_WIFI_NAME";
const char *WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const byte SDA_PIN = 27;
const byte SCL_PIN = 26;
const byte LED_PIN = 32;
WebServer server(80);
Adafruit_BME280 bme;
float temperature = 0;
float humidity = 0;
float pressure = 0;
unsigned long lastMeasurementAt = 0;
const unsigned long MEASUREMENT_PERIOD_MS = 2000;
const char PAGE[] PROGMEM = R"HTML(
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Комнатный монитор ESP32</title>
<style>
body { font: 18px sans-serif; max-width: 620px; margin: 2rem auto;
padding: 0 1rem; background: #eef3f7; color: #17202a; }
main { background: white; padding: 1.5rem; border-radius: 16px; }
.value { font-size: 1.4rem; margin: .8rem 0; }
button { font-size: 1rem; padding: .7rem 1rem; margin-right: .5rem; }
</style>
</head>
<body>
<main>
<h1>Комнатный монитор</h1>
<div class="value">Температура: <span id="t">—</span> °C</div>
<div class="value">Влажность: <span id="h">—</span> %</div>
<div class="value">Давление: <span id="p">—</span> гПа</div>
<div class="value">Светодиод: <span id="led">—</span></div>
<button onclick="setLed(1)">Включить</button>
<button onclick="setLed(0)">Выключить</button>
</main>
<script>
async function update() {
const response = await fetch('/api');
const data = await response.json();
document.getElementById('t').textContent = data.temperature.toFixed(1);
document.getElementById('h').textContent = data.humidity.toFixed(1);
document.getElementById('p').textContent = data.pressure.toFixed(1);
document.getElementById('led').textContent = data.led ? 'включён' : 'выключен';
}
async function setLed(state) {
await fetch('/led?state=' + state);
update();
}
update();
setInterval(update, 2000);
</script>
</body>
</html>
)HTML";
void readSensor() {
temperature = bme.readTemperature();
humidity = bme.readHumidity();
pressure = bme.readPressure() / 100.0F;
}
void handleRoot() {
server.send_P(200, "text/html; charset=utf-8", PAGE);
}
void handleApi() {
String json = "{";
json += "\"temperature\":" + String(temperature, 2);
json += ",\"humidity\":" + String(humidity, 2);
json += ",\"pressure\":" + String(pressure, 2);
json += ",\"led\":";
json += digitalRead(LED_PIN) == HIGH ? "true" : "false";
json += "}";
server.send(200, "application/json", json);
}
void handleLed() {
if (!server.hasArg("state")) {
server.send(400, "text/plain", "state is required");
return;
}
digitalWrite(LED_PIN, server.arg("state") == "1" ? HIGH : LOW);
server.send(204);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
Wire.begin(SDA_PIN, SCL_PIN);
bool found = bme.begin(0x76);
if (!found) found = bme.begin(0x77);
if (!found) {
Serial.println("BME280 not found. Check wiring.");
while (true) delay(100);
}
readSensor();
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_NAME, WIFI_PASSWORD);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("Wi-Fi connected");
Serial.print("Open: http://");
Serial.print(WiFi.localIP());
Serial.println("/");
server.on("/", handleRoot);
server.on("/api", handleApi);
server.on("/led", handleLed);
server.onNotFound([]() {
server.send(404, "text/plain", "Not found");
});
server.begin();
}
void loop() {
server.handleClient();
unsigned long now = millis();
if (now - lastMeasurementAt >= MEASUREMENT_PERIOD_MS) {
lastMeasurementAt = now;
readSensor();
}
}
Первый запуск
- Загрузите программу. Если IDE долго показывает Connecting..., нажмите и удерживайте кнопку BOOT на ESP32 до начала записи.
- Откройте монитор порта на 115200 бод и нажмите EN или RESET.
- Дождитесь строки с IP-адресом.
- Подключите телефон к той же Wi-Fi-сети и откройте адрес целиком, включая точки.
- Сравните значения на странице и в комнате.
- Нажмите кнопки включения и выключения — внешний светодиод должен изменить состояние.
- Закройте страницу, снова откройте её и убедитесь, что сервер продолжает работать.
Проверка API
Откройте адрес вида:
http://192.168.1.54/api
Вместо примера подставьте адрес своей платы. Ответ выглядит так:
{"temperature":23.41,"humidity":45.20,"pressure":1008.62,"led":false}
Именно этот адрес каждые две секунды запрашивает веб-страница.
Диагностика
| Симптом | Что проверить |
|---|---|
| BME280 not found | питание 3,3 В, GPIO27/GPIO26, адрес 0x76/0x77, тип датчика |
| Бесконечные точки Connecting | имя и пароль, наличие сети 2,4 ГГц, расстояние до маршрутизатора |
| IP есть, страница не открывается | телефон в той же локальной сети, мобильный VPN отключён, адрес введён без ошибок |
| Страница есть, данные не меняются | откройте /api и монитор порта, проверьте соединение BME280 |
| Светодиод не горит | полярность светодиода, резистор и провод GPIO32 |
| Плата постоянно перезапускается | другой USB-кабель или блок питания; проверьте отсутствие короткого замыкания |
Практика
- Откройте /api с двух устройств одновременно и сравните ответы.
- Измените период обновления страницы с 2000 на 5000 мс и наблюдайте сетевые запросы в инструментах разработчика браузера.
- Переместите датчик дальше от ESP32 и сравните температуру через 15 минут.
Проверьте себя
- Почему BME280 питается от 3,3 В?
- Чем локальный IP-адрес отличается от адреса сайта в Интернете?
- Для чего нужен путь /api?
- Где в скетче хранится пароль Wi-Fi и почему его нельзя публиковать?
- Что делает server.handleClient?
Словарь статьи
- ESP32 — микроконтроллер со встроенными Wi-Fi и Bluetooth.
- Локальная сеть — устройства, соединённые через один домашний или школьный маршрутизатор.
- IP-адрес — сетевой адрес устройства.
- HTTP-сервер — программа, принимающая запросы браузера.
- API — здесь отдельный адрес, возвращающий данные в JSON.
- JSON — текстовый формат с именами и значениями.
Связанные темы
Источники
- educ8s: ESP32 Web Server with BME280 Sensor — исходная работающая сборка, фотография и схема.
- Espressif: Arduino-ESP32 WebServer example — официальный пример локального HTTP-сервера.
- Espressif: установка Arduino-ESP32 — официальный пакет плат и адрес менеджера.
- Bosch Sensortec: BME280 datasheet — техническое описание датчика.