ESP-32 Realtime Spotify Music Display Clock & Timer
A multitasking desktop assistant powered by ESP32 FreeRTOS. Features real-time Spotify tracking, a Pomodoro timer, and an Alarm clock, all wrapped in a custom "Inverted Block" UI on a 16x2 I2C display.
Components and supplies
1
Mini breadboard - White
1
220Ω Resistor
1
ESP 32
1
16x2 LCD display with I²C interface
1
Active Buzzer
1
RGB LED
1
Rotary Encoder ABZ 1000Z
Apps and platforms
1
Arduino IDE 2.0 App
Project description
Code
Dual-Core ESP32 Spotify & Productivity Hub
cpp
A multitasking desktop assistant powered by ESP32
1/* 2 * PROJECT: The ISO-Deck (Spotify & Productivity Hub) 3 * AUTHOR: [Your Name] 4 * HARDWARE: ESP32, 16x2 I2C LCD, Rotary Encoder, RGB LED 5 * FEATURES: Dual-Core Multitasking, Spotify API, Pomodoro, Alarm 6 */ 7 8#include <WiFi.h> 9#include <WiFiClientSecure.h> 10#include <SpotifyArduino.h> // Library by witnessmenow 11#include <ArduinoJson.h> 12#include <LiquidCrystal_I2C.h> 13#include <ESP32Encoder.h> // Library by Kevin Harrington 14#include <NTPClient.h> 15#include <WiFiUdp.h> 16#include <Wire.h> 17 18// ================= USER CONFIGURATION ================= 19// 1. Wi-Fi Credentials 20char ssid[] = "YOUR_WIFI_NAME"; 21char password[] = "YOUR_WIFI_PASS"; 22 23// 2. Spotify Keys (Get these from developer.spotify.com) 24char clientId[] = "YOUR_CLIENT_ID"; 25char clientSecret[] = "YOUR_CLIENT_SECRET"; 26#define SPOTIFY_REFRESH_TOKEN "YOUR_REFRESH_TOKEN" 27 28#define COUNTRY_CODE "IN" 29 30// ================= PIN DEFINITIONS ================= 31#define PIN_SDA 21 32#define PIN_SCL 22 33#define PIN_BUZZER 18 34#define PIN_LED_R 16 35#define PIN_LED_G 17 36#define PIN_LED_B 5 37#define PIN_ENC_CLK 25 38#define PIN_ENC_DT 26 39#define PIN_ENC_SW 27 40 41// ================= BITMAPS (Inverted Headers) ================= 42// Custom graphics to create "Solid Block" text effect 43const uint8_t CHAR_MAP[][8] = { 44 {0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x00}, // 0: T 45 {0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E, 0x1F, 0x00}, // 1: I 46 {0x1F, 0x1F, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x00}, // 2: M 47 {0x1F, 0x1F, 0x00, 0x15, 0x15, 0x15, 0x00, 0x00}, // 3: E 48 {0x1F, 0x1F, 0x00, 0x1D, 0x1D, 0x03, 0x00, 0x00}, // 4: S 49 {0x1F, 0x1F, 0x11, 0x11, 0x11, 0x11, 0x0E, 0x00}, // 5: O 50 {0x1F, 0x1F, 0x00, 0x19, 0x15, 0x13, 0x11, 0x00}, // 6: N 51 {0x1F, 0x1F, 0x10, 0x17, 0x11, 0x11, 0x0E, 0x00}, // 7: G 52 {0x1F, 0x1F, 0x11, 0x11, 0x0A, 0x0A, 0x0A, 0x00}, // 8: W 53 {0x1F, 0x1F, 0x01, 0x11, 0x03, 0x05, 0x09, 0x00}, // 9: R 54 {0x1F, 0x1F, 0x09, 0x05, 0x03, 0x05, 0x09, 0x00}, // 10: K 55 {0x1F, 0x1F, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x00}, // 11: A 56 {0x1F, 0x1F, 0x10, 0x10, 0x10, 0x10, 0x1F, 0x00}, // 12: L 57}; 58 59// ================= OBJECTS ================= 60LiquidCrystal_I2C lcd(0x27, 16, 2); 61ESP32Encoder encoder; 62WiFiClientSecure client; 63SpotifyArduino spotify(client, clientId, clientSecret, SPOTIFY_REFRESH_TOKEN); 64WiFiUDP ntpUDP; 65NTPClient timeClient(ntpUDP, "pool.ntp.org", 19800); // UTC+5:30 Offset 66 67// ================= SHARED DATA (Dual Core Sync) ================= 68volatile int currentMode = 0; // 0=Auto, 1=Pomodoro, 2=Alarm 69 70String sharedTime = "Loading.."; 71String sharedTrack = ""; 72String sharedArtist = ""; 73bool sharedIsPlaying = false; 74 75// Flags 76bool alarmEnabled = false; 77bool pomoRunning = false; 78int pomoMinutes = 25; 79int pomoSeconds = 0; 80 81// FreeRTOS Task Handles 82TaskHandle_t TaskUI; 83TaskHandle_t TaskNet; 84 85// ================= SETUP ================= 86void setup() { 87 Serial.begin(115200); 88 Wire.begin(PIN_SDA, PIN_SCL); 89 90 // Pin Modes 91 pinMode(PIN_BUZZER, OUTPUT); 92 pinMode(PIN_LED_R, OUTPUT); pinMode(PIN_LED_G, OUTPUT); pinMode(PIN_LED_B, OUTPUT); 93 pinMode(PIN_ENC_SW, INPUT_PULLUP); 94 95 // Encoder Init 96 encoder.attachHalfQuad(PIN_ENC_DT, PIN_ENC_CLK); 97 encoder.setCount(0); 98 pinMode(PIN_ENC_CLK, INPUT_PULLUP); pinMode(PIN_ENC_DT, INPUT_PULLUP); 99 100 // LCD Init 101 lcd.init(); lcd.backlight(); 102 lcd.setCursor(0,0); lcd.print("ISO-DECK v1.0"); 103 lcd.setCursor(0,1); lcd.print("System Init..."); 104 105 // Launch Dual Core Tasks 106 // TaskUI runs on Core 1 (Fast, UI/Inputs) 107 xTaskCreatePinnedToCore(TaskUICode, "UI_Task", 10000, NULL, 1, &TaskUI, 1); 108 // TaskNet runs on Core 0 (Slow, Network/Spotify) 109 xTaskCreatePinnedToCore(TaskNetCode, "Net_Task", 10000, NULL, 0, &TaskNet, 0); 110} 111 112void loop() { 113 vTaskDelete(NULL); // Loop is unused in FreeRTOS 114} 115 116// ================= TASK 1: UI & HARDWARE (CORE 1) ================= 117// Handles Display, LED, Encoder, Button. High speed loop. 118void TaskUICode(void * pvParameters) { 119 long lastEnc = 0; 120 String line0_prev = "", line1_prev = ""; 121 bool showSong = false; 122 unsigned long lastSwap = 0; 123 124 for(;;) { 125 // 1. ENCODER INPUT 126 long newEnc = encoder.getCount() / 2; 127 if (newEnc != lastEnc) { 128 if (newEnc > lastEnc) { currentMode++; if(currentMode > 2) currentMode=0; } 129 else { currentMode--; if(currentMode < 0) currentMode=2; } 130 lastEnc = newEnc; 131 lcd.clear(); line0_prev=""; line1_prev=""; 132 } 133 134 // 2. BUTTON INPUT 135 if (digitalRead(PIN_ENC_SW) == LOW) { 136 delay(50); 137 if (digitalRead(PIN_ENC_SW) == LOW) { 138 if(currentMode == 2) alarmEnabled = !alarmEnabled; 139 if(currentMode == 1) pomoRunning = !pomoRunning; 140 // Note: Spotify Play/Pause is handled via sharedIsPlaying flag logic if needed, 141 // but for simplicity in this version, we focus on local controls. 142 while(digitalRead(PIN_ENC_SW) == LOW); 143 } 144 } 145 146 // 3. HEADER MANAGEMENT (Inverted UI Logic) 147 static int currentHeaderState = -1; 148 int targetHeader = 0; // 0=TIME, 1=SONG, 2=WORK, 3=ALRM 149 150 if (currentMode == 1) targetHeader = 2; // WORK 151 else if (currentMode == 2) targetHeader = 3; // ALRM 152 else { 153 // Auto Mode: Swap between Time and Song 154 if (millis() - lastSwap > 4000) { lastSwap = millis(); showSong = !showSong; } 155 if (sharedIsPlaying && showSong) targetHeader = 1; // SONG 156 else targetHeader = 0; // TIME 157 } 158 159 if (targetHeader != currentHeaderState) { 160 loadHeaderIcons(targetHeader); 161 currentHeaderState = targetHeader; 162 // Draw Header Block 163 lcd.setCursor(0,0); 164 for(int i=0; i<4; i++) lcd.write(i); 165 } 166 167 // 4. DATA DISPLAY 168 String line1 = ""; 169 170 if (currentMode == 0) { // AUTO 171 if (currentHeaderState == 1) { // SONG 172 line1 = sharedTrack.substring(0,16); 173 // Mood Light: Seed random based on song title length for consistent colors 174 long seed = sharedTrack.length(); randomSeed(seed); 175 analogWrite(PIN_LED_R, random(0,150)); 176 analogWrite(PIN_LED_G, random(0,150)); 177 analogWrite(PIN_LED_B, random(0,150)); 178 } else { // TIME 179 lcd.setCursor(5,0); lcd.print(sharedTime.substring(0,5)); // "12:00" 180 line1 = "Status: ONLINE"; 181 // Default Light: Soft White/Blue 182 analogWrite(PIN_LED_R, 0); analogWrite(PIN_LED_G, 20); analogWrite(PIN_LED_B, 30); 183 } 184 } 185 else if (currentMode == 1) { // POMODORO 186 // Header is "WORK" 187 line1 = String(pomoMinutes) + ":" + String(pomoSeconds); 188 if(pomoRunning) line1 += " >"; 189 analogWrite(PIN_LED_R, 50); analogWrite(PIN_LED_G, 0); analogWrite(PIN_LED_B, 0); 190 } 191 else if (currentMode == 2) { // ALARM 192 // Header is "ALRM" 193 line1 = alarmEnabled ? "Status: ON" : "Status: OFF"; 194 analogWrite(PIN_LED_R, 20); analogWrite(PIN_LED_G, 20); analogWrite(PIN_LED_B, 0); 195 } 196 197 printPadded(1, line1, line1_prev); 198 line1_prev = line1; 199 delay(10); // Keep UI responsive 200 } 201} 202 203// ================= TASK 2: NET (CORE 0) ================= 204// Handles WiFi, Spotify API, NTP Time. Low speed loop. 205void TaskNetCode(void * pvParameters) { 206 WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); 207 while (WiFi.status() != WL_CONNECTED) delay(500); 208 209 client.setInsecure(); timeClient.begin(); 210 211 for(;;) { 212 timeClient.update(); 213 sharedTime = timeClient.getFormattedTime(); 214 215 // Check Spotify status every 3 seconds 216 int status = spotify.getCurrentlyPlaying(saveData, COUNTRY_CODE); 217 sharedIsPlaying = (status == 200); 218 219 // Alarm Logic (7:00 AM) 220 if(alarmEnabled && timeClient.getHours() == 7 && timeClient.getMinutes() == 0 && timeClient.getSeconds() == 0) { 221 tone(PIN_BUZZER, 2000, 2000); 222 } 223 224 // Pomodoro Logic (Running here for accurate timing) 225 static unsigned long lastPomoTick = 0; 226 if (pomoRunning && millis() - lastPomoTick > 1000) { 227 lastPomoTick = millis(); 228 if (pomoSeconds == 0) { 229 if (pomoMinutes == 0) { 230 pomoRunning = false; 231 tone(PIN_BUZZER, 1000, 1000); 232 pomoMinutes = 25; 233 } else { pomoMinutes--; pomoSeconds = 59; } 234 } else { pomoSeconds--; } 235 } 236 237 delay(1000); // Wait 1 second 238 } 239} 240 241// Callback to save Spotify data to shared variables 242void saveData(CurrentlyPlaying cp) { 243 sharedTrack = String(cp.trackName); 244 sharedArtist = String(cp.artists[0].artistName); 245} 246 247// ================= HELPER FUNCTIONS ================= 248 249void loadHeaderIcons(int type) { 250 // Swaps bitmaps into LCD memory based on mode 251 // 0=TIME, 1=SONG, 2=WORK, 3=ALRM 252 uint8_t *c1, *c2, *c3, *c4; 253 254 if (type == 0) { // TIME 255 c1=(uint8_t*)CHAR_MAP[0]; c2=(uint8_t*)CHAR_MAP[1]; c3=(uint8_t*)CHAR_MAP[2]; c4=(uint8_t*)CHAR_MAP[3]; 256 } 257 else if (type == 1) { // SONG 258 c1=(uint8_t*)CHAR_MAP[4]; c2=(uint8_t*)CHAR_MAP[5]; c3=(uint8_t*)CHAR_MAP[6]; c4=(uint8_t*)CHAR_MAP[7]; 259 } 260 else if (type == 2) { // WORK 261 c1=(uint8_t*)CHAR_MAP[8]; c2=(uint8_t*)CHAR_MAP[5]; c3=(uint8_t*)CHAR_MAP[9]; c4=(uint8_t*)CHAR_MAP[10]; 262 } 263 else { // ALARM (Using simulated mapping for A, L, R, M) 264 c1=(uint8_t*)CHAR_MAP[11]; c2=(uint8_t*)CHAR_MAP[12]; c3=(uint8_t*)CHAR_MAP[9]; c4=(uint8_t*)CHAR_MAP[2]; 265 } 266 lcd.createChar(0, c1); lcd.createChar(1, c2); lcd.createChar(2, c3); lcd.createChar(3, c4); 267} 268 269// Ensures text overwrites previous text completely 270void printPadded(int row, String text, String prevText) { 271 if (text.length() > 16) text = text.substring(0, 16); 272 while (text.length() < 16) text += " "; 273 if (text != prevText) { 274 lcd.setCursor(0, row); 275 lcd.print(text); 276 } 277}
Downloadable files
Pinout Mapping
ESP32 PINOUT MAPPING.txt
Comments
Only logged in users can leave comments