Making a Raspberry Pi Boombox (AKA UNIAC Software In Detail)

Introduction

Most of you who see this post will have seen the original UNIAC post before this, and hopefully some will have seen the post detailing the hardware. For those who haven’t, UNIAC is a Raspberry Pi based Spotify playing boombox that is controlled with buttons on the front and displays status using Nixie tubes. This post will cover the software used to make UNIAC work — both tools developed by others and the software I wrote myself.

While I am writing this from the perspective of documenting UNIAC, the software could be pretty easily recycled to work with any display and buttons, you’d just have to rewrite the display and button event functions to support your hardware.

Also note that I did a major ripup of the UNIAC software after the original post, so this version does not use Mopidy, MPD, or Mopidy-Spotify. And if any of you have used those tools before, you’ll understand that’s a good thing.

I will start with a disclaimer however: I am an electrical engineer, not a software engineer, so this design will probably make software guys sick to their stomach. Sorry in advance…

Third Party Software

The first thing to understand about the architecture used in UNIAC is that control and playback of music are two separate processes. Raspotify is a software package that connects to Spotify and makes your RPi act as a Spotify endpoint. Essentially a dedicated speaker. There are a couple of similar packages that can give you a Spotify client on a RPi, but Raspotify has a trivial installation process:

curl -sL https://dtcooper.github.io/raspotify/install.sh | sh

The second part, Spotipy, is a Python library that you give permissions on your (paid) Spotify account. It allows you to see current playback status, and control playback on your Spotify devices (computers, speakers, etc). By combining these two, you can have a full Spotify instance running on your device with programmable control.

The two pieces are connected by the UNIAC Python script. UNIAC sets up a spotipy connection and then controls what device the playback occurs on. Since the name of the UNIAC Raspotify instance is constant, it simply has to tell Spotify to playback there, and music will play on the UNIAC hardware. This has the added benefit that I can ‘cast’ whatever I’m currently listening to over to UNIAC and it will just continue to play.

UNIAC / Spotify control.

The last piece worth mentioning is eSpeak, a relatively primitive speech synthesizer that takes strings from the command line and announces them. This lends UNIAC its characteristic robot voice when announcing settings information and playlist names.

Hardware Libraries

Path between the controls, the Display, and UNIAC.

The hardware interfaces are pretty straightforward, mostly. The buttons are controlled by an i2c GPIO expander, and Adafruit has the MCP230XX library to support it. For other GPIO, I use RPi.GPIO.

The last, and most unique library is the NixieDisplay library. It is a custom library that I developed to communicate with my Teensy 3.2 based multiplexing, crossfading Nixie display. In the first prototype, I used Taylor Edge SmartNixie modules instead of my custom display, so I also wrote a library supporting them on the RPi, though I’m not using it on the finished version.

These libraries are pretty straightforward. You print numbers to the tubes to display and forget about it.

The VU-Meter only has an audio input, so it doesn’t have any software control and doesn’t appear on any of these block diagrams.

UNIAC Software Architecture

The UNIAC software is essentially an interrupt driven button handler. There is a master ‘Menu’ class in Menu.py to which arbitrary ‘Modes’ are attached. An attached mode must provide a function handler for each physical button as well as a display update handler (and a few other helper functions).

When a button is pressed, the Menu object calls the current Mode’s handler for that button.

Button Press Event

The UNIAC.py script creates a Menu instance and attaches the modes defined in that script. The modes (currently) are: clock, track time, date, alarm clock, change playlist, and options.

Modes attached to Menu in UNIAC.py

For instance, when the ‘Plus’ button is pressed in the track time mode, the menu object will use Spotipy to progress to the next track, but when ‘Plus’ is pressed in the alarm mode, it will advance the alarm time by one.

The ‘Mode’ button is a special case that does not call an attached handler function but instead changes the selected mode in the Menu object.

The Menu object also requires that each mode have a display update handler. This handler is called by the master UNIAC.py script in an infinte loop to update the display every 100 ms.

Display Updater

This code was actually based off of another (unfinished) project of mine which used two buttons and a character LCD. By changing the displayUpdate function and the button handlers, this script could be used for virtually any menu interface with multiple ‘pages’.

Interfacing with Spotipy

As discussed in the preceeding section, UNIAC is controlled through a series of button handlers. To interface with Spotipy, most of the modes used in UNIAC inherit from the mpdGeneral class.

mpdGeneral class definition

This class has a few static variables and references the globally available spotipyLogin.sp object, which is an instance of the bare Spotipy API created by the spotipyLogin.py script. It includes things like track status, play, pause, loading a defined playlist from URI, listing available playlists, et cetera.

The UNIACConfig class allows settings like alarm time, current playlist, and settings to be pickled to a config file and loaded on restart. The other classes (including the option menu mode) use this class to handle i/o to the settings file, ‘UNIAC.conf’.

UNIACConfig class.

The remainder of the classes are more or less straightforward implementations of their self explanatory modes and hook the various parts and pieces together.

The last piece of secret sauce, which is not on git, because it is in m gitignore is spotipyLogin.py, a simple script which holds my Spotify login credentials and makes a logged in Spotipy object available.

The redacted version of spotipyLogin.py

UNIAC Supervisor

Finally, there is a UNIAC supervisor script. This is an independent Python process which checks if the UNIAC script is running and if not launches it. It also checks that only one instance of the script is running, and if a second copy has started somehow, kills all but the first process. It’s not strictly necessary, but is a nice-to-have feature. I ran without it for years, with only occasional failures.

UNIAC supervisor script.

Conclusion

The UNIAC software is a combination of several bits and pieces of code which allows a button driven paged menu system to control the Spotify web API and stream music for plaback locally on the RPi, as if it were a commercial product. The interface is ideally suited to a ‘Nixie Boombox’ but could be used with any oddball display you wanted with a little modification.

Thanks for reading.

Refactoring The UNIAC Playback Code

One of the most problematic parts of the UNIAC project has been the music playback software. The original software used the Mopidy music player for command line playback. Mopidy-spotify provided Spotify connectivity to the software. The mpd python library then allowed me to control playback programmatically.

Old Mopidy + MPD based flow

This had several problems.

  1. Mopidy-spotify is partially broken. Spotify themselves no longer support this interface, and haven’t since 2016. To get it working at all requires a hacked fork of Mopidy-spotify which might break forever at any time.
  2. Response between Python and Mopidy can be really laggy and requires a continuous ping (every 30 seconds) to keep the connection alive.
  3. Loading a new playlist in Mopidy requires significant caching and can have tens of seconds of lag, making the whole system unreasonably slow.

This was all a legacy of the fact that I had designed the software for UNIAC in 2015 and the Mopidy workflow was the only one I could find for getting Spotify playback on a Raspberry Pi. In early 2020, after hearing about Spotifyd from the Diskplayer project, I was awoken to more modern alternatives.

After some rework, I ripped out Mopidy and mpd, and replaced it with raspotify as a player and Spotipy to interface with Spotify.

New Raspotify + Spotipy based flow

This has several advantages.

  1. The control API that Spotipy uses is actively supported by Spotify
  2. Raspotify makes the Pi show up as a Spotify speaker, meaning active playback can be bounced between devices.
  3. Spotipy is controlling my Spotify state, not Mopidy, so it can actually control playback of other devices that are actively playing music.
  4. By the same token, pausing and unpausing on other clients (phone, laptop, etc.) works seamlessly.
  5. Music is streamed, not buffered. This means that loading a new playlist is as instant as it is on the desktop client.

All in all, this means that the switch resulted in a much more pleasant user experience. On top of that, the system is generally more stable than it was before.

Plus, setting up Raspotify was orders of magnitude less difficult than Mopidy. Raspotify managed to work automagically after apt-get installing it and setting it up as a service. This was unlike Mopidy, which took forever to point at the right sound card and get working.

There were some hiccups in figuring out how to connect to Spotify with Spotifyd, but the biggest tricks were:

  1. Learning that the credential saving path should be a full filename, not a path to a directory.
  2. Realizing that the redirect URI could be “http://localhost”. To make that work, all you have to do is run the login script from the command line on your Pi and then copy the URL it feeds you to a browser on a desktop machine. Once you accept the terms, all you have to do is copy the ‘broken’ URL that it takes you to back into your Pi command line and hit enter. Things should work from there.

If you’re looking to build a Raspberry Pi / Spotify player, avoid MPD and go for a Spotipy / Raspotify combo!

The revised code can be found here.