From a763c2d4f780e226e6605edb792d9b4ce3522986 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 20 Jan 2020 00:14:10 -0800 Subject: [PATCH] client scrolling --- components/services/display.h | 5 +- components/services/driver_SSD1306.c | 65 ++++++++++-- components/squeezelite/display.c | 151 +++++++++++++++++++++++++-- plugin/SqueezeESP32.zip | Bin 4599 -> 4622 bytes plugin/SqueezeESP32/Graphics.pm | 5 +- plugin/SqueezeESP32/Player.pm | 4 + plugin/SqueezeESP32/install.xml | 4 +- plugin/repo.xml | 4 +- 8 files changed, 209 insertions(+), 29 deletions(-) diff --git a/components/services/display.h b/components/services/display.h index 138af82f..6a760a0c 100644 --- a/components/services/display.h +++ b/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; \ No newline at end of file diff --git a/components/services/driver_SSD1306.c b/components/services/driver_SSD1306.c index e7c32c45..c01a8ad0 100644 --- a/components/services/driver_SSD1306.c +++ b/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 */ diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index e578e429..ee375c6a 100644 --- a/components/squeezelite/display.c +++ b/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 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); + } } diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index 4ae1a7875541b1d4e44d44e179f725daaf73c285..d9a2145994414f3786300986e6502a08fd13d15a 100644 GIT binary patch delta 2200 zcmZuy2T+q+8vRp9=mJWSmQchjN>NanbOS;tp%{>$f(8%;6lo#IhN>8m_A6D8@<2e8 znnw>R7(tMtf*{303xWtJ9n8zR^LE*B@64Hd=id8$cjkQOe0NnLK~Wr!gTeOz0Kg60 ztv-w&=XW>>fdRm^WN}5R{1{h(FkM*(Zjz{>)06&M@GCQz7*Y#;@N+fG7ox*I6}R2% z86lQA?j+UAdvM&*eY8H`u;RiGT&3@;2dyU_dU_SlH1JIVT8_PtyvVyyhsmA)pU z`-37aK9_{O&C~%cSXSbStz`RAqFX?hQDqW|0yS^j80$^g?xHGWKJ&{rz5=Hlj~v0C zQcpQ(?=U5T0bTmh$z5{s;lS10KJ!Z1tODwvbH>%98y?Hw^hOh~|D4?0lOC#^%RUzM zx-zlUjRZ1fo0^OYH+;LViaeM7g6110o((o)8)q2Yk1RT^t$Up~UNbs(O>3T}Dd2cG z&#M!g#Ckcjzl*vj(d#|7z2c_AJxuG!W3w=1`E#x3FKOq>KQzw~{|u+TpDX8fG7uZH zs%^->M9I`jHf@dZvv2me;k_PuV15)gudIStwuINWJL^OwOmWELSbnEIWHpHWY8%+;Xxc4N! zR-eF3D%njApDHqK*ZQnc&*NC~aY64x7Mz@Yeoe1;zS}TY=0c=+fN3PJ1T|uXE)cjx z4&37AmY$ED$cfW77Pc{NfF|SOP8xY)gNskfx$ovLe5;Sm+RW_-6%5A%5W?#W%DQ$ibfw3%HmQv2-I{A%0x* zN3oDCx3)#^N!DakhT!6i_6<_J$dPQPa7-YH;!#e`Ce^RSMpK#zY9_Jo^Ue$?G?z{; zja}187}2IBJ&fNk{v}WQR;%Xc&f}Z!jawa#h%Ya7vXmdblp>TTAoh~N`0e~3}_&9#?_6rDf_wi9B`}+7M<8W>2dtz6k*m;dnw>;lM z@=!dY*Js6-+IU5# zV=*yVBHI_3q&L^+~QI)r^&pLeZdAmW0cQ$DCOE36;XTC6aq-)J{*e z8l1yQKl-N|Tge(J)ox_{2F3IsbciO|boE=_8j-?%Ukr*dCh{OB1hk9I9z2fU(KkWBbT`1g!aqbU-wLBd&#PDTVy3%4|UeY4S zM%yYsv55eVK}SCZAFiyvd!N~5=|H8fwY(3 zJ_&BoBsV3kGmHC1_%tdD>}qedeV3|@v^k3T$dhtPEMb~D0h{fGb!TTo^gPF?`6Qd^ zH7G;=N1T8&Pm=>wZ%$=UM$6z?OcJclzr50n@xWl-uqDtuQi=3ml$~8=}Zbgy4e3&&f}-4tK1_>p50s} zPEIa%A+-#$IOk0GselS{h~4BIOhqSMPUr@=?`U51{<#~-eK37KMIZZ_LnKGx{3&) z!TVk$kY*)N{Vu%5Z@9{H`*cT5?I`eVHt=5s;!9^%oNazInI5R(bR-(+!){U9!?C;}NUu%j$qUbz|hsYT6SGOB{qJ-up9-9U z1b|Y4?$A6KSP4Uc*@8CEk$*33;XI(|E*mHs{_DgRz8g&2rEd6ppHbwn^sj;Dz=-RA zx(@HJO#pxZ5&+ha?cm)5B7|i1f4AJu93hAu2Hct9x;1H<)C0N;$)Y$V!^3VGv_IsS z*5a3GcV-9zbA%)i4pUxD$=zU+&@qTPxGv-hNdXDM27132d1vTx{{NJG=hF*e!8EIG Out!)PvCg>x0Dl2pLc_%X delta 2126 zcmZvd2{hDu8^>qNF!mW+QzT2=k&rEPWvQ`dE@R51mysngnvo^hjTwVX%#8VGk86u= zXcAdVE>X6LESV6mN?mj*yNY-2eb0Gs=k)&0`JVGT=lP!Jch2)U-{;5F*f@y9IzgcF zAP`6tL>4#1p3+{QT@wOV=(7MvU(%;Yfvzr;agh9ldF!b>pTUbZaHetH0XAiwx$mvLnpX)7~Dx4~`QFqYH zt-^O^qPeL=p0v9=_gABRFkYOkA>-n^Bn6XBNqGr}3RDQD`m)r=Ku!#W9zM=GD#|=T zaaE0Uol7^%7`c%L?QO!DNaOXaJl2c(!Qbw0cRDxvWrly!s>9?}#wi9=CwD{Lcn+ux zNK`@!Zfk<`P9y#JNJsNj;gQ&4du;hMs+fpmDurCS*-%ip)J-U=p!TY+k__OogyzlU zWM99M!c=)B^A54o{gjUv>m!#r6gM5R*828TUz`6#=kP@ex@om``D)8MDMHoMB7bb_ zOLMlR?zw1tL%3aCnbmGy+#f4$++g~P#=8YI&Q;-C2|LNS9boHaVktCcEbIBJsXDm% zBKb}*qIrX;)*v~=*Z%zJmhh*M& z0m`?FQ#Yk*%j{1Ghy_4LoFplbSJPi$^)TIK^0ad)6s_eNXg zp~q#+lyXP&p=&pH%7c)}h8Gp7%+*swiD<~2JuKtFgMdzCD zn}0(Y;x6d0{9GDc@A14b_a$n*bMumj{U3JDwPZ1Ogn$x=Vx0{{M2ONMhyWjbe?k@v z0xbw6AqWOBIQ)taiKG`rCXqAyPIX$#r)#PW6^!CCTFo4s3!;*uz7CciDG;HG#99 zAB*K*;Cp1(u5}$_n;3evE8x$NUl{F7V|XdWf38Q9jbE%%*wbv9eK!>X*>`h2;Oz{N^3VP#FF;|Dp}5g*!Qf6pKp-)} z@USGGX#X(15VAjRf;p^@h!1Y_Ehms=iAc?<#*~^T{+aASoGSt@s%BbLzlY+yxOS_g zdV48dZdld?UTYCPchku45`OoB2R&=mV~F47`|X1a<;}O42=ntgc#iVm8OK>&T7=!m zUBz`JyT?y0J)DMik_#D~1}xiyGuP!f&#Z2f*N(Rsj=R$uVND}G@JIyScziUl?hJg+ zYdllArme7&I=wAb&nTm#94M%BW<>zJx(j3$c~xXFH-0ybBU}hbY%&Jb!a~Gtsx%wM zkA;}~py`gk#VmKe_RjEiiJzn-NIVGg7z=fR)>~U~B%2c-cwO?}Kl1wLQ6VILIIb8u z`H>WILTxsjt6=resEi-Bx}xpC0Yfb?W`m(5n^aZLph{1+a0GdTl0tq&u8p& zAfKErB9{}tUBF?#1|byvE2K1xhOAANUrVv~7u`-Uxx;b4iu>ZCw~J3eokhpn94XP6 znHQh^q}hL+wkc|S-GDHyQ?@-2#@=nR_k%vIe#mr-K1z-Kg|^q%{(c1J1EW`a3-S-} z_h9`xnrJ_S(hkwZz6!P?aRkJ<0>K12;~#ZJ!2Kw*PrO4PVf&JIRM+ROJ^sLL7}x-`0Rjz8NmF6m|K`OMxvo%L0P?;aMVOv@NC$S?5$J09h2R6L(q zh7`0L2(wO&NrxKO;e_MBGuW>ntP@zs3iN;eJU~`TNBXwQJ@1);QSgU5`bn<`0F0C| z_y9na!h=r$<5K6q0f3>jk5Co_cmz=a>ZP592LF5YfQkV+aAzTT=--tBI9lb$WuhUN z%U?%TAY5-~J;0ody2z}+0RsI8OW>P} diff --git a/plugin/SqueezeESP32/Graphics.pm b/plugin/SqueezeESP32/Graphics.pm index f3faf768..811e2008 100644 --- a/plugin/SqueezeESP32/Graphics.pm +++ b/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; diff --git a/plugin/SqueezeESP32/Player.pm b/plugin/SqueezeESP32/Player.pm index 18a7d3ce..38b4e6a2 100644 --- a/plugin/SqueezeESP32/Player.pm +++ b/plugin/SqueezeESP32/Player.pm @@ -34,4 +34,8 @@ sub playerSettingsFrame { } } +sub hasScrolling { + return 1; +} + 1; diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index eb9ec5e8..755b1edd 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -3,13 +3,13 @@ enabled philippe_44@outlook.com - 7.9 + * *.* SlimServer PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.6 + 0.7 Philippe diff --git a/plugin/repo.xml b/plugin/repo.xml index f66f9ee8..65171627 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,10 +1,10 @@ - + https://github.com/sle118/squeezelite-esp32 Philippe - e43f87096bafbbf4d97637a690975266af8e03c4 + a1d676e7a3a2d241d17a39aff05bcb8377565a76 philippe_44@outlook.com SqueezeESP32 additional player id (100) http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip