Ver Fonte

initial work on a wifi/http configuration module

sle118 há 5 anos atrás
pai
commit
6e7793a756
63 ficheiros alterados com 4063 adições e 396 exclusões
  1. 7 3
      .cproject
  2. 0 1
      .gitignore
  3. 9 0
      .gitmodules
  4. 0 2
      Makefile
  5. 0 9
      README.md
  6. 1 1
      components/cmd_nvs/cmd_nvs.c
  7. 31 0
      components/cmd_system/cmd_system.c
  8. 1 0
      components/cmd_system/cmd_system.h
  9. 5 31
      components/driver_bt/bt_app_sink.c
  10. 1 14
      components/driver_bt/bt_app_sink.h
  11. 1 2
      components/driver_bt/component.mk
  12. 0 1
      components/io/led.c
  13. 2 2
      components/io/led.h
  14. 0 6
      components/squeezelite/buffer.c
  15. 1 2
      components/squeezelite/component.mk
  16. 1 5
      components/squeezelite/decode.c
  17. 141 0
      components/squeezelite/decode_bt.c
  18. 7 11
      components/squeezelite/embedded.h
  19. 1 1
      components/squeezelite/opus.c
  20. 1 2
      components/squeezelite/output.c
  21. 4 14
      components/squeezelite/output_bt.c
  22. 31 61
      components/squeezelite/output_i2s.c
  23. 4 5
      components/squeezelite/slimproto.c
  24. 1 5
      components/squeezelite/squeezelite.h
  25. 11 0
      components/wifi-manager/CMakeLists.txt
  26. 67 0
      components/wifi-manager/Kconfig.projbuild
  27. 19 0
      components/wifi-manager/LICENSE.md
  28. 41 0
      components/wifi-manager/README.md
  29. 12 0
      components/wifi-manager/ap.json
  30. 454 0
      components/wifi-manager/code.js
  31. 11 0
      components/wifi-manager/component.mk
  32. 2 0
      components/wifi-manager/compress.bat
  33. 2 0
      components/wifi-manager/connect
  34. 184 0
      components/wifi-manager/dns_server.c
  35. 140 0
      components/wifi-manager/dns_server.h
  36. 367 0
      components/wifi-manager/http_server.c
  37. 95 0
      components/wifi-manager/http_server.h
  38. 310 0
      components/wifi-manager/index.html
  39. BIN
      components/wifi-manager/jquery.gz
  40. 1 0
      components/wifi-manager/jquery.js
  41. 144 0
      components/wifi-manager/json.c
  42. 47 0
      components/wifi-manager/json.h
  43. BIN
      components/wifi-manager/lock.png
  44. 29 0
      components/wifi-manager/main.c.txt
  45. BIN
      components/wifi-manager/settings.png
  46. 1 0
      components/wifi-manager/status
  47. 250 0
      components/wifi-manager/style.css
  48. BIN
      components/wifi-manager/wifi0.png
  49. BIN
      components/wifi-manager/wifi1.png
  50. BIN
      components/wifi-manager/wifi2.png
  51. BIN
      components/wifi-manager/wifi24.png
  52. BIN
      components/wifi-manager/wifi3.png
  53. 1138 0
      components/wifi-manager/wifi_manager.c
  54. 397 0
      components/wifi-manager/wifi_manager.h
  55. 2 2
      main/CMakeLists.txt
  56. 3 15
      main/Kconfig.projbuild
  57. 4 3
      main/cmd_squeezelite.c
  58. 1 180
      main/cmd_wifi.c
  59. 1 2
      main/cmd_wifi.h
  60. 6 5
      main/console.c
  61. 65 0
      main/esp_app_main.c
  62. 7 7
      partitions.csv
  63. 2 4
      sdkconfig.defaults

+ 7 - 3
.cproject

@@ -14,13 +14,13 @@
 				</extensions>
 			</storageModule>
 			<storageModule moduleId="cdtBuildSystem" version="4.0.0">
-				<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cross.base.1476804786" name="Default" optionalBuildProperties="org.eclipse.cdt.docker.launcher.containerbuild.property.volumes=,org.eclipse.cdt.docker.launcher.containerbuild.property.selectedvolumes=" parent="org.eclipse.cdt.build.core.emptycfg">
+				<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cross.base.1476804786" name="Default" optionalBuildProperties="org.eclipse.cdt.docker.launcher.containerbuild.property.selectedvolumes=,org.eclipse.cdt.docker.launcher.containerbuild.property.volumes=" parent="org.eclipse.cdt.build.core.emptycfg">
 					<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.1476804786.1800826258" name="/" resourcePath="">
 						<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.811827721" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
 							<option id="cdt.managedbuild.option.gnu.cross.prefix.1666584715" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
 							<option id="cdt.managedbuild.option.gnu.cross.path.144124148" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
 							<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1562292378" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
-							<builder id="cdt.managedbuild.builder.gnu.cross.1011968237" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
+							<builder id="cdt.managedbuild.builder.gnu.cross.1011968237" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="cdt.managedbuild.builder.gnu.cross"/>
 							<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.1502936757" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
 								<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1614739014" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
 							</tool>
@@ -64,7 +64,11 @@
 			<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
 		</scannerConfigBuildInfo>
 	</storageModule>
-	<storageModule moduleId="refreshScope"/>
+	<storageModule moduleId="refreshScope" versionNumber="2">
+		<configuration configurationName="Default">
+			<resource resourceType="PROJECT" workspacePath="/squeezelite-esp32"/>
+		</configuration>
+	</storageModule>
 	<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
 		<buildTargets>
 			<target name="all" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">

+ 0 - 1
.gitignore

@@ -66,4 +66,3 @@ libs/
 /cdump.cmd
 /_*
 sdkconfig
-*_history/

+ 9 - 0
.gitmodules

@@ -0,0 +1,9 @@
+[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

+ 0 - 2
Makefile

@@ -4,6 +4,4 @@
 #
 
 PROJECT_NAME := squeezelite
-
 include $(IDF_PATH)/make/project.mk
-

+ 0 - 9
README.md

@@ -1,6 +1,3 @@
-TODO
-- when IP changes, best is to reboot at this point
-
 MOST IMPORTANT: create the right default config file
 - make defconfig
 Then adapt the config file to your wifi/BT/I2C device (can alos be done on the command line)
@@ -23,11 +20,6 @@ nvs_set autoexec2 str -v "squeezelite -o I2S -b 500:2000 -d all=info -m ESP32"
 
 nvs_set autoexec u8 -v 1		
 
-4/ set bluetooth & airplaysink name (if not set in menuconfig)
-
-nvs_set bt_sink_name str -v "<name>"
-nvs_set airplay_sink_name str -v "<name>"
-
 The "join" and "squeezelite" commands can also be typed at the prompt to start manually. Use "help" to see the list.
 
 The squeezelite options are very similar to the regular Linux ones. Differences are :
@@ -43,7 +35,6 @@ To add options that require quotes ("), escape them with \". For example, so use
 nvs_set autoexec2 str -v "squeezelite -o \"BT -n 'MySpeaker'\" -b 500:2000 -R -u m -Z 192000 -r \"44100-44100\""
 
 # Additional misc notes to do you build
-- as of this writing, ESP-IDF has a bug int he way the PLL values are calculated for i2s, so you *must* use the i2s.c file in the patch directory
 - for all libraries, add -mlongcalls. 
 - audio libraries are complicated to rebuild, open an issue if you really want to
 - libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work

+ 1 - 1
components/cmd_nvs/cmd_nvs.c

@@ -47,7 +47,7 @@ static const type_str_pair_t type_str_pair[] = {
 
 static const size_t TYPE_STR_PAIR_SIZE = sizeof(type_str_pair) / sizeof(type_str_pair[0]);
 static const char *ARG_TYPE_STR = "type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob";
-char current_namespace[16] = "storage";
+char current_namespace[16] = "espwifimgr";
 static const char * TAG = "platform_esp32";
 
 static struct {

+ 31 - 0
components/cmd_system/cmd_system.c

@@ -28,6 +28,12 @@
 #ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
 #define WITH_TASKS_INFO 1
 #endif
+#define LWS_MAGIC_REBOOT_TYPE_ADS 0x50001ffc
+#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY 0xb00bcafe
+#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY 0xfaceb00b
+#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON 0xf0cedfac
+#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY_ERASE_OTA 0xfac0eeee
+
 
 static const char * TAG = "platform_esp32";
 
@@ -37,6 +43,7 @@ static void register_version();
 static void register_restart();
 static void register_deep_sleep();
 static void register_light_sleep();
+static void register_factory_boot();
 #if WITH_TASKS_INFO
 static void register_tasks();
 #endif
@@ -49,6 +56,7 @@ void register_system()
     register_restart();
     register_deep_sleep();
     register_light_sleep();
+    register_factory_boot();
 #if WITH_TASKS_INFO
     register_tasks();
 #endif
@@ -91,7 +99,20 @@ static int restart(int argc, char **argv)
     ESP_LOGI(TAG, "Restarting");
     esp_restart();
 }
+void guided_factory()
+{
+    ESP_LOGI(TAG, "Rebooting to factory.");
+    uint32_t *p_force_factory_magic = (uint32_t *)LWS_MAGIC_REBOOT_TYPE_ADS;
+	*p_force_factory_magic = LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY;
+
+	esp_restart();
 
+}
+static int restart_factory(int argc, char **argv)
+{
+	guided_factory();
+	return 1;
+}
 static void register_restart()
 {
     const esp_console_cmd_t cmd = {
@@ -103,6 +124,16 @@ static void register_restart()
     ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
 }
 
+static void register_factory_boot()
+{
+    const esp_console_cmd_t cmd = {
+        .command = "factory",
+        .help = "Resets and boot to factory (if available)",
+        .hint = NULL,
+        .func = &restart_factory,
+    };
+    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
+}
 /** 'free' command prints available heap memory */
 
 static int free_mem(int argc, char **argv)

+ 1 - 0
components/cmd_system/cmd_system.h

@@ -14,6 +14,7 @@ extern "C" {
 
 // Register system functions
 void register_system();
+void guided_factory();
 
 #ifdef __cplusplus
 }

+ 5 - 31
components/driver_bt/bt_app_sink.c

@@ -21,7 +21,6 @@
 #include "esp_gap_bt_api.h"
 #include "esp_a2dp_api.h"
 #include "esp_avrc_api.h"
-#include "nvs.h"
 
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
@@ -40,11 +39,9 @@
 #define BT_RC_CT_TAG            "RCCT"
 
 #ifndef CONFIG_BT_SINK_NAME
-#define CONFIG_BT_SINK_NAME	"default"
+#define CONFIG_BT_SINK_NAME	"unavailable"
 #endif
 
-extern char current_namespace[];
-
 /* event for handler "bt_av_hdl_stack_up */
 enum {
     BT_APP_EVT_STACK_UP = 0,
@@ -355,7 +352,7 @@ static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
     }
 }
 
-void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb)
+void bt_sink_init(void (*cmd_cb)(bt_sink_cmd_t cmd, ...), void (*data_cb)(const uint8_t *data, uint32_t len))
 {
 	esp_err_t err;
 	
@@ -412,21 +409,6 @@ void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb)
 
 }
 
-void bt_sink_deinit(void)
-{
-	/* this still does not work, can't figure out how to stop properly this BT stack */
-	bt_app_task_shut_down();
-	ESP_LOGI(BT_AV_TAG, "bt_app_task shutdown successfully");	
-	if (esp_bluedroid_disable() != ESP_OK) return;
-    ESP_LOGI(BT_AV_TAG, "esp_bluedroid_disable called successfully");
-    if (esp_bluedroid_deinit() != ESP_OK) return;
-    ESP_LOGI(BT_AV_TAG, "esp_bluedroid_deinit called successfully");
-    if (esp_bt_controller_disable() != ESP_OK) return;
-    ESP_LOGI(BT_AV_TAG, "esp_bt_controller_disable called successfully");
-    if (esp_bt_controller_deinit() != ESP_OK) return;
-	ESP_LOGI(BT_AV_TAG, "bt stopped successfully");
-}
-
 static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
 {
     switch (event) {
@@ -467,17 +449,9 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
     switch (event) {
     case BT_APP_EVT_STACK_UP: {
         /* set up device name */
-		nvs_handle nvs;
-        char dev_name[32] = CONFIG_BT_SINK_NAME;
-				
-		if (nvs_open(current_namespace, NVS_READONLY, &nvs) == ESP_OK) {
-			size_t len = 31;
-			nvs_get_str(nvs, "bt_sink_name", dev_name, &len);
-			nvs_close(nvs);
-		}	
-				
-		esp_bt_dev_set_device_name(dev_name);
-		
+        char *dev_name = CONFIG_BT_SINK_NAME;
+        esp_bt_dev_set_device_name(dev_name);
+
         esp_bt_gap_register_callback(bt_app_gap_cb);
 
         /* initialize AVRCP controller */

+ 1 - 14
components/driver_bt/bt_app_sink.h

@@ -13,23 +13,10 @@
 
 typedef enum { 	BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE, 
 				BT_SINK_RATE, BT_SINK_VOLUME,  } bt_sink_cmd_t;
-				
-typedef void (*bt_cmd_cb_t)(bt_sink_cmd_t cmd, ...);
-typedef void (*bt_data_cb_t)(const uint8_t *data, uint32_t len);
 
 /**
  * @brief     init sink mode (need to be provided)
  */
-void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb);
-
-/**
- * @brief     deinit sink mode (need to be provided)
- */
-void bt_sink_deinit(void);
-
-/**
- * @brief     local command mode (stop, play, volume ...)
- */
-void bt_sink_cmd(bt_sink_cmd_t event, ...);
+void bt_sink_init(void (*cmd_cb)(bt_sink_cmd_t cmd, ...), void (*data_cb)(const uint8_t *data, uint32_t len));
 
 #endif /* __BT_APP_SINK_H__*/

+ 1 - 2
components/driver_bt/component.mk

@@ -7,6 +7,5 @@
 # please read the SDK documents if you need to do this.
 #
 
-CFLAGS += 	-I$(COMPONENT_PATH)/../tools
-			
+CFLAGS += -I$(COMPONENT_PATH)/../tools
 #CFLAGS += -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG

+ 0 - 1
components/io/led.c

@@ -109,7 +109,6 @@ bool led_unconfig(int idx) {
 	if (idx >= MAX_LED) return false;	
 	
 	if (leds[idx].timer) xTimerDelete(leds[idx].timer, BLOCKTIME);
-	leds[idx].timer = NULL;
 	
 	return true;
 }

+ 2 - 2
components/io/led.h

@@ -20,7 +20,7 @@
  */
  
 #ifndef LED_H
-
+#define LED_H
 #include "driver/gpio.h"
 
 enum { LED_GREEN = 0, LED_RED };
@@ -35,4 +35,4 @@ bool led_unconfig(int idx);
 bool led_blink_core(int idx, int ontime, int offtime, bool push);
 bool led_unpush(int idx);
 
-#endif
+#endif

+ 0 - 6
components/squeezelite/buffer.c

@@ -64,11 +64,6 @@ void buf_flush(struct buffer *buf) {
 	mutex_unlock(buf->mutex);
 }
 
-void _buf_flush(struct buffer *buf) {
-	buf->readp  = buf->buf;
-	buf->writep = buf->buf;
-}
-
 // adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
 void buf_adjust(struct buffer *buf, size_t mod) {
 	size_t size;
@@ -83,7 +78,6 @@ void buf_adjust(struct buffer *buf, size_t mod) {
 
 // called with mutex locked to resize, does not retain contents, reverts to original size if fails
 void _buf_resize(struct buffer *buf, size_t size) {
-	if (size == buf->size) return;
 	free(buf->buf);
 	buf->buf = malloc(size);
 	if (!buf->buf) {

+ 1 - 2
components/squeezelite/component.mk

@@ -13,8 +13,7 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON
 	-I$(COMPONENT_PATH)/../tools				\
 	-I$(COMPONENT_PATH)/../codecs/inc/opus 		\
 	-I$(COMPONENT_PATH)/../codecs/inc/opusfile	\
-	-I$(COMPONENT_PATH)/../driver_bt			\
-	-I$(COMPONENT_PATH)/../raop
+	-I$(COMPONENT_PATH)/../driver_bt
 
 #	-I$(COMPONENT_PATH)/../codecs/inc/faad2
 

+ 1 - 5
components/squeezelite/decode.c

@@ -116,10 +116,6 @@ static void *decode_thread() {
 			usleep(100000);
 		}
 	}
-	
-#if EMBEDDED	
-	deregister_external();
-#endif	
 
 	return 0;
 }
@@ -204,7 +200,7 @@ void decode_init(log_level level, const char *include_codecs, const char *exclud
 		sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mpg());
 		
 #if EMBEDDED
-	register_external();
+	register_other();
 #endif 	
 
 	LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs);

+ 141 - 0
components/squeezelite/decode_bt.c

@@ -0,0 +1,141 @@
+/* 
+ *  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 "bt_app_sink.h"
+
+#define LOCK_O   mutex_lock(outputbuf->mutex)
+#define UNLOCK_O mutex_unlock(outputbuf->mutex)
+#define LOCK_D   mutex_lock(decode.mutex);
+#define UNLOCK_D mutex_unlock(decode.mutex);
+
+extern struct outputstate output;
+extern struct decodestate decode;
+extern struct buffer *outputbuf;
+// this is the only system-wide loglevel variable
+extern log_level loglevel;
+
+/****************************************************************************************
+ * BT sink data handler
+ */
+static void bt_sink_data_handler(const uint8_t *data, uint32_t len)
+{
+    size_t bytes;
+	
+	// would be better to lock decoder, but really, it does not matter
+	if (decode.state != DECODE_STOPPED) {
+		LOG_WARN("Cannot use BT sink while LMS is controlling player");
+		return;
+	} 
+	
+	// there will always be room at some point
+	while (len) {
+		LOCK_O;
+		
+		bytes = min(len, _buf_cont_write(outputbuf));
+#if BYTES_PER_FRAME == 4
+		memcpy(outputbuf->writep, data, bytes);
+#else
+		{
+			s16_t *iptr = (s16_t*) data;
+			ISAMPLE_T *optr = (ISAMPLE_T*) outputbuf->writep;
+			size_t n = bytes / BYTES_PER_FRAME * 2;
+			while (n--) *optr++ = *iptr++ << 16;
+		}
+#endif	
+		_buf_inc_writep(outputbuf, bytes);
+		len -= bytes;
+		data += bytes;
+				
+		UNLOCK_O;
+		
+		// allow i2s to empty the buffer if needed
+		if (len) usleep(50000);
+	}	
+}
+
+/****************************************************************************************
+ * BT sink command handler
+ */
+static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...) 
+{
+	va_list args;
+	
+	LOCK_D;
+	if (decode.state != DECODE_STOPPED) {
+		LOG_WARN("Cannot use BT sink while LMS is controlling player");
+		UNLOCK_D;
+		return;
+	} 	
+	
+	va_start(args, cmd);
+	
+	if (cmd != BT_SINK_VOLUME) LOCK_O;
+		
+	switch(cmd) {
+	case BT_SINK_CONNECTED:
+		output.state = OUTPUT_STOPPED;
+		LOG_INFO("BT sink started");
+		break;
+	case BT_SINK_DISCONNECTED:	
+		output.state = OUTPUT_OFF;
+		LOG_INFO("BT sink stopped");
+		break;
+	case BT_SINK_PLAY:
+		output.state = OUTPUT_EXTERNAL;
+		LOG_INFO("BT sink playing");
+		break;
+	case BT_SINK_PAUSE:		
+	case BT_SINK_STOP:
+		output.state = OUTPUT_STOPPED;
+		LOG_INFO("BT sink stopped");
+		break;
+	case BT_SINK_RATE:
+		output.current_sample_rate = va_arg(args, u32_t);
+		LOG_INFO("Setting BT sample rate %u", output.current_sample_rate);
+		break;
+	case BT_SINK_VOLUME: {
+		u16_t volume = (u16_t) va_arg(args, u32_t);
+		volume *= 65536 / 128;
+		set_volume(volume, volume);
+		break;
+	}
+	}
+	
+	if (cmd != BT_SINK_VOLUME) UNLOCK_O;
+	UNLOCK_D;
+
+	va_end(args);
+}
+ 
+/****************************************************************************************
+ * We provide the generic codec register option
+ */
+void register_other(void) {
+#ifdef CONFIG_BT_SINK	
+	if (!strcasestr(output.device, "BT ")) {
+		bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler);
+		LOG_INFO("Initializing BT sink");
+	} else {
+		LOG_WARN("Cannot be a BT sink and source");
+	}	
+#endif	
+}

+ 7 - 11
components/squeezelite/embedded.h

@@ -19,10 +19,10 @@
 #define PTHREAD_STACK_MIN	256
 #endif
 
-#define STREAM_THREAD_STACK_SIZE  6 * 1024
-#define DECODE_THREAD_STACK_SIZE 16 * 1024
-#define OUTPUT_THREAD_STACK_SIZE  6 * 1024
-#define IR_THREAD_STACK_SIZE      6 * 1024
+#define STREAM_THREAD_STACK_SIZE  8 * 1024
+#define DECODE_THREAD_STACK_SIZE 20 * 1024
+#define OUTPUT_THREAD_STACK_SIZE  8 * 1024
+#define IR_THREAD_STACK_SIZE      8 * 1024
 
 //#define BASE_CAP "Model=squeezelite,AccuratePlayPoints=0,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION	
 
@@ -36,13 +36,9 @@ typedef unsigned long long u64_t;
 #define gettime_ms _gettime_ms_
 #define mutex_create_p(m) mutex_create(m)
 
-uint32_t 	_gettime_ms_(void);
-
-int			pthread_create_name(pthread_t *thread, _CONST pthread_attr_t  *attr, 
+uint32_t _gettime_ms_(void);
+int	pthread_create_name(pthread_t *thread, _CONST pthread_attr_t  *attr, 
 				   void *(*start_routine)( void * ), void *arg, char *name);
-			
-// these are here as they can be #define to nothing			
-void 		register_external(void);
-void 		deregister_external(void);
+void register_other(void);
 				   
 #endif // EMBEDDED_H

+ 1 - 1
components/squeezelite/opus.c

@@ -139,7 +139,7 @@ static decode_state opus_decompress(void) {
 		info = OP(u, head, u->of, -1);
 
 		LOCK_O;
-		output.next_sample_rate = decode_newstream(48000, output.supported_rates);
+		output.next_sample_rate = 48000;
 		IF_DSD(	output.next_fmt = PCM; )
 		output.track_start = outputbuf->writep;
 		if (output.fade_mode) _checkfade(true);

+ 1 - 2
components/squeezelite/output.c

@@ -345,9 +345,8 @@ void output_init_common(log_level level, const char *device, unsigned output_buf
 	unsigned i;
 
 	loglevel = level;
-	
+
 	output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
-	output.init_size = output_buf_size;
 	LOG_DEBUG("outputbuf size: %u", output_buf_size);
 
 	buf_init(outputbuf, output_buf_size);

+ 4 - 14
components/squeezelite/output_bt.c

@@ -19,7 +19,6 @@
  *
  */
  
-#include "driver/gpio.h"
 #include "squeezelite.h"
 #include "perf_trace.h"
 
@@ -39,12 +38,10 @@ extern u8_t *silencebuf;
 
 extern void hal_bluetooth_init(const char * options);
 extern void hal_bluetooth_stop(void);
-extern u8_t config_spdif_gpio;
 
 static log_level loglevel;
 static bool running = false;
-static uint8_t *btout;
-static frames_t oframes;
+uint8_t * btout;
 
 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,11 +65,6 @@ 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;
@@ -102,12 +94,12 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
 		}
 
 #if BYTES_PER_FRAME == 4
-		memcpy(btout + oframes * BYTES_PER_FRAME, outputbuf->readp, out_frames * BYTES_PER_FRAME);
+		memcpy(btout, outputbuf->readp, out_frames * BYTES_PER_FRAME);
 #else
 	{
 		frames_t count = out_frames;
 		s32_t *_iptr = (s32_t*) outputbuf->readp;
-		s16_t *_optr = (s16_t*) (btout + oframes * BYTES_PER_FRAME);
+		s16_t *_optr = (s16_t*) bt_optr;
 		while (count--) {
 			*_optr++ = *_iptr++ >> 16;
 			*_optr++ = *_iptr++ >> 16;
@@ -118,7 +110,7 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
 	} else {
 
 		u8_t *buf = silencebuf;
-		memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);
+		memcpy(btout, buf, out_frames * BYTES_PER_FRAME);
 	}
 
 	return (int)out_frames;
@@ -132,7 +124,6 @@ int32_t output_bt_data(uint8_t *data, int32_t len) {
 	}
 	
 	btout = data;
-	oframes = 0;
 
 	// This is how the BTC layer calculates the number of bytes to
 	// for us to send. (BTC_SBC_DEC_PCM_DATA_LEN * sizeof(OI_INT16) - availPcmBytes
@@ -152,7 +143,6 @@ int32_t output_bt_data(uint8_t *data, int32_t len) {
 	if (wanted_len > 0) {
 		SET_MIN_MAX(wanted_len, under);
 	}
-	output.frames_in_process = len-wanted_len;
 
 	UNLOCK;
 	SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);

+ 31 - 61
components/squeezelite/output_i2s.c

@@ -41,7 +41,6 @@ sure that using rate_delay would fix that
 */
 
 #include "squeezelite.h"
-#include "esp_pthread.h"
 #include "driver/i2s.h"
 #include "driver/i2c.h"
 #include "driver/gpio.h"
@@ -85,9 +84,9 @@ sure that using rate_delay would fix that
 
 typedef enum { DAC_ON = 0, DAC_OFF, DAC_POWERDOWN, DAC_VOLUME } dac_cmd_e;
 
-// must have an integer ratio with FRAME_BLOCK (see spdif comment)
+// must have an integer ratio with FRAME_BLOCK
 #define DMA_BUF_LEN		512	
-#define DMA_BUF_COUNT	12
+#define DMA_BUF_COUNT	16
 
 #define DECLARE_ALL_MIN_MAX 	\
 	DECLARE_MIN_MAX(o); 		\
@@ -116,9 +115,7 @@ 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;
 
 DECLARE_ALL_MIN_MAX;
 
@@ -154,14 +151,12 @@ static void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *cou
 #define I2C_PORT	0
 #define I2C_ADDR	0x4c
 #define VOLUME_GPIO	33
-#define JACK_GPIO	34
+#define JACK_GPIO	39
 
 struct tas575x_cmd_s {
 	u8_t reg;
 	u8_t value;
 };
-
-u8_t config_spdif_gpio = CONFIG_SPDIF_DO_IO;
 	
 static const struct tas575x_cmd_s tas575x_init_sequence[] = {
     { 0x00, 0x00 },		// select page 0
@@ -197,9 +192,9 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 #ifdef TAS575x
 	gpio_pad_select_gpio(JACK_GPIO);
 	gpio_set_direction(JACK_GPIO, GPIO_MODE_INPUT);
-			
+	
 	adc1_config_width(ADC_WIDTH_BIT_12);
-    adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_0);
+    adc1_config_channel_atten(ADC1_CHANNEL_0,ADC_ATTEN_DB_0);
     			
 	// init volume & mute
 	gpio_pad_select_gpio(VOLUME_GPIO);
@@ -271,37 +266,27 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 									};
 		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
-		i2s_config.dma_buf_len = DMA_BUF_LEN / 2;	
-		i2s_config.dma_buf_count = DMA_BUF_COUNT * 2;
-		/* 
-		   In DMA, we have room for (LEN * COUNT) frames of 32 bits samples that 
-		   we push at sample_rate * 2. Each of these peuso-frames is a single true
-		   audio frame. So the real depth is true frames is (LEN * COUNT / 2)
-		*/   
-		dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN / 2;	
 	} 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
 									};
 		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 TAS575x	
+#ifdef TAS575x		
 		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			
 	}
-
+	
 	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.dma_buf_count = DMA_BUF_COUNT;
+	// Counted in frames (but i2s allocates a buffer <= 4092 bytes)
+	i2s_config.dma_buf_len = DMA_BUF_LEN;
 	i2s_config.use_apll = true;
 	i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
 
@@ -316,21 +301,15 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 	isI2SStarted=false;
 	
 	dac_cmd(DAC_OFF);
-
-	esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
-	
-    cfg.thread_name= "output_i2s";
-    cfg.inherit_cfg = false;
-	cfg.prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1;
-    cfg.stack_size = PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE;
-    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);
+	pthread_attr_t attr;
+	pthread_attr_init(&attr);
+	pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE);
+	pthread_create_name(&thread, &attr, output_thread_i2s, NULL, "output_i2s");
+	pthread_attr_destroy(&attr);
+
+	// leave stack size to default 
+	pthread_create_name(&stats_thread, NULL, output_thread_i2s_stats, NULL, "output_i2s_sts");
 }
 
 
@@ -382,13 +361,13 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
 			_apply_gain(outputbuf, out_frames, gainL, gainR);
 		}
 			
-		memcpy(obuf + oframes * bytes_per_frame, outputbuf->readp, out_frames * bytes_per_frame);
+		memcpy(obuf, outputbuf->readp, out_frames * bytes_per_frame);
 #else
 		optr = (s32_t*) outputbuf->readp;	
 #endif		
 	} else {
 #if BYTES_PER_FRAME == 4		
-		memcpy(obuf + oframes * bytes_per_frame, silencebuf, out_frames * bytes_per_frame);
+		memcpy(obuf, silencebuf, out_frames * bytes_per_frame);
 #else		
 		optr = (s32_t*) silencebuf;
 #endif	
@@ -402,20 +381,19 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
 			dsd_invert((u32_t *) optr, out_frames);
 	)
 
-	_scale_and_pack_frames(obuf + oframes * bytes_per_frame, optr, out_frames, gainL, gainR, output.format);
+	_scale_and_pack_frames(obuf, optr, out_frames, gainL, gainR, output.format);
 #endif	
 
-	oframes += out_frames;
-
 	return out_frames;
 }
 
+
 /****************************************************************************************
  * Main output thread
  */
 static void *output_thread_i2s() {
 	size_t count = 0, bytes;
-	frames_t iframes = FRAME_BLOCK;
+	frames_t iframes = FRAME_BLOCK, oframes;
 	uint32_t timer_start = 0;
 	int discard = 0;
 	uint32_t fullness = gettime_ms();
@@ -426,14 +404,14 @@ static void *output_thread_i2s() {
 	// spdif needs 16 bytes per frame : 32 bits/sample, 2 channels, BMC encoded
 	if (spdif && (sbuf = malloc(FRAME_BLOCK * 16)) == NULL) {
 		LOG_ERROR("Cannot allocate SPDIF buffer");
-	}
+	}	
 	
 	while (running) {
 			
 		TIME_MEASUREMENT_START(timer_start);
 		
 		LOCK;
-				
+		
 		// manage led display
 		if (state != output.state) {
 			LOG_INFO("Output state is %d", output.state);
@@ -457,15 +435,12 @@ static void *output_thread_i2s() {
 			synced = false;
 		}
 		
-		oframes = 0;
 		output.updated = gettime_ms();
 		output.frames_played_dmp = output.frames_played;
-		// try to estimate how much we have consumed from the DMA buffer (calculation is incorrect at the very beginning ...)
-		output.device_frames = dma_buf_frames - ((output.updated - fullness) * output.current_sample_rate) / 1000;
-		_output_frames( iframes );
-		// oframes must be a global updated by the write callback
-		output.frames_in_process = oframes;
-						
+		// try to estimate how much we have consumed from the DMA buffer
+		output.device_frames = DMA_BUF_COUNT * DMA_BUF_LEN - ((output.updated - fullness) * output.current_sample_rate) / 1000;
+		oframes = _output_frames( iframes );
+				
 		SET_MIN_MAX_SIZED(oframes,rec,iframes);
 		SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
 		SET_MIN_MAX_SIZED(_buf_used(streambuf),s,streambuf->size);
@@ -526,7 +501,7 @@ static void *output_thread_i2s() {
 		if (bytes != oframes * bytes_per_frame) {
 			LOG_WARN("I2S DMA Overflow! available bytes: %d, I2S wrote %d bytes", oframes * bytes_per_frame, bytes);
 		}
-		
+			
 		SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),i2s_time);
 		
 	}
@@ -542,7 +517,7 @@ static void *output_thread_i2s() {
 static void *output_thread_i2s_stats() {
 	while (running) {
 #ifdef TAS575x		
-		LOG_ERROR("Jack %d Voltage %.2fV", !gpio_get_level(JACK_GPIO), adc1_get_raw(ADC1_CHANNEL_7) / 4095. * (10+174)/10. * 1.1);
+		LOG_ERROR("Jack %d Voltage %.2fV", !gpio_get_level(JACK_GPIO), adc1_get_raw(ADC1_CHANNEL_0) / 4095. * (10+169)/10. * 1.1);
 #endif		
 		LOCK;
 		output_state state = output.state;
@@ -567,11 +542,6 @@ static void *output_thread_i2s_stats() {
 			LOG_INFO("              ----------+----------+-----------+-----------+");
 			RESET_ALL_MIN_MAX;
 		}
-		LOG_INFO("Heap internal:%zu (min:%zu) external:%zu (min:%zu)", 
-					heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
-					heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
-					heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
-					heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
 		usleep(STATS_PERIOD_MS *1000);
 	}
 	return NULL;

+ 4 - 5
components/squeezelite/slimproto.c

@@ -371,8 +371,6 @@ static void process_strm(u8_t *pkt, int len) {
 			sendSTAT("STMc", 0);
 			sentSTMu = sentSTMo = sentSTMl = false;
 			LOCK_O;
-			output.external = false;
-			_buf_resize(outputbuf, output.init_size);
 			output.threshold = strm->output_threshold;
 			output.next_replay_gain = unpackN(&strm->replay_gain);
 			output.fade_mode = strm->transition_type - '0';
@@ -631,6 +629,7 @@ static void slimproto_run() {
 #endif
 			last = now;
 
+
 			LOCK_S;
 			status.stream_full = _buf_used(streambuf);
 			status.stream_size = streambuf->size;
@@ -689,7 +688,7 @@ static void slimproto_run() {
 			status.current_sample_rate = output.current_sample_rate;
 			status.updated = output.updated;
 			status.device_frames = output.device_frames;
-						
+			
 			if (output.track_started) {
 				_sendSTMs = true;
 				output.track_started = false;
@@ -704,7 +703,7 @@ static void slimproto_run() {
 			if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
 				output.state = OUTPUT_BUFFER;
 			}
-			if (!output.external && output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
+			if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
 				_decode_state == DECODE_STOPPED) {
 
 				_sendSTMu = true;
@@ -722,7 +721,7 @@ static void slimproto_run() {
 				output.state = OUTPUT_OFF;
 				LOG_DEBUG("output timeout");
 			}
-			if (!output.external && output.state == OUTPUT_RUNNING && now - status.last > 1000) {
+			if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
 				_sendSTMt = true;
 				status.last = now;
 			}

+ 1 - 5
components/squeezelite/squeezelite.h

@@ -539,7 +539,6 @@ unsigned _buf_cont_write(struct buffer *buf);
 void _buf_inc_readp(struct buffer *buf, unsigned by);
 void _buf_inc_writep(struct buffer *buf, unsigned by);
 void buf_flush(struct buffer *buf);
-void _buf_flush(struct buffer *buf);
 void buf_adjust(struct buffer *buf, size_t mod);
 void _buf_resize(struct buffer *buf, size_t size);
 void buf_init(struct buffer *buf, size_t size);
@@ -635,7 +634,7 @@ bool resample_init(char *opt);
 
 // output.c output_alsa.c output_pa.c output_pack.c
 typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING, 
-			   OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state;
+			   OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT, OUTPUT_EXTERNAL } output_state;
 
 #if DSD
 typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format;
@@ -655,8 +654,6 @@ struct outputstate {
 	output_state state;
 	output_format format;
 	const char *device;
-	bool external;
-	u32_t init_size;
 #if ALSA
 	unsigned buffer;
 	unsigned period;
@@ -676,7 +673,6 @@ struct outputstate {
 	unsigned default_sample_rate;
 	bool error_opening;
 	unsigned device_frames;
-	unsigned frames_in_process;
 	u32_t updated;
 	u32_t track_start_time;
 	u32_t current_replay_gain;

+ 11 - 0
components/wifi-manager/CMakeLists.txt

@@ -0,0 +1,11 @@
+set(COMPONENT_ADD_INCLUDEDIRS .)
+
+set(COMPONENT_SRCS "dns_server.c" "http_server.c" "json.c" "wifi_manager.c")
+set(REQUIRES esp_common)
+set(COMPONENT_EMBED_FILES "style.css jquery.gz code.js index.html")
+
+set(REQUIRES_COMPONENTS freertos )
+
+register_component()
+
+

+ 67 - 0
components/wifi-manager/Kconfig.projbuild

@@ -0,0 +1,67 @@
+menu "Wifi Manager Configuration"
+
+config WIFI_MANAGER_TASK_PRIORITY
+    int "RTOS Task Priority for the wifi_manager"
+    default 5
+    help
+	Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1. For this particular reason, minimum recommended task priority is 2.
+
+config WIFI_MANAGER_MAX_RETRY
+	int "Max Retry on failed connection"
+    default 2
+    help
+	Defines when a connection is lost/attempt to connect is made, how many retries should be made before giving up.
+	
+config DEFAULT_AP_SSID
+    string "Access Point SSID"
+    default "esp32"
+    help
+	SSID (network name) the the esp32 will broadcast.
+
+config DEFAULT_AP_PASSWORD
+    string "Access Point Password"
+    default "esp32pwd"
+    help
+	Password used for the Access Point. Leave empty and set AUTH MODE to WIFI_AUTH_OPEN for no password.
+
+config DEFAULT_AP_CHANNEL
+    int "Access Point WiFi Channel"
+    default 1
+    help
+	Be careful you might not see the access point if you use a channel not allowed in your country.
+	
+config DEFAULT_AP_IP
+    string "Access Point IP Address"
+    default "10.10.0.1"
+    help
+	This is used for the redirection to the captive portal. It is recommended to leave unchanged.
+	
+config DEFAULT_AP_GATEWAY
+    string "Access Point IP Gateway"
+    default "10.10.0.1"
+    help
+	This is used for the redirection to the captive portal. It is recommended to leave unchanged.
+	
+config DEFAULT_AP_NETMASK
+    string "Access Point Netmask"
+    default "255.255.255.0"
+    help
+	This is used for the redirection to the captive portal. It is recommended to leave unchanged.
+	
+config DEFAULT_AP_MAX_CONNECTIONS
+    int "Access Point Max Connections"
+    default 4
+    help
+	Max is 4.
+	
+config DEFAULT_AP_BEACON_INTERVAL
+    int "Access Point Beacon Interval (ms)"
+    default 100
+    help
+	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"
+    help
+	This is the command to run when starting the device
+endmenu

+ 19 - 0
components/wifi-manager/LICENSE.md

@@ -0,0 +1,19 @@
+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.

+ 41 - 0
components/wifi-manager/README.md

@@ -0,0 +1,41 @@
+# What is esp32-wifi-manager?
+*esp32-wifi-manager* is an esp32 program that enables easy management of wifi networks through a web application.
+
+*esp32-wifi-manager* is **lightweight** (8KB of task stack in total) and barely uses any CPU power through a completely event driven architecture. It's an all in one wifi scanner, http server & dns daemon living in the least amount of RAM possible.
+
+For real time constrained applications, *esp32-wifi-manager* can live entirely on PRO CPU, leaving the entire APP CPU untouched for your own needs.
+
+*esp32-wifi-manager* will automatically attempt to re-connect to a previously saved network on boot, and it will start its own wifi access point through which you can manage wifi networks if a saved network cannot be found and/or if the connection is lost.
+
+*esp32-wifi-manager* is an esp-idf project that compiles successfully with the esp-idf 3.2 release. You can simply copy the project and start adding your own code to it.
+
+# Demo
+[![esp32-wifi-manager demo](http://img.youtube.com/vi/hxlZi15bym4/0.jpg)](http://www.youtube.com/watch?v=hxlZi15bym4)
+
+# Look and Feel
+![esp32-wifi-manager on an mobile device](https://idyl.io/wp-content/uploads/2017/11/esp32-wifi-manager-password.png "esp32-wifi-manager") ![esp32-wifi-manager on an mobile device](https://idyl.io/wp-content/uploads/2017/11/esp32-wifi-manager-connected-to.png "esp32-wifi-manager")
+
+# Adding esp32-wifi-manager to your code
+Ther are effectively three different ways you can embed esp32-wifi-manager with your code:
+* Just forget about it and poll in your code for wifi connectivity status
+* Use event callbacks
+* Modify esp32-wifi-manager code directly to fit your needs
+
+**Event callbacks** are the cleanest way to use the wifi manager and that's the recommended way to do it. A typical use-case would be to get notified when wifi manager finally gets a connection an access point. In order to do this you can simply define a callback function:
+
+```c
+void cb_connection_ok(void *pvParameter){
+	ESP_LOGI(TAG, "I have a connection!");
+}
+```
+
+Then just register it by calling:
+
+```c
+wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_ok);
+```
+
+That's it! Now everytime the event is triggered it will call this function.
+
+# License
+*esp32-wifi-manager* is MIT licensed. As such, it can be included in any project, commercial or not, as long as you retain original copyright. Please make sure to read the license file.

+ 12 - 0
components/wifi-manager/ap.json

@@ -0,0 +1,12 @@
+[
+{"ssid":"Pantum-AP-A6D49F","chan":11,"rssi":-55,"auth":4},
+{"ssid":"a0308","chan":1,"rssi":-56,"auth":3},
+{"ssid":"dlink-D9D8","chan":11,"rssi":-82,"auth":4},
+{"ssid":"Linksys06730","chan":7,"rssi":-85,"auth":3},
+{"ssid":"SINGTEL-5171","chan":9,"rssi":-88,"auth":4},
+{"ssid":"1126-1","chan":11,"rssi":-89,"auth":4},
+{"ssid":"The Shah 5GHz-2","chan":1,"rssi":-90,"auth":3},
+{"ssid":"SINGTEL-1D28 (2G)","chan":11,"rssi":-91,"auth":3},
+{"ssid":"dlink-F864","chan":1,"rssi":-92,"auth":4},
+{"ssid":"dlink-74F0","chan":1,"rssi":-93,"auth":4}
+]

+ 454 - 0
components/wifi-manager/code.js

@@ -0,0 +1,454 @@
+// First, checks if it isn't implemented yet.
+if (!String.prototype.format) {
+  String.prototype.format = function() {
+    var args = arguments;
+    return this.replace(/{(\d+)}/g, function(match, number) { 
+      return typeof args[number] != 'undefined'
+        ? args[number]
+        : match
+      ;
+    });
+  };
+}
+
+var apList = null;
+var selectedSSID = "";
+var refreshAPInterval = null; 
+var checkStatusInterval = null;
+var checkConfigInterval = null;
+
+var StatusIntervalActive = false;
+var ConfigIntervalActive = false;
+var RefreshAPIIntervalActive = false;
+
+
+function stopCheckStatusInterval(){
+	if(checkStatusInterval != null){
+		clearTimeout(checkStatusInterval);
+		checkStatusInterval = null;
+	}
+	StatusIntervalActive = false;
+}
+function stopCheckConfigInterval(){
+	if(checkConfigInterval != null){
+		clearTimeout(checkConfigInterval);
+		checkConfigInterval = null;
+	}
+	ConfigIntervalActive=false;
+}
+
+function stopRefreshAPInterval(){
+	
+	if(refreshAPInterval != null){
+		 clearTimeout(refreshAPInterval);
+		refreshAPInterval = null;
+	}
+	RefreshAPIIntervalActive = false;
+}
+
+
+function startCheckStatusInterval(){
+	StatusIntervalActive = true;
+	checkStatusInterval = setTimeout(checkStatus, 950);
+}
+function startCheckConfigInterval(){
+	ConfigIntervalActive = true;
+	checkConfigInterval = setTimeout(checkConfig, 950);
+}
+
+function startRefreshAPInterval(){
+	RefreshAPIIntervalActive = true;
+	refreshAPInterval = setTimeout(refreshAP, 2800);
+}
+
+
+function RepeatCheckStatusInterval(){
+	if(StatusIntervalActive)
+		startCheckStatusInterval();
+}
+
+function RepeatCheckConfigInterval(){
+	if(ConfigIntervalActive)
+		startCheckConfigInterval();
+}
+
+function RepeatRefreshAPInterval(){
+	if(RefreshAPIIntervalActive)
+		startRefreshAPInterval()
+}
+
+$(document).ready(function(){
+	
+	
+	$("#wifi-status").on("click", ".ape", function() {
+		$( "#wifi" ).slideUp( "fast", function() {});
+		$( "#connect-details" ).slideDown( "fast", function() {});
+	});
+
+	$("#manual_add").on("click", ".ape", function() {
+		selectedSSID = $(this).text();
+		$( "#ssid-pwd" ).text(selectedSSID);
+		$( "#wifi" ).slideUp( "fast", function() {});
+		$( "#connect_manual" ).slideDown( "fast", function() {});
+		$( "#connect" ).slideUp( "fast", function() {});
+
+		//update wait screen
+		$( "#loading" ).show();
+		$( "#connect-success" ).hide();
+		$( "#connect-fail" ).hide();
+	});
+
+	$("#wifi-list").on("click", ".ape", function() {
+		selectedSSID = $(this).text();
+		$( "#ssid-pwd" ).text(selectedSSID);
+		$( "#wifi" ).slideUp( "fast", function() {});
+		$( "#connect_manual" ).slideUp( "fast", function() {});
+		$( "#connect" ).slideDown( "fast", function() {});
+		
+		//update wait screen
+		$( "#loading" ).show();
+		$( "#connect-success" ).hide();
+		$( "#connect-fail" ).hide();		
+	});
+	
+	$("#cancel").on("click", function() {
+		selectedSSID = "";
+		$( "#connect" ).slideUp( "fast", function() {});
+		$( "#connect_manual" ).slideUp( "fast", function() {});
+		$( "#wifi" ).slideDown( "fast", function() {});
+	});
+
+	$("#manual_cancel").on("click", function() {
+		selectedSSID = "";
+		$( "#connect" ).slideUp( "fast", function() {});
+		$( "#connect_manual" ).slideUp( "fast", function() {});
+		$( "#wifi" ).slideDown( "fast", function() {});
+	});
+	
+	$("#join").on("click", function() {
+		performConnect();
+	});
+
+	$("#manual_join").on("click", function() {
+		performConnect($(this).data('connect'));
+	});
+	
+	$("#ok-details").on("click", function() {
+		$( "#connect-details" ).slideUp( "fast", function() {});
+		$( "#wifi" ).slideDown( "fast", function() {});
+		
+	});
+	$("#update").on("click", function() {
+		
+		performUpdate();
+	});	
+		$("#factory").on("click", function() {
+		
+		performFactory();
+	});	
+	
+	$("#ok-credits").on("click", function() {
+		$( "#credits" ).slideUp( "fast", function() {});
+		$( "#app" ).slideDown( "fast", function() {});
+		
+	});
+	
+	$("#acredits").on("click", function(event) {
+		event.preventDefault();
+		$( "#app" ).slideUp( "fast", function() {});
+		$( "#credits" ).slideDown( "fast", function() {});
+	});
+	
+	$("#ok-connect").on("click", function() {
+		$( "#connect-wait" ).slideUp( "fast", function() {});
+		$( "#wifi" ).slideDown( "fast", function() {});
+	});
+	
+	$("#disconnect").on("click", function() {
+		$( "#connect-details-wrap" ).addClass('blur');
+		$( "#diag-disconnect" ).slideDown( "fast", function() {});
+	});
+	
+	$("#no-disconnect").on("click", function() {
+		$( "#diag-disconnect" ).slideUp( "fast", function() {});
+		$( "#connect-details-wrap" ).removeClass('blur');
+	});
+	
+	$("#yes-disconnect").on("click", function() {
+		
+		stopCheckStatusInterval();
+		selectedSSID = "";
+		
+		$( "#diag-disconnect" ).slideUp( "fast", function() {});
+		$( "#connect-details-wrap" ).removeClass('blur');
+		
+		$.ajax({
+			url: '/connect.json',
+			dataType: 'json',
+			method: 'DELETE',
+			cache: false,
+			data: { 'timestamp': Date.now()}
+		});
+
+		startCheckStatusInterval();
+		
+		$( "#connect-details" ).slideUp( "fast", function() {});
+		$( "#wifi" ).slideDown( "fast", function() {})
+	});
+	
+	
+	
+	
+	
+	
+	
+	
+	//first time the page loads: attempt get the connection status and start the wifi scan
+	refreshAP();
+	startCheckStatusInterval();
+	startRefreshAPInterval();
+	startCheckConfigInterval();
+
+
+	
+	
+});
+
+
+function performUpdate(){
+	autoexec1 = $("#autoexec1").val();
+	//reset connection 
+// 		
+// 	$( "#ok-connect" ).prop("disabled",true);
+// 	$( "#ssid-wait" ).text(selectedSSID);
+// 	$( "#connect" ).slideUp( "fast", function() {});
+// 	$( "#connect_manual" ).slideUp( "fast", function() {});
+// 	$( "#connect-wait" ).slideDown( "fast", function() {});
+// 	// todo: should we update the UI here? 
+	
+	$.ajax({
+		url: '/config.json',
+		dataType: 'json',
+		method: 'POST',
+		cache: false,
+		headers: { 'X-Custom-autoexec1': autoexec1 },
+		data: { 'timestamp': Date.now()}
+	});
+
+
+}
+
+function performFactory(){
+		
+// 	$( "#ok-connect" ).prop("disabled",true);
+// 	$( "#ssid-wait" ).text(selectedSSID);
+// 	$( "#connect" ).slideUp( "fast", function() {});
+// 	$( "#connect_manual" ).slideUp( "fast", function() {});
+// 	$( "#connect-wait" ).slideDown( "fast", function() {});
+// 	// todo: should we update the UI here? 
+	
+	$.ajax({
+		url: '/factory.json',
+		dataType: 'json',
+		method: 'POST',
+		cache: false,
+		data: { 'timestamp': Date.now()}
+	});
+
+
+}
+
+
+function performConnect(conntype){
+	
+	//stop the status refresh. This prevents a race condition where a status 
+	//request would be refreshed with wrong ip info from a previous connection
+	//and the request would automatically shows as succesful.
+	stopCheckStatusInterval();
+	
+	//stop refreshing wifi list
+	stopRefreshAPInterval();
+
+	var pwd;
+	if (conntype == 'manual') {
+		//Grab the manual SSID and PWD
+		selectedSSID=$('#manual_ssid').val();
+		pwd = $("#manual_pwd").val();
+	}else{
+		pwd = $("#pwd").val();
+	}
+	//reset connection 
+	$( "#loading" ).show();
+	$( "#connect-success" ).hide();
+	$( "#connect-fail" ).hide();
+	
+	$( "#ok-connect" ).prop("disabled",true);
+	$( "#ssid-wait" ).text(selectedSSID);
+	$( "#connect" ).slideUp( "fast", function() {});
+	$( "#connect_manual" ).slideUp( "fast", function() {});
+	$( "#connect-wait" ).slideDown( "fast", function() {});
+	
+	
+	$.ajax({
+		url: '/connect.json',
+		dataType: 'json',
+		method: 'POST',
+		cache: false,
+		headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd },
+		data: { 'timestamp': Date.now()}
+	});
+
+
+	//now we can re-set the intervals regardless of result
+	startCheckStatusInterval();
+	startRefreshAPInterval();
+	
+}
+
+
+
+function rssiToIcon(rssi){
+	if(rssi >= -60){
+		return 'w0';
+	}
+	else if(rssi >= -67){
+		return 'w1';
+	}
+	else if(rssi >= -75){
+		return 'w2';
+	}
+	else{
+		return 'w3';
+	}
+}
+
+
+function refreshAP(){
+	$.getJSON( "/ap.json", function( data ) {
+		if(data.length > 0){
+			//sort by signal strength
+			data.sort(function (a, b) {
+				var x = a["rssi"]; var y = b["rssi"];
+				return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+			});
+			apList = data;
+			refreshAPHTML(apList);
+			
+		}
+	});
+	RepeatRefreshAPInterval();
+
+}
+
+function refreshAPHTML(data){
+	var h = "";
+	data.forEach(function(e, idx, array) {
+		h += '<div class="ape{0}"><div class="{1}"><div class="{2}">{3}</div></div></div>'.format(idx === array.length - 1?'':' brdb', rssiToIcon(e.rssi), e.auth==0?'':'pw',e.ssid);
+		h += "\n";
+	});
+	
+	$( "#wifi-list" ).html(h)
+}
+
+
+
+
+function checkStatus(){
+	$.getJSON( "/status.json", function( data ) {
+		if(data.hasOwnProperty('autoexec1') && data['autoexec1'] != ""){
+			$("#autoexec1_current").text(data["autoexec1"]);
+		}	
+		if(data.hasOwnProperty('ssid') && data['ssid'] != ""){
+			if(data["ssid"] === selectedSSID){
+				//that's a connection attempt
+				if(data["urc"] === 0){
+					//got connection
+					$("#connected-to span").text(data["ssid"]);
+					$("#connect-details h1").text(data["ssid"]);
+					$("#ip").text(data["ip"]);
+					$("#netmask").text(data["netmask"]);
+					$("#gw").text(data["gw"]);
+					$("#wifi-status").slideDown( "fast", function() {});
+					
+					//unlock the wait screen if needed
+					$( "#ok-connect" ).prop("disabled",false);
+					
+					//update wait screen
+					$( "#loading" ).hide();
+					$( "#connect-success" ).show();
+					$( "#connect-fail" ).hide();
+				}
+				else if(data["urc"] === 1){
+					//failed attempt
+					$("#connected-to span").text('');
+					$("#connect-details h1").text('');
+					$("#ip").text('0.0.0.0');
+					$("#netmask").text('0.0.0.0');
+					$("#gw").text('0.0.0.0');
+					
+					//don't show any connection
+					$("#wifi-status").slideUp( "fast", function() {});
+					
+					//unlock the wait screen
+					$( "#ok-connect" ).prop("disabled",false);
+					
+					//update wait screen
+					$( "#loading" ).hide();
+					$( "#connect-fail" ).show();
+					$( "#connect-success" ).hide();
+				}
+			}
+			else if(data.hasOwnProperty('urc') && data['urc'] === 0){
+				//ESP32 is already connected to a wifi without having the user do anything
+				if( !($("#wifi-status").is(":visible")) ){
+					$("#connected-to span").text(data["ssid"]);
+					$("#connect-details h1").text(data["ssid"]);
+					$("#ip").text(data["ip"]);
+					$("#netmask").text(data["netmask"]);
+					$("#gw").text(data["gw"]);
+					$("#wifi-status").slideDown( "fast", function() {});
+				}
+			}
+		}
+		else if(data.hasOwnProperty('urc') && data['urc'] === 2){
+			//that's a manual disconnect
+			if($("#wifi-status").is(":visible")){
+				$("#wifi-status").slideUp( "fast", function() {});
+			}
+		}
+	})
+	.fail(function() {
+		//don't do anything, the server might be down while esp32 recalibrates radio
+	});
+
+	RepeatCheckStatusInterval();
+}
+
+
+function checkConfig(){
+	var h = "";
+	//{ "autoexec" : 0, "list" : [{ 'autoexec1' : 'squeezelite -o "I2S" -b 500:2000 -d all=info -M esp32' }]}
+	$.getJSON( "/config.json", function( data ) {
+		if(data.hasOwnProperty('autoexec')) {
+			h+= '<div id="autoexec">Autoexec: {0}</div>'.format(data["autoexec"]===1?"Active":"Inactive");
+		}
+		if(data.hasOwnProperty('list')) {
+			data["list"].forEach(function(e, idx, array) {
+		    	for (const [key, value] of Object.entries(e)) {
+  			 		h+= '<input id="{0}" type="text" maxlength="201" value="{1}"><br>'.format(key,value); 
+					}
+				}
+			
+			);
+		h += "\n";
+		$( "#command-list" ).html(h);
+		}
+		
+	})
+	.fail(function() {
+		//don't do anything, the server might be down while esp32 recalibrates radio
+	});
+
+	RepeatCheckConfigInterval();
+
+}

+ 11 - 0
components/wifi-manager/component.mk

@@ -0,0 +1,11 @@
+#
+# 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_EMBED_FILES := style.css jquery.gz code.js index.html
+CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG
+COMPONENT_ADD_INCLUDEDIRS := .

+ 2 - 0
components/wifi-manager/compress.bat

@@ -0,0 +1,2 @@
+gzip index.html style.css jquery.js --best --keep --force
+pause

+ 2 - 0
components/wifi-manager/connect

@@ -0,0 +1,2 @@
+<html>
+</html>

+ 184 - 0
components/wifi-manager/dns_server.c

@@ -0,0 +1,184 @@
+/*
+Copyright (c) 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 dns_server.c
+@author Tony Pottier
+@brief Defines an extremely basic DNS server for captive portal functionality.
+It's basically a DNS hijack that replies to the esp's address no matter which
+request is sent to it.
+
+Contains the freeRTOS task for the DNS server that processes the requests.
+
+@see https://idyl.io
+@see https://github.com/tonyp7/esp32-wifi-manager
+*/
+
+#include "dns_server.h"
+
+#include <lwip/sockets.h>
+#include <string.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/event_groups.h>
+#include <esp_system.h>
+#include <esp_wifi.h>
+#include <esp_event_loop.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <nvs_flash.h>
+
+#include <lwip/err.h>
+#include <lwip/sockets.h>
+#include <lwip/sys.h>
+#include <lwip/netdb.h>
+#include <lwip/dns.h>
+
+#include <byteswap.h>
+
+#include "wifi_manager.h"
+
+static const char TAG[] = "dns_server";
+static TaskHandle_t task_dns_server = NULL;
+int socket_fd;
+
+void dns_server_start() {
+    xTaskCreate(&dns_server, "dns_server", 3072, NULL, WIFI_MANAGER_TASK_PRIORITY-1, &task_dns_server);
+}
+
+void dns_server_stop(){
+	if(task_dns_server){
+		vTaskDelete(task_dns_server);
+		close(socket_fd);
+		task_dns_server = NULL;
+	}
+
+}
+
+
+
+void dns_server(void *pvParameters) {
+
+
+
+    struct sockaddr_in sa, ra;
+
+    /* Set redirection DNS hijack to the access point IP */
+    ip4_addr_t ip_resolved;
+    inet_pton(AF_INET, DEFAULT_AP_IP, &ip_resolved);
+
+
+    /* Create UDP socket */
+    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
+    if (socket_fd < 0){
+        ESP_LOGE(TAG, "Failed to create socket");
+        exit(0);
+    }
+    memset(&sa, 0, sizeof(struct sockaddr_in));
+
+    /* Bind to port 53 (typical DNS Server port) */
+    tcpip_adapter_ip_info_t ip;
+    tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip);
+    ra.sin_family = AF_INET;
+    ra.sin_addr.s_addr = ip.ip.addr;
+    ra.sin_port = htons(53);
+    if (bind(socket_fd, (struct sockaddr *)&ra, sizeof(struct sockaddr_in)) == -1) {
+        ESP_LOGE(TAG, "Failed to bind to 53/udp");
+        close(socket_fd);
+        exit(1);
+    }
+
+    struct sockaddr_in client;
+    socklen_t client_len;
+    client_len = sizeof(client);
+    int length;
+    uint8_t data[DNS_QUERY_MAX_SIZE];	/* dns query buffer */
+    uint8_t response[DNS_ANSWER_MAX_SIZE]; /* dns response buffer */
+    char ip_address[INET_ADDRSTRLEN]; /* buffer to store IPs as text. This is only used for debug and serves no other purpose */
+    char *domain; /* This is only used for debug and serves no other purpose */
+    int err;
+
+    ESP_LOGI(TAG, "DNS Server listening on 53/udp");
+
+    /* Start loop to process DNS requests */
+    for(;;) {
+
+    	memset(data, 0x00,  sizeof(data)); /* reset buffer */
+        length = recvfrom(socket_fd, data, sizeof(data), 0, (struct sockaddr *)&client, &client_len); /* read udp request */
+
+        /*if the query is bigger than the buffer size we simply ignore it. This case should only happen in case of multiple
+         * queries within the same DNS packet and is not supported by this simple DNS hijack. */
+        if ( length > 0   &&  ((length + sizeof(dns_answer_t)-1) < DNS_ANSWER_MAX_SIZE)   ) {
+
+        	data[length] = '\0'; /*in case there's a bogus domain name that isn't null terminated */
+
+            /* Generate header message */
+            memcpy(response, data, sizeof(dns_header_t));
+            dns_header_t *dns_header = (dns_header_t*)response;
+            dns_header->QR = 1; /*response bit */
+            dns_header->OPCode  = DNS_OPCODE_QUERY; /* no support for other type of response */
+            dns_header->AA = 1; /*authoritative answer */
+            dns_header->RCode = DNS_REPLY_CODE_NO_ERROR; /* no error */
+            dns_header->TC = 0; /*no truncation */
+            dns_header->RD = 0; /*no recursion */
+            dns_header->ANCount = dns_header->QDCount; /* set answer count = question count -- duhh! */
+            dns_header->NSCount = 0x0000; /* name server resource records = 0 */
+            dns_header->ARCount = 0x0000; /* resource records = 0 */
+
+
+            /* copy the rest of the query in the response */
+            memcpy(response + sizeof(dns_header_t), data + sizeof(dns_header_t), length - sizeof(dns_header_t));
+
+
+            /* extract domain name and request IP for debug */
+            inet_ntop(AF_INET, &(client.sin_addr), ip_address, INET_ADDRSTRLEN);
+            domain = (char*) &data[sizeof(dns_header_t) + 1];
+            for(char* c=domain; *c != '\0'; c++){
+            	if(*c < ' ' || *c > 'z') *c = '.'; /* technically we should test if the first two bits are 00 (e.g. if( (*c & 0xC0) == 0x00) *c = '.') but this makes the code a lot more readable */
+            }
+            ESP_LOGI(TAG, "Replying to DNS request for %s from %s", domain, ip_address);
+
+
+            /* create DNS answer at the end of the query*/
+            dns_answer_t *dns_answer = (dns_answer_t*)&response[length];
+            dns_answer->NAME = __bswap_16(0xC00C); /* This is a pointer to the beginning of the question. As per DNS standard, first two bits must be set to 11 for some odd reason hence 0xC0 */
+            dns_answer->TYPE = __bswap_16(DNS_ANSWER_TYPE_A);
+            dns_answer->CLASS = __bswap_16(DNS_ANSWER_CLASS_IN);
+            dns_answer->TTL = (uint32_t)0x00000000; /* no caching. Avoids DNS poisoning since this is a DNS hijack */
+            dns_answer->RDLENGTH = __bswap_16(0x0004); /* 4 byte => size of an ipv4 address */
+            dns_answer->RDATA = ip_resolved.addr;
+
+            err = sendto(socket_fd, response, length+sizeof(dns_answer_t), 0, (struct sockaddr *)&client, client_len);
+            if (err < 0) {
+            	ESP_LOGE(TAG, "UDP sendto failed: %d", err);
+            }
+        }
+
+        taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. DNS daemon should not be taxing on the system */
+
+    }
+    close(socket_fd);
+
+    vTaskDelete ( NULL );
+}
+
+
+
+

+ 140 - 0
components/wifi-manager/dns_server.h

@@ -0,0 +1,140 @@
+/*
+Copyright (c) 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 dns_server.h
+@author Tony Pottier
+@brief Defines an extremly basic DNS server for captive portal functionality.
+
+Contains the freeRTOS task for the DNS server that processes the requests.
+
+@see https://idyl.io
+@see https://github.com/tonyp7/esp32-wifi-manager
+@see http://www.zytrax.com/books/dns/ch15
+*/
+
+#ifndef MAIN_DNS_SERVER_H_
+#define MAIN_DNS_SERVER_H_
+#include <esp_system.h>
+#include <stdbool.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/** 12 byte header, 64 byte domain name, 4 byte qtype/qclass. This NOT compliant with the RFC, but it's good enough for a captive portal
+ * if a DNS query is too big it just wont be processed. */
+#define	DNS_QUERY_MAX_SIZE 80
+
+/** Query + 2 byte ptr, 2 byte type, 2 byte class, 4 byte TTL, 2 byte len, 4 byte data */
+#define	DNS_ANSWER_MAX_SIZE (DNS_QUERY_MAX_SIZE+16)
+
+
+/**
+ * @brief RCODE values used in a DNS header message
+ */
+typedef enum dns_reply_code_t {
+	DNS_REPLY_CODE_NO_ERROR = 0,
+	DNS_REPLY_CODE_FORM_ERROR = 1,
+	DNS_REPLY_CODE_SERVER_FAILURE = 2,
+	DNS_REPLY_CODE_NON_EXISTANT_DOMAIN = 3,
+	DNS_REPLY_CODE_NOT_IMPLEMENTED = 4,
+	DNS_REPLY_CODE_REFUSED = 5,
+	DNS_REPLY_CODE_YXDOMAIN = 6,
+	DNS_REPLY_CODE_YXRRSET = 7,
+	DNS_REPLY_CODE_NXRRSET = 8
+}dns_reply_code_t;
+
+
+
+/**
+ * @brief OPCODE values used in a DNS header message
+ */
+typedef enum dns_opcode_code_t {
+	DNS_OPCODE_QUERY = 0,
+	DNS_OPCODE_IQUERY = 1,
+	DNS_OPCODE_STATUS = 2
+}dns_opcode_code_t;
+
+
+
+/**
+ * @brief Represents a 12 byte DNS header.
+ * __packed__ is needed to prevent potential unwanted memory alignments
+ */
+typedef struct __attribute__((__packed__)) dns_header_t{
+  uint16_t ID;         // identification number
+  uint8_t RD : 1;      // recursion desired
+  uint8_t TC : 1;      // truncated message
+  uint8_t AA : 1;      // authoritive answer
+  uint8_t OPCode : 4;  // message_type
+  uint8_t QR : 1;      // query/response flag
+  uint8_t RCode : 4;   // response code
+  uint8_t Z : 3;       // its z! reserved
+  uint8_t RA : 1;      // recursion available
+  uint16_t QDCount;    // number of question entries
+  uint16_t ANCount;    // number of answer entries
+  uint16_t NSCount;    // number of authority entries
+  uint16_t ARCount;    // number of resource entries
+}dns_header_t;
+
+
+
+typedef enum dns_answer_type_t {
+	DNS_ANSWER_TYPE_A = 1,
+	DNS_ANSWER_TYPE_NS = 2,
+	DNS_ANSWER_TYPE_CNAME = 5,
+	DNS_ANSWER_TYPE_SOA = 6,
+	DNS_ANSWER_TYPE_WKS = 11,
+	DNS_ANSWER_TYPE_PTR = 12,
+	DNS_ANSWER_TYPE_MX = 15,
+	DNS_ANSWER_TYPE_SRV = 33,
+	DNS_ANSWER_TYPE_AAAA = 28
+}dns_answer_type_t;
+
+typedef enum dns_answer_class_t {
+	DNS_ANSWER_CLASS_IN = 1
+}dns_answer_class_t;
+
+
+
+typedef struct __attribute__((__packed__)) dns_answer_t{
+	uint16_t NAME;	/* for the sake of simplicity only 16 bit pointers are supported */
+	uint16_t TYPE; /* Unsigned 16 bit value. The resource record types - determines the content of the RDATA field. */
+	uint16_t CLASS; /* Class of response. */
+	uint32_t TTL; /* The time in seconds that the record may be cached. A value of 0 indicates the record should not be cached. */
+	uint16_t RDLENGTH; /* Unsigned 16-bit value that defines the length in bytes of the RDATA record. */
+	uint32_t RDATA; /* For the sake of simplicity only ipv4 is supported, and as such it's a unsigned 32 bit */
+}dns_answer_t;
+
+void dns_server(void *pvParameters);
+void dns_server_start();
+void dns_server_stop();
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* MAIN_DNS_SERVER_H_ */

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

@@ -0,0 +1,367 @@
+/*
+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"
+
+
+
+/* @brief tag used for ESP serial console messages */
+static const char TAG[] = "http_server";
+static const char json_start[] = "{ \"autoexec\" : %u, \"list\" : [";
+static const char json_end[] = "]}";
+static const char template[] = "{ '%s' : '%s' }";
+static const char array_separator[]=",";
+
+/* @brief task handle for the http server */
+static TaskHandle_t task_http_server = 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_gz_start");
+extern const uint8_t jquery_gz_end[] asm("_binary_jquery_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_html_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/html\n\n";
+const static char http_css_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/css\nCache-Control: public, max-age=31536000\n\n";
+const static char http_js_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\n\n";
+const static char http_jquery_gz_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\nAccept-Ranges: bytes\nContent-Length: 29995\nContent-Encoding: gzip\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\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(){
+	if(task_http_server == NULL){
+		xTaskCreate(&http_server, "http_server", 1024*3, NULL, WIFI_MANAGER_TASK_PRIORITY-1, &task_http_server);
+	}
+}
+
+void http_server(void *pvParameters) {
+
+	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);
+
+	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;
+}
+
+
+void http_server_netconn_serve(struct netconn *conn) {
+
+	struct netbuf *inbuf;
+	char *buf = NULL;
+	u16_t buflen;
+	err_t err;
+	const char new_line[2] = "\n";
+
+	err = netconn_recv(conn, &inbuf);
+	if (err == ERR_OK) {
+
+		netbuf_data(inbuf, (void**)&buf, &buflen);
+
+		/* 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);
+
+		if(line) {
+
+			/* captive portal functionality: redirect to access point IP for HOST that are not the access point IP OR the STA IP */
+			int lenH = 0;
+			char *host = http_server_get_header(save_ptr, "Host: ", &lenH);
+			/* determine if Host is from the STA IP address */
+			wifi_manager_lock_sta_ip_string(portMAX_DELAY);
+			bool access_from_sta_ip = lenH > 0?strstr(host, wifi_manager_get_sta_ip_string()):false;
+			wifi_manager_unlock_sta_ip_string();
+
+			if (lenH > 0 && !strstr(host, DEFAULT_AP_IP) && !access_from_sta_ip) {
+				ESP_LOGI(TAG,"Redirecting to default AP IP Address : %s", DEFAULT_AP_IP);
+				netconn_write(conn, http_redirect_hdr_start, sizeof(http_redirect_hdr_start) - 1, NETCONN_NOCOPY);
+				netconn_write(conn, DEFAULT_AP_IP, sizeof(DEFAULT_AP_IP) - 1, NETCONN_NOCOPY);
+				netconn_write(conn, http_redirect_hdr_end, sizeof(http_redirect_hdr_end) - 1, NETCONN_NOCOPY);
+
+			}
+			else{
+				/* 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 /jquery.js ")) {
+					netconn_write(conn, http_jquery_gz_hdr, sizeof(http_jquery_gz_hdr) - 1, NETCONN_NOCOPY);
+					netconn_write(conn, jquery_gz_start, jquery_gz_end - jquery_gz_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 /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_get_ap_list_json();
+						netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+						wifi_manager_unlock_json_buffer();
+					}
+					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();
+				}
+				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 /status.json ")){
+					ESP_LOGI(TAG,"Serving status.json");
+					if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
+						char *buff = wifi_manager_get_ip_info_json();
+						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);
+
+							wifi_manager_unlock_json_buffer();
+						}
+						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");
+					}
+				}
+				else if(strstr(line, "GET /config.json ")){
+					ESP_LOGI(TAG,"Serving config.json");
+					char autoexec_name[21]={0};
+					char * autoexec_value=NULL;
+					char * autoexec_flag_s=NULL;
+					uint8_t autoexec_flag=0;
+					int buflen=MAX_COMMAND_LINE_SIZE+strlen(template)+1;
+					char * buff = malloc(buflen);
+					if(!buff)
+					{
+						ESP_LOGE(TAG,"Unable to allocate buffer for config.json!");
+						netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
+					}
+					else
+					{
+						int i=1;
+						size_t l = 0;
+						netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
+
+						autoexec_flag = wifi_manager_get_flag();
+						snprintf(buff,buflen-1, json_start, autoexec_flag);
+						netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+						do {
+							snprintf(autoexec_name,sizeof(autoexec_name)-1,"autoexec%u",i);
+							ESP_LOGD(TAG,"Getting command name %s", autoexec_name);
+							autoexec_value= wifi_manager_alloc_get_config(autoexec_name, &l);
+							if(autoexec_value!=NULL ){
+								if(i>1)
+								{
+									netconn_write(conn, array_separator, strlen(array_separator), NETCONN_NOCOPY);
+									ESP_LOGD(TAG,"%s", array_separator);
+								}
+								ESP_LOGI(TAG,"found command %s = %s", autoexec_name, autoexec_value);
+								snprintf(buff,buflen-1,template, autoexec_name,autoexec_value);
+								netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
+								ESP_LOGD(TAG,"%s", buff);
+								ESP_LOGD(TAG,"Freeing memory for command %s name", autoexec_name);
+								free(autoexec_value);
+							}
+							else {
+								ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
+								break;
+							}
+							i++;
+						} while(1);
+						free(buff);
+						netconn_write(conn, json_end, strlen(json_end), NETCONN_NOCOPY);
+						ESP_LOGD(TAG,"%s", json_end);
+					}
+				}
+				else if(strstr(line, "POST /factory.json ")){
+					guided_factory();
+				}
+				else if(strstr(line, "POST /config.json ")){
+					ESP_LOGI(TAG,"Serving POST config.json");
+
+					if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
+						int i=1;
+						int lenS = 0, lenA=0;
+						char autoexec_name[21]={0};
+						char * autoexec_value=NULL;
+						char * autoexec_flag_s=NULL;
+						uint8_t autoexec_flag=0;
+						autoexec_flag_s = http_server_get_header(save_ptr, "X-Custom-autoexec: ", &lenA);
+						if(autoexec_flag_s!=NULL && lenA > 0)
+						{
+							autoexec_flag = atoi(autoexec_flag_s);
+							wifi_manager_save_autoexec_flag(autoexec_flag);
+						}
+
+						do {
+							snprintf(autoexec_name,sizeof(autoexec_name)-1,"X-Custom-autoexec%u:",i++);
+							ESP_LOGD(TAG,"Looking for command name %s", autoexec_name);
+							autoexec_value = http_server_get_header(save_ptr, autoexec_name, &lenS);
+
+							if(autoexec_value ){
+								if(lenS < MAX_COMMAND_LINE_SIZE ){
+									ESP_LOGD(TAG, "http_server_netconn_serve: config.json/ call, with %s: %s", autoexec_name, autoexec_value);
+									wifi_manager_save_autoexec_config(autoexec_value,autoexec_name,lenS);
+								}
+								else
+								{
+									ESP_LOGE(TAG,"command line length is too long : %s = %s", autoexec_name, autoexec_value);
+								}
+							}
+							else {
+								ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
+								break;
+							}
+						} while(1);
+
+						netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
+
+					}
+					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");
+					}
+				}
+
+				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 */
+				}
+				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;
+					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);
+
+					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", ssid, 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;
+					}
+
+					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");
+					}
+
+				}
+				else{
+					netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
+					ESP_LOGE(TAG, "bad request");
+				}
+			}
+		}
+		else{
+			ESP_LOGE(TAG, "URL Not found. Sending 404.");
+			netconn_write(conn, http_404_hdr, sizeof(http_404_hdr) - 1, NETCONN_NOCOPY);
+		}
+	}
+
+	/* free the buffer */
+	netbuf_delete(inbuf);
+}

+ 95 - 0
components/wifi-manager/http_server.h

@@ -0,0 +1,95 @@
+/*
+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.h
+@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
+*/
+
+#ifndef HTTP_SERVER_H_INCLUDED
+#define HTTP_SERVER_H_INCLUDED
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include "wifi_manager.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_event_loop.h"
+#include "nvs_flash.h"
+#include "esp_log.h"
+#include "driver/gpio.h"
+#include "mdns.h"
+#include "lwip/api.h"
+#include "lwip/err.h"
+#include "lwip/netdb.h"
+#include "lwip/opt.h"
+#include "lwip/memp.h"
+#include "lwip/ip.h"
+#include "lwip/raw.h"
+#include "lwip/udp.h"
+#include "lwip/priv/api_msg.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/priv/tcpip_priv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief RTOS task for the HTTP server. Do not start manually.
+ * @see void http_server_start()
+ */
+void http_server(void *pvParameters);
+
+/* @brief helper function that processes one HTTP request at a time */
+void http_server_netconn_serve(struct netconn *conn);
+
+/* @brief create the task for the http server */
+void http_server_start();
+
+/**
+ * @brief gets a char* pointer to the first occurence of header_name withing the complete http request request.
+ *
+ * For optimization purposes, no local copy is made. memcpy can then be used in coordination with len to extract the
+ * data.
+ *
+ * @param request the full HTTP raw request.
+ * @param header_name the header that is being searched.
+ * @param len the size of the header value if found.
+ * @return pointer to the beginning of the header value.
+ */
+char* http_server_get_header(char *request, char *header_name, int *len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

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

@@ -0,0 +1,310 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8"/>
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+		<meta name="apple-mobile-web-app-capable" content="yes" />
+		<script src="/jquery.js"></script>
+		<link rel="stylesheet" href="/style.css">
+		<script src="/code.js"></script>
+		<title>esp32-wifi-manager</title>
+	</head>
+	<script>
+var ws, sel, host, old, once = 0, jso, m;
+var to = 0, set_int = 0;
+
+
+
+function get_radio(name)
+{
+	var s = document.getElementsByName(name), sel;
+	for ( var i = 0; i < s.length; i++)
+	    if (s[i].checked) {
+	        sel = s[i].value;
+	        break;
+	    }
+
+	return sel;
+}
+
+function get_radio_index(name)
+{
+	var s = document.getElementsByName(name), i;
+
+	for (i = 0; i < s.length; i++)
+	    if (s[i].checked)
+	        return i;
+
+	return -1;
+}
+
+
+function do_reset()
+{
+	var s = "{\"reset\":\"1\"}";
+	try {
+		ws.send(s);
+	} catch(exception) {
+		alert('Sorry, there was a problem' + exception);
+	}
+
+	ws.close();
+	alert("Rebooting...");
+}
+
+function file_change()
+{
+        document.getElementById('update').disabled = 0;
+}
+
+
+
+function do_upload(f)
+{
+	var xhr = new XMLHttpRequest();
+
+        document.getElementById('update').disabled = 1;
+	//ws.close();
+	document.getElementById("progr").class = "progr-ok";
+
+	xhr.upload.addEventListener("progress", function(e) {
+		document.getElementById("progr").value = parseInt(e.loaded / e.total * 100);
+
+		if (e.loaded == e.total) {
+		//	document.getElementById("realpage").style.display = "none";
+		//	document.getElementById("waiting").style.display = "block";
+		}
+	
+	}, false);
+
+	xhr.onreadystatechange = function(e) {
+		   console.log("rs" + xhr.readyState + " status " + xhr.status); 
+		if (xhr.readyState == 4) {
+			/* it completed, for good or for ill */
+		//	document.getElementById("realpage").style.display = "none";
+		//	document.getElementById("waiting").style.display = "block";
+			document.getElementById("progr").class = "progr-ok";
+			console.log("upload reached state 4: xhr status " + xhr.status);
+			setTimeout(function() { window.location.href = location.origin + "/"; }, 9000 );
+		}
+	};
+
+	/* kill the heart timer */
+	clearInterval(set_int);
+
+	xhr.open("POST", f.action, true);
+	xhr.send(new FormData(f));
+
+	return false;
+}
+
+function do_settings(f)
+{
+	var xhr = new XMLHttpRequest();
+
+	xhr.onreadystatechange = function(e) {
+		   console.log("do_settings" + xhr.readyState + " status " + xhr.status); 
+		if (xhr.readyState == 4) {
+			document.getElementById("updsettings").style.opacity = "1.0";
+			document.getElementById("updsettings").disabled = 0;
+		}
+	};
+
+	xhr.open("POST", f.action, true);
+	document.getElementById("updsettings").style.opacity = "0.3";
+	document.getElementById("updsettings").disabled = 1;
+	xhr.send(new FormData(f));
+
+	return false;
+}
+
+function get_latest(n)
+{
+	if (n == 0)
+		ws.send("update-ota");
+	else
+		ws.send("update-factory");
+}
+
+
+function heart_timer() {
+	var s;
+	
+	s = Math.round((95 * to) / (40 * 10)) / 100;
+	
+	if (s < 0) {
+		clearInterval(set_int);
+		set_int = 0;
+		
+		ws.close();
+		
+		document.getElementById("realpage").style.opacity = "0.3";
+	}
+		
+	
+	to--;
+	document.getElementById("heart").style.opacity = s;
+}
+
+
+function heartbeat()
+{
+	to = 40 * 10;
+	if (!set_int) {
+		set_int = setInterval(heart_timer, 100);
+	}
+		
+}
+
+</script>
+	<body>
+		<div id="app">
+			<div id="app-wrap">
+				<div id="wifi">
+					<header>
+						<h1>Wi-Fi</h1>
+					</header>
+					<div id="wifi-status">
+						<h2>Connected to:</h2>
+						<section id="connected-to">
+							<div class="ape"><div class="w0"><div class="pw"><span></span></div></div></div>
+						</section>
+					</div>
+					<h2>Manual connect</h2>
+					<section id="manual_add">
+					<div class="ape">ADD (HIDDEN) SSID<div>
+					</section>
+					<h2>or choose a network...</h2>
+					<section id="wifi-list">
+					</section>
+					<div id="pwrdby"><em>Powered by </em><a id="acredits" href="#"><strong>esp32-wifi-manager</strong></a>.</div>
+				</div>
+				<div id="command_line">
+				<header>
+						<h1>Startup command</h1>
+					</header>
+					<h2>Squeezelite</span></h2>
+					
+					<div id="autoexec1_current" ></div>
+					<section id="command-list">
+					<input id="autoexec1" type="text" maxlength="201" placeholder="squeezelite -o I2S -b 500:2000 -d all=info" value="">
+					</section>
+					
+					<div class="buttons">
+					<input id="update_command" type="button" value="Update"  />
+					</div>
+				</div>
+			<div id="ota">
+				<header><h1>Application</h1></header>
+				<form name="multipart" action="otaform" method="post" enctype="multipart/form-data" onsubmit="do_upload(this); return false;">
+	          	<progress id="progr" value="0" max="100" >Upload Progress</progress>
+	            <input type="file" name="ota" id="ota" size="20" accept=".bin" onchange="file_change();" style="font-size: 12pt">
+	            <span id="file_info" style="font-size:12pt;"></span>
+	            <input type="submit" id="update" disabled="" value="upload">
+	            <input type="submit" id="factory"  disabled="" value="factory">
+	        </form>
+				</div>
+				<div id="connect_manual">
+					<header>
+						<h1>Enter Details</h1>
+					</header>
+					<h2>Manual Connection</span></h2>
+					<section>
+						<input id="manual_ssid" type="text" placeholder="SSID" value="">
+						<input id="manual_pwd" type="password" placeholder="Password" value="">
+					</section>
+					<div class="buttons">
+							<input id="manual_join" type="button" value="Join" data-connect="manual" />
+							<input id="manual_cancel" type="button" value="Cancel"/>
+					</div>
+				</div>
+				<div id="connect">
+					<header>
+						<h1>Enter Password</h1>
+					</header>
+					<h2>Password for <span id="ssid-pwd"></span></h2>
+					<section>
+						<input id="pwd" type="password" placeholder="Password" value="">
+					</section>
+					<div class="buttons">
+							<input id="join" type="button" value="Join" />
+							<input id="cancel" type="button" value="Cancel"/>
+					</div>
+				</div>
+				<div id="connect-wait">
+					<header>
+						<h1>Please wait...</h1>
+					</header>
+					<h2>Connecting to <span id="ssid-wait"></span></h2>
+					<section>
+						<div id="loading">
+							<div class="spinner"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>
+							<p class="tctr">You may lose wifi access while the esp32 recalibrates its radio. Please wait until your device automatically reconnects. This can take up to 30s.</p>
+						</div>
+						<div id="connect-success">
+							<h3 class="gr">Success!</h3>
+						</div>
+						<div id="connect-fail">
+							<h3 class="rd">Connection failed</h3>
+							<p class="tctr">Please double-check wifi password if any and make sure the access point has good signal.</p>
+						</div>
+					</section>
+					<div class="buttons">
+						<input id="ok-connect" type="button" value="OK" class="ctr" />
+					</div>
+				</div>
+				<div id="connect-details">
+					<div id="connect-details-wrap">
+						<header>
+							<h1></h1>
+						</header>
+						<h2></h2>
+						<section>
+							<div class="buttons">
+								<input id="disconnect" type="button" value="Disconnect" class="ctr"/>
+							</div>
+						</section>
+						<h2>IP Address</h2>
+						<section>
+							<div class="ape brdb">IP Address:<div id="ip" class="fr"></div></div>
+							<div class="ape brdb">Subnet Mask:<div id="netmask" class="fr"></div></div>
+							<div class="ape">Default Gateway:<div id="gw" class="fr"></div></div>
+						</section>
+						<div class="buttons">
+							<input id="ok-details" type="button" value="OK" class="ctr" />
+						</div>
+					</div>					
+					<div id="diag-disconnect" class="diag-box">
+						<div class="diag-box-win">
+							<p>Are you sure you would like to disconnect from this wifi?</p>
+							<div class="buttons">
+								<input id="no-disconnect" type="button" value="No" />
+								<input id="yes-disconnect" type="button" value="Yes" />
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		<div id="credits">
+			<header>
+				<h1>About this app...</h1>
+			</header>
+			<h2></h2>
+			<section>
+				<p><strong>esp32-wifi-manager</strong>, &copy; 2017-2019, Tony Pottier<br />Licender under the MIT License.</p>
+				<p>
+					This app would not be possible without the following libraries:
+				</p>
+				<ul>
+					<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>
+				</ul>
+			</section>
+			<div class="buttons">
+				<input id="ok-credits" type="button" value="OK" class="ctr" />
+			</div>
+		</div>
+	</body>
+<html>

BIN
components/wifi-manager/jquery.gz


Diff do ficheiro suprimidas por serem muito extensas
+ 1 - 0
components/wifi-manager/jquery.js


+ 144 - 0
components/wifi-manager/json.c

@@ -0,0 +1,144 @@
+/*
+@file json.c
+@brief handles very basic JSON with a minimal footprint on the system
+
+This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license:
+Copyright (c) 2009 Dave Gamble
+
+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.
+
+@see https://github.com/DaveGamble/cJSON
+*/
+
+#include "json.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+
+
+bool json_print_string(const unsigned char *input, unsigned char *output_buffer)
+{
+	const unsigned char *input_pointer = NULL;
+	unsigned char *output = NULL;
+	unsigned char *output_pointer = NULL;
+	size_t output_length = 0;
+	/* numbers of additional characters needed for escaping */
+	size_t escape_characters = 0;
+
+	if (output_buffer == NULL)
+	{
+		return false;
+	}
+
+	/* empty string */
+	if (input == NULL)
+	{
+		//output = ensure(output_buffer, sizeof("\"\""), hooks);
+		if (output == NULL)
+		{
+			return false;
+		}
+		strcpy((char*)output, "\"\"");
+
+		return true;
+	}
+
+	/* set "flag" to 1 if something needs to be escaped */
+	for (input_pointer = input; *input_pointer; input_pointer++)
+	{
+		if (strchr("\"\\\b\f\n\r\t", *input_pointer))
+		{
+			/* one character escape sequence */
+			escape_characters++;
+		}
+		else if (*input_pointer < 32)
+		{
+			/* UTF-16 escape sequence uXXXX */
+			escape_characters += 5;
+		}
+	}
+	output_length = (size_t)(input_pointer - input) + escape_characters;
+
+	/* in the original cJSON it is possible to realloc here in case output buffer is too small.
+	 * This is overkill for an embedded system. */
+	output = output_buffer;
+
+	/* no characters have to be escaped */
+	if (escape_characters == 0)
+	{
+		output[0] = '\"';
+		memcpy(output + 1, input, output_length);
+		output[output_length + 1] = '\"';
+		output[output_length + 2] = '\0';
+
+		return true;
+	}
+
+	output[0] = '\"';
+	output_pointer = output + 1;
+	/* copy the string */
+	for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++)
+	{
+		if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\'))
+		{
+			/* normal character, copy */
+			*output_pointer = *input_pointer;
+		}
+		else
+		{
+			/* character needs to be escaped */
+			*output_pointer++ = '\\';
+			switch (*input_pointer)
+			{
+			case '\\':
+				*output_pointer = '\\';
+				break;
+			case '\"':
+				*output_pointer = '\"';
+				break;
+			case '\b':
+				*output_pointer = 'b';
+				break;
+			case '\f':
+				*output_pointer = 'f';
+				break;
+			case '\n':
+				*output_pointer = 'n';
+				break;
+			case '\r':
+				*output_pointer = 'r';
+				break;
+			case '\t':
+				*output_pointer = 't';
+				break;
+			default:
+				/* escape and print as unicode codepoint */
+				sprintf((char*)output_pointer, "u%04x", *input_pointer);
+				output_pointer += 4;
+				break;
+			}
+		}
+	}
+	output[output_length + 1] = '\"';
+	output[output_length + 2] = '\0';
+
+	return true;
+}
+

+ 47 - 0
components/wifi-manager/json.h

@@ -0,0 +1,47 @@
+/*
+@file json.h
+@brief handles very basic JSON with a minimal footprint on the system
+
+This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license:
+Copyright (c) 2009 Dave Gamble
+
+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.
+
+@see https://github.com/DaveGamble/cJSON
+*/
+
+#ifndef JSON_H_INCLUDED
+#define JSON_H_INCLUDED
+#include <stdbool.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Render the cstring provided to a JSON escaped version that can be printed.
+ * @param input the input buffer to be escaped.
+ * @param output_buffer the output buffer to write to. You must ensure it is big enough to contain the final string.
+ * @see cJSON equivlaent static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer)
+ */
+bool json_print_string(const unsigned char *input, unsigned char *output_buffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSON_H_INCLUDED */

BIN
components/wifi-manager/lock.png


+ 29 - 0
components/wifi-manager/main.c.txt

@@ -0,0 +1,29 @@
+/*
+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 main.c
+@author Tony Pottier
+@brief Entry point for the ESP32 application.
+@see https://idyl.io
+@see https://github.com/tonyp7/esp32-wifi-manager
+*/
+
+

BIN
components/wifi-manager/settings.png


+ 1 - 0
components/wifi-manager/status

@@ -0,0 +1 @@
+{"ssid":"zodmgbbq","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":0}

+ 250 - 0
components/wifi-manager/style.css

@@ -0,0 +1,250 @@
+body {
+    background-color: #eee;
+    border: 0;
+    margin: 0;
+    font: 1.1em tahoma, arial, sans-serif;
+}
+a {
+    color: darkblue;
+    transition: color .2s ease-out;
+    text-decoration: none
+}
+a:hover {
+    color: red;
+}
+input {
+    display: none;
+    font: 1.1em tahoma, arial, sans-serif;
+}
+input:focus,
+select:focus,
+textarea:focus,
+button:focus {
+    outline: none;
+}
+input[type="button"] {
+    width: 100px;
+    padding: 5px;
+    text-align: center;
+    display: block;
+}
+p {
+    padding: 10px;
+}
+#credits {
+    display: none;
+}
+#app {} #app-wrap {} #disconnect {
+    width: 150px;
+}
+.diag-box {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    height: 100%;
+    width: 100%;
+    display: none;
+}
+.diag-box-win {
+    position: absolute;
+    left: 10%;
+    width: 80%;
+    text-align: center;
+    border: 2px outset #888;
+    background-color: #fff;
+    border-radius: 10px;
+    top: 20%;
+}
+.blur {
+    -webkit-filter: blur(2px);
+    -moz-filter: blur(2px);
+    -ms-filter: blur(2px);
+    -o-filter: blur(2px);
+    filter: blur(2px);
+}
+.ape {
+    margin-left: 20px;
+    padding: 10px 0px 10px 10px;
+}
+.ape:hover {
+    cursor: pointer;
+}
+.brdb {
+    border-bottom: 1px solid #888;
+}
+header {
+    background-color: #fff;
+    border-bottom: 1px solid #888;
+}
+section {
+    background-color: #fff;
+    border-bottom: 1px solid #888;
+    border-top: 1px solid #888;
+}
+h1 {
+    display: block;
+    text-align: center;
+    margin: 0;
+    padding: 15px;
+    font-size: 1.4em
+}
+h2 {
+    margin: 0;
+    margin-top: 20px;
+    padding: 10px;
+    text-transform: uppercase;
+    color: #888;
+    font-size: 1.0em
+}
+h3 {
+    margin: 0;
+    text-align: center;
+    padding: 20px 0px 20px 0px;
+}
+.gr {
+    color: green;
+}
+.rd {
+    color: red;
+}
+#wifi-status {
+    display: none;
+}
+#connect {
+    display: none;
+}
+#connect_manual {
+    display: none;
+}
+#manual_ssid {
+    border: none;
+    width: 80%;
+    margin-left: 35px;
+    padding: 10px 0px 10px 10px;
+    display: block
+}
+#manual_pwd {
+    border: none;
+    width: 80%;
+    margin-left: 35px;
+    padding: 10px 0px 10px 10px;
+    display: block
+}
+#pwd {
+    border: none;
+    width: 80%;
+    margin-left: 35px;
+    padding: 10px 0px 10px 10px;
+    display: block
+}
+.buttons {
+    padding: 15px;
+}
+#join {
+    float: right;
+}
+#manual_join {
+    float: right;
+}
+#yes-disconnect {
+    display: inline-block;
+    margin-left: 20px;
+}
+#no-disconnect {
+    display: inline-block;
+}
+.ctr {
+    margin: 0 auto;
+}
+.tctr {
+    text-align: center;
+}
+#connect-wait {
+    display: none;
+}
+#connect-success {
+    display: none;
+}
+#connect-fail {
+    display: none;
+}
+#connect-details {
+    display: none;
+}
+.fr {
+    float: right;
+    margin-right: 20px;
+}
+.w0 {
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTJDBGvsAAABzUlEQVRIS+WUTShEURTH3zyRhjQ+8hWxmCJMoSzEwsbCgi1LZRYW9pONptiwka9iI81CWFpYaEqNMkVKmpWN1IhYKN9ZDL/z3p3mxZh5g9X4168799xz/vPefedeLeuVC+3gdTgc07CsmCQ2DI2gg21Jci30wSpGt/CeghickTsHPVACDkgqp67rPgpO4E0ZZMIj7OHhxSvPtEyomcVDeFXJv+EZNvEsNa01rZfAuSUhThR2wU+ObJkbyhRNMMDaDIThBqy1MdZ3wAPawqfFC2Lj0Ab5kpBGxdAJs9TeW72ITUhCPZMjFYwwbwXpnkwlDzOIx50yXwP5c0MeggHGanNqSDqqBqQ7/Kxvg2zHAfMN8IE8uZhYO6eBnBXGKnOakLWfaQZ9jMRjSPXhZUuC5A9JjVFpKkeNSVVA0Tq8KJN0yFl4gilqbW2tm+SQKoybXIG8jcT34RSsh1Byt6iVg2ZLlRCg6JpROqEDpFheXZ5S9rcLFsl5YJwHad+MVA5y13w5lRY5oRsKjdm/Vz/7LR86zG+5wr+9NX+iOowjEO+aELEic+lv1ILppeUPosRst6QduTANgnE2mC+BnYswI1VwfYzCCL9dZij7pWkf6UeSTYAuE/QAAAAASUVORK5CYII=') no-repeat right top;
+    height: 24px;
+    margin-right: 20px;
+}
+.w1 {
+    background:  url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEQAACxEBf2RfkQAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xNkRpr/UAAAHiSURBVEhL5dRPKINxHMfxPVskpA35F3FYEVasHBYHFwcHrhyVZ8vBfblIceEi/4qLtINwdHCQUpQVKcnJRYqIg/J3OYz399nv0YPNtuzEt149+31/v+/n4fGYLVHpup4Rnyregd+K27TIghe63+8fx7wySqsPdbAj3qzha0MOV6ETiwTd4u0HUZxydgrtKISGj0xreG4gEAgycIRXFZCOR2yTQZSebeaa4Q1s7iOiDv/GM1bJLDJv0EHjzHLAdIFNjHBGHpkbxUo9utmbQBg3sM5G2d+AR24w82XznN4QmpGjXrCExRkXfJhk9t6aRW9YDtSwOFDNE9ZNyFLzKRczOegh406FL8ElG8JDM8S1Qtaq7KhEO0Y0TVtHGHusVxCEDy5oMLNqyVrgWm5kqaYw3mdVdmqQsENE8JbAPbY43yszMqiyHOr66QayL5XH0DJeVEgyUTxhjNmPR/vtBpZyc3hHDZohV5DfRvq7OMYtrDdZY7YwFpG8yhBi6JrrMFogww7IT1mOVsxy5oHrNIqRVpWgDtnGKn7log35xurfVxfPW/7QYT57Ybz7mapqgk9gvjU79ApiW5mpRkIvLTe4oJfyK5lKOQndgvG/wXoOSb8I061Svj4G0M9nZ6z198tmeweYtIrMYP17VAAAAABJRU5ErkJggg==') no-repeat right top;
+    height: 24px;
+    margin-right: 20px;
+}
+.w2 {
+    background:  url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEQAACxEBf2RfkQAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xNkRpr/UAAAHkSURBVEhL3dRLKERRGMDxuSPSkLzyilgo8iiUhVjYWFiwZancmSzsZSPFho28io1kISwtLKQURZGSrGykiFgo7yyG/zdzznRm5iK5Sk79uvd85/u++5hzx2Pb9q9yDLrJMWhIRB1sv98/ghlliFAXyuGFU21IbECSi9CKORrd4O0TQZyQO45mZMJCpKfZ3BcIBPooOMSravAdD9ikB63sJN1XN69kcQ8vKvknnrBMzyx9gRYCp0aCdo51DJIjr6wU2UoF2lkbxS6uYdYGWV9DtVxgMmbxjFg/apEM/ZQfyUADxqi9M3sRG5CEEib7KnjMvAaye2IbfUVupoMet6r5PDL0YjXBBY4Fai5kRxVCdscg66uQ17HDfAl9kDuXJzB3Thk5sxzzZa6DumHknN3QS+IBPvvh5ZVskN8ZU5+gz3XAlELRIp5Vk6/It/CIYWrjXm3URCkleUsV6iaXkKeR+DaOYH6EkrtCrXxoUf2iJoY8LFB0xXEA9ZBieXS5S3m/jZgi557jBGT7xvWKCxhyIP81ka/SgQ9NSDViURyDbvpTo82yrAPscl4HKxR1aRTT+BhvyhaxtPCSO6OKphfGBc6JZYaX3BnpNN1AUC7AfBrJoRUXR67X6+1BN+fp4dD/Hx7PO4o9VGuAapKIAAAAAElFTkSuQmCC') no-repeat right top;
+    height: 24px;
+    margin-right: 20px;
+}
+.w3 {
+    background:  url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAACAElEQVRIS7XUP0gbYRjH8VSpiBZJWvEflXYQKtqACg6ig4uDg651LPQSHLpLlyDoUhdpVdBFxEG0YwcHCQgRFJSCFKcuUohY6iC0VYtD9PuE9w3vXZ74h16HD3fv733e53KX9y7ied5/pYZhUkPHQ3TBSyQS7zFvTBC9RivKoK3NCwZS3IxBLNLoBFc3yOEbtR/Qj8d4gEJPt3lVMpkcY8E+Lk2D+/iDTXrQyquwfW3zdiZ38dcU/4tzrNHzib3AAMGhU2BlsYFxauSRtaDWaMMwc1PYwU+4a3PMryMuF5gJTH4ne4dOVMLeZSkx9GCatb/cXmQpKXjOYM+EB4w7ILsn2Og28mNe0ePUNF9CzE7GCZc5NpmxkB31FLI7xpn/DHkc24xXMQb55XIH7s55Qc0Cx0YZ29A2LJyzG95S+AU3/fHySNLUjwTWl9tzG7iqWbSCC9PkNvIunGGStUWP1jcwWijOmIW2yTHkbiTfwle4L6HUfmKtvGi+fr6BowHLLPrBMYVuyGK5dfmV8nx7MUvNb44fIdu3qFdR4KiDfGsKb6WiCn145GQ+ahgmNQyTGpYwxPOWP3qHc/mE+76apaih4hmND2B3TYasJlCjUkPFS5oeORfIkhVtSY0aKqI0TSP/bjCew10+hPf6D+r5fIziDefRwFxJahgmNQyPF7kGEsc1es+A2E4AAAAASUVORK5CYII=') no-repeat right top;
+    height: 24px;
+    margin-right: 20px;
+}
+.pw {
+    background:  url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTJDBGvsAAABIUlEQVRIS+3VsU7CUBTGcYhBJCwqwcmEJ2DkCQgzb8ADmLgYWXTzMVjcGNjYGEAXgoSRhTg5OroYIyFY/h+hWGwvtzQ0LpzkF8i5l/uRQ2kTjuPEKrC5T79vzHWJO4wxwzeGuMY5AitsQBFvmEObvNQboQBfhQk4gQ5wD+zgBrcYrHrSwzE2KkxAHVrQWB6QgiqJLB7xA+2pYaNsAWm8QAsa0Sn+1gU+oT1NHGFdtoAcJtBCSw1DuaPqQiNdly0gj1doQaMwleavPc+IJUDffKeADO7Rxxe08A4dEOQD2qPXJ1xh+VuYAirQVaNGFFPov2MM0OXm/UAUZRwCtjoEWP1vQBXuLTgKPYRKMAacoY0oIboDNLB8+PgC4hLY3B8nsQCQEf56jLJoQAAAAABJRU5ErkJggg==') no-repeat right top;
+    height: 24px;
+    margin-right: 20px;
+    height: 24px;
+    margin-right: 30px;
+}
+/* SpinKit is licensed under the MIT License. Copyright (c) 2015 Tobias Ahlin */
+
+.spinner {
+    width: 40px;
+    height: 40px;
+    position: relative;
+    margin: 100px auto;
+}
+.double-bounce1,
+.double-bounce2 {
+    width: 100%;
+    height: 100%;
+    border-radius: 50%;
+    background-color: #333;
+    opacity: 0.6;
+    position: absolute;
+    top: 0;
+    left: 0;
+    -webkit-animation: sk-bounce 2.0s infinite ease-in-out;
+    animation: sk-bounce 2.0s infinite ease-in-out;
+}
+.double-bounce2 {
+    -webkit-animation-delay: -1.0s;
+    animation-delay: -1.0s;
+}
+@-webkit-keyframes sk-bounce {
+    0%, 100% {
+        -webkit-transform: scale(0.0)
+    }
+    50% {
+        -webkit-transform: scale(1.0)
+    }
+}
+@keyframes sk-bounce {
+    0%, 100% {
+        transform: scale(0.0);
+        -webkit-transform: scale(0.0);
+    }
+    50% {
+        transform: scale(1.0);
+        -webkit-transform: scale(1.0);
+    }
+}
+/* end of SpinKit */

BIN
components/wifi-manager/wifi0.png


BIN
components/wifi-manager/wifi1.png


BIN
components/wifi-manager/wifi2.png


BIN
components/wifi-manager/wifi24.png


BIN
components/wifi-manager/wifi3.png


+ 1138 - 0
components/wifi-manager/wifi_manager.c

@@ -0,0 +1,1138 @@
+/*
+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 wifi_manager.c
+@author Tony Pottier
+@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis
+
+Contains the freeRTOS task and all necessary support
+
+@see https://idyl.io
+@see https://github.com/tonyp7/esp32-wifi-manager
+*/
+
+#include "wifi_manager.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "dns_server.h"
+#include "http_server.h"
+#include "json.h"
+#include "esp_system.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_event_loop.h"
+#include "esp_wifi.h"
+#include "esp_wifi_types.h"
+#include "esp_log.h"
+#include "nvs.h"
+#include "nvs_flash.h"
+#include "mdns.h"
+#include "lwip/api.h"
+#include "lwip/err.h"
+#include "lwip/netdb.h"
+#include "lwip/ip4_addr.h"
+
+
+
+
+
+/* objects used to manipulate the main queue of events */
+QueueHandle_t wifi_manager_queue;
+
+SemaphoreHandle_t wifi_manager_json_mutex = NULL;
+SemaphoreHandle_t wifi_manager_sta_ip_mutex = NULL;
+char *wifi_manager_sta_ip = NULL;
+uint16_t ap_num = MAX_AP_NUM;
+wifi_ap_record_t *accessp_records;
+char *accessp_json = NULL;
+char *ip_info_json = NULL;
+wifi_config_t* wifi_manager_config_sta = NULL;
+
+void (**cb_ptr_arr)(void*) = NULL;
+
+/* @brief tag used for ESP serial console messages */
+static const char TAG[] = "wifi_manager";
+
+/* @brief task handle for the main wifi_manager task */
+static TaskHandle_t task_wifi_manager = NULL;
+
+/**
+ * The actual WiFi settings in use
+ */
+struct wifi_settings_t wifi_settings = {
+	.ap_ssid = DEFAULT_AP_SSID,
+	.ap_pwd = DEFAULT_AP_PASSWORD,
+	.ap_channel = DEFAULT_AP_CHANNEL,
+	.ap_ssid_hidden = DEFAULT_AP_SSID_HIDDEN,
+	.ap_bandwidth = DEFAULT_AP_BANDWIDTH,
+	.sta_only = DEFAULT_STA_ONLY,
+	.sta_power_save = DEFAULT_STA_POWER_SAVE,
+	.sta_static_ip = 0,
+};
+
+const char wifi_manager_nvs_namespace[] = "espwifimgr";
+
+EventGroupHandle_t wifi_manager_event_group;
+
+/* @brief indicate that the ESP32 is currently connected. */
+const int WIFI_MANAGER_WIFI_CONNECTED_BIT = BIT0;
+
+const int WIFI_MANAGER_AP_STA_CONNECTED_BIT = BIT1;
+
+/* @brief Set automatically once the SoftAP is started */
+const int WIFI_MANAGER_AP_STARTED_BIT = BIT2;
+
+/* @brief When set, means a client requested to connect to an access point.*/
+const int WIFI_MANAGER_REQUEST_STA_CONNECT_BIT = BIT3;
+
+/* @brief This bit is set automatically as soon as a connection was lost */
+const int WIFI_MANAGER_STA_DISCONNECT_BIT = BIT4;
+
+/* @brief When set, means the wifi manager attempts to restore a previously saved connection at startup. */
+const int WIFI_MANAGER_REQUEST_RESTORE_STA_BIT = BIT5;
+
+/* @brief When set, means a client requested to disconnect from currently connected AP. */
+const int WIFI_MANAGER_REQUEST_WIFI_DISCONNECT_BIT = BIT6;
+
+/* @brief When set, means a scan is in progress */
+const int WIFI_MANAGER_SCAN_BIT = BIT7;
+
+/* @brief When set, means user requested for a disconnect */
+const int WIFI_MANAGER_REQUEST_DISCONNECT_BIT = BIT8;
+
+
+
+
+void wifi_manager_scan_async(){
+	wifi_manager_send_message(ORDER_START_WIFI_SCAN, NULL);
+}
+
+void wifi_manager_disconnect_async(){
+	wifi_manager_send_message(ORDER_DISCONNECT_STA, NULL);
+	//xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_WIFI_DISCONNECT_BIT); TODO: delete
+}
+
+
+void wifi_manager_start(){
+
+	/* disable the default wifi logging */
+	esp_log_level_set("wifi", ESP_LOG_NONE);
+
+	/* initialize flash memory */
+	nvs_flash_init();
+
+	/* memory allocation */
+	wifi_manager_queue = xQueueCreate( 3, sizeof( queue_message) );
+	wifi_manager_json_mutex = xSemaphoreCreateMutex();
+	accessp_records = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * MAX_AP_NUM);
+	accessp_json = (char*)malloc(MAX_AP_NUM * JSON_ONE_APP_SIZE + 4); /* 4 bytes for json encapsulation of "[\n" and "]\0" */
+	wifi_manager_clear_access_points_json();
+	ip_info_json = (char*)malloc(sizeof(char) * JSON_IP_INFO_SIZE);
+	wifi_manager_clear_ip_info_json();
+	wifi_manager_config_sta = (wifi_config_t*)malloc(sizeof(wifi_config_t));
+	memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t));
+	memset(&wifi_settings.sta_static_ip_config, 0x00, sizeof(tcpip_adapter_ip_info_t));
+	cb_ptr_arr = malloc(  sizeof(   sizeof( void (*)( void* ) )        ) * MESSAGE_CODE_COUNT);
+	for(int i=0; i<MESSAGE_CODE_COUNT; i++){
+		cb_ptr_arr[i] = NULL;
+	}
+	wifi_manager_sta_ip_mutex = xSemaphoreCreateMutex();
+	wifi_manager_sta_ip = (char*)malloc(sizeof(char) * IP4ADDR_STRLEN_MAX);
+	wifi_manager_safe_update_sta_ip_string((uint32_t)0);
+
+	/* start wifi manager task */
+	xTaskCreate(&wifi_manager, "wifi_manager", 4096, NULL, WIFI_MANAGER_TASK_PRIORITY, &task_wifi_manager);
+}
+uint8_t wifi_manager_get_flag(){
+	uint8_t value=0;
+	nvs_handle handle;
+	esp_err_t esp_err;
+	ESP_LOGI(TAG, "About to get config from flash");
+
+	esp_err = nvs_open(wifi_manager_nvs_namespace, NVS_READWRITE, &handle);
+	if (esp_err != ESP_OK) return 0;
+
+	esp_err= nvs_get_u8(handle, "autoexec", &value);
+	nvs_close(handle);
+	return value;
+
+}
+
+char * wifi_manager_alloc_get_config(char * name, size_t * l){
+
+	size_t len=0;
+	char * value=NULL;
+
+	nvs_handle handle;
+	ESP_LOGD(TAG, "About to get config value %s from flash",name);
+
+	if (nvs_open(wifi_manager_nvs_namespace, NVS_READWRITE, &handle) == ESP_OK) {
+		if (nvs_get_str(handle, name, NULL, &len)==ESP_OK) {
+			value=(char *)malloc(len);
+			memset(value,0x0, len);
+			nvs_get_str(handle, name, value, &len);
+			*l=len;
+			ESP_LOGD(TAG,"Found value %s, length %u = %s",name,*l,value);
+		}
+		else
+		{
+			ESP_LOGW(TAG, "Value %s does one exist in flash",name);
+		}
+		nvs_close(handle);
+	}
+	else
+	{
+		ESP_LOGE(TAG,"Unable to open nvs namespace %s",wifi_manager_nvs_namespace);
+	}
+	return value;
+
+}
+
+esp_err_t wifi_manager_save_autoexec_flag(uint8_t flag){
+	nvs_handle handle;
+	esp_err_t esp_err;
+	ESP_LOGI(TAG, "About to save config to flash");
+	esp_err=nvs_open(wifi_manager_nvs_namespace, NVS_READWRITE, &handle);
+	if (esp_err != ESP_OK) {
+		ESP_LOGE(TAG,"Unable to open nvs namespace %s",wifi_manager_nvs_namespace);
+		return esp_err;
+	}
+
+	esp_err = nvs_set_u8(handle, "autoexec", flag);
+	if (esp_err != ESP_OK){
+		ESP_LOGE(TAG,"Unable to save autoexec flag value %u",flag);
+		nvs_close(handle);
+		return esp_err;
+	}
+
+	esp_err = nvs_commit(handle);
+	if (esp_err != ESP_OK){
+		ESP_LOGE(TAG,"nvs commit error");
+		return esp_err;
+	}
+
+	nvs_close(handle);
+
+	ESP_LOGD(TAG, "wifi_manager_wrote autoexec flag value %u",flag);
+
+	return ESP_OK;
+}
+esp_err_t wifi_manager_save_autoexec_config(char * value, char * name, int len){
+	nvs_handle handle;
+	esp_err_t esp_err;
+	ESP_LOGI(TAG, "About to save config to flash");
+	esp_err = nvs_open(wifi_manager_nvs_namespace, NVS_READWRITE, &handle);
+	if (esp_err != ESP_OK) {
+		ESP_LOGE(TAG,"Unable to open nvs namespace %s",wifi_manager_nvs_namespace);
+		return esp_err;
+	}
+
+	esp_err = nvs_set_str(handle, name, value);
+	if (esp_err != ESP_OK){
+		ESP_LOGE(TAG,"Unable to save value %s=%s",name,value);
+		nvs_close(handle);
+		return esp_err;
+	}
+
+	esp_err = nvs_commit(handle);
+	if (esp_err != ESP_OK){
+		ESP_LOGE(TAG,"nvs commit error");
+		return esp_err;
+	}
+
+	nvs_close(handle);
+
+	ESP_LOGD(TAG, "wifi_manager_wrote %s config %s",name,value);
+
+	return ESP_OK;
+
+}
+esp_err_t wifi_manager_save_sta_config(){
+
+	nvs_handle handle;
+	esp_err_t esp_err;
+	ESP_LOGI(TAG, "About to save config to flash");
+
+	if(wifi_manager_config_sta){
+
+		esp_err = nvs_open(wifi_manager_nvs_namespace, NVS_READWRITE, &handle);
+		if (esp_err != ESP_OK) return esp_err;
+
+		esp_err = nvs_set_blob(handle, "ssid", wifi_manager_config_sta->sta.ssid, 32);
+		if (esp_err != ESP_OK) return esp_err;
+
+		esp_err = nvs_set_blob(handle, "password", wifi_manager_config_sta->sta.password, 64);
+		if (esp_err != ESP_OK) return esp_err;
+
+		esp_err = nvs_set_blob(handle, "settings", &wifi_settings, sizeof(wifi_settings));
+		if (esp_err != ESP_OK) return esp_err;
+
+		esp_err = nvs_commit(handle);
+		if (esp_err != ESP_OK) return esp_err;
+
+		nvs_close(handle);
+
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_sta_config: ssid:%s password:%s",wifi_manager_config_sta->sta.ssid,wifi_manager_config_sta->sta.password);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_ssid: %s",wifi_settings.ap_ssid);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_pwd: %s",wifi_settings.ap_pwd);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_channel: %i",wifi_settings.ap_channel);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_hidden (1 = yes): %i",wifi_settings.ap_ssid_hidden);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: SoftAP_bandwidth (1 = 20MHz, 2 = 40MHz): %i",wifi_settings.ap_bandwidth);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_only (0 = APSTA, 1 = STA when connected): %i",wifi_settings.sta_only);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_power_save (1 = yes): %i",wifi_settings.sta_power_save);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_static_ip (0 = dhcp client, 1 = static ip): %i",wifi_settings.sta_static_ip);
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_ip_addr: %s", ip4addr_ntoa(&wifi_settings.sta_static_ip_config.ip));
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_gw_addr: %s", ip4addr_ntoa(&wifi_settings.sta_static_ip_config.gw));
+		ESP_LOGD(TAG, "wifi_manager_wrote wifi_settings: sta_netmask: %s", ip4addr_ntoa(&wifi_settings.sta_static_ip_config.netmask));
+
+	}
+
+	return ESP_OK;
+}
+
+bool wifi_manager_fetch_wifi_sta_config(){
+
+	nvs_handle handle;
+	esp_err_t esp_err;
+
+	if(nvs_open(wifi_manager_nvs_namespace, NVS_READONLY, &handle) == ESP_OK){
+
+		if(wifi_manager_config_sta == NULL){
+			wifi_manager_config_sta = (wifi_config_t*)malloc(sizeof(wifi_config_t));
+		}
+		memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t));
+
+		//memset(&wifi_settings, 0x00, sizeof(struct wifi_settings_t));
+
+		/* allocate buffer */
+		size_t sz = sizeof(wifi_settings);
+		uint8_t *buff = (uint8_t*)malloc(sizeof(uint8_t) * sz);
+		memset(buff, 0x00, sizeof(sz));
+
+		/* ssid */
+		sz = sizeof(wifi_manager_config_sta->sta.ssid);
+		esp_err = nvs_get_blob(handle, "ssid", buff, &sz);
+		if(esp_err != ESP_OK){
+			free(buff);
+			return false;
+		}
+		memcpy(wifi_manager_config_sta->sta.ssid, buff, sz);
+
+
+
+		/* password */
+		sz = sizeof(wifi_manager_config_sta->sta.password);
+		esp_err = nvs_get_blob(handle, "password", buff, &sz);
+		if(esp_err != ESP_OK){
+			free(buff);
+			return false;
+		}
+		memcpy(wifi_manager_config_sta->sta.password, buff, sz);
+		/* memcpy(wifi_manager_config_sta->sta.password, "lewrong", strlen("lewrong")); this is debug to force a wrong password event. ignore! */
+
+		/* settings */
+		sz = sizeof(wifi_settings);
+		esp_err = nvs_get_blob(handle, "settings", buff, &sz);
+		if(esp_err != ESP_OK){
+			free(buff);
+			return false;
+		}
+		memcpy(&wifi_settings, buff, sz);
+
+		free(buff);
+		nvs_close(handle);
+
+
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_sta_config: ssid:%s password:%s",wifi_manager_config_sta->sta.ssid,wifi_manager_config_sta->sta.password);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_ssid:%s",wifi_settings.ap_ssid);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_pwd:%s",wifi_settings.ap_pwd);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_channel:%i",wifi_settings.ap_channel);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_hidden (1 = yes):%i",wifi_settings.ap_ssid_hidden);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: SoftAP_bandwidth (1 = 20MHz, 2 = 40MHz)%i",wifi_settings.ap_bandwidth);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: sta_only (0 = APSTA, 1 = STA when connected):%i",wifi_settings.sta_only);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: sta_power_save (1 = yes):%i",wifi_settings.sta_power_save);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: sta_static_ip (0 = dhcp client, 1 = static ip):%i",wifi_settings.sta_static_ip);
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: sta_static_ip_config: IP: %s , GW: %s , Mask: %s", ip4addr_ntoa(&wifi_settings.sta_static_ip_config.ip), ip4addr_ntoa(&wifi_settings.sta_static_ip_config.gw), ip4addr_ntoa(&wifi_settings.sta_static_ip_config.netmask));
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: sta_ip_addr: %s", ip4addr_ntoa(&wifi_settings.sta_static_ip_config.ip));
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: sta_gw_addr: %s", ip4addr_ntoa(&wifi_settings.sta_static_ip_config.gw));
+		ESP_LOGI(TAG, "wifi_manager_fetch_wifi_settings: sta_netmask: %s", ip4addr_ntoa(&wifi_settings.sta_static_ip_config.netmask));
+
+		return wifi_manager_config_sta->sta.ssid[0] != '\0';
+
+
+	}
+	else{
+		return false;
+	}
+
+}
+
+
+void wifi_manager_clear_ip_info_json(){
+	strcpy(ip_info_json, "{}\n");
+}
+
+
+void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code){
+
+	wifi_config_t *config = wifi_manager_get_wifi_sta_config();
+	if(config){
+
+		const char ip_info_json_format[] = ",\"ip\":\"%s\",\"netmask\":\"%s\",\"gw\":\"%s\",\"urc\":%d}\n";
+
+		memset(ip_info_json, 0x00, JSON_IP_INFO_SIZE);
+
+
+		/* to avoid declaring a new buffer we copy the data directly into the buffer at its correct address */
+		strcpy(ip_info_json, "{\"ssid\":");
+		json_print_string(config->sta.ssid,  (unsigned char*)(ip_info_json+strlen(ip_info_json)) );
+
+		if(update_reason_code == UPDATE_CONNECTION_OK){
+			/* rest of the information is copied after the ssid */
+			tcpip_adapter_ip_info_t ip_info;
+			ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
+			char ip[IP4ADDR_STRLEN_MAX]; /* note: IP4ADDR_STRLEN_MAX is defined in lwip */
+			char gw[IP4ADDR_STRLEN_MAX];
+			char netmask[IP4ADDR_STRLEN_MAX];
+			strcpy(ip, ip4addr_ntoa(&ip_info.ip));
+			strcpy(netmask, ip4addr_ntoa(&ip_info.netmask));
+			strcpy(gw, ip4addr_ntoa(&ip_info.gw));
+
+			snprintf( (ip_info_json + strlen(ip_info_json)), JSON_IP_INFO_SIZE, ip_info_json_format,
+					ip,
+					netmask,
+					gw,
+					(int)update_reason_code);
+		}
+		else{
+			/* notify in the json output the reason code why this was updated without a connection */
+			snprintf( (ip_info_json + strlen(ip_info_json)), JSON_IP_INFO_SIZE, ip_info_json_format,
+								"0",
+								"0",
+								"0",
+								(int)update_reason_code);
+		}
+	}
+	else{
+		wifi_manager_clear_ip_info_json();
+	}
+
+
+}
+
+
+void wifi_manager_clear_access_points_json(){
+	strcpy(accessp_json, "[]\n");
+}
+void wifi_manager_generate_acess_points_json(){
+
+	strcpy(accessp_json, "[");
+
+
+	const char oneap_str[] = ",\"chan\":%d,\"rssi\":%d,\"auth\":%d}%c\n";
+
+	/* stack buffer to hold on to one AP until it's copied over to accessp_json */
+	char one_ap[JSON_ONE_APP_SIZE];
+	for(int i=0; i<ap_num;i++){
+
+		wifi_ap_record_t ap = accessp_records[i];
+
+		/* ssid needs to be json escaped. To save on heap memory it's directly printed at the correct address */
+		strcat(accessp_json, "{\"ssid\":");
+		json_print_string( (unsigned char*)ap.ssid,  (unsigned char*)(accessp_json+strlen(accessp_json)) );
+
+		/* print the rest of the json for this access point: no more string to escape */
+		snprintf(one_ap, (size_t)JSON_ONE_APP_SIZE, oneap_str,
+				ap.primary,
+				ap.rssi,
+				ap.authmode,
+				i==ap_num-1?']':',');
+
+		/* add it to the list */
+		strcat(accessp_json, one_ap);
+	}
+
+}
+
+
+
+bool wifi_manager_lock_sta_ip_string(TickType_t xTicksToWait){
+	if(wifi_manager_sta_ip_mutex){
+		if( xSemaphoreTake( wifi_manager_sta_ip_mutex, xTicksToWait ) == pdTRUE ) {
+			return true;
+		}
+		else{
+			return false;
+		}
+	}
+	else{
+		return false;
+	}
+
+}
+void wifi_manager_unlock_sta_ip_string(){
+	xSemaphoreGive( wifi_manager_sta_ip_mutex );
+}
+
+void wifi_manager_safe_update_sta_ip_string(uint32_t ip){
+
+	if(wifi_manager_lock_sta_ip_string(portMAX_DELAY)){
+
+		struct ip4_addr ip4;
+		ip4.addr = ip;
+
+		strcpy(wifi_manager_sta_ip, ip4addr_ntoa(&ip4));
+
+		ESP_LOGI(TAG, "Set STA IP String to: %s", wifi_manager_sta_ip);
+
+		wifi_manager_unlock_sta_ip_string();
+
+
+	}
+}
+
+char* wifi_manager_get_sta_ip_string(){
+	return wifi_manager_sta_ip;
+}
+
+
+bool wifi_manager_lock_json_buffer(TickType_t xTicksToWait){
+	if(wifi_manager_json_mutex){
+		if( xSemaphoreTake( wifi_manager_json_mutex, xTicksToWait ) == pdTRUE ) {
+			return true;
+		}
+		else{
+			return false;
+		}
+	}
+	else{
+		return false;
+	}
+
+}
+void wifi_manager_unlock_json_buffer(){
+	xSemaphoreGive( wifi_manager_json_mutex );
+}
+
+char* wifi_manager_get_ap_list_json(){
+	return accessp_json;
+}
+
+
+esp_err_t wifi_manager_event_handler(void *ctx, system_event_t *event)
+{
+
+
+
+    switch(event->event_id) {
+
+    case SYSTEM_EVENT_WIFI_READY:
+    	ESP_LOGI(TAG, "SYSTEM_EVENT_WIFI_READY");
+    	break;
+
+    case SYSTEM_EVENT_SCAN_DONE:
+    	ESP_LOGD(TAG, "SYSTEM_EVENT_SCAN_DONE");
+    	xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT);
+    	wifi_manager_send_message(EVENT_SCAN_DONE, NULL);
+    	break;
+
+    case SYSTEM_EVENT_STA_AUTHMODE_CHANGE:
+    	ESP_LOGI(TAG, "SYSTEM_EVENT_STA_AUTHMODE_CHANGE");
+    	break;
+
+
+    case SYSTEM_EVENT_AP_START:
+    	ESP_LOGI(TAG, "SYSTEM_EVENT_AP_START");
+    	xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STARTED_BIT);
+		break;
+
+    case SYSTEM_EVENT_AP_STOP:
+    	break;
+
+    case SYSTEM_EVENT_AP_PROBEREQRECVED:
+    	break;
+
+    case SYSTEM_EVENT_AP_STACONNECTED: /* a user disconnected from the SoftAP */
+    	ESP_LOGI(TAG, "SYSTEM_EVENT_AP_STACONNECTED");
+		xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_AP_STA_CONNECTED_BIT);
+		break;
+
+    case SYSTEM_EVENT_AP_STADISCONNECTED:
+    	ESP_LOGI(TAG, "SYSTEM_EVENT_AP_STADISCONNECTED");
+    	xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_AP_STA_CONNECTED_BIT);
+		break;
+
+    case SYSTEM_EVENT_STA_START:
+    	ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
+        break;
+
+    case SYSTEM_EVENT_STA_STOP:
+    	ESP_LOGI(TAG, "SYSTEM_EVENT_STA_STOP");
+    	break;
+
+	case SYSTEM_EVENT_STA_GOT_IP:
+		ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
+        xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT);
+        wifi_manager_send_message(EVENT_STA_GOT_IP, (void*)event->event_info.got_ip.ip_info.ip.addr );
+        break;
+
+	case SYSTEM_EVENT_STA_CONNECTED:
+		ESP_LOGI(TAG, "SYSTEM_EVENT_STA_CONNECTED");
+		break;
+
+	case SYSTEM_EVENT_STA_DISCONNECTED:
+		ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
+
+		/* if a DISCONNECT message is posted while a scan is in progress this scan will NEVER end, causing scan to never work again. For this reason SCAN_BIT is cleared too */
+		xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_WIFI_CONNECTED_BIT | WIFI_MANAGER_SCAN_BIT);
+
+		/* post disconnect event with reason code */
+		wifi_manager_send_message(EVENT_STA_DISCONNECTED, (void*)( (uint32_t)event->event_info.disconnected.reason) );
+        break;
+
+	default:
+        break;
+    }
+	return ESP_OK;
+}
+
+wifi_config_t* wifi_manager_get_wifi_sta_config(){
+	return wifi_manager_config_sta;
+}
+
+
+void wifi_manager_connect_async(){
+	/* in order to avoid a false positive on the front end app we need to quickly flush the ip json
+	 * There'se a risk the front end sees an IP or a password error when in fact
+	 * it's a remnant from a previous connection
+	 */
+	if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
+		wifi_manager_clear_ip_info_json();
+		wifi_manager_unlock_json_buffer();
+	}
+	wifi_manager_send_message(ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_USER);
+}
+
+
+char* wifi_manager_get_ip_info_json(){
+	return ip_info_json;
+}
+
+void wifi_manager_destroy(){
+
+	vTaskDelete(task_wifi_manager);
+	task_wifi_manager = NULL;
+
+	/* heap buffers */
+	free(accessp_records);
+	accessp_records = NULL;
+	free(accessp_json);
+	accessp_json = NULL;
+	free(ip_info_json);
+	ip_info_json = NULL;
+	free(wifi_manager_sta_ip);
+	wifi_manager_sta_ip = NULL;
+	if(wifi_manager_config_sta){
+		free(wifi_manager_config_sta);
+		wifi_manager_config_sta = NULL;
+	}
+
+	/* RTOS objects */
+	vSemaphoreDelete(wifi_manager_json_mutex);
+	wifi_manager_json_mutex = NULL;
+	vSemaphoreDelete(wifi_manager_sta_ip_mutex);
+	wifi_manager_sta_ip_mutex = NULL;
+	vEventGroupDelete(wifi_manager_event_group);
+	wifi_manager_event_group = NULL;
+	vQueueDelete(wifi_manager_queue);
+	wifi_manager_queue = NULL;
+
+
+}
+
+
+void wifi_manager_filter_unique( wifi_ap_record_t * aplist, uint16_t * aps) {
+	int total_unique;
+	wifi_ap_record_t * first_free;
+	total_unique=*aps;
+
+	first_free=NULL;
+
+	for(int i=0; i<*aps-1;i++) {
+		wifi_ap_record_t * ap = &aplist[i];
+
+		/* skip the previously removed APs */
+		if (ap->ssid[0] == 0) continue;
+
+		/* remove the identical SSID+authmodes */
+		for(int j=i+1; j<*aps;j++) {
+			wifi_ap_record_t * ap1 = &aplist[j];
+			if ( (strcmp((const char *)ap->ssid, (const char *)ap1->ssid)==0) && 
+			     (ap->authmode == ap1->authmode) ) { /* same SSID, different auth mode is skipped */
+				/* save the rssi for the display */
+				if ((ap1->rssi) > (ap->rssi)) ap->rssi=ap1->rssi;
+				/* clearing the record */
+				memset(ap1,0, sizeof(wifi_ap_record_t));
+			}
+		}
+	}
+	/* reorder the list so APs follow each other in the list */
+	for(int i=0; i<*aps;i++) {
+		wifi_ap_record_t * ap = &aplist[i];
+		/* skipping all that has no name */
+		if (ap->ssid[0] == 0) {
+			/* mark the first free slot */
+			if (first_free==NULL) first_free=ap;
+			total_unique--;
+			continue;
+		}
+		if (first_free!=NULL) {
+			memcpy(first_free, ap, sizeof(wifi_ap_record_t));
+			memset(ap,0, sizeof(wifi_ap_record_t));
+			/* find the next free slot */
+			for(int j=0; j<*aps;j++) {
+				if (aplist[j].ssid[0]==0) {
+					first_free=&aplist[j];
+					break;
+				}
+			}
+		}
+	}
+	/* update the length of the list */
+	*aps = total_unique;
+}
+
+
+BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param){
+	queue_message msg;
+	msg.code = code;
+	msg.param = param;
+	return xQueueSendToFront( wifi_manager_queue, &msg, portMAX_DELAY);
+}
+
+BaseType_t wifi_manager_send_message(message_code_t code, void *param){
+	queue_message msg;
+	msg.code = code;
+	msg.param = param;
+	return xQueueSend( wifi_manager_queue, &msg, portMAX_DELAY);
+}
+
+
+void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) ){
+
+	if(cb_ptr_arr && message_code < MESSAGE_CODE_COUNT){
+		cb_ptr_arr[message_code] = func_ptr;
+	}
+}
+
+void wifi_manager( void * pvParameters ){
+
+
+	queue_message msg;
+	BaseType_t xStatus;
+	EventBits_t uxBits;
+	uint8_t	retries = 0;
+
+
+
+
+
+	/* initialize the tcp stack */
+	tcpip_adapter_init();
+
+	/* event handler and event group for the wifi driver */
+	wifi_manager_event_group = xEventGroupCreate();
+	ESP_ERROR_CHECK(esp_event_loop_init(wifi_manager_event_handler, NULL));
+
+	/* wifi scanner config */
+	wifi_scan_config_t scan_config = {
+		.ssid = 0,
+		.bssid = 0,
+		.channel = 0,
+		.show_hidden = true
+	};
+
+
+	/* default wifi config */
+	wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
+	ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config));
+	ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+
+
+
+	/* SoftAP - Wifi Access Point configuration setup */
+	tcpip_adapter_ip_info_t info;
+	memset(&info, 0x00, sizeof(info));
+	wifi_config_t ap_config = {
+		.ap = {
+			.ssid_len = 0,
+			.channel = wifi_settings.ap_channel,
+			.authmode = WIFI_AUTH_WPA2_PSK,
+			.ssid_hidden = wifi_settings.ap_ssid_hidden,
+			.max_connection = DEFAULT_AP_MAX_CONNECTIONS,
+			.beacon_interval = DEFAULT_AP_BEACON_INTERVAL,
+		},
+	};
+	memcpy(ap_config.ap.ssid, wifi_settings.ap_ssid , sizeof(wifi_settings.ap_ssid));
+	memcpy(ap_config.ap.password, wifi_settings.ap_pwd, sizeof(wifi_settings.ap_pwd));
+
+	ESP_ERROR_CHECK(tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP)); 	/* stop AP DHCP server */
+	inet_pton(AF_INET, DEFAULT_AP_IP, &info.ip); /* access point is on a static IP */
+	inet_pton(AF_INET, DEFAULT_AP_GATEWAY, &info.gw);
+	inet_pton(AF_INET, DEFAULT_AP_NETMASK, &info.netmask);
+	ESP_ERROR_CHECK(tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info));
+	ESP_ERROR_CHECK(tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP)); /* start AP DHCP server */
+
+	ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
+	ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));
+	ESP_ERROR_CHECK(esp_wifi_set_bandwidth(WIFI_IF_AP, wifi_settings.ap_bandwidth));
+	ESP_ERROR_CHECK(esp_wifi_set_ps(wifi_settings.sta_power_save));
+
+
+	/* STA - Wifi Station configuration setup */
+	tcpip_adapter_dhcp_status_t status;
+	if(wifi_settings.sta_static_ip) {
+		ESP_LOGI(TAG, "Assigning static ip to STA interface. IP: %s , GW: %s , Mask: %s",
+						ip4addr_ntoa(&wifi_settings.sta_static_ip_config.ip),
+						ip4addr_ntoa(&wifi_settings.sta_static_ip_config.gw),
+						ip4addr_ntoa(&wifi_settings.sta_static_ip_config.netmask));
+
+		/* stop DHCP client*/
+		ESP_ERROR_CHECK(tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA));
+		/* assign a static IP to the STA network interface */
+		ESP_ERROR_CHECK(tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &wifi_settings.sta_static_ip_config));
+		}
+	else {
+		/* start DHCP client if not started*/
+		ESP_LOGI(TAG, "wifi_manager: Start DHCP client for STA interface. If not already running");
+		ESP_ERROR_CHECK(tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &status));
+		if (status!=TCPIP_ADAPTER_DHCP_STARTED)
+			ESP_ERROR_CHECK(tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA));
+	}
+
+
+
+	/* by default the mode is STA because wifi_manager will not start the access point unless it has to! */
+	ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+	ESP_ERROR_CHECK(esp_wifi_start());
+
+
+	/* start http server */
+	http_server_start();
+
+	/* enqueue first event: load previous config */
+	wifi_manager_send_message(ORDER_LOAD_AND_RESTORE_STA, NULL);
+
+
+	/* main processing loop */
+	for(;;){
+		xStatus = xQueueReceive( wifi_manager_queue, &msg, portMAX_DELAY );
+
+		if( xStatus == pdPASS ){
+			switch(msg.code){
+
+			case EVENT_SCAN_DONE:
+				/* As input param, it stores max AP number ap_records can hold. As output param, it receives the actual AP number this API returns.
+				 * As a consequence, ap_num MUST be reset to MAX_AP_NUM at every scan */
+				ap_num = MAX_AP_NUM;
+				ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_num, accessp_records));
+				/* make sure the http server isn't trying to access the list while it gets refreshed */
+				if(wifi_manager_lock_json_buffer( pdMS_TO_TICKS(1000) )){
+					/* Will remove the duplicate SSIDs from the list and update ap_num */
+					wifi_manager_filter_unique(accessp_records, &ap_num);
+					wifi_manager_generate_acess_points_json();
+					wifi_manager_unlock_json_buffer();
+				}
+				else{
+					ESP_LOGE(TAG, "could not get access to json mutex in wifi_scan");
+				}
+
+				/* callback */
+				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
+
+				break;
+
+			case ORDER_START_WIFI_SCAN:
+				ESP_LOGD(TAG, "MESSAGE: ORDER_START_WIFI_SCAN");
+
+				/* if a scan is already in progress this message is simply ignored thanks to the WIFI_MANAGER_SCAN_BIT uxBit */
+				uxBits = xEventGroupGetBits(wifi_manager_event_group);
+				if(! (uxBits & WIFI_MANAGER_SCAN_BIT) ){
+					xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT);
+					ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, false));
+				}
+
+				/* callback */
+				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
+
+				break;
+
+			case ORDER_LOAD_AND_RESTORE_STA:
+				ESP_LOGI(TAG, "MESSAGE: ORDER_LOAD_AND_RESTORE_STA");
+				if(wifi_manager_fetch_wifi_sta_config()){
+					ESP_LOGI(TAG, "Saved wifi found on startup. Will attempt to connect.");
+					wifi_manager_send_message(ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_RESTORE_CONNECTION);
+				}
+				else{
+					/* no wifi saved: start soft AP! This is what should happen during a first run */
+					ESP_LOGI(TAG, "No saved wifi found on startup. Starting access point.");
+					wifi_manager_send_message(ORDER_START_AP, NULL);
+				}
+
+				/* callback */
+				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
+
+				break;
+
+			case ORDER_CONNECT_STA:
+				ESP_LOGI(TAG, "MESSAGE: ORDER_CONNECT_STA");
+
+				/* very important: precise that this connection attempt is specifically requested.
+				 * Param in that case is a boolean indicating if the request was made automatically
+				 * by the wifi_manager.
+				 * */
+				if((BaseType_t)msg.param == CONNECTION_REQUEST_USER) {
+					xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT);
+				}
+				else if((BaseType_t)msg.param == CONNECTION_REQUEST_RESTORE_CONNECTION) {
+					xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT);
+				}
+
+				uxBits = xEventGroupGetBits(wifi_manager_event_group);
+				if( uxBits & WIFI_MANAGER_WIFI_CONNECTED_BIT ){
+					wifi_manager_send_message(ORDER_DISCONNECT_STA, NULL);
+					/* todo: reconnect */
+				}
+				else{
+					/* update config to latest and attempt connection */
+					ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, wifi_manager_get_wifi_sta_config()));
+					ESP_ERROR_CHECK(esp_wifi_connect());
+				}
+
+				/* callback */
+				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
+
+				break;
+
+			case EVENT_STA_DISCONNECTED:
+				ESP_LOGI(TAG, "MESSAGE: EVENT_STA_DISCONNECTED with Reason code: %d", (uint32_t)msg.param);
+
+				/* this even can be posted in numerous different conditions
+				 *
+				 * 1. SSID password is wrong
+				 * 2. Manual disconnection ordered
+				 * 3. Connection lost
+				 *
+				 * Having clear understand as to WHY the event was posted is key to having an efficient wifi manager
+				 *
+				 * With wifi_manager, we determine:
+				 *  If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT is set, We consider it's a client that requested the connection.
+				 *    When SYSTEM_EVENT_STA_DISCONNECTED is posted, it's probably a password/something went wrong with the handshake.
+				 *
+				 *  If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT is set, it's a disconnection that was ASKED by the client (clicking disconnect in the app)
+				 *    When SYSTEM_EVENT_STA_DISCONNECTED is posted, saved wifi is erased from the NVS memory.
+				 *
+				 *  If WIFI_MANAGER_REQUEST_STA_CONNECT_BIT and WIFI_MANAGER_REQUEST_STA_CONNECT_BIT are NOT set, it's a lost connection
+				 *
+				 *  In this version of the software, reason codes are not used. They are indicated here for potential future usage.
+				 *
+				 *  REASON CODE:
+				 *  1		UNSPECIFIED
+				 *  2		AUTH_EXPIRE					auth no longer valid, this smells like someone changed a password on the AP
+				 *  3		AUTH_LEAVE
+				 *  4		ASSOC_EXPIRE
+				 *  5		ASSOC_TOOMANY				too many devices already connected to the AP => AP fails to respond
+				 *  6		NOT_AUTHED
+				 *  7		NOT_ASSOCED
+				 *  8		ASSOC_LEAVE
+				 *  9		ASSOC_NOT_AUTHED
+				 *  10		DISASSOC_PWRCAP_BAD
+				 *  11		DISASSOC_SUPCHAN_BAD
+				 *	12		<n/a>
+				 *  13		IE_INVALID
+				 *  14		MIC_FAILURE
+				 *  15		4WAY_HANDSHAKE_TIMEOUT		wrong password! This was personnaly tested on my home wifi with a wrong password.
+				 *  16		GROUP_KEY_UPDATE_TIMEOUT
+				 *  17		IE_IN_4WAY_DIFFERS
+				 *  18		GROUP_CIPHER_INVALID
+				 *  19		PAIRWISE_CIPHER_INVALID
+				 *  20		AKMP_INVALID
+				 *  21		UNSUPP_RSN_IE_VERSION
+				 *  22		INVALID_RSN_IE_CAP
+				 *  23		802_1X_AUTH_FAILED			wrong password?
+				 *  24		CIPHER_SUITE_REJECTED
+				 *  200		BEACON_TIMEOUT
+				 *  201		NO_AP_FOUND
+				 *  202		AUTH_FAIL
+				 *  203		ASSOC_FAIL
+				 *  204		HANDSHAKE_TIMEOUT
+				 *
+				 * */
+
+				/* reset saved sta IP */
+				wifi_manager_safe_update_sta_ip_string((uint32_t)0);
+
+				uxBits = xEventGroupGetBits(wifi_manager_event_group);
+				if( uxBits & WIFI_MANAGER_REQUEST_STA_CONNECT_BIT ){
+					/* there are no retries when it's a user requested connection by design. This avoids a user hanging too much
+					 * in case they typed a wrong password for instance. Here we simply clear the request bit and move on */
+					xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT);
+
+					if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
+						wifi_manager_generate_ip_info_json( UPDATE_FAILED_ATTEMPT );
+						wifi_manager_unlock_json_buffer();
+					}
+
+				}
+				else if (uxBits & WIFI_MANAGER_REQUEST_DISCONNECT_BIT){
+					/* user manually requested a disconnect so the lost connection is a normal event. Clear the flag and restart the AP */
+					xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_DISCONNECT_BIT);
+
+					if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
+						wifi_manager_generate_ip_info_json( UPDATE_USER_DISCONNECT );
+						wifi_manager_unlock_json_buffer();
+					}
+
+					/* erase configuration */
+					if(wifi_manager_config_sta){
+						memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t));
+					}
+
+					/* save NVS memory */
+					wifi_manager_save_sta_config();
+
+					/* start SoftAP */
+					wifi_manager_send_message(ORDER_START_AP, NULL);
+				}
+				else{
+					/* lost connection ? */
+					if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
+						wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION );
+						wifi_manager_unlock_json_buffer();
+					}
+
+					if(retries < WIFI_MANAGER_MAX_RETRY){
+						retries++;
+						wifi_manager_send_message(ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_AUTO_RECONNECT);
+					}
+					else{
+						/* In this scenario the connection was lost beyond repair: kick start the AP! */
+						retries = 0;
+
+						/* if it was a restore attempt connection, we clear the bit */
+						xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT);
+
+						/* erase configuration that could not be used to connect */
+						if(wifi_manager_config_sta){
+							memset(wifi_manager_config_sta, 0x00, sizeof(wifi_config_t));
+						}
+
+						/* save empty connection info in NVS memory */
+						wifi_manager_save_sta_config();
+
+						/* start SoftAP */
+						wifi_manager_send_message(ORDER_START_AP, NULL);
+					}
+				}
+
+				/* callback */
+				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
+
+				break;
+
+			case ORDER_START_AP:
+				ESP_LOGI(TAG, "MESSAGE: ORDER_START_AP");
+				esp_wifi_set_mode(WIFI_MODE_APSTA);
+
+				//http_server_start();
+				dns_server_start();
+
+				/* callback */
+				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
+
+				break;
+
+			case EVENT_STA_GOT_IP:
+				ESP_LOGI(TAG, "MESSAGE: EVENT_STA_GOT_IP");
+
+				uxBits = xEventGroupGetBits(wifi_manager_event_group);
+
+				/* reset connection requests bits -- doesn't matter if it was set or not */
+				xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_STA_CONNECT_BIT);
+
+				/* save IP as a string for the HTTP server host */
+				wifi_manager_safe_update_sta_ip_string((uint32_t)msg.param);
+
+				/* save wifi config in NVS if it wasn't a restored of a connection */
+				if(uxBits & WIFI_MANAGER_REQUEST_RESTORE_STA_BIT){
+					xEventGroupClearBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_RESTORE_STA_BIT);
+				}
+				else{
+					wifi_manager_save_sta_config();
+				}
+
+				/* refresh JSON with the new IP */
+				if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
+					/* generate the connection info with success */
+					wifi_manager_generate_ip_info_json( UPDATE_CONNECTION_OK );
+					wifi_manager_unlock_json_buffer();
+				}
+				else { abort(); }
+
+				/* bring down DNS hijack */
+				dns_server_stop();
+
+				/* callback */
+				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
+
+				break;
+
+			case ORDER_DISCONNECT_STA:
+				ESP_LOGI(TAG, "MESSAGE: ORDER_DISCONNECT_STA");
+
+				/* precise this is coming from a user request */
+				xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_REQUEST_DISCONNECT_BIT);
+
+				/* order wifi discconect */
+				ESP_ERROR_CHECK(esp_wifi_disconnect());
+
+				/* callback */
+				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
+
+				break;
+
+			default:
+				break;
+
+			} /* end of switch/case */
+		} /* end of if status=pdPASS */
+	} /* end of for loop */
+
+	vTaskDelete( NULL );
+
+}
+
+

+ 397 - 0
components/wifi-manager/wifi_manager.h

@@ -0,0 +1,397 @@
+/*
+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 wifi_manager.h
+@author Tony Pottier
+@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis
+
+Contains the freeRTOS task and all necessary support
+
+@see https://idyl.io
+@see https://github.com/tonyp7/esp32-wifi-manager
+*/
+
+#ifndef WIFI_MANAGER_H_INCLUDED
+#define WIFI_MANAGER_H_INCLUDED
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_wifi_types.h"
+
+
+#define DEFAULT_COMMAND_LINE  CONFIG_DEFAULT_COMMAND_LINE
+
+/**
+ * @brief Defines the maximum size of a SSID name. 32 is IEEE standard.
+ * @warning limit is also hard coded in wifi_config_t. Never extend this value.
+ */
+#define MAX_SSID_SIZE						32
+
+/**
+ * @brief Defines the maximum size of a WPA2 passkey. 64 is IEEE standard.
+ * @warning limit is also hard coded in wifi_config_t. Never extend this value.
+ */
+#define MAX_PASSWORD_SIZE					64
+#define MAX_COMMAND_LINE_SIZE				201
+
+/**
+ * @brief Defines the maximum number of access points that can be scanned.
+ *
+ * To save memory and avoid nasty out of memory errors,
+ * we can limit the number of APs detected in a wifi scan.
+ */
+#define MAX_AP_NUM 							15
+
+
+
+/**
+ * @brief Defines when a connection is lost/attempt to connect is made, how many retries should be made before giving up.
+ * Setting it to 2 for instance means there will be 3 attempts in total (original request + 2 retries)
+ */
+#define	WIFI_MANAGER_MAX_RETRY				CONFIG_WIFI_MANAGER_MAX_RETRY
+
+/** @brief Defines the task priority of the wifi_manager.
+ *
+ * Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1.
+ * For this particular reason, minimum task priority is 1. It it highly not recommended to set
+ * it to 1 though as the sub-tasks will now have a priority of 0 which is the priority
+ * of freeRTOS' idle task.
+ */
+#define WIFI_MANAGER_TASK_PRIORITY			CONFIG_WIFI_MANAGER_TASK_PRIORITY
+
+/** @brief Defines the auth mode as an access point
+ *  Value must be of type wifi_auth_mode_t
+ *  @see esp_wifi_types.h
+ *  @warning if set to WIFI_AUTH_OPEN, passowrd me be empty. See DEFAULT_AP_PASSWORD.
+ */
+#define AP_AUTHMODE 						WIFI_AUTH_WPA2_PSK
+
+/** @brief Defines visibility of the access point. 0: visible AP. 1: hidden */
+#define DEFAULT_AP_SSID_HIDDEN 				0
+
+/** @brief Defines access point's name. Default value: esp32. Run 'make menuconfig' to setup your own value or replace here by a string */
+#define DEFAULT_AP_SSID 					CONFIG_DEFAULT_AP_SSID
+
+/** @brief Defines access point's password.
+ *	@warning In the case of an open access point, the password must be a null string "" or "\0" if you want to be verbose but waste one byte.
+ *	In addition, the AP_AUTHMODE must be WIFI_AUTH_OPEN
+ */
+#define DEFAULT_AP_PASSWORD 				CONFIG_DEFAULT_AP_PASSWORD
+
+/** @brief Defines the hostname broadcasted by mDNS */
+#define DEFAULT_HOSTNAME					"esp32"
+
+/** @brief Defines access point's bandwidth.
+ *  Value: WIFI_BW_HT20 for 20 MHz  or  WIFI_BW_HT40 for 40 MHz
+ *  20 MHz minimize channel interference but is not suitable for
+ *  applications with high data speeds
+ */
+#define DEFAULT_AP_BANDWIDTH 					WIFI_BW_HT20
+
+/** @brief Defines access point's channel.
+ *  Channel selection is only effective when not connected to another AP.
+ *  Good practice for minimal channel interference to use
+ *  For 20 MHz: 1, 6 or 11 in USA and 1, 5, 9 or 13 in most parts of the world
+ *  For 40 MHz: 3 in USA and 3 or 11 in most parts of the world
+ */
+#define DEFAULT_AP_CHANNEL 					CONFIG_DEFAULT_AP_CHANNEL
+
+
+
+/** @brief Defines the access point's default IP address. Default: "10.10.0.1 */
+#define DEFAULT_AP_IP						CONFIG_DEFAULT_AP_IP
+
+/** @brief Defines the access point's gateway. This should be the same as your IP. Default: "10.10.0.1" */
+#define DEFAULT_AP_GATEWAY					CONFIG_DEFAULT_AP_GATEWAY
+
+/** @brief Defines the access point's netmask. Default: "255.255.255.0" */
+#define DEFAULT_AP_NETMASK					CONFIG_DEFAULT_AP_NETMASK
+
+/** @brief Defines access point's maximum number of clients. Default: 4 */
+#define DEFAULT_AP_MAX_CONNECTIONS		 	CONFIG_DEFAULT_AP_MAX_CONNECTIONS
+
+/** @brief Defines access point's beacon interval. 100ms is the recommended default. */
+#define DEFAULT_AP_BEACON_INTERVAL 			CONFIG_DEFAULT_AP_BEACON_INTERVAL
+
+/** @brief Defines if esp32 shall run both AP + STA when connected to another AP.
+ *  Value: 0 will have the own AP always on (APSTA mode)
+ *  Value: 1 will turn off own AP when connected to another AP (STA only mode when connected)
+ *  Turning off own AP when connected to another AP minimize channel interference and increase throughput
+ */
+#define DEFAULT_STA_ONLY 					1
+
+/** @brief Defines if wifi power save shall be enabled.
+ *  Value: WIFI_PS_NONE for full power (wifi modem always on)
+ *  Value: WIFI_PS_MODEM for power save (wifi modem sleep periodically)
+ *  Note: Power save is only effective when in STA only mode
+ */
+#define DEFAULT_STA_POWER_SAVE 				WIFI_PS_NONE
+
+/**
+ * @brief Defines the maximum length in bytes of a JSON representation of an access point.
+ *
+ *  maximum ap string length with full 32 char ssid: 75 + \\n + \0 = 77\n
+ *  example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","chan":12,"rssi":-100,"auth":4},\n
+ *  BUT: we need to escape JSON. Imagine a ssid full of \" ? so it's 32 more bytes hence 77 + 32 = 99.\n
+ *  this is an edge case but I don't think we should crash in a catastrophic manner just because
+ *  someone decided to have a funny wifi name.
+ */
+#define JSON_ONE_APP_SIZE					99
+
+/**
+ * @brief Defines the maximum length in bytes of a JSON representation of the IP information
+ * assuming all ips are 4*3 digits, and all characters in the ssid require to be escaped.
+ * example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":0}
+ */
+#define JSON_IP_INFO_SIZE 					150
+
+
+
+/**
+ * @brief Defines the complete list of all messages that the wifi_manager can process.
+ *
+ * Some of these message are events ("EVENT"), and some of them are action ("ORDER")
+ * Each of these messages can trigger a callback function and each callback function is stored
+ * in a function pointer array for convenience. Because of this behavior, it is extremely important
+ * to maintain a strict sequence and the top level special element 'MESSAGE_CODE_COUNT'
+ *
+ * @see wifi_manager_set_callback
+ */
+typedef enum message_code_t {
+	NONE = 0,
+	ORDER_START_HTTP_SERVER = 1,
+	ORDER_STOP_HTTP_SERVER = 2,
+	ORDER_START_DNS_SERVICE = 3,
+	ORDER_STOP_DNS_SERVICE = 4,
+	ORDER_START_WIFI_SCAN = 5,
+	ORDER_LOAD_AND_RESTORE_STA = 6,
+	ORDER_CONNECT_STA = 7,
+	ORDER_DISCONNECT_STA = 8,
+	ORDER_START_AP = 9,
+	ORDER_START_HTTP = 10,
+	ORDER_START_DNS_HIJACK = 11,
+	EVENT_STA_DISCONNECTED = 12,
+	EVENT_SCAN_DONE = 13,
+	EVENT_STA_GOT_IP = 14,
+	MESSAGE_CODE_COUNT = 15 /* important for the callback array */
+
+}message_code_t;
+
+/**
+ * @brief simplified reason codes for a lost connection.
+ *
+ * esp-idf maintains a big list of reason codes which in practice are useless for most typical application.
+ */
+typedef enum update_reason_code_t {
+	UPDATE_CONNECTION_OK = 0,
+	UPDATE_FAILED_ATTEMPT = 1,
+	UPDATE_USER_DISCONNECT = 2,
+	UPDATE_LOST_CONNECTION = 3
+}update_reason_code_t;
+
+typedef enum connection_request_made_by_code_t{
+	CONNECTION_REQUEST_NONE = 0,
+	CONNECTION_REQUEST_USER = 1,
+	CONNECTION_REQUEST_AUTO_RECONNECT = 2,
+	CONNECTION_REQUEST_RESTORE_CONNECTION = 3,
+	CONNECTION_REQUEST_MAX = 0x7fffffff /*force the creation of this enum as a 32 bit int */
+}connection_request_made_by_code_t;
+
+/**
+ * The actual WiFi settings in use
+ */
+struct wifi_settings_t{
+	uint8_t ap_ssid[MAX_SSID_SIZE];
+	uint8_t ap_pwd[MAX_PASSWORD_SIZE];
+	uint8_t ap_channel;
+	uint8_t ap_ssid_hidden;
+	wifi_bandwidth_t ap_bandwidth;
+	bool sta_only;
+	wifi_ps_type_t sta_power_save;
+	bool sta_static_ip;
+	tcpip_adapter_ip_info_t sta_static_ip_config;
+};
+extern struct wifi_settings_t wifi_settings;
+
+
+/**
+ * @brief Structure used to store one message in the queue.
+ */
+typedef struct{
+	message_code_t code;
+	void *param;
+} queue_message;
+
+
+
+/**
+ * Allocate heap memory for the wifi manager and start the wifi_manager RTOS task
+ */
+void wifi_manager_start();
+
+/**
+ * Frees up all memory allocated by the wifi_manager and kill the task.
+ */
+void wifi_manager_destroy();
+
+/**
+ * Filters the AP scan list to unique SSIDs
+ */
+void filter_unique( wifi_ap_record_t * aplist, uint16_t * ap_num);
+
+/**
+ * Main task for the wifi_manager
+ */
+void wifi_manager( void * pvParameters );
+
+
+char* wifi_manager_get_ap_list_json();
+char* wifi_manager_get_ip_info_json();
+
+uint8_t wifi_manager_get_flag();
+char * wifi_manager_alloc_get_config(char * name, size_t * l);
+
+
+/**
+ * @brief saves the current STA wifi config to flash ram storage.
+ */
+esp_err_t wifi_manager_save_sta_config();
+
+/**
+ * @brief saves the current configuration to flash ram storage
+ */
+esp_err_t wifi_manager_save_autoexec_config(char * value, char * name, int len);
+esp_err_t wifi_manager_save_autoexec_flag(uint8_t flag);
+
+
+/**
+ * @brief fetch a previously STA wifi config in the flash ram storage.
+ * @return true if a previously saved config was found, false otherwise.
+ */
+bool wifi_manager_fetch_wifi_sta_config();
+
+wifi_config_t* wifi_manager_get_wifi_sta_config();
+
+/**
+ * @brief A standard wifi event handler as recommended by Espressif
+ */
+esp_err_t wifi_manager_event_handler(void *ctx, system_event_t *event);
+
+
+/**
+ * @brief requests a connection to an access point that will be process in the main task thread.
+ */
+void wifi_manager_connect_async();
+
+/**
+ * @brief requests a wifi scan
+ */
+void wifi_manager_scan_async();
+
+/**
+ * @brief requests to disconnect and forget about the access point.
+ */
+void wifi_manager_disconnect_async();
+
+/**
+ * @brief Tries to get access to json buffer mutex.
+ *
+ * The HTTP server can try to access the json to serve clients while the wifi manager thread can try
+ * to update it. These two tasks are synchronized through a mutex.
+ *
+ * The mutex is used by both the access point list json and the connection status json.\n
+ * These two resources should technically have their own mutex but we lose some flexibility to save
+ * on memory.
+ *
+ * This is a simple wrapper around freeRTOS function xSemaphoreTake.
+ *
+ * @param xTicksToWait The time in ticks to wait for the semaphore to become available.
+ * @return true in success, false otherwise.
+ */
+bool wifi_manager_lock_json_buffer(TickType_t xTicksToWait);
+
+/**
+ * @brief Releases the json buffer mutex.
+ */
+void wifi_manager_unlock_json_buffer();
+
+/**
+ * @brief Generates the connection status json: ssid and IP addresses.
+ * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful.
+ */
+void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code);
+/**
+ * @brief Clears the connection status json.
+ * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful.
+ */
+void wifi_manager_clear_ip_info_json();
+
+/**
+ * @brief Generates the list of access points after a wifi scan.
+ * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful.
+ */
+void wifi_manager_generate_acess_points_json();
+
+/**
+ * @brief Clear the list of access points.
+ * @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful.
+ */
+void wifi_manager_clear_access_points_json();
+
+
+/**
+ * @brief Start the mDNS service
+ */
+void wifi_manager_initialise_mdns();
+
+
+bool wifi_manager_lock_sta_ip_string(TickType_t xTicksToWait);
+void wifi_manager_unlock_sta_ip_string();
+
+/**
+ * @brief gets the string representation of the STA IP address, e.g.: "192.168.1.69"
+ */
+char* wifi_manager_get_sta_ip_string();
+
+/**
+ * @brief thread safe char representation of the STA IP update
+ */
+void wifi_manager_safe_update_sta_ip_string(uint32_t ip);
+
+
+/**
+ * @brief Register a callback to a custom function when specific event message_code happens.
+ */
+void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) );
+
+
+BaseType_t wifi_manager_send_message(message_code_t code, void *param);
+BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WIFI_MANAGER_H_INCLUDED */

+ 2 - 2
main/CMakeLists.txt

@@ -1,7 +1,7 @@
 set(COMPONENT_ADD_INCLUDEDIRS . )
 
 set(COMPONENT_SRCS "esp_app_main.c" "platform_esp32.c" "cmd_wifi.c" "console.c" "nvs_utilities.c" "cmd_squeezelite.c")
-set(REQUIRES esp_common)
-set(REQUIRES_COMPONENTS freertos nvs_flash esp32 spi_flash newlib log console )
+set(REQUIRES esp_common )
+set(REQUIRES_COMPONENTS freertos nvs_flash esp32 spi_flash newlib log console wifi-manager )
 
 register_component()

+ 3 - 15
main/Kconfig.projbuild

@@ -229,7 +229,7 @@ menu "Squeezelite-ESP32"
 		config BT_SINK_NAME
 			depends on BT_SINK
 			string "Name of Bluetooth A2DP device"
-		        default "ESP32-BT"
+		        default "ESP32"
 		    help
 				This is the name of the bluetooth speaker that will be broadcasted			
 		config BT_SINK_PIN		
@@ -237,20 +237,8 @@ menu "Squeezelite-ESP32"
 			int "Bluetooth PIN code"
 		        default 1234
 		config AIRPLAY_SINK
-			bool "AirPlay receiver"
-			default y
-		config AIRPLAY_NAME
-			depends on AIRPLAY_SINK
-			string "Name of AirPlay device"
-				default "ESP32-AirPlay"
-		    help
-				This is the name of the AirPlay speaker that will be broadcasted		
-		config AIRPLAY_PORT
-			depends on AIRPLAY_SINK
-			string "AirPlay listening port"
-				default 5000
-		    help
-				AirPlay service listening port
+			bool "AirPlay receiver (not availabe now)"
+			default n
 	endmenu	
 
 endmenu

+ 4 - 3
main/cmd_squeezelite.c

@@ -17,7 +17,7 @@
 #include "nvs_flash.h"
 //extern char current_namespace[];
 static const char * TAG = "squeezelite_cmd";
-#define SQUEEZELITE_THREAD_STACK_SIZE (6*1024)
+#define SQUEEZELITE_THREAD_STACK_SIZE 8192
 extern int main(int argc, char **argv);
 static int launchsqueezelite(int argc, char **argv);
 pthread_t thread_squeezelite;
@@ -45,8 +45,9 @@ static void * squeezelite_thread(){
 		return NULL;
 	}
 	isRunning=true;
-	ESP_LOGI(TAG,"Waiting for WiFi.");
-	while(!wait_for_wifi()){usleep(100000);};
+//  Let's not wait on WiFi to allow squeezelite to run in bluetooth mode
+//	ESP_LOGI(TAG,"Waiting for WiFi.");
+//	while(!wait_for_wifi()){usleep(100000);};
 	ESP_LOGD(TAG ,"Number of args received: %u",thread_parms.argc );
 	ESP_LOGD(TAG ,"Values:");
     for(int i = 0;i<thread_parms.argc; i++){

+ 1 - 180
main/cmd_wifi.c

@@ -7,183 +7,4 @@
    CONDITIONS OF ANY KIND, either express or implied.
 */
 
-#include "cmd_wifi.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#include "cmd_decl.h"
-#include "esp_log.h"
-#include "esp_console.h"
-#include "argtable3/argtable3.h"
-#include "freertos/FreeRTOS.h"
-#include "freertos/event_groups.h"
-#include "esp_wifi.h"
-#include "tcpip_adapter.h"
-#include "esp_event.h"
-#include "led.h"
-
-#define JOIN_TIMEOUT_MS (10000)
-
-static EventGroupHandle_t wifi_event_group;
-const int CONNECTED_BIT = BIT0;
-static const char * TAG = "cmd_wifi";
-/** Arguments used by 'join' function */
-static struct {
-    struct arg_int *timeout;
-    struct arg_str *ssid;
-    struct arg_str *password;
-    struct arg_end *end;
-} join_args;
-
-///** Arguments used by 'join' function */
-//static struct {
-//    struct arg_int *autoconnect;
-//    struct arg_end *end;
-//} auto_connect_args;
-
-static void event_handler(void* arg, esp_event_base_t event_base, 
-                                int32_t event_id, void* event_data)
-{
-    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
-		led_blink_pushed(LED_GREEN, 250, 250);
-        esp_wifi_connect();
-        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
-    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
-		led_unpush(LED_GREEN);
-        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
-    }
-}
-bool wait_for_wifi(){
-
-	bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
-
-	if(!connected){
-		ESP_LOGD(TAG,"Waiting for WiFi...");
-	    connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
-	                                   pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
-	    if(!connected){
-	    	ESP_LOGD(TAG,"wifi timeout.");
-	    }
-	    else
-	    {
-	    	ESP_LOGI(TAG,"WiFi Connected!");
-	    }
-	}
-
-
-    return connected;
-
-}
-static void initialise_wifi(void)
-{
-    static bool initialized = false;
-    if (initialized) {
-        return;
-    }
-    tcpip_adapter_init();
-    wifi_event_group = xEventGroupCreate();
-    ESP_ERROR_CHECK(esp_event_loop_create_default());
-    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
-    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
-    ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &event_handler, NULL) );
-    ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
-    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
-    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
-    ESP_ERROR_CHECK( esp_wifi_start() );
-    initialized = true;
-	led_blink(LED_GREEN, 250, 250);
-}
-
-static bool wifi_join(const char *ssid, const char *pass, int timeout_ms)
-{
-    initialise_wifi();
-    wifi_config_t wifi_config = { 0 };
-    strncpy((char *) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
-    if (pass) {
-        strncpy((char *) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password));
-    }
-
-    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
-    ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
-    ESP_ERROR_CHECK( esp_wifi_connect() );
-
-    int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
-                                   pdFALSE, pdTRUE, timeout_ms / portTICK_PERIOD_MS);
-    return (bits & CONNECTED_BIT) != 0;
-}
-
-
-static int set_auto_connect(int argc, char **argv)
-{
-//    int nerrors = arg_parse(argc, argv, (void **) &join_args);
-//    if (nerrors != 0) {
-//        arg_print_errors(stderr, join_args.end, argv[0]);
-//        return 1;
-//    }
-//    ESP_LOGI(__func__, "Connecting to '%s'",
-//             join_args.ssid->sval[0]);
-//
-//    /* set default value*/
-//    if (join_args.timeout->count == 0) {
-//        join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
-//    }
-//
-//    bool connected = wifi_join(join_args.ssid->sval[0],
-//                               join_args.password->sval[0],
-//                               join_args.timeout->ival[0]);
-//    if (!connected) {
-//        ESP_LOGW(__func__, "Connection timed out");
-//        return 1;
-//    }
-//    ESP_LOGI(__func__, "Connected");
-    return 0;
-}
-static int connect(int argc, char **argv)
-{
-    int nerrors = arg_parse(argc, argv, (void **) &join_args);
-    if (nerrors != 0) {
-        arg_print_errors(stderr, join_args.end, argv[0]);
-        return 1;
-    }
-    ESP_LOGI(__func__, "Connecting to '%s'",
-             join_args.ssid->sval[0]);
-
-    /* set default value*/
-    if (join_args.timeout->count == 0) {
-        join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
-    }
-
-    bool connected = wifi_join(join_args.ssid->sval[0],
-                               join_args.password->sval[0],
-                               join_args.timeout->ival[0]);
-    if (!connected) {
-        ESP_LOGW(__func__, "Connection timed out");
-        return 1;
-    }
-    ESP_LOGI(__func__, "Connected");
-    return 0;
-}
-void register_wifi_join()
-{
-    join_args.timeout = arg_int0(NULL, "timeout", "<t>", "Connection timeout, ms");
-    join_args.ssid = arg_str1(NULL, NULL, "<ssid>", "SSID of AP");
-    join_args.password = arg_str0(NULL, NULL, "<pass>", "PSK of AP");
-    join_args.end = arg_end(2);
-
-    const esp_console_cmd_t join_cmd = {
-        .command = "join",
-        .help = "Join WiFi AP as a station",
-        .hint = NULL,
-        .func = &connect,
-        .argtable = &join_args
-    };
-    ESP_ERROR_CHECK( esp_console_cmd_register(&join_cmd) );
-}
-
-void register_wifi()
-{
-    register_wifi_join();
-    initialise_wifi();
-}
-
+// cmd_wifi has been replaced by wifi-manager

+ 1 - 2
main/cmd_wifi.h

@@ -12,8 +12,7 @@
 extern "C" {
 #endif
 
-// Register WiFi functions
-void register_wifi();
+
 
 #ifdef __cplusplus
 }

+ 6 - 5
main/console.c

@@ -29,6 +29,7 @@
 #include "cmd_squeezelite.h"
 #include "nvs_utilities.h"
 pthread_t thread_console;
+
 static void * console_thread();
 void console_start();
 static const char * TAG = "console";
@@ -145,12 +146,12 @@ void process_autoexec(){
 	{
 		ESP_LOGD(TAG,"No matching command found for name autoexec. Adding default entries");
 		uint8_t autoexec_dft=0;
-		char autoexec1_dft[64];
-		char autoexec2_dft[256]="squeezelite -o \"I2S\" -b 500:2000 -d all=info -M esp32";
-		snprintf(autoexec1_dft, 64, "join %s %s", CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD);
+		//char autoexec1_dft[64];
+		char autoexec1_dft[]="squeezelite -o \"I2S\" -b 500:2000 -d all=info -M esp32";
+		//snprintf(autoexec1_dft, 64, "join %s %s", CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD);
 		store_nvs_value(NVS_TYPE_U8,"autoexec",&autoexec_dft);
 		store_nvs_value(NVS_TYPE_STR,"autoexec1",autoexec1_dft);
-		store_nvs_value(NVS_TYPE_STR,"autoexec2",autoexec2_dft);
+		//store_nvs_value(NVS_TYPE_STR,"autoexec2",autoexec2_dft);
 	}
 }
 static void initialize_filesystem() {
@@ -237,7 +238,6 @@ void console_start() {
 	/* Register commands */
 	esp_console_register_help_command();
 	register_system();
-	register_wifi();
 	register_nvs();
 	register_squeezelite();
 	register_i2ctools();
@@ -276,6 +276,7 @@ void console_start() {
 	pthread_attr_t attr;
 	pthread_attr_init(&attr);
 	pthread_create(&thread_console, &attr, console_thread, NULL);
+
 	pthread_attr_destroy(&attr);
 }
 void run_command(char * line){

+ 65 - 0
main/esp_app_main.c

@@ -20,6 +20,33 @@
  */
 #include "platform_esp32.h"
 #include "led.h"
+#include <stdio.h>
+#include <string.h>
+#include "freertos/FreeRTOS.h"
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+#include "freertos/task.h"
+#include "esp_system.h"
+#include "esp_spi_flash.h"
+#include "esp_wifi.h"
+#include "esp_system.h"
+#include "esp_event_loop.h"
+#include "nvs_flash.h"
+#include "esp_log.h"
+#include "freertos/event_groups.h"
+#include "mdns.h"
+#include "lwip/api.h"
+#include "lwip/err.h"
+#include "lwip/netdb.h"
+
+#include "http_server.h"
+#include "wifi_manager.h"
+static EventGroupHandle_t wifi_event_group;
+const int CONNECTED_BIT = BIT0;
+#define JOIN_TIMEOUT_MS (10000)
+
+static const char TAG[] = "esp_app_main";
+
 
 #ifdef CONFIG_SQUEEZEAMP
 #define LED_GREEN_GPIO 	12
@@ -29,10 +56,48 @@
 #define LED_RED_GPIO	0
 #endif
 
+/* brief this is an exemple of a callback that you can setup in your own app to get notified of wifi manager event */
+void cb_connection_got_ip(void *pvParameter){
+	ESP_LOGI(TAG, "I have a connection!");
+	xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
+	led_unpush(LED_GREEN);
+}
+void cb_connection_sta_disconnected(void *pvParameter){
+	led_blink_pushed(LED_GREEN, 250, 250);
+	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
+}
+bool wait_for_wifi(){
+	bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
+	if(!connected){
+		ESP_LOGD(TAG,"Waiting for WiFi...");
+	    connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
+	                                   pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
+	    if(!connected){
+	    	ESP_LOGW(TAG,"wifi timeout.");
+	    }
+	    else
+	    {
+	    	ESP_LOGI(TAG,"WiFi Connected!");
+	    }
+	}
+
+
+    return connected;
+
+}
+
 void app_main()
 {
 	led_config(LED_GREEN, LED_GREEN_GPIO, 0);
 	led_config(LED_RED, LED_RED_GPIO, 0);
+	wifi_event_group = xEventGroupCreate();
 	
+	/* start the wifi manager */
+	led_blink(LED_GREEN, 250, 250);
+	wifi_manager_start();
+	wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_got_ip);
+	wifi_manager_set_callback(WIFI_EVENT_STA_DISCONNECTED, &cb_connection_sta_disconnected);
+
+
 	console_start();
 }

+ 7 - 7
partitions.csv

@@ -1,7 +1,7 @@
-# Name,   Type, SubType, Offset,  Size, Flags
-# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
-nvs,      data, nvs,     0x9000,  0x6000,
-phy_init, data, phy,     0xf000,  0x1000,
-factory,  app,  factory, 0x10000, 2M,
-storage,  data, fat,     ,        819200, 
-coredump, data, coredump,,        64K
+nvs,  data,  nvs,  0x9000,  0x4000,
+otadata,  data,  ota,  0xD000,  0x2000,
+phy_init,  data,  phy,  0xF000,  0x1000,
+factory,  app,  factory,  0x10000,  0x140000,
+ota_0,  app,  ota_0,  0x150000,  0x270000,
+coredump,  data,  coredump,  0x3C0000,  0x10000,
+storage,  data,  fat,  0x3D0000,  0x30000,

+ 2 - 4
sdkconfig.defaults

@@ -77,10 +77,9 @@ 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_MALLOC_ALWAYSINTERNAL=512
+CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
 CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
-CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
 CONFIG_SPIRAM_OCCUPY_VSPI_HOST=y
 CONFIG_SPIRAM_BANKSWITCH_ENABLE=n
 CONFIG_D0WD_PSRAM_CLK_IO=17
@@ -114,7 +113,6 @@ CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
 CONFIG_ESP32_PHY_MAX_TX_POWER=20
 CONFIG_FREERTOS_HZ=100
 CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
-CONFIG_LWIP_UDP_RECVMBOX_SIZE=32
 CONFIG_LWIP_NETIF_LOOPBACK=y
 CONFIG_LWIP_TCP_MSL=60000
 CONFIG_LWIP_TCP_SND_BUF_DEFAULT=8192

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff