0

I have this program that grabs data from serial and displays them on a tkinter frame.

This is the code:

import tkinter as tk
import tkinter.ttk as ttk
import serial.tools.list_ports
from tkinter import scrolledtext 
#new stuff from vid
import time
import serial
import threading
import continuous_threading


#to be used on our canvas
HEIGHT = 700
WIDTH = 800

#hardcoded baud rate
baudRate = 9600



ser = serial.Serial('COM16', baudRate)
val1 = 0
index = []
def readSerial():
    global val1
    ser_bytes = ser.readline()
    ser_bytes = ser_bytes.decode("utf-8")
    val1 = ser_bytes
    scrollbar.insert("end", val1)
    
t1 = continuous_threading.PeriodicThread(0.1, readSerial)
#----------------------------------------------------------------------

# --- functions ---

#the following two functtions are for the seria port selection, on frame 1
def serial_ports():    
    return serial.tools.list_ports.comports()

def on_select(event=None):

    global COMPort
    COMPort = cb.get()
    print(COMPort)



# --- functions ---


#--------------------------------------------------------------------------------------------------------------
# --- main ---
root = tk.Tk() #here we create our tkinter window
root.title("Sensor Interface")

#we use canvas as a placeholder, to get our initial screen size (we have defined HEIGHT and WIDTH)
canvas = tk.Canvas(root, height=HEIGHT, width=WIDTH)
canvas.pack()

#we use frames to organize all the widgets in the screen
# --- frame 1 ---
frame1 = tk.Frame(root)
frame1.place(relx=0, rely=0.05, relheight=0.03, relwidth=1, anchor='nw') #we use relheight and relwidth to fill whatever the parent is - in this case- root

label0 = tk.Label(frame1, text="Select the COM port that the device is plugged in: ")
label0.config(font=("TkDefaultFont", 8))
label0.place(relx = 0.1, rely=0.3, relwidth=0.3, relheight=0.5)


cb = ttk.Combobox(frame1, values=serial_ports())
cb.place(relx=0.5, rely=0.5, anchor='center')
# assign function to cmbobox
cb.bind('<<ComboboxSelected>>', on_select)
# --- frame 1 ---




# --- frame 2 ---
frame2 = tk.Frame(root, bg='#80c1ff') #remove color later
frame2.place(relx=0, rely=0.1, relheight=1, relwidth=1, anchor='nw')

# make a scrollbar
scrollbar = scrolledtext.ScrolledText(frame2)
scrollbar.place(relx=0, rely=0, relheight=1, relwidth=1, anchor='nw')
# --- frame 2 ---
#--------------------------------------------------------------------------------------------------------
t1.start() 

root.mainloop() #here we run our app

When i terminate the GUI that spawns, i get this exception on the terminal:

Exception in thread Thread-1:
Fatal Python error: could not acquire lock for <_io.BufferedWriter name='<stderr
>'> at interpreter shutdown, possibly due to daemon threads
Python runtime state: finalizing (tstate=005C8F38)

Thread 0x00001a30 (most recent call first):
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 1202 in invoke_excepthook
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 934 in _bootstrap_inner
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 890 in _bootstrap

Current thread 0x00001918 (most recent call first):
<no Python frame>

And when i terminate it from the terminal with control+C i get:

Traceback (most recent call last):
  File "mySerial.py", line 110, in <module>
    root.mainloop() #here we run our app
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__i
nit__.py", line 1420, in mainloop
    self.tk.mainloop(n)
KeyboardInterrupt
Exception in thread Thread-1:
Fatal Python error: could not acquire lock for <_io.BufferedWriter name='<stderr
>'> at interpreter shutdown, possibly due to daemon threads
Python runtime state: finalizing (tstate=00578F38)

Thread 0x00001ad0 (most recent call first):
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 1202 in invoke_excepthook
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 934 in _bootstrap_inner
  File "C:\Users\User1\AppData\Local\Programs\Python\Python38-32\lib\threading.p
y", line 890 in _bootstrap

Current thread 0x000013d8 (most recent call first):
<no Python frame>

Why is this hapenning?

EDIT: This is my code and error message, trying AST's advice:

import tkinter as tk
import tkinter.ttk as ttk
import serial.tools.list_ports
from tkinter import scrolledtext 
import time
import serial
import threading
import continuous_threading


#to be used on our canvas
HEIGHT = 700
WIDTH = 800

#hardcoded baud rate
baudRate = 9600

# flag to be notified when application is terminated
stop=False


ser = serial.Serial('COM16', baudRate)
val1 = 0

def readSerial():
    global val1, stop
    if not stop:
       ser_bytes = ser.readline()
       ser_bytes = ser_bytes.decode("utf-8")
       val1 = ser_bytes
       scrollbar.insert("end", val1)
    else:
        return

t1 = continuous_threading.PeriodicThread(0.1, readSerial)
#----------------------------------------------------------------------

# --- functions ---

#the following two functions are for the seria port selection, on frame 1
def serial_ports():    
    return serial.tools.list_ports.comports()

def on_select(event=None):

    global COMPort
    COMPort = cb.get()
    print(COMPort)

def on_close():
    global stop
    stop=True
    root.destroy()



# --- functions ---


#--------------------------------------------------------------------------------
# --- main ---
root = tk.Tk() #here we create our tkinter window
root.title("Sensor Interface")
root.protocol('WM_DELETE_WINDOW', on_close)

#we use canvas as a placeholder, to get our initial screen size (we have defined HEIGHT and WIDTH)
canvas = tk.Canvas(root, height=HEIGHT, width=WIDTH)
canvas.pack()

#we use frames to organize all the widgets in the screen
# --- frame 1 ---
frame1 = tk.Frame(root)
frame1.place(relx=0, rely=0.05, relheight=0.03, relwidth=1, anchor='nw') #we use relheight and relwidth to fill whatever the parent is - in this case- root

label0 = tk.Label(frame1, text="Select the COM port that the device is plugged in: ")
label0.config(font=("TkDefaultFont", 8))
label0.place(relx = 0.1, rely=0.3, relwidth=0.3, relheight=0.5)


cb = ttk.Combobox(frame1, values=serial_ports())
cb.place(relx=0.5, rely=0.5, anchor='center')
# assign function to cmbobox
cb.bind('<<ComboboxSelected>>', on_select)
# --- frame 1 ---




# --- frame 2 ---
frame2 = tk.Frame(root, bg='#80c1ff') #remove color later
frame2.place(relx=0, rely=0.1, relheight=1, relwidth=1, anchor='nw')

# make a scrollbar
scrollbar = scrolledtext.ScrolledText(frame2)
scrollbar.place(relx=0, rely=0, relheight=1, relwidth=1, anchor='nw')
# --- frame 2 ---
#--------------------------------------------------------------------------------
t1.start() 
root.mainloop() #here we run our app

The error i get, is the first error i have listed as code.

EDIT 2: When i do the second approach of AST:

If i close the GUI (pressing X), it closes without any errors, but the prompt is stuck on the terminal - i cannot type anything, not even keyboard exit (Control+C) works.

If the program runs and i exit with the keyboard exit in the terminal (Control+C), i get the keyboard interrupt error (the second one i have listed as code)

user1584421
  • 3,499
  • 11
  • 46
  • 86
  • The periodic thread keeps running even after the GUI is destroyed and produces the error since it doesn't exist anymore. You can try using a flag to avoid this. – astqx Feb 27 '21 at 02:28
  • Hmm thank you, but i am not sure i understand how a flag can be used here. – user1584421 Feb 27 '21 at 11:12

1 Answers1

3

According to me, this occurs because there could be a scheduled thread that gets executed after the application has been destroyed and hence fails to find the GUI element that it has to update.

I haven't tried running your code, but I think this might help

Associate the window deletion with a function that updates the flag (stop)

def on_close():
    global stop
    stop=True
    root.destroy()

root.protocol('WM_DELETE_WINDOW', on_close)
stop=False

And modify the readSerial function to check for the same

def readSerial():
    global val1,stop
    if not stop:
        ser_bytes = ser.readline()
        ser_bytes = ser_bytes.decode("utf-8")
        val1 = ser_bytes
        scrollbar.insert("end", val1)
    else:
        return

EDIT

This is the function in threading.py that raises the exception.

def _bootstrap(self):
    # Wrapper around the real bootstrap code that ignores
    # exceptions during interpreter cleanup.  Those typically
    # happen when a daemon thread wakes up at an unfortunate
    # moment, finds the world around it destroyed, and raises some
    # random exception *** while trying to report the exception in
    # _bootstrap_inner() below ***.  Those random exceptions
    # don't help anybody, and they confuse users, so we suppress
    # them.  We suppress them only when it appears that the world
    # indeed has already been destroyed, so that exceptions in
    # _bootstrap_inner() during normal business hours are properly
    # reported.  Also, we only suppress them for daemonic threads;
    # if a non-daemonic encounters this, something else is wrong.
    try:
        self._bootstrap_inner()
    except:
        if self._daemonic and _sys is None:
            return
        raise

As in this case it's okay to kill the thread when all the non-daemonic threads have been killed, you can try setting the thread as daemonic so that the exception is automatically handled. I don't think the flag would be required with this approach.

t1.daemon=True
t1.start()
astqx
  • 2,058
  • 1
  • 10
  • 21
  • Thank you. Tried the code believing it would work, but problem remains. edited the question with the full code (with your suggestions) and error message. – user1584421 Feb 27 '21 at 18:42
  • @user1584421 I'm not exactly able to reproduce the error at the moment, but I have updated the answer with another possible approach, let me know if that works. Also, could you try to reduce your code to a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) so that it's easier to work with. – astqx Feb 27 '21 at 20:10
  • Sorry for the escessive code. It still does not work. When i close the GUI (pressing X), it closes without any errors, but the prompt is stuck on the terminal - i cannot type anything, not even keyboard exit (Control+C) works. When the program runs and i exit with the keyboard exit in the terminal (Control+C), i get *Doesnt fit in a comment so please see EDIT2 in the question* – user1584421 Feb 27 '21 at 21:48
  • @user1584421 All the errors so far are exactly the same, please don't populate the question over and over again, strip it down to include it once, it will be easier for others to read. And I did notice that issue now, but I am just not able to get the error that proceeds. I'm just curious is there a specific need of using threading? Have you taken a look at the [`after`](https://stackoverflow.com/a/44085555/14094985) method? – astqx Feb 27 '21 at 22:15
  • i am so sorry... i didn't notice it was exactly the same... I will clean up my question. All my app does is take data from the serial (incoming from an arduino) and display them to a tkinter scrollText frame. Essentially, it replaces the arduino IDE, since i only want to receive serial. Since i am a newbie in Tkinter (and in python pretty much), i saw some youtube tutorials on how this is done. I had made it but it didn't refresh and the tutorials mentioned that i have to create threads, because serial is blocking. This is why i did it. Do you know any other way to do this? – user1584421 Feb 27 '21 at 22:20
  • That's correct, you can try to loop the function periodically using the `tkinter`'s `after` method, refer to the link in the previous comment, that should be a bit easier to work with. – astqx Feb 27 '21 at 22:37
  • Unfortunately, using after() didn't work as well. You can read about it here: https://stackoverflow.com/questions/66413081/tkinter-make-function-run-periodically-with-after/66413157#66413157 . What is going on? This was supposed to be a simple thing. Just display serial data to a frame. i don't understand why nothing works. Actually my first approach with the threads works fine, but i don't understand why it does not work when i exit the application. If i could handle it correctly, then everything would be fine. – user1584421 Feb 28 '21 at 19:51
  • @user1584421 In that answer, call the `after` at the end of the function as opposed to the starting, I believe that should work. – astqx Mar 01 '21 at 05:25