Christian Herzog 5 лет назад
Родитель
Сommit
b336198853

+ 1 - 2
Makefile

@@ -13,8 +13,7 @@
 #recovery: EXTRA_CPPFLAGS+=-DRECOVERY_APPLICATION=1
 
 PROJECT_NAME?=squeezelite
-EXTRA_CPPFLAGS+=  -I$(PROJECT_PATH)/main
-EXTRA_COMPONENT_DIRS := esp-dsp
+EXTRA_CPPFLAGS+=  -I$(PROJECT_PATH)/main -I$(IDF_PATH)/components/esp_http_server/src -I$(IDF_PATH)/components/esp_http_server/src/port/esp32  -I$(IDF_PATH)/components/esp_http_server/src/util
 #/-Wno-error=maybe-uninitialized 
 include $(IDF_PATH)/make/project.mk 
 

+ 1 - 1
README.md

@@ -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
 ```

+ 5 - 11
components/cmd_system/cmd_system.c

@@ -106,8 +106,7 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
 		if(!wait_for_commit()){
 			ESP_LOGW(TAG,"Unable to commit configuration. ");
 		}
-		ESP_LOGW(TAG, "Restarting after tx complete");
-		uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+		vTaskDelay(750/ portTICK_PERIOD_MS);
 		esp_restart();
 		return ESP_OK;
 	}
@@ -117,8 +116,7 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
 		if(!wait_for_commit()){
 			ESP_LOGW(TAG,"Unable to commit configuration. ");
 		}
-		ESP_LOGW(TAG, "Restarting after tx complete");
-		uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+		vTaskDelay(750/ portTICK_PERIOD_MS);
 		esp_restart();
 		return ESP_OK;
 	}
@@ -166,8 +164,7 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
 			if(!wait_for_commit()){
 				ESP_LOGW(TAG,"Unable to commit configuration. ");
 			}
-			ESP_LOGW(TAG, "Restarting after tx complete");
-			uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+			vTaskDelay(750/ portTICK_PERIOD_MS);
 			esp_restart();
 		}
 	}
@@ -181,8 +178,7 @@ static int restart(int argc, char **argv)
 	if(!wait_for_commit()){
 		ESP_LOGW(TAG,"Unable to commit configuration. ");
 	}
-	ESP_LOGW(TAG, "Restarting after tx complete");
-    uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+	vTaskDelay(750/ portTICK_PERIOD_MS);
     esp_restart();
     return 0;
 }
@@ -193,9 +189,7 @@ void simple_restart()
 	if(!wait_for_commit()){
 		ESP_LOGW(TAG,"Unable to commit configuration. ");
 	}
-
-	ESP_LOGW(TAG, "Restarting after tx complete");
-	uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+	vTaskDelay(750/ portTICK_PERIOD_MS);
     esp_restart();
 }
 

+ 1 - 1
components/display/SH1106.c

@@ -143,7 +143,7 @@ struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) {
 	if (!strcasestr(Driver, "SH1106")) return NULL;
 	
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
-	*Device = SH1106;	
+	*Device = SH1106;
 	Device->Depth = 1;
 #if !defined SHADOW_BUFFER && defined USE_IRAM	
 	Device->Alloc = GDS_ALLOC_IRAM_SPI;

+ 7 - 7
components/display/SSD1306.c

@@ -42,12 +42,12 @@ 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;
+	int width = Device->Width, pages = Device->Height / 8;
 	uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer;
-	int CurrentRow = -1, FirstCol = -1, LastCol = -1;
+	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) {
@@ -70,15 +70,15 @@ static void Update( struct GDS_Device* Device ) {
 			}
 			
 			// Set row only when needed, otherwise let auto-increment work
-			if (r != CurrentRow) SetPageAddress( Device, r, Device->Height / 8 - 1 );
-			CurrentRow = r + 1;
+			if (p != CurrentPage) SetPageAddress( Device, p, Device->Height / 8 - 1 );
+			CurrentPage = p + 1;
 			
 			// actual write
-			Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1);
+			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 );

+ 2 - 6
components/display/SSD132x.c

@@ -162,7 +162,7 @@ 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( 7 - XBit ) : *FBOffset | BIT( 7 - XBit );
     }
 }
 
@@ -181,8 +181,6 @@ 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 ) {
@@ -193,8 +191,6 @@ static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, i
 	
 	// just do bitreverse and if BitRemap works, there will be even nothing to do	
 	for (int i = Height * Width >> 3; --i >= 0;) *optr++ = BitReverseTable256[*Data++];
-	
-	// Dirty is set for us
 }
 
 static void SetHFlip( struct GDS_Device* Device, bool On ) { 
@@ -307,7 +303,7 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
 	
 	*Device = SSD132x;	
 	((struct PrivateSpace*) Device->Private)->Model = Model;
-	
+		
 	sscanf(Driver, "%*[^:]:%c", &Device->Depth);
 	
 	if (Model == SSD1326 && Device->Depth == 1) {

+ 252 - 0
components/display/SSD1675.c

@@ -0,0 +1,252 @@
+/**
+ * Copyright (c) 2017-2018 Tara Keeling
+ *				 2020 Philippe G.
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include <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;
+}

+ 11 - 4
components/display/core/gds.c

@@ -133,11 +133,18 @@ bool GDS_Reset( struct GDS_Device* Device ) {
 }
 
 bool GDS_Init( struct GDS_Device* Device ) {
-	Device->FramebufferSize = ( Device->Width * Device->Height ) / (8  / Device->Depth);
 	
-	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 );
+	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);

+ 3 - 3
components/display/core/gds.h

@@ -13,13 +13,13 @@
  monochrome mode is not such type of screen, SH1106 and SSD1306 are
 */ 
 
-enum { 	GDS_COLOR_L0 = 0, GDS_COLOR_L1 = 1, GDS_COLOR_L2, GDS_COLOR_L3, GDS_COLOR_L4, GDS_COLOR_L5, GDS_COLOR_L6, GDS_COLOR_L7, 
+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_MAX - 1)
+#define GDS_COLOR_BLACK (0)
+#define GDS_COLOR_WHITE (-1)
 #define GDS_COLOR_XOR 	(GDS_COLOR_MAX + 1)
 
 struct GDS_Device;

+ 3 - 2
components/display/core/gds_private.h

@@ -7,6 +7,7 @@
 #include "gds.h"
 #include "gds_err.h"
 
+#define GDS_ALLOC_NONE		0x80
 #define GDS_ALLOC_IRAM		0x01
 #define GDS_ALLOC_IRAM_SPI	0x02
 
@@ -158,7 +159,7 @@ inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int
     if ( Color == GDS_COLOR_XOR ) {
         *FBOffset ^= BIT( YBit );
     } else {
-        *FBOffset = ( Color >= GDS_COLOR_WHITE / 2 ) ? *FBOffset | BIT( YBit ) : *FBOffset & ~BIT( YBit );
+        *FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
     }
 }
 
@@ -166,7 +167,7 @@ inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, 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));
 }
 
 inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {

+ 9 - 6
components/display/display.c

@@ -59,8 +59,8 @@ static EXT_RAM_ATTR struct {
 static void displayer_task(void *args);
 
 struct GDS_Device *display;   
-extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect;
-GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, NULL };
+extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect;
+GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, NULL };
 
 /****************************************************************************************
  * 
@@ -86,15 +86,18 @@ void display_init(char *welcome) {
 			
 	// so far so good
 	if (display && width > 0 && height > 0) {
+		int RST_pin = -1;
+		if ((p = strcasestr(config, "reset")) != NULL) RST_pin = atoi(strchr(p, '=') + 1);
+		
 		// Detect driver interface
 		if (strstr(config, "I2C") && i2c_system_port != -1) {
 			int address = 0x3C;
 				
 			if ((p = strcasestr(config, "address")) != NULL) address = atoi(strchr(p, '=') + 1);
-		
+				
 			init = true;
 			GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
-			GDS_I2CAttachDevice( display, width, height, address, -1 );
+			GDS_I2CAttachDevice( display, width, height, address, RST_pin );
 		
 			ESP_LOGI(TAG, "Display is I2C on port %u", address);
 		} else if (strstr(config, "SPI") && spi_system_host != -1) {
@@ -105,7 +108,7 @@ void display_init(char *welcome) {
 		
 			init = true;
 			GDS_SPIInit( spi_system_host, spi_system_dc_gpio );
-			GDS_SPIAttachDevice( display, width, height, CS_pin, -1, speed );
+			GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, speed );
 				
 			ESP_LOGI(TAG, "Display is SPI host %u with cs:%d", spi_system_host, CS_pin);
 		} else {
@@ -189,7 +192,7 @@ static void displayer_task(void *args) {
 		
 		// handler elapsed track time
 		if (displayer.timer && displayer.state == DISPLAYER_ACTIVE) {
-			char counter[12];
+			char counter[16];
 			TickType_t tick = xTaskGetTickCount();
 			uint32_t elapsed = (tick - displayer.tick) * portTICK_PERIOD_MS;
 			

+ 23 - 14
components/raop/raop.c

@@ -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,10 +81,11 @@ 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;
 		StaticTask_t *xTaskBuffer;
 		StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
@@ -218,7 +219,7 @@ void raop_delete(struct raop_ctx_s *ctx) {
 	struct sockaddr addr;
 	socklen_t nlen = sizeof(struct sockaddr);
 #endif
-	
+
 	if (!ctx) return;
 
 #ifdef WIN32
@@ -240,7 +241,7 @@ void raop_delete(struct raop_ctx_s *ctx) {
 	// 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->search_thread, NULL);
+		pthread_join(ctx->active_remote.thread, NULL);
 	}
 
 	// stop broadcasting devices
@@ -515,7 +516,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 
 #ifdef WIN32	
 		ctx->active_remote.handle = init_mDNS(false, ctx->host);
-		pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
+		pthread_create(&ctx->active_remote.thread, NULL, &search_remote, ctx);
 #else
 		ctx->active_remote.running = true;
 		ctx->active_remote.destroy_mutex = xSemaphoreCreateMutex();		
@@ -592,7 +593,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		// 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);
+		pthread_join(ctx->active_remote.thread, NULL);
 #else
 		ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
 		ctx->active_remote.running = false;
@@ -631,18 +632,16 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 			current = ((current - start) / 44100) * 1000;
 			if (stop) stop = ((stop - start) / 44100) * 1000;
 			else stop = -1;
-			LOG_INFO("[%p]: SET PARAMETER progress %u/%u %s", ctx, current, stop, p);
-			success = ctx->cmd_cb(RAOP_PROGRESS, current, stop);
-		}
-
-		if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) {
+			LOG_INFO("[%p]: SET PARAMETER progress %d/%u %s", ctx, current, stop, p);
+			success = ctx->cmd_cb(RAOP_PROGRESS, max(current, 0), stop);
+		} else if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) {
 			struct metadata_s metadata;
 			dmap_settings settings = {
 				NULL, NULL, NULL, NULL,	NULL, NULL,	NULL, on_dmap_string, NULL,
 				NULL
 			};
 
-			LOG_INFO("[%p]: received metadata");
+			LOG_INFO("[%p]: received metadata", ctx);
 			settings.ctx = &metadata;
 			memset(&metadata, 0, sizeof(struct metadata_s));
 			if (!dmap_parse(&settings, body, len)) {
@@ -651,6 +650,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 				success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title);
 				free_metadata(&metadata);
 			}
+		} else {
+			char *dump = kd_dump(headers);
+			LOG_INFO("Unhandled SET PARAMETER\n%s", dump);
+			free(dump);
 		}
 	}
 
@@ -684,9 +687,13 @@ void abort_rtsp(raop_ctx_t *ctx) {
 		rtp_end(ctx->rtp);
 		ctx->rtp = NULL;
 		LOG_INFO("[%p]: RTP thread aborted", ctx);
-	}	
+	}
 
 	if (ctx->active_remote.running) {
+#ifdef WIN32
+		pthread_join(ctx->active_remote.thread, NULL);
+		close_mDNS(ctx->active_remote.handle);
+#else
 		// need to make sure no search is on-going and reclaim task memory
 		ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
 		ctx->active_remote.running = false;
@@ -696,8 +703,10 @@ void abort_rtsp(raop_ctx_t *ctx) {
 		vSemaphoreDelete(ctx->active_remote.thread);
 
 		heap_caps_free(ctx->active_remote.xTaskBuffer);
+#endif
 		memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
 
+
 		LOG_INFO("[%p]: Remote search thread aborted", ctx);
 	}	
 

+ 23 - 18
components/raop/rtp.c

@@ -443,25 +443,23 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
 		LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
 
 	} else if (seq_order(ctx->ab_write, seqno)) {
+		seq_t i;
+		u32_t now;
+
 		// newer than expected
 		if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
 			// only get rtp latency-1 frames back (last one is seqno)
 			LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
 			ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
 		}
-		if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_read)) {
-			// if ab_read is lagging more than http latency, advance it
-			LOG_WARN("[%p] on hold for too long %hu (W:%hu R:%hu)", ctx, seqno - ctx->ab_read + 1, ctx->ab_write, ctx->ab_read);
-			ctx->ab_read = seqno - ctx->latency / ctx->frame_size + 1;
-		}
-		if (rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1)) {
-			seq_t i;
-			u32_t now = gettime_ms();
-			for (i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
-				ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
-				ctx->audio_buffer[BUFIDX(i)].last_resend = now;
-			}
+
+		// need to request re-send and adjust timing of gaps
+		rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
+		for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
+			ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
+			ctx->audio_buffer[BUFIDX(i)].last_resend = now;
 		}
+
 		LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
 		abuf = ctx->audio_buffer + BUFIDX(seqno);
 		ctx->ab_write = seqno;
@@ -519,14 +517,21 @@ static void buffer_push_packet(rtp_t *ctx) {
 			LOG_DEBUG("[%p]: discarded frame now:%u missed by:%d (W:%hu R:%hu)", ctx, now, now - playtime, ctx->ab_write, ctx->ab_read);
 			ctx->discarded++;
 			curframe->ready = 0;
+		} else if (playtime - now <= hold) {
+			if (curframe->ready) {
+				ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
+				curframe->ready = 0;
+			} else {
+				LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
+				ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
+				ctx->silent_frames++;
+			}
 		} else if (curframe->ready) {
 			ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
 			curframe->ready = 0;
-		} else if (playtime - now <= hold) {
-			LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
-			ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
-			ctx->silent_frames++;
-		} else break;
+		} else {
+			break;
+		}
 
 		ctx->ab_read++;
 		ctx->out_frames++;
@@ -637,7 +642,7 @@ static void *rtp_thread_func(void *arg) {
 				u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
 				u16_t flags = ntohs(*(u16_t*)(pktp+2));
 				u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
-				
+
 				// something is wrong and if we are supposed to be NTP synced, better ask for re-sync
 				if (remote_gap > 10000) {
 					if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx);

+ 1 - 0
components/services/component.mk

@@ -9,3 +9,4 @@
 
 COMPONENT_SRCDIRS := . 
 COMPONENT_ADD_INCLUDEDIRS := .
+CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG

+ 40 - 25
components/services/messaging.c

@@ -27,7 +27,7 @@ typedef struct {
 	RingbufHandle_t buf_handle;
 } messaging_list_t;
 static messaging_list_t top;
-
+#define MSG_LENGTH_AVG 1024
 
 messaging_list_t * get_struct_ptr(messaging_handle_t handle){
 	return (messaging_list_t *)handle;
@@ -35,12 +35,14 @@ messaging_list_t * get_struct_ptr(messaging_handle_t handle){
 messaging_handle_t  get_handle_ptr(messaging_list_t * handle){
 	return (messaging_handle_t )handle;
 }
+
 RingbufHandle_t messaging_create_ring_buffer(uint8_t max_count){
 	RingbufHandle_t buf_handle = NULL;
 	StaticRingbuffer_t *buffer_struct = malloc(sizeof(StaticRingbuffer_t));
 	if (buffer_struct != NULL) {
-		size_t buf_size = (size_t )(sizeof(single_message_t)+8)*(size_t )(max_count>0?max_count:5); // no-split buffer requires an additional 8 bytes
-		uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+		size_t buf_size = (size_t )(sizeof(single_message_t)+8+MSG_LENGTH_AVG)*(size_t )(max_count>0?max_count:5); // no-split buffer requires an additional 8 bytes
+		buf_size = buf_size - (buf_size % 4);
+		uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_32BIT);
 		if (buffer_storage== NULL) {
 			ESP_LOGE(tag,"buff alloc failed");
 		}
@@ -62,9 +64,9 @@ void messaging_fill_messages(messaging_list_t * target_subscriber){
     	message= messaging_retrieve_message(top.buf_handle);
     	if(message){
 			//re-post to original queue so it is available to future subscribers
-			messaging_post_to_queue(get_handle_ptr(&top), message, sizeof(single_message_t));
+			messaging_post_to_queue(get_handle_ptr(&top), message, message->msg_size);
 			// post to new subscriber
-			messaging_post_to_queue(get_handle_ptr(target_subscriber) , message, sizeof(single_message_t));
+			messaging_post_to_queue(get_handle_ptr(target_subscriber) , message, message->msg_size);
 			FREE_AND_NULL(message);
     	}
     }
@@ -116,6 +118,7 @@ const char * messaging_get_class_desc(messaging_classes msg_class){
 	switch (msg_class) {
 	CASE_TO_STR(MESSAGING_CLASS_OTA);
 	CASE_TO_STR(MESSAGING_CLASS_SYSTEM);
+	CASE_TO_STR(MESSAGING_CLASS_STATS);
 		default:
 			return "Unknown";
 			break;
@@ -156,14 +159,9 @@ single_message_t *  messaging_retrieve_message(RingbufHandle_t buf_handle){
     vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
 	if(uxItemsWaiting>0){
 		message = (single_message_t *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(50));
-		if(item_size!=sizeof(single_message_t)){
-			ESP_LOGE(tag,"Invalid message length!");
-		}
-		else {
-			message_copy  = heap_caps_malloc(item_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
-			if(message_copy){
-				memcpy(message_copy,message,item_size);
-			}
+		message_copy  = heap_caps_malloc(item_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+		if(message_copy){
+			memcpy(message_copy,message,item_size);
 		}
 		vRingbufferReturnItem(buf_handle, (void *)message);
 	}
@@ -171,25 +169,34 @@ single_message_t *  messaging_retrieve_message(RingbufHandle_t buf_handle){
 }
 
 esp_err_t messaging_post_to_queue(messaging_handle_t subscriber_handle, single_message_t * message, size_t message_size){
-	UBaseType_t uxItemsWaiting=0;
 	size_t item_size=0;
 	messaging_list_t * subscriber=get_struct_ptr(subscriber_handle);
 	if(!subscriber->buf_handle){
 		ESP_LOGE(tag,"post failed: null buffer for %s", str_or_unknown(subscriber->subscriber_name));
 		return ESP_FAIL;
 	}
-	vRingbufferGetInfo(subscriber->buf_handle,NULL,NULL,NULL,NULL,&uxItemsWaiting);
-	if(uxItemsWaiting>=subscriber->max_count){
-		ESP_LOGD(tag,"messaged dropped for %s",str_or_unknown(subscriber->subscriber_name));
+	void * pItem=NULL;
+	UBaseType_t res=pdFALSE;
+	while(1){
+		ESP_LOGD(tag,"Attempting to reserve %d bytes for %s",message_size, str_or_unknown(subscriber->subscriber_name));
+		res =  xRingbufferSendAcquire(subscriber->buf_handle, &pItem, message_size, pdMS_TO_TICKS(50));
+		if(res == pdTRUE && pItem){
+			ESP_LOGD(tag,"Reserving complete for %s", str_or_unknown(subscriber->subscriber_name));
+			memcpy(pItem,message,message_size);
+			xRingbufferSendComplete(subscriber->buf_handle, pItem);
+			break;
+		}
+		ESP_LOGD(tag,"Dropping for %s",str_or_unknown(subscriber->subscriber_name));
 		single_message_t * dummy = (single_message_t *)xRingbufferReceive(subscriber->buf_handle, &item_size, pdMS_TO_TICKS(50));
 		if (dummy== NULL) {
-			ESP_LOGE(tag,"receive from buffer failed");
+			ESP_LOGE(tag,"Dropping message failed");
+			break;
 		}
 		else {
+			ESP_LOGD(tag,"Dropping message of %d bytes for %s",item_size, str_or_unknown(subscriber->subscriber_name));
 			vRingbufferReturnItem(subscriber->buf_handle, (void *)dummy);
 		}
 	}
-	UBaseType_t res =  xRingbufferSend(subscriber->buf_handle, message, message_size, pdMS_TO_TICKS(1000));
 	if (res != pdTRUE) {
 		ESP_LOGE(tag,"post to %s failed",str_or_unknown(subscriber->subscriber_name));
 		return ESP_FAIL;
@@ -197,19 +204,27 @@ esp_err_t messaging_post_to_queue(messaging_handle_t subscriber_handle, single_m
 	return ESP_OK;
 }
 void messaging_post_message(messaging_types type,messaging_classes msg_class, char *fmt, ...){
-	single_message_t message={};
+	single_message_t * message=NULL;
+	size_t msg_size=0;
+	size_t ln =0;
 	messaging_list_t * cur=&top;
 	va_list va;
 	va_start(va, fmt);
-	vsnprintf(message.message, sizeof(message.message), fmt, va);
+	ln = vsnprintf(NULL, 0, fmt, va)+1;
+	msg_size = sizeof(single_message_t)+ln;
+	message = (single_message_t *)heap_caps_malloc(msg_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+	vsprintf(message->message, fmt, va);
 	va_end(va);
-	message.type = type;
-	message.msg_class = msg_class;
-	message.sent_time = esp_timer_get_time() / 1000;
+	message->msg_size = msg_size;
+	message->type = type;
+	message->msg_class = msg_class;
+	message->sent_time = esp_timer_get_time() / 1000;
+	ESP_LOGD(tag,"Post: %s",message->message);
 	while(cur){
-		messaging_post_to_queue(get_handle_ptr(cur),  &message, sizeof(single_message_t));
+		messaging_post_to_queue(get_handle_ptr(cur),  message, msg_size);
 		cur = get_struct_ptr(cur->next);
 	}
+	FREE_AND_NULL(message);
 	return;
 
 }

+ 4 - 2
components/services/messaging.h

@@ -9,7 +9,8 @@ typedef enum {
 } messaging_types;
 typedef enum {
 	MESSAGING_CLASS_OTA,
-	MESSAGING_CLASS_SYSTEM
+	MESSAGING_CLASS_SYSTEM,
+	MESSAGING_CLASS_STATS
 } messaging_classes;
 
 typedef struct messaging_list_t *messaging_handle_t;
@@ -18,7 +19,8 @@ typedef struct {
 	time_t sent_time;
 	messaging_types type;
 	messaging_classes msg_class;
-	char message[151];
+	size_t msg_size;
+	char message[];
 } single_message_t;
 
 cJSON *  messaging_retrieve_messages(RingbufHandle_t buf_handle);

+ 39 - 4
components/services/monitor.c

@@ -21,6 +21,9 @@
 #include "globdefs.h"
 #include "config.h"
 #include "accessors.h"
+#include "messaging.h"
+#include "cJSON.h"
+#include "trace.h"
 
 #define MONITOR_TIMER	(10*1000)
 
@@ -43,16 +46,17 @@ bool spkfault_svc(void);
 /****************************************************************************************
  * 
  */
-static void task_stats( void ) {
+static void task_stats( cJSON* top ) {
 #ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY 
 	static struct {
 		TaskStatus_t *tasks;
 		uint32_t total, n;
 	} current, previous;
-	
+	cJSON * tlist=cJSON_CreateArray();
 	current.n = uxTaskGetNumberOfTasks();
 	current.tasks = malloc( current.n * sizeof( TaskStatus_t ) );
 	current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total );
+	cJSON_AddNumberToObject(top,"ntasks",current.n);
 	
 	static EXT_RAM_ATTR char scratch[128+1];
 	*scratch = '\0';
@@ -66,6 +70,15 @@ static void task_stats( void ) {
 				n += sprintf(scratch + n, "%16s %2u%% s:%5u", current.tasks[i].pcTaskName, 
 																		   100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed, 
 																		   current.tasks[i].usStackHighWaterMark);
+				cJSON * t=cJSON_CreateObject();
+				cJSON_AddNumberToObject(t,"cpu",100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed);
+				cJSON_AddNumberToObject(t,"minstk",current.tasks[i].usStackHighWaterMark);
+				cJSON_AddNumberToObject(t,"bprio",current.tasks[i].uxBasePriority);
+				cJSON_AddNumberToObject(t,"cprio",current.tasks[i].uxCurrentPriority);
+				cJSON_AddStringToObject(t,"nme",current.tasks[i].pcTaskName);
+				cJSON_AddNumberToObject(t,"st",current.tasks[i].eCurrentState);
+				cJSON_AddNumberToObject(t,"num",current.tasks[i].xTaskNumber);
+				cJSON_AddItemToArray(tlist,t);
 				if (i % 3 == 2 || i == current.n - 1) {
 					ESP_LOGI(TAG, "%s", scratch);
 					n = 0;
@@ -77,13 +90,21 @@ static void task_stats( void ) {
 #else
 	for (int i = 0, n = 0; i < current.n; i ++) {
 		n += sprintf(scratch + n, "%16s s:%5u\t", current.tasks[i].pcTaskName, current.tasks[i].usStackHighWaterMark);
+		cJSON * t=cJSON_CreateObject();
+		cJSON_AddNumberToObject(t,"minstk",current.tasks[i].usStackHighWaterMark);
+		cJSON_AddNumberToObject(t,"bprio",current.tasks[i].uxBasePriority);
+		cJSON_AddNumberToObject(t,"cprio",current.tasks[i].uxCurrentPriority);
+		cJSON_AddStringToObject(t,"nme",current.tasks[i].pcTaskName);
+		cJSON_AddStringToObject(t,"st",current.tasks[i].eCurrentState);
+		cJSON_AddNumberToObject(t,"num",current.tasks[i].xTaskNumber);
+		cJSON_AddItemToArray(tlist,t);
 		if (i % 3 == 2 || i == current.n - 1) {
 			ESP_LOGI(TAG, "%s", scratch);
 			n = 0;
 		}	
 	}
 #endif	
-	
+	cJSON_AddItemToObject(top,"tasks",tlist);
 	if (previous.tasks) free(previous.tasks);
 	previous = current;
 #endif	
@@ -93,13 +114,26 @@ static void task_stats( void ) {
  * 
  */
 static void monitor_callback(TimerHandle_t xTimer) {
+	cJSON * top=cJSON_CreateObject();
+	cJSON_AddNumberToObject(top,"free_iram",heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
+	cJSON_AddNumberToObject(top,"min_free_iram",heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
+	cJSON_AddNumberToObject(top,"free_spiram",heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
+	cJSON_AddNumberToObject(top,"min_free_spiram",heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
+
 	ESP_LOGI(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu)", 
 			heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
 			heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
 			heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
 			heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
 			
-	task_stats();
+	task_stats(top);
+	char * top_a= cJSON_PrintUnformatted(top);
+	if(top_a){
+		messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a);
+		FREE_AND_NULL(top_a);
+	}
+	cJSON_free(top);
+
 }
 
 /****************************************************************************************
@@ -107,6 +141,7 @@ static void monitor_callback(TimerHandle_t xTimer) {
  */
 static void jack_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) {
 	ESP_LOGD(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed");
+	messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_SYSTEM,"jack is %s",BUTTON_PRESSED ? "inserted" : "removed");
 	if (jack_handler_svc) (*jack_handler_svc)(event == BUTTON_PRESSED);
 }
 

+ 1 - 1
components/squeezelite-ota/squeezelite-ota.c

@@ -616,7 +616,7 @@ void ota_task(void *pvParameter)
 {
 	esp_err_t err = ESP_OK;
     int data_read = 0;
-	GDS_TextSetFont(display,2,&Font_droid_sans_fallback_15x17,-2);
+	GDS_TextSetFont(display,2,GDS_GetHeight(display)>32?&Font_droid_sans_fallback_15x17:&Font_droid_sans_fallback_11x13,-2);
 	GDS_ClearExt(display, true);
 	GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Firmware update");
 	GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Initializing");

+ 5 - 1
components/telnet/telnet.c

@@ -38,8 +38,10 @@
 #include "config.h"
 #include "nvs_utilities.h"
 #include "platform_esp32.h"
+#include "messaging.h"
 #include "trace.h"
 
+
 /************************************
  * Globals
  */
@@ -119,6 +121,8 @@ void init_telnet(){
 	buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct);
 	if (buf_handle == NULL) {
 		ESP_LOGE(TAG,"Failed to create ring buffer for telnet!");
+		messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Failed to allocate memory for telnet buffer");
+
 		return;
 	}
 
@@ -144,7 +148,7 @@ void init_telnet(){
 void start_telnet(void * pvParameter){
 	static bool isStarted=false;
 	StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
-	StackType_t *xStack = heap_caps_malloc(TELNET_STACK_SIZE,(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
+	StackType_t *xStack = heap_caps_malloc(TELNET_STACK_SIZE,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
 	
 	if(!isStarted && bIsEnabled) {
 		xTaskCreateStatic( (TaskFunction_t) &telnet_task, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_MAIN_PRIO , xStack, xTaskBuffer);

+ 93 - 0
components/wifi-manager/_esp_http_server.h

@@ -0,0 +1,93 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef __ESP_HTTP_SERVER_H_
+#define __ESP_HTTP_SERVER_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <http_parser.h>
+#include <sdkconfig.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+/**
+ * @brief Starts the web server
+ *
+ * Create an instance of HTTP server and allocate memory/resources for it
+ * depending upon the specified configuration.
+ *
+ * Example usage:
+ * @code{c}
+ *
+ * //Function for starting the webserver
+ * httpd_handle_t start_webserver(void)
+ * {
+ *      // Generate default configuration
+ *      httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+ *
+ *      // Empty handle to http_server
+ *      httpd_handle_t server = NULL;
+ *
+ *      // Start the httpd server
+ *      if (httpd_start(&server, &config) == ESP_OK) {
+ *          // Register URI handlers
+ *          httpd_register_uri_handler(server, &uri_get);
+ *          httpd_register_uri_handler(server, &uri_post);
+ *      }
+ *      // If server failed to start, handle will be NULL
+ *      return server;
+ * }
+ *
+ * @endcode
+ *
+ * @param[in]  config   Configuration for new instance of the server
+ * @param[out] handle   Handle to newly created instance of the server. NULL on error
+ * @return
+ *  - ESP_OK    : Instance created successfully
+ *  - ESP_ERR_INVALID_ARG      : Null argument(s)
+ *  - ESP_ERR_HTTPD_ALLOC_MEM  : Failed to allocate memory for instance
+ *  - ESP_ERR_HTTPD_TASK       : Failed to launch server task
+ */
+esp_err_t  __httpd_start(httpd_handle_t *handle, const httpd_config_t *config);
+static inline int __httpd_os_thread_create_static(TaskHandle_t *thread,
+                                 const char *name, uint16_t stacksize, int prio,
+                                 void (*thread_routine)(void *arg), void *arg,
+                                 BaseType_t core_id)
+{
+
+
+	StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
+	StackType_t *xStack = heap_caps_malloc(stacksize,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
+
+
+	//
+	*thread = xTaskCreateStaticPinnedToCore(thread_routine, name, stacksize, arg, prio, xStack,xTaskBuffer,core_id);
+    if (*thread) {
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+}
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! _ESP_HTTP_SERVER_H_ */

+ 373 - 0
components/wifi-manager/_esp_httpd_main.c

@@ -0,0 +1,373 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <assert.h>
+
+#include <esp_http_server.h>
+#include <_esp_http_server.h>
+#include "../src/esp_httpd_priv.h"
+#include "../src/util/ctrl_sock.h"
+
+static const char *TAG = "_httpd";
+
+
+struct httpd_ctrl_data {
+    enum httpd_ctrl_msg {
+        HTTPD_CTRL_SHUTDOWN,
+        HTTPD_CTRL_WORK,
+    } hc_msg;
+    httpd_work_fn_t hc_work;
+    void *hc_work_arg;
+};
+
+
+static esp_err_t _httpd_server_init(struct httpd_data *hd)
+{
+    int fd = socket(PF_INET6, SOCK_STREAM, 0);
+    if (fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
+        return ESP_FAIL;
+    }
+
+    struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
+    struct sockaddr_in6 serv_addr = {
+        .sin6_family  = PF_INET6,
+        .sin6_addr    = inaddr_any,
+        .sin6_port    = htons(hd->config.server_port)
+    };
+
+    /* Enable SO_REUSEADDR to allow binding to the same
+     * address and port when restarting the server */
+    int enable = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
+        /* This will fail if CONFIG_LWIP_SO_REUSE is not enabled. But
+         * it does not affect the normal working of the HTTP Server */
+        ESP_LOGW(TAG, LOG_FMT("error enabling SO_REUSEADDR (%d)"), errno);
+    }
+
+    int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
+    if (ret < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    ret = listen(fd, hd->config.backlog_conn);
+    if (ret < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
+    if (ctrl_fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (msg_fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
+        close(fd);
+        close(ctrl_fd);
+        return ESP_FAIL;
+    }
+
+    hd->listen_fd = fd;
+    hd->ctrl_fd = ctrl_fd;
+    hd->msg_fd  = msg_fd;
+    return ESP_OK;
+}
+
+static void _httpd_process_ctrl_msg(struct httpd_data *hd)
+{
+    struct httpd_ctrl_data msg;
+    int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
+    if (ret <= 0) {
+        ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
+        return;
+    }
+    if (ret != sizeof(msg)) {
+        ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
+        return;
+    }
+
+    switch (msg.hc_msg) {
+    case HTTPD_CTRL_WORK:
+        if (msg.hc_work) {
+            ESP_LOGD(TAG, LOG_FMT("work"));
+            (*msg.hc_work)(msg.hc_work_arg);
+        }
+        break;
+    case HTTPD_CTRL_SHUTDOWN:
+        ESP_LOGD(TAG, LOG_FMT("shutdown"));
+        hd->hd_td.status = THREAD_STOPPING;
+        break;
+    default:
+        break;
+    }
+}
+
+static esp_err_t _httpd_accept_conn(struct httpd_data *hd, int listen_fd)
+{
+    /* If no space is available for new session, close the least recently used one */
+    if (hd->config.lru_purge_enable == true) {
+        if (!httpd_is_sess_available(hd)) {
+            /* Queue asynchronous closure of the least recently used session */
+            return httpd_sess_close_lru(hd);
+            /* Returning from this allowes the main server thread to process
+             * the queued asynchronous control message for closing LRU session.
+             * Since connection request hasn't been addressed yet using accept()
+             * therefore _httpd_accept_conn() will be called again, but this time
+             * with space available for one session
+             */
+       }
+    }
+
+    struct sockaddr_in addr_from;
+    socklen_t addr_from_len = sizeof(addr_from);
+    int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
+    if (new_fd < 0) {
+        ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);
+        return ESP_FAIL;
+    }
+    ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
+
+    struct timeval tv;
+    /* Set recv timeout of this fd as per config */
+    tv.tv_sec = hd->config.recv_wait_timeout;
+    tv.tv_usec = 0;
+    setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
+
+    /* Set send timeout of this fd as per config */
+    tv.tv_sec = hd->config.send_wait_timeout;
+    tv.tv_usec = 0;
+    setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
+
+    if (ESP_OK != httpd_sess_new(hd, new_fd)) {
+        ESP_LOGW(TAG, LOG_FMT("session creation failed"));
+        close(new_fd);
+        return ESP_FAIL;
+    }
+    ESP_LOGD(TAG, LOG_FMT("complete"));
+    return ESP_OK;
+}
+/* Manage in-coming connection or data requests */
+static esp_err_t _httpd_server(struct httpd_data *hd)
+{
+    fd_set read_set;
+    FD_ZERO(&read_set);
+    if (hd->config.lru_purge_enable || httpd_is_sess_available(hd)) {
+        /* Only listen for new connections if server has capacity to
+         * handle more (or when LRU purge is enabled, in which case
+         * older connections will be closed) */
+        FD_SET(hd->listen_fd, &read_set);
+    }
+    FD_SET(hd->ctrl_fd, &read_set);
+
+    int tmp_max_fd;
+    httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
+    int maxfd = MAX(hd->listen_fd, tmp_max_fd);
+    tmp_max_fd = maxfd;
+    maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
+
+    ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
+    int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);
+    if (active_cnt < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);
+        httpd_sess_delete_invalid(hd);
+        return ESP_OK;
+    }
+
+    /* Case0: Do we have a control message? */
+    if (FD_ISSET(hd->ctrl_fd, &read_set)) {
+        ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
+        _httpd_process_ctrl_msg(hd);
+        if (hd->hd_td.status == THREAD_STOPPING) {
+            ESP_LOGD(TAG, LOG_FMT("stopping thread"));
+            return ESP_FAIL;
+        }
+    }
+
+    /* Case1: Do we have any activity on the current data
+     * sessions? */
+    int fd = -1;
+    while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
+        if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
+            ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
+            if (httpd_sess_process(hd, fd) != ESP_OK) {
+                ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
+                close(fd);
+                /* Delete session and update fd to that
+                 * preceding the one being deleted */
+                fd = httpd_sess_delete(hd, fd);
+            }
+        }
+    }
+
+    /* Case2: Do we have any incoming connection requests to
+     * process? */
+    if (FD_ISSET(hd->listen_fd, &read_set)) {
+        ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
+        if (_httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {
+            ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
+        }
+    }
+    return ESP_OK;
+}
+
+static void _httpd_close_all_sessions(struct httpd_data *hd)
+{
+    int fd = -1;
+    while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
+        ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
+        httpd_sess_delete(hd, fd);
+        close(fd);
+    }
+}
+/* The main HTTPD thread */
+static void _httpd_thread(void *arg)
+{
+    int ret;
+    struct httpd_data *hd = (struct httpd_data *) arg;
+    hd->hd_td.status = THREAD_RUNNING;
+
+    ESP_LOGD(TAG, LOG_FMT("web server started"));
+    while (1) {
+        ret = _httpd_server(hd);
+        if (ret != ESP_OK) {
+            break;
+        }
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("web server exiting"));
+    close(hd->msg_fd);
+    cs_free_ctrl_sock(hd->ctrl_fd);
+    _httpd_close_all_sessions(hd);
+    close(hd->listen_fd);
+    hd->hd_td.status = THREAD_STOPPED;
+    httpd_os_thread_delete();
+}
+static struct httpd_data *__httpd_create(const httpd_config_t *config)
+{
+    /* Allocate memory for httpd instance data */
+    struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
+    if (!hd) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP server instance"));
+        return NULL;
+    }
+    hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
+    if (!hd->hd_calls) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP URI handlers"));
+        free(hd);
+        return NULL;
+    }
+    hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
+    if (!hd->hd_sd) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP session data"));
+        free(hd->hd_calls);
+        free(hd);
+        return NULL;
+    }
+    struct httpd_req_aux *ra = &hd->hd_req_aux;
+    ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
+    if (!ra->resp_hdrs) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP response headers"));
+        free(hd->hd_sd);
+        free(hd->hd_calls);
+        free(hd);
+        return NULL;
+    }
+    hd->err_handler_fns = calloc(HTTPD_ERR_CODE_MAX, sizeof(httpd_err_handler_func_t));
+    if (!hd->err_handler_fns) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP error handlers"));
+        free(ra->resp_hdrs);
+        free(hd->hd_sd);
+        free(hd->hd_calls);
+        free(hd);
+        return NULL;
+    }
+    /* Save the configuration for this instance */
+    hd->config = *config;
+    return hd;
+}
+static void _httpd_delete(struct httpd_data *hd)
+{
+    struct httpd_req_aux *ra = &hd->hd_req_aux;
+    /* Free memory of httpd instance data */
+    free(hd->err_handler_fns);
+    free(ra->resp_hdrs);
+    free(hd->hd_sd);
+
+    /* Free registered URI handlers */
+    httpd_unregister_all_uri_handlers(hd);
+    free(hd->hd_calls);
+    free(hd);
+}
+esp_err_t __httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
+{
+    if (handle == NULL || config == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    /* Sanity check about whether LWIP is configured for providing the
+     * maximum number of open sockets sufficient for the server. Though,
+     * this check doesn't guarantee that many sockets will actually be
+     * available at runtime as other processes may use up some sockets.
+     * Note that server also uses 3 sockets for its internal use :
+     *     1) listening for new TCP connections
+     *     2) for sending control messages over UDP
+     *     3) for receiving control messages over UDP
+     * So the total number of required sockets is max_open_sockets + 3
+     */
+    if (CONFIG_LWIP_MAX_SOCKETS < config->max_open_sockets + 3) {
+        ESP_LOGE(TAG, "Configuration option max_open_sockets is too large (max allowed %d)\n\t"
+                      "Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",
+                      CONFIG_LWIP_MAX_SOCKETS - 3);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    struct httpd_data *hd = __httpd_create(config);
+    if (hd == NULL) {
+        /* Failed to allocate memory */
+        return ESP_ERR_HTTPD_ALLOC_MEM;
+    }
+
+    if (_httpd_server_init(hd) != ESP_OK) {
+        _httpd_delete(hd);
+        return ESP_FAIL;
+    }
+
+    httpd_sess_init(hd);
+    if (__httpd_os_thread_create_static(&hd->hd_td.handle, "httpd",
+                               hd->config.stack_size,
+                               hd->config.task_priority,
+                               _httpd_thread, hd,
+                               hd->config.core_id) != ESP_OK) {
+        /* Failed to launch task */
+        _httpd_delete(hd);
+        return ESP_ERR_HTTPD_TASK;
+    }
+
+    *handle = (httpd_handle_t *)hd;
+    return ESP_OK;
+}
+

+ 21 - 1
components/wifi-manager/code.js

@@ -24,6 +24,14 @@ var nvs_type_t = {
     NVS_TYPE_ANY   : 0xff   /*!< Must be last */
 } ;
 
+var task_state_t = {
+		 0 : "eRunning",	/*!< A task is querying the state of itself, so must be running. */
+		 1 : "eReady",			/*!< The task being queried is in a read or pending ready list. */
+		 2 : "eBlocked",		/*!< The task being queried is in the Blocked state. */
+		 3 : "eSuspended",		/*!< The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
+		 4 : "eDeleted"				
+
+}
 var releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';
 var recovery = false;
 var enableAPTimer = true;
@@ -649,7 +657,9 @@ function refreshAPHTML(data){
 function getMessages() {
 	   $.getJSON("/messages.json", function(data) {
 	        data.forEach(function(msg) {
-				var msg_age = msg["current_time"] - msg["sent_time"];
+	        	var msg_age = msg["current_time"] - msg["sent_time"];
+				var msg_time  = new Date(  );
+				msg_time.setTime( msg_time .getTime() - msg_age  );
 	        	switch (msg["class"]) {
 	        		case "MESSAGING_CLASS_OTA":
 	        			//message: "{"ota_dsc":"Erasing flash complete","ota_pct":0}"
@@ -668,6 +678,16 @@ function getMessages() {
 	        	            }
 	        	        }        			
 	        			break;
+	        		case "MESSAGING_CLASS_STATS":
+	        			// for task states, check structure : task_state_t
+	        			var stats_data = JSON.parse(msg["message"]);
+	        			console.log(msg_time + " - Number of tasks on the ESP32: " + stats_data["ntasks"]);
+	        			var stats_tasks = stats_data["tasks"];
+	        			console.log(msg_time + '\tname' + '\tcpu' + '\tstate'+ '\tminstk'+ '\tbprio'+ '\tcprio'+ '\tnum' );
+	        			stats_tasks.forEach(function(task) {
+	        				console.log(msg_time + '\t' + task["nme"] + '\t'+ task["cpu"] + '\t'+ task_state_t[task["st"]]+ '\t'+ task["minstk"]+ '\t'+ task["bprio"]+ '\t'+ task["cprio"]+ '\t'+ task["num"]);
+	        			});
+	        			break;
 	        		case "MESSAGING_CLASS_SYSTEM":
 	        			showMessage(msg["message"], msg["type"],msg_age);
 	        			lastMsg = msg;

+ 1 - 1
components/wifi-manager/component.mk

@@ -7,7 +7,7 @@
 # please read the SDK documents if you need to do this.
 #
 COMPONENT_EMBED_FILES := style.css code.js index.html bootstrap.min.css.gz jquery.min.js.gz popper.min.js.gz bootstrap.min.js.gz
-COMPONENT_ADD_INCLUDEDIRS := .
+COMPONENT_ADD_INCLUDEDIRS := . $(IDF_PATH)/components/esp_http_server/src $(IDF_PATH)/components/esp_http_server/src/port/esp32  $(IDF_PATH)/components/esp_http_server/src/util
 CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO
 
 

+ 3 - 0
components/wifi-manager/wifi_manager.c

@@ -361,6 +361,7 @@ esp_err_t wifi_manager_save_sta_config(){
 		esp_err = nvs_commit(handle);
 		if (esp_err != ESP_OK) {
 			ESP_LOGE(TAG,  "Unable to commit changes. Error %s", esp_err_to_name(esp_err));
+			messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Unable to save wifi credentials. %s",esp_err_to_name(esp_err));
 			return esp_err;
 		}
 		nvs_close(handle);
@@ -1335,6 +1336,8 @@ void wifi_manager( void * pvParameters ){
 				else{
 					/* lost connection ? */
 					ESP_LOGE(TAG,   "WiFi Connection lost.");
+					messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"WiFi Connection lost");
+
 					if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
 						wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION );
 						wifi_manager_unlock_json_buffer();

+ 3 - 1
components/wifi-manager/wifi_manager_http_server.c

@@ -22,6 +22,7 @@
 #include "http_server_handlers.h"
 #include "esp_log.h"
 #include "esp_http_server.h"
+#include "_esp_http_server.h"
 #include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -112,6 +113,7 @@ void register_regular_handlers(httpd_handle_t server){
 
 }
 
+
 esp_err_t http_server_start()
 {
 	ESP_LOGI(TAG, "Initializing HTTP Server");
@@ -131,7 +133,7 @@ esp_err_t http_server_start()
     // config.open_fn
 
     ESP_LOGI(TAG, "Starting HTTP Server");
-    esp_err_t err= httpd_start(&_server, &config);
+    esp_err_t err= __httpd_start(&_server, &config);
     if(err != ESP_OK){
     	ESP_LOGE_LOC(TAG,"Start server failed");
     }

+ 1 - 1
main/CMakeLists.txt

@@ -1,4 +1,4 @@
-set(COMPONENT_ADD_INCLUDEDIRS . )
+set(COMPONENT_ADD_INCLUDEDIRS . ${IDF_PATH}/components/esp_http_server/src ${IDF_PATH}/components/esp_http_server/src/port/esp32  ${IDF_PATH}/components/esp_http_server/src/util)
 
 set(COMPONENT_SRCS "esp_app_main.c" "platform_esp32.c" "cmd_wifi.c" "console.c" "nvs_utilities.c" "cmd_squeezelite.c" "config.c")
 set(REQUIRES esp_common)

+ 1 - 1
main/component.mk

@@ -9,4 +9,4 @@
 CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO -DMODEL_NAME=SqueezeESP32
 LDFLAGS += -s
 COMPONENT_EMBED_TXTFILES :=  ${PROJECT_PATH}/server_certs/github.pem
-COMPONENT_ADD_INCLUDEDIRS := .
+COMPONENT_ADD_INCLUDEDIRS := .  

+ 4 - 0
main/esp_app_main.c

@@ -47,6 +47,7 @@
 #include "config.h"
 #include "audio_controls.h"
 #include "telnet.h"
+#include "messaging.h"
 
 static const char certs_namespace[] = "certificates";
 static const char certs_key[] = "blob";
@@ -73,12 +74,14 @@ const char * str_or_unknown(const char * str) { return (str?str:unknown_string_p
 /* 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){
 	ESP_LOGI(TAG, "I have a connection!");
+	messaging_post_message(MESSAGING_INFO,MESSAGING_CLASS_SYSTEM,"Wifi connected");
 	xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
 	bWifiConnected=true;
 	led_unpush(LED_GREEN);
 }
 void cb_connection_sta_disconnected(void *pvParameter){
 	led_blink_pushed(LED_GREEN, 250, 250);
+	messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Wifi disconnected");
 	bWifiConnected=false;
 	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
 }
@@ -427,4 +430,5 @@ void app_main()
 #endif
 		free(fwurl);
 	}
+	messaging_post_message(MESSAGING_INFO,MESSAGING_CLASS_SYSTEM,"System started");
 }

+ 0 - 1
main/platform_esp32.h

@@ -20,7 +20,6 @@
  */
  
 #pragma once
-
 #include "esp_pthread.h"
 #ifndef SQUEEZELITE_ESP32_RELEASE_URL
 #define SQUEEZELITE_ESP32_RELEASE_URL "https://github.com/sle118/squeezelite-esp32/releases"

BIN
plugin/SqueezeESP32.zip


+ 2 - 2
plugin/SqueezeESP32/Player.pm

@@ -20,10 +20,10 @@ sub playerSettingsFrame {
 	
 	my $value;
 	my $id = unpack('C', $$data_ref);
-        
+		        
 	# New SETD command 0xfe for display width
 	if ($id == 0xfe) { 
-		$value = (unpack('CC', $$data_ref))[1];
+		$value = (unpack('Cn', $$data_ref))[1];
 		if ($value > 100 && $value < 400) {
 			$client->display->widthOverride(1, $value);
 			$client->update;

+ 1 - 1
plugin/SqueezeESP32/install.xml

@@ -10,6 +10,6 @@
   <name>PLUGIN_SQUEEZEESP32</name>
   <description>PLUGIN_SQUEEZEESP32_DESC</description>
   <module>Plugins::SqueezeESP32::Plugin</module>
-    <version>0.12</version>
+    <version>0.20</version>
   <creator>Philippe</creator>
 </extensions>

+ 2 - 2
plugin/repo.xml

@@ -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.20" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
       <link>https://github.com/sle118/squeezelite-esp32</link>
       <creator>Philippe</creator>
-      <sha>3e60650efdff28cd0da9a7e9d0ddccf3a68d350d</sha>
+      <sha>c17cce48b8fa14f995ca93f51fdab52b4bdcb422</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>