From f09a95cc8bf0fb64bbbfb144a7bb377963a505cc Mon Sep 17 00:00:00 2001 From: Philippe G Date: Mon, 10 Jan 2022 00:46:56 -0800 Subject: [PATCH] artwork for Spotify --- components/display/display.c | 14 +++-- components/spotify/cspot_sink.c | 21 ++++++- components/tools/CMakeLists.txt | 1 + components/tools/tools.c | 105 ++++++++++++++++++++++++++++++++ components/tools/tools.h | 3 + 5 files changed, 137 insertions(+), 7 deletions(-) diff --git a/components/display/display.c b/components/display/display.c index 68d6b86a..4af7eec1 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -52,7 +52,8 @@ static EXT_RAM_ATTR struct { bool visible; } duration; struct { - bool enable, fit; + bool enable, active; + bool fit; bool updated; int tick; int offset; @@ -245,7 +246,7 @@ static void displayer_task(void *args) { GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line); // if we have not received artwork after 5s, display a default icon - if (displayer.artwork.enable && !displayer.artwork.updated && tick - displayer.artwork.tick > pdMS_TO_TICKS(5000)) { + if (displayer.artwork.active && !displayer.artwork.updated && tick - displayer.artwork.tick > pdMS_TO_TICKS(5000)) { ESP_LOGI(TAG, "no artwork received, setting default"); displayer_artwork((uint8_t*) default_artwork); } @@ -265,7 +266,7 @@ static void displayer_task(void *args) { * */ void displayer_artwork(uint8_t *data) { - if (!displayer.artwork.enable) return; + if (!displayer.artwork.active) return; int x = displayer.artwork.offset ? displayer.artwork.offset + ARTWORK_BORDER : 0; int y = x ? 0 : 32; @@ -420,7 +421,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) { switch(cmd) { case DISPLAYER_ACTIVATE: { char *header = va_arg(args, char*); - bool artwork = va_arg(args, int); + displayer.artwork.active = displayer.artwork.enable && va_arg(args, int); strncpy(displayer.header, header, HEADER_SIZE); displayer.header[HEADER_SIZE] = '\0'; displayer.state = DISPLAYER_ACTIVE; @@ -431,19 +432,20 @@ void displayer_control(enum displayer_cmd_e cmd, ...) { displayer.duration.visible = false; displayer.offset = displayer.boundary = 0; display_bus(&displayer, DISPLAY_BUS_TAKE); - if (artwork) GDS_SetTextWidth(display, displayer.artwork.offset); + if (displayer.artwork.active) GDS_SetTextWidth(display, displayer.artwork.offset); vTaskResume(displayer.task); break; } case DISPLAYER_SUSPEND: // task will display the line 2 from beginning and suspend displayer.state = DISPLAYER_IDLE; + displayer_artwork(NULL); display_bus(&displayer, DISPLAY_BUS_GIVE); break; case DISPLAYER_SHUTDOWN: // let the task self-suspend (we might be doing i2c_write) GDS_SetTextWidth(display, 0); - GDS_Clear(display, GDS_COLOR_BLACK); + displayer_artwork(NULL); displayer.state = DISPLAYER_DOWN; display_bus(&displayer, DISPLAY_BUS_GIVE); break; diff --git a/components/spotify/cspot_sink.c b/components/spotify/cspot_sink.c index bf6343dc..67677e88 100644 --- a/components/spotify/cspot_sink.c +++ b/components/spotify/cspot_sink.c @@ -13,6 +13,7 @@ #include "display.h" #include "accessors.h" #include "network_services.h" +#include "tools.h" #include "cspot_private.h" #include "cspot_sink.h" @@ -87,6 +88,19 @@ const static actrls_t controls = { cspot_volume_down, cspot_volume_up, cspot_toggle// knob left, knob_right, knob push }; +/**************************************************************************************** + * Download callback + */ +void got_artwork(uint8_t* data, size_t len, void *context) { + if (data) { + ESP_LOGI(TAG, "got artwork of %zu bytes", len); + displayer_artwork(data); + free(data); + } else { + ESP_LOGW(TAG, "artwork error or too large %zu", len); + } +} + /**************************************************************************************** * Command handler */ @@ -106,7 +120,7 @@ static bool cmd_handler(cspot_event_t event, ...) { switch(event) { case CSPOT_SETUP: actrls_set(controls, false, NULL, actrls_ir_action); - displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY", false); + displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY", true); break; case CSPOT_PLAY: displayer_control(DISPLAYER_TIMER_RUN); @@ -129,6 +143,11 @@ static bool cmd_handler(cspot_event_t event, ...) { uint32_t sample_rate = va_arg(args, uint32_t); int duration = va_arg(args, int); char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*); + char *artwork = va_arg(args, char*); + if (artwork) { + ESP_LOGI(TAG, "requesting artwork %s", artwork); + http_download(artwork, 128*1024, got_artwork, NULL); + } displayer_metadata(artist, album, title); displayer_timer(DISPLAYER_ELAPSED, loaded ? -1 : 0, duration); loaded = false; diff --git a/components/tools/CMakeLists.txt b/components/tools/CMakeLists.txt index 6a36b20f..c9644f5e 100644 --- a/components/tools/CMakeLists.txt +++ b/components/tools/CMakeLists.txt @@ -1,5 +1,6 @@ idf_component_register( SRCS operator.cpp tools.c trace.c REQUIRES _override esp_common pthread + PRIV_REQUIRES esp_http_client esp-tls INCLUDE_DIRS . ) diff --git a/components/tools/tools.c b/components/tools/tools.c index 4f582b81..1c877ff2 100644 --- a/components/tools/tools.c +++ b/components/tools/tools.c @@ -11,7 +11,11 @@ #include #include #include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "tools.h" +#include "esp_tls.h" +#include "esp_http_client.h" #include "esp_heap_caps.h" #include "esp_log.h" @@ -171,3 +175,104 @@ char * strdup_psram(const char * source){ } return ptr; } + +/**************************************************************************************** + * URL download + */ + +typedef struct { + void *user_context; + http_download_cb_t callback; + size_t max, bytes; + bool abort; + uint8_t *data; + char *url; +} http_context_t; + +static void http_downloader(void *arg); +static esp_err_t http_event_handler(esp_http_client_event_t *evt); + +void http_download(char *url, size_t max, http_download_cb_t callback, void *context) { + http_context_t *http_context = (http_context_t*) heap_caps_calloc(sizeof(http_context_t), 1, MALLOC_CAP_SPIRAM); + + http_context->callback = callback; + http_context->user_context = context; + http_context->max = max; + http_context->url = strdup(url); + + xTaskCreate(http_downloader, "downloader", 4*1024, http_context, ESP_TASK_PRIO_MIN + 1, NULL); +} + +static void http_downloader(void *arg) { + http_context_t *http_context = (http_context_t*) arg; + esp_http_client_config_t config = { + .url = http_context->url, + .event_handler = http_event_handler, + .user_data = http_context, + //.cert_pem = howsmyssl_com_root_cert_pem_start, + //.skip_cert_common_name_check = true, + }; + + esp_http_client_handle_t client = esp_http_client_init(&config); + esp_http_client_perform(client); +// esp_http_client_cleanup(client); + + free(http_context->url); + free(http_context); + + vTaskDelete(NULL); +} + +static esp_err_t http_event_handler(esp_http_client_event_t *evt) { + http_context_t *http_context = (http_context_t*) evt->user_data; + + if (http_context->abort) return ESP_FAIL; + + switch(evt->event_id) { + case HTTP_EVENT_ERROR: + http_context->callback(NULL, 0, http_context->user_context); + http_context->abort = true; + break; + case HTTP_EVENT_ON_HEADER: + if (!strcasecmp(evt->header_key, "Content-Length")) { + size_t len = atoi(evt->header_value); + if (!len || len > http_context->max) { + ESP_LOGI(TAG, "content-length null or too large %zu / %zu", len, http_context->max); + http_context->abort = true; + } + } + break; + case HTTP_EVENT_ON_DATA: { + size_t len = esp_http_client_get_content_length(evt->client); + if (!http_context->data) { + if ((http_context->data = (uint8_t*) malloc(len)) == NULL) { + http_context->abort = true; + ESP_LOGE(TAG, "gailed to allocate memory for output buffer %zu", len); + return ESP_FAIL; + } + } + memcpy(http_context->data + http_context->bytes, evt->data, evt->data_len); + http_context->bytes += evt->data_len; + break; + } + case HTTP_EVENT_ON_FINISH: + http_context->callback(http_context->data, http_context->bytes, http_context->user_context); + break; + case HTTP_EVENT_DISCONNECTED: { + int mbedtls_err = 0; + esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "HTTP download disconnect %d", err); + if (http_context->data) free(http_context->data); + http_context->callback(NULL, 0, http_context->user_context); + return ESP_FAIL; + } + break; + default: + break; + } + } + + return ESP_OK; +} + diff --git a/components/tools/tools.h b/components/tools/tools.h index 785d22e0..3b29a512 100644 --- a/components/tools/tools.h +++ b/components/tools/tools.h @@ -53,6 +53,9 @@ char* strdup_psram(const char * source); const char* str_or_unknown(const char * str); const char* str_or_null(const char * str); +typedef void (*http_download_cb_t)(uint8_t* data, size_t len, void *context); +void http_download(char *url, size_t max, http_download_cb_t callback, void *context); + extern const char unknown_string_placeholder[]; #ifdef __cplusplus