I wanted to get a Creative Labs Soundblaster X-Fi Surround 5.1 Pro USB Sound Card (part number SB-1095 with remote RM-820) working with one Raspberry Pi that is set-up as a Squeezebox music server (LMS) and player (Squeezelite) and a different RPi working with the IQaudIO DAC and a generic Philips remote control.
Getting the sound working was pretty straight-forward but I also wanted to make use of the infra-red remote controls to at least control the volume and ideally do much more.
Why not simply use the web interface or a smartphone or tablet or a real Squeezebox to control it?
Well, they can be used of course and are needed to select what to play but leaving the simple remote near the player makes it very easy for someone to pause the music when the phone rings, skip a track or pump up the volume.
Getting it to work required pulling a few things together and then doing some custom scripting.
Most of this is not Raspberry Pi specific - it should work on nearly any Linux system. The exception is the installation of the IR receiver for the Philips (and in theory any other regular remote).
Note: there is no need to install an IR receiver for the Soundblaster because there is one built-in to the device and for this particular device this also works with the rotary control that is on top of the device (twist for volume and push for mute/pause).
Here is what it looks like. This is the RPI with the IQaudIO DAC installed and the lid removed from the IQaudIO case. The DAC is attached to the RPi in the top right.
Attached to that is the infra-red receiver - with the "eye" attached (using blue sticky stuff) so that a signal can get through the holes along the top edge of the IQaudIO case (you cannot see them),
Here is what it looks like. This is the RPI with the IQaudIO DAC installed and the lid removed from the IQaudIO case. The DAC is attached to the RPi in the top right.
Attached to that is the infra-red receiver - with the "eye" attached (using blue sticky stuff) so that a signal can get through the holes along the top edge of the IQaudIO case (you cannot see them),
So here is how I did it.
I decided to use the "expect" application to drive LMS because it has can send data in "telnet" style, as made available through the LMS CLI, and then take actions using a simple built-in scripting language based on the reply.
Text below that is in monospaced font and/or red shows what appears on the console or needs to be typed.
The full "expect" script is below. Some key points from it are:
- This assumes that you have already installed Squeezelite and it has access to LMS either locally on same system or somewhere remote that can be accessed through the network (for example by installing SqueezePlug on RPi)
- "expect" and possibly "telnet" might not be installed on your system. In which case you will have to install them yourself. On Debian-based systems (like Raspbian for Raspberry Pi) you can do this with
sudo apt-get install expect telnet - If you are going to force actions via infra-red (IR) remote control then you will also need to install "lirc"
sudo apt-get install lirc - You might not have the ALSA sound utilities installed. They are used to control some of the sound settings ... to install them you would do
sudo apt-get install alsa-utils - Settings (such as where is the LMS server relative to this controlling client) are included in the file but they can also come from the settings file for Squeezelite ... in which case you probably would not have to edit this file at all.
- Lines that start with "# " are comments. Some of them have some tracing statements to help if things are not working as expected. Simply remove the "#" from the front
e.g.
# puts "settings: host:\"$params(SBSHOST)\" and player MAC:\"$params(SLMAC)\""
becomes
puts "settings: host:\"$params(SBSHOST)\" and player MAC:\"$params(SLMAC)\""
In this case this will output (puts = put string on console) the settings that were found - There are some special functions in there - such as treating the OK button as a special case (to restore things to a default configuration with middle volume, mute off, power on, shuffle off, repeat off) but they should be easy to understand simply by reading the script
- Take care with line wrapping when copying the file below. It is up to you to work out where the blogging software has wrapped lines that should not have been wrapped. If you cannot do that then contact me directly and ask for a copy of the file to be sent by email to you
- I stored this file as lmscli.exp in /home/pi/lmscli and performed a chmod on it
i.e. I logged in as "pi" then
mkdir lmscli
cd lmscli
(then copy the file and save as lmscli.exp)
chmod +x lmscli.exp
You can test this by hand like this:
./lmscli.exp pause
You should see something like this if it worked (and any music that was playing via your local Squeezelite should have then paused or resumed)
spawn telnet 127.0.0.1 9090
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
login user pass b8:27:eb:aa:bb:cc
pause
exit
login user ******
Connection closed by foreign host.
Note: the output above is slightly out of sequence but do not worry about it if it worked
Here is the lmscli.exp script (between but not including the separator lines):
------------------------------------------------------------------------------
#!/usr/bin/expect -f
# lmscli "Expect" script
# Author: Paul Webster
# Version: 0.1
# Date: 29-Mar-2014
##
# Simple script (with no error checking) to send Logitech Media Server (SqueezeCenter) CLI commands to control a player
# Attempts to read parameters from Squeezelite defaults file - where syntax is
# keyword="value"
#
# "Expect" and telnet are required
# sudo apt-get install expect telnet
# Also - if this is to be driven via infrared remote (expected use) then also install lirc
# sudo apt-get install lirc
#
set settingsfile /etc/default/squeezelite
# Defaults
# Values here are overridden by values found in /etc/default/squeezelite (if present)
# SBSHOST = ip address of the LMS/Squeezebox Server
# SBSPORT = port number on LMS that cli runs on (rarely changed)
# SLMAC = the MAC address claimed by the player to be controlled - not always the real MAC address and is specified to Squeezelite
# SBSUSER / SBSPASS = username and password to access LMS. Usually not configured - in which case leave as is
# - if used then make sure that the user/pass fields are URL-encoded (e.g. %20 for space)
array set params {
SBSHOST 127.0.0.1
SBSPORT 9090
SLMAC 00:11:22:33:44:55:66
SBSUSER user
SBSPASS pass
}
# Override the defaults that are in this script with the ones from the Squeezelite settings
if {[file exists "$settingsfile"]} {
set fp [open "$settingsfile" r]
set file_data [read $fp]
close $fp
# get lines
set data [split $file_data "\n"]
foreach line $data {
#parse lines for config data
if {[regexp {^(\w+)\s*=\s*[\"|](.*)[\"|]} $line -> name value]} {
# puts "found name of \"$name\" with value \"$value\""
set params($name) $value
}
}
}
# puts "settings: host:\"$params(SBSHOST)\" and player MAC:\"$params(SLMAC)\""
# The settings are now in place
set arg1 [lindex $argv 0]
set arg2 [lindex $argv 1]
# puts "recieved arguments of \"$arg1\" and \"$arg2\""
spawn telnet $params(SBSHOST) $params(SBSPORT)
expect "Escape character is *"
send "login $params(SBSUSER) $params(SBSPASS)\n"
expect "login user"
switch $arg1 {
pause {
send "$params(SLMAC) pause\n"
expect "* pause"
}
bb {
send "$params(SLMAC) time -10\n"
expect "* time"
}
ff {
send "$params(SLMAC) time +10\n"
expect "* time"
}
muteoff {
send "$params(SLMAC) mixer muting 0\n"
expect "* mixer muting"
}
muteon {
send "$params(SLMAC) mixer muting 1\n"
expect "* mixer muting"
}
next {
send "$params(SLMAC) playlist index +1\n"
expect "* playlist index"
}
prev {
send "$params(SLMAC) playlist index -1\n"
expect "* playlist index"
}
power {
send "$params(SLMAC) power\n"
expect "* power"
}
voldown {
send "$params(SLMAC) mixer volume -2.5\n"
expect "* mixer volume"
}
volup {
send "$params(SLMAC) mixer volume +2.5\n"
expect "* mixer volume"
}
shuffle {
send "$params(SLMAC) playlist shuffle\n"
expect "* playlist shuffle"
}
repeat {
send "$params(SLMAC) playlist repeat\n"
expect "* playlist repeat"
}
stop {
send "$params(SLMAC) stop\n"
expect "* stop"
}
ok { #Restore to base values
send "$params(SLMAC) playlist repeat 0 1\n"
expect "* playlist repeat"
send "$params(SLMAC) playlist shuffle 0\n"
expect "* playlist shuffle"
send "$params(SLMAC) mixer volume 50\n"
expect "* mixer volume"
send "$params(SLMAC) mixer muting 0\n"
expect "* mixer muting"
send "$params(SLMAC) power 1\n"
expect "* power"
}
default {puts "Unknown command issued to lmscli"}
}
# End of switch (do not put on same line as the closing brace)
send "exit\n"
expect eof
------------------------------------------------------------------------------
Next step is to get the commands that are sent by infra-red to get sent to lmscli.
I have not covered all of the steps required to get lirc working (there are plenty of posts and FAQs about that elsewhere) - so all I am showing here are the configuration files to link lirc to the "expect" script.
This first file should be saved as "root" in /etc/lirc as lircrc
To be safe, make a copy of the file that is already there (if there is one).
This particular file is set-up to handle 2 different remote controls, "RM-820" and "philipsdvd".
Therefore you will see that many of the functions are repeated. By doing it this way it meant that I could have the same config file on two different RPi systems, making life easier for me.
The full file is below - some key points from it are:
- the "remote =" line define which remote control this is referring to. It has to match the name used in other lirc config files (see more about that much further below)
- the text after the "button =" has to exactly match what you have mapped the button presses to for the particular remote control - via the lirc config files (see more about that much further below)
- I was still getting occasional lock-ups resulting in no sound coming out or sometimes a few digital burps and in worst case the Ethernet connection stops responding. So while trying to work out why that was happening (and I had tried what was the most recent official firmware and the work-in-progress FIQ handler at the time) I mapped one of the keys to a "reboot" command. I chose the long-back command on the RM-820 or the "scan" button on the Philips remote rather than Power for that as it very unlikely to be used accidentally and the Power key is mapped to the player power function on LMS because sometimes toggling the soft power button is enough to get things working again since Squeezelite releases something when it is told to power down. However, if you face similar problems and you do not have a button/function that you can readily map then you could edit the "#Power" section below to comment out the current "config =" line (by putting a "#" in front of it) and then put in "config = reboot". Applications/scripts that are invoked by lirc are run as "root" so you do not need to put "sudo" in front. This makes it very powerful and dangerous so take care
- the volume is controlled through ALSA (sound libraries) directly. Therefore I set the command to change the volume via ALSA and also repeat the command to LMS so that it shows the volume change. However, these are not really matched up so it would be easy to have LMS show a very different volume to what is really set in ALSA. To help get around that potential issue I configured things to use the "OK" button to try to return this setting (and some others) to defaults. There is similar trickery in place for the "mute" function for the same reason. The ALSA settings are stored so that they can be restored when needed. The hint for some of this came from http://alsa.opensrc.org/Usb-audio and possibly other places that I found while hunting for a solution but have since forgotten.
# Power
begin
prog = irexec
remote = RM-820
button = power
config = /home/pi/lmscli/lmscli.exp power
# config = reboot
end
begin
prog = irexec
remote = philipsdvd
button = KEY_POWER
repeat = 0
config = /home/pi/lmscli/lmscli.exp power
end
# Hold the menu button down for a long time ... to try to force a reboot
begin
prog = irexec
remote = RM-820
button = menu/back-long
config = reboot
end
# On philipsdvd remote - there does not seem to be a long-hold ... so use the "scan" button - saved as SYSRQ
begin
prog = irexec
remote = philipsdvd
button = KEY_SYSRQ
repeat = 0
config = reboot
end
# S51 Volume Knob
begin
prog = irexec
remote = RM-820
button = knobvoldn
repeat = 1
config = amixer sset Master 1- ; /home/pi/lmscli/lmscli.exp voldown
end
begin
prog = irexec
remote = RM-820
button = voldn
repeat = 1
config = amixer sset Master 1- ; /home/pi/lmscli/lmscli.exp voldown
end
begin
prog = irexec
remote = philipsdvd
button = KEY_DOWN
repeat = 1
config = amixer sset Master 1- ; /home/pi/lmscli/lmscli.exp voldown
end
begin
prog = irexec
remote = RM-820
button = knobMute
repeat = 1
config = if [ `amixer sget Master|grep "Front Left:"|awk '{print $3}'` -gt 0 ]; then alsactl store -f ~/.asound.state; amixer sset Master 0; amixer sset 'Power LED' off; /home/pi/lmscli/lmscli.exp muteon; else alsactl restore -f ~/.asound.state; amixer sset 'Power LED' on ; /home/pi/lmscli/lmscli.exp muteoff; fi
end
begin
prog = irexec
remote = RM-820
button = mute
repeat = 1
config = if [ `amixer sget Master|grep "Front Left:"|awk '{print $3}'` -gt 0 ]; then alsactl store -f ~/.asound.state; amixer sset Master 0; amixer sset 'Power LED' off; /home/pi/lmscli/lmscli.exp muteon; else alsactl restore -f ~/.asound.state; amixer sset 'Power LED' on ; /home/pi/lmscli/lmscli.exp muteoff; fi
end
begin
prog = irexec
remote = philipsdvd
button = KEY_AUDIO
repeat = 1
config = if [ `amixer sget Master|grep "Front Left:"|awk '{print $3}'` -gt 0 ]; then alsactl store -f ~/.asound.state; amixer sset Master 0; /home/pi/lmscli/lmscli.exp muteon; else alsactl restore -f ~/.asound.state; /home/pi/lmscli/lmscli.exp muteoff; fi
end
begin
prog = irexec
remote = RM-820
button = play-pause
repeat = 1
config = if [ `amixer sget Master|grep "Front Left:"|awk '{print $3}'` -gt 0 ]; then alsactl store -f ~/.asound.state; amixer sset Master 0; amixer sset 'Power LED' off; /home/pi/lmscli/lmscli.exp pause; else alsactl restore -f ~/.asound.state; amixer sset 'Power LED' on ; /home/pi/lmscli/lmscli.exp pause; fi
end
begin
prog = irexec
remote = philipsdvd
button = KEY_PAUSE
repeat = 0
config = if [ `amixer sget Master|grep "Front Left:"|awk '{print $3}'` -gt 0 ]; then alsactl store -f ~/.asound.state; amixer sset Master 0; /home/pi/lmscli/lmscli.exp pause; else alsactl restore -f ~/.asound.state; /home/pi/lmscli/lmscli.exp pause; fi
end
begin
prog = irexec
remote = philipsdvd
button = KEY_PLAY
repeat = 0
config = if [ `amixer sget Master|grep "Front Left:"|awk '{print $3}'` -gt 0 ]; then alsactl store -f ~/.asound.state; amixer sset Master 0; /home/pi/lmscli/lmscli.exp pause; else alsactl restore -f ~/.asound.state; /home/pi/lmscli/lmscli.exp pause; fi
end
begin
prog = irexec
remote = RM-820
button = knobvolup
repeat = 1
config = amixer sset Master 1+ ; /home/pi/lmscli/lmscli.exp volup
end
begin
prog = irexec
remote = RM-820
button = volup
repeat = 1
config = amixer sset Master 1+ ; /home/pi/lmscli/lmscli.exp volup
end
begin
prog = irexec
remote = philipsdvd
button = KEY_UP
repeat = 1
config = amixer sset Master 1+ ; /home/pi/lmscli/lmscli.exp volup
end
begin
prog = irexec
remote = RM-820
button = bb
repeat = 1
config = /home/pi/lmscli/lmscli.exp prev
end
begin
prog = irexec
remote = philipsdvd
button = KEY_REWIND
repeat = 0
config = /home/pi/lmscli/lmscli.exp prev
end
begin
prog = irexec
remote = RM-820
button = ff
repeat = 1
config = /home/pi/lmscli/lmscli.exp next
end
begin
prog = irexec
remote = philipsdvd
button = KEY_FASTFORWARD
repeat = 0
config = /home/pi/lmscli/lmscli.exp next
end
begin
prog = irexec
remote = RM-820
button = shuffle
config = /home/pi/lmscli/lmscli.exp shuffle
end
begin
prog = irexec
remote = philipsdvd
button = KEY_SHUFFLE
repeat = 0
config = /home/pi/lmscli/lmscli.exp shuffle
end
begin
prog = irexec
remote = RM-820
button = repeat
config = /home/pi/lmscli/lmscli.exp repeat
end
begin
prog = irexec
remote = philipsdvd
button = KEY_MEDIA_REPEAT
repeat = 0
config = /home/pi/lmscli/lmscli.exp repeat
end
begin
prog = irexec
remote = philipsdvd
button = KEY_MEDIA
repeat = 0
config = /home/pi/lmscli/lmscli.exp repeat
end
begin
prog = irexec
remote = RM-820
button = right
repeat = 1
config = /home/pi/lmscli/lmscli.exp ff
end
begin
prog = irexec
remote = philipsdvd
button = KEY_RIGHT
repeat = 1
config = /home/pi/lmscli/lmscli.exp ff
end
begin
prog = irexec
remote = RM-820
button = left
repeat = 1
config = /home/pi/lmscli/lmscli.exp bb
end
begin
prog = irexec
remote = philipsdvd
button = KEY_LEFT
repeat = 1
config = /home/pi/lmscli/lmscli.exp bb
end
begin
prog = irexec
remote = philipsdvd
button = KEY_STOP
repeat = 1
config = /home/pi/lmscli/lmscli.exp stop
end
begin
prog = irexec
remote = RM-820
button = ok
config = /home/pi/lmscli/lmscli.exp ok
end
begin
prog = irexec
remote = philipsdvd
button = KEY_OK
repeat = 0
config = /home/pi/lmscli/lmscli.exp ok
end
------------------------------------------------------------------------------
Next is the set-up of lirc to know what hardware is being used.
The file shown below is my config file from the RPi that has the Philips Remote control - which is getting the data via a simple IR receiver attached to GPIO pins on the RPi.
However, I have left the RM-820 set-up in there as well - but commented out. So if you are trying to get the Creative device to work then uncomment those lines and comment out the Philips ones.
This file is stored as "root" as /etc/lirc/hardware.conf
------------------------------------------------------------------------------
# /etc/lirc/hardware.conf
#
# Arguments which will be used when launching lircd
#LIRCD_ARGS=""
LIRCD_ARGS=""
#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.
# Uncomment the line below (and comment out the one following it) for Creative Labs Soundblaster X-Fi USB DAC
#DRIVER="alsa_usb"
DRIVER="default"
# usually /dev/lirc0 is the correct setting for systems using udev
# Uncomment the line below (and comment out the one following it) for Creative Labs Soundblaster X-Fi USB DAC
#DEVICE="hw:Pro"
DEVICE="/dev/lirc0"
# Uncomment the line below (and comment out the one following it) for Creative Labs Soundblaster X-Fi USB DAC
#MODULES=""
MODULES="lirc_rpi"
# Default configuration files for your hardware if any
# Uncomment the line below (and comment out the one following it) for Creative Labs Soundblaster X-Fi USB DAC
#LIRCD_CONF="creative/lircd.conf.alsa_usb"
LIRCD_CONF="/home/pi/lircd-philipsdvd.conf"
LIRCMD_CONF=""
In this case you can see that I put the configuration file for the Philips remote into the /home/pi directory.
Here is the contents of that file.
------------------------------------------------------------------------------
# Please make this file available to others
# by sending it to
#
# this config file was automatically generated
# using lirc-0.9.0-pre1(default) on Wed Apr 16 18:37:15 2014
#
# contributed by
#
# brand: lircd-philipsdvd.conf
# model no. of remote control:
# devices being controlled by this remote:
#
begin remote
name philipsdvd
bits 8
flags RC6|CONST_LENGTH
eps 30
aeps 100
header 2690 871
one 462 422
zero 462 422
pre_data_bits 13
pre_data 0xEFB
gap 106190
toggle_bit_mask 0x10000
rc6_mask 0x10000
begin codes
KEY_POWER 0xF3
KEY_1 0xFE
KEY_2 0xFD
KEY_3 0xFC
KEY_4 0xFB
KEY_5 0xFA
KEY_6 0xF9
KEY_7 0xF8
KEY_8 0xF7
KEY_9 0xF6
KEY_0 0xFF
KEY_BACK 0x7C
KEY_DISPLAYTOGGLE 0x10
KEY_MENU 0xAB
KEY_CONTEXT_MENU 0x7D
KEY_UP 0xA7
KEY_DOWN 0xA6
KEY_LEFT 0xA5
KEY_RIGHT 0xA4
KEY_OK 0xA3
KEY_REWIND 0xDE
KEY_FASTFORWARD 0xDF
KEY_STOP 0xCE
KEY_PLAY 0xD3
KEY_PAUSE 0xCF
KEY_SUBTITLE 0xB4
KEY_ANGLE 0x7A
KEY_ZOOM 0x08
KEY_AUDIO 0xB1
KEY_MEDIA_REPEAT 0xE2
KEY_MEDIA 0xC4
KEY_SHUFFLE 0xE3
KEY_SYSRQ 0xD5
end codes
end remote
------------------------------------------------------------------------------
For the Creative device I used the default that came with lirc (as you can see from the commented out set-up in the hardware.conf).
For the particular IR module that I used I connected it to GPIO pin 23 - which is one of the pins that the IQaudIO device exports to its connectors on top of the card. I forced this pin to be used by adding the following lines to /etc/modules
lirc_dev
lirc_rpi gpio_in_pin=23
This might not be right for you - so you will need to check the documentation for the IR module that you are using to see how to do it for your particular device. Note - this is not needed for the Creative device because it has a built-in IR receiver and it passes the commands in through the USB connection not GPIO.
As always, if you have questions then ask it in the comments section below and I'll try to answer it.