Тепличный датчик с экраном созрел для финала: температура внутри есть, а снаружи — за десять километров — нет. Первый прогон в облако я сделал дерзко: раз в пять секунд шлю показания, чего он мне сделает? Через час сервис начал отвечать нулём вместо номера записи, график встал, а я полчаса перезапускал роутер, прежде чем полез в документацию: у бесплатного тарифа ThingSpeak жёсткий пол — не чаще одного показания в 15 секунд, нарушителям молча возвращают 0. Второй сюрприз поджидал после скачка питания: DHT выдавал nan, скетч честно упаковывал nan в запрос и отправлял — график падал в ноль на сутки, пока я не научился фильтровать мёртвые чтения до отправки.
В этом уроке собираем весь путь целиком: чтение DHT с проверкой, millis-планировщик отправки, WiFi с таймаутом, запрос в облако и разбор ответа. По ходу — два правила частот: датчик опрашивают не чаще раза в 2 секунды (DHT22 физически не успевает), в облако шлют не чаще лимита сервиса.
Шесть шагов от датчика до облачного графика
Шаг первый — датчик. DHT dht(2, DHT22) и dht.begin() в setup(): подключение из урока про библиотеку DHT, между чтениями — не меньше двух секунд, у DHT22 период обновления полгерца. Шаг второй — фильтр: isnan(t) || isnan(h) означает, что датчик не ответил, и такое показание не едет ни на экран, ни в облако — иначе nan разом убьёт строку запроса и график. Мёртвое чтение — не повод для паники, а повод подождать: следующий опрос через две секунды почти всегда проходит.
Шаг третий — планировщик. Millis-гейт отправки — раз в 60 секунд на живой плате: между отправками loop жив и занимается всем остальным, от экрана до кнопки. Шаг четвёртый — сеть с таймаутом: WiFi.begin() и while с проверкой статуса, но с потолком в 15 секунд — без него плата в бочке без роутера зависнет в вечном ожидании, как я описывал в уроке про первое подключение. Нет связи — работаем офлайн: показания живут на экране, счётчик пропусков растёт.
Шаг пятый — запрос. На живой плате это POST с телом api_key=...&field1=21.4&field2=58 на адрес /update: тело и заголовки собраны в уроке про телеметрию, здесь — только связка. Шаг шестой — контроль ответа: код 200 и тело, в котором ThingSpeak возвращает номер созданной записи — 1, 2, 3... Ноль в теле при коде 200 — тихий отказ из-за частоты. Две строки счётчиков ok и fail в Serial — дешёвая система мониторинга, которая через неделю жизни датчика скажет о связи больше, чем любой график.
#include <WiFi.h>#include <HTTPClient.h>#include <DHT.h> DHT dht(2, DHT22);const char* ssid = "DomaSetka";const char* password = "superParol";const char* apiKey = "XXXXXXXXXXXXXXXX";const char* url = "http://api.thingspeak.com/update"; unsigned long tSend = 0;int okCount = 0;int failCount = 0; void setup() { Serial.begin(115200); dht.begin(); WiFi.begin(ssid, password); unsigned long t0 = millis(); while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) { delay(250); Serial.print("."); } Serial.println(WiFi.status() == WL_CONNECTED ? "wifi est" : "wifi net: rabotaem oflain");} void loop() { if (millis() - tSend < 60000) return; // в облако раз в минуту tSend = millis(); float t = dht.readTemperature(); float h = dht.readHumidity(); if (isnan(t) || isnan(h)) { // датчик не ответил: ждём следующего failCount++; Serial.println("dht: net otveta, propusk"); return; } String body = String("api_key=") + apiKey + "&field1=" + String(t, 1) + "&field2=" + String(h, 0); HTTPClient http; http.begin(url); http.addHeader("Content-Type", "application/x-www-form-urlencoded"); int code = http.POST(body); String resp = (code > 0) ? http.getString() : ""; http.end(); Serial.print("code="); Serial.print(code); Serial.print(" resp="); Serial.println(resp); // номер записи или 0 if (code == 200 && resp != "0") { okCount++; } else { failCount++; }}Разбор по шагам. Таймаут WiFi — 15 секунд: в худшем случае плата ждёт четверть минуты и уходит офлайн, а не навсегда. Показания собираются в тело функцией String(t, 1) — один знак после запятой, без хвостов вида 21.400001, которыми грешит сырая конвертация float. Ответ сервера дочитывается getString() только при живом соединении: код меньше единицы означает, что до сервера дело не дошло — сеть, адрес или таймаут. И строгое правило HTTPClient — http.end() в любом исходе: соединение не должно жить дольше одного запроса.
Частоту отправки я держу в минуту и уменьшаю только по делу: три показания в минуту заливать в облако — значит кормить график шумом и упираться в лимит. Про millis-гейт вместо delay напомню уроком про millis: return в начале loop() — тот же гейт, только честный. А если связь капризная — дачный роутер, дальняя комната, — сетевую часть стоит собрать с автоматом восстановления из урока про автореконнект: он встаёт в этот скетч без единой правки остального кода.
И пара слов о мониторинге, потому что счётчики ok и fail — не украшение, а главный экран датчика. Пары code=/resp= за неделю покажут о связи больше, чем любой график: ровные 200 и растущий номер записи — всё живёт; серия -1 после грозы — роутер перегрузился и сам не поднялся; 200 с нулями — вы упёрлись в лимит, скетч уехал вперёд по частоте. Я раз в неделю захожу в Serial по кабелю именно ради этих двух строк — и дважды ловил умирающий блок питания раньше, чем он унёс весь датчик.
Прогоняем путь в симуляторе
| Куда шлют | Как отдаёт | Лимит бесплатного тарифа | Когда беру |
|---|---|---|---|
| ThingSpeak | POST на /update, поля field1..field8 | не чаще раза в 15 секунд | графики за вечер, MATLAB-аналитика |
| Народный мониторинг | показания + MAC платы, без библиотеки | свои правила частоты | погодные сети и RU-сообщество |
| Свой сервер | POST на свой URL, свой формат | без лимитов вообще | нужны своя база и форматы |
| MQTT-брокер | постоянное соединение, publish | зависит от брокера | показания чаще раза в минуту |
Развилка из таблицы важнее любого кода. HTTP-запрос раз в минуту — самый честный старт: не нужен брокер, отладка в Serial, а история хранится у сервиса. Когда показания пойдут чаще — скажем, раз в пять секунд на десять датчиков, — держать постоянное соединение станет дешевле, чем пачки заголовков на каждое измерение: это уже тема урока про MQTT на ESP32, и переход с HTTP на MQTT в моём тепличном датчике занял вечер только потому, что конвейер чтения был собран отдельно от сетевой части. Собирайте так же.
Куда расти после этого урока: добавьте в очередь ещё один датчик — земляная влажность на GPIO 4 или давление по I2C — и убедитесь, что конвейер не изменился ни строчкой, кроме тела запроса. Потом заведите на сервере второй field и посмотрите оба графика рядом. А когда появится своя база и свой API — та же схема, только адрес и формат тела ваши: шесть шагов полного пути не меняются от сервиса к сервису, меняется только конверт.
- 1. ThingSpeak вернул код 200, но в теле ноль вместо номера записи. Самая вероятная причина?
- 2. Как часто можно опрашивать DHT22?
- 3. dht.readTemperature() вернул nan. Что должен сделать скетч?
- 4. Зачем в теле запроса String(t, 1), а не сырая конвертация float?
- 5. Датчик шлёт показания раз в минуту. Что выбрать: HTTP-запрос или MQTT?
- Полный путь: DHT раз в 2 с → isnan-фильтр → millis-гейт отправки → WiFi с таймаутом 15 с → POST → разбор ответа
- Два правила частот: датчик не чаще раза в 2 с, облако не чаще лимита сервиса (ThingSpeak — 15 с)
- Ноль в теле при коде 200 — тихий отказ: сравнивайте тело, а не только код, и считайте ok/fail
- Мёртвое чтение не едет в облако: nan-показание пропускается с записью в счётчик отказов
- Метки времени ставит сервис — NTP нужен, только когда метки ложатся на вас
Раздел собрался в связку: сети и ядра из двух предыдущих уроков показали, зачем их было учить, — датчик в теплице теперь крутится месяцами и не требует к себе ни секунды внимания. Мой финальный чек для любого IoT-скетча: таймаут на WiFi, фильтр на датчик, лимит на отправку и счётчики на оба вида отказов. Четыре строки, которые отличают прибор от макетки.
И последнее: облако — не единственный адресат. Те же показания с той же очереди могут уезжать в домашний MQTT-брокер и дальше в Home Assistant, а когда доберётесь до батарейного питания — помните, что каждый лишний запрос стоит миллиамперы, и загляните в урок про бюджет глубокого сна: датчик, который спит между показаниями, живёт на аккумуляторе в разы дольше бодрствующего.
Частые вопросы по теме урока
- ThingSpeak вернул код 200, но в теле ноль вместо номера записи. Самая вероятная причина?
- Код 200 с нулём в теле — тихий отказ по частоте: запрос дошёл, запись не создана. Держите интервал не меньше 15 секунд и сравнивайте тело с нулём, а не только код. Записи идут чаще лимита: бесплатный тариф требует не меньше 15 секунд между ними
- Как часто можно опрашивать DHT22?
- DHT22 пересчитывает показания с частотой 0.5 Гц — опрос чаще раза в две секунды даёт старые значения и лишний износ линии. Мой режим: чтение раз в 2 секунды, отправка раз в минуту. Не чаще раза в 2 секунды: период обновления датчика полгерца
- dht.readTemperature() вернул nan. Что должен сделать скетч?
- Мёртвое чтение не едет ни на экран, ни в облако: nan разрывает строку запроса и топит график. Пропуск с счётчиком отказов — честнее нуля. isnan-проверка: пропустить показание и подождать следующего опроса
- Зачем в теле запроса String(t, 1), а не сырая конвертация float?
- Форматированная конвертация даёт 21.4 вместо длинного хвоста после запятой. Серверы такие хвосты терпят, но графики и логи от них замусориваются. Один знак после запятой без хвостов вида 21.400001
- Датчик шлёт показания раз в минуту. Что выбрать: HTTP-запрос или MQTT?
- Одно показание в минуту — это пара сотен байт заголовков: HTTP честнее и проще. MQTT окупается на частых показаниях и многих датчиках — та же развилка из урока про телеметрию. HTTP-запрос: для раз в минуту брокер не нужен, отладка в Serial