MPD Monitor [gav-pi6]
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:
- Assemble the enclosure.
- Mount the LCD (with LCD backpack) and Raspberry Pi to the enclosure.
- Connect VCC, GND, SDA and SCL from the Pi to the backpack.
- Install Raspbian Lite to a micro SD card.
- Put the sd card in the Pi and boot up.
- Enable I2C in the configuration.
sudo raspi-config
- Install a couple of additional python modules:
sudo apt install python3-smbus python3-mpd mpc
- Create the Python script
nano mpd_lcd.pywith 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) - Call the script from /etc/rc.local
- 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 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
|
||||
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 |
Computing Power
- Acer Aspire R3700
- Acknowledgements
- BOINC
- Desktop PC
- Eee PC 4G (701)
- Eee PC 901
- Gigabit Network
- Inspiron 14 5485 Laptop
- Kids PC 1
- Kids PC 2
- Media PC
- Mini-ITX PC
- My Useful Scripts
- Nano ITX PC
- Nook Simple Touch
- Processing Power
- Raspberry Pi
- Sharp Zaurus SL-C3200
- Storage Capacity
- The Server
- What Is Firmware