python TKinter/matplotlib图形无法更新/运行

sbdsn5lh  于 2024-01-05  发布在  Python
关注(0)|答案(1)|浏览(90)

我正试图显示我从串行端口得到的数据(由Arduino发送)在一个图表.我已经尝试了不同的东西吨,但它似乎并不像预期的那样工作.我试图有x轴的时间,因为程序开始和y轴的数据,我从串行端口是从Arduino发送.我已经尝试调试,但没有错误出现.代码似乎在update_graphs()函数的某个地方停止了,但我不知道是什么原因导致它停止。我很迷茫,不知道现在该怎么做

import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import serial
import threading
import queue
import time

# Function to read data from the serial port
def read_serial_data(serial_port, data_queue):
    while True:
        try:
            line = serial_port.readline().decode().strip()
            data_queue.put(line)
        except UnicodeDecodeError:
            pass

# Function to update the graphs
def update_graphs():
    start_time = time.time()
    global x_data
    while True:
        try:
            data = data_queue.get_nowait()

            # Calculate the time since the program started
            current_time = time.time() - start_time

            # Update the graphs with the new data and time
            x_data.append(current_time)

            # Attempt to add data to y_data as a float
            try:
                y_data.append(float(data))
            except ValueError:
                print(f"Error converting data to float: {data}")
                
            x_data = x_data[-len(y_data):] #limits data in the list

            ax1.clear()
            ax1.plot(x_data, y_data, label='Sensor Data 1')
            ax1.set_xlabel('Time (seconds)')
            ax1.set_ylabel('Sensor Data 1')
            ax1.legend()

            canvas.draw()

        except queue.Empty:
            pass
        root.after(100, update_graphs)

# GUI setup
root = tk.Tk()
root.title("Sensor Data Graphs")

# Serial port setup
ser = serial.Serial("COM5", baudrate=9600)

# Data queue for inter-thread communication
data_queue = queue.Queue()

# Create and start a thread to read data from the serial port
serial_thread = threading.Thread(target=read_serial_data, args=(ser, data_queue), daemon=True)
serial_thread.start()

# Create and start a thread to update the graphs
update_thread = threading.Thread(target=update_graphs, daemon=True)
update_thread.start()

# Create multiple graphs
fig, ax1 = plt.subplots()
x_data, y_data = [], []  # Lists to store data and corresponding time

# Create Tkinter canvas for embedding Matplotlib figure
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

# Start the Tkinter main loop
root.mainloop()

字符串
Arduino代码

void setup() {
  
    Serial.begin(9600);

}

void loop() {
  Serial.println(random(0, 50));
  delay(200);
}


我试着放慢Arduino代码的速度,认为它发送数据的速度太快,Python脚本无法读取,但它不起作用

  • 更新 *
    相当肯定,这与时间有关,因为当我在vscode中使用调试并逐行运行时,一切都正常,但当我让它自己运行时,它只是冻结,不会做任何事情

Arduino代码保持不变
更新代码:

import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import serial
import threading
import queue
import time

# Function to read data from the serial port
def read_serial_data(serial_port, data_queue):
    while True:
        try:
            line = serial_port.readline().decode().strip()
            data_queue.put(line)
        except UnicodeDecodeError:
            pass

# Function to update the graphs
def update_graphs(start_time):
    while True:
        try:
            data = data_queue.get(timeout=1)

            # Calculate the time since the program started
            current_time = time.time() - start_time

            # Update the graphs with the new data and time
            x_data.append(current_time)
            y_data.append(float(data))

            ax1.clear()
            ax1.plot(x_data, y_data, label='Sensor Data 1')
            ax1.set_xlabel('Time (seconds)')
            ax1.set_ylabel('Sensor Data 1')
            ax1.legend()

            canvas.draw()

        except queue.Empty:
            pass
        except Exception as e:
            print(f"Error in update_graphs: {e}")

# GUI setup
root = tk.Tk()
root.title("Sensor Data Graphs")

# Serial port setup
ser = serial.Serial("COM5", baudrate=9600)

# Data queue for inter-thread communication
data_queue = queue.Queue()

# Create and start a thread to read data from the serial port
serial_thread = threading.Thread(target=read_serial_data, args=(ser, data_queue), daemon=True)
serial_thread.start()

# Create multiple graphs
fig, ax1 = plt.subplots()
x_data, y_data = [], []  # Lists to store data and corresponding time

# Create Tkinter canvas for embedding Matplotlib figure
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

# Get the start time
start_time = time.time()

# Schedule the update_graphs function to run every 100 milliseconds
root.after(200, update_graphs, start_time)

# Start the Tkinter main loop
root.mainloop()

  • 更新 *
    图表似乎只在我使用vscode中的调试功能暂停和取消暂停程序时更新
pjngdqdw

pjngdqdw1#

当数据发生变化时,可以使用matplotlib.animation.FuncAnimation来更新matplotlib图形。这个答案提供了一个基于这种方法的演示(基于问题的思想)。

最终结果

下面的gif显示了演示程序的输出。“


的数据

代码

import threading, time, random
import matplotlib.pyplot as plt
import queue
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.animation import FuncAnimation

# Initialising objects to handle data
dataQueue = queue.Queue()
sensor_data = []
time_data = []

# This function mimics data generated from an arduino for this demo program
# Replace this with actual arduino-data-reading function
# This function is generating some random data every 0.2 seconds
def arduino():
    while(True):
        dataQueue.put(random.randint(1,10))
        time.sleep(0.2)

# Initialising the arduino function in separate thread, so, it generates data
# independently and in parallel to our plotting logic
arduinoThread = threading.Thread(target = arduino, daemon= True)
arduinoThread.start()

# Initialising the matplotlib figure and axis objects
fig, ax = plt.subplots()

# This function reads data from data-queue, adds time-data and updates the 
# graphs according to the new data. If there is no data, exception occurs
# and the function skips the update.
def updateGraph(frame_number):
    try:
        sensor_data.append(dataQueue.get(timeout=1))
        time_data.append(time.time())
        ax.clear()
        ax.plot(time_data, sensor_data, color = 'orange')
        ax.set_ylabel('Sensor Data')
        ax.set_xlabel('Time')
    except queue.Empty:
        pass

# Creating the tkinter root window
root = tk.Tk()
root.title('Sensor Data')

# Setting a text label (optional) in tkinter window
tk.Label(master = root, text = 'Sensor 1 Data', font = ('Courier', 15, 'bold')).pack(side = 'top', pady = 10)

# Create Tkinter canvas for embedding Matplotlib figure
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

# Initialising the matplotlib FuncAnimation which calls the updateGraph function every 500 milli-second
anim = FuncAnimation(fig, updateGraph, interval = 500)  # interval between each update (or frame) in milli-seconds

# Launching the tkinter window
root.mainloop()

字符串
解释说明
我已经将理解代码所需的大部分辅助信息放在代码的注解中。基本上,我们已经为matplotlib图形创建了一个matplotlib.animation.FuncAnimation对象,即fig,它在interval参数中指定的时间后定期调用updateGraph函数。
updateGraph尝试从dataQueue读取数据。如果找到数据,它将从dataQueue读取一个数据值,并将其添加到sensor_data,然后将当前时间添加到time_data,最后更新数字(fig)。如果dataQueue为空,则会发生异常,它将跳过更新迭代的数据和图形。

  • 注意事项:由于本演示中没有Arduino,因此我创建了一个名为arduino的函数,该函数每0.2秒生成一些随机数据,并通过arduinoThread与绘图程序并行运行,以模拟真实的Arduino生成的传感器数据的行为。*

相关问题