Skip to content

Instantly share code, notes, and snippets.

@lolarobins
Created July 31, 2022 12:10
Show Gist options
  • Save lolarobins/2dbc1e9a1ffe2af77093ed0bd942e9e0 to your computer and use it in GitHub Desktop.
Save lolarobins/2dbc1e9a1ffe2af77093ed0bd942e9e0 to your computer and use it in GitHub Desktop.
64x64 Album art showing on RGB Matrix board
#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