Расписание и таймеры в умном доме
Настоящий умный дом живёт по часам: свет в аквариуме включается в 8 утра, полив — вечером, а уведомление о двери — только днём.
Датчики реагируют на события "сейчас". Расписание добавляет измерение времени — действия, привязанные к часам и минутам.
До сих пор наши проекты реагировали на датчики. Теперь добавим время: действия по расписанию. На Pi для этого есть два пути — таймеры внутри Python и системный планировщик cron, который запускает скрипты по часам даже без работающей программы.
Представь будильник на телефоне. Ты ставишь его на 7:00, выключаешь экран, кладёшь телефон на тумбочку — и утром он звонит сам, хотя ты ничего не запускал. cron работает ровно так же, только вместо звонка он выполняет твою команду. Это очень удобно: тебе не нужно держать программу включённой круглые сутки, чтобы дождаться нужного часа. Система сама помнит расписание и в нужную минуту дёргает скрипт. Для подростка это как иметь умного помощника, который никогда не забывает: полить цветы в шесть вечера, выключить подсветку в полночь, проверить почтовый ящик каждые пятнадцать минут.
Чем расписание отличается от обычной реакции на датчик? Датчик — это "если случилось — сделай". Расписание — это "когда наступит такое-то время — сделай". Эти два подхода прекрасно дополняют друг друга: днём свет включает датчик движения, а вечером по расписанию загорается мягкая подсветка, даже если в комнате никого нет.
Как работает под капотом
cron — это служба Linux, которая по таблице расписаний запускает команды. Строка в crontab задаёт минуту, час, день и команду:
# открыть таблицу расписаний
crontab -e
# запускать скрипт каждый день в 8:00
0 8 * * * python3 /home/codechick/lights_on.py
# каждые 15 минут
*/15 * * * * python3 /home/codechick/check.py
Каждая строка cron состоит из пяти полей времени и команды. Порядок полей легко запомнить по схеме слева направо: минута, час, день месяца, месяц, день недели. Звёздочка означает "любое значение", то есть "каждый". Запись */15 читается как "каждые 15" — каждые пятнадцать минут. Вот как это выглядит наглядно:
* * * * * команда | | | | | | | | | +-- день недели (0-6) | | | +------- месяц (1-12) | | +------------ день месяца (1-31) | +----------------- час (0-23) +---------------------- минута (0-59)
Сама служба cron просыпается раз в минуту, сверяет текущее время со всеми строками таблицы и запускает те команды, у которых поля совпали. Важно понимать: cron не "следит" за твоей программой и ничего не помнит между запусками — он просто стреляет командой в нужный момент и забывает о ней. Если действие должно зависеть от состояния (например "полей, только если земля сухая"), эту проверку делает уже сам скрипт, который cron запустил.
А логику расписания внутри программы отладим на чистом Python. Проверим для набора правил, должно ли действие сработать в заданный час. Попробуй сам ▶
# Расписание умного дома: что включать в какой час
schedule = {
8: "включить свет в аквариуме",
19: "включить полив",
22: "выключить свет в аквариуме",
}
def at_hour(hour):
# вернуть действие для текущего часа, если оно есть
return schedule.get(hour)
# имитируем сутки
for hour in range(24):
action = at_hour(hour)
if action:
print(f"{hour:02d}:00 -> {action}")
print("Проверка 12:00:", at_hour(12) or "ничего не запланировано")
Мы храним расписание в словаре "час — действие". Это легко расширять и читать. На реальном Pi такой словарь проверялся бы раз в минуту или через cron.
Усложним модель: добавим интервальные задания — действия, которые повторяются каждые N минут, а не в фиксированный час. Это та же идея, что */15 в cron, только на чистом Python. Попробуй сам ▶
# Интервальные задачи: запускать раз в N минут
tasks = [
{"name": "проверить датчик двери", "every": 5},
{"name": "записать температуру", "every": 15},
{"name": "сделать резервную копию", "every": 60},
]
# имитируем 60 минут работы
for minute in range(0, 61):
for task in tasks:
if minute % task["every"] == 0 and minute > 0:
print(f"{minute:02d} мин -> {task['name']}")
Здесь оператор остатка от деления % работает как фильтр: если минута делится на интервал без остатка, значит время сработать. Именно так планировщики решают, наступил ли нужный момент, не храня длинных списков точных времён.
Частые ошибки
- Скрипт по cron не находит файлы. cron запускает с другим окружением — используй абсолютные пути.
- Неверный часовой пояс. Проверь, что на Pi выставлено правильное время (
timedatectl). - Нет логов. Перенаправляй вывод cron-задачи в файл, иначе не поймёшь, сработала ли она.
- Перепутаны поля времени. Очень легко поставить час на место минут и получить запуск раз в час вместо нужного времени — сверяйся со схемой полей.
- Задача висит дольше минуты. Если скрипт работает долго, cron может запустить второй экземпляр поверх первого — следи за длительностью или ставь блокировку.
Best practices
- Для разовых действий по часам используй cron, для сложной логики — программу-демон.
- Храни расписание данными (словарь/список), а не россыпью
if. - Всегда указывай абсолютные пути в cron-задачах и логируй результат.
- Тестируй команду вручную в терминале до того, как ставить её в crontab.
- Добавляй комментарии в crontab прямо над строкой — через полгода ты забудешь, зачем нужна та или иная задача.
Итоги. Расписание добавляет проектам измерение времени. cron запускает скрипты по часам на уровне системы, читая пять полей времени и просыпаясь раз в минуту, а внутри программы логику расписания удобно хранить словарём и отлаживать на чистом Python — как точечные действия по часам, так и интервальные задачи через остаток от деления. Дальше превратим Pi в сетевой сервер.