Matplot animation

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
XonixX
User
Beiträge: 2
Registriert: Montag 20. November 2023, 15:56

Guten Tag, liebes Forum :)
ich hätte da mal eine Frage. Ich programmiere aktuell eine GUI mit Tkinter. Das ganze läuft auf einem Raspberry Pi 4.
In dieser GUI wird ein Fahrzyklus aus einer Excel Tabelle ausgelesen und in der GUI als Diagramm animiert. An dem Raspberry Pi ist einmal per USB ein OBD2 Adapter angeschlossen und über die GPIO PINS ein Adafruit Ultimate GPS Breakout.
Ich habe also 2-mal die Gleiche GUI, die immer wieder die gleiche Excel animiert abspult und je nach Code einmal die Ermittlung der IST Geschwindigkeit über OBD2 und einmal über GPS. Jetzt sollen Zyklen damit abgefahren werden. Nun habe ich leider 2 Probleme.
Einmal habe ich mit der threading Bibliothek die SOLL Geschwindigkeit anzeigen lassen, oben rechts im Bild(Digital-Anzeige als Hilfestellung). Das gefällt mir aber überhaupt nicht, da es sich mit tkinter beißt. Also er zieht sich separat die Daten der Excel Datei und spult diese parallel ab. Deswegen würde ich gerne eine Funktion erstellen, die immer den X= Wert bei y=0 des Graphen anzeigt. Gibt es da eine Möglichkeit? Habe mich durch mehrere Foren und Videos geklickt, aber nichts in der richtig gefunden.
Der 2. Punkt wäre, dass ich das Diagramm, welches von oben nach unten läuft und animiert ist, gerne fließend animiert hätte. Aktuell ist es so, dass die Sollwerte sich einmal die Sekunde aktualisieren und dadurch das Diagramm springt. Ich hätte gerne eine fließende Animation zwischen den Aktualisierungen.
Frames ändern in einem FuncAnimation hat es leider auch nicht gebracht.
Falls jemand Lösungen oder Ideen hat, wäre ich darüber sehr dankbar.

Mit freundlichen Grüßen

Code: Alles auswählen

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='--')

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, frames=5000, 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()/code]
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@XonixX: Das ist alles ein bisschen viel auf einmal für Code der noch nicht mal Funktionen benutzt. Also Funktionen nicht im Sinne von ``def name()`` was rein formal eine Funktion ist, sondern im Sinn von einer richtigen Funktion die alles was sie ausser Konstanten benötigt als Argument(e) bekommt und Ergebnisse über den Rückgabewert zum Aufrufer kommuniziert und nicht einfach auf magische Weise auf globalen Zustand zugreift oder den gar verändert. Das dann noch mit GUI *und* dann auch noch Threads dazu ist keine Basis für ein sinnvolles Programm. Dann geht `sleep()` in GUIs nicht, das lässt alles einfrieren, und GUI-Strukturen von anderen Threads aus manipulieren als dem in dem die `mainloop()` läuft ist auch ein No-Go.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
XonixX
User
Beiträge: 2
Registriert: Montag 20. November 2023, 15:56

Danke für die schnelle Antwort.
Sorry ich bin noch nicht so lange dabei und habe einfach mal wild drauflos programmiert. Das hat eigentlich auch ganz gut funktioniert, aber leider wird mir das gerade ein wenig zum Verhängnis. Also das mit dem Threading habe ich behoben bekommen, sowie das Ersetzen der sleep Funktion auch, danke für die Information ich wusste nicht, das es ein No-Go ist :D. Das einzige Problem was ich noch habe wäre die Animation. Den Intervallen darf ich nicht verstellen, weil sonst der Ablauf des Fahrzyklus verschnellert wird, was ja nicht sein soll.

Vielleicht hast du oder jemand anderes einen Ansatz wie ich das realisieren kann.
Antworten