Датчик на даче был подключён к домашнему роутеру через сотню метров улицы, и всё работало, пока сосед не начал выключать на ночь автомат в щитке: роутер ложился, плата теряла сеть, а телефон — единственный инструмент, которым можно было посмотреть, что случилось, — оставался на мобильном интернете и до платы уже не добирался. Перезагрузить питание некому, ехать сорок километров ради кнопки reset глупо. Именно после той недели я перестал писать WiFi.mode(WIFI_STA) на автомате и включил плате второй режим: она оставалась клиентом домашней сети и одновременно держала собственную точку доступа — если роутер умирал, я стоял у шкафа, подключался телефоном прямо к плате и читал её страницу с логом.
Режим AP+STA включается одной строкой — WiFi.mode(WIFI_AP_STA) — но вокруг этой строки хватает сюрпризов: у платы появляется два IP-адреса, точка доступа молча переезжает на канал роутера, а телефон, подключившийся к сети платы, в интернет через неё не попадёт. В этом уроке разберём все три ловушки, соберём скетч с двумя адресами и посмотрим на главный сценарий режима — портал настройки, механизм, через который вы вводили пароль от WiFi половине умных розеток в магазине.
Две личности одной платы
WiFi.mode() задаёт режим радиомодуля: WIFI_STA — плата клиент, WIFI_AP — плата точка доступа, WIFI_AP_STA — оба сразу. Радиомодуль при этом один, но стек умеет обслуживать обе роли: пакет в домашнюю сеть уходит через интерфейс станции, ответ клиенту собственной сети — через интерфейс точки, переключение занимает миллисекунды. У каждой личности свой адрес: станция получает его по DHCP от роутера, например 192.168.1.100 — механика подключения разобрана в уроке про WiFi.begin, — а точка всегда сидит на 192.168.4.1.
Порядок вызовов имеет значение. Моё правило: сначала WiFi.mode(WIFI_AP_STA), затем WiFi.softAP() для собственной сети и только потом WiFi.begin() для роутера; аргументы softAP и адрес 192.168.4.1 мы подробно разбирали в уроке про точку доступа, там же таблица трёх режимов — повторять её здесь не буду. Если перепутать порядок и поднять softAP после begin, тоже заработает, но точка переедет на канал роутера уже после подъёма, и подключённый в этот момент телефон повиснет на пару секунд. В маленьком домашнем проекте это незаметно, а в портале настройки выглядит как «сервер глючит».
Отдельная цена режима — память и эфир. Каждая активная роль держит буферы в куче: чистый STA на DevKit оставляет около 300 КБ свободной кучи, включённая поверх точка съедает ещё 20–30 КБ, а если рядом запустить Bluetooth, свободного останется заметно меньше — таблица ниже раскладывает это по строкам. И эфир один на обе личности: ответ клиенту точки и запрос к роутеру не идут параллельно, они чередуются на одном радиоканале, поэтому суммарная скорость делится на всех. Для страницы управления и пары HTTP-запросов в минуту это неважно; для потоковой передачи видео по своей сети с одновременным опросом API — уже аргумент против.
#include <WiFi.h> const char* staSsid = "DomaSetka";const char* staPass = "superParol";const char* apSsid = "DachaSensor";const char* apPass = "setup777setup"; // 8+ символов, иначе сеть открытая void setup() { Serial.begin(115200); WiFi.mode(WIFI_AP_STA); // обе личности сразу WiFi.softAP(apSsid, apPass, 6); // своя сеть, канал 6 — как у роутера WiFi.begin(staSsid, staPass); // и подключение к роутеру Serial.print("AP поднята: "); Serial.println(apSsid); Serial.print("IP точки: "); Serial.println(WiFi.softAPIP()); // 192.168.4.1 по умолчанию while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.print("IP станции: "); Serial.println(WiFi.localIP()); // например 192.168.1.100} void loop() { // здесь живёт сервер: он отвечает и по адресу точки, и по адресу станции}Разбор. WiFi.softAPIP() печатает адрес точки — 192.168.4.1, WiFi.localIP() — адрес станции, который выдал роутер. Оба адреса живут одновременно, и сервер из урока про мини-веб-сервер в этом режиме отвечает по любому из них: сидя в домашней сети, вы открываете 192.168.1.100, подключившись телефоном к сети платы — 192.168.4.1. Код сервера при этом не меняется ни на символ, проверено на дачном датчике: один и тот же скетч принял запрос и от роутера, и напрямую с телефона.
Про канал — главная ловушка режима, о которой молчат примеры. Радиомодуль у платы один, поэтому точка доступа вынуждена жить на том же канале, что и станция: чип физически не может одновременно слушать канал 1 и канал 6. Когда WiFi.begin() добирается до роутера, ваша softAP молча переезжает на его канал: задали в softAP канал 1, а роутер вещает на 6 — сеть платы тихо сменила канал, и подключённый телефон отвалится и начнёт переподключаться. Моё правило: заранее посмотреть канал роутера приложением-анализатором на телефоне (занимает минуту) и вписать его третьим аргументом в softAP — переезда нет вовсе, переподключений нет, жалоб нет.
Второй адресный нюанс: обе личности регистрируются в логах по-разному. Станция видна роутеру как обычный клиент в списке DHCP, а клиенты точки видны только самой плате; печатей softAPIP и localIP в setup мне хватает — две строки, и я всегда знаю, кем плата притворяется в этот момент. Если после перезагрузки роутера адрес станции сменился, а страница по старому адресу перестала открываться — это не катастрофа AP+STA, а обычный DHCP, к которому мы уже привыкли по клиентским урокам раздела.
| Ситуация | Что происходит | Что делать |
|---|---|---|
| SoftAP на канале 1, роутер на 6 | После WiFi.begin() точка молча переезжает на канал 6, клиенты переподключаются | Задать softAP канал роутера третьим аргументом |
| Телефон в сети платы пишет «без интернета» | Норма: плата не маршрутизирует пакеты между AP и STA | Для страниц платы 192.168.4.1 работает и так |
| Оба интерфейса активны | WiFi-стек берёт из кучи на 20–30 КБ больше, чем один STA | На батарее включать AP только на время настройки |
| Рядом запущен Bluetooth | Совместное существование ест ещё память, свободной кучи меньше | Не поднимать BLE в плате, где AP+STA с сервером |
| Через сутки работы два клиента на AP | Скорость делится между ролями, страницы отвечают медленнее | Лимит клиентов 2–4 и лёгкие страницы без графики |
Портал настройки: главный сценарий AP+STA
Зачем этот режим производителям? Новая розетка не знает пароля от домашней сети, а вводить его кнопками на корпусе невозможно. Схема портала: плата стартует в AP+STA и поднимает собственную сеть, вы подключаетесь телефоном, открываете 192.168.4.1 и вводите SSID и пароль домашней сети в форму; плата сохраняет их в энергонезависимую память — механика хранения настроек между перезагрузками разобрана в уроке про первые настройки в EEPROM — и перезагружается уже только как станция. Именно так настраивают половину умных устройств: сеть с именем вида ESP_XXXX в списке WiFi — это чей-то портал настройки, ждущий вашего пароля.
Второй сценарий — страховка, тот самый дачный. Датчик живёт в STA месяцами, но по millis-таймеру следит за связью: если роутер недоступен дольше получаса, плата поднимает свою сеть и ждёт хозяина. Я ставлю порог в тридцать минут — один ложный рей роутера не будит точку, а реальная авария уже ловится. Для страницы подойдёт веб-сервер из урока выше без правок, а сам таймер без delay() собирается так, как в уроке про millis вместо delay: сравнение текущего millis с меткой последней связи. На батарейных устройствах я добавляю к этому автоматическое выключение точки: десять минут без подключённых клиентов — softAP гаснет, чтобы не жечь заряд.
- Посмотрите канал своего роутера WiFi-анализатором на телефоне и впишите его третьим аргументом softAP, как в скетче выше. Потом временно поставьте другой канал — и посмотрите в лог, как телефон переподключается после старта WiFi.begin().
- Замерьте цену режима: ESP.getFreeHeap() сразу после старта и после WiFi.mode(WIFI_AP_STA) с softAP. На DevKit увидите минус порядка 20–30 КБ свободной кучи — те самые байты, о которых спорит таблица.
- Добавьте таймер страховки: если WiFi.status() не равен WL_CONNECTED дольше тридцати минут, печатайте в Serial «роутер молчит, AP живёт». Порог для теста поставьте две минуты, чтобы не ждать полчаса.
- Откройте страницу сервера с двух устройств сразу — с ноутбука по адресу станции и с телефона по 192.168.4.1. Убедитесь, что оба клиента получают ответ: один сервер, две двери.
Ниже симулятор, и здесь нужна честная оговорка: движок эмулирует только клиентскую половину сети. WiFi.mode(WIFI_AP_STA), softAP и softAPIP в виртуальной сети не работают, поэтому скетч ниже гоняет STA-часть: плата подключается к виртуальному роутеру и делает GET-запрос на тестовый URL, как настоящий HTTP-клиент. Смотрите на фазы в Serial-логе — подключение, адрес, запрос, ответ; это ровно нижняя половина работы AP+STA. Верхнюю половину, точку доступа, добавляют в коде для живой платы две строки из скетча выше. Счётчик запросов n= в логе показывает, что скетч живёт по расписанию в loop(), а не одноразовый — на реальной плате такой цикл у меня крутится на дачном датчике месяцами.
- WiFi.mode(WIFI_AP_STA) включает обе личности: станция с адресом от DHCP и точка на 192.168.4.1
- Порядок вызовов: mode → softAP → begin; точка переезжает на канал роутера после подключения
- NAT внутри платы нет: клиенты сети платы видят только саму плату, но не интернет
- Портал настройки — главный сценарий: телефон передаёт плате пароль домашней сети через 192.168.4.1
- AP+STA стоит 20–30 КБ кучи сверх STA — на батарее включайте точку только на время настройки
- 1. Что делает WiFi.mode(WIFI_AP_STA)?
- 2. После WiFi.begin() точка доступа сменила канал с 1 на 6. Почему?
- 3. Телефон подключён к сети платы, сайты не открываются. В чём причина?
- 4. Какой адрес по умолчанию у точки доступа ESP32?
- 5. Какой порядок вызовов рабочий для AP+STA?
AP+STA решает вопрос, кто заходит к плате; но чаще плате нужно самой сходить в сеть за данными или отнести их на сервер. В следующем уроке спускаемся на два этажа сетевой библиотеки — WiFiClient и HTTPClient: соберём GET-запрос руками на сыром TCP-сокете, увидим, что HTTPClient делает за нас, и разберёмся с заголовками, из-за которых мой скетч однажды получил отказ от внешнего API. А если ваш сервер ждёт не вопросы, а показания датчиков — урок через один расскажет про POST: как сформировать тело и дождаться 201 в ответ. И держите в голове правило страховки с дачи: STA для работы, AP для доступа — включать точку постоянно на батарейке я перестал после того, как она съедала треть заряда за неделю.
Частые вопросы по теме урока
- Что делает WiFi.mode(WIFI_AP_STA)?
- WIFI_AP_STA держит обе роли: плата остаётся клиентом роутера и одновременно раздаёт собственную сеть на 192.168.4.1. Радиомодуль один, но стек обслуживает роли по очереди. Включает оба интерфейса: станция и точка доступа одновременно
- После WiFi.begin() точка доступа сменила канал с 1 на 6. Почему?
- В AP+STA радио одно, поэтому точка живёт на канале станции. Выход — задать softAP тот же канал, что у роутера. Радиомодуль один: точка следует за каналом роутера, к которому подключилась станция
- Телефон подключён к сети платы, сайты не открываются. В чём причина?
- Клиенты сети платы достают только саму плату — 192.168.4.1 и её страницы. Маршрутизацию в интернет ESP32 не выполняет: NAT внутри нет. Плата не маршрутизирует пакеты: NAT внутри нет, интернет через неё не идёт
- Какой адрес по умолчанию у точки доступа ESP32?
- SoftAP по умолчанию живёт на 192.168.4.1; адрес печатает WiFi.softAPIP() и меняет WiFi.softAPConfig(). 192.168.4.1
- Какой порядок вызовов рабочий для AP+STA?
- Сначала WiFi.mode(WIFI_AP_STA), затем softAP, затем begin — тогда канал точки переезжает до подключения клиентов, и телефон не переподключается. mode → softAP → begin