soundslab/SoundslabDisplay.py

145 lines
6.9 KiB
Python
Raw Normal View History

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.displayOn = True
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 _displayOff(self):
self.st7789.set_backlight(0) # turn off the backlight
self.st7789.command(0x28) # turn off the display itself
self.displayOn = False
def _displayOn(self):
self.st7789.command(0x29) # turn on the display itself
self.st7789.set_backlight(1) # turn on the backlight
self.displayOn = True
def toggleDisplayOnOff(self):
if self.displayOn:
self._displayOff()
else:
self._displayOn()
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))