Hero Image

Таймеры в автоматизации: от простого к надежному

Таймеры — это по сути простейшие устройства автоматизации, часто доступные в автономном или даже механическом виде. При этом, несмотря на кажущуюся простоту, их реализация в системах умного дома может вызывать вопросы.

Начнем с простого: предположим, мы хотим реализовать автоматизацию включения лампы в 5 вечера и выключения в 6. Скорее всего, первое, что приходит в голову, — это два "если то" условия: если время 17:00, включи розетку, если время 18:00, выключи розетку. Такая автоматизация выглядит так:

Состояния:
- РозеткаСостояние [Розетка.Состояние]
Триггеры:
- ВремяВключения [время: 17:00 (UTC+03:00), дни недели: пн, вт, ср, чт, пт, сб, вс]
- ВремяВыключения [время: 18:00 (UTC+03:00), дни недели: пн, вт, ср, чт, пт, сб, вс]
Правила:
- РозеткаСостояние := Вкл если Триггер = ВремяВключения
- РозеткаСостояние := Выкл если Триггер = ВремяВыключения

Это рабочий вариант. Отметим, что в классической автоматизации понадобится два сценария вместо одного, как в примере выше. Однако такой вариант может быть не оптимален с точки зрения надежности, так как мы описали поведение только для двух моментов времени. Представим, что нам было жизненно важно, чтобы розетка была включена именно в промежутке от 17:00 до 18:00, и мы очень на это надеялись. Что если в доме есть ребенок или другой взрослый, который не знал о наших важных планах и выключил розетку? С этой точки зрения лучше переформулировать автоматизацию: вместо "включи и выключи по времени", на "включи по времени на заданную длительность".

Состояния:
- РозеткаСостояние [Розетка.Состояние]
Процессы:
- ВключеннаяРозетка
Триггеры:
- ВремяВключения [время: 17:00 (UTC+03:00), дни недели: пн, вт, ср, чт, пт, сб, вс]
Правила:
- Запустить ВключеннаяРозетка на 3600 секунд если Триггер = ВремяВключения
- РозеткаСостояние := ВключеннаяРозетка

Автоматизация таймера в таком виде стала чуть компактнее, надежнее и легче поддающейся настройке. Например, легче поменять время срабатывания или добавить еще время, когда розетка должна включиться на час. Надежнее, потому что теперь мы полностью контролируем состояние розетки, и любые внешние воздействия не возымеют действия — автоматизация вернет розетку к нужному состоянию, равному активности процесса "ВключеннаяРозетка". Отметим, что и в классической "если то" автоматизации тоже лучше использовать подобный подход, если система поддерживает команду сделать паузу: по времени включить, выдержать паузу, выключить. Это даст похожие преимущества: один сценарий вместо двух и упрощение настройки. Но все равно сценарий не будет гарантировать нужное состояние, а только формировать управляющие сигналы в нужное время.

Давайте подумаем: а что если наша розетка или исполнительное устройство (например, нагреватель) по какой-то причине не очень надежно и может сломаться или не среагировать сразу? При этом нам важно убедиться, что устройство включилось или выключилось, а иначе мы что-то перегреем, переохладим, затопим и так далее. В этом случае нам нужна обратная связь от устройства. В наших исполнительных устройствах (краны, приводы окон) есть специальное свойство, сообщающее действительный статус устройства (открыто, закрыто, движется, застрял). Для розетки обратной связью может служить потребляемая мощность. То есть, если мы через розетку включили какое-то устройство, мы ожидаем, что оно начнет потреблять энергию, и розетка начнет сообщать ненулевую мощность. Как это сделать?

Состояния:
- РозеткаСостояние [Розетка.Состояние]
- РозеткаМощность [Розетка.Мощность]
Процессы:
- ВключеннаяРозетка
- КонтрольПереключения
Триггеры:
- ВремяВключения [время: 17:00 (UTC+03:00), дни недели: пн, вт, ср, чт, пт, сб, вс]
- ПереключениеРозетки [целевое состояние: РозеткаСостояние, срабатывает при изменении]
Правила:
- Запустить ВключеннаяРозетка на 3600 секунд, если Триггер = ВремяВключения
- РозеткаСостояние := ВключеннаяРозетка
- Запускать КонтрольПереключения на 290 секунд каждые 30 секунд, если Триггер = ПереключениеРозетки
- Остановить КонтрольПереключения, если ((РозеткаСостояние = Вкл) И (РозеткаМощность > 0))  
  ИЛИ ((РозеткаСостояние = Выкл) И (РозеткаМощность = 0))

Разберемся, что мы сделали. Мы добавили состояние РозеткаМощность для обратной связи. Мы добавили триггер ПереключениеРозетки, который сработает, когда желаемое состояние розетки переключилось. Также мы добавили вспомогательный процесс КонтрольПереключения, который будем запускать, когда случится ПереключениеРозетки, на приблизительно 5 минут с периодичностью 30 секунд. Тут нужно отметить, что все временные параметры, помимо автоматического управления активностью процесса, вызывают проверку правил. Указав периодичность 30 секунд, мы будем ожидать, что после переключения розетки, в течение 5 минут правила будут повторно проверяться каждые 30 секунд. И на каждую проверку правило РозеткаСостояние := ВключеннаяРозетка будет формировать нужную команду. И наконец, мы остановим процесс, если состояние розетки будет соответствовать наблюдаемому. Как сделать нечто подобное в классической "если то" автоматизации, мы не знаем, только если есть поддержка вложенных условий, но и в этом случае это будет нетривиально. Отметим также, что к процессу КонтрольПереключения мы можем добавить уведомление о его штатном завершении (а не остановке), что будет случаться, если по истечении 5 минут исполнительное устройство не оказалось в нужном состоянии.

А что если нас не интересует конкретное время, а мы просто хотим, чтобы розетка каждый час включалась, скажем, на 15 минут? То есть нет стартового триггера, а количество повторений не ограничено. Как это сделать? Снова нужно подумать не о том, что делать, а о том, как должно быть или что у нас есть. А есть у нас два процесса: ПериодВключения (1 час) и ВремяВключения (15 минут). Исходя из этого, автоматизация будет такая:

Состояния:
- РозеткаСостояние [Розетка.Состояние]
Процессы:
- ПериодВключения
- ВремяВключения
Правила:
- Запустить ПериодВключения на 3600 секунд,  
  Запустить ВремяВключения на 900 секунд, если НЕ ПериодВключения
- РозеткаСостояние := ВремяВключения

В автоматизации выше мы установили, что оба процесса запустятся на свое время, когда не активен процесс ПериодВключения. Неактивен — это процесс в начальном состоянии и каждый раз, когда заканчивается его время запуска. А что если нам нужно, чтобы такое переключение было не все время, а какую-то часть дня?

Состояния:
- РозеткаСостояние [Розетка.Состояние]
Процессы:
- ПериодВключения
- ВремяВключения
- ОкноВключения
Триггеры:
- ПораПереключаться [время: 17:00 (UTC+03:00), дни недели: пн, вт, ср, чт, пт, сб, вс]
Правила:
- Запустить ПериодВключения на 3600 секунд,  
  Запустить ВремяВключения на 900 секунд, если (НЕ ПериодВключения) И ОкноВключения
- РозеткаСостояние := ВремяВключения И ОкноВключения
- Запустить ОкноВключения на 10899 секунд, если Триггер = ПораПереключаться

Как мы узнали выше, нужно добавить процесс на длительность периода (ОкноВключения) и триггер по времени, который используется для запуска ОкнаВключения. Активность процесса ОкноВключения мы проверяем при установке состояния розетки (чтобы она выключилась сразу по завершению нужного интервала), а также при перезапуске периодических процессов. Последнее нужно в основном для экономии ресурсов, так как в неактивном состоянии процессы не расходуют вычислительное время.

Пожалуй, это пока все о таймерах. Как вы могли убедиться, всё в нашей власти.