witnessmenow / spotify-api-arduino

Arduino library for integrating with the Spotify Web-API (Does not play music)
MIT License
184 stars 33 forks source link

Issue on ESP32 getCurrentlyPlaying #33

Closed eawystr closed 3 years ago

eawystr commented 3 years ago

Hi, I am trying the getCurrentlyPlaying, with a refresh_token got from the getRefreshToken example; and it always brings up the same error:

Connected to haifisch IP address: 192.168.1.137 Refreshing Access Tokens grant_type=refresh_token&refresh_token=AQB_zX3KKxiYsogl26g4LNqXvyHJ5iR2pdT1Cc8p2uxQKEC2QybHZinD7-GF47VbXc4_c612bkVmWtjQKE4VYx8amK3m25DAv2NdEjk3NA3iM29noOn3YGwpHigM-WXSrqA&client_id=f8aeea367669458dac14b17261ff61ea&client_secret=12b50f86299943a6b7b1152eb343420d stack size -1073421775 accounts.spotify.com Status Code: 200 status Code200 Closing client Free Heap: 278432 getting currently playing song: /v1/me/player/currently-playing?market=ES stack size -1073421711 Status Code: -1 stack size -1073421711 Closing client

eawystr commented 3 years ago

I have been reviewing everything a lot of times, but still the same error:

Connected to haifisch IP address: 192.168.1.137 Refreshing Access Tokens grant_type=refresh_token&refresh_token=xxxxxxxxxxxxxxxxx&client_id=xxxxxxxxxxxxxxxxxxxx&client_secret=xxxxxxxxxxxxxxxxxxx stack size -1073421775 accounts.spotify.com Status Code: 200 status Code200 Closing client Free Heap: 277872 getting currently playing song: /v1/me/player/currently-playing?market=ES stack size -1073421711 Status Code: -1 stack size -1073421711 Closing client Free Heap: 278348 getting currently playing song: /v1/me/player/currently-playing?market=ES stack size -1073421711 Status Code: -1 stack size -1073421711 Closing client

And so on.

Any clues?

witnessmenow commented 3 years ago

Can you try test the callbacks branch?

https://github.com/witnessmenow/spotify-api-arduino/tree/Callbacks

That is the latest code and it would be easier for me to debug that rather than going back to old code that will be replaced by the callbacks branch shortly anyways.

The callbacks branch brings a fairly significant change in the way the library is called, but the getCurrentlyPlaying example should just work if you update your secret refresh token etc

eawystr commented 3 years ago

Testesd.

The getRefreshToken example worked, but when trying the getCurrentlyPlaying it brings this error and it keeps rebooting:

..... Connected to haifisch IP address: 192.168.1.137 Refreshing Access Tokens grant_type=refresh_token&refresh_token=xxxxxx&client_id=xxxxxx&client_secret=xxxxxxx stack size -1073421775 accounts.spotify.com Status: HTTP/1.0 200 OK HTTP Version: HTTP/1.0 Status Code: 200 status Code200 Guru Meditation Error: Core 1 panic'ed (InstrFetchProhibited). Exception was unhandled. Core 1 register dump: PC : 0x81000000 PS : 0x00060c30 A0 : 0x800d4600 A1 : 0x3ffb1dd0
A2 : 0x3ffc1764 A3 : 0x3f400ded A4 : 0x000000a0 A5 : 0x78f383b8
A6 : 0x3ffb1cd0 A7 : 0x3f400d92 A8 : 0x800d2fad A9 : 0x3ffb1db0
A10 : 0x3ffc1864 A11 : 0x40086860 A12 : 0x00000000 A13 : 0x3f400ad1
A14 : 0x00000132 A15 : 0x00000001 SAR : 0x0000000a EXCCAUSE: 0x00000014
EXCVADDR: 0x81000000 LBEG : 0x400012c5 LEND : 0x400012d5 LCOUNT : 0xfffffff4

ELF file SHA256: 0000000000000000

Backtrace: 0x41000000:0x3ffb1dd0 0x400d45fd:0x3ffb1df0 0x400d0b32:0x3ffb1f70 0x400d57f6:0x3ffb1fb0 0x400897b2:0x3ffb1fd0

Rebooting...

witnessmenow commented 3 years ago

Just tested that example there now and its working fine.

Can you link to the song you are listening to maybe?

It would also be useful to see what that stack trace says: https://github.com/me-no-dev/EspExceptionDecoder

witnessmenow commented 3 years ago

Also, do you have spotify premium?

eawystr commented 3 years ago

Yes, I have premium and it happens with all songs.

Connected to haifisch IP address: 192.168.1.137 Refreshing Access Tokens grant_type=refresh_token&refresh_token=xxxxx&client_id=xxxxx&client_secret=xxxx stack size -1073421775 accounts.spotify.com Status: HTTP/1.0 200 OK HTTP Version: HTTP/1.0 Status Code: 200 status Code200 Guru Meditation Error: Core 1 panic'ed (InstrFetchProhibited). Exception was unhandled. Core 1 register dump: PC : 0x00000000 PS : 0x00060c30 A0 : 0x800d4600 A1 : 0x3ffb1dd0
A2 : 0x3ffc1764 A3 : 0x3f400ded A4 : 0x000000a0 A5 : 0x410d337d
A6 : 0x3ffb1cd0 A7 : 0x3f400d92 A8 : 0x800d2fad A9 : 0x3ffb1db0
A10 : 0x3ffc1864 A11 : 0x40086860 A12 : 0x00000000 A13 : 0x3f400ad1
A14 : 0x00000132 A15 : 0x00000001 SAR : 0x0000000a EXCCAUSE: 0x00000014
EXCVADDR: 0x00000000 LBEG : 0x400012c5 LEND : 0x400012d5 LCOUNT : 0xfffffff4

ELF file SHA256: 0000000000000000

Backtrace: 0x00000000:0x3ffb1dd0 0x400d45fd:0x3ffb1df0 0x400d0b32:0x3ffb1f70 0x400d57f6:0x3ffb1fb0 0x400897b2:0x3ffb1fd0

PC: 0x00000000 EXCVADDR: 0x00000000

Decoding stack results 0x400d45fd: SpotifyArduino::refreshAccessToken() at C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-Callbacks\src\SpotifyArduino.cpp line 202 0x400d0b32: setup() at C:\Users\David Calderon\Documents\Arduino\Spotify API\getCurrentlyPlaying/getCurrentlyPlaying.ino line 115 0x400d57f6: loopTask(void*) at C:\Users\David Calderon\AppData\Local\arduino15\packages\esp32\hardware\esp32\1.0.6\cores\esp32\main.cpp line 18 0x400897b2: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c line 143

witnessmenow commented 3 years ago

Very strange. It seems to be crashing somewhere when you refresh your access token, but the call has a status 200.

Can you please update the code again from the callback branch, I've added some extra debugging on that method.

After you update could please enable #define SPOTIFY_PRINT_JSON_PARSE 1 in SpotifyArduino.h and install https://github.com/bblanchon/ArduinoStreamUtils

eawystr commented 3 years ago

Same...

.... Connected to haifisch IP address: 192.168.1.137 Refreshing Access Tokens grant_type=refresh_token&refresh_token=xxxxxx&client_id=xxxxxa&client_secret=xxxxxx stack size -1073421775 accounts.spotify.com Status: HTTP/1.0 200 OK HTTP Version: HTTP/1.0 Status Code: 200 status Code200 Guru Meditation Error: Core 1 panic'ed (InstrFetchProhibited). Exception was unhandled. Core 1 register dump: PC : 0x00000000 PS : 0x00060c30 A0 : 0x800d461a A1 : 0x3ffb1dd0
A2 : 0x3ffc1764 A3 : 0x3f400ded A4 : 0x000000a0 A5 : 0xc6908d22
A6 : 0x3ffb1cd0 A7 : 0x3f400d92 A8 : 0x800d2fad A9 : 0x3ffb1db0
A10 : 0x3ffc1864 A11 : 0x40086860 A12 : 0x00000000 A13 : 0x3f400ad1
A14 : 0x00000132 A15 : 0x00000001 SAR : 0x0000000a EXCCAUSE: 0x00000014
EXCVADDR: 0x00000000 LBEG : 0x400012c5 LEND : 0x400012d5 LCOUNT : 0xfffffff4

ELF file SHA256: 0000000000000000

Backtrace: 0x00000000:0x3ffb1dd0 0x400d4617:0x3ffb1df0 0x400d0b32:0x3ffb1f70 0x400d5816:0x3ffb1fb0 0x400897b2:0x3ffb1fd0

PC: 0x00000000 EXCVADDR: 0x00000000

Decoding stack results 0x400d4617: SpotifyArduino::refreshAccessToken() at C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-Callbacks\src\SpotifyArduino.cpp line 216 0x400d0b32: setup() at C:\Users\DAVIDC~1\AppData\Local\Temp\arduino_modified_sketch_956359/getCurrentlyPlaying.ino line 114 0x400d5816: loopTask(void*) at C:\Users\David Calderon\AppData\Local\arduino15\packages\esp32\hardware\esp32\1.0.6\cores\esp32\main.cpp line 18 0x400897b2: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c line 143

witnessmenow commented 3 years ago

Can you confirm your enabled the SPOTIFY_PRINT_JSON_PARSE flag?

Can you take a screen shot of your board settings in the Arduino ide?

eawystr commented 3 years ago

Yes, I've enabled it. Im working on a AZ-DELIVERY ESP32 WROOM 32

image

eawystr commented 3 years ago

Hi,

Any clues?

eawystr commented 3 years ago

I am getting mad on this...

witnessmenow commented 3 years ago

Get mad all you like. Works for me Figure it out yourself

eawystr commented 3 years ago

Sorry, I didn’t wanted to bother you

jLynx commented 3 years ago

You clearly did though.... 😂

witnessmenow commented 3 years ago

I think there was a misunderstanding in what was said, @eawystr apologised so I am re-opening

witnessmenow commented 3 years ago

@eawystr , I think a few places to start:

To modify the library, edit this file: C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-Callbacks\src\SpotifyArduino.cpp

Can you also post the code of the refreshAccessToken function after you have edited it.

eawystr commented 3 years ago

Hi again @witnessmenow and thanks for reopen the issue.

The ESP32 that I am using is a brand new (AZDelivery WROOM32), it works fine with other program, but I have bought a new ESP32 (the one that you recommend on the library) because is the only one that I have.

I've been adding Serial.println("1") with a different number on different lines on SpotifyArduino.cpp and it goes until lines 214, 215, 216 ultil return refreshed; If I add the Serial.println("1") after that line, it doesn't appear. If added it in other parts of the library, it doesn't appear.

And, this is the code that works for me (is the getRefreshToken example literally changing the soptify parameters and adding user-read-currently-playing scope):

(Thanks for your time and patience)

/*******************************************************************
    Get Refresh Token from spotify, this is needed for the other
    examples.

    Instructions:

    - Put in your Wifi details, Client ID, Client secret and flash to the board
    - Do one of the following

    --- IF USING IP (ESP32 MDNS does not work for me)

    - Get the Ip Address from the serial monitor
    - Add the following to Redirect URI on your Spotify app "http://[ESP_IP]/callback/"
    e.g. "http://192.168.1.20/callback/" (don't forget the last "/")
    - Open browser to esp using the IP 

    --- IF USING MDNS

    - Search for "#define USE_IP_ADDRESS" and comment it out 
    - Add the following to Redirect URI on your Spotify app "http://arduino.local/callback/" 
    (don't forget the last "/")
    - Open browser to esp: http://arduino.local 

    -----------

    - Click the link on the webpage
    - The Refresh Token will be printed to screen, use this
      for SPOTIFY_REFRESH_TOKEN in other examples.

    Compatible Boards:
      - Any ESP8266 or ESP32 board

    Parts:
    ESP32 D1 Mini style Dev board* - http://s.click.aliexpress.com/e/C6ds4my

 *  * = Affiliate

    If you find what I do useful and would like to support me,
    please consider becoming a sponsor on Github
    https://github.com/sponsors/witnessmenow/

    Written by Brian Lough
    YouTube: https://www.youtube.com/brianlough
    Tindie: https://www.tindie.com/stores/brianlough/
    Twitter: https://twitter.com/witnessmenow
 *******************************************************************/

// ----------------------------
// Standard Libraries
// ----------------------------

#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#elif defined(ESP32)
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#endif

#include <WiFiClient.h>
#include <WiFiClientSecure.h>

// ----------------------------
// Additional Libraries - each one of these will need to be installed.
// ----------------------------

#include <SpotifyArduino.h>
// Library for connecting to the Spotify API

// Install from Github
// https://github.com/witnessmenow/spotify-api-arduino

// including a "spotify_server_cert" variable
// header is included as part of the SpotifyArduino libary
#include <SpotifyArduinoCert.h>

#include <ArduinoJson.h>
// Library used for parsing Json from the API responses

// Search for "Arduino Json" in the Arduino Library manager
// https://github.com/bblanchon/ArduinoJson

//------- Replace the following! ------

char ssid[] = "haifisch";         // your network SSID (name)
char password[] = "xxxxxxxx"; // your network password

char clientId[] = "xxxxxxxxxxxxxxxxxxx";     // Your client ID of your spotify APP
char clientSecret[] = "xxxxxxxxxxxxxxx"; // Your client Secret of your spotify APP (Do Not share this!)

char scope[] = "user-read-playback-state%20user-read-currently-playing";

#define USE_IP_ADDRESS 1 //comment this out if you want to use MDNS

#ifdef USE_IP_ADDRESS
char callbackURItemplate[] = "%s%s%s";
char callbackURIProtocol[] = "http%3A%2F%2F"; // "http://"
char callbackURIAddress[] = "%2Fcallback%2F"; // "/callback/"
char callbackURI[100];
#else
char callbackURI[] = "http%3A%2F%2Farduino.local%2Fcallback%2F";
#endif

//------- ---------------------- ------

#if defined(ESP8266)
ESP8266WebServer server(80);
#elif defined(ESP32)
WebServer server(80);
#endif

WiFiClientSecure client;
SpotifyArduino spotify(client, clientId, clientSecret);

const char *webpageTemplate =
    R"(
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  </head>
  <body>
    <div>
     <a href="https://accounts.spotify.com/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=%s">spotify Auth</a>
    </div>
  </body>
</html>
)";

void handleRoot()
{
  char webpage[800];
  sprintf(webpage, webpageTemplate, clientId, callbackURI, scope);
  server.send(200, "text/html", webpage);
}

void handleCallback()
{
  String code = "";
  const char *refreshToken = NULL;
  for (uint8_t i = 0; i < server.args(); i++)
  {
    if (server.argName(i) == "code")
    {
      code = server.arg(i);
      refreshToken = spotify.requestAccessTokens(code.c_str(), callbackURI);
    }
  }

  if (refreshToken != NULL)
  {
    server.send(200, "text/plain", refreshToken);
  }
  else
  {
    server.send(404, "text/plain", "Failed to load token, check serial monitor");
  }
}

void handleNotFound()
{
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for (uint8_t i = 0; i < server.args(); i++)
  {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }

  Serial.print(message);
  server.send(404, "text/plain", message);
}

void setup()
{

  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  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: ");
  IPAddress ipAddress = WiFi.localIP();
  Serial.println(ipAddress);

  if (MDNS.begin("arduino"))
  {
    Serial.println("MDNS responder started");
  }

  // Handle HTTPS Verification
#if defined(ESP8266)
  client.setFingerprint(SPOTIFY_FINGERPRINT); // These expire every few months
#elif defined(ESP32)
  client.setCACert(spotify_server_cert);
#endif
  // ... or don't!
  //client.setInsecure();

  // If you want to enable some extra debugging
  // uncomment the "#define SPOTIFY_DEBUG" in ArduinoSpotify.h

#ifdef USE_IP_ADDRESS
  // Building up callback URL using IP address.
  sprintf(callbackURI, callbackURItemplate, callbackURIProtocol, ipAddress.toString().c_str(), callbackURIAddress);
#endif

  server.on("/", handleRoot);
  server.on("/callback/", handleCallback);
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("HTTP server started");
}

void loop()
{
#if defined(ESP8266)
  MDNS.update();
#endif

  server.handleClient();
}
witnessmenow commented 3 years ago

Can you paste your serial output and refresh access token method after your serial prints?

eawystr commented 3 years ago

Done.

.....
Connected to haifisch
IP address: 192.168.1.137
Refreshing Access Tokens
grant_type=refresh_token&refresh_token=AQBQ4P7YXiklMZ9oyGA4JOXQB9Y-cKvbqtNzJuQRk6quLheFl5rtY77a34RAGz9oLGuSYb_4dYYTD1e74JUICqBRe6oycc7FEwfE_1KcL5CWOGKR8OjPs_yZaW_j9zaYWik&client_id=f8aeea367669458dac14b17261ff61ea&client_secret=19cacb238db34764913ab16b7a210b98
stack size -1073421775
accounts.spotify.com
Status: HTTP/1.0 200 OK
HTTP Version: HTTP/1.0
Status Code: 200
status Code200
1
2
3
Guru Meditation Error: Core  1 panic'ed (InstrFetchProhibited). Exception was unhandled.
Core 1 register dump:
PC      : 0x00000000  PS      : 0x00060c30  A0      : 0x800d4669  A1      : 0x3ffb1dd0  
A2      : 0x3ffc1764  A3      : 0x00000001  A4      : 0x000000a0  A5      : 0xc787efc4  
A6      : 0x3ffb1cd0  A7      : 0x3f400d92  A8      : 0x800d2fcd  A9      : 0x3ffb1db0  
A10     : 0x3ffc1864  A11     : 0x3f400e1e  A12     : 0x00000000  A13     : 0x3f400ad1  
A14     : 0x00000132  A15     : 0x00000001  SAR     : 0x0000000a  EXCCAUSE: 0x00000014  
EXCVADDR: 0x00000000  LBEG    : 0x400012c5  LEND    : 0x400012d5  LCOUNT  : 0xfffffff4  

ELF file SHA256: 0000000000000000

Backtrace: 0x00000000:0x3ffb1dd0 0x400d4666:0x3ffb1df0 0x400d0b52:0x3ffb1f70 0x400d58a6:0x3ffb1fb0 0x400897b2:0x3ffb1fd0

Rebooting...
ets Jun  8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:10944
load:0x40080400,len:6388
entry 0x400806b4

And decoded:

PC: 0x00000000
EXCVADDR: 0x00000000

Decoding stack results
0x400d4666: SpotifyArduino::refreshAccessToken() at C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-Callbacks\src\SpotifyArduino.cpp line 216
0x400d0b52: setup() at C:\Users\David Calderon\Documents\Arduino\sketch_aug19b/sketch_aug19b.ino line 114
0x400d58a6: loopTask(void*) at C:\Users\David Calderon\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\cores\esp32\main.cpp line 18
0x400897b2: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c line 143

And the modified SpotifyArduino.cpp:

/*
  SpotifyArduino - An Arduino library to wrap the Spotify API

  Copyright (c) 2021  Brian Lough.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "SpotifyArduino.h"

SpotifyArduino::SpotifyArduino(Client &client, char *bearerToken)
{
  this->client = &client;
  sprintf(this->_bearerToken, "Bearer %s", bearerToken);
}

SpotifyArduino::SpotifyArduino(Client &client, const char *clientId, const char *clientSecret, const char *refreshToken)
{
  this->client = &client;
  this->_clientId = clientId;
  this->_clientSecret = clientSecret;
  this->_refreshToken = refreshToken;
}

int SpotifyArduino::makeRequestWithBody(const char *type, const char *command, const char *authorization, const char *body, const char *contentType, const char *host)
{
  client->flush();
#ifdef SPOTIFY_DEBUG
  Serial.println(host);
#endif
  client->setTimeout(SPOTIFY_TIMEOUT);
  if (!client->connect(host, portNumber))
  {
#ifdef SPOTIFY_SERIAL_OUTPUT
    Serial.println(F("Connection failed"));
#endif
    return -1;
  }

  // give the esp a breather
  yield();

  // Send HTTP request
  client->print(type);
  client->print(command);
  client->println(F(" HTTP/1.0"));

  //Headers
  client->print(F("Host: "));
  client->println(host);

  client->println(F("Accept: application/json"));
  client->print(F("Content-Type: "));
  client->println(contentType);

  if (authorization != NULL)
  {
    client->print(F("Authorization: "));
    client->println(authorization);
  }

  client->println(F("Cache-Control: no-cache"));

  client->print(F("Content-Length: "));
  client->println(strlen(body));

  client->println();

  client->print(body);

  if (client->println() == 0)
  {
#ifdef SPOTIFY_SERIAL_OUTPUT
    Serial.println(F("Failed to send request"));
#endif
    return -2;
  }

  int statusCode = getHttpStatusCode();
  return statusCode;
}

int SpotifyArduino::makePutRequest(const char *command, const char *authorization, const char *body, const char *contentType, const char *host)
{
  return makeRequestWithBody("PUT ", command, authorization, body, contentType);
}

int SpotifyArduino::makePostRequest(const char *command, const char *authorization, const char *body, const char *contentType, const char *host)
{
  return makeRequestWithBody("POST ", command, authorization, body, contentType, host);
}

int SpotifyArduino::makeGetRequest(const char *command, const char *authorization, const char *accept, const char *host)
{
  client->flush();
  client->setTimeout(SPOTIFY_TIMEOUT);
  if (!client->connect(host, portNumber))
  {
#ifdef SPOTIFY_SERIAL_OUTPUT
    Serial.println(F("Connection failed"));
#endif
    return -1;
  }

  // give the esp a breather
  yield();

  // Send HTTP request
  client->print(F("GET "));
  client->print(command);
  client->println(F(" HTTP/1.0"));

  //Headers
  client->print(F("Host: "));
  client->println(host);

  if (accept != NULL)
  {
    client->print(F("Accept: "));
    client->println(accept);
  }

  if (authorization != NULL)
  {
    client->print(F("Authorization: "));
    client->println(authorization);
  }

  client->println(F("Cache-Control: no-cache"));

  if (client->println() == 0)
  {
#ifdef SPOTIFY_SERIAL_OUTPUT
    Serial.println(F("Failed to send request"));
#endif
    return -2;
  }

  int statusCode = getHttpStatusCode();

  return statusCode;
}

void SpotifyArduino::setRefreshToken(const char *refreshToken)
{
  _refreshToken = refreshToken;
}

bool SpotifyArduino::refreshAccessToken()
{
  char body[300];
  sprintf(body, refreshAccessTokensBody, _refreshToken, _clientId, _clientSecret);

#ifdef SPOTIFY_DEBUG
  Serial.println(body);
  printStack();
#endif

  int statusCode = makePostRequest(SPOTIFY_TOKEN_ENDPOINT, NULL, body, "application/x-www-form-urlencoded", SPOTIFY_ACCOUNTS_HOST);
  if (statusCode > 0)
  {
    skipHeaders();
  }
  unsigned long now = millis();

#ifdef SPOTIFY_DEBUG
  Serial.print("status Code");
  Serial.println(statusCode);
#endif
  Serial.println("1");
  bool refreshed = false;
  if (statusCode == 200)
  {
    DynamicJsonDocument doc(1000);
    Serial.println("2");
    // Parse JSON object
#ifndef SPOTIFY_PRINT_JSON_PARSE
    DeserializationError error = deserializeJson(doc, *client);
#else
    ReadLoggingStream loggingStream(*client, Serial);
    DeserializationError error = deserializeJson(doc, loggingStream);
#endif
    if (!error)
    {
      sprintf(this->_bearerToken, "Bearer %s", doc["access_token"].as<const char *>());
      int tokenTtl = doc["expires_in"];             // Usually 3600 (1 hour)
      tokenTimeToLiveMs = (tokenTtl * 1000) - 2000; // The 2000 is just to force the token expiry to check if its very close
      timeTokenRefreshed = now;
      refreshed = true;
    }
    else
    {
#ifdef SPOTIFY_SERIAL_OUTPUT
      Serial.print(F("deserializeJson() failed with code "));
      Serial.println(error.c_str());
#endif
    }
  }
  else
  {
    parseError();
  }
  Serial.println("3");
  closeClient();
  Serial.println("4");
  return refreshed;
  Serial.println("5");
}

bool SpotifyArduino::checkAndRefreshAccessToken()
{
  Serial.println("6");
  unsigned long timeSinceLastRefresh = millis() - timeTokenRefreshed;
  Serial.println("7");
  if (timeSinceLastRefresh >= tokenTimeToLiveMs)
  {
#ifdef SPOTIFY_SERIAL_OUTPUT
    Serial.println("Refresh of the Access token is due, doing that now.");
#endif
    Serial.println("8");
    return refreshAccessToken();
  }

  // Token is still valid
  Serial.println("9");
  return true;
}

const char *SpotifyArduino::requestAccessTokens(const char *code, const char *redirectUrl)
{

  char body[500];
  sprintf(body, requestAccessTokensBody, code, redirectUrl, _clientId, _clientSecret);

#ifdef SPOTIFY_DEBUG
  Serial.println(body);
#endif

  int statusCode = makePostRequest(SPOTIFY_TOKEN_ENDPOINT, NULL, body, "application/x-www-form-urlencoded", SPOTIFY_ACCOUNTS_HOST);
  if (statusCode > 0)
  {
    skipHeaders();
  }
  unsigned long now = millis();

#ifdef SPOTIFY_DEBUG
  Serial.print("status Code");
  Serial.println(statusCode);
#endif

  if (statusCode == 200)
  {
    DynamicJsonDocument doc(1000);
    // Parse JSON object
#ifndef SPOTIFY_PRINT_JSON_PARSE
    DeserializationError error = deserializeJson(doc, *client);
#else
    ReadLoggingStream loggingStream(*client, Serial);
    DeserializationError error = deserializeJson(doc, loggingStream);
#endif
    if (!error)
    {
      sprintf(this->_bearerToken, "Bearer %s", doc["access_token"].as<const char *>());
      _refreshToken = doc["refresh_token"].as<const char *>();
      int tokenTtl = doc["expires_in"];             // Usually 3600 (1 hour)
      tokenTimeToLiveMs = (tokenTtl * 1000) - 2000; // The 2000 is just to force the token expiry to check if its very close
      timeTokenRefreshed = now;
    }
    else
    {
#ifdef SPOTIFY_SERIAL_OUTPUT
      Serial.print(F("deserializeJson() failed with code "));
      Serial.println(error.c_str());
#endif
    }
  }
  else
  {
    parseError();
  }

  closeClient();
  return _refreshToken;
}

bool SpotifyArduino::play(const char *deviceId)
{
  char command[100] = SPOTIFY_PLAY_ENDPOINT;
  return playerControl(command, deviceId);
}

bool SpotifyArduino::playAdvanced(char *body, const char *deviceId)
{
  char command[100] = SPOTIFY_PLAY_ENDPOINT;
  return playerControl(command, deviceId, body);
}

bool SpotifyArduino::pause(const char *deviceId)
{
  char command[100] = SPOTIFY_PAUSE_ENDPOINT;
  return playerControl(command, deviceId);
}

bool SpotifyArduino::setVolume(int volume, const char *deviceId)
{
  char command[125];
  sprintf(command, SPOTIFY_VOLUME_ENDPOINT, volume);
  return playerControl(command, deviceId);
}

bool SpotifyArduino::toggleShuffle(bool shuffle, const char *deviceId)
{
  char command[125];
  char shuffleState[10];
  if (shuffle)
  {
    strcpy(shuffleState, "true");
  }
  else
  {
    strcpy(shuffleState, "false");
  }
  sprintf(command, SPOTIFY_SHUFFLE_ENDPOINT, shuffleState);
  return playerControl(command, deviceId);
}

bool SpotifyArduino::setRepeatMode(RepeatOptions repeat, const char *deviceId)
{
  char command[125];
  char repeatState[10];
  switch (repeat)
  {
    case repeat_track:
      strcpy(repeatState, "track");
      break;
    case repeat_context:
      strcpy(repeatState, "context");
      break;
    case repeat_off:
      strcpy(repeatState, "off");
      break;
  }

  sprintf(command, SPOTIFY_REPEAT_ENDPOINT, repeatState);
  return playerControl(command, deviceId);
}

bool SpotifyArduino::playerControl(char *command, const char *deviceId, const char *body)
{
  if (deviceId[0] != 0)
  {
    char *questionMarkPointer;
    questionMarkPointer = strchr(command, '?');
    char deviceIdBuff[50];
    if (questionMarkPointer == NULL)
    {
      sprintf(deviceIdBuff, "?device_id=%s", deviceId);
    }
    else
    {
      // params already started
      sprintf(deviceIdBuff, "&device_id=%s", deviceId);
    }
    strcat(command, deviceIdBuff);
  }

#ifdef SPOTIFY_DEBUG
  Serial.println(command);
  Serial.println(body);
#endif

  if (autoTokenRefresh)
  {
    checkAndRefreshAccessToken();
  }
  int statusCode = makePutRequest(command, _bearerToken, body);

  closeClient();
  //Will return 204 if all went well.
  return statusCode == 204;
}

bool SpotifyArduino::playerNavigate(char *command, const char *deviceId)
{
  if (deviceId[0] != 0)
  {
    char deviceIdBuff[50];
    sprintf(deviceIdBuff, "?device_id=%s", deviceId);
    strcat(command, deviceIdBuff);
  }

#ifdef SPOTIFY_DEBUG
  Serial.println(command);
#endif

  if (autoTokenRefresh)
  {
    checkAndRefreshAccessToken();
  }
  int statusCode = makePostRequest(command, _bearerToken);

  closeClient();
  //Will return 204 if all went well.
  return statusCode == 204;
}

bool SpotifyArduino::nextTrack(const char *deviceId)
{
  char command[100] = SPOTIFY_NEXT_TRACK_ENDPOINT;
  return playerNavigate(command, deviceId);
}

bool SpotifyArduino::previousTrack(const char *deviceId)
{
  char command[100] = SPOTIFY_PREVIOUS_TRACK_ENDPOINT;
  return playerNavigate(command, deviceId);
}
bool SpotifyArduino::seek(int position, const char *deviceId)
{
  char command[100] = SPOTIFY_SEEK_ENDPOINT;
  char tempBuff[100];
  sprintf(tempBuff, "?position_ms=%d", position);
  strcat(command, tempBuff);
  if (deviceId[0] != 0)
  {
    sprintf(tempBuff, "?device_id=%s", deviceId);
    strcat(command, tempBuff);
  }

#ifdef SPOTIFY_DEBUG
  Serial.println(command);
  printStack();
#endif

  if (autoTokenRefresh)
  {
    checkAndRefreshAccessToken();
  }
  int statusCode = makePutRequest(command, _bearerToken);
  closeClient();
  //Will return 204 if all went well.
  return statusCode == 204;
}

bool SpotifyArduino::transferPlayback(const char *deviceId, bool play)
{
  char body[100];
  sprintf(body, "{\"device_ids\":[\"%s\"],\"play\":\"%s\"}", deviceId, (play ? "true" : "false"));

#ifdef SPOTIFY_DEBUG
  Serial.println(SPOTIFY_PLAYER_ENDPOINT);
  Serial.println(body);
  printStack();
#endif

  if (autoTokenRefresh)
  {
    checkAndRefreshAccessToken();
  }
  int statusCode = makePutRequest(SPOTIFY_PLAYER_ENDPOINT, _bearerToken, body);
  closeClient();
  //Will return 204 if all went well.
  return statusCode == 204;
}

int SpotifyArduino::getCurrentlyPlaying(processCurrentlyPlaying currentlyPlayingCallback, const char *market)
{
  Serial.println("3");
  char command[50] = SPOTIFY_CURRENTLY_PLAYING_ENDPOINT;
  if (market[0] != 0)
  {
    char marketBuff[15];
    sprintf(marketBuff, "?market=%s", market);
    strcat(command, marketBuff);
  }

#ifdef SPOTIFY_DEBUG
  Serial.println(command);
  printStack();
#endif

  // Get from https://arduinojson.org/v6/assistant/
  const size_t bufferSize = currentlyPlayingBufferSize;

  if (autoTokenRefresh)
  {
    checkAndRefreshAccessToken();
  }
  int statusCode = makeGetRequest(command, _bearerToken);
#ifdef SPOTIFY_DEBUG
  Serial.print("Status Code: ");
  Serial.println(statusCode);
  printStack();
#endif
  if (statusCode > 0)
  {
    skipHeaders();
  }

  if (statusCode == 200)
  {
    CurrentlyPlaying current;

    //Apply Json Filter: https://arduinojson.org/v6/example/filter/
    StaticJsonDocument<288> filter;
    filter["is_playing"] = true;
    filter["progress_ms"] = true;

    JsonObject filter_item = filter.createNestedObject("item");
    filter_item["duration_ms"] = true;
    filter_item["name"] = true;
    filter_item["uri"] = true;

    JsonObject filter_item_artists_0 = filter_item["artists"].createNestedObject();
    filter_item_artists_0["name"] = true;
    filter_item_artists_0["uri"] = true;

    JsonObject filter_item_album = filter_item.createNestedObject("album");
    filter_item_album["name"] = true;
    filter_item_album["uri"] = true;

    JsonObject filter_item_album_images_0 = filter_item_album["images"].createNestedObject();
    filter_item_album_images_0["height"] = true;
    filter_item_album_images_0["width"] = true;
    filter_item_album_images_0["url"] = true;

    // Allocate DynamicJsonDocument
    DynamicJsonDocument doc(bufferSize);

    // Parse JSON object
#ifndef SPOTIFY_PRINT_JSON_PARSE
    DeserializationError error = deserializeJson(doc, *client, DeserializationOption::Filter(filter));
#else
    ReadLoggingStream loggingStream(*client, Serial);
    DeserializationError error = deserializeJson(doc, loggingStream, DeserializationOption::Filter(filter));
#endif
    if (!error)
    {
#ifdef SPOTIFY_DEBUG
      serializeJsonPretty(doc, Serial);
#endif
      JsonObject item = doc["item"];

      int numArtists = item["artists"].size();
      if (numArtists > SPOTIFY_MAX_NUM_ARTISTS)
      {
        numArtists = SPOTIFY_MAX_NUM_ARTISTS;
      }
      current.numArtists = numArtists;

      for (int i = 0; i < current.numArtists; i++)
      {
        current.artists[i].artistName = item["artists"][i]["name"].as<const char *>();
        current.artists[i].artistUri = item["artists"][i]["uri"].as<const char *>();
      }

      current.albumName = item["album"]["name"].as<const char *>();
      current.albumUri = item["album"]["uri"].as<const char *>();

      JsonArray images = item["album"]["images"];

      // Images are returned in order of width, so last should be smallest.
      int numImages = images.size();
      int startingIndex = 0;
      if (numImages > SPOTIFY_NUM_ALBUM_IMAGES)
      {
        startingIndex = numImages - SPOTIFY_NUM_ALBUM_IMAGES;
        current.numImages = SPOTIFY_NUM_ALBUM_IMAGES;
      }
      else
      {
        current.numImages = numImages;
      }
#ifdef SPOTIFY_DEBUG
      Serial.print(F("Num Images: "));
      Serial.println(current.numImages);
      Serial.println(numImages);
#endif

      for (int i = 0; i < current.numImages; i++)
      {
        int adjustedIndex = startingIndex + i;
        current.albumImages[i].height = images[adjustedIndex]["height"].as<int>();
        current.albumImages[i].width = images[adjustedIndex]["width"].as<int>();
        current.albumImages[i].url = images[adjustedIndex]["url"].as<const char *>();
      }

      current.trackName = item["name"].as<const char *>();
      current.trackUri = item["uri"].as<const char *>();

      current.isPlaying = doc["is_playing"].as<bool>();

      current.progressMs = doc["progress_ms"].as<long>();
      current.durationMs = item["duration_ms"].as<long>();

      currentlyPlayingCallback(current);
    }
    else
    {
#ifdef SPOTIFY_SERIAL_OUTPUT
      Serial.print(F("deserializeJson() failed with code "));
      Serial.println(error.c_str());
#endif
      statusCode = -1;
    }
  }

  closeClient();
  return statusCode;
}

int SpotifyArduino::getPlayerDetails(processPlayerDetails playerDetailsCallback, const char *market)
{
  char command[100] = SPOTIFY_PLAYER_ENDPOINT;
  if (market[0] != 0)
  {
    char marketBuff[30];
    sprintf(marketBuff, "?market=%s", market);
    strcat(command, marketBuff);
  }

#ifdef SPOTIFY_DEBUG
  Serial.println(command);
  printStack();
#endif

  // Get from https://arduinojson.org/v6/assistant/
  const size_t bufferSize = playerDetailsBufferSize;
  if (autoTokenRefresh)
  {
    checkAndRefreshAccessToken();
  }

  int statusCode = makeGetRequest(command, _bearerToken);
#ifdef SPOTIFY_DEBUG
  Serial.print("Status Code: ");
  Serial.println(statusCode);
#endif
  if (statusCode > 0)
  {
    skipHeaders();
  }

  if (statusCode == 200)
  {

    StaticJsonDocument<192> filter;
    JsonObject filter_device = filter.createNestedObject("device");
    filter_device["id"] = true;
    filter_device["name"] = true;
    filter_device["type"] = true;
    filter_device["is_active"] = true;
    filter_device["is_private_session"] = true;
    filter_device["is_restricted"] = true;
    filter_device["volume_percent"] = true;
    filter["progress_ms"] = true;
    filter["is_playing"] = true;
    filter["shuffle_state"] = true;
    filter["repeat_state"] = true;

    // Allocate DynamicJsonDocument
    DynamicJsonDocument doc(bufferSize);

    // Parse JSON object
#ifndef SPOTIFY_PRINT_JSON_PARSE
    DeserializationError error = deserializeJson(doc, *client, DeserializationOption::Filter(filter));
#else
    ReadLoggingStream loggingStream(*client, Serial);
    DeserializationError error = deserializeJson(doc, loggingStream, DeserializationOption::Filter(filter));
#endif
    if (!error)
    {
      PlayerDetails playerDetails;

      JsonObject device = doc["device"];
      // Copy into buffer and make the last character a null just incase we went over.
      playerDetails.device.id = device["id"].as<const char *>();
      playerDetails.device.name = device["name"].as<const char *>();
      playerDetails.device.type = device["type"].as<const char *>();

      playerDetails.device.isActive = device["is_active"].as<bool>();
      playerDetails.device.isPrivateSession = device["is_private_session"].as<bool>();
      playerDetails.device.isRestricted = device["is_restricted"].as<bool>();
      playerDetails.device.volumePercent = device["volume_percent"].as<int>();

      playerDetails.progressMs = doc["progress_ms"].as<long>();
      playerDetails.isPlaying = doc["is_playing"].as<bool>();

      playerDetails.shuffleState = doc["shuffle_state"].as<bool>();

      const char *repeat_state = doc["repeat_state"];

      if (strncmp(repeat_state, "track", 5) == 0)
      {
        playerDetails.repeateState = repeat_track;
      }
      else if (strncmp(repeat_state, "context", 7) == 0)
      {
        playerDetails.repeateState = repeat_context;
      }
      else
      {
        playerDetails.repeateState = repeat_off;
      }

      playerDetailsCallback(playerDetails);
    }
    else
    {
#ifdef SPOTIFY_SERIAL_OUTPUT
      Serial.print(F("deserializeJson() failed with code "));
      Serial.println(error.c_str());
#endif
      statusCode = -1;
    }
  }

  closeClient();
  return statusCode;
}

int SpotifyArduino::getDevices(processDevices devicesCallback)
{

#ifdef SPOTIFY_DEBUG
  Serial.println(SPOTIFY_DEVICES_ENDPOINT);
  printStack();
#endif

  // Get from https://arduinojson.org/v6/assistant/
  const size_t bufferSize = getDevicesBufferSize;
  if (autoTokenRefresh)
  {
    checkAndRefreshAccessToken();
  }

  int statusCode = makeGetRequest(SPOTIFY_DEVICES_ENDPOINT, _bearerToken);
#ifdef SPOTIFY_DEBUG
  Serial.print("Status Code: ");
  Serial.println(statusCode);
#endif
  if (statusCode > 0)
  {
    skipHeaders();
  }

  if (statusCode == 200)
  {

    // Allocate DynamicJsonDocument
    DynamicJsonDocument doc(bufferSize);

    // Parse JSON object
#ifndef SPOTIFY_PRINT_JSON_PARSE
    DeserializationError error = deserializeJson(doc, *client);
#else
    ReadLoggingStream loggingStream(*client, Serial);
    DeserializationError error = deserializeJson(doc, loggingStream);
#endif
    if (!error)
    {

      uint8_t totalDevices = doc["devices"].size();

      SpotifyDevice spotifyDevice;
      for (int i = 0; i < totalDevices; i++)
      {
        JsonObject device = doc["devices"][i];
        spotifyDevice.id = device["id"].as<const char *>();
        spotifyDevice.name = device["name"].as<const char *>();
        spotifyDevice.type = device["type"].as<const char *>();

        spotifyDevice.isActive = device["is_active"].as<bool>();
        spotifyDevice.isPrivateSession = device["is_private_session"].as<bool>();
        spotifyDevice.isRestricted = device["is_restricted"].as<bool>();
        spotifyDevice.volumePercent = device["volume_percent"].as<int>();

        if (!devicesCallback(spotifyDevice, i, totalDevices))
        {
          //User has indicated they are finished.
          break;
        }
      }
    }
    else
    {
#ifdef SPOTIFY_SERIAL_OUTPUT
      Serial.print(F("deserializeJson() failed with code "));
      Serial.println(error.c_str());
#endif
      statusCode = -1;
    }
  }

  closeClient();
  return statusCode;
}

int SpotifyArduino::commonGetImage(char *imageUrl)
{
#ifdef SPOTIFY_DEBUG
  Serial.print(F("Parsing image URL: "));
  Serial.println(imageUrl);
#endif

  uint8_t lengthOfString = strlen(imageUrl);

  // We are going to just assume https, that's all I've
  // seen and I can't imagine a company will go back
  // to http

  if (strncmp(imageUrl, "https://", 8) != 0)
  {
#ifdef SPOTIFY_SERIAL_OUTPUT
    Serial.print(F("Url not in expected format: "));
    Serial.println(imageUrl);
    Serial.println("(expected it to start with \"https://\")");
#endif
    return false;
  }

  uint8_t protocolLength = 8;

  char *pathStart = strchr(imageUrl + protocolLength, '/');
  uint8_t pathIndex = pathStart - imageUrl;
  uint8_t pathLength = lengthOfString - pathIndex;
  char path[pathLength + 1];
  strncpy(path, pathStart, pathLength);
  path[pathLength] = '\0';

  uint8_t hostLength = pathIndex - protocolLength;
  char host[hostLength + 1];
  strncpy(host, imageUrl + protocolLength, hostLength);
  host[hostLength] = '\0';

#ifdef SPOTIFY_DEBUG

  Serial.print(F("host: "));
  Serial.println(host);

  Serial.print(F("len:host:"));
  Serial.println(hostLength);

  Serial.print(F("path: "));
  Serial.println(path);

  Serial.print(F("len:path: "));
  Serial.println(strlen(path));
#endif

  int statusCode = makeGetRequest(path, NULL, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", host);
#ifdef SPOTIFY_DEBUG
  Serial.print(F("statusCode: "));
  Serial.println(statusCode);
#endif
  if (statusCode == 200)
  {
    return getContentLength();
  }

  // Failed
  return -1;
}

bool SpotifyArduino::getImage(char *imageUrl, Stream *file)
{
  int totalLength = commonGetImage(imageUrl);

#ifdef SPOTIFY_DEBUG
  Serial.print(F("file length: "));
  Serial.println(totalLength);
#endif
  if (totalLength > 0)
  {
    skipHeaders(false);
    int remaining = totalLength;
    // This section of code is inspired but the "Web_Jpg"
    // example of TJpg_Decoder
    // https://github.com/Bodmer/TJpg_Decoder
    // -----------
    uint8_t buff[128] = {0};
    while (client->connected() && (remaining > 0 || remaining == -1))
    {
      // Get available data size
      size_t size = client->available();

      if (size)
      {
        // Read up to 128 bytes
        int c = client->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));

        // Write it to file
        file->write(buff, c);

        // Calculate remaining bytes
        if (remaining > 0)
        {
          remaining -= c;
        }
      }
      yield();
    }
    // ---------
#ifdef SPOTIFY_DEBUG
    Serial.println(F("Finished getting image"));
#endif
  }

  closeClient();

  return (totalLength > 0); //Probably could be improved!
}

bool SpotifyArduino::getImage(char *imageUrl, uint8_t **image, int *imageLength)
{
  int totalLength = commonGetImage(imageUrl);

#ifdef SPOTIFY_DEBUG
  Serial.print(F("file length: "));
  Serial.println(totalLength);
#endif
  if (totalLength > 0)
  {
    skipHeaders(false);
    uint8_t *imgPtr = (uint8_t *)malloc(totalLength);
    *image = imgPtr;
    *imageLength = totalLength;
    int remaining = totalLength;
    int amountRead = 0;

#ifdef SPOTIFY_DEBUG
    Serial.println(F("Fetching Image"));
#endif

    // This section of code is inspired but the "Web_Jpg"
    // example of TJpg_Decoder
    // https://github.com/Bodmer/TJpg_Decoder
    // -----------
    uint8_t buff[128] = {0};
    while (client->connected() && (remaining > 0 || remaining == -1))
    {
      // Get available data size
      size_t size = client->available();

      if (size)
      {
        // Read up to 128 bytes
        int c = client->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));

        // Write it to file
        memcpy((uint8_t *)imgPtr + amountRead, (uint8_t *)buff, c);

        // Calculate remaining bytes
        if (remaining > 0)
        {
          amountRead += c;
          remaining -= c;
        }
      }
      yield();
    }
    // ---------
#ifdef SPOTIFY_DEBUG
    Serial.println(F("Finished getting image"));
#endif
  }

  closeClient();

  return (totalLength > 0); //Probably could be improved!
}

int SpotifyArduino::getContentLength()
{

  if (client->find("Content-Length:"))
  {
    int contentLength = client->parseInt();
#ifdef SPOTIFY_DEBUG
    Serial.print(F("Content-Length: "));
    Serial.println(contentLength);
#endif
    return contentLength;
  }

  return -1;
}

void SpotifyArduino::skipHeaders(bool tossUnexpectedForJSON)
{
  // Skip HTTP headers
  if (!client->find("\r\n\r\n"))
  {
#ifdef SPOTIFY_SERIAL_OUTPUT
    Serial.println(F("Invalid response"));
#endif
    return;
  }

  if (tossUnexpectedForJSON)
  {
    // Was getting stray characters between the headers and the body
    // This should toss them away
    while (client->available() && client->peek() != '{')
    {
      char c = 0;
      client->readBytes(&c, 1);
#ifdef SPOTIFY_DEBUG
      Serial.print(F("Tossing an unexpected character: "));
      Serial.println(c);
#endif
    }
  }
}

int SpotifyArduino::getHttpStatusCode()
{
  char status[32] = {0};
  client->readBytesUntil('\r', status, sizeof(status));
#ifdef SPOTIFY_DEBUG
  Serial.print(F("Status: "));
  Serial.println(status);
#endif

  char *token;
  token = strtok(status, " "); // https://www.tutorialspoint.com/c_standard_library/c_function_strtok.htm

#ifdef SPOTIFY_DEBUG
  Serial.print(F("HTTP Version: "));
  Serial.println(token);
#endif

  if (token != NULL && (strcmp(token, "HTTP/1.0") == 0 || strcmp(token, "HTTP/1.1") == 0))
  {
    token = strtok(NULL, " ");
    if (token != NULL)
    {
#ifdef SPOTIFY_DEBUG
      Serial.print(F("Status Code: "));
      Serial.println(token);
#endif
      return atoi(token);
    }
  }

  return -1;
}

void SpotifyArduino::parseError()
{
  //This method doesn't currently do anything other than print
#ifdef SPOTIFY_SERIAL_OUTPUT
  DynamicJsonDocument doc(1000);
  DeserializationError error = deserializeJson(doc, *client);
  if (!error)
  {
    Serial.print(F("getAuthToken error"));
    serializeJson(doc, Serial);
  }
  else
  {
    Serial.print(F("Could not parse error"));
  }
#endif
}

void SpotifyArduino::closeClient()
{
  if (client->connected())
  {
#ifdef SPOTIFY_DEBUG
    Serial.println(F("Closing client"));
#endif
    client->stop();
  }
}

#ifdef SPOTIFY_DEBUG
void SpotifyArduino::printStack()
{
  char stack;
  Serial.print(F("stack size "));
  Serial.println(stack_start - &stack);
}
#endif
witnessmenow commented 3 years ago

I've created a new branch, please try that and post the serial output.

https://github.com/witnessmenow/spotify-api-arduino/tree/crashDebug%2333

eawystr commented 3 years ago

.....
Connected to haifisch
IP address: 192.168.1.137
Refreshing Access Tokens
grant_type=refresh_token&refresh_token=AQB8MslRtrbAb_W3NjuXkNaQ5RuD7oQ_5VFymPIOiq7fblbH1R9kmkWIx0VMKu8QoU-2yprBPwU7vx4mhOsPnBgQ8JSRhVuLVZi6KB2k5k4Xmv8TgQuGvuCra9bTCaBRDUY&client_id=f8aeea367669458dac14b17261ff61ea&client_secret=19cacb238db34764913ab16b7a210b98
stack size -1073421743
accounts.spotify.com
Status: HTTP/1.0 200 OK
HTTP Version: HTTP/1.0
Status Code: 200
status Code200
Inside statusCode == 200
Allocated Json
{"access_token":"BQByg4z5lWwuScztGNcftr8yO88-FZKWADiNFUs27sQA6uoO38w9iCeaMntrD2o7CYfdHzZKz3n7uM1x-_hzxSJ2BlMDrakMZ-M6pJvW8kBUC96bPB5gRYQoZonAcZ2XIr3dSjYcxi6Vhh00I_ksHItM0JFKTTvmXwJQkphNXe-ECa1wsrJw0HslNWeNnPQddqSxryJDEJwDn1IysbtCnCUcWbqrAqZmC4D-uIKeKTOORhJsNiah0TIfKqBpefp2G1ai-5scOOHRIu_ANabn-qjVcj4-y3fVV5KINyt3gxlxcNm3Sw","token_type":"Bearer","expires_in":3600,"scope":"playlist-read-private playlist-read-collaborative ugc-image-upload user-follow-read playlist-modify-private user-read-email user-read-private app-remote-control user-follow-modify user-modify-playback-state user-library-read user-library-modify playlist-modify-public user-read-playback-state user-read-currently-playing user-read-recently-played user-read-playback-position user-top-read"}Finished JSON deserilaize
No error
End of no error
Before Close client
In Close client
Guru Meditation Error: Core  1 panic'ed (InstrFetchProhibited). Exception was unhandled.
Core 1 register dump:
PC      : 0x00667ec0  PS      : 0x00060e30  A0      : 0x800d4c41  A1      : 0x3ffb1db0  
A2      : 0x3ffc1764  A3      : 0x3ffc1a18  A4      : 0x000000a0  A5      : 0xa793ee40  
A6      : 0x3ffb1cb0  A7      : 0x3f400da2  A8      : 0x800d304b  A9      : 0x3ffb1d90  
A10     : 0x3ffc1864  A11     : 0x0000000f  A12     : 0x00000000  A13     : 0x3f400ad1  
A14     : 0x00000132  A15     : 0x00000001  SAR     : 0x0000000a  EXCCAUSE: 0x00000014  
EXCVADDR: 0x00667ec0  LBEG    : 0x400014fd  LEND    : 0x4000150d  LCOUNT  : 0xfffffffc  

ELF file SHA256: 0000000000000000

Backtrace: 0x00667ec0:0x3ffb1db0 0x400d4c3e:0x3ffb1dd0 0x400d0b72:0x3ffb1f70 0x400d5f12:0x3ffb1fb0 0x400897b2:0x3ffb1fd0

Rebooting...

And decoded

PC: 0x00667ec0
EXCVADDR: 0x00667ec0

Decoding stack results
0x400d4c3e: SpotifyArduino::refreshAccessToken() at C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-crashDebug-33\src\SpotifyArduino.cpp line 228
0x400d0b72: setup() at C:\Users\DAVIDC~1\AppData\Local\Temp\arduino_modified_sketch_365107/getCurrentlyPlaying.ino line 114
0x400d5f12: loopTask(void*) at C:\Users\David Calderon\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\cores\esp32\main.cpp line 18
0x400897b2: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c line 143
witnessmenow commented 3 years ago

Ok we have narrowed down where it crashes,

client->isConnected(), the problem is why would it crash there, it makes no sense. And there is still the problem of why it works for everyone else and not for you

Can you try downgrade your version of esp32 core to 1.05? (Go to board manager, search for esp32 and there is a version drop down)

Or might be even easier to use a portable version of the Arduino ide so you don't have to impact what you currently have:

https://youtu.be/sTTY_w7Cuzk

eawystr commented 3 years ago

Same, I've downgraded to 1.0.5 and:

...
Connected to haifisch
IP address: 192.168.1.137
Refreshing Access Tokens
grant_type=refresh_token&refresh_token=AQB8MslRtrbAb_W3NjuXkNaQ5RuD7oQ_5VFymPIOiq7fblbH1R9kmkWIx0VMKu8QoU-2yprBPwU7vx4mhOsPnBgQ8JSRhVuLVZi6KB2k5k4Xmv8TgQuGvuCra9bTCaBRDUY&client_id=f8aeea367669458dac14b17261ff61ea&client_secret=19cacb238db34764913ab16b7a210b98
stack size -1073421743
accounts.spotify.com
Status: HTTP/1.0 200 OK
HTTP Version: HTTP/1.0
Status Code: 200
status Code200
Inside statusCode == 200
Allocated Json
{"access_token":"BQBwQT5kpmgDJnVUIbJIYQMECeysX3FsC3RfB-No1gsz89dDrr3sD71pJQG4aWIQv0hBAuBu_tHXUmu0XOR3n2EA9jBtfs5h2DyOBfpUb2vnOi29Y0LJbo5NH2_7blSEIvxy3fii8KJMzlbPDry0SRBp42Z_XqClOlTz7Ha8sHMm6_eKC1tSRZJpM2WoDl2Nlpjk_XoQoUWwDrLQdmk7VPkNpTV4oLOAr3IMSHrcWH3eB3AHgKJFRQJE1NXVTrX82UIsNrwrjzk7CIB8VUM-ixZaSKwLl-cw8YRkP8HSlGzauJITbw","token_type":"Bearer","expires_in":3600,"scope":"playlist-read-private playlist-read-collaborative ugc-image-upload user-follow-read playlist-modify-private user-read-email user-read-private app-remote-control user-follow-modify user-modify-playback-state user-library-read user-library-modify playlist-modify-public user-read-playback-state user-read-currently-playing user-read-recently-played user-read-playback-position user-top-read"}Finished JSON deserilaize
No error
End of no error
Before Close client
In Close client
Guru Meditation Error: Core  1 panic'ed (IllegalInstruction). Exception was unhandled.
Core 1 register dump:
PC      : 0x667ec000  PS      : 0x00060e30  A0      : 0x800d535d  A1      : 0x3ffb1db0  
A2      : 0x3ffc176c  A3      : 0x3ffc1a20  A4      : 0x000000a0  A5      : 0xb89f24b0  
A6      : 0x3ffb1cb0  A7      : 0x3f400da2  A8      : 0x800d3777  A9      : 0x3ffb1d90  
A10     : 0x3ffc186c  A11     : 0x0000000f  A12     : 0x00000000  A13     : 0x3f400ad1  
A14     : 0x00000132  A15     : 0x00000001  SAR     : 0x0000000a  EXCCAUSE: 0x00000000  
EXCVADDR: 0x00000000  LBEG    : 0x400014fd  LEND    : 0x4000150d  LCOUNT  : 0xfffffffc  

ELF file SHA256: 0000000000000000

Backtrace: 0x667ec000:0x3ffb1db0 0x400d535a:0x3ffb1dd0 0x400d12de:0x3ffb1f70 0x400d660a:0x3ffb1fb0 0x400897a2:0x3ffb1fd0

Rebooting...

Decoded

PC: 0x667ec000
EXCVADDR: 0x00000000

Decoding stack results
0x400d535a: SpotifyArduino::refreshAccessToken() at C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-crashDebug-33\src\SpotifyArduino.cpp line 228
0x400d12de: setup() at C:\Users\DAVIDC~1\AppData\Local\Temp\arduino_modified_sketch_279483/getCurrentlyPlaying.ino line 114
0x400d660a: loopTask(void*) at C:\Users\David Calderon\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.5\cores\esp32\main.cpp line 32
0x400897a2: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c line 143
witnessmenow commented 3 years ago

Can you try change the setup of your sketch to remove the part where it calls refreshAccessToken? Just comment it out like below or remove it.

The library will automatically detect you dont have a valid one when you make another call and try refresh it. I see some difference to what gets printed out on the serial monitor when using both which I'll need to look into.

void setup()
{

    Serial.begin(115200);

    WiFi.mode(WIFI_STA);
    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());

    // Handle HTTPS Verification
#if defined(ESP8266)
    client.setFingerprint(SPOTIFY_FINGERPRINT); // These expire every few months
#elif defined(ESP32)
    client.setCACert(spotify_server_cert);
#endif
    // ... or don't!
    //client.setInsecure();

    // If you want to enable some extra debugging
    // uncomment the "#define SPOTIFY_DEBUG" in ArduinoSpotify.h

//    Serial.println("Refreshing Access Tokens");
//    if (!spotify.refreshAccessToken())
//    {
//        Serial.println("Failed to get access tokens");
//    }
}
eawystr commented 3 years ago

Done.

.
Connected to haifisch
IP address: 192.168.1.137
Free Heap: 280600
getting currently playing song:
/v1/me/player/currently-playing?market=ES
stack size -1073421311
Refresh of the Access token is due, doing that now.
grant_type=refresh_token&refresh_token=AQB8MslRtrbAb_W3NjuXkNaQ5RuD7oQ_5VFymPIOiq7fblbH1R9kmkWIx0VMKu8QoU-2yprBPwU7vx4mhOsPnBgQ8JSRhVuLVZi6KB2k5k4Xmv8TgQuGvuCra9bTCaBRDUY&client_id=f8aeea367669458dac14b17261ff61ea&client_secret=19cacb238db34764913ab16b7a210b98
stack size -1073420863
accounts.spotify.com
Status: HTTP/1.0 200 OK
HTTP Version: HTTP/1.0
Status Code: 200
status Code200
Inside statusCode == 200
Allocated Json
{"access_token":"BQBzUQCgBLNlRo08wH35Dom49JZsDXLlBqVhOob8g2uT5VTBSjG-lXIvJw6W6C79M2s7_YYVagZ7NcaJxudChvCngwC3Ir4eI5lcQyb4BDZoBINtT138_kwGvAe8KRM7NlvXX03V6WRP9Ns__g2-Pr8P6pSJ_2e9IHd8UOSmtGSqrvpG7mcP3lxnCgdSZGAuPSanywxFNH6H3mKs2jvOuxSsV9gL0TlFxIP6ZYUhmSS65R-SJIofiSNj93XGmsRnVNZI3iOxWRAbi2uDZ5vr8iSbICIiUCE4MoBZsmJDXZn6-cdqoQ","token_type":"Bearer","expires_in":3600,"scope":"playlist-read-private playlist-read-collaborative ugc-image-upload user-follow-read playlist-modify-private user-read-email user-read-private app-remote-control user-follow-modify user-modify-playback-state user-library-read user-library-modify playlist-modify-public user-read-playback-state user-read-currently-playing user-read-recently-played user-read-playback-position user-top-read"}Finished JSON deserilaize
No error
End of no error
Before Close client
In Close client
Guru Meditation Error: Core  1 panic'ed (IllegalInstruction). Exception was unhandled.
Core 1 register dump:
PC      : 0x667ec000  PS      : 0x00060230  A0      : 0x800d5339  A1      : 0x3ffb1a40  
A2      : 0x3ffc176c  A3      : 0x3ffc1a20  A4      : 0x000000a0  A5      : 0x80fb61ad  
A6      : 0x3ffb1940  A7      : 0x3f400d6e  A8      : 0x800d3753  A9      : 0x3ffb1a20  
A10     : 0x3ffc186c  A11     : 0x0000000f  A12     : 0x00000000  A13     : 0x3f400a9d  
A14     : 0x00000132  A15     : 0x00000001  SAR     : 0x0000000a  EXCCAUSE: 0x00000000  
EXCVADDR: 0x00000000  LBEG    : 0x400014fd  LEND    : 0x4000150d  LCOUNT  : 0xfffffffc  

ELF file SHA256: 0000000000000000

Backtrace: 0x667ec000:0x3ffb1a40 0x400d5336:0x3ffb1a60 0x400d538d:0x3ffb1c00 0x400d541e:0x3ffb1c20 0x400d130d:0x3ffb1f90 0x400d65f5:0x3ffb1fb0 0x400897a2:0x3ffb1fd0

Rebooting...

Decoded:

PC: 0x667ec000
EXCVADDR: 0x00000000

Decoding stack results
0x400d5336: SpotifyArduino::refreshAccessToken() at C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-crashDebug-33\src\SpotifyArduino.cpp line 228
0x400d538d: SpotifyArduino::checkAndRefreshAccessToken() at C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-crashDebug-33\src\SpotifyArduino.cpp line 240
0x400d541e: SpotifyArduino::getCurrentlyPlaying(void (*)(CurrentlyPlaying), char const*) at C:\Users\David Calderon\Documents\Arduino\libraries\spotify-api-arduino-crashDebug-33\src\SpotifyArduino.cpp line 504
0x400d130d: loop() at C:\Users\DAVIDC~1\AppData\Local\Temp\arduino_modified_sketch_142759/getCurrentlyPlaying.ino line 211
0x400d65f5: loopTask(void*) at C:\Users\David Calderon\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.5\cores\esp32\main.cpp line 37
0x400897a2: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c line 143
witnessmenow commented 3 years ago

Ok, I have figured the problem out. Your access_token (one of the responses from that request) is way longer than mine and it is causing a memory overflow

Mine is ~170 characters long, yours is ~300 characters. The buffer that this eventually lives was only 200 characters and I never checked for an overflow, because I never expected the length of an access token to vary from person to person! Expect the unexpected I guess!

The branch has been updated with code that should solve this issue for you.

How did you get your RefreshToken?

Cause in my response for the same method my scope returns as:

"scope": "user-modify-playback-state user-read-playback-state user-read-email user-read-private"

yours returns as:

"scope":"playlist-read-private playlist-read-collaborative ugc-image-upload user-follow-read playlist-modify-private user-read-email user-read-private app-remote-control user-follow-modify user-modify-playback-state user-library-read user-library-modify playlist-modify-public user-read-playback-state user-read-currently-playing user-read-recently-played user-read-playback-position user-top-read"

Note: Please refresh your client secret in the Spotify Development console as currently people have access to your account

image

eawystr commented 3 years ago

Awsome!!

Worked like a charm!!

witnessmenow commented 3 years ago

Good to hear