How to stop repeat on button press/hold - Python - python

I was hoping someone might have some insight on how to stop a script from continuing to repeat if a button is held (or in my case pressed longer than a second)?
Basically i've a button setup on the breadboard, and I have it coded to play an audio file when the button is pressed. This works, however if the button isn't very quickly tapped, then the audio will repeat itself until button is fully released. Also if the button is pressed and held, the audio file will just repeat indefinitely.
I've recorded a quick recording to demonstrate the issue if its helpful, here: https://streamable.com/esvoy6
I should also note that I am very new to python (coding in general actually), so its most likely something simple that I just haven't been able to find yet. I am using gpiozero for my library.
Any help or insight is greatly appreciated!
Here is what my code looks like right now:
from gpiozero import LED, Button
import vlc
import time
import sys
def sleep_minute(minutes):
sleep(minutes * 60)
# GPIO Pins of Green LED
greenLight = LED(17)
greenButton = Button(27)
# Green Button Pressed Definition
def green_btn_pressed():
print("Green Button Pressed")
greenButton.when_pressed = greenLight.on
greenButton.when_released = greenLight.on
# Executed Script
while True:
if greenButton.is_pressed:
green_btn_pressed()
time.sleep(.1)
print("Game Audio Start")
p = vlc.MediaPlayer("/home/pi/Desktop/10 Second Countdown.mp3")
p.play()

So from a brief look at it, it seems that 'time.sleep(.1)' is not doing what you are expecting. Ie. it is obviously interrupted by button presses. This is not abnormal behaviour as button presses on Ardiuno and raspPi (guessing here) would be processed as interrupts.
The script itself does not contain any prevention from double pressing or press and hold etc.
Have you put in any debug lines to see what is executing when you press the button?
I would start there and make adjustments based on what you are seeing.
I am not familiar with this gpiozero, so I can't give any insight about what it may be doing, but looking at the code and given the issue you are having, I would start with some debug lines in both functions to confirm what is happening.
Thinking about it for a minute though, could you not just change the check to 'if greenButton.is_released:'? As then you know the button has already been pressed, and the amount of time it is held in for becomes irrelevant. May also want to put in a check for if the file is already playing to stop it and start it again, or ignore and continue playing (if that is the desired behaviour).
Further suggestions:
For this section of code:
# Executed Script
while True:
if greenButton.is_pressed:
green_btn_pressed()
time.sleep(.1)
print("Game Audio Start")
p = vlc.MediaPlayer("/home/pi/Desktop/10 Second Countdown.mp3")
p.play()
You want to change this to something along these lines:
alreadyPlaying = 0
# Executed Script
while True:
if greenButton.is_pressed:
green_btn_pressed()
#Check if already playing file.
if alreadyPlaying == 1:
# Do check to see if file is still playing (google this, not sure off the top of head how to do this easiest).
# If file still playing do nothing,
#else set 'alreadyPlaying' back to '0'
break
#Check if already playing file.
if alreadyPlaying == 0:
time.sleep(.1)
print("Game Audio Start")
p = vlc.MediaPlayer("/home/pi/Desktop/10 Second Countdown.mp3")
p.play()
alreadyPlaying = 1
Hopefully you get the idea of what I am saying. Best of luck!

i think you have to write something like this in your loop:
import time, vlc
def Sound(sound):
vlc_instance = vlc.Instance()
player = vlc_instance.media_player_new()
media = vlc_instance.media_new(sound)
player.set_media(media)
player.play()
time.sleep(1.5)
duration = player.get_length() / 1000
time.sleep(duration)

Related

A function is running despite me changing a variable that should stop it

I was hoping someone could help me with this issue. I'm hoping it's fairly simple to fix, but I have been trying for a while to figure this out. I have trimmed my larger code to this, as I believe the issue here is the crux of the problem.
I have a raspberry pi and an external button. This is on python3 on Linux. I am using GPIOZero for the button. The code below I believe is simple to understand, but basically, I want a function to loop at all times. When I press a button I want another function to run, but only if a variable is a certain number. I describe what I ultimately want to happen in a comment below, but my code is unfinished and simplified just for this problem.
I only want button.when_pressed to work when timer = 0. The problem is, once the code naturally gets into the button.when_pressed function, it never "lets go" of the function again. When I successfully redefine the variable to timer = 1, it still prints button is pressed when I press the button. I don't know why. To me, it seems like it should only work once timer = 0.
Any suggestions? I think I have a misunderstanding of global variables I will plan on researching. I'm not sure if that's the issue. I have also tried using break and continue to try to get it "back on its loop" but that hasn't worked either. Also I want to use button.when_pressed instead of btn.is_pressed, because I want the program to only do something when I'm holding the button down once, and not loop when I'm holding it down. In this case, I want button is pressed to print a single time. If I did btn.is_pressed it would print button is pressed every two seconds, which I dont want.
Thanks for any help. I'm happy to learn.
#!/usr/bin/python3
from gpiozero import Button
from time import sleep
import time
button = Button(4)
timer = 0
def press():
print("Button is pressed")
global timer
timer = 1
def loop():
global timer
timer = 1
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
If you want to disable the callback you had set to button.when_pressed, you need to do another assignment with button.when_pressed = None. This is listed in the documentation:
when_released:
[...] Set this property to None (the default) to disable the event.
It's not exactly clear what behavior you want from your current code. If you want the button to be active for 2 seconds, then be deactivated indefinitely, you can use:
button.when_pressed = press
sleep(2)
button.when_pressed = None
There's no need for a loop, since you don't want to repeat anything.
If you only want the button to be active for a single button press, that needs to happen within 2 seconds, you could instead call button.wait_for_press(2). I hesitate to write a full block of code for that though, as the docs don't specify how a timeout is signaled (it might be by return value, or via an exception). I don't have a Raspberry Pi so I can't test myself, but you could try it out and see what happens.
Treat your whole piece of code as one "black box", ask yourself, what is the input/output? button press or timer mode? (because I don't quite understand what does timer variable mean in your code)
Your code implies timer mode is the top level input to control the flow,
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
Is it expected?
If you allow user to press the button at any time, suggest you make button press to be your top level input, change the logic to keep when_pressed callback always on, set flag once triggered, and then check if the button has been pressed and still is_pressed in your while loop.
pressed = False
def play_video_1():
pass
def play_video_2():
pass
def press():
print("Button is pressed")
global pressed
pressed = True
button.when_pressed = press
while True:
if pressed and not_playing_video2:
if is_pressed:
play_video_1()
else:
pressed = False
play_video_2()
else:
play_video_2()

aplay music player with python, start/stop code

I am using python to write a small program that is supposed to play and stop a .wav file using aplay. I can get the file to play on the condition that button #1 is pressed, however I cannot seem to get the file to stop playing when button #2 is pressed. I have messed around with indentation and a couple other formats, but still have yet to achieve the desired result. I've included the base code that I first started with as it is the simplest. Any help would be wonderful, thank you! :)
from gpiozero import LED, Button
button1 = Button(22)
button2 = Button(17)
from time import sleep
import os
while True:
def kill():
os.system("pkill -9 aplay")
def playsong():
os.system("aplay test.wav6")
button1.when_pressed = playsong
button2.when_pressed = kill

Single key detection to work with the possibility of adding long inputs

I have been facing this problem for the last week,I thought it would be trivial but after trying many different approaches I don't know what else to try.
I have an application where I need to have key detection (to move a robot arm with the keyboard) but when I press enter I need to add some inputs, which should be as long as I want, just some normal input("insert here").
I know about the python libraries to get key detection, I got pynput to work successfully but it crashes my raspberry pi when I start and stop the threads a few times,I tried the Keyboard library but the whole root requirement is a let down, I also got curses to work and this seems to be solid and is (almost) not causing any issues, so detecting 1 key is not a problem.
I of course know how to name my files and get all the information that I need by doing input(), so if I had to use one of those options the job would be rather simple, the challenge comes when I try to apply both approaches together, basically detect the keys to do everything I need, and use python Input to get all the inputs from the user as soon as enter is pressed, all the libraries to detect key seems to take full control and they don't want to release it without a fight. They seem to expect the user to always require single key detection but in my case I would need to constantly turn it on and off, I couldn't figure out any efficient (or not) way to get it to work properly.
My question is:
What is the best approach to have key detection + full user input when needed with curses (or any alternative) in a non blocky way (as my code need to do some other things while listening for keys), is creating and destroying the whole thing the only alternative?
This is my current test code that I created for simplicity (which works but blocks everything while listening for keys):
import curses
import time
import os
stdscr = None
addInput = False
def SetupCurses():
global stdscr
stdscr = curses.initscr()
curses.cbreak()
stdscr.keypad(1)
def StartCurse():
global addInput
key = ''
while key != ord('q'):
key = stdscr.getch()
stdscr.addstr(str(key))
if key == ord('a'):
print("\nyou pressed a\n")
if key == 10:
print("\nyou pressed enter!\n")
addInput = True
break
def EndCurse():
curses.endwin()
while(True):
SetupCurses()
StartCurse()
EndCurse()
if addInput:
theinput = input("add your input\n")
print(theinput)
time.sleep(4)
addInput = False
#if there isn't any input to add I want the code to continue because there is non-related keys stuff to do, but of course it stopped at "StartCurse"
#if there is something to add the code can stop at addInput
The reason for the loop is because the user can save as many positions as he want, so after adding some inputs the possibility of adding more is there.
I saw people making this non-blocking by closing the curses loop after a few seconds (which stops everything anyway...) kind of getting the input by luck...something like:
def ExecuteCurses():
global AddInput
#open it and close it very quickly to grab a key if it is pressed
c = stdscr.getch()
if c == ord('a'):
print("you pressed a")
AddInput = True
time.sleep(1)
curses.endwin()
If you want a full and long user input you will need to use the curses.echo() and then use the stdscr.getstr(). That will wait for the user to press enter().
And to not block the program while getting input you need threading which you will have to import at the top of your program
And for the threading here is a link so you can find out more about threading.
I hope it answers your question

How to play different sound consecutively and asynchronously?

To be specific, I need to play sound in a while loop which is fast to execute. And the audio needs to be played separately. I've tried various functions/libraries: playsound, winsound, vlc. But none of them meet my demand.
Either the sounds are overlapped, or I need to wait for the sound to finish to continue the next line of code, which blocks the whole process, making the program running with unbearable lags.
Matters in playsound, winsound, vlc:
playsound: has block option but will block the process (with block=True) or overlap the sound (block=False).
winsound: with SND_ASYNC option, say I have audio A and audio B (which needs to be played after audio A), if audio B is played, audio A will stop immediately.
vlc: [000001ba245794d0] mmdevice audio output error: cannot initialize COM (error 0x80010106), which is kind of weird and nothing useful was found on Google. And this method seems not a good option to me and others. I use this simply for its is_playing() function and I can place the next sound to a queue.
So any advice?
If you're just after playing a sound and having it interrupt whatever was playing before, winsound does just that:
import winsound
from time import sleep
winsound.PlaySound("sound.wav", winsound.SND_ASYNC | winsound.SND_FILENAME)
sleep(1)
winsound.PlaySound("sound.wav", winsound.SND_ASYNC | winsound.SND_FILENAME)
sleep(3)
In this example (assuming sound.wav is longer than a second), the sound will start playing, be interrupted after 1 second and start playing again. The second sleep is there to avoid the script ending before the sound stops (stopping the script stops the sound).
If you want to queue up sounds to play one after the other, while your code keeps running:
import threading
import queue
import winsound
from time import sleep
q = queue.Queue()
def thread_function():
while True:
sound = q.get()
if sound is None:
break
winsound.PlaySound(sound, winsound.SND_FILENAME)
if __name__ == "__main__":
t = threading.Thread(target=thread_function)
t.start()
q.put("sound.wav")
print('One...')
sleep(1)
q.put("sound.wav")
print('Two...')
sleep(1)
q.put("sound.wav")
print('Three...')
q.put(None)
t.join()
This simple example queues up a sound which the thread starts playing, then while it is playing, it queues up the next one and a bit later a third. You'll noticed that the sounds play one after the other and the program only stops when the sounds complete playing (and the thread stops due to the None at the end of the queue).
If you're looking to play one sound over the other and have them mix, using winsound won't work, but you can use libraries like pyglet.
For example:
import pyglet
window = pyglet.window.Window()
effect = pyglet.resource.media('sound.wav', streaming=False)
#window.event
def on_key_press(symbol, modifiers):
effect.play()
#window.event
def on_draw():
window.clear()
if __name__ == "__main__":
pyglet.app.run()
This example opens a window and every time you press a key, it'll play the sound immediately, without interrupting previous sounds. The program ends immediately when you close the window.
This can be easily done with playsound.
playsound.playsound('alert.mp3', False)
Setting the second argument to False makes the function run asynchronously.

Using DirectIN Rotary Controller in PsychoPy2 in builder

I am trying to use the DirectIN Rotary Controller on Mac OS X (yosemite) with PsychoPy2 (v. 1.82.01). I would like to conduct a two-alternative forced choice experiment and use the buttons on the button box in order to respond; however, I cannot get psychopy to recognize the device.
Somebody with a similar problem was able to make the button box work in coder (see here), and there is a similar question using a different game controller here. So far I have gathered the following information:
Psychopy will recognize the button box as a joystick.
I need to use a code component in the trial routine.
The post from the emprisoft forum provides the following code:
import pyglet
joysticks = pyglet.input.get_joysticks()
for joy in joysticks:
if joy.device.name == 'Analog Scale Device':
joy.open()
break
def present_pair_joystick(trial,isi,curdata): #trial is a sound object, isis is the time to wait after response/end of sound, and curdata is a dictionary used to store response data
event.clearEvents()
while joy.buttons[0] or joy.buttons[1]:
continue
curdata['trial_start']=time.time()
trial.play()
dur = trial.getDuration()
while True:
if not (joy.buttons[0] and joy.buttons[1]):
if joy.buttons[0]:
curdata['rt'] = time.time() - curdata['trial_start']
curdata['resp'] = 'Word'
break
elif joy.buttons[1]:
curdata['rt'] = time.time() - curdata['trial_start']
curdata['resp'] = 'Nonword'
break
if 'escape' in event.getKeys():
core.quit()
if time.time() - curdata['trial_start'] > dur:
core.wait(isi)
else:
core.wait((dur - (time.time() - curdata['trial_start'])) + isi)
curdata['dur'] = dur
return
So I believe I can get the button box to work if I incorporate this code into a code component in builder, but I have not had any success with this (I am able to run an experiment without error, but the key responses are not recorded). Any help would be appreciated.
You say that "I cannot get PsychoPy to recognize the device" but don't actually say what you did, so it is hard to diagnose the problem.
Simply pasting in the code above wouldn't do anything visible for you, as it creates a function (present_pair_joystick()) which you aren't otherwise explicitly calling.
The first bit of code accesses the joystick but doesn't give you any visible feedback as to whether it succeeded, so let's address that. I'm not familiar with joysticks, so will just modify the code you found above and assume that it is appropriate. e.g. Put this in the "Begin Experiment" tab of a code component:
import pyglet
joysticks = pyglet.input.get_joysticks()
joystickFound = False
for joy in joysticks:
if joy.device.name == 'Analog Scale Device':
joy.open()
joystickFound = True
break
if joystickFound:
print('Joystick found!')
else:
print('Joystick NOT found.')
core.quit() # no point continuing
Assuming the connection is successful, then we can start dealing with the responses, at the beginning of each trial and on every screen refresh.
It seems that joystick button presses can keep registering even when they haven't been released, and hence could carry over from one trial to the next. hence, we need to ensure that at least for a moment, the button is NOT pressed at the beginning of a trial before we test for the next actual button press.
So put something like this in the "Begin Routine" tab of a code component:
buttonReleased = False
And something like this in the "Every frame" tab:
# check that the buttons go through a period of NOT being pressed
# before we check if they HAVE been pressed:
if not (joy.buttons[0] or joy.buttons[1]):
buttonReleased = True # the previous trial's press has been released
if buttonReleased:
response = ''
if joy.buttons[0]:
response = '0'
elif joy.buttons[1]:
response = response + '1' # allows you to capture if both buttons pressed simultaneously
if response != '':
thisExp.addData('buttonResponse', response) # save the response in the data
thisExp.addData('buttonRT', t) # store current time since trial start
continueRoutine = False # end trial once response received
event.clearEvents(eventType='joystick')
Caveat: I'm not entirely sure about the placement of the clearEvents call, and have no joystick to test this button press/release handling.

Categories