Wi-Fi · веб-сервер

Веб-сервер на Arduino: страница управления в браузере

Плата отдаёт браузеру обычную веб-страницу с кнопками — и телефон становится пультом, без единого установленного приложения. Разбираем, что при этом происходит по шагам, где держать страницу, почему она иногда «висит» и откуда её вообще можно открыть. Схемы живые, все три скетча собраны компилятором.

Короткий ответ. Плата с Wi-Fi умеет быть маленьким сайтом: открываете в браузере её адрес — видите свою страницу с кнопками. Приложение не нужно, работает с любого телефона и компьютера. Но только пока вы дома: из интернета такая страница не открывается — домашний роутер прячет плату. Как достать её издалека, разобрано в статье про MQTT.

Что такое «сервер на плате» и почему это не сайт

Слово «сервер» звучит внушительно, но означает простую вещь: программа, которая ждёт, когда к ней обратятся, и отвечает. Ваша плата всё время слушает: не постучится ли кто-нибудь. Когда браузер стучится и просит страницу, плата собирает текст этой страницы и отправляет его. Всё.

Сайтом это не становится по двум причинам. Во-первых, у платы нет имени в интернете — у неё есть только внутренний адрес вида 192.168.1.47, который существует лишь внутри вашей квартиры. Во-вторых, снаружи её закрывает роутер. Поэтому правильнее думать об этом так: вы делаете не сайт, а пульт, который открывается в браузере. И это очень удобно: ничего не надо ставить ни на телефон, ни на компьютер, работает и на Android, и на iPhone, и на ноутбуке.

Перед этой страницей стоит прочитать как плату вообще подключить к сети: здесь мы считаем, что плата уже в сети и напечатала свой адрес в мониторе порта.

Что происходит, когда вы набрали адрес платы

Разберём по шагам — почти каждая непонятная мелочь дальше объясняется именно этой последовательностью.

Шаг 1 из 8
Шаг 1. Вы набрали адрес платыБраузер телефонаВ адресной строке 192.168.1.47 —тот самый адрес, который платанапечатала в мониторе портаПлата с серверомСервер поднят и ждёт: слушаетпорт 80пока ничего не летит12345678Вы набрали адрес платы. Сервер поднят и ждёт: слушает порт 80
Восемь шагов от набранного адреса до нажатой кнопки. Шаг шестой объясняет странность, о которой все спотыкаются: браузер сам просит значок вкладки, и если плата на это молчит, страница открывается медленно.

Из этой картинки сразу следуют две практические вещи. Первая: плата обязана отвечать на всё, о чём её спрашивают, даже на запрос значка вкладки — иначе браузер будет терпеливо ждать несколько секунд, и страница откроется «через раз». Вторая: пока плата собирает и отправляет страницу, она не делает ничего другого. Значит, страница должна быть маленькой, а программа не должна её задерживать.

Самый маленький сервер: две ссылки и светодиод

Начнём с варианта, который умещается на экране и работает сразу. Страница здесь собирается прямо в памяти, а кнопки — обычные ссылки.

// Самый маленький веб-сервер: страница с двумя ссылками.
// Открываете адрес платы в браузере телефона - и щёлкаете светодиодом.
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
ESP8266WebServer server(80);
#define STATUS_LED LED_BUILTIN
#define LED_ON LOW
#define LED_OFF HIGH
#else
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
#define STATUS_LED 2
#define LED_ON HIGH
#define LED_OFF LOW
#endif

const char WIFI_SSID[] = "имя вашей сети";
const char WIFI_PASS[] = "пароль от сети";

bool lampOn = false;

void applyLamp() {
  digitalWrite(STATUS_LED, lampOn ? LED_ON : LED_OFF);
}

// Страница собирается прямо в памяти. Кавычки внутри - одинарные:
// так не приходится их экранировать.
void handleRoot() {
  String page = "<!DOCTYPE html><html lang='ru'><head><meta charset='utf-8'>";
  page += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
  page += "<title>Плата</title></head><body style='font-family:sans-serif'>";
  page += "<h1>Светодиод на плате</h1>";
  page += lampOn ? "<p>Сейчас: горит</p>" : "<p>Сейчас: не горит</p>";
  page += "<p><a href='/on'>Включить</a> &nbsp; <a href='/off'>Выключить</a></p>";
  page += "</body></html>";
  server.send(200, "text/html; charset=utf-8", page);
}

// После нажатия отправляем браузер обратно на главную: иначе он останется
// на пустой странице /on и покажет её адрес.
void handleSwitch(bool on) {
  lampOn = on;
  applyLamp();
  server.sendHeader("Location", "/");
  server.send(303);
}

void setup() {
  Serial.begin(115200);
  pinMode(STATUS_LED, OUTPUT);
  applyLamp();

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Наберите в браузере адрес: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/on", []() { handleSwitch(true); });
  server.on("/off", []() { handleSwitch(false); });
  // Браузер всегда просит значок вкладки. Если не ответить - будет ждать.
  server.onNotFound([]() { server.send(404, "text/plain", "нет такой страницы"); });
  server.begin();
}

void loop() {
  // Эту строчку нельзя пропускать: именно она обслуживает браузер.
  // Любой длинный delay() в loop() делает страницу «висящей».
  server.handleClient();
}

Три строчки в этом скетче стоит разобрать отдельно.

  • server.on("/", handleRoot) — «когда попросят корень сайта, вызови вот эту функцию». Так же вешаются и остальные адреса.
  • server.send(200, ...) — ответ. Число 200 означает «всё в порядке, вот твоя страница». Тип text/html; charset=utf-8 обязателен: без указания кодировки русские буквы превратятся в кракозябры.
  • server.handleClient() в loop() — та самая строчка, ради которой всё работает. Именно она проверяет, не постучался ли кто-то, и вызывает ваши обработчики.

Обратите внимание на ответ с кодом 303 после нажатия: он говорит браузеру «вернись на главную». Без него человек останется на пустой странице /on и будет недоумевать.

Почему страница моргает и как это лечится

У варианта со ссылками есть заметный недостаток: на каждое нажатие браузер уходит на другой адрес и получает всю страницу заново. Экран моргает, показания «прыгают», а если на странице было поле ввода — его содержимое пропадает.

Нажали ссылку — страница уехала и вернулась целикомбелый экранТелефонПлатасобирает всю страницуGET /onвся страница заново, около 340 байтПросто, понятно и годится для проверки. Но на каждое нажатие плата заново собирает всюстраницу, экран моргает, а поле ввода, если оно было, очищается.
Толщина обратной стрелки — это объём ответа. Разница не только в красоте: страница, которая перезагружается на каждое нажатие, на слабом сигнале начинает заметно тормозить.

Лечится это тем, что кнопка перестаёт быть ссылкой. Вместо перехода она отправляет короткий запрос и остаётся на месте. А показания страница спрашивает сама, раз в секунду, и подставляет свежие числа в уже нарисованный текст.

Второе улучшение в следующем скетче — страница переехала из оперативной памяти во флеш-память. Это важнее, чем кажется: у ESP8266 свободной оперативной памяти около сорока килобайт, и красивая страница со стилями съедает её заметную часть именно в тот момент, когда плата и так занята.

// Панель управления, которая не перезагружает страницу.
// Кнопки шлют короткие запросы, а показания обновляются раз в секунду.
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
ESP8266WebServer server(80);
#define STATUS_LED LED_BUILTIN
#define LED_ON LOW
#define LED_OFF HIGH
#else
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
#define STATUS_LED 2
#define LED_ON HIGH
#define LED_OFF LOW
#endif

const char WIFI_SSID[] = "имя вашей сети";
const char WIFI_PASS[] = "пароль от сети";

bool lampOn = false;

// Страница лежит во флеш-памяти, а не в оперативной: PROGMEM и send_P.
// Внутри только одинарные кавычки - чтобы ничего не экранировать.
const char PAGE_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang='ru'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Панель платы</title>
<style>
body{font-family:sans-serif;margin:24px;max-width:420px}
button{font-size:18px;padding:12px 20px;margin-right:8px}
.val{font-size:22px;margin:16px 0}
</style>
</head>
<body>
<h1>Панель платы</h1>
<div class='val'>Светодиод: <b id='lamp'>...</b></div>
<button onclick='cmd(1)'>Включить</button>
<button onclick='cmd(0)'>Выключить</button>
<div class='val'>Плата работает: <b id='up'>...</b> с</div>
<script>
function cmd(v){ fetch('/set?led=' + v).then(refresh); }
function refresh(){
  fetch('/state').then(function(r){ return r.text(); }).then(function(t){
    var p = t.split(';');
    document.getElementById('lamp').textContent = p[0] === '1' ? 'горит' : 'не горит';
    document.getElementById('up').textContent = p[1];
  });
}
setInterval(refresh, 1000);
refresh();
</script>
</body>
</html>
)rawliteral";

void applyLamp() {
  digitalWrite(STATUS_LED, lampOn ? LED_ON : LED_OFF);
}

void handleRoot() {
  server.send_P(200, "text/html; charset=utf-8", PAGE_HTML);
}

// Состояние отдаём двумя числами через точку с запятой: для двух значений
// городить JSON незачем, а разобрать такую строку браузер умеет сам.
void handleState() {
  String answer = lampOn ? "1;" : "0;";
  answer += String(millis() / 1000);
  server.send(200, "text/plain", answer);
}

void handleSet() {
  if (server.hasArg("led")) {
    lampOn = (server.arg("led") == "1");
    applyLamp();
  }
  server.send(200, "text/plain", "ok");
}

void setup() {
  Serial.begin(115200);
  pinMode(STATUS_LED, OUTPUT);
  applyLamp();

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Наберите в браузере адрес: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/state", handleState);
  server.on("/set", handleSet);
  server.onNotFound([]() { server.send(404, "text/plain", "нет такой страницы"); });
  server.begin();
}

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

Состояние здесь отдаётся не в модном формате, а двумя числами через точку с запятой: 1;432 означает «светодиод горит, плата работает 432 секунды». Для двух значений городить что-то сложнее незачем, а разобрать такую строку страница умеет сама.

И маленькая, но важная деталь: внутри страницы использованы только одинарные кавычки. Так текст страницы можно писать в скетче как есть, не расставляя перед каждой кавычкой обратную косую черту. Это тот приём, из-за отсутствия которого чужие примеры выглядят пугающе.

Где держать саму страницу

Три способа, и выбор между ними зависит от того, насколько сложной будет ваша панель.

СпособРазумный размерПлюсыМинусы
Собирать в переменной Stringсотни байтПроще всего: страницу можно составлять из кусочков и подставлять в неё числаЖивёт в оперативной памяти и режет её на кусочки. Большая страница легко роняет плату
Держать во флеш-памяти (PROGMEM)десятки килобайтОперативную память не трогает совсем, отдаётся одной строчкой send_PСтраница вшита в прошивку: чтобы поправить кнопку, нужно перепрошить плату
Класть файлами в файловую системусотни килобайтМожно хранить картинки, стили и несколько страниц, менять их отдельно от программыФайлы нужно заливать в плату отдельным действием, и часть памяти уходит под саму файловую систему

Практическое правило: пока страница помещается в один экран кода — держите её в памяти, так проще подставлять числа. Как только появились стили и несколько кнопок — переносите во флеш-память. Файловая система нужна тогда, когда на странице появляются картинки или вы хотите менять оформление, не трогая программу.

И общий совет: не тащите на страницу платы внешние библиотеки оформления. Они весят сотни килобайт, грузятся из интернета, а в домашней сети без интернета не загрузятся вовсе — и панель откроется голой.

Откуда эта страница откроется, а откуда нет

Самый частый вопрос после первого успеха: «дома работает, а с улицы нет — что я сделал не так?» Ничего. Так устроены домашние сети.

Телефон подключён к тому же роутеруТелефонРоутерПлатаОткроетсяПлата и телефон в одной сети, роутер их видит и соединяет напрямую.Наберите адрес, который плата напечатала в мониторе порта.
Пять ситуаций, из которых люди пробуют открыть страницу платы. Две из них не работают никогда, и это не поломка, а устройство домашних сетей.

Внутренний адрес платы существует только внутри вашей квартиры. Роутер выпускает всех наружу под одним общим внешним адресом, но снаружи внутрь никого не пускает — это его работа. Поэтому из мобильного интернета или из другого города страница не откроется, и дело тут не в коде.

Соблазн решить это «пробросом порта» на роутере лучше сразу отложить. Проброс выставляет наружу устройство, у которого нет ни пароля, ни проверки прав, и такие устройства находят автоматически, за часы. Правильный способ дотянуться до поделки издалека — посредник, и ему посвящена следующая статья.

Чтобы адрес не менялся

Роутер выдаёт плате адрес в аренду, и после перезагрузки адрес может стать другим. Каждый раз лезть в монитор порта неудобно. Есть три способа.

  • Закрепить адрес в роутере. Самый надёжный: в настройках находите плату по имени или по её аппаратному адресу и говорите «всегда выдавай ей вот этот». Плата при этом ничего не знает и скетч не меняется.
  • Задать адрес в скетче методом WiFi.config(). Работает, но требует аккуратности: адрес должен быть из диапазона вашей сети и не должен совпадать с чужим.
  • Дать плате имя вида plata.local. Красиво, но ненадёжно: такие имена понимает не всякий телефон — Android научился этому только с двенадцатой версии, а многие роутеры режут нужную для этого групповую рассылку. Как запасной путь — годится, как основной — нет.

Когда роутера нет: плата раздаёт свою сеть

Отдельный и очень полезный режим: плата сама становится точкой доступа. Телефон видит её сеть в списке, подключается — и открывает панель. Интернета в такой сети нет, но для управления он и не нужен.

Так делают поделки для улицы, теплицы и автомобиля, а ещё — первичную настройку: плата поднимает свою сеть, вы заходите на страницу и вписываете туда имя и пароль домашней сети. Именно так настраиваются готовые умные розетки.

// Плата раздаёт свою собственную сеть Wi-Fi.
// Роутер не нужен вообще: телефон подключается прямо к плате.
// Так делают настройку «в поле» и поделки для улицы.
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
ESP8266WebServer server(80);
#else
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
#endif

// Имя сети, которую увидит телефон, и пароль к ней.
// Пароль обязан быть не короче восьми знаков, иначе сеть будет открытой.
const char AP_SSID[] = "Plata-Trema";
const char AP_PASS[] = "12345678";

void handleRoot() {
  String page = "<!DOCTYPE html><html lang='ru'><head><meta charset='utf-8'>";
  page += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
  page += "<title>Плата</title></head><body style='font-family:sans-serif'>";
  page += "<h1>Плата раздаёт свою сеть</h1>";
  page += "<p>Подключено устройств: ";
  page += String(WiFi.softAPgetStationNum());
  page += "</p></body></html>";
  server.send(200, "text/html; charset=utf-8", page);
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println();

  WiFi.mode(WIFI_AP);
  WiFi.softAP(AP_SSID, AP_PASS);

  Serial.print("Сеть поднята. Адрес платы: ");
  Serial.println(WiFi.softAPIP());   // почти всегда 192.168.4.1

  server.on("/", handleRoot);
  server.begin();
}

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

Адрес платы в этом режиме почти всегда 192.168.4.1. Пароль обязан быть не короче восьми знаков — иначе сеть получится открытой, и подключиться к вашей поделке сможет любой прохожий.

Главный враг сервера — delay()

Плата делает одно дело за раз. Пока программа стоит в delay(1000), она не вызывает server.handleClient() — а значит, не отвечает браузеру. Со стороны это выглядит как «страница подвисает», хотя плата совершенно здорова.

Правило простое: loop() должен пробегать за миллисекунды. Всё, что нужно делать «раз в секунду», делается по часам:

  • запоминаем момент последнего действия в переменную и сравниваем с millis() — приём разобран в уроках и в статье про сторож связи;
  • длинные вычисления режем на куски, а внутри тяжёлых циклов вызываем delay(0) — это не пауза, а способ дать радио обслужить себя;
  • датчики, которые «думают» долго (например, ультразвуковой дальномер), опрашиваем не в каждом проходе, а по расписанию.

Отсюда же ответ на вопрос «сколько человек могут открыть страницу одновременно». Формально несколько, фактически — сервер обслуживает их по очереди, и если страница тяжёлая, второй посетитель заметит задержку. Для домашнего пульта это не проблема, для класса из тридцати учеников — уже да.

Честно про безопасность

Страница платы открыта всем, кто уже подключён к вашей сети. Никакой проверки прав в примерах выше нет — и это нормально для настольной лампы, но не для замка на двери.

Простейшую защиту дают встроенные средства: в начале обработчика вызывается server.authenticate("имя", "пароль"), и если проверка не прошла — server.requestAuthentication(). Браузер тогда сам покажет окошко с логином и паролем. Отдельная библиотека для этого не нужна.

Но иллюзий строить не стоит. Обычный веб-запрос идёт открытым текстом: тот, кто уже в вашей сети, при желании прочитает и пароль. Защищённое соединение плата тоже умеет, но браузер будет ругаться на самодельный сертификат, а на ESP8266 это ещё и заметно тяжело для памяти. Практический вывод: веб-панель на плате — это домашнее удобство, а не система безопасности. Для замков и ворот нужен другой подход.

Что делать, если не работает

Что видноЧто на самом делеЧто сделать
Страница вообще не открывается, браузер пишет, что соединение отклоненоСервер не запущен или плата не в сетиПроверьте, что в setup() есть server.begin(), а в мониторе порта напечатался адрес
Адрес набран, но браузер думает и сдаётсяТелефон и плата в разных сетях: гостевая сеть, другой диапазон, мобильный интернетОба устройства в одну и ту же сеть. Проверьте, что телефон не ушёл в мобильный интернет
Вместо русских букв кракозябрыБраузеру не сказали, в какой кодировке текстОтдавайте тип «text/html; charset=utf-8» и держите в самой странице строчку meta charset
Страница открывается по несколько секундПлата молчит на запрос значка вкладки, и браузер ждёт до таймаутаПовесьте обработчик onNotFound — пусть коротко отвечает на всё незнакомое
Кнопка нажимается, но ничего не меняетсяОбработчик не зарегистрирован или имя параметра в скетче другоеСверьте адрес в кнопке и строку server.on(). Проверить можно прямо в адресной строке браузера
Страница есть, а показания не обновляютсяloop() занят долгой работой и не вызывает handleClient()Уберите delay(), считайте время по millis(). Правило: loop() должен пробегать за миллисекунды
Плата перезагружается, как только открываешь страницуСтраница собирается в оперативной памяти и не помещаетсяПеренесите её во флеш-память (PROGMEM и send_P) или разбейте на части
Страница обрывается на серединеНе хватило памяти на отправку или клиент отвалилсяУменьшите страницу, уберите картинки, отдавайте показания отдельным коротким запросом
С компьютера открывается, с телефона нетНа компьютере набрано имя вида «плата.local», а телефон такие имена не понимаетОткройте по числовому адресу и закрепите его за платой в настройках роутера
Работает несколько минут и перестаётСвязь оборвалась, а скетч этого не заметилДобавьте сторож связи из статьи про подключение к сети

Что купить

Проверено 24 августа 2026 года: каждая карточка открыта отдельно, цена и артикул взяты со страницы товара. Того, чего нет в наличии, в подборке нет.

WeMos D1 mini на ESP8266

Для веб-панели этой платы хватает с запасом: страница с кнопками и парой показаний работает на ней годами. Самый дешёвый способ получить пульт в браузере.

290 ₽
Открыть карточку
арт. 867 · цена и наличие на 24.08.2026

Piranha ESP32 v2

Когда панель обрастает стилями, картинками и несколькими страницами, запас памяти ESP32 перестаёт быть роскошью. Плюс второе ядро: радио не мешает вашей программе.

690 ₽
Открыть карточку
арт. 1453 · цена и наличие на 24.08.2026

Источник питания 5 В, 2 А

Панель, которая должна быть доступна круглосуточно, не может висеть на USB-порту компьютера. Отдельное питание с запасом — половина надёжности проекта.

986 ₽
Открыть карточку
арт. 491 · цена и наличие на 24.08.2026

Провода «папа-мама», 40 штук

Соединить плату с реле, датчиком или светодиодом. Длина 20 см, десять цветов — путаницы будет меньше.

161 ₽
Открыть карточку
арт. 361 · цена и наличие на 24.08.2026

Макетная плата на 830 точек

Собрать схему без паяльника: плата, кнопка, светодиод и пара резисторов держатся в ней сами.

160 ₽
Открыть карточку
арт. 169 · цена и наличие на 24.08.2026

Дальше по теме

Частые вопросы

Как узнать адрес платы, чтобы открыть её страницу?

Плата печатает его сама. После подключения к сети вызовите WiFi.localIP() и выведите результат в монитор порта — там появится строчка вида 192.168.1.47. Этот адрес и набирают в браузере телефона или компьютера. Если адрес каждый раз новый, закрепите его за платой в настройках роутера: там есть список подключённых устройств и кнопка «всегда выдавать этот адрес». Способ с именем вида «плата.local» тоже существует, но он ненадёжен: такие имена понимает не всякий телефон.

Почему страница платы не открывается с мобильного интернета?

Потому что внутренний адрес вида 192.168.х.х существует только внутри вашей домашней сети. Роутер выпускает устройства наружу под одним общим внешним адресом, но снаружи внутрь никого не пускает — это его прямая работа. Из мобильного интернета, с работы или из другого города страница не откроется, и дело не в коде. Пробрасывать порт на роутере не стоит: наружу окажется устройство без пароля и без проверки прав. Правильный способ дотянуться до поделки издалека — посредник, то есть MQTT или бот в мессенджере.

Почему вместо русских букв на странице кракозябры?

Браузеру не сказали, в какой кодировке текст. Нужны две вещи сразу: во-первых, отдавать тип «text/html; charset=utf-8» третьим аргументом в server.send(); во-вторых, держать внутри самой страницы строчку с указанием кодировки в разделе head. Если страница собирается из кусочков в переменной String, проверьте, что эта строчка попала в самое начало, до текста.

Можно ли поставить пароль на страницу платы?

Да, и отдельная библиотека для этого не нужна. В начале обработчика вызывается server.authenticate() с именем и паролем, а если проверка не прошла — server.requestAuthentication(): браузер сам покажет окошко для ввода. Только не считайте это настоящей защитой. Обычный веб-запрос идёт открытым текстом, и тот, кто уже подключён к вашей сети, при желании прочитает пароль. Веб-панель на плате — это домашнее удобство, а не система безопасности: для замков и ворот нужен другой подход.

Почему страница подвисает и открывается через раз?

Две обычные причины. Первая: в loop() стоит delay(), и пока программа спит, она не вызывает server.handleClient() — а значит, не отвечает браузеру. Правило простое: loop() должен пробегать за миллисекунды, а всё, что делается «раз в секунду», считается по millis(). Вторая: плата молчит на запрос значка вкладки, который браузер посылает сам, без вашего участия. Повесьте обработчик onNotFound, который коротко отвечает на всё незнакомое, — и страница начнёт открываться сразу.

Сколько устройств могут открыть страницу платы одновременно?

Формально несколько, фактически плата обслуживает их по очереди: она делает одно дело за раз. Для домашнего пульта это незаметно — страница отдаётся за доли секунды. А вот если панель тяжёлая, со стилями и картинками, а открывают её сразу человек десять, задержки станут видны. Для класса лучше отдавать страницу из флеш-памяти, убрать с неё всё лишнее и обновлять показания короткими запросами, а не перезагрузкой всей страницы.

Что для этого нужно в среде

Все три скетча собираются в Trema IDE без единой скачанной библиотеки: и сеть, и веб-сервер входят в поддержку самих плат. Поддержку ESP8266 и ESP32 среда берёт с нашего зеркала, поэтому установка не упирается в закрытые сайты.

Что почитать рядом: подключение к сети, плата ESP8266, монитор порта — он понадобится, чтобы узнать адрес платы, и разбор ошибок сборки.