Authentic Roku Remote and YouTube TV

So, starting with:
Authentic-looking Roku remote - Share your Projects! - Home Assistant Community (home-assistant.io)
Thanks to @NYZack for this awesome remote project.

Of course, this could probably also be added to @crdougn’s Firemote project too.
PRProd/HA-Firemote: Apple TV, Amazon Fire TV, Fire streaming stick, Chromecast, NVIDIA Shield, Roku, Xiaomi Mi, and Android TV remote control emulator for Home Assistant (github.com)

I have added to it specifically for YouTubeTV. The normal remote is very slow scrolling through the channels. And, this is specifically for our most watched channels. So, I made buttons directly linking to channels in YouTubeTV.

The buttons on the right are direct links to change channels in the YouTubeTv app on our Roku devices.

We also have a couple things that do not work on all of our devices. Volume only works on some TVs. And, not all remotes can be found.

So, a couple things to setup first.
Save images of the channels you want on your favorites. I saved them from YouTubeTV’s website. I saved them to /config/www/images/channels/

I named them with simple names removing the spaces. This will be important later and in another post.

Second, you need the app id of YouTubeTV from your Roku. That can be found at:
http://[YourRokuIP]::8060/query/apps

I also got the app id for IP Camera Viewer Pro used on the Cameras button.

And, last, you need the channel id from YouTubeTV. In the image below, right click on a show and copy the link.

You will want the part of the link between the last / and the ?:
https://tv.youtube.com/watch/AW1cFldgulo?vp=0gEEEgIwAQ%3D%3D
So: AW1cFldgulo

Then, take the name of the image along with the channel id and build the channels variable in the code below.

              {%- set channels = [
                ['abc','vQPizFnl33s'],
                ['amc','TMr6pjcOURI'],
                ['bbcamerica','AW1cFldgulo'],
                ] -%}

So, a decluttering card for the remote and the options I have added along with the channel buttons. Paste the below in the Raw Configuration Editor of an empty dashboard:

decluttering_templates:
  remote:
    card:
      type: picture-elements
      view_layout:
        grid-area: main
      elements:
        - type: service-button
          service: remote.send_command
          service_data:
            command: power
            entity_id: remote.[[roku_location]]
          style:
            top: 6%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: back
            entity_id: remote.[[roku_location]]
          style:
            top: 14%
            left: 31%
        - type: service-button
          service: remote.send_command
          service_data:
            command: home
            entity_id: remote.[[roku_location]]
          style:
            top: 14%
            left: 70%
        - type: service-button
          service: remote.send_command
          service_data:
            command: up
            entity_id: remote.[[roku_location]]
          style:
            top: 22%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: left
            entity_id: remote.[[roku_location]]
          style:
            top: 29%
            left: 24%
        - type: service-button
          service: remote.send_command
          service_data:
            command: select
            entity_id: remote.[[roku_location]]
          style:
            top: 29%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: right
            entity_id: remote.[[roku_location]]
          style:
            top: 29%
            left: 76%
        - type: service-button
          service: remote.send_command
          service_data:
            command: down
            entity_id: remote.[[roku_location]]
          style:
            top: 39%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: replay
            entity_id: remote.[[roku_location]]
          style:
            top: 46%
            left: 32%
        - type: service-button
          service: remote.send_command
          service_data:
            command: info
            entity_id: remote.[[roku_location]]
          style:
            top: 46%
            left: 70%
        - type: service-button
          service: remote.send_command
          service_data:
            command: reverse
            entity_id: remote.[[roku_location]]
          style:
            top: 53%
            left: 26%
        - type: service-button
          service: remote.send_command
          service_data:
            command: play
            entity_id: remote.[[roku_location]]
          style:
            top: 53%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: forward
            entity_id: remote.[[roku_location]]
          style:
            top: 53%
            left: 75%
        - type: service-button
          service: media_player.select_source
          service_data:
            source: YouTube TV
            entity_id: media_player.[[roku_location]]
          style:
            top: 64%
            left: 50%
        - type: service-button
          service: media_player.select_source
          service_data:
            source: MLB
            entity_id: media_player.[[roku_location]]
          style:
            top: 70%
            left: 50%
        - type: service-button
          service: media_player.select_source
          service_data:
            source: Plex - Free Movies & TV
            entity_id: media_player.[[roku_location]]
          style:
            top: 77%
            left: 50%
        - type: service-button
          service: media_player.select_source
          service_data:
            source: Prime Video
            entity_id: media_player.[[roku_location]]
          style:
            top: 84%
            left: 50%
      image: /local/images/rokunew.png?v=1
  options:
    default:
      - volume: 'no'
      - find_remote: 'no'
    card:
      type: vertical-stack
      cards:
        - type: custom:state-switch
          entity: '{% if ''yes'' == ''[[volume]]'' %} yes {% else %} no {% endif %}'
          states:
            'yes':
              type: horizontal-stack
              cards:
                - type: button
                  icon: mdi:volume-minus
                  icon_height: 20px
                  show_icon: true
                  name: Decrease
                  show_name: false
                  tap_action:
                    action: call-service
                    service: remote.send_command
                    data:
                      command: volume_down
                    target:
                      entity_id: remote.[[roku_location]]
                - type: button
                  icon: mdi:volume-plus
                  icon_height: 20px
                  show_icon: true
                  name: Increase
                  show_name: false
                  tap_action:
                    action: call-service
                    service: remote.send_command
                    data:
                      command: volume_up
                    target:
                      entity_id: remote.[[roku_location]]
                - type: button
                  icon: mdi:volume-mute
                  icon_height: 20px
                  show_icon: true
                  name: Mute
                  show_name: false
                  tap_action:
                    action: call-service
                    service: remote.send_command
                    data:
                      command: volume_mute
                    target:
                      entity_id: remote.[[roku_location]]
        - type: button
          show_icon: false
          name: Cameras
          show_name: true
          tap_action:
            action: call-service
            service: rest_command.[[roku_location]]_cameras
        - type: custom:state-switch
          entity: '{% if ''yes'' == ''[[find_remote]]'' %} yes {% else %} no {% endif %}'
          states:
            'yes':
              type: button
              show_icon: false
              name: Find Remote
              show_name: true
              tap_action:
                action: call-service
                service: remote.send_command
                target:
                  entity_id: remote.[[roku_location]]
                data:
                  command:
                    - find_remote
        - type: markdown
          content: >
            Current App <img src = '{{
            state_attr('media_player.[[roku_location]]', 'entity_picture') }}'
            height='60' width='150'></img>
          theme: no_border
        - type: custom:auto-entities
          card:
            type: custom:layout-card
            view_layout:
              grid-area: sidebar
            layout_type: custom:grid-layout
            layout:
              grid-template-columns: auto
              grid-template-rows: 1fr 1fr 1fr
          card_param: cards
          filter:
            template: |
              {%- set channels = [
                ['abc','vQPizFnl33s'],
                ['amc','TMr6pjcOURI'],
                ['bbcamerica','AW1cFldgulo'],
                ]-%}
              [
                {%- for (network, channel_id) in channels %}
                  {{
                    {"type": "custom:decluttering-card",
                     "template": "youtubetv_channel",
                     "variables":
                       [
                          {"roku_location": "[[roku_location]]"},
                          {"network": network},
                          {"channel_id": channel_id},
                       ]
                    }
                  }},
                {%- endfor -%}
              ]
  youtubetv_channel:
    card:
      type: custom:button-card
      card_size: 4
      show_entity_picture: true
      entity_picture: /local/images/channels/[[network]].webp
      tap_action:
        action: call-service
        service: media_player.play_media
        target:
          entity_id: media_player.[[roku_location]]
        data:
          media_content_id: 195316
          media_content_type: app
          extra:
            content_id: '[[channel_id]]'
            media_type: live
views:
  - title: Livingroom
    theme: Backend-selected
    type: custom:layout-card
    layout_type: custom:grid-layout
    layout:
      grid-template-columns: 230px 180px
      grid-template-rows: auto
      grid-template-areas: |
        "main sidebar"
    path: livingroom_remote
    subview: false
    badges: []
    cards:
      - type: custom:decluttering-card
        template: remote
        variables:
          - roku_location: livingroom
      - type: custom:decluttering-card
        template: options
        variables:
          - roku_location: livingroom
          - volume: 'yes'
          - find_remote: 'yes'
  - title: Basement
    theme: Backend-selected
    type: custom:layout-card
    layout_type: custom:grid-layout
    layout:
      grid-template-columns: 230px 180px
      grid-template-rows: auto
      grid-template-areas: |
        "main sidebar"
    path: basement_remote
    subview: false
    badges: []
    cards:
      - type: custom:decluttering-card
        template: remote
        variables:
          - roku_location: basement
      - type: custom:decluttering-card
        template: options
        variables:
          - roku_location: basement

At the bottom are multiple views. One for each Roku device along with the needed options and the name of the Roku device. For instance, I have one named livingroom that has volume and find remote. The second one is named basement and no volume or find remote.

The main issue with this is that Roku has what is called deep linking into apps. There are two ways to do it. The first is Launch which launches the app directly into a channel. The second is Input which just changes the channel. But, YouTubeTV only supports Launch. So, these buttons cause the Roku to restart the YouTubeTV app going into the channel.

This seems to annoy my wife and she would rather go slowly through the onscreen guide.

If this is confusing or I missed anything, please feel free to ask.

More to come in the near future.

1 Like

So, based on my EPG post here
EPG (Electronic Program Guide) … who wants it in HA? - Share your Projects! / Dashboards & Frontend - Home Assistant Community (home-assistant.io)

I have updated this to show the current and next show on the button that changes the channel to a YouTubeTV channel.

decluttering_templates:
  remote:
    card:
      type: picture-elements
      view_layout:
        grid-area: main
      elements:
        - type: service-button
          service: remote.send_command
          service_data:
            command: power
            entity_id: remote.[[roku_location]]
          style:
            top: 6%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: back
            entity_id: remote.[[roku_location]]
          style:
            top: 14%
            left: 31%
        - type: service-button
          service: remote.send_command
          service_data:
            command: home
            entity_id: remote.[[roku_location]]
          style:
            top: 14%
            left: 70%
        - type: service-button
          service: remote.send_command
          service_data:
            command: up
            entity_id: remote.[[roku_location]]
          style:
            top: 22%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: left
            entity_id: remote.[[roku_location]]
          style:
            top: 29%
            left: 24%
        - type: service-button
          service: remote.send_command
          service_data:
            command: select
            entity_id: remote.[[roku_location]]
          style:
            top: 29%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: right
            entity_id: remote.[[roku_location]]
          style:
            top: 29%
            left: 76%
        - type: service-button
          service: remote.send_command
          service_data:
            command: down
            entity_id: remote.[[roku_location]]
          style:
            top: 39%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: replay
            entity_id: remote.[[roku_location]]
          style:
            top: 46%
            left: 32%
        - type: service-button
          service: remote.send_command
          service_data:
            command: info
            entity_id: remote.[[roku_location]]
          style:
            top: 46%
            left: 70%
        - type: service-button
          service: remote.send_command
          service_data:
            command: reverse
            entity_id: remote.[[roku_location]]
          style:
            top: 53%
            left: 26%
        - type: service-button
          service: remote.send_command
          service_data:
            command: play
            entity_id: remote.[[roku_location]]
          style:
            top: 53%
            left: 50%
        - type: service-button
          service: remote.send_command
          service_data:
            command: forward
            entity_id: remote.[[roku_location]]
          style:
            top: 53%
            left: 75%
        - type: service-button
          service: media_player.select_source
          service_data:
            source: YouTube TV
            entity_id: media_player.[[roku_location]]
          style:
            top: 64%
            left: 50%
        - type: service-button
          service: media_player.select_source
          service_data:
            source: MLB
            entity_id: media_player.[[roku_location]]
          style:
            top: 70%
            left: 50%
        - type: service-button
          service: media_player.select_source
          service_data:
            source: Plex - Free Movies & TV
            entity_id: media_player.[[roku_location]]
          style:
            top: 77%
            left: 50%
        - type: service-button
          service: media_player.select_source
          service_data:
            source: Prime Video
            entity_id: media_player.[[roku_location]]
          style:
            top: 84%
            left: 50%
      image: /local/images/rokunew.png?v=1
  options:
    default:
      - volume: 'no'
      - find_remote: 'no'
    card:
      type: vertical-stack
      cards:
        - type: custom:state-switch
          entity: '{% if ''yes'' == ''[[volume]]'' %} yes {% else %} no {% endif %}'
          states:
            'yes':
              type: horizontal-stack
              cards:
                - type: button
                  icon: mdi:volume-minus
                  icon_height: 20px
                  show_icon: true
                  name: Decrease
                  show_name: false
                  tap_action:
                    action: call-service
                    service: remote.send_command
                    data:
                      command: volume_down
                    target:
                      entity_id: remote.[[roku_location]]
                - type: button
                  icon: mdi:volume-plus
                  icon_height: 20px
                  show_icon: true
                  name: Increase
                  show_name: false
                  tap_action:
                    action: call-service
                    service: remote.send_command
                    data:
                      command: volume_up
                    target:
                      entity_id: remote.[[roku_location]]
                - type: button
                  icon: mdi:volume-mute
                  icon_height: 20px
                  show_icon: true
                  name: Mute
                  show_name: false
                  tap_action:
                    action: call-service
                    service: remote.send_command
                    data:
                      command: volume_mute
                    target:
                      entity_id: remote.[[roku_location]]
        - type: button
          show_icon: false
          name: Cameras
          show_name: true
          tap_action:
            action: call-service
            service: rest_command.[[roku_location]]_cameras
        - type: custom:state-switch
          entity: '{% if ''yes'' == ''[[find_remote]]'' %} yes {% else %} no {% endif %}'
          states:
            'yes':
              type: button
              show_icon: false
              name: Find Remote
              show_name: true
              tap_action:
                action: call-service
                service: remote.send_command
                target:
                  entity_id: remote.[[roku_location]]
                data:
                  command:
                    - find_remote
        - type: markdown
          content: >
            Current App <img src = '{{
            state_attr('media_player.[[roku_location]]', 'entity_picture') }}'
            height='60' width='150'></img>
          theme: no_border
        - type: custom:auto-entities
          card:
            type: custom:layout-card
            view_layout:
              grid-area: sidebar
            layout_type: custom:grid-layout
            layout:
              grid-template-columns: auto
              grid-template-rows: 1fr 1fr 1fr
          card_param: cards
          filter:
            template: >
              {%- set ns = namespace(buttons=[]) -%}
              {%- for channel in states.sensor.epg_info.attributes.programs | sort(attribute='channel_name') -%}
                {%- set program = channel.programs | selectattr('stop', 'gt', as_timestamp(now())) | list -%}
                {%- set ns.buttons = ns.buttons + [{"channel": channel.channel_name | lower() | replace(' ', ''), "current_program": program[0].title, "current_start": program[0].start | timestamp_custom('%-I:%M%p'), "next_program": program[1].title, "next_start": program[1].start | timestamp_custom('%-I:%M%p')}] -%}
              {%- endfor -%}
              {%- set channels = [
                ['abc','vQPizFnl33s'],
                ['amc','TMr6pjcOURI'],
                ['bbcamerica','AW1cFldgulo'],
              ] -%}
                {%- for (network, channel_id) in channels %}
                {%- set this_channel = ns.buttons | selectattr('channel', 'eq', network) | list | first -%}
                {%- if this_channel is defined -%}
                  {{
                    {"type": "custom:decluttering-card",
                     "template": "youtubetv_channel",
                     "variables":
                       [
                          {"roku_location": "[[roku_location]]"},
                          {"network": network},
                          {"channel_id": channel_id},
                          {"current_start": this_channel.current_start},
                          {"current_program": this_channel.current_program},
                          {"next_start": this_channel.next_start},
                          {"next_program": this_channel.next_program},
                       ]
                    }
                  }},
                {%- else -%}
                  {{
                    {"type": "custom:decluttering-card",
                     "template": "youtubetv_channel",
                     "variables":
                       [
                          {"roku_location": "[[roku_location]]"},
                          {"network": network},
                          {"channel_id": channel_id},
                       ]
                    }
                  }},
                {%- endif -%}
              {%- endfor -%}
  youtubetv_channel:
    default:
      - current_start: ''
      - current_program: ''
      - next_start: ''
      - next_program: ''
    card:
      type: custom:button-card
      card_size: 4
      styles:
        name:
          - justify-self: start
          - padding-left: 5px
          - font-size: 10px
          - font-weight: bold
        label:
          - justify-self: start
          - padding-left: 5px
          - font-size: 10px
          - font-weight: bold
      show_entity_picture: true
      entity_picture: /local/images/channels/[[network]].webp
      show_name: true
      name: '[[current_start]] [[current_program]]'
      show_label: true
      label: '[[next_start]] [[next_program]]'
      tap_action:
        action: call-service
        service: media_player.play_media
        target:
          entity_id: media_player.[[roku_location]]
        data:
          media_content_id: 195316
          media_content_type: app
          extra:
            content_id: '[[channel_id]]'
            media_type: live
views:
  - title: Livingroom
    theme: Backend-selected
    type: custom:layout-card
    layout_type: custom:grid-layout
    layout:
      grid-template-columns: 230px 180px
      grid-template-rows: auto
      grid-template-areas: |
        "main sidebar"
    path: livingroom_remote
    subview: false
    badges: []
    cards:
      - type: custom:decluttering-card
        template: remote
        variables:
          - roku_location: livingroom
      - type: custom:decluttering-card
        template: options
        variables:
          - roku_location: livingroom
          - volume: 'yes'
          - find_remote: 'yes'
  - title: Basement
    theme: Backend-selected
    type: custom:layout-card
    layout_type: custom:grid-layout
    layout:
      grid-template-columns: 230px 180px
      grid-template-rows: auto
      grid-template-areas: |
        "main sidebar"
    path: basement_remote
    subview: false
    badges: []
    cards:
      - type: custom:decluttering-card
        template: remote
        variables:
          - roku_location: basement
      - type: custom:decluttering-card
        template: options
        variables:
          - roku_location: basement

And, it looks like

The code above is missing the EPG coding even though the picture shows it. I wanted to leave all of that code and questions in the other thread.