TKinter/matplotlib 图表不会更新/运行

问题描述 投票:0回答:1

我试图将从串口(从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 中的调试功能暂停和取消暂停程序时更新

python matplotlib tkinter graph arduino
1个回答
0
投票

当数据发生变化时,可以使用

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
,它会在
updateGraph
参数中指定的时间后定期调用
interval
函数。

updateGraph
尝试从
dataQueue
读取数据。如果找到数据,它会从
dataQueue
中读取一个数据值并将其添加到
sensor_data
中,然后将当前时间添加到
time_data
中,最后更新图形 (
fig
)。如果
dataQueue
为空,则会发生异常,并且会跳过更新迭代的数据和图形。

注意:由于我们没有用于此演示的 Arduino,因此我创建了一个名为

arduino
的函数,该函数每 0.2 秒生成一些随机数据,并通过
arduinoThread
与绘图程序并行运行以模仿传感器的行为由真正的 Arduino 生成的数据。

© www.soinside.com 2019 - 2024. All rights reserved.