Freeze in a .after() Tkinter with Serial reading - python

my project is to build a scanner that send the sensor value on Serial port. I want to control the scanner using a GUI Tkinter, so I want to check if something is coming on the serial continuously. However, I used the .after() function that works when something is sent but when the scanner is "waiting" and nothing is sent, the GUI freezes and I can't to do anything until something is sent.
Thanks,
Here's the code triggered by the main button:
def Acqard():
global flag
flag = 1
arduinoData.write(b'1')
log.insert(END, "Début du scan\n")
root.after(10, write_data)
And here are the functions that save the DATA in a txt file:
def write_data():
global file_stream
file_stream = open("data.txt", "a") # mode write ou append ?
write_lines()
def write_lines():
global after_id
data = arduinoData.read()
try:
data2 = data.decode("utf-8")
file_stream.write(data2)
print(data2)
if data2 == "F":
root.after_cancel(after_id)
print("Ca a marché")
stopacq()
after_id = root.after(10, write_data)
except:
return
And here's the function that stops the scanner:
def stopacq():
global flag, file_stream
flag = 0
root.after_cancel(after_id)# Annulation des rappels automatiques de write_lines
file_stream.close()
file_stream = None
arduinoData.write(b'3')
log.insert(END, "Scan interrompu\n")

Related

How can I make tkinter wait for a line of code to finish before continuing?

so I am making a program on tkinter that gets a response from a server and depending on the answer, it will change the background color, to either green for success or red for error, the problem is that I realized that when running the code, the windows.after() method doesn't wait till is done to continue and when I do the request for the server, it have to do it three times to check if the response is correct, and it is suppossed to change the window background color each time, but it is only doing it one time. And not only the background color changing fails, also I want to change a label's text when it is doing the request,but it does it really quick and I'm not able to diferentiate the changes, so the question is: how can I
How can I make the program wait until one line finishes running to go to the next one and not everything happens at the same time and so fast?
Here is a piece of my code, I removed the request part because I'm trying to solve this problem first:
# import gpiozero
# import picamera
import json
import requests
import tkinter as tk
with open("config.json") as file:
config = json.load(file)
ENDPOINT = config["ENDPOINT"]
USUARIO = config["USUARIO"]
ESTACION = config["ESTACION"]
TIEMPO_ESPERA = config["TIEMPO_ESPERA"]
PIN_RELE = config["PIN_RELE"]
PATH_SALIDA = ENDPOINT + "Salida.getTicket/" + ESTACION + "/" + USUARIO + "/"
barcode = ""
# RELAY = gpiozero.OutputDevice(PIN_RELE, active_high=True, initial_value=False)
# CAMERA = picamera.PiCamera()
def check_scan_barcode(event=None):
info_label.config(text = "Wait...")
barcode = barcode_entry.get()
barcode_entry.delete(0, "end")
for i in range(3):
response = get_request(ENDPOINT + barcode)
if response["data"] == "True":
success()
open_barrier()
else:
error()
info_label.config(text = "Scan barcode")
def get_request(url):
response = requests.get(url)
response.raise_for_status()
response = response.json()
return response
def normal():
window.configure(bg="white")
info_label.configure(bg="white")
def success():
window.configure(bg="green")
info_label.configure(bg="green")
window.after(1000, normal)
def error():
window.configure(bg="red")
info_label.configure(bg="red")
window.after(1000, normal)
def open_barrier(barcode):
# CAMERA.capture(f"/home/pi/Pictures{barcode}.jpg")
# RELAY.on()
# window.after(TIEMPO_ESPERA, RELAY.off)
pass
window = tk.Tk()
# window.attributes('-fullscreen', True)
info_label = tk.Label(window, text= "Scan barcode.", font=("Arial", 40))
info_label.pack()
barcode_entry = tk.Entry(window, width=50)
barcode_entry.bind('<Return>', check_scan_barcode)
barcode_entry.pack(expand=True)
barcode_entry.focus()
window.mainloop()

Python threaded tkinter GUI unresponsive unless sleep time is used.

I'm using python 2.7 and have built a UI using Tkinter. I'm using threads and queues to keep the UI responsive while the main script is working. The basic summary is the script reads a text file, parses out some information on it and puts that info in a dictionary and a list in the dictionary, then uses that info to send TCP modbus request (using pyModbus). It then writes the responses/results to a text file. The results also get printed a Text widget included in the UI. The updates to the Text widget is handled by the mainloop.
I'm still fairly new to threads and queues and I'm having trouble figuring out this issue.
The problem I'm running into is I need to include a ~10ms sleep after it loops through each item in the list for the UI to remain responsive. If I include the sleep time it works as expected, if not it freezes up until the threaded process is finished then updates the UI all at once (as it would if threads weren't used). The 10ms sleep can be slightly shorter. Any amount longer also works.
Here's the code that handles updating the log:
textQueue = Queue.Queue()
def TextDisplay(message, disTime="false", myColor="black", bgColor="white"):
textQueue.put([message, disTime, myColor, bgColor])
class LogUI:
def __init__(self, master):
self.master = master
'''other ui elements, not relevent'''
self.mainLogFrame = Frame(self.master)
self.mainLogFrame.pack(side="top", fill="both", expand="yes", padx=5, pady=2)
self.logText = Text(self.mainLogFrame, height=2)
self.logText.pack(side="left", fill="both", expand="yes", padx=5, pady=2)
self.ThreadSafeTextDisplay()
def ThreadSafeTextDisplay(self):
while not textQueue.empty():
tempText = textQueue.get(0)
message = tempText[0]
disTime = tempText[1]
myColor = tempText[2]
bgColor = tempText[3]
message = str(message) + "\n"
'''bunch of formating stuff'''
logUI.logText.insert(END, message)
print message
#NOTE: tried to include a sleep time here, no effect
self.logText.after(10, self.ThreadSafeTextDisplay)
Here's the non-threaded function that's called when the user clicks a button.
def ParseInputFile():
'''non-threaded function, called when user clicks button'''
inputList = []
inputFile = mainUI.fullInFileEntry.get()
with open(inputFile, 'r') as myInput:
'''open file and put contents in list'''
for line in myInput:
inputList.append(line.strip())
outFile = mainUI.outFileEntry.get().strip() + '.txt'
i = 1
tableBol = False
inputDict = {}
inputKeys = []
tableID = None
for item in inputList:
'''parses out inputKeys, inputDict using regular expressions'''
runInputGetQueue.put([inputKeys, inputDict, outFile, inputFile])
Here's the threaded function that receives the parsed information and handles the modbus request (note: i tried commenting out the actual modbus request, no effect):
def RunInputThread():
time.sleep(.1)
while 1:
while not runInputGetQueue.empty():
tempGet = runInputGetQueue.get(0)
inputKeys = tempGet[0]
inputDict = tempGet[1]
outFile = tempGet[2]
inputFile = tempGet[3]
outFile = open(outFile, 'w')
TextDisplay('< Start of %s input file > ' % inputFile, True, 'blue')
for key in inputKeys:
'''loops through the keys in the dictionary'''
TextDisplay(key) #just used as an example.
for lineIndex in range(len(inputDict[key]['tableLines'])):
'''lots of code that loops thorugh the lines of input file, frequently calls the TextDisplay() function'''
TextDisplay(inputDict[key][lineIndex]) #just used as an example.
time.sleep(0.01) #UI will become unresponseive if not included.
outFile.close()
time.sleep(0.001)
Found a way to get the UI mostly responsive. As stated in the comments above, the queue was receiving stuff to fast the function would be constantly working causing the UI to lock up. I made it so it will print at most 5 messages before taking a 1ms break and recalling the function which allows the UI to 'catch up.' The messages are printed almost as fast as they come in.
The UI will be slightly non-responsive if you move it or resize it. I have no issues interacting with other UI elements while this is running though.
You could also change the while loop to a if statement if you don't mind it being slow. The one process i ran went from 14 seconds with an if statement down to around 5 or 6 seconds using the code below. It would be same as if you changed the pullCount break point to 1 instead of 5.
def ThreadSafeTextDisplay(self):
pullCount = 0
while not textQueue.empty():
pullCount += 1
tempText = textQueue.get(0)
message = tempText[0]
disTime = tempText[1]
myColor = tempText[2]
bgColor = tempText[3]
message = str(message) + "\n"
'''bunch of formatting stuff'''
logUI.logText.insert(END, message)
print message
if pullCount >= 5: break #you can change the 5 value to whatever you want, the higher the number the faster stuff will print but the UI will start to become unresponsive.
self.logText.after(1, self.ThreadSafeTextDisplay)

Tkinter multiprocessing can't pickle

I'm trying to write a simple messenger app with Python and Tkinter. I'm listening to to a UDPSock to receive input. However as Tkinter's mainloop() blocks the thread, I cannot receive input with only one thread. I've tried to send a reference to the Tkinter GUI to the Process that loops and listens to input but I get a Can't pickle 'tkapp' object error. As the input is continuous and the Tkinter thread can't do anything else except run the mainloop to handle the GUI I cannot put the received input onto the GUI. Even if I use a Pipe the Tkinter thread is running the mainloop and so there is nowhere I can put a
while True:
data = pipe_conn.recv()
# do something with data
or anything.
Any help would be much appreciated!
My code:
-- imports --
class MessageApp():
def __init__(self, root, host, conn, port=13000):
# Setup GUI
self.send_sock = socket(AF_INET, SOCK_DGRAM)
p = Process(target=self.recv, args=(conn,))
def recv(self, conn):
buff = 1024
print('recvin')
while True:
(data, addr) = conn.recvfrom(buff)
self.add_msg(addr + ": " + data)
def start(self):
self.root.mainloop()
def add_msg(self, text):
self.msgs.config(state=NORMAL)
self.msgs.insert(INSERT, text + "\n")
self.msgs.config(state=DISABLED)
def send(self):
self.add_msg(self.inpt.get())
self.send_sock.sendto(self.inpt.get(), self.send_addr)
self.inpt.delete(0, 'end')
# ----------WHERE CAN I PUT THIS------------
def msg_listen(messenger, conn, port=13000):
buff = 1024
recv_sock = socket(AF_INET, SOCK_DGRAM)
recv_sock.bind(('', port))
while True:
(data, addr) = recv_sock.recvfrom(buff)
conn.send()
recv_sock.close()
# ------------------------------------------
def msgr_init(conn):
root = Tk()
messenger = MessageApp(root, ip, conn)
messenger.start()
if __name__ == "__main__":
root = Tk(
par_conn, chd_conn = Pipe(True)
msg_proc = Process(target=msgr_init, args=(chd_conn,))
msg_proc.start()
msg_listen(messenger, par_conn)
I discovered Tkinter.after() after reading the docs and it solved my problem.

Python multiple telnet sessions

I need to build a script to get the telnet output of as many hosts as possible and save them to a separate file for each host. The script should run as a daemon.
For the moment i have a function that encapsulates the logic for do it for a single host with telnetlib, but i do not how to proceed. I planned to open a process (multiprocessing.Process) for each host but i suspect it's going to be a resource waste and it must to exist a better way :)
def TelnetLogSaver(hostname,ip,filename):
# open files and telnet sessions
f = open(filename,"a")
tn = telnetlib.Telnet(ip,23,TIMEOUT)
# login
e = tn.read_until("Login: ")
tn.write(USER+"\n")
# and password
e = tn.read_until("Password: ")
tn.write(PASSWORD+"\n")
# Connected. Start infinite loop to save messages log
while True:
e = tn.read_until(PROMPT,TIMEOUT)
if e is not "":
f.write(datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"))
f.write(e)
f.flush()
# avoid session timeout
tn.write("\n")
e = tn.read_until(PROMPT
I believe the following should do what your require, I took your original code and made it into a type of thread:
import threading
import telnetlib
import datetime
import sys
# Global Variable Declarations
TIMEOUT = 30
USER = "Noel"
PROMPT = "Noel"
class listener(threading.Thread):
def __init__(self, filename, ip):
# Have to make a call to the super classes' __init__ method
super(listener, self).__init__()
self.f = open(filename,"a")
try:
self.tn = telnetlib.Telnet(ip, 23, TIMEOUT)
except:
print "Bad Connection"
sys.exit(0)
def run(self):
# login
e = self.tn.read_until("Login: ")
self.tn.write(USER+"\n")
# and password
e = self.tn.read_until("Password: ")
self.tn.write(PASSWORD+"\n")
while True:
e = self.tn.read_until(PROMPT, TIMEOUT)
if e is not "":
self.f.write(datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"))
self.f.write(e.strip())
self.f.flush()
# avoid session timeout
self.tn.write("\n")
if __name__ == "__main__":
# Things to listen to is a dictionary of hosts and files to output
# to, to add more things to listen to just add an extra entry into
# the things_to_listen_to in the format: host : outputfile
things_to_listen_to = {"localhost" :"localhost_output.txt"}
# Thread holder is going to hold all the threads we are going to start
thread_holder = []
for host, file in things_to_listen_to.iteritems():
thread_holder.append(listener(file, host))
for thread in thread_holder:
thread.run()
Hope this helps, if you have any problem update your question or leave a comment.

writing text area in python, implementing sockets

I am doing a simulation of a print server in python, files are sent using sockets to a server which has to be placed in a printer queue. When the client initiates communication with the server must provide an ID (user) and PASSWORD, which will be checked against a list on the server can be verified in a file "passwordlist.txt" which has the following format:
akira Aaron
alazrea Ababa
alexander Abbott
andy Abe
andycapp Abel
anxieties Abelian
anxiety Abelson
bailey Aberdeen
batman robin
bd Abidjan
Both programs must have a graphical interface on the display:
* Client: user field, password, file to send to print and disconnect from the server.
* Server: A list of files that are queued for printing
On the server should be displayed a list of files that have been properly sent to the queue for printing.
For this I decided to use a "text area" but I have a problem, only shows me the first file in the command to print text area, when the client terminates the connection and if another client tries to connect to the server just crash the program does and does absolutely nothing. What am I doing wrong? I think the problem is that i'm putting part of the instruction code "root.mainloop ()", i have this doubt. how can resolve this failure? im stuck With This..thanks to all
Here's the Client Code:
#! /python26/python.exe
#! -*- coding: utf-8 -*-
from Tkinter import *
import Tkinter, Tkconstants, tkFileDialog
import Tkinter
import sys
import socket
import tkMessageBox
flag = False
class Exit_Button(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
self.widget1()
def salir():
root.destroy()
def adjuntar_imprimir():
global flag
if (flag==False):
tkMessageBox.showinfo("Error", "You must login")
salir ()
else:
# get filename
filename = tkFileDialog.askopenfilename(**file_opt)
# open file on your own
if filename:
s.send (filename)
f= open(filename, 'rb')
l = f.read(1024)
while (l):
s.send(l)
l = f.read(512)
def iniciar_sesion():
global flag
#invoco el metodo connect del socket pasando como parametro la tupla IP , puerto
login = value.get()
password = value_2.get()
if ((len(login) == 0) or (len(password)) == 0):
tkMessageBox.showinfo("Error", "insert correct login and password")
salir ()
else:
s.send(login)
s.send(password)
recibido = s.recv(1024)
tkMessageBox.showinfo("Notify", recibido)
if (recibido=="Error Check user & Password"):
salir ()
else:
flag = True
####--------------------------------------------------------------------------------####
root = Tk()
s = socket.socket()
s.connect(("localhost", 9999))
# define options for opening or saving a file
file_opt = options = {}
options['defaultextension'] = '' # couldn't figure out how this works
options['filetypes'] = [('all files', '.*'), ('text files', '.txt')]
options['initialdir'] = 'C:\\'
options['initialfile'] = 'myfile.txt'
options['parent'] = root
options['title'] = 'This is a title'
frame = Frame(root)
frame.pack(side=LEFT)
frame.master.title("Servicio de impresion")
value = StringVar()
value_2 = StringVar()
w = Label(root, text="User Name", fg="red")
w.pack(side = LEFT)
entry_1 = Entry(root, textvariable=value_2, bd =5, show="*")
entry_1.pack(side = RIGHT)
z= Label(root, text="Password", fg="red")
z.pack(side = RIGHT)
entry_0 = Entry(root, textvariable=value, bd =5)
entry_0.pack(side = RIGHT)
##---------------login----------------
button_0= Button (frame, text = "login", command= iniciar_sesion, bg='black', foreground ="red")
button_0.pack()
##--------------Attach and print File------------
button_3 = Button (frame, text= "print", command=adjuntar_imprimir, bg='black',foreground ="red")
button_3.pack(side=LEFT)
##-----------------Exit------------------------
button_1 = Button(frame, text= "exit", command=salir, bg='black', foreground ="red")
button_1.pack()
root.mainloop()
The Server Code:
from Tkinter import *
import Tkinter, Tkconstants, tkFileDialog
import Tkinter
import sys
import socket
import tkMessageBox
def onclick():
pass
root = Tk()
root.title("Print Server")
text = Text(root, width=60, height=30)
text.pack()
s = socket.socket()
s.bind(("localhost", 9999))
s.listen(100)
i=0
while (True):
sc, address = s.accept()
print "Connection from: ", address
recibido1 = sc.recv(1024)
recibido2 = sc.recv(1024)
print "login:", recibido1, "password:", recibido2
salida = (str(recibido1)+" "+str(recibido2)+"\n")
archivo = open("passwordlist.txt", "r")
while True:
linea = archivo.readline() #Leo del archivo
if (salida==linea):
log_ok ="login ok"
sc.send(log_ok)
break
if (len(linea))==0:
error= "Error Check user & Password"
sc.send(error)
break
f = open('print_'+ str(i)+".pdf",'wb') #abierto en escritura binaria
i=i+1
# recibimos y escribimos en el fichero
nombre_archivo = sc.recv(1024)
cadena = "On Impresion Queue.."+nombre_archivo+"\n"
text.insert(INSERT, cadena)
print "On impresion Queue.."+nombre_archivo
l = sc.recv(1024)
while (l):
f.write(l)
l = sc.recv(1024)
if not l:
notification= "Complete transfer"
sc.send(notification)
break
f.close()
sc.close()
root.mainloop()
s.close()
The program does not tell me any error just when another client tries to login, the client interface is doing nothing.
I don't know why it crashes, but I know why only one client can connect. Your server just isn't designed to handle multiple clients. When one client connects, all the server does is listen to that client - any other client trying to connect is simply ignored.
The solution is simple: Multithreading. Spawn a new thread for each connecting client, and let the "main" thread accept new connections.
The resulting "while(True)" loop should look somewhat like this:
from threading import Thread
tkinterThread= Thread(target=Tk.mainloop, args=[root])#spawn a new Thread object
tkinterThread.start()#make the thread execute the tkinter mainloop
#please note: I'm not sure if the two lines above actually work; I can't test them because Tkinter won't work for me.
def listenToClient(sc, address):
recibido1 = sc.recv(1024)
recibido2 = sc.recv(1024)
print "login:", recibido1, "password:", recibido2
salida = (str(recibido1)+" "+str(recibido2)+"\n")
archivo = open("passwordlist.txt", "r")
while True:
linea = archivo.readline() #Leo del archivo
if (salida==linea):
log_ok ="login ok"
sc.send(log_ok)
break
if (len(linea))==0:
error= "Error Check user & Password"
sc.send(error)
break
f = open('print_'+ str(i)+".pdf",'wb') #abierto en escritura binaria
i=i+1
# recibimos y escribimos en el fichero
nombre_archivo = sc.recv(1024)
cadena = "On Impresion Queue.."+nombre_archivo+"\n"
text.insert(INSERT, cadena)
print "On impresion Queue.."+nombre_archivo
l = sc.recv(1024)
while (l):
f.write(l)
l = sc.recv(1024)
if not l:
notification= "Complete transfer"
sc.send(notification)
break
f.close()
sc.close()
while (True):
sc, address = s.accept()
print "Connection from: ", address
clientThread= Thread(target=listenToClient, args=[sc,address])#spawn a new thread object
clientThread.start()#start the thread; it'll execute the "listenToClient" function, passing it "sc" and "address" as arguments
This code will (well, should) spawn a thread that takes care of the GUI, meanwhile it'll accept connection requests from clients and spawn a new thread for every client, which checks the client's username and password, and then keeps listening until the client disconnects.

Categories