fix Makefile

This commit is contained in:
Christian Herzog
2020-03-04 18:45:15 +01:00
33 changed files with 936 additions and 119 deletions

View File

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

View File

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

View File

@@ -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();
}

View File

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

View File

@@ -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 );

View File

@@ -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) {

View 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;
}

View File

@@ -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);

View File

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

View File

@@ -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 ) {

View File

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

View File

@@ -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) {
}
/*----------------------------------------------------------------------------*/
void raop_delete(struct raop_ctx_s *ctx) {
void raop_delete(struct raop_ctx_s *ctx) {
#ifdef WIN32
int sock;
struct sockaddr addr;
@@ -240,7 +241,7 @@ 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
@@ -515,7 +516,7 @@ 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);
@@ -592,7 +593,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
} 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
@@ -631,18 +632,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, &current, &stop);
current = ((current - start) / 44100) * 1000;
if (stop) stop = ((stop - start) / 44100) * 1000;
// we want ms, not s
sscanf(p, "%*[^:]:%u/%u/%u", &start, &current, &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 +650,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 {
@@ -684,9 +687,13 @@ void abort_rtsp(raop_ctx_t *ctx) {
}
/*----------------------------------------------------------------------------*/
void abort_rtsp(raop_ctx_t *ctx) {
void abort_rtsp(raop_ctx_t *ctx) {
// first stop RTP process
if (ctx->rtp) {
rtp_end(ctx->rtp);
ctx->rtp = NULL;
LOG_INFO("[%p]: RTP thread aborted", ctx);
}
if (ctx->active_remote.running) {
#ifdef WIN32
@@ -696,8 +703,10 @@ void abort_rtsp(raop_ctx_t *ctx) {
// need to make sure no search is on-going and reclaim task memory
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
vSemaphoreDelete(ctx->active_remote.thread);
heap_caps_free(ctx->active_remote.xTaskBuffer);
#endif

View File

@@ -443,25 +443,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);
@@ -519,14 +517,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;
}
@@ -637,7 +642,7 @@ static void *rtp_thread_func(void *arg) {
case 0x54: {
u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
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);

View File

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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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");

View File

@@ -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);

View File

@@ -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_ */

View File

@@ -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;
}

View File

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

View File

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

View File

@@ -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();

View File

@@ -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");
}

View File

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

View File

@@ -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 := .

View File

@@ -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");
}

View File

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

Binary file not shown.

View File

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

View File

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

View File

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