read keyboard/stdin asynchronously - python

I have a loop where I would like to do something until a key is pressed.
Is there a way to read input from stdin in another thread until a specific key is pressed?
Is there a better way to do this?
def capture_loop(dev_path, breakout_key="a"):
captured_devs = []
capture = True
def wait_for_key():
global capture
while True:
i, o, e = select.select([sys.stdin], [], [], 10)
if len(i) > 0:
input_string = i[0].read()
if breakout_key in str(input_string):
capture = False
break
else:
print("You said nothing!")
t = threading.Thread(target=wait_for_key)
t.setDaemon(False)
t.start()
while capture:
print("Doing Stuff until the 'a' key is pressed")
time.sleep(10)
print("Done doing stuff")

If all you want is to do something until a key combination is pressed, but you don't care what key combination use, you can catch a KeyboardInterrupt exception which is raised when you press control-C:
try:
while True:
print("Doing something until C-c is pressed...")
time.sleep(1)
except KeyboardInterrupt:
print("Done!")

Related

Stop While Loop mid execution in python

I am trying to stop a while loop mid execution, if I reverse the value of 'runWhile' mid execution it simply waits until it's over.
Problem: I need it to stop immediately whenever I press f10 on the keyboard.
from pynput import keyboard
import threading
import datetime, time
def exec():
while runWhile:
print("There I go")
time.sleep(3)
print("I overtaken")
time.sleep(3)
print("You cant stop me until I finish")
def on_press(key):
global runWhile # inform function to assign (`=`) to external/global `running` instead of creating local `running`
if key == keyboard.Key.f5:
runWhile = True
t = threading.Thread(target=exec)
t.start()
if key == keyboard.Key.f10:
# to stop loop in thread
print("loading STOPPED", datetime.datetime.now()) #, end='\r')
runWhile = False
if key == keyboard.Key.f11:
# stop listener
print("listener TERMINATED", datetime.datetime.now()) #, end='\r')
return False
#--- main ---
with keyboard.Listener(on_press=on_press) as listener:
listener.join()
Im using pynput, docs here
based on #furas code
Here is a solution I made. I created my own delay function as follows:
def delay(amount): #delay time in seconds
for i in range(int(amount*60)):
time.sleep(0.01)
if runWhile == False:
return True
break
You would replace your delay(3) with
if delay(3):
break
This would wait 3 seconds, however if during that time, runWhile was false, it will break out of the loop. Your code would look like this:
from pynput import keyboard
import threading
import datetime, time
def delay(amount): #delay time in seconds
for i in range(int(amount*60)):
time.sleep(0.01)
if runWhile == False:
return True
break
def exec():
while runWhile:
print("There I go")
if delay(3):
break
print("I overtaken")
if delay(3):
break
print("You cant stop me until I finish")
def on_press(key):
global runWhile # inform function to assign (`=`) to external/global `running` instead of creating local `running`
if key == keyboard.Key.f5:
runWhile = True
t = threading.Thread(target=exec)
t.start()
if key == keyboard.Key.f10:
# to stop loop in thread
print("loading STOPPED", datetime.datetime.now()) #, end='\r')
runWhile = False
if key == keyboard.Key.f11:
# stop listener
print("listener TERMINATED", datetime.datetime.now()) #, end='\r')
return False
#--- main ---
with keyboard.Listener(on_press=on_press) as listener:
listener.join()

KeyboardInterrupt not working in Python to stop thread

I want to capture and store the images from live streams using two threads. I want to stop the threads with Ctrl + C keyboard interrupt. I am calling a function from
if name == "main":
but it is not working. I am using spyder with an anaconda environment. I also tried it using terminal but it is not working. Can anyone explain why and how it will work?
The code is as follow
class ImageGrabber(threading.Thread):
def __init__(self, ID):
threading.Thread.__init__(self)
self.ID=ID
self.cam=cv2.VideoCapture(ID)
self.fps = self.cam.get(cv2.CAP_PROP_FPS)
self.w=self.cam.get(cv2.CAP_PROP_FRAME_WIDTH)
self.h=self.cam.get(cv2.CAP_PROP_FRAME_HEIGHT)
(self.grabbed, self.frame) =self.cam.read()
print(f"Camera Opened with fps {self.fps}, width = {self.w}, and height = {self.h}")
plt.imshow(self.frame)
plt.show()
self.stopped = False
def run(self):
global frames
global exitProgram
# print("Entered Grabbed Run ******:")
while not self.stopped:
if not self.grabbed or exitProgram is True:
print("Exit Command reached")
self.stop()
self.cam.release()
else:
(self.grabbed, self.frame) =self.cam.read()
frames.put(self.frame)
def stop(self):
self.stopped = True
class ImageSaver(threading.Thread):
def __init__(self,grabber,codec):
threading.Thread.__init__(self)
global saveImages
fourcc = cv2.VideoWriter_fourcc(*codec)
self.fName=self.getDateStamp() + '.avi'
if saveImages is False:
self.out = cv2.VideoWriter(self.fName, fourcc,grabber.fps ,(int(grabber.w),int(grabber.h)))
def getDateStamp(self):
filedate = str(datetime.now())
filedate = filedate[0:-7]
filedate = filedate.replace(':', '_')
filename = filedate
#print(filename)
return filename
def run(self):
global frames
global saveImages
while True:
# print("Entered Loop 1:")
if(not frames.empty()):
# print("Entered frame showing:")
self.Currframe=frames.get()
if saveImages is False:
self.out.write(self.Currframe)
else:
self.imgName=self.getDateStamp() + '.tiff'
cv2.imwrite(self.imgName,self.Currframe)
print(f"Queue Size in writing = {frames.qsize()}")
# print("Show Image")
elif exitProgram is True:
print("Exit Command Main reached")
print(f"Final Queue Size at exit = {frames.qsize()}")
break
def initStart(*args):
argsList=sum(list(args),[])
argsLen = len(argsList)
if(argsLen==2):
ID = int(argsList[0])
codec = argsList[1]
elif(argsLen==1):
ID = int(argsList[0])
codec = 'XVID'
else:
ID = 0
codec = 'XVID'
grabber = ImageGrabber(ID)
saver = ImageSaver(grabber,codec)
grabber.start()
saver.start()
e = threading.Event()
# main.join()
# grabber.join()
print ('Press CTRL-C to interrupt')
while grabber.isAlive():
try:
time.sleep(1) #wait 1 second, then go back and ask if thread is still alive
print ('Sleep...')
except: #if ctrl-C is pressed within that second,
#catch the KeyboardInterrupt exception
e.set() #set the flag that will kill the thread when it has finished
exitProgram=True
print ('Exiting...')
grabber.join()
saver.join()
if __name__ == "__main__":
full_cmd_arguments = sys.argv
# Keep all but the first. First contains the path of script or file
argument_list = full_cmd_arguments[1:]
initStart(argument_list)
If I change the program in such a way that keyboardInterrupt is called without any function then it works
grabber = ImageGrabber(0)
main = Main(grabber,'XVID')
grabber.start()
main.start()
e = threading.Event()
print ('Press CTRL-C to interrupt')
while grabber.isAlive():
try: time.sleep(1) #wait 1 second, then go back and ask if thread is
except KeyboardInterrupt: #if ctrl-C is pressed within that second,
#catch the KeyboardInterrupt exception
e.set() #set the flag that will kill the thread when it has finished
exitProgram=True
print ('Exiting...')
grabber.join()
main.join()
The above code is called directly as python script then it works. Any reason?
Any idea? how it can work?
...
print ('Press CTRL-C to interrupt')
try:
while grabber.isAlive():
time.sleep(1) #wait 1 second, then go back and ask if thread is
except KeyboardInterrupt: #if ctrl-C is pressed within that second,
#catch the KeyboardInterrupt exception
e.set()
...
Try this way.
The reason why KeyboardInterrupt was not working is because the while loop was scoping the try except statement, so it would repeat it continuously even though you were pressing the KeyboardInterrupt.
Hope this helps!

Stoping python script using KeyboardInterrupt

Hi why is my KeyboardInterrupt: is not stoping my program when i hit control c or control x? this is my current code.
I am using python Threading that runs 2 function coinPulser and coinPulserDone.
import threading
import time
lock = threading.Lock()
counter = 0
input = 3
def coinPulser ():
global counter
global input
lock.acquire()
try:
while counter < input:
counter+=1
time.sleep(.1)
if counter in [1,3,5]:
print(counter)
return counter
finally:
lock.release()
def coinPulserDone ():
while True:
print(coinPulser())
try:
coinpulser = threading.Thread(target = coinPulser)
coinpulser.start()
coinpulserdone = threading.Thread(target = coinPulserDone)
coinpulserdone.start()
except KeyboardInterrupt:
coinpulser.stop()
coinpulserdone.stop()
print('Thread Stops')
I suspect the problem is that your code exits your try/except block before you press Cntr-C. You need to add some form of a loop that will hold it in that block. A simple loop such as
while True:
time.sleep(1)
before your except line should do the trick.

Python Tkinter, Stop a threading function

I'm currently developing a GUI for a 3D printer and I'm having a problem of how to stop a threading function. I want to be able to click a button that has another function within my GUI that will stop the threading function from sending strings of G-code across the serial port. Currently the function has threading incorporated to allow other functions to be triggered during printing. I would greatly appreciate some advice on how I would incorporate this stop feature.
Below is the function that opens a G-code file and sends each line across the serial port.
def printFile():
def callback():
f = open(entryBox2.get(), 'r');
for line in f:
l = line.strip('\r')
ser.write("<" + l + ">")
while True:
response = ser.read()
if (response == 'a'):
break
t = threading.Thread(target=callback)
t.start()
Threads cannot be stopped, they have to stop themselves. So you need to send a signal to the thread that it's time to stop. This is usually done with an Event.
stop_event = threading.Event()
def callback():
f = open(entryBox2.get(), 'r');
for line in f:
l = line.strip('\r')
ser.write("<" + l + ">")
while True:
response = ser.read()
if (response == 'a'):
break
if stop_event.is_set():
break
t = threading.Thread(target=callback)
t.start()
Now if you set the event elsewhere in your code:
stop_event.set()
The thread will notice that, break the loop and die.
Use a global variable as a condition for the thread to stop.
send_gcode = True
def printFile():
def print_thread():
f = open(entryBox2.get(), 'r');
for line in f:
if not send_gcode:
break
l = line.strip('\r')
ser.write("<" + l + ">")
while True:
response = ser.read()
if (response == 'a'):
break
t = threading.Thread(target=print_thread)
send_gcode = True
t.start()
The thread will run until send_gcode is set to False (by e.g. a callback for a button:
def stop_callback(event):
global send_gcode
send_gcode = False

Disabling KeyboardInterrupt after it has been used once

Cont = 1
while Cont == 1:
try:
while Cont == 1:
counter = counter + 0.1
counter = round(counter, 1)
print(counter)
time.sleep(0.1)
if counter == crashNumber:
Cont = 0
except KeyboardInterrupt:
Multiplier = counter
Here the counter will continue to count up unitl it reaches the crashNumber, when Ctrl + C is pressed, it will take the number that the counter is at and use it for the Multiplier to be used later.
However I only want to give the user the chance to press this once, then it is disabled. Is there any way that this can be done?
The KeyboardInterrupt exception will be thrown whether you want it or not: the solution, then, is to deal with the exception in different ways in your except block. My chosen implementation will use a simple boolean value that starts as True and is set to False on the first interruption:
import time
allow_interrupt = True
while True:
try:
time.sleep(1)
print ('...')
except KeyboardInterrupt:
if allow_interrupt:
print ('interrupted!')
allow_interrupt = False
Let me know if this addresses your use case.

Categories

Resources