Control your AV receiver with Raspotify

A couple of years ago I bought an Amazon Echo Dot which I connected to my AV receiver. That means I couldn't talk to Alexa - the Echo Dot's voice-controlled assistant - when my receiver was turned off. And neither could I use it's Spotify Connect feature in that case.

After some months I realized that I don't need a voice assistant. At least not one that barely understands my intends and sends all conversations to Amazon's cloud. In the meantime, I also bought a Google Home - but that is a different story (TL;DR: Not using it anymore as well).

So I did some research and found snips.ai which offers on-device voice recognition and skills. This sounded good to me so I bought the kit - and quickly realized here as well that I don't need a voice assistant (as long as it isn't as good as Samantha). But what I really like to have is the Spotify Connect feature - and that's how I found Raspotify.

Raspotify claims to be the "Spotify Connect client for the Raspberry Pi that Just Works™." ... and it's true. You just have to follow the few instructions to get it running on your Raspberry Pi and get your own Spotify Connect client.

The only issue with playing music from a Raspberry Pi is that the sound quality through the jack really sucks. At least I couldn't get it to sound nearly as good as the Echo Dot. After playing around with it for some time I realized I could just use the HDMI port instead and let the AV receiver to the amplifying work which improved the quality a lot back to the level as I had it before.

The magic of HDMI-CEC

In addition using an HDMI cable brought a lot of new possibilities mainly in the form of HDMI-CEC (aka Bravia Sync, Anynet+,...). In short, CEC allows you to control supporting devices through HDMI e.g. when I turn on my Apple TV the receiver turns on automatically. And exactly that feature would come in really handy for my music playback device as well I thought.

So I started to look a little bit deeper into how CEC works and what is possible with it. To get it running I installed "libcec" through homebrew which allows us to use "cec-client":

brew install libcec

A command gets send via CEC as in the following example ("h" is the command I'm sending):

echo h | cec-client -s -d 1

Which results in a list of possible options:

Available commands:

[tx] {bytes}              transfer bytes over the CEC line.
[txn] {bytes}             transfer bytes but don't wait for transmission ACK.
[on] {address}            power on the device with the given logical address.
[standby] {address}       put the device with the given address in standby mode.
[la] {logical address}    change the logical address of the CEC adapter.
[p] {device} {port}       change the HDMI port number of the CEC adapter.
[pa] {physical address}   change the physical address of the CEC adapter.
[as]                      make the CEC adapter the active source.
[is]                      mark the CEC adapter as inactive source.
[osd] {addr} {string}     set OSD message on the specified device.
[ver] {addr}              get the CEC version of the specified device.
[ven] {addr}              get the vendor ID of the specified device.
[lang] {addr}             get the menu language of the specified device.
[pow] {addr}              get the power status of the specified device.
[name] {addr}             get the OSD name of the specified device.
[poll] {addr}             poll the specified device.
[lad]                     lists active devices on the bus
[ad] {addr}               checks whether the specified device is active.
[at] {type}               checks whether the specified device type is active.
[sp] {addr}               makes the specified physical address active.
[spl] {addr}              makes the specified logical address active.
[volup]                   send a volume up command to the amp if present
[voldown]                 send a volume down command to the amp if present
[mute]                    send a mute/unmute command to the amp if present
[self]                    show the list of addresses controlled by libCEC
[scan]                    scan the CEC bus and display device info
[mon] {1|0}               enable or disable CEC bus monitoring.
[log] {1 - 31}            change the log level. see cectypes.h for values.
[ping]                    send a ping command to the CEC adapter.
[bl]                      to let the adapter enter the bootloader, to upgrade
                          the flash rom.
[r]                       reconnect to the CEC adapter.
[h] or [help]             show this help.
[q] or [quit]             to quit the CEC test client and switch off all
                          connected CEC devices.

The commands that are interesting for us are "on" and "as".

As described above the "on" command will turn on a device on a given address. It took me a couple of hours and lots of searching and testing to find out what is meant by "address".

The "address" is an internal address on the CEC bus that can be found by running the "scan" command:

echo 'scan' | cec-client -s -d 1

In my case the result looks something like this:

CEC bus information
===================
device #1: Recorder 1
address:       1.4.0.0
active source: no
vendor:        Pulse Eight
osd string:    CECTester
CEC version:   1.4
power status:  on
language:      eng


device #3: Tuner 1
address:       1.0.0.0
active source: no
vendor:        Sony
osd string:    AV AMP
CEC version:   1.4
power status:  on
language:      ???


device #4: Playback 1
address:       2.3.0.0
active source: no
vendor:        Unknown
osd string:    Apple TV
CEC version:   unknown
power status:  standby
language:      ???


device #5: Audio
address:       1.0.0.0
active source: no
vendor:        Sony
osd string:    AV AMP
CEC version:   1.4
power status:  on
language:      ???

"CECTester" is our Raspberry Pi. "AV AMP" is the device we were looking for. The address can be found in the "device #" part. In our case, it would be either 3 or 5.

The "as" command will turn the sending device into the "active source". That means that the receiver will automatically switch to our Raspberry when we send this command. So what we want to execute as soon as playback starts is the following:

#!/bin/bash
echo 'on 5'|cec-client -s -d 1
echo 'as'|cec-client -s -d 1

Now we just need to get Raspotify to execute this when playback starts. Luckily there is an "onevent" hook offered in its settings (which is a little bad documented). So what you have to do is change the Raspotify's settings file ("/etc/default/raspotify") so that the "OPTIONS" line looks somehow similar to this (if you have password and username set already in that line just append this option):

OPTIONS="--onevent /usr/local/scripts/onevent.sh"

The path in that line depends on where you store your bash script with the lines to turn on the receiver from above. In my case I placed it in "/usr/local/scripts/onevent.sh".

Make sure the script has the right permissions (it's going to be executed by the Raspotify user so that user should have the required permissions as well depending on your Raspberry setup) to be run.

Turn off your AV receiver, connect to your device in Spotify, play a track and see the magic happen

Further Improvements

One thing you could do to improve the script that gets executed a little bit is by checking which event is send and only turn on the receiver on the "start" event. "onevent" gets also triggered when playback ends and sends a "stop" event in that case. The event type will be available as "PLAYER_EVENT". Additionally, you can also access "TRACK_ID" and "OLD_TRACK_ID".

So how your file could look like is:

#!/bin/bash
if [ "$PLAYER_EVENT" = "start" ]; then
  echo 'on 5'|cec-client -s -d 1
  echo 'as'|cec-client -s -d 1
fi

If it's not working you can see most of the issues in the daemon log ("/var/log/daemon.log") by e.g. running:

tail -f  /var/log/daemon.log

Let me know if you got it working and what other things you connected to music start and stop events or if you have any questions.