Prechádzať zdrojové kódy

Merge remote-tracking branch 'origin/master-cmake' into master-cmake

Sebastien 3 rokov pred
rodič
commit
a105f7fd99

+ 3 - 2
components/services/battery.c

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

+ 1 - 1
components/services/monitor.h

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

+ 0 - 0
components/squeezelite/.sc3357753833280144641.c


+ 67 - 140
components/squeezelite/ac101/ac101.c

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

+ 8 - 0
components/squeezelite/adac.h

@@ -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 - 0
components/squeezelite/adac_core.c

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

+ 2 - 2
components/squeezelite/embedded.c

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

+ 1 - 1
components/squeezelite/embedded.h

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

+ 14 - 86
components/squeezelite/external/dac_external.c

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

+ 10 - 5
components/squeezelite/helix-aac.c

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

+ 16 - 101
components/squeezelite/tas57xx/dac_5713.c

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

+ 15 - 65
components/squeezelite/tas57xx/dac_57xx.c

@@ -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 - 0
plugin/SqueezeESP32/FirmwareHelper.pm

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

+ 5 - 0
plugin/SqueezeESP32/Player.pm

@@ -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 - 144
plugin/SqueezeESP32/Plugin.pm

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

+ 1 - 1
plugin/SqueezeESP32/install.xml

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