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