소스 검색

Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3

Sebastien L 3 년 전
부모
커밋
89b4b5ca2d
77개의 변경된 파일806개의 추가작업 그리고 303개의 파일을 삭제
  1. 17 3
      build-scripts/I2S-4MFlash-sdkconfig.defaults
  2. 16 4
      build-scripts/SqueezeAmp-sdkconfig.defaults
  3. 1 0
      components/display/CMakeLists.txt
  4. 1 1
      components/display/SH1106.c
  5. 6 5
      components/display/core/gds.c
  6. 1 0
      components/display/core/gds.h
  7. 9 9
      components/display/core/gds_font.c
  8. 3 3
      components/display/core/gds_image.c
  9. 2 2
      components/display/core/gds_private.h
  10. 6 6
      components/display/core/gds_text.c
  11. 1 1
      components/display/core/ifaces/default_if_i2c.c
  12. 2 4
      components/display/core/ifaces/default_if_spi.c
  13. 56 1
      components/display/display.c
  14. 2 0
      components/display/display.h
  15. BIN
      components/display/note.jpg
  16. 1 1
      components/driver_bt/bt_app_sink.c
  17. 5 0
      components/platform_config/platform_config.h
  18. 6 0
      components/platform_console/CMakeLists.txt
  19. 5 5
      components/platform_console/platform_console.c
  20. 3 0
      components/raop/raop.c
  21. 7 1
      components/raop/raop_sink.c
  22. 1 1
      components/raop/raop_sink.h
  23. 0 6
      components/services/accessors.c
  24. 14 11
      components/services/battery.c
  25. 1 0
      components/services/monitor.h
  26. 3 3
      components/spotify/Shim.cpp
  27. 1 0
      components/spotify/cspot/CMakeLists.txt
  28. 1 2
      components/spotify/cspot/bell/CMakeLists.txt
  29. 1 1
      components/spotify/cspot/bell/include/CryptoMbedTLS.h
  30. 1 1
      components/spotify/cspot/bell/include/CryptoOpenSSL.h
  31. 4 0
      components/spotify/cspot/bell/include/NanoPBHelper.h
  32. 1 1
      components/spotify/cspot/bell/include/Task.h
  33. 5 5
      components/spotify/cspot/bell/src/CryptoMBedTLS.cpp
  34. 4 4
      components/spotify/cspot/bell/src/CryptoOpenSSL.cpp
  35. 1 1
      components/spotify/cspot/bell/src/HTTPClient.cpp
  36. 16 0
      components/spotify/cspot/bell/src/NanoPBHelper.cpp
  37. 18 8
      components/spotify/cspot/include/AudioChunk.h
  38. 1 1
      components/spotify/cspot/include/Packet.h
  39. 1 1
      components/spotify/cspot/include/Shannon.h
  40. 1 0
      components/spotify/cspot/include/SpircController.h
  41. 5 6
      components/spotify/cspot/protobuf/authentication.options
  42. 1 1
      components/spotify/cspot/protobuf/authentication.pb.c
  43. 21 19
      components/spotify/cspot/protobuf/authentication.pb.h
  44. 4 4
      components/spotify/cspot/protobuf/authentication.proto
  45. 2 2
      components/spotify/cspot/protobuf/mercury.options
  46. 9 7
      components/spotify/cspot/protobuf/mercury.pb.h
  47. 18 18
      components/spotify/cspot/protobuf/metadata.options
  48. 1 1
      components/spotify/cspot/protobuf/spirc.options
  49. 1 1
      components/spotify/cspot/protobuf/spirc.pb.c
  50. 21 21
      components/spotify/cspot/protobuf/spirc.pb.h
  51. 20 5
      components/spotify/cspot/src/AudioChunk.cpp
  52. 4 4
      components/spotify/cspot/src/AudioChunkManager.cpp
  53. 0 1
      components/spotify/cspot/src/ChunkedAudioStream.cpp
  54. 11 10
      components/spotify/cspot/src/ChunkedByteStream.cpp
  55. 1 1
      components/spotify/cspot/src/LoginBlob.cpp
  56. 7 4
      components/spotify/cspot/src/MercuryManager.cpp
  57. 1 2
      components/spotify/cspot/src/MercuryResponse.cpp
  58. 1 1
      components/spotify/cspot/src/Packet.cpp
  59. 1 1
      components/spotify/cspot/src/Player.cpp
  60. 33 36
      components/spotify/cspot/src/PlayerState.cpp
  61. 15 6
      components/spotify/cspot/src/Session.cpp
  62. 2 2
      components/spotify/cspot/src/SpircController.cpp
  63. 9 7
      components/spotify/cspot/src/SpotifyTrack.cpp
  64. 20 1
      components/spotify/cspot_sink.c
  65. 5 0
      components/squeezelite/CMakeLists.txt
  66. 3 5
      components/squeezelite/equalizer.c
  67. 19 11
      components/squeezelite/output_i2s.c
  68. 13 0
      components/targets/CMakeLists.txt
  69. 3 0
      components/targets/init.c
  70. 138 0
      components/targets/muse/muse.c
  71. 1 0
      components/tools/CMakeLists.txt
  72. 103 1
      components/tools/tools.c
  73. 3 0
      components/tools/tools.h
  74. 1 0
      components/wifi-manager/webapp/src/index.ejs
  75. 1 1
      main/CMakeLists.txt
  76. 81 32
      main/Kconfig.projbuild
  77. 2 0
      main/esp_app_main.c

+ 17 - 3
build-scripts/I2S-4MFlash-sdkconfig.defaults

@@ -281,6 +281,19 @@ CONFIG_LED_GREEN_GPIO=-1
 CONFIG_LED_RED_GPIO=-1
 # end of LED configuration
 
+
+#
+# Audio controls
+#
+CONFIG_AUDIO_CONTROLS=""
+# end of Audio Contorls configuration
+
+#
+# AMP configuration
+#
+CONFIG_AMP_GPIO=-1
+# end of AMP configuration
+
 #
 # Audio JACK
 #
@@ -296,7 +309,7 @@ CONFIG_SPKFAULT_GPIO=-1
 #
 # Battery measure
 #
-CONFIG_BAT_CHANNEL=-1
+CONFIG_BAT_CONFIG=""
 # end of Battery measure
 
 CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
@@ -533,7 +546,8 @@ CONFIG_ESP_TLS_USING_MBEDTLS=y
 # CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set
 # CONFIG_ESP_TLS_SERVER is not set
 # CONFIG_ESP_TLS_PSK_VERIFICATION is not set
-# CONFIG_ESP_TLS_INSECURE is not set
+CONFIG_ESP_TLS_INSECURE=y
+CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y
 # end of ESP-TLS
 
 #
@@ -727,7 +741,7 @@ CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 #
 # ESP HTTP client
 #
-# CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS is not set
+CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 # CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set
 # end of ESP HTTP client
 

+ 16 - 4
build-scripts/SqueezeAmp-sdkconfig.defaults

@@ -269,11 +269,22 @@ CONFIG_JACK_GPIO=34
 CONFIG_JACK_GPIO_LEVEL=0
 CONFIG_SPKFAULT_GPIO=2
 CONFIG_SPKFAULT_GPIO_LEVEL=0
-CONFIG_BAT_CHANNEL=7
-CONFIG_BAT_SCALE="20.24"
+CONFIG_BAT_CONFIG="channel=7,scale=20.24,atten=0"
 CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
 # end of Squeezelite-ESP32
 
+#
+# Audio controls
+#
+CONFIG_AUDIO_CONTROLS=""
+# end of Audio Contorls configuration
+
+#
+# AMP configuration
+#
+CONFIG_AMP_GPIO=-1
+# end of AMP configuration
+
 #
 # Compiler options
 #
@@ -505,7 +516,8 @@ CONFIG_ESP_TLS_USING_MBEDTLS=y
 # CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set
 # CONFIG_ESP_TLS_SERVER is not set
 # CONFIG_ESP_TLS_PSK_VERIFICATION is not set
-# CONFIG_ESP_TLS_INSECURE is not set
+CONFIG_ESP_TLS_INSECURE=y
+CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y
 # end of ESP-TLS
 
 #
@@ -699,7 +711,7 @@ CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 #
 # ESP HTTP client
 #
-# CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS is not set
+CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 # CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set
 # end of ESP HTTP client
 

+ 1 - 0
components/display/CMakeLists.txt

@@ -2,6 +2,7 @@ idf_component_register(SRC_DIRS . core core/ifaces  fonts
 						INCLUDE_DIRS . fonts core
 						REQUIRES platform_config tools esp_common
 						PRIV_REQUIRES services freertos driver           
+						EMBED_FILES note.jpg
 )
 
 set_source_files_properties(display.c

+ 1 - 1
components/display/SH1106.c

@@ -86,7 +86,7 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
     Device->WriteCommand( Device, Contrast );
 }
 
-static void SPIParams(int Speed, uint8_t *mode, uint8_t *CS_pre, uint8_t *CS_post) {
+static void SPIParams(int Speed, uint8_t *mode, uint16_t *CS_pre, uint8_t *CS_post) {
 	*CS_post = Speed / (8*1000*1000);
 }
 

+ 6 - 5
components/display/core/gds.c

@@ -235,12 +235,13 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
 		ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel );		
 	}
 }
-	
+
 void GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) { if (Device->SetLayout) Device->SetLayout( Device, HFlip, VFlip, Rotate ); }
 void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; }
-int	GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; }
-int	GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; }
-int	GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; }
-int	GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; }
+int	 GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; }
+void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ) { Device->TextWidth = Device && TextWidth && TextWidth < Device->Width ? TextWidth : Device->Width; }
+int	 GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; }
+int	 GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; }
+int	 GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; }
 void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); }
 void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); }

+ 1 - 0
components/display/core/gds.h

@@ -38,6 +38,7 @@ void 	GDS_Update( struct GDS_Device* Device );
 void 	GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate );
 void 	GDS_SetDirty( struct GDS_Device* Device );
 int 	GDS_GetWidth( struct GDS_Device* Device );
+void 	GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth );
 int 	GDS_GetHeight( struct GDS_Device* Device );
 int 	GDS_GetDepth( struct GDS_Device* Device );
 int 	GDS_GetMode( struct GDS_Device* Device );

+ 9 - 9
components/display/core/gds_font.c

@@ -73,13 +73,13 @@ void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y,
         CharStartY+= OffsetY;
 
         /* Do not attempt to draw if this character is entirely offscreen */
-        if ( CharEndX < 0 || CharStartX >= Device->Width || CharEndY < 0 || CharStartY >= Device->Height ) {
+        if ( CharEndX < 0 || CharStartX >= Device->TextWidth || CharEndY < 0 || CharStartY >= Device->Height ) {
             ClipDebug( x, y );
             return;
         }
 
         /* Do not attempt to draw past the end of the screen */
-        CharEndX = ( CharEndX >= Device->Width ) ? Device->Width - 1 : CharEndX;
+        CharEndX = ( CharEndX >= Device->TextWidth ) ? Device->TextWidth - 1 : CharEndX;
         CharEndY = ( CharEndY >= Device->Height ) ? Device->Height - 1 : CharEndY;
 		Device->Dirty = true;
 
@@ -146,7 +146,7 @@ int GDS_FontGetCharWidth( struct GDS_Device* Display, char Character ) {
 }
 
 int GDS_FontGetMaxCharsPerRow( struct GDS_Device* Display ) {
-    return Display->Width / Display->Font->Width;
+    return Display->TextWidth / Display->Font->Width;
 }
 
 int GDS_FontGetMaxCharsPerColumn( struct GDS_Device* Display ) {
@@ -210,7 +210,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
     switch ( Anchor ) {
         case TextAnchor_East: {
             *OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
-            *OutX = ( Display->Width - StringWidth );
+            *OutX = ( Display->TextWidth - StringWidth );
 
             break;
         }
@@ -221,19 +221,19 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
             break;
         }
         case TextAnchor_North: {
-            *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
+            *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
             *OutY = 0;
 
             break;
         }
         case TextAnchor_South: {
-            *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
+            *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
             *OutY = ( Display->Height - StringHeight );
             
             break;
         }
         case TextAnchor_NorthEast: {
-            *OutX = ( Display->Width - StringWidth );
+            *OutX = ( Display->TextWidth - StringWidth );
             *OutY = 0;
 
             break;
@@ -246,7 +246,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
         }
         case TextAnchor_SouthEast: {
             *OutY = ( Display->Height - StringHeight );
-            *OutX = ( Display->Width - StringWidth );
+            *OutX = ( Display->TextWidth - StringWidth );
 
             break;
         }
@@ -258,7 +258,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
         }
         case TextAnchor_Center: {
             *OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
-            *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
+            *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
 
             break;
         }

+ 3 - 3
components/display/core/gds_image.c

@@ -142,7 +142,7 @@ static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
 	JpegCtx *Context = (JpegCtx*) Decoder->device;
     uint8_t *Pixels = (uint8_t*) Bitmap;
 	int Shift = 8 - Context->Depth;
-	
+
 	// decoded image is RGB888, shift only make sense for grayscale
 	if (Context->Mode == GDS_RGB888) {
 		OUTHANDLERDIRECT(Scaler888, 0);
@@ -167,7 +167,7 @@ static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
 static void* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly, int RGB_Mode) {
     JDEC Decoder;
     JpegCtx Context;
-	char *Scratch = calloc(SCRATCH_SIZE, 1);
+	char *Scratch = malloc(SCRATCH_SIZE);
 	
     if (!Scratch) {
         ESP_LOGE(TAG, "Cannot allocate workspace");
@@ -372,7 +372,7 @@ bool GDS_DrawJPEG(struct GDS_Device* Device, uint8_t *Source, int x, int y, int
     JDEC Decoder;
     JpegCtx Context;
 	bool Ret = false;
-	char *Scratch = calloc(SCRATCH_SIZE, 1);
+	char *Scratch = malloc(SCRATCH_SIZE);
 	
     if (!Scratch) {
         ESP_LOGE(TAG, "Cannot allocate workspace");

+ 2 - 2
components/display/core/gds_private.h

@@ -95,7 +95,7 @@ struct GDS_Device {
 		const struct GDS_FontDef* Font;
 	} Lines[MAX_LINES];
 	
-	uint16_t Width;
+	uint16_t Width, TextWidth;
     uint16_t Height;
 	uint8_t Depth, Mode;
 	
@@ -125,7 +125,7 @@ struct GDS_Device {
 	void (*DrawRGB)( struct GDS_Device* Device, uint8_t *Image,int x, int y, int Width, int Height, int RGB_Mode );
 	void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
 	// may provide for tweaking
-	void (*SPIParams)(int Speed, uint8_t *mode, uint8_t *CS_pre, uint8_t *CS_post);
+	void (*SPIParams)(int Speed, uint8_t *mode, uint16_t *CS_pre, uint8_t *CS_post);
 		    
 	// interface-specific methods	
     WriteCommandProc WriteCommand;

+ 6 - 6
components/display/core/gds_text.c

@@ -100,13 +100,13 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
 	Width = GDS_FontMeasureString( Device, Text );
 	
 	// adjusting position, erase only EoL for rigth-justified
-	if (Pos == GDS_TEXT_RIGHT) X = Device->Width - Width - 1;
-	else if (Pos == GDS_TEXT_CENTER) X = (Device->Width - Width) / 2;
+	if (Pos == GDS_TEXT_RIGHT) X = Device->TextWidth - Width - 1;
+	else if (Pos == GDS_TEXT_CENTER) X = (Device->TextWidth - Width) / 2;
 	
 	// erase if requested
 	if (Attr & GDS_TEXT_CLEAR) {
 		int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
-		for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->Width; c++) 
+		for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++) 
 			for (int y = Y_min; y < Y_max; y++)
 				DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
 	}
@@ -119,7 +119,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
 	Device->Dirty = true;
 	if (Attr & GDS_TEXT_UPDATE) GDS_Update( Device );
 		
-	return Width + X < Device->Width;
+	return Width + X < Device->TextWidth;
 }
 
 /****************************************************************************************
@@ -146,7 +146,7 @@ int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max) {
 	
 	// we might already fit
 	GDS_SetFont( Device, Device->Lines[N].Font );	
-	if (GDS_FontMeasureString( Device, String ) <= Device->Width) return 0;
+	if (GDS_FontMeasureString( Device, String ) <= Device->TextWidth) return 0;
 		
 	// add some space for better visual 
 	strncat(String, Space, Max-Len);
@@ -157,7 +157,7 @@ int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max) {
 	Boundary = GDS_FontMeasureString( Device, String );
 			
 	// add a full display width	
-	while (Len < Max && GDS_FontMeasureString( Device, String ) - Boundary < Device->Width) {
+	while (Len < Max && GDS_FontMeasureString( Device, String ) - Boundary < Device->TextWidth) {
 		String[Len++] = String[Extra++];
 		String[Len] = '\0';
 	}

+ 1 - 1
components/display/core/ifaces/default_if_i2c.c

@@ -75,7 +75,7 @@ bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int
     Device->RSTPin = RSTPin;
 	Device->Backlight.Pin = BacklightPin;	
 	Device->IF = GDS_IF_I2C;
-	Device->Width = Width;
+	Device->Width = Device->TextWidth = Width;
 	Device->Height = Height;
 	
 	if ( RSTPin >= 0 ) {

+ 2 - 4
components/display/core/ifaces/default_if_spi.c

@@ -35,7 +35,7 @@ bool GDS_SPIInit( int SPI, int DC ) {
 }
 
 bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed ) {
-    spi_device_interface_config_t SPIDeviceConfig;
+    spi_device_interface_config_t SPIDeviceConfig = { };
     spi_device_handle_t SPIDevice;
 
     NullCheck( Device, return false );
@@ -45,8 +45,6 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
 		ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false );
 	}
 	
-    memset( &SPIDeviceConfig, 0, sizeof( spi_device_interface_config_t ) );
-
     SPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_8M;
     SPIDeviceConfig.spics_io_num = CSPin;
     SPIDeviceConfig.queue_size = 1;
@@ -63,7 +61,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
     Device->CSPin = CSPin;
 	Device->Backlight.Pin = BackLightPin;	
 	Device->IF = GDS_IF_SPI;
-	Device->Width = Width;
+	Device->Width = Device->TextWidth = Width;
 	Device->Height = Height;
 	
 	if ( RSTPin >= 0 ) {

+ 56 - 1
components/display/display.c

@@ -20,6 +20,7 @@
 #include "gds_draw.h"
 #include "gds_text.h"
 #include "gds_font.h"
+#include "gds_image.h"
 
 static const char *TAG = "display";
 
@@ -30,6 +31,9 @@ static const char *TAG = "display";
 #define SCROLLABLE_SIZE			384
 #define HEADER_SIZE				64
 #define	DEFAULT_SLEEP			3600
+#define ARTWORK_BORDER			1
+
+extern const uint8_t default_artwork[]   asm("_binary_note_jpg_start");
 
 static EXT_RAM_ATTR struct {
 	TaskHandle_t task;
@@ -47,6 +51,13 @@ static EXT_RAM_ATTR struct {
 		char string[8]; // H:MM:SS
 		bool visible;
 	} duration;
+	struct {
+		bool enable, active;
+		bool fit;
+		bool updated;
+		int tick;
+		int offset;
+	}  artwork;
 	TickType_t tick;
 } displayer;
 
@@ -147,6 +158,14 @@ void display_init(char *welcome) {
 		GDS_TextSetFontAuto(display, 2, GDS_FONT_LINE_2, -3);
 		
 		displayer.metadata_config = config_alloc_get(NVS_TYPE_STR, "metadata_config");
+		
+		// leave room for artwork is display is horizontal-style
+		if (strcasestr(displayer.metadata_config, "artwork")) {
+			displayer.artwork.enable = true;
+			displayer.artwork.fit = true;
+			if (height <= 64 && width > height * 2) displayer.artwork.offset = width - height - ARTWORK_BORDER;
+			PARSE_PARAM(displayer.metadata_config, "artwork", ':', displayer.artwork.fit);
+		}	
 	}
 	
 	free(config);
@@ -225,7 +244,12 @@ static void displayer_task(void *args) {
 				// just re-write the whole line it's easier
 				GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR, displayer.header);	
 				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.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);
+				}	
 				timer_sleep = 1000;
 			} else timer_sleep = max(1000 - elapsed, 0);	
 		} else timer_sleep = DEFAULT_SLEEP;
@@ -238,6 +262,32 @@ static void displayer_task(void *args) {
 	}
 }	
 
+/****************************************************************************************
+ * 
+ */
+void displayer_artwork(uint8_t *data) {
+	if (!displayer.artwork.active) return;
+	
+	int x = displayer.artwork.offset ? displayer.artwork.offset + ARTWORK_BORDER : 0;
+	int y = x ? 0 : 32;
+	GDS_ClearWindow(display, x, y, -1, -1, GDS_COLOR_BLACK);
+	if (data) {
+		displayer.artwork.updated = true;
+		GDS_DrawJPEG(display, data, x, y, GDS_IMAGE_CENTER | (displayer.artwork.fit ? GDS_IMAGE_FIT : 0));
+	} else {
+		displayer.artwork.updated = false;
+		displayer.artwork.tick = xTaskGetTickCount();
+	}	
+	
+}
+
+/****************************************************************************************
+ * 
+ */
+bool displayer_can_artwork(void) {
+	return displayer.artwork.active;
+}
+
 /****************************************************************************************
  * 
  */
@@ -378,6 +428,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 	switch(cmd) {
 	case DISPLAYER_ACTIVATE: {	
 		char *header = va_arg(args, char*);
+		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;
@@ -388,16 +439,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 (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);
+		displayer_artwork(NULL);
 		displayer.state = DISPLAYER_DOWN;
 		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		break;

+ 2 - 0
components/display/display.h

@@ -38,5 +38,7 @@ bool display_is_valid_driver(const char * driver);
 void displayer_scroll(char *string, int speed, int pause);
 void displayer_control(enum displayer_cmd_e cmd, ...);
 void displayer_metadata(char *artist, char *album, char *title);
+void displayer_artwork(uint8_t *data);
 void displayer_timer(enum displayer_time_e mode, int elapsed, int duration);
+bool displayer_can_artwork(void);
 char * display_get_supported_drivers(void);

BIN
components/display/note.jpg


+ 1 - 1
components/driver_bt/bt_app_sink.c

@@ -172,7 +172,7 @@ static bool cmd_handler(bt_sink_cmd_t cmd, ...) {
 	// now handle events for display
 	switch(cmd) {
 	case BT_SINK_AUDIO_STARTED:
-		displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH");
+		displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH", false);
 		break;
 	case BT_SINK_AUDIO_STOPPED:
 		displayer_control(DISPLAYER_SUSPEND);

+ 5 - 0
components/platform_config/platform_config.h

@@ -14,6 +14,11 @@ extern "C" {
 	if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); 	\
 } while (0)
 
+#define PARSE_PARAM_FLOAT(S,P,C,V) do {												\
+	char *__p;																	\
+	if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atof(__p+1); 	\
+} while (0)
+
 #define PARSE_PARAM_STR(S,P,C,V,I) do {						\
 	char *__p;                                              \
 	if ((__p = strstr(S, P)) && (__p = strchr(__p, C))) {	\

+ 6 - 0
components/platform_console/CMakeLists.txt

@@ -11,3 +11,9 @@ idf_component_register( SRCS
 						PRIV_REQUIRES console app_update tools services spi_flash  platform_config vfs pthread wifi-manager platform_config newlib  telnet display squeezelite tools)
 target_link_libraries(${COMPONENT_LIB}   "-Wl,--undefined=GDS_DrawPixelFast")
 target_link_libraries(${COMPONENT_LIB} ${build_dir}/esp-idf/$<TARGET_PROPERTY:RECOVERY_PREFIX>/lib$<TARGET_PROPERTY:RECOVERY_PREFIX>.a 	)
+
+set_source_files_properties(cmd_config.c
+    PROPERTIES COMPILE_FLAGS
+    -Wno-unused-function
+)	
+

+ 5 - 5
components/platform_console/platform_console.c

@@ -283,17 +283,17 @@ static int stdin_dummy(const char * path, int flags, int mode) {	return 0; }
 
 void initialize_console() {
 	/* Minicom, screen, idf_monitor send CR when ENTER key is pressed (unused if we redirect stdin) */
-	esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
+	esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
 	/* Move the caret to the beginning of the next line on '\n' */
-	esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
+	esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
 
 	/* Configure UART. Note that REF_TICK is used so that the baud rate remains
 	 * correct while APB frequency is changing in light sleep mode.
 	 */
-	const uart_config_t uart_config = { .baud_rate =
-			CONFIG_ESP_CONSOLE_UART_BAUDRATE, .data_bits = UART_DATA_8_BITS,
+	const uart_config_t uart_config = { .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE, 
+			.data_bits = UART_DATA_8_BITS,
 			.parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1,
-			.use_ref_tick = true };
+	};
 	ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
 
 	/* Install UART driver for interrupt-driven reads and writes */

+ 3 - 0
components/raop/raop.c

@@ -628,6 +628,9 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 				success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title);
 				free_metadata(&metadata);
 			}
+		} else if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && strcasestr(p, "image/jpeg")) {			
+			LOG_INFO("[%p]: received JPEG image of %d bytes", ctx, len);
+			ctx->cmd_cb(RAOP_ARTWORK, body, len);
 		} else {
 			char *dump = kd_dump(headers);
 			LOG_INFO("Unhandled SET PARAMETER\n%s", dump);

+ 7 - 1
components/raop/raop_sink.c

@@ -112,7 +112,7 @@ static bool cmd_handler(raop_event_t event, ...) {
 	switch(event) {
 	case RAOP_SETUP:
 		actrls_set(controls, false, NULL, actrls_ir_action);
-		displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY");
+		displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY", true);
 		break;
 	case RAOP_PLAY:
 		displayer_control(DISPLAYER_TIMER_RUN);
@@ -127,8 +127,14 @@ static bool cmd_handler(raop_event_t event, ...) {
 	case RAOP_METADATA: {
 		char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
 		displayer_metadata(artist, album, title);
+		displayer_artwork(NULL);
 		break;
 	}	
+	case RAOP_ARTWORK: {
+		uint8_t *data = va_arg(args, uint8_t*);
+		displayer_artwork(data);
+		break;
+	}
 	case RAOP_PROGRESS: {
 		int elapsed = va_arg(args, int), duration = va_arg(args, int);
 		displayer_timer(DISPLAYER_ELAPSED, elapsed, duration);

+ 1 - 1
components/raop/raop_sink.h

@@ -14,7 +14,7 @@
 
 #define RAOP_SAMPLE_RATE	44100
 
-typedef enum { 	RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP, 
+typedef enum { 	RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_ARTWORK, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP, 
 				RAOP_VOLUME, RAOP_TIMING, RAOP_PREV, RAOP_NEXT, RAOP_REW, RAOP_FWD, 
 				RAOP_VOLUME_UP, RAOP_VOLUME_DOWN, RAOP_RESUME, RAOP_TOGGLE } raop_event_t ;
 

+ 0 - 6
components/services/accessors.c

@@ -1109,7 +1109,6 @@ cJSON * get_gpio_list(bool refresh) {
 	}
 	gpio_list= cJSON_CreateArray();
 	
-#ifndef CONFIG_BAT_LOCKED
 	char *bat_config = config_alloc_get_default(NVS_TYPE_STR, "bat_config", NULL, 0);
 	if (bat_config) {
 		int channel = -1;
@@ -1121,11 +1120,6 @@ cJSON * get_gpio_list(bool refresh) {
 		}
 		free(bat_config);
 	}
-#else
-		if(adc1_pad_get_io_num(CONFIG_BAT_CHANNEL,&gpio_num )==ESP_OK){
-			cJSON_AddItemToArray(gpio_list,get_gpio_entry("bat","other",gpio_num,true));
-		}
-#endif
 	gpio_list=get_GPIO_nvs_list(gpio_list);
 	gpio_list=get_SPDIF_GPIO(gpio_list,is_spdif_config_locked());
 	gpio_list=get_Rotary_GPIO(gpio_list);

+ 14 - 11
components/services/battery.c

@@ -35,12 +35,13 @@ static struct {
 	int count;
 	int cells, attenuation;
 	TimerHandle_t timer;
-} battery = {
-	.channel = CONFIG_BAT_CHANNEL,
+} battery = { 
+	.channel = -1,
 	.cells = 2,
-	.attenuation = ADC_ATTEN_DB_0,
 };	
 
+void (*battery_handler_svc)(float value);
+
 /****************************************************************************************
  * 
  */
@@ -65,6 +66,7 @@ static void battery_callback(TimerHandle_t xTimer) {
 	if (++battery.count == 30) {
 		battery.avg = battery.sum / battery.count;
 		battery.sum = battery.count = 0;
+		if (battery_handler_svc) (battery_handler_svc)(battery.avg);
 		ESP_LOGI(TAG, "Voltage %.2fV", battery.avg);
 	}	
 }
@@ -73,17 +75,18 @@ static void battery_callback(TimerHandle_t xTimer) {
  * 
  */
 void battery_svc_init(void) {
-#ifdef CONFIG_BAT_SCALE	
-	battery.scale = atof(CONFIG_BAT_SCALE);
-#endif	
+	char *nvs_item = config_alloc_get_default(NVS_TYPE_STR, "bat_config", "", 0);
+	
+#ifdef CONFIG_BAT_LOCKED
+	char *p = nvs_item;
+	asprintf(&nvs_item, CONFIG_BAT_CONFIG ",%s", p);
+	free(p);
+#endif		
 
-	char *nvs_item = config_alloc_get_default(NVS_TYPE_STR, "bat_config", "n", 0);
 	if (nvs_item) {
-#ifndef CONFIG_BAT_LOCKED		
 		PARSE_PARAM(nvs_item, "channel", '=', battery.channel);
-		PARSE_PARAM(nvs_item, "scale", '=', battery.scale);
+		PARSE_PARAM_FLOAT(nvs_item, "scale", '=', battery.scale);
 		PARSE_PARAM(nvs_item, "atten", '=', battery.attenuation);
-#endif		
 		PARSE_PARAM(nvs_item, "cells", '=', battery.cells);
 		free(nvs_item);
 	}	
@@ -96,7 +99,7 @@ void battery_svc_init(void) {
 		battery.timer = xTimerCreate("battery", BATTERY_TIMER / portTICK_RATE_MS, pdTRUE, NULL, battery_callback);
 		xTimerStart(battery.timer, portMAX_DELAY);
 		
-		ESP_LOGI(TAG, "Battery measure channel: %u, scale %f, cells %u, avg %.2fV", battery.channel, battery.scale, battery.cells, battery.avg);		
+		ESP_LOGI(TAG, "Battery measure channel: %u, scale %f, atten %d, cells %u, avg %.2fV", battery.channel, battery.scale, battery.attenuation, battery.cells, battery.avg);		
 	} else {
 		ESP_LOGI(TAG, "No battery");
 	}	

+ 1 - 0
components/services/monitor.h

@@ -20,6 +20,7 @@ extern bool jack_inserted_svc(void);
 extern void (*spkfault_handler_svc)(bool inserted);
 extern bool spkfault_svc(void);
 
+extern void (*battery_handler_svc)(float value);
 extern float battery_value_svc(void);
 extern uint16_t battery_level_svc(void);
 

+ 3 - 3
components/spotify/Shim.cpp

@@ -119,8 +119,8 @@ static void cspotTask(void *pvParameters) {
             switch (event.eventType) {
             case CSpotEventType::TRACK_INFO: {
                 TrackInfo track = std::get<TrackInfo>(event.data);
-				// duration is in chunks of 0.5 ms
-				cspot.cHandler(CSPOT_TRACK, 44100, track.duration / 2, track.artist.c_str(), track.album.c_str(), track.name.c_str());
+				cspot.cHandler(CSPOT_TRACK, 44100, track.duration, track.artist.c_str(), 
+							   track.album.c_str(), track.name.c_str(), track.imageUrl.c_str());
                 break;
             }
             case CSpotEventType::PLAY_PAUSE: {
@@ -192,7 +192,7 @@ struct cspot_s* cspot_create(const char *name, cspot_cmd_cb_t cmd_cb, cspot_data
 	cspot.cHandler = cmd_cb;
 	cspot.dHandler = data_cb;
 	strncpy(cspot.name, name, sizeof(cspot.name) - 1);
-    cspot.TaskHandle = xTaskCreateStatic(&cspotTask, "cspot", CSPOT_STACK_SIZE, NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT, xStack, &xTaskBuffer);
+    cspot.TaskHandle = xTaskCreateStatic(&cspotTask, "cspot", CSPOT_STACK_SIZE, NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT - 2, xStack, &xTaskBuffer);
 	
 	return &cspot;
 }

+ 1 - 0
components/spotify/cspot/CMakeLists.txt

@@ -45,5 +45,6 @@ endif()
 add_library(cspot STATIC ${SOURCES} ${PROTO_SRCS})
 # PUBLIC to propagate includes from bell to cspot dependents
 target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC)
+target_compile_definitions(bell PUBLIC PB_FIELD_32BIT)
 target_link_libraries(cspot PUBLIC ${EXTRA_LIBS})
 target_include_directories(cspot PUBLIC "include" ${GENERATED_INCLUDES} ${NANOPB_INCLUDE_DIRS})

+ 1 - 2
components/spotify/cspot/bell/CMakeLists.txt

@@ -7,8 +7,6 @@ option(BELL_DISABLE_CODECS "Disable libhelix AAC and MP3 codecs" OFF)
 #set(BELL_EXTERNAL_CJSON "" CACHE STRING "External cJSON library target name, optional")
 #set(BELL_EXTERNAL_TREMOR "" CACHE STRING "External tremor library target name, optional")
 
-add_definitions(-DPB_ENABLE_MALLOC)
-
 # Include nanoPB library
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/nanopb/extra)
 find_package(Nanopb REQUIRED)
@@ -98,3 +96,4 @@ message(${NANOPB_INCLUDE_DIRS})
 # PUBLIC to propagate esp-idf includes to bell dependents
 target_link_libraries(bell PUBLIC ${EXTRA_LIBS})
 target_include_directories(bell PUBLIC "include" ${EXTRA_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
+target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC)

+ 1 - 1
components/spotify/cspot/bell/include/CryptoMbedTLS.h

@@ -56,7 +56,7 @@ public:
     std::vector<uint8_t> sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message);
 
     // AES CTR
-    void aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, std::vector<uint8_t>& data);
+    void aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, uint8_t* data, size_t nbytes);
     
     // AES ECB
     void aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data);

+ 1 - 1
components/spotify/cspot/bell/include/CryptoOpenSSL.h

@@ -63,7 +63,7 @@ public:
     std::vector<uint8_t> sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message);
 
     // AES CTR
-    void aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, std::vector<uint8_t>& data);
+    void aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, uint8_t* buffer, size_t nbytes);
     
     // AES ECB
     void aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data);

+ 4 - 0
components/spotify/cspot/bell/include/NanoPBHelper.h

@@ -42,6 +42,10 @@ void pbDecode(T &result, const pb_msgdesc_t *fields, std::vector<uint8_t> &data)
     }
 }
 
+void pbPutString(const std::string &stringToPack, char* dst);
+void pbPutCharArray(const char * stringToPack, char* dst);
+void pbPutBytes(const std::vector<uint8_t> &data, pb_bytes_array_t &dst);
+
 const char* pb_encode_to_string(const pb_msgdesc_t *fields, const void *data);
 pb_istream_t pb_istream_from_http(bell::HTTPClient::HTTPResponse *response, size_t length = 0);
 

+ 1 - 1
components/spotify/cspot/bell/include/Task.h

@@ -29,7 +29,7 @@ namespace bell
 #ifdef ESP_PLATFORM
 			this->xStack = NULL;
 			this->priority = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + priority;
-			if (this->priority < 0) this->priority = ESP_TASK_PRIO_MIN;
+			if (this->priority <= ESP_TASK_PRIO_MIN) this->priority = ESP_TASK_PRIO_MIN + 1;
 			if (runOnPSRAM) {
 				this->xStack = (StackType_t*) heap_caps_malloc(this->stackSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
 			}

+ 5 - 5
components/spotify/cspot/bell/src/CryptoMBedTLS.cpp

@@ -88,7 +88,7 @@ std::vector<uint8_t> CryptoMbedTLS::sha1HMAC(const std::vector<uint8_t> &inputKe
 }
 
 // AES CTR
-void CryptoMbedTLS::aesCTRXcrypt(const std::vector<uint8_t> &key, std::vector<uint8_t> &iv, std::vector<uint8_t> &data)
+void CryptoMbedTLS::aesCTRXcrypt(const std::vector<uint8_t> &key, std::vector<uint8_t> &iv, uint8_t* buffer, size_t nbytes)
 {
     // needed for internal cache
     size_t off = 0;
@@ -99,12 +99,12 @@ void CryptoMbedTLS::aesCTRXcrypt(const std::vector<uint8_t> &key, std::vector<ui
 
     // Perform decrypt
     mbedtls_aes_crypt_ctr(&aesCtx,
-                          data.size(),
+                          nbytes,
                           &off,
                           iv.data(),
                           streamBlock,
-                          data.data(),
-                          data.data());
+                          buffer,
+                          buffer);
 }
 
 void CryptoMbedTLS::aesECBdecrypt(const std::vector<uint8_t> &key, std::vector<uint8_t> &data)
@@ -115,7 +115,7 @@ void CryptoMbedTLS::aesECBdecrypt(const std::vector<uint8_t> &key, std::vector<u
     // Mbedtls's decrypt only works on 16 byte blocks
     for (unsigned int x = 0; x < data.size() / 16; x++)
     {
-        // Perform decrypt
+        // Perform finalize
         mbedtls_aes_crypt_ecb(&aesCtx,
                               MBEDTLS_AES_DECRYPT,
                               data.data() + (x * 16),

+ 4 - 4
components/spotify/cspot/bell/src/CryptoOpenSSL.cpp

@@ -99,7 +99,7 @@ std::vector<uint8_t> CryptoOpenSSL::sha1HMAC(const std::vector<uint8_t>& inputKe
 }
 
 // AES CTR
-void CryptoOpenSSL::aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, std::vector<uint8_t> &data)
+void CryptoOpenSSL::aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, uint8_t* buffer, size_t nbytes)
 {
     // Prepare AES_KEY
     auto cryptoKey = AES_KEY();
@@ -110,9 +110,9 @@ void CryptoOpenSSL::aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<ui
     unsigned int offsetInBlock = 0;
 
     CRYPTO_ctr128_encrypt(
-        data.data(),
-        data.data(),
-        data.size(),
+            buffer,
+            buffer,
+        nbytes,
         &cryptoKey,
         iv.data(),
         ecountBuf,

+ 1 - 1
components/spotify/cspot/bell/src/HTTPClient.cpp

@@ -287,7 +287,7 @@ std::string HTTPClient::HTTPResponse::readToString() {
 		return result;
 	}
 	std::string result;
-	char buffer[BUF_SIZE];
+        char buffer[BUF_SIZE+1]; // make space for null-terminator
 	size_t len;
 	do {
 		len = this->read(buffer, BUF_SIZE);

+ 16 - 0
components/spotify/cspot/bell/src/NanoPBHelper.cpp

@@ -47,6 +47,22 @@ pb_bytes_array_t* vectorToPbArray(const std::vector<uint8_t>& vectorToPack)
     return result;
 }
 
+void pbPutString(const std::string &stringToPack, char* dst) {
+    stringToPack.copy(dst, stringToPack.size());
+    dst[stringToPack.size()] = '\0';
+}
+
+void pbPutCharArray(const char * stringToPack, char* dst) {
+    // copy stringToPack into dst
+    strcpy(dst, stringToPack);
+    //dst[sizeof(stringToPack)-1] = '\0';
+}
+
+void pbPutBytes(const std::vector<uint8_t> &data, pb_bytes_array_t &dst) {
+    dst.size = data.size();
+    std::copy(data.begin(), data.end(), dst.bytes);
+}
+
 std::vector<uint8_t> pbArrayToVector(pb_bytes_array_t* pbArray) {
     return std::vector<uint8_t>(pbArray->bytes, pbArray->bytes + pbArray->size);
 }

+ 18 - 8
components/spotify/cspot/include/AudioChunk.h

@@ -14,12 +14,15 @@ class AudioChunk {
 private:
     /**
      * @brief Calculates a correct IV by performing bignum addition.
-     * 
+     *
      * @param num Number to add to IV.
-     * @return std::vector<uint8_t> 
+     * @return std::vector<uint8_t>
      */
     std::vector<uint8_t> getIVSum(uint32_t num);
 
+    size_t decryptedCount = 0;
+    size_t oldStartPos;
+
 public:
     std::unique_ptr<Crypto> crypto;
     std::vector<uint8_t> decryptedData;
@@ -41,14 +44,21 @@ public:
     std::unique_ptr<WrappedSemaphore> isLoadedSemaphore;
 
     /**
-     * @brief 
+     * @brief
      */
     std::unique_ptr<WrappedSemaphore> isHeaderFileSizeLoadedSemaphore;
 
+    /**
+     * Decrypts data and writes it to the target buffer
+     * @param target data buffer to write to
+     * @param offset data offset
+     * @param nbytes number of bytes to read
+     */
+    void readData(uint8_t *target, size_t offset, size_t nbytes);
 
     /**
      * @brief AudioChunk handles all audiochunk related operations.
-     * 
+     *
      * @param seqId Sequence id of requested chunk
      * @param audioKey Audio key used for decryption of audio data
      * @param startPosition Start position of current chunk in audio file
@@ -59,16 +69,16 @@ public:
 
     /**
      * @brief Appends incoming chunked data to local cache.
-     * 
+     *
      * @param data encrypted binary audio data.
      */
     void appendData(const std::vector<uint8_t> &data);
 
     /**
-     * @brief Performs AES CTR decryption of received data.
-     * 
+     * @brief Sets loaded status on the chunk
+     *
      */
-    void decrypt();
+    void finalize();
 };
 
 #endif

+ 1 - 1
components/spotify/cspot/include/Packet.h

@@ -9,7 +9,7 @@ class Packet
 private:
 
 public:
-    Packet(uint8_t command, std::vector<uint8_t> &data);
+    Packet(uint8_t command, const std::vector<uint8_t> &data);
     uint8_t command;
     std::vector<uint8_t> data;
 };

+ 1 - 1
components/spotify/cspot/include/Shannon.h

@@ -13,7 +13,7 @@ public:
     void stream(std::vector<uint8_t> &buf);  /* stream cipher */
     void maconly(std::vector<uint8_t> &buf); /* accumulate MAC */
     void encrypt(std::vector<uint8_t> &buf); /* encrypt + MAC */
-    void decrypt(std::vector<uint8_t> &buf); /* decrypt + MAC */
+    void decrypt(std::vector<uint8_t> &buf); /* finalize + MAC */
     void finish(std::vector<uint8_t> &buf);  /* finalise MAC */
 
 private:

+ 1 - 0
components/spotify/cspot/include/SpircController.h

@@ -37,6 +37,7 @@ class SpircController {
 private:
     std::shared_ptr<MercuryManager> manager;
     std::string username;
+    bool firstFrame = true;
     std::unique_ptr<Player> player;
     std::unique_ptr<PlayerState> state;
     std::shared_ptr<AudioSink> audioSink;

+ 5 - 6
components/spotify/cspot/protobuf/authentication.options

@@ -1,6 +1,5 @@
-LoginCredentials.username type:FT_POINTER
-LoginCredentials.auth_data type:FT_POINTER
-LoginCredentials.auth_data type:FT_POINTER
-SystemInfo.system_information_string type:FT_POINTER
-SystemInfo.device_id type:FT_POINTER
-ClientResponseEncrypted.version_string type:FT_POINTER
+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

+ 1 - 1
components/spotify/cspot/protobuf/authentication.pb.c

@@ -9,7 +9,7 @@
 PB_BIND(SystemInfo, SystemInfo, 2)
 
 
-PB_BIND(LoginCredentials, LoginCredentials, AUTO)
+PB_BIND(LoginCredentials, LoginCredentials, 2)
 
 
 PB_BIND(ClientResponseEncrypted, ClientResponseEncrypted, 2)

+ 21 - 19
components/spotify/cspot/protobuf/authentication.pb.h

@@ -58,23 +58,25 @@ typedef enum _AuthenticationType {
 } AuthenticationType;
 
 /* Struct definitions */
+typedef PB_BYTES_ARRAY_T(512) LoginCredentials_auth_data_t;
 typedef struct _LoginCredentials { 
-    char *username; 
+    char username[30]; 
     AuthenticationType typ; 
-    pb_bytes_array_t *auth_data; 
+    LoginCredentials_auth_data_t auth_data; 
 } LoginCredentials;
 
 typedef struct _SystemInfo { 
     CpuFamily cpu_family; 
     Os os; 
-    char *system_information_string; 
-    char *device_id; 
+    char system_information_string[16]; 
+    char device_id[50]; 
 } SystemInfo;
 
 typedef struct _ClientResponseEncrypted { 
     LoginCredentials login_credentials; 
     SystemInfo system_info; 
-    char *version_string; 
+    bool has_version_string;
+    char version_string[32]; 
 } ClientResponseEncrypted;
 
 
@@ -97,12 +99,12 @@ extern "C" {
 #endif
 
 /* Initializer values for message structs */
-#define SystemInfo_init_default                  {_CpuFamily_MIN, _Os_MIN, NULL, NULL}
-#define LoginCredentials_init_default            {NULL, _AuthenticationType_MIN, NULL}
-#define ClientResponseEncrypted_init_default     {LoginCredentials_init_default, SystemInfo_init_default, NULL}
-#define SystemInfo_init_zero                     {_CpuFamily_MIN, _Os_MIN, NULL, NULL}
-#define LoginCredentials_init_zero               {NULL, _AuthenticationType_MIN, NULL}
-#define ClientResponseEncrypted_init_zero        {LoginCredentials_init_zero, SystemInfo_init_zero, NULL}
+#define SystemInfo_init_default                  {_CpuFamily_MIN, _Os_MIN, "", ""}
+#define LoginCredentials_init_default            {"", _AuthenticationType_MIN, {0, {0}}}
+#define ClientResponseEncrypted_init_default     {LoginCredentials_init_default, SystemInfo_init_default, false, ""}
+#define SystemInfo_init_zero                     {_CpuFamily_MIN, _Os_MIN, "", ""}
+#define LoginCredentials_init_zero               {"", _AuthenticationType_MIN, {0, {0}}}
+#define ClientResponseEncrypted_init_zero        {LoginCredentials_init_zero, SystemInfo_init_zero, false, ""}
 
 /* Field tags (for use in manual encoding/decoding) */
 #define LoginCredentials_username_tag            10
@@ -120,22 +122,22 @@ extern "C" {
 #define SystemInfo_FIELDLIST(X, a) \
 X(a, STATIC,   REQUIRED, UENUM,    cpu_family,       10) \
 X(a, STATIC,   REQUIRED, UENUM,    os,               60) \
-X(a, POINTER,  OPTIONAL, STRING,   system_information_string,  90) \
-X(a, POINTER,  OPTIONAL, STRING,   device_id,       100)
+X(a, STATIC,   REQUIRED, STRING,   system_information_string,  90) \
+X(a, STATIC,   REQUIRED, STRING,   device_id,       100)
 #define SystemInfo_CALLBACK NULL
 #define SystemInfo_DEFAULT NULL
 
 #define LoginCredentials_FIELDLIST(X, a) \
-X(a, POINTER,  OPTIONAL, STRING,   username,         10) \
+X(a, STATIC,   REQUIRED, STRING,   username,         10) \
 X(a, STATIC,   REQUIRED, UENUM,    typ,              20) \
-X(a, POINTER,  OPTIONAL, BYTES,    auth_data,        30)
+X(a, STATIC,   REQUIRED, BYTES,    auth_data,        30)
 #define LoginCredentials_CALLBACK NULL
 #define LoginCredentials_DEFAULT NULL
 
 #define ClientResponseEncrypted_FIELDLIST(X, a) \
 X(a, STATIC,   REQUIRED, MESSAGE,  login_credentials,  10) \
 X(a, STATIC,   REQUIRED, MESSAGE,  system_info,      50) \
-X(a, POINTER,  OPTIONAL, STRING,   version_string,   70)
+X(a, STATIC,   OPTIONAL, STRING,   version_string,   70)
 #define ClientResponseEncrypted_CALLBACK NULL
 #define ClientResponseEncrypted_DEFAULT NULL
 #define ClientResponseEncrypted_login_credentials_MSGTYPE LoginCredentials
@@ -151,9 +153,9 @@ extern const pb_msgdesc_t ClientResponseEncrypted_msg;
 #define ClientResponseEncrypted_fields &ClientResponseEncrypted_msg
 
 /* Maximum encoded size of messages (where known) */
-/* SystemInfo_size depends on runtime parameters */
-/* LoginCredentials_size depends on runtime parameters */
-/* ClientResponseEncrypted_size depends on runtime parameters */
+#define ClientResponseEncrypted_size             665
+#define LoginCredentials_size                    550
+#define SystemInfo_size                          75
 
 #ifdef __cplusplus
 } /* extern "C" */

+ 4 - 4
components/spotify/cspot/protobuf/authentication.proto

@@ -48,14 +48,14 @@ enum AuthenticationType {
 message SystemInfo {
     required CpuFamily cpu_family = 0xa; 
     required Os os = 0x3c;
-    optional string system_information_string = 0x5a; 
-    optional string device_id = 0x64;
+    required string system_information_string = 0x5a;
+    required string device_id = 0x64;
 }
 
 message LoginCredentials {
-    optional string username = 0xa; 
+    required string username = 0xa;
     required AuthenticationType typ = 0x14; 
-    optional bytes auth_data = 0x1e; 
+    required bytes auth_data = 0x1e;
 }
 
 message ClientResponseEncrypted {

+ 2 - 2
components/spotify/cspot/protobuf/mercury.options

@@ -1,2 +1,2 @@
-Header.uri type:FT_POINTER
-Header.method type:FT_POINTER
+Header.uri max_size:64, fixed_length:false
+Header.method max_size:32, fixed_length:false

+ 9 - 7
components/spotify/cspot/protobuf/mercury.pb.h

@@ -11,8 +11,10 @@
 
 /* Struct definitions */
 typedef struct _Header { 
-    char *uri; 
-    char *method; 
+    bool has_uri;
+    char uri[64]; 
+    bool has_method;
+    char method[32]; 
 } Header;
 
 
@@ -21,8 +23,8 @@ extern "C" {
 #endif
 
 /* Initializer values for message structs */
-#define Header_init_default                      {NULL, NULL}
-#define Header_init_zero                         {NULL, NULL}
+#define Header_init_default                      {false, "", false, ""}
+#define Header_init_zero                         {false, "", false, ""}
 
 /* Field tags (for use in manual encoding/decoding) */
 #define Header_uri_tag                           1
@@ -30,8 +32,8 @@ extern "C" {
 
 /* Struct field encoding specification for nanopb */
 #define Header_FIELDLIST(X, a) \
-X(a, POINTER,  OPTIONAL, STRING,   uri,               1) \
-X(a, POINTER,  OPTIONAL, STRING,   method,            3)
+X(a, STATIC,   OPTIONAL, STRING,   uri,               1) \
+X(a, STATIC,   OPTIONAL, STRING,   method,            3)
 #define Header_CALLBACK NULL
 #define Header_DEFAULT NULL
 
@@ -41,7 +43,7 @@ extern const pb_msgdesc_t Header_msg;
 #define Header_fields &Header_msg
 
 /* Maximum encoded size of messages (where known) */
-/* Header_size depends on runtime parameters */
+#define Header_size                              98
 
 #ifdef __cplusplus
 } /* extern "C" */

+ 18 - 18
components/spotify/cspot/protobuf/metadata.options

@@ -1,18 +1,18 @@
-Track.name type:FT_POINTER
-Track.gid type:FT_POINTER
-Track.restriction type:FT_POINTER
-Track.alternative type:FT_POINTER
-Track.file type:FT_POINTER
-Track.artist type:FT_POINTER
-AudioFile.file_id type:FT_POINTER
-Image.file_id type:FT_POINTER
-Artist.gid type:FT_POINTER
-Artist.name type:FT_POINTER
-Album.name type:FT_POINTER
-Episode.gid type:FT_POINTER
-Episode.name type:FT_POINTER
-ImageGroup.image type:FT_POINTER
-Episode.audio type:FT_POINTER
-Episode.covers type:FT_POINTER
-Restriction.countries_allowed type:FT_POINTER
-Restriction.countries_forbidden type:FT_POINTER
+Track.name type: FT_POINTER
+Track.gid type: FT_POINTER
+Track.file type: FT_POINTER
+Track.restriction type: FT_POINTER
+Track.alternative type: FT_POINTER
+Track.artist type: FT_POINTER
+AudioFile.file_id type: FT_POINTER
+Image.file_id type: FT_POINTER
+Artist.gid type: FT_POINTER
+Artist.name type: FT_POINTER
+Album.name type: FT_POINTER
+Episode.gid type: FT_POINTER
+Episode.name type: FT_POINTER
+ImageGroup.image type: FT_POINTER
+Episode.audio type: FT_POINTER
+Episode.covers type: FT_POINTER
+Restriction.countries_allowed type: FT_POINTER
+Restriction.countries_forbidden type: FT_POINTER

+ 1 - 1
components/spotify/cspot/protobuf/spirc.options

@@ -7,7 +7,7 @@ DeviceState.sw_version type:FT_POINTER
 DeviceState.name type:FT_POINTER
 DeviceState.capabilities max_count:17, fixed_count:false
 State.context_uri type:FT_POINTER
-State.track max_count:100, fixed_count:false
+State.track type:FT_POINTER
 TrackRef.gid type:FT_POINTER
 TrackRef.uri type:FT_POINTER
 TrackRef.context type:FT_POINTER

+ 1 - 1
components/spotify/cspot/protobuf/spirc.pb.c

@@ -9,7 +9,7 @@
 PB_BIND(TrackRef, TrackRef, AUTO)
 
 
-PB_BIND(State, State, 4)
+PB_BIND(State, State, AUTO)
 
 
 PB_BIND(Capability, Capability, 2)

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 21 - 21
components/spotify/cspot/protobuf/spirc.pb.h


+ 20 - 5
components/spotify/cspot/src/AudioChunk.cpp

@@ -24,13 +24,28 @@ void AudioChunk::appendData(const std::vector<uint8_t> &data)
     this->decryptedData.insert(this->decryptedData.end(), data.begin(), data.end());
 }
 
-void AudioChunk::decrypt()
-{
-    // calculate the IV for right position
-    auto calculatedIV = this->getIVSum(startPosition / 16);
+void AudioChunk::readData(uint8_t *target, size_t offset, size_t nbytes) {
+    auto readPos = offset + nbytes;
+    auto modulo = (readPos % 16);
+    auto ivReadPos = readPos;
+    if (modulo != 0) {
+        ivReadPos += (16 - modulo);
+    }
+    if (ivReadPos > decryptedCount) {
+        // calculate the IV for right position
+        auto calculatedIV = this->getIVSum((oldStartPos + decryptedCount) / 16);
+
+        crypto->aesCTRXcrypt(this->audioKey, calculatedIV, decryptedData.data() + decryptedCount,  ivReadPos - decryptedCount);
+
+        decryptedCount = ivReadPos;
+    }
+    memcpy(target, this->decryptedData.data() + offset, nbytes);
 
-    crypto->aesCTRXcrypt(this->audioKey, calculatedIV, decryptedData);
+}
 
+void AudioChunk::finalize()
+{
+    this->oldStartPos = this->startPosition;
     this->startPosition = this->endPosition - this->decryptedData.size();
     this->isLoaded = true;
 }

+ 4 - 4
components/spotify/cspot/src/AudioChunkManager.cpp

@@ -3,7 +3,7 @@
 #include "Logger.h"
 
 AudioChunkManager::AudioChunkManager()
-    : bell::Task("AudioChunkManager", 4 * 1024, 2, 0) {
+    : bell::Task("AudioChunkManager", 4 * 1024, -1, 0) {
     this->chunks = std::vector<std::shared_ptr<AudioChunk>>();
     startTask();
 }
@@ -80,7 +80,7 @@ void AudioChunkManager::runTask() {
 
                         switch (data.size()) {
                         case DATA_SIZE_HEADER: {
-                            CSPOT_LOG(debug, "ID: %d: header decrypt!", seqId);
+                            CSPOT_LOG(debug, "ID: %d: header finalize!", seqId);
                             auto headerSize = ntohs(extract<uint16_t>(data, 2));
                             // Got file size!
                             chunk->headerFileSize =
@@ -92,9 +92,9 @@ void AudioChunkManager::runTask() {
                             if (chunk->endPosition > chunk->headerFileSize) {
                                 chunk->endPosition = chunk->headerFileSize;
                             }
-                            CSPOT_LOG(debug, "ID: %d: Starting decrypt!",
+                            CSPOT_LOG(debug, "ID: %d: finalize chunk!",
                                       seqId);
-                            chunk->decrypt();
+                                chunk->finalize();
                             chunk->isLoadedSemaphore->give();
                             break;
 

+ 0 - 1
components/spotify/cspot/src/ChunkedAudioStream.cpp

@@ -156,7 +156,6 @@ void ChunkedAudioStream::startPlaybackLoop()
 
 void ChunkedAudioStream::seek(size_t dpos, Whence whence)
 {
-    BELL_LOG(info, "cspot", "%d", dpos);
     auto seekPos = 0;
     switch (whence)
     {

+ 11 - 10
components/spotify/cspot/src/ChunkedByteStream.cpp

@@ -31,6 +31,7 @@ void ChunkedByteStream::fetchFileInformation() {
     endChunk->keepInMemory = true;
 
     chunks.push_back(endChunk);
+    requestChunk(0);
 }
 
 std::shared_ptr<AudioChunk> ChunkedByteStream::getChunkForPosition(size_t position) {
@@ -57,11 +58,14 @@ size_t ChunkedByteStream::read(uint8_t *buf, size_t nbytes) {
     std::scoped_lock lock(this->readMutex);
     auto chunk = getChunkForPosition(pos);
     uint16_t chunkIndex = this->pos / AUDIO_CHUNK_SIZE;
-    for (auto it = chunks.begin(); it != chunks.end();) {
-        if (((*it)->endPosition<pos || (*it)->startPosition>(pos + 2 * AUDIO_CHUNK_SIZE)) && !(*it)->keepInMemory) {
-            it = chunks.erase(it);
-        } else {
-            it++;
+
+    if (loadAheadEnabled) {
+        for (auto it = chunks.begin(); it != chunks.end();) {
+            if (((*it)->endPosition<pos || (*it)->startPosition>(pos + 2 * AUDIO_CHUNK_SIZE)) && !(*it)->keepInMemory) {
+                it = chunks.erase(it);
+            } else {
+                it++;
+            }
         }
     }
 
@@ -85,7 +89,7 @@ size_t ChunkedByteStream::read(uint8_t *buf, size_t nbytes) {
         pos += read;
 
         auto nextChunkPos = ((chunkIndex + 1) * AUDIO_CHUNK_SIZE) + 1;
-        if (loadAheadEnabled && nextChunkPos + AUDIO_CHUNK_SIZE < fileSize) {
+        if (loadAheadEnabled && nextChunkPos  < fileSize) {
             auto nextChunk = getChunkForPosition(nextChunkPos);
 
             if (nextChunk == nullptr) {
@@ -109,15 +113,12 @@ size_t ChunkedByteStream::attemptRead(uint8_t *buffer, size_t bytes, std::shared
         toRead = chunk->decryptedData.size() - offset;
     }
 
-    // Copy data
-    memcpy(buffer, chunk->decryptedData.data() + offset, toRead);
-
+    chunk->readData(buffer, offset, toRead);
     return toRead;
 }
 
 void ChunkedByteStream::seek(size_t nbytes) {
     std::scoped_lock lock(this->readMutex);
-    BELL_LOG(info, "cspot", "seeking to %d", nbytes);
     pos = nbytes;
 
 

+ 1 - 1
components/spotify/cspot/src/LoginBlob.cpp

@@ -35,7 +35,7 @@ std::vector<uint8_t> LoginBlob::decodeBlob(const std::vector<uint8_t> &blob, con
     }
 
     encryptionKey = std::vector<uint8_t>(encryptionKey.begin(), encryptionKey.begin() + 16);
-    crypto->aesCTRXcrypt(encryptionKey, iv, encrypted);
+    crypto->aesCTRXcrypt(encryptionKey, iv, encrypted.data(), encrypted.size());
 
     return encrypted;
 }

+ 7 - 4
components/spotify/cspot/src/MercuryManager.cpp

@@ -9,9 +9,9 @@ std::map<MercuryType, std::string> MercuryTypeMap({
     {MercuryType::UNSUB, "UNSUB"},
     });
 
-MercuryManager::MercuryManager(std::unique_ptr<Session> session): bell::Task("mercuryManager", 6 * 1024, 2, 1)
+MercuryManager::MercuryManager(std::unique_ptr<Session> session): bell::Task("mercuryManager", 6 * 1024, 0, 1)
 {
-    tempMercuryHeader = Header_init_default;
+    tempMercuryHeader = {};
     this->timeProvider = std::make_shared<TimeProvider>();
     this->callbacks = std::map<uint64_t, mercuryCallback>();
     this->subscriptions = std::map<std::string, mercuryCallback>();
@@ -294,8 +294,11 @@ uint64_t MercuryManager::execute(MercuryType method, std::string uri, mercuryCal
     // Construct mercury header
 
     CSPOT_LOG(debug, "executing MercuryType %s", MercuryTypeMap[method].c_str());
-    tempMercuryHeader.uri = (char *)(uri.c_str());
-    tempMercuryHeader.method = (char *)(MercuryTypeMap[method].c_str());
+    pbPutString(uri, tempMercuryHeader.uri);
+    pbPutString(MercuryTypeMap[method], tempMercuryHeader.method);
+
+    tempMercuryHeader.has_method = true;
+    tempMercuryHeader.has_uri = true;
 
     // GET and SEND are actually the same. Therefore the override
     // The difference between them is only in header's method

+ 1 - 2
components/spotify/cspot/src/MercuryResponse.cpp

@@ -3,13 +3,12 @@
 MercuryResponse::MercuryResponse(std::vector<uint8_t> &data)
 {
     // this->mercuryHeader = std::make_unique<Header>();
-    this->mercuryHeader = Header_init_default;
+    this->mercuryHeader = {};
     this->parts = mercuryParts(0);
     this->parseResponse(data);
 }
 
 MercuryResponse::~MercuryResponse() {
-    pb_release(Header_fields, &mercuryHeader);
 }
 
 void MercuryResponse::parseResponse(std::vector<uint8_t> &data)

+ 1 - 1
components/spotify/cspot/src/Packet.cpp

@@ -1,6 +1,6 @@
 #include "Packet.h"
 
-Packet::Packet(uint8_t command, std::vector<uint8_t> &data) {
+Packet::Packet(uint8_t command, const std::vector<uint8_t> &data) {
     this->command = command;
     this->data = data;
 };

+ 1 - 1
components/spotify/cspot/src/Player.cpp

@@ -3,7 +3,7 @@
 
 // #include <valgrind/memcheck.h>
 
-Player::Player(std::shared_ptr<MercuryManager> manager, std::shared_ptr<AudioSink> audioSink): bell::Task("player", 10 * 1024, +0, 1)
+Player::Player(std::shared_ptr<MercuryManager> manager, std::shared_ptr<AudioSink> audioSink): bell::Task("player", 10 * 1024, -2, 1)
 {
     this->audioSink = audioSink;
     this->manager = manager;

+ 33 - 36
components/spotify/cspot/src/PlayerState.cpp

@@ -24,18 +24,18 @@ PlayerState::PlayerState(std::shared_ptr<TimeProvider> timeProvider)
     innerFrame.state.repeat = false;
     innerFrame.state.has_repeat = true;
 
-    innerFrame.device_state.sw_version = (char*) swVersion;
+    innerFrame.device_state.sw_version = strdup(swVersion);
 
     innerFrame.device_state.is_active = false;
     innerFrame.device_state.has_is_active = true;
-    
+
     innerFrame.device_state.can_play = true;
     innerFrame.device_state.has_can_play = true;
 
     innerFrame.device_state.volume = configMan->volume;
     innerFrame.device_state.has_volume = true;
 
-    innerFrame.device_state.name = (char*) configMan->deviceName.c_str();
+    innerFrame.device_state.name = strdup(configMan->deviceName.c_str());
 
     // Prepare player's capabilities
     addCapability(CapabilityType_kCanBePlayer, 1);
@@ -53,33 +53,32 @@ PlayerState::PlayerState(std::shared_ptr<TimeProvider> timeProvider)
 }
 
 PlayerState::~PlayerState() {
+    pb_release(Frame_fields, &innerFrame);
     pb_release(Frame_fields, &remoteFrame);
-    // do not destruct inner frame as it is never allocated
-//    pb_release(Frame_fields, &innerFrame);
 }
 
 void PlayerState::setPlaybackState(const PlaybackState state)
 {
     switch (state)
     {
-    case PlaybackState::Loading:
-        // Prepare the playback at position 0
-        innerFrame.state.status = PlayStatus_kPlayStatusPause;
-        innerFrame.state.position_ms = 0;
-        innerFrame.state.position_measured_at = timeProvider->getSyncedTimestamp();
-        break;
-    case PlaybackState::Playing:
-        innerFrame.state.status = PlayStatus_kPlayStatusPlay;
-        innerFrame.state.position_measured_at = timeProvider->getSyncedTimestamp();
-        break;
-    case PlaybackState::Stopped:
-        break;
-    case PlaybackState::Paused:
-        // Update state and recalculate current song position
-        innerFrame.state.status = PlayStatus_kPlayStatusPause;
-        uint32_t diff = timeProvider->getSyncedTimestamp() - innerFrame.state.position_measured_at;
-        this->updatePositionMs(innerFrame.state.position_ms + diff);
-        break;
+        case PlaybackState::Loading:
+            // Prepare the playback at position 0
+            innerFrame.state.status = PlayStatus_kPlayStatusPause;
+            innerFrame.state.position_ms = 0;
+            innerFrame.state.position_measured_at = timeProvider->getSyncedTimestamp();
+            break;
+        case PlaybackState::Playing:
+            innerFrame.state.status = PlayStatus_kPlayStatusPlay;
+            innerFrame.state.position_measured_at = timeProvider->getSyncedTimestamp();
+            break;
+        case PlaybackState::Stopped:
+            break;
+        case PlaybackState::Paused:
+            // Update state and recalculate current song position
+            innerFrame.state.status = PlayStatus_kPlayStatusPause;
+            uint32_t diff = timeProvider->getSyncedTimestamp() - innerFrame.state.position_measured_at;
+            this->updatePositionMs(innerFrame.state.position_ms + diff);
+            break;
     }
 }
 
@@ -137,8 +136,8 @@ void PlayerState::updatePositionMs(uint32_t position)
 void PlayerState::updateTracks()
 {
     CSPOT_LOG(info, "---- Track count %d", remoteFrame.state.track_count);
-    //innerFrame.state.context_uri = remoteFrame.state.context_uri == nullptr ? nullptr : strdup(remoteFrame.state.context_uri);
-    std::copy(std::begin(remoteFrame.state.track), std::end(remoteFrame.state.track), std::begin(innerFrame.state.track));
+    std::swap(innerFrame.state.context_uri, remoteFrame.state.context_uri);
+    std::swap(innerFrame.state.track, remoteFrame.state.track);
     innerFrame.state.track_count = remoteFrame.state.track_count;
     innerFrame.state.has_playing_track_index = true;
     innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index;
@@ -167,17 +166,13 @@ void PlayerState::setShuffle(bool shuffle)
     if (shuffle)
     {
         // Put current song at the begining
-        auto tmp = innerFrame.state.track[0];
-        innerFrame.state.track[0] = innerFrame.state.track[innerFrame.state.playing_track_index];
-        innerFrame.state.track[innerFrame.state.playing_track_index] = tmp;
+        std::swap(innerFrame.state.track[0], innerFrame.state.track[innerFrame.state.playing_track_index]);
 
         // Shuffle current tracks
         for (int x = 1; x < innerFrame.state.track_count - 1; x++)
         {
             auto j = x + (std::rand() % (innerFrame.state.track_count - x));
-            tmp = innerFrame.state.track[j];
-            innerFrame.state.track[j] = innerFrame.state.track[x];
-            innerFrame.state.track[x] = tmp;
+            std::swap(innerFrame.state.track[j], innerFrame.state.track[x]);
         }
         innerFrame.state.playing_track_index = 0;
     }
@@ -196,11 +191,14 @@ std::shared_ptr<TrackReference> PlayerState::getCurrentTrack()
 
 std::vector<uint8_t> PlayerState::encodeCurrentFrame(MessageType typ)
 {
+    free(innerFrame.ident);
+    free(innerFrame.protocol_version);
+
     // Prepare current frame info
     innerFrame.version = 1;
-    innerFrame.ident = (char *) deviceId;
+    innerFrame.ident = strdup(deviceId);
     innerFrame.seq_nr = this->seqNum;
-    innerFrame.protocol_version = (char*) protocolVersion;
+    innerFrame.protocol_version = strdup(protocolVersion);
     innerFrame.typ = typ;
     innerFrame.state_update_id = timeProvider->getSyncedTimestamp();
     innerFrame.has_version = true;
@@ -233,10 +231,9 @@ void PlayerState::addCapability(CapabilityType typ, int intValue, std::vector<st
 
     for (int x = 0; x < stringValue.size(); x++)
     {
-        stringValue[x].copy(this->innerFrame.device_state.capabilities[capabilityIndex].stringValue[x], stringValue[x].size());
-        this->innerFrame.device_state.capabilities[capabilityIndex].stringValue[x][stringValue[x].size()] = '\0';
+        pbPutString(stringValue[x], this->innerFrame.device_state.capabilities[capabilityIndex].stringValue[x]);
     }
 
     this->innerFrame.device_state.capabilities[capabilityIndex].stringValue_count = stringValue.size();
     this->capabilityIndex += 1;
-}
+}

+ 15 - 6
components/spotify/cspot/src/Session.cpp

@@ -49,17 +49,26 @@ std::vector<uint8_t> Session::authenticate(std::shared_ptr<LoginBlob> blob)
     authBlob = blob;
 
     // prepare authentication request proto
-    authRequest.login_credentials.username = (char *)(blob->username.c_str());
-    authRequest.login_credentials.auth_data = vectorToPbArray(blob->authData);
+    pbPutString(blob->username, authRequest.login_credentials.username);
+
+    std::copy(blob->authData.begin(), blob->authData.end(), authRequest.login_credentials.auth_data.bytes);
+    authRequest.login_credentials.auth_data.size = blob->authData.size();
+
     authRequest.login_credentials.typ = (AuthenticationType) blob->authType;
     authRequest.system_info.cpu_family = CpuFamily_CPU_UNKNOWN;
     authRequest.system_info.os = Os_OS_UNKNOWN;
-    authRequest.system_info.system_information_string = (char *)informationString;
-    authRequest.system_info.device_id = (char *)deviceId;
-    authRequest.version_string = (char *)versionString;
+
+    auto infoStr = std::string(informationString);
+    pbPutString(infoStr, authRequest.system_info.system_information_string);
+
+    auto deviceIdStr = std::string(deviceId);
+    pbPutString(deviceId, authRequest.system_info.device_id);
+
+    auto versionStr = std::string(versionString);
+    pbPutString(versionStr, authRequest.version_string);
+    authRequest.has_version_string = true;
 
     auto data = pbEncode(ClientResponseEncrypted_fields, &authRequest);
-    free(authRequest.login_credentials.auth_data);
 
     // Send login request
     this->shanConn->sendPacket(LOGIN_REQUEST_COMMAND, data);

+ 2 - 2
components/spotify/cspot/src/SpircController.cpp

@@ -56,7 +56,7 @@ void SpircController::disconnect(void) {
     state->setActive(false);
     notify();
     // Send the event at the end at it might be a last gasp
-    sendEvent(CSpotEventType::DISC);    
+    sendEvent(CSpotEventType::DISC);
 }
 
 void SpircController::playToggle() {
@@ -103,7 +103,7 @@ void SpircController::prevSong() {
 }
 
 void SpircController::handleFrame(std::vector<uint8_t> &data) {
-    //pb_release(Frame_fields, &state->remoteFrame);
+    pb_release(Frame_fields, &state->remoteFrame);
     pbDecode(state->remoteFrame, Frame_fields, data);
 
     switch (state->remoteFrame.typ) {

+ 9 - 7
components/spotify/cspot/src/SpotifyTrack.cpp

@@ -10,8 +10,8 @@ SpotifyTrack::SpotifyTrack(std::shared_ptr<MercuryManager> manager, std::shared_
 {
     this->manager = manager;
     this->fileId = std::vector<uint8_t>();
-    episodeInfo = Episode_init_default;
-    trackInfo = Track_init_default;
+    episodeInfo = {};
+    trackInfo = {};
 
     mercuryCallback trackResponseLambda = [=](std::unique_ptr<MercuryResponse> res) {
         this->trackInformationCallback(std::move(res), position_ms, isPaused);
@@ -84,13 +84,15 @@ void SpotifyTrack::trackInformationCallback(std::unique_ptr<MercuryResponse> res
     int altIndex = 0;
     while (!canPlayTrack())
     {
-        trackInfo.restriction = trackInfo.alternative[altIndex].restriction;
-        trackInfo.restriction_count = trackInfo.alternative[altIndex].restriction_count;
-        trackInfo.gid = trackInfo.alternative[altIndex].gid;
-        trackInfo.file = trackInfo.alternative[altIndex].file;
-        altIndex++;
+        std::swap(trackInfo.restriction, trackInfo.alternative[altIndex].restriction);
+        std::swap(trackInfo.restriction_count, trackInfo.alternative[altIndex].restriction_count);
+        std::swap(trackInfo.file, trackInfo.alternative[altIndex].file);
+        std::swap(trackInfo.file_count, trackInfo.alternative[altIndex].file_count);
+        std::swap(trackInfo.gid, trackInfo.alternative[altIndex].gid);
+
         CSPOT_LOG(info, "Trying alternative %d", altIndex);
     }
+
     auto trackId = pbArrayToVector(trackInfo.gid);
     this->fileId = std::vector<uint8_t>();
 

+ 20 - 1
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");
+		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 && displayer_can_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;

+ 5 - 0
components/squeezelite/CMakeLists.txt

@@ -21,6 +21,11 @@ set_source_files_properties(mad.c pcm.c flac.c alac.c helix-aac.c vorbis.c opus.
     -Wno-maybe-uninitialized 
 )
 
+set_source_files_properties(wm8978/wm8978.c
+    PROPERTIES COMPILE_FLAGS
+    -Wno-unused-function
+)	
+
 add_definitions(-DLINKALL -DLOOPBACK -DNO_FAAD -DEMBEDDED -DTREMOR_ONLY -DCUSTOM_VERSION=${BUILD_NUMBER})
 
 if (${DEPTH} EQUAL "32")

+ 3 - 5
components/squeezelite/equalizer.c

@@ -89,16 +89,14 @@ void equalizer_close(void) {
  */
 void equalizer_update(s8_t *gain) {
 	char config[EQ_BANDS * 4 + 1] = { };
-	char *p = config;
+	int n = 0;
 	
 	for (int i = 0; i < EQ_BANDS; i++) {
 		equalizer.gain[i] = gain[i];
-		if (gain[i] < 0) *p++ = '-';
-		*p++ = (gain[i] / 10) + 0x30;
-		*p++ = (gain[i] % 10) + 0x30;
-		if (i < EQ_BANDS - 1) *p++ = ',';
+		n += sprintf(config + n, "%d,", gain[i]);
 	}
 	
+	config[n-1] = '\0';
 	config_set_value(NVS_TYPE_STR, "equalizer", config);					
 	equalizer.update = true;
 }

+ 19 - 11
components/squeezelite/output_i2s.c

@@ -75,6 +75,10 @@ sure that using rate_delay would fix that
 #define STATS_PERIOD_MS 5000
 #define STAT_STACK_SIZE	(3*1024)
 
+#ifndef CONFIG_AMP_GPIO_LEVEL
+#define CONFIG_AMP_GPIO_LEVEL 1
+#endif
+
 extern struct outputstate output;
 extern struct buffer *streambuf;
 extern struct buffer *outputbuf;
@@ -101,7 +105,7 @@ static TaskHandle_t stats_task, output_i2s_task;
 static bool stats;
 static struct {
 	int gpio, active;
-} amp_control = { -1, 1 },
+} amp_control = { CONFIG_AMP_GPIO, CONFIG_AMP_GPIO_LEVEL },
   mute_control = { CONFIG_MUTE_GPIO, CONFIG_MUTE_GPIO_LEVEL };
 
 DECLARE_ALL_MIN_MAX;
@@ -171,20 +175,16 @@ static void jack_handler(bool inserted) {
 /****************************************************************************************
  * amp GPIO
  */
+#ifndef AMP_GPIO_LOCKED 
 static void set_amp_gpio(int gpio, char *value) {
 	char *p;
 	
 	if (strcasestr(value, "amp")) {
 		amp_control.gpio = gpio;
 		if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1);
-		
-		gpio_pad_select_gpio_x(amp_control.gpio);
-		gpio_set_direction_x(amp_control.gpio, GPIO_MODE_OUTPUT);
-		gpio_set_level_x(amp_control.gpio, !amp_control.active);
-		
-		LOG_INFO("setting amplifier GPIO %d (active:%d)", amp_control.gpio, amp_control.active);
 	}	
 }	
+#endif
 
 /****************************************************************************************
  * Set pin from config string
@@ -247,7 +247,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 	// common I2S initialization
 	i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
 	i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
-	i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
+	i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
 	// in case of overflow, do not replay old buffer
 	i2s_config.tx_desc_auto_clear = true;		
 	i2s_config.use_apll = true;
@@ -347,13 +347,21 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 	jack_handler_chain = jack_handler_svc;
 	jack_handler_svc = jack_handler;
 	
+#ifndef AMP_GPIO_LOCKED	
 	parse_set_GPIO(set_amp_gpio);
+#endif
+
+	if (amp_control.gpio != -1) {
+		gpio_pad_select_gpio_x(amp_control.gpio);
+		gpio_set_direction_x(amp_control.gpio, GPIO_MODE_OUTPUT);
+		gpio_set_level_x(amp_control.gpio, !amp_control.active);
+		LOG_INFO("setting amplifier GPIO %d (active:%d)", amp_control.gpio, amp_control.active);	
+	}	
 
 	if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
 	else adac->speaker(true);
 	
-	adac->headset(jack_inserted_svc());
-	
+	adac->headset(jack_inserted_svc());	
 
 	// create task as a FreeRTOS task but uses stack in internal RAM
 	{
@@ -672,7 +680,7 @@ static const u16_t spdif_bmclookup[256] = { //biphase mark encoded values (least
  audio is transmitted first (not the MSB) and that ESP32 libray sends R then L, 
  contrary to what seems to be usually done, so (dst) order had to be changed
 */
-void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count) {
+static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count) {
 	register u16_t hi, lo, aux;
 	size_t cnt = *count;
 	

+ 13 - 0
components/targets/CMakeLists.txt

@@ -0,0 +1,13 @@
+# This should be made a pure CMake component but as CMake is even
+# more shitty under Windows, backslash in path screws it all
+
+if(CONFIG_MUSE)
+	message("Compiling for MUSE") 
+	set(src_dirs "muse")
+else()
+	set(src_dirs ".")		
+endif()
+
+idf_component_register( SRC_DIRS ${src_dirs}    				  
+					    PRIV_REQUIRES services
+)

+ 3 - 0
components/targets/init.c

@@ -0,0 +1,3 @@
+// weak should do the job but it does not...
+ __attribute__((weak)) void target_init(void) { 
+}

+ 138 - 0
components/targets/muse/muse.c

@@ -0,0 +1,138 @@
+/*
+ YOUR LICENSE
+ */
+#include <string.h>
+#include <esp_log.h>
+#include <esp_types.h>
+#include <esp_system.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+//#include <driver/adc.h>
+#include "driver/rmt.h"
+#include "monitor.h"
+
+/////////////////////////////////////////////////////////////////
+//*********************** NeoPixels  ***************************
+////////////////////////////////////////////////////////////////
+#define NUM_LEDS  1
+#define LED_RMT_TX_CHANNEL   0
+#define LED_RMT_TX_GPIO      22
+
+#define BITS_PER_LED_CMD 24 
+#define LED_BUFFER_ITEMS ((NUM_LEDS * BITS_PER_LED_CMD))
+
+// These values are determined by measuring pulse timing with logic analyzer and adjusting to match datasheet. 
+#define T0H 14  // 0 bit high time
+#define T1H 52 // 1 bit high time
+#define TL  52  // low time for either bit
+
+#define GREEN   0xFF0000
+#define RED 	0x00FF00
+#define BLUE  	0x0000FF
+#define WHITE   0xFFFFFF
+#define YELLOW  0xE0F060
+struct led_state {
+    uint32_t leds[NUM_LEDS];
+};
+
+void ws2812_control_init(void);
+void ws2812_write_leds(struct led_state new_state);
+
+///////////////////////////////////////////////////////////////////
+
+static const char TAG[] = "muse";	
+
+static void (*battery_handler_chain)(float value);
+static void battery_svc(float value);
+
+void target_init(void) { 
+	battery_handler_chain = battery_handler_svc;
+	battery_handler_svc = battery_svc;
+	ESP_LOGI(TAG, "Initializing for Muse");
+}
+
+static void battery_svc(float value) {
+	ESP_LOGI(TAG, "Called for battery service with %f", value);
+	// put here your code for LED according to value
+	if (battery_handler_chain) battery_handler_chain(value);
+}
+
+// Battery monitoring
+/*
+static void battery(void *data)
+{
+#define VGREEN  2300
+#define VRED    2000
+#define NM      10
+  static int val;
+  static int V[NM];
+  static int I=0;
+  int S;
+  for(int i=0;i<NM;i++)V[i]=VGREEN;
+  vTaskDelay(1000 / portTICK_PERIOD_MS);	  
+  struct led_state new_state;
+  ws2812_control_init();
+// init ADC interface for battery survey
+  adc1_config_width(ADC_WIDTH_BIT_12);
+  adc1_config_channel_atten(ADC1_GPIO33_CHANNEL, ADC_ATTEN_DB_11);
+  while(true)
+	{
+	vTaskDelay(1000 / portTICK_PERIOD_MS);	
+	V[I++] = adc1_get_raw(ADC1_GPIO33_CHANNEL);
+	if(I >= NM)I = 0;
+	S = 0;
+	for(int i=0;i<NM;i++)S = S + V[i];	
+	val = S / NM;	
+	new_state.leds[0] = YELLOW;
+	if(val > VGREEN) new_state.leds[0] = GREEN;	
+	if(val < VRED) new_state.leds[0] = RED;
+        printf("====> %d  %6x\n", val, new_state.leds[0]);	        
+	ws2812_write_leds(new_state);	        
+
+	}
+}
+*/
+
+// This is the buffer which the hw peripheral will access while pulsing the output pin
+rmt_item32_t led_data_buffer[LED_BUFFER_ITEMS];
+
+void setup_rmt_data_buffer(struct led_state new_state);
+
+void ws2812_control_init(void)
+{
+  rmt_config_t config;
+  config.rmt_mode = RMT_MODE_TX;
+  config.channel = LED_RMT_TX_CHANNEL;
+  config.gpio_num = LED_RMT_TX_GPIO;
+  config.mem_block_num = 3;
+  config.tx_config.loop_en = false;
+  config.tx_config.carrier_en = false;
+  config.tx_config.idle_output_en = true;
+  config.tx_config.idle_level = 0;
+  config.clk_div = 2;
+
+  ESP_ERROR_CHECK(rmt_config(&config));
+  ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
+}
+
+void ws2812_write_leds(struct led_state new_state) {
+  setup_rmt_data_buffer(new_state);
+  ESP_ERROR_CHECK(rmt_write_items(LED_RMT_TX_CHANNEL, led_data_buffer, LED_BUFFER_ITEMS, false));
+  ESP_ERROR_CHECK(rmt_wait_tx_done(LED_RMT_TX_CHANNEL, portMAX_DELAY));
+}
+
+void setup_rmt_data_buffer(struct led_state new_state) 
+{
+  for (uint32_t led = 0; led < NUM_LEDS; led++) {
+    uint32_t bits_to_send = new_state.leds[led];
+    uint32_t mask = 1 << (BITS_PER_LED_CMD - 1);
+    for (uint32_t bit = 0; bit < BITS_PER_LED_CMD; bit++) {
+      uint32_t bit_is_set = bits_to_send & mask;
+      led_data_buffer[led * BITS_PER_LED_CMD + bit] = bit_is_set ?
+                                                      (rmt_item32_t){{{T1H, 1, TL, 0}}} : 
+                                                      (rmt_item32_t){{{T0H, 1, TL, 0}}};
+      mask >>= 1;
+    }
+  }
+  }
+

+ 1 - 0
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 .
 )
 

+ 103 - 1
components/tools/tools.c

@@ -11,9 +11,14 @@
 #include <stdint.h>
 #include <string.h>
 #include <ctype.h>
-#include "tools.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_task.h"
+#include "esp_tls.h"
+#include "esp_http_client.h"
 #include "esp_heap_caps.h"
 #include "esp_log.h"
+#include "tools.h"
 
 const static char TAG[] = "tools";
 
@@ -171,3 +176,100 @@ 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;
+	esp_http_client_handle_t client;
+} 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);
+	
+	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);
+}
+
+static void http_downloader(void *arg) {
+	http_context_t *http_context = (http_context_t*) arg;
+
+	esp_http_client_perform(http_context->client);
+	esp_http_client_cleanup(http_context->client);
+	
+	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;
+}
+ 

+ 3 - 0
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

+ 1 - 0
components/wifi-manager/webapp/src/index.ejs

@@ -440,6 +440,7 @@
 							GPL
 							License.</li>
 						<li>tarablessd1306, &copy; 2017-2018, Tara Keeling. Licensed under the MIT license.</li>
+						<li>CSpot, &copy; 2020 feelfreelinux & alufers. Licensed under the GPL License</li>
 					</ul>
 				</div>
 			</div>

+ 1 - 1
main/CMakeLists.txt

@@ -1,5 +1,5 @@
 idf_component_register(SRC_DIRS . 
-						PRIV_REQUIRES esp_common wifi-manager pthread squeezelite-ota platform_console telnet display
+						PRIV_REQUIRES esp_common wifi-manager pthread squeezelite-ota platform_console telnet display targets
                     	EMBED_FILES ../server_certs/github.pem
 						LDFRAGMENTS "linker.lf"
                     	)

+ 81 - 32
main/Kconfig.projbuild

@@ -26,6 +26,8 @@ menu "Squeezelite-ESP32"
 			help
 				Set logging level info|debug|sdebug
 	endmenu
+	config AMP_LOCKED
+		bool
 	config JACK_LOCKED
 		bool
 	config BAT_LOCKED
@@ -41,6 +43,9 @@ menu "Squeezelite-ESP32"
 	config MUTE_GPIO_LEVEL
 		int
 		default 0
+	config WELL_KNOWN
+		bool
+		default n
 	menu "Target"
 		choice OUTPUT_TYPE
 			prompt "Main system"
@@ -54,12 +59,21 @@ menu "Squeezelite-ESP32"
 				select I2C_LOCKED
 				select LED_LOCKED
 				select SPKFAULT_LOCKED
-			config BASIC_I2C_BT
-				bool "Generic I2S & Bluetooth"
-			config TWATCH2020
-				bool "T-WATCH2020 by LilyGo"
+				select WELL_KNOWN
+			config MUSE
+				bool "Muse"
+				select JACK_LOCKED
+				select BAT_LOCKED
 				select I2C_LOCKED
-		endchoice
+				select AMP_LOCKED				
+				select WELL_KNOWN
+			config BASIC_I2C_BT
+				bool "Generic I2S & Bluetooth"				
+			config TWATCH2020	
+				bool "T-WATCH2020 by LilyGo"				
+				select I2C_LOCKED		
+				select WELL_KNOWN				
+		endchoice	
 		config RELEASE_API
 			string "Software update URL"
 			default "https://api.github.com/repos/sle118/squeezelite-esp32/releases"
@@ -76,11 +90,13 @@ menu "Squeezelite-ESP32"
 			string
 			default "SqueezeAMP" if SQUEEZEAMP
 			default "Squeezelite-TWATCH" if TWATCH2020
+			default "Muse" if MUSE
 			default "Squeezelite-ESP32"
 		config FW_PLATFORM_NAME
 			string
 			default "SqueezeAmp" if SQUEEZEAMP
 			default "TWATCH" if TWATCH2020
+			default "Muse" if MUSE
 			default "ESP32"
 		# AGGREGATES - begin
 		# these parameters are "aggregates"	that take precedence. They must have a default value
@@ -88,6 +104,7 @@ menu "Squeezelite-ESP32"
 			string
 			default "model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0" if SQUEEZEAMP
 			default "model=I2S,bck=26,ws=25,do=33,i2c=53,sda=21,scl=22" if TWATCH2020
+			default "model=I2S,bck=5,ws=25,do=26,di=35,i2c=16,sda=18,scl=23,mck" if MUSE
 			default ""
 		config SPDIF_CONFIG
 			string
@@ -98,7 +115,8 @@ menu "Squeezelite-ESP32"
 			default	""
 		config SPI_CONFIG
 			string
-			default "dc=27,data=19,clk=18" if TWATCH2020
+			default "dc=27,data=19,clk=18" if TWATCH2020	
+			default "mosi=15,miso=2,clk=14"	if MUSE
 			default	""
 		config DISPLAY_CONFIG
 			string
@@ -107,17 +125,28 @@ menu "Squeezelite-ESP32"
 		config ETH_CONFIG
 			string
 			default ""
+		# AGGREGATES - end							
 		config DAC_CONTROLSET
 			string
-			default '{ "init": [ {"reg":41, "val":128}, {"reg":18, "val":255} ], "poweron": [ {"reg":18, "val":64, "mode":"or"} ], "poweroff": [ {"reg":18, "val":191, "mode":"and" } ] }' if TWATCH2020
+			default "{ \"init\": [ {\"reg\":41, \"val\":128}, {\"reg\":18, \"val\":255} ], \"poweron\": [ {\"reg\":18, \"val\":64, \"mode\":\"or\"} ], \"poweroff\": [ {\"reg\":18, \"val\":191, \"mode\":\"and\"} ] }" if TWATCH2020
+			default "{\"init\":[ {\"reg\":0,\"val\":128}, {\"reg\":0,\"val\":0}, {\"reg\":25,\"val\":4}, {\"reg\":1,\"val\":80}, {\"reg\":2,\"val\":0},	{\"reg\":8,\"val\":0}, {\"reg\":4,\"val\":192},	{\"reg\":0,\"val\":18}, {\"reg\":1,\"val\":0}, {\"reg\":23,\"val\":24}, {\"reg\":24,\"val\":2}, {\"reg\":38,\"val\":9}, {\"reg\":39,\"val\":144}, {\"reg\":42,\"val\":144}, {\"reg\":43,\"val\":128}, {\"reg\":45,\"val\":128}, {\"reg\":27,\"val\":0}, {\"reg\":26,\"val\":0}, {\"reg\":2,\"val\":240}, {\"reg\":2,\"val\":0},	{\"reg\":29,\"val\":28}, {\"reg\":4,\"val\":48}, {\"reg\":25,\"val\":0} ]}" if MUSE
+			default ""		
+		config AUDIO_CONTROLS
+			string
+			default "[{\"gpio\":32, \"pull\":true, \"debounce\":10, \"normal\":{\"pressed\":\"ACTRLS_VOLDOWN\"}}, {\"gpio\":19, \"pull\":true, \"debounce\":40, \"normal\":{\"pressed\":\"ACTRLS_VOLUP\"}}, {\"gpio\":12, \"pull\":true, \"debounce\":40, \"longpress\":1000, \"normal\":{\"pressed\":\"ACTRLS_TOGGLE\"},\"longpress\":{\"pressed\":\"ACTRLS_POWER\"}}]" if MUSE
 			default ""
-		# AGGREGATES - end
-
-		# VARs that must be reset when changign target
-		config JACK_GPIO
+		config BAT_CONFIG
+			default "channel=7,scale=20.24,atten=0" if SQUEEZEAMP
+			default "channel=5,scale=1,atten=3,cells=1" if MUSE
+			default ""			
+		config AMP_GPIO		
 			int
-			default 34 if SQUEEZEAMP
+			default 21 if MUSE
 			default -1
+		config JACK_GPIO
+			int
+			default 34 if SQUEEZEAMP || MUSE
+			default -1			
 		config SPKFAULT_GPIO
 			int
 			default 2 if SQUEEZEAMP
@@ -129,6 +158,7 @@ menu "Squeezelite-ESP32"
 		config LED_GREEN_GPIO
 			int 
 			default 12 if SQUEEZEAMP
+			default 22 if MUSE
 			default -1
 		config LED_RED_GPIO
 			int
@@ -274,6 +304,14 @@ menu "Squeezelite-ESP32"
 			help
 				Enable Spotify connect using CSpot		
 	endmenu
+	
+	menu "Controls"
+		depends on !MUSE
+		config AUDIO_CONTROLS
+			string "Audio buttons set (JSON)"
+			help
+				Configuration of buttons (see README for syntax)
+	endmenu	
 
 	menu "Display Screen"
 		depends on !TWATCH2020
@@ -320,8 +358,9 @@ menu "Squeezelite-ESP32"
 				Set parameters of GPIO extender
 				model=<model>[,addr=<addr>][,base=<100..N>][,count=<0..32>][,intr=<gpio>][,port=dac|system]				
 	endmenu
+	
 	menu "LED configuration"
-		visible if !SQUEEZEAMP && !TWATCH2020
+		visible if !SQUEEZEAMP && !TWATCH2020 && !MUSE
 		config LED_GREEN_GPIO
 			int "Green led GPIO"
 			help
@@ -329,7 +368,7 @@ menu "Squeezelite-ESP32"
 		config LED_GREEN_GPIO_LEVEL
 			int "Green led ON level"
 			depends on LED_GREEN_GPIO != -1
-		config LED_RED_GPIO
+		config LED_RED_GPIO				
 			int "Red led GPIO"
 			help
 				Set to -1 for no LED
@@ -339,9 +378,10 @@ menu "Squeezelite-ESP32"
 			default 0 if SQUEEZEAMP
 			default 1
 	endmenu
-	menu "Audio JACK"
-		visible if !SQUEEZEAMP && !TWATCH2020
-		config JACK_GPIO
+	
+    menu "Audio JACK"	
+		visible if !WELL_KNOWN
+		config JACK_GPIO		
 			int "Jack insertion GPIO"
 			help
 				GPIO to detect speaker jack insertion. Set to -1 for no detection.
@@ -349,10 +389,23 @@ menu "Squeezelite-ESP32"
 			depends on JACK_GPIO != -1
 			int "Level when inserted (0/1)"
 			default 0
-	endmenu
-	menu "Speaker Fault"
-		visible if !SQUEEZEAMP && !TWATCH2020
-		config SPKFAULT_GPIO
+	endmenu	
+	
+	menu "Amplifier"	
+		visible if !WELL_KNOWN
+		config AMP_GPIO		
+			int "Amplifier GPIO"
+			help
+				GPIO to switch on/off amplifier. Set to -1 for no amplifier. 
+		config AMP_GPIO_LEVEL
+			depends on AMP_GPIO != -1
+			int "Active level(0/1)"
+			default 1
+	endmenu	
+	
+	menu "Speaker Fault"	
+		visible if !WELL_KNOWN
+		config SPKFAULT_GPIO		
 			int "Speaker fault GPIO"
 			help
 				GPIO to detect speaker fault condition. Set to -1 for no detection.
@@ -361,20 +414,16 @@ menu "Squeezelite-ESP32"
 			int "Level when fault (0/1)"
 			default 0
 	endmenu
+	
 	menu "Battery measure"
-		visible if !SQUEEZEAMP && !TWATCH2020
-		config BAT_CHANNEL
-			int "Set channel (0..7)"
-			help
-				Read a value every 10s on ADC1 on set Channel
-		config BAT_SCALE
-			string "Set scaling factor"
-			depends on BAT_CHANNEL != -1
-			default "20.24" if SQUEEZEAMP
-			default ""
+		visible if !WELL_KNOWN
+		config BAT_CONFIG
+			string "Battery acquisition configuration"
 			help
-				Set the scaling factor for this 12 bits ADC
+				Sets parameters for battery voltage measure
+				channel=<0..7>,scale=<ratio_to_4096>,atten=<adc_atten>,cells=<1..3>
 	endmenu	
+	
 	config DEFAULT_COMMAND_LINE
         string "Default command line to execute"
         default "squeezelite -o I2S -b 500:2000 -d all=info -C 30"

+ 2 - 0
main/esp_app_main.c

@@ -71,6 +71,7 @@ extern const uint8_t server_cert_pem_end[] asm("_binary_github_pem_end");
 // as an exception _init function don't need include
 extern void services_init(void);
 extern void	display_init(char *welcome);
+extern void target_init(void);
 const char * str_or_unknown(const char * str) { return (str?str:unknown_string_placeholder); }
 const char * str_or_null(const char * str) { return (str?str:null_string_placeholder); }
 bool is_recovery_running;
@@ -446,6 +447,7 @@ void app_main()
 	ESP_LOGI(TAG,"Initializing display");
 	display_init("SqueezeESP32");
 	MEMTRACE_PRINT_DELTA();
+	target_init();
 	if(is_recovery_running && display){
 		GDS_ClearExt(display, true);
 		GDS_SetFont(display, &Font_line_2 );

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.