mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-07 20:17:04 +03:00
Merge remote-tracking branch 'origin/master-cmake' into master-cmake
This commit is contained in:
@@ -51,8 +51,9 @@ float battery_value_svc(void) {
|
||||
*
|
||||
*/
|
||||
uint8_t battery_level_svc(void) {
|
||||
// TODO: this is totally incorrect
|
||||
return battery.avg ? (battery.avg - (3.0 * battery.cells)) / ((4.2 - 3.0) * battery.cells) * 100 : 0;
|
||||
// TODO: this is vastly incorrect
|
||||
int level = battery.avg ? (battery.avg - (3.0 * battery.cells)) / ((4.2 - 3.0) * battery.cells) * 100 : 0;
|
||||
return level < 100 ? level : 100;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
|
||||
@@ -21,7 +21,7 @@ extern void (*spkfault_handler_svc)(bool inserted);
|
||||
extern bool spkfault_svc(void);
|
||||
|
||||
extern float battery_value_svc(void);
|
||||
extern uint8_t battery_level_svc(void);
|
||||
extern uint16_t battery_level_svc(void);
|
||||
|
||||
extern monitor_gpio_t * get_spkfault_gpio();
|
||||
extern monitor_gpio_t * get_jack_insertion_gpio();
|
||||
|
||||
@@ -48,118 +48,84 @@ static const char TAG[] = "AC101";
|
||||
return b;\
|
||||
}
|
||||
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
|
||||
static void deinit(void);
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config);
|
||||
static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static bool volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode);
|
||||
|
||||
const struct adac_s dac_ac101 = { "AC101", init, deinit, power, speaker, headset, volume };
|
||||
const struct adac_s dac_ac101 = { "AC101", init, adac_deinit, power, speaker, headset, volume };
|
||||
|
||||
static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val);
|
||||
static uint16_t i2c_read_reg(uint8_t reg);
|
||||
static void ac101_start(ac_module_t mode);
|
||||
static void ac101_stop(void);
|
||||
static void ac101_set_earph_volume(uint8_t volume);
|
||||
static void ac101_set_spk_volume(uint8_t volume);
|
||||
|
||||
static int i2c_port;
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
esp_err_t res = ESP_OK;
|
||||
char *p;
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = -1,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = -1,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 250000,
|
||||
};
|
||||
|
||||
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
|
||||
|
||||
i2c_port = i2c_port_num;
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
|
||||
|
||||
res = i2c_read_reg(CHIP_AUDIO_RS);
|
||||
|
||||
if (!res) {
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
|
||||
adac_init(config, i2c_port);
|
||||
if (adac_read_word(AC101_ADDR, CHIP_AUDIO_RS) == 0xffff) {
|
||||
ESP_LOGW(TAG, "No AC101 detected");
|
||||
i2c_driver_delete(i2c_port);
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
res = i2c_write_reg(CHIP_AUDIO_RS, 0x123);
|
||||
// huh?
|
||||
ESP_LOGI(TAG, "AC101 detected");
|
||||
|
||||
adac_write_word(AC101_ADDR, CHIP_AUDIO_RS, 0x123);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
|
||||
// enable the PLL from BCLK source
|
||||
i2c_write_reg(PLL_CTRL1, BIN(0000,0001,0100,1111)); // F=1,M=1,PLL,INT=31 (medium)
|
||||
i2c_write_reg(PLL_CTRL2, BIN(1000,0110,0000,0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2;
|
||||
// i2c_write_reg(PLL_CTRL2, BIN(1000,0011,1100,0000));
|
||||
adac_write_word(AC101_ADDR, PLL_CTRL1, BIN(0000,0001,0100,1111)); // F=1,M=1,PLL,INT=31 (medium)
|
||||
adac_write_word(AC101_ADDR, PLL_CTRL2, BIN(1000,0110,0000,0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2;
|
||||
// adac_write_word(AC101_ADDR, PLL_CTRL2, BIN(1000,0011,1100,0000));
|
||||
|
||||
// clocking system
|
||||
i2c_write_reg(SYSCLK_CTRL, BIN(1010,1010,0000,1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK
|
||||
i2c_write_reg(MOD_CLK_ENA, BIN(1000,0000,0000,1100)); // IS21, ADC, DAC
|
||||
i2c_write_reg(MOD_RST_CTRL, BIN(1000,0000,0000,1100)); // IS21, ADC, DAC
|
||||
i2c_write_reg(I2S_SR_CTRL, BIN(0111,0000,0000,0000)); // 44.1kHz
|
||||
adac_write_word(AC101_ADDR, SYSCLK_CTRL, BIN(1010,1010,0000,1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK
|
||||
adac_write_word(AC101_ADDR, MOD_CLK_ENA, BIN(1000,0000,0000,1100)); // IS21, ADC, DAC
|
||||
adac_write_word(AC101_ADDR, MOD_RST_CTRL, BIN(1000,0000,0000,1100)); // IS21, ADC, DAC
|
||||
adac_write_word(AC101_ADDR, I2S_SR_CTRL, BIN(0111,0000,0000,0000)); // 44.1kHz
|
||||
|
||||
// analogue config
|
||||
i2c_write_reg(I2S1LCK_CTRL, BIN(1000,1000,0101,0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo
|
||||
i2c_write_reg(I2S1_SDOUT_CTRL, BIN(1100,0000,0000,0000)); // I2S1ADC (R&L)
|
||||
i2c_write_reg(I2S1_SDIN_CTRL, BIN(1100,0000,0000,0000)); // IS21DAC (R&L)
|
||||
i2c_write_reg(I2S1_MXR_SRC, BIN(0010,0010,0000,0000)); // ADCL, ADCR
|
||||
i2c_write_reg(ADC_SRCBST_CTRL, BIN(0100,0100,0100,0000)); // disable all boost (default)
|
||||
adac_write_word(AC101_ADDR, I2S1LCK_CTRL, BIN(1000,1000,0101,0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo
|
||||
adac_write_word(AC101_ADDR, I2S1_SDOUT_CTRL, BIN(1100,0000,0000,0000)); // I2S1ADC (R&L)
|
||||
adac_write_word(AC101_ADDR, I2S1_SDIN_CTRL, BIN(1100,0000,0000,0000)); // IS21DAC (R&L)
|
||||
adac_write_word(AC101_ADDR, I2S1_MXR_SRC, BIN(0010,0010,0000,0000)); // ADCL, ADCR
|
||||
adac_write_word(AC101_ADDR, ADC_SRCBST_CTRL, BIN(0100,0100,0100,0000)); // disable all boost (default)
|
||||
#if ENABLE_ADC
|
||||
i2c_write_reg(ADC_SRC, BIN(0000,0100,0000,1000)); // source=linein(R/L)
|
||||
i2c_write_reg(ADC_DIG_CTRL, BIN(1000,0000,0000,0000)); // enable digital ADC
|
||||
i2c_write_reg(ADC_ANA_CTRL, BIN(1011, 1011,0000,0000)); // enable analogue R/L, 0dB
|
||||
adac_write_word(AC101_ADDR, ADC_SRC, BIN(0000,0100,0000,1000)); // source=linein(R/L)
|
||||
adac_write_word(AC101_ADDR, ADC_DIG_CTRL, BIN(1000,0000,0000,0000)); // enable digital ADC
|
||||
adac_write_word(AC101_ADDR, ADC_ANA_CTRL, BIN(1011, 1011,0000,0000)); // enable analogue R/L, 0dB
|
||||
#else
|
||||
i2c_write_reg(ADC_SRC, BIN(0000,0000,0000,0000)); // source=none
|
||||
i2c_write_reg(ADC_DIG_CTRL, BIN(0000,0000,0000,0000)); // disable digital ADC
|
||||
i2c_write_reg(ADC_ANA_CTRL, BIN(0011, 0011,0000,0000)); // disable analogue R/L, 0dB
|
||||
adac_write_word(AC101_ADDR, ADC_SRC, BIN(0000,0000,0000,0000)); // source=none
|
||||
adac_write_word(AC101_ADDR, ADC_DIG_CTRL, BIN(0000,0000,0000,0000)); // disable digital ADC
|
||||
adac_write_word(AC101_ADDR, ADC_ANA_CTRL, BIN(0011, 0011,0000,0000)); // disable analogue R/L, 0dB
|
||||
#endif
|
||||
|
||||
//Path Configuration
|
||||
i2c_write_reg(DAC_MXR_SRC, BIN(1000,1000,0000,0000)); // DAC from I2S
|
||||
i2c_write_reg(DAC_DIG_CTRL, BIN(1000,0000,0000,0000)); // enable DAC
|
||||
i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111,0000,0000,0000)); // enable DAC/Analogue (see note on offset removal and PA)
|
||||
i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111,1111,0000,0000)); // this toggle is needed for headphone PA offset
|
||||
adac_write_word(AC101_ADDR, DAC_MXR_SRC, BIN(1000,1000,0000,0000)); // DAC from I2S
|
||||
adac_write_word(AC101_ADDR, DAC_DIG_CTRL, BIN(1000,0000,0000,0000)); // enable DAC
|
||||
adac_write_word(AC101_ADDR, OMIXER_DACA_CTRL, BIN(1111,0000,0000,0000)); // enable DAC/Analogue (see note on offset removal and PA)
|
||||
adac_write_word(AC101_ADDR, OMIXER_DACA_CTRL, BIN(1111,1111,0000,0000)); // this toggle is needed for headphone PA offset
|
||||
#if ENABLE_ADC
|
||||
i2c_write_reg(OMIXER_SR, BIN(0000,0001,0000,0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?)
|
||||
adac_write_word(AC101_ADDR, OMIXER_SR, BIN(0000,0001,0000,0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?)
|
||||
#else
|
||||
i2c_write_reg(OMIXER_SR, BIN(0000,0101,0000,1010)); // source=DAC(R/L) and LINEIN(R/L)
|
||||
adac_write_word(AC101_ADDR, OMIXER_SR, BIN(0000,0101,0000,1010)); // source=DAC(R/L) and LINEIN(R/L)
|
||||
#endif
|
||||
|
||||
// enable earphone & speaker
|
||||
i2c_write_reg(SPKOUT_CTRL, 0x0220);
|
||||
i2c_write_reg(HPOUT_CTRL, 0xf801);
|
||||
adac_write_word(AC101_ADDR, SPKOUT_CTRL, 0x0220);
|
||||
adac_write_word(AC101_ADDR, HPOUT_CTRL, 0xf801);
|
||||
|
||||
// set gain for speaker and earphone
|
||||
ac101_set_spk_volume(100);
|
||||
ac101_set_earph_volume(100);
|
||||
|
||||
ESP_LOGI(TAG, "AC101 uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
|
||||
return (res == ESP_OK);
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static void deinit(void) {
|
||||
i2c_driver_delete(i2c_port);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change volume
|
||||
*/
|
||||
@@ -190,9 +156,9 @@ static void power(adac_power_e mode) {
|
||||
* speaker
|
||||
*/
|
||||
static void speaker(bool active) {
|
||||
uint16_t value = i2c_read_reg(SPKOUT_CTRL);
|
||||
if (active) i2c_write_reg(SPKOUT_CTRL, value | SPKOUT_EN);
|
||||
else i2c_write_reg(SPKOUT_CTRL, value & ~SPKOUT_EN);
|
||||
uint16_t value = adac_read_word(AC101_ADDR, SPKOUT_CTRL);
|
||||
if (active) adac_write_word(AC101_ADDR, SPKOUT_CTRL, value | SPKOUT_EN);
|
||||
else adac_write_word(AC101_ADDR, SPKOUT_CTRL, value & ~SPKOUT_EN);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -200,51 +166,11 @@ static void speaker(bool active) {
|
||||
*/
|
||||
static void headset(bool active) {
|
||||
// there might be aneed to toggle OMIXER_DACA_CTRL 11:8, not sure
|
||||
uint16_t value = i2c_read_reg(HPOUT_CTRL);
|
||||
if (active) i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN);
|
||||
else i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN);
|
||||
uint16_t value = adac_read_word(AC101_ADDR, HPOUT_CTRL);
|
||||
if (active) adac_write_word(AC101_ADDR, HPOUT_CTRL, value | EAROUT_EN);
|
||||
else adac_write_word(AC101_ADDR, HPOUT_CTRL, value & ~EAROUT_EN);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val)
|
||||
{
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
esp_err_t ret =0;
|
||||
uint8_t send_buff[4];
|
||||
send_buff[0] = (AC101_ADDR << 1);
|
||||
send_buff[1] = reg;
|
||||
send_buff[2] = (val>>8) & 0xff;
|
||||
send_buff[3] = val & 0xff;
|
||||
ret |= i2c_master_start(cmd);
|
||||
ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN);
|
||||
ret |= i2c_master_stop(cmd);
|
||||
ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static uint16_t i2c_read_reg(uint8_t reg) {
|
||||
uint8_t data[2] = { 0 };
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, ( AC101_ADDR << 1 ) | WRITE_BIT, ACK_CHECK_EN);
|
||||
i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, ( AC101_ADDR << 1 ) | READ_BIT, ACK_CHECK_EN); //check or not
|
||||
i2c_master_read(cmd, data, 2, ACK_VAL);
|
||||
i2c_master_stop(cmd);
|
||||
i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
return (data[0] << 8) + data[1];;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -264,7 +190,7 @@ void set_sample_rate(int rate) {
|
||||
ESP_LOGW(TAG, "Unknown sample rate %hu", rate);
|
||||
rate = SAMPLE_RATE_44100;
|
||||
}
|
||||
i2c_write_reg(I2S_SR_CTRL, rate);
|
||||
adac_write_word(AC101_ADDR, I2S_SR_CTRL, rate);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -273,8 +199,8 @@ void set_sample_rate(int rate) {
|
||||
static void ac101_set_spk_volume(uint8_t volume) {
|
||||
uint16_t value = max(volume, 100);
|
||||
value = ((int) value * 0x1f) / 100;
|
||||
value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f;
|
||||
i2c_write_reg(SPKOUT_CTRL, value);
|
||||
value |= adac_read_word(AC101_ADDR, SPKOUT_CTRL) & ~0x1f;
|
||||
adac_write_word(AC101_ADDR, SPKOUT_CTRL, value);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -283,8 +209,8 @@ static void ac101_set_spk_volume(uint8_t volume) {
|
||||
static void ac101_set_earph_volume(uint8_t volume) {
|
||||
uint16_t value = max(volume, 100);
|
||||
value = (((int) value * 0x3f) / 100) << 4;
|
||||
value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4);
|
||||
i2c_write_reg(HPOUT_CTRL, value);
|
||||
value |= adac_read_word(AC101_ADDR, HPOUT_CTRL) & ~(0x3f << 4);
|
||||
adac_write_word(AC101_ADDR, HPOUT_CTRL, value);
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -292,14 +218,14 @@ static void ac101_set_earph_volume(uint8_t volume) {
|
||||
* Get normalized (0..100) speaker volume
|
||||
*/
|
||||
static int ac101_get_spk_volume(void) {
|
||||
return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f;
|
||||
return ((adac_read_word(AC101_ADDR, SPKOUT_CTRL) & 0x1f) * 100) / 0x1f;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Get normalized (0..100) earphone volume
|
||||
*/
|
||||
static int ac101_get_earph_volume(void) {
|
||||
return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
|
||||
return (((adac_read_word(AC101_ADDR, HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -308,7 +234,7 @@ static int ac101_get_earph_volume(void) {
|
||||
static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mixer_source_t source)
|
||||
{
|
||||
uint16_t regval,temp,clrbit;
|
||||
regval = i2c_read_reg(OMIXER_BST1_CTRL);
|
||||
regval = adac_read_word(AC101_ADDR, OMIXER_BST1_CTRL);
|
||||
switch(source){
|
||||
case SRC_MIC1:
|
||||
temp = (gain&0x7) << 6;
|
||||
@@ -327,14 +253,15 @@ static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mi
|
||||
}
|
||||
regval &= clrbit;
|
||||
regval |= temp;
|
||||
i2c_write_reg(OMIXER_BST1_CTRL,regval);
|
||||
adac_write_word(AC101_ADDR, OMIXER_BST1_CTRL,regval);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static void ac101_deinit(void) {
|
||||
i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset
|
||||
static void deinit(void) {
|
||||
adac_write_word(AC101_ADDR, CHIP_AUDIO_RS, 0x123); //soft reset
|
||||
adac_deinit();
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -342,11 +269,11 @@ static void ac101_deinit(void) {
|
||||
*/
|
||||
static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) {
|
||||
uint16_t regval=0;
|
||||
regval = i2c_read_reg(I2S1LCK_CTRL);
|
||||
regval = adac_read_word(AC101_ADDR, I2S1LCK_CTRL);
|
||||
regval &= 0xe03f;
|
||||
regval |= (cfg->bclk_div << 9);
|
||||
regval |= (cfg->lclk_div << 6);
|
||||
i2c_write_reg(I2S1LCK_CTRL, regval);
|
||||
adac_write_word(AC101_ADDR, I2S1LCK_CTRL, regval);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -356,21 +283,21 @@ static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) {
|
||||
*/
|
||||
static void ac101_start(ac_module_t mode) {
|
||||
if (mode == AC_MODULE_LINE) {
|
||||
i2c_write_reg(0x51, 0x0408);
|
||||
i2c_write_reg(0x40, 0x8000);
|
||||
i2c_write_reg(0x50, 0x3bc0);
|
||||
adac_write_word(AC101_ADDR, 0x51, 0x0408);
|
||||
adac_write_word(AC101_ADDR, 0x40, 0x8000);
|
||||
adac_write_word(AC101_ADDR, 0x50, 0x3bc0);
|
||||
}
|
||||
if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) {
|
||||
// I2S1_SDOUT_CTRL
|
||||
// i2c_write_reg(PLL_CTRL2, 0x8120);
|
||||
i2c_write_reg(0x04, 0x800c);
|
||||
i2c_write_reg(0x05, 0x800c);
|
||||
// res |= i2c_write_reg(0x06, 0x3000);
|
||||
// adac_write_word(AC101_ADDR, PLL_CTRL2, 0x8120);
|
||||
adac_write_word(AC101_ADDR, 0x04, 0x800c);
|
||||
adac_write_word(AC101_ADDR, 0x05, 0x800c);
|
||||
// res |= adac_write_word(AC101_ADDR, 0x06, 0x3000);
|
||||
}
|
||||
if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) {
|
||||
uint16_t value = i2c_read_reg(PLL_CTRL2);
|
||||
uint16_t value = adac_read_word(AC101_ADDR, PLL_CTRL2);
|
||||
value |= 0x8000;
|
||||
i2c_write_reg(PLL_CTRL2, value);
|
||||
adac_write_word(AC101_ADDR, PLL_CTRL2, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,8 +305,8 @@ static void ac101_start(ac_module_t mode) {
|
||||
*
|
||||
*/
|
||||
static void ac101_stop(void) {
|
||||
uint16_t value = i2c_read_reg(PLL_CTRL2);
|
||||
uint16_t value = adac_read_word(AC101_ADDR, PLL_CTRL2);
|
||||
value &= ~0x8000;
|
||||
i2c_write_reg(PLL_CTRL2, value);
|
||||
adac_write_word(AC101_ADDR, PLL_CTRL2, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/i2c.h"
|
||||
|
||||
typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e;
|
||||
|
||||
@@ -28,3 +29,10 @@ extern const struct adac_s dac_tas57xx;
|
||||
extern const struct adac_s dac_tas5713;
|
||||
extern const struct adac_s dac_ac101;
|
||||
extern const struct adac_s dac_external;
|
||||
|
||||
int adac_init(char *config, int i2c_port);
|
||||
void adac_deinit(void);
|
||||
esp_err_t adac_write_byte(int i2c_addr, uint8_t reg, uint8_t val);
|
||||
esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val);
|
||||
uint8_t adac_read_byte(int i2c_addr, uint8_t reg);
|
||||
uint16_t adac_read_word(int i2c_addr, uint8_t reg);
|
||||
|
||||
164
components/squeezelite/adac_core.c
Normal file
164
components/squeezelite/adac_core.c
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
* Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <driver/i2s.h>
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_log.h"
|
||||
#include "adac.h"
|
||||
|
||||
static const char TAG[] = "DAC core";
|
||||
static int i2c_port = -1;
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
int adac_init(char *config, int i2c_port_num) {
|
||||
char *p;
|
||||
int i2c_addr = 0;
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = -1,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = -1,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 250000,
|
||||
};
|
||||
|
||||
if ((p = strcasestr(config, "i2c")) != NULL) i2c_addr = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
|
||||
|
||||
if (i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) {
|
||||
ESP_LOGW(TAG, "DAC does not use i2c");
|
||||
return i2c_addr;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "DAC uses I2C port:%d, sda:%d, scl:%d", i2c_port, i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
|
||||
// we have an I2C configured
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
|
||||
|
||||
return i2c_addr;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* close
|
||||
*/
|
||||
void adac_deinit(void) {
|
||||
if (i2c_port != -1) i2c_driver_delete(i2c_port);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t adac_write_byte(int i2c_addr,uint8_t reg, uint8_t val) {
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
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, val, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C write failed");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
uint8_t adac_read_byte(int i2c_addr, uint8_t reg) {
|
||||
uint8_t data = 255;
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
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_start(cmd);
|
||||
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_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C read failed");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
uint16_t adac_read_word(int i2c_addr, uint8_t reg) {
|
||||
uint8_t data[2] = { 255, 255 };
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
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_start(cmd);
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_read(cmd, data, 2, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C read failed");
|
||||
}
|
||||
|
||||
return (data[0] << 8) | data[1];
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val)
|
||||
{
|
||||
uint8_t data[] = { i2c_addr << 1, reg,
|
||||
val >> 8, val & 0xff };
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
i2c_master_write(cmd, data, 4, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C write failed");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -67,8 +67,8 @@ u16_t get_plugged(void) {
|
||||
return jack_inserted_svc() ? PLUG_HEADPHONE : 0;
|
||||
}
|
||||
|
||||
u8_t get_battery(void) {
|
||||
return (battery_level_svc() * 16) / 100;
|
||||
u16_t get_battery(void) {
|
||||
return (u16_t) (battery_value_svc() * 128) & 0x0fff;
|
||||
}
|
||||
|
||||
void set_name(char *name) {
|
||||
|
||||
@@ -77,7 +77,7 @@ extern mutex_type slimp_mutex;
|
||||
#define PLUG_HEADPHONE 0x04
|
||||
u16_t get_RSSI(void); // must provide or define as 0xffff
|
||||
u16_t get_plugged(void); // must provide or define as 0x0
|
||||
u8_t get_battery(void); // must provide 0..15 or define as 0x0
|
||||
u16_t get_battery(void); // must provide 12 bits data or define as 0x0 (exact meaning is device-dependant)
|
||||
|
||||
// set name
|
||||
void set_name(char *name); // can be defined as an empty macro
|
||||
|
||||
100
components/squeezelite/external/dac_external.c
vendored
100
components/squeezelite/external/dac_external.c
vendored
@@ -20,7 +20,6 @@
|
||||
|
||||
static const char TAG[] = "DAC external";
|
||||
|
||||
static void deinit(void) { }
|
||||
static void speaker(bool active) { }
|
||||
static void headset(bool active) { }
|
||||
static bool volume(unsigned left, unsigned right) { return false; }
|
||||
@@ -28,48 +27,30 @@ static void power(adac_power_e mode);
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
|
||||
|
||||
static bool i2c_json_execute(char *set);
|
||||
static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val);
|
||||
static uint8_t i2c_read_reg(uint8_t reg);
|
||||
|
||||
const struct adac_s dac_external = { "i2s", init, deinit, power, speaker, headset, volume };
|
||||
static int i2c_port, i2c_addr;
|
||||
const struct adac_s dac_external = { "i2s", init, adac_deinit, power, speaker, headset, volume };
|
||||
static cJSON *i2c_json;
|
||||
static int i2c_addr;
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
char *p;
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = -1,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = -1,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 250000,
|
||||
};
|
||||
|
||||
if ((p = strcasestr(config, "i2c")) != NULL) i2c_addr = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
|
||||
|
||||
i2c_addr = adac_init(config, i2c_port_num);
|
||||
if (!i2c_addr) return false;
|
||||
|
||||
ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr);
|
||||
|
||||
p = config_alloc_get_str("dac_controlset", CONFIG_DAC_CONTROLSET, NULL);
|
||||
i2c_json = cJSON_Parse(p);
|
||||
|
||||
if (!i2c_addr || !i2c_json || i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) {
|
||||
if (!i2c_json) {
|
||||
if (p) free(p);
|
||||
ESP_LOGW(TAG, "No i2c controlset found");
|
||||
ESP_LOGW(TAG, "no i2c controlset found");
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "DAC uses I2C @%d with sda:%d, scl:%d", i2c_addr, i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
|
||||
// we have an I2C configured
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
|
||||
|
||||
if (!i2c_json_execute("init")) {
|
||||
ESP_LOGE(TAG, "could not intialize DAC");
|
||||
@@ -105,70 +86,17 @@ bool i2c_json_execute(char *set) {
|
||||
if (!reg || !val) continue;
|
||||
|
||||
if (!mode) {
|
||||
i2c_write_reg(reg->valueint, val->valueint);
|
||||
adac_write_byte(i2c_addr, reg->valueint, val->valueint);
|
||||
} else if (!strcasecmp(mode->valuestring, "or")) {
|
||||
uint8_t data = i2c_read_reg(reg->valueint);
|
||||
uint8_t data = adac_read_byte(i2c_addr,reg->valueint);
|
||||
data |= (uint8_t) val->valueint;
|
||||
i2c_write_reg(reg->valueint, data);
|
||||
adac_write_byte(i2c_addr, reg->valueint, data);
|
||||
} else if (!strcasecmp(mode->valuestring, "and")) {
|
||||
uint8_t data = i2c_read_reg(reg->valueint);
|
||||
uint8_t data = adac_read_byte(i2c_addr, reg->valueint);
|
||||
data &= (uint8_t) val->valueint;
|
||||
i2c_write_reg(reg->valueint, data);
|
||||
adac_write_byte(i2c_addr, reg->valueint, data);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val) {
|
||||
esp_err_t ret;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
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, val, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C write failed");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static uint8_t i2c_read_reg(uint8_t reg) {
|
||||
esp_err_t ret;
|
||||
uint8_t data = 0;
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
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_start(cmd);
|
||||
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_stop(cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C read failed");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -151,14 +151,19 @@ static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_
|
||||
LOG_WARN("error parsing esds");
|
||||
return -1;
|
||||
}
|
||||
mp4_desc_length(&ptr);
|
||||
int desc_len = mp4_desc_length(&ptr);
|
||||
info.profile = *ptr >> 3;
|
||||
info.sampRateCore = (*ptr++ & 0x07) << 1;
|
||||
info.sampRateCore |= (*ptr >> 7) & 0x01;
|
||||
info.sampRateCore = rates[info.sampRateCore];
|
||||
info.nChans = (*ptr & 0x7f) >> 3;
|
||||
*channels_p = info.nChans;
|
||||
*samplerate_p = info.sampRateCore;
|
||||
info.sampRateCore = rates[info.sampRateCore];
|
||||
info.nChans = (*ptr++ & 0x7f) >> 3;
|
||||
*channels_p = info.nChans;
|
||||
if (desc_len > 2 && ((ptr[0] << 3) | (ptr[1] >> 5)) == 0x2b7 && (ptr[1] & 0x1f) == 0x05 && (ptr[2] & 0x80)) {
|
||||
*samplerate_p = rates[(ptr[2] & 0x78) >> 3];
|
||||
LOG_WARN("AAC SBR mode activated => high CPU consumption expected, please use LMS proxy to mitigate");
|
||||
} else {
|
||||
*samplerate_p = info.sampRateCore;
|
||||
}
|
||||
HAAC(a, SetRawBlockParams, a->hAac, 0, &info);
|
||||
LOG_DEBUG("playable aac track: %u (p:%x, r:%d, c:%d)", trak, info.profile, info.sampRateCore, info.nChans);
|
||||
play = trak;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "adac.h"
|
||||
|
||||
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(*array))
|
||||
#define TAS5713 0x36 /* i2c address of TAS5713 */
|
||||
#define TAS5713 (0x36 >> 1) /* i2c address of TAS5713 */
|
||||
|
||||
// TAS5713 I2C-bus register addresses
|
||||
|
||||
@@ -40,13 +40,12 @@
|
||||
static const char TAG[] = "TAS5713";
|
||||
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
|
||||
static void deinit(void);
|
||||
static void speaker(bool active) { };
|
||||
static void headset(bool active) { } ;
|
||||
static bool volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode) { };
|
||||
|
||||
const struct adac_s dac_tas5713 = {"TAS5713", init, deinit, power, speaker, headset, volume};
|
||||
const struct adac_s dac_tas5713 = {"TAS5713", init, adac_deinit, power, speaker, headset, volume};
|
||||
|
||||
struct tas5713_cmd_s {
|
||||
uint8_t reg;
|
||||
@@ -63,53 +62,30 @@ typedef enum {
|
||||
TAS57_VOLUME
|
||||
} dac_cmd_e;
|
||||
|
||||
static int i2c_port;
|
||||
|
||||
static void tas5713_set(uint8_t reg, uint8_t val);
|
||||
static uint8_t tas5713_get(uint8_t reg);
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
char *p;
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = -1,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = -1,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 250000,
|
||||
};
|
||||
|
||||
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
|
||||
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
esp_err_t res = i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
|
||||
|
||||
/* find if there is a tas5713 attached. Reg 0 should read non-zero if so */
|
||||
if (!tas5713_get(0x00)) {
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
|
||||
/* find if there is a tas5713 attached. Reg 0 should read non-zero but not 255 if so */
|
||||
adac_init(config, i2c_port);
|
||||
if (adac_read_byte(TAS5713, 0x00) == 255) {
|
||||
ESP_LOGW(TAG, "No TAS5713 detected");
|
||||
i2c_driver_delete(i2c_port);
|
||||
adac_deinit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "TAS5713 uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
ESP_LOGI(TAG, "TAS5713 found");
|
||||
|
||||
/* do the init sequence */
|
||||
tas5713_set(TAS5713_OSC_TRIM, 0x00); /* a delay is required after this */
|
||||
esp_err_t res = adac_write_byte(TAS5713, TAS5713_OSC_TRIM, 0x00); /* a delay is required after this */
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
tas5713_set(TAS5713_SERIAL_DATA_INTERFACE, 0x03); /* I2S LJ 16 bit */
|
||||
tas5713_set(TAS5713_SYSTEM_CTRL2, 0x00); /* exit all channel shutdown */
|
||||
tas5713_set(TAS5713_SOFT_MUTE, 0x00); /* unmute */
|
||||
tas5713_set(TAS5713_VOL_MASTER, 0x20);
|
||||
tas5713_set(TAS5713_VOL_CH1, 0x30);
|
||||
tas5713_set(TAS5713_VOL_CH2, 0x30);
|
||||
tas5713_set(TAS5713_VOL_HEADPHONE, 0xFF);
|
||||
res |= adac_write_byte(TAS5713, TAS5713_SERIAL_DATA_INTERFACE, 0x03); /* I2S LJ 16 bit */
|
||||
res |= adac_write_byte(TAS5713, TAS5713_SYSTEM_CTRL2, 0x00); /* exit all channel shutdown */
|
||||
res |= adac_write_byte(TAS5713, TAS5713_SOFT_MUTE, 0x00); /* unmute */
|
||||
res |= adac_write_byte(TAS5713, TAS5713_VOL_MASTER, 0x20);
|
||||
res |= adac_write_byte(TAS5713, TAS5713_VOL_CH1, 0x30);
|
||||
res |= adac_write_byte(TAS5713, TAS5713_VOL_CH2, 0x30);
|
||||
res |= adac_write_byte(TAS5713, TAS5713_VOL_HEADPHONE, 0xFF);
|
||||
|
||||
/* The tas5713 typically has the mclk connected to the sclk. In this
|
||||
configuration, mclk must be a multiple of the sclk. The lowest workable
|
||||
@@ -126,70 +102,9 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static void deinit(void) {
|
||||
i2c_driver_delete(i2c_port);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change volume
|
||||
*/
|
||||
static bool volume(unsigned left, unsigned right) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* DAC specific commands
|
||||
*/
|
||||
void tas5713_set(uint8_t reg, uint8_t val) {
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
ESP_LOGI(TAG,"TAS5713 send %x %x", reg, val);
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd,
|
||||
TAS5713 | I2C_MASTER_WRITE,
|
||||
I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, val, I2C_MASTER_NACK);
|
||||
i2c_master_stop(i2c_cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS);
|
||||
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Could not send command to TAS5713 %d", ret);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* Read from i2c for the tas5713. This doubles as tas5713 detect. This function
|
||||
* returns zero on error, so read register 0x00 for tas detect, which will be
|
||||
* non-zero in this application.
|
||||
*/
|
||||
static uint8_t tas5713_get(uint8_t reg) {
|
||||
int ret;
|
||||
uint8_t data = 0;
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, TAS5713 | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, reg, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, TAS5713 | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG,"TAS5713 reg 0x%x is 0x%x", reg, data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -18,19 +18,18 @@
|
||||
#include "esp_log.h"
|
||||
#include "adac.h"
|
||||
|
||||
#define TAS575x 0x98
|
||||
#define TAS578x 0x90
|
||||
#define TAS575x (0x98 >> 1)
|
||||
#define TAS578x (0x90 >> 1)
|
||||
|
||||
static const char TAG[] = "TAS575x/8x";
|
||||
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
|
||||
static void deinit(void);
|
||||
static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static bool volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode);
|
||||
|
||||
const struct adac_s dac_tas57xx = { "TAS57xx", init, deinit, power, speaker, headset, volume };
|
||||
const struct adac_s dac_tas57xx = { "TAS57xx", init, adac_deinit, power, speaker, headset, volume };
|
||||
|
||||
struct tas57xx_cmd_s {
|
||||
uint8_t reg;
|
||||
@@ -60,7 +59,7 @@ static const struct tas57xx_cmd_s tas57xx_cmd[] = {
|
||||
};
|
||||
|
||||
static uint8_t tas57_addr;
|
||||
static int i2c_port;
|
||||
int i2c_port_x;
|
||||
|
||||
static void dac_cmd(dac_cmd_e cmd, ...);
|
||||
static int tas57_detect(void);
|
||||
@@ -68,32 +67,15 @@ static int tas57_detect(void);
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
char *p;
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = -1,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = -1,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 250000,
|
||||
};
|
||||
|
||||
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
|
||||
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
|
||||
|
||||
static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
|
||||
// find which TAS we are using (if any)
|
||||
i2c_port_x = i2c_port;
|
||||
adac_init(config, i2c_port);
|
||||
tas57_addr = tas57_detect();
|
||||
|
||||
|
||||
if (!tas57_addr) {
|
||||
ESP_LOGW(TAG, "No TAS57xx detected");
|
||||
i2c_driver_delete(i2c_port);
|
||||
adac_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -101,7 +83,7 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
|
||||
for (int i = 0; tas57xx_init_sequence[i].reg != 0xff; i++) {
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, (tas57_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK);
|
||||
ESP_LOGD(TAG, "i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value);
|
||||
@@ -110,8 +92,6 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
i2c_master_stop(i2c_cmd);
|
||||
esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
ESP_LOGI(TAG, "TAS57xx uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not intialize TAS57xx %d", res);
|
||||
@@ -121,13 +101,6 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static void deinit(void) {
|
||||
i2c_driver_delete(i2c_port);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change volume
|
||||
*/
|
||||
@@ -176,25 +149,17 @@ void dac_cmd(dac_cmd_e cmd, ...) {
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
va_start(args, cmd);
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
switch(cmd) {
|
||||
case TAS57_VOLUME:
|
||||
ESP_LOGE(TAG, "DAC volume not handled yet");
|
||||
break;
|
||||
default:
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].value, I2C_MASTER_NACK);
|
||||
i2c_master_stop(i2c_cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS);
|
||||
ret = adac_write_byte(tas57_addr, tas57xx_cmd[cmd].reg, tas57xx_cmd[cmd].value);
|
||||
}
|
||||
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not intialize TAS57xx %d", ret);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "could not use TAS57xx %d", ret);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
@@ -204,25 +169,10 @@ void dac_cmd(dac_cmd_e cmd, ...) {
|
||||
* TAS57 detection
|
||||
*/
|
||||
static int tas57_detect(void) {
|
||||
uint8_t data, addr[] = {TAS578x, TAS575x};
|
||||
int ret;
|
||||
uint8_t addr[] = {TAS578x, TAS575x};
|
||||
|
||||
for (int i = 0; i < sizeof(addr); i++) {
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, 00, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
if (adac_read_byte(addr[i], 0) != 255) {
|
||||
ESP_LOGI(TAG, "Detected TAS @0x%x", addr[i]);
|
||||
return addr[i];
|
||||
}
|
||||
|
||||
237
plugin/SqueezeESP32/FirmwareHelper.pm
Normal file
237
plugin/SqueezeESP32/FirmwareHelper.pm
Normal file
@@ -0,0 +1,237 @@
|
||||
package Plugins::SqueezeESP32::FirmwareHelper;
|
||||
|
||||
use strict;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use File::Spec::Functions qw(catfile);
|
||||
use JSON::XS::VersionOneAndTwo;
|
||||
|
||||
use Slim::Utils::Log;
|
||||
use Slim::Utils::Prefs;
|
||||
|
||||
use constant FIRMWARE_POLL_INTERVAL => 3600 * (5 + rand());
|
||||
use constant GITHUB_RELEASES_URI => "https://api.github.com/repos/sle118/squeezelite-esp32/releases";
|
||||
use constant GITHUB_ASSET_URI => GITHUB_RELEASES_URI . "/assets/";
|
||||
use constant GITHUB_DOWNLOAD_URI => "https://github.com/sle118/squeezelite-esp32/releases/download/";
|
||||
my $FW_DOWNLOAD_ID_REGEX = qr|plugins/SqueezeESP32/firmware/(-?\d+)|;
|
||||
my $FW_DOWNLOAD_REGEX = qr|plugins/SqueezeESP32/firmware/([-a-z0-9-/.]+\.bin)$|i;
|
||||
my $FW_FILENAME_REGEX = qr/^squeezelite-esp32-.*\.bin(\.tmp)?$/;
|
||||
my $FW_TAG_REGEX = qr/\/(ESP32-A1S|SqueezeAmp|I2S-4MFlash)\.(16|32)\.(\d+)\.(.*)\//;
|
||||
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
my $log = logger('plugin.squeezeesp32');
|
||||
|
||||
sub init {
|
||||
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_ID_REGEX, \&handleFirmwareDownload);
|
||||
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownloadDirect);
|
||||
|
||||
# start checking for firmware updates
|
||||
Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + 30 + rand(30), \&prefetchFirmware);
|
||||
}
|
||||
|
||||
sub prefetchFirmware {
|
||||
Slim::Utils::Timers::killTimers(undef, \&prefetchFirmware);
|
||||
my $releaseInfo = $prefs->get('lastReleaseTagUsed');
|
||||
|
||||
Slim::Networking::SimpleAsyncHTTP->new(
|
||||
sub {
|
||||
my $http = shift;
|
||||
my $content = eval { from_json( $http->content ) };
|
||||
|
||||
if (!$content || !ref $content) {
|
||||
$@ && $log->error("Failed to parse response: $@");
|
||||
}
|
||||
|
||||
my $regex = $releaseInfo->{model} . '\.' . $releaseInfo->{res} . '\.\d+\.' . $releaseInfo->{branch};
|
||||
my $url;
|
||||
foreach (@$content) {
|
||||
if ($_->{tag_name} =~ /$regex/ && $_->{assets} && ref $_->{assets}) {
|
||||
($url) = grep /\.bin$/, map {
|
||||
$_->{browser_download_url}
|
||||
} @{$_->{assets}};
|
||||
|
||||
last if $url;
|
||||
}
|
||||
}
|
||||
|
||||
downloadFirmwareFile(sub {
|
||||
main::INFOLOG && $log->is_info && $log->info("Pre-cached firmware file: " . $_[0]);
|
||||
}, sub {
|
||||
my ($http, $error, $url, $code) = @_;
|
||||
$error ||= ($http && $http->error) || 'unknown error';
|
||||
$url ||= ($http && $http->url) || 'no URL';
|
||||
|
||||
$log->error(sprintf("Failed to get firmware image from Github: %s (%s)", $error || $http->error, $url));
|
||||
}, $url) if $url && $url =~ /^https?/;
|
||||
|
||||
},
|
||||
sub {
|
||||
my ($http, $error) = @_;
|
||||
$log->error("Failed to get releases from Github: $error");
|
||||
},
|
||||
{
|
||||
timeout => 10,
|
||||
cache => 1,
|
||||
expires => 3600
|
||||
}
|
||||
)->get(GITHUB_RELEASES_URI) if $releaseInfo;
|
||||
|
||||
Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + FIRMWARE_POLL_INTERVAL, \&prefetchFirmware);
|
||||
}
|
||||
|
||||
sub handleFirmwareDownload {
|
||||
my ($httpClient, $response) = @_;
|
||||
|
||||
my $request = $response->request;
|
||||
|
||||
my $_errorDownloading = sub {
|
||||
_errorDownloading($httpClient, $response, @_);
|
||||
};
|
||||
|
||||
my $id;
|
||||
if (!defined $request || !(($id) = $request->uri =~ $FW_DOWNLOAD_ID_REGEX)) {
|
||||
return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
|
||||
}
|
||||
|
||||
# this is the magic number used on the client to figure out whether the plugin does support download proxying
|
||||
if ($id == -99) {
|
||||
$response->code(204);
|
||||
$response->header('Access-Control-Allow-Origin' => '*');
|
||||
|
||||
$httpClient->send_response($response);
|
||||
return Slim::Web::HTTP::closeHTTPSocket($httpClient);
|
||||
}
|
||||
|
||||
Slim::Networking::SimpleAsyncHTTP->new(
|
||||
sub {
|
||||
my $http = shift;
|
||||
my $content = eval { from_json( $http->content ) };
|
||||
|
||||
if (!$content || !ref $content) {
|
||||
$@ && $log->error("Failed to parse response: $@");
|
||||
return $_errorDownloading->($http);
|
||||
}
|
||||
elsif (!$content->{browser_download_url} || !$content->{name}) {
|
||||
return $_errorDownloading->($http, 'No download URL found');
|
||||
}
|
||||
|
||||
downloadFirmwareFile(sub {
|
||||
my $firmwareFile = shift;
|
||||
$response->code(200);
|
||||
Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
|
||||
}, $_errorDownloading, $content->{browser_download_url}, $content->{name});
|
||||
},
|
||||
$_errorDownloading,
|
||||
{
|
||||
timeout => 10,
|
||||
cache => 1,
|
||||
expires => 86400
|
||||
}
|
||||
)->get(GITHUB_ASSET_URI . $id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub handleFirmwareDownloadDirect {
|
||||
my ($httpClient, $response) = @_;
|
||||
|
||||
my $request = $response->request;
|
||||
|
||||
my $_errorDownloading = sub {
|
||||
_errorDownloading($httpClient, $response, @_);
|
||||
};
|
||||
|
||||
my $path;
|
||||
if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) {
|
||||
return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
|
||||
}
|
||||
|
||||
main::INFOLOG && $log->is_info && $log->info("Requesting firmware from: $path");
|
||||
|
||||
downloadFirmwareFile(sub {
|
||||
my $firmwareFile = shift;
|
||||
$response->code(200);
|
||||
Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
|
||||
}, $_errorDownloading, GITHUB_DOWNLOAD_URI . $path);
|
||||
}
|
||||
|
||||
sub downloadFirmwareFile {
|
||||
my ($cb, $ecb, $url, $name) = @_;
|
||||
|
||||
# keep track of the last firmware we requested, to prefetch it in the future
|
||||
_getFirmwareTag($url);
|
||||
|
||||
$name ||= basename($url);
|
||||
|
||||
if ($name !~ $FW_FILENAME_REGEX) {
|
||||
return $ecb->(undef, 'Unexpected firmware image name: ' . $name, $url, 400);
|
||||
}
|
||||
|
||||
my $updatesDir = Slim::Utils::OSDetect::dirsFor('updates');
|
||||
my $firmwareFile = catfile($updatesDir, $name);
|
||||
Slim::Utils::Misc::deleteFiles($updatesDir, $FW_FILENAME_REGEX, $firmwareFile);
|
||||
|
||||
if (-f $firmwareFile) {
|
||||
main::INFOLOG && $log->is_info && $log->info("Found cached firmware file");
|
||||
return $cb->($firmwareFile);
|
||||
}
|
||||
|
||||
Slim::Networking::SimpleAsyncHTTP->new(
|
||||
sub {
|
||||
my $http = shift;
|
||||
|
||||
if ($http->code != 200 || !-e "$firmwareFile.tmp") {
|
||||
return $ecb->($http, $http->mess);
|
||||
}
|
||||
|
||||
rename "$firmwareFile.tmp", $firmwareFile or return $ecb->($http, "Unable to rename temporary $firmwareFile file" );
|
||||
|
||||
return $cb->($firmwareFile);
|
||||
},
|
||||
$ecb,
|
||||
{
|
||||
saveAs => "$firmwareFile.tmp",
|
||||
}
|
||||
)->get($url);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _getFirmwareTag {
|
||||
my ($url) = @_;
|
||||
|
||||
if (my ($model, $resolution, $version, $branch) = $url =~ $FW_TAG_REGEX) {
|
||||
my $releaseInfo = {
|
||||
model => $model,
|
||||
res => $resolution,
|
||||
version => $version,
|
||||
branch => $branch
|
||||
};
|
||||
|
||||
$prefs->set('lastReleaseTagUsed', $releaseInfo);
|
||||
|
||||
return $releaseInfo;
|
||||
}
|
||||
}
|
||||
|
||||
sub _errorDownloading {
|
||||
my ($httpClient, $response, $http, $error, $url, $code) = @_;
|
||||
|
||||
$error ||= ($http && $http->error) || 'unknown error';
|
||||
$url ||= ($http && $http->url) || 'no URL';
|
||||
$code ||= ($http && $http->code) || 500;
|
||||
|
||||
$log->error(sprintf("Failed to get data from Github: %s (%s)", $error || $http->error, $url));
|
||||
|
||||
$response->headers->remove_content_headers;
|
||||
$response->code($code);
|
||||
$response->content_type('text/plain');
|
||||
$response->header('Connection' => 'close');
|
||||
$response->content('');
|
||||
|
||||
$httpClient->send_response($response);
|
||||
Slim::Web::HTTP::closeHTTPSocket($httpClient);
|
||||
};
|
||||
|
||||
|
||||
1;
|
||||
@@ -343,4 +343,9 @@ sub lineInOutStatus {
|
||||
}
|
||||
}
|
||||
|
||||
sub voltage {
|
||||
my $voltage = Slim::Networking::Slimproto::voltage(shift) || return 0;
|
||||
return sprintf("%.2f", ($voltage >> 4) / 128);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -3,14 +3,13 @@ package Plugins::SqueezeESP32::Plugin;
|
||||
use strict;
|
||||
|
||||
use base qw(Slim::Plugin::Base);
|
||||
use File::Basename qw(basename);
|
||||
use File::Spec::Functions qw(catfile);
|
||||
use JSON::XS::VersionOneAndTwo;
|
||||
|
||||
use Slim::Utils::Prefs;
|
||||
use Slim::Utils::Log;
|
||||
use Slim::Web::ImageProxy;
|
||||
|
||||
use Plugins::SqueezeESP32::FirmwareHelper;
|
||||
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
|
||||
my $log = Slim::Utils::Log->addLogCategory({
|
||||
@@ -19,12 +18,6 @@ my $log = Slim::Utils::Log->addLogCategory({
|
||||
'description' => 'PLUGIN_SQUEEZEESP32',
|
||||
});
|
||||
|
||||
use constant GITHUB_ASSET_URI => "https://api.github.com/repos/sle118/squeezelite-esp32/releases/assets/";
|
||||
use constant GITHUB_DOWNLOAD_URI => "https://github.com/sle118/squeezelite-esp32/releases/download/";
|
||||
my $FW_DOWNLOAD_ID_REGEX = qr|plugins/SqueezeESP32/firmware/(-?\d+)|;
|
||||
my $FW_DOWNLOAD_REGEX = qr|plugins/SqueezeESP32/firmware/([-a-z0-9-/.]+\.bin)$|i;
|
||||
my $FW_FILENAME_REGEX = qr/^squeezelite-esp32-.*\.bin(\.tmp)?$/;
|
||||
|
||||
# migrate 'eq' pref, as that's a reserved word and could cause problems in the future
|
||||
$prefs->migrateClient(1, sub {
|
||||
my ($cprefs, $client) = @_;
|
||||
@@ -68,8 +61,7 @@ sub initPlugin {
|
||||
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
|
||||
Slim::Control::Request::subscribe( \&onStopClear, [ ['playlist'], ['stop', 'clear'] ]);
|
||||
|
||||
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_ID_REGEX, \&handleFirmwareDownload);
|
||||
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownloadDirect);
|
||||
Plugins::SqueezeESP32::FirmwareHelper->init();
|
||||
}
|
||||
|
||||
sub onStopClear {
|
||||
@@ -111,137 +103,4 @@ sub setEQ {
|
||||
$client->send_equalizer(\@eqParams);
|
||||
}
|
||||
|
||||
sub handleFirmwareDownload {
|
||||
my ($httpClient, $response) = @_;
|
||||
|
||||
my $request = $response->request;
|
||||
|
||||
my $_errorDownloading = sub {
|
||||
_errorDownloading($httpClient, $response, @_);
|
||||
};
|
||||
|
||||
my $id;
|
||||
if (!defined $request || !(($id) = $request->uri =~ $FW_DOWNLOAD_ID_REGEX)) {
|
||||
return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
|
||||
}
|
||||
|
||||
# this is the magic number used on the client to figure out whether the plugin does support download proxying
|
||||
if ($id == -99) {
|
||||
$response->code(204);
|
||||
$response->header('Access-Control-Allow-Origin' => '*');
|
||||
|
||||
$httpClient->send_response($response);
|
||||
return Slim::Web::HTTP::closeHTTPSocket($httpClient);
|
||||
}
|
||||
|
||||
Slim::Networking::SimpleAsyncHTTP->new(
|
||||
sub {
|
||||
my $http = shift;
|
||||
my $content = eval { from_json( $http->content ) };
|
||||
|
||||
if (!$content || !ref $content) {
|
||||
$@ && $log->error("Failed to parse response: $@");
|
||||
return $_errorDownloading->($http);
|
||||
}
|
||||
elsif (!$content->{browser_download_url} || !$content->{name}) {
|
||||
return $_errorDownloading->($http, 'No download URL found');
|
||||
}
|
||||
|
||||
downloadAndStreamFirmware($httpClient, $response, $content->{browser_download_url}, $content->{name});
|
||||
},
|
||||
$_errorDownloading,
|
||||
{
|
||||
timeout => 10,
|
||||
cache => 1,
|
||||
expires => 86400
|
||||
}
|
||||
)->get(GITHUB_ASSET_URI . $id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub handleFirmwareDownloadDirect {
|
||||
my ($httpClient, $response) = @_;
|
||||
|
||||
my $request = $response->request;
|
||||
|
||||
my $_errorDownloading = sub {
|
||||
_errorDownloading($httpClient, $response, @_);
|
||||
};
|
||||
|
||||
my $path;
|
||||
if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) {
|
||||
return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
|
||||
}
|
||||
|
||||
main::INFOLOG && $log->is_info && $log->info("Requesting firmware from: $path");
|
||||
|
||||
downloadAndStreamFirmware($httpClient, $response, GITHUB_DOWNLOAD_URI . $path);
|
||||
}
|
||||
|
||||
sub downloadAndStreamFirmware {
|
||||
my ($httpClient, $response, $url, $name) = @_;
|
||||
|
||||
my $_errorDownloading = sub {
|
||||
_errorDownloading($httpClient, $response, @_);
|
||||
};
|
||||
|
||||
$name ||= basename($url);
|
||||
|
||||
if ($name !~ $FW_FILENAME_REGEX) {
|
||||
return $_errorDownloading->(undef, 'Unexpected firmware image name: ' . $name, $url, 400);
|
||||
}
|
||||
|
||||
my $updatesDir = Slim::Utils::OSDetect::dirsFor('updates');
|
||||
my $firmwareFile = catfile($updatesDir, $name);
|
||||
Slim::Utils::Misc::deleteFiles($updatesDir, $FW_FILENAME_REGEX, $firmwareFile);
|
||||
|
||||
if (-f $firmwareFile) {
|
||||
main::INFOLOG && $log->is_info && $log->info("Found cached firmware version");
|
||||
$response->code(200);
|
||||
return Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
|
||||
}
|
||||
|
||||
Slim::Networking::SimpleAsyncHTTP->new(
|
||||
sub {
|
||||
my $http = shift;
|
||||
|
||||
if ($http->code != 200 || !-e "$firmwareFile.tmp") {
|
||||
return $_errorDownloading->($http, $http->mess);
|
||||
}
|
||||
|
||||
rename "$firmwareFile.tmp", $firmwareFile or return $_errorDownloading->($http, "Unable to rename temporary $firmwareFile file" );
|
||||
|
||||
$response->code(200);
|
||||
Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
|
||||
},
|
||||
$_errorDownloading,
|
||||
{
|
||||
saveAs => "$firmwareFile.tmp",
|
||||
}
|
||||
)->get($url);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _errorDownloading {
|
||||
my ($httpClient, $response, $http, $error, $url, $code) = @_;
|
||||
|
||||
$error ||= ($http && $http->error) || 'unknown error';
|
||||
$url ||= ($http && $http->url) || 'no URL';
|
||||
$code ||= ($http && $http->code) || 500;
|
||||
|
||||
$log->error(sprintf("Failed to get data from Github: %s (%s)", $error || $http->error, $url));
|
||||
|
||||
$response->headers->remove_content_headers;
|
||||
$response->code($code);
|
||||
$response->content_type('text/plain');
|
||||
$response->header('Connection' => 'close');
|
||||
$response->content('');
|
||||
|
||||
$httpClient->send_response($response);
|
||||
Slim::Web::HTTP::closeHTTPSocket($httpClient);
|
||||
};
|
||||
|
||||
|
||||
1;
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<name>PLUGIN_SQUEEZEESP32</name>
|
||||
<description>PLUGIN_SQUEEZEESP32_DESC</description>
|
||||
<module>Plugins::SqueezeESP32::Plugin</module>
|
||||
<version>0.211</version>
|
||||
<version>0.310</version>
|
||||
<creator>Philippe</creator>
|
||||
</extensions>
|
||||
|
||||
Reference in New Issue
Block a user