Курятник на шесть кур я утеплил за выходные, а со светом промучился весь декабрь. Несушкам нужен световой день около четырнадцати часов — при таком ритме они продолжают нестись и зимой. Декабрьское солнце в средней полосе встаёт около половины девятого и садится около четырёх, то есть даёт часов семь — половину нормы приходится добирать лампой. Я повесил лампочку через реле-модуль и первое время включал её руками по дороге на работу. Ровно до того дня, когда уехал на сутки: вернулся к тишине в сарае и злым курам.
Тогда я решил сделать «по-умному» и повесил у окна сарая фоторезистор: темно — включай. За неделю я собрал коллекцию глюков. Фары машин, разворачивающихся напротив сарая, включали свет среди ночи. Пасмурный полдень горел как вечер. А в ясный декабрьский день лампа зажигалась в честные четыре часа — и я всё равно вручную следил, не соврало ли. Датчик отвечал на вопрос «темно ли сейчас», а мне нужен был ответ на вопрос «положено ли гореть по расписанию».
Лечится это часами реального времени и окном: включать от часа X до часа Y. DS3231 помнит время без питания, за точность у меня отвечает уже установленный модуль — как его подключить и завести, разобрано в уроке про часы DS3231, а сам модуль реле с его контактами и клеммами я раскладывал по винтикам отдельно. Осталось соединить: время на входе, реле на выходе, логика окна посередине.
Второй случай того же сорта стоял у приятеля в аквариуме: живым растениям нужно десять часов света в сутки, пересвет обрастает водорослями, недосвет — бурым налётом. Расписание там даже строже, чем в курятнике: окно 10:00–20:00, семь дней в неделю, и руки к выключателю прилагать нельзя. Формула одна и та же — и дальше в уроке я буду держать в голове именно её: окно в минутах суток, реле на выходе, флаг на границе.
Окно освещения: от часа X до часа Y
Логика окна — это два сравнения, если заранее перевести момент в одну шкалу. У меня работает минута суток: now.hour() умножить на шестьдесят и прибавить now.minute() — получится число от 0 до 1439. Семь вечера — это 1140, половина пятого утра — 270. Границы окна становятся такими же числами: 16:00 — это 960, 22:30 — это 1350. Дальше элементарно: гореть положено, если минута суток не меньше начала и меньше конца. Три вложенных if по часам и минутам, которыми грешат первые скетчи, схлопываются в одну строку — поля объекта я разбирал в уроке про DateTime, здесь беру их как готовые.
Ловушка прячется в окнах через полночь. Для курятника хватает вечернего окна, а вот дежурная подсветка теплицы горит с 22:00 до 6:00 — и условие m >= 1320 && m < 360 не срабатывает никогда: минута суток не может быть одновременно больше 1320 и меньше 360. Я сам просидел над этим вечер, проверяя реле на столе, где оно работало, — а на границе суток молчало. Правило простое: если начало больше конца, окно заворачивается через ноль, и условие разворачивается на ИЛИ — m >= начала ИЛИ m < конца. Проверяйте такое окно в трёх точках: 23:59, 00:01 и полдень.
Типовой скетч с форумов заводит две переменные — час включения и час выключения — и сравнивает только now.hour(). Это тот же приём в упрощении: шаг в целый час, без минут. Мне минутная шкала нравится больше: досветку удобно сдвигать на полчаса без переписывания логики, а цена — одно умножение.
Флаги состояния: щелчок один раз
Окно отвечает на вопрос «гореть или нет», но на границе нужно действие один раз, а не каждую секунду. Без флага скетч честно держит реле включённым — digitalWrite одного и того же значения щелчка не даёт, — но момент перехода вы не увидите: ни строки в журнал, ни гудка, ни письма. Флаг решает это за четыре строки: заводим переменную svet, и только когда решение расходится с флагом, делаем действие и запоминаем его. Вошли в окно — один раз щёлкнули реле и напечатали строку. Вышли — один раз погасили.
Такой паттерн называется «по фронту», и он же спасает от спама: без флага печать состояния в loop() каждую секунду засоряет журнал сотнями одинаковых строк, а с флагом в нём остаются только события. Для датчиков тот же приём приходится усиливать гистерезисом — температура дрожит у порога, и реле трещит; я разбирал это на термостате. У окна по часам гистерезис не нужен: время не дрожит, DS3231 не мечется между 16:59 и 17:00. Единственная защита, которую я всё же оставляю, — проверка lostPower() в шапке: после пропажи питания флаг svet обнуляется, и скетч при входе в активное окно щёлкнет реле заново. Порядок шапки с begin и adjust — в уроке про установку RTClib и первый скетч.
Про само реле пара чисел. Катушка типового модуля с оптопарой берёт около 70 мА при 5 В — вместе с платой это на грани лимита USB-порта в 500 мА, поэтому я подаю питание на модуль от отдельного источника, а не с пина 5V. Твердотельное реле (SSR) во вход ест единицы миллиампер, щёлкает молча и не изнашивается — механическое реле рассчитано на порядка ста тысяч переключений, и ежедневные два щелчка растянут его на десятки лет, но в логгере на сотни событий в день я бы уже брал SSR.
#include <Wire.h>#include <RTClib.h> RTC_DS3231 rtc; const int RELAY = 7;const int NACH = 16 * 60; // 16:00 в минутах сутокconst int KON = 22 * 60 + 30; // 22:30 int svet = 0; // флаг состояния: 0 - выключен, 1 - включён void setup() { Serial.begin(9600); if (rtc.begin() == false) { Serial.println("DS3231 ne otvechaet"); while (1) { } // без часов расписанию делать нечего } if (rtc.lostPower()) { rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } pinMode(RELAY, OUTPUT);} void loop() { DateTime now = rtc.now(); int m = now.hour() * 60 + now.minute(); // минута суток, 0..1439 int nado = 0; if (NACH < KON) { nado = (m >= NACH && m < KON); // обычное окно } else { nado = (m >= NACH || m < KON); // окно через полночь } if (nado == 1 && svet == 0) { // фронт включения svet = 1; digitalWrite(RELAY, HIGH); Serial.print("SVET ON h="); Serial.print(now.hour()); Serial.print(" m="); Serial.println(now.minute()); } if (nado == 0 && svet == 1) { // фронт выключения svet = 0; digitalWrite(RELAY, LOW); Serial.print("SVET OFF h="); Serial.print(now.hour()); Serial.print(" m="); Serial.println(now.minute()); } delay(5000);}Закат настоящий против окна фиксированного
Сосед формулировал задачу как «чтобы на закате». Проблема в том, что закат плывёт: в средней полосе он уезжает с примерно 15:50 в декабре до примерно 21:20 в июне — почти шесть часов за полгода. Фиксированное окно повторяет закат честно только в один сезон; моё окно 16:00–22:00 идеально ложится на декабрь и превращается в бессмысленное в июне, когда закат наступает на пятом часу после него. Для курятника это даже к лучшему: несушкам нужен именно световой день заданной длины, а не астрономия.
Если нужен именно закат, у меня два рабочих варианта с разными ценами. Первый — фоторезистор, но с гистерезисом и задержкой: включать не по мгновенной темноте, а когда темно уже минут пять, иначе фары и туча будут дёргать реле. Второй — таблица заката на год, зашитая массивом или в энергонезависимую память: примерно раз в месяц своё значение, честный закат без датчика. Для уличного фонаря, который должен гореть при любой погоде, я ставлю фотодатчик; для расписания — часы. Светильник по присутствию людей — совсем другой принцип, он разобран рядом с датчиком движения. Границы окна удобно править без перепрошивки: положить их в EEPROM и менять с монитора — приём из урока про первые настройки в энергонезависимой памяти.
| Способ | Чем управляется | Грабли | Когда беру |
|---|---|---|---|
| Окно по RTC | минута суток из DS3231, два сравнения | окно через полночь требует ИЛИ | курятник, аквариум, досветка |
| Фоторезистор | уровень освещённости с A1 | фары и тучи дёргают реле — нужен гистерезис и задержка | уличный фонарь |
| Таблица заката | массив дат на год в памяти | годится для одной широты | закат без датчика |
| PIR-датчик | движение в кадре | это не расписание, а присутствие | проходная, лестница |
- Запустите симулятор и найдите в журнале строку SVET ON — время в ней должно быть 17:0:10, реле на макетке щёлкнет.
- Дождитесь SVET OFF на 17:0:25 и убедитесь, что строки событий идут по одной, а не каждую секунду — это работа флага.
- Поменяйте границы местами (NACH = 50, KON = 5) — окно заворачивается через полночь: с 50-й по 60-ю и с 0-й по 5-ю секунды, проверьте оба фронта.
- Сравните с код-блоком для платы: структура та же, отличается только шкала — секунды в симе против минут суток на плате.
- Минута суток (hour()*60 + minute()) превращает окно освещения в два сравнения — вместо трёх вложенных if
- Окно через полночь обязано проверяться с ИЛИ: если начало больше конца — окно лежит через ноль часов
- Флаг состояния даёт действие по фронту: один щелчок реле и одна строка журнала на границу, а не спам каждую секунду
- Катушка реле-модуля берёт около 70 мА при 5 В — питание модуля веду от отдельного источника, не с пина 5V
- Фиксированное окно повторяет закат только один сезон: закат в средней полосе уезжает на ~6 часов от декабря к июню
- Гистерезис окну по часам не нужен — время не дрожит у порога, в отличие от датчика освещённости
- 1. Сколько даёт выражение now.hour() * 60 + now.minute() в 23:59?
- 2. Окно с 22:00 до 6:00. Каким должно быть условие?
- 3. Зачем скетчу окна флаг состояния svet?
- 4. Почему для окна по часам не нужен гистерезис, а для фоторезистора нужен?
- 5. Катушка типового реле-модуля при 5 В потребляет около 70 мА. Чем это грозит?
Окно — самый частый бытовой случай, но почти в каждом проекте за ним следом идёт вопрос «а сколько прошло между событиями» и «а что было раньше»: журнал переходов реле, интервалы поливов, время от старта до готовности. Для этого у RTClib есть второй слой — сравнение моментов и разности unix-времени; он разобран в соседнем уроке про сравнение DateTime и хронологию. А когда захочется не окно, а длительность — «держать свет три часа после заката» — со сдвигами момента справляется TimeSpan, до него черёд дойдёт следом.
Частые вопросы по теме урока
- Сколько даёт выражение now.hour() * 60 + now.minute() в 23:59?
- 23 часа умножаются на 60 — это 1380, плюс 59 минут — итого 1439. Минута суток пробегает все значения от 0 до 1439 и сравнивается с границами окна двумя неравенствами. 1439
- Окно с 22:00 до 6:00. Каким должно быть условие?
- Начало больше конца — окно лежит через полночь, поэтому условие собирается с ИЛИ. Вариант с И никогда не истинен: минута суток не бывает одновременно большой и маленькой. m >= 1320 || m < 360
- Зачем скетчу окна флаг состояния svet?
- digitalWrite и печать на фронте выполняются только когда решение расходится с флагом. Получается одна строка журнала на включение и одна на выключение вместо сотен повторов. Чтобы действие на границе выполнялось один раз
- Почему для окна по часам не нужен гистерезис, а для фоторезистора нужен?
- DS3231 не мечется между 16:59 и 17:00 — граница проходит один раз. Освённость у порога срабатывания прыгает от туч и фар, и без гистерезиса реле трещит. Время не дрожит у границы, а освещённость у порога прыгает
- Катушка типового реле-модуля при 5 В потребляет около 70 мА. Чем это грозит?
- Лимит USB-порта — 500 мА, плата со своей обвязкой берёт десятки миллиампер. Я подаю питание релейного модуля от отдельного источника, а не с пина 5V. С питанием от USB плата с модулем у края лимита порта