211 lines
7.2 KiB
Python
211 lines
7.2 KiB
Python
#!/bin/env python
|
|
# This is adapted from mpdlisten.c : https://gist.github.com/sahib/6718139
|
|
# 2024 abelliqueux <contact@arthus.net>
|
|
#
|
|
# MPD client
|
|
import musicpd
|
|
from math import floor
|
|
from os import environ
|
|
import signal
|
|
import sys
|
|
from time import sleep
|
|
# Relay
|
|
import RPi.GPIO as GPIO
|
|
# OLED SSD1306
|
|
from luma.core.interface.serial import i2c
|
|
from luma.core.render import canvas
|
|
from luma.oled.device import ssd1306
|
|
from PIL import Image, ImageDraw
|
|
|
|
# MPD config
|
|
off_delay = 3
|
|
mpd_host=None
|
|
mpd_port=None
|
|
mpd_passwd = None
|
|
mpd_states = ['play', 'pause', 'stop', 'unknown']
|
|
|
|
# Relay GPIO setup
|
|
GPIO.setmode(GPIO.BCM)
|
|
RELAIS_1_GPIO = 17
|
|
GPIO.setup(RELAIS_1_GPIO, GPIO.OUT) # GPIO Assign mode
|
|
|
|
# SSD1306 setup
|
|
serial = i2c(port=1, address=0x3C)
|
|
device = ssd1306(serial)
|
|
|
|
# GUI config
|
|
number_of_btn = 4
|
|
|
|
# TODO: get ctrlc state
|
|
ctrlc_pressed = False
|
|
|
|
mpd_client_status = {'volume': 'N/A',
|
|
'repeat': 'N/A',
|
|
'random': 'N/A',
|
|
'single': 'N/A',
|
|
'consume': 'N/A',
|
|
'partition': 'N/A',
|
|
'playlist': 'N/A',
|
|
'playlistlength': 'N/A',
|
|
'mixrampdb': 'N/A',
|
|
'state': 'N/A',
|
|
'song': 'N/A',
|
|
'songid': 'N/A',
|
|
'time': '0',
|
|
'elapsed': '0',
|
|
'bitrate': 'N/A',
|
|
'duration': '0',
|
|
'audio': 'N/A',
|
|
'nextsong': 'N/A',
|
|
'nextsongid': 'N/A'
|
|
}
|
|
mpd_client_currentsong = {'file': 'N/A',
|
|
'last-modified': 'N/A',
|
|
'format': 'N/A',
|
|
'artist': 'N/A',
|
|
'albumartist': 'N/A',
|
|
'title': 'N/A',
|
|
'album': 'N/A',
|
|
'track': 'N/A',
|
|
'date': 'N/A',
|
|
'genre': 'N/A',
|
|
'time': 'N/A',
|
|
'duration': 'N/A',
|
|
'pos': 'N/A',
|
|
'id': 'N/A'}
|
|
|
|
|
|
# Source host, port from env variables
|
|
if 'MPD_HOST' in environ:
|
|
mpd_host = environ['MPD_HOST']
|
|
# Extract password if provided
|
|
if len(mpd_host.split('@')) > 1:
|
|
mpd_passwd = mpd_host.split('@')[0]
|
|
mpd_host = mpd_host.split('@')[1]
|
|
if 'MPD_PORT' in environ:
|
|
mpd_port = environ['MPD_PORT']
|
|
|
|
static_ui = None
|
|
menu_mode = "playback"
|
|
|
|
|
|
def sectomin(sec:str):
|
|
minute = 0
|
|
minute = floor(float(sec)/60)
|
|
second = round(float(sec)-minute*60)
|
|
return "{}:{}".format(str(minute), str(second))
|
|
|
|
|
|
def generate_static_ui(mode:str):
|
|
# ~ if mode == "playback":
|
|
btn_width = floor((device.width-3) / 4)
|
|
btn_height = 8
|
|
menu_line_width = 1
|
|
menu_bar_y = device.height - btn_height - menu_line_width
|
|
|
|
im = Image.new(mode='1', size=(device.width, device.height))
|
|
draw = ImageDraw.Draw(im)
|
|
# TODO: draw static UI only once as a PIL image
|
|
draw.rectangle(device.bounding_box, outline="white", fill="black")
|
|
draw.line([(0, menu_bar_y),(device.width, menu_bar_y)], width=menu_line_width)
|
|
# TODO : use loop
|
|
i = 1
|
|
while i < number_of_btn:
|
|
draw.line([((btn_width+1)*i, menu_bar_y),((btn_width+1)*i, device.height)], width=menu_line_width, fill="white")
|
|
i+=1
|
|
# Draw icons
|
|
draw.regular_polygon(bounding_circle=(((btn_width+1)*0.5), (menu_bar_y + btn_height / 2), 3), n_sides=3, rotation=90, fill="white")
|
|
draw.regular_polygon(bounding_circle=(((btn_width+1)*1.5), (menu_bar_y + btn_height / 2), 3), n_sides=3, rotation=270, fill="white")
|
|
draw.regular_polygon(bounding_circle=(((btn_width+1)*2.5), (menu_bar_y + btn_height / 2), 3), n_sides=4, rotation=0, fill="white")
|
|
draw.regular_polygon(bounding_circle=(((btn_width+1)*3.5), (menu_bar_y + btn_height / 2), 3), n_sides=3, rotation=180, fill="white")
|
|
|
|
return im
|
|
|
|
|
|
def update_display(device, currentsong, status):
|
|
# We want to display 4 buttons at the bottom of the screen
|
|
btn_width = floor((device.width-3) / 4)
|
|
btn_height = 8
|
|
menu_line_width = 1
|
|
menu_bar_y = device.height - btn_height - menu_line_width
|
|
# Draw dynamic UI
|
|
ui = static_ui
|
|
draw = ImageDraw.Draw(ui)
|
|
draw.text((10, 2), currentsong['artist'], fill="white")
|
|
draw.text((10, 14), currentsong['title'], fill="white")
|
|
draw.text((10, 26), currentsong['album'], fill="white")
|
|
if 'elapsed' in status:
|
|
draw.text((10, 38), "{}/{}".format(sectomin(status['elapsed']), sectomin(status['duration'])), fill="white")
|
|
device.display(ui)
|
|
|
|
|
|
def main(args):
|
|
previous_sond_id = None
|
|
previous_state = None
|
|
paused_since_seconds = 0
|
|
# MPDclient setup
|
|
client = musicpd.MPDClient()
|
|
if mpd_passwd is not None:
|
|
client.pwd = mpd_passwd
|
|
if mpd_host is not None:
|
|
client.host = mpd_host
|
|
if mpd_port is not None:
|
|
client.port = mpd_port
|
|
client.mpd_timeout = 5
|
|
try:
|
|
client.connect()
|
|
except musicpd.ConnectionError as errorMessage:
|
|
print(repr(errorMessage))
|
|
print("Check host and port are correct.")
|
|
# ~ print(client.status()) # duration, elapsed, volume, repeat, random, single
|
|
# ~ print(client.currentsong()) # artist, title, album
|
|
global static_ui
|
|
static_ui = generate_static_ui(menu_mode)
|
|
while ctrlc_pressed is False:
|
|
mpd_status = client.status()
|
|
if len(mpd_status):
|
|
mpd_client_status = mpd_status
|
|
mpd_client_currentsong = client.currentsong()
|
|
play_state = client.status()['state']
|
|
current_song_id = client.status()['songid']
|
|
if play_state in mpd_states:
|
|
if play_state == 'play':
|
|
paused_since_seconds = 0
|
|
if (current_song_id != previous_sond_id) and (previous_state != play_state):
|
|
print("Play")
|
|
# Relay on
|
|
GPIO.output(RELAIS_1_GPIO, GPIO.HIGH)
|
|
if play_state == 'pause':
|
|
if paused_since_seconds < off_delay:
|
|
paused_since_seconds += 1
|
|
print("Paused for {}".format(str(paused_since_seconds)))
|
|
if (paused_since_seconds >= off_delay) and GPIO.input(RELAIS_1_GPIO):
|
|
print("Off")
|
|
# Relay off
|
|
GPIO.output(RELAIS_1_GPIO, GPIO.LOW)
|
|
if play_state == 'stop' or play_state == 'unknown':
|
|
previous_sond_id = None
|
|
paused_since_seconds = 0
|
|
if previous_state != play_state:
|
|
print("Stopped")
|
|
# Relay off
|
|
GPIO.output(RELAIS_1_GPIO, GPIO.LOW)
|
|
previous_state = play_state
|
|
sleep(1)
|
|
update_display(device, mpd_client_currentsong, mpd_client_status)
|
|
device.cleanup()
|
|
client.disconnect()
|
|
return 0
|
|
|
|
|
|
def signal_handler(sig, frame):
|
|
global ctrlc_pressed
|
|
print('You pressed Ctrl+C!')
|
|
ctrlc_pressed = True
|
|
# ~ sys.exit(0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
sys.exit(main(sys.argv[1:]))
|