[Plugin] pirate audio

@ashthespy
i use the prev next from voulumio API, but i display some information in the prev/next menu of pirate audio tft and thats not working when there is no position.
Sample (what i diplay):
10/18 (song 10 from 18 in queue)
prev / next (information which submenu is opened)
songtitle (title from queue with number from line 1)

AxLED

That makes sense, sorry for the noise! :slight_smile:

Hi,
thank you for your beautiful plugin. I finally got it working with the fix from reply 158. Would be nice to have the skipping possible even with Spotify Connect. But I have a problem with the screen timeout. If I get it right, the screen should turn off after 15 minutes or whatever is configured. Mine stays on forever, whether playing music or not. Also using the plug-inā€™s shutdown function didnā€™t turn the screen off (actually I donā€™t know if the pi was shut down or not).
Do you have any clue what could be going on?

Thank you again

@rayddorf

thanks for your response. About your questions:

Skipping Spotify: Which of the 2 available spotify plugins are you using? I developed and testet only for one of them and with a trial account of spotifiy (because it is not worth the monthly fee to me).

Screen: The screen will only turn off, if you choose sleeptimer on pirate audio menu. If you shutdown the pi via pirate audio menu, the screen turn off until shutdown. After shutdown and while connected to power supply the screen turns back on (from this state there is no way to control the screen). I am using my pi zero as portable solution with adafruit powerboost circuit, which cuts the power 10 seconds after complete shutdown.

AxLED

Thanks for getting back so quickly. I have made a PR that fixes Spotify connect including skipping.

So the screen is not turned off based on time, e.g. since Volumio last entered stop state? But what is the time setting in the plugin config then for?

@rayddorf

nice that you opened a PR right away, unfortunately it clashes with my current ideas and development, so I will not accept this one. But I will put your changes on the ToDo list.

To your 2nd question:


The minutes settings under pirate audio plugin configuration are for the sleep timer.
When you select the sleep timer on pirate audio hat following happens:

  • Display turns off
  • Pi shuts down after x minutes (x=as defined in plugin config)

AxLED

Hi AxLED,

I just did a fresh setup on a PI3 with the Pirate Audio Hat connected.
Following your instructions it is working seamlessly on v3.213 out of the box after enabling the plugin and doing a restart.

BR
Josef

Hi Drew,
your case looks very nice. Could you share the model?

1 Like

Hi AxLED,

I shutdown the Pi via a nightly crontab (/sbin/shutdown -H now).

Is there some python command I could use 5 minutes before the shutdown cron script to switch off the screen/ maybe switch off the plugin, and keep the screen off until next boot? - even though the power supply is still connected?

Another idea is to cut the pi power supply 30 minutes after shutdown using a cheap mains timer switch.

You can switch off the screen backlight (which basically turns it off) with:

disp.set_backlight(False)

Where disp is the display class (already set up in the Pirate Audio plugin script):

disp = ST7789.ST7789(
    height=240, #v0.0.6
    width=240, #v0.0.6
    rotation=90, # Needed to display the right way up on Pirate Audio
    port=0, # SPI port
    cs=1, # SPI port Chip-select channel
    dc=9, # BCM pin used for data/command
    backlight=13,
    spi_speed_hz=80 * 1000 * 1000,
    offset_left=0, #v0.0.6
    offset_top=0 #v0.0.6
)

I posted a link to a quick and dirty screensaver script earlier in this thread (post #148) which you could modify for your needs perhaps.

Thanks Darren,

I tried modifying your script and using disp.set_backlight(False), which works, but after shut-down the previous (last-used) ā€˜imageā€™, reappears on the Pirate Audio screen as before
(I presume this is a ā€˜screen bufferā€™?)

As a work-around I modified your script further, to display a black square on the screen prior to shutdown/poweroff.

Adding:

from PIL import Image, ImageDraw
buffer = Image.new(ā€œRGBā€, (240, 240))
draw = ImageDraw.Draw(buffer)
draw.rectangle((0, 0, 240, 240), (0, 0, 0))
disp.display(buffer)

ā€¦ replacing code after disp section in your version.

I added a crontab:
58 23 * * * /data/pirateoff.sh
59 23 * * * /sbin/shutdown -H now

[Ref: Drawing squares: https://github.com/pimoroni/st7789-python/blob/master/examples/320x240.py]
[Ref: Discussion on Pi poweroff/shutdown: What is the Difference between the "shutdown" and "halt" Commands? - Raspberry Pi Forums]

Any other suggestions for turning off the Pirate Audio backlight or power after pi shutdown?

@JungleMusic

as mentioned some posts before (see 166)
Screen: The screen will only turn off, if you choose sleeptimer on pirate audio menu. If you shutdown the pi via pirate audio menu, the screen turn off until shutdown. After shutdown and while connected to power supply the screen turns back on (from this state there is no way to control the screen). I am using my pi zero as portable solution with adafruit powerboost circuit, which cuts the power 10 seconds after complete shutdown.

AxLED

Hi,
first of all thank you AxLED and all volumio developers for your amazing work .
I really like what you have created.

My goal is to build a standalone music player.
I am using a raspizero with a pirateaudio headphone module and a UPS module.

I made some modifications to the display.py module to handle seek, next prev and backlight in player mode.

With this modifications it is possible to:

seek + or - by holding down the B or Y buttons
play next or previous track by double clicking on B or Y buttons
change the backlight by holding down the X button, in particular the backlight will be decreased by 20/100 every 0.75 seconds that the user holds down the X button.

I hope this will help someone who asked about the backlight capability.
I used the SW version of PWM so the backlight is not very stable, I will improve it using HW timers.

Greetings

#!/usr/bin/env python3

import time
from PIL import ImageFont, Image, ImageDraw, ImageStat, ImageFilter
from PIL import ImageFilter  # v0.0.4
import os
import os.path
#import ST7789 as ST7789
import ST7789 #v0.0.6
from socketIO_client import SocketIO
import requests
from io import BytesIO
from numpy import mean
import sys
import signal
import RPi.GPIO as GPIO
import math
import json
from signal import *
from time import strftime, gmtime  # v.0.0.4
import time  # v.0.0.4
# import logging
# logging.getLogger('socketIO-client').setLevel(logging.DEBUG)
# logging.basicConfig()


# get the path of the script
script_path = os.path.dirname(os.path.abspath(__file__))
# set script path as current directory
os.chdir(script_path)

# socketIO = SocketIO('localhost', 3000)

# Create ST7789 LCD display class.
disp = ST7789.ST7789(
    height=240, #v0.0.6
    width=240, #v0.0.6
    rotation=90,  # Needed to display the right way up on Pirate Audio
    port=0,       # SPI port
    cs=1,         # SPI port Chip-select channel
    dc=9,         # BCM pin used for data/command
    backlight=None,
    spi_speed_hz=80 * 1000 * 1000,
    offset_left=0, #v0.0.6
    offset_top=0 #v0.0.6
)


# read json file (plugin values)
with open('/data/configuration/miscellanea/pirateaudio/config.json', 'r') as myfile:
    data = myfile.read()
obj = json.loads(data)  # parse file

# read json file (volumio language)
with open('/data/configuration/miscellanea/appearance/config.json', 'r') as mylangfile:
    data_lang = mylangfile.read()
obj_lang = json.loads(data_lang)  # parse file
langcode = obj_lang['language_code']['value']
langpath = '/data/plugins/miscellanea/pirateaudio/i18n/strings_' + langcode + '.json'
if os.path.exists(langpath) is False:  # change to en as default language
    langpath = '/data/plugins/miscellanea/pirateaudio/i18n/strings_en.json'

# read json file (language file for translation)
with open(langpath, 'r') as mytransfile:
    data_trans = mytransfile.read()
obj_trans = json.loads(data_trans)  # parse file

WIDTH = 240
HEIGHT = 240
font_s = ImageFont.truetype(script_path + '/fonts/Roboto-Medium.ttf', 20)
font_m = ImageFont.truetype(script_path + '/fonts/Roboto-Medium.ttf', 24)
font_l = ImageFont.truetype(script_path + '/fonts/Roboto-Medium.ttf', 30)
font_fas = ImageFont.truetype(script_path + '/fonts/FontAwesome5-Free-Solid.otf', 28)
bg_default = Image.open('images/default.jpg').resize((240, 240))

albumart, artist, album, title, img_check = '', '', '', '', ''
mode = 'player'
title_queue, len_queue = [], 0  # v.0.0.4
position = ''  # v.0.0.4
nav_array_name, nav_array_uri, nav_array_type, nav_array_service = [], [], [], []
marker, listmax, liststart, listresult = 0, int(obj['listmax']['value']), 0, 0


BUTTONS = [5, 6, 16, obj['gpio_ybutton']['value']]
BACKLIGHT = 13
pwm_value = 100
# LABELS = ['A', 'B', 'X', 'Y']
GPIO.setmode(GPIO.BCM)  # Set up RPi.GPIO with the "BCM" numbering scheme
GPIO.setup(BACKLIGHT,GPIO.OUT)
pi_pwm = GPIO.PWM(BACKLIGHT,60)
pi_pwm.start(0)	
pi_pwm.ChangeDutyCycle(pwm_value)
# exit function (even is service is stopped)
def clean(*args):
    #disp.set_backlight(False)
    GPIO.cleanup(BACKLIGHT)
    GPIO.cleanup(BUTTONS)  # v0.0.4
    sys.exit(0)

for sig in (SIGABRT, SIGILL, SIGINT, SIGSEGV, SIGTERM):
    signal(sig, clean)
# exit function (even is service is stopped)


def on_connect():
    # print('connect')
    start_time = time.time()  # debug, time of code execution
    socketIO.on('pushState', on_push_state)
    socketIO.emit('getState', '', on_push_state)
    socketIO.on('pushBrowseSources', on_push_browsesources)
    socketIO.on('pushBrowseLibrary', on_push_browselibrary)
    socketIO.on('pushQueue', on_push_queue)
    socketIO.emit('getQueue', on_push_queue)
    # print("on_connect--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def on_disconnect():
    display_stuff('bg_default', obj_trans['DISPLAY']['LOSTCONNECTION'], 0, 0, 'info')


def navigation_handler():
    # start_time = time.time()  # debug, time of code execution
    global mode, nav_array_name, nav_array_uri, nav_array_type, marker, liststart, listresult
    if mode == 'player':
        mode = 'menu'
        emit_action = ['setSleep', {'enabled': 'true', 'time': strftime("%H:%M", gmtime(obj['sleeptimer']['value']*60))}]
        nav_array_name = [obj_trans['DISPLAY']['MUSICSELECTION'], 'Sleeptimer ' + str(obj['sleeptimer']['value']) + 'M', obj_trans['DISPLAY']['SHUTDOWN'], obj_trans['DISPLAY']['REBOOT']]
        nav_array_uri = ['', emit_action, 'shutdown', 'reboot']
        nav_array_type = ['', 'emit', 'emit', 'emit']
        listresult = 6
        display_stuff('bg_default', nav_array_name, marker, liststart)
    else:
        print('else navigation_handler() eingetreten')
    # print("navigation_handler--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def on_push_browsesources(*args):
    # start_time = time.time()  # debug, time of code execution
    global listresult  # v.0.0.4 removed some globals, as thex not needed here
    if mode == 'navigation':  # v.0.0.4 added, to make sure this getting not displayed on_connect
        listresult = len(args[0])
        i = 0
        append_n = nav_array_name.append  # to avoid dots in for loop
        append_u = nav_array_uri.append
        for i in range(listresult):
            append_n(args[0][i]['name'])
            append_u(args[0][i]['uri'])
        display_stuff('bg_default', nav_array_name, marker, 0)
    # print("on_push_browsesources--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def on_push_browselibrary(*args):
    # start_time = time.time()  # debug, time of code execution
    global listresult  # v.0.0.4 removed some globals, as thex not needed here
    reset_variable('navigation')
    listresult = len(args[0]['navigation']['lists'][0]['items'])  # v.0.0.4 code cleaning
    i = 0
    if listresult > 0:  # we have item entries
        append_s = nav_array_service.append  # to avoid dots in for loop
        append_t = nav_array_type.append
        append_n = nav_array_name.append
        append_u = nav_array_uri.append
        for i in range(listresult):
            if 'service' in args[0]['navigation']['lists'][0]['items'][i]:  # v.0.0.4
                append_s(args[0]['navigation']['lists'][0]['items'][i]['service'])  # v.0.0.4
            if 'title' in args[0]['navigation']['lists'][0]['items'][i]:  # v.0.0.4
                append_n(args[0]['navigation']['lists'][0]['items'][i]['title'])
            append_t(args[0]['navigation']['lists'][0]['items'][i]['type'])
            if 'uri' in args[0]['navigation']['lists'][0]['items'][i]:  # v.0.0.4 spotify check
                append_u(args[0]['navigation']['lists'][0]['items'][i]['uri'])  # v.0.04
        display_stuff('bg_default', nav_array_name, marker, liststart)
    elif listresult == 0:  # we have no item entries
        display_stuff('bg_default', obj_trans['DISPLAY']['EMPTY'], marker, liststart)
    # print("on_push_browselibrary--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def reset_variable(varmode):
    # start_time = time.time()  # debug, time of code execution
    global mode, nav_array_service, nav_array_name, nav_array_uri, nav_array_type, marker, liststart, img_check, albumart
    mode = varmode
    del nav_array_name[:]  # v.0.0.4 del is cleaner than = []
    del nav_array_uri[:]
    del nav_array_type[:]
    del nav_array_service[:]
    marker, liststart = 0, 0
    img_check, albumart = '', ''  # reset albumart so display gets refreshed
    # print("reset_variable--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def sendtodisplay(img):
    # start_time = time.time()  # debug, time of code execution
    disp.display(img)
    # time.sleep(0.1)  # ohne sleep 82% CPU, sleep: 0.5 = 40%, 0.25 = 53%, 0.1 = 70%
    # print("sendtodisplay--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def display_stuff(picture, text, marked, start, icons='nav'):  # v.0.0.4 test for better performance
    # start_time = time.time()  # debug, time of code execution
    global img3, listmax  # v.0.0.4
    i = 0
    if picture == 'bg_default':
        img3 = bg_default.copy()
    else:
        img3 = Image.open(picture).convert('RGBA')  # v.0.0.4
    draw3 = ImageDraw.Draw(img3, 'RGBA')

    if isinstance(text, list):  # check if text is array
        result = len(text)  # count items of list/array
        totaltextheight = 0
        # Loop for finding out the sum of textheight for positioning, only text to display
        listbis = start + listmax
        if listbis > result:
            listbis = result
        for i in range(start, listbis):  # v.0.0.4 range max werteliste
            len1, hei1 = draw3.textsize(text[0+i], font=font_m)
            totaltextheight += hei1
        i = 0
        y = (HEIGHT // 2) - (totaltextheight // 2)

        # Loop for creating text to display
        for i in range(start, listbis):  # v.0.0.4
            len1, hei1 = draw3.textsize(text[0+i], font=font_m)
            x2 = (WIDTH - len1)//2
            if x2 < 0:  # v.0.0.4 dont center text if to long
                x2 = 0
            if i == marked:
                draw3.rectangle((x2, y + 2, x2 + len1, y + hei1), (255, 255, 255))
                draw3.text((x2, y), text[0+i], font=font_m, fill=(0, 0, 0))
            else:
                draw3.text((x2 + 3, y + 3), text[0+i], font=font_m, fill=(15, 15, 15))  # shadow v.0.0.4
                draw3.text((x2, y), text[0+i], font=font_m, fill=(255, 255, 255))
            y += hei1
    else:
        result = 1  # needed for right pageindex
        len1, hei1 = draw3.textsize(text, font=font_m)
        x2 = (WIDTH - len1)//2
        y2 = (HEIGHT - hei1)//2
        draw3.rectangle((x2, y2, x2 + len1, y2 + hei1), (255, 255, 255))
        draw3.text((x2, y2), text, font=font_m, fill=(0, 0, 0))

    # draw symbols
    if icons == 'nav':  # v.0.0.4
        draw3.text((0, 50), u"\uf14a", font=font_fas, fill=(255, 255, 255))  # Fontawesome symbols ok
        draw3.text((210, 50), u"\uf151", font=font_fas, fill=(255, 255, 255))  # Fontawesome symbols up
        draw3.text((0, 170), u"\uf0e2", font=font_fas, fill=(255, 255, 255))  # Fontawesome symbols back
        draw3.text((210, 170), u"\uf150", font=font_fas, fill=(255, 255, 255))  # Fontawesome symbols down
    elif icons == 'info':
        draw3.text((10, 10), u"\uf05a", font=font_fas, fill=(255, 255, 255))  # Fontawesome symbols info
    elif icons == 'seek':
        draw3.text((210, 50), u"\uf04e", font=font_fas, fill=(255, 255, 255))  # Fontawesome symbols forward
        draw3.text((0, 170), u"\uf0e2", font=font_fas, fill=(255, 255, 255))  # Fontawesome symbols back
        draw3.text((210, 170), u"\uf04a", font=font_fas, fill=(255, 255, 255))  # Fontawesome symbols backward

    page = int(math.ceil((float(marked) + 1)/float(listmax)))
    pages = int(math.ceil(float(result)/float(listmax)))
    if pages != 1:  # only show index if more than one site
        pagestring = str(page) + '/' + str(pages)
        len1, hei1 = draw3.textsize(pagestring, font=font_m)
        x2 = (WIDTH - len1)//2
        draw3.text((x2, HEIGHT - hei1), pagestring, font=font_m, fill=(255, 255, 255))
    sendtodisplay(img3)
    # print("displaystuff--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


# position in code is important, so display_stuff works v.0.0.4
display_stuff('bg_default', obj_trans['DISPLAY']['WAIT'], 0, 0, 'info')
socketIO = SocketIO('localhost', 3000)


def seeking(direction):
    # start_time = time.time()  # debug, time of code execution
    global seek, duration
    step = 60000  # 60 seconds
    if direction == '+':
        if int(float((seek + step)/1000)) < duration:
            seek += step
            socketIO.emit('seek', int(float(seek/1000)))
            display_stuff('bg_default', [obj_trans['DISPLAY']['SEEK'], strftime("%M:%S", gmtime(int(float(seek/1000)))) + ' / ' + strftime("%M:%S", gmtime(duration))], 0, 0, 'seek')
    else:
        if int(float((seek - step)/1000)) > 0:
            seek -= step
            socketIO.emit('seek', int(float(seek/1000)))
            display_stuff('bg_default', [obj_trans['DISPLAY']['SEEK'], strftime("%M:%S", gmtime(int(float(seek/1000)))) + ' / ' + strftime("%M:%S", gmtime(duration))], 0, 0, 'seek')
    # print("seeking--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def prevnext(direction):
    # start_time = time.time()  # debug, time of code execution
    global position
    if direction == 'prev':
        position -= 1
    else:
        position += 1
    if position > len_queue - 1:  # set position to first entry to loop through playlist infinite
        position = 0
    elif position < 0:  # set position to last entry to loop through playlist infinite
        position = len_queue - 1
    display_stuff('bg_default', [str(position + 1) + '/' + str(len_queue), obj_trans['DISPLAY']['PREVNEXT'], title_queue[position]], 1, 0, 'seek')
    socketIO.emit('stop')
    socketIO.emit('play', {"value": position})
    # print("prevnext--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def on_push_queue(*args):
    global title_queue, len_queue
    # reset variables first
    del title_queue[:]
    len_queue = 0
    if len(args[0]) != 0:
        len_queue = len(args[0])
        append_t = title_queue.append  # to avoid dots in for loop
        for i in range(len_queue):
            append_t(args[0][i]['name'])


def on_push_state(*args):
    # start_time = time.time()  # debug, time of code execution
    global img, img2, dark, txt_col, str_col, bar_bgcol, bar_col, status, service, volume, albumart, img_check, mode, seek, duration, position

    def f_textsize(text, fontsize):
        w1, y1 = draw.textsize(text, fontsize)
        return w1

    def f_drawtext(x, y, text, fontstring, fillstring):
        draw.text((x, y), text, font=fontstring, fill=fillstring)

    def f_x1(textwidth):
        if textwidth <= WIDTH:
            x1 = (WIDTH - textwidth)//2
        else:
            x1 = 0
        return x1

    def f_content(field, fontsize, top, shadowoffset=1):
        if field in args[0]:
            if args[0][field] is not None:
                w1 = f_textsize(args[0][field], fontsize)
                x1 = f_x1(w1)
                f_drawtext(x1 + shadowoffset, top + shadowoffset, args[0][field], fontsize, str_col)  # shadow
                f_drawtext(x1, top, args[0][field], fontsize, txt_col)
                # return args[0][field]

    volume = int(args[0]['volume'])
    position = args[0]['position']  # v.0.0.4
    if mode == 'player':
        #status = args[0]['status'].encode('ascii', 'ignore')
        status = args[0]['status'] # v0.0.6
        #service = args[0]['service'].encode('ascii', 'ignore')
        service = args[0]['service'] # v0.0.6

        # if args[0]['albumart'].encode('ascii', 'ignore') != albumart:  # Load albumcover or radio cover (and only if changes)
        if args[0]['albumart'].encode('ascii', 'ignore').decode('utf-8') != albumart:  #v0.0.6 # Load albumcover or radio cover (and only if changes)
            # albumart = args[0]['albumart'].encode('ascii', 'ignore')
            albumart = args[0]['albumart'].encode('ascii', 'ignore').decode('utf-8')  #v0.0.6
            print('Albumart', albumart) #v0.0.6

            albumart2 = albumart
            if len(albumart2) == 0:  # to catch a empty field on start
                albumart2 = 'http://localhost:3000/albumart'
            if 'http' not in albumart2:
                albumart2 = 'http://localhost:3000'+args[0]['albumart']

            response = requests.get(albumart2)
            # img = Image.open(BytesIO(response.content)) # v.0.04 gab bei spotify probleme
            img = Image.open(BytesIO(response.content)).convert('RGBA')  # v.0.04 gab bei spotify probleme
            img = img.resize((WIDTH, HEIGHT))
            img = img.filter(ImageFilter.BLUR)  # Blur
            draw = ImageDraw.Draw(img, 'RGBA')
            # draw = ImageDraw.Draw(img)  # v.0.04 gab bei spotify probleme
            img2 = img.copy()

            # Light / Dark Symbols and bars, depending on background
            im_stat = ImageStat.Stat(img)
            im_mean = im_stat.mean
            mn = mean(im_mean)

            txt_col = (255, 255, 255)
            str_col = (15, 15, 15)  # v0.0.4 needed for shadow
            bar_bgcol = (200, 200, 200)
            bar_col = (255, 255, 255)
            dark = False
            if mn > 175:
                txt_col = (55, 55, 55)
                str_col = (200, 200, 200)  # v0.0.4 needed for shadow
                dark = True
                bar_bgcol = (255, 255, 255)
                bar_col = (100, 100, 100)
            if mn < 80:
                txt_col = (200, 200, 200)
        else:  # if albumart didnt change, copy the last unpasted version
            img = img2.copy()
            draw = ImageDraw.Draw(img, 'RGBA')

        # paste button symbol overlay in light/dark mode
        if status == 'play':
            # draw.text((4, 53), u"\uf04C", font=font_fas, fill=txt_col)  # Fontawesome symbol pause
            f_drawtext(4, 53, u"\uf04C", font_fas, txt_col)
        else:
            # draw.text((4, 53), u"\uf04b", font=font_fas, fill=txt_col)  # Fontawesome symbol play
            f_drawtext(4, 53, u"\uf04b", font_fas, txt_col)
        # draw.text((210, 53), u"\uf0c9", font=font_fas, fill=txt_col)  # Fontawesome symbol menu
        f_drawtext(210, 53, u"\uf0c9", font_fas, txt_col)
        # draw.text((210, 174), u"\uf028", font=font_fas, fill=txt_col)  # Fontawesome symbol speaker
        f_drawtext(210, 174, u"\uf028", font_fas, txt_col)

        f_content('artist', font_m, 7, 2)  # 'artist', font_m
        f_content('album', font_m, 35, 2)
        f_content('title', font_l, 105, 2)  # falscher top wert

        # volume bar
        vol_x = int((float(args[0]['volume'])/100)*(WIDTH - 33))
        draw.rectangle((5, 184, WIDTH-34, 184 + 8), bar_bgcol)  # background
        draw.rectangle((5, 184, vol_x, 184 + 8), bar_col)

        # time bar
        if 'duration' in args[0]:
            duration = args[0]['duration']  # seconds
            if duration != 0:
                # if 'seek' in args[0]:
                if 'seek' in args[0] and args[0]['seek'] is not None:  # v0.0.4 sometime seek = null or None
                    seek = args[0]['seek']  # time elapsed seconds
                    # if seek != 0:  # v0.0.4 seek=0 is ok to show
                    el_time = int(float(args[0]['seek'])/1000)
                    du_time = int(float(args[0]['duration']))
                    dur_x = int((float(el_time)/float(du_time))*(WIDTH-10))
                    draw.rectangle((5, 230, WIDTH-5, 230 + 8), bar_bgcol)  # background
                    draw.rectangle((5, 230, dur_x, 230 + 8), bar_col)

                    # v0.0.4 show remaining time of track
                    remaining = '-' + strftime("%M:%S", gmtime(duration - int(float(seek)/1000)))
                    # w4, y4 = draw.textsize(remaining, font_m)
                    w4 = f_textsize(remaining, font_m)
                    # draw.text((WIDTH - w4 - 2 + 2, 206 - 2 + 2), remaining, font=font_m, fill=str_col)  # shadow, fill by mean
                    f_drawtext(WIDTH - w4 - 2 + 2, 206 - 2 + 2, remaining, font_m, str_col)  # shadow, fill by mean)
                    # draw.text((WIDTH - w4 - 2, 206 - 2), remaining, font=font_m, fill=txt_col)  # fill by mean
                    f_drawtext(WIDTH - w4 - 2, 206 - 2, remaining, font_m, txt_col)  # fill by mean)

        # display only if img changed
        if img_check != img:
            img_check = img
            sendtodisplay(img)
    # print("on_push_state--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


img = Image.new('RGBA', (240, 240), color=(0, 0, 0, 25))
draw = ImageDraw.Draw(img, 'RGBA')
socketIO.once('connect', on_connect)
socketIO.on('disconnect', on_disconnect)


def handle_button(pin):
    # start_time = time.time()  # debug, time of code execution
    global mode, marker, liststart,position, pwm_value, seek, duration  # v.0.0.4
    browselibrary = False
    step = 30000  # 30 seconds
    if pin == 5:  # Button A, only needs single press function
        print("Button 5 service:", service)
        if mode == 'player':
            if (status == 'play') and (service == 'webradio'):
                socketIO.emit('stop')
            elif (status == 'play'):
                socketIO.emit('pause')
            else:
                socketIO.emit('play')
        elif mode == 'navigation':
            if len(nav_array_uri) != 0:
                if len(nav_array_type) == 0:
                    browselibrary = True
                else:
                    if nav_array_type[marker] == 'song' or nav_array_type[marker] == 'webradio' or nav_array_type[marker] == 'mywebradio':  # v.0.0.4 fix for mywebradio
                        socketIO.emit('replaceAndPlay', {"service": nav_array_service[marker], "type": nav_array_type[marker], "title": nav_array_name[marker], "uri": nav_array_uri[marker]})
                        reset_variable('player')
                    elif nav_array_type[marker] == 'playlist' and nav_array_service[marker] == 'mpd':  # v.0.0.4 modified because of spotifiy
                        socketIO.emit('playPlaylist', {'name': nav_array_name[marker]})
                        reset_variable('player')
                    elif nav_array_type[marker] == 'playlist' and nav_array_service[marker] == 'spop':  # v.0.0.4 condition added because of spotifiy
                        socketIO.emit('stop')  # v.0.0.4 fix otherwise change from any playing source to spotify dont work
                        time.sleep(2)  # v.0.0.4 fix otherwise change from any playing source to spotify dont work
                        socketIO.emit('replaceAndPlay', {"service": nav_array_service[marker], "type": nav_array_type[marker], "title": nav_array_name[marker], "uri": nav_array_uri[marker]})
                        reset_variable('player')
                    elif 'folder' in nav_array_type[marker]:
                        if nav_array_service[marker] == 'podcast':
                            display_stuff('bg_default', obj_trans['DISPLAY']['WAIT'], marker, liststart)  # note, please wait
                        browselibrary = True
                    elif 'radio-' in nav_array_type[marker]:  # the minus (-) is important, otherwise i cant decide between 'radiofolder' and 'webradiostream'
                        browselibrary = True
                    elif 'streaming-' in nav_array_type[marker]:
                        browselibrary = True
                    else:
                        display_stuff('bg_default', obj_trans['DISPLAY']['NOTSUPPORTED'], marker, liststart)

                if browselibrary is True:
                    # replace "mnt/" in uri through "music-library/", otherwise calling them dont work (at least in favourites)
                    uri = nav_array_uri[marker]
                    uri = uri.replace('mnt/', 'music-library/')
                    socketIO.emit('browseLibrary', {'uri': uri})
                    browselibrary = False
            else:
                reset_variable('player')
                socketIO.emit('getState', '', on_push_state)
        elif mode == 'menu':
            # socketIO.emit('getQueue', on_push_queue)  # refresh variables of queue
            if nav_array_type[marker] == 'emit':
                if 'setSleep' in nav_array_uri[marker][0]:
                    socketIO.emit(nav_array_uri[marker][0], nav_array_uri[marker][1])
                    display_stuff('bg_default', obj_trans['DISPLAY']['SETSLEEPTIMER'], 0, 0, 'info')
                    #disp.set_backlight(False)
                    pwm_value = 0
                    pi_pwm.ChangeDutyCycle(pwm_value)
                else:
                    socketIO.emit(nav_array_uri[marker])
                    display_stuff('bg_default', ['executing:', nav_array_uri[marker]], 0, 0, 'info')
            elif nav_array_type[marker] == 'seek':  # v.0.0.4
                mode = 'seek'
                display_stuff('bg_default', obj_trans['DISPLAY']['SEEK'], 0, 0, 'seek')
            elif nav_array_type[marker] == 'prevnext':  # v.0.0.4
                socketIO.emit('getQueue', on_push_queue)  # refresh variables of queue
                mode = 'prevnext'
                display_stuff('bg_default', [str(position + 1) + '/' + str(len_queue), obj_trans['DISPLAY']['PREVNEXT'], title_queue[position]], 1, 0, 'seek')
            else:  # browsesource
                reset_variable('navigation')
                socketIO.emit('getBrowseSources', '', on_push_browsesources)
        else:
            reset_variable('player')
            socketIO.emit('getState', '', on_push_state)

    if pin == 6:  # Button B, needs a pressed function in player mode
        if mode == 'player':
            tic = time.perf_counter()
            #toc = tic
            while not GPIO.input(6):
                toc = time.perf_counter()
                if toc - tic > 0.5 :
                    ##prevnext('prev')
                    #position -= 1
                    #if position < 0:  # set position to last entry to loop through playlist infinite
                    #    position = len_queue - 1
                    #socketIO.emit('stop')
                    #socketIO.emit('play', {"value": position})
                    if int(float((seek - step)/1000)) > 0:
                        seek -= step
                        socketIO.emit('seek', int(float(seek/1000)))
                time.sleep(0.25)
            if toc - tic < 0.25 and volume > 0 :
                socketIO.emit('volume', '-')
            elif toc - tic < 0.5 :
                position -= 1
                if position < 0:  # set position to last entry to loop through playlist infinite
                    position = len_queue - 1
                socketIO.emit('stop')
                socketIO.emit('play', {"value": position})
            # while not GPIO.input(6) and volume > 0:  # limit/exit at volume 0 so amixer dont go crazy
            #     socketIO.emit('volume', '-')
            #     time.sleep(0.5)
        elif mode == 'navigation' or mode == 'menu' or mode == 'seek' or mode == 'prevnext':
            reset_variable('player')
            socketIO.emit('getState', '', on_push_state)

    if pin == 16:  # Button X, needs a pressed function in navigation and menu mode
        if mode == 'player':
            tic = time.perf_counter()
            toc = tic
            while not GPIO.input(16):
                toc = time.perf_counter()
                if toc - tic > 0.75 :
                    #disp.set_backlight(False)
                    pwm_value -= 20
                    if pwm_value < 0 :
                        pwm_value = 100
                    pi_pwm.ChangeDutyCycle(pwm_value)
                    #disp.set_pwm_backlight(50)
                time.sleep(0.25)
            if toc - tic < 0.5 :
                navigation_handler()
                #disp.set_backlight(True)  # v.0.0.4
                if pwm_value < 10:
                    pwm_value = 100
                    pi_pwm.ChangeDutyCycle(pwm_value)
        elif mode == 'navigation' or mode == 'menu':
            while not GPIO.input(16):
                marker -= 1  # count minus 1
                if marker < 0:  # blaettere nach oben durch
                    marker = listresult - 1
                    if listresult > listmax - 1:  # dann aendere auch noch den liststart
                        liststart = int(liststart + (math.floor(listresult/listmax)*listmax))
                liststart = int(math.floor(marker/listmax)*listmax)  # definiert das blaettern zur naechsten Seite
                display_stuff('bg_default', nav_array_name, marker, liststart)
        elif mode == 'seek':  # v.0.0.4
            seeking('+')
        elif mode == 'prevnext':  # v.0.0.4
            prevnext('next')

    if pin == BUTTONS[3]:  # Button Y, needs a pressed function in  all modes
        if mode == 'seek':
            seeking('-')
        elif mode == 'prevnext':
            prevnext('prev')
        elif mode == 'player':
            tic = time.perf_counter()
            #toc = tic
            while not GPIO.input(BUTTONS[3]):
                toc = time.perf_counter()
                if toc - tic > 0.5 :
                    ##prevnext('next')
                    #position += 1
                    #if position > len_queue - 1:  # set position to first entry to loop through playlist infinite
                    #    position = 0
                    #socketIO.emit('stop')
                    #socketIO.emit('play', {"value": position})
                    if int(float((seek + step)/1000)) < duration:
                        seek += step
                        socketIO.emit('seek', int(float(seek/1000)))
                time.sleep(0.25)
            if toc - tic < 0.25 and volume < 100 :
                socketIO.emit('volume', '+')
            elif toc - tic < 0.5 :
                position += 1
                if position > len_queue - 1:  # set position to first entry to loop through playlist infinite
                        position = 0
                socketIO.emit('stop')
                socketIO.emit('play', {"value": position})
        else:
            
            while not GPIO.input(BUTTONS[3]):
                #if mode == 'player' and volume < 100:  # limit/exit at volume 100 so amixer dont go crazy:
                #    socketIO.emit('volume', '+')
                #    time.sleep(0.5)
                #elif mode == 'navigation' or mode == 'menu':
                if mode == 'navigation' or mode == 'menu':
                    marker += 1  # count plus 1
                    liststart = int(math.floor(marker/listmax)*listmax)  # definiert das blaettern zur naechsten Seite
                    if marker > listresult - 1:  # blaettere nach unten durch
                        marker = 0
                        liststart = 0
                    display_stuff('bg_default', nav_array_name, marker, liststart)
    # print("handle_button--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


def setup_channel(channel):
    # start_time = time.time()  # debug, time of code execution
    try:
        #print('register %d') % channel
        print('register %d' % channel) #v0.0.6
        GPIO.setup(channel, GPIO.IN, GPIO.PUD_UP)
        GPIO.add_event_detect(channel, GPIO.FALLING, handle_button, bouncetime=250)
        print('success')
    except (ValueError, RuntimeError) as e:
        print('ERROR:', e)
    # print("setup_channel--- %s seconds ---" % (time.time() - start_time))  # debug, time of code execution


for x in BUTTONS:
    setup_channel(x)


def main():
    socketIO.wait()
    time.sleep(0.5)


try:
    main()
except KeyboardInterrupt:
    clean()
    pass

2 Likes

Hi!
I am new to the community and new to using the Raspberry Pi.
Could you please write step by step how to install PirateAudio plugin on fresh Volumio v.3.233.
I have Pi Zero and Pirate Audio Headphone Amp. I have installed a clean Volumio OS - it works through the computer. And I donā€™t know what to do next, because Iā€™m green with itā€¦ Thanks.

Please have a look to post #108.
Cheers

Hi @raydorf, Iā€™m glad you like it.



The 3mf files for 3D printing are here on Github.
You are all welcome to make one. (easiest way for non-github users to get the files is to right click the green code button and ā€œdownload zipā€)
Iā€™ve all hacked the display.py a bit to make it a bit more like volumio in its appearance.
Smiles
Drew

1 Like

thanks @Dr_Ew Iā€™ll try and print this is the next day or so

1 Like

whats the recommended install methiod now for a pi 2 zero w ?

@mjb152 I think itā€™s still this post 110.

Dear pirate audio hat users,

good news.

My plugin pirate audio is now installable via the ā€œplugin test modeā€ in volumio 3.

Steps to install:

  • open your browser and enter volumio.local/dev
  • set ā€œPlugin Test Modeā€ to True (press Button true)
  • change url in your browser to volumio.local
  • under plugins ā†’ User Interface you can install Pirate Audio Plugin as beta

pirateaudio1
pirateaudio2

I am looking forward to your feedback.

AxLED

1 Like