Browse Source

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

Conflicts:
	.cproject
	.gitmodules
	.project
	.pydevproject
	.settings/language.settings.xml
	.settings/org.eclipse.cdt.core.prefs
	components/cmd_i2c/CMakeLists.txt
	components/cmd_i2c/cmd_i2ctools.c
	components/cmd_i2c/component.mk
	components/cmd_nvs/cmd_nvs.c
	components/cmd_nvs/component.mk
	components/cmd_system/cmd_system.c
	components/cmd_system/component.mk
	components/config/config.c
	components/config/config.h
	components/config/nvs_utilities.c
	components/display/CMakeLists.txt
	components/driver_bt/CMakeLists.txt
	components/driver_bt/component.mk
	components/raop/raop.c
	components/services/CMakeLists.txt
	components/squeezelite-ota/cmd_ota.c
	components/squeezelite-ota/squeezelite-ota.c
	components/squeezelite-ota/squeezelite-ota.h
	components/squeezelite/component.mk
	components/telnet/CMakeLists.txt
	components/wifi-manager/CMakeLists.txt
	components/wifi-manager/dns_server.c
	components/wifi-manager/http_server.c
	components/wifi-manager/http_server.h
	components/wifi-manager/wifi_manager.c
	components/wifi-manager/wifi_manager.h
	main/CMakeLists.txt
	main/console.c
	main/esp_app_main.c
	main/platform_esp32.h
Sebastien 5 years ago
parent
commit
39058213fa
53 changed files with 4303 additions and 1432 deletions
  1. 3 0
      .gitignore
  2. 2 2
      .gitmodules
  3. 0 3
      .pydevproject
  4. 2 0
      .settings/com.googlecode.cppcheclipse.core.prefs
  5. 89 0
      .settings/org.eclipse.cdt.codan.core.prefs
  6. 7 3
      Makefile
  7. 2 1
      build-scripts/ESP32-A1S-sdkconfig.defaults
  8. 2 2
      build-scripts/I2S-4MFlash-sdkconfig.defaults
  9. 2 1
      build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults
  10. 2 1
      build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults
  11. 2 1
      build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults
  12. 2 1
      build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults
  13. 113 0
      build_flash_cmd.sh
  14. 1 1
      components/codecs/component.mk
  15. 2 2
      components/platform_config/platform_config.c
  16. 7 2
      components/platform_config/platform_config.h
  17. 14 14
      components/platform_console/cmd_system.c
  18. 80 49
      components/platform_console/platform_console.c
  19. 5 3
      components/raop/component.mk
  20. 1 2
      components/raop/raop.c
  21. 807 807
      components/raop/rtp.c
  22. 2 1
      components/raop/util.c
  23. 0 1
      components/services/audio_controls.c
  24. 1 0
      components/services/component.mk
  25. 230 0
      components/services/messaging.c
  26. 32 0
      components/services/messaging.h
  27. 39 4
      components/services/monitor.c
  28. 2 0
      components/services/services.c
  29. 1 4
      components/squeezelite-ota/component.mk
  30. 413 220
      components/squeezelite-ota/squeezelite-ota.c
  31. 1 0
      components/squeezelite-ota/squeezelite-ota.h
  32. 1 3
      components/telnet/component.mk
  33. 77 63
      components/telnet/telnet.c
  34. 1 0
      components/telnet/telnet.h
  35. 1 6
      components/tools/platform_esp32.h
  36. 15 0
      components/tools/trace.h
  37. 93 0
      components/wifi-manager/_esp_http_server.h
  38. 373 0
      components/wifi-manager/_esp_httpd_main.c
  39. 204 58
      components/wifi-manager/code.js
  40. 3 9
      components/wifi-manager/component.mk
  41. 0 4
      components/wifi-manager/dns_server.c
  42. 1127 0
      components/wifi-manager/http_server_handlers.c
  43. 148 0
      components/wifi-manager/http_server_handlers.h
  44. 58 97
      components/wifi-manager/index.html
  45. 12 6
      components/wifi-manager/style.css
  46. 11 36
      components/wifi-manager/wifi_manager.c
  47. 17 8
      components/wifi-manager/wifi_manager.h
  48. 165 0
      components/wifi-manager/wifi_manager_http_server.c
  49. 113 0
      eclipse_make_wrapper.py
  50. 0 1
      main/CMakeLists.txt
  51. 2 5
      main/component.mk
  52. 15 10
      main/esp_app_main.c
  53. 1 1
      sdkconfig.defaults

+ 3 - 0
.gitignore

@@ -66,3 +66,6 @@ libs/
 /cdump.cmd
 /_*
 squeezelite-esp32-jsonblob.zip
+/flash_cmd.txt
+/writeSequeezeEsp.bat
+/writeSequeezeEsp.sh

+ 2 - 2
.gitmodules

@@ -2,6 +2,6 @@
 	path = components/telnet/libtelnet
 	url = https://github.com/seanmiddleditch/libtelnet
 	branch = develop
-[submodule "esp-dsp"]
+[submodule "components/esp-dsp"]
 	path = components/esp-dsp
-	url = https://github.com/philippe44/esp-dsp
+	url = https://github.com/philippe44/esp-dsp.git

+ 0 - 3
.pydevproject

@@ -1,8 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?eclipse-pydev version="1.0"?><pydev_project>
-        
     <pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
-        
     <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
-    
 </pydev_project>

+ 2 - 0
.settings/com.googlecode.cppcheclipse.core.prefs

@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+suppressions=rO0ABXNyAAxqYXZhLmlvLkZpbGUELaRFDg3k/wMAAUwABHBhdGh0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAJWNvbXBvbmVudHNcd2lmaS1tYW5hZ2VyXGh0dHBfc2VydmVyLmN3AgBceA\=\=;comparePointers;395\!rO0ABXNyAAxqYXZhLmlvLkZpbGUELaRFDg3k/wMAAUwABHBhdGh0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAE21haW5cZXNwX2FwcF9tYWluLmN3AgBceA\=\=;comparePointers;176\!rO0ABXNyAAxqYXZhLmlvLkZpbGUELaRFDg3k/wMAAUwABHBhdGh0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAJ2NvbXBvbmVudHNcdGVsbmV0XGxpYnRlbG5ldFxsaWJ0ZWxuZXQuY3cCAFx4;va_list_usedBeforeStarted;2147483647\!

+ 89 - 0
.settings/org.eclipse.cdt.codan.core.prefs

@@ -0,0 +1,89 @@
+eclipse.preferences.version=1
+org.eclipse.cdt.codan.checkers.errnoreturn=Warning
+org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return\\")",implicit\=>false}
+org.eclipse.cdt.codan.checkers.errreturnvalue=Error
+org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused return value\\")"}
+org.eclipse.cdt.codan.checkers.nocommentinside=-Error
+org.eclipse.cdt.codan.checkers.nocommentinside.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Nesting comments\\")"}
+org.eclipse.cdt.codan.checkers.nolinecomment=-Error
+org.eclipse.cdt.codan.checkers.nolinecomment.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Line comments\\")"}
+org.eclipse.cdt.codan.checkers.noreturn=Error
+org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return value\\")",implicit\=>false}
+org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=Error
+org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Abstract class cannot be instantiated\\")"}
+org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=Error
+org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Ambiguous problem\\")"}
+org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment in condition\\")"}
+org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error
+org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment to itself\\")"}
+org.eclipse.cdt.codan.internal.checkers.CStyleCastProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.CStyleCastProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"C-Style cast instead of C++ cast\\")"}
+org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No break at end of case\\")",no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false,enable_fallthrough_quickfix_param\=>false}
+org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning
+org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Catching by reference is recommended\\")",unknown\=>false,exceptions\=>()}
+org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error
+org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Circular inheritance\\")"}
+org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning
+org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class members should be properly initialized\\")",skip\=>true}
+org.eclipse.cdt.codan.internal.checkers.CopyrightProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.CopyrightProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Lack of copyright information\\")",regex\=>".*Copyright.*"}
+org.eclipse.cdt.codan.internal.checkers.DecltypeAutoProblem=Error
+org.eclipse.cdt.codan.internal.checkers.DecltypeAutoProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid 'decltype(auto)' specifier\\")"}
+org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Field cannot be resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Function cannot be resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.GotoStatementProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.GotoStatementProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Goto statement used\\")"}
+org.eclipse.cdt.codan.internal.checkers.InvalidArguments=Error
+org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid arguments\\")"}
+org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=Error
+org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid template argument\\")"}
+org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=Error
+org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Label statement not found\\")"}
+org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=Error
+org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Member declaration not found\\")"}
+org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Method cannot be resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.MissCaseProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.MissCaseProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Missing cases in switch\\")"}
+org.eclipse.cdt.codan.internal.checkers.MissDefaultProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.MissDefaultProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Missing default in switch\\")",defaultWithAllEnums\=>false}
+org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info
+org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Name convention for function\\")",pattern\=>"^[a-z]",macro\=>true,exceptions\=>()}
+org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class has a virtual method and non-virtual destructor\\")"}
+org.eclipse.cdt.codan.internal.checkers.OverloadProblem=Error
+org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid overload\\")"}
+org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=Error
+org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redeclaration\\")"}
+org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redefinition\\")"}
+org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Return with parenthesis\\")"}
+org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Format String Vulnerability\\")"}
+org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Statement has no effect\\")",macro\=>true,exceptions\=>()}
+org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suggested parenthesis around expression\\")",paramNot\=>false}
+org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suspicious semicolon\\")",else\=>false,afterelse\=>false}
+org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Type cannot be resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused function declaration\\")",macro\=>true}
+org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused static function\\")",macro\=>true}
+org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused variable declaration in file scope\\")",macro\=>true,exceptions\=>("@(\#)","$Id")}
+org.eclipse.cdt.codan.internal.checkers.UsingInHeaderProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.UsingInHeaderProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Using directive in header\\")"}
+org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Symbol is not resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallProblem=-Error
+org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Virtual method call in constructor/destructor\\")"}
+org.eclipse.cdt.qt.core.qtproblem=Warning
+org.eclipse.cdt.qt.core.qtproblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>false,RUN_ON_INC_BUILD\=>false,RUN_ON_FILE_OPEN\=>true,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>null}

+ 7 - 3
Makefile

@@ -10,9 +10,13 @@
 
 
 #recovery: PROJECT_NAME:=recovery.$(PROJECT_CONFIG_TARGET)
-#recovery: CPPFLAGS+=-DRECOVERY_APPLICATION=1
+#recovery: EXTRA_CPPFLAGS+=-DRECOVERY_APPLICATION=1
 
 PROJECT_NAME?=squeezelite
-EXTRA_COMPONENT_DIRS := esp-dsp
+EXTRA_CPPFLAGS+=  -I$(PROJECT_PATH)/main 
+
+#/-Wno-error=maybe-uninitialized 
 include $(IDF_PATH)/make/project.mk 
-CPPFLAGS+= -Wno-error=maybe-uninitialized
+
+# for future gcc version, this could be needed: CPPFLAGS+= -Wno-error=format-overflow -Wno-error=stringop-truncation
+

+ 2 - 1
build-scripts/ESP32-A1S-sdkconfig.defaults

@@ -512,7 +512,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y
 CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 
-CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
 CONFIG_HTTPD_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
 
 

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

@@ -512,7 +512,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y
 CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 
-CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
 CONFIG_HTTPD_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -594,7 +594,7 @@ CONFIG_FMB_TIMER_INDEX=0
 
 CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
-
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_HZ=100
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
 

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

@@ -513,7 +513,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y
 CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 
-CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
 CONFIG_HTTPD_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -597,6 +597,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
 
 

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

@@ -512,7 +512,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y
 CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 
-CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
 CONFIG_HTTPD_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
 
 

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

@@ -512,7 +512,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y
 CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 
-CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
 CONFIG_HTTPD_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
 
 

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

@@ -506,7 +506,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y
 CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 
-CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
 CONFIG_HTTPD_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -590,6 +590,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
 
 

+ 113 - 0
build_flash_cmd.sh

@@ -0,0 +1,113 @@
+#!/bin/bash
+echo 
+echo =================================================================
+echo Build flash command
+echo =================================================================
+# Location of partitions.csv relative to this script
+partitionsCsv="../partitions.csv"
+
+# File to output readme instructions to
+outputReadme="./flash_cmd.txt"
+
+# File to output bash script to
+outputBashScript="./writeSequeezeEsp.sh"
+
+# File to output bat script to
+outputBatScript="./writeSequeezeEsp.bat"
+
+# The name of partitions to ignore from partitions.csv
+paritionsToIgnore=(
+  "nvs"
+  "phy_init"
+  "storage"
+  "coredump"
+  "settings"
+)
+
+# Function that maps partition name to actual bin file
+# defaults to "[PARTION_NAME_FROM_CSV].bin"
+function partitionNameToBinFile {
+  if [[ "$1" == "otadata" ]]; then
+    echo "ota_data_initial.bin"
+  elif [[ "$1" == "ota_0" || "$1" == "factory" ]]; then
+    echo "squeezelite.bin"
+  else
+    echo $1.bin
+  fi
+}
+
+# write parameters for esptool.py
+writeParameters="$writeParameters write_flash"
+writeParameters="$writeParameters --flash_mode dio --flash_freq 80m --flash_size detect"
+
+# bootloader.bin and partitions.bin not in partitions.csv so manually add here
+partitionsParameters=" 0x1000 bootloader/bootloader.bin"
+partitionsParameters="$partitionsParameters 0x8000 partitions.bin"
+
+# ==============================================================================
+
+# Loop over partitions.csv and add partition bins and offsets to partitionsParameters
+
+for line in $($IDF_PATH/components/partition_table/gen_esp32part.py --quiet build/partitions.bin | grep '^[^#]')
+do
+  partitionName=$(echo $line | awk -F',' '{printf "%s", $1}' )
+  partitionOffset=$(echo $line |awk -F',' '{printf "%s", $4}' )
+  partitionFile=$(partitionNameToBinFile $partitionName)
+	
+  if [[ " ${paritionsToIgnore[@]} " =~ " ${partitionName} " ]]; then
+	continue
+  fi
+
+  partitionsParameters="$partitionsParameters $partitionOffset $partitionFile"
+  echo "$partitionsParameters"
+  
+done
+
+# Write README Instructions
+if [ ! -f "$outputReadme" ]; then
+  touch $outputReadme
+fi
+
+echo "" >> $outputReadme
+echo "====LINUX====" >> $outputReadme
+echo "To flash sequeezelite run the following script:" >> $outputReadme
+echo "$outputBashScript [PORT_HERE] [BAUD_RATE]" >> $outputReadme
+echo "e.g. $outputBashScript /dev/ttyUSB0 115200" >> $outputReadme
+echo "" >> $outputReadme
+echo "====WINDOWS====" >> $outputReadme
+echo "To flash sequeezelite run the following script:" >> $outputReadme
+echo "$outputBatScript [PORT_HERE] [BAUD_RATE]" >> $outputReadme
+echo "e.g. $outputBatScript COM11 115200" >> $outputReadme
+echo "" >> $outputReadme
+echo "If you don't know how to run the BAT file with arguments then you can" >> $outputReadme
+echo "edit the bat file in Notepad. Open the file up and edit the following:" >> $outputReadme
+echo "Change 'set port=%1' to 'set port=[PORT_HERE]'. E.g. 'set port=COM11'" >> $outputReadme
+echo "Change 'set baud=%2' to 'set baud=[BAUD_RATE]'. E.g. 'set baud=115200'" >> $outputReadme
+echo "" >> $outputReadme
+echo "====MANUAL====" >> $outputReadme
+echo "Python esptool.py --port [PORT_HERE] --baud [BAUD_RATE] $writeParameters $partitionsParameters" >> $outputReadme
+
+# Write Linux BASH File
+if [ ! -f "$outputBashScript" ]; then
+  touch $outputBashScript
+fi
+
+echo "#!/bin/bash" >> $outputBashScript
+echo >> $outputBashScript
+echo "port=\$1" >> $outputBashScript
+echo "baud=\$2" >> $outputBashScript
+linuxFlashCommand="Python esptool.py --port \$port --baud \$baud"
+echo "$linuxFlashCommand $writeParameters $partitionsParameters" >> $outputBashScript
+
+# Write Windows BAT File
+if [ ! -f "$outputBatScript" ]; then
+  touch $outputBatScript
+fi
+
+echo "echo off" >> $outputBatScript
+echo "" >> $outputBatScript
+echo "set port=%1" >> $outputBatScript
+echo "set baud=%2" >> $outputBatScript
+windowsFlashCommand="Python esptool.py --port %port% --baud %baud%"
+echo "$windowsFlashCommand $writeParameters $partitionsParameters" >> $outputBatScript
+

+ 1 - 1
components/codecs/component.mk

@@ -21,6 +21,6 @@ COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) 	\
 	#$(COMPONENT_PATH)/lib/libesp-tremor.a
 	#$(COMPONENT_PATH)/lib/libesp-ogg-container.a
 	
-
+COMPONENT_ADD_INCLUDEDIRS := /inc
 	
 	

+ 2 - 2
components/platform_config/platform_config.c

@@ -559,7 +559,7 @@ void config_delete_key(const char *key){
 	ESP_LOGD(TAG, "Deleting nvs entry for [%s]", key);
 	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
 		ESP_LOGE(TAG, "Unable to lock config for delete");
-		return false;
+		return ;
 	}
 	esp_err_t err = nvs_open_from_partition(settings_partition, current_namespace, NVS_READWRITE, &nvs);
 	if (err == ESP_OK) {
@@ -663,7 +663,7 @@ char * config_alloc_get_json(bool bFormatted){
 	config_unlock();
 	return json_buffer;
 }
-esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, void * value){
+esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, const void * value){
 	esp_err_t result = ESP_OK;
 	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
 			ESP_LOGE(TAG, "Unable to lock config after %d ms",LOCK_MAX_WAIT);

+ 7 - 2
components/platform_config/platform_config.h

@@ -3,6 +3,7 @@
 #include <string.h>
 #include "nvs.h"
 #include "assert.h"
+#include "cJSON.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -12,7 +13,9 @@ extern "C" {
 #endif
 #define DECLARE_SET_DEFAULT(t) void config_set_default_## t (const char *key, t  value);
 #define DECLARE_GET_NUM(t) esp_err_t config_get_## t (const char *key, t *  value);
-
+#ifndef FREE_RESET
+#define FREE_RESET(p) if(p!=NULL) { free(p); p=NULL; }
+#endif
 
 DECLARE_SET_DEFAULT(uint8_t);
 DECLARE_SET_DEFAULT(uint16_t);
@@ -37,5 +40,7 @@ void config_set_default(nvs_type_t type, const char *key, void * default_value,
 void * config_alloc_get(nvs_type_t nvs_type, const char *key) ;
 bool wait_for_commit();
 char * config_alloc_get_json(bool bFormatted);
-esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, void * value);
+esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, const void * value);
+nvs_type_t  config_get_item_type(cJSON * entry);
+void * config_safe_alloc_get_entry_value(nvs_type_t nvs_type, cJSON * entry);
 

+ 14 - 14
components/platform_console/cmd_system.c

@@ -29,6 +29,7 @@
 #include "platform_config.h"
 #include "esp_sleep.h"
 #include "driver/uart.h"            // for the uart driver access
+#include "messaging.h"				  
 
 #ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
 #define WITH_TASKS_INFO 1
@@ -105,8 +106,8 @@ if(is_recovery_running){
 		if(!wait_for_commit()){
 			ESP_LOGW(TAG,"Unable to commit configuration. ");
 		}
-		ESP_LOGW(TAG, "Restarting after tx complete");
-		uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+		
+		vTaskDelay(750/ portTICK_PERIOD_MS);
 		esp_restart();
 		return ESP_OK;
 	}
@@ -117,8 +118,8 @@ else {
 		if(!wait_for_commit()){
 			ESP_LOGW(TAG,"Unable to commit configuration. ");
 		}
-		ESP_LOGW(TAG, "Restarting after tx complete");
-		uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+		
+		vTaskDelay(750/ portTICK_PERIOD_MS);
 		esp_restart();
 		return ESP_OK;
 	}
@@ -131,7 +132,7 @@ else {
 
 	if(it == NULL){
 		ESP_LOGE(TAG,"Unable initialize partition iterator!");
-		set_status_message(ERROR, "Reboot failed. Cannot iterate through partitions");
+		messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Reboot failed. Cannot iterate through partitions");
 	}
 	else
 	{
@@ -145,18 +146,19 @@ else {
 			if(err!=ESP_OK){
 				ESP_LOGE(TAG,"Unable to set partition as active for next boot. %s",esp_err_to_name(err));
 				bFound=false;
-				set_status_message(ERROR, "Unable to select partition for reboot.");
+				messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Unable to select partition for reboot.");
 			}
 			else{
 				ESP_LOGW(TAG, "Application partition %s sub type %u is selected for boot", partition->label,partition_subtype);
 				bFound=true;
-				set_status_message(WARNING, "Rebooting!");
+				messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Reboot failed. Cannot iterate through partitions");
 			}
 		}
 		else
 		{
 			ESP_LOGE(TAG,"partition type %u not found!  Unable to reboot to recovery.",partition_subtype);
-			set_status_message(ERROR, "Partition not found.");
+			messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"partition type %u not found!  Unable to reboot to recovery.",partition_subtype);
+
 		}
 		ESP_LOGD(TAG, "Yielding to other processes");
 		taskYIELD();
@@ -165,8 +167,7 @@ else {
 			if(!wait_for_commit()){
 				ESP_LOGW(TAG,"Unable to commit configuration. ");
 			}
-			ESP_LOGW(TAG, "Restarting after tx complete");
-			uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+			vTaskDelay(750/ portTICK_PERIOD_MS);
 			esp_restart();
 		}
 	}
@@ -180,8 +181,7 @@ static int restart(int argc, char **argv)
 	if(!wait_for_commit()){
 		ESP_LOGW(TAG,"Unable to commit configuration. ");
 	}
-	ESP_LOGW(TAG, "Restarting after tx complete");
-    uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+    vTaskDelay(750/ portTICK_PERIOD_MS);
     esp_restart();
     return 0;
 }
@@ -193,8 +193,8 @@ void simple_restart()
 		ESP_LOGW(TAG,"Unable to commit configuration. ");
 	}
 
-	ESP_LOGW(TAG, "Restarting after tx complete");
-	uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
+											   
+	vTaskDelay(750/ portTICK_PERIOD_MS);
     esp_restart();
 }
 

+ 80 - 49
components/platform_console/platform_console.c

@@ -28,6 +28,15 @@
 #include "wifi_manager.h"
 
 #include "platform_config.h"
+#include "telnet.h"
+#include "gds.h"
+#include "gds_default_if.h"
+#include "gds_draw.h"
+#include "gds_text.h"
+#include "gds_font.h"
+#include "display.h"
+#include "cmd_squeezelite.h"
+#include "config.h"
 pthread_t thread_console;
 static void * console_thread();
 void console_start();
@@ -152,9 +161,24 @@ void initialize_console() {
 }
 
 void console_start() {
-
-	initialize_console();
-
+	if(is_recovery_running){
+		GDS_ClearExt(display, true);
+		GDS_SetFont(display, &Font_droid_sans_fallback_15x17 );
+		GDS_TextPos(display, GDS_FONT_MEDIUM, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "RECOVERY");
+	}
+	if(!is_serial_suppressed()){
+		initialize_console();
+	}
+	else {
+		/* Initialize the console */
+		esp_console_config_t console_config = { .max_cmdline_args = 22,
+				.max_cmdline_length = 600,
+	#if CONFIG_LOG_COLORS
+				.hint_color = atoi(LOG_COLOR_CYAN)
+	#endif
+				};
+		ESP_ERROR_CHECK(esp_console_init(&console_config));
+	}
 	/* Register commands */
 	esp_console_register_help_command();
 	register_system();
@@ -167,53 +191,59 @@ void console_start() {
 		register_ota_cmd();
 	}
 	register_i2ctools();
-	printf("\n");
-	if(is_recovery_running){
-		printf("****************************************************************\n"
-		"RECOVERY APPLICATION\n"
-		"This mode is used to flash Squeezelite into the OTA partition\n"
-		"****\n\n");
-	}
-	printf("Type 'help' to get the list of commands.\n"
-	"Use UP/DOWN arrows to navigate through command history.\n"
-	"Press TAB when typing command name to auto-complete.\n"
-	"\n");
-	if(!is_recovery_running){
-		printf("To automatically execute lines at startup:\n"
-				"\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n"
-				"\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n");
-	}
-	printf("\n\n");
+	
+	if(!is_serial_suppressed()){
+		printf("\n");
+		if(is_recovery_running){
+			printf("****************************************************************\n"
+			"RECOVERY APPLICATION\n"
+			"This mode is used to flash Squeezelite into the OTA partition\n"
+			"****\n\n");
+		}
+		printf("Type 'help' to get the list of commands.\n"
+		"Use UP/DOWN arrows to navigate through command history.\n"
+		"Press TAB when typing command name to auto-complete.\n"
+		"\n");
+		if(!is_recovery_running){
+			printf("To automatically execute lines at startup:\n"
+					"\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n"
+					"\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n");
+		}
+		printf("\n\n");
 
-	/* Figure out if the terminal supports escape sequences */
-	int probe_status = linenoiseProbe();
-	if (probe_status) { /* zero indicates success */
-		printf("\n****************************\n"
-				"Your terminal application does not support escape sequences.\n"
-				"Line editing and history features are disabled.\n"
-				"On Windows, try using Putty instead.\n"
-				"****************************\n");
-		linenoiseSetDumbMode(1);
-#if CONFIG_LOG_COLORS
-		/* Since the terminal doesn't support escape sequences,
-		 * don't use color codes in the prompt.
-		 */
-		prompt = "squeezelite-esp32> ";
-#endif //CONFIG_LOG_COLORS
+		/* Figure out if the terminal supports escape sequences */
+		int probe_status = linenoiseProbe();
+		if (probe_status) { /* zero indicates success */
+			printf("\n****************************\n"
+					"Your terminal application does not support escape sequences.\n"
+					"Line editing and history features are disabled.\n"
+					"On Windows, try using Putty instead.\n"
+					"****************************\n");
+			linenoiseSetDumbMode(1);
+	#if CONFIG_LOG_COLORS
+			/* Since the terminal doesn't support escape sequences,
+			 * don't use color codes in the prompt.
+			 */
+			prompt = "squeezelite-esp32> ";
+	#endif //CONFIG_LOG_COLORS
+		}
+		esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
+		cfg.thread_name= "console";
+		cfg.inherit_cfg = true;
+		if(is_recovery_running){
+			cfg.stack_size = 4096 ;
+		}
+		esp_pthread_set_cfg(&cfg);
+		pthread_attr_t attr;
+		pthread_attr_init(&attr);
 
+		pthread_create(&thread_console, &attr, console_thread, NULL);
+		pthread_attr_destroy(&attr);   	
+	} 
+	else if(!is_recovery_running){
+		process_autoexec();
 	}
-    esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
-    cfg.thread_name= "console";
-    cfg.inherit_cfg = true;
-    if(is_recovery_running){
-    	cfg.stack_size = 4096 ;
-    }
-    esp_pthread_set_cfg(&cfg);
-	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){
 	/* Try to run the command */
@@ -221,15 +251,16 @@ void run_command(char * line){
 	esp_err_t err = esp_console_run(line, &ret);
 
 	if (err == ESP_ERR_NOT_FOUND) {
-		ESP_LOGE(TAG,"Unrecognized command: %s\n", line);
+		ESP_LOGE(TAG,"Unrecognized command: %s", line);
 	} else if (err == ESP_ERR_INVALID_ARG) {
 		// command was empty
 	} else if (err == ESP_OK && ret != ESP_OK) {
-		ESP_LOGW(TAG,"Command returned non-zero error code: 0x%x (%s)\n", ret,
+		ESP_LOGW(TAG,"Command returned non-zero error code: 0x%x (%s)", ret,
 				esp_err_to_name(err));
 	} else if (err != ESP_OK) {
-		ESP_LOGE(TAG,"Internal error: %s\n", esp_err_to_name(err));
+		ESP_LOGE(TAG,"Internal error: %s", esp_err_to_name(err));
 	}
+
 }
 static void * console_thread() {
 	if(!is_recovery_running){

+ 5 - 3
components/raop/component.mk

@@ -7,7 +7,9 @@
 # please read the SDK documents if you need to do this.
 #
 
-CFLAGS += -fstack-usage \
-	-I$(COMPONENT_PATH)/../tools	\
-	-I$(COMPONENT_PATH)/../codecs/inc/alac \
+CFLAGS += -fstack-usage\
+	-I$(PROJECT_PATH)/components/tools	\
+	-I$(PROJECT_PATH)/components/codecs/inc/alac \
 	-I$(PROJECT_PATH)/main/	
+COMPONENT_ADD_INCLUDEDIRS := .
+COMPONENT_SRCDIRS := . 

+ 1 - 2
components/raop/raop.c

@@ -957,5 +957,4 @@ static void on_dmap_string(void *ctx, const char *code, const char *name, const
 	if (!strcasecmp(code, "asar")) metadata->artist = strndup(buf, len);
 	else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len);
 	else if (!strcasecmp(code, "minm")) metadata->title = strndup(buf, len);
-}
-
+}

+ 807 - 807
components/raop/rtp.c

@@ -1,807 +1,807 @@
-
-/*
- * HairTunes - RAOP packet handler and slave-clocked replay engine
- * Copyright (c) James Laird 2011
- * All rights reserved.
- *
- * Modularisation: philippe_44@outlook.com, 2019
- *
- * 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.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <sys/types.h>
-#include <pthread.h>
-#include <math.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <stdint.h>
-#include <fcntl.h>
-
-#include "platform.h"
-#include "rtp.h"
-#include "raop_sink.h"
-#include "log_util.h"
-#include "util.h"
-
-#ifdef WIN32
-#include <openssl/aes.h>
-#include "alac_wrapper.h"
-#include "assert.h"
-#define MSG_DONTWAIT 0
-#else
-#include "esp_pthread.h"
-#include "esp_system.h"
-#include "esp_assert.h"
-#include <mbedtls/version.h>
-#include <mbedtls/aes.h>
-#include "alac_wrapper.h"
-#endif
-
-#define NTP2MS(ntp) ((((ntp) >> 10) * 1000L) >> 22)
-#define MS2NTP(ms) (((((u64_t) (ms)) << 22) / 1000) << 10)
-#define NTP2TS(ntp, rate) ((((ntp) >> 16) * (rate)) >> 16)
-#define TS2NTP(ts, rate)  (((((u64_t) (ts)) << 16) / (rate)) << 16)
-#define MS2TS(ms, rate) ((((u64_t) (ms)) * (rate)) / 1000)
-#define TS2MS(ts, rate) NTP2MS(TS2NTP(ts,rate))
-
-
extern log_level 	raop_loglevel;
-
static log_level 	*loglevel = &raop_loglevel;
-
-//#define __RTP_STORE
-
-// default buffer size
-#define BUFFER_FRAMES 	( (150 * RAOP_SAMPLE_RATE * 2) / (352 * 100) )
-#define MAX_PACKET       1408
-#define MIN_LATENCY		11025
-#define MAX_LATENCY   	( (120 * RAOP_SAMPLE_RATE * 2) / 100 )
-
-#define RTP_STACK_SIZE	(4*1024)
-
-#define RTP_SYNC	(0x01)
-#define NTP_SYNC	(0x02)
-
-#define RESEND_TO	200
-
-enum { DATA = 0, CONTROL, TIMING };
-
-static const u8_t silence_frame[MAX_PACKET] = { 0 };
-
-typedef u16_t seq_t;
-typedef struct audio_buffer_entry {   // decoded audio packets
-	int ready;
-	u32_t rtptime, last_resend;
-	s16_t *data;
-	int len;
-} abuf_t;
-
-typedef struct rtp_s {
-#ifdef __RTP_STORE
-	FILE *rtpIN, *rtpOUT;
-#endif
-	bool running;
-	unsigned char aesiv[16];
-#ifdef WIN32
-	AES_KEY aes;
-#else
-	mbedtls_aes_context aes;
-#endif
-	bool decrypt;
-	u8_t *decrypt_buf;
-	u32_t frame_size, frame_duration;
-	u32_t in_frames, out_frames;
-	struct in_addr host;
-	struct sockaddr_in rtp_host;
-	struct {
-		unsigned short rport, lport;
-		int sock;
-	} rtp_sockets[3]; 					 // data, control, timing
-	struct timing_s {
-		u64_t local, remote;
-	} timing;
-	struct {
-		u32_t 	rtp, time;
-		u8_t  	status;
-	} synchro;
-	struct {
-		u32_t time;
-		seq_t seqno;
-		u32_t rtptime;
-	} record;
-	int latency;			// rtp hold depth in samples
-	u32_t resent_req, resent_rec;	// total resent + recovered frames
-	u32_t silent_frames;	// total silence frames
-	u32_t discarded;
-	abuf_t audio_buffer[BUFFER_FRAMES];
-	seq_t ab_read, ab_write;
-	pthread_mutex_t ab_mutex;
-#ifdef WIN32
-	pthread_t thread;
-#else
-	TaskHandle_t thread, joiner;
-	StaticTask_t *xTaskBuffer;
-    StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4)));
-#endif
-
-	struct alac_codec_s *alac_codec;
-	int flush_seqno;
-	bool playing;
-	raop_data_cb_t data_cb;
-	raop_cmd_cb_t cmd_cb;
-} rtp_t;
-
-
-#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
-static void 	buffer_alloc(abuf_t *audio_buffer, int size);
-static void 	buffer_release(abuf_t *audio_buffer);
-static void 	buffer_reset(abuf_t *audio_buffer);
-static void 	buffer_push_packet(rtp_t *ctx);
-static bool 	rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
-static bool 	rtp_request_timing(rtp_t *ctx);
-static void*	rtp_thread_func(void *arg);
-static int	  	seq_order(seq_t a, seq_t b);
-
-/*---------------------------------------------------------------------------*/
-static struct alac_codec_s* alac_init(int fmtp[32]) {
-	struct alac_codec_s *alac;
-	unsigned sample_rate;
-	unsigned char sample_size, channels;
-	struct {
-		uint32_t	frameLength;
-		uint8_t		compatibleVersion;
-		uint8_t		bitDepth;
-		uint8_t		pb;
-		uint8_t		mb;
-		uint8_t		kb;
-		uint8_t		numChannels;
-		uint16_t	maxRun;
-		uint32_t	maxFrameBytes;
-		uint32_t	avgBitRate;
-		uint32_t	sampleRate;
-	} config;
-
-	config.frameLength = htonl(fmtp[1]);
-	config.compatibleVersion = fmtp[2];
-	config.bitDepth = fmtp[3];
-	config.pb = fmtp[4];
-	config.mb = fmtp[5];
-	config.kb = fmtp[6];
-	config.numChannels = fmtp[7];
-	config.maxRun = htons(fmtp[8]);
-	config.maxFrameBytes = htonl(fmtp[9]);
-	config.avgBitRate = htonl(fmtp[10]);
-	config.sampleRate = htonl(fmtp[11]);
-
-	alac = alac_create_decoder(sizeof(config), (unsigned char*) &config, &sample_size, &sample_rate, &channels);
-	if (!alac) {
-		LOG_ERROR("cannot create alac codec", NULL);
-		return NULL;
-	}
-
-	return alac;
-}
-
-/*---------------------------------------------------------------------------*/
-rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, char *fmtpstr,
-								short unsigned pCtrlPort, short unsigned pTimingPort,
-								raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb)
-{
-	int i = 0;
-	char *arg;
-	int fmtp[12];
-	bool rc = true;
-	rtp_t *ctx = calloc(1, sizeof(rtp_t));
-	rtp_resp_t resp = { 0, 0, 0, NULL };
-
-	if (!ctx) return resp;
-	
-	ctx->host = host;
-	ctx->decrypt = false;
-	ctx->cmd_cb = cmd_cb;
-	ctx->data_cb = data_cb;
-	ctx->rtp_host.sin_family = AF_INET;
-	ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
-	pthread_mutex_init(&ctx->ab_mutex, 0);
-	ctx->flush_seqno = -1;
-	ctx->latency = latency;
-	ctx->ab_read = ctx->ab_write;
-
-#ifdef __RTP_STORE
-	ctx->rtpIN = fopen("airplay.rtpin", "wb");
-	ctx->rtpOUT = fopen("airplay.rtpout", "wb");
-#endif
-
-	ctx->rtp_sockets[CONTROL].rport = pCtrlPort;
-	ctx->rtp_sockets[TIMING].rport = pTimingPort;
-
-	if (aesiv && aeskey) {
-		memcpy(ctx->aesiv, aesiv, 16);
-#ifdef WIN32
-		AES_set_decrypt_key((unsigned char*) aeskey, 128, &ctx->aes);
-#else
-		memset(&ctx->aes, 0, sizeof(mbedtls_aes_context));
-		mbedtls_aes_setkey_dec(&ctx->aes, (unsigned char*) aeskey, 128);
-#endif
-		ctx->decrypt = true;
-		ctx->decrypt_buf = malloc(MAX_PACKET);
-	}
-
-	memset(fmtp, 0, sizeof(fmtp));
-	while ((arg = strsep(&fmtpstr, " \t")) != NULL) fmtp[i++] = atoi(arg);
-
-	ctx->frame_size = fmtp[1];
-	ctx->frame_duration = (ctx->frame_size * 1000) / RAOP_SAMPLE_RATE;
-
-	// alac decoder
-	ctx->alac_codec = alac_init(fmtp);
-	rc &= ctx->alac_codec != NULL;
-
-	buffer_alloc(ctx->audio_buffer, ctx->frame_size*4);
-
-	// create rtp ports
-	for (i = 0; i < 3; i++) {
-		ctx->rtp_sockets[i].sock = bind_socket(&ctx->rtp_sockets[i].lport, SOCK_DGRAM);
-		rc &= ctx->rtp_sockets[i].sock > 0;
-	}
-
-	// create http port and start listening
-	resp.cport = ctx->rtp_sockets[CONTROL].lport;
-	resp.tport = ctx->rtp_sockets[TIMING].lport;
-	resp.aport = ctx->rtp_sockets[DATA].lport;
-		
-	ctx->running = true;
-
-#ifdef WIN32
-	pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
-#else
-	ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
-	ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx,
-									 CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer );
-#endif
-	
-	// cleanup everything if we failed
-	if (!rc) {	
-		LOG_ERROR("[%p]: cannot start RTP", ctx);
-		rtp_end(ctx);
-		ctx = NULL;
-	}	
-	
-	resp.ctx = ctx;	
-	return resp;
-}
-
-/*---------------------------------------------------------------------------*/
-void rtp_end(rtp_t *ctx)
-{
-	int i;
-
-	if (!ctx) return;
-
-	if (ctx->running) {
-#if !defined WIN32		
-		ctx->joiner = xTaskGetCurrentTaskHandle();
-#endif
-		ctx->running = false;
-#ifdef WIN32
-		pthread_join(ctx->thread, NULL);
-#else
-		ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
-		vTaskDelete(ctx->thread);
-		heap_caps_free(ctx->xTaskBuffer);
-#endif
-	}
-	
-	for (i = 0; i < 3; i++) closesocket(ctx->rtp_sockets[i].sock);
-
-	if (ctx->alac_codec) alac_delete_decoder(ctx->alac_codec);
-	if (ctx->decrypt_buf) free(ctx->decrypt_buf);
-	
-	pthread_mutex_destroy(&ctx->ab_mutex);
-	buffer_release(ctx->audio_buffer);
-	
-	free(ctx);
-
-#ifdef __RTP_STORE
-	fclose(ctx->rtpIN);
-	fclose(ctx->rtpOUT);
-#endif
-}
-
-/*---------------------------------------------------------------------------*/
-bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
-{
-	bool rc = true;
-	u32_t now = gettime_ms();
-
-	if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
-		rc = false;
-		LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
-	} else {
-		pthread_mutex_lock(&ctx->ab_mutex);
-		buffer_reset(ctx->audio_buffer);
-		ctx->playing = false;
-		ctx->flush_seqno = seqno;
-		if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
-	}
-
-	LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
-
-	return rc;
-}
-
-/*---------------------------------------------------------------------------*/
-void rtp_flush_release(rtp_t *ctx) {
-	pthread_mutex_unlock(&ctx->ab_mutex);
-}
-
-
-/*---------------------------------------------------------------------------*/
-void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
-	ctx->record.seqno = seqno;
-	ctx->record.rtptime = rtptime;
-	ctx->record.time = gettime_ms();
-
-	LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_alloc(abuf_t *audio_buffer, int size) {
-	int i;
-	for (i = 0; i < BUFFER_FRAMES; i++) {
-		audio_buffer[i].data = malloc(size);
-		audio_buffer[i].ready = 0;
-	}
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_release(abuf_t *audio_buffer) {
-	int i;
-	for (i = 0; i < BUFFER_FRAMES; i++) {
-		free(audio_buffer[i].data);
-	}
-}
-
-/*---------------------------------------------------------------------------*/
-static void buffer_reset(abuf_t *audio_buffer) {
-	int i;
-	for (i = 0; i < BUFFER_FRAMES; i++) audio_buffer[i].ready = 0;
-}
-
-/*---------------------------------------------------------------------------*/
-// the sequence numbers will wrap pretty often.
-// this returns true if the second arg is after the first
-static int seq_order(seq_t a, seq_t b) {
-	s16_t d = b - a;
-	return d > 0;
-}
-
-/*---------------------------------------------------------------------------*/
-static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, int *outsize) {
-	unsigned char iv[16];
-	int aeslen;
-	assert(len<=MAX_PACKET);
-
-	if (ctx->decrypt) {
-		aeslen = len & ~0xf;
-		memcpy(iv, ctx->aesiv, sizeof(iv));
-#ifdef WIN32
-		AES_cbc_encrypt((unsigned char*)buf, ctx->decrypt_buf, aeslen, &ctx->aes, iv, AES_DECRYPT);
-#else
-		mbedtls_aes_crypt_cbc(&ctx->aes, MBEDTLS_AES_DECRYPT, aeslen, iv, (unsigned char*) buf, ctx->decrypt_buf);
-#endif
-		memcpy(ctx->decrypt_buf+aeslen, buf+aeslen, len-aeslen);
-		alac_to_pcm(ctx->alac_codec, (unsigned char*) ctx->decrypt_buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
-	} else {
-		alac_to_pcm(ctx->alac_codec, (unsigned char*) buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
-	}	
-	
-	*outsize *= 4;
-}
-
-
-/*---------------------------------------------------------------------------*/
-static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
-	abuf_t *abuf = NULL;
-	u32_t playtime;
-
-	pthread_mutex_lock(&ctx->ab_mutex);
-
-	if (!ctx->playing) {
-		if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
-		   (ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
-			ctx->ab_write = seqno-1;
-			ctx->ab_read = seqno;
-			ctx->flush_seqno = -1;
-			ctx->playing = true;
-			ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
-			playtime = ctx->synchro.time + (((s32_t)(rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
-			ctx->cmd_cb(RAOP_PLAY, playtime);
-		} else {
-			pthread_mutex_unlock(&ctx->ab_mutex);
-			return;
-		}
-	}
-
-	if (seqno == (u16_t) (ctx->ab_write+1)) {
-		// expected packet
-		abuf = ctx->audio_buffer + BUFIDX(seqno);
-		ctx->ab_write = seqno;
-		LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
-
-	} else if (seq_order(ctx->ab_write, seqno)) {
-		seq_t i;
-		u32_t now;
-
-		// newer than expected
-		if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
-			// only get rtp latency-1 frames back (last one is seqno)
-			LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
-			ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
-		}
-
-		// need to request re-send and adjust timing of gaps
-		rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
-		for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
-			ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
-			ctx->audio_buffer[BUFIDX(i)].last_resend = now;
-		}
-
-		LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
-		abuf = ctx->audio_buffer + BUFIDX(seqno);
-		ctx->ab_write = seqno;
-	} else if (seq_order(ctx->ab_read, seqno + 1)) {
-		// recovered packet, not yet sent
-		abuf = ctx->audio_buffer + BUFIDX(seqno);
-		ctx->resent_rec++;
-		LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
-	} else {
-		// too late
-		LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
-	}
-
-	if (ctx->in_frames++ > 1000) {
-		LOG_INFO("[%p]: fill [level:%hu rec:%u] [W:%hu R:%hu]", ctx, ctx->ab_write - ctx->ab_read, ctx->resent_rec, ctx->ab_write, ctx->ab_read);
-		ctx->in_frames = 0;
-	}
-
-	if (abuf) {
-		alac_decode(ctx, abuf->data, data, len, &abuf->len);
-		abuf->ready = 1;
-		// this is the local rtptime when this frame is expected to play
-		abuf->rtptime = rtptime;
-		buffer_push_packet(ctx);
-
-#ifdef __RTP_STORE
-		fwrite(data, len, 1, ctx->rtpIN);
-		fwrite(abuf->data, abuf->len, 1, ctx->rtpOUT);
-#endif
-	}
-
-	pthread_mutex_unlock(&ctx->ab_mutex);
-}
-
-/*---------------------------------------------------------------------------*/
-// push as many frames as possible through callback
-static void buffer_push_packet(rtp_t *ctx) {
-	abuf_t *curframe = NULL;
-	u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
-	int i;
-
-	// not ready to play yet
-	if (!ctx->playing ||  ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
-
-	// maybe re-evaluate time in loop in case data callback blocks ...
-	now = gettime_ms();
-
-	// there is always at least one frame in the buffer
-	do {
-
-		curframe = ctx->audio_buffer + BUFIDX(ctx->ab_read);
-		playtime = ctx->synchro.time + (((s32_t)(curframe->rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
-
-		if (now > playtime) {
-			LOG_DEBUG("[%p]: discarded frame now:%u missed by:%d (W:%hu R:%hu)", ctx, now, now - playtime, ctx->ab_write, ctx->ab_read);
-			ctx->discarded++;
-			curframe->ready = 0;
-		} else if (playtime - now <= hold) {
-			if (curframe->ready) {
-				ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
-				curframe->ready = 0;
-			} else {
-				LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
-				ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
-				ctx->silent_frames++;
-			}
-		} else if (curframe->ready) {
-			ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
-			curframe->ready = 0;
-		} else {
-			break;
-		}
-
-		ctx->ab_read++;
-		ctx->out_frames++;
-
-	} while (seq_order(ctx->ab_read, ctx->ab_write));
-
-	if (ctx->out_frames > 1000) {
-		LOG_INFO("[%p]: drain [level:%hd head:%d ms] [W:%hu R:%hu] [req:%u sil:%u dis:%u]",
-				ctx, ctx->ab_write - ctx->ab_read, playtime - now, ctx->ab_write, ctx->ab_read,
-				ctx->resent_req, ctx->silent_frames, ctx->discarded);
-		ctx->out_frames = 0;
-	}
-
-	LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
-
-	// each missing packet will be requested up to (latency_frames / 16) times
-	for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
-		abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
-		if (!frame->ready && now - frame->last_resend > RESEND_TO) {
-			rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
-			frame->last_resend = now;
-		}
-	}
-
}
-
-
-/*---------------------------------------------------------------------------*/
-static void *rtp_thread_func(void *arg) {
-	fd_set fds;
-	int i, sock = -1;
-	int count = 0;
-	bool ntp_sent;
-	char *packet = malloc(MAX_PACKET);
-	rtp_t *ctx = (rtp_t*) arg;
-
-	for (i = 0; i < 3; i++) {
-		if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
-		// send synchro request 3 times
-		ntp_sent = rtp_request_timing(ctx);
-	}
-
-	while (ctx->running) {
-		ssize_t plen;
-		char type;
-		socklen_t rtp_client_len = sizeof(struct sockaddr_in);
-		int idx = 0;
-		char *pktp = packet;
-		struct timeval timeout = {0, 100*1000};
-
-		FD_ZERO(&fds);
-		for (i = 0; i < 3; i++)	{ FD_SET(ctx->rtp_sockets[i].sock, &fds); }
-
-		if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue;
-
-		for (i = 0; i < 3; i++)
-			if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i;
-
-		plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len);
-
-		if (!ntp_sent) {
-			LOG_WARN("[%p]: NTP request not send yet", ctx);
-			ntp_sent = rtp_request_timing(ctx);
-		}
-
-		if (plen <= 0) {
-			LOG_WARN("Nothing received on a readable socket %d", plen);
-			continue;
-		}
-		
-		assert(plen <= MAX_PACKET);
-
-		type = packet[1] & ~0x80;
-		pktp = packet;
-
-		switch (type) {
-			seq_t seqno;
-			unsigned rtptime;
-
-			// re-sent packet
-			case 0x56: {
-				pktp += 4;
-				plen -= 4;
-			}
-
-			// data packet
-			case 0x60: {
-				seqno = ntohs(*(u16_t*)(pktp+2));
-				rtptime = ntohl(*(u32_t*)(pktp+4));
-
-				// adjust pointer and length
-				pktp += 12;
-				plen -= 12;
-
-				LOG_SDEBUG("[%p]: seqno:%hu rtp:%u (type: %x, first: %u)", ctx, seqno, rtptime, type, packet[1] & 0x80);
-
-				// check if packet contains enough content to be reasonable
-				if (plen < 16) break;
-
-				if ((packet[1] & 0x80) && (type != 0x56)) {
-					LOG_INFO("[%p]: 1st audio packet received", ctx);
-				}
-
-				buffer_put_packet(ctx, seqno, rtptime, packet[1] & 0x80, pktp, plen);
-
-				break;
-			}
-
-			// sync packet
-			case 0x54: {
-				u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
-				u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
-				u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
-				u16_t flags = ntohs(*(u16_t*)(pktp+2));
-				u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
-
-				// something is wrong and if we are supposed to be NTP synced, better ask for re-sync
-				if (remote_gap > 10000) {
-					if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx);
-					LOG_WARN("discarding remote timing information %u", remote_gap);
-					break;
-				}
-
-				pthread_mutex_lock(&ctx->ab_mutex);
-
-				// re-align timestamp and expected local playback time (and magic 11025 latency)
-				ctx->latency = rtp_now - rtp_now_latency;
-				if (flags == 7 || flags == 4) ctx->latency += 11025;
-				if (ctx->latency < MIN_LATENCY) ctx->latency = MIN_LATENCY;
-				else if (ctx->latency > MAX_LATENCY) ctx->latency = MAX_LATENCY;
-				ctx->synchro.rtp = rtp_now - ctx->latency;
-				ctx->synchro.time = ctx->timing.local + remote_gap;
-
-				// now we are synced on RTP frames
-				ctx->synchro.status |= RTP_SYNC;
-
-				// 1st sync packet received (signals a restart of playback)
-				if (packet[0] & 0x10) {
-					LOG_INFO("[%p]: 1st sync packet received", ctx);
-				}
-
-				pthread_mutex_unlock(&ctx->ab_mutex);
-
-				LOG_DEBUG("[%p]: sync packet latency:%d rtp_latency:%u rtp:%u remote ntp:%llx, local time:%u local rtp:%u (now:%u)",
-						  ctx, ctx->latency, rtp_now_latency, rtp_now, remote, ctx->synchro.time, ctx->synchro.rtp, gettime_ms());
-
-				if (!count--) {
-					rtp_request_timing(ctx);
-					count = 3;
-				}
-
-				if ((ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) ctx->cmd_cb(RAOP_TIMING);
-
-				break;
-			}
-
-			// NTP timing packet
-			case 0x53: {
-				u64_t expected;
-				u32_t reference   = ntohl(*(u32_t*)(pktp+12)); // only low 32 bits in our case
-				u64_t remote 	  =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
-				u32_t roundtrip   = gettime_ms() - reference;
-
-				// better discard sync packets when roundtrip is suspicious and ask for another one
-				if (roundtrip > 100) {
-					rtp_request_timing(ctx);
-					LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip);
-					break;
-				}
-
-				/*
-				  The expected elapsed remote time should be exactly the same as
-				  elapsed local time between the two request, corrected by the
-				  drifting
-				*/
-				expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
-
-				ctx->timing.remote = remote;
-				ctx->timing.local = reference;
-
-				// now we are synced on NTP (mutex not needed)
-				ctx->synchro.status |= NTP_SYNC;
-
-				LOG_DEBUG("[%p]: Timing references local:%llu, remote:%llx (delta:%lld, sum:%lld, adjust:%lld, gaps:%d)",
-						  ctx, ctx->timing.local, ctx->timing.remote);
-
-				break;
-			}
-			
-			default: {
-				LOG_WARN("Unknown packet received %x", (int) type);
-				break;
-			}
-		}
-	}
-
-	free(packet);
-	LOG_INFO("[%p]: terminating", ctx);
-
-#ifndef WIN32
-	xTaskNotifyGive(ctx->joiner);
-	vTaskSuspend(NULL);
-#endif
-
-	return NULL;
-}
-
-/*---------------------------------------------------------------------------*/
-static bool rtp_request_timing(rtp_t *ctx) {
-	unsigned char req[32];
-	u32_t now = gettime_ms();
-	int i;
-	struct sockaddr_in host;
-
-	LOG_DEBUG("[%p]: timing request now:%u (port: %hu)", ctx, now, ctx->rtp_sockets[TIMING].rport);
-
-	req[0] = 0x80;
-	req[1] = 0x52|0x80;
-	*(u16_t*)(req+2) = htons(7);
-	*(u32_t*)(req+4) = htonl(0);  // dummy
-	for (i = 0; i < 16; i++) req[i+8] = 0;
-	*(u32_t*)(req+24) = 0;
-	*(u32_t*)(req+28) = htonl(now); // this is not a real NTP, but a 32 ms counter in the low part of the NTP
-
-	if (ctx->host.s_addr != INADDR_ANY) {
-		host.sin_family = AF_INET;
-		host.sin_addr =	ctx->host;
-	} else host = ctx->rtp_host;
-
-	// no address from sender, need to wait for 1st packet to be received
-	if (host.sin_addr.s_addr == INADDR_ANY) return false;
-
-	host.sin_port = htons(ctx->rtp_sockets[TIMING].rport);
-
-	if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &host, sizeof(host))) {
-		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
-	}
-
-	return true;
-}
-
-/*---------------------------------------------------------------------------*/
-static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
-	unsigned char req[8];    // *not* a standard RTCP NACK
-
-	// do not request silly ranges (happens in case of network large blackouts)
-	if (seq_order(last, first) || last - first > BUFFER_FRAMES / 2) return false;
-	
-	ctx->resent_req += last - first + 1;
-
-	LOG_DEBUG("resend request [W:%hu R:%hu first=%hu last=%hu]", ctx->ab_write, ctx->ab_read, first, last);
-
-	req[0] = 0x80;
-	req[1] = 0x55|0x80;  // Apple 'resend'
-	*(u16_t*)(req+2) = htons(1);  // our seqnum
-	*(u16_t*)(req+4) = htons(first);  // missed seqnum
-	*(u16_t*)(req+6) = htons(last-first+1);  // count
-
-	ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
-
-	if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
-		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
-	}
-
-	return true;
-}
-
+
+/*
+ * HairTunes - RAOP packet handler and slave-clocked replay engine
+ * Copyright (c) James Laird 2011
+ * All rights reserved.
+ *
+ * Modularisation: philippe_44@outlook.com, 2019
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <pthread.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <fcntl.h>
+
+#include "platform.h"
+#include "rtp.h"
+#include "raop_sink.h"
+#include "log_util.h"
+#include "util.h"
+
+#ifdef WIN32
+#include <openssl/aes.h>
+#include "alac_wrapper.h"
+#include "assert.h"
+#define MSG_DONTWAIT 0
+#else
+#include "esp_pthread.h"
+#include "esp_system.h"
+#include "esp_assert.h"
+#include <mbedtls/version.h>
+#include <mbedtls/aes.h>
+#include "alac_wrapper.h"
+#endif
+
+#define NTP2MS(ntp) ((((ntp) >> 10) * 1000L) >> 22)
+#define MS2NTP(ms) (((((u64_t) (ms)) << 22) / 1000) << 10)
+#define NTP2TS(ntp, rate) ((((ntp) >> 16) * (rate)) >> 16)
+#define TS2NTP(ts, rate)  (((((u64_t) (ts)) << 16) / (rate)) << 16)
+#define MS2TS(ms, rate) ((((u64_t) (ms)) * (rate)) / 1000)
+#define TS2MS(ts, rate) NTP2MS(TS2NTP(ts,rate))
+
+
extern log_level 	raop_loglevel;
+
static log_level 	*loglevel = &raop_loglevel;
+
+//#define __RTP_STORE
+
+// default buffer size
+#define BUFFER_FRAMES 	( (150 * RAOP_SAMPLE_RATE * 2) / (352 * 100) )
+#define MAX_PACKET       1408
+#define MIN_LATENCY		11025
+#define MAX_LATENCY   	( (120 * RAOP_SAMPLE_RATE * 2) / 100 )
+
+#define RTP_STACK_SIZE	(4*1024)
+
+#define RTP_SYNC	(0x01)
+#define NTP_SYNC	(0x02)
+
+#define RESEND_TO	200
+
+enum { DATA = 0, CONTROL, TIMING };
+
+static const u8_t silence_frame[MAX_PACKET] = { 0 };
+
+typedef u16_t seq_t;
+typedef struct audio_buffer_entry {   // decoded audio packets
+	int ready;
+	u32_t rtptime, last_resend;
+	s16_t *data;
+	int len;
+} abuf_t;
+
+typedef struct rtp_s {
+#ifdef __RTP_STORE
+	FILE *rtpIN, *rtpOUT;
+#endif
+	bool running;
+	unsigned char aesiv[16];
+#ifdef WIN32
+	AES_KEY aes;
+#else
+	mbedtls_aes_context aes;
+#endif
+	bool decrypt;
+	u8_t *decrypt_buf;
+	u32_t frame_size, frame_duration;
+	u32_t in_frames, out_frames;
+	struct in_addr host;
+	struct sockaddr_in rtp_host;
+	struct {
+		unsigned short rport, lport;
+		int sock;
+	} rtp_sockets[3]; 					 // data, control, timing
+	struct timing_s {
+		u64_t local, remote;
+	} timing;
+	struct {
+		u32_t 	rtp, time;
+		u8_t  	status;
+	} synchro;
+	struct {
+		u32_t time;
+		seq_t seqno;
+		u32_t rtptime;
+	} record;
+	int latency;			// rtp hold depth in samples
+	u32_t resent_req, resent_rec;	// total resent + recovered frames
+	u32_t silent_frames;	// total silence frames
+	u32_t discarded;
+	abuf_t audio_buffer[BUFFER_FRAMES];
+	seq_t ab_read, ab_write;
+	pthread_mutex_t ab_mutex;
+#ifdef WIN32
+	pthread_t thread;
+#else
+	TaskHandle_t thread, joiner;
+	StaticTask_t *xTaskBuffer;
+    StackType_t xStack[RTP_STACK_SIZE] __attribute__ ((aligned (4)));
+#endif
+
+	struct alac_codec_s *alac_codec;
+	int flush_seqno;
+	bool playing;
+	raop_data_cb_t data_cb;
+	raop_cmd_cb_t cmd_cb;
+} rtp_t;
+
+
+#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
+static void 	buffer_alloc(abuf_t *audio_buffer, int size);
+static void 	buffer_release(abuf_t *audio_buffer);
+static void 	buffer_reset(abuf_t *audio_buffer);
+static void 	buffer_push_packet(rtp_t *ctx);
+static bool 	rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
+static bool 	rtp_request_timing(rtp_t *ctx);
+static void*	rtp_thread_func(void *arg);
+static int	  	seq_order(seq_t a, seq_t b);
+
+/*---------------------------------------------------------------------------*/
+static struct alac_codec_s* alac_init(int fmtp[32]) {
+	struct alac_codec_s *alac;
+	unsigned sample_rate;
+	unsigned char sample_size, channels;
+	struct {
+		uint32_t	frameLength;
+		uint8_t		compatibleVersion;
+		uint8_t		bitDepth;
+		uint8_t		pb;
+		uint8_t		mb;
+		uint8_t		kb;
+		uint8_t		numChannels;
+		uint16_t	maxRun;
+		uint32_t	maxFrameBytes;
+		uint32_t	avgBitRate;
+		uint32_t	sampleRate;
+	} config;
+
+	config.frameLength = htonl(fmtp[1]);
+	config.compatibleVersion = fmtp[2];
+	config.bitDepth = fmtp[3];
+	config.pb = fmtp[4];
+	config.mb = fmtp[5];
+	config.kb = fmtp[6];
+	config.numChannels = fmtp[7];
+	config.maxRun = htons(fmtp[8]);
+	config.maxFrameBytes = htonl(fmtp[9]);
+	config.avgBitRate = htonl(fmtp[10]);
+	config.sampleRate = htonl(fmtp[11]);
+
+	alac = alac_create_decoder(sizeof(config), (unsigned char*) &config, &sample_size, &sample_rate, &channels);
+	if (!alac) {
+		LOG_ERROR("cannot create alac codec", NULL);
+		return NULL;
+	}
+
+	return alac;
+}
+
+/*---------------------------------------------------------------------------*/
+rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, char *fmtpstr,
+								short unsigned pCtrlPort, short unsigned pTimingPort,
+								raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb)
+{
+	int i = 0;
+	char *arg;
+	int fmtp[12];
+	bool rc = true;
+	rtp_t *ctx = calloc(1, sizeof(rtp_t));
+	rtp_resp_t resp = { 0, 0, 0, NULL };
+
+	if (!ctx) return resp;
+	
+	ctx->host = host;
+	ctx->decrypt = false;
+	ctx->cmd_cb = cmd_cb;
+	ctx->data_cb = data_cb;
+	ctx->rtp_host.sin_family = AF_INET;
+	ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
+	pthread_mutex_init(&ctx->ab_mutex, 0);
+	ctx->flush_seqno = -1;
+	ctx->latency = latency;
+	ctx->ab_read = ctx->ab_write;
+
+#ifdef __RTP_STORE
+	ctx->rtpIN = fopen("airplay.rtpin", "wb");
+	ctx->rtpOUT = fopen("airplay.rtpout", "wb");
+#endif
+
+	ctx->rtp_sockets[CONTROL].rport = pCtrlPort;
+	ctx->rtp_sockets[TIMING].rport = pTimingPort;
+
+	if (aesiv && aeskey) {
+		memcpy(ctx->aesiv, aesiv, 16);
+#ifdef WIN32
+		AES_set_decrypt_key((unsigned char*) aeskey, 128, &ctx->aes);
+#else
+		memset(&ctx->aes, 0, sizeof(mbedtls_aes_context));
+		mbedtls_aes_setkey_dec(&ctx->aes, (unsigned char*) aeskey, 128);
+#endif
+		ctx->decrypt = true;
+		ctx->decrypt_buf = malloc(MAX_PACKET);
+	}
+
+	memset(fmtp, 0, sizeof(fmtp));
+	while ((arg = strsep(&fmtpstr, " \t")) != NULL) fmtp[i++] = atoi(arg);
+
+	ctx->frame_size = fmtp[1];
+	ctx->frame_duration = (ctx->frame_size * 1000) / RAOP_SAMPLE_RATE;
+
+	// alac decoder
+	ctx->alac_codec = alac_init(fmtp);
+	rc &= ctx->alac_codec != NULL;
+
+	buffer_alloc(ctx->audio_buffer, ctx->frame_size*4);
+
+	// create rtp ports
+	for (i = 0; i < 3; i++) {
+		ctx->rtp_sockets[i].sock = bind_socket(&ctx->rtp_sockets[i].lport, SOCK_DGRAM);
+		rc &= ctx->rtp_sockets[i].sock > 0;
+	}
+
+	// create http port and start listening
+	resp.cport = ctx->rtp_sockets[CONTROL].lport;
+	resp.tport = ctx->rtp_sockets[TIMING].lport;
+	resp.aport = ctx->rtp_sockets[DATA].lport;
+		
+	ctx->running = true;
+
+#ifdef WIN32
+	pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
+#else
+	ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
+	ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx,
+									 CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer );
+#endif
+	
+	// cleanup everything if we failed
+	if (!rc) {	
+		LOG_ERROR("[%p]: cannot start RTP", ctx);
+		rtp_end(ctx);
+		ctx = NULL;
+	}	
+	
+	resp.ctx = ctx;	
+	return resp;
+}
+
+/*---------------------------------------------------------------------------*/
+void rtp_end(rtp_t *ctx)
+{
+	int i;
+
+	if (!ctx) return;
+
+	if (ctx->running) {
+#if !defined WIN32		
+		ctx->joiner = xTaskGetCurrentTaskHandle();
+#endif
+		ctx->running = false;
+#ifdef WIN32
+		pthread_join(ctx->thread, NULL);
+#else
+		ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
+		vTaskDelete(ctx->thread);
+		heap_caps_free(ctx->xTaskBuffer);
+#endif
+	}
+	
+	for (i = 0; i < 3; i++) closesocket(ctx->rtp_sockets[i].sock);
+
+	if (ctx->alac_codec) alac_delete_decoder(ctx->alac_codec);
+	if (ctx->decrypt_buf) free(ctx->decrypt_buf);
+	
+	pthread_mutex_destroy(&ctx->ab_mutex);
+	buffer_release(ctx->audio_buffer);
+	
+	free(ctx);
+
+#ifdef __RTP_STORE
+	fclose(ctx->rtpIN);
+	fclose(ctx->rtpOUT);
+#endif
+}
+
+/*---------------------------------------------------------------------------*/
+bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
+{
+	bool rc = true;
+	u32_t now = gettime_ms();
+
+	if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
+		rc = false;
+		LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
+	} else {
+		pthread_mutex_lock(&ctx->ab_mutex);
+		buffer_reset(ctx->audio_buffer);
+		ctx->playing = false;
+		ctx->flush_seqno = seqno;
+		if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
+	}
+
+	LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
+
+	return rc;
+}
+
+/*---------------------------------------------------------------------------*/
+void rtp_flush_release(rtp_t *ctx) {
+	pthread_mutex_unlock(&ctx->ab_mutex);
+}
+
+
+/*---------------------------------------------------------------------------*/
+void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
+	ctx->record.seqno = seqno;
+	ctx->record.rtptime = rtptime;
+	ctx->record.time = gettime_ms();
+
+	LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_alloc(abuf_t *audio_buffer, int size) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) {
+		audio_buffer[i].data = malloc(size);
+		audio_buffer[i].ready = 0;
+	}
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_release(abuf_t *audio_buffer) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) {
+		free(audio_buffer[i].data);
+	}
+}
+
+/*---------------------------------------------------------------------------*/
+static void buffer_reset(abuf_t *audio_buffer) {
+	int i;
+	for (i = 0; i < BUFFER_FRAMES; i++) audio_buffer[i].ready = 0;
+}
+
+/*---------------------------------------------------------------------------*/
+// the sequence numbers will wrap pretty often.
+// this returns true if the second arg is after the first
+static int seq_order(seq_t a, seq_t b) {
+	s16_t d = b - a;
+	return d > 0;
+}
+
+/*---------------------------------------------------------------------------*/
+static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, int *outsize) {
+	unsigned char iv[16];
+	int aeslen;
+	assert(len<=MAX_PACKET);
+
+	if (ctx->decrypt) {
+		aeslen = len & ~0xf;
+		memcpy(iv, ctx->aesiv, sizeof(iv));
+#ifdef WIN32
+		AES_cbc_encrypt((unsigned char*)buf, ctx->decrypt_buf, aeslen, &ctx->aes, iv, AES_DECRYPT);
+#else
+		mbedtls_aes_crypt_cbc(&ctx->aes, MBEDTLS_AES_DECRYPT, aeslen, iv, (unsigned char*) buf, ctx->decrypt_buf);
+#endif
+		memcpy(ctx->decrypt_buf+aeslen, buf+aeslen, len-aeslen);
+		alac_to_pcm(ctx->alac_codec, (unsigned char*) ctx->decrypt_buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
+	} else {
+		alac_to_pcm(ctx->alac_codec, (unsigned char*) buf, (unsigned char*) dest, 2, (unsigned int*) outsize);
+	}	
+	
+	*outsize *= 4;
+}
+
+
+/*---------------------------------------------------------------------------*/
+static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
+	abuf_t *abuf = NULL;
+	u32_t playtime;
+
+	pthread_mutex_lock(&ctx->ab_mutex);
+
+	if (!ctx->playing) {
+		if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
+		   (ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
+			ctx->ab_write = seqno-1;
+			ctx->ab_read = seqno;
+			ctx->flush_seqno = -1;
+			ctx->playing = true;
+			ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
+			playtime = ctx->synchro.time + (((s32_t)(rtptime - ctx->synchro.rtp)) * 1000) / RAOP_SAMPLE_RATE;
+			ctx->cmd_cb(RAOP_PLAY, playtime);
+		} else {
+			pthread_mutex_unlock(&ctx->ab_mutex);
+			return;
+		}
+	}
+
+	if (seqno == (u16_t) (ctx->ab_write+1)) {
+		// expected packet
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		ctx->ab_write = seqno;
+		LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
+
+	} else if (seq_order(ctx->ab_write, seqno)) {
+		seq_t i;
+		u32_t now;
+
+		// newer than expected
+		if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
+			// only get rtp latency-1 frames back (last one is seqno)
+			LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
+			ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
+		}
+
+		// need to request re-send and adjust timing of gaps
+		rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
+		for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
+			ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
+			ctx->audio_buffer[BUFIDX(i)].last_resend = now;
+		}
+
+		LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		ctx->ab_write = seqno;
+	} else if (seq_order(ctx->ab_read, seqno + 1)) {
+		// recovered packet, not yet sent
+		abuf = ctx->audio_buffer + BUFIDX(seqno);
+		ctx->resent_rec++;
+		LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+	} else {
+		// too late
+		LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
+	}
+
+	if (ctx->in_frames++ > 1000) {
+		LOG_INFO("[%p]: fill [level:%hu rec:%u] [W:%hu R:%hu]", ctx, ctx->ab_write - ctx->ab_read, ctx->resent_rec, ctx->ab_write, ctx->ab_read);
+		ctx->in_frames = 0;
+	}
+
+	if (abuf) {
+		alac_decode(ctx, abuf->data, data, len, &abuf->len);
+		abuf->ready = 1;
+		// this is the local rtptime when this frame is expected to play
+		abuf->rtptime = rtptime;
+		buffer_push_packet(ctx);
+
+#ifdef __RTP_STORE
+		fwrite(data, len, 1, ctx->rtpIN);
+		fwrite(abuf->data, abuf->len, 1, ctx->rtpOUT);
+#endif
+	}
+
+	pthread_mutex_unlock(&ctx->ab_mutex);
+}
+
+/*---------------------------------------------------------------------------*/
+// push as many frames as possible through callback
+static void buffer_push_packet(rtp_t *ctx) {
+	abuf_t *curframe = NULL;
+	u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
+	int i;
+
+	// not ready to play yet
+	if (!ctx->playing ||  ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
+
+	// there is always at least one frame in the buffer
+	do {
+		// re-evaluate time in loop in case data callback blocks ...
+		now = gettime_ms();
+
+		// try to manage playtime so that we overflow as late as possible if we miss NTP (2^31 / 10 / 44100)
+		curframe = ctx->audio_buffer + BUFIDX(ctx->ab_read);
+		playtime = ctx->synchro.time + (((s32_t)(curframe->rtptime - ctx->synchro.rtp)) * 10) / (RAOP_SAMPLE_RATE / 100);
+
+		if (now > playtime) {
+			LOG_DEBUG("[%p]: discarded frame now:%u missed by:%d (W:%hu R:%hu)", ctx, now, now - playtime, ctx->ab_write, ctx->ab_read);
+			ctx->discarded++;
+			curframe->ready = 0;
+		} else if (playtime - now <= hold) {
+			if (curframe->ready) {
+				ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
+				curframe->ready = 0;
+			} else {
+				LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
+				ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
+				ctx->silent_frames++;
+			}
+		} else if (curframe->ready) {
+			ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
+			curframe->ready = 0;
+		} else {
+			break;
+		}
+
+		ctx->ab_read++;
+		ctx->out_frames++;
+
+	} while (seq_order(ctx->ab_read, ctx->ab_write));
+
+	if (ctx->out_frames > 1000) {
+		LOG_INFO("[%p]: drain [level:%hd head:%d ms] [W:%hu R:%hu] [req:%u sil:%u dis:%u]",
+				ctx, ctx->ab_write - ctx->ab_read, playtime - now, ctx->ab_write, ctx->ab_read,
+				ctx->resent_req, ctx->silent_frames, ctx->discarded);
+		ctx->out_frames = 0;
+	}
+
+	LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
+
+	// each missing packet will be requested up to (latency_frames / 16) times
+	for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
+		abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
+		if (!frame->ready && now - frame->last_resend > RESEND_TO) {
+			rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
+			frame->last_resend = now;
+		}
+	}
+
}
+
+
+/*---------------------------------------------------------------------------*/
+static void *rtp_thread_func(void *arg) {
+	fd_set fds;
+	int i, sock = -1;
+	int count = 0;
+	bool ntp_sent;
+	char *packet = malloc(MAX_PACKET);
+	rtp_t *ctx = (rtp_t*) arg;
+
+	for (i = 0; i < 3; i++) {
+		if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
+		// send synchro request 3 times
+		ntp_sent = rtp_request_timing(ctx);
+	}
+
+	while (ctx->running) {
+		ssize_t plen;
+		char type;
+		socklen_t rtp_client_len = sizeof(struct sockaddr_in);
+		int idx = 0;
+		char *pktp = packet;
+		struct timeval timeout = {0, 100*1000};
+
+		FD_ZERO(&fds);
+		for (i = 0; i < 3; i++)	{ FD_SET(ctx->rtp_sockets[i].sock, &fds); }
+
+		if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue;
+
+		for (i = 0; i < 3; i++)
+			if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i;
+
+		plen = recvfrom(ctx->rtp_sockets[idx].sock, packet, MAX_PACKET, MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, &rtp_client_len);
+
+		if (!ntp_sent) {
+			LOG_WARN("[%p]: NTP request not send yet", ctx);
+			ntp_sent = rtp_request_timing(ctx);
+		}
+
+		if (plen <= 0) {
+			LOG_WARN("Nothing received on a readable socket %d", plen);
+			continue;
+		}
+		
+		assert(plen <= MAX_PACKET);
+
+		type = packet[1] & ~0x80;
+		pktp = packet;
+
+		switch (type) {
+			seq_t seqno;
+			unsigned rtptime;
+
+			// re-sent packet
+			case 0x56: {
+				pktp += 4;
+				plen -= 4;
+			}
+
+			// data packet
+			case 0x60: {
+				seqno = ntohs(*(u16_t*)(pktp+2));
+				rtptime = ntohl(*(u32_t*)(pktp+4));
+
+				// adjust pointer and length
+				pktp += 12;
+				plen -= 12;
+
+				LOG_SDEBUG("[%p]: seqno:%hu rtp:%u (type: %x, first: %u)", ctx, seqno, rtptime, type, packet[1] & 0x80);
+
+				// check if packet contains enough content to be reasonable
+				if (plen < 16) break;
+
+				if ((packet[1] & 0x80) && (type != 0x56)) {
+					LOG_INFO("[%p]: 1st audio packet received", ctx);
+				}
+
+				buffer_put_packet(ctx, seqno, rtptime, packet[1] & 0x80, pktp, plen);
+
+				break;
+			}
+
+			// sync packet
+			case 0x54: {
+				u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
+				u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
+				u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
+				u16_t flags = ntohs(*(u16_t*)(pktp+2));
+				u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
+
+				// try to get NTP every 3 sec or every time if we are not synced
+				if (!count-- || !(ctx->synchro.status && NTP_SYNC)) {
+					rtp_request_timing(ctx);
+					count = 3;
+				}
+
+				// something is wrong, we should not have such gap
+				if (remote_gap > 10000) {
+					LOG_WARN("discarding remote timing information %u", remote_gap);
+					break;
+				}
+
+				pthread_mutex_lock(&ctx->ab_mutex);
+
+				// re-align timestamp and expected local playback time (and magic 11025 latency)
+				ctx->latency = rtp_now - rtp_now_latency;
+				if (flags == 7 || flags == 4) ctx->latency += 11025;
+				if (ctx->latency < MIN_LATENCY) ctx->latency = MIN_LATENCY;
+				else if (ctx->latency > MAX_LATENCY) ctx->latency = MAX_LATENCY;
+				ctx->synchro.rtp = rtp_now - ctx->latency;
+				ctx->synchro.time = ctx->timing.local + remote_gap;
+
+				// now we are synced on RTP frames
+				ctx->synchro.status |= RTP_SYNC;
+
+				// 1st sync packet received (signals a restart of playback)
+				if (packet[0] & 0x10) {
+					LOG_INFO("[%p]: 1st sync packet received", ctx);
+				}
+
+				pthread_mutex_unlock(&ctx->ab_mutex);
+
+				LOG_DEBUG("[%p]: sync packet latency:%d rtp_latency:%u rtp:%u remote ntp:%llx, local time:%u local rtp:%u (now:%u)",
+						  ctx, ctx->latency, rtp_now_latency, rtp_now, remote, ctx->synchro.time, ctx->synchro.rtp, gettime_ms());
+
+				if ((ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) ctx->cmd_cb(RAOP_TIMING);
+
+				break;
+			}
+
+			// NTP timing packet
+			case 0x53: {
+				u64_t expected;
+				u32_t reference   = ntohl(*(u32_t*)(pktp+12)); // only low 32 bits in our case
+				u64_t remote 	  =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
+				u32_t roundtrip   = gettime_ms() - reference;
+				
+				// better discard sync packets when roundtrip is suspicious and ask for another one
+				if (roundtrip > 100) {
+					rtp_request_timing(ctx);
+					LOG_WARN("[%p]: discarding NTP roundtrip of %u ms", ctx, roundtrip);
+					break;
+				}
+
+				/*
+				  The expected elapsed remote time should be exactly the same as
+				  elapsed local time between the two request, corrected by the
+				  drifting
+				*/
+				expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
+
+				ctx->timing.remote = remote;
+				ctx->timing.local = reference;
+
+				// now we are synced on NTP (mutex not needed)
+				ctx->synchro.status |= NTP_SYNC;
+
+				LOG_DEBUG("[%p]: Timing references local:%llu, remote:%llx (delta:%lld, sum:%lld, adjust:%lld, gaps:%d)",
+						  ctx, ctx->timing.local, ctx->timing.remote);
+
+				break;
+			}
+			
+			default: {
+				LOG_WARN("Unknown packet received %x", (int) type);
+				break;
+			}
+		}
+	}
+
+	free(packet);
+	LOG_INFO("[%p]: terminating", ctx);
+
+#ifndef WIN32
+	xTaskNotifyGive(ctx->joiner);
+	vTaskSuspend(NULL);
+#endif
+
+	return NULL;
+}
+
+/*---------------------------------------------------------------------------*/
+static bool rtp_request_timing(rtp_t *ctx) {
+	unsigned char req[32];
+	u32_t now = gettime_ms();
+	int i;
+	struct sockaddr_in host;
+
+	LOG_DEBUG("[%p]: timing request now:%u (port: %hu)", ctx, now, ctx->rtp_sockets[TIMING].rport);
+
+	req[0] = 0x80;
+	req[1] = 0x52|0x80;
+	*(u16_t*)(req+2) = htons(7);
+	*(u32_t*)(req+4) = htonl(0);  // dummy
+	for (i = 0; i < 16; i++) req[i+8] = 0;
+	*(u32_t*)(req+24) = 0;
+	*(u32_t*)(req+28) = htonl(now); // this is not a real NTP, but a 32 ms counter in the low part of the NTP
+
+	if (ctx->host.s_addr != INADDR_ANY) {
+		host.sin_family = AF_INET;
+		host.sin_addr =	ctx->host;
+	} else host = ctx->rtp_host;
+
+	// no address from sender, need to wait for 1st packet to be received
+	if (host.sin_addr.s_addr == INADDR_ANY) return false;
+
+	host.sin_port = htons(ctx->rtp_sockets[TIMING].rport);
+
+	if (sizeof(req) != sendto(ctx->rtp_sockets[TIMING].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &host, sizeof(host))) {
+		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
+	}
+
+	return true;
+}
+
+/*---------------------------------------------------------------------------*/
+static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
+	unsigned char req[8];    // *not* a standard RTCP NACK
+
+	// do not request silly ranges (happens in case of network large blackouts)
+	if (seq_order(last, first) || last - first > BUFFER_FRAMES / 2) return false;
+	
+	ctx->resent_req += (seq_t) (last - first) + 1;
+
+	LOG_DEBUG("resend request [W:%hu R:%hu first=%hu last=%hu]", ctx->ab_write, ctx->ab_read, first, last);
+
+	req[0] = 0x80;
+	req[1] = 0x55|0x80;  // Apple 'resend'
+	*(u16_t*)(req+2) = htons(1);  // our seqnum
+	*(u16_t*)(req+4) = htons(first);  // missed seqnum
+	*(u16_t*)(req+6) = htons(last-first+1);  // count
+
+	ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
+
+	if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
+		LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
+	}
+
+	return true;
+}
+

+ 2 - 1
components/raop/util.c

@@ -588,7 +588,8 @@ void free_metadata(struct metadata_s *metadata)
 	NFREE(metadata->remote_title);
 }
 
-
/*----------------------------------------------------------------------------*/
+
+/*----------------------------------------------------------------------------*/
 
 int _fprintf(FILE *file, ...)
 {

+ 0 - 1
components/services/audio_controls.c

@@ -18,7 +18,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
-//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>

+ 1 - 0
components/services/component.mk

@@ -9,3 +9,4 @@
 
 COMPONENT_SRCDIRS := . 
 COMPONENT_ADD_INCLUDEDIRS := .
+CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG

+ 230 - 0
components/services/messaging.c

@@ -0,0 +1,230 @@
+	/**
+ *
+ */
+#include <stdlib.h> // Required for libtelnet.h
+#include <esp_log.h>
+#include "stdbool.h"
+#include <lwip/def.h>
+#include <lwip/sockets.h>
+#include <errno.h>
+#include <string.h>
+#include "esp_app_trace.h"
+#include "esp_attr.h"
+#include "config.h"
+#include "nvs_utilities.h"
+#include "platform_esp32.h"
+#include "messaging.h"
+#include "trace.h"
+/************************************
+ * Globals
+ */
+
+const static char tag[] = "messaging";
+typedef struct {
+	struct messaging_list_t * next;
+	char * subscriber_name;
+	size_t max_count;
+	RingbufHandle_t buf_handle;
+} messaging_list_t;
+static messaging_list_t top;
+#define MSG_LENGTH_AVG 1024
+
+messaging_list_t * get_struct_ptr(messaging_handle_t handle){
+	return (messaging_list_t *)handle;
+}
+messaging_handle_t  get_handle_ptr(messaging_list_t * handle){
+	return (messaging_handle_t )handle;
+}
+
+RingbufHandle_t messaging_create_ring_buffer(uint8_t max_count){
+	RingbufHandle_t buf_handle = NULL;
+	StaticRingbuffer_t *buffer_struct = malloc(sizeof(StaticRingbuffer_t));
+	if (buffer_struct != NULL) {
+		size_t buf_size = (size_t )(sizeof(single_message_t)+8+MSG_LENGTH_AVG)*(size_t )(max_count>0?max_count:5); // no-split buffer requires an additional 8 bytes
+		buf_size = buf_size - (buf_size % 4);
+		uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_32BIT);
+		if (buffer_storage== NULL) {
+			ESP_LOGE(tag,"buff alloc failed");
+		}
+		else {
+			buf_handle = xRingbufferCreateStatic(buf_size, RINGBUF_TYPE_NOSPLIT, buffer_storage, buffer_struct);
+		}
+	}
+	else {
+		ESP_LOGE(tag,"ringbuf alloc failed");
+	}
+	return buf_handle;
+}
+void messaging_fill_messages(messaging_list_t * target_subscriber){
+	single_message_t * message=NULL;
+    UBaseType_t uxItemsWaiting;
+
+    vRingbufferGetInfo(top.buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
+    for(size_t i=0;i<uxItemsWaiting;i++){
+    	message= messaging_retrieve_message(top.buf_handle);
+    	if(message){
+			//re-post to original queue so it is available to future subscribers
+			messaging_post_to_queue(get_handle_ptr(&top), message, message->msg_size);
+			// post to new subscriber
+			messaging_post_to_queue(get_handle_ptr(target_subscriber) , message, message->msg_size);
+			FREE_AND_NULL(message);
+    	}
+    }
+}
+messaging_handle_t messaging_register_subscriber(uint8_t max_count, char * name){
+	messaging_list_t * cur=&top;
+	while(cur->next){
+		cur = get_struct_ptr(cur->next);
+	}
+	cur->next=heap_caps_malloc(sizeof(messaging_list_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+	if(!cur->next){
+		ESP_LOGE(tag,"subscriber alloc failed");
+		return NULL;
+	}
+	memset(cur->next,0x00,sizeof(messaging_list_t));
+	cur = get_struct_ptr(cur->next);
+	cur->max_count=max_count;
+	cur->subscriber_name=strdup(name);
+	cur->buf_handle = messaging_create_ring_buffer(max_count);
+	if(cur->buf_handle){
+		messaging_fill_messages(cur);
+	}
+	return cur->buf_handle;
+}
+void messaging_service_init(){
+	size_t max_count=15;
+	top.buf_handle = messaging_create_ring_buffer(max_count);
+	if(!top.buf_handle){
+		ESP_LOGE(tag, "messaging service init failed.");
+	}
+	else {
+		top.max_count = max_count;
+		top.subscriber_name = strdup("messaging");
+	}
+	return;
+}
+
+const char * messaging_get_type_desc(messaging_types msg_type){
+	switch (msg_type) {
+	CASE_TO_STR(MESSAGING_INFO);
+	CASE_TO_STR(MESSAGING_WARNING);
+	CASE_TO_STR(MESSAGING_ERROR);
+		default:
+			return "Unknown";
+			break;
+	}
+}
+const char * messaging_get_class_desc(messaging_classes msg_class){
+	switch (msg_class) {
+	CASE_TO_STR(MESSAGING_CLASS_OTA);
+	CASE_TO_STR(MESSAGING_CLASS_SYSTEM);
+	CASE_TO_STR(MESSAGING_CLASS_STATS);
+		default:
+			return "Unknown";
+			break;
+	}
+}
+
+cJSON *  messaging_retrieve_messages(RingbufHandle_t buf_handle){
+	single_message_t * message=NULL;
+	cJSON * json_messages=cJSON_CreateArray();
+	cJSON * json_message=NULL;
+	size_t item_size;
+    UBaseType_t uxItemsWaiting;
+    vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
+	for(int i = 0;i<uxItemsWaiting;i++){
+		message = (single_message_t *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(50));
+		//Check received data
+		if (message== NULL) {
+			ESP_LOGE(tag,"received null ptr");
+		}
+		else {
+			json_message = cJSON_CreateObject();
+			cJSON_AddStringToObject(json_message, "message", message->message);
+			vRingbufferReturnItem(buf_handle, (void *)message);
+			cJSON_AddStringToObject(json_message, "type", messaging_get_type_desc(message->type));
+			cJSON_AddStringToObject(json_message, "class", messaging_get_class_desc(message->msg_class));
+			cJSON_AddNumberToObject(json_message,"sent_time",message->sent_time);
+			cJSON_AddNumberToObject(json_message,"current_time",esp_timer_get_time() / 1000);
+			cJSON_AddItemToArray(json_messages,json_message);
+		}
+	}
+	return json_messages;
+}
+single_message_t *  messaging_retrieve_message(RingbufHandle_t buf_handle){
+	single_message_t * message=NULL;
+	single_message_t * message_copy=NULL;
+	size_t item_size;
+    UBaseType_t uxItemsWaiting;
+    vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
+	if(uxItemsWaiting>0){
+		message = (single_message_t *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(50));
+		message_copy  = heap_caps_malloc(item_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+		if(message_copy){
+			memcpy(message_copy,message,item_size);
+		}
+		vRingbufferReturnItem(buf_handle, (void *)message);
+	}
+	return message_copy;
+}
+
+esp_err_t messaging_post_to_queue(messaging_handle_t subscriber_handle, single_message_t * message, size_t message_size){
+	size_t item_size=0;
+	messaging_list_t * subscriber=get_struct_ptr(subscriber_handle);
+	if(!subscriber->buf_handle){
+		ESP_LOGE(tag,"post failed: null buffer for %s", str_or_unknown(subscriber->subscriber_name));
+		return ESP_FAIL;
+	}
+	void * pItem=NULL;
+	UBaseType_t res=pdFALSE;
+	while(1){
+		ESP_LOGD(tag,"Attempting to reserve %d bytes for %s",message_size, str_or_unknown(subscriber->subscriber_name));
+		res =  xRingbufferSendAcquire(subscriber->buf_handle, &pItem, message_size, pdMS_TO_TICKS(50));
+		if(res == pdTRUE && pItem){
+			ESP_LOGD(tag,"Reserving complete for %s", str_or_unknown(subscriber->subscriber_name));
+			memcpy(pItem,message,message_size);
+			xRingbufferSendComplete(subscriber->buf_handle, pItem);
+			break;
+		}
+		ESP_LOGD(tag,"Dropping for %s",str_or_unknown(subscriber->subscriber_name));
+		single_message_t * dummy = (single_message_t *)xRingbufferReceive(subscriber->buf_handle, &item_size, pdMS_TO_TICKS(50));
+		if (dummy== NULL) {
+			ESP_LOGE(tag,"Dropping message failed");
+			break;
+		}
+		else {
+			ESP_LOGD(tag,"Dropping message of %d bytes for %s",item_size, str_or_unknown(subscriber->subscriber_name));
+			vRingbufferReturnItem(subscriber->buf_handle, (void *)dummy);
+		}
+	}
+	if (res != pdTRUE) {
+		ESP_LOGE(tag,"post to %s failed",str_or_unknown(subscriber->subscriber_name));
+		return ESP_FAIL;
+	}
+	return ESP_OK;
+}
+void messaging_post_message(messaging_types type,messaging_classes msg_class, char *fmt, ...){
+	single_message_t * message=NULL;
+	size_t msg_size=0;
+	size_t ln =0;
+	messaging_list_t * cur=&top;
+	va_list va;
+	va_start(va, fmt);
+	ln = vsnprintf(NULL, 0, fmt, va)+1;
+	msg_size = sizeof(single_message_t)+ln;
+	message = (single_message_t *)heap_caps_malloc(msg_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+	vsprintf(message->message, fmt, va);
+	va_end(va);
+	message->msg_size = msg_size;
+	message->type = type;
+	message->msg_class = msg_class;
+	message->sent_time = esp_timer_get_time() / 1000;
+	ESP_LOGD(tag,"Post: %s",message->message);
+	while(cur){
+		messaging_post_to_queue(get_handle_ptr(cur),  message, msg_size);
+		cur = get_struct_ptr(cur->next);
+	}
+	FREE_AND_NULL(message);
+	return;
+
+}

+ 32 - 0
components/services/messaging.h

@@ -0,0 +1,32 @@
+#include "sdkconfig.h"
+#include "freertos/ringbuf.h"
+#include "cJSON.h"
+#pragma once
+typedef enum {
+	MESSAGING_INFO,
+	MESSAGING_WARNING,
+	MESSAGING_ERROR
+} messaging_types;
+typedef enum {
+	MESSAGING_CLASS_OTA,
+	MESSAGING_CLASS_SYSTEM,
+	MESSAGING_CLASS_STATS
+} messaging_classes;
+
+typedef struct messaging_list_t *messaging_handle_t;
+
+typedef struct {
+	time_t sent_time;
+	messaging_types type;
+	messaging_classes msg_class;
+	size_t msg_size;
+	char message[];
+} single_message_t;
+
+cJSON *  messaging_retrieve_messages(RingbufHandle_t buf_handle);
+messaging_handle_t messaging_register_subscriber(uint8_t max_count, char * name);
+esp_err_t messaging_post_to_queue(messaging_handle_t subscriber_handle, single_message_t * message, size_t message_size);
+void messaging_post_message(messaging_types type,messaging_classes msg_class, char * fmt, ...);
+cJSON *  messaging_retrieve_messages(RingbufHandle_t buf_handle);
+single_message_t *  messaging_retrieve_message(RingbufHandle_t buf_handle);
+void messaging_service_init();

+ 39 - 4
components/services/monitor.c

@@ -21,6 +21,9 @@
 #include "globdefs.h"
 #include "platform_config.h"
 #include "accessors.h"
+#include "messaging.h"
+#include "cJSON.h"
+#include "trace.h"
 
 #define MONITOR_TIMER	(10*1000)
 #define SCRATCH_SIZE	256
@@ -44,16 +47,17 @@ bool spkfault_svc(void);
 /****************************************************************************************
  * 
  */
-static void task_stats( void ) {
+static void task_stats( cJSON* top ) {
 #ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY 
 	static struct {
 		TaskStatus_t *tasks;
 		uint32_t total, n;
 	} current, previous;
-	
+	cJSON * tlist=cJSON_CreateArray();
 	current.n = uxTaskGetNumberOfTasks();
 	current.tasks = malloc( current.n * sizeof( TaskStatus_t ) );
 	current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total );
+	cJSON_AddNumberToObject(top,"ntasks",current.n);
 	
 	static EXT_RAM_ATTR char scratch[SCRATCH_SIZE];
 	*scratch = '\0';
@@ -68,6 +72,15 @@ static void task_stats( void ) {
 																		   current.tasks[i].eCurrentState,
 																		   100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed, 
 																		   current.tasks[i].usStackHighWaterMark);
+				cJSON * t=cJSON_CreateObject();
+				cJSON_AddNumberToObject(t,"cpu",100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed);
+				cJSON_AddNumberToObject(t,"minstk",current.tasks[i].usStackHighWaterMark);
+				cJSON_AddNumberToObject(t,"bprio",current.tasks[i].uxBasePriority);
+				cJSON_AddNumberToObject(t,"cprio",current.tasks[i].uxCurrentPriority);
+				cJSON_AddStringToObject(t,"nme",current.tasks[i].pcTaskName);
+				cJSON_AddNumberToObject(t,"st",current.tasks[i].eCurrentState);
+				cJSON_AddNumberToObject(t,"num",current.tasks[i].xTaskNumber);
+				cJSON_AddItemToArray(tlist,t);
 				if (i % 3 == 2 || i == current.n - 1) {
 					ESP_LOGI(TAG, "%s", scratch);
 					n = 0;
@@ -79,13 +92,21 @@ static void task_stats( void ) {
 #else
 	for (int i = 0, n = 0; i < current.n; i ++) {
 		n += sprintf(scratch + n, "%16s s:%5u\t", current.tasks[i].pcTaskName, current.tasks[i].usStackHighWaterMark);
+		cJSON * t=cJSON_CreateObject();
+		cJSON_AddNumberToObject(t,"minstk",current.tasks[i].usStackHighWaterMark);
+		cJSON_AddNumberToObject(t,"bprio",current.tasks[i].uxBasePriority);
+		cJSON_AddNumberToObject(t,"cprio",current.tasks[i].uxCurrentPriority);
+		cJSON_AddStringToObject(t,"nme",current.tasks[i].pcTaskName);
+		cJSON_AddStringToObject(t,"st",current.tasks[i].eCurrentState);
+		cJSON_AddNumberToObject(t,"num",current.tasks[i].xTaskNumber);
+		cJSON_AddItemToArray(tlist,t);
 		if (i % 3 == 2 || i == current.n - 1) {
 			ESP_LOGI(TAG, "%s", scratch);
 			n = 0;
 		}	
 	}
 #endif	
-	
+	cJSON_AddItemToObject(top,"tasks",tlist);
 	if (previous.tasks) free(previous.tasks);
 	previous = current;
 #endif	
@@ -95,13 +116,26 @@ static void task_stats( void ) {
  * 
  */
 static void monitor_callback(TimerHandle_t xTimer) {
+	cJSON * top=cJSON_CreateObject();
+	cJSON_AddNumberToObject(top,"free_iram",heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
+	cJSON_AddNumberToObject(top,"min_free_iram",heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
+	cJSON_AddNumberToObject(top,"free_spiram",heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
+	cJSON_AddNumberToObject(top,"min_free_spiram",heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
+
 	ESP_LOGI(TAG, "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));
 			
-	task_stats();
+	task_stats(top);
+	char * top_a= cJSON_PrintUnformatted(top);
+	if(top_a){
+		messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a);
+		FREE_AND_NULL(top_a);
+	}
+	cJSON_free(top);
+
 }
 
 /****************************************************************************************
@@ -109,6 +143,7 @@ static void monitor_callback(TimerHandle_t xTimer) {
  */
 static void jack_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) {
 	ESP_LOGD(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed");
+	messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_SYSTEM,"jack is %s",BUTTON_PRESSED ? "inserted" : "removed");
 	if (jack_handler_svc) (*jack_handler_svc)(event == BUTTON_PRESSED);
 }
 

+ 2 - 0
components/services/services.c

@@ -16,6 +16,7 @@
 #include "monitor.h"
 #include "globdefs.h"
 #include "accessors.h"
+#include "messaging.h"
 
 extern void battery_svc_init(void);
 extern void monitor_svc_init(void);
@@ -52,6 +53,7 @@ void set_power_gpio(int gpio, char *value) {
  * 
  */
 void services_init(void) {
+	messaging_service_init();
 	gpio_install_isr_service(0);
 	
 #ifdef CONFIG_I2C_LOCKED

+ 1 - 4
components/squeezelite-ota/component.mk

@@ -5,9 +5,6 @@
 
 # todo: add support for https
 COMPONENT_ADD_INCLUDEDIRS := .
-COMPONENT_ADD_INCLUDEDIRS += include
-COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/
-COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/components/tools
 CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO -DCONFIG_OTA_ALLOW_HTTP=1
-#CFLAGS += -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCONFIG_OTA_ALLOW_HTTP=1
+
 

+ 413 - 220
components/squeezelite-ota/squeezelite-ota.c

@@ -6,9 +6,6 @@
    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    CONDITIONS OF ANY KIND, either express or implied.
 */
-#ifndef LOG_LOCAL_LEVEL
-#define LOG_LOCAL_LEVEL ESP_LOG_INFO
-#endif
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "esp_system.h"
@@ -23,46 +20,69 @@
 #include "esp_err.h"
 #include "tcpip_adapter.h"
 #include "squeezelite-ota.h"
-#include "platform_config.h"
-#include "platform_esp32.h"
+#include "config.h"
 #include <time.h>
 #include <sys/time.h>
 #include <stdarg.h>
 #include "esp_secure_boot.h"
 #include "esp_flash_encrypt.h"
 #include "esp_spi_flash.h"
-#include "platform_esp32.h"
 #include "sdkconfig.h"
-
+#include "messaging.h"
+#include "trace.h"
 #include "esp_ota_ops.h"
+#include "display.h"
+#include "gds.h"
+#include "gds_text.h"
+#include "gds_draw.h"
+
 extern const char * get_certificate();
 
+#ifdef CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1
+#define OTA_CORE 0
+#else
+#define OTA_CORE 1
+#endif
+
 static const char *TAG = "squeezelite-ota";
-char * ota_write_data = NULL;
 esp_http_client_handle_t ota_http_client = NULL;
 #define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1
 #define BUFFSIZE 4096
 #define HASH_LEN 32 /* SHA-256 digest length */
-
+typedef struct  {
+	char * url;
+	char * bin;
+	uint32_t length;
+} ota_thread_parms_t ;
+static ota_thread_parms_t ota_thread_parms;
+typedef enum  {
+	OTA_TYPE_HTTP,
+	OTA_TYPE_BUFFER,
+	OTA_TYPE_INVALID
+} ota_type_t;
 
 static struct {
-	char status_text[81];
-	uint32_t ota_actual_len;
-	uint32_t ota_total_len;
-	char * redirected_url;
-	char * current_url;
+	uint32_t actual_image_len;
+	uint32_t total_image_len;
+	uint32_t remain_image_len;
+	ota_type_t ota_type;
+	char * ota_write_data;
+	char * bin_data;
 	bool bOTAStarted;
-	bool bInitialized;
+	size_t buffer_size;
 	uint8_t lastpct;
 	uint8_t newpct;
 	struct timeval OTA_start;
 	bool bOTAThreadStarted;
-
+    const esp_partition_t *configured;
+    const esp_partition_t *running;
+    const esp_partition_t * update_partition;
+    const esp_partition_t* last_invalid_app ;
+    const esp_partition_t * ota_partition;
 } ota_status;
-struct timeval tv;
-static esp_http_client_config_t ota_config;
 
-extern void wifi_manager_refresh_ota_json();
+struct timeval tv;
+static esp_http_client_config_t http_client_config;
 
 void _printMemStats(){
 	ESP_LOGD(TAG,"Heap internal:%zu (min:%zu) external:%zu (min:%zu)",
@@ -71,35 +91,121 @@ void _printMemStats(){
 			heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
 			heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
 }
-void triggerStatusJsonRefresh(bool bDelay,const char * status, ...){
-    va_list args;
-    va_start(args, status);
-    vsnprintf(ota_status.status_text,sizeof(ota_status.status_text)-1,status, args);
-    va_end(args);
-    _printMemStats();
-	wifi_manager_refresh_ota_json();
-	if(bDelay){
-		ESP_LOGD(TAG,"Holding task...");
-	    vTaskDelay(200 / portTICK_PERIOD_MS);  // wait here for a short amount of time.  This will help with refreshing the UI status
-		ESP_LOGD(TAG,"Done holding task...");
+uint8_t  ota_get_pct_complete(){
+	return ota_status.total_image_len==0?0:
+			(uint8_t)((float)ota_status.actual_image_len/(float)ota_status.total_image_len*100.0f);
+}
+typedef struct  {
+	int x1,y1,x2,y2,width,height;
+} rect_t;
+typedef struct _progress {
+	int border_thickness;
+	int sides_margin;
+	int vertical_margin;
+	int bar_tot_height;
+	int bar_fill_height;
+	rect_t border;
+	rect_t filler;
+} progress_t;
+
+static progress_t * loc_displayer_get_progress_dft(){
+	int start_coord_offset=0;
+	static progress_t def={
+		.border_thickness = 2,
+		.sides_margin = 2,
+		.bar_tot_height = 7,
+		};
+	def.bar_fill_height= def.bar_tot_height-(def.border_thickness*2);
+	def.border.x1=start_coord_offset+def.sides_margin;
+	def.border.x2=GDS_GetWidth(display)-def.sides_margin;
+	// progress bar will be drawn at the bottom of the display
+	def.border.y2= GDS_GetHeight(display)-def.border_thickness;
+	def.border.y1= def.border.y2-def.bar_tot_height;
+	def.border.width=def.border.x2-def.border.x1;
+	def.border.height=def.border.y2-def.border.y1;
+	def.filler.x1= def.border.x1+def.border_thickness;
+	def.filler.x2= def.border.x2-def.border_thickness;
+	def.filler.y1= def.border.y1+def.border_thickness;
+	def.filler.y2= def.border.y2-def.border_thickness;
+	def.filler.width=def.filler.x2-def.filler.x1;
+	def.filler.height=def.filler.y2-def.filler.y1;
+	assert(def.filler.width>0);
+	assert(def.filler.height>0);
+	assert(def.border.width>0);
+	assert(def.border.height>0);
+	assert(def.border.width>def.filler.width);
+	assert(def.border.height>def.filler.height);
+	return &def;
+
+}
+static void loc_displayer_progressbar(uint8_t pct){
+	static progress_t * progress_coordinates;
+	if(!progress_coordinates) progress_coordinates = loc_displayer_get_progress_dft();
+	int filler_x=progress_coordinates->filler.x1+(int)((float)progress_coordinates->filler.width*(float)pct/(float)100);
+
+	ESP_LOGD(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2);
+	GDS_DrawBox(display,progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2,GDS_COLOR_WHITE,false);
+	ESP_LOGD(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2);
+	if(filler_x > progress_coordinates->filler.x1){
+		GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2,GDS_COLOR_WHITE,true);
 	}
 	else {
-		ESP_LOGI(TAG,"%s",ota_status.status_text);
-		taskYIELD();
+		// Clear the inner box
+		GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,progress_coordinates->filler.x2,progress_coordinates->filler.y2,GDS_COLOR_BLACK,true);
 	}
+	ESP_LOGD(TAG,"Updating Display");
+	GDS_Update(display);
 }
-const char *  ota_get_status(){
-	if(!ota_status.bInitialized)
-		{
-			memset(ota_status.status_text, 0x00,sizeof(ota_status.status_text));
-			ota_status.bInitialized = true;
-		}
-	return ota_status.status_text;
-}
-uint8_t  ota_get_pct_complete(){
-	return ota_status.ota_total_len==0?0:
-			(uint8_t)((float)ota_status.ota_actual_len/(float)ota_status.ota_total_len*100.0f);
+void sendMessaging(messaging_types type,const char * fmt, ...){
+    va_list args;
+    cJSON * msg = cJSON_CreateObject();
+    size_t str_len=0;
+    char * msg_str=NULL;
+
+    va_start(args, fmt);
+    str_len = vsnprintf(NULL,0,fmt,args)+1;
+    if(str_len>0){
+    	msg_str = malloc(str_len);
+    	vsnprintf(msg_str,str_len,fmt,args);
+        if(type == MESSAGING_WARNING){
+        	ESP_LOGW(TAG,"%s",msg_str);
+        }
+    	else if (type == MESSAGING_ERROR){
+    		ESP_LOGE(TAG,"%s",msg_str);
+    	}
+    	else
+    		ESP_LOGI(TAG,"%s",msg_str);
+    }
+    else {
+    	ESP_LOGW(TAG, "Sending empty string message");
+    }
+    va_end(args);
+    if(type!=MESSAGING_INFO){
+    	GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, msg_str);
+    }
+
+    cJSON_AddStringToObject(msg,"ota_dsc",str_or_unknown(msg_str));
+    free(msg_str);
+    cJSON_AddNumberToObject(msg,"ota_pct",	ota_get_pct_complete()	);
+    char * json_msg = cJSON_PrintUnformatted(msg);
+	messaging_post_message(type, MESSAGING_CLASS_OTA, json_msg);
+	free(json_msg);
+	cJSON_free(msg);
+    _printMemStats();
 }
+//esp_err_t decode_alloc_ota_message(single_message_t * message, char * ota_dsc, uint8_t * ota_pct ){
+//	if(!message || !message->message) return ESP_ERR_INVALID_ARG;
+//	cJSON * json = cJSON_Parse(message->message);
+//	if(!json) return ESP_FAIL;
+//	if(ota_dsc) {
+//		ota_dsc = strdup(cJSON_GetObjectItem(json, "ota_dsc")?cJSON_GetStringValue(cJSON_GetObjectItem(json, "ota_dsc")):"");
+//	}
+//	if(ota_pct){
+//		*ota_pct = cJSON_GetObjectItem(json, "ota_pct")?cJSON_GetObjectItem(json, "ota_pct")->valueint:0;
+//	}
+//	cJSON_free(json);
+//	return ESP_OK;
+//}
 
 static void __attribute__((noreturn)) task_fatal_error(void)
 {
@@ -110,7 +216,7 @@ static void __attribute__((noreturn)) task_fatal_error(void)
         ;
     }
 }
-#define FREE_RESET(p) if(p!=NULL) { free(p); p=NULL; }
+
 esp_err_t _http_event_handler(esp_http_client_event_t *evt)
 {
 // --------------
@@ -138,10 +244,11 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt)
     case HTTP_EVENT_ON_CONNECTED:
         ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
 
-        if(ota_status.bOTAStarted) triggerStatusJsonRefresh(true,"Installing...");
-        ota_status.ota_total_len=0;
-		ota_status.ota_actual_len=0;
+        if(ota_status.bOTAStarted) sendMessaging(MESSAGING_INFO,"Connecting to URL...");
+        ota_status.total_image_len=0;
+		ota_status.actual_image_len=0;
 		ota_status.lastpct=0;
+		ota_status.remain_image_len=0;
 		ota_status.newpct=0;
 		gettimeofday(&ota_status.OTA_start, NULL);
 
@@ -152,13 +259,11 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt)
     case HTTP_EVENT_ON_HEADER:
         ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s",evt->header_key, evt->header_value);
 		if (strcasecmp(evt->header_key, "location") == 0) {
-			FREE_RESET(ota_status.redirected_url);
-        	ota_status.redirected_url=strdup(evt->header_value);
-        	ESP_LOGW(TAG,"OTA will redirect to url: %s",ota_status.redirected_url);
+        	ESP_LOGW(TAG,"OTA will redirect to url: %s",evt->header_value);
         }
         if (strcasecmp(evt->header_key, "content-length") == 0) {
-        	ota_status.ota_total_len = atol(evt->header_value);
-        	 ESP_LOGW(TAG, "Content length found: %s, parsed to %d", evt->header_value, ota_status.ota_total_len);
+        	ota_status.total_image_len = atol(evt->header_value);
+        	 ESP_LOGW(TAG, "Content length found: %s, parsed to %d", evt->header_value, ota_status.total_image_len);
         }
         break;
     case HTTP_EVENT_ON_DATA:
@@ -176,29 +281,49 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt)
     return ESP_OK;
 }
 
-esp_err_t init_config(char * url){
-	memset(&ota_config, 0x00, sizeof(ota_config));
-	ota_status.bInitialized = true;
-	triggerStatusJsonRefresh(true,"Initializing...");
-	if(url==NULL || strlen(url)==0){
-		ESP_LOGE(TAG,"HTTP OTA called without a url");
-		return ESP_FAIL;
+esp_err_t init_config(ota_thread_parms_t * p_ota_thread_parms){
+	memset(&http_client_config, 0x00, sizeof(http_client_config));
+	sendMessaging(MESSAGING_INFO,"Initializing...");
+	loc_displayer_progressbar(0);
+	ota_status.ota_type= OTA_TYPE_INVALID;
+	if(p_ota_thread_parms->url !=NULL && strlen(p_ota_thread_parms->url)>0 ){
+		ota_status.ota_type= OTA_TYPE_HTTP;
 	}
-	ota_status.current_url= url;
-	ota_config.cert_pem =get_certificate();
-	ota_config.event_handler = _http_event_handler;
-	ota_config.buffer_size = BUFFSIZE;
-	//ota_config.disable_auto_redirect=true;
-	ota_config.disable_auto_redirect=false;
-	ota_config.skip_cert_common_name_check = false;
-	ota_config.url = strdup(url);
-	ota_config.max_redirection_count = 3;
-	ota_write_data = heap_caps_malloc(ota_config.buffer_size+1 , MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
-	//ota_write_data = malloc(ota_config.buffer_size+1);
-	if(ota_write_data== NULL){
-		ESP_LOGE(TAG,"Error allocating the ota buffer");
-		return ESP_ERR_NO_MEM;
+	else if(p_ota_thread_parms->bin!=NULL && p_ota_thread_parms->length > 0) {
+		ota_status.ota_type= OTA_TYPE_BUFFER;
 	}
+
+	if(  ota_status.ota_type== OTA_TYPE_INVALID ){
+		ESP_LOGE(TAG,"HTTP OTA called without a url or a binary buffer");
+		return ESP_ERR_INVALID_ARG;
+	}
+
+	ota_status.buffer_size = BUFFSIZE;
+	ota_status.ota_write_data = heap_caps_malloc(ota_status.buffer_size+1 , (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
+	if(ota_status.ota_write_data== NULL){
+			ESP_LOGE(TAG,"Error allocating the ota buffer");
+			return ESP_ERR_NO_MEM;
+		}
+	switch (ota_status.ota_type) {
+	case OTA_TYPE_HTTP:
+		http_client_config.cert_pem =get_certificate();
+		http_client_config.event_handler = _http_event_handler;
+		http_client_config.disable_auto_redirect=true;
+		http_client_config.skip_cert_common_name_check = false;
+		http_client_config.url = strdup(p_ota_thread_parms->url);
+		http_client_config.max_redirection_count = 3;
+		// buffer size below is for http read chunks
+		http_client_config.buffer_size = 1024 ;
+		break;
+	case OTA_TYPE_BUFFER:
+		ota_status.bin_data = p_ota_thread_parms->bin;
+		ota_status.total_image_len = p_ota_thread_parms->length;
+		break;
+	default:
+		return ESP_FAIL;
+		break;
+	}
+
 	return ESP_OK;
 }
 esp_partition_t * _get_ota_partition(esp_partition_subtype_t subtype){
@@ -225,7 +350,7 @@ esp_partition_t * _get_ota_partition(esp_partition_subtype_t subtype){
 
 
 
-esp_err_t _erase_last_boot_app_partition(esp_partition_t *ota_partition)
+esp_err_t _erase_last_boot_app_partition(const esp_partition_t *ota_partition)
 {
 	uint16_t num_passes=0;
 	uint16_t remain_size=0;
@@ -257,18 +382,20 @@ esp_err_t _erase_last_boot_app_partition(esp_partition_t *ota_partition)
 		ESP_LOGD(TAG,"Pass %d of %d, with chunks of %d bytes, from %d to %d", i+1, num_passes,single_pass_size,i*single_pass_size,i*single_pass_size+single_pass_size);
 		err=esp_partition_erase_range(ota_partition, i*single_pass_size, single_pass_size);
 		if(err!=ESP_OK) return err;
-//		triggerStatusJsonRefresh(i%10==0?true:false,"Erasing flash (%u/%u)",i,num_passes);
 		if(i%2) {
-			triggerStatusJsonRefresh(false,"Erasing flash (%u/%u)",i,num_passes);
+			loc_displayer_progressbar((int)(((float)i/(float)num_passes)*100.0f));
+			sendMessaging(MESSAGING_INFO,"Erasing flash (%u/%u)",i,num_passes);
 		}
-		vTaskDelay(200/ portTICK_PERIOD_MS);  // wait here for a short amount of time.  This will help with reducing WDT errors
+		vTaskDelay(100/ portTICK_PERIOD_MS);  // wait here for a short amount of time.  This will help with reducing WDT errors
 	}
 	if(remain_size>0){
 		err=esp_partition_erase_range(ota_partition, ota_partition->size-remain_size, remain_size);
+
 		if(err!=ESP_OK) return err;
 	}
-	triggerStatusJsonRefresh(true,"Erasing flash complete.");
-	taskYIELD();
+	sendMessaging(MESSAGING_INFO,"Erasing flash complete.");
+	loc_displayer_progressbar(100);
+	vTaskDelay(200/ portTICK_PERIOD_MS);
 	return ESP_OK;
 }
 
@@ -287,28 +414,32 @@ static bool process_again(int status_code)
 static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client, int status_code)
 {
     esp_err_t err=ESP_OK;
-    if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found) {
+    if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found ) {
     	ESP_LOGW(TAG, "Handling HTTP redirection. ");
         err = esp_http_client_set_redirection(http_client);
         if (err != ESP_OK) {
             ESP_LOGE(TAG, "URL redirection Failed. %s", esp_err_to_name(err));
             return err;
         }
+        ESP_LOGW(TAG, "Done Handling HTTP redirection. ");
+
     } else if (status_code == HttpStatus_Unauthorized) {
     	ESP_LOGW(TAG, "Handling Unauthorized. ");
         esp_http_client_add_auth(http_client);
     }
     ESP_LOGD(TAG, "Redirection done, checking if we need to read the data. ");
     if (process_again(status_code)) {
-    	char * local_buff = heap_caps_malloc(ota_config.buffer_size+1, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
-    	//char * local_buff = malloc(ota_config.buffer_size+1);
-    	if(local_buff==NULL){
-    		ESP_LOGE(TAG,"Failed to allocate internal memory buffer for http processing");
-    		return ESP_ERR_NO_MEM;
-    	}
+    	//ESP_LOGD(TAG, "We have to read some more data. Allocating buffer size %u",ota_config.buffer_size+1);
+    	//char * local_buff = heap_caps_malloc(ota_status.buffer_size+1, (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
+
+//    	if(local_buff==NULL){
+//    		ESP_LOGE(TAG,"Failed to allocate internal memory buffer for http processing");
+//    		return ESP_ERR_NO_MEM;
+//    	}
+
         while (1) {
         	ESP_LOGD(TAG, "Reading data chunk. ");
-            int data_read = esp_http_client_read(http_client, local_buff, ota_config.buffer_size);
+            int data_read = esp_http_client_read(http_client, ota_status.ota_write_data, ota_status.buffer_size);
             if (data_read < 0) {
                 ESP_LOGE(TAG, "Error: SSL data read error");
                 err= ESP_FAIL;
@@ -319,7 +450,7 @@ static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client
             	break;
             }
         }
-        FREE_RESET(local_buff);
+        //FREE_RESET(local_buff);
     }
 
     return err;
@@ -329,44 +460,50 @@ static esp_err_t _http_connect(esp_http_client_handle_t http_client)
     esp_err_t err = ESP_FAIL;
     int status_code, header_ret;
     do {
-    	ESP_LOGD(TAG, "connecting the http client. ");
+    	ESP_LOGI(TAG, "connecting the http client. ");
         err = esp_http_client_open(http_client, 0);
         if (err != ESP_OK) {
             ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
+            sendMessaging(MESSAGING_ERROR,"Failed to open HTTP connection: %s", esp_err_to_name(err));
             return err;
         }
-        ESP_LOGD(TAG, "Fetching headers");
+        ESP_LOGI(TAG, "Fetching headers");
         header_ret = esp_http_client_fetch_headers(http_client);
         if (header_ret < 0) {
         	// Error found
+            sendMessaging(MESSAGING_ERROR,"Header fetch failed");
             return header_ret;
         }
-        ESP_LOGD(TAG, "HTTP Header fetch completed, found content length of %d",header_ret);
+        ESP_LOGI(TAG, "HTTP Header fetch completed, found content length of %d",header_ret);
         status_code = esp_http_client_get_status_code(http_client);
         ESP_LOGD(TAG, "HTTP status code was %d",status_code);
 
-
-
         err = _http_handle_response_code(http_client, status_code);
         if (err != ESP_OK) {
+            sendMessaging(MESSAGING_ERROR,"HTTP connect error: %s", esp_err_to_name(err));
             return err;
         }
+
     } while (process_again(status_code));
+
+    if(status_code >=400 && status_code <=900){
+    	sendMessaging(MESSAGING_ERROR,"Error: HTTP Status %d",status_code);
+    	err=ESP_FAIL;
+    }
+
     return err;
 }
 void ota_task_cleanup(const char * message, ...){
 	ota_status.bOTAThreadStarted=false;
+	loc_displayer_progressbar(0);
 	if(message!=NULL){
-
 	    va_list args;
 	    va_start(args, message);
-		triggerStatusJsonRefresh(true,message, args);
+		sendMessaging(MESSAGING_ERROR,message, args);
 	    va_end(args);
-	    ESP_LOGE(TAG, "%s",ota_status.status_text);
 	}
-	FREE_RESET(ota_status.redirected_url);
-	FREE_RESET(ota_status.current_url);
-	FREE_RESET(ota_write_data);
+	FREE_RESET(ota_status.ota_write_data);
+	FREE_RESET(ota_status.bin_data);
 	if(ota_http_client!=NULL) {
 		esp_http_client_cleanup(ota_http_client);
 		ota_http_client=NULL;
@@ -374,24 +511,120 @@ void ota_task_cleanup(const char * message, ...){
 	ota_status.bOTAStarted = false;
 	task_fatal_error();
 }
+esp_err_t ota_buffer_all(){
+	int data_read=0;
+	esp_err_t err=ESP_OK;
+	if (ota_status.ota_type == OTA_TYPE_HTTP){
+		GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Downloading file");
+	    ota_http_client = esp_http_client_init(&http_client_config);
+	    if (ota_http_client == NULL) {
+	    	sendMessaging(MESSAGING_ERROR,"Error: Failed to initialize HTTP connection.");
+	        return ESP_FAIL;
+	    }
+	    _printMemStats();
+	    // Open the http connection and follow any redirection
+	    err = _http_connect(ota_http_client);
+	    if (err != ESP_OK) {
+	       return err;
+	    }
+	    if(ota_status.total_image_len<=0){
+	    	sendMessaging(MESSAGING_ERROR,"Error: Invalid image length");
+	    	return ESP_FAIL;
+	    }
+	    ota_status.bin_data= malloc(ota_status.total_image_len);
+	    if(ota_status.bin_data==NULL){
+			sendMessaging(MESSAGING_ERROR,"Error: buffer alloc error");
+			return ESP_FAIL;
+   	    }
+		data_read = esp_http_client_read(ota_http_client, ota_status.bin_data, ota_status.total_image_len);
+		if(data_read != ota_status.total_image_len){
+			sendMessaging(MESSAGING_ERROR,"Error: Binary incomplete");
+			return ESP_FAIL;
+		}
+	}
+	else {
+		gettimeofday(&ota_status.OTA_start, NULL);
+	}
+	ota_status.remain_image_len=ota_status.total_image_len;
+
+	return err;
+}
+int ota_buffer_read(){
+	int data_read=0;
+	if(ota_status.remain_image_len >ota_status.buffer_size){
+		data_read = ota_status.buffer_size;
+	} else {
+		data_read = ota_status.remain_image_len;
+	}
+	memcpy(ota_status.ota_write_data, &ota_status.bin_data[ota_status.actual_image_len], data_read);
+
+	ota_status.actual_image_len += data_read;
+	ota_status.remain_image_len -= data_read;
+	return data_read;
+}
+esp_err_t ota_header_check(){
+	esp_app_desc_t new_app_info;
+    esp_app_desc_t running_app_info;
+
+    ota_status.configured = esp_ota_get_boot_partition();
+    ota_status.running = esp_ota_get_running_partition();
+    ota_status.last_invalid_app= esp_ota_get_last_invalid_partition();
+    ota_status.ota_partition = _get_ota_partition(ESP_PARTITION_SUBTYPE_APP_OTA_0);
+
+    ESP_LOGI(TAG, "Running partition [%s] type %d subtype %d (offset 0x%08x)", ota_status.running->label, ota_status.running->type, ota_status.running->subtype, ota_status.running->address);
+    if (ota_status.total_image_len > ota_status.ota_partition->size){
+    	ota_task_cleanup("Error: Image size too large to fit in partition.");
+        return ESP_FAIL;
+	}
+	if(ota_status.ota_partition == NULL){
+		ESP_LOGE(TAG,"Unable to locate OTA application partition. ");
+        ota_task_cleanup("Error: OTA partition not found");
+        return ESP_FAIL;
+	}
+    if (ota_status.configured != ota_status.running) {
+        ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", ota_status.configured->address, ota_status.running->address);
+        ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)");
+    }
+    ESP_LOGI(TAG, "Next ota update partition is: [%s] subtype %d at offset 0x%x",
+    		ota_status.update_partition->label, ota_status.update_partition->subtype, ota_status.update_partition->address);
+
+    if (ota_status.total_image_len >= IMAGE_HEADER_SIZE) {
+		// check current version with downloading
+		memcpy(&new_app_info, &ota_status.bin_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
+		ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
+		if (esp_ota_get_partition_description(ota_status.running, &running_app_info) == ESP_OK) {
+			ESP_LOGI(TAG, "Running recovery version: %s", running_app_info.version);
+		}
+
+		esp_app_desc_t invalid_app_info;
+		if (esp_ota_get_partition_description(ota_status.last_invalid_app, &invalid_app_info) == ESP_OK) {
+			ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
+		}
+
+		if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
+			ESP_LOGW(TAG, "Current running version is the same as a new.");
+		}
+		return ESP_OK;
+    }
+    else{
+    	ota_task_cleanup("Error: Binary file too small");
+    }
+	 return ESP_FAIL;
+}
+
 void ota_task(void *pvParameter)
 {
 	esp_err_t err = ESP_OK;
-	size_t buffer_size = BUFFSIZE;
+    int data_read = 0;
+	GDS_TextSetFont(display,2,GDS_GetHeight(display)>32?&Font_droid_sans_fallback_15x17:&Font_droid_sans_fallback_11x13,-2);
+	GDS_ClearExt(display, true);
+	GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Firmware update");
+	GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Initializing");
+	loc_displayer_progressbar(0);
 	ESP_LOGD(TAG, "HTTP ota Thread started");
-    const esp_partition_t *configured = esp_ota_get_boot_partition();
-    const esp_partition_t *running = esp_ota_get_running_partition();
-    const esp_partition_t * update_partition = esp_ota_get_next_update_partition(NULL);
-    ESP_LOGI(TAG, "esp_ota_get_next_update_partition returned : partition [%s] subtype %d at offset 0x%x",
-    			update_partition->label, update_partition->subtype, update_partition->address);
-
-    if (configured != running) {
-        ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", configured->address, running->address);
-        ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)");
-    }
-    ESP_LOGI(TAG, "Running partition [%s] type %d subtype %d (offset 0x%08x)", running->label, running->type, running->subtype, running->address);
     _printMemStats();
 
+    ota_status.update_partition = esp_ota_get_next_update_partition(NULL);
 
 	ESP_LOGI(TAG,"Initializing OTA configuration");
 	err = init_config(pvParameter);
@@ -400,139 +633,96 @@ void ota_task(void *pvParameter)
 		return;
 	}
 
+	_printMemStats();
+	ota_status.bOTAStarted = true;
+	sendMessaging(MESSAGING_INFO,"Starting OTA...");
+	err=ota_buffer_all();
+	if(err!=ESP_OK){
+		ota_task_cleanup(NULL);
+		return;
+	}
+
+
+	if(ota_header_check()!=ESP_OK){
+		ota_task_cleanup(NULL);
+		return;
+	}
+
 	/* Locate and erase ota application partition */
 	ESP_LOGW(TAG,"****************  Expecting WATCHDOG errors below during flash erase. This is OK and not to worry about **************** ");
-	triggerStatusJsonRefresh(true,"Erasing OTA partition");
-	esp_partition_t *ota_partition = _get_ota_partition(ESP_PARTITION_SUBTYPE_APP_OTA_0);
-	if(ota_partition == NULL){
-		ESP_LOGE(TAG,"Unable to locate OTA application partition. ");
-        ota_task_cleanup("Error: OTA application partition not found. (%s)",esp_err_to_name(err));
-        return;
-	}
+	GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Formatting partition");
+	sendMessaging(MESSAGING_INFO,"Formatting OTA partition");
 	_printMemStats();
-	err=_erase_last_boot_app_partition(ota_partition);
+	err=_erase_last_boot_app_partition(ota_status.ota_partition);
 	if(err!=ESP_OK){
 		ota_task_cleanup("Error: Unable to erase last APP partition. (%s)",esp_err_to_name(err));
 		return;
 	}
-
+	loc_displayer_progressbar(0);
 	_printMemStats();
-	ota_status.bOTAStarted = true;
-	triggerStatusJsonRefresh(true,"Starting OTA...");
-    ota_http_client = esp_http_client_init(&ota_config);
-    if (ota_http_client == NULL) {
-        ota_task_cleanup("Error: Failed to initialize HTTP connection.");
-        return;
-    }
-    _printMemStats();
-    // Open the http connection and follow any redirection
-    err = _http_connect(ota_http_client);
-    if (err != ESP_OK) {
-       ota_task_cleanup("Error: HTTP Start read failed. (%s)",esp_err_to_name(err));
-       return;
-    }
 
-    _printMemStats();
 
+	// Call OTA Begin with a small partition size - this minimizes the time spent in erasing partition,
+	// which was already done above
     esp_ota_handle_t update_handle = 0 ;
-    int binary_file_length = 0;
+	err = esp_ota_begin(ota_status.ota_partition, 512, &update_handle);
+	if (err != ESP_OK) {
+		ota_task_cleanup("esp_ota_begin failed (%s)", esp_err_to_name(err));
+		return;
+	}
+	ESP_LOGD(TAG, "esp_ota_begin succeeded");
+	GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Writing image...");
+    while (ota_status.remain_image_len>0) {
 
-    /*deal with all receive packet*/
-    bool image_header_was_checked = false;
-    while (1) {
-        int data_read = esp_http_client_read(ota_http_client, ota_write_data, buffer_size);
-        if (data_read < 0) {
+    	data_read = ota_buffer_read();
+        if (data_read <= 0) {
             ota_task_cleanup("Error: Data read error");
             return;
         } else if (data_read > 0) {
-        	if (image_header_was_checked == false) {
-                esp_app_desc_t new_app_info;
-                if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
-                    // check current version with downloading
-                    memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
-                    ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
-
-                    esp_app_desc_t running_app_info;
-                    if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
-                        ESP_LOGI(TAG, "Running recovery version: %s", running_app_info.version);
-                    }
-
-                    const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition();
-                    esp_app_desc_t invalid_app_info;
-                    if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
-                        ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
-                    }
-
-                    // check current version with last invalid partition
-//                    if (last_invalid_app != NULL) {
-//                        if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
-//                            ESP_LOGW(TAG, "New version is the same as invalid version.");
-//                            ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
-//                            ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
-//                    		  ota_task_cleanup("esp_ota_begin failed (%s)", esp_err_to_name(err));
-//                        }
-//                    }
-
-                    if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
-                        ESP_LOGW(TAG, "Current running version is the same as a new.");
-                    }
-
-                    image_header_was_checked = true;
-
-                    // Call OTA Begin with a small partition size - this drives the erase operation which was already done;
-                    err = esp_ota_begin(ota_partition, 512, &update_handle);
-                    if (err != ESP_OK) {
-                        ota_task_cleanup("esp_ota_begin failed (%s)", esp_err_to_name(err));
-                        return;
-                    }
-					ESP_LOGD(TAG, "esp_ota_begin succeeded");
-                } else {
-                    ota_task_cleanup("Error: Binary file too large for the current partition");
-                    return;
-                }
-            }
-            err = esp_ota_write( update_handle, (const void *)ota_write_data, data_read);
+            err = esp_ota_write( update_handle, (const void *)ota_status.ota_write_data, data_read);
             if (err != ESP_OK) {
                 ota_task_cleanup("Error: OTA Partition write failure. (%s)",esp_err_to_name(err));
                 return;
             }
-            binary_file_length += data_read;
-            ESP_LOGD(TAG, "Written image length %d", binary_file_length);
-			ota_status.ota_actual_len=binary_file_length;
+            ESP_LOGD(TAG, "Written image length %d", ota_status.actual_image_len);
+
 			if(ota_get_pct_complete()%5 == 0) ota_status.newpct = ota_get_pct_complete();
 			if(ota_status.lastpct!=ota_status.newpct ) {
+				loc_displayer_progressbar(ota_status.newpct);
 				gettimeofday(&tv, NULL);
 				uint32_t elapsed_ms= (tv.tv_sec-ota_status.OTA_start.tv_sec )*1000+(tv.tv_usec-ota_status.OTA_start.tv_usec)/1000;
-				ESP_LOGI(TAG,"OTA progress : %d/%d (%d pct), %d KB/s", ota_status.ota_actual_len, ota_status.ota_total_len, ota_status.newpct, elapsed_ms>0?ota_status.ota_actual_len*1000/elapsed_ms/1024:0);
-				triggerStatusJsonRefresh(true,"Downloading & writing update.");
+				ESP_LOGI(TAG,"OTA progress : %d/%d (%d pct), %d KB/s", ota_status.actual_image_len, ota_status.total_image_len, ota_status.newpct, elapsed_ms>0?ota_status.actual_image_len*1000/elapsed_ms/1024:0);
+				sendMessaging(MESSAGING_INFO,"Writing binary file.");
 				ota_status.lastpct=ota_status.newpct;
 			}
 			taskYIELD();
 
         } else if (data_read == 0) {
-            ESP_LOGI(TAG, "Connection closed");
+            ESP_LOGI(TAG, "End of OTA data stream");
             break;
         }
     }
 
-    ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length);
-    if (ota_status.ota_total_len != binary_file_length) {
+    ESP_LOGI(TAG, "Total Write binary data length: %d", ota_status.actual_image_len);
+    if (ota_status.total_image_len != ota_status.actual_image_len) {
         ota_task_cleanup("Error: Error in receiving complete file");
         return;
     }
     _printMemStats();
-
+    loc_displayer_progressbar(100);
     err = esp_ota_end(update_handle);
     if (err != ESP_OK) {
         ota_task_cleanup("Error: %s",esp_err_to_name(err));
         return;
      }
     _printMemStats();
-    err = esp_ota_set_boot_partition(ota_partition);
+    err = esp_ota_set_boot_partition(ota_status.ota_partition);
     if (err == ESP_OK) {
     	ESP_LOGI(TAG,"OTA Process completed successfully!");
-    	triggerStatusJsonRefresh(true,"Success!");
+    	sendMessaging(MESSAGING_INFO,"Success!");
+    	GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Success!");
     	vTaskDelay(1500/ portTICK_PERIOD_MS);  // wait here to give the UI a chance to refresh
+    	GDS_Clear(display,GDS_COLOR_BLACK);
         esp_restart();
     } else {
         ota_task_cleanup("Error: Unable to update boot partition [%s]",esp_err_to_name(err));
@@ -542,7 +732,7 @@ void ota_task(void *pvParameter)
     return;
 }
 
-esp_err_t process_recovery_ota(const char * bin_url){
+esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length){
 	int ret = 0;
 	uint16_t stack_size, task_priority;
     if(ota_status.bOTAThreadStarted){
@@ -551,23 +741,21 @@ esp_err_t process_recovery_ota(const char * bin_url){
 	}
 	memset(&ota_status, 0x00, sizeof(ota_status));
 	ota_status.bOTAThreadStarted=true;
-    char * urlPtr=strdup(bin_url);
-	// the first thing we need to do here is to erase the firmware url
-	// to avoid a boot loop
 
-#ifdef CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1
-#define OTA_CORE 0
-#warning "OTA will run on core 0"
-#else
-#pragma message "OTA will run on core 1"
-#define OTA_CORE 1
-#endif
-    ESP_LOGI(TAG, "Starting ota on core %u for : %s", OTA_CORE,urlPtr);
+	if(bin_url){
+		ota_thread_parms.url =strdup(bin_url);
+		ESP_LOGI(TAG, "Starting ota on core %u for : %s", OTA_CORE,ota_thread_parms.url);
+	}
+	else {
+		ota_thread_parms.bin = bin_buffer;
+		ota_thread_parms.length = length;
+		ESP_LOGI(TAG, "Starting ota on core %u for file upload", OTA_CORE);
+	}
+
     char * num_buffer=config_alloc_get(NVS_TYPE_STR, "ota_stack");
   	if(num_buffer!=NULL) {
   		stack_size= atol(num_buffer);
-  		free(num_buffer);
-  		num_buffer=NULL;
+  		FREE_AND_NULL(num_buffer);
   	}
   	else {
 		ESP_LOGW(TAG,"OTA stack size config not found");
@@ -576,16 +764,15 @@ esp_err_t process_recovery_ota(const char * bin_url){
   	num_buffer=config_alloc_get(NVS_TYPE_STR, "ota_prio");
 	if(num_buffer!=NULL) {
 		task_priority= atol(num_buffer);
-		free(num_buffer);
-		num_buffer=NULL;
+		FREE_AND_NULL(num_buffer);
 	}
 	else {
 		ESP_LOGW(TAG,"OTA task priority not found");
 		task_priority= OTA_TASK_PRIOTITY;
   	}
+
   	ESP_LOGD(TAG,"OTA task stack size %d, priority %d (%d %s ESP_TASK_MAIN_PRIO)",stack_size , task_priority, abs(task_priority-ESP_TASK_MAIN_PRIO), task_priority-ESP_TASK_MAIN_PRIO>0?"above":"below");
-    ret=xTaskCreatePinnedToCore(&ota_task, "ota_task", stack_size , (void *)urlPtr, task_priority, NULL, OTA_CORE);
-    //ret=xTaskCreate(&ota_task, "ota_task", 1024*20, (void *)urlPtr, ESP_TASK_MAIN_PRIO+2, NULL);
+    ret=xTaskCreatePinnedToCore(&ota_task, "ota_task", stack_size , (void *)&ota_thread_parms, task_priority, NULL, OTA_CORE);
     if (ret != pdPASS)  {
             ESP_LOGI(TAG, "create thread %s failed", "ota_task");
             return ESP_FAIL;
@@ -593,10 +780,14 @@ esp_err_t process_recovery_ota(const char * bin_url){
     return ESP_OK;
 }
 
-esp_err_t start_ota(const char * bin_url)
+esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length)
 {
-	if(is_recovery_running){
-		return process_recovery_ota(bin_url);
+#if RECOVERY_APPLICATION
+	return process_recovery_ota(bin_url,bin_buffer,length);
+#else
+	if(!bin_url){
+		ESP_LOGE(TAG,"missing URL parameter. Unable to start OTA");
+		return ESP_ERR_INVALID_ARG;
 	}
 	ESP_LOGW(TAG, "Called to update the firmware from url: %s",bin_url);
 	if(config_set_value(NVS_TYPE_STR, "fwurl", bin_url) != ESP_OK){
@@ -610,4 +801,6 @@ esp_err_t start_ota(const char * bin_url)
 
 	ESP_LOGW(TAG, "Rebooting to recovery to complete the installation");
 	return guided_factory();
+	return ESP_OK;
+#endif
 }

+ 1 - 0
components/squeezelite-ota/squeezelite-ota.h

@@ -30,4 +30,5 @@ esp_err_t start_ota(const char * bin_url);
 const char * ota_get_status();
 uint8_t ota_get_pct_complete();
 
+esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length);
 

+ 1 - 3
components/telnet/component.mk

@@ -10,6 +10,4 @@
 COMPONENT_SRCDIRS := . 
 COMPONENT_SRCDIRS += ./libtelnet 
 COMPONENT_ADD_INCLUDEDIRS := .
-COMPONENT_ADD_INCLUDEDIRS  += ./libtelnet 
-COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/
- 
+COMPONENT_PRIV_INCLUDEDIRS += ./libtelnet

+ 77 - 63
components/telnet/telnet.c

@@ -38,6 +38,8 @@
 #include "config.h"
 #include "nvs_utilities.h"
 #include "platform_esp32.h"
+#include "messaging.h"
+#include "trace.h"
 
 
 /************************************
@@ -47,15 +49,16 @@
 #define TELNET_STACK_SIZE 8048
 #define TELNET_RX_BUF 1024
 
-const static char tag[] = "telnet";
+const static char TAG[] = "telnet";
 static int uart_fd=0;
 RingbufHandle_t buf_handle;
-SemaphoreHandle_t xSemaphore = NULL;
+//static SemaphoreHandle_t xSemaphore = NULL;
 static size_t send_chunk=300;
 static size_t log_buf_size=2000;      //32-bit aligned size
 static bool bIsEnabled=false;
 static int partnerSocket=0;
 static telnet_t *tnHandle;
+extern bool bypass_wifi_manager;
 
 /************************************
  * Forward declarations
@@ -68,22 +71,34 @@ static int stdout_fstat(int fd, struct stat * st);
 static ssize_t stdout_write(int fd, const void * data, size_t size);
 static char *eventToString(telnet_event_type_t type);
 static void handle_telnet_conn();
-static void process_logs( UBaseType_t bytes);
-
+static void process_logs( UBaseType_t bytes, bool is_write_op);
+static bool bMirrorToUART=false;
 struct telnetUserData {
 	int sockfd;
 	telnet_t *tnHandle;
 	char * rxbuf;
 };
 
-
+bool is_serial_suppressed(){
+	return bIsEnabled?!bMirrorToUART:false ;
+}
 void init_telnet(){
 	char *val= get_nvs_value_alloc(NVS_TYPE_STR, "telnet_enable");
-	if (!val || strlen(val) == 0 || !strcasestr("YX",val) ) {
-		ESP_LOGI(tag,"Telnet support disabled");
+	if (!val || strlen(val) == 0 || !strcasestr("YXD",val) ) {
+		ESP_LOGI(TAG,"Telnet support disabled");
 		if(val) free(val);
 		return;
 	}
+	// if wifi manager is bypassed, there will possibly be no wifi available
+	//
+	bMirrorToUART = (strcasestr("D",val)!=NULL);
+	if(!bMirrorToUART && bypass_wifi_manager){
+		// This isn't supposed to happen, as telnet won't start if wifi manager isn't
+		// started. So this is a safeguard only.
+		ESP_LOGW(TAG,"Wifi manager is not active.  Forcing console on Serial output.");
+	}
+
+	FREE_AND_NULL(val);
 	val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block");
 	if(val){
 		send_chunk=atol(val);
@@ -97,18 +112,21 @@ void init_telnet(){
 		log_buf_size=log_buf_size>0?log_buf_size:4000;
 	}
 	// Create the semaphore to guard a shared resource.
-	vSemaphoreCreateBinary( xSemaphore );
+	//vSemaphoreCreateBinary( xSemaphore );
 
 	// Redirect the output to our telnet handler as soon as possible
-	StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)heap_caps_malloc(sizeof(StaticRingbuffer_t), MALLOC_CAP_SPIRAM);
-	uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM);
+	StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)malloc(sizeof(StaticRingbuffer_t) );
+	// All non-split ring buffer must have their memory alignment set to 32 bits.
+	uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT );
 	buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct);
 	if (buf_handle == NULL) {
-		ESP_LOGE(tag,"Failed to create ring buffer for telnet!");
+		ESP_LOGE(TAG,"Failed to create ring buffer for telnet!");
+		messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Failed to allocate memory for telnet buffer");
+
 		return;
 	}
 
-	ESP_LOGI(tag, "***Redirecting log output to telnet");
+	ESP_LOGI(TAG, "***Redirecting log output to telnet");
 	const esp_vfs_t vfs = {
 			.flags = ESP_VFS_FLAG_DEFAULT,
 			.write = &stdout_write,
@@ -116,8 +134,12 @@ void init_telnet(){
 			.fstat = &stdout_fstat,
 			.close = &stdout_close,
 			.read = &stdout_read,
+
 		};
-	uart_fd=open("/dev/uart/0", O_RDWR);
+
+	if(bMirrorToUART){
+		uart_fd=open("/dev/uart/0", O_RDWR);
+	}
 	ESP_ERROR_CHECK(esp_vfs_register("/dev/pkspstdout", &vfs, NULL));
 	freopen("/dev/pkspstdout", "w", stdout);
 	freopen("/dev/pkspstdout", "w", stderr);
@@ -125,11 +147,11 @@ void init_telnet(){
 }
 void start_telnet(void * pvParameter){
 	static bool isStarted=false;
-	StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
-	StackType_t *xStack = malloc(TELNET_STACK_SIZE);
+	StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
+	StackType_t *xStack = heap_caps_malloc(TELNET_STACK_SIZE,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
 	
 	if(!isStarted && bIsEnabled) {
-		xTaskCreateStatic( (TaskFunction_t) &telnet_task, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer);
+		xTaskCreateStatic( (TaskFunction_t) &telnet_task, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_MAIN_PRIO , xStack, xTaskBuffer);
 		isStarted=true;
 	}
 }
@@ -142,13 +164,15 @@ static void telnet_task(void *data) {
 
 	int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
 	if (rc < 0) {
-		ESP_LOGE(tag, "bind: %d (%s)", errno, strerror(errno));
+		ESP_LOGE(TAG, "bind: %d (%s)", errno, strerror(errno));
+		close(serverSocket);
 		return;
 	}
 
 	rc = listen(serverSocket, 5);
 	if (rc < 0) {
-		ESP_LOGE(tag, "listen: %d (%s)", errno, strerror(errno));
+		ESP_LOGE(TAG, "listen: %d (%s)", errno, strerror(errno));
+		close(serverSocket);
 		return;
 	}
 
@@ -156,16 +180,17 @@ static void telnet_task(void *data) {
 		socklen_t len = sizeof(serverAddr);
 		rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len);
 		if (rc < 0 ){
-			ESP_LOGE(tag, "accept: %d (%s)", errno, strerror(errno));
+			ESP_LOGE(TAG, "accept: %d (%s)", errno, strerror(errno));
 			return;
 		}
 		else {
 			partnerSocket = rc;
-			ESP_LOGD(tag, "We have a new client connection!");
+			ESP_LOGD(TAG, "We have a new client connection!");
 			handle_telnet_conn();
-			ESP_LOGD(tag, "Telnet connection terminated");
+			ESP_LOGD(TAG, "Telnet connection terminated");
 		}
 	}
+	close(serverSocket);
 	vTaskDelete(NULL);
 }
 
@@ -228,7 +253,9 @@ void process_received_data(const char * buffer, size_t size){
 	command[size]='\0';
 	if(command[0]!='\r' && command[0]!='\n'){
 		// echo the command buffer out to uart and run
-		write(uart_fd, command, size);
+		if(bMirrorToUART){
+			write(uart_fd, command, size);
+		}
 		run_command((char *)command);
 	}
 	free(command);
@@ -265,23 +292,23 @@ static void handle_telnet_events(
 } // myhandle_telnet_events
 
 
-static void process_logs(UBaseType_t count){
+static void process_logs( UBaseType_t bytes, bool is_write_op){
     //Receive an item from no-split ring buffer
 	size_t item_size;
-    UBaseType_t uxItemsWaiting;
-    UBaseType_t uxBytesToSend=count;
-
-	vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
-	if(count == 0){
-		// this sends the entire buffer to the remote client
-		uxBytesToSend = uxItemsWaiting;
-	}
-	if( partnerSocket ==0 && (uxItemsWaiting*100 / log_buf_size) <75){
-		// We still have some room in the ringbuffer and there's no telnet
-		// connection yet, so bail out for now.
-		//printf("%s() Log buffer used %u of %u bytes used\n", __FUNCTION__, uxItemsWaiting, log_buf_size);
+	UBaseType_t uxItemsWaiting;
+	UBaseType_t uxBytesToSend=bytes;
+
+    vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
+	bool is_space_available = ((log_buf_size-uxItemsWaiting)>=bytes && log_buf_size>uxItemsWaiting);
+	if( is_space_available && (is_write_op || partnerSocket == 0) ){
+		// there's still some room left in the buffer, and we're either
+		// processing a write operation or telnet isn't connected yet.
 		return;
 	}
+	if(is_write_op && !is_space_available && uxBytesToSend==0){
+		// flush at least the size of a full chunk
+		uxBytesToSend = send_chunk;
+	}
 
 	while(uxBytesToSend>0){
 		char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(50), uxBytesToSend);
@@ -322,7 +349,7 @@ static void handle_telnet_conn() {
   pTelnetUserData->sockfd = partnerSocket;
 
   // flush all the log buffer on connect
-  process_logs(0);
+  process_logs(log_buf_size, false);
 
   while(1) {
   	//ESP_LOGD(tag, "waiting for data");
@@ -339,7 +366,7 @@ static void handle_telnet_conn() {
   	  partnerSocket = 0;
   	  return;
   	}
-  	process_logs(send_chunk);
+  	process_logs(send_chunk, false);
 
 	taskYIELD();
   }
@@ -348,37 +375,24 @@ static void handle_telnet_conn() {
 
 // ******************* stdout/stderr Redirection to ringbuffer
 static ssize_t stdout_write(int fd, const void * data, size_t size) {
-	if (xSemaphoreTake(xSemaphore, (TickType_t) 10) == pdTRUE) {
-		// #1 Write to ringbuffer
-		if (buf_handle == NULL) {
-			printf("%s() ABORT. file handle _log_remote_fp is NULL\n",
-					__FUNCTION__);
-		} else {
-			//Send an item
-			UBaseType_t res = xRingbufferSend(buf_handle, data, size,
-					pdMS_TO_TICKS(100));
-			if (res != pdTRUE) {
-				// flush some entries
-				process_logs(size);
-				res = xRingbufferSend(buf_handle, data, size,
-						pdMS_TO_TICKS(100));
-				if (res != pdTRUE) {
-
-					printf("%s() ABORT. Unable to store log entry in buffer\n",
-							__FUNCTION__);
-				}
-			}
-		}
-		xSemaphoreGive(xSemaphore);
+	// #1 Write to ringbuffer
+	if (buf_handle == NULL) {
+		printf("%s() ABORT. file handle _log_remote_fp is NULL\n",
+				__FUNCTION__);
 	} else {
-		// We could not obtain the semaphore and can therefore not access
-		// the shared resource safely.
+		// flush the buffer if needed
+		process_logs(size, true);
+		//Send an item
+		UBaseType_t res = xRingbufferSend(buf_handle, data, size, pdMS_TO_TICKS(10));
+		assert(res == pdTRUE);
+
 	}
-	return write(uart_fd, data, size);
+	return bMirrorToUART?write(uart_fd, data, size):size;
 }
 
 static ssize_t stdout_read(int fd, void* data, size_t size) {
-	return read(fd, data, size);
+	//return read(fd, data, size);
+	return 0;
 }
 
 static int stdout_open(const char * path, int flags, int mode) {

+ 1 - 0
components/telnet/telnet.h

@@ -1,3 +1,4 @@
 
 void init_telnet();
 void start_telnet(void * pvParameter);
+extern bool is_serial_suppressed();

+ 1 - 6
components/tools/platform_esp32.h

@@ -31,9 +31,4 @@ extern  bool wait_for_wifi();
 extern void console_start();
 extern pthread_cond_t wifi_connect_suspend_cond;
 extern pthread_t wifi_connect_suspend_mutex;
-typedef enum {
-	INFO,
-	WARNING,
-	ERROR
-} message_severity_t;
-extern void set_status_message(message_severity_t severity, const char * message);
+

+ 15 - 0
components/tools/trace.h

@@ -28,5 +28,20 @@
 #define STR(macro)  QUOTE(macro)
 #endif
 #define ESP_LOG_DEBUG_EVENT(tag,e) ESP_LOGD(tag,"evt: " e)
+#ifndef STR_OR_ALT
+#define STR_OR_ALT(str,alt) (str?str:alt)
+#endif
+
+extern const char unknown_string_placeholder[];
+extern const char * str_or_unknown(const char * str);
 
+#ifndef FREE_AND_NULL
+#define FREE_AND_NULL(x) if(x) { free(x); x=NULL; }
+#endif
+
+#ifndef CASE_TO_STR
+#define CASE_TO_STR(x) case x: return STR(x); break;
+#endif
+#define START_FREE_MEM_CHECK(a) size_t a=heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
+#define CHECK_RESET_FREE_MEM_CHECK(a,b) ESP_LOGV(__FUNCTION__ ,b "Mem used: %i",a-heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); a=heap_caps_get_free_size(MALLOC_CAP_INTERNAL)
 

+ 93 - 0
components/wifi-manager/_esp_http_server.h

@@ -0,0 +1,93 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef __ESP_HTTP_SERVER_H_
+#define __ESP_HTTP_SERVER_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <http_parser.h>
+#include <sdkconfig.h>
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+/**
+ * @brief Starts the web server
+ *
+ * Create an instance of HTTP server and allocate memory/resources for it
+ * depending upon the specified configuration.
+ *
+ * Example usage:
+ * @code{c}
+ *
+ * //Function for starting the webserver
+ * httpd_handle_t start_webserver(void)
+ * {
+ *      // Generate default configuration
+ *      httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+ *
+ *      // Empty handle to http_server
+ *      httpd_handle_t server = NULL;
+ *
+ *      // Start the httpd server
+ *      if (httpd_start(&server, &config) == ESP_OK) {
+ *          // Register URI handlers
+ *          httpd_register_uri_handler(server, &uri_get);
+ *          httpd_register_uri_handler(server, &uri_post);
+ *      }
+ *      // If server failed to start, handle will be NULL
+ *      return server;
+ * }
+ *
+ * @endcode
+ *
+ * @param[in]  config   Configuration for new instance of the server
+ * @param[out] handle   Handle to newly created instance of the server. NULL on error
+ * @return
+ *  - ESP_OK    : Instance created successfully
+ *  - ESP_ERR_INVALID_ARG      : Null argument(s)
+ *  - ESP_ERR_HTTPD_ALLOC_MEM  : Failed to allocate memory for instance
+ *  - ESP_ERR_HTTPD_TASK       : Failed to launch server task
+ */
+esp_err_t  __httpd_start(httpd_handle_t *handle, const httpd_config_t *config);
+static inline int __httpd_os_thread_create_static(TaskHandle_t *thread,
+                                 const char *name, uint16_t stacksize, int prio,
+                                 void (*thread_routine)(void *arg), void *arg,
+                                 BaseType_t core_id)
+{
+
+
+	StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
+	StackType_t *xStack = heap_caps_malloc(stacksize,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
+
+
+	//
+	*thread = xTaskCreateStaticPinnedToCore(thread_routine, name, stacksize, arg, prio, xStack,xTaskBuffer,core_id);
+    if (*thread) {
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+}
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! _ESP_HTTP_SERVER_H_ */

+ 373 - 0
components/wifi-manager/_esp_httpd_main.c

@@ -0,0 +1,373 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <assert.h>
+
+#include <esp_http_server.h>
+#include <_esp_http_server.h>
+#include "esp_httpd_priv.h"
+#include "ctrl_sock.h"
+
+static const char *TAG = "_httpd";
+
+
+struct httpd_ctrl_data {
+    enum httpd_ctrl_msg {
+        HTTPD_CTRL_SHUTDOWN,
+        HTTPD_CTRL_WORK,
+    } hc_msg;
+    httpd_work_fn_t hc_work;
+    void *hc_work_arg;
+};
+
+
+static esp_err_t _httpd_server_init(struct httpd_data *hd)
+{
+    int fd = socket(PF_INET6, SOCK_STREAM, 0);
+    if (fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
+        return ESP_FAIL;
+    }
+
+    struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
+    struct sockaddr_in6 serv_addr = {
+        .sin6_family  = PF_INET6,
+        .sin6_addr    = inaddr_any,
+        .sin6_port    = htons(hd->config.server_port)
+    };
+
+    /* Enable SO_REUSEADDR to allow binding to the same
+     * address and port when restarting the server */
+    int enable = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
+        /* This will fail if CONFIG_LWIP_SO_REUSE is not enabled. But
+         * it does not affect the normal working of the HTTP Server */
+        ESP_LOGW(TAG, LOG_FMT("error enabling SO_REUSEADDR (%d)"), errno);
+    }
+
+    int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
+    if (ret < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    ret = listen(fd, hd->config.backlog_conn);
+    if (ret < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
+    if (ctrl_fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
+        close(fd);
+        return ESP_FAIL;
+    }
+
+    int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (msg_fd < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
+        close(fd);
+        close(ctrl_fd);
+        return ESP_FAIL;
+    }
+
+    hd->listen_fd = fd;
+    hd->ctrl_fd = ctrl_fd;
+    hd->msg_fd  = msg_fd;
+    return ESP_OK;
+}
+
+static void _httpd_process_ctrl_msg(struct httpd_data *hd)
+{
+    struct httpd_ctrl_data msg;
+    int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
+    if (ret <= 0) {
+        ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
+        return;
+    }
+    if (ret != sizeof(msg)) {
+        ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
+        return;
+    }
+
+    switch (msg.hc_msg) {
+    case HTTPD_CTRL_WORK:
+        if (msg.hc_work) {
+            ESP_LOGD(TAG, LOG_FMT("work"));
+            (*msg.hc_work)(msg.hc_work_arg);
+        }
+        break;
+    case HTTPD_CTRL_SHUTDOWN:
+        ESP_LOGD(TAG, LOG_FMT("shutdown"));
+        hd->hd_td.status = THREAD_STOPPING;
+        break;
+    default:
+        break;
+    }
+}
+
+static esp_err_t _httpd_accept_conn(struct httpd_data *hd, int listen_fd)
+{
+    /* If no space is available for new session, close the least recently used one */
+    if (hd->config.lru_purge_enable == true) {
+        if (!httpd_is_sess_available(hd)) {
+            /* Queue asynchronous closure of the least recently used session */
+            return httpd_sess_close_lru(hd);
+            /* Returning from this allowes the main server thread to process
+             * the queued asynchronous control message for closing LRU session.
+             * Since connection request hasn't been addressed yet using accept()
+             * therefore _httpd_accept_conn() will be called again, but this time
+             * with space available for one session
+             */
+       }
+    }
+
+    struct sockaddr_in addr_from;
+    socklen_t addr_from_len = sizeof(addr_from);
+    int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
+    if (new_fd < 0) {
+        ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);
+        return ESP_FAIL;
+    }
+    ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
+
+    struct timeval tv;
+    /* Set recv timeout of this fd as per config */
+    tv.tv_sec = hd->config.recv_wait_timeout;
+    tv.tv_usec = 0;
+    setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
+
+    /* Set send timeout of this fd as per config */
+    tv.tv_sec = hd->config.send_wait_timeout;
+    tv.tv_usec = 0;
+    setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
+
+    if (ESP_OK != httpd_sess_new(hd, new_fd)) {
+        ESP_LOGW(TAG, LOG_FMT("session creation failed"));
+        close(new_fd);
+        return ESP_FAIL;
+    }
+    ESP_LOGD(TAG, LOG_FMT("complete"));
+    return ESP_OK;
+}
+/* Manage in-coming connection or data requests */
+static esp_err_t _httpd_server(struct httpd_data *hd)
+{
+    fd_set read_set;
+    FD_ZERO(&read_set);
+    if (hd->config.lru_purge_enable || httpd_is_sess_available(hd)) {
+        /* Only listen for new connections if server has capacity to
+         * handle more (or when LRU purge is enabled, in which case
+         * older connections will be closed) */
+        FD_SET(hd->listen_fd, &read_set);
+    }
+    FD_SET(hd->ctrl_fd, &read_set);
+
+    int tmp_max_fd;
+    httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
+    int maxfd = MAX(hd->listen_fd, tmp_max_fd);
+    tmp_max_fd = maxfd;
+    maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
+
+    ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
+    int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);
+    if (active_cnt < 0) {
+        ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);
+        httpd_sess_delete_invalid(hd);
+        return ESP_OK;
+    }
+
+    /* Case0: Do we have a control message? */
+    if (FD_ISSET(hd->ctrl_fd, &read_set)) {
+        ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
+        _httpd_process_ctrl_msg(hd);
+        if (hd->hd_td.status == THREAD_STOPPING) {
+            ESP_LOGD(TAG, LOG_FMT("stopping thread"));
+            return ESP_FAIL;
+        }
+    }
+
+    /* Case1: Do we have any activity on the current data
+     * sessions? */
+    int fd = -1;
+    while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
+        if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
+            ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
+            if (httpd_sess_process(hd, fd) != ESP_OK) {
+                ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
+                close(fd);
+                /* Delete session and update fd to that
+                 * preceding the one being deleted */
+                fd = httpd_sess_delete(hd, fd);
+            }
+        }
+    }
+
+    /* Case2: Do we have any incoming connection requests to
+     * process? */
+    if (FD_ISSET(hd->listen_fd, &read_set)) {
+        ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
+        if (_httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {
+            ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
+        }
+    }
+    return ESP_OK;
+}
+
+static void _httpd_close_all_sessions(struct httpd_data *hd)
+{
+    int fd = -1;
+    while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
+        ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
+        httpd_sess_delete(hd, fd);
+        close(fd);
+    }
+}
+/* The main HTTPD thread */
+static void _httpd_thread(void *arg)
+{
+    int ret;
+    struct httpd_data *hd = (struct httpd_data *) arg;
+    hd->hd_td.status = THREAD_RUNNING;
+
+    ESP_LOGD(TAG, LOG_FMT("web server started"));
+    while (1) {
+        ret = _httpd_server(hd);
+        if (ret != ESP_OK) {
+            break;
+        }
+    }
+
+    ESP_LOGD(TAG, LOG_FMT("web server exiting"));
+    close(hd->msg_fd);
+    cs_free_ctrl_sock(hd->ctrl_fd);
+    _httpd_close_all_sessions(hd);
+    close(hd->listen_fd);
+    hd->hd_td.status = THREAD_STOPPED;
+    httpd_os_thread_delete();
+}
+static struct httpd_data *__httpd_create(const httpd_config_t *config)
+{
+    /* Allocate memory for httpd instance data */
+    struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
+    if (!hd) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP server instance"));
+        return NULL;
+    }
+    hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
+    if (!hd->hd_calls) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP URI handlers"));
+        free(hd);
+        return NULL;
+    }
+    hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
+    if (!hd->hd_sd) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP session data"));
+        free(hd->hd_calls);
+        free(hd);
+        return NULL;
+    }
+    struct httpd_req_aux *ra = &hd->hd_req_aux;
+    ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
+    if (!ra->resp_hdrs) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP response headers"));
+        free(hd->hd_sd);
+        free(hd->hd_calls);
+        free(hd);
+        return NULL;
+    }
+    hd->err_handler_fns = calloc(HTTPD_ERR_CODE_MAX, sizeof(httpd_err_handler_func_t));
+    if (!hd->err_handler_fns) {
+        ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP error handlers"));
+        free(ra->resp_hdrs);
+        free(hd->hd_sd);
+        free(hd->hd_calls);
+        free(hd);
+        return NULL;
+    }
+    /* Save the configuration for this instance */
+    hd->config = *config;
+    return hd;
+}
+static void _httpd_delete(struct httpd_data *hd)
+{
+    struct httpd_req_aux *ra = &hd->hd_req_aux;
+    /* Free memory of httpd instance data */
+    free(hd->err_handler_fns);
+    free(ra->resp_hdrs);
+    free(hd->hd_sd);
+
+    /* Free registered URI handlers */
+    httpd_unregister_all_uri_handlers(hd);
+    free(hd->hd_calls);
+    free(hd);
+}
+esp_err_t __httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
+{
+    if (handle == NULL || config == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    /* Sanity check about whether LWIP is configured for providing the
+     * maximum number of open sockets sufficient for the server. Though,
+     * this check doesn't guarantee that many sockets will actually be
+     * available at runtime as other processes may use up some sockets.
+     * Note that server also uses 3 sockets for its internal use :
+     *     1) listening for new TCP connections
+     *     2) for sending control messages over UDP
+     *     3) for receiving control messages over UDP
+     * So the total number of required sockets is max_open_sockets + 3
+     */
+    if (CONFIG_LWIP_MAX_SOCKETS < config->max_open_sockets + 3) {
+        ESP_LOGE(TAG, "Configuration option max_open_sockets is too large (max allowed %d)\n\t"
+                      "Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",
+                      CONFIG_LWIP_MAX_SOCKETS - 3);
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    struct httpd_data *hd = __httpd_create(config);
+    if (hd == NULL) {
+        /* Failed to allocate memory */
+        return ESP_ERR_HTTPD_ALLOC_MEM;
+    }
+
+    if (_httpd_server_init(hd) != ESP_OK) {
+        _httpd_delete(hd);
+        return ESP_FAIL;
+    }
+
+    httpd_sess_init(hd);
+    if (__httpd_os_thread_create_static(&hd->hd_td.handle, "httpd",
+                               hd->config.stack_size,
+                               hd->config.task_priority,
+                               _httpd_thread, hd,
+                               hd->config.core_id) != ESP_OK) {
+        /* Failed to launch task */
+        _httpd_delete(hd);
+        return ESP_ERR_HTTPD_TASK;
+    }
+
+    *handle = (httpd_handle_t *)hd;
+    return ESP_OK;
+}
+

+ 204 - 58
components/wifi-manager/code.js

@@ -10,7 +10,28 @@ if (!String.prototype.format) {
         });
     };
 }
+var nvs_type_t = {
+    NVS_TYPE_U8    : 0x01,  /*!< Type uint8_t */
+    NVS_TYPE_I8    : 0x11,  /*!< Type int8_t */
+    NVS_TYPE_U16   : 0x02,  /*!< Type uint16_t */
+    NVS_TYPE_I16   : 0x12,  /*!< Type int16_t */
+    NVS_TYPE_U32   : 0x04,  /*!< Type uint32_t */
+    NVS_TYPE_I32   : 0x14,  /*!< Type int32_t */
+    NVS_TYPE_U64   : 0x08,  /*!< Type uint64_t */
+    NVS_TYPE_I64   : 0x18,  /*!< Type int64_t */
+    NVS_TYPE_STR   : 0x21,  /*!< Type string */
+    NVS_TYPE_BLOB  : 0x42,  /*!< Type blob */
+    NVS_TYPE_ANY   : 0xff   /*!< Must be last */
+} ;
+
+var task_state_t = {
+		 0 : "eRunning",	/*!< A task is querying the state of itself, so must be running. */
+		 1 : "eReady",			/*!< The task being queried is in a read or pending ready list. */
+		 2 : "eBlocked",		/*!< The task being queried is in the Blocked state. */
+		 3 : "eSuspended",		/*!< The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
+		 4 : "eDeleted"				
 
+}
 var releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';
 var recovery = false;
 var enableAPTimer = true;
@@ -19,7 +40,6 @@ var commandHeader = 'squeezelite -b 500:2000 -d all=info ';
 var pname, ver, otapct, otadsc;
 var blockAjax = false;
 var blockFlashButton = false;
-var lastMsg = '';
 
 var apList = null;
 var selectedSSID = "";
@@ -189,20 +209,29 @@ $(document).ready(function(){
     $("input#autoexec-cb").on("click", function() {
         var data = { 'timestamp': Date.now() };
         autoexec = (this.checked)?1:0;
-        data['autoexec'] = autoexec;
-        showMessage('please wait for the ESP32 to reboot', 'WARNING');
+        data['config'] = {};
+        data['config'] = {
+            autoexec : {
+                value : autoexec,
+                type : 33
+            }
+        }
+
+
+        showMessage('please wait for the ESP32 to reboot', 'MESSAGING_WARNING');
         $.ajax({
             url: '/config.json',
             dataType: 'text',
             method: 'POST',
             cache: false,
-            headers: { "X-Custom-autoexec": autoexec },
+//            headers: { "X-Custom-autoexec": autoexec },
             contentType: 'application/json; charset=utf-8',
-            data: JSON.stringify(data),
+            data:  JSON.stringify(data),
+
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(thrownError);
-                if (thrownError != '') showMessage(thrownError, 'ERROR');
+                if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
             },
             complete: function(response) {
                 //var returnedResponse = JSON.parse(response.responseText);
@@ -219,7 +248,7 @@ $(document).ready(function(){
                     error: function (xhr, ajaxOptions, thrownError) {
                         console.log(xhr.status);
                         console.log(thrownError);
-                        if (thrownError != '') showMessage(thrownError, 'ERROR');
+                        if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
                     },
                     complete: function(response) {
                     	console.log('reboot call completed');
@@ -232,20 +261,26 @@ $(document).ready(function(){
     $("input#save-autoexec1").on("click", function() {
         var data = { 'timestamp': Date.now() };
         autoexec1 = $("#autoexec1").val();
-        data['autoexec1'] = autoexec1;
+        data['config'] = {};
+        data['config'] = {
+            autoexec1 : {
+                value : autoexec1,
+                type : 33
+            }
+        }
 
         $.ajax({
             url: '/config.json',
             dataType: 'text',
             method: 'POST',
             cache: false,
-            headers: { "X-Custom-autoexec1": autoexec1 },
+//            headers: { "X-Custom-autoexec1": autoexec1 },
             contentType: 'application/json; charset=utf-8',
             data: JSON.stringify(data),
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(thrownError);
-                if (thrownError != '') showMessage(thrownError, 'ERROR');
+                if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
             }
         });
         console.log('sent config JSON with headers:', autoexec1);
@@ -254,15 +289,19 @@ $(document).ready(function(){
 
     $("input#save-gpio").on("click", function() {
         var data = { 'timestamp': Date.now() };
+        var config = {};
+
         var headers = {};
         $("input.gpio").each(function() {
             var id = $(this)[0].id;
             var pin = $(this).val();
             if (pin != '') {
-                headers["X-Custom-"+id] = pin;
-                data[id] = pin;
+                config[id] = {};
+                config[id].value = pin;
+                config[id].type = nvs_type_t.NVS_TYPE_STR;
             }
         });
+        data['config'] = config;
         $.ajax({
             url: '/config.json',
             dataType: 'text',
@@ -274,7 +313,7 @@ $(document).ready(function(){
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(thrownError);
-                if (thrownError != '') showMessage(thrownError, 'ERROR');
+                if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
             }
         });
         console.log('sent config JSON with headers:', JSON.stringify(headers));
@@ -284,23 +323,40 @@ $(document).ready(function(){
     $("#save-nvs").on("click", function() {
         var headers = {};
         var data = { 'timestamp': Date.now() };
+        var config = {};
         $("input.nvs").each(function() {
             var key = $(this)[0].id;
             var val = $(this).val();
+            var nvs_type = parseInt($(this)[0].attributes.nvs_type.nodeValue,10);
             if (key != '') {
-                headers["X-Custom-"+key] = val;
-                data[key] = {};
-                data[key].value = val;
-                data[key].type = 33;
+                config[key] = {};
+                if(nvs_type == nvs_type_t.NVS_TYPE_U8    
+					|| nvs_type ==  nvs_type_t.NVS_TYPE_I8    
+					|| nvs_type ==  nvs_type_t.NVS_TYPE_U16   
+					|| nvs_type ==  nvs_type_t.NVS_TYPE_I16   
+					|| nvs_type ==  nvs_type_t.NVS_TYPE_U32   
+					|| nvs_type ==  nvs_type_t.NVS_TYPE_I32   
+					|| nvs_type ==  nvs_type_t.NVS_TYPE_U64   
+					|| nvs_type ==  nvs_type_t.NVS_TYPE_I64) {
+						config[key].value = parseInt(val);	
+				}   
+				else { 
+					config[key].value = val; 
+				} 
+                
+                
+                config[key].type = nvs_type;
             }
         });
         var key = $("#nvs-new-key").val();
         var val = $("#nvs-new-value").val();
         if (key != '') {
-            headers["X-Custom-"+key] = val;
-            data[key] = {};
-            data[key].value = val;
+//            headers["X-Custom-"	+key] = val;
+            config[key] = {};
+            config[key].value = val;
+            config[key].type = 33;
         }
+        data['config'] = config;
         $.ajax({
             url: '/config.json',
             dataType: 'text',
@@ -308,35 +364,64 @@ $(document).ready(function(){
             cache: false,
             headers: headers,
             contentType: 'application/json; charset=utf-8',
-            data: JSON.stringify(data),
+            data : JSON.stringify(data),
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(thrownError);
-                if (thrownError != '') showMessage(thrownError, 'ERROR');
+                if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
             }
         });
         console.log('sent config JSON with headers:', JSON.stringify(headers));
         console.log('sent config JSON with data:', JSON.stringify(data));
     });
-
+    $("#fwUpload").on("click", function() {
+        var upload_path = "/flash.json";
+        var fileInput = document.getElementById("flashfilename").files;
+        if (fileInput.length == 0) {
+            alert("No file selected!");
+        } else {
+            var file = fileInput[0];
+            var xhttp = new XMLHttpRequest();
+            xhttp.onreadystatechange = function() {
+                if (xhttp.readyState == 4) {
+                    if (xhttp.status == 200) {
+                    	showMessage(xhttp.responseText, 'MESSAGING_INFO')
+                    } else if (xhttp.status == 0) {
+                    	showMessage("Upload connection was closed abruptly!", 'MESSAGING_ERROR');
+                    } else {
+                    	showMessage(xhttp.status + " Error!\n" + xhttp.responseText, 'MESSAGING_ERROR');
+                    }
+                }
+            };
+            xhttp.open("POST", upload_path, true);
+            xhttp.send(file);    	
+        }
+    	enableStatusTimer = true;
+    });
     $("#flash").on("click", function() {
         var data = { 'timestamp': Date.now() };
         if (blockFlashButton) return;
         blockFlashButton = true;
         var url = $("#fwurl").val();
-        data['fwurl'] = url;
+        data['config'] = {
+            fwurl : {
+            value : url,
+            type : 33
+
+            }
+        };
+
         $.ajax({
             url: '/config.json',
             dataType: 'text',
             method: 'POST',
             cache: false,
-            headers: { "X-Custom-fwurl": url },
             contentType: 'application/json; charset=utf-8',
-            data: JSON.stringify(data),
+            data:  JSON.stringify(data),
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(thrownError);
-                if (thrownError != '') showMessage(thrownError, 'ERROR');
+                if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
             }
         });
         enableStatusTimer = true;
@@ -509,13 +594,17 @@ function performConnect(conntype){
         dataType: 'text',
         method: 'POST',
         cache: false,
-        headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd, 'X-Custom-host_name': dhcpname },
+//        headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd, 'X-Custom-host_name': dhcpname },
         contentType: 'application/json; charset=utf-8',
-        data: { 'timestamp': Date.now()},
+        data: JSON.stringify({ 'timestamp': Date.now(),
+        	'ssid' : selectedSSID, 
+        	'pwd' : pwd,
+        	'host_name' : dhcpname
+        	}), 
         error: function (xhr, ajaxOptions, thrownError) {
             console.log(xhr.status);
             console.log(thrownError);
-            if (thrownError != '') showMessage(thrownError, 'ERROR');
+            if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
         }
     });
 
@@ -564,11 +653,76 @@ function refreshAPHTML(data){
     $( "#wifi-list" ).html(h)
 }
 
+function getMessages() {
+	   $.getJSON("/messages.json?1", function(data) {
+	        data.forEach(function(msg) {
+	        	var msg_age = msg["current_time"] - msg["sent_time"];
+				var msg_time  = new Date();
+				msg_time.setTime( msg_time.getTime() - msg_age );
+	        	switch (msg["class"]) {
+	        		case "MESSAGING_CLASS_OTA":
+	        			//message: "{"ota_dsc":"Erasing flash complete","ota_pct":0}"
+	        			var ota_data = JSON.parse(msg["message"]);
+	        			if (ota_data.hasOwnProperty('ota_pct') && ota_data['ota_pct'] != 0){
+	        	            otapct = ota_data['ota_pct'];
+	        	            $('.progress-bar').css('width', otapct+'%').attr('aria-valuenow', otapct);
+	        	            $('.progress-bar').html(otapct+'%');
+	        	        }
+	        	        if (ota_data.hasOwnProperty('ota_dsc') && ota_data['ota_dsc'] != ''){
+	        	            otadsc = ota_data['ota_dsc'];
+	        	            $("span#flash-status").html(otadsc);
+	        	            if (otadsc.match(/Error:/) || otapct > 95) {
+	        	                blockFlashButton = false;
+	        	                enableStatusTimer = true;
+	        	            }
+	        	        }        			
+	        			break;
+	        		case "MESSAGING_CLASS_STATS":
+	        			// for task states, check structure : task_state_t
+	        			var stats_data = JSON.parse(msg["message"]);
+	        			console.log(msg_time.toLocaleString() + " - Number of tasks on the ESP32: " + stats_data["ntasks"]);
+	        			var stats_tasks = stats_data["tasks"];
+	        			console.log(msg_time.toLocaleString() + '\tname' + '\tcpu' + '\tstate'+ '\tminstk'+ '\tbprio'+ '\tcprio'+ '\tnum' );
+	        			stats_tasks.forEach(function(task) {
+	        				console.log(msg_time.toLocaleString() + '\t' + task["nme"] + '\t'+ task["cpu"] + '\t'+ task_state_t[task["st"]]+ '\t'+ task["minstk"]+ '\t'+ task["bprio"]+ '\t'+ task["cprio"]+ '\t'+ task["num"]);
+	        			});
+	        			break;
+	        		case "MESSAGING_CLASS_SYSTEM":
+	        			showMessage(msg["message"], msg["type"],msg_age);
+
+                        $("#syslogTable").append(
+                            "<tr class='"+msg["type"]+"'>"+
+                                "<td>"+msg_time.toLocaleString()+"</td>"+
+                                "<td>"+msg["message"]+"</td>"+
+                            "</tr>"
+                        );
+	        			break;
+				default:
+					break;
+				}	
+	        });
+	        
+	    })
+	    .fail(function(xhr, ajaxOptions, thrownError) {
+	        console.log(xhr.status);
+	        console.log(thrownError);
+	        if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
+	    });
+    /*
+    Minstk is minimum stack space left
+Bprio is base priority
+cprio is current priority
+nme is name
+st is task state. I provided a "typedef" that you can use to convert to text
+cpu is cpu percent used
+*/
+}
 function checkStatus(){
     RepeatCheckStatusInterval();
     if (!enableStatusTimer) return;
     if (blockAjax) return;
     blockAjax = true;
+    getMessages();
     $.getJSON( "/status.json", function( data ) {
         if (data.hasOwnProperty('ssid') && data['ssid'] != ""){
             if (data["ssid"] === selectedSSID){
@@ -659,20 +813,24 @@ function checkStatus(){
                 $("#otadiv").show();
                 $('a[href^="#tab-audio"]').hide();
                 $('a[href^="#tab-gpio"]').show();
+                $('#uploaddiv').show();
                 $("footer.footer").removeClass('sl');
                 $("footer.footer").addClass('recovery');
                 $("#boot-button").html('Reboot');
                 $("#boot-form").attr('action', '/reboot_ota.json');
+
                 enableStatusTimer = true;
             } else {
                 recovery = false;
                 $("#otadiv").hide();
                 $('a[href^="#tab-audio"]').show();
                 $('a[href^="#tab-gpio"]').hide();
+                $('#uploaddiv').hide();
                 $("footer.footer").removeClass('recovery');
                 $("footer.footer").addClass('sl');
                 $("#boot-button").html('Recovery');
                 $("#boot-form").attr('action', '/recovery.json');
+                
                 enableStatusTimer = false;
             }
         }
@@ -683,29 +841,10 @@ function checkStatus(){
             ver = data['version'];
             $("span#foot-fw").html("fw: <strong>"+ver+"</strong>, mode: <strong>"+pname+"</strong>");
         }
-        if (data.hasOwnProperty('ota_pct') && data['ota_pct'] != 0){
-            otapct = data['ota_pct'];
-            $('.progress-bar').css('width', otapct+'%').attr('aria-valuenow', otapct);
-            $('.progress-bar').html(otapct+'%');
-        }
-        if (data.hasOwnProperty('ota_dsc') && data['ota_dsc'] != ''){
-            otadsc = data['ota_dsc'];
-            $("span#flash-status").html(otadsc);
-            if (otadsc.match(/Error:/) || otapct > 95) {
-                blockFlashButton = false;
-                enableStatusTimer = true;
-            }
-        } else {
+         else {
             $("span#flash-status").html('');
         }
-        if (data.hasOwnProperty('message') && data['message'] != ''){
-            var msg = data['message'].text;
-            var severity = data['message'].severity;
-            if (msg != lastMsg) {
-                showMessage(msg, severity);
-                lastMsg = msg;
-            }
-        }
+       
         if (data.hasOwnProperty('Voltage')) {
             var voltage = data['Voltage'];
             var layer;
@@ -735,7 +874,7 @@ function checkStatus(){
     .fail(function(xhr, ajaxOptions, thrownError) {
         console.log(xhr.status);
         console.log(thrownError);
-        if (thrownError != '') showMessage(thrownError, 'ERROR');
+        if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
         blockAjax = false;
     });
 }
@@ -770,7 +909,7 @@ function getConfig() {
                     "<tr>"+
                         "<td>"+key+"</td>"+
                         "<td class='value'>"+
-                            "<input type='text' class='form-control nvs' id='"+key+"'>"+
+                            "<input type='text' class='form-control nvs' id='"+key+"'  nvs_type="+data[key].type+" >"+
                         "</td>"+
                     "</tr>"
                 );
@@ -783,7 +922,7 @@ function getConfig() {
                     "<input type='text' class='form-control' id='nvs-new-key' placeholder='new key'>"+
                 "</td>"+
                 "<td>"+
-                    "<input type='text' class='form-control' id='nvs-new-value' placeholder='new value'>"+
+                    "<input type='text' class='form-control' id='nvs-new-value' placeholder='new value' nvs_type=33 >"+ // todo: provide a way to choose field type 
                 "</td>"+
             "</tr>"
         );
@@ -791,19 +930,23 @@ function getConfig() {
     .fail(function(xhr, ajaxOptions, thrownError) {
         console.log(xhr.status);
         console.log(thrownError);
-        if (thrownError != '') showMessage(thrownError, 'ERROR');
+        if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
         blockAjax = false;
     });
 }
 
-function showMessage(message, severity) {
-    if (severity == 'INFO') {
+
+function showMessage(message, severity, age=0) {
+	if (severity == 'MESSAGING_INFO') {
         $('#message').css('background', '#6af');
-    } else if (severity == 'WARNING') {
+    } else if (severity == 'MESSAGING_WARNING') {
         $('#message').css('background', '#ff0');
+    } else if (severity == 'MESSAGING_ERROR' ) {
+        $('#message').css('background', '#f00');
     } else {
         $('#message').css('background', '#f00');
     }
+    	
     $('#message').html(message);
     $("#content").fadeTo("slow", 0.3, function() {
         $("#message").show(500).delay(5000).hide(500, function() {
@@ -815,3 +958,6 @@ function showMessage(message, severity) {
 function inRange(x, min, max) {
     return ((x-min)*(x-max) <= 0);
 }
+
+    
+    

+ 3 - 9
components/wifi-manager/component.mk

@@ -7,14 +7,8 @@
 # please read the SDK documents if you need to do this.
 #
 COMPONENT_EMBED_FILES := style.css code.js index.html bootstrap.min.css.gz jquery.min.js.gz popper.min.js.gz bootstrap.min.js.gz
-
-#CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG 
-CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO \
-	-I$(COMPONENT_PATH)/../tools				
-COMPONENT_ADD_INCLUDEDIRS := .
-COMPONENT_ADD_INCLUDEDIRS += $(COMPONENT_PATH)/../tools
-COMPONENT_ADD_INCLUDEDIRS += $(COMPONENT_PATH)/../squeezelite-ota
-COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/
-
+COMPONENT_ADD_INCLUDEDIRS := . 
+COMPONENT_EXTRA_INCLUDES += $(IDF_PATH)/components/esp_http_server/src $(IDF_PATH)/components/esp_http_server/src/port/esp32  $(IDF_PATH)/components/esp_http_server/src/util $(IDF_PATH)/components/esp_http_server/src/
+CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO
 
 

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

@@ -73,11 +73,7 @@ void  dns_server_stop(){
 }
 
 
-
 void  dns_server(void *pvParameters) {
-
-
-
     struct sockaddr_in sa, ra;
 
     /* Set redirection DNS hijack to the access point IP */

+ 1127 - 0
components/wifi-manager/http_server_handlers.c

@@ -0,0 +1,1127 @@
+/*
+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_handlers.h"
+
+#include "esp_http_server.h"
+#include "cmd_system.h"
+#include <inttypes.h>
+#include "squeezelite-ota.h"
+#include "nvs_utilities.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "cJSON.h"
+#include "config.h"
+#include "esp_system.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "config.h"
+#include "sys/param.h"
+#include "esp_vfs.h"
+#include "lwip/ip_addr.h"
+#include "messaging.h"
+#include "platform_esp32.h"
+#include "trace.h"
+
+#define HTTP_STACK_SIZE	(5*1024)
+const char str_na[]="N/A";
+#define STR_OR_NA(s) s?s:str_na
+/* @brief tag used for ESP serial console messages */
+static const char TAG[] = "httpd_handlers";
+/* @brief task handle for the http server */
+
+SemaphoreHandle_t http_server_config_mutex = NULL;
+extern RingbufHandle_t messaging;
+#define AUTH_TOKEN_SIZE 50
+typedef struct session_context {
+    char * auth_token;
+    bool authenticated;
+    char * sess_ip_address;
+    u16_t port;
+} session_context_t;
+
+
+union sockaddr_aligned {
+	struct sockaddr     sa;
+    struct sockaddr_storage st;
+    struct sockaddr_in  sin;
+    struct sockaddr_in6 sin6;
+} aligned_sockaddr_t;
+
+static const char redirect_payload1[]="<html><head><title>Redirecting to Captive Portal</title><meta http-equiv='refresh' content='0; url=";
+static const char redirect_payload2[]="'></head><body><p>Please wait, refreshing.  If page does not refresh, click <a href='";
+static const char redirect_payload3[]="'>here</a> to login.</p></body></html>";
+
+/**
+ * @brief embedded binary data.
+ * @see file "component.mk"
+ * @see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#embedding-binary-data
+ */
+extern const uint8_t style_css_start[] asm("_binary_style_css_start");
+extern const uint8_t style_css_end[]   asm("_binary_style_css_end");
+extern const uint8_t jquery_gz_start[] asm("_binary_jquery_min_js_gz_start");
+extern const uint8_t jquery_gz_end[] asm("_binary_jquery_min_js_gz_end");
+extern const uint8_t popper_gz_start[] asm("_binary_popper_min_js_gz_start");
+extern const uint8_t popper_gz_end[] asm("_binary_popper_min_js_gz_end");
+extern const uint8_t bootstrap_js_gz_start[] asm("_binary_bootstrap_min_js_gz_start");
+extern const uint8_t bootstrap_js_gz_end[] asm("_binary_bootstrap_min_js_gz_end");
+extern const uint8_t bootstrap_css_gz_start[] asm("_binary_bootstrap_min_css_gz_start");
+extern const uint8_t bootstrap_css_gz_end[] asm("_binary_bootstrap_min_css_gz_end");
+extern const uint8_t code_js_start[] asm("_binary_code_js_start");
+extern const uint8_t code_js_end[] asm("_binary_code_js_end");
+extern const uint8_t index_html_start[] asm("_binary_index_html_start");
+extern const uint8_t index_html_end[] asm("_binary_index_html_end");
+//
+//
+///* const http headers stored in ROM */
+//const static char http_hdr_template[] = "HTTP/1.1 200 OK\nContent-type: %s\nAccept-Ranges: bytes\nContent-Length: %d\nContent-Encoding: %s\nAccess-Control-Allow-Origin: *\n\n";
+//const static char http_html_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/html\nAccess-Control-Allow-Origin: *\nAccept-Encoding: identity\n\n";
+//const static char http_css_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/css\nCache-Control: public, max-age=31536000\nAccess-Control-Allow-Origin: *\n\n";
+//const static char http_js_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\nAccess-Control-Allow-Origin: *\n\n";
+//const static char http_400_hdr[] = "HTTP/1.1 400 Bad Request\nContent-Length: 0\n\n";
+//const static char http_404_hdr[] = "HTTP/1.1 404 Not Found\nContent-Length: 0\n\n";
+//const static char http_503_hdr[] = "HTTP/1.1 503 Service Unavailable\nContent-Length: 0\n\n";
+//const static char http_ok_json_no_cache_hdr[] = "HTTP/1.1 200 OK\nContent-type: application/json\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nAccess-Control-Allow-Origin: *\nAccept-Encoding: identity\n\n";
+//const static char http_redirect_hdr_start[] = "HTTP/1.1 302 Found\nLocation: http://";
+//const static char http_redirect_hdr_end[] = "/\n\n";
+esp_err_t redirect_processor(httpd_req_t *req, httpd_err_code_t error);
+
+
+char * alloc_get_http_header(httpd_req_t * req, const char * key){
+    char*  buf = NULL;
+    size_t buf_len;
+
+    /* Get header value string length and allocate memory for length + 1,
+     * extra byte for null termination */
+    buf_len = httpd_req_get_hdr_value_len(req, key) + 1;
+    if (buf_len > 1) {
+        buf = malloc(buf_len);
+        /* Copy null terminated value string into buffer */
+        if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {
+            ESP_LOGD_LOC(TAG, "Found header => %s: %s",key, buf);
+        }
+    }
+    return buf;
+}
+
+
+char * http_alloc_get_socket_address(httpd_req_t *req, u8_t local, in_port_t * portl) {
+
+	socklen_t len;
+	union sockaddr_aligned addr;
+	len = sizeof(addr);
+	ip_addr_t * ip_addr=NULL;
+	char * ipstr = malloc(INET6_ADDRSTRLEN);
+	memset(ipstr,0x0,INET6_ADDRSTRLEN);
+
+	typedef int (*getaddrname_fn_t)(int s, struct sockaddr *name, socklen_t *namelen);
+	getaddrname_fn_t get_addr = NULL;
+
+	int s = httpd_req_to_sockfd(req);
+	if(s == -1) {
+		free(ipstr);
+		return strdup("httpd_req_to_sockfd error");
+	}
+	ESP_LOGV_LOC(TAG,"httpd socket descriptor: %u", s);
+
+	get_addr = local?&lwip_getsockname:&lwip_getpeername;
+	if(get_addr(s, (struct sockaddr *)&addr, &len) <0){
+		ESP_LOGE_LOC(TAG,"Failed to retrieve socket address");
+		sprintf(ipstr,"N/A (0.0.0.%u)",local);
+	}
+	else {
+		if (addr.sin.sin_family!= AF_INET) {
+			ip_addr = (ip_addr_t *)&(addr.sin6.sin6_addr);
+			inet_ntop(addr.sa.sa_family, ip_addr, ipstr, INET6_ADDRSTRLEN);
+			ESP_LOGV_LOC(TAG,"Processing an IPV6 address : %s", ipstr);
+			*portl =  addr.sin6.sin6_port;
+			unmap_ipv4_mapped_ipv6(ip_2_ip4(ip_addr), ip_2_ip6(ip_addr));
+		}
+		else {
+			ip_addr = (ip_addr_t *)&(addr.sin.sin_addr);
+			inet_ntop(addr.sa.sa_family, ip_addr, ipstr, INET6_ADDRSTRLEN);
+			ESP_LOGV_LOC(TAG,"Processing an IPV6 address : %s", ipstr);
+			*portl =  addr.sin.sin_port;
+		}
+		inet_ntop(AF_INET, ip_addr, ipstr, INET6_ADDRSTRLEN);
+		ESP_LOGV_LOC(TAG,"Retrieved ip address:port = %s:%u",ipstr, *portl);
+	}
+	return ipstr;
+}
+bool is_captive_portal_host_name(httpd_req_t *req){
+	const char * host_name=NULL;
+	const char * ap_host_name=NULL;
+	char * ap_ip_address=NULL;
+	bool request_contains_hostname = false;
+	esp_err_t hn_err =ESP_OK, err=ESP_OK;
+	ESP_LOGD_LOC(TAG,  "Getting adapter host name");
+	if((err  = tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &host_name )) !=ESP_OK) {
+		ESP_LOGE_LOC(TAG,  "Unable to get host name. Error: %s",esp_err_to_name(err));
+	}
+	else {
+		ESP_LOGD_LOC(TAG,  "Host name is %s",host_name);
+	}
+
+   ESP_LOGD_LOC(TAG,  "Getting host name from request");
+	char *req_host = alloc_get_http_header(req, "Host");
+
+	if(tcpip_adapter_is_netif_up(TCPIP_ADAPTER_IF_AP)){
+		ESP_LOGD_LOC(TAG,  "Soft AP is enabled. getting ip info");
+		// Access point is up and running. Get the current IP address
+		tcpip_adapter_ip_info_t ip_info;
+		esp_err_t ap_ip_err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip_info);
+		if(ap_ip_err != ESP_OK){
+			ESP_LOGE_LOC(TAG,  "Unable to get local AP ip address. Error: %s",esp_err_to_name(ap_ip_err));
+		}
+		else {
+			ESP_LOGD_LOC(TAG,  "getting host name for TCPIP_ADAPTER_IF_AP");
+			if((hn_err  = tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_AP, &ap_host_name )) !=ESP_OK) {
+				ESP_LOGE_LOC(TAG,  "Unable to get host name. Error: %s",esp_err_to_name(hn_err));
+				err=err==ESP_OK?hn_err:err;
+			}
+			else {
+				ESP_LOGD_LOC(TAG,  "Soft AP Host name is %s",ap_host_name);
+			}
+
+			ap_ip_address =  malloc(IP4ADDR_STRLEN_MAX);
+			memset(ap_ip_address, 0x00, IP4ADDR_STRLEN_MAX);
+			if(ap_ip_address){
+				ESP_LOGD_LOC(TAG,  "Converting soft ip address to string");
+				ip4addr_ntoa_r(&ip_info.ip, ap_ip_address, IP4ADDR_STRLEN_MAX);
+				ESP_LOGD_LOC(TAG,"TCPIP_ADAPTER_IF_AP is up and has ip address %s ", ap_ip_address);
+			}
+		}
+
+	}
+
+
+    if((request_contains_hostname 		= (host_name!=NULL) && (req_host!=NULL) && strcasestr(req_host,host_name)) == true){
+    	ESP_LOGD_LOC(TAG,"http request host = system host name %s", req_host);
+    }
+    else if((request_contains_hostname 		= (ap_host_name!=NULL) && (req_host!=NULL) && strcasestr(req_host,ap_host_name)) == true){
+    	ESP_LOGD_LOC(TAG,"http request host = AP system host name %s", req_host);
+    }
+
+    FREE_AND_NULL(ap_ip_address);
+    FREE_AND_NULL(req_host);
+
+    return request_contains_hostname;
+}
+
+/* Custom function to free context */
+void free_ctx_func(void *ctx)
+{
+	START_FREE_MEM_CHECK(ff);
+	session_context_t * context = (session_context_t *)ctx;
+    if(context){
+    	ESP_LOGD(TAG, "Freeing up socket context");
+    	FREE_AND_NULL(context->auth_token);
+    	FREE_AND_NULL(context->sess_ip_address);
+    	free(context);
+    	CHECK_RESET_FREE_MEM_CHECK(ff,"free_ctx");
+    }
+}
+
+session_context_t* get_session_context(httpd_req_t *req){
+	START_FREE_MEM_CHECK(ff);
+	if (! req->sess_ctx) {
+		ESP_LOGD(TAG,"New connection context. Allocating session buffer");
+		req->sess_ctx = malloc(sizeof(session_context_t));
+		memset(req->sess_ctx,0x00,sizeof(session_context_t));
+		req->free_ctx = free_ctx_func;
+		// get the remote IP address only once per session
+	}
+	session_context_t *ctx_data = (session_context_t*)req->sess_ctx;
+	FREE_AND_NULL(ctx_data->sess_ip_address);
+	ctx_data->sess_ip_address = http_alloc_get_socket_address(req, 0, &ctx_data->port);
+	ESP_LOGD_LOC(TAG, "serving %s to peer %s port %u", req->uri, ctx_data->sess_ip_address , ctx_data->port);
+	CHECK_RESET_FREE_MEM_CHECK(ff,"get sess context");
+	return (session_context_t *)req->sess_ctx;
+}
+
+bool is_user_authenticated(httpd_req_t *req){
+	session_context_t *ctx_data = get_session_context(req);
+
+	if(ctx_data->authenticated){
+		ESP_LOGD_LOC(TAG,"User is authenticated.");
+		return true;
+	}
+
+	ESP_LOGD(TAG,"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));
+
+	// todo:  ask for user to authenticate
+	return false;
+}
+
+
+
+/* Copies the full path into destination buffer and returns
+ * pointer to requested file name */
+static const char* get_path_from_uri(char *dest, const char *uri, size_t destsize)
+{
+    size_t pathlen = strlen(uri);
+    memset(dest,0x0,destsize);
+
+    const char *quest = strchr(uri, '?');
+    if (quest) {
+        pathlen = MIN(pathlen, quest - uri);
+    }
+    const char *hash = strchr(uri, '#');
+    if (hash) {
+        pathlen = MIN(pathlen, hash - uri);
+    }
+
+    if ( pathlen + 1 > destsize) {
+        /* Full path string won't fit into destination buffer */
+        return NULL;
+    }
+
+    strlcpy(dest , uri, pathlen + 1);
+
+    // strip trailing blanks
+    char * sr = dest+pathlen;
+    while(*sr== ' ') *sr-- = '\0';
+
+    char * last_fs = strchr(dest,'/');
+    if(!last_fs) ESP_LOGD_LOC(TAG,"no / found in %s", dest);
+    char * p=last_fs;
+    while(p && *(++p)!='\0'){
+    	if(*p == '/') {
+    		last_fs=p;
+    	}
+    }
+    /* Return pointer to path, skipping the base */
+    return last_fs? ++last_fs: dest;
+}
+
+#define IS_FILE_EXT(filename, ext) \
+    (strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0)
+
+/* Set HTTP response content type according to file extension */
+static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename)
+{
+    if(strlen(filename) ==0){
+    	// for root page, etc.
+    	return httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
+    } else if (IS_FILE_EXT(filename, ".pdf")) {
+        return httpd_resp_set_type(req, "application/pdf");
+    } else if (IS_FILE_EXT(filename, ".html")) {
+        return httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
+    } else if (IS_FILE_EXT(filename, ".jpeg")) {
+        return httpd_resp_set_type(req, "image/jpeg");
+    } else if (IS_FILE_EXT(filename, ".ico")) {
+        return httpd_resp_set_type(req, "image/x-icon");
+    } else if (IS_FILE_EXT(filename, ".ico")) {
+        return httpd_resp_set_type(req, "image/x-icon");
+    } else if (IS_FILE_EXT(filename, ".css")) {
+        return httpd_resp_set_type(req, "text/css");
+    } else if (IS_FILE_EXT(filename, ".js")) {
+        return httpd_resp_set_type(req, "text/javascript");
+    } else if (IS_FILE_EXT(filename, ".json")) {
+        return httpd_resp_set_type(req, HTTPD_TYPE_JSON);
+    }
+
+    /* This is a limited set only */
+    /* For any other type always set as plain text */
+    return httpd_resp_set_type(req, "text/plain");
+}
+static esp_err_t set_content_type_from_req(httpd_req_t *req)
+{
+	char filepath[FILE_PATH_MAX];
+	const char *filename = get_path_from_uri(filepath, req->uri, sizeof(filepath));
+   if (!filename) {
+	   ESP_LOGE_LOC(TAG, "Filename is too long");
+	   /* Respond with 500 Internal Server Error */
+	   httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
+	   return ESP_FAIL;
+   }
+
+   /* If name has trailing '/', respond with directory contents */
+   if (filename[strlen(filename) - 1] == '/' && strlen(filename)>1) {
+	   httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Browsing files forbidden.");
+	   return ESP_FAIL;
+   }
+   set_content_type_from_file(req, filename);
+   return ESP_OK;
+}
+
+
+esp_err_t root_get_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
+    httpd_resp_set_hdr(req, "Accept-Encoding", "identity");
+
+    if(!is_user_authenticated(req)){
+    	// todo:  send password entry page and return
+    }
+    const size_t file_size = (index_html_end - index_html_start);
+	esp_err_t err = set_content_type_from_req(req);
+	if(err == ESP_OK){
+		httpd_resp_send(req, (const char *)index_html_start, file_size);
+	}
+    return err;
+}
+
+esp_err_t resource_filehandler(httpd_req_t *req){
+    char filepath[FILE_PATH_MAX];
+   ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+
+   const char *filename = get_path_from_uri(filepath, req->uri, sizeof(filepath));
+
+   if (!filename) {
+	   ESP_LOGE_LOC(TAG, "Filename is too long");
+	   /* Respond with 500 Internal Server Error */
+	   httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
+	   return ESP_FAIL;
+   }
+
+   /* If name has trailing '/', respond with directory contents */
+   if (filename[strlen(filename) - 1] == '/') {
+	   httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Browsing files forbidden.");
+	   return ESP_FAIL;
+   }
+
+   if(strstr(filename, "code.js")) {
+	    const size_t file_size = (code_js_end - code_js_start);
+	    set_content_type_from_file(req, filename);
+	    httpd_resp_send(req, (const char *)code_js_start, file_size);
+	}
+	else if(strstr(filename, "style.css")) {
+		set_content_type_from_file(req, filename);
+	    const size_t file_size = (style_css_end - style_css_start);
+	    httpd_resp_send(req, (const char *)style_css_start, file_size);
+	} else if(strstr(filename, "jquery.js")) {
+		set_content_type_from_file(req, filename);
+		httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
+	    const size_t file_size = (jquery_gz_end - jquery_gz_start);
+	    httpd_resp_send(req, (const char *)jquery_gz_start, file_size);
+	}else if(strstr(filename, "popper.js")) {
+		set_content_type_from_file(req, filename);
+		httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
+	    const size_t file_size = (popper_gz_end - popper_gz_start);
+	    httpd_resp_send(req, (const char *)popper_gz_start, file_size);
+	}
+	else if(strstr(filename, "bootstrap.js")) {
+			set_content_type_from_file(req, filename);
+			httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
+		    const size_t file_size = (bootstrap_js_gz_end - bootstrap_js_gz_start);
+		    httpd_resp_send(req, (const char *)bootstrap_js_gz_start, file_size);
+	}
+	else if(strstr(filename, "bootstrap.css")) {
+			set_content_type_from_file(req, filename);
+			httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
+		    const size_t file_size = (bootstrap_css_gz_end - bootstrap_css_gz_start);
+		    httpd_resp_send(req, (const char *)bootstrap_css_gz_start, file_size);
+    }
+	else {
+	   ESP_LOGE_LOC(TAG, "Unknown resource [%s] from path [%s] ", filename,filepath);
+	   /* Respond with 404 Not Found */
+	   httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
+	   return ESP_FAIL;
+   }
+   ESP_LOGD_LOC(TAG, "Resource sending complete");
+   return ESP_OK;
+
+}
+esp_err_t ap_scan_handler(httpd_req_t *req){
+    const char empty[] = "{}";
+	ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+	wifi_manager_scan_async();
+	esp_err_t err = set_content_type_from_req(req);
+	if(err == ESP_OK){
+		httpd_resp_send(req, (const char *)empty, HTTPD_RESP_USE_STRLEN);
+	}
+	return err;
+}
+esp_err_t ap_get_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+    /* if we can get the mutex, write the last version of the AP list */
+	esp_err_t err = set_content_type_from_req(req);
+	if( err == ESP_OK && wifi_manager_lock_json_buffer(( TickType_t ) 10)){
+		char *buff = wifi_manager_alloc_get_ap_list_json();
+		wifi_manager_unlock_json_buffer();
+		if(buff!=NULL){
+			httpd_resp_send(req, (const char *)buff, HTTPD_RESP_USE_STRLEN);
+			free(buff);
+		}
+		else {
+			ESP_LOGD_LOC(TAG,  "Error retrieving ap list json string. ");
+			httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to retrieve AP list");
+		}
+	}
+	else {
+		httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "AP list unavailable");
+		ESP_LOGE_LOC(TAG,   "GET /ap.json failed to obtain mutex");
+	}
+	return err;
+}
+
+esp_err_t config_get_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+	esp_err_t err = set_content_type_from_req(req);
+	if(err == ESP_OK){
+		char * json = config_alloc_get_json(false);
+		if(json==NULL){
+			ESP_LOGD_LOC(TAG,  "Error retrieving config json string. ");
+			httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Error retrieving configuration object");
+			err=ESP_FAIL;
+		}
+		else {
+			ESP_LOGD_LOC(TAG,  "config json : %s",json );
+			httpd_resp_send(req, (const char *)json, HTTPD_RESP_USE_STRLEN);
+			free(json);
+		}
+	}
+	return err;
+}
+esp_err_t post_handler_buff_receive(httpd_req_t * req){
+    esp_err_t err = ESP_OK;
+
+    int total_len = req->content_len;
+    int cur_len = 0;
+    char *buf = ((rest_server_context_t *)(req->user_ctx))->scratch;
+    int received = 0;
+    if (total_len >= SCRATCH_BUFSIZE) {
+        /* Respond with 500 Internal Server Error */
+    	ESP_LOGE_LOC(TAG,"Received content was too long. ");
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR , "Content too long");
+        err = ESP_FAIL;
+    }
+    while (err == ESP_OK && cur_len < total_len) {
+        received = httpd_req_recv(req, buf + cur_len, total_len);
+        if (received <= 0) {
+            /* Respond with 500 Internal Server Error */
+        	ESP_LOGE_LOC(TAG,"Not all data was received. ");
+            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR , "Not all data was received");
+            err = ESP_FAIL;
+        }
+        else {
+        	cur_len += received;
+        }
+    }
+
+    if(err == ESP_OK) {
+    	buf[total_len] = '\0';
+    }
+    return err;
+}
+
+esp_err_t config_post_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+	bool bOTA=false;
+	char * otaURL=NULL;
+    esp_err_t err = post_handler_buff_receive(req);
+    if(err!=ESP_OK){
+        return err;
+    }
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+	err = set_content_type_from_req(req);
+	if(err != ESP_OK){
+		return err;
+	}
+
+    char *buf = ((rest_server_context_t *)(req->user_ctx))->scratch;
+    cJSON *root = cJSON_Parse(buf);
+    if(root == NULL){
+    	ESP_LOGE_LOC(TAG, "Parsing config json failed. Received content was: %s",buf);
+    	httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Malformed config json.  Unable to parse content.");
+    	return ESP_FAIL;
+    }
+
+    char * root_str = cJSON_Print(root);
+	if(root_str!=NULL){
+		ESP_LOGD(TAG, "Processing config item: \n%s", root_str);
+		free(root_str);
+	}
+
+    cJSON *item=cJSON_GetObjectItemCaseSensitive(root, "config");
+    if(!item){
+    	ESP_LOGE_LOC(TAG, "Parsing config json failed. Received content was: %s",buf);
+    	httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Malformed config json.  Unable to parse content.");
+    	err = ESP_FAIL;
+    }
+    else{
+    	// navigate to the first child of the config structure
+    	if(item->child) item=item->child;
+    }
+
+	while (item && err == ESP_OK)
+	{
+		cJSON *prev_item = item;
+		item=item->next;
+		char * entry_str = cJSON_Print(prev_item);
+		if(entry_str!=NULL){
+			ESP_LOGD_LOC(TAG, "Processing config item: \n%s", entry_str);
+			free(entry_str);
+		}
+
+		if(prev_item->string==NULL) {
+			ESP_LOGD_LOC(TAG,"Config value does not have a name");
+			httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Malformed config json.  Value does not have a name.");
+			err = ESP_FAIL;
+		}
+		if(err == ESP_OK){
+			ESP_LOGD_LOC(TAG,"Found config value name [%s]", prev_item->string);
+			nvs_type_t item_type=  config_get_item_type(prev_item);
+			if(item_type!=0){
+				void * val = config_safe_alloc_get_entry_value(item_type, prev_item);
+				if(val!=NULL){
+					if(strcmp(prev_item->string, "fwurl")==0) {
+						if(item_type!=NVS_TYPE_STR){
+							ESP_LOGE_LOC(TAG,"Firmware url should be type %d. Found type %d instead.",NVS_TYPE_STR,item_type );
+							httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Malformed config json.  Wrong type for firmware URL.");
+							err = ESP_FAIL;
+						}
+						else {
+							// we're getting a request to do an OTA from that URL
+							ESP_LOGW_LOC(TAG,   "Found OTA request!");
+							otaURL=strdup(val);
+							bOTA=true;
+						}
+					}
+					else {
+						if(config_set_value(item_type, prev_item->string , val) != ESP_OK){
+							ESP_LOGE_LOC(TAG,"Unable to store value for [%s]", prev_item->string);
+							httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR , "Unable to store config value");
+							err = ESP_FAIL;
+						}
+						else {
+							ESP_LOGD_LOC(TAG,"Successfully set value for [%s]",prev_item->string);
+						}
+					}
+					free(val);
+				}
+				else {
+					char messageBuffer[101]={};
+					ESP_LOGE_LOC(TAG,"Value not found for [%s]", prev_item->string);
+					snprintf(messageBuffer,sizeof(messageBuffer),"Malformed config json.  Missing value for entry %s.",prev_item->string);
+					httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, messageBuffer);
+					err = ESP_FAIL;
+				}
+			}
+			else {
+				ESP_LOGE_LOC(TAG,"Unable to determine the type of config value [%s]", prev_item->string);
+				httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Malformed config json.  Missing value for entry.");
+				err = ESP_FAIL;
+			}
+		}
+	}
+
+
+	if(err==ESP_OK){
+		httpd_resp_sendstr(req, "{ \"result\" : \"OK\" }");
+	}
+    cJSON_Delete(root);
+	if(bOTA) {
+
+#if RECOVERY_APPLICATION
+		ESP_LOGW_LOC(TAG,   "Starting process OTA for url %s",otaURL);
+#else
+		ESP_LOGW_LOC(TAG,   "Restarting system to process OTA for url %s",otaURL);
+#endif
+		wifi_manager_reboot_ota(otaURL);
+		free(otaURL);
+	}
+    return err;
+
+}
+esp_err_t connect_post_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    char success[]="{}";
+    char * ssid=NULL;
+    char * password=NULL;
+    char * host_name=NULL;
+
+	esp_err_t err = post_handler_buff_receive(req);
+	if(err!=ESP_OK){
+		return err;
+	}
+	err = set_content_type_from_req(req);
+	if(err != ESP_OK){
+		return err;
+	}
+
+	char *buf = ((rest_server_context_t *)(req->user_ctx))->scratch;
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+	cJSON *root = cJSON_Parse(buf);
+
+	if(root==NULL){
+		httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR , "JSON parsing error.");
+		return ESP_FAIL;
+	}
+
+	cJSON * ssid_object = cJSON_GetObjectItem(root, "ssid");
+	if(ssid_object !=NULL){
+		ssid = strdup(ssid_object->valuestring);
+	}
+	cJSON * password_object = cJSON_GetObjectItem(root, "pwd");
+	if(password_object !=NULL){
+		password = strdup(password_object->valuestring);
+	}
+	cJSON * host_name_object = cJSON_GetObjectItem(root, "host_name");
+	if(host_name_object !=NULL){
+		host_name = strdup(host_name_object->valuestring);
+	}
+	cJSON_Delete(root);
+
+	if(host_name!=NULL){
+		if(config_set_value(NVS_TYPE_STR, "host_name", host_name) != ESP_OK){
+			ESP_LOGW_LOC(TAG,  "Unable to save host name configuration");
+		}
+	}
+
+	if(ssid !=NULL && strlen(ssid) <= MAX_SSID_SIZE && strlen(password) <= MAX_PASSWORD_SIZE  ){
+		wifi_config_t* config = wifi_manager_get_wifi_sta_config();
+		memset(config, 0x00, sizeof(wifi_config_t));
+		strlcpy((char *)config->sta.ssid, ssid, sizeof(config->sta.ssid)+1);
+		if(password){
+			strlcpy((char *)config->sta.password, password, sizeof(config->sta.password)+1);
+		}
+		ESP_LOGD_LOC(TAG,   "http_server_netconn_serve: wifi_manager_connect_async() call, with ssid: %s, password: %s", config->sta.ssid, config->sta.password);
+		wifi_manager_connect_async();
+		httpd_resp_send(req, (const char *)success, strlen(success));
+	}
+	else {
+		httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Malformed json. Missing or invalid ssid/password.");
+		err = ESP_FAIL;
+	}
+	FREE_AND_NULL(ssid);
+	FREE_AND_NULL(password);
+	FREE_AND_NULL(host_name);
+	return err;
+}
+esp_err_t connect_delete_handler(httpd_req_t *req){
+	char success[]="{}";
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+	esp_err_t err = set_content_type_from_req(req);
+	if(err != ESP_OK){
+		return err;
+	}
+	httpd_resp_send(req, (const char *)success, strlen(success));
+	wifi_manager_disconnect_async();
+
+    return ESP_OK;
+}
+esp_err_t reboot_ota_post_handler(httpd_req_t *req){
+	char success[]="{}";
+	ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+    esp_err_t err = set_content_type_from_req(req);
+	if(err != ESP_OK){
+		return err;
+	}
+
+	httpd_resp_send(req, (const char *)success, strlen(success));
+	wifi_manager_reboot(OTA);
+    return ESP_OK;
+}
+esp_err_t reboot_post_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    char success[]="{}";
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+    esp_err_t err = set_content_type_from_req(req);
+	if(err != ESP_OK){
+		return err;
+	}
+	httpd_resp_send(req, (const char *)success, strlen(success));
+	wifi_manager_reboot(RESTART);
+	return ESP_OK;
+}
+esp_err_t recovery_post_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    char success[]="{}";
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+    esp_err_t err = set_content_type_from_req(req);
+	if(err != ESP_OK){
+		return err;
+	}
+	httpd_resp_send(req, (const char *)success, strlen(success));
+	wifi_manager_reboot(RECOVERY);
+	return ESP_OK;
+}
+
+#if RECOVERY_APPLICATION
+esp_err_t flash_post_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    char success[]="File uploaded. Flashing started.";
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+    esp_err_t err = httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
+	if(err != ESP_OK){
+		return err;
+	}
+	char * binary_buffer = malloc(req->content_len);
+	if(binary_buffer == NULL){
+        ESP_LOGE(TAG, "File too large : %d bytes", req->content_len);
+        /* Respond with 400 Bad Request */
+        httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
+                            "Binary file too large. Unable to allocate memory!");
+        return ESP_FAIL;
+	}
+	ESP_LOGI(TAG, "Receiving ota binary file");
+	/* Retrieve the pointer to scratch buffer for temporary storage */
+	char *buf = ((rest_server_context_t *)(req->user_ctx))->scratch;
+
+	char *head=binary_buffer;
+	int received;
+
+	/* Content length of the request gives
+	 * the size of the file being uploaded */
+	int remaining = req->content_len;
+
+	while (remaining > 0) {
+
+		ESP_LOGI(TAG, "Remaining size : %d", remaining);
+		/* Receive the file part by part into a buffer */
+		if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) {
+			if (received == HTTPD_SOCK_ERR_TIMEOUT) {
+				/* Retry if timeout occurred */
+				continue;
+			}
+			FREE_RESET(binary_buffer);
+			ESP_LOGE(TAG, "File reception failed!");
+			/* Respond with 500 Internal Server Error */
+			httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
+			err = ESP_FAIL;
+			goto bail_out;
+		}
+
+		/* Write buffer content to file on storage */
+		if (received ) {
+			memcpy(head,buf,received );
+			head+=received;
+		}
+
+		/* Keep track of remaining size of
+		 * the file left to be uploaded */
+		remaining -= received;
+	}
+
+	/* Close file upon upload completion */
+	ESP_LOGI(TAG, "File reception complete. Invoking OTA process.");
+	err = start_ota(NULL, binary_buffer, req->content_len);
+	if(err!=ESP_OK){
+		httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA processing failed");
+		goto bail_out;
+	}
+
+	//todo:  handle this in ajax.  For now, just send the root page
+	httpd_resp_send(req, (const char *)success, strlen(success));
+
+bail_out:
+
+	return err;
+}
+
+#endif
+char * get_ap_ip_address(){
+	static char ap_ip_address[IP4ADDR_STRLEN_MAX]={};
+
+	tcpip_adapter_ip_info_t ip_info;
+	esp_err_t err=ESP_OK;
+	memset(ap_ip_address, 0x00, sizeof(ap_ip_address));
+
+	ESP_LOGD_LOC(TAG,  "checking if soft AP is enabled");
+	if(tcpip_adapter_is_netif_up(TCPIP_ADAPTER_IF_AP)){
+		ESP_LOGD_LOC(TAG,  "Soft AP is enabled. getting ip info");
+		// Access point is up and running. Get the current IP address
+		err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip_info);
+		if(err != ESP_OK){
+			ESP_LOGE_LOC(TAG,  "Unable to get local AP ip address. Error: %s",esp_err_to_name(err));
+		}
+		else {
+			ESP_LOGV_LOC(TAG,  "Converting soft ip address to string");
+			ip4addr_ntoa_r(&ip_info.ip, ap_ip_address, IP4ADDR_STRLEN_MAX);
+			ESP_LOGD_LOC(TAG,"TCPIP_ADAPTER_IF_AP is up and has ip address %s ", ap_ip_address);
+		}
+	}
+	else{
+		ESP_LOGD_LOC(TAG,"AP Is not enabled. Returning blank string");
+	}
+	return ap_ip_address;
+}
+esp_err_t process_redirect(httpd_req_t *req, const char * status){
+	const char location_prefix[] = "http://";
+	char * ap_ip_address=get_ap_ip_address();
+	char * remote_ip=NULL;
+	in_port_t port=0;
+
+	ESP_LOGD_LOC(TAG,  "Getting remote socket address");
+	remote_ip = http_alloc_get_socket_address(req,0, &port);
+
+	size_t buf_size = strlen(redirect_payload1) +strlen(redirect_payload2) + strlen(redirect_payload3) +2*(strlen(location_prefix)+strlen(ap_ip_address))+1;
+	char * redirect=malloc(buf_size);
+
+	if(strcasestr(status,"302")){
+		size_t url_buf_size = strlen(location_prefix) + strlen(ap_ip_address)+1;
+		char * redirect_url = malloc(url_buf_size);
+		memset(redirect_url,0x00,url_buf_size);
+		snprintf(redirect_url, buf_size,"%s%s/",location_prefix, ap_ip_address);
+		ESP_LOGW_LOC(TAG,  "Redirecting host [%s] to %s (from uri %s)",remote_ip, redirect_url,req->uri);
+		httpd_resp_set_hdr(req,"Location",redirect_url);
+		snprintf(redirect, buf_size,"OK");
+		FREE_AND_NULL(redirect_url);
+	}
+	else {
+
+		snprintf(redirect, buf_size,"%s%s%s%s%s%s%s",redirect_payload1, location_prefix, ap_ip_address,redirect_payload2, location_prefix, ap_ip_address,redirect_payload3);
+		ESP_LOGW_LOC(TAG,  "Responding to host [%s] (from uri %s) with redirect html page %s",remote_ip, req->uri,redirect);
+	}
+
+	httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
+	httpd_resp_set_hdr(req,"Cache-Control","no-cache");
+	httpd_resp_set_status(req, status);
+	httpd_resp_send(req, redirect, HTTPD_RESP_USE_STRLEN);
+	FREE_AND_NULL(redirect);
+	FREE_AND_NULL(remote_ip);
+
+	return ESP_OK;
+}
+esp_err_t redirect_200_ev_handler(httpd_req_t *req){
+	ESP_LOGD_LOC(TAG,"Processing known redirect url %s",req->uri);
+	process_redirect(req,"200 OK");
+	return ESP_OK;
+}
+esp_err_t redirect_processor(httpd_req_t *req, httpd_err_code_t error){
+	esp_err_t err=ESP_OK;
+	const char * host_name=NULL;
+	const char * ap_host_name=NULL;
+	char * user_agent=NULL;
+	char * remote_ip=NULL;
+	char * sta_ip_address=NULL;
+	char * ap_ip_address=get_ap_ip_address();
+	char * socket_local_address=NULL;
+	bool request_contains_hostname = false;
+	bool request_contains_ap_ip_address 	= false;
+	bool request_is_sta_ip_address 	= false;
+	bool connected_to_ap_ip_interface 	= false;
+	bool connected_to_sta_ip_interface = false;
+	bool useragentiscaptivenetwork = false;
+
+	ESP_LOGW_LOC(TAG, "Invalid URL requested: [%s]", req->uri);
+    if(wifi_manager_lock_sta_ip_string(portMAX_DELAY)){
+		sta_ip_address = strdup(wifi_manager_get_sta_ip_string());
+		wifi_manager_unlock_sta_ip_string();
+	}
+	else {
+    	ESP_LOGE(TAG,"Unable to obtain local IP address from WiFi Manager.");
+    	httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR , NULL);
+	}
+
+    in_port_t port=0;
+    ESP_LOGV_LOC(TAG,  "Getting remote socket address");
+    remote_ip = http_alloc_get_socket_address(req,0, &port);
+
+    ESP_LOGV_LOC(TAG,  "Getting host name from request");
+    char *req_host = alloc_get_http_header(req, "Host");
+
+    user_agent = alloc_get_http_header(req,"User-Agent");
+    if((useragentiscaptivenetwork = (user_agent!=NULL  && strcasestr(user_agent,"CaptiveNetworkSupport"))==true)){
+    	ESP_LOGW_LOC(TAG,"Found user agent that supports captive networks! [%s]",user_agent);
+    }
+
+	esp_err_t hn_err = ESP_OK;
+	ESP_LOGV_LOC(TAG,  "Getting adapter host name");
+	if((hn_err  = tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &host_name )) !=ESP_OK) {
+		ESP_LOGE_LOC(TAG,  "Unable to get host name. Error: %s",esp_err_to_name(hn_err));
+		err=err==ESP_OK?hn_err:err;
+	}
+	else {
+		ESP_LOGV_LOC(TAG,  "Host name is %s",host_name);
+	}
+
+
+	in_port_t loc_port=0;
+	ESP_LOGV_LOC(TAG,  "Getting local socket address");
+	socket_local_address= http_alloc_get_socket_address(req,1, &loc_port);
+
+
+
+    ESP_LOGD_LOC(TAG,  "Peer IP: %s [port %u], System AP IP address: %s, System host: %s. Requested Host: [%s], uri [%s]",STR_OR_NA(remote_ip), port, STR_OR_NA(ap_ip_address), STR_OR_NA(host_name), STR_OR_NA(req_host), req->uri);
+    /* captive portal functionality: redirect to access point IP for HOST that are not the access point IP OR the STA IP */
+	/* determine if Host is from the STA IP address */
+
+    if((request_contains_hostname 		= (host_name!=NULL) && (req_host!=NULL) && strcasestr(req_host,host_name)) == true){
+    	ESP_LOGD_LOC(TAG,"http request host = system host name %s", req_host);
+    }
+    else if((request_contains_hostname 		= (ap_host_name!=NULL) && (req_host!=NULL) && strcasestr(req_host,ap_host_name)) == true){
+    	ESP_LOGD_LOC(TAG,"http request host = AP system host name %s", req_host);
+    }
+    if((request_contains_ap_ip_address 	= (ap_ip_address!=NULL) && (req_host!=NULL) && strcasestr(req_host,ap_ip_address))== true){
+    	ESP_LOGD_LOC(TAG,"http request host is access point ip address %s", req_host);
+    }
+    if((connected_to_ap_ip_interface 	= (ap_ip_address!=NULL) && (socket_local_address!=NULL) && strcasestr(socket_local_address,ap_ip_address))==true){
+    	ESP_LOGD_LOC(TAG,"http request is connected to access point interface IP %s", ap_ip_address);
+    }
+    if((request_is_sta_ip_address 	= (sta_ip_address!=NULL) && (req_host!=NULL) && strcasestr(req_host,sta_ip_address))==true){
+    	ESP_LOGD_LOC(TAG,"http request host is WiFi client ip address %s", req_host);
+    }
+    if((connected_to_sta_ip_interface = (sta_ip_address!=NULL) && (socket_local_address!=NULL) && strcasestr(sta_ip_address,socket_local_address))==true){
+    	ESP_LOGD_LOC(TAG,"http request is connected to WiFi client ip address %s", sta_ip_address);
+    }
+
+   if((error == 0) || (error == HTTPD_404_NOT_FOUND && connected_to_ap_ip_interface && !(request_contains_ap_ip_address || request_contains_hostname ))) {
+		process_redirect(req,"302 Found");
+
+	}
+	else {
+		ESP_LOGD_LOC(TAG,"URL not found, and not processing captive portal so throw regular 404 error");
+		httpd_resp_send_err(req, error, NULL);
+	}
+
+	FREE_AND_NULL(socket_local_address);
+
+	FREE_AND_NULL(req_host);
+	FREE_AND_NULL(user_agent);
+
+    FREE_AND_NULL(sta_ip_address);
+	FREE_AND_NULL(remote_ip);
+	return err;
+
+}
+esp_err_t redirect_ev_handler(httpd_req_t *req){
+	return redirect_processor(req,0);
+}
+
+esp_err_t messages_get_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    START_FREE_MEM_CHECK(before);
+    START_FREE_MEM_CHECK(all);
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+    CHECK_RESET_FREE_MEM_CHECK(before, "after user auth");
+    esp_err_t err = set_content_type_from_req(req);
+	if(err != ESP_OK){
+		return err;
+	}
+	CHECK_RESET_FREE_MEM_CHECK(before, "after set_content_type");
+	cJSON * json_messages=  messaging_retrieve_messages(messaging);
+	CHECK_RESET_FREE_MEM_CHECK(before, "after receiving messages");
+	if(json_messages!=NULL){
+		char * json_text= cJSON_Print(json_messages);
+		CHECK_RESET_FREE_MEM_CHECK(before, "after json print");
+		httpd_resp_send(req, (const char *)json_text, strlen(json_text));
+		CHECK_RESET_FREE_MEM_CHECK(before, "after http send");
+		free(json_text);
+		CHECK_RESET_FREE_MEM_CHECK(before, "after free json message");
+		cJSON_free(json_messages);
+		CHECK_RESET_FREE_MEM_CHECK(before, "after free json");
+	}
+	else {
+		httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR , "Unable to retrieve messages");
+	}
+	CHECK_RESET_FREE_MEM_CHECK(all, "before returning");
+	return ESP_OK;
+}
+
+esp_err_t status_get_handler(httpd_req_t *req){
+    ESP_LOGD_LOC(TAG, "serving [%s]", req->uri);
+    if(!is_user_authenticated(req)){
+    	// todo:  redirect to login page
+    	// return ESP_OK;
+    }
+    esp_err_t err = set_content_type_from_req(req);
+	if(err != ESP_OK){
+		return err;
+	}
+
+	if(wifi_manager_lock_json_buffer(( TickType_t ) 10)) {
+		char *buff = wifi_manager_alloc_get_ip_info_json();
+		wifi_manager_unlock_json_buffer();
+		if(buff) {
+			httpd_resp_send(req, (const char *)buff, strlen(buff));
+			free(buff);
+		}
+		else {
+			httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR , "Empty status object");
+		}
+	}
+	else {
+		httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR , "Error retrieving status object");
+	}
+
+	return ESP_OK;
+}
+
+
+esp_err_t err_handler(httpd_req_t *req, httpd_err_code_t error){
+	esp_err_t err = ESP_OK;
+
+    if(error != HTTPD_404_NOT_FOUND){
+    	err = httpd_resp_send_err(req, error, NULL);
+    }
+    else {
+    	err = redirect_processor(req,error);
+    }
+
+	return err;
+}

+ 148 - 0
components/wifi-manager/http_server_handlers.h

@@ -0,0 +1,148 @@
+/*
+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 "esp_http_server.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"
+#include "esp_vfs.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ESP_LOGE_LOC(t,str, ...)  ESP_LOGE(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define ESP_LOGI_LOC(t,str, ...)  ESP_LOGI(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define ESP_LOGD_LOC(t,str, ...)  ESP_LOGD(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define ESP_LOGW_LOC(t,str, ...)  ESP_LOGW(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
+#define ESP_LOGV_LOC(t,str, ...)  ESP_LOGV(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
+
+
+esp_err_t root_get_handler(httpd_req_t *req);
+esp_err_t resource_filehandler(httpd_req_t *req);
+esp_err_t resource_filehandler(httpd_req_t *req);
+esp_err_t resource_filehandler(httpd_req_t *req);
+esp_err_t resource_filehandler(httpd_req_t *req);
+esp_err_t resource_filehandler(httpd_req_t *req);
+esp_err_t resource_filehandler(httpd_req_t *req);
+esp_err_t ap_get_handler(httpd_req_t *req);
+esp_err_t config_get_handler(httpd_req_t *req);
+esp_err_t config_post_handler(httpd_req_t *req);
+esp_err_t connect_post_handler(httpd_req_t *req);
+esp_err_t connect_delete_handler(httpd_req_t *req);
+esp_err_t reboot_ota_post_handler(httpd_req_t *req);
+esp_err_t reboot_post_handler(httpd_req_t *req);
+esp_err_t recovery_post_handler(httpd_req_t *req);
+#if RECOVERY_APPLICATION
+esp_err_t flash_post_handler(httpd_req_t *req);
+#endif
+esp_err_t status_get_handler(httpd_req_t *req);
+esp_err_t messages_get_handler(httpd_req_t *req);
+
+esp_err_t ap_scan_handler(httpd_req_t *req);
+esp_err_t redirect_ev_handler(httpd_req_t *req);
+esp_err_t redirect_200_ev_handler(httpd_req_t *req);
+
+
+esp_err_t err_handler(httpd_req_t *req, httpd_err_code_t error);
+#define SCRATCH_BUFSIZE (10240)
+#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
+
+typedef struct rest_server_context {
+    char base_path[ESP_VFS_PATH_MAX + 1];
+    char scratch[SCRATCH_BUFSIZE];
+} rest_server_context_t;
+/**
+ * @brief RTOS task for the HTTP server. Do not start manually.
+ * @see void http_server_start()
+ */
+void CODE_RAM_LOCATION http_server(void *pvParameters);
+
+/* @brief helper function that processes one HTTP request at a time */
+void CODE_RAM_LOCATION http_server_netconn_serve(struct netconn *conn);
+
+/* @brief create the task for the http server */
+esp_err_t CODE_RAM_LOCATION 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* CODE_RAM_LOCATION http_server_get_header(char *request, char *header_name, int *len);
+
+void CODE_RAM_LOCATION strreplace(char *src, char *str, char *rep);
+/* @brief lock the json config object */
+bool http_server_lock_json_object(TickType_t xTicksToWait);
+/* @brief unlock the json config object */
+void http_server_unlock_json_object();
+#define PROTECTED_JSON_CALL(a)  if(http_server_lock_json_object( portMAX_DELAY )){ \ a; http_server_unlocklock_json_object(); }  else{  ESP_LOGE(TAG, "could not get access to json mutex in wifi_scan"); }
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 58 - 97
components/wifi-manager/index.html

@@ -4,19 +4,12 @@
         <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" />
-        <link rel="stylesheet" href="/bootstrap.css">
-        <link rel="stylesheet" href="res/test/bootstrap.min.css"> <!-- TODO delete -->
-        <link rel="stylesheet" href="/style.css">
-        <script src="/jquery.js"></script>
-        <script src="/popper.js"></script>
-        <script src="/bootstrap.js"></script>
-
-        <script src="res/test/jquery.min.js"></script> <!-- TODO delete -->
-        <script src="res/test/popper.min.js"></script> <!-- TODO delete -->
-        <script src="res/test/bootstrap.min.js"></script> <!-- TODO delete -->
-
-        <script src="/code.js"></script>
-
+        <link rel="stylesheet" href="/res/bootstrap.css">
+        <link rel="stylesheet" href="/res/style.css">
+        <script src="/res/jquery.js"></script>
+        <script src="/res/popper.js"></script>
+        <script src="/res/bootstrap.js"></script>
+        <script src="/res/code.js"></script>
 
         <title>esp32-wifi-manager</title>
     </head>
@@ -74,7 +67,7 @@
                 <a class="nav-link" data-toggle="tab" href="#tab-firmware">Firmware</a>
             </li>
             <li class="nav-item">
-                <a class="nav-link" data-toggle="tab" href="#tab-gpio">GPIO</a>
+                <a class="nav-link" data-toggle="tab" href="#tab-syslog">Syslog</a>
             </li>
             <li class="nav-item">
                 <a class="nav-link" data-toggle="tab" href="#tab-nvs">NVS editor</a>
@@ -197,7 +190,7 @@
                             </div>
                         </div>
                     </div>
-                </div>
+                </div> <!-- wifi -->
 
                 <div class="tab-pane fade" id="tab-audio">
                     <div id="audioout">
@@ -247,72 +240,7 @@
                           <label class="custom-control-label" for="autoexec-cb"></label>
                     </div>
                     <br />
-                </div>
-
-                <div class="tab-pane fade" id="tab-gpio">
-                    <table class="table table-hover">
-                        <thead>
-                            <tr>
-                                <th scope="col">Signal</th>
-                                <th scope="col">I2S pin</th>
-                                <th scope="col">SPDIF pin</th>
-                            </tr>
-                        </thead>
-                        <tbody id="gpioTable">
-                            <tr>
-                                <td>Bit clock</td>
-                                <td>
-                                    <input type="text" class="form-control gpio" id="gpio-i2s-bc" maxlength="2" size="2">
-                                </td>
-                                <td>
-                                    <input type="text" class="form-control gpio" id="gpio-spdif-bc" maxlength="2" size="2">
-                                </td>
-                            </tr>
-                            <tr>
-                                <td>Word select</td>
-                                <td>
-                                    <input type="text" class="form-control gpio" id="gpio-i2s-ws" maxlength="2" size="2">
-                                </td>
-                                <td>
-                                    <input type="text" class="form-control gpio" id="gpio-spdif-ws" maxlength="2" size="2">
-                                </td>
-                            </tr>
-                            <tr>
-                                <td>Data</td>
-                                <td>
-                                    <input type="text" class="form-control gpio" id="gpio-i2s-data" maxlength="2" size="2">
-                                </td>
-                                <td>
-                                    <input type="text" class="form-control gpio" id="gpio-spdif-data" maxlength="2" size="2">
-                                </td>
-                            </tr>
-                        </tbody>
-                    </table>
-                    <div class="buttons">
-                        <input id="save-gpio" type="button" class="btn btn-success" value="Save" />
-                    </div>
-                </div>
-
-                <div class="tab-pane fade" id="tab-nvs">
-                    <table class="table table-hover">
-                        <thead>
-                            <tr>
-                                <th scope="col">Key</th>
-                                <th scope="col">Value</th>
-                            </tr>
-                        </thead>
-                        <tbody id="nvsTable">
-                        </tbody>
-                    </table>
-                    <div class="buttons">
-						<div id="boot-div">
-                        	<form id="reboot-form" action="/reboot.json" method="post" target="dummyframe">
-                            	<button id="reboot-button" type="submit" class="btn btn-primary">Reboot</button>
-                        	</form>
-                    	</div>                    
-                        <input id="save-nvs" type="button" class="btn btn-success" value="Save" />
-                    </div>
-                </div>
+                </div> <!-- audio -->
 
                 <div class="tab-pane fade" id="tab-firmware">
                     <div id="boot-div">
@@ -343,21 +271,17 @@
                     </table>
                     <h2>Firmware URL:</h2>
                     <textarea id="fwurl" maxlength="350"></textarea>
-                   <!-- 
-                    <br />OR<br />
-                    <div class="input-group mb-3" id="upload">
-                        <div class="custom-file">
-                            <input type="file" class="custom-file-input" id="inputGroupFile01">
-                            <label class="custom-file-label" for="inputGroupFile01"></label>
-                        </div>
-                        <div class="input-group-append">
-                            <span class="input-group-text" id="fwUpload">Upload</span>
-                        </div>
-                    </div>
-                   -->
                     <div class="buttons">
-                    	<input type="button" id="flash" class="btn btn-danger" value="Flash!" /><span id="flash-status"></span>
+                        <input type="button" id="flash" class="btn btn-danger" value="Flash!" /><span id="flash-status"></span>
+                    </div>
+                    <p>OR</p>
+                    <div class="form-group">
+                        <input type="file" class="form-control-file" id="flashfilename" aria-describedby="fileHelp">
+                        <div class="buttons">
+                            <button type="button" class="btn btn-danger" id="fwUpload">Upload!</button>
+                        </div>
                     </div>
+
                     <div id="otadiv">
                         <div class="progress" id="progress">
                             <div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%">
@@ -365,11 +289,48 @@
                             </div>
                         </div>
                     </div>
-                </div>
+                </div> <!-- firmware -->
+
+                <div class="tab-pane fade" id="tab-syslog">
+                    <table class="table table-hover">
+                        <thead>
+                            <tr>
+                                <th scope="col">Timestamp</th>
+                                <th scope="col">Message</th>
+                            </tr>
+                        </thead>
+                        <tbody id="syslogTable">
+                        </tbody>
+                    </table>
+                    <div class="buttons">
+                        <input id="clear-syslog" type="button" class="btn btn-danger btn-sm" value="Clear" />
+                    </div>
+                </div> <!-- syslog -->
+
+                <div class="tab-pane fade" id="tab-nvs">
+                    <table class="table table-hover">
+                        <thead>
+                            <tr>
+                                <th scope="col">Key</th>
+                                <th scope="col">Value</th>
+                            </tr>
+                        </thead>
+                        <tbody id="nvsTable">
+                        </tbody>
+                    </table>
+                    <div class="buttons">
+						<div id="boot-div">
+                        	<form id="reboot-form" action="/reboot.json" method="post" target="dummyframe">
+                            	<button id="reboot-button" type="submit" class="btn btn-primary">Reboot</button>
+                        	</form>
+                    	</div>                    
+                        <input id="save-nvs" type="button" class="btn btn-success" value="Save" />
+                    </div>
+                </div> <!-- nvs -->
 
                 <div class="tab-pane fade" id="tab-credits">
                     <div class="jumbotron">
-                        <p><strong><a href="https://github.com/sle118/squeezelite-esp32">squeezelite-esp32</a></strong>, &copy; 2019, philippe44, sle118, daduke<br />Licensed under the GPL</p>
+                        <p><strong><a href="https://github.com/sle118/squeezelite-esp32">squeezelite-esp32</a></strong>, &copy; 2020, philippe44, sle118, daduke<br />Licensed under the GPL</p>
                          <p>
                             This app would not be possible without the following libraries:
                         </p>
@@ -388,7 +349,7 @@
 	                      <input type="checkbox" class="custom-control-input" id="show-nvs" checked="checked">
 	                      <label class="custom-control-label" for="show-nvs"></label>
 	                </div>
-                </div>
+                </div> <!-- credits -->
             </div>
             <footer class="footer"><span id="foot-fw"></span><span id="foot-wifi"></span></footer>
             <iframe width="0" height="0" border="0" name="dummyframe" id="dummyframe"></iframe>

+ 12 - 6
components/wifi-manager/style.css

@@ -212,12 +212,6 @@ input[type='text'], input[type='password'], textarea {
   padding: 4px;
 }
 
-input.gpio {
-    width: 2em;
-    color: #000;
-    height: 1.8em;
-}
-
 .custom-switch {
     margin-left: 8px;
 }
@@ -273,6 +267,18 @@ textarea#autoexec1, textarea#fwurl, div#upload {
     width: 80%;
 }
 
+table tr.MESSAGING_INFO {
+    background: #123;
+}
+
+table tr.MESSAGING_WARNING {
+    background: #330;
+}
+
+table tr.MESSAGING_ERROR {
+    background: #300;
+}
+
 input, textarea {
     border-radius: 3px;
     border: 1px solid transparent;

+ 11 - 36
components/wifi-manager/wifi_manager.c

@@ -37,7 +37,6 @@ Contains the freeRTOS task and all necessary support
 #include <stdbool.h>
 
 #include "dns_server.h"
-#include "http_server.h"
 #include "esp_system.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
@@ -61,6 +60,9 @@ Contains the freeRTOS task and all necessary support
 #include "platform_config.h"
 #include "trace.h"
 #include "cmd_system.h"
+#include "messaging.h"
+
+#include "http_server_handlers.h"
 #include "monitor.h"
 #include "globdefs.h"
 
@@ -69,7 +71,7 @@ Contains the freeRTOS task and all necessary support
 #endif
 
 #define STR_OR_BLANK(p) p==NULL?"":p
-#define FREE_AND_NULL(p) if(p!=NULL){ free(p); p=NULL;}
+
 /* objects used to manipulate the main queue of events */
 QueueHandle_t wifi_manager_queue;
 SemaphoreHandle_t wifi_manager_json_mutex = NULL;
@@ -83,7 +85,7 @@ char *ip_info_json = NULL;
 char * release_url=NULL;
 cJSON * ip_info_cjson=NULL;
 wifi_config_t* wifi_manager_config_sta = NULL;
-static update_reason_code_t last_update_reason_code=0;
+
 
 static int32_t total_connected_time=0;
 static int64_t last_connected=0;
@@ -202,9 +204,6 @@ bool isGroupBitSet(uint8_t bit){
 	EventBits_t uxBits= xEventGroupGetBits(wifi_manager_event_group);
 	return (uxBits & bit);
 }
-void wifi_manager_refresh_ota_json(){
-	wifi_manager_send_message(EVENT_REFRESH_OTA, NULL);
-}
 
 void wifi_manager_scan_async(){
 	wifi_manager_send_message(ORDER_START_WIFI_SCAN, NULL);
@@ -357,6 +356,7 @@ esp_err_t wifi_manager_save_sta_config(){
 		esp_err = nvs_commit(handle);
 		if (esp_err != ESP_OK) {
 			ESP_LOGE(TAG,  "Unable to commit changes. Error %s", esp_err_to_name(esp_err));
+			messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Unable to save wifi credentials. %s",esp_err_to_name(esp_err));
 			return esp_err;
 		}
 		nvs_close(handle);
@@ -449,8 +449,6 @@ cJSON * wifi_manager_get_basic_info(cJSON **old){
 	cJSON_AddItemToObject(root, "version", cJSON_CreateString(desc->version));
 	if(release_url !=NULL) cJSON_AddItemToObject(root, "release_url", cJSON_CreateString(release_url));
 	cJSON_AddNumberToObject(root,"recovery",	is_recovery_running?1:0);
-	cJSON_AddItemToObject(root, "ota_dsc", cJSON_CreateString(ota_get_status()));
-	cJSON_AddNumberToObject(root,"ota_pct",	ota_get_pct_complete()	);
 	cJSON_AddItemToObject(root, "Jack", cJSON_CreateString(jack_inserted_svc() ? "1" : "0"));
 	cJSON_AddNumberToObject(root,"Voltage",	battery_value_svc());
 	cJSON_AddNumberToObject(root,"disconnect_count", num_disconnect	);
@@ -479,12 +477,6 @@ void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code)
 	wifi_config_t *config = wifi_manager_get_wifi_sta_config();
 	ip_info_cjson = wifi_manager_get_basic_info(&ip_info_cjson);
 
-	if(update_reason_code == UPDATE_OTA) {
-		update_reason_code = last_update_reason_code;
-	}
-	else {
-		last_update_reason_code = update_reason_code;
-	}
 	cJSON_AddNumberToObject(ip_info_cjson, "urc", update_reason_code);
 	if(config){
 		cJSON_AddItemToObject(ip_info_cjson, "ssid", cJSON_CreateString((char *)config->sta.ssid));
@@ -505,7 +497,7 @@ char * get_mac_string(uint8_t mac[6]){
 
 	char * macStr=malloc(LOCAL_MAC_SIZE);
 	memset(macStr, 0x00, LOCAL_MAC_SIZE);
-	snprintf(macStr, LOCAL_MAC_SIZE-1,MACSTR, MAC2STR(mac));
+	snprintf(macStr, LOCAL_MAC_SIZE,MACSTR, MAC2STR(mac));
 	return macStr;
 
 }
@@ -855,20 +847,6 @@ void wifi_manager_connect_async(){
 	wifi_manager_send_message(ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_USER);
 }
 
-void set_status_message(message_severity_t severity, const char * message){
-	if(ip_info_cjson==NULL){
-		ip_info_cjson = wifi_manager_get_new_json(&ip_info_cjson);
-	}
-	if(ip_info_cjson==NULL){
-		ESP_LOGE(TAG,  "Error setting status message. Unable to allocate cJSON.");
-		return;
-	}
-	cJSON * item=cJSON_GetObjectItem(ip_info_cjson, "message");
-	item = wifi_manager_get_new_json(&item);
-	cJSON_AddItemToObject(item, "severity", cJSON_CreateString(severity==INFO?"INFO":severity==WARNING?"WARNING":severity==ERROR?"ERROR":"" ));
-	cJSON_AddItemToObject(item, "text", cJSON_CreateString(message));
-}
-
 
 char* wifi_manager_alloc_get_ip_info_json(){
 	return cJSON_PrintUnformatted(ip_info_cjson);
@@ -1138,12 +1116,6 @@ void wifi_manager( void * pvParameters ){
 					ESP_LOGD(TAG,  "Done Invoking SCAN DONE callback");
 				}
 				break;
-			case EVENT_REFRESH_OTA:
-				if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
-					wifi_manager_generate_ip_info_json( UPDATE_OTA );
-					wifi_manager_unlock_json_buffer();
-				}
-				break;
 
 			case ORDER_START_WIFI_SCAN:
 				ESP_LOGD(TAG,   "MESSAGE: ORDER_START_WIFI_SCAN");
@@ -1153,6 +1125,7 @@ void wifi_manager( void * pvParameters ){
 					if(esp_wifi_scan_start(&scan_config, false)!=ESP_OK){
 						ESP_LOGW(TAG,  "Unable to start scan; wifi is trying to connect");
 //						set_status_message(WARNING, "Wifi Connecting. Cannot start scan.");
+						messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Wifi connecting. Cannot start scan.");
 					}
 					else {
 						xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT);
@@ -1358,6 +1331,8 @@ void wifi_manager( void * pvParameters ){
 				else{
 					/* lost connection ? */
 					ESP_LOGE(TAG,   "WiFi Connection lost.");
+					messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"WiFi Connection lost");
+
 					if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
 						wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION );
 						wifi_manager_unlock_json_buffer();
@@ -1439,7 +1414,7 @@ void wifi_manager( void * pvParameters ){
 				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
 				break;
 			case UPDATE_CONNECTION_OK:
-				/* refresh JSON with the new ota data */
+				/* refresh JSON */
 				if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
 					/* generate the connection info with success */
 					wifi_manager_generate_ip_info_json( UPDATE_CONNECTION_OK );

+ 17 - 8
components/wifi-manager/wifi_manager.h

@@ -42,6 +42,17 @@ extern "C" {
 #include "squeezelite-ota.h"
 #include "cJSON.h"
 
+#ifndef RECOVERY_APPLICATION
+#error "RECOVERY_APPLICATION not defined. Defaulting to squeezelite"
+#endif
+
+#if RECOVERY_APPLICATION==1
+#elif RECOVERY_APPLICATION==0
+#else
+#error "unknown configuration"
+#endif
+
+
 
 /**
  * @brief Defines the maximum size of a SSID name. 32 is IEEE standard.
@@ -187,12 +198,11 @@ typedef enum message_code_t {
 	EVENT_STA_DISCONNECTED = 12,
 	EVENT_SCAN_DONE = 13,
 	EVENT_STA_GOT_IP = 14,
-	EVENT_REFRESH_OTA = 15,
-	ORDER_RESTART_OTA = 16,
-	ORDER_RESTART_RECOVERY = 17,
-	ORDER_RESTART_OTA_URL = 18,
-	ORDER_RESTART = 19,
-	MESSAGE_CODE_COUNT = 20 /* important for the callback array */
+	ORDER_RESTART_OTA = 15,
+	ORDER_RESTART_RECOVERY = 16,
+	ORDER_RESTART_OTA_URL = 17,
+	ORDER_RESTART = 18,
+	MESSAGE_CODE_COUNT = 19 /* important for the callback array */
 
 }message_code_t;
 
@@ -215,8 +225,7 @@ typedef enum update_reason_code_t {
 	UPDATE_CONNECTION_OK = 0,
 	UPDATE_FAILED_ATTEMPT = 1,
 	UPDATE_USER_DISCONNECT = 2,
-	UPDATE_LOST_CONNECTION = 3,
-	UPDATE_OTA=4
+	UPDATE_LOST_CONNECTION = 3
 }update_reason_code_t;
 
 typedef enum connection_request_made_by_code_t{

+ 165 - 0
components/wifi-manager/wifi_manager_http_server.c

@@ -0,0 +1,165 @@
+/*
+ *  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 "http_server_handlers.h"
+#include "esp_log.h"
+#include "esp_http_server.h"
+#include "_esp_http_server.h"
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "esp_system.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "config.h"
+#include "messaging.h"
+static const char TAG[] = "http_server";
+
+static httpd_handle_t _server = NULL;
+rest_server_context_t *rest_context = NULL;
+RingbufHandle_t messaging=NULL;
+
+void register_common_handlers(httpd_handle_t server){
+	httpd_uri_t res_get = { .uri = "/res/*", .method = HTTP_GET, .handler = resource_filehandler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &res_get);
+
+}
+void register_regular_handlers(httpd_handle_t server){
+	httpd_uri_t root_get = { .uri = "/", .method = HTTP_GET, .handler = root_get_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &root_get);
+
+	httpd_uri_t ap_get = { .uri = "/ap.json", .method = HTTP_GET, .handler = ap_get_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &ap_get);
+	httpd_uri_t scan_get = { .uri = "/scan.json", .method = HTTP_GET, .handler = ap_scan_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &scan_get);
+	httpd_uri_t config_get = { .uri = "/config.json", .method = HTTP_GET, .handler = config_get_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &config_get);
+	httpd_uri_t status_get = { .uri = "/status.json", .method = HTTP_GET, .handler = status_get_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &status_get);
+	httpd_uri_t messages_get = { .uri = "/messages.json", .method = HTTP_GET, .handler = messages_get_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &messages_get);
+
+	httpd_uri_t config_post = { .uri = "/config.json", .method = HTTP_POST, .handler = config_post_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &config_post);
+	httpd_uri_t connect_post = { .uri = "/connect.json", .method = HTTP_POST, .handler = connect_post_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &connect_post);
+
+	httpd_uri_t reboot_ota_post = { .uri = "/reboot_ota.json", .method = HTTP_POST, .handler = reboot_ota_post_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &reboot_ota_post);
+
+	httpd_uri_t reboot_post = { .uri = "/reboot.json", .method = HTTP_POST, .handler = reboot_post_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &reboot_post);
+
+	httpd_uri_t recovery_post = { .uri = "/recovery.json", .method = HTTP_POST, .handler = recovery_post_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &recovery_post);
+
+	httpd_uri_t connect_delete = { .uri = "/connect.json", .method = HTTP_DELETE, .handler = connect_delete_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &connect_delete);
+
+#if RECOVERY_APPLICATION
+
+	httpd_uri_t flash_post = { .uri = "/flash.json", .method = HTTP_POST, .handler = flash_post_handler, .user_ctx = rest_context };
+	httpd_register_uri_handler(server, &flash_post);
+#endif
+
+
+	// from https://github.com/tripflex/wifi-captive-portal/blob/master/src/mgos_wifi_captive_portal.c
+	// https://unix.stackexchange.com/questions/432190/why-isnt-androids-captive-portal-detection-triggering-a-browser-window
+	 // Known HTTP GET requests to check for Captive Portal
+
+	///kindle-wifi/wifiredirect.html Kindle when requested with com.android.captiveportallogin
+	///kindle-wifi/wifistub.html Kindle before requesting with captive portal login window (maybe for detection?)
+
+
+	httpd_uri_t connect_redirect_1 = { .uri = "/mobile/status.php", .method = HTTP_GET, .handler = redirect_200_ev_handler, .user_ctx = rest_context };// Android 8.0 (Samsung s9+)
+	httpd_register_uri_handler(server, &connect_redirect_1);
+	httpd_uri_t connect_redirect_2 = { .uri = "/generate_204", .method = HTTP_GET, .handler = redirect_200_ev_handler, .user_ctx = rest_context };// Android
+	httpd_register_uri_handler(server, &connect_redirect_2);
+	httpd_uri_t connect_redirect_3 = { .uri = "/gen_204", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context };// Android 9.0
+	httpd_register_uri_handler(server, &connect_redirect_3);
+//	httpd_uri_t connect_redirect_4 = { .uri = "/ncsi.txt", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context };// Windows
+//	httpd_register_uri_handler(server, &connect_redirect_4);
+	httpd_uri_t connect_redirect_5 = { .uri = "/hotspot-detect.html", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context }; // iOS 8/9
+	httpd_register_uri_handler(server, &connect_redirect_5);
+	httpd_uri_t connect_redirect_6 = { .uri = "/library/test/success.html", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context };// iOS 8/9
+	httpd_register_uri_handler(server, &connect_redirect_6);
+	httpd_uri_t connect_redirect_7 = { .uri = "/hotspotdetect.html", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context }; // iOS
+	httpd_register_uri_handler(server, &connect_redirect_7);
+	httpd_uri_t connect_redirect_8 = { .uri = "/success.txt", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context }; // OSX
+	httpd_register_uri_handler(server, &connect_redirect_8);
+
+
+
+	ESP_LOGD(TAG,"Registering default error handler for 404");
+	httpd_register_err_handler(server, HTTPD_404_NOT_FOUND,&err_handler);
+
+}
+
+
+esp_err_t http_server_start()
+{
+	ESP_LOGI(TAG, "Initializing HTTP Server");
+	messaging = messaging_register_subscriber(10, "http_server");
+    rest_context = calloc(1, sizeof(rest_server_context_t));
+    if(rest_context==NULL){
+    	ESP_LOGE(TAG,"No memory for http context");
+    	return ESP_FAIL;
+    }
+    strlcpy(rest_context->base_path, "/res/", sizeof(rest_context->base_path));
+
+    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+    config.max_uri_handlers = 25;
+    config.max_open_sockets = 5;
+    config.uri_match_fn = httpd_uri_match_wildcard;
+    //todo:  use the endpoint below to configure session token?
+    // config.open_fn
+
+    ESP_LOGI(TAG, "Starting HTTP Server");
+    esp_err_t err= __httpd_start(&_server, &config);
+    if(err != ESP_OK){
+    	ESP_LOGE_LOC(TAG,"Start server failed");
+    }
+    else {
+
+    	register_common_handlers(_server);
+    	register_regular_handlers(_server);
+    }
+
+    return err;
+}
+
+
+/* Function to free context */
+void adder_free_func(void *ctx)
+{
+    ESP_LOGI(TAG, "/adder Free Context function called");
+    free(ctx);
+}
+
+
+void stop_webserver(httpd_handle_t server)
+{
+    // Stop the httpd server
+    httpd_stop(server);
+}
+
+
+

+ 113 - 0
eclipse_make_wrapper.py

@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+#
+# Wrapper to run make and preprocess any paths in the output from MSYS Unix-style paths
+# to Windows paths, for Eclipse
+from __future__ import print_function, division
+import sys
+import subprocess
+import os.path
+import os
+import re
+import glob
+from test import test_cmd_line
+
+#UNIX_PATH_RE = re.compile(r'(([a-zA-Z]{1}[:]{1}){0,1}[/\\][^\s\'\"\t\[\(]+)+')
+UNIX_PATH_RE = re.compile(r'(([a-zA-Z]{1}[:]{1}){0,1}[/\\][^\s\'\"\t\[\(]+(?![^\\/\n]*$)[/\\]?)')
+INCLUDE_PATH_RE = re.compile(r'-I[\s"]{0,}(.+?)["]{0,}(?=\s-\S)')
+INCLUDE_PATH_ADJ_RE = re.compile(r'^([/]opt[/]esp-idf[/]){1}(.*)')
+INCLUDE_PATH_ADJ2_RE = re.compile(r'^([/]c[/]){1}(.*)')
+
+paths = {}
+names = []
+idf_path= os.environ.get('IDF_PATH').replace("/", "\\")
+cwd_path= os.environ.get('CWD')
+pwd_path= os.environ.get('PWD')         
+         
+def check_path(path):
+    try:
+        return paths[path]
+    except KeyError:
+        pass
+    paths[path] = path
+    winpath =path
+    
+    if not os.path.exists(winpath):
+          # cache as failed, replace with success if it works
+        if re.match(INCLUDE_PATH_ADJ2_RE, path) is not None:
+                winpath = INCLUDE_PATH_ADJ2_RE.sub(r'c:/\2',path) #replace /c/ 
+        try:
+            winpath = subprocess.check_output(["cygpath", "-w", winpath]).strip()
+        except subprocess.CalledProcessError:
+            return path  # something went wrong running cygpath, assume this is not a path!
+    if not os.path.exists(winpath):
+        if not os.path.exists(winpath):
+            winpath=idf_path  + '\\' + re.sub(r'^[/\\]opt[/\\](esp-idf[/\\]){0,}', '', path, 1)
+            try:
+                winpath = subprocess.check_output(["cygpath", "-w", winpath]).strip()
+            except subprocess.CalledProcessError:
+                return path  # something went wrong running cygpath, assume this is not a path!
+            if not os.path.exists(winpath):
+                return path  # not actually a valid path
+    
+    winpath = winpath.replace("/", "\\")  # make consistent with forward-slashes used elsewhere
+    paths[path] = winpath
+    
+    
+    #print("In path: {0}, out path: {1}".format(path,winpath) )
+    return winpath
+def fix_paths(filename):
+    if re.match(r'.*[\\](.*$)',filename) is not None:
+        filename = re.findall(r'.*[\\](.*$)',filename)[0].replace("\\", "/")
+    
+    return filename.rstrip()
+
+def print_paths(path_list, file_name, source_file):
+    
+    new_path_list = list(set(path_list))
+    new_path_list.sort()
+    last_n = ''
+    cmd_line='xtensa-esp32-elf-gcc '
+    for n in new_path_list:
+        if re.match(INCLUDE_PATH_ADJ_RE, n) is not None:
+            n = INCLUDE_PATH_ADJ_RE.sub(idf_path+r"\2",n )
+        if re.match(INCLUDE_PATH_ADJ2_RE, n) is not None:
+            n = INCLUDE_PATH_ADJ2_RE.sub(r'c:/\2',n)
+        if last_n != n:
+            cmd_line = cmd_line + ' -I ' + n.rstrip()
+        last_n = n
+    if source_file:
+        cmd_line = cmd_line + ' -c ' + fix_paths(source_file)
+    cmd_line = cmd_line + ' -o ' + fix_paths(file_name)
+    print(cmd_line)
+def extract_includes():
+    
+    for filename in [y for x in os.walk('build') for y in glob.glob(os.path.join(x[0], '*.d'))]:
+        lines = []
+        source=''
+        with open(filename) as file_in:
+            for line in file_in:
+                if re.match(r'\S*(?=/[^/]*\.[h][p]?)',line) is not None:
+                    lines.extend(re.findall(r'\S*(?=/[^/]*\.[h][p]?)/',line))
+                if re.match(r'\S*(?=\.[cC][pP]{0,})[^\\\s]*',line) is not None:
+                    source = re.findall(r'\S*(?=\.[cC][pP]{0,})[^\\\s]*',line)[0]
+        
+        print_paths(lines,filename,source )
+            
+def main():
+    cwd_path=check_path(os.getcwd())
+    os.environ['CWD']= cwd_path
+    os.environ['PWD']= cwd_path
+    idf_path= os.environ.get('IDF_PATH').replace("/", "\\")
+    cwd_path= os.environ.get('CWD')
+    pwd_path= os.environ.get('PWD')
+    print('Running custom script make in {}, IDF_PATH={}, CWD={}, PWD={}'.format(cwd_path,idf_path,cwd_path,pwd_path))
+    
+    make = subprocess.Popen(["make"] + sys.argv[1:] + ["BATCH_BUILD=1"], stdout=subprocess.PIPE)
+    for line in iter(make.stdout.readline, ''):
+        line = re.sub(UNIX_PATH_RE, lambda m: check_path(m.group(0)), line)
+        names.extend(INCLUDE_PATH_RE.findall(line))
+        print(line.rstrip())
+    sys.exit(make.wait())
+
+if __name__ == "__main__":
+    main()

+ 0 - 1
main/CMakeLists.txt

@@ -3,4 +3,3 @@ idf_component_register(SRC_DIRS .
                     	INCLUDE_DIRS .
                     	EMBED_FILES ../server_certs/github.pem
                     	)
-                    	

+ 2 - 5
main/component.mk

@@ -6,10 +6,7 @@
 # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
 # please read the SDK documents if you need to do this.
 #
-#CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG
 CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO -DMODEL_NAME=SqueezeESP32
-COMPONENT_ADD_INCLUDEDIRS += 	$(COMPONENT_PATH)/../tools	\
-								$(COMPONENT_PATH)/../config
-COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/components/tools/
 LDFLAGS += -s
-COMPONENT_EMBED_TXTFILES :=  ${PROJECT_PATH}/server_certs/github.pem
+COMPONENT_EMBED_TXTFILES :=  ${PROJECT_PATH}/server_certs/github.pem
+COMPONENT_ADD_INCLUDEDIRS := .  

+ 15 - 10
main/esp_app_main.c

@@ -18,7 +18,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
-#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+
 #include "platform_esp32.h"
 #include "led.h"
 #include <stdio.h>
@@ -40,7 +40,6 @@
 #include "lwip/err.h"
 #include "lwip/netdb.h"
 #include "nvs_utilities.h"
-#include "http_server.h"
 #include "trace.h"
 #include "wifi_manager.h"
 #include "squeezelite-ota.h"
@@ -48,11 +47,12 @@
 #include "platform_config.h"
 #include "audio_controls.h"
 #include "telnet.h"
+#include "messaging.h"
 
 static const char certs_namespace[] = "certificates";
 static const char certs_key[] = "blob";
 static const char certs_version[] = "version";
-
+const char unknown_string_placeholder[] = "unknown";
 EventGroupHandle_t wifi_event_group;
 
 bool bypass_wifi_manager=false;
@@ -70,7 +70,9 @@ extern const uint8_t server_cert_pem_end[] asm("_binary_github_pem_end");
 // as an exception _init function don't need include
 extern void services_init(void);
 extern void	display_init(char *welcome);
+
 bool is_recovery_running;
+const char * str_or_unknown(const char * str) { return (str?str:unknown_string_placeholder); }
 
 /* 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){
@@ -87,12 +89,14 @@ void cb_connection_got_ip(void *pvParameter){
 	}
 	ip.addr = ipInfo.ip.addr;
 	ESP_LOGI(TAG, "I have a connection!");
+	messaging_post_message(MESSAGING_INFO,MESSAGING_CLASS_SYSTEM,"Wifi connected");
 	xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
 	bWifiConnected=true;
 	led_unpush(LED_GREEN);
 }
 void cb_connection_sta_disconnected(void *pvParameter){
 	led_blink_pushed(LED_GREEN, 250, 250);
+	messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Wifi disconnected");
 	bWifiConnected=false;
 	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
 }
@@ -168,7 +172,7 @@ esp_err_t update_certificates(){
 	if ( (esp_err= nvs_get_str(handle, certs_version, NULL, &len)) == ESP_OK) {
 		str=(char *)malloc(len);
 		if ( (esp_err = nvs_get_str(handle,  certs_version, str, &len)) == ESP_OK) {
-			printf("String associated with key '%s' is %s \n", certs_version, str);
+			ESP_LOGI(TAG,"String associated with key '%s' is %s", certs_version, str);
 		}
 	}
 	if(str!=NULL){
@@ -374,17 +378,15 @@ void app_main()
 	wifi_event_group = xEventGroupCreate();
 	ESP_LOGD(TAG,"Clearing CONNECTED_BIT from wifi group");
 	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
-	
 
 	ESP_LOGI(TAG,"Registering default values");
 	register_default_nvs();
 
-	ESP_LOGD(TAG,"Configuring services");
+	ESP_LOGI(TAG,"Configuring services");
 	services_init();
 
-	ESP_LOGD(TAG,"Initializing display");	
+	ESP_LOGI(TAG,"Initializing display");
 	display_init("SqueezeESP32");
-
 	if(!is_recovery_running){
 		ESP_LOGI(TAG,"Checking if certificates need to be updated");
 		update_certificates();
@@ -418,10 +420,12 @@ void app_main()
 	led_blink(LED_GREEN, 250, 250);
 
 	if(bypass_wifi_manager){
-		ESP_LOGW(TAG,"\n\nwifi manager is disabled. Please use wifi commands to connect to your wifi access point.\n\n");
+		ESP_LOGW(TAG,"*******************************************************************************************");
+		ESP_LOGW(TAG,"* wifi manager is disabled. Please use wifi commands to connect to your wifi access point.");
+		ESP_LOGW(TAG,"*******************************************************************************************");
 	}
 	else {
-		ESP_LOGW(TAG,"\n\nwifi manager is ENABLED. Starting...\n\n");
+		ESP_LOGI(TAG,"Starting Wifi Manager");
 		wifi_manager_start();
 		wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_got_ip);
 		wifi_manager_set_callback(EVENT_STA_DISCONNECTED, &cb_connection_sta_disconnected);
@@ -445,4 +449,5 @@ void app_main()
 		}
 		free(fwurl);
 	}
+	messaging_post_message(MESSAGING_INFO,MESSAGING_CLASS_SYSTEM,"System started");
 }

+ 1 - 1
sdkconfig.defaults

@@ -480,7 +480,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y
 CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
 
-CONFIG_HTTPD_MAX_REQ_HDR_LEN=512
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
 CONFIG_HTTPD_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32