Prechádzať zdrojové kódy

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 rokov pred
rodič
commit
39058213fa
53 zmenil súbory, kde vykonal 4303 pridanie a 1432 odobranie
  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
 /cdump.cmd
 /_*
 /_*
 squeezelite-esp32-jsonblob.zip
 squeezelite-esp32-jsonblob.zip
+/flash_cmd.txt
+/writeSequeezeEsp.bat
+/writeSequeezeEsp.sh

+ 2 - 2
.gitmodules

@@ -2,6 +2,6 @@
 	path = components/telnet/libtelnet
 	path = components/telnet/libtelnet
 	url = https://github.com/seanmiddleditch/libtelnet
 	url = https://github.com/seanmiddleditch/libtelnet
 	branch = develop
 	branch = develop
-[submodule "esp-dsp"]
+[submodule "components/esp-dsp"]
 	path = 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"?>
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?eclipse-pydev version="1.0"?><pydev_project>
 <?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_INTERPRETER">Default</pydev_property>
-        
     <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
     <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
-    
 </pydev_project>
 </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: PROJECT_NAME:=recovery.$(PROJECT_CONFIG_TARGET)
-#recovery: CPPFLAGS+=-DRECOVERY_APPLICATION=1
+#recovery: EXTRA_CPPFLAGS+=-DRECOVERY_APPLICATION=1
 
 
 PROJECT_NAME?=squeezelite
 PROJECT_NAME?=squeezelite
-EXTRA_COMPONENT_DIRS := esp-dsp
+EXTRA_CPPFLAGS+=  -I$(PROJECT_PATH)/main 
+
+#/-Wno-error=maybe-uninitialized 
 include $(IDF_PATH)/make/project.mk 
 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_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=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_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 CONFIG_FREERTOS_CORETIMER_0=y
 
 
 CONFIG_FREERTOS_HZ=100
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=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_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=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_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -594,7 +594,7 @@ CONFIG_FMB_TIMER_INDEX=0
 
 
 CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 CONFIG_FREERTOS_CORETIMER_0=y
-
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_HZ=100
 CONFIG_FREERTOS_HZ=100
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
 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_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=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_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -597,6 +597,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 CONFIG_FREERTOS_CORETIMER_0=y
 
 
 CONFIG_FREERTOS_HZ=100
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=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_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=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_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 CONFIG_FREERTOS_CORETIMER_0=y
 
 
 CONFIG_FREERTOS_HZ=100
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=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_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=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_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 CONFIG_FREERTOS_CORETIMER_0=y
 
 
 CONFIG_FREERTOS_HZ=100
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=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_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=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_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
 CONFIG_HTTPD_PURGE_BUF_LEN=32
@@ -590,6 +590,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
 CONFIG_FREERTOS_CORETIMER_0=y
 CONFIG_FREERTOS_CORETIMER_0=y
 
 
 CONFIG_FREERTOS_HZ=100
 CONFIG_FREERTOS_HZ=100
+CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=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-tremor.a
 	#$(COMPONENT_PATH)/lib/libesp-ogg-container.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);
 	ESP_LOGD(TAG, "Deleting nvs entry for [%s]", key);
 	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
 	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
 		ESP_LOGE(TAG, "Unable to lock config for delete");
 		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);
 	esp_err_t err = nvs_open_from_partition(settings_partition, current_namespace, NVS_READWRITE, &nvs);
 	if (err == ESP_OK) {
 	if (err == ESP_OK) {
@@ -663,7 +663,7 @@ char * config_alloc_get_json(bool bFormatted){
 	config_unlock();
 	config_unlock();
 	return json_buffer;
 	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;
 	esp_err_t result = ESP_OK;
 	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
 	if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
 			ESP_LOGE(TAG, "Unable to lock config after %d ms",LOCK_MAX_WAIT);
 			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 <string.h>
 #include "nvs.h"
 #include "nvs.h"
 #include "assert.h"
 #include "assert.h"
+#include "cJSON.h"
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
@@ -12,7 +13,9 @@ extern "C" {
 #endif
 #endif
 #define DECLARE_SET_DEFAULT(t) void config_set_default_## t (const char *key, t  value);
 #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);
 #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(uint8_t);
 DECLARE_SET_DEFAULT(uint16_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) ;
 void * config_alloc_get(nvs_type_t nvs_type, const char *key) ;
 bool wait_for_commit();
 bool wait_for_commit();
 char * config_alloc_get_json(bool bFormatted);
 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 "platform_config.h"
 #include "esp_sleep.h"
 #include "esp_sleep.h"
 #include "driver/uart.h"            // for the uart driver access
 #include "driver/uart.h"            // for the uart driver access
+#include "messaging.h"				  
 
 
 #ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
 #ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
 #define WITH_TASKS_INFO 1
 #define WITH_TASKS_INFO 1
@@ -105,8 +106,8 @@ if(is_recovery_running){
 		if(!wait_for_commit()){
 		if(!wait_for_commit()){
 			ESP_LOGW(TAG,"Unable to commit configuration. ");
 			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();
 		esp_restart();
 		return ESP_OK;
 		return ESP_OK;
 	}
 	}
@@ -117,8 +118,8 @@ else {
 		if(!wait_for_commit()){
 		if(!wait_for_commit()){
 			ESP_LOGW(TAG,"Unable to commit configuration. ");
 			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();
 		esp_restart();
 		return ESP_OK;
 		return ESP_OK;
 	}
 	}
@@ -131,7 +132,7 @@ else {
 
 
 	if(it == NULL){
 	if(it == NULL){
 		ESP_LOGE(TAG,"Unable initialize partition iterator!");
 		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
 	else
 	{
 	{
@@ -145,18 +146,19 @@ else {
 			if(err!=ESP_OK){
 			if(err!=ESP_OK){
 				ESP_LOGE(TAG,"Unable to set partition as active for next boot. %s",esp_err_to_name(err));
 				ESP_LOGE(TAG,"Unable to set partition as active for next boot. %s",esp_err_to_name(err));
 				bFound=false;
 				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{
 			else{
 				ESP_LOGW(TAG, "Application partition %s sub type %u is selected for boot", partition->label,partition_subtype);
 				ESP_LOGW(TAG, "Application partition %s sub type %u is selected for boot", partition->label,partition_subtype);
 				bFound=true;
 				bFound=true;
-				set_status_message(WARNING, "Rebooting!");
+				messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Reboot failed. Cannot iterate through partitions");
 			}
 			}
 		}
 		}
 		else
 		else
 		{
 		{
 			ESP_LOGE(TAG,"partition type %u not found!  Unable to reboot to recovery.",partition_subtype);
 			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");
 		ESP_LOGD(TAG, "Yielding to other processes");
 		taskYIELD();
 		taskYIELD();
@@ -165,8 +167,7 @@ else {
 			if(!wait_for_commit()){
 			if(!wait_for_commit()){
 				ESP_LOGW(TAG,"Unable to commit configuration. ");
 				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();
 			esp_restart();
 		}
 		}
 	}
 	}
@@ -180,8 +181,7 @@ static int restart(int argc, char **argv)
 	if(!wait_for_commit()){
 	if(!wait_for_commit()){
 		ESP_LOGW(TAG,"Unable to commit configuration. ");
 		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();
     esp_restart();
     return 0;
     return 0;
 }
 }
@@ -193,8 +193,8 @@ void simple_restart()
 		ESP_LOGW(TAG,"Unable to commit configuration. ");
 		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();
     esp_restart();
 }
 }
 
 

+ 80 - 49
components/platform_console/platform_console.c

@@ -28,6 +28,15 @@
 #include "wifi_manager.h"
 #include "wifi_manager.h"
 
 
 #include "platform_config.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;
 pthread_t thread_console;
 static void * console_thread();
 static void * console_thread();
 void console_start();
 void console_start();
@@ -152,9 +161,24 @@ void initialize_console() {
 }
 }
 
 
 void console_start() {
 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 */
 	/* Register commands */
 	esp_console_register_help_command();
 	esp_console_register_help_command();
 	register_system();
 	register_system();
@@ -167,53 +191,59 @@ void console_start() {
 		register_ota_cmd();
 		register_ota_cmd();
 	}
 	}
 	register_i2ctools();
 	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){
 void run_command(char * line){
 	/* Try to run the command */
 	/* Try to run the command */
@@ -221,15 +251,16 @@ void run_command(char * line){
 	esp_err_t err = esp_console_run(line, &ret);
 	esp_err_t err = esp_console_run(line, &ret);
 
 
 	if (err == ESP_ERR_NOT_FOUND) {
 	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) {
 	} else if (err == ESP_ERR_INVALID_ARG) {
 		// command was empty
 		// command was empty
 	} else if (err == ESP_OK && ret != ESP_OK) {
 	} 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));
 				esp_err_to_name(err));
 	} else if (err != ESP_OK) {
 	} 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() {
 static void * console_thread() {
 	if(!is_recovery_running){
 	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.
 # 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/	
 	-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);
 	if (!strcasecmp(code, "asar")) metadata->artist = strndup(buf, len);
 	else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len);
 	else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len);
 	else if (!strcasecmp(code, "minm")) metadata->title = 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);
 	NFREE(metadata->remote_title);
 }
 }
 
 
-
/*----------------------------------------------------------------------------*/
+
+/*----------------------------------------------------------------------------*/
 
 
 int _fprintf(FILE *file, ...)
 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/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  *
  */
  */
-//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
 #include <stdlib.h>
 #include <stdlib.h>
 #include <string.h>
 #include <string.h>
 #include <unistd.h>
 #include <unistd.h>

+ 1 - 0
components/services/component.mk

@@ -9,3 +9,4 @@
 
 
 COMPONENT_SRCDIRS := . 
 COMPONENT_SRCDIRS := . 
 COMPONENT_ADD_INCLUDEDIRS := .
 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 "globdefs.h"
 #include "platform_config.h"
 #include "platform_config.h"
 #include "accessors.h"
 #include "accessors.h"
+#include "messaging.h"
+#include "cJSON.h"
+#include "trace.h"
 
 
 #define MONITOR_TIMER	(10*1000)
 #define MONITOR_TIMER	(10*1000)
 #define SCRATCH_SIZE	256
 #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 
 #ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY 
 	static struct {
 	static struct {
 		TaskStatus_t *tasks;
 		TaskStatus_t *tasks;
 		uint32_t total, n;
 		uint32_t total, n;
 	} current, previous;
 	} current, previous;
-	
+	cJSON * tlist=cJSON_CreateArray();
 	current.n = uxTaskGetNumberOfTasks();
 	current.n = uxTaskGetNumberOfTasks();
 	current.tasks = malloc( current.n * sizeof( TaskStatus_t ) );
 	current.tasks = malloc( current.n * sizeof( TaskStatus_t ) );
 	current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total );
 	current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total );
+	cJSON_AddNumberToObject(top,"ntasks",current.n);
 	
 	
 	static EXT_RAM_ATTR char scratch[SCRATCH_SIZE];
 	static EXT_RAM_ATTR char scratch[SCRATCH_SIZE];
 	*scratch = '\0';
 	*scratch = '\0';
@@ -68,6 +72,15 @@ static void task_stats( void ) {
 																		   current.tasks[i].eCurrentState,
 																		   current.tasks[i].eCurrentState,
 																		   100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed, 
 																		   100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed, 
 																		   current.tasks[i].usStackHighWaterMark);
 																		   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) {
 				if (i % 3 == 2 || i == current.n - 1) {
 					ESP_LOGI(TAG, "%s", scratch);
 					ESP_LOGI(TAG, "%s", scratch);
 					n = 0;
 					n = 0;
@@ -79,13 +92,21 @@ static void task_stats( void ) {
 #else
 #else
 	for (int i = 0, n = 0; i < current.n; i ++) {
 	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);
 		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) {
 		if (i % 3 == 2 || i == current.n - 1) {
 			ESP_LOGI(TAG, "%s", scratch);
 			ESP_LOGI(TAG, "%s", scratch);
 			n = 0;
 			n = 0;
 		}	
 		}	
 	}
 	}
 #endif	
 #endif	
-	
+	cJSON_AddItemToObject(top,"tasks",tlist);
 	if (previous.tasks) free(previous.tasks);
 	if (previous.tasks) free(previous.tasks);
 	previous = current;
 	previous = current;
 #endif	
 #endif	
@@ -95,13 +116,26 @@ static void task_stats( void ) {
  * 
  * 
  */
  */
 static void monitor_callback(TimerHandle_t xTimer) {
 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)", 
 	ESP_LOGI(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu)", 
 			heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
 			heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
 			heap_caps_get_minimum_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_free_size(MALLOC_CAP_SPIRAM),
 			heap_caps_get_minimum_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) {
 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");
 	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);
 	if (jack_handler_svc) (*jack_handler_svc)(event == BUTTON_PRESSED);
 }
 }
 
 

+ 2 - 0
components/services/services.c

@@ -16,6 +16,7 @@
 #include "monitor.h"
 #include "monitor.h"
 #include "globdefs.h"
 #include "globdefs.h"
 #include "accessors.h"
 #include "accessors.h"
+#include "messaging.h"
 
 
 extern void battery_svc_init(void);
 extern void battery_svc_init(void);
 extern void monitor_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) {
 void services_init(void) {
+	messaging_service_init();
 	gpio_install_isr_service(0);
 	gpio_install_isr_service(0);
 	
 	
 #ifdef CONFIG_I2C_LOCKED
 #ifdef CONFIG_I2C_LOCKED

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

@@ -5,9 +5,6 @@
 
 
 # todo: add support for https
 # todo: add support for https
 COMPONENT_ADD_INCLUDEDIRS := .
 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 += -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
    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    CONDITIONS OF ANY KIND, either express or implied.
    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/FreeRTOS.h"
 #include "freertos/task.h"
 #include "freertos/task.h"
 #include "esp_system.h"
 #include "esp_system.h"
@@ -23,46 +20,69 @@
 #include "esp_err.h"
 #include "esp_err.h"
 #include "tcpip_adapter.h"
 #include "tcpip_adapter.h"
 #include "squeezelite-ota.h"
 #include "squeezelite-ota.h"
-#include "platform_config.h"
-#include "platform_esp32.h"
+#include "config.h"
 #include <time.h>
 #include <time.h>
 #include <sys/time.h>
 #include <sys/time.h>
 #include <stdarg.h>
 #include <stdarg.h>
 #include "esp_secure_boot.h"
 #include "esp_secure_boot.h"
 #include "esp_flash_encrypt.h"
 #include "esp_flash_encrypt.h"
 #include "esp_spi_flash.h"
 #include "esp_spi_flash.h"
-#include "platform_esp32.h"
 #include "sdkconfig.h"
 #include "sdkconfig.h"
-
+#include "messaging.h"
+#include "trace.h"
 #include "esp_ota_ops.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();
 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";
 static const char *TAG = "squeezelite-ota";
-char * ota_write_data = NULL;
 esp_http_client_handle_t ota_http_client = 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 IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1
 #define BUFFSIZE 4096
 #define BUFFSIZE 4096
 #define HASH_LEN 32 /* SHA-256 digest length */
 #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 {
 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 bOTAStarted;
-	bool bInitialized;
+	size_t buffer_size;
 	uint8_t lastpct;
 	uint8_t lastpct;
 	uint8_t newpct;
 	uint8_t newpct;
 	struct timeval OTA_start;
 	struct timeval OTA_start;
 	bool bOTAThreadStarted;
 	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;
 } 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(){
 void _printMemStats(){
 	ESP_LOGD(TAG,"Heap internal:%zu (min:%zu) external:%zu (min:%zu)",
 	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_free_size(MALLOC_CAP_SPIRAM),
 			heap_caps_get_minimum_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 {
 	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)
 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)
 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:
     case HTTP_EVENT_ON_CONNECTED:
         ESP_LOGD(TAG, "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.lastpct=0;
+		ota_status.remain_image_len=0;
 		ota_status.newpct=0;
 		ota_status.newpct=0;
 		gettimeofday(&ota_status.OTA_start, NULL);
 		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:
     case HTTP_EVENT_ON_HEADER:
         ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s",evt->header_key, evt->header_value);
         ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s",evt->header_key, evt->header_value);
 		if (strcasecmp(evt->header_key, "location") == 0) {
 		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) {
         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;
         break;
     case HTTP_EVENT_ON_DATA:
     case HTTP_EVENT_ON_DATA:
@@ -176,29 +281,49 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt)
     return ESP_OK;
     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;
 	return ESP_OK;
 }
 }
 esp_partition_t * _get_ota_partition(esp_partition_subtype_t subtype){
 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 num_passes=0;
 	uint16_t remain_size=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);
 		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);
 		err=esp_partition_erase_range(ota_partition, i*single_pass_size, single_pass_size);
 		if(err!=ESP_OK) return err;
 		if(err!=ESP_OK) return err;
-//		triggerStatusJsonRefresh(i%10==0?true:false,"Erasing flash (%u/%u)",i,num_passes);
 		if(i%2) {
 		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){
 	if(remain_size>0){
 		err=esp_partition_erase_range(ota_partition, ota_partition->size-remain_size, remain_size);
 		err=esp_partition_erase_range(ota_partition, ota_partition->size-remain_size, remain_size);
+
 		if(err!=ESP_OK) return err;
 		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;
 	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)
 static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client, int status_code)
 {
 {
     esp_err_t err=ESP_OK;
     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. ");
     	ESP_LOGW(TAG, "Handling HTTP redirection. ");
         err = esp_http_client_set_redirection(http_client);
         err = esp_http_client_set_redirection(http_client);
         if (err != ESP_OK) {
         if (err != ESP_OK) {
             ESP_LOGE(TAG, "URL redirection Failed. %s", esp_err_to_name(err));
             ESP_LOGE(TAG, "URL redirection Failed. %s", esp_err_to_name(err));
             return err;
             return err;
         }
         }
+        ESP_LOGW(TAG, "Done Handling HTTP redirection. ");
+
     } else if (status_code == HttpStatus_Unauthorized) {
     } else if (status_code == HttpStatus_Unauthorized) {
     	ESP_LOGW(TAG, "Handling Unauthorized. ");
     	ESP_LOGW(TAG, "Handling Unauthorized. ");
         esp_http_client_add_auth(http_client);
         esp_http_client_add_auth(http_client);
     }
     }
     ESP_LOGD(TAG, "Redirection done, checking if we need to read the data. ");
     ESP_LOGD(TAG, "Redirection done, checking if we need to read the data. ");
     if (process_again(status_code)) {
     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) {
         while (1) {
         	ESP_LOGD(TAG, "Reading data chunk. ");
         	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) {
             if (data_read < 0) {
                 ESP_LOGE(TAG, "Error: SSL data read error");
                 ESP_LOGE(TAG, "Error: SSL data read error");
                 err= ESP_FAIL;
                 err= ESP_FAIL;
@@ -319,7 +450,7 @@ static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client
             	break;
             	break;
             }
             }
         }
         }
-        FREE_RESET(local_buff);
+        //FREE_RESET(local_buff);
     }
     }
 
 
     return err;
     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;
     esp_err_t err = ESP_FAIL;
     int status_code, header_ret;
     int status_code, header_ret;
     do {
     do {
-    	ESP_LOGD(TAG, "connecting the http client. ");
+    	ESP_LOGI(TAG, "connecting the http client. ");
         err = esp_http_client_open(http_client, 0);
         err = esp_http_client_open(http_client, 0);
         if (err != ESP_OK) {
         if (err != ESP_OK) {
             ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
             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;
             return err;
         }
         }
-        ESP_LOGD(TAG, "Fetching headers");
+        ESP_LOGI(TAG, "Fetching headers");
         header_ret = esp_http_client_fetch_headers(http_client);
         header_ret = esp_http_client_fetch_headers(http_client);
         if (header_ret < 0) {
         if (header_ret < 0) {
         	// Error found
         	// Error found
+            sendMessaging(MESSAGING_ERROR,"Header fetch failed");
             return header_ret;
             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);
         status_code = esp_http_client_get_status_code(http_client);
         ESP_LOGD(TAG, "HTTP status code was %d",status_code);
         ESP_LOGD(TAG, "HTTP status code was %d",status_code);
 
 
-
-
         err = _http_handle_response_code(http_client, status_code);
         err = _http_handle_response_code(http_client, status_code);
         if (err != ESP_OK) {
         if (err != ESP_OK) {
+            sendMessaging(MESSAGING_ERROR,"HTTP connect error: %s", esp_err_to_name(err));
             return err;
             return err;
         }
         }
+
     } while (process_again(status_code));
     } 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;
     return err;
 }
 }
 void ota_task_cleanup(const char * message, ...){
 void ota_task_cleanup(const char * message, ...){
 	ota_status.bOTAThreadStarted=false;
 	ota_status.bOTAThreadStarted=false;
+	loc_displayer_progressbar(0);
 	if(message!=NULL){
 	if(message!=NULL){
-
 	    va_list args;
 	    va_list args;
 	    va_start(args, message);
 	    va_start(args, message);
-		triggerStatusJsonRefresh(true,message, args);
+		sendMessaging(MESSAGING_ERROR,message, args);
 	    va_end(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) {
 	if(ota_http_client!=NULL) {
 		esp_http_client_cleanup(ota_http_client);
 		esp_http_client_cleanup(ota_http_client);
 		ota_http_client=NULL;
 		ota_http_client=NULL;
@@ -374,24 +511,120 @@ void ota_task_cleanup(const char * message, ...){
 	ota_status.bOTAStarted = false;
 	ota_status.bOTAStarted = false;
 	task_fatal_error();
 	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)
 void ota_task(void *pvParameter)
 {
 {
 	esp_err_t err = ESP_OK;
 	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");
 	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();
     _printMemStats();
 
 
+    ota_status.update_partition = esp_ota_get_next_update_partition(NULL);
 
 
 	ESP_LOGI(TAG,"Initializing OTA configuration");
 	ESP_LOGI(TAG,"Initializing OTA configuration");
 	err = init_config(pvParameter);
 	err = init_config(pvParameter);
@@ -400,139 +633,96 @@ void ota_task(void *pvParameter)
 		return;
 		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 */
 	/* Locate and erase ota application partition */
 	ESP_LOGW(TAG,"****************  Expecting WATCHDOG errors below during flash erase. This is OK and not to worry about **************** ");
 	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();
 	_printMemStats();
-	err=_erase_last_boot_app_partition(ota_partition);
+	err=_erase_last_boot_app_partition(ota_status.ota_partition);
 	if(err!=ESP_OK){
 	if(err!=ESP_OK){
 		ota_task_cleanup("Error: Unable to erase last APP partition. (%s)",esp_err_to_name(err));
 		ota_task_cleanup("Error: Unable to erase last APP partition. (%s)",esp_err_to_name(err));
 		return;
 		return;
 	}
 	}
-
+	loc_displayer_progressbar(0);
 	_printMemStats();
 	_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 ;
     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");
             ota_task_cleanup("Error: Data read error");
             return;
             return;
         } else if (data_read > 0) {
         } 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) {
             if (err != ESP_OK) {
                 ota_task_cleanup("Error: OTA Partition write failure. (%s)",esp_err_to_name(err));
                 ota_task_cleanup("Error: OTA Partition write failure. (%s)",esp_err_to_name(err));
                 return;
                 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_get_pct_complete()%5 == 0) ota_status.newpct = ota_get_pct_complete();
 			if(ota_status.lastpct!=ota_status.newpct ) {
 			if(ota_status.lastpct!=ota_status.newpct ) {
+				loc_displayer_progressbar(ota_status.newpct);
 				gettimeofday(&tv, NULL);
 				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;
 				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;
 				ota_status.lastpct=ota_status.newpct;
 			}
 			}
 			taskYIELD();
 			taskYIELD();
 
 
         } else if (data_read == 0) {
         } else if (data_read == 0) {
-            ESP_LOGI(TAG, "Connection closed");
+            ESP_LOGI(TAG, "End of OTA data stream");
             break;
             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");
         ota_task_cleanup("Error: Error in receiving complete file");
         return;
         return;
     }
     }
     _printMemStats();
     _printMemStats();
-
+    loc_displayer_progressbar(100);
     err = esp_ota_end(update_handle);
     err = esp_ota_end(update_handle);
     if (err != ESP_OK) {
     if (err != ESP_OK) {
         ota_task_cleanup("Error: %s",esp_err_to_name(err));
         ota_task_cleanup("Error: %s",esp_err_to_name(err));
         return;
         return;
      }
      }
     _printMemStats();
     _printMemStats();
-    err = esp_ota_set_boot_partition(ota_partition);
+    err = esp_ota_set_boot_partition(ota_status.ota_partition);
     if (err == ESP_OK) {
     if (err == ESP_OK) {
     	ESP_LOGI(TAG,"OTA Process completed successfully!");
     	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
     	vTaskDelay(1500/ portTICK_PERIOD_MS);  // wait here to give the UI a chance to refresh
+    	GDS_Clear(display,GDS_COLOR_BLACK);
         esp_restart();
         esp_restart();
     } else {
     } else {
         ota_task_cleanup("Error: Unable to update boot partition [%s]",esp_err_to_name(err));
         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;
     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;
 	int ret = 0;
 	uint16_t stack_size, task_priority;
 	uint16_t stack_size, task_priority;
     if(ota_status.bOTAThreadStarted){
     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));
 	memset(&ota_status, 0x00, sizeof(ota_status));
 	ota_status.bOTAThreadStarted=true;
 	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");
     char * num_buffer=config_alloc_get(NVS_TYPE_STR, "ota_stack");
   	if(num_buffer!=NULL) {
   	if(num_buffer!=NULL) {
   		stack_size= atol(num_buffer);
   		stack_size= atol(num_buffer);
-  		free(num_buffer);
-  		num_buffer=NULL;
+  		FREE_AND_NULL(num_buffer);
   	}
   	}
   	else {
   	else {
 		ESP_LOGW(TAG,"OTA stack size config not found");
 		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");
   	num_buffer=config_alloc_get(NVS_TYPE_STR, "ota_prio");
 	if(num_buffer!=NULL) {
 	if(num_buffer!=NULL) {
 		task_priority= atol(num_buffer);
 		task_priority= atol(num_buffer);
-		free(num_buffer);
-		num_buffer=NULL;
+		FREE_AND_NULL(num_buffer);
 	}
 	}
 	else {
 	else {
 		ESP_LOGW(TAG,"OTA task priority not found");
 		ESP_LOGW(TAG,"OTA task priority not found");
 		task_priority= OTA_TASK_PRIOTITY;
 		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");
   	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)  {
     if (ret != pdPASS)  {
             ESP_LOGI(TAG, "create thread %s failed", "ota_task");
             ESP_LOGI(TAG, "create thread %s failed", "ota_task");
             return ESP_FAIL;
             return ESP_FAIL;
@@ -593,10 +780,14 @@ esp_err_t process_recovery_ota(const char * bin_url){
     return ESP_OK;
     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);
 	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){
 	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");
 	ESP_LOGW(TAG, "Rebooting to recovery to complete the installation");
 	return guided_factory();
 	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();
 const char * ota_get_status();
 uint8_t ota_get_pct_complete();
 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 := . 
 COMPONENT_SRCDIRS += ./libtelnet 
 COMPONENT_SRCDIRS += ./libtelnet 
 COMPONENT_ADD_INCLUDEDIRS := .
 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 "config.h"
 #include "nvs_utilities.h"
 #include "nvs_utilities.h"
 #include "platform_esp32.h"
 #include "platform_esp32.h"
+#include "messaging.h"
+#include "trace.h"
 
 
 
 
 /************************************
 /************************************
@@ -47,15 +49,16 @@
 #define TELNET_STACK_SIZE 8048
 #define TELNET_STACK_SIZE 8048
 #define TELNET_RX_BUF 1024
 #define TELNET_RX_BUF 1024
 
 
-const static char tag[] = "telnet";
+const static char TAG[] = "telnet";
 static int uart_fd=0;
 static int uart_fd=0;
 RingbufHandle_t buf_handle;
 RingbufHandle_t buf_handle;
-SemaphoreHandle_t xSemaphore = NULL;
+//static SemaphoreHandle_t xSemaphore = NULL;
 static size_t send_chunk=300;
 static size_t send_chunk=300;
 static size_t log_buf_size=2000;      //32-bit aligned size
 static size_t log_buf_size=2000;      //32-bit aligned size
 static bool bIsEnabled=false;
 static bool bIsEnabled=false;
 static int partnerSocket=0;
 static int partnerSocket=0;
 static telnet_t *tnHandle;
 static telnet_t *tnHandle;
+extern bool bypass_wifi_manager;
 
 
 /************************************
 /************************************
  * Forward declarations
  * 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 ssize_t stdout_write(int fd, const void * data, size_t size);
 static char *eventToString(telnet_event_type_t type);
 static char *eventToString(telnet_event_type_t type);
 static void handle_telnet_conn();
 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 {
 struct telnetUserData {
 	int sockfd;
 	int sockfd;
 	telnet_t *tnHandle;
 	telnet_t *tnHandle;
 	char * rxbuf;
 	char * rxbuf;
 };
 };
 
 
-
+bool is_serial_suppressed(){
+	return bIsEnabled?!bMirrorToUART:false ;
+}
 void init_telnet(){
 void init_telnet(){
 	char *val= get_nvs_value_alloc(NVS_TYPE_STR, "telnet_enable");
 	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);
 		if(val) free(val);
 		return;
 		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");
 	val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block");
 	if(val){
 	if(val){
 		send_chunk=atol(val);
 		send_chunk=atol(val);
@@ -97,18 +112,21 @@ void init_telnet(){
 		log_buf_size=log_buf_size>0?log_buf_size:4000;
 		log_buf_size=log_buf_size>0?log_buf_size:4000;
 	}
 	}
 	// Create the semaphore to guard a shared resource.
 	// Create the semaphore to guard a shared resource.
-	vSemaphoreCreateBinary( xSemaphore );
+	//vSemaphoreCreateBinary( xSemaphore );
 
 
 	// Redirect the output to our telnet handler as soon as possible
 	// 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);
 	buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct);
 	if (buf_handle == NULL) {
 	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;
 		return;
 	}
 	}
 
 
-	ESP_LOGI(tag, "***Redirecting log output to telnet");
+	ESP_LOGI(TAG, "***Redirecting log output to telnet");
 	const esp_vfs_t vfs = {
 	const esp_vfs_t vfs = {
 			.flags = ESP_VFS_FLAG_DEFAULT,
 			.flags = ESP_VFS_FLAG_DEFAULT,
 			.write = &stdout_write,
 			.write = &stdout_write,
@@ -116,8 +134,12 @@ void init_telnet(){
 			.fstat = &stdout_fstat,
 			.fstat = &stdout_fstat,
 			.close = &stdout_close,
 			.close = &stdout_close,
 			.read = &stdout_read,
 			.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));
 	ESP_ERROR_CHECK(esp_vfs_register("/dev/pkspstdout", &vfs, NULL));
 	freopen("/dev/pkspstdout", "w", stdout);
 	freopen("/dev/pkspstdout", "w", stdout);
 	freopen("/dev/pkspstdout", "w", stderr);
 	freopen("/dev/pkspstdout", "w", stderr);
@@ -125,11 +147,11 @@ void init_telnet(){
 }
 }
 void start_telnet(void * pvParameter){
 void start_telnet(void * pvParameter){
 	static bool isStarted=false;
 	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) {
 	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;
 		isStarted=true;
 	}
 	}
 }
 }
@@ -142,13 +164,15 @@ static void telnet_task(void *data) {
 
 
 	int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
 	int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
 	if (rc < 0) {
 	if (rc < 0) {
-		ESP_LOGE(tag, "bind: %d (%s)", errno, strerror(errno));
+		ESP_LOGE(TAG, "bind: %d (%s)", errno, strerror(errno));
+		close(serverSocket);
 		return;
 		return;
 	}
 	}
 
 
 	rc = listen(serverSocket, 5);
 	rc = listen(serverSocket, 5);
 	if (rc < 0) {
 	if (rc < 0) {
-		ESP_LOGE(tag, "listen: %d (%s)", errno, strerror(errno));
+		ESP_LOGE(TAG, "listen: %d (%s)", errno, strerror(errno));
+		close(serverSocket);
 		return;
 		return;
 	}
 	}
 
 
@@ -156,16 +180,17 @@ static void telnet_task(void *data) {
 		socklen_t len = sizeof(serverAddr);
 		socklen_t len = sizeof(serverAddr);
 		rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len);
 		rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len);
 		if (rc < 0 ){
 		if (rc < 0 ){
-			ESP_LOGE(tag, "accept: %d (%s)", errno, strerror(errno));
+			ESP_LOGE(TAG, "accept: %d (%s)", errno, strerror(errno));
 			return;
 			return;
 		}
 		}
 		else {
 		else {
 			partnerSocket = rc;
 			partnerSocket = rc;
-			ESP_LOGD(tag, "We have a new client connection!");
+			ESP_LOGD(TAG, "We have a new client connection!");
 			handle_telnet_conn();
 			handle_telnet_conn();
-			ESP_LOGD(tag, "Telnet connection terminated");
+			ESP_LOGD(TAG, "Telnet connection terminated");
 		}
 		}
 	}
 	}
+	close(serverSocket);
 	vTaskDelete(NULL);
 	vTaskDelete(NULL);
 }
 }
 
 
@@ -228,7 +253,9 @@ void process_received_data(const char * buffer, size_t size){
 	command[size]='\0';
 	command[size]='\0';
 	if(command[0]!='\r' && command[0]!='\n'){
 	if(command[0]!='\r' && command[0]!='\n'){
 		// echo the command buffer out to uart and run
 		// 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);
 		run_command((char *)command);
 	}
 	}
 	free(command);
 	free(command);
@@ -265,23 +292,23 @@ static void handle_telnet_events(
 } // myhandle_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
     //Receive an item from no-split ring buffer
 	size_t item_size;
 	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;
 		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){
 	while(uxBytesToSend>0){
 		char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(50), uxBytesToSend);
 		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;
   pTelnetUserData->sockfd = partnerSocket;
 
 
   // flush all the log buffer on connect
   // flush all the log buffer on connect
-  process_logs(0);
+  process_logs(log_buf_size, false);
 
 
   while(1) {
   while(1) {
   	//ESP_LOGD(tag, "waiting for data");
   	//ESP_LOGD(tag, "waiting for data");
@@ -339,7 +366,7 @@ static void handle_telnet_conn() {
   	  partnerSocket = 0;
   	  partnerSocket = 0;
   	  return;
   	  return;
   	}
   	}
-  	process_logs(send_chunk);
+  	process_logs(send_chunk, false);
 
 
 	taskYIELD();
 	taskYIELD();
   }
   }
@@ -348,37 +375,24 @@ static void handle_telnet_conn() {
 
 
 // ******************* stdout/stderr Redirection to ringbuffer
 // ******************* stdout/stderr Redirection to ringbuffer
 static ssize_t stdout_write(int fd, const void * data, size_t size) {
 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 {
 	} 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) {
 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) {
 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 init_telnet();
 void start_telnet(void * pvParameter);
 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 void console_start();
 extern pthread_cond_t wifi_connect_suspend_cond;
 extern pthread_cond_t wifi_connect_suspend_cond;
 extern pthread_t wifi_connect_suspend_mutex;
 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)
 #define STR(macro)  QUOTE(macro)
 #endif
 #endif
 #define ESP_LOG_DEBUG_EVENT(tag,e) ESP_LOGD(tag,"evt: " e)
 #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 releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';
 var recovery = false;
 var recovery = false;
 var enableAPTimer = true;
 var enableAPTimer = true;
@@ -19,7 +40,6 @@ var commandHeader = 'squeezelite -b 500:2000 -d all=info ';
 var pname, ver, otapct, otadsc;
 var pname, ver, otapct, otadsc;
 var blockAjax = false;
 var blockAjax = false;
 var blockFlashButton = false;
 var blockFlashButton = false;
-var lastMsg = '';
 
 
 var apList = null;
 var apList = null;
 var selectedSSID = "";
 var selectedSSID = "";
@@ -189,20 +209,29 @@ $(document).ready(function(){
     $("input#autoexec-cb").on("click", function() {
     $("input#autoexec-cb").on("click", function() {
         var data = { 'timestamp': Date.now() };
         var data = { 'timestamp': Date.now() };
         autoexec = (this.checked)?1:0;
         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({
         $.ajax({
             url: '/config.json',
             url: '/config.json',
             dataType: 'text',
             dataType: 'text',
             method: 'POST',
             method: 'POST',
             cache: false,
             cache: false,
-            headers: { "X-Custom-autoexec": autoexec },
+//            headers: { "X-Custom-autoexec": autoexec },
             contentType: 'application/json; charset=utf-8',
             contentType: 'application/json; charset=utf-8',
-            data: JSON.stringify(data),
+            data:  JSON.stringify(data),
+
             error: function (xhr, ajaxOptions, thrownError) {
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(xhr.status);
                 console.log(thrownError);
                 console.log(thrownError);
-                if (thrownError != '') showMessage(thrownError, 'ERROR');
+                if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
             },
             },
             complete: function(response) {
             complete: function(response) {
                 //var returnedResponse = JSON.parse(response.responseText);
                 //var returnedResponse = JSON.parse(response.responseText);
@@ -219,7 +248,7 @@ $(document).ready(function(){
                     error: function (xhr, ajaxOptions, thrownError) {
                     error: function (xhr, ajaxOptions, thrownError) {
                         console.log(xhr.status);
                         console.log(xhr.status);
                         console.log(thrownError);
                         console.log(thrownError);
-                        if (thrownError != '') showMessage(thrownError, 'ERROR');
+                        if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
                     },
                     },
                     complete: function(response) {
                     complete: function(response) {
                     	console.log('reboot call completed');
                     	console.log('reboot call completed');
@@ -232,20 +261,26 @@ $(document).ready(function(){
     $("input#save-autoexec1").on("click", function() {
     $("input#save-autoexec1").on("click", function() {
         var data = { 'timestamp': Date.now() };
         var data = { 'timestamp': Date.now() };
         autoexec1 = $("#autoexec1").val();
         autoexec1 = $("#autoexec1").val();
-        data['autoexec1'] = autoexec1;
+        data['config'] = {};
+        data['config'] = {
+            autoexec1 : {
+                value : autoexec1,
+                type : 33
+            }
+        }
 
 
         $.ajax({
         $.ajax({
             url: '/config.json',
             url: '/config.json',
             dataType: 'text',
             dataType: 'text',
             method: 'POST',
             method: 'POST',
             cache: false,
             cache: false,
-            headers: { "X-Custom-autoexec1": autoexec1 },
+//            headers: { "X-Custom-autoexec1": autoexec1 },
             contentType: 'application/json; charset=utf-8',
             contentType: 'application/json; charset=utf-8',
             data: JSON.stringify(data),
             data: JSON.stringify(data),
             error: function (xhr, ajaxOptions, thrownError) {
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(xhr.status);
                 console.log(thrownError);
                 console.log(thrownError);
-                if (thrownError != '') showMessage(thrownError, 'ERROR');
+                if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
             }
             }
         });
         });
         console.log('sent config JSON with headers:', autoexec1);
         console.log('sent config JSON with headers:', autoexec1);
@@ -254,15 +289,19 @@ $(document).ready(function(){
 
 
     $("input#save-gpio").on("click", function() {
     $("input#save-gpio").on("click", function() {
         var data = { 'timestamp': Date.now() };
         var data = { 'timestamp': Date.now() };
+        var config = {};
+
         var headers = {};
         var headers = {};
         $("input.gpio").each(function() {
         $("input.gpio").each(function() {
             var id = $(this)[0].id;
             var id = $(this)[0].id;
             var pin = $(this).val();
             var pin = $(this).val();
             if (pin != '') {
             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({
         $.ajax({
             url: '/config.json',
             url: '/config.json',
             dataType: 'text',
             dataType: 'text',
@@ -274,7 +313,7 @@ $(document).ready(function(){
             error: function (xhr, ajaxOptions, thrownError) {
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(xhr.status);
                 console.log(thrownError);
                 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 headers:', JSON.stringify(headers));
@@ -284,23 +323,40 @@ $(document).ready(function(){
     $("#save-nvs").on("click", function() {
     $("#save-nvs").on("click", function() {
         var headers = {};
         var headers = {};
         var data = { 'timestamp': Date.now() };
         var data = { 'timestamp': Date.now() };
+        var config = {};
         $("input.nvs").each(function() {
         $("input.nvs").each(function() {
             var key = $(this)[0].id;
             var key = $(this)[0].id;
             var val = $(this).val();
             var val = $(this).val();
+            var nvs_type = parseInt($(this)[0].attributes.nvs_type.nodeValue,10);
             if (key != '') {
             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 key = $("#nvs-new-key").val();
         var val = $("#nvs-new-value").val();
         var val = $("#nvs-new-value").val();
         if (key != '') {
         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({
         $.ajax({
             url: '/config.json',
             url: '/config.json',
             dataType: 'text',
             dataType: 'text',
@@ -308,35 +364,64 @@ $(document).ready(function(){
             cache: false,
             cache: false,
             headers: headers,
             headers: headers,
             contentType: 'application/json; charset=utf-8',
             contentType: 'application/json; charset=utf-8',
-            data: JSON.stringify(data),
+            data : JSON.stringify(data),
             error: function (xhr, ajaxOptions, thrownError) {
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(xhr.status);
                 console.log(thrownError);
                 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 headers:', JSON.stringify(headers));
         console.log('sent config JSON with data:', JSON.stringify(data));
         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() {
     $("#flash").on("click", function() {
         var data = { 'timestamp': Date.now() };
         var data = { 'timestamp': Date.now() };
         if (blockFlashButton) return;
         if (blockFlashButton) return;
         blockFlashButton = true;
         blockFlashButton = true;
         var url = $("#fwurl").val();
         var url = $("#fwurl").val();
-        data['fwurl'] = url;
+        data['config'] = {
+            fwurl : {
+            value : url,
+            type : 33
+
+            }
+        };
+
         $.ajax({
         $.ajax({
             url: '/config.json',
             url: '/config.json',
             dataType: 'text',
             dataType: 'text',
             method: 'POST',
             method: 'POST',
             cache: false,
             cache: false,
-            headers: { "X-Custom-fwurl": url },
             contentType: 'application/json; charset=utf-8',
             contentType: 'application/json; charset=utf-8',
-            data: JSON.stringify(data),
+            data:  JSON.stringify(data),
             error: function (xhr, ajaxOptions, thrownError) {
             error: function (xhr, ajaxOptions, thrownError) {
                 console.log(xhr.status);
                 console.log(xhr.status);
                 console.log(thrownError);
                 console.log(thrownError);
-                if (thrownError != '') showMessage(thrownError, 'ERROR');
+                if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
             }
             }
         });
         });
         enableStatusTimer = true;
         enableStatusTimer = true;
@@ -509,13 +594,17 @@ function performConnect(conntype){
         dataType: 'text',
         dataType: 'text',
         method: 'POST',
         method: 'POST',
         cache: false,
         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',
         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) {
         error: function (xhr, ajaxOptions, thrownError) {
             console.log(xhr.status);
             console.log(xhr.status);
             console.log(thrownError);
             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)
     $( "#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(){
 function checkStatus(){
     RepeatCheckStatusInterval();
     RepeatCheckStatusInterval();
     if (!enableStatusTimer) return;
     if (!enableStatusTimer) return;
     if (blockAjax) return;
     if (blockAjax) return;
     blockAjax = true;
     blockAjax = true;
+    getMessages();
     $.getJSON( "/status.json", function( data ) {
     $.getJSON( "/status.json", function( data ) {
         if (data.hasOwnProperty('ssid') && data['ssid'] != ""){
         if (data.hasOwnProperty('ssid') && data['ssid'] != ""){
             if (data["ssid"] === selectedSSID){
             if (data["ssid"] === selectedSSID){
@@ -659,20 +813,24 @@ function checkStatus(){
                 $("#otadiv").show();
                 $("#otadiv").show();
                 $('a[href^="#tab-audio"]').hide();
                 $('a[href^="#tab-audio"]').hide();
                 $('a[href^="#tab-gpio"]').show();
                 $('a[href^="#tab-gpio"]').show();
+                $('#uploaddiv').show();
                 $("footer.footer").removeClass('sl');
                 $("footer.footer").removeClass('sl');
                 $("footer.footer").addClass('recovery');
                 $("footer.footer").addClass('recovery');
                 $("#boot-button").html('Reboot');
                 $("#boot-button").html('Reboot');
                 $("#boot-form").attr('action', '/reboot_ota.json');
                 $("#boot-form").attr('action', '/reboot_ota.json');
+
                 enableStatusTimer = true;
                 enableStatusTimer = true;
             } else {
             } else {
                 recovery = false;
                 recovery = false;
                 $("#otadiv").hide();
                 $("#otadiv").hide();
                 $('a[href^="#tab-audio"]').show();
                 $('a[href^="#tab-audio"]').show();
                 $('a[href^="#tab-gpio"]').hide();
                 $('a[href^="#tab-gpio"]').hide();
+                $('#uploaddiv').hide();
                 $("footer.footer").removeClass('recovery');
                 $("footer.footer").removeClass('recovery');
                 $("footer.footer").addClass('sl');
                 $("footer.footer").addClass('sl');
                 $("#boot-button").html('Recovery');
                 $("#boot-button").html('Recovery');
                 $("#boot-form").attr('action', '/recovery.json');
                 $("#boot-form").attr('action', '/recovery.json');
+                
                 enableStatusTimer = false;
                 enableStatusTimer = false;
             }
             }
         }
         }
@@ -683,29 +841,10 @@ function checkStatus(){
             ver = data['version'];
             ver = data['version'];
             $("span#foot-fw").html("fw: <strong>"+ver+"</strong>, mode: <strong>"+pname+"</strong>");
             $("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('');
             $("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')) {
         if (data.hasOwnProperty('Voltage')) {
             var voltage = data['Voltage'];
             var voltage = data['Voltage'];
             var layer;
             var layer;
@@ -735,7 +874,7 @@ function checkStatus(){
     .fail(function(xhr, ajaxOptions, thrownError) {
     .fail(function(xhr, ajaxOptions, thrownError) {
         console.log(xhr.status);
         console.log(xhr.status);
         console.log(thrownError);
         console.log(thrownError);
-        if (thrownError != '') showMessage(thrownError, 'ERROR');
+        if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
         blockAjax = false;
         blockAjax = false;
     });
     });
 }
 }
@@ -770,7 +909,7 @@ function getConfig() {
                     "<tr>"+
                     "<tr>"+
                         "<td>"+key+"</td>"+
                         "<td>"+key+"</td>"+
                         "<td class='value'>"+
                         "<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>"+
                         "</td>"+
                     "</tr>"
                     "</tr>"
                 );
                 );
@@ -783,7 +922,7 @@ function getConfig() {
                     "<input type='text' class='form-control' id='nvs-new-key' placeholder='new key'>"+
                     "<input type='text' class='form-control' id='nvs-new-key' placeholder='new key'>"+
                 "</td>"+
                 "</td>"+
                 "<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>"+
                 "</td>"+
             "</tr>"
             "</tr>"
         );
         );
@@ -791,19 +930,23 @@ function getConfig() {
     .fail(function(xhr, ajaxOptions, thrownError) {
     .fail(function(xhr, ajaxOptions, thrownError) {
         console.log(xhr.status);
         console.log(xhr.status);
         console.log(thrownError);
         console.log(thrownError);
-        if (thrownError != '') showMessage(thrownError, 'ERROR');
+        if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
         blockAjax = false;
         blockAjax = false;
     });
     });
 }
 }
 
 
-function showMessage(message, severity) {
-    if (severity == 'INFO') {
+
+function showMessage(message, severity, age=0) {
+	if (severity == 'MESSAGING_INFO') {
         $('#message').css('background', '#6af');
         $('#message').css('background', '#6af');
-    } else if (severity == 'WARNING') {
+    } else if (severity == 'MESSAGING_WARNING') {
         $('#message').css('background', '#ff0');
         $('#message').css('background', '#ff0');
+    } else if (severity == 'MESSAGING_ERROR' ) {
+        $('#message').css('background', '#f00');
     } else {
     } else {
         $('#message').css('background', '#f00');
         $('#message').css('background', '#f00');
     }
     }
+    	
     $('#message').html(message);
     $('#message').html(message);
     $("#content").fadeTo("slow", 0.3, function() {
     $("#content").fadeTo("slow", 0.3, function() {
         $("#message").show(500).delay(5000).hide(500, function() {
         $("#message").show(500).delay(5000).hide(500, function() {
@@ -815,3 +958,6 @@ function showMessage(message, severity) {
 function inRange(x, min, max) {
 function inRange(x, min, max) {
     return ((x-min)*(x-max) <= 0);
     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.
 # 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
 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) {
 void  dns_server(void *pvParameters) {
-
-
-
     struct sockaddr_in sa, ra;
     struct sockaddr_in sa, ra;
 
 
     /* Set redirection DNS hijack to the access point IP */
     /* 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 charset="utf-8"/>
         <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
         <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
         <meta name="apple-mobile-web-app-capable" content="yes" />
         <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>
         <title>esp32-wifi-manager</title>
     </head>
     </head>
@@ -74,7 +67,7 @@
                 <a class="nav-link" data-toggle="tab" href="#tab-firmware">Firmware</a>
                 <a class="nav-link" data-toggle="tab" href="#tab-firmware">Firmware</a>
             </li>
             </li>
             <li class="nav-item">
             <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>
             <li class="nav-item">
             <li class="nav-item">
                 <a class="nav-link" data-toggle="tab" href="#tab-nvs">NVS editor</a>
                 <a class="nav-link" data-toggle="tab" href="#tab-nvs">NVS editor</a>
@@ -197,7 +190,7 @@
                             </div>
                             </div>
                         </div>
                         </div>
                     </div>
                     </div>
-                </div>
+                </div> <!-- wifi -->
 
 
                 <div class="tab-pane fade" id="tab-audio">
                 <div class="tab-pane fade" id="tab-audio">
                     <div id="audioout">
                     <div id="audioout">
@@ -247,72 +240,7 @@
                           <label class="custom-control-label" for="autoexec-cb"></label>
                           <label class="custom-control-label" for="autoexec-cb"></label>
                     </div>
                     </div>
                     <br />
                     <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 class="tab-pane fade" id="tab-firmware">
                     <div id="boot-div">
                     <div id="boot-div">
@@ -343,21 +271,17 @@
                     </table>
                     </table>
                     <h2>Firmware URL:</h2>
                     <h2>Firmware URL:</h2>
                     <textarea id="fwurl" maxlength="350"></textarea>
                     <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">
                     <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>
+
                     <div id="otadiv">
                     <div id="otadiv">
                         <div class="progress" id="progress">
                         <div class="progress" id="progress">
                             <div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%">
                             <div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%">
@@ -365,11 +289,48 @@
                             </div>
                             </div>
                         </div>
                         </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="tab-pane fade" id="tab-credits">
                     <div class="jumbotron">
                     <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>
                          <p>
                             This app would not be possible without the following libraries:
                             This app would not be possible without the following libraries:
                         </p>
                         </p>
@@ -388,7 +349,7 @@
 	                      <input type="checkbox" class="custom-control-input" id="show-nvs" checked="checked">
 	                      <input type="checkbox" class="custom-control-input" id="show-nvs" checked="checked">
 	                      <label class="custom-control-label" for="show-nvs"></label>
 	                      <label class="custom-control-label" for="show-nvs"></label>
 	                </div>
 	                </div>
-                </div>
+                </div> <!-- credits -->
             </div>
             </div>
             <footer class="footer"><span id="foot-fw"></span><span id="foot-wifi"></span></footer>
             <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>
             <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;
   padding: 4px;
 }
 }
 
 
-input.gpio {
-    width: 2em;
-    color: #000;
-    height: 1.8em;
-}
-
 .custom-switch {
 .custom-switch {
     margin-left: 8px;
     margin-left: 8px;
 }
 }
@@ -273,6 +267,18 @@ textarea#autoexec1, textarea#fwurl, div#upload {
     width: 80%;
     width: 80%;
 }
 }
 
 
+table tr.MESSAGING_INFO {
+    background: #123;
+}
+
+table tr.MESSAGING_WARNING {
+    background: #330;
+}
+
+table tr.MESSAGING_ERROR {
+    background: #300;
+}
+
 input, textarea {
 input, textarea {
     border-radius: 3px;
     border-radius: 3px;
     border: 1px solid transparent;
     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 <stdbool.h>
 
 
 #include "dns_server.h"
 #include "dns_server.h"
-#include "http_server.h"
 #include "esp_system.h"
 #include "esp_system.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "freertos/task.h"
@@ -61,6 +60,9 @@ Contains the freeRTOS task and all necessary support
 #include "platform_config.h"
 #include "platform_config.h"
 #include "trace.h"
 #include "trace.h"
 #include "cmd_system.h"
 #include "cmd_system.h"
+#include "messaging.h"
+
+#include "http_server_handlers.h"
 #include "monitor.h"
 #include "monitor.h"
 #include "globdefs.h"
 #include "globdefs.h"
 
 
@@ -69,7 +71,7 @@ Contains the freeRTOS task and all necessary support
 #endif
 #endif
 
 
 #define STR_OR_BLANK(p) p==NULL?"":p
 #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 */
 /* objects used to manipulate the main queue of events */
 QueueHandle_t wifi_manager_queue;
 QueueHandle_t wifi_manager_queue;
 SemaphoreHandle_t wifi_manager_json_mutex = NULL;
 SemaphoreHandle_t wifi_manager_json_mutex = NULL;
@@ -83,7 +85,7 @@ char *ip_info_json = NULL;
 char * release_url=NULL;
 char * release_url=NULL;
 cJSON * ip_info_cjson=NULL;
 cJSON * ip_info_cjson=NULL;
 wifi_config_t* wifi_manager_config_sta = 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 int32_t total_connected_time=0;
 static int64_t last_connected=0;
 static int64_t last_connected=0;
@@ -202,9 +204,6 @@ bool isGroupBitSet(uint8_t bit){
 	EventBits_t uxBits= xEventGroupGetBits(wifi_manager_event_group);
 	EventBits_t uxBits= xEventGroupGetBits(wifi_manager_event_group);
 	return (uxBits & bit);
 	return (uxBits & bit);
 }
 }
-void wifi_manager_refresh_ota_json(){
-	wifi_manager_send_message(EVENT_REFRESH_OTA, NULL);
-}
 
 
 void wifi_manager_scan_async(){
 void wifi_manager_scan_async(){
 	wifi_manager_send_message(ORDER_START_WIFI_SCAN, NULL);
 	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);
 		esp_err = nvs_commit(handle);
 		if (esp_err != ESP_OK) {
 		if (esp_err != ESP_OK) {
 			ESP_LOGE(TAG,  "Unable to commit changes. Error %s", esp_err_to_name(esp_err));
 			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;
 			return esp_err;
 		}
 		}
 		nvs_close(handle);
 		nvs_close(handle);
@@ -449,8 +449,6 @@ cJSON * wifi_manager_get_basic_info(cJSON **old){
 	cJSON_AddItemToObject(root, "version", cJSON_CreateString(desc->version));
 	cJSON_AddItemToObject(root, "version", cJSON_CreateString(desc->version));
 	if(release_url !=NULL) cJSON_AddItemToObject(root, "release_url", cJSON_CreateString(release_url));
 	if(release_url !=NULL) cJSON_AddItemToObject(root, "release_url", cJSON_CreateString(release_url));
 	cJSON_AddNumberToObject(root,"recovery",	is_recovery_running?1:0);
 	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_AddItemToObject(root, "Jack", cJSON_CreateString(jack_inserted_svc() ? "1" : "0"));
 	cJSON_AddNumberToObject(root,"Voltage",	battery_value_svc());
 	cJSON_AddNumberToObject(root,"Voltage",	battery_value_svc());
 	cJSON_AddNumberToObject(root,"disconnect_count", num_disconnect	);
 	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();
 	wifi_config_t *config = wifi_manager_get_wifi_sta_config();
 	ip_info_cjson = wifi_manager_get_basic_info(&ip_info_cjson);
 	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);
 	cJSON_AddNumberToObject(ip_info_cjson, "urc", update_reason_code);
 	if(config){
 	if(config){
 		cJSON_AddItemToObject(ip_info_cjson, "ssid", cJSON_CreateString((char *)config->sta.ssid));
 		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);
 	char * macStr=malloc(LOCAL_MAC_SIZE);
 	memset(macStr, 0x00, 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;
 	return macStr;
 
 
 }
 }
@@ -855,20 +847,6 @@ void wifi_manager_connect_async(){
 	wifi_manager_send_message(ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_USER);
 	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(){
 char* wifi_manager_alloc_get_ip_info_json(){
 	return cJSON_PrintUnformatted(ip_info_cjson);
 	return cJSON_PrintUnformatted(ip_info_cjson);
@@ -1138,12 +1116,6 @@ void wifi_manager( void * pvParameters ){
 					ESP_LOGD(TAG,  "Done Invoking SCAN DONE callback");
 					ESP_LOGD(TAG,  "Done Invoking SCAN DONE callback");
 				}
 				}
 				break;
 				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:
 			case ORDER_START_WIFI_SCAN:
 				ESP_LOGD(TAG,   "MESSAGE: 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){
 					if(esp_wifi_scan_start(&scan_config, false)!=ESP_OK){
 						ESP_LOGW(TAG,  "Unable to start scan; wifi is trying to connect");
 						ESP_LOGW(TAG,  "Unable to start scan; wifi is trying to connect");
 //						set_status_message(WARNING, "Wifi Connecting. Cannot start scan.");
 //						set_status_message(WARNING, "Wifi Connecting. Cannot start scan.");
+						messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Wifi connecting. Cannot start scan.");
 					}
 					}
 					else {
 					else {
 						xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT);
 						xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT);
@@ -1358,6 +1331,8 @@ void wifi_manager( void * pvParameters ){
 				else{
 				else{
 					/* lost connection ? */
 					/* lost connection ? */
 					ESP_LOGE(TAG,   "WiFi Connection lost.");
 					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 )){
 					if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
 						wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION );
 						wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION );
 						wifi_manager_unlock_json_buffer();
 						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);
 				if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
 				break;
 				break;
 			case UPDATE_CONNECTION_OK:
 			case UPDATE_CONNECTION_OK:
-				/* refresh JSON with the new ota data */
+				/* refresh JSON */
 				if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
 				if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
 					/* generate the connection info with success */
 					/* generate the connection info with success */
 					wifi_manager_generate_ip_info_json( UPDATE_CONNECTION_OK );
 					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 "squeezelite-ota.h"
 #include "cJSON.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.
  * @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_STA_DISCONNECTED = 12,
 	EVENT_SCAN_DONE = 13,
 	EVENT_SCAN_DONE = 13,
 	EVENT_STA_GOT_IP = 14,
 	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;
 }message_code_t;
 
 
@@ -215,8 +225,7 @@ typedef enum update_reason_code_t {
 	UPDATE_CONNECTION_OK = 0,
 	UPDATE_CONNECTION_OK = 0,
 	UPDATE_FAILED_ATTEMPT = 1,
 	UPDATE_FAILED_ATTEMPT = 1,
 	UPDATE_USER_DISCONNECT = 2,
 	UPDATE_USER_DISCONNECT = 2,
-	UPDATE_LOST_CONNECTION = 3,
-	UPDATE_OTA=4
+	UPDATE_LOST_CONNECTION = 3
 }update_reason_code_t;
 }update_reason_code_t;
 
 
 typedef enum connection_request_made_by_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 .
                     	INCLUDE_DIRS .
                     	EMBED_FILES ../server_certs/github.pem
                     	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,
 # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
 # please read the SDK documents if you need to do this.
 # 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
 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
 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/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  *
  */
  */
-#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+
 #include "platform_esp32.h"
 #include "platform_esp32.h"
 #include "led.h"
 #include "led.h"
 #include <stdio.h>
 #include <stdio.h>
@@ -40,7 +40,6 @@
 #include "lwip/err.h"
 #include "lwip/err.h"
 #include "lwip/netdb.h"
 #include "lwip/netdb.h"
 #include "nvs_utilities.h"
 #include "nvs_utilities.h"
-#include "http_server.h"
 #include "trace.h"
 #include "trace.h"
 #include "wifi_manager.h"
 #include "wifi_manager.h"
 #include "squeezelite-ota.h"
 #include "squeezelite-ota.h"
@@ -48,11 +47,12 @@
 #include "platform_config.h"
 #include "platform_config.h"
 #include "audio_controls.h"
 #include "audio_controls.h"
 #include "telnet.h"
 #include "telnet.h"
+#include "messaging.h"
 
 
 static const char certs_namespace[] = "certificates";
 static const char certs_namespace[] = "certificates";
 static const char certs_key[] = "blob";
 static const char certs_key[] = "blob";
 static const char certs_version[] = "version";
 static const char certs_version[] = "version";
-
+const char unknown_string_placeholder[] = "unknown";
 EventGroupHandle_t wifi_event_group;
 EventGroupHandle_t wifi_event_group;
 
 
 bool bypass_wifi_manager=false;
 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
 // as an exception _init function don't need include
 extern void services_init(void);
 extern void services_init(void);
 extern void	display_init(char *welcome);
 extern void	display_init(char *welcome);
+
 bool is_recovery_running;
 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 */
 /* 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){
 void cb_connection_got_ip(void *pvParameter){
@@ -87,12 +89,14 @@ void cb_connection_got_ip(void *pvParameter){
 	}
 	}
 	ip.addr = ipInfo.ip.addr;
 	ip.addr = ipInfo.ip.addr;
 	ESP_LOGI(TAG, "I have a connection!");
 	ESP_LOGI(TAG, "I have a connection!");
+	messaging_post_message(MESSAGING_INFO,MESSAGING_CLASS_SYSTEM,"Wifi connected");
 	xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
 	xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
 	bWifiConnected=true;
 	bWifiConnected=true;
 	led_unpush(LED_GREEN);
 	led_unpush(LED_GREEN);
 }
 }
 void cb_connection_sta_disconnected(void *pvParameter){
 void cb_connection_sta_disconnected(void *pvParameter){
 	led_blink_pushed(LED_GREEN, 250, 250);
 	led_blink_pushed(LED_GREEN, 250, 250);
+	messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Wifi disconnected");
 	bWifiConnected=false;
 	bWifiConnected=false;
 	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
 	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) {
 	if ( (esp_err= nvs_get_str(handle, certs_version, NULL, &len)) == ESP_OK) {
 		str=(char *)malloc(len);
 		str=(char *)malloc(len);
 		if ( (esp_err = nvs_get_str(handle,  certs_version, str, &len)) == ESP_OK) {
 		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){
 	if(str!=NULL){
@@ -374,17 +378,15 @@ void app_main()
 	wifi_event_group = xEventGroupCreate();
 	wifi_event_group = xEventGroupCreate();
 	ESP_LOGD(TAG,"Clearing CONNECTED_BIT from wifi group");
 	ESP_LOGD(TAG,"Clearing CONNECTED_BIT from wifi group");
 	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
 	xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
-	
 
 
 	ESP_LOGI(TAG,"Registering default values");
 	ESP_LOGI(TAG,"Registering default values");
 	register_default_nvs();
 	register_default_nvs();
 
 
-	ESP_LOGD(TAG,"Configuring services");
+	ESP_LOGI(TAG,"Configuring services");
 	services_init();
 	services_init();
 
 
-	ESP_LOGD(TAG,"Initializing display");	
+	ESP_LOGI(TAG,"Initializing display");
 	display_init("SqueezeESP32");
 	display_init("SqueezeESP32");
-
 	if(!is_recovery_running){
 	if(!is_recovery_running){
 		ESP_LOGI(TAG,"Checking if certificates need to be updated");
 		ESP_LOGI(TAG,"Checking if certificates need to be updated");
 		update_certificates();
 		update_certificates();
@@ -418,10 +420,12 @@ void app_main()
 	led_blink(LED_GREEN, 250, 250);
 	led_blink(LED_GREEN, 250, 250);
 
 
 	if(bypass_wifi_manager){
 	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 {
 	else {
-		ESP_LOGW(TAG,"\n\nwifi manager is ENABLED. Starting...\n\n");
+		ESP_LOGI(TAG,"Starting Wifi Manager");
 		wifi_manager_start();
 		wifi_manager_start();
 		wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_got_ip);
 		wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_got_ip);
 		wifi_manager_set_callback(EVENT_STA_DISCONNECTED, &cb_connection_sta_disconnected);
 		wifi_manager_set_callback(EVENT_STA_DISCONNECTED, &cb_connection_sta_disconnected);
@@ -445,4 +449,5 @@ void app_main()
 		}
 		}
 		free(fwurl);
 		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_EVENT_POST_FROM_IRAM_ISR=y
 CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=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_MAX_URI_LEN=512
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
 CONFIG_HTTPD_PURGE_BUF_LEN=32
 CONFIG_HTTPD_PURGE_BUF_LEN=32