Wait for class variable change with threading - python

I have tried the following code snippets. It exists in two separate .py files ChrMain.py and xlWingsTest.py
The meaning is to wait in the main code until excel trigger the class value to True. The code gives no errors but it doesn't trigger the main code. I don't like to use sleep in the code. The main code is already running when excel and xlWings execute the trigger.
Main Code
from waiting import wait as Wait
import time
import threading
class RunSub:
RunNu = False
class RunMain:
isRun = True
def ChrMain():
while RunMain.isRun:
try:
print('Sub is waiting')
Wait(lambda : RunSub.RunNu, timeout_seconds = 40)
print('Sub is continueing ...')
# Do other Stuff
time.sleep(5) # This line is only for testing
RunMain.isRun = False
except:
pass
m = threading.Thread(name = 'Main', target = ChrMain())
m.start()
print('Program End')
exit()
***Trigger Code***
import xlwings as xw
def SetRunNow(myBool):
if myBool:
RunSub.RunNu = True
else:
RunSub.RunNu = False
def StopProg(myBool):
if myBool:
RunMain.isRun = True
else:
RunMain.isRun = False
#xw.func
def Injector(myBool, strfunctie):
import threading
if strfunctie == 'SetRunNow':
from ChrMain import RunSub
i = threading.Thread(name = 'RunNow', target = SetRunNow(myBool))
i.start()
i.join()
return RunSub.RunNu
elif strfunctie == 'StopProg':
from ChrMain import RunMain
i = threading.Thread(name = 'StopProg', target = StopProg(myBool))
i.start()
i.join()
return RunMain.isRun
#exit()

This line might have a bug: i = threading.Thread(name = 'RunNow', target = SetRunNow(myBool))
target must be a function, but SetRunNow(myBool) is a boolean.
a way to fix this would be:
i = threading.Thread(name = 'RunNow', target = lambda: SetRunNow(myBool))

Related

Python Keyboard module - Exit blocking read event function

Hello,
I have the below code that corrects user input and I want to exit the blocking function keyboard.read_event when the control is returned from the correction thread.
The whole program works well but I cannot exit immediately after the corrector thread is finished (the program waits for key press).
I tried using a custom Exception for interrupting the keyboard.read_event function, but I didn't manage to make it work.
import keyboard
import threading
import time
class Interrupt_Custom_Exception(Exception):
"""Base class for other exceptions"""
pass
#########################################################
def delete_and_write(times_to_delete, word_to_write):
print("------------Deleting & Rewrite Started---")
time.sleep(2)
print("------------Deleting & Rewrite Ended---")
# simulate deletion and rewrite
#**here I tried the raise Interrupt_Custom_Exception and tried to catch it at the code in the class, but didn't work**
def write_the_suppressed_string(string):
keyboard.write(string)
#########################################################
class keyboard_monitor(threading.Thread):
def __init__(self,thread_name, threadID, word_typed, keyboard_suppress, counter_for_key_pressed):
threading.Thread.__init__(self)
self.name = thread_name
self.threaID = threadID
self.fstring = word_typed
self.counter_for_key_presses = counter_for_key_pressed
self.suppressed = keyboard_suppress
self.temp = ""
def stop(self):
self._is_running = False
def run(self):
if (self.suppressed is False):
while(True):
event = keyboard.read_event(suppress = self.suppressed)
if (event.event_type == keyboard.KEY_DOWN):
if (event.name == "space"):
suppressed_monitor = keyboard_monitor("suppressed_monitor", 2, self.fstring, True, self.counter_for_key_presses)
suppressed_monitor.start()
suppressed_monitor.join()
print("RETURNED TO MAIN MONITOR")
self.counter_for_key_presses = 0
self.fstring = ""
elif (event.name in "abcdefghijklmnopqrstuvwxyz"):
self.fstring = ''.join([self.fstring, event.name])
self.counter_for_key_presses += 1
elif (self.suppressed is True):
def listen_to_keyboard():
event = keyboard.read_event(suppress=self.suppressed)
# **here is where the program waits and don't continue when the correction thread is finished.**
if (event.event_type == keyboard.KEY_DOWN):
print("---KEYS PRESSED WHILE SUPPRESSED = {}---".format(event.name))
if (event.name in "abcdefghijklmnopqrstuvwxyz"):
self.fstring = ''.join([self.fstring, event.name])
self.counter_for_key_presses += 1
try:
#########################################################
# INITIALY CORRECTING THE WORD PASSED FROM THE NORMAL KEY MONITOR
self.temp = self.fstring
self.fstring = ""
thread_delete_and_rewrite = threading.Thread(
target = delete_and_write, args=(self.counter_for_key_presses, self.temp))
thread_delete_and_rewrite.start()
# raise Interrupt_Custom_Exception
#########################################################
print("-BEFORE WHILE LOOP-")
while(thread_delete_and_rewrite.is_alive() is True): # **this works ok but if the control enters the listen_to_keyboard function waits there until a key is pressed. I want somehow to stop this manually and continue the code after this while**
print("--ENTERING THE WHILE LOOP--")
listen_to_keyboard()
print("----EXITING THE WHILE LOOP----\n")
except Interrupt_Custom_Exception:
print("!!!!!!!!!!!!!!!!!CAUGHT IT!!!!!!!!!!!!!!!!!!!")
print("----EXITING THE WHILE LOOP----\n")
print("------BEFORE FINAL WRITE------")
if (self.fstring != ""):
thread_write = threading.Thread(
target = write_the_suppressed_string, args=(self.fstring, ))
thread_write.start()
thread_write.join()
print("SUPPRESSED ENDED")
self._is_running = False
if __name__ == "__main__":
kb_not_suppressed = keyboard_monitor("not_suppressed_monitor", 1, "", False, 0)
kb_not_suppressed.start()
kb_not_suppressed.join()
Any idea on how to exit this blocking function would be very very useful.
Thanks in advance.
It's not possible unless you find some keyboard.read_event that has a timeout, or does a non-blocking check if there's a event. I haven't found any of those in keyboard module ;/
A big workaround would be to keyboard.press in case you want to exit. Not sure if you can detect if it's not from the user. It's up to you if it's acceptable.

Run infinite while loop in Python module

I'm writing a Python module to read jstest output and make Xbox gamepad working in Python on Linux. I need to start in background infinite while loop in __init__ on another thread that looks like this:
import os
from threading import Thread
import time
import select
import subprocess
class Joystick:
"""Initializes base class and launches jstest and xboxdrv"""
def __init__(self, refreshRate=2000, deadzone=4000):
self.proc = subprocess.Popen(['xboxdrv', '-D', '-v', '--detach-kernel-driver', '--dpad-as-button'], stdout=subprocess.PIPE, bufsize=0)
self.pipe = self.proc.stdout
self.refresh = refreshRate
self.refreshDelay = 1.0 / refreshRate
self.refreshTime = 0 # indicates the next refresh
self.deadzone = deadzone
self.start()
self.xbox = subprocess.Popen(['jstest', '--normal', '/dev/input/js0'], stdout=subprocess.PIPE, bufsize=-1, universal_newlines=True)
self.response = self.xbox.stdout.readline()
a = Thread(target=self.reload2())
a.start()
print("working")
def reload2(self):
while True:
self.response = self.xbox.stdout.readline()
print("read")
time.sleep(0.5)
def start(self):
global leftVibrateAmount, rightVibrateAmount
leftVibrateAmount = 0
rightVibrateAmount = 0
readTime = time.time() + 1 # here we wait a while
found = False
while readTime > time.time() and not found:
readable, writeable, exception = select.select([self.pipe], [], [], 0)
if readable:
response = self.pipe.readline()
# tries to detect if controller is connected
if response == b'[ERROR] XboxdrvDaemon::run(): fatal exception: DBusSubsystem::request_name(): failed to become primary owner of dbus name\n':
raise IOError("Another instance of xboxdrv is running.")
elif response == b'[INFO] XboxdrvDaemon::connect(): connecting slot to thread\n':
found = True
self.reading = response
elif response == b'':
raise IOError('Are you running as sudo?')
if not found:
self.pipe.close()
# halt if controller not found
raise IOError("Xbox controller/receiver isn't connected")
The loop is defined to start running in __init__ function like so:
a = threading.Thread(target=self.reload2) # code hangs here
a.start()
But each time I create variable "a", whole program hangs in while loop, which should be running in another thread.
Thanks for help.
You may be having issues with your __init__. I put it in a simple class as an example, and it runs as expected.
import time
from threading import Thread
class InfiniteLooper():
def __init__(self):
a = Thread(target=self.reload2) # reload, not reload(), otherwise you're executing reload2 and assigning the result to Target, but it's an infinite loop, so...
print('Added thread')
a.start()
print('Thread started')
def reload2(self):
while True:
self.response = input('Enter something')
print('read')
time.sleep(0.5)
loop = InfiniteLooper()
Output:
Added thread
Thread started
Enter something
1
read
Enter something
1
read
As you can see, the "Enter something" appears after I've added the thread and started it. It also loops fine

Why can't I start two threads inside constructor __init__() of a python class?

I'm simply trying to run two different daemon threads and print a line from each for testing. While this code works :
import time
import threading
from threading import Thread
from myFunctions import *
class Monitoring:
def alarms(self):
return alarms.run()
def generator(self):
return generator.run()
def run(self):
generator = threading.Thread(target=self.alarms)
generator.daemon = True
generator.start()
alarm = threading.Thread(target=self.generator)
alarm.daemon = True
alarm.start()
print("started thread")
if __name__ == '__main__':
try:
d = Monitoring()
d.daemon = True
d.run()
print("started the thread")
while 1:
time.sleep(1)
except KeyboardInterrupt:
alarms.close()
generator.close()
print("Main - Keyboard interrupt in __main__")
Something like this doesn't seem to work and only the first thread "alarms" start. Why is that?
class Monitoring:
def __init__(self,a,g):
self.a = a
self.g = g
def run(self):
generator = threading.Thread(target=self.a)
generator.daemon = True
generator.start()
alarm = threading.Thread(target=self.g)
alarm.daemon = True
alarm.start()
print("#class run() ")
if __name__ == '__main__':
try:
d = Monitoring(alarms.run(), generator.run())
d.daemon = True
d.run()
print("#__main__")
while 1:
time.sleep(1)
except KeyboardInterrupt:
alarms.close()
generator.close()
print("Main - Keyboard interrupt in __main__")
In the line
d = Monitoring(alarms.run(), generator.run())
the functions alarms.run and generator.run are called instantly and the return values (probably None) are given as arguments to the Monitoring constructor.
To use the function objects as arguments (which are then called in a new thread), use
d = Monitoring(alarms.run, generator.run)
instead.

Python threaded class calls another threaded class (queue help)

I am trying to control a 3-axis printer using an x-box controller. To get inputs from the x-box I have borrowed code from martinohanlon https://github.com/martinohanlon/XboxController/blob/master/XboxController.py
I have also created code that reads a text file line by line (G-code) to move the printer.
I would like to be able to use the X-Box controller to select a G-code file and run it, then as the printer is running continue to listen for a cancel button just in case the print goes wrong. The controller is a threaded class, and my readGcode is a threaded class.
The problem I'm having is that when I use the controller to start the readGcode class I cant communicate with the controller until that thread finished.
My temporary solution is to use the controller to select a file then pass that files path to the readGcode class. In the readGcode class it keeps trying to open a file using a try block and fails until the filepath is acceptable. Then it changes a bool which makes it skip further reading until its done.
Code:
import V2_Controller as Controller
import V2_ReadFile as Read
import time
import sys
# file daialogue
import tkinter as tk
from tkinter import filedialog
# when X is selected on the x-box controller
def X(xValue):
if not bool(xValue):
try:
f=selectfile()
rf.setfilename(f)
except:
print("failed to select file")
def selectfile():
try:
root = tk.Tk() # opens tkinter
root.withdraw() # closes the tkinter window
return filedialog.askopenfilename()
except Exception:
print("no file")
# setup xbox controller
xboxCont = Controller.XboxController(controlCallBack, deadzone=30,
scale=100, invertYAxis=True)
# init the readfile class
rf = Read.Readfile()
# set the custom function for pressing X
xboxCont.setupControlCallback(xboxCont.XboxControls.X, X)
try:
# start the controller and readfile threads
xboxCont.start()
rf.start()
xboxCont.join()
rf.join()
while True:
time.sleep(1)
# Ctrl C
except KeyboardInterrupt:
print("User cancelled")
# error
except:
print("Unexpected error:", sys.exc_info()[0])
raise
finally:
# stop the controller
xboxCont.stop()
rf.stop()
V2_Readfile
# Main class for reading the script
class Readfile(threading.Thread):
# supports all variables needed to read a script
class readfile:
fileselect = True
linecount = 0
currentline = 0
commands = []
# setup readfile class
def __init__(self):
# setup threading
threading.Thread.__init__(self)
# persist values
self.running = False
self.reading = False
def setfilename(self,filename):
self.filename = filename
# called by the thread
def run(self):
self._start()
# start reading
def _start(self):
self.running = True
while self.running:
time.sleep(1)
if not self.reading:
try:
self.startread()
except:
pass
def startread(self):
try:
with open(self.filename, "r") as f: # read a local file
f1 = f.readlines()
# run through each line and extract the command from each line
linecount = 0
line = []
for x in f1:
# read each line into an array
line.append(x.split(";")[0])
linecount += 1
# Store the variables for later use
self.readfile.linecount = linecount
self.readfile.commands = line
self.reading = True
except Exception:
pass
i = 0
while i < self.readfile.linecount and self.reading:
self.readfile.currentline = i + 1
self.readline(i)
i += 1
# the following stops the code from reading again
self.reading = False
self.filename = ""
def readline(self,line):
Sort.sortline(self.readfile.commands[line])
# stops the controller
def stop(self):
self.running = False
You could use a syncronization primitive like threading.Event.
To do so, you need to modify your Readfile class like this:
from threading import Event
class Readfile(threading.Thread):
# setup readfile class
def __init__(self):
# setup threading
threading.Thread.__init__(self)
# persist values
self.running = False
self.reading = False
self.reading_file = Event() # Initialize your event here it here
def setfilename(self,filename):
self.filename = filename
self.reading_file.set() # This awakens the reader
def _start(self):
self.running = True
self.reading_file.wait() # Thread will be stopped until readfilename is called
self.startread()
Another syncronization primitive worth exploring is queue.Queue. It could be useful if you want to process more than one filename.
The pattern you describe in your question is called Busy Waiting, and should be avoided when possible.

Send file pointer to python thread and update file pointer

I have a python program with a thread and the thread should write into a file. I will spawn a thread from the main program. Now on new day trigger I will change the file pointer in the main program and I want the thread also to take the new file to write the data to the file.
I have a code which will take global variable and do this task. But is there any other better way of doing this?
#!/usr/bin/env python
import sys
import threading
import time
filePtr = None
import time
def fileWriteTh():
global filePtr
time.sleep(2)
filePtr.write("from the thrread this should in file 2")
def main():
global filePtr
filePtr = open("test1.txt","ab")
fileThread = threading.Thread(target=fileWriteTh)
fileThread.start()
if new_day_trigger:
filePtr.close()
filePtr = open("test2.txt","ab")
fileThread.join()
if __name__ == "__main__":
main()
This is the new code that is written:
#!/usr/bin/env python
import sys
import threading
import time
class SendPacket(object):
fileDesc = None
def __init__(self, fd):
super(SendPacket, self).__init__()
SendPacket.fileDesc = fd
def printFromInstance(self,var):
print var
SendPacket.fileDesc.write(var)
time.sleep(3)
print var
SendPacket.fileDesc.write(var)
def startabc(self, someVar):
self.printFromInstance(someVar)
#classmethod
def printVar(cls, printStr):
print printStr
cls.fileDesc.write(printStr)
#classmethod
def changeClsFile(cls, newFd):
cls.fileDesc = newFd
def main():
filePtr = open("test1.txt","ab")
sendPack_inst = SendPacket(filePtr)
fileThread = threading.Thread(target=sendPack_inst.startabc, args=("test1",))
fileThread.start()
time.sleep(2)
filePtr.close()
filePtr = open("test2.txt","ab")
SendPacket.changeClsFile(filePtr)
fileThread.join()
filePtr.close()
if __name__ == "__main__":
main()
Like this:
#!/usr/bin/env python
import sys
import thread
import time
class _fileACT :
def __init__(self):
self.trigger = 0
self.flag = True
self.msg = ""
self.files = (open("test1.txt","ab"),open("test2.txt","ab"))
def run(self,pssrg):
while self.flag :
if self.msg != "" :
self.files[self.trigger].write(self.msg)
self.msg = ""
def test(self,pssrg):
for i in range(20):
time.sleep(1)
if i %2 != 0 :
self.trigger = 0
elif i %2 != 1:
self.trigger = 1
self.msg = "%0.3d test-1,asdasdasd\n"%i
time.sleep(0.5)
print "wait..."
self.flag = False
for e in self.files : e.close()
print "can exit !"
if __name__ == "__main__":
fileACT = _fileACT()
thread.start_new_thread(fileACT.run,(None,))
thread.start_new_thread(fileACT.test,(None,))
We have three variables, filename, last opened file name and message. Two files, only False and True will be sufficient (of course you can use index for multiple files). We've written a test function into the class because we don't want our main cycle to freeze. The file selection is done with ' trigger ', but the previous and next file name is not the same, the previous closes.
The important point in the thread is that the time delay is strictly unavailable! The time delay is always applied to the trigger. The time delay cannot be placed in the main loop. An instance of access from outside the class is also attached. I hope it helps.

Categories