From 90f53db953c8ef7fb98b511c5d41ecbbb929e383 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 23 Sep 2023 22:20:01 -0700 Subject: [PATCH 01/11] http_download needs a bigger stack for cspot which required to move it to EXTRAM --- build-scripts/I2S-4MFlash-sdkconfig.defaults | 2 +- build-scripts/Muse-sdkconfig.defaults | 2 +- build-scripts/SqueezeAmp-sdkconfig.defaults | 2 +- components/services/monitor.c | 2 +- components/spotify/cspot_sink.c | 6 +- components/squeezelite/output_i2s.c | 1 + components/tools/tools.c | 109 ++++++++++++++----- components/tools/tools.h | 12 ++ 8 files changed, 100 insertions(+), 36 deletions(-) diff --git a/build-scripts/I2S-4MFlash-sdkconfig.defaults b/build-scripts/I2S-4MFlash-sdkconfig.defaults index 4a6633e48..967d3ba63 100644 --- a/build-scripts/I2S-4MFlash-sdkconfig.defaults +++ b/build-scripts/I2S-4MFlash-sdkconfig.defaults @@ -948,7 +948,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y -CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 # CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set CONFIG_FREERTOS_ASSERT_DISABLE=y CONFIG_FREERTOS_ISR_STACKSIZE=2096 diff --git a/build-scripts/Muse-sdkconfig.defaults b/build-scripts/Muse-sdkconfig.defaults index 68ab922d1..709a0d9ed 100644 --- a/build-scripts/Muse-sdkconfig.defaults +++ b/build-scripts/Muse-sdkconfig.defaults @@ -907,7 +907,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y -CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 # CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set CONFIG_FREERTOS_ASSERT_DISABLE=y CONFIG_FREERTOS_ISR_STACKSIZE=2096 diff --git a/build-scripts/SqueezeAmp-sdkconfig.defaults b/build-scripts/SqueezeAmp-sdkconfig.defaults index d02fbd2ae..94db1f346 100644 --- a/build-scripts/SqueezeAmp-sdkconfig.defaults +++ b/build-scripts/SqueezeAmp-sdkconfig.defaults @@ -918,7 +918,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y -CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 # CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set CONFIG_FREERTOS_ASSERT_DISABLE=y CONFIG_FREERTOS_ISR_STACKSIZE=2096 diff --git a/components/services/monitor.c b/components/services/monitor.c index 3316f8f33..f874dc6ec 100644 --- a/components/services/monitor.c +++ b/components/services/monitor.c @@ -26,7 +26,7 @@ #include "cJSON.h" #include "tools.h" -#define PSEUDO_IDLE_STACK_SIZE (8*1024) +#define PSEUDO_IDLE_STACK_SIZE (6*1024) #define MONITOR_TIMER (10*1000) #define SCRATCH_SIZE 256 diff --git a/components/spotify/cspot_sink.c b/components/spotify/cspot_sink.c index 903408243..03eddb1d1 100644 --- a/components/spotify/cspot_sink.c +++ b/components/spotify/cspot_sink.c @@ -136,10 +136,8 @@ static bool cmd_handler(cspot_event_t event, ...) { displayer_timer(DISPLAYER_ELAPSED, va_arg(args, int), -1); break; case CSPOT_TRACK_INFO: { - uint32_t duration = va_arg(args, int); - uint32_t offset = 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*); + uint32_t duration = va_arg(args, int), offset = va_arg(args, int); + char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*), *artwork = va_arg(args, char*); if (artwork && displayer_can_artwork()) { ESP_LOGI(TAG, "requesting artwork %s", artwork); http_download(artwork, 128*1024, got_artwork, NULL); diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index bf4c0e5e1..d564c4ce8 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -742,6 +742,7 @@ static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst) { register u16_t aux; #endif + // we assume frame == 0 as well... if (!src) { count = 192; vu = VUCP24[0]; diff --git a/components/tools/tools.c b/components/tools/tools.c index 9a6af79af..7f9f5abea 100644 --- a/components/tools/tools.c +++ b/components/tools/tools.c @@ -1,4 +1,4 @@ -/* +/* * (c) Philippe G. 20201, philippe_44@outlook.com * see other copyrights below * @@ -20,6 +20,10 @@ #include "esp_log.h" #include "tools.h" +#if CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS < 2 +#error CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS must be at least 2 +#endif + const static char TAG[] = "tools"; /**************************************************************************************** @@ -104,11 +108,11 @@ static uint8_t UNICODEtoCP1252(uint16_t chr) { void utf8_decode(char *src) { uint32_t codep = 0, state = UTF8_ACCEPT; char *dst = src; - + while (src && *src) { if (!decode(&state, &codep, *src++)) *dst++ = UNICODEtoCP1252(codep); } - + *dst = '\0'; } @@ -178,12 +182,61 @@ char * strdup_psram(const char * source){ } /**************************************************************************************** - * URL download + * Task manager + */ +#define TASK_TLS_INDEX 1 + +typedef struct { + StaticTask_t *xTaskBuffer; + StackType_t *xStack; +} task_context_t; + +static void task_cleanup(int index, task_context_t *context) { + free(context->xTaskBuffer); + free(context->xStack); + free(context); +} + +BaseType_t xTaskCreateEXTRAM( TaskFunction_t pvTaskCode, + const char * const pcName, + configSTACK_DEPTH_TYPE usStackDepth, + void *pvParameters, + UBaseType_t uxPriority, + TaskHandle_t *pxCreatedTask) { + // create the worker task as a static + task_context_t *context = calloc(1, sizeof(task_context_t)); + context->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT)); + context->xStack = heap_caps_malloc(usStackDepth,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT)); + TaskHandle_t handle = xTaskCreateStatic(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, context->xStack, context->xTaskBuffer); + + // store context in TCB or free everything in case of failure + if (!handle) { + free(context->xTaskBuffer); + free(context->xStack); + free(context); + } else { + vTaskSetThreadLocalStoragePointerAndDelCallback( handle, TASK_TLS_INDEX, context, (TlsDeleteCallbackFunction_t) task_cleanup); + } + + if (pxCreatedTask) *pxCreatedTask = handle; + return handle != NULL ? pdPASS : pdFAIL; +} + +void vTaskDeleteEXTRAM(TaskHandle_t xTask) { + /* At this point we leverage FreeRTOS extension to have callbacks on task deletion. + * If not, we need to have here our own deletion implementation that include delayed + * free for when this is called with NULL (self-deletion) + */ + vTaskDelete(xTask); +} + +/**************************************************************************************** + * URL download */ - + typedef struct { void *user_context; - http_download_cb_t callback; + http_download_cb_t callback; size_t max, bytes; bool abort; uint8_t *data; @@ -192,22 +245,22 @@ typedef struct { 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); - + esp_http_client_config_t config = { .url = url, .event_handler = http_event_handler, .user_data = http_context, - }; - + }; + http_context->callback = callback; http_context->user_context = context; http_context->max = max; http_context->client = esp_http_client_init(&config); - - xTaskCreate(http_downloader, "downloader", 4*1024, http_context, ESP_TASK_PRIO_MIN + 1, NULL); + + xTaskCreateEXTRAM(http_downloader, "downloader", 8*1024, http_context, ESP_TASK_PRIO_MIN + 1, NULL); } static void http_downloader(void *arg) { @@ -215,14 +268,14 @@ static void http_downloader(void *arg) { esp_http_client_perform(http_context->client); esp_http_client_cleanup(http_context->client); - + free(http_context); - vTaskDelete(NULL); + vTaskDeleteEXTRAM(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) { @@ -234,42 +287,42 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt) { 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); + 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); + ESP_LOGE(TAG, "failed 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); + 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); + 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); + 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 3b29a512c..b1626b34c 100644 --- a/components/tools/tools.h +++ b/components/tools/tools.h @@ -56,6 +56,18 @@ 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); +/* Use these to dynamically create tasks whose stack is on EXTRAM. Be aware that it + * requires configNUM_THREAD_LOCAL_STORAGE_POINTERS to bet set to 2 at least (index 0 + * is used by pthread and this uses index 1, obviously + */ +BaseType_t xTaskCreateEXTRAM( TaskFunction_t pvTaskCode, + const char * const pcName, + configSTACK_DEPTH_TYPE usStackDepth, + void *pvParameters, + UBaseType_t uxPriority, + TaskHandle_t *pxCreatedTask); +void vTaskDeleteEXTRAM(TaskHandle_t xTask); + extern const char unknown_string_placeholder[]; #ifdef __cplusplus From 53fa83b2dd0a7d30345c7d606a7d1bc2ed93c7d9 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 24 Sep 2023 00:18:30 -0700 Subject: [PATCH 02/11] cspot dsconnect when paused handling --- components/spotify/Shim.cpp | 3 +- components/spotify/cspot_sink.h | 2 +- components/squeezelite/decode_external.c | 40 ++++++++++++++++++------ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/components/spotify/Shim.cpp b/components/spotify/Shim.cpp index c70ec18f5..4f490dfb9 100644 --- a/components/spotify/Shim.cpp +++ b/components/spotify/Shim.cpp @@ -92,8 +92,7 @@ size_t cspotPlayer::pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackI trackHandler(); } - dataHandler(pcm, bytes); - return bytes; + return dataHandler(pcm, bytes); } extern "C" { diff --git a/components/spotify/cspot_sink.h b/components/spotify/cspot_sink.h index 36ed4ce57..790c302ae 100644 --- a/components/spotify/cspot_sink.h +++ b/components/spotify/cspot_sink.h @@ -25,7 +25,7 @@ typedef enum { CSPOT_START, CSPOT_DISC, CSPOT_FLUSH, CSPOT_STOP, CSPOT_PLAY, CS typedef bool (*cspot_cmd_cb_t)(cspot_event_t event, ...); typedef bool (*cspot_cmd_vcb_t)(cspot_event_t event, va_list args); -typedef void (*cspot_data_cb_t)(const uint8_t *data, size_t len); +typedef uint32_t (*cspot_data_cb_t)(const uint8_t *data, size_t len); /** * @brief init sink mode (need to be provided) diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index f938130e6..ba7269e7b 100644 --- a/components/squeezelite/decode_external.c +++ b/components/squeezelite/decode_external.c @@ -65,15 +65,16 @@ extern log_level loglevel; /**************************************************************************************** * Common sink data handler */ -static void sink_data_handler(const uint8_t *data, uint32_t len) +static uint32_t sink_data_handler(const uint8_t *data, uint32_t len, int retries) { size_t bytes, space; - int wait = 10; + uint32_t written = 0; + int wait = retries + 1; // would be better to lock output, but really, it does not matter if (!output.external) { LOG_SDEBUG("Cannot use external sink while LMS is controlling player"); - return; + return 0; } LOCK_O; @@ -98,27 +99,38 @@ static void sink_data_handler(const uint8_t *data, uint32_t len) len -= bytes; data += bytes; + written += bytes; // allow i2s to empty the buffer if needed if (len && !space) { - if (output.state == OUTPUT_RUNNING) wait--; + if (!retries) break; + wait--; UNLOCK_O; usleep(50000); LOCK_O; } } if (!wait) { - // re-align the buffer according to what we throw away + // re-align the buffer according to what we threw away _buf_inc_writep(outputbuf, outputbuf->size - (BYTES_PER_FRAME - (len % BYTES_PER_FRAME))); LOG_WARN("Waited too long, dropping frames %d", len); } UNLOCK_O; + + return written; } /**************************************************************************************** - * BT sink command handler + * BT sink data handler */ #if CONFIG_BT_SINK +static void bt_sink_data_handler(const uint8_t *data, uint32_t len) { + sink_data_handler(data, len, 10); +} + +/**************************************************************************************** + * BT sink command handler + */ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args) { // don't LOCK_O as there is always a chance that LMS takes control later anyway @@ -195,7 +207,7 @@ static void raop_sink_data_handler(const uint8_t *data, uint32_t len, u32_t play raop_sync.playtime = playtime; raop_sync.len = len; - sink_data_handler(data, len); + sink_data_handler(data, len, 10); } /**************************************************************************************** @@ -332,9 +344,17 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args) #endif /**************************************************************************************** - * cspot sink command handler + * cspot sink data handler */ #if CONFIG_CSPOT_SINK +static uint32_t cspot_sink_data_handler(const uint8_t *data, uint32_t len) { + return sink_data_handler(data, len, 0); +} + +/**************************************************************************************** + * cspot sink command handler + */ + static bool cspot_cmd_handler(cspot_event_t cmd, va_list args) { // don't LOCK_O as there is always a chance that LMS takes control later anyway @@ -433,7 +453,7 @@ void register_external(void) { enable_bt_sink = !strcmp(p,"1") || !strcasecmp(p,"y"); free(p); if (!strcasestr(output.device, "BT") && enable_bt_sink) { - bt_sink_init(bt_sink_cmd_handler, sink_data_handler); + bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler); } } #endif @@ -454,7 +474,7 @@ void register_external(void) { enable_cspot = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0; free(p); if (enable_cspot){ - cspot_sink_init(cspot_cmd_handler, sink_data_handler); + cspot_sink_init(cspot_cmd_handler, cspot_sink_data_handler); LOG_INFO("Initializing CSpot sink"); } } From 3ea26a0c6f3909a6c7f5871891c268c72fcdf1f9 Mon Sep 17 00:00:00 2001 From: Wizmo2 Date: Fri, 22 Sep 2023 19:48:33 -0400 Subject: [PATCH 03/11] allow offset on ST7789 --- components/display/ST77xx.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/display/ST77xx.c b/components/display/ST77xx.c index f38bbd1be..25df43f45 100644 --- a/components/display/ST77xx.c +++ b/components/display/ST77xx.c @@ -289,10 +289,8 @@ struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) { struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private; Private->Model = Model; - if (Model == ST7735) { - sscanf(Driver, "%*[^:]%*[^x]%*[^=]=%hu", &Private->Offset.Height); - sscanf(Driver, "%*[^:]%*[^y]%*[^=]=%hu", &Private->Offset.Width); - } + sscanf(Driver, "%*[^:]%*[^x]%*[^=]=%hu", &Private->Offset.Height); + sscanf(Driver, "%*[^:]%*[^y]%*[^=]=%hu", &Private->Offset.Width); if (Depth == 18) { Device->Mode = GDS_RGB666; From 60bd591bf83386296dccc3c8bd72b4e01bace5fb Mon Sep 17 00:00:00 2001 From: Wizmo2 Date: Mon, 25 Sep 2023 19:32:13 -0400 Subject: [PATCH 04/11] support for c3 and s3 channel restrictions --- components/led_strip/led_vu.c | 2 +- components/services/globdefs.h | 6 +++++- components/services/infrared.c | 2 +- components/services/led.c | 2 +- components/services/services.c | 3 ++- components/targets/muse/muse.c | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/components/led_strip/led_vu.c b/components/led_strip/led_vu.c index 59ca0defd..ba9ebfc22 100644 --- a/components/led_strip/led_vu.c +++ b/components/led_strip/led_vu.c @@ -97,7 +97,7 @@ void led_vu_init() led_strip_config.led_strip_working = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT); led_strip_config.led_strip_showing = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT); led_strip_config.gpio = strip.gpio; - led_strip_config.rmt_channel = rmt_system_base_channel++; + led_strip_config.rmt_channel = RMT_NEXT_TX_CHANNEL; // initialize driver bool led_init_ok = led_strip_init(&led_strip_config); diff --git a/components/services/globdefs.h b/components/services/globdefs.h index 93aafb8f0..581cd49d0 100644 --- a/components/services/globdefs.h +++ b/components/services/globdefs.h @@ -13,11 +13,15 @@ #define I2C_SYSTEM_PORT 1 #define SPI_SYSTEM_HOST SPI2_HOST +#define RMT_NEXT_TX_CHANNEL rmt_system_base_tx_channel++; +#define RMT_NEXT_RX_CHANNEL rmt_system_base_rx_channel--; + extern int i2c_system_port; extern int i2c_system_speed; extern int spi_system_host; extern int spi_system_dc_gpio; -extern int rmt_system_base_channel; +extern int rmt_system_base_tx_channel; +extern int rmt_system_base_rx_channel; typedef struct { int timer, base_channel, max; } pwm_system_t; diff --git a/components/services/infrared.c b/components/services/infrared.c index 8b22ee7aa..4827fbeaa 100644 --- a/components/services/infrared.c +++ b/components/services/infrared.c @@ -491,7 +491,7 @@ int8_t infrared_gpio(void) { * */ void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode) { - int rmt_channel = rmt_system_base_channel++; + int rmt_channel = RMT_NEXT_RX_CHANNEL; rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(gpio, rmt_channel); rmt_config(&rmt_rx_config); rmt_driver_install(rmt_rx_config.channel, 1000, 0); diff --git a/components/services/led.c b/components/services/led.c index 25e854780..491171582 100644 --- a/components/services/led.c +++ b/components/services/led.c @@ -241,7 +241,7 @@ bool led_config(int idx, gpio_num_t gpio, int color, int bright, led_type_t type for (const struct rmt_led_param_s *p = rmt_led_param; !leds[idx].rmt && p->type >= 0; p++) if (p->type == type) leds[idx].rmt = p; if (!leds[idx].rmt) return false; - if (led_rmt_channel < 0) led_rmt_channel = rmt_system_base_channel++; + if (led_rmt_channel < 0) led_rmt_channel = RMT_NEXT_TX_CHANNEL; leds[idx].channel = led_rmt_channel; leds[idx].bright = bright > 0 ? bright : 100; diff --git a/components/services/services.c b/components/services/services.c index 211ba6cbb..ba1e93df9 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -35,7 +35,8 @@ int i2c_system_port = I2C_SYSTEM_PORT; int i2c_system_speed = 400000; int spi_system_host = SPI_SYSTEM_HOST; int spi_system_dc_gpio = -1; -int rmt_system_base_channel = RMT_CHANNEL_0; +int rmt_system_base_tx_channel = RMT_CHANNEL_0; +int rmt_system_base_rx_channel = RMT_CHANNEL_MAX-1; pwm_system_t pwm_system = { .timer = LEDC_TIMER_0, diff --git a/components/targets/muse/muse.c b/components/targets/muse/muse.c index 4ee2fc12c..7a5f50d6f 100644 --- a/components/targets/muse/muse.c +++ b/components/targets/muse/muse.c @@ -95,7 +95,7 @@ void setup_rmt_data_buffer(struct led_state new_state); void ws2812_control_init(void) { - rmt_channel = rmt_system_base_channel++; + rmt_channel = RMT_NEXT_TX_CHANNEL; rmt_config_t config; config.rmt_mode = RMT_MODE_TX; config.channel = rmt_channel; From 69e61ce451e3a9afa1cf019cf705206aedc6896f Mon Sep 17 00:00:00 2001 From: Wizmo2 Date: Mon, 25 Sep 2023 20:57:37 -0400 Subject: [PATCH 05/11] add battery status to vu meter effect --- components/led_strip/led_vu.c | 56 +++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/components/led_strip/led_vu.c b/components/led_strip/led_vu.c index 59ca0defd..36b3e2886 100644 --- a/components/led_strip/led_vu.c +++ b/components/led_strip/led_vu.c @@ -13,7 +13,6 @@ * Driver does support other led device. Maybe look at supporting in future. * The VU refresh rate has been decreaced (100->75) to optimize animation of spin dial. Could make * configurable like text scrolling (or use the same value) - * Look at reserving a status led within the effects. (may require nvs setting for center or end position) * Artwork function, but not released as very buggy and not really practical */ @@ -22,12 +21,17 @@ #include "esp_log.h" #include "globdefs.h" +#include "monitor.h" #include "led_strip.h" #include "platform_config.h" #include "led_vu.h" static const char *TAG = "led_vu"; +static void (*battery_handler_chain)(float value, int cells); +static void battery_svc(float value, int cells); +static int battery_status = 0; + #define LED_VU_STACK_SIZE (3*1024) #define LED_VU_PEAK_HOLD 6U @@ -36,6 +40,9 @@ static const char *TAG = "led_vu"; #define LED_VU_DEFAULT_LENGTH 19 #define LED_VU_MAX_LENGTH 255 +#define LED_VU_STATUS_GREEN 75 +#define LED_VU_STATUS_RED 25 + #define max(a,b) (((a) > (b)) ? (a) : (b)) struct led_strip_t* led_display = NULL; @@ -47,7 +54,7 @@ static EXT_RAM_ATTR struct { int vu_length; int vu_start_l; int vu_start_r; - int vu_odd; + int vu_status; } strip; static int led_addr(int pos ) { @@ -56,6 +63,13 @@ static int led_addr(int pos ) { return pos; } +static void battery_svc(float value, int cells) { + battery_status = battery_level_svc(); + ESP_LOGI(TAG, "Called for battery service with volt:%f cells:%d status:%d", value, cells, battery_status); + + if (battery_handler_chain) battery_handler_chain(value, cells); +} + /**************************************************************************************** * Initialize the led vu strip if configured. * @@ -81,14 +95,26 @@ void led_vu_init() ESP_LOGI(TAG, "led_vu configuration invalid"); goto done; } + + battery_handler_chain = battery_handler_svc; + battery_handler_svc = battery_svc; + battery_status = battery_level_svc(); if (strip.length > LED_VU_MAX_LENGTH) strip.length = LED_VU_MAX_LENGTH; - // initialize vu settings - //strip.vu_length = (strip.length % 2) ? strip.length / 2 : (strip.length - 1) / 2; - strip.vu_length = (strip.length - 1) / 2; - strip.vu_start_l = strip.vu_length; - strip.vu_start_r = strip.vu_start_l + 1; - strip.vu_odd = strip.length - 1; + // initialize vu meter settings + if (strip.length < 10) { + // single bar for small strips + strip.vu_length = strip.length; + strip.vu_start_l = 0; + strip.vu_start_r = strip.vu_start_l; + strip.vu_status = 0; + } else { + strip.vu_length = (strip.length - 1) / 2; + strip.vu_start_l = (strip.length % 2) ? strip.vu_length -1 : strip.vu_length; + strip.vu_start_r = strip.vu_length + 1; + strip.vu_status = strip.vu_length; + } + ESP_LOGI(TAG, "vu meter using length:%d left:%d right:%d status:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status); // create driver configuration led_strip_config.rgb_led_type = RGB_LED_TYPE_WS2812; @@ -296,7 +322,11 @@ void led_vu_display(int vu_l, int vu_r, int bright, bool comet) { static int decay_r = 0; if (!led_display) return; - + // single bar + if (strip.vu_start_l == strip.vu_start_r) { + vu_r = (vu_l + vu_r) / 2; + vu_l = 0; + } // scale vu samples to length vu_l = vu_l * strip.vu_length / bright; @@ -357,6 +387,14 @@ void led_vu_display(int vu_l, int vu_r, int bright, bool comet) { g = (g < step) ? 0 : g - step; } + // show battery status + if (battery_status > LED_VU_STATUS_GREEN) + led_strip_set_pixel_rgb(led_display, strip.vu_status, 0, bright, 0); + else if (battery_status > LED_VU_STATUS_RED) + led_strip_set_pixel_rgb(led_display, strip.vu_status, bright/2, bright/2, 0); + else if (battery_status > 0) + led_strip_set_pixel_rgb(led_display, strip.vu_status, bright, 0, 0); + led_strip_show(led_display); } From 4d6cfaca1cec116354f25ed42534da0c8ca45baf Mon Sep 17 00:00:00 2001 From: Wizmo2 Date: Wed, 27 Sep 2023 06:25:30 -0400 Subject: [PATCH 06/11] format changes --- components/led_strip/led_vu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/led_strip/led_vu.c b/components/led_strip/led_vu.c index 36b3e2886..74ea69b76 100644 --- a/components/led_strip/led_vu.c +++ b/components/led_strip/led_vu.c @@ -96,8 +96,8 @@ void led_vu_init() goto done; } - battery_handler_chain = battery_handler_svc; - battery_handler_svc = battery_svc; + battery_handler_chain = battery_handler_svc; + battery_handler_svc = battery_svc; battery_status = battery_level_svc(); if (strip.length > LED_VU_MAX_LENGTH) strip.length = LED_VU_MAX_LENGTH; From fe409730e0d8e189644593498656a9ccc9a9f92c Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 27 Sep 2023 18:13:31 -0700 Subject: [PATCH 07/11] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 616f8469a..be8f39f7e 100644 --- a/README.md +++ b/README.md @@ -652,4 +652,3 @@ If you have already cloned the repository and you are getting compile errors on - libmad has been patched to avoid using a lot of stack and is not provided here. There is an issue with sync detection in 1.15.1b from where the original stack patch was done but since a few fixes have been made wrt sync detection. This 1.15.1b-10 found on debian fixes the issue where mad thinks it has reached sync but has not and so returns a wrong sample rate. It comes at the expense of 8KB (!) of code where a simple check in squeezelite/mad.c that next_frame[0] is 0xff and next_frame[1] & 0xf0 is 0xf0 does the trick ... # Footnotes -(1) SPDIF is made by tricking the I2S bus but this consumes a fair bit of CPU as it multiplies by four the throughput on the i2s bus. To optimize some computation, the parity of the spdif frames must always be 0, so at least one bit has to be available to force it. As SPDIF samples are 20+4 bits length maximum, the LSB is used for that purpose, so the bit 24 is randomly toggling. It does not matter for 16 bits samples but it has been chosen to truncate the last 4 bits for 24 bits samples. I'm sure that some smart dude can further optimize spdif_convert() and use the user bit instead. You're welcome to do a PR but, as said above, I (philippe44) am not interested by 24 bits mental illness :-) and I've already made an effort to provide 20 bits which already way more what's needed :-) From 506a5aaf7ac86d829ea81da3ce2bb7be40465a66 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 27 Sep 2023 19:22:50 -0700 Subject: [PATCH 08/11] Update README.md --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index be8f39f7e..ffbbadd91 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Squeezelite-esp32 is an audio software suite made to run on espressif's esp32 an - Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices. - Stream from a **Bluetooth** device (iPhone, Android) - Stream from an **AirPlay** controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only) -- Stream directly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)) +- Stream directly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)) - please read carefully [this](#spotify) Depending on the hardware connected to the esp32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer. @@ -537,7 +537,7 @@ The option to use multiple GPIOs is very limited on esp32 and the esp-idf 4.3.x Some have asked for a soft power on/off option. Although this is not built-in, it's easy to create yours as long as the regulator/power supply of the board can be controlled by Vcc or GND. Depending on how it is active, add a pull-up/down resistor to the regulator's control and connect it also to one GPIO of the esp32. Then using set_GPIO, set that GPIO to Vcc or GND. Use a hardware button that forces the regulator on with a pull- up/down and once the esp32 has booted, it will force the GPIO to the desired value maintaining the board on by software. To power it off by software, just use the deep sleep option which will suspend all GPIO hence switching off the regulator. -# Configuration +# Software configuration ## Setup WiFi - Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite". @@ -558,6 +558,15 @@ At this point, the device should have disabled its built-in access point and sho - The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting (you might have to fiddle with it a few times) - You can enable accessto NVS parameters under 'credits' +## Spotify +By default, SqueezeESP32 will use ZeroConf to advertise its Spotify capabilties. This means that until at least one local Spotify Connect application controllers discovers and connects to it, SqueezeESP32 will not be registered to Spotify servers. As a consequence, Spotify's WebAPI will not be able to see it (for example, Home Assistant services will miss it). Once you are connected to it using for example Spotify Desktop app, it will be registered and displayed everywhere. + +If you want the player to be registered at start-up, you need to disable the ZeroConf option using the WebUI or `cspot_config::ZeroConf`. In that mode, the first time you run SqueezeESP32, it will be in ZeroConf mode and when you connect to it using a controller for the firt time, it receives and store credentials that will be used next time (after reboot). + +Set ZeroConf to 1 will always force ZeroConf mode to be used. + +The ZeroConf mode consumes less memory as it uses the built-in HTTP and mDNS servers to broadcast its capabilities. A Spotify controller will then discover these and trigger the SqueezeESP32 Spotify stack (cspot) to start. When the controller disconnects, the stack is shut down. In non-ZeroConf mode, the stack starts immediately (providing stored credentials are valid) and always run - a disconnect will not shut it down. + ## Monitor In addition of the esp-idf serial link monitor option, you can also enable a telnet server (see NVS parameters) where you'll have access to a ton of logs of what's happening inside the WROVER. From 5068309d253fae97d4971340278b74b345bb14c2 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 27 Sep 2023 19:36:38 -0700 Subject: [PATCH 09/11] manage Spotify credentials --- components/platform_config/nvs_utilities.h | 8 +- components/platform_console/cmd_config.c | 12 ++ components/spotify/Shim.cpp | 123 +++++++++++++----- .../external/opencore-aacdec/CMakeLists.txt | 6 +- .../bell/main/utilities/include/Crypto.h | 4 +- .../spotify/cspot/include/CSpotContext.h | 31 +++++ .../cspot/protobuf/authentication.options | 7 +- .../cspot/protobuf/authentication.proto | 29 +++++ components/spotify/cspot/src/LoginBlob.cpp | 4 +- components/spotify/cspot/src/Session.cpp | 10 +- 10 files changed, 193 insertions(+), 41 deletions(-) diff --git a/components/platform_config/nvs_utilities.h b/components/platform_config/nvs_utilities.h index e789fd463..5559ec3da 100644 --- a/components/platform_config/nvs_utilities.h +++ b/components/platform_config/nvs_utilities.h @@ -13,14 +13,14 @@ esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data, siz esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data); esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size); void * get_nvs_value_alloc(nvs_type_t type, const char *key); -void * get_nvs_value_alloc_for_partition(const char * partition,const char * namespace,nvs_type_t type, const char *key, size_t * size); -esp_err_t erase_nvs_for_partition(const char * partition, const char * namespace,const char *key); -esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * namespace,nvs_type_t type, const char *key, const void * data,size_t data_len); +void * get_nvs_value_alloc_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, size_t * size); +esp_err_t erase_nvs_for_partition(const char * partition, const char * ns,const char *key); +esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, const void * data,size_t data_len); esp_err_t erase_nvs(const char *key); void print_blob(const char *blob, size_t len); const char *type_to_str(nvs_type_t type); nvs_type_t str_to_type(const char *type); -esp_err_t erase_nvs_partition(const char * partition, const char * namespace); +esp_err_t erase_nvs_partition(const char * partition, const char * ns); void erase_settings_partition(); #ifdef __cplusplus } diff --git a/components/platform_console/cmd_config.c b/components/platform_console/cmd_config.c index 3628ce515..b5d89d4ea 100644 --- a/components/platform_console/cmd_config.c +++ b/components/platform_console/cmd_config.c @@ -131,6 +131,7 @@ static struct { struct arg_str *deviceName; // struct arg_int *volume; struct arg_int *bitrate; + struct arg_int *zeroConf; struct arg_end *end; } cspot_args; static struct { @@ -656,6 +657,9 @@ static int do_cspot_config(int argc, char **argv){ if(cspot_args.bitrate->count>0){ cjson_update_number(&cspot_config,cspot_args.bitrate->hdr.longopts,cspot_args.bitrate->ival[0]); } + if(cspot_args.zeroConf->count>0){ + cjson_update_number(&cspot_config,cspot_args.zeroConf->hdr.longopts,cspot_args.zeroConf->ival[0]); + } if(!nerrors ){ fprintf(f,"Storing cspot parameters.\n"); @@ -668,6 +672,9 @@ static int do_cspot_config(int argc, char **argv){ if(cspot_args.bitrate->count>0){ fprintf(f,"Bitrate changed to %u\n",cspot_args.bitrate->ival[0]); } + if(cspot_args.zeroConf->count>0){ + fprintf(f,"ZeroConf changed to %u\n",cspot_args.zeroConf->ival[0]); + } } if(!nerrors ){ fprintf(f,"Done.\n"); @@ -853,6 +860,10 @@ cJSON * cspot_cb(){ if(cspot_values){ cJSON_AddNumberToObject(values,cspot_args.bitrate->hdr.longopts,cJSON_GetNumberValue(cspot_values)); } + cspot_values = cJSON_GetObjectItem(cspot_config,cspot_args.zeroConf->hdr.longopts); + if(cspot_values){ + cJSON_AddNumberToObject(values,cspot_args.zeroConf->hdr.longopts,cJSON_GetNumberValue(cspot_values)); + } cJSON_Delete(cspot_config); return values; @@ -1286,6 +1297,7 @@ static void register_known_templates_config(){ static void register_cspot_config(){ cspot_args.deviceName = arg_str1(NULL,"deviceName","","Device Name"); cspot_args.bitrate = arg_int1(NULL,"bitrate","96|160|320","Streaming Bitrate (kbps)"); + cspot_args.zeroConf = arg_int1(NULL,"zeroConf","0|1","Force use of ZeroConf"); // cspot_args.volume = arg_int1(NULL,"volume","","Spotify Volume"); cspot_args.end = arg_end(1); const esp_console_cmd_t cmd = { diff --git a/components/spotify/Shim.cpp b/components/spotify/Shim.cpp index 4f490dfb9..df0cbf0f0 100644 --- a/components/spotify/Shim.cpp +++ b/components/spotify/Shim.cpp @@ -30,10 +30,16 @@ #include "cspot_private.h" #include "cspot_sink.h" #include "platform_config.h" +#include "nvs_utilities.h" #include "tools.h" static class cspotPlayer *player; +static const struct { + const char *ns; + const char *credentials; +} spotify_ns = { .ns = "spotify", .credentials = "credentials" }; + /**************************************************************************************** * Player's main class & task */ @@ -42,7 +48,11 @@ class cspotPlayer : public bell::Task { private: std::string name; bell::WrappedSemaphore clientConnected; - std::atomic isPaused, isConnected; + std::atomic isPaused; + enum states { ABORT, LINKED, DISCO }; + std::atomic state; + std::string credentials; + bool zeroConf; int startOffset, volume = 0, bitrate = 160; httpd_handle_t serverHandle; @@ -57,6 +67,7 @@ class cspotPlayer : public bell::Task { void eventHandler(std::unique_ptr event); void trackHandler(void); size_t pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId); + void enableZeroConf(void); void runTask(); @@ -79,8 +90,25 @@ cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspo if ((item = cJSON_GetObjectItem(config, "volume")) != NULL) volume = item->valueint; if ((item = cJSON_GetObjectItem(config, "bitrate")) != NULL) bitrate = item->valueint; if ((item = cJSON_GetObjectItem(config, "deviceName") ) != NULL) this->name = item->valuestring; - else this->name = name; - cJSON_Delete(config); + else this->name = name; + + if ((item = cJSON_GetObjectItem(config, "zeroConf")) != NULL) { + zeroConf = item->valueint; + cJSON_Delete(config); + } else { + zeroConf = true; + cJSON_AddNumberToObject(config, "zeroConf", 1); + config_set_cjson_str_and_free("cspot_config", config); + } + + // get optional credentials from own NVS + if (!zeroConf) { + char *credentials = (char*) get_nvs_value_alloc_for_partition(NVS_DEFAULT_PART_NAME, spotify_ns.ns, NVS_TYPE_STR, spotify_ns.credentials, NULL); + if (credentials) { + this->credentials = credentials; + free(credentials); + } + } if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160; } @@ -207,7 +235,7 @@ void cspotPlayer::eventHandler(std::unique_ptr event } case cspot::SpircHandler::EventType::DISC: cmdHandler(CSPOT_DISC); - isConnected = false; + state = DISCO; break; case cspot::SpircHandler::EventType::SEEK: { cmdHandler(CSPOT_SEEK, std::get(event->data)); @@ -265,7 +293,7 @@ void cspotPlayer::command(cspot_event_t event) { * generate any cspot::event */ case CSPOT_DISC: cmdHandler(CSPOT_DISC); - isConnected = false; + state = ABORT; break; // spirc->setRemoteVolume does not generate a cspot::event so call cmdHandler case CSPOT_VOLUME_UP: @@ -285,34 +313,48 @@ void cspotPlayer::command(cspot_event_t event) { } } -void cspotPlayer::runTask() { +void cspotPlayer::enableZeroConf(void) { httpd_uri_t request = { .uri = "/spotify_info", .method = HTTP_GET, .handler = ::handleGET, .user_ctx = NULL, - }; - + }; + // register GET and POST handler for built-in server httpd_register_uri_handler(serverHandle, &request); request.method = HTTP_POST; request.handler = ::handlePOST; httpd_register_uri_handler(serverHandle, &request); - - // construct blob for that player - blob = std::make_unique(name); + + CSPOT_LOG(info, "ZeroConf mode (port %d)", serverPort); // Register mdns service, for spotify to find us bell::MDNSService::registerService( blob->getDeviceName(), "_spotify-connect", "_tcp", "", serverPort, - { {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} }); - + { {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} }); +} + +void cspotPlayer::runTask() { + bool useZeroConf = zeroConf; + + // construct blob for that player + blob = std::make_unique(name); + CSPOT_LOG(info, "CSpot instance service name %s (id %s)", blob->getDeviceName().c_str(), blob->getDeviceId().c_str()); + + if (!zeroConf && !credentials.empty()) { + blob->loadJson(credentials); + CSPOT_LOG(info, "Reusable credentials mode"); + } else { + // whether we want it or not we must use ZeroConf + useZeroConf = true; + enableZeroConf(); + } // gone with the wind... while (1) { - clientConnected.wait(); - - CSPOT_LOG(info, "Spotify client connected for %s", name.c_str()); + if (useZeroConf) clientConnected.wait(); + CSPOT_LOG(info, "Spotify client launched for %s", name.c_str()); auto ctx = cspot::Context::createFromBlob(blob); @@ -321,12 +363,26 @@ void cspotPlayer::runTask() { else ctx->config.audioFormat = AudioFormat_OGG_VORBIS_160; ctx->session->connectWithRandomAp(); - auto token = ctx->session->authenticate(blob); + ctx->config.authData = ctx->session->authenticate(blob); // Auth successful - if (token.size() > 0) { + if (ctx->config.authData.size() > 0) { + // we might have been forced to use zeroConf, so store credentials and reset zeroConf usage + if (!zeroConf) { + useZeroConf = false; + // can't call store_nvs... from a task running on EXTRAM stack + TimerHandle_t timer = xTimerCreate( "credentials", 1, pdFALSE, strdup(ctx->getCredentialsJson().c_str()), + [](TimerHandle_t xTimer) { + auto credentials = (char*) pvTimerGetTimerID(xTimer); + store_nvs_value_len_for_partition(NVS_DEFAULT_PART_NAME, spotify_ns.ns, NVS_TYPE_STR, spotify_ns.credentials, credentials, 0); + free(credentials); + xTimerDelete(xTimer, portMAX_DELAY); + } ); + xTimerStart(timer, portMAX_DELAY); + } + spirc = std::make_unique(ctx); - isConnected = true; + state = LINKED; // set call back to calculate a hash on trackId spirc->getTrackPlayer()->setDataCallback( @@ -347,7 +403,7 @@ void cspotPlayer::runTask() { cmdHandler(CSPOT_VOLUME, volume); // exit when player has stopped (received a DISC) - while (isConnected) { + while (state == LINKED) { ctx->session->handlePacket(); // low-accuracy polling events @@ -371,23 +427,32 @@ void cspotPlayer::runTask() { spirc->setPause(true); } } + + // on disconnect, stay in the core loop unless we are in ZeroConf mode + if (state == DISCO) { + // update volume then + cJSON *config = config_alloc_get_cjson("cspot_config"); + cJSON_DeleteItemFromObject(config, "volume"); + cJSON_AddNumberToObject(config, "volume", volume); + config_set_cjson_str_and_free("cspot_config", config); + + // in ZeroConf mod, stay connected (in this loop) + if (!zeroConf) state = LINKED; + } } spirc->disconnect(); spirc.reset(); CSPOT_LOG(info, "disconnecting player %s", name.c_str()); + } else { + CSPOT_LOG(error, "failed authentication, forcing ZeroConf"); + if (!useZeroConf) enableZeroConf(); + useZeroConf = true; } - + // we want to release memory ASAP and for sure - ctx.reset(); - token.clear(); - - // update volume when we disconnect - cJSON *config = config_alloc_get_cjson("cspot_config"); - cJSON_DeleteItemFromObject(config, "volume"); - cJSON_AddNumberToObject(config, "volume", volume); - config_set_cjson_str_and_free("cspot_config", config); + ctx.reset(); } } diff --git a/components/spotify/cspot/bell/external/opencore-aacdec/CMakeLists.txt b/components/spotify/cspot/bell/external/opencore-aacdec/CMakeLists.txt index 2a7b32999..1e50052e7 100644 --- a/components/spotify/cspot/bell/external/opencore-aacdec/CMakeLists.txt +++ b/components/spotify/cspot/bell/external/opencore-aacdec/CMakeLists.txt @@ -1,7 +1,9 @@ file(GLOB AACDEC_SOURCES "src/*.c") file(GLOB AACDEC_HEADERS "src/*.h" "oscl/*.h" "include/*.h") -add_library(opencore-aacdec SHARED ${AACDEC_SOURCES}) +add_library(opencore-aacdec STATIC ${AACDEC_SOURCES}) +if(NOT MSVC) + target_compile_options(opencore-aacdec PRIVATE -Wno-array-parameter) +endif() add_definitions(-DAAC_PLUS -DHQ_SBR -DPARAMETRICSTEREO -DC_EQUIVALENT) -target_compile_options(opencore-aacdec PRIVATE -Wno-array-parameter) target_include_directories(opencore-aacdec PUBLIC "src/" "oscl/" "include/") \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/include/Crypto.h b/components/spotify/cspot/bell/main/utilities/include/Crypto.h index 1ea17a718..667f13ff8 100644 --- a/components/spotify/cspot/bell/main/utilities/include/Crypto.h +++ b/components/spotify/cspot/bell/main/utilities/include/Crypto.h @@ -31,8 +31,8 @@ class CryptoMbedTLS { CryptoMbedTLS(); ~CryptoMbedTLS(); // Base64 - std::vector base64Decode(const std::string& data); - std::string base64Encode(const std::vector& data); + static std::vector base64Decode(const std::string& data); + static std::string base64Encode(const std::vector& data); // Sha1 void sha1Init(); diff --git a/components/spotify/cspot/include/CSpotContext.h b/components/spotify/cspot/include/CSpotContext.h index ed0cfd8e9..2f274ce68 100644 --- a/components/spotify/cspot/include/CSpotContext.h +++ b/components/spotify/cspot/include/CSpotContext.h @@ -6,7 +6,16 @@ #include "LoginBlob.h" #include "MercurySession.h" #include "TimeProvider.h" +#include "Crypto.h" #include "protobuf/metadata.pb.h" +#include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE... +#ifdef BELL_ONLY_CJSON +#include "cJSON.h" +#else +#include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t +#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json +#include "nlohmann/json_fwd.hpp" // for json +#endif namespace cspot { struct Context { @@ -26,6 +35,28 @@ struct Context { std::shared_ptr timeProvider; std::shared_ptr session; + std::string getCredentialsJson() { +#ifdef BELL_ONLY_CJSON + cJSON* json_obj = cJSON_CreateObject(); + cJSON_AddStringToObject(json_obj, "authData", Crypto::base64Encode(config.authData).c_str()); + cJSON_AddNumberToObject(json_obj, "authType", AuthenticationType_AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS); + cJSON_AddStringToObject(json_obj, "username", config.username.c_str()); + + char* str = cJSON_PrintUnformatted(json_obj); + cJSON_Delete(json_obj); + std::string json_objStr(str); + free(str); + + return json_objStr; +#else + nlohmann::json obj; + obj["authData"] = Crypto::base64Encode(config.authData); + obj["authType"] = AuthenticationType_AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS; + obj["username"] = config.username; + + return obj.dump(); +#endif + } static std::shared_ptr createFromBlob( std::shared_ptr blob) { diff --git a/components/spotify/cspot/protobuf/authentication.options b/components/spotify/cspot/protobuf/authentication.options index 33aad19f8..2ec38a0c8 100644 --- a/components/spotify/cspot/protobuf/authentication.options +++ b/components/spotify/cspot/protobuf/authentication.options @@ -2,4 +2,9 @@ LoginCredentials.username max_size:30, fixed_length:false LoginCredentials.auth_data max_size:512, fixed_length:false SystemInfo.system_information_string max_size:16, fixed_length:false SystemInfo.device_id max_size:50, fixed_length:false -ClientResponseEncrypted.version_string max_size:32, fixed_length:false \ No newline at end of file +ClientResponseEncrypted.version_string max_size:32, fixed_length:false +APWelcome.canonical_username max_size:30, fixed_length:false +APWelcome.reusable_auth_credentials max_size:512, fixed_length:false +APWelcome.lfs_secret max_size:128, fixed_length:false +AccountInfoFacebook.access_token max_size:128, fixed_length:false +AccountInfoFacebook.machine_id max_size:50, fixed_length:false diff --git a/components/spotify/cspot/protobuf/authentication.proto b/components/spotify/cspot/protobuf/authentication.proto index d38961477..079ab2907 100644 --- a/components/spotify/cspot/protobuf/authentication.proto +++ b/components/spotify/cspot/protobuf/authentication.proto @@ -37,6 +37,11 @@ enum Os { OS_BCO = 0x16; } +enum AccountType { + Spotify = 0x0; + Facebook = 0x1; +} + enum AuthenticationType { AUTHENTICATION_USER_PASS = 0x0; AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS = 0x1; @@ -62,4 +67,28 @@ message ClientResponseEncrypted { required LoginCredentials login_credentials = 0xa; required SystemInfo system_info = 0x32; optional string version_string = 0x46; +} + +message APWelcome { + required string canonical_username = 0xa; + required AccountType account_type_logged_in = 0x14; + required AccountType credentials_type_logged_in = 0x19; + required AuthenticationType reusable_auth_credentials_type = 0x1e; + required bytes reusable_auth_credentials = 0x28; + optional bytes lfs_secret = 0x32; + optional AccountInfo account_info = 0x3c; + optional AccountInfoFacebook fb = 0x46; +} + +message AccountInfo { + optional AccountInfoSpotify spotify = 0x1; + optional AccountInfoFacebook facebook = 0x2; +} + +message AccountInfoSpotify { +} + +message AccountInfoFacebook { + optional string access_token = 0x1; + optional string machine_id = 0x2; } \ No newline at end of file diff --git a/components/spotify/cspot/src/LoginBlob.cpp b/components/spotify/cspot/src/LoginBlob.cpp index feb5e8d22..8790071df 100644 --- a/components/spotify/cspot/src/LoginBlob.cpp +++ b/components/spotify/cspot/src/LoginBlob.cpp @@ -142,8 +142,8 @@ void LoginBlob::loadJson(const std::string& json) { cJSON* root = cJSON_Parse(json.c_str()); this->authType = cJSON_GetObjectItem(root, "authType")->valueint; this->username = cJSON_GetObjectItem(root, "username")->valuestring; - std::string authDataObject = - cJSON_GetObjectItem(root, "authData")->valuestring; + std::string authDataObject = cJSON_GetObjectItem(root, "authData")->valuestring; + this->authData = crypto->base64Decode(authDataObject); cJSON_Delete(root); #else auto root = nlohmann::json::parse(json); diff --git a/components/spotify/cspot/src/Session.cpp b/components/spotify/cspot/src/Session.cpp index 74c3f18ac..7d76b86a9 100644 --- a/components/spotify/cspot/src/Session.cpp +++ b/components/spotify/cspot/src/Session.cpp @@ -17,6 +17,10 @@ #include "PlainConnection.h" // for PlainConnection, timeoutCallback #include "ShannonConnection.h" // for ShannonConnection +#include "pb_decode.h" +#include "NanoPBHelper.h" // for pbPutString, pbEncode, pbDecode +#include "protobuf/authentication.pb.h" + using random_bytes_engine = std::independent_bits_engine; @@ -79,9 +83,13 @@ std::vector Session::authenticate(std::shared_ptr blob) { auto packet = this->shanConn->recvPacket(); switch (packet.command) { case AUTH_SUCCESSFUL_COMMAND: { + APWelcome welcome; CSPOT_LOG(debug, "Authorization successful"); + pbDecode(welcome, APWelcome_fields, packet.data); return std::vector( - {0x1}); // TODO: return actual reusable credentaials to be stored somewhere + welcome.reusable_auth_credentials.bytes, + welcome.reusable_auth_credentials.bytes + welcome.reusable_auth_credentials.size + ); break; } case AUTH_DECLINED_COMMAND: { From f5d6f26c01494691e4fdc8fa1a16c20aa2dec5ca Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 27 Sep 2023 19:42:02 -0700 Subject: [PATCH 10/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ffbbadd91..f42902cf8 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ Ground -------------------------- coax signal ground The NVS parameter "display_config" sets the parameters for an optional display. It can be I2C (see [here](#i2c) for shared bus) or SPI (see [here](#spi) for shared bus) Syntax is ``` I2C,width=,height=[address=][,reset=][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106] -SPI,width=,height=,cs=[,back=][,reset=][,speed=][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735[:x=][:y=]|ST7789|ILI9341[:16|18][,rotate]] +SPI,width=,height=,cs=[,back=][,reset=][,speed=][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[:x=][:y=]|ILI9341[:16|18][,rotate]] ``` - back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness - reset: some display have a reset pin that is should normally be pulled up if unused. Most displays require reset and will not initialize well otherwise. From f33cb569cef023d58f07b12994e4d106b17a8eab Mon Sep 17 00:00:00 2001 From: Wizmo2 Date: Thu, 28 Sep 2023 07:34:58 -0400 Subject: [PATCH 11/11] rmt helper to function --- components/led_strip/led_vu.c | 2 +- components/services/globdefs.h | 4 ++-- components/services/infrared.c | 2 +- components/services/led.c | 2 +- components/targets/muse/muse.c | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/led_strip/led_vu.c b/components/led_strip/led_vu.c index ba9ebfc22..54319bfa4 100644 --- a/components/led_strip/led_vu.c +++ b/components/led_strip/led_vu.c @@ -97,7 +97,7 @@ void led_vu_init() led_strip_config.led_strip_working = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT); led_strip_config.led_strip_showing = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT); led_strip_config.gpio = strip.gpio; - led_strip_config.rmt_channel = RMT_NEXT_TX_CHANNEL; + led_strip_config.rmt_channel = RMT_NEXT_TX_CHANNEL(); // initialize driver bool led_init_ok = led_strip_init(&led_strip_config); diff --git a/components/services/globdefs.h b/components/services/globdefs.h index 581cd49d0..b3816e064 100644 --- a/components/services/globdefs.h +++ b/components/services/globdefs.h @@ -13,8 +13,8 @@ #define I2C_SYSTEM_PORT 1 #define SPI_SYSTEM_HOST SPI2_HOST -#define RMT_NEXT_TX_CHANNEL rmt_system_base_tx_channel++; -#define RMT_NEXT_RX_CHANNEL rmt_system_base_rx_channel--; +#define RMT_NEXT_TX_CHANNEL() rmt_system_base_tx_channel++; +#define RMT_NEXT_RX_CHANNEL() rmt_system_base_rx_channel--; extern int i2c_system_port; extern int i2c_system_speed; diff --git a/components/services/infrared.c b/components/services/infrared.c index 4827fbeaa..a48aa0c57 100644 --- a/components/services/infrared.c +++ b/components/services/infrared.c @@ -491,7 +491,7 @@ int8_t infrared_gpio(void) { * */ void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode) { - int rmt_channel = RMT_NEXT_RX_CHANNEL; + int rmt_channel = RMT_NEXT_RX_CHANNEL(); rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(gpio, rmt_channel); rmt_config(&rmt_rx_config); rmt_driver_install(rmt_rx_config.channel, 1000, 0); diff --git a/components/services/led.c b/components/services/led.c index 491171582..9e7ea874a 100644 --- a/components/services/led.c +++ b/components/services/led.c @@ -241,7 +241,7 @@ bool led_config(int idx, gpio_num_t gpio, int color, int bright, led_type_t type for (const struct rmt_led_param_s *p = rmt_led_param; !leds[idx].rmt && p->type >= 0; p++) if (p->type == type) leds[idx].rmt = p; if (!leds[idx].rmt) return false; - if (led_rmt_channel < 0) led_rmt_channel = RMT_NEXT_TX_CHANNEL; + if (led_rmt_channel < 0) led_rmt_channel = RMT_NEXT_TX_CHANNEL(); leds[idx].channel = led_rmt_channel; leds[idx].bright = bright > 0 ? bright : 100; diff --git a/components/targets/muse/muse.c b/components/targets/muse/muse.c index 7a5f50d6f..71b0a62fc 100644 --- a/components/targets/muse/muse.c +++ b/components/targets/muse/muse.c @@ -95,7 +95,7 @@ void setup_rmt_data_buffer(struct led_state new_state); void ws2812_control_init(void) { - rmt_channel = RMT_NEXT_TX_CHANNEL; + rmt_channel = RMT_NEXT_TX_CHANNEL(); rmt_config_t config; config.rmt_mode = RMT_MODE_TX; config.channel = rmt_channel;