Skip to content

Instantly share code, notes, and snippets.

@dirkjanfaber
Created October 24, 2016 19:16
Show Gist options
  • Save dirkjanfaber/3c05c2cc8e9a8cf5e05af45585eba82e to your computer and use it in GitHub Desktop.
Save dirkjanfaber/3c05c2cc8e9a8cf5e05af45585eba82e to your computer and use it in GitHub Desktop.
Catch spotify song and display it on an esp98266 OLED
/*
Spotify catcher
Functions as a "man in the middle" for the Spotify Client scrobbles that
go to post.audioscrobbler.com (last.fm).
Works on an esp8266 (Arduino IDE). Connect an oled to pin 3 (SDA) and
pin4 (SCL) to show the currently playing song.
Make sure you put the IP of the esp9266 into /etc/hosts with the name
post.audioscrobbler.com.
*/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiClient.h>
#include <Credentials.h>
#include "SSD1306.h"
const char* ssid = WIFI;
const char* password = WIFIPW;
// TCP server at port 80 will respond to HTTP requests
WiFiServer server(80);
// SDA on GPIO4, SDC on GPIO5
SSD1306 display(0x3c, 4, 5);
char *req = (char *)malloc(256);
char *path = (char *)malloc(64);
void get_param(char * str, char * value, char p) {
int start, length;
for (int i=0; i<(strlen(str)-2); i++) {
if ( str[i] == p && str[i+1] == '=' ) {
start = i + 2;
i=strlen(str);
}
}
length = 0;
if ( start > 0 ) {
for ( int i=start; i<strlen(str); i++ ) {
if ( str[i] == '&' )
break;
length++;
}
}
if ( length > 0 )
memcpy(value, &str[start], length);
value[length] = '\0';
return;
}
void update_lastfm( char *req, char *str, char *feedback ) {
WiFiClient client;
const char* host = "post.audioscrobbler.com";
const int httpPort = 80;
if (!client.connect(host, httpPort)) {
Serial.println("last.fm unreachable");
}
char *post = req;
// char *replace = (char *)"Host: post.audioscrobbler.com";
// First find "Host:"
// char *rest = strstr(str, "Host:");
// Determine where to start replacing
// int s = (int)(rest - str);
// The rest of the string starts after \n
// rest = strstr(rest, "\n");
// Determine how long the replacement text is.
// int l = 0;
// while ( rest[l] != '\n' ) { l++; };
// Allocate memory
// char *output = (char *)malloc(strlen(post)+strlen(str)+strlen(replace)-l);
char *output = (char *)malloc(strlen(post)+strlen(str));
memcpy(output, post, strlen(post));
memcpy(output+strlen(post), str, strlen(str));
output[strlen(output)+strlen(post)] = '\0';
// memcpy(output+strlen(post), str, s);
// memcpy(output+strlen(post)+s, replace, strlen(replace));
// memcpy(output+strlen(post)+s+strlen(replace), rest, strlen(rest));
// output[strlen(post)+s+strlen(replace)+strlen(rest)] = '\0';
// Send the new output to last.fm
client.print(output);
delay(10);
// catch the feedback
int i=0;
while(client.available()) {
feedback[i++] = client.read();
}
feedback[i] = '\0';
delay(10);
free(output);
client.flush();
}
int url_decode(char *str, char *dst) {
unsigned int i;
char *ptr = dst;
for (i=0; i < strlen(str); i++) {
if (str[i] == '+') {
*ptr++ = ' ';
continue;
}
if (str[i] != '%') {
*ptr++ = str[i];
continue;
}
if (!isdigit(str[i+1]) || !isdigit(str[i+2])) {
*ptr++ = str[i];
continue;
}
*ptr++ = ((str[i+1] - '0') << 4) | (str[i+2] - '0');
i += 2;
}
*ptr = '\0';
return 0;
}
void setup(void)
{
Serial.begin(115200);
// Connect to WiFi network
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// Set up mDNS responder:
// - first argument is the domain name, in this example
// the fully-qualified domain name is "esp8266.local"
// - second argument is the IP address to advertise
// we send our IP address on the WiFi network
if (!MDNS.begin("esp8266")) {
Serial.println("Error setting up MDNS responder!");
while(1) {
delay(1000);
}
}
// Start TCP (HTTP) server
server.begin();
// Add service to MDNS-SD
MDNS.addService("http", "tcp", 80);
display.init();
display.clear();
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "Spotify catcher");
display.display();
}
void loop(void)
{
// Check if a client has connected
WiFiClient client = server.available();
if (!client) {
return;
}
Serial.println("New client");
// Wait for data from client to become available
while(client.connected() && !client.available()){
delay(1);
}
// Read the first line of HTTP request
client.readBytesUntil('\r', req, 255);
// Serial.print("req = ["); Serial.print(req); Serial.println("]");
// Get the path from the request (we are only interested in responding to
// /?hs=tr and /np_1.2 requests)
path = strchr(req, ' ');
path++;
if (path == NULL) {
Serial.print("Invalid request: ");
Serial.println(req);
return;
}
char feedback[512];
// Spotify still uses the deprecated API of last.fm...
if ( strncmp(path, "/np_1.2", 7) == 0 ) {
Serial.println("searching track");
char *artist;
char *track;
char data[512];
// First we need to read all of the data that is comming from the spotify
// client
int i=0;
while(client.available()) {
data[i++] = client.read();
}
data[i] = '\0';
// The interesting part is in the posted data. That contains the first = of
// the whole package, so search for the '='.
char *s;
s = strchr(data, '=');
// first url decode
char *d = (char*)malloc(strlen(s));
url_decode(s, d);
artist = (char*)malloc(strlen(s));
get_param(d, artist, 'a');
Serial.print("artist=");
Serial.println(artist);
track = (char*)malloc(strlen(s));
get_param(d, track, 't');
Serial.print("track=");
Serial.println(track);
// update last.fm and get feedback
update_lastfm(req, data, feedback);
// Now output the band and track to the oled screen.
display.clear();
display.drawString(0, 0, artist);
display.drawString(0,32, track);
display.display();
} else if ( strncmp(path, "/?hs=tr", 7) == 0 ) {
// Serial.println("Authentication.. leave that to last.fm");
char data[512];
// First we need to read all of the data that is comming from the spotify
// client
int i=0;
while(client.available()) {
data[i++] = client.read();
}
data[i] = '\0';
update_lastfm(req, data, feedback);
}
client.flush();
String s;
if ( strncmp(path, "/np_1.2", 7) == 0 ) {
s = feedback;
}
else if ( strncmp(path, "/?hs=tr", 7) == 0 ) {
s = feedback;
}
else
{
s = "HTTP/1.1 404 Not Found\r\n\r\n";
Serial.println("Sending 404");
}
client.print(s);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment