Каждые пару месяцев у меня разгорался пятиминутный спор с провайдером: интернет на даче пропадает, я звоню, в трубке вежливое «у нас всё работает, проверьте роутер», перезагрузка помогает до вечера. Доказательств у меня не было — только «иногда не грузится», на такое и отвечать нечем. Однажды я собрал арбитраж: ESP32 на полке у роутера, раз в десять секунд стучится GET-запросом на адрес роутера и на внешний сервер, зелёный светодиод — сеть жива, тухнет или мигает — нет, а Serial пишет журнал с секундами. Через неделю в журнале проступил рисунок: связь падала на 40-90 секунд по три-четыре раза за вечер, всегда после половины шестого. С этим файлом разговор с поддержкой занял два звонка вместо двух недель: слова «иногда не грузится» — не диагноз, а чувство, и прибор заменяет его числом.
Идея урока проста до примитива: HTTP-код ответа — тот же пинг, только честнее. В базовом уроке про HTTP-запросы коды 200, 404 и -1 работали ответами сервера на наши вопросы; здесь тот же код становится индикатором сети. Соберём монитор целиком: опрос по расписанию на millis, светодиод на два состояния, счётчик отказов подряд и журнал, который пишет смены состояний, а не тонет в повторах. Автоматическое возвращение платы в сеть после обрыва мы собирали раньше в уроке про реконнект — монитор встаёт рядом с тем автоматом как приборная панель.
GET как пинг: что именно проверяет код
Классический пинг — ICMP-пакет с ожиданием эха; в ядре arduino-esp32 такой утилиты нет, желающие ставят сторонние библиотеки вроде ESP32Ping. Я иду выше уровнем: GET на http://192.168.1.1/ — адрес роутера (у части прошивок он другой, 192.168.0.1, — впишите свой). Если запрос вернул любой HTTP-код — 200, 301, даже 404 — значит, TCP-соединение открылось и роутер жив. Если GET вернул -1 — соединение не открылось вовсе: роутер завис, кабель выдернут или плата вылетела из сети. Один запрос — три диагноза.
Тонкость, до которой я дошёл не сразу: для монитора сети 404 — хорошая новость. Это «сеть в порядке, адрес кривой»: TCP открыт, сервер ответил, пусть даже и отказом по пути. Красным светить надо только на -1 и на случай, когда WiFi.status() перестал быть WL_CONNECTED — тут уже не до HTTP. Второй уровень прибора — тот же GET на внешний адрес: роутер отвечает, а внешний сервер молчит? Значит, беда за роутером — канал или провайдер. Эта матрица из двух проверок и есть весь арбитраж, а сам журнал — доказательная база.
Интервал в десять секунд — не догма, а баланс. Реже — пропускаете короткие обрывы: мой провайдер рвал канал на 40-90 секунд, и опрос раз в минуту показал бы лишь каждый третий-четвёртый провал. Чаще — шум: десять запросов в минуту роутеру не навредят, но журналы растут быстрее, а виновника вы всё равно найдёте не по частоте опроса, а по времени события в журнале. Десять секунд дают 8 640 опросов в сутки — для роутера это пыль, зато рисунок обрывов проступает за неделю наблюдений без всякой статистики.
#include <WiFi.h>#include <HTTPClient.h> const char* ssid = "DomaSetka";const char* password = "superParol";const char* routerUrl = "http://192.168.1.1/"; unsigned long lastPing = 0;int svetByl = -1; // -1: состояние ещё неизвестноint otkazovPodryad = 0;int bylaNazhata = 0; void setup() { Serial.begin(115200); pinMode(12, OUTPUT); // светодиод статуса pinMode(2, INPUT); // кнопка «проверить сейчас» WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Serial.println("Monitor zapuschen: ping raz v 10 s");} void loop() { // кнопка — внеплановый опрос: обнулили таймер, пинг уйдёт в этом же проходе int nazhata = digitalRead(2); if (nazhata == HIGH && bylaNazhata == 0) lastPing = 0; bylaNazhata = nazhata; if (millis() - lastPing >= 10000) { lastPing = millis(); HTTPClient http; http.begin(routerUrl); int kod = http.GET(); http.end(); if (kod > 0) { // любой HTTP-код: TCP открыт, узел жив otkazovPodryad = 0; digitalWrite(12, HIGH); // горит ровно — сеть жива if (svetByl != 1) { Serial.print("SETEST: est, kod="); Serial.println(kod); svetByl = 1; } } else { otkazovPodryad++; // мигаем 500 мс вкл / 500 мс выкл, пока сеть мертва if (millis() % 1000 < 500) digitalWrite(12, HIGH); else digitalWrite(12, LOW); if (svetByl != 0) { Serial.println("SETEST NET: kod=-1"); svetByl = 0; } } }}Журнал — половина прибора, и писать его надо по сменам состояния, а не по каждому опросу. Сравните: раз в десять секунд строка «код 200» — за сутки 8 640 почти одинаковых строк, и нужная мысль тонет в них на первой же странице. Одна строка на переход «есть → нет» с кодом отказа и одна на обратный переход — читается за минуту в любом мониторе порта — хоть в IDE, хоть в терминале. Переменная svetByl хранит предыдущее состояние и печатает только отличия; счётчик otkazovPodryad продолжает счёт внутри затяжного обрыва — в журнале одна строка «упало», а счётчик показывает глубину. Для аккуратных журналов с уровнями и метками у меня есть отдельный разговор в уроке про уровни логов.
Светодиод я делаю на одном пине: горит ровно — сеть жива, мигает 500/500 — обрыв. Разглядывать коды в Serial у стены, где висит роутер, неудобно, а лампочка видна от двери. Кнопка на пине 2 — «проверить сейчас»: обнуление lastPing заставляет опрос уйти в этом же проходе цикла, а плановое расписание при этом не сбивается. Между пингами скетч полностью свободен — те же тайм-слоты из прошлогодних проектов встраиваются рядом без переделки. И один штрих про надёжность самого дознавателя: если плата с монитором повиснет, дознание кончится, поэтому сторожевой пёс рядом с сетевым скетчем не роскошь, а страховка.
Штамп времени в журнале — вопрос, который я тоже мучил. Метки реального времени появились позже, когда я завёл на плате NTP-клиента; в первой версии журнал писал millis()/1000 — секунды от старта. Для спора с поддержкой этого хватает: «обрыв в 17:41 по счётчику платы, длился 70 секунд, третий раз за вечер» — точность до секунды, стоимость ноль. Важно только не сбрасывать плату по питанию посреди недели наблюдений: счётчик обнулится, и журнал рассыплется на две половины, которые потом сличаешь вручную.
| Роутер 192.168.1.1 | Внешний API | Диагноз | Куда идти |
|---|---|---|---|
| 200 | 200 | Сеть и канал живы — беда в самом сервисе | К автору сервиса или на его статус-страницу |
| 200 | -1 | Роутер жив, наружу не выходит — канал или провайдер | В поддержку провайдера с журналом |
| -1 | -1 | Роутер или его питание | Перезагрузка роутера, проверка кабеля |
| -1 | 200 | Так не бывает — проверьте, с какого адреса спрашивали | Свой скетч: опечатка в адресе |
Проба в симуляторе
Честная оговорка перед запуском: демо-сеть движка всегда отвечает кодом 200 — обрыв в виртуальном роутере не устроишь никак. Поэтому в скетч ниже встроен учебный тумблер: код реального GET сохраняется, а если ввести в Serial единицу, монитор трактует следующий опрос как отказ — учебная подмена kod = -1; ввод нуля возвращает живую сеть. На реальной плате эти две строки не нужны, код придёт настоящий. Остальное честно на все сто: расписание раз в десять секунд, мигающий светодиод при отказе, счётчик подряд и журнал смен состояния. Кнопка на пине 2 даёт внеплановый пинг прямо сейчас; скорость ×10 ускоряет и расписание, и ритм мигалки.
План пробы по шагам. Запустите и подождите первый плановый пинг: в журнале появится строка SETEST: est, светодиод загорится ровно. Введите 1 — на ближайшем тике сеть «умрёт»: одна строка смены в журнале, светодиод замигает полусекундным ритмом, счётчик пойдёт со второго отказа. Введите 0 — журнал запишет возвращение, счётчик обнулится, лампа замрёт. Нажмите кнопку — пинг уйдёт вне расписания, прямо в момент нажатия. Всё это на живой плате делает тот же скетч без единой поправки, кроме учебного тумблера.
- HTTP-код — пинг выше уровнем ICMP: любой код (даже 404/301) значит «TCP открыт, узел жив»; -1 — соединения не было
- Журнал ведите по сменам состояния: 8 640 одинаковых строк в сутки тонут, две строки на переход читаются
- Счётчик отказов подряд показывает глубину затяжного обрыва, не раздувая журнал
- Матрица «роутер + внешний API» локализует виновника: сервис, канал/провайдер или роутер
- LED на один пин: ровно горит — жив, мигает 500/500 — обрыв; кнопка даёт внеплановый пинг
- 1. GET на адрес роутера вернул 404. Что это значит для сети?
- 2. Сколько строк за сутки напишет журнал, печатающий результат каждого опроса раз в 10 секунд?
- 3. Что делает кнопка «проверить сейчас» в скетче монитора?
- 4. Роутер отвечает 200, внешний API молчит (-1). Кто виноват?
- 5. Почему критерий живости в скетче — kod > 0, а не kod == 200?
Монитор отвечает на вопрос «жив ли узел», но у отказа есть характер, который виден только в реакции на повтор. Сервер, споткнувшийся с 500, чаще всего оживает сам — через секунду или через минуту. Адрес с 404 не оживёт от десяти повторов, сколько ни стучи. А моя ранняя схема «упал — долблю каждые десять секунд» однажды кончилась часом блокировки: сервис счёл лавину повторов за атаку. В следующем уроке разложим коды по классам — что повторять, что чинить на месте — и соберём вежливый повтор с нарастающей паузой 1-2-4-8 секунд. И заведите правило заранее: адреса обеих проверок хранятся в константах наверху скетча — смена роутера или провайдера станет правкой одной строки, а не раскопками по коду.
Частые вопросы по теме урока
- GET на адрес роутера вернул 404. Что это значит для сети?
- Любой HTTP-код — знак, что соединение состоялось и сервер ответил. Для монитора сети это «жив»; отказ — только -1. Сеть жива: TCP открыт, просто адрес кривой
- Сколько строк за сутки напишет журнал, печатающий результат каждого опроса раз в 10 секунд?
- Шесть строк в минуту — 8 640 в сутки. Поэтому журнал ведут по сменам состояния: две строки на переход вместо тысяч повторов. 8 640
- Что делает кнопка «проверить сейчас» в скетче монитора?
- lastPing = 0 — и условие millis() - lastPing >= 10000 срабатывает сразу. Плановое расписание при этом не сбивается. Обнуляет таймер опроса: пинг уходит в текущем проходе
- Роутер отвечает 200, внешний API молчит (-1). Кто виноват?
- Роутер жив — локальная сеть в порядке; наружу не выходит — беда за ним: канал или провайдер. Журнал с секундами — ваш аргумент в разговоре. Канал или провайдер
- Почему критерий живости в скетче — kod > 0, а не kod == 200?
- Роутер может переадресовать на https (301) или требовать вход (401) — соединение состоялось. Сравнение kod == 200 даст ложную тревогу. Любой HTTP-код говорит об открытом соединении; 200 может не прийти из-за 301/401