Projekt-Steckbrief#

  • Difficulty: Experte 5/5
  • Kosten: 0€
  • Zeitaufwand: ~400h

Galerie#

Image: C++ Logo
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](https://www. oodesign.com/factory-pattern.html) 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 /libZweck
Arduinominimalistische hardwarenahe Implementierungen, nicht Unit-testbar
Arduino_HardwareAbstractionHardware-Abstraktion, um Portabilität und Testen zu ermöglichen
ConfigSystemkonfigurationsparameter
FolderGeschäftslogik für Wiedergabeliste und Wiedergabemodus
LoaderDependency Injection Framework
MessageHandlerSystemnachrichten und Debug-Framework
Mp3Mp3-Steuerung (Status, Ordner, Sprachausgabe, Anzeigen)
NfcTag-Steuerung (Status, Lesen, Schreiben, Löschen)
PowerManagerSteuerung von Status-LED und Ruhezustand abhängig vom Systemstatus
TonuinoHaupt-Task-Scheduler
UserInputVerarbeitung von Tasten- oder Encoder-Eingaben
UtilitiesTimer, Led-Steuerung, Pin-Steuerung
VoiceMenuGeschäftslogik für das Menü Link / Löschen / Konfiguration

Verwendete externe Bibliotheken#

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:

Software-Modulübersicht

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

ActionExpectation
press Play/Pause buttonLED flashes slowly?
press Play/pause buttonWelcome prompt plays?

Switching OFF#

System Pre: System is ON

ActionExpectation
Playback on pause, no button inputSystem switches off after a time?
System shutdownFarewell prompt plays?
System shutdownLED switched off?

Play Help Prompt#

System Pre: System is ON

ActionExpectation
Long press Play/Pause buttonHelp prompt plays?
Help prompt playingCan be interrupted with any button press?

Behavior without Tag#

System Pre: System is ON, no Tag present

ActionExpectation
press Play/Pause buttonprompts “couldn’t find track” error?
press Next buttonprompts “couldn’t find track” error?
press Prev buttonprompts “couldn’t find track” error?
doubleclick Play/Pause buttonDelete Menu prompt played?

Behavior with Tag#

System Pre: System is ON, linked Tag available

PreconditionActionExpectation
no Tag placedplace known Tagstarts Playback of correct Folder?
active playbackpress Next buttonplays next track?
active playbackpress Next buttonnext track in accordance with selected playMode of Folder?
active playbacklong press Next buttonincreases volume?
active playbackpress Prev buttonplays previous track?
active playbackpress Prev buttonprevious track in accordance with selected playMode of Folder?
active playbacklong press Prev buttondecreases volume?
active playbackpress Play/Pause buttonpauses track?
paused playbackpress Play/Pause buttonresumes track?
active playbackdoubleclick Play/Pause buttonLock prompt played?
active playbackdoubleclick Play/Pause buttonPlayback resumes after Lock prompt played?
active playbackdoubleclick Play/Pause buttonlocks button input?
locked button inputdoubleclick Play/Pause buttonUnlock prompt played?
locked button inputdoubleclick Play/Pause buttonPlayback resumes after Unlock prompt played?
locked button inputdoubleclick Play/Pause buttonunlocks button input?
paused playbackdoubleclick Play/Pause buttonPlayback resumes after Lock prompt played?

Behavior with unlinked Tag#

System Pre: System is ON

ActionExpectation
place unlinked Tagplays LinkMenu Prompt?
navigate through LinkMenuplays Configuration Success Prompt?
place Tag againplays 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.

MengeArtikelZweck
1Arduino (z.B. Nano)Gehirn
1Dfplayer Mini Mp3 PlayerMund
1Micro SD KarteSpeicher
1MFRC522 Nfc-LeserPsi-Sense
1Bistabiles Relais 5V, z.B. HFE20Kaffee - kein Schlaf
2Dioden z.B. 1n4007Venenklappe
1PNP-Transistor z. B. BC327Kaffeemaschinenschalter
2Widerstände 1kBlutstromaufbereiter
1Widerstand 220Blinkstärke dimmen
1LED, Farbe nach Wahlblinken
1Powerbank/ 5V VersorgungLebensmittel
3Drucktasten, z.B. Cherry MX TastenDrucksensoren
1Lautsprecher, z.B. 5W@4OhmsStimmbänder
1USB-A-SteckerStrohhalm
1GehäuseKö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.

Schaltplan#

Stromlaufplan