Add buttons, icons, menu logic
This commit is contained in:
parent
99bc13c8bb
commit
71338cd17f
159
mpdlisten.py
159
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
|
||||
|
|
Loading…
Reference in New Issue