#!/bin/env python # This is adapted from mpdlisten.c : https://gist.github.com/sahib/6718139 # 2024 abelliqueux # # 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:]))