Compare commits
	
		
			1 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 76648f4c2d | 
							
								
								
									
										246
									
								
								.clang-format
									
									
									
									
									
								
							
							
						
						@@ -1,246 +0,0 @@
 | 
				
			|||||||
# Clang format version: 18.1.3
 | 
					 | 
				
			||||||
---
 | 
					 | 
				
			||||||
BasedOnStyle: LLVM
 | 
					 | 
				
			||||||
AccessModifierOffset: -2
 | 
					 | 
				
			||||||
AlignAfterOpenBracket: BlockIndent
 | 
					 | 
				
			||||||
AlignArrayOfStructures: None
 | 
					 | 
				
			||||||
AlignConsecutiveAssignments:
 | 
					 | 
				
			||||||
  Enabled: false
 | 
					 | 
				
			||||||
  AcrossEmptyLines: false
 | 
					 | 
				
			||||||
  AcrossComments: false
 | 
					 | 
				
			||||||
  AlignCompound: false
 | 
					 | 
				
			||||||
  AlignFunctionPointers: false
 | 
					 | 
				
			||||||
  PadOperators: true
 | 
					 | 
				
			||||||
AlignConsecutiveBitFields:
 | 
					 | 
				
			||||||
  Enabled: true
 | 
					 | 
				
			||||||
  AcrossEmptyLines: false
 | 
					 | 
				
			||||||
  AcrossComments: false
 | 
					 | 
				
			||||||
  AlignCompound: false
 | 
					 | 
				
			||||||
  AlignFunctionPointers: false
 | 
					 | 
				
			||||||
  PadOperators: false
 | 
					 | 
				
			||||||
AlignConsecutiveDeclarations:
 | 
					 | 
				
			||||||
  Enabled: false
 | 
					 | 
				
			||||||
  AcrossEmptyLines: false
 | 
					 | 
				
			||||||
  AcrossComments: false
 | 
					 | 
				
			||||||
  AlignCompound: false
 | 
					 | 
				
			||||||
  AlignFunctionPointers: false
 | 
					 | 
				
			||||||
  PadOperators: false
 | 
					 | 
				
			||||||
AlignConsecutiveMacros:
 | 
					 | 
				
			||||||
  Enabled: true
 | 
					 | 
				
			||||||
  AcrossEmptyLines: false
 | 
					 | 
				
			||||||
  AcrossComments: false
 | 
					 | 
				
			||||||
  AlignCompound: false
 | 
					 | 
				
			||||||
  AlignFunctionPointers: false
 | 
					 | 
				
			||||||
  PadOperators: false
 | 
					 | 
				
			||||||
AlignConsecutiveShortCaseStatements:
 | 
					 | 
				
			||||||
  Enabled: true
 | 
					 | 
				
			||||||
  AcrossEmptyLines: false
 | 
					 | 
				
			||||||
  AcrossComments: false
 | 
					 | 
				
			||||||
  AlignCaseColons: false
 | 
					 | 
				
			||||||
AlignEscapedNewlines: Left
 | 
					 | 
				
			||||||
AlignOperands: Align
 | 
					 | 
				
			||||||
AlignTrailingComments:
 | 
					 | 
				
			||||||
  Kind: Always
 | 
					 | 
				
			||||||
  OverEmptyLines: 0
 | 
					 | 
				
			||||||
AllowAllArgumentsOnNextLine: true
 | 
					 | 
				
			||||||
AllowAllParametersOfDeclarationOnNextLine: true
 | 
					 | 
				
			||||||
AllowBreakBeforeNoexceptSpecifier: Never
 | 
					 | 
				
			||||||
AllowShortBlocksOnASingleLine: Empty
 | 
					 | 
				
			||||||
AllowShortCaseLabelsOnASingleLine: true
 | 
					 | 
				
			||||||
AllowShortCompoundRequirementOnASingleLine: true
 | 
					 | 
				
			||||||
AllowShortEnumsOnASingleLine: false
 | 
					 | 
				
			||||||
AllowShortFunctionsOnASingleLine: Empty
 | 
					 | 
				
			||||||
AllowShortIfStatementsOnASingleLine: Never
 | 
					 | 
				
			||||||
AllowShortLambdasOnASingleLine: Empty
 | 
					 | 
				
			||||||
AllowShortLoopsOnASingleLine: true
 | 
					 | 
				
			||||||
AlwaysBreakAfterDefinitionReturnType: None
 | 
					 | 
				
			||||||
AlwaysBreakAfterReturnType: None
 | 
					 | 
				
			||||||
AlwaysBreakBeforeMultilineStrings: false
 | 
					 | 
				
			||||||
AlwaysBreakTemplateDeclarations: MultiLine
 | 
					 | 
				
			||||||
AttributeMacros:
 | 
					 | 
				
			||||||
  - __capability
 | 
					 | 
				
			||||||
BinPackArguments: true
 | 
					 | 
				
			||||||
BinPackParameters: true
 | 
					 | 
				
			||||||
BitFieldColonSpacing: Both
 | 
					 | 
				
			||||||
BraceWrapping:
 | 
					 | 
				
			||||||
  AfterCaseLabel: true
 | 
					 | 
				
			||||||
  AfterClass: false
 | 
					 | 
				
			||||||
  AfterControlStatement: Never
 | 
					 | 
				
			||||||
  AfterEnum: false
 | 
					 | 
				
			||||||
  AfterFunction: false
 | 
					 | 
				
			||||||
  AfterNamespace: false
 | 
					 | 
				
			||||||
  AfterObjCDeclaration: false
 | 
					 | 
				
			||||||
  AfterStruct: false
 | 
					 | 
				
			||||||
  AfterUnion: false
 | 
					 | 
				
			||||||
  AfterExternBlock: false
 | 
					 | 
				
			||||||
  BeforeCatch: false
 | 
					 | 
				
			||||||
  BeforeElse: false
 | 
					 | 
				
			||||||
  BeforeLambdaBody: false
 | 
					 | 
				
			||||||
  BeforeWhile: false
 | 
					 | 
				
			||||||
  IndentBraces: false
 | 
					 | 
				
			||||||
  SplitEmptyFunction: false
 | 
					 | 
				
			||||||
  SplitEmptyRecord: true
 | 
					 | 
				
			||||||
  SplitEmptyNamespace: true
 | 
					 | 
				
			||||||
BreakAdjacentStringLiterals: true
 | 
					 | 
				
			||||||
BreakAfterAttributes: Always
 | 
					 | 
				
			||||||
BreakAfterJavaFieldAnnotations: false
 | 
					 | 
				
			||||||
BreakArrays: false
 | 
					 | 
				
			||||||
BreakBeforeBinaryOperators: NonAssignment
 | 
					 | 
				
			||||||
BreakBeforeBraces: Custom
 | 
					 | 
				
			||||||
BreakBeforeConceptDeclarations: Always
 | 
					 | 
				
			||||||
BreakBeforeInlineASMColon: OnlyMultiline
 | 
					 | 
				
			||||||
BreakBeforeTernaryOperators: true
 | 
					 | 
				
			||||||
BreakConstructorInitializers: BeforeColon
 | 
					 | 
				
			||||||
BreakInheritanceList: BeforeColon
 | 
					 | 
				
			||||||
BreakStringLiterals: true
 | 
					 | 
				
			||||||
ColumnLimit: 160
 | 
					 | 
				
			||||||
CommentPragmas: ""
 | 
					 | 
				
			||||||
CompactNamespaces: false
 | 
					 | 
				
			||||||
ConstructorInitializerIndentWidth: 2
 | 
					 | 
				
			||||||
ContinuationIndentWidth: 2
 | 
					 | 
				
			||||||
Cpp11BracedListStyle: true
 | 
					 | 
				
			||||||
DerivePointerAlignment: false
 | 
					 | 
				
			||||||
DisableFormat: false
 | 
					 | 
				
			||||||
EmptyLineAfterAccessModifier: Never
 | 
					 | 
				
			||||||
EmptyLineBeforeAccessModifier: LogicalBlock
 | 
					 | 
				
			||||||
ExperimentalAutoDetectBinPacking: false
 | 
					 | 
				
			||||||
FixNamespaceComments: true
 | 
					 | 
				
			||||||
ForEachMacros:
 | 
					 | 
				
			||||||
  - foreach
 | 
					 | 
				
			||||||
  - Q_FOREACH
 | 
					 | 
				
			||||||
  - BOOST_FOREACH
 | 
					 | 
				
			||||||
IfMacros:
 | 
					 | 
				
			||||||
  - KJ_IF_MAYBE
 | 
					 | 
				
			||||||
IncludeBlocks: Preserve
 | 
					 | 
				
			||||||
IncludeCategories:
 | 
					 | 
				
			||||||
  - Regex: ^"(llvm|llvm-c|clang|clang-c)/
 | 
					 | 
				
			||||||
    Priority: 2
 | 
					 | 
				
			||||||
    SortPriority: 0
 | 
					 | 
				
			||||||
    CaseSensitive: false
 | 
					 | 
				
			||||||
  - Regex: ^(<|"(gtest|gmock|isl|json)/)
 | 
					 | 
				
			||||||
    Priority: 3
 | 
					 | 
				
			||||||
    SortPriority: 0
 | 
					 | 
				
			||||||
    CaseSensitive: false
 | 
					 | 
				
			||||||
  - Regex: .*
 | 
					 | 
				
			||||||
    Priority: 1
 | 
					 | 
				
			||||||
    SortPriority: 0
 | 
					 | 
				
			||||||
    CaseSensitive: false
 | 
					 | 
				
			||||||
IncludeIsMainRegex: ""
 | 
					 | 
				
			||||||
IncludeIsMainSourceRegex: ""
 | 
					 | 
				
			||||||
IndentAccessModifiers: false
 | 
					 | 
				
			||||||
IndentCaseBlocks: false
 | 
					 | 
				
			||||||
IndentCaseLabels: true
 | 
					 | 
				
			||||||
IndentExternBlock: NoIndent
 | 
					 | 
				
			||||||
IndentGotoLabels: false
 | 
					 | 
				
			||||||
IndentPPDirectives: None
 | 
					 | 
				
			||||||
IndentRequiresClause: false
 | 
					 | 
				
			||||||
IndentWidth: 2
 | 
					 | 
				
			||||||
IndentWrappedFunctionNames: true
 | 
					 | 
				
			||||||
InsertBraces: true
 | 
					 | 
				
			||||||
InsertNewlineAtEOF: true
 | 
					 | 
				
			||||||
InsertTrailingCommas: None
 | 
					 | 
				
			||||||
IntegerLiteralSeparator:
 | 
					 | 
				
			||||||
  Binary: 0
 | 
					 | 
				
			||||||
  BinaryMinDigits: 0
 | 
					 | 
				
			||||||
  Decimal: 0
 | 
					 | 
				
			||||||
  DecimalMinDigits: 0
 | 
					 | 
				
			||||||
  Hex: 0
 | 
					 | 
				
			||||||
  HexMinDigits: 0
 | 
					 | 
				
			||||||
JavaScriptQuotes: Leave
 | 
					 | 
				
			||||||
JavaScriptWrapImports: true
 | 
					 | 
				
			||||||
KeepEmptyLinesAtEOF: false
 | 
					 | 
				
			||||||
KeepEmptyLinesAtTheStartOfBlocks: true
 | 
					 | 
				
			||||||
LambdaBodyIndentation: Signature
 | 
					 | 
				
			||||||
Language: Cpp
 | 
					 | 
				
			||||||
LineEnding: LF
 | 
					 | 
				
			||||||
MacroBlockBegin: ""
 | 
					 | 
				
			||||||
MacroBlockEnd: ""
 | 
					 | 
				
			||||||
MaxEmptyLinesToKeep: 1
 | 
					 | 
				
			||||||
NamespaceIndentation: None
 | 
					 | 
				
			||||||
ObjCBinPackProtocolList: Auto
 | 
					 | 
				
			||||||
ObjCBlockIndentWidth: 2
 | 
					 | 
				
			||||||
ObjCBreakBeforeNestedBlockParam: true
 | 
					 | 
				
			||||||
ObjCSpaceAfterProperty: false
 | 
					 | 
				
			||||||
ObjCSpaceBeforeProtocolList: true
 | 
					 | 
				
			||||||
PPIndentWidth: -1
 | 
					 | 
				
			||||||
PackConstructorInitializers: BinPack
 | 
					 | 
				
			||||||
PenaltyBreakAssignment: 2
 | 
					 | 
				
			||||||
PenaltyBreakBeforeFirstCallParameter: 19
 | 
					 | 
				
			||||||
PenaltyBreakComment: 300
 | 
					 | 
				
			||||||
PenaltyBreakFirstLessLess: 120
 | 
					 | 
				
			||||||
PenaltyBreakOpenParenthesis: 0
 | 
					 | 
				
			||||||
PenaltyBreakScopeResolution: 500
 | 
					 | 
				
			||||||
PenaltyBreakString: 1000
 | 
					 | 
				
			||||||
PenaltyBreakTemplateDeclaration: 10
 | 
					 | 
				
			||||||
PenaltyExcessCharacter: 1000000
 | 
					 | 
				
			||||||
PenaltyIndentedWhitespace: 0
 | 
					 | 
				
			||||||
PenaltyReturnTypeOnItsOwnLine: 60
 | 
					 | 
				
			||||||
PointerAlignment: Right
 | 
					 | 
				
			||||||
QualifierAlignment: Leave
 | 
					 | 
				
			||||||
ReferenceAlignment: Pointer
 | 
					 | 
				
			||||||
ReflowComments: false
 | 
					 | 
				
			||||||
RemoveBracesLLVM: false
 | 
					 | 
				
			||||||
RemoveParentheses: Leave
 | 
					 | 
				
			||||||
RemoveSemicolon: false
 | 
					 | 
				
			||||||
RequiresClausePosition: OwnLine
 | 
					 | 
				
			||||||
RequiresExpressionIndentation: OuterScope
 | 
					 | 
				
			||||||
SeparateDefinitionBlocks: Leave
 | 
					 | 
				
			||||||
ShortNamespaceLines: 1
 | 
					 | 
				
			||||||
SkipMacroDefinitionBody: false
 | 
					 | 
				
			||||||
SortIncludes: Never
 | 
					 | 
				
			||||||
SortJavaStaticImport: Before
 | 
					 | 
				
			||||||
SortUsingDeclarations: LexicographicNumeric
 | 
					 | 
				
			||||||
SpaceAfterCStyleCast: false
 | 
					 | 
				
			||||||
SpaceAfterLogicalNot: false
 | 
					 | 
				
			||||||
SpaceAfterTemplateKeyword: false
 | 
					 | 
				
			||||||
SpaceAroundPointerQualifiers: Default
 | 
					 | 
				
			||||||
SpaceBeforeAssignmentOperators: true
 | 
					 | 
				
			||||||
SpaceBeforeCaseColon: false
 | 
					 | 
				
			||||||
SpaceBeforeCpp11BracedList: false
 | 
					 | 
				
			||||||
SpaceBeforeCtorInitializerColon: true
 | 
					 | 
				
			||||||
SpaceBeforeInheritanceColon: true
 | 
					 | 
				
			||||||
SpaceBeforeJsonColon: false
 | 
					 | 
				
			||||||
SpaceBeforeParens: ControlStatements
 | 
					 | 
				
			||||||
SpaceBeforeParensOptions:
 | 
					 | 
				
			||||||
  AfterControlStatements: true
 | 
					 | 
				
			||||||
  AfterForeachMacros: true
 | 
					 | 
				
			||||||
  AfterFunctionDeclarationName: false
 | 
					 | 
				
			||||||
  AfterFunctionDefinitionName: false
 | 
					 | 
				
			||||||
  AfterIfMacros: true
 | 
					 | 
				
			||||||
  AfterOverloadedOperator: true
 | 
					 | 
				
			||||||
  AfterPlacementOperator: true
 | 
					 | 
				
			||||||
  AfterRequiresInClause: false
 | 
					 | 
				
			||||||
  AfterRequiresInExpression: false
 | 
					 | 
				
			||||||
  BeforeNonEmptyParentheses: false
 | 
					 | 
				
			||||||
SpaceBeforeRangeBasedForLoopColon: true
 | 
					 | 
				
			||||||
SpaceBeforeSquareBrackets: false
 | 
					 | 
				
			||||||
SpaceInEmptyBlock: false
 | 
					 | 
				
			||||||
SpacesBeforeTrailingComments: 2
 | 
					 | 
				
			||||||
SpacesInAngles: Never
 | 
					 | 
				
			||||||
SpacesInContainerLiterals: false
 | 
					 | 
				
			||||||
SpacesInLineCommentPrefix:
 | 
					 | 
				
			||||||
  Minimum: 1
 | 
					 | 
				
			||||||
  Maximum: -1
 | 
					 | 
				
			||||||
SpacesInParens: Never
 | 
					 | 
				
			||||||
SpacesInParensOptions:
 | 
					 | 
				
			||||||
  InConditionalStatements: false
 | 
					 | 
				
			||||||
  InCStyleCasts: false
 | 
					 | 
				
			||||||
  InEmptyParentheses: false
 | 
					 | 
				
			||||||
  Other: false
 | 
					 | 
				
			||||||
SpacesInSquareBrackets: false
 | 
					 | 
				
			||||||
Standard: Auto
 | 
					 | 
				
			||||||
StatementAttributeLikeMacros:
 | 
					 | 
				
			||||||
  - Q_EMIT
 | 
					 | 
				
			||||||
StatementMacros:
 | 
					 | 
				
			||||||
  - Q_UNUSED
 | 
					 | 
				
			||||||
  - QT_REQUIRE_VERSION
 | 
					 | 
				
			||||||
TabWidth: 2
 | 
					 | 
				
			||||||
UseTab: Never
 | 
					 | 
				
			||||||
VerilogBreakBetweenInstancePorts: true
 | 
					 | 
				
			||||||
WhitespaceSensitiveMacros:
 | 
					 | 
				
			||||||
  - BOOST_PP_STRINGIZE
 | 
					 | 
				
			||||||
  - CF_SWIFT_NAME
 | 
					 | 
				
			||||||
  - NS_SWIFT_NAME
 | 
					 | 
				
			||||||
  - PP_STRINGIZE
 | 
					 | 
				
			||||||
  - STRINGIZE
 | 
					 | 
				
			||||||
BracedInitializerIndentWidth: 2
 | 
					 | 
				
			||||||
@@ -1,8 +0,0 @@
 | 
				
			|||||||
[codespell]
 | 
					 | 
				
			||||||
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/spell-check/.codespellrc
 | 
					 | 
				
			||||||
# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here:
 | 
					 | 
				
			||||||
ignore-words-list = ba,licence,varius
 | 
					 | 
				
			||||||
skip = ./.git,./.licenses,__pycache__,.clang-format,.codespellrc,.editorconfig,.flake8,.prettierignore,.yamllint.yml,.gitignore
 | 
					 | 
				
			||||||
builtin = clear,informal,en-GB_to_en-US
 | 
					 | 
				
			||||||
check-filenames =
 | 
					 | 
				
			||||||
check-hidden =
 | 
					 | 
				
			||||||
@@ -1,60 +0,0 @@
 | 
				
			|||||||
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/general/.editorconfig
 | 
					 | 
				
			||||||
# See: https://editorconfig.org/
 | 
					 | 
				
			||||||
# The formatting style defined in this file is the official standardized style to be used in all Arduino Tooling
 | 
					 | 
				
			||||||
# projects and should not be modified.
 | 
					 | 
				
			||||||
# Note: indent style for each file type is defined even when it matches the universal config in order to make it clear
 | 
					 | 
				
			||||||
# that this type has an official style.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*]
 | 
					 | 
				
			||||||
charset = utf-8
 | 
					 | 
				
			||||||
end_of_line = lf
 | 
					 | 
				
			||||||
indent_size = 2
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
insert_final_newline = true
 | 
					 | 
				
			||||||
trim_trailing_whitespace = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.{adoc,asc,asciidoc}]
 | 
					 | 
				
			||||||
indent_size = 2
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.{bash,sh}]
 | 
					 | 
				
			||||||
indent_size = 4
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.{c,cc,cp,cpp,cxx,h,hh,hpp,hxx,ii,inl,ino,ixx,pde,tpl,tpp,txx}]
 | 
					 | 
				
			||||||
indent_size = 2
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.{go,mod}]
 | 
					 | 
				
			||||||
indent_style = tab
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.java]
 | 
					 | 
				
			||||||
indent_size = 2
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.{js,jsx,json,jsonc,json5,ts,tsx}]
 | 
					 | 
				
			||||||
indent_size = 2
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.{md,mdx,mkdn,mdown,markdown}]
 | 
					 | 
				
			||||||
indent_size = unset
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.proto]
 | 
					 | 
				
			||||||
indent_size = 2
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.py]
 | 
					 | 
				
			||||||
indent_size = 4
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.svg]
 | 
					 | 
				
			||||||
indent_size = 2
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.{yaml,yml}]
 | 
					 | 
				
			||||||
indent_size = 2
 | 
					 | 
				
			||||||
indent_style = space
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[{.gitconfig,.gitmodules}]
 | 
					 | 
				
			||||||
indent_style = tab
 | 
					 | 
				
			||||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,5 +0,0 @@
 | 
				
			|||||||
.DS_Store
 | 
					 | 
				
			||||||
.lh
 | 
					 | 
				
			||||||
/.pio
 | 
					 | 
				
			||||||
/.vscode
 | 
					 | 
				
			||||||
/logs
 | 
					 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitpod.Dockerfile
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,2 +0,0 @@
 | 
				
			|||||||
FROM gitpod/workspace-python-3.11
 | 
					 | 
				
			||||||
USER gitpod
 | 
					 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
tasks:
 | 
					 | 
				
			||||||
  - command: pip install --upgrade pip && pip install -U platformio && platformio run
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
image:
 | 
					 | 
				
			||||||
  file: .gitpod.Dockerfile
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
vscode:
 | 
					 | 
				
			||||||
  extensions:
 | 
					 | 
				
			||||||
    - shardulm94.trailing-spaces
 | 
					 | 
				
			||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
exclude: |
 | 
					 | 
				
			||||||
  (?x)(
 | 
					 | 
				
			||||||
      ^\.github\/|
 | 
					 | 
				
			||||||
      LICENSE$
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
default_language_version:
 | 
					 | 
				
			||||||
  # force all unspecified python hooks to run python3
 | 
					 | 
				
			||||||
  python: python3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
repos:
 | 
					 | 
				
			||||||
  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
					 | 
				
			||||||
    rev: "v5.0.0"
 | 
					 | 
				
			||||||
    hooks:
 | 
					 | 
				
			||||||
      # Generic checks
 | 
					 | 
				
			||||||
      - id: check-case-conflict
 | 
					 | 
				
			||||||
      - id: check-symlinks
 | 
					 | 
				
			||||||
      - id: debug-statements
 | 
					 | 
				
			||||||
      - id: destroyed-symlinks
 | 
					 | 
				
			||||||
      - id: detect-private-key
 | 
					 | 
				
			||||||
      - id: end-of-file-fixer
 | 
					 | 
				
			||||||
        exclude: ^.*\.(bin|BIN)$
 | 
					 | 
				
			||||||
      - id: mixed-line-ending
 | 
					 | 
				
			||||||
        args: [--fix=lf]
 | 
					 | 
				
			||||||
      - id: trailing-whitespace
 | 
					 | 
				
			||||||
        args: [--markdown-linebreak-ext=md]
 | 
					 | 
				
			||||||
        exclude: ^platformio\.ini$
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - repo: https://github.com/codespell-project/codespell
 | 
					 | 
				
			||||||
    rev: "v2.3.0"
 | 
					 | 
				
			||||||
    hooks:
 | 
					 | 
				
			||||||
      # Spell checking
 | 
					 | 
				
			||||||
      - id: codespell
 | 
					 | 
				
			||||||
        exclude: ^.*\.(svd|SVD)$
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  - repo: https://github.com/pre-commit/mirrors-clang-format
 | 
					 | 
				
			||||||
    rev: "v18.1.3"
 | 
					 | 
				
			||||||
    hooks:
 | 
					 | 
				
			||||||
      # C/C++ formatting
 | 
					 | 
				
			||||||
      - id: clang-format
 | 
					 | 
				
			||||||
        types_or: [c, c++]
 | 
					 | 
				
			||||||
        exclude: ^.*\/build_opt\.h$
 | 
					 | 
				
			||||||
@@ -6,4 +6,12 @@ set(COMPONENT_ADD_INCLUDEDIRS
 | 
				
			|||||||
    "src"
 | 
					    "src"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set(COMPONENT_REQUIRES
 | 
				
			||||||
 | 
					    "arduino-esp32"
 | 
				
			||||||
 | 
					    "AsyncTCP"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
register_component()
 | 
					register_component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
 | 
				
			||||||
 | 
					target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,129 +0,0 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
# Contributor Covenant Code of Conduct
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Our Pledge
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
We as members, contributors, and leaders pledge to make participation in our
 | 
					 | 
				
			||||||
community a harassment-free experience for everyone, regardless of age, body
 | 
					 | 
				
			||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
 | 
					 | 
				
			||||||
identity and expression, level of experience, education, socioeconomic status,
 | 
					 | 
				
			||||||
nationality, personal appearance, race, religion, or sexual identity
 | 
					 | 
				
			||||||
and orientation.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
 | 
					 | 
				
			||||||
diverse, inclusive, and healthy community.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Our Standards
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Examples of behavior that contributes to a positive environment for our
 | 
					 | 
				
			||||||
community include:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Demonstrating empathy and kindness toward other people
 | 
					 | 
				
			||||||
* Being respectful of differing opinions, viewpoints, and experiences
 | 
					 | 
				
			||||||
* Giving and gracefully accepting constructive feedback
 | 
					 | 
				
			||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
 | 
					 | 
				
			||||||
  and learning from the experience
 | 
					 | 
				
			||||||
* Focusing on what is best not just for us as individuals, but for the
 | 
					 | 
				
			||||||
  overall community
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Examples of unacceptable behavior include:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* The use of sexualized language or imagery, and sexual attention or
 | 
					 | 
				
			||||||
  advances of any kind
 | 
					 | 
				
			||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
 | 
					 | 
				
			||||||
* Public or private harassment
 | 
					 | 
				
			||||||
* Publishing others' private information, such as a physical or email
 | 
					 | 
				
			||||||
  address, without their explicit permission
 | 
					 | 
				
			||||||
* Other conduct which could reasonably be considered inappropriate in a
 | 
					 | 
				
			||||||
  professional setting
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Enforcement Responsibilities
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Community leaders are responsible for clarifying and enforcing our standards of
 | 
					 | 
				
			||||||
acceptable behavior and will take appropriate and fair corrective action in
 | 
					 | 
				
			||||||
response to any behavior that they deem inappropriate, threatening, offensive,
 | 
					 | 
				
			||||||
or harmful.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Community leaders have the right and responsibility to remove, edit, or reject
 | 
					 | 
				
			||||||
comments, commits, code, wiki edits, issues, and other contributions that are
 | 
					 | 
				
			||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
 | 
					 | 
				
			||||||
decisions when appropriate.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Scope
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This Code of Conduct applies within all community spaces, and also applies when
 | 
					 | 
				
			||||||
an individual is officially representing the community in public spaces.
 | 
					 | 
				
			||||||
Examples of representing our community include using an official e-mail address,
 | 
					 | 
				
			||||||
posting via an official social media account, or acting as an appointed
 | 
					 | 
				
			||||||
representative at an online or offline event.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Enforcement
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
 | 
					 | 
				
			||||||
reported to the community leaders responsible for enforcement at
 | 
					 | 
				
			||||||
https://sidweb.nl/cms3/en/contact.
 | 
					 | 
				
			||||||
All complaints will be reviewed and investigated promptly and fairly.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
All community leaders are obligated to respect the privacy and security of the
 | 
					 | 
				
			||||||
reporter of any incident.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Enforcement Guidelines
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Community leaders will follow these Community Impact Guidelines in determining
 | 
					 | 
				
			||||||
the consequences for any action they deem in violation of this Code of Conduct:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 1. Correction
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
 | 
					 | 
				
			||||||
unprofessional or unwelcome in the community.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Consequence**: A private, written warning from community leaders, providing
 | 
					 | 
				
			||||||
clarity around the nature of the violation and an explanation of why the
 | 
					 | 
				
			||||||
behavior was inappropriate. A public apology may be requested.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 2. Warning
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Community Impact**: A violation through a single incident or series
 | 
					 | 
				
			||||||
of actions.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Consequence**: A warning with consequences for continued behavior. No
 | 
					 | 
				
			||||||
interaction with the people involved, including unsolicited interaction with
 | 
					 | 
				
			||||||
those enforcing the Code of Conduct, for a specified period of time. This
 | 
					 | 
				
			||||||
includes avoiding interactions in community spaces as well as external channels
 | 
					 | 
				
			||||||
like social media. Violating these terms may lead to a temporary or
 | 
					 | 
				
			||||||
permanent ban.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 3. Temporary Ban
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Community Impact**: A serious violation of community standards, including
 | 
					 | 
				
			||||||
sustained inappropriate behavior.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Consequence**: A temporary ban from any sort of interaction or public
 | 
					 | 
				
			||||||
communication with the community for a specified period of time. No public or
 | 
					 | 
				
			||||||
private interaction with the people involved, including unsolicited interaction
 | 
					 | 
				
			||||||
with those enforcing the Code of Conduct, is allowed during this period.
 | 
					 | 
				
			||||||
Violating these terms may lead to a permanent ban.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 4. Permanent Ban
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Community Impact**: Demonstrating a pattern of violation of community
 | 
					 | 
				
			||||||
standards, including sustained inappropriate behavior,  harassment of an
 | 
					 | 
				
			||||||
individual, or aggression toward or disparagement of classes of individuals.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Consequence**: A permanent ban from any sort of public interaction within
 | 
					 | 
				
			||||||
the community.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Attribution
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
 | 
					 | 
				
			||||||
version 2.0, available at
 | 
					 | 
				
			||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
 | 
					 | 
				
			||||||
enforcement ladder](https://github.com/mozilla/diversity).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[homepage]: https://www.contributor-covenant.org
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For answers to common questions about this code of conduct, see the FAQ at
 | 
					 | 
				
			||||||
https://www.contributor-covenant.org/faq. Translations are available at
 | 
					 | 
				
			||||||
https://www.contributor-covenant.org/translations.
 | 
					 | 
				
			||||||
							
								
								
									
										165
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						@@ -1,165 +0,0 @@
 | 
				
			|||||||
                   GNU LESSER GENERAL PUBLIC LICENSE
 | 
					 | 
				
			||||||
                       Version 3, 29 June 2007
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 | 
					 | 
				
			||||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
					 | 
				
			||||||
 of this license document, but changing it is not allowed.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  This version of the GNU Lesser General Public License incorporates
 | 
					 | 
				
			||||||
the terms and conditions of version 3 of the GNU General Public
 | 
					 | 
				
			||||||
License, supplemented by the additional permissions listed below.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  0. Additional Definitions.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  As used herein, "this License" refers to version 3 of the GNU Lesser
 | 
					 | 
				
			||||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
 | 
					 | 
				
			||||||
General Public License.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  "The Library" refers to a covered work governed by this License,
 | 
					 | 
				
			||||||
other than an Application or a Combined Work as defined below.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  An "Application" is any work that makes use of an interface provided
 | 
					 | 
				
			||||||
by the Library, but which is not otherwise based on the Library.
 | 
					 | 
				
			||||||
Defining a subclass of a class defined by the Library is deemed a mode
 | 
					 | 
				
			||||||
of using an interface provided by the Library.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  A "Combined Work" is a work produced by combining or linking an
 | 
					 | 
				
			||||||
Application with the Library.  The particular version of the Library
 | 
					 | 
				
			||||||
with which the Combined Work was made is also called the "Linked
 | 
					 | 
				
			||||||
Version".
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  The "Minimal Corresponding Source" for a Combined Work means the
 | 
					 | 
				
			||||||
Corresponding Source for the Combined Work, excluding any source code
 | 
					 | 
				
			||||||
for portions of the Combined Work that, considered in isolation, are
 | 
					 | 
				
			||||||
based on the Application, and not on the Linked Version.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  The "Corresponding Application Code" for a Combined Work means the
 | 
					 | 
				
			||||||
object code and/or source code for the Application, including any data
 | 
					 | 
				
			||||||
and utility programs needed for reproducing the Combined Work from the
 | 
					 | 
				
			||||||
Application, but excluding the System Libraries of the Combined Work.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  1. Exception to Section 3 of the GNU GPL.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  You may convey a covered work under sections 3 and 4 of this License
 | 
					 | 
				
			||||||
without being bound by section 3 of the GNU GPL.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  2. Conveying Modified Versions.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  If you modify a copy of the Library, and, in your modifications, a
 | 
					 | 
				
			||||||
facility refers to a function or data to be supplied by an Application
 | 
					 | 
				
			||||||
that uses the facility (other than as an argument passed when the
 | 
					 | 
				
			||||||
facility is invoked), then you may convey a copy of the modified
 | 
					 | 
				
			||||||
version:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   a) under this License, provided that you make a good faith effort to
 | 
					 | 
				
			||||||
   ensure that, in the event an Application does not supply the
 | 
					 | 
				
			||||||
   function or data, the facility still operates, and performs
 | 
					 | 
				
			||||||
   whatever part of its purpose remains meaningful, or
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   b) under the GNU GPL, with none of the additional permissions of
 | 
					 | 
				
			||||||
   this License applicable to that copy.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  3. Object Code Incorporating Material from Library Header Files.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  The object code form of an Application may incorporate material from
 | 
					 | 
				
			||||||
a header file that is part of the Library.  You may convey such object
 | 
					 | 
				
			||||||
code under terms of your choice, provided that, if the incorporated
 | 
					 | 
				
			||||||
material is not limited to numerical parameters, data structure
 | 
					 | 
				
			||||||
layouts and accessors, or small macros, inline functions and templates
 | 
					 | 
				
			||||||
(ten or fewer lines in length), you do both of the following:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   a) Give prominent notice with each copy of the object code that the
 | 
					 | 
				
			||||||
   Library is used in it and that the Library and its use are
 | 
					 | 
				
			||||||
   covered by this License.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   b) Accompany the object code with a copy of the GNU GPL and this license
 | 
					 | 
				
			||||||
   document.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  4. Combined Works.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  You may convey a Combined Work under terms of your choice that,
 | 
					 | 
				
			||||||
taken together, effectively do not restrict modification of the
 | 
					 | 
				
			||||||
portions of the Library contained in the Combined Work and reverse
 | 
					 | 
				
			||||||
engineering for debugging such modifications, if you also do each of
 | 
					 | 
				
			||||||
the following:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   a) Give prominent notice with each copy of the Combined Work that
 | 
					 | 
				
			||||||
   the Library is used in it and that the Library and its use are
 | 
					 | 
				
			||||||
   covered by this License.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   b) Accompany the Combined Work with a copy of the GNU GPL and this license
 | 
					 | 
				
			||||||
   document.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   c) For a Combined Work that displays copyright notices during
 | 
					 | 
				
			||||||
   execution, include the copyright notice for the Library among
 | 
					 | 
				
			||||||
   these notices, as well as a reference directing the user to the
 | 
					 | 
				
			||||||
   copies of the GNU GPL and this license document.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   d) Do one of the following:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
       0) Convey the Minimal Corresponding Source under the terms of this
 | 
					 | 
				
			||||||
       License, and the Corresponding Application Code in a form
 | 
					 | 
				
			||||||
       suitable for, and under terms that permit, the user to
 | 
					 | 
				
			||||||
       recombine or relink the Application with a modified version of
 | 
					 | 
				
			||||||
       the Linked Version to produce a modified Combined Work, in the
 | 
					 | 
				
			||||||
       manner specified by section 6 of the GNU GPL for conveying
 | 
					 | 
				
			||||||
       Corresponding Source.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
       1) Use a suitable shared library mechanism for linking with the
 | 
					 | 
				
			||||||
       Library.  A suitable mechanism is one that (a) uses at run time
 | 
					 | 
				
			||||||
       a copy of the Library already present on the user's computer
 | 
					 | 
				
			||||||
       system, and (b) will operate properly with a modified version
 | 
					 | 
				
			||||||
       of the Library that is interface-compatible with the Linked
 | 
					 | 
				
			||||||
       Version.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   e) Provide Installation Information, but only if you would otherwise
 | 
					 | 
				
			||||||
   be required to provide such information under section 6 of the
 | 
					 | 
				
			||||||
   GNU GPL, and only to the extent that such information is
 | 
					 | 
				
			||||||
   necessary to install and execute a modified version of the
 | 
					 | 
				
			||||||
   Combined Work produced by recombining or relinking the
 | 
					 | 
				
			||||||
   Application with a modified version of the Linked Version. (If
 | 
					 | 
				
			||||||
   you use option 4d0, the Installation Information must accompany
 | 
					 | 
				
			||||||
   the Minimal Corresponding Source and Corresponding Application
 | 
					 | 
				
			||||||
   Code. If you use option 4d1, you must provide the Installation
 | 
					 | 
				
			||||||
   Information in the manner specified by section 6 of the GNU GPL
 | 
					 | 
				
			||||||
   for conveying Corresponding Source.)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  5. Combined Libraries.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  You may place library facilities that are a work based on the
 | 
					 | 
				
			||||||
Library side by side in a single library together with other library
 | 
					 | 
				
			||||||
facilities that are not Applications and are not covered by this
 | 
					 | 
				
			||||||
License, and convey such a combined library under terms of your
 | 
					 | 
				
			||||||
choice, if you do both of the following:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   a) Accompany the combined library with a copy of the same work based
 | 
					 | 
				
			||||||
   on the Library, uncombined with any other library facilities,
 | 
					 | 
				
			||||||
   conveyed under the terms of this License.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   b) Give prominent notice with the combined library that part of it
 | 
					 | 
				
			||||||
   is a work based on the Library, and explaining where to find the
 | 
					 | 
				
			||||||
   accompanying uncombined form of the same work.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  6. Revised Versions of the GNU Lesser General Public License.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  The Free Software Foundation may publish revised and/or new versions
 | 
					 | 
				
			||||||
of the GNU Lesser General Public License from time to time. Such new
 | 
					 | 
				
			||||||
versions will be similar in spirit to the present version, but may
 | 
					 | 
				
			||||||
differ in detail to address new problems or concerns.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Each version is given a distinguishing version number. If the
 | 
					 | 
				
			||||||
Library as you received it specifies that a certain numbered version
 | 
					 | 
				
			||||||
of the GNU Lesser General Public License "or any later version"
 | 
					 | 
				
			||||||
applies to it, you have the option of following the terms and
 | 
					 | 
				
			||||||
conditions either of that published version or of any later version
 | 
					 | 
				
			||||||
published by the Free Software Foundation. If the Library as you
 | 
					 | 
				
			||||||
received it does not specify a version number of the GNU Lesser
 | 
					 | 
				
			||||||
General Public License, you may choose any version of the GNU Lesser
 | 
					 | 
				
			||||||
General Public License ever published by the Free Software Foundation.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  If the Library as you received it specifies that a proxy can decide
 | 
					 | 
				
			||||||
whether future versions of the GNU Lesser General Public License shall
 | 
					 | 
				
			||||||
apply, that proxy's public statement of acceptance of any version is
 | 
					 | 
				
			||||||
permanent authorization for you to choose that version for the
 | 
					 | 
				
			||||||
Library.
 | 
					 | 
				
			||||||
							
								
								
									
										1
									
								
								_config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					theme: jekyll-theme-cayman
 | 
				
			||||||
							
								
								
									
										3
									
								
								component.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					COMPONENT_ADD_INCLUDEDIRS := src
 | 
				
			||||||
 | 
					COMPONENT_SRCDIRS := src
 | 
				
			||||||
 | 
					CXXFLAGS += -fno-rtti
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								docs/logo.png
									
									
									
									
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 479 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/logo.webp
									
									
									
									
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 131 KiB  | 
| 
		 Before Width: | Height: | Size: 310 KiB  | 
| 
		 Before Width: | Height: | Size: 295 KiB  | 
@@ -1,47 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <DNSServer.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Shows how to use AsyncResponseStream.
 | 
					 | 
				
			||||||
  // The internal buffer will be allocated and data appended to it,
 | 
					 | 
				
			||||||
  // until the response is sent, then this buffer is read and committed on the network.
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    AsyncResponseStream *response = request->beginResponseStream("plain/text", 40 * 1024);
 | 
					 | 
				
			||||||
    for (int i = 0; i < 32 * 1024; i++) {
 | 
					 | 
				
			||||||
      response->write('a');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,210 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to trigger an async client request from a browser request and send the client response back to the browser through websocket
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define WIFI_SSID     "IoT"
 | 
					 | 
				
			||||||
#define WIFI_PASSWORD ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
static AsyncWebSocketMessageHandler wsHandler;
 | 
					 | 
				
			||||||
static AsyncWebSocket ws("/ws", wsHandler.eventHandler());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
  <!DOCTYPE html>
 | 
					 | 
				
			||||||
  <html>
 | 
					 | 
				
			||||||
  <head>
 | 
					 | 
				
			||||||
    <title>WebSocket Tunnel Example</title>
 | 
					 | 
				
			||||||
  </head>
 | 
					 | 
				
			||||||
  <body>
 | 
					 | 
				
			||||||
    <h1>WebSocket Tunnel Example</h1>
 | 
					 | 
				
			||||||
    <div><input type="text" id="url" value="http://www.google.com" /></div>
 | 
					 | 
				
			||||||
    <div><button onclick='fetch()'>Fetch</button></div>
 | 
					 | 
				
			||||||
    <div><pre id="response"></pre></div>
 | 
					 | 
				
			||||||
    <script>
 | 
					 | 
				
			||||||
      var ws = new WebSocket('/ws');
 | 
					 | 
				
			||||||
      ws.binaryType = "arraybuffer";
 | 
					 | 
				
			||||||
      ws.onopen = function() {
 | 
					 | 
				
			||||||
        console.log("WebSocket connected");
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      ws.onmessage = function(event) {
 | 
					 | 
				
			||||||
        let uint8array = new Uint8Array(event.data);
 | 
					 | 
				
			||||||
        let string = new TextDecoder().decode(uint8array);
 | 
					 | 
				
			||||||
        console.log("WebSocket message: " + string);
 | 
					 | 
				
			||||||
        document.getElementById("response").innerText += string;
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      ws.onclose = function() {
 | 
					 | 
				
			||||||
        console.log("WebSocket closed");
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      ws.onerror = function(error) {
 | 
					 | 
				
			||||||
        console.log("WebSocket error: " + error);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      function fetch() {
 | 
					 | 
				
			||||||
        document.getElementById("response").innerText = "";
 | 
					 | 
				
			||||||
        var url = document.getElementById("url").value;
 | 
					 | 
				
			||||||
        ws.send(url);
 | 
					 | 
				
			||||||
        console.log("WebSocket sent: " + url);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    </script>
 | 
					 | 
				
			||||||
  </body>
 | 
					 | 
				
			||||||
  </html>
 | 
					 | 
				
			||||||
    )";
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
 | 
					 | 
				
			||||||
  while (WiFi.status() != WL_CONNECTED) {
 | 
					 | 
				
			||||||
    delay(500);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  Serial.println("Connected to WiFi!");
 | 
					 | 
				
			||||||
  Serial.println(WiFi.localIP());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (const uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  wsHandler.onMessage([](AsyncWebSocket *server, AsyncWebSocketClient *wsClient, const uint8_t *data, size_t len) {
 | 
					 | 
				
			||||||
    String url;
 | 
					 | 
				
			||||||
    String host;
 | 
					 | 
				
			||||||
    String port;
 | 
					 | 
				
			||||||
    String path;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    url.concat((const char *)data, len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!url.startsWith("http://")) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!url.endsWith("/")) {
 | 
					 | 
				
			||||||
      url += "/";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Parse the URL to extract the host and port
 | 
					 | 
				
			||||||
    int start = url.indexOf("://") + 3;
 | 
					 | 
				
			||||||
    int end = url.indexOf("/", start);
 | 
					 | 
				
			||||||
    if (end == -1) {
 | 
					 | 
				
			||||||
      end = url.length();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    String hostPort = url.substring(start, end);
 | 
					 | 
				
			||||||
    int colonIndex = hostPort.indexOf(":");
 | 
					 | 
				
			||||||
    if (colonIndex != -1) {
 | 
					 | 
				
			||||||
      host = hostPort.substring(0, colonIndex);
 | 
					 | 
				
			||||||
      port = hostPort.substring(colonIndex + 1);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      host = hostPort;
 | 
					 | 
				
			||||||
      port = "80";  // Default HTTP port
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    path = url.substring(end);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Serial.printf("Host: %s\n", host.c_str());
 | 
					 | 
				
			||||||
    Serial.printf("Port: %s\n", port.c_str());
 | 
					 | 
				
			||||||
    Serial.printf("Path: %s\n", path.c_str());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Ensure client does not get deleted while the websocket holds a reference to it
 | 
					 | 
				
			||||||
    std::shared_ptr<AsyncClient> *safeAsyncClient = new std::shared_ptr<AsyncClient>(std::make_shared<AsyncClient>());
 | 
					 | 
				
			||||||
    AsyncClient *asyncClient = safeAsyncClient->get();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    asyncClient->onDisconnect([safeAsyncClient](void *arg, AsyncClient *client) {
 | 
					 | 
				
			||||||
      Serial.printf("Tunnel disconnected!\n");
 | 
					 | 
				
			||||||
      delete safeAsyncClient;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // register a callback when an error occurs
 | 
					 | 
				
			||||||
    // note: onDisconnect also called on error
 | 
					 | 
				
			||||||
    asyncClient->onError([](void *arg, AsyncClient *client, int8_t error) {
 | 
					 | 
				
			||||||
      Serial.printf("Tunnel error: %s\n", client->errorToString(error));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // register a callback when data arrives, to accumulate it
 | 
					 | 
				
			||||||
    asyncClient->onPacket(
 | 
					 | 
				
			||||||
      [safeAsyncClient](void *arg, AsyncClient *, struct pbuf *pb) {
 | 
					 | 
				
			||||||
        std::shared_ptr<AsyncClient> safeAsyncClientRef = *safeAsyncClient;  // add a reference
 | 
					 | 
				
			||||||
        AsyncWebSocketClient *wsClient = (AsyncWebSocketClient *)arg;
 | 
					 | 
				
			||||||
        Serial.printf("Tunnel received %u bytes\n", pb->len);
 | 
					 | 
				
			||||||
        AsyncWebSocketSharedBuffer wsBuffer =
 | 
					 | 
				
			||||||
          AsyncWebSocketSharedBuffer(new std::vector<uint8_t>((uint8_t *)pb->payload, (uint8_t *)pb->payload + pb->len), [=](std::vector<uint8_t> *bufptr) {
 | 
					 | 
				
			||||||
            delete bufptr;
 | 
					 | 
				
			||||||
            Serial.printf("ACK %u bytes\n", pb->len);
 | 
					 | 
				
			||||||
            safeAsyncClientRef->ackPacket(pb);
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        Serial.printf("Tunnel sending %u bytes\n", wsBuffer->size());
 | 
					 | 
				
			||||||
        Serial.printf("%.*s\n", (int)wsBuffer->size(), wsBuffer->data());
 | 
					 | 
				
			||||||
        wsClient->binary(std::move(wsBuffer));
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      wsClient
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    asyncClient->onConnect([=](void *arg, AsyncClient *client) {
 | 
					 | 
				
			||||||
      Serial.printf("Tunnel connected!\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      client->write("GET ");
 | 
					 | 
				
			||||||
      client->write(path.c_str());
 | 
					 | 
				
			||||||
      client->write(" HTTP/1.1\r\n");
 | 
					 | 
				
			||||||
      client->write("Host: ");
 | 
					 | 
				
			||||||
      client->write(host.c_str());
 | 
					 | 
				
			||||||
      client->write(":");
 | 
					 | 
				
			||||||
      client->write(port.c_str());
 | 
					 | 
				
			||||||
      client->write("\r\n");
 | 
					 | 
				
			||||||
      client->write("User-Agent: ESP32\r\n");
 | 
					 | 
				
			||||||
      client->write("Accept: */*\r\n");
 | 
					 | 
				
			||||||
      client->write("Connection: close\r\n");
 | 
					 | 
				
			||||||
      client->write("\r\n");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Serial.printf("Fetching: http://%s:%s%s\n", host.c_str(), port.c_str(), path.c_str());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!asyncClient->connect(host.c_str(), port.toInt())) {
 | 
					 | 
				
			||||||
      Serial.printf("Failed to open tunnel!\n");
 | 
					 | 
				
			||||||
      delete safeAsyncClient;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  wsHandler.onConnect([](AsyncWebSocket *server, AsyncWebSocketClient *client) {
 | 
					 | 
				
			||||||
    Serial.printf("Client %" PRIu32 " connected\n", client->id());
 | 
					 | 
				
			||||||
    client->binary("WebSocket connected!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  wsHandler.onDisconnect([](AsyncWebSocket *server, uint32_t clientId) {
 | 
					 | 
				
			||||||
    Serial.printf("Client %" PRIu32 " disconnected\n", clientId);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addHandler(&ws);
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
  Serial.println("Server started!");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static uint32_t lastHeap = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  ws.cleanupClients(2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef ESP32
 | 
					 | 
				
			||||||
  uint32_t now = millis();
 | 
					 | 
				
			||||||
  if (now - lastHeap >= 2000) {
 | 
					 | 
				
			||||||
    Serial.printf("Uptime: %3lu s, Free heap: %" PRIu32 "\n", millis() / 1000, ESP.getFreeHeap());
 | 
					 | 
				
			||||||
    lastHeap = now;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  delay(500);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,157 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Authentication and authorization middlewares
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// basicAuth
 | 
					 | 
				
			||||||
static AsyncAuthenticationMiddleware basicAuth;
 | 
					 | 
				
			||||||
static AsyncAuthenticationMiddleware basicAuthHash;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// simple digest authentication
 | 
					 | 
				
			||||||
static AsyncAuthenticationMiddleware digestAuth;
 | 
					 | 
				
			||||||
static AsyncAuthenticationMiddleware digestAuthHash;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// complex authentication which adds request attributes for the next middlewares and handler
 | 
					 | 
				
			||||||
static AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
 | 
					 | 
				
			||||||
  if (!request->authenticate("user", "password")) {
 | 
					 | 
				
			||||||
    return request->requestAuthentication();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // add attributes to the request for the next middlewares and handler
 | 
					 | 
				
			||||||
  request->setAttribute("user", "Mathieu");
 | 
					 | 
				
			||||||
  request->setAttribute("role", "staff");
 | 
					 | 
				
			||||||
  if (request->hasParam("token")) {
 | 
					 | 
				
			||||||
    request->setAttribute("token", request->getParam("token")->value().c_str());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  next();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncAuthorizationMiddleware authz([](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
  return request->getAttribute("token") == "123";
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // basic authentication
 | 
					 | 
				
			||||||
  basicAuth.setUsername("admin");
 | 
					 | 
				
			||||||
  basicAuth.setPassword("admin");
 | 
					 | 
				
			||||||
  basicAuth.setRealm("MyApp");
 | 
					 | 
				
			||||||
  basicAuth.setAuthFailureMessage("Authentication failed");
 | 
					 | 
				
			||||||
  basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
 | 
					 | 
				
			||||||
  basicAuth.generateHash();  // precompute hash (optional but recommended)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // basic authentication with hash
 | 
					 | 
				
			||||||
  basicAuthHash.setUsername("admin");
 | 
					 | 
				
			||||||
  basicAuthHash.setPasswordHash("YWRtaW46YWRtaW4=");  // BASE64(admin:admin)
 | 
					 | 
				
			||||||
  basicAuthHash.setRealm("MyApp");
 | 
					 | 
				
			||||||
  basicAuthHash.setAuthFailureMessage("Authentication failed");
 | 
					 | 
				
			||||||
  basicAuthHash.setAuthType(AsyncAuthType::AUTH_BASIC);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // digest authentication
 | 
					 | 
				
			||||||
  digestAuth.setUsername("admin");
 | 
					 | 
				
			||||||
  digestAuth.setPassword("admin");
 | 
					 | 
				
			||||||
  digestAuth.setRealm("MyApp");
 | 
					 | 
				
			||||||
  digestAuth.setAuthFailureMessage("Authentication failed");
 | 
					 | 
				
			||||||
  digestAuth.setAuthType(AsyncAuthType::AUTH_DIGEST);
 | 
					 | 
				
			||||||
  digestAuth.generateHash();  // precompute hash (optional but recommended)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // digest authentication with hash
 | 
					 | 
				
			||||||
  digestAuthHash.setUsername("admin");
 | 
					 | 
				
			||||||
  digestAuthHash.setPasswordHash("f499b71f9a36d838b79268e145e132f7");  // MD5(user:realm:pass)
 | 
					 | 
				
			||||||
  digestAuthHash.setRealm("MyApp");
 | 
					 | 
				
			||||||
  digestAuthHash.setAuthFailureMessage("Authentication failed");
 | 
					 | 
				
			||||||
  digestAuthHash.setAuthType(AsyncAuthType::AUTH_DIGEST);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // basic authentication method
 | 
					 | 
				
			||||||
  // curl -v -u admin:admin  http://192.168.4.1/auth-basic
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/auth-basic", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .addMiddleware(&basicAuth);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // basic authentication method with hash
 | 
					 | 
				
			||||||
  // curl -v -u admin:admin  http://192.168.4.1/auth-basic-hash
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/auth-basic-hash", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .addMiddleware(&basicAuthHash);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // digest authentication
 | 
					 | 
				
			||||||
  // curl -v -u admin:admin --digest  http://192.168.4.1/auth-digest
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/auth-digest", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .addMiddleware(&digestAuth);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // digest authentication with hash
 | 
					 | 
				
			||||||
  // curl -v -u admin:admin --digest  http://192.168.4.1/auth-digest-hash
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/auth-digest-hash", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .addMiddleware(&digestAuthHash);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // test digest auth custom authorization middleware
 | 
					 | 
				
			||||||
  // curl -v --digest -u user:password  http://192.168.4.1/auth-custom?token=123 => OK
 | 
					 | 
				
			||||||
  // curl -v --digest -u user:password  http://192.168.4.1/auth-custom?token=456 => 403
 | 
					 | 
				
			||||||
  // curl -v --digest -u user:FAILED  http://192.168.4.1/auth-custom?token=456 => 401
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/auth-custom", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        String buffer = "Hello ";
 | 
					 | 
				
			||||||
        buffer.concat(request->getAttribute("user"));
 | 
					 | 
				
			||||||
        buffer.concat(" with role: ");
 | 
					 | 
				
			||||||
        buffer.concat(request->getAttribute("role"));
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", buffer);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .addMiddlewares({&complexAuth, &authz});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,60 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// How to use CORS middleware
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
static AsyncCorsMiddleware cors;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  cors.setOrigin("http://192.168.4.1");
 | 
					 | 
				
			||||||
  cors.setMethods("POST, GET, OPTIONS, DELETE");
 | 
					 | 
				
			||||||
  cors.setHeaders("X-Custom-Header");
 | 
					 | 
				
			||||||
  cors.setAllowCredentials(false);
 | 
					 | 
				
			||||||
  cors.setMaxAge(600);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addMiddleware(&cors);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Test CORS preflight request
 | 
					 | 
				
			||||||
  // curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/cors
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Test CORS request
 | 
					 | 
				
			||||||
  // curl -v -H "origin: http://192.168.4.1" http://192.168.4.1/cors
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Test non-CORS request
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/cors
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/cors", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,60 +1,47 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <DNSServer.h>
 | 
					#include <DNSServer.h>
 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					#ifdef ESP32
 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					#include <WiFi.h>
 | 
				
			||||||
 | 
					#include <AsyncTCP.h>
 | 
				
			||||||
#elif defined(ESP8266)
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					#include <ESP8266WiFi.h>
 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					#include <ESPAsyncTCP.h>
 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#include "ESPAsyncWebServer.h"
 | 
					#include "ESPAsyncWebServer.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static DNSServer dnsServer;
 | 
					DNSServer dnsServer;
 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					AsyncWebServer server(80);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CaptiveRequestHandler : public AsyncWebHandler {
 | 
					class CaptiveRequestHandler : public AsyncWebHandler {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
  bool canHandle(__unused AsyncWebServerRequest *request) const override {
 | 
					  CaptiveRequestHandler() {}
 | 
				
			||||||
 | 
					  virtual ~CaptiveRequestHandler() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool canHandle(AsyncWebServerRequest *request){
 | 
				
			||||||
 | 
					    //request->addInterestingHeader("ANY");
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void handleRequest(AsyncWebServerRequest *request) {
 | 
					  void handleRequest(AsyncWebServerRequest *request) {
 | 
				
			||||||
    AsyncResponseStream *response = request->beginResponseStream("text/html");
 | 
					    AsyncResponseStream *response = request->beginResponseStream("text/html");
 | 
				
			||||||
    response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
 | 
					    response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
 | 
				
			||||||
    response->print("<p>This is our captive portal front page.</p>");
 | 
					    response->print("<p>This is out captive portal front page.</p>");
 | 
				
			||||||
    response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
 | 
					    response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
    response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
 | 
					    response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    response->print("</body></html>");
 | 
					    response->print("</body></html>");
 | 
				
			||||||
    request->send(response);
 | 
					    request->send(response);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
  Serial.println();
 | 
					 | 
				
			||||||
  Serial.println("Configuring access point...");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  if (!WiFi.softAP("esp-captive")) {
 | 
					 | 
				
			||||||
    Serial.println("Soft AP creation failed.");
 | 
					 | 
				
			||||||
    while (1);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setup(){
 | 
				
			||||||
 | 
					  //your other setup stuff...
 | 
				
			||||||
 | 
					  WiFi.softAP("esp-captive");
 | 
				
			||||||
  dnsServer.start(53, "*", WiFi.softAPIP());
 | 
					  dnsServer.start(53, "*", WiFi.softAPIP());
 | 
				
			||||||
#endif
 | 
					  server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP
 | 
				
			||||||
 | 
					  //more handlers...
 | 
				
			||||||
  server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);  // only when requested from AP
 | 
					 | 
				
			||||||
  // more handlers...
 | 
					 | 
				
			||||||
  server.begin();
 | 
					  server.begin();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void loop() {
 | 
					void loop(){
 | 
				
			||||||
  dnsServer.processNextRequest();
 | 
					  dnsServer.processNextRequest();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,133 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to catch all requests and send a 404 Not Found response
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <title>Sample HTML</title>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
    <h1>Hello, World!</h1>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // need to cast to uint8_t*
 | 
					 | 
				
			||||||
    // if you do not, the const char* will be copied in a temporary String buffer
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // catch any request, and send a 404 Not Found response
 | 
					 | 
				
			||||||
  // except for /game_log which is handled by onRequestBody
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/foo
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.onNotFound([](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    if (request->url() == "/game_log") {
 | 
					 | 
				
			||||||
      return;  // response object already created by onRequestBody
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    request->send(404, "text/plain", "Not found");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // See: https://github.com/ESP32Async/ESPAsyncWebServer/issues/6
 | 
					 | 
				
			||||||
  // catch any POST request and send a 200 OK response
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v -X POST http://192.168.4.1/game_log -H "Content-Type: application/json" -d '{"game": "test"}'
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
 | 
					 | 
				
			||||||
    if (request->url() == "/game_log") {
 | 
					 | 
				
			||||||
      request->send(200, "application/json", "{\"status\":\"OK\"}");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // note that there is no else here: the goal is only to prepare a response based on some body content
 | 
					 | 
				
			||||||
    // onNotFound will always be called after this, and will not override the response object if `/game_log` is requested
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,140 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Chunk response with caching example
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <title>Sample HTML</title>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
    <h1>Hello, World!</h1>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // first time: serves the file and cache headers
 | 
					 | 
				
			||||||
  // curl -N -v http://192.168.4.1/ --output -
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // secodn time: serves 304
 | 
					 | 
				
			||||||
  // curl -N -v -H "if-none-match: 4272" http://192.168.4.1/ --output -
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    String etag = String(htmlContentLength);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (request->header(asyncsrv::T_INM) == etag) {
 | 
					 | 
				
			||||||
      request->send(304);
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
 | 
					 | 
				
			||||||
      Serial.printf("%u / %u\n", index, htmlContentLength);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // finished ?
 | 
					 | 
				
			||||||
      if (htmlContentLength <= index) {
 | 
					 | 
				
			||||||
        Serial.println("finished");
 | 
					 | 
				
			||||||
        return 0;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // serve a maximum of 256 or maxLen bytes of the remaining content
 | 
					 | 
				
			||||||
      // this small number is specifically chosen to demonstrate the chunking
 | 
					 | 
				
			||||||
      // DO NOT USE SUCH SMALL NUMBER IN PRODUCTION
 | 
					 | 
				
			||||||
      // Reducing the chunk size will increase the response time, thus reducing the server's capacity in processing concurrent requests
 | 
					 | 
				
			||||||
      const int chunkSize = min((size_t)256, min(maxLen, htmlContentLength - index));
 | 
					 | 
				
			||||||
      Serial.printf("sending: %u\n", chunkSize);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      memcpy(buffer, htmlContent + index, chunkSize);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return chunkSize;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    response->addHeader(asyncsrv::T_Cache_Control, "public,max-age=60");
 | 
					 | 
				
			||||||
    response->addHeader(asyncsrv::T_ETag, etag);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,229 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to wait in a chunk response for incoming data
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if __has_include("ArduinoJson.h")
 | 
					 | 
				
			||||||
#include <ArduinoJson.h>
 | 
					 | 
				
			||||||
#include <AsyncJson.h>
 | 
					 | 
				
			||||||
#include <AsyncMessagePack.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <title>Sample HTML</title>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
    <h1>Hello, World!</h1>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
static AsyncLoggingMiddleware requestLogger;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static String triggerUART;
 | 
					 | 
				
			||||||
static int key = -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // adds some internal request logging for debugging
 | 
					 | 
				
			||||||
  requestLogger.setEnabled(true);
 | 
					 | 
				
			||||||
  requestLogger.setOutput(Serial);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addMiddleware(&requestLogger);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if __has_include("ArduinoJson.h")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // HOW TO RUN THIS EXAMPLE:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // 1. Trigger a request that will be blocked for a long time:
 | 
					 | 
				
			||||||
  //    > time curl -v -X POST http://192.168.4.1/api -H "Content-Type: application/json" -d '{"input": "Please type a key to continue in Serial console..."}' --output -
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // 2. While waiting, in another terminal, run some concurrent requests:
 | 
					 | 
				
			||||||
  //    > time curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // 3. Type a key in the Serial console to continue the processing within 30 seconds.
 | 
					 | 
				
			||||||
  //    This should unblock the first request.
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // need to cast to uint8_t*
 | 
					 | 
				
			||||||
    // if you do not, the const char* will be copied in a temporary String buffer
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.on(
 | 
					 | 
				
			||||||
    "/api", HTTP_POST,
 | 
					 | 
				
			||||||
    [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
      // request parsing has finished
 | 
					 | 
				
			||||||
      String *data = (String *)request->_tempObject;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!data) {
 | 
					 | 
				
			||||||
        request->send(400);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // no data ?
 | 
					 | 
				
			||||||
      if (!data->length()) {
 | 
					 | 
				
			||||||
        delete data;
 | 
					 | 
				
			||||||
        request->_tempObject = nullptr;
 | 
					 | 
				
			||||||
        request->send(400);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      JsonDocument doc;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // deserialize and check for errors
 | 
					 | 
				
			||||||
      if (deserializeJson(doc, *data)) {
 | 
					 | 
				
			||||||
        delete data;
 | 
					 | 
				
			||||||
        request->_tempObject = nullptr;
 | 
					 | 
				
			||||||
        request->send(400);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      delete data;
 | 
					 | 
				
			||||||
      request->_tempObject = nullptr;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // start UART com: UART will send the data to the Serial console and wait for the key press
 | 
					 | 
				
			||||||
      triggerUART = doc["input"].as<const char *>();
 | 
					 | 
				
			||||||
      key = -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
 | 
					 | 
				
			||||||
        // still waiting for UARY ?
 | 
					 | 
				
			||||||
        if (triggerUART.length() && key == -1) {
 | 
					 | 
				
			||||||
          return RESPONSE_TRY_AGAIN;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // finished ?
 | 
					 | 
				
			||||||
        if (!triggerUART.length() && key == -1) {
 | 
					 | 
				
			||||||
          return 0;  // 0 means we are done
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // log_d("UART answered!");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        String answer = "You typed: ";
 | 
					 | 
				
			||||||
        answer.concat((char)key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // note: I did not check for maxLen, but you should (see ChunkResponse.ino)
 | 
					 | 
				
			||||||
        memcpy(buffer, answer.c_str(), answer.length());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // finish!
 | 
					 | 
				
			||||||
        triggerUART = emptyString;
 | 
					 | 
				
			||||||
        key = -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return answer.length();
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      request->send(response);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    NULL,  // upload handler is not used so it should be NULL
 | 
					 | 
				
			||||||
    [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
 | 
					 | 
				
			||||||
      // log_d("Body: index: %u, len: %u, total: %u", index, len, total);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!index) {
 | 
					 | 
				
			||||||
        // log_d("Start body parsing");
 | 
					 | 
				
			||||||
        request->_tempObject = new String();
 | 
					 | 
				
			||||||
        // cast request->_tempObject pointer to String and reserve total size
 | 
					 | 
				
			||||||
        ((String *)request->_tempObject)->reserve(total);
 | 
					 | 
				
			||||||
        // set timeout 30s
 | 
					 | 
				
			||||||
        request->client()->setRxTimeout(30);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // log_d("Append body data");
 | 
					 | 
				
			||||||
      ((String *)request->_tempObject)->concat((const char *)data, len);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  if (triggerUART.length() && key == -1) {
 | 
					 | 
				
			||||||
    Serial.println(triggerUART);
 | 
					 | 
				
			||||||
    // log_d("Waiting for UART input...");
 | 
					 | 
				
			||||||
    while (!Serial.available()) {
 | 
					 | 
				
			||||||
      delay(100);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    key = Serial.read();
 | 
					 | 
				
			||||||
    Serial.flush();
 | 
					 | 
				
			||||||
    // log_d("UART input: %c", key);
 | 
					 | 
				
			||||||
    triggerUART = emptyString;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										257
									
								
								examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,257 @@
 | 
				
			|||||||
 | 
					// Defaulut is SPIFFS, FatFS: only on ESP32, also choose partition scheme w/ ffat. 
 | 
				
			||||||
 | 
					// Comment 2 lines below or uncomment only one of them
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//#define USE_LittleFS
 | 
				
			||||||
 | 
					//#define USE_FatFS // Only ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <ArduinoOTA.h>
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					 #include <FS.h>
 | 
				
			||||||
 | 
					 #ifdef USE_LittleFS
 | 
				
			||||||
 | 
					  #define MYFS LITTLEFS
 | 
				
			||||||
 | 
					  #include "LITTLEFS.h"
 | 
				
			||||||
 | 
					 #elif defined(USE_FatFS)
 | 
				
			||||||
 | 
					  #define MYFS FFat
 | 
				
			||||||
 | 
					  #include "FFat.h"
 | 
				
			||||||
 | 
					 #else
 | 
				
			||||||
 | 
					  #define MYFS SPIFFS
 | 
				
			||||||
 | 
					  #include <SPIFFS.h>
 | 
				
			||||||
 | 
					 #endif
 | 
				
			||||||
 | 
					 #include <ESPmDNS.h>
 | 
				
			||||||
 | 
					 #include <WiFi.h>
 | 
				
			||||||
 | 
					 #include <AsyncTCP.h>
 | 
				
			||||||
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
 | 
					 #ifdef USE_LittleFS
 | 
				
			||||||
 | 
					  #include <FS.h>
 | 
				
			||||||
 | 
					  #define MYFS LittleFS
 | 
				
			||||||
 | 
					  #include <LittleFS.h> 
 | 
				
			||||||
 | 
					 #elif defined(USE_FatFS)
 | 
				
			||||||
 | 
					  #error "FatFS only on ESP32 for now!"
 | 
				
			||||||
 | 
					 #else
 | 
				
			||||||
 | 
					  #define MYFS SPIFFS
 | 
				
			||||||
 | 
					 #endif
 | 
				
			||||||
 | 
					 #include <ESP8266WiFi.h>
 | 
				
			||||||
 | 
					 #include <ESPAsyncTCP.h>
 | 
				
			||||||
 | 
					 #include <ESP8266mDNS.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#include <ESPAsyncWebServer.h>
 | 
				
			||||||
 | 
					#include <SPIFFSEditor.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SKETCH BEGIN
 | 
				
			||||||
 | 
					AsyncWebServer server(80);
 | 
				
			||||||
 | 
					AsyncWebSocket ws("/ws");
 | 
				
			||||||
 | 
					AsyncEventSource events("/events");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
 | 
				
			||||||
 | 
					  if(type == WS_EVT_CONNECT){
 | 
				
			||||||
 | 
					    Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
 | 
				
			||||||
 | 
					    client->printf("Hello Client %u :)", client->id());
 | 
				
			||||||
 | 
					    client->ping();
 | 
				
			||||||
 | 
					  } else if(type == WS_EVT_DISCONNECT){
 | 
				
			||||||
 | 
					    Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id());
 | 
				
			||||||
 | 
					  } else if(type == WS_EVT_ERROR){
 | 
				
			||||||
 | 
					    Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
 | 
				
			||||||
 | 
					  } else if(type == WS_EVT_PONG){
 | 
				
			||||||
 | 
					    Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
 | 
				
			||||||
 | 
					  } else if(type == WS_EVT_DATA){
 | 
				
			||||||
 | 
					    AwsFrameInfo * info = (AwsFrameInfo*)arg;
 | 
				
			||||||
 | 
					    String msg = "";
 | 
				
			||||||
 | 
					    if(info->final && info->index == 0 && info->len == len){
 | 
				
			||||||
 | 
					      //the whole message is in a single frame and we got all of it's data
 | 
				
			||||||
 | 
					      Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(info->opcode == WS_TEXT){
 | 
				
			||||||
 | 
					        for(size_t i=0; i < info->len; i++) {
 | 
				
			||||||
 | 
					          msg += (char) data[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        char buff[3];
 | 
				
			||||||
 | 
					        for(size_t i=0; i < info->len; i++) {
 | 
				
			||||||
 | 
					          sprintf(buff, "%02x ", (uint8_t) data[i]);
 | 
				
			||||||
 | 
					          msg += buff ;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      Serial.printf("%s\n",msg.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(info->opcode == WS_TEXT)
 | 
				
			||||||
 | 
					        client->text("I got your text message");
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        client->binary("I got your binary message");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      //message is comprised of multiple frames or the frame is split into multiple packets
 | 
				
			||||||
 | 
					      if(info->index == 0){
 | 
				
			||||||
 | 
					        if(info->num == 0)
 | 
				
			||||||
 | 
					          Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
 | 
				
			||||||
 | 
					        Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(info->opcode == WS_TEXT){
 | 
				
			||||||
 | 
					        for(size_t i=0; i < len; i++) {
 | 
				
			||||||
 | 
					          msg += (char) data[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        char buff[3];
 | 
				
			||||||
 | 
					        for(size_t i=0; i < len; i++) {
 | 
				
			||||||
 | 
					          sprintf(buff, "%02x ", (uint8_t) data[i]);
 | 
				
			||||||
 | 
					          msg += buff ;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      Serial.printf("%s\n",msg.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if((info->index + len) == info->len){
 | 
				
			||||||
 | 
					        Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
 | 
				
			||||||
 | 
					        if(info->final){
 | 
				
			||||||
 | 
					          Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
 | 
				
			||||||
 | 
					          if(info->message_opcode == WS_TEXT)
 | 
				
			||||||
 | 
					            client->text("I got your text message");
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            client->binary("I got your binary message");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const char* ssid = "*****";
 | 
				
			||||||
 | 
					const char* password = "*****";
 | 
				
			||||||
 | 
					const char* hostName = "esp-async";
 | 
				
			||||||
 | 
					const char* http_username = "admin";
 | 
				
			||||||
 | 
					const char* http_password = "admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setup(){
 | 
				
			||||||
 | 
					  Serial.begin(115200);
 | 
				
			||||||
 | 
					  Serial.setDebugOutput(true);
 | 
				
			||||||
 | 
					  WiFi.mode(WIFI_AP_STA);
 | 
				
			||||||
 | 
					  WiFi.softAP(hostName);
 | 
				
			||||||
 | 
					  WiFi.begin(ssid, password);
 | 
				
			||||||
 | 
					  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
 | 
				
			||||||
 | 
					    Serial.printf("STA: Failed!\n");
 | 
				
			||||||
 | 
					    WiFi.disconnect(false);
 | 
				
			||||||
 | 
					    delay(1000);
 | 
				
			||||||
 | 
					    WiFi.begin(ssid, password);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  Serial.print(F("*CONNECTED* IP:"));
 | 
				
			||||||
 | 
					  Serial.println(WiFi.localIP());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //Send OTA events to the browser
 | 
				
			||||||
 | 
					  ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); });
 | 
				
			||||||
 | 
					  ArduinoOTA.onEnd([]() { events.send("Update End", "ota"); });
 | 
				
			||||||
 | 
					  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
 | 
				
			||||||
 | 
					    char p[32];
 | 
				
			||||||
 | 
					    sprintf(p, "Progress: %u%%\n", (progress/(total/100)));
 | 
				
			||||||
 | 
					    events.send(p, "ota");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  ArduinoOTA.onError([](ota_error_t error) {
 | 
				
			||||||
 | 
					    if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota");
 | 
				
			||||||
 | 
					    else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota");
 | 
				
			||||||
 | 
					    else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota");
 | 
				
			||||||
 | 
					    else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota");
 | 
				
			||||||
 | 
					    else if(error == OTA_END_ERROR) events.send("End Failed", "ota");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  ArduinoOTA.setHostname(hostName);
 | 
				
			||||||
 | 
					  ArduinoOTA.begin();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MDNS.addService("http","tcp",80);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//FS
 | 
				
			||||||
 | 
					#ifdef USE_FatFS
 | 
				
			||||||
 | 
					  if (MYFS.begin(false,"/ffat",3)) { //limit the RAM usage, bottom line 8kb + 4kb takes per each file, default is 10
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  if (MYFS.begin()) {
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    Serial.print(F("FS mounted\n"));
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    Serial.print(F("FS mount failed\n"));  
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ws.onEvent(onWsEvent);
 | 
				
			||||||
 | 
					  server.addHandler(&ws);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  events.onConnect([](AsyncEventSourceClient *client){
 | 
				
			||||||
 | 
					    client->send("hello!",NULL,millis(),1000);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  server.addHandler(&events);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					  server.addHandler(new SPIFFSEditor(MYFS, http_username,http_password));
 | 
				
			||||||
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
 | 
					  server.addHandler(new SPIFFSEditor(http_username,http_password, MYFS));
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
 | 
				
			||||||
 | 
					    request->send(200, "text/plain", String(ESP.getFreeHeap()));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.serveStatic("/", MYFS, "/").setDefaultFile("index.htm");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.onNotFound([](AsyncWebServerRequest *request){
 | 
				
			||||||
 | 
					    Serial.printf("NOT_FOUND: ");
 | 
				
			||||||
 | 
					    if(request->method() == HTTP_GET)
 | 
				
			||||||
 | 
					      Serial.printf("GET");
 | 
				
			||||||
 | 
					    else if(request->method() == HTTP_POST)
 | 
				
			||||||
 | 
					      Serial.printf("POST");
 | 
				
			||||||
 | 
					    else if(request->method() == HTTP_DELETE)
 | 
				
			||||||
 | 
					      Serial.printf("DELETE");
 | 
				
			||||||
 | 
					    else if(request->method() == HTTP_PUT)
 | 
				
			||||||
 | 
					      Serial.printf("PUT");
 | 
				
			||||||
 | 
					    else if(request->method() == HTTP_PATCH)
 | 
				
			||||||
 | 
					      Serial.printf("PATCH");
 | 
				
			||||||
 | 
					    else if(request->method() == HTTP_HEAD)
 | 
				
			||||||
 | 
					      Serial.printf("HEAD");
 | 
				
			||||||
 | 
					    else if(request->method() == HTTP_OPTIONS)
 | 
				
			||||||
 | 
					      Serial.printf("OPTIONS");
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      Serial.printf("UNKNOWN");
 | 
				
			||||||
 | 
					    Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(request->contentLength()){
 | 
				
			||||||
 | 
					      Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str());
 | 
				
			||||||
 | 
					      Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int headers = request->headers();
 | 
				
			||||||
 | 
					    int i;
 | 
				
			||||||
 | 
					    for(i=0;i<headers;i++){
 | 
				
			||||||
 | 
					      AsyncWebHeader* h = request->getHeader(i);
 | 
				
			||||||
 | 
					      Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int params = request->params();
 | 
				
			||||||
 | 
					    for(i=0;i<params;i++){
 | 
				
			||||||
 | 
					      AsyncWebParameter* p = request->getParam(i);
 | 
				
			||||||
 | 
					      if(p->isFile()){
 | 
				
			||||||
 | 
					        Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
 | 
				
			||||||
 | 
					      } else if(p->isPost()){
 | 
				
			||||||
 | 
					        Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    request->send(404);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){
 | 
				
			||||||
 | 
					    if(!index)
 | 
				
			||||||
 | 
					      Serial.printf("UploadStart: %s\n", filename.c_str());
 | 
				
			||||||
 | 
					    Serial.printf("%s", (const char*)data);
 | 
				
			||||||
 | 
					    if(final)
 | 
				
			||||||
 | 
					      Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
 | 
				
			||||||
 | 
					    if(!index)
 | 
				
			||||||
 | 
					      Serial.printf("BodyStart: %u\n", total);
 | 
				
			||||||
 | 
					    Serial.printf("%s", (const char*)data);
 | 
				
			||||||
 | 
					    if(index + len == total)
 | 
				
			||||||
 | 
					      Serial.printf("BodyEnd: %u\n", total);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  server.begin();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void loop(){
 | 
				
			||||||
 | 
					  ArduinoOTA.handle();
 | 
				
			||||||
 | 
					  ws.cleanupClients();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								examples/ESP_AsyncFSBrowser/data/.exclude.files
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					/*.gz
 | 
				
			||||||
 | 
					/edit_gz
 | 
				
			||||||
 | 
					/.exclude.files
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/ace.ico.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/acefull.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/edit_gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/folder/image.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.9 KiB  | 
							
								
								
									
										1
									
								
								examples/ESP_AsyncFSBrowser/data/folder/test.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Test
 | 
				
			||||||
							
								
								
									
										131
									
								
								examples/ESP_AsyncFSBrowser/data/index.htm
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					<!--
 | 
				
			||||||
 | 
					  FSWebServer - Example Index Page
 | 
				
			||||||
 | 
					  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
 | 
				
			||||||
 | 
					  This file is part of the ESP8266WebServer library for Arduino environment.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  This library is free software; you can redistribute it and/or
 | 
				
			||||||
 | 
					  modify it under the terms of the GNU Lesser General Public
 | 
				
			||||||
 | 
					  License as published by the Free Software Foundation; either
 | 
				
			||||||
 | 
					  version 2.1 of the License, or (at your option) any later version.
 | 
				
			||||||
 | 
					  This library is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
				
			||||||
 | 
					  Lesser General Public License for more details.
 | 
				
			||||||
 | 
					  You should have received a copy of the GNU Lesser General Public
 | 
				
			||||||
 | 
					  License along with this library; if not, write to the Free Software
 | 
				
			||||||
 | 
					  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
				
			||||||
 | 
					-->
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
 | 
				
			||||||
 | 
					    <title>WebSocketTester</title>
 | 
				
			||||||
 | 
					    <style type="text/css" media="screen">
 | 
				
			||||||
 | 
					    body {
 | 
				
			||||||
 | 
					      margin:0;
 | 
				
			||||||
 | 
					      padding:0;
 | 
				
			||||||
 | 
					      background-color: black;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #dbg, #input_div, #input_el {
 | 
				
			||||||
 | 
					      font-family: monaco;
 | 
				
			||||||
 | 
					      font-size: 12px;
 | 
				
			||||||
 | 
					      line-height: 13px;
 | 
				
			||||||
 | 
					      color: #AAA;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #dbg, #input_div {
 | 
				
			||||||
 | 
					      margin:0;
 | 
				
			||||||
 | 
					      padding:0;
 | 
				
			||||||
 | 
					      padding-left:4px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #input_el {
 | 
				
			||||||
 | 
					      width:98%;
 | 
				
			||||||
 | 
					      background-color: rgba(0,0,0,0);
 | 
				
			||||||
 | 
					      border: 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    #input_el:focus {
 | 
				
			||||||
 | 
					      outline: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					    <script type="text/javascript">
 | 
				
			||||||
 | 
					    var ws = null;
 | 
				
			||||||
 | 
					    function ge(s){ return document.getElementById(s);}
 | 
				
			||||||
 | 
					    function ce(s){ return document.createElement(s);}
 | 
				
			||||||
 | 
					    function stb(){ window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight); }
 | 
				
			||||||
 | 
					    function sendBlob(str){
 | 
				
			||||||
 | 
					      var buf = new Uint8Array(str.length);
 | 
				
			||||||
 | 
					      for (var i = 0; i < str.length; ++i) buf[i] = str.charCodeAt(i);
 | 
				
			||||||
 | 
					      ws.send(buf);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    function addMessage(m){
 | 
				
			||||||
 | 
					      var msg = ce("div");
 | 
				
			||||||
 | 
					      msg.innerText = m;
 | 
				
			||||||
 | 
					      ge("dbg").appendChild(msg);
 | 
				
			||||||
 | 
					      stb();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    function startSocket(){
 | 
				
			||||||
 | 
					      ws = new WebSocket('ws://'+document.location.host+'/ws',['arduino']);
 | 
				
			||||||
 | 
					      ws.binaryType = "arraybuffer";
 | 
				
			||||||
 | 
					      ws.onopen = function(e){
 | 
				
			||||||
 | 
					        addMessage("Connected");
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      ws.onclose = function(e){
 | 
				
			||||||
 | 
					        addMessage("Disconnected");
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      ws.onerror = function(e){
 | 
				
			||||||
 | 
					        console.log("ws error", e);
 | 
				
			||||||
 | 
					        addMessage("Error");
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      ws.onmessage = function(e){
 | 
				
			||||||
 | 
					        var msg = "";
 | 
				
			||||||
 | 
					        if(e.data instanceof ArrayBuffer){
 | 
				
			||||||
 | 
					          msg = "BIN:";
 | 
				
			||||||
 | 
					          var bytes = new Uint8Array(e.data);
 | 
				
			||||||
 | 
					          for (var i = 0; i < bytes.length; i++) {
 | 
				
			||||||
 | 
					            msg += String.fromCharCode(bytes[i]);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          msg = "TXT:"+e.data;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        addMessage(msg);
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      ge("input_el").onkeydown = function(e){
 | 
				
			||||||
 | 
					        stb();
 | 
				
			||||||
 | 
					        if(e.keyCode == 13 && ge("input_el").value != ""){
 | 
				
			||||||
 | 
					          ws.send(ge("input_el").value);
 | 
				
			||||||
 | 
					          ge("input_el").value = "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    function startEvents(){
 | 
				
			||||||
 | 
					      var es = new EventSource('/events');
 | 
				
			||||||
 | 
					      es.onopen = function(e) {
 | 
				
			||||||
 | 
					        addMessage("Events Opened");
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      es.onerror = function(e) {
 | 
				
			||||||
 | 
					        if (e.target.readyState != EventSource.OPEN) {
 | 
				
			||||||
 | 
					          addMessage("Events Closed");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      es.onmessage = function(e) {
 | 
				
			||||||
 | 
					        addMessage("Event: " + e.data);
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      es.addEventListener('ota', function(e) {
 | 
				
			||||||
 | 
					        addMessage("Event[ota]: " + e.data);
 | 
				
			||||||
 | 
					      }, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    function onBodyLoad(){
 | 
				
			||||||
 | 
					      startSocket();
 | 
				
			||||||
 | 
					      startEvents();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body id="body" onload="onBodyLoad()">
 | 
				
			||||||
 | 
					    <pre id="dbg"></pre>
 | 
				
			||||||
 | 
					    <div id="input_div">
 | 
				
			||||||
 | 
					      $<input type="text" value="" id="input_el">
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/worker-css.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/worker-html.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/worker-javascript.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/ESP_AsyncFSBrowser/data/worker-json.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -1,49 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// https://github.com/ESP32Async/ESPAsyncWebServer/discussions/23
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
  Serial.println("begin() - run: curl -v http://192.168.4.1/ => should succeed");
 | 
					 | 
				
			||||||
  delay(10000);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Serial.println("end()");
 | 
					 | 
				
			||||||
  server.end();
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
  Serial.println("begin() - run: curl -v http://192.168.4.1/ => should succeed");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,136 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to use setFilter to route requests to different handlers based on WiFi mode
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <DNSServer.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#include "ESPAsyncWebServer.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static DNSServer dnsServer;
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CaptiveRequestHandler : public AsyncWebHandler {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
  bool canHandle(__unused AsyncWebServerRequest *request) const override {
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void handleRequest(AsyncWebServerRequest *request) override {
 | 
					 | 
				
			||||||
    AsyncResponseStream *response = request->beginResponseStream("text/html");
 | 
					 | 
				
			||||||
    response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
 | 
					 | 
				
			||||||
    response->print("<p>This is out captive portal front page.</p>");
 | 
					 | 
				
			||||||
    response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
    response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    response->print("</body></html>");
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool hit1 = false;
 | 
					 | 
				
			||||||
bool hit2 = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        Serial.println("Captive portal request...");
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
        Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
        Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
 | 
					 | 
				
			||||||
#if ESP_IDF_VERSION_MAJOR >= 5
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
        Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
        Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
        Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER");
 | 
					 | 
				
			||||||
        Serial.println(WiFi.localIP() == request->client()->localIP());
 | 
					 | 
				
			||||||
        Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", "This is the captive portal");
 | 
					 | 
				
			||||||
        hit1 = true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .setFilter(ON_AP_FILTER);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        Serial.println("Website request...");
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
        Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
        Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
 | 
					 | 
				
			||||||
#if ESP_IDF_VERSION_MAJOR >= 5
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
        Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
        Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
        Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER");
 | 
					 | 
				
			||||||
        Serial.println(WiFi.localIP() == request->client()->localIP());
 | 
					 | 
				
			||||||
        Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", "This is the website");
 | 
					 | 
				
			||||||
        hit2 = true;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .setFilter(ON_STA_FILTER);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // assert(WiFi.softAP("esp-captive-portal"));
 | 
					 | 
				
			||||||
  // dnsServer.start(53, "*", WiFi.softAPIP());
 | 
					 | 
				
			||||||
  // server.begin();
 | 
					 | 
				
			||||||
  // Serial.println("Captive portal started!");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // while (!hit1) {
 | 
					 | 
				
			||||||
  //   dnsServer.processNextRequest();
 | 
					 | 
				
			||||||
  //   yield();
 | 
					 | 
				
			||||||
  // }
 | 
					 | 
				
			||||||
  // delay(1000); // Wait for the client to process the response
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Serial.println("Captive portal opened, stopping it and connecting to WiFi...");
 | 
					 | 
				
			||||||
  // dnsServer.stop();
 | 
					 | 
				
			||||||
  // WiFi.softAPdisconnect();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.persistent(false);
 | 
					 | 
				
			||||||
  WiFi.begin("IoT");
 | 
					 | 
				
			||||||
  while (WiFi.status() != WL_CONNECTED) {
 | 
					 | 
				
			||||||
    delay(500);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // while (!hit2) {
 | 
					 | 
				
			||||||
  //   delay(10);
 | 
					 | 
				
			||||||
  // }
 | 
					 | 
				
			||||||
  // delay(1000); // Wait for the client to process the response
 | 
					 | 
				
			||||||
  // ESP.restart();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,107 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to serve a large HTML page from flash memory without copying it to heap in a temporary buffer
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <title>Sample HTML</title>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
    <h1>Hello, World!</h1>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // need to cast to uint8_t*
 | 
					 | 
				
			||||||
    // if you do not, the const char* will be copied in a temporary String buffer
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,111 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Show how to manipulate headers in the request / response
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// request logger
 | 
					 | 
				
			||||||
static AsyncLoggingMiddleware requestLogger;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// filter out specific headers from the incoming request
 | 
					 | 
				
			||||||
static AsyncHeaderFilterMiddleware headerFilter;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// remove all headers from the incoming request except the ones provided in the constructor
 | 
					 | 
				
			||||||
AsyncHeaderFreeMiddleware headerFree;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  requestLogger.setEnabled(true);
 | 
					 | 
				
			||||||
  requestLogger.setOutput(Serial);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  headerFilter.filter("X-Remove-Me");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  headerFree.keep("X-Keep-Me");
 | 
					 | 
				
			||||||
  headerFree.keep("host");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addMiddlewares({&requestLogger, &headerFilter});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // x-remove-me header will be removed
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v -H "X-Header: Foo" -H "x-remove-me: value" http://192.168.4.1/remove
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/remove", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // print all headers
 | 
					 | 
				
			||||||
    for (size_t i = 0; i < request->headers(); i++) {
 | 
					 | 
				
			||||||
      const AsyncWebHeader *h = request->getHeader(i);
 | 
					 | 
				
			||||||
      Serial.printf("Header[%s]: %s\n", h->name().c_str(), h->value().c_str());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Only headers x-keep-me and host will be kept
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v -H "x-keep-me: value" -H "x-remove-me: value" http://192.168.4.1/keep
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/keep", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        // print all headers
 | 
					 | 
				
			||||||
        for (size_t i = 0; i < request->headers(); i++) {
 | 
					 | 
				
			||||||
          const AsyncWebHeader *h = request->getHeader(i);
 | 
					 | 
				
			||||||
          Serial.printf("Header[%s]: %s\n", h->name().c_str(), h->value().c_str());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .addMiddleware(&headerFree);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
    response->addHeader(AsyncWebHeader::parse("X-Test-1: value1"));
 | 
					 | 
				
			||||||
    response->addHeader(AsyncWebHeader::parse("X-Test-2:value2"));
 | 
					 | 
				
			||||||
    response->addHeader(AsyncWebHeader::parse("X-Test-3:"));
 | 
					 | 
				
			||||||
    response->addHeader(AsyncWebHeader::parse("X-Test-4: "));
 | 
					 | 
				
			||||||
    response->addHeader(AsyncWebHeader::parse(""));
 | 
					 | 
				
			||||||
    response->addHeader(AsyncWebHeader::parse(":"));
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
< HTTP/1.1 200 OK
 | 
					 | 
				
			||||||
< connection: close
 | 
					 | 
				
			||||||
< X-Test-1: value1
 | 
					 | 
				
			||||||
< X-Test-2: value2
 | 
					 | 
				
			||||||
< X-Test-3:
 | 
					 | 
				
			||||||
< X-Test-4:
 | 
					 | 
				
			||||||
< accept-ranges: none
 | 
					 | 
				
			||||||
< content-length: 13
 | 
					 | 
				
			||||||
< content-type: text/plain
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,69 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Query and send headers
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    //List all collected headers
 | 
					 | 
				
			||||||
    int headers = request->headers();
 | 
					 | 
				
			||||||
    int i;
 | 
					 | 
				
			||||||
    for (i = 0; i < headers; i++) {
 | 
					 | 
				
			||||||
      const AsyncWebHeader *h = request->getHeader(i);
 | 
					 | 
				
			||||||
      Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //Add header to the response
 | 
					 | 
				
			||||||
    response->addHeader("Server", "ESP Async Web Server");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //Add multiple headers with the same name
 | 
					 | 
				
			||||||
    response->addHeader("Set-Cookie", "sessionId=38afes7a8", false);
 | 
					 | 
				
			||||||
    response->addHeader("Set-Cookie", "id=a3fWa; Max-Age=2592000", false);
 | 
					 | 
				
			||||||
    response->addHeader("Set-Cookie", "qwerty=219ffwef9w0f; Domain=example.com", false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //Remove specific header
 | 
					 | 
				
			||||||
    response->removeHeader("Set-Cookie", "sessionId=38afes7a8");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //Remove all headers with the same name
 | 
					 | 
				
			||||||
    response->removeHeader("Set-Cookie");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  //Sleep in the loop task to not keep the CPU busy
 | 
					 | 
				
			||||||
  delay(1000);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,103 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to send and receive Json data
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if __has_include("ArduinoJson.h")
 | 
					 | 
				
			||||||
#include <ArduinoJson.h>
 | 
					 | 
				
			||||||
#include <AsyncJson.h>
 | 
					 | 
				
			||||||
#include <AsyncMessagePack.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if __has_include("ArduinoJson.h")
 | 
					 | 
				
			||||||
static AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler("/json2");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if __has_include("ArduinoJson.h")
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // sends JSON using AsyncJsonResponse
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/json1
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/json1", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    AsyncJsonResponse *response = new AsyncJsonResponse();
 | 
					 | 
				
			||||||
    JsonObject root = response->getRoot().to<JsonObject>();
 | 
					 | 
				
			||||||
    root["hello"] = "world";
 | 
					 | 
				
			||||||
    response->setLength();
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Send JSON using AsyncResponseStream
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/json2
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/json2", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    AsyncResponseStream *response = request->beginResponseStream("application/json");
 | 
					 | 
				
			||||||
    JsonDocument doc;
 | 
					 | 
				
			||||||
    JsonObject root = doc.to<JsonObject>();
 | 
					 | 
				
			||||||
    root["foo"] = "bar";
 | 
					 | 
				
			||||||
    serializeJson(root, *response);
 | 
					 | 
				
			||||||
    Serial.println();
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
 | 
					 | 
				
			||||||
  // curl -v -X PUT -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // edge cases:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 5" http://192.168.4.1/json2 => rx timeout
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 2" http://192.168.4.1/json2 => 12
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 4" http://192.168.4.1/json2 => 1234
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 10" http://192.168.4.1/json2 => rx timeout
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/json" -d "12345678" -H "Content-Length: 8" http://192.168.4.1/json2 => 12345678
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/json" -d "123456789" -H "Content-Length: 8" http://192.168.4.1/json2 => 12345678
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/json" -d "123456789" -H "Content-Length: 9" http://192.168.4.1/json2 => 413: Content length exceeds maximum allowed
 | 
					 | 
				
			||||||
  handler->setMaxContentLength(8);
 | 
					 | 
				
			||||||
  handler->setMethod(HTTP_POST | HTTP_PUT);
 | 
					 | 
				
			||||||
  handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
 | 
					 | 
				
			||||||
    serializeJson(json, Serial);
 | 
					 | 
				
			||||||
    Serial.println();
 | 
					 | 
				
			||||||
    AsyncJsonResponse *response = new AsyncJsonResponse();
 | 
					 | 
				
			||||||
    JsonObject root = response->getRoot().to<JsonObject>();
 | 
					 | 
				
			||||||
    root["hello"] = json.as<JsonObject>()["name"];
 | 
					 | 
				
			||||||
    response->setLength();
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addHandler(handler);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,49 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Show how to log the incoming request and response as a curl-like syntax
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
static AsyncLoggingMiddleware requestLogger;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  requestLogger.setEnabled(true);
 | 
					 | 
				
			||||||
  requestLogger.setOutput(Serial);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addMiddleware(&requestLogger);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v -H "X-Header:Foo" http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,88 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to send and receive Message Pack data
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if __has_include("ArduinoJson.h")
 | 
					 | 
				
			||||||
#include <ArduinoJson.h>
 | 
					 | 
				
			||||||
#include <AsyncJson.h>
 | 
					 | 
				
			||||||
#include <AsyncMessagePack.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if __has_include("ArduinoJson.h")
 | 
					 | 
				
			||||||
static AsyncCallbackMessagePackWebHandler *handler = new AsyncCallbackMessagePackWebHandler("/msgpack2");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if __has_include("ArduinoJson.h")
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // sends MessagePack using AsyncMessagePackResponse
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/msgpack1
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/msgpack1", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    AsyncMessagePackResponse *response = new AsyncMessagePackResponse();
 | 
					 | 
				
			||||||
    JsonObject root = response->getRoot().to<JsonObject>();
 | 
					 | 
				
			||||||
    root["hello"] = "world";
 | 
					 | 
				
			||||||
    response->setLength();
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Send MessagePack using AsyncResponseStream
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/msgpack2
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/msgpack2", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    AsyncResponseStream *response = request->beginResponseStream("application/msgpack");
 | 
					 | 
				
			||||||
    JsonDocument doc;
 | 
					 | 
				
			||||||
    JsonObject root = doc.to<JsonObject>();
 | 
					 | 
				
			||||||
    root["foo"] = "bar";
 | 
					 | 
				
			||||||
    serializeMsgPack(root, *response);
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handler->setMethod(HTTP_POST | HTTP_PUT);
 | 
					 | 
				
			||||||
  handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
 | 
					 | 
				
			||||||
    serializeJson(json, Serial);
 | 
					 | 
				
			||||||
    AsyncMessagePackResponse *response = new AsyncMessagePackResponse();
 | 
					 | 
				
			||||||
    JsonObject root = response->getRoot().to<JsonObject>();
 | 
					 | 
				
			||||||
    root["hello"] = json.as<JsonObject>()["name"];
 | 
					 | 
				
			||||||
    response->setLength();
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addHandler(handler);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,82 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Show how to sue Middleware
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// New middleware classes can be created!
 | 
					 | 
				
			||||||
class MyMiddleware : public AsyncMiddleware {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
  void run(AsyncWebServerRequest *request, ArMiddlewareNext next) override {
 | 
					 | 
				
			||||||
    Serial.printf("Before handler: %s %s\n", request->methodToString(), request->url().c_str());
 | 
					 | 
				
			||||||
    next();  // continue middleware chain
 | 
					 | 
				
			||||||
    Serial.printf("After handler: response code=%d\n", request->getResponse()->code());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // add a global middleware to the server
 | 
					 | 
				
			||||||
  server.addMiddleware(new MyMiddleware());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Test with:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // - curl -v http://192.168.4.1/            => 200 OK
 | 
					 | 
				
			||||||
  // - curl -v http://192.168.4.1/?user=anon  => 403 Forbidden
 | 
					 | 
				
			||||||
  // - curl -v http://192.168.4.1/?user=foo   => 200 OK
 | 
					 | 
				
			||||||
  // - curl -v http://192.168.4.1/?user=error => 400 ERROR
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  AsyncCallbackWebHandler &handler = server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    Serial.printf("In Handler: %s %s\n", request->methodToString(), request->url().c_str());
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // add a middleware to this handler only to send 403 if the user is anon
 | 
					 | 
				
			||||||
  handler.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
 | 
					 | 
				
			||||||
    Serial.println("Checking user=anon");
 | 
					 | 
				
			||||||
    if (request->hasParam("user") && request->getParam("user")->value() == "anon") {
 | 
					 | 
				
			||||||
      request->send(403, "text/plain", "Forbidden");
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      next();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // add a middleware to this handler that will replace the previously created response by another one
 | 
					 | 
				
			||||||
  handler.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
 | 
					 | 
				
			||||||
    next();
 | 
					 | 
				
			||||||
    Serial.println("Checking user=error");
 | 
					 | 
				
			||||||
    if (request->hasParam("user") && request->getParam("user")->value() == "error") {
 | 
					 | 
				
			||||||
      request->send(400, "text/plain", "ERROR");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,122 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Query parameters and body parameters
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <title>POST Request with Multiple Parameters</title>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
    <form action="http://192.168.4.1" method="POST">
 | 
					 | 
				
			||||||
        <label for="who">Who?</label>
 | 
					 | 
				
			||||||
        <input type="text" id="who" name="who" value="Carl"><br>
 | 
					 | 
				
			||||||
        <label for="g0">g0:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="g0" name="g0" value="1"><br>
 | 
					 | 
				
			||||||
        <label for="a0">a0:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="a0" name="a0" value="2"><br>
 | 
					 | 
				
			||||||
        <label for="n0">n0:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="n0" name="n0" value="3"><br>
 | 
					 | 
				
			||||||
        <label for="t10">t10:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t10" name="t10" value="3"><br>
 | 
					 | 
				
			||||||
        <label for="t20">t20:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t20" name="t20" value="4"><br>
 | 
					 | 
				
			||||||
        <label for="t30">t30:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t30" name="t30" value="5"><br>
 | 
					 | 
				
			||||||
        <label for="t40">t40:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t40" name="t40" value="6"><br>
 | 
					 | 
				
			||||||
        <label for="t50">t50:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t50" name="t50" value="7"><br>
 | 
					 | 
				
			||||||
        <label for="g1">g1:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="g1" name="g1" value="2"><br>
 | 
					 | 
				
			||||||
        <label for="a1">a1:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="a1" name="a1" value="2"><br>
 | 
					 | 
				
			||||||
        <label for="n1">n1:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="n1" name="n1" value="3"><br>
 | 
					 | 
				
			||||||
        <label for="t11">t11:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t11" name="t11" value="13"><br>
 | 
					 | 
				
			||||||
        <label for="t21">t21:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t21" name="t21" value="14"><br>
 | 
					 | 
				
			||||||
        <label for="t31">t31:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t31" name="t31" value="15"><br>
 | 
					 | 
				
			||||||
        <label for="t41">t41:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t41" name="t41" value="16"><br>
 | 
					 | 
				
			||||||
        <label for="t51">t51:</label>
 | 
					 | 
				
			||||||
        <input type="text" id="t51" name="t51" value="17"><br>
 | 
					 | 
				
			||||||
        <input type="submit" value="Submit">
 | 
					 | 
				
			||||||
    </form>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Get query parameters
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/?who=Bob
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    if (request->hasParam("who")) {
 | 
					 | 
				
			||||||
      Serial.printf("Who? %s\n", request->getParam("who")->value().c_str());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Get form body parameters
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v -H "Content-Type: application/x-www-form-urlencoded" -d "who=Carl" -d "param=value" http://192.168.4.1/
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // display params
 | 
					 | 
				
			||||||
    size_t count = request->params();
 | 
					 | 
				
			||||||
    for (size_t i = 0; i < count; i++) {
 | 
					 | 
				
			||||||
      const AsyncWebParameter *p = request->getParam(i);
 | 
					 | 
				
			||||||
      Serial.printf("PARAM[%u]: %s = %s\n", i, p->name().c_str(), p->value().c_str());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // get who param
 | 
					 | 
				
			||||||
    String who;
 | 
					 | 
				
			||||||
    if (request->hasParam("who", true)) {
 | 
					 | 
				
			||||||
      who = request->getParam("who", true)->value();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      who = "No message sent";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello " + who + "!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,130 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// - Download ESP32 partition by name and/or type and/or subtype
 | 
					 | 
				
			||||||
// - Support encrypted and non-encrypted partitions
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
#include <LittleFS.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifndef ESP32
 | 
					 | 
				
			||||||
// this example is only for the ESP32
 | 
					 | 
				
			||||||
void setup() {}
 | 
					 | 
				
			||||||
void loop() {}
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <esp_partition.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  LittleFS.begin(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // To upload the FS partition, run:
 | 
					 | 
				
			||||||
  // > pio run -e arduino-3 -t buildfs
 | 
					 | 
				
			||||||
  // > pio run -e arduino-3 -t uploadfs
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Examples:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // - Download the partition named "spiffs": http://192.168.4.1/partition?label=spiffs
 | 
					 | 
				
			||||||
  // - Download the partition named "spiffs" with type "data": http://192.168.4.1/partition?label=spiffs&type=1
 | 
					 | 
				
			||||||
  // - Download the partition named "spiffs" with type "data" and subtype "spiffs": http://192.168.4.1/partition?label=spiffs&type=1&subtype=130
 | 
					 | 
				
			||||||
  // - Download the partition with subtype "nvs": http://192.168.4.1/partition?type=1&subtype=2
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // "type" and "subtype" IDs can be found in esp_partition.h header file.
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Add "&raw=false" parameter to download the partition unencrypted (for encrypted partitions).
 | 
					 | 
				
			||||||
  // By default, the raw partition is downloaded, so if a partition is encrypted, the encrypted data will be downloaded.
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // To browse a downloaded LittleFS partition, you can use https://tniessen.github.io/littlefs-disk-img-viewer/ (block size is 4096)
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/partition", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    const AsyncWebParameter *pLabel = request->getParam("label");
 | 
					 | 
				
			||||||
    const AsyncWebParameter *pType = request->getParam("type");
 | 
					 | 
				
			||||||
    const AsyncWebParameter *pSubtype = request->getParam("subtype");
 | 
					 | 
				
			||||||
    const AsyncWebParameter *pRaw = request->getParam("raw");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!pLabel && !pType && !pSubtype) {
 | 
					 | 
				
			||||||
      request->send(400, "text/plain", "Bad request: missing parameter");
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    esp_partition_type_t type = ESP_PARTITION_TYPE_ANY;
 | 
					 | 
				
			||||||
    esp_partition_subtype_t subtype = ESP_PARTITION_SUBTYPE_ANY;
 | 
					 | 
				
			||||||
    const char *label = nullptr;
 | 
					 | 
				
			||||||
    bool raw = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (pLabel) {
 | 
					 | 
				
			||||||
      label = pLabel->value().c_str();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (pType) {
 | 
					 | 
				
			||||||
      type = (esp_partition_type_t)pType->value().toInt();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (pSubtype) {
 | 
					 | 
				
			||||||
      subtype = (esp_partition_subtype_t)pSubtype->value().toInt();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (pRaw && pRaw->value() == "false") {
 | 
					 | 
				
			||||||
      raw = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const esp_partition_t *partition = esp_partition_find_first(type, subtype, label);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!partition) {
 | 
					 | 
				
			||||||
      request->send(404, "text/plain", "Partition not found");
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    AsyncWebServerResponse *response =
 | 
					 | 
				
			||||||
      request->beginChunkedResponse("application/octet-stream", [partition, raw](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
 | 
					 | 
				
			||||||
        const size_t remaining = partition->size - index;
 | 
					 | 
				
			||||||
        if (!remaining) {
 | 
					 | 
				
			||||||
          return 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const size_t len = std::min(maxLen, remaining);
 | 
					 | 
				
			||||||
        if (raw && esp_partition_read_raw(partition, index, buffer, len) == ESP_OK) {
 | 
					 | 
				
			||||||
          return len;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!raw && esp_partition_read(partition, index, buffer, len) == ESP_OK) {
 | 
					 | 
				
			||||||
          return len;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return 0;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    response->addHeader("Content-Disposition", "attachment; filename=" + String(partition->label) + ".bin");
 | 
					 | 
				
			||||||
    response->setContentLength(partition->size);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
@@ -1,243 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Perf tests
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <title>Sample HTML</title>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
    <h1>Hello, World!</h1>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
static constexpr char characters[] = "0123456789ABCDEF";
 | 
					 | 
				
			||||||
static size_t charactersIndex = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
static AsyncEventSource events("/events");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static volatile size_t requests = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Pauses in the request parsing phase
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // autocannon -c 32 -w 32 -a 96 -t 30 --renderStatusCodes -m POST -H "Content-Type: application/json" -b '{"foo": "bar"}' http://192.168.4.1/delay
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/json" -d '{"game": "test"}' http://192.168.4.1/delay
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.onNotFound([](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    requests = requests + 1;
 | 
					 | 
				
			||||||
    if (request->url() == "/delay") {
 | 
					 | 
				
			||||||
      request->send(200, "application/json", "{\"status\":\"OK\"}");
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      request->send(404, "text/plain", "Not found");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
 | 
					 | 
				
			||||||
    if (request->url() == "/delay") {
 | 
					 | 
				
			||||||
      delay(3000);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // HTTP endpoint
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // > brew install autocannon
 | 
					 | 
				
			||||||
  // > autocannon -c 10 -w 10 -d 20 http://192.168.4.1
 | 
					 | 
				
			||||||
  // > autocannon -c 16 -w 16 -d 20 http://192.168.4.1
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // need to cast to uint8_t*
 | 
					 | 
				
			||||||
    // if you do not, the const char* will be copied in a temporary String buffer
 | 
					 | 
				
			||||||
    requests = requests + 1;
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // IMPORTANT - DO NOT WRITE SUCH CODE IN PRODUCTON !
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // This example simulates the slowdown that can happen when:
 | 
					 | 
				
			||||||
  // - downloading a huge file from sdcard
 | 
					 | 
				
			||||||
  // - doing some file listing on SDCard because it is horribly slow to get a file listing with file stats on SDCard.
 | 
					 | 
				
			||||||
  // So in both cases, ESP would deadlock or TWDT would trigger.
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // This example simulats that by slowing down the chunk callback:
 | 
					 | 
				
			||||||
  // - d=2000 is the delay in ms in the callback
 | 
					 | 
				
			||||||
  // - l=10000 is the length of the response
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // time curl -N -v -G -d 'd=2000' -d 'l=10000'  http://192.168.4.1/slow.html --output -
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    requests = requests + 1;
 | 
					 | 
				
			||||||
    uint32_t d = request->getParam("d")->value().toInt();
 | 
					 | 
				
			||||||
    uint32_t l = request->getParam("l")->value().toInt();
 | 
					 | 
				
			||||||
    Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l);
 | 
					 | 
				
			||||||
    AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [d, l](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
 | 
					 | 
				
			||||||
      Serial.printf("%u\n", index);
 | 
					 | 
				
			||||||
      // finished ?
 | 
					 | 
				
			||||||
      if (index >= l) {
 | 
					 | 
				
			||||||
        return 0;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // slow down the task to simulate some heavy processing, like SD card reading
 | 
					 | 
				
			||||||
      delay(d);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      memset(buffer, characters[charactersIndex], 256);
 | 
					 | 
				
			||||||
      charactersIndex = (charactersIndex + 1) % sizeof(characters);
 | 
					 | 
				
			||||||
      return 256;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // SSS endpoint
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // launch 16 concurrent workers for 30 seconds
 | 
					 | 
				
			||||||
  // > for i in {1..10}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
 | 
					 | 
				
			||||||
  // > for i in {1..16}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // With AsyncTCP, with 16 workers: a lot of "Event message queue overflow: discard message", no crash
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Total: 1711 events, 427.75 events / second
 | 
					 | 
				
			||||||
  // Total: 1711 events, 427.75 events / second
 | 
					 | 
				
			||||||
  // Total: 1626 events, 406.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1562 events, 390.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1706 events, 426.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1659 events, 414.75 events / second
 | 
					 | 
				
			||||||
  // Total: 1624 events, 406.00 events / second
 | 
					 | 
				
			||||||
  // Total: 1706 events, 426.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1487 events, 371.75 events / second
 | 
					 | 
				
			||||||
  // Total: 1573 events, 393.25 events / second
 | 
					 | 
				
			||||||
  // Total: 1569 events, 392.25 events / second
 | 
					 | 
				
			||||||
  // Total: 1559 events, 389.75 events / second
 | 
					 | 
				
			||||||
  // Total: 1560 events, 390.00 events / second
 | 
					 | 
				
			||||||
  // Total: 1562 events, 390.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1626 events, 406.50 events / second
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // With AsyncTCP, with 10 workers:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Total: 2038 events, 509.50 events / second
 | 
					 | 
				
			||||||
  // Total: 2120 events, 530.00 events / second
 | 
					 | 
				
			||||||
  // Total: 2119 events, 529.75 events / second
 | 
					 | 
				
			||||||
  // Total: 2038 events, 509.50 events / second
 | 
					 | 
				
			||||||
  // Total: 2037 events, 509.25 events / second
 | 
					 | 
				
			||||||
  // Total: 2119 events, 529.75 events / second
 | 
					 | 
				
			||||||
  // Total: 2119 events, 529.75 events / second
 | 
					 | 
				
			||||||
  // Total: 2120 events, 530.00 events / second
 | 
					 | 
				
			||||||
  // Total: 2038 events, 509.50 events / second
 | 
					 | 
				
			||||||
  // Total: 2038 events, 509.50 events / second
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // With AsyncTCPSock, with 16 workers: ESP32 CRASH !!!
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // With AsyncTCPSock, with 10 workers:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // Total: 1242 events, 310.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1242 events, 310.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1242 events, 310.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1242 events, 310.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1181 events, 295.25 events / second
 | 
					 | 
				
			||||||
  // Total: 1182 events, 295.50 events / second
 | 
					 | 
				
			||||||
  // Total: 1240 events, 310.00 events / second
 | 
					 | 
				
			||||||
  // Total: 1181 events, 295.25 events / second
 | 
					 | 
				
			||||||
  // Total: 1181 events, 295.25 events / second
 | 
					 | 
				
			||||||
  // Total: 1183 events, 295.75 events / second
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.addHandler(&events);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static uint32_t lastSSE = 0;
 | 
					 | 
				
			||||||
static uint32_t deltaSSE = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static uint32_t lastHeap = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  uint32_t now = millis();
 | 
					 | 
				
			||||||
  if (now - lastSSE >= deltaSSE) {
 | 
					 | 
				
			||||||
    events.send(String("ping-") + now, "heartbeat", now);
 | 
					 | 
				
			||||||
    lastSSE = millis();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef ESP32
 | 
					 | 
				
			||||||
  if (now - lastHeap >= 2000) {
 | 
					 | 
				
			||||||
    Serial.printf("Uptime: %3lu s, requests: %3u, Free heap: %" PRIu32 "\n", millis() / 1000, requests, ESP.getFreeHeap());
 | 
					 | 
				
			||||||
    lastHeap = now;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,64 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Show how to rate limit the server or some endpoints
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
static AsyncRateLimitMiddleware rateLimit;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // maximum 5 requests per 10 seconds
 | 
					 | 
				
			||||||
  rateLimit.setMaxRequests(5);
 | 
					 | 
				
			||||||
  rateLimit.setWindowSize(10);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // run quickly several times:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // run quickly several times:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/rate-limited
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server
 | 
					 | 
				
			||||||
    .on(
 | 
					 | 
				
			||||||
      "/rate-limited", HTTP_GET,
 | 
					 | 
				
			||||||
      [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
        request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .addMiddleware(&rateLimit);  // only rate limit this endpoint, but could be applied globally to the server
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,48 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to redirect
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->redirect("/index.txt");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/index.txt
 | 
					 | 
				
			||||||
  server.on("/index.txt", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,91 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
#include <list>
 | 
					 | 
				
			||||||
#include <mutex>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// request handler that is saved from the paused request to communicate with Serial
 | 
					 | 
				
			||||||
static String message;
 | 
					 | 
				
			||||||
static AsyncWebServerRequestPtr serialRequest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// request handler that is saved from the paused request to communicate with GPIO
 | 
					 | 
				
			||||||
static uint8_t pin = 35;
 | 
					 | 
				
			||||||
static AsyncWebServerRequestPtr gpioRequest;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Post a message that will be sent to the Serial console, and pause the request until the user types a key
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "question=Name%3F%20" http://192.168.4.1/serial
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl output should show "Answer: [y/n]" as the response
 | 
					 | 
				
			||||||
  server.on("/serial", HTTP_POST, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    message = request->getParam("question", true)->value();
 | 
					 | 
				
			||||||
    serialRequest = request->pause();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Wait for a GPIO to be high
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/gpio
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // curl output should show "GPIO is high!" as the response
 | 
					 | 
				
			||||||
  server.on("/gpio", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    gpioRequest = request->pause();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  pinMode(pin, INPUT);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(500);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Check for a high voltage on the RX1 pin
 | 
					 | 
				
			||||||
  if (digitalRead(pin) == HIGH) {
 | 
					 | 
				
			||||||
    if (auto request = gpioRequest.lock()) {
 | 
					 | 
				
			||||||
      request->send(200, "text/plain", "GPIO is high!");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // check for an incoming message from the Serial console
 | 
					 | 
				
			||||||
  if (message.length()) {
 | 
					 | 
				
			||||||
    Serial.printf("%s", message.c_str());
 | 
					 | 
				
			||||||
    // drops buffer
 | 
					 | 
				
			||||||
    while (Serial.available()) {
 | 
					 | 
				
			||||||
      Serial.read();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Serial.setTimeout(10000);
 | 
					 | 
				
			||||||
    String response = Serial.readStringUntil('\n');  // waits for a key to be pressed
 | 
					 | 
				
			||||||
    Serial.println();
 | 
					 | 
				
			||||||
    message = emptyString;
 | 
					 | 
				
			||||||
    if (auto request = serialRequest.lock()) {
 | 
					 | 
				
			||||||
      request->send(200, "text/plain", "Answer: " + response);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,165 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <list>
 | 
					 | 
				
			||||||
#include <memory>
 | 
					 | 
				
			||||||
#include <mutex>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ===============================================================
 | 
					 | 
				
			||||||
// The code below is used to simulate some long running operations
 | 
					 | 
				
			||||||
// ===============================================================
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef struct {
 | 
					 | 
				
			||||||
  size_t id;
 | 
					 | 
				
			||||||
  AsyncWebServerRequestPtr requestPtr;
 | 
					 | 
				
			||||||
  uint8_t data;
 | 
					 | 
				
			||||||
} LongRunningOperation;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static std::list<std::unique_ptr<LongRunningOperation>> longRunningOperations;
 | 
					 | 
				
			||||||
static size_t longRunningOperationsCount = 0;
 | 
					 | 
				
			||||||
#ifdef ESP32
 | 
					 | 
				
			||||||
static std::mutex longRunningOperationsMutex;
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void startLongRunningOperation(AsyncWebServerRequestPtr &&requestPtr) {
 | 
					 | 
				
			||||||
#ifdef ESP32
 | 
					 | 
				
			||||||
  std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // LongRunningOperation *op = new LongRunningOperation();
 | 
					 | 
				
			||||||
  std::unique_ptr<LongRunningOperation> op(new LongRunningOperation());
 | 
					 | 
				
			||||||
  op->id = ++longRunningOperationsCount;
 | 
					 | 
				
			||||||
  op->data = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // you need to hold the AsyncWebServerRequestPtr returned by pause();
 | 
					 | 
				
			||||||
  // This object is authorized to leave the scope of the request handler.
 | 
					 | 
				
			||||||
  op->requestPtr = std::move(requestPtr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Serial.printf("[%u] Start long running operation for %" PRIu8 " seconds...\n", op->id, op->data);
 | 
					 | 
				
			||||||
  longRunningOperations.push_back(std::move(op));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static bool processLongRunningOperation(LongRunningOperation *op) {
 | 
					 | 
				
			||||||
  // request was deleted ?
 | 
					 | 
				
			||||||
  if (op->requestPtr.expired()) {
 | 
					 | 
				
			||||||
    Serial.printf("[%u] Request was deleted - stopping long running operation\n", op->id);
 | 
					 | 
				
			||||||
    return true;  // operation finished
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // processing the operation
 | 
					 | 
				
			||||||
  Serial.printf("[%u] Long running operation processing... %" PRIu8 " seconds left\n", op->id, op->data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // check if we have finished ?
 | 
					 | 
				
			||||||
  op->data--;
 | 
					 | 
				
			||||||
  if (op->data) {
 | 
					 | 
				
			||||||
    // not finished yet
 | 
					 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Try to get access to the request pointer if it is still exist.
 | 
					 | 
				
			||||||
  // If there has been a disconnection during that time, the pointer won't be valid anymore
 | 
					 | 
				
			||||||
  if (auto request = op->requestPtr.lock()) {
 | 
					 | 
				
			||||||
    Serial.printf("[%u] Long running operation finished! Sending back response...\n", op->id);
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", String(op->id) + " ");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    Serial.printf("[%u] Long running operation finished, but request was deleted!\n", op->id);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return true;  // operation finished
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// ==========================================================
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Add a middleware to see how pausing a request affects the middleware chain
 | 
					 | 
				
			||||||
  server.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
 | 
					 | 
				
			||||||
    Serial.printf("Middleware chain start\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // continue to the next middleware, and at the end the request handler
 | 
					 | 
				
			||||||
    next();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // we can check the request pause state after the handler was executed
 | 
					 | 
				
			||||||
    if (request->isPaused()) {
 | 
					 | 
				
			||||||
      Serial.printf("Request was paused!\n");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Serial.printf("Middleware chain ends\n");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // HOW TO RUN THIS EXAMPLE:
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // 1. Open several terminals to trigger some requests concurrently that will be paused with:
 | 
					 | 
				
			||||||
  //    > time curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // 2. Look at the output of the Serial console to see how the middleware chain is executed
 | 
					 | 
				
			||||||
  //    and to see the long running operations being processed and resume the requests.
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // 3. You can try close your curl command to cancel the request and check that the request is deleted.
 | 
					 | 
				
			||||||
  //    Note: in case the network is disconnected, the request will be deleted.
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // Print a message in case the request is disconnected (network disconnection, client close, etc.)
 | 
					 | 
				
			||||||
    request->onDisconnect([]() {
 | 
					 | 
				
			||||||
      Serial.printf("Request was disconnected!\n");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Instruct ESPAsyncWebServer to pause the request and get a AsyncWebServerRequestPtr to be able to access the request later.
 | 
					 | 
				
			||||||
    // The AsyncWebServerRequestPtr is the ONLY object authorized to leave the scope of the request handler.
 | 
					 | 
				
			||||||
    // The Middleware chain will continue to run until the end after this handler exit, but the request will be paused and will not
 | 
					 | 
				
			||||||
    // be sent to the client until send() is called later.
 | 
					 | 
				
			||||||
    Serial.printf("Pausing request...\n");
 | 
					 | 
				
			||||||
    AsyncWebServerRequestPtr requestPtr = request->pause();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // start our long operation...
 | 
					 | 
				
			||||||
    startLongRunningOperation(std::move(requestPtr));
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static uint32_t lastTime = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  if (millis() - lastTime >= 1000) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef ESP32
 | 
					 | 
				
			||||||
    Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
 | 
					 | 
				
			||||||
    std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // process all long running operations
 | 
					 | 
				
			||||||
    longRunningOperations.remove_if([](const std::unique_ptr<LongRunningOperation> &op) {
 | 
					 | 
				
			||||||
      return processLongRunningOperation(op.get());
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    lastTime = millis();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,61 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Make sure resumable downloads can be implemented (HEAD request / response and Range header)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /*
 | 
					 | 
				
			||||||
    ❯ curl -I -X HEAD http://192.168.4.1/download
 | 
					 | 
				
			||||||
    HTTP/1.1 200 OK
 | 
					 | 
				
			||||||
    Content-Length: 1024
 | 
					 | 
				
			||||||
    Content-Type: application/octet-stream
 | 
					 | 
				
			||||||
    Connection: close
 | 
					 | 
				
			||||||
    Accept-Ranges: bytes
 | 
					 | 
				
			||||||
  */
 | 
					 | 
				
			||||||
  // Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80
 | 
					 | 
				
			||||||
  server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    if (request->method() == HTTP_HEAD) {
 | 
					 | 
				
			||||||
      AsyncWebServerResponse *response = request->beginResponse(200, "application/octet-stream");
 | 
					 | 
				
			||||||
      response->addHeader(asyncsrv::T_Accept_Ranges, "bytes");
 | 
					 | 
				
			||||||
      response->addHeader(asyncsrv::T_Content_Length, 10);
 | 
					 | 
				
			||||||
      response->setContentLength(1024);  // make sure we can overrides previously set content length
 | 
					 | 
				
			||||||
      response->addHeader(asyncsrv::T_Content_Type, "foo");
 | 
					 | 
				
			||||||
      response->setContentType("application/octet-stream");  // make sure we can overrides previously set content type
 | 
					 | 
				
			||||||
      // ...
 | 
					 | 
				
			||||||
      request->send(response);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // ...
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,52 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Shows how to rewrite URLs
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/index.txt
 | 
					 | 
				
			||||||
  server.on("/index.txt", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/index.txt
 | 
					 | 
				
			||||||
  server.on("/index.html", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/html", "<h1>Hello, world!</h1>");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.rewrite("/", "/index.html");
 | 
					 | 
				
			||||||
  server.rewrite("/index.txt", "/index.html");  // will hide the .txt file
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,105 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// SSE example
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
  <title>Server-Sent Events</title>
 | 
					 | 
				
			||||||
  <script>
 | 
					 | 
				
			||||||
    if (!!window.EventSource) {
 | 
					 | 
				
			||||||
      var source = new EventSource('/events');
 | 
					 | 
				
			||||||
      source.addEventListener('open', function(e) {
 | 
					 | 
				
			||||||
        console.log("Events Connected");
 | 
					 | 
				
			||||||
      }, false);
 | 
					 | 
				
			||||||
      source.addEventListener('error', function(e) {
 | 
					 | 
				
			||||||
        if (e.target.readyState != EventSource.OPEN) {
 | 
					 | 
				
			||||||
          console.log("Events Disconnected");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }, false);
 | 
					 | 
				
			||||||
      source.addEventListener('message', function(e) {
 | 
					 | 
				
			||||||
        console.log("message", e.data);
 | 
					 | 
				
			||||||
      }, false);
 | 
					 | 
				
			||||||
      source.addEventListener('heartbeat', function(e) {
 | 
					 | 
				
			||||||
        console.log("heartbeat", e.data);
 | 
					 | 
				
			||||||
      }, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </script>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
  <h1>Open your browser console!</h1>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
static AsyncEventSource events("/events");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // need to cast to uint8_t*
 | 
					 | 
				
			||||||
    // if you do not, the const char* will be copied in a temporary String buffer
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  events.onConnect([](AsyncEventSourceClient *client) {
 | 
					 | 
				
			||||||
    Serial.printf("SSE Client connected! ID: %" PRIu32 "\n", client->lastId());
 | 
					 | 
				
			||||||
    client->send("hello!", NULL, millis(), 1000);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  events.onDisconnect([](AsyncEventSourceClient *client) {
 | 
					 | 
				
			||||||
    Serial.printf("SSE Client disconnected! ID: %" PRIu32 "\n", client->lastId());
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addHandler(&events);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static uint32_t lastSSE = 0;
 | 
					 | 
				
			||||||
static uint32_t deltaSSE = 3000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static uint32_t lastHeap = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  uint32_t now = millis();
 | 
					 | 
				
			||||||
  if (now - lastSSE >= deltaSSE) {
 | 
					 | 
				
			||||||
    events.send(String("ping-") + now, "heartbeat", now);
 | 
					 | 
				
			||||||
    lastSSE = millis();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef ESP32
 | 
					 | 
				
			||||||
  if (now - lastHeap >= 2000) {
 | 
					 | 
				
			||||||
    Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
 | 
					 | 
				
			||||||
    lastHeap = now;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,141 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// SSE example
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
  <title>Server-Sent Events</title>
 | 
					 | 
				
			||||||
  <script>
 | 
					 | 
				
			||||||
    if (!!window.EventSource) {
 | 
					 | 
				
			||||||
      var source = new EventSource('/events');
 | 
					 | 
				
			||||||
      source.onopen = function(e) {
 | 
					 | 
				
			||||||
        console.log("Events Connected");
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      source.onerror = function(e) {
 | 
					 | 
				
			||||||
        if (e.target.readyState != EventSource.OPEN) {
 | 
					 | 
				
			||||||
          console.log("Events Disconnected");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // Uncomment below to prevent the client from proactively establishing a new connection.
 | 
					 | 
				
			||||||
        // source.close();
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      source.onmessage = function(e) {
 | 
					 | 
				
			||||||
        console.log("Message: " + e.data);
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      source.addEventListener('heartbeat', function(e) {
 | 
					 | 
				
			||||||
        console.log("Heartbeat", e.data);
 | 
					 | 
				
			||||||
      }, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  </script>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
  <h1>Open your browser console!</h1>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
static AsyncEventSource events("/events");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static volatile size_t connectionCount = 0;
 | 
					 | 
				
			||||||
static volatile uint32_t timestampConnected = 0;
 | 
					 | 
				
			||||||
static constexpr uint32_t timeoutClose = 15000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // need to cast to uint8_t*
 | 
					 | 
				
			||||||
    // if you do not, the const char* will be copied in a temporary String buffer
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  events.onConnect([](AsyncEventSourceClient *client) {
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief: Purpose for a test case: count() function
 | 
					 | 
				
			||||||
     * Task watchdog shall be triggered due to a self-deadlock by mutex handling of the AsyncEventSource.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * E (61642) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
 | 
					 | 
				
			||||||
     * E (61642) task_wdt:  - async_tcp (CPU 0/1)
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * Resolve: using recursive_mutex insteads of mutex.
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    connectionCount = events.count();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    timestampConnected = millis();
 | 
					 | 
				
			||||||
    Serial.printf("SSE Client connected! ID: %" PRIu32 "\n", client->lastId());
 | 
					 | 
				
			||||||
    client->send("hello!", NULL, millis(), 1000);
 | 
					 | 
				
			||||||
    Serial.printf("Number of connected clients: %u\n", connectionCount);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  events.onDisconnect([](AsyncEventSourceClient *client) {
 | 
					 | 
				
			||||||
    connectionCount = events.count();
 | 
					 | 
				
			||||||
    Serial.printf("SSE Client disconnected! ID: %" PRIu32 "\n", client->lastId());
 | 
					 | 
				
			||||||
    Serial.printf("Number of connected clients: %u\n", connectionCount);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.addHandler(&events);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static constexpr uint32_t deltaSSE = 3000;
 | 
					 | 
				
			||||||
static uint32_t lastSSE = 0;
 | 
					 | 
				
			||||||
static uint32_t lastHeap = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  uint32_t now = millis();
 | 
					 | 
				
			||||||
  if (connectionCount > 0) {
 | 
					 | 
				
			||||||
    if (now - lastSSE >= deltaSSE) {
 | 
					 | 
				
			||||||
      events.send(String("ping-") + now, "heartbeat", now);
 | 
					 | 
				
			||||||
      lastSSE = millis();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @brief: Purpose for a test case: close() function
 | 
					 | 
				
			||||||
     * Task watchdog shall be triggered due to a self-deadlock by mutex handling of the AsyncEventSource.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * E (61642) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
 | 
					 | 
				
			||||||
     * E (61642) task_wdt:  - async_tcp (CPU 0/1)
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * Resolve: using recursive_mutex insteads of mutex.
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    if (now - timestampConnected >= timeoutClose) {
 | 
					 | 
				
			||||||
      Serial.printf("SSE Clients close\n");
 | 
					 | 
				
			||||||
      events.close();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef ESP32
 | 
					 | 
				
			||||||
  if (now - lastHeap >= 2000) {
 | 
					 | 
				
			||||||
    Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
 | 
					 | 
				
			||||||
    lastHeap = now;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,66 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Server state example
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server1(80);
 | 
					 | 
				
			||||||
static AsyncWebServer server2(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // server state returns one of the tcp_state enum values:
 | 
					 | 
				
			||||||
  // enum tcp_state {
 | 
					 | 
				
			||||||
  //   CLOSED      = 0,
 | 
					 | 
				
			||||||
  //   LISTEN      = 1,
 | 
					 | 
				
			||||||
  //   SYN_SENT    = 2,
 | 
					 | 
				
			||||||
  //   SYN_RCVD    = 3,
 | 
					 | 
				
			||||||
  //   ESTABLISHED = 4,
 | 
					 | 
				
			||||||
  //   FIN_WAIT_1  = 5,
 | 
					 | 
				
			||||||
  //   FIN_WAIT_2  = 6,
 | 
					 | 
				
			||||||
  //   CLOSE_WAIT  = 7,
 | 
					 | 
				
			||||||
  //   CLOSING     = 8,
 | 
					 | 
				
			||||||
  //   LAST_ACK    = 9,
 | 
					 | 
				
			||||||
  //   TIME_WAIT   = 10
 | 
					 | 
				
			||||||
  // };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  assert(server1.state() == tcp_state::CLOSED);
 | 
					 | 
				
			||||||
  assert(server2.state() == tcp_state::CLOSED);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server1.begin();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  assert(server1.state() == tcp_state::LISTEN);
 | 
					 | 
				
			||||||
  assert(server2.state() == tcp_state::CLOSED);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server2.begin();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  assert(server1.state() == tcp_state::LISTEN);
 | 
					 | 
				
			||||||
  assert(server2.state() == tcp_state::CLOSED);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Serial.println("Done!");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,73 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Authentication and authorization middlewares
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncAuthenticationMiddleware basicAuth;
 | 
					 | 
				
			||||||
static AsyncLoggingMiddleware logging;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // basic authentication
 | 
					 | 
				
			||||||
  basicAuth.setUsername("admin");
 | 
					 | 
				
			||||||
  basicAuth.setPassword("admin");
 | 
					 | 
				
			||||||
  basicAuth.setRealm("MyApp");
 | 
					 | 
				
			||||||
  basicAuth.setAuthFailureMessage("Authentication failed");
 | 
					 | 
				
			||||||
  basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
 | 
					 | 
				
			||||||
  basicAuth.generateHash();  // precompute hash (optional but recommended)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // logging middleware
 | 
					 | 
				
			||||||
  logging.setEnabled(true);
 | 
					 | 
				
			||||||
  logging.setOutput(Serial);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // we apply auth middleware to the server globally
 | 
					 | 
				
			||||||
  server.addMiddleware(&basicAuth);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // protected endpoint: requires basic authentication
 | 
					 | 
				
			||||||
  // curl -v -u admin:admin  http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(200, "text/plain", "Hello, world!");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // we skip all global middleware from the catchall handler
 | 
					 | 
				
			||||||
  server.catchAllHandler().skipServerMiddlewares();
 | 
					 | 
				
			||||||
  // we apply a specific middleware to the catchall handler only to log requests without a handler defined
 | 
					 | 
				
			||||||
  server.catchAllHandler().addMiddleware(&logging);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // standard 404 handler: will display the request in the console i na curl-like style
 | 
					 | 
				
			||||||
  // curl -v -H "Foo: Bar"  http://192.168.4.1/foo
 | 
					 | 
				
			||||||
  server.onNotFound([](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    request->send(404, "text/plain", "Not found");
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// not needed
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
  delay(100);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,152 +0,0 @@
 | 
				
			|||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
					 | 
				
			||||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Simulate a slow response in a chunk response (like file download from SD Card)
 | 
					 | 
				
			||||||
// poll events will be throttled.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <Arduino.h>
 | 
					 | 
				
			||||||
#if defined(ESP32) || defined(LIBRETINY)
 | 
					 | 
				
			||||||
#include <AsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#elif defined(ESP8266)
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <ESPAsyncTCP.h>
 | 
					 | 
				
			||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
 | 
					 | 
				
			||||||
#include <RPAsyncTCP.h>
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <ESPAsyncWebServer.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static AsyncWebServer server(80);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const char *htmlContent PROGMEM = R"(
 | 
					 | 
				
			||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html>
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <title>Sample HTML</title>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
    <h1>Hello, World!</h1>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
 | 
					 | 
				
			||||||
    rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
 | 
					 | 
				
			||||||
    arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
 | 
					 | 
				
			||||||
    accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
 | 
					 | 
				
			||||||
    Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
 | 
					 | 
				
			||||||
    dapibus elit, id varius sem dui id lacus.</p>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const size_t htmlContentLength = strlen_P(htmlContent);
 | 
					 | 
				
			||||||
static constexpr char characters[] = "0123456789ABCDEF";
 | 
					 | 
				
			||||||
static size_t charactersIndex = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void setup() {
 | 
					 | 
				
			||||||
  Serial.begin(115200);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
 | 
					 | 
				
			||||||
  WiFi.mode(WIFI_AP);
 | 
					 | 
				
			||||||
  WiFi.softAP("esp-captive");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // curl -v http://192.168.4.1/
 | 
					 | 
				
			||||||
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    // need to cast to uint8_t*
 | 
					 | 
				
			||||||
    // if you do not, the const char* will be copied in a temporary String buffer
 | 
					 | 
				
			||||||
    request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // IMPORTANT - DO NOT WRITE SUCH CODE IN PRODUCTON !
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // This example simulates the slowdown that can happen when:
 | 
					 | 
				
			||||||
  // - downloading a huge file from sdcard
 | 
					 | 
				
			||||||
  // - doing some file listing on SDCard because it is horribly slow to get a file listing with file stats on SDCard.
 | 
					 | 
				
			||||||
  // So in both cases, ESP would deadlock or TWDT would trigger.
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // This example simulats that by slowing down the chunk callback:
 | 
					 | 
				
			||||||
  // - d=2000 is the delay in ms in the callback
 | 
					 | 
				
			||||||
  // - l=10000 is the length of the response
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  // time curl -N -v -G -d 'd=2000' -d 'l=10000'  http://192.168.4.1/slow.html --output -
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
  server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest *request) {
 | 
					 | 
				
			||||||
    uint32_t d = request->getParam("d")->value().toInt();
 | 
					 | 
				
			||||||
    uint32_t l = request->getParam("l")->value().toInt();
 | 
					 | 
				
			||||||
    Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l);
 | 
					 | 
				
			||||||
    AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [d, l](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
 | 
					 | 
				
			||||||
      Serial.printf("%u\n", index);
 | 
					 | 
				
			||||||
      // finished ?
 | 
					 | 
				
			||||||
      if (index >= l) {
 | 
					 | 
				
			||||||
        return 0;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // slow down the task to simulate some heavy processing, like SD card reading
 | 
					 | 
				
			||||||
      delay(d);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      memset(buffer, characters[charactersIndex], 256);
 | 
					 | 
				
			||||||
      charactersIndex = (charactersIndex + 1) % sizeof(characters);
 | 
					 | 
				
			||||||
      return 256;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    request->send(response);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server.begin();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static uint32_t lastHeap = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void loop() {
 | 
					 | 
				
			||||||
#ifdef ESP32
 | 
					 | 
				
			||||||
  uint32_t now = millis();
 | 
					 | 
				
			||||||
  if (now - lastHeap >= 2000) {
 | 
					 | 
				
			||||||
    Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
 | 
					 | 
				
			||||||
    lastHeap = now;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/1.PNG
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 17 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/2.PNG
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 12 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/3.PNG
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/4.PNG
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 18 KiB  | 
							
								
								
									
										1177
									
								
								examples/SmartSwitch/ESPAsyncWiFiManager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										283
									
								
								examples/SmartSwitch/ESPAsyncWiFiManager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,283 @@
 | 
				
			|||||||
 | 
					/**************************************************************
 | 
				
			||||||
 | 
					   WiFiManager is a library for the ESP8266/Arduino platform
 | 
				
			||||||
 | 
					   (https://github.com/esp8266/Arduino) to enable easy
 | 
				
			||||||
 | 
					   configuration and reconfiguration of WiFi credentials using a Captive Portal
 | 
				
			||||||
 | 
					   inspired by:
 | 
				
			||||||
 | 
					   http://www.esp8266.com/viewtopic.php?f=29&t=2520
 | 
				
			||||||
 | 
					   https://github.com/chriscook8/esp-arduino-apboot
 | 
				
			||||||
 | 
					   https://github.com/esp8266/Arduino/tree/esp8266/hardware/esp8266com/esp8266/libraries/DNSServer/examples/CaptivePortalAdvanced
 | 
				
			||||||
 | 
					   Built by AlexT https://github.com/tzapu
 | 
				
			||||||
 | 
					   Ported to Async Web Server by https://github.com/alanswx
 | 
				
			||||||
 | 
					   Licensed under MIT license
 | 
				
			||||||
 | 
					 **************************************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef ESPAsyncWiFiManager_h
 | 
				
			||||||
 | 
					#define ESPAsyncWiFiManager_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(ESP8266)
 | 
				
			||||||
 | 
					#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#include <WiFi.h>
 | 
				
			||||||
 | 
					#include "esp_wps.h"
 | 
				
			||||||
 | 
					#define ESP_WPS_MODE WPS_TYPE_PBC
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#include <ESPAsyncWebServer.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//#define USE_EADNS               //Uncomment to use ESPAsyncDNSServer
 | 
				
			||||||
 | 
					#ifdef USE_EADNS
 | 
				
			||||||
 | 
					#include <ESPAsyncDNSServer.h>    //https://github.com/devyte/ESPAsyncDNSServer
 | 
				
			||||||
 | 
					                                  //https://github.com/me-no-dev/ESPAsyncUDP
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#include <DNSServer.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// fix crash on ESP32 (see https://github.com/alanswx/ESPAsyncWiFiManager/issues/44)
 | 
				
			||||||
 | 
					#if defined(ESP8266)
 | 
				
			||||||
 | 
					typedef int wifi_ssid_count_t;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					typedef int16_t wifi_ssid_count_t;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(ESP8266)
 | 
				
			||||||
 | 
					extern "C" {
 | 
				
			||||||
 | 
					  #include "user_interface.h"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#if __has_include(<esp32/rom/rtc.h>) 
 | 
				
			||||||
 | 
					#include <esp32/rom/rtc.h>
 | 
				
			||||||
 | 
					#else 
 | 
				
			||||||
 | 
					#include <rom/rtc.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const char WFM_HTTP_HEAD[] PROGMEM            = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/><title>{v}</title>";
 | 
				
			||||||
 | 
					const char HTTP_STYLE[] PROGMEM           = "<style>.c{text-align: center;} div,input{padding:5px;font-size:1em;} input{width:95%;} body{text-align: center;font-family:verdana;} button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;} .q{float: right;width: 64px;text-align: right;} .l{background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==\") no-repeat left center;background-size: 1em;}</style>";
 | 
				
			||||||
 | 
					const char HTTP_SCRIPT[] PROGMEM          = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();}</script>";
 | 
				
			||||||
 | 
					const char HTTP_HEAD_END[] PROGMEM        = "</head><body><div style='text-align:left;display:inline-block;min-width:260px;'>";
 | 
				
			||||||
 | 
					const char HTTP_PORTAL_OPTIONS[] PROGMEM  = "<form action=\"/wifi\" method=\"get\"><button>Configure WiFi</button></form><br/><form action=\"/0wifi\" method=\"get\"><button>Configure WiFi (No Scan)</button></form><br/><form action=\"/i\" method=\"get\"><button>Info</button></form><br/><form action=\"/r\" method=\"post\"><button>Reset</button></form>";
 | 
				
			||||||
 | 
					const char HTTP_ITEM[] PROGMEM            = "<div><a href='#p' onclick='c(this)'>{v}</a> <span class='q {i}'>{r}%</span></div>";
 | 
				
			||||||
 | 
					const char HTTP_FORM_START[] PROGMEM      = "<form method='get' action='wifisave'><input id='s' name='s' length=32 placeholder='SSID'><br/><input id='p' name='p' length=64 type='password' placeholder='password'><br/>";
 | 
				
			||||||
 | 
					const char HTTP_FORM_PARAM[] PROGMEM      = "<br/><input id='{i}' name='{n}' length={l} placeholder='{p}' value='{v}' {c}>";
 | 
				
			||||||
 | 
					const char HTTP_FORM_END[] PROGMEM        = "<br/><button type='submit'>save</button></form>";
 | 
				
			||||||
 | 
					const char HTTP_SCAN_LINK[] PROGMEM       = "<br/><div class=\"c\"><a href=\"/wifi\">Scan</a></div>";
 | 
				
			||||||
 | 
					const char HTTP_SAVED[] PROGMEM           = "<div>Credentials Saved<br />Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
 | 
				
			||||||
 | 
					const char HTTP_END[] PROGMEM             = "</div></body></html>";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define WIFI_MANAGER_MAX_PARAMS 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AsyncWiFiManagerParameter {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  AsyncWiFiManagerParameter(const char *custom);
 | 
				
			||||||
 | 
					  AsyncWiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length);
 | 
				
			||||||
 | 
					  AsyncWiFiManagerParameter(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const char *getID();
 | 
				
			||||||
 | 
					  const char *getValue();
 | 
				
			||||||
 | 
					  const char *getPlaceholder();
 | 
				
			||||||
 | 
					  int         getValueLength();
 | 
				
			||||||
 | 
					  const char *getCustomHTML();
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  const char *_id;
 | 
				
			||||||
 | 
					  const char *_placeholder;
 | 
				
			||||||
 | 
					  char       *_value;
 | 
				
			||||||
 | 
					  int         _length;
 | 
				
			||||||
 | 
					  const char *_customHTML;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  friend class AsyncWiFiManager;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WiFiResult
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  bool duplicate;
 | 
				
			||||||
 | 
					  String SSID;
 | 
				
			||||||
 | 
					  uint8_t encryptionType;
 | 
				
			||||||
 | 
					  int32_t RSSI;
 | 
				
			||||||
 | 
					  uint8_t* BSSID;
 | 
				
			||||||
 | 
					  int32_t channel;
 | 
				
			||||||
 | 
					  bool isHidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  WiFiResult()
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AsyncWiFiManager
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  #ifdef USE_EADNS
 | 
				
			||||||
 | 
					  AsyncWiFiManager(AsyncWebServer * server, AsyncDNSServer *dns);
 | 
				
			||||||
 | 
					  #else
 | 
				
			||||||
 | 
					  AsyncWiFiManager(AsyncWebServer * server, DNSServer *dns);
 | 
				
			||||||
 | 
					  #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void          scan();
 | 
				
			||||||
 | 
					  String        scanModal();
 | 
				
			||||||
 | 
					  void          loop();
 | 
				
			||||||
 | 
					  void          safeLoop();
 | 
				
			||||||
 | 
					  void          criticalLoop();
 | 
				
			||||||
 | 
					  String        infoAsString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  boolean       autoConnect(unsigned long maxConnectRetries = 1, unsigned long retryDelayMs = 1000);
 | 
				
			||||||
 | 
					  boolean       autoConnect(char const *apName, char const *apPassword = NULL, unsigned long maxConnectRetries = 1, unsigned long retryDelayMs = 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //if you want to always start the config portal, without trying to connect first
 | 
				
			||||||
 | 
					  boolean       startConfigPortal(char const *apName, char const *apPassword = NULL);
 | 
				
			||||||
 | 
					  void startConfigPortalModeless(char const *apName, char const *apPassword);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // get the AP name of the config portal, so it can be used in the callback
 | 
				
			||||||
 | 
					  String        getConfigPortalSSID();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void          resetSettings();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //sets timeout before webserver loop ends and exits even if there has been no setup.
 | 
				
			||||||
 | 
					  //usefully for devices that failed to connect at some point and got stuck in a webserver loop
 | 
				
			||||||
 | 
					  //in seconds setConfigPortalTimeout is a new name for setTimeout
 | 
				
			||||||
 | 
					  void          setConfigPortalTimeout(unsigned long seconds);
 | 
				
			||||||
 | 
					  void          setTimeout(unsigned long seconds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //sets timeout for which to attempt connecting, usefull if you get a lot of failed connects
 | 
				
			||||||
 | 
					  void          setConnectTimeout(unsigned long seconds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //wether or not the wifi manager tries to connect to configured access point even when
 | 
				
			||||||
 | 
					  //configuration portal (ESP as access point) is running [default true/on]
 | 
				
			||||||
 | 
					  void          setTryConnectDuringConfigPortal(boolean v);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void          setDebugOutput(boolean debug);
 | 
				
			||||||
 | 
					  //defaults to not showing anything under 8% signal quality if called
 | 
				
			||||||
 | 
					  void          setMinimumSignalQuality(int quality = 8);
 | 
				
			||||||
 | 
					  //sets a custom ip /gateway /subnet configuration
 | 
				
			||||||
 | 
					  void          setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn);
 | 
				
			||||||
 | 
					  //sets config for a static IP
 | 
				
			||||||
 | 
					  void          setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn, IPAddress dns1=(uint32_t)0x00000000, IPAddress dns2=(uint32_t)0x00000000);
 | 
				
			||||||
 | 
					  //called when AP mode and config portal is started
 | 
				
			||||||
 | 
					  void          setAPCallback( void (*func)(AsyncWiFiManager*) );
 | 
				
			||||||
 | 
					  //called when settings have been changed and connection was successful
 | 
				
			||||||
 | 
					  void          setSaveConfigCallback( void (*func)(void) );
 | 
				
			||||||
 | 
					  //adds a custom parameter
 | 
				
			||||||
 | 
					  void          addParameter(AsyncWiFiManagerParameter *p);
 | 
				
			||||||
 | 
					  //if this is set, it will exit after config, even if connection is unsucessful.
 | 
				
			||||||
 | 
					  void          setBreakAfterConfig(boolean shouldBreak);
 | 
				
			||||||
 | 
					  //if this is set, try WPS setup when starting (this will delay config portal for up to 2 mins)
 | 
				
			||||||
 | 
					  //TODO
 | 
				
			||||||
 | 
					  //if this is set, customise style
 | 
				
			||||||
 | 
					  void          setCustomHeadElement(const char* element);
 | 
				
			||||||
 | 
					  //if this is true, remove duplicated Access Points - defaut true
 | 
				
			||||||
 | 
					  void          setRemoveDuplicateAPs(boolean removeDuplicates);
 | 
				
			||||||
 | 
					  //sets a custom element to add to options page
 | 
				
			||||||
 | 
					  void          setCustomOptionsElement(const char* element);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  AsyncWebServer *server;
 | 
				
			||||||
 | 
					  #ifdef USE_EADNS
 | 
				
			||||||
 | 
					  AsyncDNSServer      *dnsServer;
 | 
				
			||||||
 | 
					  #else
 | 
				
			||||||
 | 
					  DNSServer      *dnsServer;
 | 
				
			||||||
 | 
					  #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  boolean         _modeless;
 | 
				
			||||||
 | 
					  int             scannow;
 | 
				
			||||||
 | 
					  int             shouldscan;
 | 
				
			||||||
 | 
					  boolean         needInfo = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //const int     WM_DONE                 = 0;
 | 
				
			||||||
 | 
					  //const int     WM_WAIT                 = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //const String  HTTP_HEAD = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/><title>{v}</title>";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void          setupConfigPortal();
 | 
				
			||||||
 | 
					#ifdef NO_EXTRA_4K_HEAP
 | 
				
			||||||
 | 
					  void          startWPS();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  String        pager;
 | 
				
			||||||
 | 
					  wl_status_t   wifiStatus;
 | 
				
			||||||
 | 
					  const char*   _apName                 = "no-net";
 | 
				
			||||||
 | 
					  const char*   _apPassword             = NULL;
 | 
				
			||||||
 | 
					  String        _ssid                   = "";
 | 
				
			||||||
 | 
					  String        _pass                   = "";
 | 
				
			||||||
 | 
					  unsigned long _configPortalTimeout    = 0;
 | 
				
			||||||
 | 
					  unsigned long _connectTimeout         = 0;
 | 
				
			||||||
 | 
					  unsigned long _configPortalStart      = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  IPAddress     _ap_static_ip;
 | 
				
			||||||
 | 
					  IPAddress     _ap_static_gw;
 | 
				
			||||||
 | 
					  IPAddress     _ap_static_sn;
 | 
				
			||||||
 | 
					  IPAddress     _sta_static_ip;
 | 
				
			||||||
 | 
					  IPAddress     _sta_static_gw;
 | 
				
			||||||
 | 
					  IPAddress     _sta_static_sn;
 | 
				
			||||||
 | 
					  IPAddress     _sta_static_dns1= (uint32_t)0x00000000;
 | 
				
			||||||
 | 
					  IPAddress     _sta_static_dns2= (uint32_t)0x00000000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int           _paramsCount            = 0;
 | 
				
			||||||
 | 
					  int           _minimumQuality         = -1;
 | 
				
			||||||
 | 
					  boolean       _removeDuplicateAPs     = true;
 | 
				
			||||||
 | 
					  boolean       _shouldBreakAfterConfig = false;
 | 
				
			||||||
 | 
					#ifdef NO_EXTRA_4K_HEAP
 | 
				
			||||||
 | 
					  boolean       _tryWPS                 = false;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  const char*   _customHeadElement      = "";
 | 
				
			||||||
 | 
					  const char*   _customOptionsElement   = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //String        getEEPROMString(int start, int len);
 | 
				
			||||||
 | 
					  //void          setEEPROMString(int start, int len, String string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int           status = WL_IDLE_STATUS;
 | 
				
			||||||
 | 
					  int           connectWifi(String ssid, String pass);
 | 
				
			||||||
 | 
					  uint8_t       waitForConnectResult();
 | 
				
			||||||
 | 
					  void          setInfo();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String networkListAsString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void          handleRoot(AsyncWebServerRequest *);
 | 
				
			||||||
 | 
					  void          handleWifi(AsyncWebServerRequest*,boolean scan);
 | 
				
			||||||
 | 
					  void          handleWifiSave(AsyncWebServerRequest*);
 | 
				
			||||||
 | 
					  void          handleInfo(AsyncWebServerRequest*);
 | 
				
			||||||
 | 
					  void          handleReset(AsyncWebServerRequest*);
 | 
				
			||||||
 | 
					  void          handleNotFound(AsyncWebServerRequest*);
 | 
				
			||||||
 | 
					  void          handle204(AsyncWebServerRequest*);
 | 
				
			||||||
 | 
					  boolean       captivePortal(AsyncWebServerRequest*);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // DNS server
 | 
				
			||||||
 | 
					  const byte    DNS_PORT = 53;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //helpers
 | 
				
			||||||
 | 
					  int           getRSSIasQuality(int RSSI);
 | 
				
			||||||
 | 
					  boolean       isIp(String str);
 | 
				
			||||||
 | 
					  String        toStringIp(IPAddress ip);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  boolean       connect;
 | 
				
			||||||
 | 
					  boolean       _debug = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  WiFiResult          *wifiSSIDs;
 | 
				
			||||||
 | 
					  wifi_ssid_count_t   wifiSSIDCount;
 | 
				
			||||||
 | 
					  boolean             wifiSSIDscan;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  boolean             _tryConnectDuringConfigPortal = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void (*_apcallback)(AsyncWiFiManager*) = NULL;
 | 
				
			||||||
 | 
					  void (*_savecallback)(void) = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AsyncWiFiManagerParameter* _params[WIFI_MANAGER_MAX_PARAMS];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  template <typename Generic>
 | 
				
			||||||
 | 
					  void          DEBUG_WM(Generic text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  template <class T>
 | 
				
			||||||
 | 
					  auto optionalIPFromString(T *obj, const char *s) -> decltype(  obj->fromString(s)  ) {
 | 
				
			||||||
 | 
					    return  obj->fromString(s);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  auto optionalIPFromString(...) -> bool {
 | 
				
			||||||
 | 
					    DEBUG_WM("NO fromString METHOD ON IPAddress, you need ESP8266 core 2.1.0 or newer for Custom IP configuration to work.");
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										56
									
								
								examples/SmartSwitch/PinOut_Notes.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					This application:
 | 
				
			||||||
 | 
					D2   = 4;   // DHT DATA I/O
 | 
				
			||||||
 | 
					D3   = 0;	 // BUTTON - most modules have it populated on PCB
 | 
				
			||||||
 | 
					D4   = 2;   // LED (RELAY) - most modules have it populated, on ESP32 is with reversed logic levels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pinout ESP12 (8266)
 | 
				
			||||||
 | 
					D	GPIO 	In	Out 	Notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					D0	16	 no interrupt	no PWM or I2C support	HIGH at boot used to wake up from deep sleep
 | 
				
			||||||
 | 
					D1	5	 OK	OK	often used as SCL (I2C)
 | 
				
			||||||
 | 
					D2	4	 OK	OK	often used as SDA (I2C)
 | 
				
			||||||
 | 
					D3	0	 PU OK	pulled up connected to FLASH button, boot fails if pulled LOW
 | 
				
			||||||
 | 
					D4	2	 PU	OK	pulled up HIGH at boot connected to on-board LED, boot fails if pulled LOW
 | 
				
			||||||
 | 
					D5	14	 OK	OK	SPI (SCLK)
 | 
				
			||||||
 | 
					D6	12	 OK	OK	SPI (MISO)
 | 
				
			||||||
 | 
					D7	13	 OK	OK	SPI (MOSI)
 | 
				
			||||||
 | 
					D8	15	 pulled to GND	OK	SPI (CS) Boot fails if pulled HIGH
 | 
				
			||||||
 | 
					RX	3	 OK	RX pin	HIGH at boot
 | 
				
			||||||
 | 
					TX	1	 TX pin	OK	HIGH at boot debug output at boot, boot fails if pulled LOW
 | 
				
			||||||
 | 
					A0	ADC0	Analog Input 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pinout ESP32
 | 
				
			||||||
 | 
					IO  	In	Out	Notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					0	PU	OK	pulled-up input, outputs PWM signal at boot
 | 
				
			||||||
 | 
					1	TX  	OK	debug output at boot
 | 
				
			||||||
 | 
					2	OK	OK	connected to on-board LED
 | 
				
			||||||
 | 
					3	OK	RX 	HIGH at boot
 | 
				
			||||||
 | 
					4	OK	OK	
 | 
				
			||||||
 | 
					5	OK	OK	outputs PWM signal at boot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					6-11 	x	 x	connected to the integrated SPI flash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					12	OK	OK	boot fail if pulled high
 | 
				
			||||||
 | 
					13	OK	OK	
 | 
				
			||||||
 | 
					14	OK	OK	outputs PWM signal at boot
 | 
				
			||||||
 | 
					15	OK	OK	outputs PWM signal at boot
 | 
				
			||||||
 | 
					16	OK	OK	
 | 
				
			||||||
 | 
					17	OK	OK	
 | 
				
			||||||
 | 
					18	OK	OK	
 | 
				
			||||||
 | 
					19	OK	OK	
 | 
				
			||||||
 | 
					21	OK	OK	
 | 
				
			||||||
 | 
					22	OK	OK	
 | 
				
			||||||
 | 
					23	OK	OK	
 | 
				
			||||||
 | 
					25	OK	OK	
 | 
				
			||||||
 | 
					26	OK	OK	
 | 
				
			||||||
 | 
					27	OK	OK	
 | 
				
			||||||
 | 
					32	OK	OK	
 | 
				
			||||||
 | 
					33	OK	OK	
 | 
				
			||||||
 | 
					34	OK		input only
 | 
				
			||||||
 | 
					35	OK		input only
 | 
				
			||||||
 | 
					36	OK		input only
 | 
				
			||||||
 | 
					39	OK		input only
 | 
				
			||||||
							
								
								
									
										19
									
								
								examples/SmartSwitch/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<img title="" src="1.PNG" alt="" width="137">   <img title="" src="2.PNG" alt="" width="138">   <img title="" src="3.PNG" alt="" width="150">   <img title="" src="4.PNG" alt="" width="150">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## SmartSwitch
 | 
				
			||||||
 | 
					- Remote Temperature Control application with schedule 
 | 
				
			||||||
 | 
					   (example: car block heater or car battery charger for winter)
 | 
				
			||||||
 | 
					- Based on [ESP_AsyncFSBrowser](https://github.com/lorol/ESPAsyncWebServer/tree/master/examples/ESP_AsyncFSBrowser)  example that uses embedded ACE editor 
 | 
				
			||||||
 | 
					- Wide browser compatibility, no extra server-side needed
 | 
				
			||||||
 | 
					- HTTP server and WebSocket on same port 
 | 
				
			||||||
 | 
					- Standalone, no JS dependencies for the browser from Internet
 | 
				
			||||||
 | 
					- [Ace Editor](https://github.com/ajaxorg/ace) embedded to source but also - editable, upgradeable see  [extras folder](https://github.com/lorol/ESPAsyncWebServer/tree/master/extras)
 | 
				
			||||||
 | 
					- Added [ESPAsyncWiFiManager](https://github.com/alanswx/ESPAsyncWiFiManager) and fallback AP mode after timeout
 | 
				
			||||||
 | 
					- Real Time (NTP) w/ Time Zones. Sync from browser time if in AP mode
 | 
				
			||||||
 | 
					- Memorized settings to EEPROM
 | 
				
			||||||
 | 
					- Multiple clients can be connected at same time, they see each other' requests
 | 
				
			||||||
 | 
					- Authentication variants including [Cookie-based](https://github.com/me-no-dev/ESPAsyncWebServer/pull/684) idea
 | 
				
			||||||
 | 
					- Used [this Xtea implementation](https://github.com/franksmicro/Arduino/tree/master/libraries/Xtea) for getting a fancier Cookie token
 | 
				
			||||||
 | 
					- Default credentials **smart : switch** or only **switch** as password
 | 
				
			||||||
 | 
					- OTA included
 | 
				
			||||||
 | 
					- Use the latest ESP8266 ESP32 cores from GitHub
 | 
				
			||||||
							
								
								
									
										750
									
								
								examples/SmartSwitch/SmartSwitch.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,750 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					SmartSwitch application
 | 
				
			||||||
 | 
					Based on ESP_AsyncFSBrowser
 | 
				
			||||||
 | 
					Temperature Control for heater with schedule
 | 
				
			||||||
 | 
					Main purpose - for winter outside car block heater or battery charger 
 | 
				
			||||||
 | 
					Wide browser compatibility, no other server-side needed.
 | 
				
			||||||
 | 
					HTTP server and WebSocket, single port  
 | 
				
			||||||
 | 
					Standalone, no JS dependencies for the browser from Internet (I hope)
 | 
				
			||||||
 | 
					Based on ESP_AsyncFSBrowser
 | 
				
			||||||
 | 
					Real Time (NTP) w/ Time Zones
 | 
				
			||||||
 | 
					Memorized settings to EEPROM
 | 
				
			||||||
 | 
					Multiple clients can be connected at same time, they see each other requests
 | 
				
			||||||
 | 
					Use latest ESP core lib (from Github)
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Defaulut is SPIFFS, FatFS: only on ESP32
 | 
				
			||||||
 | 
					// Comment 2 lines below or uncomment only one of them
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define USE_LittleFS
 | 
				
			||||||
 | 
					//#define USE_FatFS // select partition scheme w/ ffat!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define USE_WFM   // to use ESPAsyncWiFiManager
 | 
				
			||||||
 | 
					//#define DEL_WFM   // delete Wifi credentials stored 
 | 
				
			||||||
 | 
					                  //(use once then comment and flash again), also HTTP /erase-wifi can do the same live
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AUTH COOKIE uses only the password and unsigned long MY_SECRET_NUMBER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define http_username "smart"
 | 
				
			||||||
 | 
					#define http_password "switch"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MY_SECRET_NUMBER  0xA217B02F
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//See https://github.com/me-no-dev/ESPAsyncWebServer/pull/684
 | 
				
			||||||
 | 
					//SSWI or other 4 chars
 | 
				
			||||||
 | 
					#define USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					#define MY_COOKIE_DEL "SSWI=;Max-Age=-1;Path=/;"
 | 
				
			||||||
 | 
					#define MY_COOKIE_PREF "SSWI="
 | 
				
			||||||
 | 
					#define MY_COOKIE_SUFF ";Max-Age=31536000;Path=/;"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef USE_AUTH_COOKIE             
 | 
				
			||||||
 | 
					  #define USE_AUTH_STAT   //Base Auth for stat, /commands and SPIFFSEditor
 | 
				
			||||||
 | 
					  //#define USE_AUTH_WS   //Base Auth also for WS, not very supported 
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <ArduinoOTA.h>
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					 #include <FS.h>
 | 
				
			||||||
 | 
					 #ifdef USE_LittleFS
 | 
				
			||||||
 | 
					  #define HSTNM "ssw32-littlefs"
 | 
				
			||||||
 | 
					  #define MYFS LITTLEFS
 | 
				
			||||||
 | 
					  #include "LITTLEFS.h"
 | 
				
			||||||
 | 
					 #elif defined(USE_FatFS)
 | 
				
			||||||
 | 
					  #define HSTNM "ssw32-ffat"
 | 
				
			||||||
 | 
					  #define MYFS FFat
 | 
				
			||||||
 | 
					  #include "FFat.h"
 | 
				
			||||||
 | 
					 #else
 | 
				
			||||||
 | 
					  #define MYFS SPIFFS
 | 
				
			||||||
 | 
					  #include <SPIFFS.h>
 | 
				
			||||||
 | 
					  #define HSTNM "ssw32-spiffs"
 | 
				
			||||||
 | 
					 #endif
 | 
				
			||||||
 | 
					 #include <ESPmDNS.h>
 | 
				
			||||||
 | 
					 #include <WiFi.h>
 | 
				
			||||||
 | 
					 #include <AsyncTCP.h>
 | 
				
			||||||
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
 | 
					 #ifdef USE_LittleFS
 | 
				
			||||||
 | 
					    #include <FS.h>
 | 
				
			||||||
 | 
					  #define HSTNM "ssw8266-littlefs"
 | 
				
			||||||
 | 
					    #define MYFS LittleFS
 | 
				
			||||||
 | 
					    #include <LittleFS.h> 
 | 
				
			||||||
 | 
					 #elif defined(USE_FatFS)
 | 
				
			||||||
 | 
					  #error "FatFS only on ESP32 for now!"
 | 
				
			||||||
 | 
					 #else
 | 
				
			||||||
 | 
					  #define HSTNM "ssw8266-spiffs"
 | 
				
			||||||
 | 
					  #define MYFS SPIFFS
 | 
				
			||||||
 | 
					 #endif
 | 
				
			||||||
 | 
					 #include <ESP8266WiFi.h>
 | 
				
			||||||
 | 
					 #include <ESPAsyncTCP.h>
 | 
				
			||||||
 | 
					 #include <ESP8266mDNS.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <ESPAsyncWebServer.h>
 | 
				
			||||||
 | 
					#ifdef USE_WFM 
 | 
				
			||||||
 | 
					 #include "ESPAsyncWiFiManager.h"
 | 
				
			||||||
 | 
					#endif 
 | 
				
			||||||
 | 
					#include <SPIFFSEditor.h>
 | 
				
			||||||
 | 
					#include <EEPROM.h>
 | 
				
			||||||
 | 
					#include <Ticker.h>
 | 
				
			||||||
 | 
					#include <DHT.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					 #include <stdint.h>
 | 
				
			||||||
 | 
					 #include "Xtea.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define RTC_UTC_TEST 1577836800 // Some Date
 | 
				
			||||||
 | 
					#define MYTZ PSTR("EST5EDT,M3.2.0,M11.1.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define EESC    100  // fixed eeprom address for sched choice
 | 
				
			||||||
 | 
					#define EECH    104  // fixed eeprom address to keep selected active channel, only for reference here 
 | 
				
			||||||
 | 
					#define EEBEGIN EECH + 1
 | 
				
			||||||
 | 
					#define EEMARK  0x5A
 | 
				
			||||||
 | 
					#define MEMMAX  2   // 0,1,2... last max index (only 3 channels)
 | 
				
			||||||
 | 
					#define EEALL   512
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define HYST 0.5 // C +/- hysteresis 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DHT
 | 
				
			||||||
 | 
					#define DHTTYPE DHT22  // DHT 11 // DHT 22, AM2302, AM2321 // DHT 21, AM2301
 | 
				
			||||||
 | 
					#define DHTPIN 4       //D2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DHT_T_CORR -0.3 //Temperature offset compensation of the sensor (can be -)
 | 
				
			||||||
 | 
					#define DHT_H_CORR -2.2 //Humidity offset compensation of the sensor 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SKETCH BEGIN MAIN DECLARATIONS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DHT dht(DHTPIN, DHTTYPE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ticker tim;
 | 
				
			||||||
 | 
					AsyncWebServer server(80); //single port - easy for forwarding 
 | 
				
			||||||
 | 
					AsyncWebSocket ws("/ws");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_WFM 
 | 
				
			||||||
 | 
					 #ifdef USE_EADNS
 | 
				
			||||||
 | 
					  AsyncDNSServer dns;
 | 
				
			||||||
 | 
					 #else
 | 
				
			||||||
 | 
					  DNSServer dns;
 | 
				
			||||||
 | 
					 #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Fallback timeout in seconds allowed to config or it creates an own AP, then serves 192.168.4.1 
 | 
				
			||||||
 | 
					 #define FBTO 120
 | 
				
			||||||
 | 
					 const char* fbssid = "FBSSW"; 
 | 
				
			||||||
 | 
					 const char* fbpassword = "FBpassword4";
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					 const char* ssid = "MYROUTERSSD";
 | 
				
			||||||
 | 
					 const char* password = "MYROUTERPASSWD";
 | 
				
			||||||
 | 
					#endif 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const char* hostName = HSTNM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RTC
 | 
				
			||||||
 | 
					static timeval tv;
 | 
				
			||||||
 | 
					static time_t now;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// HW I/O
 | 
				
			||||||
 | 
					const int btnPin = 0; //D3
 | 
				
			||||||
 | 
					const int ledPin = 2; //D4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					  #define LED_ON 0x1
 | 
				
			||||||
 | 
					  #define LED_OFF 0x0
 | 
				
			||||||
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
 | 
					  #define LED_ON 0x0
 | 
				
			||||||
 | 
					  #define LED_OFF 0x1
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int btnState = HIGH; 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Globals
 | 
				
			||||||
 | 
					uint8_t count = 0;
 | 
				
			||||||
 | 
					uint8_t sched = 0; // automatic schedule
 | 
				
			||||||
 | 
					byte memch = 0;    // select memory "channel" to work with 
 | 
				
			||||||
 | 
					float t = 0;
 | 
				
			||||||
 | 
					float h = 0;
 | 
				
			||||||
 | 
					bool udht = false;
 | 
				
			||||||
 | 
					bool heat_enabled_prev = false;
 | 
				
			||||||
 | 
					bool ledState = LED_OFF;
 | 
				
			||||||
 | 
					bool ledOut = LED_OFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct EE_bl { 
 | 
				
			||||||
 | 
					  byte memid;  //here goes the EEMARK stamp
 | 
				
			||||||
 | 
					  uint8_t hstart;
 | 
				
			||||||
 | 
					  uint8_t mstart;
 | 
				
			||||||
 | 
					  uint8_t hstop;
 | 
				
			||||||
 | 
					  uint8_t mstop;
 | 
				
			||||||
 | 
					  float tempe;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EE_bl ee = {0,0,0,0,0,0.1}; //populate as initial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SUBS
 | 
				
			||||||
 | 
					void writeEE(){
 | 
				
			||||||
 | 
					  ee.memid = EEMARK;
 | 
				
			||||||
 | 
					  //EEPROM.put(EESC, sched); // only separately when needed with commit()
 | 
				
			||||||
 | 
					  //EEPROM.put(EECH, memch); // not need to store and retrieve memch
 | 
				
			||||||
 | 
					  EEPROM.put(EEBEGIN + memch*sizeof(ee), ee); 
 | 
				
			||||||
 | 
					  EEPROM.commit(); //needed for ESP8266?
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void readEE(){
 | 
				
			||||||
 | 
					  byte ChkEE;
 | 
				
			||||||
 | 
					  if (memch > MEMMAX) memch = 0;
 | 
				
			||||||
 | 
					  EEPROM.get(EEBEGIN + memch*sizeof(ee), ChkEE);
 | 
				
			||||||
 | 
					  if (ChkEE == EEMARK){ //otherwise stays with defaults
 | 
				
			||||||
 | 
					    EEPROM.get(EEBEGIN  + memch*sizeof(ee), ee);
 | 
				
			||||||
 | 
					    EEPROM.get(EESC, sched);
 | 
				
			||||||
 | 
					    if (sched > MEMMAX + 1) sched = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void doOut(){
 | 
				
			||||||
 | 
					  if (ledOut != ledState){ // only if changed
 | 
				
			||||||
 | 
					    digitalWrite(ledPin, ledState); //consolidated here
 | 
				
			||||||
 | 
					    ledOut = ledState; //update
 | 
				
			||||||
 | 
					    if (ledState == LED_OFF){
 | 
				
			||||||
 | 
					        ws.textAll("led,ledoff");
 | 
				
			||||||
 | 
					        Serial.println(F("LED-OFF"));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ws.textAll("led,ledon");
 | 
				
			||||||
 | 
					        Serial.println(F("LED-ON"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void showTime() 
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  byte tmpch = 0;
 | 
				
			||||||
 | 
					  bool heat_enabled = false; 
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  gettimeofday(&tv, nullptr);
 | 
				
			||||||
 | 
					  now = time(nullptr);
 | 
				
			||||||
 | 
					  const tm* tm = localtime(&now);
 | 
				
			||||||
 | 
					  ws.printfAll("Now,Clock,%02d:%02d,%d", tm->tm_hour, tm->tm_min, tm->tm_wday);
 | 
				
			||||||
 | 
					  if ((2==tm->tm_hour )&&(2==tm->tm_min)){
 | 
				
			||||||
 | 
					    configTzTime(MYTZ, "pool.ntp.org");
 | 
				
			||||||
 | 
					    Serial.print(F("Sync Clock at 02:02\n"));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  Serial.printf("RTC: %02d:%02d\n", tm->tm_hour, tm->tm_min);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (sched == 0){ // automatic
 | 
				
			||||||
 | 
					      if ((tm->tm_wday > 0)&&(tm->tm_wday < 6)) tmpch = 0; //Mon - Fri
 | 
				
			||||||
 | 
					      else if (tm->tm_wday == 6) tmpch = 1; //Sat
 | 
				
			||||||
 | 
					      else if (tm->tm_wday == 0) tmpch = 2; //Sun
 | 
				
			||||||
 | 
					  } else { // manual
 | 
				
			||||||
 | 
					    tmpch = sched - 1; //and stays
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (tmpch != memch){ // update if different
 | 
				
			||||||
 | 
					    memch = tmpch;
 | 
				
			||||||
 | 
					    readEE();
 | 
				
			||||||
 | 
					    ws.printfAll("Now,Setting,%02d:%02d,%02d:%02d,%+2.1f", ee.hstart, ee.mstart, ee.hstop, ee.mstop, ee.tempe);    
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // process smart switch by time and temperature
 | 
				
			||||||
 | 
					  uint16_t xmi = (uint16_t)(60*tm->tm_hour) + tm->tm_min; // max 24h = 1440min, current time
 | 
				
			||||||
 | 
					  uint16_t bmi = (uint16_t)(60*ee.hstart) + ee.mstart;    // begin in minutes
 | 
				
			||||||
 | 
					  uint16_t emi = (uint16_t)(60*ee.hstop) + ee.mstop;      // end in minutes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (bmi == emi) heat_enabled = false;
 | 
				
			||||||
 | 
					  else { //enable smart if different
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (((bmi < emi)&&(bmi <= xmi)&&(xmi < emi))||
 | 
				
			||||||
 | 
					        ((emi < bmi)&&((bmi <= xmi)||(xmi < emi)))){
 | 
				
			||||||
 | 
					         heat_enabled = true;
 | 
				
			||||||
 | 
					       } else heat_enabled = false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (heat_enabled_prev){ // smart control (delayed one cycle)
 | 
				
			||||||
 | 
					    if (((t + HYST) < ee.tempe)&&(ledState == LED_OFF)){ // OFF->ON once
 | 
				
			||||||
 | 
					      ledState = LED_ON;
 | 
				
			||||||
 | 
					      ws.textAll("led,ledon");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if ((((t - HYST) > ee.tempe)&&(ledState == LED_ON))||(!heat_enabled)){ // ->OFF
 | 
				
			||||||
 | 
					      ledState = LED_OFF;
 | 
				
			||||||
 | 
					      ws.textAll("led,ledoff");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Serial.printf(ledState == LED_ON ? "LED ON" : "LED OFF");
 | 
				
			||||||
 | 
					    Serial.print(F(", Smart enabled\n"));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  heat_enabled_prev = heat_enabled; //update 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void updateDHT(){
 | 
				
			||||||
 | 
					  float h1 = dht.readHumidity();
 | 
				
			||||||
 | 
					  float t1 = dht.readTemperature(); //Celsius or dht.readTemperature(true) for Fahrenheit
 | 
				
			||||||
 | 
					  if (isnan(h1) || isnan(t1)){
 | 
				
			||||||
 | 
					  Serial.println(F("Failed to read from DHT sensor!"));
 | 
				
			||||||
 | 
					   } else {
 | 
				
			||||||
 | 
					     h = h1 + DHT_H_CORR;
 | 
				
			||||||
 | 
					     t = t1 + DHT_T_CORR;
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void analogSample()
 | 
				
			||||||
 | 
					{      
 | 
				
			||||||
 | 
					  ws.printfAll("wpMeter,Arduino,%+2.1f,%2.1f,%d", t, h, heat_enabled_prev);
 | 
				
			||||||
 | 
					  Serial.printf("T/H.: %+2.1f°C/%2.1f%%,%d\n", t, h, heat_enabled_prev);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void checkPhysicalButton()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (digitalRead(btnPin) == LOW){
 | 
				
			||||||
 | 
					    if (btnState != LOW){     // btnState is used to avoid sequential toggles
 | 
				
			||||||
 | 
					      ledState = !ledState;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    btnState = LOW;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    btnState = HIGH;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mytimer(){
 | 
				
			||||||
 | 
					    ++count;          //200ms increments
 | 
				
			||||||
 | 
					    checkPhysicalButton();
 | 
				
			||||||
 | 
					    if ((count % 25) == 1){ // update temp every 5 seconds
 | 
				
			||||||
 | 
					      analogSample();
 | 
				
			||||||
 | 
					      udht = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if ((count % 50) == 0){ // update temp every 10 seconds
 | 
				
			||||||
 | 
					      ws.cleanupClients();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (count >= 150){ // cycle every 30 sec
 | 
				
			||||||
 | 
					      showTime();
 | 
				
			||||||
 | 
					      count = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					 unsigned long key[4] = {0x01F20304,0x05060708,0x090a0b0c,0x0d0e0f00};
 | 
				
			||||||
 | 
					 Xtea x(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void encip(String &mtk, unsigned long token){
 | 
				
			||||||
 | 
					  unsigned long res[2] = {(unsigned long)random(0xFFFFFFFF),token};
 | 
				
			||||||
 | 
					  x.encrypt(res);
 | 
				
			||||||
 | 
					  char buf1[18];
 | 
				
			||||||
 | 
					  sprintf(buf1, "%08X_%08X",res[0],res[1]); //8 bytes for encryping the IP cookie 
 | 
				
			||||||
 | 
					  mtk = (String)buf1; 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unsigned long decip(const char *pch){
 | 
				
			||||||
 | 
					  unsigned long res[2] = {0,0};
 | 
				
			||||||
 | 
					  res[0] = strtoul(pch, NULL, 16);
 | 
				
			||||||
 | 
					  res[1] = strtoul(&pch[9], NULL, 16);
 | 
				
			||||||
 | 
					  x.decrypt(res);
 | 
				
			||||||
 | 
					  return res[1];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool myHandshake(AsyncWebServerRequest *request){ // false will 401
 | 
				
			||||||
 | 
					   bool rslt = false;
 | 
				
			||||||
 | 
					   if (request->hasHeader("Cookie")){
 | 
				
			||||||
 | 
					    String cookie = request->header("Cookie");
 | 
				
			||||||
 | 
					    Serial.println(cookie);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    uint8_t pos = cookie.indexOf(MY_COOKIE_PREF);
 | 
				
			||||||
 | 
					    if (pos != -1){
 | 
				
			||||||
 | 
					      unsigned long ix = decip(cookie.substring(pos+5, pos+22).c_str());
 | 
				
			||||||
 | 
					      Serial.printf("Ask:%08X Got:%08X\n", MY_SECRET_NUMBER, ix);
 | 
				
			||||||
 | 
					      if (MY_SECRET_NUMBER == ix)
 | 
				
			||||||
 | 
					       rslt=true;
 | 
				
			||||||
 | 
					    } else rslt=false;
 | 
				
			||||||
 | 
					   } else rslt=false;
 | 
				
			||||||
 | 
					   Serial.printf(rslt ? "C-YES\n" : "C-NO\n");
 | 
				
			||||||
 | 
					   return rslt;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// server
 | 
				
			||||||
 | 
					void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
 | 
				
			||||||
 | 
					  if(type == WS_EVT_CONNECT){
 | 
				
			||||||
 | 
					    Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
 | 
				
			||||||
 | 
					    //client->printf("Hello Client %u :)", client->id());
 | 
				
			||||||
 | 
					    //client->ping();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    IPAddress ip = client->remoteIP();
 | 
				
			||||||
 | 
					    Serial.printf("[%u] Connected from %d.%d.%d.%d\n", client->id(), ip[0], ip[1], ip[2], ip[3]);
 | 
				
			||||||
 | 
					    showTime();
 | 
				
			||||||
 | 
					    analogSample();
 | 
				
			||||||
 | 
					    if (ledState == LED_OFF) ws.textAll("led,ledoff");
 | 
				
			||||||
 | 
					    else ws.textAll("led,ledon");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    ws.printfAll("Now,Setting,%02d:%02d,%02d:%02d,%+2.1f", ee.hstart, ee.mstart, ee.hstop, ee.mstop, ee.tempe);
 | 
				
			||||||
 | 
					    ws.printfAll("Now,sched,%d", sched);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  } else if(type == WS_EVT_DISCONNECT){
 | 
				
			||||||
 | 
					    Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id());
 | 
				
			||||||
 | 
					    ws.textAll("Now,remoff"); 
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  } else if(type == WS_EVT_ERROR){
 | 
				
			||||||
 | 
					    Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
 | 
				
			||||||
 | 
					  } else if(type == WS_EVT_PONG){
 | 
				
			||||||
 | 
					    Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
 | 
				
			||||||
 | 
					  } else if(type == WS_EVT_DATA){
 | 
				
			||||||
 | 
					    AwsFrameInfo * info = (AwsFrameInfo*)arg;
 | 
				
			||||||
 | 
					    String msg = "";
 | 
				
			||||||
 | 
					    if(info->final && info->index == 0 && info->len == len){
 | 
				
			||||||
 | 
					      //the whole message is in a single frame and we got all of it's data
 | 
				
			||||||
 | 
					      Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(info->opcode == WS_TEXT){
 | 
				
			||||||
 | 
					        for(size_t i=0; i < info->len; i++){ //debug
 | 
				
			||||||
 | 
					          msg += (char) data[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					              if(data[0] == 'L'){ // LED
 | 
				
			||||||
 | 
					                if(data[1] == '1'){
 | 
				
			||||||
 | 
					                  ledState = LED_ON;
 | 
				
			||||||
 | 
					                  ws.textAll("led,ledon"); // for others
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if(data[1] == '0'){
 | 
				
			||||||
 | 
					                  ledState = LED_OFF;
 | 
				
			||||||
 | 
					                  ws.textAll("led,ledoff"); 
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                                  
 | 
				
			||||||
 | 
					              } else if(data[0] == 'T'){ // timeset
 | 
				
			||||||
 | 
					                  if (len > 11){
 | 
				
			||||||
 | 
					                    data[3] = data[6] = data[9] = data[12] = 0; // cut strings 
 | 
				
			||||||
 | 
					                    ee.hstart = (uint8_t) atoi((const char *) &data[1]);
 | 
				
			||||||
 | 
					                    ee.mstart = (uint8_t) atoi((const char *) &data[4]);
 | 
				
			||||||
 | 
					                    ee.hstop  = (uint8_t) atoi((const char *) &data[7]);
 | 
				
			||||||
 | 
					                    ee.mstop  = (uint8_t) atoi((const char *) &data[10]);
 | 
				
			||||||
 | 
					                    Serial.printf("[%u] Timer set %02d:%02d - %02d:%02d\n", client->id(), ee.hstart, ee.mstart, ee.hstop, ee.mstop);
 | 
				
			||||||
 | 
					                    writeEE();
 | 
				
			||||||
 | 
					                    memch = 255; // to force showTime()to send Setting
 | 
				
			||||||
 | 
					                    showTime();
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					              } else if(data[0] == 'W'){ // temperatureset
 | 
				
			||||||
 | 
					                  if (len > 3){
 | 
				
			||||||
 | 
					                    if (ee.tempe != (float) atof((const char *) &data[1])){
 | 
				
			||||||
 | 
					                     ee.tempe = (float) atof((const char *) &data[1]);
 | 
				
			||||||
 | 
					                     Serial.printf("[%u] Temp set %+2.1f\n", client->id(), ee.tempe);
 | 
				
			||||||
 | 
					                     writeEE();
 | 
				
			||||||
 | 
					                     memch = 255; // to force showTime()to send Setting
 | 
				
			||||||
 | 
					                     showTime();
 | 
				
			||||||
 | 
					                   }
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					              } else if ((data[0] == 'Z')&&(len > 2)){ // sched
 | 
				
			||||||
 | 
					                  data[2] = 0;
 | 
				
			||||||
 | 
					                  if (sched != (uint8_t) atoi((const char *) &data[1])){
 | 
				
			||||||
 | 
					                    sched = (uint8_t) atoi((const char *) &data[1]);
 | 
				
			||||||
 | 
					                    EEPROM.put(EESC, sched); //separately
 | 
				
			||||||
 | 
					                    EEPROM.commit(); //needed for ESP8266?
 | 
				
			||||||
 | 
					                    ws.printfAll("Now,sched,%d", sched);    
 | 
				
			||||||
 | 
					                    showTime();
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        char buff[3];
 | 
				
			||||||
 | 
					        for(size_t i=0; i < info->len; i++){
 | 
				
			||||||
 | 
					          sprintf(buff, "%02x ", (uint8_t) data[i]);
 | 
				
			||||||
 | 
					          msg += buff ;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      Serial.printf("%s\n",msg.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(info->opcode == WS_TEXT)
 | 
				
			||||||
 | 
					        client->text("I got your text message");
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        client->binary("I got your binary message");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      //message is comprised of multiple frames or the frame is split into multiple packets
 | 
				
			||||||
 | 
					      if(info->index == 0){
 | 
				
			||||||
 | 
					        if(info->num == 0)
 | 
				
			||||||
 | 
					          Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
 | 
				
			||||||
 | 
					        Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(info->opcode == WS_TEXT){
 | 
				
			||||||
 | 
					        for(size_t i=0; i < len; i++){
 | 
				
			||||||
 | 
					          msg += (char) data[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        char buff[3];
 | 
				
			||||||
 | 
					        for(size_t i=0; i < len; i++){
 | 
				
			||||||
 | 
					          sprintf(buff, "%02x ", (uint8_t) data[i]);
 | 
				
			||||||
 | 
					          msg += buff ;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      Serial.printf("%s\n",msg.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if((info->index + len) == info->len){
 | 
				
			||||||
 | 
					        Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
 | 
				
			||||||
 | 
					        if(info->final){
 | 
				
			||||||
 | 
					          Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
 | 
				
			||||||
 | 
					          if(info->message_opcode == WS_TEXT)
 | 
				
			||||||
 | 
					            client->text("I got your text message");
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            client->binary("I got your binary message");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// setup -----------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setup(){
 | 
				
			||||||
 | 
					  Serial.begin(115200);
 | 
				
			||||||
 | 
					  //Serial.setDebugOutput(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Wifi 
 | 
				
			||||||
 | 
					#ifdef USE_WFM 
 | 
				
			||||||
 | 
					  AsyncWiFiManager wifiManager(&server,&dns);
 | 
				
			||||||
 | 
					 #ifdef DEL_WFM
 | 
				
			||||||
 | 
					  wifiManager.resetSettings();
 | 
				
			||||||
 | 
					 #endif
 | 
				
			||||||
 | 
					  wifiManager.setTimeout(FBTO); // seconds to config or it creates an own AP, then browse 192.168.4.1
 | 
				
			||||||
 | 
					  if (!wifiManager.autoConnect(hostName)){  
 | 
				
			||||||
 | 
					    Serial.print(F("*FALLBACK AP*\n"));
 | 
				
			||||||
 | 
					    WiFi.mode(WIFI_AP);
 | 
				
			||||||
 | 
					    WiFi.softAP(fbssid, fbpassword);
 | 
				
			||||||
 | 
					 // MDNS.begin(fbssid);
 | 
				
			||||||
 | 
					 // MDNS.addService("http","tcp",80); // Core SVN 5179 use STA as default interface in mDNS (#7042)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					// Manual simple STA mode to connect to known router
 | 
				
			||||||
 | 
					    //WiFi.mode(WIFI_AP_STA); // Core SVN 5179 use STA as default interface in mDNS (#7042)
 | 
				
			||||||
 | 
					    //WiFi.softAP(hostName);  // Core SVN 5179 use STA as default interface in mDNS (#7042)
 | 
				
			||||||
 | 
					  WiFi.mode(WIFI_STA);      // Core SVN 5179 use STA as default interface in mDNS (#7042)
 | 
				
			||||||
 | 
					  WiFi.begin(ssid, password);
 | 
				
			||||||
 | 
					  if (WiFi.waitForConnectResult() != WL_CONNECTED){
 | 
				
			||||||
 | 
					    Serial.print(F("STA: Failed!\n"));
 | 
				
			||||||
 | 
					    WiFi.disconnect(false);
 | 
				
			||||||
 | 
					    delay(1000);
 | 
				
			||||||
 | 
					    WiFi.begin(ssid, password);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Serial.print(F("*CONNECTED* OWN IP:"));
 | 
				
			||||||
 | 
					  Serial.println(WiFi.localIP());
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					//DHT
 | 
				
			||||||
 | 
					  dht.begin();
 | 
				
			||||||
 | 
					  updateDHT(); //first reading takes time, hold here than the loop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Real Time
 | 
				
			||||||
 | 
					  time_t rtc = RTC_UTC_TEST;
 | 
				
			||||||
 | 
					  timeval tv = { rtc, 0 };     
 | 
				
			||||||
 | 
					                               //timezone tz = { 0, 0 };   //(insert) <#5194
 | 
				
			||||||
 | 
					  settimeofday(&tv, nullptr);  //settimeofday(&tv, &tz);   // <#5194
 | 
				
			||||||
 | 
					  configTzTime(MYTZ, "pool.ntp.org");
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					//MDNS (not needed)
 | 
				
			||||||
 | 
					  // MDNS.begin(hostName);
 | 
				
			||||||
 | 
					  // MDNS.addService("http","tcp",80); // Core SVN 5179 use STA as default interface in mDNS (#7042)
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					//I/O & DHT
 | 
				
			||||||
 | 
					  pinMode(ledPin, OUTPUT);
 | 
				
			||||||
 | 
					  pinMode(btnPin, INPUT_PULLUP);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					//EE
 | 
				
			||||||
 | 
					  EEPROM.begin(EEALL);
 | 
				
			||||||
 | 
					  //EEPROM.get(EECH, memch); //current channel, no need
 | 
				
			||||||
 | 
					  readEE(); // populate structure if healthy 
 | 
				
			||||||
 | 
					  Serial.printf("Timer set %02d:%02d - %02d:%02d\n", ee.hstart, ee.mstart, ee.hstop, ee.mstop);
 | 
				
			||||||
 | 
					  Serial.printf("Temp set %+2.1f\n", ee.tempe);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//FS
 | 
				
			||||||
 | 
					#ifdef USE_FatFS
 | 
				
			||||||
 | 
					  if (MYFS.begin(false,"/ffat",3)){ //limit the RAM usage, bottom line 8kb + 4kb takes per each file, default is 10
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  if (MYFS.begin()){
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    Serial.print(F("FS mounted\n"));
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    Serial.print(F("FS mount failed\n"));  
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_WS
 | 
				
			||||||
 | 
					  ws.setAuthentication(http_username,http_password);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					  ws.handleHandshake(myHandshake);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ws.onEvent(onWsEvent);
 | 
				
			||||||
 | 
					  server.addHandler(&ws);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					 #ifdef USE_AUTH_STAT
 | 
				
			||||||
 | 
					  server.addHandler(new SPIFFSEditor(MYFS, http_username,http_password));
 | 
				
			||||||
 | 
					 #elif defined(USE_AUTH_COOKIE)
 | 
				
			||||||
 | 
					  server.addHandler(new SPIFFSEditor(MYFS)).setFilter(myHandshake);
 | 
				
			||||||
 | 
					 #else
 | 
				
			||||||
 | 
					  server.addHandler(new SPIFFSEditor(MYFS));
 | 
				
			||||||
 | 
					 #endif
 | 
				
			||||||
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
 | 
					 #ifdef USE_AUTH_STAT
 | 
				
			||||||
 | 
					  server.addHandler(new SPIFFSEditor(http_username,http_password,MYFS));
 | 
				
			||||||
 | 
					 #elif defined(USE_AUTH_COOKIE)
 | 
				
			||||||
 | 
					  server.addHandler(new SPIFFSEditor("","",MYFS)).setFilter(myHandshake);
 | 
				
			||||||
 | 
					 #else
 | 
				
			||||||
 | 
					  server.addHandler(new SPIFFSEditor("","",MYFS)); 
 | 
				
			||||||
 | 
					 #endif
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					  server.on("/lg2n", HTTP_POST, [](AsyncWebServerRequest *request){
 | 
				
			||||||
 | 
					       
 | 
				
			||||||
 | 
					    String ckx;
 | 
				
			||||||
 | 
					    encip(ckx, MY_SECRET_NUMBER);  
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    AsyncWebServerResponse *response;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if(request->hasParam("lg0f",true)){
 | 
				
			||||||
 | 
					      response = request->beginResponse(200, "text/html;charset=utf-8", "<h1>Logged Out! <a href='/'>Back</a></h1>");
 | 
				
			||||||
 | 
					      response->addHeader("Cache-Control", "no-cache");
 | 
				
			||||||
 | 
					      response->addHeader("Set-Cookie", MY_COOKIE_DEL);
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    } else if(request->hasParam("pa2w",true) && (String(request->getParam("pa2w",true)->value().c_str()) == String(http_password))){
 | 
				
			||||||
 | 
					      response = request->beginResponse(301);
 | 
				
			||||||
 | 
					      response->addHeader("Location", "/");
 | 
				
			||||||
 | 
					      response->addHeader("Cache-Control", "no-cache");
 | 
				
			||||||
 | 
					      response->addHeader("Set-Cookie",  MY_COOKIE_PREF + ckx + MY_COOKIE_SUFF);
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    } else response = request->beginResponse(200, "text/html;charset=utf-8", "<h1>Wrong password! <a href='/'>Back</a></h1>");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    request->send(response);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// below paths need individual auth ////////////////////////////////////////////////
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.on("/free-ram", HTTP_GET, [](AsyncWebServerRequest *request){  // direct request->answer
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_STAT
 | 
				
			||||||
 | 
					    if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					    request->send(200, "text/plain", String(ESP.getMinFreeHeap()) + ':' + String(ESP.getFreeHeap()) + ':'+ String(ESP.getHeapSize())); 
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					    request->send(200, "text/plain", String(ESP.getFreeHeap())); 
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					  }).setFilter(myHandshake);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.on("/get-time", HTTP_GET, [](AsyncWebServerRequest *request){
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_STAT
 | 
				
			||||||
 | 
					      if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
 | 
				
			||||||
 | 
					#endif    
 | 
				
			||||||
 | 
					      if(request->hasParam("btime")){
 | 
				
			||||||
 | 
					       time_t rtc = (request->getParam("btime")->value()).toInt();
 | 
				
			||||||
 | 
					       timeval tv = { rtc, 0 };            
 | 
				
			||||||
 | 
					       settimeofday(&tv, nullptr);  
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					       request->send(200, "text/plain","Got browser time ...");
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					  }).setFilter(myHandshake);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.on("/hw-reset", HTTP_GET, [](AsyncWebServerRequest *request){
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_STAT
 | 
				
			||||||
 | 
					    if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      request->onDisconnect([](){
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					        ESP.restart();
 | 
				
			||||||
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
 | 
					        ESP.reset();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    request->send(200, "text/plain","Restarting ...");
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					  }).setFilter(myHandshake);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.on("/erase-wifi", HTTP_GET, [](AsyncWebServerRequest *request){
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_STAT
 | 
				
			||||||
 | 
					    if(!request->authenticate(http_username, http_password)) return request->requestAuthentication();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      request->onDisconnect([](){ 
 | 
				
			||||||
 | 
					#ifdef ESP32
 | 
				
			||||||
 | 
					/*          
 | 
				
			||||||
 | 
					        //https://github.com/espressif/arduino-esp32/issues/400#issuecomment-499631249 
 | 
				
			||||||
 | 
					        //WiFi.disconnect(true); // doesn't work on esp32, below needs #include "esp_wifi.h"
 | 
				
			||||||
 | 
					        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); //load the flash-saved configs
 | 
				
			||||||
 | 
					        esp_wifi_init(&cfg); //initiate and allocate wifi resources (does not matter if connection fails)
 | 
				
			||||||
 | 
					        if(esp_wifi_restore()!=ESP_OK){
 | 
				
			||||||
 | 
					            Serial.print(F("WiFi is not initialized by esp_wifi_init "));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Serial.print(F("WiFi Configurations Cleared!"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					*/        
 | 
				
			||||||
 | 
					        WiFi.disconnect(true, true); // Works on esp32
 | 
				
			||||||
 | 
					        ESP.restart();
 | 
				
			||||||
 | 
					#elif defined(ESP8266)
 | 
				
			||||||
 | 
					        WiFi.disconnect(true); 
 | 
				
			||||||
 | 
					        ESP.reset();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    request->send(200, "text/plain","Erasing WiFi data ...");
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					  }).setFilter(myHandshake);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					// above paths need individual auth ////////////////////////////////////////////////
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_AUTH_COOKIE
 | 
				
			||||||
 | 
					  server.serveStatic("/", MYFS, "/").setDefaultFile("index.htm").setFilter(myHandshake);
 | 
				
			||||||
 | 
					  server.serveStatic("/", MYFS, "/login/").setDefaultFile("index.htm");
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  #ifdef USE_AUTH_STAT
 | 
				
			||||||
 | 
					    server.serveStatic("/", MYFS, "/").setDefaultFile("index.htm").setAuthentication(http_username,http_password);
 | 
				
			||||||
 | 
					  #else
 | 
				
			||||||
 | 
					    server.serveStatic("/", MYFS, "/").setDefaultFile("index.htm");
 | 
				
			||||||
 | 
					  #endif
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  server.onNotFound([](AsyncWebServerRequest *request){  // nothing known
 | 
				
			||||||
 | 
					    Serial.print(F("NOT_FOUND: "));
 | 
				
			||||||
 | 
					    if (request->method() == HTTP_OPTIONS) request->send(200); //CORS
 | 
				
			||||||
 | 
					    else request->send(404);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");//CORS
 | 
				
			||||||
 | 
					  server.begin();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  //Timer tick
 | 
				
			||||||
 | 
					  tim.attach(0.2, mytimer); //every 0.2s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   //OTA
 | 
				
			||||||
 | 
					  ArduinoOTA.setHostname(hostName);
 | 
				
			||||||
 | 
					  ArduinoOTA.onStart([](){ 
 | 
				
			||||||
 | 
					    Serial.print(F("OTA Started ...\n"));
 | 
				
			||||||
 | 
					    MYFS.end(); // Clean FS
 | 
				
			||||||
 | 
					    ws.textAll("Now,OTA"); // for all clients
 | 
				
			||||||
 | 
					    ws.enable(false);
 | 
				
			||||||
 | 
					    ws.closeAll();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  ArduinoOTA.begin();
 | 
				
			||||||
 | 
					} // setup end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// loop -----------------------------------
 | 
				
			||||||
 | 
					void loop(){
 | 
				
			||||||
 | 
					  if (udht){  // 5sec
 | 
				
			||||||
 | 
					    updateDHT();
 | 
				
			||||||
 | 
					    udht = false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  doOut();
 | 
				
			||||||
 | 
					  ArduinoOTA.handle();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										48
									
								
								examples/SmartSwitch/Xtea.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					  Xtea.cpp - Xtea encryption/decryption
 | 
				
			||||||
 | 
					  Written by Frank Kienast in November, 2010
 | 
				
			||||||
 | 
					  https://github.com/franksmicro/Arduino/tree/master/libraries/Xtea
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					#include "Xtea.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NUM_ROUNDS 32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Xtea::Xtea(unsigned long key[4])
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						_key[0] = key[0];
 | 
				
			||||||
 | 
						_key[1] = key[1];
 | 
				
			||||||
 | 
						_key[2] = key[2];
 | 
				
			||||||
 | 
						_key[3] = key[3];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Xtea::encrypt(unsigned long v[2]) 
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    unsigned int i;
 | 
				
			||||||
 | 
					    unsigned long v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for (i=0; i < NUM_ROUNDS; i++) 
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + _key[sum & 3]);
 | 
				
			||||||
 | 
					        sum += delta;
 | 
				
			||||||
 | 
					        v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + _key[(sum>>11) & 3]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    v[0]=v0; v[1]=v1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					void Xtea::decrypt(unsigned long v[2]) 
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    unsigned int i;
 | 
				
			||||||
 | 
					    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*NUM_ROUNDS;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for (i=0; i < NUM_ROUNDS; i++) 
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + _key[(sum>>11) & 3]);
 | 
				
			||||||
 | 
					        sum -= delta;
 | 
				
			||||||
 | 
					        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + _key[sum & 3]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    v[0]=v0; v[1]=v1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
							
								
								
									
										20
									
								
								examples/SmartSwitch/Xtea.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					  Xtea.h - Crypto library
 | 
				
			||||||
 | 
					  Written by Frank Kienast in November, 2010
 | 
				
			||||||
 | 
					  https://github.com/franksmicro/Arduino/tree/master/libraries/Xtea
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					#ifndef Xtea_h
 | 
				
			||||||
 | 
					#define Xtea_h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Xtea
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  public:
 | 
				
			||||||
 | 
					    Xtea(unsigned long key[4]);
 | 
				
			||||||
 | 
					    void encrypt(unsigned long data[2]);
 | 
				
			||||||
 | 
					    void decrypt(unsigned long data[2]);
 | 
				
			||||||
 | 
					  private:
 | 
				
			||||||
 | 
					    unsigned long _key[4];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										3
									
								
								examples/SmartSwitch/data/.exclude.files
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					/*.gz
 | 
				
			||||||
 | 
					/edit_gz
 | 
				
			||||||
 | 
					/.exclude.files
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/ace.ico.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/acefull.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/app.css.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/app.min.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/edit_gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/favicon.ico.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										626
									
								
								examples/SmartSwitch/data/index.htm
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,626 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					    <head>
 | 
				
			||||||
 | 
					    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
 | 
				
			||||||
 | 
					    <title>Smart Switch</title>
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width">
 | 
				
			||||||
 | 
					    <link rel="apple-touch-icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="app.css" type="text/css" />
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        body {
 | 
				
			||||||
 | 
					            font-family: arial;
 | 
				
			||||||
 | 
					            color: #999
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        table {
 | 
				
			||||||
 | 
					            margin: auto;
 | 
				
			||||||
 | 
					            max-width: 600px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        td {
 | 
				
			||||||
 | 
					            padding: 1%;
 | 
				
			||||||
 | 
					            padding-inline: 2%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					       [id^="input-"] {
 | 
				
			||||||
 | 
					            width: 70%;
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            padding: 0px;
 | 
				
			||||||
 | 
					            box-sizing: border-box;
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            border-bottom: 1px solid #2196f3;
 | 
				
			||||||
 | 
					            font-size: 22px;
 | 
				
			||||||
 | 
					            color: #2196f3;
 | 
				
			||||||
 | 
					            font-family: arial;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input[type=button] {
 | 
				
			||||||
 | 
					            background-color: #2196f3;
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            color: #fff;
 | 
				
			||||||
 | 
					            padding: 10px 10px;
 | 
				
			||||||
 | 
					            width: 62px;
 | 
				
			||||||
 | 
					            text-decoration: none;
 | 
				
			||||||
 | 
					            margin: 4px 2px;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            border-radius: 34px;
 | 
				
			||||||
 | 
					            font-family: arial;
 | 
				
			||||||
 | 
					            font-weight: 700;
 | 
				
			||||||
 | 
					            -webkit-appearance: none;
 | 
				
			||||||
 | 
					            -moz-appearance: none;
 | 
				
			||||||
 | 
					            appearance: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .switch {
 | 
				
			||||||
 | 
					            position: relative;
 | 
				
			||||||
 | 
					            display: inline-block;
 | 
				
			||||||
 | 
					            width: 60px;
 | 
				
			||||||
 | 
					            height: 34px
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .switch input {
 | 
				
			||||||
 | 
					            opacity: 0;
 | 
				
			||||||
 | 
					            width: 0;
 | 
				
			||||||
 | 
					            height: 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .slider {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            right: 0;
 | 
				
			||||||
 | 
					            bottom: 0;
 | 
				
			||||||
 | 
					            background-color: #ccc;
 | 
				
			||||||
 | 
					            -webkit-transition: .4s;
 | 
				
			||||||
 | 
					            transition: .4s
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .slider:before {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            content: "";
 | 
				
			||||||
 | 
					            height: 26px;
 | 
				
			||||||
 | 
					            width: 26px;
 | 
				
			||||||
 | 
					            left: 4px;
 | 
				
			||||||
 | 
					            bottom: 4px;
 | 
				
			||||||
 | 
					            background-color: #fff;
 | 
				
			||||||
 | 
					            -webkit-transition: .4s;
 | 
				
			||||||
 | 
					            transition: .4s
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input:checked+.slider {
 | 
				
			||||||
 | 
					            background-color: #2196f3
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input:focus+.slider {
 | 
				
			||||||
 | 
					            box-shadow: 0 0 1px #2196f3
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input:checked+.slider:before {
 | 
				
			||||||
 | 
					            -webkit-transform: translateX(26px);
 | 
				
			||||||
 | 
					            -ms-transform: translateX(26px);
 | 
				
			||||||
 | 
					            transform: translateX(26px)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .slider.round {
 | 
				
			||||||
 | 
					            border-radius: 34px
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .slider.round:before {
 | 
				
			||||||
 | 
					            border-radius: 50%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .clk {
 | 
				
			||||||
 | 
					            font-size: 52px;
 | 
				
			||||||
 | 
					            color: #444;
 | 
				
			||||||
 | 
					            cursor: pointer
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .clk2 {
 | 
				
			||||||
 | 
					            font-size: 24px;
 | 
				
			||||||
 | 
					            color: #444
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .clear:after,
 | 
				
			||||||
 | 
					        .clear:before {
 | 
				
			||||||
 | 
					            content: "";
 | 
				
			||||||
 | 
					            display: table
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .clear:after {
 | 
				
			||||||
 | 
					            clear: both
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .wrapper {
 | 
				
			||||||
 | 
					            position: relative;
 | 
				
			||||||
 | 
					            top: 0px;
 | 
				
			||||||
 | 
					            right: 0;
 | 
				
			||||||
 | 
					            bottom: 0px;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            margin: auto;
 | 
				
			||||||
 | 
					            max-width: 500px;
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            border: 0px solid #ccc
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .gauge {
 | 
				
			||||||
 | 
					            display: block;
 | 
				
			||||||
 | 
					            float: left
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        #g1, #g2 {
 | 
				
			||||||
 | 
					            width: 50%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .son {
 | 
				
			||||||
 | 
					            color: green;
 | 
				
			||||||
 | 
					            font-weight: bold
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .soff {
 | 
				
			||||||
 | 
					            color: red;
 | 
				
			||||||
 | 
					            font-weight: bold
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .blinking {
 | 
				
			||||||
 | 
					            animation: blinkingText 1.2s infinite;
 | 
				
			||||||
 | 
					            -webkit-animation: blinkingText 1.2s infinite;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @keyframes blinkingText {
 | 
				
			||||||
 | 
					            0% {
 | 
				
			||||||
 | 
					                color: #ec0b0b;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            49% {
 | 
				
			||||||
 | 
					                color: #ea7272;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            60% {
 | 
				
			||||||
 | 
					                color: transparent;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            99% {
 | 
				
			||||||
 | 
					                color: transparent;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            100% {
 | 
				
			||||||
 | 
					                color: #ff0404;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container {
 | 
				
			||||||
 | 
					            display: inline-block;
 | 
				
			||||||
 | 
					            position: relative;
 | 
				
			||||||
 | 
					            padding-left: 19px;
 | 
				
			||||||
 | 
					            padding-top: 4px;
 | 
				
			||||||
 | 
					            margin-top: 8px;
 | 
				
			||||||
 | 
					            margin-bottom: 8px;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            font-size: 14px;
 | 
				
			||||||
 | 
					            -webkit-user-select: none;
 | 
				
			||||||
 | 
					            -moz-user-select: none;
 | 
				
			||||||
 | 
					            -ms-user-select: none;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            width: 12.5%;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            user-select: none
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container input {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            opacity: 0;
 | 
				
			||||||
 | 
					            cursor: pointer
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .checkmark {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            height: 25px;
 | 
				
			||||||
 | 
					            width: 25px;
 | 
				
			||||||
 | 
					            background-color: #eee;
 | 
				
			||||||
 | 
					            border-radius: 50%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container:hover input ~ .checkmark {
 | 
				
			||||||
 | 
					            background-color: #ccc
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container input:checked ~ .checkmark {
 | 
				
			||||||
 | 
					            background-color: #2196F3
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .checkmark:after {
 | 
				
			||||||
 | 
					            content: "";
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            display: none
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container input:checked ~ .checkmark:after {
 | 
				
			||||||
 | 
					            display: block
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container .checkmark:after {
 | 
				
			||||||
 | 
					            top: 6px;
 | 
				
			||||||
 | 
					            left: 6px;
 | 
				
			||||||
 | 
					            width: 13px;
 | 
				
			||||||
 | 
					            height: 13px;
 | 
				
			||||||
 | 
					            border-radius: 50%;
 | 
				
			||||||
 | 
					            background: white
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        *:focus {outline:none !important}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <table align="center">
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td colspan=3>
 | 
				
			||||||
 | 
					                <form name="sched">
 | 
				
			||||||
 | 
					                    <label class="container" id="LZ0">Def
 | 
				
			||||||
 | 
					                        <input type="radio" name="radio" onclick="handleClick(this);" value="Z0"><span class="checkmark"></span></label>
 | 
				
			||||||
 | 
					                    <label class="container" id="LZ1">M-F
 | 
				
			||||||
 | 
					                        <input type="radio" name="radio" onclick="handleClick(this);" value="Z1"><span class="checkmark"></span></label>
 | 
				
			||||||
 | 
					                    <label class="container" id="LZ2">Sat
 | 
				
			||||||
 | 
					                        <input type="radio" name="radio" onclick="handleClick(this);" value="Z2"><span class="checkmark"></span></label>
 | 
				
			||||||
 | 
					                    <label class="container" id="LZ3">Sun
 | 
				
			||||||
 | 
					                        <input type="radio" name="radio" onclick="handleClick(this);" value="Z3"><span class="checkmark"></span></label>
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td onmousedown="ent1Click();">
 | 
				
			||||||
 | 
					                <label for="input-temperature">Temp °C</label>
 | 
				
			||||||
 | 
					                <input  readonly="readonly" id="input-temperature" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td onmousedown="ent2Click();">
 | 
				
			||||||
 | 
					                <label for="input-popup-start">Time On</label>
 | 
				
			||||||
 | 
					                <input  readonly="readonly" id="input-popup-start" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td onmousedown="ent2Click();">
 | 
				
			||||||
 | 
					                <label for="input-popup-stop">Time Off</label>
 | 
				
			||||||
 | 
					                <input  readonly="readonly" id="input-popup-stop" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <input type="button" id="W" value="Temp" onclick="button2Click(this);" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <label class="switch">
 | 
				
			||||||
 | 
					                    <input id="cbStyle" type="checkbox" onclick="checkboxClick(this);"><span class="slider round"></span></label>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <input type="button" id="T" value="Timer" onclick="buttonClick(this);" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <div id="reconnect">
 | 
				
			||||||
 | 
					                    <input type="button" id="N" value="WSoff" onclick="statusWs();" />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td><span id="sid"></span><div id="free-ram"></div><div id="erase-wifi"></div><div id="hw-reset"></div><div id="get-time"></div></td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <div id="ebut">
 | 
				
			||||||
 | 
					                    <input type="button" id="E" value="WEdit" onclick="buttonEClick();" />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <input type="button" id="X" value="xWiFi" onclick="loadDoc('erase-wifi', 1);"/>
 | 
				
			||||||
 | 
					            </td>            
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <input type="button" id="H" value="Heap" onclick="loadDoc('free-ram', 0);" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <input type="button" id="R" value="HWrst" onclick="loadDoc('hw-reset', 1);"/>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					    </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="wrapper clear">
 | 
				
			||||||
 | 
					        <div id="g1" class="gauge"></div>
 | 
				
			||||||
 | 
					        <div id="g2" class="gauge"></div>
 | 
				
			||||||
 | 
					        <div id="C" class="clk" onclick="getBtime();"></div>
 | 
				
			||||||
 | 
					        <div id="C2" class="clk2"></div>
 | 
				
			||||||
 | 
					       <input type="button" id="O" value="Logout" onclick="buttonOClick();" />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script src="app.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script type="text/javascript">
 | 
				
			||||||
 | 
					        const MYCORS = '192.168.1.12';
 | 
				
			||||||
 | 
					        var g1, g2;
 | 
				
			||||||
 | 
					        var Analog0 = new Array();
 | 
				
			||||||
 | 
					        var auto = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const successNotification = window.createNotification({
 | 
				
			||||||
 | 
					            positionClass: 'nfc-bottom-right',
 | 
				
			||||||
 | 
					            theme: 'info',
 | 
				
			||||||
 | 
					            showDuration: 3000
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const warningNotification = window.createNotification({
 | 
				
			||||||
 | 
					            positionClass: 'nfc-bottom-right',
 | 
				
			||||||
 | 
					            theme: 'warning',
 | 
				
			||||||
 | 
					            showDuration: 6000
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.addEventListener("DOMContentLoaded", function(event) {
 | 
				
			||||||
 | 
					            console.log("DOM fully loaded and parsed");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            document.getElementById("R").style.backgroundColor = "red";
 | 
				
			||||||
 | 
					            document.getElementById("X").style.backgroundColor = "red";
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            g1 = new JustGage({
 | 
				
			||||||
 | 
					                id: "g1",
 | 
				
			||||||
 | 
					                value: -5.5,
 | 
				
			||||||
 | 
					                min: -40,
 | 
				
			||||||
 | 
					                max: 50,
 | 
				
			||||||
 | 
					                decimals: 1,
 | 
				
			||||||
 | 
					                labelFontColor: "#444",
 | 
				
			||||||
 | 
					                valueFontColor: "#444",
 | 
				
			||||||
 | 
					                title: "Temperature",
 | 
				
			||||||
 | 
					                titlePosition: "below",
 | 
				
			||||||
 | 
					                label: "°C",
 | 
				
			||||||
 | 
					                relativeGaugeSize: true,
 | 
				
			||||||
 | 
					                pointer: true,
 | 
				
			||||||
 | 
					                customSectors: [{
 | 
				
			||||||
 | 
					                    color: "#328da8",
 | 
				
			||||||
 | 
					                    lo: -40,
 | 
				
			||||||
 | 
					                    hi: 0
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                    color: "#32a852",
 | 
				
			||||||
 | 
					                    lo: 0,
 | 
				
			||||||
 | 
					                    hi: 35
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                    color: "#ff4d4d",
 | 
				
			||||||
 | 
					                    lo: 35,
 | 
				
			||||||
 | 
					                    hi: 50
 | 
				
			||||||
 | 
					                }],
 | 
				
			||||||
 | 
					                formatNumber: true
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            g2 = new JustGage({
 | 
				
			||||||
 | 
					                id: "g2",
 | 
				
			||||||
 | 
					                value: 40.8,
 | 
				
			||||||
 | 
					                min: 0,
 | 
				
			||||||
 | 
					                max: 100,
 | 
				
			||||||
 | 
					                decimals: 1,
 | 
				
			||||||
 | 
					                labelFontColor: "#444",
 | 
				
			||||||
 | 
					                valueFontColor: "#444",
 | 
				
			||||||
 | 
					                title: "Humidity",
 | 
				
			||||||
 | 
					                titlePosition: "below",
 | 
				
			||||||
 | 
					                label: "%",
 | 
				
			||||||
 | 
					                relativeGaugeSize: true,
 | 
				
			||||||
 | 
					                pointer: true,
 | 
				
			||||||
 | 
					                customSectors: [{
 | 
				
			||||||
 | 
					                    color: "#ffc926",
 | 
				
			||||||
 | 
					                    lo: 0,
 | 
				
			||||||
 | 
					                    hi: 45
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                    color: "#32a852",
 | 
				
			||||||
 | 
					                    lo: 45,
 | 
				
			||||||
 | 
					                    hi: 55
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                    color: "#328da8",
 | 
				
			||||||
 | 
					                    lo: 55,
 | 
				
			||||||
 | 
					                    hi: 100
 | 
				
			||||||
 | 
					                }],
 | 
				
			||||||
 | 
					                formatNumber: true
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var servurl = document.location.host;
 | 
				
			||||||
 | 
					        if (servurl.length < 5) servurl = MYCORS;
 | 
				
			||||||
 | 
					        var connection = new WebSocket('ws://' + servurl + '/ws', ['arduino']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.onopen = function() {
 | 
				
			||||||
 | 
					            //connection.send('get_something');
 | 
				
			||||||
 | 
					            document.getElementById("sid").className = "son";
 | 
				
			||||||
 | 
					            document.getElementById('sid').innerHTML = "Smart Switch";
 | 
				
			||||||
 | 
					            console.log("connection opened");
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.onclose = function() {
 | 
				
			||||||
 | 
					            document.getElementById("sid").className = "blinking";
 | 
				
			||||||
 | 
					            document.getElementById('sid').innerHTML = "Detached";
 | 
				
			||||||
 | 
					            document.getElementById("N").value = "WSon";
 | 
				
			||||||
 | 
					            console.log("connection closed");
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.onerror = function(error) {
 | 
				
			||||||
 | 
					            document.getElementById("sid").className = "soff";
 | 
				
			||||||
 | 
					            document.getElementById('sid').innerHTML = "Detached";
 | 
				
			||||||
 | 
					            document.getElementById("N").value = "WSon";
 | 
				
			||||||
 | 
					            console.log('WebSocket Error ', error);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.onmessage = function(evt) {
 | 
				
			||||||
 | 
					            // handle websocket message. update attributes or values of elements that match the name on incoming message
 | 
				
			||||||
 | 
					            console.log("msg rec", evt.data);
 | 
				
			||||||
 | 
					            var msgArray = evt.data.split(","); // split message by delimiter into a string array
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[0]);
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[1]);
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[2]);
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[3]);
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[4]);
 | 
				
			||||||
 | 
					            var indicator = msgArray[1]; // the first element in the message array is the ID of the object to update
 | 
				
			||||||
 | 
					            console.log("indiactor", indicator);
 | 
				
			||||||
 | 
					            var a = document.getElementById('cbStyle');
 | 
				
			||||||
 | 
					            if (indicator) // if an object by the name of the message exists, update its value or its attributes
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                switch (msgArray[1]) {
 | 
				
			||||||
 | 
					                    case "Arduino":
 | 
				
			||||||
 | 
					                        console.log("Got Temp / Humidity");
 | 
				
			||||||
 | 
					                        g1.refresh(msgArray[2], null);
 | 
				
			||||||
 | 
					                        g2.refresh(msgArray[3], null);
 | 
				
			||||||
 | 
					                        if (msgArray[4] == "1") document.getElementById("T").style.backgroundColor = "#32a852";
 | 
				
			||||||
 | 
					                        else document.getElementById("T").style.backgroundColor = "#2196f3";
 | 
				
			||||||
 | 
					                        var delta = (parseFloat(document.getElementById('input-temperature').value).toFixed(1) - parseFloat(msgArray[2]).toFixed(1));
 | 
				
			||||||
 | 
					                        if (delta > 0.5) document.getElementById("W").style.backgroundColor = "red";
 | 
				
			||||||
 | 
					                        else if (delta < -0.5) document.getElementById("W").style.backgroundColor = "#2196f3";
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "Clock":
 | 
				
			||||||
 | 
										            	var dn = parseInt(msgArray[3]);
 | 
				
			||||||
 | 
					                        var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
 | 
				
			||||||
 | 
					                        var dayName = days[dn] || '*'; // 0...6
 | 
				
			||||||
 | 
											            var shedtype = 0; 
 | 
				
			||||||
 | 
										              if (dn == 0) shedtype = 3;
 | 
				
			||||||
 | 
											            else if (dn == 6) shedtype = 2;
 | 
				
			||||||
 | 
											            else if ((dn > 0) && (dn < 6)) shedtype = 1;
 | 
				
			||||||
 | 
					                        document.getElementById('C').innerHTML = msgArray[2];
 | 
				
			||||||
 | 
					                        document.getElementById('C2').innerHTML = dayName;
 | 
				
			||||||
 | 
											            if (auto) document.getElementById('LZ' + shedtype).classList.add('son');
 | 
				
			||||||
 | 
											            else document.getElementById('LZ' + shedtype).classList.remove('son');
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "Setting":
 | 
				
			||||||
 | 
					                        document.getElementById('input-popup-start').value = msgArray[2];
 | 
				
			||||||
 | 
					                        document.getElementById('input-popup-stop').value = msgArray[3];
 | 
				
			||||||
 | 
					                        document.getElementById('input-temperature').value = parseFloat(msgArray[4]).toFixed(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        document.getElementById('input-popup-start').className = "";
 | 
				
			||||||
 | 
					                        document.getElementById('input-popup-stop').className = "";
 | 
				
			||||||
 | 
					                        document.getElementById('input-temperature').className = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        tpick.attach("input-popup-start");
 | 
				
			||||||
 | 
					                        tpick.attach("input-popup-stop");
 | 
				
			||||||
 | 
					                        fpick.attach("input-temperature");
 | 
				
			||||||
 | 
					                        warningNotification({
 | 
				
			||||||
 | 
					                            message: 'Client UPD'
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "ledon":
 | 
				
			||||||
 | 
					                        a.checked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "ledoff":
 | 
				
			||||||
 | 
					                        a.checked = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "remoff":
 | 
				
			||||||
 | 
					                        successNotification({
 | 
				
			||||||
 | 
					                            message: 'Client quit'
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "OTA":
 | 
				
			||||||
 | 
					                        warningNotification({
 | 
				
			||||||
 | 
					                            message: 'OTA begin'
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        statusWs();
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "sched":
 | 
				
			||||||
 | 
					                        document.sched.radio[msgArray[2]].checked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    default:
 | 
				
			||||||
 | 
					                        // unrecognized message type. do nothing
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                } // switch
 | 
				
			||||||
 | 
					            } // if (indicator)
 | 
				
			||||||
 | 
					        }; // connection.onmessage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function buttonClick(e) {
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) {
 | 
				
			||||||
 | 
					                connection.send(e.id + document.getElementById("input-popup-start").value + "|" + document.getElementById("input-popup-stop").value + "|");
 | 
				
			||||||
 | 
					                successNotification({
 | 
				
			||||||
 | 
					                    message: 'Timer REQ'
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function button2Click(e) {
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) {
 | 
				
			||||||
 | 
					                connection.send(e.id + parseFloat(document.getElementById("input-temperature").value).toFixed(1) + "|");
 | 
				
			||||||
 | 
					                successNotification({
 | 
				
			||||||
 | 
					                    message: 'Temp. REQ'
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function buttonEClick() {
 | 
				
			||||||
 | 
					            var murl = '/edit';
 | 
				
			||||||
 | 
					            if (document.location.host.length < 5) murl = 'http://' + MYCORS + '/edit'; //CORS
 | 
				
			||||||
 | 
					            successNotification({message: 'Editor'});
 | 
				
			||||||
 | 
					            window.open(murl, '_blank');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function buttonOClick() {
 | 
				
			||||||
 | 
							    var murl = "";
 | 
				
			||||||
 | 
					            // If base  auth
 | 
				
			||||||
 | 
					            //murl = document.location.href.replace("http://", "http://" + new Date().getTime() + "@");
 | 
				
			||||||
 | 
					            // If cookie auth
 | 
				
			||||||
 | 
					            murl += 'login/';
 | 
				
			||||||
 | 
					            if (document.location.host.length < 5) murl = 'http://' + MYCORS + '/login/'; //CORS
 | 
				
			||||||
 | 
					            warningNotification({ message: 'Logout'});
 | 
				
			||||||
 | 
					            window.open(murl, '_self');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function checkboxClick(e) {
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) {
 | 
				
			||||||
 | 
					                if (e.checked) connection.send('L1');
 | 
				
			||||||
 | 
					                else connection.send('L0');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function ent1Click() {
 | 
				
			||||||
 | 
					            document.getElementById("input-temperature").className = "blinking";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function ent2Click() {
 | 
				
			||||||
 | 
					            document.getElementById("input-popup-stop").className = "blinking";
 | 
				
			||||||
 | 
					            document.getElementById("input-popup-start").className = "blinking";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function handleClick(e) {
 | 
				
			||||||
 | 
					            if (e.value == 'Z0' ) auto = true;
 | 
				
			||||||
 | 
					            else auto = false;
 | 
				
			||||||
 | 
					            document.getElementById('L' + e.value).classList.remove('son');
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) connection.send(e.value + "|");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // XMLHttpRequest /non WebSocket/ command. same as command' div' id to get response to
 | 
				
			||||||
 | 
					        function loadDoc(cmd, r, param) {
 | 
				
			||||||
 | 
					            var par = param || '';
 | 
				
			||||||
 | 
					            var murl = '/' + cmd + par;
 | 
				
			||||||
 | 
					            if (document.location.host.length < 5) murl = 'http://' + MYCORS + '/' + cmd + par; //CORS
 | 
				
			||||||
 | 
					            var xhttp = new XMLHttpRequest();
 | 
				
			||||||
 | 
					            xhttp.onreadystatechange = function() {
 | 
				
			||||||
 | 
					                if (this.readyState == 4 && this.status == 200) {
 | 
				
			||||||
 | 
					                    document.getElementById(cmd).innerHTML = this.responseText;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            xhttp.open("GET", murl, true);
 | 
				
			||||||
 | 
					            xhttp.send();
 | 
				
			||||||
 | 
					            successNotification({
 | 
				
			||||||
 | 
					                message: 'Cmd: ' + cmd
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            if (r) { //restart?
 | 
				
			||||||
 | 
					                connection.close();
 | 
				
			||||||
 | 
					                document.getElementById("N").value = "WSon";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function getBtime() {
 | 
				
			||||||
 | 
					          loadDoc('get-time', 0, '?btime=' + Math.round(new Date().getTime()/1000)); 
 | 
				
			||||||
 | 
					          document.getElementById('C').innerHTML ='';
 | 
				
			||||||
 | 
					          document.getElementById('C2').innerHTML ='';
 | 
				
			||||||
 | 
					          window.location.reload();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function statusWs() {
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) {
 | 
				
			||||||
 | 
					                connection.close();
 | 
				
			||||||
 | 
					                document.getElementById("N").value = "WSon";
 | 
				
			||||||
 | 
					                warningNotification({ message: 'WS Closed'});
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                window.location.reload();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/login/favicon.ico.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										36
									
								
								examples/SmartSwitch/data/login/index.htm
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
 | 
				
			||||||
 | 
					    <title>Login</title>
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width">
 | 
				
			||||||
 | 
					    <link rel="apple-touch-icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body style="background-color:#bbb;font-family:arial;">
 | 
				
			||||||
 | 
					<center>
 | 
				
			||||||
 | 
					<br><br>
 | 
				
			||||||
 | 
					<h4>Login - Logout</h4>
 | 
				
			||||||
 | 
					<form action="/lg2n"  method="post">
 | 
				
			||||||
 | 
					  <input type="password" id="pwd" name="pa2w" placeholder="Enter the password"><br><br>
 | 
				
			||||||
 | 
					  <input type="checkbox" id="lof" name="lg0f" onclick="hidep()"><label for="lg0f">Logout</label><br><br>
 | 
				
			||||||
 | 
					  <input type="submit" value="Submit">
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					<br><a href='/'>Back</a>
 | 
				
			||||||
 | 
					</center> 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					function hidep() {
 | 
				
			||||||
 | 
					  var checkBox = document.getElementById("lof");
 | 
				
			||||||
 | 
					  var text = document.getElementById("pwd");
 | 
				
			||||||
 | 
					  if (checkBox.checked == true){
 | 
				
			||||||
 | 
					    text.style.display = "none";
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					     text.style.display = "inline";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/worker-css.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/worker-html.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/worker-javascript.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data/worker-json.js.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2
									
								
								examples/SmartSwitch/data_src/.exclude.files
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					*.gz
 | 
				
			||||||
 | 
					.exclude.files
 | 
				
			||||||
							
								
								
									
										30
									
								
								examples/SmartSwitch/data_src/acefull.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								examples/SmartSwitch/data_src/app.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										533
									
								
								examples/SmartSwitch/data_src/app.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,533 @@
 | 
				
			|||||||
 | 
					var tpick={attach:function(target){var dig=document.getElementById(target).value.split(":");var t1=dig[0]||"23";var t2=dig[1]||"59";var uniqueID=0;while(document.getElementById("tpick-"+uniqueID)!=null){uniqueID=Math.floor(Math.random()*(100-2))+1;}
 | 
				
			||||||
 | 
					var tw=document.createElement("div");tw.id="tpick-"+uniqueID;tw.classList.add("tpop");tw.dataset.target=target;tw.addEventListener("click",function(evt){if(evt.target.classList.contains("tpop")){this.classList.remove("show");}});var tp=document.createElement("div");tp.classList.add("tpicker");tp.appendChild(this.draw("h",t1));tp.appendChild(this.draw("m",t2));var bottom=document.createElement("div"),ok=document.createElement("input");ok.setAttribute("type","button");ok.value="OK";ok.addEventListener("click",function(){tpick.set(this);});bottom.classList.add("tpicker-btn");bottom.appendChild(ok);tp.appendChild(bottom);tw.appendChild(tp);document.body.appendChild(tw);var target=document.getElementById(target);target.dataset.dp=uniqueID;target.onfocus=function(){document.getElementById("tpick-"+this.dataset.dp).classList.add("show");};},draw:function(type,tv){var docket=document.createElement("div"),up=document.createElement("div"),down=document.createElement("div"),text=document.createElement("input");docket.classList.add("tpicker-"+type);up.classList.add("tpicker-up");down.classList.add("tpicker-down");up.innerHTML="︿";down.innerHTML="﹀";text.readOnly=true;text.setAttribute("type","text");if(type=="h"){text.value=tv;up.addEventListener("click",function(){tpick.spin("h",1,this);});down.addEventListener("click",function(){tpick.spin("h",0,this);});}else if(type=="m"){text.value=tv;up.addEventListener("click",function(){tpick.spin("m",1,this);});down.addEventListener("click",function(){tpick.spin("m",0,this);});}
 | 
				
			||||||
 | 
					docket.appendChild(up);docket.appendChild(text);docket.appendChild(down);return docket;},spin:function(type,direction,el){var parent=el.parentElement,field=parent.getElementsByTagName("input")[0],value=field.value;if(type=="h"){value=parseInt(value);if(direction){value++;}else{value--;}
 | 
				
			||||||
 | 
					if(value==-1){value=23;}else if(value>23){value=0;}}else if(type=="m"){value=parseInt(value);if(direction){value+=5;}else{value-=5;}
 | 
				
			||||||
 | 
					if(value<0){value=55;}else if(value>59){value=0;}
 | 
				
			||||||
 | 
					if(value<10){value="0"+value;}}
 | 
				
			||||||
 | 
					field.value=('00'+value).substr(-2);},set:function(el){var parent=el.parentElement;while(parent.classList.contains("tpop")==false){parent=parent.parentElement;}
 | 
				
			||||||
 | 
					var input=parent.querySelectorAll("input[type=text]");var time=input[0].value+":"+input[1].value;document.getElementById(parent.dataset.target).value=time;parent.classList.remove("show");}};var fpick={attach:function(target){var dig=document.getElementById(target).value.split(".");var t1=dig[0]||"1";var t2=dig[1]||"2";var uniqueID=0;while(document.getElementById("fpick-"+uniqueID)!=null){uniqueID=Math.floor(Math.random()*(100-2))+1;}
 | 
				
			||||||
 | 
					var tw=document.createElement("div");tw.id="fpick-"+uniqueID;tw.classList.add("tpop");tw.dataset.target=target;tw.addEventListener("click",function(evt){if(evt.target.classList.contains("tpop")){this.classList.remove("show");}});var tp=document.createElement("div");tp.classList.add("fpicker");tp.appendChild(this.draw("h",t1));tp.appendChild(this.draw("m",t2));var bottom=document.createElement("div"),ok=document.createElement("input");ok.setAttribute("type","button");ok.value="OK";ok.addEventListener("click",function(){fpick.set(this);});bottom.classList.add("fpicker-btn");bottom.appendChild(ok);tp.appendChild(bottom);tw.appendChild(tp);document.body.appendChild(tw);var target=document.getElementById(target);target.dataset.dp=uniqueID;target.onfocus=function(){document.getElementById("fpick-"+this.dataset.dp).classList.add("show");};},draw:function(type,tv){var docket=document.createElement("div"),up=document.createElement("div"),down=document.createElement("div"),text=document.createElement("input");docket.classList.add("fpicker-"+type);up.classList.add("fpicker-up");down.classList.add("fpicker-down");up.innerHTML="︿";down.innerHTML="﹀";text.readOnly=true;text.setAttribute("type","text");if(type=="h"){text.value=tv;up.addEventListener("click",function(){fpick.spin("h",1,this);});down.addEventListener("click",function(){fpick.spin("h",0,this);});}else if(type=="m"){text.value=tv;up.addEventListener("click",function(){fpick.spin("m",1,this);});down.addEventListener("click",function(){fpick.spin("m",0,this);});}
 | 
				
			||||||
 | 
					docket.appendChild(up);docket.appendChild(text);docket.appendChild(down);return docket;},spin:function(type,direction,el){var parent=el.parentElement,field=parent.getElementsByTagName("input")[0],value=field.value;if(type=="h"){value=parseInt(value);if(direction){value++;}else{value--;}
 | 
				
			||||||
 | 
					if(value==-41){value=99;}else if(value>99){value=-40;}}else if(type=="m"){value=parseInt(value);if(direction){value++;}else{value--;}
 | 
				
			||||||
 | 
					if(value<0){value=9;}else if(value>9){value=0;}}
 | 
				
			||||||
 | 
					field.value=value;},set:function(el){var parent=el.parentElement;while(parent.classList.contains("tpop")==false){parent=parent.parentElement;}
 | 
				
			||||||
 | 
					var input=parent.querySelectorAll("input[type=text]");var temperature=input[0].value+"."+input[1].value;document.getElementById(parent.dataset.target).value=temperature;parent.classList.remove("show");}};!function(t){function n(i){if(e[i])
 | 
				
			||||||
 | 
					return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}
 | 
				
			||||||
 | 
					var e={};n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:i})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},n.p="",n(n.s=0)}
 | 
				
			||||||
 | 
					([function(t,n,e){e(1),t.exports=e(4)},function(t,n,e){"use strict";var i=Object.assign||function(t){for(var n=1;n<arguments.length;n++){var e=arguments[n];for(var i in e)
 | 
				
			||||||
 | 
					Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i])}
 | 
				
			||||||
 | 
					return t};e(2);var o=e(3);!function(t){function n(t){return t=i({},c,t),function(t){return["nfc-top-left","nfc-top-right","nfc-bottom-left","nfc-bottom-right"].indexOf(t)>-1}
 | 
				
			||||||
 | 
					(t.positionClass)||(console.warn("An invalid notification position class has been specified."),t.positionClass=c.positionClass),t.onclick&&"function"!=typeof t.onclick&&(console.warn("Notification on click must be a function."),t.onclick=c.onclick),"number"!=typeof t.showDuration&&(t.showDuration=c.showDuration),(0,o.isString)(t.theme)&&0!==t.theme.length||(console.warn("Notification theme must be a string with length"),t.theme=c.theme),t}
 | 
				
			||||||
 | 
					function e(t){return t=n(t),function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=n.title,i=n.message,c=r(t.positionClass);if(!e&&!i)
 | 
				
			||||||
 | 
					return console.warn("Notification must contain a title or a message!");var a=(0,o.createElement)("div","ncf",t.theme);if(!0===t.closeOnClick&&a.addEventListener("click",function(){return c.removeChild(a)}),t.onclick&&a.addEventListener("click",function(n){return t.onclick(n)}),t.displayCloseButton){var s=(0,o.createElement)("button");s.innerText="X",!1===t.closeOnClick&&s.addEventListener("click",function(){return c.removeChild(a)}),(0,o.append)(a,s)}
 | 
				
			||||||
 | 
					if((0,o.isString)(e)&&e.length&&(0,o.append)(a,(0,o.createParagraph)("ncf-title")(e)),(0,o.isString)(i)&&i.length&&(0,o.append)(a,(0,o.createParagraph)("nfc-message")(i)),(0,o.append)(c,a),t.showDuration&&t.showDuration>0){var l=setTimeout(function(){c.removeChild(a),0===c.querySelectorAll(".ncf").length&&document.body.removeChild(c)},t.showDuration);(t.closeOnClick||t.displayCloseButton)&&a.addEventListener("click",function(){return clearTimeout(l)})}}}
 | 
				
			||||||
 | 
					function r(t){var n=document.querySelector("."+t);return n||(n=(0,o.createElement)("div","ncf-container",t),(0,o.append)(document.body,n)),n}
 | 
				
			||||||
 | 
					var c={closeOnClick:!0,displayCloseButton:!1,positionClass:"nfc-top-right",onclick:!1,showDuration:3500,theme:"success"};t.createNotification?console.warn("Window already contains a create notification function. Have you included the script twice?"):t.createNotification=e}
 | 
				
			||||||
 | 
					(window)},function(t,n,e){"use strict";!function(){function t(t){this.el=t;for(var n=t.className.replace(/^\s+|\s+$/g,"").split(/\s+/),i=0;i<n.length;i++)
 | 
				
			||||||
 | 
					e.call(this,n[i])}
 | 
				
			||||||
 | 
					if(!(void 0===window.Element||"classList"in document.documentElement)){var n=Array.prototype,e=n.push,i=n.splice,o=n.join;t.prototype={add:function(t){this.contains(t)||(e.call(this,t),this.el.className=this.toString())},contains:function(t){return-1!=this.el.className.indexOf(t)},item:function(t){return this[t]||null},remove:function(t){if(this.contains(t)){for(var n=0;n<this.length&&this[n]!=t;n++);i.call(this,n,1),this.el.className=this.toString()}},toString:function(){return o.call(this," ")},toggle:function(t){return this.contains(t)?this.remove(t):this.add(t),this.contains(t)}},window.DOMTokenList=t,function(t,n,e){Object.defineProperty?Object.defineProperty(t,n,{get:e}):t.__defineGetter__(n,e)}
 | 
				
			||||||
 | 
					(Element.prototype,"classList",function(){return new t(this)})}}
 | 
				
			||||||
 | 
					()},function(t,n,e){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var i=n.partial=function(t){for(var n=arguments.length,e=Array(n>1?n-1:0),i=1;i<n;i++)
 | 
				
			||||||
 | 
					e[i-1]=arguments[i];return function(){for(var n=arguments.length,i=Array(n),o=0;o<n;o++)
 | 
				
			||||||
 | 
					i[o]=arguments[o];return t.apply(void 0,e.concat(i))}},o=(n.append=function(t){for(var n=arguments.length,e=Array(n>1?n-1:0),i=1;i<n;i++)
 | 
				
			||||||
 | 
					e[i-1]=arguments[i];return e.forEach(function(n){return t.appendChild(n)})},n.isString=function(t){return"string"==typeof t},n.createElement=function(t){for(var n=arguments.length,e=Array(n>1?n-1:0),i=1;i<n;i++)
 | 
				
			||||||
 | 
					e[i-1]=arguments[i];var o=document.createElement(t);return e.length&&e.forEach(function(t){return o.classList.add(t)}),o}),r=function(t,n){return t.innerText=n,t},c=function(t){for(var n=arguments.length,e=Array(n>1?n-1:0),c=1;c<n;c++)
 | 
				
			||||||
 | 
					e[c-1]=arguments[c];return i(r,o.apply(void 0,[t].concat(e)))};n.createParagraph=function(){for(var t=arguments.length,n=Array(t),e=0;e<t;e++)
 | 
				
			||||||
 | 
					n[e]=arguments[e];return c.apply(void 0,["p"].concat(n))}},function(t,n){}]);!function(a,b){"function"==typeof define&&define.amd?define("eve",function(){return b()}):"object"==typeof exports?module.exports=b():a.eve=b()}
 | 
				
			||||||
 | 
					(this,function(){var a,b,c="0.4.2",d="hasOwnProperty",e=/[\.\/]/,f="*",g=function(){},h=function(a,b){return a-b},i={n:{}},j=function(c,d){c=String(c);var e,f=b,g=Array.prototype.slice.call(arguments,2),i=j.listeners(c),k=0,l=[],m={},n=[],o=a;a=c,b=0;for(var p=0,q=i.length;q>p;p++)"zIndex"in i[p]&&(l.push(i[p].zIndex),i[p].zIndex<0&&(m[i[p].zIndex]=i[p]));for(l.sort(h);l[k]<0;)
 | 
				
			||||||
 | 
					if(e=m[l[k++]],n.push(e.apply(d,g)),b)
 | 
				
			||||||
 | 
					return b=f,n;for(p=0;q>p;p++)
 | 
				
			||||||
 | 
					if(e=i[p],"zIndex"in e)
 | 
				
			||||||
 | 
					if(e.zIndex==l[k]){if(n.push(e.apply(d,g)),b)
 | 
				
			||||||
 | 
					break;do
 | 
				
			||||||
 | 
					if(k++,e=m[l[k]],e&&n.push(e.apply(d,g)),b)
 | 
				
			||||||
 | 
					break;while(e)}else
 | 
				
			||||||
 | 
					m[e.zIndex]=e;else if(n.push(e.apply(d,g)),b)
 | 
				
			||||||
 | 
					break;return b=f,a=o,n.length?n:null};return j._events=i,j.listeners=function(a){var b,c,d,g,h,j,k,l,m=a.split(e),n=i,o=[n],p=[];for(g=0,h=m.length;h>g;g++){for(l=[],j=0,k=o.length;k>j;j++)
 | 
				
			||||||
 | 
					for(n=o[j].n,c=[n[m[g]],n[f]],d=2;d--;)
 | 
				
			||||||
 | 
					b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}
 | 
				
			||||||
 | 
					return p},j.on=function(a,b){if(a=String(a),"function"!=typeof b)
 | 
				
			||||||
 | 
					return function(){};for(var c=a.split(e),d=i,f=0,h=c.length;h>f;f++)
 | 
				
			||||||
 | 
					d=d.n,d=d.hasOwnProperty(c[f])&&d[c[f]]||(d[c[f]]={n:{}});for(d.f=d.f||[],f=0,h=d.f.length;h>f;f++)
 | 
				
			||||||
 | 
					if(d.f[f]==b)
 | 
				
			||||||
 | 
					return g;return d.f.push(b),function(a){+a==+a&&(b.zIndex=+a)}},j.f=function(a){var b=[].slice.call(arguments,1);return function(){j.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},j.stop=function(){b=1},j.nt=function(b){return b?new RegExp("(?:\\.|\\/|^)"+b+"(?:\\.|\\/|$)").test(a):a},j.nts=function(){return a.split(e)},j.off=j.unbind=function(a,b){if(!a)
 | 
				
			||||||
 | 
					return void(j._events=i={n:{}});var c,g,h,k,l,m,n,o=a.split(e),p=[i];for(k=0,l=o.length;l>k;k++)
 | 
				
			||||||
 | 
					for(m=0;m<p.length;m+=h.length-2){if(h=[m,1],c=p[m].n,o[k]!=f)
 | 
				
			||||||
 | 
					c[o[k]]&&h.push(c[o[k]]);else
 | 
				
			||||||
 | 
					for(g in c)
 | 
				
			||||||
 | 
					c[d](g)&&h.push(c[g]);p.splice.apply(p,h)}
 | 
				
			||||||
 | 
					for(k=0,l=p.length;l>k;k++)
 | 
				
			||||||
 | 
					for(c=p[k];c.n;){if(b){if(c.f){for(m=0,n=c.f.length;n>m;m++)
 | 
				
			||||||
 | 
					if(c.f[m]==b){c.f.splice(m,1);break}
 | 
				
			||||||
 | 
					!c.f.length&&delete c.f}
 | 
				
			||||||
 | 
					for(g in c.n)
 | 
				
			||||||
 | 
					if(c.n[d](g)&&c.n[g].f){var q=c.n[g].f;for(m=0,n=q.length;n>m;m++)
 | 
				
			||||||
 | 
					if(q[m]==b){q.splice(m,1);break}
 | 
				
			||||||
 | 
					!q.length&&delete c.n[g].f}}else{delete c.f;for(g in c.n)
 | 
				
			||||||
 | 
					c.n[d](g)&&c.n[g].f&&delete c.n[g].f}
 | 
				
			||||||
 | 
					c=c.n}},j.once=function(a,b){var c=function(){return j.unbind(a,c),b.apply(this,arguments)};return j.on(a,c)},j.version=c,j.toString=function(){return"You are running Eve "+c},j}),function(a,b){"function"==typeof define&&define.amd?define("raphael.core",["eve"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("eve")):a.Raphael=b(a.eve)}
 | 
				
			||||||
 | 
					(this,function(a){function b(c){if(b.is(c,"function"))
 | 
				
			||||||
 | 
					return t?c():a.on("raphael.DOMload",c);if(b.is(c,U))
 | 
				
			||||||
 | 
					return b._engine.create[C](b,c.splice(0,3+b.is(c[0],S))).add(c);var d=Array.prototype.slice.call(arguments,0);if(b.is(d[d.length-1],"function")){var e=d.pop();return t?e.call(b._engine.create[C](b,d)):a.on("raphael.DOMload",function(){e.call(b._engine.create[C](b,d))})}
 | 
				
			||||||
 | 
					return b._engine.create[C](b,arguments)}
 | 
				
			||||||
 | 
					function c(a){if("function"==typeof a||Object(a)!==a)
 | 
				
			||||||
 | 
					return a;var b=new a.constructor;for(var d in a)
 | 
				
			||||||
 | 
					a[y](d)&&(b[d]=c(a[d]));return b}
 | 
				
			||||||
 | 
					function d(a,b){for(var c=0,d=a.length;d>c;c++)
 | 
				
			||||||
 | 
					if(a[c]===b)
 | 
				
			||||||
 | 
					return a.push(a.splice(c,1)[0])}
 | 
				
			||||||
 | 
					function e(a,b,c){function e(){var f=Array.prototype.slice.call(arguments,0),g=f.join("␀"),h=e.cache=e.cache||{},i=e.count=e.count||[];return h[y](g)?(d(i,g),c?c(h[g]):h[g]):(i.length>=1e3&&delete h[i.shift()],i.push(g),h[g]=a[C](b,f),c?c(h[g]):h[g])}
 | 
				
			||||||
 | 
					return e}
 | 
				
			||||||
 | 
					function f(){return this.hex}
 | 
				
			||||||
 | 
					function g(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}
 | 
				
			||||||
 | 
					return c}
 | 
				
			||||||
 | 
					function h(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}
 | 
				
			||||||
 | 
					function i(a,b,c,d,e,f,g,i,j){null==j&&(j=1),j=j>1?1:0>j?0:j;for(var k=j/2,l=12,m=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;l>p;p++){var q=k*m[p]+k,r=h(q,a,c,e,g),s=h(q,b,d,f,i),t=r*r+s*s;o+=n[p]*M.sqrt(t)}
 | 
				
			||||||
 | 
					return k*o}
 | 
				
			||||||
 | 
					function j(a,b,c,d,e,f,g,h,j){if(!(0>j||i(a,b,c,d,e,f,g,h)<j)){var k,l=1,m=l/2,n=l-m,o=.01;for(k=i(a,b,c,d,e,f,g,h,n);P(k-j)>o;)
 | 
				
			||||||
 | 
					m/=2,n+=(j>k?1:-1)*m,k=i(a,b,c,d,e,f,g,h,n);return n}}
 | 
				
			||||||
 | 
					function k(a,b,c,d,e,f,g,h){if(!(N(a,c)<O(e,g)||O(a,c)>N(e,g)||N(b,d)<O(f,h)||O(b,d)>N(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+O(a,c).toFixed(2)||n>+N(a,c).toFixed(2)||n<+O(e,g).toFixed(2)||n>+N(e,g).toFixed(2)||o<+O(b,d).toFixed(2)||o>+N(b,d).toFixed(2)||o<+O(f,h).toFixed(2)||o>+N(f,h).toFixed(2)))
 | 
				
			||||||
 | 
					return{x:l,y:m}}}}
 | 
				
			||||||
 | 
					function l(a,c,d){var e=b.bezierBBox(a),f=b.bezierBBox(c);if(!b.isBBoxIntersect(e,f))
 | 
				
			||||||
 | 
					return d?0:[];for(var g=i.apply(0,a),h=i.apply(0,c),j=N(~~(g/5),1),l=N(~~(h/5),1),m=[],n=[],o={},p=d?0:[],q=0;j+1>q;q++){var r=b.findDotsAtSegment.apply(b,a.concat(q/j));m.push({x:r.x,y:r.y,t:q/j})}
 | 
				
			||||||
 | 
					for(q=0;l+1>q;q++)
 | 
				
			||||||
 | 
					r=b.findDotsAtSegment.apply(b,c.concat(q/l)),n.push({x:r.x,y:r.y,t:q/l});for(q=0;j>q;q++)
 | 
				
			||||||
 | 
					for(var s=0;l>s;s++){var t=m[q],u=m[q+1],v=n[s],w=n[s+1],x=P(u.x-t.x)<.001?"y":"x",y=P(w.x-v.x)<.001?"y":"x",z=k(t.x,t.y,u.x,u.y,v.x,v.y,w.x,w.y);if(z){if(o[z.x.toFixed(4)]==z.y.toFixed(4))
 | 
				
			||||||
 | 
					continue;o[z.x.toFixed(4)]=z.y.toFixed(4);var A=t.t+P((z[x]-t[x])/(u[x]-t[x]))*(u.t-t.t),B=v.t+P((z[y]-v[y])/(w[y]-v[y]))*(w.t-v.t);A>=0&&1.001>=A&&B>=0&&1.001>=B&&(d?p++:p.push({x:z.x,y:z.y,t1:O(A,1),t2:O(B,1)}))}}
 | 
				
			||||||
 | 
					return p}
 | 
				
			||||||
 | 
					function m(a,c,d){a=b._path2curve(a),c=b._path2curve(c);for(var e,f,g,h,i,j,k,m,n,o,p=d?0:[],q=0,r=a.length;r>q;q++){var s=a[q];if("M"==s[0])
 | 
				
			||||||
 | 
					e=i=s[1],f=j=s[2];else{"C"==s[0]?(n=[e,f].concat(s.slice(1)),e=n[6],f=n[7]):(n=[e,f,e,f,i,j,i,j],e=i,f=j);for(var t=0,u=c.length;u>t;t++){var v=c[t];if("M"==v[0])
 | 
				
			||||||
 | 
					g=k=v[1],h=m=v[2];else{"C"==v[0]?(o=[g,h].concat(v.slice(1)),g=o[6],h=o[7]):(o=[g,h,g,h,k,m,k,m],g=k,h=m);var w=l(n,o,d);if(d)
 | 
				
			||||||
 | 
					p+=w;else{for(var x=0,y=w.length;y>x;x++)
 | 
				
			||||||
 | 
					w[x].segment1=q,w[x].segment2=t,w[x].bez1=n,w[x].bez2=o;p=p.concat(w)}}}}}
 | 
				
			||||||
 | 
					return p}
 | 
				
			||||||
 | 
					function n(a,b,c,d,e,f){null!=a?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}
 | 
				
			||||||
 | 
					function o(){return this.x+G+this.y+G+this.width+" × "+this.height}
 | 
				
			||||||
 | 
					function p(a,b,c,d,e,f){function g(a){return((l*a+k)*a+j)*a}
 | 
				
			||||||
 | 
					function h(a,b){var c=i(a,b);return((o*c+n)*c+m)*c}
 | 
				
			||||||
 | 
					function i(a,b){var c,d,e,f,h,i;for(e=a,i=0;8>i;i++){if(f=g(e)-a,P(f)<b)
 | 
				
			||||||
 | 
					return e;if(h=(3*l*e+2*k)*e+j,P(h)<1e-6)
 | 
				
			||||||
 | 
					break;e-=f/h}
 | 
				
			||||||
 | 
					if(c=0,d=1,e=a,c>e)
 | 
				
			||||||
 | 
					return c;if(e>d)
 | 
				
			||||||
 | 
					return d;for(;d>c;){if(f=g(e),P(f-a)<b)
 | 
				
			||||||
 | 
					return e;a>f?c=e:d=e,e=(d-c)/2+c}
 | 
				
			||||||
 | 
					return e}
 | 
				
			||||||
 | 
					var j=3*b,k=3*(d-b)-j,l=1-j-k,m=3*c,n=3*(e-c)-m,o=1-m-n;return h(a,1/(200*f))}
 | 
				
			||||||
 | 
					function q(a,b){var c=[],d={};if(this.ms=b,this.times=1,a){for(var e in a)
 | 
				
			||||||
 | 
					a[y](e)&&(d[$(e)]=a[e],c.push($(e)));c.sort(ka)}
 | 
				
			||||||
 | 
					this.anim=d,this.top=c[c.length-1],this.percents=c}
 | 
				
			||||||
 | 
					function r(c,d,e,f,g,h){e=$(e);var i,j,k,l,m,o,q=c.ms,r={},s={},t={};if(f)
 | 
				
			||||||
 | 
					for(w=0,x=fb.length;x>w;w++){var u=fb[w];if(u.el.id==d.id&&u.anim==c){u.percent!=e?(fb.splice(w,1),k=1):j=u,d.attr(u.totalOrigin);break}}
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					f=+s;for(var w=0,x=c.percents.length;x>w;w++){if(c.percents[w]==e||c.percents[w]>f*c.top){e=c.percents[w],m=c.percents[w-1]||0,q=q/c.top*(e-m),l=c.percents[w+1],i=c.anim[e];break}
 | 
				
			||||||
 | 
					f&&d.attr(c.anim[c.percents[w]])}
 | 
				
			||||||
 | 
					if(i){if(j)
 | 
				
			||||||
 | 
					j.initstatus=f,j.start=new Date-j.ms*f;else{for(var z in i)
 | 
				
			||||||
 | 
					if(i[y](z)&&(ca[y](z)||d.paper.customAttributes[y](z)))
 | 
				
			||||||
 | 
					switch(r[z]=d.attr(z),null==r[z]&&(r[z]=ba[z]),s[z]=i[z],ca[z]){case S:t[z]=(s[z]-r[z])/q;break;case"colour":r[z]=b.getRGB(r[z]);var A=b.getRGB(s[z]);t[z]={r:(A.r-r[z].r)/q,g:(A.g-r[z].g)/q,b:(A.b-r[z].b)/q};break;case"path":var B=Ia(r[z],s[z]),C=B[1];for(r[z]=B[0],t[z]=[],w=0,x=r[z].length;x>w;w++){t[z][w]=[0];for(var E=1,F=r[z][w].length;F>E;E++)
 | 
				
			||||||
 | 
					t[z][w][E]=(C[w][E]-r[z][w][E])/q}
 | 
				
			||||||
 | 
					break;case"transform":var G=d._,J=Na(G[z],s[z]);if(J)
 | 
				
			||||||
 | 
					for(r[z]=J.from,s[z]=J.to,t[z]=[],t[z].real=!0,w=0,x=r[z].length;x>w;w++)
 | 
				
			||||||
 | 
					for(t[z][w]=[r[z][w][0]],E=1,F=r[z][w].length;F>E;E++)
 | 
				
			||||||
 | 
					t[z][w][E]=(s[z][w][E]-r[z][w][E])/q;else{var K=d.matrix||new n,L={_:{transform:G.transform},getBBox:function(){return d.getBBox(1)}};r[z]=[K.a,K.b,K.c,K.d,K.e,K.f],La(L,s[z]),s[z]=L._.transform,t[z]=[(L.matrix.a-K.a)/q,(L.matrix.b-K.b)/q,(L.matrix.c-K.c)/q,(L.matrix.d-K.d)/q,(L.matrix.e-K.e)/q,(L.matrix.f-K.f)/q]}
 | 
				
			||||||
 | 
					break;case"csv":var M=H(i[z])[I](v),N=H(r[z])[I](v);if("clip-rect"==z)
 | 
				
			||||||
 | 
					for(r[z]=N,t[z]=[],w=N.length;w--;)
 | 
				
			||||||
 | 
					t[z][w]=(M[w]-r[z][w])/q;s[z]=M;break;default:for(M=[][D](i[z]),N=[][D](r[z]),t[z]=[],w=d.paper.customAttributes[z].length;w--;)
 | 
				
			||||||
 | 
					t[z][w]=((M[w]||0)-(N[w]||0))/q}
 | 
				
			||||||
 | 
					var O=i.easing,P=b.easing_formulas[O];if(!P)
 | 
				
			||||||
 | 
					if(P=H(O).match(Y),P&&5==P.length){var Q=P;P=function(a){return p(a,+Q[1],+Q[2],+Q[3],+Q[4],q)}}else
 | 
				
			||||||
 | 
					P=la;if(o=i.start||c.start||+new Date,u={anim:c,percent:e,timestamp:o,start:o+(c.del||0),status:0,initstatus:f||0,stop:!1,ms:q,easing:P,from:r,diff:t,to:s,el:d,callback:i.callback,prev:m,next:l,repeat:h||c.times,origin:d.attr(),totalOrigin:g},fb.push(u),f&&!j&&!k&&(u.stop=!0,u.start=new Date-q*f,1==fb.length))
 | 
				
			||||||
 | 
					return hb();k&&(u.start=new Date-u.ms*f),1==fb.length&&gb(hb)}
 | 
				
			||||||
 | 
					a("raphael.anim.start."+d.id,d,c)}}
 | 
				
			||||||
 | 
					function s(a){for(var b=0;b<fb.length;b++)
 | 
				
			||||||
 | 
					fb[b].el.paper==a&&fb.splice(b--,1)}
 | 
				
			||||||
 | 
					b.version="2.1.4",b.eve=a;var t,u,v=/[, ]+/,w={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},x=/\{(\d+)\}/g,y="hasOwnProperty",z={doc:document,win:window},A={was:Object.prototype[y].call(z.win,"Raphael"),is:z.win.Raphael},B=function(){this.ca=this.customAttributes={}},C="apply",D="concat",E="ontouchstart"in z.win||z.win.DocumentTouch&&z.doc instanceof DocumentTouch,F="",G=" ",H=String,I="split",J="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[I](G),K={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},L=H.prototype.toLowerCase,M=Math,N=M.max,O=M.min,P=M.abs,Q=M.pow,R=M.PI,S="number",T="string",U="array",V=Object.prototype.toString,W=(b._ISURL=/^url\(['"]?(.+?)['"]?\)$/i,/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i),X={NaN:1,Infinity:1,"-Infinity":1},Y=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,Z=M.round,$=parseFloat,_=parseInt,aa=H.prototype.toUpperCase,ba=b._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},ca=b._availableAnimAttrs={blur:S,"clip-rect":"csv",cx:S,cy:S,fill:"colour","fill-opacity":S,"font-size":S,height:S,opacity:S,path:"path",r:S,rx:S,ry:S,stroke:"colour","stroke-opacity":S,"stroke-width":S,transform:"transform",width:S,x:S,y:S},da=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,ea={hs:1,rg:1},fa=/,?([achlmqrstvxz]),?/gi,ga=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,ha=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,ia=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/gi,ja=(b._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,{}),ka=function(a,b){return $(a)-$(b)},la=function(a){return a},ma=b._rectPath=function(a,b,c,d,e){return e?[["M",a+e,b],["l",c-2*e,0],["a",e,e,0,0,1,e,e],["l",0,d-2*e],["a",e,e,0,0,1,-e,e],["l",2*e-c,0],["a",e,e,0,0,1,-e,-e],["l",0,2*e-d],["a",e,e,0,0,1,e,-e],["z"]]:[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]]},na=function(a,b,c,d){return null==d&&(d=c),[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]]},oa=b._getPath={path:function(a){return a.attr("path")},circle:function(a){var b=a.attrs;return na(b.cx,b.cy,b.r)},ellipse:function(a){var b=a.attrs;return na(b.cx,b.cy,b.rx,b.ry)},rect:function(a){var b=a.attrs;return ma(b.x,b.y,b.width,b.height,b.r)},image:function(a){var b=a.attrs;return ma(b.x,b.y,b.width,b.height)},text:function(a){var b=a._getBBox();return ma(b.x,b.y,b.width,b.height)},set:function(a){var b=a._getBBox();return ma(b.x,b.y,b.width,b.height)}},pa=b.mapPath=function(a,b){if(!b)
 | 
				
			||||||
 | 
					return a;var c,d,e,f,g,h,i;for(a=Ia(a),e=0,g=a.length;g>e;e++)
 | 
				
			||||||
 | 
					for(i=a[e],f=1,h=i.length;h>f;f+=2)
 | 
				
			||||||
 | 
					c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a};if(b._g=z,b.type=z.win.SVGAngle||z.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML","VML"==b.type){var qa,ra=z.doc.createElement("div");if(ra.innerHTML='<v:shape adj="1"/>',qa=ra.firstChild,qa.style.behavior="url(#default#VML)",!qa||"object"!=typeof qa.adj)
 | 
				
			||||||
 | 
					return b.type=F;ra=null}
 | 
				
			||||||
 | 
					b.svg=!(b.vml="VML"==b.type),b._Paper=B,b.fn=u=B.prototype=b.prototype,b._id=0,b._oid=0,b.is=function(a,b){return b=L.call(b),"finite"==b?!X[y](+a):"array"==b?a instanceof Array:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||"array"==b&&Array.isArray&&Array.isArray(a)||V.call(a).slice(8,-1).toLowerCase()==b},b.angle=function(a,c,d,e,f,g){if(null==f){var h=a-d,i=c-e;return h||i?(180+180*M.atan2(-i,-h)/R+360)%360:0}
 | 
				
			||||||
 | 
					return b.angle(a,c,f,g)-b.angle(d,e,f,g)},b.rad=function(a){return a%360*R/180},b.deg=function(a){return Math.round(180*a/R%360*1e3)/1e3},b.snapTo=function(a,c,d){if(d=b.is(d,"finite")?d:10,b.is(a,U)){for(var e=a.length;e--;)
 | 
				
			||||||
 | 
					if(P(a[e]-c)<=d)
 | 
				
			||||||
 | 
					return a[e]}else{a=+a;var f=c%a;if(d>f)
 | 
				
			||||||
 | 
					return c-f;if(f>a-d)
 | 
				
			||||||
 | 
					return c-f+a}
 | 
				
			||||||
 | 
					return c};b.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}
 | 
				
			||||||
 | 
					(/[xy]/g,function(a){var b=16*M.random()|0,c="x"==a?b:3&b|8;return c.toString(16)});b.setWindow=function(c){a("raphael.setWindow",b,z.win,c),z.win=c,z.doc=z.win.document,b._engine.initWin&&b._engine.initWin(z.win)};var sa=function(a){if(b.vml){var c,d=/^\s+|\s+$/g;try{var f=new ActiveXObject("htmlfile");f.write("<body>"),f.close(),c=f.body}catch(g){c=createPopup().document.body}
 | 
				
			||||||
 | 
					var h=c.createTextRange();sa=e(function(a){try{c.style.color=H(a).replace(d,F);var b=h.queryCommandValue("ForeColor");return b=(255&b)<<16|65280&b|(16711680&b)>>>16,"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=z.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",z.doc.body.appendChild(i),sa=e(function(a){return i.style.color=a,z.doc.defaultView.getComputedStyle(i,F).getPropertyValue("color")})}
 | 
				
			||||||
 | 
					return sa(a)},ta=function(){return"hsb("+[this.h,this.s,this.b]+")"},ua=function(){return"hsl("+[this.h,this.s,this.l]+")"},va=function(){return this.hex},wa=function(a,c,d){if(null==c&&b.is(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,c=a.g,a=a.r),null==c&&b.is(a,T)){var e=b.getRGB(a);a=e.r,c=e.g,d=e.b}
 | 
				
			||||||
 | 
					return(a>1||c>1||d>1)&&(a/=255,c/=255,d/=255),[a,c,d]},xa=function(a,c,d,e){a*=255,c*=255,d*=255;var f={r:a,g:c,b:d,hex:b.rgb(a,c,d),toString:va};return b.is(e,"finite")&&(f.opacity=e),f};b.color=function(a){var c;return b.is(a,"object")&&"h"in a&&"s"in a&&"b"in a?(c=b.hsb2rgb(a),a.r=c.r,a.g=c.g,a.b=c.b,a.hex=c.hex):b.is(a,"object")&&"h"in a&&"s"in a&&"l"in a?(c=b.hsl2rgb(a),a.r=c.r,a.g=c.g,a.b=c.b,a.hex=c.hex):(b.is(a,"string")&&(a=b.getRGB(a)),b.is(a,"object")&&"r"in a&&"g"in a&&"b"in a?(c=b.rgb2hsl(a),a.h=c.h,a.s=c.s,a.l=c.l,c=b.rgb2hsb(a),a.v=c.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1)),a.toString=va,a},b.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-P(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],xa(e,f,g,d)},b.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var e,f,g,h,i;return a=a%360/60,i=2*b*(.5>c?c:1-c),h=i*(1-P(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],xa(e,f,g,d)},b.rgb2hsb=function(a,b,c){c=wa(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=N(a,b,c),g=f-O(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:ta}},b.rgb2hsl=function(a,b,c){c=wa(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=N(a,b,c),h=O(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:ua}},b._path2string=function(){return this.join(",").replace(fa,"$1")};b._preload=function(a,b){var c=z.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,z.doc.body.removeChild(this)},c.onerror=function(){z.doc.body.removeChild(this)},z.doc.body.appendChild(c),c.src=a};b.getRGB=e(function(a){if(!a||(a=H(a)).indexOf("-")+1)
 | 
				
			||||||
 | 
					return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:f};if("none"==a)
 | 
				
			||||||
 | 
					return{r:-1,g:-1,b:-1,hex:"none",toString:f};!(ea[y](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=sa(a));var c,d,e,g,h,i,j=a.match(W);return j?(j[2]&&(e=_(j[2].substring(5),16),d=_(j[2].substring(3,5),16),c=_(j[2].substring(1,3),16)),j[3]&&(e=_((h=j[3].charAt(3))+h,16),d=_((h=j[3].charAt(2))+h,16),c=_((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4][I](da),c=$(i[0]),"%"==i[0].slice(-1)&&(c*=2.55),d=$(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=$(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(g=$(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100)),j[5]?(i=j[5][I](da),c=$(i[0]),"%"==i[0].slice(-1)&&(c*=2.55),d=$(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=$(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(c/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(g=$(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),b.hsb2rgb(c,d,e,g)):j[6]?(i=j[6][I](da),c=$(i[0]),"%"==i[0].slice(-1)&&(c*=2.55),d=$(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=$(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(c/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(g=$(i[3])),i[3]&&"%"==i[3].slice(-1)&&(g/=100),b.hsl2rgb(c,d,e,g)):(j={r:c,g:d,b:e,toString:f},j.hex="#"+(16777216|e|d<<8|c<<16).toString(16).slice(1),b.is(g,"finite")&&(j.opacity=g),j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:f}},b),b.hsb=e(function(a,c,d){return b.hsb2rgb(a,c,d).hex}),b.hsl=e(function(a,c,d){return b.hsl2rgb(a,c,d).hex}),b.rgb=e(function(a,b,c){function d(a){return a+.5|0}
 | 
				
			||||||
 | 
					return"#"+(16777216|d(c)|d(b)<<8|d(a)<<16).toString(16).slice(1)}),b.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);return b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})),c.hex},b.getColor.reset=function(){delete this.start},b.parsePathString=function(a){if(!a)
 | 
				
			||||||
 | 
					return null;var c=ya(a);if(c.arr)
 | 
				
			||||||
 | 
					return Aa(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];return b.is(a,U)&&b.is(a[0],U)&&(e=Aa(a)),e.length||H(a).replace(ga,function(a,b,c){var f=[],g=b.toLowerCase();if(c.replace(ia,function(a,b){b&&f.push(+b)}),"m"==g&&f.length>2&&(e.push([b][D](f.splice(0,2))),g="l",b="m"==b?"l":"L"),"r"==g)
 | 
				
			||||||
 | 
					e.push([b][D](f));else
 | 
				
			||||||
 | 
					for(;f.length>=d[g]&&(e.push([b][D](f.splice(0,d[g]))),d[g]););}),e.toString=b._path2string,c.arr=Aa(e),e},b.parseTransformString=e(function(a){if(!a)
 | 
				
			||||||
 | 
					return null;var c=[];return b.is(a,U)&&b.is(a[0],U)&&(c=Aa(a)),c.length||H(a).replace(ha,function(a,b,d){{var e=[];L.call(b)}
 | 
				
			||||||
 | 
					d.replace(ia,function(a,b){b&&e.push(+b)}),c.push([b][D](e))}),c.toString=b._path2string,c});var ya=function(a){var b=ya.ps=ya.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)
 | 
				
			||||||
 | 
					b[y](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]};b.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=Q(j,3),l=Q(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*M.atan2(q-s,r-t)/R;return(q>s||t>r)&&(y+=180),{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}},b.bezierBBox=function(a,c,d,e,f,g,h,i){b.is(a,"array")||(a=[a,c,d,e,f,g,h,i]);var j=Ha.apply(null,a);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},b.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},b.isBBoxIntersect=function(a,c){var d=b.isPointInsideBBox;return d(c,a.x,a.y)||d(c,a.x2,a.y)||d(c,a.x,a.y2)||d(c,a.x2,a.y2)||d(a,c.x,c.y)||d(a,c.x2,c.y)||d(a,c.x,c.y2)||d(a,c.x2,c.y2)||(a.x<c.x2&&a.x>c.x||c.x<a.x2&&c.x>a.x)&&(a.y<c.y2&&a.y>c.y||c.y<a.y2&&c.y>a.y)},b.pathIntersection=function(a,b){return m(a,b)},b.pathIntersectionNumber=function(a,b){return m(a,b,1)},b.isPointInsidePath=function(a,c,d){var e=b.pathBBox(a);return b.isPointInsideBBox(e,c,d)&&m(a,[["M",c,d],["H",e.x2+10]],1)%2==1},b._removedFactory=function(b){return function(){a("raphael.log",null,"Raphaël: you are calling to method “"+b+"” of removed object",b)}};var za=b.pathBBox=function(a){var b=ya(a);if(b.bbox)
 | 
				
			||||||
 | 
					return c(b.bbox);if(!a)
 | 
				
			||||||
 | 
					return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=Ia(a);for(var d,e=0,f=0,g=[],h=[],i=0,j=a.length;j>i;i++)
 | 
				
			||||||
 | 
					if(d=a[i],"M"==d[0])
 | 
				
			||||||
 | 
					e=d[1],f=d[2],g.push(e),h.push(f);else{var k=Ha(e,f,d[1],d[2],d[3],d[4],d[5],d[6]);g=g[D](k.min.x,k.max.x),h=h[D](k.min.y,k.max.y),e=d[5],f=d[6]}
 | 
				
			||||||
 | 
					var l=O[C](0,g),m=O[C](0,h),n=N[C](0,g),o=N[C](0,h),p=n-l,q=o-m,r={x:l,y:m,x2:n,y2:o,width:p,height:q,cx:l+p/2,cy:m+q/2};return b.bbox=c(r),r},Aa=function(a){var d=c(a);return d.toString=b._path2string,d},Ba=b._pathToRelative=function(a){var c=ya(a);if(c.rel)
 | 
				
			||||||
 | 
					return Aa(c.rel);b.is(a,U)&&b.is(a&&a[0],U)||(a=b.parsePathString(a));var d=[],e=0,f=0,g=0,h=0,i=0;"M"==a[0][0]&&(e=a[0][1],f=a[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=a.length;k>j;j++){var l=d[j]=[],m=a[j];if(m[0]!=L.call(m[0]))
 | 
				
			||||||
 | 
					switch(l[0]=L.call(m[0]),l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;o>n;n++)
 | 
				
			||||||
 | 
					l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}
 | 
				
			||||||
 | 
					else{l=d[j]=[],"m"==m[0]&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;q>p;p++)
 | 
				
			||||||
 | 
					d[j][p]=m[p]}
 | 
				
			||||||
 | 
					var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}
 | 
				
			||||||
 | 
					return d.toString=b._path2string,c.rel=Aa(d),d},Ca=b._pathToAbsolute=function(a){var c=ya(a);if(c.abs)
 | 
				
			||||||
 | 
					return Aa(c.abs);if(b.is(a,U)&&b.is(a&&a[0],U)||(a=b.parsePathString(a)),!a||!a.length)
 | 
				
			||||||
 | 
					return[["M",0,0]];var d=[],e=0,f=0,h=0,i=0,j=0;"M"==a[0][0]&&(e=+a[0][1],f=+a[0][2],h=e,i=f,j++,d[0]=["M",e,f]);for(var k,l,m=3==a.length&&"M"==a[0][0]&&"R"==a[1][0].toUpperCase()&&"Z"==a[2][0].toUpperCase(),n=j,o=a.length;o>n;n++){if(d.push(k=[]),l=a[n],l[0]!=aa.call(l[0]))
 | 
				
			||||||
 | 
					switch(k[0]=aa.call(l[0]),k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":for(var p=[e,f][D](l.slice(1)),q=2,r=p.length;r>q;q++)
 | 
				
			||||||
 | 
					p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[D](g(p,m));break;case"M":h=+l[1]+e,i=+l[2]+f;default:for(q=1,r=l.length;r>q;q++)
 | 
				
			||||||
 | 
					k[q]=+l[q]+(q%2?e:f)}
 | 
				
			||||||
 | 
					else if("R"==l[0])
 | 
				
			||||||
 | 
					p=[e,f][D](l.slice(1)),d.pop(),d=d[D](g(p,m)),k=["R"][D](l.slice(-2));else
 | 
				
			||||||
 | 
					for(var s=0,t=l.length;t>s;s++)
 | 
				
			||||||
 | 
					k[s]=l[s];switch(k[0]){case"Z":e=h,f=i;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":h=k[k.length-2],i=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}
 | 
				
			||||||
 | 
					return d.toString=b._path2string,c.abs=Aa(d),d},Da=function(a,b,c,d){return[a,b,c,d,c,d]},Ea=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},Fa=function(a,b,c,d,f,g,h,i,j,k){var l,m=120*R/180,n=R/180*(+f||0),o=[],p=e(function(a,b,c){var d=a*M.cos(c)-b*M.sin(c),e=a*M.sin(c)+b*M.cos(c);return{x:d,y:e}});if(k)
 | 
				
			||||||
 | 
					y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(a,b,-n),a=l.x,b=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(M.cos(R/180*f),M.sin(R/180*f),(a-i)/2),r=(b-j)/2,s=q*q/(c*c)+r*r/(d*d);s>1&&(s=M.sqrt(s),c=s*c,d=s*d);var t=c*c,u=d*d,v=(g==h?-1:1)*M.sqrt(P((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*c*r/d+(a+i)/2,x=v* -d*q/c+(b+j)/2,y=M.asin(((b-x)/d).toFixed(9)),z=M.asin(((j-x)/d).toFixed(9));y=w>a?R-y:y,z=w>i?R-z:z,0>y&&(y=2*R+y),0>z&&(z=2*R+z),h&&y>z&&(y-=2*R),!h&&z>y&&(z-=2*R)}
 | 
				
			||||||
 | 
					var A=z-y;if(P(A)>m){var B=z,C=i,E=j;z=y+m*(h&&z>y?1:-1),i=w+c*M.cos(z),j=x+d*M.sin(z),o=Fa(i,j,c,d,f,0,h,C,E,[z,B,w,x])}
 | 
				
			||||||
 | 
					A=z-y;var F=M.cos(y),G=M.sin(y),H=M.cos(z),J=M.sin(z),K=M.tan(A/4),L=4/3*c*K,N=4/3*d*K,O=[a,b],Q=[a+L*G,b-N*F],S=[i+L*J,j-N*H],T=[i,j];if(Q[0]=2*O[0]-Q[0],Q[1]=2*O[1]-Q[1],k)
 | 
				
			||||||
 | 
					return[Q,S,T][D](o);o=[Q,S,T][D](o).join()[I](",");for(var U=[],V=0,W=o.length;W>V;V++)
 | 
				
			||||||
 | 
					U[V]=V%2?p(o[V-1],o[V],n).y:p(o[V],o[V+1],n).x;return U},Ga=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:Q(j,3)*a+3*Q(j,2)*i*c+3*j*i*i*e+Q(i,3)*g,y:Q(j,3)*b+3*Q(j,2)*i*d+3*j*i*i*f+Q(i,3)*h}},Ha=e(function(a,b,c,d,e,f,g,h){var i,j=e-2*c+a-(g-2*e+c),k=2*(c-a)-2*(e-c),l=a-c,m=(-k+M.sqrt(k*k-4*j*l))/2/j,n=(-k-M.sqrt(k*k-4*j*l))/2/j,o=[b,h],p=[a,g];return P(m)>"1e12"&&(m=.5),P(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ga(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ga(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),j=f-2*d+b-(h-2*f+d),k=2*(d-b)-2*(f-d),l=b-d,m=(-k+M.sqrt(k*k-4*j*l))/2/j,n=(-k-M.sqrt(k*k-4*j*l))/2/j,P(m)>"1e12"&&(m=.5),P(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ga(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ga(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),{min:{x:O[C](0,p),y:O[C](0,o)},max:{x:N[C](0,p),y:N[C](0,o)}}}),Ia=b._path2curve=e(function(a,b){var c=!b&&ya(a);if(!b&&c.curve)
 | 
				
			||||||
 | 
					return Aa(c.curve);for(var d=Ca(a),e=b&&Ca(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=(function(a,b,c){var d,e,f={T:1,Q:1};if(!a)
 | 
				
			||||||
 | 
					return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in f)&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][D](Fa[C](0,[b.x,b.y][D](a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e][D](a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"][D](Ea(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][D](Ea(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][D](Da(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][D](Da(b.x,b.y,a[1],b.y));break;case"V":a=["C"][D](Da(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][D](Da(b.x,b.y,b.X,b.Y))}
 | 
				
			||||||
 | 
					return a}),i=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)
 | 
				
			||||||
 | 
					k[b]="A",e&&(l[b]="A"),a.splice(b++,0,["C"][D](c.splice(0,6)));a.splice(b,1),p=N(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&"M"==a[g][0]&&"M"!=b[g][0]&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],p=N(d.length,e&&e.length||0))},k=[],l=[],m="",n="",o=0,p=N(d.length,e&&e.length||0);p>o;o++){d[o]&&(m=d[o][0]),"C"!=m&&(k[o]=m,o&&(n=k[o-1])),d[o]=h(d[o],f,n),"A"!=k[o]&&"C"==m&&(k[o]="C"),i(d,o),e&&(e[o]&&(m=e[o][0]),"C"!=m&&(l[o]=m,o&&(n=l[o-1])),e[o]=h(e[o],g,n),"A"!=l[o]&&"C"==m&&(l[o]="C"),i(e,o)),j(d,e,f,g,o),j(e,d,g,f,o);var q=d[o],r=e&&e[o],s=q.length,t=e&&r.length;f.x=q[s-2],f.y=q[s-1],f.bx=$(q[s-4])||f.x,f.by=$(q[s-3])||f.y,g.bx=e&&($(r[t-4])||g.x),g.by=e&&($(r[t-3])||g.y),g.x=e&&r[t-2],g.y=e&&r[t-1]}
 | 
				
			||||||
 | 
					return e||(c.curve=Aa(d)),e?[d,e]:d},null,Aa),Ja=(b._parseDots=e(function(a){for(var c=[],d=0,e=a.length;e>d;d++){var f={},g=a[d].match(/^([^:]*):?([\d\.]*)/);if(f.color=b.getRGB(g[1]),f.color.error)
 | 
				
			||||||
 | 
					return null;f.opacity=f.color.opacity,f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),c.push(f)}
 | 
				
			||||||
 | 
					for(d=1,e=c.length-1;e>d;d++)
 | 
				
			||||||
 | 
					if(!c[d].offset){for(var h=$(c[d-1].offset||0),i=0,j=d+1;e>j;j++)
 | 
				
			||||||
 | 
					if(c[j].offset){i=c[j].offset;break}
 | 
				
			||||||
 | 
					i||(i=100,j=e),i=$(i);for(var k=(i-h)/(j-d+1);j>d;d++)
 | 
				
			||||||
 | 
					h+=k,c[d].offset=h+"%"}
 | 
				
			||||||
 | 
					return c}),b._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)}),Ka=(b._tofront=function(a,b){b.top!==a&&(Ja(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},b._toback=function(a,b){b.bottom!==a&&(Ja(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},b._insertafter=function(a,b,c){Ja(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},b._insertbefore=function(a,b,c){Ja(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},b.toMatrix=function(a,b){var c=za(a),d={_:{transform:F},getBBox:function(){return c}};return La(d,b),d.matrix}),La=(b.transformPath=function(a,b){return pa(a,Ka(a,b))},b._extractTransform=function(a,c){if(null==c)
 | 
				
			||||||
 | 
					return a._.transform;c=H(c).replace(/\.{3}|\u2026/g,a._.transform||F);var d=b.parseTransformString(c),e=0,f=0,g=0,h=1,i=1,j=a._,k=new n;if(j.transform=d||[],d)
 | 
				
			||||||
 | 
					for(var l=0,m=d.length;m>l;l++){var o,p,q,r,s,t=d[l],u=t.length,v=H(t[0]).toLowerCase(),w=t[0]!=v,x=w?k.invert():0;"t"==v&&3==u?w?(o=x.x(0,0),p=x.y(0,0),q=x.x(t[1],t[2]),r=x.y(t[1],t[2]),k.translate(q-o,r-p)):k.translate(t[1],t[2]):"r"==v?2==u?(s=s||a.getBBox(1),k.rotate(t[1],s.x+s.width/2,s.y+s.height/2),e+=t[1]):4==u&&(w?(q=x.x(t[2],t[3]),r=x.y(t[2],t[3]),k.rotate(t[1],q,r)):k.rotate(t[1],t[2],t[3]),e+=t[1]):"s"==v?2==u||3==u?(s=s||a.getBBox(1),k.scale(t[1],t[u-1],s.x+s.width/2,s.y+s.height/2),h*=t[1],i*=t[u-1]):5==u&&(w?(q=x.x(t[3],t[4]),r=x.y(t[3],t[4]),k.scale(t[1],t[2],q,r)):k.scale(t[1],t[2],t[3],t[4]),h*=t[1],i*=t[2]):"m"==v&&7==u&&k.add(t[1],t[2],t[3],t[4],t[5],t[6]),j.dirtyT=1,a.matrix=k}
 | 
				
			||||||
 | 
					a.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,1==h&&1==i&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1}),Ma=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}},Na=b._equaliseTransform=function(a,c){c=H(c).replace(/\.{3}|\u2026/g,a),a=b.parseTransformString(a)||[],c=b.parseTransformString(c)||[];for(var d,e,f,g,h=N(a.length,c.length),i=[],j=[],k=0;h>k;k++){if(f=a[k]||Ma(c[k]),g=c[k]||Ma(f),f[0]!=g[0]||"r"==f[0].toLowerCase()&&(f[2]!=g[2]||f[3]!=g[3])||"s"==f[0].toLowerCase()&&(f[3]!=g[3]||f[4]!=g[4]))
 | 
				
			||||||
 | 
					return;for(i[k]=[],j[k]=[],d=0,e=N(f.length,g.length);e>d;d++)
 | 
				
			||||||
 | 
					d in f&&(i[k][d]=f[d]),d in g&&(j[k][d]=g[d])}
 | 
				
			||||||
 | 
					return{from:i,to:j}};b._getContainer=function(a,c,d,e){var f;return f=null!=e||b.is(a,"object")?a:z.doc.getElementById(a),null!=f?f.tagName?null==c?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:c,height:d}:{container:1,x:a,y:c,width:d,height:e}:void 0},b.pathToRelative=Ba,b._engine={},b.path2curve=Ia,b.matrix=function(a,b,c,d,e,f){return new n(a,b,c,d,e,f)},function(a){function c(a){return a[0]*a[0]+a[1]*a[1]}
 | 
				
			||||||
 | 
					function d(a){var b=M.sqrt(c(a));a[0]&&(a[0]/=b),a[1]&&(a[1]/=b)}
 | 
				
			||||||
 | 
					a.add=function(a,b,c,d,e,f){var g,h,i,j,k=[[],[],[]],l=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],m=[[a,c,e],[b,d,f],[0,0,1]];for(a&&a instanceof n&&(m=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),g=0;3>g;g++)
 | 
				
			||||||
 | 
					for(h=0;3>h;h++){for(j=0,i=0;3>i;i++)
 | 
				
			||||||
 | 
					j+=l[g][i]*m[i][h];k[g][h]=j}
 | 
				
			||||||
 | 
					this.a=k[0][0],this.b=k[1][0],this.c=k[0][1],this.d=k[1][1],this.e=k[0][2],this.f=k[1][2]},a.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new n(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},a.clone=function(){return new n(this.a,this.b,this.c,this.d,this.e,this.f)},a.translate=function(a,b){this.add(1,0,0,1,a,b)},a.scale=function(a,b,c,d){null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},a.rotate=function(a,c,d){a=b.rad(a),c=c||0,d=d||0;var e=+M.cos(a).toFixed(9),f=+M.sin(a).toFixed(9);this.add(e,f,-f,e,c,d),this.add(1,0,0,1,-c,-d)},a.x=function(a,b){return a*this.a+b*this.c+this.e},a.y=function(a,b){return a*this.b+b*this.d+this.f},a.get=function(a){return+this[H.fromCharCode(97+a)].toFixed(4)},a.toString=function(){return b.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},a.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},a.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},a.split=function(){var a={};a.dx=this.e,a.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];a.scalex=M.sqrt(c(e[0])),d(e[0]),a.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*a.shear,e[1][1]-e[0][1]*a.shear],a.scaley=M.sqrt(c(e[1])),d(e[1]),a.shear/=a.scaley;var f=-e[0][1],g=e[1][1];return 0>g?(a.rotate=b.deg(M.acos(g)),0>f&&(a.rotate=360-a.rotate)):a.rotate=b.deg(M.asin(f)),a.isSimple=!(+a.shear.toFixed(9)||a.scalex.toFixed(9)!=a.scaley.toFixed(9)&&a.rotate),a.isSuperSimple=!+a.shear.toFixed(9)&&a.scalex.toFixed(9)==a.scaley.toFixed(9)&&!a.rotate,a.noRotation=!+a.shear.toFixed(9)&&!a.rotate,a},a.toTransformString=function(a){var b=a||this[I]();return b.isSimple?(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[b.dx,b.dy]:F)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:F)+(b.rotate?"r"+[b.rotate,0,0]:F)):"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}
 | 
				
			||||||
 | 
					(n.prototype);for(var Oa=function(){this.returnValue=!1},Pa=function(){return this.originalEvent.preventDefault()},Qa=function(){this.cancelBubble=!0},Ra=function(){return this.originalEvent.stopPropagation()},Sa=function(a){var b=z.doc.documentElement.scrollTop||z.doc.body.scrollTop,c=z.doc.documentElement.scrollLeft||z.doc.body.scrollLeft;return{x:a.clientX+c,y:a.clientY+b}},Ta=function(){return z.doc.addEventListener?function(a,b,c,d){var e=function(a){var b=Sa(a);return c.call(d,a,b.x,b.y)};if(a.addEventListener(b,e,!1),E&&K[b]){var f=function(b){for(var e=Sa(b),f=b,g=0,h=b.targetTouches&&b.targetTouches.length;h>g;g++)
 | 
				
			||||||
 | 
					if(b.targetTouches[g].target==a){b=b.targetTouches[g],b.originalEvent=f,b.preventDefault=Pa,b.stopPropagation=Ra;break}
 | 
				
			||||||
 | 
					return c.call(d,b,e.x,e.y)};a.addEventListener(K[b],f,!1)}
 | 
				
			||||||
 | 
					return function(){return a.removeEventListener(b,e,!1),E&&K[b]&&a.removeEventListener(K[b],f,!1),!0}}:z.doc.attachEvent?function(a,b,c,d){var e=function(a){a=a||z.win.event;var b=z.doc.documentElement.scrollTop||z.doc.body.scrollTop,e=z.doc.documentElement.scrollLeft||z.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;return a.preventDefault=a.preventDefault||Oa,a.stopPropagation=a.stopPropagation||Qa,c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){return a.detachEvent("on"+b,e),!0};return f}:void 0}
 | 
				
			||||||
 | 
					(),Ua=[],Va=function(b){for(var c,d=b.clientX,e=b.clientY,f=z.doc.documentElement.scrollTop||z.doc.body.scrollTop,g=z.doc.documentElement.scrollLeft||z.doc.body.scrollLeft,h=Ua.length;h--;){if(c=Ua[h],E&&b.touches){for(var i,j=b.touches.length;j--;)
 | 
				
			||||||
 | 
					if(i=b.touches[j],i.identifier==c.el._drag.id){d=i.clientX,e=i.clientY,(b.originalEvent?b.originalEvent:b).preventDefault();break}}else
 | 
				
			||||||
 | 
					b.preventDefault();var k,l=c.el.node,m=l.nextSibling,n=l.parentNode,o=l.style.display;z.win.opera&&n.removeChild(l),l.style.display="none",k=c.el.paper.getElementByPoint(d,e),l.style.display=o,z.win.opera&&(m?n.insertBefore(l,m):n.appendChild(l)),k&&a("raphael.drag.over."+c.el.id,c.el,k),d+=g,e+=f,a("raphael.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,b)}},Wa=function(c){b.unmousemove(Va).unmouseup(Wa);for(var d,e=Ua.length;e--;)
 | 
				
			||||||
 | 
					d=Ua[e],d.el._drag={},a("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,c);Ua=[]},Xa=b.el={},Ya=J.length;Ya--;)
 | 
				
			||||||
 | 
					!function(a){b[a]=Xa[a]=function(c,d){return b.is(c,"function")&&(this.events=this.events||[],this.events.push({name:a,f:c,unbind:Ta(this.shape||this.node||z.doc,a,c,d||this)})),this},b["un"+a]=Xa["un"+a]=function(c){for(var d=this.events||[],e=d.length;e--;)
 | 
				
			||||||
 | 
					d[e].name!=a||!b.is(c,"undefined")&&d[e].f!=c||(d[e].unbind(),d.splice(e,1),!d.length&&delete this.events);return this}}
 | 
				
			||||||
 | 
					(J[Ya]);Xa.data=function(c,d){var e=ja[this.id]=ja[this.id]||{};if(0==arguments.length)
 | 
				
			||||||
 | 
					return e;if(1==arguments.length){if(b.is(c,"object")){for(var f in c)
 | 
				
			||||||
 | 
					c[y](f)&&this.data(f,c[f]);return this}
 | 
				
			||||||
 | 
					return a("raphael.data.get."+this.id,this,e[c],c),e[c]}
 | 
				
			||||||
 | 
					return e[c]=d,a("raphael.data.set."+this.id,this,d,c),this},Xa.removeData=function(a){return null==a?ja[this.id]={}:ja[this.id]&&delete ja[this.id][a],this},Xa.getData=function(){return c(ja[this.id]||{})},Xa.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},Xa.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var Za=[];Xa.drag=function(c,d,e,f,g,h){function i(i){(i.originalEvent||i).preventDefault();var j=i.clientX,k=i.clientY,l=z.doc.documentElement.scrollTop||z.doc.body.scrollTop,m=z.doc.documentElement.scrollLeft||z.doc.body.scrollLeft;if(this._drag.id=i.identifier,E&&i.touches)
 | 
				
			||||||
 | 
					for(var n,o=i.touches.length;o--;)
 | 
				
			||||||
 | 
					if(n=i.touches[o],this._drag.id=n.identifier,n.identifier==this._drag.id){j=n.clientX,k=n.clientY;break}
 | 
				
			||||||
 | 
					this._drag.x=j+m,this._drag.y=k+l,!Ua.length&&b.mousemove(Va).mouseup(Wa),Ua.push({el:this,move_scope:f,start_scope:g,end_scope:h}),d&&a.on("raphael.drag.start."+this.id,d),c&&a.on("raphael.drag.move."+this.id,c),e&&a.on("raphael.drag.end."+this.id,e),a("raphael.drag.start."+this.id,g||f||this,i.clientX+m,i.clientY+l,i)}
 | 
				
			||||||
 | 
					return this._drag={},Za.push({el:this,start:i}),this.mousedown(i),this},Xa.onDragOver=function(b){b?a.on("raphael.drag.over."+this.id,b):a.unbind("raphael.drag.over."+this.id)},Xa.undrag=function(){for(var c=Za.length;c--;)
 | 
				
			||||||
 | 
					Za[c].el==this&&(this.unmousedown(Za[c].start),Za.splice(c,1),a.unbind("raphael.drag.*."+this.id));!Za.length&&b.unmousemove(Va).unmouseup(Wa),Ua=[]},u.circle=function(a,c,d){var e=b._engine.circle(this,a||0,c||0,d||0);return this.__set__&&this.__set__.push(e),e},u.rect=function(a,c,d,e,f){var g=b._engine.rect(this,a||0,c||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},u.ellipse=function(a,c,d,e){var f=b._engine.ellipse(this,a||0,c||0,d||0,e||0);return this.__set__&&this.__set__.push(f),f},u.path=function(a){a&&!b.is(a,T)&&!b.is(a[0],U)&&(a+=F);var c=b._engine.path(b.format[C](b,arguments),this);return this.__set__&&this.__set__.push(c),c},u.image=function(a,c,d,e,f){var g=b._engine.image(this,a||"about:blank",c||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},u.text=function(a,c,d){var e=b._engine.text(this,a||0,c||0,H(d));return this.__set__&&this.__set__.push(e),e},u.set=function(a){!b.is(a,"array")&&(a=Array.prototype.splice.call(arguments,0,arguments.length));var c=new jb(a);return this.__set__&&this.__set__.push(c),c.paper=this,c.type="set",c},u.setStart=function(a){this.__set__=a||this.set()},u.setFinish=function(a){var b=this.__set__;return delete this.__set__,b},u.getSize=function(){var a=this.canvas.parentNode;return{width:a.offsetWidth,height:a.offsetHeight}},u.setSize=function(a,c){return b._engine.setSize.call(this,a,c)},u.setViewBox=function(a,c,d,e,f){return b._engine.setViewBox.call(this,a,c,d,e,f)},u.top=u.bottom=null,u.raphael=b;var $a=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,h=b.top+(z.win.pageYOffset||e.scrollTop||d.scrollTop)-f,i=b.left+(z.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:h,x:i}};u.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=z.doc.elementFromPoint(a,b);if(z.win.opera&&"svg"==e.tagName){var f=$a(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var h=d.getIntersectionList(g,null);h.length&&(e=h[h.length-1])}
 | 
				
			||||||
 | 
					if(!e)
 | 
				
			||||||
 | 
					return null;for(;e.parentNode&&e!=d.parentNode&&!e.raphael;)
 | 
				
			||||||
 | 
					e=e.parentNode;return e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null},u.getElementsByBBox=function(a){var c=this.set();return this.forEach(function(d){b.isBBoxIntersect(d.getBBox(),a)&&c.push(d)}),c},u.getById=function(a){for(var b=this.bottom;b;){if(b.id==a)
 | 
				
			||||||
 | 
					return b;b=b.next}
 | 
				
			||||||
 | 
					return null},u.forEach=function(a,b){for(var c=this.bottom;c;){if(a.call(b,c)===!1)
 | 
				
			||||||
 | 
					return this;c=c.next}
 | 
				
			||||||
 | 
					return this},u.getElementsByPoint=function(a,b){var c=this.set();return this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)}),c},Xa.isPointInside=function(a,c){var d=this.realPath=oa[this.type](this);return this.attr("transform")&&this.attr("transform").length&&(d=b.transformPath(d,this.attr("transform"))),b.isPointInsidePath(d,a,c)},Xa.getBBox=function(a){if(this.removed)
 | 
				
			||||||
 | 
					return{};var b=this._;return a?((b.dirty||!b.bboxwt)&&(this.realPath=oa[this.type](this),b.bboxwt=za(this.realPath),b.bboxwt.toString=o,b.dirty=0),b.bboxwt):((b.dirty||b.dirtyT||!b.bbox)&&((b.dirty||!this.realPath)&&(b.bboxwt=0,this.realPath=oa[this.type](this)),b.bbox=za(pa(this.realPath,this.matrix)),b.bbox.toString=o,b.dirty=b.dirtyT=0),b.bbox)},Xa.clone=function(){if(this.removed)
 | 
				
			||||||
 | 
					return null;var a=this.paper[this.type]().attr(this.attr());return this.__set__&&this.__set__.push(a),a},Xa.glow=function(a){if("text"==this.type)
 | 
				
			||||||
 | 
					return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:null==a.opacity?.5:a.opacity,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||oa[this.type](this);f=this.matrix?pa(f,this.matrix):f;for(var g=1;c+1>g;g++)
 | 
				
			||||||
 | 
					e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var _a=function(a,c,d,e,f,g,h,k,l){return null==l?i(a,c,d,e,f,g,h,k):b.findDotsAtSegment(a,c,d,e,f,g,h,k,j(a,c,d,e,f,g,h,k,l))},ab=function(a,c){return function(d,e,f){d=Ia(d);for(var g,h,i,j,k,l="",m={},n=0,o=0,p=d.length;p>o;o++){if(i=d[o],"M"==i[0])
 | 
				
			||||||
 | 
					g=+i[1],h=+i[2];else{if(j=_a(g,h,i[1],i[2],i[3],i[4],i[5],i[6]),n+j>e){if(c&&!m.start){if(k=_a(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),l+=["C"+k.start.x,k.start.y,k.m.x,k.m.y,k.x,k.y],f)
 | 
				
			||||||
 | 
					return l;m.start=l,l=["M"+k.x,k.y+"C"+k.n.x,k.n.y,k.end.x,k.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}
 | 
				
			||||||
 | 
					if(!a&&!c)
 | 
				
			||||||
 | 
					return k=_a(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),{x:k.x,y:k.y,alpha:k.alpha}}
 | 
				
			||||||
 | 
					n+=j,g=+i[5],h=+i[6]}
 | 
				
			||||||
 | 
					l+=i.shift()+i}
 | 
				
			||||||
 | 
					return m.end=l,k=a?n:c?m:b.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),k.alpha&&(k={x:k.x,y:k.y,alpha:k.alpha}),k}},bb=ab(1),cb=ab(),db=ab(0,1);b.getTotalLength=bb,b.getPointAtLength=cb,b.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)
 | 
				
			||||||
 | 
					return db(a,b).end;var d=db(a,c,1);return b?db(d,b).end:d},Xa.getTotalLength=function(){var a=this.getPath();if(a)
 | 
				
			||||||
 | 
					return this.node.getTotalLength?this.node.getTotalLength():bb(a)},Xa.getPointAtLength=function(a){var b=this.getPath();if(b)
 | 
				
			||||||
 | 
					return cb(b,a)},Xa.getPath=function(){var a,c=b._getPath[this.type];if("text"!=this.type&&"set"!=this.type)
 | 
				
			||||||
 | 
					return c&&(a=c(this)),a},Xa.getSubpath=function(a,c){var d=this.getPath();if(d)
 | 
				
			||||||
 | 
					return b.getSubpath(d,a,c)};var eb=b.easing_formulas={linear:function(a){return a},"<":function(a){return Q(a,1.7)},">":function(a){return Q(a,.48)},"<>":function(a){var b=.48-a/1.04,c=M.sqrt(.1734+b*b),d=c-b,e=Q(P(d),1/3)*(0>d?-1:1),f=-c-b,g=Q(P(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){return a==!!a?a:Q(2,-10*a)*M.sin(2*(a-.075)*R/.3)+1},bounce:function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b}};eb.easeIn=eb["ease-in"]=eb["<"],eb.easeOut=eb["ease-out"]=eb[">"],eb.easeInOut=eb["ease-in-out"]=eb["<>"],eb["back-in"]=eb.backIn,eb["back-out"]=eb.backOut;var fb=[],gb=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},hb=function(){for(var c=+new Date,d=0;d<fb.length;d++){var e=fb[d];if(!e.el.removed&&!e.paused){var f,g,h=c-e.start,i=e.ms,j=e.easing,k=e.from,l=e.diff,m=e.to,n=(e.t,e.el),o={},p={};if(e.initstatus?(h=(e.initstatus*e.anim.top-e.prev)/(e.percent-e.prev)*i,e.status=e.initstatus,delete e.initstatus,e.stop&&fb.splice(d--,1)):e.status=(e.prev+(e.percent-e.prev)*(h/i))/e.anim.top,!(0>h))
 | 
				
			||||||
 | 
					if(i>h){var q=j(h/i);for(var s in k)
 | 
				
			||||||
 | 
					if(k[y](s)){switch(ca[s]){case S:f=+k[s]+q*i*l[s];break;case"colour":f="rgb("+[ib(Z(k[s].r+q*i*l[s].r)),ib(Z(k[s].g+q*i*l[s].g)),ib(Z(k[s].b+q*i*l[s].b))].join(",")+")";break;case"path":f=[];for(var t=0,u=k[s].length;u>t;t++){f[t]=[k[s][t][0]];for(var v=1,w=k[s][t].length;w>v;v++)
 | 
				
			||||||
 | 
					f[t][v]=+k[s][t][v]+q*i*l[s][t][v];f[t]=f[t].join(G)}
 | 
				
			||||||
 | 
					f=f.join(G);break;case"transform":if(l[s].real)
 | 
				
			||||||
 | 
					for(f=[],t=0,u=k[s].length;u>t;t++)
 | 
				
			||||||
 | 
					for(f[t]=[k[s][t][0]],v=1,w=k[s][t].length;w>v;v++)
 | 
				
			||||||
 | 
					f[t][v]=k[s][t][v]+q*i*l[s][t][v];else{var x=function(a){return+k[s][a]+q*i*l[s][a]};f=[["m",x(0),x(1),x(2),x(3),x(4),x(5)]]}
 | 
				
			||||||
 | 
					break;case"csv":if("clip-rect"==s)
 | 
				
			||||||
 | 
					for(f=[],t=4;t--;)
 | 
				
			||||||
 | 
					f[t]=+k[s][t]+q*i*l[s][t];break;default:var z=[][D](k[s]);for(f=[],t=n.paper.customAttributes[s].length;t--;)
 | 
				
			||||||
 | 
					f[t]=+z[t]+q*i*l[s][t]}
 | 
				
			||||||
 | 
					o[s]=f}
 | 
				
			||||||
 | 
					n.attr(o),function(b,c,d){setTimeout(function(){a("raphael.anim.frame."+b,c,d)})}
 | 
				
			||||||
 | 
					(n.id,n,e.anim)}else{if(function(c,d,e){setTimeout(function(){a("raphael.anim.frame."+d.id,d,e),a("raphael.anim.finish."+d.id,d,e),b.is(c,"function")&&c.call(d)})}
 | 
				
			||||||
 | 
					(e.callback,n,e.anim),n.attr(m),fb.splice(d--,1),e.repeat>1&&!e.next){for(g in m)
 | 
				
			||||||
 | 
					m[y](g)&&(p[g]=e.totalOrigin[g]);e.el.attr(p),r(e.anim,e.el,e.anim.percents[0],null,e.totalOrigin,e.repeat-1)}
 | 
				
			||||||
 | 
					e.next&&!e.stop&&r(e.anim,e.el,e.next,null,e.totalOrigin,e.repeat)}}}
 | 
				
			||||||
 | 
					fb.length&&gb(hb)},ib=function(a){return a>255?255:0>a?0:a};Xa.animateWith=function(a,c,d,e,f,g){var h=this;if(h.removed)
 | 
				
			||||||
 | 
					return g&&g.call(h),h;var i=d instanceof q?d:b.animation(d,e,f,g);r(i,h,i.percents[0],null,h.attr());for(var j=0,k=fb.length;k>j;j++)
 | 
				
			||||||
 | 
					if(fb[j].anim==c&&fb[j].el==a){fb[k-1].start=fb[j].start;break}
 | 
				
			||||||
 | 
					return h},Xa.onAnimation=function(b){return b?a.on("raphael.anim.frame."+this.id,b):a.unbind("raphael.anim.frame."+this.id),this},q.prototype.delay=function(a){var b=new q(this.anim,this.ms);return b.times=this.times,b.del=+a||0,b},q.prototype.repeat=function(a){var b=new q(this.anim,this.ms);return b.del=this.del,b.times=M.floor(N(a,0))||1,b},b.animation=function(a,c,d,e){if(a instanceof q)
 | 
				
			||||||
 | 
					return a;(b.is(d,"function")||!d)&&(e=e||d||null,d=null),a=Object(a),c=+c||0;var f,g,h={};for(g in a)
 | 
				
			||||||
 | 
					a[y](g)&&$(g)!=g&&$(g)+"%"!=g&&(f=!0,h[g]=a[g]);if(f)
 | 
				
			||||||
 | 
					return d&&(h.easing=d),e&&(h.callback=e),new q({100:h},c);if(e){var i=0;for(var j in a){var k=_(j);a[y](j)&&k>i&&(i=k)}
 | 
				
			||||||
 | 
					i+="%",!a[i].callback&&(a[i].callback=e)}
 | 
				
			||||||
 | 
					return new q(a,c)},Xa.animate=function(a,c,d,e){var f=this;if(f.removed)
 | 
				
			||||||
 | 
					return e&&e.call(f),f;var g=a instanceof q?a:b.animation(a,c,d,e);return r(g,f,g.percents[0],null,f.attr()),f},Xa.setTime=function(a,b){return a&&null!=b&&this.status(a,O(b,a.ms)/a.ms),this},Xa.status=function(a,b){var c,d,e=[],f=0;if(null!=b)
 | 
				
			||||||
 | 
					return r(a,this,-1,O(b,1)),this;for(c=fb.length;c>f;f++)
 | 
				
			||||||
 | 
					if(d=fb[f],d.el.id==this.id&&(!a||d.anim==a)){if(a)
 | 
				
			||||||
 | 
					return d.status;e.push({anim:d.anim,status:d.status})}
 | 
				
			||||||
 | 
					return a?0:e},Xa.pause=function(b){for(var c=0;c<fb.length;c++)
 | 
				
			||||||
 | 
					fb[c].el.id!=this.id||b&&fb[c].anim!=b||a("raphael.anim.pause."+this.id,this,fb[c].anim)!==!1&&(fb[c].paused=!0);return this},Xa.resume=function(b){for(var c=0;c<fb.length;c++)
 | 
				
			||||||
 | 
					if(fb[c].el.id==this.id&&(!b||fb[c].anim==b)){var d=fb[c];a("raphael.anim.resume."+this.id,this,d.anim)!==!1&&(delete d.paused,this.status(d.anim,d.status))}
 | 
				
			||||||
 | 
					return this},Xa.stop=function(b){for(var c=0;c<fb.length;c++)
 | 
				
			||||||
 | 
					fb[c].el.id!=this.id||b&&fb[c].anim!=b||a("raphael.anim.stop."+this.id,this,fb[c].anim)!==!1&&fb.splice(c--,1);return this},a.on("raphael.remove",s),a.on("raphael.clear",s),Xa.toString=function(){return"Raphaël’s object"};var jb=function(a){if(this.items=[],this.length=0,this.type="set",a)
 | 
				
			||||||
 | 
					for(var b=0,c=a.length;c>b;b++)
 | 
				
			||||||
 | 
					!a[b]||a[b].constructor!=Xa.constructor&&a[b].constructor!=jb||(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},kb=jb.prototype;kb.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)
 | 
				
			||||||
 | 
					a=arguments[c],!a||a.constructor!=Xa.constructor&&a.constructor!=jb||(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},kb.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},kb.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)
 | 
				
			||||||
 | 
					if(a.call(b,this.items[c],c)===!1)
 | 
				
			||||||
 | 
					return this;return this};for(var lb in Xa)
 | 
				
			||||||
 | 
					Xa[y](lb)&&(kb[lb]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][C](c,b)})}}
 | 
				
			||||||
 | 
					(lb));return kb.attr=function(a,c){if(a&&b.is(a,U)&&b.is(a[0],"object"))
 | 
				
			||||||
 | 
					for(var d=0,e=a.length;e>d;d++)
 | 
				
			||||||
 | 
					this.items[d].attr(a[d]);else
 | 
				
			||||||
 | 
					for(var f=0,g=this.items.length;g>f;f++)
 | 
				
			||||||
 | 
					this.items[f].attr(a,c);return this},kb.clear=function(){for(;this.length;)
 | 
				
			||||||
 | 
					this.pop()},kb.splice=function(a,b,c){a=0>a?N(this.length+a,0):a,b=N(0,O(this.length-a,b));var d,e=[],f=[],g=[];for(d=2;d<arguments.length;d++)
 | 
				
			||||||
 | 
					g.push(arguments[d]);for(d=0;b>d;d++)
 | 
				
			||||||
 | 
					f.push(this[a+d]);for(;d<this.length-a;d++)
 | 
				
			||||||
 | 
					e.push(this[a+d]);var h=g.length;for(d=0;d<h+e.length;d++)
 | 
				
			||||||
 | 
					this.items[a+d]=this[a+d]=h>d?g[d]:e[d-h];for(d=this.items.length=this.length-=b-h;this[d];)
 | 
				
			||||||
 | 
					delete this[d++];return new jb(f)},kb.exclude=function(a){for(var b=0,c=this.length;c>b;b++)
 | 
				
			||||||
 | 
					if(this[b]==a)
 | 
				
			||||||
 | 
					return this.splice(b,1),!0},kb.animate=function(a,c,d,e){(b.is(d,"function")||!d)&&(e=d||null);var f,g,h=this.items.length,i=h,j=this;if(!h)
 | 
				
			||||||
 | 
					return this;e&&(g=function(){!--h&&e.call(j)}),d=b.is(d,T)?d:g;var k=b.animation(a,c,d,g);for(f=this.items[--i].animate(k);i--;)
 | 
				
			||||||
 | 
					this.items[i]&&!this.items[i].removed&&this.items[i].animateWith(f,k,k),this.items[i]&&!this.items[i].removed||h--;return this},kb.insertAfter=function(a){for(var b=this.items.length;b--;)
 | 
				
			||||||
 | 
					this.items[b].insertAfter(a);return this},kb.getBBox=function(){for(var a=[],b=[],c=[],d=[],e=this.items.length;e--;)
 | 
				
			||||||
 | 
					if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}
 | 
				
			||||||
 | 
					return a=O[C](0,a),b=O[C](0,b),c=N[C](0,c),d=N[C](0,d),{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},kb.clone=function(a){a=this.paper.set();for(var b=0,c=this.items.length;c>b;b++)
 | 
				
			||||||
 | 
					a.push(this.items[b].clone());return a},kb.toString=function(){return"Raphaël‘s set"},kb.glow=function(a){var b=this.paper.set();return this.forEach(function(c,d){var e=c.glow(a);null!=e&&e.forEach(function(a,c){b.push(a)})}),b},kb.isPointInside=function(a,b){var c=!1;return this.forEach(function(d){return d.isPointInside(a,b)?(c=!0,!1):void 0}),c},b.registerFont=function(a){if(!a.face)
 | 
				
			||||||
 | 
					return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)
 | 
				
			||||||
 | 
					a.face[y](d)&&(b.face[d]=a.face[d]);if(this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b],!a.svg){b.face["units-per-em"]=_(a.face["units-per-em"],10);for(var e in a.glyphs)
 | 
				
			||||||
 | 
					if(a.glyphs[y](e)){var f=a.glyphs[e];if(b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}
 | 
				
			||||||
 | 
					[a]||"M"})+"z"},f.k)
 | 
				
			||||||
 | 
					for(var g in f.k)
 | 
				
			||||||
 | 
					f[y](g)&&(b.glyphs[e].k[g]=f.k[g])}}
 | 
				
			||||||
 | 
					return a},u.getFont=function(a,c,d,e){if(e=e||"normal",d=d||"normal",c=+c||{normal:400,bold:700,lighter:300,bolder:800}
 | 
				
			||||||
 | 
					[c]||400,b.fonts){var f=b.fonts[a];if(!f){var g=new RegExp("(^|\\s)"+a.replace(/[^\w\d\s+!~.:_-]/g,F)+"(\\s|$)","i");for(var h in b.fonts)
 | 
				
			||||||
 | 
					if(b.fonts[y](h)&&g.test(h)){f=b.fonts[h];break}}
 | 
				
			||||||
 | 
					var i;if(f)
 | 
				
			||||||
 | 
					for(var j=0,k=f.length;k>j&&(i=f[j],i.face["font-weight"]!=c||i.face["font-style"]!=d&&i.face["font-style"]||i.face["font-stretch"]!=e);j++);return i}},u.print=function(a,c,d,e,f,g,h,i){g=g||"middle",h=N(O(h||0,1),-1),i=N(O(i||1,3),1);var j,k=H(d)[I](F),l=0,m=0,n=F;if(b.is(e,"string")&&(e=this.getFont(e)),e){j=(f||16)/e.face["units-per-em"];for(var o=e.face.bbox[I](v),p=+o[0],q=o[3]-o[1],r=0,s=+o[1]+("baseline"==g?q+ +e.face.descent:q/2),t=0,u=k.length;u>t;t++){if("\n"==k[t])
 | 
				
			||||||
 | 
					l=0,x=0,m=0,r+=q*i;else{var w=m&&e.glyphs[k[t-1]]||{},x=e.glyphs[k[t]];l+=m?(w.w||e.w)+(w.k&&w.k[k[t]]||0)+e.w*h:0,m=1}
 | 
				
			||||||
 | 
					x&&x.d&&(n+=b.transformPath(x.d,["t",l*j,r*j,"s",j,j,p,s,"t",(a-p)/j,(c-s)/j]))}}
 | 
				
			||||||
 | 
					return this.path(n).attr({fill:"#000",stroke:"none"})},u.add=function(a){if(b.is(a,"array"))
 | 
				
			||||||
 | 
					for(var c,d=this.set(),e=0,f=a.length;f>e;e++)
 | 
				
			||||||
 | 
					c=a[e]||{},w[y](c.type)&&d.push(this[c.type]().attr(c));return d},b.format=function(a,c){var d=b.is(c,U)?[0][D](c):arguments;return a&&b.is(a,T)&&d.length-1&&(a=a.replace(x,function(a,b){return null==d[++b]?F:d[b]})),a||F},b.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}
 | 
				
			||||||
 | 
					(),b.ninja=function(){return A.was?z.win.Raphael=A.is:delete Raphael,b},b.st=kb,a.on("raphael.DOMload",function(){t=!0}),function(a,c,d){function e(){/in/.test(a.readyState)?setTimeout(e,9):b.eve("raphael.DOMload")}
 | 
				
			||||||
 | 
					null==a.readyState&&a.addEventListener&&(a.addEventListener(c,d=function(){a.removeEventListener(c,d,!1),a.readyState="complete"},!1),a.readyState="loading"),e()}
 | 
				
			||||||
 | 
					(document,"DOMContentLoaded"),b}),function(a,b){"function"==typeof define&&define.amd?define("raphael.svg",["raphael.core"],function(a){return b(a)}):b("object"==typeof exports?require("./raphael.core"):a.Raphael)}
 | 
				
			||||||
 | 
					(this,function(a){if(!a||a.svg){var b="hasOwnProperty",c=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=a.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};a.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){"string"==typeof d&&(d=q(d));for(var f in e)
 | 
				
			||||||
 | 
					e[b](f)&&("xlink:"==f.substring(0,6)?d.setAttributeNS(n,f.substring(6),c(e[f])):d.setAttribute(f,c(e[f])))}else
 | 
				
			||||||
 | 
					d=a._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(b,e){var j="linear",k=b.id+e,m=.5,n=.5,o=b.node,p=b.paper,r=o.style,s=a._g.doc.getElementById(k);if(!s){if(e=c(e).replace(a._radial_gradient,function(a,b,c){if(j="radial",b&&c){m=d(b),n=d(c);var e=2*(n>.5)-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&.5!=n&&(n=n.toFixed(5)-1e-5*e)}
 | 
				
			||||||
 | 
					return l}),e=e.split(/\s*\-\s*/),"linear"==j){var t=e.shift();if(t=-d(t),isNaN(t))
 | 
				
			||||||
 | 
					return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}
 | 
				
			||||||
 | 
					var w=a._parseDots(e);if(!w)
 | 
				
			||||||
 | 
					return null;if(k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient),!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,"radial"==j?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;y>x;x++)
 | 
				
			||||||
 | 
					s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff","stop-opacity":isFinite(w[x].opacity)?w[x].opacity:1}))}}
 | 
				
			||||||
 | 
					return q(o,{fill:"url('"+document.location.origin+document.location.pathname+"#"+k+"')",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1,1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if("path"==d.type){for(var g,h,i,j,k,m=c(e).toLowerCase().split("-"),n=d.paper,r=f?"end":"start",s=d.node,t=d.attrs,u=t["stroke-width"],v=m.length,w="classic",x=3,y=3,z=5;v--;)
 | 
				
			||||||
 | 
					switch(m[v]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":w=m[v];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}
 | 
				
			||||||
 | 
					if("open"==w?(x+=2,y+=2,z+=2,i=1,j=f?4:1,k={fill:"none",stroke:t.stroke}):(j=i=x/2,k={fill:t.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={},"none"!=w){var A="raphael-marker-"+w,B="raphael-marker-"+r+w+x+y+"-obj"+d.id;a._g.doc.getElementById(A)?p[A]++:(n.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[w],id:A})),p[A]=1);var C,D=a._g.doc.getElementById(B);D?(p[B]++,C=D.getElementsByTagName("use")[0]):(D=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:j,refY:y/2}),C=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),D.appendChild(C),n.defs.appendChild(D),p[B]=1),q(C,k);var E=i*("diamond"!=w&&"oval"!=w);f?(g=d._.arrows.startdx*u||0,h=a.getTotalLength(t.path)-E*u):(g=E*u,h=a.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),k={},k["marker-"+r]="url(#"+B+")",(h||g)&&(k.d=a.getSubpath(t.path,g,h)),q(s,k),d._.arrows[r+"Path"]=A,d._.arrows[r+"Marker"]=B,d._.arrows[r+"dx"]=E,d._.arrows[r+"Type"]=w,d._.arrows[r+"String"]=e}else
 | 
				
			||||||
 | 
					f?(g=d._.arrows.startdx*u||0,h=a.getTotalLength(t.path)-g):(g=0,h=a.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),d._.arrows[r+"Path"]&&q(s,{d:a.getSubpath(t.path,g,h)}),delete d._.arrows[r+"Path"],delete d._.arrows[r+"Marker"],delete d._.arrows[r+"dx"],delete d._.arrows[r+"Type"],delete d._.arrows[r+"String"];for(k in p)
 | 
				
			||||||
 | 
					if(p[b](k)&&!p[k]){var F=a._g.doc.getElementById(k);F&&F.parentNode.removeChild(F)}}},u={"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,b,d){if(b=u[c(b).toLowerCase()]){for(var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}
 | 
				
			||||||
 | 
					[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=b.length;h--;)
 | 
				
			||||||
 | 
					g[h]=b[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}else
 | 
				
			||||||
 | 
					q(a.node,{"stroke-dasharray":"none"})},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)
 | 
				
			||||||
 | 
					if(f[b](o)){if(!a._availableAttrs[b](o))
 | 
				
			||||||
 | 
					continue;var p=f[o];switch(k[o]=p,o){case"blur":d.blur(p);break;case"title":var u=i.getElementsByTagName("title");if(u.length&&(u=u[0]))
 | 
				
			||||||
 | 
					u.firstChild.nodeValue=p;else{u=q("title");var w=a._g.doc.createTextNode(p);u.appendChild(w),i.appendChild(u)}
 | 
				
			||||||
 | 
					break;case"href":case"target":var x=i.parentNode;if("a"!=x.tagName.toLowerCase()){var z=q("a");x.insertBefore(z,i),z.appendChild(i),x=z}"target"==o?x.setAttributeNS(n,"show","blank"==p?"new":p):x.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var A=c(p).split(j);if(4==A.length){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var B=q("clipPath"),C=q("rect");B.id=a.createUUID(),q(C,{x:A[0],y:A[1],width:A[2],height:A[3]}),B.appendChild(C),d.paper.defs.appendChild(B),q(i,{"clip-path":"url(#"+B.id+")"}),d.clip=C}
 | 
				
			||||||
 | 
					if(!p){var D=i.getAttribute("clip-path");if(D){var E=a._g.doc.getElementById(D.replace(/(^url\(#|\)$)/g,l));E&&E.parentNode.removeChild(E),q(i,{"clip-path":l}),delete d.clip}}
 | 
				
			||||||
 | 
					break;case"path":"path"==d.type&&(q(i,{d:p?k.path=a._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":if(i.setAttribute(o,p),d._.dirty=1,!k.fx)
 | 
				
			||||||
 | 
					break;o="x",p=k.x;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if("rx"==o&&"rect"==d.type)
 | 
				
			||||||
 | 
					break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":if(i.setAttribute(o,p),d._.dirty=1,!k.fy)
 | 
				
			||||||
 | 
					break;o="y",p=k.y;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if("ry"==o&&"rect"==d.type)
 | 
				
			||||||
 | 
					break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":"rect"==d.type?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":"image"==d.type&&i.setAttributeNS(n,"href",p);break;case"stroke-width":(1!=d._.sx||1!=d._.sy)&&(p/=g(h(d._.sx),h(d._.sy))||1),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var F=c(p).match(a._ISURL);if(F){B=q("pattern");var G=q("image");B.id=a.createUUID(),q(B,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(G,{x:0,y:0,"xlink:href":F[1]}),B.appendChild(G),function(b){a._preload(F[1],function(){var a=this.offsetWidth,c=this.offsetHeight;q(b,{width:a,height:c}),q(G,{width:a,height:c})})}
 | 
				
			||||||
 | 
					(B),d.paper.defs.appendChild(B),q(i,{fill:"url(#"+B.id+")"}),d.pattern=B,d.pattern&&s(d);break}
 | 
				
			||||||
 | 
					var H=a.getRGB(p);if(H.error){if(("circle"==d.type||"ellipse"==d.type||"r"!=c(p).charAt())&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var I=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(I){var J=I.getElementsByTagName("stop");q(J[J.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}
 | 
				
			||||||
 | 
					k.gradient=p,k.fill="none";break}}else
 | 
				
			||||||
 | 
					delete f.gradient,delete k.gradient,!a.is(k.opacity,"undefined")&&a.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!a.is(k["fill-opacity"],"undefined")&&a.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});H[b]("opacity")&&q(i,{"fill-opacity":H.opacity>1?H.opacity/100:H.opacity});case"stroke":H=a.getRGB(p),i.setAttribute(o,H.hex),"stroke"==o&&H[b]("opacity")&&q(i,{"stroke-opacity":H.opacity>1?H.opacity/100:H.opacity}),"stroke"==o&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":("circle"==d.type||"ellipse"==d.type||"r"!=c(p).charAt())&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){I=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),I&&(J=I.getElementsByTagName("stop"),q(J[J.length-1],{"stop-opacity":p}));break}
 | 
				
			||||||
 | 
					default:"font-size"==o&&(p=e(p,10)+"px");var K=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[K]=p,d._.dirty=1,i.setAttribute(o,p)}}
 | 
				
			||||||
 | 
					y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if("text"==d.type&&(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){for(g.text=f.text;h.firstChild;)
 | 
				
			||||||
 | 
					h.removeChild(h.firstChild);for(var j,k=c(f.text).split("\n"),m=[],n=0,o=k.length;o>n;n++)
 | 
				
			||||||
 | 
					j=q("tspan"),n&&q(j,{dy:i*x,x:g.x}),j.appendChild(a._g.doc.createTextNode(k[n])),h.appendChild(j),m[n]=j}else
 | 
				
			||||||
 | 
					for(m=h.getElementsByTagName("tspan"),n=0,o=m.length;o>n;n++)
 | 
				
			||||||
 | 
					n?q(m[n],{dy:i*x,x:g.x}):q(m[0],{dy:0});q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&a.is(r,"finite")&&q(m[0],{dy:r})}},z=function(a){return a.parentNode&&"a"===a.parentNode.tagName.toLowerCase()?a.parentNode:a},A=function(b,c){this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.matrix=a.matrix(),this.realPath=null,this.paper=c,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},B=a.el;A.prototype=B,B.constructor=A,a._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new A(c,b);return d.type="path",w(d,{fill:"none",stroke:"#000",path:a}),d},B.rotate=function(a,b,e){if(this.removed)
 | 
				
			||||||
 | 
					return this;if(a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(b=e),null==b||null==e){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}
 | 
				
			||||||
 | 
					return this.transform(this._.transform.concat([["r",a,b,e]])),this},B.scale=function(a,b,e,f){if(this.removed)
 | 
				
			||||||
 | 
					return this;if(a=c(a).split(j),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),null==b&&(b=a),null==f&&(e=f),null==e||null==f)
 | 
				
			||||||
 | 
					var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]])),this},B.translate=function(a,b){return this.removed?this:(a=c(a).split(j),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this.transform(this._.transform.concat([["t",a,b]])),this)},B.transform=function(c){var d=this._;if(null==c)
 | 
				
			||||||
 | 
					return d.transform;if(a._extractTransform(this,c),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix}),1!=d.sx||1!=d.sy){var e=this.attrs[b]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}
 | 
				
			||||||
 | 
					return this},B.hide=function(){return this.removed||(this.node.style.display="none"),this},B.show=function(){return this.removed||(this.node.style.display=""),this},B.remove=function(){var b=z(this.node);if(!this.removed&&b.parentNode){var c=this.paper;c.__set__&&c.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&c.defs.removeChild(this.gradient),a._tear(this,c),b.parentNode.removeChild(b),this.removeData();for(var d in this)
 | 
				
			||||||
 | 
					this[d]="function"==typeof this[d]?a._removedFactory(d):null;this.removed=!0}},B._getBBox=function(){if("none"==this.node.style.display){this.show();var a=!0}
 | 
				
			||||||
 | 
					var b,c=!1;this.paper.canvas.parentElement?b=this.paper.canvas.parentElement.style:this.paper.canvas.parentNode&&(b=this.paper.canvas.parentNode.style),b&&"none"==b.display&&(c=!0,b.display="");var d={};try{d=this.node.getBBox()}catch(e){d={x:this.node.clientLeft,y:this.node.clientTop,width:this.node.clientWidth,height:this.node.clientHeight}}
 | 
				
			||||||
 | 
					finally{d=d||{},c&&(b.display="none")}
 | 
				
			||||||
 | 
					return a&&this.hide(),d},B.attr=function(c,d){if(this.removed)
 | 
				
			||||||
 | 
					return this;if(null==c){var e={};for(var f in this.attrs)
 | 
				
			||||||
 | 
					this.attrs[b](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}
 | 
				
			||||||
 | 
					if(null==d&&a.is(c,"string")){if("fill"==c&&"none"==this.attrs.fill&&this.attrs.gradient)
 | 
				
			||||||
 | 
					return this.attrs.gradient;if("transform"==c)
 | 
				
			||||||
 | 
					return this._.transform;for(var g=c.split(j),h={},i=0,l=g.length;l>i;i++)
 | 
				
			||||||
 | 
					c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return l-1?h:h[g[0]]}
 | 
				
			||||||
 | 
					if(null==d&&a.is(c,"array")){for(h={},i=0,l=c.length;l>i;i++)
 | 
				
			||||||
 | 
					h[c[i]]=this.attr(c[i]);return h}
 | 
				
			||||||
 | 
					if(null!=d){var m={};m[c]=d}else
 | 
				
			||||||
 | 
					null!=c&&a.is(c,"object")&&(m=c);for(var n in m)
 | 
				
			||||||
 | 
					k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)
 | 
				
			||||||
 | 
					if(this.paper.customAttributes[b](n)&&m[b](n)&&a.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)
 | 
				
			||||||
 | 
					o[b](p)&&(m[p]=o[p])}
 | 
				
			||||||
 | 
					return w(this,m),this},B.toFront=function(){if(this.removed)
 | 
				
			||||||
 | 
					return this;var b=z(this.node);b.parentNode.appendChild(b);var c=this.paper;return c.top!=this&&a._tofront(this,c),this},B.toBack=function(){if(this.removed)
 | 
				
			||||||
 | 
					return this;var b=z(this.node),c=b.parentNode;c.insertBefore(b,c.firstChild),a._toback(this,this.paper);this.paper;return this},B.insertAfter=function(b){if(this.removed||!b)
 | 
				
			||||||
 | 
					return this;var c=z(this.node),d=z(b.node||b[b.length-1].node);return d.nextSibling?d.parentNode.insertBefore(c,d.nextSibling):d.parentNode.appendChild(c),a._insertafter(this,b,this.paper),this},B.insertBefore=function(b){if(this.removed||!b)
 | 
				
			||||||
 | 
					return this;var c=z(this.node),d=z(b.node||b[0].node);return d.parentNode.insertBefore(c,d),a._insertbefore(this,b,this.paper),this},B.blur=function(b){var c=this;if(0!==+b){var d=q("filter"),e=q("feGaussianBlur");c.attrs.blur=b,d.id=a.createUUID(),q(e,{stdDeviation:+b||1.5}),d.appendChild(e),c.paper.defs.appendChild(d),c._blur=d,q(c.node,{filter:"url(#"+d.id+")"})}else
 | 
				
			||||||
 | 
					c._blur&&(c._blur.parentNode.removeChild(c._blur),delete c._blur,delete c.attrs.blur),c.node.removeAttribute("filter");return c},a._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new A(e,a);return f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs),f},a._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new A(g,a);return h.attrs={x:b,y:c,width:d,height:e,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs),h},a._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new A(f,a);return g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs),g},a._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new A(g,a);return h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image",h},a._engine.text=function(b,c,d,e){var f=q("text");b.canvas&&b.canvas.appendChild(f);var g=new A(f,b);return g.attrs={x:c,y:d,"text-anchor":"middle",text:e,"font-family":a._availableAttrs["font-family"],"font-size":a._availableAttrs["font-size"],stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs),g},a._engine.setSize=function(a,b){return this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox),this},a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b&&b.container,d=b.x,e=b.y,f=b.width,g=b.height;if(!c)
 | 
				
			||||||
 | 
					throw new Error("SVG container not found.");var h,i=q("svg"),j="overflow:hidden;";return d=d||0,e=e||0,f=f||512,g=g||342,q(i,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"}),1==c?(i.style.cssText=j+"position:absolute;left:"+d+"px;top:"+e+"px",a._g.doc.body.appendChild(i),h=1):(i.style.cssText=j+"position:relative",c.firstChild?c.insertBefore(i,c.firstChild):c.appendChild(i)),c=new a._Paper,c.width=f,c.height=g,c.canvas=i,c.clear(),c._left=c._top=0,h&&(c.renderfix=function(){}),c.renderfix(),c},a._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f,h,i=this.getSize(),j=g(c/i.width,d/i.height),l=this.top,n=e?"xMidYMid meet":"xMinYMin";for(null==a?(this._vbSize&&(j=1),delete this._vbSize,f="0 0 "+this.width+m+this.height):(this._vbSize=j,f=a+m+b+m+c+m+d),q(this.canvas,{viewBox:f,preserveAspectRatio:n});j&&l;)
 | 
				
			||||||
 | 
					h="stroke-width"in l.attrs?l.attrs["stroke-width"]:1,l.attr({"stroke-width":h}),l._.dirty=1,l._.dirtyT=1,l=l.prev;return this._viewBox=[a,b,c,d,!!e],this},a.prototype.renderfix=function(){var a,b=this.canvas,c=b.style;try{a=b.getScreenCTM()||b.createSVGMatrix()}catch(d){a=b.createSVGMatrix()}
 | 
				
			||||||
 | 
					var e=-a.e%1,f=-a.f%1;(e||f)&&(e&&(this._left=(this._left+e)%1,c.left=this._left+"px"),f&&(this._top=(this._top+f)%1,c.top=this._top+"px"))},a.prototype.clear=function(){a.eve("raphael.clear",this);for(var b=this.canvas;b.firstChild;)
 | 
				
			||||||
 | 
					b.removeChild(b.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(a._g.doc.createTextNode("Created with Raphaël "+a.version)),b.appendChild(this.desc),b.appendChild(this.defs=q("defs"))},a.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var b in this)
 | 
				
			||||||
 | 
					this[b]="function"==typeof this[b]?a._removedFactory(b):null};var C=a.st;for(var D in B)
 | 
				
			||||||
 | 
					B[b](D)&&!C[b](D)&&(C[D]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}
 | 
				
			||||||
 | 
					(D))}}),function(a,b){"function"==typeof define&&define.amd?define("raphael.vml",["raphael.core"],function(a){return b(a)}):b("object"==typeof exports?require("./raphael.core"):a.Raphael)}
 | 
				
			||||||
 | 
					(this,function(a){if(!a||a.vml){var b="hasOwnProperty",c=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=a.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px;behavior:url(#default#VML)",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(b){var d=/[ahqstv]/gi,e=a._pathToAbsolute;if(c(b).match(d)&&(e=a._path2curve),d=/[clmz]/g,e==a._pathToAbsolute&&!c(b).match(d)){var g=c(b).replace(q,function(a,b,c){var d=[],e="m"==b.toLowerCase(),g=p[b];return c.replace(s,function(a){e&&2==d.length&&(g+=d+p["m"==b?"l":"L"],d=[]),d.push(f(a*u))}),g+d});return g}
 | 
				
			||||||
 | 
					var h,i,j=e(b);g=[];for(var k=0,l=j.length;l>k;k++){h=j[k],i=j[k][0].toLowerCase(),"z"==i&&(i="x");for(var m=1,r=h.length;r>m;m++)
 | 
				
			||||||
 | 
					i+=f(h[m]*u)+(m!=r-1?",":o);g.push(i)}
 | 
				
			||||||
 | 
					return g.join(n)},y=function(b,c,d){var e=a.matrix();return e.rotate(-b,.5,.5),{dx:e.x(c,d),dy:e.y(c,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q=u/b,r=u/c;if(m.visibility="hidden",b&&c){if(l.coordsize=i(q)+n+i(r),m.rotation=f*(0>b*c?-1:1),f){var s=y(f,d,e);d=s.dx,e=s.dy}
 | 
				
			||||||
 | 
					if(0>b&&(p+="x"),0>c&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d* -q+n+e* -r,k||g.fillsize){var t=l.getElementsByTagName(j);t=t&&t[0],l.removeChild(t),k&&(s=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),t.position=s.dx*o+n+s.dy*o),g.fillsize&&(t.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(t)}
 | 
				
			||||||
 | 
					m.visibility="visible"}};a.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,b,d){for(var e=c(b).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";g--;)
 | 
				
			||||||
 | 
					switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}
 | 
				
			||||||
 | 
					var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),r=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),s=e;for(var t in i)
 | 
				
			||||||
 | 
					i[b](t)&&(m[t]=i[t]);if(q&&(m.path=a._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur),(i.path&&"path"==e.type||q)&&(l.path=x(~c(m.path).toLowerCase().indexOf("r")?a._pathToAbsolute(m.path):m.path),e._.dirty=1,"image"==e.type&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0))),"transform"in i&&e.transform(i.transform),r){var y=+m.cx,B=+m.cy,D=+m.rx||+m.r||0,E=+m.ry||+m.r||0;l.path=a.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((y-D)*u),f((B-E)*u),f((y+D)*u),f((B+E)*u),f(y*u)),e._.dirty=1}
 | 
				
			||||||
 | 
					if("clip-rect"in i){var G=c(i["clip-rect"]).split(k);if(4==G.length){G[2]=+G[2]+ +G[0],G[3]=+G[3]+ +G[1];var H=l.clipRect||a._g.doc.createElement("div"),I=H.style;I.clip=a.format("rect({1}px {2}px {3}px {0}px)",G),l.clipRect||(I.position="absolute",I.top=0,I.left=0,I.width=e.paper.width+"px",I.height=e.paper.height+"px",l.parentNode.insertBefore(H,l),H.appendChild(l),l.clipRect=H)}
 | 
				
			||||||
 | 
					i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}
 | 
				
			||||||
 | 
					if(e.textpath){var J=e.textpath.style;i.font&&(J.font=i.font),i["font-family"]&&(J.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(J.fontSize=i["font-size"]),i["font-weight"]&&(J.fontWeight=i["font-weight"]),i["font-style"]&&(J.fontStyle=i["font-style"])}
 | 
				
			||||||
 | 
					if("arrow-start"in i&&A(s,i["arrow-start"]),"arrow-end"in i&&A(s,i["arrow-end"],1),null!=i.opacity||null!=i["stroke-width"]||null!=i.fill||null!=i.src||null!=i.stroke||null!=i["stroke-width"]||null!=i["stroke-opacity"]||null!=i["fill-opacity"]||null!=i["stroke-dasharray"]||null!=i["stroke-miterlimit"]||null!=i["stroke-linejoin"]||null!=i["stroke-linecap"]){var K=l.getElementsByTagName(j),L=!1;if(K=K&&K[0],!K&&(L=K=F(j)),"image"==e.type&&i.src&&(K.src=i.src),i.fill&&(K.on=!0),(null==K.on||"none"==i.fill||null===i.fill)&&(K.on=!1),K.on&&i.fill){var M=c(i.fill).match(a._ISURL);if(M){K.parentNode==l&&l.removeChild(K),K.rotate=!0,K.src=M[1],K.type="tile";var N=e.getBBox(1);K.position=N.x+n+N.y,e._.fillpos=[N.x,N.y],a._preload(M[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else
 | 
				
			||||||
 | 
					K.color=a.getRGB(i.fill).hex,K.src=o,K.type="solid",a.getRGB(i.fill).error&&(s.type in{circle:1,ellipse:1}||"r"!=c(i.fill).charAt())&&C(s,i.fill,K)&&(m.fill="none",m.gradient=i.fill,K.rotate=!1)}
 | 
				
			||||||
 | 
					if("fill-opacity"in i||"opacity"in i){var O=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+a.getRGB(i.fill).o+1||2)-1);O=h(g(O,0),1),K.opacity=O,K.src&&(K.color="none")}
 | 
				
			||||||
 | 
					l.appendChild(K);var P=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],Q=!1;!P&&(Q=P=F("stroke")),(i.stroke&&"none"!=i.stroke||i["stroke-width"]||null!=i["stroke-opacity"]||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])&&(P.on=!0),("none"==i.stroke||null===i.stroke||null==P.on||0==i.stroke||0==i["stroke-width"])&&(P.on=!1);var R=a.getRGB(i.stroke);P.on&&i.stroke&&(P.color=R.hex),O=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+R.o+1||2)-1);var S=.75*(d(i["stroke-width"])||1);if(O=h(g(O,0),1),null==i["stroke-width"]&&(S=m["stroke-width"]),i["stroke-width"]&&(P.weight=S),S&&1>S&&(O*=S)&&(P.weight=1),P.opacity=O,i["stroke-linejoin"]&&(P.joinstyle=i["stroke-linejoin"]||"miter"),P.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(P.endcap="butt"==i["stroke-linecap"]?"flat":"square"==i["stroke-linecap"]?"square":"round"),"stroke-dasharray"in i){var T={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};P.dashstyle=T[b](i["stroke-dasharray"])?T[i["stroke-dasharray"]]:o}
 | 
				
			||||||
 | 
					Q&&l.appendChild(P)}
 | 
				
			||||||
 | 
					if("text"==s.type){s.paper.canvas.style.display=o;var U=s.paper.span,V=100,W=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=U.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),W=d(m["font-size"]||W&&W[0])||10,p.fontSize=W*V+"px",s.textpath.string&&(U.innerHTML=c(s.textpath.string).replace(/</g,"<").replace(/&/g,"&").replace(/\n/g,"<br>"));var X=U.getBoundingClientRect();s.W=m.w=(X.right-X.left)/V,s.H=m.h=(X.bottom-X.top)/V,s.X=m.x,s.Y=m.y+s.H/2,("x"in i||"y"in i)&&(s.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));for(var Y=["x","y","text","font","font-family","font-weight","font-style","font-size"],Z=0,$=Y.length;$>Z;Z++)
 | 
				
			||||||
 | 
					if(Y[Z]in i){s._.dirty=1;break}
 | 
				
			||||||
 | 
					switch(m["text-anchor"]){case"start":s.textpath.style["v-text-align"]="left",s.bbx=s.W/2;break;case"end":s.textpath.style["v-text-align"]="right",s.bbx=-s.W/2;break;default:s.textpath.style["v-text-align"]="center",s.bbx=0}
 | 
				
			||||||
 | 
					s.textpath.style["v-text-kern"]=!0}},C=function(b,f,g){b.attrs=b.attrs||{};var h=(b.attrs,Math.pow),i="linear",j=".5 .5";if(b.attrs.gradient=f,f=c(f).replace(a._radial_gradient,function(a,b,c){return i="radial",b&&c&&(b=d(b),c=d(c),h(b-.5,2)+h(c-.5,2)>.25&&(c=e.sqrt(.25-h(b-.5,2))*(2*(c>.5)-1)+.5),j=b+n+c),o}),f=f.split(/\s*\-\s*/),"linear"==i){var k=f.shift();if(k=-d(k),isNaN(k))
 | 
				
			||||||
 | 
					return null}
 | 
				
			||||||
 | 
					var l=a._parseDots(f);if(!l)
 | 
				
			||||||
 | 
					return null;if(b=b.shape||b.node,l.length){b.removeChild(g),g.on=!0,g.method="none",g.color=l[0].color,g.color2=l[l.length-1].color;for(var m=[],p=0,q=l.length;q>p;p++)
 | 
				
			||||||
 | 
					l[p].offset&&m.push(l[p].offset+n+l[p].color);g.colors=m.length?m.join():"0% "+g.color,"radial"==i?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=j,g.angle=0):(g.type="gradient",g.angle=(270-k)%360),b.appendChild(g)}
 | 
				
			||||||
 | 
					return 1},D=function(b,c){this[0]=this.node=b,b.raphael=!0,this.id=a._oid++,b.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=c,this.matrix=a.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!c.bottom&&(c.bottom=this),this.prev=c.top,c.top&&(c.top.next=this),c.top=this,this.next=null},E=a.el;D.prototype=E,E.constructor=D,E.transform=function(b){if(null==b)
 | 
				
			||||||
 | 
					return this._.transform;var d,e=this.paper._viewBoxShift,f=e?"s"+[e.scale,e.scale]+"-1-1t"+[e.dx,e.dy]:o;e&&(d=b=c(b).replace(/\.{3}|\u2026/g,this._.transform||o)),a._extractTransform(this,f+b);var g,h=this.matrix.clone(),i=this.skew,j=this.node,k=~c(this.attrs.fill).indexOf("-"),l=!c(this.attrs.fill).indexOf("url(");if(h.translate(1,1),l||k||"image"==this.type)
 | 
				
			||||||
 | 
					if(i.matrix="1 0 0 1",i.offset="0 0",g=h.split(),k&&g.noRotation||!g.isSimple){j.style.filter=h.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;j.coordorigin=q* -u+n+r* -u,z(this,1,1,q,r,0)}else
 | 
				
			||||||
 | 
					j.style.filter=o,z(this,g.scalex,g.scaley,g.dx,g.dy,g.rotate);else
 | 
				
			||||||
 | 
					j.style.filter=o,i.matrix=c(h),i.offset=h.offset();return null!==d&&(this._.transform=d,a._extractTransform(this,d)),this},E.rotate=function(a,b,e){if(this.removed)
 | 
				
			||||||
 | 
					return this;if(null!=a){if(a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(b=e),null==b||null==e){var f=this.getBBox(1);b=f.x+f.width/2,e=f.y+f.height/2}
 | 
				
			||||||
 | 
					return this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,b,e]])),this}},E.translate=function(a,b){return this.removed?this:(a=c(a).split(k),a.length-1&&(b=d(a[1])),a=d(a[0])||0,b=+b||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=b),this.transform(this._.transform.concat([["t",a,b]])),this)},E.scale=function(a,b,e,f){if(this.removed)
 | 
				
			||||||
 | 
					return this;if(a=c(a).split(k),a.length-1&&(b=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),null==b&&(b=a),null==f&&(e=f),null==e||null==f)
 | 
				
			||||||
 | 
					var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,b,e,f]])),this._.dirtyT=1,this},E.hide=function(){return!this.removed&&(this.node.style.display="none"),this},E.show=function(){return!this.removed&&(this.node.style.display=o),this},E.auxGetBBox=a.el.getBBox,E.getBBox=function(){var a=this.auxGetBBox();if(this.paper&&this.paper._viewBoxShift){var b={},c=1/this.paper._viewBoxShift.scale;return b.x=a.x-this.paper._viewBoxShift.dx,b.x*=c,b.y=a.y-this.paper._viewBoxShift.dy,b.y*=c,b.width=a.width*c,b.height=a.height*c,b.x2=b.x+b.width,b.y2=b.y+b.height,b}
 | 
				
			||||||
 | 
					return a},E._getBBox=function(){return this.removed?{}:{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),a.eve.unbind("raphael.*.*."+this.id),a._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var b in this)
 | 
				
			||||||
 | 
					this[b]="function"==typeof this[b]?a._removedFactory(b):null;this.removed=!0}},E.attr=function(c,d){if(this.removed)
 | 
				
			||||||
 | 
					return this;if(null==c){var e={};for(var f in this.attrs)
 | 
				
			||||||
 | 
					this.attrs[b](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}
 | 
				
			||||||
 | 
					if(null==d&&a.is(c,"string")){if(c==j&&"none"==this.attrs.fill&&this.attrs.gradient)
 | 
				
			||||||
 | 
					return this.attrs.gradient;for(var g=c.split(k),h={},i=0,m=g.length;m>i;i++)
 | 
				
			||||||
 | 
					c=g[i],c in this.attrs?h[c]=this.attrs[c]:a.is(this.paper.customAttributes[c],"function")?h[c]=this.paper.customAttributes[c].def:h[c]=a._availableAttrs[c];return m-1?h:h[g[0]]}
 | 
				
			||||||
 | 
					if(this.attrs&&null==d&&a.is(c,"array")){for(h={},i=0,m=c.length;m>i;i++)
 | 
				
			||||||
 | 
					h[c[i]]=this.attr(c[i]);return h}
 | 
				
			||||||
 | 
					var n;null!=d&&(n={},n[c]=d),null==d&&a.is(c,"object")&&(n=c);for(var o in n)
 | 
				
			||||||
 | 
					l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)
 | 
				
			||||||
 | 
					if(this.paper.customAttributes[b](o)&&n[b](o)&&a.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)
 | 
				
			||||||
 | 
					p[b](q)&&(n[q]=p[q])}
 | 
				
			||||||
 | 
					n.text&&"text"==this.type&&(this.textpath.string=n.text),B(this,n)}
 | 
				
			||||||
 | 
					return this},E.toFront=function(){return!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&a._tofront(this,this.paper),this},E.toBack=function(){return this.removed?this:(this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),a._toback(this,this.paper)),this)},E.insertAfter=function(b){return this.removed?this:(b.constructor==a.st.constructor&&(b=b[b.length-1]),b.node.nextSibling?b.node.parentNode.insertBefore(this.node,b.node.nextSibling):b.node.parentNode.appendChild(this.node),a._insertafter(this,b,this.paper),this)},E.insertBefore=function(b){return this.removed?this:(b.constructor==a.st.constructor&&(b=b[0]),b.node.parentNode.insertBefore(this.node,b.node),a._insertbefore(this,b,this.paper),this)},E.blur=function(b){var c=this.node.runtimeStyle,d=c.filter;return d=d.replace(r,o),0!==+b?(this.attrs.blur=b,c.filter=d+n+m+".Blur(pixelradius="+(+b||1.5)+")",c.margin=a.format("-{0}px 0 0 -{0}px",f(+b||1.5))):(c.filter=d,c.margin=0,delete this.attrs.blur),this},a._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");return f.on=!0,c.appendChild(f),d.skew=f,d.transform(o),d},a._engine.rect=function(b,c,d,e,f,g){var h=a._rectPath(c,d,e,f,g),i=b.path(h),j=i.attrs;return i.X=j.x=c,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect",i},a._engine.ellipse=function(a,b,c,d,e){{var f=a.path();f.attrs}
 | 
				
			||||||
 | 
					return f.X=b-d,f.Y=c-e,f.W=2*d,f.H=2*e,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e}),f},a._engine.circle=function(a,b,c,d){{var e=a.path();e.attrs}
 | 
				
			||||||
 | 
					return e.X=b-d,e.Y=c-d,e.W=e.H=2*d,e.type="circle",B(e,{cx:b,cy:c,r:d}),e},a._engine.image=function(b,c,d,e,f,g){var h=a._rectPath(d,e,f,g),i=b.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];return k.src=c,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=c,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0),i},a._engine.text=function(b,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=a.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=c(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,b),l={fill:"#000",stroke:"none",font:a._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=c(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),b.canvas.appendChild(h);var m=F("skew");return m.on=!0,h.appendChild(m),k.skew=m,k.transform(o),k},a._engine.setSize=function(b,c){var d=this.canvas.style;return this.width=b,this.height=c,b==+b&&(b+="px"),c==+c&&(c+="px"),d.width=b,d.height=c,d.clip="rect(0 "+b+" "+c+" 0)",this._viewBox&&a._engine.setViewBox.apply(this,this._viewBox),this},a._engine.setViewBox=function(b,c,d,e,f){a.eve("raphael.setViewBox",this,this._viewBox,[b,c,d,e,f]);var g,h,i=this.getSize(),j=i.width,k=i.height;return f&&(g=k/e,h=j/d,j>d*g&&(b-=(j-d*g)/2/g),k>e*h&&(c-=(k-e*h)/2/h)),this._viewBox=[b,c,d,e,!!f],this._viewBoxShift={dx:-b,dy:-c,scale:i},this.forEach(function(a){a.transform("...")}),this};var F;a._engine.initWin=function(a){var b=a.document;b.styleSheets.length<31?b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)"):b.styleSheets[0].addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("<rvml:"+a+' class="rvml">')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e=b.width,f=b.x,g=b.y;if(!c)
 | 
				
			||||||
 | 
					throw new Error("VML container not found.");var h=new a._Paper,i=h.canvas=a._g.doc.createElement("div"),j=i.style;return f=f||0,g=g||0,e=e||512,d=d||342,h.width=e,h.height=d,e==+e&&(e+="px"),d==+d&&(d+="px"),h.coordsize=1e3*u+n+1e3*u,h.coordorigin="0 0",h.span=a._g.doc.createElement("span"),h.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",i.appendChild(h.span),j.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",e,d),1==c?(a._g.doc.body.appendChild(i),j.left=f+"px",j.top=g+"px",j.position="absolute"):c.firstChild?c.insertBefore(i,c.firstChild):c.appendChild(i),h.renderfix=function(){},h},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)
 | 
				
			||||||
 | 
					this[b]="function"==typeof this[b]?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)
 | 
				
			||||||
 | 
					E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}
 | 
				
			||||||
 | 
					(H))}});JustGage=function(config){var obj=this;if(config===null||config===undefined){console.log('* justgage: Make sure to pass options to the constructor!');return false;}
 | 
				
			||||||
 | 
					var node;if(config.id!==null&&config.id!==undefined){node=document.getElementById(config.id);if(!node){console.log('* justgage: No element with id : %s found',config.id);return false;}}else if(config.parentNode!==null&&config.parentNode!==undefined){node=config.parentNode;}else{console.log('* justgage: Make sure to pass the existing element id or parentNode to the constructor.');return false;}
 | 
				
			||||||
 | 
					var dataset=node.dataset?node.dataset:{};var defaults=(config.defaults!==null&&config.defaults!==undefined)?config.defaults:false;if(defaults!==false){config=extend({},config,defaults);delete config.defaults;}
 | 
				
			||||||
 | 
					obj.config={id:config.id,value:kvLookup('value',config,dataset,0,'float'),defaults:kvLookup('defaults',config,dataset,0,false),parentNode:kvLookup('parentNode',config,dataset,null),width:kvLookup('width',config,dataset,null),height:kvLookup('height',config,dataset,null),title:kvLookup('title',config,dataset,""),titleFontColor:kvLookup('titleFontColor',config,dataset,"#999999"),titleFontFamily:kvLookup('titleFontFamily',config,dataset,"sans-serif"),titlePosition:kvLookup('titlePosition',config,dataset,"above"),valueFontColor:kvLookup('valueFontColor',config,dataset,"#010101"),valueFontFamily:kvLookup('valueFontFamily',config,dataset,"Arial"),symbol:kvLookup('symbol',config,dataset,''),min:kvLookup('min',config,dataset,0,'float'),max:kvLookup('max',config,dataset,100,'float'),reverse:kvLookup('reverse',config,dataset,false),humanFriendlyDecimal:kvLookup('humanFriendlyDecimal',config,dataset,0),textRenderer:kvLookup('textRenderer',config,dataset,null),gaugeWidthScale:kvLookup('gaugeWidthScale',config,dataset,1.0),gaugeColor:kvLookup('gaugeColor',config,dataset,"#edebeb"),label:kvLookup('label',config,dataset,''),labelFontColor:kvLookup('labelFontColor',config,dataset,"#b3b3b3"),shadowOpacity:kvLookup('shadowOpacity',config,dataset,0.2),shadowSize:kvLookup('shadowSize',config,dataset,5),shadowVerticalOffset:kvLookup('shadowVerticalOffset',config,dataset,3),levelColors:kvLookup('levelColors',config,dataset,["#a9d70b","#f9c802","#ff0000"],'array',','),startAnimationTime:kvLookup('startAnimationTime',config,dataset,700),startAnimationType:kvLookup('startAnimationType',config,dataset,'>'),refreshAnimationTime:kvLookup('refreshAnimationTime',config,dataset,700),refreshAnimationType:kvLookup('refreshAnimationType',config,dataset,'>'),donutStartAngle:kvLookup('donutStartAngle',config,dataset,90),valueMinFontSize:kvLookup('valueMinFontSize',config,dataset,16),titleMinFontSize:kvLookup('titleMinFontSize',config,dataset,10),labelMinFontSize:kvLookup('labelMinFontSize',config,dataset,10),minLabelMinFontSize:kvLookup('minLabelMinFontSize',config,dataset,10),maxLabelMinFontSize:kvLookup('maxLabelMinFontSize',config,dataset,10),hideValue:kvLookup('hideValue',config,dataset,false),hideMinMax:kvLookup('hideMinMax',config,dataset,false),hideInnerShadow:kvLookup('hideInnerShadow',config,dataset,false),humanFriendly:kvLookup('humanFriendly',config,dataset,false),noGradient:kvLookup('noGradient',config,dataset,false),donut:kvLookup('donut',config,dataset,false),relativeGaugeSize:kvLookup('relativeGaugeSize',config,dataset,false),counter:kvLookup('counter',config,dataset,false),decimals:kvLookup('decimals',config,dataset,0),customSectors:kvLookup('customSectors',config,dataset,[]),formatNumber:kvLookup('formatNumber',config,dataset,false),pointer:kvLookup('pointer',config,dataset,false),pointerOptions:kvLookup('pointerOptions',config,dataset,[])};var
 | 
				
			||||||
 | 
					canvasW,canvasH,widgetW,widgetH,aspect,dx,dy,titleFontSize,titleX,titleY,valueFontSize,valueX,valueY,labelFontSize,labelX,labelY,minFontSize,minX,minY,maxFontSize,maxX,maxY;if(obj.config.value>obj.config.max)
 | 
				
			||||||
 | 
					obj.config.value=obj.config.max;if(obj.config.value<obj.config.min)
 | 
				
			||||||
 | 
					obj.config.value=obj.config.min;obj.originalValue=kvLookup('value',config,dataset,-1,'float');if(obj.config.id!==null&&(document.getElementById(obj.config.id))!==null){obj.canvas=Raphael(obj.config.id,"100%","100%");}else if(obj.config.parentNode!==null){obj.canvas=Raphael(obj.config.parentNode,"100%","100%");}
 | 
				
			||||||
 | 
					if(obj.config.relativeGaugeSize===true){obj.canvas.setViewBox(0,0,200,150,true);}
 | 
				
			||||||
 | 
					if(obj.config.relativeGaugeSize===true){canvasW=200;canvasH=150;}else if(obj.config.width!==null&&obj.config.height!==null){canvasW=obj.config.width;canvasH=obj.config.height;}else if(obj.config.parentNode!==null){obj.canvas.setViewBox(0,0,200,150,true);canvasW=200;canvasH=150;}else{canvasW=getStyle(document.getElementById(obj.config.id),"width").slice(0,-2)*1;canvasH=getStyle(document.getElementById(obj.config.id),"height").slice(0,-2)*1;}
 | 
				
			||||||
 | 
					if(obj.config.donut===true){if(canvasW>canvasH){widgetH=canvasH;widgetW=widgetH;}else if(canvasW<canvasH){widgetW=canvasW;widgetH=widgetW;if(widgetH>canvasH){aspect=widgetH/canvasH;widgetH=widgetH/aspect;widgetW=widgetH/aspect;}}else{widgetW=canvasW;widgetH=widgetW;}
 | 
				
			||||||
 | 
					dx=(canvasW-widgetW)/2;dy=(canvasH-widgetH)/2;titleFontSize=((widgetH/8)>10)?(widgetH/10):10;titleX=dx+widgetW/2;titleY=dy+widgetH/11;valueFontSize=((widgetH/6.4)>16)?(widgetH/5.4):18;valueX=dx+widgetW/2;if(obj.config.label!==''){valueY=dy+widgetH/1.85;}else{valueY=dy+widgetH/1.7;}
 | 
				
			||||||
 | 
					labelFontSize=((widgetH/16)>10)?(widgetH/16):10;labelX=dx+widgetW/2;labelY=valueY+labelFontSize;minFontSize=((widgetH/16)>10)?(widgetH/16):10;minX=dx+(widgetW/10)+(widgetW/6.666666666666667*obj.config.gaugeWidthScale)/2;minY=labelY;maxFontSize=((widgetH/16)>10)?(widgetH/16):10;maxX=dx+widgetW-(widgetW/10)-(widgetW/6.666666666666667*obj.config.gaugeWidthScale)/2;maxY=labelY;}else{if(canvasW>canvasH){widgetH=canvasH;widgetW=widgetH*1.25;if(widgetW>canvasW){aspect=widgetW/canvasW;widgetW=widgetW/aspect;widgetH=widgetH/aspect;}}else if(canvasW<canvasH){widgetW=canvasW;widgetH=widgetW/1.25;if(widgetH>canvasH){aspect=widgetH/canvasH;widgetH=widgetH/aspect;widgetW=widgetH/aspect;}}else{widgetW=canvasW;widgetH=widgetW*0.75;}
 | 
				
			||||||
 | 
					dx=(canvasW-widgetW)/2;dy=(canvasH-widgetH)/2;if(obj.config.titlePosition==='below'){dy-=(widgetH/6.4);}
 | 
				
			||||||
 | 
					titleFontSize=((widgetH/8)>obj.config.titleMinFontSize)?(widgetH/10):obj.config.titleMinFontSize;titleX=dx+widgetW/2;titleY=dy+(obj.config.titlePosition==='below'?(widgetH*1.07):(widgetH/6.4));valueFontSize=((widgetH/6.5)>obj.config.valueMinFontSize)?(widgetH/6.5):obj.config.valueMinFontSize;valueX=dx+widgetW/2;valueY=dy+widgetH/1.275;labelFontSize=((widgetH/16)>obj.config.labelMinFontSize)?(widgetH/16):obj.config.labelMinFontSize;labelX=dx+widgetW/2;labelY=valueY+valueFontSize/2+5;minFontSize=((widgetH/16)>obj.config.minLabelMinFontSize)?(widgetH/16):obj.config.minLabelMinFontSize;minX=dx+(widgetW/10)+(widgetW/6.666666666666667*obj.config.gaugeWidthScale)/2;minY=labelY;maxFontSize=((widgetH/16)>obj.config.maxLabelMinFontSize)?(widgetH/16):obj.config.maxLabelMinFontSize;maxX=dx+widgetW-(widgetW/10)-(widgetW/6.666666666666667*obj.config.gaugeWidthScale)/2;maxY=labelY;}
 | 
				
			||||||
 | 
					obj.params={canvasW:canvasW,canvasH:canvasH,widgetW:widgetW,widgetH:widgetH,dx:dx,dy:dy,titleFontSize:titleFontSize,titleX:titleX,titleY:titleY,valueFontSize:valueFontSize,valueX:valueX,valueY:valueY,labelFontSize:labelFontSize,labelX:labelX,labelY:labelY,minFontSize:minFontSize,minX:minX,minY:minY,maxFontSize:maxFontSize,maxX:maxX,maxY:maxY};canvasW,canvasH,widgetW,widgetH,aspect,dx,dy,titleFontSize,titleX,titleY,valueFontSize,valueX,valueY,labelFontSize,labelX,labelY,minFontSize,minX,minY,maxFontSize,maxX,maxY=null;obj.canvas.customAttributes.pki=function(value,min,max,w,h,dx,dy,gws,donut,reverse){var alpha,Ro,Ri,Cx,Cy,Xo,Yo,Xi,Yi,path;if(donut){alpha=(1-2*(value-min)/(max-min))*Math.PI;Ro=w/2-w/7;Ri=Ro-w/6.666666666666667*gws;Cx=w/2+dx;Cy=h/1.95+dy;Xo=w/2+dx+Ro*Math.cos(alpha);Yo=h-(h-Cy)-Ro*Math.sin(alpha);Xi=w/2+dx+Ri*Math.cos(alpha);Yi=h-(h-Cy)-Ri*Math.sin(alpha);path="M"+(Cx-Ri)+","+Cy+" ";path+="L"+(Cx-Ro)+","+Cy+" ";if(value>((max-min)/2)){path+="A"+Ro+","+Ro+" 0 0 1 "+(Cx+Ro)+","+Cy+" ";}
 | 
				
			||||||
 | 
					path+="A"+Ro+","+Ro+" 0 0 1 "+Xo+","+Yo+" ";path+="L"+Xi+","+Yi+" ";if(value>((max-min)/2)){path+="A"+Ri+","+Ri+" 0 0 0 "+(Cx+Ri)+","+Cy+" ";}
 | 
				
			||||||
 | 
					path+="A"+Ri+","+Ri+" 0 0 0 "+(Cx-Ri)+","+Cy+" ";path+="Z ";return{path:path};}else{alpha=(1-(value-min)/(max-min))*Math.PI;Ro=w/2-w/10;Ri=Ro-w/6.666666666666667*gws;Cx=w/2+dx;Cy=h/1.25+dy;Xo=w/2+dx+Ro*Math.cos(alpha);Yo=h-(h-Cy)-Ro*Math.sin(alpha);Xi=w/2+dx+Ri*Math.cos(alpha);Yi=h-(h-Cy)-Ri*Math.sin(alpha);path="M"+(Cx-Ri)+","+Cy+" ";path+="L"+(Cx-Ro)+","+Cy+" ";path+="A"+Ro+","+Ro+" 0 0 1 "+Xo+","+Yo+" ";path+="L"+Xi+","+Yi+" ";path+="A"+Ri+","+Ri+" 0 0 0 "+(Cx-Ri)+","+Cy+" ";path+="Z ";return{path:path};}
 | 
				
			||||||
 | 
					alpha,Ro,Ri,Cx,Cy,Xo,Yo,Xi,Yi,path=null;};obj.canvas.customAttributes.ndl=function(value,min,max,w,h,dx,dy,gws,donut){var dlt=w*3.5/100;var dlb=w/15;var dw=w/100;if(obj.config.pointerOptions.toplength!=null&&obj.config.pointerOptions.toplength!=undefined)
 | 
				
			||||||
 | 
					dlt=obj.config.pointerOptions.toplength;if(obj.config.pointerOptions.bottomlength!=null&&obj.config.pointerOptions.bottomlength!=undefined)
 | 
				
			||||||
 | 
					dlb=obj.config.pointerOptions.bottomlength;if(obj.config.pointerOptions.bottomwidth!=null&&obj.config.pointerOptions.bottomwidth!=undefined)
 | 
				
			||||||
 | 
					dw=obj.config.pointerOptions.bottomwidth;var alpha,Ro,Ri,Cx,Cy,Xo,Yo,Xi,Yi,Xc,Yc,Xz,Yz,Xa,Ya,Xb,Yb,path;if(donut){alpha=(1-2*(value-min)/(max-min))*Math.PI;Ro=w/2-w/7;Ri=Ro-w/6.666666666666667*gws;Cx=w/2+dx;Cy=h/1.95+dy;Xo=w/2+dx+Ro*Math.cos(alpha);Yo=h-(h-Cy)-Ro*Math.sin(alpha);Xi=w/2+dx+Ri*Math.cos(alpha);Yi=h-(h-Cy)-Ri*Math.sin(alpha);Xc=Xo+dlt*Math.cos(alpha);Yc=Yo-dlt*Math.sin(alpha);Xz=Xi-dlb*Math.cos(alpha);Yz=Yi+dlb*Math.sin(alpha);Xa=Xz+dw*Math.sin(alpha);Ya=Yz+dw*Math.cos(alpha);Xb=Xz-dw*Math.sin(alpha);Yb=Yz-dw*Math.cos(alpha);path='M'+Xa+','+Ya+' ';path+='L'+Xb+','+Yb+' ';path+='L'+Xc+','+Yc+' ';path+='Z ';return{path:path};}else{alpha=(1-(value-min)/(max-min))*Math.PI;Ro=w/2-w/10;Ri=Ro-w/6.666666666666667*gws;Cx=w/2+dx;Cy=h/1.25+dy;Xo=w/2+dx+Ro*Math.cos(alpha);Yo=h-(h-Cy)-Ro*Math.sin(alpha);Xi=w/2+dx+Ri*Math.cos(alpha);Yi=h-(h-Cy)-Ri*Math.sin(alpha);Xc=Xo+dlt*Math.cos(alpha);Yc=Yo-dlt*Math.sin(alpha);Xz=Xi-dlb*Math.cos(alpha);Yz=Yi+dlb*Math.sin(alpha);Xa=Xz+dw*Math.sin(alpha);Ya=Yz+dw*Math.cos(alpha);Xb=Xz-dw*Math.sin(alpha);Yb=Yz-dw*Math.cos(alpha);path='M'+Xa+','+Ya+' ';path+='L'+Xb+','+Yb+' ';path+='L'+Xc+','+Yc+' ';path+='Z ';return{path:path};}
 | 
				
			||||||
 | 
					alpha,Ro,Ri,Cx,Cy,Xo,Yo,Xi,Yi,Xc,Yc,Xz,Yz,Xa,Ya,Xb,Yb,path=null;};obj.gauge=obj.canvas.path().attr({"stroke":"none","fill":obj.config.gaugeColor,pki:[obj.config.max,obj.config.min,obj.config.max,obj.params.widgetW,obj.params.widgetH,obj.params.dx,obj.params.dy,obj.config.gaugeWidthScale,obj.config.donut,obj.config.reverse]});obj.level=obj.canvas.path().attr({"stroke":"none","fill":getColor(obj.config.value,(obj.config.value-obj.config.min)/(obj.config.max-obj.config.min),obj.config.levelColors,obj.config.noGradient,obj.config.customSectors),pki:[obj.config.min,obj.config.min,obj.config.max,obj.params.widgetW,obj.params.widgetH,obj.params.dx,obj.params.dy,obj.config.gaugeWidthScale,obj.config.donut,obj.config.reverse]});if(obj.config.donut){obj.level.transform("r"+obj.config.donutStartAngle+", "+(obj.params.widgetW/2+obj.params.dx)+", "+(obj.params.widgetH/1.95+obj.params.dy));}
 | 
				
			||||||
 | 
					if(obj.config.pointer){obj.needle=obj.canvas.path().attr({"stroke":(obj.config.pointerOptions.stroke!==null&&obj.config.pointerOptions.stroke!==undefined)?obj.config.pointerOptions.stroke:"none","stroke-width":(obj.config.pointerOptions.stroke_width!==null&&obj.config.pointerOptions.stroke_width!==undefined)?obj.config.pointerOptions.stroke_width:0,"stroke-linecap":(obj.config.pointerOptions.stroke_linecap!==null&&obj.config.pointerOptions.stroke_linecap!==undefined)?obj.config.pointerOptions.stroke_linecap:"square","fill":(obj.config.pointerOptions.color!==null&&obj.config.pointerOptions.color!==undefined)?obj.config.pointerOptions.color:"#000000",ndl:[obj.config.min,obj.config.min,obj.config.max,obj.params.widgetW,obj.params.widgetH,obj.params.dx,obj.params.dy,obj.config.gaugeWidthScale,obj.config.donut]});if(obj.config.donut){obj.needle.transform("r"+obj.config.donutStartAngle+", "+(obj.params.widgetW/2+obj.params.dx)+", "+(obj.params.widgetH/1.95+obj.params.dy));}}
 | 
				
			||||||
 | 
					obj.txtTitle=obj.canvas.text(obj.params.titleX,obj.params.titleY,obj.config.title);obj.txtTitle.attr({"font-size":obj.params.titleFontSize,"font-weight":"bold","font-family":obj.config.titleFontFamily,"fill":obj.config.titleFontColor,"fill-opacity":"1"});setDy(obj.txtTitle,obj.params.titleFontSize,obj.params.titleY);obj.txtValue=obj.canvas.text(obj.params.valueX,obj.params.valueY,0);obj.txtValue.attr({"font-size":obj.params.valueFontSize,"font-weight":"bold","font-family":obj.config.valueFontFamily,"fill":obj.config.valueFontColor,"fill-opacity":"0"});setDy(obj.txtValue,obj.params.valueFontSize,obj.params.valueY);obj.txtLabel=obj.canvas.text(obj.params.labelX,obj.params.labelY,obj.config.label);obj.txtLabel.attr({"font-size":obj.params.labelFontSize,"font-weight":"normal","font-family":"Arial","fill":obj.config.labelFontColor,"fill-opacity":"0"});setDy(obj.txtLabel,obj.params.labelFontSize,obj.params.labelY);var min=obj.config.min;if(obj.config.reverse){min=obj.config.max;}
 | 
				
			||||||
 | 
					obj.txtMinimum=min;if(obj.config.humanFriendly){obj.txtMinimum=humanFriendlyNumber(min,obj.config.humanFriendlyDecimal);}else if(obj.config.formatNumber){obj.txtMinimum=formatNumber(min);}
 | 
				
			||||||
 | 
					obj.txtMin=obj.canvas.text(obj.params.minX,obj.params.minY,obj.txtMinimum);obj.txtMin.attr({"font-size":obj.params.minFontSize,"font-weight":"normal","font-family":"Arial","fill":obj.config.labelFontColor,"fill-opacity":(obj.config.hideMinMax||obj.config.donut)?"0":"1"});setDy(obj.txtMin,obj.params.minFontSize,obj.params.minY);var max=obj.config.max;if(obj.config.reverse){max=obj.config.min;}
 | 
				
			||||||
 | 
					obj.txtMaximum=max;if(obj.config.humanFriendly){obj.txtMaximum=humanFriendlyNumber(max,obj.config.humanFriendlyDecimal);}else if(obj.config.formatNumber){obj.txtMaximum=formatNumber(max);}
 | 
				
			||||||
 | 
					obj.txtMax=obj.canvas.text(obj.params.maxX,obj.params.maxY,obj.txtMaximum);obj.txtMax.attr({"font-size":obj.params.maxFontSize,"font-weight":"normal","font-family":"Arial","fill":obj.config.labelFontColor,"fill-opacity":(obj.config.hideMinMax||obj.config.donut)?"0":"1"});setDy(obj.txtMax,obj.params.maxFontSize,obj.params.maxY);var defs=obj.canvas.canvas.childNodes[1];var svg="http://www.w3.org/2000/svg";if(ie!=='undefined'&&ie<9){}
 | 
				
			||||||
 | 
					else if(ie!=='undefined'){onCreateElementNsReady(function(){obj.generateShadow(svg,defs);});}else{obj.generateShadow(svg,defs);}
 | 
				
			||||||
 | 
					defs,svg=null;if(obj.config.textRenderer){obj.originalValue=obj.config.textRenderer(obj.originalValue);}else if(obj.config.humanFriendly){obj.originalValue=humanFriendlyNumber(obj.originalValue,obj.config.humanFriendlyDecimal)+obj.config.symbol;}else if(obj.config.formatNumber){obj.originalValue=formatNumber(obj.originalValue)+obj.config.symbol;}else{obj.originalValue=(obj.originalValue*1).toFixed(obj.config.decimals)+obj.config.symbol;}
 | 
				
			||||||
 | 
					if(obj.config.counter===true){eve.on("raphael.anim.frame."+(obj.level.id),function(){var currentValue=obj.level.attr("pki")[0];if(obj.config.reverse){currentValue=(obj.config.max*1)+(obj.config.min*1)-(obj.level.attr("pki")[0]*1);}
 | 
				
			||||||
 | 
					if(obj.config.textRenderer){obj.txtValue.attr("text",obj.config.textRenderer(Math.floor(currentValue)));}else if(obj.config.humanFriendly){obj.txtValue.attr("text",humanFriendlyNumber(Math.floor(currentValue),obj.config.humanFriendlyDecimal)+obj.config.symbol);}else if(obj.config.formatNumber){obj.txtValue.attr("text",formatNumber(Math.floor(currentValue))+obj.config.symbol);}else{obj.txtValue.attr("text",(currentValue*1).toFixed(obj.config.decimals)+obj.config.symbol);}
 | 
				
			||||||
 | 
					setDy(obj.txtValue,obj.params.valueFontSize,obj.params.valueY);currentValue=null;});eve.on("raphael.anim.finish."+(obj.level.id),function(){obj.txtValue.attr({"text":obj.originalValue});setDy(obj.txtValue,obj.params.valueFontSize,obj.params.valueY);});}else{eve.on("raphael.anim.start."+(obj.level.id),function(){obj.txtValue.attr({"text":obj.originalValue});setDy(obj.txtValue,obj.params.valueFontSize,obj.params.valueY);});}
 | 
				
			||||||
 | 
					var rvl=obj.config.value;if(obj.config.reverse){rvl=(obj.config.max*1)+(obj.config.min*1)-(obj.config.value*1);}
 | 
				
			||||||
 | 
					obj.level.animate({pki:[rvl,obj.config.min,obj.config.max,obj.params.widgetW,obj.params.widgetH,obj.params.dx,obj.params.dy,obj.config.gaugeWidthScale,obj.config.donut,obj.config.reverse]},obj.config.startAnimationTime,obj.config.startAnimationType);if(obj.config.pointer){obj.needle.animate({ndl:[rvl,obj.config.min,obj.config.max,obj.params.widgetW,obj.params.widgetH,obj.params.dx,obj.params.dy,obj.config.gaugeWidthScale,obj.config.donut]},obj.config.startAnimationTime,obj.config.startAnimationType);}
 | 
				
			||||||
 | 
					obj.txtValue.animate({"fill-opacity":(obj.config.hideValue)?"0":"1"},obj.config.startAnimationTime,obj.config.startAnimationType);obj.txtLabel.animate({"fill-opacity":"1"},obj.config.startAnimationTime,obj.config.startAnimationType);};JustGage.prototype.refresh=function(val,max){var obj=this;var displayVal,color,max=max||null;if(max!==null){obj.config.max=max;obj.txtMaximum=obj.config.max;if(obj.config.humanFriendly){obj.txtMaximum=humanFriendlyNumber(obj.config.max,obj.config.humanFriendlyDecimal);}else if(obj.config.formatNumber){obj.txtMaximum=formatNumber(obj.config.max);}
 | 
				
			||||||
 | 
					if(!obj.config.reverse){obj.txtMax.attr({"text":obj.txtMaximum});setDy(obj.txtMax,obj.params.maxFontSize,obj.params.maxY);}else{obj.txtMin.attr({"text":obj.txtMaximum});setDy(obj.txtMin,obj.params.minFontSize,obj.params.minY);}}
 | 
				
			||||||
 | 
					displayVal=val;if((val*1)>(obj.config.max*1)){val=(obj.config.max*1);}
 | 
				
			||||||
 | 
					if((val*1)<(obj.config.min*1)){val=(obj.config.min*1);}
 | 
				
			||||||
 | 
					color=getColor(val,(val-obj.config.min)/(obj.config.max-obj.config.min),obj.config.levelColors,obj.config.noGradient,obj.config.customSectors);if(obj.config.textRenderer){displayVal=obj.config.textRenderer(displayVal);}else if(obj.config.humanFriendly){displayVal=humanFriendlyNumber(displayVal,obj.config.humanFriendlyDecimal)+obj.config.symbol;}else if(obj.config.formatNumber){displayVal=formatNumber((displayVal*1).toFixed(obj.config.decimals))+obj.config.symbol;}else{displayVal=(displayVal*1).toFixed(obj.config.decimals)+obj.config.symbol;}
 | 
				
			||||||
 | 
					obj.originalValue=displayVal;obj.config.value=val*1;if(!obj.config.counter){obj.txtValue.attr({"text":displayVal});setDy(obj.txtValue,obj.params.valueFontSize,obj.params.valueY);}
 | 
				
			||||||
 | 
					var rvl=obj.config.value;if(obj.config.reverse){rvl=(obj.config.max*1)+(obj.config.min*1)-(obj.config.value*1);}
 | 
				
			||||||
 | 
					obj.level.animate({pki:[rvl,obj.config.min,obj.config.max,obj.params.widgetW,obj.params.widgetH,obj.params.dx,obj.params.dy,obj.config.gaugeWidthScale,obj.config.donut,obj.config.reverse],"fill":color},obj.config.refreshAnimationTime,obj.config.refreshAnimationType);if(obj.config.pointer){obj.needle.animate({ndl:[rvl,obj.config.min,obj.config.max,obj.params.widgetW,obj.params.widgetH,obj.params.dx,obj.params.dy,obj.config.gaugeWidthScale,obj.config.donut]},obj.config.refreshAnimationTime,obj.config.refreshAnimationType);}
 | 
				
			||||||
 | 
					obj,displayVal,color,max=null;};JustGage.prototype.generateShadow=function(svg,defs){var obj=this;var sid="inner-shadow-"+obj.config.id;var gaussFilter,feOffset,feGaussianBlur,feComposite1,feFlood,feComposite2,feComposite3;gaussFilter=document.createElementNS(svg,"filter");gaussFilter.setAttribute("id",sid);defs.appendChild(gaussFilter);feOffset=document.createElementNS(svg,"feOffset");feOffset.setAttribute("dx",0);feOffset.setAttribute("dy",obj.config.shadowVerticalOffset);gaussFilter.appendChild(feOffset);feGaussianBlur=document.createElementNS(svg,"feGaussianBlur");feGaussianBlur.setAttribute("result","offset-blur");feGaussianBlur.setAttribute("stdDeviation",obj.config.shadowSize);gaussFilter.appendChild(feGaussianBlur);feComposite1=document.createElementNS(svg,"feComposite");feComposite1.setAttribute("operator","out");feComposite1.setAttribute("in","SourceGraphic");feComposite1.setAttribute("in2","offset-blur");feComposite1.setAttribute("result","inverse");gaussFilter.appendChild(feComposite1);feFlood=document.createElementNS(svg,"feFlood");feFlood.setAttribute("flood-color","black");feFlood.setAttribute("flood-opacity",obj.config.shadowOpacity);feFlood.setAttribute("result","color");gaussFilter.appendChild(feFlood);feComposite2=document.createElementNS(svg,"feComposite");feComposite2.setAttribute("operator","in");feComposite2.setAttribute("in","color");feComposite2.setAttribute("in2","inverse");feComposite2.setAttribute("result","shadow");gaussFilter.appendChild(feComposite2);feComposite3=document.createElementNS(svg,"feComposite");feComposite3.setAttribute("operator","over");feComposite3.setAttribute("in","shadow");feComposite3.setAttribute("in2","SourceGraphic");gaussFilter.appendChild(feComposite3);if(!obj.config.hideInnerShadow){obj.canvas.canvas.childNodes[2].setAttribute("filter","url(#"+sid+")");obj.canvas.canvas.childNodes[3].setAttribute("filter","url(#"+sid+")");}
 | 
				
			||||||
 | 
					gaussFilter,feOffset,feGaussianBlur,feComposite1,feFlood,feComposite2,feComposite3=null;};function kvLookup(key,tablea,tableb,defval,datatype,delimiter){var val=defval;var canConvert=false;if(!(key===null||key===undefined)){if(tableb!==null&&tableb!==undefined&&typeof tableb==="object"&&key in tableb){val=tableb[key];canConvert=true;}else if(tablea!==null&&tablea!==undefined&&typeof tablea==="object"&&key in tablea){val=tablea[key];canConvert=true;}else{val=defval;}
 | 
				
			||||||
 | 
					if(canConvert===true){if(datatype!==null&&datatype!==undefined){switch(datatype){case'int':val=parseInt(val,10);break;case'float':val=parseFloat(val);break;default:break;}}}}
 | 
				
			||||||
 | 
					return val;};function getColor(val,pct,col,noGradient,custSec){var no,inc,colors,percentage,rval,gval,bval,lower,upper,range,rangePct,pctLower,pctUpper,color;var noGradient=noGradient||custSec.length>0;if(custSec.length>0){for(var i=0;i<custSec.length;i++){if(val>custSec[i].lo&&val<=custSec[i].hi){return custSec[i].color;}}}
 | 
				
			||||||
 | 
					no=col.length;if(no===1)
 | 
				
			||||||
 | 
					return col[0];inc=(noGradient)?(1/no):(1/(no-1));colors=[];for(i=0;i<col.length;i++){percentage=(noGradient)?(inc*(i+1)):(inc*i);rval=parseInt((cutHex(col[i])).substring(0,2),16);gval=parseInt((cutHex(col[i])).substring(2,4),16);bval=parseInt((cutHex(col[i])).substring(4,6),16);colors[i]={pct:percentage,color:{r:rval,g:gval,b:bval}};}
 | 
				
			||||||
 | 
					if(pct===0){return'rgb('+[colors[0].color.r,colors[0].color.g,colors[0].color.b].join(',')+')';}
 | 
				
			||||||
 | 
					for(var j=0;j<colors.length;j++){if(pct<=colors[j].pct){if(noGradient){return'rgb('+[colors[j].color.r,colors[j].color.g,colors[j].color.b].join(',')+')';}else{lower=colors[j-1];upper=colors[j];range=upper.pct-lower.pct;rangePct=(pct-lower.pct)/range;pctLower=1-rangePct;pctUpper=rangePct;color={r:Math.floor(lower.color.r*pctLower+upper.color.r*pctUpper),g:Math.floor(lower.color.g*pctLower+upper.color.g*pctUpper),b:Math.floor(lower.color.b*pctLower+upper.color.b*pctUpper)};return'rgb('+[color.r,color.g,color.b].join(',')+')';}}}}
 | 
				
			||||||
 | 
					function setDy(elem,fontSize,txtYpos){if((!ie||ie>9)&&elem.node.firstChild.attributes.dy){elem.node.firstChild.attributes.dy.value=0;}}
 | 
				
			||||||
 | 
					function getRandomInt(min,max){return Math.floor(Math.random()*(max-min+1))+min;}
 | 
				
			||||||
 | 
					function cutHex(str){return(str.charAt(0)=="#")?str.substring(1,7):str;}
 | 
				
			||||||
 | 
					function humanFriendlyNumber(n,d){var p,d2,i,s;p=Math.pow;d2=p(10,d);i=7;while(i){s=p(10,i-- *3);if(s<=n){n=Math.round(n*d2/s)/d2+"KMGTPE"[i];}}
 | 
				
			||||||
 | 
					return n;}
 | 
				
			||||||
 | 
					function formatNumber(x){var parts=x.toString().split(".");parts[0]=parts[0].replace(/\B(?=(\d{3})+(?!\d))/g,",");return parts.join(".");}
 | 
				
			||||||
 | 
					function getStyle(oElm,strCssRule){var strValue="";if(document.defaultView&&document.defaultView.getComputedStyle){strValue=document.defaultView.getComputedStyle(oElm,"").getPropertyValue(strCssRule);}else if(oElm.currentStyle){strCssRule=strCssRule.replace(/\-(\w)/g,function(strMatch,p1){return p1.toUpperCase();});strValue=oElm.currentStyle[strCssRule];}
 | 
				
			||||||
 | 
					return strValue;}
 | 
				
			||||||
 | 
					function onCreateElementNsReady(func){if(document.createElementNS!==undefined){func();}else{setTimeout(function(){onCreateElementNsReady(func);},100);}}
 | 
				
			||||||
 | 
					var ie=(function(){var undef,v=3,div=document.createElement('div'),all=div.getElementsByTagName('i');while(div.innerHTML='<!--[if gt IE '+(++v)+']><i></i><![endif]-->',all[0]);return v>4?v:undef;}
 | 
				
			||||||
 | 
					());function extend(out){out=out||{};for(var i=1;i<arguments.length;i++){if(!arguments[i])
 | 
				
			||||||
 | 
					continue;for(var key in arguments[i]){if(arguments[i].hasOwnProperty(key))
 | 
				
			||||||
 | 
					out[key]=arguments[i][key];}}
 | 
				
			||||||
 | 
					return out;};
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								examples/SmartSwitch/data_src/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										599
									
								
								examples/SmartSwitch/data_src/index.htm
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,599 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
 | 
				
			||||||
 | 
					    <title>Smart Switch</title>
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width">
 | 
				
			||||||
 | 
					    <link rel="apple-touch-icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="icon" href="/favicon.ico" type="image/x-icon" />
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="app.css" type="text/css" />
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        body {
 | 
				
			||||||
 | 
					            font-family: arial;
 | 
				
			||||||
 | 
					            color: #999
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        table {
 | 
				
			||||||
 | 
					            margin: auto;
 | 
				
			||||||
 | 
					            max-width: 600px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        td {
 | 
				
			||||||
 | 
					            padding: 1%;
 | 
				
			||||||
 | 
					            padding-inline: 2%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input[type=text] {
 | 
				
			||||||
 | 
					            width: 70%;
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            padding: 0px;
 | 
				
			||||||
 | 
					            box-sizing: border-box;
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            border-bottom: 1px solid #2196f3;
 | 
				
			||||||
 | 
					            font-size: 22px;
 | 
				
			||||||
 | 
					            color: #2196f3;
 | 
				
			||||||
 | 
					            font-family: arial;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input[type=button] {
 | 
				
			||||||
 | 
					            background-color: #2196f3;
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            color: #fff;
 | 
				
			||||||
 | 
					            padding: 10px 10px;
 | 
				
			||||||
 | 
					            width: 62px;
 | 
				
			||||||
 | 
					            text-decoration: none;
 | 
				
			||||||
 | 
					            margin: 4px 2px;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            border-radius: 34px;
 | 
				
			||||||
 | 
					            font-family: arial;
 | 
				
			||||||
 | 
					            font-weight: 700;
 | 
				
			||||||
 | 
					            -webkit-appearance: none;
 | 
				
			||||||
 | 
					            -moz-appearance: none;
 | 
				
			||||||
 | 
					            appearance: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .switch {
 | 
				
			||||||
 | 
					            position: relative;
 | 
				
			||||||
 | 
					            display: inline-block;
 | 
				
			||||||
 | 
					            width: 60px;
 | 
				
			||||||
 | 
					            height: 34px
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .switch input {
 | 
				
			||||||
 | 
					            opacity: 0;
 | 
				
			||||||
 | 
					            width: 0;
 | 
				
			||||||
 | 
					            height: 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .slider {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            right: 0;
 | 
				
			||||||
 | 
					            bottom: 0;
 | 
				
			||||||
 | 
					            background-color: #ccc;
 | 
				
			||||||
 | 
					            -webkit-transition: .4s;
 | 
				
			||||||
 | 
					            transition: .4s
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .slider:before {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            content: "";
 | 
				
			||||||
 | 
					            height: 26px;
 | 
				
			||||||
 | 
					            width: 26px;
 | 
				
			||||||
 | 
					            left: 4px;
 | 
				
			||||||
 | 
					            bottom: 4px;
 | 
				
			||||||
 | 
					            background-color: #fff;
 | 
				
			||||||
 | 
					            -webkit-transition: .4s;
 | 
				
			||||||
 | 
					            transition: .4s
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input:checked+.slider {
 | 
				
			||||||
 | 
					            background-color: #2196f3
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input:focus+.slider {
 | 
				
			||||||
 | 
					            box-shadow: 0 0 1px #2196f3
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        input:checked+.slider:before {
 | 
				
			||||||
 | 
					            -webkit-transform: translateX(26px);
 | 
				
			||||||
 | 
					            -ms-transform: translateX(26px);
 | 
				
			||||||
 | 
					            transform: translateX(26px)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .slider.round {
 | 
				
			||||||
 | 
					            border-radius: 34px
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .slider.round:before {
 | 
				
			||||||
 | 
					            border-radius: 50%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .clk {
 | 
				
			||||||
 | 
					            font-size: 54px;
 | 
				
			||||||
 | 
					            color: #444;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .clear:after,
 | 
				
			||||||
 | 
					        .clear:before {
 | 
				
			||||||
 | 
					            content: "";
 | 
				
			||||||
 | 
					            display: table
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .clear:after {
 | 
				
			||||||
 | 
					            clear: both
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .wrapper {
 | 
				
			||||||
 | 
					            position: relative;
 | 
				
			||||||
 | 
					            top: 0px;
 | 
				
			||||||
 | 
					            right: 0;
 | 
				
			||||||
 | 
					            bottom: 0px;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            margin: auto;
 | 
				
			||||||
 | 
					            max-width: 500px;
 | 
				
			||||||
 | 
					            border: 0px solid #ccc
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .gauge {
 | 
				
			||||||
 | 
					            display: block;
 | 
				
			||||||
 | 
					            float: left
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        #g1 {
 | 
				
			||||||
 | 
					            width: 50%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        #g2 {
 | 
				
			||||||
 | 
					            width: 50%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .son {
 | 
				
			||||||
 | 
					            color: green;
 | 
				
			||||||
 | 
					            font-weight: bold
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .soff {
 | 
				
			||||||
 | 
					            color: red;
 | 
				
			||||||
 | 
					            font-weight: bold
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .blinking {
 | 
				
			||||||
 | 
					            animation: blinkingText 1.2s infinite;
 | 
				
			||||||
 | 
					            -webkit-animation: blinkingText 1.2s infinite;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @keyframes blinkingText {
 | 
				
			||||||
 | 
					            0% {
 | 
				
			||||||
 | 
					                color: #ec0b0b;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            49% {
 | 
				
			||||||
 | 
					                color: #ea7272;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            60% {
 | 
				
			||||||
 | 
					                color: transparent;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            99% {
 | 
				
			||||||
 | 
					                color: transparent;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            100% {
 | 
				
			||||||
 | 
					                color: #ff0404;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container {
 | 
				
			||||||
 | 
					            display: inline-block;
 | 
				
			||||||
 | 
					            position: relative;
 | 
				
			||||||
 | 
					            padding-left: 28px;
 | 
				
			||||||
 | 
					            padding-top: 4px;
 | 
				
			||||||
 | 
					            margin-top: 8px;
 | 
				
			||||||
 | 
					            margin-bottom: 8px;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            font-size: 14px;
 | 
				
			||||||
 | 
					            -webkit-user-select: none;
 | 
				
			||||||
 | 
					            -moz-user-select: none;
 | 
				
			||||||
 | 
					            -ms-user-select: none;
 | 
				
			||||||
 | 
					            user-select: none
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container input {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            opacity: 0;
 | 
				
			||||||
 | 
					            cursor: pointer
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .checkmark {
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            top: 0;
 | 
				
			||||||
 | 
					            left: 0;
 | 
				
			||||||
 | 
					            height: 25px;
 | 
				
			||||||
 | 
					            width: 25px;
 | 
				
			||||||
 | 
					            background-color: #eee;
 | 
				
			||||||
 | 
					            border-radius: 50%
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container:hover input ~ .checkmark {
 | 
				
			||||||
 | 
					            background-color: #ccc
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container input:checked ~ .checkmark {
 | 
				
			||||||
 | 
					            background-color: #2196F3
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .checkmark:after {
 | 
				
			||||||
 | 
					            content: "";
 | 
				
			||||||
 | 
					            position: absolute;
 | 
				
			||||||
 | 
					            display: none
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container input:checked ~ .checkmark:after {
 | 
				
			||||||
 | 
					            display: block
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .container .checkmark:after {
 | 
				
			||||||
 | 
					            top: 6px;
 | 
				
			||||||
 | 
					            left: 6px;
 | 
				
			||||||
 | 
					            width: 13px;
 | 
				
			||||||
 | 
					            height: 13px;
 | 
				
			||||||
 | 
					            border-radius: 50%;
 | 
				
			||||||
 | 
					            background: white
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <table align="center">
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td colspan=3>
 | 
				
			||||||
 | 
					                <form name="sched">
 | 
				
			||||||
 | 
					                    <label class="container" id="LZ0">Auto   
 | 
				
			||||||
 | 
					                        <input type="radio" name="radio" onclick="handleClick(this);" value="Z0"><span class="checkmark"></span></label>
 | 
				
			||||||
 | 
					                    <label class="container" id="LZ1">M-F
 | 
				
			||||||
 | 
					                        <input type="radio" name="radio" onclick="handleClick(this);" value="Z1"><span class="checkmark"></span></label>
 | 
				
			||||||
 | 
					                    <label class="container" id="LZ2">Sat
 | 
				
			||||||
 | 
					                        <input type="radio" name="radio" onclick="handleClick(this);" value="Z2"><span class="checkmark"></span></label>
 | 
				
			||||||
 | 
					                    <label class="container" id="LZ3">Sun
 | 
				
			||||||
 | 
					                        <input type="radio" name="radio" onclick="handleClick(this);" value="Z3"><span class="checkmark"></span></label>
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td onclick="ent1Click();">
 | 
				
			||||||
 | 
					                <label for="input-temperature">Temp °C</label>
 | 
				
			||||||
 | 
					                <input type="text" readonly="readonly" id="input-temperature" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td onclick="ent2Click();">
 | 
				
			||||||
 | 
					                <label for="input-popup-start">Time On</label>
 | 
				
			||||||
 | 
					                <input type="text" readonly="readonly" id="input-popup-start" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td onclick="ent2Click();">
 | 
				
			||||||
 | 
					                <label for="input-popup-stop">Time Off</label>
 | 
				
			||||||
 | 
					                <input type="text" readonly="readonly" id="input-popup-stop" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <input type="button" id="W" value="Temp" onclick="button2Click(this);" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <label class="switch">
 | 
				
			||||||
 | 
					                    <input id="cbStyle" type="checkbox" onclick="checkboxClick(this);"><span class="slider round"></span></label>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <input type="button" id="T" value="Timer" onclick="buttonClick(this);" />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <div id="reconnect">
 | 
				
			||||||
 | 
					                    <input type="button" id="N" value="WSoff" onclick="statusWs();" />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td><span id="sid"></span></td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <div id="hw-reset">
 | 
				
			||||||
 | 
					                    <input type="button" id="R" value="HWrst" onclick="loadDoc('hw-reset', 1);" />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        <tr align="center">
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <div id="hbut">
 | 
				
			||||||
 | 
					                    <input type="button" id="H" value="Heap" onclick="loadDoc('free-ram', 0);" />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <div id="free-ram"></div>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					            <td>
 | 
				
			||||||
 | 
					                <div id="ebut">
 | 
				
			||||||
 | 
					                    <input type="button" id="E" value="WEdit" onclick="buttonEClick();" />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					    </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="wrapper clear">
 | 
				
			||||||
 | 
					        <div id="g1" class="gauge"></div>
 | 
				
			||||||
 | 
					        <div id="g2" class="gauge"></div>
 | 
				
			||||||
 | 
					        <center>
 | 
				
			||||||
 | 
					            <label id="C" class="clk"></label>
 | 
				
			||||||
 | 
					        </center>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script src="app.min.js"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script type="text/javascript">
 | 
				
			||||||
 | 
					        const MYCORS = '192.168.0.12';
 | 
				
			||||||
 | 
					        var g1, g2;
 | 
				
			||||||
 | 
					        var Analog0 = new Array();
 | 
				
			||||||
 | 
					        var auto = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const successNotification = window.createNotification({
 | 
				
			||||||
 | 
					            positionClass: 'nfc-bottom-right',
 | 
				
			||||||
 | 
					            theme: 'info',
 | 
				
			||||||
 | 
					            showDuration: 3000
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const warningNotification = window.createNotification({
 | 
				
			||||||
 | 
					            positionClass: 'nfc-bottom-right',
 | 
				
			||||||
 | 
					            theme: 'warning',
 | 
				
			||||||
 | 
					            showDuration: 6000
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.addEventListener("DOMContentLoaded", function(event) {
 | 
				
			||||||
 | 
					            console.log("DOM fully loaded and parsed");
 | 
				
			||||||
 | 
					            g1 = new JustGage({
 | 
				
			||||||
 | 
					                id: "g1",
 | 
				
			||||||
 | 
					                value: -5.5,
 | 
				
			||||||
 | 
					                min: -40,
 | 
				
			||||||
 | 
					                max: 50,
 | 
				
			||||||
 | 
					                decimals: 1,
 | 
				
			||||||
 | 
					                title: "Temperature",
 | 
				
			||||||
 | 
					                titlePosition: "below",
 | 
				
			||||||
 | 
					                label: "°C",
 | 
				
			||||||
 | 
					                relativeGaugeSize: true,
 | 
				
			||||||
 | 
					                pointer: true,
 | 
				
			||||||
 | 
					                customSectors: [{
 | 
				
			||||||
 | 
					                    color: "#328da8",
 | 
				
			||||||
 | 
					                    lo: -40,
 | 
				
			||||||
 | 
					                    hi: 0
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                    color: "#32a852",
 | 
				
			||||||
 | 
					                    lo: 0,
 | 
				
			||||||
 | 
					                    hi: 25
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                    color: "#ff4d4d",
 | 
				
			||||||
 | 
					                    lo: 25,
 | 
				
			||||||
 | 
					                    hi: 50
 | 
				
			||||||
 | 
					                }],
 | 
				
			||||||
 | 
					                formatNumber: true
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            g2 = new JustGage({
 | 
				
			||||||
 | 
					                id: "g2",
 | 
				
			||||||
 | 
					                value: 40.8,
 | 
				
			||||||
 | 
					                min: 0,
 | 
				
			||||||
 | 
					                max: 100,
 | 
				
			||||||
 | 
					                decimals: 1,
 | 
				
			||||||
 | 
					                title: "Humidity",
 | 
				
			||||||
 | 
					                titlePosition: "below",
 | 
				
			||||||
 | 
					                label: "%",
 | 
				
			||||||
 | 
					                relativeGaugeSize: true,
 | 
				
			||||||
 | 
					                pointer: true,
 | 
				
			||||||
 | 
					                customSectors: [{
 | 
				
			||||||
 | 
					                    color: "#ffc926",
 | 
				
			||||||
 | 
					                    lo: 0,
 | 
				
			||||||
 | 
					                    hi: 45
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                    color: "#32a852",
 | 
				
			||||||
 | 
					                    lo: 45,
 | 
				
			||||||
 | 
					                    hi: 55
 | 
				
			||||||
 | 
					                }, {
 | 
				
			||||||
 | 
					                    color: "#328da8",
 | 
				
			||||||
 | 
					                    lo: 55,
 | 
				
			||||||
 | 
					                    hi: 100
 | 
				
			||||||
 | 
					                }],
 | 
				
			||||||
 | 
					                formatNumber: true
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var servurl = document.location.host;
 | 
				
			||||||
 | 
					        if (servurl.length < 5) servurl = MYCORS;
 | 
				
			||||||
 | 
					        var connection = new WebSocket('ws://' + servurl + '/ws', ['arduino']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.onopen = function() {
 | 
				
			||||||
 | 
					            //connection.send('get_something');
 | 
				
			||||||
 | 
					            document.getElementById("sid").className = "son";
 | 
				
			||||||
 | 
					            document.getElementById('sid').innerHTML = "Smart Switch";
 | 
				
			||||||
 | 
					            console.log("connection opened");
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.onclose = function() {
 | 
				
			||||||
 | 
					            document.getElementById("sid").className = "blinking";
 | 
				
			||||||
 | 
					            document.getElementById('sid').innerHTML = "Detached";
 | 
				
			||||||
 | 
					            document.getElementById("N").value = "WSon";
 | 
				
			||||||
 | 
					            console.log("connection closed");
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.onerror = function(error) {
 | 
				
			||||||
 | 
					            document.getElementById("sid").className = "soff";
 | 
				
			||||||
 | 
					            document.getElementById('sid').innerHTML = "Detached";
 | 
				
			||||||
 | 
					            document.getElementById("N").value = "WSon";
 | 
				
			||||||
 | 
					            console.log('WebSocket Error ', error);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.onmessage = function(evt) {
 | 
				
			||||||
 | 
					            // handle websocket message. update attributes or values of elements that match the name on incoming message
 | 
				
			||||||
 | 
					            console.log("msg rec", evt.data);
 | 
				
			||||||
 | 
					            var msgArray = evt.data.split(","); // split message by delimiter into a string array
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[0]);
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[1]);
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[2]);
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[3]);
 | 
				
			||||||
 | 
					            console.log("msgArray", msgArray[4]);
 | 
				
			||||||
 | 
					            var indicator = msgArray[1]; // the first element in the message array is the ID of the object to update
 | 
				
			||||||
 | 
					            console.log("indiactor", indicator);
 | 
				
			||||||
 | 
					            var a = document.getElementById('cbStyle');
 | 
				
			||||||
 | 
					            if (indicator) // if an object by the name of the message exists, update its value or its attributes
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                switch (msgArray[1]) {
 | 
				
			||||||
 | 
					                    case "Arduino":
 | 
				
			||||||
 | 
					                        console.log("Got Temp / Humidity");
 | 
				
			||||||
 | 
					                        g1.refresh(msgArray[2], null);
 | 
				
			||||||
 | 
					                        g2.refresh(msgArray[3], null);
 | 
				
			||||||
 | 
					                        if (msgArray[4] == "1") document.getElementById("T").style.backgroundColor = "#32a852";
 | 
				
			||||||
 | 
					                        else document.getElementById("T").style.backgroundColor = "#2196f3";
 | 
				
			||||||
 | 
					                        var delta = (parseFloat(document.getElementById('input-temperature').value).toFixed(1) - parseFloat(msgArray[2]).toFixed(1));
 | 
				
			||||||
 | 
					                        if (delta > 0.5) document.getElementById("W").style.backgroundColor = "red";
 | 
				
			||||||
 | 
					                        else if (delta < -0.5) document.getElementById("W").style.backgroundColor = "#2196f3";
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "Clock":
 | 
				
			||||||
 | 
										            	var dn = parseInt(msgArray[3]);
 | 
				
			||||||
 | 
					                        var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
 | 
				
			||||||
 | 
					                        var dayName = days[dn] || '*'; // 0...6
 | 
				
			||||||
 | 
											            var shedtype = 0; 
 | 
				
			||||||
 | 
										              if (dn == 0) shedtype = 3;
 | 
				
			||||||
 | 
											            else if (dn == 6) shedtype = 2;
 | 
				
			||||||
 | 
											            else if ((dn > 0) && (dn < 6)) shedtype = 1;
 | 
				
			||||||
 | 
					                        document.getElementById('C').innerHTML = msgArray[2] + ' ' + dayName;
 | 
				
			||||||
 | 
											            if (auto) document.getElementById('LZ' + shedtype).classList.add('son');
 | 
				
			||||||
 | 
											            else document.getElementById('LZ' + shedtype).classList.remove('son');
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "Setting":
 | 
				
			||||||
 | 
					                        document.getElementById('input-popup-start').value = msgArray[2];
 | 
				
			||||||
 | 
					                        document.getElementById('input-popup-stop').value = msgArray[3];
 | 
				
			||||||
 | 
					                        document.getElementById('input-temperature').value = parseFloat(msgArray[4]).toFixed(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        document.getElementById('input-popup-start').className = "";
 | 
				
			||||||
 | 
					                        document.getElementById('input-popup-stop').className = "";
 | 
				
			||||||
 | 
					                        document.getElementById('input-temperature').className = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        tpick.attach("input-popup-start");
 | 
				
			||||||
 | 
					                        tpick.attach("input-popup-stop");
 | 
				
			||||||
 | 
					                        fpick.attach("input-temperature");
 | 
				
			||||||
 | 
					                        warningNotification({
 | 
				
			||||||
 | 
					                            message: 'Client UPD'
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "ledon":
 | 
				
			||||||
 | 
					                        a.checked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "ledoff":
 | 
				
			||||||
 | 
					                        a.checked = false;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "remoff":
 | 
				
			||||||
 | 
					                        successNotification({
 | 
				
			||||||
 | 
					                            message: 'Client quit'
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "OTA":
 | 
				
			||||||
 | 
					                        warningNotification({
 | 
				
			||||||
 | 
					                            message: 'OTA begin'
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        statusWs();
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "sched":
 | 
				
			||||||
 | 
					                        document.sched.radio[msgArray[2]].checked = true;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    default:
 | 
				
			||||||
 | 
					                        // unrecognized message type. do nothing
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                } // switch
 | 
				
			||||||
 | 
					            } // if (indicator)
 | 
				
			||||||
 | 
					        }; // connection.onmessage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function buttonClick(e) {
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) {
 | 
				
			||||||
 | 
					                connection.send(e.id + document.getElementById("input-popup-start").value + "|" + document.getElementById("input-popup-stop").value + "|");
 | 
				
			||||||
 | 
					                successNotification({
 | 
				
			||||||
 | 
					                    message: 'Timer REQ'
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function button2Click(e) {
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) {
 | 
				
			||||||
 | 
					                connection.send(e.id + parseFloat(document.getElementById("input-temperature").value).toFixed(1) + "|");
 | 
				
			||||||
 | 
					                successNotification({
 | 
				
			||||||
 | 
					                    message: 'Temp. REQ'
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function buttonEClick() {
 | 
				
			||||||
 | 
					            var murl = '/edit';
 | 
				
			||||||
 | 
					            if (document.location.host.length < 5) murl = 'http://' + MYCORS + '/edit'; //CORS
 | 
				
			||||||
 | 
					            window.open(murl, '_blank')
 | 
				
			||||||
 | 
					            warningNotification({
 | 
				
			||||||
 | 
					                message: 'Editor'
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function checkboxClick(e) {
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) {
 | 
				
			||||||
 | 
					                if (e.checked) connection.send('L1');
 | 
				
			||||||
 | 
					                else connection.send('L0');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function ent1Click() {
 | 
				
			||||||
 | 
					            document.getElementById("input-temperature").className = "blinking";
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function ent2Click() {
 | 
				
			||||||
 | 
					            document.getElementById("input-popup-stop").className = "blinking";
 | 
				
			||||||
 | 
					            document.getElementById("input-popup-start").className = "blinking";
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function handleClick(e) {
 | 
				
			||||||
 | 
					            if (e.value == 'Z0' ) auto = true;
 | 
				
			||||||
 | 
					            else auto = false;
 | 
				
			||||||
 | 
					            document.getElementById('L' + e.value).classList.remove('son');
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) connection.send(e.value + "|");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // XMLHttpRequest /non WebSocket/ command. same as command' div' id to get response to
 | 
				
			||||||
 | 
					        function loadDoc(cmd, r) {
 | 
				
			||||||
 | 
					            var murl = '/' + cmd;
 | 
				
			||||||
 | 
					            if (document.location.host.length < 5) murl = 'http://' + MYCORS + '/' + cmd; //CORS
 | 
				
			||||||
 | 
					            var xhttp = new XMLHttpRequest();
 | 
				
			||||||
 | 
					            xhttp.onreadystatechange = function() {
 | 
				
			||||||
 | 
					                if (this.readyState == 4 && this.status == 200) {
 | 
				
			||||||
 | 
					                    document.getElementById(cmd).innerHTML = this.responseText;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            xhttp.open("GET", murl, true);
 | 
				
			||||||
 | 
					            xhttp.send();
 | 
				
			||||||
 | 
					            warningNotification({
 | 
				
			||||||
 | 
					                message: 'Cmd: ' + cmd
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            if (r) { //restart?
 | 
				
			||||||
 | 
					                connection.close();
 | 
				
			||||||
 | 
					                document.getElementById("N").value = "WSon";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function statusWs() {
 | 
				
			||||||
 | 
					            if (connection.readyState === WebSocket.OPEN) {
 | 
				
			||||||
 | 
					                connection.close();
 | 
				
			||||||
 | 
					                document.getElementById("N").value = "WSon";
 | 
				
			||||||
 | 
					                warningNotification({
 | 
				
			||||||
 | 
					                    message: 'WS Closed'
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                window.location.reload();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										2
									
								
								examples/SmartSwitch/data_src/js_css_src/.exclude.files
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					/*.js.gz
 | 
				
			||||||
 | 
					/.exclude.files
 | 
				
			||||||