浏览代码

Improved audio ui, bug fixes

Sebastien L 1 年之前
父节点
当前提交
7ac628a29d
共有 34 个文件被更改,包括 1136 次插入162 次删除
  1. 2 2
      components/platform_config/platform_config.c
  2. 4 6
      components/platform_console/app_squeezelite/cmd_squeezelite.c
  3. 561 0
      components/squeezelite/cs4265/cs4265.c
  4. 5 0
      components/wifi-manager/network_status.c
  5. 0 6
      components/wifi-manager/webapp/dist/css/index.60aa97be4459083adfab.css
  6. 二进制
      components/wifi-manager/webapp/dist/css/index.60aa97be4459083adfab.css.gz
  7. 6 0
      components/wifi-manager/webapp/dist/css/index.cd56ff129e3113d8cd3a.css
  8. 二进制
      components/wifi-manager/webapp/dist/css/index.cd56ff129e3113d8cd3a.css.gz
  9. 0 0
      components/wifi-manager/webapp/dist/dist/js/index.bd4c5d.bundle.d.ts
  10. 0 0
      components/wifi-manager/webapp/dist/dist/js/node_vendors.bd4c5d.bundle.d.ts
  11. 0 0
      components/wifi-manager/webapp/dist/index.html
  12. 二进制
      components/wifi-manager/webapp/dist/index.html.gz
  13. 0 0
      components/wifi-manager/webapp/dist/js/index.392dfa.bundle.js
  14. 二进制
      components/wifi-manager/webapp/dist/js/index.392dfa.bundle.js.gz
  15. 0 0
      components/wifi-manager/webapp/dist/js/index.392dfa.bundle.js.map
  16. 0 0
      components/wifi-manager/webapp/dist/js/index.ab1d13.bundle.js
  17. 二进制
      components/wifi-manager/webapp/dist/js/index.ab1d13.bundle.js.gz
  18. 0 0
      components/wifi-manager/webapp/dist/js/index.ab1d13.bundle.js.map
  19. 0 0
      components/wifi-manager/webapp/dist/js/node_vendors.ab1d13.bundle.js
  20. 二进制
      components/wifi-manager/webapp/dist/js/node_vendors.ab1d13.bundle.js.gz
  21. 0 0
      components/wifi-manager/webapp/dist/js/node_vendors.ab1d13.bundle.js.map
  22. 6 0
      components/wifi-manager/webapp/dist/report.html
  23. 42 0
      components/wifi-manager/webapp/dist/src/js/test.d.ts
  24. 3 1
      components/wifi-manager/webapp/dist/webpack/webpack.dev.d.ts
  25. 2 1
      components/wifi-manager/webapp/mock/status.json
  26. 6 2
      components/wifi-manager/webapp/package.json
  27. 102 15
      components/wifi-manager/webapp/src/index.ejs
  28. 332 99
      components/wifi-manager/webapp/src/js/custom.js
  29. 3 3
      components/wifi-manager/webapp/webapp.cmake
  30. 15 15
      components/wifi-manager/webapp/webpack.c
  31. 19 3
      components/wifi-manager/webapp/webpack.config.js
  32. 1 1
      components/wifi-manager/webapp/webpack.h
  33. 17 8
      components/wifi-manager/webapp/webpack/webpack.dev.js
  34. 10 0
      squeezelite.cmake

+ 2 - 2
components/platform_config/platform_config.c

@@ -121,7 +121,7 @@ void config_start_timer(){
 nvs_type_t  config_get_item_type(cJSON * entry){
 	if(entry==NULL){
 		ESP_LOGE(TAG,"null pointer received!");
-		return true;
+		return 0;
 	}
 	cJSON * item_type = cJSON_GetObjectItemCaseSensitive(entry, "type");
 	if(item_type ==NULL ) {
@@ -142,7 +142,7 @@ cJSON * config_set_value_safe(nvs_type_t nvs_type, const char *key,  const void
 		return NULL;
 	}
 
-	cJSON * existing = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
+cJSON * existing = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
 	if(existing !=NULL && nvs_type == NVS_TYPE_STR && config_get_item_type(existing) != NVS_TYPE_STR  ) {
 		ESP_LOGW(TAG, "Storing numeric value from string");
 		numvalue = atof((char *)value);

+ 4 - 6
components/platform_console/app_squeezelite/cmd_squeezelite.c

@@ -61,21 +61,19 @@ static void squeezelite_thread(void *arg){
     for(int i = 0;i<thread_parms.argc; i++){
     	ESP_LOGV(TAG ,"     %s",thread_parms.argv[i]);
     }
-    
     ESP_LOGI(TAG ,"Calling squeezelite");
     int ret = squeezelite_main(thread_parms.argc, thread_parms.argv);
         
-    messaging_post_message(ret > 1 ?  MESSAGING_ERROR : MESSAGING_WARNING,
-                           MESSAGING_CLASS_SYSTEM, "squeezelite exited with error code %d", ret);   
+    cmd_send_messaging("cfg-audio-tmpl",ret > 1 ?  MESSAGING_ERROR : MESSAGING_WARNING,"squeezelite exited with error code %d\n", ret);
 
     if (ret == 1) {
-        int wait = 60;      
+        int wait = 60;
         wait_for_commit();
-        messaging_post_message(MESSAGING_WARNING, MESSAGING_CLASS_SYSTEM, "Rebooting in %d sec", wait); 
+        cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Rebooting in %d sec\n", wait);
         vTaskDelay( pdMS_TO_TICKS(wait * 1000));
         esp_restart();
     } else {
-        messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM, "Correct command line and reboot"); 
+		cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Correct command line and reboot\n");
         vTaskSuspend(NULL);
     }
 

+ 561 - 0
components/squeezelite/cs4265/cs4265.c

@@ -0,0 +1,561 @@
+/* 
+ *  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>
+//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/i2s.h"
+#include "driver/i2c.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "adac.h"
+#include "stdio.h"
+#include "math.h"
+#define CS4265_PULL_UP (0x4F )
+#define CS4265_PULL_DOWN (0x4E )
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+
+static const char TAG[] = "CS4265";
+
+static bool init(char *config, int i2c_port_num, 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);
+static esp_err_t cs4265_update_bit(uint8_t reg_no,uint8_t mask,uint8_t val );
+static esp_err_t set_clock();
+const struct adac_s dac_cs4265 = { "CS4265", init, adac_deinit, power, speaker, headset, volume };
+
+struct cs4265_cmd_s {
+	uint8_t reg;
+	uint8_t value;
+};
+struct cs4265_private {
+	uint8_t format;
+	uint32_t sysclk;
+	i2s_config_t *i2s_config;
+	int i2c_port;
+};
+struct cs4265_private cs4265;
+
+#define CS4265_CHIP_ID				0x1
+#define CS4265_CHIP_ID_VAL			0xD0
+#define CS4265_CHIP_ID_MASK			0xF0
+#define CS4265_REV_ID_MASK			0x0F
+
+#define CS4265_PWRCTL				0x02
+#define CS4265_PWRCTL_PDN			(1 << 0)
+#define CS4265_PWRCTL_PDN_DAC       (1 << 1)
+#define CS4265_PWRCTL_PDN_ADC       (1 << 2)
+#define CS4265_PWRCTL_PDN_MIC       (1 << 3)
+#define CS4265_PWRCTL_FREEZE        (1 << 7)
+#define CS4265_PWRCTL_PDN_ALL   	CS4265_PWRCTL_PDN | CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC
+
+
+
+#define CS4265_DAC_CTL				0x3
+// De-Emphasis Control (Bit 1)
+// The standard 50/15 i2s digital de-emphasis filter response may be implemented for a sample
+// rate of 44.1 kHz when the DeEmph bit is set.  NOTE: De-emphasis is available only in Single-Speed Mode.
+#define CS4265_DAC_CTL_DEEMPH		(1 << 1)
+// MUTE DAC
+// The DAC outputs will mute and the MUTEC pin will become active when this bit is set. Though this bit is
+// active high, it should be noted that the MUTEC pin is active low. The common mode voltage on the outputs
+// will be retained when this bit is set. The muting function is effected, similar to attenuation changes, by the
+// DACSoft and DACZero bits in the DAC Control 2 register.
+#define CS4265_DAC_CTL_MUTE			(1 << 2)
+// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital Interface
+// DAC_DIF1 DAC_DIF0 Description                                    Format Figure
+// 0        0        Left Justified, up to 24-bit data (default)    0       5
+// 0        1        I²S, up to 24-bit data                         1       6
+// 1        0        Right-Justified, 16-bit Data                   2       7
+// 1        1        Right-Justified, 24-bit Data                   3       7
+#define CS4265_DAC_CTL_DIF0			(1 << 4)
+// The required relationship between LRCK, SCLK and SDIN for the DAC is defined by the DAC Digital Interface
+// DAC_DIF1 DAC_DIF0 Description                                    Format Figure
+// 0        0        Left Justified, up to 24-bit data (default)    0       5
+// 0        1        I²S, up to 24-bit data                         1       6
+// 1        0        Right-Justified, 16-bit Data                   2       7
+// 1        1        Right-Justified, 24-bit Data                   3       7
+#define CS4265_DAC_CTL_DIF1			(1 << 5)
+
+
+
+#define CS4265_ADC_CTL				0x4
+#define CS4265_ADC_MASTER			1
+
+#define CS4265_ADC_CTL_MUTE   		(1 << 2)
+#define CS4265_ADC_DIF				(1 << 4)
+#define CS4265_ADC_FM				(3 << 6)
+
+//Master Clock Dividers (Bits 6:4)
+//Sets the frequency of the supplied MCLK signal. 
+//
+//MCLK Divider MCLK Freq2 MCLK Freq1 MCLK Freq0
+// ÷   1 	   0          0           0
+// ÷   1.5 	   0          0           1
+// ÷   2 	   0          1           0
+// ÷   3 	   0          1           1
+// ÷   4 	   1          0           0
+// NA          1          0           1
+// NA 		   1          1           x 
+#define CS4265_MCLK_FREQ			0x5
+#define CS4265_MCLK_FREQ_1_0X	(0b000<<4 )
+#define CS4265_MCLK_FREQ_1_5X	(0b001<<4 )
+#define CS4265_MCLK_FREQ_2_0X	(0b010<<4 )
+#define CS4265_MCLK_FREQ_3_0X	(0b011<<4 )
+#define CS4265_MCLK_FREQ_4_0X	(0b100<<4 )
+
+
+#define CS4265_MCLK_FREQ_MASK			(7 << 4)
+
+#define CS4265_SIG_SEL				0x6
+#define CS4265_SIG_SEL_LOOP			(1 << 1)
+#define CS4265_SIG_SEL_SDIN2		(1 << 7)
+#define CS4265_SIG_SEL_SDIN1		(0 << 7)
+
+// Sets the gain or attenuation for the ADC input PGA stage. The gain may be adjusted from -12 dB to
+// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB step.
+// Register settings outside of the ±12 dB range are reserved and must not be used. See Table 13 for example settings
+#define CS4265_CHB_PGA_CTL			0x7
+// Sets the gain or attenuation for the ADC input PGA stage. The gain may be adjusted from -12 dB to
+// +12 dB in 0.5 dB steps. The gain bits are in two’s complement with the Gain0 bit set for a 0.5 dB step.
+// Register settings outside of the ±12 dB range are reserved and must not be used. See Table 13 for example settings
+#define CS4265_CHA_PGA_CTL			0x8
+// Gain[5:0]    Setting
+// 101000       -12 dB
+// 000000       0 dB
+// 011000       +12 dB
+
+
+#define CS4265_ADC_CTL2				0x9
+
+// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to -127 dB.
+// The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared. The Vol[7:1] bits
+// activate attenuation equal to their decimal equivalent (in dB). 
+//Binary Code 	Volume Setting
+//00000000 		0 dB
+//00000001 		-0.5 dB
+//00101000 		-20 dB
+//00101001 		-20.5 dB
+//11111110 		-127 dB
+//11111111 		-127.5 dB
+#define CS4265_DAC_CHA_VOL			0xA
+// The digital volume control allows the user to attenuate the signal in 0.5 dB increments from 0 to -127 dB.
+// The Vol0 bit activates a 0.5 dB attenuation when set, and no attenuation when cleared. The Vol[7:1] bits
+// activate attenuation equal to their decimal equivalent (in dB). 
+//Binary Code 	Volume Setting
+//00000000 		0 dB
+//00000001 		-0.5 dB
+//00101000 		-20 dB
+//00101001 		-20.5 dB
+//11111110 		-127 dB
+//11111111 		-127.5 dB
+#define CS4265_DAC_CHB_VOL			0xB
+#define CS4265_DAC_VOL_ATT_000_0		0b00000000
+#define CS4265_DAC_VOL_ATT_000_5		0b00000001
+#define CS4265_DAC_VOL_ATT_020_0		0b00101000
+#define CS4265_DAC_VOL_ATT_020_5		0b00101001
+#define CS4265_DAC_VOL_ATT_127_0		0b11111110
+#define CS4265_DAC_VOL_ATT_127_5		0b11111111
+
+// DAC Soft Ramp or Zero Cross Enable (Bits 7:6)
+//
+// Soft Ramp Enable
+// Soft Ramp allows level changes, both muting and attenuation, to be implemented by incrementally ramping, in 1/8 dB steps, from the current level to the new level at a rate of 1 dB per 8 left/right clock periods.
+// See Table 17.
+// Zero Cross Enable
+// Zero Cross Enable dictates that signal-level changes, either by attenuation changes or muting, will occur
+// on a signal zero crossing to minimize audible artifacts. The requested level change will occur after a timeout period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the signal
+// does not encounter a zero crossing. The zero cross function is independently monitored and implemented
+// for each channel. See Table 17.
+// Soft Ramp and Zero Cross Enable
+// Soft Ramp and Zero Cross Enable dictate that signal-level changes, either by attenuation changes or muting, will occur in 1/8 dB steps and be implemented on a signal zero crossing. The 1/8 dB level change will
+// occur after a time-out period between 512 and 1024 sample periods (10.7 ms to 21.3 ms at 48 kHz sample rate) if the signal does not encounter a zero crossing. The zero cross function is independently monitored and implemented for each channel
+// DACSoft DACZeroCross Mode
+// 0 0 Changes to affect immediately
+// 0 1 Zero Cross enabled
+// 1 0 Soft Ramp enabled
+// 1 1 Soft Ramp and Zero Cross enabled (default)
+#define CS4265_DAC_CTL2								0xC
+#define CS4265_DAC_CTL2_ZERO_CROSS_EN  				(uint8_t)(0b01 <<7)
+#define CS4265_DAC_CTL2_SOFT_RAMP_EN  				(uint8_t)(0b10 <<7)
+#define CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN  	(uint8_t)(0b11 <<7)
+
+
+#define CS4265_INT_STATUS			0xD
+#define CS4265_INT_STATUS_ADC_UNDF  (1<<0)
+#define CS4265_INT_STATUS_ADC_OVF   (1<<1)
+#define CS4265_INT_STATUS_CLKERR    (1<<3)
+
+
+#define CS4265_INT_MASK				0xE
+#define CS4265_STATUS_MODE_MSB			0xF
+#define CS4265_STATUS_MODE_LSB			0x10
+
+//Transmitter Control 1 - Address 11h
+#define CS4265_SPDIF_CTL1			0x11
+
+
+
+#define CS4265_SPDIF_CTL2			0x12
+// Transmitter Digital Interface Format (Bits 7:6)
+// Function:
+// The required relationship between LRCK, SCLK and SDIN for the transmitter is defined
+// Tx_DIF1 Tx_DIF0 Description Format Figure
+// 0 0 Left Justified, up to 24-bit data (default) 0 5
+// 0 1 I²S, up to 24-bit data 1 6
+// 1 0 Right-Justified, 16-bit Data 2 7
+// 1 1 Right-Justified, 24-bit Data 3 7
+#define CS4265_SPDIF_CTL2_MMTLR         (1<<0)
+#define CS4265_SPDIF_CTL2_MMTCS         (1<<1)
+#define CS4265_SPDIF_CTL2_MMT           (1<<2)
+#define CS4265_SPDIF_CTL2_V             (1<<3)
+#define CS4265_SPDIF_CTL2_TXMUTE        (1<<4)
+#define CS4265_SPDIF_CTL2_TXOFF         (1<<5)
+#define CS4265_SPDIF_CTL2_MUTE			(1 << 4)
+#define CS4265_SPDIF_CTL2_DIF			(3 << 6)
+#define CS4265_SPDIF_CTL2_DIF0			(1 << 6)
+#define CS4265_SPDIF_CTL2_DIF1			(1 << 7)
+
+
+
+
+
+
+#define CS4265_C_DATA_BUFF			0x13
+#define CS4265_MAX_REGISTER			0x2A
+struct cs4265_clk_para {
+	uint32_t mclk;
+	uint32_t rate;
+	uint8_t fm_mode; /* values 1, 2, or 4 */
+	uint8_t mclkdiv;
+};
+static const struct cs4265_clk_para clk_map_table[] = {
+	/*32k*/
+	{8192000, 32000, 0, 0},
+	{12288000, 32000, 0, 1},
+	{16384000, 32000, 0, 2},
+	{24576000, 32000, 0, 3},
+	{32768000, 32000, 0, 4},
+
+	/*44.1k*/
+	{11289600, 44100, 0, 0},
+	{16934400, 44100, 0, 1},
+	{22579200, 44100, 0, 2},
+	{33868000, 44100, 0, 3},
+	{45158400, 44100, 0, 4},
+
+	/*48k*/
+	{12288000, 48000, 0, 0},
+	{18432000, 48000, 0, 1},
+	{24576000, 48000, 0, 2},
+	{36864000, 48000, 0, 3},
+	{49152000, 48000, 0, 4},
+
+	/*64k*/
+	{8192000, 64000, 1, 0},
+	{12288000, 64000, 1, 1},
+	{16934400, 64000, 1, 2},
+	{24576000, 64000, 1, 3},
+	{32768000, 64000, 1, 4},
+
+	/* 88.2k */
+	{11289600, 88200, 1, 0},
+	{16934400, 88200, 1, 1},
+	{22579200, 88200, 1, 2},
+	{33868000, 88200, 1, 3},
+	{45158400, 88200, 1, 4},
+
+	/* 96k */
+	{12288000, 96000, 1, 0},
+	{18432000, 96000, 1, 1},
+	{24576000, 96000, 1, 2},
+	{36864000, 96000, 1, 3},
+	{49152000, 96000, 1, 4},
+
+	/* 128k */
+	{8192000, 128000, 2, 0},
+	{12288000, 128000, 2, 1},
+	{16934400, 128000, 2, 2},
+	{24576000, 128000, 2, 3},
+	{32768000, 128000, 2, 4},
+
+	/* 176.4k */
+	{11289600, 176400, 2, 0},
+	{16934400, 176400, 2, 1},
+	{22579200, 176400, 2, 2},
+	{33868000, 176400, 2, 3},
+	{49152000, 176400, 2, 4},
+
+	/* 192k */
+	{12288000, 192000, 2, 0},
+	{18432000, 192000, 2, 1},
+	{24576000, 192000, 2, 2},
+	{36864000, 192000, 2, 3},
+	{49152000, 192000, 2, 4},
+};
+static const struct cs4265_cmd_s cs4265_init_sequence[] = {
+	{CS4265_PWRCTL, CS4265_PWRCTL_PDN_ADC | CS4265_PWRCTL_FREEZE | CS4265_PWRCTL_PDN_DAC | CS4265_PWRCTL_PDN_MIC},
+ 	{CS4265_DAC_CTL, CS4265_DAC_CTL_DIF0 | CS4265_DAC_CTL_MUTE}, 
+ 	{CS4265_SIG_SEL, CS4265_SIG_SEL_SDIN1},/// SDIN1
+ 	{CS4265_SPDIF_CTL2, CS4265_SPDIF_CTL2_DIF0 },//
+ 	{CS4265_ADC_CTL, 0x00 },// // Set the serial audio port in slave mode
+ 	{CS4265_MCLK_FREQ, CS4265_MCLK_FREQ_1_0X },// // no divider 
+ 	{CS4265_CHB_PGA_CTL, 0x00 },// // sets the gain to 0db on channel B
+ 	{CS4265_CHA_PGA_CTL, 0x00 },// // sets the gain to 0db on channel A
+ 	{CS4265_ADC_CTL2, 0x19 },//
+ 	{CS4265_DAC_CHA_VOL,CS4265_DAC_VOL_ATT_000_0   },// Full volume out 
+ 	{CS4265_DAC_CHB_VOL, CS4265_DAC_VOL_ATT_000_0 },// // Full volume out 
+ 	{CS4265_DAC_CTL2, CS4265_DAC_CTL2_SOFT_RAMP_ZERO_CROSS_EN },//
+ 	{CS4265_SPDIF_CTL1, 0x00 },//
+ 	{CS4265_INT_MASK, 0x00 },//
+ 	{CS4265_STATUS_MODE_MSB, 0x00 },//
+ 	{CS4265_STATUS_MODE_LSB, 0x00 },//
+	{0xff,0xff}
+};
+
+
+// matching orders
+typedef enum { cs4265_ACTIVE = 0, cs4265_STANDBY, cs4265_DOWN, cs4265_ANALOGUE_OFF, cs4265_ANALOGUE_ON, cs4265_VOLUME } dac_cmd_e;
+
+
+
+static int cs4265_addr;
+
+static void dac_cmd(dac_cmd_e cmd, ...);
+static int cs4265_detect(void);
+static uint32_t calc_rnd_mclk_freq(){
+	float m_scale = (cs4265.i2s_config->sample_rate > 96000 && cs4265.i2s_config->bits_per_sample > 16) ? 4 : 8;
+	float num_channels = cs4265.i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 2 : 1;
+     return (uint32_t) round(cs4265.i2s_config->bits_per_sample*i2s_get_clk(cs4265.i2c_port)* m_scale*num_channels/100)*100;
+}
+static int cs4265_get_clk_index(int mclk, int rate)
+{
+	for (int i = 0; i < ARRAY_SIZE(clk_map_table); i++) {
+		if (clk_map_table[i].rate == rate &&
+				clk_map_table[i].mclk == mclk)
+			return i;
+	}
+	return -1;
+}
+
+static esp_err_t set_clock(){
+    esp_err_t err = ESP_OK;
+	uint32_t mclk = calc_rnd_mclk_freq();
+    int index = cs4265_get_clk_index(mclk,cs4265.i2s_config->sample_rate );
+	if (index >= 0) {
+        ESP_LOGD(TAG, "Setting clock for mclk %u, rate %u (fm mode:%u, clk div:%u))", mclk,cs4265.i2s_config->sample_rate,clk_map_table[index].fm_mode,clk_map_table[index].mclkdiv);
+		err=cs4265_update_bit(CS4265_ADC_CTL,CS4265_ADC_FM, clk_map_table[index].fm_mode << 6);
+		err|=cs4265_update_bit( CS4265_MCLK_FREQ,CS4265_MCLK_FREQ_MASK,clk_map_table[index].mclkdiv << 4);
+	} else {
+		ESP_LOGE(TAG,"can't get correct mclk for ");
+		return -1;
+	}
+    return err;
+}
+
+
+static void get_status(){
+    uint8_t sts1= adac_read_byte(cs4265_addr, CS4265_INT_STATUS);	
+    ESP_LOGD(TAG,"Status: %s",sts1&CS4265_INT_STATUS_CLKERR?"CLK Error":"CLK OK");
+}
+
+
+/****************************************************************************************
+ * init
+ */
+static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {	 
+	// find which TAS we are using (if any)
+	cs4265_addr = adac_init(config, i2c_port);
+	cs4265.i2s_config = i2s_config;
+	cs4265.i2c_port=i2c_port;
+	if (!cs4265_addr) cs4265_addr = cs4265_detect();
+	if (!cs4265_addr) {
+		ESP_LOGE(TAG, "No cs4265 detected");
+		adac_deinit();
+		return false;
+	}
+	#if BYTES_PER_FRAME == 8
+		ESP_LOGE(TAG,"The CS4265 does not support 32 bits mode. ");
+		adac_deinit();
+		return false;
+	#endif	
+	// configure MLK
+    ESP_LOGD(TAG, "Configuring MCLK on GPIO0");
+	PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
+   	REG_WRITE(PIN_CTRL, 0xFFFFFFF0);
+	i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
+	for (int i = 0; cs4265_init_sequence[i].reg != 0xff; i++) {
+		i2c_master_start(i2c_cmd);
+		i2c_master_write_byte(i2c_cmd, (cs4265_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
+		i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].reg, I2C_MASTER_NACK);
+		i2c_master_write_byte(i2c_cmd, cs4265_init_sequence[i].value, I2C_MASTER_NACK);
+		ESP_LOGD(TAG, "i2c write %x at %u", cs4265_init_sequence[i].reg, cs4265_init_sequence[i].value);
+	}
+
+	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);
+
+	if (res != ESP_OK) {
+		ESP_LOGE(TAG, "could not intialize cs4265 %d", res);
+		return false;
+	}	
+	
+	return true;
+
+}	
+
+static esp_err_t cs4265_update_bit(uint8_t reg_no,uint8_t mask,uint8_t val ){
+    esp_err_t ret=ESP_OK;
+    uint8_t old= adac_read_byte(cs4265_addr, reg_no);
+    uint8_t newval = (old & ~mask) | (val & mask);
+	bool change = old != newval;
+	if (change){
+		ret = adac_write_byte(cs4265_addr, reg_no, newval);
+		if(ret != ESP_OK){
+        	ESP_LOGE(TAG,"Unable to change dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x  ",reg_no,old,newval,val,mask);
+    	}
+    	else {
+	        ESP_LOGD(TAG,"Changed dac register 0x%02x [0x%02x->0x%02x] from value 0x%02x, mask 0x%02x ",reg_no,old,newval,val,mask);
+	    }
+	}
+	
+    return ret;
+}
+
+/****************************************************************************************
+ * change volume
+ */
+static bool volume(unsigned left, unsigned right) { 
+	return false; 
+}
+
+/****************************************************************************************
+ * power
+ */
+static void power(adac_power_e mode) {
+	switch(mode) {
+	case ADAC_STANDBY:
+		dac_cmd(cs4265_STANDBY);
+		break;
+	case ADAC_ON:
+		dac_cmd(cs4265_ACTIVE);
+		break;		
+	case ADAC_OFF:
+		dac_cmd(cs4265_DOWN);
+		break;				
+	default:
+		ESP_LOGW(TAG, "unknown DAC command");
+		break;
+	}
+}
+
+/****************************************************************************************
+ * speaker
+ */
+static void speaker(bool active) {
+	if (active) dac_cmd(cs4265_ANALOGUE_ON);
+	else dac_cmd(cs4265_ANALOGUE_OFF);
+} 
+
+/****************************************************************************************
+ * headset
+ */
+static void headset(bool active) { } 
+ 
+/****************************************************************************************
+ * DAC specific commands
+ */
+void dac_cmd(dac_cmd_e cmd, ...) {
+	va_list args;
+	esp_err_t ret = ESP_OK;
+	
+	va_start(args, cmd);
+
+	switch(cmd) {
+	case cs4265_VOLUME:
+		ESP_LOGE(TAG, "DAC volume not handled yet");
+		break;
+    case cs4265_ACTIVE:
+		ESP_LOGD(TAG, "Activating DAC");
+        adac_write_byte(cs4265_addr, CS4265_PWRCTL,0);
+        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,0);
+        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,0);
+		cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,0);				
+		break;
+    case cs4265_STANDBY:
+		ESP_LOGD(TAG, "DAC Stand-by");
+        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,CS4265_SPDIF_CTL2_TXOFF);
+        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,CS4265_SPDIF_CTL2_TXMUTE);
+		cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,CS4265_DAC_CTL_MUTE);
+		break;
+    case cs4265_DOWN:
+		ESP_LOGD(TAG, "DAC Power Down");
+        adac_write_byte(cs4265_addr, CS4265_PWRCTL,CS4265_PWRCTL_PDN_ALL);
+		break;
+    case cs4265_ANALOGUE_OFF:
+		ESP_LOGD(TAG, "DAC Analog off");
+        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,CS4265_SPDIF_CTL2_TXOFF);
+        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,CS4265_SPDIF_CTL2_TXMUTE);
+		cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,CS4265_DAC_CTL_MUTE);
+		break;
+    case cs4265_ANALOGUE_ON:
+		ESP_LOGD(TAG, "DAC Analog on");
+		adac_write_byte(cs4265_addr, CS4265_PWRCTL,0);
+        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXOFF,0);
+        cs4265_update_bit(CS4265_SPDIF_CTL2,CS4265_SPDIF_CTL2_TXMUTE,0);
+		cs4265_update_bit(CS4265_DAC_CTL,CS4265_DAC_CTL_MUTE,0);		
+		break;
+	}
+	
+  	if (ret != ESP_OK) {
+		ESP_LOGE(TAG, "could not use cs4265 %d", ret);
+	}
+    get_status();
+	// now set the clock
+	ret=set_clock(cs4265.i2s_config,cs4265.i2c_port);
+	if (ret != ESP_OK) {
+		ESP_LOGE(TAG, "could not set the cs4265's clock %d", ret);
+	}	
+
+	va_end(args);
+}
+
+/****************************************************************************************
+ * TAS57 detection
+ */
+static int cs4265_detect(void) {
+	uint8_t addr[] = {CS4265_PULL_DOWN,CS4265_PULL_UP};
+	
+	for (int i = 0; i < sizeof(addr); i++) {
+		ESP_LOGI(TAG,"Looking for CS4265 @0x%x",addr[i]);
+		uint8_t reg=adac_read_byte(addr[i], CS4265_CHIP_ID);
+		if(reg==255){
+			continue;
+		}
+			// found a device at that address
+		uint8_t devid = reg & CS4265_CHIP_ID_MASK;
+		if (devid != CS4265_CHIP_ID_VAL) {
+			ESP_LOGE(TAG,"CS4265 Device ID (%X). Expected %X",devid, CS4265_CHIP_ID);
+			return 0;
+		}
+		ESP_LOGI(TAG,"Found DAC @0x%x, Version %x",addr[i], reg & CS4265_REV_ID_MASK);
+		return addr[i];	
+	}
+	return 0;
+}
+

+ 5 - 0
components/wifi-manager/network_status.c

@@ -266,6 +266,11 @@ cJSON* network_status_get_basic_info(cJSON** old) {
         *old = network_status_update_float(old, "avg_conn_time", nm->num_disconnect > 0 ? (nm->total_connected_time / nm->num_disconnect) : 0);
         *old = network_update_cjson_number(old, "bt_status", bt_app_source_get_a2d_state());
         *old = network_update_cjson_number(old, "bt_sub_status", bt_app_source_get_media_state());
+#if DEPTH == 16
+        *old = network_update_cjson_number(old, "depth", 16);
+#elif DEPTH == 32
+        *old = network_update_cjson_number(old, "depth", 32);
+#endif        
 #if CONFIG_I2C_LOCKED
         *old = network_status_update_bool(old, "is_i2c_locked", true);
 #else

文件差异内容过多而无法显示
+ 0 - 6
components/wifi-manager/webapp/dist/css/index.60aa97be4459083adfab.css


二进制
components/wifi-manager/webapp/dist/css/index.60aa97be4459083adfab.css.gz


文件差异内容过多而无法显示
+ 6 - 0
components/wifi-manager/webapp/dist/css/index.cd56ff129e3113d8cd3a.css


二进制
components/wifi-manager/webapp/dist/css/index.cd56ff129e3113d8cd3a.css.gz


+ 0 - 0
components/wifi-manager/webapp/dist/dist/js/index.9d3dbc.bundle.d.ts → components/wifi-manager/webapp/dist/dist/js/index.bd4c5d.bundle.d.ts


+ 0 - 0
components/wifi-manager/webapp/dist/dist/js/node_vendors.9d3dbc.bundle.d.ts → components/wifi-manager/webapp/dist/dist/js/node_vendors.bd4c5d.bundle.d.ts


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/dist/index.html


二进制
components/wifi-manager/webapp/dist/index.html.gz


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/dist/js/index.392dfa.bundle.js


二进制
components/wifi-manager/webapp/dist/js/index.392dfa.bundle.js.gz


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/dist/js/index.392dfa.bundle.js.map


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/dist/js/index.ab1d13.bundle.js


二进制
components/wifi-manager/webapp/dist/js/index.ab1d13.bundle.js.gz


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/dist/js/index.ab1d13.bundle.js.map


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/dist/js/node_vendors.ab1d13.bundle.js


二进制
components/wifi-manager/webapp/dist/js/node_vendors.392dfa.bundle.js.gz → components/wifi-manager/webapp/dist/js/node_vendors.ab1d13.bundle.js.gz


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/dist/js/node_vendors.ab1d13.bundle.js.map


文件差异内容过多而无法显示
+ 6 - 0
components/wifi-manager/webapp/dist/report.html


+ 42 - 0
components/wifi-manager/webapp/dist/src/js/test.d.ts

@@ -26,6 +26,27 @@ declare function getStatus(): {};
 declare function getStatus(): {};
 declare function getStatus(): {};
 declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getStatus(): {};
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
 declare function getRadioButton(entry: any): string;
 declare function getRadioButton(entry: any): string;
 declare function getRadioButton(entry: any): string;
@@ -54,6 +75,27 @@ declare function getRadioButton(entry: any): string;
 declare function getRadioButton(entry: any): string;
 declare function getRadioButton(entry: any): string;
 declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function getRadioButton(entry: any): string;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
+declare function pushStatus(): void;
 declare function pushStatus(): void;
 declare function pushStatus(): void;
 declare function pushStatus(): void;

+ 3 - 1
components/wifi-manager/webapp/dist/webpack/webpack.dev.d.ts

@@ -1,3 +1,4 @@
+declare const PORT: 9100;
 import HtmlWebPackPlugin = require("html-webpack-plugin");
 export namespace entry {
     const test: string;
@@ -17,7 +18,7 @@ export namespace devServer {
     }
     export const open: boolean;
     export const compress: boolean;
-    export const port: number;
+    export { PORT as port };
     export const host: string;
     export const allowedHosts: string;
     export const headers: {
@@ -33,3 +34,4 @@ export namespace devServer {
     export function onBeforeSetupMiddleware(devServer: any): void;
 }
 export const plugins: HtmlWebPackPlugin[];
+export {};

+ 2 - 1
components/wifi-manager/webapp/mock/status.json

@@ -23,5 +23,6 @@
 	"mock_plugin_has_proxy": "x",
 	"mock_fail_fw_update":"",
 	"mock_fail_recovery":"",
-	"mock_old_recovery":""
+	"mock_old_recovery":"",
+	"depth": 16
 }

+ 6 - 2
components/wifi-manager/webapp/package.json

@@ -8,6 +8,7 @@
   "scripts": {
     "prod": "webpack serve --open  --mode=production",
     "build": "webpack  --mode=production ",
+    "stats": "webpack  --env ANALYZE_SIZE=1 --mode=production ",
     "watch": "webpack --progress --watch  --mode=development ",
     "dev": "webpack serve --open  --mode=development"
   },
@@ -51,6 +52,7 @@
     "lodash-webpack-plugin": "^0.11.6",
     "mini-css-extract-plugin": "^2.5.2",
     "node-sass": "^7.0.1",
+    "open": "^9.1.0",
     "postcss": "^8.4.5",
     "postcss-loader": "^6.2.1",
     "purgecss-webpack-plugin": "^4.1.3",
@@ -65,7 +67,7 @@
     "ts-loader": "^9.2.6",
     "typescript": "^4.5.5",
     "webpack": "^5.67.0",
-    "webpack-bundle-analyzer": "^4.5.0",
+    "webpack-bundle-analyzer": "^4.8.0",
     "webpack-cli": "^4.9.2",
     "webpack-dev-server": "^4.7.3"
   },
@@ -74,7 +76,9 @@
     "async-mutex": "^0.3.2",
     "bootstrap": "^5.1.3",
     "jquery": "^3.6.0",
-    "popper.js": "^1.16.1"
+    "popper.js": "^1.16.1",
+    "webpack-visualizer-plugin": "^0.1.11",
+    "webpack-visualizer-plugin2": "^1.0.0"
   },
   "keywords": [
     "webppack4",

+ 102 - 15
components/wifi-manager/webapp/src/index.ejs

@@ -44,19 +44,23 @@
 
 		</div>
 		<div class="info navbar-right" style="display: inline-flex;">
-			<span class="recovery_element material-icons " style="color:orange; display: none" aria-label="🛑" >system_update_alt</span>
-			<span id="battery" class="material-icons" style="fill:white; display: none" aria-label="🔋" >battery_full</span>
+			<span class="recovery_element material-icons " style="color:orange; display: none"
+				aria-label="🛑">system_update_alt</span>
+			<span id="battery" class="material-icons" style="fill:white; display: none"
+				aria-label="🔋">battery_full</span>
 			<span id="o_jack" class="material-icons" style="fill:white; display: none" aria-label="🎧">headphones</span>
 			<span id="s_airplay" class="material-icons" style="fill:white; display: none" aria-label="🍎">airplay</span>
 			<em id="s_cspot" class="fab fa-spotify" style="fill:white; display: inline"></em>
 			<span data-bs-toggle="tooltip" id="o_type" data-bs-placement="top" title="">
 				<span id="o_bt" class="material-icons" style="fill:white; display: none" aria-label="">bluetooth</span>
-				<span id="o_spdif" class="material-icons" style="fill:white; display: none" aria-label="">graphic_eq</span>
+				<span id="o_spdif" class="material-icons" style="fill:white; display: none"
+					aria-label="">graphic_eq</span>
 				<span id="o_i2s" class="material-icons" style="fill:white; display: none" aria-label="🔈">speaker</span>
 			</span>
-			<span id="ethernet" class="material-icons if_eth" style="fill:white; display: none" aria-label="ETH">cable</span>
-			<span id="wifiStsIcon" class="material-icons if_wifi"
-				style="fill:white; display: none" aria-label=""></span>
+			<span id="ethernet" class="material-icons if_eth" style="fill:white; display: none"
+				aria-label="ETH">cable</span>
+			<span id="wifiStsIcon" class="material-icons if_wifi" style="fill:white; display: none"
+				aria-label=""></span>
 
 		</div>
 	</header>
@@ -215,33 +219,116 @@
 				<div class="card text-white  mb-3">
 					<div class="card-header">Usage Templates</div>
 					<div class="card-body">
-						<fieldset>
 							<fieldset class="form-group" id="output-tmpl">
-								<legend>Output</legend>
-								<div class="form-check">
+								<label>Output</label><br>
+								<div class="form-check form-check-inline">
 									<label class="form-check-label">
 										<input type="radio" class="form-check-input" name="output-tmpl" id="i2s">
 										I2S Dac
 									</label>
 								</div>
-								<div class="form-check">
+								<div class="form-check form-check-inline">
 									<label class="form-check-label">
 										<input type="radio" class="form-check-input" name="output-tmpl" id="spdif">
 										SPDIF
 									</label>
 								</div>
-								<div class="form-check">
+								<div class="form-check form-check-inline">
 									<label class="form-check-label">
 										<input type="radio" class="form-check-input" name="output-tmpl" id="bt">
 										Bluetooth
 									</label>
 								</div>
 							</fieldset>
-							<div class="form-group"><label for="player">Player Name</label><input type="text"
-									class="form-control " placeholder="Squeezelite" id="player"></div>
-							<div class="form-group"><label for="optional">Optional setting (e.g. for LMS IP
-									address)</label><input type="text" class="form-control" id="optional"></div>
+							<fieldset>
+							<div id="options">
+								<div class="form-group"><label for="cmd_opt_n">Set the player name</label><input
+										type="text" class="form-control sqcmd" placeholder="name" id="cmd_opt_n"></div>
+								<div class="form-group"><label for="cmd_opt_s">Server</label><input type="text"
+										class="form-control sqcmd" placeholder="server[:port]" id="cmd_opt_s"></div>
+								<div class="form-group"><label for="cmd_opt_b">Stream and Output buffer sizes (in
+										Kbytes)</label><input type="text" class="form-control sqcmd"
+										placeholder="stream:output" id="cmd_opt_b"></div>
+								<div class="form-group"><label for="cmd_opt_c">Restrict codecs </label><input
+										type="text" class="form-control sqcmd" placeholder="codec1,codec2"
+										id="cmd_opt_c"><small class="form-text text-muted">Supported: flac,pcm,mp3,ogg
+										(mad,mpg for specific mp3 codec)</small></div>
+								<div class="form-group"><label for="cmd_opt_C">Ouput device close timeout</label><input
+										type="text" class="form-control sqcmd" placeholder="timeout"
+										id="cmd_opt_C"><small class="form-text text-muted">Close output device after
+										timeout seconds, default
+										is to keep it open while player is 'on'</small></div>
+								<div class="form-group"><label for="cmd_opt_d">Set logging level</label><input
+										type="text" class="form-control sqcmd" placeholder="log=level"
+										id="cmd_opt_d"><small class="form-text text-muted">Logs:
+										all|slimproto|stream|decode|output, level:
+										info|debug|sdebug</small></div>
+								<div class="form-group"><label for="cmd_opt_e">Explicitly exclude native support of one
+										or more codecs</label><input type="text" class="form-control sqcmd"
+										placeholder="codec1,codec2" id="cmd_opt_e"><small
+										class="form-text text-muted">Supported: flac,pcm,mp3,ogg (mad,mpg for specific
+										mp3 codec)</small></div>
+								<div class="form-group"><label for="cmd_opt_m">Set mac address</label><input type="text"
+										class="form-control sqcmd" placeholder="mac addr" id="cmd_opt_m"><small
+										class="form-text text-muted">Format: ab:cd:ef:12:34:56</small></div>
+								<div class="form-group"><label for="cmd_opt_r">Sample rates supported, allows output to
+										be off when squeezelite is started</label><input type="text"
+										class="form-control sqcmd" placeholder="rates" id="cmd_opt_r"><small
+										class="form-text text-muted">&lt;maxrate&gt;|&lt;minrate&gt;&lt;maxrate&gt;&lt;rate1&gt;&lt;rate2&gt;&lt;rate3&gt;</small>
+								</div>
+
+								<div class="form-group hide" id="cmd_opt_R">
+									<label>Resample</label><br>
+									<div class="form-check form-check-inline">
+										<input class="form-check-input" type="radio" name="resample" id="resample_none"
+											suffix="" checked aint="false">
+										<label class="form-check-label" for="resampleNone">No resampling</label>
+									</div>
+									<div class="form-check form-check-inline">
+										<input class="form-check-input" type="radio" name="resample" id="resample"
+											suffix=' -R' aint="false">
+										<label class="form-check-label" for="resampleNone">Default</label>
+									</div>
+
+									<div class="form-check form-check-inline">
+										<input class="form-check-input" type="radio" name="resample" id="resample_b"
+											suffix=' -R -u b' aint="true">
+										<label class="form-check-label" for="resampleBasic">Basic linear
+											interpolation</label>
+									</div>
+									<div class="form-check form-check-inline">
+										<input class="form-check-input" type="radio" name="resample" id="resample_l"
+											suffix=' -R -u l' aint="true">
+										<label class="form-check-label" for="resample13Taps">13 taps</label>
+									</div>
+									<div class="form-check form-check-inline">
+										<input class="form-check-input" type="radio" name="resample" id="resample_m"
+											suffix=' -R -u m' aint="true">
+										<label class="form-check-label" for="resample21Taps">21 taps</label>
+									</div>
+									<div class="form-check form-check-inline">
+										<input class="form-check-input" type="checkbox" name="interpolate"
+											id="resample_i" suffix=":i">
+										<label class="form-check-label" for="interpolate">Interpolate filter
+											coefficients</label>
+									</div>
+								</div>
+
+								<div class="form-group"><label for="cmd_opt_Z">Report rate to server in helo as the
+										maximum sample rate we can support</label><input type="text"
+										class="form-control" placeholder="rate" id="cmd_opt_Z"></div>
+								<div class="form-group">
+									<div class="form-check">
+										<label class="form-check-label">
+											<input class="form-check-input" type="checkbox" id="cmd_opt_W" value=""
+												checked="">
+											Read wave and aiff format from header, ignore server parameters
+										</label>
+									</div>
+								</div>
+							</div>
 							<div class="form-group">
+
 								<div class="form-check">
 									<label class="form-check-label">
 										<input class="form-check-input" type="checkbox" id="disable-squeezelite"

+ 332 - 99
components/wifi-manager/webapp/src/js/custom.js

@@ -28,71 +28,95 @@ Object.assign(Date.prototype, {
     return this.toLocaleString(undefined, opt);
   },
 });
-function handleNVSVisible(){
+function get_control_option_value(obj) {
+  let ctrl,id,val,opt;
+  let radio = false;
+  let checked = false;
+  if (typeof (obj) === 'string') {
+    id = obj;
+    ctrl = $(`#${id}`);
+  } else {
+    id = $(obj).attr('id');
+    ctrl = $(obj);
+  }
+  if(ctrl.attr('type') === 'checkbox'){
+    opt = $(obj).checked?id.replace('cmd_opt_', ''):'';
+    val = true;
+  }
+  else {
+    opt = id.replace('cmd_opt_', '');
+    val = $(obj).val();
+    val = `${val.includes(" ") ? '"' : ''}${val}${val.includes(" ") ? '"' : ''}`;
+  }
+
+  return { opt, val };
+}
+function handleNVSVisible() {
   let nvs_previous_checked = isEnabled(Cookies.get("show-nvs"));
-  $('input#show-nvs')[0].checked = nvs_previous_checked ;
+  $('input#show-nvs')[0].checked = nvs_previous_checked;
   if ($('input#show-nvs')[0].checked || recovery) {
-      $('*[href*="-nvs"]').show();
-    } else {
-      $('*[href*="-nvs"]').hide();
+    $('*[href*="-nvs"]').show();
+  } else {
+    $('*[href*="-nvs"]').hide();
+  }
+}
+function concatenateOptions(options) {
+  let commandLine = ' ';
+  for (const [option, value] of Object.entries(options)) {
+    if (option !== 'n' && option !== 'o') {
+      commandLine += `-${option} `;
+      if (value !== true) {
+        commandLine += `${value} `;
+      }
     }
+  }
+  return commandLine;
 }
 
-
 function isEnabled(val) {
-  return val!=undefined && typeof val === 'string' && val.match("[Yy1]");
+  return val != undefined && typeof val === 'string' && val.match("[Yy1]");
 }
 
 const nvsTypes = {
   NVS_TYPE_U8: 0x01,
-
   /*! < Type uint8_t */
   NVS_TYPE_I8: 0x11,
-
   /*! < Type int8_t */
   NVS_TYPE_U16: 0x02,
-
   /*! < Type uint16_t */
   NVS_TYPE_I16: 0x12,
-
   /*! < Type int16_t */
   NVS_TYPE_U32: 0x04,
-
   /*! < Type uint32_t */
   NVS_TYPE_I32: 0x14,
-
   /*! < Type int32_t */
   NVS_TYPE_U64: 0x08,
-
   /*! < Type uint64_t */
   NVS_TYPE_I64: 0x18,
-
   /*! < Type int64_t */
   NVS_TYPE_STR: 0x21,
-
   /*! < Type string */
   NVS_TYPE_BLOB: 0x42,
-
   /*! < Type blob */
   NVS_TYPE_ANY: 0xff /*! < Must be last */,
 };
 const btIcons = {
-  bt_playing: {'label':'','icon': 'media_bluetooth_on'},
-  bt_disconnected: {'label':'','icon': 'media_bluetooth_off'},
-  bt_neutral: {'label':'','icon': 'bluetooth'},
-  bt_connecting: {'label':'','icon': 'bluetooth_searching'},
-  bt_connected: {'label':'','icon': 'bluetooth_connected'},
-  bt_disabled: {'label':'','icon': 'bluetooth_disabled'},
-  play_arrow: {'label':'','icon': 'play_circle_filled'},
-  pause: {'label':'','icon': 'pause_circle'},
-  stop: {'label':'','icon': 'stop_circle'},
-  '':  {'label':'','icon':''}
+  bt_playing: { 'label': '', 'icon': 'media_bluetooth_on' },
+  bt_disconnected: { 'label': '', 'icon': 'media_bluetooth_off' },
+  bt_neutral: { 'label': '', 'icon': 'bluetooth' },
+  bt_connecting: { 'label': '', 'icon': 'bluetooth_searching' },
+  bt_connected: { 'label': '', 'icon': 'bluetooth_connected' },
+  bt_disabled: { 'label': '', 'icon': 'bluetooth_disabled' },
+  play_arrow: { 'label': '', 'icon': 'play_circle_filled' },
+  pause: { 'label': '', 'icon': 'pause_circle' },
+  stop: { 'label': '', 'icon': 'stop_circle' },
+  '': { 'label': '', 'icon': '' }
 };
 const batIcons = [
-  { icon: "battery_0_bar", label:'▪', ranges: [{ f: 5.8, t: 6.8 }, { f: 8.8, t: 10.2 }] },
-  { icon: "battery_2_bar", label:'▪▪', ranges: [{ f: 6.8, t: 7.4 }, { f: 10.2, t: 11.1 }] },
-  { icon: "battery_3_bar", label:'▪▪▪', ranges: [{ f: 7.4, t: 7.5 }, { f: 11.1, t: 11.25 }] },
-  { icon: "battery_4_bar", label:'▪▪▪▪', ranges: [{ f: 7.5, t: 7.8 }, { f: 11.25, t: 11.7 }] }
+  { icon: "battery_0_bar", label: '▪', ranges: [{ f: 5.8, t: 6.8 }, { f: 8.8, t: 10.2 }] },
+  { icon: "battery_2_bar", label: '▪▪', ranges: [{ f: 6.8, t: 7.4 }, { f: 10.2, t: 11.1 }] },
+  { icon: "battery_3_bar", label: '▪▪▪', ranges: [{ f: 7.4, t: 7.5 }, { f: 11.1, t: 11.25 }] },
+  { icon: "battery_4_bar", label: '▪▪▪▪', ranges: [{ f: 7.5, t: 7.8 }, { f: 11.25, t: 11.7 }] }
 ];
 const btStateIcons = [
   { desc: 'Idle', sub: ['bt_neutral'] },
@@ -162,7 +186,7 @@ let flashState = {
     $('.flact').prop('disabled', false);
     $('#flashfilename').value = null;
     $('#fw-url-input').value = null;
-    if(!this.isStateError()){
+    if (!this.isStateError()) {
       $('span#flash-status').html('');
       $('#fwProgressLabel').parent().removeClass('bg-danger');
     }
@@ -357,8 +381,8 @@ let flashState = {
     const xhttp = new XMLHttpRequest();
     xhttp.context = this;
     var boundHandleUploadProgressEvent = this.HandleUploadProgressEvent.bind(this);
-    var boundsetOTAError=this.setOTAError.bind(this);
-    xhttp.upload.addEventListener("progress",boundHandleUploadProgressEvent, false);
+    var boundsetOTAError = this.setOTAError.bind(this);
+    xhttp.upload.addEventListener("progress", boundHandleUploadProgressEvent, false);
     xhttp.onreadystatechange = function () {
       if (xhttp.readyState === 4) {
         if (xhttp.status === 0 || xhttp.status === 404) {
@@ -464,11 +488,89 @@ window.handleReboot = function (link) {
     $('#reboot_nav').removeClass('active'); delayReboot(500, '', link);
   }
 }
-function isConnected(){
-  return ConnectedTo.hasOwnProperty('ip') && ConnectedTo.ip!='0.0.0.0'&& ConnectedTo.ip!='';
+
+function parseSqueezeliteCommandLine(commandLine) {
+  const options = {};
+  let output, name;
+  let otherValues = '';
+
+  const argRegex = /("[^"]+"|'[^']+'|\S+)/g;
+  const args = commandLine.match(argRegex);
+
+  let i = 0;
+
+  while (i < args.length) {
+    const arg = args[i];
+
+    if (arg.startsWith('-')) {
+      const option = arg.slice(1);
+
+      if (option === '') {
+        otherValues += args.slice(i).join(' ');
+        break;
+      }
+
+      let value = true;
+
+      if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
+        value = args[i + 1].replace(/"/g, '').replace(/'/g, '');
+        i++;
+      }
+
+      options[option] = value;
+    } else {
+      otherValues += arg + ' ';
+    }
+
+    i++;
+  }
+
+  otherValues = otherValues.trim();
+  output = getOutput(options);
+  name = getName(options);
+  let otherOptions={btname:null,n:null};
+  // assign o and n options to otheroptions if present
+  if (options.o && output.toUpperCase() === 'BT') {
+    let temp = parseSqueezeliteCommandLine(options.o);
+    if(temp.name) {
+      otherOptions.btname = temp.name;
+    }
+    delete options.o;
+  }
+  if (options.n) {
+    otherOptions['n'] = options.n;
+    delete options.n;
+  }
+  return { name, output, options, otherValues,otherOptions };  
+}
+
+function getOutput(options) {
+  let output;
+  if (options.o){
+    output = options.o.replace(/"/g, '').replace(/'/g, '');
+    /* set output as the first alphanumerical word in the command line */
+    if (output.indexOf(' ') > 0) {
+      output = output.substring(0, output.indexOf(' '));
+    }
+  }
+  return output;
+}
+
+function getName(options) {
+  let name;
+  /* if n option present, assign to name variable */
+  if (options.n){
+    name = options.n.replace(/"/g, '').replace(/'/g, '');
+  }
+  return name;
+}
+
+
+function isConnected() {
+  return ConnectedTo.hasOwnProperty('ip') && ConnectedTo.ip != '0.0.0.0' && ConnectedTo.ip != '';
 }
-function getIcon(icons){
-  return isConnected()?icons.icon:icons.label;
+function getIcon(icons) {
+  return isConnected() ? icons.icon : icons.label;
 }
 function handlebtstate(data) {
   let icon = '';
@@ -476,7 +578,7 @@ function handlebtstate(data) {
   if (data.bt_status !== undefined && data.bt_sub_status !== undefined) {
     const iconindex = btStateIcons[data.bt_status].sub[data.bt_sub_status];
     if (iconindex) {
-      icon =  btIcons[iconindex];
+      icon = btIcons[iconindex];
       tt = btStateIcons[data.bt_status].desc;
     } else {
       icon = btIcons.bt_connected;
@@ -485,19 +587,28 @@ function handlebtstate(data) {
   }
 
   $('#o_type').attr('title', tt);
-  $('#o_bt').html(isConnected()?icon.label:icon.text);
+  $('#o_bt').html(isConnected() ? icon.label : icon.text);
 }
 function handleTemplateTypeRadio(outtype) {
   $('#o_type').children('span').css({ display: 'none' });
+  let changed = false;
   if (outtype === 'bt') {
+    changed = output !== 'bt' && output !== '';
     output = 'bt';
   } else if (outtype === 'spdif') {
+    changed = output !== 'spdif' && output !== '';
     output = 'spdif';
   } else {
+    changed = output !== 'i2s' && output !== '';
     output = 'i2s';
   }
   $('#' + output).prop('checked', true);
   $('#o_' + output).css({ display: 'inline' });
+  if (changed) {
+    Object.keys(commandDefaults[output]).forEach(function (key) {
+      $(`#cmd_opt_${key}`).val(commandDefaults[output][key]);
+    });
+  }
 }
 
 function handleExceptionResponse(xhr, _ajaxOptions, thrownError) {
@@ -545,7 +656,17 @@ let releaseURL =
 
 let recovery = false;
 let messagesHeld = false;
-const commandHeader = 'squeezelite -b 500:2000 -d all=info -C 30 -W';
+let commandBTSinkName = '';
+const commandHeader = 'squeezelite ';
+const commandDefaults = {
+  i2s: { b: "500:2000", C: "30", W: "", Z: "96000", o: "I2S" },
+  spdif: { b: "500:2000", C: "30", W: "", Z: "48000", o: "SPDIF" },
+  bt: { b: "500:2000", C: "30", W: "", Z: "44100", o: "BT" },
+};
+let validOptions = {
+  codecs: ['flac', 'pcm', 'mp3', 'ogg', 'aac', 'wma', 'alac', 'dsd', 'mad', 'mpg']
+};
+
 //let blockFlashButton = false;
 let apList = null;
 //let selectedSSID = '';
@@ -559,6 +680,7 @@ let hostName = '';
 let versionName = 'Squeezelite-ESP32';
 let prevmessage = '';
 let project_name = versionName;
+let depth = 16;
 let board_model = '';
 let platform_name = versionName;
 let preset_name = '';
@@ -690,11 +812,6 @@ function handleHWPreset(allfields, reboot) {
       }
     },
   });
-
-
-
-
-
 }
 
 
@@ -773,23 +890,34 @@ function delayReboot(duration, cmdname, ota = 'reboot') {
 // eslint-disable-next-line no-unused-vars
 window.saveAutoexec1 = function (apply) {
   showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Saving.\n', false);
-  let commandLine = commandHeader + ' -n "' + $('#player').val() + '"';
+  let commandLine = `${commandHeader} -o ${output} `;
+  $('.sqcmd').each(function () {
+    let { opt, val } = get_control_option_value($(this));
+    if ((opt && opt.length>0 ) && typeof(val) == 'boolean' || val.length > 0) {
+      const optStr=opt===':'?opt:(` -${opt} `);
+      val = typeof(val) == 'boolean'?'':val;
+      commandLine += `${optStr} ${val}`;
+    }
+  });
+  const resample=$('#cmd_opt_R input[name=resample]:checked');
+  if (resample.length>0 && resample.attr('suffix')!=='') {
+    commandLine += resample.attr('suffix');
+    // now check resample_i option and if checked, add suffix to command line
+    if ($('#resample_i').is(":checked") && resample.attr('aint') =='true')  {
+          commandLine += $('#resample_i').attr('suffix');
+    }
+}
+
+    
   if (output === 'bt') {
-    commandLine += ' -o "BT" -R -Z 192000';
     showCmdMessage(
       'cfg-audio-tmpl',
       'MESSAGING_INFO',
       'Remember to configure the Bluetooth audio device name.\n',
       true
     );
-  } else if (output === 'spdif') {
-    commandLine += ' -o SPDIF -Z 192000';
-  } else {
-    commandLine += ' -o I2S';
-  }
-  if ($('#optional').val() !== '') {
-    commandLine += ' ' + $('#optional').val();
   }
+  commandLine += concatenateOptions(options);
   const data = {
     timestamp: Date.now(),
   };
@@ -880,9 +1008,33 @@ window.handleConnect = function () {
   // now we can re-set the intervals regardless of result
 
 }
+function renderError(opt,error){
+  const fieldname = `cmd_opt_${opt}`;
+  let errorFieldName=`${fieldname}-error`;
+  let errorField=$(`#${errorFieldName}`);
+  let field=$(`#${fieldname}`);
+  
+  if (!errorField || errorField.length ==0) {
+    field.after(`<div id="${errorFieldName}" class="invalid-feedback"></div>`);
+    errorField=$(`#${errorFieldName}`);
+  }
+  if(error.length ==0){
+      errorField.hide();
+      field.removeClass('is-invalid');
+      field.addClass('is-valid');
+      errorField.text('');
+  }
+  else {     
+      errorField.show();
+      errorField.text(error);
+      field.removeClass('is-valid');
+      field.addClass('is-invalid');
+  }
+  return errorField;
+}
 $(document).ready(function () {
   $('.material-icons').each(function (_index, entry) {
-    entry.attributes['icon']=entry.textContent;
+    entry.attributes['icon'] = entry.textContent;
   });
   setIcons(true);
   handleNVSVisible();
@@ -908,6 +1060,43 @@ $(document).ready(function () {
 
   });
   setTimeout(refreshAP, 1500);
+  /* add validation for cmd_opt_c, which accepts a comma separated list. 
+    getting known codecs from validOptions.codecs array
+    use bootstrap classes to highlight the error with an overlay message */
+  $('#options input').on('input', function () {
+    const { opt, val } = get_control_option_value(this);
+    if (opt === 'c' || opt === 'e') {
+      const fieldname = `cmd_opt_${opt}_codec-error`;
+      
+      const values = val.split(',').map(function (item) {
+        return item.trim();
+      });
+      /* get a list of invalid codecs */
+      const invalid = values.filter(function (item) {
+        return !validOptions.codecs.includes(item);
+      });
+      renderError(opt,invalid.length > 0 ? `Invalid codec(s) ${invalid.join(', ')}` : '');
+    }
+    /* add validation for cmd_opt_m, which accepts a mac_address */
+    if (opt === 'm') {
+      const mac_regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
+      renderError(opt,mac_regex.test(val) ? '' : 'Invalid MAC address');
+    }
+    if (opt === 'r') {
+        const rateRegex =  /^(\d+\.?\d*|\.\d+)-(\d+\.?\d*|\.\d+)$|^(\d+\.?\d*)$|^(\d+\.?\d*,)+\d+\.?\d*$/;
+        renderError(opt,rateRegex.test(val)?'':`Invalid rate(s) ${val}. Acceptable format: <maxrate>|<minrate>-<maxrate>|<rate1>,<rate2>,<rate3>`);
+    }
+
+
+
+  }
+
+
+  );
+
+
+
+
 
   $('#WifiConnectDialog')[0].addEventListener('shown.bs.modal', function (event) {
     $("*[class*='connecting']").hide();
@@ -1029,7 +1218,7 @@ $(document).ready(function () {
 
   $('input#show-nvs').on('click', function () {
     this.checked = this.checked ? 1 : 0;
-    Cookies.set("show-nvs", this.checked?'Y':'N');
+    Cookies.set("show-nvs", this.checked ? 'Y' : 'N');
     handleNVSVisible();
   });
   $('#btn_reboot_recovery').on('click', function () {
@@ -1048,7 +1237,7 @@ $(document).ready(function () {
     saveAutoexec1(true);
   });
   $('#btn_disconnect').on('click', function () {
-    ConnectedTo={};
+    ConnectedTo = {};
     refreshAPHTML2();
     $.ajax({
       url: '/connect.json',
@@ -1303,15 +1492,15 @@ window.setURL = function (button) {
 
 function rssiToIcon(rssi) {
   if (rssi >= -55) {
-    return {'label':'****','icon':`signal_wifi_statusbar_4_bar`};
+    return { 'label': '****', 'icon': `signal_wifi_statusbar_4_bar` };
   } else if (rssi >= -60) {
-    return {'label':'***','icon':`network_wifi_3_bar`};
+    return { 'label': '***', 'icon': `network_wifi_3_bar` };
   } else if (rssi >= -65) {
-    return {'label':'**','icon':`network_wifi_2_bar`};
+    return { 'label': '**', 'icon': `network_wifi_2_bar` };
   } else if (rssi >= -70) {
-    return {'label':'*','icon':`network_wifi_1_bar`};
+    return { 'label': '*', 'icon': `network_wifi_1_bar` };
   } else {
-    return {'label':'.','icon':`signal_wifi_statusbar_null`};
+    return { 'label': '.', 'icon': `signal_wifi_statusbar_null` };
   }
 }
 
@@ -1339,9 +1528,9 @@ function refreshAP() {
   });
 }
 function formatAP(ssid, rssi, auth) {
-  const rssi_icon=rssiToIcon(rssi);
-  const auth_icon={label:auth == 0 ? '🔓' : '🔒',icon:auth == 0 ? 'no_encryption' : 'lock'};
-  
+  const rssi_icon = rssiToIcon(rssi);
+  const auth_icon = { label: auth == 0 ? '🔓' : '🔒', icon: auth == 0 ? 'no_encryption' : 'lock' };
+
   return `<tr data-bs-toggle="modal" data-bs-target="#WifiConnectDialog"><td></td><td>${ssid}</td><td>
   <span class="material-icons" style="fill:white; display: inline" aria-label="${rssi_icon.label}" icon="${rssi_icon.icon}" >${getIcon(rssi_icon)}</span>
   	</td><td>
@@ -1512,9 +1701,9 @@ function getMessages() {
           break;
       }
     }
-    setTimeout(getMessages,messageInterval);
+    setTimeout(getMessages, messageInterval);
   }).fail(function (xhr, ajaxOptions, thrownError) {
-    
+
     if (xhr.status == 404) {
       $('.orec').hide(); // system commands won't be available either
       messagesHeld = true;
@@ -1522,15 +1711,15 @@ function getMessages() {
     else {
       handleExceptionResponse(xhr, ajaxOptions, thrownError);
     }
-    if(xhr.status == 0 && xhr.readyState ==0){
+    if (xhr.status == 0 && xhr.readyState == 0) {
       // probably a timeout. Target is rebooting? 
-      setTimeout(getMessages,messageInterval*2); // increase duration if a failure happens
+      setTimeout(getMessages, messageInterval * 2); // increase duration if a failure happens
     }
-    else if(!messagesHeld){
+    else if (!messagesHeld) {
       // 404 here means we rebooted to an old recovery
-      setTimeout(getMessages,messageInterval); // increase duration if a failure happens
+      setTimeout(getMessages, messageInterval); // increase duration if a failure happens
     }
-    
+
   }
   );
 
@@ -1552,18 +1741,18 @@ function handleRecoveryMode(data) {
     $('#boot-button').html('Reboot');
     $('#boot-form').attr('action', '/reboot_ota.json');
   } else {
-    if(!recovery && messagesHeld){
-      messagesHeld=false;
-      setTimeout(getMessages,messageInterval); // increase duration if a failure happens
+    if (!recovery && messagesHeld) {
+      messagesHeld = false;
+      setTimeout(getMessages, messageInterval); // increase duration if a failure happens
     }
     recovery = false;
-    
+
     $('.recovery_element').hide();
     $('.ota_element').show();
     $('#boot-button').html('Recovery');
     $('#boot-form').attr('action', '/recovery.json');
   }
-  
+
 }
 
 function hasConnectionChanged(data) {
@@ -1645,9 +1834,9 @@ function handleWifiDialog(data) {
 
   }
 }
-function setIcons(offline){
+function setIcons(offline) {
   $('.material-icons').each(function (_index, entry) {
-    entry.textContent = entry.attributes[offline?'aria-label':'icon'].value;
+    entry.textContent = entry.attributes[offline ? 'aria-label' : 'icon'].value;
   });
 }
 function handleNetworkStatus(data) {
@@ -1682,13 +1871,13 @@ function batteryToIcon(voltage) {
   for (const iconEntry of batIcons) {
     for (const entryRanges of iconEntry.ranges) {
       if (inRange(voltage, entryRanges.f, entryRanges.t)) {
-        return { label: iconEntry.label, icon:iconEntry.icon};
+        return { label: iconEntry.label, icon: iconEntry.icon };
       }
     }
   }
 
 
-  return {label:'▪▪▪▪',icon:"battery_full"};
+  return { label: '▪▪▪▪', icon: "battery_full" };
 }
 function checkStatus() {
   $.ajaxSetup({
@@ -1700,6 +1889,16 @@ function checkStatus() {
     handleNetworkStatus(data);
     handlebtstate(data);
     flashState.EventTargetStatus(data);
+    if(data.depth) {
+      depth = data.depth;
+      if(depth==16){
+        $('#cmd_opt_R').show();
+      }
+      else{
+        $('#cmd_opt_R').hide();
+      }
+    }
+
 
     if (data.project_name && data.project_name !== '') {
       project_name = data.project_name;
@@ -1717,10 +1916,10 @@ function checkStatus() {
       $('span#flash-status').html('');
     }
     if (data.Voltage) {
-      const bat_icon=batteryToIcon(data.Voltage);
+      const bat_icon = batteryToIcon(data.Voltage);
       $('#battery').html(`${getIcon(bat_icon)}`);
-      $('#battery').attr("aria-label",bat_icon.label);
-      $('#battery').attr("icon",bat_icon.icon);
+      $('#battery').attr("aria-label", bat_icon.label);
+      $('#battery').attr("icon", bat_icon.icon);
       $('#battery').show();
     } else {
       $('#battery').hide();
@@ -1757,15 +1956,15 @@ function checkStatus() {
       });
     }
     $('#o_jack').css({ display: Number(data.Jack) ? 'inline' : 'none' });
-    setTimeout(checkStatus,statusInterval);
+    setTimeout(checkStatus, statusInterval);
   }).fail(function (xhr, ajaxOptions, thrownError) {
     handleExceptionResponse(xhr, ajaxOptions, thrownError);
-    if(xhr.status == 0 && xhr.readyState ==0){
+    if (xhr.status == 0 && xhr.readyState == 0) {
       // probably a timeout. Target is rebooting? 
-      setTimeout(checkStatus,messageInterval*2); // increase duration if a failure happens
+      setTimeout(checkStatus, messageInterval * 2); // increase duration if a failure happens
     }
     else {
-      setTimeout(checkStatus,messageInterval); // increase duration if a failure happens
+      setTimeout(checkStatus, messageInterval); // increase duration if a failure happens
     }
   });
 }
@@ -1995,6 +2194,7 @@ function getConfig() {
     $('#nvsTable tr').remove();
     const data = (entries.config ? entries.config : entries);
     SystemConfig = data;
+    commandBTSinkName = '';
     Object.keys(data)
       .sort()
       .forEach(function (key) {
@@ -2006,20 +2206,15 @@ function getConfig() {
             $('#disable-squeezelite')[0].checked = false;
           }
         } else if (key === 'autoexec1') {
-          const re = /-o\s?(["][^"]*["]|[^-]+)/g;
-          const m = re.exec(val);
-          if (m[1].toUpperCase().startsWith('I2S')) {
-            handleTemplateTypeRadio('i2s');
-          } else if (m[1].toUpperCase().startsWith('SPDIF')) {
-            handleTemplateTypeRadio('spdif');
-          } else if (m[1].toUpperCase().startsWith('"BT')) {
-            handleTemplateTypeRadio('bt');
-          }
+          /* call new function to parse the squeezelite options */
+          processSqueezeliteCommandLine(val);
         } else if (key === 'host_name') {
           val = val.replaceAll('"', '');
           $('input#dhcp-name1').val(val);
           $('input#dhcp-name2').val(val);
-          $('#player').val(val);
+          if ($('#cmd_opt_n').length == 0) {
+            $('#cmd_opt_n').val(val);
+          }
           document.title = val;
           hostName = val;
         } else if (key === 'rel_api') {
@@ -2054,6 +2249,10 @@ function getConfig() {
         );
         $('input#' + key).val(data[key].value);
       });
+    if(commandBTSinkName.length > 0) {
+      // persist the sink name found in the autoexec1 command line
+      $('#cfg-audio-bt_source-sink_name').val(commandBTSinkName);
+    }
     $('tbody#nvsTable').append(
       "<tr><td><input type='text' class='form-control' id='nvs-new-key' placeholder='new key'></td><td><input type='text' class='form-control' id='nvs-new-value' placeholder='new value' nvs_type=33 ></td></tr>"
     );
@@ -2083,6 +2282,40 @@ function getConfig() {
     handleExceptionResponse(xhr, ajaxOptions, thrownError);
   });
 }
+
+function processSqueezeliteCommandLine(val) {
+  const parsed = parseSqueezeliteCommandLine(val);
+  if (parsed.output.toUpperCase().startsWith('I2S')) {
+    handleTemplateTypeRadio('i2s');
+  } else if (parsed.output.toUpperCase().startsWith('SPDIF')) {
+    handleTemplateTypeRadio('spdif');
+  } else if (parsed.output.toUpperCase().startsWith('BT')) {
+    if(parsed.otherOptions.btname){ 
+      commandBTSinkName= parsed.otherOptions.btname;
+    }
+    handleTemplateTypeRadio('bt');
+  }
+  Object.keys(parsed.options).forEach(function (key) {
+    const option = parsed.options[key];
+    if (!$(`#cmd_opt_${key}`).hasOwnProperty('checked')) {
+      $(`#cmd_opt_${key}`).val(option);
+    } else {
+      $(`#cmd_opt_${key}`)[0].checked = option;
+    }
+  });
+  if (parsed.options.hasOwnProperty('u')) {
+    // parse -u v[:i] and check the appropriate radio button with id #resample_v
+    const [resampleValue, resampleInterpolation] = parsed.options.u.split(':');
+    $(`#resample_${resampleValue}`).prop('checked', true);
+    // if resampleinterpolation is set, check  resample_i checkbox
+    if (resampleInterpolation) {
+      $('#resample_i').prop('checked', true);
+    }
+  }
+
+
+}
+
 function showLocalMessage(message, severity) {
   const msg = {
     message: message,

+ 3 - 3
components/wifi-manager/webapp/webapp.cmake

@@ -1,5 +1,5 @@
-target_add_binary_data( __idf_wifi-manager webapp/dist/css/index.60aa97be4459083adfab.css.gz BINARY)
+target_add_binary_data( __idf_wifi-manager webapp/dist/css/index.cd56ff129e3113d8cd3a.css.gz BINARY)
 target_add_binary_data( __idf_wifi-manager webapp/dist/favicon-32x32.png BINARY)
 target_add_binary_data( __idf_wifi-manager webapp/dist/index.html.gz BINARY)
-target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.392dfa.bundle.js.gz BINARY)
-target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.392dfa.bundle.js.gz BINARY)
+target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.ab1d13.bundle.js.gz BINARY)
+target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.ab1d13.bundle.js.gz BINARY)

+ 15 - 15
components/wifi-manager/webapp/webpack.c

@@ -1,34 +1,34 @@
 // Automatically generated. Do not edit manually!.
 #include <inttypes.h>
-extern const uint8_t _index_60aa97be4459083adfab_css_gz_start[] asm("_binary_index_60aa97be4459083adfab_css_gz_start");
-extern const uint8_t _index_60aa97be4459083adfab_css_gz_end[] asm("_binary_index_60aa97be4459083adfab_css_gz_end");
+extern const uint8_t _index_cd56ff129e3113d8cd3a_css_gz_start[] asm("_binary_index_cd56ff129e3113d8cd3a_css_gz_start");
+extern const uint8_t _index_cd56ff129e3113d8cd3a_css_gz_end[] asm("_binary_index_cd56ff129e3113d8cd3a_css_gz_end");
 extern const uint8_t _favicon_32x32_png_start[] asm("_binary_favicon_32x32_png_start");
 extern const uint8_t _favicon_32x32_png_end[] asm("_binary_favicon_32x32_png_end");
 extern const uint8_t _index_html_gz_start[] asm("_binary_index_html_gz_start");
 extern const uint8_t _index_html_gz_end[] asm("_binary_index_html_gz_end");
-extern const uint8_t _index_392dfa_bundle_js_gz_start[] asm("_binary_index_392dfa_bundle_js_gz_start");
-extern const uint8_t _index_392dfa_bundle_js_gz_end[] asm("_binary_index_392dfa_bundle_js_gz_end");
-extern const uint8_t _node_vendors_392dfa_bundle_js_gz_start[] asm("_binary_node_vendors_392dfa_bundle_js_gz_start");
-extern const uint8_t _node_vendors_392dfa_bundle_js_gz_end[] asm("_binary_node_vendors_392dfa_bundle_js_gz_end");
+extern const uint8_t _index_ab1d13_bundle_js_gz_start[] asm("_binary_index_ab1d13_bundle_js_gz_start");
+extern const uint8_t _index_ab1d13_bundle_js_gz_end[] asm("_binary_index_ab1d13_bundle_js_gz_end");
+extern const uint8_t _node_vendors_ab1d13_bundle_js_gz_start[] asm("_binary_node_vendors_ab1d13_bundle_js_gz_start");
+extern const uint8_t _node_vendors_ab1d13_bundle_js_gz_end[] asm("_binary_node_vendors_ab1d13_bundle_js_gz_end");
 const char * resource_lookups[] = {
-	"/css/index.60aa97be4459083adfab.css.gz",
+	"/css/index.cd56ff129e3113d8cd3a.css.gz",
 	"/favicon-32x32.png",
 	"/index.html.gz",
-	"/js/index.392dfa.bundle.js.gz",
-	"/js/node_vendors.392dfa.bundle.js.gz",
+	"/js/index.ab1d13.bundle.js.gz",
+	"/js/node_vendors.ab1d13.bundle.js.gz",
 ""
 };
 const uint8_t * resource_map_start[] = {
-	_index_60aa97be4459083adfab_css_gz_start,
+	_index_cd56ff129e3113d8cd3a_css_gz_start,
 	_favicon_32x32_png_start,
 	_index_html_gz_start,
-	_index_392dfa_bundle_js_gz_start,
-	_node_vendors_392dfa_bundle_js_gz_start
+	_index_ab1d13_bundle_js_gz_start,
+	_node_vendors_ab1d13_bundle_js_gz_start
 };
 const uint8_t * resource_map_end[] = {
-	_index_60aa97be4459083adfab_css_gz_end,
+	_index_cd56ff129e3113d8cd3a_css_gz_end,
 	_favicon_32x32_png_end,
 	_index_html_gz_end,
-	_index_392dfa_bundle_js_gz_end,
-	_node_vendors_392dfa_bundle_js_gz_end
+	_index_ab1d13_bundle_js_gz_end,
+	_node_vendors_ab1d13_bundle_js_gz_end
 };

+ 19 - 3
components/wifi-manager/webapp/webpack.config.js

@@ -10,6 +10,7 @@ const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
 const webpack = require("webpack");
 const path = require("path");
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+
 const globSync = require("glob").sync;
 const glob = require('glob');
 const { merge } = require('webpack-merge');
@@ -39,7 +40,15 @@ class BuildEventsHook {
 module.exports = (env, options) => (
   merge(
     env.WEBPACK_SERVE ?  devserver : {},
-    env.ANALYZE_SIZE?{ plugins: [ new BundleAnalyzerPlugin() ]}:{},
+    env.ANALYZE_SIZE?{ plugins: [ new BundleAnalyzerPlugin(
+      {
+        analyzerMode: 'static',
+        generateStatsFile: true,
+        statsFilename: 'stats.json',
+      }
+    )      ]}:{},
+
+    
     {
       entry: 
       {
@@ -170,7 +179,6 @@ module.exports = (env, options) => (
         ],
       },
       plugins: [
-      
         new HtmlWebpackPlugin({
           title: 'SqueezeESP32',
           template: './src/index.ejs',
@@ -328,6 +336,7 @@ extern const uint8_t * resource_map_end[];`;
       optimization: {
         minimize: true,
         providedExports: true,
+        usedExports: true,
         minimizer: [
          
           new TerserPlugin({
@@ -340,7 +349,14 @@ extern const uint8_t * resource_map_end[];`;
           // enable parallel running
             parallel: true,
           }),
-          new HtmlMinimizerPlugin(),
+          new HtmlMinimizerPlugin({
+            minimizerOptions: {
+              removeComments: true,
+              removeOptionalTags: true,
+
+            }
+          }
+          ),
           new CssMinimizerPlugin(),
           new ImageMinimizerPlugin({
             minimizer: {

+ 1 - 1
components/wifi-manager/webapp/webpack.h

@@ -1,6 +1,6 @@
 /***********************************
 webpack_headers
-dist/css/index.60aa97be4459083adfab.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.392dfa.bundle.js.gz,dist/js/node_vendors.392dfa.bundle.js.gz
+dist/css/index.cd56ff129e3113d8cd3a.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.ab1d13.bundle.js.gz,dist/js/node_vendors.ab1d13.bundle.js.gz
 ***********************************/
 #pragma once
 #include <inttypes.h>

+ 17 - 8
components/wifi-manager/webapp/webpack/webpack.dev.js

@@ -6,6 +6,7 @@ const HtmlWebPackPlugin = require('html-webpack-plugin');
 const { Command } = require('commander');
 let  cmdLines= { };
 var { parseArgsStringToArgv } = require('string-argv');
+const PORT = 9100;
 
 const data = {
     messages: require("../mock/messages.json"),
@@ -133,11 +134,10 @@ const connectReturnCode = {
   }
 module.exports ={
     entry: {
-        test: './src/test.ts',
+        test: './src/test.ts'
+        
     },
     devServer: {
-
-        
         static: {
             directory: path.resolve(__dirname, './dist'),
             staticOptions: {},
@@ -158,11 +158,10 @@ module.exports ={
           },
         open: true,
         compress: true,
-        port: 9100, 
+        port: PORT, 
         host: '127.0.0.1',//your ip address
         allowedHosts: "all",
-        headers: {'Access-Control-Allow-Origin': '*',
-    'Accept-Encoding': 'identity'},
+        headers: {'Access-Control-Allow-Origin': '*',    'Accept-Encoding': 'identity'},
     client: {
         logging: "verbose",
         // Can be used only for `errors`/`warnings`
@@ -179,8 +178,18 @@ module.exports ={
           throw new Error('webpack-dev-server is not defined');
         }
   
-        const port = devServer.server.address().port;
-        console.log('Listening on port:', port);
+        const PORT = devServer.server.address().port;
+
+        // get the path to the test page
+        const compiler = devServer.compiler;
+        const entry = compiler.options.entry;
+        const testEntry = entry['test'].import[0];
+        const testPath = testEntry.replace('./src/', '').replace('.ts', '.html');
+  
+        // open the test page
+        import('open').then((open) => open.default(`http://localhost:${PORT}/${testPath}`));
+  
+
       },
 
         onBeforeSetupMiddleware: function (devServer) {

+ 10 - 0
squeezelite.cmake

@@ -1,3 +1,13 @@
+
+# Check if the required dependencies are installed
+find_package(Python3 COMPONENTS Interpreter)
+if(Python3_Interpreter_FOUND)
+    execute_process(COMMAND pip3 install protobuf grpcio-tools)
+else()
+    message(FATAL_ERROR "Python3 interpreter not found. Please install Python3 before building the project.")
+endif()
+
+
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 
 function(___register_flash partition_name sub_type)

部分文件因为文件数量过多而无法显示