0

I've been working on a project that updates the label and plots it in a live graph that's coming from the Arduino. It outputs temperature and humidity. My arduino serial outputs looks like this:

29,50,44

I use this line of codes in separating the serial data in python:

ser = serial.Serial('COM3',9600)
pullData = ser.readline().decode('utf-8')
get_data = pullData.split(',')

I get to separate the values and update them in their respective labels. But what I don't get it to work is when I dynamically update the graph using those same serial values. I want to update them both at the same time. I get errors like

 28,90,43
Exception in thread Thread-3:
Traceback (most recent call last):
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\threading.py", line 916, in _bootstrap_inner
    self.run()
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "c:\Users\Deanne\OneDrive\Documents\Example#1\example#7.py", line 68, in animate
    humid = int(data_1[0])
ValueError: invalid literal for int() with base 10: '\x005\n'
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\threading.py", line 916, in _bootstrap_inner
    self.run()
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "c:\Users\Deanne\OneDrive\Documents\Example#1\example#7.py", line 69, in animate
    temp = int(data_1[1])
ValueError: invalid literal for int() with base 10: '\x00\x00\n'
Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1699, in __call__
    return self.func(*args)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 745, in callit
    func(*args)
  File "c:\Users\Deanne\OneDrive\Documents\Example#1\example#7.py", line 86, in GetSerialData
    data_array2 = int(get_data[2])
IndexError: list index out of range

This error just keeps on going, I know that maybe my code is not the most suitable way to approach this or to achieve what I want but this is what I've come up from doing researches. I tried this way of approach Trying to plot real time serial port data from Arduino in Python but to no avail since the question was not answered at all. I've also read many somewhat related problem but still no luck. I don't know how to grasp the topic of queue and threads. I wish someone could point out what I'm doing wrong. Any help will be appreciated.

Here's my full code:

from tkinter import *
import serial
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
import threading

my_window = Tk()
my_window.title("Graphical User Interface Demo#1")
my_window.geometry("720x720")

#For raising frames
def raise_frame(frame):
    frame.tkraise()

F1 = Frame(my_window, relief = RAISED)
F2 = Frame(my_window, relief = RAISED)
F3 = Frame(my_window, relief = RAISED)
F4 = Frame(my_window, relief = RAISED)

for frame in(F1, F2, F3, F4):
    frame.grid(row = 0, column = 0, sticky = "NSEW")

raise_frame(F1)


List_1 = []
List_2 = []
List_3 = []

#Initialization of Serial Comm
ser = serial.Serial('COM3', 9600)


style.use("ggplot")
a = Figure(figsize = (7,6))
plot_a = a.add_subplot(111)
plot_a.set_title('Temperature Graph')
plot_a.set_ylabel('Temperature')
plot_a.set_xlabel('Time')
plot_a.plot(List_1, 'c', marker = 'o',label = 'Degrees C')
plot_a.legend(loc= 'upper left')

b = Figure(figsize = (7,6))
plot_b = b.add_subplot(111)
plot_b.set_title('Humidity Graph')
plot_b.set_ylabel('Humidity')
plot_b.plot(List_2,'g', marker = 'o', label = 'Percentage %')
plot_c.legend(loc = 'upper right')

c = Figure(figsize = (7,6))
plot_c = c.add_subplot(111)
plot_c.set_title('Solved Water Graph')
plot_c.set_ylabel('Water Volume')
plot_c.plot(List_3,'b', marker = 'o', label = 'mL')
plot_c.legend(loc = 'upper right')


def animate_thread(i):
    threading.Thread(target=animate, args=(i,)).start()

def animate(i):
    pulldata = ser.readline().decode('ascii')
    data_1 = pulldata.split(',')
    humid = int(data_1[0])
    temp = int(data_1[1])
    solved_water = int(data_1[2])
    List_1.append(humid)
    List_2.append(temp)
    List_3.append(solved_water)
    plot_a.set_ylim(0,40)
    plot_a.plot(List_1, 'c', marker = 'o',label = 'Degrees C')
    plot_b.set_ylim(0,100)
    plot_b.plot(List_2, 'g', marker = 'o',label = 'Percentage %')
    plot_c.set_ylim(0,55)
    plot_c.plot(List_3, 'b', marker = 'o',label = 'mL')

def GetSerialData():
    pulldata = ser.readline().decode('ascii')
    get_data = pulldata.split(',')
    data_array = int(get_data[0])
    data_array1 = int(get_data[1])
    data_array2 = int(get_data[2])
    label_2data.config(text = str(data_array))
    label_3data.config(text = str(data_array1))
    label_4data.config(text = str(data_array2))
    print(pulldata)
    my_window.after(10000, GetSerialData)


#For Frame One
label_1 = Label(F1, text = "Homepage of GUI", relief = "solid", font = "Times 22 bold")
label_1.grid(row = 0, column = 3)
button_1 = Button(F1, text = "Page of Humidity", relief = GROOVE, bd = 8, command = lambda:raise_frame(F2))
button_1.grid(row = 1, column = 2)
button_2 = Button(F1, text = "Page of Temperature", relief = GROOVE, bd = 8, command = lambda:raise_frame(F3))
button_2.grid(row = 1, column = 3)
button_3 = Button(F1, text = "Page of Water", relief = GROOVE, bd = 8, command = lambda:raise_frame(F4))
button_3.grid(row = 1, column = 4)


#For Frame Two
label_2 = Label(F2, text = "Temperature", relief = "solid", font = "Times 22 bold")
label_2.grid(row = 0, column = 3)
button_1 = Button(F2, text = "Back To Homepage", command = lambda:raise_frame(F1))
button_1.grid(row = 1, column = 2)
label_2_1 = Label(F2, text = "Current Value: ", relief = "solid", font = "Verdana 10 bold")
label_2_1.grid(row = 2, column = 2)
label_2data = Label(F2, font = "Verdana 10")
label_2data.grid(row = 2, column = 3)
canvas1 = FigureCanvasTkAgg(a, F2)
canvas1.get_tk_widget().grid(row = 3, column = 3)
F2.canvas = canvas1


#For Frame Three
label_3 = Label(F3, text = "Humidity", relief = "solid", font = "Times 22 bold")
label_3.grid(row = 0, column = 3)
button_1 = Button(F3, text = "Back To Homepage", command = lambda:raise_frame(F1))
button_1.grid(row = 1, column = 2)
label_3_1 = Label(F3, text = "Current Value: ", relief = "solid", font = "Verdana 10 bold")
label_3_1.grid(row = 2, column = 2)
label_3data = Label(F3, font = "Verdana 10")
label_3data.grid(row = 2, column = 3)
canvas2 = FigureCanvasTkAgg(b, F3)
canvas2.get_tk_widget().grid(row = 3, column = 3)
F3.canvas = canvas2


#For Frame Four
label_4 = Label(F4, text = "Solved Water Value", relief = "solid", font = "Times 22 bold")
label_4.grid(row = 0, column = 3)
button_1 = Button(F4, text = "Back To Homepage", command = lambda:raise_frame(F1))
button_1.grid(row = 1, column = 2)
label_4_1 = Label(F4, text = "Current Value: ", relief = "solid", font = "Verdana 10 bold")
label_4_1.grid(row = 2, column = 2)
label_4data = Label(F4,font = "Verdana 10")
label_4data.grid(row = 2, column = 3)
canvas3 = FigureCanvasTkAgg(b, F4)
canvas3.get_tk_widget().grid(row = 3, column = 3)
F4.canvas = canvas3


aniA = animation.FuncAnimation(a, animate_thread, interval = 20000, blit = False)
aniB = animation.FuncAnimation(b, animate_thread, interval = 20000, blit = False)
aniC = animation.FuncAnimation(c, animate_thread, interval = 20000, blit = False)
GetSerialData()
my_window.mainloop()

Update: I've tried using @Novel's neater code. Here's the code:

#from tkinter import *
import tkinter as tk # proper way to import tkinter
import serial
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
style.use("ggplot")
import threading

class Dee(tk.Frame):
    def __init__(self, master=None, title='', ylabel='', label='', color='c', ylim=1, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)
        self.data = []
        fig = Figure(figsize = (7,6))
        self.plot = fig.add_subplot(111)
        self.plot.set_title(title)
        self.plot.set_ylabel(ylabel)
        self.plot.set_ylim(0, ylim)
        self.line, = self.plot.plot([], [], color, marker = 'o',label = label)
        self.plot.legend(loc='upper left')

        label = tk.Label(self, text = ylabel, font = "Times 22 bold")
        label.grid(row = 0, column = 3)
        button_1 = tk.Button(self, text = "Back To Homepage", command = F1.tkraise)
        button_1.grid(row = 1, column = 2)
        label_1 = tk.Label(self, text = "Current Value: ", font = "Verdana 10 bold")
        label_1.grid(row = 2, column = 2)
        self.label_data = tk.Label(self, font = "Verdana 10")
        self.label_data.grid(row = 2, column = 3)
        canvas = FigureCanvasTkAgg(fig, master=self)
        canvas.get_tk_widget().grid(row = 3, column = 3)

        ani = animation.FuncAnimation(fig, self.update_graph, interval = 1000, blit = False)
        canvas.draw()

    def update_graph(self, i):
        if self.data:
        self.line.set_data(range(len(self.data)), self.data)
        self.plot.set_xlim(0, len(self.data))

    def set(self, value):
        self.data.append(value)
        self.label_data.config(text=value)

my_window = tk.Tk()
my_window.title("Graphical User Interface Demo#1")
my_window.geometry("720x720")

F1 = tk.Frame(my_window)
F2 = Dee(my_window, title='Temperature Graph', ylabel='Temperature', color='c', label='Degrees C', ylim=40)
F3 = Dee(my_window, title='Humidity Graph', ylabel='Humidity', color='g', label='Percentage %', ylim=100)
F4 = Dee(my_window, title='Solved Water Graph', ylabel='Water Volume', color='b', label='mL', ylim=55)

#For Frame One
label_1 = tk.Label(F1, text = "Homepage of GUI", font = "Times 22 bold")
label_1.grid(row = 0, column = 3)
button_1 = tk.Button(F1, text = "Page of Humidity", bd = 8, command = F2.tkraise)
button_1.grid(row = 1, column = 2)
button_2 = tk.Button(F1, text = "Page of Temperature", bd = 8, command = F3.tkraise)
button_2.grid(row = 1, column = 3)
button_3 = tk.Button(F1, text = "Page of Water",  bd = 8, command = F4.tkraise)
button_3.grid(row = 1, column = 4)

for frame in(F1, F2, F3, F4):
    frame.grid(row = 0, column = 0, sticky = "NSEW")

F1.tkraise()

def get_data():
    #Initialization of Serial Comm
    ser = serial.Serial('COM3', 9600)
    while True:
        pulldata = ser.readline().decode('ascii')
        get_data = pulldata.split(',')
        F2.set(int(get_data[0]))
        F3.set(int(get_data[1]))
        F4.set(int(get_data[2]))
        print(pulldata)

# start the thread that will poll the arduino
t = threading.Thread(target=get_data)
t.daemon = True
t.start()

my_window.mainloop()

The graphs and labels are updating but the problem that now arises is that it's freezing. Also this gives me this error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 1699, in __call__
    return self.func(*args)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py", line 745, in callit
    func(*args)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\backends\_backend_tk.py", line 310, in idle_draw
    self.draw()
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 12, in draw
    super(FigureCanvasTkAgg, self).draw()
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\backends\backend_agg.py", line 433, in draw
    self.figure.draw(self.renderer)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\figure.py", line 1475, in draw
    renderer, self, artists, self.suppressComposite)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\image.py", line 141, in _draw_list_compositing_images
    a.draw(renderer)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\axes\_base.py", line 2607, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\image.py", line 141, in _draw_list_compositing_images
    a.draw(renderer)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\lines.py", line 738, in draw
    self.recache()
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\matplotlib\lines.py", line 661, in recache
    self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\numpy\lib\stride_tricks.py", line 249, in broadcast_arrays
    shape = _broadcast_shape(*args)
  File "C:\Users\Deanne\AppData\Local\Programs\Python\Python36\lib\site-packages\numpy\lib\stride_tricks.py", line 184, in _broadcast_shape
    b = np.broadcast(*args[:32])
ValueError: shape mismatch: objects cannot be broadcast to a single shape

I can tell that it's still working because it still outputs data. Any help again would be appreciated. Thank you.

10
  • The error means that not enough data was available. Python received a string like "29,50" or similar, but expected 3 values. Check your arduino code to make sure it ALWAYS sends triplets. Maybe also add a print("incoming data:", repr(pulldata)) to the method so you can see the incoming data and troubleshoot. Commented Aug 19, 2018 at 16:28
  • Also, each figure needs it's own animation function, and the function should update the plot using line.set_data, not make a new plot. Commented Aug 19, 2018 at 16:31
  • @Novel Hello! I am sure that my arduino code is always sending triplets. I just don't know why it's not receiving 3 values. I tried my code with only the updating label part, no updating of graphs. And it works flawlessly. But when I'll add my code for live plotting of data, it'll just do that error. Any more insights would be appreciated. Commented Aug 19, 2018 at 16:39
  • I notice the code you gave is not the same as the code in the error message. Are you sure you are running the right file? Commented Aug 19, 2018 at 16:43
  • BTW, I wasn't guessing about the error cause. That error means you don't have enough data in the string; that part is fact. The only thing to figure out now is why there's not enough data in the string. Are you sure the arduino code isn't missing a comma in the output? Or maybe sending a period instead of a comma? Show us the error with my debug print statement in there. Commented Aug 19, 2018 at 16:48

1 Answer 1

0

Here's a wild guess that shows how to make a single thread for polling the arduino and also how to make your code neater by using a class instead of copy / paste:

from tkinter import *
import tkinter as tk # proper way to import tkinter
import serial
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
style.use("ggplot")
import threading

class Dee(tk.Frame):
    def __init__(self, master=None, title='', ylabel='', label='', color='c', ylim=1, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)
        self.data = []
        fig = Figure(figsize = (7,6))
        self.plot = fig.add_subplot(111)
        self.plot.set_title(title)
        self.plot.set_ylabel(ylabel)
        self.plot.set_ylim(0, ylim)
        self.line, = self.plot.plot([], [], color, marker = 'o',label = label)
        self.plot.legend(loc='upper left')

        label = Label(self, text = ylabel, relief = "solid", font = "Times 22 bold")
        label.grid(row = 0, column = 3)
        button_1 = Button(self, text = "Back To Homepage", command = F1.tkraise)
        button_1.grid(row = 1, column = 2)
        label_1 = Label(self, text = "Current Value: ", relief = "solid", font = "Verdana 10 bold")
        label_1.grid(row = 2, column = 2)
        self.label_data = Label(self, font = "Verdana 10")
        self.label_data.grid(row = 2, column = 3)
        canvas = FigureCanvasTkAgg(fig, master=self)
        canvas.get_tk_widget().grid(row = 3, column = 3)

        ani = animation.FuncAnimation(fig, self.update_graph, interval = 1000, blit = False)
        canvas.draw()

    def update_graph(self, i):
        if self.data:
            self.line.set_data(range(len(self.data)), self.data)
            self.plot.set_xlim(0, len(self.data))

    def set(self, value):
        self.data.append(value)
        self.label_data.config(text=value)

my_window = Tk()
my_window.title("Graphical User Interface Demo#1")
my_window.geometry("720x720")

F1 = Frame(my_window, relief = RAISED)
F2 = Dee(my_window, title='Temperature Graph', ylabel='Temperature', color='c', label='Degrees C', ylim=40, relief = RAISED)
F3 = Dee(my_window, title='Humidity Graph', ylabel='Humidity', color='g', label='Percentage %', ylim=100, relief = RAISED)
F4 = Dee(my_window, title='Solved Water Graph', ylabel='Water Volume', color='b', label='mL', ylim=55, relief = RAISED)

#For Frame One
label_1 = Label(F1, text = "Homepage of GUI", relief = "solid", font = "Times 22 bold")
label_1.grid(row = 0, column = 3)
button_1 = Button(F1, text = "Page of Humidity", relief = GROOVE, bd = 8, command = F2.tkraise)
button_1.grid(row = 1, column = 2)
button_2 = Button(F1, text = "Page of Temperature", relief = GROOVE, bd = 8, command = F3.tkraise)
button_2.grid(row = 1, column = 3)
button_3 = Button(F1, text = "Page of Water", relief = GROOVE, bd = 8, command = F4.tkraise)
button_3.grid(row = 1, column = 4)

for frame in(F1, F2, F3, F4):
    frame.grid(row = 0, column = 0, sticky = "NSEW")

F1.tkraise()

def get_data():
    #Initialization of Serial Comm
    ser = serial.Serial('COM3', 9600)
    while True:
        pulldata = ser.readline().decode('ascii')
        get_data = pulldata.split(',')
        F2.set(int(get_data[0]))
        F3.set(int(get_data[1]))
        F4.set(int(get_data[3]))

# start the thread that will poll the arduino
t = threading.Thread(target=get_data)
t.daemon = True
t.start()

my_window.mainloop()
Sign up to request clarification or add additional context in comments.

9 Comments

Hello there,thank you for your answer. So I tried using this simplified code. It's kinda working but my GUI is freezing. When I tried to click on different frames, it freezes before it even shows the graph and labels. And also I get this new errors in my debug output b = np.broadcast(*args[:32]) ValueError: shape mismatch: objects cannot be broadcast to a single shape.
I can't help you without seeing your code and full traceback. That error sounds like you changed set_data to set_ydata. If your GUI is freezing it sounds like you moved the readline out of the child thread.
Hello again, I've updated my question above. I've included the code that I used and the full traceback error. I just changed something in the code like including tk on the widgets. Nonetheless I've changed nothing nor moved the readline. I hope this helps.
@dee hmm no idea. Usually that error means that the X and Y data arrays were not the same length. I tried the code with a dummy arduino and it works fine for me.
Hello. I've changed the interval = 1000 in the FuncAnimation to interval = 10000. I noticed that my delay in the arduino code is 10000 ms. Maybe that's the reason my gui is freezing. It's not freezing anymore and it's working fine now. It's just that I got a few seconds delay for the graph to update when the labels update. But overall it's working the way I wanted it to be. Thank you for your help. Kudos
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.