Датчик в гараже молчал до вечера, и виновником оказался ночной перезапуск роутера — такой, после которого часть клиентов бодро возвращается, а часть зависает в стороне навсегда. Ручная перезагрузка платы чинила всё, но ездить в гараж ради кнопки reset — занятие для фанатов. Вариантов у нас два: доверить восстановление флагу setAutoReconnect или написать собственную reconnect-машину на millis. Здесь разбираю, что флаг делает на самом деле, где проходит его граница, и как выглядит ручной цикл, который не стыдно оставить на даче на зиму. Спойлер, к которому мы придём к концу: лучший вариант — оба сразу, и это не перестраховка, а разные слои обороны.
Флаг сам по себе разобран в уроке про WiFi.status() и авто-реконнект — механику и связи с кодами статуса я там не повторяю. Первый сетевой скетч и его подводные камни живут в уроке про первый WiFi; здесь же вся дистанция между «связь отвалилась» и «плата сама вернулась».
Что делает setAutoReconnect на самом деле
WiFi.setAutoReconnect(true) включает в Wi-Fi-драйвере автоповтор ассоциации: когда установленный было соединение рвётся, стек сам дёргает подключение к последней сети — без вашего участия и без единой строки в loop(). Это честный и рабочий механизм, и для одинокой метеостанции его хватает годами. Но у флага жёсткая граница: он подхватывает только отвал после успешного подключения. Если begin() закончился неуспехом — код 1 (сети нет) или 4 (пароль не подошёл) — никто за вас begin() снова не вызовет: драйвер отработал свой заход и уснул. Проверяется это за вечер: скормите плате неверный SSID, а потом верните роутер — с одним флагом она так и останется в стороне, хотя эфир давно поправился.
Вторая граница — тишина. Флаг молчит: ни счётчика попыток, ни лога, ни различения кодов — упал, поднялся, и хорошо. Мой практический вывод после пары зависших зимой плат: флаг оставляю включённым всегда — в ESP32-ядре он по умолчанию активен, но строка WiFi.setAutoReconnect(true) в setup() стоит ноль байт и снимает вопросы о дефолтах в чужих прошивках, — но строить на нём единственную линию обороны не стал ни разу. Всё, что должно дожить до утра без меня, получает ручную машину поверх флага.
const char* SSID_NAME = "HomeNet";const char* PASS = "12345678";unsigned long lastWatch = 0;int drops = 0; void setup() { Serial.begin(115200); WiFi.setAutoReconnect(true); // явная строка вместо надежды на дефолт WiFi.begin(SSID_NAME, PASS); while (WiFi.status() != WL_CONNECTED && millis() < 15000) delay(300); Serial.print("старт: "); Serial.println(WiFi.localIP());} void loop() { unsigned long ms = millis(); if (ms - lastWatch >= 5000) { lastWatch = ms; if (WiFi.status() != WL_CONNECTED) { drops++; Serial.print("статус "); Serial.print(WiFi.status()); Serial.print(", обрыв № "); Serial.println(drops); } }}Ручная машина на millis
Ручной цикл берёт то, чего у флага нет: детерминизм и телеметрию. Вы сами решаете, когда повторять, как долго и с какой паузой; сами считаете попытки; сами печатаете диагноз. Основа — опрос статуса по millis, не по delay: неблокирующий каркас с метками времени разобран в уроке про millis вместо delay. Пауза между попытками растёт: две секунды, четыре, восемь, потолок в минуту — роутер, который перезагружается две минуты, не должен ловить спам begin() каждую секунду. Ту же арифметику backoff применяют и на уровне HTTP-запросов, и на уровне брокера — в уроке про reconnect с backoff она разложена по шагам, здесь только связка с WiFi. И ещё один аргумент в копилку ручного: пауза, которую вы выбрали, видна в журнале, а у флага невидима даже собственная частота повторов — настройте по обстоятельствам места, а не по умолчанию чьей-то прошивки.
Второй слой — различение кодов. После кода 1 рвать эфир попытками смешно: сети нет, поставьте паузу подлиннее и печатайте напоминание. После 4 — вопрос к паролю, повторять тот же begin() можно хоть каждый час, результат не изменится, пока человек не вмешается. А когда попыток накопилось десяток, а статус так и не тройка — самый дешёвый довод это ESP.restart(): свежий старт прошивки расшивает застрявшие состояния драйвера, которые иначе лечатся только выдёргиванием питания. Причины, по которым плата вообще уходит в перезапуск, и как их потом читать — в уроке про аптайм и причины перезагрузок. Различение кодов — единственное место, где полезен сам словарь статусов: без него машина слепа, с ним различает капитуляцию от краткого мигания.
const char* SSID_NAME = "HomeNet";const char* PASS = "12345678";const int LED = 12; unsigned long lastAttempt = 0;unsigned long pauseMs = 2000; // растёт вдвое, потолок 60000int fails = 0; // неудачных попыток подрядbool wasConnected = false; void setup() { Serial.begin(115200); pinMode(LED, OUTPUT); WiFi.setAutoReconnect(true); WiFi.begin(SSID_NAME, PASS); lastAttempt = millis();} void loop() { unsigned long ms = millis(); bool connected = WiFi.status() == WL_CONNECTED; digitalWrite(LED, connected ? HIGH : LOW); if (connected) { if (!wasConnected) Serial.println("связь восстановлена"); wasConnected = true; pauseMs = 2000; // успех сбрасывает паузу и счётчик fails = 0; return; } wasConnected = false; int st = WiFi.status(); if (st == 1) Serial.println("код 1: сети нет — попытки с длинной паузой"); if (ms - lastAttempt >= pauseMs) { lastAttempt = ms; fails++; Serial.print("попытка "); Serial.print(fails); Serial.print(" через "); Serial.print(pauseMs / 1000); Serial.println(" c"); WiFi.disconnect(); // расшивает застрявшие состояния драйвера WiFi.begin(SSID_NAME, PASS); pauseMs = pauseMs * 2; if (pauseMs > 60000) pauseMs = 60000; if (fails >= 10) { Serial.println("10 неудач подряд — ESP.restart() как последний довод"); delay(100); ESP.restart(); } }}Когда какой выбирать
Критерий у меня один: что случится с устройством, если связь не вернётся до утра. Одинокий датчик температуры, который пишет в облако раз в пять минут, — флага и наблюдателя в Serial достаточно: спит он или шлёт, вы узнаете по пропавшим точкам на графике. Узел, через который идут команды, — реле, шлюз, всё, что ждёт входящие, — обязан иметь ручную машину: молчание такого узла равно поломке. Гибрид — тот скетч выше: флаг включён и подхватывает лёгкие отвалы за вас, машина следит за грубыми случаями, когда даже драйвер сдался. Встроить опрос статуса в большой проект удобнее как отдельную задачу — раскладка задач на millis уже описана в уроке про планировщик. И будьте честны с собой при выборе: если узел командный, флаг без машины — экономия в тридцать строк кода ценой утреннего рейса в гараж.
И не путайте рестарт с капитуляцией: десять неудач и ESP.restart() — это политика, записанная заранее, а не паника. Счётчик выживших обрывов смотрите потом по аптайму и причинам перезагрузок — там же, в уроке про перезагрузки, есть готовый формат отчёта, а серверная сторона видит обрывы своих узлов раньше, чем вы: у тишины нет пакета, который можно было бы гарантировать.
| Сценарий | Флаг setAutoReconnect | Ручная машина на millis |
|---|---|---|
| Отвал после живого соединения (роутер мигнул) | подхватывает сам за секунды | тоже справится, но пишется дольше |
| begin() закончился неуспехом (код 1 или 4) | бессилен: никто не вызовет begin() заново | повторяет попытки с растущей паузой и печатает код |
| Диагностика: счётчик обрывов, лог, паузы | никак: флаг молчит | всё в Serial: коды, попытки, паузы, рестарт |
| Драйвер завис в застрявшем состоянии | не лечит | disconnect() перед begin(), на десятой неудаче — ESP.restart() |
Как всё это проверяется без полевых испытаний. Ночной тест занимает тридцать секунд настройки: розетка с механическим таймером рвёт питание роутера посреди ночи, а утром читается журнал платы. С одним флагом в логе видна тишина и внезапное оживление — починилось само, неизвестно когда и почему. С ручной машиной тот же сценарий разворачивается колонкой: обрыв, код, попытка через две секунды, через четыре, через восемь, связь восстановлена. Разница между «само заработало» и «заработало, и я знаю почему» — это ровно те строки, которые вы с благодарностью прочитаете морозным утром, когда узел на улице снова замолчал.
Пока сети нет, машина обязана вести себя по-хозяйски. Показания копятся в буфере и уходят пачкой после восстановления — график честнее, чем дыра в час. Команды в офлайне получают внятный отказ, а не молчание: пользователь знает, что нажатие не дошло, и повторяет осознанно. Счётчик обрывов пишется в энергонезависимую память, чтобы пережить даже рестарт платы, — приём со счётчиком в EEPROM разобран в уроке про счётчик включений: замена включений на обрывы делается одним полем.
И честная граница применимости: узлам на батарейках вся эта машина не нужна. Они решают проблему радикальнее — засыпают и подключаются заново при каждом пробуждении, проводя в активной жизни секунды вместо часов; сон с таймером и будильником разобран в уроке про глубокий сон раздела. Для постоянно питающихся узлов — машина состояний, для спящих — сон; смешивать их в одном скетче я перестал после второй бессонной ночи с платой, которая умудрялась делать и то и другое. А если узел всё же и спит, и reconnect-ит — он не экономит, а тратит: сон дешевле любой самой умной машины.
- 1. Что делает WiFi.setAutoReconnect(true)?
- 2. begin() закончился кодом 1, WL_NO_SSID_AVAIL. Поднимет ли соединение авто-флаг?
- 3. Зачем в ручной машине пауза растёт вдвое до потолка?
- 4. Зачем перед повторным begin() зовут WiFi.disconnect()?
- 5. Что разумно сделать после десяти неудачных попыток подряд?
- setAutoReconnect(true) подхватывает только отвал после живого соединения; неуспешный begin() (коды 1 и 4) его не касается
- Флаг молчит: ни счётчика, ни лога — строка в setup() стоит ноль байт, но единственной линией обороны быть не должна
- Ручная машина: опрос статуса по millis, растущая пауза 2-4-8 с до потолка, счётчик попыток, disconnect() перед begin()
- После десятка неудач — ESP.restart() как заранее записанная политика, не паника
- begin() в каждой итерации loop без пауз — грабля: драйвер захлёбывается, статус дёргается, плата зависает
Частые вопросы по теме урока
- Что делает WiFi.setAutoReconnect(true)?
- Флаг относится к Wi-Fi-драйверу: когда установленный было соединение рвётся, стек сам повторяет подключение к последней сети. Всё, что до успешного подключения, его не касается. Включает автоповтор ассоциации драйвером после обрыва живого соединения
- begin() закончился кодом 1, WL_NO_SSID_AVAIL. Поднимет ли соединение авто-флаг?
- Граница флага: он работает после успешного подключения. Неуспешный begin() — за пределами его работы, и begin() должен звать ваш ручной цикл. Нет: флаг подхватывает лишь отвал после успеха, begin() снова не вызовется
- Зачем в ручной машине пауза растёт вдвое до потолка?
- Роутер перезагружается десятки секунд — минуты. Растущая пауза (2, 4, 8 с, потолок) терпит долгие простои и при этом быстро возвращает узел после коротких миганий. Чтобы роутер с долгой перезагрузкой не ловил спам попыток каждую секунду
- Зачем перед повторным begin() зовут WiFi.disconnect()?
- Драйвер иногда залипает в промежуточном состоянии, где begin() молча ничего не делает. Явный disconnect() чистит контекст, и следующий begin() стартует с чистого листа. Чтобы сбросить застрявшее состояние драйвера перед новым заходом
- Что разумно сделать после десяти неудачных попыток подряд?
- Свежий старт прошивки расшивает состояния драйвера, которые иначе лечатся выдёргиванием питания. Решение записывают в код заранее со счётчиком неудач — тогда рестарт выглядит как политика. Мой потолок для неважных узлов — десять, для командных — пять: командный узел дорог в молчании. Вызвать ESP.restart() — заранее записанная политика, а не паника