Статья

Home Assistant: Алиса говорит, какие окна у вас не закрыты

(ciaturiu)

О чем тут воообще?

В своей реализации умного дома я сделал скрипт, который называется “Я ухожу”. По задумке он вызывается перед уходом из дома, когда никого не остается. Скрипт выключает свет и разные устройства. Его можно активировать фразой “Алиса, я ухожу”.

Затем я приобрел контактный сенсор Zigbee и повесил его на окно. Мне захотелось, чтобы Алиса сообщала, в каких комнатах открыты окна. Все таки дома есть кот, а погодные условия бывают разными. Когда такой сенсор один, то никаких проблем не возникает: проверяем, что он в статусе “Открыто” и воспроизводим нужную фразу. Когда же таких сенсоров много, то хочется, чтобы Алиса говорила красивое предложение о том, в каких комнатах окна открыты.

Задумка

По моей задумке, если окно открыто лишь в одной комнате, то Алиса должна говорить, в какой именно комнате. Если в двух, то “там И там”. А если же в трех и более, то нужно через запятую сообщить - в каких комнатах окно не закрыто, а последнюю воспроизвести с приставкой И (там, там и там).

Итак, приступим к реализации.

Реализация

Для начала определим объект (в шаблонитизаторе Jinja2 это называется dictionary), в котором ключ - это кусочек фразы с названием комнаты, а значение - состояние окна в виде boolean.

python
Копировать
{% set window_sensors = {
  "в спальне": is_state("binary_sensor.bedroom_window_contact", "on"),
  "на кухне":  is_state("binary_sensor.kitchen_window_contact", "on"),
  "в гостиной":  is_state("binary_sensor.guestroom_window_contact", "on")
} %}

Затем нужно определить массив, в который мы будем запихивать кусочки фраз, для того, чтобы собрать конечное предложение с помощью join. Так гораздо проще, чем напрямую в шаблоне выводить текст и мучиться с его форматированием.

python
Копировать
{% set data = namespace(text=[]) %}

Ну а дальше просто пробегаемся по объекту, но с помощью условия берем только те сенсоры, у которых состояние “Открыто”:

python
Копировать
{% for k in window_sensors if window_sensors[k] == true %}
	{% if loop.first %}
	  {% set data.text = data.text + [["Кстати,", "Не забудьте, что", "Напоминаю, что"]|random] + [" у вас"] + [[" открыто", " не закрыто"]|random] + [" окно"] %}
	{% endif %}
	
	{% if not loop.first and not loop.last %}
	  {% set data.text = data.text + [", "] + [k] %}
	{% elif loop.last and not loop.first %}
	  {% set data.text = data.text + [" и "] + [k] %}
	{% else %}
	  {% set data.text = data.text + [" "] + [k] %}
	{% endif %}
{% endfor %}

На первой итерации я добавляю начало фразы. Как можно увидеть, используются разные фразы, чтобы придать “человечности” этому бездушному роботу. Далее идут условия. Если итерация не первая и не последняя, то мы в процессе перечисления - добавляем запятую и имя сенсора (ключ объекта). Если же итерации последняя, но не первая (т. е. открытых окон точно больше одного), то добавляем приставку “И”, а затем имя сенсора. В ином случае просто через пробел добавляем имя сенсора (т. е. это начало перечисления).

В результате получается вот такой массив:

python
Копировать
['Напоминаю, что', ' у вас', ' открыто', ' окно', ' ',  спальне', ' и ',  гостиной']

Собираем с помощью join в строку:

python
Копировать
{{ data.text|join("") }}

Результат: “Напоминаю, что у вас открыто окно в спальне и в гостиной”.

Готово! Остается только попросить Алису воспроизвести текст с помощью вызова службы media_player.play_media согласно документации AlexxIT/YandexStation.

Финальный код

yaml
Копировать
service: media_player.play_media
target:
  entity_id: media_player.yandex_station_ # ID вашей станции
data:
  media_content_id: >-
    {% set window_sensors = {
      "в спальне": is_state("binary_sensor.bedroom_window_contact", "on"),
      "на кухне":  is_state("binary_sensor.kitchen_window_contact", "on"),
      "в гостиной":  is_state("binary_sensor.guestroom_window_contact", "on")
    } %}
    
    {% set data = namespace(text=[]) %}
    
    {% for k in window_sensors if window_sensors[k] == true %}
      {% if loop.first %}
        {% set data.text = data.text + [["Кстати,", "Не забудьте, что", "Напоминаю, что"]|random] + [" у вас"] + [[" открыто", " не закрыто"]|random] + [" окно"] %}
      {% endif %}
    
      {% if not loop.first and not loop.last %}
        {% set data.text = data.text + [", "] + [k] %}
      {% elif loop.last and not loop.first %}
        {% set data.text = data.text + [" и "] + [k] %}
      {% else %}
        {% set data.text = data.text + [" "] + [k] %}
      {% endif %}
    {% endfor %}
    
    {{ data.text|join("") }}
  media_content_type: text

Автор, отзовись! Страна должна знать своих героев.
Я же тут 😄
Статья классная, но надо указывать под какую систему: HA и тд
Самое смешное, что я похожее делал еще пару лет назад.

```
- alias: Check for open windows
trigger:
- platform: event
event_type: yandex_intent
event_data:
text: "Проверь окна"
action:
- alias: Set up variables
variables:
areas_ru:
'Hallway': ['в', 'прихожей']
'AV Room': ['в', 'гардеробной']
'Toilet': ['в', 'туалете']
'Lounge': ['в', 'гостинной']
'Dining room': ['в', 'столовой']
'Kitchen': ['на', 'кухне']
'Utility room': ['в', 'подсобке']
'Boiler room': ['в', 'котельной']
'Staircase': ['на', 'лестнице']
'Landing': ['на', 'площадке второго этажа']
'Bedroom': ['в', 'спальне']
'Bathroom': ['в', 'ванной']
'Store room': ['в', 'кладовке']
'Kids room': ['в', 'детской']
'Study': ['в', 'кабинете']
'Guest room': ['в', 'гостевой']
'Guest bathroom': ['в', 'гостевой ванной']
message: >-
{% set ns = namespace(last_pre = '', areas =[]) %}
{%- for area in state_attr('sensor.open_windows', 'areas') -%}
{% set area_ru = areas_ru[area] %}
{% set item = area_ru | join (' ') %}
{%- if ns.last_pre == area_ru[0] -%}
{% set item = area_ru[1] %}
{%- endif -%}
{% set ns.areas = ns.areas + [item] %}
{% set ns.last_pre = area_ru[0] %}
{%- endfor -%}
{%- if ns.areas | count == 0 -%}
Все окна закрыты
{%- elif ns.areas | count > 1 -%}
Окна открыты {{ ns.areas[:-1] | join(", ") }} и {{ ns.areas[-1] }}
{%- else -%}
Окно открыто {{ ns.areas[0] }}
{%- endif -%}

- alias: Respond via Yandex Station
service: media_player.play_media
target:
entity_id: "{{ trigger.event.data.entity_id }}"
data:
media_content_type: text
media_content_id: "{{ message }}"
```

```
- sensor:
- name: Open windows
icon: mdi:window-open-variant
state: >-
{{
states.binary_sensor
| selectattr('attributes.device_class', 'eq', 'door')
| rejectattr('object_id', 'search', 'door')
| selectattr('state', 'eq', 'on')
| map(attribute='entity_id')
| map('area_name')
| list
| count
}}
attributes:
areas: >-
{{
states.binary_sensor
| selectattr('attributes.device_class', 'eq', 'door')
| rejectattr('object_id', 'search', 'door')
| selectattr('state', 'eq', 'on')
| map(attribute='entity_id')
| map('area_name')
| unique
| list
}}
```

Вернуться назад
Вернуться назад