0

Question: How can I read data from the serial port without blocking operations occuring in the rest of my GUI.

Current Problem: I have a GUI that can't perform all of the tasks in the time that I need it to. I need to read data -> store the incoming data (in a file, list, doesnt matter) -> display a few of the most recent data points ~50 for 4 different variables -> check for any changes in the GUI all in under .05 seconds.

Approaches I've tried:(I may have done these incorrectly and maybe that's why it didn't work)

Using timers for different functions: Was still too slow

Reading in blocks of data: was still too slow

Grpahing every other point: Still too slow

Threading to read buffer: Read incomplete (not sure if I did this correctly but seemed like a good approach would love to see an example of how it could be done to readlines)

Multiprocessing: couldn't get to work but seemed like the best approach

Currently trying to read in packed binary data to decrease read time.

What I am receiving a line containing integer values that is twelve variables long

int1.int2.int3|int1,int2,int3|int1,int2,int3|int1,int2,int3

I am splitting this string at the "|" and graphing one integer variable from each split and storing all the variables in separate lists

I am receiving this data via usb from an arduino that is outputing a string in that format every 50ms or at 20Hz.

The major issue that I have run into is that it takes too long to read the serial port and when it backs up it causes significant lag and I need it to be real time.

Here is the GUI I am using:

import Tkinter
import serial
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from collections import deque
import random
import time

class App:
    def __init__(self, master):
        self.arduinoData = serial.Serial('com5', 250000, timeout=None)

        frame = Tkinter.Frame(master)

        self.running = False
        self.ani = None

        self.run = Tkinter.LabelFrame(frame, text="Testing", borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10)
        self.run.grid(row=0, column=0, padx=20, pady=20)

        self.run_respiration = Tkinter.Button(self.run, text="RUN",bd=10, height=5, width=10, command=self.getData)
        self.run_respiration.grid(row=0, column=0, padx=5, pady=5)

        self.test_options = Tkinter.LabelFrame(frame, text="Test Options", borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10 )
        self.test_options.grid(row=0, column=1, padx=20, pady=20)

        self.stop = Tkinter.Button(self.test_options, text="STOP", bd=10, height=5, width=10, command=self.stopTest)
        self.stop.grid(row=0, column=0, padx=5, pady=5)


        self.fig = plt.Figure()
        self.ax1 = self.fig.add_subplot(111)
        self.line0, = self.ax1.plot([], [], lw=2)
        self.line1, = self.ax1.plot([], [], lw=2)
        self.line2, = self.ax1.plot([], [], lw=2)
        self.line3, = self.ax1.plot([], [], lw=2)
        self.canvas = FigureCanvasTkAgg(self.fig,master=master)
        self.canvas.show()
        self.canvas.get_tk_widget().grid(row=0, column=4, padx=20, pady=20)
        frame.grid(row=0, column=0, padx=20, pady=20)

    def getData(self):
        if self.ani is None:
            self.k = 0
            self.arduinoData.flushInput()
            self.arduinoData.write("<L>")
            return self.start()
        else:
            self.arduinoData.write("<L>")
            self.arduinoData.flushInput()
            self.ani.event_source.start()
        self.running = not self.running

    def stopTest(self):
        self.arduinoData.write("<H>")
        if self.running:
            self.ani.event_source.stop()
        self.running = not self.running

    def start(self):
        self.xdata = []
        self.pressure1 = []
        self.displacement1 = []
        self.cycle1 = []
        self.pressure2 = []
        self.displacement2 = []
        self.cycle2 = []
        self.pressure3 = []
        self.displacement3 = []
        self.cycle3 = []
        self.pressure4 = []
        self.displacement4 = []
        self.cycle4 = []
        self.k = 0
        self.limit = 300
        self.arduinoData.flushInput()
        self.timer()
        self.ani = animation.FuncAnimation(
            self.fig,
            self.setData,
            interval=10,
            repeat=True)
        self.arduinoData.write("<L>")
        self.running = True
        self.ani._start()


    def readData(self):
        if (self.arduinoData.inWaiting()>0):
            self.xdata.append(self.k)
            x = self.arduinoData.readline()
            strip_data = x.strip()
            split_data = x.split("|")
            actuator1 = split_data[0].split(".")
            actuator2 = split_data[1].split(".")
            actuator3 = split_data[2].split(".")
            actuator4 = split_data[3].split(".")
            self.pressure1.append(int(actuator1[0]))
            self.displacement1.append(int(actuator1[1]))
            self.cycle1 = int(actuator1[2])
            self.pressure2.append(int(actuator2[0]))
            self.displacement2.append(int(actuator2[1]))
            self.cycle2 = int(actuator2[2])
            self.pressure3.append(int(actuator3[0]))
            self.displacement3.append(int(actuator3[1]))
            self.cycle3 = int(actuator3[2])
            self.pressure4.append(int(actuator4[0]))
            self.displacement4.append(int(actuator4[1]))
            self.cycle4 = int(actuator4[2])
            if self.k > 0:
                self.line0.set_data(self.xdata, self.pressure1)
                self.line1.set_data(self.xdata, self.pressure2)
                self.line2.set_data(self.xdata, self.pressure3)
                self.line3.set_data(self.xdata, self.pressure4)
                if self.k < 49:
                    self.ax1.set_ylim(min(self.pressure1)-1, max(self.pressure4) + 1)
                    self.ax1.set_xlim(0, self.k+1)
                elif self.k >= 49:
                    self.ax1.set_ylim(min(self.pressure1[self.k-49:self.k])-1, max(self.pressure4[self.k-49:self.k]) + 1)
                    self.ax1.set_xlim(self.xdata[self.k-49], self.xdata[self.k-1])
                if self.cycle1 >= self.limit:
                    self.running = False
                    self.ani = None
                    self.ani.event_source.stop()
                    self.running = not self.running
            self.k += 1


    def setData(self, i):
            return self.line0, self.line1, self.line2, self.line3,



    def timer(self):
        self.readData()
        root.after(1, self.timer)

root = Tkinter.Tk()
app = App(root)
root.mainloop()

So basically I need it so that the readData() function is able to operate without blocking the rest of the GUI and have the data accesible to the gui or to be able to keep up with the incoming data while graphing it.

Any approaches for feasibility or general guidance will be greatly appreciated and thanks in advance for any help.

emg184
  • 608
  • 5
  • 16
  • How about launching a thread to do it asynchronously and put the data to a buffer, your GUI will be notified when the buffer is full. – minhhn2910 Jul 06 '17 at 14:08
  • I am not sure how to do that if you could provide an example that would be great, but I'll try to read up on it and see what i can do – emg184 Jul 06 '17 at 14:22
  • read more here: https://stackoverflow.com/questions/1239035/asynchronous-method-call-in-python – minhhn2910 Jul 06 '17 at 14:32

0 Answers0