Найти в Дзене
K12 :: О ESP32 и не только

Arduino ESP32 шаг за шагом. Телеметрия через WiFi и MQTT для чайников. Часть 5

Часть 1. Знакомство с ESP32
Часть 2. Подключение к WiFi
Часть 3. Получение даты и времени
Часть 4. MQTT-клиент
Полная статья на сайте. Ещё одна частая необходимость – отправлять HTTP GET-запросы на различные ресурсы “в этих ваших интерьнетах”. С помощью этих самых запросов можно: Если вы не сталкивались с этим, то рекомендую вам предварительно ознакомиться с другой статьей: HTTP запросы на ESP8266 и ESP32 и другими статьями. Давайте посмотрим, как это можно реализовать. Как и в предыдущих случаях, сделать это можно как с помощью библиотек Arduino ESP32 – WiFiClient или HTTPClient, так и с помощью более низкоуровневой библиотеки ESP HTTP Client. В качестве примеров я буду использовать два варианта: Многие современные сайты и сервисы, которые допускают обмена данными через REST API, допускают только шифрованные TLS-подключения. Например telegram api вернет ошибку, если вы попытаетесь отправить запрос через открытое HTTP-подключению. Поэтому волей-неволей нам придется использовать HTTPS п
Оглавление

Часть 1. Знакомство с ESP32
Часть 2. Подключение к WiFi
Часть 3. Получение даты и времени
Часть 4. MQTT-клиент
Полная статья на сайте.

5. Создаем и отправляем HTTP(S)-запросы

Ещё одна частая необходимость – отправлять HTTP GET-запросы на различные ресурсы “в этих ваших интерьнетах”. С помощью этих самых запросов можно:

Если вы не сталкивались с этим, то рекомендую вам предварительно ознакомиться с другой статьей: HTTP запросы на ESP8266 и ESP32 и другими статьями.

Давайте посмотрим, как это можно реализовать. Как и в предыдущих случаях, сделать это можно как с помощью библиотек Arduino ESP32WiFiClient или HTTPClient, так и с помощью более низкоуровневой библиотеки ESP HTTP Client.

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

Многие современные сайты и сервисы, которые допускают обмена данными через REST API, допускают только шифрованные TLS-подключения. Например telegram api вернет ошибку, если вы попытаетесь отправить запрос через открытое HTTP-подключению. Поэтому волей-неволей нам придется использовать HTTPS протокол. Подробнее о защищенных соединениях вы можете прочитать тут: HTTPS, SSL/TLS соединения на Arduino или в разделе 4.1.4.1. Библиотека NetworkClientSecure данной статьи.

5.1. Отправка запросов с помощью Arduino WiFiClient

Во первых, можно вообще не использовать никаких дополнительных библиотек, а воспользоваться средствами уже подключенной к проекту библиотеки WiFi, а точнее – классом WiFiClient. Но этот класс представляет нам только функции транспортного уровня, то есть может только принимать и отправлять какие-то обезличенные данные по каналу связи между вашим устройством и неким сервером. Всё остальное – формирование заголовков и тела запроса, анализ ответа сервера – всё это придется делать что называется “ручками”. Этот способ подходит для простых задач, когда не требуется дополнительная обработка заголовков или тела ответа.

Примерный порядок работы:

  • Устанавливается соединение с сервером с помощью метода connect()
  • Формируются вручную и построчно отправляются заголовки HTTP GET-запроса
  • Читается и при необходимости анализируется ответ сервера

Обращение к web-серверу напишем в виде отдельной функции.

5.1.1 Динамическое и статическое создание экземпляра WiFiClient

Для работы c HTTP-запросами нам понадобится “отдельный” экземпляр класса WiFiClient, причем это может быть как “простой” WiFiClient, так и его потомок – “защищенный” WiFiClientSecure. Не допускается использовать тот же самый экземпляр, что мы использовали, например для PubSubClient. Поэтому нам нужно создать ещё одну переменную. Но сделать это можно двумя способами: статическим и динамическим.

Глобальная статическая переменная

Когда я рассказывал про PubSubClient, я просто объявил глобальную переменную wifi_client – она находится вне какой-либо функции вашего скетча и доступна во всех частях кода. Можно и сейчас сделать точно также (я привел варианты как для HTTP, так и для HTTPS-запросов – выберите один вариант):

-2

В этом случае компилятор поместит этот объект global_http(s)_wifi_client в специальную секцию памяти BSS, а инициализация экземпляра класса (то есть вызов конструктора класса) будет происходить даже до того, как скетч будет запущен на выполнение. Переменная будет находиться там постоянно, все время пока работает ваше приложение. Даже если вы отправляете HTTP-запросы один раз в пять минут, память под global_http(s)_wifi_client будет занята всегда.

Тоже самое происходит, если вы помечаете переменную как static.

Для MQTT-клиента это было совершенно правильно, так как PubSubClient постоянно поддерживает соединение постоянно. Для HTTP-запросов это может оказаться лишним и приведет к перерасходу оперативной памяти.

Глобальная локальная переменная

Допустим, мы хотим написать некую функцию, которая будет выполнять некий HTTP-запрос время от времени. В этом случае разумным будет поместить объявление переменной внутри этой функции, например так:

-3

Локальные переменные компилятор помещает в стек задачи, в которой выполняется вызываемая функция. Экземпляр класса будет создан и инициализирован в момент входа в данную функцию, а после выхода из неё – удален безвозвратно (только если вы не пометили её как static).  Вызывали функцию десять раз – десять раз объект будет создан и удален.

Это приемлемое поведение для таких объектов в большинстве случаев. Но есть одно небольшое “но” – стек задачи “не резиновый”. Потому что у каждой задачи ESP32 (а скетч – это тоже задача, как мы помним) – свой личный стек. Если попытаться запихнуть в стек одновременно много “тяжелых” объектов, а памяти под задачу при её создании выделено слишком мало, то можно нарваться на переполнение стека, и как следствие – аварийную перезагрузку. Конечно, такое поведение маловероятно, но не исключено полностью.

Динамическая переменная

Есть альтернативный вариант – выделить под объект память в общей куче heap. Сделать это можно так:

-4

В этом случае переменная-объект размещается в общей для всех задач куче с помощью new, и “живет” до тех пор, пока не будет принудительно удалена с помощью delete. При использовании этого метода мы почти не затрагиваем стек задачи, но должны всегда сами тщательно следить за уничтожением объектов.

Никогда не забывайте удалять объекты, под которые вы выделяли память из кучи! Иначе это приведет к так называемой “утечке памяти”, и при каждой итерации свободная память будет уменьшаться, уменьшаться и уменьшаться, что приведет к печальным последствиям.

Ещё одно отличие динамических переменных – для этого варианта при к доступе к полям структур или методам класса необходимо указывать -> вместо точки:

client->connect(hostName, httpPort);

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

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

5.1.2 Отправка GET-запроса с использованием WiFiClient

Вариант 1 без шифрования

Отправка данных в открытом виде, без шифрования:

-5

Вариант 2 без шифрования

Точно тоже самое, но записано другим способом:

-6

Мне этот вариант нравится больше.

Вариант  с TLS-шифрованием

Добавим шифрование. Для этого мы должны изменить следующее:

  • Заменить класс WiFiClient на WiFiClientSecure
  • Заменить порт на сервере с 80 на 443
  • Добавить корневой сертификат сервера setCACert(cert_ISRG_Root_x1) (это не обязательно должен быть cert_ISRG_Root_x1) или отключить проверку сертификата через setInsecure()
-7

5.1.3 Отправка POST-запроса с использованием WiFiClient

Telegram API принимает только защищенные запросы, поэтому простой HTTP-вариант отметаем сразу. Кроме того, лучше воспользоваться POST-запросом, так как это дает больше возможностей. Сообщение и сопроводительные данные “упаковываются” в JSON-объект и прикрепляются к запросу. Подробнее обо всем этом здесь: Отправка сообщений в Telegram на ESP8266 (и ESP32) с использованием фреймворка Arduino

Объявляем необходимые константы:

const char* tgToken = "токен вашего бота"; // Укажите здесь токен вашего бота

const char* chatId = "идентификатор чата"; // Укажите здесь номер чата получателя

Далее пишем такой код, он не сильно отличается от предыдущего варианта:

-8

5.2. Библиотека Arduino HTTPClient

Для работы с HTTP(S)-запросами в фреймворке Arduino ESP32 имеется специальная библиотека HTTPClient. Она позволяет выполнять GET, PUT, POST и другие типы запросов без необходимости “вручную” отправлять заголовки и тело запроса. В ней уже реализованы все необходимые механизмы для работы с HTTP-запросами и вам не придется с ними разбираться.

Но размер двоичного кода, генерируемого компилятором, будет немного больше, чем в предыдущем случае – но это очень часто не важно. А вот удобство работы решает всё.

Давайте попробуем реализовать то же самое, но с перламутровыми пуговицами этой библиотекой.

5.2.1 Отправка GET-запроса с использованием HTTPClient

HTTPClient по умолчанию использует “внутренний” экземпляр WiFiClient, поэтому объявлять его не нужно (хотя и можно это сделать, если вам это необходимо для каких-то целей).

-9

5.2.2 Отправка POST-запроса с использованием HTTPClient

Теперь давайте попробуем отправить сообщение в telegram с помощью POST-запроса:

-10

Проверим работу:

-11

5.3. Библиотека ESP-IDF ESP HTTP Client

Если вам нужно больше возможностей или более низкоуровневый контроль над процессом, то для ESP-IDF существует компонент esp_http_client, который вполне можно использовать и из экосистемы Arduino. Подробнее об этом компоненте я писал ранее в других статьях: HTTP запросы на ESP8266 и ESP32 и HTTPS, SSL/TLS соединения на ESP32 и ESP-IDF.

При работе с ESP HTTP Client из Arduino ESP32 имеется одна особенность: нельзя отключить проверку сертификата сервера, так как мы не имеем доступа к menuconfig (кроме варианта с “arduino as component“, конечно), а по умолчанию эта опция заблокирована. Поэтому либо используем “опасные” подключения, либо прикрепляем к проекту корневой сертификат сервера и проверяем его “как положено”.

5.3.1 Отправка GET-запроса с использованием ESP HTTP Client

Прежде всего необходимо подключить библиотеку к проекту:

#include "esp_http_client.h"

Теперь можно писать код для работы с HTTP. Он более “громоздкий”, чем для HTTPClient, но по сути ничуть не более сложный.

-12

5.3.2 Отправка GET-запроса с использованием ESP HTTP Client

Telegram API принимает только защищенные запросы, поэтому простой HTTP-вариант отметаем сразу. Кроме того, лучше воспользоваться POST-запросом, так как это дает больше возможностей. Сообщение и сопроводительные данные “упаковываются” в JSON-объект и прикрепляются к запросу. Подробнее обо всем этом здесь: Отправка сообщений в Telegram на ESP32 с использованием фреймворка ESP-IDF

Прежде всего необходимо подключить библиотеку к проекту:

#include "esp_http_client.h"

Объявляем необходимые константы:

const char* tgToken = "токен вашего бота"; // Укажите здесь токен вашего бота

const char* chatId = "идентификатор чата"; // Укажите здесь номер чата получателя

Функция отправки сообщения:

-13

Проверяем работу (токен я не указывал и API послал меня лесом, разумеется):

-14

Продолжение следует...