浏览代码

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

Conflicts:
	components/wifi-manager/http_server.c
sebastien 5 年之前
父节点
当前提交
e6bad26ef0
共有 63 个文件被更改,包括 2408 次插入401 次删除
  1. 4 0
      .gitmodules
  2. 16 4
      README.md
  3. 1 1
      build-scripts/16M-sdkconfig.defaults
  4. 1 1
      build-scripts/I2S-16MFlash-sdkconfig.defaults
  5. 1 1
      build-scripts/I2S-4MFlash-sdkconfig.defaults
  6. 1 1
      build-scripts/NonOTA-16M-sdkconfig.defaults
  7. 1 1
      build-scripts/NonOTA-I2S-16MFlash-sdkconfig.defaults
  8. 1 1
      build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults
  9. 1 1
      build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults
  10. 1 1
      build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults
  11. 1 1
      build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults
  12. 1 1
      build-scripts/squeezelite-esp32-16M-sdkconfig.defaults
  13. 1 1
      build-scripts/squeezelite-esp32-I2S-16MFlash-sdkconfig.defaults
  14. 1 1
      build-scripts/squeezelite-esp32-I2S-4MFlash-NOAirplay-sdkconfig.defaults
  15. 1 1
      build-scripts/squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults
  16. 1 1
      build-scripts/squeezelite-esp32-SqueezeAmp-sdkconfig.defaults
  17. 1 1
      components/cmd_nvs/cmd_nvs.c
  18. 1 1
      components/driver_bt/bt_app_core.c
  19. 4 4
      components/driver_bt/bt_app_sink.c
  20. 4 3
      components/raop/raop.c
  21. 5 2
      components/raop/rtp.c
  22. 19 0
      components/services/accessors.c
  23. 1 0
      components/services/accessors.h
  24. 14 0
      components/services/battery.c
  25. 7 1
      components/services/buttons.c
  26. 30 10
      components/services/display.c
  27. 1 1
      components/services/driver_SSD1306.c
  28. 2 0
      components/services/globdefs.h
  29. 29 10
      components/services/monitor.c
  30. 2 0
      components/services/monitor.h
  31. 21 14
      components/services/services.c
  32. 1 1
      components/services/tarablessd1306/ssd1306_font.c
  33. 1 1
      components/squeezelite-ota/cmd_ota.c
  34. 435 0
      components/squeezelite/a1s/ac101.c
  35. 176 0
      components/squeezelite/a1s/ac101.h
  36. 38 0
      components/squeezelite/adac.h
  37. 2 1
      components/squeezelite/component.mk
  38. 1 1
      components/squeezelite/decode_external.c
  39. 13 5
      components/squeezelite/display.c
  40. 4 2
      components/squeezelite/embedded.c
  41. 4 1
      components/squeezelite/embedded.h
  42. 3 1
      components/squeezelite/main.c
  43. 32 0
      components/squeezelite/null/dac_null.c
  44. 7 7
      components/squeezelite/output_bt.c
  45. 108 257
      components/squeezelite/output_i2s.c
  46. 4 12
      components/squeezelite/slimproto.c
  47. 248 0
      components/squeezelite/tas57xx/dac_57xx.c
  48. 5 0
      components/telnet/CMakeLists.txt
  49. 15 0
      components/telnet/component.mk
  50. 1 0
      components/telnet/libtelnet
  51. 419 0
      components/telnet/telnet.c
  52. 4 0
      components/telnet/telnet.h
  53. 1 1
      components/wifi-manager/Kconfig.projbuild
  54. 654 0
      components/wifi-manager/http_server.c
  55. 6 3
      components/wifi-manager/wifi_manager.c
  56. 16 25
      main/Kconfig.projbuild
  57. 3 3
      main/console.c
  58. 27 8
      main/esp_app_main.c
  59. 二进制
      plugin/SqueezeESP32.zip
  60. 2 4
      plugin/SqueezeESP32/Player.pm
  61. 二进制
      plugin/SqueezeESP32/SqueezeESP32.zip
  62. 1 1
      plugin/SqueezeESP32/install.xml
  63. 2 2
      plugin/repo.xml

+ 4 - 0
.gitmodules

@@ -7,3 +7,7 @@
 [submodule "components/lws-esp32"]
 	path = components/lws-esp32
 	url = https://github.com/huming2207/lws-esp32.git
+[submodule "components/telnet/libtelnet"]
+	path = components/telnet/libtelnet
+	url = https://github.com/seanmiddleditch/libtelnet
+	branch = develop

+ 16 - 4
README.md

@@ -47,8 +47,20 @@ The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay a
 
 - 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only <title> will be displayed not " - <title>".
 
-### Vcc GPIO
-The parameter "Vcc_GPIO" is a comma-separated list of GPIO that will be configured as output with their value set to 1 (Vcc) at boot. Be careful because there is no conflict checks being made wrt which GPIO you're changing, so you might damage your board or create a conflict here.
+### Set GPIO
+The parameter "set_GPIO" is use to set assign GPIO to various functions.
+
+GPIO can be set to GND provide or Vcc at boot. This is convenient to power devices that consume less than 40mA from the side connector. Be careful because there is no conflict checks being made wrt which GPIO you're changing, so you might damage your board or create a conflict here. 
+
+This parameter can use used as well to assign a GPIO that will be set to 1 when playback starts and wil be reset to 0 when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through -C \<timeout\>
+
+Finally, if you have an audio jack that supports insertion (set to GND when inserted), you can specify whch GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
+
+Syntax is:
+
+```
+<gpio_1>=Vcc|GND|amp|jack[,<gpio_n>=Vcc|GND|amp|jack]
+```
 
 ### Buttons
 Buttons are described using a JSON string with the following syntax
@@ -81,7 +93,7 @@ Where (all parameters are optionals except gpio)
  - "shifted": action to take when a button is pressed/released and shifted (see above/below)
  - "longshifted": action to take when a button is long-pressed/released and shifted (see above/below)
 
-Where <action> is either the name of another configuration to load or one amongst 
+Where \<action\> is either the name of another configuration to load or one amongst 
 				ACTRLS_NONE, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY, 
 				ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT, 
 				BCTRLS_PUSH, BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT
@@ -95,7 +107,7 @@ For example a config named "buttons" :
 ``` 
 Defines two buttons
 - first on GPIO 4, active low. When pressed, it triggers a volume down command. When pressed more than 1000ms, it changes the button configuration for the one named "buttons_remap"
-- second on GPIO 5, acive low. When pressed it triggers a volume up command. If first button is pressed together with this button, then a play/pause toggle command is generated.
+- second on GPIO 5, active low. When pressed it triggers a volume up command. If first button is pressed together with this button, then a play/pause toggle command is generated.
 
 While the config named "buttons_remap"
 ```

+ 1 - 1
build-scripts/16M-sdkconfig.defaults

@@ -144,4 +144,4 @@ CONFIG_DEFAULT_AP_PASSWORD="squeezelite"
 CONFIG_DEFAULT_AP_IP="192.168.4.1"
 CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"

+ 1 - 1
build-scripts/I2S-16MFlash-sdkconfig.defaults

@@ -116,7 +116,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/I2S-4MFlash-sdkconfig.defaults

@@ -116,7 +116,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/NonOTA-16M-sdkconfig.defaults

@@ -140,4 +140,4 @@ CONFIG_DEFAULT_AP_PASSWORD="squeezelite"
 CONFIG_DEFAULT_AP_IP="192.168.4.1"
 CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"

+ 1 - 1
build-scripts/NonOTA-I2S-16MFlash-sdkconfig.defaults

@@ -116,7 +116,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults

@@ -116,7 +116,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults

@@ -104,7 +104,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults

@@ -104,7 +104,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults

@@ -71,7 +71,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
 CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y

+ 1 - 1
build-scripts/squeezelite-esp32-16M-sdkconfig.defaults

@@ -138,4 +138,4 @@ CONFIG_DEFAULT_AP_PASSWORD="squeezelite"
 CONFIG_DEFAULT_AP_IP="192.168.4.1"
 CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info"

+ 1 - 1
build-scripts/squeezelite-esp32-I2S-16MFlash-sdkconfig.defaults

@@ -115,7 +115,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/squeezelite-esp32-I2S-4MFlash-NOAirplay-sdkconfig.defaults

@@ -112,7 +112,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults

@@ -132,7 +132,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
build-scripts/squeezelite-esp32-SqueezeAmp-sdkconfig.defaults

@@ -103,7 +103,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
 CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
 CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
 CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
-CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info "
+CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
 
 CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y

+ 1 - 1
components/cmd_nvs/cmd_nvs.c

@@ -29,7 +29,7 @@ extern "C" {
 
 
 static const char *ARG_TYPE_STR = "type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob";
-static const char * TAG = "platform_esp32";
+static const char * TAG = "cmd_nvs";
 
 static struct {
     struct arg_str *key;

+ 1 - 1
components/driver_bt/bt_app_core.c

@@ -18,7 +18,7 @@
 #include "freertos/queue.h"
 #include "freertos/task.h"
 
-static const char * TAG = "platform_esp32";
+static const char * TAG = "btappcore";
 
 static void bt_app_task_handler(void *arg);
 static bool bt_app_send_msg(bt_app_msg_t *msg);

+ 4 - 4
components/driver_bt/bt_app_sink.c

@@ -162,7 +162,7 @@ static bool cmd_handler(bt_sink_cmd_t cmd, ...) {
 		va_end(args);
 		return false;
 	}
-
+	
 	// now handle events for display
 	switch(cmd) {
 	case BT_SINK_AUDIO_STARTED:
@@ -175,7 +175,7 @@ static bool cmd_handler(bt_sink_cmd_t cmd, ...) {
 		displayer_control(DISPLAYER_TIMER_RUN);
 		break;		
 	case BT_SINK_STOP:		
-		// not sure of difference between pause and stop for displayer 
+		 // not sure of difference between pause and stop for displayer 
 	case BT_SINK_PAUSE:
 		displayer_control(DISPLAYER_TIMER_PAUSE);
 		break;		
@@ -397,7 +397,7 @@ void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_param
         bt_av_playback_changed();
         break;
     case ESP_AVRC_RN_PLAY_POS_CHANGED:
-        ESP_LOGI(BT_AV_TAG, "Play position changed: %d-ms", event_parameter->play_pos);
+        ESP_LOGD(BT_AV_TAG, "Play position changed: %d (ms)", event_parameter->play_pos);
 		(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, event_parameter->play_pos, -1);
         bt_av_play_pos_changed();
         break;
@@ -440,7 +440,7 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
         break;
     }
     case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: {
-        ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
+        ESP_LOGD(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
         bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter);
         break;
     }

+ 4 - 3
components/raop/raop.c

@@ -542,7 +542,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 						
 		ctx->rtp = rtp.ctx;
 		
-		if (cport * tport * rtp.cport * rtp.tport * rtp.aport && rtp.ctx) {
+		if ( (cport * tport * rtp.cport * rtp.tport * rtp.aport) != 0 && rtp.ctx) {
 			char *transport;
 			asprintf(&transport, "RTP/AVP/UDP;unicast;mode=record;control_port=%u;timing_port=%u;server_port=%u", rtp.cport, rtp.tport, rtp.aport);
 			LOG_DEBUG("[%p]: audio=(%hu:%hu), timing=(%hu:%hu), control=(%hu:%hu)", ctx, 0, rtp.aport, tport, rtp.tport, cport, rtp.cport);
@@ -630,9 +630,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
 		} else if (body && (p = strcasestr(body, "progress")) != NULL) {
 			int start, current, stop = 0;
 
+			// we want ms, not s
 			sscanf(p, "%*[^:]:%u/%u/%u", &start, &current, &stop);
-			current = (current - start) / 44100;
-			if (stop) stop = (stop - start) / 44100;
+			current = ((current - start) / 44100) * 1000;
+			if (stop) stop = ((stop - start) / 44100) * 1000;
 			else stop = -1;
 			LOG_INFO("[%p]: SET PARAMETER progress %u/%u %s", ctx, current, stop, p);
 			success = ctx->cmd_cb(RAOP_PROGRESS, current, stop);

+ 5 - 2
components/raop/rtp.c

@@ -564,7 +564,7 @@ static void *rtp_thread_func(void *arg) {
 
 	for (i = 0; i < 3; i++) {
 		if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
-		// send synchro requets 3 times
+		// send synchro request 3 times
 		ntp_sent = rtp_request_timing(ctx);
 	}
 
@@ -638,7 +638,9 @@ static void *rtp_thread_func(void *arg) {
 				u16_t flags = ntohs(*(u16_t*)(pktp+2));
 				u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
 				
+				// something is wrong and if we are supposed to be NTP synced, better ask for re-sync
 				if (remote_gap > 10000) {
+					if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx);
 					LOG_WARN("discarding remote timing information %u", remote_gap);
 					break;
 				}
@@ -683,8 +685,9 @@ static void *rtp_thread_func(void *arg) {
 				u64_t remote 	  =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
 				u32_t roundtrip   = gettime_ms() - reference;
 
-				// better discard sync packets when roundtrip is suspicious
+				// better discard sync packets when roundtrip is suspicious and ask for another one
 				if (roundtrip > 100) {
+					rtp_request_timing(ctx);
 					LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip);
 					break;
 				}

+ 19 - 0
components/services/accessors.c

@@ -58,3 +58,22 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
 	if(i2c_port) *i2c_port=i2c_system_port;
 	return &i2c;
 }
+
+/****************************************************************************************
+ * 
+ */
+void parse_set_GPIO(void (*cb)(int gpio, char *value)) {
+	char *nvs_item, *p, type[4];
+	int gpio;
+	
+	if ((nvs_item = config_alloc_get(NVS_TYPE_STR, "set_GPIO")) == NULL) return;
+	
+	p = nvs_item;
+	
+	do {
+		if (sscanf(p, "%d=%3[^,]", &gpio, type) > 0) cb(gpio, type);
+		p = strchr(p, ',');
+	} while (p++);
+	
+	free(nvs_item);
+}	

+ 1 - 0
components/services/accessors.h

@@ -13,3 +13,4 @@
 
 esp_err_t 				config_i2c_set(const i2c_config_t * config, int port);
 const i2c_config_t * 	config_i2c_get(int * i2c_port);
+void 					parse_set_GPIO(void (*cb)(int gpio, char *value));

+ 14 - 0
components/services/battery.c

@@ -17,6 +17,13 @@
 #include "driver/adc.h"
 #include "battery.h"
 
+/* 
+ There is a bug in esp32 which causes a spurious interrupt on gpio 36/39 when
+ using ADC, AMP and HALL sensor. Rather than making battery aware, we just ignore
+ if as the interrupt lasts 80ns and should be debounced (and the ADC read does not
+ happen very often)
+*/ 
+
 #define BATTERY_TIMER	(10*1000)
 
 static const char *TAG = "battery";
@@ -27,6 +34,13 @@ static struct {
 	TimerHandle_t timer;
 } battery;
 
+/****************************************************************************************
+ * 
+ */
+ int battery_value_svc(void) {
+	 return battery.avg;
+ }
+
 /****************************************************************************************
  * 
  */

+ 7 - 1
components/services/buttons.c

@@ -31,6 +31,9 @@
 #include "esp_task.h"
 #include "driver/gpio.h"
 #include "buttons.h"
+#include "globdefs.h"
+
+bool gpio36_39_used;
 
 static const char * TAG = "buttons";
 
@@ -208,7 +211,10 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
 			ESP_LOGW(TAG, "cannot set pull up/down for gpio %u", gpio);
 		}
 	}
- 
+	
+	// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
+	if (gpio == 36 || gpio == 39) gpio36_39_used = true;
+
 	gpio_isr_handler_add(gpio, gpio_isr_handler, (void*) &buttons[n_buttons]);
 	gpio_intr_enable(gpio);
 

+ 30 - 10
components/services/display.c

@@ -29,7 +29,7 @@
 // here we should include all possible drivers
 extern struct display_s SSD1306_display;
 
-struct display_s *display;
+struct display_s *display = NULL;
 
 static const char *TAG = "display";
 
@@ -72,6 +72,7 @@ void display_init(char *welcome) {
 				init = true;
 				ESP_LOGI(TAG, "Display initialization successful");
 			} else {
+				display = NULL;
 				ESP_LOGE(TAG, "Display initialization failed");
 			}
 		} else {
@@ -103,7 +104,7 @@ void display_init(char *welcome) {
 }
 
 /****************************************************************************************
- * This is not really thread-safe as displayer_task might be in the middle of line drawing
+ * This is not thread-safe as displayer_task might be in the middle of line drawing
  * but it won't crash (I think) and making it thread-safe would be complicated for a
  * feature which is secondary (the LMS version of scrolling is thread-safe)
  */
@@ -128,11 +129,19 @@ static void displayer_task(void *args) {
 		if (scroll_sleep <= 10) {
 			// something to scroll (or we'll wake-up every pause ms ... no big deal)
 			if (*displayer.string && displayer.state == DISPLAYER_ACTIVE) {
-				display->line(2, -displayer.offset, DISPLAY_CLEAR | DISPLAY_UPDATE, displayer.string);
 				xSemaphoreTake(displayer.mutex, portMAX_DELAY);
+				
+				// need to work with local copies as we don't want to suspend caller
+				int offset = -displayer.offset;
+				char *string = strdup(displayer.string);
 				scroll_sleep = displayer.offset ? displayer.speed : displayer.pause;
 				displayer.offset = displayer.offset >= displayer.boundary ? 0 : (displayer.offset + min(displayer.by, displayer.boundary - displayer.offset));			
-				xSemaphoreGive(displayer.mutex);
+				
+				xSemaphoreGive(displayer.mutex);				
+				
+				// now display using safe copies, can be lengthy
+				display->line(2, offset, DISPLAY_CLEAR | DISPLAY_UPDATE, string);
+				free(string);
 			} else {
 				scroll_sleep = DEFAULT_SLEEP;
 			}	
@@ -149,8 +158,8 @@ static void displayer_task(void *args) {
 				displayer.tick = tick;
 				displayer.elapsed += elapsed / 1000;
 				xSemaphoreGive(displayer.mutex);				
-				if (displayer.elapsed < 3600) sprintf(counter, "%2u:%02u", displayer.elapsed / 60, displayer.elapsed % 60);
-				else sprintf(counter, "%2u:%2u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60);
+				if (displayer.elapsed < 3600) sprintf(counter, "%5u:%02u", displayer.elapsed / 60, displayer.elapsed % 60);
+				else sprintf(counter, "%2u:%02u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60);
 				display->line(1, DISPLAY_RIGHT, (DISPLAY_CLEAR | DISPLAY_ONLY_EOL) | DISPLAY_UPDATE, counter);
 				timer_sleep = 1000;
 			} else timer_sleep = max(1000 - elapsed, 0);	
@@ -171,6 +180,10 @@ void displayer_metadata(char *artist, char *album, char *title) {
 	char *string = displayer.string, *p;
 	int len = SCROLLABLE_SIZE;
 	
+	// need a display!
+	if (!display) return;
+	
+	// just do title if there is no config set
 	if (!displayer.metadata_config) {
 		strncpy(displayer.string, title ? title : "", SCROLLABLE_SIZE);
 		return;
@@ -218,7 +231,7 @@ void displayer_metadata(char *artist, char *album, char *title) {
 			p = strchr(q + 1, '%');
 		}
 	} else {
-		string = strdup(title ? title : "");
+		strncpy(string, title ? title : "", SCROLLABLE_SIZE);
 	}
 	
 	// get optional scroll speed
@@ -232,11 +245,13 @@ void displayer_metadata(char *artist, char *album, char *title) {
 	xSemaphoreGive(displayer.mutex);
 }	
 
-
 /****************************************************************************************
  *
  */
 void displayer_scroll(char *string, int speed) {
+	// need a display!
+	if (!display) return;
+	
 	xSemaphoreTake(displayer.mutex, portMAX_DELAY);
 
 	if (speed) displayer.speed = speed;
@@ -252,10 +267,13 @@ void displayer_scroll(char *string, int speed) {
  * 
  */
 void displayer_timer(enum displayer_time_e mode, int elapsed, int duration) {
+	// need a display!
+	if (!display) return;
+	
 	xSemaphoreTake(displayer.mutex, portMAX_DELAY);
 
-	if (elapsed >= 0) displayer.elapsed = elapsed;	
-	if (duration >= 0) displayer.duration = duration;
+	if (elapsed >= 0) displayer.elapsed = elapsed / 1000;	
+	if (duration >= 0) displayer.duration = duration / 1000;
 	if (displayer.timer) displayer.tick = xTaskGetTickCount();
 		
 	xSemaphoreGive(displayer.mutex);
@@ -267,6 +285,8 @@ void displayer_timer(enum displayer_time_e mode, int elapsed, int duration) {
 void displayer_control(enum displayer_cmd_e cmd, ...) {
 	va_list args;
 	
+	if (!display) return;
+	
 	va_start(args, cmd);
 	xSemaphoreTake(displayer.mutex, portMAX_DELAY);
 		

+ 1 - 1
components/services/driver_SSD1306.c

@@ -164,7 +164,7 @@ static bool set_font(int num, enum display_font_e font, int space) {
 	lines[0].y = lines[0].space;
 	for (int i = 1; i <= num; i++) lines[i].y = lines[i-1].y + lines[i-1].font->Height + lines[i].space;
 		
-	ESP_LOGI(TAG, "adding line %u at %u (height:%u)", num + 1, lines[num].y, lines[num].font->Height);
+	ESP_LOGI(TAG, "Adding line %u at %d (height:%u)", num + 1, lines[num].y, lines[num].font->Height);
 	
 	if (lines[num].y + lines[num].font->Height > Display.Height) {
 		ESP_LOGW(TAG, "line does not fit display");

+ 2 - 0
components/services/globdefs.h

@@ -22,6 +22,7 @@
 
 #define I2C_SYSTEM_PORT	1
 extern int i2c_system_port;
+extern bool gpio36_39_used;
 
 #ifdef CONFIG_SQUEEZEAMP
 #define JACK_GPIO		34
@@ -31,4 +32,5 @@ extern int i2c_system_port;
 #else 
 #define LED_GREEN_GPIO	CONFIG_LED_GREEN_GPIO
 #define LED_RED_GPIO	CONFIG_LED_RED_GPIO
+#define JACK_GPIO		CONFIG_JACK_GPIO
 #endif

+ 29 - 10
components/services/monitor.c

@@ -19,6 +19,7 @@
 #include "buttons.h"
 #include "led.h"
 #include "globdefs.h"
+#include "config.h"
 
 #define MONITOR_TIMER	(10*1000)
 
@@ -83,19 +84,31 @@ bool spkfault_svc (void) {
 #endif
 }
 
-#include "driver/rtc_io.h" 
+/****************************************************************************************
+ * 
+ */
+void set_jack_gpio(int gpio, char *value) {
+	 if (!strcasecmp(value, "jack")) {
+		ESP_LOGI(TAG,"Adding jack detection GPIO %d", gpio);
+		
+		gpio_pad_select_gpio(JACK_GPIO);
+		gpio_set_direction(JACK_GPIO, GPIO_MODE_INPUT);
+
+		// re-use button management for jack handler, it's a GPIO after all
+		button_create(NULL, JACK_GPIO, BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
+	 }	
+ }
+
 /****************************************************************************************
  * 
  */
 void monitor_svc_init(void) {
 	ESP_LOGI(TAG, "Initializing monitoring");
-
-#ifdef JACK_GPIO
-	gpio_pad_select_gpio(JACK_GPIO);
-	gpio_set_direction(JACK_GPIO, GPIO_MODE_INPUT);
-
-	// re-use button management for jack handler, it's a GPIO after all
-	button_create(NULL, JACK_GPIO, BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
+	
+#if !defined(JACK_GPIO) || JACK_GPIO == -1
+	parse_set_GPIO(set_jack_gpio);
+#else 
+	set_jack_gpio(JACK_GPIO, "jack");	
 #endif
 
 #ifdef SPKFAULT_GPIO
@@ -107,6 +120,12 @@ void monitor_svc_init(void) {
 	button_create(NULL, SPKFAULT_GPIO, BUTTON_LOW, true, 0, spkfault_handler_default, 0, -1);
 #endif
 
-	monitor_timer = xTimerCreate("monitor", MONITOR_TIMER / portTICK_RATE_MS, pdTRUE, NULL, monitor_callback);
-	xTimerStart(monitor_timer, portMAX_DELAY);
+	// do we want stats
+	char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
+	if (p && (*p == '1' || *p == 'Y' || *p == 'y')) {
+		monitor_timer = xTimerCreate("monitor", MONITOR_TIMER / portTICK_RATE_MS, pdTRUE, NULL, monitor_callback);
+		xTimerStart(monitor_timer, portMAX_DELAY);
+	}	
+	free(p);
+	
 }

+ 2 - 0
components/services/monitor.h

@@ -26,3 +26,5 @@ extern bool jack_inserted_svc(void);
 extern void (*spkfault_handler_svc)(bool inserted);
 extern bool spkfault_svc(void);
 
+extern int battery_value_svc(void);
+

+ 21 - 14
components/services/services.c

@@ -25,6 +25,25 @@ int i2c_system_port = I2C_SYSTEM_PORT;
 
 static const char *TAG = "services";
 
+/****************************************************************************************
+ * 
+ */
+void set_power_gpio(int gpio, char *value) {
+	bool parsed = true;
+	
+	if (!strcasecmp(value, "vcc") ) {
+		gpio_pad_select_gpio(gpio);
+		gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
+		gpio_set_level(gpio, 1);
+	} else if (!strcasecmp(value, "gnd")) {
+		gpio_pad_select_gpio(gpio);
+		gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
+		gpio_set_level(gpio, 0);
+	} else parsed = false	;
+	
+	if (parsed) ESP_LOGI(TAG, "set GPIO %u to %s", gpio, value);
+ }	
+
 /****************************************************************************************
  * 
  */
@@ -40,20 +59,8 @@ void services_init(void) {
 	}
 #endif
 
-	// set fixed gpio if any
-	if ((nvs_item = config_alloc_get(NVS_TYPE_STR, "Vcc_GPIO")) != NULL) {
-		char *p = nvs_item;
-		while (p && *p) {
-			int gpio = atoi(p);
-			gpio_pad_select_gpio(gpio);
-			gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
-			gpio_set_level(gpio, 1);
-			p = strchr(p, ',');
-			ESP_LOGI(TAG, "set GPIO %u to Vcc", gpio);
-			if (p) p++;
-		} 
-		free(nvs_item);
-	}	
+	// set potential power GPIO
+	parse_set_GPIO(set_power_gpio);
 
 	const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port);
 

+ 1 - 1
components/services/tarablessd1306/ssd1306_font.c

@@ -47,7 +47,7 @@ void SSD1306_FontDrawChar( struct SSD1306_Device* DisplayHandle, char Character,
     
     NullCheck( ( GlyphData = GetCharPtr( DisplayHandle->Font, Character ) ), return );
 
-    if ( Character >= DisplayHandle->Font->StartChar || Character <= DisplayHandle->Font->EndChar ) {
+    if ( Character >= DisplayHandle->Font->StartChar && Character <= DisplayHandle->Font->EndChar ) {
         /* The first byte in the glyph data is the width of the character in pixels, skip over */
         GlyphData++;
         GlyphColumnLen = RoundUpFontHeight( DisplayHandle->Font ) / 8;

+ 1 - 1
components/squeezelite-ota/cmd_ota.c

@@ -26,7 +26,7 @@
 #include "esp32/rom/uart.h"
 #include "sdkconfig.h"
 
-static const char * TAG = "platform_esp32";
+static const char * TAG = "ota";
 extern esp_err_t start_ota(const char * bin_url);
 static struct {
     struct arg_str *url;

+ 435 - 0
components/squeezelite/a1s/ac101.c

@@ -0,0 +1,435 @@
+/*
+ * ESPRESSIF MIT License
+ *
+ * Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
+ *
+ * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
+ * it is free of charge, to any person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <string.h>
+#include <esp_log.h>
+#include <esp_types.h>
+#include <esp_system.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <driver/i2c.h>
+#include "adac.h"
+
+//#include "audio_hal.h"
+#include "ac101.h"
+
+const static char TAG[] = "AC101";
+
+#define AC_ASSERT(a, format, b, ...) \
+    if ((a) != 0) { \
+        ESP_LOGE(TAG, format, ##__VA_ARGS__); \
+        return b;\
+    }
+	
+static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config);
+static void deinit(void);
+static void speaker(bool active);
+static void headset(bool active);
+static void volume(unsigned left, unsigned right);
+static void power(adac_power_e mode);
+
+struct adac_s dac_a1s = { init, 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 esp_err_t ac101_start(ac_module_t mode);
+static esp_err_t ac101_stop(void);
+static esp_err_t ac101_set_earph_volume(uint8_t volume);
+static esp_err_t ac101_set_spk_volume(uint8_t volume);
+	
+//static void pa_power(bool enable);
+
+static int i2c_port;
+
+/****************************************************************************************
+ * init
+ */
+static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {	 
+	esp_err_t res;
+	
+	ESP_LOGI(TAG, "Initializing AC101");
+	i2c_port = i2c_port_num;
+
+	// configure i2c
+	i2c_config_t i2c_config = {
+			.mode = I2C_MODE_MASTER,
+			.sda_io_num = 33,
+			.sda_pullup_en = GPIO_PULLUP_ENABLE,
+			.scl_io_num = 32,
+			.scl_pullup_en = GPIO_PULLUP_ENABLE,
+			.master.clk_speed = 100000,
+		};
+		
+	i2c_param_config(i2c_port, &i2c_config);
+	i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
+	ESP_LOGI(TAG, "DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num);
+	
+	res = i2c_write_reg(CHIP_AUDIO_RS, 0x123);
+	
+	//huh?
+	//vTaskDelay(1000 / portTICK_PERIOD_MS); 
+	if (ESP_OK != res) {
+		ESP_LOGE(TAG, "reset failed!");
+		return false;
+	} 
+	
+	i2c_write_reg(SPKOUT_CTRL, 0xe880);
+
+	// Enable the PLL from 256*44.1KHz MCLK source
+	i2c_write_reg(PLL_CTRL1, 0x014f);
+	//res |= i2c_write_reg(PLL_CTRL2, 0x83c0);
+	i2c_write_reg(PLL_CTRL2, 0x8600);
+
+	//Clocking system
+	i2c_write_reg(SYSCLK_CTRL, 0x8b08);
+	i2c_write_reg(MOD_CLK_ENA, 0x800c);
+	i2c_write_reg(MOD_RST_CTRL, 0x800c);
+	i2c_write_reg(I2S_SR_CTRL, 0x7000);			//sample rate
+	 
+	//AIF config
+	i2c_write_reg(I2S1LCK_CTRL, 0x8850);			//BCLK/LRCK
+	i2c_write_reg(I2S1_SDOUT_CTRL, 0xc000);		//
+	i2c_write_reg(I2S1_SDIN_CTRL, 0xc000);
+	i2c_write_reg(I2S1_MXR_SRC, 0x2200);			//
+
+	i2c_write_reg(ADC_SRCBST_CTRL, 0xccc4);
+	i2c_write_reg(ADC_SRC, 0x2020);
+	i2c_write_reg(ADC_DIG_CTRL, 0x8000);
+	i2c_write_reg(ADC_APC_CTRL, 0xbbc3);
+
+	//Path Configuration
+	i2c_write_reg(DAC_MXR_SRC, 0xcc00);
+	i2c_write_reg(DAC_DIG_CTRL, 0x8000);
+	i2c_write_reg(OMIXER_SR, 0x0081);
+	i2c_write_reg(OMIXER_DACA_CTRL, 0xf080);//}
+
+	//* Enable Speaker output
+	i2c_write_reg(0x58, 0xeabd);
+
+    //ac101_pa_power(true);
+
+	uint16_t regval;
+
+	// configure I2S		
+	regval = i2c_read_reg(I2S1LCK_CTRL);
+	regval &= 0xffc3;
+	regval |= (AC_MODE_SLAVE << 15);
+	regval |= (BIT_LENGTH_16_BITS << 4);
+	regval |= (AC_MODE_SLAVE << 2);
+	res |= i2c_write_reg(I2S1LCK_CTRL, regval);
+	res |= i2c_write_reg(I2S_SR_CTRL, SAMPLE_RATE_44100);
+			
+	// configure I2S pins & install driver	
+	i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { 	.bck_io_num = 27, .ws_io_num = 26, 
+															.data_out_num = 25, .data_in_num = 35 //Not used 
+								};
+	i2s_driver_install(i2s_num, i2s_config, 0, NULL);
+	i2s_set_pin(i2s_num, &i2s_pin_config);
+
+	ESP_LOGI(TAG, "DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
+
+	return true;
+}	
+
+/****************************************************************************************
+ * init
+ */
+static void deinit(void)	{	 
+	i2c_driver_delete(i2c_port);
+}
+
+/****************************************************************************************
+ * change volume
+ */
+static void volume(unsigned left, unsigned right) {
+	// nothing at that point, volume is handled by backend
+} 
+
+/****************************************************************************************
+ * power
+ */
+static void power(adac_power_e mode) {
+	esp_err_t ret = ESP_OK;
+	
+	switch(mode) {
+	case ADAC_STANDBY:
+	case ADAC_OFF:
+		ret = ac101_stop();
+		break;
+	case ADAC_ON:
+		ret = ac101_start(AC_MODULE_DAC);
+		break;		
+	default:
+		ESP_LOGW(TAG, "unknown power command");
+		break;
+	}
+	
+	if (ret != ESP_OK) ESP_LOGW(TAG, "can't start AC101 %d", ret);
+}
+
+/****************************************************************************************
+ * speaker
+ */
+static void speaker(bool active) {
+	if (active) i2c_write_reg(SPKOUT_CTRL, 0xeabd);
+	else i2c_write_reg(SPKOUT_CTRL, 0xe880);		//disable speaker
+} 
+
+/****************************************************************************************
+ * headset
+ */
+static void headset(bool active) {
+	if (active) {
+		i2c_write_reg(OMIXER_DACA_CTRL, 0xff80);
+    	i2c_write_reg(HPOUT_CTRL, 0xc3c1);
+    	i2c_write_reg(HPOUT_CTRL, 0xcb00);
+		// huh?
+    	vTaskDelay(100 / portTICK_PERIOD_MS);
+		i2c_write_reg(HPOUT_CTRL, 0xfbc0);
+	} else {
+		i2c_write_reg(HPOUT_CTRL, 0x01);			//disable earphone
+	}	
+} 	
+
+/****************************************************************************************
+ * 
+ */
+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];;
+}
+
+/****************************************************************************************
+ * 
+ */
+void set_codec_clk(ac_adda_fs_i2s1_t rate) {
+	i2c_write_reg(I2S_SR_CTRL, rate);
+}
+
+/****************************************************************************************
+ * 
+ */
+static int ac101_get_spk_volume(void) {
+    int res;
+    res = i2c_read_reg(SPKOUT_CTRL);
+    res &= 0x1f;
+    return res*2;
+}
+
+/****************************************************************************************
+ * 
+ */
+static esp_err_t ac101_set_spk_volume(uint8_t volume) {
+	uint16_t res;
+	esp_err_t ret;
+	volume = volume/2;
+	res = i2c_read_reg(SPKOUT_CTRL);
+	res &= (~0x1f);
+	volume &= 0x1f;
+	res |= volume;
+	ret = i2c_write_reg(SPKOUT_CTRL,res);
+	return ret;
+}
+
+/****************************************************************************************
+ * 
+ */
+static int ac101_get_earph_volume(void) {
+    int res;
+    res = i2c_read_reg(HPOUT_CTRL);
+    return (res>>4)&0x3f;
+}
+
+/****************************************************************************************
+ * 
+ */
+static esp_err_t ac101_set_earph_volume(uint8_t volume) {
+	uint16_t res,tmp;
+	esp_err_t ret;
+	res = i2c_read_reg(HPOUT_CTRL);
+	tmp = ~(0x3f<<4);
+	res &= tmp;
+	volume &= 0x3f;
+	res |= (volume << 4);
+	ret = i2c_write_reg(HPOUT_CTRL,res);
+	return ret;
+}
+
+/****************************************************************************************
+ * 
+ */
+static esp_err_t ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mixer_source_t source)
+{
+	uint16_t regval,temp,clrbit;
+	esp_err_t ret;
+	regval = i2c_read_reg(OMIXER_BST1_CTRL);
+	switch(source){
+	case SRC_MIC1:
+		temp = (gain&0x7) << 6;
+		clrbit = ~(0x7<<6);
+		break;
+	case SRC_MIC2:
+		temp = (gain&0x7) << 3;
+		clrbit = ~(0x7<<3);
+		break;
+	case SRC_LINEIN:
+		temp = (gain&0x7);
+		clrbit = ~0x7;
+		break;
+	default:
+		return -1;
+	}
+	regval &= clrbit;
+	regval |= temp;
+	ret = i2c_write_reg(OMIXER_BST1_CTRL,regval);
+	return ret;
+}
+
+/****************************************************************************************
+ * 
+ */
+static esp_err_t ac101_start(ac_module_t mode) {
+	esp_err_t res = 0;
+	
+    if (mode == AC_MODULE_LINE) {
+		res |= i2c_write_reg(0x51, 0x0408);
+		res |= i2c_write_reg(0x40, 0x8000);
+		res |= i2c_write_reg(0x50, 0x3bc0);
+    }
+    if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) {
+		//I2S1_SDOUT_CTRL
+		//res |= i2c_write_reg(PLL_CTRL2, 0x8120);
+    	res |= i2c_write_reg(0x04, 0x800c);
+    	res |= i2c_write_reg(0x05, 0x800c);
+		//res |= i2c_write_reg(0x06, 0x3000);
+    }
+    if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) {
+    	//* Enable Headphone output   注意使用耳机时,最后开以下寄存器
+		res |= i2c_write_reg(OMIXER_DACA_CTRL, 0xff80);
+    	res |= i2c_write_reg(HPOUT_CTRL, 0xc3c1);
+    	res |= i2c_write_reg(HPOUT_CTRL, 0xcb00);
+		// huh?
+    	vTaskDelay(100 / portTICK_PERIOD_MS);
+		res |= i2c_write_reg(HPOUT_CTRL, 0xfbc0);
+
+    	//* Enable Speaker output
+		res |= i2c_write_reg(SPKOUT_CTRL, 0xeabd);
+		// huh?
+		vTaskDelay(10 / portTICK_PERIOD_MS);
+		ac101_set_earph_volume(255);
+		ac101_set_spk_volume(255);
+    }
+
+    return res;
+}
+
+/****************************************************************************************
+ * 
+ */
+esp_err_t ac101_stop(void) {
+	esp_err_t res = 0;
+	res |= i2c_write_reg(HPOUT_CTRL, 0x01);			//disable earphone
+	res |= i2c_write_reg(SPKOUT_CTRL, 0xe880);		//disable speaker
+	return res;
+}
+
+/****************************************************************************************
+ * 
+ */
+esp_err_t ac101_deinit(void) {
+	return	i2c_write_reg(CHIP_AUDIO_RS, 0x123);		//soft reset
+}
+
+
+/****************************************************************************************
+ * Don't know when this one is supposed to be called
+ */
+esp_err_t AC101_i2s_config_clock(ac_i2s_clock_t *cfg) {
+	esp_err_t res = 0;
+	uint16_t regval=0;
+	regval = i2c_read_reg(I2S1LCK_CTRL);
+	regval &= 0xe03f;
+	regval |= (cfg->bclk_div << 9);
+	regval |= (cfg->lclk_div << 6);
+	res = i2c_write_reg(I2S1LCK_CTRL, regval);
+	return res;
+}
+
+/****************************************************************************************
+ * 
+ */
+esp_err_t ac101_get_voice_volume(int* volume) {
+	*volume = ac101_get_earph_volume();
+	return 0;
+}
+
+/*
+void ac101_pa_power(bool enable) {
+    gpio_config_t  io_conf;
+    memset(&io_conf, 0, sizeof(io_conf));
+    io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
+    io_conf.mode = GPIO_MODE_OUTPUT;
+    io_conf.pin_bit_mask = BIT(GPIO_PA_EN);
+    io_conf.pull_down_en = 0;
+    io_conf.pull_up_en = 0;
+    gpio_config(&io_conf);
+    if (enable) {
+        gpio_set_level(GPIO_PA_EN, 1);
+    } else {
+        gpio_set_level(GPIO_PA_EN, 0);
+    }
+}
+*/

+ 176 - 0
components/squeezelite/a1s/ac101.h

@@ -0,0 +1,176 @@
+/*
+ * ESPRESSIF MIT License
+ *
+ * Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
+ *
+ * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
+ * it is free of charge, to any person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+ 
+#ifndef __AC101_H__
+#define __AC101_H__
+
+#include "esp_types.h"
+
+#define AC101_ADDR			0x1a				/*!< Device address*/
+
+#define WRITE_BIT  			I2C_MASTER_WRITE 	/*!< I2C master write */
+#define READ_BIT   			I2C_MASTER_READ  	/*!< I2C master read */
+#define ACK_CHECK_EN   		0x1     			/*!< I2C master will check ack from slave*/
+#define ACK_CHECK_DIS  		0x0     			/*!< I2C master will not check ack from slave */
+#define ACK_VAL    			0x0         		/*!< I2C ack value */
+#define NACK_VAL   			0x1         		/*!< I2C nack value */
+
+#define CHIP_AUDIO_RS		0x00
+#define PLL_CTRL1			0x01
+#define PLL_CTRL2			0x02
+#define SYSCLK_CTRL			0x03
+#define MOD_CLK_ENA			0x04
+#define MOD_RST_CTRL		0x05
+#define I2S_SR_CTRL			0x06
+#define I2S1LCK_CTRL		0x10
+#define I2S1_SDOUT_CTRL		0x11
+#define I2S1_SDIN_CTRL		0x12
+#define I2S1_MXR_SRC		0x13
+#define I2S1_VOL_CTRL1		0x14
+#define I2S1_VOL_CTRL2		0x15
+#define I2S1_VOL_CTRL3		0x16
+#define I2S1_VOL_CTRL4		0x17
+#define I2S1_MXR_GAIN		0x18
+#define ADC_DIG_CTRL		0x40
+#define ADC_VOL_CTRL		0x41
+#define HMIC_CTRL1			0x44
+#define HMIC_CTRL2			0x45
+#define HMIC_STATUS			0x46
+#define DAC_DIG_CTRL		0x48
+#define DAC_VOL_CTRL		0x49
+#define DAC_MXR_SRC			0x4c
+#define DAC_MXR_GAIN		0x4d
+#define ADC_APC_CTRL		0x50
+#define ADC_SRC				0x51
+#define ADC_SRCBST_CTRL		0x52
+#define OMIXER_DACA_CTRL	0x53
+#define OMIXER_SR			0x54
+#define OMIXER_BST1_CTRL	0x55
+#define HPOUT_CTRL			0x56
+#define SPKOUT_CTRL			0x58
+#define AC_DAC_DAPCTRL		0xa0
+#define AC_DAC_DAPHHPFC 	0xa1
+#define AC_DAC_DAPLHPFC 	0xa2
+#define AC_DAC_DAPLHAVC 	0xa3
+#define AC_DAC_DAPLLAVC 	0xa4
+#define AC_DAC_DAPRHAVC 	0xa5
+#define AC_DAC_DAPRLAVC 	0xa6
+#define AC_DAC_DAPHGDEC 	0xa7
+#define AC_DAC_DAPLGDEC 	0xa8
+#define AC_DAC_DAPHGATC 	0xa9
+#define AC_DAC_DAPLGATC 	0xaa
+#define AC_DAC_DAPHETHD 	0xab
+#define AC_DAC_DAPLETHD 	0xac
+#define AC_DAC_DAPHGKPA 	0xad
+#define AC_DAC_DAPLGKPA 	0xae
+#define AC_DAC_DAPHGOPA 	0xaf
+#define AC_DAC_DAPLGOPA 	0xb0
+#define AC_DAC_DAPOPT   	0xb1
+#define DAC_DAP_ENA     	0xb5
+
+typedef enum{
+	SAMPLE_RATE_8000	= 0x0000,
+	SAMPLE_RATE_11052	= 0x1000,
+	SAMPLE_RATE_12000	= 0x2000,
+	SAMPLE_RATE_16000	= 0x3000,
+	SAMPLE_RATE_22050	= 0x4000,
+	SAMPLE_RATE_24000	= 0x5000,
+	SAMPLE_RATE_32000	= 0x6000,
+	SAMPLE_RATE_44100	= 0x7000,
+	SAMPLE_RATE_48000	= 0x8000,
+	SAMPLE_RATE_96000	= 0x9000,
+	SAMPLE_RATE_192000	= 0xa000,
+} ac_adda_fs_i2s1_t;
+
+typedef enum{
+	BCLK_DIV_1		= 0x0,
+	BCLK_DIV_2		= 0x1,
+	BCLK_DIV_4		= 0x2,
+	BCLK_DIV_6		= 0x3,
+	BCLK_DIV_8		= 0x4,
+	BCLK_DIV_12		= 0x5,
+	BCLK_DIV_16		= 0x6,
+	BCLK_DIV_24		= 0x7,
+	BCLK_DIV_32		= 0x8,
+	BCLK_DIV_48		= 0x9,
+	BCLK_DIV_64		= 0xa,
+	BCLK_DIV_96		= 0xb,
+	BCLK_DIV_128	= 0xc,
+	BCLK_DIV_192	= 0xd,
+} ac_i2s1_bclk_div_t;
+
+typedef enum{
+	LRCK_DIV_16		=0x0,
+	LRCK_DIV_32		=0x1,
+	LRCK_DIV_64		=0x2,
+	LRCK_DIV_128	=0x3,
+	LRCK_DIV_256	=0x4,
+} ac_i2s1_lrck_div_t;
+
+typedef enum {
+    BIT_LENGTH_8_BITS = 0x00,
+    BIT_LENGTH_16_BITS = 0x01,
+    BIT_LENGTH_20_BITS = 0x02,
+    BIT_LENGTH_24_BITS = 0x03,
+} ac_bits_length_t;
+
+typedef enum {
+    AC_MODE_MIN = -1,
+    AC_MODE_SLAVE = 0x00,
+    AC_MODE_MASTER = 0x01,
+    AC_MODE_MAX,
+} ac_mode_sm_t;
+
+typedef enum {
+    AC_MODULE_MIN = -1,
+    AC_MODULE_ADC = 0x01,
+    AC_MODULE_DAC = 0x02,
+    AC_MODULE_ADC_DAC = 0x03,
+    AC_MODULE_LINE = 0x04,
+    AC_MODULE_MAX
+} ac_module_t;
+
+typedef enum{
+	SRC_MIC1	= 1,
+	SRC_MIC2	= 2,
+	SRC_LINEIN	= 3,
+}ac_output_mixer_source_t;
+
+typedef enum {
+    GAIN_N45DB = 0,
+    GAIN_N30DB = 1,
+    GAIN_N15DB = 2,
+    GAIN_0DB   = 3,
+    GAIN_15DB  = 4,
+    GAIN_30DB  = 5,
+    GAIN_45DB  = 6,
+    GAIN_60DB  = 7,
+} ac_output_mixer_gain_t;
+
+typedef struct {
+	ac_i2s1_bclk_div_t bclk_div;    /*!< bits clock divide */
+	ac_i2s1_lrck_div_t lclk_div;    /*!< WS clock divide */
+} ac_i2s_clock_t;
+
+#endif

+ 38 - 0
components/squeezelite/adac.h

@@ -0,0 +1,38 @@
+/* 
+ *  Squeezelite for esp32
+ *
+ *  (c) Sebastien 2019
+ *      Philippe G. 2019, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "freertos/FreeRTOS.h"
+#include "driver/i2s.h"
+
+typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e;
+
+struct adac_s {
+	bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t *config);
+	void (*deinit)(void);
+	void (*power)(adac_power_e mode);
+	void (*speaker)(bool active);
+	void (*headset)(bool active);
+	void (*volume)(unsigned left, unsigned right);
+};
+
+extern struct adac_s dac_tas57xx;
+extern struct adac_s dac_a1s;
+extern struct adac_s dac_null;

+ 2 - 1
components/squeezelite/component.mk

@@ -19,5 +19,6 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON
 
 #	-I$(COMPONENT_PATH)/../codecs/inc/faad2
 
-
+COMPONENT_SRCDIRS := . tas57xx a1s null
+COMPONENT_ADD_INCLUDEDIRS := . ./tas57xx ./a1s
 

+ 1 - 1
components/squeezelite/decode_external.c

@@ -207,7 +207,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
 				ms = ((u64_t) ((_buf_used(outputbuf) - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 1000) / RAOP_SAMPLE_RATE - (now - output.updated);
 				raop_sync.error[raop_sync.idx] = (raop_sync.playtime - now) - ms;
 				sync_nb = SYNC_NB;
-				LOG_INFO("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, raop_sync.error[raop_sync.idx]);
+				LOG_DEBUG("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, raop_sync.error[raop_sync.idx]);
 				LOG_DEBUG("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
 			}	
 			

+ 13 - 5
components/squeezelite/display.c

@@ -137,10 +137,16 @@ static void scroll_task(void* arg);
 /****************************************************************************************
  * 
  */
-void sb_display_init(void) {
+bool sb_display_init(void) {
 	static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
 	static EXT_RAM_ATTR StackType_t xStack[SCROLL_STACK_SIZE] __attribute__ ((aligned (4)));
 	
+	// no display, just make sure we won't have requests
+	if (!display || display->height == 0 || display->width == 0) {
+		LOG_INFO("no display for LMS");
+		return false;
+	}	
+	
 	// need to force height to 32 maximum
 	display_width = display->width;
 	display_height = min(display->height, 32);
@@ -163,6 +169,8 @@ void sb_display_init(void) {
 	
 	notify_chain = server_notify;
 	server_notify = server;
+	
+	return true;
 }
 
 /****************************************************************************************
@@ -288,7 +296,7 @@ static void show_display_buffer(char *ddram) {
 	makeprintable((unsigned char *)line1);
 	makeprintable((unsigned char *)line2);
 
-	LOG_INFO("\n\t%.40s\n\t%.40s", line1, line2);
+	LOG_DEBUG("\n\t%.40s\n\t%.40s", line1, line2);
 
 	display->line(1, DISPLAY_LEFT, DISPLAY_CLEAR, line1);	
 	display->line(2, DISPLAY_LEFT, DISPLAY_CLEAR | DISPLAY_UPDATE, line2);	
@@ -380,7 +388,7 @@ static void grfs_handler(u8_t *data, int len) {
 	int size = len - sizeof(struct grfs_packet);
 	int offset = htons(pkt->offset);
 	
-	LOG_INFO("gfrs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu", 
+	LOG_DEBUG("gfrs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu", 
 				(int) pkt->screen,
 				(int) pkt->direction,	// 1=left, 2=right
 				htonl(pkt->pause),		// in ms	
@@ -425,7 +433,7 @@ static void grfs_handler(u8_t *data, int len) {
 static void grfg_handler(u8_t *data, int len) {
 	struct grfg_packet *pkt = (struct grfg_packet*) data;
 	
-	LOG_INFO("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
+	LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
 	
 	memcpy(scroller.back_frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
 	scroller.window_width = htons(pkt->width);
@@ -457,7 +465,7 @@ static void grfg_handler(u8_t *data, int len) {
 	}	
 	else {
 		// if we just got a content update, let the scroller manage the screen
-		LOG_INFO("resuming scrolling task");
+		LOG_DEBUG("resuming scrolling task");
 		vTaskResume(scroller.task);
 	}
 	

+ 4 - 2
components/squeezelite/embedded.c

@@ -50,9 +50,11 @@ uint32_t _gettime_ms_(void) {
 }
 
 extern void sb_controls_init(void);
-extern void sb_display_init(void);
+extern bool sb_display_init(void);
+
+u8_t custom_player_id = 12;
 
 void embedded_init(void) {
 	sb_controls_init();
-	sb_display_init();
+	if (sb_display_init()) custom_player_id = 100;
 }

+ 4 - 1
components/squeezelite/embedded.h

@@ -26,7 +26,10 @@
 #define OUTPUT_THREAD_STACK_SIZE  6 * 1024
 #define IR_THREAD_STACK_SIZE      6 * 1024
 
-#define PLAYER_ID 100
+// or can be as simple as #define PLAYER_ID 100
+#define PLAYER_ID custom_player_id;
+extern u8_t custom_player_id;
+
 #define BASE_CAP "Model=squeezeesp32,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
 #define EXT_BSS __attribute__((section(".ext_ram.bss"))) 
 

+ 3 - 1
components/squeezelite/main.c

@@ -52,8 +52,10 @@ static void usage(const char *argv0) {
 	printf(TITLE " See -t for license terms\n"
 		   "Usage: %s [options]\n"
 		   "  -s <server>[:<port>]\tConnect to specified server, otherwise uses autodiscovery to find server\n"
+#if !EMBEDDED		   
 		   "  -o <output device>\tSpecify output device, default \"default\", - = output to stdout\n"
 		   "  -l \t\t\tList output devices\n"
+#endif		   
 #if ALSA
 		   "  -a <b>:<p>:<f>:<m>\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n"
 #endif
@@ -535,7 +537,7 @@ int main(int argc, char **argv) {
 			pidfile = optarg;
 			break;
 #endif
-#ifndef EMBEDDED
+#if !EMBEDDED
 		case 'l':
 			list_devices();
 			exit(0);

+ 32 - 0
components/squeezelite/null/dac_null.c

@@ -0,0 +1,32 @@
+/* 
+ *  Squeezelite for esp32
+ *
+ *  (c) Sebastien 2019
+ *      Philippe G. 2019, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+ 
+#include "adac.h"
+
+static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config) { return true; };
+static void deinit(void) { };
+static void speaker(bool active) { };
+static void headset(bool active) { } ;
+static void volume(unsigned left, unsigned right) { };
+static void power(adac_power_e mode) { };
+
+struct adac_s dac_null = { init, deinit, power, speaker, headset, volume };
+

+ 7 - 7
components/squeezelite/output_bt.c

@@ -22,6 +22,7 @@
 #include "driver/gpio.h"
 #include "squeezelite.h"
 #include "perf_trace.h"
+#include "config.h"
 
 extern struct outputstate output;
 extern struct buffer *outputbuf;
@@ -45,6 +46,7 @@ static log_level loglevel;
 static bool running = false;
 static uint8_t *btout;
 static frames_t oframes;
+static bool stats;
 
 static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
 								s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
@@ -68,15 +70,13 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
 DECLARE_ALL_MIN_MAX;	
 	
 void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
-#ifdef CONFIG_SQUEEZEAMP
-	gpio_pad_select_gpio(config_spdif_gpio);
-	gpio_set_direction(config_spdif_gpio, GPIO_MODE_OUTPUT);
-	gpio_set_level(config_spdif_gpio, 0);
-#endif			
 	loglevel = level;
 	running = true;
 	output.write_cb = &_write_frames;
 	hal_bluetooth_init(device);
+	char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
+	stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
+	free(p);
 }
 
 void output_close_bt(void) {
@@ -166,12 +166,12 @@ void output_bt_tick(void) {
 	static time_t lastTime=0;
 	
 	if (!running) return;
-		
+	
 	LOCK_S;
     SET_MIN_MAX_SIZED(_buf_used(streambuf), stream_buf, streambuf->size);
     UNLOCK_S;
 	
-	if (lastTime <= gettime_ms() )
+	if (stats && lastTime <= gettime_ms() )
 	{
 		lastTime = gettime_ms() + STATS_REPORT_DELAY_MS;
 		LOG_INFO("Statistics over %u secs. " , STATS_REPORT_DELAY_MS/1000);

+ 108 - 257
components/squeezelite/output_i2s.c

@@ -47,46 +47,18 @@ sure that using rate_delay would fix that
 #include "driver/gpio.h"
 #include "perf_trace.h"
 #include <signal.h>
+#include "adac.h"
 #include "time.h"
 #include "led.h"
 #include "monitor.h"
 #include "config.h"
+#include "accessors.h"
 
 #define LOCK   mutex_lock(outputbuf->mutex)
 #define UNLOCK mutex_unlock(outputbuf->mutex)
 
 #define FRAME_BLOCK MAX_SILENCE_FRAMES
 
-// Prevent compile errors if dac output is
-// included in the build and not actually activated in menuconfig
-#ifndef CONFIG_I2S_BCK_IO
-#define CONFIG_I2S_BCK_IO -1
-#endif
-#ifndef CONFIG_I2S_WS_IO
-#define CONFIG_I2S_WS_IO -1
-#endif
-#ifndef CONFIG_I2S_DO_IO
-#define CONFIG_I2S_DO_IO -1
-#endif
-#ifndef CONFIG_I2S_NUM
-#define CONFIG_I2S_NUM -1
-#endif
-
-#ifndef CONFIG_SPDIF_BCK_IO
-#define CONFIG_SPDIF_BCK_IO -1
-#endif
-#ifndef CONFIG_SPDIF_WS_IO
-#define CONFIG_SPDIF_WS_IO -1
-#endif
-#ifndef CONFIG_SPDIF_DO_IO
-#define CONFIG_SPDIF_DO_IO -1
-#endif
-#ifndef CONFIG_SPDIF_NUM
-#define CONFIG_SPDIF_NUM -1
-#endif
-
-typedef enum { DAC_ACTIVE = 0, DAC_STANDBY, DAC_DOWN, DAC_ANALOGUE_OFF, DAC_ANALOGUE_ON, DAC_VOLUME } dac_cmd_e;
-
 // must have an integer ratio with FRAME_BLOCK (see spdif comment)
 #define DMA_BUF_LEN		512	
 #define DMA_BUF_COUNT	12
@@ -106,48 +78,43 @@ typedef enum { DAC_ACTIVE = 0, DAC_STANDBY, DAC_DOWN, DAC_ANALOGUE_OFF, DAC_ANAL
 	RESET_MIN_MAX(buffering);
 	
 #define STATS_PERIOD_MS 5000
+#define STAT_STACK_SIZE	(3*1024)
 
 extern struct outputstate output;
 extern struct buffer *streambuf;
 extern struct buffer *outputbuf;
 extern u8_t *silencebuf;
 
+// by default no DAC selected
+struct adac_s *adac = &dac_null;
+
 static log_level loglevel;
 
 static bool jack_mutes_amp;
 static bool running, isI2SStarted;
 static i2s_config_t i2s_config;
 static int bytes_per_frame;
-static thread_type thread, stats_thread;
 static u8_t *obuf;
 static frames_t oframes;
 static bool spdif;
 static size_t dma_buf_frames;
+static pthread_t thread;
+static TaskHandle_t stats_task;
+static bool stats;
+static int amp_gpio = -1;
 
 DECLARE_ALL_MIN_MAX;
 
 static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
 								s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
-static void *output_thread_i2s();
-static void *output_thread_i2s_stats();
-static void dac_cmd(dac_cmd_e cmd, ...);
-static int tas57_detect(void);
+static void *output_thread_i2s(void *arg);
+static void *output_thread_i2s_stats(void *arg);
 static void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count);
 static void (*jack_handler_chain)(bool inserted);
 
+// force all GPIOs to what we need
 #ifdef CONFIG_SQUEEZEAMP
-
 #define TAS57xx
-
-#undef	CONFIG_I2S_BCK_IO 
-#define CONFIG_I2S_BCK_IO 	33
-#undef 	CONFIG_I2S_WS_IO	
-#define CONFIG_I2S_WS_IO	25
-#undef 	CONFIG_I2S_DO_IO
-#define CONFIG_I2S_DO_IO	32
-#undef 	CONFIG_I2S_NUM
-#define CONFIG_I2S_NUM		0
-
 #undef	CONFIG_SPDIF_BCK_IO 
 #define CONFIG_SPDIF_BCK_IO 33
 #undef 	CONFIG_SPDIF_WS_IO	
@@ -156,51 +123,15 @@ static void (*jack_handler_chain)(bool inserted);
 #define CONFIG_SPDIF_DO_IO	15
 #undef 	CONFIG_SPDIF_NUM
 #define CONFIG_SPDIF_NUM	0
+#undef 	CONFIG_I2S_NUM
+#define CONFIG_I2S_NUM		0
+#elif defined CONFIG_A1S
+#define A1S
+#undef 	CONFIG_I2S_NUM
+#define CONFIG_I2S_NUM		0
+#endif
 
 #define I2C_PORT	0
-#define VOLUME_GPIO	14
-
-#define TAS575x 0x98
-#define TAS578x	0x90
-
-struct tas57xx_cmd_s {
-	u8_t reg;
-	u8_t value;
-};
-
-u8_t config_spdif_gpio = CONFIG_SPDIF_DO_IO;
-	
-static const struct tas57xx_cmd_s tas57xx_init_sequence[] = {
-    { 0x00, 0x00 },		// select page 0
-    { 0x02, 0x10 },		// standby
-    { 0x0d, 0x10 },		// use SCK for PLL
-	{ 0x25, 0x08 },		// ignore SCK halt 
-	{ 0x08, 0x10 },		// Mute control enable (from TAS5780)
-	{ 0x54, 0x02 },		// Mute output control (from TAS5780)
-	{ 0x02, 0x00 },		// restart
-	{ 0xff, 0xff }		// end of table
-};
-
-static const i2c_config_t i2c_config = {
-        .mode = I2C_MODE_MASTER,
-        .sda_io_num = 27,
-        .sda_pullup_en = GPIO_PULLUP_ENABLE,
-        .scl_io_num = 26,
-        .scl_pullup_en = GPIO_PULLUP_ENABLE,
-        .master.clk_speed = 100000,
-};
-
-static const struct tas57xx_cmd_s tas57xx_cmd[] = {
-	{ 0x02, 0x00 },	// DAC_ACTIVE
-	{ 0x02, 0x10 },	// DAC_STANDBY
-	{ 0x02, 0x01 },	// DAC_DOWN
-	{ 0x56, 0x10 },	// DAC_ANALOGUE_OFF
-	{ 0x56, 0x00 },	// DAC_ANALOGUE_ON
-};
-
-static u8_t tas57_addr;
-
-#endif
 
 /****************************************************************************************
  * jack insertion handler
@@ -209,12 +140,33 @@ static void jack_handler(bool inserted) {
 	// jack detection bounces a bit but that seems fine
 	if (jack_mutes_amp) {
 		LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON");
-		if (inserted) dac_cmd(DAC_ANALOGUE_OFF);
-		else dac_cmd(DAC_ANALOGUE_ON);
+		if (inserted) adac->speaker(false);
+		else adac->speaker(true);
 	}
+	
+	// activate headset
+	if (inserted) adac->headset(true);
+	else adac->headset(false);
+	
+	// and chain if any
 	if (jack_handler_chain) (jack_handler_chain)(inserted);
 }
 
+/****************************************************************************************
+ * amp GPIO
+ */
+void set_amp_gpio(int gpio, char *value) {
+	if (!strcasecmp(value, "amp")) {
+		amp_gpio = gpio;
+		
+		gpio_pad_select_gpio(amp_gpio);
+		gpio_set_direction(amp_gpio, GPIO_MODE_OUTPUT);
+		gpio_set_level(amp_gpio, 0);
+		
+		LOG_INFO("setting amplifier GPIO %d", amp_gpio);
+	}	
+}	
+
 /****************************************************************************************
  * Initialize the DAC output
  */
@@ -226,44 +178,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 	jack_mutes_amp = (strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0);
 	free(p);
 	
-#ifdef TAS57xx
-	LOG_INFO("Initializing TAS57xx ");
-				
-	adc1_config_width(ADC_WIDTH_BIT_12);
-    adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_0);
-    			
-	// init volume & mute
-	gpio_pad_select_gpio(VOLUME_GPIO);
-	gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT);
-	gpio_set_level(VOLUME_GPIO, 0);
-	
-	// configure i2c
-	i2c_param_config(I2C_PORT, &i2c_config);
-	i2c_driver_install(I2C_PORT, I2C_MODE_MASTER, false, false, false);
-	
-	// find which TAS we are using
-	tas57_addr = tas57_detect();
-	
-	i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
-	
-	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, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK);
-		i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK);
-
-		LOG_DEBUG("i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value);
-	}
-
-	i2c_master_stop(i2c_cmd);	
-	esp_err_t ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, 500 / portTICK_RATE_MS);
-    i2c_cmd_link_delete(i2c_cmd);
-	
-	if (ret != ESP_OK) {
-		LOG_ERROR("could not intialize TAS57xx %d", ret);
-	}
-#endif	
-	
 #ifdef CONFIG_I2S_BITS_PER_CHANNEL
 	switch (CONFIG_I2S_BITS_PER_CHANNEL) {
 		case 24:
@@ -287,8 +201,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 	bytes_per_frame = 2*2;
 #endif
 
-	if (strcasestr(device, "spdif")) spdif = true;
-
 	output.write_cb = &_i2s_write_frames;
 	obuf = malloc(FRAME_BLOCK * bytes_per_frame);
 	if (!obuf) {
@@ -296,12 +208,20 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 		return;
 	}
 		
-	running=true;
+	running = true;
 
-	i2s_pin_config_t pin_config;
-	
-	if (spdif) {
-		pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_SPDIF_BCK_IO, .ws_io_num = CONFIG_SPDIF_WS_IO, 
+	// common I2S initialization
+	i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
+	i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
+	i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
+	// in case of overflow, do not replay old buffer
+	i2s_config.tx_desc_auto_clear = true;		
+	i2s_config.use_apll = true;
+	i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
+
+	if (strcasestr(device, "spdif")) {
+		spdif = true;	
+		i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_SPDIF_BCK_IO, .ws_io_num = CONFIG_SPDIF_WS_IO, 
 										  .data_out_num = CONFIG_SPDIF_DO_IO, .data_in_num = -1 //Not used
 									};
 		i2s_config.sample_rate = output.current_sample_rate * 2;
@@ -315,49 +235,47 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 		   audio frame. So the real depth is true frames is (LEN * COUNT / 2)
 		*/   
 		dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN / 2;	
+		i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
+		i2s_set_pin(CONFIG_I2S_NUM, &i2s_pin_config);
+		LOG_INFO("SPDIF using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
 	} else {
-		pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO, 
-										.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = -1 //Not used
-									};
+#ifdef TAS57xx
+		gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO);
+		gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT);
+		gpio_set_level(CONFIG_SPDIF_DO_IO, 0);
+		adac = &dac_tas57xx;
+#elif defined(A1S)
+		adac = &dac_a1s;
+#endif	
 		i2s_config.sample_rate = output.current_sample_rate;
 		i2s_config.bits_per_sample = bytes_per_frame * 8 / 2;
 		// Counted in frames (but i2s allocates a buffer <= 4092 bytes)
 		i2s_config.dma_buf_len = DMA_BUF_LEN;	
 		i2s_config.dma_buf_count = DMA_BUF_COUNT;
 		dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN;	
-#ifdef TAS57xx	
-		gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO);
-		gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT);
-		gpio_set_level(CONFIG_SPDIF_DO_IO, 0);
-#endif			
+		
+		// finally let DAC driver initialize I2C and I2S
+		adac->init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config);
 	}
 
-	i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
-	i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
-	i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
-	// in case of overflow, do not replay old buffer
-	i2s_config.tx_desc_auto_clear = true;		
-	i2s_config.use_apll = true;
-	i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
-
 	LOG_INFO("Initializing I2S mode %s with rate: %d, bits per sample: %d, buffer frames: %d, number of buffers: %d ", 
 			spdif ? "S/PDIF" : "normal", 
 			i2s_config.sample_rate, i2s_config.bits_per_sample, i2s_config.dma_buf_len, i2s_config.dma_buf_count);
-
-	i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
-	i2s_set_pin(CONFIG_I2S_NUM, &pin_config);
+	
 	i2s_stop(CONFIG_I2S_NUM);
 	i2s_zero_dma_buffer(CONFIG_I2S_NUM);
 	isI2SStarted=false;
 	
-	dac_cmd(DAC_STANDBY);
+	adac->power(ADAC_STANDBY);
 
 	jack_handler_chain = jack_handler_svc;
 	jack_handler_svc = jack_handler;
 	
-	if (jack_mutes_amp && jack_inserted_svc()) dac_cmd(DAC_ANALOGUE_OFF);
-	else dac_cmd(DAC_ANALOGUE_ON);
+	if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
+	else adac->speaker(true);
 	
+	parse_set_GPIO(set_amp_gpio);
+		
 	esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
 	
     cfg.thread_name= "output_i2s";
@@ -367,11 +285,17 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
     esp_pthread_set_cfg(&cfg);
 	pthread_create(&thread, NULL, output_thread_i2s, NULL);
 	
-	cfg.thread_name= "output_i2s_sts";
-	cfg.prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT - 1;
-    cfg.stack_size = 2048;	
-	esp_pthread_set_cfg(&cfg);
-	pthread_create(&stats_thread, NULL, output_thread_i2s_stats, NULL);
+	// do we want stats
+	p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
+	stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
+	free(p);
+	
+	// memory still used but at least task is not created
+	if (stats) {
+		static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
+		static EXT_RAM_ATTR StackType_t xStack[STAT_STACK_SIZE] __attribute__ ((aligned (4)));
+		stats_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s_stats, "output_i2s_sts", STAT_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
+	}	
 }
 
 
@@ -383,27 +307,20 @@ void output_close_i2s(void) {
 	running = false;
 	UNLOCK;
 	pthread_join(thread, NULL);
-	pthread_join(stats_thread, NULL);
+	if (stats) vTaskDelete(stats_task);
 	
 	i2s_driver_uninstall(CONFIG_I2S_NUM);
 	free(obuf);
 	
-#ifdef TAS57xx	
-	i2c_driver_delete(I2C_PORT);
-#endif	
+	adac->deinit();
 }
 
 /****************************************************************************************
  * change volume
  */
 bool output_volume_i2s(unsigned left, unsigned right) {
-#ifdef TAS57xx	
-	if (!spdif) {
-		LOG_INFO("TAS57xx volume (L:%u R:%u)", left, right);
-		gpio_set_level(VOLUME_GPIO, left || right);
-	}
-#endif	
- return false;	
+	adac->volume(left, right);
+	return false;	
 } 
 
 /****************************************************************************************
@@ -456,7 +373,7 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
 /****************************************************************************************
  * Main output thread
  */
-static void *output_thread_i2s() {
+static void *output_thread_i2s(void *arg) {
 	size_t count = 0, bytes;
 	frames_t iframes = FRAME_BLOCK;
 	uint32_t timer_start = 0;
@@ -480,16 +397,15 @@ static void *output_thread_i2s() {
 		// manage led display & analogue
 		if (state != output.state) {
 			LOG_INFO("Output state is %d", output.state);
-			if (output.state == OUTPUT_OFF) led_blink(LED_GREEN, 100, 2500);
-			else if (output.state == OUTPUT_STOPPED) {
-#ifdef TAS57xx				
-				dac_cmd(DAC_ANALOGUE_OFF);
-#endif				
+			if (output.state == OUTPUT_OFF) {
+				led_blink(LED_GREEN, 100, 2500);
+				if (amp_gpio != -1) gpio_set_level(amp_gpio, 0);
+				LOG_INFO("switching off amp GPIO %d", amp_gpio);
+			} else if (output.state == OUTPUT_STOPPED) {
+				adac->speaker(false);
 				led_blink(LED_GREEN, 200, 1000);
 			} else if (output.state == OUTPUT_RUNNING) {
-#ifdef TAS57xx				
-				if (!jack_mutes_amp || !jack_inserted_svc()) dac_cmd(DAC_ANALOGUE_ON);
-#endif				
+				if (!jack_mutes_amp || !jack_inserted_svc()) adac->speaker(true);
 				led_on(LED_GREEN);
 			}	
 		}
@@ -500,7 +416,7 @@ static void *output_thread_i2s() {
 			if (isI2SStarted) {
 				isI2SStarted = false;
 				i2s_stop(CONFIG_I2S_NUM);
-				if (!spdif) dac_cmd(DAC_STANDBY);
+				adac->power(ADAC_STANDBY);
 				count = 0;
 			}
 			usleep(200000);
@@ -546,7 +462,8 @@ static void *output_thread_i2s() {
 			LOG_INFO("Restarting I2S.");
 			i2s_zero_dma_buffer(CONFIG_I2S_NUM);
 			i2s_start(CONFIG_I2S_NUM);
-			if (!spdif) dac_cmd(DAC_ACTIVE);	
+			adac->power(ADAC_ON);	
+			if (amp_gpio != -1) gpio_set_level(amp_gpio, 1);
 		} 
 		
 		// this does not work well as set_sample_rates resets the fifos (and it's too early)
@@ -591,12 +508,12 @@ static void *output_thread_i2s() {
 /****************************************************************************************
  * Stats output thread
  */
-static void *output_thread_i2s_stats() {
-	while (running) {
-		LOCK;
+static void *output_thread_i2s_stats(void *arg) {
+	while (1) {
+		// no need to lock
 		output_state state = output.state;
-		UNLOCK;
-		if(state>OUTPUT_STOPPED){
+		
+		if(stats && state>OUTPUT_STOPPED){
 			LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d",state,output.current_sample_rate, bytes_per_frame);
 			LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD1);
 			LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD2);
@@ -616,77 +533,11 @@ static void *output_thread_i2s_stats() {
 			LOG_INFO("              ----------+----------+-----------+-----------+");
 			RESET_ALL_MIN_MAX;
 		}
-		usleep(STATS_PERIOD_MS *1000);
+		vTaskDelay( pdMS_TO_TICKS( STATS_PERIOD_MS ) );
 	}
 	return NULL;
 }
 
-/****************************************************************************************
- * DAC specific commands
- */
-void dac_cmd(dac_cmd_e cmd, ...) {
-	va_list args;
-	esp_err_t ret = ESP_OK;
-	
-	va_start(args, cmd);
-#ifdef TAS57xx	
-	i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
-
-	switch(cmd) {
-	case DAC_VOLUME:
-		LOG_ERROR("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);
-	}
-	
-    i2c_cmd_link_delete(i2c_cmd);
-	
-	if (ret != ESP_OK) {
-		LOG_ERROR("could not intialize TAS57xx %d", ret);
-	}
-#endif	
-	va_end(args);
-}
-
-/****************************************************************************************
- * TAS57 detection
- */
-#ifdef TAS57xx
-static int tas57_detect(void) {
-	u8_t data, addr[] = {TAS578x, TAS575x};
-	int ret;
-	
-	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) {
-			LOG_INFO("Detected TAS @0x%x", addr[i]);
-			return addr[i];
-		}	
-	}	
-	
-	return 0;
-}
-#endif
-
 /****************************************************************************************
  * SPDIF support
  */

+ 4 - 12
components/squeezelite/slimproto.c

@@ -45,7 +45,7 @@ static sockfd sock = -1;
 static in_addr_t slimproto_ip = 0;
 static u16_t slimproto_hport = 9000;
 static u16_t slimproto_cport = 9090;
-static u8_t	player_id = PLAYER_ID;
+static u8_t	player_id;
 
 extern struct buffer *streambuf;
 extern struct buffer *outputbuf;
@@ -284,22 +284,14 @@ static void process_strm(u8_t *pkt, int len) {
 	case 't':
 		sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
 		break;
+	case 'f':	
 	case 'q':
 		decode_flush();
 		if (!output.external) output_flush();
 		status.frames_played = 0;
-		stream_disconnect();
-		sendSTAT("STMf", 0);
-		buf_flush(streambuf);
-		break;
-	case 'f':
-		decode_flush();
-		if (!output.external) output_flush();
-		status.frames_played = 0;
-		if (stream_disconnect()) {
-			sendSTAT("STMf", 0);
-		}
+		if (stream_disconnect() && strm->command == 'f') sendSTAT("STMf", 0);
 		buf_flush(streambuf);
+		output.stop_time = gettime_ms();
 		break;
 	case 'p':
 		{

+ 248 - 0
components/squeezelite/tas57xx/dac_57xx.c

@@ -0,0 +1,248 @@
+/* 
+ *  Squeezelite for esp32
+ *
+ *  (c) Sebastien 2019
+ *      Philippe G. 2019, philippe_44@outlook.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+ 
+#include "squeezelite.h" 
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/i2s.h"
+#include "driver/i2c.h"
+#include "driver/gpio.h"
+#include "adac.h"
+
+#define VOLUME_GPIO	14
+#define TAS575x 0x98
+#define TAS578x	0x90
+
+static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config);
+static void deinit(void);
+static void speaker(bool active);
+static void headset(bool active);
+static void volume(unsigned left, unsigned right);
+static void power(adac_power_e mode);
+
+struct adac_s dac_tas57xx = { init, deinit, power, speaker, headset, volume };
+
+struct tas57xx_cmd_s {
+	uint8_t reg;
+	uint8_t value;
+};
+
+static const struct tas57xx_cmd_s tas57xx_init_sequence[] = {
+    { 0x00, 0x00 },		// select page 0
+    { 0x02, 0x10 },		// standby
+    { 0x0d, 0x10 },		// use SCK for PLL
+	{ 0x25, 0x08 },		// ignore SCK halt 
+	{ 0x08, 0x10 },		// Mute control enable (from TAS5780)
+	{ 0x54, 0x02 },		// Mute output control (from TAS5780)
+	{ 0x02, 0x00 },		// restart
+	{ 0xff, 0xff }		// end of table
+};
+
+// matching orders
+typedef enum { TAS57_ACTIVE = 0, TAS57_STANDBY, TAS57_DOWN, TAS57_ANALOGUE_OFF, TAS57_ANALOGUE_ON, TAS57_VOLUME } dac_cmd_e;
+
+static const struct tas57xx_cmd_s tas57xx_cmd[] = {
+	{ 0x02, 0x00 },	// TAS57_ACTIVE
+	{ 0x02, 0x10 },	// TAS57_STANDBY
+	{ 0x02, 0x01 },	// TAS57_DOWN
+	{ 0x56, 0x10 },	// TAS57_ANALOGUE_OFF
+	{ 0x56, 0x00 },	// TAS57_ANALOGUE_ON
+};
+
+static log_level loglevel = lINFO;
+static u8_t tas57_addr;
+static int i2c_port;
+
+static void dac_cmd(dac_cmd_e cmd, ...);
+static int tas57_detect(void);
+
+/****************************************************************************************
+ * init
+ */
+static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config)	{	 
+	LOG_INFO("Initializing TAS57xx ");
+	
+	i2c_port = i2c_port_num;
+	
+	// init volume & mute
+	gpio_pad_select_gpio(VOLUME_GPIO);
+	gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT);
+	gpio_set_level(VOLUME_GPIO, 0);
+	
+	// configure i2c
+	i2c_config_t i2c_config = {
+			.mode = I2C_MODE_MASTER,
+			.sda_io_num = 27,
+			.sda_pullup_en = GPIO_PULLUP_ENABLE,
+			.scl_io_num = 26,
+			.scl_pullup_en = GPIO_PULLUP_ENABLE,
+			.master.clk_speed = 100000,
+		};
+	i2c_param_config(i2c_port, &i2c_config);
+	i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
+	LOG_INFO("DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num);
+	
+	// find which TAS we are using
+	tas57_addr = tas57_detect();
+	
+	i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
+	
+	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, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK);
+		i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK);
+
+		LOG_DEBUG("i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value);
+	}
+
+	i2c_master_stop(i2c_cmd);	
+	esp_err_t ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(i2c_cmd);
+
+	// configure I2S pins & install driver	
+	i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { 	.bck_io_num = 33, .ws_io_num = 25, 
+														.data_out_num = 32, .data_in_num = -1 //Not used 
+								};
+	i2s_driver_install(i2s_num, i2s_config, 0, NULL);
+	i2s_set_pin(i2s_num, &i2s_pin_config);
+	LOG_INFO("DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
+	
+	if (ret != ESP_OK) {
+		LOG_ERROR("could not intialize TAS57xx %d", ret);
+		return false;
+	} else {
+		return true;
+	}	
+}	
+
+/****************************************************************************************
+ * init
+ */
+static void deinit(void)	{	 
+	i2c_driver_delete(i2c_port);
+}
+
+/****************************************************************************************
+ * change volume
+ */
+static void volume(unsigned left, unsigned right) {
+	LOG_INFO("TAS57xx volume (L:%u R:%u)", left, right);
+	gpio_set_level(VOLUME_GPIO, left || right);
+} 
+
+/****************************************************************************************
+ * power
+ */
+static void power(adac_power_e mode) {
+	switch(mode) {
+	case ADAC_STANDBY:
+		dac_cmd(TAS57_STANDBY);
+		break;
+	case ADAC_ON:
+		dac_cmd(TAS57_ACTIVE);
+		break;		
+	case ADAC_OFF:
+		dac_cmd(TAS57_DOWN);
+		break;				
+	default:
+		LOG_WARN("unknown DAC command");
+		break;
+	}
+}
+
+/****************************************************************************************
+ * speaker
+ */
+static void speaker(bool active) {
+	if (active) dac_cmd(TAS57_ANALOGUE_ON);
+	else dac_cmd(TAS57_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);
+	i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
+
+	switch(cmd) {
+	case TAS57_VOLUME:
+		LOG_ERROR("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);
+	}
+	
+    i2c_cmd_link_delete(i2c_cmd);
+	
+	if (ret != ESP_OK) {
+		LOG_ERROR("could not intialize TAS57xx %d", ret);
+	}
+
+	va_end(args);
+}
+
+/****************************************************************************************
+ * TAS57 detection
+ */
+static int tas57_detect(void) {
+	u8_t data, addr[] = {TAS578x, TAS575x};
+	int ret;
+	
+	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) {
+			LOG_INFO("Detected TAS @0x%x", addr[i]);
+			return addr[i];
+		}	
+	}	
+	
+	return 0;
+}
+

+ 5 - 0
components/telnet/CMakeLists.txt

@@ -0,0 +1,5 @@
+idf_component_register(SRCS "telnet.c" 
+						INCLUDE_DIRS . 
+						INCLUDE_DIRS . ../tools/
+                   
+)

+ 15 - 0
components/telnet/component.mk

@@ -0,0 +1,15 @@
+#
+# Component Makefile
+#
+# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
+# this will take the sources in the src/ directory, compile them and link them into
+# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
+# please read the SDK documents if you need to do this.
+#
+
+COMPONENT_SRCDIRS := . 
+COMPONENT_SRCDIRS += ./libtelnet 
+COMPONENT_ADD_INCLUDEDIRS := .
+COMPONENT_ADD_INCLUDEDIRS  += ./libtelnet 
+COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/
+ 

+ 1 - 0
components/telnet/libtelnet

@@ -0,0 +1 @@
+Subproject commit 4218ce9c01f6edb7d219b7a3590d9b3f75e12a74

+ 419 - 0
components/telnet/telnet.c

@@ -0,0 +1,419 @@
+	/**
+ * Test the telnet functions.
+ *
+ * Perform a test using the telnet functions.
+ * This code exports two new global functions:
+ *
+ * void telnet_listenForClients(void (*callback)(uint8_t *buffer, size_t size))
+ * void telnet_sendData(uint8_t *buffer, size_t size)
+ *
+ * For additional details and documentation see:
+ * * Free book on ESP32 - https://leanpub.com/kolban-ESP32
+ *
+ *
+ * Neil Kolban <kolban1@kolban.com>
+ *
+ * ****************************
+ * Additional portions were taken from
+ * https://github.com/PocketSprite/8bkc-sdk/blob/master/8bkc-components/8bkc-hal/vfs-stdout.c
+ *
+ */
+#include <stdlib.h> // Required for libtelnet.h
+#include <esp_log.h>
+#include "libtelnet.h"
+#include "stdbool.h"
+#include <lwip/def.h>
+#include <lwip/sockets.h>
+#include <errno.h>
+#include <string.h>
+#include "sdkconfig.h"
+#include "freertos/ringbuf.h"
+#include "esp_app_trace.h"
+#include "telnet.h"
+#include "esp_vfs.h"
+#include "esp_vfs_dev.h"
+#include "esp_attr.h"
+#include "soc/uart_struct.h"
+#include "driver/uart.h"
+#include "config.h"
+#include "nvs_utilities.h"
+#include "platform_esp32.h"
+
+
+#define TELNET_STACK_SIZE 8048
+
+RingbufHandle_t buf_handle;
+SemaphoreHandle_t xSemaphore = NULL;
+static size_t send_chunk=300;
+static size_t log_buf_size=2000;      //32-bit aligned size
+static bool bIsEnabled=false;
+const static char tag[] = "telnet";
+
+int _log_vprintf(const char *fmt, va_list args);
+void telnet_esp32_listenForClients();
+int telnet_esp32_vprintf(const char *fmt, va_list va);
+
+static void telnetTask(void *data);
+
+
+static int uart_fd=0;
+
+
+// The global tnHandle ... since we are only processing ONE telnet
+// client at a time, this can be a global static.
+static telnet_t *tnHandle;
+static void handleLogBuffer(int partnerSocket, UBaseType_t bytes);
+
+struct telnetUserData {
+	int sockfd;
+};
+
+
+
+static void telnetTask(void *data) {
+	ESP_LOGD(tag, ">> telnetTask");
+	telnet_esp32_listenForClients();
+	ESP_LOGD(tag, "<< telnetTask");
+	vTaskDelete(NULL);
+}
+
+void start_telnet(void * pvParameter){
+	static bool isStarted=false;
+	StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
+	StackType_t *xStack = malloc(TELNET_STACK_SIZE);
+	
+	if(!isStarted && bIsEnabled) {
+		xTaskCreateStatic( (TaskFunction_t) &telnetTask, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer);
+		isStarted=true;
+	}
+}
+
+static ssize_t stdout_write(int fd, const void * data, size_t size) {
+	if (xSemaphoreTake(xSemaphore, (TickType_t) 10) == pdTRUE) {
+		// #1 Write to ringbuffer
+		if (buf_handle == NULL) {
+			printf("%s() ABORT. file handle _log_remote_fp is NULL\n",
+					__FUNCTION__);
+		} else {
+			//Send an item
+			UBaseType_t res = xRingbufferSend(buf_handle, data, size,
+					pdMS_TO_TICKS(100));
+			if (res != pdTRUE) {
+				// flush some entries
+				handleLogBuffer(0, size);
+				res = xRingbufferSend(buf_handle, data, size,
+						pdMS_TO_TICKS(100));
+				if (res != pdTRUE) {
+
+					printf("%s() ABORT. Unable to store log entry in buffer\n",
+							__FUNCTION__);
+				}
+			}
+		}
+		xSemaphoreGive(xSemaphore);
+	} else {
+		// We could not obtain the semaphore and can therefore not access
+		// the shared resource safely.
+	}
+	return write(uart_fd, data, size);
+}
+
+static ssize_t stdout_read(int fd, void* data, size_t size) {
+	return read(fd, data, size);
+}
+
+static int stdout_open(const char * path, int flags, int mode) {
+	return 0;
+}
+
+static int stdout_close(int fd) {
+	return 0;
+}
+
+static int stdout_fstat(int fd, struct stat * st) {
+	st->st_mode = S_IFCHR;
+	return 0;
+}
+
+
+void kchal_stdout_register() {
+	const esp_vfs_t vfs = {
+		.flags = ESP_VFS_FLAG_DEFAULT,
+		.write = &stdout_write,
+		.open = &stdout_open,
+		.fstat = &stdout_fstat,
+		.close = &stdout_close,
+		.read = &stdout_read,
+	};
+	uart_fd=open("/dev/uart/0", O_RDWR);
+	ESP_ERROR_CHECK(esp_vfs_register("/dev/pkspstdout", &vfs, NULL));
+	freopen("/dev/pkspstdout", "w", stdout);
+	freopen("/dev/pkspstdout", "w", stderr);
+	//printf("8bkc_hal_stdout_register: Custom stdout/stderr handler installed.\n");
+}
+/*********************************
+ * Telnet Support
+ */
+
+
+void init_telnet(){
+
+	char *val= get_nvs_value_alloc(NVS_TYPE_STR, "telnet_enable");
+
+	if (!val || !strcasestr("YX",val) ) {
+		ESP_LOGI(tag,"Telnet support disabled");
+		if(val) free(val);
+		return;
+	}
+	val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block");
+	if(val){
+		send_chunk=atol(val);
+		free(val);
+		send_chunk=send_chunk>0?send_chunk:500;
+	}
+	val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_buffer");
+	if(val){
+		log_buf_size=atol(val);
+		free(val);
+		log_buf_size=log_buf_size>0?log_buf_size:4000;
+	}
+	bIsEnabled=true;
+
+	// Create the semaphore to guard a shared resource.
+	vSemaphoreCreateBinary( xSemaphore );
+
+	// First thing we need to do here is to redirect the output to our telnet handler
+	//Allocate ring buffer data structure and storage area into external RAM
+	StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)heap_caps_malloc(sizeof(StaticRingbuffer_t), MALLOC_CAP_SPIRAM);
+	uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM);
+
+	//Create a ring buffer with manually allocated memory
+	buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct);
+
+	if (buf_handle == NULL) {
+		ESP_LOGE(tag,"Failed to create ring buffer for telnet!");
+		return;
+	}
+
+	ESP_LOGI(tag, "***Redirecting log output to telnet");
+	//esp_log_set_vprintf(&_log_vprintf);
+	kchal_stdout_register();
+}
+/**
+ * Convert a telnet event type to its string representation.
+ */
+static char *eventToString(telnet_event_type_t type) {
+	switch(type) {
+	case TELNET_EV_COMPRESS:
+		return "TELNET_EV_COMPRESS";
+	case TELNET_EV_DATA:
+		return "TELNET_EV_DATA";
+	case TELNET_EV_DO:
+		return "TELNET_EV_DO";
+	case TELNET_EV_DONT:
+		return "TELNET_EV_DONT";
+	case TELNET_EV_ENVIRON:
+		return "TELNET_EV_ENVIRON";
+	case TELNET_EV_ERROR:
+		return "TELNET_EV_ERROR";
+	case TELNET_EV_IAC:
+		return "TELNET_EV_IAC";
+	case TELNET_EV_MSSP:
+		return "TELNET_EV_MSSP";
+	case TELNET_EV_SEND:
+		return "TELNET_EV_SEND";
+	case TELNET_EV_SUBNEGOTIATION:
+		return "TELNET_EV_SUBNEGOTIATION";
+	case TELNET_EV_TTYPE:
+		return "TELNET_EV_TTYPE";
+	case TELNET_EV_WARNING:
+		return "TELNET_EV_WARNING";
+	case TELNET_EV_WILL:
+		return "TELNET_EV_WILL";
+	case TELNET_EV_WONT:
+		return "TELNET_EV_WONT";
+	case TELNET_EV_ZMP:
+		return "TELNET_EV_ZMP";
+	}
+	return "Unknown type";
+} // eventToString
+
+
+/**
+ * Send data to the telnet partner.
+ */
+void telnet_esp32_sendData(uint8_t *buffer, size_t size) {
+	if (tnHandle != NULL) {
+		telnet_send(tnHandle, (char *)buffer, size);
+	}
+} // telnet_esp32_sendData
+
+
+/**
+ * Send a vprintf formatted output to the telnet partner.
+ */
+int telnet_esp32_vprintf(const char *fmt, va_list va) {
+	if (tnHandle == NULL) {
+		return 0;
+	}
+	return telnet_vprintf(tnHandle, fmt, va);
+} // telnet_esp32_vprintf
+
+/**
+ * Telnet handler.
+ */
+void processReceivedData(const char * buffer, size_t size){
+	//ESP_LOGD(tag, "received data, len=%d", event->data.size);
+
+	char * command = malloc(size+1);
+	memcpy(command,buffer,size);
+	command[size]='\0';
+	// todo: implement conditional remote echo
+	//telnet_esp32_sendData((uint8_t *)command, size);
+	if(command[0]!='\r' && command[0]!='\n'){
+		// some telnet clients will send data and crlf in two separate buffers
+		printf(command);
+		printf("\r\n");
+		run_command((char *)command);
+
+	}
+	free(command);
+
+}
+static void telnetHandler(
+		telnet_t *thisTelnet,
+		telnet_event_t *event,
+		void *userData) {
+	int rc;
+	struct telnetUserData *telnetUserData = (struct telnetUserData *)userData;
+	switch(event->type) {
+	case TELNET_EV_SEND:
+		rc = send(telnetUserData->sockfd, event->data.buffer, event->data.size, 0);
+		if (rc < 0) {
+			//printf("ERROR: (telnet) send: %d (%s)", errno, strerror(errno));
+		}
+		break;
+
+	case TELNET_EV_DATA:
+		 processReceivedData(event->data.buffer, event->data.size);
+		break;
+
+	default:
+		printf("telnet event: %s\n", eventToString(event->type));
+		break;
+	} // End of switch event type
+} // myTelnetHandler
+
+
+static void handleLogBuffer(int partnerSocket, UBaseType_t count){
+    //Receive an item from no-split ring buffer
+	size_t item_size;
+    UBaseType_t uxItemsWaiting;
+    UBaseType_t uxBytesToSend=count;
+
+	vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, &uxItemsWaiting);
+	if( partnerSocket ==0 && (uxItemsWaiting*100 / log_buf_size) <75){
+		// We still have some room in the ringbuffer and there's no telnet
+		// connection yet, so bail out for now.
+		//printf("%s() Log buffer used %u of %u bytes used\n", __FUNCTION__, uxItemsWaiting, log_buf_size);
+		return;
+	}
+
+	while(uxBytesToSend>0){
+		char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(50), uxBytesToSend);
+		//Check received data
+
+		if (item != NULL) {
+			uxBytesToSend-=item_size;
+			if(partnerSocket!=0)
+				telnet_esp32_sendData((uint8_t *)item, item_size);
+			else{
+				//printf("%s() flushing %u bytes from log buffer\n", __FUNCTION__, item_size);
+			}
+			//Return Item
+			vRingbufferReturnItem(buf_handle, (void *)item);
+		}
+		else{
+			break;
+		}
+	}
+}
+
+static void doTelnet(int partnerSocket) {
+	//ESP_LOGD(tag, ">> doTelnet");
+  static const telnet_telopt_t my_telopts[] = {
+    { TELNET_TELOPT_ECHO,      TELNET_WILL, TELNET_DONT },
+    { TELNET_TELOPT_TTYPE,     TELNET_WILL, TELNET_DONT },
+    { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO   },
+    { TELNET_TELOPT_ZMP,       TELNET_WONT, TELNET_DO   },
+    { TELNET_TELOPT_MSSP,      TELNET_WONT, TELNET_DO   },
+    { TELNET_TELOPT_BINARY,    TELNET_WILL, TELNET_DO   },
+    { TELNET_TELOPT_NAWS,      TELNET_WILL, TELNET_DONT },
+    { -1, 0, 0 }
+  };
+  struct telnetUserData *pTelnetUserData = (struct telnetUserData *)malloc(sizeof(struct telnetUserData));
+  pTelnetUserData->sockfd = partnerSocket;
+
+
+  tnHandle = telnet_init(my_telopts, telnetHandler, 0, pTelnetUserData);
+
+  uint8_t buffer[1024];
+  while(1) {
+  	//ESP_LOGD(tag, "waiting for data");
+  	ssize_t len = recv(partnerSocket, (char *)buffer, sizeof(buffer), MSG_DONTWAIT);
+  	if (len >0 ) {
+		//ESP_LOGD(tag, "received %d bytes", len);
+		telnet_recv(tnHandle, (char *)buffer, len);
+  	}
+  	else if (errno != EAGAIN && errno !=EWOULDBLOCK ){
+  	  telnet_free(tnHandle);
+  	  tnHandle = NULL;
+  	  free(pTelnetUserData);
+  	  return;
+  	}
+  	handleLogBuffer(partnerSocket,  send_chunk);
+
+	taskYIELD();
+  }
+
+} // doTelnet
+
+/**
+ * Listen for telnet clients and handle them.
+ */
+void telnet_esp32_listenForClients() {
+	//ESP_LOGD(tag, ">> telnet_listenForClients");
+	int serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+	struct sockaddr_in serverAddr;
+	serverAddr.sin_family = AF_INET;
+	serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+	serverAddr.sin_port = htons(23);
+
+	int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
+	if (rc < 0) {
+		ESP_LOGE(tag, "bind: %d (%s)", errno, strerror(errno));
+		return;
+	}
+
+	rc = listen(serverSocket, 5);
+	if (rc < 0) {
+		ESP_LOGE(tag, "listen: %d (%s)", errno, strerror(errno));
+		return;
+	}
+
+	while(1) {
+		socklen_t len = sizeof(serverAddr);
+		rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len);
+		if (rc < 0 ){
+			ESP_LOGE(tag, "accept: %d (%s)", errno, strerror(errno));
+			return;
+		}
+		else {
+			int partnerSocket = rc;
+			ESP_LOGD(tag, "We have a new client connection!");
+			doTelnet(partnerSocket);
+			ESP_LOGD(tag, "Telnet connection terminated");
+		}
+	}
+} // listenForNewClient

+ 4 - 0
components/telnet/telnet.h

@@ -0,0 +1,4 @@
+
+void init_telnet();
+void start_telnet(void * pvParameter);
+void telnet_esp32_sendData(uint8_t *buffer, size_t size);

+ 1 - 1
components/wifi-manager/Kconfig.projbuild

@@ -61,7 +61,7 @@ config DEFAULT_AP_BEACON_INTERVAL
 	100ms is the recommended default.
 config DEFAULT_COMMAND_LINE
     string "Default command line to execute"
-    default "squeezelite -o I2S -b 500:2000 -d all=info"
+    default "squeezelite -o I2S -b 500:2000 -d all=info -C 30"
     help
 	This is the command to run when starting the device
 endmenu

+ 654 - 0
components/wifi-manager/http_server.c

@@ -0,0 +1,654 @@
+/*
+Copyright (c) 2017-2019 Tony Pottier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+@file http_server.c
+@author Tony Pottier
+@brief Defines all functions necessary for the HTTP server to run.
+
+Contains the freeRTOS task for the HTTP listener and all necessary support
+function to process requests, decode URLs, serve files, etc. etc.
+
+@note http_server task cannot run without the wifi_manager task!
+@see https://idyl.io
+@see https://github.com/tonyp7/esp32-wifi-manager
+*/
+
+#include "http_server.h"
+#include "cmd_system.h"
+#include <inttypes.h>
+#include "squeezelite-ota.h"
+#include "nvs_utilities.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "cJSON.h"
+#include "esp_system.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "config.h"
+
+#define HTTP_STACK_SIZE	(5*1024)
+
+/* @brief tag used for ESP serial console messages */
+static const char TAG[] = "http_server";
+/* @brief task handle for the http server */
+static TaskHandle_t task_http_server = NULL;
+static StaticTask_t task_http_buffer;
+#if RECOVERY_APPLICATION
+static StackType_t task_http_stack[HTTP_STACK_SIZE];
+#else
+static StackType_t EXT_RAM_ATTR task_http_stack[HTTP_STACK_SIZE];
+#endif
+SemaphoreHandle_t http_server_config_mutex = NULL;
+
+/**
+ * @brief embedded binary data.
+ * @see file "component.mk"
+ * @see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#embedding-binary-data
+ */
+extern const uint8_t style_css_start[] asm("_binary_style_css_start");
+extern const uint8_t style_css_end[]   asm("_binary_style_css_end");
+extern const uint8_t jquery_gz_start[] asm("_binary_jquery_min_js_gz_start");
+extern const uint8_t jquery_gz_end[] asm("_binary_jquery_min_js_gz_end");
+extern const uint8_t popper_gz_start[] asm("_binary_popper_min_js_gz_start");
+extern const uint8_t popper_gz_end[] asm("_binary_popper_min_js_gz_end");
+extern const uint8_t bootstrap_js_gz_start[] asm("_binary_bootstrap_min_js_gz_start");
+extern const uint8_t bootstrap_js_gz_end[] asm("_binary_bootstrap_min_js_gz_end");
+extern const uint8_t bootstrap_css_gz_start[] asm("_binary_bootstrap_min_css_gz_start");
+extern const uint8_t bootstrap_css_gz_end[] asm("_binary_bootstrap_min_css_gz_end");
+extern const uint8_t code_js_start[] asm("_binary_code_js_start");
+extern const uint8_t code_js_end[] asm("_binary_code_js_end");
+extern const uint8_t index_html_start[] asm("_binary_index_html_start");
+extern const uint8_t index_html_end[] asm("_binary_index_html_end");
+
+
+/* const http headers stored in ROM */
+const static char http_hdr_template[] = "HTTP/1.1 200 OK\nContent-type: %s\nAccept-Ranges: bytes\nContent-Length: %d\nContent-Encoding: %s\nAccess-Control-Allow-Origin: *\n\n";
+const static char http_html_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/html\nAccess-Control-Allow-Origin: *\nAccept-Encoding: identity\n\n";
+const static char http_css_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/css\nCache-Control: public, max-age=31536000\nAccess-Control-Allow-Origin: *\n\n";
+const static char http_js_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\nAccess-Control-Allow-Origin: *\n\n";
+const static char http_400_hdr[] = "HTTP/1.1 400 Bad Request\nContent-Length: 0\n\n";
+const static char http_404_hdr[] = "HTTP/1.1 404 Not Found\nContent-Length: 0\n\n";
+const static char http_503_hdr[] = "HTTP/1.1 503 Service Unavailable\nContent-Length: 0\n\n";
+const static char http_ok_json_no_cache_hdr[] = "HTTP/1.1 200 OK\nContent-type: application/json\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nAccess-Control-Allow-Origin: *\nAccept-Encoding: identity\n\n";
+const static char http_redirect_hdr_start[] = "HTTP/1.1 302 Found\nLocation: http://";
+const static char http_redirect_hdr_end[] = "/\n\n";
+
+
+void http_server_start() {
+	ESP_LOGD(TAG,  "http_server_start ");
+	if(task_http_server == NULL) {
+		task_http_server = xTaskCreateStatic( (TaskFunction_t) &http_server, "http_server", HTTP_STACK_SIZE, NULL, 
+										 WIFI_MANAGER_TASK_PRIORITY, task_http_stack, &task_http_buffer);
+	}
+}
+void http_server(void *pvParameters) {
+	http_server_config_mutex = xSemaphoreCreateMutex();
+	struct netconn *conn, *newconn;
+	err_t err;
+	conn = netconn_new(NETCONN_TCP);
+	netconn_bind(conn, IP_ADDR_ANY, 80);
+	netconn_listen(conn);
+	ESP_LOGI(TAG,   "HTTP Server listening on 80/tcp");
+	do {
+		err = netconn_accept(conn, &newconn);
+		if(err == ERR_OK) {
+			http_server_netconn_serve(newconn);
+			netconn_delete(newconn);
+		}
+		else
+		{
+			ESP_LOGE(TAG,  "Error accepting new connection. Terminating HTTP server");
+		}
+		taskYIELD();  /* allows the freeRTOS scheduler to take over if needed. */
+	} while(err == ERR_OK);
+
+	netconn_close(conn);
+	netconn_delete(conn);
+	vSemaphoreDelete(http_server_config_mutex);
+	http_server_config_mutex = NULL;
+	vTaskDelete( NULL );
+}
+
+
+char* http_server_get_header(char *request, char *header_name, int *len) {
+	*len = 0;
+	char *ret = NULL;
+	char *ptr = NULL;
+
+	ptr = strstr(request, header_name);
+	if(ptr) {
+		ret = ptr + strlen(header_name);
+		ptr = ret;
+		while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r') {
+			(*len)++;
+			ptr++;
+		}
+		return ret;
+	}
+	return NULL;
+}
+char* http_server_search_header(char *request, char *header_name, int *len, char ** parm_name, char ** next_position, char * bufEnd) {
+	*len = 0;
+	char *ret = NULL;
+	char *ptr = NULL;
+	int currentLength=0;
+
+	ESP_LOGV(TAG,   "searching for header name: [%s]", header_name);
+	ptr = strstr(request, header_name);
+
+
+	if(ptr!=NULL && ptr<bufEnd) {
+		ret = ptr + strlen(header_name);
+		ptr = ret;
+		currentLength=(int)(ptr-request);
+		ESP_LOGV(TAG,   "found string at %d", currentLength);
+
+		while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r' && *ptr != ':' && ptr<bufEnd) {
+			ptr++;
+		}
+		if(*ptr==':') {
+			currentLength=(int)(ptr-ret);
+			ESP_LOGV(TAG,   "Found parameter name end, length : %d", currentLength);
+			// save the parameter name: the string between header name and ":"
+			*parm_name=malloc(currentLength+1);
+			if(*parm_name==NULL) {
+				ESP_LOGE(TAG,   "Unable to allocate memory for new header name");
+				return NULL;
+			}
+			memset(*parm_name, 0x00,currentLength+1);
+			strncpy(*parm_name,ret,currentLength);
+			ESP_LOGV(TAG,   "Found parameter name : %s ", *parm_name);
+			ptr++;
+			while (*ptr == ' ' && ptr<bufEnd) {
+				ptr++;
+			}
+
+		}
+		ret=ptr;
+		while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r'&& ptr<bufEnd) {
+			(*len)++;
+			ptr++;
+		}
+		// Terminate value inside its actual buffer so we can treat it as individual string
+		*ptr='\0';
+		currentLength=(int)(ptr-ret);
+		ESP_LOGV(TAG,   "Found parameter value end, length : %d, 	value: %s", currentLength,ret );
+
+		*next_position=++ptr;
+		return ret;
+	}
+	ESP_LOGD(TAG,   "No more match for : %s", header_name);
+	return NULL;
+}
+void http_server_send_resource_file(struct netconn *conn,const uint8_t * start, const uint8_t * end, char * content_type,char * encoding) {
+	uint16_t len=end - start;
+	size_t  buff_length= sizeof(http_hdr_template)+strlen(content_type)+strlen(encoding);
+	char * http_hdr=malloc(buff_length);
+	if( http_hdr == NULL) {
+		ESP_LOGE(TAG,  "Cound not allocate %d bytes for headers.",buff_length);
+		netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+	}
+	else
+	{
+		memset(http_hdr,0x00,buff_length);
+		snprintf(http_hdr, buff_length-1,http_hdr_template,content_type,len,encoding);
+		netconn_write(conn, http_hdr, strlen(http_hdr), NETCONN_NOCOPY);
+		ESP_LOGD(TAG,  "sending response : %s",http_hdr);
+		netconn_write(conn, start, end - start, NETCONN_NOCOPY);
+		free(http_hdr);
+	}
+}
+
+err_t http_server_send_config_json(struct netconn *conn) {
+	char * json = config_alloc_get_json(false);
+	if(json!=NULL){
+		ESP_LOGD(TAG,  "config json : %s",json );
+		netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
+		netconn_write(conn, json, strlen(json), NETCONN_NOCOPY);
+		free(json);
+	}
+	else{
+		ESP_LOGD(TAG,  "Error retrieving config json string. ");
+		netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+	}
+
+	return ESP_OK;
+}
+
+void http_server_process_config(struct netconn *conn, 	char *inbuf) {
+
+	// Here, we are passed a buffer which contains the http request
+
+//		netbuf_data(inbuf, (void**)&buf, &buflen);
+//		err = netconn_recv(conn, &inbuf);
+//		if(err == ERR_OK) {
+//
+//		/* extract the first line of the request */
+//		char *save_ptr = buf;
+//		char *line = strtok_r(save_ptr, new_line, &save_ptr);
+//		ESP_LOGD(TAG,  "Processing line %s",line);
+	ESP_LOGD(TAG,  "Processing request buffer: \n%s",inbuf);
+	char *last = NULL;
+	char *ptr = NULL;
+	last = ptr = inbuf;
+	bool bHeaders= true;
+	while(ptr!=NULL && *ptr != '\0') {
+		// Move to the end of the line, or to the end of the buffer
+		if(bHeaders) {
+			while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r') {
+				ptr++;
+			}
+			// terminate the header string
+			if( *(ptr) == '\0' ) {
+				ESP_LOGD(TAG,   "End of buffer found");
+				return;
+			}
+			*ptr = '\0';
+			if( *(ptr+1) == '\n' ) {
+				*(ptr+1)='\0';
+				ptr+=2;
+			}
+			if(ptr==last) {
+				ESP_LOGD(TAG,  "Processing body. ");
+				break;
+			}
+			if(strlen(last)>0) {
+				ESP_LOGD(TAG,  "Found Header Line %s ", last);
+				//Content-Type: application/json
+			}
+			else {
+				ESP_LOGD(TAG,  "Found end of headers");
+				bHeaders = false;
+			}
+			last=ptr;
+		}
+		else {
+			//ESP_LOGD(TAG,  "Body content: %s", last);
+			//cJSON * json = cJSON_Parse(last);
+			//cJSON_Delete(json);
+			//todo:  implement body json parsing
+			// right now, body is coming as compressed, so we need some type of decompression to happen.
+			return;
+		}
+	}
+	return ;
+
+}
+
+void dump_net_buffer(void * buf, u16_t buflen) {
+	char * curbuf = malloc(buflen+1);
+	ESP_LOGV(TAG,  "netconn buffer, length=%u",buflen);
+	if(curbuf==NULL) {
+		ESP_LOGE(TAG,  "Unable to show netconn buffer.  Malloc failed");
+	}
+	memset(curbuf,0x0, buflen+1);
+	memcpy(curbuf,buf,buflen);
+	ESP_LOGV(TAG,  "netconn buffer content:\n%s",curbuf);
+	free(curbuf);
+}
+
+void http_server_netconn_serve(struct netconn *conn) {
+
+	struct netbuf *inbuf;
+	char *buf = NULL;
+	u16_t buflen = 0;
+	err_t err;
+	ip_addr_t remote_add;
+	u16_t port;
+	ESP_LOGV(TAG,  "Serving page.  Getting device AP address.");
+	const char new_line[2] = "\n";
+	char * ap_ip_address= config_alloc_get_default(NVS_TYPE_STR, "ap_ip_address", DEFAULT_AP_IP, 0);
+	if(ap_ip_address==NULL){
+		ESP_LOGE(TAG,  "Unable to retrieve default AP IP Address");
+		netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+		netconn_close(conn);
+		return;
+	}
+	ESP_LOGV(TAG,  "Getting remote device IP address.");
+	netconn_getaddr(conn,	&remote_add,	&port,	0);
+	char * remote_address = strdup(ip4addr_ntoa(ip_2_ip4(&remote_add)));
+	ESP_LOGD(TAG,  "Local Access Point IP address is: %s. Remote device IP address is %s. Receiving request buffer", ap_ip_address, remote_address);
+
+	u16_t bufsize = 0;
+	netconn_set_recvtimeout(conn, 50);
+	while (netconn_recv(conn, &inbuf) == ERR_OK) {
+		do {
+			u8_t *rcvbuf;
+			u16_t rcvlen;
+			netbuf_data(inbuf, (void**)&rcvbuf, &rcvlen);
+			dump_net_buffer(rcvbuf, rcvlen);
+			if (buflen + rcvlen > bufsize) {
+				bufsize += 2048;
+				buf = realloc(buf, bufsize);
+			}
+			memcpy(buf + buflen, rcvbuf, rcvlen);
+			buflen += rcvlen;
+			ESP_LOGI(TAG, "received netbuf of %hu", rcvlen);
+		} while (netbuf_next(inbuf) != -1);
+		netbuf_delete(inbuf);
+	}
+
+	if(buflen) {
+		ESP_LOGV(TAG,  "Getting data buffer.");
+		int lenH = 0;
+		/* extract the first line of the request */
+		char *save_ptr = buf;
+		char *line = strtok_r(save_ptr, new_line, &save_ptr);
+		char *temphost = http_server_get_header(save_ptr, "Host: ", &lenH);
+		char * host = malloc(lenH+1);
+		memset(host,0x00,lenH+1);
+		if(lenH>0){
+			strlcpy(host,temphost,lenH+1);
+		}
+		ESP_LOGD(TAG,  "http_server_netconn_serve Host: [%s], host: [%s], Processing line [%s]",remote_address,host,line);
+
+		if(line) {
+
+			/* captive portal functionality: redirect to access point IP for HOST that are not the access point IP OR the STA IP */
+			const char * host_name=NULL;
+			if((err=tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &host_name )) !=ESP_OK) {
+				ESP_LOGE(TAG,  "Unable to get host name. Error: %s",esp_err_to_name(err));
+			}
+			else {
+				ESP_LOGI(TAG,"System host name %s, http requested host: %s.",host_name, host);
+			}
+
+			/* determine if Host is from the STA IP address */
+			wifi_manager_lock_sta_ip_string(portMAX_DELAY);
+			bool access_from_sta_ip = lenH > 0?strcasestr(host, wifi_manager_get_sta_ip_string()):false;
+			wifi_manager_unlock_sta_ip_string();
+			bool access_from_host_name = (host_name!=NULL) && strcasestr(host,host_name);
+
+			if(lenH > 0 && !strcasestr(host, ap_ip_address) && !(access_from_sta_ip || access_from_host_name)) {
+				ESP_LOGI(TAG,  "Redirecting host [%s] to AP IP Address : %s",remote_address, ap_ip_address);
+				netconn_write(conn, http_redirect_hdr_start, sizeof(http_redirect_hdr_start) - 1, NETCONN_NOCOPY);
+				netconn_write(conn, ap_ip_address, strlen(ap_ip_address), NETCONN_NOCOPY);
+				netconn_write(conn, http_redirect_hdr_end, sizeof(http_redirect_hdr_end) - 1, NETCONN_NOCOPY);
+			}
+			else {
+                //static stuff
+				/* default page */
+				if(strstr(line, "GET / ")) {
+					netconn_write(conn, http_html_hdr, sizeof(http_html_hdr) - 1, NETCONN_NOCOPY);
+					netconn_write(conn, index_html_start, index_html_end- index_html_start, NETCONN_NOCOPY);
+				}
+				else if(strstr(line, "GET /code.js ")) {
+					netconn_write(conn, http_js_hdr, sizeof(http_js_hdr) - 1, NETCONN_NOCOPY);
+					netconn_write(conn, code_js_start, code_js_end - code_js_start, NETCONN_NOCOPY);
+				}
+				else if(strstr(line, "GET /style.css ")) {
+					netconn_write(conn, http_css_hdr, sizeof(http_css_hdr) - 1, NETCONN_NOCOPY);
+					netconn_write(conn, style_css_start, style_css_end - style_css_start, NETCONN_NOCOPY);
+				}
+				else if(strstr(line, "GET /jquery.js ")) {
+					http_server_send_resource_file(conn,jquery_gz_start, jquery_gz_end, "text/javascript", "gzip" );
+				}
+				else if(strstr(line, "GET /popper.js ")) {
+					http_server_send_resource_file(conn,popper_gz_start, popper_gz_end, "text/javascript", "gzip" );
+				}
+				else if(strstr(line, "GET /bootstrap.js ")) {
+					http_server_send_resource_file(conn,bootstrap_js_gz_start, bootstrap_js_gz_end, "text/javascript", "gzip" );
+				}
+				else if(strstr(line, "GET /bootstrap.css ")) {
+					http_server_send_resource_file(conn,bootstrap_css_gz_start, bootstrap_css_gz_end, "text/css", "gzip" );
+				}
+
+                //dynamic stuff
+				else if(strstr(line, "GET /scan.json ")) {
+					ESP_LOGI(TAG,  "Starting wifi scan");
+					wifi_manager_scan_async();
+				}
+				else if(strstr(line, "GET /ap.json ")) {
+					/* if we can get the mutex, write the last version of the AP list */
+					ESP_LOGI(TAG,  "Processing ap.json request");
+					if(wifi_manager_lock_json_buffer(( TickType_t ) 10)) {
+						netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
+						char *buff = wifi_manager_alloc_get_ap_list_json();
+						wifi_manager_unlock_json_buffer();
+						if(buff!=NULL){
+							netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+							free(buff);
+						}
+						else {
+							ESP_LOGD(TAG,  "Error retrieving ap list json string. ");
+							netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+						}
+					}
+					else {
+						netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+						ESP_LOGE(TAG,   "http_server_netconn_serve: GET /ap.json failed to obtain mutex");
+					}
+					/* request a wifi scan */
+					ESP_LOGI(TAG,  "Starting wifi scan");
+					wifi_manager_scan_async();
+					ESP_LOGI(TAG,  "Done serving ap.json");
+				}
+				else if(strstr(line, "GET /config.json ")) {
+					ESP_LOGI(TAG,  "Serving config.json");
+					ESP_LOGI(TAG,   "About to get config from flash");
+					http_server_send_config_json(conn);
+					ESP_LOGD(TAG,  "Done serving config.json");
+				}
+				else if(strstr(line, "POST /config.json ")) {
+					ESP_LOGI(TAG,  "Serving POST config.json");
+					int lenA=0;
+					char * last_parm=save_ptr;
+					char * next_parm=save_ptr;
+					char  * last_parm_name=NULL;
+					bool bErrorFound=false;
+					bool bOTA=false;
+					char * otaURL=NULL;
+					// todo:  implement json body parsing
+					//http_server_process_config(conn,save_ptr);
+
+					while(last_parm!=NULL) {
+						// Search will return
+						ESP_LOGD(TAG,   "Getting parameters from X-Custom headers");
+						last_parm = http_server_search_header(next_parm, "X-Custom-", &lenA, &last_parm_name,&next_parm,buf+buflen);
+						if(last_parm!=NULL && last_parm_name!=NULL) {
+							ESP_LOGI(TAG,   "http_server_netconn_serve: POST config.json, config %s=%s", last_parm_name, last_parm);
+							if(strcmp(last_parm_name, "fwurl")==0) {
+								// we're getting a request to do an OTA from that URL
+								ESP_LOGW(TAG,   "Found OTA request!");
+								otaURL=strdup(last_parm);
+								bOTA=true;
+							}
+							else {
+								ESP_LOGV(TAG,   "http_server_netconn_serve: POST config.json Storing parameter");
+								if(config_set_value(NVS_TYPE_STR, last_parm_name , last_parm) != ESP_OK){
+									ESP_LOGE(TAG,  "Unable to save nvs value.");
+								}
+							}
+						}
+						if(last_parm_name!=NULL) {
+							free(last_parm_name);
+							last_parm_name=NULL;
+						}
+					}
+					if(bErrorFound) {
+						netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY); //400 invalid request
+					}
+					else {
+						netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
+						if(bOTA) {
+
+#if RECOVERY_APPLICATION
+							ESP_LOGW(TAG,   "Starting process OTA for url %s",otaURL);
+#else
+							ESP_LOGW(TAG,   "Restarting system to process OTA for url %s",otaURL);
+#endif
+							wifi_manager_reboot_ota(otaURL);
+							free(otaURL);
+						}
+					}
+					ESP_LOGI(TAG,  "Done Serving POST config.json");
+				} 
+				else if(strstr(line, "POST /connect.json ")) {
+					ESP_LOGI(TAG,   "http_server_netconn_serve: POST /connect.json");
+					bool found = false;
+					int lenS = 0, lenP = 0, lenN = 0;
+					char *ssid = NULL, *password = NULL;
+					ssid = http_server_get_header(save_ptr, "X-Custom-ssid: ", &lenS);
+					password = http_server_get_header(save_ptr, "X-Custom-pwd: ", &lenP);
+					char * new_host_name_b = http_server_get_header(save_ptr, "X-Custom-host_name: ", &lenN);
+					if(lenN > 0){
+						lenN++;
+						char * new_host_name = malloc(lenN);
+						strlcpy(new_host_name, new_host_name_b, lenN);
+						if(config_set_value(NVS_TYPE_STR, "host_name", new_host_name) != ESP_OK){
+							ESP_LOGE(TAG,  "Unable to save host name configuration");
+						}
+						free(new_host_name);
+					}
+
+					if(ssid && lenS <= MAX_SSID_SIZE && password && lenP <= MAX_PASSWORD_SIZE) {
+						wifi_config_t* config = wifi_manager_get_wifi_sta_config();
+						memset(config, 0x00, sizeof(wifi_config_t));
+						memcpy(config->sta.ssid, ssid, lenS);
+						memcpy(config->sta.password, password, lenP);
+						ESP_LOGD(TAG,   "http_server_netconn_serve: wifi_manager_connect_async() call, with ssid: %s, password: %s", config->sta.ssid, config->sta.password);
+						wifi_manager_connect_async();
+						netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
+						found = true;
+					}
+					else{
+						ESP_LOGE(TAG,  "SSID or Password invalid");
+					}
+
+
+					if(!found) {
+						/* bad request the authentification header is not complete/not the correct format */
+						netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
+						ESP_LOGE(TAG,   "bad request the authentification header is not complete/not the correct format");
+					}
+
+					ESP_LOGI(TAG,   "http_server_netconn_serve: done serving connect.json");
+				}
+				else if(strstr(line, "DELETE /connect.json ")) {
+					ESP_LOGI(TAG,   "http_server_netconn_serve: DELETE /connect.json");
+					/* request a disconnection from wifi and forget about it */
+					wifi_manager_disconnect_async();
+					netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
+					ESP_LOGI(TAG,   "http_server_netconn_serve: done serving DELETE /connect.json");
+				}
+				else if(strstr(line, "POST /reboot_ota.json ")) {
+					ESP_LOGI(TAG,   "http_server_netconn_serve: POST reboot_ota.json");
+					netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
+					wifi_manager_reboot(OTA);
+					ESP_LOGI(TAG,   "http_server_netconn_serve: done serving POST reboot_ota.json");
+				}
+				else if(strstr(line, "POST /reboot.json ")) {
+					ESP_LOGI(TAG,   "http_server_netconn_serve: POST reboot.json");
+					netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
+					wifi_manager_reboot(RESTART);
+					ESP_LOGI(TAG,   "http_server_netconn_serve: done serving POST reboot.json");
+				}
+				else if(strstr(line, "POST /recovery.json ")) {
+					ESP_LOGI(TAG,   "http_server_netconn_serve: POST recovery.json");
+					netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
+					wifi_manager_reboot(RECOVERY);
+					ESP_LOGI(TAG,   "http_server_netconn_serve: done serving POST recovery.json");
+				}
+				else if(strstr(line, "GET /status.json ")) {
+					ESP_LOGI(TAG,  "Serving status.json");
+					if(wifi_manager_lock_json_buffer(( TickType_t ) 10)) {
+						char *buff = wifi_manager_alloc_get_ip_info_json();
+						wifi_manager_unlock_json_buffer();
+						if(buff) {
+							netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
+							netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+							free(buff);
+						}
+						else {
+							netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+						}
+
+					}
+					else {
+						netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+						ESP_LOGE(TAG,   "http_server_netconn_serve: GET /status failed to obtain mutex");
+					}
+					ESP_LOGI(TAG,  "Done Serving status.json");
+				}
+				else {
+					netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
+					ESP_LOGE(TAG,   "bad request from host: %s, request %s",remote_address, line);
+				}
+			}
+		}
+		else {
+			ESP_LOGE(TAG,   "URL not found processing for remote host : %s",remote_address);
+			netconn_write(conn, http_404_hdr, sizeof(http_404_hdr) - 1, NETCONN_NOCOPY);
+		}
+		free(host);
+		free(buf);
+	}
+
+	free(ap_ip_address);
+	free(remote_address);
+	netconn_close(conn);
+	/* free the buffer */
+
+}
+
+bool http_server_lock_json_object(TickType_t xTicksToWait) {
+	ESP_LOGD(TAG,  "Locking config json object");
+	if(http_server_config_mutex) {
+		if( xSemaphoreTake( http_server_config_mutex, xTicksToWait ) == pdTRUE ) {
+			ESP_LOGV(TAG,  "config Json object locked!");
+			return true;
+		}
+		else {
+			ESP_LOGW(TAG,  "Semaphore take failed. Unable to lock config Json object mutex");
+			return false;
+		}
+	}
+	else {
+		ESP_LOGW(TAG,  "Unable to lock config Json object mutex");
+		return false;
+	}
+
+}
+
+void http_server_unlock_json_object() {
+	ESP_LOGD(TAG,  "Unlocking json buffer!");
+	xSemaphoreGive( http_server_config_mutex );
+}
+
+void strreplace(char *src, char *str, char *rep)
+{
+    char *p = strstr(src, str);
+    if(p)
+    {
+        int len = strlen(src)+strlen(rep)-strlen(str);
+        char r[len];
+        memset(r, 0, len);
+        if( p >= src ) {
+            strncpy(r, src, p-src);
+            r[p-src]='\0';
+            strncat(r, rep, strlen(rep));
+            strncat(r, p+strlen(str), p+strlen(str)-src+strlen(src));
+            strcpy(src, r);
+            strreplace(p+strlen(rep), str, rep);
+        }
+    }
+}
+

+ 6 - 3
components/wifi-manager/wifi_manager.c

@@ -56,8 +56,6 @@ Contains the freeRTOS task and all necessary support
 #include "lwip/ip4_addr.h"
 #include "esp_ota_ops.h"
 #include "esp_app_format.h"
-#include "driver/gpio.h"
-#include "driver/adc.h"
 #include "cJSON.h"
 #include "config.h"
 #include "trace.h"
@@ -65,6 +63,7 @@ Contains the freeRTOS task and all necessary support
 
 #include "http_server_handlers.h"
 #include "monitor.h"
+#include "globdefs.h"
 
 #ifndef RECOVERY_APPLICATION
 #define RECOVERY_APPLICATION 0
@@ -271,6 +270,10 @@ void wifi_manager_init_wifi(){
     ESP_LOGD(TAG,   "Initializing wifi. Setting WiFi mode to WIFI_MODE_NULL");
     ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
     ESP_LOGD(TAG,   "Initializing wifi. Starting wifi");
+	if (gpio36_39_used) {
+		ESP_LOGW(TAG, "GPIO 36 or 39 are in use, need to disable WiFi PowerSave!");
+		esp_wifi_set_ps(WIFI_PS_NONE); 
+		}	
     ESP_ERROR_CHECK( esp_wifi_start() );
     taskYIELD();
     ESP_LOGD(TAG,   "Initializing wifi. done");
@@ -454,7 +457,7 @@ cJSON * wifi_manager_get_basic_info(cJSON **old){
 	cJSON_AddItemToObject(root, "ota_dsc", cJSON_CreateString(ota_get_status()));
 	cJSON_AddNumberToObject(root,"ota_pct",	ota_get_pct_complete()	);
 	cJSON_AddItemToObject(root, "Jack", cJSON_CreateString(jack_inserted_svc() ? "1" : "0"));
-	cJSON_AddNumberToObject(root,"Voltage",	adc1_get_raw(ADC1_CHANNEL_7) / 4095. * (10+174)/10. * 1.1);
+	cJSON_AddNumberToObject(root,"Voltage",	battery_value_svc());
 	cJSON_AddNumberToObject(root,"disconnect_count", num_disconnect	);
 	cJSON_AddNumberToObject(root,"avg_conn_time", num_disconnect>0?(total_connected_time/num_disconnect):0	);
 

+ 16 - 25
main/Kconfig.projbuild

@@ -62,6 +62,8 @@ menu "Squeezelite-ESP32"
 	            Type of hardware platform
 	        config SQUEEZEAMP 
 	            bool "SqueezeAMP (TAS575x & Bluetooth)"
+			config A1S
+	            bool "ESP32-A1S module"				
 	        config BASIC_I2C_BT
 	            bool "Generic I2S & Bluetooth"
 	    endchoice
@@ -75,7 +77,7 @@ menu "Squeezelite-ESP32"
 		            I2S dma channel to use.  
 		    config I2S_BCK_IO         
 		        int "I2S Bit clock GPIO number. "
-		        default 26
+		        default 33
 		        help
 		            I2S Bit Clock gpio pin to use.  
 		    config I2S_WS_IO         
@@ -85,29 +87,13 @@ menu "Squeezelite-ESP32"
 		            I2S Word Select gpio pin to use.
 		    config I2S_DO_IO         
 		        int "I2S Data I/O GPIO number. "
-		        default 22
+		        default 32
 		        help
 		            I2S data I/O gpio pin to use.
-		    choice 
-	  			prompt "Bit Depth for I2S output"
-		        default I2S_BITS_PER_CHANNEL_16
-		        config I2S_BITS_PER_CHANNEL_24
-		            bool "24 Bits"
-		        config I2S_BITS_PER_CHANNEL_16
-		            bool "16 Bits"
-		        config I2S_BITS_PER_CHANNEL_8
-		            bool "8 Bits"
-		    endchoice
-			config I2S_BITS_PER_CHANNEL
-			    int
-			    default 16
-			    default 16 if I2S_BITS_PER_CHANNEL_16
-			    default 24 if I2S_BITS_PER_CHANNEL_24
-			    default 8 if I2S_BITS_PER_CHANNEL_8
 		endmenu
 		
 		menu "SPDIF settings" 
-		    depends on BASIC_I2C_BT
+		    depends on BASIC_I2C_BT || A1S
 			config SDIF_NUM         
 		        int "SDPIF/I2S channel (0 or 1)"
 		        default 0
@@ -115,19 +101,19 @@ menu "Squeezelite-ESP32"
 		            I2S dma channel to use.  
 		    config SPDIF_BCK_IO         
 		        int "SDPIF/I2S Bit clock GPIO number"
-		        default 26
+		        default -1
 		        help
-		            Not used but must be configured.  
+		            Must be set even if you don't use SPDIF  
 		    config SPDIF_WS_IO         
 		        int "SPDIF/I2S Word Select GPIO number"
-		        default 25
+		        default -1
 		        help
-		            Not used but must be configured.  
+		            Must be set even if you don't use SPDIF  
 		    config SPDIF_DO_IO         
 		        int "I2S Data I/O GPIO number"
-		        default 15
+		        default -1
 		        help
-		            SPDIF/I2S data I/O gpio pin to use
+		            Must be set even if you don't use SPDIF  
 		endmenu
 				
 		menu "A2DP settings"
@@ -217,6 +203,11 @@ menu "Squeezelite-ESP32"
 			default -1
 			help
 				Set to -1 for no LED
+		config JACK_GPIO				
+			int "Jack insertion GPIO"
+			default -1
+			help
+				GPIO to detect speaker jack insertion (0 = inserted). Set to -1 for no detection
 	endmenu	
 	
 endmenu

+ 3 - 3
main/console.c

@@ -226,14 +226,14 @@ void run_command(char * line){
 	esp_err_t err = esp_console_run(line, &ret);
 
 	if (err == ESP_ERR_NOT_FOUND) {
-		printf("Unrecognized command\n");
+		ESP_LOGE(TAG,"Unrecognized command: %s\n", line);
 	} else if (err == ESP_ERR_INVALID_ARG) {
 		// command was empty
 	} else if (err == ESP_OK && ret != ESP_OK) {
-		printf("Command returned non-zero error code: 0x%x (%s)\n", ret,
+		ESP_LOGW(TAG,"Command returned non-zero error code: 0x%x (%s)\n", ret,
 				esp_err_to_name(err));
 	} else if (err != ESP_OK) {
-		printf("Internal error: %s\n", esp_err_to_name(err));
+		ESP_LOGE(TAG,"Internal error: %s\n", esp_err_to_name(err));
 	}
 }
 static void * console_thread() {

+ 27 - 8
main/esp_app_main.c

@@ -46,6 +46,7 @@
 #include <math.h>
 #include "config.h"
 #include "audio_controls.h"
+#include "telnet.h"
 
 static const char certs_namespace[] = "certificates";
 static const char certs_key[] = "blob";
@@ -305,30 +306,44 @@ void register_default_nvs(){
 	ESP_LOGD(TAG,"Registering default value for key %s", "i2c_config");
 	config_set_default(NVS_TYPE_STR, "i2c_config", "", 0);
 	
-	ESP_LOGD(TAG,"Registering default value for key %s", "Vcc_GPIO");
-	config_set_default(NVS_TYPE_STR, "Vcc_GPIO", "", 0);
+	ESP_LOGD(TAG,"Registering default value for key %s", "set_GPIO");
+	config_set_default(NVS_TYPE_STR, "set_GPIO", "", 0);
 	
 	ESP_LOGD(TAG,"Registering default value for key %s", "metadata_config");
 	config_set_default(NVS_TYPE_STR, "metadata_config", "", 0);
 	
+	ESP_LOGD(TAG,"Registering default value for key %s", "telnet_enable");
+	config_set_default(NVS_TYPE_STR, "telnet_enable", "", 0);
+
+	ESP_LOGD(TAG,"Registering default value for key %s", "telnet_buffer");
+	config_set_default(NVS_TYPE_STR, "telnet_buffer", "40000", 0);
+
+	ESP_LOGD(TAG,"Registering default value for key %s", "telnet_block");
+	config_set_default(NVS_TYPE_STR, "telnet_block", "500", 0);
+	
+	ESP_LOGD(TAG,"Registering default value for key %s", "stats");
+	config_set_default(NVS_TYPE_STR, "stats", "n", 0);
+	
 	ESP_LOGD(TAG,"Done setting default values in nvs.");
 }
 
 void app_main()
 {
 	char * fwurl = NULL;
-	esp_err_t update_certificates();
-	ESP_LOGD(TAG,"Creating event group for wifi");
-	wifi_event_group = xEventGroupCreate();
-	ESP_LOGD(TAG,"Clearing CONNECTED_BIT from wifi group");
-	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
-	
 	ESP_LOGI(TAG,"Starting app_main");
 	initialize_nvs();
+	ESP_LOGI(TAG,"Setting up telnet.");
+	init_telnet(); // align on 32 bits boundaries
 
 	ESP_LOGI(TAG,"Setting up config subsystem.");
 	config_init();
 
+	ESP_LOGD(TAG,"Creating event group for wifi");
+	wifi_event_group = xEventGroupCreate();
+	ESP_LOGD(TAG,"Clearing CONNECTED_BIT from wifi group");
+	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
+	
+
 	ESP_LOGI(TAG,"Registering default values");
 	register_default_nvs();
 
@@ -381,6 +396,10 @@ void app_main()
 		wifi_manager_start();
 		wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_got_ip);
 		wifi_manager_set_callback(EVENT_STA_DISCONNECTED, &cb_connection_sta_disconnected);
+		/* Start the telnet service after we are certain that the network stack has been properly initialized.
+		 * This can be either after we're started the AP mode, or after we've started the STA mode  */
+		wifi_manager_set_callback(ORDER_START_AP, &start_telnet);
+		wifi_manager_set_callback(ORDER_CONNECT_STA, &start_telnet);
 	}
 	console_start();
 	if(fwurl && strlen(fwurl)>0){

二进制
plugin/SqueezeESP32.zip


+ 2 - 4
plugin/SqueezeESP32/Player.pm

@@ -12,10 +12,6 @@ sub model { 'squeezeesp32' }
 sub modelName { 'SqueezeESP32' }
 sub hasIR { 0 }
 
-# We need to implement this to allow us to receive SETD commands
-# and we need SETD to support custom display widths
-sub directBodyFrame { 1 }
-
 # Allow the player to define it's display width (and probably more)
 sub playerSettingsFrame {
 	my $client   = shift;
@@ -32,6 +28,8 @@ sub playerSettingsFrame {
 			$client->update;
 		} 
 	}
+	
+	$client->SUPER::playerSettingsFrame($data_ref);
 }
 
 sub hasScrolling  {

二进制
plugin/SqueezeESP32/SqueezeESP32.zip


+ 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.7</version>
+    <version>0.8</version>
   <creator>Philippe</creator>
 </extensions>

+ 2 - 2
plugin/repo.xml

@@ -1,10 +1,10 @@
 <?xml version='1.0' standalone='yes'?>
 <extensions>
   <plugins>
-    <plugin version="0.7" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
+    <plugin version="0.8" name="SqueezeESP32" minTarget="7.5" maxTarget="*">
       <link>https://github.com/sle118/squeezelite-esp32</link>
       <creator>Philippe</creator>
-      <sha>0d5d5101edf534a6eaac8e42cec88532011976a5</sha>
+      <sha>66cde7aee1f92e82087e5a7a68c6d57f1229e2a0</sha>
       <email>philippe_44@outlook.com</email>
       <desc lang="EN">SqueezeESP32 additional player id (100)</desc>
       <url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>