Is there a way to have a urwid app to do a sys.exit() after a configurable timeout if no input has been received from the user in more than 30 seconds?
We are facing network outages and that leads to the SSH Session being dropped but the client program keeps running and holds a Database lock and manually killing is the only option for now and hence this requirement.
You can set an alarm in the main loop that will call whatever code you want when it times out. Here I call a function that uses the ExitMainLoop exception, but sys.exit() would also work.
This code cancels the previous alarm (if any) when keyboard input happens, then sets a new alarm. As long as keys are coming in, the alarm should never go off.
Internally, as of late 2020, for alarms urwid seems to use Python's time.time(), which is not guaranteed to only go forward one-second-per-second. The alarm might go off early, exiting the program, if the system clock gets adjusted forward (by NTP?).
import urwid
timeout_time=30
def urwid_exit(loop, user_data):
raise urwid.ExitMainLoop
def handle_input(input):
global txt
global loop
#don't reset the alarm on a mouse click,
# and don't try to display it (the .set_text would error if given a tuple)
if not type(input) is tuple:
if hasattr(handle_input, "last_alarm") and handle_input.last_alarm:
loop.remove_alarm(handle_input.last_alarm)
handle_input.last_alarm = loop.set_alarm_in(timeout_time, urwid_exit)
txt.set_text("key pressed: %s" % input)
txt = urwid.Text("Hello world")
fill = urwid.Filler(txt, 'top')
loop = urwid.MainLoop(fill, unhandled_input=handle_input)
#call handle input once without any input to get the alarm started
handle_input(None)
loop.run()
A slight variation on StephenK's answer is to use loop.event_loop.enter_idle(callback) instead of unhandled_input. The callback function will be run whenever urwid enters an idle state, including after processing a keypress event. This is somewhat more general: the timer starts after all activity has finished. (Say, the last keypress starts an action that takes many seconds to finish)
The relevant documentation is at
https://urwid.readthedocs.io/en/latest/reference/main_loop.html
import urwid
timeout = 10
txt = urwid.Text(
'This program will exit after '
f'_{timeout}_ seconds of inactivity '
'(no keypresses, etc)\n',
align='center'
)
fill = urwid.Filler(txt, 'top')
loop = urwid.MainLoop(fill)
alarm_handle = None
def alarm_callback(_loop, _user_data):
raise urwid.ExitMainLoop
def idle_callback():
global alarm_handle
loop.remove_alarm(alarm_handle) # remove old alarm
alarm_handle = loop.set_alarm_in(timeout, alarm_callback)
text = txt.get_text()[0] + f"\nAlarm has been reset _{alarm_handle[1]}_ times"
txt.set_text(text)
loop.event_loop.enter_idle(idle_callback)
loop.run()
Related
I am trying to control OMXplayer during playback of a video using a Python script. I am new to Dbus on Python, and am hopefully just missing something simple.
Eventually I want to do this with a motion sensor using the GPIO pins, but first I am just trying to get control over the player. What I need to do is loop a section of the video once a certain time passes, and then jump forward to the 'second' section when the passage is interrupted. I am testing this using a key entry.
My problem is that the 'if' statement within the code does not loop unless there is an interruption from a key being inputted, or any other signal. Any key will cause an interruption, but I want the time-specific 'if' statement in the loop to trigger without any input being entered. I hope what I am saying is clear in the below code, but if not please ask any question and I'll be happy to try to clarify.
I am using Will Price's OMXplayer wrapper: https://github.com/willprice/python-omxplayer-wrapper/
My hardware is a Raspberry Pi 3B+
I have looked at similar questions including those below, but have not found the answer:
How to open and close omxplayer (Python/Raspberry Pi) while playing video?
https://www.raspberrypi.org/forums/viewtopic.php?p=533160
I have cleaned out unnecessary parts of the code so this is a basic functioning version.
from omxplayer.player import OMXPlayer #runs from the popcornmix omxplayer wrapper at https://github.com/popcornmix/omxplayerhttps://github.com/popcornmix/omxplayer and https://python-omxplayer-wrapper.readthedocs.io/en/latest/)
from pathlib import Path
import time
import RPi.GPIO as GPIO #for taking signal from GPIO
import subprocess
import logging
logging.basicConfig(level=logging.INFO)
VIDEO_PATH = Path("VIDEO.m4v")
player_log = logging.getLogger("Player 1")
player = OMXPlayer(VIDEO_PATH, dbus_name='org.mpris.MediaPlayer2.omxplayer1')
player.playEvent += lambda _: player_log.info("Play")
player.pauseEvent += lambda _: player_log.info("Pause")
player.stopEvent += lambda _: player_log.info("Stop")
positionEvent = 12
flyin = 3
flyaway = 15
'''
THE ERROR OCCURS IN THE BELOW FUNCTION!!!
the following function does not run each time
the 'while' loop below is playing, but does run
when there is a key pressed as an 'input'
I want to add a chck so that each time a certain
amount of time passes, the video jumps back
'''
def checktime():
currtime = player.position()
print("current time is " + str(currtime))
if(currtime > 3):
print("currtime is greater than 3")
try:
player.play()
print (" Ready")
while True:
'''
Below is the call for the function 'checktime'
This runs once when the video starts, and only
runs again when a key is entered.
I want this to run on a continuous loop
and call each time the video loops
'''
checktime()
key = input()
if key == 'd': #works perfectly
currtime = player.position()
player.seek(flyin-currtime)
print("player position is" + str(player.position()))
if key == 'a': #works perfectly
currtime = player.position()
player.seek(flyaway-currtime)
if key == 's': #works perfectly
player.play()
'''
in addition to the key inputs above, entering any key
and pressing return will run the checktime()
function. I understand that this is receiving an
input, therefore 'waking' the program up,
but I don't understand why the loop is not working
continuously
'''
# Wait for 10 milliseconds
time.sleep(.01)
except KeyboardInterrupt:
print (" Quit")
# Reset GPIO settings
GPIO.cleanup()
The video is controlled using the 's', 'a' and 'd' keys as inputs, and this works perfectly. However, the 'checktime()' function does not call each time the while loop goes around. My guess is that this is because once the video is playing, it is not looking for anything from the Python program until it receives another input.
I am not sure if I am using DBus correctly. Ideally, I wanted to use the seek_absolute function of omxplayer-wrapper, but I haven't been able to understand this. I have been ploughing ahead on this project and have gotten a better understanding since posting an earlier question (see here: OMXplayer-wrapper and Python - jump to a specific point in video) but I am a bit stumped here.
If anyone can help I would appreciate any suggestions!
I need to execute a bash-command in a python program on a raspberry pi, which starts a record, which really starts recording if their is the sound above a certain frequency and finishes after 8sec or if there is silence. After a record finished, it starts itself an waits for new sound to record. Each record is labeled with time. That's the code doing I quoted here:
while GPIO.input(26) == False:
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
process = subprocess.Popen ("rec -c 2 -r 192000 --buffer 50000 -b 32" + filepath + timestamp + ".wav sinc 0.1k silence 1 0.1 0.8% 1 0.4 0.8% vol 6 trim 0 8", shell = True)
process.communicate()
As you can see, to finish the loop, the program waits for a GPIO-input signal (push button). I have an extra code which looks for the name of the subprocess and kills it.
But here is my problem: While the loop runs, it only "looks" for input in the millisecond between one record finishes and a new starts". If I push my button during a record, the loop continues after the record. It only breaks if I push in between.
At first, I thought maybe the while-loop is a bad choice...but if I am not wrong, the problem seems to be the running subprocess.
So here is my question: How can I accomplish this record loop but can stop/kill during a running record through user-input by GPIO. (ctrl+c won't be a viable option)
Many thanks
The issue is that the while loop only checks the condition once each time through the loop. The process.communicate() call waits for the rec command to finish, so you are absolutely right - the button is only checked once, just after the rec and before the timestamp = ....
A solution is to watch for the button in a separate thread, running in parallel with the rec. This answer suggests using event detection from the RPi.GPIO library. Try something like this:
should_exit = False
def my_callback(channel):
if GPIO.input(26):
should_exit = True
GPIO.add_event_detect(26, GPIO.RISING, callback=my_callback, bouncetime=300)
while not should_exit:
timestamp = ...
The my_callback routine should run when the button is pressed, and set the should_exit flag. Once the rec finishes, the while will check should_exit and terminate. (Caution - not tested!)
I've written a program in python and pygame on Linux. My issue is that when I call my function that dials out on a modem and then delivers a message the program pauses until the function has finished. I need a way to subprocess or thread the function. I'm new to python and pygame so this may be simple but everything I've tried has failed.
Also, I'm sure there's probably a more elegant way to process the sounds and pauses. The first sleep gives the modem time to call. The next two are for natural sounding pauses between words and sentences, and the last is to give the entire script time to deliver the message before the modem hangs up.
I'm calling the function and passing variables like this:
A = 'Electronic_Chime.mp3'
B = 'please_check.mp3'
C = 'three.mp3'
contact_user(A,B,C)
And this is the function:
def contact_user( A, B, C ):
ser.write("ATDT441\r") # Attention - Dial via tone number
time.sleep(6)
pygame.mixer.music.load(A)
pygame.mixer.music.play()
time.sleep(2)
pygame.mixer.music.load(B)
pygame.mixer.music.play(1)
time.sleep(2)
pygame.mixer.music.load(C)
pygame.mixer.music.queue(C)
pygame.mixer.music.play()
time.sleep(10)
Any ideas or suggestions would be appreciated.
PS. I have tried:
thread1 = threading.Thread(contact_user(A,B,C))
thread1.start()
The program seems to behave exactly the same even though I have threaded contact_user(A,B,C).
My main issue was that I wasn't passing my arguments to my thread properly. Following is the code that worked properly for me.
My function:
def contact_user(A,B,C):
ser.write("ATDT411\r") # Attention - Dial Tone 411
time.sleep(5) # Wait for modem to finish dialing
pygame.mixer.music.load(A)
pygame.mixer.music.play() # Play attention chime
time.sleep(2) # Wait long enough for first message chime to play before moving on
pygame.mixer.music.load(B)
pygame.mixer.music.play(1) # Play next message
time.sleep(2) # Wait long enough for second message to play before moving on
pygame.mixer.music.load(C) # Load last message
pygame.mixer.music.queue(C) # Queue second instance of last message
pygame.mixer.music.play() # Play last message twice
time.sleep(10) # Wait long enough for last message to play
ser.write("ATH\r") # Attention - Hang up line
ser.write("ATZ\r") # Attention - Reset modem
ser.close()
Calling function from main program:
t = threading.Thread(name = 'dial', target = contact_user, args = (A,B,C))
t.start()
Thank you to everyone that helped me with this. Very appreciated.
I wrote a program that reads a text file and runs an .exe for every line in the text file. This results in opening a new command line window for every time i run the .exe. The windows do close on their own once the current task is finished, but the problem is as follows:
If i have 100 lines in the text file, this means that i call the .exe file 100 times. My problem with that is if i want to cancel the run after it already started, i have to click on the red "X" to close every window one after the another.
What i am trying to do is have some sort of a command interrupt the running program and either close all upcoming windows or just stop the for loop from running.
Is it possible to write into the console a command to interrupt the current running code?
Would it be better to use some sort of a key event listener? If so, are there any built-in key listeners in Python? I can't seem to find any. Does that mean that i have to install Pygame just so i can use a key event listener?
Maybe i should try to listen to the command line and detect an exit code on one of the windows that i manually close and that way end the for loop?
There are a few ways you could go about this. But pretty much you have one main issue - you need some sort of flag that can be switched such that the code will know it must stop. For instance, if the code is working in a while-loop, it should check at the start of this loop if the flag is valid, or if the flag is telling the loop to stop...
while flag:
# do code
There are a few ways to implement this flagging like operation for your needs. I will discuss the threading option. First, you need to understand how threading works, and then you need to mold your script such that instead of "running an executable" for each line of the text file, you would read the text file, and put all the lines into a queue, then you would have a few threads that read from that queue, and perform the desired action (like running an executable) but instead of running an external executable, you should mimick this with Python, this thread should be a daemon thread.. and it should have a main loop which checks if a flag that exists in the parent thread is turned on...
Below is an example:
from threading import Thread
from Queue import Queue
import sys
import time
class Performer():
def __init__(self):
self.active = False
self.queue = Queue()
def action(self, line):
pass # your code should be here
def operate(self, text_file, threads=5):
with open(text_file) as f:
for line in f:
self.queue.put(line)
self.active = True
thread_pool = []
for i in range(threads):
t = Thread(target=self.__thread, name=('worker-%d' % i))
t.daemon = True
t.start()
thread_pool.append(t)
while self.active:
try:
if self.queue.empty():
break
except KeyboardInterrupt:
self.active = False
sys.exit('user + keyboard = byebye')
else:
time.sleep(1)
def __thread(self):
while self.active:
if not self.queue.empty():
try:
self.action(self.queue.get())
except Exception:
pass # do something here
I have paused a script for lets say 3500 seconds by using time module for ex time.sleep(3500).
Now, my aim is to scan for keypresses while the script is on sleep, i mean its on this line.
Its like I want to restart the script if a "keypress Ctrl+R" is pressed.
For ex.. consider
#!/usr/bin/python
import time
print "Hello.. again"
while True:
time.sleep(3500)
Now while the code is at last line, If i press Ctrl+R, i want to re-print "Hello.. again" line.
I am aware that this does not fully answer your question, but you could do the following:
Put the program logic code in a function, say perform_actions. Call it when the program starts.
After the code has been run, start listening for an interrupt.
That is, the user must press ctrl+c instead of ctrl+r.
On receiving an interrupt, wait half a second; if ctrl+c is pressed again, then exit.
Otherwise, restart the code.
Thus one interrupt behaves as you want ctrl+r to behave. Two quick interrupts quit the program.
import time
def perform_actions():
print("Hello.. again")
try:
while True:
perform_actions()
try:
while True: time.sleep(3600)
except KeyboardInterrupt:
time.sleep(0.5)
except KeyboardInterrupt:
pass
A nice side-effect of using a signal (in this case SIGINT) is that you also restart the script through other means, e.g. by running kill -int <pid>.
You may want to use Tkinter {needs X :(}
#!/usr/bin/env python
from Tkinter import * # needs python-tk
root = Tk()
def hello(*ignore):
print 'Hello World'
root.bind('<Control-r>', hello)
root.mainloop() # starts an X widget
This script prints Hello World to the console if you press ctrl+r
See also Tkinter keybindings. Another solution uses GTK can be found here
in a for loop sleep 3500 times for 1 second checking if a key was pressed each time
# sleep for 3500 seconds unless ctrl+r is pressed
for i in range(3500):
time.sleep(1)
# check if ctrl+r is pressed
# if pressed -> do something
# otherwise go back to sleep