04-07-2019, 06:43 PM
Dear Tim and Remy, I have added a rotaty encoder for volume control (based on hrvoje's algorithm, see link in code). And a way to add the current song to a favorites list (or a list of files to be deleted) by communicating with mpd directly.
Code:
#!/usr/bin/python
#coding: utf8
# Script for mpc/mpd audio GPIO buttons partly based on remy1961's code on http://moodeaudio.org/forum/showthread.php?tid=198&pid=8382#pid8382
# Rotary encoder mostly based on hrvoje's code https://www.raspberrypi.org/forums/viewtopic.php?t=140250 . Comments on the reliability of RPi.GPIO library there
# mpd on default settings: localhost 6600
# Worked with one rotary encoder only (with two not reliably enough) in my case
# All buttons and rotary encoder connect to ground without hardware debouncing or hardware pull-up/-down. Rotary encoder bouncing is not improved with capacitors across A/B and ground.
import RPi.GPIO as GPIO # pigpio library does NOT work, due to loud noise (louder than the music)
import threading # rotary interrupt
import time
import os
import subprocess
#import datetime # for testing only
#GPIO.setwarnings(True)
GPIO.setmode(GPIO.BCM) # dset GPIO mode instead of GPIO.setmode(GPIO.BOARD) - Zählweise der Pins festlegen
#On playlist names no spaces
PLFAV = "Favoriten" # This is the playlist to which files are saved when pushing the button. /var/lib/mpd/playlists/Favoriten.m3u will be created if it does not exist yet.
PLDEF = "Aktuell" # This is the default playlist which is played when pushing the respective button (/var/lib/mpd/playlists/Aktuell.m3u needs to be generated e.g. by renaming an existing .m3u)
PLDLT = "Delete" # This is the delete list. /var/lib/mpd/playlists/Delete.m3u will be created if it does not exist.
#Define your GPIO pins (pin 20, 18, etc. not suitable in my case, i.e. not working or crashing), 7 buttons + 1 rotary encoder with button
SW_PREV = 22 # Previous
SW_NEXT = 4 # Next
SW_FAV = 26 # Add to favorites
SW_POWEROFF = 6 # Shut down
SW_PLAY = 17 # Toggle play/pause, e.g. button on the rotary encoder
SW_FW = 5 # Go forward within current song
SW_DEF = 13 # Play default playlist
SW_DLT = 25 # adds the currently played song to the delete list (to be deleted semi-manually on the master repository - the Raspberry Pi accesses a copy on my NAS only)
RO_A = 24 # Volume up
RO_B = 23 # Volume down
PRELL = 500 # debouncing for regular buttons
PRELLL = 1500 # debouncing for "add to list" buttons
PRELLROT = 30 # debouncing for rotary, favorable to diminish skipping. Higher value allows only slow volume increase
SCHRITT = 5 # % change in Volume
SPRING = 10 # % forward within current song
LockRotary = threading.Lock() # create lock for rotary switch
Current_A = 0 # Assume that rotary switch is not moving while we init software
Current_B = 0
#Code to manage BUTTONS
GPIO.setup(SW_PREV, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_NEXT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_FAV, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_POWEROFF, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_PLAY, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_FW, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_DEF, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(SW_DLT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(RO_A, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(RO_B, GPIO.IN, pull_up_down=GPIO.PUD_UP)
#Interrupt events
def eSW_PREV(channel):
subprocess.call(['mpc', 'prev' ])
# print str(datetime.datetime.now())[:19] + " previous" # for testing only
def eSW_NEXT(channel):
subprocess.call(['mpc', 'next' ])
# print str(datetime.datetime.now())[:19] + " next" # for testing only
def eSW_FAV(channel):
NAMEN = subprocess.check_output("mpc -f %file% current", shell=True)[:-1]
NAME = NAMEN.replace("\'","\\'\\'\'") # for the sigle quotes in echo
PFAD = "echo 'playlistadd " + PLFAV + " \"" + NAME + "\"' | netcat -q 0 localhost 6600" # or "-w 1" instead of "-q 0" if not saving reliably?
os.system(PFAD)
print "Zu " + PLFAV + ": " + NAMEN
def eSW_POWEROFF(channel):
subprocess.call(['mpc', 'stop' ])
subprocess.call(['sudo', 'poweroff' ])
# print str(datetime.datetime.now())[:19] + " shutting down" # for testing only
def eSW_PLAY(channel):
subprocess.call(['mpc', 'toggle' ])
# print str(datetime.datetime.now())[:19] + " play" # for testing only
def eSW_FW(channel):
subprocess.call(['mpc', 'seek', '+'+str(SPRING)+'%' ])
# print str(datetime.datetime.now())[:19] + " forward" # for testing only
def eSW_DEF(channel):
subprocess.call(['mpc','clear' ])
subprocess.call(['mpc','load',PLDEF ])
subprocess.call(['mpc','play' ])
# print str(datetime.datetime.now())[:19] + " default playlist" # for testing only
def eSW_DLT(channel):
NAMEN = subprocess.check_output("mpc -f %file% current", shell=True)[:-1]
NAME = NAMEN.replace("\'","\\'\\'\'") # for the sigle quotes in echo
PFAD = "echo 'playlistadd " + PLDLT + " \"" + NAME + "\"' | netcat -q 0 localhost 6600" # or "-w 1" instead of "-q 0" if not saving reliably?
os.system(PFAD)
print "Zur Löschliste " + PLDLT + ": " + NAMEN
# Rotary encoder interrupt is called for both inputs from rotary switch (A and B)
def eRO(A_or_B):
global Current_A, Current_B, LockRotary
Switch_A = GPIO.input(RO_A) # Read both of the switches
Switch_B = GPIO.input(RO_B)
if Current_A == Switch_A and Current_B == Switch_B: # Now check if state of A or B has changed. If not that means that bouncing caused it
return # Same interrupt as before (Bouncing)? Ignore interrupt!
Current_A = Switch_A # remember new state
Current_B = Switch_B # for next bouncing check
if (Switch_A == 0 and Switch_B == 0): # Both one active? Yes -> end of sequence
LockRotary.acquire() # get lock
if A_or_B == RO_B: # Turning direction depends on which input gave last interrupt
subprocess.call(['mpc', 'volume', '+'+str(SCHRITT) ])
else: # so depending on direction either increase or decrease counter
subprocess.call(['mpc', 'volume', '-'+str(SCHRITT) ])
LockRotary.release() # and release lock
return # done
# Declare interrupt events
GPIO.add_event_detect(SW_PREV, GPIO.FALLING, callback = eSW_PREV, bouncetime = PRELL)
GPIO.add_event_detect(SW_NEXT, GPIO.FALLING, callback = eSW_NEXT, bouncetime = PRELL)
GPIO.add_event_detect(SW_FAV, GPIO.FALLING, callback = eSW_FAV, bouncetime = PRELLL)
GPIO.add_event_detect(SW_POWEROFF, GPIO.FALLING, callback = eSW_POWEROFF, bouncetime = PRELL)
GPIO.add_event_detect(SW_PLAY, GPIO.FALLING, callback = eSW_PLAY, bouncetime = PRELL)
GPIO.add_event_detect(SW_FW, GPIO.FALLING, callback = eSW_FW, bouncetime = PRELL)
GPIO.add_event_detect(SW_DEF, GPIO.FALLING, callback = eSW_DEF, bouncetime = PRELL)
GPIO.add_event_detect(SW_DLT, GPIO.FALLING, callback = eSW_DLT, bouncetime = PRELLL)
GPIO.add_event_detect(RO_A, GPIO.FALLING, callback=eRO, bouncetime = PRELLROT)
GPIO.add_event_detect(RO_B, GPIO.FALLING, callback=eRO, bouncetime = PRELLROT)
# Main
while True:
time.sleep(1)
# the following is for testing pins. In my case e.g. pin 20 was low (0) all the time
'''
print str(GPIO.input(SW_PREV)) + " SW_PREV " + str(SW_PREV) + " | " + \
str(GPIO.input(SW_NEXT)) + " SW_NEXT " + str(SW_NEXT) + " | " + \
str(GPIO.input(SW_POWEROFF)) + " SW_POWEROFF " + str(SW_POWEROFF) + " | " + \
str(GPIO.input(SW_PLAY)) + " SW_PLAY " + str(SW_PLAY) + " | " + \
str(GPIO.input(RO_A)) + " RO_A " + str(RO_A) + " | " + \
str(GPIO.input(RO_B)) + " RO_B " + str(RO_B) + " | " + \ # for testing only
'''