Selaa lähdekoodia

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

Conflicts:
	components/services/monitor.c
	components/telnet/telnet.c
sebastien 5 vuotta sitten
vanhempi
commit
58a2d0bea7

+ 0 - 9
.gitmodules

@@ -1,12 +1,3 @@
-[submodule "components/libwebsockets"]
-	path = components/libwebsockets
-	url = https://github.com/warmcat/libwebsockets.git
-[submodule "components/mbedtls"]
-	path = components/mbedtls
-	url = https://github.com/lws-team/mbedtls.git
-[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

+ 38 - 8
README.md

@@ -5,9 +5,11 @@ Works with the SqueezeAMP see [here](https://forums.slimdevices.com/showthread.p
 
 Use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
 
+### ESP32-A1S
+Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board or an external amplifier if you want direct speaker connection. 
+
 ### ESP32-WROVER + I2S DAC
-Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements.  
-To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
+Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
 
 I2S - WROVER  
 VCC - 3.3V  
@@ -16,9 +18,9 @@ GND - GND
 FLT - GND  
 DMP - GND  
 SCL - GND  
-BCK - 26  
-DIN - 22  
-LCK - 25  
+BCK - (see below)  
+DIN - (see below)  
+LCK - (see below)
 FMT - GND  
 XMT - 3.3V 
 
@@ -28,9 +30,25 @@ Use the `squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults` configuration file.
 To access NVS, in the webUI, go to credits and select "shows nvs editor". Go into the NVS editor tab to change NFS parameters. In syntax description below \<\> means a value while \[\] describe optional parameters. 
 
 ### I2C
-The NVS parameter "i2c_config" set the I2C's gpio needed to enable. Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Syntax is
+The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Syntax is
+```
+bck=<gpio>,ws=<gpio>,do=<gpio>
+```
+### DAC/I2S
+The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can also define these at compile time but nvs parameter takes precedence. Note that on SqueezeAMP and A1S, these are forced at runtime, so this parameter does not matter. If your DAC also requires i2c, then you must go the re-compile route. Syntax is
+```
+sda=<gpio>,scl=<gpio>,port=0|1
+```
+### SPDIF
+The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF. 
+
+SPDIF is made available by re-using i2s interface in a non-standard way, so although only one pin (DO) is needed, the controller must be fully initialized, so the bit clock (bck) and word clock (ws) must be set as well. As i2s and SPDIF are mutually exclusive, you can reuse the same IO if your hardware allows so.
+
+Note that on SqueezeAMP, these are automatically defined, so this parameter does not matter.
+
+Leave it blank to disable SPDIF usage, you can also define them at compile time using "make menuconfig". Syntax is 
 ```
-sda=<gpio_num>,scl=<gpio_num>,port=0|1
+bck=<gpio>,ws=<gpio>,do=<gpio>
 ```
 ## Display
 The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
@@ -47,6 +65,8 @@ 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>".
 
+Currently only 128x32 I2C display like [this](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi) are supported
+
 ### Set GPIO
 The parameter "set_GPIO" is use to set assign GPIO to various functions.
 
@@ -62,6 +82,17 @@ Syntax is:
 <gpio_1>=Vcc|GND|amp|jack[,<gpio_n>=Vcc|GND|amp|jack]
 ```
 
+### Rotary Encoder
+One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software. 
+
+Encoder is hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay. There is no complex configuration like buttons (see below). Long press is not supported
+
+Use parameter rotary_config with the following syntax:
+
+```
+A=<gpio>,B=<gpio>[,SW=gpio>]
+```
+
 ### Buttons
 Buttons are described using a JSON string with the following syntax
 ```
@@ -143,7 +174,6 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
  "longshifted":{"pressed":"BCTRLS_LEFT"}}
 ]
 ```
-
 ## Setting up ESP-IDF
 ### Docker
 You can use docker to build squeezelite-esp32  

+ 1133 - 0
build-scripts/ESP32-A1S.defaults

@@ -0,0 +1,1133 @@
+#
+# Automatically generated file. DO NOT EDIT.
+# Espressif IoT Development Framework (ESP-IDF) Project Configuration
+#
+CONFIG_IDF_TARGET_ESP32=y
+CONFIG_IDF_TARGET="esp32"
+
+#
+# SDK tool configuration
+#
+CONFIG_SDK_TOOLPREFIX="xtensa-esp32-elf-"
+CONFIG_SDK_MAKE_WARN_UNDEFINED_VARIABLES=y
+CONFIG_APP_COMPILE_TIME_DATE=y
+
+
+
+CONFIG_OTA_ALLOW_HTTP=y
+
+
+CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y
+
+
+CONFIG_BOOTLOADER_LOG_LEVEL=3
+CONFIG_BOOTLOADER_SPI_WP_PIN=7
+CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y
+
+
+CONFIG_BOOTLOADER_WDT_ENABLE=y
+
+CONFIG_BOOTLOADER_WDT_TIME_MS=9000
+
+
+
+
+CONFIG_ESPTOOLPY_BAUD_OTHER_VAL=115200
+CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
+
+
+
+CONFIG_ESPTOOLPY_FLASHMODE="dio"
+CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
+
+
+
+CONFIG_ESPTOOLPY_FLASHFREQ="80m"
+
+
+CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
+
+
+CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
+CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y
+CONFIG_ESPTOOLPY_BEFORE_RESET=y
+
+CONFIG_ESPTOOLPY_BEFORE="default_reset"
+CONFIG_ESPTOOLPY_AFTER_RESET=y
+
+CONFIG_ESPTOOLPY_AFTER="hard_reset"
+
+
+CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y
+
+
+
+
+CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER_VAL=115200
+CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
+
+
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
+CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
+CONFIG_PARTITION_TABLE_OFFSET=0x8000
+CONFIG_PARTITION_TABLE_MD5=y
+CONFIG_LOGGING_SLIMPROTO="info"
+CONFIG_LOGGING_STREAM="info"
+CONFIG_LOGGING_DECODE="info"
+CONFIG_LOGGING_OUTPUT="info"
+CONFIG_INCLUDE_FLAC=y
+CONFIG_INCLUDE_FAAD=y
+CONFIG_INCLUDE_MAD=y
+CONFIG_INCLUDE_VORBIS=y
+CONFIG_INCLUDE_ALAC=y
+
+CONFIG_A1S=y
+CONFIG_SDIF_NUM=0
+CONFIG_SPDIF_BCK_IO=-1
+CONFIG_SPDIF_WS_IO=-1
+CONFIG_SPDIF_DO_IO=-1
+
+CONFIG_A2DP_SINK_NAME="SMSL BT4.2"
+CONFIG_A2DP_DEV_NAME="Squeezelite"
+CONFIG_A2DP_CONTROL_DELAY_MS=500
+CONFIG_A2DP_CONNECT_TIMEOUT_MS=1000
+CONFIG_BT_SINK=y
+CONFIG_BT_SINK_NAME="ESP32-BT"
+CONFIG_BT_SINK_PIN=1234
+CONFIG_AIRPLAY_SINK=y
+CONFIG_AIRPLAY_NAME="ESP32-AirPlay"
+CONFIG_AIRPLAY_PORT="5000"
+CONFIG_WIFI_MANAGER_TASK_PRIORITY=5
+CONFIG_WIFI_MANAGER_MAX_RETRY=2
+CONFIG_DEFAULT_AP_SSID="squeezelite"
+CONFIG_DEFAULT_AP_PASSWORD="squeezelite"
+CONFIG_DEFAULT_AP_CHANNEL=1
+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_AP_MAX_CONNECTIONS=4
+CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
+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
+
+
+
+
+
+
+
+CONFIG_ESP32_APPTRACE_DEST_NONE=y
+
+CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
+CONFIG_BT_ENABLED=y
+
+CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
+
+CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN=2
+CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN=0
+
+CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_PCM=y
+CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_EFF=1
+CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF=0
+CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF=2
+CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF=0
+CONFIG_BTDM_CTRL_PINNED_TO_CORE_0=y
+
+CONFIG_BTDM_CTRL_PINNED_TO_CORE=0
+CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y
+
+CONFIG_BTDM_MODEM_SLEEP=y
+CONFIG_BTDM_MODEM_SLEEP_MODE_ORIG=y
+
+CONFIG_BTDM_LPCLK_SEL_MAIN_XTAL=y
+CONFIG_BTDM_BLE_SLEEP_CLOCK_ACCURACY_INDEX_EFF=1
+CONFIG_BT_BLUEDROID_ENABLED=y
+
+CONFIG_BT_BTC_TASK_STACK_SIZE=3072
+CONFIG_BT_BLUEDROID_PINNED_TO_CORE_0=y
+
+CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0
+CONFIG_BT_BTU_TASK_STACK_SIZE=4096
+
+CONFIG_BT_CLASSIC_ENABLED=y
+CONFIG_BT_A2DP_ENABLE=y
+CONFIG_BT_A2DP_SINK_TASK_STACK_SIZE=2048
+CONFIG_BT_A2DP_SOURCE_TASK_STACK_SIZE=2048
+
+
+CONFIG_BT_SSP_ENABLED=y
+CONFIG_BT_BLE_ENABLED=y
+CONFIG_BT_GATTS_ENABLE=y
+
+CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_AUTO=y
+CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_MODE=0
+CONFIG_BT_GATTC_ENABLE=y
+
+CONFIG_BT_BLE_SMP_ENABLE=y
+
+
+
+
+CONFIG_BT_LOG_HCI_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_HCI_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_BTM_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_BTM_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_L2CAP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_L2CAP_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_RFCOMM_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_RFCOMM_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_SDP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_SDP_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_GAP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_GAP_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_BNEP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_BNEP_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_PAN_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_PAN_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_A2D_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_A2D_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_AVDT_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_AVDT_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_AVCT_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_AVCT_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_AVRC_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_AVRC_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_MCA_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_MCA_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_HID_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_HID_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_APPL_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_APPL_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_GATT_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_GATT_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_SMP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_SMP_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_BTIF_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_BTIF_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_BTC_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_BTC_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_OSI_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_OSI_TRACE_LEVEL=2
+
+
+CONFIG_BT_LOG_BLUFI_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BT_LOG_BLUFI_TRACE_LEVEL=2
+CONFIG_BT_ACL_CONNECTIONS=4
+
+
+
+CONFIG_BT_SMP_ENABLE=y
+CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT=30
+CONFIG_BT_RESERVE_DRAM=0xdb5c
+
+
+CONFIG_ADC_DISABLE_DAC=y
+
+CONFIG_SPI_MASTER_ISR_IN_IRAM=y
+
+CONFIG_SPI_SLAVE_ISR_IN_IRAM=y
+
+
+
+CONFIG_EFUSE_CODE_SCHEME_COMPAT_3_4=y
+
+CONFIG_EFUSE_MAX_BLK_LEN=192
+
+
+
+CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
+CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
+CONFIG_ESP32_SPIRAM_SUPPORT=y
+CONFIG_SPIRAM_BOOT_INIT=y
+
+
+CONFIG_SPIRAM_USE_MALLOC=y
+CONFIG_SPIRAM_TYPE_AUTO=y
+
+
+CONFIG_SPIRAM_SIZE=-1
+
+CONFIG_SPIRAM_SPEED_80M=y
+CONFIG_SPIRAM_MEMTEST=y
+CONFIG_SPIRAM_CACHE_WORKAROUND=y
+
+CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=256
+CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536
+CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
+CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
+
+
+CONFIG_SPIRAM_OCCUPY_VSPI_HOST=y
+CONFIG_D0WD_PSRAM_CLK_IO=17
+CONFIG_D0WD_PSRAM_CS_IO=16
+CONFIG_D2WD_PSRAM_CLK_IO=9
+CONFIG_D2WD_PSRAM_CS_IO=10
+CONFIG_PICO_PSRAM_CS_IO=10
+
+
+
+CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0
+
+CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y
+CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4
+
+CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=0
+
+CONFIG_ESP32_PANIC_PRINT_REBOOT=y
+
+
+CONFIG_ESP32_DEBUG_OCDAWARE=y
+
+CONFIG_ESP32_BROWNOUT_DET=y
+CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_0=y
+
+
+
+
+
+
+
+CONFIG_ESP32_BROWNOUT_DET_LVL=0
+CONFIG_ESP32_REDUCE_PHY_TX_POWER=y
+CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y
+
+
+
+CONFIG_ESP32_RTC_CLK_SRC_INT_RC=y
+
+
+
+CONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024
+CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000
+CONFIG_ESP32_XTAL_FREQ_40=y
+
+
+CONFIG_ESP32_XTAL_FREQ=40
+
+
+
+
+CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y
+CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y
+CONFIG_ADC_CAL_LUT_ENABLE=y
+
+CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
+CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
+CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
+CONFIG_ESP_IPC_TASK_STACK_SIZE=1024
+CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584
+CONFIG_ESP_CONSOLE_UART_DEFAULT=y
+
+
+CONFIG_ESP_CONSOLE_UART_NUM=0
+CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
+CONFIG_ESP_INT_WDT=y
+CONFIG_ESP_INT_WDT_TIMEOUT_MS=800
+CONFIG_ESP_INT_WDT_CHECK_CPU1=y
+CONFIG_ESP_TASK_WDT=y
+
+CONFIG_ESP_TASK_WDT_TIMEOUT_S=5
+CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
+CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y
+CONFIG_ETH_USE_ESP32_EMAC=y
+CONFIG_ETH_PHY_INTERFACE_RMII=y
+
+CONFIG_ETH_RMII_CLK_INPUT=y
+
+CONFIG_ETH_RMII_CLK_IN_GPIO=0
+CONFIG_ETH_SMI_MDC_GPIO=23
+CONFIG_ETH_SMI_MDIO_GPIO=18
+CONFIG_ETH_PHY_USE_RST=y
+CONFIG_ETH_PHY_RST_GPIO=5
+CONFIG_ETH_DMA_BUFFER_SIZE=512
+CONFIG_ETH_DMA_RX_BUFFER_NUM=10
+CONFIG_ETH_DMA_TX_BUFFER_NUM=10
+
+CONFIG_ESP_EVENT_POST_FROM_ISR=y
+CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
+CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
+
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
+CONFIG_HTTPD_MAX_URI_LEN=512
+CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
+CONFIG_HTTPD_PURGE_BUF_LEN=32
+
+
+
+CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y
+
+
+CONFIG_ESP32_WIFI_SW_COEXIST_PREFERENCE_BALANCE=y
+CONFIG_ESP32_WIFI_SW_COEXIST_PREFERENCE_VALUE=2
+CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=12
+CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=40
+CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
+CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
+CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=12
+
+
+
+CONFIG_ESP32_WIFI_NVS_ENABLED=y
+CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y
+
+CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752
+CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32
+
+CONFIG_ESP32_WIFI_IRAM_OPT=y
+CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y
+
+CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
+CONFIG_ESP32_PHY_MAX_TX_POWER=20
+
+
+CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
+
+
+CONFIG_FATFS_CODEPAGE_437=y
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+CONFIG_FATFS_CODEPAGE=437
+CONFIG_FATFS_LFN_NONE=y
+
+
+CONFIG_FATFS_FS_LOCK=0
+CONFIG_FATFS_TIMEOUT_MS=10000
+CONFIG_FATFS_PER_FILE_CACHE=y
+CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
+CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=150
+CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200
+CONFIG_FMB_QUEUE_LENGTH=20
+CONFIG_FMB_SERIAL_TASK_STACK_SIZE=2048
+CONFIG_FMB_SERIAL_BUF_SIZE=256
+CONFIG_FMB_SERIAL_TASK_PRIO=10
+
+CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT=20
+CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE=20
+CONFIG_FMB_CONTROLLER_STACK_SIZE=4096
+CONFIG_FMB_EVENT_QUEUE_TIMEOUT=20
+CONFIG_FMB_TIMER_PORT_ENABLED=y
+CONFIG_FMB_TIMER_GROUP=0
+CONFIG_FMB_TIMER_INDEX=0
+
+CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
+CONFIG_FREERTOS_CORETIMER_0=y
+
+CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
+
+
+CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
+
+CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
+CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
+CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y
+
+
+CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536
+CONFIG_FREERTOS_ISR_STACKSIZE=1536
+
+CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
+CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
+
+CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1
+CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048
+CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10
+CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
+
+
+
+CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y
+
+CONFIG_HEAP_POISONING_DISABLED=y
+
+
+CONFIG_HEAP_TRACING_OFF=y
+
+
+
+CONFIG_LIBSODIUM_USE_MBEDTLS_SHA=y
+
+
+
+CONFIG_LOG_DEFAULT_LEVEL_INFO=y
+
+
+CONFIG_LOG_DEFAULT_LEVEL=3
+CONFIG_LOG_COLORS=y
+CONFIG_LWIP_LOCAL_HOSTNAME="espressif"
+
+
+CONFIG_LWIP_TIMERS_ONDEMAND=y
+CONFIG_LWIP_MAX_SOCKETS=16
+
+CONFIG_LWIP_SO_REUSE=y
+CONFIG_LWIP_SO_REUSE_RXTOALL=y
+
+
+
+
+
+CONFIG_LWIP_ESP_GRATUITOUS_ARP=y
+CONFIG_LWIP_GARP_TMR_INTERVAL=60
+CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
+CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
+
+CONFIG_LWIP_DHCPS_LEASE_UNIT=60
+CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8
+
+CONFIG_LWIP_NETIF_LOOPBACK=y
+CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
+CONFIG_LWIP_MAX_ACTIVE_TCP=16
+CONFIG_LWIP_MAX_LISTENING_TCP=16
+CONFIG_LWIP_TCP_MAXRTX=12
+CONFIG_LWIP_TCP_SYNMAXRTX=6
+CONFIG_LWIP_TCP_MSS=1440
+CONFIG_LWIP_TCP_MSL=60000
+CONFIG_LWIP_TCP_SND_BUF_DEFAULT=8192
+CONFIG_LWIP_TCP_WND_DEFAULT=32768
+CONFIG_LWIP_TCP_RECVMBOX_SIZE=32
+CONFIG_LWIP_TCP_QUEUE_OOSEQ=y
+
+CONFIG_LWIP_TCP_OVERSIZE_MSS=y
+
+
+CONFIG_LWIP_MAX_UDP_PCBS=16
+CONFIG_LWIP_UDP_RECVMBOX_SIZE=32
+CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072
+CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y
+
+
+CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF
+
+
+
+CONFIG_LWIP_MAX_RAW_PCBS=16
+CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1
+CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000
+
+CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
+
+
+CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384
+
+
+CONFIG_MBEDTLS_HARDWARE_AES=y
+
+
+CONFIG_MBEDTLS_HAVE_TIME=y
+
+CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y
+
+
+
+CONFIG_MBEDTLS_TLS_SERVER=y
+CONFIG_MBEDTLS_TLS_CLIENT=y
+CONFIG_MBEDTLS_TLS_ENABLED=y
+
+CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y
+CONFIG_MBEDTLS_SSL_RENEGOTIATION=y
+
+CONFIG_MBEDTLS_SSL_PROTO_TLS1=y
+CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y
+CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y
+
+CONFIG_MBEDTLS_SSL_ALPN=y
+CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y
+CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y
+CONFIG_MBEDTLS_AES_C=y
+
+
+CONFIG_MBEDTLS_RC4_DISABLED=y
+
+
+
+
+CONFIG_MBEDTLS_CCM_C=y
+CONFIG_MBEDTLS_GCM_C=y
+
+CONFIG_MBEDTLS_PEM_PARSE_C=y
+CONFIG_MBEDTLS_PEM_WRITE_C=y
+CONFIG_MBEDTLS_X509_CRL_PARSE_C=y
+CONFIG_MBEDTLS_X509_CSR_PARSE_C=y
+CONFIG_MBEDTLS_ECP_C=y
+CONFIG_MBEDTLS_ECDH_C=y
+CONFIG_MBEDTLS_ECDSA_C=y
+CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y
+CONFIG_MBEDTLS_ECP_NIST_OPTIM=y
+CONFIG_MDNS_MAX_SERVICES=10
+CONFIG_MQTT_PROTOCOL_311=y
+CONFIG_MQTT_TRANSPORT_SSL=y
+CONFIG_MQTT_TRANSPORT_WEBSOCKET=y
+CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y
+
+
+
+CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y
+
+
+
+
+CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y
+
+
+CONFIG_OPENSSL_ASSERT_DO_NOTHING=y
+
+CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5
+CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072
+CONFIG_ESP32_PTHREAD_STACK_MIN=768
+
+
+CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1=y
+CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=1
+CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread"
+
+
+CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y
+CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y
+
+
+
+CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y
+CONFIG_SPIFFS_MAX_PARTITIONS=3
+CONFIG_SPIFFS_CACHE=y
+CONFIG_SPIFFS_CACHE_WR=y
+
+CONFIG_SPIFFS_PAGE_CHECK=y
+CONFIG_SPIFFS_GC_MAX_RUNS=10
+
+CONFIG_SPIFFS_PAGE_SIZE=256
+CONFIG_SPIFFS_OBJ_NAME_LEN=32
+CONFIG_SPIFFS_USE_MAGIC=y
+CONFIG_SPIFFS_USE_MAGIC_LENGTH=y
+CONFIG_SPIFFS_META_LENGTH=4
+CONFIG_SPIFFS_USE_MTIME=y
+
+
+
+
+
+
+CONFIG_NETIF_IP_LOST_TIMER_INTERVAL=120
+CONFIG_TCPIP_LWIP=y
+CONFIG_UNITY_ENABLE_FLOAT=y
+CONFIG_UNITY_ENABLE_DOUBLE=y
+
+CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y
+
+
+CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y
+CONFIG_VFS_SUPPORT_TERMIOS=y
+CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1
+CONFIG_SEMIHOSTFS_HOST_PATH_MAX_LEN=128
+
+CONFIG_WL_SECTOR_SIZE_512=y
+#CONFIG_WL_SECTOR_SIZE_4096 is not defined
+CONFIG_WL_SECTOR_SIZE=512
+CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16
+
+
+
+# Deprecated options for backward compatibility
+CONFIG_TOOLPREFIX="xtensa-esp32-elf-"
+CONFIG_MAKE_WARN_UNDEFINED_VARIABLES=y
+
+
+
+CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y
+
+
+CONFIG_LOG_BOOTLOADER_LEVEL=3
+
+
+CONFIG_FLASHMODE_QIO=y
+
+
+
+
+
+CONFIG_MONITOR_BAUD_115200B=y
+
+
+
+
+CONFIG_MONITOR_BAUD_OTHER_VAL=115200
+CONFIG_MONITOR_BAUD=115200
+
+CONFIG_OPTIMIZATION_LEVEL_RELEASE=y
+CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
+
+
+
+CONFIG_STACK_CHECK_NONE=y
+
+
+
+
+
+
+
+CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY=y
+
+CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_ACL_CONN=2
+CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_SYNC_CONN=0
+CONFIG_BTDM_CONTROLLER_BLE_MAX_CONN_EFF=0
+CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_ACL_CONN_EFF=2
+CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_SYNC_CONN_EFF=0
+CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE=0
+CONFIG_BTDM_CONTROLLER_HCI_MODE_VHCI=y
+
+CONFIG_BTDM_CONTROLLER_MODEM_SLEEP=y
+CONFIG_BLUEDROID_ENABLED=y
+CONFIG_BTC_TASK_STACK_SIZE=3072
+CONFIG_BLUEDROID_PINNED_TO_CORE_0=y
+
+CONFIG_BLUEDROID_PINNED_TO_CORE=0
+CONFIG_BTU_TASK_STACK_SIZE=4096
+
+CONFIG_CLASSIC_BT_ENABLED=y
+CONFIG_A2DP_ENABLE=y
+CONFIG_A2DP_SINK_TASK_STACK_SIZE=2048
+CONFIG_A2DP_SOURCE_TASK_STACK_SIZE=2048
+
+CONFIG_GATTS_ENABLE=y
+
+CONFIG_GATTS_SEND_SERVICE_CHANGE_AUTO=y
+CONFIG_GATTS_SEND_SERVICE_CHANGE_MODE=0
+CONFIG_GATTC_ENABLE=y
+
+CONFIG_BLE_SMP_ENABLE=y
+
+
+
+CONFIG_HCI_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_HCI_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_BTM_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BTM_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_L2CAP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_L2CAP_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_RFCOMM_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_RFCOMM_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_SDP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BTH_LOG_SDP_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_GAP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_GAP_INITIAL_TRACE_LEVEL=2
+CONFIG_BNEP_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_PAN_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_PAN_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_A2D_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_A2D_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_AVDT_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_AVDT_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_AVCT_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_AVCT_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_AVRC_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_AVRC_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_MCA_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_MCA_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_HID_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_HID_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_APPL_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_APPL_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_GATT_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_GATT_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_SMP_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_SMP_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_BTIF_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BTIF_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_BTC_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BTC_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_OSI_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_OSI_INITIAL_TRACE_LEVEL=2
+
+
+CONFIG_BLUFI_TRACE_LEVEL_WARNING=y
+
+
+
+
+CONFIG_BLUFI_INITIAL_TRACE_LEVEL=2
+
+CONFIG_SMP_ENABLE=y
+CONFIG_BLE_ESTABLISH_LINK_CONNECTION_TIMEOUT=30
+CONFIG_ADC2_DISABLE_DAC=y
+CONFIG_SPIRAM_SUPPORT=y
+
+
+
+CONFIG_TRACEMEM_RESERVE_DRAM=0x0
+
+CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y
+CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4
+
+CONFIG_ULP_COPROC_RESERVE_MEM=0
+CONFIG_BROWNOUT_DET=y
+CONFIG_BROWNOUT_DET_LVL_SEL_0=y
+
+
+
+
+
+
+
+CONFIG_BROWNOUT_DET_LVL=0
+CONFIG_REDUCE_PHY_TX_POWER=y
+CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y
+
+
+
+
+
+CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
+CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
+CONFIG_MAIN_TASK_STACK_SIZE=8192
+CONFIG_IPC_TASK_STACK_SIZE=1024
+CONFIG_TIMER_TASK_STACK_SIZE=3584
+CONFIG_CONSOLE_UART_DEFAULT=y
+
+
+CONFIG_CONSOLE_UART_NUM=0
+CONFIG_CONSOLE_UART_BAUDRATE=115200
+CONFIG_INT_WDT=y
+CONFIG_INT_WDT_TIMEOUT_MS=800
+CONFIG_INT_WDT_CHECK_CPU1=y
+CONFIG_TASK_WDT=y
+
+CONFIG_TASK_WDT_TIMEOUT_S=5
+CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
+CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y
+
+CONFIG_POST_EVENTS_FROM_ISR=y
+CONFIG_POST_EVENTS_FROM_IRAM_ISR=y
+CONFIG_SW_COEXIST_ENABLE=y
+
+
+CONFIG_SW_COEXIST_PREFERENCE_BALANCE=y
+CONFIG_SW_COEXIST_PREFERENCE_VALUE=2
+CONFIG_MB_MASTER_TIMEOUT_MS_RESPOND=150
+CONFIG_MB_MASTER_DELAY_MS_CONVERT=200
+CONFIG_MB_QUEUE_LENGTH=20
+CONFIG_MB_SERIAL_TASK_STACK_SIZE=2048
+CONFIG_MB_SERIAL_BUF_SIZE=256
+CONFIG_MB_SERIAL_TASK_PRIO=10
+
+CONFIG_MB_CONTROLLER_NOTIFY_TIMEOUT=20
+CONFIG_MB_CONTROLLER_NOTIFY_QUEUE_SIZE=20
+CONFIG_MB_CONTROLLER_STACK_SIZE=4096
+CONFIG_MB_EVENT_QUEUE_TIMEOUT=20
+CONFIG_MB_TIMER_PORT_ENABLED=y
+CONFIG_MB_TIMER_GROUP=0
+CONFIG_MB_TIMER_INDEX=0
+CONFIG_SUPPORT_STATIC_ALLOCATION=y
+
+CONFIG_TIMER_TASK_PRIORITY=1
+CONFIG_TIMER_TASK_STACK_DEPTH=2048
+CONFIG_TIMER_QUEUE_LENGTH=10
+
+
+CONFIG_ESP_GRATUITOUS_ARP=y
+CONFIG_GARP_TMR_INTERVAL=60
+CONFIG_TCPIP_RECVMBOX_SIZE=32
+CONFIG_TCP_MAXRTX=12
+CONFIG_TCP_SYNMAXRTX=6
+CONFIG_TCP_MSS=1440
+CONFIG_TCP_MSL=60000
+CONFIG_TCP_SND_BUF_DEFAULT=8192
+CONFIG_TCP_WND_DEFAULT=32768
+CONFIG_TCP_RECVMBOX_SIZE=32
+CONFIG_TCP_QUEUE_OOSEQ=y
+
+CONFIG_TCP_OVERSIZE_MSS=y
+
+
+CONFIG_UDP_RECVMBOX_SIZE=32
+CONFIG_TCPIP_TASK_STACK_SIZE=3072
+CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y
+
+
+CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF
+
+CONFIG_PTHREAD_STACK_MIN=768
+CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y
+
+
+CONFIG_IP_LOST_TIMER_INTERVAL=120
+CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y
+CONFIG_SUPPORT_TERMIOS=y
+# End of deprecated options

+ 2 - 0
components/driver_bt/bt_app_sink.c

@@ -132,6 +132,8 @@ const static actrls_t controls = {
 	bt_pause, bt_stop,	// pause, stop
 	NULL, NULL,			// rew, fwd
 	bt_prev, bt_next,	// prev, next
+	NULL, NULL, NULL, NULL, // left, right, up, down
+	bt_volume_down, bt_volume_up, bt_toggle// knob left, knob_right, knob push
 };
 
 /* disconnection */

+ 0 - 7
components/driver_i2s/CMakeLists.txt

@@ -1,7 +0,0 @@
-set(COMPONENT_ADD_INCLUDEDIRS .)
-
-set(COMPONENT_SRCS "tas5756m.c")
-
-set(COMPONENT_REQUIRES console spi_flash)
-
-register_component()

+ 0 - 13
components/driver_i2s/component.mk

@@ -1,13 +0,0 @@
-#
-# 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_ADD_INCLUDEDIRS := .
-CFLAGS += -Os -DPOSIX -DLINKALL -DLOOPBACK -DNO_FAAD -DEMBEDDED -DTREMOR_ONLY -DBYTES_PER_FRAME=4 	
-#CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG
-CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO

+ 0 - 0
components/driver_i2s/tas5756m.c


+ 2 - 0
components/raop/raop_sink.c

@@ -77,6 +77,8 @@ const static actrls_t controls = {
 	raop_pause, raop_stop,				// pause, stop
 	NULL, NULL,							// rew, fwd
 	raop_prev, raop_next,				// prev, next
+	NULL, NULL, NULL, NULL, // left, right, up, down
+	raop_volume_down, raop_volume_up, raop_toggle// knob left, knob_right, knob push
 };
 
 /****************************************************************************************

+ 58 - 26
components/services/audio_controls.c

@@ -62,14 +62,23 @@ static const actrls_config_map_t actrls_config_map[] =
 #define EP(x) [x] = #x  /* ENUM PRINT */
 static const char * actrls_action_s[ ] = { EP(ACTRLS_VOLUP),EP(ACTRLS_VOLDOWN),EP(ACTRLS_TOGGLE),EP(ACTRLS_PLAY),
 									EP(ACTRLS_PAUSE),EP(ACTRLS_STOP),EP(ACTRLS_REW),EP(ACTRLS_FWD),EP(ACTRLS_PREV),EP(ACTRLS_NEXT),
-									EP(BCTRLS_PUSH), EP(BCTRLS_UP),EP(BCTRLS_DOWN),EP(BCTRLS_LEFT),EP(BCTRLS_RIGHT), ""} ;
+									EP(BCTRLS_UP),EP(BCTRLS_DOWN),EP(BCTRLS_LEFT),EP(BCTRLS_RIGHT), 
+									EP(KNOB_LEFT),EP(KNOB_RIGHT),EP(KNOB_PUSH),
+									""} ;
 									
 static const char * TAG = "audio controls";
 static actrls_config_t *json_config;
 cJSON * control_profiles = NULL;
 static actrls_t default_controls, current_controls;
 static actrls_hook_t *default_hook, *current_hook;
+static struct {
+	bool long_state;
+	bool volume_lock;
+} rotary;
 
+/****************************************************************************************
+ * 
+ */
 static void control_handler(void *client, button_event_e event, button_press_e press, bool long_press) {
 	actrls_config_t *key = (actrls_config_t*) client;
 	actrls_action_detail_t  action_detail;
@@ -119,31 +128,34 @@ static void control_handler(void *client, button_event_e event, button_press_e p
 	}	
 }
 
-/*
-void up(void *id, button_event_e event, button_press_e press, bool longpress) {
-	if (press == BUTTON_NORMAL) {
-		if (longpress) ESP_LOGI(TAG, "up long %u", event);
-		else ESP_LOGI(TAG, "up %u", event);
-	} else if (press == BUTTON_SHIFTED) {
-		if (longpress) ESP_LOGI(TAG, "up shifted long %u", event);
-		else ESP_LOGI(TAG, "up shifted %u", event);
-	} else {
-		ESP_LOGI(TAG, "don't know what we are doing here %u", event);
-	}
-}
-
-void down(void *id, button_event_e event, button_press_e press, bool longpress) {
-	if (press == BUTTON_NORMAL) {
-		if (longpress) ESP_LOGI(TAG, "down long %u", event);
-		else ESP_LOGI(TAG, "down %u", event);
-	} else if (press == BUTTON_SHIFTED) {
-		if (longpress) ESP_LOGI(TAG, "down shifted long %u", event);
-		else ESP_LOGI(TAG, "down shifted %u", event);
-	} else {
-		ESP_LOGI(TAG, "don't know what we are doing here %u", event);
+/****************************************************************************************
+ * 
+ */
+static void control_rotary_handler(void *client, rotary_event_e event, bool long_press) {
+	actrls_action_e action = ACTRLS_NONE;
+	
+	switch(event) {
+	case ROTARY_LEFT:
+		if (rotary.long_state) action = ACTRLS_PREV;
+		else if (rotary.volume_lock) action = ACTRLS_VOLDOWN;
+		else action = KNOB_LEFT;
+		break;
+	case ROTARY_RIGHT:
+		if (rotary.long_state) action = ACTRLS_NEXT;
+		else if (rotary.volume_lock) action = ACTRLS_VOLUP;
+		else action = KNOB_RIGHT;
+		break;
+	case ROTARY_PRESSED:
+		if (long_press)	rotary.long_state = !rotary.long_state;
+		else if (rotary.volume_lock) action = ACTRLS_TOGGLE;
+		else action = KNOB_RIGHT;
+		break;
+	default:
+		break;
 	}
+	
+	if (action != ACTRLS_NONE) (*current_controls[action])();
 }
-*/
 
 /****************************************************************************************
  * 
@@ -364,8 +376,28 @@ esp_err_t actrls_init_json(const char *profile_name, bool create) {
 	actrls_config_t *cur_config = NULL;
 	actrls_config_t *config_root = NULL;
 	const cJSON *button;
-
-	char *config = config_alloc_get_default(NVS_TYPE_STR, profile_name, NULL, 0);
+	
+	char *config = config_alloc_get_default(NVS_TYPE_STR, "rotary_config", NULL, 0);
+	if (config && *config) {
+		char *p;
+		int A = -1, B = -1, SW = -1, longpress = 0;
+		
+		// parse config
+		if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(config, "volume")) != NULL) rotary.volume_lock = true;
+		if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000;
+				
+		// create rotary (no handling of long press)
+		err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
+	}
+			
+	if (config) free(config);	
+		
+	if (!profile_name || !*profile_name) return ESP_OK;
+	
+	config = config_alloc_get_default(NVS_TYPE_STR, profile_name, NULL, 0);
 	if(!config) return ESP_FAIL;
 
 	ESP_LOGD(TAG,"Parsing JSON structure %s", config);

+ 3 - 2
components/services/audio_controls.h

@@ -23,8 +23,9 @@
 // BEWARE: this is the index of the array of action below (change actrls_action_s as well!)
 typedef enum { 	ACTRLS_NONE = -1, 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, ACTRLS_REMAP,
-				ACTRLS_MAX 
+				BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT, 
+				KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
+				ACTRLS_REMAP, ACTRLS_MAX 
 		} actrls_action_e;
 
 typedef void (*actrls_handler)(void);

+ 124 - 37
components/services/buttons.c

@@ -31,6 +31,7 @@
 #include "esp_task.h"
 #include "driver/gpio.h"
 #include "buttons.h"
+#include "rotary_encoder.h"
 #include "globdefs.h"
 
 bool gpio36_39_used;
@@ -42,6 +43,7 @@ static int n_buttons = 0;
 #define BUTTON_STACK_SIZE	4096
 #define MAX_BUTTONS			16
 #define DEBOUNCE			50
+#define BUTTON_QUEUE_LEN	10
 
 static EXT_RAM_ATTR struct button_s {
 	void *client;
@@ -56,7 +58,16 @@ static EXT_RAM_ATTR struct button_s {
 	TimerHandle_t timer;
 } buttons[MAX_BUTTONS];
 
-static xQueueHandle button_evt_queue = NULL;
+static struct {
+	QueueHandle_t queue;
+	void *client;
+	rotary_encoder_info_t info;
+	int A, B, SW;
+	rotary_handler handler;
+} rotary;
+
+static xQueueHandle button_evt_queue;
+static QueueSetHandle_t button_queue_set;
 
 static void buttons_task(void* arg);
 
@@ -103,44 +114,63 @@ static void buttons_task(void* arg) {
 	ESP_LOGI(TAG, "starting button tasks");
 	
     while (1) {
-		struct button_s button;
-		button_event_e event;
-		button_press_e press;
-
-        if (!xQueueReceive(button_evt_queue, &button, portMAX_DELAY)) continue;
-
-		event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;		
-
-		ESP_LOGD(TAG, "received event:%u from gpio:%u level:%u (timer %u shifting %u)", event, button.gpio, button.level, button.long_timer, button.shifting);
-
-		// find if shifting is activated
-		if (button.shifter && button.shifter->type == button.shifter->level) press = BUTTON_SHIFTED;
-		else press = BUTTON_NORMAL;
+		QueueSetMemberHandle_t xActivatedMember;
+
+		// wait on button and rotary queues
+		if ((xActivatedMember = xQueueSelectFromSet( button_queue_set, portMAX_DELAY )) == NULL) continue;
+		
+		if (xActivatedMember == button_evt_queue) {
+			struct button_s button;
+			button_event_e event;
+			button_press_e press;
+			
+			// received a button event
+			xQueueReceive(button_evt_queue, &button, 0);
+
+			event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;		
+
+			ESP_LOGD(TAG, "received event:%u from gpio:%u level:%u (timer %u shifting %u)", event, button.gpio, button.level, button.long_timer, button.shifting);
+
+			// find if shifting is activated
+			if (button.shifter && button.shifter->type == button.shifter->level) press = BUTTON_SHIFTED;
+			else press = BUTTON_NORMAL;
 	
-		/* 
-		long_timer will be set either because we truly have a long press 
-		or we have a release before the long press timer elapsed, so two 
-		events shall be sent
-		*/
-		if (button.long_timer) {
-			if (event == BUTTON_RELEASED) {
-				// early release of a long-press button, send press/release
-				if (!button.shifting) {
-					(*button.handler)(button.client, BUTTON_PRESSED, press, false);		
-					(*button.handler)(button.client, BUTTON_RELEASED, press, false);		
-				}
+			/* 
+			long_timer will be set either because we truly have a long press 
+			or we have a release before the long press timer elapsed, so two 
+			events shall be sent
+			*/
+			if (button.long_timer) {
+				if (event == BUTTON_RELEASED) {
+					// early release of a long-press button, send press/release
+					if (!button.shifting) {
+						(*button.handler)(button.client, BUTTON_PRESSED, press, false);		
+						(*button.handler)(button.client, BUTTON_RELEASED, press, false);		
+					}
+					// button is a copy, so need to go to real context
+					button.self->shifting = false;
+				} else if (!button.shifting) {
+					// normal long press and not shifting so don't discard
+					(*button.handler)(button.client, BUTTON_PRESSED, press, true);
+				}  
+			} else {
+				// normal press/release of a button or release of a long-press button
+				if (!button.shifting) (*button.handler)(button.client, event, press, button.long_press);
 				// button is a copy, so need to go to real context
 				button.self->shifting = false;
-			} else if (!button.shifting) {
-				// normal long press and not shifting so don't discard
-				(*button.handler)(button.client, BUTTON_PRESSED, press, true);
-			}  
+			}
 		} else {
-			// normal press/release of a button or release of a long-press button
-			if (!button.shifting) (*button.handler)(button.client, event, press, button.long_press);
-			// button is a copy, so need to go to real context
-			button.self->shifting = false;
-		}
+			rotary_encoder_event_t event = { 0 };
+			
+			// received a rotary event
+		    xQueueReceive(rotary.queue, &event, 0);
+			
+			ESP_LOGI(TAG, "Event: position %d, direction %s", event.state.position,
+                     event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
+			
+			rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? 
+										  ROTARY_RIGHT : ROTARY_LEFT, false);   
+		}	
     }
 }	
 	
@@ -163,7 +193,9 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
 	ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %u", gpio, type, pull, long_press, shifter_gpio);
 
 	if (!n_buttons) {
-		button_evt_queue = xQueueCreate(10, sizeof(struct button_s));
+		button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
+		if (!button_queue_set) button_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
+		xQueueAddToSet( button_evt_queue, button_queue_set );
 		xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
 	}
 	
@@ -229,7 +261,18 @@ void *button_get_client(int gpio) {
 		 if (buttons[i].gpio == gpio) return buttons[i].client;
 	 }
 	 return NULL;
- }
+}
+
+/****************************************************************************************
+ * Get stored id
+ */
+bool button_is_pressed(int gpio, void *client) {
+	for (int i = 0; i < n_buttons; i++) {
+		if (gpio != -1 && buttons[i].gpio == gpio) return buttons[i].level == buttons[i].type;
+		else if (client && buttons[i].client == client) return buttons[i].level == buttons[i].type;
+	}
+	return false; 
+}
 
 /****************************************************************************************
  * Update buttons 
@@ -270,3 +313,47 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
 	
 	return prev_client;
 }
+
+/****************************************************************************************
+ * Create rotary encoder
+ */
+static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
+	ESP_LOGI(TAG, "Rotary push-button %d", event);
+	rotary.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
+}
+
+/****************************************************************************************
+ * Create rotary encoder
+ */
+bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) {
+	if (A == -1 || B == -1) {
+		ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
+		return false;
+	}
+
+	rotary.A = A;
+	rotary.B = B;
+	rotary.SW = SW;
+	rotary.client = id;
+	rotary.handler = handler;
+	
+	// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
+	if (A == 36 || A == 39 || B == 36 || B == 39 || SW == 36 || SW == 39) gpio36_39_used = true;
+
+    // Initialise the rotary encoder device with the GPIOs for A and B signals
+    rotary_encoder_init(&rotary.info, A, B);
+		
+    // Create a queue for events from the rotary encoder driver.
+    rotary.queue = rotary_encoder_create_queue();
+    rotary_encoder_set_queue(&rotary.info, rotary.queue);
+	
+	if (!button_queue_set) button_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
+	xQueueAddToSet( rotary.queue, button_queue_set );
+
+	// create companion button if rotary has a switch
+	if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
+	
+	ESP_LOGI(TAG, "Creating rotary encoder A:%d B:%d, SW:%d", A, B, SW);
+	
+	return true;
+}	

+ 7 - 1
components/services/buttons.h

@@ -18,7 +18,7 @@
 
 #pragma once
  
-// button type (pressed = LOW or HIGH)
+// button type (pressed = LOW or HIGH, matches GPIO level)
 #define BUTTON_LOW 		0
 #define BUTTON_HIGH		1
 
@@ -36,3 +36,9 @@ NOTE: shifter buttons *must* be created before shiftee
 void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio);
 void *button_remap(void *client, int gpio, button_handler handler, int long_press, int shifter_gpio);
 void *button_get_client(int gpio);
+bool button_is_pressed(int gpio, void *client);
+
+typedef enum { ROTARY_LEFT, ROTARY_RIGHT, ROTARY_PRESSED, ROTARY_RELEASED } rotary_event_e; 
+typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press);
+
+bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler);

+ 35 - 18
components/services/monitor.c

@@ -21,12 +21,17 @@
 #include "globdefs.h"
 #include "config.h"
 #include "accessors.h"
-
+#include "accessors.h"
 #define MONITOR_TIMER	(10*1000)
 
 static const char *TAG = "monitor";
 
 static TimerHandle_t monitor_timer;
+#ifdef JACK_GPIO
+static int jack_gpio = JACK_GPIO;
+#else
+static int jack_gpio = -1;
+#endif
 
 void (*jack_handler_svc)(bool inserted);
 bool jack_inserted_svc(void);
@@ -57,11 +62,8 @@ static void jack_handler_default(void *id, button_event_e event, button_press_e
  * 
  */
 bool jack_inserted_svc (void) {
-#ifdef JACK_GPIO
-	return !gpio_get_level(JACK_GPIO);
-#else
-	return false;
-#endif
+	if (jack_gpio != -1) return button_is_pressed(jack_gpio, NULL);
+	else return false;
 }
 
 /****************************************************************************************
@@ -89,15 +91,25 @@ bool spkfault_svc (void) {
  * 
  */
 void set_jack_gpio(int gpio, char *value) {
-	 if (!strcasecmp(value, "jack")) {
-		ESP_LOGI(TAG,"Adding jack detection GPIO %d", gpio);
+	bool low = false;
+	
+	if (!strcasecmp(value, "jack_l")) {
+		jack_gpio = gpio;	
+		low = true;
+	} else if (!strcasecmp(value, "jack_h")) {
+		jack_gpio = gpio;	
+	}	
+	
+	if (jack_gpio != -1) {
+		gpio_pad_select_gpio(jack_gpio);
+		gpio_set_direction(jack_gpio, GPIO_MODE_INPUT);
+		gpio_set_pull_mode(jack_gpio, low ? GPIO_PULLUP_ONLY : GPIO_PULLDOWN_ONLY);
+		
+		ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", low ? "low" : "high", 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);
-	 }	
+		button_create(NULL, jack_gpio, low ? BUTTON_LOW : BUTTON_HIGH, false, 250, jack_handler_default, 0, -1);
+	}	
  }
 
 /****************************************************************************************
@@ -105,12 +117,17 @@ void set_jack_gpio(int gpio, char *value) {
  */
 void monitor_svc_init(void) {
 	ESP_LOGI(TAG, "Initializing monitoring");
-	
-#if !defined(JACK_GPIO) || JACK_GPIO == -1
-	parse_set_GPIO(set_jack_gpio);
-#else 
-	set_jack_gpio(JACK_GPIO, "jack");	
+
+	// if JACK_GPIO is compiled-time defined set it there
+	if (jack_gpio != -1) {
+#if JACK_GPIO_LEVEL == 1		
+		set_jack_gpio(JACK_GPIO, "jack_h");	
+#else
+		set_jack_gpio(JACK_GPIO, "jack_l");	
 #endif
+	} else {
+		parse_set_GPIO(set_jack_gpio);
+	}	
 
 #ifdef SPKFAULT_GPIO
 	gpio_pad_select_gpio(SPKFAULT_GPIO);

+ 345 - 0
components/services/rotary_encoder.c

@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2019 David Antliff
+ * Copyright 2011 Ben Buxton
+ *
+ * This file is part of the esp32-rotary-encoder component.
+ *
+ * esp32-rotary-encoder 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.
+ *
+ * esp32-rotary-encoder 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 esp32-rotary-encoder.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file rotary_encoder.c
+ * @brief Driver implementation for the ESP32-compatible Incremental Rotary Encoder component.
+ *
+ * Based on https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
+ * Original header follows:
+ *
+ * Rotary encoder handler for arduino. v1.1
+ *
+ * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
+ * Contact: bb@cactii.net
+ *
+ * A typical mechanical rotary encoder emits a two bit gray code
+ * on 3 output pins. Every step in the output (often accompanied
+ * by a physical 'click') generates a specific sequence of output
+ * codes on the pins.
+ *
+ * There are 3 pins used for the rotary encoding - one common and
+ * two 'bit' pins.
+ *
+ * The following is the typical sequence of code on the output when
+ * moving from one step to the next:
+ *
+ *   Position   Bit1   Bit2
+ *   ----------------------
+ *     Step1     0      0
+ *      1/4      1      0
+ *      1/2      1      1
+ *      3/4      0      1
+ *     Step2     0      0
+ *
+ * From this table, we can see that when moving from one 'click' to
+ * the next, there are 4 changes in the output code.
+ *
+ * - From an initial 0 - 0, Bit1 goes high, Bit0 stays low.
+ * - Then both bits are high, halfway through the step.
+ * - Then Bit1 goes low, but Bit2 stays high.
+ * - Finally at the end of the step, both bits return to 0.
+ *
+ * Detecting the direction is easy - the table simply goes in the other
+ * direction (read up instead of down).
+ *
+ * To decode this, we use a simple state machine. Every time the output
+ * code changes, it follows state, until finally a full steps worth of
+ * code is received (in the correct order). At the final 0-0, it returns
+ * a value indicating a step in one direction or the other.
+ *
+ * It's also possible to use 'half-step' mode. This just emits an event
+ * at both the 0-0 and 1-1 positions. This might be useful for some
+ * encoders where you want to detect all positions.
+ *
+ * If an invalid state happens (for example we go from '0-1' straight
+ * to '1-0'), the state machine resets to the start until 0-0 and the
+ * next valid codes occur.
+ *
+ * The biggest advantage of using a state machine over other algorithms
+ * is that this has inherent debounce built in. Other algorithms emit spurious
+ * output with switch bounce, but this one will simply flip between
+ * sub-states until the bounce settles, then continue along the state
+ * machine.
+ * A side effect of debounce is that fast rotations can cause steps to
+ * be skipped. By not requiring debounce, fast rotations can be accurately
+ * measured.
+ * Another advantage is the ability to properly handle bad state, such
+ * as due to EMI, etc.
+ * It is also a lot simpler than others - a static state table and less
+ * than 10 lines of logic.
+ */
+
+#include "rotary_encoder.h"
+
+#include "esp_log.h"
+#include "driver/gpio.h"
+
+#define TAG "rotary_encoder"
+
+//#define ROTARY_ENCODER_DEBUG
+
+// Use a single-item queue so that the last value can be easily overwritten by the interrupt handler
+#define EVENT_QUEUE_LENGTH 1
+
+#define TABLE_ROWS 7
+
+#define DIR_NONE 0x0   // No complete step yet.
+#define DIR_CW   0x10  // Clockwise step.
+#define DIR_CCW  0x20  // Anti-clockwise step.
+
+// Create the half-step state table (emits a code at 00 and 11)
+#define R_START       0x0
+#define H_CCW_BEGIN   0x1
+#define H_CW_BEGIN    0x2
+#define H_START_M     0x3
+#define H_CW_BEGIN_M  0x4
+#define H_CCW_BEGIN_M 0x5
+
+static const uint8_t _ttable_half[TABLE_ROWS][TABLE_COLS] = {
+    // 00                  01              10            11                   // BA
+    {H_START_M,            H_CW_BEGIN,     H_CCW_BEGIN,  R_START},            // R_START (00)
+    {H_START_M | DIR_CCW,  R_START,        H_CCW_BEGIN,  R_START},            // H_CCW_BEGIN
+    {H_START_M | DIR_CW,   H_CW_BEGIN,     R_START,      R_START},            // H_CW_BEGIN
+    {H_START_M,            H_CCW_BEGIN_M,  H_CW_BEGIN_M, R_START},            // H_START_M (11)
+    {H_START_M,            H_START_M,      H_CW_BEGIN_M, R_START | DIR_CW},   // H_CW_BEGIN_M
+    {H_START_M,            H_CCW_BEGIN_M,  H_START_M,    R_START | DIR_CCW},  // H_CCW_BEGIN_M
+};
+
+// Create the full-step state table (emits a code at 00 only)
+#  define F_CW_FINAL  0x1
+#  define F_CW_BEGIN  0x2
+#  define F_CW_NEXT   0x3
+#  define F_CCW_BEGIN 0x4
+#  define F_CCW_FINAL 0x5
+#  define F_CCW_NEXT  0x6
+
+static const uint8_t _ttable_full[TABLE_ROWS][TABLE_COLS] = {
+    // 00        01           10           11                  // BA
+    {R_START,    F_CW_BEGIN,  F_CCW_BEGIN, R_START},           // R_START
+    {F_CW_NEXT,  R_START,     F_CW_FINAL,  R_START | DIR_CW},  // F_CW_FINAL
+    {F_CW_NEXT,  F_CW_BEGIN,  R_START,     R_START},           // F_CW_BEGIN
+    {F_CW_NEXT,  F_CW_BEGIN,  F_CW_FINAL,  R_START},           // F_CW_NEXT
+    {F_CCW_NEXT, R_START,     F_CCW_BEGIN, R_START},           // F_CCW_BEGIN
+    {F_CCW_NEXT, F_CCW_FINAL, R_START,     R_START | DIR_CCW}, // F_CCW_FINAL
+    {F_CCW_NEXT, F_CCW_FINAL, F_CCW_BEGIN, R_START},           // F_CCW_NEXT
+};
+
+static uint8_t _process(rotary_encoder_info_t * info)
+{
+    uint8_t event = 0;
+    if (info != NULL)
+    {
+        // Get state of input pins.
+        uint8_t pin_state = (gpio_get_level(info->pin_b) << 1) | gpio_get_level(info->pin_a);
+
+        // Determine new state from the pins and state table.
+#ifdef ROTARY_ENCODER_DEBUG
+        uint8_t old_state = info->table_state;
+#endif
+        info->table_state = info->table[info->table_state & 0xf][pin_state];
+
+        // Return emit bits, i.e. the generated event.
+        event = info->table_state & 0x30;
+#ifdef ROTARY_ENCODER_DEBUG
+        ESP_EARLY_LOGD(TAG, "BA %d%d, state 0x%02x, new state 0x%02x, event 0x%02x",
+                       pin_state >> 1, pin_state & 1, old_state, info->table_state, event);
+#endif
+    }
+    return event;
+}
+
+static void _isr_rotenc(void * args)
+{
+    rotary_encoder_info_t * info = (rotary_encoder_info_t *)args;
+    uint8_t event = _process(info);
+    bool send_event = false;
+
+    switch (event)
+    {
+    case DIR_CW:
+        ++info->state.position;
+        info->state.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE;
+        send_event = true;
+        break;
+    case DIR_CCW:
+        --info->state.position;
+        info->state.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE;
+        send_event = true;
+        break;
+    default:
+        break;
+    }
+
+    if (send_event && info->queue)
+    {
+        rotary_encoder_event_t queue_event =
+        {
+            .state =
+            {
+                .position = info->state.position,
+                .direction = info->state.direction,
+            },
+        };
+        BaseType_t task_woken = pdFALSE;
+        xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
+        if (task_woken)
+        {
+            portYIELD_FROM_ISR();
+        }
+    }
+}
+
+esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b)
+{
+    esp_err_t err = ESP_OK;
+    if (info)
+    {
+        info->pin_a = pin_a;
+        info->pin_b = pin_b;
+        info->table = &_ttable_full[0];   //enable_half_step ? &_ttable_half[0] : &_ttable_full[0];
+        info->table_state = R_START;
+        info->state.position = 0;
+        info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
+
+        // configure GPIOs
+        gpio_pad_select_gpio(info->pin_a);
+        gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY);
+        gpio_set_direction(info->pin_a, GPIO_MODE_INPUT);
+        gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE);
+
+        gpio_pad_select_gpio(info->pin_b);
+        gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY);
+        gpio_set_direction(info->pin_b, GPIO_MODE_INPUT);
+        gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE);
+
+        // install interrupt handlers
+        gpio_isr_handler_add(info->pin_a, _isr_rotenc, info);
+        gpio_isr_handler_add(info->pin_b, _isr_rotenc, info);
+    }
+    else
+    {
+        ESP_LOGE(TAG, "info is NULL");
+        err = ESP_ERR_INVALID_ARG;
+    }
+    return err;
+}
+
+esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable)
+{
+    esp_err_t err = ESP_OK;
+    if (info)
+    {
+        info->table = enable ? &_ttable_half[0] : &_ttable_full[0];
+        info->table_state = R_START;
+    }
+    else
+    {
+        ESP_LOGE(TAG, "info is NULL");
+        err = ESP_ERR_INVALID_ARG;
+    }
+    return err;
+}
+
+esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info)
+{
+    esp_err_t err = ESP_OK;
+    if (info)
+    {
+        gpio_num_t temp = info->pin_a;
+        info->pin_a = info->pin_b;
+        info->pin_b = temp;
+    }
+    else
+    {
+        ESP_LOGE(TAG, "info is NULL");
+        err = ESP_ERR_INVALID_ARG;
+    }
+    return err;
+}
+
+esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info)
+{
+    esp_err_t err = ESP_OK;
+    if (info)
+    {
+        gpio_isr_handler_remove(info->pin_a);
+        gpio_isr_handler_remove(info->pin_b);
+    }
+    else
+    {
+        ESP_LOGE(TAG, "info is NULL");
+        err = ESP_ERR_INVALID_ARG;
+    }
+    return err;
+}
+
+QueueHandle_t rotary_encoder_create_queue(void)
+{
+    return xQueueCreate(EVENT_QUEUE_LENGTH, sizeof(rotary_encoder_event_t));
+}
+
+esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue)
+{
+    esp_err_t err = ESP_OK;
+    if (info)
+    {
+        info->queue = queue;
+    }
+    else
+    {
+        ESP_LOGE(TAG, "info is NULL");
+        err = ESP_ERR_INVALID_ARG;
+    }
+    return err;
+}
+
+esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state)
+{
+    esp_err_t err = ESP_OK;
+    if (info && state)
+    {
+        // make a snapshot of the state
+        state->position = info->state.position;
+        state->direction = info->state.direction;
+    }
+    else
+    {
+        ESP_LOGE(TAG, "info and/or state is NULL");
+        err = ESP_ERR_INVALID_ARG;
+    }
+    return err;
+}
+
+esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info)
+{
+    esp_err_t err = ESP_OK;
+    if (info)
+    {
+        info->state.position = 0;
+        info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
+    }
+    else
+    {
+        ESP_LOGE(TAG, "info is NULL");
+        err = ESP_ERR_INVALID_ARG;
+    }
+    return err;
+}

+ 172 - 0
components/services/rotary_encoder.h

@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2019 David Antliff
+ * Copyright 2011 Ben Buxton
+ *
+ * This file is part of the esp32-rotary-encoder component.
+ *
+ * esp32-rotary-encoder 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.
+ *
+ * esp32-rotary-encoder 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 esp32-rotary-encoder.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file rotary_encoder.h
+ * @brief Interface definitions for the ESP32-compatible Incremental Rotary Encoder component.
+ *
+ * This component provides a means to interface with a typical rotary encoder such as the EC11 or LPD3806.
+ * These encoders produce a quadrature signal on two outputs, which can be used to track the position and
+ * direction as movement occurs.
+ *
+ * This component provides functions to initialise the GPIOs and install appropriate interrupt handlers to
+ * track a single device's position. An event queue is used to provide a way for a user task to obtain
+ * position information from the component as it is generated.
+ *
+ * Note that the queue is of length 1, and old values will be overwritten. Using a longer queue is
+ * possible with some minor modifications however newer values are lost if the queue overruns. A circular
+ * buffer where old values are lost would be better (maybe StreamBuffer in FreeRTOS 10.0.0?).
+ */
+
+#ifndef ROTARY_ENCODER_H
+#define ROTARY_ENCODER_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
+#include "esp_err.h"
+#include "driver/gpio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int32_t rotary_encoder_position_t;
+
+/**
+ * @brief Enum representing the direction of rotation.
+ */
+typedef enum
+{
+    ROTARY_ENCODER_DIRECTION_NOT_SET = 0,        ///< Direction not yet known (stationary since reset)
+    ROTARY_ENCODER_DIRECTION_CLOCKWISE,
+    ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE,
+} rotary_encoder_direction_t;
+
+// Used internally
+///@cond INTERNAL
+#define TABLE_COLS 4
+typedef uint8_t table_row_t[TABLE_COLS];
+///@endcond
+
+/**
+ * @brief Struct represents the current state of the device in terms of incremental position and direction of last movement
+ */
+typedef struct
+{
+    rotary_encoder_position_t position;    ///< Numerical position since reset. This value increments on clockwise rotation, and decrements on counter-clockewise rotation. Counts full or half steps depending on mode. Set to zero on reset.
+    rotary_encoder_direction_t direction;  ///< Direction of last movement. Set to NOT_SET on reset.
+} rotary_encoder_state_t;
+
+/**
+ * @brief Struct carries all the information needed by this driver to manage the rotary encoder device.
+ *        The fields of this structure should not be accessed directly.
+ */
+typedef struct
+{
+    gpio_num_t pin_a;                       ///< GPIO for Signal A from the rotary encoder device
+    gpio_num_t pin_b;                       ///< GPIO for Signal B from the rotary encoder device
+    QueueHandle_t queue;                    ///< Handle for event queue, created by ::rotary_encoder_create_queue
+    const table_row_t * table;              ///< Pointer to active state transition table
+    uint8_t table_state;                    ///< Internal state
+    volatile rotary_encoder_state_t state;  ///< Device state
+} rotary_encoder_info_t;
+
+/**
+ * @brief Struct represents a queued event, used to communicate current position to a waiting task
+ */
+typedef struct
+{
+    rotary_encoder_state_t state;  ///< The device state corresponding to this event
+} rotary_encoder_event_t;
+
+/**
+ * @brief Initialise the rotary encoder device with the specified GPIO pins and full step increments.
+ *        This function will set up the GPIOs as needed,
+ *        Note: this function assumes that gpio_install_isr_service(0) has already been called.
+ * @param[in, out] info Pointer to allocated rotary encoder info structure.
+ * @param[in] pin_a GPIO number for rotary encoder output A.
+ * @param[in] pin_b GPIO number for rotary encoder output B.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b);
+
+/**
+ * @brief Enable half-stepping mode. This generates twice as many counted steps per rotation.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @param[in] enable If true, count half steps. If false, only count full steps.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable);
+
+/**
+ * @brief Reverse (flip) the sense of the direction.
+ *        Use this if clockwise/counterclockwise are not what you expect.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info);
+
+/**
+ * @brief Remove the interrupt handlers installed by ::rotary_encoder_init.
+ *        Note: GPIOs will be left in the state they were configured by ::rotary_encoder_init.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info);
+
+/**
+ * @brief Create a queue handle suitable for use as an event queue.
+ * @return A handle to a new queue suitable for use as an event queue.
+ */
+QueueHandle_t rotary_encoder_create_queue(void);
+
+/**
+ * @brief Set the driver to use the specified queue as an event queue.
+ *        It is recommended that a queue constructed by ::rotary_encoder_create_queue is used.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @param[in] queue Handle to queue suitable for use as an event queue. See ::rotary_encoder_create_queue.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue);
+
+/**
+ * @brief Get the current position of the rotary encoder.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @param[in, out] state Pointer to an allocated rotary_encoder_state_t struct that will
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state);
+
+/**
+ * @brief Reset the current position of the rotary encoder to zero.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ROTARY_ENCODER_H

+ 1 - 2
components/squeezelite/a1s/ac101.c

@@ -29,9 +29,8 @@
 #include <freertos/FreeRTOS.h>
 #include <freertos/task.h>
 #include <driver/i2c.h>
+#include <driver/i2s.h>
 #include "adac.h"
-
-//#include "audio_hal.h"
 #include "ac101.h"
 
 const static char TAG[] = "AC101";

+ 10 - 2
components/squeezelite/controls.c

@@ -85,7 +85,15 @@ static void lms_right(void) {
 	cli_send_cmd("button arrow_right");
 }
 
-static void lms_push(void) {
+static void lms_knob_left(void) {
+	cli_send_cmd("button knob_left");
+}
+
+static void lms_knob_right(void) {
+	cli_send_cmd("button knob_right");
+}
+
+static void lms_knob_push(void) {
 	cli_send_cmd("button knob_push");
 }
 
@@ -95,9 +103,9 @@ const actrls_t LMS_controls = {
 	lms_pause, lms_stop,	// pause, stop
 	lms_rew, lms_fwd,		// rew, fwd
 	lms_prev, lms_next,		// prev, next
-	lms_push, 
 	lms_up, lms_down,
 	lms_left, lms_right, 
+	lms_knob_left, lms_knob_right, lms_knob_push,
 };
 
 /****************************************************************************************

+ 36 - 1
components/squeezelite/null/dac_null.c

@@ -19,9 +19,13 @@
  *
  */
  
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <driver/i2s.h>
+#include "config.h"
 #include "adac.h"
 
-static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config) { return true; };
+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) { } ;
@@ -30,3 +34,34 @@ static void power(adac_power_e mode) { };
 
 struct adac_s dac_null = { init, deinit, power, speaker, headset, volume };
 
+static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config) { 
+#if !defined(CONFIG_SQUEEZEAMP) && !defined(CONFIG_A1S)
+	i2s_pin_config_t i2s_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 	};
+	char *nvs_item = config_alloc_get(NVS_TYPE_STR, "dac_config");
+	
+	if (nvs_item) {
+		if ((p = strcasestr(nvs_item, "bck")) != NULL) i2s_pin_config.bck_io_num = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(nvs_item, "ws")) != NULL) i2s_pin_config.ws_io_num = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(nvs_item, "do")) != NULL) i2s_pin_config.data_out_num = atoi(strchr(p, '=') + 1);
+		free(nvs_item);
+	} 
+	
+	if (i2s_pin_config.bck_io_num != -1 && i2s_pin_config.ws_io_num != -1 && i2s_pin_config.data_out_num != -1) {
+		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;
+	} else {
+		LOG_WARN("Cannot initialize I2S for SPDIF bck:%d ws:%d do:%d", i2s_pin_config.bck_io_num, 
+																		   i2s_pin_config.ws_io_num, 
+																		   i2s_pin_config.data_out_num);
+		return false;
+	}
+#else
+	return true;
+#endif	
+}
+

+ 25 - 14
components/squeezelite/output_i2s.c

@@ -114,19 +114,11 @@ static void (*jack_handler_chain)(bool inserted);
 
 // force all GPIOs to what we need
 #ifdef CONFIG_SQUEEZEAMP
-#define TAS57xx
-#undef	CONFIG_SPDIF_BCK_IO 
-#define CONFIG_SPDIF_BCK_IO 33
-#undef 	CONFIG_SPDIF_WS_IO	
-#define CONFIG_SPDIF_WS_IO	25
-#undef 	CONFIG_SPDIF_DO_IO
-#define CONFIG_SPDIF_DO_IO	15
-#undef 	CONFIG_SPDIF_NUM
-#define CONFIG_SPDIF_NUM	0
 #undef 	CONFIG_I2S_NUM
 #define CONFIG_I2S_NUM		0
+#undef	CONFIG_SPDIF_DO_IO
+#define	CONFIG_SPDIF_DO_IO	15
 #elif defined CONFIG_A1S
-#define A1S
 #undef 	CONFIG_I2S_NUM
 #define CONFIG_I2S_NUM		0
 #endif
@@ -221,9 +213,28 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 
 	if (strcasestr(device, "spdif")) {
 		spdif = true;	
+		
+#ifdef CONFIG_SQUEEZEAMP
+		i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = 33, .ws_io_num = 25, 
+															  .data_out_num = CONFIG_SPDIF_DO_IO, .data_in_num = -1 };
+#else
 		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
-									};
+															  .data_out_num = CONFIG_SPDIF_DO_IO, .data_in_num = -1 };
+		char *nvs_item = config_alloc_get(NVS_TYPE_STR, "spdif_config");
+		if (nvs_item) {
+			if ((p = strcasestr(nvs_item, "bck")) != NULL) i2s_pin_config.bck_io_num = atoi(strchr(p, '=') + 1);
+			if ((p = strcasestr(nvs_item, "ws")) != NULL) i2s_pin_config.ws_io_num = atoi(strchr(p, '=') + 1);
+			if ((p = strcasestr(nvs_item, "do")) != NULL) i2s_pin_config.data_out_num = atoi(strchr(p, '=') + 1);
+			free(nvs_item);
+		} 
+		
+		if (i2s_pin_config.bck_io_num == -1 || i2s_pin_config.ws_io_num == -1 || i2s_pin_config.data_out_num == -1) {
+			LOG_WARN("Cannot initialize I2S for SPDIF bck:%d ws:%d do:%d", i2s_pin_config.bck_io_num, 
+																		   i2s_pin_config.ws_io_num, 
+																		   i2s_pin_config.data_out_num);
+		}
+#endif	
+									
 		i2s_config.sample_rate = output.current_sample_rate * 2;
 		i2s_config.bits_per_sample = 32;
 		// Normally counted in frames, but 16 sample are transformed into 32 bits in spdif
@@ -239,12 +250,12 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 		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 {
-#ifdef TAS57xx
+#ifdef CONFIG_SQUEEZEAMP
 		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)
+#elif defined(CONFIG_A1S)
 		adac = &dac_a1s;
 #endif	
 		i2s_config.sample_rate = output.current_sample_rate;

+ 1 - 0
components/wifi-manager/index.html

@@ -381,6 +381,7 @@
                             <li>SpinKit, &copy;  2015, Tobias Ahlin. Licensed under the MIT License.</li>
                             <li>jQuery, The jQuery Foundation. Licensed under the MIT License.</li>
                             <li>cJSON, &copy; 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.</li>
+							<li>esp32-rotary-encoder, &copy; 2011-2019, David Antliff and Ben Buxton. Licensed under the GPL License.</li>
                         </ul>
                     </div>
 	                <h2>Show NVS Editor</h2>

+ 5 - 1
main/Kconfig.projbuild

@@ -207,7 +207,11 @@ menu "Squeezelite-ESP32"
 			int "Jack insertion GPIO"
 			default -1
 			help
-				GPIO to detect speaker jack insertion (0 = inserted). Set to -1 for no detection
+				GPIO to detect speaker jack insertion. Set to -1 for no detection
+		config JACK_GPIO_LEVEL
+			depends on JACK_GPIO != -1
+			int "Level when inserted (0/1)"
+		        default 0
 	endmenu	
 	
 endmenu

+ 12 - 6
main/esp_app_main.c

@@ -280,6 +280,9 @@ void register_default_nvs(){
 
 	ESP_LOGD(TAG,"Registering default Audio control board type %s, value ","actrls_config");
 	config_set_default(NVS_TYPE_STR, "actrls_config", "", 0);
+	
+	ESP_LOGD(TAG,"Registering default Audio control board type %s, value ","rotary_config");
+	config_set_default(NVS_TYPE_STR, "rotary_config", "", 0);
 
 	char number_buffer[101] = {};
 	snprintf(number_buffer,sizeof(number_buffer)-1,"%u",OTA_FLASH_ERASE_BLOCK);
@@ -324,6 +327,12 @@ void register_default_nvs(){
 	ESP_LOGD(TAG,"Registering default value for key %s", "stats");
 	config_set_default(NVS_TYPE_STR, "stats", "n", 0);
 	
+	ESP_LOGD(TAG,"Registering default value for key %s", "spdif_config");
+	config_set_default(NVS_TYPE_STR, "spdif_config", "", 0);
+	
+	ESP_LOGD(TAG,"Registering default value for key %s", "dac_config");
+	config_set_default(NVS_TYPE_STR, "dac_config", "", 0);
+	
 	ESP_LOGD(TAG,"Done setting default values in nvs.");
 }
 
@@ -374,15 +383,12 @@ void app_main()
 
 	ESP_LOGD(TAG,"Getting audio control mapping ");
 	char *actrls_config = config_alloc_get_default(NVS_TYPE_STR, "actrls_config", NULL, 0);
-	if (actrls_config) {
-		if(actrls_config[0] !='\0'){
-			ESP_LOGD(TAG,"Initializing audio control buttons type %s", actrls_config);
-			actrls_init_json(actrls_config, true);
-		}
-		free(actrls_config);
+	if (actrls_init_json(actrls_config, true) == ESP_OK) {
+		ESP_LOGD(TAG,"Initializing audio control buttons type %s", actrls_config);	
 	} else {
 		ESP_LOGD(TAG,"No audio control buttons");
 	}
+	if (actrls_config) free(actrls_config);
 
 	/* start the wifi manager */
 	ESP_LOGD(TAG,"Blinking led");