Raspberry Pi Gramophone
I wanted to recreate the experience of browsing through a physical album collection, while still utilising modern technology. Realistically, I wasn't going to purchase a gramophone and hunt down music records, so I decided to build a hybrid solution with my Raspberry Pi and Spotify's API.
The RFID discs represent the albums, and tapping them on the gramophone device plays the respective album on Spotify on any smart device (such as your TV).
Supplies
- Raspberry Pi Zero W
- EM4100 USB RFID Card Reader
- RFID discs
- WiFi dongle (to run wireless)
- Portable mobile charger (to run wireless)
You can also use any type of RFID tags or cards, as long as they are of the same frequency as the RFID reader (125 kHz in my case). I used RFID tags that looked like little record discs for thematic purposes.
Set Up Raspberry Pi
I personally use this headless setup, but any standard Raspberry Pi setup should work fine. Attach the WiFi dongle for wireless internet connection. Once complete, you should also install Python 3 if not already preinstalled.
Make sure your RFID reader is working. Attach the USB RFID reader and tap one of the RFID tags on it. If all goes well, there should be some feedback in the form of either a beep or the device lighting up, depending on your model. You can also attach the reader to a regular computer to test it.
Set Up Spotify API
Go to the Spotify Developers page and press 'Create app'. Fill in the app name and description, and use http://localhost (or whatever you want) for Redirect URIs. Note down the Client ID and Client secret from the dashboard.
To allow your Raspberry Pi to use your Spotify API, you will need to complete the Authorization flow. You can also use spotipy to complete your authorization, as we will be using the same library later.
You can run this basic script to make sure the authorization works, and that you are able to send the command to play a specific album (the following example plays the album "Siamese Dream") on your device. If you want it to play on a specific device associated with your Spotify account, you will have to indicate the device ID, which can be found by calling the Get Available Devices API. Otherwise, you can omit that parameter and it will simply play on the device that Spotify is currently playing from.
import spotipy
from spotipy.oauth2 import SpotifyOAuth
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
redirect_uri="http://localhost",
scope="user-modify-playback-state",
open_browser=False,
cache_path='./tokens.txt'
))
// Play an album
sp.start_playback(context_uri='spotify:album:2Qs2SpclDToB087fLolhCN', device_id=DEVICE_ID)
Read RFID Input Stream
Every RFID tag is assigned a random unique ID. We need to be able to parse its input in order to distinguish between them.
For testing the input stream, I slightly modified some code that I found online to fit this particular use case. You might have to do some trial-and-error with the USB port interface to find the right one, and modify some of the variables depending on your device. If working, you should see some output being printed when you scan an RFID tag.
import usb.core
import usb.util
import time
import RPi.GPIO as GPIO
USB_IF = 0 # Interface
USB_TIMEOUT = 5 # Timeout in MS
USB_VENDOR = 0xffff # Vendor-ID:
USB_PRODUCT = 0x0035 # Product-ID
# Find the HID device by vender/product ID
dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
# Get and store the endpoint
endpoint = dev[0][(0,0)][0]
if dev.is_kernel_driver_active(USB_IF) is True:
try:
dev.detach_kernel_driver(USB_IF)
except:
sys.exit("Could not detatch kernel driver from interface({0}): {1}".format(USB_IF, str(e)))
# Claim the device
usb.util.claim_interface(dev, USB_IF)
# Configure the Raspberry Pi GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(11, GPIO.OUT)
receivedNumber = 0
control = None
# Read input
while True:
try:
# Read a character from the device
control = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, USB_TIMEOUT)
print(control)
except KeyboardInterrupt:
GPIO.cleanup()
except Exception as e:
pass
time.sleep(.01) # Let CTRL+C actually exit
An example of the output is displayed in the attached screenshot. For this project, I didn't really care about parsing the bytes properly, as I just needed a unique identifier for each tag. Hence, I just took the non-zero values and concatenated them together to form an ID.
You might have to make more adjustments based on the device/tags that you have.
1) My RFID tags had a length of 22: segment the string once an appropriate length has been read.
2) The input of the RFID tag sometimes gets transposed (ie. 23325 gets read as 32523): compare the sorted string (ie. 23325 -> 22335) instead to preserve the match.
Putting Everything Together
You will need to prepare a JSON file in the following format with your Spotify details, and name it data.json.
{
"spotify": {
"client_id": <CLIENT_ID>,
"client_secret": <CLIENT_SECRET>,
"device_id": <DEVICE_ID>,
"albums": [
{
"title" : "Rumours",
"rfid": "3939393437303837333240",
"album_id": "1bt6q2SruMsBtcerNVtpZB"
},
{
"title" : "Since I Left You",
"rfid": "3939393437323736333940",
"album_id": "3GBnNRYsxBfEeMSMmTpJ25"
},
{
"title" : "Teen Dream",
"rfid": "3939393437323930343140",
"album_id": "51AxfjN2gEt5qeJqPY5w0e"
},
...
]
}
}
The album entries simply maps an RFID tag to the specific album ID to be played. The easiest way to get the ID of a Spotify album is to just extract it from the end of its Spotify Web URL. For example, the URL for The Colour And The Shape is https://open.spotify.com/album/30ly6F6Xl0TKmyBCU50Khv, thus its ID is 30ly6F6Xl0TKmyBCU50Khv.
The following is the code I used to put everything together, I have also attached the file here.
import usb.core
import usb.util
import time
import json
import RPi.GPIO as GPIO
import spotipy
from spotipy.oauth2 import SpotifyOAuth
data = json.load(open('data.json', 'r', encoding='utf-8'))
def main():
USB_IF = 0 # Interface
USB_TIMEOUT = 5 # Timeout in MS
USB_VENDOR = 0xffff # Vendor-ID:
USB_PRODUCT = 0x0035 # Product-ID
# Find the HID device by vender/product ID
dev = usb.core.find(idVendor=USB_VENDOR, idProduct=USB_PRODUCT)
# Get and store the endpoint
endpoint = dev[0][(0,0)][0]
if dev.is_kernel_driver_active(USB_IF) is True:
try:
dev.detach_kernel_driver(USB_IF)
except:
sys.exit("Could not detatch kernel driver from interface({0}): {1}".format(USB_IF, str(e)))
# Claim the device
usb.util.claim_interface(dev, USB_IF)
# Configure the Raspberry Pi GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(11, GPIO.OUT)
receivedNumber = 0
control = None
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
client_id=data["spotify"]["client_id"],
client_secret=data["spotify"]["client_secret"],
redirect_uri="http://localhost",
scope="user-modify-playback-state",
open_browser=False,
cache_path='./tokens.txt'
))
input_str = ""
while True:
try:
# Read a character from the device
control = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize, USB_TIMEOUT)
input_substr = get_input_substr(control)
input_str += input_substr
if len(input_str) == 22:
print("Input: " + input_str)
album_id = get_album_id(input_str)
if album_id:
print("Album ID: " + album_id)
play_album(album_id)
input_str = ""
except KeyboardInterrupt:
GPIO.cleanup()
except Exception as e:
pass
time.sleep(.01) # Let CTRL+C actually exit
def get_input_str(input):
for n in input:
if n != 0:
return str(n).zfill(2)
return ""
def get_album_id(input_str):
# Fetch the corresponding album ID based on the RFID tag
input_str = sorted(input_str)
return data["spotify"]["albums"].get(input_str)
def play_album(album_id):
# Play album using Spotify API
sp.start_playback(context_uri='spotify:album:' + album_id, device_id=data["spotify"]["device_id"])
main()
Lastly, you can make your Raspberry Pi run this python script on startup, so you don't have to SSH in to run it everytime you restart the device. There are many ways to do this, but I personally chose to set up a systemd service.
Downloads
Final Touches
Since the device is already functional, this part is purely cosmetic. I wanted the whole device to be standalone and wireless, so I used a mobile charger as a power supply, encased everything in a wooden box, and taped the RFID reader to the top interior, so that the RFID tags can be read by tapping the top of the box. The 'horn' of the gramophone was 3D printed (model file attached).
Initially, I wanted to print out the album art and stick them onto the RFID tags for labelling. However, the hand-drawn placeholder art that I used eventually grew on me, and I decided it would be funny to continue using it.