Reputation: 11
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.
Upvotes: 0
Views: 1014
Reputation: 13729
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()
Upvotes: 0