Created
July 31, 2022 12:10
-
-
Save lolarobins/2dbc1e9a1ffe2af77093ed0bd942e9e0 to your computer and use it in GitHub Desktop.
64x64 Album art showing on RGB Matrix board
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <ArduinoJson.h> | |
#include <Preferences.h> | |
#include <WiFi.h> | |
#include <WebServer.h> | |
#include <Adafruit_GFX.h> | |
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h> | |
#include <HTTPClient.h> | |
#include <JPEGDecoder.h> | |
#include <mbedtls/base64.h> | |
#define R1_PIN 25 | |
#define G1_PIN 27 | |
#define B1_PIN 26 | |
#define R2_PIN 14 | |
#define G2_PIN 13 | |
#define B2_PIN 12 | |
#define A_PIN 23 | |
#define B_PIN 22 | |
#define C_PIN 5 | |
#define D_PIN 17 | |
#define E_PIN 21 | |
#define LAT_PIN 4 | |
#define OE_PIN 15 | |
#define CLK_PIN 16 | |
HUB75_I2S_CFG::i2s_pins _pins = {R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN}; | |
HUB75_I2S_CFG mxconfig(64, 64, 1, _pins); | |
Preferences preferences; | |
MatrixPanel_I2S_DMA *display = nullptr; | |
GFXcanvas1 canvas(64, 64); | |
WebServer server(80); | |
bool auth; | |
String access_token, refresh_token, code, last; | |
void print_scr(String text) | |
{ | |
canvas.fillScreen(0x00); | |
canvas.setCursor(0, 0); | |
display->clearScreen(); | |
canvas.println(text.c_str()); | |
Serial.println(text); | |
for (int i = 0; i < 64; i++) | |
for (int j = 0; j < 64; j++) | |
if (canvas.getPixel(i, j)) | |
display->drawPixel(i, j, 0xFFFF); | |
} | |
void restart() | |
{ | |
print_scr("Restarting"); | |
delay(2500); | |
ESP.restart(); | |
} | |
bool authorize() | |
{ | |
WiFiClientSecure socket; | |
socket.setInsecure(); | |
if (!socket.connect("accounts.spotify.com", 443)) | |
{ | |
print_scr("Failed\nto connect\nto Spotify"); | |
delay(2500); | |
return false; | |
} | |
String request = "POST /api/token HTTP/1.1\r\n" | |
"Content-Type: application/x-www-form-urlencoded\r\n" | |
"Connection: close\r\n" | |
"Host: accounts.spotify.com\r\n" | |
"Authorization: Basic "; | |
String msg = preferences.getString("client_id") + ":" + preferences.getString("client_secret"); | |
size_t out_len = 0; | |
mbedtls_base64_encode(NULL, 0, &out_len, (unsigned char *)msg.c_str(), msg.length()); | |
unsigned char b64_cipher[out_len]; | |
mbedtls_base64_encode(b64_cipher, out_len, &out_len, (unsigned char *)msg.c_str(), msg.length()); | |
request.concat((char *)b64_cipher); | |
request += "\r\nContent-Length: "; | |
IPAddress address = WiFi.localIP(); | |
char addr_buffer[32]; | |
sprintf(addr_buffer, "http://%d.%d.%d.%d/auth", address[0], address[1], address[2], address[3]); | |
String parsed = refresh_token.length() ? "refresh_token&refresh_token=" + code : "authorization_code" | |
String body = "grant_type=authorization_code&code=" + code + "&redirect_uri=" + addr_buffer; | |
char size_buffer[8]; | |
sprintf(size_buffer, "%d", body.length()); | |
request.concat(size_buffer); | |
request += "\r\n\r\n" + body; | |
socket.print(request); | |
String response = socket.readString(); | |
response = response.substring(response.indexOf("\r\n\r\n") + 4); | |
DynamicJsonDocument json(1024); | |
deserializeJson(json, response.c_str()); | |
if (json.containsKey("access_token")) | |
{ | |
print_scr("Authorized"); | |
delay(2500); | |
access_token = json["access_token"].as<String>(); | |
return true; | |
} | |
else | |
{ | |
print_scr("Error\nin auth\nprocess"); | |
delay(2500); | |
return false; | |
} | |
} | |
void setup() | |
{ | |
Serial.begin(115200); | |
// display initialization | |
mxconfig.clkphase = false; | |
mxconfig.latch_blanking = 4; | |
mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; | |
display = new MatrixPanel_I2S_DMA(mxconfig); | |
display->begin(); | |
display->setBrightness8(40); | |
display->clearScreen(); | |
canvas.setTextWrap(true); | |
print_scr("Starting"); | |
delay(2500); | |
// load flash | |
preferences.begin("spotify", false); | |
// connect to network | |
print_scr("Connecting"); | |
preferences.getString("password").length() == 0 ? WiFi.begin(preferences.getString("ssid").c_str()) : WiFi.begin(preferences.getString("ssid").c_str(), preferences.getString("password").c_str()); | |
for (int i = 0; i < 30; i++) | |
if (WiFi.status() != WL_CONNECTED) | |
delay(500); // 15s to connect | |
if (WiFi.status() != WL_CONNECTED) | |
{ | |
print_scr("WiFi Setup\n\nConnect to\nAP & visit\n192.168.4.1"); | |
WiFi.disconnect(); | |
WiFi.softAP("ESP32-Spotify"); | |
server.on("/", []() | |
{ | |
if (server.hasArg("ssid")) | |
preferences.putString("ssid", server.arg("ssid")); | |
if (server.hasArg("password")) | |
preferences.putString("password", server.arg("password")); | |
server.send(200, "text/html", "<html><body><form><input name=\"ssid\" type=\"text\" placeholder=\"SSID\" value=\"" + | |
preferences.getString("ssid") + "\"><input name=\"password\" placeholder=\"Password\" type=\"text\"><input type=\"submit\"></form></body></html>"); | |
if (server.args() > 0) | |
restart(); }); | |
server.begin(); | |
while (true) | |
server.handleClient(); | |
} | |
// allow user to set client id, secret, and/or login | |
IPAddress address = WiFi.localIP(); | |
char buffer[64]; | |
sprintf(buffer, "Connect to\n%d.%d.%d.%d\nin browser", address[0], address[1], address[2], address[3]); | |
print_scr(buffer); | |
server.on("/", []() | |
{ | |
if (server.hasArg("id")) | |
preferences.putString("client_id", server.arg("id")); | |
if (server.hasArg("secret")) | |
preferences.putString("client_secret", server.arg("secret")); | |
IPAddress address = WiFi.localIP(); | |
char buffer[64]; | |
sprintf(buffer, "http://%d.%d.%d.%d/auth", address[0], address[1], address[2], address[3]); | |
String body = "<html><body><form><p>Callback URI: "; | |
body.concat(buffer); | |
body += "<br><a href=\"https://accounts.spotify.com/en/authorize?client_id=" + preferences.getString("client_id") + "&response_type=code&scope=user-read-currently-playing&redirect_uri="; | |
body.concat(buffer); | |
body += "\">Login</a></p><input name=\"id\" type=\"text\" placeholder=\"Client ID\" value=\"" + | |
preferences.getString("client_id") + "\"><input name=\"secret\" placeholder=\"Client Secret\" type=\"text\"><input type=\"submit\"></form></body></html>"; | |
server.send(200, "text/html", body); | |
if (server.args() > 0) | |
{ | |
print_scr("Client info\nupdated"); | |
delay(2500); | |
restart(); | |
} }); | |
server.on("/auth", []() | |
{ | |
if (server.hasArg("code")) { | |
code = server.arg("code"); | |
server.send(200, "text/plain", "Authorized"); | |
auth = true; | |
if(!authorize()) | |
restart(); | |
} else { | |
server.send(200, "text/plain", "Auth failed"); | |
print_scr("No code\nreceived"); | |
delay(2500); | |
restart(); | |
} }); | |
server.begin(); | |
while (!auth) | |
server.handleClient(); | |
} | |
void loop() | |
{ | |
WiFiClientSecure socket, img_socket; | |
socket.setInsecure(); | |
img_socket.setInsecure(); | |
if (!socket.connect("api.spotify.com", 443) || !img_socket.connect("i.scdn.co", 443)) | |
{ | |
print_scr("Failed\nto connect\nto API"); | |
delay(2500); | |
restart(); | |
} | |
String request = "GET /v1/me/player/currently-playing HTTP/1.1\r\n" | |
"Accept: application/json\r\n" | |
"Connection: close\r\n" | |
"Host: api.spotify.com\r\n" | |
"Authorization: Bearer " + | |
access_token + | |
"\r\n\r\n"; | |
socket.print(request); | |
String track = socket.readString(); | |
int status = track.substring(9, 12).toInt(); | |
track = track.substring(track.indexOf("\r\n\r\n") + 4); | |
DynamicJsonDocument json(1024 * 12); | |
deserializeJson(json, track); | |
uint8_t x = 0, y = 0; | |
switch (status) | |
{ | |
case 200: // Track playing | |
if (last.equals(json["item"]["id"].as<String>())) | |
break; | |
last = json["item"]["id"].as<String>(); | |
track = json["item"]["album"]["images"][2]["url"].as<String>(); | |
track = track.substring(17); | |
request = "GET " + track + " HTTP/1.1\r\n" | |
"Connection: close\r\n" | |
"Host: i.scdn.co\r\n" | |
"Authorization: Bearer " + | |
access_token + | |
"\r\n\r\n"; | |
img_socket.print(request); | |
track = img_socket.readString(); | |
track = track.substring(track.indexOf("\r\n\r\n") + 4); // track is now image data | |
// from examples | |
JpegDec.decodeArray((uint8_t *)track.c_str(), track.length()); | |
while (JpegDec.read()) | |
{ | |
display->drawRGBBitmap(x, y, JpegDec.pImage, JpegDec.MCUWidth, JpegDec.MCUHeight); | |
x += JpegDec.MCUWidth; | |
if (x == 64) | |
{ | |
x = 0; | |
y += JpegDec.MCUHeight; | |
} | |
delay(20); | |
} | |
break; | |
case 204: // No track playing | |
display->clearScreen(); | |
break; | |
case 401: // Auth failed | |
if (!authorize()) | |
restart(); | |
break; | |
default: | |
restart(); | |
break; | |
} | |
delay(1000); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment