I have managed to offload many tasks to the Raspberry Pi. Deleting SPAM from my email’s SPAM box, checking for modifications to old-style pages that do not yet offer RSS, having a VPN to home from anywhere in the world, using it as a print server, and other similar things. The temptation is also great to use it as a media server too. A headless media server, that is. And player.

My requirements

  • serve media files over the network using DLNA
  • play music from the Pi over my Logitech speakers
  • controllable from my computer or mobile, with no monitor needed on the Pi

Also, I’d like to save power and turn the speakers off when not in use. I mean really off, not standby. (I can almost hear the gurgling sound of money that goes to feed the countless “standby power vampires” at home.) I could just get up and plug the speakers off, but then we’d have no tutorial 🙂 So:

  • Turn the speakers on and off automatically when music is playing/stopped

All this should require little or no programming. That’s not laziness — I’m just trying to make it easy for everyone else 😉

Don't care about speakers and power? Click for some devilish ideas!

Ok, so you don’t care about energy saving. Whatever rocks your boat! You can still use the scripts below for much more than turning speakers on and off. Just replace the power on/off commands with the appropriate commands to:

  • Turn camera on and automatically record yourself dancing when “Gangnam Style” starts
  • Send yourself an email or SMS when your girlfriend listens to your song, so you can call her “just at the right moment”. (Hint: this qualifies as stalking, try at your own risk!)
  • Delete the currently playing song. This will make the playlist self-destructing, i.e. files will be deleted from the harddisk immediately after being played
  • Play weird noises instead of the song you hate, but your roommate loves and has put in every playlist. This will convince him the file is broken. For long term solution, get a new roommate 😉

Along the way we will need a remote-controlled plug to power the speakers, and a DIY or hacked remote control to switch the plug on and off from the Raspberry Pi. (DIY tutorial is coming.)

Setting up the system

First things first:

  • serve media files over the network using DLNA
  • play music from the Pi over my Logitech speakers
  • controllable from my computer or mobile, with no monitor needed on the Pi

Login to the Pi and:

sudo apt-get install minidlna mpd ncmpc

Minidlna is the media server, mpd is the media player daemon that will play music over the speakers, and ncmpc is a console application to control mpd during testing. You need to do some simple configuration for minidlna and mpd, telling them where your music, videos, and pictures are, and on what network card to listen. There are loads of tutorials on how to configure minidlna and mpd, so I’ll not repeat the instructions here. Go on and do the configuration, then come back. While you are at it, install MPDroid on your Android phone, MPod on your iPhone/iPod, Theremin on your Mac, or any of the other clients for Windows/Linux/AppleTV etc. This will allow you to control mpd’s playback over the network.

  • Turn the speakers on and off automatically when music is playing/stops

I’ll assume the command for that is “/opt/powerswitch/soundon” or “/opt/powerswitch/soundoff”. Follow the “DIY remote control for the power plug” tutorial (coming next) to build the remote control and to install those programs.

Understanding mpd

In order to run the commands at the right time, we need to find out when a song starts or stops playing. It’s quite easy, but you need 3 minutes of learning about the MPD control protocol. Make sure mpd is not playing anything first, then run telnet localhost 6600. (If your settings are non-default, use your settings instead of the “localhost” and 6600. If telnet is missing, install it with sudo apt-get install telnet.

You should now see:

OK MPD 0.16.0

That is mpd telling you it is ready for further commands. Type “status” and press enter. You should get something similar to this, showing the internal state of mpd:

status
volume: 75
repeat: 0
random: 0
single: 0
consume: 0
playlist: 2
playlistlength: 1
xfade: 0
mixrampdb: 0.000000
mixrampdelay: nan
state: stop
song: 0
songid: 0
OK

That last OK means mpd is waiting for your next command. The “state: stop” line means music is not playing. The other lines can also be useful for automating other tasks, but for now we only need the “state” line.

Now type “idle” and press enter. You’ll not get an OK. The “idle” command means “hey, mpd, I’ll stop sending you commands for now; go on and do whatever you are doing, but please tell me when something important happens.”

Now open another console (or your mobile app) and start playing some music. You’ll notice over in the telnet window that mpd says:

changed: player
OK

Now if you give the “status” command again, you’ll see:

repeat: 0
random: 0
single: 0
consume: 0
playlist: 2
playlistlength: 1
xfade: 0
mixrampdb: 0.000000
mixrampdelay: nan
state: play
song: 0
songid: 0
time: 7:217
elapsed: 7.419
bitrate: 192
audio: 44100:24:2
OK

See the “state: play” line? That’s how we know that mpd started playing.

Algorithm

You see where I’m going with this. To be notified when mpd plays and stops, we need to connect to mpd, give it the “idle” command, wait until it reports a change, give it the “status” command, and look at the “state” line. Depending on whether the state is “stop” or play”, we give the “soundoff” or “soundon” command. We can optimize this communication further by sending “idle player” instead of “idle”. This will cause mpd to notify us only when the player state, i.e. play/stop/pause changes.

This is how it looks as an algorithm:

    connect to mpd
    send “idle player”
    wait for mpd to say “changed: player”
    when that line comes, we know something has changed so we send “status”
    if status contains “stop”, then turn the speakers off and send “idle player” again, to wait for the next change
    if status contains “play”, then turn the speakers on and send “idle player” again to wait for the next change
    go to line 3 and wait

First working version

To automate this kind of text-based dialogues, GNU/Linux provides the “expect” utility program. Install it with “sudo apt-get install expect”. You can learn more about how to automate tasks with expect by reading these tutorials. I will instead show here the complete script you need for mpd, because it is so simple you’ll be able to understand it immediately:

#!/usr/bin/expect --

#we may wait a looooong time before something happens in mpd, so disable timeouts
set timeout -1

#the output from expect is not nice to look at, so disable it
log_user 0

#here we start the connection to mpd
spawn -noecho telnet localhost 6600

#this means: wait for mpd to say "OK MPD", then send it the command "idle player"
# note the \r is the equivalent of pressing enter on your keyboard
expect {
	"OK MPD" {
    	send "idle player\r"
	}
}

#remember that "goto line 3" in the algorithm? That is a loop. 
#As soon as we finish doing the things inside the loop, we turn back 
#and do them again. In expect's language, that is done by putting 
#everything inside the "for" command

for {} 1 {} {

#this "expect" with many nested matches means that when mpd says 
#something, it should be compared against all these matches at the same time. 
# this makes our lives easier by not having to care for the 
#order in which mpd says things, but only about what it says

	expect {
		"changed: player" {
    		    send "status\r"
    		    #when mpd tells us the status, it will be 
                    #compared against the following lines and 
                    #only the correct one will match
		}
		
		#e.g. if we receive "state: play", then we should turn 
                #on the speakers and then send the idle command again 
                #to wait until something interesting, like a stop 
                #or pause happens again 
		"state: play" {
        	exec /opt/powerswitch/soundon
    	    send "idle player\r"
		}
		"state: pause" {
	        exec /opt/powerswitch/poweroff
        	send "idle player\r"
		}
		"state: stop" {
        	exec /opt/powerswitch/poweroff
        	send "idle player\r"
		}
	}
}

Improved script

And here is an extended version, that accepts the ip and port for the mpd server as the first two command-line arguments. The commands to be run when mpd is playing, paused, or stopped are passed as command-line arguments 3, 4, and 5, for example:


mpdmonitor localhost 6600 /opt/powerswitch/soundon /opt/powerswitch/soundoff /opt/powerswitch/soundoff

This means “connect to mpd on localhost port 6600, run /opt/powerswitch/soundon when playing, /opt/powerswitch/soundoff when paused, and /opt/powerswitch/soundoff when stopped.

The following version also takes care of the case when mpd is playing a last.fm stream and the “idle” command returns almost immediately. Left unchecked, this could cause a high CPU load or run the “soundon” command too often. The solution is to check whether the state has really changed from last time, and run the soundon command only then.

#!/usr/bin/expect --


set timeout -1
log_user 0

trap {send "close\r" 
        exit} {SIGINT SIGTERM}
set onplay [lindex $argv 2]
set onpause [lindex $argv 3]
set onstop [lindex $argv 4]

send_user "Starting connection to MPD [lrange $argv 0 1]\r\n"
send_user "On play: $onplay\r\n"
send_user "On pause: $onpause\r\n"
send_user "On stop: $onstop\r\n"

spawn -noecho telnet [lindex $argv 0] [lindex $argv 1]

expect {
    "OK MPD" {
            send_user "Connected\r\n"
            send "status\r"
    }
}

for {} 1 {} {
    expect {
       "changed: player" {
            send "status\r"
       }
       "state: play" {
           if { "$currentState" != "play" } {
                send_user "State changed"
                send_user " to play\r\n"
                if { "$onplay" != "" } {
                        exec $onplay
                }
                set currentState "play"
           }
           send "idle player\r"
       }
       "state: pause" {
           if { "$currentState" != "pause" } {
                send_user "State changed"
                send_user " to pause\r\n"
                if { "$onpause" != "" } {
                        exec "$onpause"
                }
                set currentState "pause"
           }
           send "idle player\r"
       }
       "state: stop" {
           if { "$currentState" != "stop" } {
                send_user "State changed"
                send_user " to stop\r\n"
                if { "$onstop" != "" } {
                        exec $onstop
                }
                set currentState "stop"
           }
           send "idle player\r"
       }
    }
}

Now have fun and be creative with the commands you run 😉