Skip to content

V2.0.0 Initial Release: Complete Spotify Controller for ESP32 with UI and Album Art Support #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from

Conversation

Electric-Diversions
Copy link

This pull request contributes a fully functional and extensible implementation of the Spotify Controller for the ThingPulse Color Kit Grande. Built on the original prototype, this version introduces a complete UI system, album art caching, and a refined internal architecture.

Key Features:
• Spotify Playback Control: Play, pause, skip tracks from the touch display.
• Album Art Display & Caching: Downloads and locally caches artwork via Spotify Web API.
• Multiple UI Views: Home, Cover Art, Clock, and Diagnostics views with easy screen switching.
• Designed for ESP32: Dual-core support, touch handling, and PlatformIO/Arduino compatibility.
• Extensible & Modular: Built for growth with a clean architecture and support for additional UI modes.

This version preserves the ThingPulse startup branding and includes significant refactoring to improve maintainability, performance, and future development.

@squix78
Copy link

squix78 commented May 16, 2025

Wow, this is an impressive PR. I would love to share your experience on the ThingPulse blog, can you contact me? And please give me some time to test the PR out

@marcelstoer
Copy link
Member

@Electric-Diversions Very nicely done, thanks! I am looking forward to taking this for a test ride. There's just a small thing that caught my attention while scrolling through the changes.

@squix78 This started off at https://support.thingpulse.com/1543/esp32-spotify-remote-where-is-logic-to-show-song-information

@Electric-Diversions
Copy link
Author

I'll also add this line (or similar) to the README.md usage instructions. I accidentally left it out.

  • Tap the top-left corner for Prev, the top-center for Pause/Play, and the top-right for Next when using views other than Home.

@squix78
Copy link

squix78 commented May 31, 2025

@Electric-Diversions thank you again for your work!

I noticed a couple of things:

  • you moved the wifi settings to connectivity.cpp. We sometimes have users that do not have a lot of experience with C/C++. It might be hard for them to find important settings in different files. Is there a good reason to move the wifi settings away from settings.h? Another advantage to have all customer specific settings in one file would be to mark the settings file as ignored by git and then avoid committing credentials to the repository. This is harder if mixed with other functions
  • same about spotify credentials
  • The screen stops for me on "Checking Spotify Status" where it should display "Open browser at http://tp-spotify.local" and waiting for the user to open the web page
  • The following is already a short coming with the main branch: the README should explain which settings where should be changed. Also it should mention that the filesystem should be uploaded. I did not upload the filesystem and then the filesystem was not properly initialized. Ideally there would also be a screen mentioning this. As a consequence I had to register after every start the refresh token and no cover art was displayed

Thank you

@Electric-Diversions
Copy link
Author

@squix78 . thanks! Please see my responses below:

  • Regarding the Wi-Fi and Spotify credentials/settings: I understand the concern. I’ll review and plan to move them back into settings.h. I had originally moved them while trying to better understand the codebase and consolidate related functionality.
  • For the initialization screen: that’s a formatting issue that slipped through. I’ll clean it up.
  • About the README: The existing Precondition section covers some of those details via the instructions link, but I agree with your points. It is annoying when the filesystem isn't uploaded. I’ll revise the README to clearly highlight which settings to change and the importance of uploading the filesystem.

@marcelstoer
Copy link
Member

(in addition to Dani's feedback)

@Electric-Diversions thanks again for this enormous contribution! I spent some time with this application today.

ThingPulse users are often hobbyists rather than professional engineers or developers. To cater for such a user group, hardware and software has to be as simple as possible (but not simpler 😄). Furthermore, the apps should ideally have a high degree of compatibility within a chip family.

I see some room for improvement in both areas. There's artificial complexity the app doesn't really need IMO.

  • I happened to use test this with an original 1st gen kit (4MB Wrover-B). The app crashed first due to the custom 8MB board definition and then due to partition misalignment (custom partitions file). Unless there's a specific reason I suggest we use the original configuration. You don't really need 8MB do you?
  • Do you really need the gnu++14 instruction or could we make this work with the standard setting?
  • There's code for encrypted WiFi credentials but the actual implementation doesn't do anything, does it? I feel for this use case we can safely keep the original "plain text" code.
  • The font includes at main.cpp:103pp fail the CI build as the paths are wrong.
  • Commented out code should IMO be removed as it adds no value (unless there are feature options).

@Electric-Diversions
Copy link
Author

Electric-Diversions commented Jun 1, 2025

@marcelstoer @squix78

Thanks for the feedback. I'm sorry it's been difficult getting the project running. I only have the current kit to test with and, having started with the Weather app, I don't think I ever tested it with a completely erased filesystem. I was able to recreate what Dani described though and I've now added a check for whether the filesystem is initialized and display a screen explaining the error.

I understand that many users are hobbyists and I am open to making the project more accessible to a broader audience. However, the project was developed to meet a specific functional need, and I have limited capacity to refactor it into more of a demonstration. The configuration changes mentioned were all made based on necessity. As the codebase grew, the available space became limited, putting at risk the ability to add planned features. The custom file partition was introduced so that the art cache could be a decent size and improve performance. The inclusion of C++14 allowed me to get past issues I had creating the view framework. It may feel over-engineered and artificially complex, but these solutions addressed real limitations and allowed the project to fully use the capabilities advertised by ThingPulse. Rather than see this as a break with older generations of products, I encourage you to look at this as an example project that demonstrates how to fully tap into the potential of the Color Kit Grande as sold today.

Regarding the encrypted WiFi, I'll be cleaning this up as part of the changes Dani requested. My original project actually used encrypted credentials and I stripped that out for the PR. I believe it is good privacy practice to minimize the surface area that credentials are exposed, but I understand the feeling it might be heavy. I think I have an idea that will allow the original settings.h approach while also offering a more advanced option for those who want more protection in this project or others. Much of this startup logic dates back to when I first got the kit working and it didn't receive much attention afterward.

Can you help me with the CI build issue? The paths look correct to me, and it builds fine locally.

Regarding the commented code, I will take a more aggressive stance at removing the comments and adding context for the ones I keep. I tried to base it on the original project's approach and leave comments that would help me and potentially others iterate over the code. Part of me worries I might need those comments later, but can't really defend keeping them beyond that.

Update: Found the path issue. The commit has the wrong case for the Font folder. It doesn't affect me locally and VSCode matches the code. I'll fix it.

@marcelstoer
Copy link
Member

I'm sorry it's been difficult getting the project running.

No worries, it's been an interesting experience. It wasn't immediately obvious to me what was going on and the crash dumps gave only vague clues.

I understand that many users are hobbyists and I am open to making the project more accessible to a broader audience.

My primary motivation behind the feedback is keeping support effort for Dani low. Making all code public, free for anyone to use (customer or not), also means dealing with user support for years to come. Removing as many potential obstacles as possible does help.

Also, I don't see my feedback as a list of tasks for you to fix. The better we understand your motivation behind doing things the way you did, the easier it is to assess what we might want to modify after merging.

The configuration changes mentioned were all made based on necessity. ... The custom file partition was introduced so that the art cache could be a decent size and improve performance.

Thanks for the hint. I didn't notice any performance issues while testing on my device so far but I'll pay more attention to this now.

@Electric-Diversions
Copy link
Author

@marcelstoer

Thanks for the additional context. My first prototype was sluggish and this implementation is heavily optimized to provide a responsive, quick, and reliable device (once it is up and running). I use the device extensively and it sits right next to my monitor. I also tried to lay a foundation I could use in future ESP32/Color Kit Grande efforts. I didn't know there were 4MB Wrover-B kits in circulation and didn't originally plan to release the code as open source. Once I get the next batch of updates out, we can better align on ThingPulse's hopes for the project. We can also walk through the design in more detail and I can provide feedback on ways to fit it into earlier generation kits.

The existing codebase may still fit at the lower memory limit (much of the space is taken up by the existing dependencies) and the file system can go back to 4MB by reducing the cache size from 60 albums to around 10. To reduce code complexity and size, there is logic that can be removed and still leave a functioning application — such as the extra views, performance monitoring instrumentation, cover art caching, build time logic, FileIO class, and the custom logger (which only exists because I couldn't get the log_x and ESP_LOGx macros to respect the tags and levels).

I think these things are useful to advanced users, but I appreciate it increases the support footprint and may make the project less accessible. However, I think the biggest challenge for inexperienced developers will be the multi-threaded architecture. I didn't start out with that in mind, but it makes a big difference from an end-user experience perspective.

@marcelstoer
Copy link
Member

marcelstoer commented Jun 3, 2025

I really appreciate you taking the time to engage in a discussion here.

The code we end up maintaining here doesn't necessarily need to be the same running on your device i.e. living in your fork. It's fine if our priorities don't exactly align. I would say, push the changes you feel comfortable with, then we'll take it from there.

I can provide feedback on ways to fit it into earlier generation kits

I don't think we need to change anything (apart from the board definition of course). The app works really well on mine so far.

I didn't know there were 4MB Wrover-B kits in circulation

They come with 4, 8 or 16MB each having 8MB PSRAM. The sizes are coded into the product identifier e.g. N4R8 → 4MB Flash, 8MB PSRAM.

...custom logger (which only exists because I couldn't get the log_x and ESP_LOGx macros to respect the tags and levels)

Yes, that's a good example for what I call "artificial complexity". I'm quite confident your logging code is bug free and just works, but the only code not requiring any maintenance is code that isn't there 😜 You are right, the log_x macros don't support tags but setting the level through -DCORE_DEBUG_LEVEL=n works well out of the box. We could easily search-replace all spLogX invocations, should we decide to stick to off-the-shelve logging.

@Electric-Diversions
Copy link
Author

Electric-Diversions commented Jun 9, 2025

Well, I pretty much had all the changes in and just as I was doing one last round of testing after doing a 'Full Clean' (which I've done before) I started experiencing an issue setting the refresh token with SpotifyArduino. I know why the issue is happening in the library's code, but don't understand why I suddenly started getting it. I'm investigating.

@marcelstoer
Copy link
Member

marcelstoer commented Jun 9, 2025

I also spotted an issue with the playback controls; not something I see consistently though IIRC.

14:11:51.714 > [170574][I][SCLogger.cpp:94] logMessage(): [Input] Initial Press Detected x=412, y=90 [src/main.cpp:545][I] (UIHandler)
14:11:51.744 > [170598][I][SCLogger.cpp:94] logMessage(): [Player] Skipping to the next song. [src/SpotifyPlayer.cpp:268][I] (UIHandler)
14:11:51.749 > /v1/me/player/next
14:11:51.752 > api.spotify.com
14:11:52.483 > [171337][E][ssl_client.cpp:37] _handle_error(): [send_ssl_data():382]: (-80) UNKNOWN ERROR CODE (0050)
14:11:52.493 > Failed to send request

I didn't dig into this yet.

@Electric-Diversions
Copy link
Author

Thx. Does the playback operation still perform from an end-user perspective (i.e., the skip takes place anyway)? If so, I think I've seen that logging from the very beginning but wasn't able to identify the cause. I kinda forgot it was doing that and assumed it was an issue with the library. If you see anything I am doing to cause it, please let me know. :)

@marcelstoer
Copy link
Member

Does the playback operation still perform from an end-user perspective

No, and this seems to correlate with the fact that the error occurs in **send**_ssl_data(). Those "unknown SSL errors" might be caused by low heap space even though in such cases the messages are usually more specific in my experience.

So, I monitored the stats screen you built, and I found that we get dangerously close to heap exhaustion even in regular playback mode. The value oscillates between 70-something and 98%.
IMG_8335

I've seen that logging from the very beginning but wasn't able to identify the cause.

Once I silence the library's debug out (more about that below), with playback stopped when the device starts, I see this - maybe what you refer to?

0:13:29.559 > [ 10725][I][SCLogger.cpp:94] logMessage(): [General] Current local time: 2025-06-09 13:13:29 [src/main.cpp:272][I] (loopTask)
20:13:31.337 > [ 12503][E][ssl_client.cpp:37] _handle_error(): [data_to_read():361]: (-76) UNKNOWN ERROR CODE (004C)
20:13:34.148 > [ 15318][E][ssl_client.cpp:37] _handle_error(): [data_to_read():361]: (-76) UNKNOWN ERROR CODE (004C)
20:13:36.960 > [ 18124][E][ssl_client.cpp:37] _handle_error(): [data_to_read():361]: (-76) UNKNOWN ERROR CODE (004C)
20:13:39.760 > [ 20928][E][ssl_client.cpp:37] _handle_error(): [data_to_read():361]: (-76) UNKNOWN ERROR CODE (004C)
20:13:42.564 > [ 23733][E][ssl_client.cpp:37] _handle_error(): [data_to_read():361]: (-76) UNKNOWN ERROR CODE (004C)
20:13:45.372 > [ 26540][E][ssl_client.cpp:37] _handle_error(): [data_to_read():361]: (-76) UNKNOWN ERROR CODE (004C)
20:13:48.183 > [ 29345][E][ssl_client.cpp:37] _handle_error(): [data_to_read():361]: (-76) UNKNOWN ERROR CODE (004C)
20:13:50.978 > [ 32145][E][ssl_client.cpp:37] _handle_error(): [data_to_read():361]: (-76) UNKNOWN ERROR CODE (004C)
...

Side note 1: I'm pretty sure there's a bug in the library when it reports the stack size in debug mode; shouldn't be negative.

19:58:48.444 > stack size -1073590367

If you see anything I am doing to cause it, please let me know.

I still don't know, sorry. Still investigating...
I wonder why your device would behave differently. Yes, you use different hardware but that only affects flash size.

Side note 2: I haven't found a way to disable the library's debug mode other than to go into .pio/libdeps/thingpulse-color-kit-grande/SpotifyArduino/src/SpotifyArduino.h and uncomment the defines. Do you think we should mention this in the README?

src/settings.h Outdated
** timezone Europe/Zurich as per
** https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
*/
#define TIMEZONE "CST6CDT,M3.2.0,M11.1.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You changed the timezone but not the corresponding comment ("timezone Europe/Zurich as per...").

20:46:53.184 > [ 13836][I][util.cpp:58] initTime(): UTC time: 2025-06-09 18:46:53.
20:46:53.190 > [ 13842][I][util.cpp:97] setTimezone(): Setting timezone to 'CST6CDT,M3.2.0,M11.1.0'.
20:46:53.193 > [ 13850][I][SCLogger.cpp:94] logMessage(): [General] Current local time: 2025-06-09 13:46:53 [src/main.cpp:272][I] (loopTask)

Also, I feel it would make sense to reintroduce the #ifdef DATE_TIME_FORMAT_US-section in this file and use the format specifiers (→ HomeView::handleClock()). The time is currently displayed in US format.

}
else
{
if (strcmp(currentlyPlaying.trackUri, _currentlyPlayingMetadata.trackUri) != 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This crash-reboots the device if you are on the Spotify free tier (i.e. not premium) when Spotify injects a commercial into the playback stream.

21:16:00.731 > Backtrace: 0x4008c3ac:0x3ffdb7b0 0x400d95ae:0x3ffdb7c0 0x400d967f:0x3ffdb880 0x400f560b:0x3ffdb920 0x400d925f:0x3ffdbc10 0x400d97b5:0x3ffdbc50
21:16:01.150 >   #0  0x4008c3ac in strcmp at /builds/idf/crosstool-NG/.build/HOST-x86_64-apple-darwin12/xtensa-esp32-elf/src/newlib/newlib/libc/machine/xtensa/strcmp.S:467
21:16:01.150 >   #1  0x400d95ae in SpotifyPlayer::refreshCurrentSong(CurrentlyPlaying) at src/SpotifyPlayer.cpp:629
21:16:01.150 >   #2  0x400d967f in SpotifyPlayer::getCurrentlyPlayingCallback(CurrentlyPlaying) at src/SpotifyPlayer.cpp:94
21:16:01.150 >   #3  0x400f560b in SpotifyArduino::getCurrentlyPlaying(void (*)(CurrentlyPlaying), char const*) at .pio/libdeps/thingpulse-color-kit-grande/SpotifyArduino/src/SpotifyArduino.cpp:719
21:16:01.150 >   #4  0x400d925f in SpotifyPlayer::refreshCurrentTrack() at src/SpotifyPlayer.cpp:438
21:16:01.150 >   #5  0x400d97b5 in SpotifyPlayer::refreshCurrentSongTask(void*) at src/SpotifyPlayer.cpp:830

@Electric-Diversions
Copy link
Author

Electric-Diversions commented Jun 10, 2025

I've pushed the latest batch of changes to the GitHub repository. I resolved the issue I was having yesterday. I'll continue testing them and maybe iterate over the documentation some. I can also provide more context on some of the decisions I made. I'm going to be less available over the next few days and wanted to get out what I have so far. Here is a recap of the changes:

  • Switched back to no_ota.csv partitions setup to support the ESP-WROVER-B. Code now checks partition size and uses a 10 album art cache size for smaller partitions and a 60 album size for devices with enough space.
  • Switched back to the out of the box esp-wrover-kit definition.
  • Fixed and improved display of the “Open browser at http://tp-spotify.local” message during the Getting Spotify Token progress bar state.
  • Added error screen when filesystem is not initialized.
  • Fixed case sensitivity issue in #include "Fonts/" vs "fonts/" that caused pipeline build failures.
  • Moved credential hardcoding back to settings.h; introduced optional user.ini file with .gitignore support and enhanced privacy options.
  • Expanded README.md to include additional instructions. Added new UserSettings.md file to documentation folder.
  • Additional commented code removal.

@Electric-Diversions
Copy link
Author

Electric-Diversions commented Jun 10, 2025

@marcelstoer
Some initial comments on your latest feedback:

  • Heap Exhaustion Concerns: Sorry for the confusion. I'm actually showing free heap there. It will show in RED if the available heap drops lower than I've seen in the past.
  • Prev/Next Track error logs: I confirmed I get the same logs you do when I go forward or back; however, the action works.
  • (-76) ssl_client issue: I have noticed also since the beginning that there are situations where I will see those or similar errors. If the Spotify session is stopped and expires, the user controls won't work - for example, depending on the Spotify client, stopping music may not be replayable from the API. I've pretty much attributed those to the SpotifyArduino library not handling edge cases well. I haven't ever noticed any issues other than the logging. I'm open to addressing the logging if it is something the caller can do.
  • SpotifyArduino.h Logging: Yes. I'm thinking maybe a Known Issues section with hints on how to do things like that.
  • TimeZone: I restored the TimeZone back to Zurich time as part of the settings.h changes in this drop. I like the idea of putting the format specifiers back in. I added the Clock view months after taking them out and forgot they were even in the original header.
  • Spotify free tier: Good catch. I haven't tested the free tier and wrongly thought the API wasn't even supported. Interestingly, I've tested a lot while using Spotify's DJ X and it handles the DJ talking with no track information. I don't currently have a free account to test with. (update: see next comment for potential fix).

@Electric-Diversions
Copy link
Author

Electric-Diversions commented Jun 10, 2025

Concerning the Spotify free tier, can you send me the SpotifyArduino json dump of an ad playing? I wonder how it differs from DJ X talking.

Update 6/10: I suspect this code put at the top right after the first spLogI statement in SpotifyPlayer::refreshCurrentSong(CurrentlyPlaying currentlyPlaying) will fix the issue. Ads should have a value of 'ad' for playing type; however, the SpotifyArduino library just has track, episode, and other. If this works for you, I will play with it some. Right now it will filter out audiobooks too which isn't necessary.

    // If not a song, update the UI accordingly and indicate music isn't available
    if (currentlyPlaying.currentlyPlayingType != SpotifyPlayingType::track)
    {
        spLogI(LOGTAG_GENERAL, " _isMusicAvailable set to false. currentlyPlayingType is not track." );
        _isMusicAvailable = false; 
        // force a refresh to get the waiting message
        postScuiMessage(SCUIMessageType::UM_MARK_DIRTY,
                        "",
                        true);        
        return;
    }

@marcelstoer
Copy link
Member

Thanks for your comments!

Prev/Next Track error logs: I confirmed I get the same logs you do when I go forward or back; however, the action works.

I set the log level back to 5 and tested this again.

22:13:51.250 > [ 46712][I][SCLogger.cpp:94] logMessage(): [Input] Initial Press Detected x=419, y=98 [src/main.cpp:540][I] (UIHandler)
22:13:51.269 > [ 46736][I][SCLogger.cpp:94] logMessage(): [Player] Skipping to the next song. [src/SpotifyPlayer.cpp:244][I] (UIHandler)
22:13:51.446 > [ 46908][V][ssl_client.cpp:321] stop_ssl_socket(): Cleaning SSL connection.
22:13:51.454 > [ 46916][V][ssl_client.cpp:62] start_ssl_client(): Free internal heap before TLS 164944
22:13:51.462 > [ 46924][V][ssl_client.cpp:68] start_ssl_client(): Starting socket
22:13:51.477 > [ 46938][V][ssl_client.cpp:146] start_ssl_client(): Seeding the random number generator
22:13:51.480 > [ 46947][V][ssl_client.cpp:155] start_ssl_client(): Setting up the SSL/TLS structure...
22:13:51.488 > [ 46955][V][ssl_client.cpp:178] start_ssl_client(): Loading CA cert
22:13:51.498 > [ 46966][V][ssl_client.cpp:254] start_ssl_client(): Setting hostname for TLS session...
22:13:51.507 > [ 46974][V][ssl_client.cpp:269] start_ssl_client(): Performing the SSL/TLS handshake...
22:13:52.280 > [ 47747][V][ssl_client.cpp:290] start_ssl_client(): Verifying peer X.509 certificate...
22:13:52.288 > [ 47755][V][ssl_client.cpp:298] start_ssl_client(): Certificate verified.
22:13:52.300 > [ 47762][V][ssl_client.cpp:313] start_ssl_client(): Free internal heap after TLS 123392
22:13:52.302 > [ 47770][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 5 bytes...
22:13:52.318 > [ 47780][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 18 bytes...
22:13:52.324 > [ 47791][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 9 bytes...
22:13:52.336 > [ 47804][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 2 bytes...
22:13:52.347 > [ 47814][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 6 bytes...
22:13:52.357 > [ 47824][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 15 bytes...
22:13:52.373 > [ 47835][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 2 bytes...
22:13:52.384 > [ 47845][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 24 bytes...
22:13:52.394 > [ 47856][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 2 bytes...
22:13:52.404 > [ 47866][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 14 bytes...
22:13:52.409 > [ 47876][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 16 bytes...
22:13:52.420 > [ 47887][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 2 bytes...
22:13:52.431 > [ 47897][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 15 bytes...
22:13:52.447 > [ 47909][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 234 bytes...
22:13:52.452 > [ 47919][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 2 bytes...
22:13:52.463 > [ 47930][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 23 bytes...
22:13:52.473 > [ 47940][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 2 bytes...
22:13:52.483 > [ 47950][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 16 bytes...
22:13:52.494 > [ 47961][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 1 bytes...
22:13:52.504 > [ 47971][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 2 bytes...
22:13:52.514 > [ 47981][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 2 bytes...
22:13:52.525 > [ 47992][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 0 bytes...
22:13:52.566 > [ 48033][V][ssl_client.cpp:381] send_ssl_data(): Handling error -80
22:13:52.578 > [ 48040][E][ssl_client.cpp:37] _handle_error(): [send_ssl_data():382]: (-80) UNKNOWN ERROR CODE (0050)

I'm reluctant to draw much conclusion from the extra statements. However, it's reassuring that the error doesn't seem to be related to the handshake process. The only thing that sticks out to me is Writing HTTP request with 0 bytes right before the error.

I also updated the platform to espressif32@~6.11.0 (up from 6.10); makes no difference.

It's not possible for potentially concurrent requests to garble the SSL stack? The "next track" request still being processed while the next "fetch song info" request is issued?

Concerning the Spotify free tier, can you send me the SpotifyArduino json dump of an ad playing? I wonder how it differs from DJ X talking.

As you mentioned in the update, it's currently_playing_type = ad.

{
  "context": {
    "uri": "spotify:playlist:37i9dQZF1DWX7rdRjOECPW"
  },
  "progress_ms": 7079,
  "item": null,
  "currently_playing_type": "ad",
  "is_playing": true
}

I tested your proposed fix - all good 👍

@Electric-Diversions
Copy link
Author

Electric-Diversions commented Jun 12, 2025

Glad that fixed the Ad issue! I'll clean up and test the approach some and commit it to the PR.

It's not possible for potentially concurrent requests to garble the SSL stack? The "next track" request still being processed while the next "fetch song info" request is issued?

I don't think so for these reasons:

  • I have had the issue since my very first invoke of the API long before I ever added multi-threading support. To confirm this, I pulled up my early prototype code from December based on the original GitHub project before I made my own copy and it shows the exact same error. This code looks almost exactly like the original project except for some very basic code displaying song information and allowing Prev/Pause/Next operations.
23:24:36.283 > [ 67244][V][ssl_client.cpp:369] send_ssl_data(): Writing HTTP request with 0 bytes...
23:24:36.440 > [ 67407][V][ssl_client.cpp:381] send_ssl_data(): Handling error -80
23:24:36.446 > [ 67408][E][ssl_client.cpp:37] _handle_error(): [send_ssl_data():382]: (-80) UNKNOWN ERROR CODE (0050)
23:24:36.452 > [ 67412][V][ssl_client.cpp:321] stop_ssl_socket(): Cleaning SSL connection.
23:24:36.457 > Failed to send request
  • It is always the exact error and always happens. If it was a timing issue, I would expect it to be more random.
  • When I developed the application I had a 15 second delay between refreshes and the error was always logged when doing the music control operations.
  • In the final design, SpotifyPlayer wraps all the networking calls with the _xSemaphoreNetwork semaphore. There shouldn't be a path where two calls to SpotifyArdiuno overlap each other.

UPDATE: Found this: witnessmenow/spotify-api-arduino#69

@marcelstoer
Copy link
Member

marcelstoer commented Jun 12, 2025

Your reasoning makes perfect sense. I'll investigate further by making "standalone" requests using the library in an isolated sketch.

Found this: witnessmenow/spotify-api-arduino#69

I'll send an MR for that to the library project when I fix witnessmenow/spotify-api-arduino#76

@Electric-Diversions
Copy link
Author

Hmmm, I was planning on fixing the Ad issue with this:

    // If not a track or episode, update the UI accordingly and indicate music isn't available
    if (currentlyPlaying.currentlyPlayingType == SpotifyPlayingType::other)

However, if you are going to add Ad as a supported type, I probably should explicitly check for Track and Episode instead.

If you are feeling like contributing back the fix for the logging and adding the Ad type, I have another SpotifyArduino contribution for you if you are game. Could you initialize the pointer instance variables in the SpotifyArduiino header file:

  char *_refreshToken = nullptr;
  const char *_clientId = nullptr;
  const char *_clientSecret = nullptr;

_refreshToken isn't initialized today and setRefreshToken(const char *refreshToken) can try to release the pointer corrupting the heap and causing a crash. It doesn't happen when the instance of SpotifyArduino is a global in a header file because in that case I think the memory is zero-initialized due to C++ static storage duration rules before any dynamic initialization occurs; however, if the class is initialized at runtime, _refreshToken can point to anything and the heap can be corrupted when refreshToken deletes the memory at _refreshToken. This is the issue I experienced when getting ready to release the last batch of updates. I could correct it by simply initializing _refreshToken. Here is the problem code:

void SpotifyArduino::setRefreshToken(const char *refreshToken)
{
    int newRefreshTokenLen = strlen(refreshToken);
    if (_refreshToken == NULL || strlen(_refreshToken) < newRefreshTokenLen)
    {
        delete _refreshToken;
        _refreshToken = new char[newRefreshTokenLen + 1]();
    }

    strncpy(_refreshToken, refreshToken, newRefreshTokenLen + 1);
}

When _refreshToken is not initialized and points to arbitrary memory, somethings the string length is less than the value in newRefreshTokenLen and it attempts to release what _refreshToken points to. It is a nasty error.

@marcelstoer
Copy link
Member

marcelstoer commented Jun 13, 2025

I probably should explicitly check for Track and Episode instead.

👍 Being defensive here is more future proof anyway IMO. Spotify could extend the enum with a new value, which would break things for us if we don't explicitly check for "known to be working"-values.

If you are feeling like contributing back the fix for the logging and adding the Ad type, I have another SpotifyArduino contribution for you if you are game.

Thanks, I created witnessmenow/spotify-api-arduino#77 to wrap this up.

@Electric-Diversions
Copy link
Author

I have started adding support for US/Non-US date/time formatting on the Home and Clock views.

@Electric-Diversions
Copy link
Author

Hi @marcelstoer @squix78

I think I have everything in that was mentioned except for not using gnu++14. Backporting to C++11 should be doable, though. The only dependency on C++14 is the use of std::make_unique throughout the view framework. I'm not aware of any issues this is causing with the existing project dependencies; however, if you really want to go back to C++11, I think all that you need to do is implement your own version of std::make_unique.

I realize that I didn't necessarily make the same design decisions that you would have. I do try to manage complexity, but at the end of the day, it is about what we're prioritizing. There is a lot going on in this codebase and being able to make sense of the logging based on function and noting which tasks are doing the logging adds a lot of value. Similarly, having the credentials in plain text on a filesystem that is backed up multiple times and also stored in the cloud can be more complex than just encrypting them and not worrying about it.

While pulling out items to reduce code maintenance might make sense for you, I hope you will resist the urge until you see indications of a problem. Doing so will make it easier for me to contribute in the future. While I don't know what the future will hold, I plan to keep my copies running for as long as I can and am probably not done with the code.

Here is a recap of everything that has been added/updated since I first created the PR.

  1. Switched back to no_ota.csv partitions setup to support the ESP-WROVER-B. Code now checks partition size and uses a 10 album art cache size for smaller partitions and a 60 album size for devices with enough space.
  2. Switched back to the out of the box esp-wrover-kit definition.
  3. Fixed and improved display of the “Open browser at http://tp-spotify.local” message during the Getting Spotify Token progress bar state.
  4. Added error screen when filesystem is not initialized.
  5. Fixed case sensitivity issue in #include "Fonts/" vs "fonts/" that caused pipeline build failures.
  6. Moved credential hardcoding back to settings.h; introduced optional user.ini file with .gitignore support and enhanced privacy options.
  7. Expanded README.md to include additional instructions. Added new UserSettings.md file to documentation folder.
    • Filesystem setup
    • User settings
    • Uploading code to device
  8. Additional commented code removal.
  9. Added configuration information to the Diagnostic View.
  10. Added support for Spotify free-tier / fixed fatal error when ads play.
  11. Created Tips and Known Issues documenting:
    • How to turn off SpotifyArduino logging
    • How to expand album art cache from 10 albums to 60
    • How to update WiFi and Spotify credentials without modifying source code
    • Spotify Premium vs. Ad Supported Tier
    • Known logged errors when starting and an active device is not present
    • Known logged errors when performing playback operations
    • Limitations using controls when an active device is no longer present
  12. Added support for international (not US) date/time formatting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants