Tonuino - alternative Firmware
Projekt-Steckbrief
- Difficulty: Experte 5/5
- Kosten: 0€
- Zeitaufwand: ~400h
Galerie




In disem Projekt entstand eine Software für eine NFC-Tag gesteuerte Jukebox. Sie ist für das Arduino-Framework optimiert und verbindet die Hardware eines Mp3-Players (mit SD-Karte), ein NFC-Tag-Lesegerät und ein paar Bedienelemente.
Ein NFC-Tag wird mit einem Ordner auf der SD-Karte verknüpft, welcher Audiodateien enthält. Das Tag speichert auch Informationen über den gewünschten Wiedergabemodus für dieses Album, z. B. “Zufällig”. Sobald ein verknüpfter Tag in die Nähe der Jukebox gebracht wird, wird der konfigurierte Ordner im entsprechenden Wiedergabemodus abgespielt. Mit den Bedienelementen kann der Titel ausgewählt, die Lautstärke geändert und sogar durch ein Sprachmenü navigiert werden, was das Löschen, Verknüpfen und Konfigurieren eines Tags sowie Sperren/Entsperren der Eingabetasten ermöglicht.
Motivation
Idee und fantastische Umsetzung hatte ich auf Thorsten Voß’ blog gesehen. Es begann mit der Funktionalität (encoder support), die ich hinzufügen wollte. Leider war der ursprüngliche Code ein zusammenhängender Block mit mehreren tausend Zeilen Code und für mich schlechter Lesbarkeit, so dass es mir schwer fiel, meine Änderungen einzubringen.
Also habe ich den Code von Grund auf neu geschrieben, um eine bessere Struktur, Lesbarkeit, Wartbarkeit und Erweiterbarkeit zu erreichen. Parallel dazu las ich einige Bücher über objektorientiertes Design in C++
, Clean Code und Software Design Patterns und wandte einige davon dort an, wo ich sie für geeignet hielt. Auf diese Weise konnte ich sowohl die Modularität als auch die Lesbarkeit des Codes verbessern und habe außerdem gelernt, wie man Code in einem “größeren” Projekt schreibt.
Schwierigkeiten
Dies war mein erstes eigenes C++
-Projekt, und vielleicht war es für den Anfang ein wenig zu groß. All die für mich neuen Konzepte, das Schreiben an eine Schnittstelle, die erstmalige Verwendung von Platformio als IDE, die Verwendung des googletest unit test framework, die Einsatz von Coding Patterns wie factory oder Dependency Injection kostete mich Abend um Abend über fast ein Jahr, bis ich dieses Projekt schließlich fertigstellen konnte. Und es gibt immer noch etwas zu überarbeiten und zu verbessern.
Übersicht
- Unit-Test-Suite mit über 250 Testfällen
- Serielle Debug-Ausgabe konfigurierbar
- Lose gekoppelte, objektorientierte Architektur in
C++
- Einzelne Module mit klaren Aufgaben, skalierbar und leicht zugänglich für zukünftige Funktionen
- benutzerdefinierte Hardware-Abstraktionsschicht; der größte Teil des Codes sollte ohne Änderungen auf andere MCUs portierbar sein
- eigenes Dependency Injection Framework (Loader Klasse)
Funktionen
- Konfigurierbare Benutzereingabe (Tasten oder Encoder)
- Auto-Poweroff, wenn für eine konfigurierbare Zeit keine Taste gedrückt wird
- Einschalten durch Drücken der “Play”-Taste
- Autoplay-Funktion
- Status-Led-Funktion
- Sperren/Entsperren der Benutzereingabe
- Mehrere Wiedergabemodi [Album, Zufall, Speichern des Titelfortschritts, Nur ein Titel] pro Nfc-Tag verfügbar
- Sprachmenüs zum Verknüpfen oder Löschen von Nfc-Tags
- Optimiert für Batterieanwendungen (z.B. Powerbank) unter Verwendung von Ruhezuständen
- Niedriger Stromverbrauch
@5V
:~40mA
im Leerlauf,~75mA
bei mittlerer Lautstärke - Konfigurationsdatei für Startlautstärke, Einschlaf-Timer etc.
- Automatische Wiederherstellung bei Eingabeaufforderung, die niemand bedient
- Sprachansagen für die häufigsten Fehlermeldungen
Nicht enthaltene Funktionen
- Keine Erkennung des Ladezustands der Powerbank
- Kein Konfigurationsmenü (Startlautstärke, Dauer des Einschlaf-Timers, Standby-Dauer usw.)
- …
Dokumentation
Das Projekt ist für das Arduino Framework erstellt - getestet auf einem Arduino nano Board - unter Verwendung der PlatformIO IDE. Die folgenden Abschnitte zeigen das Design.
Projekt Modulübersicht
Ordnername in /lib |
Zweck |
---|---|
Arduino | minimalistische hardwarenahe Implementierungen, nicht Unit-testbar |
Arduino_HardwareAbstraction | Hardware-Abstraktion, um Portabilität und Testen zu ermöglichen |
Config | Systemkonfigurationsparameter |
Folder | Geschäftslogik für Wiedergabeliste und Wiedergabemodus |
Loader | Dependency Injection Framework |
MessageHandler | Systemnachrichten und Debug-Framework |
Mp3 | Mp3-Steuerung (Status, Ordner, Sprachausgabe, Anzeigen) |
Nfc | Tag-Steuerung (Status, Lesen, Schreiben, Löschen) |
PowerManager | Steuerung von Status-LED und Ruhezustand abhängig vom Systemstatus |
Tonuino | Haupt-Task-Scheduler |
UserInput | Verarbeitung von Tasten- oder Encoder-Eingaben |
Utilities | Timer, Led-Steuerung, Pin-Steuerung |
VoiceMenu | Geschäftslogik für das Menü Link / Löschen / Konfiguration |
Verwendete externe Bibliotheken
- Arduino’s TimerOne
- Arduino’s SoftwareSerial
- Schallbert’s ClickEncoder
- Makuna’s DFMini Mp3
- Miguel Balboa’s MFRC522
Automatische Installation durch Platformios “Library Dependency Finder” beim erstem Kompiliervorgang
Klassendiagramme
Klassendiagramme wären zu viel nicht-automatisierte Arbeit gewesen. Stattdessen habe ich das hier vorbereitet:
Sie sollte ein genaues Verständnis dafür vermitteln, wie die Softwaremodule zusammenwirken und welche APIs die Module anbieten.
Los geht’s!
Zuerst muss mein repository, geklont werden - entweder per git clone
, Download als zip
-Datei oder per https
. Das README.md
Dokument bietet einen leichten Einstieg. Ich empfehle die Verwendung von PlatformIO für dieses Projekt.
In den folgenden Abschnitten sind die Teilfunktionen des Programms erläutert, und wie deren korrekte Funktion sichergestellt wird.
Testen
Unit-Tests
Unit-Tests wurden mit dem gtest
C++ Unit-Test-Framework geschrieben. Sie befinden sich im Ordner test/desktop
. Zu beachten ist, dass googletest die Installation von gcc
mit einigen Bibliotheken voraussetzt. Die Anleitung dazu findet man in einem meiner anderen Projekte.
Sobald diese Voraussetzungen erfüllt sind und ein einfacher ASSERT_TRUE(false);
-Test mit der gtest-Umgebung erwartungsgemäß fehlschlägt, können die Unit-Tests des Projekts erstellt und mit pio test -e desktop -f desktop
, in der PlatformIO CLI (Terminal) ausgeführt werden.
Akzeptanztests
Obwohl diese Tests automatisiert werden könnten, ist es viel einfacher, diese Tests von Hand durchzuführen, nachdem der Mikrocontroller programmiert und alle elektronischen Komponenten zusammengesteckt wurden. Jede Zeile in den folgenden Tabellen ist ein eigenständiger Testfall. Die Testsuiten (“Überschrift Name”) haben eine System Pre: Eigenschaft, die vor der Ausführung der einzelnen Testfälle wiederhergestellt werden muss. Wenn die Erwartungsklausel erfüllt ist, ist der Test BESTANDEN. Ich habe die Tests auf Englisch belassen, weil sie recht eng mit dem Code korrespondieren.
Switching ON
System Pre: System is OFF
Action | Expectation |
---|---|
press Play/Pause button | LED flashes slowly? |
press Play/pause button | Welcome prompt plays? |
Switching OFF
System Pre: System is ON
Action | Expectation |
---|---|
Playback on pause, no button input | System switches off after a time? |
System shutdown | Farewell prompt plays? |
System shutdown | LED switched off? |
Play Help Prompt
System Pre: System is ON
Action | Expectation |
---|---|
Long press Play/Pause button | Help prompt plays? |
Help prompt playing | Can be interrupted with any button press? |
Behavior without Tag
System Pre: System is ON, no Tag present
Action | Expectation |
---|---|
press Play/Pause button | prompts “couldn’t find track” error? |
press Next button | prompts “couldn’t find track” error? |
press Prev button | prompts “couldn’t find track” error? |
doubleclick Play/Pause button | Delete Menu prompt played? |
Behavior with Tag
System Pre: System is ON, linked Tag available
Precondition | Action | Expectation |
---|---|---|
no Tag placed | place known Tag | starts Playback of correct Folder? |
active playback | press Next button | plays next track? |
active playback | press Next button | next track in accordance with selected playMode of Folder? |
active playback | long press Next button | increases volume? |
active playback | press Prev button | plays previous track? |
active playback | press Prev button | previous track in accordance with selected playMode of Folder? |
active playback | long press Prev button | decreases volume? |
active playback | press Play/Pause button | pauses track? |
paused playback | press Play/Pause button | resumes track? |
active playback | doubleclick Play/Pause button | Lock prompt played? |
active playback | doubleclick Play/Pause button | Playback resumes after Lock prompt played? |
active playback | doubleclick Play/Pause button | locks button input? |
locked button input | doubleclick Play/Pause button | Unlock prompt played? |
locked button input | doubleclick Play/Pause button | Playback resumes after Unlock prompt played? |
locked button input | doubleclick Play/Pause button | unlocks button input? |
paused playback | doubleclick Play/Pause button | Playback resumes after Lock prompt played? |
Behavior with unlinked Tag
System Pre: System is ON
Action | Expectation |
---|---|
place unlinked Tag | plays LinkMenu Prompt? |
navigate through LinkMenu | plays Configuration Success Prompt? |
place Tag again | plays linked folder? |
Hardware
Materialliste
Ich habe versucht mit möglichst wenigen Einzelteilen auszukommen. Für ein Projekt dieser Größe ist die Liste denke ich recht kurz geblieben.
Menge | Artikel | Zweck |
---|---|---|
1 | Arduino (z.B. Nano) | Gehirn |
1 | Dfplayer Mini Mp3 Player | Mund |
1 | Micro SD Karte | Speicher |
1 | MFRC522 Nfc-Leser | Psi-Sense |
1 | Bistabiles Relais 5V, z.B. HFE20 | Kaffee - kein Schlaf |
2 | Dioden z.B. 1n4007 | Venenklappe |
1 | PNP-Transistor z. B. BC327 | Kaffeemaschinenschalter |
2 | Widerstände 1k | Blutstromaufbereiter |
1 | Widerstand 220 | Blinkstärke dimmen |
1 | LED, Farbe nach Wahl | blinken |
1 | Powerbank/ 5V Versorgung | Lebensmittel |
3 | Drucktasten, z.B. Cherry MX Tasten | Drucksensoren |
1 | Lautsprecher, z.B. 5W@4Ohms | Stimmbänder |
1 | USB-A-Stecker | Strohhalm |
1 | Gehäuse | Körper |
Zusätzliche Bauteile und Tipps
Überbrückungsdrähte, Platine, Buchsen und Verbrauchsmaterial nach eigenem Ermessen Wie beim “Original”-Projekt; alternativ einen Drehgeber anstelle der Drucktasten kaufen und das Projekt so konfigurieren, dass er statt der Taster ausgelesen wird. Da das System für den Batteriebetrieb optimiert ist, nehmen Sie ein bi-stabiles Relais oder einen JFET-Transistor (mit niedriger Gate-Spannung und niedrigem Source-Drain-Spannungsabfall) für die KeepAlive-Funktionalität, die den zusätzlichen Strom durch die ständig eingeschalteten Relaisspulen vermeidet. Eine billige Powerbank reicht für viele Stunden Betrieb. Alternativ können z.B. 3xAA-Batterien verwendet werden - allerdings macht der Dfmini-Player ordentlich Lärm, wenn die Spannung zu sehr einbricht. Er kann sogar bei wiederholten Neustarts durch Stromstöße zerstört werden, also Vorsicht bei nachlassender Akkuladung.