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 4ae1a787..d9a21459 100644 Binary files a/plugin/SqueezeESP32.zip and b/plugin/SqueezeESP32.zip differ 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