Need an Asterisk setup? Why not combine the long term support of an Ubuntu LTS release with the long term support of a Certified Asterisk release?

Certified Asterisk releases are supported for around 4 years, and Ubuntu LTS for around 5 years, helping ensure you don’t need to mess around with major reconfiguration again for some time.

We’ll be working with Certified Asterisk 13 and Ubuntu 14.04. Certified Asterisk 13 has an end-of-life date (EOL) of October 24, 2019, and Ubuntu 14.04 has an EOL of April 2019.

A list of Asterisk versions and their end of life dates can be found here:
https://wiki.asterisk.org/wiki/display/AST/Asterisk+Versions

Prerequisites

Let’s start by making sure we are up to date

apt-get update && apt-get -y upgrade

Make sure kernel headers are installed

apt-get -y install linux-headers-$(uname -r)

Grab a sensible build environment along with subversion and git which we will use later to retrieve additional source code

apt-get -y install build-essential subversion git

For many people, the next two sections will be optional, you can probably skip down to the “Asterisk” section below.

DAHDI

On the system I’m working with, I have a Digium T1/E1 PRI card, so I’m going to grab the DAHDI modules and tools as well. You may want to install DAHDI regardless of your hardware for the dahdi_dummy timing driver. At one point the Zaptel dummy driver was used for MeetMe conferences when Digium hardware based timing was absent, although I’m not sure if this still remains the case.

We’ll be building our source under /usr/local/src, so switch in to that directory.

cd /usr/local/src

Download and unpack DAHDI

wget http://downloads.asterisk.org/pub/telephony/dahdi-linux-complete/dahdi-linux-complete-current.tar.gz
tar zxvf dahdi-linux-complete-current.tar.gz

Switch in to the newly created source directory, compile, and install DAHDI.

cd dahdi-linux-complete-2.10.0.1+2.10.0.1/
make all
make install
make config

If you have DAHDI hardware you should see the type of card in the make config output

 Adding system startup for /etc/init.d/dahdi ...
   /etc/rc0.d/K30dahdi -> ../init.d/dahdi
   /etc/rc1.d/K30dahdi -> ../init.d/dahdi
   /etc/rc6.d/K30dahdi -> ../init.d/dahdi
   /etc/rc2.d/S15dahdi -> ../init.d/dahdi
   /etc/rc3.d/S15dahdi -> ../init.d/dahdi
   /etc/rc4.d/S15dahdi -> ../init.d/dahdi
   /etc/rc5.d/S15dahdi -> ../init.d/dahdi
DAHDI has been configured.

List of detected DAHDI devices:

pci:0000:04:02.0     wcte13xp+    d161:800b Wildcard TE132/TE134

run 'dahdi_genconf modules' to load support for only
the DAHDI hardware installed in this system.  By
default support for all DAHDI hardware is loaded at
DAHDI start.

Switch back to /usr/local/src to continue building other packages

cd /usr/local/src

LIBPRI


As I mentioned above I have a PRI card, so I also will be installing libpri, but you can skip this step if it doesn’t apply to you.

wget http://downloads.asterisk.org/pub/telephony/libpri/libpri-1.4-current.tar.gz
tar zxvf libpri-1.4-current.tar.gz
cd libpri-1.4.15/
make
make install
cd ..

Asterisk

OK, finally we can get to building Asterisk. Let’s find the latest certified Asterisk 13 on this page:
http://www.asterisk.org/downloads/asterisk/all-asterisk-versions

At the time of writing it looks like that is 13.1-cert1, so that’s what we’ll use in this example (you may need to adjust these instructions accordingly).

Download the gzip compressed Asterisk tarball.

cd /usr/local/src
wget http://downloads.asterisk.org/pub/telephony/certified-asterisk/certified-asterisk-13.1-current.tar.gz

Decompress and unpack the file

tar zxvf certified-asterisk-13.1-current.tar.gz

Switch in to the newly created source directory

cd certified-asterisk-13.1-cert1/

Add mp3 support

./contrib/scripts/get_mp3_source.sh

Fetch all the prerequisites available in the package repository

 ./contrib/scripts/install_prereq install

If libvpb0 gets installed you may be prompted to type in your country calling code

asterisk13_libvpb0

After installation completes you should see a message indicating success.

#############################################
## install completed successfully
#############################################

There may be additional source code to grab that wasn’t retrieved from the Ubuntu repository. This will potentially install Network Broadcast Sound, libresample, jansson, libsrtp, and pjproject

./contrib/scripts/install_prereq install-unpackaged

Now that the prerequisites should be well covered, let’s configure Asterisk.
Run the configure script

./configure

If everything works out, you should get the ASCII art Asterisk logo

               .$$$$$$$$$$$$$$$=..
            .$7$7..          .7$$7:.
          .$$:.                 ,$7.7
        .$7.     7$$$$           .$$77
     ..$$.       $$$$$            .$$$7
    ..7$   .?.   $$$$$   .?.       7$$$.
   $.$.   .$$$7. $$$$7 .7$$$.      .$$$.
 .777.   .$$$$$$77$$$77$$$$$7.      $$$,
 $$$~      .7$$$$$$$$$$$$$7.       .$$$.
.$$7          .7$$$$$$$7:          ?$$$.
$$$          ?7$$$$$$$$$$I        .$$$7
$$$       .7$$$$$$$$$$$$$$$$      :$$$.
$$$       $$$$$$7$$$$$$$$$$$$    .$$$.
$$$        $$$   7$$$7  .$$$    .$$$.
$$$$             $$$$7         .$$$.
7$$$7            7$$$$        7$$$
 $$$$$                        $$$
  $$$$7.                       $$  (TM)
   $$$$$$$.           .7$$$$$$  $$
     $$$$$$$$$$$$7$$$$$$$$$.$$$$$$
       $$$$$$$$$$$$$$$$.

Ensure the the modules you want are enabled.

make menuconfig

You might want to see if there are any neat things you want. format_mp3 for example, or EXTRA-SOUNDS-EN-GSM might be desirable.

Channel Drivers -> chan_sip
Add-ons -> format_mp3
Extra Sounds Packages -> EXTRA-SOUNDS-EN-GSM

asterisk13_makemenuconfig

N.B.
Recently the Asterisk project started using PJSIP as a replacement for the older chan_sip. If you want or need the classic Asterisk SIP module you’ll have to manually select it.

To use the deprecated chan_sip, unselect the the PJSIP channel driver.

asterisk13_chan_pjsip_default

Next, select the chan_sip driver.
asterisk13_chan_sip

When you are done making any changes “Save & Exit” out of menuconfig.

Now it is time to build Asterisk

make

You should get a message that the build completed successfully.

 +--------- Asterisk Build Complete ---------+
 + Asterisk has successfully been built, and +
 + can be installed by running:              +
 +                                           +
 +                make install               +
 +-------------------------------------------+

So let’s copy the newly built files into the right places on the system

make install

If everything went to plan, you should see a message that the install completed successfully.

 +---- Asterisk Installation Complete -------+
 +                                           +
 +    YOU MUST READ THE SECURITY DOCUMENT    +
 +                                           +
 + Asterisk has successfully been installed. +
 + If you would like to install the sample   +
 + configuration files (overwriting any      +
 + existing config files), run:              +
 +                                           +
 +                make samples               +
 +                                           +
 +-----------------  or ---------------------+
 +                                           +
 + You can go ahead and install the asterisk +
 + program documentation now or later run:   +
 +                                           +
 +               make progdocs               +
 +                                           +
 + **Note** This requires that you have      +
 + doxygen installed on your local system    +
 +-------------------------------------------+

Copy the init startup scripts to make asterisk start on boot

make config
 Adding system startup for /etc/init.d/asterisk ...
   /etc/rc0.d/K91asterisk -> ../init.d/asterisk
   /etc/rc1.d/K91asterisk -> ../init.d/asterisk
   /etc/rc6.d/K91asterisk -> ../init.d/asterisk
   /etc/rc2.d/S50asterisk -> ../init.d/asterisk
   /etc/rc3.d/S50asterisk -> ../init.d/asterisk
   /etc/rc4.d/S50asterisk -> ../init.d/asterisk
   /etc/rc5.d/S50asterisk -> ../init.d/asterisk

And you’re done.

It appears I’m not the only person using osTicket for internal ticket tracking. The project has come a long way since I started using it, unfortunately most of the advancements are undesirable to my use. The software is geared toward having new tickets represent requests created by or in behalf of a user, rather than a pool of tasks needing assignment to agents. After replacing most of the default department names, groups, help topics, and SLA plans with things more meaningful to my use I was ready to get to work adding tickets. As I was populating tickets I grew increasingly annoyed by the need to fill in my name as the requester for each ticket. This dialog floats over the rest of the page content each time I click New Ticket.

osticket_new_ticket_popup

Coworkers in my department already seemed uninterested in the prospect of using an internal ticket system, so I felt compelled to try and iron out foreseeable gripes about the particular implementation before subjecting them to it.

My fix for this annoyance was a quick edit to the file include/staff/ticket-open.inc.php. At the very end of that file I commented out the original javascript and added some that behaves as if a user was just selected through the popup. The user ID I choose was “6” which corresponds to a new user I created named “Internal Ticket”, but this value will obviously need to be adjusted for your own use.

   <?php
    // Popup user lookup on the initial page load (not post) if we don't have a
    // user selected
    if (!$_POST && !$user) {?>
    /*setTimeout(function() {
      $.userLookup('ajax.php/users/lookup/form', function (user) {
        window.location.href = window.location.href+'&uid='+user.id;
      });
    }, 100); */
    setTimeout(function() {
    window.location.href = window.location.href+'&uid=6';
    }, 100);
    <?php
    } ?>
});
</script>

So now when I click on New Ticket, the “Internal Ticket” user is auto-filled for me.
osticket_new_ticket_autofill

YAC Caller ID Server for Asterisk

I recently upgraded the firmware of the IP phones in a small office using my Grandstream HTTP configuration pusher. One of the users complained that the Caller ID display under the new firmware was too small to be legible. I’m not convinced that the size of the font actually changed, but I wanted to offer some kind of solution. The ever helpful (although often outdated) voip-info.org wiki lists several options for call notifications under asterisk (the PBX in use in the office):

http://www.voip-info.org/wiki/view/Asterisk+call+notification

I liked the look of YAC: Yet Another Caller ID Program, by Jensen Harris the best. It is intended for use with a Caller ID capable modem, but works fine for my purpose as well. YAC has two pieces, a YAC listener, which runs on workstations that want to see incoming Caller ID, and a YAC server which collects Caller ID and sends to the listener(s). I’m using just the listener side and have written my own YAC server portion that collects its information through the Asterisk Management Interface. I used the SiComponents Resource Builder 3 resource editor to replace the yak graphic normally shown in YAC with the office’s logo inside of the yak.exe binary (a fake example is shown below).

yac_sample

Here is my YAC Caller ID server for Asterisk:

#!/bin/bash

extensions=(1100[192.168.1.100] 1101[192.168.1.101] 1102[192.168.1.102])

ami_user="admin"
ami_pass="elastix456"
server="127.0.0.1"
port="5038"

if [ "$1" == "--detach" ] ; then
        $0 >/dev/null &
        exit
fi

exec 3<>/dev/tcp/$server/$port

ami_write() {
        echo "$@" >&3
}

ami_read() {
        read -t1 server_out  <&3
        # strip CR 0x0d line terminator
        server_out="${server_out%$'\x0d'*}"
        echo "$server_out"

}

ami_get_response() {
        while [ 1 ]
        do
                response="$(ami_read)"
                if [ "${response:0:10}" == "Response: " ]; then
                        response="${response:10}"
                        echo "$response"
                        break
                fi
        done
}

ami_logout() {
        if [ -n "$connected" ]; then
                ami_write "Action: Logoff"
                ami_write ""
                if [ "$(ami_get_response)" == "Goodbye" ]; then
                        echo "Logged out."
                fi
        fi
}

trap "ami_logout; exit" EXIT SIGTERM

ami_login() {
        ami_write "Action: Login"
        ami_write "Username: $ami_user"
        ami_write "Secret: $ami_pass"
        ami_write ""
        if [ "$(ami_get_response)" != "Success" ]; then
                echo "Login failed." >&2
                exit
        else
                echo "Logged in."
                connected=1
        fi
}

ami_login

echo "Waiting for calls..."

while [ 1 ];
do
        line="$(ami_read)"

        if [ "${line:0:11}" == "Event: Dial" ]; then
                while [ -n "$line" ]
                do
                        line="$(ami_read)"
                        if [ "${line:0:13}" == "CallerIDNum: " ]; then
                                callerid="~${line:13}"
                        fi
                        if [ "${line:0:14}" == "CallerIDName: " ]; then
                                callerid="${line:14} $callerid"
                        fi
                        if [ "${line:0:12}" == "Dialstring: " ]; then
                                called_extension="${line:12}"
                        fi
                done
        fi

        if [ -n "$called_extension" ] && [ -n "$callerid" ]; then
                for extension in ${extensions[@]}
                do
                        ip="${extension##*'['}"
                        ip="${ip//]/}"
                        extension="${extension%%'['*}"
                        if [ "$extension" == "$called_extension" ]; then
                                echo "New call to $extension from $callerid"
                                exec 4<>/dev/tcp/$ip/10629
                                echo "@CALL$callerid" >&4
                                exec 4>&-
                        fi

                done
                unset called_extension
                unset callerid
        fi
done

exec 3>&-
echo "Quit."

You’ll need to replace the server, port, user and password with the correct information for your AMI setup. The “extensions” variable includes the internal extension and the IP address of the associated desktop/workstation that is running the YAC listener. The format is 1100[192.168.1.100] where 1100 is the extension, and 192.168.1.100 is the This way people see the Caller ID destined for their extension on their desktop. In environments with dynamically assigned desktop IP addresses this will require some additional effort to keep updated. Ideally I’d like to see something like YAC that logs in to a SIP server to monitor calls that way, seems like less to deal with, but many offerings like that included too many other features for my liking (click to call functionality, dial pad, etc).

The server captures SIGTERM and tries to logoff the AMI server before exiting

[root@phone ~]# ./yac-d.sh
Logged in.
Waiting for calls...
New call to 1100 from WIRELESS CALLER ~5551212"
^C
Logged out.

You can start the script into the background by using the –detach switch.

[root@phone ~]# ./yac-d.sh --detach

Santa Hatifyer v1.0

Well, it’s Christmas time again. I really haven’t had a lot of blog posts since last Christmas, so my 2013 desk-decorations (deskorations?) are still fresh on my mind. I cheated this year, and didn’t stick to stuff I had on hand (although at the core is a Raspberry Pi, which the office had available).

I purchased one of those cool new Raspberry Pi camera modules and stuck it in a [faux] Christmas tree. These use the Pi’s camera serial interface (CSI) port which may provide better frame rates than processing from some USB webcams. I didn’t benefit from any of that efficiency in my project, but that’s my own fault.

rpi_cam_xmas_tree

I used a 60 LED 1 meter “Neopixel” WS2812B series from Adafruit to decorate the folding table holding my tree and greeting cards. My coworker Eric programmed a Larson scanner sequence (think of the lights on the KITT car from the television show Knight Rider, or the cylons from Battlestar Galactica). He wrote some other sequences as well, but they were all pretty hard to capture with my camera due to the brightness of the LEDs.

christmas_cylon

We drove these with an Arduino compatible “Trinket Pro” also from Adafruit, although they could have been driven by the Pi itself with some extra effort. The Trinket route was pretty straight forward thanks to the Adafruit Neopixel library.

arduino_trinket

A monitor connected to the Raspberry Pi displays a festive youtube video of a fire and falling snow. This 2 hour long video plays on a loop, unless it is interrupted.

While the video plays a Python script takes images from the Raspberry Pi camera module and uses OpenCV to detect faces. When a face is found a Santa hat is placed on the subject’s head (or thereabouts) and the picture is displayed to the viewer in a greeting card-esque format. The hat is scaled to fit the head of the individual subject, and multiple subjects are supported. The photos are of course saved for future embarrassment.

xmascam_eric

The script is ugly, and as I said earlier pretty inefficient (unnecessary disk I/O and format conversions for starters). The slowness is amplified by the limited resources available on the Pi. No doubt this can be done better, quicker, and probably as a real-time video. Here it is regardless. The background comes from scigola’s contribution over at openclipart.org (thank-you).

https://github.com/ethertubes/santa_hatifyer

xmasvision.py

#!/usr/bin/env python

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
import cv2
import sys
import io
import time
import picamera
import picamera.array
import os
import pygame

CAMERA_WIDTH = 640
CAMERA_HEIGHT = 480

# Taken from "Capturing to an OpenCV object"
# http://picamera.readthedocs.org/en/latest/recipes1.html

# Create the in-memory stream
stream = io.BytesIO()

face = 0
while face == 0:
    # Acquiring pic
    with picamera.PiCamera() as camera:
        camera.resolution = (CAMERA_WIDTH, CAMERA_HEIGHT)
        camera.vflip = True
        time.sleep(1)
        with picamera.array.PiRGBArray(camera) as stream:
            camera.capture(stream, format='bgr')
            # At this point the image is available as stream.array
            image = stream.array

    # Adapted from "Haar-cascade Detection in OpenCV"
    # http://docs.opencv.org/trunk/doc/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html
    face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

    # Got pic, checking for faces
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(image, 1.3, 5)

    if len(faces) > 0:
        face = 1
        print "Found Face! "
        cv2.imwrite('foundface.jpg', image)

cv2.destroyAllWindows()

# driver selection routine borrowed from:
# https://web.archive.org/web/20130601053413/http://www.karoltomala.com/blog/?p=679

screen = None;

drivers = ['fbcon', 'directfb', 'svgalib']

for driver in drivers:
    if not os.getenv('SDL_VIDEODRIVER'):
        os.putenv('SDL_VIDEODRIVER', driver)
    try:
        print "trying: " + driver
        pygame.display.init()
    except pygame.error:
        print 'Driver: {0} failed.'.format(driver)
        continue
    found = True
    break

    if not found:
        raise Exception('No suitable video driver found!')

original_width = pygame.display.Info().current_w
original_height = pygame.display.Info().current_h

# match camera/image resolution
width = CAMERA_WIDTH
height = CAMERA_HEIGHT

# Use the face photo as our canvas
screen = pygame.display.set_mode((width, height))
pygame.mouse.set_visible(False)
bg = pygame.image.load('foundface.jpg')
bg = pygame.transform.scale(bg, (width, height))
screen.blit(bg, bg.get_rect())

# hat is longer on the right than the wearable
# area (because of the little puff ball) tweak
# value for your own hats
hat_offset = 330

# put the hat on the cat
if len(faces) > 0:
    for (x,y,w,h) in faces:
        hat = pygame.image.load('hat.png').convert_alpha()
        hat_size = int((hat.get_width() - hat_offset) / w)
        if hat_size < 1:
            hat_size = 1
        hat_offset = int(hat_offset * (1.0 / hat_size))
        hat = pygame.transform.scale(hat, (int(hat.get_width() * (1.0 / hat_size)), int(hat.get_height() * (1.0 / hat_size))))
        hat_w = hat.get_width()
        hat_h = hat.get_height()
        #pygame.draw.rect(screen, (255, 0, 0), (x, y - hat_h, hat_w, hat_h), 1) # hat border, helpful for debugging
        print "x: " + str(x)
        print "y: " + str(y)
        # fudge placement a little to put hat on, rather than over
        fx = int(x * 0.96)
        fy = int(y * 1.04)
        screen.blit(hat, (fx, fy - hat_h, hat_w, hat_h)) # fudge placement a little to put hat on, rather than over
        #pygame.draw.rect(screen, (0, 255, 0), (x, y, w, h), 1) # face border

# Uncomment if you want to see the intermediary face + hat photo
#pygame.display.update()
pygame.image.save(screen, 'hatted.png')

# Resize canvas to fit monitor
width = original_width
height = original_height

# load background and photo (with hat) into objects
# display background over photo, allowing transparent region to
# show the photo behind it.
screen = pygame.display.set_mode((width, height))
bg = pygame.image.load('xmascam.png').convert_alpha()
bg = pygame.transform.scale(bg, (width, height))
photo = pygame.image.load('hatted.png')
photo = pygame.transform.scale(photo, (int(1.339 * photo.get_width()), int(1.339 * photo.get_height())))
screen.blit(photo, (622, 115, photo.get_width(), photo.get_height()))
screen.blit(bg, bg.get_rect())
pygame.display.update()

time.sleep(10)
sys.exit

pygame.display.quit()

This script is ran, the photos archived, and the fire video started/stopped by the following bash script

watcher.sh

#!/bin/bash

facewatch() {
        if ! [ -d "./archive" ]; then
                mkdir "./archive"
        fi

        while [ 1 ]
        do
                ./xmasvision.py
                stamp="$(date +%s)"
                if [ -f "foundface.jpg" ]; then
                        mv foundface.jpg ./archive/foundface_$stamp.jpg
                fi
                if [ -f "hatted.png" ]; then
                        mv hatted.png ./archive/hatted_$stamp.png
                fi
        done
}

facewatch &
while [ 1 ]
do
        # if "hatted.png" exists then the thing should display soon
        # so stop the fire video
        if [ -f "hatted.png" ]; then
                if ! [ -z "$(ps aux |grep -i omxplayer.bin |grep -v grep)" ]; then
                        killall omxplayer.bin
                fi
        fi
        # start the fire video if needed
        if [ -z "$(ps aux |grep omxplayer.bin |grep -v grep)" ]; then
                omxplayer video/Christmas\ Yule\ Log\ Fireplace\ with\ Snow\ and\ Crackling\ Fire\ Sounds\ \(HD\)-y_VD92xKS5w.mp4 &
        fi
        sleep 1
done

Happy Holidays

Last month I had a Halloween party. Many people don’t seem to like the music I like, and I generally don’t like their music either. While planning my party it occurred to me that the music selection should be a collaborative process that included the guests. I spent the next few hours making my “Partytube” script, a collaborative Youtube-video powered jukebox for the Raspberry Pi. It’s just a series of quick and dirty little scripts that glue existing projects together for my purpose.

A pygame based display shows the song queue momentarily before the next music video begins playing.

partytube_playlist_small

You can download the project from GitHub: https://github.com/ethertubes/partytube

Install

Installation requires a few important third-party programs. First start with a copy of Raspbian, this will include most of the python/pygame stuff, the omxplayer and other things presumed to be in the working environment.

You’ll need the youtube-dl youtube ripping script available at https://github.com/rg3/youtube-dl, but it’s quicker just to download it from yt-dl.org (see below). It’s a fairly large and complex script, and not really knowing what is in it, I decided against running it as root. I made a dedicated youtube user on my Pi for the purpose of running the youtube-dl script.

root@raspberrypi:~# adduser youtube
Adding user `youtube' ...
Adding new group `youtube' (1004) ...
Adding new user `youtube' (1001) with group `youtube' ...
Creating home directory `/home/youtube' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: youtube
Retype new UNIX password: youtube
passwd: password updated successfully
Changing the user information for youtube
Enter the new value, or press ENTER for the default
        Full Name []: Youtube Video Fetcher
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n] y

root@raspberrypi:~# su - youtube
youtube@raspberrypi~$ curl https://yt-dl.org/latest/youtube-dl -o youtube-dl
youtube@raspberrypi~$ chmod a+x youtube-dl
youtube@raspberrypi~$ ./youtube-dl
Usage: youtube-dl [options] url [url...]

I also made some working directories, and downloaded and unpacked my scripts

youtube@raspberrypi~$ mkdir input
youtube@raspberrypi~$ mkdir output
youtube@raspberrypi~$ mkdir archive
youtube@raspberrypi~$ wget https://github.com/ethertubes/partytube/archive/master.zip
--2014-11-28 15:11:02--  https://github.com/ethertubes/partytube/archive/master.zip
Resolving github.com (github.com)... 192.30.252.130
Connecting to github.com (github.com)|192.30.252.130|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://codeload.github.com/ethertubes/partytube/zip/master [following]
--2014-11-28 15:11:08--  https://codeload.github.com/ethertubes/partytube/zip/master
Resolving codeload.github.com (codeload.github.com)... 192.30.252.146
Connecting to codeload.github.com (codeload.github.com)|192.30.252.146|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/zip]
Saving to: `master.zip'

    [  <=>                                                                                                                                                                                               ] 247,104      923K/s   in 0.3s

2014-11-28 15:11:14 (923 KB/s) - `master.zip' saved [247104]
youtube@raspberrypi~$ unzip master.zip
Archive:  master.zip
361942a1cf79d40976e4e40ad6fab6263fc775fd
   creating: partytube-master/
  inflating: partytube-master/LICENSE
  inflating: partytube-master/README.md
  inflating: partytube-master/YouTube-logo-light.png
  inflating: partytube-master/future_date.sh
  inflating: partytube-master/get_songs_http.sh
  inflating: partytube-master/get_songs_nfc.sh
   creating: partytube-master/output/
  inflating: partytube-master/output/none.jpg
   creating: partytube-master/php/
  inflating: partytube-master/php/get.php
  inflating: partytube-master/php/index.html
 extracting: partytube-master/php/playlist.txt
  inflating: partytube-master/php/save.php
  inflating: partytube-master/run.sh
  inflating: partytube-master/show_playlist.py
 extracting: partytube-master/url.png
  inflating: partytube-master/wood.jpg
  inflating: partytube-master/youtube-thumb.sh
  inflating: partytube-master/youtube-title.sh
youtube@raspberrypi~$ rm master.zip
youtube@raspberrypi~$ mv ./partytube-master/* .
youtube@raspberrypi~$ rmdir partytube-master/
youtube@raspberrypi~$ exit

Originally I planned on guests submitting their song selections over the web. I figured it would be nice to have a QR code to save them the effort of typing in a URL on their smartphone. I installed the qrencode package available in Raspbian.

root@raspberrypi:~# apt-get install libqrencode3 qrencode

This way I can do something like:

qrencode -o url.png -t PNG "http://www.example.org"

to create an image that I can print and display near the television. You’ll probably want to include the text form of the URL in your printed signage as well in case people don’t have a QR reader program. Obviously, replace example.org with whatever your URL is. You’ll want to use a web server capable of serving php content. Upload the content from the “php” directory from partytube to the place you selected on your web server. Make sure that the file playlist.txt is readable and writable by the effective user ID of the web server. I would recommend using public web space rather than a server hosted on the Pi itself, this prevents guests from needing to be part of your local network, but if your Pi is publicly facing I suppose this isn’t an issue. Please keep in mind that there is really no thought put into the security of this hastily created project, and anyone who knows the URL can clear the submissions waiting to be collected by the partytube scripts.

I showed a friend my script and he suggested it would be cool if I could add NFC support. Many new Android phones have NFC hardware that makes sharing website links as easy as bringing the phone in proximity to another phone or NFC capable device. I actually had a PN532 NFC/RFID controller breakout board from Adafruit.com that I purchased to play with, but never got around to doing anything with, so this sounded like a good opportunity to use it. The Adafruit PN532 breakout board has a 3.3v TTL UART interface that makes connecting it to the Raspberry Pi a straight forward process.

Solder some header pins on to the board in the area marked “FTDICABLE
nfc_uart_header

Then use some female to female jumper wires to connect the the NFC board to the Pi.

The RXD pin of the NFC board should go to the TXD pin of the Pi, and the TXD pin of the NFC board should go to the RXD pin on the Pi. You’ll also need to connect the ground pin of the NFC board to any of the ground pins available on the Pi, and connect the NFC board 5.0V pin to one of the 5v power pins on the Pi. Make sure to consult the latest documentation for your version of the Raspberry Pi in order to identify the correct pins as these could change with different versions of the Pi.

bplus-gpioSource: http://www.raspberrypi.org/wp-content/uploads/2014/04/bplus-gpio.png

I also soldered the headers for the jumpers which let me select between operating modes (UART, SPI, or I2C), but this isn’t required since the default mode is UART.

nfc_pi_uart

I printed my QR code and some instructions for guests and adhered it over the antenna area on the NFC board.

partytube_nfc_label

So, of course we need a way to be able to get data from the NFC reader now. There is a python module that can interface to this reader available at https://launchpad.net/nfcpy with documentation at http://nfcpy.readthedocs.org/en/latest/. The sample beam.py script that comes with the nfcpy module source seems to do what I want, so I just used that.

root@raspberrypi:~# apt-get install bzr
root@raspberrypi:~# cd /usr/local/src
root@raspberrypi:/usr/local/src# bzr branch lp:nfcpy

I added the youtube user to the dialout group to give it access to the Pi’s TTL UART port on /dev/ttyAMA0.

root@raspberrypi:/usr/local/src# adduser youtube dialout
Adding user `youtube' to group `dialout' ...
Adding user youtube to group dialout
Done.

You’ll also want to disable the serial console setup on /dev/ttyAMA0 to make sure that doesn’t interfere with the communications to the NFC board. Make sure you have ssh access or some other way to access your Pi besides serial console, and then edit /etc/inittab and comment out the ttyAMA0 line:

#Spawn a getty on Raspberry Pi serial line
#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100

Edit /boot/cmdline.txt and remove the console=ttyAMA0,115200 portion of the line

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

At this point it’s probably a good idea to reboot the Pi. You should be able to test the NFC board by running the command:

root@raspberrypi:~# sudo -u youtube /usr/local/src/nfcpy/examples/beam.py --device tty:AMA0:pn53x recv print

Try beaming a URL from your phone over NFC, you should see the URL in the on-screen output.

All that should be left is to hook the Pi up to an HDMI display with audio support like a television set or multimedia receiver. Omxplayer can be also output through the analog sound port using the -o local command line argument, but you’ll have to edit the script to make use of that if you require it.

To start the whole thing in motion run the run.sh script as root

root@raspberrypi:# cd ~youtube
root@raspberrypi:/home/youtube# ./run.sh

Songs are played in the order of their unix timestamp. I added a bunch of song selections before the party started because I didn’t want it to be boring and quiet until guests had a chance to figure out the partytube process. I didn’t want my playlist to preempt their choices though, so I modified the time/date of my selections to keep them at the bottom of the playlist queue (setting their dates to the future). Take a look at the future_date.sh script if you’d like to do something similar.

Explanation

There are a hodgepodge of shell scripts at work here, so a brief description is probably in order.

save.php

This file should be hosted on a web server capable of running php and accessible to party guests. If collects a submitted youtube URL and writes it to the playlist.txt file within the same directory

get.php

This displays the contents of playlists.txt and then truncates the file. It should be hosted on a web server capable of running php alongside save.php and accessible to the Raspberry Pi.

run.sh

Before running this script, the “URL=” variable should be modified to reflect the location where save.php and get.php are hosted on the web (for example http://example.org/party would be correct if http://example.org/party/save.php and http://example.org/party/get.php are hosted at those locations).

After starting the run.sh script, the URL specified in that script is turned into a QR code image which will be displayed on screen between songs. The NFC and web-based URL submission collection scripts are started in the background. The web-based collection script is passed (as a command line argument) the URL the user specified with “/get.php” appended. Next the show_playlist.py is ran. After show_playlist.py exists the omxplayer displays the next music video (as determined by timestamp) from the “./output” directory. Once the video is done playing it and it’s corresponding meta data are moved to the “./archive” directory, show_playlist.py is called again and the cycle repeats indefinitely until run.sh receives the SIGEXIT or SIGTERM signal, at which time it attempts clean up by killing all screen sessions owned by the youtube user and the omxplayer before exiting.

show_playlist.py

The show_playlist.py script is started which shows the aforementioned QR code as well as any songs currently in the queue. The “queue” consists of files in the “./output” directory. Songs are sorted according to their timestamp, from oldest to newest. All video files should have a corresponding (same base filename) text file containing the video title and a thumbnail image. Show_playlist.py displays the contents of these corresponding files and a list of the next few songs to be played. After a few seconds of displaying this information to the screen the script exits.

get_songs_nfc.sh

The get_songs.nfc.sh script runs the beam.py example from nfcpy and parses the output for URLs containing ‘youtube‘. The youtube video ID is parsed out and used as the filename for the video and corresponding files. The youtube URL is handed to youtube-dl which downloads the Youtube video to the “./input” directory. Once the download is complete the video ID is passed to the “youtube-title.sh” and “youtube-tumb.sh” scripts which save the video title and thumbnail image. The title, thumbnail and video file for all videos in “./input” are processed and moved to the “./output” directory. If a file in “./input” is not an mp4 file it is deleted. This process repeats until the script is killed.

get_songs_http.sh

The get_songs_http.sh script collects song submissions from a remote website. It takes one command line argument: the web address of a list of youtube URLs (one per line). This address is specified in the run.sh script. For each line in the downloaded list get_songs_http.sh passes the URL to youtube-dl to download the video. Youtube-dl saves the video to the “./input” directory. Once the download is complete the Youtube ID of the downloaded video is passed to the “youtube-title.sh” and “youtube-tumb.sh” scripts which save the video title and thumbnail image. The title, thumbnail and video file for all mp4 videos in the “./input” directory are processed and moved to the “./output” directory. If a file in “./input” is not an mp4 file it is deleted. The script waits 60 seconds before repeating this process. This continues until the script is killed.

Well, I think this covers everything, it’s a kludge that’s for sure, but it worked well for my party. Maybe I’ll make some updates for my next gathering.