I have been running this script:
from threading import Thread import serial import time import collections import matplotlib.pyplot as plt import matplotlib.animation as animation import struct import copy from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk import tkinter as Tk from tkinter.ttk import Frame class serialPlot: def __init__(self, serialPort='/dev/ttyACM0', serialBaud=38400, plotLength=100, dataNumBytes=2, numPlots=4): self.port = serialPort self.baud = serialBaud self.plotMaxLength = plotLength self.dataNumBytes = dataNumBytes self.numPlots = numPlots self.rawData = bytearray(numPlots * dataNumBytes) self.dataType = None if dataNumBytes == 2: self.dataType = 'h' # 2 byte integer elif dataNumBytes == 4: self.dataType = 'f' # 4 byte float self.data = [] self.privateData = None for i in range(numPlots): # give an array for each type of data and store them in a list self.data.append(collections.deque([0] * plotLength, maxlen=plotLength)) self.isRun = True self.isReceiving = False self.thread = None self.plotTimer = 0 self.previousTimer = 0 # self.csvData = [] print('Trying to connect to: ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.') try: self.serialConnection = serial.Serial(serialPort, serialBaud, timeout=4) print('Connected to ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.') except: print("Failed to connect with " + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.') def readSerialStart(self): if self.thread == None: self.thread = Thread(target=self.backgroundThread) self.thread.start() # Block till we start receiving values while self.isReceiving != True: time.sleep(0.1) def getSerialData(self, frame, lines, lineValueText, lineLabel, timeText, pltNumber): if pltNumber == 0: # in order to make all the clocks show the same reading currentTimer = time.perf_counter() self.plotTimer = int((currentTimer - self.previousTimer) * 1000) # the first reading will be erroneous self.previousTimer = currentTimer self.privateData = copy.deepcopy(self.rawData) # so that the 3 values in our plots will be synchronized to the same sample time timeText.set_text('' + str(self.plotTimer) + '') data = self.privateData[(pltNumber*self.dataNumBytes):(self.dataNumBytes + pltNumber*self.dataNumBytes)] value, = struct.unpack(self.dataType, data) self.data[pltNumber].append(value) # we get the latest data point and append it to our array lines.set_data(range(self.plotMaxLength), self.data[pltNumber]) lineValueText.set_text('[' + lineLabel + '] = ' + str(value)) def backgroundThread(self): # retrieve data time.sleep(1.0) # give some buffer time for retrieving data self.serialConnection.reset_input_buffer() while (self.isRun): self.serialConnection.readinto(self.rawData) self.isReceiving = True #print(self.rawData) def sendSerialData(self, data): self.serialConnection.write(data.encode('utf-8')) def close(self): self.isRun = False self.thread.join() self.serialConnection.close() print('Disconnected...') # df = pd.DataFrame(self.csvData) # df.to_csv('/home/rikisenia/Desktop/data.csv') class Window(Frame): def __init__(self, figure, master, SerialReference): Frame.__init__(self, master) self.entry = [] self.setPoint = None self.master = master # a reference to the master window self.serialReference = SerialReference # keep a reference to our serial connection so that we can use it for bi-directional communicate from this class self.initWindow(figure) # initialize the window with our settings def initWindow(self, figure): self.master.title("Haptic Feedback Grasping Controller") canvas = FigureCanvasTkAgg(figure, master=self.master) toolbar = NavigationToolbar2Tk(canvas, self.master) canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1) # create out widgets in the master frame lbl1 = Tk.Label(self.master, text="Distance") lbl1.pack(padx=5, pady=5) self.entry = Tk.Entry(self.master) self.entry.insert(0, '0') # (index, string) self.entry.pack(padx=5) SendButton = Tk.Button(self.master, text='Send', command=self.sendFactorToMCU) SendButton.pack(padx=5) def sendFactorToMCU(self): self.serialReference.sendSerialData(self.entry.get() + '%') # '%' is our ending marker def main(): # portName = 'COM5' portName = '/dev/ttyACM0' baudRate = 38400 maxPlotLength = 100 # number of points in x-axis of real time plot dataNumBytes = 4 # number of bytes of 1 data point numPlots = 1 # number of plots in 1 graph s = serialPlot(portName, baudRate, maxPlotLength, dataNumBytes, numPlots) # initializes all required variables s.readSerialStart() # starts background thread # plotting starts below pltInterval = 50 # Period at which the plot animation updates [ms] xmin = 0 xmax = maxPlotLength ymin = -(1) ymax = 200 fig = plt.figure() ax = plt.axes(xlim=(xmin, xmax), ylim=(float(ymin - (ymax - ymin) / 10), float(ymax + (ymax - ymin) / 10))) ax.set_title('Strain Gauge/ToF') ax.set_xlabel("Time") ax.set_ylabel("Force/Distance") # put our plot onto Tkinter's GUI root = Tk.Tk() app = Window(fig, root, s) lineLabel = ['W', 'X', 'Y', 'Z'] style = ['y-', 'r-', 'c-', 'b-'] # linestyles for the different plots timeText = ax.text(0.70, 0.95, '', transform=ax.transAxes) lines = [] lineValueText = [] for i in range(numPlots): lines.append(ax.plot([], [], style[i], label=lineLabel[i])[0]) lineValueText.append(ax.text(0.70, 0.90-i*0.05, '', transform=ax.transAxes)) anim = animation.FuncAnimation(fig, s.getSerialData, fargs=(lines, lineValueText, lineLabel, timeText), interval=pltInterval) # fargs has to be a tuple plt.legend(loc="upper left") root.mainloop() # use this instead of plt.show() since we are encapsulating everything in Tkinter s.close() if __name__ == '__main__': main()
A window shows up with no data passing through it even though I have 4 sensors that have data coming from an Arduino. The windows contains 1 graph with 4 plots in it currently. I want 4 graphs each with one plot. I have been using https://thepoorengineer.com/en/python-gui/ as a reference to make graphs within python. The code for the data transfer is within the link as well. I tried to combine his 2 different codes and debugging it to make 4 graphs each with one plot to work with one Tkinter GUI window but it doesn't work. I also get an error of TypeError: getSerialData() missing 1 required positional argument: 'pltNumber' . Not sure why I get this error if pltNumber is in the parentheses in the code. I'm a beginner at python. What should I change to make the code work?
Script that can generate 4 separate graphs each with one plot that are not within a Tkinter GUI(works with 4 sensors but I need them within a Tkinter window):
from threading import Thread import serial import time import collections import matplotlib.pyplot as plt import matplotlib.animation as animation import struct import copy class serialPlot: def __init__(self, serialPort='/dev/ttyACM0', serialBaud=38400, plotLength=100, dataNumBytes=2, numPlots=1): self.port = serialPort self.baud = serialBaud self.plotMaxLength = plotLength self.dataNumBytes = dataNumBytes self.numPlots = numPlots self.rawData = bytearray(numPlots * dataNumBytes) self.dataType = None if dataNumBytes == 2: self.dataType = 'h' # 2 byte integer elif dataNumBytes == 4: self.dataType = 'f' # 4 byte float self.data = [] self.privateData = None # for storing a copy of the data so all plots are synchronized for i in range(numPlots): # give an array for each type of data and store them in a list self.data.append(collections.deque([0] * plotLength, maxlen=plotLength)) self.isRun = True self.isReceiving = False self.thread = None self.plotTimer = 0 self.previousTimer = 0 print('Trying to connect to: ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.') try: self.serialConnection = serial.Serial(serialPort, serialBaud, timeout=4) print('Connected to ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.') except: print("Failed to connect with " + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.') def readSerialStart(self): if self.thread == None: self.thread = Thread(target=self.backgroundThread) self.thread.start() # Block till we start receiving values while self.isReceiving != True: time.sleep(0.1) def getSerialData(self, frame, lines, lineValueText, lineLabel, timeText, pltNumber): if pltNumber == 0: # in order to make all the clocks show the same reading currentTimer = time.perf_counter() self.plotTimer = int((currentTimer - self.previousTimer) * 1000) # the first reading will be erroneous self.previousTimer = currentTimer self.privateData = copy.deepcopy(self.rawData) # so that the 3 values in our plots will be synchronized to the same sample time timeText.set_text('' + str(self.plotTimer) + '') data = self.privateData[(pltNumber*self.dataNumBytes):(self.dataNumBytes + pltNumber*self.dataNumBytes)] value, = struct.unpack(self.dataType, data) self.data[pltNumber].append(value) # we get the latest data point and append it to our array lines.set_data(range(self.plotMaxLength), self.data[pltNumber]) lineValueText.set_text('[' + lineLabel + '] = ' + str(value)) def backgroundThread(self): # retrieve data time.sleep(1.0) # give some buffer time for retrieving data self.serialConnection.reset_input_buffer() while (self.isRun): self.serialConnection.readinto(self.rawData) self.isReceiving = True def close(self): self.isRun = False self.thread.join() self.serialConnection.close() print('Disconnected...') def makeFigure(xLimit, yLimit, title): xmin, xmax = xLimit ymin, ymax = yLimit fig = plt.figure() ax = plt.axes(xlim=(xmin, xmax), ylim=(int(ymin - (ymax - ymin) / 10), int(ymax + (ymax - ymin) / 10))) ax.set_title(title) ax.set_xlabel("Time") ax.set_ylabel("Force/Distance") return fig, ax def main(): # portName = 'COM5' portName = '/dev/ttyACM0' baudRate = 38400 maxPlotLength = 100 # number of points in x-axis of real time plot dataNumBytes = 4 # number of bytes of 1 data point numPlots = 4 # number of plots in 1 graph s = serialPlot(portName, baudRate, maxPlotLength, dataNumBytes, numPlots) # initializes all required variables s.readSerialStart() # starts background thread # plotting starts below pltInterval = 50 # Period at which the plot animation updates [ms] lineLabelText = ['W', 'X', 'Y', 'Z'] title = ['Strain Gauge 1 Force', 'Strain Gauge 2 Force', 'ToF 1 Distance', 'ToF 2 Distance'] xLimit = [(0, maxPlotLength), (0, maxPlotLength), (0, maxPlotLength), (0, maxPlotLength)] yLimit = [(-1, 1), (-1, 1), (-1, 1), (-1, 1)] style = ['y-', 'r-', 'g-', 'b-'] # linestyles for the different plots anim = [] for i in range(numPlots): fig, ax = makeFigure(xLimit[i], yLimit[i], title[i]) lines = ax.plot([], [], style[i], label=lineLabelText[i])[0] timeText = ax.text(0.50, 0.95, '', transform=ax.transAxes) lineValueText = ax.text(0.50, 0.90, '', transform=ax.transAxes) anim.append(animation.FuncAnimation(fig, s.getSerialData, fargs=(lines, lineValueText, lineLabelText[i], timeText, i), interval=pltInterval)) # fargs has to be a tuple plt.legend(loc="upper left") plt.show() s.close() if __name__ == '__main__': main()
https://stackoverflow.com/questions/67390775/how-to-put-multiple-separate-graphs-into-one-tkinter-window May 05, 2021 at 03:11AM
没有评论:
发表评论