mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'beta'
This commit is contained in:
		
							
								
								
									
										137
									
								
								.clang-format
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								.clang-format
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| Language:        Cpp | ||||
| AccessModifierOffset: -1 | ||||
| AlignAfterOpenBracket: Align | ||||
| AlignConsecutiveAssignments: false | ||||
| AlignConsecutiveDeclarations: false | ||||
| AlignEscapedNewlines: DontAlign | ||||
| AlignOperands:   true | ||||
| AlignTrailingComments: true | ||||
| AllowAllParametersOfDeclarationOnNextLine: true | ||||
| AllowShortBlocksOnASingleLine: false | ||||
| AllowShortCaseLabelsOnASingleLine: false | ||||
| AllowShortFunctionsOnASingleLine: All | ||||
| AllowShortIfStatementsOnASingleLine: false | ||||
| AllowShortLoopsOnASingleLine: false | ||||
| AlwaysBreakAfterReturnType: None | ||||
| AlwaysBreakBeforeMultilineStrings: false | ||||
| AlwaysBreakTemplateDeclarations: MultiLine | ||||
| BinPackArguments: true | ||||
| BinPackParameters: true | ||||
| BraceWrapping: | ||||
|   AfterClass:      false | ||||
|   AfterControlStatement: false | ||||
|   AfterEnum:       false | ||||
|   AfterFunction:   false | ||||
|   AfterNamespace:  false | ||||
|   AfterObjCDeclaration: false | ||||
|   AfterStruct:     false | ||||
|   AfterUnion:      false | ||||
|   AfterExternBlock: false | ||||
|   BeforeCatch:     false | ||||
|   BeforeElse:      false | ||||
|   IndentBraces:    false | ||||
|   SplitEmptyFunction: true | ||||
|   SplitEmptyRecord: true | ||||
|   SplitEmptyNamespace: true | ||||
| BreakBeforeBinaryOperators: None | ||||
| BreakBeforeBraces: Attach | ||||
| BreakBeforeInheritanceComma: false | ||||
| BreakInheritanceList: BeforeColon | ||||
| BreakBeforeTernaryOperators: true | ||||
| BreakConstructorInitializersBeforeComma: false | ||||
| BreakConstructorInitializers: BeforeColon | ||||
| BreakAfterJavaFieldAnnotations: false | ||||
| BreakStringLiterals: true | ||||
| ColumnLimit:     120 | ||||
| CommentPragmas:  '^ IWYU pragma:' | ||||
| CompactNamespaces: false | ||||
| ConstructorInitializerAllOnOneLineOrOnePerLine: true | ||||
| ConstructorInitializerIndentWidth: 4 | ||||
| ContinuationIndentWidth: 4 | ||||
| Cpp11BracedListStyle: true | ||||
| DerivePointerAlignment: true | ||||
| DisableFormat:   false | ||||
| ExperimentalAutoDetectBinPacking: false | ||||
| FixNamespaceComments: true | ||||
| ForEachMacros: | ||||
|   - foreach | ||||
|   - Q_FOREACH | ||||
|   - BOOST_FOREACH | ||||
| IncludeBlocks:   Preserve | ||||
| IncludeCategories: | ||||
|   - Regex:           '^<ext/.*\.h>' | ||||
|     Priority:        2 | ||||
|   - Regex:           '^<.*\.h>' | ||||
|     Priority:        1 | ||||
|   - Regex:           '^<.*' | ||||
|     Priority:        2 | ||||
|   - Regex:           '.*' | ||||
|     Priority:        3 | ||||
| IncludeIsMainRegex: '([-_](test|unittest))?$' | ||||
| IndentCaseLabels: true | ||||
| IndentPPDirectives: None | ||||
| IndentWidth:     2 | ||||
| IndentWrappedFunctionNames: false | ||||
| KeepEmptyLinesAtTheStartOfBlocks: false | ||||
| MacroBlockBegin: '' | ||||
| MacroBlockEnd:   '' | ||||
| MaxEmptyLinesToKeep: 1 | ||||
| NamespaceIndentation: None | ||||
| PenaltyBreakAssignment: 2 | ||||
| PenaltyBreakBeforeFirstCallParameter: 1 | ||||
| PenaltyBreakComment: 300 | ||||
| PenaltyBreakFirstLessLess: 120 | ||||
| PenaltyBreakString: 1000 | ||||
| PenaltyBreakTemplateDeclaration: 10 | ||||
| PenaltyExcessCharacter: 1000000 | ||||
| PenaltyReturnTypeOnItsOwnLine: 2000 | ||||
| PointerAlignment: Right | ||||
| RawStringFormats: | ||||
|   - Language:        Cpp | ||||
|     Delimiters: | ||||
|       - cc | ||||
|       - CC | ||||
|       - cpp | ||||
|       - Cpp | ||||
|       - CPP | ||||
|       - 'c++' | ||||
|       - 'C++' | ||||
|     CanonicalDelimiter: '' | ||||
|     BasedOnStyle:    google | ||||
|   - Language:        TextProto | ||||
|     Delimiters: | ||||
|       - pb | ||||
|       - PB | ||||
|       - proto | ||||
|       - PROTO | ||||
|     EnclosingFunctions: | ||||
|       - EqualsProto | ||||
|       - EquivToProto | ||||
|       - PARSE_PARTIAL_TEXT_PROTO | ||||
|       - PARSE_TEST_PROTO | ||||
|       - PARSE_TEXT_PROTO | ||||
|       - ParseTextOrDie | ||||
|       - ParseTextProtoOrDie | ||||
|     CanonicalDelimiter: '' | ||||
|     BasedOnStyle:    google | ||||
| ReflowComments:  true | ||||
| SortIncludes:    false | ||||
| SortUsingDeclarations: false | ||||
| SpaceAfterCStyleCast: true | ||||
| SpaceAfterTemplateKeyword: false | ||||
| SpaceBeforeAssignmentOperators: true | ||||
| SpaceBeforeCpp11BracedList: false | ||||
| SpaceBeforeCtorInitializerColon: true | ||||
| SpaceBeforeInheritanceColon: true | ||||
| SpaceBeforeParens: ControlStatements | ||||
| SpaceBeforeRangeBasedForLoopColon: true | ||||
| SpaceInEmptyParentheses: false | ||||
| SpacesBeforeTrailingComments: 2 | ||||
| SpacesInAngles:  false | ||||
| SpacesInContainerLiterals: false | ||||
| SpacesInCStyleCastParentheses: false | ||||
| SpacesInParentheses: false | ||||
| SpacesInSquareBrackets: false | ||||
| Standard:        Auto | ||||
| TabWidth:        2 | ||||
| UseTab:          Never | ||||
							
								
								
									
										127
									
								
								.clang-tidy
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								.clang-tidy
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| --- | ||||
| Checks: >- | ||||
|   *, | ||||
|   -abseil-*, | ||||
|   -android-*, | ||||
|   -boost-*, | ||||
|   -bugprone-macro-parentheses, | ||||
|   -cert-dcl50-cpp, | ||||
|   -cert-err58-cpp, | ||||
|   -clang-analyzer-core.CallAndMessage, | ||||
|   -clang-analyzer-osx.*, | ||||
|   -clang-analyzer-security.*, | ||||
|   -cppcoreguidelines-avoid-goto, | ||||
|   -cppcoreguidelines-c-copy-assignment-signature, | ||||
|   -cppcoreguidelines-owning-memory, | ||||
|   -cppcoreguidelines-pro-bounds-array-to-pointer-decay, | ||||
|   -cppcoreguidelines-pro-bounds-constant-array-index, | ||||
|   -cppcoreguidelines-pro-bounds-pointer-arithmetic, | ||||
|   -cppcoreguidelines-pro-type-const-cast, | ||||
|   -cppcoreguidelines-pro-type-cstyle-cast, | ||||
|   -cppcoreguidelines-pro-type-member-init, | ||||
|   -cppcoreguidelines-pro-type-reinterpret-cast, | ||||
|   -cppcoreguidelines-pro-type-static-cast-downcast, | ||||
|   -cppcoreguidelines-pro-type-union-access, | ||||
|   -cppcoreguidelines-pro-type-vararg, | ||||
|   -cppcoreguidelines-special-member-functions, | ||||
|   -fuchsia-*, | ||||
|   -fuchsia-default-arguments, | ||||
|   -fuchsia-multiple-inheritance, | ||||
|   -fuchsia-overloaded-operator, | ||||
|   -fuchsia-statically-constructed-objects, | ||||
|   -google-build-using-namespace, | ||||
|   -google-explicit-constructor, | ||||
|   -google-readability-braces-around-statements, | ||||
|   -google-readability-casting, | ||||
|   -google-readability-todo, | ||||
|   -google-runtime-int, | ||||
|   -google-runtime-references, | ||||
|   -hicpp-*, | ||||
|   -llvm-header-guard, | ||||
|   -llvm-include-order, | ||||
|   -misc-unconventional-assign-operator, | ||||
|   -misc-unused-parameters, | ||||
|   -modernize-deprecated-headers, | ||||
|   -modernize-pass-by-value, | ||||
|   -modernize-pass-by-value, | ||||
|   -modernize-return-braced-init-list, | ||||
|   -modernize-use-auto, | ||||
|   -modernize-use-default-member-init, | ||||
|   -modernize-use-equals-default, | ||||
|   -mpi-*, | ||||
|   -objc-*, | ||||
|   -performance-unnecessary-value-param, | ||||
|   -readability-braces-around-statements, | ||||
|   -readability-else-after-return, | ||||
|   -readability-implicit-bool-conversion, | ||||
|   -readability-named-parameter, | ||||
|   -readability-redundant-member-init, | ||||
|   -warnings-as-errors, | ||||
|   -zircon-* | ||||
| WarningsAsErrors: '*' | ||||
| HeaderFilterRegex: '^.*/src/esphome/.*' | ||||
| AnalyzeTemporaryDtors: false | ||||
| FormatStyle:     google | ||||
| CheckOptions: | ||||
|   - key:             google-readability-braces-around-statements.ShortStatementLines | ||||
|     value:           '1' | ||||
|   - key:             google-readability-function-size.StatementThreshold | ||||
|     value:           '800' | ||||
|   - key:             google-readability-namespace-comments.ShortNamespaceLines | ||||
|     value:           '10' | ||||
|   - key:             google-readability-namespace-comments.SpacesBeforeComments | ||||
|     value:           '2' | ||||
|   - key:             modernize-loop-convert.MaxCopySize | ||||
|     value:           '16' | ||||
|   - key:             modernize-loop-convert.MinConfidence | ||||
|     value:           reasonable | ||||
|   - key:             modernize-loop-convert.NamingStyle | ||||
|     value:           CamelCase | ||||
|   - key:             modernize-pass-by-value.IncludeStyle | ||||
|     value:           llvm | ||||
|   - key:             modernize-replace-auto-ptr.IncludeStyle | ||||
|     value:           llvm | ||||
|   - key:             modernize-use-nullptr.NullMacros | ||||
|     value:           'NULL' | ||||
|   - key:             readability-identifier-naming.LocalVariableCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.ClassCase | ||||
|     value:           'CamelCase' | ||||
|   - key:             readability-identifier-naming.StructCase | ||||
|     value:           'CamelCase' | ||||
|   - key:             readability-identifier-naming.EnumCase | ||||
|     value:           'CamelCase' | ||||
|   - key:             readability-identifier-naming.EnumConstantCase | ||||
|     value:           'UPPER_CASE' | ||||
|   - key:             readability-identifier-naming.StaticConstantCase | ||||
|     value:           'UPPER_CASE' | ||||
|   - key:             readability-identifier-naming.StaticVariableCase | ||||
|     value:           'UPPER_CASE' | ||||
|   - key:             readability-identifier-naming.GlobalConstantCase | ||||
|     value:           'UPPER_CASE' | ||||
|   - key:             readability-identifier-naming.ParameterCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.PrivateMemberPrefix | ||||
|     value:           'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED' | ||||
|   - key:             readability-identifier-naming.PrivateMethodPrefix | ||||
|     value:           'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED' | ||||
|   - key:             readability-identifier-naming.ClassMemberCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.ClassMemberCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.ProtectedMemberCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.ProtectedMemberSuffix | ||||
|     value:           '_' | ||||
|   - key:             readability-identifier-naming.FunctionCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.ClassMethodCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.ProtectedMethodCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.ProtectedMethodSuffix | ||||
|     value:           '_' | ||||
|   - key:             readability-identifier-naming.VirtualMethodCase | ||||
|     value:           'lower_case' | ||||
|   - key:             readability-identifier-naming.VirtualMethodSuffix | ||||
|     value:           '' | ||||
							
								
								
									
										27
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| root = true | ||||
|  | ||||
| # general | ||||
| [*] | ||||
| end_of_line = lf | ||||
| insert_final_newline = true | ||||
| charset = utf-8 | ||||
|  | ||||
| # python | ||||
| [*.{py}] | ||||
| indent_style = space | ||||
| indent_size = 4 | ||||
|  | ||||
| # C++ | ||||
| [*.{cpp,h,tcc}] | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
|  | ||||
| # Web | ||||
| [*.{js,html,css}] | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
|  | ||||
| # YAML | ||||
| [*.{yaml,yml}] | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
							
								
								
									
										3
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							| @@ -4,11 +4,10 @@ | ||||
| **Related issue (if applicable):** fixes <link to issue> | ||||
|  | ||||
| **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> | ||||
| **Pull request in [esphome-core](https://github.com/esphome/esphome-core) with C++ framework changes (if applicable):** esphome/esphome-core#<esphome-core PR number goes here> | ||||
|  | ||||
| ## Checklist: | ||||
|   - [ ] The code change is tested and works locally. | ||||
|   - [ ] Tests have been added to verify that the new code works (under `tests/` folder). | ||||
|  | ||||
| If user exposed functionality or configuration variables are added/changed: | ||||
|   - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs). | ||||
|   - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs). | ||||
|   | ||||
							
								
								
									
										93
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										93
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -6,6 +6,19 @@ __pycache__/ | ||||
| # C extensions | ||||
| *.so | ||||
|  | ||||
| # Hide sublime text stuff | ||||
| *.sublime-project | ||||
| *.sublime-workspace | ||||
|  | ||||
| # Hide some OS X stuff | ||||
| .DS_Store | ||||
| .AppleDouble | ||||
| .LSOverride | ||||
| Icon | ||||
|  | ||||
| # Thumbnails | ||||
| ._* | ||||
|  | ||||
| # Distribution / packaging | ||||
| .Python | ||||
| build/ | ||||
| @@ -25,12 +38,6 @@ wheels/ | ||||
| *.egg | ||||
| MANIFEST | ||||
|  | ||||
| # PyInstaller | ||||
| #  Usually these files are written by a python script from a template | ||||
| #  before PyInstaller builds the exe, so as to inject date/other infos into it. | ||||
| *.manifest | ||||
| *.spec | ||||
|  | ||||
| # Installer logs | ||||
| pip-log.txt | ||||
| pip-delete-this-directory.txt | ||||
| @@ -51,36 +58,9 @@ coverage.xml | ||||
| *.mo | ||||
| *.pot | ||||
|  | ||||
| # Django stuff: | ||||
| *.log | ||||
| local_settings.py | ||||
| db.sqlite3 | ||||
|  | ||||
| # Flask stuff: | ||||
| instance/ | ||||
| .webassets-cache | ||||
|  | ||||
| # Scrapy stuff: | ||||
| .scrapy | ||||
|  | ||||
| # Sphinx documentation | ||||
| docs/_build/ | ||||
|  | ||||
| # PyBuilder | ||||
| target/ | ||||
|  | ||||
| # Jupyter Notebook | ||||
| .ipynb_checkpoints | ||||
|  | ||||
| # pyenv | ||||
| .python-version | ||||
|  | ||||
| # celery beat schedule file | ||||
| celerybeat-schedule | ||||
|  | ||||
| # SageMath parsed files | ||||
| *.sage.py | ||||
|  | ||||
| # Environments | ||||
| .env | ||||
| .venv | ||||
| @@ -90,19 +70,46 @@ ENV/ | ||||
| env.bak/ | ||||
| venv.bak/ | ||||
|  | ||||
| # Spyder project settings | ||||
| .spyderproject | ||||
| .spyproject | ||||
|  | ||||
| # Rope project settings | ||||
| .ropeproject | ||||
|  | ||||
| # mkdocs documentation | ||||
| /site | ||||
|  | ||||
| # mypy | ||||
| .mypy_cache/ | ||||
|  | ||||
| .pioenvs | ||||
| .piolibdeps | ||||
| .vscode | ||||
| CMakeListsPrivate.txt | ||||
| CMakeLists.txt | ||||
|  | ||||
| # User-specific stuff: | ||||
| .idea/**/workspace.xml | ||||
| .idea/**/tasks.xml | ||||
| .idea/dictionaries | ||||
|  | ||||
| # Sensitive or high-churn files: | ||||
| .idea/**/dataSources/ | ||||
| .idea/**/dataSources.ids | ||||
| .idea/**/dataSources.xml | ||||
| .idea/**/dataSources.local.xml | ||||
| .idea/**/dynamic.xml | ||||
|  | ||||
| # CMake | ||||
| cmake-build-debug/ | ||||
| cmake-build-release/ | ||||
|  | ||||
| CMakeCache.txt | ||||
| CMakeFiles | ||||
| CMakeScripts | ||||
| Testing | ||||
| Makefile | ||||
| cmake_install.cmake | ||||
| install_manifest.txt | ||||
| compile_commands.json | ||||
| CTestTestfile.cmake | ||||
| /*.cbp | ||||
|  | ||||
| .clang_complete | ||||
| .gcc-flags.json | ||||
|  | ||||
| config/ | ||||
| tests/build/ | ||||
| tests/.esphome/ | ||||
| /.temp-clang-tidy.cpp | ||||
|   | ||||
							
								
								
									
										185
									
								
								.gitlab-ci.yml
									
									
									
									
									
								
							
							
						
						
									
										185
									
								
								.gitlab-ci.yml
									
									
									
									
									
								
							| @@ -3,6 +3,8 @@ | ||||
| variables: | ||||
|   DOCKER_DRIVER: overlay2 | ||||
|   DOCKER_HOST: tcp://docker:2375/ | ||||
|   BASE_VERSION: '1.5.1' | ||||
|   TZ: UTC | ||||
|  | ||||
| stages: | ||||
|   - lint | ||||
| @@ -10,23 +12,20 @@ stages: | ||||
|   - deploy | ||||
|  | ||||
| .lint: &lint | ||||
|   image: esphome/esphome-base-amd64 | ||||
|   image: esphome/esphome-lint:latest | ||||
|   stage: lint | ||||
|   before_script: | ||||
|     - pip install -e . | ||||
|     - pip install flake8==3.6.0 pylint==1.9.4 pillow | ||||
|     - script/setup | ||||
|   tags: | ||||
|     - docker | ||||
|  | ||||
| .test: &test | ||||
|   image: esphome/esphome-base-amd64 | ||||
|   image: esphome/esphome-lint:latest | ||||
|   stage: test | ||||
|   before_script: | ||||
|     - pip install -e . | ||||
|     - script/setup | ||||
|   tags: | ||||
|     - docker | ||||
|   variables: | ||||
|     TZ: UTC | ||||
|  | ||||
| .docker-base: &docker-base | ||||
|   image: esphome/esphome-base-builder | ||||
| @@ -41,11 +40,11 @@ stages: | ||||
|  | ||||
|     - | | ||||
|       if [[ "${IS_HASSIO}" == "YES" ]]; then | ||||
|         BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.3 | ||||
|         BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:${BASE_VERSION} | ||||
|         BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH} | ||||
|         DOCKERFILE=docker/Dockerfile.hassio | ||||
|       else | ||||
|         BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.3 | ||||
|         BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:${BASE_VERSION} | ||||
|         if [[ "${BUILD_ARCH}" == "amd64" ]]; then | ||||
|           BUILD_TO=esphome/esphome | ||||
|         else | ||||
| @@ -94,15 +93,32 @@ stages: | ||||
|     - docker | ||||
|   stage: deploy | ||||
|  | ||||
| flake8: | ||||
| lint-custom: | ||||
|   <<: *lint | ||||
|   script: | ||||
|     - flake8 esphome | ||||
|     - script/ci-custom.py | ||||
|  | ||||
| pylint: | ||||
| lint-python: | ||||
|   <<: *lint | ||||
|   script: | ||||
|     - pylint esphome | ||||
|     - script/lint-python | ||||
|  | ||||
| lint-tidy: | ||||
|   <<: *lint | ||||
|   script: | ||||
|     - pio init --ide atom | ||||
|     - | | ||||
|       if ! patch -R -p0 -s -f --dry-run <script/.neopixelbus.patch; then | ||||
|         patch -p0 < script/.neopixelbus.patch | ||||
|       fi | ||||
|     - script/clang-tidy --all-headers --fix | ||||
|     - script/ci-suggest-changes | ||||
|  | ||||
| lint-format: | ||||
|   <<: *lint | ||||
|   script: | ||||
|     - script/clang-format -i | ||||
|     - script/ci-suggest-changes | ||||
|  | ||||
| test1: | ||||
|   <<: *test | ||||
| @@ -120,16 +136,12 @@ test3: | ||||
|     - esphome tests/test3.yaml compile | ||||
|  | ||||
| .deploy-pypi: &deploy-pypi | ||||
|   <<: *lint | ||||
|   stage: deploy | ||||
|   image: python:2.7 | ||||
|   before_script: | ||||
|   - pip install -e . | ||||
|   - pip install twine | ||||
|   script: | ||||
|   - python setup.py sdist bdist_wheel | ||||
|   - twine upload dist/* | ||||
|   tags: | ||||
|   - docker | ||||
|     - pip install twine wheel | ||||
|     - python setup.py sdist bdist_wheel | ||||
|     - twine upload dist/* | ||||
|  | ||||
| deploy-release:pypi: | ||||
|   <<: *deploy-pypi | ||||
| @@ -148,77 +160,64 @@ deploy-beta:pypi: | ||||
| .latest: &latest | ||||
|   <<: *docker-base | ||||
|   only: | ||||
|   - /^v([0-9\.]+)$/ | ||||
|     - /^v([0-9\.]+)$/ | ||||
|   except: | ||||
|   - branches | ||||
|  | ||||
| .latest-vars: &latest-vars | ||||
|   RELEASE: YES | ||||
|   LATEST: YES | ||||
|   # Also push to beta tag | ||||
|   BETA: YES | ||||
|     - branches | ||||
|  | ||||
| .beta: &beta | ||||
|   <<: *docker-base | ||||
|   only: | ||||
|   - /^v([0-9\.]+b\d+)$/ | ||||
|     - /^v([0-9\.]+b\d+)$/ | ||||
|   except: | ||||
|   - branches | ||||
|  | ||||
| .beta-vars: &beta-vars | ||||
|   RELEASE: YES | ||||
|   BETA: YES | ||||
|     - branches | ||||
|  | ||||
| .dev: &dev | ||||
|   <<: *docker-base | ||||
|   only: | ||||
|   - dev | ||||
|     - dev | ||||
|  | ||||
| .dev-vars: &dev-vars | ||||
|   DEV: YES | ||||
|  | ||||
| #aarch64-beta-docker: | ||||
| #  <<: *beta | ||||
| #  variables: | ||||
| #    BETA: "YES" | ||||
| #    BUILD_ARCH: aarch64 | ||||
| #    IS_HASSIO: "NO" | ||||
| #    RELEASE: "YES" | ||||
| #aarch64-beta-hassio: | ||||
| #  <<: *beta | ||||
| #  variables: | ||||
| #    BETA: "YES" | ||||
| #    BUILD_ARCH: aarch64 | ||||
| #    IS_HASSIO: "YES" | ||||
| #    RELEASE: "YES" | ||||
| #aarch64-dev-docker: | ||||
| #  <<: *dev | ||||
| #  variables: | ||||
| #    BUILD_ARCH: aarch64 | ||||
| #    DEV: "YES" | ||||
| #    IS_HASSIO: "NO" | ||||
| #aarch64-dev-hassio: | ||||
| #  <<: *dev | ||||
| #  variables: | ||||
| #    BUILD_ARCH: aarch64 | ||||
| #    DEV: "YES" | ||||
| #    IS_HASSIO: "YES" | ||||
| #aarch64-latest-docker: | ||||
| #  <<: *latest | ||||
| #  variables: | ||||
| #    BETA: "YES" | ||||
| #    BUILD_ARCH: aarch64 | ||||
| #    IS_HASSIO: "NO" | ||||
| #    LATEST: "YES" | ||||
| #    RELEASE: "YES" | ||||
| #aarch64-latest-hassio: | ||||
| #  <<: *latest | ||||
| #  variables: | ||||
| #    BETA: "YES" | ||||
| #    BUILD_ARCH: aarch64 | ||||
| #    IS_HASSIO: "YES" | ||||
| #    LATEST: "YES" | ||||
| #    RELEASE: "YES" | ||||
| aarch64-beta-docker: | ||||
|   <<: *beta | ||||
|   variables: | ||||
|     BETA: "YES" | ||||
|     BUILD_ARCH: aarch64 | ||||
|     IS_HASSIO: "NO" | ||||
|     RELEASE: "YES" | ||||
| aarch64-beta-hassio: | ||||
|   <<: *beta | ||||
|   variables: | ||||
|     BETA: "YES" | ||||
|     BUILD_ARCH: aarch64 | ||||
|     IS_HASSIO: "YES" | ||||
|     RELEASE: "YES" | ||||
| aarch64-dev-docker: | ||||
|   <<: *dev | ||||
|   variables: | ||||
|     BUILD_ARCH: aarch64 | ||||
|     DEV: "YES" | ||||
|     IS_HASSIO: "NO" | ||||
| aarch64-dev-hassio: | ||||
|   <<: *dev | ||||
|   variables: | ||||
|     BUILD_ARCH: aarch64 | ||||
|     DEV: "YES" | ||||
|     IS_HASSIO: "YES" | ||||
| aarch64-latest-docker: | ||||
|   <<: *latest | ||||
|   variables: | ||||
|     BETA: "YES" | ||||
|     BUILD_ARCH: aarch64 | ||||
|     IS_HASSIO: "NO" | ||||
|     LATEST: "YES" | ||||
|     RELEASE: "YES" | ||||
| aarch64-latest-hassio: | ||||
|   <<: *latest | ||||
|   variables: | ||||
|     BETA: "YES" | ||||
|     BUILD_ARCH: aarch64 | ||||
|     IS_HASSIO: "YES" | ||||
|     LATEST: "YES" | ||||
|     RELEASE: "YES" | ||||
| amd64-beta-docker: | ||||
|   <<: *beta | ||||
|   variables: | ||||
| @@ -261,45 +260,45 @@ amd64-latest-hassio: | ||||
|     IS_HASSIO: "YES" | ||||
|     LATEST: "YES" | ||||
|     RELEASE: "YES" | ||||
| armhf-beta-docker: | ||||
| armv7-beta-docker: | ||||
|   <<: *beta | ||||
|   variables: | ||||
|     BETA: "YES" | ||||
|     BUILD_ARCH: armhf | ||||
|     BUILD_ARCH: armv7 | ||||
|     IS_HASSIO: "NO" | ||||
|     RELEASE: "YES" | ||||
| armhf-beta-hassio: | ||||
| armv7-beta-hassio: | ||||
|   <<: *beta | ||||
|   variables: | ||||
|     BETA: "YES" | ||||
|     BUILD_ARCH: armhf | ||||
|     BUILD_ARCH: armv7 | ||||
|     IS_HASSIO: "YES" | ||||
|     RELEASE: "YES" | ||||
| armhf-dev-docker: | ||||
| armv7-dev-docker: | ||||
|   <<: *dev | ||||
|   variables: | ||||
|     BUILD_ARCH: armhf | ||||
|     BUILD_ARCH: armv7 | ||||
|     DEV: "YES" | ||||
|     IS_HASSIO: "NO" | ||||
| armhf-dev-hassio: | ||||
| armv7-dev-hassio: | ||||
|   <<: *dev | ||||
|   variables: | ||||
|     BUILD_ARCH: armhf | ||||
|     BUILD_ARCH: armv7 | ||||
|     DEV: "YES" | ||||
|     IS_HASSIO: "YES" | ||||
| armhf-latest-docker: | ||||
| armv7-latest-docker: | ||||
|   <<: *latest | ||||
|   variables: | ||||
|     BETA: "YES" | ||||
|     BUILD_ARCH: armhf | ||||
|     BUILD_ARCH: armv7 | ||||
|     IS_HASSIO: "NO" | ||||
|     LATEST: "YES" | ||||
|     RELEASE: "YES" | ||||
| armhf-latest-hassio: | ||||
| armv7-latest-hassio: | ||||
|   <<: *latest | ||||
|   variables: | ||||
|     BETA: "YES" | ||||
|     BUILD_ARCH: armhf | ||||
|     BUILD_ARCH: armv7 | ||||
|     IS_HASSIO: "YES" | ||||
|     LATEST: "YES" | ||||
|     RELEASE: "YES" | ||||
|   | ||||
							
								
								
									
										6
									
								
								.gitpod.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.gitpod.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| ports: | ||||
| - port: 6052 | ||||
|   onOpen: open-preview | ||||
| tasks: | ||||
| - before: script/setup | ||||
|   command: python -m esphome config dashboard | ||||
							
								
								
									
										38
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,9 +1,10 @@ | ||||
| sudo: false | ||||
| language: python | ||||
|  | ||||
| install: script/setup | ||||
| cache: | ||||
|   directories: | ||||
|     - "~/.platformio" | ||||
|     - "$TRAVIS_BUILD_DIR/.piolibdeps" | ||||
|     - "$TRAVIS_BUILD_DIR/tests/build/test1/.piolibdeps" | ||||
|     - "$TRAVIS_BUILD_DIR/tests/build/test2/.piolibdeps" | ||||
|     - "$TRAVIS_BUILD_DIR/tests/build/test3/.piolibdeps" | ||||
| @@ -13,26 +14,43 @@ matrix: | ||||
|   include: | ||||
|     - python: "2.7" | ||||
|       env: TARGET=Lint2.7 | ||||
|       install: pip install -e . && pip install flake8==3.6.0 pylint==1.9.4 pillow | ||||
|       script: | ||||
|         - script/ci-custom.py | ||||
|         - flake8 esphome | ||||
|         - pylint esphome | ||||
|     - python: "3.5.3" | ||||
|       env: TARGET=Lint3.5 | ||||
|       install: pip install -U https://github.com/platformio/platformio-core/archive/develop.zip && pip install -e . && pip install flake8==3.6.0 pylint==2.3.0 pillow | ||||
|       script: | ||||
|         - script/ci-custom.py | ||||
|         - flake8 esphome | ||||
|         - pylint esphome | ||||
|     - python: "2.7" | ||||
|       env: TARGET=Test2.7 | ||||
|       install: pip install -e . && pip install flake8==3.6.0 pylint==1.9.4 pillow | ||||
|       script: | ||||
|         - esphome tests/test1.yaml compile | ||||
|         - esphome tests/test2.yaml compile | ||||
|         - esphome tests/test3.yaml compile | ||||
|     #- python: "3.5.3" | ||||
|     #  env: TARGET=Test3.5 | ||||
|     #  install: pip install -U https://github.com/platformio/platformio-core/archive/develop.zip && pip install -e . && pip install flake8==3.6.0 pylint==2.3.0 pillow | ||||
|     #  script: | ||||
|     #    - esphome tests/test1.yaml compile | ||||
|     #    - esphome tests/test2.yaml compile | ||||
|     - env: TARGET=Cpp-Lint | ||||
|       dist: trusty | ||||
|       sudo: required | ||||
|       addons: | ||||
|         apt: | ||||
|           sources: | ||||
|             - ubuntu-toolchain-r-test | ||||
|             - llvm-toolchain-trusty-7 | ||||
|           packages: | ||||
|             - clang-tidy-7 | ||||
|             - clang-format-7 | ||||
|       before_script: | ||||
|         - pio init --ide atom | ||||
|         - | | ||||
|           if ! patch -R -p0 -s -f --dry-run <script/.neopixelbus.patch; then | ||||
|             patch -p0 < script/.neopixelbus.patch | ||||
|           fi | ||||
|         - clang-tidy-7 -version | ||||
|         - clang-format-7 -version | ||||
|         - clang-apply-replacements-7 -version | ||||
|       script: | ||||
|         - script/clang-tidy --all-headers -j 2 --fix | ||||
|         - script/clang-format -i -j 2 | ||||
|         - script/ci-suggest-changes | ||||
|   | ||||
							
								
								
									
										692
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										692
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,6 +1,17 @@ | ||||
| MIT License | ||||
| # ESPHome License | ||||
|  | ||||
| Copyright (c) 2018 Otto Winter | ||||
| Copyright (c) 2019 ESPHome | ||||
|  | ||||
| The ESPHome License is made up of two base licenses: MIT and the GNU GENERAL PUBLIC LICENSE. | ||||
| The C++/runtime codebase of the ESPHome project (file extensions .c, .cpp, .h, .hpp, .tcc, .ino) are | ||||
| published under the GPLv3 license. The python codebase and all other parts of this codebase are | ||||
| published under the MIT license. | ||||
|  | ||||
| Both MIT and GPLv3 licenses are attached to this document. | ||||
|  | ||||
| ## MIT License | ||||
|  | ||||
| Copyright (c) 2019 ESPHome | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| @@ -19,3 +30,680 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
|  | ||||
| ## GPLv3 License | ||||
|  | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|                        Version 3, 29 June 2007 | ||||
|  | ||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
|  | ||||
|                             Preamble | ||||
|  | ||||
|   The GNU General Public License is a free, copyleft license for | ||||
| software and other kinds of works. | ||||
|  | ||||
|   The licenses for most software and other practical works are designed | ||||
| to take away your freedom to share and change the works.  By contrast, | ||||
| the GNU General Public License is intended to guarantee your freedom to | ||||
| share and change all versions of a program--to make sure it remains free | ||||
| software for all its users.  We, the Free Software Foundation, use the | ||||
| GNU General Public License for most of our software; it applies also to | ||||
| any other work released this way by its authors.  You can apply it to | ||||
| your programs, too. | ||||
|  | ||||
|   When we speak of free software, we are referring to freedom, not | ||||
| price.  Our General Public Licenses are designed to make sure that you | ||||
| have the freedom to distribute copies of free software (and charge for | ||||
| them if you wish), that you receive source code or can get it if you | ||||
| want it, that you can change the software or use pieces of it in new | ||||
| free programs, and that you know you can do these things. | ||||
|  | ||||
|   To protect your rights, we need to prevent others from denying you | ||||
| these rights or asking you to surrender the rights.  Therefore, you have | ||||
| certain responsibilities if you distribute copies of the software, or if | ||||
| you modify it: responsibilities to respect the freedom of others. | ||||
|  | ||||
|   For example, if you distribute copies of such a program, whether | ||||
| gratis or for a fee, you must pass on to the recipients the same | ||||
| freedoms that you received.  You must make sure that they, too, receive | ||||
| or can get the source code.  And you must show them these terms so they | ||||
| know their rights. | ||||
|  | ||||
|   Developers that use the GNU GPL protect your rights with two steps: | ||||
| (1) assert copyright on the software, and (2) offer you this License | ||||
| giving you legal permission to copy, distribute and/or modify it. | ||||
|  | ||||
|   For the developers' and authors' protection, the GPL clearly explains | ||||
| that there is no warranty for this free software.  For both users' and | ||||
| authors' sake, the GPL requires that modified versions be marked as | ||||
| changed, so that their problems will not be attributed erroneously to | ||||
| authors of previous versions. | ||||
|  | ||||
|   Some devices are designed to deny users access to install or run | ||||
| modified versions of the software inside them, although the manufacturer | ||||
| can do so.  This is fundamentally incompatible with the aim of | ||||
| protecting users' freedom to change the software.  The systematic | ||||
| pattern of such abuse occurs in the area of products for individuals to | ||||
| use, which is precisely where it is most unacceptable.  Therefore, we | ||||
| have designed this version of the GPL to prohibit the practice for those | ||||
| products.  If such problems arise substantially in other domains, we | ||||
| stand ready to extend this provision to those domains in future versions | ||||
| of the GPL, as needed to protect the freedom of users. | ||||
|  | ||||
|   Finally, every program is threatened constantly by software patents. | ||||
| States should not allow patents to restrict development and use of | ||||
| software on general-purpose computers, but in those that do, we wish to | ||||
| avoid the special danger that patents applied to a free program could | ||||
| make it effectively proprietary.  To prevent this, the GPL assures that | ||||
| patents cannot be used to render the program non-free. | ||||
|  | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow. | ||||
|  | ||||
|                        TERMS AND CONDITIONS | ||||
|  | ||||
|   0. Definitions. | ||||
|  | ||||
|   "This License" refers to version 3 of the GNU General Public License. | ||||
|  | ||||
|   "Copyright" also means copyright-like laws that apply to other kinds of | ||||
| works, such as semiconductor masks. | ||||
|  | ||||
|   "The Program" refers to any copyrightable work licensed under this | ||||
| License.  Each licensee is addressed as "you".  "Licensees" and | ||||
| "recipients" may be individuals or organizations. | ||||
|  | ||||
|   To "modify" a work means to copy from or adapt all or part of the work | ||||
| in a fashion requiring copyright permission, other than the making of an | ||||
| exact copy.  The resulting work is called a "modified version" of the | ||||
| earlier work or a work "based on" the earlier work. | ||||
|  | ||||
|   A "covered work" means either the unmodified Program or a work based | ||||
| on the Program. | ||||
|  | ||||
|   To "propagate" a work means to do anything with it that, without | ||||
| permission, would make you directly or secondarily liable for | ||||
| infringement under applicable copyright law, except executing it on a | ||||
| computer or modifying a private copy.  Propagation includes copying, | ||||
| distribution (with or without modification), making available to the | ||||
| public, and in some countries other activities as well. | ||||
|  | ||||
|   To "convey" a work means any kind of propagation that enables other | ||||
| parties to make or receive copies.  Mere interaction with a user through | ||||
| a computer network, with no transfer of a copy, is not conveying. | ||||
|  | ||||
|   An interactive user interface displays "Appropriate Legal Notices" | ||||
| to the extent that it includes a convenient and prominently visible | ||||
| feature that (1) displays an appropriate copyright notice, and (2) | ||||
| tells the user that there is no warranty for the work (except to the | ||||
| extent that warranties are provided), that licensees may convey the | ||||
| work under this License, and how to view a copy of this License.  If | ||||
| the interface presents a list of user commands or options, such as a | ||||
| menu, a prominent item in the list meets this criterion. | ||||
|  | ||||
|   1. Source Code. | ||||
|  | ||||
|   The "source code" for a work means the preferred form of the work | ||||
| for making modifications to it.  "Object code" means any non-source | ||||
| form of a work. | ||||
|  | ||||
|   A "Standard Interface" means an interface that either is an official | ||||
| standard defined by a recognized standards body, or, in the case of | ||||
| interfaces specified for a particular programming language, one that | ||||
| is widely used among developers working in that language. | ||||
|  | ||||
|   The "System Libraries" of an executable work include anything, other | ||||
| than the work as a whole, that (a) is included in the normal form of | ||||
| packaging a Major Component, but which is not part of that Major | ||||
| Component, and (b) serves only to enable use of the work with that | ||||
| Major Component, or to implement a Standard Interface for which an | ||||
| implementation is available to the public in source code form.  A | ||||
| "Major Component", in this context, means a major essential component | ||||
| (kernel, window system, and so on) of the specific operating system | ||||
| (if any) on which the executable work runs, or a compiler used to | ||||
| produce the work, or an object code interpreter used to run it. | ||||
|  | ||||
|   The "Corresponding Source" for a work in object code form means all | ||||
| the source code needed to generate, install, and (for an executable | ||||
| work) run the object code and to modify the work, including scripts to | ||||
| control those activities.  However, it does not include the work's | ||||
| System Libraries, or general-purpose tools or generally available free | ||||
| programs which are used unmodified in performing those activities but | ||||
| which are not part of the work.  For example, Corresponding Source | ||||
| includes interface definition files associated with source files for | ||||
| the work, and the source code for shared libraries and dynamically | ||||
| linked subprograms that the work is specifically designed to require, | ||||
| such as by intimate data communication or control flow between those | ||||
| subprograms and other parts of the work. | ||||
|  | ||||
|   The Corresponding Source need not include anything that users | ||||
| can regenerate automatically from other parts of the Corresponding | ||||
| Source. | ||||
|  | ||||
|   The Corresponding Source for a work in source code form is that | ||||
| same work. | ||||
|  | ||||
|   2. Basic Permissions. | ||||
|  | ||||
|   All rights granted under this License are granted for the term of | ||||
| copyright on the Program, and are irrevocable provided the stated | ||||
| conditions are met.  This License explicitly affirms your unlimited | ||||
| permission to run the unmodified Program.  The output from running a | ||||
| covered work is covered by this License only if the output, given its | ||||
| content, constitutes a covered work.  This License acknowledges your | ||||
| rights of fair use or other equivalent, as provided by copyright law. | ||||
|  | ||||
|   You may make, run and propagate covered works that you do not | ||||
| convey, without conditions so long as your license otherwise remains | ||||
| in force.  You may convey covered works to others for the sole purpose | ||||
| of having them make modifications exclusively for you, or provide you | ||||
| with facilities for running those works, provided that you comply with | ||||
| the terms of this License in conveying all material for which you do | ||||
| not control copyright.  Those thus making or running the covered works | ||||
| for you must do so exclusively on your behalf, under your direction | ||||
| and control, on terms that prohibit them from making any copies of | ||||
| your copyrighted material outside their relationship with you. | ||||
|  | ||||
|   Conveying under any other circumstances is permitted solely under | ||||
| the conditions stated below.  Sublicensing is not allowed; section 10 | ||||
| makes it unnecessary. | ||||
|  | ||||
|   3. Protecting Users' Legal Rights From Anti-Circumvention Law. | ||||
|  | ||||
|   No covered work shall be deemed part of an effective technological | ||||
| measure under any applicable law fulfilling obligations under article | ||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or | ||||
| similar laws prohibiting or restricting circumvention of such | ||||
| measures. | ||||
|  | ||||
|   When you convey a covered work, you waive any legal power to forbid | ||||
| circumvention of technological measures to the extent such circumvention | ||||
| is effected by exercising rights under this License with respect to | ||||
| the covered work, and you disclaim any intention to limit operation or | ||||
| modification of the work as a means of enforcing, against the work's | ||||
| users, your or third parties' legal rights to forbid circumvention of | ||||
| technological measures. | ||||
|  | ||||
|   4. Conveying Verbatim Copies. | ||||
|  | ||||
|   You may convey verbatim copies of the Program's source code as you | ||||
| receive it, in any medium, provided that you conspicuously and | ||||
| appropriately publish on each copy an appropriate copyright notice; | ||||
| keep intact all notices stating that this License and any | ||||
| non-permissive terms added in accord with section 7 apply to the code; | ||||
| keep intact all notices of the absence of any warranty; and give all | ||||
| recipients a copy of this License along with the Program. | ||||
|  | ||||
|   You may charge any price or no price for each copy that you convey, | ||||
| and you may offer support or warranty protection for a fee. | ||||
|  | ||||
|   5. Conveying Modified Source Versions. | ||||
|  | ||||
|   You may convey a work based on the Program, or the modifications to | ||||
| produce it from the Program, in the form of source code under the | ||||
| terms of section 4, provided that you also meet all of these conditions: | ||||
|  | ||||
|     a) The work must carry prominent notices stating that you modified | ||||
|     it, and giving a relevant date. | ||||
|  | ||||
|     b) The work must carry prominent notices stating that it is | ||||
|     released under this License and any conditions added under section | ||||
|     7.  This requirement modifies the requirement in section 4 to | ||||
|     "keep intact all notices". | ||||
|  | ||||
|     c) You must license the entire work, as a whole, under this | ||||
|     License to anyone who comes into possession of a copy.  This | ||||
|     License will therefore apply, along with any applicable section 7 | ||||
|     additional terms, to the whole of the work, and all its parts, | ||||
|     regardless of how they are packaged.  This License gives no | ||||
|     permission to license the work in any other way, but it does not | ||||
|     invalidate such permission if you have separately received it. | ||||
|  | ||||
|     d) If the work has interactive user interfaces, each must display | ||||
|     Appropriate Legal Notices; however, if the Program has interactive | ||||
|     interfaces that do not display Appropriate Legal Notices, your | ||||
|     work need not make them do so. | ||||
|  | ||||
|   A compilation of a covered work with other separate and independent | ||||
| works, which are not by their nature extensions of the covered work, | ||||
| and which are not combined with it such as to form a larger program, | ||||
| in or on a volume of a storage or distribution medium, is called an | ||||
| "aggregate" if the compilation and its resulting copyright are not | ||||
| used to limit the access or legal rights of the compilation's users | ||||
| beyond what the individual works permit.  Inclusion of a covered work | ||||
| in an aggregate does not cause this License to apply to the other | ||||
| parts of the aggregate. | ||||
|  | ||||
|   6. Conveying Non-Source Forms. | ||||
|  | ||||
|   You may convey a covered work in object code form under the terms | ||||
| of sections 4 and 5, provided that you also convey the | ||||
| machine-readable Corresponding Source under the terms of this License, | ||||
| in one of these ways: | ||||
|  | ||||
|     a) Convey the object code in, or embodied in, a physical product | ||||
|     (including a physical distribution medium), accompanied by the | ||||
|     Corresponding Source fixed on a durable physical medium | ||||
|     customarily used for software interchange. | ||||
|  | ||||
|     b) Convey the object code in, or embodied in, a physical product | ||||
|     (including a physical distribution medium), accompanied by a | ||||
|     written offer, valid for at least three years and valid for as | ||||
|     long as you offer spare parts or customer support for that product | ||||
|     model, to give anyone who possesses the object code either (1) a | ||||
|     copy of the Corresponding Source for all the software in the | ||||
|     product that is covered by this License, on a durable physical | ||||
|     medium customarily used for software interchange, for a price no | ||||
|     more than your reasonable cost of physically performing this | ||||
|     conveying of source, or (2) access to copy the | ||||
|     Corresponding Source from a network server at no charge. | ||||
|  | ||||
|     c) Convey individual copies of the object code with a copy of the | ||||
|     written offer to provide the Corresponding Source.  This | ||||
|     alternative is allowed only occasionally and noncommercially, and | ||||
|     only if you received the object code with such an offer, in accord | ||||
|     with subsection 6b. | ||||
|  | ||||
|     d) Convey the object code by offering access from a designated | ||||
|     place (gratis or for a charge), and offer equivalent access to the | ||||
|     Corresponding Source in the same way through the same place at no | ||||
|     further charge.  You need not require recipients to copy the | ||||
|     Corresponding Source along with the object code.  If the place to | ||||
|     copy the object code is a network server, the Corresponding Source | ||||
|     may be on a different server (operated by you or a third party) | ||||
|     that supports equivalent copying facilities, provided you maintain | ||||
|     clear directions next to the object code saying where to find the | ||||
|     Corresponding Source.  Regardless of what server hosts the | ||||
|     Corresponding Source, you remain obligated to ensure that it is | ||||
|     available for as long as needed to satisfy these requirements. | ||||
|  | ||||
|     e) Convey the object code using peer-to-peer transmission, provided | ||||
|     you inform other peers where the object code and Corresponding | ||||
|     Source of the work are being offered to the general public at no | ||||
|     charge under subsection 6d. | ||||
|  | ||||
|   A separable portion of the object code, whose source code is excluded | ||||
| from the Corresponding Source as a System Library, need not be | ||||
| included in conveying the object code work. | ||||
|  | ||||
|   A "User Product" is either (1) a "consumer product", which means any | ||||
| tangible personal property which is normally used for personal, family, | ||||
| or household purposes, or (2) anything designed or sold for incorporation | ||||
| into a dwelling.  In determining whether a product is a consumer product, | ||||
| doubtful cases shall be resolved in favor of coverage.  For a particular | ||||
| product received by a particular user, "normally used" refers to a | ||||
| typical or common use of that class of product, regardless of the status | ||||
| of the particular user or of the way in which the particular user | ||||
| actually uses, or expects or is expected to use, the product.  A product | ||||
| is a consumer product regardless of whether the product has substantial | ||||
| commercial, industrial or non-consumer uses, unless such uses represent | ||||
| the only significant mode of use of the product. | ||||
|  | ||||
|   "Installation Information" for a User Product means any methods, | ||||
| procedures, authorization keys, or other information required to install | ||||
| and execute modified versions of a covered work in that User Product from | ||||
| a modified version of its Corresponding Source.  The information must | ||||
| suffice to ensure that the continued functioning of the modified object | ||||
| code is in no case prevented or interfered with solely because | ||||
| modification has been made. | ||||
|  | ||||
|   If you convey an object code work under this section in, or with, or | ||||
| specifically for use in, a User Product, and the conveying occurs as | ||||
| part of a transaction in which the right of possession and use of the | ||||
| User Product is transferred to the recipient in perpetuity or for a | ||||
| fixed term (regardless of how the transaction is characterized), the | ||||
| Corresponding Source conveyed under this section must be accompanied | ||||
| by the Installation Information.  But this requirement does not apply | ||||
| if neither you nor any third party retains the ability to install | ||||
| modified object code on the User Product (for example, the work has | ||||
| been installed in ROM). | ||||
|  | ||||
|   The requirement to provide Installation Information does not include a | ||||
| requirement to continue to provide support service, warranty, or updates | ||||
| for a work that has been modified or installed by the recipient, or for | ||||
| the User Product in which it has been modified or installed.  Access to a | ||||
| network may be denied when the modification itself materially and | ||||
| adversely affects the operation of the network or violates the rules and | ||||
| protocols for communication across the network. | ||||
|  | ||||
|   Corresponding Source conveyed, and Installation Information provided, | ||||
| in accord with this section must be in a format that is publicly | ||||
| documented (and with an implementation available to the public in | ||||
| source code form), and must require no special password or key for | ||||
| unpacking, reading or copying. | ||||
|  | ||||
|   7. Additional Terms. | ||||
|  | ||||
|   "Additional permissions" are terms that supplement the terms of this | ||||
| License by making exceptions from one or more of its conditions. | ||||
| Additional permissions that are applicable to the entire Program shall | ||||
| be treated as though they were included in this License, to the extent | ||||
| that they are valid under applicable law.  If additional permissions | ||||
| apply only to part of the Program, that part may be used separately | ||||
| under those permissions, but the entire Program remains governed by | ||||
| this License without regard to the additional permissions. | ||||
|  | ||||
|   When you convey a copy of a covered work, you may at your option | ||||
| remove any additional permissions from that copy, or from any part of | ||||
| it.  (Additional permissions may be written to require their own | ||||
| removal in certain cases when you modify the work.)  You may place | ||||
| additional permissions on material, added by you to a covered work, | ||||
| for which you have or can give appropriate copyright permission. | ||||
|  | ||||
|   Notwithstanding any other provision of this License, for material you | ||||
| add to a covered work, you may (if authorized by the copyright holders of | ||||
| that material) supplement the terms of this License with terms: | ||||
|  | ||||
|     a) Disclaiming warranty or limiting liability differently from the | ||||
|     terms of sections 15 and 16 of this License; or | ||||
|  | ||||
|     b) Requiring preservation of specified reasonable legal notices or | ||||
|     author attributions in that material or in the Appropriate Legal | ||||
|     Notices displayed by works containing it; or | ||||
|  | ||||
|     c) Prohibiting misrepresentation of the origin of that material, or | ||||
|     requiring that modified versions of such material be marked in | ||||
|     reasonable ways as different from the original version; or | ||||
|  | ||||
|     d) Limiting the use for publicity purposes of names of licensors or | ||||
|     authors of the material; or | ||||
|  | ||||
|     e) Declining to grant rights under trademark law for use of some | ||||
|     trade names, trademarks, or service marks; or | ||||
|  | ||||
|     f) Requiring indemnification of licensors and authors of that | ||||
|     material by anyone who conveys the material (or modified versions of | ||||
|     it) with contractual assumptions of liability to the recipient, for | ||||
|     any liability that these contractual assumptions directly impose on | ||||
|     those licensors and authors. | ||||
|  | ||||
|   All other non-permissive additional terms are considered "further | ||||
| restrictions" within the meaning of section 10.  If the Program as you | ||||
| received it, or any part of it, contains a notice stating that it is | ||||
| governed by this License along with a term that is a further | ||||
| restriction, you may remove that term.  If a license document contains | ||||
| a further restriction but permits relicensing or conveying under this | ||||
| License, you may add to a covered work material governed by the terms | ||||
| of that license document, provided that the further restriction does | ||||
| not survive such relicensing or conveying. | ||||
|  | ||||
|   If you add terms to a covered work in accord with this section, you | ||||
| must place, in the relevant source files, a statement of the | ||||
| additional terms that apply to those files, or a notice indicating | ||||
| where to find the applicable terms. | ||||
|  | ||||
|   Additional terms, permissive or non-permissive, may be stated in the | ||||
| form of a separately written license, or stated as exceptions; | ||||
| the above requirements apply either way. | ||||
|  | ||||
|   8. Termination. | ||||
|  | ||||
|   You may not propagate or modify a covered work except as expressly | ||||
| provided under this License.  Any attempt otherwise to propagate or | ||||
| modify it is void, and will automatically terminate your rights under | ||||
| this License (including any patent licenses granted under the third | ||||
| paragraph of section 11). | ||||
|  | ||||
|   However, if you cease all violation of this License, then your | ||||
| license from a particular copyright holder is reinstated (a) | ||||
| provisionally, unless and until the copyright holder explicitly and | ||||
| finally terminates your license, and (b) permanently, if the copyright | ||||
| holder fails to notify you of the violation by some reasonable means | ||||
| prior to 60 days after the cessation. | ||||
|  | ||||
|   Moreover, your license from a particular copyright holder is | ||||
| reinstated permanently if the copyright holder notifies you of the | ||||
| violation by some reasonable means, this is the first time you have | ||||
| received notice of violation of this License (for any work) from that | ||||
| copyright holder, and you cure the violation prior to 30 days after | ||||
| your receipt of the notice. | ||||
|  | ||||
|   Termination of your rights under this section does not terminate the | ||||
| licenses of parties who have received copies or rights from you under | ||||
| this License.  If your rights have been terminated and not permanently | ||||
| reinstated, you do not qualify to receive new licenses for the same | ||||
| material under section 10. | ||||
|  | ||||
|   9. Acceptance Not Required for Having Copies. | ||||
|  | ||||
|   You are not required to accept this License in order to receive or | ||||
| run a copy of the Program.  Ancillary propagation of a covered work | ||||
| occurring solely as a consequence of using peer-to-peer transmission | ||||
| to receive a copy likewise does not require acceptance.  However, | ||||
| nothing other than this License grants you permission to propagate or | ||||
| modify any covered work.  These actions infringe copyright if you do | ||||
| not accept this License.  Therefore, by modifying or propagating a | ||||
| covered work, you indicate your acceptance of this License to do so. | ||||
|  | ||||
|   10. Automatic Licensing of Downstream Recipients. | ||||
|  | ||||
|   Each time you convey a covered work, the recipient automatically | ||||
| receives a license from the original licensors, to run, modify and | ||||
| propagate that work, subject to this License.  You are not responsible | ||||
| for enforcing compliance by third parties with this License. | ||||
|  | ||||
|   An "entity transaction" is a transaction transferring control of an | ||||
| organization, or substantially all assets of one, or subdividing an | ||||
| organization, or merging organizations.  If propagation of a covered | ||||
| work results from an entity transaction, each party to that | ||||
| transaction who receives a copy of the work also receives whatever | ||||
| licenses to the work the party's predecessor in interest had or could | ||||
| give under the previous paragraph, plus a right to possession of the | ||||
| Corresponding Source of the work from the predecessor in interest, if | ||||
| the predecessor has it or can get it with reasonable efforts. | ||||
|  | ||||
|   You may not impose any further restrictions on the exercise of the | ||||
| rights granted or affirmed under this License.  For example, you may | ||||
| not impose a license fee, royalty, or other charge for exercise of | ||||
| rights granted under this License, and you may not initiate litigation | ||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that | ||||
| any patent claim is infringed by making, using, selling, offering for | ||||
| sale, or importing the Program or any portion of it. | ||||
|  | ||||
|   11. Patents. | ||||
|  | ||||
|   A "contributor" is a copyright holder who authorizes use under this | ||||
| License of the Program or a work on which the Program is based.  The | ||||
| work thus licensed is called the contributor's "contributor version". | ||||
|  | ||||
|   A contributor's "essential patent claims" are all patent claims | ||||
| owned or controlled by the contributor, whether already acquired or | ||||
| hereafter acquired, that would be infringed by some manner, permitted | ||||
| by this License, of making, using, or selling its contributor version, | ||||
| but do not include claims that would be infringed only as a | ||||
| consequence of further modification of the contributor version.  For | ||||
| purposes of this definition, "control" includes the right to grant | ||||
| patent sublicenses in a manner consistent with the requirements of | ||||
| this License. | ||||
|  | ||||
|   Each contributor grants you a non-exclusive, worldwide, royalty-free | ||||
| patent license under the contributor's essential patent claims, to | ||||
| make, use, sell, offer for sale, import and otherwise run, modify and | ||||
| propagate the contents of its contributor version. | ||||
|  | ||||
|   In the following three paragraphs, a "patent license" is any express | ||||
| agreement or commitment, however denominated, not to enforce a patent | ||||
| (such as an express permission to practice a patent or covenant not to | ||||
| sue for patent infringement).  To "grant" such a patent license to a | ||||
| party means to make such an agreement or commitment not to enforce a | ||||
| patent against the party. | ||||
|  | ||||
|   If you convey a covered work, knowingly relying on a patent license, | ||||
| and the Corresponding Source of the work is not available for anyone | ||||
| to copy, free of charge and under the terms of this License, through a | ||||
| publicly available network server or other readily accessible means, | ||||
| then you must either (1) cause the Corresponding Source to be so | ||||
| available, or (2) arrange to deprive yourself of the benefit of the | ||||
| patent license for this particular work, or (3) arrange, in a manner | ||||
| consistent with the requirements of this License, to extend the patent | ||||
| license to downstream recipients.  "Knowingly relying" means you have | ||||
| actual knowledge that, but for the patent license, your conveying the | ||||
| covered work in a country, or your recipient's use of the covered work | ||||
| in a country, would infringe one or more identifiable patents in that | ||||
| country that you have reason to believe are valid. | ||||
|  | ||||
|   If, pursuant to or in connection with a single transaction or | ||||
| arrangement, you convey, or propagate by procuring conveyance of, a | ||||
| covered work, and grant a patent license to some of the parties | ||||
| receiving the covered work authorizing them to use, propagate, modify | ||||
| or convey a specific copy of the covered work, then the patent license | ||||
| you grant is automatically extended to all recipients of the covered | ||||
| work and works based on it. | ||||
|  | ||||
|   A patent license is "discriminatory" if it does not include within | ||||
| the scope of its coverage, prohibits the exercise of, or is | ||||
| conditioned on the non-exercise of one or more of the rights that are | ||||
| specifically granted under this License.  You may not convey a covered | ||||
| work if you are a party to an arrangement with a third party that is | ||||
| in the business of distributing software, under which you make payment | ||||
| to the third party based on the extent of your activity of conveying | ||||
| the work, and under which the third party grants, to any of the | ||||
| parties who would receive the covered work from you, a discriminatory | ||||
| patent license (a) in connection with copies of the covered work | ||||
| conveyed by you (or copies made from those copies), or (b) primarily | ||||
| for and in connection with specific products or compilations that | ||||
| contain the covered work, unless you entered into that arrangement, | ||||
| or that patent license was granted, prior to 28 March 2007. | ||||
|  | ||||
|   Nothing in this License shall be construed as excluding or limiting | ||||
| any implied license or other defenses to infringement that may | ||||
| otherwise be available to you under applicable patent law. | ||||
|  | ||||
|   12. No Surrender of Others' Freedom. | ||||
|  | ||||
|   If conditions are imposed on you (whether by court order, agreement or | ||||
| otherwise) that contradict the conditions of this License, they do not | ||||
| excuse you from the conditions of this License.  If you cannot convey a | ||||
| covered work so as to satisfy simultaneously your obligations under this | ||||
| License and any other pertinent obligations, then as a consequence you may | ||||
| not convey it at all.  For example, if you agree to terms that obligate you | ||||
| to collect a royalty for further conveying from those to whom you convey | ||||
| the Program, the only way you could satisfy both those terms and this | ||||
| License would be to refrain entirely from conveying the Program. | ||||
|  | ||||
|   13. Use with the GNU Affero General Public License. | ||||
|  | ||||
|   Notwithstanding any other provision of this License, you have | ||||
| permission to link or combine any covered work with a work licensed | ||||
| under version 3 of the GNU Affero General Public License into a single | ||||
| combined work, and to convey the resulting work.  The terms of this | ||||
| License will continue to apply to the part which is the covered work, | ||||
| but the special requirements of the GNU Affero General Public License, | ||||
| section 13, concerning interaction through a network will apply to the | ||||
| combination as such. | ||||
|  | ||||
|   14. Revised Versions of this License. | ||||
|  | ||||
|   The Free Software Foundation may publish revised and/or new versions of | ||||
| the GNU 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 | ||||
| Program specifies that a certain numbered version of the GNU General | ||||
| Public License "or any later version" applies to it, you have the | ||||
| option of following the terms and conditions either of that numbered | ||||
| version or of any later version published by the Free Software | ||||
| Foundation.  If the Program does not specify a version number of the | ||||
| GNU General Public License, you may choose any version ever published | ||||
| by the Free Software Foundation. | ||||
|  | ||||
|   If the Program specifies that a proxy can decide which future | ||||
| versions of the GNU General Public License can be used, that proxy's | ||||
| public statement of acceptance of a version permanently authorizes you | ||||
| to choose that version for the Program. | ||||
|  | ||||
|   Later license versions may give you additional or different | ||||
| permissions.  However, no additional obligations are imposed on any | ||||
| author or copyright holder as a result of your choosing to follow a | ||||
| later version. | ||||
|  | ||||
|   15. Disclaimer of Warranty. | ||||
|  | ||||
|   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | ||||
| APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | ||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | ||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | ||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | ||||
| IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | ||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | ||||
|  | ||||
|   16. Limitation of Liability. | ||||
|  | ||||
|   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | ||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | ||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | ||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | ||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | ||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | ||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | ||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | ||||
| SUCH DAMAGES. | ||||
|  | ||||
|   17. Interpretation of Sections 15 and 16. | ||||
|  | ||||
|   If the disclaimer of warranty and limitation of liability provided | ||||
| above cannot be given local legal effect according to their terms, | ||||
| reviewing courts shall apply local law that most closely approximates | ||||
| an absolute waiver of all civil liability in connection with the | ||||
| Program, unless a warranty or assumption of liability accompanies a | ||||
| copy of the Program in return for a fee. | ||||
|  | ||||
|                      END OF TERMS AND CONDITIONS | ||||
|  | ||||
|             How to Apply These Terms to Your New Programs | ||||
|  | ||||
|   If you develop a new program, and you want it to be of the greatest | ||||
| possible use to the public, the best way to achieve this is to make it | ||||
| free software which everyone can redistribute and change under these terms. | ||||
|  | ||||
|   To do so, attach the following notices to the program.  It is safest | ||||
| to attach them to the start of each source file to most effectively | ||||
| state the exclusion of warranty; and each file should have at least | ||||
| the "copyright" line and a pointer to where the full notice is found. | ||||
|  | ||||
|     <one line to give the program's name and a brief idea of what it does.> | ||||
|     Copyright (C) <year>  <name of author> | ||||
|  | ||||
|     This program is free software: you can redistribute it and/or modify | ||||
|     it under the terms of the GNU General Public License as published by | ||||
|     the Free Software Foundation, either version 3 of the License, or | ||||
|     (at your option) any later version. | ||||
|  | ||||
|     This program is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
|  | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
|  | ||||
|   If the program does terminal interaction, make it output a short | ||||
| notice like this when it starts in an interactive mode: | ||||
|  | ||||
|     <program>  Copyright (C) <year>  <name of author> | ||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|     This is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
|  | ||||
| The hypothetical commands `show w' and `show c' should show the appropriate | ||||
| parts of the General Public License.  Of course, your program's commands | ||||
| might be different; for a GUI interface, you would use an "about box". | ||||
|  | ||||
|   You should also get your employer (if you work as a programmer) or school, | ||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. | ||||
| For more information on this, and how to apply and follow the GNU GPL, see | ||||
| <http://www.gnu.org/licenses/>. | ||||
|  | ||||
|   The GNU General Public License does not permit incorporating your program | ||||
| into proprietary programs.  If your program is a subroutine library, you | ||||
| may consider it more useful to permit linking proprietary applications with | ||||
| the library.  If this is what you want to do, use the GNU Lesser General | ||||
| Public License instead of this License.  But first, please read | ||||
| <http://www.gnu.org/philosophy/why-not-lgpl.html>. | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| include LICENSE | ||||
| include README.md | ||||
| include esphome/dashboard/templates/*.html | ||||
| include esphome/dashboard/static/*.js | ||||
| include esphome/dashboard/static/*.css | ||||
| include esphome/dashboard/static/*.ico | ||||
| recursive-include esphome/dashboard/static *.ico *.js *.css | ||||
| recursive-include esphome *.cpp *.h *.tcc | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| ARG BUILD_FROM=esphome/esphome-base-amd64:1.4.3 | ||||
| ARG BUILD_FROM=esphome/esphome-base-amd64:1.5.1 | ||||
| FROM ${BUILD_FROM} | ||||
|  | ||||
| COPY . . | ||||
| RUN \ | ||||
|     pip2 install --no-cache-dir --no-binary :all: -e . | ||||
| RUN pip2 install --no-cache-dir -e . | ||||
|  | ||||
| WORKDIR /config | ||||
| ENTRYPOINT ["esphome"] | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| ARG BUILD_FROM=esphome/esphome-hassio-base-amd64:1.4.3 | ||||
| ARG BUILD_FROM | ||||
| FROM ${BUILD_FROM} | ||||
|  | ||||
| # Copy root filesystem | ||||
| @@ -6,8 +6,7 @@ COPY docker/rootfs/ / | ||||
| COPY setup.py setup.cfg MANIFEST.in /opt/esphome/ | ||||
| COPY esphome /opt/esphome/esphome | ||||
|  | ||||
| RUN \ | ||||
|     pip2 install --no-cache-dir --no-binary :all: -e /opt/esphome | ||||
| RUN pip2 install --no-cache-dir -e /opt/esphome | ||||
|  | ||||
| # Build arguments | ||||
| ARG BUILD_VERSION=dev | ||||
|   | ||||
| @@ -1,6 +1,18 @@ | ||||
| FROM python:2.7 | ||||
| FROM esphome/esphome-base-amd64:1.5.1 | ||||
|  | ||||
| COPY requirements.txt /requirements.txt | ||||
| RUN \ | ||||
|     apt-get update \ | ||||
|     && apt-get install -y --no-install-recommends \ | ||||
|         clang-format-7 \ | ||||
|         clang-tidy-7 \ | ||||
|         patch \ | ||||
|     && rm -rf \ | ||||
|         /tmp/* \ | ||||
|         /var/{cache,log}/* \ | ||||
|         /var/lib/apt/lists/* | ||||
|  | ||||
| RUN pip install -r /requirements.txt && \ | ||||
|     pip install flake8==3.6.0 pylint==1.9.4 pillow | ||||
| COPY requirements_test.txt /requirements_test.txt | ||||
| RUN pip2 install -r /requirements_test.txt | ||||
|  | ||||
| VOLUME ["/esphome"] | ||||
| WORKDIR /esphome | ||||
|   | ||||
| @@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ | ||||
|         python-pil \ | ||||
|         git \ | ||||
|     && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*rm -rf /var/lib/apt/lists/* /tmp/* && \ | ||||
|     pip install --no-cache-dir --no-binary :all: platformio && \ | ||||
|     pip install --no-cache-dir platformio && \ | ||||
|     platformio settings set enable_telemetry No && \ | ||||
|     platformio settings set check_libraries_interval 1000000 && \ | ||||
|     platformio settings set check_platformio_interval 1000000 && \ | ||||
|   | ||||
| @@ -1,18 +0,0 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| # the architecture to build | ||||
| declare BUILD_ARCH | ||||
|  | ||||
| echo "BUILD_ARCH: ${BUILD_ARCH}" | ||||
|  | ||||
| if [[ ${BUILD_ARCH} = "amd64" ]]; then | ||||
|     echo "No qemu required..." | ||||
|     exit 0 | ||||
| fi | ||||
| if [[ ${BUILD_ARCH} = "i386" ]]; then | ||||
|     echo "No qemu required..." | ||||
|     exit 0 | ||||
| fi | ||||
|  | ||||
| echo "Installing qemu..." | ||||
| docker run --rm --privileged multiarch/qemu-user-static:register --reset | ||||
| @@ -6,21 +6,29 @@ | ||||
|  | ||||
| declare certfile | ||||
| declare keyfile | ||||
| declare port | ||||
| declare direct_port | ||||
| declare ingress_interface | ||||
| declare ingress_port | ||||
|  | ||||
| mkdir -p /var/log/nginx | ||||
|  | ||||
| # Enable SSL | ||||
| if bashio::config.true 'ssl'; then | ||||
|     rm /etc/nginx/nginx.conf | ||||
|     mv /etc/nginx/nginx-ssl.conf /etc/nginx/nginx.conf | ||||
| direct_port=$(bashio::addon.port 6052) | ||||
| if bashio::var.has_value "${direct_port}"; then | ||||
|     if bashio::config.true 'ssl'; then | ||||
|         certfile=$(bashio::config 'certfile') | ||||
|         keyfile=$(bashio::config 'keyfile') | ||||
|  | ||||
|     certfile=$(bashio::config 'certfile') | ||||
|     keyfile=$(bashio::config 'keyfile') | ||||
|         mv /etc/nginx/servers/direct-ssl.disabled /etc/nginx/servers/direct.conf | ||||
|         sed -i "s/%%certfile%%/${certfile}/g" /etc/nginx/servers/direct.conf | ||||
|         sed -i "s/%%keyfile%%/${keyfile}/g" /etc/nginx/servers/direct.conf | ||||
|     else | ||||
|         mv /etc/nginx/servers/direct.disabled /etc/nginx/servers/direct.conf | ||||
|     fi | ||||
|  | ||||
|     sed -i "s/%%certfile%%/${certfile}/g" /etc/nginx/nginx.conf | ||||
|     sed -i "s/%%keyfile%%/${keyfile}/g" /etc/nginx/nginx.conf | ||||
|     sed -i "s/%%port%%/${direct_port}/g" /etc/nginx/servers/direct.conf | ||||
| fi | ||||
|  | ||||
| port=$(bashio::config 'port') | ||||
| sed -i "s/%%port%%/${port}/g" /etc/nginx/nginx.conf | ||||
| ingress_port=$(bashio::addon.ingress_port) | ||||
| ingress_interface=$(bashio::addon.ip_address) | ||||
| sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf | ||||
| sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf | ||||
|   | ||||
| @@ -10,6 +10,6 @@ if bashio::config.has_value 'esphome_version'; then | ||||
|     esphome_version=$(bashio::config 'esphome_version') | ||||
|     full_url="https://github.com/esphome/esphome/archive/${esphome_version}.zip" | ||||
|     bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..." | ||||
|     pip2 install --no-cache-dir --no-binary :all: "${full_url}" \ | ||||
|     pip2 install -U --no-cache-dir "${full_url}" \ | ||||
|       || bashio::exit.nok "Failed installing esphome pinned version." | ||||
| fi | ||||
|   | ||||
							
								
								
									
										96
									
								
								docker/rootfs/etc/nginx/includes/mime.types
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								docker/rootfs/etc/nginx/includes/mime.types
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| types { | ||||
|     text/html                                        html htm shtml; | ||||
|     text/css                                         css; | ||||
|     text/xml                                         xml; | ||||
|     image/gif                                        gif; | ||||
|     image/jpeg                                       jpeg jpg; | ||||
|     application/javascript                           js; | ||||
|     application/atom+xml                             atom; | ||||
|     application/rss+xml                              rss; | ||||
|  | ||||
|     text/mathml                                      mml; | ||||
|     text/plain                                       txt; | ||||
|     text/vnd.sun.j2me.app-descriptor                 jad; | ||||
|     text/vnd.wap.wml                                 wml; | ||||
|     text/x-component                                 htc; | ||||
|  | ||||
|     image/png                                        png; | ||||
|     image/svg+xml                                    svg svgz; | ||||
|     image/tiff                                       tif tiff; | ||||
|     image/vnd.wap.wbmp                               wbmp; | ||||
|     image/webp                                       webp; | ||||
|     image/x-icon                                     ico; | ||||
|     image/x-jng                                      jng; | ||||
|     image/x-ms-bmp                                   bmp; | ||||
|  | ||||
|     font/woff                                        woff; | ||||
|     font/woff2                                       woff2; | ||||
|  | ||||
|     application/java-archive                         jar war ear; | ||||
|     application/json                                 json; | ||||
|     application/mac-binhex40                         hqx; | ||||
|     application/msword                               doc; | ||||
|     application/pdf                                  pdf; | ||||
|     application/postscript                           ps eps ai; | ||||
|     application/rtf                                  rtf; | ||||
|     application/vnd.apple.mpegurl                    m3u8; | ||||
|     application/vnd.google-earth.kml+xml             kml; | ||||
|     application/vnd.google-earth.kmz                 kmz; | ||||
|     application/vnd.ms-excel                         xls; | ||||
|     application/vnd.ms-fontobject                    eot; | ||||
|     application/vnd.ms-powerpoint                    ppt; | ||||
|     application/vnd.oasis.opendocument.graphics      odg; | ||||
|     application/vnd.oasis.opendocument.presentation  odp; | ||||
|     application/vnd.oasis.opendocument.spreadsheet   ods; | ||||
|     application/vnd.oasis.opendocument.text          odt; | ||||
|     application/vnd.openxmlformats-officedocument.presentationml.presentation | ||||
|                                                      pptx; | ||||
|     application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | ||||
|                                                      xlsx; | ||||
|     application/vnd.openxmlformats-officedocument.wordprocessingml.document | ||||
|                                                      docx; | ||||
|     application/vnd.wap.wmlc                         wmlc; | ||||
|     application/x-7z-compressed                      7z; | ||||
|     application/x-cocoa                              cco; | ||||
|     application/x-java-archive-diff                  jardiff; | ||||
|     application/x-java-jnlp-file                     jnlp; | ||||
|     application/x-makeself                           run; | ||||
|     application/x-perl                               pl pm; | ||||
|     application/x-pilot                              prc pdb; | ||||
|     application/x-rar-compressed                     rar; | ||||
|     application/x-redhat-package-manager             rpm; | ||||
|     application/x-sea                                sea; | ||||
|     application/x-shockwave-flash                    swf; | ||||
|     application/x-stuffit                            sit; | ||||
|     application/x-tcl                                tcl tk; | ||||
|     application/x-x509-ca-cert                       der pem crt; | ||||
|     application/x-xpinstall                          xpi; | ||||
|     application/xhtml+xml                            xhtml; | ||||
|     application/xspf+xml                             xspf; | ||||
|     application/zip                                  zip; | ||||
|  | ||||
|     application/octet-stream                         bin exe dll; | ||||
|     application/octet-stream                         deb; | ||||
|     application/octet-stream                         dmg; | ||||
|     application/octet-stream                         iso img; | ||||
|     application/octet-stream                         msi msp msm; | ||||
|  | ||||
|     audio/midi                                       mid midi kar; | ||||
|     audio/mpeg                                       mp3; | ||||
|     audio/ogg                                        ogg; | ||||
|     audio/x-m4a                                      m4a; | ||||
|     audio/x-realaudio                                ra; | ||||
|  | ||||
|     video/3gpp                                       3gpp 3gp; | ||||
|     video/mp2t                                       ts; | ||||
|     video/mp4                                        mp4; | ||||
|     video/mpeg                                       mpeg mpg; | ||||
|     video/quicktime                                  mov; | ||||
|     video/webm                                       webm; | ||||
|     video/x-flv                                      flv; | ||||
|     video/x-m4v                                      m4v; | ||||
|     video/x-mng                                      mng; | ||||
|     video/x-ms-asf                                   asx asf; | ||||
|     video/x-ms-wmv                                   wmv; | ||||
|     video/x-msvideo                                  avi; | ||||
| } | ||||
							
								
								
									
										16
									
								
								docker/rootfs/etc/nginx/includes/proxy_params.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								docker/rootfs/etc/nginx/includes/proxy_params.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| proxy_http_version 1.1; | ||||
| proxy_ignore_client_abort off; | ||||
| proxy_read_timeout 86400s; | ||||
| proxy_redirect off; | ||||
| proxy_send_timeout 86400s; | ||||
| proxy_max_temp_file_size 0; | ||||
|  | ||||
| proxy_set_header Accept-Encoding ""; | ||||
| proxy_set_header Connection $connection_upgrade; | ||||
| proxy_set_header Host $http_host; | ||||
| proxy_set_header Upgrade $http_upgrade; | ||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||||
| proxy_set_header X-Forwarded-Proto $scheme; | ||||
| proxy_set_header X-NginX-Proxy true; | ||||
| proxy_set_header X-Real-IP $remote_addr; | ||||
| proxy_set_header Authorization ""; | ||||
							
								
								
									
										6
									
								
								docker/rootfs/etc/nginx/includes/server_params.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								docker/rootfs/etc/nginx/includes/server_params.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| root /dev/null; | ||||
| server_name $hostname; | ||||
|  | ||||
| add_header X-Content-Type-Options nosniff; | ||||
| add_header X-XSS-Protection "1; mode=block"; | ||||
| add_header X-Robots-Tag none; | ||||
							
								
								
									
										9
									
								
								docker/rootfs/etc/nginx/includes/ssl_params.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								docker/rootfs/etc/nginx/includes/ssl_params.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| ssl_protocols TLSv1.2; | ||||
| ssl_prefer_server_ciphers on; | ||||
| ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA; | ||||
| ssl_ecdh_curve secp384r1; | ||||
| ssl_session_timeout  10m; | ||||
| ssl_session_cache shared:SSL:10m; | ||||
| ssl_session_tickets off; | ||||
| ssl_stapling on; | ||||
| ssl_stapling_verify on; | ||||
| @@ -1,62 +0,0 @@ | ||||
| worker_processes  1; | ||||
| pid /var/run/nginx.pid; | ||||
| error_log stderr; | ||||
|  | ||||
| events { | ||||
|     worker_connections  1024; | ||||
| } | ||||
|  | ||||
| http { | ||||
|     access_log         stdout; | ||||
|     include            mime.types; | ||||
|     default_type       application/octet-stream; | ||||
|     sendfile           on; | ||||
|     keepalive_timeout  65; | ||||
|  | ||||
|     upstream esphome { | ||||
|         ip_hash; | ||||
|         server unix:/var/run/esphome.sock; | ||||
|     } | ||||
|     map $http_upgrade $connection_upgrade { | ||||
|         default upgrade; | ||||
|         ''      close; | ||||
|     } | ||||
|  | ||||
|     server { | ||||
|         server_name hassio.local; | ||||
|         listen %%port%% default_server ssl; | ||||
|         root /dev/null; | ||||
|  | ||||
|         ssl_certificate /ssl/%%certfile%%; | ||||
|         ssl_certificate_key /ssl/%%keyfile%%; | ||||
|         ssl_protocols TLSv1.2; | ||||
|         ssl_prefer_server_ciphers on; | ||||
|         ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA; | ||||
|         ssl_ecdh_curve secp384r1; | ||||
|         ssl_session_timeout  10m; | ||||
|         ssl_session_cache shared:SSL:10m; | ||||
|         ssl_session_tickets off; | ||||
|         ssl_stapling on; | ||||
|         ssl_stapling_verify on; | ||||
|  | ||||
|         # Redirect http requests to https on the same port. | ||||
|         # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/ | ||||
|         error_page 497 https://$http_host$request_uri; | ||||
|  | ||||
|         location / { | ||||
|             proxy_redirect off; | ||||
|             proxy_pass http://esphome; | ||||
|  | ||||
|             proxy_http_version 1.1; | ||||
|             proxy_set_header Upgrade $http_upgrade; | ||||
|             proxy_set_header Connection $connection_upgrade; | ||||
|             proxy_set_header Authorization ""; | ||||
|  | ||||
|             proxy_set_header X-Real-IP $remote_addr; | ||||
|             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||||
|             proxy_set_header X-Forwarded-Proto $scheme; | ||||
|             proxy_set_header Host $http_host; | ||||
|             proxy_set_header X-NginX-Proxy true; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,46 +1,33 @@ | ||||
| worker_processes  1; | ||||
| daemon off; | ||||
| user root; | ||||
| pid /var/run/nginx.pid; | ||||
| error_log stderr; | ||||
|  | ||||
| worker_processes 1; | ||||
| # Hass.io addon log | ||||
| error_log /proc/1/fd/1 error; | ||||
| events { | ||||
|     worker_connections  1024; | ||||
|     worker_connections 1024; | ||||
| } | ||||
|  | ||||
| http { | ||||
|     access_log         stdout; | ||||
|     include            mime.types; | ||||
|     default_type       application/octet-stream; | ||||
|     sendfile           on; | ||||
|     keepalive_timeout  65; | ||||
|     include /etc/nginx/includes/mime.types; | ||||
|     access_log stdout; | ||||
|     default_type application/octet-stream; | ||||
|     gzip on; | ||||
|     keepalive_timeout 65; | ||||
|     sendfile on; | ||||
|     server_tokens off; | ||||
|  | ||||
|     upstream esphome { | ||||
|         ip_hash; | ||||
|         server unix:/var/run/esphome.sock; | ||||
|     } | ||||
|     map $http_upgrade $connection_upgrade { | ||||
|         default upgrade; | ||||
|         ''      close; | ||||
|     } | ||||
|  | ||||
|     server { | ||||
|         server_name hassio.local; | ||||
|         listen %%port%% default_server; | ||||
|         root /dev/null; | ||||
|     # Use Hass.io supervisor as resolver | ||||
|     resolver 172.30.32.2; | ||||
|  | ||||
|         location / { | ||||
|             proxy_redirect off; | ||||
|             proxy_pass http://esphome; | ||||
|  | ||||
|             proxy_http_version 1.1; | ||||
|             proxy_set_header Upgrade $http_upgrade; | ||||
|             proxy_set_header Connection $connection_upgrade; | ||||
|             proxy_set_header Authorization ""; | ||||
|  | ||||
|             proxy_set_header X-Real-IP $remote_addr; | ||||
|             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||||
|             proxy_set_header X-Forwarded-Proto $scheme; | ||||
|             proxy_set_header Host $http_host; | ||||
|             proxy_set_header X-NginX-Proxy true; | ||||
|         } | ||||
|     upstream esphome { | ||||
|         server unix:/var/run/esphome.sock; | ||||
|     } | ||||
|  | ||||
|     include /etc/nginx/servers/*.conf; | ||||
| } | ||||
|   | ||||
							
								
								
									
										17
									
								
								docker/rootfs/etc/nginx/servers/direct-ssl.disabled
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								docker/rootfs/etc/nginx/servers/direct-ssl.disabled
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| server { | ||||
|     listen %%port%% default_server ssl http2; | ||||
|  | ||||
|     include /etc/nginx/includes/server_params.conf; | ||||
|     include /etc/nginx/includes/proxy_params.conf; | ||||
|     include /etc/nginx/includes/ssl_params.conf; | ||||
|     # Clear Hass.io Ingress header | ||||
|     proxy_set_header X-Hassio-Ingress ""; | ||||
|  | ||||
|     # Redirect http requests to https on the same port. | ||||
|     # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/ | ||||
|     error_page 497 https://$http_host$request_uri; | ||||
|  | ||||
|     location / { | ||||
|         proxy_pass http://esphome; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										12
									
								
								docker/rootfs/etc/nginx/servers/direct.disabled
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docker/rootfs/etc/nginx/servers/direct.disabled
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| server { | ||||
|     listen %%port%% default_server; | ||||
|  | ||||
|     include /etc/nginx/includes/server_params.conf; | ||||
|     include /etc/nginx/includes/proxy_params.conf; | ||||
|     # Clear Hass.io Ingress header | ||||
|     proxy_set_header X-Hassio-Ingress ""; | ||||
|  | ||||
|     location / { | ||||
|         proxy_pass http://esphome; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								docker/rootfs/etc/nginx/servers/ingress.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								docker/rootfs/etc/nginx/servers/ingress.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| server { | ||||
|     listen %%interface%%:%%port%% default_server; | ||||
|  | ||||
|     include /etc/nginx/includes/server_params.conf; | ||||
|     include /etc/nginx/includes/proxy_params.conf; | ||||
|     # Set Hass.io Ingress header | ||||
|     proxy_set_header X-Hassio-Ingress "YES"; | ||||
|  | ||||
|     location / { | ||||
|         # Only allow from Hass.io supervisor | ||||
|         allow   172.30.32.2; | ||||
|         deny    all; | ||||
|  | ||||
|         proxy_pass http://esphome; | ||||
|     } | ||||
| } | ||||
| @@ -4,5 +4,11 @@ | ||||
| # Runs the NGINX proxy | ||||
| # ============================================================================== | ||||
|  | ||||
| bashio::log.info "Waiting for dashboard to come up..." | ||||
|  | ||||
| while [[ ! -S /var/run/esphome.sock ]]; do | ||||
|   sleep 0.5 | ||||
| done | ||||
|  | ||||
| bashio::log.info "Starting NGINX..." | ||||
| exec nginx -g "daemon off;" | ||||
| exec nginx | ||||
|   | ||||
| @@ -1,30 +1,24 @@ | ||||
| from __future__ import print_function | ||||
|  | ||||
| import argparse | ||||
| from datetime import datetime | ||||
| import functools | ||||
| import logging | ||||
| import os | ||||
| import random | ||||
| import sys | ||||
| from datetime import datetime | ||||
|  | ||||
| from esphome import const, core_config, mqtt, platformio_api, wizard, writer, yaml_util | ||||
| from esphome.api.client import run_logs | ||||
| from esphome.config import get_component, iter_components, read_config, strip_default_ids | ||||
| from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_ESPHOME, CONF_LOGGER, \ | ||||
|     CONF_USE_CUSTOM_CODE | ||||
| from esphome.core import CORE, EsphomeError | ||||
| from esphome.cpp_generator import Expression, RawStatement, add, statement | ||||
| from esphome import const, writer, yaml_util | ||||
| import esphome.codegen as cg | ||||
| from esphome.config import iter_components, read_config, strip_default_ids | ||||
| from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \ | ||||
|     CONF_PASSWORD, CONF_PORT | ||||
| from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority | ||||
| from esphome.helpers import color, indent | ||||
| from esphome.py_compat import IS_PY2, safe_input, text_type | ||||
| from esphome.storage_json import StorageJSON, storage_path | ||||
| from esphome.util import run_external_command, run_external_process, safe_print, \ | ||||
|     is_dev_esphome_version | ||||
| from esphome.py_compat import IS_PY2, safe_input | ||||
| from esphome.util import run_external_command, run_external_process, safe_print | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| PRE_INITIALIZE = ['esphome', 'logger', 'wifi', 'ethernet', 'ota', 'mqtt', 'web_server', 'api', | ||||
|                   'i2c'] | ||||
|  | ||||
|  | ||||
| def get_serial_ports(): | ||||
|     # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py | ||||
| @@ -94,6 +88,8 @@ def get_port_type(port): | ||||
|  | ||||
| def run_miniterm(config, port): | ||||
|     import serial | ||||
|     from esphome import platformio_api | ||||
|  | ||||
|     if CONF_LOGGER not in config: | ||||
|         _LOGGER.info("Logger is not enabled. Not starting UART logs.") | ||||
|         return | ||||
| @@ -123,50 +119,45 @@ def run_miniterm(config, port): | ||||
|                 config, line, backtrace_state=backtrace_state) | ||||
|  | ||||
|  | ||||
| def wrap_to_code(name, comp): | ||||
|     coro = coroutine(comp.to_code) | ||||
|  | ||||
|     @functools.wraps(comp.to_code) | ||||
|     @coroutine_with_priority(coro.priority) | ||||
|     def wrapped(conf): | ||||
|         cg.add(cg.LineComment(u"{}:".format(name))) | ||||
|         if comp.config_schema is not None: | ||||
|             conf_str = yaml_util.dump(conf) | ||||
|             if IS_PY2: | ||||
|                 conf_str = conf_str.decode('utf-8') | ||||
|             cg.add(cg.LineComment(indent(conf_str))) | ||||
|         yield coro(conf) | ||||
|  | ||||
|     return wrapped | ||||
|  | ||||
|  | ||||
| def write_cpp(config): | ||||
|     _LOGGER.info("Generating C++ source...") | ||||
|  | ||||
|     CORE.add_job(core_config.to_code, config[CONF_ESPHOME], domain='esphome') | ||||
|     for domain in PRE_INITIALIZE: | ||||
|         if domain == CONF_ESPHOME or domain not in config: | ||||
|             continue | ||||
|         CORE.add_job(get_component(domain).to_code, config[domain], domain=domain) | ||||
|  | ||||
|     for domain, component, conf in iter_components(config): | ||||
|         if domain in PRE_INITIALIZE or not hasattr(component, 'to_code'): | ||||
|             continue | ||||
|         CORE.add_job(component.to_code, conf, domain=domain) | ||||
|     for name, component, conf in iter_components(CORE.config): | ||||
|         if component.to_code is not None: | ||||
|             coro = wrap_to_code(name, component) | ||||
|             CORE.add_job(coro, conf) | ||||
|  | ||||
|     CORE.flush_tasks() | ||||
|     add(RawStatement('')) | ||||
|     add(RawStatement('')) | ||||
|     all_code = [] | ||||
|     for exp in CORE.expressions: | ||||
|         if not config[CONF_ESPHOME][CONF_USE_CUSTOM_CODE]: | ||||
|             if isinstance(exp, Expression) and not exp.required: | ||||
|                 continue | ||||
|         all_code.append(text_type(statement(exp))) | ||||
|  | ||||
|     writer.write_platformio_project() | ||||
|  | ||||
|     code_s = indent('\n'.join(line.rstrip() for line in all_code)) | ||||
|     code_s = indent(CORE.cpp_main_section) | ||||
|     writer.write_cpp(code_s) | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def compile_program(args, config): | ||||
|     from esphome import platformio_api | ||||
|  | ||||
|     _LOGGER.info("Compiling app...") | ||||
|     rc = platformio_api.run_compile(config, args.verbose) | ||||
|     if rc != 0 and CORE.is_dev_esphome_core_version and not is_dev_esphome_version(): | ||||
|         _LOGGER.warning("You're using 'esphome_core_version: dev' but not using the " | ||||
|                         "dev version of the ESPHome tool.") | ||||
|         _LOGGER.warning("Expect compile errors if these versions are out of sync.") | ||||
|         _LOGGER.warning("Please install the dev version of ESPHome too when using " | ||||
|                         "'esphome_core_version: dev'.") | ||||
|         _LOGGER.warning(" - Hass.io: Install 'ESPHome (dev)' addon") | ||||
|         _LOGGER.warning(" - Docker: docker run [...] esphome/esphome:dev [...]") | ||||
|         _LOGGER.warning(" - PIP: pip install -U https://github.com/esphome/esphome/archive/dev.zip") | ||||
|     return rc | ||||
|     return platformio_api.run_compile(config, args.verbose) | ||||
|  | ||||
|  | ||||
| def upload_using_esptool(config, port): | ||||
| @@ -185,35 +176,19 @@ def upload_using_esptool(config, port): | ||||
| def upload_program(config, args, host): | ||||
|     # if upload is to a serial port use platformio, otherwise assume ota | ||||
|     if get_port_type(host) == 'SERIAL': | ||||
|         from esphome import platformio_api | ||||
|  | ||||
|         if CORE.is_esp8266: | ||||
|             return upload_using_esptool(config, host) | ||||
|         return platformio_api.run_upload(config, args.verbose, host) | ||||
|  | ||||
|     from esphome.components import ota | ||||
|     from esphome import espota2 | ||||
|  | ||||
|     if args.host_port is not None: | ||||
|         host_port = args.host_port | ||||
|     else: | ||||
|         host_port = int(os.getenv('ESPHOME_OTA_HOST_PORT', random.randint(10000, 60000))) | ||||
|  | ||||
|     verbose = args.verbose | ||||
|     remote_port = ota.get_port(config) | ||||
|     password = ota.get_auth(config) | ||||
|  | ||||
|     storage = StorageJSON.load(storage_path()) | ||||
|     ota_conf = config[CONF_OTA] | ||||
|     remote_port = ota_conf[CONF_PORT] | ||||
|     password = ota_conf[CONF_PASSWORD] | ||||
|     res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin) | ||||
|     if res == 0: | ||||
|         if storage is not None and storage.use_legacy_ota: | ||||
|             storage.use_legacy_ota = False | ||||
|             storage.save(storage_path()) | ||||
|         return res | ||||
|     if storage is not None and not storage.use_legacy_ota: | ||||
|         return res | ||||
|  | ||||
|     _LOGGER.warning("OTA v2 method failed. Trying with legacy OTA...") | ||||
|     return espota2.run_legacy_ota(verbose, host_port, host, remote_port, password, | ||||
|                                   CORE.firmware_bin) | ||||
|     return res | ||||
|  | ||||
|  | ||||
| def show_logs(config, args, port): | ||||
| @@ -223,19 +198,30 @@ def show_logs(config, args, port): | ||||
|         run_miniterm(config, port) | ||||
|         return 0 | ||||
|     if get_port_type(port) == 'NETWORK' and 'api' in config: | ||||
|         from esphome.api.client import run_logs | ||||
|  | ||||
|         return run_logs(config, port) | ||||
|     if get_port_type(port) == 'MQTT' and 'mqtt' in config: | ||||
|         from esphome import mqtt | ||||
|  | ||||
|         return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) | ||||
|  | ||||
|     raise ValueError | ||||
|     raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)") | ||||
|  | ||||
|  | ||||
| def clean_mqtt(config, args): | ||||
|     from esphome import mqtt | ||||
|  | ||||
|     return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id) | ||||
|  | ||||
|  | ||||
| def setup_log(debug=False): | ||||
|     log_level = logging.DEBUG if debug else logging.INFO | ||||
| def setup_log(debug=False, quiet=False): | ||||
|     if debug: | ||||
|         log_level = logging.DEBUG | ||||
|     elif quiet: | ||||
|         log_level = logging.CRITICAL | ||||
|     else: | ||||
|         log_level = logging.INFO | ||||
|     logging.basicConfig(level=log_level) | ||||
|     fmt = "%(levelname)s %(message)s" | ||||
|     colorfmt = "%(log_color)s{}%(reset)s".format(fmt) | ||||
| @@ -262,6 +248,8 @@ def setup_log(debug=False): | ||||
|  | ||||
|  | ||||
| def command_wizard(args): | ||||
|     from esphome import wizard | ||||
|  | ||||
|     return wizard.wizard(args.configuration) | ||||
|  | ||||
|  | ||||
| @@ -273,6 +261,13 @@ def command_config(args, config): | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def command_vscode(args): | ||||
|     from esphome import vscode | ||||
|  | ||||
|     CORE.config_path = args.configuration | ||||
|     vscode.read_config(args) | ||||
|  | ||||
|  | ||||
| def command_compile(args, config): | ||||
|     exit_code = write_cpp(config) | ||||
|     if exit_code != 0: | ||||
| @@ -329,6 +324,8 @@ def command_clean_mqtt(args, config): | ||||
|  | ||||
|  | ||||
| def command_mqtt_fingerprint(args, config): | ||||
|     from esphome import mqtt | ||||
|  | ||||
|     return mqtt.get_fingerprint(config) | ||||
|  | ||||
|  | ||||
| @@ -356,7 +353,8 @@ def command_dashboard(args): | ||||
| PRE_CONFIG_ACTIONS = { | ||||
|     'wizard': command_wizard, | ||||
|     'version': command_version, | ||||
|     'dashboard': command_dashboard | ||||
|     'dashboard': command_dashboard, | ||||
|     'vscode': command_vscode, | ||||
| } | ||||
|  | ||||
| POST_CONFIG_ACTIONS = { | ||||
| @@ -375,8 +373,9 @@ def parse_args(argv): | ||||
|     parser = argparse.ArgumentParser(prog='esphome') | ||||
|     parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.", | ||||
|                         action='store_true') | ||||
|     parser.add_argument('--dashboard', help="Internal flag to set if the command is run from the " | ||||
|                                             "dashboard.", action='store_true') | ||||
|     parser.add_argument('-q', '--quiet', help="Disable all esphome logs.", | ||||
|                         action='store_true') | ||||
|     parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true') | ||||
|     parser.add_argument('configuration', help='Your YAML configuration file.') | ||||
|  | ||||
|     subparsers = parser.add_subparsers(help='Commands', dest='command') | ||||
| @@ -393,7 +392,6 @@ def parse_args(argv): | ||||
|                                                          'and upload the latest binary.') | ||||
|     parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. " | ||||
|                                                      "For example /dev/cu.SLAB_USBtoUART.") | ||||
|     parser_upload.add_argument('--host-port', help="Specify the host port.", type=int) | ||||
|  | ||||
|     parser_logs = subparsers.add_parser('logs', help='Validate the configuration ' | ||||
|                                                      'and show all MQTT logs.') | ||||
| @@ -408,7 +406,6 @@ def parse_args(argv): | ||||
|                                                    'upload it, and start MQTT logs.') | ||||
|     parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. " | ||||
|                                                   "For example /dev/cu.SLAB_USBtoUART.") | ||||
|     parser_run.add_argument('--host-port', help="Specify the host port to use for OTA", type=int) | ||||
|     parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.', | ||||
|                             action='store_true') | ||||
|     parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.') | ||||
| @@ -441,12 +438,14 @@ def parse_args(argv): | ||||
|     dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.", | ||||
|                            action='store_true') | ||||
|     dashboard.add_argument("--hassio", | ||||
|                            help="Internal flag used to tell esphome is started as a Hass.io " | ||||
|                                 "add-on.", | ||||
|                            help=argparse.SUPPRESS, | ||||
|                            action="store_true") | ||||
|     dashboard.add_argument("--socket", | ||||
|                            help="Make the dashboard serve under a unix socket", type=str) | ||||
|  | ||||
|     vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS) | ||||
|     vscode.add_argument('--ace', action='store_true') | ||||
|  | ||||
|     return parser.parse_args(argv[1:]) | ||||
|  | ||||
|  | ||||
| @@ -454,7 +453,7 @@ def run_esphome(argv): | ||||
|     args = parse_args(argv) | ||||
|     CORE.dashboard = args.dashboard | ||||
|  | ||||
|     setup_log(args.verbose) | ||||
|     setup_log(args.verbose, args.quiet) | ||||
|     if args.command in PRE_CONFIG_ACTIONS: | ||||
|         try: | ||||
|             return PRE_CONFIG_ACTIONS[args.command](args) | ||||
|   | ||||
| @@ -1,145 +1,94 @@ | ||||
| import copy | ||||
|  | ||||
| import voluptuous as vol | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ABOVE, CONF_ACTION_ID, CONF_AND, CONF_AUTOMATION_ID, CONF_BELOW, \ | ||||
|     CONF_CONDITION, CONF_CONDITION_ID, CONF_DELAY, CONF_ELSE, CONF_ID, CONF_IF, CONF_LAMBDA, \ | ||||
|     CONF_OR, CONF_RANGE, CONF_THEN, CONF_TRIGGER_ID, CONF_WAIT_UNTIL, CONF_WHILE | ||||
| from esphome.core import CORE | ||||
| from esphome.cpp_generator import Pvariable, TemplateArguments, add, get_variable, \ | ||||
|     process_lambda, templatable | ||||
| from esphome.cpp_types import Action, App, Component, PollingComponent, Trigger, bool_, \ | ||||
|     esphome_ns, float_, uint32, void | ||||
| from esphome.util import ServiceRegistry | ||||
| from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \ | ||||
|     CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME | ||||
| from esphome.core import coroutine | ||||
| from esphome.util import Registry | ||||
|  | ||||
|  | ||||
| def maybe_simple_id(*validators): | ||||
|     validator = vol.All(*validators) | ||||
|     validator = cv.All(*validators) | ||||
|  | ||||
|     def validate(value): | ||||
|         if isinstance(value, dict): | ||||
|             return validator(value) | ||||
|         return validator({CONF_ID: value}) | ||||
|         with cv.remove_prepend_path([CONF_ID]): | ||||
|             return validator({CONF_ID: value}) | ||||
|  | ||||
|     return validate | ||||
|  | ||||
|  | ||||
| def validate_recursive_condition(value): | ||||
|     is_list = isinstance(value, list) | ||||
|     value = cv.ensure_list()(value)[:] | ||||
|     for i, item in enumerate(value): | ||||
|         path = [i] if is_list else [] | ||||
|         item = copy.deepcopy(item) | ||||
|         if not isinstance(item, dict): | ||||
|             raise vol.Invalid(u"Condition must consist of key-value mapping! Got {}".format(item), | ||||
|                               path) | ||||
|         key = next((x for x in item if x != CONF_CONDITION_ID), None) | ||||
|         if key is None: | ||||
|             raise vol.Invalid(u"Key missing from action! Got {}".format(item), path) | ||||
|         if key not in CONDITION_REGISTRY: | ||||
|             raise vol.Invalid(u"Unable to find condition with the name '{}', is the " | ||||
|                               u"component loaded?".format(key), path + [key]) | ||||
|         item.setdefault(CONF_CONDITION_ID, None) | ||||
|         key2 = next((x for x in item if x not in (CONF_CONDITION_ID, key)), None) | ||||
|         if key2 is not None: | ||||
|             raise vol.Invalid(u"Cannot have two conditions in one item. Key '{}' overrides '{}'! " | ||||
|                               u"Did you forget to indent the block inside the condition?" | ||||
|                               u"".format(key, key2), path) | ||||
|         validator = CONDITION_REGISTRY[key][0] | ||||
|         try: | ||||
|             condition = validator(item[key] or {}) | ||||
|         except vol.Invalid as err: | ||||
|             err.prepend(path) | ||||
|             raise err | ||||
|         value[i] = { | ||||
|             CONF_CONDITION_ID: cv.declare_variable_id(Condition)(item[CONF_CONDITION_ID]), | ||||
|             key: condition, | ||||
|         } | ||||
|     return value | ||||
| def register_action(name, action_type, schema): | ||||
|     return ACTION_REGISTRY.register(name, action_type, schema) | ||||
|  | ||||
|  | ||||
| def validate_recursive_action(value): | ||||
|     is_list = isinstance(value, list) | ||||
|     if not is_list: | ||||
|         value = [value] | ||||
|     for i, item in enumerate(value): | ||||
|         path = [i] if is_list else [] | ||||
|         item = copy.deepcopy(item) | ||||
|         if not isinstance(item, dict): | ||||
|             raise vol.Invalid(u"Action must consist of key-value mapping! Got {}".format(item), | ||||
|                               path) | ||||
|         key = next((x for x in item if x != CONF_ACTION_ID), None) | ||||
|         if key is None: | ||||
|             raise vol.Invalid(u"Key missing from action! Got {}".format(item), path) | ||||
|         if key not in ACTION_REGISTRY: | ||||
|             raise vol.Invalid(u"Unable to find action with the name '{}', is the component loaded?" | ||||
|                               u"".format(key), path + [key]) | ||||
|         item.setdefault(CONF_ACTION_ID, None) | ||||
|         key2 = next((x for x in item if x not in (CONF_ACTION_ID, key)), None) | ||||
|         if key2 is not None: | ||||
|             raise vol.Invalid(u"Cannot have two actions in one item. Key '{}' overrides '{}'! " | ||||
|                               u"Did you forget to indent the block inside the action?" | ||||
|                               u"".format(key, key2), path) | ||||
|         validator = ACTION_REGISTRY[key][0] | ||||
|         try: | ||||
|             action = validator(item[key] or {}) | ||||
|         except vol.Invalid as err: | ||||
|             err.prepend(path) | ||||
|             raise err | ||||
|         value[i] = { | ||||
|             CONF_ACTION_ID: cv.declare_variable_id(Action)(item[CONF_ACTION_ID]), | ||||
|             key: action, | ||||
|         } | ||||
|     return value | ||||
| def register_condition(name, condition_type, schema): | ||||
|     return CONDITION_REGISTRY.register(name, condition_type, schema) | ||||
|  | ||||
|  | ||||
| ACTION_REGISTRY = ServiceRegistry() | ||||
| CONDITION_REGISTRY = ServiceRegistry() | ||||
| Action = cg.esphome_ns.class_('Action') | ||||
| Trigger = cg.esphome_ns.class_('Trigger') | ||||
| ACTION_REGISTRY = Registry() | ||||
| Condition = cg.esphome_ns.class_('Condition') | ||||
| CONDITION_REGISTRY = Registry() | ||||
| validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY) | ||||
| validate_action_list = cv.validate_registry('action', ACTION_REGISTRY) | ||||
| validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY) | ||||
| validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY) | ||||
|  | ||||
| # pylint: disable=invalid-name | ||||
| DelayAction = esphome_ns.class_('DelayAction', Action, Component) | ||||
| LambdaAction = esphome_ns.class_('LambdaAction', Action) | ||||
| IfAction = esphome_ns.class_('IfAction', Action) | ||||
| WhileAction = esphome_ns.class_('WhileAction', Action) | ||||
| WaitUntilAction = esphome_ns.class_('WaitUntilAction', Action, Component) | ||||
| UpdateComponentAction = esphome_ns.class_('UpdateComponentAction', Action) | ||||
| Automation = esphome_ns.class_('Automation') | ||||
|  | ||||
| Condition = esphome_ns.class_('Condition') | ||||
| AndCondition = esphome_ns.class_('AndCondition', Condition) | ||||
| OrCondition = esphome_ns.class_('OrCondition', Condition) | ||||
| RangeCondition = esphome_ns.class_('RangeCondition', Condition) | ||||
| LambdaCondition = esphome_ns.class_('LambdaCondition', Condition) | ||||
| def validate_potentially_and_condition(value): | ||||
|     if isinstance(value, list): | ||||
|         with cv.remove_prepend_path(['and']): | ||||
|             return validate_condition({ | ||||
|                 'and': value | ||||
|             }) | ||||
|     return validate_condition(value) | ||||
|  | ||||
|  | ||||
| DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component) | ||||
| LambdaAction = cg.esphome_ns.class_('LambdaAction', Action) | ||||
| IfAction = cg.esphome_ns.class_('IfAction', Action) | ||||
| WhileAction = cg.esphome_ns.class_('WhileAction', Action) | ||||
| WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component) | ||||
| UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action) | ||||
| Automation = cg.esphome_ns.class_('Automation') | ||||
|  | ||||
| LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition) | ||||
| ForCondition = cg.esphome_ns.class_('ForCondition', Condition) | ||||
|  | ||||
|  | ||||
| def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
|     if extra_schema is None: | ||||
|         extra_schema = {} | ||||
|     if isinstance(extra_schema, vol.Schema): | ||||
|     if isinstance(extra_schema, cv.Schema): | ||||
|         extra_schema = extra_schema.schema | ||||
|     schema = AUTOMATION_SCHEMA.extend(extra_schema) | ||||
|  | ||||
|     def validator_(value): | ||||
|         if isinstance(value, list): | ||||
|             # List of items, there are two possible options here, either a sequence of | ||||
|             # actions (no then:) or a list of automations. | ||||
|             try: | ||||
|                 # First try as a sequence of actions | ||||
|                 return [schema({CONF_THEN: value})] | ||||
|             except vol.Invalid as err: | ||||
|                 if err.path and err.path[0] == CONF_THEN: | ||||
|                     err.path.pop(0) | ||||
|  | ||||
|                 # If that succeeds, return immediately | ||||
|                 with cv.remove_prepend_path([CONF_THEN]): | ||||
|                     return [schema({CONF_THEN: value})] | ||||
|             except cv.Invalid as err: | ||||
|                 # Next try as a sequence of automations | ||||
|                 try: | ||||
|                     return cv.Schema([schema])(value) | ||||
|                 except vol.Invalid as err2: | ||||
|                     if 'Unable to find action' in str(err): | ||||
|                 except cv.Invalid as err2: | ||||
|                     if u'extra keys not allowed' in str(err2) and len(err2.path) == 2: | ||||
|                         raise err | ||||
|                     if u'Unable to find action' in str(err): | ||||
|                         raise err2 | ||||
|                     raise vol.MultipleInvalid([err, err2]) | ||||
|                     raise cv.MultipleInvalid([err, err2]) | ||||
|         elif isinstance(value, dict): | ||||
|             if CONF_THEN in value: | ||||
|                 return [schema(value)] | ||||
|             return [schema({CONF_THEN: value})] | ||||
|             with cv.remove_prepend_path([CONF_THEN]): | ||||
|                 return [schema({CONF_THEN: value})] | ||||
|         # This should only happen with invalid configs, but let's have a nice error message. | ||||
|         return [schema(value)] | ||||
|  | ||||
| @@ -149,7 +98,7 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
|             value = cv.Schema([extra_validators])(value) | ||||
|         if single: | ||||
|             if len(value) != 1: | ||||
|                 raise vol.Invalid("Cannot have more than 1 automation for templates") | ||||
|                 raise cv.Invalid("Cannot have more than 1 automation for templates") | ||||
|             return value[0] | ||||
|         return value | ||||
|  | ||||
| @@ -157,228 +106,161 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
|  | ||||
|  | ||||
| AUTOMATION_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(Trigger), | ||||
|     cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_variable_id(Automation), | ||||
|     vol.Required(CONF_THEN): validate_recursive_action, | ||||
|     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger), | ||||
|     cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation), | ||||
|     cv.Required(CONF_THEN): validate_action_list, | ||||
| }) | ||||
|  | ||||
| AND_CONDITION_SCHEMA = validate_recursive_condition | ||||
| AndCondition = cg.esphome_ns.class_('AndCondition', Condition) | ||||
| OrCondition = cg.esphome_ns.class_('OrCondition', Condition) | ||||
| NotCondition = cg.esphome_ns.class_('NotCondition', Condition) | ||||
|  | ||||
|  | ||||
| @CONDITION_REGISTRY.register(CONF_AND, AND_CONDITION_SCHEMA) | ||||
| @register_condition('and', AndCondition, validate_condition_list) | ||||
| def and_condition_to_code(config, condition_id, template_arg, args): | ||||
|     for conditions in build_conditions(config, template_arg, args): | ||||
|         yield | ||||
|     rhs = AndCondition.new(template_arg, conditions) | ||||
|     type = AndCondition.template(template_arg) | ||||
|     yield Pvariable(condition_id, rhs, type=type) | ||||
|     conditions = yield build_condition_list(config, template_arg, args) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, conditions) | ||||
|  | ||||
|  | ||||
| OR_CONDITION_SCHEMA = validate_recursive_condition | ||||
|  | ||||
|  | ||||
| @CONDITION_REGISTRY.register(CONF_OR, OR_CONDITION_SCHEMA) | ||||
| @register_condition('or', OrCondition, validate_condition_list) | ||||
| def or_condition_to_code(config, condition_id, template_arg, args): | ||||
|     for conditions in build_conditions(config, template_arg, args): | ||||
|         yield | ||||
|     rhs = OrCondition.new(template_arg, conditions) | ||||
|     type = OrCondition.template(template_arg) | ||||
|     yield Pvariable(condition_id, rhs, type=type) | ||||
|     conditions = yield build_condition_list(config, template_arg, args) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, conditions) | ||||
|  | ||||
|  | ||||
| RANGE_CONDITION_SCHEMA = vol.All(cv.Schema({ | ||||
|     vol.Optional(CONF_ABOVE): cv.templatable(cv.float_), | ||||
|     vol.Optional(CONF_BELOW): cv.templatable(cv.float_), | ||||
| }), cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW)) | ||||
| @register_condition('not', NotCondition, validate_potentially_and_condition) | ||||
| def not_condition_to_code(config, condition_id, template_arg, args): | ||||
|     condition = yield build_condition(config, template_arg, args) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, condition) | ||||
|  | ||||
|  | ||||
| @CONDITION_REGISTRY.register(CONF_RANGE, RANGE_CONDITION_SCHEMA) | ||||
| def range_condition_to_code(config, condition_id, template_arg, args): | ||||
|     for conditions in build_conditions(config, template_arg, args): | ||||
|         yield | ||||
|     rhs = RangeCondition.new(template_arg, conditions) | ||||
|     type = RangeCondition.template(template_arg) | ||||
|     condition = Pvariable(condition_id, rhs, type=type) | ||||
|     if CONF_ABOVE in config: | ||||
|         for template_ in templatable(config[CONF_ABOVE], args, float_): | ||||
|             yield | ||||
|         condition.set_min(template_) | ||||
|     if CONF_BELOW in config: | ||||
|         for template_ in templatable(config[CONF_BELOW], args, float_): | ||||
|             yield | ||||
|         condition.set_max(template_) | ||||
|     yield condition | ||||
| @register_condition('lambda', LambdaCondition, cv.lambda_) | ||||
| def lambda_condition_to_code(config, condition_id, template_arg, args): | ||||
|     lambda_ = yield cg.process_lambda(config, args, return_type=bool) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, lambda_) | ||||
|  | ||||
|  | ||||
| DELAY_ACTION_SCHEMA = cv.templatable(cv.positive_time_period_milliseconds) | ||||
| @register_condition('for', ForCondition, cv.Schema({ | ||||
|     cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds), | ||||
|     cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
| }).extend(cv.COMPONENT_SCHEMA)) | ||||
| def for_condition_to_code(config, condition_id, template_arg, args): | ||||
|     condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), []) | ||||
|     var = cg.new_Pvariable(condition_id, template_arg, condition) | ||||
|     yield cg.register_component(var, config) | ||||
|     templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32) | ||||
|     cg.add(var.set_time(templ)) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @ACTION_REGISTRY.register(CONF_DELAY, DELAY_ACTION_SCHEMA) | ||||
| @register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds)) | ||||
| def delay_action_to_code(config, action_id, template_arg, args): | ||||
|     rhs = App.register_component(DelayAction.new(template_arg)) | ||||
|     type = DelayAction.template(template_arg) | ||||
|     action = Pvariable(action_id, rhs, type=type) | ||||
|     for template_ in templatable(config, args, uint32): | ||||
|         yield | ||||
|     add(action.set_delay(template_)) | ||||
|     yield action | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_component(var, {}) | ||||
|     template_ = yield cg.templatable(config, args, cg.uint32) | ||||
|     cg.add(var.set_delay(template_)) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| IF_ACTION_SCHEMA = vol.All({ | ||||
|     vol.Required(CONF_CONDITION): validate_recursive_condition, | ||||
|     vol.Optional(CONF_THEN): validate_recursive_action, | ||||
|     vol.Optional(CONF_ELSE): validate_recursive_action, | ||||
| }, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE)) | ||||
|  | ||||
|  | ||||
| @ACTION_REGISTRY.register(CONF_IF, IF_ACTION_SCHEMA) | ||||
| @register_action('if', IfAction, cv.All({ | ||||
|     cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|     cv.Optional(CONF_THEN): validate_action_list, | ||||
|     cv.Optional(CONF_ELSE): validate_action_list, | ||||
| }, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE))) | ||||
| def if_action_to_code(config, action_id, template_arg, args): | ||||
|     for conditions in build_conditions(config[CONF_CONDITION], template_arg, args): | ||||
|         yield None | ||||
|     rhs = IfAction.new(template_arg, conditions) | ||||
|     type = IfAction.template(template_arg) | ||||
|     action = Pvariable(action_id, rhs, type=type) | ||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||
|     if CONF_THEN in config: | ||||
|         for actions in build_actions(config[CONF_THEN], template_arg, args): | ||||
|             yield None | ||||
|         add(action.add_then(actions)) | ||||
|         actions = yield build_action_list(config[CONF_THEN], template_arg, args) | ||||
|         cg.add(var.add_then(actions)) | ||||
|     if CONF_ELSE in config: | ||||
|         for actions in build_actions(config[CONF_ELSE], template_arg, args): | ||||
|             yield None | ||||
|         add(action.add_else(actions)) | ||||
|     yield action | ||||
|         actions = yield build_action_list(config[CONF_ELSE], template_arg, args) | ||||
|         cg.add(var.add_else(actions)) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| WHILE_ACTION_SCHEMA = cv.Schema({ | ||||
|     vol.Required(CONF_CONDITION): validate_recursive_condition, | ||||
|     vol.Required(CONF_THEN): validate_recursive_action, | ||||
| }) | ||||
|  | ||||
|  | ||||
| @ACTION_REGISTRY.register(CONF_WHILE, WHILE_ACTION_SCHEMA) | ||||
| @register_action('while', WhileAction, cv.Schema({ | ||||
|     cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|     cv.Required(CONF_THEN): validate_action_list, | ||||
| })) | ||||
| def while_action_to_code(config, action_id, template_arg, args): | ||||
|     for conditions in build_conditions(config[CONF_CONDITION], template_arg, args): | ||||
|         yield None | ||||
|     rhs = WhileAction.new(template_arg, conditions) | ||||
|     type = WhileAction.template(template_arg) | ||||
|     action = Pvariable(action_id, rhs, type=type) | ||||
|     for actions in build_actions(config[CONF_THEN], template_arg, args): | ||||
|         yield None | ||||
|     add(action.add_then(actions)) | ||||
|     yield action | ||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||
|     actions = yield build_action_list(config[CONF_THEN], template_arg, args) | ||||
|     cg.add(var.add_then(actions)) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| def validate_wait_until(value): | ||||
|     schema = cv.Schema({ | ||||
|         vol.Required(CONF_CONDITION): validate_recursive_condition | ||||
|         cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|     }) | ||||
|     if isinstance(value, dict) and CONF_CONDITION in value: | ||||
|         return schema(value) | ||||
|     return validate_wait_until({CONF_CONDITION: value}) | ||||
|  | ||||
|  | ||||
| WAIT_UNTIL_ACTION_SCHEMA = validate_wait_until | ||||
|  | ||||
|  | ||||
| @ACTION_REGISTRY.register(CONF_WAIT_UNTIL, WAIT_UNTIL_ACTION_SCHEMA) | ||||
| @register_action('wait_until', WaitUntilAction, validate_wait_until) | ||||
| def wait_until_action_to_code(config, action_id, template_arg, args): | ||||
|     for conditions in build_conditions(config[CONF_CONDITION], template_arg, args): | ||||
|         yield None | ||||
|     rhs = WaitUntilAction.new(template_arg, conditions) | ||||
|     type = WaitUntilAction.template(template_arg) | ||||
|     action = Pvariable(action_id, rhs, type=type) | ||||
|     add(App.register_component(action)) | ||||
|     yield action | ||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||
|     yield cg.register_component(var, {}) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| LAMBDA_ACTION_SCHEMA = cv.lambda_ | ||||
|  | ||||
|  | ||||
| @ACTION_REGISTRY.register(CONF_LAMBDA, LAMBDA_ACTION_SCHEMA) | ||||
| @register_action('lambda', LambdaAction, cv.lambda_) | ||||
| def lambda_action_to_code(config, action_id, template_arg, args): | ||||
|     for lambda_ in process_lambda(config, args, return_type=void): | ||||
|         yield None | ||||
|     rhs = LambdaAction.new(template_arg, lambda_) | ||||
|     type = LambdaAction.template(template_arg) | ||||
|     yield Pvariable(action_id, rhs, type=type) | ||||
|     lambda_ = yield cg.process_lambda(config, args, return_type=cg.void) | ||||
|     yield cg.new_Pvariable(action_id, template_arg, lambda_) | ||||
|  | ||||
|  | ||||
| LAMBDA_CONDITION_SCHEMA = cv.lambda_ | ||||
|  | ||||
|  | ||||
| @CONDITION_REGISTRY.register(CONF_LAMBDA, LAMBDA_CONDITION_SCHEMA) | ||||
| def lambda_condition_to_code(config, condition_id, template_arg, args): | ||||
|     for lambda_ in process_lambda(config, args, return_type=bool_): | ||||
|         yield | ||||
|     rhs = LambdaCondition.new(template_arg, lambda_) | ||||
|     type = LambdaCondition.template(template_arg) | ||||
|     yield Pvariable(condition_id, rhs, type=type) | ||||
|  | ||||
|  | ||||
| CONF_COMPONENT_UPDATE = 'component.update' | ||||
| COMPONENT_UPDATE_ACTION_SCHEMA = maybe_simple_id({ | ||||
|     vol.Required(CONF_ID): cv.use_variable_id(PollingComponent), | ||||
| }) | ||||
|  | ||||
|  | ||||
| @ACTION_REGISTRY.register(CONF_COMPONENT_UPDATE, COMPONENT_UPDATE_ACTION_SCHEMA) | ||||
| @register_action('component.update', UpdateComponentAction, maybe_simple_id({ | ||||
|     cv.Required(CONF_ID): cv.use_id(cg.PollingComponent), | ||||
| })) | ||||
| def component_update_action_to_code(config, action_id, template_arg, args): | ||||
|     for var in get_variable(config[CONF_ID]): | ||||
|         yield None | ||||
|     rhs = UpdateComponentAction.new(template_arg, var) | ||||
|     type = UpdateComponentAction.template(template_arg) | ||||
|     yield Pvariable(action_id, rhs, type=type) | ||||
|     comp = yield cg.get_variable(config[CONF_ID]) | ||||
|     yield cg.new_Pvariable(action_id, template_arg, comp) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| def build_action(full_config, template_arg, args): | ||||
|     action_id = full_config[CONF_ACTION_ID] | ||||
|     key, config = next((k, v) for k, v in full_config.items() if k in ACTION_REGISTRY) | ||||
|  | ||||
|     builder = ACTION_REGISTRY[key][1] | ||||
|     for result in builder(config, action_id, template_arg, args): | ||||
|         yield None | ||||
|     yield result | ||||
|     registry_entry, config = cg.extract_registry_entry_config(ACTION_REGISTRY, full_config) | ||||
|     action_id = full_config[CONF_TYPE_ID] | ||||
|     builder = registry_entry.coroutine_fun | ||||
|     yield builder(config, action_id, template_arg, args) | ||||
|  | ||||
|  | ||||
| def build_actions(config, templ, arg_type): | ||||
| @coroutine | ||||
| def build_action_list(config, templ, arg_type): | ||||
|     actions = [] | ||||
|     for conf in config: | ||||
|         for action in build_action(conf, templ, arg_type): | ||||
|             yield None | ||||
|         action = yield build_action(conf, templ, arg_type) | ||||
|         actions.append(action) | ||||
|     yield actions | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| def build_condition(full_config, template_arg, args): | ||||
|     action_id = full_config[CONF_CONDITION_ID] | ||||
|     key, config = next((k, v) for k, v in full_config.items() if k in CONDITION_REGISTRY) | ||||
|  | ||||
|     builder = CONDITION_REGISTRY[key][1] | ||||
|     for result in builder(config, action_id, template_arg, args): | ||||
|         yield None | ||||
|     yield result | ||||
|     registry_entry, config = cg.extract_registry_entry_config(CONDITION_REGISTRY, full_config) | ||||
|     action_id = full_config[CONF_TYPE_ID] | ||||
|     builder = registry_entry.coroutine_fun | ||||
|     yield builder(config, action_id, template_arg, args) | ||||
|  | ||||
|  | ||||
| def build_conditions(config, templ, args): | ||||
| @coroutine | ||||
| def build_condition_list(config, templ, args): | ||||
|     conditions = [] | ||||
|     for conf in config: | ||||
|         for condition in build_condition(conf, templ, args): | ||||
|             yield None | ||||
|         condition = yield build_condition(conf, templ, args) | ||||
|         conditions.append(condition) | ||||
|     yield conditions | ||||
|  | ||||
|  | ||||
| def build_automation_(trigger, args, config): | ||||
| @coroutine | ||||
| def build_automation(trigger, args, config): | ||||
|     arg_types = [arg[0] for arg in args] | ||||
|     templ = TemplateArguments(*arg_types) | ||||
|     rhs = App.make_automation(templ, trigger) | ||||
|     type = Automation.template(templ) | ||||
|     obj = Pvariable(config[CONF_AUTOMATION_ID], rhs, type=type) | ||||
|     for actions in build_actions(config[CONF_THEN], templ, args): | ||||
|         yield None | ||||
|     add(obj.add_actions(actions)) | ||||
|     templ = cg.TemplateArguments(*arg_types) | ||||
|     obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger) | ||||
|     actions = yield build_action_list(config[CONF_THEN], templ, args) | ||||
|     cg.add(obj.add_actions(actions)) | ||||
|     yield obj | ||||
|  | ||||
|  | ||||
| def build_automations(trigger, args, config): | ||||
|     CORE.add_job(build_automation_, trigger, args, config) | ||||
|   | ||||
							
								
								
									
										26
									
								
								esphome/codegen.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/codegen.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # Base file for all codegen-related imports | ||||
| # All integrations should have a line in the import section like this | ||||
| # | ||||
| # >>> import esphome.codegen as cg | ||||
| # | ||||
| # Integrations should specifically *NOT* import directly from the | ||||
| # other helper modules (cpp_generator etc) directly if they don't | ||||
| # want to break suddenly due to a rename (this file will get backports for features). | ||||
|  | ||||
| # pylint: disable=unused-import | ||||
| from esphome.cpp_generator import (  # noqa | ||||
|     Expression, RawExpression, RawStatement, TemplateArguments, | ||||
|     StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment, | ||||
|     progmem_array, statement, variable, Pvariable, new_Pvariable, | ||||
|     add, add_global, add_library, add_build_flag, add_define, | ||||
|     get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj, | ||||
|     MockObjClass) | ||||
| from esphome.cpp_helpers import (  # noqa | ||||
|     gpio_pin_expression, register_component, build_registry_entry, | ||||
|     build_registry_list, extract_registry_entry_config, register_parented) | ||||
| from esphome.cpp_types import (  # noqa | ||||
|     global_ns, void, nullptr, float_, double, bool_, std_ns, std_string, | ||||
|     std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN, | ||||
|     esphome_ns, App, Nameable, Component, ComponentPtr, | ||||
|     PollingComponent, Application, optional, arduino_json_ns, JsonObject, | ||||
|     JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin) | ||||
							
								
								
									
										0
									
								
								esphome/components/a4988/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/a4988/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										49
									
								
								esphome/components/a4988/a4988.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								esphome/components/a4988/a4988.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| #include "a4988.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace a4988 { | ||||
|  | ||||
| static const char *TAG = "a4988.stepper"; | ||||
|  | ||||
| void A4988::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up A4988..."); | ||||
|   if (this->sleep_pin_ != nullptr) { | ||||
|     this->sleep_pin_->setup(); | ||||
|     this->sleep_pin_->digital_write(false); | ||||
|   } | ||||
|   this->step_pin_->setup(); | ||||
|   this->step_pin_->digital_write(false); | ||||
|   this->dir_pin_->setup(); | ||||
|   this->dir_pin_->digital_write(false); | ||||
| } | ||||
| void A4988::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "A4988:"); | ||||
|   LOG_PIN("  Step Pin: ", this->step_pin_); | ||||
|   LOG_PIN("  Dir Pin: ", this->dir_pin_); | ||||
|   LOG_PIN("  Sleep Pin: ", this->sleep_pin_); | ||||
|   LOG_STEPPER(this); | ||||
| } | ||||
| void A4988::loop() { | ||||
|   bool at_target = this->has_reached_target(); | ||||
|   if (this->sleep_pin_ != nullptr) { | ||||
|     this->sleep_pin_->digital_write(!at_target); | ||||
|   } | ||||
|   if (at_target) { | ||||
|     this->high_freq_.stop(); | ||||
|   } else { | ||||
|     this->high_freq_.start(); | ||||
|   } | ||||
|  | ||||
|   int32_t dir = this->should_step_(); | ||||
|   if (dir == 0) | ||||
|     return; | ||||
|  | ||||
|   this->dir_pin_->digital_write(dir == 1); | ||||
|   this->step_pin_->digital_write(true); | ||||
|   delayMicroseconds(5); | ||||
|   this->step_pin_->digital_write(false); | ||||
| } | ||||
|  | ||||
| }  // namespace a4988 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										28
									
								
								esphome/components/a4988/a4988.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/a4988/a4988.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/esphal.h" | ||||
| #include "esphome/components/stepper/stepper.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace a4988 { | ||||
|  | ||||
| class A4988 : public stepper::Stepper, public Component { | ||||
|  public: | ||||
|   void set_step_pin(GPIOPin *step_pin) { step_pin_ = step_pin; } | ||||
|   void set_dir_pin(GPIOPin *dir_pin) { dir_pin_ = dir_pin; } | ||||
|   void set_sleep_pin(GPIOPin *sleep_pin) { this->sleep_pin_ = sleep_pin; } | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void loop() override; | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|  protected: | ||||
|   GPIOPin *step_pin_; | ||||
|   GPIOPin *dir_pin_; | ||||
|   GPIOPin *sleep_pin_{nullptr}; | ||||
|   HighFrequencyLoopRequester high_freq_; | ||||
| }; | ||||
|  | ||||
| }  // namespace a4988 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										31
									
								
								esphome/components/a4988/stepper.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/a4988/stepper.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| from esphome import pins | ||||
| from esphome.components import stepper | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN | ||||
|  | ||||
|  | ||||
| a4988_ns = cg.esphome_ns.namespace('a4988') | ||||
| A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({ | ||||
|     cv.Required(CONF_ID): cv.declare_id(A4988), | ||||
|     cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema, | ||||
|     cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema, | ||||
|     cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema, | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield stepper.register_stepper(var, config) | ||||
|  | ||||
|     step_pin = yield cg.gpio_pin_expression(config[CONF_STEP_PIN]) | ||||
|     cg.add(var.set_step_pin(step_pin)) | ||||
|     dir_pin = yield cg.gpio_pin_expression(config[CONF_DIR_PIN]) | ||||
|     cg.add(var.set_dir_pin(dir_pin)) | ||||
|  | ||||
|     if CONF_SLEEP_PIN in config: | ||||
|         sleep_pin = yield cg.gpio_pin_expression(config[CONF_SLEEP_PIN]) | ||||
|         cg.add(var.set_sleep_pin(sleep_pin)) | ||||
							
								
								
									
										0
									
								
								esphome/components/adc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/adc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										92
									
								
								esphome/components/adc/adc_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								esphome/components/adc/adc_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| #include "adc_sensor.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
| ADC_MODE(ADC_VCC) | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace adc { | ||||
|  | ||||
| static const char *TAG = "adc"; | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; } | ||||
| #endif | ||||
|  | ||||
| void ADCSensor::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||
|   GPIOPin(this->pin_, INPUT).setup(); | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   analogSetPinAttenuation(this->pin_, this->attenuation_); | ||||
| #endif | ||||
| } | ||||
| void ADCSensor::dump_config() { | ||||
|   LOG_SENSOR("", "ADC Sensor", this); | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
|   ESP_LOGCONFIG(TAG, "  Pin: VCC"); | ||||
| #else | ||||
|   ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_); | ||||
| #endif | ||||
| #endif | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_); | ||||
|   switch (this->attenuation_) { | ||||
|     case ADC_0db: | ||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); | ||||
|       break; | ||||
|     case ADC_2_5db: | ||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); | ||||
|       break; | ||||
|     case ADC_6db: | ||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); | ||||
|       break; | ||||
|     case ADC_11db: | ||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); | ||||
|       break; | ||||
|   } | ||||
| #endif | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
| float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } | ||||
| void ADCSensor::update() { | ||||
|   float value_v = this->sample(); | ||||
|   ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); | ||||
|   this->publish_state(value_v); | ||||
| } | ||||
| float ADCSensor::sample() { | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   float value_v = analogRead(this->pin_) / 4095.0f; | ||||
|   switch (this->attenuation_) { | ||||
|     case ADC_0db: | ||||
|       value_v *= 1.1; | ||||
|       break; | ||||
|     case ADC_2_5db: | ||||
|       value_v *= 1.5; | ||||
|       break; | ||||
|     case ADC_6db: | ||||
|       value_v *= 2.2; | ||||
|       break; | ||||
|     case ADC_11db: | ||||
|       value_v *= 3.9; | ||||
|       break; | ||||
|   } | ||||
|   return value_v; | ||||
| #endif | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
|   return ESP.getVcc() / 1024.0f; | ||||
| #else | ||||
|   return analogRead(this->pin_) / 1024.0f; | ||||
| #endif | ||||
| #endif | ||||
| } | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } | ||||
| #endif | ||||
|  | ||||
| }  // namespace adc | ||||
| }  // namespace esphome | ||||
							
								
								
									
										42
									
								
								esphome/components/adc/adc_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								esphome/components/adc/adc_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/esphal.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace adc { | ||||
|  | ||||
| class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { | ||||
|  public: | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   /// Set the attenuation for this pin. Only available on the ESP32. | ||||
|   void set_attenuation(adc_attenuation_t attenuation); | ||||
| #endif | ||||
|  | ||||
|   /// Update adc values. | ||||
|   void update() override; | ||||
|   /// Setup ADc | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   /// `HARDWARE_LATE` setup priority. | ||||
|   float get_setup_priority() const override; | ||||
|   void set_pin(uint8_t pin) { this->pin_ = pin; } | ||||
|   float sample() override; | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
|   std::string unique_id() override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   uint8_t pin_; | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   adc_attenuation_t attenuation_{ADC_0db}; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace adc | ||||
| }  // namespace esphome | ||||
							
								
								
									
										48
									
								
								esphome/components/adc/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/adc/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.components import sensor, voltage_sampler | ||||
| from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT | ||||
|  | ||||
|  | ||||
| AUTO_LOAD = ['voltage_sampler'] | ||||
|  | ||||
| ATTENUATION_MODES = { | ||||
|     '0db': cg.global_ns.ADC_0db, | ||||
|     '2.5db': cg.global_ns.ADC_2_5db, | ||||
|     '6db': cg.global_ns.ADC_6db, | ||||
|     '11db': cg.global_ns.ADC_11db, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_adc_pin(value): | ||||
|     vcc = str(value).upper() | ||||
|     if vcc == 'VCC': | ||||
|         return cv.only_on_esp8266(vcc) | ||||
|     return pins.analog_pin(value) | ||||
|  | ||||
|  | ||||
| adc_ns = cg.esphome_ns.namespace('adc') | ||||
| ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent, | ||||
|                           voltage_sampler.VoltageSampler) | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({ | ||||
|     cv.GenerateID(): cv.declare_id(ADCSensor), | ||||
|     cv.Required(CONF_PIN): validate_adc_pin, | ||||
|     cv.SplitDefault(CONF_ATTENUATION, esp32='0db'): | ||||
|         cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)), | ||||
| }).extend(cv.polling_component_schema('60s')) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield sensor.register_sensor(var, config) | ||||
|  | ||||
|     if config[CONF_PIN] == 'VCC': | ||||
|         cg.add_define('USE_ADC_SENSOR_VCC') | ||||
|     else: | ||||
|         cg.add(var.set_pin(config[CONF_PIN])) | ||||
|  | ||||
|     if CONF_ATTENUATION in config: | ||||
|         cg.add(var.set_attenuation(config[CONF_ATTENUATION])) | ||||
| @@ -1,27 +0,0 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphome.components import i2c, sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ADDRESS, CONF_ID | ||||
| from esphome.cpp_generator import Pvariable | ||||
| from esphome.cpp_helpers import setup_component | ||||
| from esphome.cpp_types import App, Component | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| ADS1115Component = sensor.sensor_ns.class_('ADS1115Component', Component, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_variable_id(ADS1115Component), | ||||
|     vol.Required(CONF_ADDRESS): cv.i2c_address, | ||||
| }).extend(cv.COMPONENT_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_ads1115_component(config[CONF_ADDRESS]) | ||||
|     var = Pvariable(config[CONF_ID], rhs) | ||||
|     setup_component(var, config) | ||||
|  | ||||
|  | ||||
| BUILD_FLAGS = '-DUSE_ADS1115_SENSOR' | ||||
							
								
								
									
										21
									
								
								esphome/components/ads1115/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								esphome/components/ads1115/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| AUTO_LOAD = ['sensor', 'voltage_sampler'] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| ads1115_ns = cg.esphome_ns.namespace('ads1115') | ||||
| ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(ADS1115Component), | ||||
| }).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield i2c.register_i2c_device(var, config) | ||||
							
								
								
									
										164
									
								
								esphome/components/ads1115/ads1115.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								esphome/components/ads1115/ads1115.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,164 @@ | ||||
| #include "ads1115.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ads1115 { | ||||
|  | ||||
| static const char *TAG = "ads1115"; | ||||
| static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00; | ||||
| static const uint8_t ADS1115_REGISTER_CONFIG = 0x01; | ||||
|  | ||||
| static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111; | ||||
|  | ||||
| void ADS1115Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ADS1115..."); | ||||
|   uint16_t value; | ||||
|   if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   uint16_t config = 0; | ||||
|   // Clear single-shot bit | ||||
|   //        0b0xxxxxxxxxxxxxxx | ||||
|   config |= 0b0000000000000000; | ||||
|   // Setup multiplexer | ||||
|   //        0bx000xxxxxxxxxxxx | ||||
|   config |= ADS1115_MULTIPLEXER_P0_N1 << 12; | ||||
|  | ||||
|   // Setup Gain | ||||
|   //        0bxxxx000xxxxxxxxx | ||||
|   config |= ADS1115_GAIN_6P144 << 9; | ||||
|  | ||||
|   // Set singleshot mode | ||||
|   //        0bxxxxxxx1xxxxxxxx | ||||
|   config |= 0b0000000100000000; | ||||
|  | ||||
|   // Set data rate - 860 samples per second (we're in singleshot mode) | ||||
|   //        0bxxxxxxxx100xxxxx | ||||
|   config |= ADS1115_DATA_RATE_860_SPS << 5; | ||||
|  | ||||
|   // Set comparator mode - hysteresis | ||||
|   //        0bxxxxxxxxxxx0xxxx | ||||
|   config |= 0b0000000000000000; | ||||
|  | ||||
|   // Set comparator polarity - active low | ||||
|   //        0bxxxxxxxxxxxx0xxx | ||||
|   config |= 0b0000000000000000; | ||||
|  | ||||
|   // Set comparator latch enabled - false | ||||
|   //        0bxxxxxxxxxxxxx0xx | ||||
|   config |= 0b0000000000000000; | ||||
|  | ||||
|   // Set comparator que mode - disabled | ||||
|   //        0bxxxxxxxxxxxxxx11 | ||||
|   config |= 0b0000000000000011; | ||||
|  | ||||
|   if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   for (auto *sensor : this->sensors_) { | ||||
|     this->set_interval(sensor->get_name(), sensor->update_interval(), | ||||
|                        [this, sensor] { this->request_measurement(sensor); }); | ||||
|   } | ||||
| } | ||||
| void ADS1115Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ADS1115..."); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication with ADS1115 failed!"); | ||||
|   } | ||||
|  | ||||
|   for (auto *sensor : this->sensors_) { | ||||
|     LOG_SENSOR("  ", "Sensor", sensor); | ||||
|     ESP_LOGCONFIG(TAG, "    Multiplexer: %u", sensor->get_multiplexer()); | ||||
|     ESP_LOGCONFIG(TAG, "    Gain: %u", sensor->get_gain()); | ||||
|   } | ||||
| } | ||||
| float ADS1115Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
| float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { | ||||
|   uint16_t config; | ||||
|   if (!this->read_byte_16(ADS1115_REGISTER_CONFIG, &config)) { | ||||
|     this->status_set_warning(); | ||||
|     return NAN; | ||||
|   } | ||||
|   // Multiplexer | ||||
|   //        0bxBBBxxxxxxxxxxxx | ||||
|   config &= 0b1000111111111111; | ||||
|   config |= (sensor->get_multiplexer() & 0b111) << 12; | ||||
|  | ||||
|   // Gain | ||||
|   //        0bxxxxBBBxxxxxxxxx | ||||
|   config &= 0b1111000111111111; | ||||
|   config |= (sensor->get_gain() & 0b111) << 9; | ||||
|   // Start conversion | ||||
|   config |= 0b1000000000000000; | ||||
|  | ||||
|   if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) { | ||||
|     this->status_set_warning(); | ||||
|     return NAN; | ||||
|   } | ||||
|  | ||||
|   // about 1.6 ms with 860 samples per second | ||||
|   delay(2); | ||||
|  | ||||
|   uint32_t start = millis(); | ||||
|   while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { | ||||
|     if (millis() - start > 100) { | ||||
|       ESP_LOGW(TAG, "Reading ADS1115 timed out"); | ||||
|       this->status_set_warning(); | ||||
|       return NAN; | ||||
|     } | ||||
|     yield(); | ||||
|   } | ||||
|  | ||||
|   uint16_t raw_conversion; | ||||
|   if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) { | ||||
|     this->status_set_warning(); | ||||
|     return NAN; | ||||
|   } | ||||
|   auto signed_conversion = static_cast<int16_t>(raw_conversion); | ||||
|  | ||||
|   float millivolts; | ||||
|   switch (sensor->get_gain()) { | ||||
|     case ADS1115_GAIN_6P144: | ||||
|       millivolts = signed_conversion * 0.187500f; | ||||
|       break; | ||||
|     case ADS1115_GAIN_4P096: | ||||
|       millivolts = signed_conversion * 0.125000f; | ||||
|       break; | ||||
|     case ADS1115_GAIN_2P048: | ||||
|       millivolts = signed_conversion * 0.062500f; | ||||
|       break; | ||||
|     case ADS1115_GAIN_1P024: | ||||
|       millivolts = signed_conversion * 0.031250f; | ||||
|       break; | ||||
|     case ADS1115_GAIN_0P512: | ||||
|       millivolts = signed_conversion * 0.015625f; | ||||
|       break; | ||||
|     case ADS1115_GAIN_0P256: | ||||
|       millivolts = signed_conversion * 0.007813f; | ||||
|       break; | ||||
|     default: | ||||
|       millivolts = NAN; | ||||
|   } | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
|   return millivolts / 1e4f; | ||||
| } | ||||
|  | ||||
| uint8_t ADS1115Sensor::get_multiplexer() const { return this->multiplexer_; } | ||||
| void ADS1115Sensor::set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; } | ||||
| uint8_t ADS1115Sensor::get_gain() const { return this->gain_; } | ||||
| void ADS1115Sensor::set_gain(ADS1115Gain gain) { this->gain_ = gain; } | ||||
| float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); } | ||||
| void ADS1115Sensor::update() { | ||||
|   float v = this->parent_->request_measurement(this); | ||||
|   if (!isnan(v)) { | ||||
|     ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v); | ||||
|     this->publish_state(v); | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace ads1115 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										68
									
								
								esphome/components/ads1115/ads1115.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								esphome/components/ads1115/ads1115.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ads1115 { | ||||
|  | ||||
| enum ADS1115Multiplexer { | ||||
|   ADS1115_MULTIPLEXER_P0_N1 = 0b000, | ||||
|   ADS1115_MULTIPLEXER_P0_N3 = 0b001, | ||||
|   ADS1115_MULTIPLEXER_P1_N3 = 0b010, | ||||
|   ADS1115_MULTIPLEXER_P2_N3 = 0b011, | ||||
|   ADS1115_MULTIPLEXER_P0_NG = 0b100, | ||||
|   ADS1115_MULTIPLEXER_P1_NG = 0b101, | ||||
|   ADS1115_MULTIPLEXER_P2_NG = 0b110, | ||||
|   ADS1115_MULTIPLEXER_P3_NG = 0b111, | ||||
| }; | ||||
|  | ||||
| enum ADS1115Gain { | ||||
|   ADS1115_GAIN_6P144 = 0b000, | ||||
|   ADS1115_GAIN_4P096 = 0b001, | ||||
|   ADS1115_GAIN_2P048 = 0b010, | ||||
|   ADS1115_GAIN_1P024 = 0b011, | ||||
|   ADS1115_GAIN_0P512 = 0b100, | ||||
|   ADS1115_GAIN_0P256 = 0b101, | ||||
| }; | ||||
|  | ||||
| class ADS1115Sensor; | ||||
|  | ||||
| class ADS1115Component : public Component, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void register_sensor(ADS1115Sensor *obj) { this->sensors_.push_back(obj); } | ||||
|   /// Set up the internal sensor array. | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   /// HARDWARE_LATE setup priority | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|   /// Helper method to request a measurement from a sensor. | ||||
|   float request_measurement(ADS1115Sensor *sensor); | ||||
|  | ||||
|  protected: | ||||
|   std::vector<ADS1115Sensor *> sensors_; | ||||
| }; | ||||
|  | ||||
| /// Internal holder class that is in instance of Sensor so that the hub can create individual sensors. | ||||
| class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { | ||||
|  public: | ||||
|   ADS1115Sensor(ADS1115Component *parent) : parent_(parent) {} | ||||
|   void update() override; | ||||
|   void set_multiplexer(ADS1115Multiplexer multiplexer); | ||||
|   void set_gain(ADS1115Gain gain); | ||||
|  | ||||
|   float sample() override; | ||||
|   uint8_t get_multiplexer() const; | ||||
|   uint8_t get_gain() const; | ||||
|  | ||||
|  protected: | ||||
|   ADS1115Component *parent_; | ||||
|   ADS1115Multiplexer multiplexer_; | ||||
|   ADS1115Gain gain_; | ||||
| }; | ||||
|  | ||||
| }  // namespace ads1115 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										63
									
								
								esphome/components/ads1115/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/ads1115/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, voltage_sampler | ||||
| from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID | ||||
| from esphome.py_compat import string_types | ||||
| from . import ads1115_ns, ADS1115Component | ||||
|  | ||||
| DEPENDENCIES = ['ads1115'] | ||||
|  | ||||
| ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer') | ||||
| MUX = { | ||||
|     'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1, | ||||
|     'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3, | ||||
|     'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3, | ||||
|     'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3, | ||||
|     'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG, | ||||
|     'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG, | ||||
|     'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG, | ||||
|     'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG, | ||||
| } | ||||
|  | ||||
| ADS1115Gain = ads1115_ns.enum('ADS1115Gain') | ||||
| GAIN = { | ||||
|     '6.144': ADS1115Gain.ADS1115_GAIN_6P144, | ||||
|     '4.096': ADS1115Gain.ADS1115_GAIN_4P096, | ||||
|     '2.048': ADS1115Gain.ADS1115_GAIN_2P048, | ||||
|     '1.024': ADS1115Gain.ADS1115_GAIN_1P024, | ||||
|     '0.512': ADS1115Gain.ADS1115_GAIN_0P512, | ||||
|     '0.256': ADS1115Gain.ADS1115_GAIN_0P256, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_gain(value): | ||||
|     if isinstance(value, float): | ||||
|         value = u'{:0.03f}'.format(value) | ||||
|     elif not isinstance(value, string_types): | ||||
|         raise cv.Invalid('invalid gain "{}"'.format(value)) | ||||
|  | ||||
|     return cv.enum(GAIN)(value) | ||||
|  | ||||
|  | ||||
| ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent, | ||||
|                                   voltage_sampler.VoltageSampler) | ||||
|  | ||||
| CONF_ADS1115_ID = 'ads1115_id' | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({ | ||||
|     cv.GenerateID(): cv.declare_id(ADS1115Sensor), | ||||
|     cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component), | ||||
|     cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'), | ||||
|     cv.Required(CONF_GAIN): validate_gain, | ||||
| }).extend(cv.polling_component_schema('60s')) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     paren = yield cg.get_variable(config[CONF_ADS1115_ID]) | ||||
|     var = cg.new_Pvariable(config[CONF_ID], paren) | ||||
|     yield sensor.register_sensor(var, config) | ||||
|     yield cg.register_component(var, config) | ||||
|  | ||||
|     cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER])) | ||||
|     cg.add(var.set_gain(config[CONF_GAIN])) | ||||
|  | ||||
|     cg.add(paren.register_sensor(var)) | ||||
							
								
								
									
										0
									
								
								esphome/components/am2320/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/am2320/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										107
									
								
								esphome/components/am2320/am2320.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								esphome/components/am2320/am2320.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| // Implementation based on: | ||||
| //  - ESPEasy: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P034_DHT12.ino | ||||
| //  - DHT12_sensor_library: https://github.com/xreef/DHT12_sensor_library/blob/master/DHT12.cpp | ||||
| //  - Arduino - AM2320: https://github.com/EngDial/AM2320/blob/master/src/AM2320.cpp | ||||
|  | ||||
| #include "am2320.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace am2320 { | ||||
|  | ||||
| static const char *TAG = "am2320"; | ||||
|  | ||||
| // ---=== Calc CRC16 ===--- | ||||
| uint16_t crc_16(uint8_t *ptr, uint8_t length) { | ||||
|   uint16_t crc = 0xFFFF; | ||||
|   uint8_t i; | ||||
|   //------------------------------ | ||||
|   while (length--) { | ||||
|     crc ^= *ptr++; | ||||
|     for (i = 0; i < 8; i++) | ||||
|       if ((crc & 0x01) != 0) { | ||||
|         crc >>= 1; | ||||
|         crc ^= 0xA001; | ||||
|       } else | ||||
|         crc >>= 1; | ||||
|   } | ||||
|   return crc; | ||||
| } | ||||
|  | ||||
| void AM2320Component::update() { | ||||
|   uint8_t data[8]; | ||||
|   data[0] = 0; | ||||
|   data[1] = 4; | ||||
|   if (!this->read_data_(data)) { | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0; | ||||
|   temperature = (data[4] & 0x80) ? -temperature : temperature; | ||||
|   float humidity = ((data[2] << 8) + data[3]) / 10.0; | ||||
|  | ||||
|   ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); | ||||
|   if (this->temperature_sensor_ != nullptr) | ||||
|     this->temperature_sensor_->publish_state(temperature); | ||||
|   if (this->humidity_sensor_ != nullptr) | ||||
|     this->humidity_sensor_->publish_state(humidity); | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
| void AM2320Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up AM2320..."); | ||||
|   uint8_t data[8]; | ||||
|   data[0] = 0; | ||||
|   data[1] = 4; | ||||
|   if (!this->read_data_(data)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| } | ||||
| void AM2320Component::dump_config() { | ||||
|   ESP_LOGD(TAG, "AM2320:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication with AM2320 failed!"); | ||||
|   } | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_sensor_); | ||||
| } | ||||
| float AM2320Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) { | ||||
|   if (!this->write_bytes(a_register, data, 2)) { | ||||
|     ESP_LOGW(TAG, "Writing bytes for AM2320 failed!"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   if (conversion > 0) | ||||
|     delay(conversion); | ||||
|   return this->parent_->raw_receive(this->address_, data, len); | ||||
| } | ||||
|  | ||||
| bool AM2320Component::read_data_(uint8_t *data) { | ||||
|   // Wake up | ||||
|   this->write_bytes(0, data, 0); | ||||
|  | ||||
|   // Write instruction 3, 2 bytes, get 8 bytes back (2 preamble, 2 bytes temperature, 2 bytes humidity, 2 bytes CRC) | ||||
|   if (!this->read_bytes_(3, data, 8, 2)) { | ||||
|     ESP_LOGW(TAG, "Updating AM2320 failed!"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   uint16_t checksum; | ||||
|  | ||||
|   checksum = data[7] << 8; | ||||
|   checksum += data[6]; | ||||
|  | ||||
|   if (crc_16(data, 6) != checksum) { | ||||
|     ESP_LOGW(TAG, "AM2320 Checksum invalid!"); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| }  // namespace am2320 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										29
									
								
								esphome/components/am2320/am2320.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphome/components/am2320/am2320.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace am2320 { | ||||
|  | ||||
| class AM2320Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void update() override; | ||||
|  | ||||
|   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } | ||||
|   void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } | ||||
|  | ||||
|  protected: | ||||
|   bool read_data_(uint8_t *data); | ||||
|   bool read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); | ||||
|  | ||||
|   sensor::Sensor *temperature_sensor_; | ||||
|   sensor::Sensor *humidity_sensor_; | ||||
| }; | ||||
|  | ||||
| }  // namespace am2320 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										30
									
								
								esphome/components/am2320/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/am2320/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| am2320_ns = cg.esphome_ns.namespace('am2320') | ||||
| AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(AM2320Component), | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature_sensor(sens)) | ||||
|  | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity_sensor(sens)) | ||||
| @@ -1,33 +0,0 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphome.components import i2c, sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ADDRESS, CONF_ID, CONF_UPDATE_INTERVAL | ||||
| from esphome.cpp_generator import Pvariable, add | ||||
| from esphome.cpp_helpers import setup_component | ||||
| from esphome.cpp_types import App, PollingComponent | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_APDS9960_ID = 'apds9960_id' | ||||
| APDS9960 = sensor.sensor_ns.class_('APDS9960', PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_variable_id(APDS9960), | ||||
|     vol.Optional(CONF_ADDRESS): cv.i2c_address, | ||||
|     vol.Optional(CONF_UPDATE_INTERVAL): cv.update_interval, | ||||
| }).extend(cv.COMPONENT_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     rhs = App.make_apds9960(config.get(CONF_UPDATE_INTERVAL)) | ||||
|     var = Pvariable(config[CONF_ID], rhs) | ||||
|  | ||||
|     if CONF_ADDRESS in config: | ||||
|         add(var.set_address(config[CONF_ADDRESS])) | ||||
|  | ||||
|     setup_component(var, config) | ||||
|  | ||||
|  | ||||
| BUILD_FLAGS = '-DUSE_APDS9960' | ||||
							
								
								
									
										23
									
								
								esphome/components/apds9960/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/apds9960/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| AUTO_LOAD = ['sensor', 'binary_sensor'] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_APDS9960_ID = 'apds9960_id' | ||||
|  | ||||
| apds9960_nds = cg.esphome_ns.namespace('apds9960') | ||||
| APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(APDS9960), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield i2c.register_i2c_device(var, config) | ||||
							
								
								
									
										374
									
								
								esphome/components/apds9960/apds9960.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										374
									
								
								esphome/components/apds9960/apds9960.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,374 @@ | ||||
| #include "apds9960.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace apds9960 { | ||||
|  | ||||
| static const char *TAG = "apds9960"; | ||||
|  | ||||
| #define APDS9960_ERROR_CHECK(func) \ | ||||
|   if (!func) { \ | ||||
|     this->mark_failed(); \ | ||||
|     return; \ | ||||
|   } | ||||
| #define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value)); | ||||
|  | ||||
| void APDS9960::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up APDS9960..."); | ||||
|   uint8_t id; | ||||
|   if (!this->read_byte(0x92, &id)) {  // ID register | ||||
|     this->error_code_ = COMMUNICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (id != 0xAB && id != 0x9C) {  // APDS9960 all should have one of these IDs | ||||
|     this->error_code_ = WRONG_ID; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // ATime (ADC integration time, 2.78ms increments, 0x81) -> 0xDB (103ms) | ||||
|   APDS9960_WRITE_BYTE(0x81, 0xDB); | ||||
|   // WTime (Wait time, 0x83) -> 0xF6 (27ms) | ||||
|   APDS9960_WRITE_BYTE(0x83, 0xF6); | ||||
|   // PPulse (0x8E) -> 0x87 (16us, 8 pulses) | ||||
|   APDS9960_WRITE_BYTE(0x8E, 0x87); | ||||
|   // POffset UR (0x9D) -> 0 (no offset) | ||||
|   APDS9960_WRITE_BYTE(0x9D, 0x00); | ||||
|   // POffset DL (0x9E) -> 0 (no offset) | ||||
|   APDS9960_WRITE_BYTE(0x9E, 0x00); | ||||
|   // Config 1 (0x8D) -> 0x60 (no wtime factor) | ||||
|   APDS9960_WRITE_BYTE(0x8D, 0x60); | ||||
|  | ||||
|   // Control (0x8F) -> | ||||
|   uint8_t val = 0; | ||||
|   APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val)); | ||||
|   val &= 0b00111111; | ||||
|   uint8_t led_drive = 0;  // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA | ||||
|   val |= (led_drive & 0b11) << 6; | ||||
|  | ||||
|   val &= 0b11110011; | ||||
|   uint8_t proximity_gain = 2;  // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 4 -> 8X | ||||
|   val |= (proximity_gain & 0b11) << 2; | ||||
|  | ||||
|   val &= 0b11111100; | ||||
|   uint8_t ambient_gain = 1;  // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x | ||||
|   val |= (ambient_gain & 0b11) << 0; | ||||
|   APDS9960_WRITE_BYTE(0x8F, val); | ||||
|  | ||||
|   // Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt) | ||||
|   APDS9960_WRITE_BYTE(0x8C, 0x11); | ||||
|   // Config 2 (0x90) -> 0x01 (no saturation interrupts or LED boost) | ||||
|   APDS9960_WRITE_BYTE(0x90, 0x01); | ||||
|   // Config 3 (0x9F) -> 0x00 (enable all photodiodes, no SAI) | ||||
|   APDS9960_WRITE_BYTE(0x9F, 0x00); | ||||
|   // GPenTh (0xA0, gesture enter threshold) -> 0x28 (also 0x32) | ||||
|   APDS9960_WRITE_BYTE(0xA0, 0x28); | ||||
|   // GPexTh (0xA1, gesture exit threshold) -> 0x1E | ||||
|   APDS9960_WRITE_BYTE(0xA1, 0x1E); | ||||
|  | ||||
|   // GConf 1 (0xA2, gesture config 1) -> 0x40 (4 gesture events for interrupt (GFIFO 3), 1 for exit) | ||||
|   APDS9960_WRITE_BYTE(0xA2, 0x40); | ||||
|  | ||||
|   // GConf 2 (0xA3, gesture config 2) -> | ||||
|   APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val)); | ||||
|   val &= 0b10011111; | ||||
|   uint8_t gesture_gain = 2;  // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x | ||||
|   val |= (gesture_gain & 0b11) << 5; | ||||
|  | ||||
|   val &= 0b11100111; | ||||
|   uint8_t gesture_led_drive = 0;  // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA | ||||
|   val |= (gesture_led_drive & 0b11) << 3; | ||||
|  | ||||
|   val &= 0b11111000; | ||||
|   // gesture wait time | ||||
|   // 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms | ||||
|   // 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms | ||||
|   uint8_t gesture_wait_time = 1;  // gesture wait time | ||||
|   val |= (gesture_wait_time & 0b111) << 0; | ||||
|   APDS9960_WRITE_BYTE(0xA3, val); | ||||
|  | ||||
|   // GOffsetU (0xA4) -> 0x00 (no offset) | ||||
|   APDS9960_WRITE_BYTE(0xA4, 0x00); | ||||
|   // GOffsetD (0xA5) -> 0x00 (no offset) | ||||
|   APDS9960_WRITE_BYTE(0xA5, 0x00); | ||||
|   // GOffsetL (0xA7) -> 0x00 (no offset) | ||||
|   APDS9960_WRITE_BYTE(0xA7, 0x00); | ||||
|   // GOffsetR (0xA9) -> 0x00 (no offset) | ||||
|   APDS9960_WRITE_BYTE(0xA9, 0x00); | ||||
|   // GPulse (0xA6) -> 0xC9 (32 µs, 10 pulses) | ||||
|   APDS9960_WRITE_BYTE(0xA6, 0xC9); | ||||
|  | ||||
|   // GConf 3 (0xAA, gesture config 3) -> 0x00 (all photodiodes active during gesture, all gesture dimensions enabled) | ||||
|   // 0x00 -> all dimensions, 0x01 -> up down, 0x02 -> left right | ||||
|   APDS9960_WRITE_BYTE(0xAA, 0x00); | ||||
|  | ||||
|   // Enable (0x80) -> | ||||
|   val = 0; | ||||
|   val |= (0b1) << 0;  // power on | ||||
|   val |= (this->is_color_enabled_() & 0b1) << 1; | ||||
|   val |= (this->is_proximity_enabled_() & 0b1) << 2; | ||||
|   val |= 0b0 << 3;                                  // wait timer disabled | ||||
|   val |= 0b0 << 4;                                  // color interrupt disabled | ||||
|   val |= 0b0 << 5;                                  // proximity interrupt disabled | ||||
|   val |= (this->is_gesture_enabled_() & 0b1) << 6;  // proximity is required for gestures | ||||
|   APDS9960_WRITE_BYTE(0x80, val); | ||||
| } | ||||
| bool APDS9960::is_color_enabled_() const { | ||||
|   return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr || | ||||
|          this->clear_channel_ != nullptr; | ||||
| } | ||||
|  | ||||
| void APDS9960::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "APDS9960:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|  | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
|   if (this->is_failed()) { | ||||
|     switch (this->error_code_) { | ||||
|       case COMMUNICATION_FAILED: | ||||
|         ESP_LOGE(TAG, "Communication with APDS9960 failed!"); | ||||
|         break; | ||||
|       case WRONG_ID: | ||||
|         ESP_LOGE(TAG, "APDS9960 has invalid id!"); | ||||
|         break; | ||||
|       default: | ||||
|         ESP_LOGE(TAG, "Setting up APDS9960 registers failed!"); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| #define APDS9960_WARNING_CHECK(func, warning) \ | ||||
|   if (!(func)) { \ | ||||
|     ESP_LOGW(TAG, warning); \ | ||||
|     this->status_set_warning(); \ | ||||
|     return; \ | ||||
|   } | ||||
|  | ||||
| void APDS9960::update() { | ||||
|   uint8_t status; | ||||
|   APDS9960_WARNING_CHECK(this->read_byte(0x93, &status), "Reading status bit failed."); | ||||
|   this->status_clear_warning(); | ||||
|  | ||||
|   this->read_color_data_(status); | ||||
|   this->read_proximity_data_(status); | ||||
| } | ||||
|  | ||||
| void APDS9960::loop() { this->read_gesture_data_(); } | ||||
|  | ||||
| void APDS9960::read_color_data_(uint8_t status) { | ||||
|   if (!this->is_color_enabled_()) | ||||
|     return; | ||||
|  | ||||
|   if ((status & 0x01) == 0x00) { | ||||
|     // color data not ready yet. | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   uint8_t raw[8]; | ||||
|   APDS9960_WARNING_CHECK(this->read_bytes(0x94, raw, 8), "Reading color values failed."); | ||||
|  | ||||
|   uint16_t uint_clear = (uint16_t(raw[1]) << 8) | raw[0]; | ||||
|   uint16_t uint_red = (uint16_t(raw[3]) << 8) | raw[2]; | ||||
|   uint16_t uint_green = (uint16_t(raw[5]) << 8) | raw[4]; | ||||
|   uint16_t uint_blue = (uint16_t(raw[7]) << 8) | raw[6]; | ||||
|  | ||||
|   float clear_perc = (uint_clear / float(UINT16_MAX)) * 100.0f; | ||||
|   float red_perc = (uint_red / float(UINT16_MAX)) * 100.0f; | ||||
|   float green_perc = (uint_green / float(UINT16_MAX)) * 100.0f; | ||||
|   float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f; | ||||
|  | ||||
|   ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc); | ||||
|   if (this->clear_channel_ != nullptr) | ||||
|     this->clear_channel_->publish_state(clear_perc); | ||||
|   if (this->red_channel_ != nullptr) | ||||
|     this->red_channel_->publish_state(red_perc); | ||||
|   if (this->green_channel_ != nullptr) | ||||
|     this->green_channel_->publish_state(green_perc); | ||||
|   if (this->blue_channel_ != nullptr) | ||||
|     this->blue_channel_->publish_state(blue_perc); | ||||
| } | ||||
| void APDS9960::read_proximity_data_(uint8_t status) { | ||||
|   if (this->proximity_ == nullptr) | ||||
|     return; | ||||
|  | ||||
|   if ((status & 0b10) == 0x00) { | ||||
|     // proximity data not ready yet. | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   uint8_t prox; | ||||
|   APDS9960_WARNING_CHECK(this->read_byte(0x9C, &prox), "Reading proximity values failed."); | ||||
|  | ||||
|   float prox_perc = (prox / float(UINT8_MAX)) * 100.0f; | ||||
|   ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc); | ||||
|   this->proximity_->publish_state(prox_perc); | ||||
| } | ||||
| void APDS9960::read_gesture_data_() { | ||||
|   if (!this->is_gesture_enabled_()) | ||||
|     return; | ||||
|  | ||||
|   uint8_t status; | ||||
|   APDS9960_WARNING_CHECK(this->read_byte(0xAF, &status), "Reading gesture status failed."); | ||||
|  | ||||
|   if ((status & 0b01) == 0) { | ||||
|     // GVALID is false | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if ((status & 0b10) == 0b10) { | ||||
|     ESP_LOGV(TAG, "FIFO buffer has filled to capacity!"); | ||||
|   } | ||||
|  | ||||
|   uint8_t fifo_level; | ||||
|   APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed."); | ||||
|   if (fifo_level == 0) | ||||
|     // no data to process | ||||
|     return; | ||||
|  | ||||
|   APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.") | ||||
|  | ||||
|   uint8_t buf[128]; | ||||
|   for (uint8_t pos = 0; pos < fifo_level * 4; pos += 32) { | ||||
|     // The ESP's i2c driver has a limited buffer size. | ||||
|     // This way of retrieving the data should be wrong according to the datasheet | ||||
|     // but it seems to work. | ||||
|     uint8_t read = std::min(32, fifo_level * 4 - pos); | ||||
|     APDS9960_WARNING_CHECK(this->read_bytes(0xFC + pos, buf + pos, read), "Reading FIFO buffer failed."); | ||||
|   } | ||||
|  | ||||
|   if (millis() - this->gesture_start_ > 500) { | ||||
|     this->gesture_up_started_ = false; | ||||
|     this->gesture_down_started_ = false; | ||||
|     this->gesture_left_started_ = false; | ||||
|     this->gesture_right_started_ = false; | ||||
|   } | ||||
|  | ||||
|   for (uint32_t i = 0; i < fifo_level * 4; i += 4) { | ||||
|     const int up = buf[i + 0];  // NOLINT | ||||
|     const int down = buf[i + 1]; | ||||
|     const int left = buf[i + 2]; | ||||
|     const int right = buf[i + 3]; | ||||
|     this->process_dataset_(up, down, left, right); | ||||
|   } | ||||
| } | ||||
| void APDS9960::report_gesture_(int gesture) { | ||||
|   binary_sensor::BinarySensor *bin; | ||||
|   switch (gesture) { | ||||
|     case 1: | ||||
|       bin = this->up_direction_; | ||||
|       this->gesture_up_started_ = false; | ||||
|       this->gesture_down_started_ = false; | ||||
|       ESP_LOGD(TAG, "Got gesture UP"); | ||||
|       break; | ||||
|     case 2: | ||||
|       bin = this->down_direction_; | ||||
|       this->gesture_up_started_ = false; | ||||
|       this->gesture_down_started_ = false; | ||||
|       ESP_LOGD(TAG, "Got gesture DOWN"); | ||||
|       break; | ||||
|     case 3: | ||||
|       bin = this->left_direction_; | ||||
|       this->gesture_left_started_ = false; | ||||
|       this->gesture_right_started_ = false; | ||||
|       ESP_LOGD(TAG, "Got gesture LEFT"); | ||||
|       break; | ||||
|     case 4: | ||||
|       bin = this->right_direction_; | ||||
|       this->gesture_left_started_ = false; | ||||
|       this->gesture_right_started_ = false; | ||||
|       ESP_LOGD(TAG, "Got gesture RIGHT"); | ||||
|       break; | ||||
|     default: | ||||
|       return; | ||||
|   } | ||||
|  | ||||
|   if (bin != nullptr) { | ||||
|     bin->publish_state(true); | ||||
|     bin->publish_state(false); | ||||
|   } | ||||
| } | ||||
| void APDS9960::process_dataset_(int up, int down, int left, int right) { | ||||
|   /* Algorithm: (see Figure 11 in datasheet) | ||||
|    * | ||||
|    * Observation: When a gesture is started, we will see a short amount of time where | ||||
|    * the photodiode in the direction of the motion has a much higher count value | ||||
|    * than where the gesture originates. | ||||
|    * | ||||
|    * In this algorithm we continually check the difference between the count values of opposing | ||||
|    * directions. For example in the down/up direction we continually look at the difference of the | ||||
|    * up count and down count. When DOWN gesture begins, this difference will be positive with a | ||||
|    * high magnitude for a short amount of time (magic value here is the difference is at least 13). | ||||
|    * | ||||
|    * If we see such a pattern, we store that we saw the first part of a gesture (the leading edge). | ||||
|    * After that some time can pass during which the difference is zero again (though the count values | ||||
|    * are not zero). At the end of a gesture, we will see this difference go into the opposite direction | ||||
|    * for a short period of time. | ||||
|    * | ||||
|    * If a gesture is not ended within 500 milliseconds, we consider the initial trailing edge invalid | ||||
|    * and reset the state. | ||||
|    * | ||||
|    * This algorithm does work, but not too well. Some good signal processing algorithms could | ||||
|    * probably improve this a lot, especially since the incoming signal has such a characteristic | ||||
|    * and quite noise-free pattern. | ||||
|    */ | ||||
|   const int up_down_delta = up - down; | ||||
|   const int left_right_delta = left - right; | ||||
|   const bool up_down_significant = abs(up_down_delta) > 13; | ||||
|   const bool left_right_significant = abs(left_right_delta) > 13; | ||||
|  | ||||
|   if (up_down_significant) { | ||||
|     if (up_down_delta < 0) { | ||||
|       if (this->gesture_up_started_) { | ||||
|         // trailing edge of gesture up | ||||
|         this->report_gesture_(1);  // UP | ||||
|       } else { | ||||
|         // leading edge of gesture down | ||||
|         this->gesture_down_started_ = true; | ||||
|         this->gesture_start_ = millis(); | ||||
|       } | ||||
|     } else { | ||||
|       if (this->gesture_down_started_) { | ||||
|         // trailing edge of gesture down | ||||
|         this->report_gesture_(2);  // DOWN | ||||
|       } else { | ||||
|         // leading edge of gesture up | ||||
|         this->gesture_up_started_ = true; | ||||
|         this->gesture_start_ = millis(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (left_right_significant) { | ||||
|     if (left_right_delta < 0) { | ||||
|       if (this->gesture_left_started_) { | ||||
|         // trailing edge of gesture left | ||||
|         this->report_gesture_(3);  // LEFT | ||||
|       } else { | ||||
|         // leading edge of gesture right | ||||
|         this->gesture_right_started_ = true; | ||||
|         this->gesture_start_ = millis(); | ||||
|       } | ||||
|     } else { | ||||
|       if (this->gesture_right_started_) { | ||||
|         // trailing edge of gesture right | ||||
|         this->report_gesture_(4);  // RIGHT | ||||
|       } else { | ||||
|         // leading edge of gesture left | ||||
|         this->gesture_left_started_ = true; | ||||
|         this->gesture_start_ = millis(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| float APDS9960::get_setup_priority() const { return setup_priority::DATA; } | ||||
| bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); } | ||||
| bool APDS9960::is_gesture_enabled_() const { | ||||
|   return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr || | ||||
|          this->right_direction_ != nullptr; | ||||
| } | ||||
|  | ||||
| }  // namespace apds9960 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										61
									
								
								esphome/components/apds9960/apds9960.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								esphome/components/apds9960/apds9960.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace apds9960 { | ||||
|  | ||||
| class APDS9960 : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void update() override; | ||||
|   void loop() override; | ||||
|  | ||||
|   void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; } | ||||
|   void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; } | ||||
|   void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; } | ||||
|   void set_clear_channel(sensor::Sensor *clear_channel) { clear_channel_ = clear_channel; } | ||||
|   void set_up_direction(binary_sensor::BinarySensor *up_direction) { up_direction_ = up_direction; } | ||||
|   void set_right_direction(binary_sensor::BinarySensor *right_direction) { right_direction_ = right_direction; } | ||||
|   void set_down_direction(binary_sensor::BinarySensor *down_direction) { down_direction_ = down_direction; } | ||||
|   void set_left_direction(binary_sensor::BinarySensor *left_direction) { left_direction_ = left_direction; } | ||||
|   void set_proximity(sensor::Sensor *proximity) { proximity_ = proximity; } | ||||
|  | ||||
|  protected: | ||||
|   bool is_color_enabled_() const; | ||||
|   bool is_proximity_enabled_() const; | ||||
|   bool is_gesture_enabled_() const; | ||||
|   void read_color_data_(uint8_t status); | ||||
|   void read_proximity_data_(uint8_t status); | ||||
|   void read_gesture_data_(); | ||||
|   void report_gesture_(int gesture); | ||||
|   void process_dataset_(int up, int down, int left, int right); | ||||
|  | ||||
|   sensor::Sensor *red_channel_{nullptr}; | ||||
|   sensor::Sensor *green_channel_{nullptr}; | ||||
|   sensor::Sensor *blue_channel_{nullptr}; | ||||
|   sensor::Sensor *clear_channel_{nullptr}; | ||||
|   binary_sensor::BinarySensor *up_direction_{nullptr}; | ||||
|   binary_sensor::BinarySensor *right_direction_{nullptr}; | ||||
|   binary_sensor::BinarySensor *down_direction_{nullptr}; | ||||
|   binary_sensor::BinarySensor *left_direction_{nullptr}; | ||||
|   sensor::Sensor *proximity_{nullptr}; | ||||
|   enum ErrorCode { | ||||
|     NONE = 0, | ||||
|     COMMUNICATION_FAILED, | ||||
|     WRONG_ID, | ||||
|   } error_code_{NONE}; | ||||
|   bool gesture_up_started_{false}; | ||||
|   bool gesture_down_started_{false}; | ||||
|   bool gesture_left_started_{false}; | ||||
|   bool gesture_right_started_{false}; | ||||
|   uint32_t gesture_start_{0}; | ||||
| }; | ||||
|  | ||||
| }  // namespace apds9960 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										27
									
								
								esphome/components/apds9960/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/apds9960/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import binary_sensor | ||||
| from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING | ||||
| from . import APDS9960, CONF_APDS9960_ID | ||||
|  | ||||
| DEPENDENCIES = ['apds9960'] | ||||
|  | ||||
| DIRECTIONS = { | ||||
|     'UP': 'set_up_direction', | ||||
|     'DOWN': 'set_down_direction', | ||||
|     'LEFT': 'set_left_direction', | ||||
|     'RIGHT': 'set_right_direction', | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
|     cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), | ||||
|     cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ||||
|     cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class, | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     hub = yield cg.get_variable(config[CONF_APDS9960_ID]) | ||||
|     var = yield binary_sensor.new_binary_sensor(config) | ||||
|     func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]]) | ||||
|     cg.add(func(var)) | ||||
							
								
								
									
										27
									
								
								esphome/components/apds9960/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/apds9960/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB | ||||
| from . import APDS9960, CONF_APDS9960_ID | ||||
|  | ||||
| DEPENDENCIES = ['apds9960'] | ||||
|  | ||||
| TYPES = { | ||||
|     'CLEAR': 'set_clear_channel', | ||||
|     'RED': 'set_red_channel', | ||||
|     'GREEN': 'set_green_channel', | ||||
|     'BLUE': 'set_blue_channel', | ||||
|     'PROXIMITY': 'set_proximity', | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({ | ||||
|     cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True), | ||||
|     cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     hub = yield cg.get_variable(config[CONF_APDS9960_ID]) | ||||
|     var = yield sensor.new_sensor(config) | ||||
|     func = getattr(hub, TYPES[config[CONF_TYPE]]) | ||||
|     cg.add(func(var)) | ||||
							
								
								
									
										118
									
								
								esphome/components/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								esphome/components/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.automation import Condition | ||||
| from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \ | ||||
|     CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
|  | ||||
| DEPENDENCIES = ['network'] | ||||
|  | ||||
| api_ns = cg.esphome_ns.namespace('api') | ||||
| APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) | ||||
| HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action) | ||||
| KeyValuePair = api_ns.class_('KeyValuePair') | ||||
| TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair') | ||||
| APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition) | ||||
|  | ||||
| UserService = api_ns.class_('UserService', automation.Trigger) | ||||
| ServiceTypeArgument = api_ns.class_('ServiceTypeArgument') | ||||
| ServiceArgType = api_ns.enum('ServiceArgType') | ||||
| SERVICE_ARG_TYPES = { | ||||
|     'bool': ServiceArgType.SERVICE_ARG_TYPE_BOOL, | ||||
|     'int': ServiceArgType.SERVICE_ARG_TYPE_INT, | ||||
|     'float': ServiceArgType.SERVICE_ARG_TYPE_FLOAT, | ||||
|     'string': ServiceArgType.SERVICE_ARG_TYPE_STRING, | ||||
| } | ||||
| SERVICE_ARG_NATIVE_TYPES = { | ||||
|     'bool': bool, | ||||
|     'int': cg.int32, | ||||
|     'float': float, | ||||
|     'string': cg.std_string, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(APIServer), | ||||
|     cv.Optional(CONF_PORT, default=6053): cv.port, | ||||
|     cv.Optional(CONF_PASSWORD, default=''): cv.string_strict, | ||||
|     cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_SERVICES): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserService), | ||||
|         cv.Required(CONF_SERVICE): cv.valid_name, | ||||
|         cv.Optional(CONF_VARIABLES, default={}): cv.Schema({ | ||||
|             cv.validate_id_name: cv.one_of(*SERVICE_ARG_TYPES, lower=True), | ||||
|         }), | ||||
|     }), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(40.0) | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|  | ||||
|     cg.add(var.set_port(config[CONF_PORT])) | ||||
|     cg.add(var.set_password(config[CONF_PASSWORD])) | ||||
|     cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) | ||||
|  | ||||
|     for conf in config.get(CONF_SERVICES, []): | ||||
|         template_args = [] | ||||
|         func_args = [] | ||||
|         service_type_args = [] | ||||
|         for name, var_ in conf[CONF_VARIABLES].items(): | ||||
|             native = SERVICE_ARG_NATIVE_TYPES[var_] | ||||
|             template_args.append(native) | ||||
|             func_args.append((native, name)) | ||||
|             service_type_args.append(ServiceTypeArgument(name, SERVICE_ARG_TYPES[var_])) | ||||
|         templ = cg.TemplateArguments(*template_args) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ, | ||||
|                                    conf[CONF_SERVICE], service_type_args) | ||||
|         cg.add(var.register_user_service(trigger)) | ||||
|         yield automation.build_automation(trigger, func_args, conf) | ||||
|  | ||||
|     cg.add_define('USE_API') | ||||
|     if CORE.is_esp32: | ||||
|         cg.add_library('AsyncTCP', '1.0.3') | ||||
|     elif CORE.is_esp8266: | ||||
|         cg.add_library('ESPAsyncTCP', '1.2.0') | ||||
|  | ||||
|  | ||||
| HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.use_id(APIServer), | ||||
|     cv.Required(CONF_SERVICE): cv.string, | ||||
|     cv.Optional(CONF_DATA): cv.Schema({ | ||||
|         cv.string: cv.string, | ||||
|     }), | ||||
|     cv.Optional(CONF_DATA_TEMPLATE): cv.Schema({ | ||||
|         cv.string: cv.string, | ||||
|     }), | ||||
|     cv.Optional(CONF_VARIABLES): cv.Schema({ | ||||
|         cv.string: cv.returning_lambda, | ||||
|     }), | ||||
| }) | ||||
|  | ||||
|  | ||||
| @automation.register_action('homeassistant.service', HomeAssistantServiceCallAction, | ||||
|                             HOMEASSISTANT_SERVICE_ACTION_SCHEMA) | ||||
| def homeassistant_service_to_code(config, action_id, template_arg, args): | ||||
|     serv = yield cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, serv) | ||||
|     cg.add(var.set_service(config[CONF_SERVICE])) | ||||
|     if CONF_DATA in config: | ||||
|         datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()] | ||||
|         cg.add(var.set_data(datas)) | ||||
|     if CONF_DATA_TEMPLATE in config: | ||||
|         datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()] | ||||
|         cg.add(var.set_data_template(datas)) | ||||
|     if CONF_VARIABLES in config: | ||||
|         datas = [] | ||||
|         for key, value in config[CONF_VARIABLES].items(): | ||||
|             value_ = yield cg.process_lambda(value, []) | ||||
|             datas.append(TemplatableKeyValuePair(key, value_)) | ||||
|         cg.add(var.set_variables(datas)) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_condition('api.connected', APIConnectedCondition, {}) | ||||
| def api_connected_to_code(config, condition_id, template_arg, args): | ||||
|     yield cg.new_Pvariable(condition_id, template_arg) | ||||
							
								
								
									
										506
									
								
								esphome/components/api/api.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										506
									
								
								esphome/components/api/api.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,506 @@ | ||||
| syntax = "proto3"; | ||||
|  | ||||
|  | ||||
| // ==================== BASE PACKETS ==================== | ||||
|  | ||||
| // The Home Assistant protocol is structured as a simple | ||||
| // TCP socket with short binary messages encoded in the protocol buffers format | ||||
| // First, a message in this protocol has a specific format: | ||||
| //  * VarInt denoting the size of the message object. (type is not part of this) | ||||
| //  * VarInt denoting the type of message. | ||||
| //  * The message object encoded as a ProtoBuf message | ||||
|  | ||||
| // The connection is established in 4 steps: | ||||
| //  * First, the client connects to the server and sends a "Hello Request" identifying itself | ||||
| //  * The server responds with a "Hello Response" and selects the protocol version | ||||
| //  * After receiving this message, the client attempts to authenticate itself using | ||||
| //    the password and a "Connect Request" | ||||
| //  * The server responds with a "Connect Response" and notifies of invalid password. | ||||
| // If anything in this initial process fails, the connection must immediately closed | ||||
| // by both sides and _no_ disconnection message is to be sent. | ||||
|  | ||||
| // Message sent at the beginning of each connection | ||||
| // Can only be sent by the client and only at the beginning of the connection | ||||
| // ID: 1 | ||||
| message HelloRequest { | ||||
|   // Description of client (like User Agent) | ||||
|   // For example "Home Assistant" | ||||
|   // Not strictly necessary to send but nice for debugging | ||||
|   // purposes. | ||||
|   string client_info = 1; | ||||
| } | ||||
|  | ||||
| // Confirmation of successful connection request. | ||||
| // Can only be sent by the server and only at the beginning of the connection | ||||
| // ID: 2 | ||||
| message HelloResponse { | ||||
|   // The version of the API to use. The _client_ (for example Home Assistant) needs to check | ||||
|   // for compatibility and if necessary adopt to an older API. | ||||
|   // Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_ | ||||
|   // Minor is for breaking changes in individual messages - a mismatch will lead to a warning message | ||||
|   uint32 api_version_major = 1; | ||||
|   uint32 api_version_minor = 2; | ||||
|  | ||||
|   // A string identifying the server (ESP); like client info this may be empty | ||||
|   // and only exists for debugging/logging purposes. | ||||
|   // For example "ESPHome v1.10.0 on ESP8266" | ||||
|   string server_info = 3; | ||||
| } | ||||
|  | ||||
| // Message sent at the beginning of each connection to authenticate the client | ||||
| // Can only be sent by the client and only at the beginning of the connection | ||||
| // ID: 3 | ||||
| message ConnectRequest { | ||||
|   // The password to log in with | ||||
|   string password = 1; | ||||
| } | ||||
|  | ||||
| // Confirmation of successful connection. After this the connection is available for all traffic. | ||||
| // Can only be sent by the server and only at the beginning of the connection | ||||
| // ID: 4 | ||||
| message ConnectResponse { | ||||
|   bool invalid_password = 1; | ||||
| } | ||||
|  | ||||
| // Request to close the connection. | ||||
| // Can be sent by both the client and server | ||||
| // ID: 5 | ||||
| message DisconnectRequest { | ||||
|   // Do not close the connection before the acknowledgement arrives | ||||
| } | ||||
|  | ||||
| // ID: 6 | ||||
| message DisconnectResponse { | ||||
|   // Empty - Both parties are required to close the connection after this | ||||
|   // message has been received. | ||||
| } | ||||
|  | ||||
| // ID: 7 | ||||
| message PingRequest { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ID: 8 | ||||
| message PingResponse { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ID: 9 | ||||
| message DeviceInfoRequest { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ID: 10 | ||||
| message DeviceInfoResponse { | ||||
|   bool uses_password = 1; | ||||
|  | ||||
|   // The name of the node, given by "App.set_name()" | ||||
|   string name = 2; | ||||
|  | ||||
|   // The mac address of the device. For example "AC:BC:32:89:0E:A9" | ||||
|   string mac_address = 3; | ||||
|  | ||||
|   // A string describing the ESPHome version. For example "1.10.0" | ||||
|   string esphome_core_version = 4; | ||||
|  | ||||
|   // A string describing the date of compilation, this is generated by the compiler | ||||
|   // and therefore may not be in the same format all the time. | ||||
|   // If the user isn't using ESPHome, this will also not be set. | ||||
|   string compilation_time = 5; | ||||
|  | ||||
|   // The model of the board. For example NodeMCU | ||||
|   string model = 6; | ||||
|  | ||||
|   bool has_deep_sleep = 7; | ||||
| } | ||||
|  | ||||
| // ID: 11 | ||||
| message ListEntitiesRequest { | ||||
|   // Empty | ||||
| } | ||||
| // ID: 19 | ||||
| message ListEntitiesDoneResponse { | ||||
|   // Empty | ||||
| } | ||||
| // ID: 20 | ||||
| message SubscribeStatesRequest { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ==================== BINARY SENSOR ==================== | ||||
| // ID: 12 | ||||
| message ListEntitiesBinarySensorResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string device_class = 5; | ||||
|   bool is_status_binary_sensor = 6; | ||||
| } | ||||
| // ID: 21 | ||||
| message BinarySensorStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== COVER ==================== | ||||
| // ID: 13 | ||||
| message ListEntitiesCoverResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   bool assumed_state = 5; | ||||
|   bool supports_position = 6; | ||||
|   bool supports_tilt = 7; | ||||
|   string device_class = 8; | ||||
| } | ||||
| // ID: 22 | ||||
| message CoverStateResponse { | ||||
|   fixed32 key = 1; | ||||
|  | ||||
|   // legacy: state has been removed in 1.13 | ||||
|   // clients/servers must still send/accept it until the next protocol change | ||||
|   enum LegacyCoverState { | ||||
|     OPEN = 0; | ||||
|     CLOSED = 1; | ||||
|   } | ||||
|   LegacyCoverState legacy_state = 2; | ||||
|  | ||||
|   float position = 3; | ||||
|   float tilt = 4; | ||||
|   enum CoverOperation { | ||||
|     IDLE = 0; | ||||
|     IS_OPENING = 1; | ||||
|     IS_CLOSING = 2; | ||||
|   } | ||||
|   CoverOperation current_operation = 5; | ||||
| } | ||||
| // ID: 30 | ||||
| message CoverCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|  | ||||
|   // legacy: command has been removed in 1.13 | ||||
|   // clients/servers must still send/accept it until the next protocol change | ||||
|   enum LegacyCoverCommand { | ||||
|     OPEN = 0; | ||||
|     CLOSE = 1; | ||||
|     STOP = 2; | ||||
|   } | ||||
|   bool has_legacy_command = 2; | ||||
|   LegacyCoverCommand legacy_command = 3; | ||||
|  | ||||
|   bool has_position = 4; | ||||
|   float position = 5; | ||||
|   bool has_tilt = 6; | ||||
|   float tilt = 7; | ||||
|   bool stop = 8; | ||||
| } | ||||
|  | ||||
| // ==================== FAN ==================== | ||||
| // ID: 14 | ||||
| message ListEntitiesFanResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   bool supports_oscillation = 5; | ||||
|   bool supports_speed = 6; | ||||
| } | ||||
| enum FanSpeed { | ||||
|   LOW = 0; | ||||
|   MEDIUM = 1; | ||||
|   HIGH = 2; | ||||
| } | ||||
| // ID: 23 | ||||
| message FanStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
|   bool oscillating = 3; | ||||
|   FanSpeed speed = 4; | ||||
| } | ||||
| // ID: 31 | ||||
| message FanCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   bool has_state = 2; | ||||
|   bool state = 3; | ||||
|   bool has_speed = 4; | ||||
|   FanSpeed speed = 5; | ||||
|   bool has_oscillating = 6; | ||||
|   bool oscillating = 7; | ||||
| } | ||||
|  | ||||
| // ==================== LIGHT ==================== | ||||
| // ID: 15 | ||||
| message ListEntitiesLightResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   bool supports_brightness = 5; | ||||
|   bool supports_rgb = 6; | ||||
|   bool supports_white_value = 7; | ||||
|   bool supports_color_temperature = 8; | ||||
|   float min_mireds = 9; | ||||
|   float max_mireds = 10; | ||||
|   repeated string effects = 11; | ||||
| } | ||||
| // ID: 24 | ||||
| message LightStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
|   float brightness = 3; | ||||
|   float red = 4; | ||||
|   float green = 5; | ||||
|   float blue = 6; | ||||
|   float white = 7; | ||||
|   float color_temperature = 8; | ||||
|   string effect = 9; | ||||
| } | ||||
| // ID: 32 | ||||
| message LightCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   bool has_state = 2; | ||||
|   bool state = 3; | ||||
|   bool has_brightness = 4; | ||||
|   float brightness = 5; | ||||
|   bool has_rgb = 6; | ||||
|   float red = 7; | ||||
|   float green = 8; | ||||
|   float blue = 9; | ||||
|   bool has_white = 10; | ||||
|   float white = 11; | ||||
|   bool has_color_temperature = 12; | ||||
|   float color_temperature = 13; | ||||
|   bool has_transition_length = 14; | ||||
|   uint32 transition_length = 15; | ||||
|   bool has_flash_length = 16; | ||||
|   uint32 flash_length = 17; | ||||
|   bool has_effect = 18; | ||||
|   string effect = 19; | ||||
| } | ||||
|  | ||||
| // ==================== SENSOR ==================== | ||||
| // ID: 16 | ||||
| message ListEntitiesSensorResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   string unit_of_measurement = 6; | ||||
|   int32 accuracy_decimals = 7; | ||||
| } | ||||
| // ID: 25 | ||||
| message SensorStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   float state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== SWITCH ==================== | ||||
| // ID: 17 | ||||
| message ListEntitiesSwitchResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
|   bool assumed_state = 6; | ||||
| } | ||||
| // ID: 26 | ||||
| message SwitchStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
| // ID: 33 | ||||
| message SwitchCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== TEXT SENSOR ==================== | ||||
| // ID: 18 | ||||
| message ListEntitiesTextSensorResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   string icon = 5; | ||||
| } | ||||
| // ID: 27 | ||||
| message TextSensorStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   string state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== SUBSCRIBE LOGS ==================== | ||||
| enum LogLevel { | ||||
|   NONE = 0; | ||||
|   ERROR = 1; | ||||
|   WARN = 2; | ||||
|   INFO = 3; | ||||
|   DEBUG = 4; | ||||
|   VERBOSE = 5; | ||||
|   VERY_VERBOSE = 6; | ||||
| } | ||||
| // ID: 28 | ||||
| message SubscribeLogsRequest { | ||||
|   LogLevel level = 1; | ||||
|   bool dump_config = 2; | ||||
| } | ||||
| // ID: 29 | ||||
| message SubscribeLogsResponse { | ||||
|   LogLevel level = 1; | ||||
|   string tag = 2; | ||||
|   string message = 3; | ||||
|   bool send_failed = 4; | ||||
| } | ||||
|  | ||||
| // ==================== HOMEASSISTANT.SERVICE ==================== | ||||
| // ID: 34 | ||||
| message SubscribeServiceCallsRequest { | ||||
|  | ||||
| } | ||||
|  | ||||
| // ID: 35 | ||||
| message ServiceCallResponse { | ||||
|   string service = 1; | ||||
|   map<string, string> data = 2; | ||||
|   map<string, string> data_template = 3; | ||||
|   map<string, string> variables = 4; | ||||
| } | ||||
|  | ||||
| // ==================== IMPORT HOME ASSISTANT STATES ==================== | ||||
| // 1. Client sends SubscribeHomeAssistantStatesRequest | ||||
| // 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async) | ||||
| // 3. Client sends HomeAssistantStateResponse for state changes. | ||||
| // ID: 38 | ||||
| message SubscribeHomeAssistantStatesRequest { | ||||
|  | ||||
| } | ||||
|  | ||||
| // ID: 39 | ||||
| message SubscribeHomeAssistantStateResponse { | ||||
|   string entity_id = 1; | ||||
| } | ||||
|  | ||||
| // ID: 40 | ||||
| message HomeAssistantStateResponse { | ||||
|   string entity_id = 1; | ||||
|   string state = 2; | ||||
| } | ||||
|  | ||||
| // ==================== IMPORT TIME ==================== | ||||
| // ID: 36 | ||||
| message GetTimeRequest { | ||||
|  | ||||
| } | ||||
|  | ||||
| // ID: 37 | ||||
| message GetTimeResponse { | ||||
|   fixed32 epoch_seconds = 1; | ||||
| } | ||||
|  | ||||
| // ==================== USER-DEFINES SERVICES ==================== | ||||
| message ListEntitiesServicesArgument { | ||||
|   string name = 1; | ||||
|   enum Type { | ||||
|     BOOL = 0; | ||||
|     INT = 1; | ||||
|     FLOAT = 2; | ||||
|     STRING = 3; | ||||
|   } | ||||
|   Type type = 2; | ||||
| } | ||||
| // ID: 41 | ||||
| message ListEntitiesServicesResponse { | ||||
|   string name = 1; | ||||
|   fixed32 key = 2; | ||||
|   repeated ListEntitiesServicesArgument args = 3; | ||||
| } | ||||
| message ExecuteServiceArgument { | ||||
|   bool bool_ = 1; | ||||
|   int32 int_ = 2; | ||||
|   float float_ = 3; | ||||
|   string string_ = 4; | ||||
| } | ||||
| // ID: 42 | ||||
| message ExecuteServiceRequest { | ||||
|   fixed32 key = 1; | ||||
|   repeated ExecuteServiceArgument args = 2; | ||||
| } | ||||
|  | ||||
| // ==================== CAMERA ==================== | ||||
| // ID: 43 | ||||
| message ListEntitiesCameraResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
| } | ||||
|  | ||||
| // ID: 44 | ||||
| message CameraImageResponse { | ||||
|   fixed32 key = 1; | ||||
|   bytes data = 2; | ||||
|   bool done = 3; | ||||
| } | ||||
| // ID: 45 | ||||
| message CameraImageRequest { | ||||
|   bool single = 1; | ||||
|   bool stream = 2; | ||||
| } | ||||
|  | ||||
| // ==================== CLIMATE ==================== | ||||
| enum ClimateMode { | ||||
|   OFF = 0; | ||||
|   AUTO = 1; | ||||
|   COOL = 2; | ||||
|   HEAT = 3; | ||||
| } | ||||
| // ID: 46 | ||||
| message ListEntitiesClimateResponse { | ||||
|   string object_id = 1; | ||||
|   fixed32 key = 2; | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|  | ||||
|   bool supports_current_temperature = 5; | ||||
|   bool supports_two_point_target_temperature = 6; | ||||
|   repeated ClimateMode supported_modes = 7; | ||||
|   float visual_min_temperature = 8; | ||||
|   float visual_max_temperature = 9; | ||||
|   float visual_temperature_step = 10; | ||||
|   bool supports_away = 11; | ||||
| } | ||||
| // ID: 47 | ||||
| message ClimateStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   ClimateMode mode = 2; | ||||
|   float current_temperature = 3; | ||||
|   float target_temperature = 4; | ||||
|   float target_temperature_low = 5; | ||||
|   float target_temperature_high = 6; | ||||
|   bool away = 7; | ||||
| } | ||||
| // ID: 48 | ||||
| message ClimateCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   bool has_mode = 2; | ||||
|   ClimateMode mode = 3; | ||||
|   bool has_target_temperature = 4; | ||||
|   float target_temperature = 5; | ||||
|   bool has_target_temperature_low = 6; | ||||
|   float target_temperature_low = 7; | ||||
|   bool has_target_temperature_high = 8; | ||||
|   float target_temperature_high = 9; | ||||
|   bool has_away = 10; | ||||
|   bool away = 11; | ||||
| } | ||||
							
								
								
									
										87
									
								
								esphome/components/api/api_message.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphome/components/api/api_message.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| #include "api_message.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| static const char *TAG = "api.message"; | ||||
|  | ||||
| bool APIMessage::decode_varint(uint32_t field_id, uint32_t value) { return false; } | ||||
| bool APIMessage::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { return false; } | ||||
| bool APIMessage::decode_32bit(uint32_t field_id, uint32_t value) { return false; } | ||||
| void APIMessage::encode(APIBuffer &buffer) {} | ||||
| void APIMessage::decode(const uint8_t *buffer, size_t length) { | ||||
|   uint32_t i = 0; | ||||
|   bool error = false; | ||||
|   while (i < length) { | ||||
|     uint32_t consumed; | ||||
|     auto res = proto_decode_varuint32(&buffer[i], length - i, &consumed); | ||||
|     if (!res.has_value()) { | ||||
|       ESP_LOGV(TAG, "Invalid field start at %u", i); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     uint32_t field_type = (*res) & 0b111; | ||||
|     uint32_t field_id = (*res) >> 3; | ||||
|     i += consumed; | ||||
|  | ||||
|     switch (field_type) { | ||||
|       case 0: {  // VarInt | ||||
|         res = proto_decode_varuint32(&buffer[i], length - i, &consumed); | ||||
|         if (!res.has_value()) { | ||||
|           ESP_LOGV(TAG, "Invalid VarInt at %u", i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         if (!this->decode_varint(field_id, *res)) { | ||||
|           ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, *res); | ||||
|         } | ||||
|         i += consumed; | ||||
|         break; | ||||
|       } | ||||
|       case 2: {  // Length-delimited | ||||
|         res = proto_decode_varuint32(&buffer[i], length - i, &consumed); | ||||
|         if (!res.has_value()) { | ||||
|           ESP_LOGV(TAG, "Invalid Length Delimited at %u", i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         i += consumed; | ||||
|         if (*res > length - i) { | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         if (!this->decode_length_delimited(field_id, &buffer[i], *res)) { | ||||
|           ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id); | ||||
|         } | ||||
|         i += *res; | ||||
|         break; | ||||
|       } | ||||
|       case 5: {  // 32-bit | ||||
|         if (length - i < 4) { | ||||
|           ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i); | ||||
|           error = true; | ||||
|           break; | ||||
|         } | ||||
|         uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) | | ||||
|                        (uint32_t(buffer[i + 3]) << 24); | ||||
|         if (!this->decode_32bit(field_id, val)) { | ||||
|           ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val); | ||||
|         } | ||||
|         i += 4; | ||||
|         break; | ||||
|       } | ||||
|       default: | ||||
|         ESP_LOGV(TAG, "Invalid field type at %u", i); | ||||
|         error = true; | ||||
|         break; | ||||
|     } | ||||
|     if (error) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										79
									
								
								esphome/components/api/api_message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								esphome/components/api/api_message.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "util.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| enum class APIMessageType { | ||||
|   HELLO_REQUEST = 1, | ||||
|   HELLO_RESPONSE = 2, | ||||
|   CONNECT_REQUEST = 3, | ||||
|   CONNECT_RESPONSE = 4, | ||||
|   DISCONNECT_REQUEST = 5, | ||||
|   DISCONNECT_RESPONSE = 6, | ||||
|   PING_REQUEST = 7, | ||||
|   PING_RESPONSE = 8, | ||||
|   DEVICE_INFO_REQUEST = 9, | ||||
|   DEVICE_INFO_RESPONSE = 10, | ||||
|  | ||||
|   LIST_ENTITIES_REQUEST = 11, | ||||
|   LIST_ENTITIES_BINARY_SENSOR_RESPONSE = 12, | ||||
|   LIST_ENTITIES_COVER_RESPONSE = 13, | ||||
|   LIST_ENTITIES_FAN_RESPONSE = 14, | ||||
|   LIST_ENTITIES_LIGHT_RESPONSE = 15, | ||||
|   LIST_ENTITIES_SENSOR_RESPONSE = 16, | ||||
|   LIST_ENTITIES_SWITCH_RESPONSE = 17, | ||||
|   LIST_ENTITIES_TEXT_SENSOR_RESPONSE = 18, | ||||
|   LIST_ENTITIES_SERVICE_RESPONSE = 41, | ||||
|   LIST_ENTITIES_CAMERA_RESPONSE = 43, | ||||
|   LIST_ENTITIES_CLIMATE_RESPONSE = 46, | ||||
|   LIST_ENTITIES_DONE_RESPONSE = 19, | ||||
|  | ||||
|   SUBSCRIBE_STATES_REQUEST = 20, | ||||
|   BINARY_SENSOR_STATE_RESPONSE = 21, | ||||
|   COVER_STATE_RESPONSE = 22, | ||||
|   FAN_STATE_RESPONSE = 23, | ||||
|   LIGHT_STATE_RESPONSE = 24, | ||||
|   SENSOR_STATE_RESPONSE = 25, | ||||
|   SWITCH_STATE_RESPONSE = 26, | ||||
|   TEXT_SENSOR_STATE_RESPONSE = 27, | ||||
|   CAMERA_IMAGE_RESPONSE = 44, | ||||
|   CLIMATE_STATE_RESPONSE = 47, | ||||
|  | ||||
|   SUBSCRIBE_LOGS_REQUEST = 28, | ||||
|   SUBSCRIBE_LOGS_RESPONSE = 29, | ||||
|  | ||||
|   COVER_COMMAND_REQUEST = 30, | ||||
|   FAN_COMMAND_REQUEST = 31, | ||||
|   LIGHT_COMMAND_REQUEST = 32, | ||||
|   SWITCH_COMMAND_REQUEST = 33, | ||||
|   CAMERA_IMAGE_REQUEST = 45, | ||||
|   CLIMATE_COMMAND_REQUEST = 48, | ||||
|  | ||||
|   SUBSCRIBE_SERVICE_CALLS_REQUEST = 34, | ||||
|   SERVICE_CALL_RESPONSE = 35, | ||||
|   GET_TIME_REQUEST = 36, | ||||
|   GET_TIME_RESPONSE = 37, | ||||
|  | ||||
|   SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST = 38, | ||||
|   SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE = 39, | ||||
|   HOME_ASSISTANT_STATE_RESPONSE = 40, | ||||
|  | ||||
|   EXECUTE_SERVICE_REQUEST = 42, | ||||
| }; | ||||
|  | ||||
| class APIMessage { | ||||
|  public: | ||||
|   void decode(const uint8_t *buffer, size_t length); | ||||
|   virtual bool decode_varint(uint32_t field_id, uint32_t value); | ||||
|   virtual bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len); | ||||
|   virtual bool decode_32bit(uint32_t field_id, uint32_t value); | ||||
|   virtual APIMessageType message_type() const = 0; | ||||
|  | ||||
|   virtual void encode(APIBuffer &buffer); | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										1201
									
								
								esphome/components/api/api_server.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1201
									
								
								esphome/components/api/api_server.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										242
									
								
								esphome/components/api/api_server.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								esphome/components/api/api_server.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,242 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/controller.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "util.h" | ||||
| #include "api_message.h" | ||||
| #include "basic_messages.h" | ||||
| #include "list_entities.h" | ||||
| #include "subscribe_state.h" | ||||
| #include "subscribe_logs.h" | ||||
| #include "command_messages.h" | ||||
| #include "service_call_message.h" | ||||
| #include "user_services.h" | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| #include <AsyncTCP.h> | ||||
| #endif | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| #include <ESPAsyncTCP.h> | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class APIServer; | ||||
|  | ||||
| class APIConnection { | ||||
|  public: | ||||
|   APIConnection(AsyncClient *client, APIServer *parent); | ||||
|   ~APIConnection(); | ||||
|  | ||||
|   void disconnect_client(); | ||||
|   APIBuffer get_buffer(); | ||||
|   bool send_buffer(APIMessageType type); | ||||
|   bool send_message(APIMessage &msg); | ||||
|   bool send_empty_message(APIMessageType type); | ||||
|   void loop(); | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state); | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool send_cover_state(cover::Cover *cover); | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool send_fan_state(fan::FanState *fan); | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool send_light_state(light::LightState *light); | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool send_sensor_state(sensor::Sensor *sensor, float state); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool send_switch_state(switch_::Switch *a_switch, bool state); | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state); | ||||
| #endif | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool send_climate_state(climate::Climate *climate); | ||||
| #endif | ||||
|   bool send_log_message(int level, const char *tag, const char *line); | ||||
|   bool send_disconnect_request(); | ||||
|   bool send_ping_request(); | ||||
|   void send_service_call(ServiceCallResponse &call); | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
|   void send_time_request(); | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   friend APIServer; | ||||
|  | ||||
|   void on_error_(int8_t error); | ||||
|   void on_disconnect_(); | ||||
|   void on_timeout_(uint32_t time); | ||||
|   void on_data_(uint8_t *buf, size_t len); | ||||
|   void fatal_error_(); | ||||
|   bool valid_rx_message_type_(uint32_t msg_type); | ||||
|   void read_message_(uint32_t size, uint32_t type, uint8_t *msg); | ||||
|   void parse_recv_buffer_(); | ||||
|  | ||||
|   // request types | ||||
|   void on_hello_request_(const HelloRequest &req); | ||||
|   void on_connect_request_(const ConnectRequest &req); | ||||
|   void on_disconnect_request_(const DisconnectRequest &req); | ||||
|   void on_disconnect_response_(const DisconnectResponse &req); | ||||
|   void on_ping_request_(const PingRequest &req); | ||||
|   void on_ping_response_(const PingResponse &req); | ||||
|   void on_device_info_request_(const DeviceInfoRequest &req); | ||||
|   void on_list_entities_request_(const ListEntitiesRequest &req); | ||||
|   void on_subscribe_states_request_(const SubscribeStatesRequest &req); | ||||
|   void on_subscribe_logs_request_(const SubscribeLogsRequest &req); | ||||
| #ifdef USE_COVER | ||||
|   void on_cover_command_request_(const CoverCommandRequest &req); | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   void on_fan_command_request_(const FanCommandRequest &req); | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   void on_light_command_request_(const LightCommandRequest &req); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   void on_switch_command_request_(const SwitchCommandRequest &req); | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   void on_climate_command_request_(const ClimateCommandRequest &req); | ||||
| #endif | ||||
|   void on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req); | ||||
|   void on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req); | ||||
|   void on_home_assistant_state_response_(const HomeAssistantStateResponse &req); | ||||
|   void on_execute_service_(const ExecuteServiceRequest &req); | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   void on_camera_image_request_(const CameraImageRequest &req); | ||||
| #endif | ||||
|  | ||||
|   enum class ConnectionState { | ||||
|     WAITING_FOR_HELLO, | ||||
|     WAITING_FOR_CONNECT, | ||||
|     CONNECTED, | ||||
|   } connection_state_{ConnectionState::WAITING_FOR_HELLO}; | ||||
|  | ||||
|   bool remove_{false}; | ||||
|  | ||||
|   std::vector<uint8_t> send_buffer_; | ||||
|   std::vector<uint8_t> recv_buffer_; | ||||
|  | ||||
|   std::string client_info_; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   esp32_camera::CameraImageReader image_reader_; | ||||
| #endif | ||||
|  | ||||
|   bool state_subscription_{false}; | ||||
|   int log_subscription_{ESPHOME_LOG_LEVEL_NONE}; | ||||
|   uint32_t last_traffic_; | ||||
|   bool sent_ping_{false}; | ||||
|   bool service_call_subscription_{false}; | ||||
|   AsyncClient *client_; | ||||
|   APIServer *parent_; | ||||
|   InitialStateIterator initial_state_iterator_; | ||||
|   ListEntitiesIterator list_entities_iterator_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class HomeAssistantServiceCallAction; | ||||
|  | ||||
| class APIServer : public Component, public Controller { | ||||
|  public: | ||||
|   APIServer(); | ||||
|   void setup() override; | ||||
|   uint16_t get_port() const; | ||||
|   float get_setup_priority() const override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   void on_shutdown() override; | ||||
|   bool check_password(const std::string &password) const; | ||||
|   bool uses_password() const; | ||||
|   void set_port(uint16_t port); | ||||
|   void set_password(const std::string &password); | ||||
|   void set_reboot_timeout(uint32_t reboot_timeout); | ||||
|   void handle_disconnect(APIConnection *conn); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   void on_cover_update(cover::Cover *obj) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   void on_fan_update(fan::FanState *obj) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   void on_light_update(light::LightState *obj) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   void on_sensor_update(sensor::Sensor *obj, float state) override; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   void on_switch_update(switch_::Switch *obj, bool state) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   void on_climate_update(climate::Climate *obj) override; | ||||
| #endif | ||||
|   void send_service_call(ServiceCallResponse &call); | ||||
|   void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
|   void request_time(); | ||||
| #endif | ||||
|  | ||||
|   bool is_connected() const; | ||||
|  | ||||
|   struct HomeAssistantStateSubscription { | ||||
|     std::string entity_id; | ||||
|     std::function<void(std::string)> callback; | ||||
|   }; | ||||
|  | ||||
|   void subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f); | ||||
|   const std::vector<HomeAssistantStateSubscription> &get_state_subs() const; | ||||
|   const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; } | ||||
|  | ||||
|  protected: | ||||
|   AsyncServer server_{0}; | ||||
|   uint16_t port_{6053}; | ||||
|   uint32_t reboot_timeout_{300000}; | ||||
|   uint32_t last_connected_{0}; | ||||
|   std::vector<APIConnection *> clients_; | ||||
|   std::string password_; | ||||
|   std::vector<HomeAssistantStateSubscription> state_subs_; | ||||
|   std::vector<UserServiceDescriptor *> user_services_; | ||||
| }; | ||||
|  | ||||
| extern APIServer *global_api_server; | ||||
|  | ||||
| template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit HomeAssistantServiceCallAction(APIServer *parent) : parent_(parent) {} | ||||
|   void set_service(const std::string &service) { this->resp_.set_service(service); } | ||||
|   void set_data(const std::vector<KeyValuePair> &data) { this->resp_.set_data(data); } | ||||
|   void set_data_template(const std::vector<KeyValuePair> &data_template) { | ||||
|     this->resp_.set_data_template(data_template); | ||||
|   } | ||||
|   void set_variables(const std::vector<TemplatableKeyValuePair> &variables) { this->resp_.set_variables(variables); } | ||||
|   void play(Ts... x) override { this->parent_->send_service_call(this->resp_); } | ||||
|  | ||||
|  protected: | ||||
|   APIServer *parent_; | ||||
|   ServiceCallResponse resp_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> { | ||||
|  public: | ||||
|   bool check(Ts... x) override { return global_api_server->is_connected(); } | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										57
									
								
								esphome/components/api/basic_messages.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/api/basic_messages.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| #include "basic_messages.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| // Hello | ||||
| bool HelloRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // string client_info = 1; | ||||
|       this->client_info_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| const std::string &HelloRequest::get_client_info() const { return this->client_info_; } | ||||
| void HelloRequest::set_client_info(const std::string &client_info) { this->client_info_ = client_info; } | ||||
| APIMessageType HelloRequest::message_type() const { return APIMessageType::HELLO_REQUEST; } | ||||
|  | ||||
| // Connect | ||||
| bool ConnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // string password = 1; | ||||
|       this->password_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| const std::string &ConnectRequest::get_password() const { return this->password_; } | ||||
| void ConnectRequest::set_password(const std::string &password) { this->password_ = password; } | ||||
| APIMessageType ConnectRequest::message_type() const { return APIMessageType::CONNECT_REQUEST; } | ||||
|  | ||||
| APIMessageType DeviceInfoRequest::message_type() const { return APIMessageType::DEVICE_INFO_REQUEST; } | ||||
| APIMessageType DisconnectRequest::message_type() const { return APIMessageType::DISCONNECT_REQUEST; } | ||||
| bool DisconnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // string reason = 1; | ||||
|       this->reason_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| const std::string &DisconnectRequest::get_reason() const { return this->reason_; } | ||||
| void DisconnectRequest::set_reason(const std::string &reason) { this->reason_ = reason; } | ||||
| void DisconnectRequest::encode(APIBuffer &buffer) { | ||||
|   // string reason = 1; | ||||
|   buffer.encode_string(1, this->reason_); | ||||
| } | ||||
| APIMessageType DisconnectResponse::message_type() const { return APIMessageType::DISCONNECT_RESPONSE; } | ||||
| APIMessageType PingRequest::message_type() const { return APIMessageType::PING_REQUEST; } | ||||
| APIMessageType PingResponse::message_type() const { return APIMessageType::PING_RESPONSE; } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										63
									
								
								esphome/components/api/basic_messages.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/api/basic_messages.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class HelloRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   const std::string &get_client_info() const; | ||||
|   void set_client_info(const std::string &client_info); | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|  protected: | ||||
|   std::string client_info_; | ||||
| }; | ||||
|  | ||||
| class ConnectRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   const std::string &get_password() const; | ||||
|   void set_password(const std::string &password); | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|  protected: | ||||
|   std::string password_; | ||||
| }; | ||||
|  | ||||
| class DeviceInfoRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class DisconnectRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   void encode(APIBuffer &buffer) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   const std::string &get_reason() const; | ||||
|   void set_reason(const std::string &reason); | ||||
|  | ||||
|  protected: | ||||
|   std::string reason_; | ||||
| }; | ||||
|  | ||||
| class DisconnectResponse : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class PingRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class PingResponse : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										417
									
								
								esphome/components/api/command_messages.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								esphome/components/api/command_messages.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,417 @@ | ||||
| #include "command_messages.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| #ifdef USE_COVER | ||||
| bool CoverCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool has_legacy_command = 2; | ||||
|       this->has_legacy_command_ = value; | ||||
|       return true; | ||||
|     case 3: | ||||
|       // enum LegacyCoverCommand { | ||||
|       //   OPEN = 0; | ||||
|       //   CLOSE = 1; | ||||
|       //   STOP = 2; | ||||
|       // } | ||||
|       // LegacyCoverCommand legacy_command_ = 3; | ||||
|       this->legacy_command_ = static_cast<LegacyCoverCommand>(value); | ||||
|       return true; | ||||
|     case 4: | ||||
|       // bool has_position = 4; | ||||
|       this->has_position_ = value; | ||||
|       return true; | ||||
|     case 6: | ||||
|       // bool has_tilt = 6; | ||||
|       this->has_tilt_ = value; | ||||
|       return true; | ||||
|     case 8: | ||||
|       // bool stop = 8; | ||||
|       this->stop_ = value; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool CoverCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     case 5: | ||||
|       // float position = 5; | ||||
|       this->position_ = as_float(value); | ||||
|       return true; | ||||
|     case 7: | ||||
|       // float tilt = 7; | ||||
|       this->tilt_ = as_float(value); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType CoverCommandRequest::message_type() const { return APIMessageType ::COVER_COMMAND_REQUEST; } | ||||
| uint32_t CoverCommandRequest::get_key() const { return this->key_; } | ||||
| optional<LegacyCoverCommand> CoverCommandRequest::get_legacy_command() const { | ||||
|   if (!this->has_legacy_command_) | ||||
|     return {}; | ||||
|   return this->legacy_command_; | ||||
| } | ||||
| optional<float> CoverCommandRequest::get_position() const { | ||||
|   if (!this->has_position_) | ||||
|     return {}; | ||||
|   return this->position_; | ||||
| } | ||||
| optional<float> CoverCommandRequest::get_tilt() const { | ||||
|   if (!this->has_tilt_) | ||||
|     return {}; | ||||
|   return this->tilt_; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
| bool FanCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool has_state = 2; | ||||
|       this->has_state_ = value; | ||||
|       return true; | ||||
|     case 3: | ||||
|       // bool state = 3; | ||||
|       this->state_ = value; | ||||
|       return true; | ||||
|     case 4: | ||||
|       // bool has_speed = 4; | ||||
|       this->has_speed_ = value; | ||||
|       return true; | ||||
|     case 5: | ||||
|       // FanSpeed speed = 5; | ||||
|       this->speed_ = static_cast<fan::FanSpeed>(value); | ||||
|       return true; | ||||
|     case 6: | ||||
|       // bool has_oscillating = 6; | ||||
|       this->has_oscillating_ = value; | ||||
|       return true; | ||||
|     case 7: | ||||
|       // bool oscillating = 7; | ||||
|       this->oscillating_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool FanCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType FanCommandRequest::message_type() const { return APIMessageType::FAN_COMMAND_REQUEST; } | ||||
| uint32_t FanCommandRequest::get_key() const { return this->key_; } | ||||
| optional<bool> FanCommandRequest::get_state() const { | ||||
|   if (!this->has_state_) | ||||
|     return {}; | ||||
|   return this->state_; | ||||
| } | ||||
| optional<fan::FanSpeed> FanCommandRequest::get_speed() const { | ||||
|   if (!this->has_speed_) | ||||
|     return {}; | ||||
|   return this->speed_; | ||||
| } | ||||
| optional<bool> FanCommandRequest::get_oscillating() const { | ||||
|   if (!this->has_oscillating_) | ||||
|     return {}; | ||||
|   return this->oscillating_; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_LIGHT | ||||
| bool LightCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool has_state = 2; | ||||
|       this->has_state_ = value; | ||||
|       return true; | ||||
|     case 3: | ||||
|       // bool state = 3; | ||||
|       this->state_ = value; | ||||
|       return true; | ||||
|     case 4: | ||||
|       // bool has_brightness = 4; | ||||
|       this->has_brightness_ = value; | ||||
|       return true; | ||||
|     case 6: | ||||
|       // bool has_rgb = 6; | ||||
|       this->has_rgb_ = value; | ||||
|       return true; | ||||
|     case 10: | ||||
|       // bool has_white = 10; | ||||
|       this->has_white_ = value; | ||||
|       return true; | ||||
|     case 12: | ||||
|       // bool has_color_temperature = 12; | ||||
|       this->has_color_temperature_ = value; | ||||
|       return true; | ||||
|     case 14: | ||||
|       // bool has_transition_length = 14; | ||||
|       this->has_transition_length_ = value; | ||||
|       return true; | ||||
|     case 15: | ||||
|       // uint32 transition_length = 15; | ||||
|       this->transition_length_ = value; | ||||
|       return true; | ||||
|     case 16: | ||||
|       // bool has_flash_length = 16; | ||||
|       this->has_flash_length_ = value; | ||||
|       return true; | ||||
|     case 17: | ||||
|       // uint32 flash_length = 17; | ||||
|       this->flash_length_ = value; | ||||
|       return true; | ||||
|     case 18: | ||||
|       // bool has_effect = 18; | ||||
|       this->has_effect_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool LightCommandRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 19: | ||||
|       // string effect = 19; | ||||
|       this->effect_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool LightCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     case 5: | ||||
|       // float brightness = 5; | ||||
|       this->brightness_ = as_float(value); | ||||
|       return true; | ||||
|     case 7: | ||||
|       // float red = 7; | ||||
|       this->red_ = as_float(value); | ||||
|       return true; | ||||
|     case 8: | ||||
|       // float green = 8; | ||||
|       this->green_ = as_float(value); | ||||
|       return true; | ||||
|     case 9: | ||||
|       // float blue = 9; | ||||
|       this->blue_ = as_float(value); | ||||
|       return true; | ||||
|     case 11: | ||||
|       // float white = 11; | ||||
|       this->white_ = as_float(value); | ||||
|       return true; | ||||
|     case 13: | ||||
|       // float color_temperature = 13; | ||||
|       this->color_temperature_ = as_float(value); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType LightCommandRequest::message_type() const { return APIMessageType::LIGHT_COMMAND_REQUEST; } | ||||
| uint32_t LightCommandRequest::get_key() const { return this->key_; } | ||||
| optional<bool> LightCommandRequest::get_state() const { | ||||
|   if (!this->has_state_) | ||||
|     return {}; | ||||
|   return this->state_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_brightness() const { | ||||
|   if (!this->has_brightness_) | ||||
|     return {}; | ||||
|   return this->brightness_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_red() const { | ||||
|   if (!this->has_rgb_) | ||||
|     return {}; | ||||
|   return this->red_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_green() const { | ||||
|   if (!this->has_rgb_) | ||||
|     return {}; | ||||
|   return this->green_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_blue() const { | ||||
|   if (!this->has_rgb_) | ||||
|     return {}; | ||||
|   return this->blue_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_white() const { | ||||
|   if (!this->has_white_) | ||||
|     return {}; | ||||
|   return this->white_; | ||||
| } | ||||
| optional<float> LightCommandRequest::get_color_temperature() const { | ||||
|   if (!this->has_color_temperature_) | ||||
|     return {}; | ||||
|   return this->color_temperature_; | ||||
| } | ||||
| optional<uint32_t> LightCommandRequest::get_transition_length() const { | ||||
|   if (!this->has_transition_length_) | ||||
|     return {}; | ||||
|   return this->transition_length_; | ||||
| } | ||||
| optional<uint32_t> LightCommandRequest::get_flash_length() const { | ||||
|   if (!this->has_flash_length_) | ||||
|     return {}; | ||||
|   return this->flash_length_; | ||||
| } | ||||
| optional<std::string> LightCommandRequest::get_effect() const { | ||||
|   if (!this->has_effect_) | ||||
|     return {}; | ||||
|   return this->effect_; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_SWITCH | ||||
| bool SwitchCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool state = 2; | ||||
|       this->state_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool SwitchCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType SwitchCommandRequest::message_type() const { return APIMessageType::SWITCH_COMMAND_REQUEST; } | ||||
| uint32_t SwitchCommandRequest::get_key() const { return this->key_; } | ||||
| bool SwitchCommandRequest::get_state() const { return this->state_; } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool CameraImageRequest::get_single() const { return this->single_; } | ||||
| bool CameraImageRequest::get_stream() const { return this->stream_; } | ||||
| bool CameraImageRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // bool single = 1; | ||||
|       this->single_ = value; | ||||
|       return true; | ||||
|     case 2: | ||||
|       // bool stream = 2; | ||||
|       this->stream_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType CameraImageRequest::message_type() const { return APIMessageType::CAMERA_IMAGE_REQUEST; } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_CLIMATE | ||||
| bool ClimateCommandRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 2: | ||||
|       // bool has_mode = 2; | ||||
|       this->has_mode_ = value; | ||||
|       return true; | ||||
|     case 3: | ||||
|       // ClimateMode mode = 3; | ||||
|       this->mode_ = static_cast<climate::ClimateMode>(value); | ||||
|       return true; | ||||
|     case 4: | ||||
|       // bool has_target_temperature = 4; | ||||
|       this->has_target_temperature_ = value; | ||||
|       return true; | ||||
|     case 6: | ||||
|       // bool has_target_temperature_low = 6; | ||||
|       this->has_target_temperature_low_ = value; | ||||
|       return true; | ||||
|     case 8: | ||||
|       // bool has_target_temperature_high = 8; | ||||
|       this->has_target_temperature_high_ = value; | ||||
|       return true; | ||||
|     case 10: | ||||
|       // bool has_away = 10; | ||||
|       this->has_away_ = value; | ||||
|       return true; | ||||
|     case 11: | ||||
|       // bool away = 11; | ||||
|       this->away_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool ClimateCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     case 5: | ||||
|       // float target_temperature = 5; | ||||
|       this->target_temperature_ = as_float(value); | ||||
|       return true; | ||||
|     case 7: | ||||
|       // float target_temperature_low = 7; | ||||
|       this->target_temperature_low_ = as_float(value); | ||||
|       return true; | ||||
|     case 9: | ||||
|       // float target_temperature_high = 9; | ||||
|       this->target_temperature_high_ = as_float(value); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType ClimateCommandRequest::message_type() const { return APIMessageType::CLIMATE_COMMAND_REQUEST; } | ||||
| uint32_t ClimateCommandRequest::get_key() const { return this->key_; } | ||||
| optional<climate::ClimateMode> ClimateCommandRequest::get_mode() const { | ||||
|   if (!this->has_mode_) | ||||
|     return {}; | ||||
|   return this->mode_; | ||||
| } | ||||
| optional<float> ClimateCommandRequest::get_target_temperature() const { | ||||
|   if (!this->has_target_temperature_) | ||||
|     return {}; | ||||
|   return this->target_temperature_; | ||||
| } | ||||
| optional<float> ClimateCommandRequest::get_target_temperature_low() const { | ||||
|   if (!this->has_target_temperature_low_) | ||||
|     return {}; | ||||
|   return this->target_temperature_low_; | ||||
| } | ||||
| optional<float> ClimateCommandRequest::get_target_temperature_high() const { | ||||
|   if (!this->has_target_temperature_high_) | ||||
|     return {}; | ||||
|   return this->target_temperature_high_; | ||||
| } | ||||
| optional<bool> ClimateCommandRequest::get_away() const { | ||||
|   if (!this->has_away_) | ||||
|     return {}; | ||||
|   return this->away_; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										162
									
								
								esphome/components/api/command_messages.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								esphome/components/api/command_messages.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| #ifdef USE_COVER | ||||
| enum LegacyCoverCommand { | ||||
|   LEGACY_COVER_COMMAND_OPEN = 0, | ||||
|   LEGACY_COVER_COMMAND_CLOSE = 1, | ||||
|   LEGACY_COVER_COMMAND_STOP = 2, | ||||
| }; | ||||
|  | ||||
| class CoverCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   optional<LegacyCoverCommand> get_legacy_command() const; | ||||
|   optional<float> get_position() const; | ||||
|   optional<float> get_tilt() const; | ||||
|   bool get_stop() const { return this->stop_; } | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool has_legacy_command_{false}; | ||||
|   LegacyCoverCommand legacy_command_{LEGACY_COVER_COMMAND_OPEN}; | ||||
|   bool has_position_{false}; | ||||
|   float position_{0.0f}; | ||||
|   bool has_tilt_{false}; | ||||
|   float tilt_{0.0f}; | ||||
|   bool stop_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
| class FanCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   optional<bool> get_state() const; | ||||
|   optional<fan::FanSpeed> get_speed() const; | ||||
|   optional<bool> get_oscillating() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool has_state_{false}; | ||||
|   bool state_{false}; | ||||
|   bool has_speed_{false}; | ||||
|   fan::FanSpeed speed_{fan::FAN_SPEED_LOW}; | ||||
|   bool has_oscillating_{false}; | ||||
|   bool oscillating_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_LIGHT | ||||
| class LightCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   optional<bool> get_state() const; | ||||
|   optional<float> get_brightness() const; | ||||
|   optional<float> get_red() const; | ||||
|   optional<float> get_green() const; | ||||
|   optional<float> get_blue() const; | ||||
|   optional<float> get_white() const; | ||||
|   optional<float> get_color_temperature() const; | ||||
|   optional<uint32_t> get_transition_length() const; | ||||
|   optional<uint32_t> get_flash_length() const; | ||||
|   optional<std::string> get_effect() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool has_state_{false}; | ||||
|   bool state_{false}; | ||||
|   bool has_brightness_{false}; | ||||
|   float brightness_{0.0f}; | ||||
|   bool has_rgb_{false}; | ||||
|   float red_{0.0f}; | ||||
|   float green_{0.0f}; | ||||
|   float blue_{0.0f}; | ||||
|   bool has_white_{false}; | ||||
|   float white_{0.0f}; | ||||
|   bool has_color_temperature_{false}; | ||||
|   float color_temperature_{0.0f}; | ||||
|   bool has_transition_length_{false}; | ||||
|   uint32_t transition_length_{0}; | ||||
|   bool has_flash_length_{false}; | ||||
|   uint32_t flash_length_{0}; | ||||
|   bool has_effect_{false}; | ||||
|   std::string effect_{}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_SWITCH | ||||
| class SwitchCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   bool get_state() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool state_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| class CameraImageRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool get_single() const; | ||||
|   bool get_stream() const; | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|  protected: | ||||
|   bool single_{false}; | ||||
|   bool stream_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_CLIMATE | ||||
| class ClimateCommandRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_key() const; | ||||
|   optional<climate::ClimateMode> get_mode() const; | ||||
|   optional<float> get_target_temperature() const; | ||||
|   optional<float> get_target_temperature_low() const; | ||||
|   optional<float> get_target_temperature_high() const; | ||||
|   optional<bool> get_away() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_{0}; | ||||
|   bool has_mode_{false}; | ||||
|   climate::ClimateMode mode_{climate::CLIMATE_MODE_OFF}; | ||||
|   bool has_target_temperature_{false}; | ||||
|   float target_temperature_{0.0f}; | ||||
|   bool has_target_temperature_low_{false}; | ||||
|   float target_temperature_low_{0.0f}; | ||||
|   bool has_target_temperature_high_{false}; | ||||
|   float target_temperature_high_{0.0f}; | ||||
|   bool has_away_{false}; | ||||
|   bool away_{false}; | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										190
									
								
								esphome/components/api/list_entities.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								esphome/components/api/list_entities.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| #include "list_entities.h" | ||||
| #include "esphome/core/util.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) { | ||||
|   return App.get_name() + component_type + nameable->get_object_id(); | ||||
| } | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(binary_sensor); | ||||
|   // string unique_id = 4; | ||||
|   buffer.encode_string(4, get_default_unique_id("binary_sensor", binary_sensor)); | ||||
|   // string device_class = 5; | ||||
|   buffer.encode_string(5, binary_sensor->get_device_class()); | ||||
|   // bool is_status_binary_sensor = 6; | ||||
|   buffer.encode_bool(6, binary_sensor->is_status_binary_sensor()); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool ListEntitiesIterator::on_cover(cover::Cover *cover) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(cover); | ||||
|   // string unique_id = 4; | ||||
|   buffer.encode_string(4, get_default_unique_id("cover", cover)); | ||||
|   auto traits = cover->get_traits(); | ||||
|  | ||||
|   // bool assumed_state = 5; | ||||
|   buffer.encode_bool(5, traits.get_is_assumed_state()); | ||||
|   // bool supports_position = 6; | ||||
|   buffer.encode_bool(6, traits.get_supports_position()); | ||||
|   // bool supports_tilt = 7; | ||||
|   buffer.encode_bool(7, traits.get_supports_tilt()); | ||||
|   // string device_class = 8; | ||||
|   buffer.encode_string(8, cover->get_device_class()); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_COVER_RESPONSE); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool ListEntitiesIterator::on_fan(fan::FanState *fan) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(fan); | ||||
|   // string unique_id = 4; | ||||
|   buffer.encode_string(4, get_default_unique_id("fan", fan)); | ||||
|   // bool supports_oscillation = 5; | ||||
|   buffer.encode_bool(5, fan->get_traits().supports_oscillation()); | ||||
|   // bool supports_speed = 6; | ||||
|   buffer.encode_bool(6, fan->get_traits().supports_speed()); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_FAN_RESPONSE); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool ListEntitiesIterator::on_light(light::LightState *light) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(light); | ||||
|   // string unique_id = 4; | ||||
|   buffer.encode_string(4, get_default_unique_id("light", light)); | ||||
|   // bool supports_brightness = 5; | ||||
|   auto traits = light->get_traits(); | ||||
|   buffer.encode_bool(5, traits.get_supports_brightness()); | ||||
|   // bool supports_rgb = 6; | ||||
|   buffer.encode_bool(6, traits.get_supports_rgb()); | ||||
|   // bool supports_white_value = 7; | ||||
|   buffer.encode_bool(7, traits.get_supports_rgb_white_value()); | ||||
|   // bool supports_color_temperature = 8; | ||||
|   buffer.encode_bool(8, traits.get_supports_color_temperature()); | ||||
|   if (traits.get_supports_color_temperature()) { | ||||
|     // float min_mireds = 9; | ||||
|     buffer.encode_float(9, traits.get_min_mireds()); | ||||
|     // float max_mireds = 10; | ||||
|     buffer.encode_float(10, traits.get_max_mireds()); | ||||
|   } | ||||
|   // repeated string effects = 11; | ||||
|   if (light->supports_effects()) { | ||||
|     buffer.encode_string(11, "None"); | ||||
|     for (auto *effect : light->get_effects()) { | ||||
|       buffer.encode_string(11, effect->get_name()); | ||||
|     } | ||||
|   } | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(sensor); | ||||
|   // string unique_id = 4; | ||||
|   std::string unique_id = sensor->unique_id(); | ||||
|   if (unique_id.empty()) | ||||
|     unique_id = get_default_unique_id("sensor", sensor); | ||||
|   buffer.encode_string(4, unique_id); | ||||
|   // string icon = 5; | ||||
|   buffer.encode_string(5, sensor->get_icon()); | ||||
|   // string unit_of_measurement = 6; | ||||
|   buffer.encode_string(6, sensor->get_unit_of_measurement()); | ||||
|   // int32 accuracy_decimals = 7; | ||||
|   buffer.encode_int32(7, sensor->get_accuracy_decimals()); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(a_switch); | ||||
|   // string unique_id = 4; | ||||
|   buffer.encode_string(4, get_default_unique_id("switch", a_switch)); | ||||
|   // string icon = 5; | ||||
|   buffer.encode_string(5, a_switch->get_icon()); | ||||
|   // bool assumed_state = 6; | ||||
|   buffer.encode_bool(6, a_switch->assumed_state()); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(text_sensor); | ||||
|   // string unique_id = 4; | ||||
|   std::string unique_id = text_sensor->unique_id(); | ||||
|   if (unique_id.empty()) | ||||
|     unique_id = get_default_unique_id("text_sensor", text_sensor); | ||||
|   buffer.encode_string(4, unique_id); | ||||
|   // string icon = 5; | ||||
|   buffer.encode_string(5, text_sensor->get_icon()); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| bool ListEntitiesIterator::on_end() { | ||||
|   return this->client_->send_empty_message(APIMessageType::LIST_ENTITIES_DONE_RESPONSE); | ||||
| } | ||||
| ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) | ||||
|     : ComponentIterator(server), client_(client) {} | ||||
| bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   service->encode_list_service_response(buffer); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE); | ||||
| } | ||||
|  | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(camera); | ||||
|   // string unique_id = 4; | ||||
|   buffer.encode_string(4, get_default_unique_id("camera", camera)); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_CLIMATE | ||||
| bool ListEntitiesIterator::on_climate(climate::Climate *climate) { | ||||
|   auto buffer = this->client_->get_buffer(); | ||||
|   buffer.encode_nameable(climate); | ||||
|   // string unique_id = 4; | ||||
|   buffer.encode_string(4, get_default_unique_id("climate", climate)); | ||||
|  | ||||
|   auto traits = climate->get_traits(); | ||||
|   // bool supports_current_temperature = 5; | ||||
|   buffer.encode_bool(5, traits.get_supports_current_temperature()); | ||||
|   // bool supports_two_point_target_temperature = 6; | ||||
|   buffer.encode_bool(6, traits.get_supports_two_point_target_temperature()); | ||||
|   // repeated ClimateMode supported_modes = 7; | ||||
|   for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, | ||||
|                     climate::CLIMATE_MODE_HEAT}) { | ||||
|     if (traits.supports_mode(mode)) | ||||
|       buffer.encode_uint32(7, mode, true); | ||||
|   } | ||||
|  | ||||
|   // float visual_min_temperature = 8; | ||||
|   buffer.encode_float(8, traits.get_visual_min_temperature()); | ||||
|   // float visual_max_temperature = 9; | ||||
|   buffer.encode_float(9, traits.get_visual_max_temperature()); | ||||
|   // float visual_temperature_step = 10; | ||||
|   buffer.encode_float(10, traits.get_visual_temperature_step()); | ||||
|   // bool supports_away = 11; | ||||
|   buffer.encode_bool(11, traits.get_supports_away()); | ||||
|   return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| APIMessageType ListEntitiesRequest::message_type() const { return APIMessageType::LIST_ENTITIES_REQUEST; } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										57
									
								
								esphome/components/api/list_entities.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/api/list_entities.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class ListEntitiesRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class APIConnection; | ||||
|  | ||||
| class ListEntitiesIterator : public ComponentIterator { | ||||
|  public: | ||||
|   ListEntitiesIterator(APIServer *server, APIConnection *client); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool on_cover(cover::Cover *cover) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool on_fan(fan::FanState *fan) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool on_light(light::LightState *light) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool on_sensor(sensor::Sensor *sensor) override; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool on_switch(switch_::Switch *a_switch) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||
| #endif | ||||
|   bool on_service(UserServiceDescriptor *service) override; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   bool on_camera(esp32_camera::ESP32Camera *camera) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool on_climate(climate::Climate *climate) override; | ||||
| #endif | ||||
|   bool on_end() override; | ||||
|  | ||||
|  protected: | ||||
|   APIConnection *client_; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|  | ||||
| #include "api_server.h" | ||||
							
								
								
									
										49
									
								
								esphome/components/api/service_call_message.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								esphome/components/api/service_call_message.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| #include "service_call_message.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| APIMessageType SubscribeServiceCallsRequest::message_type() const { | ||||
|   return APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST; | ||||
| } | ||||
|  | ||||
| APIMessageType ServiceCallResponse::message_type() const { return APIMessageType::SERVICE_CALL_RESPONSE; } | ||||
| void ServiceCallResponse::encode(APIBuffer &buffer) { | ||||
|   // string service = 1; | ||||
|   buffer.encode_string(1, this->service_); | ||||
|   // map<string, string> data = 2; | ||||
|   for (auto &it : this->data_) { | ||||
|     auto nested = buffer.begin_nested(2); | ||||
|     buffer.encode_string(1, it.key); | ||||
|     buffer.encode_string(2, it.value); | ||||
|     buffer.end_nested(nested); | ||||
|   } | ||||
|   // map<string, string> data_template = 3; | ||||
|   for (auto &it : this->data_template_) { | ||||
|     auto nested = buffer.begin_nested(3); | ||||
|     buffer.encode_string(1, it.key); | ||||
|     buffer.encode_string(2, it.value); | ||||
|     buffer.end_nested(nested); | ||||
|   } | ||||
|   // map<string, string> variables = 4; | ||||
|   for (auto &it : this->variables_) { | ||||
|     auto nested = buffer.begin_nested(4); | ||||
|     buffer.encode_string(1, it.key); | ||||
|     buffer.encode_string(2, it.value()); | ||||
|     buffer.end_nested(nested); | ||||
|   } | ||||
| } | ||||
| void ServiceCallResponse::set_service(const std::string &service) { this->service_ = service; } | ||||
| void ServiceCallResponse::set_data(const std::vector<KeyValuePair> &data) { this->data_ = data; } | ||||
| void ServiceCallResponse::set_data_template(const std::vector<KeyValuePair> &data_template) { | ||||
|   this->data_template_ = data_template; | ||||
| } | ||||
| void ServiceCallResponse::set_variables(const std::vector<TemplatableKeyValuePair> &variables) { | ||||
|   this->variables_ = variables; | ||||
| } | ||||
|  | ||||
| KeyValuePair::KeyValuePair(const std::string &key, const std::string &value) : key(key), value(value) {} | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										53
									
								
								esphome/components/api/service_call_message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								esphome/components/api/service_call_message.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class SubscribeServiceCallsRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class KeyValuePair { | ||||
|  public: | ||||
|   KeyValuePair(const std::string &key, const std::string &value); | ||||
|  | ||||
|   std::string key; | ||||
|   std::string value; | ||||
| }; | ||||
|  | ||||
| class TemplatableKeyValuePair { | ||||
|  public: | ||||
|   template<typename T> TemplatableKeyValuePair(std::string key, T func); | ||||
|  | ||||
|   std::string key; | ||||
|   std::function<std::string()> value; | ||||
| }; | ||||
| template<typename T> TemplatableKeyValuePair::TemplatableKeyValuePair(std::string key, T func) : key(key) { | ||||
|   this->value = [func]() -> std::string { return to_string(func()); }; | ||||
| } | ||||
|  | ||||
| class ServiceCallResponse : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|   void encode(APIBuffer &buffer) override; | ||||
|  | ||||
|   void set_service(const std::string &service); | ||||
|   void set_data(const std::vector<KeyValuePair> &data); | ||||
|   void set_data_template(const std::vector<KeyValuePair> &data_template); | ||||
|   void set_variables(const std::vector<TemplatableKeyValuePair> &variables); | ||||
|  | ||||
|  protected: | ||||
|   std::string service_; | ||||
|   std::vector<KeyValuePair> data_; | ||||
|   std::vector<KeyValuePair> data_template_; | ||||
|   std::vector<TemplatableKeyValuePair> variables_; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										26
									
								
								esphome/components/api/subscribe_logs.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/api/subscribe_logs.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| #include "subscribe_logs.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| APIMessageType SubscribeLogsRequest::message_type() const { return APIMessageType::SUBSCRIBE_LOGS_REQUEST; } | ||||
| bool SubscribeLogsRequest::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // LogLevel level = 1; | ||||
|       this->level_ = value; | ||||
|       return true; | ||||
|     case 2:  // bool dump_config = 2; | ||||
|       this->dump_config_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| uint32_t SubscribeLogsRequest::get_level() const { return this->level_; } | ||||
| void SubscribeLogsRequest::set_level(uint32_t level) { this->level_ = level; } | ||||
| bool SubscribeLogsRequest::get_dump_config() const { return this->dump_config_; } | ||||
| void SubscribeLogsRequest::set_dump_config(bool dump_config) { this->dump_config_ = dump_config; } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										24
									
								
								esphome/components/api/subscribe_logs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/api/subscribe_logs.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class SubscribeLogsRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   uint32_t get_level() const; | ||||
|   void set_level(uint32_t level); | ||||
|   bool get_dump_config() const; | ||||
|   void set_dump_config(bool dump_config); | ||||
|  | ||||
|  protected: | ||||
|   uint32_t level_{6}; | ||||
|   bool dump_config_{false}; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										77
									
								
								esphome/components/api/subscribe_state.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/api/subscribe_state.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| #include "subscribe_state.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { | ||||
|   if (!binary_sensor->has_state()) | ||||
|     return true; | ||||
|  | ||||
|   return this->client_->send_binary_sensor_state(binary_sensor, binary_sensor->state); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
| bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool InitialStateIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_state(fan); } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); } | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) { | ||||
|   if (!sensor->has_state()) | ||||
|     return true; | ||||
|  | ||||
|   return this->client_->send_sensor_state(sensor, sensor->state); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| bool InitialStateIterator::on_switch(switch_::Switch *a_switch) { | ||||
|   return this->client_->send_switch_state(a_switch, a_switch->state); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||
|   if (!text_sensor->has_state()) | ||||
|     return true; | ||||
|  | ||||
|   return this->client_->send_text_sensor_state(text_sensor, text_sensor->state); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
| bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); } | ||||
| #endif | ||||
| InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) | ||||
|     : ComponentIterator(server), client_(client) {} | ||||
|  | ||||
| APIMessageType SubscribeStatesRequest::message_type() const { return APIMessageType::SUBSCRIBE_STATES_REQUEST; } | ||||
|  | ||||
| bool HomeAssistantStateResponse::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 1: | ||||
|       // string entity_id = 1; | ||||
|       this->entity_id_ = as_string(value, len); | ||||
|       return true; | ||||
|     case 2: | ||||
|       // string state = 2; | ||||
|       this->state_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType HomeAssistantStateResponse::message_type() const { | ||||
|   return APIMessageType::HOME_ASSISTANT_STATE_RESPONSE; | ||||
| } | ||||
| const std::string &HomeAssistantStateResponse::get_entity_id() const { return this->entity_id_; } | ||||
| const std::string &HomeAssistantStateResponse::get_state() const { return this->state_; } | ||||
| APIMessageType SubscribeHomeAssistantStatesRequest::message_type() const { | ||||
|   return APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST; | ||||
| } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										70
									
								
								esphome/components/api/subscribe_state.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/api/subscribe_state.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/controller.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "util.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class SubscribeStatesRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class APIConnection; | ||||
|  | ||||
| class InitialStateIterator : public ComponentIterator { | ||||
|  public: | ||||
|   InitialStateIterator(APIServer *server, APIConnection *client); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   bool on_cover(cover::Cover *cover) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool on_fan(fan::FanState *fan) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool on_light(light::LightState *light) override; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   bool on_sensor(sensor::Sensor *sensor) override; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   bool on_switch(switch_::Switch *a_switch) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   bool on_climate(climate::Climate *climate) override; | ||||
| #endif | ||||
|  protected: | ||||
|   APIConnection *client_; | ||||
| }; | ||||
|  | ||||
| class SubscribeHomeAssistantStatesRequest : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
| }; | ||||
|  | ||||
| class HomeAssistantStateResponse : public APIMessage { | ||||
|  public: | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   APIMessageType message_type() const override; | ||||
|   const std::string &get_entity_id() const; | ||||
|   const std::string &get_state() const; | ||||
|  | ||||
|  protected: | ||||
|   std::string entity_id_; | ||||
|   std::string state_; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|  | ||||
| #include "api_server.h" | ||||
							
								
								
									
										74
									
								
								esphome/components/api/user_services.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphome/components/api/user_services.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| #include "user_services.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| template<> bool ExecuteServiceArgument::get_value<bool>() { return this->value_bool_; } | ||||
| template<> int ExecuteServiceArgument::get_value<int>() { return this->value_int_; } | ||||
| template<> float ExecuteServiceArgument::get_value<float>() { return this->value_float_; } | ||||
| template<> std::string ExecuteServiceArgument::get_value<std::string>() { return this->value_string_; } | ||||
|  | ||||
| APIMessageType ExecuteServiceArgument::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; } | ||||
| bool ExecuteServiceArgument::decode_varint(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // bool bool_ = 1; | ||||
|       this->value_bool_ = value; | ||||
|       return true; | ||||
|     case 2:  // int32 int_ = 2; | ||||
|       this->value_int_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool ExecuteServiceArgument::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 3:  // float float_ = 3; | ||||
|       this->value_float_ = as_float(value); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool ExecuteServiceArgument::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 4:  // string string_ = 4; | ||||
|       this->value_string_ = as_string(value, len); | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool ExecuteServiceRequest::decode_32bit(uint32_t field_id, uint32_t value) { | ||||
|   switch (field_id) { | ||||
|     case 1:  // fixed32 key = 1; | ||||
|       this->key_ = value; | ||||
|       return true; | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| bool ExecuteServiceRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { | ||||
|   switch (field_id) { | ||||
|     case 2: {  // repeated ExecuteServiceArgument args = 2; | ||||
|       ExecuteServiceArgument arg; | ||||
|       arg.decode(value, len); | ||||
|       this->args_.push_back(arg); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| APIMessageType ExecuteServiceRequest::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; } | ||||
| const std::vector<ExecuteServiceArgument> &ExecuteServiceRequest::get_args() const { return this->args_; } | ||||
| uint32_t ExecuteServiceRequest::get_key() const { return this->key_; } | ||||
|  | ||||
| ServiceTypeArgument::ServiceTypeArgument(const std::string &name, ServiceArgType type) : name_(name), type_(type) {} | ||||
| const std::string &ServiceTypeArgument::get_name() const { return this->name_; } | ||||
| ServiceArgType ServiceTypeArgument::get_type() const { return this->type_; } | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										125
									
								
								esphome/components/api/user_services.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								esphome/components/api/user_services.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "api_message.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| enum ServiceArgType { | ||||
|   SERVICE_ARG_TYPE_BOOL = 0, | ||||
|   SERVICE_ARG_TYPE_INT = 1, | ||||
|   SERVICE_ARG_TYPE_FLOAT = 2, | ||||
|   SERVICE_ARG_TYPE_STRING = 3, | ||||
| }; | ||||
|  | ||||
| class ServiceTypeArgument { | ||||
|  public: | ||||
|   ServiceTypeArgument(const std::string &name, ServiceArgType type); | ||||
|   const std::string &get_name() const; | ||||
|   ServiceArgType get_type() const; | ||||
|  | ||||
|  protected: | ||||
|   std::string name_; | ||||
|   ServiceArgType type_; | ||||
| }; | ||||
|  | ||||
| class ExecuteServiceArgument : public APIMessage { | ||||
|  public: | ||||
|   APIMessageType message_type() const override; | ||||
|   template<typename T> T get_value(); | ||||
|  | ||||
|   bool decode_varint(uint32_t field_id, uint32_t value) override; | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|  | ||||
|  protected: | ||||
|   bool value_bool_{false}; | ||||
|   int value_int_{0}; | ||||
|   float value_float_{0.0f}; | ||||
|   std::string value_string_{}; | ||||
| }; | ||||
|  | ||||
| class ExecuteServiceRequest : public APIMessage { | ||||
|  public: | ||||
|   bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override; | ||||
|   bool decode_32bit(uint32_t field_id, uint32_t value) override; | ||||
|   APIMessageType message_type() const override; | ||||
|  | ||||
|   uint32_t get_key() const; | ||||
|   const std::vector<ExecuteServiceArgument> &get_args() const; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t key_; | ||||
|   std::vector<ExecuteServiceArgument> args_; | ||||
| }; | ||||
|  | ||||
| class UserServiceDescriptor { | ||||
|  public: | ||||
|   virtual void encode_list_service_response(APIBuffer &buffer) = 0; | ||||
|  | ||||
|   virtual bool execute_service(const ExecuteServiceRequest &req) = 0; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class UserService : public UserServiceDescriptor, public Trigger<Ts...> { | ||||
|  public: | ||||
|   UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args); | ||||
|  | ||||
|   void encode_list_service_response(APIBuffer &buffer) override; | ||||
|  | ||||
|   bool execute_service(const ExecuteServiceRequest &req) override; | ||||
|  | ||||
|  protected: | ||||
|   template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>); | ||||
|  | ||||
|   std::string name_; | ||||
|   uint32_t key_{0}; | ||||
|   std::array<ServiceTypeArgument, sizeof...(Ts)> args_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> | ||||
| template<int... S> | ||||
| void UserService<Ts...>::execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) { | ||||
|   this->trigger((args[S].get_value<Ts>())...); | ||||
| } | ||||
| template<typename... Ts> void UserService<Ts...>::encode_list_service_response(APIBuffer &buffer) { | ||||
|   // string name = 1; | ||||
|   buffer.encode_string(1, this->name_); | ||||
|   // fixed32 key = 2; | ||||
|   buffer.encode_fixed32(2, this->key_); | ||||
|  | ||||
|   // repeated ListServicesArgument args = 3; | ||||
|   for (auto &arg : this->args_) { | ||||
|     auto nested = buffer.begin_nested(3); | ||||
|     // string name = 1; | ||||
|     buffer.encode_string(1, arg.get_name()); | ||||
|     // Type type = 2; | ||||
|     buffer.encode_int32(2, arg.get_type()); | ||||
|     buffer.end_nested(nested); | ||||
|   } | ||||
| } | ||||
| template<typename... Ts> bool UserService<Ts...>::execute_service(const ExecuteServiceRequest &req) { | ||||
|   if (req.get_key() != this->key_) | ||||
|     return false; | ||||
|  | ||||
|   if (req.get_args().size() != this->args_.size()) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   this->execute_(req.get_args(), typename gens<sizeof...(Ts)>::type()); | ||||
|   return true; | ||||
| } | ||||
| template<typename... Ts> | ||||
| UserService<Ts...>::UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args) | ||||
|     : name_(name), args_(args) { | ||||
|   this->key_ = fnv1_hash(this->name_); | ||||
| } | ||||
|  | ||||
| template<> bool ExecuteServiceArgument::get_value<bool>(); | ||||
| template<> int ExecuteServiceArgument::get_value<int>(); | ||||
| template<> float ExecuteServiceArgument::get_value<float>(); | ||||
| template<> std::string ExecuteServiceArgument::get_value<std::string>(); | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										353
									
								
								esphome/components/api/util.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										353
									
								
								esphome/components/api/util.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,353 @@ | ||||
| #include "util.h" | ||||
| #include "api_server.h" | ||||
| #include "user_services.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| APIBuffer::APIBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {} | ||||
| size_t APIBuffer::get_length() const { return this->buffer_->size(); } | ||||
| void APIBuffer::write(uint8_t value) { this->buffer_->push_back(value); } | ||||
| void APIBuffer::encode_uint32(uint32_t field, uint32_t value, bool force) { | ||||
|   if (value == 0 && !force) | ||||
|     return; | ||||
|  | ||||
|   this->encode_field_raw(field, 0); | ||||
|   this->encode_varint_raw(value); | ||||
| } | ||||
| void APIBuffer::encode_int32(uint32_t field, int32_t value, bool force) { | ||||
|   this->encode_uint32(field, static_cast<uint32_t>(value), force); | ||||
| } | ||||
| void APIBuffer::encode_bool(uint32_t field, bool value, bool force) { | ||||
|   if (!value && !force) | ||||
|     return; | ||||
|  | ||||
|   this->encode_field_raw(field, 0); | ||||
|   this->write(0x01); | ||||
| } | ||||
| void APIBuffer::encode_string(uint32_t field, const std::string &value) { | ||||
|   this->encode_string(field, value.data(), value.size()); | ||||
| } | ||||
| void APIBuffer::encode_bytes(uint32_t field, const uint8_t *data, size_t len) { | ||||
|   this->encode_string(field, reinterpret_cast<const char *>(data), len); | ||||
| } | ||||
| void APIBuffer::encode_string(uint32_t field, const char *string, size_t len) { | ||||
|   if (len == 0) | ||||
|     return; | ||||
|  | ||||
|   this->encode_field_raw(field, 2); | ||||
|   this->encode_varint_raw(len); | ||||
|   const uint8_t *data = reinterpret_cast<const uint8_t *>(string); | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     this->write(data[i]); | ||||
|   } | ||||
| } | ||||
| void APIBuffer::encode_fixed32(uint32_t field, uint32_t value, bool force) { | ||||
|   if (value == 0 && !force) | ||||
|     return; | ||||
|  | ||||
|   this->encode_field_raw(field, 5); | ||||
|   this->write((value >> 0) & 0xFF); | ||||
|   this->write((value >> 8) & 0xFF); | ||||
|   this->write((value >> 16) & 0xFF); | ||||
|   this->write((value >> 24) & 0xFF); | ||||
| } | ||||
| void APIBuffer::encode_float(uint32_t field, float value, bool force) { | ||||
|   if (value == 0.0f && !force) | ||||
|     return; | ||||
|  | ||||
|   union { | ||||
|     float value_f; | ||||
|     uint32_t value_raw; | ||||
|   } val; | ||||
|   val.value_f = value; | ||||
|   this->encode_fixed32(field, val.value_raw); | ||||
| } | ||||
| void APIBuffer::encode_field_raw(uint32_t field, uint32_t type) { | ||||
|   uint32_t val = (field << 3) | (type & 0b111); | ||||
|   this->encode_varint_raw(val); | ||||
| } | ||||
| void APIBuffer::encode_varint_raw(uint32_t value) { | ||||
|   if (value <= 0x7F) { | ||||
|     this->write(value); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   while (value) { | ||||
|     uint8_t temp = value & 0x7F; | ||||
|     value >>= 7; | ||||
|     if (value) { | ||||
|       this->write(temp | 0x80); | ||||
|     } else { | ||||
|       this->write(temp); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| void APIBuffer::encode_sint32(uint32_t field, int32_t value, bool force) { | ||||
|   if (value < 0) | ||||
|     this->encode_uint32(field, ~(uint32_t(value) << 1), force); | ||||
|   else | ||||
|     this->encode_uint32(field, uint32_t(value) << 1, force); | ||||
| } | ||||
| void APIBuffer::encode_nameable(Nameable *nameable) { | ||||
|   // string object_id = 1; | ||||
|   this->encode_string(1, nameable->get_object_id()); | ||||
|   // fixed32 key = 2; | ||||
|   this->encode_fixed32(2, nameable->get_object_id_hash()); | ||||
|   // string name = 3; | ||||
|   this->encode_string(3, nameable->get_name()); | ||||
| } | ||||
| size_t APIBuffer::begin_nested(uint32_t field) { | ||||
|   this->encode_field_raw(field, 2); | ||||
|   return this->buffer_->size(); | ||||
| } | ||||
| void APIBuffer::end_nested(size_t begin_index) { | ||||
|   const uint32_t nested_length = this->buffer_->size() - begin_index; | ||||
|   // add varint | ||||
|   std::vector<uint8_t> var; | ||||
|   uint32_t val = nested_length; | ||||
|   if (val <= 0x7F) { | ||||
|     var.push_back(val); | ||||
|   } else { | ||||
|     while (val) { | ||||
|       uint8_t temp = val & 0x7F; | ||||
|       val >>= 7; | ||||
|       if (val) { | ||||
|         var.push_back(temp | 0x80); | ||||
|       } else { | ||||
|         var.push_back(temp); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   this->buffer_->insert(this->buffer_->begin() + begin_index, var.begin(), var.end()); | ||||
| } | ||||
|  | ||||
| optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed) { | ||||
|   if (len == 0) | ||||
|     return {}; | ||||
|  | ||||
|   uint32_t result = 0; | ||||
|   uint8_t bitpos = 0; | ||||
|  | ||||
|   for (uint32_t i = 0; i < len; i++) { | ||||
|     uint8_t val = buf[i]; | ||||
|     result |= uint32_t(val & 0x7F) << bitpos; | ||||
|     bitpos += 7; | ||||
|     if ((val & 0x80) == 0) { | ||||
|       if (consumed != nullptr) { | ||||
|         *consumed = i + 1; | ||||
|       } | ||||
|       return result; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
|  | ||||
| std::string as_string(const uint8_t *value, size_t len) { | ||||
|   return std::string(reinterpret_cast<const char *>(value), len); | ||||
| } | ||||
|  | ||||
| int32_t as_sint32(uint32_t val) { | ||||
|   if (val & 1) | ||||
|     return uint32_t(~(val >> 1)); | ||||
|   else | ||||
|     return uint32_t(val >> 1); | ||||
| } | ||||
|  | ||||
| float as_float(uint32_t val) { | ||||
|   static_assert(sizeof(uint32_t) == sizeof(float), "float must be 32bit long"); | ||||
|   union { | ||||
|     uint32_t raw; | ||||
|     float value; | ||||
|   } x; | ||||
|   x.raw = val; | ||||
|   return x.value; | ||||
| } | ||||
|  | ||||
| ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {} | ||||
| void ComponentIterator::begin() { | ||||
|   this->state_ = IteratorState::BEGIN; | ||||
|   this->at_ = 0; | ||||
| } | ||||
| void ComponentIterator::advance() { | ||||
|   bool advance_platform = false; | ||||
|   bool success = true; | ||||
|   switch (this->state_) { | ||||
|     case IteratorState::NONE: | ||||
|       // not started | ||||
|       return; | ||||
|     case IteratorState::BEGIN: | ||||
|       if (this->on_begin()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         return; | ||||
|       } | ||||
|       break; | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|     case IteratorState::BINARY_SENSOR: | ||||
|       if (this->at_ >= App.get_binary_sensors().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *binary_sensor = App.get_binary_sensors()[this->at_]; | ||||
|         if (binary_sensor->is_internal()) { | ||||
|           success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           success = this->on_binary_sensor(binary_sensor); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|     case IteratorState::COVER: | ||||
|       if (this->at_ >= App.get_covers().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *cover = App.get_covers()[this->at_]; | ||||
|         if (cover->is_internal()) { | ||||
|           success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           success = this->on_cover(cover); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|     case IteratorState::FAN: | ||||
|       if (this->at_ >= App.get_fans().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *fan = App.get_fans()[this->at_]; | ||||
|         if (fan->is_internal()) { | ||||
|           success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           success = this->on_fan(fan); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|     case IteratorState::LIGHT: | ||||
|       if (this->at_ >= App.get_lights().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *light = App.get_lights()[this->at_]; | ||||
|         if (light->is_internal()) { | ||||
|           success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           success = this->on_light(light); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|     case IteratorState::SENSOR: | ||||
|       if (this->at_ >= App.get_sensors().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *sensor = App.get_sensors()[this->at_]; | ||||
|         if (sensor->is_internal()) { | ||||
|           success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           success = this->on_sensor(sensor); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|     case IteratorState::SWITCH: | ||||
|       if (this->at_ >= App.get_switches().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *a_switch = App.get_switches()[this->at_]; | ||||
|         if (a_switch->is_internal()) { | ||||
|           success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           success = this->on_switch(a_switch); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|     case IteratorState::TEXT_SENSOR: | ||||
|       if (this->at_ >= App.get_text_sensors().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *text_sensor = App.get_text_sensors()[this->at_]; | ||||
|         if (text_sensor->is_internal()) { | ||||
|           success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           success = this->on_text_sensor(text_sensor); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
|     case IteratorState ::SERVICE: | ||||
|       if (this->at_ >= this->server_->get_user_services().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *service = this->server_->get_user_services()[this->at_]; | ||||
|         success = this->on_service(service); | ||||
|       } | ||||
|       break; | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|     case IteratorState::CAMERA: | ||||
|       if (esp32_camera::global_esp32_camera == nullptr) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         if (esp32_camera::global_esp32_camera->is_internal()) { | ||||
|           advance_platform = success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           advance_platform = success = this->on_camera(esp32_camera::global_esp32_camera); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|     case IteratorState::CLIMATE: | ||||
|       if (this->at_ >= App.get_climates().size()) { | ||||
|         advance_platform = true; | ||||
|       } else { | ||||
|         auto *climate = App.get_climates()[this->at_]; | ||||
|         if (climate->is_internal()) { | ||||
|           success = true; | ||||
|           break; | ||||
|         } else { | ||||
|           success = this->on_climate(climate); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
| #endif | ||||
|     case IteratorState::MAX: | ||||
|       if (this->on_end()) { | ||||
|         this->state_ = IteratorState::NONE; | ||||
|       } | ||||
|       return; | ||||
|   } | ||||
|  | ||||
|   if (advance_platform) { | ||||
|     this->state_ = static_cast<IteratorState>(static_cast<uint32_t>(this->state_) + 1); | ||||
|     this->at_ = 0; | ||||
|   } else if (success) { | ||||
|     this->at_++; | ||||
|   } | ||||
| } | ||||
| bool ComponentIterator::on_end() { return true; } | ||||
| bool ComponentIterator::on_begin() { return true; } | ||||
| bool ComponentIterator::on_service(UserServiceDescriptor *service) { return true; } | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										127
									
								
								esphome/components/api/util.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								esphome/components/api/util.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/controller.h" | ||||
| #ifdef USE_ESP32_CAMERA | ||||
| #include "esphome/components/esp32_camera/esp32_camera.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| class APIBuffer { | ||||
|  public: | ||||
|   APIBuffer(std::vector<uint8_t> *buffer); | ||||
|  | ||||
|   size_t get_length() const; | ||||
|   void write(uint8_t value); | ||||
|  | ||||
|   void encode_int32(uint32_t field, int32_t value, bool force = false); | ||||
|   void encode_uint32(uint32_t field, uint32_t value, bool force = false); | ||||
|   void encode_sint32(uint32_t field, int32_t value, bool force = false); | ||||
|   void encode_bool(uint32_t field, bool value, bool force = false); | ||||
|   void encode_string(uint32_t field, const std::string &value); | ||||
|   void encode_string(uint32_t field, const char *string, size_t len); | ||||
|   void encode_bytes(uint32_t field, const uint8_t *data, size_t len); | ||||
|   void encode_fixed32(uint32_t field, uint32_t value, bool force = false); | ||||
|   void encode_float(uint32_t field, float value, bool force = false); | ||||
|   void encode_nameable(Nameable *nameable); | ||||
|  | ||||
|   size_t begin_nested(uint32_t field); | ||||
|   void end_nested(size_t begin_index); | ||||
|  | ||||
|   void encode_field_raw(uint32_t field, uint32_t type); | ||||
|   void encode_varint_raw(uint32_t value); | ||||
|  | ||||
|  protected: | ||||
|   std::vector<uint8_t> *buffer_; | ||||
| }; | ||||
|  | ||||
| optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed = nullptr); | ||||
|  | ||||
| std::string as_string(const uint8_t *value, size_t len); | ||||
| int32_t as_sint32(uint32_t val); | ||||
| float as_float(uint32_t val); | ||||
|  | ||||
| class APIServer; | ||||
| class UserServiceDescriptor; | ||||
|  | ||||
| class ComponentIterator { | ||||
|  public: | ||||
|   ComponentIterator(APIServer *server); | ||||
|  | ||||
|   void begin(); | ||||
|   void advance(); | ||||
|   virtual bool on_begin(); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   virtual bool on_cover(cover::Cover *cover) = 0; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   virtual bool on_fan(fan::FanState *fan) = 0; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   virtual bool on_light(light::LightState *light) = 0; | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   virtual bool on_sensor(sensor::Sensor *sensor) = 0; | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   virtual bool on_switch(switch_::Switch *a_switch) = 0; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; | ||||
| #endif | ||||
|   virtual bool on_service(UserServiceDescriptor *service); | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|   virtual bool on_camera(esp32_camera::ESP32Camera *camera); | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|   virtual bool on_climate(climate::Climate *climate) = 0; | ||||
| #endif | ||||
|   virtual bool on_end(); | ||||
|  | ||||
|  protected: | ||||
|   enum class IteratorState { | ||||
|     NONE = 0, | ||||
|     BEGIN, | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|     BINARY_SENSOR, | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|     COVER, | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|     FAN, | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|     LIGHT, | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|     SENSOR, | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|     SWITCH, | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|     TEXT_SENSOR, | ||||
| #endif | ||||
|     SERVICE, | ||||
| #ifdef USE_ESP32_CAMERA | ||||
|     CAMERA, | ||||
| #endif | ||||
| #ifdef USE_CLIMATE | ||||
|     CLIMATE, | ||||
| #endif | ||||
|     MAX, | ||||
|   } state_{IteratorState::NONE}; | ||||
|   size_t at_{0}; | ||||
|  | ||||
|   APIServer *server_; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
							
								
								
									
										0
									
								
								esphome/components/bang_bang/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/bang_bang/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										156
									
								
								esphome/components/bang_bang/bang_bang_climate.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								esphome/components/bang_bang/bang_bang_climate.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| #include "bang_bang_climate.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bang_bang { | ||||
|  | ||||
| static const char *TAG = "bang_bang.climate"; | ||||
|  | ||||
| void BangBangClimate::setup() { | ||||
|   this->sensor_->add_on_state_callback([this](float state) { | ||||
|     this->current_temperature = state; | ||||
|     // control may have changed, recompute | ||||
|     this->compute_state_(); | ||||
|     // current temperature changed, publish state | ||||
|     this->publish_state(); | ||||
|   }); | ||||
|   this->current_temperature = this->sensor_->state; | ||||
|   // restore set points | ||||
|   auto restore = this->restore_state_(); | ||||
|   if (restore.has_value()) { | ||||
|     restore->to_call(this).perform(); | ||||
|   } else { | ||||
|     // restore from defaults, change_away handles those for us | ||||
|     this->mode = climate::CLIMATE_MODE_AUTO; | ||||
|     this->change_away_(false); | ||||
|   } | ||||
| } | ||||
| void BangBangClimate::control(const climate::ClimateCall &call) { | ||||
|   if (call.get_mode().has_value()) | ||||
|     this->mode = *call.get_mode(); | ||||
|   if (call.get_target_temperature_low().has_value()) | ||||
|     this->target_temperature_low = *call.get_target_temperature_low(); | ||||
|   if (call.get_target_temperature_high().has_value()) | ||||
|     this->target_temperature_high = *call.get_target_temperature_high(); | ||||
|   if (call.get_away().has_value()) | ||||
|     this->change_away_(*call.get_away()); | ||||
|  | ||||
|   this->compute_state_(); | ||||
|   this->publish_state(); | ||||
| } | ||||
| climate::ClimateTraits BangBangClimate::traits() { | ||||
|   auto traits = climate::ClimateTraits(); | ||||
|   traits.set_supports_current_temperature(true); | ||||
|   traits.set_supports_auto_mode(true); | ||||
|   traits.set_supports_cool_mode(this->supports_cool_); | ||||
|   traits.set_supports_heat_mode(this->supports_heat_); | ||||
|   traits.set_supports_two_point_target_temperature(true); | ||||
|   traits.set_supports_away(this->supports_away_); | ||||
|   return traits; | ||||
| } | ||||
| void BangBangClimate::compute_state_() { | ||||
|   if (this->mode != climate::CLIMATE_MODE_AUTO) { | ||||
|     // in non-auto mode | ||||
|     this->switch_to_mode_(this->mode); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // auto mode, compute target mode | ||||
|   if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) { | ||||
|     // if any control values are nan, go to OFF (idle) mode | ||||
|     this->switch_to_mode_(climate::CLIMATE_MODE_OFF); | ||||
|     return; | ||||
|   } | ||||
|   const bool too_cold = this->current_temperature < this->target_temperature_low; | ||||
|   const bool too_hot = this->current_temperature > this->target_temperature_high; | ||||
|  | ||||
|   climate::ClimateMode target_mode; | ||||
|   if (too_cold) { | ||||
|     // too cold -> enable heating if possible, else idle | ||||
|     if (this->supports_heat_) | ||||
|       target_mode = climate::CLIMATE_MODE_HEAT; | ||||
|     else | ||||
|       target_mode = climate::CLIMATE_MODE_OFF; | ||||
|   } else if (too_hot) { | ||||
|     // too hot -> enable cooling if possible, else idle | ||||
|     if (this->supports_cool_) | ||||
|       target_mode = climate::CLIMATE_MODE_COOL; | ||||
|     else | ||||
|       target_mode = climate::CLIMATE_MODE_OFF; | ||||
|   } else { | ||||
|     // neither too hot nor too cold -> in range | ||||
|     if (this->supports_cool_ && this->supports_heat_) { | ||||
|       // if supports both ends, go to idle mode | ||||
|       target_mode = climate::CLIMATE_MODE_OFF; | ||||
|     } else { | ||||
|       // else use current mode and don't change (hysteresis) | ||||
|       target_mode = this->internal_mode_; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   this->switch_to_mode_(target_mode); | ||||
| } | ||||
| void BangBangClimate::switch_to_mode_(climate::ClimateMode mode) { | ||||
|   if (mode == this->internal_mode_) | ||||
|     // already in target mode | ||||
|     return; | ||||
|  | ||||
|   if (this->prev_trigger_ != nullptr) { | ||||
|     this->prev_trigger_->stop(); | ||||
|     this->prev_trigger_ = nullptr; | ||||
|   } | ||||
|   Trigger<> *trig; | ||||
|   switch (mode) { | ||||
|     case climate::CLIMATE_MODE_OFF: | ||||
|       trig = this->idle_trigger_; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_COOL: | ||||
|       trig = this->cool_trigger_; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_HEAT: | ||||
|       trig = this->heat_trigger_; | ||||
|       break; | ||||
|     default: | ||||
|       trig = nullptr; | ||||
|   } | ||||
|   if (trig != nullptr) { | ||||
|     // trig should never be null, but still check so that we don't crash | ||||
|     trig->trigger(); | ||||
|     this->internal_mode_ = mode; | ||||
|     this->prev_trigger_ = trig; | ||||
|     this->publish_state(); | ||||
|   } | ||||
| } | ||||
| void BangBangClimate::change_away_(bool away) { | ||||
|   if (!away) { | ||||
|     this->target_temperature_low = this->normal_config_.default_temperature_low; | ||||
|     this->target_temperature_high = this->normal_config_.default_temperature_high; | ||||
|   } else { | ||||
|     this->target_temperature_low = this->away_config_.default_temperature_low; | ||||
|     this->target_temperature_high = this->away_config_.default_temperature_high; | ||||
|   } | ||||
|   this->away = away; | ||||
| } | ||||
| void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) { | ||||
|   this->normal_config_ = normal_config; | ||||
| } | ||||
| void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &away_config) { | ||||
|   this->supports_away_ = true; | ||||
|   this->away_config_ = away_config; | ||||
| } | ||||
| BangBangClimate::BangBangClimate() | ||||
|     : idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {} | ||||
| void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } | ||||
| Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; } | ||||
| Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; } | ||||
| void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } | ||||
| Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; } | ||||
| void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } | ||||
|  | ||||
| BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default; | ||||
| BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig(float default_temperature_low, | ||||
|                                                                  float default_temperature_high) | ||||
|     : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {} | ||||
|  | ||||
| }  // namespace bang_bang | ||||
| }  // namespace esphome | ||||
							
								
								
									
										89
									
								
								esphome/components/bang_bang/bang_bang_climate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								esphome/components/bang_bang/bang_bang_climate.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/components/climate/climate.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bang_bang { | ||||
|  | ||||
| struct BangBangClimateTargetTempConfig { | ||||
|  public: | ||||
|   BangBangClimateTargetTempConfig(); | ||||
|   BangBangClimateTargetTempConfig(float default_temperature_low, float default_temperature_high); | ||||
|  | ||||
|   float default_temperature_low{NAN}; | ||||
|   float default_temperature_high{NAN}; | ||||
| }; | ||||
|  | ||||
| class BangBangClimate : public climate::Climate, public Component { | ||||
|  public: | ||||
|   BangBangClimate(); | ||||
|   void setup() override; | ||||
|  | ||||
|   void set_sensor(sensor::Sensor *sensor); | ||||
|   Trigger<> *get_idle_trigger() const; | ||||
|   Trigger<> *get_cool_trigger() const; | ||||
|   void set_supports_cool(bool supports_cool); | ||||
|   Trigger<> *get_heat_trigger() const; | ||||
|   void set_supports_heat(bool supports_heat); | ||||
|   void set_normal_config(const BangBangClimateTargetTempConfig &normal_config); | ||||
|   void set_away_config(const BangBangClimateTargetTempConfig &away_config); | ||||
|  | ||||
|  protected: | ||||
|   /// Override control to change settings of the climate device. | ||||
|   void control(const climate::ClimateCall &call) override; | ||||
|   /// Change the away setting, will reset target temperatures to defaults. | ||||
|   void change_away_(bool away); | ||||
|   /// Return the traits of this controller. | ||||
|   climate::ClimateTraits traits() override; | ||||
|  | ||||
|   /// Re-compute the state of this climate controller. | ||||
|   void compute_state_(); | ||||
|  | ||||
|   /// Switch the climate device to the given climate mode. | ||||
|   void switch_to_mode_(climate::ClimateMode mode); | ||||
|  | ||||
|   /// The sensor used for getting the current temperature | ||||
|   sensor::Sensor *sensor_{nullptr}; | ||||
|   /** The trigger to call when the controller should switch to idle mode. | ||||
|    * | ||||
|    * In idle mode, the controller is assumed to have both heating and cooling disabled. | ||||
|    */ | ||||
|   Trigger<> *idle_trigger_; | ||||
|   /** The trigger to call when the controller should switch to cooling mode. | ||||
|    */ | ||||
|   Trigger<> *cool_trigger_; | ||||
|   /** Whether the controller supports cooling. | ||||
|    * | ||||
|    * A false value for this attribute means that the controller has no cooling action | ||||
|    * (for example a thermostat, where only heating and not-heating is possible). | ||||
|    */ | ||||
|   bool supports_cool_{false}; | ||||
|   /** The trigger to call when the controller should switch to heating mode. | ||||
|    * | ||||
|    * A null value for this attribute means that the controller has no heating action | ||||
|    * For example window blinds, where only cooling (blinds closed) and not-cooling | ||||
|    * (blinds open) is possible. | ||||
|    */ | ||||
|   Trigger<> *heat_trigger_{nullptr}; | ||||
|   bool supports_heat_{false}; | ||||
|   /** A reference to the trigger that was previously active. | ||||
|    * | ||||
|    * This is so that the previous trigger can be stopped before enabling a new one. | ||||
|    */ | ||||
|   Trigger<> *prev_trigger_{nullptr}; | ||||
|   /** The climate mode that is currently active - for a `.mode = AUTO` this will | ||||
|    * contain the actual mode the device | ||||
|    * | ||||
|    */ | ||||
|   climate::ClimateMode internal_mode_{climate::CLIMATE_MODE_OFF}; | ||||
|  | ||||
|   BangBangClimateTargetTempConfig normal_config_{}; | ||||
|   bool supports_away_{false}; | ||||
|   BangBangClimateTargetTempConfig away_config_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace bang_bang | ||||
| }  // namespace esphome | ||||
							
								
								
									
										57
									
								
								esphome/components/bang_bang/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/bang_bang/climate.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.components import climate, sensor | ||||
| from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \ | ||||
|     CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, \ | ||||
|     CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR | ||||
|  | ||||
| bang_bang_ns = cg.esphome_ns.namespace('bang_bang') | ||||
| BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate, cg.Component) | ||||
| BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig') | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_id(BangBangClimate), | ||||
|     cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||
|     cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, | ||||
|     cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, | ||||
|     cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), | ||||
|     cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), | ||||
|     cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), | ||||
|     cv.Optional(CONF_AWAY_CONFIG): cv.Schema({ | ||||
|         cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, | ||||
|         cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, | ||||
|     }), | ||||
| }).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield climate.register_climate(var, config) | ||||
|  | ||||
|     sens = yield cg.get_variable(config[CONF_SENSOR]) | ||||
|     cg.add(var.set_sensor(sens)) | ||||
|  | ||||
|     normal_config = BangBangClimateTargetTempConfig( | ||||
|         config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], | ||||
|         config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] | ||||
|     ) | ||||
|     cg.add(var.set_normal_config(normal_config)) | ||||
|  | ||||
|     yield automation.build_automation(var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]) | ||||
|  | ||||
|     if CONF_COOL_ACTION in config: | ||||
|         yield automation.build_automation(var.get_cool_trigger(), [], config[CONF_COOL_ACTION]) | ||||
|         cg.add(var.set_supports_cool(True)) | ||||
|     if CONF_HEAT_ACTION in config: | ||||
|         yield automation.build_automation(var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]) | ||||
|         cg.add(var.set_supports_heat(True)) | ||||
|  | ||||
|     if CONF_AWAY_CONFIG in config: | ||||
|         away = config[CONF_AWAY_CONFIG] | ||||
|         away_config = BangBangClimateTargetTempConfig( | ||||
|             away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], | ||||
|             away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] | ||||
|         ) | ||||
|         cg.add(var.set_away_config(away_config)) | ||||
							
								
								
									
										0
									
								
								esphome/components/bh1750/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/bh1750/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										78
									
								
								esphome/components/bh1750/bh1750.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/bh1750/bh1750.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| #include "bh1750.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bh1750 { | ||||
|  | ||||
| static const char *TAG = "bh1750.sensor"; | ||||
|  | ||||
| static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001; | ||||
|  | ||||
| void BH1750Sensor::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str()); | ||||
|   if (!this->write_bytes(BH1750_COMMAND_POWER_ON, nullptr, 0)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| } | ||||
| void BH1750Sensor::dump_config() { | ||||
|   LOG_SENSOR("", "BH1750", this); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication with BH1750 failed!"); | ||||
|   } | ||||
|  | ||||
|   const char *resolution_s; | ||||
|   switch (this->resolution_) { | ||||
|     case BH1750_RESOLUTION_0P5_LX: | ||||
|       resolution_s = "0.5"; | ||||
|       break; | ||||
|     case BH1750_RESOLUTION_1P0_LX: | ||||
|       resolution_s = "1"; | ||||
|       break; | ||||
|     case BH1750_RESOLUTION_4P0_LX: | ||||
|       resolution_s = "4"; | ||||
|       break; | ||||
|     default: | ||||
|       resolution_s = "Unknown"; | ||||
|       break; | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Resolution: %s", resolution_s); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| void BH1750Sensor::update() { | ||||
|   if (!this->write_bytes(this->resolution_, nullptr, 0)) | ||||
|     return; | ||||
|  | ||||
|   uint32_t wait = 0; | ||||
|   // use max conversion times | ||||
|   switch (this->resolution_) { | ||||
|     case BH1750_RESOLUTION_0P5_LX: | ||||
|     case BH1750_RESOLUTION_1P0_LX: | ||||
|       wait = 180; | ||||
|       break; | ||||
|     case BH1750_RESOLUTION_4P0_LX: | ||||
|       wait = 24; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   this->set_timeout("illuminance", wait, [this]() { this->read_data_(); }); | ||||
| } | ||||
| float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } | ||||
| void BH1750Sensor::read_data_() { | ||||
|   uint16_t raw_value; | ||||
|   if (!this->parent_->raw_receive_16(this->address_, &raw_value, 1)) { | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   float lx = float(raw_value) / 1.2f; | ||||
|   ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); | ||||
|   this->publish_state(lx); | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
| void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; } | ||||
|  | ||||
| }  // namespace bh1750 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										46
									
								
								esphome/components/bh1750/bh1750.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphome/components/bh1750/bh1750.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bh1750 { | ||||
|  | ||||
| /// Enum listing all resolutions that can be used with the BH1750 | ||||
| enum BH1750Resolution { | ||||
|   BH1750_RESOLUTION_4P0_LX = 0b00100011,  // one-time low resolution mode | ||||
|   BH1750_RESOLUTION_1P0_LX = 0b00100000,  // one-time high resolution mode 1 | ||||
|   BH1750_RESOLUTION_0P5_LX = 0b00100001,  // one-time high resolution mode 2 | ||||
| }; | ||||
|  | ||||
| /// This class implements support for the i2c-based BH1750 ambient light sensor. | ||||
| class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   /** Set the resolution of this sensor. | ||||
|    * | ||||
|    * Possible values are: | ||||
|    * | ||||
|    *  - `BH1750_RESOLUTION_4P0_LX` | ||||
|    *  - `BH1750_RESOLUTION_1P0_LX` | ||||
|    *  - `BH1750_RESOLUTION_0P5_LX` (default) | ||||
|    * | ||||
|    * @param resolution The new resolution of the sensor. | ||||
|    */ | ||||
|   void set_resolution(BH1750Resolution resolution); | ||||
|  | ||||
|   // ========== INTERNAL METHODS ========== | ||||
|   // (In most use cases you won't need these) | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|  protected: | ||||
|   void read_data_(); | ||||
|  | ||||
|   BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX}; | ||||
| }; | ||||
|  | ||||
| }  // namespace bh1750 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										30
									
								
								esphome/components/bh1750/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/bh1750/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_ID, CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5 | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| bh1750_ns = cg.esphome_ns.namespace('bh1750') | ||||
| BH1750Resolution = bh1750_ns.enum('BH1750Resolution') | ||||
| BH1750_RESOLUTIONS = { | ||||
|     4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX, | ||||
|     1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX, | ||||
|     0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX, | ||||
| } | ||||
|  | ||||
| BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({ | ||||
|     cv.GenerateID(): cv.declare_id(BH1750Sensor), | ||||
|     cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield sensor.register_sensor(var, config) | ||||
|     yield i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_resolution(config[CONF_RESOLUTION])) | ||||
							
								
								
									
										3
									
								
								esphome/components/binary/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								esphome/components/binary/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| binary_ns = cg.esphome_ns.namespace('binary') | ||||
							
								
								
									
										27
									
								
								esphome/components/binary/fan/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/binary/fan/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import fan, output | ||||
| from esphome.const import CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_OUTPUT_ID | ||||
| from .. import binary_ns | ||||
|  | ||||
| BinaryFan = binary_ns.class_('BinaryFan', cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), | ||||
|     cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), | ||||
|     cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|  | ||||
|     fan_ = yield fan.create_fan_state(config) | ||||
|     cg.add(var.set_fan(fan_)) | ||||
|     output_ = yield cg.get_variable(config[CONF_OUTPUT]) | ||||
|     cg.add(var.set_output(output_)) | ||||
|  | ||||
|     if CONF_OSCILLATION_OUTPUT in config: | ||||
|         oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) | ||||
|         cg.add(var.set_oscillation(oscillation_output)) | ||||
							
								
								
									
										48
									
								
								esphome/components/binary/fan/binary_fan.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/binary/fan/binary_fan.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| #include "binary_fan.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace binary { | ||||
|  | ||||
| static const char *TAG = "binary.fan"; | ||||
|  | ||||
| void binary::BinaryFan::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str()); | ||||
|   if (this->fan_->get_traits().supports_oscillation()) { | ||||
|     ESP_LOGCONFIG(TAG, "  Oscillation: YES"); | ||||
|   } | ||||
| } | ||||
| void BinaryFan::setup() { | ||||
|   auto traits = fan::FanTraits(this->oscillating_ != nullptr, false); | ||||
|   this->fan_->set_traits(traits); | ||||
|   this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); | ||||
| } | ||||
| void BinaryFan::loop() { | ||||
|   if (!this->next_update_) { | ||||
|     return; | ||||
|   } | ||||
|   this->next_update_ = false; | ||||
|  | ||||
|   { | ||||
|     bool enable = this->fan_->state; | ||||
|     if (enable) | ||||
|       this->output_->turn_on(); | ||||
|     else | ||||
|       this->output_->turn_off(); | ||||
|     ESP_LOGD(TAG, "Setting binary state: %s", ONOFF(enable)); | ||||
|   } | ||||
|  | ||||
|   if (this->oscillating_ != nullptr) { | ||||
|     bool enable = this->fan_->oscillating; | ||||
|     if (enable) { | ||||
|       this->oscillating_->turn_on(); | ||||
|     } else { | ||||
|       this->oscillating_->turn_off(); | ||||
|     } | ||||
|     ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); | ||||
|   } | ||||
| } | ||||
| float BinaryFan::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| }  // namespace binary | ||||
| }  // namespace esphome | ||||
							
								
								
									
										28
									
								
								esphome/components/binary/fan/binary_fan.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/binary/fan/binary_fan.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/output/binary_output.h" | ||||
| #include "esphome/components/fan/fan_state.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace binary { | ||||
|  | ||||
| class BinaryFan : public Component { | ||||
|  public: | ||||
|   void set_fan(fan::FanState *fan) { fan_ = fan; } | ||||
|   void set_output(output::BinaryOutput *output) { output_ = output; } | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } | ||||
|  | ||||
|  protected: | ||||
|   fan::FanState *fan_; | ||||
|   output::BinaryOutput *output_; | ||||
|   output::BinaryOutput *oscillating_{nullptr}; | ||||
|   bool next_update_{true}; | ||||
| }; | ||||
|  | ||||
| }  // namespace binary | ||||
| }  // namespace esphome | ||||
							
								
								
									
										20
									
								
								esphome/components/binary/light/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/binary/light/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import light, output | ||||
| from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT | ||||
| from .. import binary_ns | ||||
|  | ||||
| BinaryLightOutput = binary_ns.class_('BinaryLightOutput', light.LightOutput) | ||||
|  | ||||
| CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput), | ||||
|     cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) | ||||
|     yield light.register_light(var, config) | ||||
|  | ||||
|     out = yield cg.get_variable(config[CONF_OUTPUT]) | ||||
|     cg.add(var.set_output(out)) | ||||
							
								
								
									
										32
									
								
								esphome/components/binary/light/binary_light_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								esphome/components/binary/light/binary_light_output.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/output/binary_output.h" | ||||
| #include "esphome/components/light/light_output.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace binary { | ||||
|  | ||||
| class BinaryLightOutput : public light::LightOutput { | ||||
|  public: | ||||
|   void set_output(output::BinaryOutput *output) { output_ = output; } | ||||
|   light::LightTraits get_traits() override { | ||||
|     auto traits = light::LightTraits(); | ||||
|     traits.set_supports_brightness(false); | ||||
|     return traits; | ||||
|   } | ||||
|   void write_state(light::LightState *state) override { | ||||
|     bool binary; | ||||
|     state->current_values_as_binary(&binary); | ||||
|     if (binary) | ||||
|       this->output_->turn_on(); | ||||
|     else | ||||
|       this->output_->turn_off(); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   output::BinaryOutput *output_; | ||||
| }; | ||||
|  | ||||
| }  // namespace binary | ||||
| }  // namespace esphome | ||||
| @@ -1,19 +1,16 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphome import automation, core | ||||
| from esphome.automation import CONDITION_REGISTRY, Condition, maybe_simple_id | ||||
| from esphome.components import mqtt | ||||
| from esphome.components.mqtt import setup_mqtt_component | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_DELAYED_OFF, CONF_DELAYED_ON, CONF_DEVICE_CLASS, CONF_FILTERS, \ | ||||
|     CONF_HEARTBEAT, CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERT, CONF_INVERTED, \ | ||||
|     CONF_LAMBDA, CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_MQTT_ID, CONF_ON_CLICK, \ | ||||
| from esphome import automation, core | ||||
| from esphome.automation import Condition, maybe_simple_id | ||||
| from esphome.components import mqtt | ||||
| from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \ | ||||
|     CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, \ | ||||
|     CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \ | ||||
|     CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \ | ||||
|     CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR | ||||
| from esphome.core import CORE | ||||
| from esphome.cpp_generator import Pvariable, StructInitializer, add, get_variable, process_lambda | ||||
| from esphome.cpp_types import App, Component, Nameable, Trigger, bool_, esphome_ns, optional | ||||
|     CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID | ||||
| from esphome.core import CORE, coroutine, coroutine_with_priority | ||||
| from esphome.py_compat import string_types | ||||
| from esphome.util import Registry | ||||
|  | ||||
| DEVICE_CLASSES = [ | ||||
|     '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', | ||||
| @@ -22,50 +19,67 @@ DEVICE_CLASSES = [ | ||||
|     'sound', 'vibration', 'window' | ||||
| ] | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| }) | ||||
|  | ||||
| binary_sensor_ns = esphome_ns.namespace('binary_sensor') | ||||
| BinarySensor = binary_sensor_ns.class_('BinarySensor', Nameable) | ||||
| binary_sensor_ns = cg.esphome_ns.namespace('binary_sensor') | ||||
| BinarySensor = binary_sensor_ns.class_('BinarySensor', cg.Nameable) | ||||
| BinarySensorPtr = BinarySensor.operator('ptr') | ||||
| MQTTBinarySensorComponent = binary_sensor_ns.class_('MQTTBinarySensorComponent', mqtt.MQTTComponent) | ||||
|  | ||||
| # Triggers | ||||
| PressTrigger = binary_sensor_ns.class_('PressTrigger', Trigger.template()) | ||||
| ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', Trigger.template()) | ||||
| ClickTrigger = binary_sensor_ns.class_('ClickTrigger', Trigger.template()) | ||||
| DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', Trigger.template()) | ||||
| MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', Trigger.template(), Component) | ||||
| PressTrigger = binary_sensor_ns.class_('PressTrigger', automation.Trigger.template()) | ||||
| ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', automation.Trigger.template()) | ||||
| ClickTrigger = binary_sensor_ns.class_('ClickTrigger', automation.Trigger.template()) | ||||
| DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', automation.Trigger.template()) | ||||
| MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', automation.Trigger.template(), | ||||
|                                             cg.Component) | ||||
| MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent') | ||||
| StateTrigger = binary_sensor_ns.class_('StateTrigger', Trigger.template(bool_)) | ||||
| StateTrigger = binary_sensor_ns.class_('StateTrigger', automation.Trigger.template(bool)) | ||||
| BinarySensorPublishAction = binary_sensor_ns.class_('BinarySensorPublishAction', automation.Action) | ||||
|  | ||||
| # Condition | ||||
| BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition) | ||||
|  | ||||
| # Filters | ||||
| Filter = binary_sensor_ns.class_('Filter') | ||||
| DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, Component) | ||||
| DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, Component) | ||||
| HeartbeatFilter = binary_sensor_ns.class_('HeartbeatFilter', Filter, Component) | ||||
| DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component) | ||||
| DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component) | ||||
| InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter) | ||||
| LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter) | ||||
|  | ||||
| FILTER_KEYS = [CONF_INVERT, CONF_DELAYED_ON, CONF_DELAYED_OFF, CONF_LAMBDA, CONF_HEARTBEAT] | ||||
| FILTER_REGISTRY = Registry() | ||||
| validate_filters = cv.validate_registry('filter', FILTER_REGISTRY) | ||||
|  | ||||
| FILTERS_SCHEMA = cv.ensure_list({ | ||||
|     vol.Optional(CONF_INVERT): None, | ||||
|     vol.Optional(CONF_DELAYED_ON): cv.positive_time_period_milliseconds, | ||||
|     vol.Optional(CONF_DELAYED_OFF): cv.positive_time_period_milliseconds, | ||||
|     vol.Optional(CONF_LAMBDA): cv.lambda_, | ||||
|  | ||||
|     vol.Optional(CONF_HEARTBEAT): cv.invalid("The heartbeat filter has been removed in 1.11.0"), | ||||
| }, cv.has_exactly_one_key(*FILTER_KEYS)) | ||||
| @FILTER_REGISTRY.register('invert', InvertFilter, {}) | ||||
| def invert_filter_to_code(config, filter_id): | ||||
|     yield cg.new_Pvariable(filter_id) | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('delayed_on', DelayedOnFilter, | ||||
|                           cv.positive_time_period_milliseconds) | ||||
| def delayed_on_filter_to_code(config, filter_id): | ||||
|     var = cg.new_Pvariable(filter_id, config) | ||||
|     yield cg.register_component(var, {}) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('delayed_off', DelayedOffFilter, cv.positive_time_period_milliseconds) | ||||
| def delayed_off_filter_to_code(config, filter_id): | ||||
|     var = cg.new_Pvariable(filter_id, config) | ||||
|     yield cg.register_component(var, {}) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda) | ||||
| def lambda_filter_to_code(config, filter_id): | ||||
|     lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool)) | ||||
|     yield cg.new_Pvariable(filter_id, lambda_) | ||||
|  | ||||
|  | ||||
| MULTI_CLICK_TIMING_SCHEMA = cv.Schema({ | ||||
|     vol.Optional(CONF_STATE): cv.boolean, | ||||
|     vol.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds, | ||||
|     vol.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_STATE): cv.boolean, | ||||
|     cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds, | ||||
| }) | ||||
|  | ||||
|  | ||||
| @@ -75,15 +89,15 @@ def parse_multi_click_timing_str(value): | ||||
|  | ||||
|     parts = value.lower().split(' ') | ||||
|     if len(parts) != 5: | ||||
|         raise vol.Invalid("Multi click timing grammar consists of exactly 5 words, not {}" | ||||
|                           "".format(len(parts))) | ||||
|         raise cv.Invalid("Multi click timing grammar consists of exactly 5 words, not {}" | ||||
|                          "".format(len(parts))) | ||||
|     try: | ||||
|         state = cv.boolean(parts[0]) | ||||
|     except vol.Invalid: | ||||
|         raise vol.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0])) | ||||
|     except cv.Invalid: | ||||
|         raise cv.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0])) | ||||
|  | ||||
|     if parts[1] != 'for': | ||||
|         raise vol.Invalid(u"Second word must be 'for', got {}".format(parts[1])) | ||||
|         raise cv.Invalid(u"Second word must be 'for', got {}".format(parts[1])) | ||||
|  | ||||
|     if parts[2] == 'at': | ||||
|         if parts[3] == 'least': | ||||
| @@ -91,29 +105,29 @@ def parse_multi_click_timing_str(value): | ||||
|         elif parts[3] == 'most': | ||||
|             key = CONF_MAX_LENGTH | ||||
|         else: | ||||
|             raise vol.Invalid(u"Third word after at must either be 'least' or 'most', got {}" | ||||
|                               u"".format(parts[3])) | ||||
|             raise cv.Invalid(u"Third word after at must either be 'least' or 'most', got {}" | ||||
|                              u"".format(parts[3])) | ||||
|         try: | ||||
|             length = cv.positive_time_period_milliseconds(parts[4]) | ||||
|         except vol.Invalid as err: | ||||
|             raise vol.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err)) | ||||
|         except cv.Invalid as err: | ||||
|             raise cv.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err)) | ||||
|         return { | ||||
|             CONF_STATE: state, | ||||
|             key: str(length) | ||||
|         } | ||||
|  | ||||
|     if parts[3] != 'to': | ||||
|         raise vol.Invalid("Multi click grammar: 4th word must be 'to'") | ||||
|         raise cv.Invalid("Multi click grammar: 4th word must be 'to'") | ||||
|  | ||||
|     try: | ||||
|         min_length = cv.positive_time_period_milliseconds(parts[2]) | ||||
|     except vol.Invalid as err: | ||||
|         raise vol.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err)) | ||||
|     except cv.Invalid as err: | ||||
|         raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err)) | ||||
|  | ||||
|     try: | ||||
|         max_length = cv.positive_time_period_milliseconds(parts[4]) | ||||
|     except vol.Invalid as err: | ||||
|         raise vol.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err)) | ||||
|     except cv.Invalid as err: | ||||
|         raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err)) | ||||
|  | ||||
|     return { | ||||
|         CONF_STATE: state, | ||||
| @@ -124,7 +138,7 @@ def parse_multi_click_timing_str(value): | ||||
|  | ||||
| def validate_multi_click_timing(value): | ||||
|     if not isinstance(value, list): | ||||
|         raise vol.Invalid("Timing option must be a *list* of times!") | ||||
|         raise cv.Invalid("Timing option must be a *list* of times!") | ||||
|     timings = [] | ||||
|     state = None | ||||
|     for i, v_ in enumerate(value): | ||||
| @@ -132,17 +146,17 @@ def validate_multi_click_timing(value): | ||||
|         min_length = v_.get(CONF_MIN_LENGTH) | ||||
|         max_length = v_.get(CONF_MAX_LENGTH) | ||||
|         if min_length is None and max_length is None: | ||||
|             raise vol.Invalid("At least one of min_length and max_length is required!") | ||||
|             raise cv.Invalid("At least one of min_length and max_length is required!") | ||||
|         if min_length is None and max_length is not None: | ||||
|             min_length = core.TimePeriodMilliseconds(milliseconds=0) | ||||
|  | ||||
|         new_state = v_.get(CONF_STATE, not state) | ||||
|         if new_state == state: | ||||
|             raise vol.Invalid("Timings must have alternating state. Indices {} and {} have " | ||||
|                               "the same state {}".format(i, i + 1, state)) | ||||
|             raise cv.Invalid("Timings must have alternating state. Indices {} and {} have " | ||||
|                              "the same state {}".format(i, i + 1, state)) | ||||
|         if max_length is not None and max_length < min_length: | ||||
|             raise vol.Invalid("Max length ({}) must be larger than min length ({})." | ||||
|                               "".format(max_length, min_length)) | ||||
|             raise cv.Invalid("Max length ({}) must be larger than min length ({})." | ||||
|                              "".format(max_length, min_length)) | ||||
|  | ||||
|         state = new_state | ||||
|         tim = { | ||||
| @@ -155,167 +169,140 @@ def validate_multi_click_timing(value): | ||||
|     return timings | ||||
|  | ||||
|  | ||||
| device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space='_') | ||||
|  | ||||
| BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTBinarySensorComponent), | ||||
|     cv.GenerateID(): cv.declare_id(BinarySensor), | ||||
|     cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTBinarySensorComponent), | ||||
|  | ||||
|     vol.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), | ||||
|     vol.Optional(CONF_FILTERS): FILTERS_SCHEMA, | ||||
|     vol.Optional(CONF_ON_PRESS): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(PressTrigger), | ||||
|     cv.Optional(CONF_DEVICE_CLASS): device_class, | ||||
|     cv.Optional(CONF_FILTERS): validate_filters, | ||||
|     cv.Optional(CONF_ON_PRESS): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), | ||||
|     }), | ||||
|     vol.Optional(CONF_ON_RELEASE): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ReleaseTrigger), | ||||
|     cv.Optional(CONF_ON_RELEASE): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), | ||||
|     }), | ||||
|     vol.Optional(CONF_ON_CLICK): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(ClickTrigger), | ||||
|         vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, | ||||
|         vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_ON_CLICK): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), | ||||
|         cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, | ||||
|         cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, | ||||
|     }), | ||||
|     vol.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(DoubleClickTrigger), | ||||
|         vol.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, | ||||
|         vol.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), | ||||
|         cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, | ||||
|         cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, | ||||
|     }), | ||||
|     vol.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(MultiClickTrigger), | ||||
|         vol.Required(CONF_TIMING): vol.All([parse_multi_click_timing_str], | ||||
|                                            validate_multi_click_timing), | ||||
|         vol.Optional(CONF_INVALID_COOLDOWN): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), | ||||
|         cv.Required(CONF_TIMING): cv.All([parse_multi_click_timing_str], | ||||
|                                          validate_multi_click_timing), | ||||
|         cv.Optional(CONF_INVALID_COOLDOWN, default='1s'): cv.positive_time_period_milliseconds, | ||||
|     }), | ||||
|     vol.Optional(CONF_ON_STATE): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_variable_id(StateTrigger), | ||||
|     cv.Optional(CONF_ON_STATE): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||
|     }), | ||||
|  | ||||
|     vol.Optional(CONF_INVERTED): cv.invalid( | ||||
|     cv.Optional(CONF_INVERTED): cv.invalid( | ||||
|         "The inverted binary_sensor property has been replaced by the " | ||||
|         "new 'invert' binary  sensor filter. Please see " | ||||
|         "https://esphome.io/components/binary_sensor/index.html." | ||||
|     ), | ||||
| }) | ||||
|  | ||||
| BINARY_SENSOR_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(BINARY_SENSOR_SCHEMA.schema) | ||||
|  | ||||
|  | ||||
| def setup_filter(config): | ||||
|     if CONF_INVERT in config: | ||||
|         yield InvertFilter.new() | ||||
|     elif CONF_DELAYED_OFF in config: | ||||
|         yield App.register_component(DelayedOffFilter.new(config[CONF_DELAYED_OFF])) | ||||
|     elif CONF_DELAYED_ON in config: | ||||
|         yield App.register_component(DelayedOnFilter.new(config[CONF_DELAYED_ON])) | ||||
|     elif CONF_LAMBDA in config: | ||||
|         for lambda_ in process_lambda(config[CONF_LAMBDA], [(bool_, 'x')], | ||||
|                                       return_type=optional.template(bool_)): | ||||
|             yield None | ||||
|         yield LambdaFilter.new(lambda_) | ||||
|  | ||||
|  | ||||
| def setup_filters(config): | ||||
|     filters = [] | ||||
|     for conf in config: | ||||
|         for filter in setup_filter(conf): | ||||
|             yield None | ||||
|         filters.append(filter) | ||||
|     yield filters | ||||
|  | ||||
|  | ||||
| def setup_binary_sensor_core_(binary_sensor_var, config): | ||||
| @coroutine | ||||
| def setup_binary_sensor_core_(var, config): | ||||
|     cg.add(var.set_name(config[CONF_NAME])) | ||||
|     if CONF_INTERNAL in config: | ||||
|         add(binary_sensor_var.set_internal(CONF_INTERNAL)) | ||||
|         cg.add(var.set_internal(CONF_INTERNAL)) | ||||
|     if CONF_DEVICE_CLASS in config: | ||||
|         add(binary_sensor_var.set_device_class(config[CONF_DEVICE_CLASS])) | ||||
|         cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) | ||||
|     if CONF_INVERTED in config: | ||||
|         add(binary_sensor_var.set_inverted(config[CONF_INVERTED])) | ||||
|         cg.add(var.set_inverted(config[CONF_INVERTED])) | ||||
|     if CONF_FILTERS in config: | ||||
|         for filters in setup_filters(config[CONF_FILTERS]): | ||||
|             yield | ||||
|         add(binary_sensor_var.add_filters(filters)) | ||||
|         filters = yield cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS]) | ||||
|         cg.add(var.add_filters(filters)) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_PRESS, []): | ||||
|         rhs = binary_sensor_var.make_press_trigger() | ||||
|         trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) | ||||
|         automation.build_automations(trigger, [], conf) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_RELEASE, []): | ||||
|         rhs = binary_sensor_var.make_release_trigger() | ||||
|         trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) | ||||
|         automation.build_automations(trigger, [], conf) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_CLICK, []): | ||||
|         rhs = binary_sensor_var.make_click_trigger(conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) | ||||
|         trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) | ||||
|         automation.build_automations(trigger, [], conf) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, | ||||
|                                    conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_DOUBLE_CLICK, []): | ||||
|         rhs = binary_sensor_var.make_double_click_trigger(conf[CONF_MIN_LENGTH], | ||||
|                                                           conf[CONF_MAX_LENGTH]) | ||||
|         trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) | ||||
|         automation.build_automations(trigger, [], conf) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, | ||||
|                                    conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_MULTI_CLICK, []): | ||||
|         timings = [] | ||||
|         for tim in conf[CONF_TIMING]: | ||||
|             timings.append(StructInitializer( | ||||
|             timings.append(cg.StructInitializer( | ||||
|                 MultiClickTriggerEvent, | ||||
|                 ('state', tim[CONF_STATE]), | ||||
|                 ('min_length', tim[CONF_MIN_LENGTH]), | ||||
|                 ('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)), | ||||
|             )) | ||||
|         rhs = App.register_component(binary_sensor_var.make_multi_click_trigger(timings)) | ||||
|         trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings) | ||||
|         if CONF_INVALID_COOLDOWN in conf: | ||||
|             add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN])) | ||||
|         automation.build_automations(trigger, [], conf) | ||||
|             cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN])) | ||||
|         yield cg.register_component(trigger, conf) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_STATE, []): | ||||
|         rhs = binary_sensor_var.make_state_trigger() | ||||
|         trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) | ||||
|         automation.build_automations(trigger, [(bool_, 'x')], conf) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         yield automation.build_automation(trigger, [(bool, 'x')], conf) | ||||
|  | ||||
|     setup_mqtt_component(binary_sensor_var.Pget_mqtt(), config) | ||||
|  | ||||
|  | ||||
| def setup_binary_sensor(binary_sensor_obj, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         binary_sensor_obj = Pvariable(config[CONF_ID], binary_sensor_obj, has_side_effects=True) | ||||
|     CORE.add_job(setup_binary_sensor_core_, binary_sensor_obj, config) | ||||
|     if CONF_MQTT_ID in config: | ||||
|         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) | ||||
|         yield mqtt.register_mqtt_component(mqtt_, config) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| def register_binary_sensor(var, config): | ||||
|     binary_sensor_var = Pvariable(config[CONF_ID], var, has_side_effects=True) | ||||
|     add(App.register_binary_sensor(binary_sensor_var)) | ||||
|     CORE.add_job(setup_binary_sensor_core_, binary_sensor_var, config) | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_binary_sensor(var)) | ||||
|     yield setup_binary_sensor_core_(var, config) | ||||
|  | ||||
|  | ||||
| BUILD_FLAGS = '-DUSE_BINARY_SENSOR' | ||||
| @coroutine | ||||
| def new_binary_sensor(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME]) | ||||
|     yield register_binary_sensor(var, config) | ||||
|     yield var | ||||
|  | ||||
| CONF_BINARY_SENSOR_IS_ON = 'binary_sensor.is_on' | ||||
| BINARY_SENSOR_IS_ON_CONDITION_SCHEMA = maybe_simple_id({ | ||||
|     vol.Required(CONF_ID): cv.use_variable_id(BinarySensor), | ||||
|     vol.Optional(CONF_FOR): cv.positive_time_period_milliseconds, | ||||
|  | ||||
| BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({ | ||||
|     cv.Required(CONF_ID): cv.use_id(BinarySensor), | ||||
|     cv.Optional(CONF_FOR): cv.invalid("This option has been removed in 1.13, please use the " | ||||
|                                       "'for' condition instead."), | ||||
| }) | ||||
|  | ||||
|  | ||||
| @CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_ON, BINARY_SENSOR_IS_ON_CONDITION_SCHEMA) | ||||
| @automation.register_condition('binary_sensor.is_on', BinarySensorCondition, | ||||
|                                BINARY_SENSOR_CONDITION_SCHEMA) | ||||
| def binary_sensor_is_on_to_code(config, condition_id, template_arg, args): | ||||
|     for var in get_variable(config[CONF_ID]): | ||||
|         yield None | ||||
|     rhs = var.make_binary_sensor_is_on_condition(template_arg, config.get(CONF_FOR)) | ||||
|     type = BinarySensorCondition.template(template_arg) | ||||
|     yield Pvariable(condition_id, rhs, type=type) | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, paren, True) | ||||
|  | ||||
|  | ||||
| CONF_BINARY_SENSOR_IS_OFF = 'binary_sensor.is_off' | ||||
| BINARY_SENSOR_IS_OFF_CONDITION_SCHEMA = maybe_simple_id({ | ||||
|     vol.Required(CONF_ID): cv.use_variable_id(BinarySensor), | ||||
|     vol.Optional(CONF_FOR): cv.positive_time_period_milliseconds, | ||||
| }) | ||||
|  | ||||
|  | ||||
| @CONDITION_REGISTRY.register(CONF_BINARY_SENSOR_IS_OFF, BINARY_SENSOR_IS_OFF_CONDITION_SCHEMA) | ||||
| @automation.register_condition('binary_sensor.is_off', BinarySensorCondition, | ||||
|                                BINARY_SENSOR_CONDITION_SCHEMA) | ||||
| def binary_sensor_is_off_to_code(config, condition_id, template_arg, args): | ||||
|     for var in get_variable(config[CONF_ID]): | ||||
|         yield None | ||||
|     rhs = var.make_binary_sensor_is_off_condition(template_arg, config.get(CONF_FOR)) | ||||
|     type = BinarySensorCondition.template(template_arg) | ||||
|     yield Pvariable(condition_id, rhs, type=type) | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, paren, False) | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(100.0) | ||||
| def to_code(config): | ||||
|     cg.add_define('USE_BINARY_SENSOR') | ||||
|     cg.add_global(binary_sensor_ns.using) | ||||
|   | ||||
| @@ -1,32 +0,0 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphome.components import binary_sensor, sensor | ||||
| from esphome.components.apds9960 import APDS9960, CONF_APDS9960_ID | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_DIRECTION, CONF_NAME | ||||
| from esphome.cpp_generator import get_variable | ||||
|  | ||||
| DEPENDENCIES = ['apds9960'] | ||||
| APDS9960GestureDirectionBinarySensor = sensor.sensor_ns.class_( | ||||
|     'APDS9960GestureDirectionBinarySensor', binary_sensor.BinarySensor) | ||||
|  | ||||
| DIRECTIONS = { | ||||
|     'UP': 'make_up_direction', | ||||
|     'DOWN': 'make_down_direction', | ||||
|     'LEFT': 'make_left_direction', | ||||
|     'RIGHT': 'make_right_direction', | ||||
| } | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_variable_id(APDS9960GestureDirectionBinarySensor), | ||||
|     vol.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), | ||||
|     cv.GenerateID(CONF_APDS9960_ID): cv.use_variable_id(APDS9960) | ||||
| })) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     for hub in get_variable(config[CONF_APDS9960_ID]): | ||||
|         yield | ||||
|     func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]]) | ||||
|     rhs = func(config[CONF_NAME]) | ||||
|     binary_sensor.register_binary_sensor(rhs, config) | ||||
							
								
								
									
										113
									
								
								esphome/components/binary_sensor/automation.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								esphome/components/binary_sensor/automation.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| #include "automation.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace binary_sensor { | ||||
|  | ||||
| static const char *TAG = "binary_sensor.automation"; | ||||
|  | ||||
| void binary_sensor::MultiClickTrigger::on_state_(bool state) { | ||||
|   // Handle duplicate events | ||||
|   if (state == this->last_state_) { | ||||
|     return; | ||||
|   } | ||||
|   this->last_state_ = state; | ||||
|  | ||||
|   // Cooldown: Do not immediately try matching after having invalid timing | ||||
|   if (this->is_in_cooldown_) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->at_index_.has_value()) { | ||||
|     // Start matching | ||||
|     MultiClickTriggerEvent evt = this->timing_[0]; | ||||
|     if (evt.state == state) { | ||||
|       ESP_LOGV(TAG, "START min=%u max=%u", evt.min_length, evt.max_length); | ||||
|       ESP_LOGV(TAG, "Multi Click: Starting multi click action!"); | ||||
|       this->at_index_ = 1; | ||||
|       if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) { | ||||
|         this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); }); | ||||
|       } else { | ||||
|         this->schedule_is_valid_(evt.min_length); | ||||
|         this->schedule_is_not_valid_(evt.max_length); | ||||
|       } | ||||
|     } else { | ||||
|       ESP_LOGV(TAG, "Multi Click: action not started because first level does not match!"); | ||||
|     } | ||||
|  | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->is_valid_) { | ||||
|     this->schedule_cooldown_(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (*this->at_index_ == this->timing_.size()) { | ||||
|     this->trigger_(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   MultiClickTriggerEvent evt = this->timing_[*this->at_index_]; | ||||
|  | ||||
|   if (evt.max_length != 4294967294UL) { | ||||
|     ESP_LOGV(TAG, "A i=%u min=%u max=%u", *this->at_index_, evt.min_length, evt.max_length);  // NOLINT | ||||
|     this->schedule_is_valid_(evt.min_length); | ||||
|     this->schedule_is_not_valid_(evt.max_length); | ||||
|   } else if (*this->at_index_ + 1 != this->timing_.size()) { | ||||
|     ESP_LOGV(TAG, "B i=%u min=%u", *this->at_index_, evt.min_length);  // NOLINT | ||||
|     this->cancel_timeout("is_not_valid"); | ||||
|     this->schedule_is_valid_(evt.min_length); | ||||
|   } else { | ||||
|     ESP_LOGV(TAG, "C i=%u min=%u", *this->at_index_, evt.min_length);  // NOLINT | ||||
|     this->is_valid_ = false; | ||||
|     this->cancel_timeout("is_not_valid"); | ||||
|     this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); }); | ||||
|   } | ||||
|  | ||||
|   *this->at_index_ = *this->at_index_ + 1; | ||||
| } | ||||
| void binary_sensor::MultiClickTrigger::schedule_cooldown_() { | ||||
|   ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %u ms...", this->invalid_cooldown_); | ||||
|   this->is_in_cooldown_ = true; | ||||
|   this->set_timeout("cooldown", this->invalid_cooldown_, [this]() { | ||||
|     ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again."); | ||||
|     this->is_in_cooldown_ = false; | ||||
|   }); | ||||
|   this->at_index_.reset(); | ||||
|   this->cancel_timeout("trigger"); | ||||
|   this->cancel_timeout("is_valid"); | ||||
|   this->cancel_timeout("is_not_valid"); | ||||
| } | ||||
| void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) { | ||||
|   this->is_valid_ = false; | ||||
|   this->set_timeout("is_valid", min_length, [this]() { | ||||
|     ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS"); | ||||
|     this->is_valid_ = true; | ||||
|   }); | ||||
| } | ||||
| void binary_sensor::MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) { | ||||
|   this->set_timeout("is_not_valid", max_length, [this]() { | ||||
|     ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS"); | ||||
|     this->is_valid_ = false; | ||||
|     this->schedule_cooldown_(); | ||||
|   }); | ||||
| } | ||||
| void binary_sensor::MultiClickTrigger::trigger_() { | ||||
|   ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!"); | ||||
|   this->at_index_.reset(); | ||||
|   this->cancel_timeout("trigger"); | ||||
|   this->cancel_timeout("is_valid"); | ||||
|   this->cancel_timeout("is_not_valid"); | ||||
|   this->trigger(); | ||||
| } | ||||
|  | ||||
| bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) { | ||||
|   if (max_length == 0) { | ||||
|     return length >= min_length; | ||||
|   } else { | ||||
|     return length >= min_length && length <= max_length; | ||||
|   } | ||||
| } | ||||
| }  // namespace binary_sensor | ||||
| }  // namespace esphome | ||||
							
								
								
									
										150
									
								
								esphome/components/binary_sensor/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								esphome/components/binary_sensor/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace binary_sensor { | ||||
|  | ||||
| struct MultiClickTriggerEvent { | ||||
|   bool state; | ||||
|   uint32_t min_length; | ||||
|   uint32_t max_length; | ||||
| }; | ||||
|  | ||||
| class PressTrigger : public Trigger<> { | ||||
|  public: | ||||
|   explicit PressTrigger(BinarySensor *parent) { | ||||
|     parent->add_on_state_callback([this](bool state) { | ||||
|       if (state) | ||||
|         this->trigger(); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class ReleaseTrigger : public Trigger<> { | ||||
|  public: | ||||
|   explicit ReleaseTrigger(BinarySensor *parent) { | ||||
|     parent->add_on_state_callback([this](bool state) { | ||||
|       if (!state) | ||||
|         this->trigger(); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length); | ||||
|  | ||||
| class ClickTrigger : public Trigger<> { | ||||
|  public: | ||||
|   explicit ClickTrigger(BinarySensor *parent, uint32_t min_length, uint32_t max_length) | ||||
|       : min_length_(min_length), max_length_(max_length) { | ||||
|     parent->add_on_state_callback([this](bool state) { | ||||
|       if (state) { | ||||
|         this->start_time_ = millis(); | ||||
|       } else { | ||||
|         const uint32_t length = millis() - this->start_time_; | ||||
|         if (match_interval(this->min_length_, this->max_length_, length)) | ||||
|           this->trigger(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   uint32_t start_time_{0};  /// The millis() time when the click started. | ||||
|   uint32_t min_length_;     /// Minimum length of click. 0 means no minimum. | ||||
|   uint32_t max_length_;     /// Maximum length of click. 0 means no maximum. | ||||
| }; | ||||
|  | ||||
| class DoubleClickTrigger : public Trigger<> { | ||||
|  public: | ||||
|   explicit DoubleClickTrigger(BinarySensor *parent, uint32_t min_length, uint32_t max_length) | ||||
|       : min_length_(min_length), max_length_(max_length) { | ||||
|     parent->add_on_state_callback([this](bool state) { | ||||
|       const uint32_t now = millis(); | ||||
|  | ||||
|       if (state && this->start_time_ != 0 && this->end_time_ != 0) { | ||||
|         if (match_interval(this->min_length_, this->max_length_, this->end_time_ - this->start_time_) && | ||||
|             match_interval(this->min_length_, this->max_length_, now - this->end_time_)) { | ||||
|           this->trigger(); | ||||
|           this->start_time_ = 0; | ||||
|           this->end_time_ = 0; | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       this->start_time_ = this->end_time_; | ||||
|       this->end_time_ = now; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   uint32_t start_time_{0}; | ||||
|   uint32_t end_time_{0}; | ||||
|   uint32_t min_length_;  /// Minimum length of click. 0 means no minimum. | ||||
|   uint32_t max_length_;  /// Maximum length of click. 0 means no maximum. | ||||
| }; | ||||
|  | ||||
| class MultiClickTrigger : public Trigger<>, public Component { | ||||
|  public: | ||||
|   explicit MultiClickTrigger(BinarySensor *parent, const std::vector<MultiClickTriggerEvent> &timing) | ||||
|       : parent_(parent), timing_(timing) {} | ||||
|  | ||||
|   void setup() override { | ||||
|     this->last_state_ = this->parent_->state; | ||||
|     auto f = std::bind(&MultiClickTrigger::on_state_, this, std::placeholders::_1); | ||||
|     this->parent_->add_on_state_callback(f); | ||||
|   } | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   void set_invalid_cooldown(uint32_t invalid_cooldown) { this->invalid_cooldown_ = invalid_cooldown; } | ||||
|  | ||||
|  protected: | ||||
|   void on_state_(bool state); | ||||
|   void schedule_cooldown_(); | ||||
|   void schedule_is_valid_(uint32_t min_length); | ||||
|   void schedule_is_not_valid_(uint32_t max_length); | ||||
|   void trigger_(); | ||||
|  | ||||
|   BinarySensor *parent_; | ||||
|   std::vector<MultiClickTriggerEvent> timing_; | ||||
|   uint32_t invalid_cooldown_{1000}; | ||||
|   optional<size_t> at_index_{}; | ||||
|   bool last_state_{false}; | ||||
|   bool is_in_cooldown_{false}; | ||||
|   bool is_valid_{false}; | ||||
| }; | ||||
|  | ||||
| class StateTrigger : public Trigger<bool> { | ||||
|  public: | ||||
|   explicit StateTrigger(BinarySensor *parent) { | ||||
|     parent->add_on_state_callback([this](bool state) { this->trigger(state); }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> { | ||||
|  public: | ||||
|   BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {} | ||||
|   bool check(Ts... x) override { return this->parent_->state == this->state_; } | ||||
|  | ||||
|  protected: | ||||
|   BinarySensor *parent_; | ||||
|   bool state_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {} | ||||
|   TEMPLATABLE_VALUE(bool, state) | ||||
|   void play(Ts... x) override { | ||||
|     auto val = this->state_.value(x...); | ||||
|     this->sensor_->publish_state(val); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   BinarySensor *sensor_; | ||||
| }; | ||||
|  | ||||
| }  // namespace binary_sensor | ||||
| }  // namespace esphome | ||||
							
								
								
									
										71
									
								
								esphome/components/binary_sensor/binary_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								esphome/components/binary_sensor/binary_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| #include "binary_sensor.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| namespace binary_sensor { | ||||
|  | ||||
| static const char *TAG = "binary_sensor"; | ||||
|  | ||||
| void BinarySensor::add_on_state_callback(std::function<void(bool)> &&callback) { | ||||
|   this->state_callback_.add(std::move(callback)); | ||||
| } | ||||
|  | ||||
| void BinarySensor::publish_state(bool state) { | ||||
|   if (!this->publish_dedup_.next(state)) | ||||
|     return; | ||||
|   if (this->filter_list_ == nullptr) { | ||||
|     this->send_state_internal(state, false); | ||||
|   } else { | ||||
|     this->filter_list_->input(state, false); | ||||
|   } | ||||
| } | ||||
| void BinarySensor::publish_initial_state(bool state) { | ||||
|   if (!this->publish_dedup_.next(state)) | ||||
|     return; | ||||
|   if (this->filter_list_ == nullptr) { | ||||
|     this->send_state_internal(state, true); | ||||
|   } else { | ||||
|     this->filter_list_->input(state, true); | ||||
|   } | ||||
| } | ||||
| void BinarySensor::send_state_internal(bool state, bool is_initial) { | ||||
|   ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state ? "ON" : "OFF"); | ||||
|   this->has_state_ = true; | ||||
|   this->state = state; | ||||
|   if (!is_initial) { | ||||
|     this->state_callback_.call(state); | ||||
|   } | ||||
| } | ||||
| std::string BinarySensor::device_class() { return ""; } | ||||
| BinarySensor::BinarySensor(const std::string &name) : Nameable(name), state(false) {} | ||||
| BinarySensor::BinarySensor() : BinarySensor("") {} | ||||
| void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } | ||||
| std::string BinarySensor::get_device_class() { | ||||
|   if (this->device_class_.has_value()) | ||||
|     return *this->device_class_; | ||||
|   return this->device_class(); | ||||
| } | ||||
| void BinarySensor::add_filter(Filter *filter) { | ||||
|   filter->parent_ = this; | ||||
|   if (this->filter_list_ == nullptr) { | ||||
|     this->filter_list_ = filter; | ||||
|   } else { | ||||
|     Filter *last_filter = this->filter_list_; | ||||
|     while (last_filter->next_ != nullptr) | ||||
|       last_filter = last_filter->next_; | ||||
|     last_filter->next_ = filter; | ||||
|   } | ||||
| } | ||||
| void BinarySensor::add_filters(std::vector<Filter *> filters) { | ||||
|   for (Filter *filter : filters) { | ||||
|     this->add_filter(filter); | ||||
|   } | ||||
| } | ||||
| bool BinarySensor::has_state() const { return this->has_state_; } | ||||
| uint32_t BinarySensor::hash_base() { return 1210250844UL; } | ||||
| bool BinarySensor::is_status_binary_sensor() const { return false; } | ||||
|  | ||||
| }  // namespace binary_sensor | ||||
|  | ||||
| }  // namespace esphome | ||||
							
								
								
									
										90
									
								
								esphome/components/binary_sensor/binary_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								esphome/components/binary_sensor/binary_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/components/binary_sensor/filter.h" | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| namespace binary_sensor { | ||||
|  | ||||
| #define LOG_BINARY_SENSOR(prefix, type, obj) \ | ||||
|   if (obj != nullptr) { \ | ||||
|     ESP_LOGCONFIG(TAG, prefix type " '%s'", obj->get_name().c_str()); \ | ||||
|     if (!obj->get_device_class().empty()) { \ | ||||
|       ESP_LOGCONFIG(TAG, prefix "  Device Class: '%s'", obj->get_device_class().c_str()); \ | ||||
|     } \ | ||||
|   } | ||||
|  | ||||
| /** Base class for all binary_sensor-type classes. | ||||
|  * | ||||
|  * This class includes a callback that components such as MQTT can subscribe to for state changes. | ||||
|  * The sub classes should notify the front-end of new states via the publish_state() method which | ||||
|  * handles inverted inputs for you. | ||||
|  */ | ||||
| class BinarySensor : public Nameable { | ||||
|  public: | ||||
|   explicit BinarySensor(); | ||||
|   /** Construct a binary sensor with the specified name | ||||
|    * | ||||
|    * @param name Name of this binary sensor. | ||||
|    */ | ||||
|   explicit BinarySensor(const std::string &name); | ||||
|  | ||||
|   /** Add a callback to be notified of state changes. | ||||
|    * | ||||
|    * @param callback The void(bool) callback. | ||||
|    */ | ||||
|   void add_on_state_callback(std::function<void(bool)> &&callback); | ||||
|  | ||||
|   /** Publish a new state to the front-end. | ||||
|    * | ||||
|    * @param state The new state. | ||||
|    */ | ||||
|   void publish_state(bool state); | ||||
|  | ||||
|   /** Publish the initial state, this will not make the callback manager send callbacks | ||||
|    * and is meant only for the initial state on boot. | ||||
|    * | ||||
|    * @param state The new state. | ||||
|    */ | ||||
|   void publish_initial_state(bool state); | ||||
|  | ||||
|   /// The current reported state of the binary sensor. | ||||
|   bool state; | ||||
|  | ||||
|   /// Manually set the Home Assistant device class (see binary_sensor::device_class) | ||||
|   void set_device_class(const std::string &device_class); | ||||
|  | ||||
|   /// Get the device class for this binary sensor, using the manual override if specified. | ||||
|   std::string get_device_class(); | ||||
|  | ||||
|   void add_filter(Filter *filter); | ||||
|   void add_filters(std::vector<Filter *> filters); | ||||
|  | ||||
|   // ========== INTERNAL METHODS ========== | ||||
|   // (In most use cases you won't need these) | ||||
|   void send_state_internal(bool state, bool is_initial); | ||||
|  | ||||
|   /// Return whether this binary sensor has outputted a state. | ||||
|   bool has_state() const; | ||||
|  | ||||
|   virtual bool is_status_binary_sensor() const; | ||||
|  | ||||
|   // ========== OVERRIDE METHODS ========== | ||||
|   // (You'll only need this when creating your own custom binary sensor) | ||||
|   /// Get the default device class for this sensor, or empty string for no default. | ||||
|   virtual std::string device_class(); | ||||
|  | ||||
|  protected: | ||||
|   uint32_t hash_base() override; | ||||
|  | ||||
|   CallbackManager<void(bool)> state_callback_{}; | ||||
|   optional<std::string> device_class_{};  ///< Stores the override of the device class | ||||
|   Filter *filter_list_{nullptr}; | ||||
|   bool has_state_{false}; | ||||
|   Deduplicator<bool> publish_dedup_; | ||||
| }; | ||||
|  | ||||
| }  // namespace binary_sensor | ||||
| }  // namespace esphome | ||||
| @@ -1,35 +0,0 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphome.components import binary_sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA, CONF_NAME | ||||
| from esphome.cpp_generator import add, process_lambda, variable | ||||
| from esphome.cpp_types import std_vector | ||||
|  | ||||
| CustomBinarySensorConstructor = binary_sensor.binary_sensor_ns.class_( | ||||
|     'CustomBinarySensorConstructor') | ||||
|  | ||||
| PLATFORM_SCHEMA = binary_sensor.PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_variable_id(CustomBinarySensorConstructor), | ||||
|     vol.Required(CONF_LAMBDA): cv.lambda_, | ||||
|     vol.Required(CONF_BINARY_SENSORS): | ||||
|         cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
|             cv.GenerateID(): cv.declare_variable_id(binary_sensor.BinarySensor), | ||||
|         })), | ||||
| }) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     for template_ in process_lambda(config[CONF_LAMBDA], [], | ||||
|                                     return_type=std_vector.template(binary_sensor.BinarySensorPtr)): | ||||
|         yield | ||||
|  | ||||
|     rhs = CustomBinarySensorConstructor(template_) | ||||
|     custom = variable(config[CONF_ID], rhs) | ||||
|     for i, conf in enumerate(config[CONF_BINARY_SENSORS]): | ||||
|         rhs = custom.Pget_binary_sensor(i) | ||||
|         add(rhs.set_name(conf[CONF_NAME])) | ||||
|         binary_sensor.register_binary_sensor(rhs, conf) | ||||
|  | ||||
|  | ||||
| BUILD_FLAGS = '-DUSE_CUSTOM_BINARY_SENSOR' | ||||
| @@ -1,25 +0,0 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphome.components import binary_sensor | ||||
| from esphome.components.esp32_ble_tracker import CONF_ESP32_BLE_ID, ESP32BLETracker, \ | ||||
|     make_address_array | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_MAC_ADDRESS, CONF_NAME | ||||
| from esphome.cpp_generator import get_variable | ||||
| from esphome.cpp_types import esphome_ns | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| ESP32BLEPresenceDevice = esphome_ns.class_('ESP32BLEPresenceDevice', binary_sensor.BinarySensor) | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_variable_id(ESP32BLEPresenceDevice), | ||||
|     vol.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.GenerateID(CONF_ESP32_BLE_ID): cv.use_variable_id(ESP32BLETracker) | ||||
| })) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     for hub in get_variable(config[CONF_ESP32_BLE_ID]): | ||||
|         yield | ||||
|     rhs = hub.make_presence_sensor(config[CONF_NAME], make_address_array(config[CONF_MAC_ADDRESS])) | ||||
|     binary_sensor.register_binary_sensor(rhs, config) | ||||
| @@ -1,58 +0,0 @@ | ||||
| import voluptuous as vol | ||||
|  | ||||
| from esphome.components import binary_sensor | ||||
| from esphome.components.esp32_touch import ESP32TouchComponent | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_NAME, CONF_PIN, CONF_THRESHOLD, ESP_PLATFORM_ESP32 | ||||
| from esphome.cpp_generator import get_variable | ||||
| from esphome.cpp_types import global_ns | ||||
| from esphome.pins import validate_gpio_pin | ||||
|  | ||||
| ESP_PLATFORMS = [ESP_PLATFORM_ESP32] | ||||
|  | ||||
| DEPENDENCIES = ['esp32_touch'] | ||||
|  | ||||
| CONF_ESP32_TOUCH_ID = 'esp32_touch_id' | ||||
|  | ||||
| TOUCH_PADS = { | ||||
|     4: global_ns.TOUCH_PAD_NUM0, | ||||
|     0: global_ns.TOUCH_PAD_NUM1, | ||||
|     2: global_ns.TOUCH_PAD_NUM2, | ||||
|     15: global_ns.TOUCH_PAD_NUM3, | ||||
|     13: global_ns.TOUCH_PAD_NUM4, | ||||
|     12: global_ns.TOUCH_PAD_NUM5, | ||||
|     14: global_ns.TOUCH_PAD_NUM6, | ||||
|     27: global_ns.TOUCH_PAD_NUM7, | ||||
|     33: global_ns.TOUCH_PAD_NUM8, | ||||
|     32: global_ns.TOUCH_PAD_NUM9, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_touch_pad(value): | ||||
|     value = validate_gpio_pin(value) | ||||
|     if value not in TOUCH_PADS: | ||||
|         raise vol.Invalid("Pin {} does not support touch pads.".format(value)) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| ESP32TouchBinarySensor = binary_sensor.binary_sensor_ns.class_('ESP32TouchBinarySensor', | ||||
|                                                                binary_sensor.BinarySensor) | ||||
|  | ||||
| PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({ | ||||
|     cv.GenerateID(): cv.declare_variable_id(ESP32TouchBinarySensor), | ||||
|     vol.Required(CONF_PIN): validate_touch_pad, | ||||
|     vol.Required(CONF_THRESHOLD): cv.uint16_t, | ||||
|     cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_variable_id(ESP32TouchComponent), | ||||
| })) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     hub = None | ||||
|     for hub in get_variable(config[CONF_ESP32_TOUCH_ID]): | ||||
|         yield | ||||
|     touch_pad = TOUCH_PADS[config[CONF_PIN]] | ||||
|     rhs = hub.make_touch_pad(config[CONF_NAME], touch_pad, config[CONF_THRESHOLD]) | ||||
|     binary_sensor.register_binary_sensor(rhs, config) | ||||
|  | ||||
|  | ||||
| BUILD_FLAGS = '-DUSE_ESP32_TOUCH_BINARY_SENSOR' | ||||
							
								
								
									
										68
									
								
								esphome/components/binary_sensor/filter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								esphome/components/binary_sensor/filter.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| #include "filter.h" | ||||
| #include "binary_sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| namespace binary_sensor { | ||||
|  | ||||
| static const char *TAG = "sensor.filter"; | ||||
|  | ||||
| void Filter::output(bool value, bool is_initial) { | ||||
|   if (!this->dedup_.next(value)) | ||||
|     return; | ||||
|  | ||||
|   if (this->next_ == nullptr) { | ||||
|     this->parent_->send_state_internal(value, is_initial); | ||||
|   } else { | ||||
|     this->next_->input(value, is_initial); | ||||
|   } | ||||
| } | ||||
| void Filter::input(bool value, bool is_initial) { | ||||
|   auto b = this->new_value(value, is_initial); | ||||
|   if (b.has_value()) { | ||||
|     this->output(*b, is_initial); | ||||
|   } | ||||
| } | ||||
| DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {} | ||||
| optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) { | ||||
|   if (value) { | ||||
|     this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); | ||||
|     return {}; | ||||
|   } else { | ||||
|     this->cancel_timeout("ON"); | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
| DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {} | ||||
| optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) { | ||||
|   if (!value) { | ||||
|     this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); | ||||
|     return {}; | ||||
|   } else { | ||||
|     this->cancel_timeout("OFF"); | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
| float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||
|  | ||||
| optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; } | ||||
|  | ||||
| LambdaFilter::LambdaFilter(const std::function<optional<bool>(bool)> &f) : f_(f) {} | ||||
|  | ||||
| optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); } | ||||
|  | ||||
| optional<bool> UniqueFilter::new_value(bool value, bool is_initial) { | ||||
|   if (this->last_value_.has_value() && *this->last_value_ == value) { | ||||
|     return {}; | ||||
|   } else { | ||||
|     this->last_value_ = value; | ||||
|     return value; | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace binary_sensor | ||||
|  | ||||
| }  // namespace esphome | ||||
							
								
								
									
										77
									
								
								esphome/components/binary_sensor/filter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/binary_sensor/filter.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| namespace esphome { | ||||
|  | ||||
| namespace binary_sensor { | ||||
|  | ||||
| class BinarySensor; | ||||
|  | ||||
| class Filter { | ||||
|  public: | ||||
|   virtual optional<bool> new_value(bool value, bool is_initial) = 0; | ||||
|  | ||||
|   void input(bool value, bool is_initial); | ||||
|  | ||||
|   void output(bool value, bool is_initial); | ||||
|  | ||||
|  protected: | ||||
|   friend BinarySensor; | ||||
|  | ||||
|   Filter *next_{nullptr}; | ||||
|   BinarySensor *parent_{nullptr}; | ||||
|   Deduplicator<bool> dedup_; | ||||
| }; | ||||
|  | ||||
| class DelayedOnFilter : public Filter, public Component { | ||||
|  public: | ||||
|   explicit DelayedOnFilter(uint32_t delay); | ||||
|  | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t delay_; | ||||
| }; | ||||
|  | ||||
| class DelayedOffFilter : public Filter, public Component { | ||||
|  public: | ||||
|   explicit DelayedOffFilter(uint32_t delay); | ||||
|  | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|  | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|  protected: | ||||
|   uint32_t delay_; | ||||
| }; | ||||
|  | ||||
| class InvertFilter : public Filter { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
| }; | ||||
|  | ||||
| class LambdaFilter : public Filter { | ||||
|  public: | ||||
|   explicit LambdaFilter(const std::function<optional<bool>(bool)> &f); | ||||
|  | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|  | ||||
|  protected: | ||||
|   std::function<optional<bool>(bool)> f_; | ||||
| }; | ||||
|  | ||||
| class UniqueFilter : public Filter { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value, bool is_initial) override; | ||||
|  | ||||
|  protected: | ||||
|   optional<bool> last_value_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace binary_sensor | ||||
|  | ||||
| }  // namespace esphome | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user