MPD Monitor [gav-pi6]

Raspberry Pi - MPD Monitor

At times, with my Multimedia System playing random music by default (obviously, it can be overridden with any number of Music Player Clients), I just wasn't sure which track was playing.

So, with a spare Raspbery Pi Zero W, a 4x20 dot matrix display, an enclosure that I bought from a seller who's no longer on Ebay, and a little bit of Python, we have a fully functioning MPD monitor (that also tells me if my Internet connection is live)!

Installation was simple:

Raspberry Pi - MPD Monitor
Raspberry Pi - MPD Monitor
  1. Assemble the enclosure.
  2. Mount the LCD (with LCD backpack) and Raspberry Pi to the enclosure.
  3. Connect VCC, GND, SDA and SCL from the Pi to the backpack.
  4. Install Raspbian Lite to a micro SD card.
  5. Put the sd card in the Pi and boot up.
  6. Enable I2C in the configuration.
    sudo raspi-config
  7. Install a couple of additional python modules:
    sudo apt install python3-smbus python3-mpd mpc
  8. Create the Python script
    nano mpd_lcd.py
    with the following contents:
    #!/usr/bin/python
    import smbus
    import time
    from mpd import MPDClient
    import socket

    # Server for MPD
    SERVER = "lounge-pi"
    client = MPDClient() # create client object
    client.timeout = 10 # network timeout in seconds (floats allowed), default: None
    client.idletimeout = None # timeout for fetching the result of the idle command is handled seperately, default: None

    # Host lookup for determining Internet connectivity
    INET_SERVER = "www.google.com"

    # Define some device parameters
    I2C_ADDR = 0x27 # I2C device address
    LCD_WIDTH = 20 # Maximum characters per line

    # Define some device constants
    LCD_CHR = 1 # Mode - Sending data
    LCD_CMD = 0 # Mode - Sending command

    LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
    LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
    LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
    LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

    LCD_BACKLIGHT = 0x08 # On
    #LCD_BACKLIGHT = 0x00 # Off

    ENABLE = 0b00000100 # Enable bit

    # Timing constants
    E_PULSE = 0.0005
    E_DELAY = 0.0005
    ZERO_DISP = 3

    #Open I2C interface
    #bus = smbus.SMBus(0) # Rev 1 Pi uses 0
    bus = smbus.SMBus(1) # Rev 2 Pi uses 1

    def lcd_init():
      # Initialist display
      lcd_byte(0x33,LCD_CMD) # 110011 Initialise
      lcd_byte(0x32,LCD_CMD) # 110010 Initialise
      lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
      lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
      lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
      lcd_byte(0x01,LCD_CMD) # 000001 Clear display
      time.sleep(E_DELAY)

    def lcd_byte(bits, mode):
      # Send byte to data pins
      # bits = the data
      # mode = 1 for data
      # 0 for command

      bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
      bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT

      # High bits
      bus.write_byte(I2C_ADDR, bits_high)
      lcd_toggle_enable(bits_high)

      # Low bits
      bus.write_byte(I2C_ADDR, bits_low)
      lcd_toggle_enable(bits_low)

    def lcd_toggle_enable(bits):
      # Toggle enable
      time.sleep(E_DELAY)
      bus.write_byte(I2C_ADDR, (bits | ENABLE))
      time.sleep(E_PULSE)
      bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
      time.sleep(E_DELAY)

    def lcd_string(message,line):
      # Send string to display
      message = message.ljust(LCD_WIDTH," ")
      lcd_byte(line, LCD_CMD)
      for i in range(LCD_WIDTH):
        lcd_byte(ord(message[i]),LCD_CHR)

    def music_details():
      # Set defaults just in case of errors
      artist = "Cannot connect to"
      album = SERVER
      song = ""
      length = 0
      position = 0

      # Connect to MPD on the server
      server_up = 1
      try:
        client.connect(SERVER, 6600)
      # Catch socket errors
      except IOError:
        server_up = 0
        print("Car_Server_LCD.py: Could not connect to " + SERVER)
      # Catch Other MPD Errors
      except MPDError:
        server_up = 0
        print("Car_Server_LCD.py: Could not connect to " + SERVER)

      if server_up == 1:
        status = client.status()
        if status['state'] == "play":
          position = int(float(status['elapsed']))
        track = client.currentsong()
        if len(track) > 0:
          artist = track['artist']
          if len(artist) > LCD_WIDTH:
            artist += " "
          album = track['album']
          if len(album) > LCD_WIDTH:
            album += " "
          song = track['title']
          if len(song) > LCD_WIDTH:
            song += " "
          length = int(track['time'])
        else:
          artist = "Unknown"
          album = "Unknown"
          song = "Unknown"

        # Close and disconnect from the server
        client.close()
        client.disconnect()

      return (artist, album, song, length, position)

    def format_string(old_string, cur_string, pos):
      if old_string != cur_string:
        # We need to reset the position on a new string
        pos = 0
      if len(cur_string) > LCD_WIDTH:
        return cur_string[pos:pos + 20]
      else:
        return cur_string

    def string_pos(cur_string, pos, zero_count):
      return_pos = 0
      if (pos + 20) < len(cur_string):
        if (zero_count < ZERO_DISP) and (pos == 0):
          zero_count = zero_count + 1
        else:
          zero_count = 0
          return_pos = pos + 1
      return (return_pos, zero_count)

    def is_connected(hostname):
      try:
        # see if we can resolve the host name -- tells us if there is
        # a DNS listening
        host = socket.gethostbyname(hostname)
        # connect to the host -- tells us if the host is actually
        # reachable
        s = socket.create_connection((host, 80), 2)
        s.close()
        return True
      except:
        pass
      return False

    def main():
      # Main program block

      # Initialise display
      lcd_init()

      # Define old variables to speed up display updates
      old_artist = ""
      old_album = ""
      old_song = ""
      # Define old displays to speed up display updates
      disp_artist = ""
      disp_album = ""
      disp_song = ""
      disp_timer = ""
      # Define the start positions of text strings
      start_artist = 0
      start_album = 0
      start_song = 0
      # Define the number of times a scrolling message has been at pos=0
      zero_artist = 0
      zero_album = 0
      zero_song = 0

      # Define an internet check timer
      inet_timer = 0

      while True:

        # Query the MPD server
        the_music = music_details()
        artist = the_music[0]
        album = the_music[1]
        song = the_music[2]
        length = the_music[3]
        position = the_music[4]

        # Convert the times into HH:SS
        song_len = time.strftime('%M:%S', time.gmtime(length))
        song_pos = time.strftime('%M:%S', time.gmtime(position))

        # Work out if we need to display part of a string
        artist_string = format_string(old_artist, artist, start_artist)
        old_artist = artist
        pos_artist = string_pos(artist, start_artist, zero_artist)
        start_artist = pos_artist[0]
        zero_artist = pos_artist[1]

        album_string = format_string(old_album, album, start_album)
        old_album = album
        pos_album = string_pos(album, start_album, zero_album)
        start_album = pos_album[0]
        zero_album = pos_album[1]

        song_string = format_string(old_song, song, start_song)
        old_song = song
        pos_song = string_pos(song, start_song, zero_song)
        start_song = pos_song[0]
        zero_song = pos_song[1]

        # Now work out if we have Internet connection, but only
        # do it every 10 seconds to save bandwidth
        if time.time() - inet_timer >= 10:
          if is_connected(INET_SERVER):
            inet = "*"
          else:
            inet = "-"
          inet_timer = time.time()

        timer = song_pos + " - " + song_len + " " + inet

        # Send some test
        if artist_string != disp_artist:
          disp_artist = artist_string
          lcd_string(artist_string, LCD_LINE_1)
        if album_string != disp_album:
          disp_album = album_string
          lcd_string(album_string, LCD_LINE_2)
        if song_string != disp_song:
          disp_song = song_string
          lcd_string(song_string, LCD_LINE_3)
        if timer != disp_timer:
          disp_timer = timer
          lcd_string(timer, LCD_LINE_4)

        time.sleep(0.2)

    if __name__ == '__main__':

      try:
        main()
      except KeyboardInterrupt:
        pass
      finally:
        lcd_byte(0x01, LCD_CMD)
  9. Call the script from /etc/rc.local
  10. Reboot and hope it works (you may want to run it from the command line first of all though)!

Note: there may be a slightly later version of the script on my useful scripts page (which may be safer, just in case I've got the multiple &nbsp; required to show white space delimiation for Python wrong.

Specifications

Case: Laser cut 20x4 LCD enclosure
Board: Rapsberry Pi Zero-W (version 1.1)
SoC: Broadcom BCM2835
CPU: ARM1176JZF-S (ARM11, ARMv6Z arch) @ 1Ghz
GPU: Broadcom VideoCore IV @ 250 MHz
RAM: 512MiBytes SDRAM
Storage: Sandisk Ultra, class 10 (16GByte micro SDHC)
Display: RGB backlight negative LCD 20x4 + extras (RGB on black)
with I2C LCD Backpack



Del.ico.us Digg Facebook Google LinkedIn LiveJournal NewsVine reddit StumbleUpon Twitter
Valid XHTML 1.0 Transitional Valid CSS! [Valid Atom 1.0] [Valid RSS 2.0]
[ Page last updated Fri 5th Apr 2024 | viewed 207 times ]