Documentation Index Fetch the complete documentation index at: https://mintlify.com/benz206/SpotifyMacroBoard/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Spotify MacroBoard requires a personal server that acts as a proxy between the ESP32 and the Spotify Web API. This server handles OAuth authentication, token refresh, and API requests on behalf of the MacroBoard.
The reference implementation runs on benzhou.tech at port 443 (HTTPS). You’ll need to set up your own server with a similar configuration.
Why a proxy server?
The ESP32 has limited computational resources and memory, making it impractical to:
Implement OAuth 2.0 authentication flow
Store and refresh access tokens
Parse complex JSON responses from Spotify API
Handle API rate limiting and retries
The proxy server handles these complexities, allowing the ESP32 to make simple GET requests with minimal processing.
Server requirements
Domain and SSL certificate
Your server must be accessible via HTTPS with a valid SSL certificate:
Domain name : The ESP32 connects to a hardcoded domain (e.g., benzhou.tech)
SSL certificate : Required for secure HTTPS communication
Port 443 : Standard HTTPS port
The ESP32 validates the server’s SSL certificate against the benzServerCert constant in SampleCredentials.h. If you use a self-signed certificate, you must include the full certificate chain.
Obtaining your SSL certificate
After setting up your server with an SSL certificate, extract the certificate:
# Get the certificate from your server
openssl s_client -connect your-domain.com:443 -showcerts < /dev/null 2> /dev/null | \
openssl x509 -outform PEM > server-cert.pem
# View the certificate content
cat server-cert.pem
Copy the certificate content (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----) into your SampleCredentials.h file:
const char * benzServerCert =
"-----BEGIN CERTIFICATE----- \n "
"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh \n "
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 \n "
// ... rest of certificate ...
"-----END CERTIFICATE-----" ;
Spotify API setup
Before implementing your server, register your application with Spotify:
1. Create a Spotify app
Go to Spotify Developer Dashboard
Log in with your Spotify account
Click “Create app”
Fill in the app details:
App name : Spotify MacroBoard Server
App description : Personal proxy server for ESP32 MacroBoard
Redirect URI : https://your-domain.com/callback
Accept the terms and click “Create”
2. Get your credentials
After creating the app:
Click on your app in the dashboard
Go to “Settings”
Note your Client ID and Client Secret
These will be used in your server implementation
3. Required scopes
Your server needs to request the following OAuth scopes:
user-read-playback-state - Get current playback information
user-modify-playback-state - Control playback (play, pause, skip, etc.)
user-read-currently-playing - Get currently playing track
Server implementation
Your server must implement two endpoints that match the ESP32’s expectations:
Endpoint 1: Manage playback state
GET /api/manageState/{password}/{action}
This endpoint receives control commands from the MacroBoard and forwards them to Spotify.
Implementation requirements:
Validate the password parameter
Map the action to the appropriate Spotify API endpoint:
playPause → PUT /me/player/pause or /me/player/play
skip → POST /me/player/next
back → POST /me/player/previous
vinc → PUT /me/player/volume (increase by 10%)
vdec → PUT /me/player/volume (decrease by 10%)
shuffle → PUT /me/player/shuffle
loop → PUT /me/player/repeat
Make the authenticated request to Spotify API
Return success/failure status
Endpoint 2: Get current playback
GET /api/getCurrent/{password}
This endpoint returns the current playback state formatted for the ESP32.
Response format:
{
"title" : "Song Title" ,
"artist" : "Artist Name" ,
"album" : "Album Name" ,
"duration" : 240 ,
"progress" : 45 ,
"paused" : false ,
"volume" : 75 ,
"color" : [ 255 , 87 , 34 ]
}
Artist name (first artist if multiple)
Track duration in seconds
Current playback position in seconds
Whether playback is currently paused
Current volume level (0-100)
RGB color array extracted from album artwork [R, G, B] where each value is 0-255
The color field provides an RGB array that represents the dominant color from the album artwork. The ESP32 uses this to set the RGB LED strip color, creating ambient lighting that matches the current track.
Implementation approaches:
Image processing library : Use a library like Pillow (Python) or Sharp (Node.js) to analyze the album art
Color quantization : Extract the dominant color using k-means clustering or similar algorithms
Caching : Cache colors by album ID to avoid reprocessing
Example implementation (Python with Pillow):
from PIL import Image
import requests
from io import BytesIO
import colorsys
def get_dominant_color ( image_url ):
response = requests.get(image_url)
img = Image.open(BytesIO(response.content))
img = img.resize(( 150 , 150 )) # Resize for faster processing
# Get color palette
colors = img.getcolors( 150 * 150 )
# Find most common color
dominant = max (colors, key = lambda x : x[ 0 ])[ 1 ]
return list (dominant)[: 3 ] # Return [R, G, B]
Example server (Node.js)
Here’s a basic implementation using Node.js and Express:
const express = require ( 'express' );
const axios = require ( 'axios' );
const app = express ();
const PASSWORD = 'your-password-here' ;
let accessToken = 'your-spotify-access-token' ;
let refreshToken = 'your-spotify-refresh-token' ;
// Middleware to validate password
function validatePassword ( req , res , next ) {
if ( req . params . password !== PASSWORD ) {
return res . status ( 401 ). json ({ error: 'Invalid password' });
}
next ();
}
// Manage playback state
app . get ( '/api/manageState/:password/:action' , validatePassword , async ( req , res ) => {
const { action } = req . params ;
try {
let endpoint , method = 'PUT' , body = {};
switch ( action ) {
case 'playPause' :
// Check current state and toggle
const state = await getCurrentPlayback ();
endpoint = state . is_playing ? 'pause' : 'play' ;
break ;
case 'skip' :
endpoint = 'next' ;
method = 'POST' ;
break ;
case 'back' :
endpoint = 'previous' ;
method = 'POST' ;
break ;
case 'vinc' :
const currentVol = await getCurrentVolume ();
endpoint = 'volume' ;
body = { volume_percent: Math . min ( 100 , currentVol + 10 ) };
break ;
case 'vdec' :
const currentVol2 = await getCurrentVolume ();
endpoint = 'volume' ;
body = { volume_percent: Math . max ( 0 , currentVol2 - 10 ) };
break ;
case 'shuffle' :
endpoint = 'shuffle' ;
body = { state: true };
break ;
case 'loop' :
endpoint = 'repeat' ;
body = { state: 'track' };
break ;
}
await axios ({
method ,
url: `https://api.spotify.com/v1/me/player/ ${ endpoint } ` ,
headers: { 'Authorization' : `Bearer ${ accessToken } ` },
data: body
});
res . json ({ success: true });
} catch ( error ) {
res . status ( 500 ). json ({ error: error . message });
}
});
// Get current playback
app . get ( '/api/getCurrent/:password' , validatePassword , async ( req , res ) => {
try {
const response = await axios . get ( 'https://api.spotify.com/v1/me/player' , {
headers: { 'Authorization' : `Bearer ${ accessToken } ` }
});
const data = response . data ;
const color = await getDominantColor ( data . item . album . images [ 0 ]. url );
res . json ({
title: data . item . name ,
artist: data . item . artists [ 0 ]. name ,
album: data . item . album . name ,
duration: Math . floor ( data . item . duration_ms / 1000 ),
progress: Math . floor ( data . progress_ms / 1000 ),
paused: ! data . is_playing ,
volume: data . device . volume_percent ,
color: color
});
} catch ( error ) {
res . status ( 500 ). json ({ error: error . message });
}
});
app . listen ( 443 , () => {
console . log ( 'Server running on port 443' );
});
This is a simplified example. A production server should include:
Automatic token refresh logic
Error handling and retries
Rate limiting
Logging and monitoring
Environment variables for credentials
Token refresh
Spotify access tokens expire after 1 hour. Implement automatic token refresh:
async function refreshAccessToken () {
const response = await axios . post ( 'https://accounts.spotify.com/api/token' ,
new URLSearchParams ({
grant_type: 'refresh_token' ,
refresh_token: refreshToken ,
client_id: CLIENT_ID ,
client_secret: CLIENT_SECRET
})
);
accessToken = response . data . access_token ;
// Schedule next refresh before expiration
setTimeout ( refreshAccessToken , 55 * 60 * 1000 ); // 55 minutes
}
Testing your server
Before connecting the ESP32, test your endpoints:
# Test getCurrent endpoint
curl https://your-domain.com/api/getCurrent/your-password
# Test playPause
curl https://your-domain.com/api/manageState/your-password/playPause
# Test skip
curl https://your-domain.com/api/manageState/your-password/skip
Expected response from getCurrent:
{
"title" : "Bohemian Rhapsody" ,
"artist" : "Queen" ,
"album" : "A Night at the Opera" ,
"duration" : 354 ,
"progress" : 120 ,
"paused" : false ,
"volume" : 80 ,
"color" : [ 45 , 52 , 71 ]
}
Updating ESP32 configuration
After setting up your server, update your SampleCredentials.h file:
const String PASSWORD = "your-secure-password" ;
const char SSID[] = "Your-WiFi-SSID" ;
const char SSID_PASS[] = "your-wifi-password" ;
const char * benzServerCert =
"-----BEGIN CERTIFICATE----- \n "
"YOUR-CERTIFICATE-CONTENT-HERE \n "
"-----END CERTIFICATE-----" ;
If you’re using a different domain, you’ll also need to modify the hardcoded domain in src.ino:195 and src.ino:210:
if ( ! wifiClient . connect ( "your-domain.com" , 443 )) {
Next steps
API endpoints Detailed reference for all API endpoints
Troubleshooting Common issues and solutions