soundslab/SoundslabDisplay.py

128 lines
6.3 KiB
Python

import time
from PIL import Image, ImageFont, ImageDraw
from ST7789 import ST7789
from glob import glob
import struct
import smbus
import sys
import musicpd
from math import floor
class SoundslabDisplay:
def __init__(self, player_client):
self.screen_size = 240, 240
self.spi_speed_mhz = 80
self.st7789 = ST7789(
rotation=90,
port=0,
cs=1,
dc=9,
backlight=13,
spi_speed_hz=self.spi_speed_mhz * 1000 * 1000
)
self.current_background = Image.new("RGBA", self.screen_size, (0, 0, 255, 255))
self.current_overlay = Image.new("RGBA", self.screen_size, (0, 0, 0, 0))
self.current_menu = Image.new("RGBA", self.screen_size, (0, 0, 0, 0))
self.fg_color = (255, 255, 255, 211)
self.bg_color = (0, 0, 0, 127)
# the connection to mpd passed in
self.player_client = player_client
# track battery voltage over time so we can figure out if we're charging the battery or not
self.previous_voltage = 0.0
# track currently playing song's art path to eliminate unnecessary reloads of the same image file
self.current_art_path = None
def getBatteryState(self):
bus = smbus.SMBus(1)
address = 0x36
read = bus.read_word_data(address, 2)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
voltage = swapped * 78.125 / 1000000
read = bus.read_word_data(address, 4)
swapped = struct.unpack("<H", struct.pack(">H", read))[0]
capacity = swapped / 256
self.previous_voltage = voltage
return (voltage, capacity)
def updateOverlay(self):
# initialize overlay
im_overlay = Image.new("RGBA", self.screen_size, (0, 0, 0, 0))
font = ImageFont.truetype(font='/usr/share/fonts/truetype/hack/Hack-Bold.ttf', size=18)
small_font = ImageFont.truetype(font='/usr/share/fonts/truetype/hack/Hack-Regular.ttf', size=14)
overlay = ImageDraw.Draw(im_overlay)
# draw four rects on edges of screen_size
overlay.rectangle([(0, 0), (self.screen_size[0], 20)], self.bg_color) # top bar
overlay.rectangle([(0, self.screen_size[1] - 20), (self.screen_size[0], self.screen_size[1])], self.bg_color) # bottom bar
overlay.rectangle([(0, 20), (20, self.screen_size[1] - 20)], self.bg_color) # left bar
overlay.rectangle([(self.screen_size[0] - 20, 20), (self.screen_size[0], self.screen_size[1] - 20)], self.bg_color) # right bar
# get status from mpd
current_status = self.player_client.status()
current_track_number = int(current_status['song']) + 1
total_track_number = current_status['playlistlength']
track_progress_percent = float(current_status['elapsed']) / float(current_status['duration'])
print("track progress:" + str(track_progress_percent))
# add track # / total #
track_and_queue = str(current_track_number) + " of " + str(total_track_number)
overlay.text((10, 0), track_and_queue, font=font, fill=self.fg_color)
# add battery level
previous_voltage = self.previous_voltage
(voltage, capacity) = self.getBatteryState()
battery_display = '{:02.2f}'.format(capacity) + '%'
battery_display_offset = 80
print("Battery: voltage: " + str(voltage) + " prev voltage: " + str(previous_voltage) + " capacity: " + str(capacity))
# below method doesn't work - very inaccurate!
# should probably track over a longer period of time, which will mean
# it's harder to come up with a valid "charging or not" decision
# if voltage > previous_voltage:
# # we're probably charging!
# battery_display = 'Chrg ' + battery_display
# battery_display_offset = 130
overlay.text((self.screen_size[0] - battery_display_offset, 0), battery_display, font=font, fill=self.fg_color)
# add progress meter
overlay.rectangle([(70, self.screen_size[1] - 15), (self.screen_size[0] - 70, self.screen_size[1] - 5)], fill=(0, 0, 0, 0), outline=self.fg_color, width=1)
overlay.rectangle([(70, self.screen_size[1] - 15), (int((self.screen_size[0] - 140) * track_progress_percent) + 70, self.screen_size[1] - 5)], fill=self.fg_color, outline=self.fg_color, width=1)
# add playhead position and song duration displays at beginning and end of progress meter
(time_width, time_height) = overlay.textsize(text='00:00', font=small_font)
playhead = str(floor(float(current_status['elapsed']) / 60)) + ":" + "{:02d}".format(floor(float(current_status['elapsed']) % 60))
duration = str(floor(float(current_status['duration']) / 60)) + ":" + "{:02d}".format(floor(float(current_status['duration']) % 60))
overlay.text((5, self.screen_size[1] - 20 + floor(time_height / 2)), playhead, font=small_font, fill=self.fg_color)
overlay.text((self.screen_size[0] - 5 - time_width, self.screen_size[1] - 20 + floor(time_height / 2)), duration, font=small_font, fill=self.fg_color)
self.current_overlay = im_overlay.copy()
def updateAlbumArt(self):
current_status = self.player_client.status()
song_data = self.player_client.playlistid(current_status['songid'])
path_info = song_data[0]['file'].split('/')
cover_image_file = '/media/usb0/' + path_info[0] + '/' + path_info[1] + '/cover.jpg'
if cover_image_file != self.current_art_path:
self.current_art_path = cover_image_file
im = Image.open(cover_image_file).convert("RGBA")
image = im.resize((200, 200))
# get dominant color from album art
#test_image = im.convert("RGB")
#test_image.resize((1, 1), resample=0)
#dominant_color = test_image.getpixel((0, 0))
# get top left pixel color instead and use that!
dominant_color = image.getpixel((0, 0))
backing = Image.new("RGBA", self.screen_size, dominant_color)
backing.paste(image, (20, 20))
self.current_background = backing.copy()
def updateDisplay(self):
tempoutput = Image.alpha_composite(self.current_background, self.current_overlay)
self.st7789.display(Image.alpha_composite(tempoutput, self.current_menu))