dirtyhorror
Posts: 11
Joined: Sun Sep 16, 2018 10:00 am

alsamixer volume control via GPIO buttons

Sun Sep 16, 2018 11:27 am

Hello everyone,
I was wondering If anyone could help me out with controlling the system volume with some buttons. What I'm doing is running a video looper with omxplayer, but after every video the volume setting inside of omxplayer reverts to the default before starting the next video. Since this happens the best way to control volume for the looper is the volume setting inside of alsamixer.

From SSH I can run:
amixer set Speaker 5%+
amixer set Speaker 5%-

These commands control the volume perfectly, but now I want to set up two GPIO buttons to be volume up and down buttons. Can this be done with a simple Python script? Any help?

Thanks!
Last edited by dirtyhorror on Sat Sep 22, 2018 10:41 am, edited 1 time in total.

User avatar
paddyg
Posts: 2227
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: alsamixer volume control via GPIO buttons

Sun Sep 16, 2018 6:45 pm

In python you can run terminal commands quite easily using the subprocess module. Best look up the documentation but you might do something like

Code: Select all

import subprocess
...
def change_volume_function(percent_change):
    if percent_change < 0:
        sign_char = '-'
    else:
        sign_char = '+'
    change_str = '{}%{}'.format(abs(percentage_change), sign_char)
    subprocess.Popen(['amixer', 'set', 'Speaker', change_str]
Have a look at the gpiozero docs for how to connect your gpio pins to enable this to happen
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

dirtyhorror
Posts: 11
Joined: Sun Sep 16, 2018 10:00 am

Re: alsamixer volume control via GPIO buttons

Mon Sep 17, 2018 12:32 pm

Thanks for the reply. I was having a hard time following the gpiozero doc to get this working, so what I did was edit a shutdown button script I had to run the volume command instead and it works! However my button only runs the script once and then it stops working. Here is the code:

Code: Select all

#!/bin/python

import RPi.GPIO as GPIO
import os

gpio1=38
GPIO.setmode(GPIO.BOARD)
GPIO.setup(gpio1, GPIO.IN, pull_up_down=GPIO.PUD_UP)

try:
    GPIO.wait_for_edge(gpio1, GPIO.FALLING)
    os.system("amixer set Speaker 5%+")
except:
    pass

GPIO.cleanup()
I also tried to add the volume down button into the script like this:

Code: Select all

#!/bin/python

import RPi.GPIO as GPIO
import os

gpio1=38
gpio2=40
GPIO.setmode(GPIO.BOARD)
GPIO.setup(gpio1, GPIO.IN, pull_up_down=GPIO.PUD_UP)

try:
    GPIO.wait_for_edge(gpio1, GPIO.FALLING)
    os.system("amixer set Speaker 5%+")
    
    GPIO.wait_for_edge(gpio2, GPIO.FALLING)
    os.system("amixer set Speaker 5%-")
except:
    pass

GPIO.cleanup()

This works, but it will only let me trigger gpio 2 after triggering gpio 1.

Can someone please help me fix the code to keep running and to be able to trigger either command at any time?

Thanks!

User avatar
paddyg
Posts: 2227
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: alsamixer volume control via GPIO buttons

Mon Sep 17, 2018 2:15 pm

If you want to keep on triggering increase or decrease then you will need to keep your program running in a loop. In gpiozero (or GPIO using callback functions) then the loop can just be a pause as in the code below. Alternatively it could be a `while True:` kind of thing.

It might not matter most of the time but it's a good idea to stop using os.system. Look at the python documentation to see why.

Code: Select all

#!/bin/python
from gpiozero import Button
from signal import pause
from subprocess import Popen

def increase_volume():
    Popen(['amixer', 'set', 'Speaker', '5%+'])

def decrease_volume():
    Popen(['amixer', 'set', 'Speaker', '5%-'])

button_up = Button(20) # bcm20 is board38 (I think, better check)
button_up.when_pressed = increase_volume
button_down = Button(21) # bcm21 is board40
button_down.when_pressed = decrease_volume

pause()
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

dirtyhorror
Posts: 11
Joined: Sun Sep 16, 2018 10:00 am

Re: alsamixer volume control via GPIO buttons

Mon Sep 17, 2018 10:52 pm

Thanks Paddy! That works beautifully!

I'm pretty happy with just being able to control the volume up and down, because it stays put afterwards, but is there an easy way to add to the code so that if you hold one of the buttons something else would happen?

Instead of setting up a 3rd button, I'd like to make the audio mute when gpio 21 is held.

The terminal command to mute the alsamixer is:
amixer set Speaker toggle

I tried this but it doesn't work and breaks script.

Code: Select all

#!/bin/python

from gpiozero import Button
from signal import pause
from subprocess import Popen

def increase_volume():
    Popen(['amixer', 'set', 'Speaker', '10%+'])

def decrease_volume():
    Popen(['amixer', 'set', 'Speaker', '10%-'])

def mute_volume():
    Popen(['amixer', 'set', 'Speaker', 'toggle'])

button_up = Button(20) # bcm20 is board38 (I think, better check)
button_up.when_pressed = increase_volume
button_down = Button(21) # bcm21 is board40
button_down.when_pressed = decrease_volume
button_mute = Button(21, hold_time=2)
button_mute.when_held = mute_volume

pause()


Thank you so much!

User avatar
paddyg
Posts: 2227
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: alsamixer volume control via GPIO buttons

Tue Sep 18, 2018 8:03 am

Hi, I don't think you need to set up a different `Button` as you're not using an additional physical button. You just need to add to the existing behavior of your existing button(s). I would try something like

Code: Select all

...
def mute_volume():
    Popen(['amixer', 'set', 'Speaker', 'toggle'])

button_up = Button(20)
button_up.when_pressed = increase_volume
button_down = Button(21, hold_time=2)
button_down.when_pressed = decrease_volume
button_down.when_held = mute_volume

pause()
PS you might want to include some extra logic so that you keep track (say in a global variable) on whether the sound is muted then unmute if either button is pressed but not held.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

dirtyhorror
Posts: 11
Joined: Sun Sep 16, 2018 10:00 am

Re: alsamixer volume control via GPIO buttons

Tue Sep 18, 2018 8:18 am

Thanks again, Paddy! That code works great! And I agree about setting up some extra logic to unmute if either volume buttons are pressed. That would definitely make the experience more intuitive. I will do some research and hopefully be able to get it working without bugging you some more :D

Thank you so much for you help

dirtyhorror
Posts: 11
Joined: Sun Sep 16, 2018 10:00 am

Re: alsamixer volume control via GPIO buttons

Tue Sep 18, 2018 10:09 am

I managed to break the code again.

Code: Select all


def unmute_volume():
    if mute_volume == True
    Popen(['amixer', 'set', 'Speaker', 'toggle'])

button_up = Button(20)
button_up.when_pressed = increase_volume
button_down = Button(21, hold_time=1)
button_down.when_pressed = decrease_volume
button_down.when_held = mute_volume
button_up.when_pressed = unmute_volume
button_down.when_pressed = unmute_volume

pause()
I tried to add a new process that would toggle the mute by a button press if the mute had previously been executed by holding the button. This obviously is not the right way to do this.. :lol:

Please lend me some guidance.

User avatar
paddyg
Posts: 2227
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: alsamixer volume control via GPIO buttons

Tue Sep 18, 2018 12:07 pm

Ah, you need to set the global variable and check it in the other functions so something along the lines of: (not checked or tested)

Code: Select all

#!/bin/python
from gpiozero import Button
from signal import pause
from subprocess import Popen

def increase_volume():
    global is_muted
    if is_muted:
        toggle_volume()
    Popen(['amixer', 'set', 'Speaker', '5%+'])

def decrease_volume():
    global is_muted
    if is_muted:
        toggle_volume()
    Popen(['amixer', 'set', 'Speaker', '5%-'])

def toggle_volume():
    global is_muted
    Popen(['amixer', 'set', 'Speaker', 'toggle'])
    is_muted = not is_muted

def mute_volume():
    global is_muted
    if not is_muted:
        toggle_volume()

is_muted = False  #set it off to begin with
 
button_up = Button(20)
button_up.when_pressed = increase_volume
button_down = Button(21)
button_down.when_pressed = decrease_volume
button_down.when_held = mute_volume
pause()
As you can see there is much duplication between increase_volume and decrease_volume so I would look at combining them but it's a bit messy with gpiozero callbacks (using globals isn't ideal either, which you have to do in order for this to be relatively simple code - look up on google why globals not ideal and things that can go wrong with python globals in functions)
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

dirtyhorror
Posts: 11
Joined: Sun Sep 16, 2018 10:00 am

Re: alsamixer volume control via GPIO buttons

Tue Sep 18, 2018 12:42 pm

Brilliant! Works perfectly! Thank you so much, Paddy!

dirtyhorror
Posts: 11
Joined: Sun Sep 16, 2018 10:00 am

Re: alsamixer volume control via GPIO buttons

Fri Sep 21, 2018 11:22 am

After some testing, everything works great except if you shutdown the Pi with the mute engaged. When it powers back up the behavior of the code is reversed and the volume controls no longer work. It just allows for one touch mute, and hold to unmute.

The cure was to unmute the system and then reboot. It started to work normal again after that.

Any fix, or just avoid shutting down with mute engaged?

User avatar
paddyg
Posts: 2227
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: alsamixer volume control via GPIO buttons

Fri Sep 21, 2018 3:25 pm

hi. the obvious solution would be to use amixer to determine state rather than a variable in the python code. I will have a look at some stage.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

User avatar
paddyg
Posts: 2227
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: alsamixer volume control via GPIO buttons

Sat Sep 22, 2018 12:13 am

OK, after a quick google https://linux.die.net/man/1/amixer I would suggest using 'amixer set Speaker mute' and '... unmute' instead of toggling. i.e. something like (again untested. And you see how I've tried to reduce the duplication in the increase and decrease functions by having a change function)

Code: Select all

#!/bin/python
from gpiozero import Button
from signal import pause
from subprocess import Popen

def increase_volume():
    change_volume('+')

def decrease_volume():
    change_volume('-')

def change_volume(ch_sign):
    Popen(['amixer', 'set', 'Speaker', 'unmute'])
    Popen(['amixer', 'set', 'Speaker', '5%{}'.format(ch_sign)])

def mute_volume():
    Popen(['amixer', 'set', 'Speaker', 'mute'])
 
button_up = Button(20)
button_up.when_pressed = increase_volume
button_down = Button(21)
button_down.when_pressed = decrease_volume
button_down.when_held = mute_volume
pause()
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

dirtyhorror
Posts: 11
Joined: Sun Sep 16, 2018 10:00 am

Re: alsamixer volume control via GPIO buttons

Sat Sep 22, 2018 9:57 am

Thanks Paddy! That did the trick!

BTW, Would you happen to know of any solution for an OSD that will overlay omxplayer and show the volume level from amixer? Since I am adjusting the master system volume I have lost my volume OSD. It is not super important, but would be very nice to have!

Return to “Python”