Рубрики
Проекты на ESP32

Веб-сервер WebSocket на ESP32 для управления светодиодами с помощью ESP-IDF

В этом уроке мы научимся создавать веб-сервер WebSocket на ESP32, используя SPIFFS и ESP-IDF. Сервер WebSocket — это сервер, который использует протокол WebSocket для обработки соединений WebSocket. Для демонстрации мы создадим веб-страницу, которая сможет включать светодиод на плате ESP32. Эта веб-страница будет также отображать состояние светодиода.

В сети есть примеры веб-серверов на базе HTTP для ESP32, которые предоставляют HTML/CSS-файлы веб-клиентам. Недостатком использования библиотеки HTTP-сервера является то, что она не обновляет автоматически состояние светодиода на веб-странице для всех клиентов, если к веб-серверу подключено более одного клиента. Эту проблему можно решить, используя протокол связи WebSocket. Например, если к веб-серверу подключено несколько клиентов, и любой из них изменяет состояние контакта GPIO, он автоматически уведомит об этом всех подключенных клиентов.

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

Как работает веб-сервер WebSocket на ESP32

Этот WebSocket-сервер для ESP32 предоставляет две основные функции: отображение веб-страницы с кнопкой для переключения состояния светодиода и отправку состояния светодиода в виде сообщения WebSocket всем подключенным клиентам. Когда сервер получает сообщение «toggle» от любого клиента, состояние светодиода инвертируется, и обновленное состояние затем отправляется обратно всем подключенным клиентам в виде сообщения WebSocket. Сервер также предоставляет два URI: «/» и «/ws». Первый используется для отображения веб-страницы, а второй — для обмена данными по протоколу WebSocket. Веб-страница клиента хранится в файловой системе SPI flash и отображается сервером по URI «/».

Пользователи могут получить доступ к веб-серверу, используя IP-адрес, присвоенный ESP32, по локальной сети.

Создание веб-сервера WebSocket для ESP32 с использованием ESP-IDF

В этом разделе давайте создадим проект веб-сервера ESP32 WebSocket с использованием ESP-IDF.

Создание примера проекта

Откройте VS Code и перейдите в меню View > Command Palette («Вид» > «Палитра команд»). Введите в строке поиска ESP-IDF: New Project и нажмите Enter.

Укажите название проекта и каталог, а также плату и целевое устройство ESP-IDF. Мы назвали наш проект «esp32_websocket_server». Для платы ESP-IDF мы выбрали опцию custom board («пользовательская плата»). В качестве целевого устройства ESP-IDF мы выбрали модуль ESP32. Нажмите кнопку ‘Choose Template’ («Выбрать шаблон»), чтобы продолжить.

В разделе «Расширение» выберите опцию ESP-IDF.

На вкладке get-started («Начало работы») мы щелкнем по пункту «sample_project». Затем нажмем ‘Create project using template sample_project’) («Создать проект, используя шаблон sample_project»).

Вы получите уведомление о создании проекта. Чтобы открыть проект в новом окне, нажмите «Да».

Это открывает наш проект esp32_websocket_server, который мы создали на вкладке EXPLORER. Внутри папки проекта находится несколько папок. Это справедливо для всех проектов, которые вы будете создавать с помощью ESP-IDF Explorer.

Создание раздела для SPIFFS

В этом разделе мы создадим раздел для SPIFFS, где будем хранить HTML/CSS для веб-сервера.

Создайте новый файл и назовите его «partitions.csv». Скопируйте в этот файл приведенные ниже данные и сохраните его. Нам нужно добавить собственную пользовательскую таблицу разделов, поэтому мы будем использовать этот файл.

После создания файла partitions.csv создайте в каталоге проекта папку с именем ‘data‘. Внутри этой папки создайте текстовый файл с именем ‘index.html‘.

Файл index.htm определяет структуру и содержимое веб-страницы, которую веб-сервер ESP32 будет отправлять в ответ на HTTP-запросы от веб-клиентов.

Веб-страница веб-сервера WebSocket на ESP32

Этот HTML/CSS-код создает веб-страницу для сервера WebSocket на ESP32. На странице будет кнопка с надписью «Переключить светодиод» и текст, отображающий текущее состояние светодиода (включен или выключен).

Файл начинается с установки типа документа (doctype) на HTML и создания базовой структуры HTML-страницы с помощью тегов head и body. Внутри тега head находятся заголовок (title), несколько метатегов (meta tags) и блок CSS, определяющий макет, цвета и стили элементов страницы.

В теле HTML-кода находится верхняя панель навигации с надписью «ESP32 WebSocket Server», а внутри неё — ещё один div с классом «content», где размещена карточка с кнопкой для переключения светодиода и текстом, отображающим текущее состояние светодиода.

В конце добавляется блок скрипта, содержащий код JavaScript, в котором определяется переменная «gateway», являющаяся конечной точкой WebSocket. Имеется обработчик событий, который отслеживает событие «load» окна, и когда это событие происходит, вызывается функция «onLoad()», которая, в свою очередь, вызывает функцию «initWebSocket()» для открытия соединения WebSocket. Также имеются другие функции, такие как «onOpen()», «onClose()», «onMessage()» и т. д., которые обрабатывают различные события, которые могут происходить в WebSocket.

При нажатии кнопки «Переключить светодиод» отправляется сообщение на ESP32 через WebSocket для переключения состояния светодиода, а также обновляется текст состояния на странице, отражающий текущее состояние светодиода.

Он также содержит функцию с именем ‘initButton()’, которая вызывается в функции ‘onLoad()’. Эта функция отвечает за инициализацию элемента кнопки на странице и прикрепление к нему обработчика событий. При нажатии кнопки она отправляет сообщение через соединение WebSocket на ESP32 для переключения состояния светодиода.

В коде также используется метод DOM document.getElementById() для получения ссылок на конкретные элементы на странице, такие как элемент span с состоянием и элемент button. Это позволяет коду JavaScript обновлять содержимое этих элементов и изменять состояние светодиода на основе сообщений, полученных от ESP32.

В коде также используется функция console.log(), которая выводит сообщения в консоль разработчика браузера. Это полезно для отладки и понимания логики выполнения кода.

В целом, этот код создает простую веб-страницу, которая подключается к WebSocket-серверу ESP32 и позволяет переключать состояние светодиода, подключенного к ESP32, нажатием кнопки на странице. Состояние светодиода также отображается на странице в режиме реального времени.

Перейдите в папку с именем ‘main’ и откройте файл CMakeLists.txt. Добавьте в конец этого файла следующую строку и сохраните его.

Настройка веб-сервера WebSocket на ESP32 в ESP-IDF

Теперь давайте настроим некоторые параметры для ESP32 и создадим файл Kconfig.projbuild для настройки учетных данных Wi-Fi.

Настройка пользовательского файла разделов в ESP-IDF

Сначала перейдите в меню menuconfig. Щелкните значок, показанный ниже. Откроется редактор конфигурации ESP-IDF SDK.

Прокрутите вниз и откройте конфигурацию Serial Flasher. Здесь вы можете установить режим SPI для флэш-памяти, режим выборки флэш-памяти, скорость SPI для флэш-памяти, размер флэш-памяти и т. д. Установите параметры, как показано ниже, и сохраните изменения. Обратите внимание, что мы установили размер флэш-памяти равным 4 МБ.

Далее прокрутите вниз и нажмите Partition Table («Таблица разделов»). Здесь мы выберем опцию ‘custom partition table CSV’ («Пользовательская таблица разделов CSV») в разделе «Таблица разделов», укажем имя созданного нами CSV-файла и смещение таблицы разделов.

Настройка учетных данных Wi-Fi

Создайте новый файл с именем ‘Kconfig.projbuild‘ в основной папке. Скопируйте приведенный ниже код  в этот новый файл.

Теперь перейдите в меню menuconfig. Щелкните значок, показанный ниже. Откроется редактор конфигурации ESP-IDF SDK.

Прокрутите вниз и откройте Example Configuration (пример конфигурации). Здесь мы можем установить параметры конфигурации для этого примера в соответствии с нашими потребностями. Это включает в себя SSID сети Wi-Fi, пароль сети Wi-Fi и максимальное количество попыток повтора. Укажите SSID и пароль сети Wi-Fi вашего маршрутизатора, к которому будет подключаться плата ESP32. По умолчанию максимальное количество попыток повтора установлено на 5. Вы можете изменить это значение по своему усмотрению. После указания параметров конфигурации нажмите кнопку «Сохранить» вверху.

Включение настроек сервера WebSocket

ESP-IDF поддерживает серверы WebSocket, и эта функция доступна в библиотеке HTTP-серверов. Она отключена по умолчанию. Её необходимо включить через menuconfig. Перейдите в setting->HTTP Server и выберите опцию поддержки серверов WebSocket.

Код веб-сервера WebSocket для ESP32

Этот код настраивает ESP32 в качестве WebSocket-сервера, который отправляет веб-страницу клиенту (веб-браузеру) и переключает состояние светодиода через эту веб-страницу.

Как работает код

Сначала подключим заголовочные файлы предназначенные для библиотек, специфичных для ESP32, и предоставляющих различные функции, необходимые ESP32 для обработки задач, связанных с веб-сервером и WebSocket, таких как создание и управление сетевыми соединениями, а также хранение и чтение файлов из флэш-памяти.

Параметр Define LED_PIN 2 определяет номер контакта, к которому подключен светодиод.

Параметр httpd_handle_t server = NULL; объявляет переменную server типа httpd_handle_t, которая является дескриптором веб-сервера WebSocker.

static const char *TAG = “WebSocket Server”; определяет строковую константу, используемую в целях отладки.

Настройка веб-сервера WebSocket на ESP32

Функция setup_websocket_server() настраивает и запускает HTTP-сервер с конечной точкой WebSocket.

Функция начинается с определения переменной типа httpd_config_t с именем config и конфигурацией по умолчанию. Затем она определяет две переменные типа httpd_uri_t: uri_get и ws. uri_get — это конфигурация для обработки GET-запросов к корневому URL («/»), а функция обработчика для этого URI устанавливается на get_req_handler().

ws — это конфигурация для обработки запросов WebSocket. URI для этой конфигурации устанавливается на «/ws», метод для этого URI — на HTTP_GET, а функция обработчика для этого URI — на handle_ws_req(). Также устанавливается значение is_websocket равным true, что сообщает серверу httpd, что этот URI будет обрабатывать трафик WebSocket.

После этого вызывается функция httpd_start(), в которую передаются переменная сервера и переменная конфигурации. Функция httpd_start() настраивает HTTP-сервер с указанной конфигурацией и возвращает ESP_OK, если сервер успешно запускается.

Затем, после функции httpd_start(), вызывается httpd_register_uri, код вызывает httpd_register_uri_handler(server, &uri_get); и httpd_register_uri_handler(server, &ws); для регистрации переменных uri_get и ws на сервере. Это связывает соответствующую функцию обработчика с соответствующим URI, так что при получении сервером запроса для этого URI будет вызвана соответствующая функция обработчика.

Функция handle_ws_req() отвечает за обработку запросов WebSocket и занимается открытием, закрытием и обработкой данных, передаваемых по WebSocket-соединению, что объясняется в следующем разделе. Функция get_req_handler() обрабатывает HTTP GET-запрос к корневому URL-адресу и предоставляет файл index.html, хранящийся во флэш-памяти SPIFFS, в качестве ответа на первоначальное рукопожатие с клиентами.

Наконец, функция setup_websocket_server() возвращает переменную server, которая является дескриптором сервера. Этот дескриптор можно использовать для доступа к серверу и выполнения таких задач, как остановка или отмена регистрации обработчиков URI.

Вкратце, эта функция настраивает и запускает HTTP-сервер с конечной точкой WebSocket, конфигурирует его для обработки GET-запросов по корневому URL и WebSocket-запросов по URI «/ws», а также регистрирует соответствующие функции обработчика для этих URI на сервере. Она также запускает HTTP-сервер и возвращает дескриптор сервера.

Функция обработки полученных кадров WebSocket

Функция handle_ws_req(httpd_req_t *req) отвечает за обработку запросов WebSocket, отправляемых на сервер от всех веб-клиентов.

Функция начинает с проверки, является ли метод запроса HTTP_GET. Если да, она выводит сообщение о завершении рукопожатия, открытии WebSocket-соединения и возвращает ESP_OK. Это связано с тем, что веб-серверы WebSocket начинают первоначальное взаимодействие с HTTP-рукопожатия, а затем следуют протоколу WebSocket.

Если метод запроса не HTTP_GET, это означает, что запрос отправляет кадры данных WebSocket. Затем функция объявляет переменную ws_pkt типа httpd_ws_frame_t и переменную buf типа uint8_t (указатели), которые будут использоваться для хранения кадров данных. После этого она устанавливает поля переменной ws_pkt в ноль и задает тип HTTPD_WS_TYPE_TEXT.

Затем функция вызывает httpd_ws_recv_frame(req, &ws_pkt, 0) для получения фрейма данных WebSocket и сохранения его в переменной ws_pkt. Эта функция возвращает ESP_OK, если фрейм данных получен успешно, и сохраняет длину фрейма данных в поле ws_pkt.len. Если функция возвращает ошибку, она регистрирует ошибку и возвращает её.

Если полученный кадр данных имеет ненулевую длину, функция вызывает calloc(1, ws_pkt.len + 1) для выделения памяти для переменной buf, которая будет использоваться для хранения полезной нагрузки кадра данных. Затем она снова вызывает httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len) для получения полезной нагрузки кадра данных и сохранения её в переменной buf. Функция выводит в лог полученное сообщение и длину кадра.

Наконец, функция проверяет, является ли полученное сообщение «toggle», и если да, то вызывает функцию trigger_async_send(req->handle, req) и возвращает ESP_OK.

Вкратце, эта функция обрабатывает WebSocket-запросы, обрабатывая WebSocket-кадры данных, принимая сообщение, отправленное в запросе, и обрабатывая это сообщение путем отправки асинхронного сообщения всем подключенным клиентам.

Функция отправки ответа на кадры WebSocket

Следующие две функции отвечают на запросы WebSocket и отправляют кадры всем подключенным клиентам. Функция ws_async_send отвечает за обработку отправки данных по WebSocket-соединению клиентам. Эта функция запускается при вызове функции trigger_async_send.

Функция trigger_async_send отвечает за запуск функции ws_async_send путем выделения памяти для структуры async_resp_arg, заполнения ее дескриптором сервера и файловым дескриптором веб-сокетного соединения, а затем постановки функции ws_async_send в очередь с помощью функции httpd_queue_work и передачи в качестве аргументов дескриптора сервера, функции ws_async_send и указателя на структуру resp_arg.

Функция ws_async_send принимает в качестве входного параметра указатель типа void arg, который представляет собой указатель на структуру async_resp_arg, содержащую дескриптор веб-сервера и файловый дескриптор соединения. Функция выполняет следующие шаги:

  • Переключает состояние переменной led_state, которая отслеживает состояние светодиода.
  • Обновляет состояние светодиода в соответствии с параметром led_state.
  • Форматирует строковый буфер с текущим значением led_state и формирует полезную нагрузку пакета веб-сокета.
  • Отправляет пакет всем подключенным клиентам, используя функцию httpd_ws_send_frame_async, передавая дескриптор веб-сервера, дескриптор файла и веб-сокет-пакет.
  • В итоге освобождается память, выделенная для структуры resp_arg.

В этом коде функция ws_async_send вызывается асинхронно для отправки сообщения WebSocket всем подключенным клиентам. Она принимает один аргумент — указатель на структуру async_resp_arg, содержащую информацию о текущем соединении.

Функция сначала инвертирует состояние светодиода, устанавливая led_state = !led_state, а также устанавливает состояние контакта светодиода в это значение с помощью функции gpio_set_level. Затем она создает буфер размером 4 и использует функцию sprintf для записи значения led_state в этот буфер в виде строки.

Затем создается структура httpd_ws_frame_t с именем ws_pkt, в ее полезной нагрузке указывается путь к буферу (buff), а len — длина строки, хранящейся в буфере, а также тип сообщения устанавливается на HTTPD_WS_TYPE_TEXT.

Затем функция получает список подключенных клиентов и перебирает его, отправляя сообщение ws_pkt всем клиентам, тип подключения которых равен HTTPD_WS_CLIENT_WEBSOCKET.

Наконец, освобождается память структуры resp_arg с помощью функции free(resp_arg);

Обработчик начального HTTP-рукопожатия WebSocket

Как обсуждалось ранее, веб-серверы WebSocket начинают первоначальное взаимодействие с HTTP-рукопожатия, а затем следуют протоколу связи WebSocket. Поэтому нам необходимо настроить обработчик HTTP GET-запросов.

Функция «get_req_handler()» используется для отправки веб-страницы клиенту в ответ на HTTP-запрос. В качестве аргумента она принимает указатель на структуру «httpd_req_t», которая представляет собой HTTP-запрос.

Функция начинается с объявления переменной «response» типа «int». Затем она проверяет значение глобальной переменной «led_state». Если оно истинно, функция использует функцию «sprintf()» для форматирования содержимого буфера «index_html» и строки «ON» в буфер «response_data». Если «led_state» ложно, функция форматирует содержимое буфера «index_html» и строку «OFF» в буфер «response_data».

Затем функция вызывает функцию «httpd_resp_send()», передавая ей в качестве аргументов указатель на аргумент «req», указатель на буфер «response_data» и значение «HTTPD_RESP_USE_STRLEN». Это отправляет содержимое буфера «response_data» в качестве ответа на HTTP-запрос. Затем функция возвращает значение, возвращаемое функцией «httpd_resp_send()».

Функция «get_req_handler()» используется в качестве обработчика запроса для корневого URI «/». В качестве аргумента она принимает указатель на структуру «httpd_req_t», которая представляет собой HTTP-запрос. Функция просто вызывает функцию «send_web_page()» и возвращает её значение.

Считывание содержимого файла index.html из SPIFSS в буфер (initi_web_page_buffer)

В коде веб-сервера ESP32 WebSocket функция initi_web_page_buffer() инициализирует буфер с именем «index_html» содержимым файла «index.html», хранящегося в файловой системе SPIFFS (SPI Flash File System).

Функция начинается с определения структуры с именем «conf» типа «esp_vfs_spiffs_conf_t» и инициализации её несколькими полями. Поле «base_path» задаёт корневой каталог файловой системы SPIFFS, поле «partition_label» задаёт метку раздела SPIFFS (в данном случае она устанавливается в NULL), поле «max_files» задаёт максимальное количество файлов, которые могут быть открыты одновременно, а поле «format_if_mount_failed» указывает, следует ли форматировать файловую систему в случае неудачного монтирования.

Затем функция вызывает функцию «esp_vfs_spiffs_register()», передавая ей в качестве аргумента указатель на структуру «conf». Эта функция монтирует файловую систему SPIFFS, используя указанную конфигурацию.

Затем функция обнуляет все байты в буфере «index_html» с помощью функции «memset()». После этого она объявляет структуру с именем «st» типа «struct stat» и проверяет, существует ли файл «index.html», используя функцию «stat()». Если файл не существует, она выводит сообщение об ошибке в лог и завершает работу функции.

Демонстрация работы веб-сервера WebSocket на ESP32

В этом разделе мы рассмотрим демонстрацию работы веб-сервера WebSocket на ESP32, созданного с помощью ESP-IDF. Для начала скачайте полный проект по следующей ссылке и соберите его.

Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.

После успешной прошивки кода вы можете просмотреть все информационные журналы. Сначала инициализируется Wi-Fi станции. Затем мы можем просмотреть esp_netif_handlers, который включает IP-адрес, маску и адрес шлюза. Затем мы получаем запись «получен IP-адрес», за которой следует сам IP-адрес. Мы будем использовать этот IP-адрес для доступа к веб-серверу.

Откройте новый веб-браузер и введите IP-адрес, полученный в терминале, затем нажмите Enter. Откроется веб-сервер ESP32 WebSocket, отображающий веб-страницу для переключения встроенного светодиода ESP32 (GPIO2). Изначально светодиод выключен, и его состояние также отображается на веб-сервере.

Теперь переключите кнопку, и светодиод будет включаться/выключаться соответственно. Аналогичным образом, состояние GPIO2 также будет постоянно обновляться на веб-сервере.

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

Заключение

В заключение, представленный код представляет собой простую реализацию WebSocket-сервера на микроконтроллере ESP32. WebSocket-сервер создан с использованием фреймворка ESP-IDF и библиотеки lwIP и основан на HTTP-сервере. Сервер предоставляет две основные функции: отображение веб-страницы с кнопкой для переключения состояния светодиода и отправку состояния светодиода в виде сообщения WebSocket всем подключенным клиентам. Когда сервер получает сообщение «toggle» от клиента, состояние светодиода инвертируется, и обновленное состояние затем отправляется обратно всем подключенным клиентам в виде сообщения WebSocket. Сервер также предоставляет два URI: «/» и «/ws», первый используется для отображения веб-страницы, а второй — для обмена данными по протоколу WebSocket. Веб-страница клиента хранится в файловой системе и отображается сервером по URI «/».

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

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