Pi Node-Red Internet Radio

Node-Red is graphical programming interface that allows logic to be created using nodes that are wired together. Node-Red has been pre-installed in the Raspian OS since 2015 and it has a very easy learning curve.

In this blog I wanted to show an example of Node-Red being used with a five button LCD faceplate to play Internet radio stations.

For this project I used a basic USB powered speaker and a Raspberry Pi 5-button LCD faceplate. The cost of the faceplates start at about $10.

Getting Started with Internet Radio

There are a good number of Internet radio resources, https://www.internet-radio.com has a good selection of stations to choose from.

To find a URL of a radio station, look through the stations until you find what you like and then right click on the .pls link, and “Save Link as…”. Save this link as a file and then open the file in a text editor to get the URL.

radio_stations

 

MPD – Music Player Daemon

MPD is a Linux based music service that supports the playing of both music files and internet based radio stations. For command line operations that is also a MPD client application called mpc. To install both the service and client:

sudo apt-get install mpd mpc

Before I started building the node-red application I played with the mpc commands to ensure that I understood the basics.

Internet radio stations are added like a song list:

mpc add 'http://uk2.internet-radio.com:8062'
mpc add 'http://live.leanstream.co/CKNXFM'
mpc add 'http://66.85.88.2:7136'

Some key mpc control commands are:

mpc play  # play the current station
mpc play 3 # play radio station 3
mpc pause  # pause the music
mpc next  # play the next radio station
mpc prev  # play the previous radio station 

mpc volume 90 # set the volume to 90%
mpc volume +5 # increase the volume 5%
mpc volume -5 # decrease the volume 5%

The mpc status command will show the volume, what is playing along with the current station number and total number of stations:

$ mpc status
Comedy104 - A Star104.net Station: Doug Stanhope - To Tell You the Truth
[playing] #2/4 1:45/0:00 (0%)
volume: 75% repeat: off random: off single: off consume: off

Node-Red Logic

Node-Red can be started from the Raspberry Pi menus, or from the command line:

node-red-start &

To access the Node-Red web page, enter the Raspberry Pi ip address with port 1880, for example : http://192.168.0.121:1880

For this project two extra Node-Red components are needed, and they are for the LCD faceplate and the MPD music player. To add components use the “Palette Manager” option.

palette

For the LCD faceplate, search for Adafruit, and select the i2c-lcd-adafruit-sainsmart component.

adafruit_palette

Similarly search for mpd and add the node-red-contrib-mpd component.

mpd_palette To create logic select a node from the left node panel and drag it onto the center flow palette, and then “wire” the nodes together.

For the Internet music example I used four function nodes, and the two i2cLED and the two MPD nodes. (Comment nodes are only used to explain the logic).

node_red_radio

The first step is to double click on the MPD nodes and add an MPD server.

Select Button Logic

I used the select button to turn on and off the music player.

A context variable is created to save the current state of the player. Note: a context variable is only accessible for the node where it is defined..

The ic2_LCD_Input node message has a msg.button_name and msg.button_state item that is used to determine which button is pushed.

For the select button logic a group of messages was used to add the different radio stations.


// create an "is setup" variable
var issetup = context.get('issetup')||0;

if ((msg.button_name == "SELECT") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
if (issetup === 0) {
context.set('issetup',1);
var msg0 = { payload:"clear"};
var msg1 = { payload:"add http://185.33.21.112:11029" }; // 1.FM Trance
var msg2 = { payload:"add http://66.85.88.2:7136" }; // Comedy 104
var msg3 = { payload:"add http://live.leanstream.co/CKNXFM"}; // The One - Wingham
var msg4 = { payload:"add http://185.33.21.112:11269" }; // Baroque
var msg5 = { payload:"play" };
return [ [ msg0, msg1, msg2, msg3, msg4, msg5] ];
} else {
context.set('issetup',0);
msg0 = { payload:"pause"};
return msg0;
}
}

Up/Down Button Logic

The UP button will issue an MPD command equivalent to :

mpc volume +5

This will up the volume by 5%. The total volume will max at 100%.

The DOWN button will issue an MPD command equivalent to :

mpc volume -5

// Raise/Lower the volume
var msg1;
var thevolume = 5; //volume % increment to change

if ((msg.button_name == "UP") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
msg1 = { payload:"volume +" + thevolume };
return msg1;
}
if ((msg.button_name == "DOWN") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
msg1 = { payload:"volume -" + thevolume};
return msg1;
}

Current and Max Radio Station Logic

The ‘Current and Max Radio Stations’ node is updated from the MPD in node when there are any changes to the volume or when a new song or station is played.

This logic creates two flow variables (stnmax, stncnt) that are available in any node in this flow.  The station max (stnmax) and current radio station (stncnt) variables are used in the LEFT/RIGHT button logic to determine which station to change to.


// Get the max number of radio stations and the current radio statio
// Make context variables that can be used in other node, like the LEFT/RIGHT button

var msg1 = msg.payload.status ; //create a simplier message
var stnmax = msg1.playlistlength;
flow.set('stnmax',stnmax);
var stncur = msg1.nextsong;
if (isNaN(stncur)) {stncur = stnmax;} // ensure a valid station

flow.set('stncur',stncur);

return msg1; // only needed for possible debugging

While the code is running it is possible to view the context date.

context_flow

UP/DOWN Button Logic

The UP / DOWN logic changes between the radio stations using the mpc commands:

mpc next
mpc prev

It is important to not move past the range of the radio stations or MPD will hang. The stnmax and stncur variables are used to determine if the next or previous commands are to be allowed.


// Move left and right in radion stations
var stnmax = flow.get('stnmax');
var stncur = flow.get('stncur');
if ((msg.button_name == "LEFT") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
if (stncur > 1) {
var msg0 = {payload:"previous"};
return msg0;
}
}
if ((msg.button_name == "RIGHT") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
if (stncur < stnmax)
var msg1 = {payload:"next"};
return msg1;

}

Final Comments

The Pi LCD faceplate is an excellent hardware add-on for any Raspberry Pi project. However it important to know that clone hardware may work as expected. For my hardware I was not able to easily turn off the extra LED.

A future enhancement would be to add a Web interface so that you could change the volume or stations without using the 5 button Pi faceplate.

 

Simulate Raspberry Pi Hardware

The Raspberry Pi has some great add-on hardware, such as Pi Tops that fit directly on top of the Pi module and wired components.

A good number of the wired Arduino designed parts now can also be used with Rasp PI’s. Some examples of this includes the HT16K33 and TM1637 seven segment displays.

Nothing beats using real hardware to show Pi values and status, but if you’re missing the hardware or you’d like to duplicate a displayed value remotely, then a soft version of the hardware can be very useful.

In this blog we’ll look at a three Python soft display examples, a seven-segment display, a LCD Keypad Top and a gauge.

Seven Segment Display

The tk_tools module is based on the Python tkinter module and it is has some cool components such as LEDs, Charts, Gauges and Seven Segment displays. The module is installed by:

pip install tk_tools

The tk_tools Seven Segment component can function like an Arduino TM1637 or HT16K33 display component. The tk_tools seven-segment display supports a height, digit_color and a background color.

Below is a some example code that shows the Pi’s CPU temperature in the soft seven segment display. 

import tkinter as tk
import tk_tools

root = tk.Tk()
root.title("CPU Temp")

ss = tk_tools.SevenSegmentDigits(root, digits=5, background='black',   
  digit_color='yellow', height=100)
ss.grid(row=0, column=1, sticky='news')

# Update the Pi CPU Temperature every 1 second
def update_gauge():
    # Get the Raspberry CPU Temp
    tFile = open('/sys/class/thermal/thermal_zone0/temp')
    # Scale the temp from milliC to C
    thetemp = int(float(tFile.read())/1000)

    ss.set_value(str(thetemp))
    root.after(1000, update_gauge)

root.after(500, update_gauge)

root.mainloop()

 

LCD Keypad 

The LCD Keypad I’ve used on a lot of my Pi Projects, (below is a PI FM radio example). Its supports 2 lines of text and it has 5 (or 6) buttons that can be used in your Python app. 

LCD_radio

The standard Python Tkinter library can be used to create a custom LCD keypad display. For my example I tried to replicate the look-and-feel of the Pi Top that I had, but you could enhance or change it to meet your requirements.

Below is an example that writes the button pushed to the 2 line label.

lcd_keypad_up

import tkinter as tk

def myfunc(action):
   print ("Requested action: ",action)
   Line1.config(text = "Requested action: \n" + action)

root = tk.Tk()
root.title("LCD Keypad Shield")
root.configure(background='black')

Line1 = tk.Label(root, 
		 text="ADC key testing     \nRight Key OK        ",
		 fg = "white",
		 bg = "blue",
		 font = "Courier 45",
                 borderwidth=4, relief="raised")
Line1.grid(row = 0,column = 0, columnspan =15, rowspan = 2)

selectB = tk.Button(root, width=10,text= "SELECT",bg='silver' ,
  command = lambda: myfunc("SELECT"),relief="raised")
selectB.grid(row = 3,column = 0)

leftB = tk.Button(root, width=10,text= "LEFT", bg='silver' ,
  command = lambda: myfunc("LEFT"),relief="raised")
leftB.grid(row = 3,column = 1)

rootB = tk.Button(root, width=10,text= "UP", bg='silver' ,
  command = lambda: myfunc("UP"),relief="raised")
rootB.grid(row = 2,column = 2)

rightB = tk.Button(root, width=10,text= "DOWN", bg='silver' , 
  command = lambda: myfunc("DOWN"),relief="raised")
rightB.grid(row = 3,column = 3)

bottomB = tk.Button(root, width=10,text= "RIGHT", bg='silver',
 command = lambda: myfunc("RIGHT"),relief="raised")
bottomB.grid(row = 4,column = 2)

rstB = tk.Button(root, width=10,text= "RST", bg='silver' ,
  command = lambda: myfunc("RESET"),relief="raised")
rstB.grid(row = 3,column = 4)

root.mainloop()

Gauge and Rotary Scale

There aren’t any mainstream low cost gauges that are available for the Rasp Pi, but I wanted to show how to setup a soft gauge.

The tk_tools gauge component is very similar to a speedometer. The rotary scale is more like a 180° circular meter. Both components support digital values, units and  color scales.gaugedoc

Below is a gauge example that reads the Pi CPU temperature every second.

import tkinter as tk
import tk_tools

root = tk.Tk()
root.title("CPU Temp")

my_gauge = tk_tools.Gauge(root, height = 200, width = 400,
                             max_value=70,
                             label='CPU Temp',
                             unit='°C',
                             bg='grey')
my_gauge.grid(row=0, column=0, sticky='news')

def update_gauge():
    # Get the Raspberry CPU Temp
    tFile = open('/sys/class/thermal/thermal_zone0/temp')
    # Scale the temp from milliC to C
    thetemp = int(float(tFile.read())/1000)
    my_gauge.set_value(thetemp)

    # update the gauges according to their value

    root.after(1000, update_gauge)


root.after(500, update_gauge)

root.mainloop()

gauge_temp

Final Thoughts

There are a lot of soft hardware components that could be created.

I found myself getting tripped up thinking : “What would be a good tkinter component and what should be  a Web component”. This is especially true when looking at charting examples, or when I was looking a remote connections.

NodeJS Raspberry Pi Rover

My typical Raspberry Pi projects are done in Python. I thought that I’d try some Node.js testing because I find the standalone Python Webserver (http.server library) to be a little slow on the Pi hardware.

Getting Started with Node.js on Raspberry Pi

Node.js can be installed on your Pi by:

$ sudo apt-get update
$ sudo apt-get install -y nodejs

There are a few options on how to talk to the GPIO (General Purpose Input/Output) pins on the Pi. I tested a few and I found that pigpio worked well for my setup. It is installed by:

sudo apt-get install pigpio

To set a GPIO pin to be an output and next to turn it on, a simple Node.js program (gpio.js) would be:

// gpio.js - set GPIO pin 4 to ON

const Gpio = require('pigpio').Gpio; 
const led = new Gpio(4, {mode: Gpio.OUTPUT});
led.digitalWrite(1);

To run the program:

sudo node gpio.js

It is important to note that access to the GPIO pins require admin rights so you will need to run your scripts with sudo (super user do).

Node.js Webserver

To make a simple webserver (mywebserver.js) :

// mywebserver.js - a simple websever on port 8080
//
var http = require('http').createServer(handler); //require http server, and create server with function handler()
var fs = require('fs'); //require filesystem module

http.listen(8080); //listen to port 8080

function handler (req, res) { //create server
  fs.readFile(__dirname + '/index.html', function(err, data) { //read file index.html in public folder
    if (err) {
      res.writeHead(404, {'Content-Type': 'text/html'}); //display 404 on error
      return res.end("404 Not Found");
    } 
    res.writeHead(200, {'Content-Type': 'text/html'}); //write HTML
    res.write(data); //write data from index.html
    return res.end();
  });
}

This script references the http and the fs (file system) modules, and this allow us to reference an external index.html page. The handler function is used to manage the HTTP requests.

Next you’ll need to make an index.html page, below is a simple example:

<!DOCTYPE html>
<html>
<body>
<h1>Dummy HTML Test Page</h1><hr>
</html>
</body>
</html>

To test this page run: node mywebserver.js , and from a browser use your Pi’s IP address with port 8080, for example : 

dummy

Make the Web Page Dynamic

To make the Web Page dynamic we can use the socket.io package. It is installed by:

$ npm install socket.io --save

By using socket.io, javascript functions on the web page can communicate to functions running on the Node.js web server.socketio

Once a function is created in the Node.js webserver application the Web page can pass data to that function.

The Raspberry Pi Rover

There are some low cost Arduino car frames that cost under $10. These car frames can be used with a Raspberry Pi but you will need to be careful on how the motors are powered. Depending on the motors you might be able to drive them directly from Pi GPIO pins, however it is recommended that you use some external hardware to protect your Pi. There are some good Pi motor top options available, for my project I used the Pimoroni Explorer Hat Pro

js_rover2

My hardware setup used a Pi 3 with a portable phone charger. I used some duct tape to secure the wiring, charger and Pi together.

The motor pins will vary based on hardware that you use, so my code my need to be tweeked for your setup. The ‘control’ function is what I used to define the motor state. Some key words : forward, left, right, stop or back were passed between the web page and the server app to define the rover’s motor action. 

// ws_2_rover.js - NodeJS WebServer App to control a Rover

var http = require('http').createServer(handler); //require http server, and create server with function handler()
var fs = require('fs'); //require filesystem module
var io = require('socket.io')(http) //require socket.io module and pass the http object (server)

const Gpio = require('pigpio').Gpio;
// modify for your motor pinouts
const MOTOR1 = new Gpio(21, {mode: Gpio.OUTPUT}); 
const MOTOR2 = new Gpio(19, {mode: Gpio.OUTPUT}); 
const MOTOR3 = new Gpio(20, {mode: Gpio.OUTPUT}); 
const MOTOR4 = new Gpio(26, {mode: Gpio.OUTPUT}); 

// Ensure that the rover app starts without the motors running
function rover_stop() {
    MOTOR1.digitalWrite(0);
    MOTOR2.digitalWrite(0);
    MOTOR3.digitalWrite(0);
    MOTOR4.digitalWrite(0);
}

http.listen(8080); //listen to port 8080

function handler (req, res) { //create server

  fs.readFile(__dirname + '/web_2_rover.htm', function(err, data) { //read file index.html in public folder
    if (err) {
      res.writeHead(404, {'Content-Type': 'text/html'}); //display 404 on error
      return res.end("404 Not Found");
    } 
    res.writeHead(200, {'Content-Type': 'text/html'}); //write HTML
    res.write(data); //write data from index.html
    return res.end();
  });
}

rover_stop();

io.sockets.on('connection', function (socket) {// WebSocket Connection
  console.log('A user connected');
  var controlvalue = ""; // variable for current status of the rover

  socket.on('control', function(data) { //get light switch status from client
    controlvalue = data;
    console.log('control input: ' + data);

    rover_stop(); // stop the motors, and then do the required action

    if (controlvalue == "forward") { 
      MOTOR1.digitalWrite(1); 
      MOTOR2.digitalWrite(1); 
    }
    if (controlvalue == "left") { 
      MOTOR2.digitalWrite(1); 
    }
    if (controlvalue == "right") { 
      MOTOR1.digitalWrite(1); 
    }

    if (controlvalue == "backward") { 
      MOTOR3.digitalWrite(1); 
      MOTOR4.digitalWrite(1); 
    }

  });
});

I used the Bootstrap template to offer a mobile friendly web interface. A button onclick function was used to pass the requested motor action (forward, left, right, stop, backward) to the control socket function. Below is my web page (web_2_rover.htm):

<!DOCTYPE html>
<html>
<head>
<title>NodeJS Web Rover Control</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>

<script>
var socket = io(); //load socket.io-client and connect to the host that serves the page
</script>
</head>

<body>
<div class="container">
  <h1>NodeJS Web Rover Control</h1>
  <button onclick="socket.emit('control','forward')" class="btn btn-success" style="width: 100%">Forwards</button>
  <button onclick="socket.emit('control','left')" class="btn btn-primary" style="width: 49%">Left</button>
  <button onclick="socket.emit('control','right')" class="btn btn-primary" style="width: 49%">Right</button>
  <button onclick="socket.emit('control','stop')" class="btn btn-danger" style="width: 100%">Stop</button>
  <button onclick="socket.emit('control','backward')" class="btn btn-warning" style="width: 100%">Backwards</button>     
</div>
</body>
</html>

To run the rover app enter:

sudo node ws_2_rover.js

Final Comments

I have done this project also in Python. The Python code is a little cleaner because the Pimoroni Explorer Hat has a Python library so I could easily adjust the motor speeds. However I found that the Node.js web interface to be a little faster than Python on the Pi.

 

 

Pi with PS2/PS3/XBox/Generic Keypads

For a lot of Pi projects it’s nice to have some kind of handheld controller.

Our goal was to create a simple Meccano crane that we could control with one of the game controllers that we had around the house.

PS3_crane1

There are some options like PyGame that offer a keyboard and joystick interface, but we found the evdev library to be a little simpler and more straight-forward.

The Python evdev Libraray

The Python evdev library offers is a simple way to connect up almost any USB keypad type device, the libary is installed by:

sudo apt-get install python-evdev

A simple monitoring program

The first step is to figure out what the PS2, PS3 or Xbox button codes are. The code below will show you the keycodes and key descriptions for your key presses:

from evdev import InputDevice, categorize, ecodes
gamepad = InputDevice('/dev/input/event0')

for event in gamepad.read_loop():
    if event.type == ecodes.EV_KEY and event.value == 1:
        try:
            keyevent = categorize(event)
            print (keyevent) # comment to remove key messages

        except:
            print ("Problem key pressed...")

The output will appears something like:


key event at 1488557515.714249, 295 (BTN_BASE2), down
key event at 1488557517.491448, 294 (BTN_BASE), down
key event at 1488557519.594712, 293 (BTN_PINKIE), down

Once you’ve got the required keycode then you’re ready to start your main project.

Generic Keypad

There are many low cost generic pads that can be used. Below is a picture of a generic gamepad with some sample code to pick up its keys. (Note: the joystick keys are not addressed in this project).

gamepad_pi

from evdev import InputDevice, categorize, ecodes
gamepad = InputDevice('/dev/input/event0')

for event in gamepad.read_loop():
    if event.type == ecodes.EV_KEY and event.value == 1:
        try:
            keyevent = categorize(event)
            if keyevent.keycode == "BTN_THUMB":
                print ("Right - 'A' button")
            if keyevent.keycode == "BTN_THUMB2":
                print ("Bottom - 'B' button")
            if 'BTN_TRIGGER' in keyevent.keycode: # ['BTN_JOYSTICK', 'BTN_TRIGGER']
                print ("Top - 'X' button")
            if keyevent.keycode == "BTN_TOP":
                print ("Left - 'Y' button")
            if keyevent.keycode == "BTN_TOP2":
                print ("Left shoulder button")
            if keyevent.keycode == "BTN_PINKIE":
                print ("Right shoulder button")
            if keyevent.keycode == "BTN_BASE3":
                print ("SELECT button")
            if keyevent.keycode == "BTN_BASE4":
                print ("START button")

            print (keyevent) # comment to remove key messages

        except:
            print ("Problem key pressed...")

PS2 Controller

The PS2 key mapping is not the same as a generic keypad. Some issues that we found using PS2 controllers:

  • the PS2 controller may require the “PS” key to be pushed before the controller is active
  • the right “triangle”, “circle” and “X” are not directly supported in the keyevent.keycode.

controller_ps2_diagram

Below is some Python code to pick up the PS2 keys.

from evdev import InputDevice, categorize, ecodes
gamepad = InputDevice('/dev/input/event0')

for event in gamepad.read_loop():
    if event.type == ecodes.EV_KEY and event.value == 1:
        try:
            keyevent = categorize(event)
            if 'BTN_TRIGGER_HAPPY1' in keyevent.keycode: #'BTN_TRIGGER_HAPPY', 'BTN_TRIGGER_HAPPY1'
                print ("Playstation button")

            if keyevent.keycode == "BTN_TOP2":
                print ("Top D-Pad button")
            if keyevent.keycode == "BTN_BASE":
                print ("Bottom D-Pad button")
            if keyevent.keycode == "(BTN_BASE2":
                print ("Left D-Pad button")
            if keyevent.keycode == "(BTN_PINKIE":
                print ("Right D-Pad button")

            if keyevent.keycode == "BTN_BASE5":
                print ("Left top shoulder button")
            if keyevent.keycode == "BTN_BASE6":
                print ("Right top shoulder button")
            if keyevent.keycode == "BTN_BASE3":
                print ("Left bottom shoulder button")
            if keyevent.keycode == "BTN_BASE4":
                print ("Right bottom shoulder button")

            if "BTN_TRIGGER" in keyevent.keycode: #'BTN_JOYSTICK', 'BTN_TRIGGER'
                print ("SELECT button")
            if keyevent.keycode == "BTN_TOP":
                print ("START button")

            if keyevent.keycode == "BTN_DEAD":
                print ("SQUARE button")

            print (keyevent) # comment to remove key messages

        except:
            print ("Problem key pressed...")

Wireless Keyboards

We found that evdev also worked well with wireless keyboards. One of the advantages of evdev is that it will get the key stroke directly and you don’t need to do an “Enter”.

We used a wireless keyboard to drive and control a mobile missile launcher.

wl_kb_missile2

 

Gesture Controlled Radio

Our goal was to make an kitchen Internet radio player where we could change the volume and stations without touching the player. For this project we used:

  • Raspberry Pi 3 (or 2 with a Wifi dongle)
  • Pimoroni SkyWriter (~$20US)
  • USB powered portable speaker

The Pimoroni SkyWriter supports both gesture and touch control.

Setup

To install the Pimoroni SkyWriter python libraries:

curl -sS get.pimoroni.com/skywriter | bash

For the internet music interface we used the mpd , Music Player Daemon, it can be installed by:

sudo apt-get install mpd mpc

The SkyWriter can be wrapped in plastic wrap and the gestures can still be picked up. For a more robust project it would probably be best to design a more waterproof enclosure.

Python Code

A few years ago we found that the mpc calls were bullet-proof, but when we did this project we found that the mpd service would often lock up  if a new internet radio station was selected (mpc next). For this reason we used external calls to adjust the volume and we restarted the mpd service when we changed radio stations

We used a gesture of up/down to adjust the volume, and a left/right to change the station.

OLYMPUS DIGITAL CAMERA

Below is our final code:

#!/usr/bin/env python
import skywriter
import signal
import os

thevolume = 70 #starting volume
thestation = 1 #starting station

# Some internet radio stations
stations = (('http://185.33.21.112:11029','1.FM Amsterdam Trance Radio'),
            ('http://www.partyviberadio.com:8000','Raggae Roots'),
            ('http://66.85.88.2:7136','Comedy 104'),
            ('http://eu.radioboss.fm:8121','Yoga'),
            ('http://185.33.21.112:11269','Baroque'))

def setvolume(voldif):
  global thevolume
  if (thevolume + voldif >= 0) and (thevolume + voldif 0) and (direction + thestation <= len(stations))):
    thestation = thestation + direction
    os.system('mpc clear')
    os.system('sudo service mpd restart')
    os.system('mpc add ' + stations[thestation][0] )
    os.system('mpc play')
    print thestation, stations[thestation][0], stations[thestation][1]

@skywriter.flick()
def flick(start,finish):
  print('Got a flick!', start, finish)
  if (finish == 'north'):
    setvolume(5)
  if (finish == 'south'):
    setvolume(-5)
  if (finish == 'west'):
    newstation(-1)
  if (finish == 'east'):
    newstation(1)

# start playing the radio at defaults
setvolume(0)
newstation(0)

signal.pause() # wait for a new gesture

Pi FM Radio

There are few options for FM radio projects on the Pi, such as the RDA5807M and TEA5767 chips. We’ve built an Arduino FM radio using the RDA5807M chips but we found the documentation to be quite weak. There are some Python libraries for TEA5767 chips, unfortunately however you have no volume control with this chip.

RTL-SDR is a software defined radio (SDR) that uses a DVB-T TV tuner dongle based on the RTL2832U chipset. RTL-SDR dongles are well priced at $10-$15 and they are easy to use for Pi FM radio projects. Software Defined Radios have a large list applications that can done, some of the cooler projects include: tracking airplanes and ships, free-to-air TV, and monitoring satellite data.

Getting Started

For setup an external speaker is required (powered speakers work the best). The RTL-SDR dongle includes an externally connected antenna, if possible try to place the antenna close to a window.

fm_setup

To install the basic software enter:


sudo apt-get install rtl-sdr

The rtl_fm utility is an FM demodulator that is used to read and sample a selected frequency. The output of rtl_fm needs to be directed to an audio player program such as aplay. There are a few sampling and buffering options that must be defined. The syntax with the required options to play an FM radio station at 107.9 MHz is:

rtl_fm -f 107.9e6 -M wbfm -s 200000 -r 48000 | aplay -r 48k -f S16_LE

The rtl_fm application needs to be stopped when a new radio station is selected. If the rtl_fm is running in the background the ps command can be used to find process IDs. The kill command can then be used to terminate the task. An example to find and terminate the rtl_fm task:

pi@raspberrypi:~ $ ps -e | grep rtl_fm
1709 pts/0 00:00:33 rtl_fm
pi@raspberrypi:~ $ kill 1709
pi@raspberrypi:~ $ ps -e | grep rtl_fm
pi@raspberrypi:~ $

Adjusting the Volume

Typically the audio would be from an external speaker rather than the HDMI connection. To force the audio connection, use raspi-config and select the “Advanced” menu option, then selection “Audio”.

advanced

There are a few ways to adjust the volume on the audio. One method is to use the amixer utility. An example to change the radio volume to 70%:

amixer sset "PCM" 70%

A Python Example

For our basic testing we created a simple Python command line application. This application accepts new FM radio frequencies and volume settings. When a new radio frequency is selected the rtl_fm task needs to be stopped and restarted.

This application used a few Python libraries. The os library has the os.system call to shell out to external programs like amixer. The os.kill call terminates processes with a give process ID. The subprocess library has the call:

# simple PI FM radio utility using a RTL SDR dongle 

import subprocess, signal, os

def newstation(station):
    global process, stnum
      
    part1 = "rtl_fm -f " 
    part2 = "e6 -M wbfm -s 200000 -r 48000 | aplay -r 48k -f S16_LE"
    cmd = part1 + station + part2
    
    print 'Playing station :', station 

    # kill the old fm connection
    if process != 0:
        process = int(subprocess.check_output(["pidof","rtl_fm"] ))
        print "Process pid = ", process
        os.kill(process,signal.SIGINT) 

    # start the new fm connection
    print cmd
    process = subprocess.Popen(cmd, shell=True)


def setvolume(thevolume):

    os.system('amixer sset "PCM" ' + thevolume)
    print 'volume = ' , thevolume


process = 0

while True:
    answer = raw_input("Enter a radio station (i.e. 107.9) or volume (i.e. 50%): ")

    if answer.find('%') > 0:  
        setvolume(answer)
    else: 
        newstation(answer)                      

Summary

After the command line FM radio is working, it is possible to create standalone PI radio projects that use pushbuttons, TV remotes or Wii controllers to adjust the volume and stations. We built some projects using PiFace and LCD button shields.

The LCD Python code is below:

#!/usr/bin/python
# Pi FM Radio using an LCD Shield for controlling the stations and volume 

import os, subprocess, signal
import time
import Adafruit_CharLCD as LCD


def newstation(direction):
        global stnum, stations, process

        print 'stnum=',stnum,'direction=',direction              
          
        part1 = "rtl_fm -f " 
        part2 = " -M wbfm -s 200000 -r 48000 | aplay -r 48k -f S16_LE"

        if (stnum + direction  -1):
                stnum = stnum + direction
                print('Playing station :', stations[stnum]) 
                cmd = part1 + stations[stnum] + part2
                if process != 0:
                        process = int(subprocess.check_output(["pidof","rtl_fm"] ))
                        print "Process pid = ", process
                        os.kill(process,signal.SIGINT) 
                # start the new fm connection
                print cmd
                process = subprocess.Popen(cmd, shell=True)
            


def setvolume(voldif):
        global thevolume
        if (thevolume + voldif > 0) and (thevolume + voldif <100):
                thevolume = thevolume + voldif
                os.system('amixer sset "PCM" ' + str(thevolume) + '%')
                print 'volume = ' , thevolume
                

lcd = LCD.Adafruit_CharLCDPlate()
lcd.set_color(0,0,0)

## Add your own stations and station info
stations = ['95.3e6','94.7e6','102.9e6','107.9e6']
sinfo = ['95.3 country', '94.7 light','102.9 easy','Y108 Rock\nHamilton ']
thevolume = 40

stnum = 1               #pick a starting station
process = 0
newstation(0)
lcd.message(sinfo[stnum])
setvolume(thevolume)


print 'Press Ctrl-C to quit.'
while True:
        if lcd.is_pressed(LCD.UP):
                setvolume(5)
                time.sleep(0.25)
        if lcd.is_pressed(LCD.DOWN):
                setvolume(-5)
                time.sleep(0.25)
        if lcd.is_pressed(LCD.LEFT):
                newstation(-1)
                lcd.clear()
                lcd.message(sinfo[stnum])
                time.sleep(0.25)
        if lcd.is_pressed(LCD.RIGHT):
                newstation(1)
                lcd.clear()
                lcd.message(sinfo[stnum])
                time.sleep(0.25)   

Wake Up !!! Wake Up !!!

The goal of the “Wake me up” programs is to set a wake time that will turn on the lights and say something like: “Wake Up Wake Up”.  The logic was done in Node-Red and it was quite straightforward. The hardware used was:

The PowerSwitch Tail II ($26) is a power cord that is enabled/disabled with I/O pins. The PowerSwitch pins connect to GPIO18 and GND on the Pi. A desk light is plugged into the PowerSwitch Tail and speakers are connected to audio jack on the Pi.

pi2switch

With the PowerSwitch Tail II there are a lot of devices that could be controlled. For us, we simply connected it to a desk lamp. The setup shown below was later moved into a bedroom and place on a bedside table.

wakeup_hwd

For this project two Node-Red libraries were used; a scheduler library and a text to speech library. They are installed by:

 sudo apt-get install festival
cd $HOME/.node-red
npm install node-red-contrib-say
npm install node-red-contrib-simple-weekly-scheduler 

After the packages are loaded restart the Pi, and then start up Node-Red. On the Node-Red configuration Web page, drop a scheduler, and wire a Pi GPIO, and a Say node to the scheduler.

3nodes

Double-click the scheduler node and set the wake up times. The start/end payloads are numeric and 1/0. Below is the scheduler configuration.

schedule

For the Pi GPIO node, set the GPIO to Pin 12/GPIO18.

powerinfo

Next for the Say node, enter the text you want spoken .

wakeup

For the final circuit, inject nodes can be included for testing. An “on” inject sends a 1, and an “off” inject sends 0. At the wake up time the schedule node will send a payload that will trigger the speaking of the wake up text and the desk light will be turned on for 15 minutes.

final_wakeup