Тема шаблонов (Templates) в HA всегда вызывает много вопросов, причем порой и у пользователей системы со стажем. Здесь я хочу собрать некоторое кол-во примеров с комментариями, которые помогут лучше понять принцип работы и синтаксис темплейтов.
Для погружения в тему, начать стоит как всегда с изучения официальной документации, а так же руководства на шаблонизатор Jinja2, именно его под капотом использует HA.
Шаблоны можно применять во множестве мест, начиная от объектов (например, создав собственный sensor или switch) и заканчивая скриптами, автоматизациями и интерфейсом lovelace. С их помощью можно получать необходимые данные из объектов (сущностей) – стейты и атрибуты, при необходимости модифицировать их, а так же реализовывать всевозможную логику (переменные, циклы, проверки и т.д.). Это по-настоящему мощный инструмент, открывающий массу возможностей.
Один из главных инструментов для работы с шаблонами находится в разделе “Панель разработчика” – “Шаблоны” (/developer-tools/template), здесь можно в он-лайн режиме проверить корректность составленного шаблона и оценить его результат. Не забывайте про него, это сохранит множество времени и нервов при отладке сложных конструкций, а так же поможет лучше понять как все это работает.
Коротко про синтаксис
Обратите внимание на оформление, в шаблонах могут использоваться строки с разным обрамлением:
{{ }}
– Expressions (выражения), эти конструкции после вычисления возвращают какой-то объект.
Пример:{{ as_timestamp(now()) | timestamp_custom ('%W') }}
вернет номер текущей недели.
А{{ (state_attr('light.0x86bd7fffe616b72_light', 'brightness') }}
– атрибут brightness (яркость) лампы.{% %}
– Statements (не знаю как правильно перевести), в этих конструкциях размещается все то, что содержит в себе какую-то “логику”: переменные, проверки if\else, циклы и т.д.
Пример:{% if is_state('group.family_persons', 'not_home') %}Никого нет{% endif %}
вернет текст “Никого нет” если стейт у группы group.family_persons равен not_home.
По умолчанию, каждая строка шаблона на выходе будет оканчиваться символом новой строки “\n”.
Причем даже те строки, которые ничего не выводят (например, объявление переменной – {% set a = 123 %}
).
Об этом необходимо помнить и учитывать при составлении темплейта.
К примеру, если шаблон используется для вывода чего-либо на экран, можно потратить уйму времени в борьбе с лишними пустыми строками на выходе.
Для управления этим поведением в jinja2 существует специальный механизм:
{{- }}
или{%- %}
знак минуса в левой части уберет символ новой строки до текущей.{{ -}}
или{% -%}
знак минуса в правой части уберет символ новой строки после текущей.
Помимо этого, стоит уделить время ресурсу yaml-multiline.info, на котором можно наглядно разобраться с вопросом форматирования и переноса строк в YAML’е (символы |, > и т.д.), это бывает полезно при форматировании многострочных шаблонов.
Отдельно стоит упомянуть о том, какие операторы можно использовать в шаблонах.
Полную информацию об этом стоит искать в документации на jinja, а вкратце это:
- Математические (сложение, вычитание, умножение и т.д.)
- Сравнения (больше, меньше, равно и т.д.)
- Логические (и, или, не)
Помимо этого, в выражениях можно использовать фильтры jinja и HA, передавая значения в них через символ “|”.
Пример: ((states('sensor.0x158d0003230618_pressure') | float) / 1.333) | round(2)
Важные моменты в документации, на которые стоит обратить особое внимание:
- 4 Важных правила написания шаблонов
- Особая форма записи объектов, имена которых начинаются с чисел
- Приоритет операторов в шаблонах
– ~ –
Примеры
Сенсоры и их атрибуты
С помощью шаблонов можно создавать как бинарные (on\off), так и обычные сенсоры:
binary_sensor: - platform: template sensors: # Создаем выделенный сенсор для водонагревателя, отражающий его статус (вкл\выкл) boiler_status: device_class: power # Сенсор примет состояние 'on', если стейт объекта switch.tplink_smartplug_01 будет 'on' value_template: "{{ is_state('switch.tplink_smartplug_01', 'on') }}" # Собственный датчик протечки, меняющий свое состояние в зависимости от input_boolean neptun_water_leakage: friendly_name: Датчики протечки Нептун device_class: 'moisture' value_template: > # Если input_boolean.neptun_activated = 'on', то у нас протечка {{ is_state('input_boolean.neptun_activated', "on") }} sensor: - platform: template sensors: # Создаем выделенный сенсор на основе атрибута другого объекта gismeteo_temperature: unit_of_measurement: °C # В этот сенсор будет попадать данные из атрибута temperature (температура) value_template: '{{ state_attr("weather.gismeteo","temperature") }}' # Сенсор уровня воды в увлажнителе воздуха (в %) smartmi_humidifier_01_water_level: friendly_name: "Остаток воды" value_template: > # Здесь сырые данные из атрибута depth переводятся в проценты {{ ((state_attr('fan.xiaomi_miio_device', 'depth') / 120) * 100) | int }}
В примерах выше используются простые выражения (Expressions) с использованием встроенных в HA функций is_state() и state_attr(). С полным перечнем функций и правилами их применения лучше всего ознакомится в документации.
Более интересный пример:
sensor: - platform: template sensors: date_formatted: friendly_name: 'Date (DD.MM.YYYY)' value_template: "{{ as_timestamp(states('sensor.date_time_iso')) | timestamp_custom('%d.%m.%Y') }}" icon_template: mdi:calendar attribute_templates: day_of_week: >- {% set day_num = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"] %} {% set day_of_week = day_num[now().weekday()] %} {{ day_of_week }}
Здесь в качестве стейта (state) сенсора date_formatted будет выведен результат выражения {{ as_timestamp(states('sensor.date_time_iso')) | timestamp_custom('%d.%m.%Y') }}
и дополнительно, в виде атрибута – день недели, получаемый с помощью ряда операций.
В качестве значения сенсора можно использовать произвольный текст, в этом примере в зависимости от состояния датчика двери (геркона) в стейт будет записан текст “Открыта” или “Закрыта” (речь про дверь):
sensor: - platform: template sensors: entrance_door_status: value_template: > {% if is_state('binary_sensor.0x158d00031c790f_contact', "on") %}Открыта {% elif is_state('binary_sensor.0x158d00031c790f_contact', "off") %}Закрыта {% else %}Unavailable {% endif %}
Более комплексный пример:
- platform: template sensors: local_pressure_mmhg: value_template: > {% set pressure = states('sensor.0x158d0003230618_pressure') | float(default=-1) %} {% if pressure != -1 and (((states('sensor.0x158d0003230618_pressure') | float) / 1.333) | round(2)) > 500 %} {{ ((states('sensor.0x158d0003230618_pressure') | float) / 1.333) | round(2) }} {% else %} {{ states('sensor.local_pressure_mmhg') }} {% endif %}
Темплейты можно использовать практически во всех возможных местах, например в именах будущих сенсоров:
- platform: template sensors: tplinksmartplug01_amps: friendly_name_template: "{{ states.switch.tplink_smartplug_01.name}} Current" value_template: '{{ state_attr("switch.tplink_smartplug_01","current_a") | float }}' unit_of_measurement: 'A' tplinksmartplug01_watts: friendly_name_template: "{{ states.switch.tplink_smartplug_01.name}} Current Consumption" value_template: '{{ state_attr("switch.tplink_smartplug_01","current_power_w") | float }}' unit_of_measurement: 'W'
Или меняя иконку сенсора в зависимости от различных условий:
mirobot_1s_battery: friendly_name: "Xiaomi Vacuum Cleaner 1S" device_class: battery unit_of_measurement: '%' icon_template: > {% if state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') %} {% if state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') >= 98 %} mdi:battery {% elif state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') >= 85 %} mdi:battery-90 {% elif state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') >= 75 %} mdi:battery-80 {% elif state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') >= 65 %} mdi:battery-70 {% elif state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') >= 50 %} mdi:battery-50 {% elif state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') >= 35 %} mdi:battery-30 {% elif state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') >= 25 %} mdi:battery-20 {% elif state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') <= 15 %} mdi:battery-10 {% else %} mdi:battery-outline {% endif %} {% endif %} value_template: "{{ state_attr('vacuum.xiaomi_vacuum_cleaner', 'battery_level') }}"
Switches
В выключателях так же есть возможность использовать шаблоны.
Например, можно создать выключатель телевизора с разными действиями на включение и выключение:
switch: - platform: template switches: samsungtv_40c5100: # Считать выключатель включенным, если binary_sensor.samsungtv_40c5100 = 'on' value_template: "{{ is_state('binary_sensor.samsungtv_40c5100', 'on') }}" # Выключатель доступен если выполняется условие ниже, иначе он становится unavailable availability_template: "{{ is_state('binary_sensor.smartir_01_status', 'on') }}" turn_on: service: switch.turn_on data: entity_id: switch.smartir_01_tv_samsung_power turn_off: service: script.turn_on data: entity_id: script.power_off_samsungtv_40c5100 # Иконка у выключателя будет меняться в зависимости от состояния сенсора binary_sensor.samsungtv_40c5100 icon_template: >- {% if is_state('binary_sensor.samsungtv_40c5100', 'on') %} mdi:television {% else %} mdi:television-off {% endif %}
Скрипты и автоматизации
Самое широкое применение шаблоны встречают в скриптах и автоматизациях.
Скрипт для запуска уборки роботом-пылесосом конкретной комнаты (в интерфейсе input_select.room_to_vacuum это выпадающий список с комнатами):
script: start_vacuum_room: alias: 'Clean Selected Room [Mi Robot]' sequence: - service: script.turn_on data_template: entity_id: >- {% if is_state("input_select.room_to_vacuum", "Прихожая") %} script.start_vacuum_hallway {% elif is_state("input_select.room_to_vacuum", "Детская") %} script.start_vacuum_nursery {% elif is_state("input_select.room_to_vacuum", "Гостиная") %} script.start_vacuum_living_room {% elif is_state("input_select.room_to_vacuum", "Кухня") %} script.start_vacuum_kitchen {% elif is_state("input_select.room_to_vacuum", "Спальня") %} script.start_vacuum_bedroom {% endif %}
Автоматизация, меняющая мощность работы робота-пылесоса:
automation: - alias: 'Set cleaning mode' trigger: platform: state entity_id: input_select.vacuum_power action: - service: > {% if trigger.to_state.state == 'Silent' %} script.set_vacuum_power_silent {% elif trigger.to_state.state == 'Standard' %} script.set_vacuum_power_standard {% elif trigger.to_state.state == 'Medium' %} script.set_vacuum_power_medium {% elif trigger.to_state.state == 'Turbo' %} script.set_vacuum_power_turbo {% elif trigger.to_state.state == 'Gentle' %} script.set_vacuum_power_gentle {% endif %}
Оповещение о произошедшей ошибке.
С помощью шаблона можно получить текст ошибки из атрибута error:
automation: - alias: 'Оповещение об ошибке' initial_state: true trigger: platform: state entity_id: vacuum.xiaomi_vacuum_cleaner to: "error" action: - service: notify.telegram data_template: message: | Mi Robot: Произошла *ошибка*! {{ state_attr('vacuum.xiaomi_vacuum_cleaner', "error") }}
Автоматизация меняющая яркость лампы по двойному клику на кнопку (по кругу):
automation: - alias: 'Яркость света в детской' initial_state: true trigger: platform: state entity_id: sensor.0x158d00033efd9e_action to: 'double' action: service: light.turn_on data_template: entity_id: light.detskaia transition: '0.5' brightness: > {%- if (state_attr('light.detskaia', 'brightness') | int) <= 3 %} 51 {% elif (state_attr('light.detskaia', 'brightness') | int) <= 51 %} 102 {% elif (state_attr('light.detskaia', 'brightness') | int) <= 102 %} 153 {% elif (state_attr('light.detskaia', 'brightness') | int) <= 153 %} 204 {% elif (state_attr('light.detskaia', 'brightness') | int) <= 204 %} 255 {% elif (state_attr('light.detskaia', 'brightness') | int) <= 255 %} 3 {% endif %}
Плавное включение света в заданное в интерфейсе HA время (будильник):
automation: - alias: Sunrise Lighting (Bedroom) initial_state: true trigger: platform: template # Триггером служит совпадение времени в sensor.time и input_datetime.sunrise_in_bedroom value_template: "{{ states('sensor.time') == (states('input_datetime.sunrise_in_bedroom')[:5]) }}" condition: condition: and conditions: - condition: state entity_id: binary_sensor.workday_sensor state: 'on' - condition: sun before: sunrise before_offset: "00:30:00" action: - service: light.turn_on entity_id: light.spalnia_stol data: effect: SunriseBW
Постепенное увеличение громкости:
script: googlehome3792_increase_volume: alias: Increase volume by 5% sequence: - service: media_player.volume_set entity_id: media_player.googlehome3792 data_template: volume_level: > {% set level = (state_attr('media_player.googlehome3792', 'volume_level') | float) + (0.05 | float) %} {% if level < 1 %} {{ level }} {% else %} 1 {% endif %} automation: - alias: Increase volume loop - Childrens Room initial_state: false trigger: platform: time_pattern minutes: "/3" action: - service: script.turn_on entity_id: script.googlehome3792_increase_volume
Изменение громкости у выбранного из выпадающего списка источника:
automation: - alias: 'Громкость радио' trigger: platform: state entity_id: input_number.volume_radio action: service: media_player.volume_set data_template: entity_id: > {% if is_state("input_select.output_device", "Гостинная (TV)") %} media_player.gostinaia {% elif is_state("input_select.output_device", "Гостинная (Home Mini)") %} media_player.googlehome9967 {% elif is_state("input_select.output_device", "Детская (TV)") %} media_player.detskaia {% elif is_state("input_select.output_device", "Детская (Home Mini)") %} media_player.googlehome3792 {% endif %} volume_level: '{{ states.input_number.volume_radio.state }}'
Отправка оповещений о приходе или уходе из дома:
automation: - alias: 'Home Presence Alert' initial_state: true trigger: platform: state entity_id: person.alexander, person.irina condition: condition: and conditions: - condition: template value_template: "{{ trigger.to_state.state != trigger.from_state.state }}" action: - service: notify.telegram data_template: message: > {{ trigger.to_state.attributes.friendly_name }} {% if trigger.to_state.state == 'home' %}дома! {% else %}скорее всего вне дома. {% endif %}
В примере выше сообщение будет выглядеть примерно так – “Александр скорее всего вне дома.”
Шаблон соберется в одну строку, т.к. перед ним указан символ “>”, означающий замену символов новой строки на пробелы.
Еще одно оповещение, на этот раз о температуре в комнате (холодно или жарко):
automation: - alias: Termo alert [Living Room] trigger: - platform: template value_template: "{{ (states('sensor.0x158d0003230618_temperature') | float) < 22 }}" for: minutes: 5 - platform: template value_template: "{{ (states('sensor.0x158d0003230618_temperature') | float) > 25 }}" for: minutes: 5 action: - service: notify.telegram data_template: message: >- В *Гостиной* {% if (trigger.to_state.state | float) > 23 -%} жарко, {% elif (trigger.to_state.state | float) < 23 -%} холодно, {% endif -%} температура: *{{ trigger.to_state.state }}°C*
Команда для телеграм-бота, возвращающая имена находящихся дома:
automation: - alias: 'Telegram Bot - Who is home?' trigger: platform: event event_type: telegram_command event_data: command: '/whoishome' action: service: telegram_bot.send_message data_template: target: '{{ trigger.event.data.user_id }}' message: | Сейчас дома: {%- set entites = expand('group.family_persons') %}{% for prs in entites %}{% if prs.state == "home" %} {{ prs.attributes.friendly_name }}{% endif %}{% endfor %} {% if is_state("group.family_persons", "not_home") %}Никого нет{% endif %}
Здесь, в отличии от предыдущего примера, перед шаблоном указан символ “|”, означающий сохранение всех переносов строк в шаблоне. Лишняя пустая строка из вывода убрана с помощью конструкции “{%-“.
Еще одна команда для бота, присылающая текущую погоду:
- alias: 'Telegram Bot - Weather' trigger: platform: event event_type: telegram_command event_data: command: '/weather' action: - service: telegram_bot.send_photo data_template: target: '{{ trigger.event.data.user_id }}' file: '/config/www/weather_icons/{{ states("weather.gismeteo") }}.webp' caption: | Температура {{ state_attr('weather.gismeteo', 'temperature') }}°C Влажность {{ state_attr('weather.gismeteo', 'humidity') }}% Давление {{ states('sensor.gismeteo_pressure_mmhg') }} mmHg
Здесь, в зависимости от стейта сенсора weather.gismeteo (sunny, rainy, snowy и т.д.) отправляется картинка соответствующая текущей погоде (картинки подготовлены заранее).
– ~ –
Я постарался собрать здесь как можно более разнообразные примеры использования шаблонов в HA, но т.к. эта тема невероятно обширная, получилось это у меня слабо… =)
В будущем я надеюсь дополнить заметку другими интересными (с разных точек зрения) примерами.