Threading in python on a raspberry pi issues - python

I am trying to run a GUI that spawns threads that perform very basic and not computationally complicated tasks on a raspberry pi 1 and I cannot seem to get the threads to work.
I developed the code on a x86 intel computer and it works perfectly well. The threading commands basically just allow for button presses and listening for serial data concurrently.
def extra_thread_disable():
# Disables buttons that would interfere with data that is currently being sent
while threading.active_count() == 3:
run_file_butt.config(state = 'disabled')
run_butt.config(state = 'disabled')
serial_butt.config(state = 'disabled')
popup_butt.config(state = 'disabled')
homing_butt.config(state = 'disabled')
level_butt.config(state = 'disabled')
zero_button1.config(state = 'disabled')
zero_button2.config(state = 'disabled')
zero_button3.config(state = 'disabled')
else:
run_file_butt.config(state = 'normal')
run_butt.config(state = 'normal')
serial_butt.config(state = 'normal')
popup_butt.config(state = 'normal')
homing_butt.config(state = 'normal')
level_butt.config(state = 'normal')
zero_button1.config(state = 'normal')
zero_button2.config(state = 'normal')
zero_button3.config(state = 'normal')
pass
def thread_data():
# Starts a thread to send data while allowing stop button to be pressed
try:
global t2
t2 = threading.Thread(name='send_line', target = send_data, daemon = True)
t_disable = threading.Thread(name='disable', target = extra_thread_disable, daemon = True)
t2.start()
t_disable.start()
except:
update_textbox("Threading Error: data thread not properly created")
def send_data():
# Sends single motion commands and waits for response to continue
global save_path
global motor_param
vals = get_vals()
try:
data = struct.pack("!llllhhhhhhhh", vals['dist1'], vals['dist2'], vals['dist34'], vals['dist34'], vals['speed1'], vals['speed2'], vals['speed34'], vals['speed34'], vals['accel1'], vals['accel2'], vals['accel34'], vals['accel34'])
try:
ser.write(data)
update_textbox("Running...")
except:
update_textbox("Error: Data not sent")
try:
motor1pos = int(ser.readline())
motor2pos = int(ser.readline())
motor3pos = int(ser.readline())
motor4pos = int(ser.readline())
ready = ser.read(1)
update_textbox("Movement complete")
axis1_current.set(str(reverse_convert(motor1pos, 1)))
axis2_current.set(str(reverse_convert(motor2pos, 2)))
axis3_current.set(str(reverse_convert(motor3pos, 3)))
writetofile()
except:
update_textbox("Error: reading data improperly")
except:
update_textbox("Error: data not sent properly")
pass
The code basically just allows the main GUI thread to allow for a stop button to be pressed and disable all the buttons that could interfere with the sent data. That thread then just waits for the response from an arduino it is connected to. Again this all works flawlessly on a normal computer. I get no errors or warnings in the terminal when run on a raspberry pi but it seems to be blocking. I thought maybe it was just such a slow computer or the infamous GIL. It seems like that might be the reason. If so, should I switch to the multiprocessing library in python? is there a way to get around this? It doesn't work when run in terminal calling python3 and it doesn't work when it was compiled to a static binary using pyinstaller.

The answer is to get better hardware. I changed nothing in my code but instead bought a newer raspberry pi 4 instead of the original raspberry pi and the threading worked as it originally did on my other pc running arch.

Related

Problem using threading in tkinter GUI interface

I am trying to create a GUI python interface to display the value from sensors which are connected to an arduino via serial communication. For this interface I am using a class named application().
I first created the GUI application using Tkinter which is functional. That is to say that the different elements (frame, widgets, etc.) are displayed correctly.
Then I created a function (get_data), to retrieve the data from the arduino. Each byte I retrieve the value and store it in an array with the associated key (the name of the sensors). Then I retrieve the array for the parse and assign the values ​​in variables (integer or float) so that I can retrieve them in the GUI via the function (update_data).
To avoid having to worry between displaying and looping data, I decided to use the library threading to run the get_data() and update_data() functions continuously in other threads.
when I launch the application, the code loops on get_data() (see the print below), but the Tkinter interface does not launch.
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Wait, sending data
Here are the two functions to retrieve data:
def get_data(self):
ser = serial.Serial('/dev/cu.usbmodem14201', 9600)
ser.flushInput()
index=0
self.currentDataArray = np.array(np.zeros([index]))
while True:
try:
for c in ser.readline():
current_decoded_bytes = float(ser_bytes[0:len(ser_bytes)-2].decode("utf-8"))
print(current_decoded_bytes)
self.currentDataArray = np.append(self.currentDataArray,current_decoded_bytes)
if c == '\n':
return self.currentDataArray
self.currentDataArray = np.array(np.zeros([index]))
except:
print("Wait, sending data")
pass
def Update_value(self, currentDataArray,update_period):
print("updating data")
new = time.time()
print(self.currentDataArray)
analogValue=float()
VoltageEcSensor=float()
tempWaterSensor=float()
TemperatureB=float()
HumidityB=float()
pHvalue=float()
EcSensorValue=float()
extractorStatement=int()
ligthStatement=int()
intractorStatement=int()
fanStatement=int()
while(1):
currentDataArray.all()
try:
self.analogValue=self.currentDataArray[0]
self.VoltageEcSensor=self.currentDataArray[1]
self.tempWaterSensor=self.currentDataArray[2]
self.TemperatureB=self.currentDataArray[3]
self.HumidityB=self.currentDataArray[4]
self.pHvalue=self.currentDataArray[5]
self.EcSensorValue=self.currentDataArray[6]
self.extractorStatement=self.currentDataArray[7]
self.ligthStatement=self.currentDataArray[8]
self.intractorStatement=self.currentDataArray[9]
self.fanStatement=self.currentDataArray[10]
except:
pass
if time.time() - new >= update_period:
self.analogValue=0
self.VoltageEcSensor=0
self.tempWaterSensor=0
self.TemperatureB=0
self.HumidityB=0
self.pHvalue=0
self.EcSensorValue=0
self.extractorStatement=0
self.ligthStatement=0
self.intractorStatement=0
self.fanStatement=0
new = time.time()
pass
Here is a function to acquire data continuously using other threads:
def threading(self):
multi = threading.Thread(target = self.get_data())
# multi.deamon = True
multi.start()
multi2 = threading.Thread(target = self.Update_value(self.currentDataArray, self.update_period))
# multi2.deamon = True
multi2.start()
This is how I initialize in the tkinter interface:
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
update_period = 5
tempBox=self.TemperatureB
tempExtBox=23
humidityBox=self.HumidityB
TempSolution=self.tempWaterSensor
pH_solution=self.pHvalue
Ec_solution=self.EcSensorValue
Global_conso=110
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
# set all width and height for each Pane
width_MODEpane=screen_width/3
height_MODEpane=screen_height/4
self.MainWindow(screen_height, screen_height)
if __name__ == "__main__":
app = Application()
app.threading()
app.title("Automate GUI app")
app.mainloop()
I think I'm not using correctly the threading library, do you have any suggestion to solve this issue?

Tkinter GUI updating depending on serial connection

Good afternoon thank you all who look at my issue,
I am building a script to do initial configuration on switches through console port, I can make a connection to the console port the issue comes when building a GUI in TKINTER.
I want the gui to connect through a specified some port when the button is pressed and when the connection is made make a light(using canvas) go green showing a successful connection went through.
My issue is getting the light to change to green and stay when a connection is made I have tried
my initally also tried to use global then realized the window loop constantly set it back to red
nested while loop--- breaks Tkinker
importing a file I created called variableset which stores variable to set green but since it constantly has a new instance it just sets the variable in tinker back to red.
any help would be greatly appreciated
GUI CODE
import tkinter as tk
from tkinter import *
import connect
import variableset
setting = variableset.setting
window = tk.Tk()
window.title("Network Wizard 1")
window.geometry('300x500')
# setting =tk.IntVar()
# setting.set(variableset.setting)
#serial port pick the right one
serialportlabel = tk.Label(text="COM Number")
serialport = tk.Entry(width= 7)
serialportlabel.pack()
serialport.pack()
#color alerting!! red bad
alert = Canvas(window, width=50, height=20)
alert.pack()
if setting == 0:
a=alert.create_rectangle(5, 0, 50, 50, fill='red')
else:
a=alert.create_rectangle(5, 0, 50, 50, fill='green')
# #connection part woop woop
connection = tk.Button(text="connect", command = lambda: bus())
connection.pack()
# #firmwarecheck
# firmwarecheck = tk.Button(text="Firmware check")
# firmwarecheck.pack()
# #firmware update
# firmwareupdate= tk.Button(text="Firmware update")
# firmwareupdate.pack()
# #software update
# software = tk.Button(text="software update")
# connect.pack()
# #vmlans
# vlanupdate = tk.Button(text="Vlanupdate")
# vlanupdate.pack()
# disconnect = tk.Button(text="disconnect")
# disconnect.pack()
# qut = tk.Button(text="quite")
# qut.pack()
def bus():
global ser
ser = connect.connect(serialport.get())
global setting
setting = variableset.initial
# def firmware(ser):
# if ser.isOpen() == true :
window.mainloop()
Connect code
import serial
import time
import sys
#connect
def connect(com):
ser = serial.Serial(
port = com, #COM
baudrate=9600,
parity='N',
stopbits=1,
bytesize=8,
timeout=8
)
ser.isOpen()
print(ser.name)
#set variables
enter = str.encode('\r\n') #enter
user = str.encode('admin#sytem\r\n') #default user name
pwd = str.encode('\r\n') #defualt password
qut = str.encode('quit\r')
time.sleep(1.0)
# ser.inWaiting()
ser.write(enter) #promt login
time.sleep(0.5)
ser.write(user) #enter user name
time.sleep(0.5)
ser.write(pwd) #enter password
time.sleep(0.5)
ser.write(enter)
time.sleep(0.5)
ser.write(enter)
time.sleep(0.5)
ser.write(str.encode("sytem\r\n"))
time.sleep(0.5)
ser.write(qut)
ser.write(qut)
input_read = ser.read(500)
input_read = input_read.decode("utf-8","ignore")
print(input_read)
ser.close()
return ser
def write(ser):
ser.write(str.encode(''+'\r\n'))
def disconnect(ser):
ser.write(str.encode('quit\r\n'))
time.sleep(.2)
ser.write(str.encode('quit\r\n'))
time.sleep(.2)
ser.write(str.encode('quit\r\n'))
time.sleep(.2)
ser.close()
Variableset Code
global setting
setting = 0
def initial():
global setting
setting = 1
return setting
def unset():
global setting
setting = 0
return setting
Any help greatly appreciated
Your if-else statement only runs once, before the setting variable has been set, as far as we can tell. It's not in a loop or a function that gets called from somewhere else. What exactly is the problem you're having with the code that you posted? The issues you described seem to relate to a loop setting it back to red in a different version of the code.
In any event, I suspect that your problem is trying to use infinite loops to keep checking the setting, which prevents the tkinter mainloop() from running, and therefore blocks the GUI from updating. If so, the best method to fix it is probably to change your code for "check the setting and then change the color" into its own function, which gets called for the first time at the end of your connect function. Then, the end of the check setting function should schedule itself to be run after a time delay using the tkinter after() method, which is non-blocking (asnychronous) and allows the mainloop() to keep running. Alternative methods include a separate thread to run the check-setting code, or using the tkinter update() method, but after() is easiest.
Note that your sleep() functions in the connect code will also block the mainloop while they're running. I'm also not clear what you're trying to do with the variableset code; it appears to set the setting to 1 (aka green light) when you call initial(), regardless of the actual status on the serial port.

How do I make a client socket receive data from another socket in a non-blocking manner?

I am trying to make my client socket non-blocking. But unsuccessful. I'm not sure how to fix it.
I am trying to build a simple chat box and I am using tkinter for the GUI.
The main part that is to note is the while loop that I have segmented within the code
My client code is:
.
.
.
class GroupFrame(Frame):
def __init__(self, master):
super().__init__(master)
global memList
global rootHome
self.master.geometry("400x500")
self.master.resizable(width=FALSE, height=FALSE)
self.master.title("PowerPuff Chat Girls")
#TextArea
self.ChatLog = Text(self, bd=0, bg="light grey", height="13", width="55", font="Arial")
self.ChatLog.insert(END, 'Welcome to the PowerPuff Chat, ' + username + '\n', 'INIT')
self.ChatLog.config(state=DISABLED)
self.ChatLog.tag_config('INIT', foreground='red', justify=CENTER)
self.ChatLog.tag_config('BLUE', foreground='blue', justify=LEFT)
self.ChatLog.tag_config('BLK', foreground='black', justify=RIGHT)
#ScrollBar
self.scrollbar = Scrollbar(self, command = self.ChatLog.yview, cursor="heart")
self.ChatLog['yscrollcommand'] = self.scrollbar.set
#EntryBox
self.EntryBox = Text(self, bg="white", width="29", height="5", font="Arial")
self.EntryBox.bind("<KeyRelease-Return>", lambda event: sendData(self.EntryBox.get(1.0, END)))
#SendButton
self.SendButton = Button(self, font=30, text="Send", width="11", height=1,
bg="white", fg='navy blue', activebackground="#FACC2E", command=lambda: sendData(self.EntryBox.get(1.0, END)))
#Place them on Screen
self.scrollbar.place(x=380, y=6, height=386)
self.ChatLog.place(x=8, y=6, height=405, width=370)
self.EntryBox.place(x=128, y=425, height=60, width=248)
self.SendButton.place(x=6, y=425, height=60)
def sendData(param):
if param == '\n\n':
self.EntryBox.delete(1.0, END)
return
if len(param) > 1:
if '\n\n' in param:
# strip both the carriage return and appened with only one.
param = param.rstrip('\n')
param = param + '\n'
self.EntryBox.delete(1.0, END)
insertText(1, '>>' + param)
s.sendall(str.encode(param))
# data = s.recv(4500)
def inspectData(data):
if 'joined the chat **\n' in data.decode('utf-8'):
insertText(3, data.decode('utf-8'))
else:
insertText(2, '>>' + data.decode('utf-8'))
self.ChatLog.see(END) # this shows the END of the chatlog; auto scroll down
def insertText(num, param):
self.ChatLog.config(state=NORMAL)
if num == 1:
self.ChatLog.insert(END, param, 'BLK')
if num ==2 :
self.ChatLog.insert(END, param, 'BLUE')
if num ==3:
self.ChatLog.insert(END, param, 'INIT')
self.ChatLog.config(state=DISABLED)
*******************************************************
while True:
try:
data = s.recv(4096)
if(data):
inspectData(data)
except:
break
*******************************************************
page = Tk()
page.geometry("400x500")
page.resizable(width=FALSE, height=FALSE)
pf = HomeFrame(page)
page.mainloop()
With my current code, when I run it the client just freezes when it is currently in the GroupFrame Frame.
I am not sure why this happens. And If i haven't made my question clear please let me know so I can provide some images and parts of code that will help.
There are a few different problems with the code as presented. The main issue is the while loop in the __init__ function. This will make __init__ run forever (assuming the socket is blocking). This, in turn, will block the main loop of Tk, and therefore the whole program.
When working with Tk, one must realize that Tk owns the main loop. This means that one must always allow Tk to run, to dequeue events posted to its internal event queue. If your program needs to do something else, like, for example, listen to a socket, this has to be done, either in a separate thread or to be incorporated in the Tk loop.
Unfortunately there is no general way to make the socket input part of the Tk loop. However, what we can do, is to post a certain event using after that will ensure the main loop will call a predefined function after a certain time. This can be used in order to create a solution, where it is possible to poll the socket at certain times, and check if there are new data to process.
Another way is to run the socket in a separate thread, that posts messages (through a synchronized Queue) to the Tk thread. That might by a more general solution, but it still requires the main loop to poll the queue instead of the socket.
All of this is quite a bit to digest. I suggest that you try to reduce your example further, and then ask questions on smaller examples if there are parts that are still hard to grasp.
I managed to fix my issue. In case anyone else has a similar problem of polling for a data received from another socket within a Tkinter loop. This is how I fixed it:
Create a function that polls through the socket within your Frame and make a separate thread within that Frame that runs it.
Code:
def polling():
while True:
try:
polledData = s.recv(4096)
print(polledData.decode('utf-8'))
except:
pass #or you can print out the exception if you need to.
threading.Thread(target = polling, args=[]).start()

Update Tkinter from within an Exscript function

I am writing a Python script that uses Exscript to log into network switches to gather information. I'm using Tkinter to present a graphical interface.
I'm trying to create a canvas window with status so that when the "go" button is clicked, the status windows is made visible and as the script logs into devices a label on the status window is updated (logging into device x of y).
I found the .update() and .update_idletasks(), which works when I enter the initial function (clicking the go button), but updating the label happens 2 functions deep in the Exscript start() function. In there anything I do to my canvas causes the script to hang and I have to force close it.
onhost = 0
hostcount = 0
def go_main():
canvas.itemconfig(statuswindow, state='normal')
canvas.itemconfig(statuslabel_window, state='normal')
canvas.update()
filename = InvEntry.get()
accounts = [Account(unameEntry.get(), pwordEntry.get())]
workbook = xlrd.open_workbook(filename)
sheet = workbook.sheet_by_name("Infrastructure")
global hostcount
hostcount = sheet.nrows
hosts = []
for x in range (1, hostcount):
hosts.append(conproto.get() + "://" + sheet.cell_value(x, 0))
start(accounts, hosts, login_func)
def login_func(job, host, conn):
global onhost
global hostcount
onhost = onhost + 1
newstat = "Gathering MACs on switch %s of %s..." % (onhost, hostcount)
stat.set(newstat)
#canvas.update()
conn.execute('term len 0') #We don't want a pause in the output
conn.execute('sh ver') #Make sure we're a Nexus or Catalyst switch before continuing
r = conn.response
if r != '' and ('nx-os' or 'catalyst') in r.lower():
#Grab the device's hostname:
As soon as it hits the "stat.set(newstat)" line it hangs. What am I doing wrong? Sorry if I slaughtered all the programmer lingo, I'm a network engineer pretending to be a programmer. Thanks!

Python - Regarding tkinter and a updating program

Basically, I am asking how to put a continuously updating program on display into tkinter's text widget.
from tkinter import Tk, Frame, Text, BOTH
class FrameApp(Frame):
def __init__(self, parent):
Frame.__init__(self, parent, background="white")
self.parent = parent
self.parent.title("Ethis")
self.pack(fill=BOTH, expand=1)
self.centerWindow()
def centerWindow(self):
w = 900
h = 450
sw = self.parent.winfo_screenwidth()
sh = self.parent.winfo_screenheight()
x = (sw - w)/2
y = (sh - h)/2
self.parent.geometry("%dx%d+%d+%d" % (w, h, x, y))
def theText(self):
w = Text ()
def main():
root=Tk()
app = FrameApp(root)
root.mainloop()
if __name__ == '__main__':
main()
This is my tkinter program. As you can see, I've centered it and set it up with a text function defined as theText(self). I have done anything with theText(self) because I don't know where to begin. This works fine alone, it starts up, as expected, in the center with it's title.
# Money Generator Mark 1
import time
t = 'true'
while t == 'true':
s = 0
x = 1
print ("You have $%s." % (s))
time.sleep(.75)
t = 'false'
while t == 'false':
s = s + (1 * x)
print ("You have $%s." % (s))
time.sleep(.75)
if s >= 100 and s < 200:
x = 2
if s >= 200:
x = 4
Here I have another program that works fine on it's own. I've dubbed it Money Generator as is akin to Cookie Clicker and Candy Box, those types of things. This also works fine in the command box, function and printing to there. I was wondering how to integrate these two separate programs so that the second program that is listed here will be displayed in tkinter's window.
Here is my new code that has a new issue. I'm receiving an error stating that 'generate_money' is not defined in the theText function. These new functions are within my frameApp class.
def theText(self):
self.w = Text()
self.t = threading.Thread(target=generate_money, args=(self.w))
self.t.daemon = True
def generate_money(textwidget):
p = subprocess.Popen([sys.executable, os.path.join('window.py', 'moneygenerator.py')],
stdout = subprocess.PIPE)
for line in p.stdout:
do_stuff_with(textwidget, line)
p.close()
This is unfortunately going to be a little trickier than you'd like.
The first part is easy: you can just use the subprocess module to run the background script and capture its output:
p = subprocess.Popen([sys.executable, os.path.join(scriptpath, 'moneygenerator.py')],
stdout = subprocess.PIPE)
for line in p.stdout:
do_stuff_with(line)
p.close()
The problem is that doing this in the middle of a Tkinter callback will block the entire program until the background program is done. So, instead of getting updated on each line, you'll just freeze up the app until the OS kills you/displays a beachball/etc.
There are two usual solutions to doing stuff without blocking: Do it on a thread—which is easy, except that you can't access Tkinter widgets from a background thread. Or check the subprocess's pipe in a non-blocking way—which would be great if there were a cross-platform way to do that without potentially blocking forever. (There are, however, third-party "async subprocess" wrappers on PyPI, which may be an alternate way to solve this problem.)
So, you need to combine both of these. Have a thread that blocks on the subprocess and posts messages on something that you can check in a non-blocking way in the main thread, like a queue.Queue.
But, instead of writing it yourself, there's a nice wrapper called mtTkinter that does most of the hard part for you. You can just write your background thread as if it were legal to access the widgets, and it will intercept that access and turn it into queue posts. Sadly, mtTkinter doesn't work in Python 3—but it looks pretty easy to fix; I slapped together a port in a few minutes. Although I haven't done much testing on it, I think this is probably the simplest way forward.
So:
from tkinter import Tk, Frame, Text, BOTH
import subprocess
import sys
import threading
# ...
def generate_money(textwidget):
p = subprocess.Popen([sys.executable, os.path.join(scriptpath, 'moneygenerator.py')],
stdout = subprocess.PIPE)
for line in p.stdout:
do_stuff_with(textwidget, line)
p.close()
# ...
class FrameApp(Frame):
# ...
def theText(self):
self.w = Text()
self.t = threading.Thread(target=generate_money, args=(self.w,))
You'll probably want to add some way to either tell self.t to shut down early, or just wait for it to finish, at quit time. (Or, alternatively, because you know that it can't leave any "dangerous garbage" behind it killed suddenly, you may be able to just set self.t.daemon = True.) But this is enough to show the basics.

Categories

Resources