I have a Python script where I would like to replace the sleep() with wait in order to interrupt the threads instantly even when they are sleeping.
However I don't know how to transform my functions into Events.
I read that asyncio could also be used but I am not sure to understand how it works.
Here is the code :
from pynput import keyboard
from pynput.keyboard import Key, Controller
import datetime
import time
import threading
import random
import pyautogui
from threading import Event
# --- functions ---
keyboardCtrl = Controller()
def run():
print('Running thread')
time.sleep(random.randrange(150,350)/1000)
while running:
my_keylist1=['e']
while len(my_keylist1) > 0:
n = random.choice(my_keylist1)
keyboardCtrl.press(n)
#print('Time:',datetime.datetime.now(),n)
my_keylist1.remove(n)
time.sleep(random.randrange(10200,10450)/1000)
print('Exiting thread 1')
def run2():
time.sleep(random.randrange(150,350)/1000)
while running:
pyautogui.keyDown('z')
#print('T2',datetime.datetime.now())
#time.sleep(random.randrange(1000,3000)/1000)
print('Exiting thread 2')
pyautogui.keyUp('z')
def run3():
time.sleep(random.randrange(150,350)/1000)
while running:
my_keylist1=['f']
while len(my_keylist1) > 0:
n = random.choice(my_keylist1)
keyboardCtrl.press(n)
#print('Time:',datetime.datetime.now(),n)
my_keylist1.remove(n)
time.sleep(random.randrange(6250,6550)/1000)
print('Exiting thread 3')
def on_press(key):
global running # inform function that it has to assign value to external variable
global clicker
global clicker2,clicker3
try: # PEP8: don't put it in one line - it make code unreadable for human
k = key.char
except:
k = key.name
if key == keyboard.KeyCode(char='q'):
print("Key Pressed")
if not running: # the same as `if running == False:`
print("Starting thread")
clicker = threading.Thread(target=run)
clicker2 = threading.Thread(target=run2)
clicker3 = threading.Thread(target=run3)
running = True # it has to be before `start()`
clicker.start()
clicker2.start()
clicker3.start()
else:
print("Stopping thread")
running = False # it has to be before `join()`
clicker.join()
clicker2.join()
clicker3.join()
# press `F1` to exit
if key == keyboard.Key.f1:
return False
# --- main ---
running = False # default value at start
try:
print("Starting program")
print("- press E to start/stop thread")
print("- press F1 to exit")
print("- press Ctrl+C to exit")
lis = keyboard.Listener(on_press=on_press)
lis.start()
print("Listening ...")
lis.join()
print("Exiting program")
except KeyboardInterrupt:
print("Stoped by Ctrl+C")
else:
print("Stoped by F1")
finally:
if running:
running = False
clicker.join()
clicker2.join()
clicker3.join()
Maybe you can try something like, I am sure there is a better solution, I would suggest reading the documentation.
import asyncio
from pynput import keyboard
from pynput.keyboard import Key, Controller
import datetime
import time
import threading
import random
import pyautogui
from threading import Event
# --- functions ---
keyboardCtrl = Controller()
async def run():
print('Running thread')
await asyncio.sleep(random.randrange(150, 350) / 1000)
while running:
my_keylist1 = ['e']
while len(my_keylist1) > 0:
n = random.choice(my_keylist1)
keyboardCtrl.press(n)
# print('Time:',datetime.datetime.now(),n)
my_keylist1.remove(n)
#time.sleep(random.randrange(10200, 10450) / 1000)
await asyncio.sleep(random.randrange(10200, 10450) / 1000)
print('Exiting thread 1')
async def run2():
#time.sleep(random.randrange(150, 350) / 1000)
await asyncio.sleep(random.randrange(150, 350) / 1000)
while running:
pyautogui.keyDown('z')
# print('T2',datetime.datetime.now())
# time.sleep(random.randrange(1000,3000)/1000)
print('Exiting thread 2')
pyautogui.keyUp('z')
async def run3():
#time.sleep(random.randrange(150, 350) / 1000)
await asyncio.sleep(random.randrange(150, 350) / 1000)
while running:
my_keylist1 = ['f']
while len(my_keylist1) > 0:
n = random.choice(my_keylist1)
keyboardCtrl.press(n)
# print('Time:',datetime.datetime.now(),n)
my_keylist1.remove(n)
#time.sleep(random.randrange(6250, 6550) / 1000)
await asyncio.sleep(random.randrange(6250, 6550) / 1000)
print('Exiting thread 3')
async def on_press(key):
global running # inform function that it has to assign value to external variable
global clicker
global clicker2, clicker3
try: # PEP8: don't put it in one line - it make code unreadable for human
k = key.char
except:
k = key.name
if key == keyboard.KeyCode(char='q'):
print("Key Pressed")
if not running: # the same as `if running == False:`
print("Starting thread")
#await run()
#await run2()
#await run3()
await asyncio.gather(*[run(), run2(), run3()])
#clicker = threading.Thread(target=run)
#clicker2 = threading.Thread(target=run2)
#clicker3 = threading.Thread(target=run3)
running = True # it has to be before `start()`
#clicker.start()
#clicker2.start()
#clicker3.start()
else:
print("Stopping thread")
running = False # it has to be before `join()`
#clicker.join()
#clicker2.join()
#clicker3.join()
# press `F1` to exit
if key == keyboard.Key.f1:
return False
# --- main ---
running = False # default value at start
def main(*args, **kwargs):
asyncio.run(on_press(args[0]))
try:
print("Starting program")
print("- press E to start/stop thread")
print("- press F1 to exit")
print("- press Ctrl+C to exit")
lis = keyboard.Listener(on_press=main)
lis.start()
print("Listening ...")
lis.join()
print("Exiting program")
except KeyboardInterrupt:
print("Stoped by Ctrl+C")
else:
print("Stoped by F1")
finally:
if running:
running = False
#clicker.join()
#clicker2.join()
#clicker3.join()
Related
I have a program that spams messages, but I need to make such a hot key that will stop the loop, how can I do this?
I trying do this with failsafe, while True wait key, add_hotkey, but it doesn't work
(Sorry for comments in Russian.)
import codecs
import sys
import time
from pathlib import Path
import colorama
import keyboard
from Tools.i18n.makelocalealias import pprint
from art import text2art
import yaml
import pyautogui as pag
pag.FAILSAFE = True
colorama.init()
# Клавиша для активации
hotkey = "9"
# Клавиша для открытия чата
open_hotkey = "shift+t"
# Включить кнопку открытия чата
enable_open_chat_hotkey = True
# Клавиша для отправки сообщения
send_msg_hotkey = "enter"
# Интервал между символами
character_interval = 0
# Интервал между сообщениями
interval = 0.15
close_hotkey = "H+J"
can_run = True
def initConfig():
global hotkey, open_hotkey, enable_open_chat_hotkey, send_msg_hotkey, character_interval, interval, close_hotkey
if Path("config.yml").is_file():
with codecs.open('config.yml', encoding="UTF-8") as f:
loadedConfig = yaml.safe_load(f)
hotkey = loadedConfig['hotkey']
open_hotkey = loadedConfig['open_hotkey']
enable_open_chat_hotkey = loadedConfig['enable_open_chat_hotkey']
send_msg_hotkey = loadedConfig['send_msg_hotkey']
character_interval = loadedConfig['character_interval']
interval = loadedConfig['interval']
close_hotkey = loadedConfig['close_hotkey']
print(
f"Клавиша активации: {hotkey}\n"
f"Клавиша открытия чата (если включено): {open_hotkey}\n"
f"Включить ли клавишу для открытия чата в играх?: " + ("Нет", "Да")[enable_open_chat_hotkey] + "\n"
f"Интервал между символами: {character_interval}\n"
f"Интервал между сообшениями (рекомендуем оставить 0.2, меньше Дота не тянет): {interval}\n"
f"Клавиша деактивации: {close_hotkey}\n"
)
def main():
print(text2art("1000-7 SCRIPT", "standart"))
print(
"Привет, твои текущие настройки программы: \n")
initConfig()
keyboard.add_hotkey(hotkey, lambda: print_1000_7())
keyboard.wait()
def invert_run():
global can_run
can_run = not can_run
print(can_run)
def print_1000_7():
print("What is 1000-7?")
if enable_open_chat_hotkey:
keyboard.press_and_release(open_hotkey)
keyboard.press_and_release('ctrl+a')
keyboard.press_and_release('backspace')
time.sleep(interval)
keyboard.write("What is 1000-7?", character_interval)
keyboard.press_and_release(send_msg_hotkey)
keyboard.press_and_release('ctrl+a')
keyboard.press_and_release('backspace')
keyboard.press_and_release('F9')
keyboard.write("What is 1000-7?")
keyboard.press_and_release(send_msg_hotkey)
if enable_open_chat_hotkey:
keyboard.press_and_release(open_hotkey)
for i in range(5):
if not can_run:
return
print(5 - i)
if enable_open_chat_hotkey:
keyboard.press_and_release(open_hotkey)
keyboard.press_and_release('ctrl+a')
keyboard.press_and_release('backspace')
time.sleep(interval)
keyboard.write(str(5-i), character_interval)
keyboard.press_and_release(send_msg_hotkey)
time.sleep(1)
x = 1000
while x > 0:
if not can_run:
return
var = x - 7
if enable_open_chat_hotkey:
keyboard.press_and_release(open_hotkey)
keyboard.press_and_release('ctrl+a')
keyboard.press_and_release('backspace')
time.sleep(interval)
keyboard.write(f"{x} - 7 = {var}", character_interval)
# print(f"{x} - 7 = {var}")
x = var
time.sleep(interval)
keyboard.press_and_release(send_msg_hotkey)
if __name__ == '__main__':
main()
while True:
keyboard.wait(close_hotkey)
invert_run()
I'm still new to python so I'm sure this is not the best answer, also as much as i could understand you want to create a ON\OFF hotkey and i wanted to do same in some program before, so i made sure that my whole program runs only if variable a==True
and i made a hotkey that changes the 'a', so can u do same ? i mean make whole code work only if 'a' variable is set to True
[ofcourse if there is a 'while True' loop inside one of those spam then im not sure that this will work...]
a=True
def b():
global a
while a :
sleep(0.10)
keyboard.press_and_release('F2')
for i in range(45):
sleep(0.1)
if keyboard.is_pressed('e') :#this is my fail-safe cuz in my program
#i didnt want to wait for the full 4.5 sec in case i wanted to stop
a=False
keyboard.press_and_release('f')
sleep(0.1)
b()
while not a:
if keyboard.is_pressed('e') :
a=True
sleep(0.5)
b()
or another version of it
a=True
def Endb():
global a
if a==False:
a=True
else:
a=False
keyboard.add_hotkey('e',lambda: Endb())
def b():
global a
while a :
sleep(0.10)
keyboard.press_and_release('F2')
for i in range(45):
sleep(0.1)
keyboard.press_and_release('f')
sleep(0.1)
b()
while True :
if keyboard.is_pressed('e'):
Endb()
b()
this one starts\stops when ever i press 'e' as a toggle for On\Off too, and it saves me the trouble of putting the if keyboard.is_pressed('e'): a=False in every step in case i have long code like you
I fixed this problem, i just use keyboard.add_hotkey(hotkey, lambda: on_off(), suppress=True)
and
def on_off():
global enabled
enabled = not enabled
I checked threads and solutions about multiprocessing on pyhton 3, but could not adapt it to my case because its containing a loop:
import time
anotherFunctionRunning = False
def anotherFunction():
global anotherFunctionRunning
anotherFunctionRunning = True
print("Another function started")
time.sleep(5)
print("Another function stopped running")
anotherFunctionRunning = False
def mainLoop():
global anotherFunctionRunning
while True:
print("running")
time.sleep(1)
if (anotherFunctionRunning == False):
anotherFunction()
else:
print("loop running, another function running")
print("loop ended")
mainLoop()
My problem here is when anotherFunction starts running, script waits it to be over (in the example 5 seconds) and continues the loop.
I want it to continue the loop while anotherFunction running.
I saw this common solution but could not adapt it to my case and dont know how to do because its becoming too complex:
from multiprocessing import Process
def func1:
#does something
def func2:
#does something
if __name__=='__main__':
p1 = Process(target = func1)
p1.start()
p2 = Process(target = func2)
p2.start()
Any ideas ?
Thanks for support
I'm not sure I understand what you want, but I think this should do the job
import time
from threading import Thread
anotherFunctionRunning = False
def anotherFunction():
global anotherFunctionRunning
anotherFunctionRunning = True
print("Another function started")
time.sleep(5)
print("Another function stopped running")
anotherFunctionRunning = False
def mainLoop():
global anotherFunctionRunning
thread = None
counter = 0
while counter < 5:
counter += 1
print("running")
time.sleep(1)
if anotherFunctionRunning == False:
thread = Thread(target= anotherFunction, name="AnotherThread")
thread.start()
else:
print("loop running, another function running")
print("loop ended")
thread.join()
mainLoop()
Beware however I have royally ignored the dangers of competitive access
As a new programmer, I'm trying to create a Python3 script that creates a Countdown timer based on the KeyCombination used which is then written to a text file used by StreamlabsOBS. The idea is that when a KeyCombo is pressed (e.g ctrl+alt+z) a function starts a timer, and at the same time, another function writes the current time in the countdown to a txt file. This script would ideally be running during the entire stream.
So far, I can get the countdown working and writing to the file exactly how I want it to. But it seems the Thread is still alive after finishing the countdown. I'm not sure if this is a problem or not. Another thing is that I want to implement some kind of pause feature, that would pause the timer function. I'm not sure how to even start on that part.
The print statements are for me to know what part of the function I am at.
from pynput import keyboard
from pathlib import Path
from threading import Thread
import queue
from time import sleep
script_location = Path(__file__).absolute().parent
timer_queue = queue.Queue()
def storeInQueue(function):
print("Sent Start Thread msg")
def storer(*args):
for time in function(*args):
timer_queue.put(time)
print(f"stored {time}")
return storer
#storeInQueue
def timer(t):
print("Iterating Timer Loop")
while t > -1:
yield t
sleep(1)
t -= 1
def speakKorean():
print("starting Thread")
timer_thread = Thread(target=timer, args=(5,))
timer_thread.start()
ctime = timer_queue.get()
while ctime >-1:
with open(script_location / 'ChronoDown.txt', "w") as timer_file:
timer_file.write(f"Speak Korean for {ctime}s")
timer_file.flush()
sleep(1)
ctime = timer_queue.get()
if ctime == 0: break
print('Speak Korean done!')
with open(script_location / 'ChronoDown.txt', "w") as timer_file:
timer_file.write(f"Done!")
timer_file.flush()
while timer_thread.is_alive:
print("timer thread still running?")
timer_thread.join()
break
if timer_thread.is_alive:
print("didn't work")
def on_activate_z():
timer_file = open(script_location / 'ChronoDown.txt', "w")
timer_file.write("other keywords")
timer_file.close()
def on_activate_c():
korean_thread = Thread(target=speakKorean,)
korean_thread.start()
print("Working")
def on_activate_x():
timer_file = open(script_location / 'ChronoDown.txt', "w")
timer_file.write("No cursing for time")
timer_file.close()
with keyboard.GlobalHotKeys({
'<ctrl>+<alt>+z': on_activate_z,'<ctrl>+<alt>+c': on_activate_c,
'<ctrl>+<alt>+x': on_activate_x}) as h:
h.join()
My console output looks like this after I run it. I'm not sure why "Sent Start Thread msg" sends before I start thread too.
Sent Start Thread msg
starting Thread
Working
Iterating Timer Loop
stored 5
stored 4
stored 3
stored 2
stored 1
stored 0
Speak Korean done!
timer thread still running?
didn't work
Also if you have any optimization tips that would be appreciated. Thank you in advance for any help.
Thanks to #furas , I've now implemented a pause function that properly resumes as well. This is my updated code :
from pynput import keyboard
from pathlib import Path
from threading import Thread
import queue
from time import sleep
script_location = Path(__file__).absolute().parent
timer_queue = queue.Queue()
paused = False
while paused == False:
def storeInQueue(function):
print("Sent Start Thread msg")
def storer(*args):
for time in function(*args):
timer_queue.put(time)
print(f"stored {time}")
return storer
#storeInQueue
def timer(t):
print("Iterating Timer Loop")
while t > -1:
if paused == False:
yield t
sleep(1)
t -= 1
else: continue
def speakKorean():
print("starting Thread")
timer_thread = Thread(target=timer, args=(5,))
timer_thread.start()
ctime = timer_queue.get()
while ctime >-1:
with open(script_location / 'ChronoDown.txt', "w") as timer_file:
timer_file.write(f"Speak Korean for {ctime}s")
timer_file.flush()
sleep(1)
ctime = timer_queue.get()
if ctime == 0: break
print('Speak Korean done!')
with open(script_location / 'ChronoDown.txt', "w") as timer_file:
timer_file.write(f"Done!")
timer_file.flush()
timer_thread.join()
if timer_thread.is_alive():
print("didn't work")
else: print("its dead")
def on_activate_z():
global paused
timer_file = open(script_location / 'ChronoDown.txt', "w")
timer_file.write("Paused")
timer_file.close()
if paused == True:
paused = False
print(f'Paused = {paused}')
else:
paused =True
print(f'Paused = {paused}')
def on_activate_c():
korean_thread = Thread(target=speakKorean,)
korean_thread.start()
print("Working")
def on_activate_x():
timer_file = open(script_location / 'ChronoDown.txt', "w")
timer_file.write("No cursing for time")
timer_file.close()
with keyboard.GlobalHotKeys({
'<ctrl>+<alt>+z': on_activate_z,'<ctrl>+<alt>+c': on_activate_c,
'<ctrl>+<alt>+x': on_activate_x}) as h:
h.join()
The main differences:
My entire code is now encapsulated in a While paused == False loop, which allows me to pause my timer function based on the state of paused using an if statement
I've added the missing ( ) to timer_thread.is_alive() which allowed me to properly end the timer Thread
I have a program that is supposed to send a few data points over a serial connection to an arduino which will control some motors to move. I can send the control signals individually as well as by txt file which will run repeatedly until the file is complete. While running a txt file, I want to be able to exit the loop like a pause or stop button. I think the best way to do that is via a thread that I can close. I have never done any threading before and my rudimentary attempts have not worked. Here is the function that sends the file data.
def send_file():
# Global vars
global moto1pos
global motor2pos
# Set Ready value
global isready
# Get File location
program_file_name = file_list.get('active')
file_path = "/home/evan/Documents/bar_text_files/"
program_file = Path(file_path + program_file_name)
file = open(program_file)
pos1 = []
pos2 = []
speed1 = []
speed2 = []
accel1 = []
accel2 = []
for each in file:
vals = each.split()
pos1.append(int(vals[0]))
pos2.append(int(vals[1]))
speed1.append(int(vals[2]))
speed2.append(int(vals[3]))
accel1.append(int(vals[4]))
accel2.append(int(vals[5]))
# Send file values
try:
while isready == 1:
for i in range(len(pos1)):
print("Step: " + str(i+1))
data = struct.pack("!llhhhh", pos1[i], pos2[i], speed1[i], speed2[i], accel1[i], accel2[i])
ser.write(data)
try:
pos1time = abs(pos1[i]/speed1[i])
except:
pos1time = 0
try:
pos2time = abs(pos2[i]/speed2[i])
except:
pos2time = 0
time_array = (pos1time, pos2time)
time.sleep(max(time_array))
motor1pos = ser.readline()
motor2pos = ser.readline()
if i < (len(pos1)-1):
isready = ord(ser.read(1))
else:
isready = 0
except:
print("Error: data not sent. Check serial port is open")
Here is the threading command which I want the sendfile command to work from.
def thread():
try:
global isready
isready = 1
t = threading.Thread(name='sending_data', target=command)
t.start()
except:
print("Threading Error: you don't know what you are doing")
And here is the stop function I want the thread to be killed by:
def stop():
try:
global isready
isready = 0
t.kill()
except:
print("Error: thread wasn't killed")
I know you aren't supposed to kill a thread but the data isn't very important. Whats more important is to stop the motors before something breaks.
The button in tkinter is:
run_file_butt = tk.Button(master = file_frame, text = "Run File", command = thread)
When I click the button, the program runs but the stop function does nothing to stop the motion.
Question: run and kill a thread on a button press
There is no such a thing called .kill(....
Start making your def send_file(... a Thread object which is waiting your commands.
Note: As it stands, your inner while isready == 1: will not stop by using m.set_state('stop').
It's mandatory to start the Thread object inside:
if __name__ == '__main__':
m = MotorControl()
import threading, time
class MotorControl(threading.Thread):
def __init__(self):
super().__init__()
self.state = {'is_alive'}
self.start()
def set_state(self, state):
if state == 'stop':
state = 'idle'
self.state.add(state)
def terminate(self):
self.state = {}
# main function in a Thread object
def run(self):
# Here goes your initalisation
# ...
while 'is_alive' in self.state:
if 'start' in self.state:
isready = 1
while isready == 1:
# Here goes your activity
# Simulate activity
print('running')
time.sleep(2)
isready = 0
self.state = self.state - {'start'}
self.state.add('idle')
elif 'idle' in self.state:
print('idle')
time.sleep(1)
if __name__ == '__main__':
m = MotorControl()
time.sleep(2)
m.set_state('start')
time.sleep(3)
m.set_state('stop')
time.sleep(3)
m.set_state('start')
time.sleep(4)
m.terminate()
print('EXIT __main__')
Your tk.Button should look like:
tk.Button(text = "Run File", command = lambda:m.set_state('start'))
tk.Button(text = "Stop File", command = lambda:m.set_state('stop'))
tk.Button(text = "Terminate", command = m.terminate)
The answer I have gone with is simple due to my simple understanding of threading and unique circumstances with which I am using the threading. Instead of terminating the thread in a way I was hoping, I added another conditional statement to the sending line of the send_file function.
while isready == 1:
for i in range(len(pos1)):
if motorstop == False:
print("Step: " + str(i+1))
#data = struct.pack('!llllhhhhhhhh', pos1[i], pos2[i], pos3[i], pos4[i], speed1[i], speed2[i], speed3[i], speed[4], accel1[i], accel2[i], accel3[i], accel4[i])
data = struct.pack("!llhhhh", pos1[i], pos2[i], speed1[i], speed2[i], accel1[i], accel2[i])
ser.write(data)
else:
isready = 0
break
and I have updated my stop() func to the following:
def stop():
try:
global motorstop
global t
motorstop = True
t.join()
except:
print("Error: thread wasn't killed")
I'm not exactly sure how it works but it is much simpler than what was mentioned by #stovefl.
With this code, since the function is mostly just sleeping, it can run but it won't send any new information and then will .join() after the next iteration.
Hey I am trying to have a loop be pausable from user input like having a input box in the terminal that if you type pause it will pause the loop and then if you type start it will start again.
while True:
#Do something
pause = input('Pause or play:')
if pause == 'Pause':
#Paused
Something like this but having the #Do something continually happening without waiting for the input to be sent.
Ok I get it now, here is a solution with Threads:
from threading import Thread
import time
paused = "play"
def loop():
global paused
while not (paused == "pause"):
print("do some")
time.sleep(3)
def interrupt():
global paused
paused = input('pause or play:')
if __name__ == "__main__":
thread2 = Thread(target = interrupt, args = [])
thread = Thread(target = loop, args = [])
thread.start()
thread2.start()
You can't directly, as input blocks everything until it returns.
The _thread module, though, can help you with that:
import _thread
def input_thread(checker):
while True:
text = input()
if text == 'Pause':
checker.append(True)
break
else:
print('Unknown input: "{}"'.format(text))
def do_stuff():
checker = []
_thread.start_new_thread(input_thread, (checker,))
counter = 0
while not checker:
counter += 1
return counter
print(do_stuff())