В этой статье я расскажу про написание скрипта HA, который продолжает воспроизведение видео запущенного с Plex на другом устройстве: выключает источник, включает устройство назначения, включает Plex, включает текущее видео, перематывает видео в нужную позицию, устанавливает необходимую громкость.
Написание скрипта и тестирование заняло больше времени, чем сэкономит его использование, тем более Plex и так сохраняет текущую позицию, но скрипт писался скорее в исследовательских целях. Возможно вы сможете подчерпнуть полезные решения, которые были использованы в нем, а может подскажете мне в комментариях как что-то можно было сделать проще.
В статье будет рассмотрено использование переменных (variables), триггеров (wait triggers), циклов (loops), условий (conditions), шаблонов значений (value templates).
А теперь давайте посмотрим каждое действие по отдельности.
-
Определение переменных target, source, plextarget и volume.
Эти переменные будут использоваться в скрипте:
target - телевизор назначение
source - телевизор источник
plextarget - медиаплеер Plex назначение. Каждый клиент plex регистрируется в Home Assistant как отдельный device - mediaplayer. Управление контентом и позицией возможно только через него, так как сам телевизор знает только какой запущен источник (hdmi или приложение) и статус (idle, playing, paused) и не понимает, что конкретно на нем проигрывается и какая текущая позиция.
volume - текущая громкость на источнике. Так как в фильмах используется разный уровень аудиодорожек, то не хочется руками регулировать громкость на ТВ назначении, а тем более чтобы он заорал на всю квартиру, если там был установлен высокий уровень.
Сначала нужно определить откуда и куда необходимо перенести просмотр фильма. Так как я автоматизировал только для сценария перехода между двумя комнатами (кухней и спальней), то предполагаем что один ТВ в момент запуска скрипта включен, другой выключен (можно реализовать и более сложные проверки, например, что включен источник plex, телевизор в статусе playing и т.д.).
Соответственно если ТВ спальня включен, то target = ТВ кухня, иначе target = ТВ спальня. Аналогично поступаем с source, plextarget и volume.
Для получения значения entity необходимо воспользоваться states, параметром с который передается entity, а он возвращает его значение.
Код
variables:
target: >-
{{ 'media_player.lg_webos_kitchen' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.lg_webos_bedroom' }}
source: >-
{{ 'media_player.lg_webos_bedroom' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.lg_webos_kitchen' }}
plextarget: >-
{{ 'media_player.plex_plex_for_lg_lg_65up751c0zf' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.plex_plex_for_lg_lg_oled65c1rla' }}
volume: >-
{{ state_attr('media_player.lg_webos_bedroom', 'volume_level') if
(states('media_player.lg_webos_bedroom') == 'on') else
state_attr('media_player.lg_webos_kitchen', 'volume_level') }}
-
Поставить Plex на паузу.
Придется немного забежать вперед, чтобы объяснить зачем ставить Plex на паузу на источнике. Как выяснилось, Plex обновляет атрибут media_position с большим интервалом, а значит узнать точную текущую позицию нельзя, но оказалось, что позиция обновляется при постановке на паузу.
Код
action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ source }}"
alias: Поставить Plex на паузу, чтобы обновилась media_position
-
Дождаться пока Plex станет на паузу
Поскольку в wait trigger нельзя использовать template entity, то я не смог использовать переменную source. А так как один из медиаплеер все равно в данный момент находится в статусе unavailable (напоминаю, что он выключен), то просто добавляем оба в entity_id (далее по этой причине в wait trigger везде будут добавляться и источник и назначение) и при изменении статуса любого на paused продолжаем выполнение скрипта.
Код
wait_for_trigger:
- trigger: state
entity_id:
- media_player.plex_plex_for_lg_lg_65up751c0zf
- media_player.plex_plex_for_lg_lg_oled65c1rla
to: paused
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
-
Определить переменные media_content_id и media_position
В интерфейсе HA идем в Developer toolsStates, находим mediaplayer Plex и смотрим какие там есть атрибуты:
Entity |
State |
Attributes |
---|---|---|
media_player.plex_plex_for_lg_lg_oled65c1rla Plex (Plex for LG - LG OLED65C1RLA) |
playing |
friendly_name: Plex (Plex for LG - LG OLED65C1RLA) |
Из полезного видим media_content_id и media_position. Проверяем и убеждаемся, что в этих атрибутах содержится id видео и текущая позиция в секундах.
Сохраняем эти значения в соответствующие переменные, они нам еще пригодятся. Для этого нам понадобится if, который мы использовали ранее и state_attr. Если у entity, кроме state еще есть атрибуты, то их значения можно получить с помощью state_attr, первым параметром в него передается entity, вторым имя атрибута.
Код
variables:
media_content_id: >-
{{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',
'media_content_id') if (states('media_player.lg_webos_bedroom') == 'on')
else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',
'media_content_id') }}
media_position: >-
{{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',
'media_position') if (states('media_player.lg_webos_bedroom') == 'on') else
state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf', 'media_position')
}}
-
Выключить ТВ источник.
Код
action: media_player.turn_off
metadata: {}
data: {}
target:
entity_id: "{{ source }}"
alias: Выключить ТВ источник
-
Включить ТВ назначение.
Код
action: media_player.turn_on
metadata: {}
data: {}
target:
entity_id: "{{ target }}"
alias: Включить ТВ назначение
-
Ждать пока ТВ не включится
Код
wait_for_trigger:
- trigger: state
entity_id:
- media_player.lg_webos_kitchen
- media_player.lg_webos_bedroom
from: "off"
to: "on"
for:
hours: 0
minutes: 0
seconds: 2
timeout:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
alias: Ждать пока ТВ не включится
-
Убавить звук до 0
Код
action: media_player.volume_set
metadata: {}
data:
volume_level: 0
target:
entity_id: "{{ target }}"
alias: Убавить громкость до 0
-
Запустить приложение Plex на ТВ
Код
action: media_player.select_source
metadata: {}
data:
source: Plex
target:
entity_id: "{{ target }}"
alias: Включить источник Plex
-
Определить переменную timestamp
Получаем ее с помощью now().timestamp() и преобразовываем в int. Объясню ее назначение в следующем пункте.
Код
variables:
timestamp: "{{ (now().timestamp() | int) }}"
-
Сканировать клиентов Plex
Как выяснилось, mediaplayer в HA остается очень долгое время в статусе unavailable после запуска Plex на ТВ. В документации на интеграцию не нашел никакой информации, что с этим можно было бы сделать. Но если нет документации, то идем методом научного тыка. Интеграция Plex кроме медиаплееров создает еще одно устройство с несколькими сенсорами и одной кнопкой "Scan clients". Пробуем ее нажимать и видим, что это существенно ускоряет переход клиента в статус idle (запущен, но ничего не проигрывается). В этом статусе можно управлять клиентом.
Действие выглядит предельно простым: нажимать scan clients каждые 5 секунд, пока один из медиаплееров (кухня, спальня) не перейдет в состояние idle. Но чтобы скрипт не завис хочется ожидание статуса idle ограничить одной минутой. Иначе я устану ждать включу фильм сам и вдруг в какой-то момент скрипт начнет управлять медиаплеером, когда это уже не нужно.
Казалось бы элементарная задача, объявить переменную, после каждой итерации делать инкремент и на 12 раз (цикл длится 5 секунд) остановить цикл, однако в HA область действия переменных не позволяет это сделать. Можно почитать подробнее здесь https://www.home-assistant.io/docs/scripts/#scope-of-variables
Поэтому воспользуемся созданной в предыдущем пункте переменной timestamp и будем ждать пока текущее время в секундах не станет больше timestamp на 60 или пока один из медиаплееров не перейдет в статус idle.
Код
alias: Сканировать клиентов Plex каждые 5 секунд, пока не найдется клиент
repeat:
sequence:
- delay:
hours: 0
minutes: 0
seconds: 5
milliseconds: 0
- action: button.press
metadata: {}
data: {}
target:
entity_id: button.media_scan_clients
while:
- condition: and
conditions:
- condition: template
value_template: "{{ (now().timestamp() | int) - timestamp < 60 }}"
- condition: not
conditions:
- condition: or
conditions:
- condition: state
entity_id: media_player.plex_plex_for_lg_lg_oled65c1rla
state: idle
- condition: state
entity_id: media_player.plex_plex_for_lg_lg_65up751c0zf
state: idle
enabled: true
-
Включить фильм на Plex
Для этого воспользуемся переменной media_content_id
Код
action: media_player.play_media
metadata: {}
data:
media_content_id: "{{ media_content_id }}"
media_content_type: movie
target:
entity_id: "{{ plextarget }}"
alias: Включить фильм на Plex
-
Ждать пока статус Plex не станет Playing
Пару секунд фильм успеет проиграться пока встанет на паузу, на случай если там громкая заставка мы ранее убавили звук на ТВ на 0.
Код
alias: Ждать пока статус Plex не станет Playing
wait_for_trigger:
- trigger: state
entity_id:
- media_player.plex_plex_for_lg_lg_oled65c1rla
- media_player.plex_plex_for_lg_lg_65up751c0zf
to: playing
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
-
Поставить на паузу
Код
action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ target }}"
alias: Поставить на паузу
-
Ждать пока не станет на паузу
Код
wait_for_trigger:
- trigger: state
entity_id:
- media_player.lg_webos_kitchen
- media_player.lg_webos_bedroom
to: paused
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
alias: Ждать пока не станет на паузу
-
Перемотать фильм
Воспользуемся переменной media_position чтобы перемотать фильм в нужное место
Код
action: media_player.media_seek
metadata: {}
data:
seek_position: "{{ media_position }}"
target:
entity_id: "{{ plextarget }}"
alias: Перемотать фильм в нужное место
-
Установить уровень громкости
Воспользуемся переменной volume, чтобы установить уровень громкости.
Код
action: media_player.volume_set
metadata: {}
data:
volume_level: "{{ volume }}"
target:
entity_id: "{{ target }}"
alias: Установить уровень громкости как на источнике
Ну и на последок полный текст скрипта.
Полный текст скрипта
alias: plex_transfer_movie_position
sequence:
- variables:
target: >-
{{ 'media_player.lg_webos_kitchen' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.lg_webos_bedroom' }}
source: >-
{{ 'media_player.lg_webos_bedroom' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.lg_webos_kitchen' }}
plextarget: >-
{{ 'media_player.plex_plex_for_lg_lg_65up751c0zf' if
(states('media_player.lg_webos_bedroom') == 'on') else
'media_player.plex_plex_for_lg_lg_oled65c1rla' }}
volume: >-
{{ state_attr('media_player.lg_webos_bedroom', 'volume_level') if
(states('media_player.lg_webos_bedroom') == 'on') else
state_attr('media_player.lg_webos_kitchen', 'volume_level') }}
- action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ source }}"
alias: Поставить Plex на паузу, чтобы обновилась media_position
- wait_for_trigger:
- trigger: state
entity_id:
- media_player.plex_plex_for_lg_lg_65up751c0zf
- media_player.plex_plex_for_lg_lg_oled65c1rla
to: paused
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
- variables:
media_content_id: >-
{{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',
'media_content_id') if (states('media_player.lg_webos_bedroom') == 'on')
else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',
'media_content_id') }}
media_position: >-
{{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',
'media_position') if (states('media_player.lg_webos_bedroom') == 'on')
else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',
'media_position') }}
- action: media_player.turn_off
metadata: {}
data: {}
target:
entity_id: "{{ source }}"
alias: Выключить ТВ источник
- action: media_player.turn_on
metadata: {}
data: {}
target:
entity_id: "{{ target }}"
alias: Включить ТВ назначение
- wait_for_trigger:
- trigger: state
entity_id:
- media_player.lg_webos_kitchen
- media_player.lg_webos_bedroom
from: "off"
to: "on"
for:
hours: 0
minutes: 0
seconds: 2
timeout:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
alias: Ждать пока ТВ не включится
- action: media_player.volume_set
metadata: {}
data:
volume_level: 0
target:
entity_id: "{{ target }}"
alias: Убавить громкость до 0
- action: media_player.select_source
metadata: {}
data:
source: Plex
target:
entity_id: "{{ target }}"
alias: Включить источник Plex
- variables:
timestamp: "{{ (now().timestamp() | int) }}"
- alias: Сканировать клиентов Plex каждые 5 секунд, пока не найдется клиент
repeat:
sequence:
- delay:
hours: 0
minutes: 0
seconds: 5
milliseconds: 0
- action: button.press
metadata: {}
data: {}
target:
entity_id: button.media_scan_clients
while:
- condition: and
conditions:
- condition: template
value_template: "{{ (now().timestamp() | int) - timestamp < 60 }}"
- condition: not
conditions:
- condition: or
conditions:
- condition: state
entity_id: media_player.plex_plex_for_lg_lg_oled65c1rla
state: idle
- condition: state
entity_id: media_player.plex_plex_for_lg_lg_65up751c0zf
state: idle
enabled: true
- action: telegram_bot.send_message
metadata: {}
data:
message: |-
media_content: {{ media_content_id }}
media_position: {{ media_position }}
target: -1001108604669
enabled: false
- action: media_player.play_media
metadata: {}
data:
media_content_id: "{{ media_content_id }}"
media_content_type: movie
target:
entity_id: "{{ plextarget }}"
alias: Включить фильм на Plex
- alias: Ждать пока статус Plex не станет Playing
wait_for_trigger:
- trigger: state
entity_id:
- media_player.plex_plex_for_lg_lg_oled65c1rla
- media_player.plex_plex_for_lg_lg_65up751c0zf
to: playing
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
- action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ target }}"
alias: Поставить на паузу
- wait_for_trigger:
- trigger: state
entity_id:
- media_player.lg_webos_kitchen
- media_player.lg_webos_bedroom
to: paused
for:
hours: 0
minutes: 0
seconds: 1
timeout:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
alias: Ждать пока не станет на паузу
- action: media_player.media_seek
metadata: {}
data:
seek_position: "{{ media_position }}"
target:
entity_id: "{{ plextarget }}"
alias: Перемотать фильм в нужное место
- action: media_player.volume_set
metadata: {}
data:
volume_level: "{{ volume }}"
target:
entity_id: "{{ target }}"
alias: Установить уровень громкости как на источнике
description: ""
icon: mdi:movie
Буду рад вашим комментариям и советам по улучшению. Спасибо.
Автор: tmv002