Thursday, April 20, 2017

Our house has a finished basement with speakers built into the ceiling and walls. The speaker wires run to a common spot behind a mount for a flat-screen TV. I already own a Sonos Connect Amp and I wanted to use it to play the audio from the TV on the builtin speakers. Acoustically, everything is great, but adjusting the volume is less than ideal. Because the Connect Amp does not have an IR receiver, a universal remote is not able to change the volume. The only way to change the volume is to use the Sonos app on my smartphone or by reaching into the equipment cabinet and physically pressing the volume buttons on the Connect Amp (I know, I know, first world problems...). What I really wanted was to control the volume from the same remote that I was using to change channels.

My solution was to use a Raspberry Pi Zero W with an IR receiver attached to convert the signals from the TV remote to HTTP calls on my local network. The HTTP calls then control the Sonos Amp in the same way the smartphone app does.

I do realize Sonos makes a speaker specifcally for a TV, the Playbar, and it is controllable from an IR remote. But for my application, I already had the Connect Amp and I wanted to reuse the builtin speakers.

It took me few days of trial and error to get things working. There is a lot of online content for using the Raspberry Pi as an infrared receiver. Unfortunately, there is a lot of contradictory information in that content. I'm not a "linux guy", so I can't claim that I fully understand all of the linux commands required to make this work. BUT, my hope is to provide enough detail for others to recreate this project.

Software for this Project

The Hardware Part

Get out your soldering iron (or your jumper wires)! You will need to connect the IR Receiver module to the GPIO header of your Pi. There are only 3 connection, so this is super easy.

Raspberry Pi Zero W wired with IR Receiver

Raspberry Pi Setup

I used a Raspberry Pi Zero W because it was the smallest Pi and it had built in Wi-Fi. I haven't tried it, but I think a Raspberry Pi 2 or 3 would work as well. If you are going to attempt this with a version 2, you'll need a USB Wi-Fi adapter.

A note about Nano

Many times in these instruction I make use of a linux text editor called nano. Nano isn't pretty, but it gets the job done. After making edits with nano, you will save by hitting Control-X, they Y to the question about saving the buffer and finally Enter to overwrite the existing file. I'm mentioning this now so I can shorten the instructions.

Okay, let's get started: basic setup first

First get your Pi up and running with Noobs OS. This will involve connecting your Pi to a monitor with the micro HDMI adapter and cable, and connecting a mouse & keyboard to USB with the USB OTG hub. Follow this tutorial if you need help with that.

At this point you should be staring at the desktop of a running Raspberry Pi. If it's not already, get it connected to the internet. Config the Wi-Fi... or plug in the ethernet cable if you decide to go that route.

Open up a terminal window, enter this and wait while things get updated:

sudo apt-get update

Install Node.js

Steven de Salas has done a great job of making this a relativity painless procedure by creating some scripts specifically for installing Node.js on a Pi Zero. In the terminal type this (all as one line):

wget -O - https://raw.githubusercontent.com/sdesalas/node-pi-zero/master/install-node-v7.7.1.sh | bash
This may take a minute or two and don't worry about any unlink warning you may see.

Install the Sonos HTTP API

This is the service that will allow you to interact with the Sonos devices on your network. Kudos to the creator, Jimmy Shimizu, for making such a great piece of software!

wget http://github.com/jishi/node-sonos-http-api/archive/master.zip

unzip master.zip

You should now have a folder called node-sonos-http-api-master in your /home/pi directory. You can use the ls command to confirm.

Now get the Sonos HTTP API server up and running with all of the dependencies.

cd node-sonos-http-api-master

It will probably take a few minutes for this next step to finish.

npm install --production

npm start

The API should be running at this point. You can check this by getting the state of your Sonos speaker. Open up a second terminal and use the the following command. Replace <your speaker> with the name of your Sonos speaker (leaving out the brackets).

curl http://localhost:5005/<your speaker>/state

For example, my Connect Amp is named "Basement". Here is what it looks like:

As you can see, a bunch of details about the state of the Basement speaker is returned.

You can stop the HTTP API process my hitting Control-C in the terminal window where you typed npm start.

Install and Configure LIRC

LIRC is the software that decodes the IR signals. There is a lot of information out there about LIRC and it seems that it can do a lot. Unfortunately, all that information is very confusing for a noob who just wants to control his Sonos. This is the most complicated part of the entire project. Double check your work, as it is easy to make a typo.

Install LIRC

sudo apt-get install lirc

Assign the GPIO we are using

sudo nano /etc/modules

Add this text to the bottom of the text that is already there and exit/save:

lirc_devlirc_rpi gpio_in_pin=18

Set up the LIRC driver.

sudo nano /etc/lirc/hardware.confThe text that pops up will need to be edited. Here is the file, with the edits in red. Make the changes and exit/save.# /etc/lirc/hardware.conf## Arguments which will be used when launching lircdLIRCD_ARGS="--uinput"# Don't start lircmd even if there seems to be a good config file# START_LIRCMD=false# Don't start irexec, even if a good config file seems to exist.# START_IREXEC=false# Try to load appropriate kernel modulesLOAD_MODULES=true# Run "lircd --driver=help" for a list of supported drivers.DRIVER="default"# usually /dev/lirc0 is the correct setting for systems using udevDEVICE="/dev/lirc0"MODULES="lirc_rpi"# Default configuration files for your hardware if anyLIRCD_CONF=""LIRCMD_CONF=""

Next, edit some boot settings
sudo nano /boot/config.txt
Towards the end of the file, there is a line that looks like this:
#dtoverlay=lirc-rpi
change it to this:
dtoverlay=lirc-rpi,gpio_in_pin=18

Now restart your Raspberry Pi

The next few steps involve using your remote control. Because there is no actual IR codes for a Sonos Connect Amp, I decided to use the codes from a Sony Amplifier/Receiver instead. You don't have to use Sony, especially if it will interfere with some Sony hardware that you already have. The point is that I picked a well known brand that didn't do anything crazy with its IR codes. So, get your universal remote set up and ready.

Capture the IR codes from your remote. From a terminal, type the following command:

sudo /etc/init.d/lirc stop

sudo mv /etc/lirc/lircd.conf /etc/lirc/lircd_original.conf

sudo irrecord -d /dev/lirc0 ~/lircd.confThis will start up a utility to analyse your remote control's IR signal and capture the specific button codes. Follow the directions carefully. The first part of process is to press a bunch of buttons on the remote while aiming at the IR receiver. After it is happy with that it will want to record the specific button codes.

We'll be recording 3 different buttons: volume up, volume down and mute. When asked, use these names for the buttons:

KEY_VOLUMEUP KEY_VOLUMEDOWN KEY_MUTE

After using irrecord, we should have a file with codes for volume and mute.

Copy the file to where is needs to go

sudo cp ~/lircd.conf /etc/lirc/lircd.conf

This step is optional but I needed to do it to get my setup to work correctly. The remote I was using sent out IR codes in groups of twos. But I should have been single codes.

sudo nano /etc/lirc/lircd.confNow remove the # sign from the line #supress_repeat 2 so that it reads

supress_repeat 2

Restart LIRC

sudo /etc/init.d/lirc restart

Check to see if your Raspberry Pi is actually picking up the codes

sudo lircd -d /dev/lirc0

irw

Now point your remote at the Pi and press the volume and mute buttons. You should see some text pop up like this. (Hit Control-C to exit the irw tool)

Connecting button to actions

Now that LIRC is happily decoding IR signals, we need to make a file to execute commands when a signal is detected. In this case we'll be hitting the Sonos HTTP API.

Make the lirc command file:

nano ~/.lircrc

Add this text to the empty file and exit/save. Make sure to replace <your speaker> with the name if your Sonos device. Again, leave out the brackets.

begin

prog=irexec

button=KEY_VOLUMEUP

config=curl http://localhost:5005/<your speaker>/volume/+1

end

begin

prog=irexec

button=KEY_VOLUMEDOWN

config=curl http://localhost:5005/<your speaker>/volume/-1

end

begin

prog=irexec

button=KEY_MUTE

config=curl http://localhost:5005/<your speaker>/linein

end

begin

prog=irexec

button=KEY_MUTE

config=curl http://localhost:5005/<your speaker>/togglemute

end

Save and exit

Line-In Work-around

Most of the lircrc file should be easy to follow. There is a specific HTTP API call being associated with each button: volume up, volume down and mute. But what is the deal with the call to linein and the mute button?

Well, like every other Sonos owner, I use this device to play more than just TV audio. I use it for music just as often as I use it for TV. The problem is, if I last used it for music then I would need to use the app to set it back to line-in. My solution was to re-select line-in anytime the mute button with pressed. So if I turn on the TV and I hear no audio, I just hit the mute button once or twice and it start working.

A better solution would be to link the TV's power button code to the line-in selection. Unfortunately, I have a Samsung smart TV that uses an RF remote instead of infrared. The Raspberry Pi can't detect the RF signals. So I'm stuck with the mute button trick for now.

Getting everything to run at startup (almost there!)

Do get all this to work every time you power up the Pi, we need to edit a boot file.