В одной части теплицы уже жарко, возле двери прохладнее, а телефон пока ничего об этом не знает. Дадим ему простой источник новостей: ESP32 с датчиком температуры и влажности DHT22. Поставим датчик у растений и получим сообщение, когда показания выйдут за выбранные пределы.
Начинаем с лабораторной сборки: подключение, полный скетч и первые опыты. Она сообщает об измеренных условиях. Место датчика, рабочие пороги и связь проверим перед установкой в теплице.
Собираем точку наблюдения
- Плата на классическом ESP32, например ESP32-DevKitC V4, с подходящим USB-кабелем.
- DHT22 или модуль на его основе; назначение контактов сверяют по маркировке и документации конкретного изделия.
- Резистор подтяжки DATA к питанию, если его нет на модуле. В схеме ниже — 10 кОм.
- Стабилизированный USB-источник для платы, провода и макетная плата для сухого стенда.
- Для установки — подходящий корпус электроники, кабельные вводы и защищённое размещение датчика воздуха.
ESP32 3V3 → DHT22 VCC
ESP32 GND → DHT22 GND
ESP32 GPIO4 → DHT22 DATA
DATA → резистор 10 кОм → ESP32 3V3
У отдельного датчика и готового модуля могут различаться порядок выводов и встроенная подтяжка. Обозначение GPIO4 не следует автоматически заменять надписью D4 на любой плате. Схема рассчитана на совместимый с питанием 3,3 В DHT22; если модуль требует иного питания, нужно согласовать и уровень сигнала.
Не переключайте питание датчика на 5 В как способ устранения ошибки, сохраняя подтяжку DATA к его питанию: так на вход ESP32 может попасть 5 В. Сначала проверьте контакты, распиновку, резистор и документацию. Для ESP32-DevKitC V4 производитель требует выбрать один способ питания — USB, 5V/GND либо 3V3/GND — и не соединять их одновременно.
Создаём бота, который будет писать вам
- Установите Arduino IDE и поддержку ESP32 по инструкции Espressif. Для этого примера зафиксированы Arduino-ESP32 3.3.12 и плата ESP32 Dev Module.
- Установите DHT sensor library 1.4.7, Adafruit Unified Sensor 1.1.15 и ArduinoJson 7.4.3.
- Создайте бота через официальный BotFather и сохраните токен. Откройте личный чат со своим ботом и отправьте /start.
- Получите chat.id из обновления своего бота методом getUpdates. Для группы добавьте туда бота, создайте доступное ему сообщение и используйте идентификатор соответствующего чата. У бота должно быть право отправлять сообщения.
- Заполните SSID, PASSWORD, BOT_TOKEN и CHAT_ID в скетче. Не публикуйте токен, файл прошивки с секретами и ссылки с токеном. При утечке отзовите токен через BotFather.
Для HTTPS пример использует встроенный набор доверенных сертификатов Arduino-ESP32 и синхронизацию времени. Если время ещё не получено, сообщение остаётся в очереди. При ошибках TLS не отключайте проверку сертификата: проверьте часы, доступность сети и актуальность платформы.
Какие новости присылает теплица
Пусть каждое важное изменение приходит отдельной новостью. Стало холодно — одно событие; затем выросла влажность — другое. Даже если температурное предупреждение уже было, влажность останется заметной. При ошибке датчика придёт сообщение о том, что условия сейчас неизвестны.
Для опыта используются пороги: холод ниже 5 °C с возвратом при 6 °C, жара выше 35 °C с возвратом при 34 °C, влажность выше 90% с возвратом при 85%. Небольшой зазор между входом и выходом убирает поток одинаковых предупреждений возле границы. После знакомства с алгоритмом замените числа по регламенту своей культуры.
Если сеть прервалась, последние 32 изменения ждут в оперативной памяти. Когда места больше нет, старые события уступают место новым, а в отчёт добавляется число пропусков. Для хранения истории после перезапуска следующим шагом станет запись в энергонезависимую память или на сервер.
Отправку удобно проверить по ответу Telegram: HTTP 200 и ok=true означают, что API принял сообщение. После этого событие покидает очередь. При неопределённом сетевом результате возможна повторная отправка. Время измерения в секундах после запуска поможет отличить задержавшуюся новость от свежей.
Скетч: от показания к сообщению
#include <Arduino.h>
#include <WiFi.h>
#include <NetworkClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <time.h>
#include <math.h>
constexpr char SSID[] = "CHANGE_ME", PASSWORD[] = "CHANGE_ME";
constexpr char BOT_TOKEN[] = "CHANGE_ME", CHAT_ID[] = "CHANGE_ME";
constexpr int DHT_PIN = 4;
constexpr uint32_t SAMPLE_MS = 10000, RETRY_MS = 60000, REPORT_MS = 3600000;
constexpr uint8_t COLD = 1, HOT = 2, WET = 4, SENSOR_BAD = 8;
constexpr size_t CAPACITY = 32;
struct Event { uint32_t uptime; uint8_t state; float t, h; };
Event events[CAPACITY];
size_t head = 0, count = 0;
uint32_t dropped = 0, sampledAt = 0, attemptedAt = 0, reportedAt = 0, reconnectAt = 0;
uint32_t retryMs = RETRY_MS;
bool sampled = false, attempted = false;
uint8_t state = SENSOR_BAD;
float temperature = NAN, humidity = NAN;
DHT dht(DHT_PIN, DHT22);
uint8_t classify(float t, float h, uint8_t previous) {
if (!isfinite(t) || !isfinite(h) || h < 0 || h > 100) return SENSOR_BAD;
uint8_t next = 0;
// Учебные пороги; подбираются по регламенту хозяйства.
if (t < ((previous & COLD) ? 6.0f : 5.0f)) next |= COLD;
if (t > ((previous & HOT) ? 34.0f : 35.0f)) next |= HOT;
if (h > ((previous & WET) ? 85.0f : 90.0f)) next |= WET;
return next;
}
void enqueue(Event e) {
if (count == CAPACITY) { head = (head + 1) % CAPACITY; --count; ++dropped; }
events[(head + count) % CAPACITY] = e;
++count;
}
String describe(Event e) {
String s = "Теплица. Измерение через " + String(e.uptime / 1000) + " с после запуска.\n";
if (e.state & SENSOR_BAD) return s + "Ошибка датчика: условия неизвестны.";
s += "T=" + String(e.t,1) + " °C; RH=" + String(e.h,1) + " %.\n";
if (!e.state) s += "Показатели в заданных пределах.";
if (e.state & COLD) s += "Ниже порога температуры. ";
if (e.state & HOT) s += "Выше порога температуры. ";
if (e.state & WET) s += "Выше порога влажности.";
return s;
}
bool sendTelegram(const String& text) {
if (WiFi.status() != WL_CONNECTED || time(nullptr) < 1735689600) return false;
if (String(BOT_TOKEN) == "CHANGE_ME" || String(CHAT_ID) == "CHANGE_ME") return false;
NetworkClientSecure client;
client.useBuiltinCACertBundle();
client.setHandshakeTimeout(5);
HTTPClient http;
http.setConnectTimeout(3000);
http.setTimeout(5000);
const String url = String("https://api.telegram.org/bot") + BOT_TOKEN + "/sendMessage";
if (!http.begin(client, url)) return false;
JsonDocument request;
request["chat_id"] = CHAT_ID;
request["text"] = text;
String payload;
serializeJson(request, payload);
http.addHeader("Content-Type", "application/json");
const int code = http.POST(payload);
String response = http.getString();
http.end();
JsonDocument result;
const bool parsed = !deserializeJson(result, response);
if (code == 429 && parsed) {
uint32_t seconds = result["parameters"]["retry_after"] | 60U;
if (seconds < 60) seconds = 60;
if (seconds > 86400) seconds = 86400;
retryMs = seconds * 1000U;
} else retryMs = RETRY_MS;
const bool ok = code == 200 && parsed && result["ok"].is<bool>() && result["ok"].as<bool>();
Serial.printf("Telegram HTTP=%d accepted=%d\n",code,ok); // Не печатаем токен и тело ответа.
return ok;
}
void setup() {
Serial.begin(115200);
dht.begin();
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(SSID, PASSWORD);
configTime(0, 0, "pool.ntp.org", "time.cloudflare.com");
delay(2000); // Начальная пауза для датчика; сеть не ожидаем.
// Ожидания Wi-Fi нет: измерения продолжаются и без сети.
}
void loop() {
uint32_t now = millis();
if (WiFi.status() != WL_CONNECTED && uint32_t(now-reconnectAt) >= 30000) {
reconnectAt = now;
WiFi.reconnect();
}
if (!sampled || uint32_t(now-sampledAt) >= SAMPLE_MS) {
sampledAt = now;
temperature = dht.readTemperature(); humidity = dht.readHumidity();
uint8_t next = classify(temperature, humidity, state);
if (!sampled || next != state) enqueue({now,next,temperature,humidity});
state = next; sampled = true;
}
if (attempted && uint32_t(now-attemptedAt) < retryMs) return;
if (WiFi.status() != WL_CONNECTED) return;
bool hourly = uint32_t(now-reportedAt) >= REPORT_MS;
if (!count && !dropped && !hourly) return;
attempted = true; attemptedAt = now;
if (dropped) {
String s = "Очередь переполнилась: пропущено переходов " + String(dropped) + ".\n";
s += describe({now,state,temperature,humidity});
if (sendTelegram(s)) dropped = 0;
} else if (count) {
if (sendTelegram(describe(events[head]))) { head=(head+1)%CAPACITY; --count; }
} else if (sendTelegram("Периодический отчёт.\n" + describe({now,state,temperature,humidity}))) {
reportedAt = now;
}
}
Опрос запланирован раз в десять секунд; ожидание сетевого ответа может его задержать. Между попытками отправки проходит хотя бы минута, а при ограничении Telegram учитывается retry_after. Когда событий нет, раз в час приходит обычный отчёт — удобный повод заглянуть в микроклимат.
Проведите опыт с отключённым Wi-Fi: измерения продолжаются, события ждут возвращения связи. Для контроля полного исчезновения станции нужен отдельный получатель, который замечает пропавшие отчёты. Там, где промедление критично, добавляют местную сигнализацию.
Первая новость из теплицы
- На сухом стенде проверьте показания и сравните их с контрольным прибором. Не принимайте цифру на экране за доказательство точности.
- Задайте тестовые пороги близко к текущим показаниям, затем проверьте вход в каждое состояние и возврат в норму. Верните рабочие настройки после проверки.
- Отключите Wi-Fi, вызовите изменение состояния, восстановите сеть. Событие должно сохраниться в очереди до подтверждения API.
- Проверьте неверный токен, недоступный чат и ошибку датчика. В журнале не должно появляться подтверждение accepted=1 при отказе.
- Проверьте переполнение очереди и перезапуск. Убедитесь, что выбранные ограничения хранения приемлемы для вашей задачи.
- Разместите датчик вне корпуса нагревающейся платы, в защищённой от прямого солнца и воды точке, соответствующей зоне наблюдения. Обеспечьте контакт с воздухом и проверьте влияние корпуса и места установки.
После опытов перенесите сборку с макетной платы на надёжные соединения. У корпуса продумайте вводы проводов, отвод конденсата и доступ для обслуживания. Если хочется убрать USB-провод, измерьте ток всей сборки — с этих данных начинается выбор аккумулятора.
Куда двигаться после первого сообщения
Подключение датчика: руководство Adafruit DHT. Питание платы: ESP32-DevKitC V4. Получение обновлений и отправка сообщений: официальный Telegram Bot API. Когда сообщения приходят стабильно, сравните разные места установки датчика: это поможет выбрать точку, которая отвечает на ваш вопрос о теплице.
Автономное питание датчика пригодится для места, куда неудобно тянуть USB-провод.
Управление вентиляцией теплицы поможет перейти от наблюдения к движению форточки.





