Compare commits

..

18 Commits

Author SHA1 Message Date
Sebastien
a46bbb409f Fixes #50 - Green led flash state reset on wifi connect - release 2020-09-01 15:11:45 -04:00
Sebastien
08d16c2ca2 Led configuration wasn't correctly reported in logs 2020-09-01 12:03:31 -04:00
Philippe G
b501352ddc Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2020-08-31 15:36:21 -07:00
Philippe G
1c51598366 shift i2c address by 1 for consistency - release 2020-08-31 15:36:17 -07:00
Sebastien
db839a9ccd Add new status field: is_i2c_locked to help with the new config page 2020-08-31 16:56:54 -04:00
Philippe G
5aba426b98 no mutex needed for polling - release 2020-08-30 12:30:30 -07:00
Philippe G
6c184efa92 faster & simpler solution for stream poll() - release 2020-08-30 11:22:03 -07:00
Philippe G
028a090864 update plugin files 2020-08-29 22:20:26 -07:00
Philippe G
7e097a7ee9 ST77xx memory corruption + mp3 sync using library + mutex for poll in stream() - release 2020-08-29 22:07:07 -07:00
Philippe G
ce369638da extend poll() protection - release 2020-08-28 19:40:57 -07:00
Philippe G
c8054ff9d2 protect stream poll against race condition - release 2020-08-28 17:13:05 -07:00
Philippe G
e0e02f1e5f release ! 2020-08-27 16:43:49 -07:00
Philippe G
7f671909bb better mad sync 2020-08-27 16:40:11 -07:00
Philippe G
d2ca0b3f33 identify patch - release 2020-08-26 19:41:43 -07:00
Philippe G
e448a265c0 i2s.c strange issue to enable SPDIF
Not accessible from userland...
2020-08-26 19:37:35 -07:00
Philippe G
7a3774be7c i2c comment for SPDIF
esp-idf i2c.c *must* be patched at 2 different places for SPDIF to work
2020-08-26 19:26:12 -07:00
Sébastien
07930f6a56 i2s no longer need to be patched
We are now using our own i2s driver, which includes the required changes for SPDIF to work
2020-08-24 11:29:34 -04:00
Sebastien
8172ab535f Use our own local i2s driver as opposed to patching the esp-idf.
The esp-idf has a known issue with the i2s frequency calculation that
prevents us from leveraging the i2s output to drive SPDIF.  We used to
patch the esp-idf with our changed code before compiling, but that may
cause problems when setting up a new build environment and not reading
the instructions.
2020-08-24 11:28:12 -04:00
21 changed files with 153 additions and 192 deletions

View File

@@ -257,7 +257,6 @@ Follow the instructions from https://docs.espressif.com/projects/esp-idf/en/v4.0
If you want to use a more recent version of gcc and IDF (4.0 stable), move to cmake-master branch</strong> If you want to use a more recent version of gcc and IDF (4.0 stable), move to cmake-master branch</strong>
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/
And then copying the i2s.c patch file from this repo over to the esp-idf folder
You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264 You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264
>>>>>>> refs/remotes/origin/master >>>>>>> refs/remotes/origin/master

Binary file not shown.

View File

@@ -90,7 +90,7 @@ static void Update16( struct GDS_Device* Device ) {
for (int i = FirstRow; i <= LastRow; i++) { for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize); memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
optr += ChunkSize; optr += ChunkSize;
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue; if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM); Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM; optr = Private->iRAM;
} }
@@ -157,7 +157,7 @@ static void Update24( struct GDS_Device* Device ) {
for (int i = FirstRow; i <= LastRow; i++) { for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize); memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize);
optr += ChunkSize; optr += ChunkSize;
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue; if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM); Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM; optr = Private->iRAM;
} }

View File

@@ -1,6 +1,6 @@
idf_component_register(SRC_DIRS . idf_component_register(SRC_DIRS .
INCLUDE_DIRS . INCLUDE_DIRS . ${IDF_PATH}/components/driver
REQUIRES json tools platform_config display REQUIRES json tools platform_config display
) )

View File

@@ -28,7 +28,7 @@
#include "driver/i2s.h" #include "driver/i2s.h"
#include "driver/rtc_io.h" #include "driver/rtc_io.h"
#include "driver/dac.h" #include "driver/dac.h"
#include "adc1_i2s_private.h" #include <adc1_i2s_private.h>
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "esp_err.h" #include "esp_err.h"
@@ -96,7 +96,8 @@ typedef struct {
} i2s_obj_t; } i2s_obj_t;
static i2s_obj_t *p_i2s_obj[I2S_NUM_MAX] = {0}; static i2s_obj_t *p_i2s_obj[I2S_NUM_MAX] = {0};
static i2s_dev_t* I2S[I2S_NUM_MAX] = {&I2S0, &I2S1}; /* DRAM_ATTR is required to avoid I2S array placed in flash, due to accessed from ISR */
static DRAM_ATTR i2s_dev_t* I2S[I2S_NUM_MAX] = {&I2S0, &I2S1};
static portMUX_TYPE i2s_spinlock[I2S_NUM_MAX] = {portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED}; static portMUX_TYPE i2s_spinlock[I2S_NUM_MAX] = {portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED};
static int _i2s_adc_unit = -1; static int _i2s_adc_unit = -1;
static int _i2s_adc_channel = -1; static int _i2s_adc_channel = -1;
@@ -240,14 +241,14 @@ static float i2s_apll_get_fi2s(int bits_per_sample, int sdm0, int sdm1, int sdm2
* *
* @return ESP_ERR_INVALID_ARG or ESP_OK * @return ESP_ERR_INVALID_ARG or ESP_OK
*/ */
static esp_err_t i2s_apll_calculate_fi2s(int rate, int bits_per_sample, int *sdm0, int *sdm1, int *sdm2, int *odir) static esp_err_t i2s_apll_calculate_fi2s(int rate, int bits_per_sample, int *sdm0, int *sdm1, int *sdm2, int *odir)
{ {
int _odir, _sdm0, _sdm1, _sdm2; int _odir, _sdm0, _sdm1, _sdm2;
float r = rtc_clk_xtal_freq_get() * 1000000. / (rate * 2 * 2); float r = rtc_clk_xtal_freq_get() * 1000000. / (rate * 2 * 2);
int _sdm2_max; int _sdm2_max;
uint32_t prec = -1; uint32_t prec = -1;
int o, s1, s0; int o, s1, s0;
if (rate/bits_per_sample/2/8 < APLL_I2S_MIN_RATE) { if (rate/bits_per_sample/2/8 < APLL_I2S_MIN_RATE) {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
@@ -291,80 +292,11 @@ static esp_err_t i2s_apll_calculate_fi2s(int rate, int bits_per_sample, int *sdm
} }
} }
} }
if (*sdm2 + *sdm0 + *sdm0 + *odir) return ESP_OK; if (*sdm2 + *sdm0 + *sdm0 + *odir) return ESP_OK;
else return ESP_ERR_INVALID_ARG; else return ESP_ERR_INVALID_ARG;
} }
#if 0
static esp_err_t i2s_apll_calculate_fi2s(int rate, int bits_per_sample, int *sdm0, int *sdm1, int *sdm2, int *odir)
{
int _odir, _sdm0, _sdm1, _sdm2;
float avg;
float min_rate, max_rate, min_diff;
if (rate/bits_per_sample/2/8 < APLL_I2S_MIN_RATE) {
return ESP_ERR_INVALID_ARG;
}
*sdm0 = 0;
*sdm1 = 0;
*sdm2 = 0;
*odir = 0;
min_diff = APLL_MAX_FREQ;
for (_sdm2 = 4; _sdm2 < 9; _sdm2 ++) {
max_rate = i2s_apll_get_fi2s(bits_per_sample, 255, 255, _sdm2, 0);
min_rate = i2s_apll_get_fi2s(bits_per_sample, 0, 0, _sdm2, 31);
avg = (max_rate + min_rate)/2;
if (abs(avg - rate) < min_diff) {
min_diff = abs(avg - rate);
*sdm2 = _sdm2;
}
}
min_diff = APLL_MAX_FREQ;
for (_odir = 0; _odir < 32; _odir ++) {
max_rate = i2s_apll_get_fi2s(bits_per_sample, 255, 255, *sdm2, _odir);
min_rate = i2s_apll_get_fi2s(bits_per_sample, 0, 0, *sdm2, _odir);
avg = (max_rate + min_rate)/2;
if (abs(avg - rate) < min_diff) {
min_diff = abs(avg - rate);
*odir = _odir;
}
}
min_diff = APLL_MAX_FREQ;
for (_sdm2 = 4; _sdm2 < 9; _sdm2 ++) {
max_rate = i2s_apll_get_fi2s(bits_per_sample, 255, 255, _sdm2, *odir);
min_rate = i2s_apll_get_fi2s(bits_per_sample, 0, 0, _sdm2, *odir);
avg = (max_rate + min_rate)/2;
if (abs(avg - rate) < min_diff) {
min_diff = abs(avg - rate);
*sdm2 = _sdm2;
}
}
min_diff = APLL_MAX_FREQ;
for (_sdm1 = 0; _sdm1 < 256; _sdm1 ++) {
max_rate = i2s_apll_get_fi2s(bits_per_sample, 255, _sdm1, *sdm2, *odir);
min_rate = i2s_apll_get_fi2s(bits_per_sample, 0, _sdm1, *sdm2, *odir);
avg = (max_rate + min_rate)/2;
if (abs(avg - rate) < min_diff) {
min_diff = abs(avg - rate);
*sdm1 = _sdm1;
}
}
min_diff = APLL_MAX_FREQ;
for (_sdm0 = 0; _sdm0 < 256; _sdm0 ++) {
avg = i2s_apll_get_fi2s(bits_per_sample, _sdm0, *sdm1, *sdm2, *odir);
if (abs(avg - rate) < min_diff) {
min_diff = abs(avg - rate);
*sdm0 = _sdm0;
}
}
return ESP_OK;
}
#endif
esp_err_t i2s_set_clk(i2s_port_t i2s_num, uint32_t rate, i2s_bits_per_sample_t bits, i2s_channel_t ch) esp_err_t i2s_set_clk(i2s_port_t i2s_num, uint32_t rate, i2s_bits_per_sample_t bits, i2s_channel_t ch)
{ {
int factor = (256%bits)? 384 : 256; // According to hardware codec requirement(supported 256fs or 384fs) int factor = (256%bits)? 384 : 256; // According to hardware codec requirement(supported 256fs or 384fs)
@@ -797,7 +729,7 @@ esp_err_t i2s_set_dac_mode(i2s_dac_mode_t dac_mode)
return ESP_OK; return ESP_OK;
} }
static esp_err_t _i2s_adc_mode_recover(void) static esp_err_t _i2s_adc_mode_recover()
{ {
I2S_CHECK(((_i2s_adc_unit != -1) && (_i2s_adc_channel != -1)), "i2s ADC recover error, not initialized...", ESP_ERR_INVALID_ARG); I2S_CHECK(((_i2s_adc_unit != -1) && (_i2s_adc_channel != -1)), "i2s ADC recover error, not initialized...", ESP_ERR_INVALID_ARG);
return adc_i2s_mode_init(_i2s_adc_unit, _i2s_adc_channel); return adc_i2s_mode_init(_i2s_adc_unit, _i2s_adc_channel);
@@ -943,12 +875,6 @@ static esp_err_t i2s_param_config(i2s_port_t i2s_num, const i2s_config_t *i2s_co
I2S_CHECK(!((i2s_config->mode & I2S_MODE_DAC_BUILT_IN) && (i2s_num != I2S_NUM_0)), "I2S DAC built-in only support on I2S0", ESP_ERR_INVALID_ARG); I2S_CHECK(!((i2s_config->mode & I2S_MODE_DAC_BUILT_IN) && (i2s_num != I2S_NUM_0)), "I2S DAC built-in only support on I2S0", ESP_ERR_INVALID_ARG);
I2S_CHECK(!((i2s_config->mode & I2S_MODE_PDM) && (i2s_num != I2S_NUM_0)), "I2S DAC PDM only support on I2S0", ESP_ERR_INVALID_ARG); I2S_CHECK(!((i2s_config->mode & I2S_MODE_PDM) && (i2s_num != I2S_NUM_0)), "I2S DAC PDM only support on I2S0", ESP_ERR_INVALID_ARG);
if (i2s_num == I2S_NUM_1) {
periph_module_enable(PERIPH_I2S1_MODULE);
} else {
periph_module_enable(PERIPH_I2S0_MODULE);
}
if(i2s_config->mode & I2S_MODE_ADC_BUILT_IN) { if(i2s_config->mode & I2S_MODE_ADC_BUILT_IN) {
//in ADC built-in mode, we need to call i2s_set_adc_mode to //in ADC built-in mode, we need to call i2s_set_adc_mode to
//initialize the specific ADC channel. //initialize the specific ADC channel.
@@ -1001,6 +927,7 @@ static esp_err_t i2s_param_config(i2s_port_t i2s_num, const i2s_config_t *i2s_co
I2S[i2s_num]->conf.rx_start = 0; I2S[i2s_num]->conf.rx_start = 0;
if (i2s_config->mode & I2S_MODE_TX) { if (i2s_config->mode & I2S_MODE_TX) {
// PATCH
I2S[i2s_num]->conf.tx_msb_right = 1; I2S[i2s_num]->conf.tx_msb_right = 1;
I2S[i2s_num]->conf.tx_right_first = 0; I2S[i2s_num]->conf.tx_right_first = 0;
@@ -1013,7 +940,8 @@ static esp_err_t i2s_param_config(i2s_port_t i2s_num, const i2s_config_t *i2s_co
} }
if (i2s_config->mode & I2S_MODE_RX) { if (i2s_config->mode & I2S_MODE_RX) {
I2S[i2s_num]->conf.rx_msb_right = 1; // PATCH
I2S[i2s_num]->conf.rx_msb_right = 1;
I2S[i2s_num]->conf.rx_right_first = 0; I2S[i2s_num]->conf.rx_right_first = 0;
I2S[i2s_num]->conf.rx_slave_mod = 0; // Master I2S[i2s_num]->conf.rx_slave_mod = 0; // Master
I2S[i2s_num]->fifo_conf.rx_fifo_mod_force_en = 1; I2S[i2s_num]->fifo_conf.rx_fifo_mod_force_en = 1;
@@ -1156,8 +1084,10 @@ esp_err_t i2s_driver_install(i2s_port_t i2s_num, const i2s_config_t *i2s_config,
//To make sure hardware is enabled before any hardware register operations. //To make sure hardware is enabled before any hardware register operations.
if (i2s_num == I2S_NUM_1) { if (i2s_num == I2S_NUM_1) {
periph_module_reset(PERIPH_I2S1_MODULE);
periph_module_enable(PERIPH_I2S1_MODULE); periph_module_enable(PERIPH_I2S1_MODULE);
} else { } else {
periph_module_reset(PERIPH_I2S0_MODULE);
periph_module_enable(PERIPH_I2S0_MODULE); periph_module_enable(PERIPH_I2S0_MODULE);
} }
@@ -1241,18 +1171,6 @@ esp_err_t i2s_driver_uninstall(i2s_port_t i2s_num)
return ESP_OK; return ESP_OK;
} }
int i2s_write_bytes(i2s_port_t i2s_num, const void *src, size_t size, TickType_t ticks_to_wait)
{
size_t bytes_written = 0;
int res = 0;
res = i2s_write(i2s_num, src, size, &bytes_written, ticks_to_wait);
if (res != ESP_OK) {
return ESP_FAIL;
} else {
return bytes_written;
}
}
esp_err_t i2s_write(i2s_port_t i2s_num, const void *src, size_t size, size_t *bytes_written, TickType_t ticks_to_wait) esp_err_t i2s_write(i2s_port_t i2s_num, const void *src, size_t size, size_t *bytes_written, TickType_t ticks_to_wait)
{ {
char *data_ptr, *src_byte; char *data_ptr, *src_byte;
@@ -1378,18 +1296,6 @@ esp_err_t i2s_write_expand(i2s_port_t i2s_num, const void *src, size_t size, siz
return ESP_OK; return ESP_OK;
} }
int i2s_read_bytes(i2s_port_t i2s_num, void *dest, size_t size, TickType_t ticks_to_wait)
{
size_t bytes_read = 0;
int res = 0;
res = i2s_read(i2s_num, dest, size, &bytes_read, ticks_to_wait);
if (res != ESP_OK) {
return ESP_FAIL;
} else {
return bytes_read;
}
}
esp_err_t i2s_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_read, TickType_t ticks_to_wait) esp_err_t i2s_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_read, TickType_t ticks_to_wait)
{ {
char *data_ptr, *dest_byte; char *data_ptr, *dest_byte;
@@ -1428,29 +1334,3 @@ esp_err_t i2s_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_re
xSemaphoreGive(p_i2s_obj[i2s_num]->rx->mux); xSemaphoreGive(p_i2s_obj[i2s_num]->rx->mux);
return ESP_OK; return ESP_OK;
} }
int i2s_push_sample(i2s_port_t i2s_num, const void *sample, TickType_t ticks_to_wait)
{
size_t bytes_push = 0;
int res = 0;
I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL);
res = i2s_write(i2s_num, sample, p_i2s_obj[i2s_num]->bytes_per_sample, &bytes_push, ticks_to_wait);
if (res != ESP_OK) {
return ESP_FAIL;
} else {
return bytes_push;
}
}
int i2s_pop_sample(i2s_port_t i2s_num, void *sample, TickType_t ticks_to_wait)
{
size_t bytes_pop = 0;
int res = 0;
I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL);
res = i2s_read(i2s_num, sample, p_i2s_obj[i2s_num]->bytes_per_sample, &bytes_pop, ticks_to_wait);
if (res != ESP_OK) {
return ESP_FAIL;
} else {
return bytes_pop;
}
}

View File

@@ -5,7 +5,6 @@
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. CONDITIONS OF ANY KIND, either express or implied.
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@@ -84,8 +83,8 @@ static void vCallbackFunction( TimerHandle_t xTimer ) {
*/ */
bool led_blink_core(int idx, int ontime, int offtime, bool pushed) { bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
if (!leds[idx].gpio || leds[idx].gpio < 0 ) return false; if (!leds[idx].gpio || leds[idx].gpio < 0 ) return false;
ESP_LOGD(TAG,"led_blink_core"); ESP_LOGD(TAG,"led_blink_core led idx %d, ontime %d, offtime %d, pushing %s. Led state: pushedon %d, pushedoff %d, timer %s, pushed %s", idx,ontime,offtime,pushed?"TRUE":"FALSE", leds[idx].pushedon,leds[idx].pushedoff,leds[idx].timer?"TRUE":"FALSE", leds[idx].pushed?"TRUE":"FALSE");
if (leds[idx].timer) { if (leds[idx].timer) {
// normal requests waits if a pop is pending // normal requests waits if a pop is pending
if (!pushed && leds[idx].pushed) { if (!pushed && leds[idx].pushed) {
@@ -98,15 +97,18 @@ bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
// save current state if not already pushed // save current state if not already pushed
if (!leds[idx].pushed) { if (!leds[idx].pushed) {
ESP_LOGD(TAG,"Pushing state. Ontime %d->%d. Offtime %d->%d",leds[idx].pushedon,leds[idx].ontime, leds[idx].pushedoff,leds[idx].offtime);
leds[idx].pushedon = leds[idx].ontime; leds[idx].pushedon = leds[idx].ontime;
leds[idx].pushedoff = leds[idx].offtime; leds[idx].pushedoff = leds[idx].offtime;
leds[idx].pushed = pushed; leds[idx].pushed = pushed;
} }
// then set new one // then set new one
leds[idx].ontime = ontime; leds[idx].ontime = ontime;
leds[idx].offtime = offtime; leds[idx].offtime = offtime;
if (ontime == 0) { if (ontime == 0) {
ESP_LOGD(TAG,"led %d, setting reverse level", idx); ESP_LOGD(TAG,"led %d, setting reverse level", idx);
set_level(leds + idx, false); set_level(leds + idx, false);
@@ -124,7 +126,8 @@ bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
ESP_LOGD(TAG,"led %d, Setting gpio %d and starting timer", idx, leds[idx].gpio); ESP_LOGD(TAG,"led %d, Setting gpio %d and starting timer", idx, leds[idx].gpio);
if (xTimerStart(leds[idx].timer, BLOCKTIME) == pdFAIL) return false; if (xTimerStart(leds[idx].timer, BLOCKTIME) == pdFAIL) return false;
} }
ESP_LOGD(TAG,"led_blink_core END led idx %d, ontime %d, offtime %d, pushing %s. Led state: pushedon %d, pushedoff %d, timer %s, pushed %s", idx,ontime,offtime,pushed?"TRUE":"FALSE", leds[idx].pushedon,leds[idx].pushedoff,leds[idx].timer?"TRUE":"FALSE", leds[idx].pushed?"TRUE":"FALSE");
return true; return true;
} }
@@ -231,15 +234,13 @@ void led_svc_init(void) {
#ifndef CONFIG_LED_LOCKED #ifndef CONFIG_LED_LOCKED
parse_set_GPIO(set_led_gpio); parse_set_GPIO(set_led_gpio);
#endif #endif
ESP_LOGI(TAG,"Configuring LEDs green:%d (active:%d %d%%), red:%d (active:%d %d%%)", green.gpio, green.active, green.pwm, green.gpio, green.active, green.pwm );
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness"), *p; char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness"), *p;
if (nvs_item) { if (nvs_item) {
if ((p = strcasestr(nvs_item, "green")) != NULL) green.pwm = atoi(strchr(p, '=') + 1); if ((p = strcasestr(nvs_item, "green")) != NULL) green.pwm = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "red")) != NULL) red.pwm = atoi(strchr(p, '=') + 1); if ((p = strcasestr(nvs_item, "red")) != NULL) red.pwm = atoi(strchr(p, '=') + 1);
free(nvs_item); free(nvs_item);
} }
ESP_LOGI(TAG,"Configuring LEDs green:%d (active:%d %d%%), red:%d (active:%d %d%%)", green.gpio, green.active, green.pwm, red.gpio, red.active, red.pwm );
led_config(LED_GREEN, green.gpio, green.active, green.pwm); led_config(LED_GREEN, green.gpio, green.active, green.pwm);
led_config(LED_RED, red.gpio, red.active, red.pwm); led_config(LED_RED, red.gpio, red.active, red.pwm);
} }

View File

@@ -628,8 +628,9 @@ static void grfe_handler( u8_t *data, int len) {
scroller.active = false; scroller.active = false;
// visu has priority when full screen on small screens // full screen artwork or for small screen, full screen visu has priority
if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) { if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) ||
(artwork.enable && artwork.x == 0 && artwork.y == 0)) {
xSemaphoreGive(displayer.mutex); xSemaphoreGive(displayer.mutex);
return; return;
} }
@@ -751,8 +752,11 @@ static void grfg_handler(u8_t *data, int len) {
LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len); LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
// on small screen, visu has priority when full screen // full screen artwork or for small screen, visu has priority when full screen
if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) return; if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) ||
(artwork.enable && artwork.x == 0 && artwork.y == 0)) {
return;
}
xSemaphoreTake(displayer.mutex, portMAX_DELAY); xSemaphoreTake(displayer.mutex, portMAX_DELAY);
@@ -795,7 +799,7 @@ static void grfa_handler(u8_t *data, int len) {
// when using full screen visualizer on small screen there is a brief overlay // when using full screen visualizer on small screen there is a brief overlay
artwork.enable = (length != 0); artwork.enable = (length != 0);
// just a config or an actual artwork // just a config or an actual artwork
if (length < 32) { if (length < 32) {
if (artwork.enable) { if (artwork.enable) {
@@ -840,8 +844,10 @@ static void grfa_handler(u8_t *data, int len) {
* Update visualization bars * Update visualization bars
*/ */
static void visu_update(void) { static void visu_update(void) {
// no need to protect against no woning the display as we are playing // no update when artwork is full screen (but no need to protect against not owning the display as we are playing
if (pthread_mutex_trylock(&visu_export.mutex)) return; if ((artwork.enable && artwork.x == 0 && artwork.y == 0) || pthread_mutex_trylock(&visu_export.mutex)) {
return;
}
int mode = visu.mode & ~VISU_ESP32; int mode = visu.mode & ~VISU_ESP32;

View File

@@ -128,7 +128,7 @@ static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd); i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK); i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK); i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, val, I2C_MASTER_NACK); i2c_master_write_byte(cmd, val, I2C_MASTER_NACK);
@@ -153,11 +153,11 @@ static uint8_t i2c_read_reg(uint8_t reg) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd); i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK); i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK); i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
i2c_master_start(cmd); i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_READ, I2C_MASTER_NACK); i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK); i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK);
i2c_master_stop(cmd); i2c_master_stop(cmd);

View File

@@ -417,3 +417,4 @@ struct codec *register_mad(void) {
LOG_INFO("using mad to decode mp3"); LOG_INFO("using mad to decode mp3");
return &ret; return &ret;
} }

View File

@@ -234,6 +234,11 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
set_i2s_pin(spdif_config, &i2s_spdif_pin); set_i2s_pin(spdif_config, &i2s_spdif_pin);
set_i2s_pin(dac_config, &i2s_dac_pin); set_i2s_pin(dac_config, &i2s_dac_pin);
/* BEWARE: i2s. must be patched to set tx_msb_right/rx_msb_right to 1
* or SPDIF will not work. These settings are not accessible from
* userland and I don't know why
*/
// common I2S initialization // common I2S initialization
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX; i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;

View File

@@ -42,9 +42,21 @@ static log_level loglevel;
static struct buffer buf; static struct buffer buf;
struct buffer *streambuf = &buf; struct buffer *streambuf = &buf;
#define LOCK mutex_lock(streambuf->mutex) #define LOCK mutex_lock(streambuf->mutex)
#define UNLOCK mutex_unlock(streambuf->mutex) #define UNLOCK mutex_unlock(streambuf->mutex)
/*
When LMS sends a close/open sequence very quickly, the stream thread might
still be waiting in the poll() on the closed socket. It is never recommended
to have a thread closing a socket used by another thread but it works, as
opposed to an infinite select().
In stream_sock() a new socket is created and full OS will allocate a different
one but on RTOS and simple IP stack, the same might be re-used and that causes
an exception as a thread is already waiting on a newly allocated socket
A simple variable that forces stream_sock() to wait until we are out of poll()
is enough and much faster than a mutex
*/
static bool polling;
static sockfd fd; static sockfd fd;
struct streamstate stream; struct streamstate stream;
@@ -195,9 +207,12 @@ static void *stream_thread() {
} }
UNLOCK; UNLOCK;
// no mutex needed - we just want to know if we are inside poll()
polling = true;
if (_poll(ssl, &pollinfo, 100)) { if (_poll(ssl, &pollinfo, 100)) {
polling = false;
LOCK; LOCK;
// check socket has not been closed while in poll // check socket has not been closed while in poll
@@ -350,7 +365,7 @@ static void *stream_thread() {
UNLOCK; UNLOCK;
} else { } else {
polling = false;
LOG_SDEBUG("poll timeout"); LOG_SDEBUG("poll timeout");
} }
} }
@@ -438,7 +453,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
buf_flush(streambuf); buf_flush(streambuf);
LOCK; LOCK;
stream.header_len = header_len; stream.header_len = header_len;
memcpy(stream.header, header, header_len); memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0'; *(stream.header+header_len) = '\0';
@@ -473,6 +488,11 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) { void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
struct sockaddr_in addr; struct sockaddr_in addr;
#if EMBEDDED
// wait till we are not polling anymore
while (polling && running) { usleep(10000); }
#endif
int sock = socket(AF_INET, SOCK_STREAM, 0); int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) { if (sock < 0) {

View File

@@ -462,7 +462,11 @@ cJSON * wifi_manager_get_basic_info(cJSON **old){
cJSON_AddNumberToObject(root,"Voltage", battery_value_svc()); cJSON_AddNumberToObject(root,"Voltage", battery_value_svc());
cJSON_AddNumberToObject(root,"disconnect_count", num_disconnect ); cJSON_AddNumberToObject(root,"disconnect_count", num_disconnect );
cJSON_AddNumberToObject(root,"avg_conn_time", num_disconnect>0?(total_connected_time/num_disconnect):0 ); cJSON_AddNumberToObject(root,"avg_conn_time", num_disconnect>0?(total_connected_time/num_disconnect):0 );
#if CONFIG_I2C_LOCKED
cJSON_AddTrueToObject(root, "is_i2c_locked");
#else
cJSON_AddFalseToObject(root, "is_i2c_locked");
#endif
ESP_LOGV(TAG, "wifi_manager_get_basic_info done"); ESP_LOGV(TAG, "wifi_manager_get_basic_info done");
return root; return root;
} }
@@ -689,6 +693,8 @@ static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_
ESP_LOGD(TAG, "WIFI_EVENT_AP_STACONNECTED. aid: %d, mac: %s",stac->aid,STR_OR_BLANK(mac)); ESP_LOGD(TAG, "WIFI_EVENT_AP_STACONNECTED. aid: %d, mac: %s",stac->aid,STR_OR_BLANK(mac));
FREE_AND_NULL(mac); FREE_AND_NULL(mac);
xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STA_CONNECTED_BIT); xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STA_CONNECTED_BIT);
wifi_manager_send_message(EVENT_STA_CONNECTED, NULL);
} }
break; break;
case WIFI_EVENT_AP_STADISCONNECTED: case WIFI_EVENT_AP_STADISCONNECTED:
@@ -1476,6 +1482,9 @@ void wifi_manager( void * pvParameters ){
/* callback */ /* callback */
if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL); if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
break; break;
case EVENT_STA_CONNECTED:
if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
break;
case UPDATE_CONNECTION_OK: case UPDATE_CONNECTION_OK:
/* refresh JSON */ /* refresh JSON */
if(wifi_manager_lock_json_buffer( portMAX_DELAY )){ if(wifi_manager_lock_json_buffer( portMAX_DELAY )){

View File

@@ -191,7 +191,8 @@ typedef enum message_code_t {
ORDER_RESTART_RECOVERY = 16, ORDER_RESTART_RECOVERY = 16,
ORDER_RESTART_OTA_URL = 17, ORDER_RESTART_OTA_URL = 17,
ORDER_RESTART = 18, ORDER_RESTART = 18,
MESSAGE_CODE_COUNT = 19 /* important for the callback array */ EVENT_STA_CONNECTED = 19,
MESSAGE_CODE_COUNT = 20 /* important for the callback array */
}message_code_t; }message_code_t;

View File

@@ -44,7 +44,7 @@ menu "Squeezelite-ESP32"
string string
default "model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0" if SQUEEZEAMP default "model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0" if SQUEEZEAMP
default "model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32" if A1S default "model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32" if A1S
default "model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22" if TWATCH2020 default "model=I2S,bck=26,ws=25,do=33,i2c=53,sda=21,scl=22" if TWATCH2020
default "" default ""
config SPDIF_CONFIG config SPDIF_CONFIG
string string

View File

@@ -95,6 +95,10 @@ void cb_connection_sta_disconnected(void *pvParameter){
bWifiConnected=false; bWifiConnected=false;
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
} }
void cb_connection_sta_connected(void *pvParameter){
}
bool wait_for_wifi(){ bool wait_for_wifi(){
bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0; bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
if(!connected){ if(!connected){
@@ -447,7 +451,7 @@ void app_main()
/* start the wifi manager */ /* start the wifi manager */
ESP_LOGD(TAG,"Blinking led"); ESP_LOGD(TAG,"Blinking led");
led_blink(LED_GREEN, 250, 250); led_blink_pushed(LED_GREEN, 250, 250);
if(bypass_wifi_manager){ if(bypass_wifi_manager){
ESP_LOGW(TAG,"*******************************************************************************************"); ESP_LOGW(TAG,"*******************************************************************************************");
@@ -463,6 +467,7 @@ void app_main()
* This can be either after we're started the AP mode, or after we've started the STA mode */ * This can be either after we're started the AP mode, or after we've started the STA mode */
wifi_manager_set_callback(ORDER_START_AP, &start_telnet); wifi_manager_set_callback(ORDER_START_AP, &start_telnet);
wifi_manager_set_callback(ORDER_CONNECT_STA, &start_telnet); wifi_manager_set_callback(ORDER_CONNECT_STA, &start_telnet);
wifi_manager_set_callback(EVENT_STA_CONNECTED, &cb_connection_sta_connected);
} }
console_start(); console_start();

Binary file not shown.

View File

@@ -78,7 +78,7 @@ sub displayWidth {
if ($display->widthOverride) { if ($display->widthOverride) {
my $artwork = $prefs->client($client)->get('artwork'); my $artwork = $prefs->client($client)->get('artwork');
if ($artwork->{'enable'} && $artwork->{'y'} < 32 && ($client->isPlaying || $client->isPaused)) { if ($artwork->{'enable'} && $artwork->{'y'} < 32 && ($client->isPlaying || $client->isPaused)) {
return $artwork->{x} + ($display->modes->[$mode || 0]{_width} || 0); return ($artwork->{x} || $display->widthOverride) + ($display->modes->[$mode || 0]{_width} || 0);
} else { } else {
return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0); return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0);
} }
@@ -113,9 +113,9 @@ sub build_modes {
my $artwork = $cprefs->get('artwork'); my $artwork = $cprefs->get('artwork');
my $disp_width = $cprefs->get('width') || 128; my $disp_width = $cprefs->get('width') || 128;
# if artwork is in main display, reduce width # if artwork is in main display, reduce width but when artwork is (0,0) fake it
my $width = ($artwork->{'enable'} && $artwork->{'y'} < 32) ? $artwork->{'x'} : $disp_width; my $width = ($artwork->{'enable'} && $artwork->{'y'} < 32 && $artwork->{'x'}) ? $artwork->{'x'} : $disp_width;
my $width_low = ($artwork->{'enable'} && ($artwork->{'y'} >= 32 || $disp_width - $artwork->{'x'} > 32)) ? $artwork->{'x'} : $disp_width; my $width_low = ($artwork->{'enable'} && $artwork->{'x'} && ($artwork->{'y'} >= 32 || $disp_width - $artwork->{'x'} > 32)) ? $artwork->{'x'} : $disp_width;
my $small_VU = $cprefs->get('small_VU'); my $small_VU = $cprefs->get('small_VU');
my $spectrum = $cprefs->get('spectrum'); my $spectrum = $cprefs->get('spectrum');

View File

@@ -13,6 +13,19 @@ my $sprefs = preferences('server');
my $prefs = preferences('plugin.squeezeesp32'); my $prefs = preferences('plugin.squeezeesp32');
my $log = logger('plugin.squeezeesp32'); my $log = logger('plugin.squeezeesp32');
{
__PACKAGE__->mk_accessor('rw', 'tone_update');
}
sub new {
my $class = shift;
my $client = $class->SUPER::new(@_);
$client->init_accessor(
tone_update => 0,
);
return $client;
}
our $defaultPrefs = { our $defaultPrefs = {
'analogOutMode' => 0, 'analogOutMode' => 0,
'bass' => 0, 'bass' => 0,
@@ -44,7 +57,6 @@ sub hasIR { 1 }
# TODO: add in settings when ready # TODO: add in settings when ready
sub hasLineIn { 0 } sub hasLineIn { 0 }
sub hasHeadSubOut { 1 } sub hasHeadSubOut { 1 }
# TODO: LMS sliders are hard-coded in html file from -23 to +23
sub maxTreble { 20 } sub maxTreble { 20 }
sub minTreble { -13 } sub minTreble { -13 }
sub maxBass { 20 } sub maxBass { 20 }
@@ -54,7 +66,7 @@ sub init {
my $client = shift; my $client = shift;
if (!$handlersAdded) { if (!$handlersAdded) {
# Add a handler for line-in/out status changes # Add a handler for line-in/out status changes
Slim::Networking::Slimproto::addHandler( LIOS => \&lineInOutStatus ); Slim::Networking::Slimproto::addHandler( LIOS => \&lineInOutStatus );
@@ -117,25 +129,41 @@ sub playerSettingsFrame {
} }
sub bass { sub bass {
return tone(2, @_); my ($client, $new) = @_;
} my $value = $client->SUPER::bass($new);
sub treble { $client->update_equalizer($value, [2, 1, 3]) if defined $new;
return tone(8, @_);
return $value;
} }
sub tone { sub treble {
my ($center, $client, $value) = @_; my ($client, $new) = @_;
my $equalizer = $prefs->client($client)->get('equalizer'); my $value = $client->SUPER::treble($new);
if (defined($value)) { $client->update_equalizer($value, [8, 9, 7]) if defined $new;
$equalizer->[$center-1] = int($value * 0.2 + 0.5);
$equalizer->[$center] = int($value * 0.7 + 0.5);
$equalizer->[$center+1] = int($value * 0.1 + 0.5);
$prefs->client($client)->set('equalizer', $equalizer);
}
return int($equalizer->[$center-1] * 0.2 + $equalizer->[$center] * 0.7 + $equalizer->[$center+1] * 0.1); return $value;
}
sub update_equalizer {
my ($client, $value, $index) = @_;
return if $client->tone_update;
my $equalizer = $prefs->client($client)->get('equalizer');
$equalizer->[$index->[0]] = $value;
$equalizer->[$index->[1]] = int($value / 2 + 0.5);
$equalizer->[$index->[2]] = int($value / 4 + 0.5);
$prefs->client($client)->set('equalizer', $equalizer);
}
sub update_tones {
my ($client, $equalizer) = @_;
$client->tone_update(1);
$sprefs->client($client)->set('bass', int(($equalizer->[1] * 2 + $equalizer->[2] + $equalizer->[3] * 4) / 7 + 0.5));
$sprefs->client($client)->set('treble', int(($equalizer->[7] * 4 + $equalizer->[8] + $equalizer->[9] * 2) / 7 + 0.5));
$client->tone_update(0);
} }
sub update_artwork { sub update_artwork {
@@ -193,6 +221,11 @@ sub clear_artwork {
if ($artwork && $artwork->{'enable'}) { if ($artwork && $artwork->{'enable'}) {
main::INFOLOG && $log->is_info && $log->info("artwork stop/clear " . $request->getRequestString()); main::INFOLOG && $log->is_info && $log->info("artwork stop/clear " . $request->getRequestString());
$client->pluginData('artwork_md5', ''); $client->pluginData('artwork_md5', '');
# refresh screen and disable artwork when artwork was full screen (hack)
if (!$artwork->{'x'} && !$artwork->{'y'}) {
$client->sendFrame(grfa => \("\x00"x4)) unless $artwork->{'x'} || $artwork->{'y'};
$client->display->update;
}
} }
} }

View File

@@ -31,7 +31,7 @@ sub page {
sub prefs { sub prefs {
my ($class, $client) = @_; my ($class, $client) = @_;
my @prefs; my @prefs;
push @prefs, qw(width small_VU) if $client->displayWidth; push @prefs, qw(width small_VU) if defined $client->displayWidth;
return ($prefs->client($client), @prefs); return ($prefs->client($client), @prefs);
} }
@@ -41,7 +41,7 @@ sub handler {
my ($cprefs, @prefs) = $class->prefs($client); my ($cprefs, @prefs) = $class->prefs($client);
if ($paramRef->{'saveSettings'}) { if ($paramRef->{'saveSettings'}) {
if ($client->displayWidth) { if (defined $client->displayWidth) {
$cprefs->set('small_VU', $paramRef->{'pref_small_VU'} || 15); $cprefs->set('small_VU', $paramRef->{'pref_small_VU'} || 15);
my $spectrum = { my $spectrum = {
scale => $paramRef->{'pref_spectrum_scale'} || 25, scale => $paramRef->{'pref_spectrum_scale'} || 25,
@@ -73,9 +73,10 @@ sub handler {
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0; $equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
} }
$cprefs->set('equalizer', $equalizer); $cprefs->set('equalizer', $equalizer);
$client->update_tones($equalizer);
} }
if ($client->displayWidth) { if (defined $client->displayWidth) {
# the Settings super class can't handle anything but scalar values # the Settings super class can't handle anything but scalar values
# we need to populate the $paramRef for the other prefs manually # we need to populate the $paramRef for the other prefs manually
$paramRef->{'pref_spectrum'} = $cprefs->get('spectrum'); $paramRef->{'pref_spectrum'} = $cprefs->get('spectrum');

View File

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

View File

@@ -1,10 +1,10 @@
<?xml version='1.0' standalone='yes'?> <?xml version='1.0' standalone='yes'?>
<extensions> <extensions>
<plugins> <plugins>
<plugin version="0.100" name="SqueezeESP32" minTarget="7.9" maxTarget="*"> <plugin version="0.104" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<link>https://github.com/sle118/squeezelite-esp32</link> <link>https://github.com/sle118/squeezelite-esp32</link>
<creator>Philippe</creator> <creator>Philippe</creator>
<sha>572fa8afeaa3bc3cfb245b8d42ba05739aec584b</sha> <sha>79e505a30d7b6dbf43893acab176d57438e2a4a1</sha>
<email>philippe_44@outlook.com</email> <email>philippe_44@outlook.com</email>
<desc lang="EN">SqueezeESP32 additional player id (100)</desc> <desc lang="EN">SqueezeESP32 additional player id (100)</desc>
<url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url> <url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>