Project stats#

  • Difficulty: hard 5/5
  • Cost: 0€
  • Time: ~400h

Impressions#

Image: C++ logo
A software written for the Arduino Framework that controls an Mp3 Player (with SD card), an NFC Tag Reader, and provides user controls that together work like a jukebox that can be controlled via NFC Tag. For example, an NFC tag can be linked to a folder on the SD card containing audio files. The tag will also store information about the requested play mode for this album, e.g. “Random”. Once a linked tag is placed on this jukebox, the configured folder will play with the selected playmode. User controls allow to select the track, change volume and even to navigate a voice menu that allows deleting a tag, linking/ configuring a tag, and to lock/ unlock the input keys if required.

Motivation#

Idea and fantastic execution found on Thorsten Voß’s blog. It began with a feature (encoder support) that I wanted to add. Unfortunately, the original code was a single-file application with several thousand lines of code and poor readabilty, so I had a hard time getting my changes in. So I re-wrote the code from scratch for better structure, readability, maintainability, and extensibility. By doing this, in parallel I read some books about Object Oriented design in C++, Clean Code and Software Design Patterns and applied some where I tought they’d fit in. This way, I was able to both improve modularity and readability of the code, plus I taught myself how to write code in a “bigger” project.

Hurdles to overcome#

This was first real OO C++ project, and, maybe, it was a little big to start with. All the new-to-me concepts, writing to an interface, first-time usage of Platformio as IDE, utilizing the googletest unit test framework, using coding patterns like factory or Dependency Injection took nearly a year of evenings before this project could be completed. And there’s always something left to refactor and to improve.

Quick facts#

  • Unit test suite of 250+ test cases
  • Serial debug output configurable
  • Loosely coupled C++ OO architecture
  • Individual modules with clear tasks, scaleable and easily accessible for future feature add
  • custom Hardware abstraction layer; most of the code should be portable to other mcus without changes
  • custom Dependency Injection framework (Loader class)

Features#

  • Configurable user input (buttons or encoder)
  • Auto-Poweroff when no button pressed for a configurable time
  • Power on via “play” button press
  • Autoplay feature
  • Status Led feature
  • User Input lock/unlock feature
  • Multiple playmodes [Album, Random, SaveProgress, OneTrack] available per Nfc Tag
  • Voice menus for linking or deletion of Nfc Tags
  • Optimized for battery applications (e.g. powerbank) using sleep states
  • Low power consumption @5V: ~40mA in idle, ~75mA playing medium volume
  • Config file for init volume, lullabye timer etc.
  • Auto-recovery from stuck prompts
  • Voice prompts for most common state errors

Features not incliuded#

  • No powerbank state of charge detection
  • No config settings menu (init volume, lullabye timer duration, standby duration etc.)
  • …

Documentation#

The Project is build for the Arduino Framework - tested on an Arduino nano board - using the PlatformIO IDE. The following sections show the design.

Project Module overview#

Folder name in /libPurpose
Arduinominimalistic close-to-Hardware implementations, not unit-testable
Arduino_HardwareAbstractionHardware abstraction to enable portability and testing
ConfigSystem configuration parameters
FolderPlaylist and playmode business logic
LoaderDependency Injection Framework
MessageHandlerSystem messages and Debug Framework
Mp3Mp3 control (Status, Folder, Prompts, Advertisements)
NfcTag control (Status, Read, Write, Delete)
PowerManagerControl Status Led and Keep Alive based on system state
TonuinoMain task scheduler
UserInputButton or Encoder input handling
UtilitiesTimers, Led Control, Pin control
VoiceMenuLink / Delete / Config menu business logic

External Libraries#

Auto-install through Platformio on initial build through Library Dependency Finder.

Class Diagrams#

Class diagrams would have been too much non-automated work. Take this instead, please. Software module overview It should give an accurate understanding of how the software module are interacting and what APIs the modules are offering.

Get started!#

It’s DIY time! Clone my repository to get started with. Read the README.md document. Build it with PlatformIO. Below you can find what you need to get started with your unique speaker design.

Testing#

Unit tests#

Unit tests are written with the gtest C++ unit test framework. The reside in the test/desktop folder. Note that googletest will require gcc with some libraries to be installed, how to can be found in one of my other projects. Once these prerequisites are complete and a simple ASSERT_TRUE(false); test fails as expected using the gtest environment, the project’s Unit Tests can be built and run using pio test -e desktop -f desktop, detailed out here, in the PlatformIO CLI (terminal).

Acceptance tests#

Although these tests could be automated, it is much easier to just perform those tests by hand after you programmed your µC and put all electronic components together. Each line in the tables below is a test case on its own. The test suites (Heading name) do have a System Pre: property, that needs to be re-established prior to executing the individual test cases. If the expectation clause is met, the test is a PASS.

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 prompty 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#

Material list#

Minimal discrete parts approach

AmountItemPurpose
1Arduino (e.g. Nano)Brains
1Dfplayer Mini Mp3 PlayerMouth
1Micro SD CardMemory
1MFRC522 Nfc ReaderPsi-Sense
1Bistable Relay 5V, e.g. HFE20Coffee - no sleep
2Diodes e.g. 1n4007vene valve
1PNP Transistor e.g. BC327Coffee maker switch
2Resistors 1kblood-stream conditioner
1Resistor 220dim blink strength
1LED, color of choiceblink
1Powerbank/ 5V supplyFood
3push buttons, e.g. cherry MX keyspressure sensors
1speaker, e.g.5W@4OhmsLungs
1USB-A plugStraw
1housingBody

Additional coponents and tips#

Jumper wires, PCB, Sockets, and expendable materials as you deem fit Same as for the “original” project; Alternatively buy one rotary encoder instead of push buttons and configure the project to use it. As the system is optimized for battery usage, take a bi-stable relay or a JFET transistor (with low gate voltage and low Source-Drain voltage drop) for keepAlive functionality mitigating additional current add through always-on relay coils. A cheap powerbank will last for many hours. Alternatively, e.g. 3xAA batteries can be used - but the Dfmini will really make some noise if not enough current can be supplied due to running resets (and may even get bricked through current surges, so beware).

Schematics#

Circuit Diagram