Yamaha receiver - a better media player interface!

First things first - my apologies for the length of the post. But, if you have a Yamaha receiver, and have been searching for a better media player, I promise it will be worth it. Please do read until the end!

So, before I get into the nitty gritty of it, I feel the need to provide a bit of background:

I’m still relatively new to the HA ecosystem, having only gotten into this about 6 months ago. However, I have a very technical background, so I’m not afraid to dive in and figure out how to make things work the way I want them to work. That said - I’m not a programmer by trade, so while my solutions work, they may or may not be the most efficient or the most eloquent. I am doing this for functionality, not to flex my programming prowess for all the masses to critique.

The reason this particular setup came to be is that I started, like everyone else, I’m sure, by just using the default media player in HA. I quickly came to realize that this was somewhat limited - first, in terms of the volume control (so I wrote my own for that (was a separate post/project, but is also included here since it’s easier than trying to filter it out)), and then I wasn’t happy with the way the default media player handled source selection. I was further dissatisfied with the fact that when using the tuner, I was limited to presets, and there was no way to simply punch in the radio station I wanted to listen to. So I set about to change that.

Some of my requirements: It needed to be easy enough to use that my wife, or a guest, could look at the interface and figure it out without any coaching or instruction - that was first and foremost. After that, I wanted to be able to click a button and choose Plex, or Tuner, or to sync that channel/zone with the main channel/zone on that receiver (I have 3 receivers with a total of 6 zones for music). Further, I wanted the ability to sync the three receivers with one another, so that one source will play through the entire house (useful for parties). I wanted the volume control to allow me to adjust the volume above 0dB (which the built-in media player cannot do), and I wanted the ability to type in a radio station to change the tuner to that station (something the built-in media player also cannot do). Lastly, in the interest of a clean interface, I wanted these controls to only be visible when they were needed - meaning that you shouldn’t be able to adjust the source or the volume on a channel that isn’t even powered on, and you shouldn’t be able to adjust the tuner when the selected source was not the tuner. Further - when the amp for that zone was powered off, none of the controls should be visible at all.

This is what it looks like when the media player is off:

off

This is what it looks like when it’s turned on, and using any source other than the tuner:

plex

This is what it looks like when the selected source is the tuner:

Some of this was relatively easily accomplished with some programming (of varying degrees of difficulty), and some of this was accomplished using hardware to link everything together and provide the necessary functionality.

To that end, here is my hardware list, and how everything is connected:

My receivers are Yamaha TSR-700s, which are the Costco version of the RX-V6A. (Note that I have also tested all of this on an RX-V681, which is like 6-7 years old, and it worked fine - so I imagine this would all work fine on any network-connected model, although I have no way of verifying directly on anything other than the two models listed above.) Each receiver has 2 Chromecast devices connected to it - one on HDMI1, and one using an HDMI to RCA adapter from Amazon that is then connected to Audio3. Then, the HDMI out from the main receiver goes into a 1:4 HDMI splitter. Each of the other two receivers have an HDMI cable coming into HDMI2 to receive the signal for the whole house setup, and then there are two more HDMI to RCA adapters employed that go into Audio4 on each of the other receivers to provide that signal to Zone 2. The use case for these is, admittedly, limited - however, I wanted the ability to be able to sync each zone independently to the primary source, in the unlikely event that someone wished to play something else on the primary zone of that receiver. This likely could have been avoided with more careful planning about which speaker pairs were the main zone and which were zone 2 - but this way does provide some additional flexibility - albeit a fringe use case. For those of you wondering what’s with all the RCA adapters - Zone 2 does not allow the selection of HDMI inputs as sources, at least not on any of the receivers I have. I suspect this is because they only have a single HDMI out - but if you have a more expensive receiver with multiple HDMI outs, you may be able to avoid all this extra nonsense.

I’m not going to provide the config for all three receivers and all 6 of the media player cards, nor am I going to go into a whole bunch of detail around the Plex setup (there are plenty of good resources on that already, and frankly - I’m far from an expert on that topic!). The media player card I am providing is the card from Zone 2 of one of the “slave” receivers, since Zone 2 has the most complexity - it can be sync’d to either the main zone on that receiver, or it can be sync’d to the main receiver channel. The main zone on the “slave” receiver config is the same, except it lacks the button for Sync to Main Zone, since it is the main zone. The “master” receiver in the chain, the Main Zone has only two source options - Plex and Tuner - since there is nothing else for it to sync to. Zone 2 on the “master” receiver only has the “Sync to Main Zone” option, since that has the same net effect as syncing any channel on the other 2 receivers to the HDMI output from the “master” receiver.

One other thing to note: Some of the cards and interface bits that were used are custom - and they all came from HACS. Accordingly, if you see a card type that begins with “custom:”, and you don’t have that card type yet, HACS is where to get it from. Good? Good. Let’s proceed!

Media Player card:
(Note that this card relies on 4 input_booleans - laundryplex, laundrytuner, laundrysync, and layndrypartysync. These 4 booleans control the display color of the 4 shortcut buttons for the 4 various sources that are directly selectable via the interface to provide feedback to the user for which source is presently selected. Also required is an input_text, in this case named laundryradio)

type: vertical-stack
cards:
  - type: media-control
    entity: media_player.floor2_zone2
  - type: conditional
    conditions:
      - entity: media_player.floor2_zone2
        state: playing
    card:
      type: vertical-stack
      cards:
        - type: horizontal-stack
          cards:
            - type: custom:button-card
              entity: input_boolean.laundryplex
              icon: mdi:plex
              icon_height: 50px
              styles:
                card:
                  - height: 100px
                icon:
                  - color: |
                      [[[
                        if (entity.state == 'on') return 'lime';
                        return 'grey'
                      ]]]
              tap_action:
                action: call-service
                service: media_player.select_source
                service_data:
                  entity_id: media_player.floor2_zone2
                  source: audio3
              name: plex
            - type: custom:button-card
              entity: input_boolean.laundrytuner
              icon: mdi:radio
              icon_height: 50px
              styles:
                card:
                  - height: 100px
                icon:
                  - color: |
                      [[[
                        if (entity.state == 'on') return 'lime';
                        return 'grey'
                      ]]]
              tap_action:
                action: call-service
                service: media_player.select_source
                service_data:
                  entity_id: media_player.floor2_zone2
                  source: tuner
              name: radio
            - type: custom:button-card
              entity: input_boolean.laundrysync
              icon: mdi:party-popper
              icon_height: 75px
              styles:
                card:
                  - height: 100px
                icon:
                  - color: |
                      [[[
                        if (entity.state == 'on') return 'lime';
                        return 'grey'
                      ]]]
              tap_action:
                action: call-service
                service: media_player.select_source
                service_data:
                  entity_id: media_player.floor2_zone2
                  source: main_sync
              name: Master BR
            - type: custom:button-card
              entity: input_boolean.laundrypartysync
              icon: mdi:party-popper
              icon_height: 75px
              styles:
                card:
                  - height: 100px
                icon:
                  - color: |
                      [[[
                        if (entity.state == 'on') return 'lime';
                        return 'grey'
                      ]]]
              tap_action:
                action: call-service
                service: media_player.select_source
                service_data:
                  entity_id: media_player.floor2_zone2
                  source: audio4
              name: Party
        - type: entities
          entities:
            - type: custom:slider-entity-row
              entity: number.tone_control_bass_5
              name: Bass
            - type: custom:slider-entity-row
              entity: number.tone_control_treble_5
              name: Treble
            - type: section
              label: Volume dB
            - type: custom:slider-entity-row
              entity: input_number.laundry_volume
              full_row: true
        - type: conditional
          conditions:
            - entity: input_boolean.laundrytuner
              state: 'on'
          card:
            type: horizontal-stack
            cards:
              - entity: input_text.laundryradio
                type: custom:text-input-row
                name: WARNING - also changes Master BR radio
              - entity: script.change_station_laundry
                name: Send
                icon: mdi:send
                show_name: false
                show_icon: true
                size: 35%
                styles:
                  card:
                    - height: 60px
                    - width: 60px
                type: custom:button-card
                tap_action:
                  action: call-service
                  service: script.change_station_laundry
                hold_action:
                  haptic: success
                  action: call-service
                  service: script.change_station_laundry

Input slider:

  laundry_volume:
    icon: mdi:volume-medium
    name: Laundry Volume
    initial: -35
    min: -80
    max: 8
    step: 1
    mode: slider

Change station laundry script (HA script, not shell script - goes in scripts.yaml):

change_station_laundry:
  alias: change_station_laundry
  sequence:
  - service: shell_command.yamaha_tuner
    data:
      ip: IP.OF.YOUR.RECEIVER
      station: '{{ states(''input_text.laundryradio'') }}'
  mode: single
  icon: mdi:send

Automations:
(Note that in the last automation in here, the one that sets the volume - the possible values for zone are Main_Zone and Zone_2)

- id: '1643111111114'
  alias: Set laundryplex
  description: ''
  trigger:
  - platform: state
    entity_id: media_player.floor2_zone2
    to: playing
    from: 'off'
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: audio3
    from: tuner
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: audio3
    from: main_sync
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: audio3
    from: audio4
  condition:
  - condition: state
    entity_id: media_player.floor2_zone2
    attribute: source
    state: audio3
  action:
  - service: input_boolean.turn_on
    target:
      entity_id:
      - input_boolean.laundryplex
    data: {}
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.laundrysync
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.laundrytuner
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.laundrypartysync
  mode: single
- id: '1643111111115'
  alias: Set laundrytuner
  description: ''
  trigger:
  - platform: state
    entity_id: media_player.floor2_zone2
    to: playing
    from: 'off'
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: tuner
    from: audio3
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: tuner
    from: main_sync
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: tuner
    from: audio4
  condition:
  - condition: state
    entity_id: media_player.floor2_zone2
    attribute: source
    state: tuner
  action:
  - service: input_boolean.turn_off
    target:
      entity_id:
      - input_boolean.laundryplex
    data: {}
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.laundrypartysync
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.laundrysync
  - service: input_boolean.turn_on
    target:
      entity_id:
      - input_boolean.laundrytuner
    data: {}
  mode: single
- id: '1643111111116'
  alias: Set laundrysync
  description: ''
  trigger:
  - platform: state
    entity_id: media_player.floor2_zone2
    to: playing
    from: 'off'
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: main_sync
    from: audio3
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: main_sync
    from: tuner
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: main_sync
    from: audio4
  condition:
  - condition: state
    entity_id: media_player.floor2_zone2
    attribute: source
    state: main_sync
  action:
  - service: input_boolean.turn_off
    target:
      entity_id:
      - input_boolean.laundryplex
    data: {}
  - service: input_boolean.turn_on
    target:
      entity_id:
      - input_boolean.laundrysync
    data: {}
  - service: input_boolean.turn_off
    target:
      entity_id:
      - input_boolean.laundrytuner
    data: {}
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.laundrypartysync
  mode: single
- id: '1643111111117'
  alias: Set laundrypartysync
  description: ''
  trigger:
  - platform: state
    entity_id: media_player.floor2_zone2
    to: playing
    from: 'off'
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: audio4
    from: audio3
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: audio4
    from: tuner
  - platform: state
    entity_id: media_player.floor2_zone2
    attribute: source
    to: audio4
    from: main_sync
  condition:
  - condition: state
    entity_id: media_player.floor2_zone2
    attribute: source
    state: audio4
  action:
  - service: input_boolean.turn_off
    target:
      entity_id:
      - input_boolean.laundryplex
    data: {}
  - service: input_boolean.turn_off
    target:
      entity_id:
      - input_boolean.laundrysync
    data: {}
  - service: input_boolean.turn_off
    target:
      entity_id:
      - input_boolean.laundrytuner
    data: {}
  - service: input_boolean.turn_on
    data: {}
    target:
      entity_id: input_boolean.laundrypartysync
  mode: single
- id: '1643111111118'
  alias: Set volume Laundry
  description: ''
  trigger:
  - platform: state
    entity_id: input_number.laundry_volume
  condition:
  - condition: state
    entity_id: media_player.floor2_zone2
    state: playing
  action:
  - service: shell_command.yamaha_vol
    data:
      ip: IP.OF.YOUR.RECEIVER
      zone: Zone_2
      vol: '{{ (trigger.to_state.state) | int * 10 }}

        '
  mode: single

yamahatuner.sh (goes in /config/scripts):
(Note the line that begins “ent=”. This line strips the . out of the input string so that you can enter stations like “103.3”, even though the receiver is expecting “103300” (the following line multiplies it by 100 to format it correctly).)

#!/bin/sh
curl_cmd="curl --silent --output /dev/null"

tune() {
    foo=${2}
    ent="${foo//./}"
    stat=$((${ent} * 100))
    ${curl_cmd} http://${1}/YamahaExtendedControl/v1/tuner/setFreq?band=fm\&tuning=direct\&num=${stat}

}

"$@"

yamahavolume.sh (goes in /config/scripts):

#!/bin/sh
curl_cmd="curl --silent --output /dev/null"

volume() {


    ${curl_cmd} http://${1}/YamahaRemoteControl/ctrl --data-binary "'<YAMAHA_AV cmd=\"PUT\"><${2}><Volume><Lvl><Val>${3}</Val><Exp>1</Exp><Unit>dB</Unit></Lvl></Volume></${2}></YAMAHA_AV>'"

}

"$@"

config.yaml shell_command definitions:

shell_command:
    yamaha_tuner: /bin/bash /config/scripts/yamahatuner.sh tune {{ ip }} {{ station }}
    yamaha_vol: /bin/bash /config/scripts/yamahavolume.sh volume {{ ip }} {{ zone }} {{ vol }}
2 Likes

Hi, tried to implement this. Looks like it is looking for a service called select_source. Did you forget to include it?

media_player.select_source is a standard Home Assistant service call available on all mediaplayers that have sources, so it should just work.

1 Like