Browse Source

client scrolling

philippe44 5 years ago
parent
commit
a763c2d4f7

+ 4 - 1
components/services/display.h

@@ -23,11 +23,14 @@
 
 enum display_pos_e { DISPLAY_TOP_LEFT, DISPLAY_MIDDLE_LEFT, DISPLAY_BOTTOM_LEFT, DISPLAY_CENTER };
 
+// don't change anything there w/o changing all drivers init code
 extern struct display_s {
+	int width, height;
 	bool (*init)(char *config, char *welcome);
 	void (*on)(bool state);
 	void (*brightness)(u8_t level);
 	void (*text)(enum display_pos_e pos, int attribute, char *msg);
 	void (*update)(void);
-	void (*v_draw)(u8_t *data);
+	void (*draw)(int x1, int y1, int x2, int y2, bool by_column, u8_t *data);
+	void (*draw_cbr)(u8_t *data);
 } *display;

+ 55 - 10
components/services/driver_SSD1306.c

@@ -37,13 +37,14 @@ static const char *TAG = "display";
 // handlers
 static bool init(char *config, char *welcome);
 static void text(enum display_pos_e pos, int attribute, char *text);
-static void v_draw(u8_t *data);
+static void draw_cbr(u8_t *data);
+static void draw(int x1, int y1, int x2, int y2, bool by_column, u8_t *data);
 static void brightness(u8_t level);
 static void on(bool state);
 static void update(void);
 
 // display structure for others to use
-struct display_s SSD1306_display = { init, on, brightness, text, update, v_draw, NULL };
+struct display_s SSD1306_display = { 0, 0, init, on, brightness, text, update, draw, draw_cbr, NULL };
 
 // SSD1306 specific function
 static struct SSD1306_Device Display;
@@ -90,6 +91,8 @@ static bool init(char *config, char *welcome) {
 			SSD1306_SetHFlip( &Display, strcasestr(config, "HFlip") ? true : false);
 			SSD1306_SetVFlip( &Display, strcasestr(config, "VFlip") ? true : false);
 			SSD1306_SetFont( &Display, &Font_droid_sans_fallback_15x17 );
+			SSD1306_display.width = width;
+			SSD1306_display.height = height;
 			text(DISPLAY_CENTER, DISPLAY_CLEAR | DISPLAY_UPDATE, welcome);
 			ESP_LOGI(TAG, "Initialized I2C display %dx%d", width, height);
 			res = true;
@@ -138,9 +141,9 @@ static void text(enum display_pos_e pos, int attribute, char *text) {
 }
 
 /****************************************************************************************
- * Process graphic display data
+ * Process graphic display data from column-oriented bytes, MSbit first
  */
-static void v_draw( u8_t *data) {
+static void draw_cbr(u8_t *data) {
 #ifndef FULL_REFRESH
 	// force addressing mode by rows
 	if (AddressMode != AddressMode_Horizontal) {
@@ -149,6 +152,7 @@ static void v_draw( u8_t *data) {
 	}
 	
 	// try to minimize I2C traffic which is very slow
+	// TODO: this should move to grfe
 	int rows = (Display.Height > 32) ? 4 : Display.Height / 8;
 	for (int r = 0; r < rows; r++) {
 		uint8_t first = 0, last;	
@@ -156,12 +160,12 @@ static void v_draw( u8_t *data) {
 		
 		// row/col swap, frame buffer comparison and bit-reversing
 		for (int c = 0; c < Display.Width; c++) {
-			*iptr = BitReverseTable256[*iptr];
-			if (*iptr != *optr) {
+			u8_t byte = BitReverseTable256[*iptr];
+			if (byte != *optr) {
 				if (!first) first = c + 1;
 				last = c ;
 			}	
-			*optr++ = *iptr;
+			*optr++ = byte;
 			iptr += rows;
 		}
 		
@@ -174,11 +178,12 @@ static void v_draw( u8_t *data) {
 	}	
 #else
 	int len = (Display.Width * Display.Height) / 8;
-
+	
 	// to be verified, but this is as fast as using a pointer on data
-	for (int i = len - 1; i >= 0; i--) data[i] = BitReverseTable256[data[i]];
+	for (int i = len - 1; i >= 0; i--) Display.Framebuffer[i] = BitReverseTable256[data[i]];
 	
 	// 64 pixels display are not handled by LMS (bitmap is 32 pixels)
+	// TODO: this should move to grfe
 	if (Display.Height > 32) SSD1306_SetPageAddress( &Display, 0, 32/8-1);
 	
 	// force addressing mode by columns
@@ -187,10 +192,50 @@ static void v_draw( u8_t *data) {
 		SSD1306_SetDisplayAddressMode( &Display, AddressMode );
 	}
 	
-	SSD1306_WriteRawData(&Display, data, len);
+	SSD1306_WriteRawData(&Display, Display.Framebuffer, len);
  #endif	
 }
 
+/****************************************************************************************
+ * Process graphic display data MSBit first
+ */
+static void draw(int x1, int y1, int x2, int y2, bool by_column, u8_t *data) {
+	
+	if (y1 % 8 || y2 % 8) {
+		ESP_LOGW(TAG, "must write rows on byte boundaries (%u,%u) to (%u,%u)", x1, y1, x2, y2);
+		return;
+	}
+	
+	// default end point to display size
+	if (x2 == -1) x2 = Display.Width - 1;
+	if (y2 == -1) y2 = Display.Height - 1;
+			
+	// set addressing mode to match data
+	if (by_column && AddressMode != AddressMode_Vertical) {
+		AddressMode = AddressMode_Vertical;
+		SSD1306_SetDisplayAddressMode( &Display, AddressMode );
+		
+		// copy the window and do row/col exchange
+		for (int r = y1/8; r <=  y2/8; r++) {
+			uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r;
+			for (int c = x1; c <= x2; c++) {
+				*optr++ = *iptr;
+				iptr += (y2-y1)/8 + 1;
+			}	
+		}	
+	} else {
+		// just copy the window inside the frame buffer
+		for (int r = y1/8; r <= y2/8; r++) {
+			uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r*(x2-x1+1);
+			for (int c = x1; c <= x2; c++) *optr++ = *iptr++;
+		}	
+	}
+		
+	SSD1306_SetColumnAddress( &Display, x1, x2);
+	SSD1306_SetPageAddress( &Display, y1/8, y2/8);
+	SSD1306_WriteRawData( &Display, data, (x2-x1 + 1) * ((y2-y1)/8 + 1));
+}
+
 /****************************************************************************************
  * Brightness
  */

+ 141 - 10
components/squeezelite/display.c

@@ -21,9 +21,11 @@
 #include "squeezelite.h"
 #include "display.h"
 
+#pragma pack(push, 1)
+
 struct grfb_packet {
 	char  opcode[4];
-	u16_t  brightness;
+	s16_t  brightness;
 };
 
 struct grfe_packet {
@@ -51,13 +53,28 @@ struct grfg_packet {
 	u16_t  width;		// # of pixels of scrollable
 };
 
-#define LINELEN		40
+#pragma pack(pop)
 
-static log_level loglevel = lINFO;
+static struct scroller_s {
+	TaskHandle_t task;
+	bool active;
+	u8_t  screen, direction;	
+	u32_t pause, speed;		
+	u16_t by, mode, width, scroll_width;		
+	u16_t max, size;
+	u8_t *scroll_frame, *back_frame;
+} scroller;
 
+#define SCROLL_STACK_SIZE	2048
+#define LINELEN				40
+
+static log_level loglevel = lINFO;
+static SemaphoreHandle_t display_sem;
 static bool (*slimp_handler_chain)(u8_t *data, int len);
 static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport);
 
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+
 static void server(in_addr_t ip, u16_t hport, u16_t cport);
 static bool handler(u8_t *data, int len);
 static void vfdc_handler( u8_t *_data, int bytes_read);
@@ -65,6 +82,7 @@ static void grfe_handler( u8_t *data, int len);
 static void grfb_handler(u8_t *data, int len);
 static void grfs_handler(u8_t *data, int len);
 static void grfg_handler(u8_t *data, int len);
+static void scroll_task(void* arg);
 
 /* scrolling undocumented information
 	grfs	
@@ -91,7 +109,19 @@ ANIM_SCREEN_2     0x08 # For scrollonce only, screen 2 was scrolling
  * 
  */
 void sb_display_init(void) {
+	static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
+	static EXT_RAM_ATTR StackType_t xStack[SCROLL_STACK_SIZE] __attribute__ ((aligned (4)));
+	
+	// create scroll management task
+	display_sem = xSemaphoreCreateMutex();
+	scroller.task = xTaskCreateStatic( (TaskFunction_t) scroll_task, "scroll_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
 	
+	// size scroller
+	scroller.max = (display->width * display->height / 8) * 10;
+	scroller.scroll_frame = malloc(scroller.max);
+	scroller.back_frame = malloc(display->width * display->height / 8);
+
+	// chain handlers
 	slimp_handler_chain = slimp_handler;
 	slimp_handler = handler;
 	
@@ -251,21 +281,26 @@ static void vfdc_handler( u8_t *_data, int bytes_read) {
  * Process graphic display data
  */
 static void grfe_handler( u8_t *data, int len) {
-	display->v_draw(data + 8);
+	scroller.active = false;
+	xSemaphoreTake(display_sem, portMAX_DELAY);
+	display->draw_cbr(data + sizeof(struct grfe_packet));
+	xSemaphoreGive(display_sem);
 }	
 
 /****************************************************************************************
  * Brightness
  */
 static void grfb_handler(u8_t *data, int len) {
-	s16_t brightness = htons(*(uint16_t*) (data + 4));
+	struct grfb_packet *pkt = (struct grfb_packet*) data;
 	
-	LOG_INFO("brightness %hx", brightness);
-	if (brightness < 0) {
+	pkt->brightness = htons(pkt->brightness);
+	
+	LOG_INFO("brightness %hu", pkt->brightness);
+	if (pkt->brightness < 0) {
 		display->on(false); 
 	} else {
 		display->on(true);
-		display->brightness(brightness);
+		display->brightness(pkt->brightness);
 	}
 }
 
@@ -273,12 +308,108 @@ static void grfb_handler(u8_t *data, int len) {
  * Scroll set
  */
 static void grfs_handler(u8_t *data, int len) {
-}	
+	struct grfs_packet *pkt = (struct grfs_packet*) data;
+	int size = len - sizeof(struct grfs_packet);
+	int offset = htons(pkt->offset);
 	
+	LOG_INFO("gfrs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu", 
+				(int) pkt->screen,
+				(int) pkt->direction,	// 1=left, 2=right
+				htonl(pkt->pause),		// in ms	
+				htonl(pkt->speed),		// in ms
+				htons(pkt->by),			// # of pixel of scroll step
+				htons(pkt->mode),			// 0=continuous, 1=once and stop, 2=once and end
+				htons(pkt->width),		// total width of animation
+				htons(pkt->offset)		// offset if multiple packets are sent
+	);
+
+	// copy scroll parameters
+	scroller.screen = pkt->screen;
+	scroller.direction = pkt->direction;
+	scroller.pause = htonl(pkt->pause);
+	scroller.speed = htonl(pkt->speed);
+	scroller.by = htons(pkt->by);
+	scroller.mode = htons(pkt->mode);
+	scroller.width = htons(pkt->width);
+
+	// copy scroll frame data
+	if (scroller.size + size < scroller.max) {
+		memcpy(scroller.scroll_frame + offset, data + sizeof(struct grfs_packet), size);
+		scroller.size = offset + size;
+		LOG_INFO("scroller size now %u", scroller.size);
+	} else {
+		LOG_INFO("scroller too larger %u/%u", scroller.size + size, scroller.max);
+	}	
+}
+
 /****************************************************************************************
- * Scroll go
+ * Scroll background frame update & go
  */
 static void grfg_handler(u8_t *data, int len) {
+	struct grfg_packet *pkt = (struct grfg_packet*) data;
+	
+	memcpy(scroller.back_frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
+	scroller.scroll_width = htons(pkt->width);
+	scroller.active = true;
+	vTaskResume(scroller.task);
+	
+	LOG_INFO("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
+}
+
+/****************************************************************************************
+ * Scroll task
+ */
+static void scroll_task(void *args) {
+	u8_t *scroll_ptr, *frame;
+	bool active = false;
+	int len = display->width * display->height / 8;
+	int scroll_len;
+
+	while (1) {
+		if (!active) vTaskSuspend(NULL);
+		
+		// restart at the beginning - I don't know what right scrolling means ...
+		scroll_ptr = scroller.scroll_frame;
+		scroll_len = len - (display->width - scroller.scroll_width) * display->height / 8;
+		frame = malloc(display->width * display->height / 8);
+						
+		// scroll required amount of columns (within the window)
+		while (scroll_ptr <= scroller.scroll_frame + scroller.size - scroller.by * display->height / 8 - len) {
+			scroll_ptr += scroller.by * display->height / 8;
+						
+			// build a combined frame
+			memcpy(frame, scroller.back_frame, len);
+			for (int i = 0; i < scroll_len; i++) frame[i] |= scroll_ptr[i];
+			
+			xSemaphoreTake(display_sem, portMAX_DELAY);
+			active = scroller.active;
+			
+			if (!active) {
+				LOG_INFO("scrolling interrupted");
+				xSemaphoreGive(display_sem);
+				break;
+			}
+			
+			display->draw_cbr(frame);		
+			xSemaphoreGive(display_sem);
+			usleep(scroller.speed * 1000);
+		}
+		
+		if (active) {
+			// build default screen
+			memcpy(frame, scroller.back_frame, len);
+			for (int i = 0; i < scroll_len; i++) frame[i] |= scroller.scroll_frame[i];
+			xSemaphoreTake(display_sem, portMAX_DELAY);
+			display->draw_cbr(frame);		
+			xSemaphoreGive(display_sem);
+		
+			// pause for required time and continue (or not)
+			usleep(scroller.pause * 1000);
+			active = (scroller.mode == 0);
+		}	
+		
+		free(frame);
+	}	
 }	
 
 

BIN
plugin/SqueezeESP32.zip


+ 1 - 4
plugin/SqueezeESP32/Graphics.pm

@@ -8,6 +8,7 @@ use Slim::Utils::Prefs;
 use Slim::Utils::Log;
 
 my $prefs = preferences('plugin.squeezeesp32');
+my $log   = logger('plugin.squeezeesp32');
 
 my $VISUALIZER_NONE = 0;
 my $width = $prefs->get('width') || 128;
@@ -61,10 +62,6 @@ sub brightnessMap {
 	return (65535, 10, 50, 100, 200);
 }
 
-sub hasScrolling  {
-	return 0;
-}
-
 =comment
 sub bytesPerColumn {
 	return 4;

+ 4 - 0
plugin/SqueezeESP32/Player.pm

@@ -34,4 +34,8 @@ sub playerSettingsFrame {
 	}
 }
 
+sub hasScrolling  {
+	return 1;
+}
+
 1;

+ 2 - 2
plugin/SqueezeESP32/install.xml

@@ -3,13 +3,13 @@
   <defaultState>enabled</defaultState>
   <email>philippe_44@outlook.com</email>
   <targetApplication>
-    <minVersion>7.9</minVersion>
+    <minVersion>*</minVersion>
     <maxVersion>*.*</maxVersion>
     <id>SlimServer</id>
   </targetApplication>
   <name>PLUGIN_SQUEEZEESP32</name>
   <description>PLUGIN_SQUEEZEESP32_DESC</description>
   <module>Plugins::SqueezeESP32::Plugin</module>
-    <version>0.6</version>
+    <version>0.7</version>
   <creator>Philippe</creator>
 </extensions>

+ 2 - 2
plugin/repo.xml

@@ -1,10 +1,10 @@
 <?xml version='1.0' standalone='yes'?>
 <extensions>
   <plugins>
-    <plugin version="0.6" name="SqueezeESP32" minTarget="7.9" maxTarget="7.*">
+    <plugin version="0.7" name="SqueezeESP32" minTarget="*" maxTarget="7.*">
       <link>https://github.com/sle118/squeezelite-esp32</link>
       <creator>Philippe</creator>
-      <sha>e43f87096bafbbf4d97637a690975266af8e03c4</sha>
+      <sha>a1d676e7a3a2d241d17a39aff05bcb8377565a76</sha>
       <email>philippe_44@outlook.com</email>
       <desc lang="EN">SqueezeESP32 additional player id (100)</desc>
       <url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>