diff --git a/CMakeLists.txt b/CMakeLists.txt index fa676185..7ebea6f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,3 +46,10 @@ endif() + + + +# add_custom_target(size +# DEPENDS ${project_elf} +# COMMAND ${idf_size} ${mapfile} +# ) \ No newline at end of file diff --git a/README.md b/README.md index bb701d3b..531128de 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ SPI,width=,height=,cs=[,speed=][,HFlip][,VFlip][dri - VFlip and HFlip are optional can be used to change display orientation - Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz -Currently 128x32/64 I2C display like [this](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi) and [this](https://www.waveshare.com/wiki/1.3inch_OLED_HAT) are supported +Currently 128x32/64 I2C and SPI display like [this](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi) and [this](https://www.waveshare.com/wiki/1.3inch_OLED_HAT) are supported The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is ``` diff --git a/components/codecs/CMakeLists.txt b/components/codecs/CMakeLists.txt index e8a87688..7863a6d5 100644 --- a/components/codecs/CMakeLists.txt +++ b/components/codecs/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS link_helper.c +idf_component_register(SRC_DIRS . INCLUDE_DIRS . ./inc inc/alac inc/faad2 inc/FLAC inc/helix-aac inc/mad inc/ogg inc/opus inc/opusfile inc/resample16 inc/soxr inc/vorbis REQUIRES esp_common platform_config freertos nvs_flash esp32 spi_flash newlib log pthread ) diff --git a/components/display/CMakeLists.txt b/components/display/CMakeLists.txt index 1420a84d..9735503f 100644 --- a/components/display/CMakeLists.txt +++ b/components/display/CMakeLists.txt @@ -1,28 +1,5 @@ -idf_component_register(SRCS display.c - SH1106.c - SSD1306.c - SSD132x.c - core/ifaces/default_if_i2c.c - core/ifaces/default_if_spi.c - core/gds_draw.c - core/gds_font.c - core/gds_image.c - core/gds_text.c - core/gds.c - fonts/font_droid_sans_fallback_11x13.c - fonts/font_droid_sans_fallback_15x17.c - fonts/font_droid_sans_fallback_24x28.c - fonts/font_droid_sans_mono_13x24.c - fonts/font_droid_sans_mono_16x31.c - fonts/font_droid_sans_mono_7x13.c - fonts/font_liberation_mono_13x21.c - fonts/font_liberation_mono_17x30.c - fonts/font_liberation_mono_9x15.c - fonts/font_line_1.c - fonts/font_line_2.c - fonts/font_tarable7seg_16x32.c - fonts/font_tarable7seg_32x64.c +idf_component_register(SRC_DIRS . core core/ifaces fonts REQUIRES services INCLUDE_DIRS . fonts core ) @@ -30,4 +7,11 @@ idf_component_register(SRCS display.c set_source_files_properties(display.c PROPERTIES COMPILE_FLAGS -Wno-format-overflow -) \ No newline at end of file +) + +#target_link_libraries(${COMPONENT_LIB} PUBLIC +# -Wl,--whole-archive +# $ +# -Wl,--no-whole-archive +#) + diff --git a/components/display/SH1106.c b/components/display/SH1106.c index f6c1dc2f..91e33a54 100644 --- a/components/display/SH1106.c +++ b/components/display/SH1106.c @@ -21,6 +21,10 @@ static char TAG[] = "SH1106"; +struct PrivateSpace { + uint8_t *Shadowbuffer; +}; + // Functions are not declared to minimize # of lines static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { @@ -36,9 +40,10 @@ static void SetPageAddress( struct GDS_Device* Device, uint8_t Start, uint8_t En static void Update( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; // not sure the compiler does not have to redo all calculation in for loops, so local it is int width = Device->Width, rows = Device->Height / 8; - uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; // by row, find first and last columns that have been updated for (int r = 0; r < rows; r++) { @@ -55,7 +60,7 @@ static void Update( struct GDS_Device* Device ) { if (first--) { SetColumnAddress( Device, first, last ); SetPageAddress( Device, r, r); - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1); } } #else @@ -79,26 +84,15 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { } static bool Init( struct GDS_Device* Device ) { - Device->FramebufferSize = ( Device->Width * Device->Height ) / 8; - -// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy #ifdef SHADOW_BUFFER - Device->Framebuffer = calloc( 1, Device->FramebufferSize ); - NullCheck( Device->Framebuffer, return false ); + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; #ifdef USE_IRAM - if (Device->IF == IF_SPI) Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + if (Device->IF == GDS_IF_SPI) Private->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); else #endif - Device->Shadowbuffer = malloc( Device->FramebufferSize ); - NullCheck( Device->Shadowbuffer, return false ); - memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize); -#else // not SHADOW_BUFFER -#ifdef USE_IRAM - // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy - if (Device->IF == IF_SPI) Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); - else -#endif - Device->Framebuffer = calloc( 1, Device->FramebufferSize ); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + NullCheck( Private->Shadowbuffer, return false ); + memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize); #endif // need to be off and disable display RAM @@ -143,16 +137,17 @@ static const struct GDS_Device SH1106 = { .DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast, .SetVFlip = SetVFlip, .SetHFlip = SetHFlip, .Update = Update, .Init = Init, - //.DrawPixelFast = GDS_DrawPixelFast, - //.ClearWindow = ClearWindow, }; struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) { if (!strcasestr(Driver, "SH1106")) return NULL; if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); - *Device = SH1106; + *Device = SH1106; Device->Depth = 1; +#if !defined SHADOW_BUFFER && defined USE_IRAM + Device->Alloc = GDS_ALLOC_IRAM_SPI; +#endif ESP_LOGI(TAG, "SH1106 driver"); return Device; diff --git a/components/display/SSD1306.c b/components/display/SSD1306.c index 044aca24..e1280b8b 100644 --- a/components/display/SSD1306.c +++ b/components/display/SSD1306.c @@ -21,7 +21,11 @@ static char TAG[] = "SSD1306"; -// Functions are not deckared to minimize # of lines +struct PrivateSpace { + uint8_t *Shadowbuffer; +}; + +// Functions are not declared to minimize # of lines static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { Device->WriteCommand( Device, 0x21 ); @@ -36,12 +40,14 @@ static void SetPageAddress( struct GDS_Device* Device, uint8_t Start, uint8_t En static void Update( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; // not sure the compiler does not have to redo all calculation in for loops, so local it is - int width = Device->Width, rows = Device->Height / 8; - uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + int width = Device->Width, pages = Device->Height / 8; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; + int CurrentPage = -1, FirstCol = -1, LastCol = -1; // by row, find first and last columns that have been updated - for (int r = 0; r < rows; r++) { + for (int p = 0; p < pages; p++) { uint8_t first = 0, last; for (int c = 0; c < width; c++) { if (*iptr != *optr) { @@ -53,13 +59,26 @@ static void Update( struct GDS_Device* Device ) { // now update the display by "byte rows" if (first--) { - SetColumnAddress( Device, first, last ); - SetPageAddress( Device, r, r); - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + // only set column when useful, saves a fair bit of CPU + if (first > FirstCol && first <= FirstCol + 4 && last < LastCol && last >= LastCol - 4) { + first = FirstCol; + last = LastCol; + } else { + SetColumnAddress( Device, first, last ); + FirstCol = first; + LastCol = last; + } + + // Set row only when needed, otherwise let auto-increment work + if (p != CurrentPage) SetPageAddress( Device, p, Device->Height / 8 - 1 ); + CurrentPage = p + 1; + + // actual write + Device->WriteData( Device, Private->Shadowbuffer + p*width + first, last - first + 1 ); } } #else - // automatic counter and end Page/Column + // automatic counter and end Page/Column (we assume Height % 8 == 0) SetColumnAddress( Device, 0, Device->Width - 1); SetPageAddress( Device, 0, Device->Height / 8 - 1); Device->WriteData( Device, Device->Framebuffer, Device->FramebufferSize ); @@ -77,26 +96,15 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { } static bool Init( struct GDS_Device* Device ) { - Device->FramebufferSize = ( Device->Width * Device->Height ) / 8; - -// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy #ifdef SHADOW_BUFFER - Device->Framebuffer = calloc( 1, Device->FramebufferSize ); - NullCheck( Device->Framebuffer, return false ); + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; #ifdef USE_IRAM - if (Device->IF == IF_SPI) Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + if (Device->IF == GDS_IF_SPI) Private->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); else #endif - Device->Shadowbuffer = malloc( Device->FramebufferSize ); - NullCheck( Device->Shadowbuffer, return false ); - memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize); -#else // not SHADOW_BUFFER -#ifdef USE_IRAM - // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy - if (Device->IF == IF_SPI) Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); - else -#endif - Device->Framebuffer = calloc( 1, Device->FramebufferSize ); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + NullCheck( Private->Shadowbuffer, return false ); + memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize); #endif // need to be off and disable display RAM @@ -106,6 +114,10 @@ static bool Init( struct GDS_Device* Device ) { // charge pump regulator, do direct init Device->WriteCommand( Device, 0x8D ); Device->WriteCommand( Device, 0x14 ); + + // set Clocks + Device->WriteCommand( Device, 0xD5 ); + Device->WriteCommand( Device, ( 0x08 << 4 ) | 0x00 ); // COM pins HW config (alternative:EN if 64, DIS if 32, remap:DIS) - some display might need something different Device->WriteCommand( Device, 0xDA ); @@ -114,6 +126,12 @@ static bool Init( struct GDS_Device* Device ) { // MUX Ratio Device->WriteCommand( Device, 0xA8 ); Device->WriteCommand( Device, Device->Height - 1); + // Page & GDDRAM Start Column High/Low + /* + Device->WriteCommand( Device, 0x00 ); + Device->WriteCommand( Device, 0x10 ); + Device->WriteCommand( Device, 0xB0 ); + */ // Display Offset Device->WriteCommand( Device, 0xD3 ); Device->WriteCommand( Device, 0 ); @@ -125,9 +143,6 @@ static bool Init( struct GDS_Device* Device ) { Device->SetHFlip( Device, false ); // no Display Inversion Device->WriteCommand( Device, 0xA6 ); - // set Clocks - Device->WriteCommand( Device, 0xD5 ); - Device->WriteCommand( Device, ( 0x08 << 4 ) | 0x00 ); // set Adressing Mode Horizontal Device->WriteCommand( Device, 0x20 ); Device->WriteCommand( Device, 0 ); @@ -144,8 +159,6 @@ static const struct GDS_Device SSD1306 = { .DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast, .SetVFlip = SetVFlip, .SetHFlip = SetHFlip, .Update = Update, .Init = Init, - //.DrawPixelFast = GDS_DrawPixelFast, - //.ClearWindow = ClearWindow, }; struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) { @@ -154,6 +167,9 @@ struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) { if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); *Device = SSD1306; Device->Depth = 1; +#if !defined SHADOW_BUFFER && defined USE_IRAM + Device->Alloc = GDS_ALLOC_IRAM_SPI; +#endif ESP_LOGI(TAG, "SSD1306 driver"); return Device; diff --git a/components/display/SSD132x.c b/components/display/SSD132x.c index cdb5779f..e68a4d74 100644 --- a/components/display/SSD132x.c +++ b/components/display/SSD132x.c @@ -26,8 +26,8 @@ static char TAG[] = "SSD132x"; enum { SSD1326, SSD1327 }; -struct SSD132x_Private { - uint8_t *iRAM; +struct PrivateSpace { + uint8_t *iRAM, *Shadowbuffer; uint8_t ReMap, PageSize; uint8_t Model; }; @@ -67,13 +67,13 @@ static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End } static void Update4( struct GDS_Device* Device ) { - struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private; + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; // always update by full lines SetColumnAddress( Device, 0, Device->Width / 2 - 1); #ifdef SHADOW_BUFFER - uint16_t *optr = (uint16_t*) Device->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer; + uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer; bool dirty = false; for (int r = 0, page = 0; r < Device->Height; r++) { @@ -92,10 +92,10 @@ static void Update4( struct GDS_Device* Device ) { SetRowAddress( Device, r - page + 1, r ); // own use of IRAM has not proven to be much better than letting SPI do its copy if (Private->iRAM) { - memcpy(Private->iRAM, Device->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); + memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 ); } else { - Device->WriteData( Device, Device->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); + Device->WriteData( Device, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); } dirty = false; } @@ -122,9 +122,11 @@ static void Update4( struct GDS_Device* Device ) { */ static void Update1( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; // not sure the compiler does not have to redo all calculation in for loops, so local it is int width = Device->Width / 8, rows = Device->Height; - uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; + int CurrentRow = -1, FirstCol = -1, LastCol = -1; // by row, find first and last columns that have been updated for (int r = 0; r < rows; r++) { @@ -139,9 +141,22 @@ static void Update1( struct GDS_Device* Device ) { // now update the display by "byte rows" if (first--) { - SetColumnAddress( Device, first, last ); - SetRowAddress( Device, r, r); - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + // only set column when useful, saves a fair bit of CPU + if (first > FirstCol && first <= FirstCol + 4 && last < LastCol && last >= LastCol - 4) { + first = FirstCol; + last = LastCol; + } else { + SetColumnAddress( Device, first, last ); + FirstCol = first; + LastCol = last; + } + + // Set row only when needed, otherwise let auto-increment work + if (r != CurrentRow) SetRowAddress( Device, r, Device->Height - 1 ); + CurrentRow = r + 1; + + // actual write + Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1 ); } } #else @@ -161,15 +176,15 @@ static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, i *FBOffset ^= BIT( 7 - XBit ); } else { // we might be able to save the 7-Xbit using BitRemap (A0 bit 2) - *FBOffset = ( Color == GDS_COLOR_WHITE ) ? *FBOffset | BIT( 7 - XBit ) : *FBOffset & ~BIT( 7 - XBit ); + *FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( XBit ) : *FBOffset | BIT( XBit ); } } static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) { uint8_t _Color = Color == GDS_COLOR_BLACK ? 0: 0xff; - uint8_t Width = Device->Width >> 3; + int Width = Device->Width >> 3; uint8_t *optr = Device->Framebuffer; - + for (int r = y1; r <= y2; r++) { int c = x1; // for a row that is not on a boundary, not column opt can be done, so handle all columns on that line @@ -180,24 +195,35 @@ static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int c += chunk * 8; while (c <= x2) DrawPixel1Fast( Device, c++, r, Color ); } - - Device->Dirty = true; } static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) { - uint8_t *optr = Device->Framebuffer; - if (!Height) Height = Device->Height; if (!Width) Width = Device->Width; + int DWidth = Device->Width >> 3; - // just do bitreverse and if BitRemap works, there will be even nothing to do - for (int i = Height * Width >> 3; --i >= 0;) *optr++ = BitReverseTable256[*Data++]; - - // Dirty is set for us + // Two consecutive bits of source data are split over two different bytes of framebuffer + for (int c = 0; c < Width; c++) { + uint8_t shift = c & 0x07, bit = ~(1 << shift); + uint8_t *optr = Device->Framebuffer + (c >> 3); + + // we need to linearize code to let compiler better optimize + for (int r = Height >> 3; --r >= 0;) { + uint8_t Byte = BitReverseTable256[*Data++]; + *optr = (*optr & bit) | ((Byte & 0x01) << shift); optr += DWidth; Byte >>= 1; + *optr = (*optr & bit) | ((Byte & 0x01) << shift); optr += DWidth; Byte >>= 1; + *optr = (*optr & bit) | ((Byte & 0x01) << shift); optr += DWidth; Byte >>= 1; + *optr = (*optr & bit) | ((Byte & 0x01) << shift); optr += DWidth; Byte >>= 1; + *optr = (*optr & bit) | ((Byte & 0x01) << shift); optr += DWidth; Byte >>= 1; + *optr = (*optr & bit) | ((Byte & 0x01) << shift); optr += DWidth; Byte >>= 1; + *optr = (*optr & bit) | ((Byte & 0x01) << shift); optr += DWidth; Byte >>= 1; + *optr = (*optr & bit) | ((Byte & 0x01) << shift); optr += DWidth; + } + } } static void SetHFlip( struct GDS_Device* Device, bool On ) { - struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private; + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; if (Private->Model == SSD1326) Private->ReMap = On ? (Private->ReMap | ((1 << 0) | (1 << 2))) : (Private->ReMap & ~((1 << 0) | (1 << 2))); else Private->ReMap = On ? (Private->ReMap | ((1 << 0) | (1 << 1))) : (Private->ReMap & ~((1 << 0) | (1 << 1))); Device->WriteCommand( Device, 0xA0 ); @@ -205,7 +231,7 @@ static void SetHFlip( struct GDS_Device* Device, bool On ) { } static void SetVFlip( struct GDS_Device *Device, bool On ) { - struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private; + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; if (Private->Model == SSD1326) Private->ReMap = On ? (Private->ReMap | (1 << 1)) : (Private->ReMap & ~(1 << 1)); else Private->ReMap = On ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4)); Device->WriteCommand( Device, 0xA0 ); @@ -221,50 +247,32 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { } static bool Init( struct GDS_Device* Device ) { - struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private; + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; // find a page size that is not too small is an integer of height Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2)); Private->PageSize = Device->Height / (Device->Height / Private->PageSize) ; -#ifdef USE_IRAM - // let SPI driver allocate memory, it has not proven to be more efficient - if (Device->IF == IF_SPI) Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); -#endif - Device->FramebufferSize = ( Device->Width * Device->Height ) / 2; - Device->Framebuffer = calloc( 1, Device->FramebufferSize ); - NullCheck( Device->Framebuffer, return false ); - -// benchmarks showed little gain to have SPI memory already in IRAM vs letting driver copy #ifdef SHADOW_BUFFER - Device->Framebuffer = calloc( 1, Device->FramebufferSize ); - NullCheck( Device->Framebuffer, return false ); #ifdef USE_IRAM - if (Device->IF == IF_SPI) { + if (Device->IF == GDS_IF_SPI) { if (Device->Depth == 1) { - Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + Private->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); } else { - Device->Shadowbuffer = malloc( Device->FramebufferSize ); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); } } else #endif - Device->Shadowbuffer = malloc( Device->FramebufferSize ); - memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize); -#else // not SHADOW_BUFFER -#ifdef USE_IRAM - if (Device->IF == IF_SPI) { - if (Device->Depth == 1) { - Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); - } else { - Device->Framebuffer = calloc( 1, Device->FramebufferSize ); - Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); - } - } else -#endif - Device->Framebuffer = calloc( 1, Device->FramebufferSize ); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize); +#else +#ifdef USE_IRAM + if (Device->Depth == 4 && Device->IF == GDS_IF_SPI) Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); #endif +#endif + ESP_LOGI(TAG, "SSD1326/7 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM); // need to be off and disable display RAM @@ -315,23 +323,28 @@ static const struct GDS_Device SSD132x = { struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) { uint8_t Model; + int Depth; - if (!strcasestr(Driver, "SSD1326")) Model = SSD1326; - else if (!strcasestr(Driver, "SSD1327")) Model = SSD1327; + if (strcasestr(Driver, "SSD1326")) Model = SSD1326; + else if (strcasestr(Driver, "SSD1327")) Model = SSD1327; else return NULL; if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); *Device = SSD132x; - ((struct SSD132x_Private*) Device->Private)->Model = Model; - - sscanf(Driver, "%*[^:]:%c", &Device->Depth); + ((struct PrivateSpace*) Device->Private)->Model = Model; + + sscanf(Driver, "%*[^:]:%u", &Depth); + Device->Depth = Depth; if (Model == SSD1326 && Device->Depth == 1) { Device->Update = Update1; Device->DrawPixelFast = DrawPixel1Fast; Device->DrawBitmapCBR = DrawBitmapCBR; Device->ClearWindow = ClearWindow; +#if !defined SHADOW_BUFFER && defined USE_IRAM + Device->Alloc = GDS_ALLOC_IRAM_SPI; +#endif } else { Device->Depth = 4; } diff --git a/components/display/SSD1675.c b/components/display/SSD1675.c new file mode 100644 index 00000000..390c8986 --- /dev/null +++ b/components/display/SSD1675.c @@ -0,0 +1,252 @@ +/** + * Copyright (c) 2017-2018 Tara Keeling + * 2020 Philippe G. + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include + +#include "gds.h" +#include "gds_private.h" + +static char TAG[] = "SSD1675"; + +const unsigned char EPD_lut_full_update[] = { + 0x80,0x60,0x40,0x00,0x00,0x00,0x00, //LUT0: BB: VS 0 ~7 + 0x10,0x60,0x20,0x00,0x00,0x00,0x00, //LUT1: BW: VS 0 ~7 + 0x80,0x60,0x40,0x00,0x00,0x00,0x00, //LUT2: WB: VS 0 ~7 + 0x10,0x60,0x20,0x00,0x00,0x00,0x00, //LUT3: WW: VS 0 ~7 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT4: VCOM: VS 0 ~7 + 0x03,0x03,0x00,0x00,0x02, // TP0 A~D RP0 + 0x09,0x09,0x00,0x00,0x02, // TP1 A~D RP1 + 0x03,0x03,0x00,0x00,0x02, // TP2 A~D RP2 + 0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3 + 0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4 + 0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5 + 0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6 + 0x15,0x41,0xA8,0x32,0x30,0x0A, +}; + +const unsigned char EPD_lut_partial_update[]= { //20 bytes + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT0: BB: VS 0 ~7 + 0x80,0x00,0x00,0x00,0x00,0x00,0x00, //LUT1: BW: VS 0 ~7 + 0x40,0x00,0x00,0x00,0x00,0x00,0x00, //LUT2: WB: VS 0 ~7 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT3: WW: VS 0 ~7 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT4: VCOM: VS 0 ~7 + 0x0A,0x00,0x00,0x00,0x00, // TP0 A~D RP0 + 0x00,0x00,0x00,0x00,0x00, // TP1 A~D RP1 + 0x00,0x00,0x00,0x00,0x00, // TP2 A~D RP2 + 0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3 + 0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4 + 0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5 + 0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6 + 0x15,0x41,0xA8,0x32,0x30,0x0A, +}; + +struct PrivateSpace { + int ReadyPin; + uint16_t Height; +}; + +// Functions are not declared to minimize # of lines + +void WaitReady( struct GDS_Device* Device) { + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; + if (Private->ReadyPin >= 0) { + int count = 4*1000; + while (gpio_get_level( Private->ReadyPin ) && count) { + vTaskDelay( pdMS_TO_TICKS(100) ); + count -= 100; + } + } else { + vTaskDelay( pdMS_TO_TICKS(2000) ); + } +} + +static void WriteByte( struct GDS_Device* Device, uint8_t Data ) { + Device->WriteData( Device, &Data, 1 ); +} + +static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { + // start might be greater than end if we decrement + Device->WriteCommand( Device, 0x44 ); + Device->WriteData( Device, &Start, 1 ); + Device->WriteData( Device, &End, 1 ); + + // we obviously want to start ... from the start + Device->WriteCommand( Device, 0x4e ); + WriteByte( Device, Start ); +} +static void SetRowAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) { + // start might be greater than end if we decrement + Device->WriteCommand( Device, 0x45 ); + WriteByte( Device, Start ); + WriteByte( Device, Start >> 8 ); + WriteByte( Device, End ); + WriteByte( Device, End >> 8 ); + + // we obviously want to start ... from the start + Device->WriteCommand( Device, 0x4f ); + WriteByte( Device, Start ); + WriteByte( Device, Start >> 8 ); +} + +static void Update( struct GDS_Device* Device ) { + uint8_t *iptr = Device->Framebuffer; + + Device->WriteCommand( Device, 0x24 ); + + // this is awfully slow, but e-ink are slow anyway ... + for (int i = Device->FramebufferSize; --i >= 0;) { + WriteByte( Device, ~*iptr++ ); + } + + Device->WriteCommand( Device, 0x22 ); + WriteByte( Device, 0xC7); + Device->WriteCommand( Device, 0X20 ); + + WaitReady( Device ); +} + +// remember that for these ELD drivers W and H are "inverted" +static void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) { + uint32_t YBit = ( Y & 0x07 ); + Y>>= 3; + + uint8_t* FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X ); + *FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( 7-YBit ) : *FBOffset | BIT( 7-YBit ); +} + +static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) { + for (int r = y1; r <= y2; r++) { + for (int c = x1; c <= x2; c++) { + DrawPixelFast( Device, c, r, Color ); + } + } +} + +static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) { + if (!Height) Height = Device->Height; + if (!Width) Width = Device->Width; + + // just do row/column swap + for (int r = 0; r < Height; r++) { + uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r; + for (int c = Width; --c >= 0;) { + *optr++ = *iptr; + iptr += Height; + } + } +} + +static bool Init( struct GDS_Device* Device ) { + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; + + // need to re-adjust framebuffer because these are non % 8 + Private->Height = Device->Height; + if (Device->Height & 0x07) Device->Height = ((Device->Height >> 3) + 1) << 3; + + Device->FramebufferSize = Device->Width * Device->Height / 8; + Device->Framebuffer = calloc(1, Device->FramebufferSize); + NullCheck( Device->Framebuffer, return false ); + + if (Private->ReadyPin >= 0) { + gpio_pad_select_gpio( Private->ReadyPin ); + gpio_set_pull_mode( Private->ReadyPin, GPIO_PULLUP_ONLY); + gpio_set_direction( Private->ReadyPin, GPIO_MODE_INPUT ); + } + + // soft reset + vTaskDelay(pdMS_TO_TICKS( 2000 )); + Device->WriteCommand( Device, 0x12 ); + WaitReady( Device ); + + Device->WriteCommand( Device, 0x74 ); + WriteByte( Device, 0x54 ); + Device->WriteCommand( Device, 0x7e ); + WriteByte( Device, 0x3B ); + + Device->WriteCommand( Device, 0x3c ); + WriteByte( Device, 0x03 ); + + Device->WriteCommand( Device, 0x2c ); + WriteByte( Device, 0x55 ); + + Device->WriteCommand( Device, 0x03 ); + WriteByte( Device, EPD_lut_full_update[70] ); + + Device->WriteCommand( Device, 0x04 ); + WriteByte( Device, EPD_lut_full_update[71] ); + WriteByte( Device, EPD_lut_full_update[72] ); + WriteByte( Device, EPD_lut_full_update[73] ); + + Device->WriteCommand( Device, 0x3a ); + WriteByte( Device, EPD_lut_full_update[74] ); + Device->WriteCommand( Device, 0x3b ); + WriteByte( Device, EPD_lut_full_update[75] ); + + Device->WriteCommand( Device, 0X32 ); + for (int i = 0; i < 70; i++) { + WriteByte( Device, EPD_lut_full_update[i] ); + } + + // now deal with funny X/Y layout (W and H are "inverted") + Device->WriteCommand( Device, 0x01 ); + WriteByte( Device, Device->Width - 1 ); + WriteByte( Device, (Device->Width - 1) >> 8 ); + WriteByte( Device, (0 << 0) ); + + /* + Start from 0, Ymax, incX, decY. Starting from X=Height would be difficult + as we would hit the extra bits added because height % 8 != 0 and they are + not written by the DrawPixel. By starting from X=0 we are aligned and + doing incY is like a clockwise 90° rotation (vs otherwise we would virtually + do a counter-clockwise rotation but again have the "border" issue. + */ + Device->WriteCommand( Device, 0x11 ); + WriteByte( Device, (1 << 2) | (0 << 1) | (1 << 0)); + + // must be in order with increment/decrement i.e start might be > end if we decrement + SetColumnAddress( Device, 0x0, (Device->Height >> 3) - 1 ); + SetRowAddress ( Device, Device->Width - 1, 0 ); + + WaitReady( Device ); + + Update( Device ); + + return true; +} + +static const struct GDS_Device SSD1675 = { + .DrawBitmapCBR = DrawBitmapCBR, .ClearWindow = ClearWindow, + .DrawPixelFast = DrawPixelFast, + .Update = Update, .Init = Init, +}; + +struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) { + if (!strcasestr(Driver, "SSD1675")) return NULL; + + if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); + *Device = SSD1675; + + Device->Depth = 1; + Device->Alloc = GDS_ALLOC_NONE; + + char *p; + struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private; + Private->ReadyPin = -1; + if ((p = strcasestr(Driver, "ready")) != NULL) Private->ReadyPin = atoi(strchr(p, '=') + 1); + + ESP_LOGI(TAG, "SSD1675 driver with ready GPIO %d", Private->ReadyPin); + + return Device; +} \ No newline at end of file diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 7f59f79e..d315b027 100644 --- a/components/display/core/gds.c +++ b/components/display/core/gds.c @@ -95,7 +95,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize ); } else { uint8_t _Color = Color | (Color << 4); - uint8_t Width = Device->Width; + int Width = Device->Width; uint8_t *optr = Device->Framebuffer; // try to do byte processing as much as possible for (int r = y1; r <= y2; r++) { @@ -113,7 +113,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, } } } - + // make sure diplay will do update Device->Dirty = true; } @@ -132,10 +132,29 @@ bool GDS_Reset( struct GDS_Device* Device ) { return true; } -void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { Device->SetContrast( Device, Contrast); } -void GDS_SetHFlip( struct GDS_Device* Device, bool On ) { Device->SetHFlip( Device, On ); } -void GDS_SetVFlip( struct GDS_Device* Device, bool On ) { Device->SetVFlip( Device, On ); } +bool GDS_Init( struct GDS_Device* Device ) { + + Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth); + + // allocate FB unless explicitely asked not to + if (!(Device->Alloc & GDS_ALLOC_NONE)) { + if ((Device->Alloc & GDS_ALLOC_IRAM) || ((Device->Alloc & GDS_ALLOC_IRAM_SPI) && Device->IF == GDS_IF_SPI)) { + heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + } else { + Device->Framebuffer = calloc( 1, Device->FramebufferSize ); + } + NullCheck( Device->Framebuffer, return false ); + } + + bool Res = Device->Init( Device ); + if (!Res) free(Device->Framebuffer); + return Res; +} + +void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { if (Device->SetContrast) Device->SetContrast( Device, Contrast); } +void GDS_SetHFlip( struct GDS_Device* Device, bool On ) { if (Device->SetHFlip) Device->SetHFlip( Device, On ); } +void GDS_SetVFlip( struct GDS_Device* Device, bool On ) { if (Device->SetVFlip) Device->SetVFlip( Device, On ); } int GDS_GetWidth( struct GDS_Device* Device ) { return Device->Width; } int GDS_GetHeight( struct GDS_Device* Device ) { return Device->Height; } -void GDS_DisplayOn( struct GDS_Device* Device ) { Device->DisplayOn( Device ); } -void GDS_DisplayOff( struct GDS_Device* Device ) { Device->DisplayOff( Device ); } \ No newline at end of file +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 ); } \ No newline at end of file diff --git a/components/display/core/gds.h b/components/display/core/gds.h index 2c3a9e26..49cd7c6c 100644 --- a/components/display/core/gds.h +++ b/components/display/core/gds.h @@ -15,11 +15,12 @@ enum { GDS_COLOR_L0 = 0, GDS_COLOR_L1, GDS_COLOR_L2, GDS_COLOR_L3, GDS_COLOR_L4, GDS_COLOR_L5, GDS_COLOR_L6, GDS_COLOR_L7, GDS_COLOR_L8, GDS_COLOR_L9, GDS_COLOR_L10, GDS_COLOR_L11, GDS_COLOR_L12, GDS_COLOR_L13, GDS_COLOR_L14, GDS_COLOR_L15, + GDS_COLOR_MAX }; -#define GDS_COLOR_BLACK GDS_COLOR_L0 -#define GDS_COLOR_WHITE GDS_COLOR_L15 -#define GDS_COLOR_XOR 2 +#define GDS_COLOR_BLACK (0) +#define GDS_COLOR_WHITE (-1) +#define GDS_COLOR_XOR (GDS_COLOR_MAX + 1) struct GDS_Device; struct GDS_FontDef; diff --git a/components/display/core/gds_draw.c b/components/display/core/gds_draw.c index 94ad1ce4..51789e10 100644 --- a/components/display/core/gds_draw.c +++ b/components/display/core/gds_draw.c @@ -58,7 +58,7 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo if (XEnd >= Device->Width) XEnd = Device->Width - 1; if (y < 0) y = 0; - else if (y >= Device->Height) x = Device->Height - 1; + else if (y >= Device->Height) y = Device->Height - 1; for ( ; x < XEnd; x++ ) GDS_DrawPixelFast( Device, x, y, Color ); } @@ -195,11 +195,11 @@ void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) { if (!Height) Height = Device->Height; if (!Width) Width = Device->Width; - Height >>= 3; if (Device->DrawBitmapCBR) { Device->DrawBitmapCBR( Device, Data, Width, Height, Color ); } else if (Device->Depth == 1) { + Height >>= 3; // need to do row/col swap and bit-reverse for (int r = 0; r < Height; r++) { uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r; @@ -211,6 +211,7 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int } else if (Device->Depth == 4) { uint8_t *optr = Device->Framebuffer; int LineLen = Device->Width >> 1; + Height >>= 3; for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { uint8_t Byte = BitReverseTable256[*Data++]; // we need to linearize code to let compiler better optimize @@ -237,6 +238,7 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); } } } else { + Height >>= 3; // don't know bitdepth, use brute-force solution for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { uint8_t Byte = *Data++; diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c index e7d4dac7..a5919f74 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -21,6 +21,7 @@ typedef struct { struct { // DirectDraw struct GDS_Device * Device; int XOfs, YOfs; + int XMin, YMin; int Depth; }; }; @@ -38,9 +39,7 @@ static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) { uint8_t *Pixels = (uint8_t*) Bitmap; for (int y = Frame->top; y <= Frame->bottom; y++) { - if (y < Context->YOfs) continue; for (int x = Frame->left; x <= Frame->right; x++) { - if (x < Context->XOfs) continue; // Convert the 888 to RGB565 uint16_t Value = (*Pixels++ & ~0x07) << 8; Value |= (*Pixels++ & ~0x03) << 3; @@ -57,12 +56,14 @@ static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) { int Shift = 8 - Context->Depth; for (int y = Frame->top; y <= Frame->bottom; y++) { + if (y < Context->YMin) continue; for (int x = Frame->left; x <= Frame->right; x++) { + if (x < Context->XMin) continue; // Convert the 888 to RGB565 int Value = ((Pixels[0]*11 + Pixels[1]*59 + Pixels[2]*30) / 100) >> Shift; Pixels += 3; // used DrawPixel and not "fast" version as X,Y may be beyond screen - GDS_DrawPixel( Context->Device, Context->XOfs + x, Context->YOfs + y, Value); + GDS_DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, Value); } } return 1; @@ -90,11 +91,16 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal Decoder.scale = Scale; if (Res == JDR_OK && !SizeOnly) { - // find the scaling factor Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t)); + + // find the scaling factor uint8_t N = 0, ScaleInt = ceil(1.0 / Scale); ScaleInt--; ScaleInt |= ScaleInt >> 1; ScaleInt |= ScaleInt >> 2; ScaleInt++; while (ScaleInt >>= 1) N++; + if (N > 3) { + ESP_LOGW(TAG, "Image will not fit %dx%d", Decoder.width, Decoder.height); + N = 3; + } // ready to decode if (Context.OutData) { @@ -102,7 +108,7 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal Context.Height = Decoder.height / (1 << N); if (Width) *Width = Context.Width; if (Height) *Height = Context.Height; - Res = jd_decomp(&Decoder, OutHandler, N > 3 ? 3 : N); + Res = jd_decomp(&Decoder, OutHandler, N); if (Res != JDR_OK) { ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res); } @@ -202,16 +208,23 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int uint8_t Ratio = XRatio < YRatio ? ceil(1/XRatio) : ceil(1/YRatio); Ratio--; Ratio |= Ratio >> 1; Ratio |= Ratio >> 2; Ratio++; while (Ratio >>= 1) N++; + if (N > 3) { + ESP_LOGW(TAG, "Image will not fit %dx%d", Decoder.width, Decoder.height); + N = 3; + } Context.Width /= 1 << N; Context.Height /= 1 << N; } // then place it - if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = x + ((Device->Width - x) - Context.Width) / 2; - if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = y + ((Device->Height - y) - Context.Height) / 2; + if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = (Device->Width + x - Context.Width) / 2; + if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = (Device->Height + y - Context.Height) / 2; + Context.XMin = x - Context.XOfs; + Context.YMin = y - Context.YOfs; + // do decompress & draw - Res = jd_decomp(&Decoder, OutHandlerDirect, N > 3 ? 3 : N); + Res = jd_decomp(&Decoder, OutHandlerDirect, N); if (Res == JDR_OK) { Ret = true; } else { diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index ea05e99b..73b9d13c 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -7,6 +7,10 @@ #include "gds.h" #include "gds_err.h" +#define GDS_ALLOC_NONE 0x80 +#define GDS_ALLOC_IRAM 0x01 +#define GDS_ALLOC_IRAM_SPI 0x02 + #define GDS_CLIPDEBUG_NONE 0 #define GDS_CLIPDEBUG_WARNING 1 #define GDS_CLIPDEBUG_ERROR 2 @@ -55,8 +59,8 @@ typedef bool ( *WriteDataProc ) ( struct GDS_Device* Device, const uint8_t* Data struct spi_device_t; typedef struct spi_device_t* spi_device_handle_t; -#define IF_SPI 0 -#define IF_I2C 1 +#define GDS_IF_SPI 0 +#define GDS_IF_I2C 1 struct GDS_Device { uint8_t IF; @@ -82,8 +86,9 @@ struct GDS_Device { uint16_t Width; uint16_t Height; uint8_t Depth; - - uint8_t* Framebuffer, *Shadowbuffer; + + uint8_t Alloc; + uint8_t* Framebuffer; uint16_t FramebufferSize; bool Dirty; @@ -95,12 +100,13 @@ struct GDS_Device { // various driver-specific method // must always provide bool (*Init)( struct GDS_Device* Device); + void (*Update)( struct GDS_Device* Device ); + // may provide if supported void (*SetContrast)( struct GDS_Device* Device, uint8_t Contrast ); void (*DisplayOn)( struct GDS_Device* Device ); void (*DisplayOff)( struct GDS_Device* Device ); void (*SetHFlip)( struct GDS_Device* Device, bool On ); void (*SetVFlip)( struct GDS_Device* Device, bool On ); - void (*Update)( struct GDS_Device* Device ); // must provide for depth other than 1 (vertical) and 4 (may provide for optimization) void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color ); void (*DrawBitmapCBR)(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ); @@ -117,6 +123,7 @@ struct GDS_Device { }; bool GDS_Reset( struct GDS_Device* Device ); +bool GDS_Init( struct GDS_Device* Device ); static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) { bool Result = ( @@ -152,7 +159,7 @@ static inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int if ( Color == GDS_COLOR_XOR ) { *FBOffset ^= BIT( YBit ); } else { - *FBOffset = ( Color == GDS_COLOR_WHITE ) ? *FBOffset | BIT( YBit ) : *FBOffset & ~BIT( YBit ); + *FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit ); } } @@ -160,7 +167,7 @@ static inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int uint8_t* FBOffset; FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1)); - *FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | (Color << 4) : ((*FBOffset & 0xf0) | Color); + *FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f)); } static inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) { diff --git a/components/display/core/ifaces/default_if_i2c.c b/components/display/core/ifaces/default_if_i2c.c index c4b91b16..a7545b45 100644 --- a/components/display/core/ifaces/default_if_i2c.c +++ b/components/display/core/ifaces/default_if_i2c.c @@ -73,7 +73,7 @@ bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int Device->WriteData = I2CDefaultWriteData; Device->Address = I2CAddress; Device->RSTPin = RSTPin; - Device->IF = IF_I2C; + Device->IF = GDS_IF_I2C; Device->Width = Width; Device->Height = Height; @@ -83,7 +83,7 @@ bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int GDS_Reset( Device ); } - return Device->Init( Device ); + return GDS_Init( Device ); } static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Data, size_t DataLength ) { diff --git a/components/display/core/ifaces/default_if_spi.c b/components/display/core/ifaces/default_if_spi.c index 0bfb7c4e..7ab48e7a 100644 --- a/components/display/core/ifaces/default_if_spi.c +++ b/components/display/core/ifaces/default_if_spi.c @@ -58,7 +58,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int Device->SPIHandle = SPIDevice; Device->RSTPin = RSTPin; Device->CSPin = CSPin; - Device->IF = IF_SPI; + Device->IF = GDS_IF_SPI; Device->Width = Width; Device->Height = Height; @@ -68,7 +68,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int GDS_Reset( Device ); } - return Device->Init( Device ); + return GDS_Init( Device ); } static bool SPIDefaultWriteBytes( spi_device_handle_t SPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength ) { diff --git a/components/display/display.c b/components/display/display.c index 6c7eba41..e9b83bb0 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -59,8 +59,8 @@ static EXT_RAM_ATTR struct { static void displayer_task(void *args); struct GDS_Device *display; -extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect; -GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, NULL }; +extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect; +GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, NULL }; /**************************************************************************************** * @@ -86,15 +86,18 @@ void display_init(char *welcome) { // so far so good if (display && width > 0 && height > 0) { + int RST_pin = -1; + if ((p = strcasestr(config, "reset")) != NULL) RST_pin = atoi(strchr(p, '=') + 1); + // Detect driver interface if (strstr(config, "I2C") && i2c_system_port != -1) { int address = 0x3C; if ((p = strcasestr(config, "address")) != NULL) address = atoi(strchr(p, '=') + 1); - + init = true; GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ; - GDS_I2CAttachDevice( display, width, height, address, -1 ); + GDS_I2CAttachDevice( display, width, height, address, RST_pin ); ESP_LOGI(TAG, "Display is I2C on port %u", address); } else if (strstr(config, "SPI") && spi_system_host != -1) { @@ -105,7 +108,7 @@ void display_init(char *welcome) { init = true; GDS_SPIInit( spi_system_host, spi_system_dc_gpio ); - GDS_SPIAttachDevice( display, width, height, CS_pin, -1, speed ); + GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, speed ); ESP_LOGI(TAG, "Display is SPI host %u with cs:%d", spi_system_host, CS_pin); } else { @@ -189,7 +192,7 @@ static void displayer_task(void *args) { // handler elapsed track time if (displayer.timer && displayer.state == DISPLAYER_ACTIVE) { - char counter[12]; + char counter[16]; TickType_t tick = xTaskGetTickCount(); uint32_t elapsed = (tick - displayer.tick) * portTICK_PERIOD_MS; @@ -198,8 +201,8 @@ static void displayer_task(void *args) { displayer.tick = tick; displayer.elapsed += elapsed / 1000; xSemaphoreGive(displayer.mutex); - if (displayer.elapsed < 3600) sprintf(counter, "%5u:%02u", displayer.elapsed / 60, displayer.elapsed % 60); - else sprintf(counter, "%2u:%02u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60); + if (displayer.elapsed < 3600) snprintf(counter, 16, "%5u:%02u", displayer.elapsed / 60, displayer.elapsed % 60); + else snprintf(counter, 16, "%2u:%02u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60); GDS_TextLine(display, 1, GDS_TEXT_RIGHT, (GDS_TEXT_CLEAR | GDS_TEXT_CLEAR_EOL) | GDS_TEXT_UPDATE, counter); timer_sleep = 1000; } else timer_sleep = max(1000 - elapsed, 0); @@ -289,13 +292,14 @@ void displayer_metadata(char *artist, char *album, char *title) { /**************************************************************************************** * */ -void displayer_scroll(char *string, int speed) { +void displayer_scroll(char *string, int speed, int pause) { // need a display! if (!display) return; xSemaphoreTake(displayer.mutex, portMAX_DELAY); if (speed) displayer.speed = speed; + if (pause) displayer.pause = pause; displayer.offset = 0; strncpy(displayer.string, string, SCROLLABLE_SIZE); displayer.string[SCROLLABLE_SIZE] = '\0'; diff --git a/components/display/display.h b/components/display/display.h index 98b2dc92..e1b809e5 100644 --- a/components/display/display.h +++ b/components/display/display.h @@ -42,7 +42,7 @@ enum displayer_time_e { DISPLAYER_ELAPSED, DISPLAYER_REMAINING }; enum display_bus_cmd_e { DISPLAY_BUS_TAKE, DISPLAY_BUS_GIVE }; bool (*display_bus)(void *from, enum display_bus_cmd_e cmd); -void displayer_scroll(char *string, int speed); +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_timer(enum displayer_time_e mode, int elapsed, int duration); diff --git a/components/platform_bluetooth/CMakeLists.txt b/components/platform_bluetooth/CMakeLists.txt index 3aaa7463..7433b286 100644 --- a/components/platform_bluetooth/CMakeLists.txt +++ b/components/platform_bluetooth/CMakeLists.txt @@ -1,8 +1,6 @@ -idf_component_register(SRCS platform_bt_core.c - platform_bt_sink.c - platform_bt_source.c - INCLUDE_DIRS . - REQUIRES bt display esp_common freertos nvs_flash esp32 spi_flash newlib log pthread platform_config +idf_component_register( SRC_DIRS . + INCLUDE_DIRS . + REQUIRES bt display esp_common freertos nvs_flash esp32 spi_flash newlib pthread platform_config ) diff --git a/components/platform_config/CMakeLists.txt b/components/platform_config/CMakeLists.txt index 3d7f005c..784ba99c 100644 --- a/components/platform_config/CMakeLists.txt +++ b/components/platform_config/CMakeLists.txt @@ -1,6 +1,6 @@ -idf_component_register(SRCS "nvs_utilities.c" "platform_config.c" - INCLUDE_DIRS . ../tools/ - REQUIRES newlib nvs_flash json platform_console services vfs +idf_component_register( SRC_DIRS . + INCLUDE_DIRS . + REQUIRES newlib nvs_flash json platform_console services vfs ) diff --git a/components/platform_console/CMakeLists.txt b/components/platform_console/CMakeLists.txt index c8c318b2..e49f226a 100644 --- a/components/platform_console/CMakeLists.txt +++ b/components/platform_console/CMakeLists.txt @@ -1,7 +1,4 @@ -set(COMPONENT_ADD_INCLUDEDIRS .) - -set(COMPONENT_SRCS "platform_console.c" "cmd_ota.c" "cmd_nvs.c" "cmd_i2ctools.c" "cmd_squeezelite.c" "cmd_system.c" "cmd_wifi.c" ) - -set(COMPONENT_REQUIRES squeezelite console nvs_flash spi_flash app_update platform_config vfs pthread wifi-manager) - -register_component() +idf_component_register( SRC_DIRS . + INCLUDE_DIRS . + REQUIRES console nvs_flash spi_flash app_update platform_config vfs pthread wifi-manager + PRIV_REQUIRES display squeezelite ) \ No newline at end of file diff --git a/components/platform_console/cmd_squeezelite.c b/components/platform_console/cmd_squeezelite.c index ae958166..0d1db085 100644 --- a/components/platform_console/cmd_squeezelite.c +++ b/components/platform_console/cmd_squeezelite.c @@ -1,3 +1,4 @@ + //size_t esp_console_split_argv(char *line, char **argv, size_t argv_size); #include "cmd_squeezelite.h" @@ -13,9 +14,8 @@ #include "freertos/event_groups.h" #include "pthread.h" #include "platform_esp32.h" -#include "nvs.h" -#include "nvs_flash.h" -//extern char current_namespace[]; +#include "config.h" + static const char * TAG = "squeezelite_cmd"; #define SQUEEZELITE_THREAD_STACK_SIZE (6*1024) extern int main(int argc, char **argv); @@ -36,7 +36,7 @@ static void * squeezelite_runner_thread(){ main(thread_parms.argc,thread_parms.argv); return NULL; } -#define ADDITIONAL_SQUEEZELILTE_ARGS 5 +#define ADDITIONAL_SQUEEZELITE_ARGS 5 static void * squeezelite_thread(){ int * exit_code; static bool isRunning=false; @@ -45,9 +45,6 @@ static void * squeezelite_thread(){ return NULL; } isRunning=true; -// Let's not wait on WiFi to allow squeezelite to run in bluetooth mode -// ESP_LOGI(TAG,"Waiting for WiFi."); -// while(!wait_for_wifi()){usleep(100000);}; ESP_LOGV(TAG ,"Number of args received: %u",thread_parms.argc ); ESP_LOGV(TAG ,"Values:"); for(int i = 0;iactive_remote.handle) { close_mDNS(ctx->active_remote.handle); - pthread_join(ctx->search_thread, NULL); + pthread_join(ctx->active_remote.thread, NULL); } // stop broadcasting devices mdns_service_remove(ctx->svr, ctx->svc); mdnsd_stop(ctx->svr); #else - // first stop the search task if any - if (ctx->active_remote.running) { - ctx->active_remote.joiner = xTaskGetCurrentTaskHandle(); - ctx->active_remote.running = false; - - vTaskResume(ctx->active_remote.thread); - ulTaskNotifyTake(pdFALSE, portMAX_DELAY); - vTaskDelete(ctx->active_remote.thread); - - heap_caps_free(ctx->active_remote.xTaskBuffer); - } - // then the RTSP task ctx->joiner = xTaskGetCurrentTaskHandle(); ctx->running = false; @@ -267,10 +256,11 @@ void raop_delete(struct raop_ctx_s *ctx) { vTaskDelete(ctx->thread); heap_caps_free(ctx->xTaskBuffer); - rtp_end(ctx->rtp); - shutdown(ctx->sock, SHUT_RDWR); closesocket(ctx->sock); + + // cleanup all session-created items + cleanup_rtsp(ctx, true); mdns_service_remove("_raop", "_tcp"); #endif @@ -405,7 +395,7 @@ static void *rtsp_thread(void *arg) { if (n > 0) res = handle_rtsp(ctx, sock); if (n < 0 || !res || ctx->abort) { - abort_rtsp(ctx); + cleanup_rtsp(ctx, true); closesocket(sock); LOG_INFO("RTSP close %u", sock); sock = -1; @@ -515,10 +505,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) #ifdef WIN32 ctx->active_remote.handle = init_mDNS(false, ctx->host); - pthread_create(&ctx->search_thread, NULL, &search_remote, ctx); + pthread_create(&ctx->active_remote.thread, NULL, &search_remote, ctx); #else ctx->active_remote.running = true; - ctx->active_remote.destroy_mutex = xSemaphoreCreateMutex(); + ctx->active_remote.destroy_mutex = xSemaphoreCreateBinary(); ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); ctx->active_remote.thread = xTaskCreateStatic( (TaskFunction_t) search_remote, "search_remote", SEARCH_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->active_remote.xStack, ctx->active_remote.xTaskBuffer); #endif @@ -580,37 +570,14 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) if ((p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime); // only send FLUSH if useful (discards frames above buffer head and top) - if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime)) + if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime, true)) { success = ctx->cmd_cb(RAOP_FLUSH); + rtp_flush_release(ctx->rtp); + } } else if (!strcmp(method, "TEARDOWN")) { - rtp_end(ctx->rtp); - - ctx->rtp = NULL; - - // need to make sure no search is on-going and reclaim pthread memory -#ifdef WIN32 - if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle); - pthread_join(ctx->search_thread, NULL); -#else - ctx->active_remote.joiner = xTaskGetCurrentTaskHandle(); - ctx->active_remote.running = false; - - xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY); - vTaskDelete(ctx->active_remote.thread); - vSemaphoreDelete(ctx->active_remote.thread); - - heap_caps_free(ctx->active_remote.xTaskBuffer); - - LOG_INFO("[%p]: mDNS search task terminated", ctx); -#endif - - memset(&ctx->active_remote, 0, sizeof(ctx->active_remote)); - NFREE(ctx->rtsp.aeskey); - NFREE(ctx->rtsp.aesiv); - NFREE(ctx->rtsp.fmtp); - + cleanup_rtsp(ctx, false); success = ctx->cmd_cb(RAOP_STOP); } else if (!strcmp(method, "SET_PARAMETER")) { @@ -631,18 +598,16 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) current = ((current - start) / 44100) * 1000; if (stop) stop = ((stop - start) / 44100) * 1000; else stop = -1; - LOG_INFO("[%p]: SET PARAMETER progress %u/%u %s", ctx, current, stop, p); - success = ctx->cmd_cb(RAOP_PROGRESS, current, stop); - } - - if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) { + LOG_INFO("[%p]: SET PARAMETER progress %d/%u %s", ctx, current, stop, p); + success = ctx->cmd_cb(RAOP_PROGRESS, max(current, 0), stop); + } else if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) { struct metadata_s metadata; dmap_settings settings = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, on_dmap_string, NULL, NULL }; - LOG_INFO("[%p]: received metadata"); + LOG_INFO("[%p]: received metadata", ctx); settings.ctx = &metadata; memset(&metadata, 0, sizeof(struct metadata_s)); if (!dmap_parse(&settings, body, len)) { @@ -651,6 +616,10 @@ 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 { + char *dump = kd_dump(headers); + LOG_INFO("Unhandled SET PARAMETER\n%s", dump); + free(dump); } } @@ -678,26 +647,28 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) } /*----------------------------------------------------------------------------*/ -void abort_rtsp(raop_ctx_t *ctx) { +void cleanup_rtsp(raop_ctx_t *ctx, bool abort) { // first stop RTP process if (ctx->rtp) { rtp_end(ctx->rtp); ctx->rtp = NULL; - LOG_INFO("[%p]: RTP thread aborted", ctx); - } + if (abort) LOG_INFO("[%p]: RTP thread aborted", ctx); + } if (ctx->active_remote.running) { +#ifdef WIN32 + pthread_join(ctx->active_remote.thread, NULL); + close_mDNS(ctx->active_remote.handle); +#else // need to make sure no search is on-going and reclaim task memory - ctx->active_remote.joiner = xTaskGetCurrentTaskHandle(); ctx->active_remote.running = false; - xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY); vTaskDelete(ctx->active_remote.thread); vSemaphoreDelete(ctx->active_remote.thread); heap_caps_free(ctx->active_remote.xTaskBuffer); +#endif memset(&ctx->active_remote, 0, sizeof(ctx->active_remote)); - LOG_INFO("[%p]: Remote search thread aborted", ctx); } @@ -767,7 +738,7 @@ static void* search_remote(void *args) { LOG_INFO("found remote %s %s:%hu", r->instance_name, inet_ntoa(ctx->active_remote.host), ctx->active_remote.port); } } - + mdns_query_results_free(results); } @@ -947,9 +918,8 @@ static unsigned int token_decode(const char *token) else val += pos(token[i]); } - if (marker > 2){ - return DECODE_ERROR; - } + if (marker > 2) + return DECODE_ERROR; return (marker << 24) | val; } diff --git a/components/raop/rtp.c b/components/raop/rtp.c index 08bd1ff7..5716e888 100644 --- a/components/raop/rtp.c +++ b/components/raop/rtp.c @@ -38,6 +38,7 @@ #include #include #include +#include #include "platform.h" #include "rtp.h" @@ -48,11 +49,10 @@ #ifdef WIN32 #include #include "alac_wrapper.h" -#include +#define MSG_DONTWAIT 0 #else #include "esp_pthread.h" #include "esp_system.h" -#include "assert.h" #include #include #include "alac_wrapper.h" @@ -268,25 +268,25 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, resp.cport = ctx->rtp_sockets[CONTROL].lport; resp.tport = ctx->rtp_sockets[TIMING].lport; resp.aport = ctx->rtp_sockets[DATA].lport; - - if (rc) { - ctx->running = true; + + ctx->running = true; + #ifdef WIN32 - pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx); + pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx); #else - // xTaskCreate((TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_TASK_SIZE, ctx, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1 , &ctx->thread); - ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx, - CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer ); + ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx, + CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer ); #endif - } else { + + // cleanup everything if we failed + if (!rc) { LOG_ERROR("[%p]: cannot start RTP", ctx); rtp_end(ctx); ctx = NULL; - } - - resp.ctx = ctx; - + } + + resp.ctx = ctx; return resp; } @@ -328,7 +328,7 @@ void rtp_end(rtp_t *ctx) } /*---------------------------------------------------------------------------*/ -bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime) +bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked) { bool rc = true; u32_t now = gettime_ms(); @@ -341,7 +341,7 @@ bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime) buffer_reset(ctx->audio_buffer); ctx->playing = false; ctx->flush_seqno = seqno; - pthread_mutex_unlock(&ctx->ab_mutex); + if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex); } LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime); @@ -350,8 +350,13 @@ bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime) } /*---------------------------------------------------------------------------*/ -void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) -{ +void rtp_flush_release(rtp_t *ctx) { + pthread_mutex_unlock(&ctx->ab_mutex); +} + + +/*---------------------------------------------------------------------------*/ +void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) { ctx->record.seqno = seqno; ctx->record.rtptime = rtptime; ctx->record.time = gettime_ms(); @@ -444,25 +449,23 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read); } else if (seq_order(ctx->ab_write, seqno)) { + seq_t i; + u32_t now; + // newer than expected if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) { // only get rtp latency-1 frames back (last one is seqno) LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read); ctx->ab_write = seqno - ctx->latency / ctx->frame_size; } - if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_read)) { - // if ab_read is lagging more than http latency, advance it - LOG_WARN("[%p] on hold for too long %hu (W:%hu R:%hu)", ctx, seqno - ctx->ab_read + 1, ctx->ab_write, ctx->ab_read); - ctx->ab_read = seqno - ctx->latency / ctx->frame_size + 1; - } - if (rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1)) { - seq_t i; - u32_t now = gettime_ms(); - for (i = ctx->ab_write + 1; seq_order(i, seqno); i++) { - ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size; - ctx->audio_buffer[BUFIDX(i)].last_resend = now; - } + + // need to request re-send and adjust timing of gaps + rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1); + for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) { + ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size; + ctx->audio_buffer[BUFIDX(i)].last_resend = now; } + LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read); abuf = ctx->audio_buffer + BUFIDX(seqno); ctx->ab_write = seqno; @@ -520,14 +523,21 @@ static void buffer_push_packet(rtp_t *ctx) { LOG_DEBUG("[%p]: discarded frame now:%u missed by:%d (W:%hu R:%hu)", ctx, now, now - playtime, ctx->ab_write, ctx->ab_read); ctx->discarded++; curframe->ready = 0; + } else if (playtime - now <= hold) { + if (curframe->ready) { + ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime); + curframe->ready = 0; + } else { + LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read); + ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime); + ctx->silent_frames++; + } } else if (curframe->ready) { ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime); curframe->ready = 0; - } else if (playtime - now <= hold) { - LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read); - ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime); - ctx->silent_frames++; - } else break; + } else { + break; + } ctx->ab_read++; ctx->out_frames++; @@ -572,7 +582,7 @@ static void *rtp_thread_func(void *arg) { while (ctx->running) { ssize_t plen; char type; - socklen_t rtp_client_len = sizeof(struct sockaddr_storage); + socklen_t rtp_client_len = sizeof(struct sockaddr_in); int idx = 0; char *pktp = packet; struct timeval timeout = {0, 100*1000}; @@ -585,14 +595,18 @@ static void *rtp_thread_func(void *arg) { for (i = 0; i < 3; i++) if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i; - plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, 0, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len); + plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len); if (!ntp_sent) { LOG_WARN("[%p]: NTP request not send yet", ctx); ntp_sent = rtp_request_timing(ctx); } - if (plen < 0) continue; + if (plen <= 0) { + LOG_WARN("Nothing received on a readable socket %d", plen); + continue; + } + assert(plen <= MAX_PACKET); type = packet[1] & ~0x80; @@ -638,7 +652,7 @@ static void *rtp_thread_func(void *arg) { u32_t rtp_now = ntohl(*(u32_t*)(pktp+16)); u16_t flags = ntohs(*(u16_t*)(pktp+2)); u32_t remote_gap = NTP2MS(remote - ctx->timing.remote); - + // something is wrong and if we are supposed to be NTP synced, better ask for re-sync if (remote_gap > 10000) { if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx); @@ -711,6 +725,11 @@ static void *rtp_thread_func(void *arg) { break; } + + default: { + LOG_WARN("Unknown packet received %x", (int) type); + break; + } } } @@ -752,7 +771,7 @@ static bool rtp_request_timing(rtp_t *ctx) { host.sin_port = htons(ctx->rtp_sockets[TIMING].rport); - if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), 0, (struct sockaddr*) &host, sizeof(host))) { + if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &host, sizeof(host))) { LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno)); } @@ -778,7 +797,7 @@ static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) { ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport); - if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), 0, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) { + if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) { LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno)); } diff --git a/components/raop/rtp.h b/components/raop/rtp.h index 554690ad..ebf734f5 100644 --- a/components/raop/rtp.h +++ b/components/raop/rtp.h @@ -14,7 +14,8 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, short unsigned pCtrlPort, short unsigned pTimingPort, raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb); void rtp_end(struct rtp_s *ctx); -bool rtp_flush(struct rtp_s *ctx, unsigned short seqno, unsigned rtptime); +bool rtp_flush(struct rtp_s *ctx, unsigned short seqno, unsigned rtptime, bool exit_locked); +void rtp_flush_release(struct rtp_s *ctx); void rtp_record(struct rtp_s *ctx, unsigned short seqno, unsigned rtptime); void rtp_metadata(struct rtp_s *ctx, struct metadata_s *metadata); diff --git a/components/raop/util.c b/components/raop/util.c index 2b7f3a17..13744409 100644 --- a/components/raop/util.c +++ b/components/raop/util.c @@ -401,7 +401,7 @@ bool http_parse(int sock, char *method, key_data_t *rkd, char **body, int *len) } if (*len) { - int size = 0; + int size = 0; *body = malloc(*len + 1); while (*body && size < *len) { diff --git a/components/services/CMakeLists.txt b/components/services/CMakeLists.txt index db23404d..58d8f2d9 100644 --- a/components/services/CMakeLists.txt +++ b/components/services/CMakeLists.txt @@ -1,6 +1,6 @@ -idf_component_register(SRCS "accessors.c" "led.c" "audio_controls.c" "battery.c" "buttons.c" "services.c" "monitor.c" "rotary_encoder.c" - INCLUDE_DIRS . ../tools/ - REQUIRES json platform_config +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES json tools platform_config ) diff --git a/components/services/monitor.c b/components/services/monitor.c index a0ab4c4a..e50c1dd0 100644 --- a/components/services/monitor.c +++ b/components/services/monitor.c @@ -23,6 +23,7 @@ #include "accessors.h" #define MONITOR_TIMER (10*1000) +#define SCRATCH_SIZE 256 static const char *TAG = "monitor"; @@ -54,7 +55,7 @@ static void task_stats( void ) { current.tasks = malloc( current.n * sizeof( TaskStatus_t ) ); current.n = uxTaskGetSystemState( current.tasks, current.n, ¤t.total ); - static EXT_RAM_ATTR char scratch[128+1]; + static EXT_RAM_ATTR char scratch[SCRATCH_SIZE]; *scratch = '\0'; #ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS @@ -63,7 +64,8 @@ static void task_stats( void ) { for(int i = 0, n = 0; i < current.n; i++ ) { for (int j = 0; j < previous.n; j++) { if (current.tasks[i].xTaskNumber == previous.tasks[j].xTaskNumber) { - n += sprintf(scratch + n, "%16s %2u%% s:%5u", current.tasks[i].pcTaskName, + n += snprintf(scratch + n, SCRATCH_SIZE - n, "%16s (%u) %2u%% s:%5u", current.tasks[i].pcTaskName, + current.tasks[i].eCurrentState, 100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed, current.tasks[i].usStackHighWaterMark); if (i % 3 == 2 || i == current.n - 1) { @@ -72,7 +74,7 @@ static void task_stats( void ) { } break; } - } + } } #else for (int i = 0, n = 0; i < current.n; i ++) { diff --git a/components/squeezelite-ota/CMakeLists.txt b/components/squeezelite-ota/CMakeLists.txt index 1b495278..bbfeb209 100644 --- a/components/squeezelite-ota/CMakeLists.txt +++ b/components/squeezelite-ota/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "squeezelite-ota.c" +idf_component_register(SRC_DIRS . INCLUDE_DIRS . REQUIRES console spi_flash console freertos platform_console esp_https_ota ) diff --git a/components/squeezelite/CMakeLists.txt b/components/squeezelite/CMakeLists.txt index 7aa0ee5e..b03655be 100644 --- a/components/squeezelite/CMakeLists.txt +++ b/components/squeezelite/CMakeLists.txt @@ -1,36 +1,8 @@ -idf_component_register(SRCS "alac.c" - "buffer.c" - "controls.c" - "decode_external.c" - "decode.c" - "display.c" - "embedded.c" - "flac.c" - "helix-aac.c" - "mad.c" - "main.c" - "mpg.c" - "opus.c" - "output_bt.c" - "output_embedded.c" - "output_i2s.c" - "output_pack.c" - "output_visu.c" - "output.c" - "pcm.c" - "process.c" - "resample.c" - "resample16.c" - "slimproto.c" - "stream.c" - "utils.c" - "vorbis.c" - "a1s/ac101.c" - "tas57xx/dac_57xx.c" - "external/dac_external.c" + +idf_component_register( SRC_DIRS . external a1s tas57xx INCLUDE_DIRS . a1s - REQUIRES newlib + PRIV_REQUIRES newlib esp_common esp-dsp display @@ -58,4 +30,8 @@ set_source_files_properties(flac.c ) add_definitions(-DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4) -add_compile_options (-O3) +add_compile_options (-O3 ) + + + + \ No newline at end of file diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index 20785666..06341eb4 100644 --- a/components/squeezelite/decode_external.c +++ b/components/squeezelite/decode_external.c @@ -206,6 +206,10 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args) // in how many ms will the most recent block play ms = ((u64_t) ((_buf_used(outputbuf) - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 1000) / RAOP_SAMPLE_RATE - (now - output.updated); raop_sync.error[raop_sync.idx] = (raop_sync.playtime - now) - ms; + if (abs(raop_sync.error[raop_sync.idx]) > 1000) { + LOG_INFO("erroneous sync point %d", raop_sync.error[raop_sync.idx]); + raop_sync.error[raop_sync.idx] = raop_sync.error[raop_sync.idx] > 0 ? 1000 : -1000; + } sync_nb = SYNC_NB; LOG_DEBUG("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, raop_sync.error[raop_sync.idx]); LOG_DEBUG("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process); diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index f7c9b385..544bdf13 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -100,7 +100,7 @@ static struct { #define SB_HEIGHT 32 // lenght are number of frames, i.e. 2 channels of 16 bits -#define FFT_LEN_BIT 6 +#define FFT_LEN_BIT 7 #define FFT_LEN (1 << FFT_LEN_BIT) #define RMS_LEN_BIT 6 #define RMS_LEN (1 << RMS_LEN_BIT) @@ -115,7 +115,7 @@ static struct scroller_s { u16_t mode; s16_t by; // scroller management & sharing between grfg and scrolling task - bool active, first; + bool active, first, overflow; int scrolled; struct { u8_t *frame; @@ -151,7 +151,7 @@ static EXT_RAM_ATTR struct { #define ANIM_SCREEN_2 0x08 static u8_t ANIC_resp = ANIM_NONE; -static u8_t SETD_width; +static uint16_t SETD_width; #define SCROLL_STACK_SIZE (3*1024) #define LINELEN 40 @@ -234,7 +234,7 @@ bool sb_display_init(void) { displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer); // size scroller (width + current screen) - scroller.scroll.max = (displayer.width * displayer.height / 8) * (10 + 1); + scroller.scroll.max = (displayer.width * displayer.height / 8) * (15 + 1); scroller.scroll.frame = malloc(scroller.scroll.max); scroller.back.frame = malloc(displayer.width * displayer.height / 8); scroller.frame = malloc(displayer.width * displayer.height / 8); @@ -316,8 +316,9 @@ static void send_server(void) { pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2 pkt_header.length = htonl(sizeof(pkt_header) + 2 - 8); + SETD_width = htons(SETD_width); send_packet((u8_t *)&pkt_header, sizeof(pkt_header)); - send_packet(&SETD_width, 2); + send_packet((uint8_t*) &SETD_width, 2); SETD_width = 0; } @@ -559,6 +560,7 @@ static void grfs_handler(u8_t *data, int len) { scroller.mode = htons(pkt->mode); scroller.scroll.width = htons(pkt->width); scroller.first = true; + scroller.overflow = false; // background excludes space taken by visu (if any) scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0); @@ -576,13 +578,14 @@ static void grfs_handler(u8_t *data, int len) { } // copy scroll frame data (no semaphore needed) - if (scroller.scroll.size + size < scroller.scroll.max) { + if (scroller.scroll.size + size < scroller.scroll.max && !scroller.overflow) { memcpy(scroller.scroll.frame + offset, data + sizeof(struct grfs_packet), size); scroller.scroll.size = offset + size; - LOG_INFO("scroller current size %u", scroller.scroll.size); + LOG_INFO("scroller current size %u (w:%u)", scroller.scroll.size, scroller.scroll.width); } else { - LOG_INFO("scroller too larger %u/%u/%u", scroller.scroll.size + size, scroller.scroll.max, scroller.scroll.width); - scroller.scroll.width = scroller.scroll.size / (displayer.height / 8); + LOG_INFO("scroller too large %u/%u (w:%u)", scroller.scroll.size + size, scroller.scroll.max, scroller.scroll.width); + scroller.scroll.width = scroller.scroll.size / (displayer.height / 8) - scroller.back.width; + scroller.overflow = true; } } @@ -736,10 +739,10 @@ static void visu_update(void) { */ void spectrum_limits(int min, int n, int pos) { if (n / 2) { - int step = ((DISPLAY_BW - min) * visu.spectrum_scale * 2) / n; + int step = ((DISPLAY_BW - min) * visu.spectrum_scale) / (n/2); visu.bars[pos].limit = min + step; for (int i = 1; i < n/2; i++) visu.bars[pos+i].limit = visu.bars[pos+i-1].limit + step; - spectrum_limits(visu.bars[pos + n/2 - 1].limit, n/2, pos + n/2); + spectrum_limits(visu.bars[pos + n/2 - 1].limit, n - n/2, pos + n/2); } else { visu.bars[pos].limit = DISPLAY_BW; } @@ -860,7 +863,7 @@ static void displayer_task(void *args) { // by default go for the long sleep, will change below if required scroller.wake = LONG_WAKE; - // do we have more to scroll (scroll.width is the last column from which we havea full zone) + // do we have more to scroll (scroll.width is the last column from which we have a full zone) if (scroller.by > 0 ? (scroller.scrolled <= scroller.scroll.width) : (scroller.scrolled >= 0)) { memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8); for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i]; diff --git a/components/squeezelite/embedded.h b/components/squeezelite/embedded.h index 71127406..34ebfcab 100644 --- a/components/squeezelite/embedded.h +++ b/components/squeezelite/embedded.h @@ -6,6 +6,7 @@ /* must provide - mutex_create_p - pthread_create_name + - register_xxx (see below) - stack size - s16_t, s32_t, s64_t and u64_t - PLAYER_ID / custom_player_id @@ -16,6 +17,11 @@ - EXT_BSS recommended to add platform specific include(s) here */ + +typedef int16_t s16_t; +typedef int32_t s32_t; +typedef int64_t s64_t; +typedef unsigned long long u64_t; #ifndef PTHREAD_STACK_MIN #define PTHREAD_STACK_MIN 256 @@ -29,18 +35,17 @@ #define OUTPUT_THREAD_STACK_SIZE 6 * 1024 #define IR_THREAD_STACK_SIZE 6 * 1024 +// number of 5s times search for a server will happen beforee slimproto exits (0 = no limit) +#define MAX_SERVER_RETRIES 5 + // or can be as simple as #define PLAYER_ID 100 -#define PLAYER_ID custom_player_id; +#define PLAYER_ID custom_player_id extern u8_t custom_player_id; #define BASE_CAP "Model=squeezeesp32,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION +// to force some special buffer attribute #define EXT_BSS __attribute__((section(".ext_ram.bss"))) -typedef int16_t s16_t; -typedef int32_t s32_t; -typedef int64_t s64_t; -typedef unsigned long long u64_t; - // all exit() calls are made from main thread (or a function called in main thread) #define exit(code) { int ret = code; pthread_exit(&ret); } #define gettime_ms _gettime_ms_ diff --git a/components/squeezelite/slimproto.c b/components/squeezelite/slimproto.c index af7f7bde..3c55ec43 100644 --- a/components/squeezelite/slimproto.c +++ b/components/squeezelite/slimproto.c @@ -773,7 +773,7 @@ void wake_controller(void) { wake_signal(wake_e); } -in_addr_t discover_server(char *default_server) { +in_addr_t discover_server(char *default_server, int max) { struct sockaddr_in d; struct sockaddr_in s; char buf[32], port_d[] = "JSON", clip_d[] = "CLIP"; @@ -827,7 +827,7 @@ in_addr_t discover_server(char *default_server) { server_addr(default_server, &s.sin_addr.s_addr, &port); } - } while (s.sin_addr.s_addr == 0 && running); + } while (s.sin_addr.s_addr == 0 && running && (!max || --max)); closesocket(disc_sock); @@ -858,7 +858,7 @@ void slimproto(log_level level, char *server, u8_t mac[6], const char *name, con } if (!slimproto_ip) { - slimproto_ip = discover_server(server); + slimproto_ip = discover_server(server, 0); } if (!slimproto_port) { @@ -937,10 +937,18 @@ void slimproto(log_level level, char *server, u8_t mac[6], const char *name, con sleep(5); } - // rediscover server if it was not set at startup +#if EMBEDDED + // in embedded we give up after a while no matter what + if (++failed_connect > 5 && !server) { + slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL, MAX_SERVER_RETRIES); + if (!slimproto_ip) return; + } else if (MAX_SERVER_RETRIES && failed_connect > 5 * MAX_SERVER_RETRIES) return; +#else + // rediscover server if it was not set at startup or exit if (!server && ++failed_connect > 5) { - slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL); - } + slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL, 0); + } +#endif } else { diff --git a/components/telnet/CMakeLists.txt b/components/telnet/CMakeLists.txt index 37afd1c9..5ec8b9a4 100644 --- a/components/telnet/CMakeLists.txt +++ b/components/telnet/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "telnet.c" "libtelnet/libtelnet.c" +idf_component_register(SRC_DIRS . libtelnet INCLUDE_DIRS . libtelnet REQUIRES platform_config services ) diff --git a/components/tools/CMakeLists.txt b/components/tools/CMakeLists.txt index 47356349..283c9f8c 100644 --- a/components/tools/CMakeLists.txt +++ b/components/tools/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "utf8.c" +idf_component_register(SRC_DIRS . REQUIRES esp_common pthread INCLUDE_DIRS . ) \ No newline at end of file diff --git a/components/wifi-manager/CMakeLists.txt b/components/wifi-manager/CMakeLists.txt index 51fc521b..5760b8aa 100644 --- a/components/wifi-manager/CMakeLists.txt +++ b/components/wifi-manager/CMakeLists.txt @@ -1,7 +1,7 @@ -idf_component_register(SRCS "dns_server.c" "http_server.c" "wifi_manager.c" - INCLUDE_DIRS . - REQUIRES esp_common newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console squeezelite-ota - EMBED_FILES style.css code.js index.html bootstrap.min.css.gz jquery.min.js.gz popper.min.js.gz bootstrap.min.js.gz +idf_component_register( SRC_DIRS . + INCLUDE_DIRS . + REQUIRES esp_common newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console squeezelite-ota + EMBED_FILES style.css code.js index.html bootstrap.min.css.gz jquery.min.js.gz popper.min.js.gz bootstrap.min.js.gz ) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b9ce81b1..a08e6fc2 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,6 @@ -idf_component_register(SRCS "esp_app_main.c" +idf_component_register(SRC_DIRS . REQUIRES esp_common pthread squeezelite-ota platform_console telnet INCLUDE_DIRS . EMBED_FILES ../server_certs/github.pem - ) \ No newline at end of file + ) + \ No newline at end of file diff --git a/main/esp_app_main.c b/main/esp_app_main.c index b343a41c..8be23725 100644 --- a/main/esp_app_main.c +++ b/main/esp_app_main.c @@ -73,6 +73,18 @@ extern void display_init(char *welcome); /* brief this is an exemple of a callback that you can setup in your own app to get notified of wifi manager event */ void cb_connection_got_ip(void *pvParameter){ + static ip4_addr_t ip; + tcpip_adapter_ip_info_t ipInfo; + + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + if (ip.addr && ipInfo.ip.addr != ip.addr) { + ESP_LOGW(TAG, "IP change, need to reboot"); + if(!wait_for_commit()){ + ESP_LOGW(TAG,"Unable to commit configuration. "); + } + esp_restart(); + } + ip.addr = ipInfo.ip.addr; ESP_LOGI(TAG, "I have a connection!"); xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); bWifiConnected=true; diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index f9688f9a..871345cc 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 70909e7d..225c907b 100644 --- a/plugin/SqueezeESP32/Graphics.pm +++ b/plugin/SqueezeESP32/Graphics.pm @@ -15,90 +15,51 @@ my $VISUALIZER_VUMETER = 1; my $VISUALIZER_SPECTRUM_ANALYZER = 2; my $VISUALIZER_WAVEFORM = 3; -my $width = $prefs->get('width') || 128; -my $spectrum_scale = $prefs->get('spectrum_scale') || 50; +{ + #__PACKAGE__->mk_accessor('array', 'modes'); + __PACKAGE__->mk_accessor('rw', 'modes'); + __PACKAGE__->mk_accessor('rw', qw(vfdmodel)); +} -my @modes = ( - # mode 0 - { desc => ['BLANK'], - bar => 0, secs => 0, width => $width, - params => [$VISUALIZER_NONE] }, - # mode 1 - { desc => ['PROGRESS_BAR'], - bar => 1, secs => 0, width => $width, - params => [$VISUALIZER_NONE] }, - # mode 2 - { desc => ['ELAPSED'], - bar => 0, secs => 1, width => $width, - params => [$VISUALIZER_NONE] }, - # mode 3 - { desc => ['ELAPSED', 'AND', 'PROGRESS_BAR'], - bar => 1, secs => 1, width => $width, - params => [$VISUALIZER_NONE] }, - # mode 4 - { desc => ['REMAINING'], - bar => 0, secs => -1, width => $width, - params => [$VISUALIZER_NONE] }, - # mode 5 - { desc => ['CLOCK'], - bar => 0, secs => 0, width => $width, clock => 1, - params => [$VISUALIZER_NONE] }, - # mode 6 - { desc => ['SETUP_SHOWBUFFERFULLNESS'], - bar => 0, secs => 0, width => $width, fullness => 1, - params => [$VISUALIZER_NONE] }, - # mode 7 - { desc => ['VISUALIZER_VUMETER_SMALL'], - bar => 0, secs => 0, width => $width, _width => -20, - # extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), bars, left space) - params => [$VISUALIZER_VUMETER, 20, 32, -20, 0, 2] }, - # mode 8 - { desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'], - bar => 0, secs => 0, width => $width, _width => -32, - # extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), bars, left space) - params => [$VISUALIZER_SPECTRUM_ANALYZER, 32, 32, -32, 0, 2, 6, $spectrum_scale] }, - # mode 9 - { desc => ['VISUALIZER_VUMETER'], - bar => 0, secs => 0, width => $width, - params => [$VISUALIZER_VUMETER] }, - # mode 10 - { desc => ['VISUALIZER_SPECTRUM_ANALYZER'], - bar => 0, secs => 0, width => $width, - # extra parameters (bars) - params => [$VISUALIZER_SPECTRUM_ANALYZER, 16, $spectrum_scale] }, - # mode 11 - { desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'], - bar => 0, secs => 1, width => $width, - params => [$VISUALIZER_VUMETER] }, - # mode 12 - { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'], - bar => 0, secs => 1, width => $width, - # extra parameters (bars) - params => [$VISUALIZER_SPECTRUM_ANALYZER, 16, $spectrum_scale] }, - # mode 13 - { desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'], - bar => 0, secs => -1, width => $width, - params => [$VISUALIZER_VUMETER] }, - # mode 14 - { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'], - bar => 0, secs => -1, width => $width, - # extra parameters (bars) - params => [$VISUALIZER_SPECTRUM_ANALYZER, 16, $spectrum_scale] }, - -); +sub new { + my $class = shift; + my $client = shift; + + my $display = $class->SUPER::new($client); + my $cprefs = $prefs->client($client); + + $cprefs->init( { + width => 128, + small_VU => 15, + spectrum => { scale => 25, + small => { size => 25, band => 5.33 }, + full => { band => 8 }, + }, + } + ); + + $display->init_accessor( + modes => $display->build_modes, + vfdmodel => 'graphic-x32', # doesn't matter much + ); + + return $display; +} +=comment sub modes { return \@modes; } +=cut sub nmodes { - return $#modes; + return scalar($#{shift->modes()}); } sub displayWidth { my $display = shift; my $client = $display->client; - + # if we're showing the always-on visualizer & the current buttonmode # hasn't overridden, then use the playing display mode to index # into the display width, otherwise, it's fullscreen. @@ -116,12 +77,6 @@ sub displayWidth { } } -# I don't think LMS renderer handles properly screens other than 32 pixels. It -# seems that all we get is a 32 pixel-tall data with anything else padded to 0 -# i.e. if we try 64 pixels height, bytes 0..3 and 4..7 will contains the same -# pattern than the 32 pixels version, where one would have expected bytes 4..7 -# to be empty - sub brightnessMap { return (65535, 10, 50, 100, 200); } @@ -132,12 +87,92 @@ sub bytesPerColumn { } =cut +# I don't think LMS renderer handles properly screens other than 32 pixels. It +# seems that all we get is a 32 pixel-tall data with anything else padded to 0 +# i.e. if we try 64 pixels height, bytes 0..3 and 4..7 will contains the same +# pattern than the 32 pixels version, where one would have expected bytes 4..7 +# to be empty sub displayHeight { return 32; } -sub vfdmodel { - return 'graphic-'.$width.'x32'; -} +sub build_modes { + my $client = shift->client; + my $cprefs = $prefs->client($client); + + my $width = shift || $cprefs->get('width') || 128; + my $small_VU = $cprefs->get('small_VU'); + my $spectrum = $cprefs->get('spectrum'); + + my @modes = ( + # mode 0 + { desc => ['BLANK'], + bar => 0, secs => 0, width => $width, + params => [$VISUALIZER_NONE] }, + # mode 1 + { desc => ['PROGRESS_BAR'], + bar => 1, secs => 0, width => $width, + params => [$VISUALIZER_NONE] }, + # mode 2 + { desc => ['ELAPSED'], + bar => 0, secs => 1, width => $width, + params => [$VISUALIZER_NONE] }, + # mode 3 + { desc => ['ELAPSED', 'AND', 'PROGRESS_BAR'], + bar => 1, secs => 1, width => $width, + params => [$VISUALIZER_NONE] }, + # mode 4 + { desc => ['REMAINING'], + bar => 0, secs => -1, width => $width, + params => [$VISUALIZER_NONE] }, + # mode 5 + { desc => ['CLOCK'], + bar => 0, secs => 0, width => $width, clock => 1, + params => [$VISUALIZER_NONE] }, + # mode 6 + { desc => ['SETUP_SHOWBUFFERFULLNESS'], + bar => 0, secs => 0, width => $width, fullness => 1, + params => [$VISUALIZER_NONE] }, + # mode 7 + { desc => ['VISUALIZER_VUMETER_SMALL'], + bar => 0, secs => 0, width => $width, _width => int -($small_VU*$width/100), + # extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space) + params => [$VISUALIZER_VUMETER, int ($small_VU*$width/100), 32, int -($small_VU*$width/100), 0, 2] }, + # mode 8 + { desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'], + bar => 0, secs => 0, width => $width, _width => int -($spectrum->{small}->{size}*$width/100), + # extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space, bars) + params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($spectrum->{small}->{size}*$width/100), 32, int -($spectrum->{small}->{size}*$width/100), 0, 2, int ($spectrum->{small}->{size}/100*$width/$spectrum->{small}->{band}), $spectrum->{scale}] }, + # mode 9 + { desc => ['VISUALIZER_VUMETER'], + bar => 0, secs => 0, width => $width, + params => [$VISUALIZER_VUMETER] }, + # mode 10 + { desc => ['VISUALIZER_SPECTRUM_ANALYZER'], + bar => 0, secs => 0, width => $width, + # extra parameters (bars) + params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] }, + # mode 11 + { desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'], + bar => 0, secs => 1, width => $width, + params => [$VISUALIZER_VUMETER] }, + # mode 12 + { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'], + bar => 0, secs => 1, width => $width, + # extra parameters (bars) + params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] }, + # mode 13 + { desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'], + bar => 0, secs => -1, width => $width, + params => [$VISUALIZER_VUMETER] }, + # mode 14 + { desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'], + bar => 0, secs => -1, width => $width, + # extra parameters (bars) + params => [$VISUALIZER_SPECTRUM_ANALYZER, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] }, + ); + + return \@modes; +} 1; \ No newline at end of file diff --git a/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html b/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html new file mode 100644 index 00000000..17589674 --- /dev/null +++ b/plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html @@ -0,0 +1,28 @@ +[% PROCESS settings/header.html %] + + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %] + + [% prefs.pref_width %] + [% END %] + + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_VU" desc="PLUGIN_SQUEEZEESP32_SMALL_VU_DESC" %] + + [% END %] + + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE" desc="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC" %] + + [% END %] + + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM" desc="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC" %] + [% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE" | string %]  + + [% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND" | string %]  + + [% END %] + + [% WRAPPER setting title="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND" desc="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC" %] + + [% END %] + + +[% PROCESS settings/footer.html %] diff --git a/plugin/SqueezeESP32/Player.pm b/plugin/SqueezeESP32/Player.pm index fe6b208c..946eb303 100644 --- a/plugin/SqueezeESP32/Player.pm +++ b/plugin/SqueezeESP32/Player.pm @@ -20,11 +20,13 @@ sub playerSettingsFrame { my $value; my $id = unpack('C', $$data_ref); - + # New SETD command 0xfe for display width if ($id == 0xfe) { - $value = (unpack('CC', $$data_ref))[1]; + $value = (unpack('Cn', $$data_ref))[1]; if ($value > 100 && $value < 400) { + $prefs->client($client)->set('width', $value); + $client->display->modes($client->display->build_modes($value)); $client->display->widthOverride(1, $value); $client->update; } diff --git a/plugin/SqueezeESP32/PlayerSettings.pm b/plugin/SqueezeESP32/PlayerSettings.pm new file mode 100644 index 00000000..f4fc24b0 --- /dev/null +++ b/plugin/SqueezeESP32/PlayerSettings.pm @@ -0,0 +1,66 @@ +package Plugins::SqueezeESP32::PlayerSettings; + +use strict; +use base qw(Slim::Web::Settings); +use List::Util qw(first); + +use Slim::Utils::Log; +use Slim::Utils::Prefs; + +my $sprefs = preferences('server'); +my $prefs = preferences('plugin.squeezeesp32'); +my $log = logger('plugin.squeezeesp32'); + +sub name { + return Slim::Web::HTTP::CSRF->protectName('PLUGIN_SQUEEZEESP32_PLAYERSETTINGS'); +} + +sub needsClient { + return 1; +} + +sub validFor { + my ($class, $client) = @_; + return $client->model eq 'squeezeesp32'; +} + +sub page { + return Slim::Web::HTTP::CSRF->protectURI('plugins/SqueezeESP32/settings/player.html'); +} + +sub prefs { + my ($class, $client) = @_; + my @prefs = qw(width small_VU spectrum); + return ($prefs->client($client), @prefs); +} + +sub handler { + my ($class, $client, $paramRef) = @_; + + my ($cprefs, @prefs) = $class->prefs($client); + + if ($paramRef->{'saveSettings'}) { + $cprefs->set('small_VU', $paramRef->{'pref_small_VU'}); + my $spectrum = { scale => $paramRef->{'pref_spectrum_scale'}, + small => { size => $paramRef->{'pref_spectrum_small_size'}, + band => $paramRef->{'pref_spectrum_small_band'} }, + full => { band => $paramRef->{'pref_spectrum_full_band'} }, + }; + $cprefs->set('spectrum', $spectrum); + $client->display->modes($client->display->build_modes); + $client->display->update; + } + + # as there is nothing captured, we need to re-set these variables + $paramRef->{'pref_width'} = $cprefs->get('width'); + + # here I don't know why you need to set again spectrum which is a reference + # to a hash. Using $paramRef->{prefs} does not work either. It seems that + # soem are copies of value, some are references, can't figure out.This whole + # logic of "Settings" is beyond me and I really hate it + $paramRef->{'pref_spectrum'} = $cprefs->get('spectrum'); + + return $class->SUPER::handler($client, $paramRef); +} + +1; \ No newline at end of file diff --git a/plugin/SqueezeESP32/Plugin.pm b/plugin/SqueezeESP32/Plugin.pm index e39979bf..5078f66c 100644 --- a/plugin/SqueezeESP32/Plugin.pm +++ b/plugin/SqueezeESP32/Plugin.pm @@ -8,11 +8,6 @@ use Slim::Utils::Log; my $prefs = preferences('plugin.squeezeesp32'); -$prefs->init({ - width => 128, - spectrum_scale => 50, -}); - my $log = Slim::Utils::Log->addLogCategory({ 'category' => 'plugin.squeezeesp32', 'defaultLevel' => 'INFO', @@ -23,10 +18,13 @@ sub initPlugin { my $class = shift; if ( main::WEBUI ) { - require Plugins::SqueezeESP32::Settings; - Plugins::SqueezeESP32::Settings->new; + require Plugins::SqueezeESP32::PlayerSettings; + Plugins::SqueezeESP32::PlayerSettings->new; + + # require Plugins::SqueezeESP32::Settings; + # Plugins::SqueezeESP32::Settings->new; } - + $class->SUPER::initPlugin(@_); Slim::Networking::Slimproto::addPlayerClass($class, 100, 'squeezeesp32', { client => 'Plugins::SqueezeESP32::Player', display => 'Plugins::SqueezeESP32::Graphics' }); $log->info("Added class 100 for SqueezeESP32"); diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index c8f6edc7..338f4283 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.12 + 0.31 Philippe diff --git a/plugin/SqueezeESP32/strings.txt b/plugin/SqueezeESP32/strings.txt index d00ff9b5..1dd9e492 100644 --- a/plugin/SqueezeESP32/strings.txt +++ b/plugin/SqueezeESP32/strings.txt @@ -13,13 +13,43 @@ PLUGIN_SQUEEZEESP32_BANNER_TEXT PLUGIN_SQUEEZEESP32_DESC EN Adds a new player id (100) to enable display with SqueezeESP32 +PLUGIN_SQUEEZEESP32_PLAYERSETTINGS + EN Display (ESP32) + PLUGIN_SQUEEZEESP32_WIDTH EN Screen width + +PLUGIN_SQUEEZEESP32_WIDTH_DESC + EN Width of the display in pixel as reported by the player + +PLUGIN_SQUEEZEESP32_SMALL_VU + EN Small VU size + +PLUGIN_SQUEEZEESP32_SMALL_VU_DESC + EN % of the display used for small VU (right-justified) PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE - EN Spectrum scale - + EN Spectrum scaling + PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC - EN Sets the scale factor % of spectrum visualizer by halves for better representation. - EN For example, 50 means that 50% of spectrum is displayed in 1/2 of the screen, so it's linear... + EN % of Spectrum displayed in first half of the screen. For example, 50 means that 50% of spectrum is displayed in 1/2 of the screen EN But 25 means that only 25% of spectrum is displayed in 1/2 of the screen, so it's a sort of log + +PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM + EN Small spectrum options + +PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC + EN Size: % of the screen used by small spectrum + EN
Band factor: number of bands is the width of the spectrum screen divided by this factor + +PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE + EN Size + +PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND + EN Band factor + +PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND + EN Full spectrum band factor + +PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC + EN The number of bands is the width of the screen divided by this factor diff --git a/plugin/repo.xml b/plugin/repo.xml index 51d66d5c..45a2e536 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,10 +1,10 @@ - + https://github.com/sle118/squeezelite-esp32 Philippe - 3e60650efdff28cd0da9a7e9d0ddccf3a68d350d + 6dc35a0f9f9b287d205f7532cbb642b08407a284 philippe_44@outlook.com SqueezeESP32 additional player id (100) http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip