MP3-Player mit Pico und DFPlayer Mini

  • Hallo zusammen,

    Ich könnte Hilfe beim folgenden Projekt gebrauchen, Vorweg: Ich bin noch Anfänger. Der untere Code ist eine Kombination aus eigenem Schaffen und unserem guten Freund (?) Chat-GPT. Vor allem beim erstellen der beiden Modi hat mir Chat geholfen.

    An sich funktioniert der Code auch erstmal ganz gut, bis zu dem Punkt, wo der DFPlayer in eine Dauerschleife des Abspielens kommen soll.


    Der DFPlayer spielt ein Lied zuende, sofern er keinen anderen Input bekommt. Soweit so gut. Allerdings stehe ich jetzt auf dem Schlauch, wie ich es hinkriegen kann, dass er (sowohl im Shuffle-, als auch im Reihenfolgen-Modus) selbstständig das nächste (bzw. im Shuffle-Mode ein neues zufälliges) Lied abspielt und ich gleichzeitig noch die Tasteninputs "abhorchen" kann.


    Ergo: Wie kann ich eine Dauerschleife (je Modus) erstellen, welche von den Tasten unterbrochen werden kann, ohne dass der DFPlayer alle paar ms das nächste Lied spielt?

    Hilfe ist sehr willkommen. Vielen Dank im Voraus!


    Hier mein Code:

  • Code
    while (button2.value() == 0 and buttonX.val...):
    	pass

    Die play, vor,.. Kunktionen kannst du innerhalb der Schleife oder durch Funktonsausruf schreiben. Man kann sie auch schachteln und ganz außen die while True: Schleife

    Edited once, last by HiddenSound70841 (April 2, 2025 at 10:58 PM).

  • CptAmmogeddon Wahrscheinlich liegt der Mangel an Rückmeldung ein dem Riesenklumpen Code — 170 Zeilen mit 20 globalen Variablen die den Zustand ausmachen. Mit teilweise sehr nichtssagenden Namen wie S0 bis S2 oder x und y, die hier keine Koordinaten sind, sondern ganze Zahlen mit irgendeiner magischen Bedeutung. Von den fünf def-Anweisungen definiert gerade mal eine so etwas ähnliches wie eine Funktion — die anderen werden eher als Sprungmarken für Code missbraucht.

    Das sollte dringen alles mal aufgeräumt und sauber geschrieben werden. Dabei ist es dann auch nicht unwahrscheinlich, dass das Problem gelöst wird, oder zumindest einfacher erkennbar und damit lösbar ist.

    Auf Modulebene sollte nur Code stehen, der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht in einer Funktion die üblicherweise main() heisst. Alles was eine Funktion (oder Methode) ausser Konstanten benötigt, wird als Argument(e) übergeben. Ergebnisse werden als Rückgabewerte an den Aufrufer zurückgegeben. Das Schlüsselwort global hat in einem sauberen Programm nichts zu suchen, genau so wenig wie globale Variablen die ohne das Schlüsselwort auskommen.

    Namen sollten dem Leser verraten was der Wert dahinter bedeutet. Man nummeriert keine Namen. Da will man sich entweder passendere Namen überlegen, oder gar keine Einzelnamen und -werte, sondern eine Datenstruktur. Oft eine Liste. Zum Beispiel bei S0 bis S2, was eher eine Liste sein sollte, und vielleicht mit dem Namen channel_multiplexer_pins.

    x hiesse besser is_paused und wäre ein Wahrheitswert statt der Zahlen 0 und 1. Wobei diese Variable momentan zu einem Problem werden kann, weil die wahr sein kann auch wenn der Player etwas spielt, denn man muss eigentlich sicherstellen, dass die immer auf False wechselt wenn der Player gestartet wird, oder das man den Player garantiert nicht starten kann, ausser über die eine Stelle im Code, wo die wieder auf False gesetzt wird.

    y sollte nicht y heissen und keine magischen Werte von 0 bis 2 annehmen wo man wissen/suchen muss was diese Zahlen bedeuten. Ich nenne das mal button_b_mode, weil ich nicht weiss was das letztendlich tatsächlich bedeutet — da kann man sicher noch einen verständlicheren Namen für vergeben. Als Ersatz für magische Zahlen gibt es Aufzählungstypen und -werte.

    current_channel wird definiert, aber nirgends verwendet. click_count wird definiert bevor überhaupt klar ist, ob der Wert benötigt wird.

    check_buttons() wird nur an einer Stelle aufgerufen und ist so trivial, dass das eigentlich eher direkt an der Stelle stehen sollte. switch_mode() ist ähnlich. Das tut zwar mehr, aber ich sehe nicht warum das ausgerechnet in dieser Form aus der Aufrufstelle herausgezogen ist.

    Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht was der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht. Kommentare vor Funktionen die beschreiben was die Funktions macht, sind eher Docstrings und ganz sicher braucht man diese Information nicht als Kommentar und als Docstring.

    Kommentare sollte man möglichst überflüssig machen. Zum Beispiel durch bessere Namen. Ein Name switched in einem Programm mit vielen Sachen die umgeschaltet werden können braucht einen Kommentar worauf sich das bezieht. Der Name is_aux_cable_detected braucht diesen Kommentar nicht mehr. Und man die Information aus dem Kommentar jetzt durch den Namen überall wo der Name verwendet wird, und nicht nur an der einen Stelle wo vorher der Kommentar stand.

    Falsche Kommentare sind schlimmer als keine Kommentare. Ein Kommentar soll eventuelle Fragen mit dem Code klären, aber wenn dort falsche Informationen drin stehen, oder gar welche die dem Code direkt widersprechen, erreicht man genau das Gegenteil. Der Leser weiss dann nicht was falsch ist, der Code oder der Kommentar. Im Code steht y != 2, im Kommentar steht „Wenn der Status "SD" ist […]“. Ungleich zwei bedeutet aber nicht SD sondern „nicht BT“, also könnte der Status auch AUX sein. Ist das okay oder nicht, also ist hier der Kommentar falsch, oder der Code, oder nur unvollständig (wieder Kommentar? Code? Beides?)?

    Wenn man sich den Code anschaut und ein bisschen umsortiert, sieht man a) das ein Wechsel des y Wertes immer einen Kanalwechsel zur Folge hat, und das das je nach y-Wert auch immer der gleiche Kanal ist. Dieser Zusammenhang sollte im Code wesentlich deutlicher sein.

    Der Abspielmodus „Shuffle“ sollte „Random“ heissen. „Shuffle“ gibt es auch, das ist aber etwas anders, der Code hier macht „Random“.

    Zwischenstand (ungetestet):

    Die Hauptfunktion ist zu lang und zu komplex, die sollte man sinnvoll auf Funktionen aufteilen. Es gibt Variablen/Zustand der so eng zusammengehört, dass man da sinnvoll zu Objekten zusammenfassen kann. Es gibt ähnlichen Code, beispielsweise bei den Zeitmessungen wo sich auch sinnvoll Objekte machen lassen. Benutzerinteraktion und Programmlogik sollte man trennen.

    Tradition is just peer pressure from dead people.

  • Wahrscheinlich liegt der Mangel an Rückmeldung ein dem Riesenklumpen Code

    Und ehrlich gesagt auch etwas daran das ChatGPT im Spiel ist. Ich weis da nie in wie weit welcher Teil vom Code vom Ersteller verstanden ist, denn nur da macht es meiner Meinung nach Sinn, Erklärungen/Verbesserungen zu schreiben. Dann lieber den Code soweit selbst schreiben wie man kann und die nächsten (unklaren) Schritte zusammen hier (oder sonst ein Forum) erarbeiten. Der User lernt und die Helfenden wissen, dass die Zeit sinnvoll investiert ist.


    Grüße
    Dennis

    🎧 Hate the jocks, the preps, the hippie fuckin' scumbags.
    Heavy-metalers with their awful, pussy hairbands.
    Counting seconds until we can get away.
    Ditching school almost every single day, oh, yeah 🎧

  • Zwischenstand (ungetestet):

    Erstmal vielen Dank für deine Mühe und das Feedback!

    Da ich als Novize MicroPython nutze, kann ich (meines Wissens nach) nicht enum verwenden. Aber vielleicht ist es dann auch an der Zeit, zu CircuitPython (hat das enum?) zu wechseln. :saint:


    Dann lieber den Code soweit selbst schreiben wie man kann und die nächsten (unklaren) Schritte zusammen hier (oder sonst ein Forum) erarbeiten. Der User lernt und die Helfenden wissen, dass die Zeit sinnvoll investiert ist.

    Ja, da hast du recht. Der Code, den mir ChatGPT am Anfang ausgeworfen hat (ich habe es auch genutzt, um meinen Code zu "verbessern") schien perfekt zu funktionieren. Da hab ich mich dann verführen lassen :)

    Das Hauptproblem habe ich übrigens inzwischen (glaube ich) auffinden können:

    Sobald ich eine Schleife bastle, die dafür sorgt dass ein Lied nach dem anderen abgespielt wird, kommt der Pico (oder die Library die ich nutze) nicht mehr mit.

    Hier mal mein aktueller Code für das Input-Handling (nicht wundern, ich selber schreibe lieber auf englisch die Kommentare ;)). Auch der ist vermutlich noch sehr ineffizient, aber zumindest ist das Problem jetzt lokalisierter. Der Code funktioniert auch wunderbar, aber nur bis ich den Abschnitt ganz unten (aktuell deaktiviert) einfüge. Möglicherweise ist die Funktion "get_status" zu komplex oder lang und lastet den Pico aus bzw. nimmt einen großteil der Schleife ein. Denn mit diesem Abschnitt werden die Knopfinputs sehr unzuverlässig oder teilweise gar nicht erkannt.

    Unter diesem Kommentar werde ich auch mal die heruntergeladene Library für den DFPlayer gepackt, vielleicht kann da ja auch was optimiert werden, was das Problem löst.

  • Hier die genutzte Library:

  • Meh, ich hätte gedacht so etwas wie Enum gibt's schon in/für MicroPython. Es gibt seit letztem Monat einen Pull-Request der gut aussieht: https://github.com/micropython/micropython-lib/pull/980

    Dann würde ich da zumindest für den Übergang halt Konstanten für definieren, wo man sonst und auch in C beispielsweise einen Aufzählungstyp verwenden würde.

    Ich vermute mal das get_status() (zu) lange läuft. Da wird ja mindestens ein mal 0,2 Sekunden gewartet. Und dass dann bei jedem Schleifendurchlauf. Das in Kombination, dass die Mehrfachklick-Erkennung auch mehrere Durchläufe der Hauptschleife benötigt, macht das dann eher unbenutzbar.

    Die API von der Methode ist auch total bescheiden. Die gibt False im Fehlerfall oder 0, 1, oder 2 zurück, dass heisst man muss zwischen False und 0 unterscheiden, was ein bisschen tricky ist in Python weil die Wahrheitswerte von ganzen Zahlen erben und False gleich der Zahl 0 ist:

    Wer da auf die Idee gekommen ist das False und 0 zurückgegeben werden sollte und das unterschiedliche Bedeutungen hat, sollte mal ordentlich… naja, Gewalt ist keine Lösung. Die Bibliothek sollte insgesamt nicht immer True/False zurückgeben wenn es gar kein Rückgabewert gibt sondern das eine Fehlersituation darstellt. Dafür gibt es in Python Ausnahmen.

    Der neue Code hat wieder ein paar sehr schlechte Namen (b, y, v beispielsweise). playing ist überflüssig, diese Information kann man vom Player-Objekt bereits abfragen, und das auch zuverlässiger.

    Man vergleicht Wahrheitswerte übrigens nicht mit literalen Wahrheitswerden, also ein if ham == True: macht keinen Sinn, denn bei dem Vergleich kommt ja nur wieder ein Wahrheitswert heraus, aber den hat man ja bereits und kann ihn direkt verwenden (if ham: in diesem Fall) oder dessen Negation mit not, falls man den anderen Fall prüfen möchte (if not ham:).

    In dem Zusammenhang viel mir auch das playing = True if playing == False else False auf, was aber nicht zu playing = False if playing else True werden sollte, sondern einfach zu playing = not playing.

    Es wäre sinnvoll den Unterschied zwischen Variablen und Konstanten deutlicher zu machen. Letztere werden per Konvention KOMPLETT_GROSS geschrieben.

    Die Werte aus click_times werden nirgends verwendet, nur die Länge dieser Liste, also braucht man die Elemente gar nicht, sondern kann einfach zählen wie oft man da etwas angehängt hätte.

    Literale Zeichenketten sind keine Kommentare und sollten nicht dazu missbraucht werden Code auszukommentieren. An bestimmten Stellen im Code haben die eine besondere Bedeutung für Python und für externe Werkzeuge an noch mehr Stellen. Python hat nur ein Kommentarzeichen, das #, und auch Blöcke werden damit auskommentiert. Editoren die zum Programmieren geeignet sind, haben eigentlich alle eine Funktion/Tastenkombination um Code ein-/auszukommentieren, so dass man da nicht mit einzelnen Zeilen und # beschäftigt ist, wenn man einen Block auskommentieren möchte.

    Man muss da wie gesagt ein bisschen aufwändiger Testen um False und 0 zu unterscheiden. Und dann könnte man die beiden verschachtelten if zu einem zusammenfassen.

    Zwischenstand (ungetestet):

    Für meinen Geschmack zu viel für eine Funktion und auch wieder/immer noch die Benutzerinteraktion mit der Programmlogik vermischt.

    Es gibt ja noch andere Bibliotheken für den DFPlayer, unter anderem auch async — das ist zwar jetzt auch nicht das einfachste zu verwenden, aber wenn man auf Mikroprozessoren mehrere Dinge nebenläufig machen möchte, ist das oft der einzig sinnvolle Weg.

    Tradition is just peer pressure from dead people.

  • Leider besteht immer noch das Problem, dass die Wiedergabe-Schleife und die Knopfabfrage sich nicht mögen.

    Das sind zu viele `sleep()` 's. Nehm die doch mal testweise aus `get_status` raus oder reduziere die Zeit.


    Grüße
    Dennis

    🎧 Hate the jocks, the preps, the hippie fuckin' scumbags.
    Heavy-metalers with their awful, pussy hairbands.
    Counting seconds until we can get away.
    Ditching school almost every single day, oh, yeah 🎧

  • Das sind zu viele `sleep()` 's. Nehm die doch mal testweise aus `get_status` raus oder reduziere die Zeit.

    Das habe ich jetzt versucht, und es hat ein wenig geholfen.

    Ich werde jetzt auch statt des einen Knopfes, der alles steuert, 3 Knöpfe einbauen. Einen für Play/Pause, einen für Next/Random und einen für Previous. Weniger elegant, aber viel leichter zu programmieren und viel zuverlässiger.

    Damit funktioniert jetzt auch (bisher) alles.

    Wenn wir grad schon dabei sind: Was wäre die einfachste Möglichkeit, den folgenden Abschnitt so zu modifizieren, dass jedes Lied einmal abgespielt wird, nicht einfach alle durcheinander? Ähnlich wie ein Shuffle-Modus?

    Code
        if player1.get_status() == 0:		#wenn kein Song läuft
            if started:						#und der Player bereits gespielt hat
                if not shuffle:				#und der Player nicht im Shuffle-Modus ist
                    player1.play_next()		#nächstes Lied
                else:
                    z = random.randint(1,5)	#zufälliges Lied
                    player1.play(z)

    Edited once, last by CptAmmogeddon (April 6, 2025 at 6:10 PM).

  • CptAmmogeddon Du kannst den Status nicht einfach nur mit 0 vergleichen. Das greift auch wenn die Methode False zurück gibt, also wenn irgend etwas mit der Kommunikation mit dem Player nicht stimmt.

    Warum denn jetzt z? Schlechte Namen wird man nicht los in dem man immer neue davon einführt, sondern in dem man lernt/übt gleich sinnvolle Namen zu verwenden. Und die 1 bei player1 hätte mittlerweile auch leicht mal verschwinden können.

    Das random-Modul hat eine shuffle()-Funktion. Neben einer Liste mit den Nummern, die man damit mischen kann, braucht es dann noch eine Variable in der man sich den aktuellen Index merkt, oder man erstellt und merkt sich einen Iterator über die Liste. Den normalen Modus würde ich dann gleich behandeln und in dem Falle dann einfach eine nicht-gemischte Liste mit den Songs verwenden. Dann muss man die beiden Fälle weniger unterschiedlich behandeln im Code.

    Eine offene Frage ist ja auch noch was an den ”Enden” der Playliste passieren soll.

    Tradition is just peer pressure from dead people.

Participate now!

Don’t have an account yet? Register yourself now and be a part of our community!