Raspberry Pi Internet Radio

Getting started …

How cool would it be take a vintage 1940′s or 1950′s wooden radio and rebuild it as a modern Internet Radio?  That’s the premise I had in mind when I started out on my first Raspberry Pi project.

I used the Adafruit Occidentalis Linux distribution and Adafruit python class for the 16×2 LCD display.

I found a great YouTube tutorial by MeisterVision that covered the basics of the MPC/MPD Linux Music Player really well.

RaspberryPiRadio1

Parts

To build this project, I used:

I like the Edimax WiFi dongle because it works well and requires low power which makes it perfect for use with the Raspberry Pi without a powered USB hub.

YouTube Video

Here’s a 20-minute YouTube video that describes the radio in detail and demonstrates many of its capabilities, including:

  • an LCD display;
  • push button control;
  • a menu system; and
  • a loadable playlist.

The Python Source Code

The source code for this project is in one file called radio.py.  This is my first ever Python program so please forgive any non-pythonesque usage. ☺

#!/usr/bin/python

# radio.py
# January 23, 2013
# Written by Sheldon Hartling for Usual Panic.
# MIT license, all text above must be included in any redistribution
#

#
# based on code from Kyle Prier (http://wwww.youtube.com/meistervision)
# and AdaFruit Industries (https://www.adafruit.com)
# Kyle Prier - https://www.dropbox.com/s/w2y8xx7t6gkq8yz/radio.py
# AdaFruit   - https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code.git, Adafruit_CharLCD
#


# Define GPIO output pins for Radio Controls
PIN_SW1_PREV = 18
PIN_SW2_NEXT = 4

# The Adafruit_CharLCD class controls the LCD display
# using six GPIO output pins:
#  25 = RS (register select)
#  24 = E (enable/strobe)
#  23 = Data bit 4
#  17 = Data bit 5
#  27 = Data bit 6
#  22 = Data bit 7

#dependancies
from Adafruit_CharLCD import Adafruit_CharLCD
from datetime         import datetime
from subprocess       import *
from time             import sleep, strftime
import RPi.GPIO as GPIO

# globals
LCD = Adafruit_CharLCD()
PLAYLIST_MSG = []
STATION = 1
NUM_STATIONS = 0



def main():
   global STATION, NUM_STATIONS, PLAYLIST_MSG

   # Stop music player
   output = run_cmd("mpc stop" )

   # Setup GPIO
   GPIO.setmode(GPIO.BCM)            # Use BCM GPIO numbers
   GPIO.setup(PIN_SW1_PREV, GPIO.IN) # Previous Channel button
   GPIO.setup(PIN_SW2_NEXT, GPIO.IN) # Next Channel button

   # Setup AdaFruit LCD
   LCD.begin(16,2)
   LCD.clear()

# ----------------------------
# LOAD PLAYLIST OF STATIONS
# ----------------------------

   # Startup banner
   LCD.message('Welcome to\nSheldon Radio!')

   # Run shell script to add all stations
   # to the MPC/MPD music player playlist
   output = run_cmd("/home/pi/projects/radio/radio_playlist.sh")

   # Load PLAYLIST_MSG list
   with open ("/home/pi/projects/radio/radio_playlist.sh", "r") as playlist:
      # Skip leading hash-bang line
      for line in playlist:
         if line[0:1] != '#!':  
               break
      # Remaining comment lines are loaded
      for line in playlist:
         if line[0] == "#" :
            PLAYLIST_MSG.append(line.replace(r'\n','\n')[1:-1] + "                ")
   playlist.close()

   sleep(1.5)
   NUM_STATIONS = len(PLAYLIST_MSG)

# ----------------------------
# START THE MUSIC!
# ----------------------------

   # Start music player
   LCD.clear()
   LCD.message(PLAYLIST_MSG[STATION - 1] )
   mpc_play(STATION)
   countdown_to_play = 0
      
   # Main loop
   while True:
      press = read_switches()

      # PREV button pressed
      if(press == 1):
         STATION -= 1
         if(STATION < 1):
            STATION = NUM_STATIONS
         LCD.setCursor(0,0)
         LCD.message(PLAYLIST_MSG[STATION - 1] )
         # start play in 300msec unless another key pressed
         countdown_to_play = 3

      # NEXT button pressed
      if(press == 2):
         STATION += 1
         if(STATION > NUM_STATIONS):
            STATION = 1
         LCD.setCursor(0,0)
         LCD.message(PLAYLIST_MSG[STATION - 1] )
         # start play in 300msec unless another key pressed
         countdown_to_play = 3

      # BOTH buttons pressed together
      if(press == 3):
         menu_pressed()
      # If we haven't had a key press in 300 msec
      # go ahead and issue the MPC command
      if(countdown_to_play > 0):
         countdown_to_play -= 1
         if(countdown_to_play == 0):
            # Play requested station
            mpc_play(STATION)

      delay_milliseconds(100)


def read_switches():

   # Initialize
   got_prev = False
   got_next = False

   # Read switches
   sw1_prev = GPIO.input(PIN_SW1_PREV)
   sw2_next = GPIO.input(PIN_SW2_NEXT)

   # Debounce switches and look for two-button combo
   while(sw1_prev or sw2_next):
      if(sw1_prev):
         got_prev = True
      if(sw2_next):
         got_next = True
      delay_milliseconds(1)
      sw1_prev = GPIO.input(PIN_SW1_PREV)
      sw2_next = GPIO.input(PIN_SW2_NEXT)
   if(got_prev and got_next):
      return 3
   if(got_next):
      return 2
   if(got_prev):
      return 1
   return 0



def delay_milliseconds(milliseconds):
   seconds = milliseconds / float(1000)	# divide milliseconds by 1000 for seconds
   sleep(seconds)



# ----------------------------
# RADIO SETUP MENU
# ----------------------------

MENU_LIST = [
   '1. Display Time \n   & IP Address ',
   '2. Output Audio \n   to HDMI      ',
   '3. Output Audio \n   to Headphones',
   '4. Auto Select  \n   Audio Output ',
   '5. System       \n   Shutdown!    ',
   '6. Exit         \n                ']

def menu_pressed():
   global MENU_LIST

   item = 0
   LCD.clear()
   LCD.message(MENU_LIST[item] )

   keep_looping = True
   while (keep_looping):

      # Wait for a key press
      press = read_switches()

      # PREV button
      if(press == 1):
         item -= 1
         if(item < 0):
            item = len(MENU_LIST) - 1
         LCD.clear()
         LCD.message(MENU_LIST[item] )

      # NEXT button
      elif(press == 2):
         item += 1
         if(item >= len(MENU_LIST)):
            item = 0
         LCD.clear()
         LCD.message(MENU_LIST[item] )

      # BOTH buttons together, This is "Select"
      elif(press == 3):
         keep_looping = False

         # Take action
         if(  item == 0):
            # display time and IP address
            display_ipaddr()
         elif(item == 1):
            # output to HDMI
            output = run_cmd("amixer -q cset numid=3 2")
         elif(item == 2):
            # output to headphone jack
            output = run_cmd("amixer -q cset numid=3 1")
         elif(item == 3):
            # output auto-select
            output = run_cmd("amixer -q cset numid=3 0")
         elif(item == 4):
            #shutdown the system
            output = run_cmd("mpc clear")
            output = run_cmd("sudo shutdown now")
      else:
         delay_milliseconds(100)

   # Restore display
   LCD.clear()
   LCD.message(PLAYLIST_MSG[STATION - 1] )



def display_ipaddr():
   show_wlan0 = "ip addr show wlan0 | cut -d/ -f1 | awk '/inet/ {printf \"w%15.15s\", $2}'"
   show_eth0  = "ip addr show eth0  | cut -d/ -f1 | awk '/inet/ {printf \"e%15.15s\", $2}'"
   ipaddr = run_cmd(show_eth0)
   if ipaddr == "":
      ipaddr = run_cmd(show_wlan0)

   i = 0
   keep_looping = True
   while (keep_looping):

      # Every second, update the time display
      i += 1
      if(i % 10 == 0):
         LCD.setCursor(0,0)
         LCD.message(datetime.now().strftime('%b %d  %H:%M:%S\n'))
         LCD.message(ipaddr)
         #LCD.message('e%15.15s' % ( ipaddr ) )

      # Every 3 seconds, update ethernet or wi-fi IP address
      if(i == 60):
         ipaddr = run_cmd(show_eth0)
         i = 0
      elif(i == 30):
         ipaddr = run_cmd(show_wlan0)

      # Every 100 milliseconds, read the switches
      press = read_switches()
      if(press == 3):
         keep_looping = False
      delay_milliseconds(99)



def run_cmd(cmd):
   p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT)
   output = p.communicate()[0]
   return output



def run_cmd_nowait(cmd):
   pid = Popen(cmd, shell=True, stdout=NONE, stderr=STDOUT).pid



def mpc_play(STATION):
   pid = Popen(["/usr/bin/mpc", "play", '%d' % ( STATION )]).pid



if __name__ == '__main__':
  main()

The Radio Play List

The radio play list / channel line-up is loaded dynamically from a file called radio_playlist.sh.  This file is processed twice by the radio.py program: the first time it is executed as a shell script; and the second time it is read as a text file to load the channel display names.

Here’s the sample file I used in my video …

#! /bin/sh
mpc clear 1>/dev/null

#1 SomaFM, Lush  \nfemale vocals
mpc add http://mp1.somafm.com:8800

#2 CHFX Halifax  \ncountry
mpc add mms://ysj00ms01s0.aliant.net/CHFX

#3 CJCB Sydney   \ncountry
mpc add mms://ysj00ms01s0.aliant.net/CJCB

#4 CHNS Halifax  \nclassic rock
mpc add mms://ysj00ms01s0.aliant.net/CHNS

#5 CFQM Moncton  \nadult Contempora
mpc add mms://ysj00ms01s0.aliant.net/CFQM

#6 CBD  St John  \npublic radio
mpc add mms://ysj00ms01s0.aliant.net/CBD

#7 CJYC St John  \nclassic rock
mpc add mms://ysj00ms01s0.aliant.net/CJYC

#8 CFCY CharTown \ncountry
mpc add mms://ysj00ms01s0.aliant.net/CFCY

#9 CHLQ ChrlTown \nhot AC
mpc add mms://ysj00ms01s0.aliant.net/CHLQ

#10 CHTN ChrlTown\nclassic hits
mpc add mms://ysj00ms01s0.aliant.net/CHTN

#11 CJRW ChrlTown\nclassic rock
mpc add mms://ysj00ms01s0.aliant.net/CJRW

#12 CBC Halifax  \nradio 2
mpc add http://3143.live.streamtheworld.com:80/CBC_R2_HFX_H_SC

#13 CFMJ Toronto \ntalk radio
mpc add http://208.92.54.5:80/CFMJAMAAC_SC

#14 CBC Halifax  \nradio 1
mpc add http://3383.live.streamtheworld.com:80/CBC_R1_HFX_H_SC

Next Up …

Radio Pi Plate.  A Raspberry Pi Internet Radio that uses Adafruit’s RGB Negative 16×2 LCD+Keypad Kit for Raspberry Pi pi plate. (coming soon)

radiopiplate

10 thoughts on “Raspberry Pi Internet Radio

  1. Les Orchard

    Here’s something that might interest you as food for thought on this project:

    https://www.flickr.com/photos/deusx/8747957195/in/photostream/

    That’s a hack I did on top of the Adafruit LCD class, which lets me use only 3 GPIO pins to drive the LCD (not counting power & ground). I’m thinking I might try building an internet radio like this, but add a few more buttons and possibly some more blinking lights & maybe change colors on the RGB backlight

    Reply
  2. Martyn

    I notice 2 resistors on the breadboard, are they in use? Could you give more information on those please?

    Reply
    1. sheldon Post author

      These are 10K ohm pull-down resisters used for the Previous and Next switches. Each resister is connected between ground and one end of a SPST switch. The other side of each switch is tied to 3.3V and also to one of the Raspberry Pi’s GPIO input connections; pin 18 for the previous switch; and pin 4 for next switch.

      Reply
  3. Bryan

    I tried this and having two distinct problems. The first us that no matter what I cannot get the playlist to load. And the second that even without being connected its acting as if the push buttons are being pressed in a random sequence. Any thoughts?

    Reply
    1. sheldon Post author

      Hi Bryan,

      1. The Playlist.

      What permissions do you have on the file? Remember that it is run as a Linux shell command; so you’ll need execute permissions. What happens when you try to run it from a SSH session? A chmod 775 on the radio_playlist.sh file may help.

      2. The switches.

      How do you have the switches wired? I used 10Kohm pull-down resisters. Each resister is connected between ground and one end of a switch. The other side of each switch is tied to 3.3V and also to one of the Raspberry Pi’s GPIO input connections; pin 18 for the previous switch; and pin 4 for next switch.

      Good luck,
      Sheldon

      Reply
      1. Bryan

        I’ll try the permissions settings. That may help. Thank you.

        In regards to the switches – I had them wired this exact same way when I got this result. Curiously, I get the same result even when I physically disconnect from 4 and 18. Could it be something in RPi.GPIO that’s sending a false positive to the Pins?

        Reply
  4. Dennis Andersson

    Hello!
    I have got everything to work with some modifications.
    But I get no sound out of the speakers!
    I have tried but I can not think of anything that can help me.
    The components I have are:
    Raspberry Pi mod: B, 512 Kb
    Adafruit Blue & White 16×2 LCD + Keypad.
    Computer speakers.

    If I write from SSH:
    MPC add …………………..
    MPC play

    Then the sound works but not via the python program.

    This is a fantastic program that I really want to get to work!

    Please, someone help me!

    Reply
    1. sheldon Post author

      Hi Dennis

      It sounds like the file protection might be wrong on the playlist file. Remember the radio_playlist.sh file is run as a Linux shell command so you’ll need execute permissions. What happens if you run your radio_playlist.sh script from SSH?

      I’ve emailed a copy of the files I used.

      Good luck with your project!
      Sheldon

      Reply
  5. Pingback: 「Raspberry Pi(ラズベリー・パイ)」について知っておくべきこと | ReadWrite Japan

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>