浏览代码

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. 二进制
      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
 CONFIG_LED_RED_GPIO=-1
 # end of LED configuration
 # 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
 # Audio JACK
 #
 #
@@ -296,7 +309,7 @@ CONFIG_SPKFAULT_GPIO=-1
 #
 #
 # Battery measure
 # Battery measure
 #
 #
-CONFIG_BAT_CHANNEL=-1
+CONFIG_BAT_CONFIG=""
 # end of Battery measure
 # end of Battery measure
 
 
 CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
 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_USE_SECURE_ELEMENT is not set
 # CONFIG_ESP_TLS_SERVER is not set
 # CONFIG_ESP_TLS_SERVER is not set
 # CONFIG_ESP_TLS_PSK_VERIFICATION 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
 # end of ESP-TLS
 
 
 #
 #
@@ -727,7 +741,7 @@ CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 #
 #
 # ESP HTTP client
 # 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
 # CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set
 # end of ESP HTTP client
 # 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_JACK_GPIO_LEVEL=0
 CONFIG_SPKFAULT_GPIO=2
 CONFIG_SPKFAULT_GPIO=2
 CONFIG_SPKFAULT_GPIO_LEVEL=0
 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"
 CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
 # end of Squeezelite-ESP32
 # 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
 # Compiler options
 #
 #
@@ -505,7 +516,8 @@ CONFIG_ESP_TLS_USING_MBEDTLS=y
 # CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set
 # CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set
 # CONFIG_ESP_TLS_SERVER is not set
 # CONFIG_ESP_TLS_SERVER is not set
 # CONFIG_ESP_TLS_PSK_VERIFICATION 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
 # end of ESP-TLS
 
 
 #
 #
@@ -699,7 +711,7 @@ CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 #
 #
 # ESP HTTP client
 # 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
 # CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set
 # end of ESP HTTP client
 # 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
 						INCLUDE_DIRS . fonts core
 						REQUIRES platform_config tools esp_common
 						REQUIRES platform_config tools esp_common
 						PRIV_REQUIRES services freertos driver           
 						PRIV_REQUIRES services freertos driver           
+						EMBED_FILES note.jpg
 )
 )
 
 
 set_source_files_properties(display.c
 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 );
     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);
 	*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 );		
 		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_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; }
 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_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); }
 void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( 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_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate );
 void 	GDS_SetDirty( struct GDS_Device* Device );
 void 	GDS_SetDirty( struct GDS_Device* Device );
 int 	GDS_GetWidth( 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_GetHeight( struct GDS_Device* Device );
 int 	GDS_GetDepth( struct GDS_Device* Device );
 int 	GDS_GetDepth( struct GDS_Device* Device );
 int 	GDS_GetMode( 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;
         CharStartY+= OffsetY;
 
 
         /* Do not attempt to draw if this character is entirely offscreen */
         /* 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 );
             ClipDebug( x, y );
             return;
             return;
         }
         }
 
 
         /* Do not attempt to draw past the end of the screen */
         /* 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;
         CharEndY = ( CharEndY >= Device->Height ) ? Device->Height - 1 : CharEndY;
 		Device->Dirty = true;
 		Device->Dirty = true;
 
 
@@ -146,7 +146,7 @@ int GDS_FontGetCharWidth( struct GDS_Device* Display, char Character ) {
 }
 }
 
 
 int GDS_FontGetMaxCharsPerRow( struct GDS_Device* Display ) {
 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 ) {
 int GDS_FontGetMaxCharsPerColumn( struct GDS_Device* Display ) {
@@ -210,7 +210,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
     switch ( Anchor ) {
     switch ( Anchor ) {
         case TextAnchor_East: {
         case TextAnchor_East: {
             *OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
             *OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
-            *OutX = ( Display->Width - StringWidth );
+            *OutX = ( Display->TextWidth - StringWidth );
 
 
             break;
             break;
         }
         }
@@ -221,19 +221,19 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
             break;
             break;
         }
         }
         case TextAnchor_North: {
         case TextAnchor_North: {
-            *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
+            *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
             *OutY = 0;
             *OutY = 0;
 
 
             break;
             break;
         }
         }
         case TextAnchor_South: {
         case TextAnchor_South: {
-            *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
+            *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
             *OutY = ( Display->Height - StringHeight );
             *OutY = ( Display->Height - StringHeight );
             
             
             break;
             break;
         }
         }
         case TextAnchor_NorthEast: {
         case TextAnchor_NorthEast: {
-            *OutX = ( Display->Width - StringWidth );
+            *OutX = ( Display->TextWidth - StringWidth );
             *OutY = 0;
             *OutY = 0;
 
 
             break;
             break;
@@ -246,7 +246,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
         }
         }
         case TextAnchor_SouthEast: {
         case TextAnchor_SouthEast: {
             *OutY = ( Display->Height - StringHeight );
             *OutY = ( Display->Height - StringHeight );
-            *OutX = ( Display->Width - StringWidth );
+            *OutX = ( Display->TextWidth - StringWidth );
 
 
             break;
             break;
         }
         }
@@ -258,7 +258,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
         }
         }
         case TextAnchor_Center: {
         case TextAnchor_Center: {
             *OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
             *OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
-            *OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
+            *OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
 
 
             break;
             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;
 	JpegCtx *Context = (JpegCtx*) Decoder->device;
     uint8_t *Pixels = (uint8_t*) Bitmap;
     uint8_t *Pixels = (uint8_t*) Bitmap;
 	int Shift = 8 - Context->Depth;
 	int Shift = 8 - Context->Depth;
-	
+
 	// decoded image is RGB888, shift only make sense for grayscale
 	// decoded image is RGB888, shift only make sense for grayscale
 	if (Context->Mode == GDS_RGB888) {
 	if (Context->Mode == GDS_RGB888) {
 		OUTHANDLERDIRECT(Scaler888, 0);
 		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) {
 static void* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly, int RGB_Mode) {
     JDEC Decoder;
     JDEC Decoder;
     JpegCtx Context;
     JpegCtx Context;
-	char *Scratch = calloc(SCRATCH_SIZE, 1);
+	char *Scratch = malloc(SCRATCH_SIZE);
 	
 	
     if (!Scratch) {
     if (!Scratch) {
         ESP_LOGE(TAG, "Cannot allocate workspace");
         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;
     JDEC Decoder;
     JpegCtx Context;
     JpegCtx Context;
 	bool Ret = false;
 	bool Ret = false;
-	char *Scratch = calloc(SCRATCH_SIZE, 1);
+	char *Scratch = malloc(SCRATCH_SIZE);
 	
 	
     if (!Scratch) {
     if (!Scratch) {
         ESP_LOGE(TAG, "Cannot allocate workspace");
         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;
 		const struct GDS_FontDef* Font;
 	} Lines[MAX_LINES];
 	} Lines[MAX_LINES];
 	
 	
-	uint16_t Width;
+	uint16_t Width, TextWidth;
     uint16_t Height;
     uint16_t Height;
 	uint8_t Depth, Mode;
 	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 (*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 );
 	void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
 	// may provide for tweaking
 	// 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	
 	// interface-specific methods	
     WriteCommandProc WriteCommand;
     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 );
 	Width = GDS_FontMeasureString( Device, Text );
 	
 	
 	// adjusting position, erase only EoL for rigth-justified
 	// 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
 	// erase if requested
 	if (Attr & GDS_TEXT_CLEAR) {
 	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);
 		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++)
 			for (int y = Y_min; y < Y_max; y++)
 				DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
 				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;
 	Device->Dirty = true;
 	if (Attr & GDS_TEXT_UPDATE) GDS_Update( Device );
 	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
 	// we might already fit
 	GDS_SetFont( Device, Device->Lines[N].Font );	
 	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 
 	// add some space for better visual 
 	strncat(String, Space, Max-Len);
 	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 );
 	Boundary = GDS_FontMeasureString( Device, String );
 			
 			
 	// add a full display width	
 	// 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++] = String[Extra++];
 		String[Len] = '\0';
 		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->RSTPin = RSTPin;
 	Device->Backlight.Pin = BacklightPin;	
 	Device->Backlight.Pin = BacklightPin;	
 	Device->IF = GDS_IF_I2C;
 	Device->IF = GDS_IF_I2C;
-	Device->Width = Width;
+	Device->Width = Device->TextWidth = Width;
 	Device->Height = Height;
 	Device->Height = Height;
 	
 	
 	if ( RSTPin >= 0 ) {
 	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 ) {
 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;
     spi_device_handle_t SPIDevice;
 
 
     NullCheck( Device, return false );
     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 );
 		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.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_8M;
     SPIDeviceConfig.spics_io_num = CSPin;
     SPIDeviceConfig.spics_io_num = CSPin;
     SPIDeviceConfig.queue_size = 1;
     SPIDeviceConfig.queue_size = 1;
@@ -63,7 +61,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
     Device->CSPin = CSPin;
     Device->CSPin = CSPin;
 	Device->Backlight.Pin = BackLightPin;	
 	Device->Backlight.Pin = BackLightPin;	
 	Device->IF = GDS_IF_SPI;
 	Device->IF = GDS_IF_SPI;
-	Device->Width = Width;
+	Device->Width = Device->TextWidth = Width;
 	Device->Height = Height;
 	Device->Height = Height;
 	
 	
 	if ( RSTPin >= 0 ) {
 	if ( RSTPin >= 0 ) {

+ 56 - 1
components/display/display.c

@@ -20,6 +20,7 @@
 #include "gds_draw.h"
 #include "gds_draw.h"
 #include "gds_text.h"
 #include "gds_text.h"
 #include "gds_font.h"
 #include "gds_font.h"
+#include "gds_image.h"
 
 
 static const char *TAG = "display";
 static const char *TAG = "display";
 
 
@@ -30,6 +31,9 @@ static const char *TAG = "display";
 #define SCROLLABLE_SIZE			384
 #define SCROLLABLE_SIZE			384
 #define HEADER_SIZE				64
 #define HEADER_SIZE				64
 #define	DEFAULT_SLEEP			3600
 #define	DEFAULT_SLEEP			3600
+#define ARTWORK_BORDER			1
+
+extern const uint8_t default_artwork[]   asm("_binary_note_jpg_start");
 
 
 static EXT_RAM_ATTR struct {
 static EXT_RAM_ATTR struct {
 	TaskHandle_t task;
 	TaskHandle_t task;
@@ -47,6 +51,13 @@ static EXT_RAM_ATTR struct {
 		char string[8]; // H:MM:SS
 		char string[8]; // H:MM:SS
 		bool visible;
 		bool visible;
 	} duration;
 	} duration;
+	struct {
+		bool enable, active;
+		bool fit;
+		bool updated;
+		int tick;
+		int offset;
+	}  artwork;
 	TickType_t tick;
 	TickType_t tick;
 } displayer;
 } displayer;
 
 
@@ -147,6 +158,14 @@ void display_init(char *welcome) {
 		GDS_TextSetFontAuto(display, 2, GDS_FONT_LINE_2, -3);
 		GDS_TextSetFontAuto(display, 2, GDS_FONT_LINE_2, -3);
 		
 		
 		displayer.metadata_config = config_alloc_get(NVS_TYPE_STR, "metadata_config");
 		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);
 	free(config);
@@ -225,7 +244,12 @@ static void displayer_task(void *args) {
 				// just re-write the whole line it's easier
 				// 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_LEFT, GDS_TEXT_CLEAR, displayer.header);	
 				GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line);
 				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;
 				timer_sleep = 1000;
 			} else timer_sleep = max(1000 - elapsed, 0);	
 			} else timer_sleep = max(1000 - elapsed, 0);	
 		} else timer_sleep = DEFAULT_SLEEP;
 		} 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) {
 	switch(cmd) {
 	case DISPLAYER_ACTIVATE: {	
 	case DISPLAYER_ACTIVATE: {	
 		char *header = va_arg(args, char*);
 		char *header = va_arg(args, char*);
+		displayer.artwork.active = displayer.artwork.enable && va_arg(args, int);
 		strncpy(displayer.header, header, HEADER_SIZE);
 		strncpy(displayer.header, header, HEADER_SIZE);
 		displayer.header[HEADER_SIZE] = '\0';
 		displayer.header[HEADER_SIZE] = '\0';
 		displayer.state = DISPLAYER_ACTIVE;
 		displayer.state = DISPLAYER_ACTIVE;
@@ -388,16 +439,20 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 		displayer.duration.visible = false;
 		displayer.duration.visible = false;
 		displayer.offset = displayer.boundary = 0;
 		displayer.offset = displayer.boundary = 0;
 		display_bus(&displayer, DISPLAY_BUS_TAKE);
 		display_bus(&displayer, DISPLAY_BUS_TAKE);
+		if (displayer.artwork.active) GDS_SetTextWidth(display, displayer.artwork.offset);
 		vTaskResume(displayer.task);
 		vTaskResume(displayer.task);
 		break;
 		break;
 	}	
 	}	
 	case DISPLAYER_SUSPEND:		
 	case DISPLAYER_SUSPEND:		
 		// task will display the line 2 from beginning and suspend
 		// task will display the line 2 from beginning and suspend
 		displayer.state = DISPLAYER_IDLE;
 		displayer.state = DISPLAYER_IDLE;
+		displayer_artwork(NULL);
 		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		break;		
 		break;		
 	case DISPLAYER_SHUTDOWN:
 	case DISPLAYER_SHUTDOWN:
 		// let the task self-suspend (we might be doing i2c_write)
 		// let the task self-suspend (we might be doing i2c_write)
+		GDS_SetTextWidth(display, 0);
+		displayer_artwork(NULL);
 		displayer.state = DISPLAYER_DOWN;
 		displayer.state = DISPLAYER_DOWN;
 		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		break;
 		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_scroll(char *string, int speed, int pause);
 void displayer_control(enum displayer_cmd_e cmd, ...);
 void displayer_control(enum displayer_cmd_e cmd, ...);
 void displayer_metadata(char *artist, char *album, char *title);
 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);
 void displayer_timer(enum displayer_time_e mode, int elapsed, int duration);
+bool displayer_can_artwork(void);
 char * display_get_supported_drivers(void);
 char * display_get_supported_drivers(void);

二进制
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
 	// now handle events for display
 	switch(cmd) {
 	switch(cmd) {
 	case BT_SINK_AUDIO_STARTED:
 	case BT_SINK_AUDIO_STARTED:
-		displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH");
+		displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH", false);
 		break;
 		break;
 	case BT_SINK_AUDIO_STOPPED:
 	case BT_SINK_AUDIO_STOPPED:
 		displayer_control(DISPLAYER_SUSPEND);
 		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); 	\
 	if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); 	\
 } while (0)
 } 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 {						\
 #define PARSE_PARAM_STR(S,P,C,V,I) do {						\
 	char *__p;                                              \
 	char *__p;                                              \
 	if ((__p = strstr(S, P)) && (__p = strchr(__p, C))) {	\
 	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)
 						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}   "-Wl,--undefined=GDS_DrawPixelFast")
 target_link_libraries(${COMPONENT_LIB} ${build_dir}/esp-idf/$<TARGET_PROPERTY:RECOVERY_PREFIX>/lib$<TARGET_PROPERTY:RECOVERY_PREFIX>.a 	)
 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() {
 void initialize_console() {
 	/* Minicom, screen, idf_monitor send CR when ENTER key is pressed (unused if we redirect stdin) */
 	/* 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' */
 	/* 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
 	/* Configure UART. Note that REF_TICK is used so that the baud rate remains
 	 * correct while APB frequency is changing in light sleep mode.
 	 * 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,
 			.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));
 	ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
 
 
 	/* Install UART driver for interrupt-driven reads and writes */
 	/* 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);
 				success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title);
 				free_metadata(&metadata);
 				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 {
 		} else {
 			char *dump = kd_dump(headers);
 			char *dump = kd_dump(headers);
 			LOG_INFO("Unhandled SET PARAMETER\n%s", dump);
 			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) {
 	switch(event) {
 	case RAOP_SETUP:
 	case RAOP_SETUP:
 		actrls_set(controls, false, NULL, actrls_ir_action);
 		actrls_set(controls, false, NULL, actrls_ir_action);
-		displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY");
+		displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY", true);
 		break;
 		break;
 	case RAOP_PLAY:
 	case RAOP_PLAY:
 		displayer_control(DISPLAYER_TIMER_RUN);
 		displayer_control(DISPLAYER_TIMER_RUN);
@@ -127,8 +127,14 @@ static bool cmd_handler(raop_event_t event, ...) {
 	case RAOP_METADATA: {
 	case RAOP_METADATA: {
 		char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
 		char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
 		displayer_metadata(artist, album, title);
 		displayer_metadata(artist, album, title);
+		displayer_artwork(NULL);
 		break;
 		break;
 	}	
 	}	
+	case RAOP_ARTWORK: {
+		uint8_t *data = va_arg(args, uint8_t*);
+		displayer_artwork(data);
+		break;
+	}
 	case RAOP_PROGRESS: {
 	case RAOP_PROGRESS: {
 		int elapsed = va_arg(args, int), duration = va_arg(args, int);
 		int elapsed = va_arg(args, int), duration = va_arg(args, int);
 		displayer_timer(DISPLAYER_ELAPSED, elapsed, duration);
 		displayer_timer(DISPLAYER_ELAPSED, elapsed, duration);

+ 1 - 1
components/raop/raop_sink.h

@@ -14,7 +14,7 @@
 
 
 #define RAOP_SAMPLE_RATE	44100
 #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, RAOP_TIMING, RAOP_PREV, RAOP_NEXT, RAOP_REW, RAOP_FWD, 
 				RAOP_VOLUME_UP, RAOP_VOLUME_DOWN, RAOP_RESUME, RAOP_TOGGLE } raop_event_t ;
 				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();
 	gpio_list= cJSON_CreateArray();
 	
 	
-#ifndef CONFIG_BAT_LOCKED
 	char *bat_config = config_alloc_get_default(NVS_TYPE_STR, "bat_config", NULL, 0);
 	char *bat_config = config_alloc_get_default(NVS_TYPE_STR, "bat_config", NULL, 0);
 	if (bat_config) {
 	if (bat_config) {
 		int channel = -1;
 		int channel = -1;
@@ -1121,11 +1120,6 @@ cJSON * get_gpio_list(bool refresh) {
 		}
 		}
 		free(bat_config);
 		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_GPIO_nvs_list(gpio_list);
 	gpio_list=get_SPDIF_GPIO(gpio_list,is_spdif_config_locked());
 	gpio_list=get_SPDIF_GPIO(gpio_list,is_spdif_config_locked());
 	gpio_list=get_Rotary_GPIO(gpio_list);
 	gpio_list=get_Rotary_GPIO(gpio_list);

+ 14 - 11
components/services/battery.c

@@ -35,12 +35,13 @@ static struct {
 	int count;
 	int count;
 	int cells, attenuation;
 	int cells, attenuation;
 	TimerHandle_t timer;
 	TimerHandle_t timer;
-} battery = {
-	.channel = CONFIG_BAT_CHANNEL,
+} battery = { 
+	.channel = -1,
 	.cells = 2,
 	.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) {
 	if (++battery.count == 30) {
 		battery.avg = battery.sum / battery.count;
 		battery.avg = battery.sum / battery.count;
 		battery.sum = battery.count = 0;
 		battery.sum = battery.count = 0;
+		if (battery_handler_svc) (battery_handler_svc)(battery.avg);
 		ESP_LOGI(TAG, "Voltage %.2fV", 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) {
 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) {
 	if (nvs_item) {
-#ifndef CONFIG_BAT_LOCKED		
 		PARSE_PARAM(nvs_item, "channel", '=', battery.channel);
 		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);
 		PARSE_PARAM(nvs_item, "atten", '=', battery.attenuation);
-#endif		
 		PARSE_PARAM(nvs_item, "cells", '=', battery.cells);
 		PARSE_PARAM(nvs_item, "cells", '=', battery.cells);
 		free(nvs_item);
 		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);
 		battery.timer = xTimerCreate("battery", BATTERY_TIMER / portTICK_RATE_MS, pdTRUE, NULL, battery_callback);
 		xTimerStart(battery.timer, portMAX_DELAY);
 		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 {
 	} else {
 		ESP_LOGI(TAG, "No battery");
 		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 void (*spkfault_handler_svc)(bool inserted);
 extern bool spkfault_svc(void);
 extern bool spkfault_svc(void);
 
 
+extern void (*battery_handler_svc)(float value);
 extern float battery_value_svc(void);
 extern float battery_value_svc(void);
 extern uint16_t battery_level_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) {
             switch (event.eventType) {
             case CSpotEventType::TRACK_INFO: {
             case CSpotEventType::TRACK_INFO: {
                 TrackInfo track = std::get<TrackInfo>(event.data);
                 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;
                 break;
             }
             }
             case CSpotEventType::PLAY_PAUSE: {
             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.cHandler = cmd_cb;
 	cspot.dHandler = data_cb;
 	cspot.dHandler = data_cb;
 	strncpy(cspot.name, name, sizeof(cspot.name) - 1);
 	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;
 	return &cspot;
 }
 }

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

@@ -45,5 +45,6 @@ endif()
 add_library(cspot STATIC ${SOURCES} ${PROTO_SRCS})
 add_library(cspot STATIC ${SOURCES} ${PROTO_SRCS})
 # PUBLIC to propagate includes from bell to cspot dependents
 # PUBLIC to propagate includes from bell to cspot dependents
 target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC)
 target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC)
+target_compile_definitions(bell PUBLIC PB_FIELD_32BIT)
 target_link_libraries(cspot PUBLIC ${EXTRA_LIBS})
 target_link_libraries(cspot PUBLIC ${EXTRA_LIBS})
 target_include_directories(cspot PUBLIC "include" ${GENERATED_INCLUDES} ${NANOPB_INCLUDE_DIRS})
 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_CJSON "" CACHE STRING "External cJSON library target name, optional")
 #set(BELL_EXTERNAL_TREMOR "" CACHE STRING "External tremor library target name, optional")
 #set(BELL_EXTERNAL_TREMOR "" CACHE STRING "External tremor library target name, optional")
 
 
-add_definitions(-DPB_ENABLE_MALLOC)
-
 # Include nanoPB library
 # Include nanoPB library
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/nanopb/extra)
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/nanopb/extra)
 find_package(Nanopb REQUIRED)
 find_package(Nanopb REQUIRED)
@@ -98,3 +96,4 @@ message(${NANOPB_INCLUDE_DIRS})
 # PUBLIC to propagate esp-idf includes to bell dependents
 # PUBLIC to propagate esp-idf includes to bell dependents
 target_link_libraries(bell PUBLIC ${EXTRA_LIBS})
 target_link_libraries(bell PUBLIC ${EXTRA_LIBS})
 target_include_directories(bell PUBLIC "include" ${EXTRA_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
 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);
     std::vector<uint8_t> sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message);
 
 
     // AES CTR
     // 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
     // AES ECB
     void aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data);
     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);
     std::vector<uint8_t> sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message);
 
 
     // AES CTR
     // 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
     // AES ECB
     void aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data);
     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);
 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);
 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
 #ifdef ESP_PLATFORM
 			this->xStack = NULL;
 			this->xStack = NULL;
 			this->priority = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + priority;
 			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) {
 			if (runOnPSRAM) {
 				this->xStack = (StackType_t*) heap_caps_malloc(this->stackSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
 				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
 // 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
     // needed for internal cache
     size_t off = 0;
     size_t off = 0;
@@ -99,12 +99,12 @@ void CryptoMbedTLS::aesCTRXcrypt(const std::vector<uint8_t> &key, std::vector<ui
 
 
     // Perform decrypt
     // Perform decrypt
     mbedtls_aes_crypt_ctr(&aesCtx,
     mbedtls_aes_crypt_ctr(&aesCtx,
-                          data.size(),
+                          nbytes,
                           &off,
                           &off,
                           iv.data(),
                           iv.data(),
                           streamBlock,
                           streamBlock,
-                          data.data(),
-                          data.data());
+                          buffer,
+                          buffer);
 }
 }
 
 
 void CryptoMbedTLS::aesECBdecrypt(const std::vector<uint8_t> &key, std::vector<uint8_t> &data)
 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
     // Mbedtls's decrypt only works on 16 byte blocks
     for (unsigned int x = 0; x < data.size() / 16; x++)
     for (unsigned int x = 0; x < data.size() / 16; x++)
     {
     {
-        // Perform decrypt
+        // Perform finalize
         mbedtls_aes_crypt_ecb(&aesCtx,
         mbedtls_aes_crypt_ecb(&aesCtx,
                               MBEDTLS_AES_DECRYPT,
                               MBEDTLS_AES_DECRYPT,
                               data.data() + (x * 16),
                               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
 // 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
     // Prepare AES_KEY
     auto cryptoKey = 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;
     unsigned int offsetInBlock = 0;
 
 
     CRYPTO_ctr128_encrypt(
     CRYPTO_ctr128_encrypt(
-        data.data(),
-        data.data(),
-        data.size(),
+            buffer,
+            buffer,
+        nbytes,
         &cryptoKey,
         &cryptoKey,
         iv.data(),
         iv.data(),
         ecountBuf,
         ecountBuf,

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

@@ -287,7 +287,7 @@ std::string HTTPClient::HTTPResponse::readToString() {
 		return result;
 		return result;
 	}
 	}
 	std::string result;
 	std::string result;
-	char buffer[BUF_SIZE];
+        char buffer[BUF_SIZE+1]; // make space for null-terminator
 	size_t len;
 	size_t len;
 	do {
 	do {
 		len = this->read(buffer, BUF_SIZE);
 		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;
     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) {
 std::vector<uint8_t> pbArrayToVector(pb_bytes_array_t* pbArray) {
     return std::vector<uint8_t>(pbArray->bytes, pbArray->bytes + pbArray->size);
     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:
 private:
     /**
     /**
      * @brief Calculates a correct IV by performing bignum addition.
      * @brief Calculates a correct IV by performing bignum addition.
-     * 
+     *
      * @param num Number to add to IV.
      * @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);
     std::vector<uint8_t> getIVSum(uint32_t num);
 
 
+    size_t decryptedCount = 0;
+    size_t oldStartPos;
+
 public:
 public:
     std::unique_ptr<Crypto> crypto;
     std::unique_ptr<Crypto> crypto;
     std::vector<uint8_t> decryptedData;
     std::vector<uint8_t> decryptedData;
@@ -41,14 +44,21 @@ public:
     std::unique_ptr<WrappedSemaphore> isLoadedSemaphore;
     std::unique_ptr<WrappedSemaphore> isLoadedSemaphore;
 
 
     /**
     /**
-     * @brief 
+     * @brief
      */
      */
     std::unique_ptr<WrappedSemaphore> isHeaderFileSizeLoadedSemaphore;
     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.
      * @brief AudioChunk handles all audiochunk related operations.
-     * 
+     *
      * @param seqId Sequence id of requested chunk
      * @param seqId Sequence id of requested chunk
      * @param audioKey Audio key used for decryption of audio data
      * @param audioKey Audio key used for decryption of audio data
      * @param startPosition Start position of current chunk in audio file
      * @param startPosition Start position of current chunk in audio file
@@ -59,16 +69,16 @@ public:
 
 
     /**
     /**
      * @brief Appends incoming chunked data to local cache.
      * @brief Appends incoming chunked data to local cache.
-     * 
+     *
      * @param data encrypted binary audio data.
      * @param data encrypted binary audio data.
      */
      */
     void appendData(const std::vector<uint8_t> &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
 #endif

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

@@ -9,7 +9,7 @@ class Packet
 private:
 private:
 
 
 public:
 public:
-    Packet(uint8_t command, std::vector<uint8_t> &data);
+    Packet(uint8_t command, const std::vector<uint8_t> &data);
     uint8_t command;
     uint8_t command;
     std::vector<uint8_t> data;
     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 stream(std::vector<uint8_t> &buf);  /* stream cipher */
     void maconly(std::vector<uint8_t> &buf); /* accumulate MAC */
     void maconly(std::vector<uint8_t> &buf); /* accumulate MAC */
     void encrypt(std::vector<uint8_t> &buf); /* encrypt + 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 */
     void finish(std::vector<uint8_t> &buf);  /* finalise MAC */
 
 
 private:
 private:

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

@@ -37,6 +37,7 @@ class SpircController {
 private:
 private:
     std::shared_ptr<MercuryManager> manager;
     std::shared_ptr<MercuryManager> manager;
     std::string username;
     std::string username;
+    bool firstFrame = true;
     std::unique_ptr<Player> player;
     std::unique_ptr<Player> player;
     std::unique_ptr<PlayerState> state;
     std::unique_ptr<PlayerState> state;
     std::shared_ptr<AudioSink> audioSink;
     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(SystemInfo, SystemInfo, 2)
 
 
 
 
-PB_BIND(LoginCredentials, LoginCredentials, AUTO)
+PB_BIND(LoginCredentials, LoginCredentials, 2)
 
 
 
 
 PB_BIND(ClientResponseEncrypted, ClientResponseEncrypted, 2)
 PB_BIND(ClientResponseEncrypted, ClientResponseEncrypted, 2)

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

@@ -58,23 +58,25 @@ typedef enum _AuthenticationType {
 } AuthenticationType;
 } AuthenticationType;
 
 
 /* Struct definitions */
 /* Struct definitions */
+typedef PB_BYTES_ARRAY_T(512) LoginCredentials_auth_data_t;
 typedef struct _LoginCredentials { 
 typedef struct _LoginCredentials { 
-    char *username; 
+    char username[30]; 
     AuthenticationType typ; 
     AuthenticationType typ; 
-    pb_bytes_array_t *auth_data; 
+    LoginCredentials_auth_data_t auth_data; 
 } LoginCredentials;
 } LoginCredentials;
 
 
 typedef struct _SystemInfo { 
 typedef struct _SystemInfo { 
     CpuFamily cpu_family; 
     CpuFamily cpu_family; 
     Os os; 
     Os os; 
-    char *system_information_string; 
-    char *device_id; 
+    char system_information_string[16]; 
+    char device_id[50]; 
 } SystemInfo;
 } SystemInfo;
 
 
 typedef struct _ClientResponseEncrypted { 
 typedef struct _ClientResponseEncrypted { 
     LoginCredentials login_credentials; 
     LoginCredentials login_credentials; 
     SystemInfo system_info; 
     SystemInfo system_info; 
-    char *version_string; 
+    bool has_version_string;
+    char version_string[32]; 
 } ClientResponseEncrypted;
 } ClientResponseEncrypted;
 
 
 
 
@@ -97,12 +99,12 @@ extern "C" {
 #endif
 #endif
 
 
 /* Initializer values for message structs */
 /* 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) */
 /* Field tags (for use in manual encoding/decoding) */
 #define LoginCredentials_username_tag            10
 #define LoginCredentials_username_tag            10
@@ -120,22 +122,22 @@ extern "C" {
 #define SystemInfo_FIELDLIST(X, a) \
 #define SystemInfo_FIELDLIST(X, a) \
 X(a, STATIC,   REQUIRED, UENUM,    cpu_family,       10) \
 X(a, STATIC,   REQUIRED, UENUM,    cpu_family,       10) \
 X(a, STATIC,   REQUIRED, UENUM,    os,               60) \
 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_CALLBACK NULL
 #define SystemInfo_DEFAULT NULL
 #define SystemInfo_DEFAULT NULL
 
 
 #define LoginCredentials_FIELDLIST(X, a) \
 #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, 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_CALLBACK NULL
 #define LoginCredentials_DEFAULT NULL
 #define LoginCredentials_DEFAULT NULL
 
 
 #define ClientResponseEncrypted_FIELDLIST(X, a) \
 #define ClientResponseEncrypted_FIELDLIST(X, a) \
 X(a, STATIC,   REQUIRED, MESSAGE,  login_credentials,  10) \
 X(a, STATIC,   REQUIRED, MESSAGE,  login_credentials,  10) \
 X(a, STATIC,   REQUIRED, MESSAGE,  system_info,      50) \
 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_CALLBACK NULL
 #define ClientResponseEncrypted_DEFAULT NULL
 #define ClientResponseEncrypted_DEFAULT NULL
 #define ClientResponseEncrypted_login_credentials_MSGTYPE LoginCredentials
 #define ClientResponseEncrypted_login_credentials_MSGTYPE LoginCredentials
@@ -151,9 +153,9 @@ extern const pb_msgdesc_t ClientResponseEncrypted_msg;
 #define ClientResponseEncrypted_fields &ClientResponseEncrypted_msg
 #define ClientResponseEncrypted_fields &ClientResponseEncrypted_msg
 
 
 /* Maximum encoded size of messages (where known) */
 /* 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
 #ifdef __cplusplus
 } /* extern "C" */
 } /* extern "C" */

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

@@ -48,14 +48,14 @@ enum AuthenticationType {
 message SystemInfo {
 message SystemInfo {
     required CpuFamily cpu_family = 0xa; 
     required CpuFamily cpu_family = 0xa; 
     required Os os = 0x3c;
     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 {
 message LoginCredentials {
-    optional string username = 0xa; 
+    required string username = 0xa;
     required AuthenticationType typ = 0x14; 
     required AuthenticationType typ = 0x14; 
-    optional bytes auth_data = 0x1e; 
+    required bytes auth_data = 0x1e;
 }
 }
 
 
 message ClientResponseEncrypted {
 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 */
 /* Struct definitions */
 typedef struct _Header { 
 typedef struct _Header { 
-    char *uri; 
-    char *method; 
+    bool has_uri;
+    char uri[64]; 
+    bool has_method;
+    char method[32]; 
 } Header;
 } Header;
 
 
 
 
@@ -21,8 +23,8 @@ extern "C" {
 #endif
 #endif
 
 
 /* Initializer values for message structs */
 /* 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) */
 /* Field tags (for use in manual encoding/decoding) */
 #define Header_uri_tag                           1
 #define Header_uri_tag                           1
@@ -30,8 +32,8 @@ extern "C" {
 
 
 /* Struct field encoding specification for nanopb */
 /* Struct field encoding specification for nanopb */
 #define Header_FIELDLIST(X, a) \
 #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_CALLBACK NULL
 #define Header_DEFAULT NULL
 #define Header_DEFAULT NULL
 
 
@@ -41,7 +43,7 @@ extern const pb_msgdesc_t Header_msg;
 #define Header_fields &Header_msg
 #define Header_fields &Header_msg
 
 
 /* Maximum encoded size of messages (where known) */
 /* Maximum encoded size of messages (where known) */
-/* Header_size depends on runtime parameters */
+#define Header_size                              98
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 } /* extern "C" */
 } /* 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.name type:FT_POINTER
 DeviceState.capabilities max_count:17, fixed_count:false
 DeviceState.capabilities max_count:17, fixed_count:false
 State.context_uri type:FT_POINTER
 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.gid type:FT_POINTER
 TrackRef.uri type:FT_POINTER
 TrackRef.uri type:FT_POINTER
 TrackRef.context 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(TrackRef, TrackRef, AUTO)
 
 
 
 
-PB_BIND(State, State, 4)
+PB_BIND(State, State, AUTO)
 
 
 
 
 PB_BIND(Capability, Capability, 2)
 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());
     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->startPosition = this->endPosition - this->decryptedData.size();
     this->isLoaded = true;
     this->isLoaded = true;
 }
 }

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

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

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

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

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

@@ -31,6 +31,7 @@ void ChunkedByteStream::fetchFileInformation() {
     endChunk->keepInMemory = true;
     endChunk->keepInMemory = true;
 
 
     chunks.push_back(endChunk);
     chunks.push_back(endChunk);
+    requestChunk(0);
 }
 }
 
 
 std::shared_ptr<AudioChunk> ChunkedByteStream::getChunkForPosition(size_t position) {
 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);
     std::scoped_lock lock(this->readMutex);
     auto chunk = getChunkForPosition(pos);
     auto chunk = getChunkForPosition(pos);
     uint16_t chunkIndex = this->pos / AUDIO_CHUNK_SIZE;
     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;
         pos += read;
 
 
         auto nextChunkPos = ((chunkIndex + 1) * AUDIO_CHUNK_SIZE) + 1;
         auto nextChunkPos = ((chunkIndex + 1) * AUDIO_CHUNK_SIZE) + 1;
-        if (loadAheadEnabled && nextChunkPos + AUDIO_CHUNK_SIZE < fileSize) {
+        if (loadAheadEnabled && nextChunkPos  < fileSize) {
             auto nextChunk = getChunkForPosition(nextChunkPos);
             auto nextChunk = getChunkForPosition(nextChunkPos);
 
 
             if (nextChunk == nullptr) {
             if (nextChunk == nullptr) {
@@ -109,15 +113,12 @@ size_t ChunkedByteStream::attemptRead(uint8_t *buffer, size_t bytes, std::shared
         toRead = chunk->decryptedData.size() - offset;
         toRead = chunk->decryptedData.size() - offset;
     }
     }
 
 
-    // Copy data
-    memcpy(buffer, chunk->decryptedData.data() + offset, toRead);
-
+    chunk->readData(buffer, offset, toRead);
     return toRead;
     return toRead;
 }
 }
 
 
 void ChunkedByteStream::seek(size_t nbytes) {
 void ChunkedByteStream::seek(size_t nbytes) {
     std::scoped_lock lock(this->readMutex);
     std::scoped_lock lock(this->readMutex);
-    BELL_LOG(info, "cspot", "seeking to %d", nbytes);
     pos = 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);
     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;
     return encrypted;
 }
 }

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

@@ -9,9 +9,9 @@ std::map<MercuryType, std::string> MercuryTypeMap({
     {MercuryType::UNSUB, "UNSUB"},
     {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->timeProvider = std::make_shared<TimeProvider>();
     this->callbacks = std::map<uint64_t, mercuryCallback>();
     this->callbacks = std::map<uint64_t, mercuryCallback>();
     this->subscriptions = std::map<std::string, 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
     // Construct mercury header
 
 
     CSPOT_LOG(debug, "executing MercuryType %s", MercuryTypeMap[method].c_str());
     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
     // GET and SEND are actually the same. Therefore the override
     // The difference between them is only in header's method
     // 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)
 MercuryResponse::MercuryResponse(std::vector<uint8_t> &data)
 {
 {
     // this->mercuryHeader = std::make_unique<Header>();
     // this->mercuryHeader = std::make_unique<Header>();
-    this->mercuryHeader = Header_init_default;
+    this->mercuryHeader = {};
     this->parts = mercuryParts(0);
     this->parts = mercuryParts(0);
     this->parseResponse(data);
     this->parseResponse(data);
 }
 }
 
 
 MercuryResponse::~MercuryResponse() {
 MercuryResponse::~MercuryResponse() {
-    pb_release(Header_fields, &mercuryHeader);
 }
 }
 
 
 void MercuryResponse::parseResponse(std::vector<uint8_t> &data)
 void MercuryResponse::parseResponse(std::vector<uint8_t> &data)

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

@@ -1,6 +1,6 @@
 #include "Packet.h"
 #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->command = command;
     this->data = data;
     this->data = data;
 };
 };

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

@@ -3,7 +3,7 @@
 
 
 // #include <valgrind/memcheck.h>
 // #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->audioSink = audioSink;
     this->manager = manager;
     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.repeat = false;
     innerFrame.state.has_repeat = true;
     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.is_active = false;
     innerFrame.device_state.has_is_active = true;
     innerFrame.device_state.has_is_active = true;
-    
+
     innerFrame.device_state.can_play = true;
     innerFrame.device_state.can_play = true;
     innerFrame.device_state.has_can_play = true;
     innerFrame.device_state.has_can_play = true;
 
 
     innerFrame.device_state.volume = configMan->volume;
     innerFrame.device_state.volume = configMan->volume;
     innerFrame.device_state.has_volume = true;
     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
     // Prepare player's capabilities
     addCapability(CapabilityType_kCanBePlayer, 1);
     addCapability(CapabilityType_kCanBePlayer, 1);
@@ -53,33 +53,32 @@ PlayerState::PlayerState(std::shared_ptr<TimeProvider> timeProvider)
 }
 }
 
 
 PlayerState::~PlayerState() {
 PlayerState::~PlayerState() {
+    pb_release(Frame_fields, &innerFrame);
     pb_release(Frame_fields, &remoteFrame);
     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)
 void PlayerState::setPlaybackState(const PlaybackState state)
 {
 {
     switch (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()
 void PlayerState::updateTracks()
 {
 {
     CSPOT_LOG(info, "---- Track count %d", remoteFrame.state.track_count);
     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.track_count = remoteFrame.state.track_count;
     innerFrame.state.has_playing_track_index = true;
     innerFrame.state.has_playing_track_index = true;
     innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index;
     innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index;
@@ -167,17 +166,13 @@ void PlayerState::setShuffle(bool shuffle)
     if (shuffle)
     if (shuffle)
     {
     {
         // Put current song at the begining
         // 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
         // Shuffle current tracks
         for (int x = 1; x < innerFrame.state.track_count - 1; x++)
         for (int x = 1; x < innerFrame.state.track_count - 1; x++)
         {
         {
             auto j = x + (std::rand() % (innerFrame.state.track_count - 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;
         innerFrame.state.playing_track_index = 0;
     }
     }
@@ -196,11 +191,14 @@ std::shared_ptr<TrackReference> PlayerState::getCurrentTrack()
 
 
 std::vector<uint8_t> PlayerState::encodeCurrentFrame(MessageType typ)
 std::vector<uint8_t> PlayerState::encodeCurrentFrame(MessageType typ)
 {
 {
+    free(innerFrame.ident);
+    free(innerFrame.protocol_version);
+
     // Prepare current frame info
     // Prepare current frame info
     innerFrame.version = 1;
     innerFrame.version = 1;
-    innerFrame.ident = (char *) deviceId;
+    innerFrame.ident = strdup(deviceId);
     innerFrame.seq_nr = this->seqNum;
     innerFrame.seq_nr = this->seqNum;
-    innerFrame.protocol_version = (char*) protocolVersion;
+    innerFrame.protocol_version = strdup(protocolVersion);
     innerFrame.typ = typ;
     innerFrame.typ = typ;
     innerFrame.state_update_id = timeProvider->getSyncedTimestamp();
     innerFrame.state_update_id = timeProvider->getSyncedTimestamp();
     innerFrame.has_version = true;
     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++)
     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->innerFrame.device_state.capabilities[capabilityIndex].stringValue_count = stringValue.size();
     this->capabilityIndex += 1;
     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;
     authBlob = blob;
 
 
     // prepare authentication request proto
     // 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.login_credentials.typ = (AuthenticationType) blob->authType;
     authRequest.system_info.cpu_family = CpuFamily_CPU_UNKNOWN;
     authRequest.system_info.cpu_family = CpuFamily_CPU_UNKNOWN;
     authRequest.system_info.os = Os_OS_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);
     auto data = pbEncode(ClientResponseEncrypted_fields, &authRequest);
-    free(authRequest.login_credentials.auth_data);
 
 
     // Send login request
     // Send login request
     this->shanConn->sendPacket(LOGIN_REQUEST_COMMAND, data);
     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);
     state->setActive(false);
     notify();
     notify();
     // Send the event at the end at it might be a last gasp
     // Send the event at the end at it might be a last gasp
-    sendEvent(CSpotEventType::DISC);    
+    sendEvent(CSpotEventType::DISC);
 }
 }
 
 
 void SpircController::playToggle() {
 void SpircController::playToggle() {
@@ -103,7 +103,7 @@ void SpircController::prevSong() {
 }
 }
 
 
 void SpircController::handleFrame(std::vector<uint8_t> &data) {
 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);
     pbDecode(state->remoteFrame, Frame_fields, data);
 
 
     switch (state->remoteFrame.typ) {
     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->manager = manager;
     this->fileId = std::vector<uint8_t>();
     this->fileId = std::vector<uint8_t>();
-    episodeInfo = Episode_init_default;
-    trackInfo = Track_init_default;
+    episodeInfo = {};
+    trackInfo = {};
 
 
     mercuryCallback trackResponseLambda = [=](std::unique_ptr<MercuryResponse> res) {
     mercuryCallback trackResponseLambda = [=](std::unique_ptr<MercuryResponse> res) {
         this->trackInformationCallback(std::move(res), position_ms, isPaused);
         this->trackInformationCallback(std::move(res), position_ms, isPaused);
@@ -84,13 +84,15 @@ void SpotifyTrack::trackInformationCallback(std::unique_ptr<MercuryResponse> res
     int altIndex = 0;
     int altIndex = 0;
     while (!canPlayTrack())
     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);
         CSPOT_LOG(info, "Trying alternative %d", altIndex);
     }
     }
+
     auto trackId = pbArrayToVector(trackInfo.gid);
     auto trackId = pbArrayToVector(trackInfo.gid);
     this->fileId = std::vector<uint8_t>();
     this->fileId = std::vector<uint8_t>();
 
 

+ 20 - 1
components/spotify/cspot_sink.c

@@ -13,6 +13,7 @@
 #include "display.h"
 #include "display.h"
 #include "accessors.h"
 #include "accessors.h"
 #include "network_services.h"
 #include "network_services.h"
+#include "tools.h"
 #include "cspot_private.h"
 #include "cspot_private.h"
 #include "cspot_sink.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
 	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
  * Command handler
  */
  */
@@ -106,7 +120,7 @@ static bool cmd_handler(cspot_event_t event, ...) {
 	switch(event) {
 	switch(event) {
 	case CSPOT_SETUP:
 	case CSPOT_SETUP:
 		actrls_set(controls, false, NULL, actrls_ir_action);
 		actrls_set(controls, false, NULL, actrls_ir_action);
-		displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY");
+		displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY", true);
 		break;
 		break;
 	case CSPOT_PLAY:
 	case CSPOT_PLAY:
 		displayer_control(DISPLAYER_TIMER_RUN);
 		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);
 		uint32_t sample_rate = va_arg(args, uint32_t);
 		int duration = va_arg(args, int);
 		int duration = va_arg(args, int);
 		char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
 		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_metadata(artist, album, title);
 		displayer_timer(DISPLAYER_ELAPSED, loaded ? -1 : 0, duration);
 		displayer_timer(DISPLAYER_ELAPSED, loaded ? -1 : 0, duration);
 		loaded = false;
 		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 
     -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})
 add_definitions(-DLINKALL -DLOOPBACK -DNO_FAAD -DEMBEDDED -DTREMOR_ONLY -DCUSTOM_VERSION=${BUILD_NUMBER})
 
 
 if (${DEPTH} EQUAL "32")
 if (${DEPTH} EQUAL "32")

+ 3 - 5
components/squeezelite/equalizer.c

@@ -89,16 +89,14 @@ void equalizer_close(void) {
  */
  */
 void equalizer_update(s8_t *gain) {
 void equalizer_update(s8_t *gain) {
 	char config[EQ_BANDS * 4 + 1] = { };
 	char config[EQ_BANDS * 4 + 1] = { };
-	char *p = config;
+	int n = 0;
 	
 	
 	for (int i = 0; i < EQ_BANDS; i++) {
 	for (int i = 0; i < EQ_BANDS; i++) {
 		equalizer.gain[i] = gain[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);					
 	config_set_value(NVS_TYPE_STR, "equalizer", config);					
 	equalizer.update = true;
 	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 STATS_PERIOD_MS 5000
 #define STAT_STACK_SIZE	(3*1024)
 #define STAT_STACK_SIZE	(3*1024)
 
 
+#ifndef CONFIG_AMP_GPIO_LEVEL
+#define CONFIG_AMP_GPIO_LEVEL 1
+#endif
+
 extern struct outputstate output;
 extern struct outputstate output;
 extern struct buffer *streambuf;
 extern struct buffer *streambuf;
 extern struct buffer *outputbuf;
 extern struct buffer *outputbuf;
@@ -101,7 +105,7 @@ static TaskHandle_t stats_task, output_i2s_task;
 static bool stats;
 static bool stats;
 static struct {
 static struct {
 	int gpio, active;
 	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 };
   mute_control = { CONFIG_MUTE_GPIO, CONFIG_MUTE_GPIO_LEVEL };
 
 
 DECLARE_ALL_MIN_MAX;
 DECLARE_ALL_MIN_MAX;
@@ -171,20 +175,16 @@ static void jack_handler(bool inserted) {
 /****************************************************************************************
 /****************************************************************************************
  * amp GPIO
  * amp GPIO
  */
  */
+#ifndef AMP_GPIO_LOCKED 
 static void set_amp_gpio(int gpio, char *value) {
 static void set_amp_gpio(int gpio, char *value) {
 	char *p;
 	char *p;
 	
 	
 	if (strcasestr(value, "amp")) {
 	if (strcasestr(value, "amp")) {
 		amp_control.gpio = gpio;
 		amp_control.gpio = gpio;
 		if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1);
 		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
  * 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
 	// common I2S initialization
 	i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
 	i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
 	i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
 	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
 	// in case of overflow, do not replay old buffer
 	i2s_config.tx_desc_auto_clear = true;		
 	i2s_config.tx_desc_auto_clear = true;		
 	i2s_config.use_apll = 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_chain = jack_handler_svc;
 	jack_handler_svc = jack_handler;
 	jack_handler_svc = jack_handler;
 	
 	
+#ifndef AMP_GPIO_LOCKED	
 	parse_set_GPIO(set_amp_gpio);
 	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);
 	if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
 	else adac->speaker(true);
 	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
 	// 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, 
  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
  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;
 	register u16_t hi, lo, aux;
 	size_t cnt = *count;
 	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
 idf_component_register( SRCS operator.cpp tools.c trace.c
 						REQUIRES _override esp_common pthread 
 						REQUIRES _override esp_common pthread 
+						PRIV_REQUIRES esp_http_client esp-tls
 						INCLUDE_DIRS .
 						INCLUDE_DIRS .
 )
 )
 
 

+ 103 - 1
components/tools/tools.c

@@ -11,9 +11,14 @@
 #include <stdint.h>
 #include <stdint.h>
 #include <string.h>
 #include <string.h>
 #include <ctype.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_heap_caps.h"
 #include "esp_log.h"
 #include "esp_log.h"
+#include "tools.h"
 
 
 const static char TAG[] = "tools";
 const static char TAG[] = "tools";
 
 
@@ -171,3 +176,100 @@ char * strdup_psram(const char * source){
 	}
 	}
 	return ptr;
 	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_unknown(const char * str);
 const char* str_or_null(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[];
 extern const char unknown_string_placeholder[];
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

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

@@ -440,6 +440,7 @@
 							GPL
 							GPL
 							License.</li>
 							License.</li>
 						<li>tarablessd1306, &copy; 2017-2018, Tara Keeling. Licensed under the MIT 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>
 					</ul>
 				</div>
 				</div>
 			</div>
 			</div>

+ 1 - 1
main/CMakeLists.txt

@@ -1,5 +1,5 @@
 idf_component_register(SRC_DIRS . 
 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
                     	EMBED_FILES ../server_certs/github.pem
 						LDFRAGMENTS "linker.lf"
 						LDFRAGMENTS "linker.lf"
                     	)
                     	)

+ 81 - 32
main/Kconfig.projbuild

@@ -26,6 +26,8 @@ menu "Squeezelite-ESP32"
 			help
 			help
 				Set logging level info|debug|sdebug
 				Set logging level info|debug|sdebug
 	endmenu
 	endmenu
+	config AMP_LOCKED
+		bool
 	config JACK_LOCKED
 	config JACK_LOCKED
 		bool
 		bool
 	config BAT_LOCKED
 	config BAT_LOCKED
@@ -41,6 +43,9 @@ menu "Squeezelite-ESP32"
 	config MUTE_GPIO_LEVEL
 	config MUTE_GPIO_LEVEL
 		int
 		int
 		default 0
 		default 0
+	config WELL_KNOWN
+		bool
+		default n
 	menu "Target"
 	menu "Target"
 		choice OUTPUT_TYPE
 		choice OUTPUT_TYPE
 			prompt "Main system"
 			prompt "Main system"
@@ -54,12 +59,21 @@ menu "Squeezelite-ESP32"
 				select I2C_LOCKED
 				select I2C_LOCKED
 				select LED_LOCKED
 				select LED_LOCKED
 				select SPKFAULT_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
 				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
 		config RELEASE_API
 			string "Software update URL"
 			string "Software update URL"
 			default "https://api.github.com/repos/sle118/squeezelite-esp32/releases"
 			default "https://api.github.com/repos/sle118/squeezelite-esp32/releases"
@@ -76,11 +90,13 @@ menu "Squeezelite-ESP32"
 			string
 			string
 			default "SqueezeAMP" if SQUEEZEAMP
 			default "SqueezeAMP" if SQUEEZEAMP
 			default "Squeezelite-TWATCH" if TWATCH2020
 			default "Squeezelite-TWATCH" if TWATCH2020
+			default "Muse" if MUSE
 			default "Squeezelite-ESP32"
 			default "Squeezelite-ESP32"
 		config FW_PLATFORM_NAME
 		config FW_PLATFORM_NAME
 			string
 			string
 			default "SqueezeAmp" if SQUEEZEAMP
 			default "SqueezeAmp" if SQUEEZEAMP
 			default "TWATCH" if TWATCH2020
 			default "TWATCH" if TWATCH2020
+			default "Muse" if MUSE
 			default "ESP32"
 			default "ESP32"
 		# AGGREGATES - begin
 		# AGGREGATES - begin
 		# these parameters are "aggregates"	that take precedence. They must have a default value
 		# these parameters are "aggregates"	that take precedence. They must have a default value
@@ -88,6 +104,7 @@ menu "Squeezelite-ESP32"
 			string
 			string
 			default "model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0" if SQUEEZEAMP
 			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=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 ""
 			default ""
 		config SPDIF_CONFIG
 		config SPDIF_CONFIG
 			string
 			string
@@ -98,7 +115,8 @@ menu "Squeezelite-ESP32"
 			default	""
 			default	""
 		config SPI_CONFIG
 		config SPI_CONFIG
 			string
 			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	""
 			default	""
 		config DISPLAY_CONFIG
 		config DISPLAY_CONFIG
 			string
 			string
@@ -107,17 +125,28 @@ menu "Squeezelite-ESP32"
 		config ETH_CONFIG
 		config ETH_CONFIG
 			string
 			string
 			default ""
 			default ""
+		# AGGREGATES - end							
 		config DAC_CONTROLSET
 		config DAC_CONTROLSET
 			string
 			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 ""
 			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
 			int
-			default 34 if SQUEEZEAMP
+			default 21 if MUSE
 			default -1
 			default -1
+		config JACK_GPIO
+			int
+			default 34 if SQUEEZEAMP || MUSE
+			default -1			
 		config SPKFAULT_GPIO
 		config SPKFAULT_GPIO
 			int
 			int
 			default 2 if SQUEEZEAMP
 			default 2 if SQUEEZEAMP
@@ -129,6 +158,7 @@ menu "Squeezelite-ESP32"
 		config LED_GREEN_GPIO
 		config LED_GREEN_GPIO
 			int 
 			int 
 			default 12 if SQUEEZEAMP
 			default 12 if SQUEEZEAMP
+			default 22 if MUSE
 			default -1
 			default -1
 		config LED_RED_GPIO
 		config LED_RED_GPIO
 			int
 			int
@@ -274,6 +304,14 @@ menu "Squeezelite-ESP32"
 			help
 			help
 				Enable Spotify connect using CSpot		
 				Enable Spotify connect using CSpot		
 	endmenu
 	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"
 	menu "Display Screen"
 		depends on !TWATCH2020
 		depends on !TWATCH2020
@@ -320,8 +358,9 @@ menu "Squeezelite-ESP32"
 				Set parameters of GPIO extender
 				Set parameters of GPIO extender
 				model=<model>[,addr=<addr>][,base=<100..N>][,count=<0..32>][,intr=<gpio>][,port=dac|system]				
 				model=<model>[,addr=<addr>][,base=<100..N>][,count=<0..32>][,intr=<gpio>][,port=dac|system]				
 	endmenu
 	endmenu
+	
 	menu "LED configuration"
 	menu "LED configuration"
-		visible if !SQUEEZEAMP && !TWATCH2020
+		visible if !SQUEEZEAMP && !TWATCH2020 && !MUSE
 		config LED_GREEN_GPIO
 		config LED_GREEN_GPIO
 			int "Green led GPIO"
 			int "Green led GPIO"
 			help
 			help
@@ -329,7 +368,7 @@ menu "Squeezelite-ESP32"
 		config LED_GREEN_GPIO_LEVEL
 		config LED_GREEN_GPIO_LEVEL
 			int "Green led ON level"
 			int "Green led ON level"
 			depends on LED_GREEN_GPIO != -1
 			depends on LED_GREEN_GPIO != -1
-		config LED_RED_GPIO
+		config LED_RED_GPIO				
 			int "Red led GPIO"
 			int "Red led GPIO"
 			help
 			help
 				Set to -1 for no LED
 				Set to -1 for no LED
@@ -339,9 +378,10 @@ menu "Squeezelite-ESP32"
 			default 0 if SQUEEZEAMP
 			default 0 if SQUEEZEAMP
 			default 1
 			default 1
 	endmenu
 	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"
 			int "Jack insertion GPIO"
 			help
 			help
 				GPIO to detect speaker jack insertion. Set to -1 for no detection.
 				GPIO to detect speaker jack insertion. Set to -1 for no detection.
@@ -349,10 +389,23 @@ menu "Squeezelite-ESP32"
 			depends on JACK_GPIO != -1
 			depends on JACK_GPIO != -1
 			int "Level when inserted (0/1)"
 			int "Level when inserted (0/1)"
 			default 0
 			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"
 			int "Speaker fault GPIO"
 			help
 			help
 				GPIO to detect speaker fault condition. Set to -1 for no detection.
 				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)"
 			int "Level when fault (0/1)"
 			default 0
 			default 0
 	endmenu
 	endmenu
+	
 	menu "Battery measure"
 	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
 			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	
 	endmenu	
+	
 	config DEFAULT_COMMAND_LINE
 	config DEFAULT_COMMAND_LINE
         string "Default command line to execute"
         string "Default command line to execute"
         default "squeezelite -o I2S -b 500:2000 -d all=info -C 30"
         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
 // as an exception _init function don't need include
 extern void services_init(void);
 extern void services_init(void);
 extern void	display_init(char *welcome);
 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_unknown(const char * str) { return (str?str:unknown_string_placeholder); }
 const char * str_or_null(const char * str) { return (str?str:null_string_placeholder); }
 const char * str_or_null(const char * str) { return (str?str:null_string_placeholder); }
 bool is_recovery_running;
 bool is_recovery_running;
@@ -446,6 +447,7 @@ void app_main()
 	ESP_LOGI(TAG,"Initializing display");
 	ESP_LOGI(TAG,"Initializing display");
 	display_init("SqueezeESP32");
 	display_init("SqueezeESP32");
 	MEMTRACE_PRINT_DELTA();
 	MEMTRACE_PRINT_DELTA();
+	target_init();
 	if(is_recovery_running && display){
 	if(is_recovery_running && display){
 		GDS_ClearExt(display, true);
 		GDS_ClearExt(display, true);
 		GDS_SetFont(display, &Font_line_2 );
 		GDS_SetFont(display, &Font_line_2 );

部分文件因为文件数量过多而无法显示