As a child, 30+ years ago, I was not exposed to computer screens - or much technology at all. I had my cassette player. I listened to radio plays from a selection of collected cassettes - or eventually compact discs. Self service. Pick a cover, insert cassette, play, done. And I was able to do so without help from an adult.
Today, if you didn't stick to compact disc for the last decade or more years you're possibly streaming contents using Amazon, Spotify, Apple Music or using a NAS in your home network. It's an environment for the tech savvy people. And it just works. Mostly. Technically.
Lately, I found myself dis-liking the process of getting my smartphone out at home, unlock it, open Sonos, search for the playlist I had in mind and then noticing that Sonos cannot be found because I'm in the wrong Wifi. Wash, rinse, repeat with the right Wifi. Eventually I start playing some music. I go upstairs, to move he music with me, I have to pick my phone again and change the speakers to the room where I'm headed.
Another attempt is voice commands using "Alexa", "Ok google" or "Hey Siri". I don't have words for how awkward that is. Half of the time it doesn't work. Have you ever tried to play a specific Album using voice commands? Good luck at radio plays too. Especially if you don't remember the exact title of the playlist or album. Did you try while music is playing? Speech is not yet a usable thing for me. Even if it works 9 out of 10 times, the one time it doesn't work is as annoying as using the smartphone for switching on lights.It's just not as convenient as a button.
Our pre-bed time for the kid sometimes involves picking an audio book at the computer next door to the kid's room or eventually at the smartphone to play it on a bluetooth speaker. That worked for the last few months but interest in "screens" in general is strong in the little one now and it just doesn't feel right as a process. I like the idea of having something "offline" like cassettes to choose from but still use today's streaming capabilities.
After deciding what I want to listen to, there should be no digital obstacles to start playing it. "Oh, look, 10 new emails on my phone".
The idea was born to use a physical thing that represents a playlist to play over the existing Sonos speakers. Requirements:
My initial ideas were around scanning bar- or QR-codes attached to something cassette-like but that also depend on the light situation. One could print the cover, add a barcode with a certain code. I went doing nothing for weeks or months eventually. The idea was just not ready yet.
Finally - and I'm by no means the first one with the idea - I learned that RFID readers are extremely cheap and white RFID plastic cards are even cheaper. A quick google search revealed that other people already had the idea playing music using RFID tags or cards. Awesome.
I'm naturally lazy. Also, I don't have too much spare time for projects like this. Family, work, sports, friends, travel. So, I have to find the easiest way possible and "reuse" existing stuff. It was clear from the beginning to use a streaming music service. For the ease of use I picked Spotify. Second, I already have Sonos speakers which offer a great API and I already played with the API in the past.
To summarize, the tech stack:
A quick search at Amazon revealed that there are masses of RFID hardware for Raspberry Pi and because I did some prototypes on that platform before I was thrilled that it could be a really easy approach and my brain got triggered with the low hanging fruit and not much effort ahead of me.
Late night shopping spree. As I really didn't have anything at home for that project I literally had to order everything from scratch – for example to solder some pins for the Raspberry Pi. Here's the shopping list:
Some days later all parts arrived and I began looking into the software part for the project before assembling the hardware entirely. For that I installed Raspbian on the SD card, booted the Raspberry Pi, connected my wifi and was good to go. To my surprise, playing with the Sonos API, I discovered that I don't even have to bother with any Spotify API at all by using the existing Spotify integration from the Sonos speakers.
There's a good PHP library to talk to Sonos directly. Using that, it turned out it's fairly easy to load a "Sonos Playlist" (Attention: not a Spotify playlist). This is even better than using a Spotify playlist. It removes the limitation of any specific source. A Sonos playlist could be anything. Music from Spotify, Amazon, your Smartphone, UPnP, a NAS, you name it. That approach ended up in a few lines of code and I could pass a playlist by name and Sonos would begin playing that playlist. As a benefit it would be super easy to identify a RFID card somehow and relate that to a playlist. The first part was fairly easy.
use duncan3dc\Sonos\Network; use Doctrine\Common\Cache\ArrayCache; $sonos = new Network(new ArrayCache); $controller = $sonos->getControllerByRoom($roomName); $playlist = $sonos->getPlaylistByName($playListName); $tracks = $playlist->getTracks(); $controller->getQueue()->clear(); $controller->getQueue()->addTracks($tracks); $controller->play();
Now, that the playlist playing part was solved I started with the unknown part of RFID hardware. First step assemble the hardware. The RFID module needed some soldering for the connecting pins. Luckily the RFID module fit into the Raspberry PI starter kit's enclosure. With the help of the plastic spacers everything is holding together nicely.
Do not forget: The SPI has to be enabled for the Raspberry Pi, otherwise you won't have any luck getting it running. And let me tell you, the error messages are everything but detailed what the issue is. The module took some more time, namely you need two pieces: SPI-py and MRC522-python. Two python modules that enable easy access polling the module.
git clone https://github.com/lthiery/SPI-Py.git cd SPI-Py sudo python setup.py install git clone https://github.com/mxgxw/MFRC522-python.git cd MFRC522-python
In the MFRC522 repository are some example how to access the RFID module for authentication, reading and writing. For the ease of this article, here's a simplified code snippet polling the RFID reader and upon receiving a card contact it will start the PHP script and pass the RFID card's ID.
import RPi.GPIO as GPIO import MFRC522 import signal import time import subprocess continue_reading = True def end_read(signal,frame): global continue_reading continue_reading = False GPIO.cleanup() signal.signal(signal.SIGINT, end_read) MIFAREReader = MFRC522.MFRC522() while continue_reading: (status,uid) = MIFAREReader.MFRC522_Anticoll() if status == MIFAREReader.MI_OK: subprocess.call([ "php", "/path/to/sonos.php", str(uid[0])+","+str(uid[1])+","+str(uid[2])+","+str(uid[3]) ]) time.sleep(0.1)
This script will call sonos.php and pass the card's UID. What's not included here but what I've done in PHP is initializing a SQLite database and insert the card's uid if it doesn't exist. If the uid already exists and is associated with a playlist name, the script with play that playlist.
At the same time the Raspberry Pi is running a little web interface showing all card uids and playlists. If you scan a new unknown card, it appears without playlist and will not trigger any play action until someone assigned a playlist.
Here's a shot demo video scanning cards and changing playlists.
Of course that works but it's only a proof of concept. I don't really like the look of the Raspberry Pi case, the bright red LEDs and especially the very high frequency sound the Raspberry Pi board seems to emit that might be caused in combination with CPU usage. You clearly can hear the polling interval of the RFID module if you're in a quite environment.
Additional to the shopping list above I've ordered
The card reader can be attached via USB and emulates an USB keyboard. It basically enters the card's uid followed by a carriage return. That is very nice. I could switch to a much smaller Raspberry Zero W and hide it completely and only expose the RFID reader somewhere.
First things first. It turns out that the USB RFID reader has a very loud and annoying speaker soldered on the board. Every time you scan a card it would beep. WTF? Removing the four soft pads at the bottom of the case reveals some screws to open it. It turned out, beside some option switches you cannot disable the beep. So, I removed the speaker from the board and it still works without a problem.
This time I was able to skip the python script entirely that was being used to communicate to the RFID module. The USB module is basically emulating a keyboard and sends information directly to the system's keyboard events. Depending on the USB port the device in /dev/input/ could be something else.
$fp = fopen('/dev/input/event0', 'rb'); $code = []; while ($buffer = fread($fp, 16)) { if (bufferTooSmall($buffer)) { continue; } $data = unpack('ltv_sec/ltv_usec/Stype/Scode/Ivalue', $buffer); if (!isValidKeyReleaseEvent($data)) { continue; } if ($this->codeIsComplete($data['code'])) { playPlaylistAttachedToCard($data['code']); $code = []; continue; } $code[] = getKeyFromKeyCode($data['code']); }
The keyboard data in linux arrives in it's very special "binary" format. What the script basically does is to capture all events, ignore everything that is not a "key up" event and concatenate the input until an ENTER code has been sent. When receiving ENTER, call a function that receives the card's UID and plays a playlist like the first solution at the beginning of this article.
The Raspberry Pi Zero W and the board of the USB RFID reader are mounted behind the device and connected with the micro-usb-to-mini-usb cable.
Additionally, I'm using a Text-to-Speech API to render error messages into audio and play that via Sonos in case something went wrong.
The Raspberry Pi also exposes an HTTP based project with a user interface to assign playlists to a card. The workflow is easy: Scan a new card, nothing happens and no audio will be played but the new card's UID appears in a database and the user interface allows to connect the card to a playlist available in Sonos. After connecting a card to a playlist and scanning that card again it would play the music.
Now, I have to print some covers on those RFID cards ...
Oh, find the source on github