diff --git a/.gitignore b/.gitignore index ac5f8b6b..8eb0898f 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,6 @@ libs/ /cdump.cmd /_* squeezelite-esp32-jsonblob.zip +/flash_cmd.txt +/writeSequeezeEsp.bat +/writeSequeezeEsp.sh diff --git a/.gitmodules b/.gitmodules index 847780c7..17261bba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,6 @@ path = components/telnet/libtelnet url = https://github.com/seanmiddleditch/libtelnet branch = develop -[submodule "esp-dsp"] +[submodule "components/esp-dsp"] path = components/esp-dsp - url = https://github.com/philippe44/esp-dsp + url = https://github.com/philippe44/esp-dsp.git diff --git a/.pydevproject b/.pydevproject index af296c54..2b045655 100644 --- a/.pydevproject +++ b/.pydevproject @@ -1,8 +1,5 @@ - Default - python interpreter - diff --git a/.settings/com.googlecode.cppcheclipse.core.prefs b/.settings/com.googlecode.cppcheclipse.core.prefs new file mode 100644 index 00000000..b21d0f0a --- /dev/null +++ b/.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\! diff --git a/.settings/org.eclipse.cdt.codan.core.prefs b/.settings/org.eclipse.cdt.codan.core.prefs new file mode 100644 index 00000000..9a577d2b --- /dev/null +++ b/.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} diff --git a/Makefile b/Makefile index d35a1ae8..51ac175c 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,13 @@ #recovery: PROJECT_NAME:=recovery.$(PROJECT_CONFIG_TARGET) -#recovery: CPPFLAGS+=-DRECOVERY_APPLICATION=1 +#recovery: EXTRA_CPPFLAGS+=-DRECOVERY_APPLICATION=1 PROJECT_NAME?=squeezelite -EXTRA_COMPONENT_DIRS := esp-dsp +EXTRA_CPPFLAGS+= -I$(PROJECT_PATH)/main + +#/-Wno-error=maybe-uninitialized include $(IDF_PATH)/make/project.mk -CPPFLAGS+= -Wno-error=maybe-uninitialized + +# for future gcc version, this could be needed: CPPFLAGS+= -Wno-error=format-overflow -Wno-error=stringop-truncation + diff --git a/build-scripts/ESP32-A1S-sdkconfig.defaults b/build-scripts/ESP32-A1S-sdkconfig.defaults index 700873f0..930cb24d 100644 --- a/build-scripts/ESP32-A1S-sdkconfig.defaults +++ b/build-scripts/ESP32-A1S-sdkconfig.defaults @@ -512,7 +512,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y -CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 @@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_CORETIMER_0=y CONFIG_FREERTOS_HZ=100 +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y diff --git a/build-scripts/I2S-4MFlash-sdkconfig.defaults b/build-scripts/I2S-4MFlash-sdkconfig.defaults index 73f5ec2e..d24ce9fa 100644 --- a/build-scripts/I2S-4MFlash-sdkconfig.defaults +++ b/build-scripts/I2S-4MFlash-sdkconfig.defaults @@ -512,7 +512,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y -CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 @@ -594,7 +594,7 @@ CONFIG_FMB_TIMER_INDEX=0 CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_CORETIMER_0=y - +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y CONFIG_FREERTOS_HZ=100 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y diff --git a/build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults b/build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults index 1bf9ba85..31bfb903 100644 --- a/build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults +++ b/build-scripts/NonOTA-I2S-4MFlash-sdkconfig.defaults @@ -513,7 +513,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y -CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 @@ -597,6 +597,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_CORETIMER_0=y CONFIG_FREERTOS_HZ=100 +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y diff --git a/build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults b/build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults index d5a059b1..92529a83 100644 --- a/build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults +++ b/build-scripts/NonOTA-SqueezeAmp-sdkconfig.defaults @@ -512,7 +512,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y -CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 @@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_CORETIMER_0=y CONFIG_FREERTOS_HZ=100 +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y diff --git a/build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults b/build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults index 6ec437e1..aa2c5c9f 100644 --- a/build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults +++ b/build-scripts/SqueezeAmp4MBFlash-sdkconfig.defaults @@ -512,7 +512,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y -CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 @@ -596,6 +596,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_CORETIMER_0=y CONFIG_FREERTOS_HZ=100 +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y diff --git a/build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults b/build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults index 2d22287e..60456e95 100644 --- a/build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults +++ b/build-scripts/SqueezeAmp8MBFlash-sdkconfig.defaults @@ -506,7 +506,7 @@ CONFIG_ESP_EVENT_POST_FROM_ISR=y CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y -CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 @@ -590,6 +590,7 @@ CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_CORETIMER_0=y CONFIG_FREERTOS_HZ=100 +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y diff --git a/build_flash_cmd.sh b/build_flash_cmd.sh new file mode 100644 index 00000000..7124e1f3 --- /dev/null +++ b/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 + diff --git a/components/codecs/component.mk b/components/codecs/component.mk index 99e87521..fb83f6be 100644 --- a/components/codecs/component.mk +++ b/components/codecs/component.mk @@ -21,6 +21,6 @@ COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) \ #$(COMPONENT_PATH)/lib/libesp-tremor.a #$(COMPONENT_PATH)/lib/libesp-ogg-container.a - +COMPONENT_ADD_INCLUDEDIRS := /inc diff --git a/components/platform_config/platform_config.c b/components/platform_config/platform_config.c index 9c578a18..a55f4c29 100644 --- a/components/platform_config/platform_config.c +++ b/components/platform_config/platform_config.c @@ -559,7 +559,7 @@ void config_delete_key(const char *key){ ESP_LOGD(TAG, "Deleting nvs entry for [%s]", key); if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){ ESP_LOGE(TAG, "Unable to lock config for delete"); - return false; + return ; } esp_err_t err = nvs_open_from_partition(settings_partition, current_namespace, NVS_READWRITE, &nvs); if (err == ESP_OK) { @@ -663,7 +663,7 @@ char * config_alloc_get_json(bool bFormatted){ config_unlock(); return json_buffer; } -esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, void * value){ +esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, const void * value){ esp_err_t result = ESP_OK; if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){ ESP_LOGE(TAG, "Unable to lock config after %d ms",LOCK_MAX_WAIT); diff --git a/components/platform_config/platform_config.h b/components/platform_config/platform_config.h index 29509867..92d38c86 100644 --- a/components/platform_config/platform_config.h +++ b/components/platform_config/platform_config.h @@ -3,6 +3,7 @@ #include #include "nvs.h" #include "assert.h" +#include "cJSON.h" #ifdef __cplusplus extern "C" { @@ -12,7 +13,9 @@ extern "C" { #endif #define DECLARE_SET_DEFAULT(t) void config_set_default_## t (const char *key, t value); #define DECLARE_GET_NUM(t) esp_err_t config_get_## t (const char *key, t * value); - +#ifndef FREE_RESET +#define FREE_RESET(p) if(p!=NULL) { free(p); p=NULL; } +#endif DECLARE_SET_DEFAULT(uint8_t); DECLARE_SET_DEFAULT(uint16_t); @@ -37,5 +40,7 @@ void config_set_default(nvs_type_t type, const char *key, void * default_value, void * config_alloc_get(nvs_type_t nvs_type, const char *key) ; bool wait_for_commit(); char * config_alloc_get_json(bool bFormatted); -esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, void * value); +esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, const void * value); +nvs_type_t config_get_item_type(cJSON * entry); +void * config_safe_alloc_get_entry_value(nvs_type_t nvs_type, cJSON * entry); diff --git a/components/platform_console/cmd_system.c b/components/platform_console/cmd_system.c index cc4dbb8e..f6ee7bc8 100644 --- a/components/platform_console/cmd_system.c +++ b/components/platform_console/cmd_system.c @@ -29,6 +29,7 @@ #include "platform_config.h" #include "esp_sleep.h" #include "driver/uart.h" // for the uart driver access +#include "messaging.h" #ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS #define WITH_TASKS_INFO 1 @@ -105,8 +106,8 @@ if(is_recovery_running){ if(!wait_for_commit()){ ESP_LOGW(TAG,"Unable to commit configuration. "); } - ESP_LOGW(TAG, "Restarting after tx complete"); - uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS); + + vTaskDelay(750/ portTICK_PERIOD_MS); esp_restart(); return ESP_OK; } @@ -117,8 +118,8 @@ else { if(!wait_for_commit()){ ESP_LOGW(TAG,"Unable to commit configuration. "); } - ESP_LOGW(TAG, "Restarting after tx complete"); - uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS); + + vTaskDelay(750/ portTICK_PERIOD_MS); esp_restart(); return ESP_OK; } @@ -131,7 +132,7 @@ else { if(it == NULL){ ESP_LOGE(TAG,"Unable initialize partition iterator!"); - set_status_message(ERROR, "Reboot failed. Cannot iterate through partitions"); + messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Reboot failed. Cannot iterate through partitions"); } else { @@ -145,18 +146,19 @@ else { if(err!=ESP_OK){ ESP_LOGE(TAG,"Unable to set partition as active for next boot. %s",esp_err_to_name(err)); bFound=false; - set_status_message(ERROR, "Unable to select partition for reboot."); + messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Unable to select partition for reboot."); } else{ ESP_LOGW(TAG, "Application partition %s sub type %u is selected for boot", partition->label,partition_subtype); bFound=true; - set_status_message(WARNING, "Rebooting!"); + messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Reboot failed. Cannot iterate through partitions"); } } else { ESP_LOGE(TAG,"partition type %u not found! Unable to reboot to recovery.",partition_subtype); - set_status_message(ERROR, "Partition not found."); + messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"partition type %u not found! Unable to reboot to recovery.",partition_subtype); + } ESP_LOGD(TAG, "Yielding to other processes"); taskYIELD(); @@ -165,8 +167,7 @@ else { if(!wait_for_commit()){ ESP_LOGW(TAG,"Unable to commit configuration. "); } - ESP_LOGW(TAG, "Restarting after tx complete"); - uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS); + vTaskDelay(750/ portTICK_PERIOD_MS); esp_restart(); } } @@ -180,8 +181,7 @@ static int restart(int argc, char **argv) if(!wait_for_commit()){ ESP_LOGW(TAG,"Unable to commit configuration. "); } - ESP_LOGW(TAG, "Restarting after tx complete"); - uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS); + vTaskDelay(750/ portTICK_PERIOD_MS); esp_restart(); return 0; } @@ -193,8 +193,8 @@ void simple_restart() ESP_LOGW(TAG,"Unable to commit configuration. "); } - ESP_LOGW(TAG, "Restarting after tx complete"); - uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS); + + vTaskDelay(750/ portTICK_PERIOD_MS); esp_restart(); } diff --git a/components/platform_console/platform_console.c b/components/platform_console/platform_console.c index cdaf3451..b65f2474 100644 --- a/components/platform_console/platform_console.c +++ b/components/platform_console/platform_console.c @@ -28,6 +28,15 @@ #include "wifi_manager.h" #include "platform_config.h" +#include "telnet.h" +#include "gds.h" +#include "gds_default_if.h" +#include "gds_draw.h" +#include "gds_text.h" +#include "gds_font.h" +#include "display.h" +#include "cmd_squeezelite.h" +#include "config.h" pthread_t thread_console; static void * console_thread(); void console_start(); @@ -152,9 +161,24 @@ void initialize_console() { } void console_start() { - - initialize_console(); - + if(is_recovery_running){ + GDS_ClearExt(display, true); + GDS_SetFont(display, &Font_droid_sans_fallback_15x17 ); + GDS_TextPos(display, GDS_FONT_MEDIUM, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "RECOVERY"); + } + if(!is_serial_suppressed()){ + initialize_console(); + } + else { + /* Initialize the console */ + esp_console_config_t console_config = { .max_cmdline_args = 22, + .max_cmdline_length = 600, + #if CONFIG_LOG_COLORS + .hint_color = atoi(LOG_COLOR_CYAN) + #endif + }; + ESP_ERROR_CHECK(esp_console_init(&console_config)); + } /* Register commands */ esp_console_register_help_command(); register_system(); @@ -167,53 +191,59 @@ void console_start() { register_ota_cmd(); } register_i2ctools(); - printf("\n"); - if(is_recovery_running){ - printf("****************************************************************\n" - "RECOVERY APPLICATION\n" - "This mode is used to flash Squeezelite into the OTA partition\n" - "****\n\n"); - } - printf("Type 'help' to get the list of commands.\n" - "Use UP/DOWN arrows to navigate through command history.\n" - "Press TAB when typing command name to auto-complete.\n" - "\n"); - if(!is_recovery_running){ - printf("To automatically execute lines at startup:\n" - "\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n" - "\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n"); - } - printf("\n\n"); + + if(!is_serial_suppressed()){ + printf("\n"); + if(is_recovery_running){ + printf("****************************************************************\n" + "RECOVERY APPLICATION\n" + "This mode is used to flash Squeezelite into the OTA partition\n" + "****\n\n"); + } + printf("Type 'help' to get the list of commands.\n" + "Use UP/DOWN arrows to navigate through command history.\n" + "Press TAB when typing command name to auto-complete.\n" + "\n"); + if(!is_recovery_running){ + printf("To automatically execute lines at startup:\n" + "\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n" + "\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n"); + } + printf("\n\n"); - /* Figure out if the terminal supports escape sequences */ - int probe_status = linenoiseProbe(); - if (probe_status) { /* zero indicates success */ - printf("\n****************************\n" - "Your terminal application does not support escape sequences.\n" - "Line editing and history features are disabled.\n" - "On Windows, try using Putty instead.\n" - "****************************\n"); - linenoiseSetDumbMode(1); -#if CONFIG_LOG_COLORS - /* Since the terminal doesn't support escape sequences, - * don't use color codes in the prompt. - */ - prompt = "squeezelite-esp32> "; -#endif //CONFIG_LOG_COLORS + /* Figure out if the terminal supports escape sequences */ + int probe_status = linenoiseProbe(); + if (probe_status) { /* zero indicates success */ + printf("\n****************************\n" + "Your terminal application does not support escape sequences.\n" + "Line editing and history features are disabled.\n" + "On Windows, try using Putty instead.\n" + "****************************\n"); + linenoiseSetDumbMode(1); + #if CONFIG_LOG_COLORS + /* Since the terminal doesn't support escape sequences, + * don't use color codes in the prompt. + */ + prompt = "squeezelite-esp32> "; + #endif //CONFIG_LOG_COLORS + } + esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); + cfg.thread_name= "console"; + cfg.inherit_cfg = true; + if(is_recovery_running){ + cfg.stack_size = 4096 ; + } + esp_pthread_set_cfg(&cfg); + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_create(&thread_console, &attr, console_thread, NULL); + pthread_attr_destroy(&attr); + } + else if(!is_recovery_running){ + process_autoexec(); } - esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); - cfg.thread_name= "console"; - cfg.inherit_cfg = true; - if(is_recovery_running){ - cfg.stack_size = 4096 ; - } - esp_pthread_set_cfg(&cfg); - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_create(&thread_console, &attr, console_thread, NULL); - pthread_attr_destroy(&attr); } void run_command(char * line){ /* Try to run the command */ @@ -221,15 +251,16 @@ void run_command(char * line){ esp_err_t err = esp_console_run(line, &ret); if (err == ESP_ERR_NOT_FOUND) { - ESP_LOGE(TAG,"Unrecognized command: %s\n", line); + ESP_LOGE(TAG,"Unrecognized command: %s", line); } else if (err == ESP_ERR_INVALID_ARG) { // command was empty } else if (err == ESP_OK && ret != ESP_OK) { - ESP_LOGW(TAG,"Command returned non-zero error code: 0x%x (%s)\n", ret, + ESP_LOGW(TAG,"Command returned non-zero error code: 0x%x (%s)", ret, esp_err_to_name(err)); } else if (err != ESP_OK) { - ESP_LOGE(TAG,"Internal error: %s\n", esp_err_to_name(err)); + ESP_LOGE(TAG,"Internal error: %s", esp_err_to_name(err)); } + } static void * console_thread() { if(!is_recovery_running){ diff --git a/components/raop/component.mk b/components/raop/component.mk index 9614efb8..93f7da76 100644 --- a/components/raop/component.mk +++ b/components/raop/component.mk @@ -7,7 +7,9 @@ # please read the SDK documents if you need to do this. # -CFLAGS += -fstack-usage \ - -I$(COMPONENT_PATH)/../tools \ - -I$(COMPONENT_PATH)/../codecs/inc/alac \ +CFLAGS += -fstack-usage\ + -I$(PROJECT_PATH)/components/tools \ + -I$(PROJECT_PATH)/components/codecs/inc/alac \ -I$(PROJECT_PATH)/main/ +COMPONENT_ADD_INCLUDEDIRS := . +COMPONENT_SRCDIRS := . \ No newline at end of file diff --git a/components/raop/raop.c b/components/raop/raop.c index 4e548656..10b732df 100644 --- a/components/raop/raop.c +++ b/components/raop/raop.c @@ -957,5 +957,4 @@ static void on_dmap_string(void *ctx, const char *code, const char *name, const if (!strcasecmp(code, "asar")) metadata->artist = strndup(buf, len); else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len); else if (!strcasecmp(code, "minm")) metadata->title = strndup(buf, len); -} - +} \ No newline at end of file diff --git a/components/raop/rtp.c b/components/raop/rtp.c index e11a8f0c..a715785c 100644 --- a/components/raop/rtp.c +++ b/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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "platform.h" -#include "rtp.h" -#include "raop_sink.h" -#include "log_util.h" -#include "util.h" - -#ifdef WIN32 -#include -#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 -#include -#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "platform.h" +#include "rtp.h" +#include "raop_sink.h" +#include "log_util.h" +#include "util.h" + +#ifdef WIN32 +#include +#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 +#include +#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; +} + diff --git a/components/raop/util.c b/components/raop/util.c index 2f8a912c..e41b436f 100644 --- a/components/raop/util.c +++ b/components/raop/util.c @@ -588,7 +588,8 @@ void free_metadata(struct metadata_s *metadata) NFREE(metadata->remote_title); } - /*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ int _fprintf(FILE *file, ...) { diff --git a/components/services/audio_controls.c b/components/services/audio_controls.c index a525eee6..2ee931a8 100644 --- a/components/services/audio_controls.c +++ b/components/services/audio_controls.c @@ -18,7 +18,6 @@ * along with this program. If not, see . * */ -//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #include #include #include diff --git a/components/services/component.mk b/components/services/component.mk index ace79fdb..0bf7bd7a 100644 --- a/components/services/component.mk +++ b/components/services/component.mk @@ -9,3 +9,4 @@ COMPONENT_SRCDIRS := . COMPONENT_ADD_INCLUDEDIRS := . +CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG \ No newline at end of file diff --git a/components/services/messaging.c b/components/services/messaging.c new file mode 100644 index 00000000..065de8d3 --- /dev/null +++ b/components/services/messaging.c @@ -0,0 +1,230 @@ + /** + * + */ +#include // Required for libtelnet.h +#include +#include "stdbool.h" +#include +#include +#include +#include +#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;imsg_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=⊤ + 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;imessage); + 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=⊤ + 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; + +} diff --git a/components/services/messaging.h b/components/services/messaging.h new file mode 100644 index 00000000..f9ecbca6 --- /dev/null +++ b/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(); diff --git a/components/services/monitor.c b/components/services/monitor.c index d280fe2f..6b39909a 100644 --- a/components/services/monitor.c +++ b/components/services/monitor.c @@ -21,6 +21,9 @@ #include "globdefs.h" #include "platform_config.h" #include "accessors.h" +#include "messaging.h" +#include "cJSON.h" +#include "trace.h" #define MONITOR_TIMER (10*1000) #define SCRATCH_SIZE 256 @@ -44,16 +47,17 @@ bool spkfault_svc(void); /**************************************************************************************** * */ -static void task_stats( void ) { +static void task_stats( cJSON* top ) { #ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY static struct { TaskStatus_t *tasks; uint32_t total, n; } current, previous; - + cJSON * tlist=cJSON_CreateArray(); current.n = uxTaskGetNumberOfTasks(); current.tasks = malloc( current.n * sizeof( TaskStatus_t ) ); current.n = uxTaskGetSystemState( current.tasks, current.n, ¤t.total ); + cJSON_AddNumberToObject(top,"ntasks",current.n); static EXT_RAM_ATTR char scratch[SCRATCH_SIZE]; *scratch = '\0'; @@ -68,6 +72,15 @@ static void task_stats( void ) { current.tasks[i].eCurrentState, 100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed, current.tasks[i].usStackHighWaterMark); + cJSON * t=cJSON_CreateObject(); + cJSON_AddNumberToObject(t,"cpu",100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed); + cJSON_AddNumberToObject(t,"minstk",current.tasks[i].usStackHighWaterMark); + cJSON_AddNumberToObject(t,"bprio",current.tasks[i].uxBasePriority); + cJSON_AddNumberToObject(t,"cprio",current.tasks[i].uxCurrentPriority); + cJSON_AddStringToObject(t,"nme",current.tasks[i].pcTaskName); + cJSON_AddNumberToObject(t,"st",current.tasks[i].eCurrentState); + cJSON_AddNumberToObject(t,"num",current.tasks[i].xTaskNumber); + cJSON_AddItemToArray(tlist,t); if (i % 3 == 2 || i == current.n - 1) { ESP_LOGI(TAG, "%s", scratch); n = 0; @@ -79,13 +92,21 @@ static void task_stats( void ) { #else for (int i = 0, n = 0; i < current.n; i ++) { n += sprintf(scratch + n, "%16s s:%5u\t", current.tasks[i].pcTaskName, current.tasks[i].usStackHighWaterMark); + cJSON * t=cJSON_CreateObject(); + cJSON_AddNumberToObject(t,"minstk",current.tasks[i].usStackHighWaterMark); + cJSON_AddNumberToObject(t,"bprio",current.tasks[i].uxBasePriority); + cJSON_AddNumberToObject(t,"cprio",current.tasks[i].uxCurrentPriority); + cJSON_AddStringToObject(t,"nme",current.tasks[i].pcTaskName); + cJSON_AddStringToObject(t,"st",current.tasks[i].eCurrentState); + cJSON_AddNumberToObject(t,"num",current.tasks[i].xTaskNumber); + cJSON_AddItemToArray(tlist,t); if (i % 3 == 2 || i == current.n - 1) { ESP_LOGI(TAG, "%s", scratch); n = 0; } } #endif - + cJSON_AddItemToObject(top,"tasks",tlist); if (previous.tasks) free(previous.tasks); previous = current; #endif @@ -95,13 +116,26 @@ static void task_stats( void ) { * */ static void monitor_callback(TimerHandle_t xTimer) { + cJSON * top=cJSON_CreateObject(); + cJSON_AddNumberToObject(top,"free_iram",heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + cJSON_AddNumberToObject(top,"min_free_iram",heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL)); + cJSON_AddNumberToObject(top,"free_spiram",heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + cJSON_AddNumberToObject(top,"min_free_spiram",heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM)); + ESP_LOGI(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu)", heap_caps_get_free_size(MALLOC_CAP_INTERNAL), heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL), heap_caps_get_free_size(MALLOC_CAP_SPIRAM), heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM)); - task_stats(); + task_stats(top); + char * top_a= cJSON_PrintUnformatted(top); + if(top_a){ + messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a); + FREE_AND_NULL(top_a); + } + cJSON_free(top); + } /**************************************************************************************** @@ -109,6 +143,7 @@ static void monitor_callback(TimerHandle_t xTimer) { */ static void jack_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) { ESP_LOGD(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed"); + messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_SYSTEM,"jack is %s",BUTTON_PRESSED ? "inserted" : "removed"); if (jack_handler_svc) (*jack_handler_svc)(event == BUTTON_PRESSED); } diff --git a/components/services/services.c b/components/services/services.c index fa88d4d8..631d2cf9 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -16,6 +16,7 @@ #include "monitor.h" #include "globdefs.h" #include "accessors.h" +#include "messaging.h" extern void battery_svc_init(void); extern void monitor_svc_init(void); @@ -52,6 +53,7 @@ void set_power_gpio(int gpio, char *value) { * */ void services_init(void) { + messaging_service_init(); gpio_install_isr_service(0); #ifdef CONFIG_I2C_LOCKED diff --git a/components/squeezelite-ota/component.mk b/components/squeezelite-ota/component.mk index 35902feb..d0d6918e 100644 --- a/components/squeezelite-ota/component.mk +++ b/components/squeezelite-ota/component.mk @@ -5,9 +5,6 @@ # todo: add support for https COMPONENT_ADD_INCLUDEDIRS := . -COMPONENT_ADD_INCLUDEDIRS += include -COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/ -COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/components/tools CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO -DCONFIG_OTA_ALLOW_HTTP=1 -#CFLAGS += -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCONFIG_OTA_ALLOW_HTTP=1 + diff --git a/components/squeezelite-ota/squeezelite-ota.c b/components/squeezelite-ota/squeezelite-ota.c index 27bda936..ea7bd719 100644 --- a/components/squeezelite-ota/squeezelite-ota.c +++ b/components/squeezelite-ota/squeezelite-ota.c @@ -6,9 +6,6 @@ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ -#ifndef LOG_LOCAL_LEVEL -#define LOG_LOCAL_LEVEL ESP_LOG_INFO -#endif #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" @@ -23,46 +20,69 @@ #include "esp_err.h" #include "tcpip_adapter.h" #include "squeezelite-ota.h" -#include "platform_config.h" -#include "platform_esp32.h" +#include "config.h" #include #include #include #include "esp_secure_boot.h" #include "esp_flash_encrypt.h" #include "esp_spi_flash.h" -#include "platform_esp32.h" #include "sdkconfig.h" - +#include "messaging.h" +#include "trace.h" #include "esp_ota_ops.h" +#include "display.h" +#include "gds.h" +#include "gds_text.h" +#include "gds_draw.h" + extern const char * get_certificate(); +#ifdef CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 +#define OTA_CORE 0 +#else +#define OTA_CORE 1 +#endif + static const char *TAG = "squeezelite-ota"; -char * ota_write_data = NULL; esp_http_client_handle_t ota_http_client = NULL; #define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1 #define BUFFSIZE 4096 #define HASH_LEN 32 /* SHA-256 digest length */ - +typedef struct { + char * url; + char * bin; + uint32_t length; +} ota_thread_parms_t ; +static ota_thread_parms_t ota_thread_parms; +typedef enum { + OTA_TYPE_HTTP, + OTA_TYPE_BUFFER, + OTA_TYPE_INVALID +} ota_type_t; static struct { - char status_text[81]; - uint32_t ota_actual_len; - uint32_t ota_total_len; - char * redirected_url; - char * current_url; + uint32_t actual_image_len; + uint32_t total_image_len; + uint32_t remain_image_len; + ota_type_t ota_type; + char * ota_write_data; + char * bin_data; bool bOTAStarted; - bool bInitialized; + size_t buffer_size; uint8_t lastpct; uint8_t newpct; struct timeval OTA_start; bool bOTAThreadStarted; - + const esp_partition_t *configured; + const esp_partition_t *running; + const esp_partition_t * update_partition; + const esp_partition_t* last_invalid_app ; + const esp_partition_t * ota_partition; } ota_status; -struct timeval tv; -static esp_http_client_config_t ota_config; -extern void wifi_manager_refresh_ota_json(); +struct timeval tv; +static esp_http_client_config_t http_client_config; void _printMemStats(){ ESP_LOGD(TAG,"Heap internal:%zu (min:%zu) external:%zu (min:%zu)", @@ -71,35 +91,121 @@ void _printMemStats(){ heap_caps_get_free_size(MALLOC_CAP_SPIRAM), heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM)); } -void triggerStatusJsonRefresh(bool bDelay,const char * status, ...){ - va_list args; - va_start(args, status); - vsnprintf(ota_status.status_text,sizeof(ota_status.status_text)-1,status, args); - va_end(args); - _printMemStats(); - wifi_manager_refresh_ota_json(); - if(bDelay){ - ESP_LOGD(TAG,"Holding task..."); - vTaskDelay(200 / portTICK_PERIOD_MS); // wait here for a short amount of time. This will help with refreshing the UI status - ESP_LOGD(TAG,"Done holding task..."); +uint8_t ota_get_pct_complete(){ + return ota_status.total_image_len==0?0: + (uint8_t)((float)ota_status.actual_image_len/(float)ota_status.total_image_len*100.0f); +} +typedef struct { + int x1,y1,x2,y2,width,height; +} rect_t; +typedef struct _progress { + int border_thickness; + int sides_margin; + int vertical_margin; + int bar_tot_height; + int bar_fill_height; + rect_t border; + rect_t filler; +} progress_t; + +static progress_t * loc_displayer_get_progress_dft(){ + int start_coord_offset=0; + static progress_t def={ + .border_thickness = 2, + .sides_margin = 2, + .bar_tot_height = 7, + }; + def.bar_fill_height= def.bar_tot_height-(def.border_thickness*2); + def.border.x1=start_coord_offset+def.sides_margin; + def.border.x2=GDS_GetWidth(display)-def.sides_margin; + // progress bar will be drawn at the bottom of the display + def.border.y2= GDS_GetHeight(display)-def.border_thickness; + def.border.y1= def.border.y2-def.bar_tot_height; + def.border.width=def.border.x2-def.border.x1; + def.border.height=def.border.y2-def.border.y1; + def.filler.x1= def.border.x1+def.border_thickness; + def.filler.x2= def.border.x2-def.border_thickness; + def.filler.y1= def.border.y1+def.border_thickness; + def.filler.y2= def.border.y2-def.border_thickness; + def.filler.width=def.filler.x2-def.filler.x1; + def.filler.height=def.filler.y2-def.filler.y1; + assert(def.filler.width>0); + assert(def.filler.height>0); + assert(def.border.width>0); + assert(def.border.height>0); + assert(def.border.width>def.filler.width); + assert(def.border.height>def.filler.height); + return &def; + +} +static void loc_displayer_progressbar(uint8_t pct){ + static progress_t * progress_coordinates; + if(!progress_coordinates) progress_coordinates = loc_displayer_get_progress_dft(); + int filler_x=progress_coordinates->filler.x1+(int)((float)progress_coordinates->filler.width*(float)pct/(float)100); + + ESP_LOGD(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2); + GDS_DrawBox(display,progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2,GDS_COLOR_WHITE,false); + ESP_LOGD(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2); + if(filler_x > progress_coordinates->filler.x1){ + GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2,GDS_COLOR_WHITE,true); } else { - ESP_LOGI(TAG,"%s",ota_status.status_text); - taskYIELD(); + // Clear the inner box + GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,progress_coordinates->filler.x2,progress_coordinates->filler.y2,GDS_COLOR_BLACK,true); } + ESP_LOGD(TAG,"Updating Display"); + GDS_Update(display); } -const char * ota_get_status(){ - if(!ota_status.bInitialized) - { - memset(ota_status.status_text, 0x00,sizeof(ota_status.status_text)); - ota_status.bInitialized = true; - } - return ota_status.status_text; -} -uint8_t ota_get_pct_complete(){ - return ota_status.ota_total_len==0?0: - (uint8_t)((float)ota_status.ota_actual_len/(float)ota_status.ota_total_len*100.0f); +void sendMessaging(messaging_types type,const char * fmt, ...){ + va_list args; + cJSON * msg = cJSON_CreateObject(); + size_t str_len=0; + char * msg_str=NULL; + + va_start(args, fmt); + str_len = vsnprintf(NULL,0,fmt,args)+1; + if(str_len>0){ + msg_str = malloc(str_len); + vsnprintf(msg_str,str_len,fmt,args); + if(type == MESSAGING_WARNING){ + ESP_LOGW(TAG,"%s",msg_str); + } + else if (type == MESSAGING_ERROR){ + ESP_LOGE(TAG,"%s",msg_str); + } + else + ESP_LOGI(TAG,"%s",msg_str); + } + else { + ESP_LOGW(TAG, "Sending empty string message"); + } + va_end(args); + if(type!=MESSAGING_INFO){ + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, msg_str); + } + + cJSON_AddStringToObject(msg,"ota_dsc",str_or_unknown(msg_str)); + free(msg_str); + cJSON_AddNumberToObject(msg,"ota_pct", ota_get_pct_complete() ); + char * json_msg = cJSON_PrintUnformatted(msg); + messaging_post_message(type, MESSAGING_CLASS_OTA, json_msg); + free(json_msg); + cJSON_free(msg); + _printMemStats(); } +//esp_err_t decode_alloc_ota_message(single_message_t * message, char * ota_dsc, uint8_t * ota_pct ){ +// if(!message || !message->message) return ESP_ERR_INVALID_ARG; +// cJSON * json = cJSON_Parse(message->message); +// if(!json) return ESP_FAIL; +// if(ota_dsc) { +// ota_dsc = strdup(cJSON_GetObjectItem(json, "ota_dsc")?cJSON_GetStringValue(cJSON_GetObjectItem(json, "ota_dsc")):""); +// } +// if(ota_pct){ +// *ota_pct = cJSON_GetObjectItem(json, "ota_pct")?cJSON_GetObjectItem(json, "ota_pct")->valueint:0; +// } +// cJSON_free(json); +// return ESP_OK; +//} static void __attribute__((noreturn)) task_fatal_error(void) { @@ -110,7 +216,7 @@ static void __attribute__((noreturn)) task_fatal_error(void) ; } } -#define FREE_RESET(p) if(p!=NULL) { free(p); p=NULL; } + esp_err_t _http_event_handler(esp_http_client_event_t *evt) { // -------------- @@ -138,10 +244,11 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt) case HTTP_EVENT_ON_CONNECTED: ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); - if(ota_status.bOTAStarted) triggerStatusJsonRefresh(true,"Installing..."); - ota_status.ota_total_len=0; - ota_status.ota_actual_len=0; + if(ota_status.bOTAStarted) sendMessaging(MESSAGING_INFO,"Connecting to URL..."); + ota_status.total_image_len=0; + ota_status.actual_image_len=0; ota_status.lastpct=0; + ota_status.remain_image_len=0; ota_status.newpct=0; gettimeofday(&ota_status.OTA_start, NULL); @@ -152,13 +259,11 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt) case HTTP_EVENT_ON_HEADER: ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s",evt->header_key, evt->header_value); if (strcasecmp(evt->header_key, "location") == 0) { - FREE_RESET(ota_status.redirected_url); - ota_status.redirected_url=strdup(evt->header_value); - ESP_LOGW(TAG,"OTA will redirect to url: %s",ota_status.redirected_url); + ESP_LOGW(TAG,"OTA will redirect to url: %s",evt->header_value); } if (strcasecmp(evt->header_key, "content-length") == 0) { - ota_status.ota_total_len = atol(evt->header_value); - ESP_LOGW(TAG, "Content length found: %s, parsed to %d", evt->header_value, ota_status.ota_total_len); + ota_status.total_image_len = atol(evt->header_value); + ESP_LOGW(TAG, "Content length found: %s, parsed to %d", evt->header_value, ota_status.total_image_len); } break; case HTTP_EVENT_ON_DATA: @@ -176,29 +281,49 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt) return ESP_OK; } -esp_err_t init_config(char * url){ - memset(&ota_config, 0x00, sizeof(ota_config)); - ota_status.bInitialized = true; - triggerStatusJsonRefresh(true,"Initializing..."); - if(url==NULL || strlen(url)==0){ - ESP_LOGE(TAG,"HTTP OTA called without a url"); +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; + } + 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; } - 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; - } + return ESP_OK; } esp_partition_t * _get_ota_partition(esp_partition_subtype_t subtype){ @@ -225,7 +350,7 @@ esp_partition_t * _get_ota_partition(esp_partition_subtype_t subtype){ -esp_err_t _erase_last_boot_app_partition(esp_partition_t *ota_partition) +esp_err_t _erase_last_boot_app_partition(const esp_partition_t *ota_partition) { uint16_t num_passes=0; uint16_t remain_size=0; @@ -257,18 +382,20 @@ esp_err_t _erase_last_boot_app_partition(esp_partition_t *ota_partition) ESP_LOGD(TAG,"Pass %d of %d, with chunks of %d bytes, from %d to %d", i+1, num_passes,single_pass_size,i*single_pass_size,i*single_pass_size+single_pass_size); err=esp_partition_erase_range(ota_partition, i*single_pass_size, single_pass_size); if(err!=ESP_OK) return err; -// triggerStatusJsonRefresh(i%10==0?true:false,"Erasing flash (%u/%u)",i,num_passes); if(i%2) { - triggerStatusJsonRefresh(false,"Erasing flash (%u/%u)",i,num_passes); + loc_displayer_progressbar((int)(((float)i/(float)num_passes)*100.0f)); + sendMessaging(MESSAGING_INFO,"Erasing flash (%u/%u)",i,num_passes); } - vTaskDelay(200/ portTICK_PERIOD_MS); // wait here for a short amount of time. This will help with reducing WDT errors + vTaskDelay(100/ portTICK_PERIOD_MS); // wait here for a short amount of time. This will help with reducing WDT errors } if(remain_size>0){ err=esp_partition_erase_range(ota_partition, ota_partition->size-remain_size, remain_size); + if(err!=ESP_OK) return err; } - triggerStatusJsonRefresh(true,"Erasing flash complete."); - taskYIELD(); + sendMessaging(MESSAGING_INFO,"Erasing flash complete."); + loc_displayer_progressbar(100); + vTaskDelay(200/ portTICK_PERIOD_MS); return ESP_OK; } @@ -287,28 +414,32 @@ static bool process_again(int status_code) static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client, int status_code) { esp_err_t err=ESP_OK; - if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found) { + if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found ) { ESP_LOGW(TAG, "Handling HTTP redirection. "); err = esp_http_client_set_redirection(http_client); if (err != ESP_OK) { ESP_LOGE(TAG, "URL redirection Failed. %s", esp_err_to_name(err)); return err; } + ESP_LOGW(TAG, "Done Handling HTTP redirection. "); + } else if (status_code == HttpStatus_Unauthorized) { ESP_LOGW(TAG, "Handling Unauthorized. "); esp_http_client_add_auth(http_client); } ESP_LOGD(TAG, "Redirection done, checking if we need to read the data. "); if (process_again(status_code)) { - char * local_buff = heap_caps_malloc(ota_config.buffer_size+1, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - //char * local_buff = malloc(ota_config.buffer_size+1); - if(local_buff==NULL){ - ESP_LOGE(TAG,"Failed to allocate internal memory buffer for http processing"); - return ESP_ERR_NO_MEM; - } + //ESP_LOGD(TAG, "We have to read some more data. Allocating buffer size %u",ota_config.buffer_size+1); + //char * local_buff = heap_caps_malloc(ota_status.buffer_size+1, (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT)); + +// if(local_buff==NULL){ +// ESP_LOGE(TAG,"Failed to allocate internal memory buffer for http processing"); +// return ESP_ERR_NO_MEM; +// } + while (1) { ESP_LOGD(TAG, "Reading data chunk. "); - int data_read = esp_http_client_read(http_client, local_buff, ota_config.buffer_size); + int data_read = esp_http_client_read(http_client, ota_status.ota_write_data, ota_status.buffer_size); if (data_read < 0) { ESP_LOGE(TAG, "Error: SSL data read error"); err= ESP_FAIL; @@ -319,7 +450,7 @@ static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client break; } } - FREE_RESET(local_buff); + //FREE_RESET(local_buff); } return err; @@ -329,44 +460,50 @@ static esp_err_t _http_connect(esp_http_client_handle_t http_client) esp_err_t err = ESP_FAIL; int status_code, header_ret; do { - ESP_LOGD(TAG, "connecting the http client. "); + ESP_LOGI(TAG, "connecting the http client. "); err = esp_http_client_open(http_client, 0); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); + sendMessaging(MESSAGING_ERROR,"Failed to open HTTP connection: %s", esp_err_to_name(err)); return err; } - ESP_LOGD(TAG, "Fetching headers"); + ESP_LOGI(TAG, "Fetching headers"); header_ret = esp_http_client_fetch_headers(http_client); if (header_ret < 0) { // Error found + sendMessaging(MESSAGING_ERROR,"Header fetch failed"); return header_ret; } - ESP_LOGD(TAG, "HTTP Header fetch completed, found content length of %d",header_ret); + ESP_LOGI(TAG, "HTTP Header fetch completed, found content length of %d",header_ret); status_code = esp_http_client_get_status_code(http_client); ESP_LOGD(TAG, "HTTP status code was %d",status_code); - - err = _http_handle_response_code(http_client, status_code); if (err != ESP_OK) { + sendMessaging(MESSAGING_ERROR,"HTTP connect error: %s", esp_err_to_name(err)); return err; } + } while (process_again(status_code)); + + if(status_code >=400 && status_code <=900){ + sendMessaging(MESSAGING_ERROR,"Error: HTTP Status %d",status_code); + err=ESP_FAIL; + } + return err; } void ota_task_cleanup(const char * message, ...){ ota_status.bOTAThreadStarted=false; + loc_displayer_progressbar(0); if(message!=NULL){ - va_list args; va_start(args, message); - triggerStatusJsonRefresh(true,message, args); + sendMessaging(MESSAGING_ERROR,message, args); va_end(args); - ESP_LOGE(TAG, "%s",ota_status.status_text); } - FREE_RESET(ota_status.redirected_url); - FREE_RESET(ota_status.current_url); - FREE_RESET(ota_write_data); + FREE_RESET(ota_status.ota_write_data); + FREE_RESET(ota_status.bin_data); if(ota_http_client!=NULL) { esp_http_client_cleanup(ota_http_client); ota_http_client=NULL; @@ -374,24 +511,120 @@ void ota_task_cleanup(const char * message, ...){ ota_status.bOTAStarted = false; task_fatal_error(); } +esp_err_t ota_buffer_all(){ + int data_read=0; + esp_err_t err=ESP_OK; + if (ota_status.ota_type == OTA_TYPE_HTTP){ + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Downloading file"); + ota_http_client = esp_http_client_init(&http_client_config); + if (ota_http_client == NULL) { + sendMessaging(MESSAGING_ERROR,"Error: Failed to initialize HTTP connection."); + return ESP_FAIL; + } + _printMemStats(); + // Open the http connection and follow any redirection + err = _http_connect(ota_http_client); + if (err != ESP_OK) { + return err; + } + if(ota_status.total_image_len<=0){ + sendMessaging(MESSAGING_ERROR,"Error: Invalid image length"); + return ESP_FAIL; + } + ota_status.bin_data= malloc(ota_status.total_image_len); + if(ota_status.bin_data==NULL){ + sendMessaging(MESSAGING_ERROR,"Error: buffer alloc error"); + return ESP_FAIL; + } + data_read = esp_http_client_read(ota_http_client, ota_status.bin_data, ota_status.total_image_len); + if(data_read != ota_status.total_image_len){ + sendMessaging(MESSAGING_ERROR,"Error: Binary incomplete"); + return ESP_FAIL; + } + } + else { + gettimeofday(&ota_status.OTA_start, NULL); + } + ota_status.remain_image_len=ota_status.total_image_len; + + return err; +} +int ota_buffer_read(){ + int data_read=0; + if(ota_status.remain_image_len >ota_status.buffer_size){ + data_read = ota_status.buffer_size; + } else { + data_read = ota_status.remain_image_len; + } + memcpy(ota_status.ota_write_data, &ota_status.bin_data[ota_status.actual_image_len], data_read); + + ota_status.actual_image_len += data_read; + ota_status.remain_image_len -= data_read; + return data_read; +} +esp_err_t ota_header_check(){ + esp_app_desc_t new_app_info; + esp_app_desc_t running_app_info; + + ota_status.configured = esp_ota_get_boot_partition(); + ota_status.running = esp_ota_get_running_partition(); + ota_status.last_invalid_app= esp_ota_get_last_invalid_partition(); + ota_status.ota_partition = _get_ota_partition(ESP_PARTITION_SUBTYPE_APP_OTA_0); + + ESP_LOGI(TAG, "Running partition [%s] type %d subtype %d (offset 0x%08x)", ota_status.running->label, ota_status.running->type, ota_status.running->subtype, ota_status.running->address); + if (ota_status.total_image_len > ota_status.ota_partition->size){ + ota_task_cleanup("Error: Image size too large to fit in partition."); + return ESP_FAIL; + } + if(ota_status.ota_partition == NULL){ + ESP_LOGE(TAG,"Unable to locate OTA application partition. "); + ota_task_cleanup("Error: OTA partition not found"); + return ESP_FAIL; + } + if (ota_status.configured != ota_status.running) { + ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", ota_status.configured->address, ota_status.running->address); + ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)"); + } + ESP_LOGI(TAG, "Next ota update partition is: [%s] subtype %d at offset 0x%x", + ota_status.update_partition->label, ota_status.update_partition->subtype, ota_status.update_partition->address); + + if (ota_status.total_image_len >= IMAGE_HEADER_SIZE) { + // check current version with downloading + memcpy(&new_app_info, &ota_status.bin_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t)); + ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); + if (esp_ota_get_partition_description(ota_status.running, &running_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Running recovery version: %s", running_app_info.version); + } + + esp_app_desc_t invalid_app_info; + if (esp_ota_get_partition_description(ota_status.last_invalid_app, &invalid_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version); + } + + if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) { + ESP_LOGW(TAG, "Current running version is the same as a new."); + } + return ESP_OK; + } + else{ + ota_task_cleanup("Error: Binary file too small"); + } + return ESP_FAIL; +} + void ota_task(void *pvParameter) { esp_err_t err = ESP_OK; - size_t buffer_size = BUFFSIZE; + int data_read = 0; + GDS_TextSetFont(display,2,GDS_GetHeight(display)>32?&Font_droid_sans_fallback_15x17:&Font_droid_sans_fallback_11x13,-2); + GDS_ClearExt(display, true); + GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Firmware update"); + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Initializing"); + loc_displayer_progressbar(0); ESP_LOGD(TAG, "HTTP ota Thread started"); - const esp_partition_t *configured = esp_ota_get_boot_partition(); - const esp_partition_t *running = esp_ota_get_running_partition(); - const esp_partition_t * update_partition = esp_ota_get_next_update_partition(NULL); - ESP_LOGI(TAG, "esp_ota_get_next_update_partition returned : partition [%s] subtype %d at offset 0x%x", - update_partition->label, update_partition->subtype, update_partition->address); - - if (configured != running) { - ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", configured->address, running->address); - ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)"); - } - ESP_LOGI(TAG, "Running partition [%s] type %d subtype %d (offset 0x%08x)", running->label, running->type, running->subtype, running->address); _printMemStats(); + ota_status.update_partition = esp_ota_get_next_update_partition(NULL); ESP_LOGI(TAG,"Initializing OTA configuration"); err = init_config(pvParameter); @@ -400,139 +633,96 @@ void ota_task(void *pvParameter) return; } + _printMemStats(); + ota_status.bOTAStarted = true; + sendMessaging(MESSAGING_INFO,"Starting OTA..."); + err=ota_buffer_all(); + if(err!=ESP_OK){ + ota_task_cleanup(NULL); + return; + } + + + if(ota_header_check()!=ESP_OK){ + ota_task_cleanup(NULL); + return; + } + /* Locate and erase ota application partition */ ESP_LOGW(TAG,"**************** Expecting WATCHDOG errors below during flash erase. This is OK and not to worry about **************** "); - triggerStatusJsonRefresh(true,"Erasing OTA partition"); - esp_partition_t *ota_partition = _get_ota_partition(ESP_PARTITION_SUBTYPE_APP_OTA_0); - if(ota_partition == NULL){ - ESP_LOGE(TAG,"Unable to locate OTA application partition. "); - ota_task_cleanup("Error: OTA application partition not found. (%s)",esp_err_to_name(err)); - return; - } + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Formatting partition"); + sendMessaging(MESSAGING_INFO,"Formatting OTA partition"); _printMemStats(); - err=_erase_last_boot_app_partition(ota_partition); + err=_erase_last_boot_app_partition(ota_status.ota_partition); if(err!=ESP_OK){ ota_task_cleanup("Error: Unable to erase last APP partition. (%s)",esp_err_to_name(err)); return; } - + loc_displayer_progressbar(0); _printMemStats(); - ota_status.bOTAStarted = true; - triggerStatusJsonRefresh(true,"Starting OTA..."); - ota_http_client = esp_http_client_init(&ota_config); - if (ota_http_client == NULL) { - ota_task_cleanup("Error: Failed to initialize HTTP connection."); - return; - } - _printMemStats(); - // Open the http connection and follow any redirection - err = _http_connect(ota_http_client); - if (err != ESP_OK) { - ota_task_cleanup("Error: HTTP Start read failed. (%s)",esp_err_to_name(err)); - return; - } - _printMemStats(); + // Call OTA Begin with a small partition size - this minimizes the time spent in erasing partition, + // which was already done above esp_ota_handle_t update_handle = 0 ; - int binary_file_length = 0; + err = esp_ota_begin(ota_status.ota_partition, 512, &update_handle); + if (err != ESP_OK) { + ota_task_cleanup("esp_ota_begin failed (%s)", esp_err_to_name(err)); + return; + } + ESP_LOGD(TAG, "esp_ota_begin succeeded"); + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Writing image..."); + while (ota_status.remain_image_len>0) { - /*deal with all receive packet*/ - bool image_header_was_checked = false; - while (1) { - int data_read = esp_http_client_read(ota_http_client, ota_write_data, buffer_size); - if (data_read < 0) { + data_read = ota_buffer_read(); + if (data_read <= 0) { ota_task_cleanup("Error: Data read error"); return; } else if (data_read > 0) { - if (image_header_was_checked == false) { - esp_app_desc_t new_app_info; - if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) { - // check current version with downloading - memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t)); - ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); - - esp_app_desc_t running_app_info; - if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { - ESP_LOGI(TAG, "Running recovery version: %s", running_app_info.version); - } - - const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition(); - esp_app_desc_t invalid_app_info; - if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) { - ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version); - } - - // check current version with last invalid partition -// if (last_invalid_app != NULL) { -// if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) { -// ESP_LOGW(TAG, "New version is the same as invalid version."); -// ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version); -// ESP_LOGW(TAG, "The firmware has been rolled back to the previous version."); -// ota_task_cleanup("esp_ota_begin failed (%s)", esp_err_to_name(err)); -// } -// } - - if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) { - ESP_LOGW(TAG, "Current running version is the same as a new."); - } - - image_header_was_checked = true; - - // Call OTA Begin with a small partition size - this drives the erase operation which was already done; - err = esp_ota_begin(ota_partition, 512, &update_handle); - if (err != ESP_OK) { - ota_task_cleanup("esp_ota_begin failed (%s)", esp_err_to_name(err)); - return; - } - ESP_LOGD(TAG, "esp_ota_begin succeeded"); - } else { - ota_task_cleanup("Error: Binary file too large for the current partition"); - return; - } - } - err = esp_ota_write( update_handle, (const void *)ota_write_data, data_read); + err = esp_ota_write( update_handle, (const void *)ota_status.ota_write_data, data_read); if (err != ESP_OK) { ota_task_cleanup("Error: OTA Partition write failure. (%s)",esp_err_to_name(err)); return; } - binary_file_length += data_read; - ESP_LOGD(TAG, "Written image length %d", binary_file_length); - ota_status.ota_actual_len=binary_file_length; + ESP_LOGD(TAG, "Written image length %d", ota_status.actual_image_len); + if(ota_get_pct_complete()%5 == 0) ota_status.newpct = ota_get_pct_complete(); if(ota_status.lastpct!=ota_status.newpct ) { + loc_displayer_progressbar(ota_status.newpct); gettimeofday(&tv, NULL); uint32_t elapsed_ms= (tv.tv_sec-ota_status.OTA_start.tv_sec )*1000+(tv.tv_usec-ota_status.OTA_start.tv_usec)/1000; - ESP_LOGI(TAG,"OTA progress : %d/%d (%d pct), %d KB/s", ota_status.ota_actual_len, ota_status.ota_total_len, ota_status.newpct, elapsed_ms>0?ota_status.ota_actual_len*1000/elapsed_ms/1024:0); - triggerStatusJsonRefresh(true,"Downloading & writing update."); + ESP_LOGI(TAG,"OTA progress : %d/%d (%d pct), %d KB/s", ota_status.actual_image_len, ota_status.total_image_len, ota_status.newpct, elapsed_ms>0?ota_status.actual_image_len*1000/elapsed_ms/1024:0); + sendMessaging(MESSAGING_INFO,"Writing binary file."); ota_status.lastpct=ota_status.newpct; } taskYIELD(); } else if (data_read == 0) { - ESP_LOGI(TAG, "Connection closed"); + ESP_LOGI(TAG, "End of OTA data stream"); break; } } - ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length); - if (ota_status.ota_total_len != binary_file_length) { + ESP_LOGI(TAG, "Total Write binary data length: %d", ota_status.actual_image_len); + if (ota_status.total_image_len != ota_status.actual_image_len) { ota_task_cleanup("Error: Error in receiving complete file"); return; } _printMemStats(); - + loc_displayer_progressbar(100); err = esp_ota_end(update_handle); if (err != ESP_OK) { ota_task_cleanup("Error: %s",esp_err_to_name(err)); return; } _printMemStats(); - err = esp_ota_set_boot_partition(ota_partition); + err = esp_ota_set_boot_partition(ota_status.ota_partition); if (err == ESP_OK) { ESP_LOGI(TAG,"OTA Process completed successfully!"); - triggerStatusJsonRefresh(true,"Success!"); + sendMessaging(MESSAGING_INFO,"Success!"); + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Success!"); vTaskDelay(1500/ portTICK_PERIOD_MS); // wait here to give the UI a chance to refresh + GDS_Clear(display,GDS_COLOR_BLACK); esp_restart(); } else { ota_task_cleanup("Error: Unable to update boot partition [%s]",esp_err_to_name(err)); @@ -542,7 +732,7 @@ void ota_task(void *pvParameter) return; } -esp_err_t process_recovery_ota(const char * bin_url){ +esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length){ int ret = 0; uint16_t stack_size, task_priority; if(ota_status.bOTAThreadStarted){ @@ -551,23 +741,21 @@ esp_err_t process_recovery_ota(const char * bin_url){ } memset(&ota_status, 0x00, sizeof(ota_status)); ota_status.bOTAThreadStarted=true; - char * urlPtr=strdup(bin_url); - // the first thing we need to do here is to erase the firmware url - // to avoid a boot loop -#ifdef CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 -#define OTA_CORE 0 -#warning "OTA will run on core 0" -#else -#pragma message "OTA will run on core 1" -#define OTA_CORE 1 -#endif - ESP_LOGI(TAG, "Starting ota on core %u for : %s", OTA_CORE,urlPtr); + if(bin_url){ + ota_thread_parms.url =strdup(bin_url); + ESP_LOGI(TAG, "Starting ota on core %u for : %s", OTA_CORE,ota_thread_parms.url); + } + else { + ota_thread_parms.bin = bin_buffer; + ota_thread_parms.length = length; + ESP_LOGI(TAG, "Starting ota on core %u for file upload", OTA_CORE); + } + char * num_buffer=config_alloc_get(NVS_TYPE_STR, "ota_stack"); if(num_buffer!=NULL) { stack_size= atol(num_buffer); - free(num_buffer); - num_buffer=NULL; + FREE_AND_NULL(num_buffer); } else { ESP_LOGW(TAG,"OTA stack size config not found"); @@ -576,16 +764,15 @@ esp_err_t process_recovery_ota(const char * bin_url){ num_buffer=config_alloc_get(NVS_TYPE_STR, "ota_prio"); if(num_buffer!=NULL) { task_priority= atol(num_buffer); - free(num_buffer); - num_buffer=NULL; + FREE_AND_NULL(num_buffer); } else { ESP_LOGW(TAG,"OTA task priority not found"); task_priority= OTA_TASK_PRIOTITY; } + ESP_LOGD(TAG,"OTA task stack size %d, priority %d (%d %s ESP_TASK_MAIN_PRIO)",stack_size , task_priority, abs(task_priority-ESP_TASK_MAIN_PRIO), task_priority-ESP_TASK_MAIN_PRIO>0?"above":"below"); - ret=xTaskCreatePinnedToCore(&ota_task, "ota_task", stack_size , (void *)urlPtr, task_priority, NULL, OTA_CORE); - //ret=xTaskCreate(&ota_task, "ota_task", 1024*20, (void *)urlPtr, ESP_TASK_MAIN_PRIO+2, NULL); + ret=xTaskCreatePinnedToCore(&ota_task, "ota_task", stack_size , (void *)&ota_thread_parms, task_priority, NULL, OTA_CORE); if (ret != pdPASS) { ESP_LOGI(TAG, "create thread %s failed", "ota_task"); return ESP_FAIL; @@ -593,10 +780,14 @@ esp_err_t process_recovery_ota(const char * bin_url){ return ESP_OK; } -esp_err_t start_ota(const char * bin_url) +esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length) { - if(is_recovery_running){ - return process_recovery_ota(bin_url); +#if RECOVERY_APPLICATION + return process_recovery_ota(bin_url,bin_buffer,length); +#else + if(!bin_url){ + ESP_LOGE(TAG,"missing URL parameter. Unable to start OTA"); + return ESP_ERR_INVALID_ARG; } ESP_LOGW(TAG, "Called to update the firmware from url: %s",bin_url); if(config_set_value(NVS_TYPE_STR, "fwurl", bin_url) != ESP_OK){ @@ -610,4 +801,6 @@ esp_err_t start_ota(const char * bin_url) ESP_LOGW(TAG, "Rebooting to recovery to complete the installation"); return guided_factory(); + return ESP_OK; +#endif } diff --git a/components/squeezelite-ota/squeezelite-ota.h b/components/squeezelite-ota/squeezelite-ota.h index 76492565..c523d3cc 100644 --- a/components/squeezelite-ota/squeezelite-ota.h +++ b/components/squeezelite-ota/squeezelite-ota.h @@ -30,4 +30,5 @@ esp_err_t start_ota(const char * bin_url); const char * ota_get_status(); uint8_t ota_get_pct_complete(); +esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length); diff --git a/components/telnet/component.mk b/components/telnet/component.mk index 6c466cfb..2fd9a49d 100644 --- a/components/telnet/component.mk +++ b/components/telnet/component.mk @@ -10,6 +10,4 @@ COMPONENT_SRCDIRS := . COMPONENT_SRCDIRS += ./libtelnet COMPONENT_ADD_INCLUDEDIRS := . -COMPONENT_ADD_INCLUDEDIRS += ./libtelnet -COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/ - \ No newline at end of file +COMPONENT_PRIV_INCLUDEDIRS += ./libtelnet \ No newline at end of file diff --git a/components/telnet/telnet.c b/components/telnet/telnet.c index 552c678e..03bab437 100644 --- a/components/telnet/telnet.c +++ b/components/telnet/telnet.c @@ -38,6 +38,8 @@ #include "config.h" #include "nvs_utilities.h" #include "platform_esp32.h" +#include "messaging.h" +#include "trace.h" /************************************ @@ -47,15 +49,16 @@ #define TELNET_STACK_SIZE 8048 #define TELNET_RX_BUF 1024 -const static char tag[] = "telnet"; +const static char TAG[] = "telnet"; static int uart_fd=0; RingbufHandle_t buf_handle; -SemaphoreHandle_t xSemaphore = NULL; +//static SemaphoreHandle_t xSemaphore = NULL; static size_t send_chunk=300; static size_t log_buf_size=2000; //32-bit aligned size static bool bIsEnabled=false; static int partnerSocket=0; static telnet_t *tnHandle; +extern bool bypass_wifi_manager; /************************************ * Forward declarations @@ -68,22 +71,34 @@ static int stdout_fstat(int fd, struct stat * st); static ssize_t stdout_write(int fd, const void * data, size_t size); static char *eventToString(telnet_event_type_t type); static void handle_telnet_conn(); -static void process_logs( UBaseType_t bytes); - +static void process_logs( UBaseType_t bytes, bool is_write_op); +static bool bMirrorToUART=false; struct telnetUserData { int sockfd; telnet_t *tnHandle; char * rxbuf; }; - +bool is_serial_suppressed(){ + return bIsEnabled?!bMirrorToUART:false ; +} void init_telnet(){ char *val= get_nvs_value_alloc(NVS_TYPE_STR, "telnet_enable"); - if (!val || strlen(val) == 0 || !strcasestr("YX",val) ) { - ESP_LOGI(tag,"Telnet support disabled"); + if (!val || strlen(val) == 0 || !strcasestr("YXD",val) ) { + ESP_LOGI(TAG,"Telnet support disabled"); if(val) free(val); return; } + // if wifi manager is bypassed, there will possibly be no wifi available + // + bMirrorToUART = (strcasestr("D",val)!=NULL); + if(!bMirrorToUART && bypass_wifi_manager){ + // This isn't supposed to happen, as telnet won't start if wifi manager isn't + // started. So this is a safeguard only. + ESP_LOGW(TAG,"Wifi manager is not active. Forcing console on Serial output."); + } + + FREE_AND_NULL(val); val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block"); if(val){ send_chunk=atol(val); @@ -97,18 +112,21 @@ void init_telnet(){ log_buf_size=log_buf_size>0?log_buf_size:4000; } // Create the semaphore to guard a shared resource. - vSemaphoreCreateBinary( xSemaphore ); + //vSemaphoreCreateBinary( xSemaphore ); // Redirect the output to our telnet handler as soon as possible - StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)heap_caps_malloc(sizeof(StaticRingbuffer_t), MALLOC_CAP_SPIRAM); - uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM); + StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)malloc(sizeof(StaticRingbuffer_t) ); + // All non-split ring buffer must have their memory alignment set to 32 bits. + uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT ); buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct); if (buf_handle == NULL) { - ESP_LOGE(tag,"Failed to create ring buffer for telnet!"); + ESP_LOGE(TAG,"Failed to create ring buffer for telnet!"); + messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Failed to allocate memory for telnet buffer"); + return; } - ESP_LOGI(tag, "***Redirecting log output to telnet"); + ESP_LOGI(TAG, "***Redirecting log output to telnet"); const esp_vfs_t vfs = { .flags = ESP_VFS_FLAG_DEFAULT, .write = &stdout_write, @@ -116,8 +134,12 @@ void init_telnet(){ .fstat = &stdout_fstat, .close = &stdout_close, .read = &stdout_read, + }; - uart_fd=open("/dev/uart/0", O_RDWR); + + if(bMirrorToUART){ + uart_fd=open("/dev/uart/0", O_RDWR); + } ESP_ERROR_CHECK(esp_vfs_register("/dev/pkspstdout", &vfs, NULL)); freopen("/dev/pkspstdout", "w", stdout); freopen("/dev/pkspstdout", "w", stderr); @@ -125,11 +147,11 @@ void init_telnet(){ } void start_telnet(void * pvParameter){ static bool isStarted=false; - StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - StackType_t *xStack = malloc(TELNET_STACK_SIZE); + StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT)); + StackType_t *xStack = heap_caps_malloc(TELNET_STACK_SIZE,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT)); if(!isStarted && bIsEnabled) { - xTaskCreateStatic( (TaskFunction_t) &telnet_task, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer); + xTaskCreateStatic( (TaskFunction_t) &telnet_task, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_MAIN_PRIO , xStack, xTaskBuffer); isStarted=true; } } @@ -142,13 +164,15 @@ static void telnet_task(void *data) { int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); if (rc < 0) { - ESP_LOGE(tag, "bind: %d (%s)", errno, strerror(errno)); + ESP_LOGE(TAG, "bind: %d (%s)", errno, strerror(errno)); + close(serverSocket); return; } rc = listen(serverSocket, 5); if (rc < 0) { - ESP_LOGE(tag, "listen: %d (%s)", errno, strerror(errno)); + ESP_LOGE(TAG, "listen: %d (%s)", errno, strerror(errno)); + close(serverSocket); return; } @@ -156,16 +180,17 @@ static void telnet_task(void *data) { socklen_t len = sizeof(serverAddr); rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len); if (rc < 0 ){ - ESP_LOGE(tag, "accept: %d (%s)", errno, strerror(errno)); + ESP_LOGE(TAG, "accept: %d (%s)", errno, strerror(errno)); return; } else { partnerSocket = rc; - ESP_LOGD(tag, "We have a new client connection!"); + ESP_LOGD(TAG, "We have a new client connection!"); handle_telnet_conn(); - ESP_LOGD(tag, "Telnet connection terminated"); + ESP_LOGD(TAG, "Telnet connection terminated"); } } + close(serverSocket); vTaskDelete(NULL); } @@ -228,7 +253,9 @@ void process_received_data(const char * buffer, size_t size){ command[size]='\0'; if(command[0]!='\r' && command[0]!='\n'){ // echo the command buffer out to uart and run - write(uart_fd, command, size); + if(bMirrorToUART){ + write(uart_fd, command, size); + } run_command((char *)command); } free(command); @@ -265,23 +292,23 @@ static void handle_telnet_events( } // myhandle_telnet_events -static void process_logs(UBaseType_t count){ +static void process_logs( UBaseType_t bytes, bool is_write_op){ //Receive an item from no-split ring buffer size_t item_size; - UBaseType_t uxItemsWaiting; - UBaseType_t uxBytesToSend=count; + UBaseType_t uxItemsWaiting; + UBaseType_t uxBytesToSend=bytes; - 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); + vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting); + bool is_space_available = ((log_buf_size-uxItemsWaiting)>=bytes && log_buf_size>uxItemsWaiting); + if( is_space_available && (is_write_op || partnerSocket == 0) ){ + // there's still some room left in the buffer, and we're either + // processing a write operation or telnet isn't connected yet. return; } + if(is_write_op && !is_space_available && uxBytesToSend==0){ + // flush at least the size of a full chunk + uxBytesToSend = send_chunk; + } while(uxBytesToSend>0){ char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(50), uxBytesToSend); @@ -322,7 +349,7 @@ static void handle_telnet_conn() { pTelnetUserData->sockfd = partnerSocket; // flush all the log buffer on connect - process_logs(0); + process_logs(log_buf_size, false); while(1) { //ESP_LOGD(tag, "waiting for data"); @@ -339,7 +366,7 @@ static void handle_telnet_conn() { partnerSocket = 0; return; } - process_logs(send_chunk); + process_logs(send_chunk, false); taskYIELD(); } @@ -348,37 +375,24 @@ static void handle_telnet_conn() { // ******************* stdout/stderr Redirection to ringbuffer static ssize_t stdout_write(int fd, const void * data, size_t size) { - if (xSemaphoreTake(xSemaphore, (TickType_t) 10) == pdTRUE) { - // #1 Write to ringbuffer - if (buf_handle == NULL) { - printf("%s() ABORT. file handle _log_remote_fp is NULL\n", - __FUNCTION__); - } else { - //Send an item - UBaseType_t res = xRingbufferSend(buf_handle, data, size, - pdMS_TO_TICKS(100)); - if (res != pdTRUE) { - // flush some entries - process_logs(size); - res = xRingbufferSend(buf_handle, data, size, - pdMS_TO_TICKS(100)); - if (res != pdTRUE) { - - printf("%s() ABORT. Unable to store log entry in buffer\n", - __FUNCTION__); - } - } - } - xSemaphoreGive(xSemaphore); + // #1 Write to ringbuffer + if (buf_handle == NULL) { + printf("%s() ABORT. file handle _log_remote_fp is NULL\n", + __FUNCTION__); } else { - // We could not obtain the semaphore and can therefore not access - // the shared resource safely. + // flush the buffer if needed + process_logs(size, true); + //Send an item + UBaseType_t res = xRingbufferSend(buf_handle, data, size, pdMS_TO_TICKS(10)); + assert(res == pdTRUE); + } - return write(uart_fd, data, size); + return bMirrorToUART?write(uart_fd, data, size):size; } static ssize_t stdout_read(int fd, void* data, size_t size) { - return read(fd, data, size); + //return read(fd, data, size); + return 0; } static int stdout_open(const char * path, int flags, int mode) { diff --git a/components/telnet/telnet.h b/components/telnet/telnet.h index 255fa3d6..eb563ece 100644 --- a/components/telnet/telnet.h +++ b/components/telnet/telnet.h @@ -1,3 +1,4 @@ void init_telnet(); void start_telnet(void * pvParameter); +extern bool is_serial_suppressed(); diff --git a/components/tools/platform_esp32.h b/components/tools/platform_esp32.h index 06a8c7bf..b7368101 100644 --- a/components/tools/platform_esp32.h +++ b/components/tools/platform_esp32.h @@ -31,9 +31,4 @@ extern bool wait_for_wifi(); extern void console_start(); extern pthread_cond_t wifi_connect_suspend_cond; extern pthread_t wifi_connect_suspend_mutex; -typedef enum { - INFO, - WARNING, - ERROR -} message_severity_t; -extern void set_status_message(message_severity_t severity, const char * message); + diff --git a/components/tools/trace.h b/components/tools/trace.h index ae24c70a..82c20434 100644 --- a/components/tools/trace.h +++ b/components/tools/trace.h @@ -28,5 +28,20 @@ #define STR(macro) QUOTE(macro) #endif #define ESP_LOG_DEBUG_EVENT(tag,e) ESP_LOGD(tag,"evt: " e) +#ifndef STR_OR_ALT +#define STR_OR_ALT(str,alt) (str?str:alt) +#endif +extern const char unknown_string_placeholder[]; +extern const char * str_or_unknown(const char * str); + +#ifndef FREE_AND_NULL +#define FREE_AND_NULL(x) if(x) { free(x); x=NULL; } +#endif + +#ifndef CASE_TO_STR +#define CASE_TO_STR(x) case x: return STR(x); break; +#endif +#define START_FREE_MEM_CHECK(a) size_t a=heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#define CHECK_RESET_FREE_MEM_CHECK(a,b) ESP_LOGV(__FUNCTION__ ,b "Mem used: %i",a-heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); a=heap_caps_get_free_size(MALLOC_CAP_INTERNAL) diff --git a/components/wifi-manager/_esp_http_server.h b/components/wifi-manager/_esp_http_server.h new file mode 100644 index 00000000..bde57c65 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +#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_ */ diff --git a/components/wifi-manager/_esp_httpd_main.c b/components/wifi-manager/_esp_httpd_main.c new file mode 100644 index 00000000..255f6d78 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +#include +#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; +} + diff --git a/components/wifi-manager/code.js b/components/wifi-manager/code.js index 70dd7f1f..17093d99 100644 --- a/components/wifi-manager/code.js +++ b/components/wifi-manager/code.js @@ -10,7 +10,28 @@ if (!String.prototype.format) { }); }; } +var nvs_type_t = { + NVS_TYPE_U8 : 0x01, /*!< Type uint8_t */ + NVS_TYPE_I8 : 0x11, /*!< Type int8_t */ + NVS_TYPE_U16 : 0x02, /*!< Type uint16_t */ + NVS_TYPE_I16 : 0x12, /*!< Type int16_t */ + NVS_TYPE_U32 : 0x04, /*!< Type uint32_t */ + NVS_TYPE_I32 : 0x14, /*!< Type int32_t */ + NVS_TYPE_U64 : 0x08, /*!< Type uint64_t */ + NVS_TYPE_I64 : 0x18, /*!< Type int64_t */ + NVS_TYPE_STR : 0x21, /*!< Type string */ + NVS_TYPE_BLOB : 0x42, /*!< Type blob */ + NVS_TYPE_ANY : 0xff /*!< Must be last */ +} ; +var task_state_t = { + 0 : "eRunning", /*!< A task is querying the state of itself, so must be running. */ + 1 : "eReady", /*!< The task being queried is in a read or pending ready list. */ + 2 : "eBlocked", /*!< The task being queried is in the Blocked state. */ + 3 : "eSuspended", /*!< The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */ + 4 : "eDeleted" + +} var releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases'; var recovery = false; var enableAPTimer = true; @@ -19,7 +40,6 @@ var commandHeader = 'squeezelite -b 500:2000 -d all=info '; var pname, ver, otapct, otadsc; var blockAjax = false; var blockFlashButton = false; -var lastMsg = ''; var apList = null; var selectedSSID = ""; @@ -189,20 +209,29 @@ $(document).ready(function(){ $("input#autoexec-cb").on("click", function() { var data = { 'timestamp': Date.now() }; autoexec = (this.checked)?1:0; - data['autoexec'] = autoexec; - showMessage('please wait for the ESP32 to reboot', 'WARNING'); + data['config'] = {}; + data['config'] = { + autoexec : { + value : autoexec, + type : 33 + } + } + + + showMessage('please wait for the ESP32 to reboot', 'MESSAGING_WARNING'); $.ajax({ url: '/config.json', dataType: 'text', method: 'POST', cache: false, - headers: { "X-Custom-autoexec": autoexec }, +// headers: { "X-Custom-autoexec": autoexec }, contentType: 'application/json; charset=utf-8', - data: JSON.stringify(data), + data: JSON.stringify(data), + error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); }, complete: function(response) { //var returnedResponse = JSON.parse(response.responseText); @@ -219,7 +248,7 @@ $(document).ready(function(){ error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); }, complete: function(response) { console.log('reboot call completed'); @@ -232,20 +261,26 @@ $(document).ready(function(){ $("input#save-autoexec1").on("click", function() { var data = { 'timestamp': Date.now() }; autoexec1 = $("#autoexec1").val(); - data['autoexec1'] = autoexec1; + data['config'] = {}; + data['config'] = { + autoexec1 : { + value : autoexec1, + type : 33 + } + } $.ajax({ url: '/config.json', dataType: 'text', method: 'POST', cache: false, - headers: { "X-Custom-autoexec1": autoexec1 }, +// headers: { "X-Custom-autoexec1": autoexec1 }, contentType: 'application/json; charset=utf-8', data: JSON.stringify(data), error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); } }); console.log('sent config JSON with headers:', autoexec1); @@ -254,15 +289,19 @@ $(document).ready(function(){ $("input#save-gpio").on("click", function() { var data = { 'timestamp': Date.now() }; + var config = {}; + var headers = {}; $("input.gpio").each(function() { var id = $(this)[0].id; var pin = $(this).val(); if (pin != '') { - headers["X-Custom-"+id] = pin; - data[id] = pin; + config[id] = {}; + config[id].value = pin; + config[id].type = nvs_type_t.NVS_TYPE_STR; } }); + data['config'] = config; $.ajax({ url: '/config.json', dataType: 'text', @@ -274,7 +313,7 @@ $(document).ready(function(){ error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); } }); console.log('sent config JSON with headers:', JSON.stringify(headers)); @@ -284,23 +323,40 @@ $(document).ready(function(){ $("#save-nvs").on("click", function() { var headers = {}; var data = { 'timestamp': Date.now() }; + var config = {}; $("input.nvs").each(function() { var key = $(this)[0].id; var val = $(this).val(); + var nvs_type = parseInt($(this)[0].attributes.nvs_type.nodeValue,10); if (key != '') { - headers["X-Custom-"+key] = val; - data[key] = {}; - data[key].value = val; - data[key].type = 33; + config[key] = {}; + if(nvs_type == nvs_type_t.NVS_TYPE_U8 + || nvs_type == nvs_type_t.NVS_TYPE_I8 + || nvs_type == nvs_type_t.NVS_TYPE_U16 + || nvs_type == nvs_type_t.NVS_TYPE_I16 + || nvs_type == nvs_type_t.NVS_TYPE_U32 + || nvs_type == nvs_type_t.NVS_TYPE_I32 + || nvs_type == nvs_type_t.NVS_TYPE_U64 + || nvs_type == nvs_type_t.NVS_TYPE_I64) { + config[key].value = parseInt(val); + } + else { + config[key].value = val; + } + + + config[key].type = nvs_type; } }); var key = $("#nvs-new-key").val(); var val = $("#nvs-new-value").val(); if (key != '') { - headers["X-Custom-"+key] = val; - data[key] = {}; - data[key].value = val; +// headers["X-Custom-" +key] = val; + config[key] = {}; + config[key].value = val; + config[key].type = 33; } + data['config'] = config; $.ajax({ url: '/config.json', dataType: 'text', @@ -308,35 +364,64 @@ $(document).ready(function(){ cache: false, headers: headers, contentType: 'application/json; charset=utf-8', - data: JSON.stringify(data), + data : JSON.stringify(data), error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); } }); console.log('sent config JSON with headers:', JSON.stringify(headers)); console.log('sent config JSON with data:', JSON.stringify(data)); }); - + $("#fwUpload").on("click", function() { + var upload_path = "/flash.json"; + var fileInput = document.getElementById("flashfilename").files; + if (fileInput.length == 0) { + alert("No file selected!"); + } else { + var file = fileInput[0]; + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (xhttp.readyState == 4) { + if (xhttp.status == 200) { + showMessage(xhttp.responseText, 'MESSAGING_INFO') + } else if (xhttp.status == 0) { + showMessage("Upload connection was closed abruptly!", 'MESSAGING_ERROR'); + } else { + showMessage(xhttp.status + " Error!\n" + xhttp.responseText, 'MESSAGING_ERROR'); + } + } + }; + xhttp.open("POST", upload_path, true); + xhttp.send(file); + } + enableStatusTimer = true; + }); $("#flash").on("click", function() { var data = { 'timestamp': Date.now() }; if (blockFlashButton) return; blockFlashButton = true; var url = $("#fwurl").val(); - data['fwurl'] = url; + data['config'] = { + fwurl : { + value : url, + type : 33 + + } + }; + $.ajax({ url: '/config.json', dataType: 'text', method: 'POST', cache: false, - headers: { "X-Custom-fwurl": url }, contentType: 'application/json; charset=utf-8', - data: JSON.stringify(data), + data: JSON.stringify(data), error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); } }); enableStatusTimer = true; @@ -509,13 +594,17 @@ function performConnect(conntype){ dataType: 'text', method: 'POST', cache: false, - headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd, 'X-Custom-host_name': dhcpname }, +// headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd, 'X-Custom-host_name': dhcpname }, contentType: 'application/json; charset=utf-8', - data: { 'timestamp': Date.now()}, + data: JSON.stringify({ 'timestamp': Date.now(), + 'ssid' : selectedSSID, + 'pwd' : pwd, + 'host_name' : dhcpname + }), error: function (xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); } }); @@ -564,11 +653,76 @@ function refreshAPHTML(data){ $( "#wifi-list" ).html(h) } +function getMessages() { + $.getJSON("/messages.json?1", function(data) { + data.forEach(function(msg) { + var msg_age = msg["current_time"] - msg["sent_time"]; + var msg_time = new Date(); + msg_time.setTime( msg_time.getTime() - msg_age ); + switch (msg["class"]) { + case "MESSAGING_CLASS_OTA": + //message: "{"ota_dsc":"Erasing flash complete","ota_pct":0}" + var ota_data = JSON.parse(msg["message"]); + if (ota_data.hasOwnProperty('ota_pct') && ota_data['ota_pct'] != 0){ + otapct = ota_data['ota_pct']; + $('.progress-bar').css('width', otapct+'%').attr('aria-valuenow', otapct); + $('.progress-bar').html(otapct+'%'); + } + if (ota_data.hasOwnProperty('ota_dsc') && ota_data['ota_dsc'] != ''){ + otadsc = ota_data['ota_dsc']; + $("span#flash-status").html(otadsc); + if (otadsc.match(/Error:/) || otapct > 95) { + blockFlashButton = false; + enableStatusTimer = true; + } + } + break; + case "MESSAGING_CLASS_STATS": + // for task states, check structure : task_state_t + var stats_data = JSON.parse(msg["message"]); + console.log(msg_time.toLocaleString() + " - Number of tasks on the ESP32: " + stats_data["ntasks"]); + var stats_tasks = stats_data["tasks"]; + console.log(msg_time.toLocaleString() + '\tname' + '\tcpu' + '\tstate'+ '\tminstk'+ '\tbprio'+ '\tcprio'+ '\tnum' ); + stats_tasks.forEach(function(task) { + console.log(msg_time.toLocaleString() + '\t' + task["nme"] + '\t'+ task["cpu"] + '\t'+ task_state_t[task["st"]]+ '\t'+ task["minstk"]+ '\t'+ task["bprio"]+ '\t'+ task["cprio"]+ '\t'+ task["num"]); + }); + break; + case "MESSAGING_CLASS_SYSTEM": + showMessage(msg["message"], msg["type"],msg_age); + + $("#syslogTable").append( + ""+ + ""+msg_time.toLocaleString()+""+ + ""+msg["message"]+""+ + "" + ); + break; + default: + break; + } + }); + + }) + .fail(function(xhr, ajaxOptions, thrownError) { + console.log(xhr.status); + console.log(thrownError); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); + }); + /* + Minstk is minimum stack space left +Bprio is base priority +cprio is current priority +nme is name +st is task state. I provided a "typedef" that you can use to convert to text +cpu is cpu percent used +*/ +} function checkStatus(){ RepeatCheckStatusInterval(); if (!enableStatusTimer) return; if (blockAjax) return; blockAjax = true; + getMessages(); $.getJSON( "/status.json", function( data ) { if (data.hasOwnProperty('ssid') && data['ssid'] != ""){ if (data["ssid"] === selectedSSID){ @@ -659,20 +813,24 @@ function checkStatus(){ $("#otadiv").show(); $('a[href^="#tab-audio"]').hide(); $('a[href^="#tab-gpio"]').show(); + $('#uploaddiv').show(); $("footer.footer").removeClass('sl'); $("footer.footer").addClass('recovery'); $("#boot-button").html('Reboot'); $("#boot-form").attr('action', '/reboot_ota.json'); + enableStatusTimer = true; } else { recovery = false; $("#otadiv").hide(); $('a[href^="#tab-audio"]').show(); $('a[href^="#tab-gpio"]').hide(); + $('#uploaddiv').hide(); $("footer.footer").removeClass('recovery'); $("footer.footer").addClass('sl'); $("#boot-button").html('Recovery'); $("#boot-form").attr('action', '/recovery.json'); + enableStatusTimer = false; } } @@ -683,29 +841,10 @@ function checkStatus(){ ver = data['version']; $("span#foot-fw").html("fw: "+ver+", mode: "+pname+""); } - if (data.hasOwnProperty('ota_pct') && data['ota_pct'] != 0){ - otapct = data['ota_pct']; - $('.progress-bar').css('width', otapct+'%').attr('aria-valuenow', otapct); - $('.progress-bar').html(otapct+'%'); - } - if (data.hasOwnProperty('ota_dsc') && data['ota_dsc'] != ''){ - otadsc = data['ota_dsc']; - $("span#flash-status").html(otadsc); - if (otadsc.match(/Error:/) || otapct > 95) { - blockFlashButton = false; - enableStatusTimer = true; - } - } else { + else { $("span#flash-status").html(''); } - if (data.hasOwnProperty('message') && data['message'] != ''){ - var msg = data['message'].text; - var severity = data['message'].severity; - if (msg != lastMsg) { - showMessage(msg, severity); - lastMsg = msg; - } - } + if (data.hasOwnProperty('Voltage')) { var voltage = data['Voltage']; var layer; @@ -735,7 +874,7 @@ function checkStatus(){ .fail(function(xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); blockAjax = false; }); } @@ -770,7 +909,7 @@ function getConfig() { ""+ ""+key+""+ ""+ - ""+ + ""+ ""+ "" ); @@ -783,7 +922,7 @@ function getConfig() { ""+ ""+ ""+ - ""+ + ""+ // todo: provide a way to choose field type ""+ "" ); @@ -791,19 +930,23 @@ function getConfig() { .fail(function(xhr, ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); - if (thrownError != '') showMessage(thrownError, 'ERROR'); + if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR'); blockAjax = false; }); } -function showMessage(message, severity) { - if (severity == 'INFO') { + +function showMessage(message, severity, age=0) { + if (severity == 'MESSAGING_INFO') { $('#message').css('background', '#6af'); - } else if (severity == 'WARNING') { + } else if (severity == 'MESSAGING_WARNING') { $('#message').css('background', '#ff0'); + } else if (severity == 'MESSAGING_ERROR' ) { + $('#message').css('background', '#f00'); } else { $('#message').css('background', '#f00'); } + $('#message').html(message); $("#content").fadeTo("slow", 0.3, function() { $("#message").show(500).delay(5000).hide(500, function() { @@ -815,3 +958,6 @@ function showMessage(message, severity) { function inRange(x, min, max) { return ((x-min)*(x-max) <= 0); } + + + diff --git a/components/wifi-manager/component.mk b/components/wifi-manager/component.mk index 7fef2d6b..491db5eb 100644 --- a/components/wifi-manager/component.mk +++ b/components/wifi-manager/component.mk @@ -7,14 +7,8 @@ # please read the SDK documents if you need to do this. # COMPONENT_EMBED_FILES := style.css code.js index.html bootstrap.min.css.gz jquery.min.js.gz popper.min.js.gz bootstrap.min.js.gz - -#CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG -CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO \ - -I$(COMPONENT_PATH)/../tools -COMPONENT_ADD_INCLUDEDIRS := . -COMPONENT_ADD_INCLUDEDIRS += $(COMPONENT_PATH)/../tools -COMPONENT_ADD_INCLUDEDIRS += $(COMPONENT_PATH)/../squeezelite-ota -COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/ - +COMPONENT_ADD_INCLUDEDIRS := . +COMPONENT_EXTRA_INCLUDES += $(IDF_PATH)/components/esp_http_server/src $(IDF_PATH)/components/esp_http_server/src/port/esp32 $(IDF_PATH)/components/esp_http_server/src/util $(IDF_PATH)/components/esp_http_server/src/ +CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO diff --git a/components/wifi-manager/dns_server.c b/components/wifi-manager/dns_server.c index eb61323d..f084c90b 100644 --- a/components/wifi-manager/dns_server.c +++ b/components/wifi-manager/dns_server.c @@ -73,11 +73,7 @@ void dns_server_stop(){ } - void dns_server(void *pvParameters) { - - - struct sockaddr_in sa, ra; /* Set redirection DNS hijack to the access point IP */ diff --git a/components/wifi-manager/http_server_handlers.c b/components/wifi-manager/http_server_handlers.c new file mode 100644 index 00000000..5e6f7b25 --- /dev/null +++ b/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 +#include "squeezelite-ota.h" +#include "nvs_utilities.h" +#include +#include +#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[]="Redirecting to Captive Portal

Please wait, refreshing. If page does not refresh, click here to login.

"; + +/** + * @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; +} diff --git a/components/wifi-manager/http_server_handlers.h b/components/wifi-manager/http_server_handlers.h new file mode 100644 index 00000000..390394a1 --- /dev/null +++ b/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 +#include +#include +#include +#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 diff --git a/components/wifi-manager/index.html b/components/wifi-manager/index.html index 07a894db..445bd99a 100644 --- a/components/wifi-manager/index.html +++ b/components/wifi-manager/index.html @@ -4,19 +4,12 @@ - - - - - - - - - - - - - + + + + + + esp32-wifi-manager @@ -74,7 +67,7 @@ Firmware