Роутер в доме перезагружается по расписанию: провайдер любит четыре часа ночи, роутер — среду. Обычно всё само поднимается за минуту, но однажды датчик в гараже молчал до вечера: сеть давно была жива, а связь не вернулась. С тех пор я считаю reconnect не деталью, а обязательной частью любого WiFi-скетча, который живёт дольше суток: обрывы случаются у всех, вопрос лишь в том, что делает loop(), когда WL_CONNECTED пропадает. У платы нет ни глаз, ни рук — есть только статус, таймер и Serial; этого, как ни странно, хватает.
В уроке про reconnect-машину уже есть выбор между setAutoReconnect и ручным циклом с рестартом платы как последним доводом. Здесь я забираю из той машины только расписание — backoff-лестницу с удвоением паузы от 2 с к потолку 60 с — и обвешиваю её журналом в Serial, который утром читается как дневник. В отличие от уроков про радио и сон этот симулируется честно: WiFi.begin() и WiFi.status() движок умеет, тумблер D3 сыграет выключенный роутер.
Полезно различать, что именно отвалилось: связь с роутером и адрес в ней — два этажа, и падают они по-разному. WiFi-ассоциация может быть жива при устаревшем DHCP-аренде, или статус WL_CONNECTED светится, когда интернет за роутером мёртв. Лестница ниже лечит первый этаж — сам WiFi; второй она поднимает заодно, потому что повторный begin() проходит путь от ассоциации до DHCP целиком. Но проверять после возврата я советую оба: лёгкий запрос к заранее известному серверу — и только после него плата считает связь рабочей.
Почему пауза должна расти
Фиксированная пауза «пять секунд и ещё раз» плоха с обеих сторон. Пока роутер поднимается — типичные 40-60 секунд, — скетч успевает десять раз потревожить эфир: каждая попытка begin() — это всплеск тока на плате и строка в логе роутера. Лестница с удвоением ведёт себя приличнее: попытки на 2, 4, 8, 16 и 32 секундах, дальше потолок 60 с. Короткие ступени ловят мигнувший роутер почти мгновенно, длинные не тратят эфир и ток, пока сеть встаёт. Коды статусов помогают экономить ещё и на этом: по шпаргалке WiFi.status() статус 1 (WL_NO_SSID_AVAIL) значит, что сети вообще нет, — тогда я прыгаю сразу на потолок, зачем долбить то, чего не существует.
Лестницу можно гнуть под задачу. Классика из сетевых книг — удвоение от единицы: 1, 2, 4, 8, 16 секунд; у меня старт с двух, потому что первая попытка и так случается сразу после обрыва, и метровая ступень редко что-то меняет. Для уличной метеостанции я растянул потолок до пяти минут: роутер на вышке перезагружается по десять минут, и честнее спать, чем щупать. А для тестового стенда, наоборот, потолок десять секунд — чтобы видеть лестницу глазами за кофе. Один нюанс: сотня плат с одинаковым расписанием бьют в роутер синхронно и ходят в атаку строем, поэтому к паузе стоит добавлять случайные проценты.
Потолок обязателен. Без него пауза удвоениями уезжает в часы: связь упала на минуту, а плата дремлет на ступенях 32, 64, 128 секунд и просыпается, когда история давно остыла. С потолком худший случай — минута ожидания, лучший — две секунды. Отдельно про setAutoReconnect(true): флаг полезен, но молчалив — без счётчика, без лога, без различения кодов; всё, что делает плата, я хочу видеть сам, поэтому у меня флаг включён как база, а расписание и журнал — ручные.
Обрывы, с которыми я встречался за пару лет, укладываются в три сорта. Роутер перезагружается сам — минуты: лестница честно дорастает до потолка и встречает сеть на нём. Провайдер или удлинитель отрезают на час — статус 1, сети нет, плата стоит на потолке и щупает раз в минуту. Мгновенные моргания — соседская микроволновка, перегруженный канал: тут работают первые ступени. Одна и та же лестница покрывает все три сорта, а различать сценарии помогают коды статуса — они честно рассказывают, что именно вы наблюдаете в конкретную минуту.
Сброс лестницы — только после устойчивой связи
Классическая грабля этой схемы — качели. Связь вспыхивает на секунду и рвётся; если сбрасывать лестницу к первой ступени при каждом WL_CONNECTED, плата вечность качается на быстрых попытках, а журнал пестрит одними «восстановлено». Моё правило: ступень возвращается к началу только после минуты непрерывной связи. До этого лестница живёт своей жизнью: пара мгновенных возвратов подряд — и следующая попытка уже не через 2 с, а через 8 и дальше. Для чутких задач берут 10-15 секунд вместо минуты, но меньше пяти я не ставил ни разу: даже пинг роутера живёт дольше.
И ещё одна деталь, которую я добавил после гаража: WL_CONNECTED — не гарантия интернета. Ассоциация живёт, DHCP отдал адрес, а наружу пакеты не идут — роутер завис на своём WAN. Поэтому минута стабильности у меня считается не по статусу, а по реальному трафику: раз в минуту лёгкий запрос, и только его успех двигает счётчик устойчивости. Полноценный мониторинг доступности с пингами и порогами уже делали в уроке про ping-монитор — здесь достаточно одного факта успеха в минуту.
#include <WiFi.h> const char* SSID_NAME = "HomeNet";const char* PASS = "12345678";const int LED = 2; // встроенный LED DevKit: горит = связь long ladder[] = {2000, 4000, 8000, 16000, 32000, 60000};int step = 0; // текущая ступень лестницыunsigned long lastTry = 0;unsigned long upSince = 0; // с какого мгновения связь непрерывнаint tries = 0; void setup() { Serial.begin(115200); pinMode(LED, OUTPUT); WiFi.begin(SSID_NAME, PASS); lastTry = millis();} void loop() { unsigned long ms = millis(); bool up = WiFi.status() == WL_CONNECTED; digitalWrite(LED, up ? HIGH : LOW); if (up) { if (upSince == 0) upSince = ms; // связь только что появилась if (step > 0 && ms - upSince >= 60000) { step = 0; // минута без обрывов — лестница к началу Serial.println("связь стабильна 60 с: лестница сброшена"); } return; } upSince = 0; // связь потеряна, минута начнётся заново if (ms - lastTry >= ladder[step]) { lastTry = ms; tries++; if (step < 5) step++; // следующая ступень, потолок 60 с Serial.print("попытка "); Serial.print(tries); Serial.print(", статус "); Serial.print(WiFi.status()); Serial.print(", следующая пауза "); Serial.print(ladder[step] / 1000); Serial.println(" с"); WiFi.begin(SSID_NAME, PASS); // новая попытка ассоциации }}Разбор по строкам. Повторный WiFi.begin() делает попытку явной и журналируемой — молчаливые внутренние повторы драйвера в дневник не попадают. upSince хранит мгновение, с которого связь непрерывна; каждый обрыв обнуляет его, и минута стабильности начинается заново. Лестница лежит в массиве, и её легко укоротить или удлинить; потолок 60 с у меня не менялся годами. Если нужен фундамент — сам момент первого подключения и его ловушки разобраны в уроке про первый WiFi. Если ваш роутер поднимается быстрее сорока секунд, потолок можно опустить до тридцати — лестнице без разницы, лишь бы потолок был дольше подъёма сети.
Журнал, который утром читается как дневник
Что печатать при каждой попытке: метку millis() в секундах, код статуса, номер попытки и выбранную паузу. Мой ночной лог выглядит так: «1215 с: статус 6, попытка 2, пауза 4 с… 1231 с: статус 6, попытка 3, пауза 8 с… 1257 с: связь восстановлена, IP 192.168.1.42». Утром видно всё: роутер поднялся за 42 секунды, плата вернулась на третьей попытке, до потолка не дошла. Без таких строк утренняя диагностика — гадание: связь же просто отвалилась. Слабый сигнал, при котором связь числится живой, но рвётся, — отдельная история из урока про RSSI.
Какие числа из журнала стоят долгой жизни. Три моих любимых: обрывов за сутки, время последнего успеха, суммарные секунды на потолке лестницы. Первое показывает характер сети: у здорового роутера — ноль-два, у проблемного — десятки. Второе — база для будильника «связи нет больше часа». Третье отличает «роутер ребутнулся» от «WiFi подох окончательно»: в первом случае плата редко добирается до потолка, во втором живёт там ночами. Эти же три числа я отправляю в телеметрию — они дешевле любого выезда на дачу в мае.
И как проверить reconnect, не дожидаясь ночи: выключите роутер из розетки на минуту и смотрите лестницу в мониторе. Это лучший приёмочный тест перед монтажом: видно, растут ли паузы, возвращается ли связь, печатается ли журнал. Я гоняю три отключения подряд с передышкой между ними — и знаю, что ночной перезапуск роутера плата переживёт без моего участия. Минута теста заменяет неделю надежды.
Куда журнал живёт дальше. Для платы, которая сутками одна на участке, Serial видите только вы — в MQTT статус уезжает брокеру: машина состояний связи уже разобрана в уроке про связь MQTT. Счётчик попыток и время последнего успеха сохраняю в NVS через Preferences: эта память лежит во флеше и переживает и сон, и перезагрузку — по ней видно, сколько ночей подряд плата ходила по потолку лестницы. Такие числа за месяц решают, пора ли переносить датчик ближе к роутеру.
В журнале я оставляю место ещё для двух полей, когда сеть капризничает: RSSI и число попыток с последнего успеха. Первое отделяет «роутер умер» от «антенна на грани»: статус 6 при сигнале минус девяносто — это физика, а не софт. Второе показывает скорость деградации: лестница, живущая на потолке неделями, — диагноз сети, а не скетча. Печатать всё подряд не стоит: строка на попытку и одна на восстановление — сорок байт за ночь, монитор за месяц не распухнет, а картина не размоется.
| Стратегия | Возврат при обрыве 60 с | Нагрузка на эфир | Когда беру |
|---|---|---|---|
| Фиксированные 5 с | в первые 5 с после подъёма сети | 12 попыток за обрыв | макетка на столе |
| Лестница 2-60 с | на ближайшей ступени | 6 попыток за обрыв | платы без присмотра |
| setAutoReconnect(true) | сам, без счётчика и лога | на усмотрение драйвера | как база под ручной журнал |
| Рестарт после N неудач | с полной загрузкой, 3-5 с | нулевая до рестарта | последний довод |
- 1. Почему пауза между попытками reconnect должна расти?
- 2. Когда сбрасывать лестницу к первой ступени?
- 3. WiFi.status() возвращает 1 (WL_NO_SSID_AVAIL). Что это значит?
- 4. Что печатать в Serial-журнал при каждой попытке?
- 5. Что умеет движок симулятора в этом уроке?
- Обрывы WiFi неизбежны; reconnect — часть скетча, а не надежда на драйвер.
- Лестница пауз 2-4-8-16-32-60 с: короткие ступени ловят мигнувший роутер, потолок бережёт эфир и ток.
- Сброс лестницы к первой ступени — только после минуты непрерывной связи, иначе качели.
- Статус 1 (WL_NO_SSID_AVAIL) — сети нет: следующая попытка сразу по потолку.
- Журнал с метками millis() и статусами превращает утреннюю диагностику в чтение дневника.
Частые вопросы по теме урока
- Почему пауза между попытками reconnect должна расти?
- Пока роутер поднимается 40-60 секунд, короткая фиксированная пауза даёт десятки бесполезных попыток. Лестница ловит быстрый возврат короткими ступенями и бережёт эфир длинными. Роутер поднимается десятки секунд, а долбить эфир и тратить ток незачем
- Когда сбрасывать лестницу к первой ступени?
- Если сбрасывать при каждом появлении связи, при качелях плата навсегда застрянет на быстрых попытках. Минута непрерывной связи отсекает моргания. После минуты непрерывной связи
- WiFi.status() возвращает 1 (WL_NO_SSID_AVAIL). Что это значит?
- Статус 1 — сеть не найдена. Долбить эфир смысла нет: следующий шаг лестницы сразу ставится максимальным, попытки продолжаются по потолку. Сети нет — можно сразу прыгать на потолок паузы
- Что печатать в Serial-журнал при каждой попытке?
- Журнал нужен для утреннего чтения: метка времени даёт хронологию, статус и пауза — картину поведения лестницы. Метку millis(), статус, номер попытки и выбранную паузу
- Что умеет движок симулятора в этом уроке?
- begin(), status(), localIP() и disconnect() поддержаны: тумблер рвёт связь, лестница отрабатывает настоящими паузами. Умеет WiFi.begin() и WiFi.status() — reconnect симулируется честно