exit loop on keypress in either Shell/Terminal or variable change - python

So i'm on my Raspberry pi, and i'm wanting to check to see if a sensor has been activated. I'm potentially running off two different things though, one from a shell that i remote into, and the other off a LCD screen with a couple buttons directly connected to the RPi. I think the best way to do this is to run a loop to see if the user press a key (like the enter key or something) OR if the LCD interface has selected to go on. i'd like to run a loop that check if the user has pressed a key or if a variable has changed somewhere denoting the LCD interface has been changed and then move on with my code, but i don't know the best way to do this. Currently, this is what i have:
import thread
import time
import globals #this is where i keep my project wide global variables. it this this is a good way to do it....
try:
from msvcrt import getch
except ImportError:
def getch():
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
char = None
def keypress():
global char
char = getch()
thread.start_new_thread(keypress, ())
globals.LCDVar = 0
while True:
if char is not None:
print "Key pressed is " + char
break
if globals.LCDVar == 1
break
time.sleep(.1)
print "Program is running\n"
<Run whatever code next>
This works, but i'm not sure what happens to the thread that's created. Does the thread stay alive after i press a key? I if change the variable instead of pressing a key, won't the thread still be there? I would feel comfortable with this if the event would only happen once or twice, but this keypress/varble check might happen 100s or 1000s of times and i don't want to keep starting new threads over and over again.
Is there a better way to do this?
Thanks for any advice!!

The thread you created exits when the function called in start_new_thread returns, so you don't need to worry about it running forever (that's from the official documentation on thread.start_new_thread).
As far as the "best" way to do this, I think that reversing what is run in a separate thread and what is run in the main line of execution would be helpful. That is, do work in a separate thread and wait for a key press in the main thread. That's traditionally how functionality like this is implemented and it reduces some of the complexity of your loop.

Related

Whole program typing effect

I'm making a text adventure game in Python 3. Is there any way to add a typing effect to any text that gets printed without repeating a command after every line?
Assuming that with "typing effect" you mean that the messages should slowly appear one character at a time, you can define a function that iterates the given message and prints it one character at a time, using time.sleep in between to wait a bit. Make sure to flush the buffer after each character.
import time
def slow_print(msg):
for c in msg:
print(c, end="", flush=True)
time.sleep(0.1)
print()
slow_print("Hello World!")
If you really want to apply this for each and every output in your game (which I'd really not recommend) you can overwrite the print function, keeping a reference to the original print function to use within your new slow print function.
original_print = print
def slow_print(msg):
# same as above, but using original_print instead of print
print = slow_print
print("Hello World!")
You could also just def print(...) directly, but I'd recommend defining it as a separate function and then assigning it to print. This way, you can still make this optional, as this will most likely annoy the player after the first few minutes.
I assume you want the characters to appear as if someone were typing them so I'll just assume that
Import modules
import os
import sys
import time
from colr import color
Define your function
def function_name(phrase,speed,r_value,g_value,b_value):
for char in phrase:
sys.stdout.write(color(char, fore=(r_value,g_value,b_value)))
sys.stdout.flush()
time.sleep(speed)
Test the function
function_name("Hello",0.05,0,255,0)
#prints the phrase "Hello" in green text
Alternatively you could write the function using the threading library, which would allow users to skip the typing effect if they so wish.
import time, threading, os, sys, tty, termios
from colr import color
def slow_type_interrupt(phrase,speed,r_value,g_value,b_value):
done = False # this acts as the kill switch, using if statements, you can make certain button presses stop the message printing and outright display it
def type_out():
for char in phrase:
if done:
break
sys.stdout.write(color(char,fore=(r_value,g_value,b_value)))
sys.stdout.flush()
time.sleep(speed)
os.system('clear')
print(color(phrase,fore=(r_value,g_value,b_value)))
t = threading.Thread(target=type_out)
t.start()
def getkey():
ky = sys.stdin.fileno()
Ab = termios.tcgetattr(ky)
try:
tty.setraw(sys.stdin.fileno())
key = sys.stdin.read(1)
finally:
termios.tcsetattr(ky, termios.TCSADRAIN, Ab)
return key
while not done:
key_press = getkey()
if key_press == 'a': #You can replace a with whatever key you want to act as the "kill key"
done = True
os.system('clear')
print(color(phrase,fore=(r_value,g_value,b_value)))
slow_type_interrupt("Hello this is a test. Pressing 'a' will end this and immediatley display the message",.05,0,255,0)
As I mentioned in the comments of the code, a can be replaced by whatever you want. The reason I use this particular method for retrieving keypresses is because it works on almost anything that runs Python. I would suggest reading up on some other ways to retrieve keyboard inputs.
Hope I could help :)

How to exit a Python program or loop via keybind or macro? Keyboardinterrupt not working

I am trying to complete a simple GUI automation program that merely opens a web page and then clicks on a specific spot on the page every 0.2 seconds until I tell it to stop. I want my code to run and have its loop run infinitely until a keybind I specify breaks the loop (or entire program). I started out with the classic, KeyboardInterrupt, which enables CTRL+C to exit a program. Here is what I thought my final code would look like:
import webbrowser, pyautogui, time
webbrowser.open('https://example.com/')
print('Press Ctrl-C to quit.')
time.sleep(5)
#pyautogui.moveTo(1061, 881)
try:
while True:
time.sleep(0.2)
pyautogui.click(1061,881)
except KeyboardInterrupt:
print('\nDone.')
Everything about the code works, except the fact that I can't exit it once the clicking loop starts. Keyboard interrupt and using CTRL-C to exit do not work at all for this script, for whatever reason.
I merely want to be able to press "escape" (or any other key) to exit the loop (or the program altogether) - just any way to make the loop exit and stop. Right now it runs ad infinitum, but I want a simple keybind macro to be able to stop/break it.
I've tried using getch to keybind the escape key to cause a break, but to no avail:
import webbrowser, pyautogui, time, msvcrt
webbrowser.open('https://example.com')
print('Press Ctrl-C to quit.')
time.sleep(5)
#pyautogui.moveTo(1061, 881)
try:
while True:
time.sleep(0.2)
pyautogui.click(1061,881)
if msvcrt.kbhit():
key = ord(readch())
if key == 27:
break
I'm surprised it's been so hard to do this in Python. I've checked out a lot of similar problems across Stackoverflow, but with unsatisfactory answers, and none that solve my problem, unfortunately. I've been able to do things like this in simpler coding languages like AuotHotKeys with ease. I feel like I'm dancing around the solution. Any and all help would be wonderfully appreciated! Thanks in advance.
If I understood correctly, you want to be able to stop your program by pressing a key on your keyboard.
To make you create a thread that will check in background if you press the key in question.
A little example:
import threading, time
from msvcrt import getch
key = "lol"
def thread1():
global key
lock = threading.Lock()
while True:
with lock:
key = getch()
threading.Thread(target = thread1).start() # start the background task
while True:
time.sleep(1)
if key == "the key choosen":
# break the loop or quit your program
Hope its help.

How do I abort reading a user's input in Python 3.x?

I have a thread that monitors user input which looks like this:
def keyboard_monitor(temp): #temp is sys.stdin
global flag_exit
while True:
keyin = temp.readline().rstrip().lower()
if keyin == "exit":
flag_exit = True
print("Stopping...")
if flag_exit == True:
break
If I type exit the flag is properly set and all the other threads terminate. If another one of my threads sets the flag, this thread refuses to finish because it's hanging on the user input. I have to input something to get it to finish. How do I change this code so that the program finishes when the flag is set externally?
Its hard to tell exactly what is going wrong without more of your code, but as an easy solution you could rather exit() which is a python built in. This should reliably terminate the application, also sys.exit()
From wim's comment:
You can use the atexit module to register clean up handlers
import atexit
def cleanup():
pass
# TODO cleanup
atexit.register(cleanup)

How to get keyboard event in console with python

I want to deal with keyboard event in console with python. The running script has some persistent output stream, when admin trigger a keypress event, the script will change its output content.
I have done it with code as follows(press 'q' will trigger the output-change), but there are two issues
there is an increased space in my output. After debug, i find the code "tty.setraw(fd)" lead to that, But i don't know how to solve it
ctrl+c couldn't work anymore (if # "tty.setraw(fd)", ctrl+c will work)
If it is too complex, any else module could do what I want ? I tried curse module, it seems that will freeze the window-output and couldn't coordinate in mutlithread
#!/usr/bin/python
import sys
import select
import tty, termios
import threading
import time
def loop():
while loop_bool:
if switch:
output = 'aaaa'
else:
output = 'bbbb'
print output
time.sleep(0.2)
def change():
global switch
global loop_bool
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
while loop_bool:
tty.setraw(fd)
i,o,e = select.select([sys.stdin],[],[],1)
if len(i)!=0:
if i[0] == sys.stdin:
input = sys.stdin.read(1)
if input =='q':
if switch:
switch = False
else:
switch = True
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
except KeyboardInterrupt:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
loop_bool = False
try:
switch = True
loop_bool = True
t1=threading.Thread(target=loop)
t2=threading.Thread(target=change)
t1.start()
t2.start()
t1.join(1)
t2.join(1)
except KeyboardInterrupt:
loop_bool = False
This probably depends on what platform you're on, and maybe even what terminal emulator you're using, and I'm not sure whether it will solve your problem or not, but…
You should be able to get character-by-character input without calling tty.setraw, just by setting "canonical mode" off, which you do by masking out the ICANON bit in the lflag attribute of tcgetattr(). You may also need to set VMIN or VTIME attributes, but the defaults should already be correct.
For details, see the section "Canonical and noncanonical mode" in the linux man page, "Noncanonical Mode Input Processing" in the OS X man page, or the equivalent if you're on a different platform.
It's probably cleaner to write this as a context manager than to do explicit cleanup. Especially since your existing code does setraw each time through the loop, and only restores at the end; they should ideally be in matched pairs, and using a with statement guarantees that. (Also, this way you don't need to repeat yourself in the except clause and the normal flow.) So:
#contextlib.contextmanager
def decanonize(fd):
old_settings = termios.tcgetattr(fd)
new_settings = old_settings[:]
new_settings[3] &= ~termios.ICANON
termios.tcsetattr(fd, termios.TCSAFLUSH, new_settings)
yield
termios.tcsetattr(fd, termios.TCSAFLUSH, old_settings)
Now:
def change():
global switch
global loop_bool
with decanonize(sys.stdin.fileno()):
try:
while loop_bool:
i,o,e = select.select([sys.stdin],[],[],1)
if i and i[0] == sys.stdin:
input = sys.stdin.read(1)
if input =='q':
switch = not switch
except KeyboardInterrupt:
loop_bool = False
Or maybe you want the with block at a lower level (inside the while, or at least the try).
(PS, I transformed a few lines of your code into equivalent but simpler forms to remove a few levels of nesting.)
YMMV, but here's a test on my Mac:
Retina:test abarnert$ python termtest.py
aaaa
aaaa
aaaa
qbbbb
bbbb
bbbb
qaaaa
aaaa
aaaa
^CRetina:test abarnert$
This makes me think you might want to turn off input echo (which you do by new_settings[3] &= ~termios.ECHO), which implies that you probably want to replace the decanonize function with something more general, for temporarily setting or clearing arbitrary termios flags. (Also, it would be nice if tcgetattr returned a namedtuple instead of a list, so you could do new_settings.lflag instead of new_settings[3], or at least provided symbolic constants for the attribute indices.)
Meanwhile, from your comments, it sounds like ^C only works during the first second or two, and it has something to do with the timeout in the joins. This makes sense—the main thread just kicks off the two threads, does two join(1) calls, and then finishes. So, 2.something seconds after startup, it's finished all of its work—and left the try: block—so there's no longer any way for a KeyboardInterrupt to trigger the loop_bool = False and signal the worker threads to quit.
I'm not sure why you have timeouts on the joins in the first place, and what's supposed to happen when they time out, but there are three possibilities:
You don't want to quit until ^C, and the timeouts aren't there for any good reason. So take them out. Then the main thread will wait forever for the other two threads to finish, and it's still inside the try block, so ^C should be able to set loop_bool = False.
The app is supposed to exit normally after 2 seconds. (I'm guessing you would have preferred a single join-any or join-all on the pair of threads, with a 2-second timeout, but because Python doesn't have any easy way to do that, you joined the threads sequentially.) In this case, you want to set loop_bool = False as soon as the timeouts end. So just change the except to a finally.
The timeouts are always supposed to be generous enough (presumably this is just a simplified version of your real app), and if you get past the timeout, that's an exceptional condition. The previous option may or may not still work. If it doesn't, set daemon = True on the two threads, and they'll be killed (not nicely asked to shut down) when the main thread finishes. (Note that the way this works is a bit different on Windows vs. Unix—although you presumably don't care much about Windows for this app. More importantly, all the docs say is "The entire Python program exits when no alive non-daemon threads are left," so you shouldn't count on any daemon threads being able to do any cleanup, but also shouldn't count on them not doing any cleanup. Don't do anything in a daemon thread that could leave temporary files around, crucial log messages unwritten, etc.)

Drop into an Interpreter anytime in Python

I know how to drop into an interpreter with pdb and IPython, but this requires me knowing beforehand exactly where I want to stop. However, I often run number crunching scripts that take minutes to hours, and I would like to know exactly what it's progress is. One solution is to simply put lots of logging statements everywhere, but then I either inundate myself with too much information or fail to log exactly what I want to know.
Is there a way to initialize a listener loop that under some key combination will drop me into the code wherever it currently is? Think CTRL+Z but leaving me in Python rather than Bash.
You can use the signal module to setup a handler that will launch the debugger when you hit control-C or control-Z or whatever.. SIGINTR, SIGSUSP.
For example, define a module instant_debug.py that overrides SIGQUIT,
import signal
import pdb
def handler(signum, frame):
pdb.set_trace()
signal.signal(signal.SIGQUIT, handler)
Then make a script
import instant_debug
import time
for i in xrange(1000000):
print i
time.sleep(0.1)
At any point during execution, you can jump into the code by typing CTRL+\, examine the stack with u and d as in normal pdb, then continue with c as if nothing ever happened. Note that you will only jump in at the end of the next "atomic" operation -- that means no stopping in the middle of a giant C module.
You could do this
def main():
i = 1000
while True:
print "Count Down %s" % i
time.sleep(1)
i -= 1
try:
main()
except KeyboardInterrupt:
pass # Swallow ctrl-c
finally:
code.interact("Dropped into interpreter", local=globals())

Categories

Resources