Финальная сборка у меня провалилась, и это лучшее, что могло случиться. По отдельности всё работало: DS3231 из первых уроков показывал время, реле из проекта термостата щёлкало, датчик почвы честно сыпал числа в монитор. Но в одном скетче система встала: я украсил полив delay(30000) — и свет на эти полминуты замер, потому что loop стоял; полив без ограничений включался каждые полминуты, пока «время полива» оставалось истинным; а одно сравнение времени строками перепутало 9 утра с 10 вечера. Рассада, которой досталась вся вода за один заход, дала мне больше обратной связи, чем любой компилятор.
Проект пришёл в порядок, когда я перестал писать «когда включать» и начал писать диспетчер: что сейчас должно быть включено, исходя из времени, почвы и воздуха. Входы — часы, датчик почвы, DHT. Выходы — реле лампы и реле насоса. Между ними — правила с запретами, и ни одного delay: всё на миллис-планировщике, который мы строили в уроке про планировщик задач.
Этот урок — свод всего раздела. Расписание поведёт DS3231 из первого урока про часы, завод и lostPower возьмём из установки RTClib, а аварийный костыль на millis из предыдущего урока останется в запасе на случай смерти модуля. В конце — прогон проекта в симуляторе с честно сжатыми сутками.
Правила: расписание с запретами
Каждое правило в диспетчере — это условие включения плюс страховки. Свет: часы DS3231 показывают 06:00–20:00 — реле замкнуто, иначе разомкнуто; расписание чисто временное, датчиков не нужно. Полив сложнее, потому что вода без контроля и вредна, и опасна: окно времени (07:00–07:10 и 19:00–19:10), сухая почва (analogRead выше 600 — сухо, у типовых резистивных датчиков значение падает по мере влаги), тёплый воздух (ниже +10 полив отменяется — холодный душ по корням в десять вечера чесночный урожай не растит, а губит), не дольше 30 секунд и один полив на окно. Пять условий вместо одного «в 7 утра включить насос» — и каждое спасало мой урожай минимум один раз.
Страховки стоят на двух местах. Флаг polivDone гарантирует один полив на окно: без него насос включает-выключается весь десятиминутный интервал, как реле в уроке про клацанье и гистерезис. Ограничение в 30 секунд — защита от лжи датчика: если почвенный датчик отгнил и всегда показывает сухо, ограничитель превращает катастрофу в короткий полив. У границы сухости работает тот же приём, что и в термостате: два порога — включаю выше 600, принудительно останавливаю ниже 300, в мёртвой зоне ничего не меняю.
Почва опрашивается каждый цикл — analogRead стоит микросекунды, а решения меняются редко: выходы получают один digitalWrite на цикл, и ни одно реле не дёргается само по себе. Журнал раз в десять секунд парой имя=значение — как учил разбор DateTime, числа печатаются отдельно, чтобы Serial-плантер строил графики.
| Узел | Пин | Правило срабатывания | Страховка |
|---|---|---|---|
| Фитолампа | D12 через реле | 06:00–20:00 по часам DS3231 | не включится после сброса часов без завода |
| Насос | D7 через реле | окно 07:00–07:10 и 19:00–19:10, почва сухая, воздух теплее +10 | 30 с максимум, один полив на окно, стоп при мокро |
| Датчик почвы | A0 | сухо выше 600, мокро ниже 300 | два порога против качанья на границе |
| DS3231 | SDA A4, SCL A5 | даёт час и минуту для всех окон | lostPower() и батарейка CR2032 |
#include <Wire.h>#include <RTClib.h>#include <DHT.h> RTC_DS3231 rtc;DHT dht(2, DHT11); const int PIN_PUMP = 7; // модуль реле насосаconst int PIN_LAMP = 12; // модуль реле фитолампыconst int PIN_SOIL = A0; // датчик влажности почвы const int SOIL_DRY = 600; // выше - сухоconst int SOIL_WET = 300; // ниже - мокроconst int T_MIN = 10; // ниже не поливаемconst unsigned long PUMP_MAX_MS = 30000UL; unsigned long pumpUntil = 0;int pumpOn = 0;int polivDone = 0;unsigned long lastPrint = 0; void setup() { Serial.begin(9600); pinMode(PIN_PUMP, OUTPUT); pinMode(PIN_LAMP, OUTPUT); dht.begin(); if (rtc.begin() == false) { Serial.println("DS3231 ne otvechaet: bez nego raspisanie stoit"); while (1) { } } if (rtc.lostPower()) { Serial.println("Batareyka sadilas: zavodite vremya"); // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // один раз при заводе }} void loop() { DateTime now = rtc.now(); int h = now.hour(); int m = now.minute(); // Свет: чистое расписание по часам int lampOn = 0; if (h >= 6 && h < 20) lampOn = 1; digitalWrite(PIN_LAMP, lampOn); // Полив: окно времени И сухо И тепло И ещё не поливали int win = 0; if ((h == 7 && m < 10) || (h == 19 && m < 10)) win = 1; int soil = analogRead(PIN_SOIL); float t = dht.readTemperature(); if (win == 1 && polivDone == 0 && soil > SOIL_DRY && t > T_MIN && pumpOn == 0) { pumpOn = 1; pumpUntil = millis() + PUMP_MAX_MS; polivDone = 1; Serial.println("Nasos: poliv do 30 s"); } if (pumpOn == 1 && (millis() >= pumpUntil || soil < SOIL_WET)) { pumpOn = 0; Serial.println("Nasos: stop"); } digitalWrite(PIN_PUMP, pumpOn); if (win == 0) polivDone = 0; // Журнал раз в 10 секунд if (millis() - lastPrint >= 10000) { lastPrint = millis(); Serial.print("h="); Serial.print(h); Serial.print(" m="); Serial.print(m); Serial.print(" soil="); Serial.print(soil); Serial.print(" t="); Serial.print(t); Serial.print(" lamp="); Serial.print(lampOn); Serial.print(" pump="); Serial.println(pumpOn); }}Обратите внимание на разрыв флага: polivDone сбрасывается, когда окно закрывается, — так каждый следующий интервал получает свой полив, а внутри окна насос молчит после отработки. Диапазон — это не прихоть: без сброса флага полив случится один раз за весь срок жизни платы. Порог SOIL_WET в условии остановки — вторая страховка: если датчик вдруг ожил в мокром грунте, насос глушится немедленно, не дожидаясь 30 секунд.
Куда расти дальше. Пороги и границы окон сейчас — константы в шапке скетча; их место в энергонезависимых настройках, которые переживают отключение, — путь начинается в уроке про первые настройки в EEPROM. Журнал с метками времени просится на карту — схема записи была в уроке про логирование на SD. А когда теплиц станет две, добавится ESP32 с сетью: расписание уедет в веб-морду, а RTC останется страховкой на случай пропажи Wi-Fi.
Прогон в симуляторе
Движок не эмулирует ход RTC: rtc.now() возвращает снимок на момент запуска, и расписанию по часам в нём ловить нечего. Поэтому демо идёт на программных часах из предыдущего урока — заведённых на 06:00:05, — а настоящие границы с rtc.now().hour() остаются в скетче для платы выше. Логика правил идентична, меняется только источник «который час» — и это честная демонстрация того, как диспетчер разделяет время и решения.
Сутки в демо сжаты до одной минуты: свет горит первые 20 секунд каждой минуты, окно полива — с 8-й по 19-ю секунду, насос отрабатывает 3 секунды вместо 30. За минуту вы увидите весь суточный цикл: утренний полив, смену дня и ночи, вечерний полив. Ползунок влажности почвы крутите вверх — сухо, насос получит разрешение; вниз — получите отказ по влажности. Температуру DHT ниже +10 — увидите запрет по холоду.
- Диспетчер вместо «когда включать»: каждый цикл — входы, правила, один digitalWrite на выход; ноль delay в проекте
- Полив = окно времени И сухо (soil выше 600) И тепло (выше +10) И не дольше 30 с И один полив на окно — страховки важнее расписания
- Два порога почвы (600/300) — гистерезис: без него реле клацает на границе, как в уроке про гистерезис
- Насос и 220 В — только через модуль реле: пин Arduino держит максимум 40 мА, насос берёт сотни
- В симуляторе сутки сжаты до минуты: свет 20 с, полив 8–19 с — на плате те же правила читают час и минуту из rtc.now()
- 1. Почему для полива нужны два порога влажности, а не один?
- 2. Что защищает ограничение полива в 30 секунд?
- 3. Зачем в правиле полива запрет при температуре ниже +10?
- 4. Что даёт флаг polivDone?
- 5. Как сутки сжаты в симуляторе теплицы?
Раздел закрыт: от «у Arduino нет часов» до теплицы, которая сама решает, светить ей или поливать. Дальше проект растёт в сторону хранилищ и связи — энергонезависимые настройки, журнал на карте, расписания из сети — и всюду время остаётся фундаментом: пока DS3231 тикает, у автоматики есть «сейчас», от которого можно отсчитать любое «потом».
Частые вопросы по теме урока
- Почему для полива нужны два порога влажности, а не один?
- У границы значение дрожит — насос включался бы и выключался каждую секунду. Гистерезис 600/300 держит решение до явного пересечения второй границы. Один порог даёт качанье реле на границе значения
- Что защищает ограничение полива в 30 секунд?
- Если датчик отгнил и всегда «сухо», таймер millis() + 30000 превращает бесконечный полив в короткий. Страховка по времени обязательна у любого исполнительного механизма. Против лжи датчика: если он вечно показывает сухо, грядку не зальёт
- Зачем в правиле полива запрет при температуре ниже +10?
- Полив ледяной водой по тёплой почве — стресс для корней. Вечерний полив при похолодании отменяется и переносится на следующее окно. Холодная вода вызывает стресс корней — растения гибнут
- Что даёт флаг polivDone?
- Пока окно открыто, условие «время полива» остаётся истинным — без флага насос включался бы снова и снова. Флаг сбрасывается при закрытии окна. Один полив на окно вместо непрерывного повторения
- Как сутки сжаты в симуляторе теплицы?
- Демо-сутки дляжатся минуту: свет — первые 20 секунд каждой минуты, полив — с 8-й по 19-ю. На плате те же правила смотрят на часы и минуты из rtc.now(). Минута демонстрации = сутки: свет 20 с, окно полива 8–19 с