Un experimento con Raspberry Pi y NFC para representar discos mediante tarjetas físicas. Elijo una tarjeta, la acerco al lector y el álbum empieza a sonar: la propia Raspberry Pi está dada de alta como altavoz de Spotify. La misma tarjeta puede apuntar a un audiolibro de mi servidor AudiobookShelf, y en ese caso el libro continúa por donde lo dejé.
Es un prototipo personal que fue creciendo, no la ficha de un producto. Empezó como excusa para trastear con NFC y ha acabado con aplicación web, servidor propio y una matriz de LED que se ha convertido en la única interfaz del aparato.
Qué es
Cada tarjeta NFC tiene un identificador (UID) que el lector PN532 lee al acercarla. El programa busca ese UID primero en una base de datos local SQLite, donde está guardado qué contenido le corresponde. Si es música, ordena por la API web de Spotify que ese álbum o esa canción suene en el aparato; si es un audiolibro, pide el contenido a AudiobookShelf y lo reproduce con mpv. El audio sale por un altavoz Bluetooth.
Mientras no suena nada, la matriz de LED muestra la hora; cuando suena algo, muestra el título en desplazamiento. En el repositorio y en el código el proyecto aparece como MusicBox y como Rockola; este último es también el nombre con el que el aparato se ve como altavoz dentro de Spotify.
Por qué lo construí
Quería que poner música fuese un gesto físico: coger una tarjeta con un disco y acercarla, sin abrir el móvil ni buscar nada en una aplicación. En la aplicación web de tarjetas ese objeto se llama «Digital Vinyls» y la pantalla de bienvenida invita a «pasar tus tarjetas físicas para controlar tu música».
Añadí los audiolibros porque ya tenía una biblioteca propia en AudiobookShelf y me interesaba que el mismo gesto sirviera para volver a un libro por donde iba, sin depender del móvil.
Qué hace hoy
Reproducción. Lee tarjetas NFC y reproduce álbumes y canciones de Spotify o audiolibros de AudiobookShelf. No vuelve a leer la misma tarjeta si se queda apoyada en el lector. Al pasar de un audiolibro a música detiene el libro, y al revés pausa Spotify. Si la tarjeta es desconocida, lo comunica al servidor y lo indica en la pantalla.
Audiolibros con memoria. Antes de reproducir pregunta a AudiobookShelf por dónde iba y arranca mpv desde ese punto. Mientras suena informa del progreso cada unos 20 segundos y marca el libro como terminado si se acaba, así que el avance queda sincronizado con el resto de clientes de AudiobookShelf.
Pantalla de LED. Es la única interfaz del aparato. En reposo alterna la hora y un símbolo de infinito dibujado punto a punto; cuando suena algo muestra «título – artista» o «título – autor» desplazándose. Un hilo consulta a Spotify cada 5 segundos qué está sonando, de modo que también refleja lo que se ponga desde el móvil en ese dispositivo. Durante el arranque va mostrando mensajes: «Booting…», «Wifi OK», «Connecting Speaker…», «Speaker OK», «Loading App…».
Arranque. Al encender espera a tener red, intenta conectar el altavoz Bluetooth y luego lanza el programa. Dos servicios de systemd, el reproductor y Librespot, se reinician solos si fallan. Hay además un script de despliegue y otro de verificación.
Aplicación web de tarjetas. Muestra una cuadrícula con la portada y el nombre de cada tarjeta, y si es de Spotify o de audiolibro. Cuando el aparato lee una tarjeta nueva aparece una ventana con el UID y el formulario para asociarle contenido. Al guardar, la aplicación consulta Spotify o AudiobookShelf para traer el nombre y la portada reales. Al pulsar una tarjeta se ve la lista de canciones del álbum, o los capítulos del audiolibro con sus duraciones.
Cómo está hecho
El aparato. Una Raspberry Pi Zero 2W con un lector PN532 por I2C, una matriz MAX7219 de cuatro módulos de 8×8 encadenados por SPI (32×8 puntos) y un altavoz Bluetooth. El programa principal, en Python, usa varios hilos: uno lee el NFC, otro dibuja en la pantalla, otro consulta a Spotify qué suena y otro sincroniza las tarjetas con el servidor. Se apoya en adafruit_pn532, luma.led_matrix y spotipy. Librespot convierte la Raspberry Pi en un altavoz Spotify Connect y mpv reproduce los audiolibros, controlado por su socket IPC para saber por qué segundo va.
El servidor. En mi red doméstica tengo PocketBase como base de datos y API, con colecciones para tarjetas, dispositivos y registros de lecturas, junto a la instancia de AudiobookShelf que ya usaba.
La aplicación web. React 19 con Vite y Tailwind CSS, usando el SDK de PocketBase. La identidad visual es sencilla: fondo casi negro, tarjetas gris oscuro y naranja como color de acento.
Decisiones que me costaron
Caché local antes que nube. Al leer una tarjeta, el aparato mira primero en SQLite y solo si no la encuentra pregunta al servidor. Así las tarjetas ya conocidas no dependen de la red.
Dar de alta tarjetas sin interfaz en el aparato. El aparato no tiene forma de asignar contenido por sí mismo. Cuando lee una tarjeta desconocida escribe un registro de esa lectura en PocketBase; la aplicación web está suscrita a esa colección en tiempo real y abre la ventana «New Vinyl Detected» con el UID. Al principio esto se hacía con un script de terminal en el que pegabas la URI a mano.
Separar qué suena de dónde suena. Librespot convierte la Raspberry Pi en altavoz, y el programa en Python usa la API de Spotify únicamente para dar órdenes. Entender que son dos piezas distintas, y que Librespot no se configura con el token de la API, me llevó a escribir un documento entero para aclarármelo.
Del modo manual al automático. En la primera versión había que abrir Spotify en el móvil y elegir el dispositivo antes de que la tarjeta hiciera nada. Después empecé a guardar la autorización de Spotify asociada al aparato para que él mismo busque su dispositivo y lance la reproducción sin tocar el móvil. Está en desarrollo: el código existe, pero no está terminado.
Dibujar en 32×8 píxeles. El símbolo de infinito está definido como una lista de coordenadas, y mostrar texto en una pantalla tan pequeña obliga a desplazarlo.
Lo que está a medias
Varias cosas funcionan solo en parte. La búsqueda de tarjetas en la nube, cuando la tarjeta no está en la caché local, tiene un fallo y no devuelve el resultado. La sincronización con el servidor está documentada como periódica, pero en el código se hace una sola vez al arrancar. Los audiolibros de varios archivos solo reproducen el primero. El panel de configuración del aparato está en desarrollo, y de momento guarda los cambios en el navegador y no en la Raspberry Pi. También hay ideas apuntadas sin empezar, como las listas de reproducción desde la aplicación web.
No me molesta dejarlo escrito así. La diferencia entre lo que dice la documentación y lo que hace el código es, en este proyecto, la parte de la que más he aprendido.
