Banshee LCD Display

Back in 2009, Junke1990 wrote about using a Python script to read information from Rhythmbox on a Linux machine and to use an Arduino to display it on an serial LCD.
Ref http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1253025207

I thought this was a great idea and I built a little Atom based machine that runs 24/7 serving files and playing music through my stereo system. I found an LCD display big enough that I could read it from my recliner and life was good. However, I’ve found that newer versions of Rhythmbox aren’t compatible with this project. I think Rhythmbox no longer includes the required Rhythmbox-client. Because of this incompatibility, I’m still using an old version of Ubuntu with its compatible version of Rhythmbox.

I’ve been using Banshee a little on another computer and decided to see if I could update Junke’s project to use Banshee, an Arduino and an LCD. I used the Arduino LCD shield from nuElectronics for this first version for simplicity. One major hurdle is that I don’t know any Python but I found src/music_track_listener.py written by Gustavo Carneiro,
Nikos Kouremenos, Yann Leboulanger, Jean-Marie Traissard, Jonathan Schleifer, and Stephan Erb for Gajim. I think this script could be modified for a variety of music players but I limited myself to Banshee for now. [Gajim is another thing I know zero about - I just needed some code to start with.]

I started trashing everything in the script that didn’t look Banshee-like and didn’t cause a crash when it was removed. Then, I took what was left and made it executable. It needs error checking and to be more robust but it’s been running the last two days with no problems. If you start Banshee and then execute the script in a console, it should the artist and then the song title in the console. It works with nearly all the Internet radio stations I’ve tried and with my music collection. The script is attached.

After those two are playing nice, I run the Arduino portion. If the Arduino serial monitor isn’t open, the display gets spastic.

I’d appreciate any criticisms, especially ones that will improve the Python portion. I know the slash and burn method of coding is not one that provides optimal code. Obviously, all the real work was done by Junke and the Gajim team, I just mashed the pieces together.

#include <LiquidCrystal.h>
/* Based on project by Junke1990  Ref http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1253025207
and on music_track_listener.py 
## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro AT gmail.com>
##                    Nikos Kouremenos <kourem AT gmail.com>
## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org>
##                    Jonathan Schleifer <js-gajim AT webkeks.org>
##                    Stephan Erb <steve-e AT h3c.de>
*/

/* Code here is written to use an LCD shield from nuelectronics
requires python script interface to Banshee.  python sends "+", then artist name,
then "^", then song title and then another "^".  These character delimiters used
to avoid occassional confusion in original that resulted in a few scrambled displays 
before resync

*/

//Set up for LCD Shield
int RS = 8;
int ENABLE = 9;
int d4 = 4;
int d5 = 5;
int d6 = 6;
int d7 = 7;
int row =2;
int col =16;

////////////
char incoming;                          //temp storage for single character
char title[40];                         //char array for song title
char artist[40];                        //char array for artist name
int  Acounter;                          //counter for # of char's in artist name
int  Tcounter;                          //counter for # of char's in song title

LiquidCrystal lcd(RS, ENABLE, d4, d5, d6, d7); //create LCD object

void setup()
{   lcd.begin(col,row);                    
    Serial.begin(9600);
}

void getNewInfo()
{   for(int i=0; i<40; i++)               //read up to 40 characters
    {   artist[i] =  Serial.read();
        Acounter =i;                      //keep track of artist name length
        if (artist[i] == '^') break;      //if end of name, stop reading artist
    }
    if (Acounter>=18) Acounter =col+2;    //limit artist name to LCD width

    for(int i=0; i<40; i++)               //read up to 40 characters
    {   title[i] =  Serial.read();
        Tcounter =i;                       //keep track of song title length
        if (title[i] == '^') break;        //if end of name, stop reading title
    }          
    if (Tcounter>=18) Tcounter =col +2;    //limit artist name to LCD width

    lcd.clear();                           //clear the LCD
    
    lcd.setCursor(0,0);                    //move to first row, first column
    for(int i=1; i<Acounter-1; i++)        //skip first char, it's junk
    {  lcd.print(artist[i]);               //print artist name, 1 char at a time
       Serial.print (artist[i]);           //echo to serial monitor
    }
    Serial.println (" was the artist.");

   lcd.setCursor(0,1);                    //move to second row, first column
   for(int i=1; i<Tcounter-1; i++)        //skip first char, it's junk 
   {   lcd.print(title[i]);               //print song title, 1 char at a time
       Serial.print (title[i]);           //echo to serial monitor
   }
   Serial.println (" was the song title.");
}


void loop()
{  if (Serial.available() )
   {   delay(100);
       while (Serial.available() > 0) 
       {  incoming = Serial.read();
          if (incoming == '+')    getNewInfo();            //look for start of artist name
       }      
   }
}

SIMPLEmusic.py (3.96 KB)

In case anyone cares about this, I found that the Python script didn’t handle some Uni-code characters. These changes seem to improve the handling of Uni-code and the occasional Null.

#!/usr/bin/python
# -*- coding: utf-8 -*-
## Original source music_track_listener.py
##
## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro AT gmail.com>
##                    Nikos Kouremenos <kourem AT gmail.com>
## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org>
##                    Jonathan Schleifer <js-gajim AT webkeks.org>
##                    Stephan Erb <steve-e AT h3c.de>
##
### CONFIG ###
dev = "/dev/ttyUSB0"    # path to the arduino
rows = 2                # number of rows of your LCD
cols = 16               # number of cols of your LCD

import dbus
import dbus.glib
import gobject, time
import os
import unicodedata



class MusicTrackInfo(object):
	__slots__ = ['title', 'album', 'artist', 'duration', 'track_number',
		'paused']

class MusicTrackListener(gobject.GObject):
	__gsignals__ = {
		'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)),
	}

	_instance = None
	@classmethod
	def get(cls):
		if cls._instance is None:
			cls._instance = cls()
		return cls._instance

	def __init__(self):
		super(MusicTrackListener, self).__init__()
		self._last_playing_music = None

		bus = dbus.SessionBus()



		## Banshee
		bus.add_signal_receiver(self._banshee_state_changed_cb,
			'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine')
		bus.add_signal_receiver(self._player_name_owner_changed,
			'NameOwnerChanged', 'org.freedesktop.DBus',
			arg0='org.bansheeproject.Banshee')



	def _player_name_owner_changed(self, name, old, new):
		if not new:
			self.emit('music-track-changed', None)

	def _player_playing_changed_cb(self, playing):
		if playing:
			self.emit('music-track-changed', self._last_playing_music)
		else:
			self.emit('music-track-changed', None)

	def _player_playing_song_property_changed_cb(self, a, b, c, d):
		if b == 'rb:stream-song-title':
			self.emit('music-track-changed', self._last_playing_music)



	def _banshee_state_changed_cb(self, state):
		if state == 'playing':
			bus = dbus.SessionBus()
			banshee = bus.get_object('org.bansheeproject.Banshee',
				'/org/bansheeproject/Banshee/PlayerEngine')
			currentTrack = banshee.GetCurrentTrack()
			self._last_playing_music = self._banshee_properties_extract(
				currentTrack)
			self.emit('music-track-changed', self._last_playing_music)
		elif state == 'paused':
			self.emit('music-track-changed', None)

	def _banshee_properties_extract(self, props):
		info = MusicTrackInfo()
		info.title = props.get('name', None)
		info.album = props.get('album', None)
		info.artist = props.get('artist', None)
		info.duration = int(props.get('length', 0))
		return info


	def get_playing_track(self):
		'''Return a MusicTrackInfo for the currently playing
		song, or None if no song is playing'''

		bus = dbus.SessionBus()




		## Check Banshee playing track
		test = False
		if hasattr(bus, 'name_has_owner'):
			if bus.name_has_owner('org.bansheeproject.Banshee'):
				test = True
		elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
		'org.bansheeproject.Banshee'):
			test = True
		if test:
			banshee = bus.get_object('org.bansheeproject.Banshee',
				'/org/bansheeproject/Banshee/PlayerEngine')
			currentTrack = banshee.GetCurrentTrack()
			if currentTrack:
				song = self._banshee_properties_extract(currentTrack)
				self._last_playing_music = song
				return song


		return None
def display():
  error = os.system('echo "%s" >> %s' %("+",dev,))
  error = os.system('echo "%s" >> %s' %(track.artist,dev,))
  error = os.system('echo "%s" >> %s' %("^",dev,))
  error = os.system('echo "%s" >> %s' %(track.title,dev,))           #error here fixed by 2 encodes below
  error = os.system('echo "%s" >> %s' %("^",dev,))
  if error == 0:
    print '%s' % (track.artist)
    print '%s' % (track.title)
  else:
    print "Failed to display '%s'" %(message,)


### loop ###
# based on Junke1990
message = None
while True:
#if __name__ == '__main__':
   listener = MusicTrackListener.get()
   track = listener.get_playing_track()
   if track.title != message:
      message = track.title
      if track.title != None:                                                              #needed to prevent encoding 'NONETYPE' error
          track.title=track.title.encode("iso-8859-1", "ignore")       #correction for unicode error
      if track.artist != None:                                                            #needed to prevent encoding 'NONETYPE' error
          track.artist=  track.artist.encode("iso-8859-1", "ignore")  #correction for unicode error
      display()
   time.sleep(2)