Exiting a while loop when a global variable changes - python

So, I've been working on an LED strip connected to my raspberry pi 4, and I've got TouchOSC working between the Pi and my phone. The current problem I'm having is when a toggle button I've pressed is turned off, the program it's designated to run continues running. I have a global variable that's supposed to determine if the "while" loop continues, but instead of setting the button state to zero and terminating the program, it continues to run until it's interrupted by a Ctrl+C in the terminal. I was wondering if anyone would happen to know why the program doesn't stop when the button state is change.
def twinkleBtn(path, tags, args, source):
global twinkleState
twinkleState = int(args[0])
if twinkleState:
turnOff(strip)
while twinkleState:
twinkleTest(strip, False, 10)
if not twinkleState:
turnOff(strip)
This is triggered when the twinkle button is pressed, but the program continues to run when I toggle it back to zero. Below is the code to the function "twinkleTest"
def twinkleTest(strip, onlyOne, count, wait_ms=50):
setPixels(Color(0, 0, 0))
for i in range(count):
strip.setPixelColor(randint(0,LED_COUNT), Color(170, 180, 30))
strip.show()
time.sleep(wait_ms/250.0)
if (onlyOne):
setPixels(Color(0,0,0))
time.sleep(wait_ms/500.0)
I'm not sure if I'm just clueless or what exactly is being done wrong here. I'm pretty new to Python so it may not be the best. Thanks for any help!

Here is a simple threading solution. A thread waits on a threading.Event. twinkleTest is called whenever the event is set. The GUI sets the event, and twinkling will happen in the background until the button is pressed a second time to stop the twinkling.
import threading
def twinkleTest(strip, onlyOne, count, wait_ms=50):
setPixels(Color(0, 0, 0))
for i in range(count):
strip.setPixelColor(randint(0,LED_COUNT), Color(170, 180, 30))
strip.show()
time.sleep(wait_ms/250.0)
if (onlyOne):
setPixels(Color(0,0,0))
time.sleep(wait_ms/500.0)
def twinkler(twinkleEvent):
"""Call twinkleTest whenever the given twinkleEvent event is set
and keeps calling until the event is cleared. Designed to be used in
a separate thread."""
while True:
twinkleEvent.wait()
twinkleTest(strip, False, 10)
def setup_twinkle_daemon_thread():
"""Creates a daemon thread to twinkle screen whenever an event is set.
Returns event, threadhandle"""
twinkleEvent = threading.Event()
twinkleThread = threading.Thread(target=twinker, args=tuple(twinkleEvent))
twinkleThread.daemon = True
twinkleThread.start()
return twinkleEvent, twinkleThread
# controls twinkler
twinkleEvent = twinkleThread = None
def twinkleBtn(path, tags, args, source):
global twinkleEvent, twinkleThread
if twinkleEvent is None:
# start daemon thread on first use
twinkleEvent, twinkleThread = setup_twinkle_daemon_thread()
if twinkleEvent.is_set():
twinkleEvent.clear()
else:
twinkleEvent.set()

Related

Increasing Thread_ID by using Timers

I have a short and probably simple question refering to Timers:
Is it normal for the thread ID to increment every time I start a timer?
from threading import Timer
class Watchdog():
def __init__(self, timeout, user_Handler): # timeout in seconds
self.timeout = timeout
self.handler = user_Handler
self.timer = Timer(self.timeout, self.handler)
def reset(self):
self.timer.cancel() # stop the timer's action if it's still running
print("A", self.timer)
self.timer = Timer(self.timeout, self.handler) # Timer: Calls a function=handler after a specified number of seconds
print("B", self.timer)
self.timer.start()
print("C", self.timer)
def stop(self):
"""Stop the timer if it hasn't finished yet."""
self.timer.cancel()
print("D", self.timer)
When I use my class, the print statements return sth similar like this:
print(self.timer)
>>> <Timer(Thread-1, started daemon 19447202672)>
>>> <Timer(Thread-2, stopped daemon 19447202672)>
...
>>> <Timer(Thread-n, started daemon 19447202672)>
--> n (=ID) rises
Reference: How to implement a watchdog timer in Python?
UPDATE - Use of the class:
As I use this class with a button which triggers an interrupt, I'll try a textual description. I'm pretty sure, this might be enough for a python/programming expert (If not, please tell me).
A push of the button changes the view of a LC-Display from a default view to one of three special views. After the last special view it starts again with the default view (kind of round robin). After 5s without pushing the button, screen is resetted to default view.
Pushing the button calls the watchdog interrupt handler where my_watchdog.reset() is called to start the watchdog timer. By changing the view (button push) within the 5s , this happens again and so the timer is resetted by being canceled and started. If you switch from the last special view to the default view by hand, my_watchdog.stop() is called. This is to avoid the call of the watchdog_expired_handler. This handler resets the view to the default view.
In summary you could say that my_watchdog is instantiated and my_watchdog.reset() and my_watchdog.stop() are called at random times.
UPDATE_2 - Simplified use of the class in python:
import time
def watchdog_handler():
print("watchdog timer expired")
my_watchdog = Watchdog(timeout=5, user_Handler=watchdog_handler)
my_watchdog.reset() # starts a watchdog timer (no running timer has to be stopped)
time.sleep(6) # is equivalent to no action of the user
my_watchdog.reset() # user presses button again
time.sleep(2) # user presses button while timer is still running --> watchdog.reset() is called
my_watchdog.reset()
time.sleep(2) # user presses button while timer is still running
my_watchdog.reset()
time.sleep(2) # user presses button while timer is still running and reaches default view again
my_watchdog.stop()
Output of the simplified use (on Raspian OS):
A <Timer(Thread-106, initial)>
B <Timer(Thread-107, initial)>
C <Timer(Thread-107, started 14176)>
watchdog timer expired
A <Timer(Thread-107, stopped 14176)>
B <Timer(Thread-108, initial)>
C <Timer(Thread-108, started 1256)>
A <Timer(Thread-108, stopped 1256)>
B <Timer(Thread-109, initial)>
C <Timer(Thread-109, started 6796)>
A <Timer(Thread-109, stopped 6796)>
B <Timer(Thread-110, initial)>
I've written a lot and learned also sth by breaking it down here. I think the key questions are very simple:
Is there anything critical about re/starting the timer on a new thread (I assume that this is what I do). What happens to the old one (is there some kind of garbage collector)? When is the ID reset, on restart?

How to add On and Off buttons with pyautogui?

I made a simple auto-clicker in python. Every three seconds the script clicks wherever your mouse is. How would I add "on and off" keys? I imagine it is a simple if/else statement but I don't know how to write it.
As of Wed Sep 15 12:10, I do not have an answer that works well.
import pyautogui
import time
def Auto_Click():
width, height = pyautogui.position()
pyautogui.click(width, height)
time.sleep(3)
while True:
Auto_Click()
I'd suggest listening to specific key presses indefinitely to switch clicking on and off. And as there is an indefinite loop for the clicking as well, you will need multithreading (to perform clicking and listening for key presses simultaneously).
Notes
The auto clicker is switched off by default right on start (To avoid clicks at unwanted positions on screen right after running it). Press SHIFT to toggle it after pointing the mouse at wanted position.
Press ESC to exit the program.
I have used SHIFT and ESC keys for toggles so that the key presses won't show up in the next prompt unlike the character keys.
Use the below code if you really need to use pyautogui. Here is the solution using pynput for handling both mouse and keyboard. (My code is basically a modified version which uses keyboard module and pyautogui instead)
import time
import keyboard
import pyautogui
import threading
INTERVAL = 0.5 # Time interval between consecutive clicks
DELAY = 0.5 # Time delay between consecutive program cycles [after the clicks are turned off]
TOGGLE_KEY = 'shift' # Key to toggle the clicking
EXIT_KEY = 'esc' # Key to stop and exit from the program
class AutoClicker(threading.Thread):
def __init__(self, interval, delay):
super(AutoClicker, self).__init__()
self.interval = interval
self.delay = delay
self.running = False
self.program_running = True
def start_clicking(self):
self.running = True
def stop_clicking(self):
self.running = False
def exit(self):
self.stop_clicking()
self.program_running = False
def toggle_clicking(self):
if self.running:
self.stop_clicking()
else:
self.start_clicking()
def click(self):
width, height = pyautogui.position()
pyautogui.click(width, height)
# This function is invoked when the thread starts.
def run(self):
while self.program_running:
while self.running:
self.click()
time.sleep(self.interval)
time.sleep(self.delay)
if __name__ == '__main__':
# Run indefinite loop of clicking on seperate thread
auto_clicker_thread = AutoClicker(INTERVAL, DELAY)
auto_clicker_thread.start() # Invokes run() function of the thread
# So that we can listen for key presses on the main thread
keyboard.add_hotkey(TOGGLE_KEY, lambda: auto_clicker_thread.toggle_clicking())
keyboard.add_hotkey(EXIT_KEY, lambda: auto_clicker_thread.exit())

Auto-Clicker Code Not Working (First Project)

I'm trying to use pynput to make an autoclicker as my first project, but I'm having a tough time understanding why my code won't work. The code is meant to start/stop clicking when I hit "ctrl + alt + i" and click once every 1 second. Here is my current code. I can't really understand why it doesn't work, but what I have made work so far is that "click_thread.running" is changing from true to false, python listens to my keyboard, and the clicking works ONLY WHEN I set the "self.running" in the "ClickMouse" class to true. The output I get from printing "click_thread.running" seems to change from true to false, but if that's happening then why doesn't the clicking start? I would imagine it has something to do with how it's a subclass of "threading.Thread"? Or maybe I made the class wrong? Either way I've been working on it for a few days now and I feel like I have hit a wall trying to figure it out alone. Any help greatly appreciated!
import time
import threading
from pynput.mouse import Button, Controller
from pynput import keyboard
delay = 1
button = Button.left
class ClickMouse(threading.Thread):
def __init__(self, delay, button):
super().__init__()
self.delay = delay
self.button = button
self.running = False
def run(self):
while self.running == True:
mouse.click(self.button)
time.sleep(self.delay)
def start_clicking(self):
self.running = True
def stop_clicking(self):
self.running = False
mouse = Controller()
click_thread = ClickMouse(delay, button)
click_thread.start()
def on_activate_i():
print('<ctrl>+<alt>+i pressed')
if click_thread.running == False:
click_thread.start_clicking()
else:
click_thread.stop_clicking()
print(click_thread.running)
with keyboard.GlobalHotKeys({'<ctrl>+<alt>+i': on_activate_i,}) as h:
h.join()
As soon as you call click_thread.start(), the start handler is going to call your run function in the new thread. At that point, self.running is False. Thus, your while loop will immediately exit, and the thread will end.
So, set running=True as the default, and don't create the thread until on_activate_i.
Where are you clicking? At random?

pyqt: How to quit a thread properly

I wrote an pyqt gui and used threading to run code which needs a long time to be executed, but I want to have the choice to stop the execution safely. I dont want to use the get_thread.terminate() method. I want to stop the code by a special function (maybe del()). My problem is that, I wrote the code in a own class and just want to abort the class without changing a lot of syntax.
Edit: It was mentioned that one has to pass a flag to the class, which has to be checked constantly. How do I send this flag to the class? Because the flag has to change the value, when one presses the stop button.
Edit 2: My solution so far is, to declerate a global variable with the name running_global. I changed self.get_thread.terminate() to running_global = False and I check constantly in my long_running_prog if the variable has been set False. I think this solution is ugly, so I would be pretty happy if someone has a better idea.
This is my code for the dialog where I start the thread:
class SomeDialog(QtGui.QDialog,
userinterface_status.Ui_status_window):
finished = QtCore.pyqtSignal(bool)
def __init__(self):
"""
:param raster: Coordinates which are going to be scanned.
"""
super(self.__class__, self).__init__() # old version, used in python 2.
self.setupUi(self) # It sets up layout and widgets that are defined
self.get_thread = SomeThread()
# Conencting the buttons
self.start_button.clicked.connect(self.start)
self.stop_button.clicked.connect(self.stop)
self.close_button.clicked.connect(self.return_main)
# Connecting other signals
self.connect(self.get_thread, QtCore.SIGNAL("stop()"), self.stop)
self.connect(self.get_thread, QtCore.SIGNAL("update_status_bar()"), self.update_status_bar)
def return_main(self):
"""
Function is excecuted, when close button is clicked.
"""
print("return main")
self.get_thread.terminate()
self.close()
def start(self):
"""
Starts the thread, which means that the run method of the thread is started.
"""
self.start_button.setEnabled(False)
self.get_thread.start()
def stop(self):
print("Stop programm.")
self.start_button.setEnabled(True)
self.get_thread.quit()
def end(self):
QtGui.QMessageBox.information(self, "Done!", "Programm finished")
def closeEvent(self, event):
"""
This method is called, when the window is closed and will send a signal to the main window to activaete the
window again.
:param event:
"""
self.finished.emit(True)
# close window
event.accept()
In the following class is the code for the thread:
class SomeThread(QtCore.QThread):
finished = QtCore.pyqtSignal(bool)
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
print("del")
self.wait()
def run(self):
self.prog = long_running_prog(self.emit) # Sending from the prog signals
self.prog.run()
self.prog.closeSystem() # Leaving the programm in a safe way.
So if one presses the stop button, the programm should instantly shut down in a save way. Is there a way to abort the class in a save way? For example can I pass a variable to the long_running_prog class which turns True, when one presses the stop button? If somethin like this is possible, could one tell me how?
Thanks for your help in advance
I hope you understand my problem.
Greetings
Hizzy
This is impossible to do unless prog.run(self) would periodically inspect a value of a flag to break out of its loop. Once you implement it, __del__(self) on the thread should set the flag and only then wait.

Having trouble quitting sched.scheduler module?

I'm using Selenium Webdriver in my program in order to try and automate something. I am then parsing th resulting page, and checking for a specific element in the page. If the page doesn't have the specific element, then I use sched.scheduler to re-automate the task, by having the user click a button (in the Tkinter GUI). The button runs a function, which schedules a task for sched.scheduler, and has the task be sent to a function in which I created a new process from the multiprocessing module.
This is basically what it is:
import time
import sched
from multiprocessing import Process
#the function needs to run for the first time, then waits for user input if an error shows up
#if it's the second time around, the worker function runs the scheduler
global first_time_happening
first_time_happening = True
terminate = False
scheduler = sched.scheduler(time.time, time.sleep)
def worker():
#insert some working process here using selenium webdriver
print("Worker happened!")
global first_time_happening
if first_time_happening:
first_time_happening = False
elif not first_time_happening:
global relay_to_timer
relay_to_timer = scheduler.enter(5, 2, timer)
scheduler.run()
def process():
p = Process(target=worker)
#p.daemon = True
p.start()
def timer():
if not terminate:
global relay_to_process
relay_to_process = scheduler.enter(5, 2, process)
scheduler.run()
if terminate:
scheduler.cancel(relay_to_process)
scheduler.cancel(relay_to_timer)
def quit_button():
global terminate
terminate = True
if scheduler.empty:
print("The line is empty")
elif not scheduler.empty:
print("Something in the queue!")
while not scheduler.empty:
scheduler.cancel(relay_to_process)
scheduler.cancel(relay_to_timer)
worker()
#simulating where the GUI asks a question, person presses a button, and the button redirects them
#to function worker()
worker()
#simulating a user press the quit button
quit_button()
It keeps running even after I "hit" quit (or call the quit function in this case). I keep getting the queue is empty, but I'm not sure why it isn't working? Any help is appreciated, thanks!!
The scheduler keeps running even with an empty queue just in case somebody (presumably another thread) entered something again. I believe the way to make it end is to raise an exception (whether from the action or delay function) -- .run will propagate it and you can catch it.
To wit...
class AllDoneException(Exception): pass
def worker():
#insert some working process here using selenium webdriver
print("Worker happened!")
global first_time_happening
if first_time_happening:
first_time_happening = False
elif not first_time_happening:
global relay_to_timer
relay_to_timer = scheduler.enter(5, 2, timer)
try:
scheduler.run()
except AllDoneException:
pass
and in function timer
if terminate:
raise AllDoneException

Categories

Resources