Табло погоды над моим столом началось с облома: плата уже висела в WiFi (мы сделали это в прошлом уроке), но данных у неё не было никаких. Умения «быть в сети» мало — нужен способ спросить у интернета: какая сейчас температура за окном? Ответ носится в воздухе в виде HTTP-запросов — тех самых, которыми браузер ходит по сайтам. У меня ушло полчаса, чтобы заставить ESP32 задавать такие вопросы и получать ответы, и ещё два часа, чтобы понять, почему сначала ничего не работало.
В этом уроке шлём настоящий GET-запрос библиотекой HTTPClient, разбираем коды ответов — от честного 200 до загадочного -1, — вытаскиваем число из текстового ответа сервера и печатаем всё это в Serial. Заодно объясню, почему аналогичный фокус на Arduino Uno не выезжает: там банально не хватает памяти даже на здоровенный ответ, а про HTTPS — и подавно.
Как плата разговаривает с сервером
HTTP — тупо текстовый протокол, и это отлично: его можно читать глазами. Плата шлёт серверу строку вида GET /v1/weather HTTP/1.1 плюс пару служебных заголовков. Сервер отвечает первой строкой со статусом (HTTP/1.1 200 OK), своими заголовками и телом — данными, ради которых всё затевалось. Для железных проектов тело чаще всего выглядит как JSON: {"temp":21.4,"hum":58}.
GET означает «дай мне»: плата только читает. Есть ещё POST — «прими от меня», им пользуются, когда нужно отправить показания датчика на сервер. В этой паре GET — ваш первый инструмент: погода, курс, показания чужой метеостанции, статус принтера — всё достаётся именно им.
Кстати, сервер видит о плате больше, чем кажется: в заголовке User-Agent клиент представляется, и плату можно узнать по строке ESP32HTTPClient. Некоторые сервисы дают таким клиентам облегчённые ответы, другие наоборот прикрывают доступ — если API молчит, сначала загляните в его требования к заголовкам, а уже потом вините свой код.
Код ответа — три цифры, по которым плата понимает, что случилось. Запомните пять штук, они покрывают 95% ситуаций:
| Код | Что значит | Что делать плате |
|---|---|---|
| 200 | OK, ответ в теле | Читать getString() |
| 301, 302 | Редирект: ресурс переехал, часто на https | Сразу просить новый адрес |
| 404 | Нет такой страницы | Проверить адрес и параметры |
| 401, 403 | Нужен ключ API или доступ закрыт | Добавить ключ в запрос |
| -1 | До сервера не добрались вообще | Проверить WiFi и адрес |
HTTPClient: GET-запрос в шести строках
#include <WiFi.h>#include <HTTPClient.h> const char* ssid = "MoyaSetka";const char* password = "superParol";String url = "http://api.example.com/v1/weather"; void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { // сперва — сеть! delay(500); Serial.print("."); } Serial.println(); Serial.println("WiFi есть, шлю GET-запрос");} void loop() { HTTPClient http; http.begin(url); // адрес сервера int httpCode = http.GET(); // сам запрос: вернёт код ответа if (httpCode == 200) { String body = http.getString(); // тело ответа Serial.println("Код 200 OK, ответ сервера:"); Serial.println(body); int p = body.indexOf("temp"); // быстрый поиск значения if (p >= 0) { String num = body.substring(p + 6, p + 10); Serial.print("Температура примерно: "); Serial.println(num); } } else if (httpCode == 404) { Serial.println("404: проверьте адрес запроса"); } else { Serial.print("Запрос не удался, код: "); Serial.println(httpCode); // -1 — не добрались до сервера } http.end(); // освободили соединение delay(10000); // не чаще раза в 10 секунд}Разбор по строкам. http.begin(url) говорит клиенту, куда ходить. http.GET() выполняет запрос и возвращает int — тот самый код ответа; никаких true/false, только цифры. http.getString() забирает тело ответа как String. http.end() освобождает соединение — без него через десятки запросов клиент начнёт течь и сыпать ошибками.
Блок с indexOf и substring — мой любимый способ «на коленке»: ищем слово temp в ответе и откусываем четыре символа после него. Для одного-двух значений этого хватает, но помните условие: если структура ответа сложнее плоского словарика — берите ArduinoJson, у нас под него есть отдельный раздел. Грязный парсинг на проде рано или поздно кусается.
Пара слов о времени. http.GET() — вызов блокирующий: пока сервер соображает, ваш loop() стоит на месте, поэтому в больших скетчах запросы шлют по расписанию из millis(), а не в каждый проход цикла. Если сервер завис, на реальной плате выручает http.setConnectTimeout(5000) — через пять секунд GET вернёт -1, и программа хотя бы не зависнет навсегда. А если сервису нужен ключ API в заголовке, после begin его добавляют вызовом http.addHeader(): заголовки — просто текстовые строчки, которые клиент приписывает к запросу.
И мнение, к которому я пришёл на своих проектах: данным, которые плате нужны каждую минуту, место в постоянном соединении (MQTT — у нас есть раздел и про него), а HTTP GET — инструмент для опроса раз в минуту или реже. И ещё условие по памяти: если бесплатное API отдаёт ответы больше пары килобайт, на Uno такой запрос уже не влезает безопасно — String в 2 КБ RAM фрагментируется, плата начинает виснуть через сутки работы. На ESP32 запас в сотни раз больше, и это одна из главных причин, почему сетевые проекты я делаю только на ней.
Почему на Uno так нельзя: HTTPS и память
Серьёзные API сегодня почти все живут за HTTPS — это тот же HTTP, но завёрнутый в шифрование TLS. Для рукопожатия TLS нужны буферы на десятки килобайт: сертификаты, ключи сессии, криптографические промежуточные данные — на практике от 30 до 50 КБ RAM. У Arduino Uno всего 2 КБ. Отсюда приговор: HTTPS-запрос на Uno невозможен в принципе — ни библиотекой, ни хитростью. Люди выкручиваются внешними криптомодулями или своим прокси-сервером, который снимает шифрование, — но это уже совсем другая история и лишнее железо.
ESP32 с 520 КБ RAM шифрует TLS сама, без обвязки — поэтому, когда сервер требует https-адрес, плата справится (с сертификатами разберёмся отдельно). А вот HTTP-клиенту на Uno доступен только голый http, и условие простое: ответы до ~1 КБ и никаких редиректов на https, иначе сервер ответит 301, а Uno пойдёт за ним и утонет.
Что делать, если Uno у вас всё-таки в руках и в сеть очень хочется? Для локальной сети без шифрования существует Ethernet-шилд на чипе W5100: он даёт ATmega328P сетевой интерфейс, и простые http-запросы по кабелю уходят. Но условия жёсткие: ответ сервера — не больше пары сотен байт, никакого https, и следить за каждым байтом в String. Мой вывод после пары таких проектов: если данным нужен https или ответ длиннее килобайта — дешевле сразу поставить ESP32, чем бороться с памятью ATmega328P.
Запустите код ниже в симуляторе: сеть тут эмулируется, WiFi «подключится» за секунду-другую, а GET вернёт демо-ответ — как будто реальный сервер ответил кодом 200 и телом с температурой. На настоящей плате тот же код пойдёт по настоящему адресу.
Эксперименты: закрепите руками
- Считайте запросы: заведите счётчик
int n = 0;, увеличивайте его после каждого http.end() и печатайте вместе с кодом ответа. Через час с реальным API вы точно будете знать, сколько трафика нагенерировали, — счётчик и есть ваш личный биллинг. - Добавьте разбор влажности: найдите в теле ответа слово
humтем же indexOf и выведите значение рядом с температурой. Убедитесь, что два substring не конфликтуют, если слова стоят в разном порядке. - Обработайте редирект: в блоке else добавьте проверку
if (httpCode == 301)с печатью «Сервер переехал». Так вы встретите проблему до того, как она встретит вас в ночи. - Поставьте между запросами delay(2000) вместо 10000 и понаблюдайте за логом. Потом верните обратно — и запомните: бесплатные API считают запросы, а за флуд дают бан по ключу.
- HTTP — текстовый протокол: строка запроса, код ответа, тело; 200 — успех, -1 — до сервера не добрались
- HTTPClient: begin(url) → GET() → getString() → end(); WiFi должен быть подключён ДО begin
- indexOf + substring — быстрый разбор плоских ответов; для честного JSON — ArduinoJson
- HTTPS на Uno невозможен: TLS требует 30–50 КБ RAM, у Uno 2 КБ; ESP32 шифрует сама
- Опрашивайте API не чаще, чем разрешают условия сервиса, и всегда закрывайте запрос http.end()
- 1. Что означает код ответа 200?
- 2. http.GET() вернул -1. Что проверить в первую очередь?
- 3. Почему с Arduino Uno нельзя делать HTTPS-запросы?
- 4. Что вернёт http.getString()?
- 5. Сервер ответил 404. Где искать проблему?
- 6. Какая строка освобождает соединение после запроса?
Теперь плата умеет спрашивать. В следующем уроке поменяем роли: браузер будет спрашивать у платы. Соберём мини-веб-сервер на ESP32, который слушает 80 порт, читает запрос-строку и отвечает HTML-страницей с кнопкой — вы будете включать светодиод со смартфона, лежа на диване. Если есть желание повторить это на железе, заранее проверьте, что телефон и плата смогут сидеть в одной WiFi-сети: к чему приводит гостевая сеть роутера, расскажу на примере собственного провала.
Частые вопросы по теме урока
- Что означает код ответа 200?
- 200 OK — стандартный знак удачного запроса: тело ответа забирают через http.getString(). Запрос прошёл, данные лежат в теле ответа
- http.GET() вернул -1. Что проверить в первую очередь?
- -1 значит, что до сервера дело не дошло: типичные причины — WiFi ещё не подключился, опечатка в адресе, сеть без интернета. Подключён ли WiFi (WL_CONNECTED) и верен ли адрес
- Почему с Arduino Uno нельзя делать HTTPS-запросы?
- Рукопожатие TLS требует буферов на 30–50 КБ. У Uno 2 КБ RAM — шифрование физически не помещается; ESP32 с 520 КБ справляется. Мало памяти: TLS требует десятки килобайт, у Uno 2 КБ RAM
- Что вернёт http.getString()?
- getString() возвращает тело ответа как String. Код ответа — это возвращаемое значение http.GET(). Тело ответа в виде строки
- Сервер ответил 404. Где искать проблему?
- 404 — «нет такой страницы»: сервер доступен, но путь неверный. Проверьте URL, параметры и ключ в адресе. В адресе или параметрах запроса
- Какая строка освобождает соединение после запроса?
- После серии запросов без http.end() клиент течёт и сыпать ошибками. end() освобождает ресурсы соединения. http.end();