Save Songs to Spotify

Update for the ESP32 Internet Radio: Save Songs to Spotify

Have you ever heard a song on the radio that you wanted to remember so you could listen to it later on Spotify? Perhaps you relied on your memory or used pen and paper. But there’s a more elegant solution:

In this project, you’ll build an extension for the ESP32 Internet radio that allows you to save the currently playing song to your Spotify favorites with just the press of a button.

Listen to our Google-powered Podcast to learn more about this project:

For this update you’ll need:

  • Raspberry Pi Zero W or a standard Raspberry Pi plus a micro-SD card

Since you’ll be programming your Raspberry Pi via SSH (more on this later), you’ll need another computer where you can use a console or terminal. The software for the Internet radio is written in Python – an editor like Visual Studio Code would be ideal, but a simple text editor will also work. The best way to create the SD card with the operating system for the Raspberry Pi is with the free Raspberry Pi Imager tool.

Preparing the Raspberry Pi

Before connecting your first component, you need to prepare your Raspberry Pi by installing the operating system and the required Python packages and libraries. Let’s go through this step by step:

Installing the Operating System

To configure the operating system and write it to an SD card, proceed as follows:

  1. Download the Raspberry Pi Imager from the official website
  2. Launch the Imager and select your model (in this project, that’s a Raspberry Pi Zero 2) and “Raspberry Pi OS Lite (64-bit)”. You’ll find this under Raspberry Pi OS (other) and it comes without a graphical interface, as we don’t need it.
  3. Select your SD card as the destination
Configuring the Raspberry Pi Imager
  1. Click on Edit Settings in the next screen and:
    • Set a username and password
    • Configure your Wi-Fi (SSID and password)
    • Activate SSH under the Services tab (use password for authentication)
  2. Confirm your settings with a click on Yes and write the image to the SD card

Connect to the Raspberry Pi via SSH

Once the Raspberry Imager is finished, insert the SD card into the corresponding slot on the Raspberry Pi and start it up. Now you’ll need some patience – the first start with the new operating system can take a few minutes. Open the terminal or console on your computer and connect with the following command – replacing “pi” with the username you set previously in the Raspberry Pi Imager.

sudo ssh pi@raspberrypi.local

Once your Raspberry Pi is ready, you’ll be prompted to enter the password you set in the Pi Imager twice.

Install the Required Packages

Now you can install the packages and libraries you need for the Internet radio. But first, let’s update the operating system:

sudo apt update
sudo apt upgrade

Then you need Pip, which you’ll use to install the library you’ll use with Spotify.

sudo apt install python3-pip

Setting up a Virtual Environment

With the introduction of the Bookworm operating system, it became necessary to install Python libraries in a virtual environment. By installing them in a protected area, the system-wide Python installation is prevented from being modified. To set up a virtual environment, use these commands:

sudo apt install python3-venv
python3 -m venv RadioSpotify --system-site-packages

Unfortunately, you need to reactivate the virtual environment every time you restart your Raspberry Pi. Later in the project, you’ll automate this, but for now, you’ll need to do it manually. You can do this with this command:

source RadioSpotify/bin/activate

By the way, you can deactivate it simply with the command deactivate. Now that your virtual environment is running, you can continue with installing the following Python library:

pip3 install spotipy

Preparing Spotify

For your ESP32 Internet radio or Raspberry Pi to search for a song on Spotify and add it to your playlist, you need to create your own app there. This is quickly set up:

  1. Create a free developer account at https://developer.spotify.com
  2. Create a new app in the Spotify Developer Dashboard:
Creating a new Spotify App

You can enter a name and a short description of your choice in the App name and App description fields. However, you must enter the address http://localhost:8080 in the Redirect URIs field. Then click on Save.

Now click on Settings on the next screen. There you’ll find your Client ID and Client Secret. You’ll need both keys in the Python script for the Raspberry Pi.

Settings in the Spotify Developer Account

And that’s it for now. Later, you’ll authenticate your Raspberry Pi with Spotify to finally establish the connection between the two.

The Python Script for the Radio

The code for the Raspberry Pi Internet radio is as follows. Copy it and create a new script called radiospotify.py

___STEADY_PAYWALL___

import sys
import os
import fcntl
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import time
import json
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import logging
from http.server import HTTPServer, BaseHTTPRequestHandler
import webbrowser
# Set up logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
# Spotify API credentials
SPOTIPY_CLIENT_ID = 'your_client_id'
SPOTIPY_CLIENT_SECRET = 'your_client_secret'
SPOTIPY_REDIRECT_URI = 'http://localhost:8080'
SCOPE = 'user-library-modify'
# Define cache path in user's home directory
CACHE_PATH = os.path.expanduser('~/.spotify_token_cache')
# Global variable to store the authentication code
auth_code = None
class AuthHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        global auth_code
        query_components = parse_qs(urlparse(self.path).query)
        if "code" in query_components:
            auth_code = query_components["code"][0]
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(b"Authentication successful! You can close this window.")
            logging.info("Received authentication code")
        else:
            self.send_response(400)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(b"Authentication failed! No code received.")
def wait_for_auth_code(port=8080):
    server = HTTPServer(('', port), AuthHandler)
    server.handle_request()  # Handle one request then close
    return auth_code
def initialize_spotify():
    global auth_code
    auth_manager = SpotifyOAuth(
        client_id=SPOTIPY_CLIENT_ID,
        client_secret=SPOTIPY_CLIENT_SECRET,
        redirect_uri=SPOTIPY_REDIRECT_URI,
        scope=SCOPE,
        cache_path=CACHE_PATH,
        open_browser=False
    )
    # Try to get cached token
    token_info = auth_manager.get_cached_token()
    if not token_info or auth_manager.is_token_expired(token_info):
        auth_url = auth_manager.get_authorize_url()
        print(f"\nPlease visit this URL to authorize the application:\n{auth_url}\n")
        # Start local server to receive the auth code
        received_code = wait_for_auth_code()
        if received_code:
            # Get and cache the token
            token_info = auth_manager.get_access_token(received_code)
            logging.info("New authentication token obtained and cached")
        else:
            logging.error("No authentication code received")
            return None
    return spotipy.Spotify(auth_manager=auth_manager)
class SpotifyServerHandler(BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        logging.info(f"{self.client_address[0]}:{self.client_address[1]} - {format%args}")
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        parsed_path = urlparse(self.path)
        params = parse_qs(parsed_path.query)
        logging.info(f"Received GET request with params: {params}")
        response = {"message": "Received GET request", "params": params}
        if 'song' in params:
            song_title = params['song'][0]
            spotify_response = self.save_to_spotify(song_title)
            response.update(spotify_response)
        self.wfile.write(json.dumps(response).encode())
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length).decode('utf-8')
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        logging.info(f"Received POST data: {post_data}")
        params = parse_qs(post_data)
        response = {"message": "Received POST request", "data": params}
        if 'song' in params:
            song_title = params['song'][0]
            spotify_response = self.save_to_spotify(song_title)
            response.update(spotify_response)
        self.wfile.write(json.dumps(response).encode())
    def save_to_spotify(self, song_title):
        global sp
        if sp is None:
            logging.error("Spotify client is not initialized")
            return {
                "spotify_status": "error",
                "message": "Spotify client is not initialized"
            }
        logging.info(f"Attempting to save song: {song_title}")
        try:
            # Search for the track
            results = sp.search(q=song_title, type='track', limit=1)
            if results['tracks']['items']:
                track = results['tracks']['items'][0]
                # Save the track to the user's library
                sp.current_user_saved_tracks_add(tracks=[track['id']])
                logging.info(f"Successfully saved track: {track['name']} by {track['artists'][0]['name']}")
                return {
                    "spotify_status": "success",
                    "saved_track": f"{track['name']} by {track['artists'][0]['name']}"
                }
            else:
                logging.warning(f"Track not found on Spotify: {song_title}")
                return {
                    "spotify_status": "not_found",
                    "message": f"Track not found on Spotify: {song_title}"
                }
        except Exception as e:
            logging.error(f"An error occurred while saving to Spotify: {e}")
            return {
                "spotify_status": "error",
                "message": f"An error occurred: {str(e)}"
            }
def run_server(port=8080):
    server_address = ('', port)
    try:
        httpd = HTTPServer(server_address, SpotifyServerHandler)
        logging.info(f"Server running on port {port}")
        httpd.serve_forever()
    except OSError as e:
        if e.errno == 98:
            logging.error(f"Error: Port {port} is already in use. Try a different port.")
        else:
            logging.error(f"Error: {e}")
        sys.exit(1)
if __name__ == '__main__':
    lock_file = '/tmp/spotify_server.lock'
    try:
        lock_handle = open(lock_file, 'w')
        fcntl.lockf(lock_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:
        logging.error("Another instance of this script is already running.")
        sys.exit(1)
    try:
        # Initialize Spotify client
        sp = initialize_spotify()
        if not sp:
            logging.error("Failed to initialize Spotify client")
            sys.exit(1)
        port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
        run_server(port)
    except KeyboardInterrupt:
        logging.info("\nServer stopped.")
    finally:
        fcntl.lockf(lock_handle, fcntl.LOCK_UN)
        lock_handle.close()
        os.unlink(lock_file)

In this script, you need to enter the following two keys at the top – you can find these in your Spotify Developer Account.

SPOTIPY_CLIENT_ID = 'your_client_id'SPOTIPY_CLIENT_SECRET = 'your_client_secret'

And that’s it for the adjustments to the Python script. If you’re still logged into your Raspberry Pi in the terminal, log out with logout and navigate to the folder where your recently created Python script is located.

Once you’re in the directory, run the following command:

scp radiospotify.py pi@raspberrypi.local:/home/pi/RadioSpotify/

Running the Code

Now that the code is on your Raspberry Pi, you can run it. First, log in via SSH again (replace “pi” with your username):

sudo ssh pi@raspberrypi.local

Then you can start the script as follows:

source RadioSpotify/bin/activate
python3 RadioSpotify/radiospotify.py

Next, you need to give your Raspberry Pi access to your Spotify account. This is a bit cumbersome – but fortunately you only need to do it once. The corresponding token will be stored on your Raspberry Pi, so you won’t need to authenticate again the next time you start the script.

After starting the script, you’ll be prompted in the terminal to open an address in a browser – you can do this in a browser of your choice on your computer. Spotify will ask if it can connect to your app – agree to this. You should then see something like “Website not available” in the browser, but a different URL in the address bar.

Copy the entire new URL from the address bar, open a new terminal window on your computer, log in via SSH to your Raspberry Pi there too, and use the following command:

curl "YOUR COPIED URL"

Now you should see the info “Authentication successful!” in this terminal window and the line “INFO – Server running on port 8080” in your first window. This means that the login to Spotify worked and your Raspberry Pi is now ready to receive songs from the ESP32 Internet radio and save them to your Spotify playlist. Let’s continue with your ESP32.

Another Cable on the ESP32

To send songs to Spotify, you need a button to trigger this function. Conveniently, your Rotary Encoder already has one – you can not only turn it but also press it audibly. This press is read at the SW pin. Therefore, connect this pin to GPIO 7 of your ESP32 – if you’re using an ESP32-S3 Zero, the new connection looks like this (the yellow cable is the connection from the button to the ESP32):

diagram on how to connect the rotary button to the ESP32

You can of course use a different pin on the ESP32, but you’ll need to change this accordingly in the following sketch.

Update the Sketch on the ESP32

Your ESP32 Internet radio now only needs the function to send the current song to the Raspberry Pi and have it saved to your Spotify favorites. This requires a modification – here’s the updated sketch:

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Audio.h>
#include <AiEsp32RotaryEncoder.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoJson.h>

// Pin definitions remain unchanged
#define I2S_DOUT 2
#define I2S_BCLK 3
#define I2S_LRC 4
#define VOLUME_PIN 5

#define ROTARY_ENCODER_A_PIN 12
#define ROTARY_ENCODER_B_PIN 13
#define ROTARY_ENCODER_BUTTON_PIN 7
#define ROTARY_ENCODER_STEPS 4

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET     -1
#define SCREEN_ADDRESS 0x3C

#define I2C_SDA 8
#define I2C_SCL 9

// Debug Level for ESP32
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE

// Watchdog Timeout
const int wdtTimeout = 5000;  // 5 seconds Watchdog Timeout

// Initialization Flags
bool isWiFiConnected = false;
bool isDisplayInitialized = false;
bool isAudioInitialized = false;

AiEsp32RotaryEncoder rotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, -1, ROTARY_ENCODER_STEPS);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

Audio audio;

// WiFi credentials
const char ssid[] = "YOUR_WIFI_NETWORK";
const char password[] = "YOUR_WIFI_PASSWORD";

// Spotify server details
const char* serverName = "IP-ADDRESS:8080";

// Radio stations remain unchanged
const char* stations[] = {
    "http://www.byte.fm/stream/bytefm.m3u",
    "https://st01.sslstream.dlf.de/dlf/01/128/mp3/stream.mp3",
    "https://frontend.streamonkey.net/fho-schwarzwaldradiolive/mp3-stream.m3u",
    "https://kexp-mp3-128.streamguys1.com/kexp128.mp3",
    "https://eagle.streemlion.com:2199/tunein/psychedelicj.asx"
};
const char* stationNames[] = {
    "Byte.fm",
    "Deutschlandfunk",
    "Schwarzwaldradio",
    "KEXP",
    "Psychedelic Jukebox"
};
const int NUM_STATIONS = sizeof(stations) / sizeof(stations[0]);
int currentStation = 0;

// Static buffers instead of dynamic strings
char streamTitle[64] = "";
char urlBuffer[256] = "";
char jsonBuffer[512] = "";

// Volume control variables remain unchanged
const int SAMPLES = 5;
int volumeReadings[SAMPLES];
int readIndex = 0;
int total = 0;
int average = 0;
unsigned long lastVolumeCheck = 0;
const unsigned long VOLUME_CHECK_INTERVAL = 500;

void IRAM_ATTR readEncoderISR() {
    rotaryEncoder.readEncoder_ISR();
}

// Optimized string replacement function with static buffer
void replaceSpecialChars(const char* input, char* output, size_t outputSize) {
    size_t i = 0, j = 0;
    while (input[i] && j < outputSize - 1) {
        char c = input[i++];
        switch (c) {
            case 'ä': memcpy(&output[j], "a", 1); j += 1; break;
            case 'ö': memcpy(&output[j], "o", 1); j += 1; break;
            case 'ü': memcpy(&output[j], "u", 1); j += 1; break;
            case 'Ä': memcpy(&output[j], "A", 1); j += 1; break;
            case 'Ö': memcpy(&output[j], "O", 1); j += 1; break;
            case 'Ü': memcpy(&output[j], "U", 1); j += 1; break;
            case 'ß': memcpy(&output[j], "ss", 2); j += 2; break;
            default: output[j++] = c;
        }
    }
    output[j] = '\0';
}

void setup() {
    Serial.begin(115200);
    
    // Set debug level
    esp_log_level_set("*", ESP_LOG_VERBOSE);
    
    Serial.println(F("ESP32-S3 Internet Radio starting..."));
    Serial.printf("Initial free heap: %d bytes\n", ESP.getFreeHeap());
    
    // Basic setup
    pinMode(VOLUME_PIN, INPUT);
    
    // Encoder Setup
    rotaryEncoder.begin();
    rotaryEncoder.setup(readEncoderISR);
    rotaryEncoder.setBoundaries(0, NUM_STATIONS - 1, true);
    rotaryEncoder.setAcceleration(0);
    
    // Initialize volume readings
    for (int i = 0; i < SAMPLES; i++) {
        volumeReadings[i] = 0;
    }
    
    // Wire begin - basic I2C initialization
    Wire.begin(I2C_SDA, I2C_SCL);
}

void loop() {
    static unsigned long lastInitAttempt = 0;
    const unsigned long initInterval = 5000;
    
    // Heap monitoring
    static unsigned long lastHeapCheck = 0;
    if (millis() - lastHeapCheck > 10000) {  // Every 10 seconds
        Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
        lastHeapCheck = millis();
    }
    
    // Delayed initialization
    if (!isDisplayInitialized && millis() - lastInitAttempt > initInterval) {
        initializeDisplay();
        lastInitAttempt = millis();
    }
    
    if (!isWiFiConnected && millis() - lastInitAttempt > initInterval) {
        connectToWiFi();
        lastInitAttempt = millis();
    }
    
    if (isWiFiConnected && !isAudioInitialized && millis() - lastInitAttempt > initInterval) {
        initializeAudio();
        lastInitAttempt = millis();
    }
    
    // Normal loop functionality only when everything is initialized
    if (isDisplayInitialized && isWiFiConnected && isAudioInitialized) {
        audio.loop();
        yield();
        checkEncoder();
        yield();
        checkVolumeControl();
        yield();
    }
    
    delay(10);  // Small pause for stability
}

void initializeDisplay() {
    Serial.println(F("Initializing OLED display..."));
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 initialization failed"));
        return;  // Simply return instead of an endless loop
    }
    
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0,0);
    display.println(F("Initializing..."));
    display.display();
    
    isDisplayInitialized = true;
    Serial.println(F("Display initialized successfully"));
}

void connectToWiFi() {
    Serial.println(F("Connecting to WiFi..."));
    WiFi.begin(ssid, password);
    
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
        delay(500);
        Serial.print(".");
        attempts++;
    }
    
    if (WiFi.status() == WL_CONNECTED) {
        Serial.println(F("\nWiFi connected"));
        isWiFiConnected = true;
        if (isDisplayInitialized) {
            display.clearDisplay();
            display.setCursor(0,0);
            display.println(F("WiFi connected"));
            display.display();
        }
    } else {
        Serial.println(F("\nWiFi connection failed"));
    }
}

void initializeAudio() {
    audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
    audio.setVolume(10);
    connectToStation(currentStation);
    isAudioInitialized = true;
    Serial.println(F("Audio initialized"));
}

void checkEncoder() {
    if (rotaryEncoder.encoderChanged()) {
        currentStation = rotaryEncoder.readEncoder();
        connectToStation(currentStation);
    }
    
    if (rotaryEncoder.isEncoderButtonClicked()) {
        Serial.println(F("Encoder button clicked"));
        sendToSpotify();
    }
}

void connectToStation(int stationIndex) {
    audio.stopSong();
    audio.connecttohost(stations[stationIndex]);
    updateDisplay();
}

void checkVolumeControl() {
    unsigned long currentMillis = millis();
    if (currentMillis - lastVolumeCheck >= VOLUME_CHECK_INTERVAL) {
        lastVolumeCheck = currentMillis;
        
        total = total - volumeReadings[readIndex];
        volumeReadings[readIndex] = analogRead(VOLUME_PIN);
        total = total + volumeReadings[readIndex];
        readIndex = (readIndex + 1) % SAMPLES;
        
        average = total / SAMPLES;
        int volume = map(average, 0, 4095, 5, 23);
        
        static int lastVolume = -1;
        if (volume != lastVolume) {
            audio.setVolume(volume);
            lastVolume = volume;
            updateDisplay();
        }
    }
}

void updateDisplay() {
    if (!isDisplayInitialized) return;
    
    display.clearDisplay();
    display.setCursor(0,0);
    
    char buffer[64];
    replaceSpecialChars(stationNames[currentStation], buffer, sizeof(buffer));
    display.println(buffer);
    
    display.println();
    replaceSpecialChars(streamTitle, buffer, sizeof(buffer));
    display.println(buffer);
    
    display.display();
}

// Optimized URL-Encoding function with static buffer
void urlEncode(const char* input, char* output, size_t outputSize) {
    size_t j = 0;
    for (size_t i = 0; input[i] && j < outputSize - 4; i++) {
        char c = input[i];
        if (isalnum(c)) {
            output[j++] = c;
        } else if (c == ' ') {
            output[j++] = '+';
        } else {
            if (j + 3 >= outputSize) break;
            sprintf(&output[j], "%%%02X", c);
            j += 3;
        }
        yield();  // Feed the watchdog during long operations
    }
    output[j] = '\0';
}

void sendToSpotify() {
    static unsigned long lastRequestTime = 0;
    unsigned long currentTime = millis();
    
    if (currentTime - lastRequestTime < 5000) {
        Serial.println(F("Request blocked: Too soon since last request"));
        return;
    }
    lastRequestTime = currentTime;
    
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        
        // URL-Encoding with static buffer
        char encodedTitle[128];
        urlEncode(streamTitle, encodedTitle, sizeof(encodedTitle));
        
        // Build URL with snprintf
        snprintf(urlBuffer, sizeof(urlBuffer), "%s/?song=%s", serverName, encodedTitle);
        
        Serial.println(F("--------- New Request ---------"));
        Serial.print(F("Request URL: "));
        Serial.println(urlBuffer);
        
        http.begin(urlBuffer);
        http.setTimeout(5000);  // 5 seconds timeout
        http.setReuse(false);   // No connection reuse
        
        yield();  // Feed the watchdog
        
        int httpResponseCode = http.GET();
        
        if (httpResponseCode > 0) {
            // Static JSON document
            StaticJsonDocument<512> doc;
            
            // Read response
            String payload = http.getString();
            yield();  // Feed the watchdog
            
            DeserializationError error = deserializeJson(doc, payload);
            
            if (!error) {
                const char* message = doc["message"];
                Serial.print(F("Server message: "));
                Serial.println(message);
                
                if (doc.containsKey("spotify_status")) {
                    const char* spotifyStatus = doc["spotify_status"];
                    Serial.print(F("Spotify status: "));
                    Serial.println(spotifyStatus);
                }
            }
        } else {
            Serial.print(F("Error on HTTP request: "));
            Serial.println(httpResponseCode);
        }
        
        http.end();
        Serial.println(F("Connection closed"));
    }
    
    yield();  // Feed the watchdog at the end
}

// Audio callback functions
void audio_info(const char *info) { 
    Serial.print(F("info        ")); Serial.println(info);
}

void audio_id3data(const char *info) {
    Serial.print(F("id3data     ")); Serial.println(info);
}

void audio_eof_mp3(const char *info) {
    Serial.print(F("eof_mp3     ")); Serial.println(info);
}

void audio_showstation(const char *info) {
    Serial.print(F("station     ")); Serial.println(info);
}

void audio_showstreaminfo(const char *info) {
    Serial.print(F("streaminfo  ")); Serial.println(info);
}

void audio_showstreamtitle(const char *info) {
    Serial.print(F("streamtitle: ")); Serial.println(info);
    strncpy(streamTitle, info, sizeof(streamTitle) - 1);
    streamTitle[sizeof(streamTitle) - 1] = '\0';
    updateDisplay();
}

void audio_bitrate(const char *info) {
    Serial.print(F("bitrate     ")); Serial.println(info);
}

void audio_commercial(const char *info) {
    Serial.print(F("commercial  ")); Serial.println(info);
}

void audio_icyurl(const char *info) {
    Serial.print(F("icyurl      ")); Serial.println(info);
}

void audio_lasthost(const char *info) {
    Serial.print(F("lasthost    ")); Serial.println(info);
}

void audio_eof_speech(const char *info) {
    Serial.print(F("eof_speech  ")); Serial.println(info);
}

In the above sketch, you first need to enter your own Wi-Fi credentials:

const char ssid[] = "YOUR_WIFI_NETWORK";
const char password[] = "YOUR_WIFI_PASSWORD";

You also need the IP address of your Raspberry Pi. You can find this, for example, with the following command, which you enter in a terminal (while connected to it via SSH):

hostname -I

The IP address will then appear in the terminal (in the red marked part).

IP-Address of the Rasperry Pi

You then enter this address in the sketch here, appended with the port :8080

const char* serverName = "http://192.168.0.45:8080";

The choice of radio stations or their stream addresses and names is of course up to you – you’ve probably already created a list here and can transfer it to this sketch.

Once you’ve done everything, you can upload this sketch to your ESP32 and listen to the radio as usual.

Transferring a Song to Spotify

Now it’s time to test your new function! As soon as you hear a song whose artist and title you see on the display, press the button on your Rotary Encoder. The song should briefly stop and then resume. In the meantime, the Serial Monitor will tell you what just happened:

Successful transmission of the currently playing song to Spotify

For the transfer, the IP address of your Raspberry Pi was extended with the song information and called up by your ESP32. Your Raspberry successfully forwarded the song to Spotify and sends a Spotify status: success to the ESP32.

Now take a look at your Favorite Songs playlist in Spotify. The song you just transferred should already be there.

Starting the Script Automatically

One last building block for this project: It’s very impractical if you always have to activate your new feature by activating the virtual environment on the Raspberry Pi and manually starting the Python script. Therefore, you’ll now ensure that the script starts automatically as soon as you boot up your Raspberry Pi. This works as follows:

Create a Service File

sudo nano /etc/systemd/system/spotify-radio.service

Copy the following code, paste it in, and save it with CTRL+O, CTRL+X.

[Unit]
Description=Spotify Radio Service
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/RadioSpotify
ExecStart=/home/pi/RadioSpotify/bin/python /home/pi/RadioSpotify/radiospotify.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Reload the systemd Configuration

sudo systemctl daemon-reload

Enable the Service for Autostart

sudo systemctl enable spotify-radio

Start the Service

sudo systemctl start spotify-radio

Check if the Service is Running

You can check if the service is running with the following command:

sudo systemctl status spotify-radio

The terminal should now show something like this, indicating that your Spotify service is running:

Spotify service is online

Restart your Raspberry Pi as a test and run the above check again. Your terminal should again show that the service is active.

And that’s it! Your ESP32 Internet radio is now directly connected to Spotify, and you have the ability to save interesting songs to your favorites there. Enjoy!

We don't track you. Enjoy your cookies while making awesome projects!