From 71338cd17fbccb530b117a615d67fb8c93488482 Mon Sep 17 00:00:00 2001 From: ABelliqueux Date: Wed, 20 Mar 2024 11:33:14 +0100 Subject: [PATCH] Add buttons, icons, menu logic --- mpdlisten.py | 159 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 125 insertions(+), 34 deletions(-) diff --git a/mpdlisten.py b/mpdlisten.py index 4575233..58445b0 100644 --- a/mpdlisten.py +++ b/mpdlisten.py @@ -29,14 +29,54 @@ GPIO.setmode(GPIO.BCM) RELAIS_1_GPIO = 17 GPIO.setup(RELAIS_1_GPIO, GPIO.OUT) # GPIO Assign mode -# SSD1306 setup +# Buttons GPIO +BTNS = { 'BTN_1' : dict(GPIO=7, state=1), + 'BTN_2' : dict(GPIO=8, state=1), + 'BTN_3' : dict(GPIO=11, state=1), + 'BTN_4' : dict(GPIO=25, state=1), + 'BTN_5' : dict(GPIO=9, state=1), + } +for BTN in BTNS: + GPIO.setup(BTNS[BTN]['GPIO'], GPIO.IN, pull_up_down=GPIO.PUD_UP) + +# SSD1306 setup - bouding box is (0, 0, 127, 63), 128x64 serial = i2c(port=1, address=0x3C) device = ssd1306(serial) -# GUI config -number_of_btn = 4 +# GUI config - top left is 0,0, bottom right is 127,63 +number_of_btns = 5 +btn_width = floor((device.width-4) / number_of_btns ) +btn_height = 10 +menu_line_width = 1 +menu_bar_y = device.height - btn_height - menu_line_width - 1 +ui_text_x = 4 +ui_vol_width = 6 +ui_vol_y = 4 +ui_vol_x = device.width - ui_vol_width - 8 +ui_vol_icon_coords = (ui_vol_x - 10, 4) +ui_vol_icon_polygon = [0,3,3,3,8,0,8,8,3,5,0,5] +play_icon = [0,0,8,4,0,8] +pause_icon = [0,0,3,0,3,8,5,8,5,0,8,0,8,8,0,8] +stop_icon = [0,0,8,0,8,8,0,8] +next_icon = [0,0,3,3,3,0,8,4,3,8,3,5,0,8] +prev_icon = [0,4,3,0,3,3,8,0,8,8,3,5,3,8] +menu_icon = [0,0, 5,0, 4,1, 8,4, 6,6, 2,4, 0,6] -# TODO: get ctrlc state +MODES_ORDER = ['playback', 'browse'] +MODES = { 'playback' : dict(BTN_1=dict(FUNCTION='prev', ICON=prev_icon, XY=(((btn_width+1)*0.5), (menu_bar_y + 2)) ), + BTN_2=dict(FUNCTION='stop', ICON=[0,0,8,0,8,8,0,8], XY=(((btn_width+1)*1.5), (menu_bar_y + 2))), + BTN_3=dict(FUNCTION='toggle', ICON=[0,0,8,4,0,8], XY=(((btn_width+1)*2.5), (menu_bar_y + 2))), + BTN_4=dict(FUNCTION='next', ICON=[0,0,3,3,3,0,8,4,3,8,3,5,0,8], XY=(((btn_width+1)*3.5), (menu_bar_y + 2))), + BTN_5=dict(FUNCTION='menu', ICON=[0,0, 5,0, 4,2, 8,5, 10,10, 7,7, 2,4, 0,6], XY=(((btn_width+1)*4.5), (menu_bar_y + 2))), + ), + 'browse' : dict(BTN_1=dict(FUNCTION='none', ICON=[0,0,8,0,8,8,0,8], XY=(((btn_width+1)*0.5), (menu_bar_y + 2)) ), + BTN_2=dict(FUNCTION='none', ICON=[0,0,8,0,8,8,0,8], XY=(((btn_width+1)*1.5), (menu_bar_y + 2))), + BTN_3=dict(FUNCTION='none', ICON=[0,0,8,0,8,8,0,8], XY=(((btn_width+1)*2.5), (menu_bar_y + 2))), + BTN_4=dict(FUNCTION='none', ICON=[0,0,8,0,8,8,0,8], XY=(((btn_width+1)*3.5), (menu_bar_y + 2))), + BTN_5=dict(FUNCTION='menu', ICON=[0,0, 5,0, 4,2, 8,5, 10,10, 7,7, 2,4, 0,6], XY=(((btn_width+1)*4.5), (menu_bar_y + 2))), + ), + } +# Becomes true when receiving SIGINT ctrlc_pressed = False mpd_client_status = {'volume': 'N/A', @@ -86,39 +126,47 @@ if 'MPD_PORT' in environ: mpd_port = environ['MPD_PORT'] static_ui = None -menu_mode = "playback" +current_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)) + # Format time as 00:00 + return "{:02d}:{:02d}".format(minute, second) + + +def apply_xy_offset(polygon:list, offset:tuple): + i=0 + while i < len(polygon): + polygon[i] += offset[0] + polygon[i+1] += offset[1] + i+=2 + return polygon + + +def offset_polygons(modes:dict): + for mode in modes: + for btn in modes[mode]: + modes[mode][btn]['ICON'] = apply_xy_offset(modes[mode][btn]['ICON'], modes[mode][btn]['XY']) 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 + # ~ draw.rectangle(device.bounding_box, outline="white", fill="black") + draw.line([(0, menu_bar_y - 2),(device.width, menu_bar_y - 2)], width=menu_line_width, fill="white") i = 1 - while i < number_of_btn: + while i < number_of_btns: 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") - + # Draw icons according to current mode + # ~ for mode in MODES: + for btn in MODES[current_mode]: + draw.polygon(MODES[mode][btn]['ICON'], fill="white") + draw.polygon(ui_vol_icon_polygon, fill="white") return im @@ -129,16 +177,44 @@ def update_display(device, currentsong, status): menu_line_width = 1 menu_bar_y = device.height - btn_height - menu_line_width # Draw dynamic UI - ui = static_ui + ui = static_ui.copy() 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") + draw.text((ui_vol_x, ui_vol_y), status['volume'], fill="white") + if current_mode == 'playback': + draw.text((ui_text_x, 2), currentsong['artist'], fill="white") + draw.text((ui_text_x, 14), currentsong['title'], fill="white") + draw.text((ui_text_x, 26), currentsong['album'], fill="white") + if 'elapsed' in status: + draw.text((ui_text_x, 38), "{}/{}".format(sectomin(status['elapsed']), sectomin(status['duration'])), fill="white") + device.contrast(0) device.display(ui) +def send_mpd_cmd(client, cmd:str): + if cmd == 'menu': + global current_mode + current_mode_index = MODES_ORDER.index(current_mode) + # Avoid out of range + if current_mode_index >= len(MODES_ORDER)-1: + current_mode_index = 0 + else: + current_mode_index += 1 + current_mode = MODES_ORDER[current_mode_index] + global static_ui + static_ui = generate_static_ui(current_mode) + if cmd == 'prev': + client.previous() + elif cmd == 'next': + client.next() + elif cmd == 'toggle': + client.pause() + elif cmd == 'stop': + client.stop() + else: + return 0 + return 1 + + def main(args): previous_sond_id = None previous_state = None @@ -159,15 +235,19 @@ def main(args): print("Check host and port are correct.") # ~ print(client.status()) # duration, elapsed, volume, repeat, random, single # ~ print(client.currentsong()) # artist, title, album + offset_polygons(MODES) + global ui_vol_icon_polygon + ui_vol_icon_polygon = apply_xy_offset(ui_vol_icon_polygon, ui_vol_icon_coords) global static_ui - static_ui = generate_static_ui(menu_mode) + static_ui = generate_static_ui(current_mode) while ctrlc_pressed is False: + # MPD 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'] + play_state = mpd_client_status['state'] + current_song_id = mpd_client_status['songid'] if play_state in mpd_states: if play_state == 'play': paused_since_seconds = 0 @@ -177,8 +257,8 @@ def main(args): 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))) + paused_since_seconds += 0.2 + print("Paused for {:.1f}".format(paused_since_seconds)) if (paused_since_seconds >= off_delay) and GPIO.input(RELAIS_1_GPIO): print("Off") # Relay off @@ -191,8 +271,19 @@ def main(args): # Relay off GPIO.output(RELAIS_1_GPIO, GPIO.LOW) previous_state = play_state - sleep(1) + sleep(.2) update_display(device, mpd_client_currentsong, mpd_client_status) + # Handle buttons + for BTN in BTNS: + # Avoid double trigger by saving the previous state of the button and commpare it to current state + if (GPIO.input(BTNS[BTN]['GPIO']) == 0) and (GPIO.input(BTNS[BTN]['GPIO']) != BTNS[BTN]['state']): + send_mpd_cmd(client, MODES[current_mode][BTN]['FUNCTION']) + print("{} pressed".format(MODES[current_mode][BTN]['FUNCTION'])) + # Save previous state + BTNS[BTN]['state'] = GPIO.input(BTNS[BTN]['GPIO']) + if (GPIO.input(BTNS[BTN]['GPIO']) == 1) and (GPIO.input(BTNS[BTN]['GPIO']) != BTNS[BTN]['state']): + # Save previous state + BTNS[BTN]['state'] = GPIO.input(BTNS[BTN]['GPIO']) device.cleanup() client.disconnect() return 0