mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-06 11:36:59 +03:00
Merge remote-tracking branch 'origin/master' into master-cmake
Conflicts: components/raop/raop.c components/raop/rtp.c main/cmd_squeezelite.c
This commit is contained in:
@@ -46,3 +46,10 @@ endif()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# add_custom_target(size
|
||||
# DEPENDS ${project_elf}
|
||||
# COMMAND ${idf_size} ${mapfile}
|
||||
# )
|
||||
@@ -64,7 +64,7 @@ SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,speed=<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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -31,3 +8,10 @@ set_source_files_properties(display.c
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
-Wno-format-overflow
|
||||
)
|
||||
|
||||
#target_link_libraries(${COMPONENT_LIB} PUBLIC
|
||||
# -Wl,--whole-archive
|
||||
# $<TARGET_PROPERTY,INTERFACE_LINK_LIBRARIES>
|
||||
# -Wl,--no-whole-archive
|
||||
#)
|
||||
|
||||
|
||||
@@ -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,8 +137,6 @@ 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) {
|
||||
@@ -153,6 +145,9 @@ struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
*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;
|
||||
|
||||
@@ -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
|
||||
@@ -107,6 +115,10 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
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 );
|
||||
Device->WriteCommand( Device, ((Device->Height == 64 ? 1 : 0) << 4) | (0 < 5) );
|
||||
@@ -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;
|
||||
|
||||
@@ -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,13 +176,13 @@ 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++) {
|
||||
@@ -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++];
|
||||
// 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);
|
||||
|
||||
// Dirty is set for us
|
||||
// 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,48 +247,30 @@ 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
|
||||
Private->Shadowbuffer = malloc( Device->FramebufferSize );
|
||||
memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
|
||||
#else
|
||||
#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
|
||||
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
|
||||
Device->Framebuffer = calloc( 1, Device->FramebufferSize );
|
||||
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "SSD1326/7 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
|
||||
@@ -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;
|
||||
((struct PrivateSpace*) Device->Private)->Model = Model;
|
||||
|
||||
sscanf(Driver, "%*[^:]:%c", &Device->Depth);
|
||||
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;
|
||||
}
|
||||
|
||||
252
components/display/SSD1675.c
Normal file
252
components/display/SSD1675.c
Normal file
@@ -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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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++) {
|
||||
@@ -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 ); }
|
||||
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 ); }
|
||||
@@ -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;
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -83,7 +87,8 @@ struct GDS_Device {
|
||||
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 ) {
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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,6 +86,9 @@ 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;
|
||||
@@ -94,7 +97,7 @@ void display_init(char *welcome) {
|
||||
|
||||
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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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 )
|
||||
@@ -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;i<thread_parms.argc; i++){
|
||||
@@ -73,6 +70,11 @@ static void * squeezelite_thread(){
|
||||
ESP_LOGV(TAG ,"Freeing argv pointer");
|
||||
free(thread_parms.argv);
|
||||
isRunning=false;
|
||||
ESP_LOGE(TAG, "Exited from squeezelite thread, something's wrong ... rebooting");
|
||||
if(!wait_for_commit()){
|
||||
ESP_LOGW(TAG,"Unable to commit configuration. ");
|
||||
}
|
||||
esp_restart();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -87,8 +89,8 @@ static int launchsqueezelite(int argc, char **argv)
|
||||
ESP_LOGV(TAG,"Saving args in thread structure");
|
||||
|
||||
thread_parms.argc=0;
|
||||
thread_parms.argv = malloc(sizeof(char**)*(argc+ADDITIONAL_SQUEEZELILTE_ARGS));
|
||||
memset(thread_parms.argv,'\0',sizeof(char**)*(argc+ADDITIONAL_SQUEEZELILTE_ARGS));
|
||||
thread_parms.argv = malloc(sizeof(char**)*(argc+ADDITIONAL_SQUEEZELITE_ARGS));
|
||||
memset(thread_parms.argv,'\0',sizeof(char**)*(argc+ADDITIONAL_SQUEEZELITE_ARGS));
|
||||
|
||||
for(int i=0;i<argc;i++){
|
||||
ESP_LOGD(TAG ,"assigning parm %u : %s",i,argv[i]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
idf_component_register(SRCS dmap_parser.c raop_sink.c raop.c rtp.c util.c
|
||||
idf_component_register(SRC_DIRS .
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES newlib platform_config services codecs tools display
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
#include "log_util.h"
|
||||
|
||||
#define RTSP_STACK_SIZE (8*1024)
|
||||
#define SEARCH_STACK_SIZE (2*1048)
|
||||
#define SEARCH_STACK_SIZE (3*1048)
|
||||
|
||||
typedef struct raop_ctx_s {
|
||||
#ifdef WIN32
|
||||
@@ -57,9 +57,9 @@ typedef struct raop_ctx_s {
|
||||
struct in_addr peer; // IP of the iDevice (airplay sender)
|
||||
bool running;
|
||||
#ifdef WIN32
|
||||
pthread_t thread, search_thread;
|
||||
pthread_t thread;
|
||||
#else
|
||||
TaskHandle_t thread, search_thread, joiner;
|
||||
TaskHandle_t thread, joiner;
|
||||
StaticTask_t *xTaskBuffer;
|
||||
StackType_t xStack[RTSP_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
#endif
|
||||
@@ -81,11 +81,12 @@ typedef struct raop_ctx_s {
|
||||
char DACPid[32], id[32];
|
||||
struct in_addr host;
|
||||
u16_t port;
|
||||
bool running;
|
||||
#ifdef WIN32
|
||||
struct mDNShandle_s *handle;
|
||||
pthread_t thread;
|
||||
#else
|
||||
bool running;
|
||||
TaskHandle_t thread, joiner;
|
||||
TaskHandle_t thread;
|
||||
StaticTask_t *xTaskBuffer;
|
||||
StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
|
||||
SemaphoreHandle_t destroy_mutex;
|
||||
@@ -99,7 +100,7 @@ extern log_level raop_loglevel;
|
||||
static log_level *loglevel = &raop_loglevel;
|
||||
|
||||
static void* rtsp_thread(void *arg);
|
||||
static void abort_rtsp(raop_ctx_t *ctx);
|
||||
static void cleanup_rtsp(raop_ctx_t *ctx, bool abort);
|
||||
static bool handle_rtsp(raop_ctx_t *ctx, int sock);
|
||||
|
||||
static char* rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
|
||||
@@ -240,25 +241,13 @@ void raop_delete(struct raop_ctx_s *ctx) {
|
||||
|
||||
rtp_end(ctx->rtp);
|
||||
|
||||
shutdown(ctx->sock, SD_BOTH);
|
||||
shutdown(ctx->sock, SD_BOTH);
|
||||
closesocket(ctx->sock);
|
||||
|
||||
// terminate search, but do not reclaim memory of pthread if never launched
|
||||
if (ctx->active_remote.handle) {
|
||||
close_mDNS(ctx->active_remote.handle);
|
||||
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);
|
||||
}
|
||||
|
||||
// stop broadcasting devices
|
||||
@@ -267,11 +256,12 @@ void raop_delete(struct raop_ctx_s *ctx) {
|
||||
#else
|
||||
// then the RTSP task
|
||||
ctx->joiner = xTaskGetCurrentTaskHandle();
|
||||
ctx->running = false;
|
||||
|
||||
ctx->running = false;
|
||||
|
||||
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
vTaskDelete(ctx->thread);
|
||||
heap_caps_free(ctx->xTaskBuffer);
|
||||
|
||||
shutdown(ctx->sock, SHUT_RDWR);
|
||||
closesocket(ctx->sock);
|
||||
|
||||
@@ -405,7 +395,7 @@ static void *rtsp_thread(void *arg) {
|
||||
FD_SET(sock, &rfds);
|
||||
|
||||
n = select(sock + 1, &rfds, NULL, NULL, &timeout);
|
||||
|
||||
|
||||
if (!n && !ctx->abort) continue;
|
||||
|
||||
if (n > 0) res = handle_rtsp(ctx, sock);
|
||||
@@ -515,10 +505,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
|
||||
NFREE(p);
|
||||
}
|
||||
|
||||
// on announce, search remote
|
||||
// on announce, search remote
|
||||
if ((buf = kd_lookup(headers, "DACP-ID")) != NULL) strcpy(ctx->active_remote.DACPid, buf);
|
||||
if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
ctx->active_remote.handle = init_mDNS(false, ctx->host);
|
||||
pthread_create(&ctx->active_remote.thread, NULL, &search_remote, ctx);
|
||||
@@ -580,37 +570,14 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
|
||||
unsigned short seqno = 0;
|
||||
unsigned rtptime = 0;
|
||||
char *p;
|
||||
|
||||
|
||||
buf = kd_lookup(headers, "RTP-Info");
|
||||
if ((p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
|
||||
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, true)) {
|
||||
// only send FLUSH if useful (discards frames above buffer head and top)
|
||||
if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime))
|
||||
success = ctx->cmd_cb(RAOP_FLUSH);
|
||||
|
||||
} 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
|
||||
success = ctx->cmd_cb(RAOP_FLUSH);
|
||||
rtp_flush_release(ctx->rtp);
|
||||
}
|
||||
|
||||
@@ -631,18 +598,16 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
|
||||
success = ctx->cmd_cb(RAOP_VOLUME, volume);
|
||||
} else if (body && (p = strcasestr(body, "progress")) != NULL) {
|
||||
int start, current, stop = 0;
|
||||
|
||||
// we want ms, not s
|
||||
sscanf(p, "%*[^:]:%u/%u/%u", &start, ¤t, &stop);
|
||||
current = ((current - start) / 44100) * 1000;
|
||||
if (stop) stop = ((stop - start) / 44100) * 1000;
|
||||
|
||||
// we want ms, not s
|
||||
sscanf(p, "%*[^:]:%u/%u/%u", &start, ¤t, &stop);
|
||||
current = ((current - start) / 44100) * 1000;
|
||||
if (stop) stop = ((stop - start) / 44100) * 1000;
|
||||
else stop = -1;
|
||||
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;
|
||||
struct metadata_s metadata;
|
||||
dmap_settings settings = {
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, on_dmap_string, NULL,
|
||||
NULL
|
||||
@@ -651,6 +616,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
|
||||
LOG_INFO("[%p]: received metadata", ctx);
|
||||
settings.ctx = &metadata;
|
||||
memset(&metadata, 0, sizeof(struct metadata_s));
|
||||
if (!dmap_parse(&settings, body, len)) {
|
||||
LOG_INFO("[%p]: received metadata\n\tartist: %s\n\talbum: %s\n\ttitle: %s",
|
||||
ctx, metadata.artist, metadata.album, metadata.title);
|
||||
success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title);
|
||||
free_metadata(&metadata);
|
||||
}
|
||||
} else {
|
||||
@@ -678,26 +647,28 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
|
||||
NFREE(body);
|
||||
NFREE(buf);
|
||||
kd_free(resp);
|
||||
kd_free(headers);
|
||||
kd_free(headers);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/*----------------------------------------------------------------------------*/
|
||||
void cleanup_rtsp(raop_ctx_t *ctx, bool abort) {
|
||||
// first stop RTP process
|
||||
if (ctx->rtp) {
|
||||
rtp_end(ctx->rtp);
|
||||
ctx->rtp = NULL;
|
||||
if (abort) LOG_INFO("[%p]: RTP thread aborted", ctx);
|
||||
}
|
||||
|
||||
ctx->rtp = NULL;
|
||||
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.running = false;
|
||||
xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
|
||||
xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
|
||||
vTaskDelete(ctx->active_remote.thread);
|
||||
vSemaphoreDelete(ctx->active_remote.thread);
|
||||
|
||||
@@ -947,9 +918,8 @@ static unsigned int token_decode(const char *token)
|
||||
return DECODE_ERROR;
|
||||
for (i = 0; i < 4; i++) {
|
||||
val *= 64;
|
||||
if (token[i] == '=')
|
||||
marker++;
|
||||
else if (marker > 0)
|
||||
if (token[i] == '=')
|
||||
marker++;
|
||||
else if (marker > 0)
|
||||
return DECODE_ERROR;
|
||||
else
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "rtp.h"
|
||||
@@ -48,11 +49,10 @@
|
||||
#ifdef WIN32
|
||||
#include <openssl/aes.h>
|
||||
#include "alac_wrapper.h"
|
||||
#include <assert.h>
|
||||
#define MSG_DONTWAIT 0
|
||||
#else
|
||||
#include "esp_pthread.h"
|
||||
#include "esp_system.h"
|
||||
#include "assert.h"
|
||||
#include <mbedtls/version.h>
|
||||
#include <mbedtls/aes.h>
|
||||
#include "alac_wrapper.h"
|
||||
@@ -269,24 +269,24 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
|
||||
// create http port and start listening
|
||||
resp.cport = ctx->rtp_sockets[CONTROL].lport;
|
||||
resp.tport = ctx->rtp_sockets[TIMING].lport;
|
||||
resp.aport = ctx->rtp_sockets[DATA].lport;
|
||||
|
||||
resp.aport = ctx->rtp_sockets[DATA].lport;
|
||||
|
||||
ctx->running = true;
|
||||
ctx->running = true;
|
||||
|
||||
#ifdef WIN32
|
||||
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);
|
||||
pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
|
||||
#else
|
||||
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 );
|
||||
CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer );
|
||||
#endif
|
||||
|
||||
// cleanup everything if we failed
|
||||
if (!rc) {
|
||||
LOG_ERROR("[%p]: cannot start RTP", ctx);
|
||||
rtp_end(ctx);
|
||||
ctx = NULL;
|
||||
}
|
||||
|
||||
|
||||
resp.ctx = ctx;
|
||||
return resp;
|
||||
@@ -328,7 +328,7 @@ void rtp_end(rtp_t *ctx)
|
||||
fclose(ctx->rtpOUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
|
||||
{
|
||||
@@ -341,7 +341,7 @@ bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime)
|
||||
} else {
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
buffer_reset(ctx->audio_buffer);
|
||||
ctx->playing = false;
|
||||
ctx->playing = false;
|
||||
ctx->flush_seqno = seqno;
|
||||
if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
}
|
||||
@@ -350,8 +350,13 @@ bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime)
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
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;
|
||||
@@ -444,25 +449,23 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
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->ab_write = seqno - ctx->latency / ctx->frame_size;
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -520,14 +523,21 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
|
||||
if (now > playtime) {
|
||||
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->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
|
||||
curframe->ready = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -572,7 +582,7 @@ static void *rtp_thread_func(void *arg) {
|
||||
ntp_sent = rtp_request_timing(ctx);
|
||||
}
|
||||
|
||||
while (ctx->running) {
|
||||
while (ctx->running) {
|
||||
ssize_t plen;
|
||||
char type;
|
||||
socklen_t rtp_client_len = sizeof(struct sockaddr_in);
|
||||
@@ -585,14 +595,18 @@ static void *rtp_thread_func(void *arg) {
|
||||
|
||||
if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
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, 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);
|
||||
ntp_sent = rtp_request_timing(ctx);
|
||||
}
|
||||
|
||||
if (plen <= 0) {
|
||||
LOG_WARN("Nothing received on a readable socket %d", plen);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -711,6 +725,11 @@ static void *rtp_thread_func(void *arg) {
|
||||
|
||||
LOG_DEBUG("[%p]: Timing references local:%llu, remote:%llx (delta:%lld, sum:%lld, adjust:%lld, gaps:%d)",
|
||||
ctx, ctx->timing.local, ctx->timing.remote);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
LOG_WARN("Unknown packet received %x", (int) type);
|
||||
break;
|
||||
}
|
||||
@@ -752,7 +771,7 @@ static bool rtp_request_timing(rtp_t *ctx) {
|
||||
|
||||
// no address from sender, need to wait for 1st packet to be received
|
||||
if (host.sin_addr.s_addr == INADDR_ANY) return false;
|
||||
|
||||
|
||||
host.sin_port = htons(ctx->rtp_sockets[TIMING].rport);
|
||||
|
||||
if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &host, sizeof(host))) {
|
||||
@@ -778,7 +797,7 @@ static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
|
||||
*(u16_t*)(req+2) = htons(1); // our seqnum
|
||||
*(u16_t*)(req+4) = htons(first); // missed seqnum
|
||||
*(u16_t*)(req+6) = htons(last-first+1); // count
|
||||
|
||||
|
||||
ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
|
||||
|
||||
if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 )
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
@@ -17,6 +18,11 @@
|
||||
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
|
||||
#endif
|
||||
@@ -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_
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
idf_component_register(SRCS "utf8.c"
|
||||
idf_component_register(SRC_DIRS .
|
||||
REQUIRES esp_common pthread
|
||||
INCLUDE_DIRS .
|
||||
)
|
||||
@@ -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
|
||||
|
||||
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Binary file not shown.
@@ -15,84 +15,45 @@ 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-<width>x32', # doesn't matter much
|
||||
);
|
||||
|
||||
return $display;
|
||||
}
|
||||
|
||||
=comment
|
||||
sub modes {
|
||||
return \@modes;
|
||||
}
|
||||
=cut
|
||||
|
||||
sub nmodes {
|
||||
return $#modes;
|
||||
return scalar($#{shift->modes()});
|
||||
}
|
||||
|
||||
sub displayWidth {
|
||||
@@ -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;
|
||||
@@ -0,0 +1,28 @@
|
||||
[% PROCESS settings/header.html %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %]
|
||||
<!--<input type="text" readonly class="stdedit" name="pref_width" id="width" value="[% prefs.pref_width %]" size="3">-->
|
||||
[% prefs.pref_width %]
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_VU" desc="PLUGIN_SQUEEZEESP32_SMALL_VU_DESC" %]
|
||||
<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_small_VU" id="small_VU" value="[% prefs.pref_small_VU %]" size="3">
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE" desc="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC" %]
|
||||
<input type="number" min="10" max= "50" step="5" class="stdedit" name="pref_spectrum_scale" id="spectrum_scale" value="[% prefs.pref_spectrum.scale %]" size="3">
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM" desc="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC" %]
|
||||
[% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE" | string %] 
|
||||
<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_spectrum_small_size" id="spectrum_small_size" value="[% prefs.pref_spectrum.small.size %]" size="3">
|
||||
[% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND" | string %] 
|
||||
<input type="text" class="stdedit" name="pref_spectrum_small_band" id="spectrum_small_band" value="[% prefs.pref_spectrum.small.band %]" size="3">
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND" desc="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC" %]
|
||||
<input type="text" class="stdedit" name="pref_spectrum_full_band" id="spectrum_full_band" value="[% prefs.pref_spectrum.full.band %]" size="3">
|
||||
[% END %]
|
||||
|
||||
|
||||
[% PROCESS settings/footer.html %]
|
||||
@@ -23,8 +23,10 @@ sub playerSettingsFrame {
|
||||
|
||||
# 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;
|
||||
}
|
||||
|
||||
66
plugin/SqueezeESP32/PlayerSettings.pm
Normal file
66
plugin/SqueezeESP32/PlayerSettings.pm
Normal file
@@ -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;
|
||||
@@ -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,8 +18,11 @@ 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(@_);
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<name>PLUGIN_SQUEEZEESP32</name>
|
||||
<description>PLUGIN_SQUEEZEESP32_DESC</description>
|
||||
<module>Plugins::SqueezeESP32::Plugin</module>
|
||||
<version>0.12</version>
|
||||
<version>0.31</version>
|
||||
<creator>Philippe</creator>
|
||||
</extensions>
|
||||
|
||||
@@ -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 <i>Size</i>: % of the screen used by small spectrum
|
||||
EN <br><i>Band factor</i>: number of bands is the width of the <b>spectrum</b> 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
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
<extensions>
|
||||
<plugins>
|
||||
<plugin version="0.12" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
|
||||
<plugin version="0.31" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
|
||||
<link>https://github.com/sle118/squeezelite-esp32</link>
|
||||
<creator>Philippe</creator>
|
||||
<sha>3e60650efdff28cd0da9a7e9d0ddccf3a68d350d</sha>
|
||||
<sha>6dc35a0f9f9b287d205f7532cbb642b08407a284</sha>
|
||||
<email>philippe_44@outlook.com</email>
|
||||
<desc lang="EN">SqueezeESP32 additional player id (100)</desc>
|
||||
<url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>
|
||||
|
||||
Reference in New Issue
Block a user