Перейти к содержимому

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Метки:

Добрый день, уважаемый читатель! В данной статье обсудим довольно непростую тему – подключение к WiFi-точке доступа в режиме STA. ESP32 не был бы так популярен, если б в нем не было встроенной поддержки WiFi подключений.

Режим станции (STA) — это такой режим, в котором контроллер не создает собственную сеть, а подключается к уже существующей сети Wi-Fi, например, к вашей локальной сети (роутеру или иному “раздающему” устройству).

Если вы программировали ESP32 из Arduino, то наверняка видели, как просто и быстро осуществляется подключение к точке доступа:

Не самый простейший вариант подключения к точке доступа в Arduino

Не самый простейший вариант подключения к точке доступа в Arduino


Теперь давайте взглянем на пример такого же подключения, но уже из ESP-IDF:

Пример подключения к WiFi из protocol_examples_common, который используется во всех сетевых примерах ESP-IDF

Пример подключения к WiFi из protocol_examples_common, который используется во всех сетевых примерах ESP-IDF

Кошмар! Куча непонятных вызовов функций, которые сходу непонятно зачем нужны. И это ещё не самый сложный вариант, который не учитывает многие моменты и проблемы.

Зачем всё это надо? Может быть, проще вовсе не подключаться к WiFi?

Источник: Яндекс.Картинки

Источник: Яндекс.Картинки

Но мы же легких путей не ищем! Давайте разбираться, что, зачем и почему.

Необходимое предупреждение. Статья получилась довольно длинной, и возможно, немного запутанной. Потому что процесс не очень простой. Я попытался описать всё максимально подробно, но возможны недочеты. Если таковые найдутся – прошу в комментарии.


Что можно на WiFi ESP32

ESP32 поддерживает следующие функции:

  • 3 виртуальных интерфейса Wi-Fi: STA (станция), AP (точка доступа) и Sniffer. Есть ещё четвертый режим, но он пока “в резерве”.
  • Можно скрещивать ежа с ужом режим STA c AP – поддерживается режим STA+AP.
  • Протоколы IEEE 802.11b, IEEE 802.11g, IEEE 802.11n и API для настройки режима протокола.
  • Протоколы шифрования WPA/WPA2/WPA3/WPA2-Enterprise/WPA3-Enterprise/WAPI/WPS and DPP
  • AMSDU, AMPDU, HT40, QoS и другие функции
  • Есть возможность перевода модема в режим сна для экономии энергии
  • Протокол ESP-NOW и режим Long Range, поддерживающий передачу данных на расстояние до 1 км, но только между двумя ESP32. Эти режимы разработаны Espessif и поддерживаются только на ESP.
  • Пропускная способность TCP до 20 Мбит/с и пропускная способность UDP 30 Мбит/с по радиоканалу.
  • Как быстрое сканирование, так и сканирование всех каналов.
  • Несколько типов антенн.
  • Информация о состоянии канала.

API-интерфейсы, предоставляемые ESP-IDF, являются потокобезопасными, и вам не требуется дополнительно заботиться о синхронизации доступа к интернету из различных задач и сервисов.

 


Последовательность операций для подключения к WiFi

Итак, что же необходимо сделать для того, чтобы наш контроллер подключился к WiFi сети?

  • Создать и запустить системный цикл событий. В принципе, можно использовать и пользовательский цикл событий, но никакого смысла в этом нет. Через этот цикл событий драйвер WiFi будет рассылать уведомления об изменении состояния WiFi подсистемы.
  • Так как подключение к WiFi всегда обрабатывается “по событиям”, мы должны создать и зарегистрировать обработчики для различных событий, генерируемых ESP-IDF при подключении: WIFI_EVENT_STA_CONNECTED (подключение к точке доступа установлено), IP_EVENT_STA_GOT_IP (IP-адрес получен), WIFI_EVENT_STA_DISCONNECTED (подключение к точке доступа потеряно) и других. В принципе, это почти то же самое, что и callback-функции, так что особых проблем это не доставляет.
  • Создать группу событий, в которой удобно регистрировать текущее состояние WiFi-подсистемы. Это не обязательно, в некоторых примерах этого нет. Но без этого в некоторых состояниях сложно понять – в каком месте алгоритма мы находимся и как реагировать на то или иное поступившее событие. Например, событие WIFI_EVENT_STA_DISCONNECTED будет сгенерировано в двух случаях – при разрыве существующего соединения или при неудачной попытке подключения к заданной точке доступа. И когда это событие поступит, вам нужно будет решить – как на него реагировать. Группа событий поможет вам в этом.
  • Прочитать MAC-адрес устройства из EFUSE, если это необходимо. По умолчанию WiFi не знает MAC адрес, он хранится в EFUSE микроконтроллера.
  • Инициализировать TCP/IP стек через библиотеку ESP-NETIF. Назначение библиотеки ESP-NETIF – дополнительный уровень абстракции для приложения поверх стека TCP/IP, который позволит приложениям выбирать между стеками IP. API-интерфейсы, которые он предоставляет, являются потокобезопасными, даже если базовые API-интерфейсы стека TCP/IP таковыми не являются.
  • Установить тип хранилища для служебных данных (RAM или NVS-раздел) и инициализировать NVS раздел Flash (если это ещё не было сделано). Если флэш-память NVS для WiFi включена, все конфигурации WiFi, установленные с помощью API WiFi, будут сохранены во флэш-памяти, и драйвер WiFi запустится с этими конфигурациями при следующем включении или перезагрузке. Однако приложение может отключить запись конфигурации во флэш-память NVS, если ему это не нужно.
  • Установить режим WIFI_MODE_STA. И дополнительные параметры, если требуется.
  • Запустить STA режим и дождаться выполнения нескольких последовательных стадий: STA запущен, подключение выполнено, IP-адрес получен. Да, именно в такой последовательности: вначале придет событие WIFI_EVENT_STA_CONNECTED, и только потом IP_EVENT_STA_GOT_IP. IP_EVENT_STA_GOT_IP можно считать финальным этапом подключения к сети, после чего запускать другие сетевые сервисы (SNTP, PING, MQTT, HTTP и т.д.).

Ниже представлен «общий сценарий», описывающий работу в режиме STA:

Примерная схема работы с драйвером WiFi

Примерная схема работы с драйвером WiFi

Примечание:

  • Main Task – ваше основное приложение
  • App Task – ваше приложение, осуществляющее работу с WiFi модулем.

Кстати, для своего варианта библиотеки WiFi я вначале беззастенчиво позаимствовал исходники модуля WiFi для ArduinoIDE. Не всё, конечно, но основной алгоритм. Постепенно, с ростом понимания “что и зачем”, отличий в моей версии от “оригинала” стало ещё больше.


Используемые технологии и принципы работы

Для начала я поясню, какие механизмы и технологии я использую в своих проектах. Все примеры, приведенные в данной статье, основаны на моей библиотеке, которую я использую в своих проектах для установки и поддержания подключения к WiFi в режиме STA.

GitHub – kotyara12/reWiFi: A library for connecting to a WiFi hotspot using only the ESP-IDF framework

1. Я не использую web-интерфейс в своих проектах, так как не вижу в нем вообще никакой необходимости – всё, что нужно настроить, я настраиваю в самой прошивке. Зачем же тратить на это свое время и силы, а также ресурсы микроконтроллера? Поэтому для меня нет необходимости и в использовании режима AP. Если вам позарез нужен режим AP – увы, но тут я вам помочь не смогу.

2. Для хранения состояния WiFi модуля используется специально выделенная группа событий. Там хранится следующая информация:

Набор используемых флагов

Набор используемых флагов

Эти флаги позволяют в любой момент времени однозначно понять состояние WiFi подсистемы. Флаги обычно устанавливаются или сбрасываются в обработчиках событий WIFI_EVENTS.

3. Я использую “родные” / native события WIFI_EVENTS только внутри модуля reWiFi. Для оповещения других прикладных задач об подключении и отключении к сети WiFi я создал “свои” события RE_WIFI_EVENTS с немного измененной логикой, так как native события мне были не совсем удобны. Например – задаче MQTT совсем не обязательно помнить и знать, что послужило причиной события WIFI_EVENT_STA_DISCONNECTED – потеря соединения или неудачная попытка подключения. Поэтому я “транслировал” их удобным мне образом. Кроме того, для прикладного уровня я часто использую дополнительный прикладной цикл событий, дабы не перегружать системный. Поэтому новые “переведенные” события отправляются уже туда.

4. При подключении к сети WiFi при очень слабом уровне сигнала и высоком уровне помех возможны любые проблемы и промежуточные состояния. Поэтому для прерывания попытки подключения по таймауту используется программный таймер ESP-IDF, про которые я рассказывал в одной из предыдущих статей.


Обработчики событий для режима STA

Для начала давайте рассмотрим систему событий WiFi модуля и как с ней работать. Возможно, вначале вам будут непонятны внутренние функции библиотеки типа wifiConnectSTA() – не пугайтесь, про них я подробнее расскажу чуть позже.

Как я уже кратко упомянул выше, вся работа с драйвером WiFi основана на событиях. Поэтому прежде чем начинать подключение к WiFi, нам необходимо запустить системный цикл событий и зарегистрировать обработчики этих событий. Драйвер WiFi поддерживает следующие события для STA режима:

  • WIFI_EVENT_SCAN_DONE – завершено сканирование (поиск) точек доступа. На самом деле это событие не совсем относится к режиму STA, так как при прямом подключении к заранее известной точке доступа его обработка не требуется. Но если вам заранее не известно, к какой точке доступа вы хотите подключиться (например чтобы выбрать сеть с лучшим сигналом), то вы можете использовать функцию esp_wifi_scan_start() и обработать это событие. Данное событие будет сгенерировано только после завершения сканирования, например если целевая точка доступа была успешно найдена или все каналы были просканированы. Либо если сканирование было принудительно прервано esp_wifi_scan_stop(). Событие «сканирование выполнено» не будет сгенерировано при “прямом” подключении через esp_wifi_connect().

Я не использую обработчики для этого события, нет необходимости.

 

  • WIFI_EVENT_STA_START – режим STA был успешно запущен с помощью esp_wifi_start() и текущий режим Wi-Fi – станция (STA) или станция/точка доступа (STA+AP).

Получив это событие, внутренняя задача драйвера инициализирует сетевой интерфейс LwIP (netif).

Ваш обработчик этого события в самом простом случае должен инициировать подключение к ранее настроенной точке доступа через вызов esp_wifi_connect(). Дополнительно можно переключить какие-то флаги в группе событий, если вы их используете.

Мой обработчик для данного события выглядит следующим образом:

Обработчик запуска STA режима

Обработчик запуска STA режима

Что здесь происходит:

  1. Устанавливаем биты STA_ENABLED и STA_STARTED в группе событий, и сбрасываем биты STA_CONNECTED, STA_GOT_IP, а также служебные биты STA_DISCONNECT_STOP и STA_DISCONNECT_RESTORE.
  2. Сбрасываем счетчик попыток подключения
  3. Отправляем сообщение о запуске STA в прикладной цикл сообщений
  4. Запускаем таймер для принудительного перезапуска устройства из-за зависания попытки подключения, если “что-то пошло не так”. На самом деле сейчас этот таймер был добавлен из-за моего же собственного “косяка” в коде, который уже исправлен, и сейчас уже не актуален.
  5. Запускаем процесс подключения, но если эта функция вдруг вернула ошибку, останавливаем STA (а уже обработчик WIFI_EVENT_STA_STOP должен инициировать перезапуск STA с самого начала).

 

  • WIFI_EVENT_STA_STOP – режим STA был остановлен.

При получении этого события внутренняя задача драйвера освободит IP-адрес станции, остановит клиент DHCP, удалит соединения, связанные с TCP/UDP, и очистит netif станции LwIP и т.д.

Ваш обработчик этого события приложения может заключаться в том, чтобы приостановить выполнение прикладных сетевых задач (например MQTT-клиента) и (или) перезапустить STA режим заново.

Мой обработчик для данного события выглядит следующим образом:

Обработчик остановки STA режима

Обработчик остановки STA режима

Что здесь происходит:

  1. Сбрасываем биты STA_STARTED, STA_CONNECTED, STA_GOT_IP, если они были установлены.
  2. Отправляем сообщение об отключении STA в прикладной цикл сообщений
  3. Останавливаем и удаляем таймер подключения, если он был.
  4. Если флаг STA_ENABLED установлен (то есть это не принудительное отключение от WiFi), то начинаем процесс запуска режима STA заново. Иначе – выгружаем из памяти всё, что там было для работы STA.

 

  • WIFI_EVENT_STA_CONNECTED – если функция esp_wifi_connect() вернула ESP_OK и ESP32 успешно подключилась к указанной точке доступа, возникает событие подключения.

При получении этого события внутренняя задача драйвера запускает DHCP-клиент и начинает процесс DHCP для получения IP-адреса.

После этого драйвер Wi-Fi готов к отправке и приему данных. Однако начинать работу с сетью еще рановато – если приложение основано на LwIP (по умолчанию оно так и есть), вам придется подождать, пока не придет событие IP_EVENT_STA_GOT_IP.

Мой обработчик для данного события выглядит следующим образом:

Обработчик подключения к точке доступа

Обработчик подключения к точке доступа

Что здесь происходит:

  1. Устанавливаем бит STA_CONNECTED, но сбрасываем биты STA_GOT_IP, STA_DISCONNECT_STOP и STA_DISCONNECT_RESTORE, если они были установлены.
  2. Если используется режим выбора из нескольких сетей WiFi, запоминаем в NVS индекс сети, к которой удалось подключиться. В следующий раз устройство начнет именно с этой сети.
  3. Выводим в лог имя сети и уровень сигнала, для отладки
  4. Перезапускаем таймер таймаута подключения – при неустойчивом сигнале получения IP-адреса можно и не дождаться, таймер при этом перезапустит STA полностью.

 

  • WIFI_EVENT_STA_DISCONNECTED – данное событие может быть сгенерировано в следующих случаях:
  1. Когда вызывается esp_wifi_disconnect() или esp_wifi_stop() и станция уже была подключена к точке доступа, то есть при принудительном отключении от точки доступа.
  2. При попытке подключения к точке доступа через esp_wifi_connect(), но при этом драйверу Wi-Fi не удается установить соединение по каким-либо причинам: например, при сканировании не удается найти целевую точку доступа или истекло время аутентификации. Если имеется более одной точки доступа с одним и тем же SSID, событие разъединения будет вызвано после того, как ESP32 не удастся подключиться ко всем найденным точкам доступа.
  3. Когда существующее соединение Wi-Fi прерывается по причине сбоя: например, станция теряет N маяков подряд, точка доступа отключает станцию ​​или изменяется режим аутентификации точки доступа.

После получения этого события внутренняя задача драйвера вызывает отключение LwIP станции и уведомление задачи LwIP об очистке соединений UDP/TCP, которые вызывают неправильное состояние всех сокетов. Для приложений на основе сокетов функция обратного вызова приложения может выбрать закрытие всех сокетов и их повторное создание, если это необходимо, после получения этого события.

Наиболее распространенным кодом обработки этого события в вашем приложении является вызов esp_wifi_connect() для повторного подключения к сети Wi-Fi. Однако если событие вызывается из-за принудительного вызова esp_wifi_disconnect(), приложение не должно вызывать повторное подключение. Ваше приложение обязано различать, вызвано ли данное событие принудительным отключением либо внешними причинами.

Мой обработчик для данного события самый сложный из всех и выглядит следующим образом (внимание – на скриншоте текст обработчика не полный, для просмотра перейдите по ссылке):

Часть обработчика для события отключения

Часть обработчика для события отключения

Что здесь происходит:

  1. Для начала определяем, чем было вызвано это событие. Если на момент события был установлен флаг STA_CONNECTED, значит это потеря подключения, если нет – неудачная попытка подключения к точке доступа. То же самое определяем и для бита STA_GOT_IP.
  2. Сбрасываем биты STA_CONNECTED и STA_GOT_IP.
  3. Останавливаем таймер подключения – это актуально, когда событие было вызвано из попытки подключения.
  4. По возможности определяем причину отключения. Затем, если это было именно потеря соединения, уведомляем прикладные задачи через прикладной цикл событий. Если это просто неудачная попытка соединения – ничего отправлять не требуется.
  5. Если это принудительное отключение командой, останавливаем WiFi режим и ждем дальнейших указаний программиста.
  6. Иначе пробуем переподключиться к точке доступа ещё раз. Если это не удалось выполнить – сбрасываем STA в исходное состояние и останавливаем его. Далее обработчик WIFI_EVENT_STA_STOP должен, в свою очередь, запустить STA заново, что называется “с нуля”.

 

  • IP_EVENT_STA_GOT_IP – это событие возникает, когда DHCP-клиент успешно получает адрес IPV4 от DHCP-сервера или когда адрес IPV4 изменяется.
  • IP_EVENT_GOT_IP6 – то же самое, но при получении IP адреса IPV6. Событие будет сгенерировано, если включена поддержка IPV6 и ваш провайдер поддерживает работу с IPV6.

Данное событие означает, что все готово и приложение может приступить к своим задачам (например, запустить MQTT клиент или HTTP-сервер).

Мой обработчик для данного события выглядит следующим образом:

Обработчик события получения IP-адреса

Обработчик события получения IP-адреса

Что здесь происходит:

  1. Устанавливаем бит STA_GOT_IP.
  2. Сбрасываем счетчик попыток подключения и последнюю ошибку.
  3. Отправляем уведомление прикладным задачам “всё отлично, можно начинать работу
  4. Удаляем таймер таймаута подключения и останавливаем таймер аварийной перезагрузки устройства.

 

  • IP_EVENT_STA_LOST_IP – данное событие возникает, когда адрес IPV4 становится недействительным. IP_EVENT_STA_LOST_IP не возникает сразу после отключения Wi-Fi. Вместо этого он запускает таймер потери адреса. Если адрес IPV4 будет заново получен до истечения времени таймера потери IP, IP_EVENT_STA_LOST_IP не генерируется.

Как правило, ваше приложение может игнорировать это событие, потому что это просто событие отладки, сообщающее о потере адреса IPV4.

Я не использую обработчики для этого события, ввиду отсутствия необходимости.

Примечание: можно создать один “большой” обработчик сразу на все события (и внутри уже проверять тип события), но я предпочитаю создать несколько “мелких” отдельных обработчиков.

 

Есть ещё события для AP-режима, но я не буду обсуждать их в рамках данной статьи.


Подключение к сети WiFi

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

Запуск WiFi в моем случае осуществляется с помощью вот такой нехитрой функции (открыть исходники на GitHub):

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Шаг 1. Инициализация необходимых объектов и регистрация параметров

Первым делом необходимо создать группу событий для хранения состояния системы и сторожевой программный таймер (если он разрешен через файл конфигурации проекта). Делается это с помощью функции wifiInit():

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Если вы читали мои статьи о группах событий и таймерах, то вопросов возникнуть не должно. Если ещё не читали – рекомендую ознакомиться:

EventGroup – группы событийИспользование таймеров на ESP32

Шаг 2. Остановка текущего режима, если он был запущен

Как я уже упоминал, ESP-IDF может автоматически восстановить режим работы WiFi, если он был сохранён в NVS разделе в предыдущем сеансе работы. Чтобы запустить новый сеанс с новыми параметрами, необходимо остановить текущий с помощью функции wifiStop():

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Вначале снимаем флаг, разрешающий автоматическое пере-подключение к точке доступа после отключения, а затем инициируем отключение от сети:

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Если на данный момент соединения не было, то ничего и не выполнится.

Шаг 3. Низкоуровневая инициализация TCP/IP и netif

В первую очередь нужно прочитать MAC-адрес, запустить системный цикл событий и инициализировать библиотеку сетевого интерфейса LwIP (netif):

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Делается это один раз при первом запуске WiFi драйвера:

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Что здесь происходит:

  1. Проверяем, установлен ли бит _WIFI_LOWLEVEL_INIT – если да, то просто выходим.
  2. Выполняем инициализацию стека TCP/IP и netif, если это ещё не было выполнено.
  3. Создаем экземпляр сетевого интерфейса netif в режиме STA. Если экземпляр сетевого интерфейса netif уже был создан ранее, перед созданием уничтожаем предыдущий экземпляр.
  4. Инициализируем wifi с помощью функции esp_wifi_init(). Если данная функция вернула код ошибки 4543, это означает, что раздел NVS ещё не был инициализирован. Даже если вы не собираетесь хранить параметры сети в NVS, придется его инициализировать и повторить попытку заново. Подробнее про работу с NVS я расскажу позднее.
  5. При необходимости (если это задано макросом CONFIG_WIFI_STORAGE) указываем режим хранения служебных данных WiFi драйвера – в памяти или в NVS разделе.
  6. Регистрируем обработчики событий, которые мы рассматривали в предыдущем разделе, с помощью функции wifiRegisterEventHandlers():

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Шаг 4. Устанавливаем флаг _WIFI_STA_ENABLED

Этот бит разрешает автоматическое переподключение к сети после сбоя или неудачной попытки соединения.

Шаг 5. Настраиваем и запускаем режим STA

Почти всё готово к запуску STA. Можно начинать с помощью функции wifiStartWiFi(), которая в свою очередь вызывает другую, внутреннюю, функцию _wifiStartSTA():

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Здесь мы:

  1. Устанавливаем режим с помощью esp_wifi_set_mode()
  2. В случае необходимости настраиваем ширину занимаемого диапазона частот – 20 или 40 МГц. При плохом уровне сигнала имеет смысл снизить ширину канала, чтобы повысить устойчивость передачи, но это приведет к снижению скорости передачи.
  3. Запускаем STA режим с помощью esp_wifi_start().

На этом все подготовительные операции закончены, и в дело вступают события и их обработчики.

 

Дальнейшая последовательность событий

Через некоторое время драйвер WiFi будет запущен и будет сгенерировано событие WIFI_EVENT_STA_START. В конце обработчика, как я описал выше, будет вызвана функция начала подключения к точке доступа wifiConnectSTA():

Для простоты я свернул часть кода, где выбирается одна сеть из нескольких возможных

Для простоты я свернул часть кода, где выбирается одна сеть из нескольких возможных

Здесь происходит вся настройка подключения к заданной точке доступа с помощью sp_wifi_set_config() – именно мы указываем имя сети SSID и пароль подключения. После этого запускаем сторожевой таймер подключения и инициируем подключение с помощью системной функции esp_wifi_connect().

Если попытка соединения будет не столь успешной, то через несколько секунд мы получим событие WIFI_EVENT_STA_DISCONNECTED, которое, в свою очередь, передаст управление достаточно запутанной функции wifiReconnectWiFi(), которая по сути выполняет простую задачу повторной попытки подключения к сети, и цикл начнется заново.

Если попытка соединения будет успешной, то через ещё какое время будет сгенерировано другое событие – WIFI_EVENT_STA_CONNECTED (в обработчике которого, по сути, ничего толкового не происходит, только биты состояний переключаются), а ещё через какое-то время и ещё одно, финальное событие – IP_EVENT_STA_GOT_IP.

На это все, можно начинать работу с сетью.


Сценарий “дождливого дня”

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

Ох сколько нам глюков чудных дарует кроворуких программистов век…
(с) неизвестный автор

Уход процесса подключения в глухую несознанку

Частая проблема при большом удалении esp32 от точки доступа (на пределе “слышимости”) – зависание при попытке подключения. Когда соединение уже состоялось, драйвер контролирует соединение с помощью периодической отправки маячка. В процессе установки соединения иногда возникает ситуация, когда процесс слишком затягивается. Самое простое решение проблемы – перед установкой соединения запустить программный таймер на несколько минут, который в случае зависания прервет процесс и начнет соединение заново.

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Это поможет избежать ситуации, когда ESP32 завис на процессе подключения к сети на неопределенное время

 

Логика при неудачной попытке подключения

Если после попытки подключения к точке доступа esp_wifi_connect() вместо ожидаемого WIFI_EVENT_STA_CONNECTED приходит унылое WIFI_EVENT_STA_DISCONNECTED, то мы имеем следующую логику:

1. Первые CONFIG_WIFI_RECONNECT_ATTEMPTS (30 по умолчанию) попыток просто пытаемся подключиться к точке доступа через тот же вызов esp_wifi_connect(). Это необходимо, чтобы иметь достаточный запас времени для перезагрузки роутера, например. В среднем на одну попытку уходит 2-3 секунды, то мы имеем 1-1,5 минут времени без смены точки доступа.

2. Когда количество попыток превысит заданные CONFIG_WIFI_RECONNECT_ATTEMPTS, библиотека начнет перебирать все доступные варианты подключений (можно настроить до пяти сетей) по очереди. Это позволяет безболезненно переключить устройство к другому роутеру.

3. Если количество неудачных попыток превысит следующий порог CONFIG_WIFI_RESTART_ATTEMPTS (100 по умолчанию), то будет отдана команда на полный перезапуск STA режима со сбросом всех параметров на начальные. Очень изредка, но увы, случается такое, что ESP32 “ни в какую” не хочет подключаться к точке доступа, хотя она успешно функционирует. “Холодный старт” STA режима позволяет решить проблему в таком случае.

Проблема перезапуска WiFi драйвера

Драйвер WiFi основан на событиях. Давайте подумаем, что нужно сделать, чтобы перезапустить или даже просто остановить WiFi?

  • Отправить команду на отключение, если оно имеется с помощью esp_wifi_disconnect()
  • Дождаться события WIFI_EVENT_STA_DISCONNECTED
  • Отправить команду на остановку STA, выполнив esp_wifi_stop()
  • Дождаться события WIFI_EVENT_STA_STOP
  • Предпринять какие-либо действия по восстановлению подключения…

Я не зря выделил жирным шрифтом слова “дождаться события” – в них заключается очень большая скрытая проблема. Казалось бы – в чем проблема-то? Отправили команду, ждем события через xEventGroupGetBits()

А теперь представьте, что вам необходимо сделать это из обработчика другого события. Например, вы получили событие WIFI_EVENT_STA_DISCONNECTED уже много раз, и необходимо перезапустить весь интерфейс. Вы отдаете команду esp_wifi_stop() и начинаете ожидать другого события. Ждать вы будете долго. Очень долго…. Событие WIFI_EVENT_STA_STOP будет сгенерировано, как ему и положено. Но вот задача обработки событий занята пока ещё вашим предыдущим событием и обработать новое пока не в состоянии. Возникает deadlock, тупиковая ситуация. Шах и мат.

Поэтому я в такой ситуации выставляю “служебные” биты _WIFI_STA_DISCONNECT_STOP или _WIFI_STA_DISCONNECT_RESTORE и выхожу из текущего обработчика без ожидания. А уже другой обработчик, “увидев” взведенные служебные биты, будет действовать по другому алгоритму, чем обычно.


Использование библиотеки reWiFi

В заключение хочу дать несколько советов по использованию или адаптации моей библиотеки reWiFi, которую можно найти на GitHub.

В конечном итоге её использование сводится к простому коду:

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

Как видите – все предельно просто. Все настройки – имя сети, пароль и т.д. зашиты в файле конфигурации проекта, про который я писал ранее:

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

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

Если взглянуть на список прилинкованных библиотек, то можно увидеть, что кроме стандартных используется несколько моих библиотек:

Подключение к точке доступа WiFi из ESP32 с ESP-IDF

  • rLog – от неё, в принципе, достаточно легко избавиться, просто заменив вызовы re_logx() на ESP_LOGx().
  • rTypes – по большому счету это просто объявление “стандартных” типов данных в моей прошивке. Вы можете просто перенести используемые объявления в свой код.
  • rStrings – библиотечка для работы с текстовыми строками в динамической памяти, про неё я уже писал ранее.
  • reEsp32 – сборник разных “общесистемных” функций
  • reEvents – моя “обёртка” для циклов событий, через которую в том числе идет общение с прикладным циклом
  • reNVS – ещё одна библиотека – “обёртка” для работы с NVS разделом flash
  • reParams – библиотека для обслуживания списка параметров устройства и хранения их в NVS разделе

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


Полезные ссылки

  1. ESP-IDF :: Wi-Fi driver
  2. ESP-IDF :: NETIF
  3. Библиотека reWiFi

 

На этом пока всё, до встречи на сайте и на dzen-канале!


Пожалуйста, оцените статью:
[ 0 из 5, всего 0 оценок ]

4 комментария для “Подключение к точке доступа WiFi из ESP32 с ESP-IDF”

  1. Николай

    Добрый день.
    Я пытаюсь построить простой проект в котором хочу просто подключить ESP к WiFi.
    При попытке скомпилировть проект получаю ошибку:
    PlatformIO/libs/wifi/reWiFi/include/reWiFi.h:18:10: fatal error: cstring: No such file or directory
    18 | #include
    Пути к библиотекам указаны верно.
    При этом, если компилировать Ваш проект telemeter_dzen, то всё проходит без ошибок.
    Можите подсказать, в чём проблема и что надо “подкрутить”.
    Спасибо.

    1. А какой именно библиотеки не находит компилятор? Системной или моей?
      Если системной – то скорее всего проблема в версии, так как статья написана была еще на 4.4.3, а в 5.0.0 уже многое поменялось
      Если моей – тот тут ничего не поделаешь, у меня многие библиотеки связаны друг с другом, это по сути цельная прошивка и одна либа “тащит всех подруг в круг”.
      Или переделывайте код по своему, без этих включений, но тогда вам придется не просто скомпилировать пример, а еще и немного подумать, как заменить или исключить некоторые фрагменты.
      Или используйте все библиотеки, как в telemeter_dzen

      1. Николай

        Спасибо за Ваш ответ.
        Я нашёл проблему. Ошибка возникала в includ в вашей либе reWiFi, при попытке выполнить #include .Но это, как оказалось, больше поя вина. При создании нового проекта, VSCode создал файл main.c с расширением “С”. Очевидно компилятор основываясь на этом выполнял компиляцию с настройками именно для Си проекта. А библиотека cstring и её импорт без указания расширения .h, если я не ошибаюсь, является частью С++. Стоило переименовать main.c в main.cpp, как всё стало на свои места. 🙂
        Да, я заметил что Ваши библиотеки, во многом, связаны друг с другом. Я как раз пытаюсь эту связь немного ослабить/удалить. И для подключения к WiFi мне это удалось. Код скомпилировался, прошился и работает. Но я продолжаю изучать библиотеки. Следующий шаг будет добавление кода работы с датой и временем.
        Ещё хотел спросить. Будет ли заметка про режимы сна для ESP32? Я имею ввиду как это делается в ESP-IDF. Я сделал устройство на либах из Arduino, но мне это не очень нравится. Хотелось бы его переделать. И так как устройство питается от аккумулятора, зарядка от солнечной панели, то устройство надо укладывать спать. А при пробуждении делать замеры нужных параметров, в том числе и состояние батареи, проверять нет ли обновлений прошивки (если есть то загрузить и установить) и опять в сон. Поэтому, освещение этих вопросов в контексте ESP-IDF было бы очень интересно.
        Ну, как-то так. 🙂
        Ещё раз спасибо.

        1. С режимами сна я не разбирался, у меня все устройства с сетевым питанием и работают в цикле. Но в общих чертах знаю, что пробуждать из сна можно по таймеру или сигналу на выводе. Но там есть кое-какие ограничения, например слетает счетчик тактов процессора.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *