Pārlūkot izejas kodu

airplay artwork and CSpot leak fix (temporary)

Philippe G 3 gadi atpakaļ
vecāks
revīzija
3125a095fa
43 mainītis faili ar 371 papildinājumiem un 247 dzēšanām
  1. 1 0
      components/display/CMakeLists.txt
  2. 1 1
      components/display/SH1106.c
  3. 6 5
      components/display/core/gds.c
  4. 1 0
      components/display/core/gds.h
  5. 9 9
      components/display/core/gds_font.c
  6. 3 3
      components/display/core/gds_image.c
  7. 2 2
      components/display/core/gds_private.h
  8. 6 6
      components/display/core/gds_text.c
  9. 1 1
      components/display/core/ifaces/default_if_i2c.c
  10. 2 4
      components/display/core/ifaces/default_if_spi.c
  11. 47 1
      components/display/display.c
  12. 1 0
      components/display/display.h
  13. BIN
      components/display/note.jpg
  14. 1 1
      components/driver_bt/bt_app_sink.c
  15. 3 0
      components/raop/raop.c
  16. 7 1
      components/raop/raop_sink.c
  17. 1 1
      components/raop/raop_sink.h
  18. 2 1
      components/spotify/Shim.cpp
  19. 1 0
      components/spotify/cspot/CMakeLists.txt
  20. 1 2
      components/spotify/cspot/bell/CMakeLists.txt
  21. 4 0
      components/spotify/cspot/bell/include/NanoPBHelper.h
  22. 16 0
      components/spotify/cspot/bell/src/NanoPBHelper.cpp
  23. 1 1
      components/spotify/cspot/include/Packet.h
  24. 5 6
      components/spotify/cspot/protobuf/authentication.options
  25. 1 1
      components/spotify/cspot/protobuf/authentication.pb.c
  26. 21 19
      components/spotify/cspot/protobuf/authentication.pb.h
  27. 4 4
      components/spotify/cspot/protobuf/authentication.proto
  28. 2 2
      components/spotify/cspot/protobuf/mercury.options
  29. 9 7
      components/spotify/cspot/protobuf/mercury.pb.h
  30. 16 18
      components/spotify/cspot/protobuf/metadata.options
  31. 4 4
      components/spotify/cspot/protobuf/metadata.pb.c
  32. 87 72
      components/spotify/cspot/protobuf/metadata.pb.h
  33. 8 9
      components/spotify/cspot/protobuf/spirc.options
  34. 1 1
      components/spotify/cspot/protobuf/spirc.pb.c
  35. 17 10
      components/spotify/cspot/protobuf/spirc.pb.h
  36. 6 3
      components/spotify/cspot/src/MercuryManager.cpp
  37. 1 2
      components/spotify/cspot/src/MercuryResponse.cpp
  38. 1 1
      components/spotify/cspot/src/Packet.cpp
  39. 13 5
      components/spotify/cspot/src/PlayerState.cpp
  40. 15 6
      components/spotify/cspot/src/Session.cpp
  41. 39 34
      components/spotify/cspot/src/SpotifyTrack.cpp
  42. 3 3
      components/spotify/cspot/src/TrackReference.cpp
  43. 1 1
      components/spotify/cspot_sink.c

+ 1 - 0
components/display/CMakeLists.txt

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

+ 1 - 1
components/display/SH1106.c

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 47 - 1
components/display/display.c

@@ -20,6 +20,7 @@
 #include "gds_draw.h"
 #include "gds_text.h"
 #include "gds_font.h"
+#include "gds_image.h"
 
 static const char *TAG = "display";
 
@@ -30,6 +31,9 @@ static const char *TAG = "display";
 #define SCROLLABLE_SIZE			384
 #define HEADER_SIZE				64
 #define	DEFAULT_SLEEP			3600
+#define ARTWORK_BORDER			1
+
+extern const uint8_t default_artwork[]   asm("_binary_note_jpg_start");
 
 static EXT_RAM_ATTR struct {
 	TaskHandle_t task;
@@ -47,6 +51,12 @@ static EXT_RAM_ATTR struct {
 		char string[8]; // H:MM:SS
 		bool visible;
 	} duration;
+	struct {
+		bool enable, fit;
+		bool updated;
+		int tick;
+		int offset;
+	}  artwork;
 	TickType_t tick;
 } displayer;
 
@@ -147,6 +157,14 @@ void display_init(char *welcome) {
 		GDS_TextSetFontAuto(display, 2, GDS_FONT_LINE_2, -3);
 		
 		displayer.metadata_config = config_alloc_get(NVS_TYPE_STR, "metadata_config");
+		
+		// leave room for artwork is display is horizontal-style
+		if (strcasestr(displayer.metadata_config, "artwork")) {
+			displayer.artwork.enable = true;
+			displayer.artwork.fit = true;
+			if (height <= 64 && width > height * 2) displayer.artwork.offset = width - height - ARTWORK_BORDER;
+			PARSE_PARAM(displayer.metadata_config, "artwork", ':', displayer.artwork.fit);
+		}	
 	}
 	
 	free(config);
@@ -225,7 +243,12 @@ static void displayer_task(void *args) {
 				// just re-write the whole line it's easier
 				GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR, displayer.header);	
 				GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line);
-
+				
+				// if we have not received artwork after 5s, display a default icon
+				if (displayer.artwork.enable && !displayer.artwork.updated && tick - displayer.artwork.tick > pdMS_TO_TICKS(5000)) {
+					ESP_LOGI(TAG, "no artwork received, setting default");
+					displayer_artwork((uint8_t*) default_artwork);
+				}	
 				timer_sleep = 1000;
 			} else timer_sleep = max(1000 - elapsed, 0);	
 		} else timer_sleep = DEFAULT_SLEEP;
@@ -238,6 +261,25 @@ static void displayer_task(void *args) {
 	}
 }	
 
+/****************************************************************************************
+ * 
+ */
+void displayer_artwork(uint8_t *data) {
+	if (!displayer.artwork.enable) 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();
+	}	
+	
+}
+
 /****************************************************************************************
  * 
  */
@@ -378,6 +420,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 	switch(cmd) {
 	case DISPLAYER_ACTIVATE: {	
 		char *header = va_arg(args, char*);
+		bool artwork = va_arg(args, int);
 		strncpy(displayer.header, header, HEADER_SIZE);
 		displayer.header[HEADER_SIZE] = '\0';
 		displayer.state = DISPLAYER_ACTIVE;
@@ -388,6 +431,7 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 		displayer.duration.visible = false;
 		displayer.offset = displayer.boundary = 0;
 		display_bus(&displayer, DISPLAY_BUS_TAKE);
+		if (artwork) GDS_SetTextWidth(display, displayer.artwork.offset);
 		vTaskResume(displayer.task);
 		break;
 	}	
@@ -398,6 +442,8 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
 		break;		
 	case DISPLAYER_SHUTDOWN:
 		// let the task self-suspend (we might be doing i2c_write)
+		GDS_SetTextWidth(display, 0);
+		GDS_Clear(display, GDS_COLOR_BLACK); 
 		displayer.state = DISPLAYER_DOWN;
 		display_bus(&displayer, DISPLAY_BUS_GIVE);
 		break;

+ 1 - 0
components/display/display.h

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

BIN
components/display/note.jpg


+ 1 - 1
components/driver_bt/bt_app_sink.c

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

+ 3 - 0
components/raop/raop.c

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

+ 7 - 1
components/raop/raop_sink.c

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

+ 1 - 1
components/raop/raop_sink.h

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

+ 2 - 1
components/spotify/Shim.cpp

@@ -120,7 +120,8 @@ static void cspotTask(void *pvParameters) {
             case CSpotEventType::TRACK_INFO: {
                 TrackInfo track = std::get<TrackInfo>(event.data);
 				// duration is in chunks of 0.5 ms
-				cspot.cHandler(CSPOT_TRACK, 44100, track.duration / 2, track.artist.c_str(), track.album.c_str(), track.name.c_str());
+				cspot.cHandler(CSPOT_TRACK, 44100, track.duration / 2, track.artist.c_str(), 
+							   track.album.c_str(), track.name.c_str(), track.imageUrl.c_str());
                 break;
             }
             case CSpotEventType::PLAY_PAUSE: {

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

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

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

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

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

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

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

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

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

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

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

@@ -1,6 +1,5 @@
-LoginCredentials.username type:FT_POINTER
-LoginCredentials.auth_data type:FT_POINTER
-LoginCredentials.auth_data type:FT_POINTER
-SystemInfo.system_information_string type:FT_POINTER
-SystemInfo.device_id type:FT_POINTER
-ClientResponseEncrypted.version_string type:FT_POINTER
+LoginCredentials.username max_size:30, fixed_length:false
+LoginCredentials.auth_data max_size:512, fixed_length:false
+SystemInfo.system_information_string max_size:16, fixed_length:false
+SystemInfo.device_id max_size:50, fixed_length:false
+ClientResponseEncrypted.version_string max_size:32, fixed_length:false

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

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

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

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

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

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

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

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

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

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

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

@@ -1,18 +1,16 @@
-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 max_size: 512
+Track.gid max_size: 64
+Track.file max_count: 16
+Track.artist max_count: 8
+AudioFile.file_id max_size: 128
+Image.file_id max_size: 128
+Artist.gid max_size: 128
+Artist.name max_size: 512
+Album.name max_size: 512
+Episode.gid max_size: 64
+Episode.name max_size: 512
+ImageGroup.image max_count: 10
+Episode.audio max_count: 10
+Episode.covers max_count: 10
+Restriction.countries_allowed max_size: 32
+Restriction.countries_forbidden max_size: 32

+ 4 - 4
components/spotify/cspot/protobuf/metadata.pb.c

@@ -15,16 +15,16 @@ PB_BIND(Restriction, Restriction, AUTO)
 PB_BIND(Image, Image, AUTO)
 
 
-PB_BIND(ImageGroup, ImageGroup, AUTO)
+PB_BIND(ImageGroup, ImageGroup, 2)
 
 
-PB_BIND(Album, Album, AUTO)
+PB_BIND(Album, Album, 2)
 
 
-PB_BIND(Artist, Artist, AUTO)
+PB_BIND(Artist, Artist, 2)
 
 
-PB_BIND(Track, Track, AUTO)
+PB_BIND(Track, Track, 4)
 
 
 PB_BIND(Episode, Episode, 2)

+ 87 - 72
components/spotify/cspot/protobuf/metadata.pb.h

@@ -24,63 +24,78 @@ typedef enum _AudioFormat {
 } AudioFormat;
 
 /* Struct definitions */
+typedef PB_BYTES_ARRAY_T(128) Artist_gid_t;
 typedef struct _Artist { 
-    pb_bytes_array_t *gid; 
-    char *name; 
+    bool has_gid;
+    Artist_gid_t gid; 
+    bool has_name;
+    char name[512]; 
 } Artist;
 
+typedef PB_BYTES_ARRAY_T(128) AudioFile_file_id_t;
+typedef struct _AudioFile { 
+    bool has_file_id;
+    AudioFile_file_id_t file_id; 
+    bool has_format;
+    AudioFormat format; 
+} AudioFile;
+
+typedef PB_BYTES_ARRAY_T(128) Image_file_id_t;
 typedef struct _Image { 
-    pb_bytes_array_t *file_id; 
+    bool has_file_id;
+    Image_file_id_t file_id; 
 } Image;
 
+typedef struct _Restriction { 
+    bool has_countries_allowed;
+    char countries_allowed[32]; 
+    bool has_countries_forbidden;
+    char countries_forbidden[32]; 
+} Restriction;
+
 typedef struct _ImageGroup { 
     pb_size_t image_count;
-    struct _Image *image; 
+    Image image[10]; 
 } ImageGroup;
 
-typedef struct _Restriction { 
-    char *countries_allowed; 
-    char *countries_forbidden; 
-} Restriction;
-
 typedef struct _Album { 
     pb_callback_t gid; 
-    char *name; 
+    bool has_name;
+    char name[512]; 
     bool has_cover_group;
     ImageGroup cover_group; 
 } Album;
 
-typedef struct _AudioFile { 
-    pb_bytes_array_t *file_id; 
-    bool has_format;
-    AudioFormat format; 
-} AudioFile;
-
+typedef PB_BYTES_ARRAY_T(64) Episode_gid_t;
 typedef struct _Episode { 
-    pb_bytes_array_t *gid; 
-    char *name; 
+    bool has_gid;
+    Episode_gid_t gid; 
+    bool has_name;
+    char name[512]; 
     bool has_duration;
     int32_t duration; 
     pb_size_t audio_count;
-    struct _AudioFile *audio; 
-    struct _ImageGroup *covers; 
+    AudioFile audio[10]; 
+    bool has_covers;
+    ImageGroup covers; 
 } Episode;
 
+typedef PB_BYTES_ARRAY_T(64) Track_gid_t;
 typedef struct _Track { 
-    pb_bytes_array_t *gid; 
-    char *name; 
+    bool has_gid;
+    Track_gid_t gid; 
+    bool has_name;
+    char name[512]; 
     bool has_album;
     Album album; 
     pb_size_t artist_count;
-    struct _Artist *artist; 
+    Artist artist[8]; 
     bool has_duration;
     int32_t duration; 
-    pb_size_t restriction_count;
-    struct _Restriction *restriction; 
+    pb_callback_t restriction; 
     pb_size_t file_count;
-    struct _AudioFile *file; 
-    pb_size_t alternative_count;
-    struct _Track *alternative; 
+    AudioFile file[16]; 
+    pb_callback_t alternative; 
 } Track;
 
 
@@ -95,35 +110,35 @@ extern "C" {
 #endif
 
 /* Initializer values for message structs */
-#define AudioFile_init_default                   {NULL, false, _AudioFormat_MIN}
-#define Restriction_init_default                 {NULL, NULL}
-#define Image_init_default                       {NULL}
-#define ImageGroup_init_default                  {0, NULL}
-#define Album_init_default                       {{{NULL}, NULL}, NULL, false, ImageGroup_init_default}
-#define Artist_init_default                      {NULL, NULL}
-#define Track_init_default                       {NULL, NULL, false, Album_init_default, 0, NULL, false, 0, 0, NULL, 0, NULL, 0, NULL}
-#define Episode_init_default                     {NULL, NULL, false, 0, 0, NULL, NULL}
-#define AudioFile_init_zero                      {NULL, false, _AudioFormat_MIN}
-#define Restriction_init_zero                    {NULL, NULL}
-#define Image_init_zero                          {NULL}
-#define ImageGroup_init_zero                     {0, NULL}
-#define Album_init_zero                          {{{NULL}, NULL}, NULL, false, ImageGroup_init_zero}
-#define Artist_init_zero                         {NULL, NULL}
-#define Track_init_zero                          {NULL, NULL, false, Album_init_zero, 0, NULL, false, 0, 0, NULL, 0, NULL, 0, NULL}
-#define Episode_init_zero                        {NULL, NULL, false, 0, 0, NULL, NULL}
+#define AudioFile_init_default                   {false, {0, {0}}, false, _AudioFormat_MIN}
+#define Restriction_init_default                 {false, "", false, ""}
+#define Image_init_default                       {false, {0, {0}}}
+#define ImageGroup_init_default                  {0, {Image_init_default, Image_init_default, Image_init_default, Image_init_default, Image_init_default, Image_init_default, Image_init_default, Image_init_default, Image_init_default, Image_init_default}}
+#define Album_init_default                       {{{NULL}, NULL}, false, "", false, ImageGroup_init_default}
+#define Artist_init_default                      {false, {0, {0}}, false, ""}
+#define Track_init_default                       {false, {0, {0}}, false, "", false, Album_init_default, 0, {Artist_init_default, Artist_init_default, Artist_init_default, Artist_init_default, Artist_init_default, Artist_init_default, Artist_init_default, Artist_init_default}, false, 0, {{NULL}, NULL}, 0, {AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default}, {{NULL}, NULL}}
+#define Episode_init_default                     {false, {0, {0}}, false, "", false, 0, 0, {AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default, AudioFile_init_default}, false, ImageGroup_init_default}
+#define AudioFile_init_zero                      {false, {0, {0}}, false, _AudioFormat_MIN}
+#define Restriction_init_zero                    {false, "", false, ""}
+#define Image_init_zero                          {false, {0, {0}}}
+#define ImageGroup_init_zero                     {0, {Image_init_zero, Image_init_zero, Image_init_zero, Image_init_zero, Image_init_zero, Image_init_zero, Image_init_zero, Image_init_zero, Image_init_zero, Image_init_zero}}
+#define Album_init_zero                          {{{NULL}, NULL}, false, "", false, ImageGroup_init_zero}
+#define Artist_init_zero                         {false, {0, {0}}, false, ""}
+#define Track_init_zero                          {false, {0, {0}}, false, "", false, Album_init_zero, 0, {Artist_init_zero, Artist_init_zero, Artist_init_zero, Artist_init_zero, Artist_init_zero, Artist_init_zero, Artist_init_zero, Artist_init_zero}, false, 0, {{NULL}, NULL}, 0, {AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero}, {{NULL}, NULL}}
+#define Episode_init_zero                        {false, {0, {0}}, false, "", false, 0, 0, {AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero, AudioFile_init_zero}, false, ImageGroup_init_zero}
 
 /* Field tags (for use in manual encoding/decoding) */
 #define Artist_gid_tag                           1
 #define Artist_name_tag                          2
+#define AudioFile_file_id_tag                    1
+#define AudioFile_format_tag                     2
 #define Image_file_id_tag                        1
-#define ImageGroup_image_tag                     1
 #define Restriction_countries_allowed_tag        2
 #define Restriction_countries_forbidden_tag      3
+#define ImageGroup_image_tag                     1
 #define Album_gid_tag                            1
 #define Album_name_tag                           2
 #define Album_cover_group_tag                    17
-#define AudioFile_file_id_tag                    1
-#define AudioFile_format_tag                     2
 #define Episode_gid_tag                          1
 #define Episode_name_tag                         2
 #define Episode_duration_tag                     7
@@ -140,52 +155,52 @@ extern "C" {
 
 /* Struct field encoding specification for nanopb */
 #define AudioFile_FIELDLIST(X, a) \
-X(a, POINTER,  OPTIONAL, BYTES,    file_id,           1) \
+X(a, STATIC,   OPTIONAL, BYTES,    file_id,           1) \
 X(a, STATIC,   OPTIONAL, UENUM,    format,            2)
 #define AudioFile_CALLBACK NULL
 #define AudioFile_DEFAULT NULL
 
 #define Restriction_FIELDLIST(X, a) \
-X(a, POINTER,  OPTIONAL, STRING,   countries_allowed,   2) \
-X(a, POINTER,  OPTIONAL, STRING,   countries_forbidden,   3)
+X(a, STATIC,   OPTIONAL, STRING,   countries_allowed,   2) \
+X(a, STATIC,   OPTIONAL, STRING,   countries_forbidden,   3)
 #define Restriction_CALLBACK NULL
 #define Restriction_DEFAULT NULL
 
 #define Image_FIELDLIST(X, a) \
-X(a, POINTER,  OPTIONAL, BYTES,    file_id,           1)
+X(a, STATIC,   OPTIONAL, BYTES,    file_id,           1)
 #define Image_CALLBACK NULL
 #define Image_DEFAULT NULL
 
 #define ImageGroup_FIELDLIST(X, a) \
-X(a, POINTER,  REPEATED, MESSAGE,  image,             1)
+X(a, STATIC,   REPEATED, MESSAGE,  image,             1)
 #define ImageGroup_CALLBACK NULL
 #define ImageGroup_DEFAULT NULL
 #define ImageGroup_image_MSGTYPE Image
 
 #define Album_FIELDLIST(X, a) \
 X(a, CALLBACK, OPTIONAL, BYTES,    gid,               1) \
-X(a, POINTER,  OPTIONAL, STRING,   name,              2) \
+X(a, STATIC,   OPTIONAL, STRING,   name,              2) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  cover_group,      17)
 #define Album_CALLBACK pb_default_field_callback
 #define Album_DEFAULT NULL
 #define Album_cover_group_MSGTYPE ImageGroup
 
 #define Artist_FIELDLIST(X, a) \
-X(a, POINTER,  OPTIONAL, BYTES,    gid,               1) \
-X(a, POINTER,  OPTIONAL, STRING,   name,              2)
+X(a, STATIC,   OPTIONAL, BYTES,    gid,               1) \
+X(a, STATIC,   OPTIONAL, STRING,   name,              2)
 #define Artist_CALLBACK NULL
 #define Artist_DEFAULT NULL
 
 #define Track_FIELDLIST(X, a) \
-X(a, POINTER,  OPTIONAL, BYTES,    gid,               1) \
-X(a, POINTER,  OPTIONAL, STRING,   name,              2) \
+X(a, STATIC,   OPTIONAL, BYTES,    gid,               1) \
+X(a, STATIC,   OPTIONAL, STRING,   name,              2) \
 X(a, STATIC,   OPTIONAL, MESSAGE,  album,             3) \
-X(a, POINTER,  REPEATED, MESSAGE,  artist,            4) \
+X(a, STATIC,   REPEATED, MESSAGE,  artist,            4) \
 X(a, STATIC,   OPTIONAL, SINT32,   duration,          7) \
-X(a, POINTER,  REPEATED, MESSAGE,  restriction,      11) \
-X(a, POINTER,  REPEATED, MESSAGE,  file,             12) \
-X(a, POINTER,  REPEATED, MESSAGE,  alternative,      13)
-#define Track_CALLBACK NULL
+X(a, CALLBACK, REPEATED, MESSAGE,  restriction,      11) \
+X(a, STATIC,   REPEATED, MESSAGE,  file,             12) \
+X(a, CALLBACK, REPEATED, MESSAGE,  alternative,      13)
+#define Track_CALLBACK pb_default_field_callback
 #define Track_DEFAULT NULL
 #define Track_album_MSGTYPE Album
 #define Track_artist_MSGTYPE Artist
@@ -194,11 +209,11 @@ X(a, POINTER,  REPEATED, MESSAGE,  alternative,      13)
 #define Track_alternative_MSGTYPE Track
 
 #define Episode_FIELDLIST(X, a) \
-X(a, POINTER,  OPTIONAL, BYTES,    gid,               1) \
-X(a, POINTER,  OPTIONAL, STRING,   name,              2) \
+X(a, STATIC,   OPTIONAL, BYTES,    gid,               1) \
+X(a, STATIC,   OPTIONAL, STRING,   name,              2) \
 X(a, STATIC,   OPTIONAL, SINT32,   duration,          7) \
-X(a, POINTER,  REPEATED, MESSAGE,  audio,            12) \
-X(a, POINTER,  OPTIONAL, MESSAGE,  covers,           68)
+X(a, STATIC,   REPEATED, MESSAGE,  audio,            12) \
+X(a, STATIC,   OPTIONAL, MESSAGE,  covers,           68)
 #define Episode_CALLBACK NULL
 #define Episode_DEFAULT NULL
 #define Episode_audio_MSGTYPE AudioFile
@@ -224,14 +239,14 @@ extern const pb_msgdesc_t Episode_msg;
 #define Episode_fields &Episode_msg
 
 /* Maximum encoded size of messages (where known) */
-/* AudioFile_size depends on runtime parameters */
-/* Restriction_size depends on runtime parameters */
-/* Image_size depends on runtime parameters */
-/* ImageGroup_size depends on runtime parameters */
 /* Album_size depends on runtime parameters */
-/* Artist_size depends on runtime parameters */
 /* Track_size depends on runtime parameters */
-/* Episode_size depends on runtime parameters */
+#define Artist_size                              645
+#define AudioFile_size                           133
+#define Episode_size                             3290
+#define ImageGroup_size                          1340
+#define Image_size                               131
+#define Restriction_size                         66
 
 #ifdef __cplusplus
 } /* extern "C" */

+ 8 - 9
components/spotify/cspot/protobuf/spirc.options

@@ -1,13 +1,12 @@
-Frame.ident type:FT_POINTER
-Frame.protocol_version type:FT_POINTER
-Frame.recipient type:FT_POINTER
+Frame.ident max_size: 64, fixed_length:false
+Frame.protocol_version max_size: 64, fixed_length:false
+Frame.recipient max_count: 64, max_size: 64
 Capability.stringValue max_count:50, max_size: 50
 Capability.intValue max_count:50
-DeviceState.sw_version type:FT_POINTER
-DeviceState.name type:FT_POINTER
+DeviceState.sw_version max_size: 64, fixed_length:false
+DeviceState.name max_size: 64, fixed_length:false
 DeviceState.capabilities max_count:17, fixed_count:false
-State.context_uri type:FT_POINTER
+State.context_uri max_size: 256, fixed_length:false
 State.track max_count:100, fixed_count:false
-TrackRef.gid type:FT_POINTER
-TrackRef.uri type:FT_POINTER
-TrackRef.context type:FT_POINTER
+TrackRef.gid max_size: 128, fixed_length:false
+TrackRef.uri max_size: 256 fixed_length:false

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

@@ -6,7 +6,7 @@
 #error Regenerate this file with the current version of nanopb generator.
 #endif
 
-PB_BIND(TrackRef, TrackRef, AUTO)
+PB_BIND(TrackRef, TrackRef, 2)
 
 
 PB_BIND(State, State, 4)

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 17 - 10
components/spotify/cspot/protobuf/spirc.pb.h


+ 6 - 3
components/spotify/cspot/src/MercuryManager.cpp

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

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

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

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

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

+ 13 - 5
components/spotify/cspot/src/PlayerState.cpp

@@ -24,7 +24,8 @@ PlayerState::PlayerState(std::shared_ptr<TimeProvider> timeProvider)
     innerFrame.state.repeat = false;
     innerFrame.state.has_repeat = true;
 
-    innerFrame.device_state.sw_version = (char*) swVersion;
+    innerFrame.device_state.has_sw_version = true;
+    pbPutCharArray(swVersion, innerFrame.device_state.sw_version);
 
     innerFrame.device_state.is_active = false;
     innerFrame.device_state.has_is_active = true;
@@ -35,7 +36,8 @@ PlayerState::PlayerState(std::shared_ptr<TimeProvider> timeProvider)
     innerFrame.device_state.volume = configMan->volume;
     innerFrame.device_state.has_volume = true;
 
-    innerFrame.device_state.name = (char*) configMan->deviceName.c_str();
+    innerFrame.device_state.has_name = true;
+    pbPutString(configMan->deviceName, innerFrame.device_state.name);
 
     // Prepare player's capabilities
     addCapability(CapabilityType_kCanBePlayer, 1);
@@ -137,7 +139,8 @@ void PlayerState::updatePositionMs(uint32_t position)
 void PlayerState::updateTracks()
 {
     CSPOT_LOG(info, "---- Track count %d", remoteFrame.state.track_count);
-    innerFrame.state.context_uri = remoteFrame.state.context_uri == nullptr ? nullptr : strdup(remoteFrame.state.context_uri);
+    strcpy(innerFrame.state.context_uri, remoteFrame.state.context_uri);
+
     std::copy(std::begin(remoteFrame.state.track), std::end(remoteFrame.state.track), std::begin(innerFrame.state.track));
     innerFrame.state.track_count = remoteFrame.state.track_count;
     innerFrame.state.has_playing_track_index = true;
@@ -198,9 +201,14 @@ std::vector<uint8_t> PlayerState::encodeCurrentFrame(MessageType typ)
 {
     // Prepare current frame info
     innerFrame.version = 1;
-    innerFrame.ident = (char *) deviceId;
+
+    pbPutCharArray(deviceId, innerFrame.ident);
+    innerFrame.has_ident = true;
+
     innerFrame.seq_nr = this->seqNum;
-    innerFrame.protocol_version = (char*) protocolVersion;
+    pbPutCharArray(protocolVersion, innerFrame.protocol_version);
+    innerFrame.has_protocol_version = true;
+
     innerFrame.typ = typ;
     innerFrame.state_update_id = timeProvider->getSyncedTimestamp();
     innerFrame.has_version = true;

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

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

+ 39 - 34
components/spotify/cspot/src/SpotifyTrack.cpp

@@ -10,8 +10,8 @@ SpotifyTrack::SpotifyTrack(std::shared_ptr<MercuryManager> manager, std::shared_
 {
     this->manager = manager;
     this->fileId = std::vector<uint8_t>();
-    episodeInfo = Episode_init_default;
-    trackInfo = Track_init_default;
+    episodeInfo = {};
+    trackInfo = {};
 
     mercuryCallback trackResponseLambda = [=](std::unique_ptr<MercuryResponse> res) {
         this->trackInformationCallback(std::move(res), position_ms, isPaused);
@@ -53,18 +53,18 @@ bool SpotifyTrack::countryListContains(std::string countryList, std::string coun
 
 bool SpotifyTrack::canPlayTrack()
 {
-    for (int x = 0; x < trackInfo.restriction_count; x++)
-    {
-        if (trackInfo.restriction[x].countries_allowed != nullptr)
-        {
-            return countryListContains(std::string(trackInfo.restriction[x].countries_allowed), manager->countryCode);
-        }
-
-        if (trackInfo.restriction[x].countries_forbidden != nullptr)
-        {
-            return !countryListContains(std::string(trackInfo.restriction[x].countries_forbidden), manager->countryCode);
-        }
-    }
+//    for (int x = 0; x < trackInfo.restriction_count; x++)
+//    {
+//        if (strlen(trackInfo.restriction[x].countries_allowed) > 0)
+//        {
+//            return countryListContains(std::string(trackInfo.restriction[x].countries_allowed), manager->countryCode);
+//        }
+//
+//        if (strlen(trackInfo.restriction[x].countries_forbidden) > 0)
+//        {
+//            return !countryListContains(std::string(trackInfo.restriction[x].countries_forbidden), manager->countryCode);
+//        }
+//    }
 
     return true;
 }
@@ -77,35 +77,39 @@ void SpotifyTrack::trackInformationCallback(std::unique_ptr<MercuryResponse> res
 
     pb_release(Track_fields, &trackInfo);
     pbDecode(trackInfo, Track_fields, response->parts[0]);
-
-    CSPOT_LOG(info, "Track name: %s", trackInfo.name);
-    CSPOT_LOG(info, "Track duration: %d", trackInfo.duration);
-    CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", trackInfo.restriction_count);
-    int altIndex = 0;
-    while (!canPlayTrack())
-    {
-        trackInfo.restriction = trackInfo.alternative[altIndex].restriction;
-        trackInfo.restriction_count = trackInfo.alternative[altIndex].restriction_count;
-        trackInfo.gid = trackInfo.alternative[altIndex].gid;
-        trackInfo.file = trackInfo.alternative[altIndex].file;
-        altIndex++;
-        CSPOT_LOG(info, "Trying alternative %d", altIndex);
-    }
-    auto trackId = pbArrayToVector(trackInfo.gid);
+//
+//    CSPOT_LOG(info, "Track name: %s", trackInfo.name);
+//    CSPOT_LOG(info, "Track duration: %d", trackInfo.duration);
+//    CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", trackInfo.restriction_count);
+//    int altIndex = 0;
+//    while (!canPlayTrack())
+//    {
+//        auto src = trackInfo.alternative[altIndex].restriction;
+////        std::copy(std::begin(src), std::end(src), std::begin(trackInfo.restriction));
+////        trackInfo.restriction_count = trackInfo.alternative[altIndex].restriction_count;
+////
+////        free(trackInfo.gid);
+//        trackInfo.gid = trackInfo.alternative[altIndex].gid;
+//        altIndex++;
+//        CSPOT_LOG(info, "Trying alternative %d", altIndex);
+//    }
+
+    auto trackId = std::vector(trackInfo.gid.bytes, trackInfo.gid.bytes + trackInfo.gid.size);
     this->fileId = std::vector<uint8_t>();
 
     for (int x = 0; x < trackInfo.file_count; x++)
     {
         if (trackInfo.file[x].format == configMan->format)
         {
-            this->fileId = pbArrayToVector(trackInfo.file[x].file_id);
+            this->fileId = std::vector(trackInfo.file[x].file_id.bytes, trackInfo.file[x].file_id.bytes + trackInfo.file[x].file_id.size);
             break; // If file found stop searching
         }
     }
 
     if (trackInfoReceived != nullptr)
     {
-        auto imageId = pbArrayToVector(trackInfo.album.cover_group.image[0].file_id);
+        auto imageIdBytes = trackInfo.album.cover_group.image[0].file_id;
+        auto imageId = std::vector(imageIdBytes.bytes, imageIdBytes.bytes + imageIdBytes.size);
         TrackInfo simpleTrackInfo = {
             .name = std::string(trackInfo.name),
             .album = std::string(trackInfo.album.name),
@@ -139,14 +143,15 @@ void SpotifyTrack::episodeInformationCallback(std::unique_ptr<MercuryResponse> r
     {
         if (episodeInfo.audio[x].format == AudioFormat_OGG_VORBIS_96)
         {
-            this->fileId = pbArrayToVector(episodeInfo.audio[x].file_id);
+            this->fileId = std::vector(episodeInfo.audio[x].file_id.bytes, episodeInfo.audio[x].file_id.bytes + episodeInfo.audio[x].file_id.size);
             break; // If file found stop searching
         }
     }
 
     if (trackInfoReceived != nullptr)
     {
-        auto imageId = pbArrayToVector(episodeInfo.covers->image[0].file_id);
+        auto imageFileId = episodeInfo.covers.image[0].file_id;
+        auto imageId = std::vector(imageFileId.bytes, imageFileId.bytes + imageFileId.size);
         TrackInfo simpleTrackInfo = {
             .name = std::string(episodeInfo.name),
             .album = "",
@@ -159,7 +164,7 @@ void SpotifyTrack::episodeInformationCallback(std::unique_ptr<MercuryResponse> r
         trackInfoReceived(simpleTrackInfo);
     }
 
-    this->requestAudioKey(pbArrayToVector(episodeInfo.gid), this->fileId, episodeInfo.duration, position_ms, isPaused);
+    this->requestAudioKey(std::vector(episodeInfo.gid.bytes, episodeInfo.gid.bytes + episodeInfo.gid.size), this->fileId, episodeInfo.duration, position_ms, isPaused);
 }
 
 void SpotifyTrack::requestAudioKey(std::vector<uint8_t> fileId, std::vector<uint8_t> trackId, int32_t trackDuration, uint32_t position_ms, bool isPaused)

+ 3 - 3
components/spotify/cspot/src/TrackReference.cpp

@@ -3,11 +3,11 @@
 
 TrackReference::TrackReference(TrackRef *ref)
 {
-    if (ref->gid != nullptr)
+    if (ref->gid.size > 0)
     {
-        gid = pbArrayToVector(ref->gid);
+        gid = std::vector(ref->gid.bytes, ref->gid.bytes + ref->gid.size);
     }
-    else if (ref->uri != nullptr)
+    else if (strlen(ref->uri) > 0)
     {
         auto uri = std::string(ref->uri);
         auto idString = uri.substr(uri.find_last_of(":") + 1, uri.size());

+ 1 - 1
components/spotify/cspot_sink.c

@@ -106,7 +106,7 @@ static bool cmd_handler(cspot_event_t event, ...) {
 	switch(event) {
 	case CSPOT_SETUP:
 		actrls_set(controls, false, NULL, actrls_ir_action);
-		displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY");
+		displayer_control(DISPLAYER_ACTIVATE, "SPOTIFY", false);
 		break;
 	case CSPOT_PLAY:
 		displayer_control(DISPLAYER_TIMER_RUN);

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels