Tonuino - alternative Firmware
Projekt-Steckbrief
- Difficulty: Experte 5/5
- Kosten: 0€
- Zeitaufwand: ~400h
Galerie
Eine für das Arduino-Framework geschriebene Software, die einen Mp3-Player (mit SD-Karte), ein NFC-Tag-Lesegerät und ein paar Bedienelemente verbindet, die zusammen wie eine Jukebox funktioniert welche über NFC-Tags gesteuert werden kann. Ein NFC-Tag wird mit einem Ordner auf der SD-Karte verknüpft, der 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 programmierten Wiedergabemodus abgespielt. Mit den Bedienelementen kann der Titel ausgewählt, die Lautstärke geändert und sogar durch ein Sprachmenü navigiert werden, das das Löschen eines Tags, das Verknüpfen/Konfigurieren eines Tags und das Sperren/Entsperren der Eingabetasten ermöglicht.
Motivation
Idee und phantastische Umsetzung hatte uch auf Thorsten Voß’ blog gesehen. Es begann mit einer Funktion (encoder support), die ich hinzufügen wollte. Leider war der ursprüngliche Code ein Monolith 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 OO-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 Verwendung von Coding Patterns wie factory oder Dependency Injection kostete mich Abend um Abend über fast ein Jahr, bis dieses Projekt abgeschlossen werden 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 C++ OO-Architektur
- 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!
Es ist Zeit zum Selbermachen!
Klonen Sie mein repository, um zu beginnen. Das README.md
Dokument bietet einen leichten Einstieg. Ich empfehle die Verwendung von PlatformIO für dieses Projekt.
Nachfolgend finden Sie alles, was Sie brauchen, um mit dem Design Ihres einzigartigen Lautsprechers zu beginnen.
Testen
Unit-Tests
Unit-Tests werden 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{:rel=”noopener noreferrer”, 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 Sie Ihren µC programmiert und alle elektronischen Komponenten zusammengesetzt haben. 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 ersucht 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 Komponenten 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 verwendet 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 mindert. Eine billige Powerbank reicht für viele Stunden. Alternativ können z.B. 3xAA-Batterien verwendet werden - allerdings macht der Dfmini dann ordentlich Krach, wenn nicht genügend Strom durch laufende Resets geliefert werden kann (und kann sogar durch Stromstöße gebrickt werden, also Vorsicht).