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.
Hardware List
- Sonos Connect Amp
- Raspberry Pi Zero W and case
- USB Power Supply
- IR Receiver Module TSOP38238
- Universal Remote
- Micro SD card (I used an 16 GB card)
- Micro USB On-The-Go (OTG) Hub
- Micro HDMI to Standard HDMI Adapter
- Monitor, Keyboard & Mouse
Software for this Project
- Noobs Linux OS for Raspberry Pi (I used v2.4.0)
- LIRC
- Node.js
- Sonos HTTP API
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.
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!
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).
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.
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_dev
lirc_rpi gpio_in_pin=18 - 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 - Restart you Raspberry Pi
- Set up the LIRC driver.
sudo nano /etc/lirc/hardware.conf
This will open an empty file. Paste the following text into the editor and exit/save.
# /etc/lirc/hardware.conf
#
# Arguments which will be used when launching lircd
LIRCD_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 modules
LOAD_MODULES=true
# Run "lircd --driver=help" for a list of supported drivers.
DRIVER="default"
# usually /dev/lirc0 is the correct setting for systems using udev
DEVICE="/dev/lirc0"
MODULES="lirc_rpi"
# Default configuration files for your hardware if any
LIRCD_CONF=""
LIRCMD_CONF="" - Now, because of some major changes to the way LIRC 0.9.4 works, we need to convert our configuration to the new setup.
cd /usr/share/lirc
sudo ./lirc-old2new - Check if the IR receiver works.
cd
mode2 -d /dev/lirc0
Point your remote at the Pi and press some buttons. You should see a ton of timing data get printed. Hit control-C to exit mode2.
IR Code Capture
- 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 service lircd stop
sudo irrecord -d /dev/lirc0 ~/lircd.conf
This 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.
When asked for a file/remote name, you can use "sonos".
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.
- Move the file to where is needs to go
sudo mv sonos.lircd.conf /etc/lirc/lircd.conf.d/sonos.lircd.conf - Restart LIRC
sudo service lircd start
- Check to see if your Raspberry Pi is actually picking up the codes
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:
sudo nano ~/.config/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.beginprog=irexec
button=KEY_VOLUMEUP
config=curl http://localhost:5005/<your speaker>/volume/+1
endbeginprog=irexec
button=KEY_VOLUMEDOWN
config=curl http://localhost:5005/<your speaker>/volume/-1
endbeginprog=irexec
button=KEY_MUTE
config=curl http://localhost:5005/<your speaker>/linein
endbeginprog=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.
sudo nano /etc/rc.local
Add this text right before exit 0
su pi -c 'node /home/pi/node-sonos-http-api-master/server.js > /dev/null &'
sleep 2
sudo /usr/sbin/lircd -d /dev/lirc0 > /dev/null &
sleep 2
su pi -c 'irexec > /dev/null &'
Save and exit.
Because we'll be running LIRC on startup, we need to disable the service from running.
sudo systemctl disable irexec.service
Finally, restart the Raspberry Pi and you be able to control your Sonos from your remote!