Sunday 27 April 2014

TubeNetRadio - The Raspberry Pi powered tube radio


This Sachsenwerk Olympia 59-3W from 1959 learned to play web radio and mp3s


No drilling required - Minimally invasive connection of the Raspberry to the tube radio


This post describes how to setup your Raspberry Pi to work as a web radio and an mp3 player based on mpd and Arch Linux. This description is a part of the TubeNetRadio project described in more detail under www.doc-diy.net/electronics/tubenetradio. In this project the Raspberry Pi is embedded in an old tube radio to add an internet radio and mp3 player functionality.
The user interface consists of just to buttons mounted in the back plate to avoid destroying the original look of the old radio. One button allows to step through radio stations stored in a file. The other one allows you to skip album-wise though your mp3 collection stored locally or remotely. In the following, the essential steps of the software setup are described. The hardware side is described under www.doc-diy.net/electronics/tubenetradio.

Main installation steps

  • Installation of Arch and Python
  • Installation of mpd/mpc
  • The buttons and the player function
  • The start script
The idea behind Arch is to have a thin and fast booting operating system. The price to pay is that you need more steps for the setup than for example with Raspbian. Some packages are just not available off-the-shelf. Since one of the targets was to beat down the boot time close to the tube heat up time of the radio, Arch Linux was the perfect choice.

Copy a fresh Arch image on the SD card. At the time of writing this was
ArchLinuxARM-2014.01-rpi. Configure and update your system. Arch lacks pretty much everything at the beginning. Follow one of the tutorials on the net.

Install base-devel for gcc and others. You will need it to compile the python-pip tool which in turn installs the RPi.GPIO.
pacman -Sy base-devel
Install python2. Because python 3 and 2 are not compatible, the packages are split. The old python 2 is marked with a 2, the new version has no marking
pacman -S python2 python2-pip
Install mpd along with the Python interface using
pacman -S mpd mpc
pacman -S python2-mpd
Important: In Jan 2014 python-mpd doesn't exist! Only the python2 version exists. This is the reason why you have to install the python2 stuff and not the new one. This will probably change soon.

To read out the buttons needed for the user interface you have to install RPi.GPIO by typing
python2-pip RPi.GPIO
RPi.GPIO is a comfortable Python interface to the GPIO connector on the Raspi board.

Starting mpd

Compared to Ubuntu, things turned out to be more complicated. I took me quite a time to sort out all the groups and user settings, including adding group read permissions for /home/pi. In case of trouble, I recommend to set up mpd on a linux PC and try to copy the settings of a working system.

Have a look at this guide:
https://wiki.archlinux.org/index.php/Music_Player_Daemon#Starting_mpd_as_a_user
https://wiki.archlinux.de/title/Music_Player_Daemon

Worth reading is also this:
http://crunchbang.org/forums/viewtopic.php?pid=182574

Use the following listing of the home folder as reference. The user and the group of each file are specified in brackets (generated with tree -uga). Although alternative and better settings probably exist, the following proved to work for me.
[pi@alarmpi ~]$ tree -u -g -a
.
├── [pi       users   ]  .bash_history
├── [pi       users   ]  .bash_logout
├── [pi       users   ]  .bash_profile
├── [pi       users   ]  .bashrc
├── [mpd      audio   ]  .mpd
│   ├── [mpd      audio   ]  database
│   ├── [mpd      mpd     ]  log
│   ├── [mpd      mpd     ]  pid
│   ├── [mpd      mpd     ]  state
│   └── [mpd      audio   ]  sticker.sql
├── [root     root    ]  lastradiopos
├── [root     root    ]  lastsongpos
├── [pi       audio   ]  music
│   ├── [pi       users   ]  Dive Deep
│   │   ├── [pi       users   ]  01 - Morcheeba - Enjoy the Ride (ft Judy Tzuke).mp3
│   │   ├── [pi       users   ]  02 - Morcheeba - Riverbed (ft Thomas Dybdahl).mp3
│   │   ├── [pi       users   ]  03 - Morcheeba - Thumbnails.mp3
│   │   ├── [pi       users   ]  04 - Morcheeba - Run Honey Run (ft Bradley).mp3
│   ├── [pi       users   ]  Love Deluxe
│   │   ├── [pi       users   ]  01 - Sade - No Ordinary Love.mp3
│   │   ├── [pi       users   ]  02 - Sade - Feel No Pain.mp3
│   │   ├── [pi       users   ]  03 - Sade - I Couldn't Love You More.mp3
│   ├── [pi       users   ]  Smolik
│   │   ├── [pi       users   ]  01 - Smolik - SOS Songs feat. Gaba Kulka.mp3
│   │   ├── [pi       users   ]  02 - Smolik - Not Always Happy feat. Joao T. de Sousa.mp3
│   │   ├── [pi       users   ]  03 - Smolik - Memotion feat. Mika Urbaniak.mp3
│   └── [pi       audio   ]  playlists
│       └── [pi       users   ]  myradiostations.m3u
├── [pi       users   ]  tubeNetRadio.py
└── [pi       users   ]  tubeNetRadio.sh

After everything is set up correctly you should be able to launch mpd and manually control it with the frontend called mpc. Try mpc update, mpc ls, mpc add "", mpc play...
For mpd to start always at boot, you have to type the following
systemctl enable mpd
and type
systemctl start mpd
to start it immediately. Some info about starting services on boot in Arch Linux can be found here: https://wiki.archlinux.org/index.php/Systemd#Using_units


GPIO and the button routine


Raspberry Pi GPIO wiring plan for the buttons

We have now mpd running and need some routine on top to poll the buttons and control the music according to the users needs. This is done using a single Python script which uses RPi.GPIO to understand the buttons and mpd-python to play the music. Using these two extensions we have everything on Python level.

Let me explain how the user interface works. There are just two buttons. One skips the radio stations which are stored in the file myradiostations.m3u in the music folder. The other button skips local mp3 music album-wise (not song-by-song) for faster navigation. All the functionality is contained in the following routine.
One issue that stole lots of time was the fact that the connection to the mpd server drops automatically and needs to reestablished or checked each time you want to perform an mpd operation. Skipping whole albums instead of songs adds some complexity to the script. It works basically by looking for the next song with a different album tag than the one currently played. For proper operation your songs have to be tagged.
The buttons have to be connected to GPIO02 and GPIO03 of the GPIO port. Resistors are not necessary since these pins have internal pull-ups.


#!/usr/bin/python2
# pyhton code for the tubeNetRadio project. tubeNetRadio is a Raspberry Pi
# based internet radio / mp3 player with a minimalistic user interface 
# consisting of just two knobs (no display)
#
# Function:
# 
# Button 1: skip to next radio station 
# Button 2: skip to next album of available mp3 archive
#
# www.doc-diy.net
#
#

import mpd
import RPi.GPIO as GPIO
import os
import time

###############################################################################
# constants for readable code
RADIO          = 0
ALBUMS         = 1
PRESSED        = 0

# name of files where the recently played track/radio is stored
lastsongfile   = "/home/pi/lastsongpos"
lastradiofile  = "/home/pi/lastradiopos"
# file with internet radio station playlist
myplaylist     = "myradiostations" # refers to "myradiostations.m3u" in playlist folder

###############################################################################
# setup GPIO
# pinout in chip nomenclature (BCM)

buttonPin0     = 2
buttonPin1     = 3

GPIO.setmode(GPIO.BCM)

GPIO.setup(buttonPin0, GPIO.IN)
GPIO.setup(buttonPin1, GPIO.IN)

##############################################################################
# connect to mpd server

client = mpd.MPDClient() # create client object (this is how mpd works with 
                         # python)

# Reconnect until successful
while 1:
    try:
        status = client.status()
        #print("Initial connect")
        break

    except:
        client.connect("localhost", 6600)
        #print("Initial connect failed ...")
        time.sleep(1)

###############################################################################
# set initial playmode. this setting decides if the player starts as mp3 player 
# or internet radio
playmode = RADIO

# initialize mpd
client.clear()                  # clear playlist
client.update()                 # update library
client.load(myplaylist)         # load playlist with radio stations
client.play()                   # play music
client.repeat(1)                # repeat playlist

###############################################################################
# generate 'recently played' files if missing (at first start for example)
if not os.path.exists(lastsongfile):
    with open(lastsongfile, 'w') as f:
        f.write(str(1))
if not os.path.exists(lastradiofile):
    with open(lastradiofile, 'w') as f:
        f.write(str(1))


###############################################################################
# infinite button polling loop

while True:

    input0 = GPIO.input(buttonPin0)
    input1 = GPIO.input(buttonPin1)

    # because mpd drops the connection automatically it has to be 
    # checked or restablished before any operation. otherwise the 
    # scripts stops
    while 1:
        try:
            status = client.status()
            break
        except:
            client.connect("localhost", 6600)
            print("Reconnect ...")


    ###########################################################################
    if input0 == PRESSED:  # go to album mode or skip album if already in album mode
        if playmode == RADIO:
            playmode = ALBUMS
            client.clear()
            tracks=client.list('file')   # get all files from data base 
                                         # (doesn't load playlists with radio 
                                         # stations)
            for t in tracks:
                client.add(t)

            # play song lastly played
            with open(lastsongfile, 'r') as f:
                songpos = int(f.read())
            client.play(songpos)

        else:

            # get current song id
            songposcur = int(client.currentsong()['pos'])  # get playlist 
                                          # position of current song

            # create list of albums, replace empty entries with dummy
            plsinfo = client.playlistinfo()  # get all track information for 
                                             # playlist
            plsalbums = []
            for alb in plsinfo:
                tmp = alb.get('album','-no-')  # get albums and replace empty 
                                               # entries by -no-
                plsalbums.append(tmp)

            # get album of current song 
            songalbum = plsalbums[songposcur]

            # go through album list and search for the next song with a 
            # differing album name
            for x in range(songposcur, len(plsalbums)):
                songpos = 0 # go to start of playlist, valid only if if 
                            # statement below fails
                if plsalbums[x] != songalbum:
                    songpos = x
                    break

            client.play(songpos)  # play first song of next album

            # save current song id for next restart
            songpos = int(client.currentsong()['pos'])
            print(str(songpos))
            with open(lastradiofile, 'w') as f:
                f.write(str(songpos))
                f.truncate()      # cuts all previous contents like digits


    time.sleep(0.1)   # results in 10 Hz polling of button


Make the button Python script start automatically


We want the python script to start automatically after boot. Similarly to mpd
we need to place the corresponding .service file in /etc/systemd/system. Making this script run properly turned out to be a nightmare, probably due to the trial and error approach I followed. The final outcome that proved to work is here:
[Unit]
Description=autostart tubeNetRadio mpd script
After=default.target

[Service]
Type=oneshot
ExecStart=/home/pi/tubeNetRadio.sh

[Install]
WantedBy=multi-user.target

The first part specifies on what modules this service depends. The middle part says what command should be executed. The last part tells Arch what other modules need our service to start. Using some more elaborated settings smart things can be done. In makes sense to run this service after mpd and wlan started. Any improvements or comments are welcome.
The service has to be launched using
systemctl enable tubeNetRadio
systemctl start tubeNetRadio
Here is a listing of /etc/systemd/system after the launch for reference
[pi@alarmpi ~]$ tree /etc/systemd/system
/etc/systemd/system
├── default.target.wants
│   └── tubeNetRadio.service -> /etc/systemd/system/tubeNetRadio.service
├── getty.target.wants
│   └── getty@tty1.service -> /usr/lib/systemd/system/getty@.service
├── multi-user.target.wants
│   ├── avahi-dnsconfd.service -> /usr/lib/systemd/system/avahi-dnsconfd.service
│   ├── cronie.service -> /usr/lib/systemd/system/cronie.service
│   ├── haveged.service -> /usr/lib/systemd/system/haveged.service
│   ├── mpd.service -> /usr/lib/systemd/system/mpd.service
│   ├── netctl-ifplugd@eth0.service -> /usr/lib/systemd/system/netctl-ifplugd@.service
│   ├── netctl@wlan0\x2dlothar\x2dbucher.service -> /etc/systemd/system/netctl@wlan0\x2dlothar\x2dbucher.service
│   ├── remote-fs.target -> /usr/lib/systemd/system/remote-fs.target
│   ├── rngd.service -> /usr/lib/systemd/system/rngd.service
│   └── sshd.service -> /usr/lib/systemd/system/sshd.service
├── netctl@wlan0\x2dlothar\x2dbucher.service
├── sockets.target.wants
│   └── avahi-daemon.socket -> /usr/lib/systemd/system/avahi-daemon.socket
└── tubeNetRadio.service
You might have noticed, the tubeNetRadio.service launches an intermediate shell script called tubeNetRadio.sh:
#!/bin/bash

sleep 1 && /usr/bin/python2 /home/pi/tubeNetRadio.py
This script just launches the actual tubeNetRadio.py script after the delay of 1 second. The delay has been added because the service didn't start reliably, meaning that after some boots the buttons didn't react and I couldn't switch the radio stations or albums. The delay seems to help, but is a dirty workaround.  I guess setting up the tubeNetRadio.service with some more care will solve the problem and make the intermediate script and the delay unnecessary.


Some facts

  • mpd is very robust and always plays some music, even if the button routine fails.
  • The time from power-on to music is about 30 s for Internet radio
  • Tube heat-up time is about 15 s
  • The "analog" two button interface proved to work much better then any smart phone remote control dropping the connection again and again.
  • Radio can be controlled by non-freaks;)

Download


All source files and some extra info can be downloaded from https://github.com/doc-diy/tubeNetRadio or checked out directly with git:
git clone https://github.com/doc-diy/tubeNetRadio.git
Details of the electrical connections and the way the Raspi is mounted can be found under
www.doc-diy.net/electronics/tubenetradio


Mounting an additional partition


You might have noticed that the Arch image expands to about 2 GB with 1.6 GB already occupied. This leaves just 400 MB for your mp3 collection. To add some volume you can expand the partition to use the whole SD card or create a new partition filling the remaining space. I went for the second option because of a lower risk of breaking the Linux. A second partition can be also easier accessed when the SD card is plugged into a computer, especially when FAT32 has been chosen. I can recommend the programm gparted for all formatting operations if you are under Linux.
Adding a second partition requires the fstab to be modified, see the last line below. The second partition was called /dev/mmcblk0p6 on my Arch Raspberry Pi.
# 
# /etc/fstab: static file system information
#
# <file system> <dir> <type>  <options> <dump>  <pass>
/dev/mmcblk0p1  /boot           vfat    defaults        0       0
/dev/mmcblk0p6 /media/sdcard2gb/ vfat   user,rw,umask=111,dmask=000 0       0

Setting the right options requires some experience. I recommend to have a look at this article: https://wiki.archlinux.org/index.php/fstab

I created the folder /media/sdcard2gb/ as the mount point, but you are free to choose. On the new partition the music resides in the folder music. Finally I deleted the original ~/music folder in home and made a softlink with the same name to the new partition with the music. The link command could be
ln -s /media/sdcard2gb/music ~/music
Remember, mpd was configured to look into the folder called ~/music for playable media.





Feel free to give me any feed back. I would be particularly interested in improving the launching script. Happy implementing!!!

Luk