В этой статье я расскажу про написание скрипта HA, который продолжает воспроизведение видео запущенного с Plex на другом устройстве: выключает источник, включает устройство назначения, включает Plex, включает текущее видео, перематывает видео в нужную позицию, устанавливает необходимую громкость.
Написание скрипта и тестирование заняло больше времени, чем сэкономит его использование, тем более Plex и так сохраняет текущую позицию, но скрипт писался скорее в исследовательских целях. Возможно вы сможете подчерпнуть полезные решения, которые были использованы в нем, а может подскажете мне в комментариях как что-то можно было сделать проще.
В статье будет рассмотрено использование переменных (variables), триггеров (wait triggers), циклов (loops), условий (conditions), шаблонов значений (value templates).
А теперь давайте посмотрим каждое действие по отдельности.
1. Определение переменных target, source, plextarget и volume
Эти переменные будут использоваться в скрипте:
target — телевизор назначение
source — телевизор источник
plextarget — медиаплеер Plex на ТВ нахначении. Каждый клиент plex регистрируется в Home Assistant как отдельный device — media_player. Управление контентом и позицией возможно только через него, так как сам телевизор знает только какой запущен источник (hdmi или приложение) и статус (idle, playing, paused) и не понимает, что конкретно на нем проигрывается и какая текущая позиция.
volume — текущая громкость на источнике. Так как в фильмах используется разный уровень аудиодорожек, то не хочется руками регулировать громкость на ТВ назначении, а тем более чтобы он заорал на всю квартиру, если там был установлен высокий уровень.
Сначала нужно определить откуда и куда необходимо перенести просмотр фильма. Так как я автоматизировал только для сценария перехода между двумя комнатами (кухней и спальней), то предполагаем что один ТВ в момент запуска скрипта включен, другой выключен (можно реализовать и более сложные проверки, например, что включен источник plex, телевизор в статусе playing и т.д.).
Соответственно если ТВ спальня включен, то target = ТВ кухня, иначе target = ТВ спальня. Аналогично поступаем с source, plextarget и volume.
Для получения значения entity необходимо воспользоваться states, параметром в который передается entity, а states возвращает его значение (статус).
Код
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') }}
2. Поставить Plex на паузу
Придется немного забежать вперед, чтобы объяснить зачем ставить Plex на паузу на источнике. Как выяснилось, Plex обновляет атрибут media_position с большим интервалом, а значит узнать точную текущую позицию нельзя, но оказалось, что позиция обновляется при постановке на паузу.
Код
action: media_player.media_pause metadata: {} data: {} target: entity_id: "{{ source }}" alias: Поставить Plex на паузу, чтобы обновилась media_position
3. Дождаться пока 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
4. Определить переменные media_content_id и media_position
В интерфейсе HA идем в Developer tools\States, находим media_player 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') }}
5. Выключить ТВ источник
Код
action: media_player.turn_off metadata: {} data: {} target: entity_id: "{{ source }}" alias: Выключить ТВ источник
6. Включить ТВ назначение
Код
action: media_player.turn_on metadata: {} data: {} target: entity_id: "{{ target }}" alias: Включить ТВ назначение
7. Ждать пока ТВ не включится
Код
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: Ждать пока ТВ не включится
8. Убавить звук до 0
Код
action: media_player.volume_set metadata: {} data: volume_level: 0 target: entity_id: "{{ target }}" alias: Убавить громкость до 0
9. Запустить приложение Plex на ТВ
Код
action: media_player.select_source metadata: {} data: source: Plex target: entity_id: "{{ target }}" alias: Включить источник Plex
10. Определить переменную timestamp
Получаем ее с помощью now().timestamp() и преобразовываем в int. Объясню ее назначение в следующем пункте.
Код
variables: timestamp: "{{ (now().timestamp() | int) }}"
11. Сканировать клиентов Plex
Как выяснилось, media_player в HA остается очень долгое время в статусе unavailable после запуска Plex на ТВ. В документации на интеграцию не нашел никакой информации, что с этим можно было бы сделать. Но если нет документации, то идем методом научного тыка. Интеграция Plex кроме медиаплееров создает еще одно устройство с несколькими сенсорами и одной кнопкой «Scan clients». Пробуем ее нажимать и видим, что это существенно ускоряет переход клиента в статус idle (запущен, но ничего не проигрывается). В этом статусе можно управлять клиентом.
Действие выглядит предельно простым: нажимать scan clients каждые 5 секунд, пока один из медиаплееров (кухня, спальня) не перейдет в состояние idle. Но чтобы скрипт не завис, хочется ожидание статуса idle ограничить одной минутой. Иначе я устану ждать включу фильм сам и вдруг в какой-то момент скрипт начнет управлять медиаплеером, когда это уже не нужно.
Казалось бы элементарная задача, объявить переменную, после каждой итерации делать инкремент и на 12 раз (цикл длится 5 секунд) остановить цикл, однако в HA область действия переменных не позволяет это сделать. Можно почитать подробнее здесь.
Поэтому воспользуемся созданной в предыдущем пункте переменной 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
12. Включить фильм на 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
13. Ждать пока статус 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
14. Поставить на паузу
Код
action: media_player.media_pause metadata: {} data: {} target: entity_id: "{{ target }}" alias: Поставить на паузу
15. Ждать пока не станет на паузу
Код
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: Ждать пока не станет на паузу
16. Перемотать фильм
Воспользуемся переменной media_position чтобы перемотать фильм в нужное место
Код
action: media_player.media_seek metadata: {} data: seek_position: "{{ media_position }}" target: entity_id: "{{ plextarget }}" alias: Перемотать фильм в нужное место
17. Установить уровень громкости
Воспользуемся переменной 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
Буду рад вашим комментариям и советам по улучшению. Спасибо.
ссылка на оригинал статьи https://habr.com/ru/articles/871862/
Добавить комментарий