Matplot Animated

51 Views Asked by At

I am currently programming a GUI with Tkinter. The whole thing is running on a Raspberry Pi 4.

In this GUI, a driving cycle is read from an Excel table and animated in the GUI as a graph. A USB OBD2 adapter is connected to the Raspberry Pi, and an Adafruit Ultimate GPS Breakout is connected via the GPIO pins.

So, I have two instances of the same GUI, which repeatedly plays the same Excel animation and, depending on the code, determines the actual speed either through OBD2 or GPS. Now, cycles are supposed to be run with this setup. Unfortunately, I have two problems.

First, I used the threading library to display the target speed in the top right of the image (digital display as a guide). But I don't like it at all because it conflicts with Tkinter. So, it separately pulls the data from the Excel file and plays it in parallel. Therefore, I would like to create a function that always displays the X-value at y=0 of the graph. Is there a way to do this? I've clicked through several forums and videos, but haven't found anything in that direction.

The second point is that I would like the graph, which runs from top to bottom and is animated, to be smoothly animated. Currently, the target values update once per second, causing the graph to jump. I would like a smooth animation between updates. Unfortunately, changing frames in a FuncAnimation hasn't worked either.

import tkinter as tk
import pandas as pd
from matplotlib.animation import FuncAnimation
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import xlrd
import threading
from time import sleep
import obd

df = pd.read_excel('Fahrzyklen.xls', header=None, skiprows=[0])
df.columns = ['Zeit', 'Geschwindigkeit']

workbook = xlrd.open_workbook('Fahrzyklen.xls')
sheet = workbook.sheet_by_index(0)

connection = obd.OBD(portstr='/dev/ttyUSB0', baudrate=38400, protocol="6")

root = tk.Tk()
root.title("Geschwindigkeitsanzeige")

window_width = 1280
window_height = 720
root.geometry(f"{window_width}x{window_height}")

font_size = 16

fig = Figure(figsize=(window_width / 100, window_height / 100))
ax = fig.add_subplot(111)

fig.patch.set_facecolor('black')
ax.set_facecolor('black')

canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.grid(row=0, column=0, columnspan=2)

soll_label = tk.Label(root, text="SOLL: N/A", font=("Helvetica", font_size), fg="white", bg="black")
soll_label.grid(row=0, column=1, sticky="ne")

ist_arrows = ax.annotate("<", (0, 0), fontsize=font_size, color="white")

def update_soll_label():
    row = 1
    while True:
        soll = sheet.cell_value(row, 1)
        soll_label.config(text=f'SOLL: {soll:.1f} km/h')

        tol_upper = soll + 2.0
        tol_lower = soll - 2.0
        tol_upper_line.set_data([tol_upper] * 2, [df['Zeit'].min(), df['Zeit'].max()])
        tol_lower_line.set_data([tol_lower] * 2, [df['Zeit'].min(), df['Zeit'].max()])

        row += 1
        sleep(1)

def init():
    ax.set_xlim(0, 150)
    ax.set_ylim(-2, 15)
    ax.set_xlabel('Geschwindigkeit (km/h)', fontsize=font_size, color='white')
    ax.set_ylabel('Zeit (s)', fontsize=font_size, color='white')
    ax.set_xticks(range(0, 151, 20))
    ax.set_yticks(range(-2, 16, 2))
    ax.spines['bottom'].set_color('white')
    ax.spines['top'].set_color('white')
    ax.spines['right'].set_color('white')
    ax.spines['left'].set_color('white')
    ax.tick_params(axis='x', labelsize=font_size, colors='white')
    ax.tick_params(axis='y', labelsize=font_size, colors='white')
    x = df['Geschwindigkeit'].values
    y = df['Zeit'].values
    line.set_data(x, y)

    tol_upper_line.set_data([], [])
    tol_lower_line.set_data([], [])

    return line, tol_upper_line, tol_lower_line, ist_arrows

def animate(i):
    df['Zeit'] = df['Zeit'].apply(lambda x: x - 1)
    x = df['Geschwindigkeit'].values
    y = df['Zeit'].values
    line.set_data(x, y)

    ist_speed = read_ist_speed()
    ist_arrows.set_text(f"< {ist_speed:.1f} km/h")
    ist_arrows.xy = (ist_speed, 0)

    ist_arrows.set_x(float(ist_speed))

    canvas.draw()

    return line, tol_upper_line, tol_lower_line, ist_arrows

def read_ist_speed():
    cmd = obd.commands.SPEED
    response = connection.query(cmd)
    if response.is_null():
        return 0.0
    return float(response.value.magnitude)

line, = ax.plot([], [], color='blue')
tol_upper_line, = ax.plot([], [], color='green', linestyle='--')
tol_lower_line, = ax.plot([], [], color='red', linestyle='--')

# Hier wird die FuncAnimation erst erstellt, aber nicht gestartet
ani = None

update_thread = threading.Thread(target=update_soll_label)
update_thread.daemon = True

def start_animation():
    global ani
    ani = FuncAnimation(fig, animate, init_func=init, interval=1000, blit=True)
    ani.event_source.stop()
    update_thread.start()
    ani.event_source.start()
    start_button.config(state=tk.DISABLED)

start_button = tk.Button(root, text="Start", command=start_animation, font=("Helvetica", font_size), fg="white", bg="green")
start_button.grid(row=0, column=0, sticky="nw", padx=10, pady=10)

root.mainloop()

I would like a smooth animation, but one that still adheres to the specified frequency of 1 Hz. Additionally, I would like the target speed in the top right to be determined not through threading but as a function of the graph, specifically the x-value at y=0 on the graph.

0

There are 0 best solutions below